@mmerterden/multi-agent-pipeline 12.5.0 → 12.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +1 -1
  3. package/docs/features.md +20 -0
  4. package/index.js +7 -1
  5. package/install/_dev-only-files.mjs +1 -0
  6. package/package.json +4 -3
  7. package/pipeline/agents/security-auditor.md +1 -1
  8. package/pipeline/commands/archive-guard.md +5 -5
  9. package/pipeline/commands/multi-agent/SKILL.md +2 -0
  10. package/pipeline/commands/multi-agent/design-check/SKILL.md +287 -0
  11. package/pipeline/commands/multi-agent/help/SKILL.md +45 -5
  12. package/pipeline/commands/multi-agent/refactor/SKILL.md +92 -12
  13. package/pipeline/commands/multi-agent/review/SKILL.md +1 -1
  14. package/pipeline/commands/multi-agent/sync/SKILL.md +119 -12
  15. package/pipeline/commands/multi-agent/test/SKILL.md +1 -1
  16. package/pipeline/commands/sim-test.md +5 -5
  17. package/pipeline/lib/credential-store.sh +32 -0
  18. package/pipeline/multi-agent-refs/cross-cli-contract.md +3 -3
  19. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +2 -2
  20. package/pipeline/multi-agent-refs/phases/phase-5-test.md +4 -4
  21. package/pipeline/preferences-template.json +18 -1
  22. package/pipeline/schemas/agent-state.schema.json +90 -0
  23. package/pipeline/schemas/design-check-config.schema.json +162 -0
  24. package/pipeline/schemas/migrations/state-2.0.0-to-2.1.0.mjs +30 -12
  25. package/pipeline/schemas/prefs.schema.json +161 -5
  26. package/pipeline/scripts/README.md +7 -5
  27. package/pipeline/scripts/classify-plan-safety.mjs +8 -3
  28. package/pipeline/scripts/cost-budget-check.mjs +9 -5
  29. package/pipeline/scripts/fixtures/install-layout.tsv +6 -6
  30. package/pipeline/scripts/learnings-ledger.mjs +18 -0
  31. package/pipeline/scripts/lint-mcp-refs.mjs +207 -0
  32. package/pipeline/scripts/memory-load.sh +5 -1
  33. package/pipeline/scripts/render-work-summary.sh +4 -1
  34. package/pipeline/scripts/smoke-command-inventory.sh +81 -0
  35. package/pipeline/scripts/smoke-commands-skills-parity.sh +1 -1
  36. package/pipeline/scripts/smoke-compliance-skills.sh +4 -4
  37. package/pipeline/scripts/smoke-cross-cli-behavior.sh +12 -2
  38. package/pipeline/scripts/smoke-generate-issue.sh +6 -5
  39. package/pipeline/scripts/smoke-per-repo-memory.sh +2 -2
  40. package/pipeline/scripts/smoke-review-readiness.sh +3 -2
  41. package/pipeline/scripts/smoke-schema-validation.sh +19 -5
  42. package/pipeline/scripts/smoke-shadow-git.sh +4 -2
  43. package/pipeline/scripts/triage-memory.mjs +18 -0
  44. package/pipeline/scripts/uninstall.mjs +1 -1
  45. package/pipeline/skills/.skill-manifest.json +24 -8
  46. package/pipeline/skills/.skills-index.json +39 -3
  47. package/pipeline/skills/shared/README.md +10 -6
  48. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +10 -8
  49. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +248 -0
  50. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +22 -0
  51. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +67 -11
  52. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +1 -1
  53. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +51 -9
  54. package/pipeline/skills/shared/core/multi-agent-test/SKILL.md +1 -1
  55. package/pipeline/skills/skills-index.md +8 -4
package/CHANGELOG.md CHANGED
@@ -14,6 +14,136 @@ Internal file-layout changes that don't affect the slash-command surface are sti
14
14
 
15
15
  ---
16
16
 
