@mmerterden/multi-agent-pipeline 12.7.0 → 12.8.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.
Files changed (244) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/install/_common.mjs +48 -0
  3. package/install/_dev-only-files.mjs +125 -5
  4. package/install/claude.mjs +14 -8
  5. package/install/copilot.mjs +5 -8
  6. package/package.json +17 -2
  7. package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
  8. package/pipeline/lib/credential-store.sh +20 -0
  9. package/pipeline/multi-agent-refs/_account-picker.md +1 -1
  10. package/pipeline/multi-agent-refs/_dev-context.md +1 -1
  11. package/pipeline/multi-agent-refs/_repo-picker.md +1 -1
  12. package/pipeline/multi-agent-refs/phases/operations.md +28 -0
  13. package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -0
  14. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +1 -1
  15. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -2
  16. package/pipeline/multi-agent-refs/phases/phase-4-review.md +49 -4
  17. package/pipeline/schemas/prefs.schema.json +6 -0
  18. package/pipeline/scripts/_smoke-root.sh +61 -0
  19. package/pipeline/scripts/audit-log.sh +25 -0
  20. package/pipeline/scripts/smoke-cross-cli-behavior.sh +21 -7
  21. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +0 -25
  22. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +0 -30
  23. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +0 -20
  24. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +0 -15
  25. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +0 -14
  26. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +0 -12
  27. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +0 -29
  28. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +0 -43
  29. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +0 -35
  30. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +0 -35
  31. package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +0 -14
  32. package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +0 -12
  33. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-1-analysis.json +0 -29
  34. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-2-plan.json +0 -42
  35. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-review.json +0 -20
  36. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-triage.json +0 -15
  37. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/metadata.json +0 -14
  38. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/task.json +0 -12
  39. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-1-analysis.json +0 -29
  40. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-2-plan.json +0 -40
  41. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-review.json +0 -20
  42. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-triage.json +0 -15
  43. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/metadata.json +0 -14
  44. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/task.json +0 -12
  45. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-1-analysis.json +0 -29
  46. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-2-plan.json +0 -42
  47. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-review.json +0 -28
  48. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-triage.json +0 -27
  49. package/pipeline/eval/golden-tasks/05-ios-security-keychain/metadata.json +0 -14
  50. package/pipeline/eval/golden-tasks/05-ios-security-keychain/task.json +0 -12
  51. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-1-analysis.json +0 -29
  52. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-2-plan.json +0 -41
  53. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-review.json +0 -12
  54. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-triage.json +0 -6
  55. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/metadata.json +0 -14
  56. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/task.json +0 -12
  57. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-1-analysis.json +0 -29
  58. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-2-plan.json +0 -42
  59. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-review.json +0 -28
  60. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-triage.json +0 -27
  61. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/metadata.json +0 -14
  62. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/task.json +0 -12
  63. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-1-analysis.json +0 -25
  64. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-2-plan.json +0 -31
  65. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-review.json +0 -12
  66. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-triage.json +0 -18
  67. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/metadata.json +0 -14
  68. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/task.json +0 -12
  69. package/pipeline/eval/golden-tasks/README.md +0 -65
  70. package/pipeline/eval/intent-cases.json +0 -40
  71. package/pipeline/eval/run-metrics-fixture.json +0 -93
  72. package/pipeline/eval/triage/01-empty-findings/expected.json +0 -6
  73. package/pipeline/eval/triage/01-empty-findings/input.json +0 -5
  74. package/pipeline/eval/triage/01-empty-findings/notes.md +0 -7
  75. package/pipeline/eval/triage/02-real-blocker/expected.json +0 -15
  76. package/pipeline/eval/triage/02-real-blocker/input.json +0 -14
  77. package/pipeline/eval/triage/02-real-blocker/notes.md +0 -7
  78. package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +0 -18
  79. package/pipeline/eval/triage/03-out-of-scope-defer/input.json +0 -14
  80. package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +0 -10
  81. package/pipeline/eval/triage/04-false-positive-reject/expected.json +0 -18
  82. package/pipeline/eval/triage/04-false-positive-reject/input.json +0 -14
  83. package/pipeline/eval/triage/04-false-positive-reject/notes.md +0 -10
  84. package/pipeline/eval/triage/05-mixed-classification/expected.json +0 -43
  85. package/pipeline/eval/triage/05-mixed-classification/input.json +0 -38
  86. package/pipeline/eval/triage/05-mixed-classification/notes.md +0 -17
  87. package/pipeline/eval/triage/06-severity-mismatch/expected.json +0 -15
  88. package/pipeline/eval/triage/06-severity-mismatch/input.json +0 -14
  89. package/pipeline/eval/triage/06-severity-mismatch/notes.md +0 -9
  90. package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +0 -27
  91. package/pipeline/eval/triage/07-duplicate-reviewers/input.json +0 -22
  92. package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +0 -9
  93. package/pipeline/eval/triage/08-style-misclassified/expected.json +0 -18
  94. package/pipeline/eval/triage/08-style-misclassified/input.json +0 -14
  95. package/pipeline/eval/triage/08-style-misclassified/notes.md +0 -9
  96. package/pipeline/eval/triage/09-cascading-finding/expected.json +0 -23
  97. package/pipeline/eval/triage/09-cascading-finding/input.json +0 -22
  98. package/pipeline/eval/triage/09-cascading-finding/notes.md +0 -9
  99. package/pipeline/eval/triage/10-deferred-crossref/expected.json +0 -18
  100. package/pipeline/eval/triage/10-deferred-crossref/input.json +0 -14
  101. package/pipeline/eval/triage/10-deferred-crossref/notes.md +0 -9
  102. package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +0 -27
  103. package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +0 -22
  104. package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +0 -14
  105. package/pipeline/eval/triage/README.md +0 -54
  106. package/pipeline/scripts/benchmark-phase-0.sh +0 -128
  107. package/pipeline/scripts/check-md-links.mjs +0 -88
  108. package/pipeline/scripts/eval-golden-tasks-live.mjs +0 -302
  109. package/pipeline/scripts/eval-golden-tasks.mjs +0 -224
  110. package/pipeline/scripts/eval-intent.mjs +0 -107
  111. package/pipeline/scripts/eval-mine-corpus.mjs +0 -211
  112. package/pipeline/scripts/eval-triage.mjs +0 -171
  113. package/pipeline/scripts/fixtures/diff-risk-android.diff +0 -40
  114. package/pipeline/scripts/fixtures/diff-risk-ios.diff +0 -48
  115. package/pipeline/scripts/fixtures/diff-risk-test-removal.diff +0 -40
  116. package/pipeline/scripts/fixtures/install-layout.tsv +0 -19
  117. package/pipeline/scripts/fixtures/pack-expected-count.txt +0 -1
  118. package/pipeline/scripts/fixtures/test-gap-node.diff +0 -30
  119. package/pipeline/scripts/fixtures/test-gap-python.diff +0 -32
  120. package/pipeline/scripts/lint-mcp-refs.mjs +0 -218
  121. package/pipeline/scripts/lint-skills.mjs +0 -154
  122. package/pipeline/scripts/run-smokes.mjs +0 -130
  123. package/pipeline/scripts/scorecard.mjs +0 -258
  124. package/pipeline/scripts/smoke-add-detail.sh +0 -137
  125. package/pipeline/scripts/smoke-agent-guard.sh +0 -74
  126. package/pipeline/scripts/smoke-agent-log-cost.sh +0 -262
  127. package/pipeline/scripts/smoke-agent-model-routing.sh +0 -87
  128. package/pipeline/scripts/smoke-ask-choice.sh +0 -42
  129. package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +0 -36
  130. package/pipeline/scripts/smoke-bitbucket-contract.sh +0 -255
  131. package/pipeline/scripts/smoke-changelog-version.sh +0 -47
  132. package/pipeline/scripts/smoke-channels-approval-gate.sh +0 -60
  133. package/pipeline/scripts/smoke-channels-flow.sh +0 -130
  134. package/pipeline/scripts/smoke-ci-workflows.sh +0 -88
  135. package/pipeline/scripts/smoke-clarify.sh +0 -148
  136. package/pipeline/scripts/smoke-command-inventory.sh +0 -81
  137. package/pipeline/scripts/smoke-commands-skills-parity.sh +0 -87
  138. package/pipeline/scripts/smoke-community-gates.sh +0 -75
  139. package/pipeline/scripts/smoke-compliance-skills.sh +0 -119
  140. package/pipeline/scripts/smoke-config-hygiene.sh +0 -58
  141. package/pipeline/scripts/smoke-context-budget.sh +0 -72
  142. package/pipeline/scripts/smoke-cost-budget.sh +0 -70
  143. package/pipeline/scripts/smoke-cost-summary.sh +0 -139
  144. package/pipeline/scripts/smoke-cross-phase-cohesion.sh +0 -128
  145. package/pipeline/scripts/smoke-description-tr.sh +0 -82
  146. package/pipeline/scripts/smoke-dev-critic.sh +0 -144
  147. package/pipeline/scripts/smoke-diff-explain.sh +0 -147
  148. package/pipeline/scripts/smoke-diff-risk.sh +0 -190
  149. package/pipeline/scripts/smoke-dynamic-skill-loading.sh +0 -160
  150. package/pipeline/scripts/smoke-eval-live.sh +0 -136
  151. package/pipeline/scripts/smoke-evidence-gate.sh +0 -93
  152. package/pipeline/scripts/smoke-extract-conventions.sh +0 -163
  153. package/pipeline/scripts/smoke-fetchers-offline.sh +0 -448
  154. package/pipeline/scripts/smoke-figma-dispatch.sh +0 -112
  155. package/pipeline/scripts/smoke-gate-hooks.sh +0 -74
  156. package/pipeline/scripts/smoke-gc-tmp.sh +0 -130
  157. package/pipeline/scripts/smoke-gc-worktrees.sh +0 -125
  158. package/pipeline/scripts/smoke-generate-issue.sh +0 -120
  159. package/pipeline/scripts/smoke-handoff-contract.sh +0 -92
  160. package/pipeline/scripts/smoke-identity-isolation.sh +0 -70
  161. package/pipeline/scripts/smoke-install-layout.sh +0 -248
  162. package/pipeline/scripts/smoke-intent-guard.sh +0 -86
  163. package/pipeline/scripts/smoke-issue-comment-template.sh +0 -86
  164. package/pipeline/scripts/smoke-issue-jira-triad.sh +0 -120
  165. package/pipeline/scripts/smoke-keychain.sh +0 -158
  166. package/pipeline/scripts/smoke-language-axis.sh +0 -109
  167. package/pipeline/scripts/smoke-learning-curve.sh +0 -61
  168. package/pipeline/scripts/smoke-learnings-ledger.sh +0 -86
  169. package/pipeline/scripts/smoke-lib-scripts.sh +0 -448
  170. package/pipeline/scripts/smoke-mcp-gate.sh +0 -68
  171. package/pipeline/scripts/smoke-md-links.sh +0 -8
  172. package/pipeline/scripts/smoke-md2confluence.sh +0 -126
  173. package/pipeline/scripts/smoke-metrics-cache-ratio.sh +0 -72
  174. package/pipeline/scripts/smoke-migrate-state.sh +0 -102
  175. package/pipeline/scripts/smoke-mode-dispatch-drift.sh +0 -161
  176. package/pipeline/scripts/smoke-model-fallback.sh +0 -89
  177. package/pipeline/scripts/smoke-multi-repo-integration.sh +0 -116
  178. package/pipeline/scripts/smoke-multi-repo-worktree.sh +0 -61
  179. package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +0 -194
  180. package/pipeline/scripts/smoke-no-token-prompt.sh +0 -85
  181. package/pipeline/scripts/smoke-own-punctuation.sh +0 -103
  182. package/pipeline/scripts/smoke-pack-contents.sh +0 -140
  183. package/pipeline/scripts/smoke-pat-audit.sh +0 -128
  184. package/pipeline/scripts/smoke-per-repo-memory.sh +0 -156
  185. package/pipeline/scripts/smoke-phase-0-multi-repo.sh +0 -170
  186. package/pipeline/scripts/smoke-phase-6-multi.sh +0 -79
  187. package/pipeline/scripts/smoke-phase-banner.sh +0 -101
  188. package/pipeline/scripts/smoke-phase-tracker.sh +0 -324
  189. package/pipeline/scripts/smoke-phase0-bridge-contract.sh +0 -241
  190. package/pipeline/scripts/smoke-phase4-gates.sh +0 -45
  191. package/pipeline/scripts/smoke-phase4-triage.sh +0 -229
  192. package/pipeline/scripts/smoke-plan-approval-gate.sh +0 -71
  193. package/pipeline/scripts/smoke-plan-safety.sh +0 -139
  194. package/pipeline/scripts/smoke-plan-todos.sh +0 -196
  195. package/pipeline/scripts/smoke-pr-review-actions.sh +0 -152
  196. package/pipeline/scripts/smoke-pre-commit.sh +0 -170
  197. package/pipeline/scripts/smoke-pref-migration.sh +0 -226
  198. package/pipeline/scripts/smoke-prefs-language.sh +0 -134
  199. package/pipeline/scripts/smoke-progress-contract.sh +0 -127
  200. package/pipeline/scripts/smoke-prune-logs.sh +0 -137
  201. package/pipeline/scripts/smoke-purge.sh +0 -138
  202. package/pipeline/scripts/smoke-push-retry.sh +0 -75
  203. package/pipeline/scripts/smoke-repo-map.sh +0 -300
  204. package/pipeline/scripts/smoke-review-readiness.sh +0 -92
  205. package/pipeline/scripts/smoke-review-watch.sh +0 -146
  206. package/pipeline/scripts/smoke-routines.sh +0 -84
  207. package/pipeline/scripts/smoke-run-aggregator.sh +0 -216
  208. package/pipeline/scripts/smoke-run-metrics.sh +0 -50
  209. package/pipeline/scripts/smoke-search.sh +0 -187
  210. package/pipeline/scripts/smoke-shadow-git.sh +0 -224
  211. package/pipeline/scripts/smoke-skill-authoring.sh +0 -137
  212. package/pipeline/scripts/smoke-skill-language.sh +0 -83
  213. package/pipeline/scripts/smoke-skill-manifest.sh +0 -138
  214. package/pipeline/scripts/smoke-skill-scan.sh +0 -198
  215. package/pipeline/scripts/smoke-source-parity.sh +0 -85
  216. package/pipeline/scripts/smoke-subagent-validators.sh +0 -108
  217. package/pipeline/scripts/smoke-sync-parity.sh +0 -92
  218. package/pipeline/scripts/smoke-tasklist-ordering.sh +0 -112
  219. package/pipeline/scripts/smoke-telemetry.sh +0 -147
  220. package/pipeline/scripts/smoke-test-gap.sh +0 -183
  221. package/pipeline/scripts/smoke-token-budget.sh +0 -67
  222. package/pipeline/scripts/smoke-token-preflight.sh +0 -82
  223. package/pipeline/scripts/smoke-tracker-contract.sh +0 -191
  224. package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +0 -73
  225. package/pipeline/scripts/smoke-triage-memory.sh +0 -174
  226. package/pipeline/scripts/smoke-update-check.sh +0 -135
  227. package/pipeline/scripts/smoke-url-enrichment.sh +0 -70
  228. package/pipeline/scripts/smoke-validate-analysis-doc.sh +0 -161
  229. package/pipeline/scripts/smoke-validator-contradiction.sh +0 -67
  230. package/pipeline/scripts/smoke-validator-gates.sh +0 -164
  231. package/pipeline/scripts/smoke-vercel-deploy-redact.sh +0 -129
  232. package/pipeline/scripts/smoke-verify-by-test.sh +0 -148
  233. package/pipeline/scripts/smoke-wiki-integration.sh +0 -122
  234. package/pipeline/scripts/smoke-work-summary.sh +0 -163
  235. package/pipeline/scripts/smoke-workflow-audit.sh +0 -101
  236. package/pipeline/scripts/smoke-worktree-path-convention.sh +0 -86
  237. package/pipeline/scripts/smoke-wrapper-preservation.sh +0 -68
  238. package/pipeline/scripts/smoke-write-state.sh +0 -159
  239. package/pipeline/scripts/sync-parity-check.sh +0 -135
  240. package/pipeline/scripts/test-gap-rules/android.json +0 -25
  241. package/pipeline/scripts/test-gap-rules/ios.json +0 -34
  242. package/pipeline/scripts/test-gap-rules/node.json +0 -29
  243. package/pipeline/scripts/test-gap-rules/python.json +0 -25
  244. package/pipeline/scripts/validate-schemas.mjs +0 -88
