feat: add production init wizard UX
This commit is contained in:
@@ -1,24 +1,45 @@
|
||||
# CASAN
|
||||
|
||||
CASAN là governance harness cho agentic coding. CASAN được cài một lần trên máy
|
||||
developer, sau đó được liên kết vào từng repository bằng project hooks. CASAN
|
||||
không thay thế IDE, coding agent hoặc workflow phát triển của project.
|
||||
CASAN là governance harness cho agentic coding. CASAN bổ sung policy gates,
|
||||
audit trail, evidence và kiểm tra integrity vào lifecycle của coding agent,
|
||||
nhưng không thay thế IDE, model, agent, skill, slash command hay workflow hiện
|
||||
có của repository.
|
||||
|
||||
Project có thể tiếp tục dùng nguyên trạng slash commands, agents, skills, review
|
||||
loops và cấu trúc source hiện hữu. CASAN không áp đặt một pipeline hoặc số bước
|
||||
cố định.
|
||||
Mô hình production mặc định:
|
||||
|
||||
- Cài **DevKit một lần trên máy** để có launcher và lệnh quản trị.
|
||||
- Chạy `casan init` trong từng repository.
|
||||
- Project mới dùng **Core level + Managed runtime + Enforce mode** nếu người
|
||||
dùng không chọn khác.
|
||||
- Project offline, air-gapped hoặc cần tự chứa runtime có thể chọn
|
||||
**Vendored runtime**.
|
||||
|
||||
`casan init` chỉ thêm lớp tích hợp cần thiết. Runtime phát hành không chứa test
|
||||
suite, test scripts, internal CI runners, `level5`, dashboard lab, source docs
|
||||
hay release tooling.
|
||||
|
||||
## Trạng thái sản phẩm
|
||||
|
||||
| Thành phần | Trạng thái | Phạm vi |
|
||||
|---|---|---|
|
||||
| Core — Level 1 | Implemented | H1–H7 harness, hooks, policy gates, audit, evidence và CLI |
|
||||
| DevKit — Level 2 | Implemented | Core + `casan init`, domain-pack và CI template |
|
||||
| Platform — Level 3 | Preview | Control Panel được deploy riêng, không được cài vào project bằng `casan init` |
|
||||
| Enterprise — Level 4 | Chưa phát hành | Installer chủ động từ chối |
|
||||
| DevKit — Level 2 | Implemented | Core + adoption tooling, domain-pack và CI template |
|
||||
| Platform — Level 3 | Preview | Control Panel deploy riêng; `casan init` chỉ áp dụng nền Level 2 |
|
||||
| Enterprise — Level 4 | Chưa phát hành | CLI chủ động từ chối, không giả lập tính năng |
|
||||
|
||||
Đối với repository đã có sẵn vỏ dự án, nên dùng **Level 1 cho project**. Chỉ
|
||||
chọn Level 2 khi project thực sự cần domain-pack và CI template của CASAN.
|
||||
## Bốn quyết định cần phân biệt
|
||||
|
||||
CASAN tách riêng bốn khái niệm để cấu hình rõ ràng:
|
||||
|
||||
| Quyết định | Lựa chọn | Mặc định production |
|
||||
|---|---|---|
|
||||
| Gói cài trên máy | `core`, `devkit`, `platform` | `devkit`, vì `casan init` thuộc DevKit |
|
||||
| Capability của project | `--level core`, `devkit`, `platform` | `core` |
|
||||
| Vị trí Core runtime | `--runtime managed`, `vendored` | `managed` |
|
||||
| Cách thực thi policy | `--mode enforce`, `observe` | `enforce` |
|
||||
|
||||
`--level core` không có nghĩa Core phải nằm trong repository. Level mô tả
|
||||
capability; runtime mô tả vị trí. Đây là hai quyết định độc lập.
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -28,15 +49,15 @@ chọn Level 2 khi project thực sự cần domain-pack và CI template của C
|
||||
- Windows: PowerShell 5.1+, Python 3 và Git for Windows/Git Bash.
|
||||
- Client tương ứng nếu cần: Claude Code, Codex hoặc VS Code.
|
||||
|
||||
### 1. Cài CASAN một lần trên máy
|
||||
### 1. Cài DevKit một lần trên máy
|
||||
|
||||
Từ checkout hoặc release bundle của CASAN:
|
||||
Từ checkout hoặc release bundle đã được duyệt:
|
||||
|
||||
```bash
|
||||
# macOS/Linux
|
||||
sh install.sh --level devkit
|
||||
|
||||
# Nếu launcher chưa nằm trên PATH
|
||||
# Chỉ cần nếu terminal hiện tại chưa nhận launcher
|
||||
export PATH="${CASAN_HOME:-$HOME/.casan}/bin:$PATH"
|
||||
|
||||
casan version
|
||||
@@ -50,74 +71,195 @@ pwsh .\install.ps1
|
||||
casan version
|
||||
```
|
||||
|
||||
Gói global `devkit` được dùng vì nó chứa lệnh adoption `casan init`. Harness
|
||||
được cài mặc định tại:
|
||||
Vị trí mặc định:
|
||||
|
||||
- macOS/Linux: `~/.casan`
|
||||
- Windows: `%LOCALAPPDATA%\casan`
|
||||
|
||||
Bản cài là runtime allowlist tối giản: không mang theo test suites, internal CI
|
||||
runners, thư mục legacy `level5`, Platform dashboard/local lab, source docs hay
|
||||
release tooling. Source repository vẫn giữ tests để kiểm chứng chính CASAN.
|
||||
Có thể đặt `CASAN_HOME` trước khi cài nếu tổ chức dùng một vị trí quản lý khác.
|
||||
`casan init` không hỏi một đường dẫn runtime tùy ý: Managed luôn dùng
|
||||
installation đã resolve từ `CASAN_HOME`; Vendored luôn dùng
|
||||
`.casan/runtime/casan-core` trong project.
|
||||
|
||||
### 2. Adopt vào repository hiện hữu
|
||||
|
||||
CASAN tách rõ hai quyết định:
|
||||
|
||||
| Phạm vi | Ý nghĩa |
|
||||
|---|---|
|
||||
| `--level core` (mặc định) | Capability áp dụng cho project: governance Core, không thêm domain-pack/CI |
|
||||
| `--runtime managed` (mặc định project mới) | Dùng Core global đã pin version/hash; repo nhẹ, nâng cấp tập trung |
|
||||
| `--runtime vendored` | Copy Core production-only vào `.casan/runtime/casan-core`; phù hợp offline/air-gapped/self-contained |
|
||||
### 2. Khởi tạo CASAN trong repository
|
||||
|
||||
```bash
|
||||
cd <project-root>
|
||||
|
||||
# Production mặc định: managed Core
|
||||
casan init --client claude,codex --mode enforce
|
||||
|
||||
casan doctor
|
||||
casan verify-harness
|
||||
casan init
|
||||
```
|
||||
|
||||
`casan init` có menu chọn client khi chạy tương tác. Trong automation nên chỉ
|
||||
định rõ `--client`. Project hiện hữu mặc định dùng Level 1 (`core`); chỉ truyền
|
||||
`--level devkit` khi muốn CASAN bổ sung CI template và domain-pack:
|
||||
Với project mới và terminal tương tác, wizard hỏi hai lựa chọn:
|
||||
|
||||
```text
|
||||
Core runtime placement
|
||||
|
||||
1) Managed (Recommended)
|
||||
Use the shared CASAN installation, pinned by version and hash.
|
||||
Best for developer workstations and managed CI.
|
||||
|
||||
2) Vendored
|
||||
Copy production-only Core into .casan/runtime/casan-core.
|
||||
Best for offline, air-gapped, or self-contained repositories.
|
||||
|
||||
Select runtime [1]:
|
||||
|
||||
Client integrations
|
||||
|
||||
1) Claude Code
|
||||
2) Codex
|
||||
3) GitHub Copilot in VS Code via explicit @casan route
|
||||
|
||||
Select clients (comma-separated) [1,2]:
|
||||
```
|
||||
|
||||
Nhấn Enter để dùng Managed và Claude Code + Codex. Nhập sai sẽ được hướng dẫn
|
||||
chọn lại. Sau khi hoàn tất, output terminal là bản tóm tắt dễ đọc gồm project,
|
||||
level, runtime, mode, client, file thay đổi và next steps.
|
||||
|
||||
Project đã init không bị hỏi lại runtime: CASAN giữ nguyên mode hiện tại. Muốn
|
||||
đổi, truyền rõ `--runtime managed` hoặc `--runtime vendored`.
|
||||
|
||||
### 3. Kiểm tra readiness
|
||||
|
||||
```bash
|
||||
casan init --level core --client claude
|
||||
casan init --level core --client codex
|
||||
casan init --level core --client claude,codex
|
||||
casan init --level core --client vscode-copilot --vscode-install yes
|
||||
casan init --level core --client all
|
||||
casan init --level devkit --client claude,codex
|
||||
|
||||
# Project phải tự chứa Core (offline/air-gapped)
|
||||
casan init --runtime vendored --client claude,codex
|
||||
casan doctor
|
||||
casan verify-harness
|
||||
casan level show
|
||||
```
|
||||
|
||||
Output `init` và `casan level show` luôn hiển thị runtime mode cùng đường dẫn
|
||||
thực tế. Chạy lại `init` giữ mode hiện tại; chỉ đổi khi truyền rõ
|
||||
`--runtime managed` hoặc `--runtime vendored`.
|
||||
Với Codex, mở `/hooks`, review và trust đúng project hook sau lần init hoặc khi
|
||||
bootstrap hash thay đổi.
|
||||
|
||||
Với Codex, sau init phải mở `/hooks`, kiểm tra và trust đúng project hook hash.
|
||||
### 4. Dùng workflow hiện có
|
||||
|
||||
### 3. Dùng project bình thường
|
||||
Tiếp tục dùng chat, agents, skills và slash commands của project như bình
|
||||
thường. Không cần gọi một “CASAN agent” hoặc chạy một pipeline CASAN riêng.
|
||||
|
||||
Không cần gọi CASAN agent hoặc CASAN pipeline. Tiếp tục dùng workflow hiện hữu,
|
||||
ví dụ `/bd:boss`, `/bd:generation`, `/bd:review`, hoặc chat bình thường không
|
||||
chỉ định agent.
|
||||
|
||||
CASAN tự tham gia vào lifecycle của client đã enable:
|
||||
CASAN tham gia tại lifecycle của client đã enable:
|
||||
|
||||
1. `UserPromptSubmit`: admission và quét prompt.
|
||||
2. `PreToolUse`: kiểm tra tool input và chặn side effect không hợp lệ.
|
||||
2. `PreToolUse`: kiểm tra tool input, chặn side effect không hợp lệ.
|
||||
3. `PostToolUse`: ghi evidence của tool result.
|
||||
4. `Stop`: finalize trace, telemetry và trạng thái certification.
|
||||
|
||||
CASAN bridge không gọi model lần thứ hai. Claude Code/Codex vẫn là model
|
||||
CASAN bridge không gọi model lần thứ hai. Claude Code hoặc Codex vẫn là model
|
||||
executor duy nhất.
|
||||
|
||||
## Dùng trong script và CI
|
||||
|
||||
Automation phải khai báo quyết định rõ ràng:
|
||||
|
||||
```bash
|
||||
casan init \
|
||||
--non-interactive \
|
||||
--level core \
|
||||
--runtime managed \
|
||||
--mode enforce \
|
||||
--client claude,codex
|
||||
```
|
||||
|
||||
`--non-interactive` và `--json` không bao giờ chờ input. Nếu không truyền
|
||||
`--runtime`, project mới mặc định Managed; nếu không truyền `--client`, mặc
|
||||
định Claude Code + Codex để tương thích các bản trước.
|
||||
|
||||
Output mặc định dành cho người đọc. Chỉ dùng JSON khi một chương trình cần xử
|
||||
lý dữ liệu:
|
||||
|
||||
```bash
|
||||
casan init --non-interactive --client codex --json
|
||||
casan doctor --json
|
||||
casan verify-harness --json
|
||||
casan level show --json
|
||||
```
|
||||
|
||||
## Chọn runtime
|
||||
|
||||
### Managed — khuyến nghị cho đa số tổ chức
|
||||
|
||||
Managed dùng Core trong global installation và pin version/hash theo từng
|
||||
project.
|
||||
|
||||
Ưu điểm:
|
||||
|
||||
- Repository nhẹ, không nhân bản runtime.
|
||||
- Nâng cấp và rollback tập trung.
|
||||
- Phù hợp workstation và CI runner được quản lý.
|
||||
- Nhiều project có thể dùng chung một CASAN installation.
|
||||
|
||||
Yêu cầu:
|
||||
|
||||
- Máy developer/runner phải cài release CASAN mà project đã pin.
|
||||
- Không thay runtime global tùy tiện; luôn chạy `casan verify-harness`.
|
||||
|
||||
```bash
|
||||
casan init --runtime managed
|
||||
```
|
||||
|
||||
### Vendored — cho môi trường tự chứa
|
||||
|
||||
Vendored copy Core production-only vào
|
||||
`.casan/runtime/casan-core`. Bootstrap và launcher trong project resolve runtime
|
||||
này trước, không âm thầm fallback sang global nếu runtime bị thiếu.
|
||||
|
||||
Ưu điểm:
|
||||
|
||||
- Có thể hoạt động offline hoặc air-gapped.
|
||||
- Runtime đi cùng đúng project.
|
||||
- Phù hợp repository được đóng gói thành một deliverable độc lập.
|
||||
|
||||
Đánh đổi:
|
||||
|
||||
- Repository/artifact lớn hơn.
|
||||
- Mỗi project phải chủ động nhận bản vá và upgrade.
|
||||
- Tổ chức phải quản lý quyền phân phối runtime theo license.
|
||||
|
||||
```bash
|
||||
casan init --runtime vendored
|
||||
```
|
||||
|
||||
### Bảng quyết định
|
||||
|
||||
| Tình huống | Runtime nên dùng |
|
||||
|---|---|
|
||||
| Máy developer và CI do công ty quản lý | Managed |
|
||||
| Nhiều repository dùng chung CASAN | Managed |
|
||||
| Cần rollout/rollback tập trung | Managed |
|
||||
| Offline hoặc air-gapped | Vendored |
|
||||
| Deliverable phải tự chứa toàn bộ Core | Vendored |
|
||||
| Không thể đảm bảo CASAN đã cài trên runner | Vendored |
|
||||
|
||||
## Chọn project level
|
||||
|
||||
| Level | Dùng khi | Nội dung thêm vào project |
|
||||
|---|---|---|
|
||||
| `core` | Mặc định cho repository đã có cấu trúc | Governance Core, bootstrap, hooks và config |
|
||||
| `devkit` | Cần CASAN domain-pack và CI template | Core + `.gitea/workflows/casan-ci.yml` + domain scaffold |
|
||||
| `platform` | Đánh giá Platform preview | Áp dụng nền Level 2; Control Panel vẫn deploy riêng |
|
||||
| `enterprise` | Chưa khả dụng | CLI từ chối |
|
||||
|
||||
```bash
|
||||
casan init --level core
|
||||
casan init --level devkit
|
||||
```
|
||||
|
||||
Level 1 Core không đưa test folders, test scripts, `level5` hoặc source-only
|
||||
components vào project. Chỉ chọn DevKit khi thực sự cần scaffold bổ sung.
|
||||
|
||||
## Enforcement mode
|
||||
|
||||
| Mode | Dùng cho | Hành vi |
|
||||
|---|---|---|
|
||||
| `enforce` | Production | Side effect fail-closed; turn đủ evidence có thể certified |
|
||||
| `observe` | Pilot và thu telemetry | Ghi nhận nhưng không chặn như production; luôn `observed_only` |
|
||||
|
||||
```bash
|
||||
casan init --mode enforce
|
||||
casan init --mode observe
|
||||
```
|
||||
|
||||
Không gọi một turn là CASAN-certified nếu không có trace tương ứng hoặc trace bị
|
||||
đánh dấu `observed_only`/`non_certified`.
|
||||
|
||||
## Client support
|
||||
|
||||
| Client | Chat bình thường tự qua CASAN | Bước bắt buộc |
|
||||
@@ -126,224 +268,300 @@ executor duy nhất.
|
||||
| Codex CLI/extension | Có | Mở `/hooks`, review và trust hook hash |
|
||||
| GitHub Copilot Chat | Không | Cài CASAN VSIX và gửi `@casan <prompt>` |
|
||||
|
||||
Các cách chọn client:
|
||||
|
||||
```bash
|
||||
casan init --client claude
|
||||
casan init --client codex
|
||||
casan init --client claude,codex
|
||||
casan init --client vscode-copilot --vscode-install yes
|
||||
casan init --client all
|
||||
casan init --client none
|
||||
```
|
||||
|
||||
GitHub Copilot không cung cấp public API để extension intercept toàn bộ built-in
|
||||
chat. Chỉ route explicit `@casan` mới là CASAN-owned. Một backend tự gọi LLM API
|
||||
chat. Chỉ route explicit `@casan` mới là CASAN-owned. Backend tự gọi LLM API
|
||||
cũng không đi qua IDE hooks và cần adapter riêng.
|
||||
|
||||
## Kiến trúc runtime
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
U["Developer"] --> C1["Claude Code"]
|
||||
U --> C2["Codex"]
|
||||
U --> C3["VS Code: @casan"]
|
||||
|
||||
C1 --> E1["Project hook events"]
|
||||
C2 --> E1
|
||||
C3 --> E2["CASAN-owned VSIX route"]
|
||||
|
||||
E1 --> B[".casan/casan-hook.py"]
|
||||
E2 --> B
|
||||
B --> V{"Global harness<br/>matches version.lock?"}
|
||||
V -- "No" --> D["Deny or degrade<br/>according to mode"]
|
||||
U["Developer"] --> C["Claude Code / Codex / @casan"]
|
||||
C --> B["Project bootstrap<br/>.casan/casan-hook.py"]
|
||||
B --> R{"Runtime mode"}
|
||||
R -- "Managed" --> GM["CASAN_HOME/current"]
|
||||
R -- "Vendored" --> GV[".casan/runtime/casan-core"]
|
||||
GM --> V{"Version + hash<br/>match version.lock?"}
|
||||
GV --> V
|
||||
V -- "No" --> D["Deny or degrade<br/>according to enforcement mode"]
|
||||
V -- "Yes" --> A["Client adapter"]
|
||||
A --> G["Agentic bridge"]
|
||||
|
||||
subgraph TURN["Per-turn lifecycle"]
|
||||
direction LR
|
||||
L1["Admission<br/>H1 + H4"] --> L2["Pre-tool gate<br/>H2 + H4"]
|
||||
L2 --> L3["Post-tool evidence<br/>H5"]
|
||||
L3 --> L4["Finalize<br/>H3 + H5 + H6 + H7"]
|
||||
end
|
||||
|
||||
G --> L1
|
||||
L4 --> S["Project runtime state<br/>.specify/logs + state"]
|
||||
L4 --> R["Native client result"]
|
||||
G --> L1["Admission"]
|
||||
L1 --> L2["Pre-tool gate"]
|
||||
L2 --> L3["Post-tool evidence"]
|
||||
L3 --> L4["Finalize trace"]
|
||||
L4 --> S[".specify/logs + state"]
|
||||
L4 --> O["Native client result"]
|
||||
```
|
||||
|
||||
## Cấu trúc cài đặt thực tế
|
||||
Runtime resolution fail-closed: một project pin Vendored nhưng thiếu
|
||||
`.casan/runtime/casan-core` sẽ báo lỗi và yêu cầu restore bằng release DevKit đã
|
||||
duyệt, không tự chuyển sang Managed.
|
||||
|
||||
CASAN mặc định dùng managed runtime: policy code nằm ở global installation,
|
||||
project giữ bootstrap, pin và state riêng. Với `--runtime vendored`, cùng Core
|
||||
production-only được đặt tại `.casan/runtime/casan-core`.
|
||||
## Cấu trúc cài đặt
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph M["Developer machine"]
|
||||
H["CASAN_HOME"]
|
||||
CUR["current<br/>symlink hoặc junction"]
|
||||
VER["versions/<version>"]
|
||||
CLI["bin/casan"]
|
||||
HAR["packages/casan-harness"]
|
||||
DEV["packages/casan-devkit"]
|
||||
### Global installation
|
||||
|
||||
H --> CUR --> VER
|
||||
H --> CLI
|
||||
VER --> HAR
|
||||
VER --> DEV
|
||||
end
|
||||
```text
|
||||
CASAN_HOME/
|
||||
├── bin/casan
|
||||
├── current -> versions/<version>
|
||||
└── versions/<version>/
|
||||
├── VERSION
|
||||
├── bin/casan
|
||||
├── packages/casan-harness/
|
||||
└── packages/casan-devkit/
|
||||
```
|
||||
|
||||
INIT["casan init"] --> CFG
|
||||
CLI --> INIT
|
||||
DEV --> INIT
|
||||
Release runtime là allowlist production-only. Tests và release tooling vẫn nằm
|
||||
trong source repository để kiểm chứng CASAN, nhưng không được copy vào
|
||||
installation hoặc project runtime.
|
||||
|
||||
subgraph P["Existing project"]
|
||||
CFG[".casan/<br/>config.json<br/>version.lock<br/>agentic.env<br/>init-manifest.json"]
|
||||
BOOT[".casan/casan-hook.py"]
|
||||
STATE[".specify/<br/>logs/<br/>state/<br/>.gitignore"]
|
||||
CLIENTS["Client config khi được chọn<br/>.claude/settings.json<br/>.codex/hooks.json<br/>.vscode/extensions.json"]
|
||||
L2["Level 2 only<br/>.gitea/workflows/casan-ci.yml<br/>apps/<project-id>/domain/"]
|
||||
OWNED["Project-owned<br/>source, agents, skills,<br/>commands, hooks và CI khác"]
|
||||
end
|
||||
### Project sau `casan init`
|
||||
|
||||
INIT --> BOOT
|
||||
INIT --> STATE
|
||||
INIT --> CLIENTS
|
||||
INIT -. "chỉ khi --level devkit" .-> L2
|
||||
INIT -. "không thay đổi" .-> OWNED
|
||||
CFG --> BOOT
|
||||
HAR -. "runtime policy" .-> BOOT
|
||||
```text
|
||||
<project>/
|
||||
├── .casan/
|
||||
│ ├── config.json
|
||||
│ ├── version.lock
|
||||
│ ├── casan-hook.py
|
||||
│ ├── agentic.env
|
||||
│ ├── init-manifest.json
|
||||
│ └── runtime/casan-core/ # chỉ khi --runtime vendored
|
||||
├── .specify/
|
||||
│ ├── .gitignore
|
||||
│ ├── logs/ # runtime, không commit
|
||||
│ └── state/ # runtime, không commit
|
||||
├── .claude/settings.json # khi enable Claude
|
||||
├── .codex/hooks.json # khi enable Codex
|
||||
├── .vscode/extensions.json # merge theo client
|
||||
└── .gitea/workflows/casan-ci.yml # chỉ Level 2, nếu chưa có
|
||||
```
|
||||
|
||||
### File nào được thay đổi
|
||||
|
||||
| Path | Hành vi |
|
||||
|---|---|
|
||||
| `.casan/config.json` | Lưu project id, mode và danh sách client |
|
||||
| `.casan/version.lock` | Pin version và SHA-256 của global harness |
|
||||
| `.casan/casan-hook.py` | Bootstrap stdlib, verify pin rồi dispatch adapter |
|
||||
| `.casan/config.json` | Project id, level, runtime mode, enforcement mode và clients |
|
||||
| `.casan/version.lock` | Pin version và SHA-256 của Core runtime đã resolve |
|
||||
| `.casan/casan-hook.py` | Stdlib bootstrap, verify pin rồi dispatch adapter |
|
||||
| `.casan/agentic.env` | Compatibility/reference flags; runtime đọc `config.json` |
|
||||
| `.casan/init-manifest.json` | Ghi file đã tạo và backup |
|
||||
| `.specify/logs`, `.specify/state` | Runtime trace, audit và state; không commit |
|
||||
| `.casan/init-manifest.json` | Danh sách file CASAN quản lý, checksum và backup |
|
||||
| `.casan/runtime/casan-core/` | Core production-only; chỉ có ở Vendored |
|
||||
| `.specify/logs`, `.specify/state` | Trace, audit và state runtime; không commit |
|
||||
| `.claude/settings.json` | Merge CASAN handlers khi enable Claude |
|
||||
| `.codex/hooks.json` | Merge CASAN handlers khi enable Codex |
|
||||
| `.vscode/extensions.json` | Merge extension recommendations theo client |
|
||||
| `.gitea/workflows/casan-ci.yml` | Chỉ Level 2, chỉ tạo khi chưa có |
|
||||
| `apps/<project-id>/domain/` | Chỉ Level 2, chỉ bổ sung file còn thiếu |
|
||||
| `.gitea/workflows/casan-ci.yml` | Chỉ Level 2; chỉ tạo khi chưa có |
|
||||
| `apps/<project-id>/domain/` | Chỉ Level 2; chỉ bổ sung file còn thiếu |
|
||||
|
||||
Trước lần thay đổi đầu tiên, init tạo backup `<file>.casan-bak` cho file hiện
|
||||
hữu. `init-manifest.json` ghi lại các file và backup liên quan.
|
||||
Trước lần thay đổi đầu tiên, init tạo `<file>.casan-bak` cho file hiện hữu.
|
||||
`init-manifest.json` giúp re-init và uninstall chỉ quản lý đúng tài sản CASAN.
|
||||
|
||||
### Nội dung luôn được giữ nguyên
|
||||
### Nội dung luôn được bảo toàn
|
||||
|
||||
- Source code và cấu trúc ứng dụng.
|
||||
- `.claude/agents`, `.claude/skills`, `.claude/commands`.
|
||||
- Agents, skills, prompts và instructions trong `.github/`.
|
||||
- Hook và JSON key không thuộc CASAN.
|
||||
- Hook, JSON key và VS Code recommendation không thuộc CASAN.
|
||||
- CI/workflow hiện hữu.
|
||||
- Vendored `packages/casan-harness` của project cũ; chỉ xóa sau khi đã migration
|
||||
toàn bộ CI và scripts sang global harness.
|
||||
- Scaffold file đã được người dùng chỉnh sửa.
|
||||
- Backup `.casan-bak`.
|
||||
|
||||
Nếu target chính là CASAN source hub, init từ chối để tránh self-adoption. Không
|
||||
dùng `--force` trừ khi chủ động muốn kiểm thử trường hợp này.
|
||||
CASAN source hub tự từ chối self-adoption. Không dùng `--force` trừ khi chủ động
|
||||
kiểm thử trường hợp này.
|
||||
|
||||
## Chọn mode
|
||||
## Nên commit gì
|
||||
|
||||
| Mode | Dùng cho | Certification |
|
||||
|---|---|---|
|
||||
| `enforce` | Mặc định production | Side effect fail-closed; turn đủ evidence có thể certified |
|
||||
| `observe` | Pilot và thu telemetry | Không chặn như production; luôn `observed_only` |
|
||||
Thông thường nên commit:
|
||||
|
||||
- `.casan/config.json`
|
||||
- `.casan/version.lock`
|
||||
- `.casan/casan-hook.py`
|
||||
- `.casan/agentic.env`
|
||||
- `.casan/init-manifest.json`
|
||||
- các client config đã merge
|
||||
- Level 2 workflow/domain files sau khi review
|
||||
|
||||
Không commit:
|
||||
|
||||
- `.specify/logs/`
|
||||
- `.specify/state/`
|
||||
- file `.casan-bak`
|
||||
|
||||
Với Managed, không có runtime để commit. Với Vendored, tổ chức phải đưa
|
||||
`.casan/runtime/casan-core` vào cùng deliverable bằng Git hoặc artifact channel
|
||||
đã duyệt; nếu bỏ runtime này, project sẽ fail-closed. Quyết định phân phối phải
|
||||
tuân theo license và policy nội bộ.
|
||||
|
||||
## Cấu hình lại
|
||||
|
||||
Chạy lại init với **toàn bộ danh sách client mong muốn**:
|
||||
|
||||
```bash
|
||||
casan init --level core --client claude,codex --mode enforce
|
||||
# Chỉ giữ Claude
|
||||
casan init --client claude
|
||||
|
||||
# Tắt CASAN IDE integrations nhưng giữ config và evidence
|
||||
casan init --client none
|
||||
|
||||
# Chuyển runtime có chủ đích
|
||||
casan init --runtime vendored
|
||||
casan init --runtime managed
|
||||
```
|
||||
|
||||
Không gọi một turn là CASAN-certified nếu không có trace tương ứng hoặc trace bị
|
||||
đánh dấu `observed_only`/`non_certified`.
|
||||
Client bị bỏ khỏi danh sách sẽ được gỡ CASAN handler; hook và config không thuộc
|
||||
CASAN vẫn được giữ. `--client none` không tự gỡ VS Code extension dùng chung.
|
||||
|
||||
## Kiểm tra, cấu hình lại và nâng cấp
|
||||
## Nâng cấp và rollback
|
||||
|
||||
Kiểm tra project:
|
||||
### Managed
|
||||
|
||||
```bash
|
||||
casan doctor
|
||||
casan verify-harness
|
||||
casan level show
|
||||
```
|
||||
|
||||
- Output mặc định được tối ưu để đọc trực tiếp trong terminal. Thêm `--json`
|
||||
sau command khi cần payload đầy đủ cho CI hoặc script, ví dụ
|
||||
`casan doctor --json`.
|
||||
- `doctor`: kiểm tra config, bootstrap, hook schema, adapter smoke test, VSIX và
|
||||
cảnh báo trust.
|
||||
- `verify-harness`: tính lại live hash và so với project pin; drift trả exit
|
||||
code `3`.
|
||||
- `level show`: hiển thị package level đã cài và target level của project.
|
||||
|
||||
Đổi danh sách client bằng cách chạy lại init với **toàn bộ danh sách mong muốn**.
|
||||
CASAN handler của client bị bỏ khỏi danh sách sẽ được gỡ, còn hook khác được giữ:
|
||||
|
||||
```bash
|
||||
casan init --level core --client claude
|
||||
|
||||
# Tắt toàn bộ IDE integration của CASAN nhưng giữ config/state
|
||||
casan init --level core --client none
|
||||
```
|
||||
|
||||
`--client none` không uninstall VSIX đã cài trên máy; nếu không còn dùng route
|
||||
`@casan`, gỡ extension `fpt-casan.casan-governed-chat` trong VS Code.
|
||||
|
||||
Gỡ CASAN khỏi project:
|
||||
|
||||
```bash
|
||||
# Gỡ project hooks và config CASAN; giữ hook người dùng và runtime evidence
|
||||
casan uninstall
|
||||
|
||||
# Đồng thời xóa .specify/logs và .specify/state
|
||||
casan uninstall --purge
|
||||
|
||||
# Chỉ dùng khi extension dùng chung không còn cần trên máy
|
||||
casan uninstall --remove-vscode-extension
|
||||
```
|
||||
|
||||
`uninstall` xóa workflow CASAN trong `.gitea`, xóa các scaffold file CASAN còn
|
||||
nguyên checksum và tự dọn thư mục cha khi đã rỗng. Workflow/file của project,
|
||||
scaffold file đã chỉnh sửa và `.casan-bak` được giữ lại để tránh mất dữ liệu.
|
||||
Vendored Core trong `.casan/runtime/casan-core` cũng được xóa. Lệnh không mặc
|
||||
định gỡ VS Code extension dùng chung cho các project khác.
|
||||
|
||||
Khi nâng cấp CASAN:
|
||||
|
||||
1. Chạy lại installer từ release đã duyệt.
|
||||
2. Chạy lại `casan init` trong từng project để cập nhật bootstrap và pin.
|
||||
1. Cài release CASAN đã duyệt trên workstation/runner.
|
||||
2. Chạy lại `casan init --runtime managed` trong từng project để cập nhật
|
||||
bootstrap và pin.
|
||||
3. Chạy `casan doctor` và `casan verify-harness`.
|
||||
4. Với Codex, review/trust lại hook nếu hash thay đổi.
|
||||
|
||||
Trong production, không bỏ qua `HARNESS_INTEGRITY_DRIFT`.
|
||||
### Vendored
|
||||
|
||||
## CI
|
||||
1. Cài hoặc giải nén DevKit release đã duyệt trên máy thực hiện upgrade.
|
||||
2. Chạy `casan init --runtime vendored`; CASAN refresh Core production-only
|
||||
trong project.
|
||||
3. Review thay đổi runtime, chạy `doctor` và `verify-harness`.
|
||||
4. Phát hành lại deliverable Vendored.
|
||||
|
||||
Runner phải cài cùng release CASAN mà project đã pin. Gate tối thiểu:
|
||||
Rollback dùng đúng release đã duyệt trước đó, chạy lại init và verify. Không bỏ
|
||||
qua `HARNESS_INTEGRITY_DRIFT` trong production.
|
||||
|
||||
## Uninstall
|
||||
|
||||
```bash
|
||||
# Gỡ hooks, config, CASAN workflow/scaffold còn nguyên checksum và Vendored Core
|
||||
casan uninstall
|
||||
|
||||
# Đồng thời xóa runtime evidence
|
||||
casan uninstall --purge
|
||||
|
||||
# Chỉ khi VSIX dùng chung không còn cần trên máy
|
||||
casan uninstall --remove-vscode-extension
|
||||
```
|
||||
|
||||
`casan uninstall`:
|
||||
|
||||
- gỡ CASAN handlers nhưng giữ handlers của project;
|
||||
- xóa CASAN-owned `.gitea` workflow và dọn thư mục cha nếu đã rỗng;
|
||||
- xóa `.casan/runtime/casan-core` nếu dùng Vendored;
|
||||
- chỉ xóa scaffold file còn đúng checksum;
|
||||
- giữ file người dùng đã sửa và `.casan-bak`;
|
||||
- giữ `.specify/logs` và `.specify/state` trừ khi có `--purge`;
|
||||
- không mặc định gỡ VSIX vì extension có thể được project khác dùng chung.
|
||||
|
||||
Sau uninstall, output liệt kê rõ mục đã xóa và mục được giữ lại.
|
||||
|
||||
## CI production
|
||||
|
||||
Gate tối thiểu:
|
||||
|
||||
```bash
|
||||
casan verify-harness
|
||||
casan gate
|
||||
```
|
||||
|
||||
Managed runner phải cài đúng approved release trước khi chạy gate. Vendored
|
||||
runner phải nhận `.casan/runtime/casan-core` cùng checkout/artifact; global
|
||||
launcher vẫn có thể được dùng, nhưng runtime policy được resolve từ project.
|
||||
|
||||
Level 2 tạo `.gitea/workflows/casan-ci.yml` như một template nếu file chưa tồn
|
||||
tại. Template phải được review theo runner và mô hình cài đặt của tổ chức trước
|
||||
khi enable; CASAN không ghi đè workflow hiện hữu.
|
||||
tại. Luôn review template theo runner, secret model và runtime mode của tổ chức
|
||||
trước khi enable. CASAN không ghi đè workflow hiện hữu.
|
||||
|
||||
Đảm bảo `.specify/logs/` và `.specify/state/` không được commit. Init chỉ tạo
|
||||
`.specify/.gitignore` khi file đó chưa tồn tại.
|
||||
|
||||
## CLI tham khảo
|
||||
|
||||
| Lệnh | Mục đích |
|
||||
|---|---|
|
||||
| `casan init` | Adopt hoặc cấu hình lại CASAN trong project |
|
||||
| `casan uninstall` | Gỡ CASAN-owned project integration an toàn |
|
||||
| `casan doctor` | Kiểm tra config, bootstrap, pin, adapters và client readiness |
|
||||
| `casan verify-harness` | So live Core hash với project pin |
|
||||
| `casan level show` | Hiển thị installed level, project level và runtime |
|
||||
| `casan gate` | Chạy production checks từ project manifest |
|
||||
| `casan verify` | Verify audit chain, tool audit và policy bundle |
|
||||
| `casan prompt verify` | Verify prompt-enforcement contract |
|
||||
| `casan prompt trace <trace-id>` | Verify H1–H7 certification của một turn |
|
||||
| `casan version` | Hiển thị phiên bản |
|
||||
| `casan help` | Hiển thị toàn bộ command |
|
||||
|
||||
Các trạng thái quan trọng:
|
||||
|
||||
- `doctor` trả non-zero khi project chưa sẵn sàng.
|
||||
- `verify-harness` trả exit code `3` khi phát hiện drift.
|
||||
- Cú pháp/giá trị CLI không hợp lệ trả exit code `64`.
|
||||
- JSON là opt-in qua `--json`; lỗi vận hành vẫn được viết ngắn gọn ra stderr.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `casan` không có trên PATH
|
||||
|
||||
Mở terminal mới hoặc thêm `${CASAN_HOME:-$HOME/.casan}/bin` vào `PATH`.
|
||||
|
||||
### Managed báo integrity drift
|
||||
|
||||
Cài đúng release đã pin, chạy lại `casan init --runtime managed`, sau đó
|
||||
`casan verify-harness`. Không sửa `version.lock` thủ công để né kiểm tra.
|
||||
|
||||
### Vendored báo runtime missing
|
||||
|
||||
Từ một approved DevKit installation, chạy:
|
||||
|
||||
```bash
|
||||
casan init --runtime vendored
|
||||
casan verify-harness
|
||||
```
|
||||
|
||||
### Codex chưa chạy hook
|
||||
|
||||
Mở `/hooks`, review project hook và trust hash hiện tại.
|
||||
|
||||
### GitHub Copilot chat không tự qua CASAN
|
||||
|
||||
Dùng explicit route `@casan <prompt>` và kiểm tra
|
||||
`fpt-casan.casan-governed-chat` đã được cài.
|
||||
|
||||
## Cấu trúc source repository CASAN
|
||||
|
||||
| Path | Trách nhiệm |
|
||||
|---|---|
|
||||
| `bin/casan` | CLI entrypoint |
|
||||
| `install.sh`, `install.ps1` | Global installers |
|
||||
| `packages/casan-harness/` | Runtime controls, adapters, policies, evidence và tests |
|
||||
| `packages/casan-devkit/` | Hybrid adoption, project bootstrap và templates |
|
||||
| `packages/casan-harness/` | Runtime controls, adapters, policies, evidence và source tests |
|
||||
| `packages/casan-devkit/` | Project adoption tooling và templates |
|
||||
| `packages/casan-control-panel/` | Platform UI/API preview, deploy riêng |
|
||||
| `packaging/levels.json` | Nguồn sự thật cho package level và maturity |
|
||||
| `infra/` | Local/production deployment references |
|
||||
| `docs/` | Security, operations, packaging và design records |
|
||||
| `apps/` | Demo/validation applications; không phải runtime dependency của `casan init` |
|
||||
|
||||
Source tree có tests để phát triển sản phẩm. Release packaging dùng allowlist để
|
||||
chỉ đưa production runtime cần thiết vào global install hoặc Vendored Core.
|
||||
|
||||
## Tài liệu chi tiết
|
||||
|
||||
- [Hybrid installation và migration](docs/casan/CASAN_INSTALL_HYBRID.md)
|
||||
- [Production installation và migration](docs/casan/CASAN_INSTALL_HYBRID.md)
|
||||
- [Agentic client security boundary](docs/casan/CASAN_AGENTIC_CLIENT_SECURITY.md)
|
||||
- [Windows client setup](docs/casan/CASAN_AGENTIC_CLIENTS_WINDOWS.md)
|
||||
- [Packaging levels](docs/packaging/CASAN_PACKAGING_PLAN.md)
|
||||
|
||||
@@ -72,7 +72,7 @@ casan — CASAN governance harness CLI ($(version))
|
||||
Usage: casan <command> [args]
|
||||
|
||||
Commands:
|
||||
init [--runtime managed|vendored] Adopt CASAN Core into this project
|
||||
init [--runtime managed|vendored] Adopt/reconfigure CASAN (interactive wizard by default)
|
||||
uninstall [--purge] Remove CASAN from this project (preserves user config)
|
||||
doctor [--client ...] Verify configured hooks, pin, adapters, and VS Code route
|
||||
level <show|set 1..4> Show / change the project's packaging level
|
||||
|
||||
@@ -60,17 +60,19 @@ pwsh .\install.ps1
|
||||
|
||||
cd C:\work\my-project
|
||||
|
||||
# Menu chọn Claude / Codex / VS Code Copilot
|
||||
# Project mới: menu chọn Managed/Vendored, sau đó chọn client
|
||||
casan init
|
||||
|
||||
# Hoặc non-interactive, chọn chính xác client
|
||||
casan init --client claude,codex --mode enforce
|
||||
casan init --client vscode-copilot --mode enforce --vscode-install yes
|
||||
# Hoặc non-interactive, chọn chính xác runtime và client
|
||||
casan init --non-interactive --runtime managed --client claude,codex --mode enforce
|
||||
casan init --non-interactive --runtime vendored --client vscode-copilot --mode enforce --vscode-install yes
|
||||
```
|
||||
|
||||
`init` merge CASAN handlers vào config hiện hữu, ghi bootstrap
|
||||
`.casan\casan-hook.py`, pin harness global, và tạo VSIX `@casan` khi Copilot
|
||||
được chọn. Bootstrap tự load `config.json`; không cần source env thủ công.
|
||||
`.casan\casan-hook.py`, pin Core runtime đã resolve, và tạo VSIX `@casan` khi
|
||||
Copilot được chọn. Managed dùng global installation; Vendored copy Core
|
||||
production-only vào `.casan\runtime\casan-core`. Bootstrap tự load
|
||||
`config.json`; không cần source env thủ công.
|
||||
|
||||
> Đường dẫn có dấu cách được xử lý đúng (ví dụ `C:\Users\Nguyen Van A\project`).
|
||||
|
||||
@@ -117,5 +119,5 @@ cho automation tương thích cũ.
|
||||
| Codex không chạy hook | Chưa qua trust review | Mở repo trong Codex, chấp nhận trust; chạy lại doctor |
|
||||
| Turn hiện `observed_only` | Đang ở observe mode hoặc client ngoài allowlist | Chạy lại `casan init --mode enforce --client ...` |
|
||||
| Token/cost là `null` | Client chưa cấp nguồn usage đáng tin | Đúng theo thiết kế — không bịa số; `telemetry_quality=partial` |
|
||||
| `casan doctor` báo integrity/bootstrap missing | Global install hoặc project init chưa đầy đủ | Chạy lại `install.ps1`, rồi `casan init` |
|
||||
| `casan doctor` báo integrity/bootstrap missing | Runtime đã chọn hoặc project init chưa đầy đủ | Managed: chạy lại `install.ps1`; Vendored: chạy `casan init --runtime vendored`; sau đó chạy doctor |
|
||||
| Mọi turn hiện `observed_only` trên Windows | Không có `bash` (thiếu Git Bash) → gate H4/H2 không chạy | Cài Git for Windows hoặc set `CASAN_AGENTIC_BASH`; `doctor` sẽ báo `gates_runnable=true` |
|
||||
|
||||
@@ -64,7 +64,7 @@ cục bộ), `CASAN_DIST_URL` (tải tarball), `CASAN_NO_PATH_LINK=1` (không t
|
||||
|
||||
```bash
|
||||
cd <dự-án-của-bạn>
|
||||
casan init # mặc định project Level 1/core; interactive chọn client
|
||||
casan init # project mới: interactive chọn runtime rồi client
|
||||
# hoặc chọn level áp dụng cho project:
|
||||
casan init --level 1 --project my-app --client claude
|
||||
casan init --level 2 --project my-app --client claude,codex
|
||||
@@ -74,8 +74,14 @@ casan level show # xem level đã cài + level project
|
||||
casan level set 2 # đổi level project (không cần init lại)
|
||||
```
|
||||
|
||||
Project mới chạy trong terminal tương tác sẽ được hỏi vị trí Core trước:
|
||||
`Managed (Recommended)` hoặc `Vendored`, sau đó mới chọn client. Nhấn Enter dùng
|
||||
Managed và Claude+Codex. Nhập sai sẽ được hỏi lại thay vì kết thúc bằng payload
|
||||
khó đọc.
|
||||
|
||||
`init` và `level show` luôn in runtime mode/path. Project mới mặc định
|
||||
`managed`; chạy lại init giữ nguyên mode đã chọn. Chuyển mode phải explicit:
|
||||
`managed`; chạy lại init giữ nguyên mode đã chọn và không hỏi lại runtime.
|
||||
Chuyển mode phải explicit:
|
||||
|
||||
```bash
|
||||
casan init --runtime managed
|
||||
@@ -111,7 +117,7 @@ file đã chỉnh sửa được giữ lại. Thêm `--purge` để xóa cả ru
|
||||
| `.casan/config.json` | project id, enforcement/integration mode, clients |
|
||||
| `.casan/version.lock` | **pin** harness version + hash gate-code |
|
||||
| `.casan/agentic.env` | feature flags bridge Plan-20 |
|
||||
| `.casan/casan-hook.py` | bootstrap stdlib: load config, resolve + verify harness global, dispatch adapter |
|
||||
| `.casan/casan-hook.py` | bootstrap stdlib: load config, resolve + verify Core managed/vendored, dispatch adapter |
|
||||
| `.specify/` | thư mục state runtime (logs/trace/admission) |
|
||||
| `.claude/settings.json` | hook Claude Code (Plan-20) |
|
||||
| `.codex/hooks.json` | hook Codex theo schema hiện hành; cần review/trust bằng `/hooks` |
|
||||
@@ -120,8 +126,16 @@ file đã chỉnh sửa được giữ lại. Thêm `--purge` để xóa cả ru
|
||||
|
||||
Tham số `--client` có thể lặp hoặc comma-separated:
|
||||
`claude`, `codex`, `vscode-copilot`, `all`, `none`. Khi chạy `casan init` trực
|
||||
tiếp trong terminal, CLI hiển thị menu chọn. Trong automation không có TTY,
|
||||
mặc định tương thích ngược là `claude,codex`.
|
||||
tiếp trong terminal, CLI hiển thị menu chọn. Trong automation, dùng
|
||||
`--non-interactive` và khai báo rõ runtime/client:
|
||||
|
||||
```bash
|
||||
casan init --non-interactive --level core --runtime managed \
|
||||
--mode enforce --client claude,codex
|
||||
```
|
||||
|
||||
`--non-interactive`, `--json` hoặc môi trường không có TTY không bao giờ chờ
|
||||
input. Khi không truyền lựa chọn, mặc định là Managed và `claude,codex`.
|
||||
|
||||
Các command dành cho người vận hành (`init`, `doctor`, `verify-harness`,
|
||||
`level show`, `uninstall`) mặc định in bản tóm tắt dễ đọc. Thêm `--json` sau
|
||||
@@ -139,8 +153,9 @@ telemetry-only. `--integration-mode` nhận
|
||||
hiện tại.
|
||||
|
||||
Bootstrap `.casan/casan-hook.py` tự đọc `config.json`; developer không còn phải
|
||||
source `.casan/agentic.env` trước khi mở IDE. Mỗi invocation kiểm tra live hash
|
||||
của harness global so với `version.lock` trước khi chạy adapter.
|
||||
source `.casan/agentic.env` trước khi mở IDE. Mỗi invocation resolve Core theo
|
||||
runtime mode rồi kiểm tra live hash so với `version.lock` trước khi chạy
|
||||
adapter.
|
||||
|
||||
### Project đã có `.claude`, `.github`, agents, skills hoặc CASAN vendored
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ What init writes into the target repo:
|
||||
optional self-contained Core (--runtime vendored)
|
||||
|
||||
`verify` recomputes the resolved harness gate-code hash and compares it to
|
||||
version.lock — the pin+VERIFY half. Drift/tamper of the global harness relative
|
||||
to what the project pinned is caught here (preserves the Plan-16 "gates are
|
||||
trusted code" guarantee even though the harness lives outside the repo).
|
||||
version.lock — the pin+VERIFY half. Drift/tamper of the managed or vendored
|
||||
runtime relative to what the project pinned is caught here (preserves the
|
||||
Plan-16 "gates are trusted code" guarantee).
|
||||
|
||||
stdlib-only. Resolves the harness via CASAN_HARNESS_ROOT (set by the global
|
||||
launcher) or --harness.
|
||||
@@ -64,6 +64,7 @@ CLIENT_LABELS = {
|
||||
"codex": "Codex",
|
||||
"vscode-copilot": "VS Code / @casan",
|
||||
}
|
||||
RUNTIME_MODES = ("managed", "vendored")
|
||||
|
||||
|
||||
def _color(code, text):
|
||||
@@ -806,25 +807,61 @@ def migrate_vendored_prompt_contract(target, clients, backups):
|
||||
}
|
||||
|
||||
|
||||
def select_clients(values, interactive):
|
||||
"""Normalize repeatable/comma-separated selections.
|
||||
def _normalize_runtime_mode(value):
|
||||
aliases = {
|
||||
"1": "managed",
|
||||
"global": "managed",
|
||||
"shared": "managed",
|
||||
"2": "vendored",
|
||||
"local": "vendored",
|
||||
"offline": "vendored",
|
||||
"self-contained": "vendored",
|
||||
"selfcontained": "vendored",
|
||||
}
|
||||
normalized = aliases.get(str(value).strip().lower(),
|
||||
str(value).strip().lower())
|
||||
if normalized not in RUNTIME_MODES:
|
||||
raise ValueError(
|
||||
"unknown runtime %r (choose managed or vendored)" % value)
|
||||
return normalized
|
||||
|
||||
A plain interactive `casan init` presents the requested IDE menu. In
|
||||
non-interactive automation, the historical Claude+Codex default is kept.
|
||||
|
||||
def select_runtime_mode(explicit, previous, interactive):
|
||||
"""Resolve Core placement without surprising existing projects.
|
||||
|
||||
An explicit flag always wins. Re-init keeps a valid existing selection.
|
||||
Only a new interactive adoption opens the placement wizard; automation
|
||||
defaults to managed and never waits for input.
|
||||
"""
|
||||
if not values:
|
||||
if interactive:
|
||||
if explicit:
|
||||
return _normalize_runtime_mode(explicit)
|
||||
if previous in RUNTIME_MODES:
|
||||
return previous
|
||||
if not interactive:
|
||||
return "managed"
|
||||
|
||||
while True:
|
||||
sys.stderr.write(
|
||||
"\nCore runtime placement\n\n"
|
||||
" 1) Managed (Recommended)\n"
|
||||
" Use the shared CASAN installation, pinned by version and hash.\n"
|
||||
" Best for developer workstations and managed CI.\n\n"
|
||||
" 2) Vendored\n"
|
||||
" Copy production-only Core into .casan/runtime/casan-core.\n"
|
||||
" Best for offline, air-gapped, or self-contained repositories.\n\n"
|
||||
"Select runtime [1]: ")
|
||||
sys.stderr.flush()
|
||||
answer = sys.stdin.readline()
|
||||
if not answer or not answer.strip():
|
||||
return "managed"
|
||||
try:
|
||||
return _normalize_runtime_mode(answer)
|
||||
except ValueError:
|
||||
sys.stderr.write(
|
||||
"\nEnable CASAN integrations (comma-separated numbers):\n"
|
||||
" 1) Claude Code (CLI + official VS Code extension)\n"
|
||||
" 2) Codex (CLI + official VS Code extension)\n"
|
||||
" 3) GitHub Copilot in VS Code via explicit @casan route\n"
|
||||
"Selection [1,2]: ")
|
||||
sys.stderr.flush()
|
||||
answer = sys.stdin.readline().strip() or "1,2"
|
||||
values = [answer]
|
||||
else:
|
||||
values = ["claude,codex"]
|
||||
"Invalid selection. Enter 1 for Managed or 2 for Vendored.\n")
|
||||
|
||||
|
||||
def _normalize_clients(values):
|
||||
aliases = {
|
||||
"1": "claude",
|
||||
"2": "codex",
|
||||
@@ -852,6 +889,35 @@ def select_clients(values, interactive):
|
||||
return selected
|
||||
|
||||
|
||||
def select_clients(values, interactive):
|
||||
"""Normalize repeatable/comma-separated selections.
|
||||
|
||||
A plain interactive `casan init` presents the requested IDE menu. In
|
||||
non-interactive automation, the historical Claude+Codex default is kept.
|
||||
"""
|
||||
if values:
|
||||
return _normalize_clients(values)
|
||||
if not interactive:
|
||||
return _normalize_clients(["claude,codex"])
|
||||
|
||||
while True:
|
||||
sys.stderr.write(
|
||||
"\nClient integrations\n\n"
|
||||
" 1) Claude Code (CLI + official VS Code extension)\n"
|
||||
" 2) Codex (CLI + official VS Code extension)\n"
|
||||
" 3) GitHub Copilot in VS Code via explicit @casan route\n\n"
|
||||
"Select clients (comma-separated) [1,2]: ")
|
||||
sys.stderr.flush()
|
||||
answer = sys.stdin.readline()
|
||||
values = [answer.strip() or "1,2"] if answer else ["1,2"]
|
||||
try:
|
||||
return _normalize_clients(values)
|
||||
except ValueError:
|
||||
sys.stderr.write(
|
||||
"Invalid selection. Enter 1, 2, 3, a comma-separated list, "
|
||||
"`all`, or `none`.\n")
|
||||
|
||||
|
||||
def merge_vscode_recommendations(target_file, clients, backups):
|
||||
existed = os.path.exists(target_file)
|
||||
doc = _load_json_or(target_file, {}) if existed else {}
|
||||
@@ -965,20 +1031,18 @@ def cmd_init(args):
|
||||
preview = (lvl == 3) # platform is a separate preview SERVICE; init applies the L2 base
|
||||
apply_devkit = (lvl >= 2)
|
||||
|
||||
try:
|
||||
clients = select_clients(args.client, sys.stdin.isatty() and not args.non_interactive)
|
||||
except ValueError as error:
|
||||
sys.stderr.write("casan init: %s\n" % error)
|
||||
return 64
|
||||
|
||||
previous_config = _load_json_or(
|
||||
os.path.join(target, ".casan", "config.json"), {})
|
||||
previous_runtime_mode = previous_config.get("runtime_mode")
|
||||
runtime_mode = (
|
||||
args.runtime or
|
||||
(previous_runtime_mode
|
||||
if previous_runtime_mode in ("managed", "vendored") else "managed")
|
||||
)
|
||||
interactive = (
|
||||
sys.stdin.isatty() and not args.non_interactive and not args.json)
|
||||
try:
|
||||
runtime_mode = select_runtime_mode(
|
||||
args.runtime, previous_runtime_mode, interactive)
|
||||
clients = select_clients(args.client, interactive)
|
||||
except ValueError as error:
|
||||
sys.stderr.write("casan init: %s\n" % error)
|
||||
return 64
|
||||
runtime_removed = False
|
||||
runtime_files = 0
|
||||
if runtime_mode == "vendored":
|
||||
@@ -1734,10 +1798,25 @@ def cmd_uninstall(args):
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(prog="casan-init", description="CASAN hybrid adoption")
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="casan-init",
|
||||
description="Adopt CASAN governance into an existing project")
|
||||
sub = parser.add_subparsers(dest="cmd")
|
||||
|
||||
pi = sub.add_parser("init", help="adopt CASAN into the current project (config only)")
|
||||
pi = sub.add_parser(
|
||||
"init",
|
||||
help="adopt or reconfigure CASAN in the current project",
|
||||
description=(
|
||||
"Adopt CASAN governance into an existing project. New interactive "
|
||||
"projects are guided through runtime and client selection; existing "
|
||||
"projects preserve their current runtime unless --runtime is set."),
|
||||
epilog=(
|
||||
"examples:\n"
|
||||
" casan init\n"
|
||||
" casan init --runtime managed --client claude,codex\n"
|
||||
" casan init --runtime vendored --client codex\n"
|
||||
" casan init --non-interactive --runtime managed --client none"),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
pi.add_argument("--target", help="project root (default: cwd)")
|
||||
pi.add_argument("--project", help="project id (^[a-z][a-z0-9-]{1,62}$; default: dir name)")
|
||||
pi.add_argument(
|
||||
@@ -1746,7 +1825,8 @@ def main(argv=None):
|
||||
"vscode-copilot, all, none. Interactive init shows a menu."))
|
||||
pi.add_argument(
|
||||
"--non-interactive", action="store_true",
|
||||
help="do not prompt; defaults to the backward-compatible claude,codex set")
|
||||
help=("do not prompt; new projects default to managed runtime and the "
|
||||
"backward-compatible claude,codex client set"))
|
||||
pi.add_argument(
|
||||
"--vscode-install", choices=["auto", "yes", "no"], default="auto",
|
||||
help=("install the local CASAN @casan VSIX when vscode-copilot is selected "
|
||||
|
||||
@@ -41,8 +41,8 @@ assert not (root / "packages/casan-harness/level5").exists()
|
||||
assert not (root / "packages/casan-harness/scripts/bash/ci-harness-gate.sh").exists()
|
||||
for relative in ("docs/casan/CASAN_ADOPTION_WINDOWS.md", "docs/casan/CASAN_PROMPT_ENFORCEMENT.md"):
|
||||
text = (root / relative).read_text(encoding="utf-8")
|
||||
assert "sample-project" in text
|
||||
assert "Sample Project" in text or relative.endswith("CASAN_PROMPT_ENFORCEMENT.md")
|
||||
assert "casan init" in text
|
||||
assert "managed" in text.lower() and "vendored" in text.lower()
|
||||
assert "__PROJECT_ID__" not in text and "__PROJECT_NAME__" not in text
|
||||
PY
|
||||
|
||||
|
||||
@@ -80,11 +80,38 @@ assert registry["projects"] == []
|
||||
PY
|
||||
"$CASAN" version >/dev/null 2>&1 && pass "casan version works via launcher" || fail "casan version failed"
|
||||
python3 - "$CASAN_HOME/current/packages/casan-devkit/casan-init.py" <<'PY' \
|
||||
&& pass "client selector accepts menu numbers, aliases, repeats, and all" \
|
||||
|| fail "client selector normalization failed"
|
||||
import importlib.util,sys
|
||||
&& pass "init wizard resolves runtime and client choices safely" \
|
||||
|| fail "init wizard selection failed"
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
import sys
|
||||
|
||||
spec=importlib.util.spec_from_file_location("casan_init",sys.argv[1])
|
||||
m=importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
|
||||
|
||||
# Explicit flags and existing project configuration never open a prompt.
|
||||
assert m.select_runtime_mode("vendored",None,True)=="vendored"
|
||||
assert m.select_runtime_mode(None,"vendored",True)=="vendored"
|
||||
assert m.select_runtime_mode(None,None,False)=="managed"
|
||||
|
||||
# Interactive defaults, aliases, validation, and retry behavior.
|
||||
old_stdin=m.sys.stdin
|
||||
try:
|
||||
m.sys.stdin=io.StringIO("\n")
|
||||
with contextlib.redirect_stderr(io.StringIO()):
|
||||
assert m.select_runtime_mode(None,None,True)=="managed"
|
||||
m.sys.stdin=io.StringIO("9\n2\n")
|
||||
with contextlib.redirect_stderr(io.StringIO()) as errors:
|
||||
assert m.select_runtime_mode(None,None,True)=="vendored"
|
||||
assert "Invalid selection" in errors.getvalue()
|
||||
m.sys.stdin=io.StringIO("bogus\n3\n")
|
||||
with contextlib.redirect_stderr(io.StringIO()) as errors:
|
||||
assert m.select_clients(None,True)==["vscode-copilot"]
|
||||
assert "Invalid selection" in errors.getvalue()
|
||||
finally:
|
||||
m.sys.stdin=old_stdin
|
||||
|
||||
assert m.select_clients(["1,3"],False)==["claude","vscode-copilot"]
|
||||
assert m.select_clients(["codex","copilot"],False)==["codex","vscode-copilot"]
|
||||
assert m.select_clients(["all"],False)==["claude","codex","vscode-copilot"]
|
||||
|
||||
Reference in New Issue
Block a user