17
+ ## [Unreleased]
18
+
19
+ ## [12.6.0] - 2026-07-25
20
+
21
+ Two threads. The design-check command gains a scenario inventory, a coverage
22
+ gate and MCP-currency gating, and the refactor/sync pair learns to research,
23
+ audit and ship the companion dev-toolkit MCP server. Alongside that, a defect
24
+ sweep found eight gates and features that exited 0 while doing nothing - six
25
+ of them guarded by a smoke test whose fixture had the wrong shape, so the suite
26
+ stayed green the whole time.
27
+
28
+ Requires `@mmerterden/dev-toolkit-mcp` >= v2.9.0 for the App Store audit path.
29
+
30
+ ### Fixed
31
+
32
+ - **Eight gates and features that reported success while doing nothing.** Each
33
+ exited 0 without doing its job, and in six cases the guarding smoke test
34
+ hand-wrote a fixture with the wrong shape, so the suite stayed green.
35
+ `npx @mmerterden/multi-agent-pipeline uninstall` was a silent no-op (the
36
+ `isMainModule` guard compared `import.meta.url` to `argv[1]`, which is
37
+ `index.js` under the bin dispatcher). `cost-budget-check.mjs` probed
38
+ `.worktrees/<id>/phase-tracker.json` while `phase-tracker.sh` writes
39
+ `~/.claude/logs/multi-agent/<id>/tracker-state.json`, so the cost ceiling
40
+ could never fire; it also now prices `tokens_cached`. `classify-plan-safety.mjs`
41
+ required `score >= 50` while its heaviest rule is 35, so no single heavy
42
+ signal could trip the autopilot pause its own contract promises.
43
+ `triage-memory.mjs` and `learnings-ledger.mjs` derived the repo slug from
44
+ `--show-toplevel`, which inside a worktree is the task id, sharding the
45
+ per-repo store one directory per task; they now use `--git-common-dir`.
46
+ `state-2.0.0-to-2.1.0.mjs` emitted keys `agent-state.schema.json` rejects
47
+ under `additionalProperties: false`. `memory-load.sh` read `preferences.json`
48
+ while the installer only writes `multi-agent-preferences.json`, so per-repo
49
+ memory was dead in production. `smoke-cross-cli-behavior.sh` had unquoted
50
+ command substitution (SC2046) that made two gates pass while violations were
51
+ present. The CI leak gate in all three workflows grepped 5 of the 23
52
+ forbidden patterns and was `release.yml`'s only PII gate; all three now call
53
+ `smoke-personal-data.sh --root`, keeping one pattern list.
54
+ - **shellcheck moves from `error` to `warning` severity.** `error` returns zero
55
+ findings across all 175 scripts, so the gate could not catch this repo's
56
+ actual defect class - SC2046 is a warning. Noisy codes are excluded with a
57
+ per-code rationale. Four real findings surfaced and are fixed, including two
58
+ unguarded `cd` calls in `smoke-shadow-git.sh`, one preceding a relative
59
+ `rm -rf`.
60
+ - **`credential-store.sh` resolves logical keys through
61
+ `prefs.global.keychainMapping`.** `get github` searched the backend for a
62
+ credential literally named "github" and returned empty with exit 1 -
63
+ indistinguishable from "no such credential" - because the entry is named by
64
+ the mapping. Applied in `get`/`set`/`delete`, falling back to the logical key
65
+ when no mapping exists.
66
+
67
+ ### Changed
68
+
69
+ - **`ios_app_store_audit` catalog grows to 18 rules**, requiring
70
+ `@mmerterden/dev-toolkit-mcp` >= v2.9.0. Adds `sdk-floor` (ITMS-90725, the
71
+ iOS 26 / Xcode 26 build floor in force since 2026-04-28). `embedded-sdk`'s
72
+ missing framework privacy manifest moves from a `5.1.1` WARNING to
73
+ `ITMS-91061` ERROR, an enforced rejection since 2025-02-12, so archives that
74
+ previously passed with a warning now fail. The rule count and the declared
75
+ minimum are updated across all nine files that asserted them.
76
+
77
+ ### Added
78
+
79
+ - **`/multi-agent:refactor` band E (Step 0c)**: the companion dev-toolkit MCP
80
+ server is now in scope. The step researches current MCP practice (protocol
81
+ revisions and SDK releases, host-client conventions, peer servers, the
82
+ platform tooling it wraps, field practice) and audits the toolkit repo
83
+ against it: syntax, stdout hygiene (stdout carries the JSON-RPC frames),
84
+ advertised tool counts vs reality, `files[]` coverage, dependency freshness.
85
+ Findings land in the merged Step 4 plan as band-E items, applied in that repo
86
+ and shipped by sync. New focus filter: `/multi-agent:refactor dev-toolkit`.
87
+ - **`/multi-agent:sync` Step 3d**: ships the dev-toolkit MCP server when it
88
+ moved (dirty tree, unpushed commits, or an untagged version). Seven ship
89
+ gates run before anything leaves the machine, including a `tools/list`
90
+ stdio handshake that must answer with a non-zero tool count, an
91
+ advertised-count match against README and `package.json`, an
92
+ `npm pack --dry-run` check that every runtime `tools/*/` directory is inside
93
+ `files[]`, and a version-contract check against the minimums pipeline skills
94
+ declare. Publish goes to the registry from that repo's `publishConfig`
95
+ through a throwaway `--userconfig` built from the `npm` logical Keychain key.
96
+ Outside autopilot / `release` it asks first; `/multi-agent:sync dev-toolkit`
97
+ runs only this step.
98
+ - **`global.devToolkit` preference** (schema + template): `enabled`, `label`,
99
+ `localPath`, `mcpServerName`, `packageName`, `registry`, `repoUrl`. Both
100
+ commands fall back to auto-detecting the repo from the `mcpServers`
101
+ registration when `localPath` is absent, and skip silently when nothing
102
+ resolves. No path is ever hardcoded.
103
+
104
+ - **`smoke-command-inventory.sh`**: the command count is now DERIVED from
105
+ `pipeline/commands/multi-agent/*/` and the prose must agree with the tree
106
+ (contract header, both sync surfaces, every command listed on all three, and a
107
+ `shared/core` skill counterpart per command). `smoke-generate-issue.sh` and
108
+ `smoke-review-readiness.sh` stopped asserting the literal `41` and now compare
109
+ against the derived count too.
110
+ - **`lint-mcp-refs.mjs`**: every `mcp__<server>__<tool>` reference in
111
+ `commands/`, `skills/` and `multi-agent-refs/` must name a known registered
112
+ MCP server, and when the companion toolkit resolves locally the referenced
113
+ `dev-toolkit` tools are checked against its live `tools/list`. Wired into
114
+ `npm test` and `npm run test:quick`.
115
+ - **`validate-prefs.mjs`**: validates `preferences-template.json` against
116
+ `prefs.schema.json` (hard failure) and the local preferences file (advisory,
117
+ `--strict-live` to enforce). Dev-only (ajv), excluded from the published
118
+ package so the runtime stays zero-dep.
119
+ - `design-check` is registered in the command inventory: contract list +
120
+ category row, both sync inventories, and the Copilot mirror. Inventory is 42
121
+ commands.
122
+
123
+ ### Fixed
124
+
125
+ - **Every `mcp__dev_toolkit__*` reference is now `mcp__dev-toolkit__*`** (55
126
+ lines across 10 files). A host composes MCP tool names as
127
+ `mcp__<registered-server-name>__<tool>` and the server registers as
128
+ `dev-toolkit`, so the snake_case form named tools that do not exist. The worst
129
+ case was silent: `design-check`'s `allowed-tools` allowlist matched nothing, so
130
+ the skill was denied the tools it needs, and `smoke-compliance-skills.sh`
131
+ asserted the wrong spelling and kept the gate green.
132
+ - `prefs.schema.json` accepted neither the template it describes nor a real
133
+ preferences file. Now declared: `global.modelFallback`, the `_*Template`
134
+ documentation blocks, nullable `defaultJiraKey`, `keychainMapping.figma_pat` /
135
+ `figma_user` / `claude_oauth_token` / `claude_oauth_token_fallback` (all four
136
+ resolved by shipped flows), project-level `componentDevWorkflow`, and a
137
+ `defaultReviewers` ceiling that fits real reviewer groups (10 -> 40).
138
+ - `global.derivedSkillSources` is now declared in `prefs.schema.json`. It was
139
+ used by refactor Step 0b and shipped in the preferences template, but the
140
+ strict (`additionalProperties: false`) global block rejected it.
141
+ - `/multi-agent:sync` Step 3d prefers the toolkit repo's own gate script
142
+ (`npm run gates`) over the inline gate list, so the definition lives in the
143
+ repo being shipped and the two cannot drift apart.
144
+
145
+ ---
146
+
17
147
  ## [12.5.0] - 2026-07-24
18
148
 
19
149
  Repo learning loop: repeated runs on the same repo produce better and cheaper
package/README.md CHANGED
@@ -64,7 +64,7 @@ The discipline behind all of this — bounded loops, evidence gates, token-budge
64
64
  | Local | `/multi-agent:local "task"` | Full pipeline, current branch (no worktree) |
65
65
  | Finish | `/multi-agent:finish` | Run the review→test→commit→report tail over local work |
66
66
 
67
- Helpers: `setup`, `status`, `resume #N`, `review`, `test`, `channels`, `stack`, `update`, `sync`, `refactor`, `jira`, `issue`, `analysis`, `create-jira`. Full list: `/multi-agent:help`.
67
+ Helpers: `setup`, `status`, `resume #N`, `review`, `test`, `channels`, `stack`, `update`, `sync`, `refactor`, `jira`, `issue`, `analysis`, `create-jira`, `save`, `routines`, `forget`. Full list: `/multi-agent:help`.
68
68
 
69
69
  ## Stacks
70
70
 
