@mmerterden/multi-agent-pipeline 13.0.0 → 13.1.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.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,65 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [13.1.0] - 2026-07-27
20
+
21
+ Six gates, from one branch that spent half its commits on rework.
22
+
23
+ A Figma-driven screen task was built through the generic development path. The cause
24
+ chain, measured from the run's own artefacts: Phase 0 reported `completed` having
25
+ written only `tracker-state.json`, so `agent-state.json` and its `taskType` never
26
+ existed, so Phase 3's component dispatch could not fire. The stack plugin already
27
+ ships `create-screen`, `figma-validate` (7 criteria including design-token compliance
28
+ and Code Connect strategy) and `figma-review` (14-item checklist) - none of them ran.
29
+ Padding came out 16 where the frame said `Spacing/12`. Three of six commits were fixes,
30
+ the last a full sheet rebuild.
31
+
32
+ Nothing here is a new capability. Every gate enforces a rule that already existed or
33
+ invokes a skill that was already written but never called.
34
+
35
+ ### Added
36
+
37
+ - **`phase0-exit-gate.mjs`, blocking.** Phase 0 may not be marked completed until
38
+ `agent-state.json` exists with a `taskType`, and a Figma reference forces
39
+ `taskType: "component"` plus a recorded `figmaAccess.tier`. A phase that reports
40
+ success without its output is worse than one that fails: every later phase then
41
+ reasons from a field that is not there. The evaluator is pure and exported, so the
42
+ gate is testable without reproducing a run.
43
+ - **Phase 4 Step 2.8, visual conformance gate.** Runs `figma-review` and
44
+ `/multi-agent:design-check` for component/screen work, with the coverage gate, and
45
+ asserts Code Connect was **published** rather than merely written - a
46
+ `*.figma.swift` on disk with "Not published" in Figma is a binding that exists for
47
+ nobody. `design-check` had been a command with no phase invoking it, so the only
48
+ thing standing between a build and visual drift was the user opening the app.
49
+ - **Phase 1 Step 1.45, reuse discovery, blocking.** Search for an existing wrapper,
50
+ entity, mapper or screen before proposing a new one. One run wrote a repository over
51
+ an endpoint a sibling domain already wrapped **with its country parameter**, called
52
+ the generated method without it, and re-invented an entity the module already had.
53
+ "Copy X and rename it" is the reuse answer, not a hint.
54
+ - **`smoke-component-dispatch-gates.sh`** (12 assertions) pins all six.
55
+
56
+ ### Changed
57
+
58
+ - **Component dispatch halts instead of degrading.** The contract used to send an
59
+ incomplete-state component task down the generic TDD path while the next sentence
60
+ said "never silently skip the Figma work" - taking the generic path *is* skipping
61
+ it. That wording authorised the exact failure above.
62
+ - **Dispatch routes on scope, not just platform.** A screen and a component are
63
+ different jobs and the plugin ships a skill for each; routing a screen to the
64
+ component skill is why one run produced entities and a mapper but left the screen
65
+ half-wired. `figma-validate` now runs before the create skill.
66
+ - **Phase 1 captures spacing by token name, per atom.** Phase 3 is forbidden from
67
+ calling Figma, so a pixel number - or a missing entry - is unrecoverable later. A
68
+ UI frame with no spacing entries is a capture failure, not an empty frame.
69
+ - **Phase 3 records that generated trees are not editable.** A mock fixture went into
70
+ the generated tree; the fix moved it to the custom tree and registered the scenario
71
+ in the generated index. Same content, wrong side of the generator, and the Debug menu
72
+ never showed it.
73
+ - **Fast modes warn when handed an analysis document.** `--dev` skips Analysis and
74
+ Planning by design, so there is no phase that turns a document into a plan. The doc
75
+ becomes context for one pass and work lands in whatever order it was read.
76
+
77
+
19
78
  ## [13.0.0] - 2026-07-27
20
79
 
