@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,324 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-phase-tracker.sh - contract check for phase-tracker.sh.
3
- #
4
- # Verifies state file shape, status transitions, sub-phase add/update,
5
- # enum enforcement, render output structure, and that the .tracker-current
6
- # pointer redirects subsequent invocations.
7
-
8
- set -uo pipefail
9
-
10
- HERE="$(cd "$(dirname "$0")" && pwd)"
11
- ROOT="$(cd "$HERE/../.." && pwd)"
12
- SCRIPT="$ROOT/pipeline/scripts/phase-tracker.sh"
13
-
14
- # Use a sandbox HOME so init writes into a temp tree, not the real one.
15
- SANDBOX_HOME="$(mktemp -d -t multi-agent-tracker-XXXX)"
16
- trap 'rm -rf "$SANDBOX_HOME"' EXIT
17
- export HOME="$SANDBOX_HOME"
18
-
19
- PASS=0
20
- FAIL=0
21
- pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
22
- fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
23
-
24
- need() { command -v "$1" >/dev/null 2>&1 || { echo "error: $1 is required" >&2; exit 127; }; }
25
- need jq
26
-
27
- run_plain() {
28
- TERM=dumb "$SCRIPT" "$@" 2>/tmp/tracker-err
29
- }
30
- run_plain_status() {
31
- local rc=0
32
- TERM=dumb "$SCRIPT" "$@" >/dev/null 2>/tmp/tracker-err || rc=$?
33
- echo "$rc"
34
- }
35
-
36
- STATE_FILE="$SANDBOX_HOME/.claude/logs/multi-agent/TASK-1/tracker-state.json"
37
-
38
- echo "→ 1. init creates state file with empty phases"
39
- run_plain init TASK-1 >/dev/null
40
- [ -f "$STATE_FILE" ] && pass "state file created at expected path" || fail "state file not at $STATE_FILE"
41
- PHASES=$(jq '.phases | length' "$STATE_FILE")
42
- [ "$PHASES" = "0" ] && pass "starts with empty phases array" || fail "expected 0 phases, got $PHASES"
43
- TASK_ID=$(jq -r '.task_id' "$STATE_FILE")
44
- [ "$TASK_ID" = "TASK-1" ] && pass "task_id persisted" || fail "task_id wrong: $TASK_ID"
45
-
46
- echo ""
47
- echo "→ 2. .tracker-current pointer is written"
48
- POINTER="$SANDBOX_HOME/.claude/logs/multi-agent/.tracker-current"
49
- [ -f "$POINTER" ] && pass "pointer file exists" || fail "pointer not created"
50
- [ "$(cat "$POINTER")" = "$STATE_FILE" ] && pass "pointer points at correct state file" || fail "pointer wrong"
51
-
52
- echo ""
53
- echo "→ 3. add appends pending phases"
54
- run_plain add 0 "Init" >/dev/null
55
- run_plain add 1 "Analysis" >/dev/null
56
- run_plain add 2 "Planning" >/dev/null
57
- PHASES=$(jq '.phases | length' "$STATE_FILE")
58
- [ "$PHASES" = "3" ] && pass "3 phases registered" || fail "expected 3, got $PHASES"
59
- P0_STATUS=$(jq -r '.phases[0].status' "$STATE_FILE")
60
- [ "$P0_STATUS" = "pending" ] && pass "new phase defaults to pending" || fail "expected pending, got $P0_STATUS"
61
-
62
- echo ""
63
- echo "→ 4. update transitions status"
64
- run_plain update 0 in_progress >/dev/null
65
- [ "$(jq -r '.phases[0].status' "$STATE_FILE")" = "in_progress" ] && pass "pending → in_progress" || fail "transition failed"
66
- run_plain update 0 completed >/dev/null
67
- [ "$(jq -r '.phases[0].status' "$STATE_FILE")" = "completed" ] && pass "in_progress → completed" || fail "transition failed"
68
-
69
- echo ""
70
- echo "→ 5. status enum enforcement"
71
- RC=$(run_plain_status update 0 nonsense)
72
- [ "$RC" = "64" ] && pass "bogus status rejected (exit 64)" || fail "bogus status accepted (rc=$RC)"
73
-
74
- echo ""
75
- echo "→ 6. sub adds and updates a sub-phase"
76
- run_plain sub 1 1 "Stack detection" completed >/dev/null
77
- run_plain sub 1 2 "Codebase scan" in_progress >/dev/null
78
- SUBS=$(jq '.phases[1].subs | length' "$STATE_FILE")
79
- [ "$SUBS" = "2" ] && pass "two subs added under phase 1" || fail "expected 2 subs, got $SUBS"
80
- S2_STATUS=$(jq -r '.phases[1].subs[1].status' "$STATE_FILE")
81
- [ "$S2_STATUS" = "in_progress" ] && pass "sub 1.2 is in_progress" || fail "wrong status: $S2_STATUS"
82
- # Update existing sub (same id, change status)
83
- run_plain sub 1 2 "Codebase scan" completed >/dev/null
84
- S2_STATUS=$(jq -r '.phases[1].subs[1].status' "$STATE_FILE")
85
- [ "$S2_STATUS" = "completed" ] && pass "existing sub updated in place (no duplicate)" || fail "in-place update failed"
86
- SUBS=$(jq '.phases[1].subs | length' "$STATE_FILE")
87
- [ "$SUBS" = "2" ] && pass "still 2 subs (no duplication)" || fail "duplicate created (got $SUBS)"
88
-
89
- echo ""
90
- echo "→ 7. render output contains the title and box borders"
91
- OUT=$(run_plain render)
92
- echo "$OUT" | grep -q "Pipeline: TASK-1" && pass "render shows task title" || fail "title missing"
93
- echo "$OUT" | grep -q '╭─' && pass "top border present" || fail "top border missing"
94
- echo "$OUT" | grep -q '╰──' && pass "bottom border present" || fail "bottom border missing"
95
-
96
- echo ""
97
- echo "→ 8. plain mode strips ANSI"
98
- if echo "$OUT" | grep -q $'\033\['; then
99
- fail "ANSI escapes leaked into TERM=dumb output"
100
- else
101
- pass "no ANSI under TERM=dumb"
102
- fi
103
-
104
- echo ""
105
- echo "→ 9. unknown action → exit 64"
106
- RC=$(run_plain_status flarfle)
107
- [ "$RC" = "64" ] && pass "unknown action rejected" || fail "unknown action got rc=$RC"
108
-
109
- echo ""
110
- echo "→ 10. update stamps started_at on first in_progress (v5.4.0)"
111
- run_plain init BAR-1 >/dev/null
112
- run_plain add 0 "Init" >/dev/null
113
- run_plain update 0 in_progress >/dev/null
114
- STATE_B1="$HOME/.claude/logs/multi-agent/BAR-1/tracker-state.json"
115
- STARTED=$(jq -r '.phases[0].started_at // ""' "$STATE_B1")
116
- [ -n "$STARTED" ] && pass "started_at stamped: $STARTED" || fail "started_at not stamped"
117
-
118
- echo ""
119
- echo "→ 11. update stamps completed_at on terminal status (completed/failed/skipped)"
120
- run_plain update 0 completed >/dev/null
121
- COMPLETED=$(jq -r '.phases[0].completed_at // ""' "$STATE_B1")
122
- [ -n "$COMPLETED" ] && pass "completed status stamps completed_at" || fail "completed did not stamp"
123
-
124
- run_plain add 1 "FailPhase" >/dev/null
125
- run_plain update 1 in_progress >/dev/null
126
- run_plain update 1 failed >/dev/null
127
- [ "$(jq -r '.phases[1].completed_at // ""' "$STATE_B1")" != "" ] && pass "failed stamps completed_at" || fail "failed did not stamp"
128
-
129
- run_plain add 2 "SkipPhase" >/dev/null
130
- run_plain update 2 skipped >/dev/null
131
- [ "$(jq -r '.phases[2].completed_at // ""' "$STATE_B1")" != "" ] && pass "skipped stamps completed_at" || fail "skipped did not stamp"
132
-
133
- echo ""
134
- echo "→ 12. started_at idempotent on repeat in_progress (v5.4.0)"
135
- run_plain init BAR-2 >/dev/null
136
- run_plain add 0 "X" >/dev/null
137
- run_plain update 0 in_progress >/dev/null
138
- STATE_B2="$HOME/.claude/logs/multi-agent/BAR-2/tracker-state.json"
139
- FIRST_TS=$(jq -r '.phases[0].started_at' "$STATE_B2")
140
- sleep 1
141
- run_plain update 0 in_progress >/dev/null
142
- SECOND_TS=$(jq -r '.phases[0].started_at' "$STATE_B2")
143
- [ "$FIRST_TS" = "$SECOND_TS" ] && pass "started_at preserved on repeat update" || fail "started_at changed: $FIRST_TS → $SECOND_TS"
144
-
145
- echo ""
146
- echo "→ 13. render draws 16-char progress bar + elapsed labels (v5.4.0)"
147
- run_plain init BAR-3 >/dev/null
148
- for p in 0:Init 1:Dev 2:Commit; do
149
- run_plain add "${p%%:*}" "${p#*:}" >/dev/null
150
- done
151
- STATE_B3="$HOME/.claude/logs/multi-agent/BAR-3/tracker-state.json"
152
- NOW_S=$(date +%s)
153
- mk_ts() { date -u -r "$1" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d "@$1" +"%Y-%m-%dT%H:%M:%SZ"; }
154
- jq \
155
- --arg s0 "$(mk_ts $((NOW_S - 300)))" --arg e0 "$(mk_ts $((NOW_S - 295)))" \
156
- --arg s1 "$(mk_ts $((NOW_S - 280)))" --arg e1 "$(mk_ts $((NOW_S - 120)))" \
157
- --arg s2 "$(mk_ts $((NOW_S - 60)))" --arg e2 "$(mk_ts $((NOW_S - 58)))" \
158
- '.phases |= map(
159
- if .id=="0" then .status="completed"|.started_at=$s0|.completed_at=$e0
160
- elif .id=="1" then .status="completed"|.started_at=$s1|.completed_at=$e1
161
- elif .id=="2" then .status="completed"|.started_at=$s2|.completed_at=$e2
162
- else . end)' "$STATE_B3" > "$STATE_B3.new" && mv "$STATE_B3.new" "$STATE_B3"
163
- OUT_BAR=$(run_plain render)
164
- echo "$OUT_BAR" | grep -q "5s" && pass "elapsed formatted as '5s'" || fail "'5s' missing in render"
165
- echo "$OUT_BAR" | grep -qE "2m 40s|160s" && pass "elapsed formatted as '2m 40s' (160s)" || fail "2m-range elapsed missing"
166
- # v8.1.0: progress bar removed; elapsed label is the only timing visualization.
167
-
168
- echo ""
169
- echo "→ 14. graceful fallback on old state without timestamps (v5.4.0)"
170
- mkdir -p "$HOME/.claude/logs/multi-agent/OLD-SCHEMA"
171
- cat > "$HOME/.claude/logs/multi-agent/OLD-SCHEMA/tracker-state.json" <<'JSON'
172
- {"task_id":"OLD-SCHEMA","started_at":"","phases":[
173
- {"id":"0","name":"Init","status":"completed","subs":[]},
174
- {"id":"1","name":"Dev","status":"in_progress","subs":[]}
175
- ]}
176
- JSON
177
- echo "$HOME/.claude/logs/multi-agent/OLD-SCHEMA/tracker-state.json" > "$HOME/.claude/logs/multi-agent/.tracker-current"
178
- OUT_OLD=$(TERM=dumb "$SCRIPT" render 2>&1)
179
- OLD_RC=$?
180
- [ "$OLD_RC" -eq 0 ] && pass "render exits 0 on old-schema state" || fail "render exit=$OLD_RC on old state"
181
- echo "$OUT_OLD" | grep -q "Phase 0" && pass "renders Phase 0 from old state" || fail "Phase 0 missing"
182
- # v8.1.0: progress bar removed entirely; old-schema state should still render text.
183
-
184
- echo ""
185
- echo "→ 15. tokens action accumulates per phase (v5.5.0)"
186
- run_plain init TOK-1 >/dev/null
187
- run_plain add 0 "Init" >/dev/null
188
- run_plain tokens 0 100 50 >/dev/null
189
- run_plain tokens 0 200 75 >/dev/null
190
- STATE_T1="$HOME/.claude/logs/multi-agent/TOK-1/tracker-state.json"
191
- TIN=$(jq -r '.phases[0].tokens_in // 0' "$STATE_T1")
192
- TOUT=$(jq -r '.phases[0].tokens_out // 0' "$STATE_T1")
193
- [ "$TIN" = "300" ] && pass "tokens_in accumulated (100+200=300)" || fail "tokens_in=$TIN expected 300"
194
- [ "$TOUT" = "125" ] && pass "tokens_out accumulated (50+75=125)" || fail "tokens_out=$TOUT expected 125"
195
-
196
- echo ""
197
- echo "→ 16. tokens validates non-negative integers (v5.5.0)"
198
- RC=$(run_plain_status tokens 0 foo 10)
199
- [ "$RC" = "64" ] && pass "non-integer rejected (exit 64)" || fail "non-integer got rc=$RC"
200
- RC=$(run_plain_status tokens 0 -5 10)
201
- [ "$RC" = "64" ] && pass "negative rejected (exit 64)" || fail "negative got rc=$RC"
202
-
203
- echo ""
204
- echo "→ 17. render shows token label per phase + total footer (v5.5.0)"
205
- run_plain init TOK-2 >/dev/null
206
- run_plain add 0 "Init" >/dev/null
207
- run_plain add 1 "Dev" >/dev/null
208
- run_plain tokens 0 180 50 >/dev/null # 230 → "230"
209
- run_plain tokens 1 14500 4200 >/dev/null # 18700 → "18.7k"
210
- OUT_TOK=$(run_plain render)
211
- echo "$OUT_TOK" | grep -qE "230 tok" && pass "raw count '230 tok' rendered" || fail "'230 tok' missing"
212
- echo "$OUT_TOK" | grep -qE "18\.7k tok" && pass "k-formatted '18.7k tok' rendered" || fail "'18.7k tok' missing"
213
- echo "$OUT_TOK" | grep -qE "Total.*18\.9k tok" && pass "Total footer '18.9k tok' rendered" || fail "Total footer missing (expected 18.9k)"
214
-
215
- echo ""
216
- echo "→ 18. pending phases without tokens render no label (v5.5.0)"
217
- run_plain init TOK-3 >/dev/null
218
- run_plain add 0 "Empty" >/dev/null
219
- OUT_NOP=$(run_plain render)
220
- # Should NOT contain " tok" string since no tokens recorded
221
- if echo "$OUT_NOP" | grep -qE "tok"; then
222
- fail "phase without tokens should not show 'tok' label"
223
- else
224
- pass "phase without tokens: no 'tok' label"
225
- fi
226
- # No "Total" footer when total is 0
227
- if echo "$OUT_NOP" | grep -q "Total"; then
228
- fail "no-token tracker should not show Total footer"
229
- else
230
- pass "no-token tracker: no Total footer"
231
- fi
232
-
233
- echo ""
234
- echo "→ 19. tokens action re-renders live (v5.6.0)"
235
- run_plain init TOK-LIVE >/dev/null
236
- run_plain add 0 "Init" >/dev/null
237
- run_plain update 0 in_progress >/dev/null
238
- # Without TRACKER_QUIET the tokens call MUST emit a render to stdout
239
- LIVE_OUT=$(run_plain tokens 0 500 250)
240
- echo "$LIVE_OUT" | grep -qE "750 tok" && pass "tokens action re-renders with new total (750 tok)" || fail "tokens did not re-render (out: $LIVE_OUT)"
241
- # With TRACKER_QUIET=1 the tokens call must NOT emit a render
242
- QUIET_OUT=$(TRACKER_QUIET=1 run_plain tokens 0 100 50)
243
- if [ -n "$QUIET_OUT" ]; then
244
- fail "TRACKER_QUIET=1 should suppress render (got: $QUIET_OUT)"
245
- else
246
- pass "TRACKER_QUIET=1 suppresses re-render"
247
- fi
248
- # State still updated under TRACKER_QUIET (accumulation must still happen)
249
- STATE_LIVE="$HOME/.claude/logs/multi-agent/TOK-LIVE/tracker-state.json"
250
- TIN_LIVE=$(jq -r '.phases[0].tokens_in // 0' "$STATE_LIVE")
251
- [ "$TIN_LIVE" = "600" ] && pass "TRACKER_QUIET still accumulates state (500+100=600)" || fail "quiet mode broke accumulation (got $TIN_LIVE)"
252
-
253
- echo ""
254
- echo "→ 20. now action: quiet, truncating current-action line"
255
- run_plain init NOW-1 >/dev/null
256
- run_plain add 3 "Dev" >/dev/null
257
- run_plain update 3 in_progress >/dev/null
258
- NOW_OUT=$(run_plain now 3 "editing TopBarView.swift")
259
- if [ -n "$NOW_OUT" ]; then
260
- fail "now must not render (got: $NOW_OUT)"
261
- else
262
- pass "now is quiet (no stdout)"
263
- fi
264
- STATE_NOW="$HOME/.claude/logs/multi-agent/NOW-1/tracker-state.json"
265
- NOW_VAL=$(jq -r '.phases[0].meta.Now' "$STATE_NOW")
266
- [ "$NOW_VAL" = "editing TopBarView.swift" ] && pass "now stored in meta.Now" || fail "meta.Now wrong: $NOW_VAL"
267
- run_plain now 3 "this is a deliberately very long current action description that exceeds sixty characters"
268
- NOW_LEN=$(jq -r '.phases[0].meta.Now | length' "$STATE_NOW")
269
- [ "$NOW_LEN" = "60" ] && pass "now truncates to 60 chars" || fail "expected 60 chars, got $NOW_LEN"
270
- RENDER_NOW=$(run_plain render)
271
- echo "$RENDER_NOW" | grep -q "Now:" && pass "render shows Now: line for active phase" || fail "render missing Now: line"
272
-
273
- echo ""
274
- echo "→ 21. add is idempotent (continuation contract)"
275
- run_plain tokens 3 1000 200 >/dev/null
276
- run_plain add 3 "DevRenamed" >/dev/null
277
- CNT=$(jq '.phases | length' "$STATE_NOW")
278
- [ "$CNT" = "1" ] && pass "duplicate add does not append" || fail "expected 1 phase, got $CNT"
279
- NAME3=$(jq -r '.phases[0].name' "$STATE_NOW")
280
- [ "$NAME3" = "Dev" ] && pass "duplicate add preserves original name" || fail "name overwritten: $NAME3"
281
- TIN3=$(jq -r '.phases[0].tokens_in' "$STATE_NOW")
282
- [ "$TIN3" = "1000" ] && pass "duplicate add preserves token history" || fail "tokens lost: $TIN3"
283
-
284
- echo ""
285
- echo "→ 22. cost action prices via cost-table.json"
286
- run_plain init COST-1 >/dev/null
287
- run_plain add 3 "Dev" >/dev/null
288
- run_plain update 3 in_progress >/dev/null
289
- TRACKER_QUIET=1 run_plain model 3 opus >/dev/null
290
- TRACKER_QUIET=1 run_plain tokens 3 100000 20000 >/dev/null
291
- # opus: 0.1*5 + 0.02*25 = 0.5 + 0.5 = 1.00
292
- COST3=$(run_plain cost 3)
293
- [ "$COST3" = "1" ] && pass "cost 3 = 1 (opus 100k in + 20k out)" || fail "expected 1, got $COST3"
294
- TRACKER_QUIET=1 run_plain tokens 3 0 0 50000 >/dev/null
295
- # + 50k cached * 0.5/M = 0.025 -> total 1.025 -> floored 1.02
296
- COST3C=$(run_plain cost 3)
297
- [ "$COST3C" = "1.02" ] && pass "cached tokens priced at cacheReadPerMtok (1.02)" || fail "expected 1.02, got $COST3C"
298
- COSTTOT=$(run_plain cost total)
299
- [ "$COSTTOT" = "1.02" ] && pass "cost total sums priceable phases" || fail "expected 1.02, got $COSTTOT"
300
- run_plain add 4 "Review" >/dev/null
301
- TRACKER_QUIET=1 run_plain tokens 4 5000 1000 >/dev/null
302
- COST4=$(run_plain cost 4)
303
- [ "$COST4" = "-" ] && pass "cost is '-' for a phase without a model" || fail "expected -, got $COST4"
304
- COSTBAD=$(run_plain cost 9)
305
- [ "$COSTBAD" = "-" ] && pass "cost is '-' for unknown phase id" || fail "expected -, got $COSTBAD"
306
-
307
- echo ""
308
- echo "→ 23. render shows per-tile USD and cached footer"
309
- RENDER_COST=$(run_plain render)
310
- echo "$RENDER_COST" | grep -q '~\$1.02' && pass "tile shows ~\$ estimate for priced phase" || fail "render missing tile USD"
311
- echo "$RENDER_COST" | grep -q "cached" && pass "footer shows cached tokens" || fail "render missing cached footer"
312
- echo "$RENDER_COST" | grep -qE "Total.*~\\\$" && pass "footer shows total USD" || fail "render missing total USD"
313
-
314
- echo ""
315
- echo "→ 24. render sorts phases by numeric id (continuation ordering)"
316
- run_plain init ORDER-1 >/dev/null
317
- for pp in "0:Init" "5:Test"; do run_plain add "${pp%%:*}" "${pp#*:}" >/dev/null; done
318
- for pp in "4:Review" "6:Commit"; do run_plain add "${pp%%:*}" "${pp#*:}" >/dev/null; done
319
- ORDER_OUT=$(run_plain render | grep -oE "Phase [0-9]+" | tr -d 'Phase ' | tr '\n' ',')
320
- [ "$ORDER_OUT" = "0,4,5,6," ] && pass "phases render sorted by id (0,4,5,6)" || fail "order wrong: $ORDER_OUT"
321
-
322
- echo ""
323
- echo "══ phase-tracker smoke: $PASS passed, $FAIL failed ══"
324
- [ "$FAIL" -eq 0 ]
@@ -1,241 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-phase0-bridge-contract.sh - verify the agent-state.json produced by
3
- # `multi-repo-pipeline.sh bridge` satisfies every field that Phase 0 (and the
4
- # downstream phases) read from it.
5
- #
6
- # Without this gate, a bridge schema bump could leave Phase 0 reading
7
- # undefined fields silently - the picker → Phase 0 handoff is the seam where
8
- # multi-repo state changes from the picker world to the pipeline runtime.
9
- #
10
- # Live multi-repo E2E (real branch, real PR) cannot run in CI, so this
11
- # contract test is the closest offline equivalent. It covers:
12
- # 1. Bridge output validates against agent-state.schema.json v2.1.0.
13
- # 2. Every required Phase 0 field is non-null.
14
- # 3. projects[] entries are well-formed (length, branch, baseBranch, identity).
15
- # 4. Single-repo input still produces a one-entry projects[] (or omits it
16
- # cleanly, matching phase-0-init.md "Single-repo selection" rule).
17
- # 5. Round-trip: re-running bridge on the same state.json is idempotent.
18
-
19
- set -uo pipefail
20
-
21
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
22
- REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
23
- LIB_DIR="$REPO_ROOT/pipeline/lib"
24
- SCHEMA="$REPO_ROOT/pipeline/schemas/agent-state.schema.json"
25
-
26
- PASS=0; FAIL=0
27
- ok() { echo " ✓ $1"; PASS=$((PASS+1)); }
28
- fail() { echo " ✗ $1" >&2; FAIL=$((FAIL+1)); }
29
-
30
- MRP="$LIB_DIR/multi-repo-pipeline.sh"
31
- [ -x "$MRP" ] || { fail "multi-repo-pipeline.sh not executable"; exit 2; }
32
- [ -f "$SCHEMA" ] || { fail "agent-state.schema.json missing"; exit 2; }
33
-
34
- # Need jsonschema for full validation - fall back to shape check if unavailable.
35
- HAS_JSONSCHEMA=0
36
- python3 -c "import jsonschema" 2>/dev/null && HAS_JSONSCHEMA=1
37
-
38
- TMP=$(mktemp -d)
39
- trap 'rm -rf "$TMP"; rm -rf $HOME/.claude/logs/multi-agent/repo-multi/CONTRACT-1; rm -rf $HOME/.claude/logs/multi-agent/repo-single/CONTRACT-2' EXIT
40
-
41
- # --- Multi-repo case --------------------------------------------------------
42
- cat > "$TMP/multi.json" <<EOF
43
- {
44
- "taskId":"CONTRACT-1",
45
- "shortId":1,
46
- "branch":"feature/CONTRACT-1",
47
- "baseBranch":"develop",
48
- "issueRef":{"kind":"jira","key":"CONTRACT-1","title":"phase-0 contract","owner":null,"repo":null},
49
- "primary":{"name":"repo-multi","cloneUrl":"git@example.com:o/repo-multi.git","provider":"github"},
50
- "extras":[
51
- {"name":"repo-extra","cloneUrl":"git@example.com:o/repo-extra.git","provider":"github"}
52
- ],
53
- "worktreeRoot":"$TMP/wt",
54
- "commitType":"feat",
55
- "identity":{"name":"Tester","email":"t@e.com"}
56
- }
57
- EOF
58
-
59
- bridged=$("$MRP" bridge "$TMP/multi.json" 2>/dev/null || echo "")
60
- if [ -z "$bridged" ] || [ ! -f "$bridged" ]; then
61
- fail "bridge produced no output for multi-repo input"
62
- echo "══ aborted ══"; exit 1
63
- fi
64
-
65
- echo "→ 1. Bridge writes agent-state.json at the canonical logs path"
66
- expected_dir="$HOME/.claude/logs/multi-agent/repo-multi/CONTRACT-1"
67
- if [ "$(dirname "$bridged")" = "$expected_dir" ]; then
68
- ok "writes to ~/.claude/logs/multi-agent/{primary}/{taskId}/agent-state.json"
69
- else
70
- fail "wrong path: got $bridged, expected $expected_dir/agent-state.json"
71
- fi
72
-
73
- echo "→ 2. Bridge output validates against agent-state.schema.json v2.1.0"
74
- if [ $HAS_JSONSCHEMA -eq 1 ]; then
75
- out=$(python3 -c '
76
- import json, sys
77
- from jsonschema import Draft202012Validator
78
- schema = json.load(open(sys.argv[1]))
79
- data = json.load(open(sys.argv[2]))
80
- errs = list(Draft202012Validator(schema).iter_errors(data))
81
- if errs:
82
- for e in errs: print("ERR " + "/".join(str(p) for p in e.absolute_path) + ": " + e.message)
83
- sys.exit(1)
84
- print("VALID")
85
- ' "$SCHEMA" "$bridged" 2>&1)
86
- if printf '%s' "$out" | grep -q "^VALID"; then
87
- ok "schema validates without errors"
88
- else
89
- fail "schema validation failed: $out"
90
- fi
91
- else
92
- echo " (skipping - jsonschema not installed)"
93
- fi
94
-
95
- echo "→ 3. Phase 0 required-field contract"
96
- phase0_check=$(python3 -c '
97
- import json, sys
98
- d = json.load(open(sys.argv[1]))
99
- need = ["schemaVersion","taskId","shortId","project","projectRoot","worktreePath",
100
- "branch","baseBranch","remoteType","currentPhase","status","startedAt",
101
- "phases","identity","projects"]
102
- miss = [k for k in need if d.get(k) in (None, "")]
103
- if miss:
104
- print("MISS:" + ",".join(miss))
105
- sys.exit(1)
106
- # projects[] must be at least 1
107
- if not isinstance(d["projects"], list) or len(d["projects"]) < 1:
108
- print("PROJECTS_BAD")
109
- sys.exit(1)
110
- # Every project must carry the per-repo Phase 0 contract
111
- for p in d["projects"]:
112
- pneed = ["project","projectRoot","branch","baseBranch","identity"]
113
- pmiss = [k for k in pneed if not p.get(k)]
114
- if pmiss:
115
- print("PROJECT_MISS:" + p.get("project","?") + ":" + ",".join(pmiss))
116
- sys.exit(1)
117
- print("OK")
118
- ' "$bridged" 2>&1)
119
- if [ "$phase0_check" = "OK" ]; then
120
- ok "every Phase 0 required field is non-null + projects[] entries well-formed"
121
- else
122
- fail "Phase 0 contract: $phase0_check"
123
- fi
124
-
125
- echo "→ 4. projects[] cardinality matches input (primary + extras)"
126
- count=$(python3 -c 'import json,sys; print(len(json.load(open(sys.argv[1]))["projects"]))' "$bridged")
127
- if [ "$count" = "2" ]; then
128
- ok "primary + 1 extra → projects[].length = 2"
129
- else
130
- fail "projects[].length $count != 2"
131
- fi
132
-
133
- echo "→ 5. Top-level scalars mirror primary (back-compat)"
134
- mirror_check=$(python3 -c '
135
- import json, sys
136
- d = json.load(open(sys.argv[1]))
137
- primary = d["projects"][0]
138
- if d["project"] != primary["project"]: print("PROJECT"); sys.exit(1)
139
- if d["projectRoot"] != primary["projectRoot"]: print("ROOT"); sys.exit(1)
140
- if d["branch"] != primary["branch"]: print("BRANCH"); sys.exit(1)
141
- print("OK")
142
- ' "$bridged" 2>&1)
143
- if [ "$mirror_check" = "OK" ]; then
144
- ok "top-level project/projectRoot/branch mirror projects[0]"
145
- else
146
- fail "scalar mirror broken: $mirror_check"
147
- fi
148
-
149
- echo "→ 6. Identity propagated to every project"
150
- identity_check=$(python3 -c '
151
- import json, sys
152
- d = json.load(open(sys.argv[1]))
153
- top = d["identity"]
154
- for p in d["projects"]:
155
- if p["identity"] != top:
156
- print("DRIFT:" + p["project"]); sys.exit(1)
157
- print("OK")
158
- ' "$bridged")
159
- if [ "$identity_check" = "OK" ]; then
160
- ok "every project carries the same identity (no drift)"
161
- else
162
- fail "identity drift: $identity_check"
163
- fi
164
-
165
- echo "→ 7. Bridge is idempotent (re-run yields same content)"
166
- sha1=$(shasum -a 256 "$bridged" | awk '{print $1}')
167
- "$MRP" bridge "$TMP/multi.json" >/dev/null
168
- sha2=$(shasum -a 256 "$bridged" | awk '{print $1}')
169
- # startedAt timestamp will differ between runs (it's `now`), so we mask it
170
- masked1=$(python3 -c '
171
- import json, sys
172
- d = json.load(open(sys.argv[1]))
173
- d["startedAt"] = "MASKED"
174
- print(json.dumps(d, sort_keys=True))
175
- ' "$bridged" | shasum -a 256 | awk '{print $1}')
176
- "$MRP" bridge "$TMP/multi.json" >/dev/null
177
- masked2=$(python3 -c '
178
- import json, sys
179
- d = json.load(open(sys.argv[1]))
180
- d["startedAt"] = "MASKED"
181
- print(json.dumps(d, sort_keys=True))
182
- ' "$bridged" | shasum -a 256 | awk '{print $1}')
183
- if [ "$masked1" = "$masked2" ]; then
184
- ok "idempotent (modulo startedAt) on re-run"
185
- else
186
- fail "bridge content drifts between runs"
187
- fi
188
-
189
- # --- Single-repo case -------------------------------------------------------
190
- cat > "$TMP/single.json" <<EOF
191
- {
192
- "taskId":"CONTRACT-2",
193
- "shortId":2,
194
- "branch":"feature/CONTRACT-2",
195
- "baseBranch":"main",
196
- "issueRef":{"kind":"github","key":"99","title":"single","owner":"o","repo":"repo-single"},
197
- "primary":{"name":"repo-single","cloneUrl":"git@example.com:o/repo-single.git","provider":"github"},
198
- "extras":[],
199
- "worktreeRoot":"$TMP/wt-single",
200
- "commitType":"fix",
201
- "identity":{"name":"Tester","email":"t@e.com"}
202
- }
203
- EOF
204
-
205
- bridged_single=$("$MRP" bridge "$TMP/single.json" 2>/dev/null)
206
- [ -f "$bridged_single" ] || { fail "single-repo bridge produced no output"; exit 1; }
207
-
208
- echo "→ 8. Single-repo input → projects[].length = 1"
209
- count_s=$(python3 -c 'import json,sys; print(len(json.load(open(sys.argv[1]))["projects"]))' "$bridged_single")
210
- if [ "$count_s" = "1" ]; then
211
- ok "single-repo bridges into a one-entry projects[]"
212
- else
213
- fail "single projects[].length $count_s != 1"
214
- fi
215
-
216
- echo "→ 9. Single-repo → githubIssue populated, jiraId null"
217
- gh_check=$(python3 -c '
218
- import json, sys
219
- d = json.load(open(sys.argv[1]))
220
- if d.get("jiraId") is not None: print("JIRA_NOT_NULL"); sys.exit(1)
221
- ghi = d.get("githubIssue") or {}
222
- if ghi.get("owner") != "o" or ghi.get("repo") != "repo-single" or ghi.get("number") != 99:
223
- print("GH_WRONG:" + str(ghi)); sys.exit(1)
224
- print("OK")
225
- ' "$bridged_single")
226
- if [ "$gh_check" = "OK" ]; then
227
- ok "GitHub-issue task: githubIssue object populated, jiraId stays null"
228
- else
229
- fail "github issue propagation: $gh_check"
230
- fi
231
-
232
- # --- Summary ----------------------------------------------------------------
233
- echo ""
234
- TOTAL=$((PASS+FAIL))
235
- if [ $FAIL -eq 0 ]; then
236
- echo "══ phase0-bridge-contract smoke: $PASS passed, 0 failed ══"
237
- exit 0
238
- else
239
- echo "══ phase0-bridge-contract smoke: $PASS passed, $FAIL failed (of $TOTAL) ══" >&2
240
- exit 1
241
- fi
@@ -1,45 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-phase4-gates.sh - contract for the two Phase 4 diff-risk consumers:
3
- # test-integrity-gate.mjs (A2) - test_lines_removed -> blocking finding
4
- # review-scope.mjs (A3) - trivial diff -> single reviewer, else full
5
- # Both are pure functions of the diff-risk JSON (fed on stdin). Deterministic,
6
- # no git, no LLM. Fail-safe: bad input -> integrity emits 0 findings, scope -> full.
7
- #
8
- # Exit 0 = all pass, 1 = any failure.
9
-
10
- set -uo pipefail
11
- ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
12
- INTEG="$ROOT/pipeline/scripts/test-integrity-gate.mjs"
13
- SCOPE="$ROOT/pipeline/scripts/review-scope.mjs"
14
- command -v node >/dev/null 2>&1 || { echo "error: node required" >&2; exit 127; }
15
- [ -f "$INTEG" ] && [ -f "$SCOPE" ] || { echo "FAIL: gate scripts missing" >&2; exit 1; }
16
-
17
- PASS=0; FAIL=0
18
- pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
19
- fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
20
-
21
- echo "→ A2 test-integrity-gate"
22
- OUT=$(echo '{"files":[{"file":"FooTests.swift","signals":[{"name":"test_lines_removed","weight":3,"value":9}]},{"file":"Foo.swift","signals":[{"name":"loc_changed"}]}]}' | node "$INTEG")
23
- echo "$OUT" | grep -q '"count":1' && pass "flags a shrinking test file (count=1)" || fail "did not flag test shrink: $OUT"
24
- echo "$OUT" | grep -q '"tag":"test_integrity"' && pass "finding tagged test_integrity + blocking" || fail "missing tag/severity"
25
- echo "$OUT" | grep -q '"severity":"blocking"' && pass "severity blocking" || fail "not blocking"
26
- OUT2=$(echo '{"files":[{"file":"Foo.swift","signals":[{"name":"loc_changed"}]}]}' | node "$INTEG")
27
- echo "$OUT2" | grep -q '"count":0' && pass "clean diff -> 0 findings" || fail "false positive on clean diff"
28
- OUT3=$(echo 'not-json' | node "$INTEG")
29
- echo "$OUT3" | grep -q '"count":0' && pass "bad input -> fail-open 0 findings" || fail "bad input not handled"
30
-
31
- echo "→ A3 review-scope"
32
- S1=$(echo '{"totals":{"loc_added":5,"loc_removed":3,"max_score":1.2,"files":1},"files":[{"file":"README.md","signals":[{"name":"loc_changed"}]}]}' | node "$SCOPE")
33
- echo "$S1" | grep -q '"scope":"single"' && pass "trivial diff -> single reviewer" || fail "trivial not single: $S1"
34
- S2=$(echo '{"totals":{"loc_added":200,"loc_removed":40,"max_score":2,"files":6},"files":[]}' | node "$SCOPE")
35
- echo "$S2" | grep -q '"scope":"full"' && pass "large churn -> full" || fail "large churn not full: $S2"
36
- S3=$(echo '{"totals":{"loc_added":5,"loc_removed":1,"max_score":3,"files":1},"files":[{"file":"Auth.swift","signals":[{"name":"security_path","weight":3}]}]}' | node "$SCOPE")
37
- echo "$S3" | grep -q '"scope":"full"' && pass "high-stakes signal -> full (even if small)" || fail "security not full: $S3"
38
- S4=$(echo '{"totals":{"loc_added":3,"loc_removed":1,"max_score":3,"files":1},"files":[{"file":"X","signals":[{"name":"test_lines_removed"}]}]}' | node "$SCOPE")
39
- echo "$S4" | grep -q '"scope":"full"' && pass "test_lines_removed forces full review" || fail "test shrink not full: $S4"
40
- S5=$(echo 'garbage' | node "$SCOPE")
41
- echo "$S5" | grep -q '"scope":"full"' && pass "bad input -> fail-safe full" || fail "bad input not fail-safe: $S5"
42
-
43
- echo ""
44
- echo "══ phase4-gates smoke: $PASS passed, $FAIL failed ══"
45
- [ "$FAIL" -eq 0 ]