diff --git a/README.md b/README.md index d783736..ef2da43 100644 --- a/README.md +++ b/README.md @@ -267,11 +267,21 @@ Không gọi một turn là CASAN-certified nếu không có trace tương ứng ## Client support -| Client | Chat bình thường tự qua CASAN | Bước bắt buộc | +| Client surface | Chat bình thường tự qua CASAN | Bước bắt buộc | |---|---:|---| -| Claude Code CLI/extension | Có | Mở repository dưới dạng trusted project | -| Codex CLI/extension | Có | Mở `/hooks`, review và trust hook hash | -| GitHub Copilot Chat | Không | Cài CASAN VSIX và gửi `@casan ` | +| Claude Code CLI | Có | Mở repository dưới dạng trusted project | +| Claude Code trong VS Code / JetBrains | Có | Dùng project `.claude/settings.json`; mở trusted project | +| Codex desktop app — Local | Có | Mở `/hooks`, review và trust hook hash | +| Codex CLI / IDE extension — Local | Có | Mở `/hooks`, review và trust hook hash | +| Codex Cloud / Web | Chưa | Project hook local không phải cloud enforcement boundary | +| Claude Desktop / claude.ai | Chưa | Không chạy Claude Code project hooks | +| GitHub Copilot Chat | Không tự động | Cài CASAN VSIX và gửi `@casan ` | + +`--client claude` cấu hình **Claude Code runtime**, dùng chung cho CLI, VS Code +extension và JetBrains integration. `--client codex` cấu hình **Codex local +runtime**, dùng chung cho desktop app, CLI và IDE extension. CASAN không tạo +adapter trùng lặp theo từng UI; cùng một project hook và cùng một integrity pin +được dùng trên các local surface. Các cách chọn client: diff --git a/VERSION b/VERSION index 7dea76e..6d7de6e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.1 +1.0.2 diff --git a/docs/casan/CASAN_INSTALL_HYBRID.md b/docs/casan/CASAN_INSTALL_HYBRID.md index 404c9d3..471ea01 100644 --- a/docs/casan/CASAN_INSTALL_HYBRID.md +++ b/docs/casan/CASAN_INSTALL_HYBRID.md @@ -193,12 +193,16 @@ cấp có chủ đích bằng cách chạy lại `casan init --runtime vendored` ### Capability theo client -| Lựa chọn | Trải nghiệm | Bước trust/cài đặt bắt buộc | +| Lựa chọn | Local surface được hỗ trợ | Bước trust/cài đặt bắt buộc | |---|---|---| -| `claude` | Claude Code CLI và extension chính thức dùng project hooks | Mở trusted project; hook chạy tự động | -| `codex` | Codex CLI và IDE extension dùng `.codex/hooks.json` | Mở `/hooks`, review và trust đúng hook hash | +| `claude` | Claude Code CLI, VS Code extension và JetBrains integration dùng chung project settings/hooks | Mở trusted project; hook chạy tự động | +| `codex` | Codex desktop app (Local), CLI và IDE extension (Local) dùng `.codex/hooks.json` | Mở `/hooks`, review và trust đúng hook hash | | `vscode-copilot` | GitHub Copilot Chat qua route **explicit `@casan`** | Cài VSIX do init tạo/cài; dùng `@casan ` | +Không gắn badge project-hook cho Codex Cloud/Web, Claude Desktop hoặc +claude.ai. Các surface đó không chạy local project hook; muốn support phải có +remote/managed integration riêng và một qualification suite riêng. + GitHub Copilot Chat mặc định **không** có public API để CASAN intercept mọi prompt. Chỉ route `@casan` là `casan_owned`; participant detection/built-in Copilot không được quảng bá là certified. diff --git a/docs/spikes/CASAN_SPIKE_20_AGENTIC_CLIENT_HOOKS.md b/docs/spikes/CASAN_SPIKE_20_AGENTIC_CLIENT_HOOKS.md index 7bd802d..fbbc56f 100644 --- a/docs/spikes/CASAN_SPIKE_20_AGENTIC_CLIENT_HOOKS.md +++ b/docs/spikes/CASAN_SPIKE_20_AGENTIC_CLIENT_HOOKS.md @@ -36,7 +36,8 @@ Spike tập trung trả lời bốn câu: | Claude Code managed/custom SDK | Có | Có | Có | SDK ResultMessage có usage/cost tốt hơn | **GO cho assurance cao hơn** | | Codex project hooks | `UserPromptSubmit` | `PreToolUse`, `PostToolUse` | `Stop` | Cần prototype nguồn usage/cost | **GO sau Claude, có điều kiện** | | Codex managed hooks | Có thể pin bằng policy/requirements | Có, theo coverage docs | Có | Như trên | **GO cho enterprise rollout** | -| Claude/Codex VS Code extension | Chưa black-box verify | Chưa verify | Chưa verify | Chưa verify | **CONDITIONAL** | +| Claude Code CLI / VS Code / JetBrains | Dùng chung Claude Code project settings | Dùng chung hook contract | Dùng chung hook contract | Như Claude Code project hooks | **SUPPORTED local surfaces** | +| Codex desktop / CLI / IDE — Local | Dùng chung Codex project hook layer | Dùng chung hook contract | Dùng chung hook contract | Như Codex project hooks | **SUPPORTED local surfaces; trust bắt buộc** | | VS Code Chat Participant | Participant sở hữu request được route tới nó | Participant tự điều phối tools | Participant sở hữu response | Tự ghi được usage do model API trả về | **GO cho `@casan`, không phải global interceptor** | | VS Code participant detection | Auto-route best effort | Như participant nếu được route | Có | Có | **Không đủ để cam kết mọi prompt** | | Generic Copilot built-in chat | Không có API công khai để intercept toàn bộ prompt | Không có CASAN gate chung | Không | Không | **NO-GO cho tuyên bố transparent absolute** | @@ -95,7 +96,7 @@ runtime event/telemetry của Codex. Nếu không có nguồn ổn định, reco **Verdict Codex:** GO có điều kiện, bắt đầu sau khi Claude adapter dùng chung lifecycle contract đã pass. Managed hook là đường khuyến nghị khi tổ chức yêu cầu enforcement. -### 4.3 VS Code, Claude/Codex extension và Copilot +### 4.3 Local UI surfaces và Copilot VS Code Chat Participant API cho phép extension tạo participant như `@casan` và sở hữu toàn bộ prompt được route tới participant đó. Participant detection có thể tự chọn participant @@ -106,14 +107,20 @@ Language Model Chat Provider chỉ xử lý request khi model/provider đó đư không phải interceptor cho mọi model/chat có sẵn. Enterprise policy còn có thể vô hiệu provider kiểu BYOK. -Claude Code hoặc Codex extension có thể tái sử dụng CLI/project hooks, nhưng spike chưa có -black-box evidence trên máy hiện tại. Mỗi extension/version phải được test độc lập trước khi -gắn badge `verified`. +Claude Code settings được tài liệu chính thức xác nhận dùng chung giữa CLI và VS Code +extension; cùng settings precedence áp dụng cho CLI, VS Code và JetBrains. Codex official +surface matrix xác định Hook áp dụng cho desktop app, CLI và IDE extension. Vì vậy CASAN +dùng một adapter theo runtime, không fork adapter theo UI. + +Qualification vẫn phải phân biệt `supported contract` với `tested build`: mỗi release nên +smoke-test ít nhất một build của từng UI family và không suy rộng local project hook sang +Codex Cloud/Web, Claude Desktop hoặc claude.ai. **Verdict VS Code:** +- GO cho Claude Code local trên CLI, VS Code và JetBrains qua shared project settings; +- GO có trust gate cho Codex local trên desktop app, CLI và IDE extension; - GO cho CASAN-owned `@casan` Chat Participant; -- CONDITIONAL cho native Claude/Codex extension tới khi black-box test pass; - NO-GO cho tuyên bố CASAN tự động intercept mọi prompt built-in Copilot bằng public API. ## 5. Kiến trúc prototype được chọn @@ -233,8 +240,9 @@ Hướng này khả thi và nên làm theo đúng thứ tự đã yêu cầu: 1. **Claude Code:** có đủ lifecycle hook để làm MVP; đây là spike/prototype đầu tiên. 2. **Codex:** API hook phù hợp, reuse common bridge; managed hooks đáng ưu tiên cho rollout tổ chức. -3. **VS Code:** xác minh native Claude/Codex extension riêng; bổ sung `@casan` participant - cho đường CASAN-owned. Không hứa intercept toàn bộ Copilot built-in chat. +3. **Local UI surfaces:** dùng chung adapter theo runtime; smoke-test desktop/CLI/IDE trong + release qualification. Bổ sung `@casan` participant cho đường CASAN-owned và không hứa + intercept toàn bộ Copilot built-in chat. Kết quả được gọi là “đi qua CASAN” khi có trace/admission/finalize và certification strength rõ ràng, không chỉ vì project có một file hướng dẫn hoặc hook telemetry. @@ -242,6 +250,10 @@ rõ ràng, không chỉ vì project có một file hướng dẫn hoặc hook te ## 11. Nguồn chính thức - [Claude Code hooks](https://code.claude.com/docs/en/hooks) +- [Claude Code IDE integrations](https://code.claude.com/docs/en/ide-integrations) +- [Claude Code settings](https://code.claude.com/docs/en/settings) +- [Codex hooks](https://learn.chatgpt.com/docs/hooks) +- [Codex surface glossary](https://learn.chatgpt.com/docs/glossary) - [Claude Code status line](https://code.claude.com/docs/en/statusline) - [Claude Agent SDK cost tracking](https://code.claude.com/docs/en/agent-sdk/cost-tracking) - [Codex hooks](https://learn.chatgpt.com/docs/hooks) diff --git a/packages/casan-devkit/casan-init.py b/packages/casan-devkit/casan-init.py index 866f792..2af16fe 100755 --- a/packages/casan-devkit/casan-init.py +++ b/packages/casan-devkit/casan-init.py @@ -60,10 +60,35 @@ VSCODE_EXTENSION_IDS = { PROMPT_MARKER_START = "" PROMPT_MARKER_END = "" CLIENT_LABELS = { - "claude": "Claude Code", - "codex": "Codex", + "claude": "Claude Code (CLI + VS Code + JetBrains)", + "codex": "Codex local (desktop + CLI + IDE)", "vscode-copilot": "VS Code / @casan", } +CLIENT_SURFACES = { + "claude": { + "supported_local": [ + "claude-code-cli", + "claude-code-vscode", + "claude-code-jetbrains", + ], + "not_covered": ["claude-desktop", "claude-web"], + "contract": "shared_claude_code_project_settings", + }, + "codex": { + "supported_local": [ + "codex-desktop-local", + "codex-cli", + "codex-ide-extension-local", + ], + "not_covered": ["codex-cloud", "codex-web"], + "contract": "shared_codex_local_project_hooks", + }, + "vscode-copilot": { + "supported_local": ["vscode-copilot-explicit-at-casan"], + "not_covered": ["github-copilot-built-in-chat"], + "contract": "casan_owned_explicit_route", + }, +} RUNTIME_MODES = ("managed", "vendored") @@ -131,7 +156,7 @@ def _render_init(result): print(_color("1", "Next steps")) print(" 1. Run `casan doctor`") if "codex" in result["clients"]: - print(" 2. In Codex, open `/hooks` and trust this project's hook.") + print(" 2. In a local Codex client, open `/hooks` and trust this project's hook.") def _render_verify(result): @@ -173,6 +198,9 @@ def _render_doctor(result): for client, item in result["client_checks"].items(): label = CLIENT_LABELS.get(client, client) print(" %s %s" % (_mark(item.get("ready", False)), label)) + surfaces = item.get("supported_surfaces") or [] + if surfaces: + print(" Surfaces: %s" % ", ".join(surfaces)) reason = item.get("smoke", {}).get("reason") if reason and not item.get("ready"): print(" %s" % reason) @@ -903,8 +931,8 @@ def select_clients(values, interactive): 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" + " 1) Claude Code (CLI + VS Code + JetBrains)\n" + " 2) Codex local (desktop app + CLI + IDE extension)\n" " 3) GitHub Copilot in VS Code via explicit @casan route\n\n" "Select clients (comma-separated) [1,2]: ") sys.stderr.flush() @@ -1108,7 +1136,7 @@ def cmd_init(args): # ── .casan/config.json ── cfg_dir = os.path.join(target, ".casan") cfg = { - "schema_version": "21.1", + "schema_version": "21.2", "project_id": project, "created_at": now_iso(), "enforcement_mode": args.mode, @@ -1121,6 +1149,13 @@ def cmd_init(args): if "vscode-copilot" in clients else "disabled", "vscode-native-copilot": "unsupported_global_interception", }, + "client_surfaces": { + client: { + "enabled": client in clients, + **surface, + } + for client, surface in CLIENT_SURFACES.items() + }, "harness_version": version, "adoption_model": ( "managed-global" if runtime_mode == "managed" @@ -1511,7 +1546,13 @@ def cmd_doctor(args): line.strip().lower() for line in listed.stdout.splitlines() if line.strip()} for client in clients: - item = {"configured": True} + surface = CLIENT_SURFACES.get(client, {}) + item = { + "configured": True, + "supported_surfaces": list(surface.get("supported_local", [])), + "not_covered_surfaces": list(surface.get("not_covered", [])), + "surface_contract": surface.get("contract"), + } if client == "claude": commands = _commands_in_hooks( os.path.join(target, ".claude", "settings.json")) diff --git a/packages/casan-devkit/templates/claude/README.md b/packages/casan-devkit/templates/claude/README.md index 45f9e47..e6f3e4d 100644 --- a/packages/casan-devkit/templates/claude/README.md +++ b/packages/casan-devkit/templates/claude/README.md @@ -6,6 +6,10 @@ installer does this). It wires the four Claude Code lifecycle hooks bridge so a developer can type prompts normally while every certified turn still carries an H1→H7 trace and an H6 record. +Claude Code shares these project settings across its CLI, official VS Code +extension, and JetBrains integration. Claude Desktop and claude.ai are separate +products and are not covered by this local project-hook integration. + - The hook command resolves the repo root via `$CLAUDE_PROJECT_DIR`, so **no machine-specific path is committed**. - It invokes `.casan/casan-hook.py`. That bootstrap reads project config, diff --git a/packages/casan-devkit/templates/codex/README.md b/packages/casan-devkit/templates/codex/README.md index e06d5ed..34020a0 100644 --- a/packages/casan-devkit/templates/codex/README.md +++ b/packages/casan-devkit/templates/codex/README.md @@ -5,6 +5,11 @@ handler calls `.casan/casan-hook.py`, which resolves and verifies the pinned global harness before dispatching the Codex adapter. No custom `[casan]` TOML keys are required. +This one integration covers the local Codex runtime across the desktop app, +CLI, and IDE extension. It does not claim coverage for Codex Cloud/Web, where a +repo-local process and its `.casan/casan-hook.py` bootstrap are not the local +execution boundary. + Important Codex-specific facts (Spike-20 §4.2): - **Trust review is mandatory.** Codex loads project-local hooks only after the diff --git a/packages/casan-devkit/tests/hybrid-install-tests.sh b/packages/casan-devkit/tests/hybrid-install-tests.sh index aeca053..af75b42 100755 --- a/packages/casan-devkit/tests/hybrid-install-tests.sh +++ b/packages/casan-devkit/tests/hybrid-install-tests.sh @@ -151,6 +151,23 @@ PROJ="$WORK/proj/my-app"; mkdir -p "$PROJ"; echo '{"name":"x"}' > "$PROJ/package for f in .casan/config.json .casan/version.lock .casan/agentic.env .casan/casan-hook.py .claude/settings.json .codex/hooks.json .vscode/extensions.json .specify/.gitignore; do [[ -f "$PROJ/$f" ]] && pass "init wrote $f" || fail "init missing $f" done +python3 - "$PROJ/.casan/config.json" <<'PY' \ + && pass "config declares supported and excluded client surfaces" \ + || fail "config client surface contract is incomplete" +import json +import sys + +d = json.load(open(sys.argv[1], encoding="utf-8")) +assert d["schema_version"] == "21.2" +assert d["client_surfaces"]["claude"]["supported_local"] == [ + "claude-code-cli", "claude-code-vscode", "claude-code-jetbrains", +] +assert d["client_surfaces"]["codex"]["supported_local"] == [ + "codex-desktop-local", "codex-cli", "codex-ide-extension-local", +] +assert "claude-desktop" in d["client_surfaces"]["claude"]["not_covered"] +assert "codex-cloud" in d["client_surfaces"]["codex"]["not_covered"] +PY if [[ -d "$PROJ/packages/casan-harness" ]]; then fail "harness was copied into the repo (should not be)"; else pass "harness NOT copied into repo (hybrid model)"; fi if grep -R -q 'packages/casan-harness/adapters' "$PROJ/.claude/settings.json" "$PROJ/.codex/hooks.json"; then fail "generated hooks still target a repo-local harness"; else pass "generated hooks target the stable project bootstrap"; fi diff --git a/packages/casan-harness/tests/phase-upgrade-compatibility-tests.sh b/packages/casan-harness/tests/phase-upgrade-compatibility-tests.sh index e885995..d7cf6c1 100755 --- a/packages/casan-harness/tests/phase-upgrade-compatibility-tests.sh +++ b/packages/casan-harness/tests/phase-upgrade-compatibility-tests.sh @@ -6,8 +6,8 @@ S="$CASAN_HARNESS_ROOT/scripts/bash/upgrade-compatibility.sh" M="$CASAN_APP_ROOT/infra/production/compatibility-matrix.json" PASS=0; FAIL=0 pass(){ echo "PASS: $1"; PASS=$((PASS+1)); }; fail(){ echo "FAIL: $1"; FAIL=$((FAIL+1)); } -if bash "$S" check --from 1.0.0 --to 1.0.1 --matrix "$M" | grep -q '"decision": "allow"'; then pass "patch upgrade is explicitly allowed"; else fail "patch upgrade denied"; fi -if bash "$S" check --from 1.0.1 --to 1.0.0 --rollback --matrix "$M" | grep -q '"rollback": true'; then pass "approved rollback is explicitly allowed"; else fail "approved rollback denied"; fi -if bash "$S" check --from 1.0.1 --to 2.0.0 --matrix "$M" >/dev/null 2>&1; then fail "unreviewed major upgrade accepted"; else pass "unreviewed major upgrade denied"; fi +if bash "$S" check --from 1.0.1 --to 1.0.2 --matrix "$M" | grep -q '"decision": "allow"'; then pass "patch upgrade is explicitly allowed"; else fail "patch upgrade denied"; fi +if bash "$S" check --from 1.0.2 --to 1.0.1 --rollback --matrix "$M" | grep -q '"rollback": true'; then pass "approved rollback is explicitly allowed"; else fail "approved rollback denied"; fi +if bash "$S" check --from 1.0.2 --to 2.0.0 --matrix "$M" >/dev/null 2>&1; then fail "unreviewed major upgrade accepted"; else pass "unreviewed major upgrade denied"; fi if bash "$S" check --from invalid --to 1.0.0 --matrix "$M" >/dev/null 2>&1; then fail "invalid version accepted"; else pass "invalid version denied"; fi echo "===== UPGRADE COMPATIBILITY SUMMARY: PASS=$PASS FAIL=$FAIL ====="; [[ "$FAIL" -eq 0 ]]