@mmerterden/multi-agent-pipeline 12.7.0 → 12.9.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 (247) hide show
  1. package/CHANGELOG.md +207 -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/features/review-multi-repo.md +65 -0
  13. package/pipeline/multi-agent-refs/features/url-enrichment.md +93 -0
  14. package/pipeline/multi-agent-refs/phases/operations.md +28 -0
  15. package/pipeline/multi-agent-refs/phases/phase-0-init.md +3 -83
  16. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +1 -1
  17. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -2
  18. package/pipeline/multi-agent-refs/phases/phase-4-review.md +50 -62
  19. package/pipeline/schemas/prefs.schema.json +6 -0
  20. package/pipeline/scripts/_smoke-root.sh +61 -0
  21. package/pipeline/scripts/agent-guard.py +57 -3
  22. package/pipeline/scripts/audit-log.sh +25 -0
  23. package/pipeline/scripts/smoke-cross-cli-behavior.sh +21 -7
  24. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +0 -25
  25. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +0 -30
  26. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +0 -20
  27. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +0 -15
  28. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +0 -14
  29. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +0 -12
  30. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +0 -29
  31. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +0 -43
  32. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +0 -35
  33. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +0 -35
  34. package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +0 -14
  35. package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +0 -12
  36. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-1-analysis.json +0 -29
  37. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-2-plan.json +0 -42
  38. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-review.json +0 -20
  39. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-triage.json +0 -15
  40. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/metadata.json +0 -14
  41. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/task.json +0 -12
  42. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-1-analysis.json +0 -29
  43. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-2-plan.json +0 -40
  44. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-review.json +0 -20
  45. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-triage.json +0 -15
  46. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/metadata.json +0 -14
  47. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/task.json +0 -12
  48. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-1-analysis.json +0 -29
  49. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-2-plan.json +0 -42
  50. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-review.json +0 -28
  51. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-triage.json +0 -27
  52. package/pipeline/eval/golden-tasks/05-ios-security-keychain/metadata.json +0 -14
  53. package/pipeline/eval/golden-tasks/05-ios-security-keychain/task.json +0 -12
  54. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-1-analysis.json +0 -29
  55. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-2-plan.json +0 -41
  56. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-review.json +0 -12
  57. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-triage.json +0 -6
  58. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/metadata.json +0 -14
  59. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/task.json +0 -12
  60. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-1-analysis.json +0 -29
  61. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-2-plan.json +0 -42
  62. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-review.json +0 -28
  63. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-triage.json +0 -27
  64. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/metadata.json +0 -14
  65. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/task.json +0 -12
  66. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-1-analysis.json +0 -25
  67. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-2-plan.json +0 -31
  68. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-review.json +0 -12
  69. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-triage.json +0 -18
  70. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/metadata.json +0 -14
  71. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/task.json +0 -12
  72. package/pipeline/eval/golden-tasks/README.md +0 -65
  73. package/pipeline/eval/intent-cases.json +0 -40
  74. package/pipeline/eval/run-metrics-fixture.json +0 -93
  75. package/pipeline/eval/triage/01-empty-findings/expected.json +0 -6
  76. package/pipeline/eval/triage/01-empty-findings/input.json +0 -5
  77. package/pipeline/eval/triage/01-empty-findings/notes.md +0 -7
  78. package/pipeline/eval/triage/02-real-blocker/expected.json +0 -15
  79. package/pipeline/eval/triage/02-real-blocker/input.json +0 -14
  80. package/pipeline/eval/triage/02-real-blocker/notes.md +0 -7
  81. package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +0 -18
  82. package/pipeline/eval/triage/03-out-of-scope-defer/input.json +0 -14
  83. package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +0 -10
  84. package/pipeline/eval/triage/04-false-positive-reject/expected.json +0 -18
  85. package/pipeline/eval/triage/04-false-positive-reject/input.json +0 -14
  86. package/pipeline/eval/triage/04-false-positive-reject/notes.md +0 -10
  87. package/pipeline/eval/triage/05-mixed-classification/expected.json +0 -43
  88. package/pipeline/eval/triage/05-mixed-classification/input.json +0 -38
  89. package/pipeline/eval/triage/05-mixed-classification/notes.md +0 -17
  90. package/pipeline/eval/triage/06-severity-mismatch/expected.json +0 -15
  91. package/pipeline/eval/triage/06-severity-mismatch/input.json +0 -14
  92. package/pipeline/eval/triage/06-severity-mismatch/notes.md +0 -9
  93. package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +0 -27
  94. package/pipeline/eval/triage/07-duplicate-reviewers/input.json +0 -22
  95. package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +0 -9
  96. package/pipeline/eval/triage/08-style-misclassified/expected.json +0 -18
  97. package/pipeline/eval/triage/08-style-misclassified/input.json +0 -14
  98. package/pipeline/eval/triage/08-style-misclassified/notes.md +0 -9
  99. package/pipeline/eval/triage/09-cascading-finding/expected.json +0 -23
  100. package/pipeline/eval/triage/09-cascading-finding/input.json +0 -22
  101. package/pipeline/eval/triage/09-cascading-finding/notes.md +0 -9
  102. package/pipeline/eval/triage/10-deferred-crossref/expected.json +0 -18
  103. package/pipeline/eval/triage/10-deferred-crossref/input.json +0 -14
  104. package/pipeline/eval/triage/10-deferred-crossref/notes.md +0 -9
  105. package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +0 -27
  106. package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +0 -22
  107. package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +0 -14
  108. package/pipeline/eval/triage/README.md +0 -54
  109. package/pipeline/scripts/benchmark-phase-0.sh +0 -128
  110. package/pipeline/scripts/check-md-links.mjs +0 -88
  111. package/pipeline/scripts/eval-golden-tasks-live.mjs +0 -302
  112. package/pipeline/scripts/eval-golden-tasks.mjs +0 -224
  113. package/pipeline/scripts/eval-intent.mjs +0 -107
  114. package/pipeline/scripts/eval-mine-corpus.mjs +0 -211
  115. package/pipeline/scripts/eval-triage.mjs +0 -171
  116. package/pipeline/scripts/fixtures/diff-risk-android.diff +0 -40
  117. package/pipeline/scripts/fixtures/diff-risk-ios.diff +0 -48
  118. package/pipeline/scripts/fixtures/diff-risk-test-removal.diff +0 -40
  119. package/pipeline/scripts/fixtures/install-layout.tsv +0 -19
  120. package/pipeline/scripts/fixtures/pack-expected-count.txt +0 -1
  121. package/pipeline/scripts/fixtures/test-gap-node.diff +0 -30
  122. package/pipeline/scripts/fixtures/test-gap-python.diff +0 -32
  123. package/pipeline/scripts/lint-mcp-refs.mjs +0 -218
  124. package/pipeline/scripts/lint-skills.mjs +0 -154
  125. package/pipeline/scripts/run-smokes.mjs +0 -130
  126. package/pipeline/scripts/scorecard.mjs +0 -258
  127. package/pipeline/scripts/smoke-add-detail.sh +0 -137
  128. package/pipeline/scripts/smoke-agent-guard.sh +0 -74
  129. package/pipeline/scripts/smoke-agent-log-cost.sh +0 -262
  130. package/pipeline/scripts/smoke-agent-model-routing.sh +0 -87
  131. package/pipeline/scripts/smoke-ask-choice.sh +0 -42
  132. package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +0 -36
  133. package/pipeline/scripts/smoke-bitbucket-contract.sh +0 -255
  134. package/pipeline/scripts/smoke-changelog-version.sh +0 -47
  135. package/pipeline/scripts/smoke-channels-approval-gate.sh +0 -60
  136. package/pipeline/scripts/smoke-channels-flow.sh +0 -130
  137. package/pipeline/scripts/smoke-ci-workflows.sh +0 -88
  138. package/pipeline/scripts/smoke-clarify.sh +0 -148
  139. package/pipeline/scripts/smoke-command-inventory.sh +0 -81
  140. package/pipeline/scripts/smoke-commands-skills-parity.sh +0 -87
  141. package/pipeline/scripts/smoke-community-gates.sh +0 -75
  142. package/pipeline/scripts/smoke-compliance-skills.sh +0 -119
  143. package/pipeline/scripts/smoke-config-hygiene.sh +0 -58
  144. package/pipeline/scripts/smoke-context-budget.sh +0 -72
  145. package/pipeline/scripts/smoke-cost-budget.sh +0 -70
  146. package/pipeline/scripts/smoke-cost-summary.sh +0 -139
  147. package/pipeline/scripts/smoke-cross-phase-cohesion.sh +0 -128
  148. package/pipeline/scripts/smoke-description-tr.sh +0 -82
  149. package/pipeline/scripts/smoke-dev-critic.sh +0 -144
  150. package/pipeline/scripts/smoke-diff-explain.sh +0 -147
  151. package/pipeline/scripts/smoke-diff-risk.sh +0 -190
  152. package/pipeline/scripts/smoke-dynamic-skill-loading.sh +0 -160
  153. package/pipeline/scripts/smoke-eval-live.sh +0 -136
  154. package/pipeline/scripts/smoke-evidence-gate.sh +0 -93
  155. package/pipeline/scripts/smoke-extract-conventions.sh +0 -163
  156. package/pipeline/scripts/smoke-fetchers-offline.sh +0 -448
  157. package/pipeline/scripts/smoke-figma-dispatch.sh +0 -112
  158. package/pipeline/scripts/smoke-gate-hooks.sh +0 -74
  159. package/pipeline/scripts/smoke-gc-tmp.sh +0 -130
  160. package/pipeline/scripts/smoke-gc-worktrees.sh +0 -125
  161. package/pipeline/scripts/smoke-generate-issue.sh +0 -120
  162. package/pipeline/scripts/smoke-handoff-contract.sh +0 -92
  163. package/pipeline/scripts/smoke-identity-isolation.sh +0 -70
  164. package/pipeline/scripts/smoke-install-layout.sh +0 -248
  165. package/pipeline/scripts/smoke-intent-guard.sh +0 -86
  166. package/pipeline/scripts/smoke-issue-comment-template.sh +0 -86
  167. package/pipeline/scripts/smoke-issue-jira-triad.sh +0 -120
  168. package/pipeline/scripts/smoke-keychain.sh +0 -158
  169. package/pipeline/scripts/smoke-language-axis.sh +0 -109
  170. package/pipeline/scripts/smoke-learning-curve.sh +0 -61
  171. package/pipeline/scripts/smoke-learnings-ledger.sh +0 -86
  172. package/pipeline/scripts/smoke-lib-scripts.sh +0 -448
  173. package/pipeline/scripts/smoke-mcp-gate.sh +0 -68
  174. package/pipeline/scripts/smoke-md-links.sh +0 -8
  175. package/pipeline/scripts/smoke-md2confluence.sh +0 -126
  176. package/pipeline/scripts/smoke-metrics-cache-ratio.sh +0 -72
  177. package/pipeline/scripts/smoke-migrate-state.sh +0 -102
  178. package/pipeline/scripts/smoke-mode-dispatch-drift.sh +0 -161
  179. package/pipeline/scripts/smoke-model-fallback.sh +0 -89
  180. package/pipeline/scripts/smoke-multi-repo-integration.sh +0 -116
  181. package/pipeline/scripts/smoke-multi-repo-worktree.sh +0 -61
  182. package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +0 -194
  183. package/pipeline/scripts/smoke-no-token-prompt.sh +0 -85
  184. package/pipeline/scripts/smoke-own-punctuation.sh +0 -103
  185. package/pipeline/scripts/smoke-pack-contents.sh +0 -140
  186. package/pipeline/scripts/smoke-pat-audit.sh +0 -128
  187. package/pipeline/scripts/smoke-per-repo-memory.sh +0 -156
  188. package/pipeline/scripts/smoke-phase-0-multi-repo.sh +0 -170
  189. package/pipeline/scripts/smoke-phase-6-multi.sh +0 -79
  190. package/pipeline/scripts/smoke-phase-banner.sh +0 -101
  191. package/pipeline/scripts/smoke-phase-tracker.sh +0 -324
  192. package/pipeline/scripts/smoke-phase0-bridge-contract.sh +0 -241
  193. package/pipeline/scripts/smoke-phase4-gates.sh +0 -45
  194. package/pipeline/scripts/smoke-phase4-triage.sh +0 -229
  195. package/pipeline/scripts/smoke-plan-approval-gate.sh +0 -71
  196. package/pipeline/scripts/smoke-plan-safety.sh +0 -139
  197. package/pipeline/scripts/smoke-plan-todos.sh +0 -196
  198. package/pipeline/scripts/smoke-pr-review-actions.sh +0 -152
  199. package/pipeline/scripts/smoke-pre-commit.sh +0 -170
  200. package/pipeline/scripts/smoke-pref-migration.sh +0 -226
  201. package/pipeline/scripts/smoke-prefs-language.sh +0 -134
  202. package/pipeline/scripts/smoke-progress-contract.sh +0 -127
  203. package/pipeline/scripts/smoke-prune-logs.sh +0 -137
  204. package/pipeline/scripts/smoke-purge.sh +0 -138
  205. package/pipeline/scripts/smoke-push-retry.sh +0 -75
  206. package/pipeline/scripts/smoke-repo-map.sh +0 -300
  207. package/pipeline/scripts/smoke-review-readiness.sh +0 -92
  208. package/pipeline/scripts/smoke-review-watch.sh +0 -146
  209. package/pipeline/scripts/smoke-routines.sh +0 -84
  210. package/pipeline/scripts/smoke-run-aggregator.sh +0 -216
  211. package/pipeline/scripts/smoke-run-metrics.sh +0 -50
  212. package/pipeline/scripts/smoke-search.sh +0 -187
  213. package/pipeline/scripts/smoke-shadow-git.sh +0 -224
  214. package/pipeline/scripts/smoke-skill-authoring.sh +0 -137
  215. package/pipeline/scripts/smoke-skill-language.sh +0 -83
  216. package/pipeline/scripts/smoke-skill-manifest.sh +0 -138
  217. package/pipeline/scripts/smoke-skill-scan.sh +0 -198
  218. package/pipeline/scripts/smoke-source-parity.sh +0 -85
  219. package/pipeline/scripts/smoke-subagent-validators.sh +0 -108
  220. package/pipeline/scripts/smoke-sync-parity.sh +0 -92
  221. package/pipeline/scripts/smoke-tasklist-ordering.sh +0 -112
  222. package/pipeline/scripts/smoke-telemetry.sh +0 -147
  223. package/pipeline/scripts/smoke-test-gap.sh +0 -183
  224. package/pipeline/scripts/smoke-token-budget.sh +0 -67
  225. package/pipeline/scripts/smoke-token-preflight.sh +0 -82
  226. package/pipeline/scripts/smoke-tracker-contract.sh +0 -191
  227. package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +0 -73
  228. package/pipeline/scripts/smoke-triage-memory.sh +0 -174
  229. package/pipeline/scripts/smoke-update-check.sh +0 -135
  230. package/pipeline/scripts/smoke-url-enrichment.sh +0 -70
  231. package/pipeline/scripts/smoke-validate-analysis-doc.sh +0 -161
  232. package/pipeline/scripts/smoke-validator-contradiction.sh +0 -67
  233. package/pipeline/scripts/smoke-validator-gates.sh +0 -164
  234. package/pipeline/scripts/smoke-vercel-deploy-redact.sh +0 -129
  235. package/pipeline/scripts/smoke-verify-by-test.sh +0 -148
  236. package/pipeline/scripts/smoke-wiki-integration.sh +0 -122
  237. package/pipeline/scripts/smoke-work-summary.sh +0 -163
  238. package/pipeline/scripts/smoke-workflow-audit.sh +0 -101
  239. package/pipeline/scripts/smoke-worktree-path-convention.sh +0 -86
  240. package/pipeline/scripts/smoke-wrapper-preservation.sh +0 -68
  241. package/pipeline/scripts/smoke-write-state.sh +0 -159
  242. package/pipeline/scripts/sync-parity-check.sh +0 -135
  243. package/pipeline/scripts/test-gap-rules/android.json +0 -25
  244. package/pipeline/scripts/test-gap-rules/ios.json +0 -34
  245. package/pipeline/scripts/test-gap-rules/node.json +0 -29
  246. package/pipeline/scripts/test-gap-rules/python.json +0 -25
  247. 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 ]