21
80
  > **Why major and not minor.** The additions here are additive, but two defaults
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "13.0.0",
3
+ "version": "13.1.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex 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",
@@ -270,3 +270,24 @@ bash $HOME/.claude/scripts/phase-tracker.sh render
270
270
  ```
271
271
 
272
272
  Do NOT call TaskCreate on these CLIs - the tool does not exist and the call fails.
273
+
274
+ ## Analysis doc supplied to a fast mode (warn before starting)
275
+
276
+ The `--dev` family skips Phase 1 (Analysis) and Phase 2 (Planning) by design. So when
277
+ the input references an analysis document - a Confluence URL, a local analysis file,
278
+ or the user says "I ran analysis for this" - there is **no phase that turns it into a
279
+ plan**. The doc becomes raw context for one Dev pass, and work comes out ordered by
280
+ whatever the model read first: the bottom of the dependency chain lands, the screen
281
+ wiring does not.
282
+
283
+ Say so before starting, once, and offer the choice:
284
+
285
+ ```
286
+ This mode skips Analysis and Planning, so the analysis document will not be turned
287
+ into a task breakdown. For analysis-driven screen work, /multi-agent or
288
+ /multi-agent:local run both phases.
289
+ 1. Continue with --dev (doc as context only)
290
+ 2. Switch to the full pipeline
291
+ ```
292
+
293
+ Autopilot picks 1 and logs the warning rather than asking.
@@ -109,3 +109,24 @@ Routes to the orchestrator with `--dev --local` flags. Apply the `$HOME/.claude/
109
109
  /multi-agent:dev-local "PROJ-12345" # Jira
110
110
  /multi-agent:dev-local "Bug: LoginView dark mode" # Free-text
111
111
  ```
112
+
113
+ ## Analysis doc supplied to a fast mode (warn before starting)
114
+
115
+ The `--dev` family skips Phase 1 (Analysis) and Phase 2 (Planning) by design. So when
116
+ the input references an analysis document - a Confluence URL, a local analysis file,
117
+ or the user says "I ran analysis for this" - there is **no phase that turns it into a
118
+ plan**. The doc becomes raw context for one Dev pass, and work comes out ordered by
119
+ whatever the model read first: the bottom of the dependency chain lands, the screen
120
+ wiring does not.
121
+
122
+ Say so before starting, once, and offer the choice:
123
+
124
+ ```
125
+ This mode skips Analysis and Planning, so the analysis document will not be turned
126
+ into a task breakdown. For analysis-driven screen work, /multi-agent or
127
+ /multi-agent:local run both phases.
128
+ 1. Continue with --dev (doc as context only)
129
+ 2. Switch to the full pipeline
130
+ ```
131
+
132
+ Autopilot picks 1 and logs the warning rather than asking.
@@ -11,20 +11,53 @@ Phase 3 checks, in order:
11
11
  1. `agent-state.json` has `taskType: "component"` (set by Phase 0 Step 7).
12
12
  2. `agent-state.json` has a non-null `figmaUrl`.
13
13
 
14
- Either missing → log the anomaly to `agent-log.md` ("component dispatch expected but state incomplete"), fall through to the standard TDD path. Never silently skip the Figma work.
14
+ Either missing → **HALT.** Log the anomaly to `agent-log.md` ("component dispatch
15
+ expected but state incomplete: <which field>") and stop with a user-visible error
16
+ naming the missing field and pointing at `phase0-exit-gate.mjs`.
17
+
18
+ > Earlier wording sent an incomplete-state component task down the generic TDD path,
19
+ > which contradicted the sentence that followed it: taking the generic path **is**
20
+ > skipping the Figma work.
21
+ > It also authorised the exact degradation that broke a real run - Phase 0 never
22
+ > wrote `agent-state.json`, so `taskType` was absent, so a Figma-driven screen was
23
+ > built through the generic path with no token-compliance check, no Code Connect
24
+ > publish and no component review. Spacing came out `16` where the frame said
25
+ > `Spacing/12`, and half the branch's commits were rework.
26
+ >
27
+ > The Phase 0 exit gate now prevents reaching Phase 3 in that state at all; this
28
+ > halt is the second line of defence. A component task that cannot be dispatched as
29
+ > one must fail loudly, because the generic path produces artefacts that look
30
+ > finished and are not.
15
31
 
16
32
  ## Plugin skill resolution
17
33
 
34
+ Scope first, then platform. A screen and a single component are different jobs and
35
+ the plugin ships a skill for each; routing a screen to the component skill is why one
36
+ run produced entities and a mapper but left the screen half-wired.
37
+
38
+ | `state.componentScope` | Meaning | iOS skill | Android skill |
39
+ |---|---|---|---|
40
+ | `screen` (default when the frame is a full screen, or the task names a screen) | Full clean-architecture vertical: Entity → Repository → Mapper → UseCase → LocalizedText → AnalyticsTracking → CoordinatorEvent → ViewModel → Scene → Preview, then verify | `ai-ios-engineering-toolkit:create-screen` | `ai-android-engineering-toolkit:create-screen` |
41
+ | `component` | One reusable UI component (Configuration / View / +Modifiers / Code Connect) | `ai-ios-engineering-toolkit:create-component` (fallback `create-ui-component`) | `ai-android-engineering-toolkit:create-component` (fallback `create-ui-component`) |
42
+ | `evolve` | Change an existing component | `evolve-component` (fallback `evolve-ui-component`) | same |
43
+
18
44
  ```
