@mmerterden/multi-agent-pipeline 13.0.0 → 13.2.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 +111 -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/commands/multi-agent/ios-coding-standard/SKILL.md +257 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +4 -4
- package/pipeline/multi-agent-refs/component-dispatch.md +40 -7
- package/pipeline/multi-agent-refs/cross-cli-contract.md +3 -3
- 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/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +258 -0
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +4 -4
- package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +78 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +445 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/lint-local.sh +160 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +1163 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/swiftlint.draft.yml +371 -0
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +1 -1
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +1 -1
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +1 -1
|
@@ -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.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: multi-agent-ios-coding-standard
|
|
3
|
+
language: en
|
|
4
|
+
description: "Audit an iOS module against the shared coding-standard registry (95 stable-ID rules), produce a remediation plan, then hand off to dev/dev-local. Use for a standards pass on a module, or when a review needs rule IDs rather than opinions."
|
|
5
|
+
user-invocable: true
|
|
6
|
+
argument-hint: "[module name or path]"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# multi-agent ios-coding-standard — Module audit → plan → dev handoff
|
|
10
|
+
|
|
11
|
+
**Input**: $ARGUMENTS — optionally a module name or path. When absent, Phase 1 discovers and asks.
|
|
12
|
+
|
|
13
|
+
This routine is the **procedure**. The rules live in the `ios-coding-standard` skill, whose registry is
|
|
14
|
+
`references/rules.yml` and whose teaching doc is `references/STANDARD.md`. Load that
|
|
15
|
+
skill first; it is installed on every host and carried by the iOS stack plugin, so
|
|
16
|
+
there is one registry rather than a copy per CLI. Never restate a rule here — cite its ID. A rule that is not in the
|
|
17
|
+
registry is not a rule; if the audit needs one, propose it as `status: proposed` and say so.
|
|
18
|
+
|
|
19
|
+
Read-only + planning. This routine never edits source — the dev pipeline does.
|
|
20
|
+
|
|
21
|
+
**Goal.** A developer new to the repo can open any file in the module and understand it without a
|
|
22
|
+
guided tour, and nothing sensitive leaks on the way. Rank every finding by "does fixing this
|
|
23
|
+
shorten the time to first productive PR, or close a real risk?"
|
|
24
|
+
|
|
25
|
+
Render assistant-facing prose in `outputLanguage`. External payloads (branch names, plan file,
|
|
26
|
+
dev task) stay English.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Phase 1 — Discover and pick the module
|
|
31
|
+
|
|
32
|
+
1. Repo root via `git rev-parse --show-toplevel`.
|
|
33
|
+
2. **Discover module roots** — any directory with a `Package.swift`, a `Sources/` subtree or an
|
|
34
|
+
`.xcodeproj`. Sweep the container dirs that exist (`Domains/`, `Packages/`, `Modules/`,
|
|
35
|
+
`Features/`, `Core/`, `Common/`, top-level module dirs) one level deep, then two if empty.
|
|
36
|
+
3. Record per module: name · path · Swift files · lines · governance docs present · SPM targets.
|
|
37
|
+
Keep this **module registry** for the whole run — `MOD-*` greps every import against it.
|
|
38
|
+
Classify each module's role, because the role decides which dependency edges are legal:
|
|
39
|
+
**feature** · **core/common** · **seam** (cross-module contracts/bridges/navigation) ·
|
|
40
|
+
**composition root** (legitimately knows every module).
|
|
41
|
+
4. If `$ARGUMENTS` resolves to a module, skip the prompt. Otherwise `AskUserQuestion`
|
|
42
|
+
(single-select, `outputLanguage`), documented modules first, each option showing file/line
|
|
43
|
+
counts and `docs: yes/no`. If the list exceeds the question limit, group the smallest into an
|
|
44
|
+
"other" option and ask again — never truncate silently.
|
|
45
|
+
5. Scope = the whole module's sources across all its targets, unless the user narrows it.
|
|
46
|
+
|
|
47
|
+
## Phase 2 — Resolve what applies to THIS module
|
|
48
|
+
|
|
49
|
+
### 2a. The registry is the standard — in-module prose docs are NOT consulted
|
|
50
|
+
|
|
51
|
+
The skill's `references/rules.yml` (+ a project `modules/<Module>.yml` overlay) is the single source of truth. **Do not read, cite, or
|
|
52
|
+
derive rules from a module's own `*-CLAUDE.md`, `docs/` set or any other in-repo prose.** They are
|
|
53
|
+
being retired precisely so there is one place a rule can live; consulting them re-creates the
|
|
54
|
+
split this command exists to remove. If such a file exists and contradicts the registry, that is a
|
|
55
|
+
finding against the file, not against the code.
|
|
56
|
+
|
|
57
|
+
The only in-repo inputs are **code and manifests**: sources, tests, `Package.swift`, `Info.plist`,
|
|
58
|
+
the specs the generator reads, and the module registry from Phase 1.
|
|
59
|
+
|
|
60
|
+
### 2b. Per-module overlay, then inference
|
|
61
|
+
|
|
62
|
+
1. **`modules/<Module>.yml`** beside the project's own config carries what code cannot state: the vocabulary
|
|
63
|
+
bindings, the module's role, prohibitions that currently have **zero** instances, deliberate
|
|
64
|
+
carve-outs, name locks, and the real verification command. Registry-backed, so its findings may
|
|
65
|
+
be `blocking`.
|
|
66
|
+
*Why prohibitions need to be written down:* inference reads dominant patterns, and a rule
|
|
67
|
+
obeyed everywhere has no counter-example to infer from. A ban at 100% compliance is invisible
|
|
68
|
+
to a scan — it survives only if the overlay states it.
|
|
69
|
+
2. **Repo-level rules** (root `CLAUDE.md`, contributing docs) apply to every module.
|
|
70
|
+
3. **The module's own dominant pattern is its de-facto convention.** Count variants per dimension;
|
|
71
|
+
the minority instances are the finding. Internal consistency beats conformity to a sibling.
|
|
72
|
+
4. No majority (even split, single instance) → not a finding. Record an Open Question.
|
|
73
|
+
|
|
74
|
+
Findings from step 3 are tagged `inferred` and capped at **suggestion**. Only registry- or
|
|
75
|
+
overlay-backed rules may be `blocking`.
|
|
76
|
+
|
|
77
|
+
**When the overlay is missing**, generate a draft from code evidence, print it for confirmation,
|
|
78
|
+
and mark every slot it could not derive — a frozen UI target, a name lock, a carve-out and an
|
|
79
|
+
operational constraint all look like ordinary code from the outside. Do not guess them; list them
|
|
80
|
+
as Open Questions.
|
|
81
|
+
|
|
82
|
+
### 2c. Vocabulary
|
|
83
|
+
|
|
84
|
+
Bind each slot to a real symbol by grepping the module, then the reference module. **An unbound
|
|
85
|
+
slot disables its rules** — never invent a name, never import a sibling's.
|
|
86
|
+
|
|
87
|
+
`HandlerName` · `EventParam` · `CoordinatorType` · `ResultType` / `ErrorType` / `ErrorFactory` ·
|
|
88
|
+
`DIResolver` / `DIConfigurator` · `RequestModelSuffix` / `ResponseModelSuffix` / `TransportSuffix` ·
|
|
89
|
+
`MapperShape` · `SafeEnumProtocol` / `UnknownCase` · `CopySurface` · `AnalyticsSurface` ·
|
|
90
|
+
`A11yIdentifierSource` · `GeneratedServiceRoot` · `SharedComponentsDir` / `SharedEntitiesDir` ·
|
|
91
|
+
`MicroComponentLibrary` · `FrozenUITarget` · `DesignTokenNamespaces` · `CalendarDayHelper` ·
|
|
92
|
+
`LoggerAPI` · `FileHeaderShape` · `ScreenRoot` · `ScreenRoleSuffixes` · `AsyncStyle` ·
|
|
93
|
+
`CompositionRoot` · `SeamLayer` · `ModuleEntrySurface` · `ConcurrencyPosture` · `LintToolchain` ·
|
|
94
|
+
`CredentialStore` (the module's Keychain wrapper, if any).
|
|
95
|
+
|
|
96
|
+
Print the resolved table before scanning so a wrong binding is caught early.
|
|
97
|
+
|
|
98
|
+
### 2d. Sensitive-data inventory — required before any SEC rule runs
|
|
99
|
+
|
|
100
|
+
The `SEC-*` rules are written against the **data classes** in `references/rules.yml →
|
|
101
|
+
sensitive_data_classes`, never against one module's field names. Resolve the module's concrete
|
|
102
|
+
instances of each class: grep entities, request/response models, storage calls and analytics
|
|
103
|
+
events for the values the module actually handles, and map each to its class.
|
|
104
|
+
|
|
105
|
+
Produce the inventory as a table — **class · concrete symbols · where it enters · required
|
|
106
|
+
lifetime (transient / survives-restart / survives-reinstall) · where it is actually stored ·
|
|
107
|
+
where it is logged · where it leaves (network, analytics, pasteboard, another module)**.
|
|
108
|
+
|
|
109
|
+
The **lifetime column is the one that decides SEC-01**, and it is filled by walking
|
|
110
|
+
`references/rules.yml → persistence_decision`, not by looking at what the code currently does. Compare it
|
|
111
|
+
against the storage column and report both mismatches:
|
|
112
|
+
- required transient but persisted anywhere (including the Keychain) → **over-persistence**;
|
|
113
|
+
- required to survive a restart but stored outside the Keychain → **under-protection**.
|
|
114
|
+
|
|
115
|
+
Then generate the SEC lint patterns *from this inventory*, so the checks match this module's real
|
|
116
|
+
vocabulary. A class with no instance here yields no finding; a symbol you cannot classify, or one
|
|
117
|
+
whose required lifetime nobody can state, is an Open Question, not a silent pass.
|
|
118
|
+
|
|
119
|
+
This table is a deliverable in its own right, independent of whether any violation is found.
|
|
120
|
+
|
|
121
|
+
## Phase 3 — Scan
|
|
122
|
+
|
|
123
|
+
Apply the rules in `references/rules.yml` that survived Phase 2 binding, plus the module's own `validation`
|
|
124
|
+
gate. Tag every finding: **rule ID · severity · source (`registry` / `doc` / `inferred`) ·
|
|
125
|
+
onboarding impact (high/medium/low)**.
|
|
126
|
+
|
|
127
|
+
### Coverage gate — on disk, not in memory
|
|
128
|
+
|
|
129
|
+
Before scanning, write `.<module>-audit-coverage.tsv` to the scratch dir: one row per target
|
|
130
|
+
(every screen under `ScreenRoot`, every shared component, every entity, every file in every
|
|
131
|
+
target) with columns `path · status · rules-applied`. Mark rows as you go.
|
|
132
|
+
|
|
133
|
+
- Each target is **audited** or **explicitly skipped with a reason** (generated, fixture,
|
|
134
|
+
vendored). No third state.
|
|
135
|
+
- **The run is incomplete while any row is unmarked** — and the file, not a claim, is the proof.
|
|
136
|
+
Chunk the module if it is too large for one pass; the checklist survives a context reset.
|
|
137
|
+
- No sampling. A partial audit reported as complete certifies the unscanned screens as clean.
|
|
138
|
+
|
|
139
|
+
### Grep hygiene
|
|
140
|
+
|
|
141
|
+
Registry `mechanism` patterns are line-based and match doc comments. Filter
|
|
142
|
+
`^[[:space:]]*//` — BSD grep does not understand `\s`, so a `\s*//` filter silently lets `/// …`
|
|
143
|
+
through — then open each surviving hit and confirm it is real code. Report pre-filter and
|
|
144
|
+
post-verification counts separately; a table padded with comment matches destroys trust.
|
|
145
|
+
|
|
146
|
+
### Judgement rules need evidence, not assertion
|
|
147
|
+
|
|
148
|
+
For any rule marked `enforcement: judgement`, a finding must carry the measurement its `check`
|
|
149
|
+
names — a reference count, a call-site count, a consumer count, a file list. **No count, no
|
|
150
|
+
finding.** This is what keeps a subjective rule from becoming an opinion.
|
|
151
|
+
|
|
152
|
+
Every judgement rule has a worked ✗/✓ pair in `EXAMPLES.md`, keyed by ID. Cite it in the finding's
|
|
153
|
+
proposed fix rather than re-describing the shape — the developer applying the fix should be
|
|
154
|
+
reading the same picture the audit used.
|
|
155
|
+
|
|
156
|
+
### Module-specific overrides
|
|
157
|
+
|
|
158
|
+
The module's own docs win over the registry, including where they contradict a sibling module.
|
|
159
|
+
Never apply one module's dialect to another. List every override honoured, so the reader sees why
|
|
160
|
+
a registry rule was not raised.
|
|
161
|
+
|
|
162
|
+
## Phase 4 — Produce the plan
|
|
163
|
+
|
|
164
|
+
No findings → report compliant and stop. Otherwise write
|
|
165
|
+
`<ModulePath>/ios-coding-standard-plan-<module>.md` (or the repo's scratch/docs dir if the module
|
|
166
|
+
tree must stay clean), containing, in order:
|
|
167
|
+
|
|
168
|
+
1. **Coverage ledger** — total targets · audited · skipped with reasons. Not 100% accounted for
|
|
169
|
+
means the plan is not deliverable.
|
|
170
|
+
2. **Summary** — counts by severity and source, resolved vocabulary, branch to create.
|
|
171
|
+
3. **Sensitive-data inventory** (Phase 2d) — first, because it is the highest-risk content.
|
|
172
|
+
4. **Screen × role matrix** `[STRUCT-02]` — screens as rows, manifest roles as columns, cells
|
|
173
|
+
`✓ / missing / misplaced`. The structural map a newcomer reads first.
|
|
174
|
+
5. **Type placement table** `[STRUCT-05]` — type · location · consumer count · correct tier ·
|
|
175
|
+
move required. Paired with the nested-type list `[STRUCT-01]`: owner · nested type · kind ·
|
|
176
|
+
reference count · verdict.
|
|
177
|
+
6. **Visibility report** `[VIS-01, VIS-02]` — over-exposed declarations per file, non-final
|
|
178
|
+
classes with no subclass.
|
|
179
|
+
7. **Boundary report** `[MOD-*]` — import graph (allowed vs forbidden, with coupling depth) ·
|
|
180
|
+
manifest graph (declared vs legal for the role, plus dead edges) · **removability delta**
|
|
181
|
+
("removing X touches N files: …") · inbound test (tests compile with no sibling present).
|
|
182
|
+
8. **Testability seam report** `[TEST-*]` — types reaching for the environment, logic requiring a
|
|
183
|
+
view to execute, doubles whose signatures have drifted.
|
|
184
|
+
9. **Change-cost probe** `[FLEX-*]` — pick two plausible upcoming changes (a new variant of an
|
|
185
|
+
existing screen; a new field on a shared entity) and state what each costs in files today.
|
|
186
|
+
10. **Findings table** — rule ID · `file:line` · current state · proposed fix · severity · source ·
|
|
187
|
+
onboarding impact.
|
|
188
|
+
11. **Per-file work** — grouped per file, blocking → important → suggestion; within a severity,
|
|
189
|
+
highest onboarding impact first.
|
|
190
|
+
12. **Behaviour-preservation guard** — splitting files, adding MARKs, extracting extensions and
|
|
191
|
+
moving types must not change behaviour. Anything requiring a behaviour change (a missing
|
|
192
|
+
analytics event, a wrong result type, a timezone fix, a storage migration) goes in a
|
|
193
|
+
**separate group**, reviewed and tested on its own, never inside a mechanical split commit.
|
|
194
|
+
13. **Tooling delegation & the ratchet** — the section that decides whether any of this survives.
|
|
195
|
+
- Classify each applied rule by its registry `enforcement`. For every `lint` / `format` /
|
|
196
|
+
`scan` rule, state the concrete mechanism and whether that toolchain exists in the repo
|
|
197
|
+
(`LintToolchain`). **When it does not exist, saying so is a finding**, ranked above most
|
|
198
|
+
individual violations it would have caught.
|
|
199
|
+
- Recommend **baseline-and-ratchet, never big-bang**: grandfather existing violations, surface
|
|
200
|
+
only new ones, drain the backlog behind a green build. Order the plan the same way — stop
|
|
201
|
+
the bleeding, then clean up.
|
|
202
|
+
- Custom lint rule identifiers **are** the registry IDs (`sec_01_no_plaintext_persistence`,
|
|
203
|
+
`mod_01_no_sibling_import`) so a violation points straight at the rule entry.
|
|
204
|
+
- **Linting is local-only by decision** — the skill's `references/lint-local.sh` with `references/swiftlint.draft.yml`, config
|
|
205
|
+
and baseline kept outside the repository; no committed config, no build phase, no CI job.
|
|
206
|
+
State this honestly in the plan rather than describing a gate that does not exist: a
|
|
207
|
+
pre-PR habit catches less than a wall, so the residual risk is real and belongs in the
|
|
208
|
+
report. Adopting it project-wide remains an open decision — record it as one so it stays
|
|
209
|
+
visible instead of quietly lapsing.
|
|
210
|
+
- Never imply the audit substitutes for the linter, or the linter for a gate. The audit finds
|
|
211
|
+
the debt once; the linter keeps a developer honest; only a gate stops it returning.
|
|
212
|
+
14. **Numeric exit criteria** — target and today's measured value side by side: lint violations
|
|
213
|
+
per 1k lines (<1) · public-surface ratio, public declarations ÷ externally consumed symbols
|
|
214
|
+
(≈1) · forbidden cross-module imports (0) · removability delta · files over threshold and
|
|
215
|
+
files >120 lines with no sectioning (0) · screens missing a manifest role (0) · escape-hatch
|
|
216
|
+
count `[CONC-03]` (flat or falling) · exception-marker count (flat or falling) · dead code (0)
|
|
217
|
+
· screens with logic and no mirrored tests (0) · unclassified sensitive symbols (0).
|
|
218
|
+
15. **Exception ledger** — every `// standard:exception(<ID>)` in the module: rule · reason ·
|
|
219
|
+
expiry · expired yes/no.
|
|
220
|
+
16. **Open Questions** — Phase 2b dimensions with no majority, and unclassifiable symbols.
|
|
221
|
+
17. **Suggested commit slicing** — one commit per file-group / concern.
|
|
222
|
+
18. **Scope guard** — only this module is touched; definition of done is the module's own
|
|
223
|
+
verification path.
|
|
224
|
+
|
|
225
|
+
Then write a **separate one-page onboarding summary** beside the plan, aimed at a developer
|
|
226
|
+
joining next week rather than at whoever does the remediation: the ten always/never lines
|
|
227
|
+
specialised to this module, the screen file manifest, the placement ladder, the sensitive-data
|
|
228
|
+
classes in play, and the verification command. Nobody learns a codebase from a 40-finding audit —
|
|
229
|
+
this page is the actual deliverable, and the audit is how you earn the right to write it
|
|
230
|
+
accurately. Derive it from `references/STANDARD.md § 0`, do not re-invent it.
|
|
231
|
+
|
|
232
|
+
Show a concise version of the plan to the user too.
|
|
233
|
+
|
|
234
|
+
## Phase 5 — Hand off
|
|
235
|
+
|
|
236
|
+
`AskUserQuestion` (single-select, `outputLanguage`):
|
|
237
|
+
|
|
238
|
+
| Option | What it does |
|
|
239
|
+
|---|---|
|
|
240
|
+
| `/multi-agent:dev-local` | No worktree; branches from the main development branch, fixes on the current checkout |
|
|
241
|
+
| `/multi-agent:dev` | Opens a worktree; Opus fixes on a fresh branch + PR |
|
|
242
|
+
|
|
243
|
+
Invoke the chosen command with the plan file as the task input, branching off the repo's main
|
|
244
|
+
development branch (e.g. `chore/<module>-coding-standard`). The dev pipeline applies the fixes,
|
|
245
|
+
verifies, and commits only when asked. This routine ends at the handoff.
|
|
246
|
+
|
|
247
|
+
## Notes
|
|
248
|
+
|
|
249
|
+
- **Verification reality is per-module — establish it, do not assume it.** Some targets cannot be
|
|
250
|
+
compiled from the CLI (asset symbol generation, UIKit-dependent packages, a dependency failing
|
|
251
|
+
earlier in the graph); there, verification is Xcode and a manifest dump validates the manifest
|
|
252
|
+
only. Never trust a piped build's exit code — a pipe reports the tail's status. Record the
|
|
253
|
+
module's actual verification command in the plan.
|
|
254
|
+
- One module per run. Several modules means several runs and several plans, never one
|
|
255
|
+
cross-module churn commit.
|
|
256
|
+
- If the audit wants a rule the registry lacks, add it to the skill's `references/rules.yml` as `status: proposed` with a
|
|
257
|
+
rationale and surface it in Open Questions. Do not enforce an unregistered rule.
|
|
258
|
+
- No AI attribution anywhere (code, commits, PRs). Author = git identity.
|
|
@@ -31,8 +31,8 @@ Run all steps automatically:
|
|
|
31
31
|
|
|
32
32
|
```
|
|
33
33
|
Step 1: DETECT Compare timestamps, find stale targets
|
|
34
|
-
Step 2: COPILOT Claude Code -> Copilot CLI (instructions +
|
|
35
|
-
Step 2b: CODEX Claude Code -> Codex CLI (1 router skill +
|
|
34
|
+
Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 44 sub-command skills)
|
|
35
|
+
Step 2b: CODEX Claude Code -> Codex CLI (1 router skill + 44 specs as refs + 8 agent TOML)
|
|
36
36
|
Step 3: REPO Claude Code -> pipeline repo (genericized, personal data scrub)
|
|
37
37
|
Step 3d: DEV-TOOLKIT Companion MCP server -> detect movement, ship gates, commit + publish
|
|
38
38
|
Step 4: WEBSITE Version + phase/model counts -> {website-host} (i18n + projects.ts)
|
|
@@ -223,12 +223,12 @@ When invoked with the `release` argument:
|
|
|
223
223
|
|-------------|-------------|
|
|
224
224
|
| `~/.claude/commands/multi-agent/{cmd}.md` | `~/.copilot/skills/multi-agent-{cmd}/SKILL.md` |
|
|
225
225
|
|
|
226
|
-
**
|
|
226
|
+
**44 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
|
|
227
227
|
|
|
228
228
|
```
|
|
229
229
|
analysis, analysis-resolve, autopilot, build-optimize, channels, create-jira, design-check, dev,
|
|
230
230
|
dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, forget, garbage-collect,
|
|
231
|
-
help, issue, jira, kill, language, local,
|
|
231
|
+
help, ios-coding-standard, issue, jira, kill, language, local,
|
|
232
232
|
local-autopilot, log, manual-test, prune-logs, purge, refactor, resume, review, review-issue, review-jira,
|
|
233
233
|
routines, save, scan, search, setup, stack, status, sync, test, testflight-validation, uninstall, update
|
|
234
234
|
```
|