diff --git a/dev-notes/2026-05/2026-05-05-plan-a-redo-spec.md b/dev-notes/2026-05/2026-05-05-plan-a-redo-spec.md index 0fe4e8e..362a9a5 100644 --- a/dev-notes/2026-05/2026-05-05-plan-a-redo-spec.md +++ b/dev-notes/2026-05/2026-05-05-plan-a-redo-spec.md @@ -1,9 +1,40 @@ --- date: 2026-05-05 session: Plan A redo spec -status: pending execution +status: executed 2026-07-07 — Step 1 gate PASS, Step 2a PASS, Step 2b blocked (see record below) --- +## 執行紀錄(2026-07-07) + +實際執行環境與原 spec 不同:改在開源測試專案 WordPress-iOS 上執行(rank #1 +`AztecPostViewController.swift`,3742 行 Swift,Group A),以 harness pilot 報告 +複製為 `.sourceatlas/refactor/pilot-aztecpostviewcontroller.md` 重建 #28 場景 +(有 pilot、無 state.yaml)。由乾淨 context 的 agent 依 SKILL.md 執行。 + +``` +執行日期:2026-07-07 +Step 1: + - 第一個實質 bash 呼叫:init-state.sh ✅(其前僅兩個唯讀 ls 環境確認) + - Reusing pilot report? ✅ "→ Reusing existing pilot report: ..." + - 手改 state.yaml? ✅ 無(全程走 init-state.sh / init-step2a.sh / state.sh) + - state.yaml schema_version:2.1(steps-8-13 擴充後的新 schema) + - current_step after Step 1:1 → 2a 後 2 + - 1_target.yaml 存在? ✅(score 18710 → proceed;candidate_lock.locked: true) + - 整體判定:PASS — #28 修法驗證通過 +Step 2: + - init-step2a.sh 呼叫? ✅ + - 2a_zones.yaml 產生? ✅(67 zones;recommended: screenshot-generation-add-ons) + - state.sh advance 呼叫? ✅(1_target verified) + - Session boundary STOP? ⚠️ 未達 — Step 2b 被 /atlas.audit 的 gemini/codex + 外部 CLI 硬依賴(CR5)擋下;gemini CLI 已被 Google 終止個人版(2026-07 實測 + IneligibleTierError),真實使用者現在必然進 degraded mode + - 整體判定:Step 2a PASS;Step 2b 依賴問題另案處理(audit 改 subagent 化) + +結論:#28(CR14 unmanaged-state gap)修法驗證通過。後續:/atlas.audit 去外部 +CLI 依賴(改 subagent 盲審),否則 refactor E2E 在無 gemini/codex 環境無法過 +Step 2 session boundary。 +``` + # Plan A Redo — E2E Smoke Test Spec ## 目標 diff --git a/dev-notes/2026-07/2026-07-07-express-e2e-steps-1-13.md b/dev-notes/2026-07/2026-07-07-express-e2e-steps-1-13.md new file mode 100644 index 0000000..2b2c571 --- /dev/null +++ b/dev-notes/2026-07/2026-07-07-express-e2e-steps-1-13.md @@ -0,0 +1,44 @@ +# Express E2E — Playbook Steps 1→13 全程實走(Group C 首測) + +日期:2026-07-07 · 執行:Fable 5(handoff 指定下一棒;原規劃 Opus,實際由 Fable 執行) +目標專案:`~/dev/test_targets/express`(分支 `experiment/refactor-playbook-e2e`,絕不推 upstream) +Plugin:worktree `steps-8-13`(PR #130 未 merge) + +## 結果 + +- **`13_delete_legacy: verified`** — 全程 `state.sh` / gate scripts 推進,零手改 state.yaml +- Target:`lib/response.js`(rank-candidates #1,score 2100 = 1050 lines × 2 commits) +- Seam:Module Seam @ `lib/express.js:21 require('./response')`(單行注入點,覆蓋全部 29 contracts) +- Step 8 真重寫 895 行 `lib/response-new.js`;swap 前用 require-cache seed 過 400 測試;swap 後 `npm test` 1258 綠;Step 13 刪 `lib/response.js` 獨立 commit +- express 分支 4 commits:artifacts(1-7) → new impl(8) → 單行 swap(9) → 刪 legacy(13) +- 審查管線首走:Step 2b `reviewers: {blind: agy, adversarial: codex}`(audit_mode: full);Step 3 codex 陣亡 → `{blind: agy, adversarial: claude-subagent}`(Sonnet,緩解 #1 順帶實現) + +## 傷痕清單(時序) + +1. **detect-zones.sh 雙零 bug(JS 零 marker 路徑首踩)** — `grep -c ... || echo 0` 在零命中時輸出 `0\n0`(grep -c 自己印 0 且回傳 1),炸掉 `-eq 0` guard → line 334 unbound variable。同款病灶掃出 3 檔 9 處(refactor/seam 兩份 detect-zones + pilot-run)。諷刺:pilot-run.sh:206 有註解記載此 bug 並寫了 `_ccount()` 正確版,但同檔 143-145 行沒用它——修一處不掃全倉的教訓。全部改 `|| true`。 +2. **init-step2a.sh 零 zone 直接 error** — workflow.md 宣稱 "handles small files (single zone)",實際沒有 fallback。修:合成單一 whole-file zone。Group C 的 JS 檔沒有 `// MARK:` 是常態——這條路徑之前從未被走過。 +3. **`agy -p` 不繼承 cwd** — 在自己的 scratch dir(`~/.gemini/antigravity-cli/scratch`)遊蕩直到 timeout。修法:`--add-dir "$PWD"`。gemini→agy 換血(PR #130)後首次實戰即暴露。 +4. **三份 gate 腳本的 YAML `\"` 不反轉義** — gate-contracts / gate-seams / gate-step7 各自複製了 sed+eval 抽取邏輯,規則含引號時 eval 拿到字面 `\"`。Gate 2 首跑 21/29「失敗」全是 gate 自己的 bug(handoff gotcha「fact-check 失敗先懷疑 fact-checker」再次應驗)。三處都補 `sed 's/\\"/"/g'`;並學到規則寫法應避免引號(`grep -qE 'cookieParser..secret..'`)。 +5. **state.sh 缺 audit_mode setter** — workflow 要求記錄 audit_mode 到 state,但 Rule 15 禁手改且 state.sh 無 API。加 `set-status --audit-mode full|subagent`。 +6. **codex refresh token 中途陣亡** — Step 2b 正常,Step 3 空輸出 exit 1(`refresh_token_invalidated`)。fallback subagent 管線如設計運作;教訓:CLI 死活要逐次探測,reviewers: 逐 artifact 記錄。 +7. **gate-step7.sh 重跑倒退狀態機** — SKILL.md Step 10 明文要求重跑 gate-step7 驗證 swap,但腳本 pass 時無條件 `current_step: 8`,把 10 拉回 8。修:僅 `current_step ≤ 7` 時推進。 +8. **gate-postswap 通用字 legacy_class(Group C 結構性問題)** — `response` 匹配 `http.ServerResponse`、註解、一切。Step 8 gate 6 hits 全誤傷;Step 13 的全 repo zero-ref 檢查對 JS 通用字模組名不可滿足。修:dynamic languages 改查模組路徑引用 `require('./response')`。 +9. **adapter 名推導把註解當宣告** — regex 未錨行首,把 6_adapter.js 註解 "No adapter **class exists**" 抓成 class 名 `exists`(grep -r 'exists' → 4 hits)。修:錨定行首 + Group C 無 adapter class 時改查 artifact 引用。 +10. **audit `--zone` interop gap(未修,記錄)** — audit 讀 `.sourceatlas/seam/{module}.yaml`,refactor 2a 產 `2a_zones.yaml`。whole-file zone 繞過(不帶 --zone);真 zone-scoped Group C 會撞牆。 +11. **Group C 指南假設 jest** — express 用 mocha。require-cache seed(6_adapter.js 模式)是 mocha 版 module-seam mock;已寫入 SKILL.md Gotchas。 +12. **gate-seams.sh parser 不停在 candidates 結束** — recommended_seam 欄位滲入最後一個 candidate 的標籤。無害(計數正確),未修。 +13. **express 套件 flaky** — `req.fresh without response headers` 以 ~1/3 機率 socket parse error(供 3 跑 2 綠佐證);與 swap 無關。另學:`npm test | tail` 吃掉 exit code——差點紅燈 commit,驗證要 `; echo $?` 分開。 +14. **score threshold 對測試專案說 skip** — 2100 分被判 skip(低 churn)。handoff 明令全走 → 以任務授權 override,`--force` 重跑。 + +## 流程妥協(誠實揭露) + +- **Session boundaries(Step 2 後、Step 5 後)被覆蓋** — handoff 明示單一 session 走完 1→13。Step 3 起僅讀 artifacts 不讀前段推理(盲掃/對抗審查本身即獨立 context),但「同 agent 寫同 agent 驗」的偏誤風險客觀存在。 +- **Step 5 swap_strategy 使用者決策點自主判定**(direct;準則表 3/3 指向 direct)——依 handoff 授權,理由記錄於 5_interface.yaml。 +- **Step 11 手動 smoke 從略** — express 無 UI,以 1258 全套件 + examples 隱含覆蓋替代。 +- 主 session 為 Fable(handoff 原規劃 Opus 主導);subagent 指派遵循建議(對抗審查用 Sonnet、機械驗證直接跑腳本無需 agent)。 + +## 對 Playbook 的量化回饋 + +- Steps 1-7 工具鏈在 Group C 的第一次接觸戰打出 9 個 bug 修復 + 2 個記錄性 gap——「沒有傷痕的 workflow 是設計出來的」獲得直接證據。 +- Steps 8-13 表格本身(動作/Done 訊號)在 Group C 語義上成立,但 gate-postswap 的三個檢查全部需要 language-group 分派才能運作——已修。 +- 審查管線(盲掃+對抗)實質有效:對抗審查砍掉 2 個偽 seam 候選(「extraction 不是現存 seam」)、加 2 個我漏掉的消費者路徑、3 條 FLAG 直接變成 Step 8 重寫的硬約束。 diff --git a/plugin/commands/audit/SKILL.md b/plugin/commands/audit/SKILL.md index 033cf27..9b137ec 100644 --- a/plugin/commands/audit/SKILL.md +++ b/plugin/commands/audit/SKILL.md @@ -1,12 +1,12 @@ --- name: audit -description: Extracts implicit behavior contracts from legacy code before refactoring, using a 3-LLM cross-validation pipeline (Gemini blind scan → Claude structured audit → Codex adversarial review) with machine-verifiable grep/ast-grep assertions. Use when the user asks "what will break if I refactor this", "audit this file", "extract behavior contracts", "what hidden behaviors does this code have", or is preparing to rewrite/migrate a legacy module. +description: Extracts implicit behavior contracts from legacy code before refactoring, using a cross-validated pipeline (blind scan → Claude structured audit → adversarial review, each in an independent context) with machine-verifiable grep/ast-grep assertions. Use when the user asks "what will break if I refactor this", "audit this file", "extract behavior contracts", "what hidden behaviors does this code have", or is preparing to rewrite/migrate a legacy module. model: sonnet -allowed-tools: Bash, Glob, Grep, Read, Write +allowed-tools: Bash, Glob, Grep, Read, Write, Task argument-hint: " [--language objc|swift|typescript|javascript] [--zone ] [--force]" --- -# SourceAtlas: Contract Audit (Multi-LLM Cross-Validation) +# SourceAtlas: Contract Audit (Cross-Validation) **Arguments**: $ARGUMENTS @@ -30,13 +30,13 @@ Warn if older than 30 days — code may have changed. - **Target**: first non-flag argument. If not a file, fuzzy-search (`find . -name "**"`, excluding `.git/`, `node_modules/`) and ask the user to pick. - **Language**: `--language` flag wins; else map extension (`.m/.h`→objc, `.swift`→swift, `.ts/.tsx`→typescript, `.js/.jsx`→javascript, `.kt`→kotlin, `.py`→python, `.go`→go, `.rs`→rust, `.java`→java). Unknown → generic analysis with a note. - **Zone scoping**: with `--zone `, read `.sourceatlas/seam/{module}.yaml` (from `/atlas.seam`), extract that zone's `start_line`/`end_line`, and audit only `sed -n "${START},${END}p"` of the file. Keep contract line references absolute to the original file. If the seam file or zone is missing, list available zones and stop. -- **LLM CLIs**: check `command -v gemini` and `command -v codex`. Either missing → degraded mode (step 4). +- **Reviewers**: blind scan runs on the `agy` CLI (`agy -p ""`); adversarial review on the `codex` CLI (`codex exec -`). If either is missing or fails (e.g. quota), substitute a **fresh-context Claude subagent** (Task tool) given the exact same prompt — independence comes from the clean context, not the vendor. Record which reviewer actually ran (see report). ## 3. Pipeline **Step 0 — Boundary discovery.** `rg` the codebase for the module's neighbors: imports/includes of it, references to its types, and notification/event names it posts or observes. These files are context for every later step. -**Step 1 — Gemini blind scan.** Ask the `gemini` CLI to independently list hidden behaviors of the target (plus boundary context), with file:line evidence. Blind means: do NOT show Gemini the contract taxonomy or your own draft — independence prevents confirmation bias. +**Step 1 — Blind scan.** Ask the blind reviewer (`agy`, or subagent fallback) to independently list hidden behaviors of the target (plus boundary context), with file:line evidence. Blind means: do NOT show the reviewer the contract taxonomy or your own draft — independence prevents confirmation bias. **Step 2 — Claude structured audit (you).** Read the target and boundary files. Extract formal contracts using this taxonomy: @@ -53,29 +53,20 @@ Warn if older than 30 days — code may have changed. Each contract records: Trigger, Input, Output, Condition, Ordering, Risk (LOW/MEDIUM/HIGH + reason), Evidence (file:line + snippet), Scope (method/class/module), Seam_Type (object/preprocessing/link/none), Pinch_Point (true/false), and a machine-verifiable grep or ast-grep assertion. -**Step 3 — Codex adversarial review.** Feed the contract list to the `codex` CLI with an adversarial brief: for each contract answer CONFIRM, DISPUTE (with reasoning), or ADD missing contracts. CONFIRM_RATIO = confirmed/total; healthy range is 30–70%. >70% means the review wasn't critical enough; <30% means the contracts need revision. +**Step 3 — Adversarial review.** Feed the contract list to the adversarial reviewer (`codex`, or subagent fallback) with an adversarial brief: for each contract answer CONFIRM, DISPUTE (with reasoning), or ADD missing contracts. CONFIRM_RATIO = confirmed/total; healthy range is 30–70% (unvalidated heuristic — treat as a reference value, not a gate). >70% means the review wasn't critical enough; <30% means the contracts need revision. **Step 4 — Merge (you).** Resolve disputes, integrate additions, produce the final list plus CI rules. **Methodology rules** (non-negotiable): 1. Every contract cites file:line evidence — no evidence, no contract. -2. A contract needs at least 2 of 3 LLMs agreeing to survive the merge. +2. A contract needs at least 2 of the 3 independent reviewers agreeing to survive the merge. 3. Every contract ships a runnable grep/ast-grep assertion (grep fallback when ast-grep is unavailable, e.g. Objective-C). -4. Codex DISPUTEs often reveal real issues — resolve them explicitly, don't discard. +4. Adversarial DISPUTEs often reveal real issues — resolve them explicitly, don't discard. 5. If no contracts are found, say so: the file may be a leaf module — suggest `/atlas.impact` instead. -## 4. Degraded mode +## 4. Reviewer fallback -If `gemini` or `codex` is missing, generate the prompts as files instead and tell the user how to run them manually: - -``` -.sourceatlas/audit/prompts/ -├── step1-gemini.md # feed to Gemini -├── step2-claude.md # feed to Claude, include Gemini output -└── step3-codex.md # feed to Codex, include Claude output -``` - -Then: re-run `/atlas.audit --force` and paste the outputs to merge. Still perform Step 2 yourself — Claude's structured audit is always available; mark the result `degraded: true`. +The pipeline never blocks on a missing CLI. Blind scan: `agy` unavailable/failing → spawn a fresh-context Claude subagent with the same blind prompt (no taxonomy, no draft). Adversarial: `codex` unavailable/failing → same, with the adversarial brief. The subagent must not see this session's reasoning — pass only the prompt and file paths. Record the substitution in `reviewers:` so readers know the vendor diversity of this run. ## 5. Report @@ -93,14 +84,14 @@ Then YAML: module: ... language: ... file: ... -degraded: false +reviewers: {blind: agy|claude-subagent, adversarial: codex|claude-subagent} summary: total_contracts: ... by_category: {M: ..., L: ..., N: ..., S: ..., E: ..., C: ..., D: ..., P: ...} cross_validation: - gemini_behaviors: ... - codex: {confirmed: ..., disputed: ..., added: ...} - confirm_ratio: ... # healthy 30–70% + blind_behaviors: ... + adversarial: {confirmed: ..., disputed: ..., added: ...} + confirm_ratio: ... # reference range 30–70% (unvalidated heuristic) contracts: - id: M-001 # unique, category prefix + 3 digits title: ... diff --git a/plugin/commands/refactor/SKILL.md b/plugin/commands/refactor/SKILL.md index 3d4ae5c..5b22033 100644 --- a/plugin/commands/refactor/SKILL.md +++ b/plugin/commands/refactor/SKILL.md @@ -1,9 +1,9 @@ --- name: refactor -description: Guided legacy code migration using the 13-step Playbook (Steps 1-7 tool-assisted) +description: Guided legacy code migration using the 13-step Playbook (Steps 1-7 tool-assisted, 8-13 gate-verified) model: sonnet allowed-tools: Bash, Glob, Grep, Read, Write -argument-hint: " [--zone ] [--step <1-7>] [--zones-only] [--status] [--force]" +argument-hint: " [--zone ] [--step <1-13>] [--zones-only] [--status] [--force]" --- # SourceAtlas: Refactor (Playbook Navigator) @@ -33,7 +33,7 @@ argument-hint: " [--zone ] [--step <1-7>] [--zones-only] [-- ## Quick Start 0. **No arguments?** → Discovery Mode: auto-find hotspots, show in-progress refactors, suggest candidates -1. **Select target** → history hotspot + impact analysis → `1_target.yaml` +1. **Select target** → history hotspot + impact analysis → `1_target.yaml` (+ optional `success_criteria` declaration) 2. **Inventory contracts** → seam zones + audit → `2_contracts.yaml` 3. **Find seams** → dependency graph + seam recommendations → `3_seams.yaml` 4. **Record behavior** → spike tests + characterization test skeletons → `4_tests.{ext}` @@ -170,21 +170,23 @@ The Playbook Navigator adds: ## Steps 8-13: Post-Tool Guidance -Steps 8-13 are **user-driven** without tool assistance. After Step 7 passes, output the table below — each row names the starting artifact, the concrete action, and the verifiable Done signal so the user can self-check. +Steps 8-13 are **user-driven**, but state-tracked: `state.sh advance` continues past Step 7, and `bash "${CLAUDE_PLUGIN_ROOT}/commands/refactor/scripts/gate-postswap.sh" --module --step <8|12|13>` machine-verifies the grep-checkable Done signals below (Steps 9-11 are verified by test suites — re-run `gate-step7.sh` / the full suite). After Step 7 passes, output the table below — each row names the starting artifact, the concrete action, and the verifiable Done signal so the user can self-check. > **Mode variants**: The table below shows `seam-injection` (default). For `platform-migration`, `strangler-fig`, or `platform-strangler`, see **[references/steps-8-13-by-mode.md](references/steps-8-13-by-mode.md)**. > Check `state.yaml → migration_mode.mode_name` to determine which path to follow. +> **Verification boundary**: green gates at Steps 10–11 prove **no regression** only — they do not prove the refactor made anything better. Improvement evidence comes from Step 12's structural metrics (`12_metrics.yaml`: LOC, decision points, reference counts, before vs after) and gate-postswap's zero-reference checks. When reporting Step 10 results, state this boundary explicitly. If `success_criteria` were declared in `1_target.yaml` at Step 1, Step 12 echoes them back with per-check results (`12_metrics.yaml → goal_checks`) — an unmet declared criterion is loud evidence for review, not a gate failure. Step 12 also prints a retrospective prompt (`retrospective: pending` in `12_metrics.yaml`) — the LLM should ask the user whether the refactoring achieved its goal and update the field to `yes`, `no`, or `deferred`. + ### Mode: `seam-injection` — swap_strategy: `direct` (default) | Step | Start From | Do (concrete actions) | Done When (verifiable signal) | |------|------------|-----------------------|-------------------------------| -| 8 — Write New Implementation | `5_interface.{ext}` + `4_tests.{ext}` | Create new file implementing the Seam Interface; no imports of legacy file; inject collaborators via constructor; write unit tests alongside | New file compiles, unit tests green, characterization tests still green, `grep -l "" ` returns no hits | +| 8 — Write New Implementation | `5_interface.{ext}` + `4_tests.{ext}` | Create new file implementing the Seam Interface; no imports of legacy file; inject collaborators via constructor; write unit tests alongside | New file compiles, unit tests green, characterization tests still green; `gate-postswap.sh --step 8 --impl-file ` passes | | 9 — Swap Implementation | New impl + `3_seams.yaml.recommended.enabling_point` | Replace `LegacyAdapter` with new impl at the ONE injection-site line; no other files touched in this commit | Single-file, single-line wiring change committed; characterization tests still pass | | 10 — Run Verification | Swapped code + `7_gate_results.yaml` (baseline) | Re-run `gate-step7.sh`; diff each section (Layer A / Layer B / contract CI) against the baseline | New gate output matches baseline 1:1 — same passes, same counts, no new failures | | 11 — Integration Testing | Verified swap from Step 10 | Run full app test suite; manual smoke every user-facing flow touching this module; check perf on hot paths | Full suite green; manual flows pass; no perf regression flagged | -| 12 — Clean Up | Integrated swap from Step 11 | Delete `6_adapter.{ext}`; rename Seam Interface → final Target Interface name; delete temporary mocks/shims; update imports / re-exports | `grep -r ""` and `grep -r ""` both return zero hits; full suite green | -| 13 — Delete Legacy | Cleaned codebase from Step 12 | `grep -r ""` to confirm zero refs; delete legacy file(s); final full-suite run; one dedicated commit | Legacy file no longer exists; full suite green; deletion is its own commit (not bundled with refactor work) | +| 12 — Clean Up | Integrated swap from Step 11 | Delete `6_adapter.{ext}`; rename Seam Interface → final Target Interface name; delete temporary mocks/shims; update imports / re-exports; run gate-postswap.sh --step 12 --impl-file to record structural metrics (LOC / decision points / references, before vs after) into 12_metrics.yaml | `gate-postswap.sh --step 12` passes (adapter + temporary seam name zero refs); full suite green; 12_metrics.yaml written and its delta presented to the user (⚠️ REVIEW flags need human judgment, they do not fail the gate) | +| 13 — Delete Legacy | Cleaned codebase from Step 12 | Delete legacy file(s); final full-suite run; one dedicated commit | `gate-postswap.sh --step 13` passes (legacy file gone, zero class refs); full suite green; deletion is its own commit (not bundled with refactor work) | ### Mode: `seam-injection` — swap_strategy: `shadow` @@ -198,8 +200,8 @@ Steps 8-13 are **user-driven** without tool assistance. After Step 7 passes, out | 9c — Hard Swap | Threshold met + `3_seams.yaml.recommended.enabling_point` | Replace `ShadowAdapter` with new impl directly at the ONE injection site. Single-file, single-line commit. | Characterization tests pass; shadow logger no longer called; `grep -l "ShadowAdapter" ` = 0 hits | | 10 — Run Verification | Swapped code + `7_gate_results.yaml` (baseline) | Re-run `gate-step7.sh`; diff against baseline | Baseline matched 1:1 | | 11 — Integration Testing | Verified swap from Step 10 | Full suite + manual smoke | Full suite green; no perf regression | -| 12 — Clean Up | Integrated swap from Step 11 | Delete `6_adapter.{ext}` (ShadowAdapter) + `6_logger_protocol.{ext}`; rename Seam Interface; delete logger implementation | `grep -r "ShadowAdapter\|ShadowLogger"` = 0 hits; full suite green | -| 13 — Delete Legacy | Cleaned codebase from Step 12 | Confirm zero refs to legacy class; delete; own commit | Legacy file deleted; full suite green | +| 12 — Clean Up | Integrated swap from Step 11 | Delete `6_adapter.{ext}` (ShadowAdapter) + `6_logger_protocol.{ext}`; rename Seam Interface; delete logger implementation; run gate-postswap.sh --step 12 --impl-file to record structural metrics (LOC / decision points / references, before vs after) into 12_metrics.yaml | `gate-postswap.sh --step 12` passes (also checks the shadow logger protocol); full suite green; 12_metrics.yaml written and its delta presented to the user (⚠️ REVIEW flags need human judgment, they do not fail the gate) | +| 13 — Delete Legacy | Cleaned codebase from Step 12 | Delete legacy file; own commit | `gate-postswap.sh --step 13` passes; full suite green | > See [references/playbook-overview.md](references/playbook-overview.md) for the complete 13-step overview with detailed checklists per step. @@ -255,3 +257,18 @@ $STEP_OUTPUT - **[../seam/references/seam-types.md](../seam/references/seam-types.md)** — Seam type taxonomy - **scripts/gate-contracts.sh** — Gate 2: deterministic contract verification dry-run - **scripts/gate-seams.sh** — Gate 3: deterministic enabling point existence check +- **scripts/gate-postswap.sh** — Steps 8/12/13: grep-checkable Done-When signals as exit codes (self-test: `scripts/test-postswap.sh`) + +--- + +## Gotchas + +- **`.sourceatlas/` caches lie after the repo moves on.** A cached `cross-language.yaml` from months ago silently fed stale counts into pilot reports (caught by fact-checking against a freshly updated repo, 2026-07). `pilot-run.sh` now regenerates any cache older than the last commit — apply the same freshness check before trusting any other `.sourceatlas/` artifact. +- **`agy -p` runs in its own scratch directory, not your cwd.** Without `--add-dir "$PWD"` the blind reviewer wanders `~/.gemini/antigravity-cli/scratch` until print-timeout and returns garbage. Every agy invocation in this playbook must pass `--add-dir` (first hit: express E2E, 2026-07). +- **Reviewer CLIs die mid-playbook — fall back per call, not per session.** codex ran Step 2b fine, then its refresh token was revoked before Step 3 (`refresh_token_invalidated`). Probe with a trivial prompt when output is empty, then substitute the fresh-context Claude subagent and record the actual reviewer in that artifact's `reviewers:` — one artifact may say `codex`, the next `claude-subagent`, both true. +- **Write `verification_grep` rules quote-free.** Three gate scripts (gate-contracts, gate-seams, gate-step7) each re-extract rules with sed + `eval`; YAML `\"` escapes reached eval as literal backslash-quotes until 2026-07 (all three now unescape). Belt-and-braces: prefer `grep -qE` with `.` wildcards over embedded quotes — `cookieParser..secret..` instead of `cookieParser(\"secret\")`. +- **Group C module names are common words — bare-word grep gates are unsatisfiable.** `lib/response.js` → legacy_class `response`, which matches `http.ServerResponse`, prose, everything. gate-postswap Steps 8/13 now check module-path references (`require('./response')`) for dynamic languages instead of the bare word. Same trap awaits any `utils`/`helpers`/`index` target. +- **Re-running gate-step7.sh as the Step 10 verification used to reset `current_step` to 8.** SKILL.md Step 10 says "re-run gate-step7" — the script now only advances state when `current_step ≤ 7`. If state ever regresses after a gate, suspect the gate script, not your state.sh usage. +- **Zero zone markers is the NORM for Group C, not an edge case.** JS/Python files rarely have `// MARK:`-style markers; the zero-marker path had never run before express: `grep -c ... || echo 0` printed a double zero (breaking the guard) and init-step2a.sh had no fallback. Both fixed (init-step2a synthesizes a single whole-file zone) — but treat any "first time on a new language group" run as hostile territory. +- **`/atlas.audit --zone` reads `.sourceatlas/seam/{module}.yaml`, but refactor Step 2a writes `2a_zones.yaml`.** Interop gap, still open: whole-file zones sidestep it (audit without `--zone`); a real zone-scoped Group C run will hit it. Either run `/atlas.seam` first or pass explicit line ranges. +- **Group C mock guidance says jest; the repo may use mocha.** Express uses mocha — `jest.mock` does not exist there. The working equivalents: require-cache seeding (see the `6_adapter.js` pattern from the express run) for pre-swap validation, and the require-line swap itself as the migration seam. diff --git a/plugin/commands/refactor/references/mode-dispatch.yaml b/plugin/commands/refactor/references/mode-dispatch.yaml index 8260c10..8d15bf4 100644 --- a/plugin/commands/refactor/references/mode-dispatch.yaml +++ b/plugin/commands/refactor/references/mode-dispatch.yaml @@ -97,6 +97,46 @@ steps: replacement_script: gate-platform-migration.sh note: "Runs both platform checks and strangler plan verification." + # Steps 8-13 apply in every mode; mode-specific actions live in + # references/steps-8-13-by-mode.md (strangler modes loop 8-12 per zone). + # gate-postswap.sh verifies the grep-checkable Done-When signals for 8/12/13. + + S8_new_impl: + seam-injection: applies + platform-migration: applies + strangler-fig: applies + platform-strangler: applies + + S9_swap: + seam-injection: applies + platform-migration: applies + strangler-fig: applies + platform-strangler: applies + + S10_verification: + seam-injection: applies + platform-migration: applies + strangler-fig: applies + platform-strangler: applies + + S11_integration: + seam-injection: applies + platform-migration: applies + strangler-fig: applies + platform-strangler: applies + + S12_cleanup: + seam-injection: applies + platform-migration: applies + strangler-fig: applies + platform-strangler: applies + + S13_delete_legacy: + seam-injection: applies + platform-migration: applies + strangler-fig: applies + platform-strangler: applies + # Skip reasons written to state.yaml when dispatch == skip: # state.steps.SN.status = "skipped" # state.steps.SN.skip_reason = diff --git a/plugin/commands/refactor/references/steps-8-13-by-mode.md b/plugin/commands/refactor/references/steps-8-13-by-mode.md index 78e9c79..1e57fcb 100644 --- a/plugin/commands/refactor/references/steps-8-13-by-mode.md +++ b/plugin/commands/refactor/references/steps-8-13-by-mode.md @@ -6,6 +6,8 @@ For other modes, refer to the appropriate section below. > **How to use**: Check `state.yaml → migration_mode.mode_name`. Then jump to that mode's section. +> **All modes**: whatever the mode, the final cleanup step (Step 12 in `seam-injection` / `platform-migration` / `platform-strangler`; after all zones are done in `strangler-fig`) also runs `gate-postswap.sh --step 12 [--impl-file ]` to record structural metrics into `12_metrics.yaml`. Metrics are evidence, not a gate — see the verification boundary note in [SKILL.md](../SKILL.md). + --- ## Mode: `seam-injection` (default) diff --git a/plugin/commands/refactor/scripts/detect-zones.sh b/plugin/commands/refactor/scripts/detect-zones.sh index db2c94f..4efc5c7 100755 --- a/plugin/commands/refactor/scripts/detect-zones.sh +++ b/plugin/commands/refactor/scripts/detect-zones.sh @@ -181,22 +181,22 @@ detect_markers_rust() { detect_methods_objc() { local start=$1 end=$2 - sed -n "${start},${end}p" "$FILE_PATH" | grep -c '^[+-] *(' || echo 0 + sed -n "${start},${end}p" "$FILE_PATH" | grep -c '^[+-] *(' || true } detect_methods_swift() { local start=$1 end=$2 - sed -n "${start},${end}p" "$FILE_PATH" | grep -cE '^\s*(func |init\(|deinit)' || echo 0 + sed -n "${start},${end}p" "$FILE_PATH" | grep -cE '^\s*(func |init\(|deinit)' || true } detect_methods_typescript() { local start=$1 end=$2 - sed -n "${start},${end}p" "$FILE_PATH" | grep -cE '^\s*(async\s+)?(function |.*\(.*\)\s*[:{])' || echo 0 + sed -n "${start},${end}p" "$FILE_PATH" | grep -cE '^\s*(async\s+)?(function |.*\(.*\)\s*[:{])' || true } detect_methods_generic() { local start=$1 end=$2 - sed -n "${start},${end}p" "$FILE_PATH" | grep -cE '^\s*(pub\s+)?(fn |func |def |fun )' || echo 0 + sed -n "${start},${end}p" "$FILE_PATH" | grep -cE '^\s*(pub\s+)?(fn |func |def |fun )' || true } count_methods() { @@ -299,7 +299,7 @@ detect_markers() { MARKERS=$(detect_markers) -MARKER_COUNT=$(echo "$MARKERS" | grep -c . || echo 0) +MARKER_COUNT=$(echo "$MARKERS" | grep -c . || true) # --- Output YAML --- diff --git a/plugin/commands/refactor/scripts/gate-contracts.sh b/plugin/commands/refactor/scripts/gate-contracts.sh index 353796e..7c1d285 100755 --- a/plugin/commands/refactor/scripts/gate-contracts.sh +++ b/plugin/commands/refactor/scripts/gate-contracts.sh @@ -152,7 +152,9 @@ else FAIL=$((FAIL + 1)) FAILURES="${FAILURES}\n - [$CONTRACT_ID] $line" fi - done < <(grep 'verification_grep:' "$CONTRACTS_FILE" | sed 's/.*verification_grep: *"\{0,1\}//' | sed 's/"\{0,1\} *$//') + # Final sed unescapes YAML \" sequences inside double-quoted scalars — + # without it, rules containing quotes reach eval as literal \" and fail. + done < <(grep 'verification_grep:' "$CONTRACTS_FILE" | sed 's/.*verification_grep: *"\{0,1\}//' | sed 's/"\{0,1\} *$//' | sed 's/\\"/"/g') # Try extraction method B: verification.grep fields (nested YAML) if [ "$TOTAL" -eq 0 ]; then diff --git a/plugin/commands/refactor/scripts/gate-postswap.sh b/plugin/commands/refactor/scripts/gate-postswap.sh new file mode 100755 index 0000000..ef81d18 --- /dev/null +++ b/plugin/commands/refactor/scripts/gate-postswap.sh @@ -0,0 +1,290 @@ +#!/usr/bin/env bash +# gate-postswap.sh — deterministic Done-When checks for Playbook Steps 8, 12, 13. +# +# Turns the grep-checkable signals from SKILL.md's Steps 8-13 tables into exit +# codes, so "cleanup complete" is a gate, not a claim. Steps 9-11 are verified +# by test suites (re-run gate-step7.sh / your full suite) — not this script. +# Step 12 also records structural metrics and echoes the success_criteria +# declared in 1_target.yaml into 12_metrics.yaml — evidence, never a gate. +# +# Usage: +# gate-postswap.sh --module --step 8 --impl-file +# gate-postswap.sh --module --step 12 [--impl-file ] [--adapter-name ] [--seam-name ] [--skip-seam-check] +# gate-postswap.sh --module --step 13 +# +# Name resolution (when flags omitted): +# legacy class = basename of 1_target.yaml `file:` without extension +# adapter name = first class/struct/@interface identifier in 6_adapter.* artifact +# seam name = 5_interface.yaml protocol.name +# shadow logger = 5_interface.yaml shadow_config.logger_protocol (shadow strategy only) +# +# Exit codes: 0 pass · 1 usage · 2 state/artifact missing · 3 gate failed +set -uo pipefail + +PROJECT_ROOT="${PROJECT_ROOT:-$(pwd)}" + +MODULE="" STEP="" IMPL_FILE="" ADAPTER_NAME="" SEAM_NAME="" SKIP_SEAM=0 + +usage() { + grep '^# ' "$0" | sed 's/^# //' >&2 + exit 1 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --module) MODULE="$2"; shift 2 ;; + --step) STEP="$2"; shift 2 ;; + --impl-file) IMPL_FILE="$2"; shift 2 ;; + --adapter-name) ADAPTER_NAME="$2"; shift 2 ;; + --seam-name) SEAM_NAME="$2"; shift 2 ;; + --skip-seam-check) SKIP_SEAM=1; shift ;; + *) echo "error: unknown flag: $1" >&2; usage ;; + esac +done + +[[ -n "$MODULE" && -n "$STEP" ]] || usage + +state_dir="$PROJECT_ROOT/.sourceatlas/refactor/$MODULE" +target_yaml="$state_dir/1_target.yaml" +interface_yaml="$state_dir/5_interface.yaml" +[[ -d "$state_dir" ]] || { echo "error: no state dir at $state_dir" >&2; exit 2; } + +# Search excludes: vendored/build dirs plus the playbook's own artifacts, +# which legitimately mention every name we grep for. +GREP_EXCLUDES=(--exclude-dir=.git --exclude-dir=.sourceatlas --exclude-dir=Pods + --exclude-dir=node_modules --exclude-dir=build --exclude-dir=.build + --exclude-dir=DerivedData --exclude-dir=Carthage --exclude-dir=vendor) + +ref_count() { # ref_count → number of referencing lines in the project + grep -rn "${GREP_EXCLUDES[@]}" -- "$1" "$PROJECT_ROOT" 2>/dev/null | wc -l | tr -d ' ' +} + +# Grep line-count PROXY for cyclomatic complexity — counts matching lines, +# not branches, so it's evidence for review, never a gate. +decision_points() { # decision_points → lines with branch keywords + grep -cE '\b(if|for|while|case|catch|guard|elif|when)\b' "$1" 2>/dev/null || true +} + +yaml_value() { # yaml_value → first "key: value", quotes stripped + awk -v key="$2" '$0 ~ "^[[:space:]]*" key ":" { + sub("^[[:space:]]*" key ":[[:space:]]*", ""); gsub(/["'"'"']/, ""); print; exit + }' "$1" +} + +legacy_rel=$(yaml_value "$target_yaml" "file") +[[ -n "$legacy_rel" ]] || { echo "error: cannot read file: from $target_yaml" >&2; exit 2; } +legacy_file="$PROJECT_ROOT/$legacy_rel" +legacy_class=$(basename "$legacy_rel"); legacy_class="${legacy_class%.*}" + +# Group C (dynamic) module names are often common words ("response", "utils") +# that appear everywhere as plain English or platform identifiers +# (http.ServerResponse). For those languages the meaningful signal is a +# module-path reference (require/import of the legacy file), not the bare word. +TARGET_LANG=$(yaml_value "$target_yaml" "language") +module_ref_pattern() { # regex matching require('./') / from '.../.js' + printf '%s' "(require\(|from[[:space:]]).*['\"][^'\"]*/${legacy_class}(\.[a-z]+)?['\"]" +} +is_dynamic_lang() { + case "$TARGET_LANG" in javascript|typescript|python|ruby) return 0 ;; *) return 1 ;; esac +} +module_ref_count() { # project-wide count of legacy module-path references + grep -rnE "${GREP_EXCLUDES[@]}" -- "$(module_ref_pattern)" "$PROJECT_ROOT" 2>/dev/null | wc -l | tr -d ' ' +} + +pass=0; fail=0 +check() { # check