19
- project.platform → component skill (enabled marketplace plugin, Claude Code)
20
- ──────────────────────────────────────────────────────────────────────────────
21
- ios → Skill: ai-ios-engineering-toolkit:create-component
22
- (fallback: ai-ios-engineering-toolkit:create-ui-component)
23
- android → Skill: ai-android-engineering-toolkit:create-component
24
- (fallback: ai-android-engineering-toolkit:create-ui-component)
25
45
  web, multi-* → HALT with clear error (no web target)
26
46
  ```
27
47
 
48
+ Phase 0 Step 7 sets `state.componentScope` alongside `taskType`: a Figma frame that
49
+ is a full screen, or a task whose title names a screen, is `screen`; a frame that is
50
+ a single atom is `component`. When it cannot be decided, ask - do not default to
51
+ `component`, because the screen path is a superset and the component path silently
52
+ omits the wiring.
53
+
54
+ **Pre-implementation validation is not optional on iOS.** Before the create skill
55
+ runs, dispatch `ai-ios-engineering-toolkit:figma-validate` for the frame. It checks
56
+ registry presence, Code Connect strategy, **design token compliance**, dependency
57
+ readiness, atomic scope and already-implemented status in about ten seconds. Those
58
+ are precisely the checks whose absence produced guessed spacing and an unpublished
59
+ Code Connect binding. A `figma-validate` failure halts the dispatch.
60
+
28
61
  **Dual-name resolution.** The public (`multi-agent-plugins`) and a corporate/private marketplace named the same skill differently - `create-component` vs `create-ui-component`. Dispatch tries `create-component` first; if it is not available in the current repo, tries `create-ui-component`. (Same dual-name rule applies when `taskType` maps to evolve → `evolve-component`/`evolve-ui-component`, or fix → `fix-bug`.)
29
62
 
30
63
  If **neither** resolves, the platform's `ai-<platform>-engineering-toolkit` plugin is not enabled in this repo. **Halt with a user-visible error**: "component task requires the ai-<platform>-engineering-toolkit plugin enabled in this repo (`.claude/settings.local.json`)." Do not silently fall back to TDD - a component task ran through the bugfix path would produce wrong artefacts.
@@ -531,3 +531,32 @@ Phase 7 cost rollup carries this as a `phase 0` line item so the user sees ambig
531
531
  **Progress (per `$HOME/.claude/multi-agent-refs/progress-contract.md`):** emit one `→ <verb> <object>` line for each of: `→ parsing input`, `→ checking token <service>`, `→ scanning project <root>`, `→ creating worktree <repo>`, `→ binding identity <name>`, `→ writing state`. When `clarifyAmbiguous.enabled`, also emit `→ scoring task ambiguity` before Step 8 and `→ asking clarifying questions <N>` when `stopAndAsk` fires.
532
532
 
533
533
  **Save preferences**: Write updated prefs to `$HOME/.claude/multi-agent-preferences.json` with all Phase 0 selections.
534
+
535
+ ---
536
+
537
+ #### Phase 0 exit gate (BLOCKING - run before marking the phase completed)
538
+
539
+ Phase 0 owns `agent-state.json`. Do not call
540
+ `phase-tracker.sh update 0 completed` until this gate passes:
541
+
542
+ ```bash
543
+ node "$HOME/.claude/scripts/phase0-exit-gate.mjs" "$TASK_ID" --input "$ORIGINAL_INPUT"
544
+ ```
545
+
546
+ It asserts three things, each of which has failed silently in a real run:
547
+
548
+ 1. **`agent-state.json` exists.** A run once reported Phase 0 `completed` with only
549
+ `tracker-state.json` on disk. Every later phase then reasons from fields that are
550
+ not there.
551
+ 2. **`taskType` is set.** Phase 3 branches on it (Step 7). Absent, a Figma-driven
552
+ screen is dispatched as generic development, skipping the stack plugin's
553
+ token-compliance check, Code Connect publish and component review. That run
554
+ guessed `16` where the frame said `Spacing/12`, and half its commits were rework.
555
+ 3. **A Figma reference forces `taskType: "component"`, and `figmaAccess.tier` is
556
+ recorded.** Without the tier, a later phase cannot tell "the design was confirmed"
557
+ from "the design was never fetched" - which is exactly when spacing gets guessed.
558
+
559
+ A failure is a halt, not a warning. Fix the state and re-run the gate; the phase
560
+ stays `in_progress` until it passes. **Never** mark Phase 0 completed on the grounds
561
+ that its steps ran - the gate checks the output, and the output is what Phase 3
562
+ consumes.
@@ -63,7 +63,14 @@ When `state.contextLinks[]` or the task description contains a Figma reference,
63
63
  | 2 (REST) | `GET /v1/files/{fileKey}/nodes?ids={nodeId}` + `GET /v1/images/{fileKey}?ids={nodeId}&format=png&scale=2`, PAT via `~/.claude/lib/credential-store.sh get <logical-key>` (logical key = `prefs.global.keychainMapping.figma_pat`); canonical component resolved from repo `*.figma.swift` / `*.figma.kt` mapping keyed by `fileKey` + `nodeId` | same shape, but `codeConnectSnippets[]` is empty when repo mapping is absent (record an Open Question), `tier: 2` |
64
64
  | 3 (screenshot) | User-attached screenshot stored alongside task evidence | degraded record: `codeConnectSnippets: []`, forced Open Question, `tier: 3` |
65
65
 
66
- Persist results under `state.evidence.figma[]`. Halt the run if all three tiers fail; never substitute primitives or invent layout from prose. Canonical chain reference: `pipeline/rules/figma-pipeline.md` "MUST: Figma access - 3-tier fallback chain".
66
+ Persist results under `state.evidence.figma[]`. Halt the run if all three tiers fail; never substitute primitives or invent layout from prose.
67
+
68
+ **Spacing goes in by token NAME, per atom - never a pixel number.** `tokens[]` must
69
+ carry each frame's spacing/padding as Figma names them (`Spacing/12`, edge `4`), keyed
70
+ to the atom. Phase 3 cannot call Figma, so what is missed here is gone: one run guessed
71
+ `16` where the frame said `Spacing/12` and the sheet was rebuilt. A pixel number also
72
+ cannot map back to a token. No spacing entries on a UI frame is a **capture failure**,
73
+ not an empty frame - Open Question and halt. Canonical chain reference: `pipeline/rules/figma-pipeline.md` "MUST: Figma access - 3-tier fallback chain".
67
74
 
68
75
  **Telemetry (required for the no-MCP gate):** Tier 1 uses `mcp__claude_ai_Figma__*` tools. Every such MCP invocation MUST append an entry to `state.telemetry.mcpCalls[]` as `{ "tool": "<full mcp tool name>", "phase": 1, "timestamp": "<ISO-8601>" }`. This is the only phase permitted to record `phase: 1` (or `0`) entries; `smoke-no-mcp-in-dev-phases.sh` fails the run if any entry carries `phase >= 2`. Recording is what makes that BLOCKING contract enforceable - an MCP call left unrecorded defeats the gate, so record every one.
69
76
 
@@ -73,6 +80,22 @@ Progress lines:
73
80
  → figma evidence: <N> frames captured (tier=<n>, code-connect=<M>, open-questions=<K>)
74
81
  ```
