@mmerterden/multi-agent-pipeline 13.5.0 → 14.0.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 +243 -0
- package/README.md +3 -3
- package/docs/features.md +1 -1
- package/install/_common.mjs +73 -0
- package/install/_mcp-register.mjs +70 -31
- package/install/_plugin-skills.mjs +73 -14
- package/install/claude.mjs +28 -4
- package/install/codex.mjs +33 -2
- package/install/copilot.mjs +145 -9
- package/install/index.mjs +10 -6
- package/install/templates/copilot-instructions.md +1 -1
- package/package.json +1 -1
- package/pipeline/agents/code-reviewer.md +58 -1
- package/pipeline/commands/multi-agent/SKILL.md +7 -5
- package/pipeline/commands/multi-agent/analysis/SKILL.md +7 -7
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +7 -7
- package/pipeline/commands/multi-agent/channels/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/dev/SKILL.md +23 -18
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +19 -13
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +14 -12
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +17 -12
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/help/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/review/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/scan/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/search/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +6 -6
- package/pipeline/commands/multi-agent/{finish → ship}/SKILL.md +12 -12
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/update/SKILL.md +5 -2
- package/pipeline/commands/sim-test.md +2 -2
- package/pipeline/lib/credential-store-resolver.sh +16 -0
- package/pipeline/lib/credential-store.sh +47 -4
- package/pipeline/lib/fetch-figma-annotations.sh +26 -28
- package/pipeline/lib/figma-screenshot.sh +28 -39
- package/pipeline/lib/figma-token.sh +63 -0
- package/pipeline/multi-agent-refs/analysis-template.md +1 -1
- package/pipeline/multi-agent-refs/android-guide.md +1 -1
- package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +2 -2
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
- package/pipeline/multi-agent-refs/features/dev-critic.md +2 -2
- package/pipeline/multi-agent-refs/features/model-fallback.md +35 -2
- package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
- package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
- package/pipeline/multi-agent-refs/features/review-multi-repo.md +3 -3
- package/pipeline/multi-agent-refs/features/shadow-git.md +1 -1
- package/pipeline/multi-agent-refs/features/skill-conformance.md +116 -0
- package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
- package/pipeline/multi-agent-refs/generate-issue.md +1 -1
- package/pipeline/multi-agent-refs/multi-repo-integration-build.md +1 -1
- package/pipeline/multi-agent-refs/phases/log-format.md +4 -4
- package/pipeline/multi-agent-refs/phases/modes.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +13 -11
- package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +17 -15
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +28 -13
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +90 -58
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -8
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +8 -8
- package/pipeline/multi-agent-refs/phases.md +13 -13
- package/pipeline/multi-agent-refs/progress-contract.md +2 -2
- package/pipeline/multi-agent-refs/rules.md +7 -5
- package/pipeline/multi-agent-refs/swiftui-guide.md +1 -1
- package/pipeline/multi-agent-refs/tracker-contract.md +16 -15
- package/pipeline/preferences-template.json +7 -1
- package/pipeline/rules/figma-pipeline.md +2 -2
- package/pipeline/schemas/agent-state.schema.json +333 -79
- package/pipeline/schemas/criteria-manifest.schema.json +228 -0
- package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +64 -0
- package/pipeline/schemas/prefs.schema.json +118 -262
- package/pipeline/schemas/reviewer-output.schema.json +48 -3
- package/pipeline/schemas/token-budget.json +34 -10
- package/pipeline/schemas/triage-output.schema.json +112 -27
- package/pipeline/scripts/cost-table.json +7 -4
- package/pipeline/scripts/gc-worktrees.sh +1 -1
- package/pipeline/scripts/gen-mode-dispatch.mjs +6 -6
- package/pipeline/scripts/match-skills.mjs +37 -4
- package/pipeline/scripts/migrate-prefs.mjs +88 -17
- package/pipeline/scripts/phase-tracker.sh +14 -3
- package/pipeline/scripts/pre-commit-check.sh +49 -2
- package/pipeline/scripts/skill-conformance.mjs +960 -0
- package/pipeline/scripts/smoke-schema-validation.sh +17 -4
- package/pipeline/scripts/uninstall.mjs +35 -9
- package/pipeline/scripts/validate-reviewer.mjs +108 -1
- package/pipeline/skills/.skill-manifest.json +1 -1
- package/pipeline/skills/.skills-index.json +36 -9
- package/pipeline/skills/shared/README.md +15 -12
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +1 -0
- package/pipeline/skills/shared/core/apple-archive-compliance/references/rules.yml +167 -0
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +1 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +184 -0
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +10 -10
- package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +4 -4
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +6 -5
- package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +7 -6
- package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -4
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -5
- package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +1 -1
- package/pipeline/skills/shared/core/{multi-agent-finish → multi-agent-ship}/SKILL.md +8 -8
- package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +44 -5
- package/pipeline/skills/shared/external/ios-coding-standard/modules/_TEMPLATE.yml +82 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +169 -10
- package/pipeline/skills/shared/external/ios-coding-standard/references/lint-local.sh +13 -1
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +335 -16
- package/pipeline/skills/skills-index.md +11 -8
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
28
28
|
import { join } from "path";
|
|
29
29
|
|
|
30
|
-
import { copyDir, countFiles, ensureDir, ensureRealDir, isDryRun } from "./_common.mjs";
|
|
30
|
+
import { copyDir, countFiles, ensureDir, ensureRealDir, isDryRun, wipeDir } from "./_common.mjs";
|
|
31
31
|
|
|
32
32
|
/** Subtrees a plugin authors itself. `knowledge/` is generated, so it is excluded. */
|
|
33
33
|
export const AUTHORED_GROUPS = Object.freeze(["index", "reference", "workflow", "tools"]);
|
|
@@ -148,16 +148,17 @@ export function pluginsToDeliver(home, platformFlag) {
|
|
|
148
148
|
* @param {string} opts.dest - flat skills directory
|
|
149
149
|
* @param {"ios"|"android"|"all"} opts.platformFlag
|
|
150
150
|
* @param {string} opts.label - host label for log lines
|
|
151
|
-
* @param {Set<string>} [opts.skipNames] - skill names
|
|
152
|
-
* @returns {{copied: number,
|
|
151
|
+
* @param {Set<string>} [opts.skipNames] - PIPELINE-owned skill names; a plugin skill matching one is delivered plugin-prefixed rather than dropped
|
|
152
|
+
* @returns {{copied: number, renamed: string[], collided: number, plugins: string[], missing: string[]}}
|
|
153
153
|
*/
|
|
154
154
|
export function installAuthoredPluginSkills(opts) {
|
|
155
155
|
const { home, dest, platformFlag, label, skipNames = new Set() } = opts;
|
|
156
156
|
const { names: wanted, source: selectionSource } = pluginsToDeliver(home, platformFlag);
|
|
157
157
|
|
|
158
158
|
let copied = 0;
|
|
159
|
-
let skipped = 0;
|
|
160
159
|
let collided = 0;
|
|
160
|
+
/** `<original> -> <plugin-prefixed>` for each name a pipeline skill already owns. */
|
|
161
|
+
const renamed = [];
|
|
161
162
|
const plugins = [];
|
|
162
163
|
const missing = [];
|
|
163
164
|
// Names delivered in THIS pass, so a later plugin cannot overwrite an earlier
|
|
@@ -186,17 +187,43 @@ export function installAuthoredPluginSkills(opts) {
|
|
|
186
187
|
.map((e) => ({ name: e.name, from: join(groupDir, e.name) }));
|
|
187
188
|
|
|
188
189
|
for (const { name, from } of entries) {
|
|
190
|
+
// A plugin skill whose name a PIPELINE skill already owns used to be dropped.
|
|
191
|
+
// That is not a duplicate: the pipeline's `architecture` is a generic ADR
|
|
192
|
+
// framework while the iOS plugin's is that stack's structural rules, and the
|
|
193
|
+
// same holds for `backlog`. Claude Code reaches both because its loader
|
|
194
|
+
// namespaces plugin skills (`ai-ios-engineering-toolkit:architecture`); the
|
|
195
|
+
// copy hosts had no namespace, so the stack-specific version was silently
|
|
196
|
+
// unreachable on exactly the repos that need it most.
|
|
197
|
+
//
|
|
198
|
+
// Deliver it under the plugin-prefixed name instead - the flat analogue of
|
|
199
|
+
// what the loader does. The pipeline keeps the bare name, so nothing that
|
|
200
|
+
// dispatches on it changes.
|
|
201
|
+
let deliveredName = name;
|
|
189
202
|
if (skipNames.has(name)) {
|
|
190
|
-
|
|
191
|
-
|
|
203
|
+
deliveredName = `${pluginName}-${name}`;
|
|
204
|
+
renamed.push(`${name} -> ${deliveredName}`);
|
|
192
205
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
206
|
+
// Two plugins can author the same skill name - `index` in every toolkit,
|
|
207
|
+
// `fix-bug` and `branch-and-pr` in four of them. Dropping the later one lost
|
|
208
|
+
// it entirely on the copy hosts: with the iOS and common toolkits both
|
|
209
|
+
// enabled, the common toolkit's `index` was simply gone. Prefix instead, so
|
|
210
|
+
// the first (stack) plugin keeps the bare name that dispatch expects and the
|
|
211
|
+
// later one is still reachable.
|
|
212
|
+
if (delivered.has(deliveredName)) {
|
|
213
|
+
const prefixed = `${pluginName}-${name}`;
|
|
214
|
+
if (delivered.has(prefixed)) {
|
|
215
|
+
collided++;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
renamed.push(`${deliveredName} -> ${prefixed}`);
|
|
219
|
+
deliveredName = prefixed;
|
|
196
220
|
}
|
|
197
|
-
delivered.add(
|
|
198
|
-
const to = join(dest,
|
|
221
|
+
delivered.add(deliveredName);
|
|
222
|
+
const to = join(dest, deliveredName);
|
|
199
223
|
ensureDir(dest);
|
|
224
|
+
// Wipe before copy: `copyDir` only adds, so a file the plugin dropped upstream
|
|
225
|
+
// would linger in the installed skill and keep being read as current.
|
|
226
|
+
wipeDir(to);
|
|
200
227
|
copyDir(from, to, {});
|
|
201
228
|
copied += countFiles(from);
|
|
202
229
|
perPlugin++;
|
|
@@ -215,8 +242,11 @@ export function installAuthoredPluginSkills(opts) {
|
|
|
215
242
|
` -> ${label}: ${collided} name collision(s) resolved in favour of the earlier plugin`,
|
|
216
243
|
);
|
|
217
244
|
}
|
|
218
|
-
if (
|
|
219
|
-
console.log(
|
|
245
|
+
if (renamed.length > 0) {
|
|
246
|
+
console.log(
|
|
247
|
+
` -> ${label}: ${renamed.length} plugin skill(s) plugin-prefixed so a name clash loses ` +
|
|
248
|
+
`nothing (${renamed.join(", ")})`,
|
|
249
|
+
);
|
|
220
250
|
}
|
|
221
251
|
if (missing.length > 0) {
|
|
222
252
|
console.log(
|
|
@@ -224,7 +254,7 @@ export function installAuthoredPluginSkills(opts) {
|
|
|
224
254
|
`clone {owner}/multi-agent-plugins or enable them in Claude Code to deliver their skills`,
|
|
225
255
|
);
|
|
226
256
|
}
|
|
227
|
-
return { copied,
|
|
257
|
+
return { copied, collided, renamed, plugins, missing, selectionSource };
|
|
228
258
|
}
|
|
229
259
|
|
|
230
260
|
/**
|
|
@@ -243,4 +273,33 @@ export function existingSkillNames(dir) {
|
|
|
243
273
|
);
|
|
244
274
|
}
|
|
245
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Skill names the PIPELINE owns, derived from its source tree.
|
|
278
|
+
*
|
|
279
|
+
* This is what `skipNames` should be. Callers used to pass
|
|
280
|
+
* `existingSkillNames(dest)` - a snapshot of whatever was already installed - which
|
|
281
|
+
* did protect pipeline skills from being shadowed by a plugin, but also meant a
|
|
282
|
+
* plugin skill was copied exactly once and then frozen: on the second install its
|
|
283
|
+
* own name was "already there", so it was skipped, forever. A stale plugin skill
|
|
284
|
+
* then outlives every upstream fix in it, which is how a reference to a path the
|
|
285
|
+
* plugin no longer names survived in an installed tree.
|
|
286
|
+
*
|
|
287
|
+
* Deriving from the source keeps the shadowing protection (a plugin still cannot
|
|
288
|
+
* overwrite a pipeline skill) while letting plugin skills refresh on every install.
|
|
289
|
+
*
|
|
290
|
+
* @param {string} pipelineSrc - the repo's `pipeline/` directory
|
|
291
|
+
* @returns {Set<string>}
|
|
292
|
+
*/
|
|
293
|
+
export function pipelineOwnedSkillNames(pipelineSrc) {
|
|
294
|
+
const owned = new Set();
|
|
295
|
+
for (const group of ["core", "external"]) {
|
|
296
|
+
const dir = join(pipelineSrc, "skills", "shared", group);
|
|
297
|
+
if (!existsSync(dir)) continue;
|
|
298
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
299
|
+
if (e.isDirectory()) owned.add(e.name);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return owned;
|
|
303
|
+
}
|
|
304
|
+
|
|
246
305
|
export { ensureRealDir };
|
package/install/claude.mjs
CHANGED
|
@@ -23,18 +23,21 @@ import { localizeCommands } from "../pipeline/scripts/localize-commands.mjs";
|
|
|
23
23
|
import {
|
|
24
24
|
copyDir,
|
|
25
25
|
copyFile,
|
|
26
|
+
copySkillsIndex,
|
|
26
27
|
countFiles,
|
|
27
28
|
ensureDir,
|
|
28
29
|
ensureRealDir,
|
|
29
30
|
isDryRun,
|
|
30
31
|
pruneAbandonedTrees,
|
|
31
32
|
pruneLegacyMultiAgentSkills,
|
|
33
|
+
pruneOrphanSkillFiles,
|
|
32
34
|
removePipelineAgentFiles,
|
|
33
35
|
wipeDir,
|
|
34
36
|
writeFile,
|
|
35
37
|
} from "./_common.mjs";
|
|
36
38
|
import { copyExternalSkillsFiltered } from "./_platform-filter.mjs";
|
|
37
39
|
import { DEV_ONLY_SCRIPTS, countDevOnlyFiles } from "./_dev-only-files.mjs";
|
|
40
|
+
import { registerMcpServer } from "./_mcp-register.mjs";
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
43
|
* Dead matcher written by pre-v11.4.1 installs. Hook matchers only ever see
|
|
@@ -86,6 +89,14 @@ export function installClaude(ctx) {
|
|
|
86
89
|
ensurePreferences(PREFS_PATH, pipelineSrc);
|
|
87
90
|
configureSettings(home);
|
|
88
91
|
|
|
92
|
+
// Claude Code was the last target still shipping skills without the tools those
|
|
93
|
+
// skills call. The dev-toolkit server backs design-check, every ios_* / android_*
|
|
94
|
+
// simulator call and the archive audits, and its absence is invisible until a run
|
|
95
|
+
// reaches for one of them. `--scope user` is not optional here: `claude mcp add`
|
|
96
|
+
// defaults to project scope, which would register the server only for whichever
|
|
97
|
+
// directory the installer ran in.
|
|
98
|
+
registerMcpServer("claude", "Claude Code");
|
|
99
|
+
|
|
89
100
|
console.log("");
|
|
90
101
|
}
|
|
91
102
|
|
|
@@ -325,10 +336,7 @@ function installSkills(opts) {
|
|
|
325
336
|
|
|
326
337
|
if (indexOnly) {
|
|
327
338
|
ensureDir(dest);
|
|
328
|
-
|
|
329
|
-
const indexMd = join(pipelineSrc, "skills", "skills-index.md");
|
|
330
|
-
if (existsSync(indexJson)) copyFile(indexJson, join(dest, ".skills-index.json"));
|
|
331
|
-
if (existsSync(indexMd)) copyFile(indexMd, join(dest, "skills-index.md"));
|
|
339
|
+
copySkillsIndex(pipelineSrc, dest);
|
|
332
340
|
console.log(` -> --index-only: wrote skills-index.{json,md} to ${dest}`);
|
|
333
341
|
console.log(` -> set prefs.global.dynamicSkillLoading=true to use (see docs)`);
|
|
334
342
|
return;
|
|
@@ -386,6 +394,22 @@ function installSkills(opts) {
|
|
|
386
394
|
}
|
|
387
395
|
}
|
|
388
396
|
|
|
397
|
+
// The index ships on FULL installs too, not just --index-only. It is what
|
|
398
|
+
// `match-skills.mjs` reads when prefs.global.dynamicSkillLoading is on, and the
|
|
399
|
+
// schema has always claimed a full install provides it. It did not: only
|
|
400
|
+
// --index-only copied it, so flipping the pref on a normal install produced an
|
|
401
|
+
// exit-1 "cannot read index" on the first dispatch. Two small files, and the
|
|
402
|
+
// feature is either wired or it is not.
|
|
403
|
+
copySkillsIndex(pipelineSrc, dest);
|
|
404
|
+
|
|
405
|
+
// Same pre-directory-layout leftovers Copilot accumulated. Claude Code's tree is
|
|
406
|
+
// clean today, but the legacy shape predates both targets, so the prune runs here
|
|
407
|
+
// too rather than waiting for someone to find the first stale copy.
|
|
408
|
+
const orphans = pruneOrphanSkillFiles(dest);
|
|
409
|
+
if (orphans > 0) {
|
|
410
|
+
console.log(` -> pruned ${orphans} stale flat skill file(s) from an older layout`);
|
|
411
|
+
}
|
|
412
|
+
|
|
389
413
|
console.log(` -> ${claudeSkillCount} skill files installed to ${dest}`);
|
|
390
414
|
|
|
391
415
|
// Keep the shared skills index README in sync.
|
package/install/codex.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { join } from "path";
|
|
|
23
23
|
import {
|
|
24
24
|
copyDir,
|
|
25
25
|
copyFile,
|
|
26
|
+
copySkillsIndex,
|
|
26
27
|
countFiles,
|
|
27
28
|
ensureDir,
|
|
28
29
|
ensureRealDir,
|
|
@@ -33,7 +34,7 @@ import {
|
|
|
33
34
|
import { DEV_ONLY_SCRIPTS, countDevOnlyFiles } from "./_dev-only-files.mjs";
|
|
34
35
|
import { registerMcpServer } from "./_mcp-register.mjs";
|
|
35
36
|
import { installCodexAgents } from "./_codex-agents.mjs";
|
|
36
|
-
import {
|
|
37
|
+
import { installAuthoredPluginSkills, pipelineOwnedSkillNames } from "./_plugin-skills.mjs";
|
|
37
38
|
import { generateCodexInstructions } from "./_codex-instructions.mjs";
|
|
38
39
|
import { mergeManagedBlock } from "./_managed-block.mjs";
|
|
39
40
|
|
|
@@ -86,6 +87,12 @@ export const CODEX_PATH_REWRITES = Object.freeze([
|
|
|
86
87
|
// refs/commands dir, so these generic rules follow the multi-agent ones.
|
|
87
88
|
{ from: /\$HOME\/\.claude\/commands\b/g, to: "$HOME/.codex/multi-agent-refs/commands" },
|
|
88
89
|
{ from: /~\/\.claude\/commands\b/g, to: "~/.codex/multi-agent-refs/commands" },
|
|
90
|
+
// Skills live under the refs tree on this host, so a skill referenced by path
|
|
91
|
+
// (the compliance pair, humanizer, the xcode-build family) has to point there.
|
|
92
|
+
// MUST precede the bare `multi-agent-refs` rule below, which would otherwise
|
|
93
|
+
// leave the `/skills` segment pointing at the Claude tree.
|
|
94
|
+
{ from: /\$HOME\/\.claude\/skills\b/g, to: "$HOME/.codex/multi-agent-refs/skills" },
|
|
95
|
+
{ from: /~\/\.claude\/skills\b/g, to: "~/.codex/multi-agent-refs/skills" },
|
|
89
96
|
{ from: /\$HOME\/\.claude\/multi-agent-refs\b/g, to: "$HOME/.codex/multi-agent-refs" },
|
|
90
97
|
{ from: /~\/\.claude\/multi-agent-refs\b/g, to: "~/.codex/multi-agent-refs" },
|
|
91
98
|
{ from: /\$HOME\/\.claude\/agents\b/g, to: "$HOME/.codex/agents" },
|
|
@@ -375,6 +382,7 @@ function installPrompt(promptsDir) {
|
|
|
375
382
|
*/
|
|
376
383
|
function installSkillRefs(pipelineSrc, dest, home, platformFlag) {
|
|
377
384
|
const externalSrc = join(pipelineSrc, "skills", "shared", "external");
|
|
385
|
+
const coreSrc = join(pipelineSrc, "skills", "shared", "core");
|
|
378
386
|
|
|
379
387
|
ensureRealDir(dest);
|
|
380
388
|
ensureDir(dest);
|
|
@@ -383,14 +391,37 @@ function installSkillRefs(pipelineSrc, dest, home, platformFlag) {
|
|
|
383
391
|
let count = 0;
|
|
384
392
|
if (existsSync(externalSrc)) count += copyTreeRewritten(externalSrc, dest);
|
|
385
393
|
|
|
394
|
+
// `shared/core` also holds two skills that are NOT pipeline sub-commands:
|
|
395
|
+
// apple-archive-compliance and google-play-compliance. Copying only `external`
|
|
396
|
+
// left both unreachable on Codex while `smoke-compliance-skills.sh` asserted they
|
|
397
|
+
// were wired to four consumers - the gate passed, the host had nothing to load.
|
|
398
|
+
// The `multi-agent*` dirs stay out: those are sub-command specs, and they ship as
|
|
399
|
+
// refs under `commands/` precisely so they never enter the skills block.
|
|
400
|
+
if (existsSync(coreSrc)) {
|
|
401
|
+
count += copyTreeRewritten(
|
|
402
|
+
coreSrc,
|
|
403
|
+
dest,
|
|
404
|
+
(rel) => !rel.startsWith("multi-agent/") && !rel.startsWith("multi-agent-"),
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Pipeline-owned names come from the SOURCE tree. A destination snapshot also
|
|
409
|
+
// matched plugin skills from a previous install, so each was copied once and then
|
|
410
|
+
// skipped forever - frozen against every upstream fix in it.
|
|
386
411
|
const res = installAuthoredPluginSkills({
|
|
387
412
|
home,
|
|
388
413
|
dest,
|
|
389
414
|
platformFlag,
|
|
390
415
|
label: "Codex CLI",
|
|
391
|
-
skipNames:
|
|
416
|
+
skipNames: pipelineOwnedSkillNames(pipelineSrc),
|
|
392
417
|
});
|
|
393
418
|
|
|
419
|
+
// The index lands beside the refs it indexes. Codex has no skills block to feed,
|
|
420
|
+
// but `match-skills.mjs` is installed here too and dynamic skill loading reads the
|
|
421
|
+
// same file on every host - and the router's `ls`/`grep` discovery gets a cheap
|
|
422
|
+
// catalogue out of it rather than heading four lines of each SKILL.md.
|
|
423
|
+
copySkillsIndex(pipelineSrc, dest);
|
|
424
|
+
|
|
394
425
|
console.log(
|
|
395
426
|
` -> ${count + res.copied} skill file(s) copied to ${dest} as refs (0 skills-block cost)`,
|
|
396
427
|
);
|
package/install/copilot.mjs
CHANGED
|
@@ -8,24 +8,27 @@
|
|
|
8
8
|
* @module install/copilot
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync, mkdirSync, readdirSync, rmSync } from "fs";
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "fs";
|
|
12
12
|
import { join } from "path";
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
copyDir,
|
|
16
16
|
copyFile,
|
|
17
|
+
copySkillsIndex,
|
|
17
18
|
countFiles,
|
|
18
19
|
ensureDir,
|
|
19
20
|
ensureRealDir,
|
|
20
21
|
isDryRun,
|
|
22
|
+
pruneOrphanSkillFiles,
|
|
21
23
|
removePipelineAgentFiles,
|
|
22
24
|
wipeDir,
|
|
25
|
+
writeFile,
|
|
23
26
|
} from "./_common.mjs";
|
|
24
27
|
import { copyExternalSkillsFiltered } from "./_platform-filter.mjs";
|
|
25
28
|
import { DEV_ONLY_SCRIPTS, countDevOnlyFiles } from "./_dev-only-files.mjs";
|
|
26
29
|
import { generateCopilotInstructions } from "./_copilot-instructions.mjs";
|
|
27
30
|
import { legacyTrailingContent, mergeManagedBlock } from "./_managed-block.mjs";
|
|
28
|
-
import {
|
|
31
|
+
import { installAuthoredPluginSkills, pipelineOwnedSkillNames } from "./_plugin-skills.mjs";
|
|
29
32
|
import { registerMcpServer } from "./_mcp-register.mjs";
|
|
30
33
|
|
|
31
34
|
/**
|
|
@@ -47,6 +50,7 @@ export function installCopilot(ctx) {
|
|
|
47
50
|
const COPILOT_SKILLS = join(COPILOT_DIR, "skills");
|
|
48
51
|
const COPILOT_SCHEMAS = join(COPILOT_DIR, "schemas");
|
|
49
52
|
const COPILOT_LIB = join(COPILOT_DIR, "lib");
|
|
53
|
+
const COPILOT_RULES = join(COPILOT_DIR, "rules");
|
|
50
54
|
|
|
51
55
|
console.log(" [Copilot CLI] Installing pipeline instructions...");
|
|
52
56
|
ensureDir(COPILOT_DIR);
|
|
@@ -56,6 +60,7 @@ export function installCopilot(ctx) {
|
|
|
56
60
|
installAgents(pipelineSrc, COPILOT_AGENTS, useSymlinks);
|
|
57
61
|
installSchemas(pipelineSrc, COPILOT_SCHEMAS, useSymlinks);
|
|
58
62
|
installLib(pipelineSrc, COPILOT_LIB, useSymlinks);
|
|
63
|
+
installRules(pipelineSrc, COPILOT_RULES, useSymlinks);
|
|
59
64
|
installSkills({ home, pipelineSrc, dest: COPILOT_SKILLS, indexOnly, useSymlinks, platformFlag });
|
|
60
65
|
|
|
61
66
|
// Copilot never registered the companion MCP server, while Codex did from the day its
|
|
@@ -66,6 +71,91 @@ export function installCopilot(ctx) {
|
|
|
66
71
|
registerMcpServer("copilot", "Copilot CLI");
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
/**
|
|
75
|
+
* `$HOME/.claude/...` rewrites applied to skill files copied into the Copilot tree.
|
|
76
|
+
*
|
|
77
|
+
* Copilot's skills used to be copied byte-for-byte, so a Copilot-only install
|
|
78
|
+
* shipped 15 references to `~/.claude/scripts` and `~/.claude/lib` - trees that
|
|
79
|
+
* install never creates. Every one of those was a dangling path: the skill told the
|
|
80
|
+
* agent to run a script at a location that does not exist on that machine. Codex has
|
|
81
|
+
* had this rewrite since its installer was written; Copilot simply never got it.
|
|
82
|
+
*
|
|
83
|
+
* Only the trees Copilot actually OWNS are rewritten:
|
|
84
|
+
*
|
|
85
|
+
* - `scripts`, `lib`, `schemas`, `agents`, `rules` and `skills` are installed under
|
|
86
|
+
* `~/.copilot/`, so references must point there.
|
|
87
|
+
* - `multi-agent-refs` and `commands` are deliberately NOT rewritten: Copilot does
|
|
88
|
+
* not install them, and the instructions block already routes those to GitHub or
|
|
89
|
+
* to `~/.claude` when Claude Code is present on the same machine.
|
|
90
|
+
* - Shared state (`logs/`, `multi-agent-preferences.json`, `knowledge/`) is NOT
|
|
91
|
+
* rewritten either. `~/.claude` is the cross-host state root - that is what lets
|
|
92
|
+
* `:resume` / `:log` / `:status` see a task started from another CLI.
|
|
93
|
+
*
|
|
94
|
+
* @type {ReadonlyArray<{from: RegExp, to: string}>}
|
|
95
|
+
*/
|
|
96
|
+
export const COPILOT_PATH_REWRITES = Object.freeze([
|
|
97
|
+
// Skills first: a skill that names a sibling skill by path (the compliance pair,
|
|
98
|
+
// humanizer, the xcode-build family) has to land inside THIS host's skills tree.
|
|
99
|
+
{ from: /\$HOME\/\.claude\/skills\b/g, to: "$HOME/.copilot/skills" },
|
|
100
|
+
{ from: /~\/\.claude\/skills\b/g, to: "~/.copilot/skills" },
|
|
101
|
+
{ from: /\$HOME\/\.claude\/scripts\b/g, to: "$HOME/.copilot/scripts" },
|
|
102
|
+
{ from: /~\/\.claude\/scripts\b/g, to: "~/.copilot/scripts" },
|
|
103
|
+
{ from: /\$HOME\/\.claude\/lib\b/g, to: "$HOME/.copilot/lib" },
|
|
104
|
+
{ from: /~\/\.claude\/lib\b/g, to: "~/.copilot/lib" },
|
|
105
|
+
{ from: /\$HOME\/\.claude\/schemas\b/g, to: "$HOME/.copilot/schemas" },
|
|
106
|
+
{ from: /~\/\.claude\/schemas\b/g, to: "~/.copilot/schemas" },
|
|
107
|
+
{ from: /\$HOME\/\.claude\/agents\b/g, to: "$HOME/.copilot/agents" },
|
|
108
|
+
{ from: /~\/\.claude\/agents\b/g, to: "~/.copilot/agents" },
|
|
109
|
+
{ from: /\$HOME\/\.claude\/rules\b/g, to: "$HOME/.copilot/rules" },
|
|
110
|
+
{ from: /~\/\.claude\/rules\b/g, to: "~/.copilot/rules" },
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Apply the Copilot path map to a file's text.
|
|
115
|
+
* @param {string} text
|
|
116
|
+
* @returns {string}
|
|
117
|
+
*/
|
|
118
|
+
export function rewriteCopilotPaths(text) {
|
|
119
|
+
let out = text;
|
|
120
|
+
for (const { from, to } of COPILOT_PATH_REWRITES) out = out.replace(from, to);
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Recursively copy a tree, applying the Copilot path map to text files.
|
|
126
|
+
*
|
|
127
|
+
* Not `copyDir`: that copies bytes, and every skill file here carries
|
|
128
|
+
* `$HOME/.claude/...` references that have to be retargeted.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} src
|
|
131
|
+
* @param {string} dest
|
|
132
|
+
* @param {(relPath: string) => boolean} [filter] - receives the path relative to the
|
|
133
|
+
* copy root (POSIX-separated). Return false to skip.
|
|
134
|
+
* @param {string} [relBase] - internal: accumulated relative path
|
|
135
|
+
* @returns {number} files written
|
|
136
|
+
*/
|
|
137
|
+
function copyTreeRewritten(src, dest, filter, relBase = "") {
|
|
138
|
+
const TEXT = /\.(md|json|sh|mjs|js|txt|ya?ml|toml)$/i;
|
|
139
|
+
let n = 0;
|
|
140
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
141
|
+
const from = join(src, entry.name);
|
|
142
|
+
const to = join(dest, entry.name);
|
|
143
|
+
const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
144
|
+
if (entry.isDirectory()) {
|
|
145
|
+
n += copyTreeRewritten(from, to, filter, rel);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!entry.isFile()) continue;
|
|
149
|
+
if (filter && !filter(rel)) continue;
|
|
150
|
+
ensureDir(dest);
|
|
151
|
+
const isText = TEXT.test(entry.name);
|
|
152
|
+
const raw = readFileSync(from, isText ? "utf-8" : null);
|
|
153
|
+
writeFile(to, isText ? rewriteCopilotPaths(raw) : raw);
|
|
154
|
+
n++;
|
|
155
|
+
}
|
|
156
|
+
return n;
|
|
157
|
+
}
|
|
158
|
+
|
|
69
159
|
/** Start of the pipeline-managed span in copilot-instructions.md. */
|
|
70
160
|
export const INSTRUCTIONS_START_MARKER = "# Multi-Agent Development Pipeline";
|
|
71
161
|
|
|
@@ -168,6 +258,26 @@ function installLib(pipelineSrc, dest, useSymlinks) {
|
|
|
168
258
|
console.log(` -> ${countFiles(libSrc)} files copied to ${dest}`);
|
|
169
259
|
}
|
|
170
260
|
|
|
261
|
+
/**
|
|
262
|
+
* The convention rules the analysis phase falls back to when a project ships no
|
|
263
|
+
* standards file of its own.
|
|
264
|
+
*
|
|
265
|
+
* Codex has installed these since its installer was written, with the reason stated
|
|
266
|
+
* there: a dangling `rules/` means that fallback silently yields nothing. Copilot never
|
|
267
|
+
* got the tree, so every `rules/figma-pipeline.md` and `rules/tdd.md` reference in its
|
|
268
|
+
* skills pointed at a directory that install never created - the same silent-nothing
|
|
269
|
+
* outcome, on the host that has no `~/.claude` to borrow from.
|
|
270
|
+
*/
|
|
271
|
+
function installRules(pipelineSrc, dest, useSymlinks) {
|
|
272
|
+
console.log(" [Copilot CLI] Installing convention rules...");
|
|
273
|
+
const rulesSrc = join(pipelineSrc, "rules");
|
|
274
|
+
if (!existsSync(rulesSrc)) return;
|
|
275
|
+
if (!useSymlinks) ensureRealDir(dest);
|
|
276
|
+
wipeDir(dest);
|
|
277
|
+
copyDir(rulesSrc, dest, { useSymlinks });
|
|
278
|
+
console.log(` -> ${countFiles(rulesSrc)} files copied to ${dest}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
171
281
|
function installSkills(opts) {
|
|
172
282
|
const { home, pipelineSrc, dest, indexOnly, useSymlinks, platformFlag } = opts;
|
|
173
283
|
console.log(" [Copilot CLI] Installing skills...");
|
|
@@ -182,10 +292,7 @@ function installSkills(opts) {
|
|
|
182
292
|
} catch {
|
|
183
293
|
/* ok */
|
|
184
294
|
}
|
|
185
|
-
|
|
186
|
-
const indexMd = join(pipelineSrc, "skills", "skills-index.md");
|
|
187
|
-
if (existsSync(indexJson)) copyFile(indexJson, join(dest, ".skills-index.json"));
|
|
188
|
-
if (existsSync(indexMd)) copyFile(indexMd, join(dest, "skills-index.md"));
|
|
295
|
+
copySkillsIndex(pipelineSrc, dest);
|
|
189
296
|
console.log(` -> --index-only: wrote skills-index.{json,md} to ${dest}`);
|
|
190
297
|
console.log("");
|
|
191
298
|
return;
|
|
@@ -229,8 +336,24 @@ function installSkills(opts) {
|
|
|
229
336
|
}
|
|
230
337
|
|
|
231
338
|
if (existsSync(sharedCoreSrc)) {
|
|
232
|
-
|
|
233
|
-
|
|
339
|
+
if (useSymlinks) {
|
|
340
|
+
// --link (dev) mode points at the repo, so there is nothing to rewrite -
|
|
341
|
+
// and rewriting would mean editing the checkout.
|
|
342
|
+
copyDir(sharedCoreSrc, dest, { useSymlinks });
|
|
343
|
+
copilotSkillCount += countFiles(sharedCoreSrc);
|
|
344
|
+
} else {
|
|
345
|
+
// Wipe each pipeline-owned skill dir before refilling it. Copying only ADDS,
|
|
346
|
+
// so a subtree from an older layout survives inside a directory the installer
|
|
347
|
+
// still manages - a real install carried `skills/multi-agent/refs/` for
|
|
348
|
+
// releases after Copilot stopped installing refs, and every file in it still
|
|
349
|
+
// pointed at `~/.claude/lib`.
|
|
350
|
+
for (const e of readdirSync(sharedCoreSrc, { withFileTypes: true })) {
|
|
351
|
+
if (e.isDirectory()) wipeDir(join(dest, e.name));
|
|
352
|
+
}
|
|
353
|
+
// The core skills are the ones that invoke scripts and lib helpers, so they
|
|
354
|
+
// are the ones whose `~/.claude/...` paths have to be retargeted.
|
|
355
|
+
copilotSkillCount += copyTreeRewritten(sharedCoreSrc, dest);
|
|
356
|
+
}
|
|
234
357
|
}
|
|
235
358
|
|
|
236
359
|
if (existsSync(sharedExternalSrc)) {
|
|
@@ -273,17 +396,30 @@ function installSkills(opts) {
|
|
|
273
396
|
// `knowledge/` is skipped: it is generated from this pipeline's own
|
|
274
397
|
// skills/shared/external, which the step above already installed. Copying it again
|
|
275
398
|
// would duplicate ~5 MB the host already has.
|
|
399
|
+
// skipNames is the PIPELINE-owned set, derived from the source tree - not a
|
|
400
|
+
// snapshot of the destination. A dest snapshot also matched plugin skills from a
|
|
401
|
+
// previous install, so each was copied once and then skipped forever, freezing it
|
|
402
|
+
// against every upstream fix.
|
|
276
403
|
const pluginResult = installAuthoredPluginSkills({
|
|
277
404
|
home,
|
|
278
405
|
dest,
|
|
279
406
|
platformFlag,
|
|
280
407
|
label: "Copilot CLI",
|
|
281
|
-
skipNames:
|
|
408
|
+
skipNames: pipelineOwnedSkillNames(pipelineSrc),
|
|
282
409
|
});
|
|
283
410
|
if (pluginResult.copied > 0) {
|
|
284
411
|
console.log(` -> ${pluginResult.copied} plugin skill file(s) delivered to ${dest}`);
|
|
285
412
|
}
|
|
286
413
|
|
|
414
|
+
// Full installs ship the index too - see copySkillsIndex for why.
|
|
415
|
+
copySkillsIndex(pipelineSrc, dest);
|
|
416
|
+
|
|
417
|
+
// Drop `<name>.md` duplicates the pre-directory layout left behind.
|
|
418
|
+
const orphans = pruneOrphanSkillFiles(dest);
|
|
419
|
+
if (orphans > 0) {
|
|
420
|
+
console.log(` -> pruned ${orphans} stale flat skill file(s) from an older layout`);
|
|
421
|
+
}
|
|
422
|
+
|
|
287
423
|
console.log(` -> ${copilotSkillCount} skill files installed to ${dest}`);
|
|
288
424
|
|
|
289
425
|
const sharedReadmeSrc = join(pipelineSrc, "skills", "shared", "README.md");
|
package/install/index.mjs
CHANGED
|
@@ -182,16 +182,20 @@ function printSummary(opts) {
|
|
|
182
182
|
console.log("");
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
// All three register automatically now. Claude Code was the last to gain it, and
|
|
186
|
+
// this summary kept telling users to do it by hand long enough that the
|
|
187
|
+
// hand-registered entry became the only reason it worked on any given machine.
|
|
188
|
+
console.log(" For UI testing, the dev-toolkit MCP server is registered automatically:");
|
|
189
|
+
if (forClaude) {
|
|
190
|
+
console.log(" Claude Code: claude mcp add --scope user dev-toolkit (user scope, all projects)");
|
|
188
191
|
}
|
|
189
192
|
if (forCopilot) {
|
|
190
|
-
console.log(" Copilot CLI:
|
|
193
|
+
console.log(" Copilot CLI: copilot mcp add dev-toolkit");
|
|
191
194
|
}
|
|
192
|
-
if (
|
|
193
|
-
console.log("
|
|
195
|
+
if (forCodex) {
|
|
196
|
+
console.log(" Codex CLI: codex mcp add dev-toolkit");
|
|
194
197
|
}
|
|
198
|
+
console.log(" Skipped only when the host CLI is not on PATH - the command to run is printed above.");
|
|
195
199
|
console.log("");
|
|
196
200
|
console.log(" Uninstall everything: npx @mmerterden/multi-agent-pipeline uninstall");
|
|
197
201
|
console.log(" (Personal access tokens in keychain are preserved.)");
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
## Modes
|
|
27
27
|
|
|
28
28
|
- **Normal**: Full 8 phases with code review + Plan Approval Gate
|
|
29
|
-
- **Fast (multi-agent-dev)**: Init -> Dev(Opus) -> Commit -> Report
|
|
29
|
+
- **Fast (multi-agent-dev)**: Init -> Dev(Opus) -> Review -> Test -> Commit -> Report
|
|
30
30
|
- **Autopilot (multi-agent-autopilot)**: Skip confirmations, auto commit/PR
|
|
31
31
|
- **Fastest (multi-agent-dev-autopilot)**: Combine dev + autopilot
|
|
32
32
|
- **Local (multi-agent-local)**: Full 8 phases on current branch - no worktree
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.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",
|
|
@@ -21,9 +21,17 @@ You are a code reviewer. Review the provided diff for:
|
|
|
21
21
|
Return ONLY a JSON object:
|
|
22
22
|
|
|
23
23
|
```json
|
|
24
|
-
{
|
|
24
|
+
{
|
|
25
|
+
"findings": [{"severity": "blocking|important|suggestion", "file": "...", "line": N, "issue": "...", "fix": "...", "ruleId": "SEC-01", "criteriaSource": "ios-coding-standard"}],
|
|
26
|
+
"conformance": [{"ruleId": "SEC-01", "verdict": "conformant|violated|not-applicable", "file": "Sources/X.swift", "line": 42, "reason": "..."}],
|
|
27
|
+
"approved": true|false
|
|
28
|
+
}
|
|
25
29
|
```
|
|
26
30
|
|
|
31
|
+
`ruleId` + `criteriaSource` are required on a finding that comes from a cited
|
|
32
|
+
rule and omitted otherwise. `conformance` is required whenever a `${CRITERIA}`
|
|
33
|
+
block was supplied - see below.
|
|
34
|
+
|
|
27
35
|
## Severity Classification
|
|
28
36
|
|
|
29
37
|
- **blocking**: Must fix - bugs, security holes, data loss risk, architecture violations
|
|
@@ -32,6 +40,55 @@ Return ONLY a JSON object:
|
|
|
32
40
|
|
|
33
41
|
Do NOT approve if any blocking findings exist.
|
|
34
42
|
|
|
43
|
+
## Criteria (required when supplied)
|
|
44
|
+
|
|
45
|
+
The orchestrator may pass a `${CRITERIA}` block: the rule IDs the changed code
|
|
46
|
+
was supposed to honour, resolved deterministically before you were dispatched,
|
|
47
|
+
plus the paths of the registry files that define them. Read the registry files
|
|
48
|
+
you are pointed at. They are YAML, and a rule carries `title`, `severity` and
|
|
49
|
+
`enforcement`; **many but not all** carry a `check` spelling out what counts as a
|
|
50
|
+
violation (in the shipped iOS registry, 53 of 99 do). Where `check` is absent,
|
|
51
|
+
judge against the rule's `title` and `rationale` rather than assuming the rule is
|
|
52
|
+
unusable - and say so in the conformance row's evidence, so a thin rule reads as
|
|
53
|
+
a thin rule and not as a pass.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
${CRITERIA}
|
|
57
|
+
registries:
|
|
58
|
+
<name> (<version>) -> <absolute path to rules.yml>
|
|
59
|
+
rules in scope:
|
|
60
|
+
<RULE-ID> [<severity>/<enforcement>] -> <files it applies to>
|
|
61
|
+
...
|
|
62
|
+
module guides:
|
|
63
|
+
<repo-relative path>
|
|
64
|
+
coverage gaps:
|
|
65
|
+
<language>: <why no rule applies - review these files on general criteria>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Return one `conformance` row **per rule ID in that list**, and no rows for IDs
|
|
69
|
+
outside it. Rules:
|
|
70
|
+
|
|
71
|
+
- `conformant` requires evidence: the `file` and `line` you checked. A verdict
|
|
72
|
+
with no evidence is an assertion, and the report would then certify
|
|
73
|
+
completeness you never established.
|
|
74
|
+
- `violated` must have a matching entry in `findings` carrying the same
|
|
75
|
+
`ruleId`.
|
|
76
|
+
- `not-applicable` requires a `reason` naming why the rule cannot bind here.
|
|
77
|
+
- An ID that is neither checked nor explicitly waived fails the stage. This is
|
|
78
|
+
the whole point of the list: "I found nothing" and "I checked nothing"
|
|
79
|
+
produce identical `findings` arrays, so the checklist is what tells them
|
|
80
|
+
apart.
|
|
81
|
+
- Never invent a rule ID. Cite only IDs from the block, and only for rules the
|
|
82
|
+
registry actually contains.
|
|
83
|
+
|
|
84
|
+
Enforcement kinds tell you who owns a rule: `format` belongs to the formatter
|
|
85
|
+
and `lint` to the linter, so do not hand-review those beyond noticing an
|
|
86
|
+
obvious breach; `judgement` rules are yours, and each needs the measurement its
|
|
87
|
+
`check` names (a count, a call-site list) rather than an impression.
|
|
88
|
+
|
|
89
|
+
When no `${CRITERIA}` block is supplied, omit `conformance` entirely and review
|
|
90
|
+
on the focus areas above.
|
|
91
|
+
|
|
35
92
|
## Priority Files (advisory)
|
|
36
93
|
|
|
37
94
|
When the orchestrator passes a `${PRIORITY_FILES}` block, treat it as a heuristic
|