@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,194 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # smoke-no-mcp-in-dev-phases.sh
4
- #
5
- # Gate: pipeline run telemetry must not contain MCP calls outside Phase 1
6
- # (Analysis). Per Locked decision 30, MCP usage is permitted only during
7
- # Phase 1; any entry in state.telemetry.mcpCalls[] with phase >= 2 is a
8
- # violation.
9
- #
10
- # Two modes.
11
- #
12
- # CHECK MODE (--state-file <path>): audit that one state file.
13
- # - telemetry.mcpCalls field absent -> PASS, exit 0 (older run, no telemetry)
14
- # - All entries phase < 2 -> PASS, exit 0
15
- # - One or more violations -> FAIL summary on stdout, exit 1
16
- # - Missing / corrupt file -> exit 2
17
- #
18
- # SELF-TEST MODE (no arguments): drive check mode over fixtures that cover
19
- # every verdict, then audit the live state file if one happens to exist.
20
- #
21
- # Why: with no --state-file this script used to print "no run to check" and
22
- # exit 0. On a clean clone, on CI, and on any machine that had not just
23
- # finished a pipeline run - which is nearly always - the gate for Locked
24
- # decision 30 therefore asserted nothing while reporting success. A gate whose
25
- # green tick means "I had nothing to look at" is indistinguishable from one
26
- # that passed, and this suite had three of those.
27
- #
28
- # stdout: human-readable summary only. stderr: empty in normal operation.
29
-
30
- set -euo pipefail
31
-
32
- STATE_FILE="${HOME}/.claude/state/run/latest.json"
33
- STATE_FILE_EXPLICIT=0
34
-
35
- while [ "$#" -gt 0 ]; do
36
- case "$1" in
37
- --state-file)
38
- shift
39
- if [ "$#" -eq 0 ]; then
40
- echo "FAIL: --state-file requires a path argument"
41
- exit 2
42
- fi
43
- STATE_FILE="$1"
44
- STATE_FILE_EXPLICIT=1
45
- shift
46
- ;;
47
- --state-file=*)
48
- STATE_FILE="${1#--state-file=}"
49
- STATE_FILE_EXPLICIT=1
50
- shift
51
- ;;
52
- -h|--help)
53
- echo "Usage: $0 [--state-file <path>]"
54
- echo ""
55
- echo "No arguments: run the fixture self-test suite."
56
- echo "Default state file when checking live: ~/.claude/state/run/latest.json"
57
- exit 0
58
- ;;
59
- *)
60
- echo "FAIL: unknown argument: $1"
61
- exit 2
62
- ;;
63
- esac
64
- done
65
-
66
- if ! command -v jq >/dev/null 2>&1; then
67
- echo "FAIL: jq is required but not installed"
68
- exit 2
69
- fi
70
-
71
- # ── self-test mode ─────────────────────────────────────────────────────────
72
- if [ "$STATE_FILE_EXPLICIT" -eq 0 ]; then
73
- SELF="$0"
74
- TMP=$(mktemp -d)
75
- trap 'rm -rf "$TMP"' EXIT
76
- PASS=0
77
- FAIL=0
78
-
79
- expect() {
80
- local name="$1" want="$2" file="$3"
81
- local got=0
82
- bash "$SELF" --state-file "$file" >/dev/null 2>&1 || got=$?
83
- if [ "$got" -eq "$want" ]; then
84
- PASS=$((PASS + 1)); echo " βœ“ $name (exit $got)"
85
- else
86
- FAIL=$((FAIL + 1)); echo " βœ— $name (exit $got, expected $want)"
87
- fi
88
- }
89
-
90
- echo "β†’ Locked decision 30 gate, fixture self-test"
91
-
92
- printf '%s' '{"telemetry":{"mcpCalls":[{"tool":"mcp__figma__get_design_context","phase":1}]}}' \
93
- > "$TMP/phase1-only.json"
94
- expect "MCP call in Phase 1 passes" 0 "$TMP/phase1-only.json"
95
-
96
- printf '%s' '{"telemetry":{"mcpCalls":[{"tool":"mcp__figma__get_design_context","phase":3}]}}' \
97
- > "$TMP/phase3.json"
98
- expect "MCP call in Phase 3 is a violation" 1 "$TMP/phase3.json"
99
-
100
- printf '%s' '{"telemetry":{"mcpCalls":[{"tool":"a","phase":1},{"tool":"b","phase":2}]}}' \
101
- > "$TMP/mixed.json"
102
- expect "one violation among clean entries still fails" 1 "$TMP/mixed.json"
103
-
104
- printf '%s' '{"telemetry":{"mcpCalls":[]}}' > "$TMP/empty.json"
105
- expect "empty call list passes" 0 "$TMP/empty.json"
106
-
107
- printf '%s' '{"telemetry":{}}' > "$TMP/no-field.json"
108
- expect "absent telemetry.mcpCalls passes (older state format)" 0 "$TMP/no-field.json"
109
-
110
- printf '%s' '{"telemetry":{"mcpCalls":[{"tool":"a"}]}}' > "$TMP/no-phase.json"
111
- expect "entry with no phase defaults to Phase 0 and passes" 0 "$TMP/no-phase.json"
112
-
113
- printf '%s' 'not json at all' > "$TMP/corrupt.json"
114
- expect "corrupt state is an error, never a silent pass" 2 "$TMP/corrupt.json"
115
-
116
- expect "missing state file is an error in check mode" 2 "$TMP/does-not-exist.json"
117
-
118
- # The live run, when there is one. Absence is not a failure here - the
119
- # fixtures above already proved the gate works.
120
- if [ -f "$STATE_FILE" ]; then
121
- echo "β†’ live state file"
122
- if bash "$SELF" --state-file "$STATE_FILE"; then
123
- PASS=$((PASS + 1)); echo " βœ“ live run clean"
124
- else
125
- FAIL=$((FAIL + 1)); echo " βœ— live run has violations"
126
- fi
127
- else
128
- echo "β†’ live state file: none at $STATE_FILE (fixtures already exercised every path)"
129
- fi
130
-
131
- echo ""
132
- echo "══ smoke-no-mcp-in-dev-phases: $PASS passed, $FAIL failed ══"
133
- [ "$FAIL" -gt 0 ] && exit 1 || exit 0
134
- fi
135
-
136
- # ── check mode ─────────────────────────────────────────────────────────────
137
- if [ ! -f "$STATE_FILE" ]; then
138
- echo "FAIL: state file not found at $STATE_FILE"
139
- exit 2
140
- fi
141
-
142
- # Validate JSON parse first; corrupt state cannot be silently passed.
143
- if ! jq empty "$STATE_FILE" >/dev/null 2>&1; then
144
- echo "FAIL: state file is not valid JSON: $STATE_FILE"
145
- exit 2
146
- fi
147
-
148
- # If telemetry.mcpCalls is absent or not an array, this is an older state
149
- # format that did not record MCP usage. PASS by contract.
150
- HAS_FIELD=$(jq -r '
151
- if (.telemetry // empty) | type == "object"
152
- then
153
- if (.telemetry.mcpCalls // empty) | type == "array" then "yes" else "no" end
154
- else "no" end
155
- ' "$STATE_FILE")
156
-
157
- if [ "$HAS_FIELD" != "yes" ]; then
158
- echo "PASS: telemetry.mcpCalls field absent - no MCP usage recorded"
159
- exit 0
160
- fi
161
-
162
- # Count total entries and violations (phase >= 2).
163
- TOTAL=$(jq -r '.telemetry.mcpCalls | length' "$STATE_FILE")
164
- VIOLATIONS=$(jq -r '
165
- .telemetry.mcpCalls
166
- | map(select((.phase // 0) >= 2))
167
- | length
168
- ' "$STATE_FILE")
169
-
170
- if [ "$VIOLATIONS" -eq 0 ]; then
171
- if [ "$TOTAL" -eq 0 ]; then
172
- echo "PASS: no MCP calls outside Phase 1 (Analysis) - 0 entries"
173
- else
174
- echo "PASS: no MCP calls outside Phase 1 (Analysis) - ${TOTAL} entries, all in Phase 1"
175
- fi
176
- exit 0
177
- fi
178
-
179
- # Emit FAIL summary with up to first 5 offenders.
180
- echo "FAIL: ${VIOLATIONS} MCP call(s) outside Phase 1 (Analysis) - Locked decision 30 violation"
181
- echo ""
182
- echo "First $([ "$VIOLATIONS" -lt 5 ] && echo "$VIOLATIONS" || echo 5) violation(s):"
183
-
184
- jq -r '
185
- .telemetry.mcpCalls
186
- | map(select((.phase // 0) >= 2))
187
- | .[0:5]
188
- | .[]
189
- | " FAIL: MCP call \(.tool // "<unknown>") in Phase \(.phase) at \(.timestamp // "<no timestamp>") - Locked decision 30 violation"
190
- ' "$STATE_FILE"
191
-
192
- echo ""
193
- echo "Total violations: ${VIOLATIONS} of ${TOTAL} entries"
194
- exit 1
@@ -1,85 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # smoke-no-token-prompt.sh
4
- #
5
- # Asserts the pipeline never asks the user to type a token in chat. Tokens
6
- # are resolved via credential-store.sh; missing/expired tokens trigger the
7
- # Setup Wizard, not an inline prompt. See refs/keychain.md Rule 1.
8
- #
9
- # Greps shell scripts, markdown adapters, and orchestrator commands for
10
- # hardcoded prompt patterns. Allowlist for legitimate setup wizard prompts.
11
-
12
- set -euo pipefail
13
-
14
- SELF_DIR=$(cd "$(dirname "$0")" && pwd)
15
- PIPELINE_ROOT=$(cd "$SELF_DIR/.." && pwd)
16
-
17
- cd "$PIPELINE_ROOT"
18
-
19
- FAIL=0
20
-
21
- # Pattern: anything that looks like the script asking for a token interactively.
22
- # Acceptable: setup wizard explicitly tagged with `# allow: token-prompt`.
23
- PATTERNS=(
24
- "read -rp.*[Tt]oken"
25
- "read -rp.*[Aa]pi.[Kk]ey"
26
- "read -rp.*[Pp]aste.*token"
27
- "read -rp.*[Ee]nter.*token"
28
- 'echo "Enter your.*[Tt]oken'
29
- 'echo "Paste.*[Tt]oken'
30
- "AskUserQuestion.*[Tt]oken"
31
- )
32
-
33
- # Files in scope: shell scripts + adapter markdown.
34
- TARGET_GLOB=(
35
- "scripts/*.sh"
36
- "scripts/*.mjs"
37
- "multi-agent-refs/channels/*.md"
38
- "multi-agent-refs/phases/*.md"
39
- "commands/multi-agent/dev/SKILL.md"
40
- "commands/multi-agent/dev-autopilot/SKILL.md"
41
- "commands/multi-agent/autopilot/SKILL.md"
42
- )
43
-
44
- # Build a single grep invocation: all patterns via repeated -e flags,
45
- # all target globs expanded once. Drops 7x7=49 grep subprocesses down to 1.
46
- GREP_PATTERN_ARGS=()
47
- for pattern in "${PATTERNS[@]}"; do
48
- GREP_PATTERN_ARGS+=(-e "$pattern")
49
- done
50
-
51
- # Expand all target globs to actual paths once. Globs that don't match are
52
- # silently dropped so the single grep call sees only existing files.
53
- TARGET_PATHS=()
54
- for glob in "${TARGET_GLOB[@]}"; do
55
- for path in pipeline/$glob; do
56
- [ -e "$path" ] && TARGET_PATHS+=("$path")
57
- done
58
- done
59
-
60
- if [ "${#TARGET_PATHS[@]}" -gt 0 ]; then
61
- while IFS= read -r match; do
62
- [ -z "$match" ] && continue
63
- file="${match%%:*}"
64
- # Allowlist: setup.md and setup-related files explicitly handle token entry
65
- # via the inline Token Save Flow (clipboard-based, not chat).
66
- case "$file" in
67
- */setup.md|*setup-token-flow*|*credential-store*) continue ;;
68
- esac
69
- # Allowlist: explicit `# allow: token-prompt` annotation on the line.
70
- case "$match" in
71
- *'# allow: token-prompt'*) continue ;;
72
- esac
73
- echo "FAIL [smoke-no-token-prompt]: token prompt found in $file"
74
- echo " $match"
75
- FAIL=$((FAIL + 1))
76
- done < <(grep -rEn "${GREP_PATTERN_ARGS[@]}" "${TARGET_PATHS[@]}" 2>/dev/null || true)
77
- fi
78
-
79
- if [ "$FAIL" -gt 0 ]; then
80
- echo ""
81
- echo "smoke-no-token-prompt: $FAIL violation(s). Tokens must be resolved via credential-store.sh; see refs/keychain.md Rule 1."
82
- exit 1
83
- fi
84
-
85
- echo "smoke-no-token-prompt: ok"
@@ -1,103 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # smoke-own-punctuation.sh - the punctuation rule applies to this repo's own
4
- # shipped files, not only to the documents it generates.
5
- #
6
- # validate-analysis-doc.mjs has enforced the locked policy (no em-dash, en-dash,
7
- # ellipsis or section sign) on generated analysis documents for a long time,
8
- # and smoke-generate-issue.sh / smoke-review-readiness.sh enforce it on issue
9
- # and review payloads. Nothing applied it to the pipeline's own tree, so 77
10
- # shipped files carried 719 em-dashes, 10 en-dashes and 24 ellipses while the
11
- # rule was described as enforced.
12
- #
13
- # ALLOWED holds the files that carry these characters as data rather than
14
- # prose: the detector's own table and the grep patterns that look for them.
15
- # Stripping those would disable the very checks this rule depends on. Every
16
- # entry needs a reason, and the list is expected to stay this short.
17
- #
18
- # Exit codes: 0=clean, 1=violations
19
-
20
- set -euo pipefail
21
-
22
- REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
23
- cd "$REPO_ROOT"
24
-
25
- # file path -> why the characters are legitimately present
26
- ALLOWED_PATHS="
27
- pipeline/scripts/validate-analysis-doc.mjs|the detector's own table of banned characters
28
- pipeline/scripts/smoke-generate-issue.sh|greps for the banned characters
29
- pipeline/scripts/smoke-review-readiness.sh|greps for the banned characters
30
- pipeline/scripts/smoke-md2confluence.sh|dirty-input fixture plus the forbidden list
31
- pipeline/scripts/smoke-own-punctuation.sh|this file names the characters it bans
32
- pipeline/lib/md2confluence-v3.py|the converter's codepoint-to-ASCII mapping table
33
- "
34
-
35
- # The same characters written as escapes. prefs.schema.json carried 50 of them
36
- # in its descriptions and was invisible to a search for the literal character,
37
- # so the first version of this gate passed it. A rule that only sees one
38
- # encoding of what it bans is not enforcing the rule.
39
- ESCAPED='\\u2014|\\u2013|\\u2026|\\u00a7'
40
-
41
- is_allowed() {
42
- printf '%s\n' "$ALLOWED_PATHS" | grep -q "^$1|"
43
- }
44
-
45
- PASS=0
46
- FAIL=0
47
- pass() { PASS=$((PASS + 1)); echo " βœ“ $1"; }
48
- fail() { FAIL=$((FAIL + 1)); echo " βœ— $1"; }
49
-
50
- echo "β†’ 1. no em-dash, en-dash, ellipsis or section sign in shipped files"
51
-
52
- # CHANGELOG history is left as written; rewriting released notes to satisfy a
53
- # later style rule would falsify the record.
54
- VIOLATIONS=""
55
- while IFS= read -r f; do
56
- case "$f" in CHANGELOG*.md) continue ;; esac
57
- is_allowed "$f" && continue
58
- VIOLATIONS="${VIOLATIONS}${f}\n"
59
- done < <(git grep -lI -e $'β€”' -e $'–' -e $'…' -e $'Β§' -- . 2>/dev/null || true)
60
-
61
- if [ -z "$VIOLATIONS" ]; then
62
- pass "no banned punctuation outside the allowlist"
63
- else
64
- COUNT=$(printf '%b' "$VIOLATIONS" | grep -c . || true)
65
- fail "$COUNT file(s) carry banned punctuation"
66
- printf '%b' "$VIOLATIONS" | sed 's/^/ /'
67
- echo " fix: plain ASCII - or : or parentheses; 'section 4' rather than the sign"
68
- fi
69
-
70
- echo "β†’ 2. same characters written as unicode escapes"
71
- ESC_VIOLATIONS=""
72
- while IFS= read -r f; do
73
- case "$f" in CHANGELOG*.md) continue ;; esac
74
- is_allowed "$f" && continue
75
- ESC_VIOLATIONS="${ESC_VIOLATIONS}${f}\n"
76
- done < <(git grep -lI -E "$ESCAPED" -- . 2>/dev/null || true)
77
-
78
- if [ -z "$ESC_VIOLATIONS" ]; then
79
- pass "no escaped forms outside the allowlist"
80
- else
81
- COUNT=$(printf '%b' "$ESC_VIOLATIONS" | grep -c . || true)
82
- fail "$COUNT file(s) carry the banned characters as unicode escapes"
83
- printf '%b' "$ESC_VIOLATIONS" | sed 's/^/ /'
84
- fi
85
-
86
- echo "β†’ 3. every allowlist entry still exists and still needs the exemption"
87
- while IFS='|' read -r path reason; do
88
- [ -z "$path" ] && continue
89
- if [ ! -f "$path" ]; then
90
- fail "allowlisted path no longer exists: $path"
91
- elif ! grep -qI -e $'β€”' -e $'–' -e $'…' -e $'Β§' "$path" 2>/dev/null \
92
- && ! grep -qIE "$ESCAPED" "$path" 2>/dev/null; then
93
- # Either encoding justifies the entry: md2confluence-v3.py holds them as
94
- # escapes, the detector table holds them literally.
95
- fail "$path is allowlisted ($reason) but is now clean - drop it from the list"
96
- else
97
- pass "allowlist justified: ${path##*/} ($reason)"
98
- fi
99
- done < <(printf '%s\n' "$ALLOWED_PATHS" | grep '|')
100
-
101
- echo ""
102
- echo "══ smoke-own-punctuation: $PASS passed, $FAIL failed ══"
103
- [ "$FAIL" -gt 0 ] && exit 1 || exit 0
@@ -1,140 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # smoke-pack-contents.sh - guard what `npm publish` would actually ship.
4
- #
5
- # 1. Deny patterns: package.json files[] carries negations (`!...`) for
6
- # dev-only / personal-data files. The published tarball must contain
7
- # no file matching any of them. Patterns are read from package.json at
8
- # runtime (via node), so the smoke never goes stale when the deny list
9
- # changes.
10
- # 2. File-count band: the total packed file count is compared against the
11
- # committed fixture pipeline/scripts/fixtures/pack-expected-count.txt.
12
- # Drift beyond +-15 percent fails - that magnitude means the files[]
13
- # globs picked up (or dropped) a whole subtree, which deserves a human
14
- # look. Refresh the fixture after reviewing:
15
- # npm pack --dry-run --json 2>/dev/null \
16
- # | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s)[0].files.length))' \
17
- # > pipeline/scripts/fixtures/pack-expected-count.txt
18
- # 3. publint (optional): linted via `npx --no-install` so it only runs
19
- # when the package is already installed - never a network fetch.
20
- #
21
- # Exit codes: 0=pass, 1=violation, 2=environment broken
22
-
23
- set -uo pipefail
24
-
25
- REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
26
- FIXTURE="$REPO_ROOT/pipeline/scripts/fixtures/pack-expected-count.txt"
27
-
28
- command -v npm >/dev/null 2>&1 || { echo "FAIL: npm not on PATH"; exit 2; }
29
- command -v node >/dev/null 2>&1 || { echo "FAIL: node not on PATH"; exit 2; }
30
-
31
- PASS=0
32
- FAIL=0
33
- pass() { PASS=$((PASS + 1)); echo " βœ“ $1"; }
34
- fail() { FAIL=$((FAIL + 1)); echo " βœ— $1"; }
35
-
36
- cleanup() { [ -n "${TMP:-}" ] && rm -rf "$TMP"; }
37
- trap cleanup EXIT
38
- TMP=$(mktemp -d)
39
-
40
- # ──────────────────────────────────────────────────────────────────────────
41
- echo "β†’ 1. npm pack --dry-run --json"
42
- if ! (cd "$REPO_ROOT" && npm pack --dry-run --json 2>/dev/null > "$TMP/pack.json"); then
43
- echo "FAIL: npm pack --dry-run failed"
44
- exit 2
45
- fi
46
-
47
- # Deny check runs in node so the patterns come straight from package.json
48
- # files[] negations - never hardcoded here.
49
- cat > "$TMP/check.mjs" <<'MJS'
50
- import { readFileSync } from "node:fs";
51
-
52
- const [packJsonPath, pkgJsonPath] = process.argv.slice(2);
53
- const packed = JSON.parse(readFileSync(packJsonPath, "utf-8"))[0].files.map((f) => f.path);
54
- const files = JSON.parse(readFileSync(pkgJsonPath, "utf-8")).files || [];
55
- const denies = files.filter((p) => p.startsWith("!")).map((p) => p.slice(1));
56
-
57
- // Minimal glob -> regex: '**' spans path separators, '*' does not.
58
- const toRegex = (glob) => {
59
- const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
60
- const body = escaped
61
- .split("**")
62
- .map((part) => part.replace(/\*/g, "[^/]*"))
63
- .join(".*");
64
- return new RegExp("^" + body + "$");
65
- };
66
-
67
- const regexes = denies.map((d) => [d, toRegex(d)]);
68
- let bad = 0;
69
- for (const path of packed) {
70
- for (const [glob, re] of regexes) {
71
- if (re.test(path)) {
72
- console.log(`DENY ${path} (matches !${glob})`);
73
- bad++;
74
- }
75
- }
76
- }
77
- console.log(`DENYCOUNT ${bad}`);
78
- console.log(`PATTERNS ${denies.length}`);
79
- console.log(`COUNT ${packed.length}`);
80
- MJS
81
-
82
- CHECK=$(node "$TMP/check.mjs" "$TMP/pack.json" "$REPO_ROOT/package.json")
83
- COUNT=$(echo "$CHECK" | awk '/^COUNT /{print $2}')
84
- DENYCOUNT=$(echo "$CHECK" | awk '/^DENYCOUNT /{print $2}')
85
- PATTERNS=$(echo "$CHECK" | awk '/^PATTERNS /{print $2}')
86
-
87
- if [ -z "$COUNT" ] || [ -z "$DENYCOUNT" ]; then
88
- echo "FAIL: deny-pattern checker produced no result (node error?)"
89
- exit 2
90
- fi
91
-
92
- pass "pack dry-run produced $COUNT files ($PATTERNS deny patterns loaded from package.json)"
93
-
94
- # ──────────────────────────────────────────────────────────────────────────
95
- echo "β†’ 2. deny patterns (files[] negations must not ship)"
96
- if [ "$DENYCOUNT" -eq 0 ]; then
97
- pass "no packed file matches a files[] negation"
98
- else
99
- fail "$DENYCOUNT packed file(s) match dev-only/personal-data exclusions:"
100
- echo "$CHECK" | grep '^DENY ' | sed 's/^/ /'
101
- fi
102
-
103
- # ──────────────────────────────────────────────────────────────────────────
104
- echo "β†’ 3. file-count band vs fixture (+-15 percent)"
105
- if [ ! -f "$FIXTURE" ]; then
106
- fail "fixture missing: $FIXTURE - create it from the refresh command in this script's header"
107
- else
108
- EXPECTED=$(tr -cd '0-9' < "$FIXTURE")
109
- if [ -z "$EXPECTED" ] || [ "$EXPECTED" -eq 0 ]; then
110
- fail "fixture unreadable or zero: $FIXTURE"
111
- else
112
- DIFF=$((COUNT - EXPECTED))
113
- [ "$DIFF" -lt 0 ] && DIFF=$((-DIFF))
114
- LIMIT=$((EXPECTED * 15 / 100))
115
- if [ "$DIFF" -le "$LIMIT" ]; then
116
- pass "count $COUNT within +-15 percent of expected $EXPECTED (drift $DIFF, limit $LIMIT)"
117
- else
118
- fail "count $COUNT drifted beyond +-15 percent of expected $EXPECTED (drift $DIFF, limit $LIMIT)"
119
- echo " Review what the files[] globs now include, then refresh the fixture"
120
- echo " (refresh command is in this script's header comment)."
121
- fi
122
- fi
123
- fi
124
-
125
- # ──────────────────────────────────────────────────────────────────────────
126
- echo "β†’ 4. publint (optional, npx --no-install only)"
127
- if (cd "$REPO_ROOT" && npx --no-install publint --help >/dev/null 2>&1); then
128
- if OUT=$(cd "$REPO_ROOT" && npx --no-install publint 2>&1); then
129
- pass "publint: no errors"
130
- else
131
- fail "publint reported errors"
132
- echo "$OUT" | sed 's/^/ /'
133
- fi
134
- else
135
- echo " β†· SKIP: publint not installed (npx --no-install found no local package)"
136
- fi
137
-
138
- echo ""
139
- echo "══ smoke-pack-contents: $PASS passed, $FAIL failed ══"
140
- [ "$FAIL" -gt 0 ] && exit 1 || exit 0
@@ -1,128 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-pat-audit.sh - exercises audit-log.sh + audit-log-rotate.sh and
3
- # asserts the section18.2 contract:
4
- #
5
- # β€’ Every PAT lookup writes a structured event to audit.jsonl
6
- # β€’ Repo URLs are SHA-256 hashed (no plaintext)
7
- # β€’ Identity is logged by name (NOT anonymized - user's own debug)
8
- # β€’ Audit log is gitignored
9
- # β€’ Daily rotation script preserves history (gzipped) + drops >30d archives
10
-
11
- set -uo pipefail
12
-
13
- AUDIT_FILE="$HOME/.claude/logs/multi-agent/audit.jsonl"
14
- TMP_AUDIT="$(mktemp -d -t ma-audit-smoke-XXXX)"
15
- TMP_FILE="$TMP_AUDIT/audit.jsonl"
16
- trap 'rm -rf "$TMP_AUDIT"' EXIT
17
-
18
- PASS=0; FAIL=0
19
- pass() { PASS=$((PASS+1)); echo " βœ“ $1"; }
20
- fail() { FAIL=$((FAIL+1)); echo " βœ— $1"; }
21
-
22
- LOG="$HOME/.claude/scripts/audit-log.sh"
23
- ROTATE="$HOME/.claude/scripts/audit-log-rotate.sh"
24
-
25
- if [ ! -x "$LOG" ]; then
26
- fail "audit-log.sh missing or not executable: $LOG"
27
- echo "══ aborted ══"; exit 2
28
- fi
29
-
30
- echo "β†’ 1. Emit a sample PAT lookup"
31
- TEST_IDENTITY="smoke-user"
32
- AUDIT_FILE="$TMP_FILE" "$LOG" pat_lookup bitbucket "$TEST_IDENTITY" \
33
- "https://bitbucket.example.com/proj/repo" true scope=REPO_WRITE
34
- [ -s "$TMP_FILE" ] && pass "audit.jsonl has content after emit" \
35
- || fail "audit.jsonl empty after emit"
36
-
37
- LINE=$(tail -1 "$TMP_FILE")
38
- echo " emitted: $LINE"
39
-
40
- echo ""
41
- echo "β†’ 2. Repo URL is SHA-256 hashed (not plaintext)"
42
- echo "$LINE" | grep -q '"repo_url_sha":"[a-f0-9]\{64\}"' \
43
- && pass "repo_url_sha is a 64-char hex SHA-256" \
44
- || fail "repo_url_sha not in expected SHA-256 form"
45
- echo "$LINE" | grep -q "bitbucket.example.com" \
46
- && fail "PLAINTEXT URL leaked into audit log!" \
47
- || pass "Plaintext URL absent (only hash present)"
48
-
49
- echo ""
50
- echo "β†’ 3. Identity is logged by name (per section18.2.3 - not anonymized)"
51
- echo "$LINE" | grep -q "\"identity\":\"$TEST_IDENTITY\"" \
52
- && pass "identity logged as plaintext name" \
53
- || fail "identity not logged or anonymized"
54
-
55
- echo ""
56
- echo "β†’ 4. Service is captured"
57
- echo "$LINE" | grep -q '"service":"bitbucket"' \
58
- && pass "service field captured" \
59
- || fail "service field missing"
60
-
61
- echo ""
62
- echo "β†’ 5. Success flag is a JSON bool (not string)"
63
- echo "$LINE" | grep -q '"success":true' \
64
- && pass "success is JSON bool" \
65
- || fail "success is not bool"
66
-
67
- echo ""
68
- echo "β†’ 6. Extras (key=value pairs) preserved"
69
- echo "$LINE" | grep -q '"scope":"REPO_WRITE"' \
70
- && pass "extras preserved (scope=REPO_WRITE)" \
71
- || fail "extras not preserved"
72
-
73
- echo ""
74
- echo "β†’ 7. Rotation script exists + executable"
75
- [ -x "$ROTATE" ] \
76
- && pass "audit-log-rotate.sh present + executable" \
77
- || fail "audit-log-rotate.sh missing or not executable"
78
-
79
- echo ""
80
- echo "β†’ 8. Rotation produces a dated archive + truncates live file"
81
- # Drive a dry rotation in our TMP dir
82
- AUDIT_DIR="$TMP_AUDIT" bash "$ROTATE" >/dev/null 2>&1
83
- ROTATED=$(find "$TMP_AUDIT" -name 'audit.*.jsonl*' -type f 2>/dev/null | head -1)
84
- [ -n "$ROTATED" ] \
85
- && pass "rotation produced archive: $(basename "$ROTATED")" \
86
- || fail "rotation produced no archive"
87
- [ -f "$TMP_FILE" ] && [ ! -s "$TMP_FILE" ] \
88
- && pass "live audit.jsonl truncated to empty after rotation" \
89
- || fail "live audit.jsonl not properly truncated"
90
-
91
- echo ""
92
- echo "β†’ 9. .gitignore covers audit logs (sensitive - never sync upstream)"
93
- SMOKE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
94
- GITIGNORES=(
95
- "$HOME/.claude/.gitignore"
96
- "$SMOKE_DIR/../../.gitignore"
97
- "$HOME/multi-agent-pipeline/.gitignore"
98
- )
99
- GITIGNORE_OK=false
100
- for gi in "${GITIGNORES[@]}"; do
101
- [ -f "$gi" ] && grep -qE 'audit\.jsonl|logs/multi-agent' "$gi" 2>/dev/null && GITIGNORE_OK=true
102
- done
103
- $GITIGNORE_OK \
104
- && pass ".gitignore covers audit.jsonl / logs/multi-agent" \
105
- || fail ".gitignore does NOT cover audit logs (sensitive!)"
106
-
107
- echo ""
108
- echo "β†’ 10. Quote-bearing fields are JSON-escaped (no malformed lines)"
109
- REPO_LOG="$(cd "$(dirname "$0")" && pwd)/audit-log.sh"
110
- ESC_FILE="$TMP_AUDIT/escape.jsonl"
111
- AUDIT_FILE="$ESC_FILE" bash "$REPO_LOG" \
112
- 'pat_"lookup' 'bit"bucket' 'evil" ,"injected":"x' \
113
- "https://x.example/r" true 'rea"son=quote'
114
- ESC_LINE=$(tail -1 "$ESC_FILE")
115
- if printf '%s' "$ESC_LINE" | python3 -c 'import json,sys; json.loads(sys.stdin.read())' 2>/dev/null; then
116
- pass "line with embedded double-quotes parses as valid JSON"
117
- else
118
- fail "embedded quotes produced malformed JSON: $ESC_LINE"
119
- fi
120
- if printf '%s' "$ESC_LINE" | python3 -c 'import json,sys; d=json.load(sys.stdin); sys.exit(0 if "injected" not in d else 1)' 2>/dev/null; then
121
- pass "quote in field value did not inject a sibling JSON key"
122
- else
123
- fail "quote in field value injected an extra top-level key"
124
- fi
125
-
126
- echo ""
127
- echo "══ pat-audit smoke: $PASS passed, $FAIL failed ══"
128
- [ "$FAIL" -eq 0 ]