75
82
 
83
+ #### Step 1.45 - Reuse discovery (BLOCKING for new services, entities, mappers)
84
+
85
+ Before proposing any new service call, entity or mapper, search for what already
86
+ covers it. Record hits under `state.reuse[]` and cite them in the doc; proposing new
87
+ code over a hit needs a one-line reason.
88
+
89
+ Search for: a **wrapper** over the same endpoint (especially one supplying parameters
90
+ the generated call leaves optional); an **entity** for the same concept (module's
91
+ shared entities first, then siblings); a **mapper** over the same response; a **screen**
92
+ doing the same interaction.
93
+
94
+ Why blocking: one run proposed a new repository over an endpoint a sibling already
95
+ wrapped **with its country parameter**, called the generated method without it, and
96
+ re-invented an entity the module had. Half that branch's commits went to converging
97
+ back. "Copy X and rename it" is the reuse answer, not a hint - name X's files.
98
+
76
99
  #### Step 1.5 - External Context Injection (`state.contextLinks[]`)
77
100
 
78
101
  Phase 0 Step 1b catalogued every typed external link from the task description into `state.contextLinks[]`. Phase 1 dispatches each entry to its matching fetcher (crashlytics, fortify, graylog, swagger, confluence, figma, generic-doc) and prepends results under a **Referenced External Sources** section in the analysis prompt - so the agent doesn't re-discover what the ticket already pointed at. `state.graylogContext` is injected there too, as diagnostic context (advisory only). Failures never fatal (a non-zero fetcher exit is marked skipped and the analysis still runs, exactly as for crashlytics); pending refs are advisories. Full dispatch table, exit-code handling, prompt injection shape, log line shape: `$HOME/.claude/multi-agent-refs/features/external-context-injection.md`.