package/docs/features.md CHANGED
@@ -249,6 +249,26 @@ Pipeline learns behavioral signals (feedback corrections, project constraints, e
249
249
 
250
250
  **What does NOT go in memory**: architecture, code patterns, build gotchas, design decisions — those belong in the knowledge base.
251
251
 
252
+ ### Lesson Diagnosis (Reflexion)
253
+
254
+ Phase 4's lesson-memory loop records the causal root cause of each fix (`--diagnosis`), not just the outcome: the verbal "why" that prevents recurrence (Reflexion). `learnings-ledger.mjs brief` renders it as `(why: ...)` back into Phase 1 + triage on the next run, so the reason re-enters the loop, not only the symptom.
255
+
256
+ ### Corpus Freshness Gate
257
+
258
+ Each triage-corpus row is stamped with the file's git `file_sha`; a query annotates `stale=true` when the file changed since the lesson was recorded, so lessons about code that has since moved on stop resurfacing.
259
+
260
+ ### Learning Curve
261
+
262
+ `learning-curve.mjs` renders a time-bucketed trend over `metrics.jsonl` (first-pass clean rate, review cycles, rework per task, tokens per task, cache ratio) so a repo's runs can be shown getting better and cheaper over time. Flags: `--bucket=<days>`, `--since`, `--json`, `--markdown`.
263
+
264
+ ## User-Defined Routines
265
+
266
+ Turn a recurring, project-specific job into a first-class `/multi-agent:<name>` command.
267
+
268
+ - **`/multi-agent:save`** distills candidate routines from the work just done this session (and named procedures in `~/.claude/CLAUDE.md`), offers them in a multi-select picker (pick one, combine several into one, or free-text a new one), and registers the chosen routine.
269
+ - **`/multi-agent:routines`** lists saved routines (in `outputLanguage`); **`/multi-agent:forget`** removes one (guarded: never touches a shipped command).
270
+ - Backed by `routine-registry.mjs`. Saved routines are `local-only: true` command dirs + a `prefs.global.routines` entry: preserved across `/multi-agent:update` by install snapshot/restore, never synced to the public repo, and never counted in the command inventory.
271
+
252
272
  ## Integrations
253
273
 
254
274
  ### Figma / Component Generation (dispatched to marketplace plugins)
package/index.js CHANGED
@@ -36,7 +36,13 @@ if (command === "--version" || command === "-v" || command === "version") {
36
36
  } else if (!command || command === "install") {
37
37
  await import(pathToFileURL(join(__dirname, "install.js")).href);
38
38
  } else if (command === "uninstall" || command === "delete") {
39
- await import(pathToFileURL(join(__dirname, "pipeline", "scripts", "uninstall.mjs")).href);
39
+ const { main } = await import(
40
+ pathToFileURL(join(__dirname, "pipeline", "scripts", "uninstall.mjs")).href
41
+ );
42
+ await main().catch((e) => {
43
+ console.error("uninstall failed:", e.message);
44
+ process.exit(1);
45
+ });
40
46
  } else if (command === "help") {
41
47
  console.log(`
42
48
  multi-agent-pipeline — 8-phase AI development pipeline
@@ -13,5 +13,6 @@ export const DEV_ONLY_SCRIPTS = Object.freeze([
13
13
  "smoke-figma-config-schema.sh",
14
14
  "smoke-personal-data.sh", // scanner contains the patterns it detects
15
15
  "smoke-install-leak-gate.sh", // same — scanner shadows the install pipeline
16
+ "validate-prefs.mjs", // needs ajv (devDependency); end users have no node_modules here
16
17
  "fixtures", // smoke test fixtures live in the repo only, never ship to users
17
18
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "12.5.0",
3
+ "version": "12.6.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code and Copilot CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -14,11 +14,11 @@
14
14
  },
15
15
  "scripts": {
16
16
  "start": "node index.js",
17
- "test": "node --test test/*.test.mjs && node pipeline/scripts/run-smokes.mjs && node pipeline/scripts/lint-skills.mjs && node pipeline/scripts/eval-triage.mjs && node pipeline/scripts/eval-golden-tasks.mjs && node pipeline/scripts/eval-intent.mjs && node pipeline/scripts/validate-schemas.mjs",
17
+ "test": "node --test test/*.test.mjs && node pipeline/scripts/run-smokes.mjs && node pipeline/scripts/lint-skills.mjs && node pipeline/scripts/lint-mcp-refs.mjs && node pipeline/scripts/eval-triage.mjs && node pipeline/scripts/eval-golden-tasks.mjs && node pipeline/scripts/eval-intent.mjs && node pipeline/scripts/validate-schemas.mjs && node pipeline/scripts/validate-prefs.mjs",
18
18
  "test:unit": "node --test test/*.test.mjs",
19
19
  "test:smoke": "node pipeline/scripts/run-smokes.mjs",
20
20
  "lint:skills": "node pipeline/scripts/lint-skills.mjs",
21
- "test:quick": "node --test test/*.test.mjs && node pipeline/scripts/lint-skills.mjs",
21
+ "test:quick": "node --test test/*.test.mjs && node pipeline/scripts/lint-skills.mjs && node pipeline/scripts/lint-mcp-refs.mjs",
22
22
  "test:coverage": "c8 --check-coverage --reporter=text --reporter=lcov node --test test/*.test.mjs && c8 report",
23
23
  "lint": "eslint .",
24
24
  "lint:fix": "eslint . --fix",
@@ -72,6 +72,7 @@
72
72
  "LICENSE",
73
73
  "!pipeline/scripts/smoke-figma-config-schema.sh",
74
74
  "!pipeline/scripts/smoke-personal-data.sh",
75
+ "!pipeline/scripts/validate-prefs.mjs",
75
76
  "!pipeline/scripts/smoke-install-leak-gate.sh",
76
77
  "!docs/internal/**"
77
78
  ],
@@ -85,7 +85,7 @@ Example: new `MANAGE_EXTERNAL_STORAGE` permission → `(google-play-compliance /
85
85
 
86
86
  The two SKILL.md files contain tabular rule catalogs:
87
87
 
88
- - **apple-archive-compliance** - 17 rules (privacy-manifest, required-reason-api, info-plist, code-signing, embedded-sdk, entitlement, asset-validation, binary-size, team-id-consistency, provisioning-profile, swift-abi, extension-signing, ipv6-compliance, debug-tool-leak, production-hygiene, duplicate-resource, dead-reference) with ITMS codes + App Store Review Guideline refs.
88
+ - **apple-archive-compliance** - 18 rules (privacy-manifest, required-reason-api, info-plist, code-signing, embedded-sdk, entitlement, asset-validation, binary-size, team-id-consistency, provisioning-profile, swift-abi, extension-signing, ipv6-compliance, debug-tool-leak, production-hygiene, duplicate-resource, dead-reference, sdk-floor) with ITMS codes + App Store Review Guideline refs.
89
89
  - **google-play-compliance** - 21 rules across Technical / Security / Privacy / Hygiene categories with Play policy refs.
90
90
 
91
91
  Cite rule + ref in your Output Format's `Category:` line so the code-reviewer and triage layers inherit the reference text unchanged.
@@ -1,14 +1,14 @@
1
1
  ---
2
- description: Scan an .xcarchive for Apple App Store Review compliance (17-rule deep audit)
2
+ description: Scan an .xcarchive for Apple App Store Review compliance (18-rule deep audit)
3
3
  allowed-tools: Bash, Read, Glob
4
4
  ---
5
5
 
6
- Run the `ios_app_store_audit` MCP tool (shipped in `@mmerterden/dev-toolkit-mcp` ≥ v2.4)
7
- on an iOS archive. Backed by the same 17-rule catalog as `/multi-agent:test "store-ready"`
6
+ Run the `ios_app_store_audit` MCP tool (shipped in `@mmerterden/dev-toolkit-mcp` ≥ v2.9.0)
7
+ on an iOS archive. Backed by the same 18-rule catalog as `/multi-agent:test "store-ready"`
8
8
  - this command is the lighter, post-hoc form (no platform-detect, no UI sweep).
9
9
 
10
10
  > **v8.4.0 migration note:** The standalone `~/ArchiveGuard/.build/release/archive-guard`
11
- > binary has been retired. The 17 rules live in
11
+ > binary has been retired. The 18 rules live in
12
12
  > `dev-toolkit-mcp/tools/ios-app-store-audit/rules/` as a pure-Node port. Output JSON
13
13
  > shape is unchanged.
14
14
 
@@ -21,7 +21,7 @@ on an iOS archive. Backed by the same 17-rule catalog as `/multi-agent:test "sto
21
21
  2. Ask user which archive to scan (or use the argument if provided: $ARGUMENTS).
22
22
  3. Run the scan - preferred mode is the native MCP tool call:
23
23
  ```
24
- mcp__dev_toolkit__ios_app_store_audit({
24
+ mcp__dev-toolkit__ios_app_store_audit({
25
25
  archive_path: "<archive_path>",
26
26
  rules: "all" // "all" | "core" | "deep" | csv of ruleIDs
27
27
  })
@@ -83,6 +83,7 @@ Lib scripts (`~/.claude/lib/`):
83
83
  | `create-jira ["desc"] [figma-url] [swagger-url]` | Create a standards-compliant Jira issue: asks the type (**Task** / **Bug** / **Story**), mines the project's recent same-type issues for conventions (summary format, labels, priority, test-scenario style), detects the active sprint, drafts from a standard template with auto-sizing sections (Design Reference / API Contract / Screenshots appear only when their source is given), asks about unknown fields, then full draft preview + explicit approval before create. No worktree, no commits. |
84
84
  | `test` or `test [args]` | UI Bug Hunter - screenshot + tap + analyze on booted simulator via MCP (read `$HOME/.claude/commands/sim-test.md`). `/multi-agent:test` also resolves here via the `commands/multi-agent/test/SKILL.md` delegate. |
85
85
  | `manual-test [#id]` | Phase 5 standalone Manual Test - checks out the task branch, prints Xcode / SourceTree hints, waits for user verdict (`ok` / `fix: ...`). |
86
+ | `design-check [scope]` | Mock-mode vs Figma design audit (iOS / Android, local-only). Pick repo + module → mock-support feasibility gate (halts if unsupported) → **scenario inventory** (every launch arg / scenario case / scenario code / fixture / deep link becomes a countable target with file+line evidence) → scope resolve (empty = module, screen, `screen@variant`, target id, Figma URLs, `--resume`) → worktree Debug build + mock launch → drive EACH target by its own driver, capturing tap-reachable sub-states with it → per-variant pixel + px-spacing + typography + color compare → report (side-by-side + annotated overlay + stacked findings + fix prompt) exported to `~/DesignChecks/` as HTML + PDF (+ Confluence if enabled). **Coverage gate**: a target is audited or skipped with a concrete reason, else the run is reported INCOMPLETE with the missing ids. No commits, no CI. |
86
87
  | `stack [ios\|android\|backend\|mobile\|all]` | Swap skills for next conversation. No arg = show current stack. |
87
88
  | `language [en\|tr]` | Show or set the assistant `outputLanguage` (explanations and chat replies). `promptLanguage` is locked to `en` and is not toggleable. No arg = show current `outputLanguage`. With `en` or `tr` = set and persist `outputLanguage`. External payloads (commits, PR bodies, Jira) stay English. |
88
89
  | `setup` | Keychain token + Git Identity onboarding |
@@ -134,6 +135,7 @@ This command uses lazy loading for token efficiency. Read the relevant sub-file
134
135
  | Audit tools (Phase 5/6) | `$HOME/.claude/multi-agent-refs/audit-guide.md` |
135
136
  | `test` | `$HOME/.claude/commands/sim-test.md` (colon-form `/multi-agent:test` uses the delegate at `commands/multi-agent/test/SKILL.md`) |
136
137
  | `manual-test` | `$HOME/.claude/commands/multi-agent/manual-test/SKILL.md` |
138
+ | `design-check` | `$HOME/.claude/commands/multi-agent/design-check/SKILL.md` |
137
139
 
138
140
  **Modifier flags** (`--dev`, `--local`, `autopilot`) and **ops** (`status`, `log`, `resume`, `kill`, `clear-logs`, `purge`, `review`) are parsed inline by this file - no separate spec files, they compose with the pipeline or do one-shot work.
139
141
 
@@ -0,0 +1,287 @@
1
+ ---
2
+ description: "Mock-mode vs Figma design audit (iOS / Android, local-only). Pick repo + module, gate on mock support, enumerate every state driver into a countable target set, build Debug in a worktree, launch in mock mode, fetch Figma variants, compare each pixel + px-spacing + typography + color, and export a side-by-side annotated report (HTML + PDF + Confluence) to ~/DesignChecks. A coverage gate fails the run when a target is neither audited nor skipped with a reason."
3
+ description-tr: "Mock-mod vs Figma tasarım denetimi (iOS / Android, yalnızca lokal). Repo + modül seç, mock desteğini geçitle, her state sürücüsünü sayılabilir bir hedef kümesine çıkar, worktree'de Debug derle, mock modda aç, Figma varyantlarını çek, her varyantı piksel + px-boşluk + tipografi + renk düzeyinde karşılaştır, yan yana annotasyonlu raporu (HTML + PDF + Confluence) ~/DesignChecks altına aktar. Bir hedef ne denetlenmiş ne de gerekçeyle atlanmışsa kapsam geçidi koşuyu başarısız sayar."
4
+ argument-hint: '[scope] - empty = whole module; screen name; screen@variant; Figma URL(s); --resume'
5
+ allowed-tools: Agent, Bash, Read, Write, Edit, Glob, Grep, TaskCreate, TaskUpdate, AskUserQuestion, Skill, mcp__claude_ai_Figma__get_metadata, mcp__claude_ai_Figma__get_design_context, mcp__claude_ai_Figma__get_screenshot, mcp__dev-toolkit__design_mock_detect, mcp__dev-toolkit__design_scenario_inventory, mcp__dev-toolkit__design_mock_launch, mcp__dev-toolkit__design_ui_geometry, mcp__dev-toolkit__design_visual_compare, mcp__dev-toolkit__design_report, mcp__dev-toolkit__ios_list_devices, mcp__dev-toolkit__ios_boot_device, mcp__dev-toolkit__ios_launch_app, mcp__dev-toolkit__ios_terminate_app, mcp__dev-toolkit__ios_screenshot, mcp__dev-toolkit__ios_tap, mcp__dev-toolkit__ios_swipe, mcp__dev-toolkit__ios_type_text, mcp__dev-toolkit__ios_open_url, mcp__dev-toolkit__ios_get_ui_tree, mcp__dev-toolkit__ios_status_bar, mcp__dev-toolkit__ios_set_appearance, mcp__dev-toolkit__ios_set_locale, mcp__dev-toolkit__ios_xcodebuild, mcp__dev-toolkit__ios_xcresult, mcp__dev-toolkit__android_list_devices, mcp__dev-toolkit__android_screenshot, mcp__dev-toolkit__android_tap, mcp__dev-toolkit__android_swipe, mcp__dev-toolkit__android_type_text, mcp__dev-toolkit__android_open_url, mcp__dev-toolkit__android_launch_app, mcp__dev-toolkit__android_stop_app
6
+ ---
7
+
8
+ # /multi-agent:design-check - Mock-mode vs Figma design audit
9
+
10
+ Local, on-device design-conformance auditor. It runs the app in a **mock mode** on a simulator / emulator and compares each screen (and every mock variant the build exposes) against its Figma design at the **pixel + geometry + typography + color** level, then produces a detailed report.
11
+
12
+ **Local-only**: no CI/CD, no cron, no commits, no PR. It is a read-only audit that writes a report to `~/DesignChecks/`. The worktree exists only to build the Debug app without touching the user's working tree.
13
+
14
+ > **Language (read FIRST)**: Two independent axes.
15
+ >
16
+ > 1. **`prefs.global.promptLanguage` is always `en`** - this spec and every instruction to the LLM stay English.
17
+ > 2. **`prefs.global.outputLanguage` is user-selectable** - applies to conversational lines and the human-readable parts of the report. Pass it to `design_report` as `report.lang`; the engine defaults to English and ships an `en` + `tr` label pack, plus per-key `report.labels` overrides.
18
+ >
19
+ > Regardless of `outputLanguage`, these stay English: `AskUserQuestion` labels/headers, branch names, file paths, code identifiers, log lines. Full contract: `$HOME/.claude/multi-agent-refs/rules.md` "Language Application".
20
+
21
+ ## Scope - `$ARGUMENTS`
22
+
23
+ The scope decides which **inventory targets** (Phase 0 step 5) the run must audit. It never changes how thoroughly each target is audited.
24
+
25
+ | Form | Example | Scope |
26
+ |------|---------|-------|
27
+ | empty / `module` | `/multi-agent:design-check` | Every target in the module's inventory |
28
+ | screen name | `boarding-pass` | Every target whose `screen` matches (case/separator-insensitive) |
29
+ | `screen@variant` | `boarding-pass@expired` | One target |
30
+ | target id | `scenario-case:boardingpassoutcome-expired` | One target, exactly |
31
+ | Figma URL(s) | `https://figma.com/design/…?node-id=1-2` | Only the frames those URLs name |
32
+ | `--resume` | `/multi-agent:design-check --resume` | The unaudited remainder of the most recent run for this repo + module |
33
+ | combinations | `seat-map summary@semi-success` | Union of the above, space- or newline-separated |
34
+
35
+ Multiple forms may be combined. A scope that matches **no** inventory target is an error, not an empty run: print the closest inventory ids and halt.
36
+
37
+ **Whole-module is the default, and it is meant to finish.** The inventory prices each target (`cost: relaunch | in-app`) and batches them into a run `plan`, so a 50-target module is typically about a dozen relaunches rather than fifty - a full audit is one sitting, not a project. Scope exists for resuming an interrupted run and for re-checking one screen after a fix, **not** for trimming an audit down to what feels affordable. The coverage gate applies to the **scoped** target set, so a deliberate scoped run is never penalised for out-of-scope targets, and `--resume` closes the remainder.
38
+
39
+ ## Pipeline
40
+
41
+ ```
42
+ Phase 0: Init & Gate → repo + module picker, platform detect, MOCK FEASIBILITY GATE, SCENARIO INVENTORY, scope resolve, report dir, worktree
43
+ Phase 1: Build & Launch → Debug build in worktree, boot device, install, launch in mock mode
44
+ Phase 2: Figma → fetch module frame tree, enumerate + map variants (ask when ambiguous)
45
+ Phase 3: Drive & Compare → per plan batch: one launch → capture + flip in-app states → match → batch-ask → compare
46
+ Phase 4: Report → coverage gate + assemble + export HTML + PDF (+ Confluence) to ~/DesignChecks/
47
+ ```
48
+
49
+ ## Requirements
50
+
51
+ - **iOS**: Xcode + an available Simulator. **Android**: Android SDK + a running emulator / device.
52
+ - **MCP**: the `dev-toolkit` MCP server (>= 2.8.0, for `design_scenario_inventory`, the `design_report` coverage gate, and `design_visual_compare` region alignment). The Figma MCP (`mcp__claude_ai_Figma__*`) must be authenticated - the user supplies the Figma URL.
53
+ - The selected module must support a **mock mode** (Phase 0 gate decides this). No mock support → the command halts.
54
+
55
+ ## Phase Tracker Contract (mandatory)
56
+
57
+ Register one tile per phase in strict order BEFORE any update, on both channels. Full spec: `$HOME/.claude/multi-agent-refs/tracker-contract.md`.
58
+
59
+ ```bash
60
+ bash $HOME/.claude/scripts/phase-tracker.sh init "$TASK_ID"
61
+ for p in "0:Init & Gate" "1:Build & Launch" "2:Figma" "3:Drive & Compare" "4:Report"; do
62
+ bash $HOME/.claude/scripts/phase-tracker.sh add "${p%%:*}" "${p#*:}"
63
+ done
64
+ ```
65
+
66
+ In Claude Code also drive the native TaskList widget: fire all `TaskCreate` calls in phase order (0→1→2→3→4) first, persist each `taskId` via `phase-tracker.sh meta <N> tasklist_id`, then flip status with `TaskUpdate` + `phase-tracker.sh update` at every boundary. On Copilot / plain shell call `phase-tracker.sh render` instead (no TaskCreate).
67
+
68
+ ## STOP-AND-CONFIRM format
69
+
70
+ Every Phase 0 / Phase 2 decision uses a native `AskUserQuestion` picker (numbered text menus are forbidden - `feedback_native_picker_ui`). Print a `Step <i>/<n>: <what it decides>` breadcrumb before each picker. Confirmation is required even with a single option; state inheritance from a previous run is FORBIDDEN.
71
+
72
+ ---
73
+
74
+ ## Phase 0 - Init, Feasibility Gate & Inventory
75
+
76
+ 0. **MCP CURRENCY GATE (first, before anything expensive)** - an MCP stdio server publishes its tool list once at `initialize` and never re-reads the code, so a session connected to a server process that started before the last update keeps serving the OLD tool list while still reporting "connected". Discovering that after a Debug build and a device drive wastes the whole run and produces a report missing the very checks this spec relies on.
77
+
78
+ Assert both halves before continuing:
79
+
80
+ a. **Session** - `mcp__dev-toolkit__design_scenario_inventory` must be present in the tools available to you, and `mcp__dev-toolkit__design_visual_compare` must accept `live_region`. Absent → the session is bound to a stale process.
81
+
82
+ b. **Disk** - probe the configured server directly, which reports what a fresh connection WOULD serve:
83
+ ```bash
84
+ cd "$(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.claude.json')))['mcpServers']['dev-toolkit']['args'][0].rsplit('/',1)[0])")" && \
85
+ printf '%s\n%s\n' \
86
+ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
87
+ '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
88
+ | node index.js 2>/dev/null | python3 -c "
89
+ import sys,json
90
+ for l in sys.stdin:
91
+ try: m=json.loads(l)
92
+ except: continue
93
+ if m.get('id')==1: print('version', m['result']['serverInfo']['version'])
94
+ if m.get('id')==2:
95
+ n=[t['name'] for t in m['result']['tools']]
96
+ print('tools', len(n), 'inventory', 'design_scenario_inventory' in n)"
97
+ ```
98
+
99
+ Branch on the two results:
100
+ - **disk OK, session missing the tool** → **HALT** and tell the user to reconnect: `/mcp` → `dev-toolkit` → Reconnect. Note the tool-count tell (a stale build advertises one fewer `design_*` tool). If a reconnect does not take, stale server processes may be lingering - `pgrep -f dev-toolkit-mcp/index.js` with `ps -o lstart=` shows their start times, and any that predate the code's mtime cannot serve the current tools.
101
+ - **disk itself stale** (older version, or `inventory False`) → **HALT**: the checkout needs updating before a reconnect can help.
102
+ - **both current** → continue.
103
+
104
+ Never work around a stale server by substituting a local `node` call for the missing tool: the run would silently lose the coverage gate and region alignment, which is exactly the failure this gate exists to prevent.
105
+
106
+ 1. **Repo picker** - reuse `$HOME/.claude/multi-agent-refs/_repo-picker.md`. The account picker is skipped (Figma token is user-supplied, no Jira/GitHub fetch happens) unless Confluence export is enabled, in which case resolve the Confluence token via `account-resolver.sh`.
107
+ 2. **Module / dev-context picker** - reuse `$HOME/.claude/multi-agent-refs/_dev-context.md`. Auto-suggest submodules from `.gitmodules` (`submodule-detector.sh`); the user selects the single module to audit (multi-module allowed - each is audited independently).
108
+ 3. **Platform detect** - `.xcodeproj` / `Package.swift` → iOS; `build.gradle*` → Android (same markers as `phase-0-init.md`). Persist `state.platform`.
109
+ 4. **MOCK FEASIBILITY GATE** - run against the selected module path:
110
+ ```
111
+ mcp__dev-toolkit__design_mock_detect({ repo_path: "<module path>", platform: "<ios|android>",
112
+ extra_keys: <design-check-config mock.keys, if any> })
113
+ ```
114
+ Branch on `supported`:
115
+ - **`false`** → **HALT**. Show `reason` + that no runtime mock switch / launchable app / mock fixtures were found. Tell the user this module cannot be audited by design-check and stop. Do not fabricate a comparison.
116
+ - **`"debug-only"`** → surface an `AskUserQuestion` warning: mocks are compiled into the Debug build via `#if DEBUG` DI, so **variants cannot be toggled at launch** - only the default Debug state is comparable. Options: `{ label: "Continue", description: "Audit the single default Debug state only" }`, `{ label: "Cancel", description: "Stop" }`.
117
+ - **`true`** → continue. Persist `state.designCheck.mock = <detect result>` (mechanism, activation, variantsHint, evidence).
118
+ 5. **SCENARIO INVENTORY (this is the audit's target set)**:
119
+ ```
120
+ mcp__dev-toolkit__design_scenario_inventory({ repo_path: "<module path>", platform,
121
+ extra_launch_args: <config inventory.extraLaunchArgs>,
122
+ extra_targets: <config inventory.extraTargets>,
123
+ ignore_targets: <config inventory.ignoreTargets> })
124
+ ```
125
+ Returns `targets[]` - each `{ id, kind, label, screen, driver, cost, evidence }` - plus `plan[]`, `relaunchCount`, `groups[]`, `byKind`, `byCost`, `ignored[]`, `truncated`, `scanStrategy`. Kinds: `launch-arg`, `scenario-case`, **`prefix-code`** (a mock repository branching on the prefix of the reference the user types - usually the LARGEST group, and the one a selector-only search misses entirely), `code-scenario`, `fixture`, `deep-link`.
126
+
127
+ This list, not the agent's reading of the code and not what the UI happens to expose to tapping, is what the run is measured against. Persist verbatim to `state.designCheck.inventory`.
128
+ - `targetCount: 0` → warn that no state driver was found and that only the default launch state can be audited; a `debug-only` project legitimately lands here.
129
+ - `truncated: true` → the inventory hit its cap, so the target set is INCOMPLETE. Say so now, and pass `coverage.truncatedInventory: true` in Phase 4 so the gate fails rather than reporting a clean percentage of a partial denominator. Narrow the scope to a screen and work through the module in `--resume` steps.
130
+ - `ignored[]` non-empty → list what the config dropped, so a stale `ignoreTargets` cannot quietly shrink the audit.
131
+ 6. **Scope resolve** - intersect `$ARGUMENTS` (grammar above) with `inventory.targets`. Persist `state.designCheck.scope = { argument, targetIds[] }`.
132
+
133
+ Print the resolved set grouped by screen **with its relaunch cost**: `<n> targets · <relaunchCount> relaunches`. Read the cost from `plan`, not from the target count - one relaunch serves every in-app target on that screen, so a 50-target module is typically a dozen relaunches, not fifty. **Whole-module is the intended default**; only ask for confirmation when `relaunchCount` exceeds `config coverage.confirmAbove` (default 25), and phrase it as a cost estimate, not as an invitation to shrink the audit. Never propose a smaller scope as the easy path - a scoped run is for resuming or for a focused re-check, not for avoiding work.
134
+ 7. **`--resume`** - read the most recent `~/DesignChecks/{repo}__{module}/*/run-state.json`; the scope becomes that run's targets minus its `covered` and minus its `skipped` entries. Skips carry a reason and are honoured, so a resume covers the genuinely unaudited remainder. No previous run → tell the user and fall back to whole-module scope after confirmation.
135
+ 8. **Report dir** - create `~/DesignChecks/{repo}__{module}/{UTC-timestamp}/` (and `assets/` inside it) now, and persist it as `state.designCheck.reportDir`. Phase 3 writes captures, comparison images, and `run-state.json` into it, so it must exist before driving starts - not at export time. This is report output, not a worktree; $HOME is intended here.
136
+ 9. **Worktree** - build the Debug app in an isolated worktree so the user's tree is untouched. Follow `phase-0-init.md` Step 8 convention exactly: `{projectRoot}/{worktreeBasePath}/{taskId}` (default `.worktrees/DC-<shortId>`), **never under $HOME** (`feedback_worktree_path_convention`). Stale-lock heal (`git worktree prune`) + residue guard (`.worktrees/` in `.git/info/exclude`) first. Local mode is not offered - the audit always uses a worktree checkout of the current branch's HEAD (no fetch/push).
137
+
138
+ Persist `agent-state.json` with `taskId`, `mode: "design-check"`, `platform`, `projectRoot`, `worktreePath`, `module`, `designCheck`.
139
+
140
+ ## Phase 1 - Build & Launch (mock)
141
+
142
+ 1. **Debug build** in the worktree (mock activation requires the Debug configuration):
143
+ - iOS: `mcp__dev-toolkit__ios_xcodebuild({ workspace|project, scheme, configuration: "Debug", action: "build", destination: "generic/platform=iOS Simulator" })`. Drill failures via `ios_xcresult`.
144
+ - Android: `./gradlew :<module>:assembleDebug` (or `installDebug`) via Bash.
145
+ 2. **Boot device + install**: iOS `ios_list_devices` → `ios_boot_device` → install the built `.app` (`xcrun simctl install`). Android: ensure an emulator is running, `adb install -r <apk>`.
146
+ 3. **Deterministic state**: iOS `ios_status_bar({ preset: "clean" })` (09:41 / full battery); set a fixed appearance + locale so captures are stable across runs.
147
+ 4. **Launch in mock mode** using the activation from Phase 0:
148
+ ```
149
+ mcp__dev-toolkit__design_mock_launch({ platform, bundle_id|package_name,
150
+ launch_arg: state.designCheck.mock.activation.launchArg, // iOS, e.g. "-debugMockMode YES"
151
+ intent_extra: state.designCheck.mock.activation.intentExtra }) // Android
152
+ ```
153
+ For `debug-only` there is no switch - just launch the Debug build. The build is installed once and stays installed: Phase 3 relaunches it per target rather than rebuilding.
154
+
155
+ ## Phase 2 - Figma fetch & variant mapping (module-scoped)
156
+
157
+ Figma **discovery** happens only here: the frame tree is fetched once and no later phase browses Figma looking for candidates. Phase 3 may still fetch a specific node the user hands over in the 3.3 batch-ask - that is a targeted fetch of an already-identified frame, not discovery, and it is the only Figma call allowed after this phase.
158
+
159
+ 1. Resolve the Figma URL (`$ARGUMENTS` or ask via `AskUserQuestion`). Parse `fileKey` + `nodeId` (`figma.com/design/{fileKey}/...?node-id={id}`; convert `-`→`:` in node ids).
160
+ 2. `mcp__claude_ai_Figma__get_metadata({ fileKey, nodeId })` → the frame tree. Treat direct child frames as **candidate variants**.
161
+ 3. **Map inventory targets → Figma frames**: match each scoped target's `screen` + `label` (and `state.designCheck.mock.variantsHint`) to frame names by similarity.
162
+ - Confident 1:1 match → accept.
163
+ - **Ambiguous or unmatched** → deferred to the Phase 3.3 batch-ask, where the live capture can be shown alongside the question. Never guess silently.
164
+ - `debug-only` projects → a single variant (the default state) mapped to the primary frame.
165
+ 4. For each mapped variant: `mcp__claude_ai_Figma__get_screenshot` (the design render PNG, download locally) and `mcp__claude_ai_Figma__get_design_context` (node geometry: `absoluteBoundingBox`, `itemSpacing`, paddings, text `fontSize`/`fontFamily`, `fills` colors, and any `CodeConnectSnippet` component name). Flatten the node subtree with the geometry helper into a `figma_spec` element list. Record the Figma frame size (`figmaFrame`).
166
+
167
+ Persist the variant list + spec + PNG paths to `state.designCheck.variants[]`.
168
+
169
+ ## Phase 3 - Drive every scoped target → capture → match → batch-ask → compare
170
+
171
+ **This phase iterates `state.designCheck.scope.targetIds` - a finite list decided in Phase 0. It ends when every id is resolved, not when the UI stops offering new taps.** Tap-walking alone cannot reach a state that needs a different launch argument, a different scenario case, or a typed scenario code, which is exactly how a module's error / edge states go missing.
172
+
173
+ ### 3.1 Drive the inventory's plan, batch by batch
174
+
175
+ **Follow `inventory.plan`, filtered to the scoped ids.** Each batch is one launch: the batch's relaunch target opens a screen, and every in-app target for that same screen is then flipped while the app is already sitting there. Driving the plan is what makes full coverage affordable - target-by-target relaunching is what made earlier runs give up a third of the way in.
176
+
177
+ For each batch:
178
+ 1. Launch once with the batch's `launch` driver (or the plain mock activation for the `(default launch)` batch).
179
+ 2. Navigate to the screen (`ios_tap` / `ios_swipe` / `ios_type_text` route through idb).
180
+ 3. Capture the batch's relaunch target.
181
+ 4. For each in-app target in the batch: flip its selector (scenario case / scenario code), let the screen re-render, capture. **No relaunch between these.**
182
+
183
+ Per-target activation by `driver.type`:
184
+
185
+ | `driver.type` | Activation |
186
+ |---|---|
187
+ | `launch-arg` | `design_mock_launch({ launch_arg: "<mock activation> <driver.launchArg>" })` - the mock switch AND the target's flag together |
188
+ | `intent-extra` | `design_mock_launch({ intent_extra: "<mock extra> <driver.intentExtra>" })` |
189
+ | `scenario` | Set `driver.enum` to `driver.case` through the build's debug scenario picker (navigate to it, select, return), then drive the flow that consumes it |
190
+ | `code` (from `prefix-code`) | Type a reference beginning with `driver.code` at the entry field the flow starts from, then walk to `driver.appliesTo`. The prefix decides which fixture the mock repository returns, so this is how most per-screen variants are reached |
191
+ | `code` (from `code-scenario`) | Same entry field, but the code is flow-wide rather than owned by one screen |
192
+ | `fixture` | Activate the launch arg / scenario that reads `driver.file`; when nothing does, the target is a skip with reason "fixture not reachable from any driver" |
193
+ | `deep-link` | `ios_open_url` / `android_open_url` with `driver.url` |
194
+ | `manual` | Config-declared: follow the config's note, or skip with that note as the reason |
195
+
196
+ While a target's state is on screen, also capture the **sub-states reachable from it by tapping** - overlays, bottom sheets, modals, popups, inline errors, QR / share sheets. These cost no relaunch and belong to the target that exposed them; record them as additional captures under that target id (`<id>#<sub-label>`).
197
+
198
+ For each capture: `ios_screenshot` (save PNG into the Phase 0 report dir) + `mcp__dev-toolkit__design_ui_geometry` (live boxes + screen size) → append to `state.designCheck.captured[]` with the target id and a human label. **Persist `state` and `run-state.json` after every capture**, so a run that dies mid-way resumes from where it stopped instead of starting over.
199
+
200
+ If a target cannot be reached, record it immediately as `{ id, reason }` in `state.designCheck.skipped[]` with a concrete reason ("scenario picker not present in this build", "needs a live PNR", "crashes on launch: <symbol>"). "Requires a scenario / prefix / launch-arg" is **not** a reason - that is a description of the work, and the work is this phase's job.
201
+
202
+ ### 3.2 Match each capture → Figma frame (auto)
203
+
204
+ For each captured screen, resolve its Figma frame: drill the relevant board (`get_metadata` result from Phase 2), match by structure/content; accept only a **confident** match. Add matched → `pairs[]`.
205
+
206
+ ### 3.3 Batch-ask the user for EVERYTHING unmatched (ONE prompt, WITH the visual)
207
+
208
+ Collect ALL captures with no confident Figma match into a single list and ask the user ONCE - never guess, never show a wrong frame. **For each unmatched screen you MUST SHOW its captured live screenshot** (render/attach the PNG so they see exactly which visual you mean), name the screen, and ask for the Figma node-id / URL (in `outputLanguage`). Optionally list your best-guess candidate frame names. The user supplies the node-id/URL per shown screenshot; each becomes a confirmed pair. A screen the user declines to map is a skip with reason "no Figma frame supplied".
209
+
210
+ ### 3.4 Compare ALL pairs in one pass
211
+
212
+ For each pair (matched + user-supplied), fetch `get_screenshot` + `get_design_context`, then:
213
+ ```
214
+ mcp__dev-toolkit__design_visual_compare({
215
+ figma_png, live_png, out_dir: "<report dir>/assets", label: "<variant>",
216
+ figma_spec, live_geometry, figma_frame, live_screen,
217
+ crop_top_live: <status-bar px>, // strip device chrome the Figma frame lacks
218
+ tolerance_px: 2, color_tolerance: 3, max_diff_pct: 1.0 })
219
+ ```
220
+ Collect `findings` (spacing / size / position in px, color ΔE with hex, typography), `perceptualPct`, and the written image paths (figma / live / diff / overlay / side-by-side).
221
+
222
+ **Bottom sheets, modals, and any partial overlay need region alignment.** Their Figma frame covers only the sheet, while the capture is the whole screen. Passing that pair as-is stretches a full screen onto a sheet-shaped frame, which misplaces every element inside the sheet and buries the real defect in noise. So for any capture whose frame is not full-screen:
223
+
224
+ ```
225
+ mcp__dev-toolkit__design_visual_compare({ ...as above,
226
+ live_region: <the sheet container's {x,y,w,h} from design_ui_geometry>,
227
+ expected_region: <where the design puts that sheet, same units> })
228
+ ```
229
+ - `live_region` rebases the comparison onto the sheet's own box. Take it from the `design_ui_geometry` element that is the sheet container (the outermost element whose height is well under the screen's and which holds the sheet's content), NOT from a guess.
230
+ - `expected_region` comes from the Figma frame's own placement inside its parent screen frame. With it, the engine emits an `inset` finding per edge.
231
+ - `crop_top_live` is ignored when `live_region` is given - the region crop has already excluded device chrome.
232
+
233
+ **Edge insets are defects, not tolerances.** A design that shows a sheet flush to the screen edges is not satisfied by one floating in from them, and the reverse is equally wrong. Report every `inset` finding as a deviation with its px delta; never absorb a side gap as "close enough", never raise `tolerance_px` to make one disappear. The same applies to the sheet's internal content padding, which the normal element pairing measures once the region alignment is right.
234
+
235
+ **Component reference (best-effort)**: if a Figma atom carries a Code Connect component name and the repo has a matching `*.figma.swift` / `*.figma.kt`, attach that component's reference render to the variant's `componentRefs`.
236
+
237
+ **Fix prompt**: from the findings, compose a ready-to-paste developer prompt (target file/screen + each deviation with expected vs actual token/value) so the user can hand it straight to an agent to apply the fix.
238
+
239
+ ### 3.5 Close the ledger
240
+
241
+ Every scoped target id must end in exactly one bucket:
242
+
243
+ - **covered** - captured AND compared (or captured and explicitly awaiting a Figma frame the user declined to supply, which is a skip)
244
+ - **skipped** - with a concrete reason recorded in 3.1 / 3.3
245
+
246
+ Write `run-state.json` into the Phase 0 report dir with `{ targetIds, covered, skipped }` so `--resume` can pick up the remainder. An id in neither bucket is a bug in this phase, and Phase 4's gate will fail the run for it.
247
+
248
+ Before leaving this phase, compare `covered.length + skipped.length` against `scope.targetIds.length`. If they differ, the difference is a set of targets nobody decided about - go back and drive them, or record why they cannot be driven. Reaching Phase 4 with a mismatch and letting the gate report it is the worst of both worlds: the work is undone AND the run is marked incomplete.
249
+
250
+ ## Phase 4 - Report, coverage gate & export
251
+
252
+ 1. **Output dir**: `state.designCheck.reportDir`, already created in Phase 0 - Phase 3's captures and comparison images are already inside it. Do not mint a second timestamped dir here.
253
+ 2. Assemble the report object:
254
+ ```
255
+ { project, module, platform, figmaUrl, figmaFileKey, timestamp,
256
+ lang: <prefs.global.outputLanguage>,
257
+ coverage: { targets: <scoped inventory targets, as the id array or the target objects>,
258
+ covered: <audited id array>,
259
+ skipped: [ { id, group, reason } ],
260
+ truncatedInventory: <inventory.truncated>,
261
+ floor: <config coverage.floor, optional> },
262
+ variants: [ { name, figmaNodeId, perceptualPct, passed, compareSize, liveSize,
263
+ images, findings, fixPrompt, componentRefs } ] }
264
+ ```
265
+ Pass **ids**, not counts: the engine then names each unaccounted target in the report instead of printing an anonymous tally. `compareSize` and `liveSize` come straight from the `design_visual_compare` result.
266
+ 3. **Render + export**:
267
+ ```
268
+ mcp__dev-toolkit__design_report({ report, out_dir: state.designCheck.reportDir,
269
+ formats: ["html", "pdf"] + (confluence enabled ? ["confluence"] : []) })
270
+ ```
271
+ - HTML: self-contained (base64 images, CSS-positioned annotations over the live capture, stacked findings, fix prompt), with the coverage gate banner at the top.
272
+ - PDF: via Playwright if present; if unavailable the tool returns `pdfError` - report it and note the HTML can be printed manually.
273
+ - **Confluence**: only if `figmaConfig.confluence.enabled` (or prefs). Reuse the pipeline's Confluence adapter (`/multi-agent:channels` storage-format upload) with the report HTML as the page body + PNGs as attachments. Disabled → graceful skip with a one-line note.
274
+ 4. **COVERAGE GATE (blocking, not advisory)** - read `coverage` from the tool result and persist it to `state.designCheck.coverage`:
275
+ - `gate: "pass"` → report per-variant PASS/FAIL + findings count + report paths.
276
+ - `gate: "fail"` → the run is **INCOMPLETE**. Print `coverageError`, the `unaccountedIds`, and the exact command that closes the gap: `/multi-agent:design-check --resume`. Do not present an incomplete audit as a finished one, do not bury the miss in a footnote, and never pad the covered list to make the gate pass.
277
+
278
+ The gate is a last line of defence, not the plan. If it fires, Phase 3 left work undone - the correct response is to drive the missing targets, and only then to report.
279
+ 5. Clean up the worktree (`git worktree remove --force`) unless the user asked to keep the built app. The report dir is never removed.
280
+
281
+ ## Notes
282
+
283
+ - **Feasibility is real**: many projects legitimately cannot be audited (no launchable app, no runtime switch). Report that honestly and stop - never substitute a fabricated or partial comparison.
284
+ - **The inventory is the contract**: Phase 0 step 5 decides what "done" means. An audit that visits the linear happy path and lists the rest as "not visited" is the failure mode this pipeline exists to prevent; the gate makes it visible instead of polite.
285
+ - **Chunk with scope, not with silence**: a 50-target module is a legitimate multi-run job. Scope it, finish each run's gate cleanly, and let `--resume` carry the remainder.
286
+ - **"Pixel perfect" is perceptual + geometric**: a Figma render never matches device dimensions 1:1, so the engine normalizes scale and crops chrome. Findings are reported with explicit px / pt / hex deltas and a tolerance, not as strict pixel equality.
287
+ - The engine (`design_*` tools) is generic and project-agnostic; all project specifics (mock switch keys, extra launch args / targets, variant→node overrides, status-bar crop, coverage floor, Confluence toggle) come from `design-check-config.json` / prefs, never hardcoded.