@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,448 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-lib-scripts.sh - sanity checks for picker library scripts.
3
- # Verifies each lib emits the expected JSON shape so the picker pipeline can
4
- # rely on it. Network-dependent paths (real Jira/GH fetch) are skipped; only
5
- # offline-detectable behavior is asserted.
6
- #
7
- # Covered:
8
- # credential-store.sh doctor, platform detection, get/set/delete cycle (macOS only)
9
- # account-resolver.sh returns JSON array with required fields
10
- # issue-fetcher.sh input-kind detection + maturity object shape
11
- # multi-repo-pipeline.sh bridge → schema-valid agent-state.json v2.1.0
12
-
13
- set -uo pipefail
14
-
15
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
16
- REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
17
- LIB_DIR="$REPO_ROOT/pipeline/lib"
18
-
19
- PASS=0
20
- FAIL=0
21
-
22
- ok() { echo " ✓ $1"; PASS=$((PASS+1)); }
23
- fail() { echo " ✗ $1" >&2; FAIL=$((FAIL+1)); }
24
-
25
- # --- credential-store.sh -----------------------------------------------------
26
- echo "→ credential-store.sh"
27
- CRED="$LIB_DIR/credential-store.sh"
28
- if [ ! -x "$CRED" ]; then
29
- fail "credential-store.sh missing or not executable"
30
- else
31
- PLATFORM=$("$CRED" platform 2>/dev/null || echo "")
32
- case "$PLATFORM" in
33
- macos|windows|linux|unknown) ok "platform detected: $PLATFORM" ;;
34
- *) fail "unexpected platform output: $PLATFORM" ;;
35
- esac
36
-
37
- if "$CRED" doctor 2>&1 | grep -qE '^Backend:'; then
38
- ok "doctor reports backend"
39
- else
40
- fail "doctor missing Backend: line"
41
- fi
42
-
43
- # macOS only: full get/set/delete round-trip with throwaway key
44
- if [ "$PLATFORM" = "macos" ]; then
45
- KEY="multi-agent-smoke-test-$$"
46
- VAL="smoke-value-$(date +%s)"
47
- if "$CRED" set "$KEY" "$VAL" 2>/dev/null; then
48
- readback=$("$CRED" get "$KEY" 2>/dev/null || echo "")
49
- if [ "$readback" = "$VAL" ]; then
50
- ok "set/get round-trip works"
51
- else
52
- fail "get returned '$readback' instead of '$VAL'"
53
- fi
54
- "$CRED" delete "$KEY" 2>/dev/null
55
- empty=$("$CRED" get "$KEY" 2>/dev/null || echo "")
56
- if [ -z "$empty" ]; then
57
- ok "delete clears the entry"
58
- else
59
- fail "entry still present after delete"
60
- fi
61
- else
62
- fail "set failed on macOS"
63
- fi
64
- fi
65
- fi
66
-
67
- # --- account-resolver.sh -----------------------------------------------------
68
- echo "→ account-resolver.sh"
69
- RESOLVER="$LIB_DIR/account-resolver.sh"
70
- if [ ! -x "$RESOLVER" ]; then
71
- fail "account-resolver.sh missing or not executable"
72
- else
73
- out=$("$RESOLVER" 2>/dev/null || echo "[]")
74
- if printf '%s' "$out" | python3 -c 'import json,sys; json.loads(sys.stdin.read())' 2>/dev/null; then
75
- ok "emits valid JSON"
76
- else
77
- fail "JSON output invalid: $(printf '%s' "$out" | head -c 200)"
78
- fi
79
-
80
- # When non-empty, every account must have the required fields
81
- python3 - <<PY
82
- import json, sys
83
- try:
84
- data = json.loads('''$out''')
85
- except Exception as e:
86
- print(f"PARSE_FAIL:{e}")
87
- sys.exit(0)
88
-
89
- if not data:
90
- print("EMPTY")
91
- sys.exit(0)
92
-
93
- required = {"id","label","prefix","providers","tokens","userKeys","users","jiraHost","bitbucketHost","isWork"}
94
- missing = []
95
- for i, acc in enumerate(data):
96
- miss = required - set(acc.keys())
97
- if miss:
98
- missing.append((i, sorted(miss)))
99
- if missing:
100
- print(f"MISSING_FIELDS:{missing}")
101
- else:
102
- print(f"OK:{len(data)} accounts, all required fields present")
103
- PY
104
- status=$(python3 - <<PY
105
- import json
106
- data = json.loads('''$out''')
107
- print("ok" if data and all({"id","label","prefix","providers","tokens","userKeys","users","jiraHost","bitbucketHost","isWork"} <= set(a.keys()) for a in data) else ("empty" if not data else "fields-missing"))
108
- PY
109
- )
110
- case "$status" in
111
- ok) ok "all accounts have required schema fields" ;;
112
- empty) ok "no accounts on this machine (skipping schema check)" ;;
113
- fields-missing) fail "some accounts missing required fields" ;;
114
- esac
115
- fi
116
-
117
- # --- issue-fetcher.sh: input-kind detection (no network) ---------------------
118
- echo "→ issue-fetcher.sh"
119
- FETCHER="$LIB_DIR/issue-fetcher.sh"
120
- if [ ! -x "$FETCHER" ]; then
121
- fail "issue-fetcher.sh missing or not executable"
122
- else
123
- # detect_kind is internal; we exercise via freetext + jira-id paths that
124
- # don't hit the network. Freetext is the safest offline assertion.
125
- out=$("$FETCHER" "Login crash on iOS 17" 2>/dev/null || echo "")
126
- if printf '%s' "$out" | python3 -c 'import json,sys; d=json.loads(sys.stdin.read()); assert d["kind"]=="freetext", d["kind"]; assert d["maturity"]["score"] is None, "freetext score should be None"' 2>/dev/null; then
127
- ok "freetext path emits kind=freetext + maturity.score=None"
128
- else
129
- fail "freetext path output unexpected: $(printf '%s' "$out" | head -c 200)"
130
- fi
131
-
132
- # Jira-id detection without token (fetch will fail, but kind detection should still work)
133
- out=$(ACCOUNT_JIRA_TOKEN_KEY="" ACCOUNT_JIRA_HOST="" "$FETCHER" "PROJ-1" 2>/dev/null || echo "")
134
- if printf '%s' "$out" | python3 -c 'import json,sys; d=json.loads(sys.stdin.read()); assert d["kind"]=="jira", d["kind"]; assert d["key"]=="PROJ-1", d["key"]' 2>/dev/null; then
135
- ok "jira-id path emits kind=jira + key parsed"
136
- else
137
- fail "jira-id path output unexpected"
138
- fi
139
-
140
- # Bare "#316" and "316" must land in the gh-num branch (needsRepoPicker
141
- # descriptor when no default owner/repo), NOT gh-short with an empty repo
142
- # prefix that builds https://github.com/<owner>//issues/316.
143
- for bare in "#316" "316"; do
144
- out=$(ACCOUNT_DEFAULT_OWNER="" ACCOUNT_DEFAULT_REPO="" "$FETCHER" "$bare" 2>/dev/null || echo "")
145
- if printf '%s' "$out" | python3 -c 'import json,sys; d=json.loads(sys.stdin.read()); assert d["kind"]=="github", d["kind"]; assert d["key"]=="316", d["key"]; assert d.get("needsRepoPicker") is True, d' 2>/dev/null; then
146
- ok "bare '$bare' classified as gh-num (key=316, needsRepoPicker)"
147
- else
148
- fail "bare '$bare' misclassified: $(printf '%s' "$out" | head -c 200)"
149
- fi
150
- done
151
- fi
152
-
153
- # --- multi-repo-pipeline.sh: bridge --------------------------------------
154
- echo "→ multi-repo-pipeline.sh bridge"
155
- MRP="$LIB_DIR/multi-repo-pipeline.sh"
156
- if [ ! -x "$MRP" ]; then
157
- fail "multi-repo-pipeline.sh missing or not executable"
158
- else
159
- TMP=$(mktemp -d)
160
- cat > "$TMP/state.json" <<EOF
161
- {
162
- "taskId": "PROJ-1-smoke-$$",
163
- "shortId": 1,
164
- "branch": "feature/PROJ-1",
165
- "baseBranch": "develop",
166
- "issueRef": {"kind":"jira","key":"PROJ-1","title":"Smoke test"},
167
- "primary": {"name":"my-app","cloneUrl":"git@github.com:org/my-app.git","provider":"github"},
168
- "extras": [{"name":"common","cloneUrl":"git@github.com:org/common.git","provider":"github"}],
169
- "worktreeRoot": "$TMP/worktree",
170
- "commitType": "feat",
171
- "identity": {"name":"Smoke","email":"smoke@example.com"}
172
- }
173
- EOF
174
- bridged=$("$MRP" bridge "$TMP/state.json" 2>/dev/null || echo "")
175
- if [ -z "$bridged" ] || [ ! -f "$bridged" ]; then
176
- fail "bridge did not write agent-state.json"
177
- else
178
- if python3 - <<PY 2>/dev/null
179
- import json
180
- import jsonschema # may not be installed in CI; fall back to shape check
181
- PY
182
- then
183
- python3 - "$bridged" "$REPO_ROOT/pipeline/schemas/agent-state.schema.json" <<'PY' && ok "bridge output schema-valid (jsonschema)" || fail "bridge output schema-invalid"
184
- import json, sys
185
- from jsonschema import Draft202012Validator
186
- data = json.load(open(sys.argv[1]))
187
- schema = json.load(open(sys.argv[2]))
188
- errs = list(Draft202012Validator(schema).iter_errors(data))
189
- if errs:
190
- for e in errs: print("✗", "/".join(str(p) for p in e.absolute_path), e.message)
191
- sys.exit(1)
192
- PY
193
- else
194
- # Fallback: shape-only check
195
- if python3 - "$bridged" <<'PY' 2>/dev/null
196
- import json, sys
197
- d = json.load(open(sys.argv[1]))
198
- required = {"schemaVersion","taskId","shortId","project","branch","baseBranch","currentPhase","status","startedAt","phases","identity","projects"}
199
- missing = required - set(d.keys())
200
- assert not missing, f"missing: {missing}"
201
- assert d["schemaVersion"] == "2.1.0"
202
- assert len(d["projects"]) == 2
203
- PY
204
- then
205
- ok "bridge output has required fields (jsonschema unavailable; shape check)"
206
- else
207
- fail "bridge output missing required fields"
208
- fi
209
- fi
210
- rm -rf "$(dirname "$bridged")"
211
- fi
212
- rm -rf "$TMP"
213
- fi
214
-
215
- # --- multi-repo-pipeline.sh: prepare + commit + teardown (offline) -----------
216
- # Uses local bare-repo fixtures so we can exercise prepare/commit/teardown
217
- # without network access. push and pr remain unverified (network-bound).
218
- echo "→ multi-repo-pipeline.sh prepare/commit/teardown (offline fixtures)"
219
- MRP="$LIB_DIR/multi-repo-pipeline.sh"
220
- if [ ! -x "$MRP" ]; then
221
- fail "multi-repo-pipeline.sh not executable (already reported above)"
222
- else
223
- FIXTURE_ROOT=$(mktemp -d)
224
- REPO_A_BARE="$FIXTURE_ROOT/repo-a.git"
225
- REPO_B_BARE="$FIXTURE_ROOT/repo-b.git"
226
- WORKTREE_ROOT="$FIXTURE_ROOT/wt"
227
-
228
- # Build two bare-repo fixtures with one initial commit each.
229
- for bare in "$REPO_A_BARE" "$REPO_B_BARE"; do
230
- git init --quiet --bare -b main "$bare"
231
- seed=$(mktemp -d)
232
- git -C "$seed" init --quiet -b main
233
- echo "init" > "$seed/README.md"
234
- git -C "$seed" add README.md
235
- git -C "$seed" -c user.email=t@e.com -c user.name=T commit --quiet -m "init"
236
- git -C "$seed" remote add origin "$bare"
237
- git -C "$seed" push --quiet origin main
238
- rm -rf "$seed"
239
- done
240
-
241
- cat > "$FIXTURE_ROOT/state.json" <<EOF
242
- {
243
- "taskId":"OFFLINE-1",
244
- "shortId":1,
245
- "branch":"feature/OFFLINE-1",
246
- "baseBranch":"main",
247
- "issueRef":{"kind":"jira","key":"OFFLINE-1","title":"smoke fixture"},
248
- "primary":{"name":"repo-a","cloneUrl":"$REPO_A_BARE","provider":"github"},
249
- "extras":[{"name":"repo-b","cloneUrl":"$REPO_B_BARE","provider":"github"}],
250
- "worktreeRoot":"$WORKTREE_ROOT",
251
- "commitType":"feat",
252
- "identity":{"name":"T","email":"t@e.com"}
253
- }
254
- EOF
255
-
256
- # prepare
257
- if "$MRP" prepare "$FIXTURE_ROOT/state.json" >/dev/null 2>&1; then
258
- if [ -d "$WORKTREE_ROOT/repo-a/.git" ] && [ -d "$WORKTREE_ROOT/repo-b/.git" ]; then
259
- ok "prepare clones every repo into worktreeRoot"
260
- else
261
- fail "prepare did not create both worktrees"
262
- fi
263
- branch_a=$(git -C "$WORKTREE_ROOT/repo-a" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
264
- branch_b=$(git -C "$WORKTREE_ROOT/repo-b" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
265
- if [ "$branch_a" = "feature/OFFLINE-1" ] && [ "$branch_b" = "feature/OFFLINE-1" ]; then
266
- ok "prepare checks out the task branch in every repo"
267
- else
268
- fail "prepare branch mismatch: a=$branch_a b=$branch_b"
269
- fi
270
- else
271
- fail "prepare exited non-zero on offline fixture"
272
- fi
273
-
274
- # commit
275
- echo "change-a" > "$WORKTREE_ROOT/repo-a/feature.txt"
276
- echo "change-b" > "$WORKTREE_ROOT/repo-b/feature.txt"
277
- git -C "$WORKTREE_ROOT/repo-a" config user.email t@e.com >/dev/null 2>&1
278
- git -C "$WORKTREE_ROOT/repo-a" config user.name T >/dev/null 2>&1
279
- git -C "$WORKTREE_ROOT/repo-b" config user.email t@e.com >/dev/null 2>&1
280
- git -C "$WORKTREE_ROOT/repo-b" config user.name T >/dev/null 2>&1
281
-
282
- if "$MRP" commit "$FIXTURE_ROOT/state.json" >/dev/null 2>&1; then
283
- msg_a=$(git -C "$WORKTREE_ROOT/repo-a" log -1 --pretty=%s 2>/dev/null || echo "")
284
- msg_b=$(git -C "$WORKTREE_ROOT/repo-b" log -1 --pretty=%s 2>/dev/null || echo "")
285
- if printf '%s' "$msg_a" | grep -q '\[OFFLINE-1\]' && printf '%s' "$msg_b" | grep -q '\[OFFLINE-1\]'; then
286
- ok "commit creates one commit per repo with [JIRA-KEY] suffix"
287
- else
288
- fail "commit messages missing key suffix: a=$msg_a b=$msg_b"
289
- fi
290
- else
291
- fail "commit exited non-zero"
292
- fi
293
-
294
- # bridge (re-verify schema-valid output uses the repo names)
295
- bridged=$("$MRP" bridge "$FIXTURE_ROOT/state.json" 2>/dev/null || echo "")
296
- if [ -n "$bridged" ] && python3 -c '
297
- import json, sys
298
- d = json.load(open(sys.argv[1]))
299
- assert len(d["projects"]) == 2
300
- assert d["projects"][0]["project"] == "repo-a"
301
- assert d["projects"][1]["project"] == "repo-b"
302
- ' "$bridged" 2>/dev/null; then
303
- ok "bridge populates projects[] from primary + extras"
304
- rm -rf "$(dirname "$bridged")"
305
- else
306
- fail "bridge output missing projects[] entries"
307
- fi
308
-
309
- # teardown guard: a worktreeRoot of "." (the current project root) must be
310
- # refused loudly, never rm -rf'd.
311
- UNSAFE_STATE="$FIXTURE_ROOT/unsafe-state.json"
312
- sed 's|"worktreeRoot":"[^"]*"|"worktreeRoot":"."|' "$FIXTURE_ROOT/state.json" > "$UNSAFE_STATE"
313
- GUARD_DIR=$(mktemp -d)
314
- ( cd "$GUARD_DIR" && "$MRP" teardown "$UNSAFE_STATE" ) >/dev/null 2>&1
315
- guard_rc=$?
316
- if [ "$guard_rc" -ne 0 ] && [ -d "$GUARD_DIR" ]; then
317
- ok "teardown refuses worktreeRoot='.' (exit=$guard_rc, dir intact)"
318
- else
319
- fail "teardown did not refuse unsafe worktreeRoot='.' (exit=$guard_rc)"
320
- fi
321
- rm -rf "$GUARD_DIR"
322
-
323
- # teardown
324
- "$MRP" teardown "$FIXTURE_ROOT/state.json" >/dev/null 2>&1
325
- if [ ! -d "$WORKTREE_ROOT" ]; then
326
- ok "teardown removes worktreeRoot"
327
- else
328
- fail "teardown left $WORKTREE_ROOT in place"
329
- fi
330
-
331
- rm -rf "$FIXTURE_ROOT"
332
- fi
333
-
334
- # --- channels-multi-repo.sh: render fan-out (offline) ------------------------
335
- echo "→ channels-multi-repo.sh renderers"
336
- CMR="$LIB_DIR/channels-multi-repo.sh"
337
- if [ ! -x "$CMR" ]; then
338
- fail "channels-multi-repo.sh missing or not executable"
339
- else
340
- TMP=$(mktemp -d)
341
- cat > "$TMP/multi.json" <<'EOF'
342
- {
343
- "schemaVersion":"2.1.0","taskId":"X","shortId":1,"project":"my-app",
344
- "branch":"feature/X","baseBranch":"develop","currentPhase":6,"status":"in_progress",
345
- "startedAt":"2026-04-27T12:00:00Z","phases":{},"identity":{"name":"T","email":"t@e.com"},
346
- "projects":[
347
- {"project":"my-app","projectRoot":"/tmp/a","branch":"feature/X","baseBranch":"develop","identity":{"name":"T","email":"t@e.com"},"pr":{"number":42,"url":"https://github.com/org/my-app/pull/42"}},
348
- {"project":"common","projectRoot":"/tmp/b","branch":"feature/X","baseBranch":"develop","identity":{"name":"T","email":"t@e.com"},"pr":{"number":17,"url":"https://github.com/org/common/pull/17"}}
349
- ]
350
- }
351
- EOF
352
- cat > "$TMP/single.json" <<'EOF'
353
- {
354
- "schemaVersion":"2.1.0","taskId":"Y","shortId":1,"project":"my-app",
355
- "branch":"feature/Y","baseBranch":"develop","currentPhase":6,"status":"in_progress",
356
- "startedAt":"2026-04-27T12:00:00Z","phases":{},"identity":{"name":"T","email":"t@e.com"},
357
- "pr":{"number":1,"url":"https://github.com/org/my-app/pull/1"}
358
- }
359
- EOF
360
- echo "body content" > "$TMP/body.md"
361
-
362
- # targets: multi-repo gives 2 entries with cross-links populated
363
- count=$("$CMR" targets "$TMP/multi.json" 2>/dev/null | python3 -c 'import json,sys; d=json.load(sys.stdin); print(len(d))')
364
- if [ "$count" = "2" ]; then
365
- ok "targets emits one entry per project (multi-repo)"
366
- else
367
- fail "targets count $count != 2"
368
- fi
369
-
370
- # render-pr: extras body must mention 'Part of:' primary URL
371
- rendered=$("$CMR" render-pr "$TMP/multi.json" "$TMP/body.md" "common" 2>/dev/null)
372
- if printf '%s' "$rendered" | grep -q "Part of: https://github.com/org/my-app"; then
373
- ok "render-pr injects 'Part of:' link on extras"
374
- else
375
- fail "render-pr missing 'Part of:' on extras"
376
- fi
377
- rendered=$("$CMR" render-pr "$TMP/multi.json" "$TMP/body.md" "my-app" 2>/dev/null)
378
- if printf '%s' "$rendered" | grep -q "Related: https://github.com/org/common"; then
379
- ok "render-pr injects 'Related:' link on primary"
380
- else
381
- fail "render-pr missing 'Related:' on primary"
382
- fi
383
-
384
- # render-jira: bulleted PR list at top
385
- rendered=$("$CMR" render-jira "$TMP/multi.json" "$TMP/body.md" 2>/dev/null)
386
- bullet_count=$(printf '%s' "$rendered" | grep -c '^\* PR:' || true)
387
- if [ "$bullet_count" = "2" ]; then
388
- ok "render-jira prepends bulleted PR list (2 entries)"
389
- else
390
- fail "render-jira bullet count $bullet_count != 2"
391
- fi
392
-
393
- # render-jira/-conf with ZERO PR urls: must pass the body through unchanged
394
- # (the old `grep -c || echo 0` idiom emitted "0\n0" here and prepended
395
- # bogus "* PR:" bullets to the posted body).
396
- cat > "$TMP/noprs.json" <<'EOF'
397
- {
398
- "schemaVersion":"2.1.0","taskId":"Z","shortId":1,"project":"my-app",
399
- "branch":"feature/Z","baseBranch":"develop","currentPhase":6,"status":"in_progress",
400
- "startedAt":"2026-04-27T12:00:00Z","phases":{},"identity":{"name":"T","email":"t@e.com"},
401
- "projects":[
402
- {"project":"my-app","projectRoot":"/tmp/a","branch":"feature/Z","baseBranch":"develop","identity":{"name":"T","email":"t@e.com"},"pr":null},
403
- {"project":"common","projectRoot":"/tmp/b","branch":"feature/Z","baseBranch":"develop","identity":{"name":"T","email":"t@e.com"},"pr":null}
404
- ]
405
- }
406
- EOF
407
- rendered=$("$CMR" render-jira "$TMP/noprs.json" "$TMP/body.md" 2>/dev/null)
408
- if [ "$rendered" = "body content" ]; then
409
- ok "render-jira with zero PR urls passes body through unchanged"
410
- else
411
- fail "render-jira with zero PR urls mutated body: $rendered"
412
- fi
413
- rendered=$("$CMR" render-conf "$TMP/noprs.json" "$TMP/body.md" 2>/dev/null)
414
- if [ "$rendered" = "body content" ]; then
415
- ok "render-conf with zero PR urls passes body through unchanged"
416
- else
417
- fail "render-conf with zero PR urls mutated body: $rendered"
418
- fi
419
-
420
- # render-conf: ## Related PRs heading
421
- rendered=$("$CMR" render-conf "$TMP/multi.json" "$TMP/body.md" 2>/dev/null)
422
- if printf '%s' "$rendered" | grep -q "## Related PRs"; then
423
- ok "render-conf prepends 'Related PRs' heading"
424
- else
425
- fail "render-conf missing heading block"
426
- fi
427
-
428
- # single-repo passthrough: render-pr on single state must return body unchanged
429
- rendered=$("$CMR" render-pr "$TMP/single.json" "$TMP/body.md" "my-app" 2>/dev/null)
430
- if [ "$rendered" = "body content" ]; then
431
- ok "single-repo passthrough leaves body unchanged"
432
- else
433
- fail "single-repo passthrough mutated body: $rendered"
434
- fi
435
-
436
- rm -rf "$TMP"
437
- fi
438
-
439
- # --- Summary -----------------------------------------------------------------
440
- echo ""
441
- TOTAL=$((PASS+FAIL))
442
- if [ $FAIL -eq 0 ]; then
443
- echo "══ lib-scripts smoke: $PASS passed, 0 failed ══"
444
- exit 0
445
- else
446
- echo "══ lib-scripts smoke: $PASS passed, $FAIL failed (of $TOTAL) ══" >&2
447
- exit 1
448
- fi
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-mcp-gate.sh - fixture-based test harness for smoke-no-mcp-in-dev-phases.sh.
3
- #
4
- # The gate script doubles as the production check (run against a live
5
- # state.telemetry.mcpCalls[] after a pipeline run) and is also picked up by the
6
- # smoke-*.sh glob - but bare, with no live run, it only WARNs. This harness
7
- # drives the gate with crafted state files so the actual decision logic
8
- # (PASS / FAIL / WARN / parse-error) is exercised in CI on every run.
9
-
10
- set -uo pipefail
11
-
12
- REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
13
- GATE="$REPO_ROOT/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh"
14
-
15
- PASS=0
16
- FAIL=0
17
- pass() { PASS=$((PASS + 1)); echo " ✓ $1"; }
18
- fail() { FAIL=$((FAIL + 1)); echo " ✗ $1"; }
19
-
20
- cleanup() { [ -n "${TMP:-}" ] && rm -rf "$TMP"; }
21
- trap cleanup EXIT
22
- TMP=$(mktemp -d)
23
-
24
- run_gate() { bash "$GATE" --state-file "$1" >/dev/null 2>&1; echo $?; }
25
-
26
- echo "→ 1. Clean run (all MCP calls in Phase 1) PASSES"
27
- cat > "$TMP/clean.json" <<'EOF'
28
- {"telemetry":{"mcpCalls":[
29
- {"tool":"mcp__claude_ai_Figma__get_design_context","phase":1,"timestamp":"2026-05-29T10:00:00Z"},
30
- {"tool":"mcp__claude_ai_Figma__get_screenshot","phase":1,"timestamp":"2026-05-29T10:00:05Z"}
31
- ]}}
32
- EOF
33
- [ "$(run_gate "$TMP/clean.json")" = "0" ] && pass "phase-1-only telemetry exits 0" || fail "phase-1-only telemetry should exit 0"
34
-
35
- echo "→ 2. Violation (MCP call in Phase 3) FAILS"
36
- cat > "$TMP/viol.json" <<'EOF'
37
- {"telemetry":{"mcpCalls":[
38
- {"tool":"mcp__claude_ai_Figma__get_design_context","phase":1,"timestamp":"2026-05-29T10:00:00Z"},
39
- {"tool":"mcp__claude_ai_Figma__get_screenshot","phase":3,"timestamp":"2026-05-29T11:00:00Z"}
40
- ]}}
41
- EOF
42
- [ "$(run_gate "$TMP/viol.json")" = "1" ] && pass "phase-3 MCP call exits 1" || fail "phase-3 MCP call should exit 1 (gate is a no-op?)"
43
-
44
- echo "→ 3. Phase 2+ boundary (phase 2 is a violation)"
45
- cat > "$TMP/viol2.json" <<'EOF'
46
- {"telemetry":{"mcpCalls":[{"tool":"mcp__claude_ai_Figma__get_metadata","phase":2,"timestamp":"2026-05-29T10:30:00Z"}]}}
47
- EOF
48
- [ "$(run_gate "$TMP/viol2.json")" = "1" ] && pass "phase-2 MCP call exits 1" || fail "phase-2 MCP call should exit 1"
49
-
50
- echo "→ 4. Empty mcpCalls array PASSES"
51
- cat > "$TMP/empty.json" <<'EOF'
52
- {"telemetry":{"mcpCalls":[]}}
53
- EOF
54
- [ "$(run_gate "$TMP/empty.json")" = "0" ] && pass "empty mcpCalls exits 0" || fail "empty mcpCalls should exit 0"
55
-
56
- echo "→ 5. Absent telemetry field PASSES (backward compat with pre-v9 runs)"
57
- cat > "$TMP/absent.json" <<'EOF'
58
- {"taskId":"#1","status":"complete"}
59
- EOF
60
- [ "$(run_gate "$TMP/absent.json")" = "0" ] && pass "absent telemetry exits 0" || fail "absent telemetry should exit 0"
61
-
62
- echo "→ 6. Corrupt JSON state is a hard error (not silently passed)"
63
- echo '{not valid json' > "$TMP/corrupt.json"
64
- [ "$(run_gate "$TMP/corrupt.json")" = "2" ] && pass "corrupt state exits 2" || fail "corrupt state should exit 2"
65
-
66
- echo ""
67
- echo "══ mcp-gate smoke: $PASS passed, $FAIL failed ══"
68
- [ "$FAIL" -eq 0 ]
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-md-links.sh - thin wrapper so the internal markdown link gate runs
3
- # inside the standard smoke discovery (run-smokes.mjs picks up smoke-*.sh).
4
- # Logic lives in check-md-links.mjs.
5
- set -euo pipefail
6
-
7
- ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
8
- node "$ROOT/pipeline/scripts/check-md-links.mjs"
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-md2confluence.sh - offline sanity checks for md2confluence-v3.py.
3
- # Imports the module from its file path and exercises the pure conversion
4
- # layer: front-matter parsing, the TR/EN punctuation gate (fancy punctuation
5
- # stripped, diacritics preserved), and markdown -> storage-XML rendering for
6
- # headings, tables, and code blocks. No network, no credentials, no Confluence.
7
-
8
- set -uo pipefail
9
-
10
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
11
- REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
12
- MOD="$REPO_ROOT/pipeline/lib/md2confluence-v3.py"
13
-
14
- if ! command -v python3 >/dev/null 2>&1; then
15
- echo "smoke-md2confluence: python3 not found; skipping" >&2
16
- exit 0
17
- fi
18
-
19
- if [ ! -f "$MOD" ]; then
20
- echo " ✗ md2confluence-v3.py missing at $MOD" >&2
21
- exit 1
22
- fi
23
-
24
- MOD_PATH="$MOD" python3 - <<'PYEOF'
25
- import importlib.util
26
- import os
27
- import sys
28
-
29
- spec = importlib.util.spec_from_file_location("md2c", os.environ["MOD_PATH"])
30
- mod = importlib.util.module_from_spec(spec)
31
- sys.modules["md2c"] = mod # dataclass introspection needs the module registered
32
- spec.loader.exec_module(mod)
33
-
34
- passed = 0
35
- failed = 0
36
-
37
- def ok(msg):
38
- global passed
39
- print(f" ✓ {msg}")
40
- passed += 1
41
-
42
- def fail(msg):
43
- global failed
44
- print(f" ✗ {msg}", file=sys.stderr)
45
- failed += 1
46
-
47
- # --- parse_front_matter ------------------------------------------------------
48
- print("→ parse_front_matter")
49
- fm, body = mod.parse_front_matter("---\nfeature: Sample\nlanguage: tr\n---\n# Heading\nbody text\n")
50
- if fm.get("feature") == "Sample" and fm.get("language") == "tr":
51
- ok("front-matter keys parsed")
52
- else:
53
- fail(f"front-matter parse wrong: {fm}")
54
- if body.startswith("# Heading"):
55
- ok("body separated from front-matter")
56
- else:
57
- fail(f"body not separated: {body[:40]!r}")
58
-
59
- fm2, body2 = mod.parse_front_matter("# No front matter\n")
60
- if fm2 == {} and body2.startswith("# No"):
61
- ok("no-front-matter input passes through")
62
- else:
63
- fail("no-front-matter input mangled")
64
-
65
- # --- apply_punctuation_gate ----------------------------------------------------
66
- print("→ apply_punctuation_gate")
67
- dirty = "Geliştirme Özeti — kullanıcı için “detay” …"
68
- clean, warnings = mod.apply_punctuation_gate(dirty, "tr")
69
- forbidden = ["—", "–", "…", "“", "”", "‘", "’"]
70
- if not any(ch in clean for ch in forbidden):
71
- ok("fancy punctuation stripped")
72
- else:
73
- fail(f"fancy punctuation survived: {clean!r}")
74
- for ch in ("ş", "Ö", "ı", "ç"):
75
- if ch not in clean:
76
- fail(f"diacritic {ch!r} was lost - gate must not ASCII-fold")
77
- break
78
- else:
79
- ok("Turkish diacritics preserved")
80
-
81
- # --- markdown_to_storage ---------------------------------------------------------
82
- print("→ markdown_to_storage")
83
- md = "\n".join([
84
- "# Title",
85
- "",
86
- "Intro paragraph.",
87
- "",
88
- "| A | B |",
89
- "|---|---|",
90
- "| 1 | 2 |",
91
- "",
92
- "```swift",
93
- "let x = 1",
94
- "```",
95
- ])
96
- res = mod.markdown_to_storage(
97
- md,
98
- attachments_available=set(),
99
- mermaid_fallback=True,
100
- tooltip_macro_supported=False,
101
- )
102
- xml = res.storage_xml
103
- checks = [
104
- ("<h1>", "h1 heading rendered"),
105
- ("<table>", "table rendered"),
106
- ("ac:structured-macro", "code block rendered as macro"),
107
- ]
108
- for needle, label in checks:
109
- if needle in xml:
110
- ok(label)
111
- else:
112
- fail(f"{label}: {needle!r} not found in storage XML")
113
-
114
- if res.images_referenced == []:
115
- ok("no phantom image references")
116
- else:
117
- fail(f"unexpected images referenced: {res.images_referenced}")
118
-
119
- # --- summary -------------------------------------------------------------------
120
- print()
121
- if failed == 0:
122
- print(f"══ md2confluence smoke: {passed} passed, 0 failed ══")
123
- sys.exit(0)
124
- print(f"══ md2confluence smoke: {passed} passed, {failed} failed ══", file=sys.stderr)
125
- sys.exit(1)
126
- PYEOF