@@ -367,3 +367,35 @@ bash $HOME/.claude/scripts/phase-tracker.sh tokens 3 <input_count> <output_count
367
367
  The tracker accumulates the totals additively, so multiple calls in the same phase compound. The render output then shows live cost on the active phase tile (e.g. `Phase 3 Dev 2m 14s · 12.4k tok`). This satisfies the contract in `$HOME/.claude/multi-agent-refs/tracker-contract.md` and the `smoke-tracker-tokens-invocation.sh` enforcement gate. Skipping this call is the #1 cause of "I can't see how much it cost" complaints.
368
368
 
369
369
  If you do not have access to the model's reported token counts, pass best-effort estimates derived from input length / output length - partial cost data is better than none.
370
+
371
+
372
+ #### Generated trees are not yours to edit
373
+
374
+ Many repos generate part of their source: a service client from an OpenAPI spec, mock
375
+ scenario indexes, localization keys, testing identifiers, design tokens. A generated
376
+ file is regenerated on the next build, so an edit there is lost silently, and the
377
+ matching hand-authored tree is the one that takes the change.
378
+
379
+ Before writing into any path, check whether it is generated:
380
+
381
+ ```bash
382
+ # a Generated/ segment, or a header saying so, is the signal
383
+ find . -type d -name Generated -not -path './.*' | head
384
+ grep -rl "DO NOT EDIT\|auto-generated\|Generated by" --include="*.swift" --include="*.kt" . | head
385
+ ```
386
+
387
+ The pairing is usually `Generated/<x>` for output and `Custom<X>/` or
388
+ `CustomSources/` for input. Two concrete shapes seen in the wild:
389
+
390
+ | Want to | Wrong place | Right place |
391
+ |---|---|---|
392
+ | add a mock fixture / named scenario | a `Fixtures/` file under a generated tree | the repo's custom fixture tree, plus registering the scenario in the generated index the build reads |
393
+ | add or change a service endpoint | the generated client method | the OpenAPI source the generator consumes, then regenerate |
394
+
395
+ One run wrote a mock fixture into the generated fixtures tree; the fix commit moved it
396
+ to the custom tree and registered the scenario in the generated index. Same content,
397
+ wrong side of the generator, and the Debug menu never showed it.
398
+
399
+ When the analysis doc has not recorded which trees are generated, that is a Phase 1
400
+ gap - say so rather than guessing, since guessing wrong is invisible until the next
401
+ regeneration.
@@ -272,6 +272,32 @@ Each reviewer inherits the `code-reviewer` agent's focus areas (Security, Archit
272
272
 
273
273
  Skills are injected into reviewer prompt context - the reviewer uses them as reference, not as commands.
274
274
 