@@ -1,258 +0,0 @@
1
- #!/usr/bin/env node
2
- // scorecard.mjs - the measurable part of the review score, as a gate.
3
- //
4
- // A refactor review scores ten categories out of ten. Six of those numbers can
5
- // be measured; four cannot, and writing a number for those is how a scorecard
6
- // turns into self-congratulation. So this reports exactly two things:
7
- //
8
- // MEASURED a metric with a threshold, pass or fail
9
- // UNMEASURED a category no script can score, named as such
10
- //
11
- // Nothing here re-implements a check. Each metric either reads a committed
12
- // threshold file or shells out to the gate that already owns the question, so
13
- // there is one source of truth per question and the scorecard cannot drift from
14
- // the gates it summarises.
15
- //
16
- // Thresholds are locked at the values measured when the gate was written. They
17
- // are a ratchet: lowering one is a deliberate, reviewable edit, and a metric
18
- // that improves should have its floor raised.
19
- //
20
- // Runs on a clean clone: no $HOME lookups, no credentials, no network. The one
21
- // metric that needs local configuration (derived-skill drift) reports
22
- // NOT CONFIGURED rather than passing by default.
23
- //
24
- // Usage: node pipeline/scripts/scorecard.mjs [--json]
25
- // Exit: 0 every measured metric within threshold, 1 otherwise
26
-
27
- import { execFileSync } from "node:child_process";
28
- import { existsSync, readFileSync } from "node:fs";
29
- import { dirname, join } from "node:path";
30
- import { fileURLToPath } from "node:url";
31
-
32
- const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
33
- const asJson = process.argv.includes("--json");
34
-
35
- // Locked floors. Raise when a metric improves; lowering one is the reviewable act.
36
- const FLOORS = {
37
- coverageLines: 72,
38
- coverageBranches: 68,
39
- coverageFunctions: 85,
40
- contextBytes: 60000,
41
- };
42
-
43
- const results = [];
44
-
45
- function measured(category, metric, ok, detail) {
46
- results.push({ kind: "MEASURED", category, metric, ok, detail });
47
- }
48
- function unmeasured(category, why) {
49
- results.push({ kind: "UNMEASURED", category, why });
50
- }
51
-
52
- // Run a gate and report whether it passed. The gate owns the assertion; this
53
- // only records the verdict, so the two can never disagree.
54
- function gate(category, metric, argv) {
55
- try {
56
- execFileSync(argv[0], argv.slice(1), { cwd: ROOT, stdio: "pipe" });
57
- return measured(category, metric, true, "gate passed");
58
- } catch (e) {
59
- const out = `${e.stdout || ""}${e.stderr || ""}`.trim().split("\n").slice(-3).join(" | ");
60
- return measured(category, metric, false, out || `exit ${e.status}`);
61
- }
62
- }
63
-
64
- // --- Tests: the coverage floors must not be lowered ------------------------
65
- // c8 enforces the numbers on every run; what nothing enforced was the numbers
66
- // themselves. .c8rc.json reported 83% while the real figure over its own include
67
- // set was 33%, because `all` was missing - so the floors are worth pinning.
68
- {
69
- const c8 = JSON.parse(readFileSync(join(ROOT, ".c8rc.json"), "utf-8"));
70
- const checks = [
71
- ["lines", c8.lines, FLOORS.coverageLines],
72
- ["branches", c8.branches, FLOORS.coverageBranches],
73
- ["functions", c8.functions, FLOORS.coverageFunctions],
74
- ];
75
- const low = checks.filter(([, actual, floor]) => !(actual >= floor));
76
- measured(
77
- "Tests",
78
- "coverage floors held",
79
- c8.all === true && low.length === 0,
80
- c8.all !== true
81
- ? "`all` is not set, so the floors only cover files the tests already load"
82
- : low.length
83
- ? low.map(([k, a, f]) => `${k} ${a} below the locked ${f}`).join(", ")
84
- : checks.map(([k, a]) => `${k} ${a}`).join(", "),
85
- );
86
- }
87
-
88
- // --- Dependency management: no high or critical advisories ------------------
89
- {
90
- let vulns;
91
- try {
92
- const raw = execFileSync("npm", ["audit", "--json"], { cwd: ROOT, encoding: "utf-8" });
93
- vulns = JSON.parse(raw).metadata.vulnerabilities;
94
- } catch (e) {
95
- // npm audit exits non-zero when it finds something; the JSON is still on stdout.
96
- try {
97
- vulns = JSON.parse(e.stdout).metadata.vulnerabilities;
98
- } catch {
99
- vulns = null;
100
- }
101
- }
102
- if (!vulns) {
103
- measured(
104
- "Dependency management",
105
- "no high or critical advisories",
106
- false,
107
- "npm audit produced no parseable report",
108
- );
109
- } else {
110
- const bad = vulns.high + vulns.critical;
111
- measured(
112
- "Dependency management",
113
- "no high or critical advisories",
114
- bad === 0,
115
- `critical ${vulns.critical}, high ${vulns.high}, moderate ${vulns.moderate}`,
116
- );
117
- }
118
- }
119
-
120
- // --- Performance: the bytes every run pays before it starts ----------------
121
- gate("Performance", "fixed per-run context within budget", [
122
- "bash",
123
- "pipeline/scripts/smoke-context-budget.sh",
124
- ]);
125
-
126
- // --- CI/CD: the workflow audit runs, and finds nothing ---------------------
127
- // Strict by default, so a runner without the linters fails here rather than
128
- // reporting a clean audit it never performed.
129
- gate("CI/CD", "workflow audit clean (zizmor + actionlint)", [
130
- "bash",
131
- "pipeline/scripts/smoke-workflow-audit.sh",
132
- ]);
133
-
134
- // --- Code quality: our own punctuation rule, both encodings ---------------
135
- gate("Code quality", "no banned punctuation in shipped files", [
136
- "bash",
137
- "pipeline/scripts/smoke-own-punctuation.sh",
138
- ]);
139
-
140
- // --- Documentation: every skill description routes ------------------------
141
- gate("Documentation", "every skill description has a routing clause", [
142
- "node",
143
- "pipeline/scripts/lint-skills.mjs",
144
- ]);
145
-
146
- // --- Documentation: the licence and changelog a consumer expects ----------
147
- {
148
- const missing = ["LICENSE", "CHANGELOG.md", "README.md", "SECURITY.md"].filter(
149
- (f) => !existsSync(join(ROOT, f)),
150
- );
151
- measured(
152
- "Documentation",
153
- "licence, changelog, readme, security present",
154
- missing.length === 0,
155
- missing.length ? `missing: ${missing.join(", ")}` : "all four present",
156
- );
157
- }
158
-
159
- // --- Tests: no suite reports success without asserting anything ------------
160
- // The runner owns this check; asking it for the counts would re-run 120 suites,
161
- // so the scorecard asserts the capability is wired rather than re-measuring it.
162
- {
163
- const runner = readFileSync(join(ROOT, "pipeline", "scripts", "run-smokes.mjs"), "utf-8");
164
- const wired = runner.includes("countAssertions") && /asserted nothing/.test(runner);
165
- measured(
166
- "Tests",
167
- "runner rejects a suite that asserted nothing",
168
- wired,
169
- wired
170
- ? "zero-assertion check present in run-smokes.mjs"
171
- : "run-smokes.mjs no longer counts assertions",
172
- );
173
- }
174
-
175
- // --- Drift: needs local configuration, so it reports honestly -------------
176
- {
177
- // The exit code alone cannot answer this. check-derived-drift exits 0 both
178
- // when every derivation is current AND when nothing is configured at all, and
179
- // those are different facts: one is a pass, the other is an absence of
180
- // evidence. Reading `configured` off the JSON is what tells them apart. The
181
- // first version of this block trusted exit 0 and reported an unconfigured
182
- // machine as a passing metric, which is the exact failure this whole gate set
183
- // exists to prevent.
184
- let state;
185
- let ok = false;
186
- let parsed;
187
- try {
188
- parsed = JSON.parse(
189
- execFileSync("node", ["pipeline/scripts/check-derived-drift.mjs", "--json"], {
190
- cwd: ROOT,
191
- encoding: "utf-8",
192
- stdio: ["ignore", "pipe", "ignore"],
193
- }),
194
- );
195
- } catch (e) {
196
- // Non-zero exit still writes the report to stdout; an unparseable one means
197
- // the check itself could not run.
198
- try {
199
- parsed = JSON.parse(e.stdout);
200
- } catch {
201
- parsed = undefined;
202
- }
203
- }
204
- if (parsed && parsed.configured > 0) {
205
- const bad = parsed.drifted + parsed.unverified + parsed.unreachable;
206
- ok = bad === 0;
207
- state = ok
208
- ? `${parsed.configured} derivation(s) current with upstream`
209
- : `${parsed.drifted} drifted, ${parsed.unverified} unverified, ${parsed.unreachable} unreachable`;
210
- } else if (parsed) {
211
- state = "NOT CONFIGURED - prefs carry no derivedSkillSources on this machine";
212
- } else {
213
- state = "NOT CONFIGURED - the drift check produced no parseable report";
214
- }
215
- if (state.startsWith("NOT CONFIGURED")) {
216
- unmeasured("Derived-skill drift", state);
217
- } else {
218
- measured("Architecture", "derived skills current with upstream", ok, state);
219
- }
220
- }
221
-
222
- // --- What no script can score --------------------------------------------
223
- unmeasured("Architecture", "layer separation and dependency direction are judgement, not a number");
224
- unmeasured("DevEx", "onboarding time and error-message quality need a person");
225
- unmeasured(
226
- "Accessibility",
227
- "nothing in this repo renders UI; the audits it ships are scored per target app",
228
- );
229
- unmeasured(
230
- "Code quality",
231
- "naming, cohesion and altitude are judgement; only the mechanical rules above are gated",
232
- );
233
-
234
- const failed = results.filter((r) => r.kind === "MEASURED" && !r.ok);
235
- const passed = results.filter((r) => r.kind === "MEASURED" && r.ok);
236
-
237
- if (asJson) {
238
- console.log(JSON.stringify({ passed: passed.length, failed: failed.length, results }, null, 2));
239
- } else {
240
- console.log("MEASURED");
241
- for (const r of results.filter((x) => x.kind === "MEASURED")) {
242
- console.log(` ${r.ok ? "✓" : "✗"} ${r.category}: ${r.metric}`);
243
- console.log(` ${r.detail}`);
244
- }
245
- console.log("");
246
- console.log("UNMEASURED (reported, never scored)");
247
- for (const r of results.filter((x) => x.kind === "UNMEASURED")) {
248
- console.log(` - ${r.category}: ${r.why}`);
249
- }
250
- console.log("");
251
- console.log(`══ scorecard: ${passed.length} measured metrics passed, ${failed.length} failed ══`);
252
- if (failed.length === 0) {
253
- console.log("Every mechanically checkable claim holds. The four unmeasured");
254
- console.log("categories above are not covered by that statement.");
255
- }
256
- }
257
-
258
- process.exit(failed.length === 0 ? 0 : 1);
@@ -1,137 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-add-detail.sh - contract check for the `add-detail` command.
3
- #
4
- # Does NOT hit real Bitbucket / Jira / GitHub. Instead:
5
- # 1. Generates a fake PR diff and body fixture.
6
- # 2. Runs the body-file + jq --rawfile + curl --data-binary pipeline
7
- # against a local `jq` / `python -c` simulator that checks the
8
- # resulting JSON payload round-trips with:
9
- # - real newlines (no literal "\n")
10
- # - no HTML entities (& / < / " / ')
11
- # - required Bitbucket PUT fields present: reviewers, fromRef, toRef, draft
12
- # 3. Returns non-zero if any contract is violated.
13
- #
14
- # Intended as a CI guard so regressions like the "23 reviewers wiped" incident
15
- # cannot land unnoticed.
16
-
17
- set -euo pipefail
18
-
19
- TMP="$(mktemp -d -t multi-agent-smoke-XXXX)" || { echo "FATAL: mktemp failed" >&2; exit 1; }
20
- trap 'rm -rf "$TMP"' EXIT
21
-
22
- PASS=0
23
- FAIL=0
24
-
25
- pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
26
- fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
27
-
28
- need() {
29
- command -v "$1" >/dev/null 2>&1 || {
30
- echo "error: $1 is required" >&2
31
- exit 2
32
- }
33
- }
34
- need jq
35
- need python3
36
-
37
- echo "→ Writing fixture body with newlines, '&' and Turkish chars"
38
- cat > "$TMP/body.md" <<'EOF'
39
- ## Özet
40
-
41
- - Kök neden: UILabel'e `adjustsFontSizeToFitWidth` eklenmemiş
42
- - Çözüm: `minimumScaleFactor = 0.7` ile ölçek desteği
43
- - Etki: İspanyolca & Almanca'da taşma düzeliyor
44
-
45
- ## Test Senaryoları
46
-
47
- | # | Ön koşul | Adımlar | Beklenen |
48
- |---|----------|---------|----------|
49
- | 1 | Dil = ES | Check-in Summary aç | Şehir isimleri sığar |
50
- EOF
51
-
52
- echo "→ Building payload with jq --rawfile"
53
- jq -n \
54
- --rawfile body "$TMP/body.md" \
55
- --arg title 'fix(checkin): Spanish overflow [PROJ-12345]' \
56
- '{
57
- title: $title,
58
- description: $body,
59
- fromRef: { id: "refs/heads/bugfix/PROJ-12345", repository: { slug: "my-app", project: { key: "APP" } } },
60
- toRef: { id: "refs/heads/develop", repository: { slug: "my-app", project: { key: "APP" } } },
61
- reviewers: [{ user: { name: "alice" } }, { user: { name: "bob" } }],
62
- draft: false,
63
- state: "OPEN", open: true, closed: false, locked: false
64
- }' > "$TMP/payload.json"
65
-
66
- echo "→ Contract checks"
67
-
68
- # Contract 1: payload is valid JSON.
69
- if jq -e . "$TMP/payload.json" >/dev/null 2>&1; then
70
- pass "payload is valid JSON"
71
- else
72
- fail "payload is not valid JSON"
73
- fi
74
-
75
- # Contract 2: description field round-trips with real newlines (not literal \n).
76
- DESC="$(jq -r .description "$TMP/payload.json")"
77
- if [[ "$DESC" == *$'\n'* ]]; then
78
- pass "description contains real newlines"
79
- else
80
- fail "description is single-line - newlines were escaped"
81
- fi
82
-
83
- # Contract 3: no literal backslash-n sequence survived.
84
- if grep -q '\\n' <<< "$DESC"; then
85
- fail "literal \\n escaped into description"
86
- else
87
- pass "no literal \\n in description"
88
- fi
89
-
90
- # Contract 4: no HTML entities in title or description.
91
- for field in title description; do
92
- VALUE="$(jq -r --arg f "$field" '.[$f]' "$TMP/payload.json")"
93
- if grep -Eq '&(amp|lt|gt|quot|#39);' <<< "$VALUE"; then
94
- fail "$field contains HTML entities"
95
- else
96
- pass "$field has no HTML entities"
97
- fi
98
- done
99
-
100
- # Contract 5: UTF-8 survives end-to-end (checks 'Ö', 'İ', 'ç').
101
- for glyph in 'Ö' 'İ' 'ç'; do
102
- if grep -q "$glyph" <<< "$DESC"; then
103
- pass "UTF-8 glyph '$glyph' preserved"
104
- else
105
- fail "UTF-8 glyph '$glyph' missing from description"
106
- fi
107
- done
108
-
109
- # Contract 6: Bitbucket PUT contract - all of reviewers/fromRef/toRef/draft present.
110
- for field in reviewers fromRef toRef draft; do
111
- if jq -e "has(\"$field\")" "$TMP/payload.json" >/dev/null; then
112
- pass "payload has .$field"
113
- else
114
- fail "payload missing .$field (Bitbucket will wipe it)"
115
- fi
116
- done
117
-
118
- # Contract 7: reviewers is a non-empty array for this fixture.
119
- LEN="$(jq '.reviewers | length' "$TMP/payload.json")"
120
- if [ "$LEN" -gt 0 ]; then
121
- pass "reviewers array preserved ($LEN entries)"
122
- else
123
- fail "reviewers array is empty - would wipe defaults"
124
- fi
125
-
126
- # Contract 8: fromRef / toRef are distinct (PR to self is rejected by Bitbucket).
127
- FROM="$(jq -r .fromRef.id "$TMP/payload.json")"
128
- TO="$(jq -r .toRef.id "$TMP/payload.json")"
129
- if [ "$FROM" != "$TO" ]; then
130
- pass "fromRef ($FROM) ≠ toRef ($TO)"
131
- else
132
- fail "fromRef == toRef - invalid PR"
133
- fi
134
-
135
- echo
136
- echo "══ add-detail smoke: $PASS passed, $FAIL failed ══"
137
- [ "$FAIL" -eq 0 ]
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-agent-guard.sh - behavioral contract for the agent-guard PreToolUse hook.
3
- #
4
- # Verifies the guard blocks (exit 2) AI-attribution commits and force-push to a
5
- # protected branch, allows (exit 0) everything else, fails open on bad input, and
6
- # NEVER executes the inspected command (injection safety).
7
- #
8
- # Runs the guard from the repo copy in a throwaway git repo whose branch we set,
9
- # so the "bare push on a protected branch" path is exercised deterministically.
10
- #
11
- # Exit 0 = all pass, 1 = any failure.
12
-
13
- set -uo pipefail
14
-
15
- ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
16
- GUARD="$ROOT/pipeline/scripts/agent-guard.sh"
17
- command -v python3 >/dev/null 2>&1 || { echo "error: python3 required" >&2; exit 127; }
18
- [ -f "$GUARD" ] || { echo "FAIL: agent-guard.sh missing at $GUARD" >&2; exit 1; }
19
- [ -f "$ROOT/pipeline/scripts/agent-guard.py" ] || { echo "FAIL: agent-guard.py missing" >&2; exit 1; }
20
-
21
- PASS=0; FAIL=0
22
- pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
23
- fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
24
-
25
- # throwaway repo on a protected branch so `git rev-parse` inside the guard = main
26
- WORK="$(mktemp -d)"
27
- trap 'rm -rf "$WORK"' EXIT
28
- # init on main WITH a commit so `git rev-parse --abbrev-ref HEAD` resolves to main
29
- # (an unborn branch reports HEAD, which would not exercise the bare-push path)
30
- ( cd "$WORK" \
31
- && git init -q \
32
- && git symbolic-ref HEAD refs/heads/main \
33
- && git -c user.email=t@t -c user.name=t commit -q --allow-empty -m init ) 2>/dev/null
34
-
35
- J() { python3 -c 'import json,sys; print(json.dumps({"tool_input":{"command":sys.argv[1]}}))' "$1"; }
36
- run() { # label expected-exit command-string
37
- local got
38
- ( cd "$WORK" && printf '%s' "$(J "$3")" | bash "$GUARD" >/dev/null 2>&1 )
39
- got=$?
40
- if [ "$got" = "$2" ]; then pass "[$got] $1"; else fail "expected $2 got $got: $1"; fi
41
- }
42
-
43
- echo "→ blocks (exit 2)"
44
- run "commit Co-Authored-By: Claude" 2 'git commit -m "x
45
-
46
- Co-Authored-By: Claude <noreply@anthropic.com>"'
47
- run "commit robot-emoji" 2 'git commit -m "x
48
-
49
- 🤖 Generated with Claude Code"'
50
- run "push --force origin main" 2 'git push --force origin main'
51
- run "push -f origin develop" 2 'git push -f origin develop'
52
- run "push --force-with-lease master" 2 'git push --force-with-lease origin master'
53
- run "bare push -f on main" 2 'git push -f'
54
-
55
- echo "→ allows (exit 0)"
56
- run "normal commit" 0 'git commit -m "feat: normal"'
57
- run "push no force" 0 'git push origin main'
58
- run "force push feature branch" 0 'git push --force origin feature/DEMO-1'
59
- run "force push tag" 0 'git push -f origin v1.2.3'
60
- run "non-git command" 0 'ls -la /tmp'
61
- run "empty command" 0 ''
62
-
63
- echo "→ fail-open on bad input"
64
- ( cd "$WORK" && printf 'not-json{' | bash "$GUARD" >/dev/null 2>&1 ); [ $? -eq 0 ] && pass "malformed json -> allow" || fail "malformed json blocked"
65
- ( cd "$WORK" && printf '' | bash "$GUARD" >/dev/null 2>&1 ); [ $? -eq 0 ] && pass "empty stdin -> allow" || fail "empty stdin blocked"
66
-
67
- echo "→ injection safety (inspected command must NOT execute)"
68
- SENT="$WORK/sentinel"; : > "$SENT"
69
- ( cd "$WORK" && printf '%s' "$(J "git commit -m \"\$(rm -f '$SENT')\"")" | bash "$GUARD" >/dev/null 2>&1 )
70
- [ -f "$SENT" ] && pass "sentinel survived (no execution)" || fail "sentinel deleted - command executed"
71
-
72
- echo ""
73
- echo "══ agent-guard smoke: $PASS passed, $FAIL failed ══"
74
- [ "$FAIL" -eq 0 ]