@mmerterden/multi-agent-pipeline 14.2.2 → 15.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 +76 -6
- package/README.md +15 -8
- package/README.tr.md +15 -8
- package/docs/FIGMA_PIPELINE.md +3 -3
- package/docs/adr/0006-skills-core-external-split.md +1 -1
- package/docs/adr/0009-claude-stack-skills-plugin-only.md +31 -0
- package/docs/adr/README.md +1 -0
- package/docs/architecture.md +7 -7
- package/docs/ecosystem.md +28 -28
- package/docs/features.md +5 -5
- package/index.js +2 -0
- package/install/_codex-agents.mjs +11 -2
- package/install/_common.mjs +65 -1
- package/install/_dev-only-files.mjs +0 -1
- package/install/_platform-filter.mjs +73 -7
- package/install/_plugin-skills.mjs +19 -8
- package/install/claude.mjs +144 -59
- package/install/codex.mjs +28 -3
- package/install/copilot.mjs +36 -11
- package/install/index.mjs +6 -2
- package/install/templates/codex-instructions.md +1 -1
- package/install/templates/copilot-instructions.md +3 -3
- package/package.json +1 -2
- package/pipeline/commands/multi-agent/SKILL.md +2 -0
- package/pipeline/commands/multi-agent/analysis/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +186 -0
- package/pipeline/commands/multi-agent/dev/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/help/SKILL.md +19 -4
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/jira/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +81 -0
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/{ship → resume-local}/SKILL.md +8 -8
- package/pipeline/commands/multi-agent/setup/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/stack/SKILL.md +55 -43
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/sync/SKILL.md +18 -11
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/uninstall/SKILL.md +2 -0
- package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
- package/pipeline/lib/issue-fetcher.sh +1 -1
- package/pipeline/lib/parse-complaints.sh +306 -0
- package/pipeline/multi-agent-refs/channels/wiki.md +3 -3
- package/pipeline/multi-agent-refs/complaint-analysis-template.md +99 -0
- package/pipeline/multi-agent-refs/component-dispatch.md +6 -6
- package/pipeline/multi-agent-refs/cross-cli-contract.md +16 -16
- package/pipeline/multi-agent-refs/features/external-context-injection.md +1 -1
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +5 -5
- package/pipeline/multi-agent-refs/generate-issue.md +1 -1
- package/pipeline/multi-agent-refs/phases/modes.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +5 -5
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +3 -3
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +12 -12
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +1 -1
- package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
- package/pipeline/multi-agent-refs/wiki-capture.md +2 -2
- package/pipeline/preferences-template.json +13 -5
- package/pipeline/rules/figma-pipeline.md +2 -2
- package/pipeline/schemas/agent-state.schema.json +1 -1
- package/pipeline/schemas/complaint-analysis-spec.schema.json +216 -0
- package/pipeline/schemas/migrations/prefs-2.5.0-to-2.6.0.mjs +46 -0
- package/pipeline/schemas/prefs.schema.json +276 -66
- package/pipeline/schemas/token-budget.json +2 -2
- package/pipeline/scripts/_stack-routing.mjs +79 -0
- package/pipeline/scripts/audit-log-rotate.sh +4 -1
- package/pipeline/scripts/build-skills-index.mjs +11 -0
- package/pipeline/scripts/build-stack-plugins.mjs +28 -60
- package/pipeline/scripts/check-derived-drift.mjs +52 -28
- package/pipeline/scripts/gc-worktrees.sh +4 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/scripts/match-skills.mjs +8 -2
- package/pipeline/scripts/migrate-prefs.mjs +28 -20
- package/pipeline/scripts/phase-tracker.sh +13 -5
- package/pipeline/scripts/phase0-exit-gate.mjs +3 -2
- package/pipeline/scripts/run-aggregator.mjs +7 -2
- package/pipeline/scripts/scan-agent-config.sh +1 -1
- package/pipeline/scripts/skill-conformance.mjs +165 -30
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
- package/pipeline/scripts/test-gap-rules/android.json +25 -0
- package/pipeline/scripts/test-gap-rules/ios.json +34 -0
- package/pipeline/scripts/test-gap-rules/node.json +29 -0
- package/pipeline/scripts/test-gap-rules/python.json +25 -0
- package/pipeline/scripts/uninstall.mjs +158 -11
- package/pipeline/scripts/validate-complaint-doc.mjs +229 -0
- package/pipeline/scripts/validate-reviewer.mjs +9 -3
- package/pipeline/skills/.skill-manifest.json +156 -108
- package/pipeline/skills/.skills-index.json +449 -12
- package/pipeline/skills/shared/README.md +14 -10
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +49 -0
- package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +83 -0
- package/pipeline/skills/shared/core/{multi-agent-ship → multi-agent-resume-local}/SKILL.md +6 -6
- package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +79 -22
- package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +1 -1
- package/pipeline/skills/shared/external/ios-coding-standard/modules/_TEMPLATE.yml +2 -2
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +368 -33
- package/pipeline/skills/shared/external/ios-coding-standard/references/swiftlint.draft.yml +1 -2
- package/pipeline/skills/shared/external/ios-coding-standard/scripts/check_structure.py +765 -0
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +75 -0
- package/pipeline/skills/shared/external/ios-module-structure/modules/_TEMPLATE.yml +131 -0
- package/pipeline/skills/shared/external/ios-module-structure/references/rules.yml +559 -0
- package/pipeline/skills/shared/external/ios-module-structure/scripts/check_structure.py +765 -0
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +53 -10
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +4 -3
- package/pipeline/skills/skills-index.md +7 -4
|
@@ -97,7 +97,9 @@ function resolveTrackerCandidates() {
|
|
|
97
97
|
if (!HOME) die("neither HOME nor USERPROFILE is set - pass --task-dir");
|
|
98
98
|
const taskId = String(flags["task-id"]);
|
|
99
99
|
if (flags.project) {
|
|
100
|
-
candidates.push(
|
|
100
|
+
candidates.push(
|
|
101
|
+
join(HOME, ".claude", "logs", "multi-agent", flags.project, taskId, "tracker-state.json"),
|
|
102
|
+
);
|
|
101
103
|
}
|
|
102
104
|
candidates.push(join(HOME, ".claude", "logs", "multi-agent", taskId, "tracker-state.json"));
|
|
103
105
|
}
|
|
@@ -209,7 +211,10 @@ let unknownModelHits = 0;
|
|
|
209
211
|
// rest of this file can treat it as a plain array.
|
|
210
212
|
const phaseList = Array.isArray(tracker.phases)
|
|
211
213
|
? tracker.phases
|
|
212
|
-
: Object.entries(tracker.phases || {}).map(([key, value]) => ({
|
|
214
|
+
: Object.entries(tracker.phases || {}).map(([key, value]) => ({
|
|
215
|
+
...value,
|
|
216
|
+
id: value?.id ?? key,
|
|
217
|
+
}));
|
|
213
218
|
|
|
214
219
|
for (const phase of phaseList) {
|
|
215
220
|
const tIn = phase.tokens_in || 0;
|
|
@@ -31,7 +31,7 @@ add "$ROOT/install/templates/claude-hooks.json"
|
|
|
31
31
|
add "$ROOT/install/templates/copilot-instructions.md"
|
|
32
32
|
add "$ROOT/pipeline/preferences-template.json"
|
|
33
33
|
for f in "$ROOT"/pipeline/agents/*.md; do add "$f"; done
|
|
34
|
-
# (figma component skills + their scripts now live in the ai-*-
|
|
34
|
+
# (figma component skills + their scripts now live in the ai-*-toolkit
|
|
35
35
|
# marketplace plugin, not in this repo, so there is nothing figma to scan here.)
|
|
36
36
|
add "$ROOT/pipeline/scripts/agent-guard.sh"
|
|
37
37
|
add "$ROOT/pipeline/scripts/pre-commit-check.sh"
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
|
|
44
44
|
import { dirname, join, resolve, relative, basename, extname, sep } from "node:path";
|
|
45
45
|
import { fileURLToPath } from "node:url";
|
|
46
|
+
import { homedir } from "node:os";
|
|
46
47
|
|
|
47
48
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
48
49
|
|
|
@@ -64,10 +65,74 @@ function skillsRootCandidates() {
|
|
|
64
65
|
return [
|
|
65
66
|
join(installRoot, "skills"), // ~/.claude/skills, ~/.copilot/skills
|
|
66
67
|
join(installRoot, "multi-agent-refs", "skills"), // ~/.codex/multi-agent-refs/skills
|
|
68
|
+
...pluginSkillRoots(), // marketplace stack plugins (Claude Code's only stack-skill source)
|
|
67
69
|
join(repoRoot, "pipeline", "skills"), // checkout
|
|
68
70
|
];
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Marketplace plugin roots.
|
|
75
|
+
//
|
|
76
|
+
// On Claude Code the stack skills no longer land in ~/.claude/skills - they are
|
|
77
|
+
// loaded from the multi-agent-plugins marketplace. The registries this gate
|
|
78
|
+
// depends on (`ios-coding-standard`, `ios-module-structure`) live in the iOS
|
|
79
|
+
// plugin's knowledge/ tree, so without these roots the registry set silently
|
|
80
|
+
// collapses to the two local compliance catalogs and Phase 4 reads green over
|
|
81
|
+
// criteria that were never loaded. Local checkout is preferred over the plugin
|
|
82
|
+
// cache for the same reason install/_plugin-skills.mjs prefers it: the checkout
|
|
83
|
+
// is what /multi-agent:sync rebuilds, the cache only advances on marketplace
|
|
84
|
+
// refresh. Cache dirs are version-named; newest wins.
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
const PLUGIN_MARKETPLACE = "multi-agent-plugins";
|
|
87
|
+
const STACK_PLUGIN_NAMES = [
|
|
88
|
+
"ai-ios-toolkit",
|
|
89
|
+
"ai-android-toolkit",
|
|
90
|
+
"ai-frontend-toolkit",
|
|
91
|
+
"ai-backend-toolkit",
|
|
92
|
+
"ai-common-toolkit",
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
function semverCompare(a, b) {
|
|
96
|
+
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
97
|
+
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
98
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
99
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
100
|
+
if (d !== 0) return d;
|
|
101
|
+
}
|
|
102
|
+
return a.localeCompare(b);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function pluginSkillRoots() {
|
|
106
|
+
const home = homedir();
|
|
107
|
+
const roots = [];
|
|
108
|
+
for (const name of STACK_PLUGIN_NAMES) {
|
|
109
|
+
const checkout = join(home, PLUGIN_MARKETPLACE, "plugins", name, "skills");
|
|
110
|
+
if (existsSync(checkout)) {
|
|
111
|
+
roots.push(checkout);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const cacheRoot = join(home, ".claude", "plugins", "cache", PLUGIN_MARKETPLACE, name);
|
|
115
|
+
if (!existsSync(cacheRoot)) continue;
|
|
116
|
+
const versions = readdirSafe(cacheRoot)
|
|
117
|
+
.filter((v) => {
|
|
118
|
+
try {
|
|
119
|
+
return statSync(join(cacheRoot, v)).isDirectory();
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
.sort(semverCompare);
|
|
125
|
+
for (const v of versions.reverse()) {
|
|
126
|
+
const p = join(cacheRoot, v, "skills");
|
|
127
|
+
if (existsSync(p)) {
|
|
128
|
+
roots.push(p);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return roots;
|
|
134
|
+
}
|
|
135
|
+
|
|
71
136
|
/**
|
|
72
137
|
* Resolve the roots to search, and report every candidate that was PROBED - not
|
|
73
138
|
* only the ones that existed.
|
|
@@ -496,28 +561,64 @@ function auditExceptions(addedByFile, knownIds, today, markerToken) {
|
|
|
496
561
|
const reason = rest.replace(/(\d{4}-\d{2}-\d{2})/, "").trim();
|
|
497
562
|
|
|
498
563
|
if (!id) {
|
|
499
|
-
findings.push(
|
|
500
|
-
|
|
564
|
+
findings.push(
|
|
565
|
+
f(
|
|
566
|
+
file,
|
|
567
|
+
line,
|
|
568
|
+
"blocking",
|
|
569
|
+
null,
|
|
570
|
+
"Exception marker names no rule ID.",
|
|
571
|
+
"Name the rule the exception applies to: standard:exception(<RULE-ID>) <reason> <expiry>.",
|
|
572
|
+
),
|
|
573
|
+
);
|
|
501
574
|
continue;
|
|
502
575
|
}
|
|
503
576
|
if (knownIds.size > 0 && !knownIds.has(id)) {
|
|
504
|
-
findings.push(
|
|
505
|
-
|
|
506
|
-
|
|
577
|
+
findings.push(
|
|
578
|
+
f(
|
|
579
|
+
file,
|
|
580
|
+
line,
|
|
581
|
+
"blocking",
|
|
582
|
+
id,
|
|
583
|
+
`Exception cites '${id}', which is not in any registry in scope for this diff.`,
|
|
584
|
+
"Use a real rule ID, or drop the marker. An exception against an unknown rule suppresses nothing and hides intent.",
|
|
585
|
+
),
|
|
586
|
+
);
|
|
507
587
|
}
|
|
508
588
|
if (!dateMatch) {
|
|
509
|
-
findings.push(
|
|
510
|
-
|
|
511
|
-
|
|
589
|
+
findings.push(
|
|
590
|
+
f(
|
|
591
|
+
file,
|
|
592
|
+
line,
|
|
593
|
+
"important",
|
|
594
|
+
id,
|
|
595
|
+
`Exception for '${id}' has no expiry date.`,
|
|
596
|
+
"Add an expiry: standard:exception(<RULE-ID>) <reason> YYYY-MM-DD. An exception with no expiry is a permanent silent carve-out.",
|
|
597
|
+
),
|
|
598
|
+
);
|
|
512
599
|
} else if (dateMatch[1] < today) {
|
|
513
|
-
findings.push(
|
|
514
|
-
|
|
515
|
-
|
|
600
|
+
findings.push(
|
|
601
|
+
f(
|
|
602
|
+
file,
|
|
603
|
+
line,
|
|
604
|
+
"blocking",
|
|
605
|
+
id,
|
|
606
|
+
`Exception for '${id}' expired on ${dateMatch[1]}.`,
|
|
607
|
+
"Fix the underlying violation, or renew the exception with a new expiry and a current reason.",
|
|
608
|
+
),
|
|
609
|
+
);
|
|
516
610
|
}
|
|
517
611
|
if (!reason) {
|
|
518
|
-
findings.push(
|
|
519
|
-
|
|
520
|
-
|
|
612
|
+
findings.push(
|
|
613
|
+
f(
|
|
614
|
+
file,
|
|
615
|
+
line,
|
|
616
|
+
"important",
|
|
617
|
+
id,
|
|
618
|
+
`Exception for '${id}' gives no reason.`,
|
|
619
|
+
"State why the rule does not apply here. An unexplained exception cannot be reviewed, only trusted.",
|
|
620
|
+
),
|
|
621
|
+
);
|
|
521
622
|
}
|
|
522
623
|
}
|
|
523
624
|
}
|
|
@@ -614,7 +715,8 @@ function parseArgs(argv) {
|
|
|
614
715
|
else if (a === "--repo") o.repo = argv[++i];
|
|
615
716
|
else if (a === "--out") o.out = argv[++i];
|
|
616
717
|
else if (a === "--skills-root") o.skillsRoot = argv[++i];
|
|
617
|
-
else if (a === "--today")
|
|
718
|
+
else if (a === "--today")
|
|
719
|
+
o.today = argv[++i]; // tests pin the clock
|
|
618
720
|
else {
|
|
619
721
|
console.error(`unknown argument: ${a}`);
|
|
620
722
|
process.exit(1);
|
|
@@ -692,7 +794,11 @@ const unparseable = [...escapedDeclarations];
|
|
|
692
794
|
|
|
693
795
|
for (const d of discovered) {
|
|
694
796
|
if (!existsSync(d.rulesPath)) {
|
|
695
|
-
unparseable.push({
|
|
797
|
+
unparseable.push({
|
|
798
|
+
name: d.name,
|
|
799
|
+
path: d.rulesPath,
|
|
800
|
+
reason: "declared standards-registry file does not exist",
|
|
801
|
+
});
|
|
696
802
|
continue;
|
|
697
803
|
}
|
|
698
804
|
let loaded;
|
|
@@ -703,7 +809,11 @@ for (const d of discovered) {
|
|
|
703
809
|
continue;
|
|
704
810
|
}
|
|
705
811
|
if (loaded.rules.length === 0) {
|
|
706
|
-
unparseable.push({
|
|
812
|
+
unparseable.push({
|
|
813
|
+
name: d.name,
|
|
814
|
+
path: d.rulesPath,
|
|
815
|
+
reason: "no rules extracted - rules: block missing or unexpected indentation",
|
|
816
|
+
});
|
|
707
817
|
continue;
|
|
708
818
|
}
|
|
709
819
|
registries.push({ ...d, ...loaded });
|
|
@@ -716,9 +826,10 @@ const perRegistry = [];
|
|
|
716
826
|
for (const reg of registries) {
|
|
717
827
|
const scope = reg.meta.scope;
|
|
718
828
|
const regLanguages = scope?.languages ?? [];
|
|
719
|
-
const langHit =
|
|
720
|
-
|
|
721
|
-
|
|
829
|
+
const langHit =
|
|
830
|
+
regLanguages.length === 0
|
|
831
|
+
? Object.keys(languages)
|
|
832
|
+
: regLanguages.filter((l) => Object.prototype.hasOwnProperty.call(languages, l));
|
|
722
833
|
|
|
723
834
|
// Files this registry may speak about at all.
|
|
724
835
|
const inScopeFiles = changedFiles.filter((file) => {
|
|
@@ -747,7 +858,9 @@ for (const reg of registries) {
|
|
|
747
858
|
? inScopeFiles.filter((f2) => matchesAnyGlob(f2, rule.scope.paths))
|
|
748
859
|
: inScopeFiles;
|
|
749
860
|
if (files.length === 0) {
|
|
750
|
-
const why = rule.scopeReason
|
|
861
|
+
const why = rule.scopeReason
|
|
862
|
+
? `out-of-scope: ${rule.scopeReason}`
|
|
863
|
+
: "out-of-scope: no changed file matches the rule's paths";
|
|
751
864
|
droppedReasons[why] = (droppedReasons[why] ?? 0) + 1;
|
|
752
865
|
continue;
|
|
753
866
|
}
|
|
@@ -776,9 +889,16 @@ for (const reg of registries) {
|
|
|
776
889
|
languagesMatched: langHit,
|
|
777
890
|
inScopeFileCount: inScopeFiles.length,
|
|
778
891
|
notCovered: reg.meta.notCovered,
|
|
779
|
-
loadedAs: unscoped
|
|
892
|
+
loadedAs: unscoped
|
|
893
|
+
? "reference-only"
|
|
894
|
+
: inScopeFiles.length
|
|
895
|
+
? "rule-checklist"
|
|
896
|
+
: "not-applicable",
|
|
780
897
|
...(unscoped
|
|
781
|
-
? {
|
|
898
|
+
? {
|
|
899
|
+
referenceOnlyReason:
|
|
900
|
+
"registry declares no scope; applying its rules to unknown languages would manufacture findings",
|
|
901
|
+
}
|
|
782
902
|
: {}),
|
|
783
903
|
toolchains: detectToolchains(repoRoot, reg.rules),
|
|
784
904
|
exceptionMarker: reg.meta.exceptionMarker,
|
|
@@ -794,7 +914,9 @@ for (const [lang, files] of Object.entries(languages)) {
|
|
|
794
914
|
const covered = files.filter((f2) => coveredFiles.has(f2));
|
|
795
915
|
if (covered.length === 0) {
|
|
796
916
|
const claims = perRegistry
|
|
797
|
-
.flatMap((r) =>
|
|
917
|
+
.flatMap((r) =>
|
|
918
|
+
Object.entries(r.notCovered ?? {}).map(([k, v]) => ({ registry: r.name, key: k, note: v })),
|
|
919
|
+
)
|
|
798
920
|
.filter((c) => c.key === lang || lang.startsWith(c.key));
|
|
799
921
|
gaps.push({
|
|
800
922
|
language: lang,
|
|
@@ -803,7 +925,8 @@ for (const [lang, files] of Object.entries(languages)) {
|
|
|
803
925
|
reason: claims.length
|
|
804
926
|
? `no registry in scope covers ${lang}: ${claims.map((c) => `${c.registry} declares '${c.key}' not covered`).join("; ")}`
|
|
805
927
|
: `no registry in scope declares ${lang}`,
|
|
806
|
-
consequence:
|
|
928
|
+
consequence:
|
|
929
|
+
"these files are reviewed on general criteria only - no rule IDs apply to them",
|
|
807
930
|
});
|
|
808
931
|
}
|
|
809
932
|
}
|
|
@@ -815,7 +938,7 @@ const { guides, dropped: guidesDropped } = collectModuleGuides(repoRoot, changed
|
|
|
815
938
|
// knownIds spans EVERY discovered registry, not just the in-scope ones. Narrowing
|
|
816
939
|
// it to the selected set would report a valid rule as unknown whenever the
|
|
817
940
|
// exception sits in a file that rule does not cover, which is a false accusation
|
|
818
|
-
// in the one place the author is trying to be explicit. The narrower question -
|
|
941
|
+
// in the one place the author is trying to be explicit. The narrower question -
|
|
819
942
|
// "is this exception citing a rule that applies here?" - is answered separately
|
|
820
943
|
// below, so a mis-scoped exception is still reported, just not as a typo.
|
|
821
944
|
const knownIds = new Set(registries.flatMap((r) => r.rules.map((x) => x.id)));
|
|
@@ -872,7 +995,9 @@ for (const reg of perRegistry) {
|
|
|
872
995
|
const resolvedNames = new Set(perRegistry.map((r) => r.name));
|
|
873
996
|
const declaredNames = ledger.entries.map((e) => e?.skill).filter(Boolean);
|
|
874
997
|
const declaredNotResolved = [...new Set(declaredNames)].filter((n) => !resolvedNames.has(n));
|
|
875
|
-
const ledgerMissingTargets = ledger.entries.filter(
|
|
998
|
+
const ledgerMissingTargets = ledger.entries.filter(
|
|
999
|
+
(e) => !Array.isArray(e?.targetFiles) || e.targetFiles.length === 0,
|
|
1000
|
+
).length;
|
|
876
1001
|
|
|
877
1002
|
// Entries a stack toolkit's own index chose (Phase 3 step 9 stamps `routedBy`).
|
|
878
1003
|
// Surfaced separately because "the project's toolkit says this skill governs this
|
|
@@ -940,10 +1065,20 @@ if (opts.json) {
|
|
|
940
1065
|
process.stdout.write(JSON.stringify(manifest, null, 2) + "\n");
|
|
941
1066
|
} else {
|
|
942
1067
|
console.log(`criteria-manifest: ${outPath}`);
|
|
943
|
-
console.log(
|
|
944
|
-
|
|
1068
|
+
console.log(
|
|
1069
|
+
` languages: ${
|
|
1070
|
+
Object.entries(manifest.languages)
|
|
1071
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
1072
|
+
.join(" ") || "(none)"
|
|
1073
|
+
}`,
|
|
1074
|
+
);
|
|
1075
|
+
console.log(
|
|
1076
|
+
` registries: ${perRegistry.length} discovered, ${perRegistry.filter((r) => r.loadedAs === "rule-checklist").length} as rule-checklist`,
|
|
1077
|
+
);
|
|
945
1078
|
console.log(` selected rules: ${selected.length}`);
|
|
946
|
-
console.log(
|
|
1079
|
+
console.log(
|
|
1080
|
+
` coverage: ${coveredFiles.size}/${changedFiles.length} changed files carry rule criteria`,
|
|
1081
|
+
);
|
|
947
1082
|
for (const g of gaps) console.log(` GAP ${g.language} (${g.fileCount} file(s)): ${g.reason}`);
|
|
948
1083
|
console.log(` ledger: ${manifest.ledger.source} (self-report: ${ledger.status})`);
|
|
949
1084
|
console.log(` findings: ${findings.length}`);
|
|
@@ -152,7 +152,7 @@ fi
|
|
|
152
152
|
# ──────────────────────────────────────────────────────────────────────────
|
|
153
153
|
echo ""
|
|
154
154
|
# Figma/component skill trees no longer ship in the pipeline (v11.2.0+): they
|
|
155
|
-
# live in the marketplace component plugins (ai-ios-
|
|
155
|
+
# live in the marketplace component plugins (ai-ios-toolkit /
|
|
156
156
|
# multi-agent-plugins), which each CLI loads through its own plugin channel.
|
|
157
157
|
# The pipeline therefore has no figma tree to deploy or parity-check here.
|
|
158
158
|
echo "→ 7. Agent personas deployed to both CLIs"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"stack": "android",
|
|
4
|
+
"description": "Android / Jetpack Compose / Kotlin test gap rules. Sources in *.kt; tests under app/src/test/ or app/src/androidTest/.",
|
|
5
|
+
"sourceExtensions": [".kt", ".java"],
|
|
6
|
+
"testPathSuffixes": ["Test.kt", "Tests.kt", "Test.java"],
|
|
7
|
+
"testPathContains": ["/src/test/", "/src/androidTest/", "/test/", "/tests/"],
|
|
8
|
+
"excludePathGlobs": ["build/", ".gradle/", "generated/", "**/BuildConfig.java", "**/R.java"],
|
|
9
|
+
"publicApiPatterns": [
|
|
10
|
+
{ "id": "open_class", "regex": "\\bopen\\s+class\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
11
|
+
{ "id": "open_fun", "regex": "\\bopen\\s+fun\\s+([a-z_][A-Za-z0-9_]*)" },
|
|
12
|
+
{ "id": "public_fun", "regex": "^\\s*(?:public\\s+)?fun\\s+([a-z_][A-Za-z0-9_]*)" },
|
|
13
|
+
{ "id": "public_class", "regex": "^\\s*(?:public\\s+)?class\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
14
|
+
{ "id": "jvmstatic", "regex": "@JvmStatic\\b" },
|
|
15
|
+
{ "id": "interface", "regex": "^\\s*(?:public\\s+)?interface\\s+([A-Z][A-Za-z0-9_]*)" }
|
|
16
|
+
],
|
|
17
|
+
"composablePatterns": [
|
|
18
|
+
{
|
|
19
|
+
"id": "composable_fun",
|
|
20
|
+
"regex": "@Composable\\s+(?:public\\s+|internal\\s+)?fun\\s+([A-Z][A-Za-z0-9_]*)"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"expectedTestKindsForComposable": ["compose-ui-test"],
|
|
24
|
+
"hint": "Cover @Composable functions with createComposeRule() + onNodeWithText/Tag/ContentDescription assertions; ViewModels with runTest + Turbine."
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"stack": "ios",
|
|
4
|
+
"description": "iOS / SwiftUI / UIKit test gap rules. Source files in *.swift; tests in *Tests.swift. Snapshot/ViewInspector tests are also recognised when the source is a SwiftUI View.",
|
|
5
|
+
"sourceExtensions": [".swift"],
|
|
6
|
+
"testPathSuffixes": ["Tests.swift", "Test.swift"],
|
|
7
|
+
"testPathContains": ["/Tests/", "/UnitTests/", "/SnapshotTests/"],
|
|
8
|
+
"excludePathGlobs": [
|
|
9
|
+
"Pods/",
|
|
10
|
+
"Carthage/",
|
|
11
|
+
".build/",
|
|
12
|
+
"DerivedData/",
|
|
13
|
+
"Generated/",
|
|
14
|
+
"Sourcery/",
|
|
15
|
+
"_Generated.swift",
|
|
16
|
+
"Tests/Fixtures/"
|
|
17
|
+
],
|
|
18
|
+
"publicApiPatterns": [
|
|
19
|
+
{ "id": "public_func", "regex": "\\bpublic\\s+func\\s+([A-Za-z_][A-Za-z0-9_]*)" },
|
|
20
|
+
{ "id": "public_class", "regex": "\\bpublic\\s+(?:final\\s+)?class\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
21
|
+
{ "id": "public_struct", "regex": "\\bpublic\\s+struct\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
22
|
+
{ "id": "public_enum", "regex": "\\bpublic\\s+enum\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
23
|
+
{ "id": "public_proto", "regex": "\\bpublic\\s+protocol\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
24
|
+
{ "id": "public_init", "regex": "\\bpublic\\s+init\\b" },
|
|
25
|
+
{ "id": "objc_export", "regex": "@objc(?:Members)?\\s+(?:public\\s+)?(?:func|class|var)" }
|
|
26
|
+
],
|
|
27
|
+
"swiftUiViewPatterns": [
|
|
28
|
+
{ "id": "view_struct", "regex": "struct\\s+([A-Z][A-Za-z0-9_]*)\\s*:\\s*View\\b" },
|
|
29
|
+
{ "id": "config_struct", "regex": "struct\\s+([A-Z][A-Za-z0-9_]*Configuration)\\b" }
|
|
30
|
+
],
|
|
31
|
+
"expectedTestKindsForView": ["snapshot", "viewinspector", "unit"],
|
|
32
|
+
"snapshotPathHints": ["SnapshotTests", "__Snapshots__"],
|
|
33
|
+
"hint": "Cover with a 3-layer test (structural ViewInspector + snapshot + unit) per swiftui-qa.md."
|
|
34
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"stack": "node",
|
|
4
|
+
"description": "Node.js / TypeScript test gap rules. Sources in *.ts/*.tsx/*.js/*.mjs; tests in *.test.* / *.spec.* / __tests__/.",
|
|
5
|
+
"sourceExtensions": [".ts", ".tsx", ".js", ".mjs", ".cjs"],
|
|
6
|
+
"testPathSuffixes": [
|
|
7
|
+
".test.ts",
|
|
8
|
+
".test.tsx",
|
|
9
|
+
".test.js",
|
|
10
|
+
".test.mjs",
|
|
11
|
+
".spec.ts",
|
|
12
|
+
".spec.tsx",
|
|
13
|
+
".spec.js",
|
|
14
|
+
".spec.mjs"
|
|
15
|
+
],
|
|
16
|
+
"testPathContains": ["/__tests__/", "/test/", "/tests/"],
|
|
17
|
+
"excludePathGlobs": ["node_modules/", "dist/", "build/", ".next/", "coverage/", "*.d.ts"],
|
|
18
|
+
"publicApiPatterns": [
|
|
19
|
+
{
|
|
20
|
+
"id": "named_export",
|
|
21
|
+
"regex": "^export\\s+(?:async\\s+)?(?:function|class|const|let|interface|type|enum)\\s+([A-Za-z_$][A-Za-z0-9_$]*)"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "default_export",
|
|
25
|
+
"regex": "^export\\s+default\\s+(?:async\\s+)?(?:function|class)\\s+([A-Za-z_$][A-Za-z0-9_$]*)"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"hint": "Cover named exports with vitest/jest test('<name>', ...). React components: React Testing Library (render + screen + userEvent)."
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"stack": "python",
|
|
4
|
+
"description": "Python test gap rules. Sources in *.py; tests in test_*.py / *_test.py / tests/.",
|
|
5
|
+
"sourceExtensions": [".py"],
|
|
6
|
+
"testPathSuffixes": ["_test.py"],
|
|
7
|
+
"testPathPrefixes": ["test_"],
|
|
8
|
+
"testPathContains": ["/tests/", "/test/"],
|
|
9
|
+
"excludePathGlobs": [
|
|
10
|
+
"__pycache__/",
|
|
11
|
+
".venv/",
|
|
12
|
+
"venv/",
|
|
13
|
+
".tox/",
|
|
14
|
+
"build/",
|
|
15
|
+
"dist/",
|
|
16
|
+
"*.egg-info/",
|
|
17
|
+
"**/migrations/**"
|
|
18
|
+
],
|
|
19
|
+
"publicApiPatterns": [
|
|
20
|
+
{ "id": "top_def", "regex": "^def\\s+([a-z_][a-z0-9_]*)" },
|
|
21
|
+
{ "id": "top_class", "regex": "^class\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
22
|
+
{ "id": "async_def", "regex": "^async\\s+def\\s+([a-z_][a-z0-9_]*)" }
|
|
23
|
+
],
|
|
24
|
+
"hint": "Cover with pytest test_<symbol> style. Async with pytest-asyncio. Avoid name collision with private helpers (prefix _)."
|
|
25
|
+
}
|