275
+ #### Step 2.8 - Visual conformance gate (component / screen work only)
276
+
277
+ Runs when `state.taskType == "component"` **or** the diff touches SwiftUI UI files
278
+ AND the task carried a Figma reference. Two checks, in order:
279
+
280
+ 1. **`ai-ios-engineering-toolkit:figma-review`** over the implemented frames - the
281
+ plugin's own component review, including the 14-item checklist that covers design
282
+ tokens, accessibility identifiers, previews and Code Connect.
283
+ 2. **`/multi-agent:design-check`** for pixel + spacing + typography + colour
284
+ conformance against the Figma variants, with its coverage gate: a variant that is
285
+ neither audited nor skipped-with-a-reason fails the run.
286
+
287
+ **Code Connect must be published, not merely written.** A `*.figma.swift` file on
288
+ disk with `Code Connect: Not published` in Figma means the binding does not exist for
289
+ anyone but the author. Assert the publish step ran; an unpublished binding is a
290
+ blocking finding.
291
+
292
+ Why this is a gate and not advice: `design-check` existed as a command for a while
293
+ with **no phase invoking it**, so the only thing standing between a build and visual
294
+ drift was the user opening the app and looking. On one run that produced 16pt padding
295
+ where the frame said `Spacing/12`, and a full sheet rebuild afterwards. A reviewer
296
+ reading a diff cannot see spacing; something has to compare against the design.
297
+
298
+ Skip only when the diff has no UI change. Record the outcome in
299
+ `consensus.visualConformance` so Phase 7 reports whether it ran.
300
+
275
301
  **iOS/Swift - interaction & convention checks (conditional).** When the diff touches SwiftUI UI files (`*View.swift`, `*Screen.swift`, `*Configuration.swift`, `*+Modifiers.swift`), the iOS reviewers additionally apply the component/interaction conventions documented in the analysis doc (Section 14 Code Connect mapping) and, where the repo has the marketplace component toolkit enabled (`ai-ios-engineering-toolkit`), that plugin's navigation / overlay / bottom-sheet conventions (interaction: emit-intent vs self-route/self-present; native-SwiftUI-first vs the project's `ui.*` custom system) plus the component accessibility rules (minimalism). These back the Step 1.5 iOS convention checks. Generic across SwiftUI projects - not tied to any one app. Omit when the diff has no SwiftUI UI changes (keeps the reviewer prompt lean).
276
302
 
