@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 +59 -0
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/dev/SKILL.md +21 -0
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +21 -0
- package/pipeline/multi-agent-refs/component-dispatch.md +40 -7
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +29 -0
- package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +24 -1
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +32 -0
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +26 -0
- package/pipeline/scripts/phase0-exit-gate.mjs +185 -0
- package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +21 -0
- package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +21 -0
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.
|
|
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 →
|
|
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.
|
|
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.
|