277
303
  **Module review guides (conditional, all stacks).** A module in the repo may carry its own CLAUDE guide - a convention/checklist file living somewhere in the module's directory tree that the host CLI never auto-loads. When a changed file's module has such a guide, the review must consult it. Discovery is deterministic, from the diff's changed paths: for each changed file, walk its directory chain up to the repo root and collect `CLAUDE.md`, `*-CLAUDE.md`, and `AGENTS.md` files (root-level ones excluded - the host CLI already loads those). Dedupe, cap at 5 (log any dropped). Inject into every reviewer prompt with the directive: read each guide before reviewing and apply its rules/checklist to the changed files under its directory - a guide governs only its own subtree, and guide violations are findings triaged by severity like any other. No guides found → no-op, prompt stays lean. Same contract as `/multi-agent:review` Step 2b.
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * phase0-exit-gate.mjs - Phase 0 may not be marked completed until it has
4
+ * actually produced its own output.
5
+ *
6
+ * Why this exists. Phase 0 Step 7 says to classify the task and "persist to
7
+ * `agent-state.json.taskType`", and Phase 3 branches on that field to route a
8
+ * Figma-driven task to the component skills (`create-screen` / `create-component`
9
+ * in the stack plugin) instead of generic development. On a real run the tracker
10
+ * showed Phase 0 `completed` while the task directory held only
11
+ * `tracker-state.json` - no `agent-state.json` at all. With no `taskType`, the
12
+ * dispatch could not fire, so a Figma-driven screen was built as generic work: no
13
+ * token-compliance check, no Code Connect publish, no 14-item component review,
14
+ * and spacing guessed at 16 where the frame said `Spacing/12`. Half the commits on
15
+ * that branch were rework.
16
+ *
17
+ * A phase that reports success without its output is worse than one that fails:
18
+ * every later phase then reasons from a field that is not there. So this is a
19
+ * gate, not a lint - the spec already said what to write, and prose alone did
20
+ * not make it happen.
21
+ *
22
+ * Usage:
23
+ * node phase0-exit-gate.mjs <task_id> [--input "<original user input>"] [--json]
24
+ *
25
+ * Exit codes: 0 = pass, 1 = gate failure (Phase 0 must not be closed), 2 = usage
26
+ *
27
+ * @module pipeline/scripts/phase0-exit-gate
28
+ */
29
+
30
+ import { existsSync, readFileSync } from "fs";
31
+ import { join } from "path";
32
+ import { homedir } from "os";
33
+
34
+ const FIGMA_URL = /figma\.com\/(design|make|file)\//i;
35
+
36
+ /** Fields that can carry the user's original request text. */
37
+ const INPUT_TEXT_FIELDS = [
38
+ ["input", "summary"],
39
+ ["input", "raw"],
40
+ ["issue", "body"],
41
+ ["issue", "title"],
42
+ ["jira", "summary"],
43
+ ["jira", "description"],
44
+ ["task", "description"],
45
+ ];
46
+
47
+ /**
48
+ * Pull a nested value without throwing on a missing branch.
49
+ * @param {object} obj
50
+ * @param {string[]} path
51
+ * @returns {unknown}
52
+ */
53
+ function at(obj, path) {
54
+ return path.reduce((acc, k) => (acc && typeof acc === "object" ? acc[k] : undefined), obj);
55
+ }
56
+
57
+ /**
58
+ * Collect every string in the state that could hold the original request, so a
59
+ * Figma reference is found wherever the input parser happened to store it.
60
+ *
61
+ * @param {object} state
62
+ * @returns {string}
63
+ */
64
+ export function inputTextOf(state) {
65
+ const parts = [];
66
+ for (const path of INPUT_TEXT_FIELDS) {
67
+ const v = at(state, path);
68
+ if (typeof v === "string") parts.push(v);
69
+ }
70
+ // evidence[].url / figmaFrames[] are where the analysis phase parks references.
71
+ for (const key of ["figmaFrames", "designRefs"]) {
72
+ const v = state[key];
73
+ if (Array.isArray(v)) parts.push(v.map((x) => (typeof x === "string" ? x : JSON.stringify(x))).join(" "));
74
+ }
75
+ return parts.join("\n");
76
+ }
77
+
78
+ /**
79
+ * Evaluate the gate against a parsed state object.
80
+ *
81
+ * Kept pure and exported so the smoke can drive it without laying down a task
82
+ * directory: a gate that can only be tested by reproducing a full run does not
83
+ * get tested.
84
+ *
85
+ * @param {object|null} state - parsed agent-state.json, or null when absent
86
+ * @param {string} extraInput - input text supplied on the command line
87
+ * @returns {{ok: boolean, failures: string[], taskType: string|undefined, figmaSeen: boolean}}
88
+ */
89
+ export function evaluate(state, extraInput = "") {
90
+ const failures = [];
91
+
92
+ if (state === null) {
93
+ return {
94
+ ok: false,
95
+ failures: [
96
+ "agent-state.json is missing. Phase 0 owns this file; without it taskType, " +
97
+ "maturity, account and repo resolution are all unreadable by later phases.",
98
+ ],
99
+ taskType: undefined,
100
+ figmaSeen: FIGMA_URL.test(extraInput),
101
+ };
102
+ }
103
+
104
+ const taskType = typeof state.taskType === "string" ? state.taskType.trim() : "";
105
+ if (!taskType) {
106
+ failures.push(
107
+ "agent-state.json has no taskType. Phase 3 branches on it: without the field " +
108
+ "a component task is dispatched as generic development (Step 7 of phase-0-init).",
109
+ );
110
+ }
111
+
112
+ const haystack = `${inputTextOf(state)}\n${extraInput}`;
113
+ const figmaSeen = FIGMA_URL.test(haystack);
114
+
115
+ if (figmaSeen && taskType && taskType !== "component") {
116
+ failures.push(
117
+ `the input carries a Figma URL but taskType is "${taskType}". Step 7 rule 1 makes ` +
118
+ `"component" mandatory here - otherwise the run skips the stack plugin's ` +
119
+ `token-compliance check, Code Connect publish and component review.`,
120
+ );
121
+ }
122
+
123
+ // The Figma access chain records which tier answered. A component task with no
124
+ // recorded tier means nothing verified that the design was actually reachable,
125
+ // which is how a run ends up guessing spacing.
126
+ if (figmaSeen) {
127
+ const tier = at(state, ["figmaAccess", "tier"]);
128
+ if (tier === undefined || tier === null || tier === "") {
129
+ failures.push(
130
+ "the input carries a Figma URL but state.figmaAccess.tier is unset. The 3-tier " +
131
+ "access chain must record which tier answered, so a later phase can tell " +
132
+ "'design confirmed' from 'design never fetched'.",
133
+ );
134
+ }
135
+ }
136
+
137
+ return { ok: failures.length === 0, failures, taskType: taskType || undefined, figmaSeen };
138
+ }
139
+
140
+ /** @param {string} taskId */
141
+ function statePathFor(taskId) {
142
+ return join(homedir(), ".claude", "logs", "multi-agent", taskId, "agent-state.json");
143
+ }
144
+
145
+ function main(argv) {
146
+ const args = argv.slice(2);
147
+ const taskId = args.find((a) => !a.startsWith("--"));
148
+ if (!taskId) {
149
+ console.error("usage: phase0-exit-gate.mjs <task_id> [--input \"<text>\"] [--json]");
150
+ return 2;
151
+ }
152
+ const inputIdx = args.indexOf("--input");
153
+ const extraInput = inputIdx >= 0 ? (args[inputIdx + 1] ?? "") : "";
154
+ const asJson = args.includes("--json");
155
+
156
+ const path = statePathFor(taskId);
157
+ let state = null;
158
+ if (existsSync(path)) {
159
+ try {
160
+ state = JSON.parse(readFileSync(path, "utf-8"));
161
+ } catch (e) {
162
+ console.error(`phase0-exit-gate: ${path} is not valid JSON: ${e.message}`);
163
+ return 1;
164
+ }
165
+ }
166
+
167
+ const result = evaluate(state, extraInput);
168
+
169
+ if (asJson) {
170
+ console.log(JSON.stringify({ taskId, statePath: path, ...result }, null, 2));
171
+ } else if (result.ok) {
172
+ console.log(
173
+ `phase0-exit-gate: PASS (taskType=${result.taskType}${result.figmaSeen ? ", figma reference present" : ""})`,
174
+ );
175
+ } else {
176
+ console.error("phase0-exit-gate: FAIL - Phase 0 must not be marked completed");
177
+ for (const f of result.failures) console.error(` - ${f}`);
178
+ console.error(` state: ${path}`);
179
+ }
180
+ return result.ok ? 0 : 1;
181
+ }
182
+
183
+ if (import.meta.url === `file://${process.argv[1]}`) {
184
+ process.exit(main(process.argv));
185
+ }
@@ -62,3 +62,24 @@ Phase 7: Report → Channels (Jira / Confluence / PR / Wiki)
62
62
  | Phase 5 User Test | ✅ | ✅ (same) |
63
63
  | Phase 7 channels (Jira / Confluence / PR / Wiki) | ✅ | ✅ (same) |
64
64
  | Duration | ~10-15 min | ~5-7 min |
65
+
66
+ ## Analysis doc supplied to a fast mode (warn before starting)
67
+
68
+ The `--dev` family skips Phase 1 (Analysis) and Phase 2 (Planning) by design. So when
69
+ the input references an analysis document - a Confluence URL, a local analysis file,
70
+ or the user says "I ran analysis for this" - there is **no phase that turns it into a
71
+ plan**. The doc becomes raw context for one Dev pass, and work comes out ordered by
72
+ whatever the model read first: the bottom of the dependency chain lands, the screen
73
+ wiring does not.
74
+
75
+ Say so before starting, once, and offer the choice:
76
+
77
+ ```
78
+ This mode skips Analysis and Planning, so the analysis document will not be turned
79
+ into a task breakdown. For analysis-driven screen work, /multi-agent or
80
+ /multi-agent:local run both phases.
81
+ 1. Continue with --dev (doc as context only)
82
+ 2. Switch to the full pipeline
83
+ ```
84
+
85
+ Autopilot picks 1 and logs the warning rather than asking.
@@ -34,3 +34,24 @@ Routes to the orchestrator with the `--dev --local` flags. The pipeline contract
34
34
  multi-agent-dev-local "PROJ-12345" # Jira
35
35
  multi-agent-dev-local "Bug: LoginView dark mode" # Free-text
36
36
  ```
37
+
38
+ ## Analysis doc supplied to a fast mode (warn before starting)
39
+
40
+ The `--dev` family skips Phase 1 (Analysis) and Phase 2 (Planning) by design. So when
41
+ the input references an analysis document - a Confluence URL, a local analysis file,
42
+ or the user says "I ran analysis for this" - there is **no phase that turns it into a
43
+ plan**. The doc becomes raw context for one Dev pass, and work comes out ordered by
44
+ whatever the model read first: the bottom of the dependency chain lands, the screen
45
+ wiring does not.
46
+
47
+ Say so before starting, once, and offer the choice:
48
+
49
+ ```
50
+ This mode skips Analysis and Planning, so the analysis document will not be turned
51
+ into a task breakdown. For analysis-driven screen work, /multi-agent or
52
+ /multi-agent:local run both phases.
53
+ 1. Continue with --dev (doc as context only)
54
+ 2. Switch to the full pipeline
55
+ ```
56
+
57
+ Autopilot picks 1 and logs the warning rather than asking.