@mmerterden/multi-agent-pipeline 12.5.0 → 12.7.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 (290) hide show
  1. package/CHANGELOG.md +213 -0
  2. package/README.md +19 -19
  3. package/docs/FIGMA_PIPELINE.md +34 -34
  4. package/docs/adr/0001-three-model-triage.md +12 -12
  5. package/docs/adr/0002-instruction-driven-flag.md +5 -5
  6. package/docs/adr/0003-unified-shared-skills.md +5 -5
  7. package/docs/adr/0004-zero-dependency-philosophy.md +5 -5
  8. package/docs/adr/0005-lazy-phase-docs.md +2 -2
  9. package/docs/adr/0006-skills-core-external-split.md +6 -6
  10. package/docs/adr/0007-multi-tool-adapter-framework.md +19 -19
  11. package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +19 -19
  12. package/docs/adr/README.md +1 -1
  13. package/docs/best-practices.md +3 -3
  14. package/docs/features.md +48 -28
  15. package/docs/performance.md +16 -16
  16. package/docs/recovery-guide.md +39 -39
  17. package/index.js +11 -5
  18. package/install/_common.mjs +5 -11
  19. package/install/_copilot-instructions.mjs +2 -2
  20. package/install/_dev-only-files.mjs +2 -1
  21. package/install/_platform-filter.mjs +1 -1
  22. package/install/_telemetry.mjs +1 -1
  23. package/install/claude.mjs +10 -9
  24. package/install/copilot.mjs +10 -19
  25. package/install/index.mjs +7 -15
  26. package/install/templates/copilot-instructions.md +54 -54
  27. package/install.js +1 -1
  28. package/package.json +15 -11
  29. package/pipeline/agents/security-auditor.md +1 -1
  30. package/pipeline/commands/archive-guard.md +5 -5
  31. package/pipeline/commands/multi-agent/SKILL.md +3 -1
  32. package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
  33. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -1
  34. package/pipeline/commands/multi-agent/autopilot/SKILL.md +1 -1
  35. package/pipeline/commands/multi-agent/build-optimize/SKILL.md +1 -1
  36. package/pipeline/commands/multi-agent/create-jira/SKILL.md +1 -1
  37. package/pipeline/commands/multi-agent/design-check/SKILL.md +287 -0
  38. package/pipeline/commands/multi-agent/dev/SKILL.md +1 -1
  39. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -1
  40. package/pipeline/commands/multi-agent/dev-local/SKILL.md +1 -1
  41. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -1
  42. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +1 -1
  43. package/pipeline/commands/multi-agent/finish/SKILL.md +6 -6
  44. package/pipeline/commands/multi-agent/forget/SKILL.md +1 -1
  45. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  46. package/pipeline/commands/multi-agent/help/SKILL.md +46 -6
  47. package/pipeline/commands/multi-agent/issue/SKILL.md +1 -1
  48. package/pipeline/commands/multi-agent/jira/SKILL.md +1 -1
  49. package/pipeline/commands/multi-agent/kill/SKILL.md +1 -1
  50. package/pipeline/commands/multi-agent/language/SKILL.md +1 -1
  51. package/pipeline/commands/multi-agent/local/SKILL.md +1 -1
  52. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -1
  53. package/pipeline/commands/multi-agent/log/SKILL.md +1 -1
  54. package/pipeline/commands/multi-agent/manual-test/SKILL.md +1 -1
  55. package/pipeline/commands/multi-agent/prune-logs/SKILL.md +1 -1
  56. package/pipeline/commands/multi-agent/purge/SKILL.md +1 -1
  57. package/pipeline/commands/multi-agent/refactor/SKILL.md +107 -19
  58. package/pipeline/commands/multi-agent/resume/SKILL.md +2 -2
  59. package/pipeline/commands/multi-agent/review/SKILL.md +2 -2
  60. package/pipeline/commands/multi-agent/review-issue/SKILL.md +1 -1
  61. package/pipeline/commands/multi-agent/review-jira/SKILL.md +1 -1
  62. package/pipeline/commands/multi-agent/routines/SKILL.md +1 -1
  63. package/pipeline/commands/multi-agent/save/SKILL.md +1 -1
  64. package/pipeline/commands/multi-agent/scan/SKILL.md +1 -1
  65. package/pipeline/commands/multi-agent/search/SKILL.md +1 -1
  66. package/pipeline/commands/multi-agent/setup/SKILL.md +2 -2
  67. package/pipeline/commands/multi-agent/stack/SKILL.md +3 -3
  68. package/pipeline/commands/multi-agent/status/SKILL.md +1 -1
  69. package/pipeline/commands/multi-agent/sync/SKILL.md +123 -16
  70. package/pipeline/commands/multi-agent/test/SKILL.md +2 -2
  71. package/pipeline/commands/multi-agent/uninstall/SKILL.md +1 -1
  72. package/pipeline/commands/multi-agent/update/SKILL.md +3 -3
  73. package/pipeline/commands/sim-test.md +5 -5
  74. package/pipeline/eval/run-metrics-fixture.json +60 -13
  75. package/pipeline/lib/account-resolver.sh +1 -1
  76. package/pipeline/lib/channels-multi-repo.sh +1 -1
  77. package/pipeline/lib/context-link-extractor.sh +1 -1
  78. package/pipeline/lib/credential-store.sh +33 -1
  79. package/pipeline/lib/fetch-confluence.sh +1 -1
  80. package/pipeline/lib/fetch-crashlytics.sh +1 -1
  81. package/pipeline/lib/fetch-fortify.sh +1 -1
  82. package/pipeline/lib/fetch-graylog.sh +1 -1
  83. package/pipeline/lib/fetch-swagger.sh +1 -1
  84. package/pipeline/lib/issue-fetcher.sh +1 -1
  85. package/pipeline/lib/multi-repo-pipeline.sh +1 -1
  86. package/pipeline/lib/repo-cache.sh +1 -1
  87. package/pipeline/lib/submodule-detector.sh +1 -1
  88. package/pipeline/multi-agent-refs/component-dispatch.md +1 -1
  89. package/pipeline/multi-agent-refs/component-generation.md +121 -0
  90. package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
  91. package/pipeline/multi-agent-refs/features/model-fallback.md +2 -2
  92. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +2 -2
  93. package/pipeline/multi-agent-refs/phases/phase-4-review.md +1 -1
  94. package/pipeline/multi-agent-refs/phases/phase-5-test.md +4 -4
  95. package/pipeline/preferences-template.json +23 -12
  96. package/pipeline/schemas/agent-state.schema.json +125 -5
  97. package/pipeline/schemas/analysis-output.schema.json +18 -4
  98. package/pipeline/schemas/analysis-spec.schema.json +120 -32
  99. package/pipeline/schemas/clarify-output.schema.json +15 -5
  100. package/pipeline/schemas/design-check-config.schema.json +183 -0
  101. package/pipeline/schemas/dev-critic-output.schema.json +20 -5
  102. package/pipeline/schemas/figma-project-config.schema.json +42 -10
  103. package/pipeline/schemas/learnings-ledger.schema.json +10 -2
  104. package/pipeline/schemas/migrations/figma-config-1.0.0-to-2.0.0.mjs +1 -4
  105. package/pipeline/schemas/migrations/prefs-2.0.0-to-2.1.0.mjs +24 -7
  106. package/pipeline/schemas/migrations/state-2.0.0-to-2.1.0.mjs +30 -12
  107. package/pipeline/schemas/plan-todos.schema.json +6 -3
  108. package/pipeline/schemas/planning-output.schema.json +5 -1
  109. package/pipeline/schemas/prefs.schema.json +215 -197
  110. package/pipeline/schemas/test-gap.schema.json +5 -5
  111. package/pipeline/schemas/token-budget.json +8 -8
  112. package/pipeline/schemas/triage-corpus.schema.json +1 -1
  113. package/pipeline/scripts/README.md +7 -5
  114. package/pipeline/scripts/aggregate-metrics.mjs +18 -6
  115. package/pipeline/scripts/build-skills-index.mjs +6 -2
  116. package/pipeline/scripts/build-stack-plugins.mjs +142 -39
  117. package/pipeline/scripts/check-derived-drift.mjs +196 -0
  118. package/pipeline/scripts/check-md-links.mjs +6 -2
  119. package/pipeline/scripts/classify-plan-safety.mjs +26 -8
  120. package/pipeline/scripts/cost-budget-check.mjs +11 -6
  121. package/pipeline/scripts/cost-table.json +1 -1
  122. package/pipeline/scripts/diff-explain.mjs +7 -3
  123. package/pipeline/scripts/diff-risk-score.mjs +13 -3
  124. package/pipeline/scripts/eval-golden-tasks-live.mjs +8 -3
  125. package/pipeline/scripts/eval-golden-tasks.mjs +21 -9
  126. package/pipeline/scripts/eval-intent.mjs +8 -4
  127. package/pipeline/scripts/eval-mine-corpus.mjs +14 -4
  128. package/pipeline/scripts/evidence-gate.mjs +7 -2
  129. package/pipeline/scripts/fixtures/install-layout.tsv +7 -7
  130. package/pipeline/scripts/gen-mode-dispatch.mjs +38 -21
  131. package/pipeline/scripts/gen-skills-index.mjs +18 -3
  132. package/pipeline/scripts/learning-curve.mjs +13 -3
  133. package/pipeline/scripts/learnings-ledger.mjs +119 -34
  134. package/pipeline/scripts/lint-mcp-refs.mjs +218 -0
  135. package/pipeline/scripts/lint-skills.mjs +20 -9
  136. package/pipeline/scripts/localize-commands.mjs +6 -1
  137. package/pipeline/scripts/match-skills.mjs +15 -4
  138. package/pipeline/scripts/memory-load.sh +5 -1
  139. package/pipeline/scripts/migrate-prefs.mjs +33 -16
  140. package/pipeline/scripts/phase-tracker.sh +3 -1
  141. package/pipeline/scripts/render-work-summary.sh +4 -1
  142. package/pipeline/scripts/repo-map.mjs +110 -64
  143. package/pipeline/scripts/review-scope.mjs +7 -1
  144. package/pipeline/scripts/routine-registry.mjs +4 -9
  145. package/pipeline/scripts/run-aggregator.mjs +11 -5
  146. package/pipeline/scripts/run-metrics.mjs +13 -8
  147. package/pipeline/scripts/run-smokes.mjs +57 -3
  148. package/pipeline/scripts/scorecard.mjs +258 -0
  149. package/pipeline/scripts/smoke-command-inventory.sh +81 -0
  150. package/pipeline/scripts/smoke-commands-skills-parity.sh +1 -1
  151. package/pipeline/scripts/smoke-compliance-skills.sh +4 -4
  152. package/pipeline/scripts/smoke-context-budget.sh +72 -0
  153. package/pipeline/scripts/smoke-cross-cli-behavior.sh +12 -2
  154. package/pipeline/scripts/smoke-generate-issue.sh +6 -5
  155. package/pipeline/scripts/smoke-model-fallback.sh +1 -1
  156. package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +86 -7
  157. package/pipeline/scripts/smoke-own-punctuation.sh +103 -0
  158. package/pipeline/scripts/smoke-per-repo-memory.sh +2 -2
  159. package/pipeline/scripts/smoke-review-readiness.sh +3 -2
  160. package/pipeline/scripts/smoke-schema-validation.sh +19 -5
  161. package/pipeline/scripts/smoke-shadow-git.sh +4 -2
  162. package/pipeline/scripts/smoke-workflow-audit.sh +43 -11
  163. package/pipeline/scripts/smoke-write-state.sh +49 -5
  164. package/pipeline/scripts/test-gap-rules/android.json +11 -11
  165. package/pipeline/scripts/test-gap-rules/ios.json +16 -11
  166. package/pipeline/scripts/test-gap-rules/node.json +19 -7
  167. package/pipeline/scripts/test-gap-rules/python.json +10 -4
  168. package/pipeline/scripts/test-gap-scan.mjs +44 -12
  169. package/pipeline/scripts/test-integrity-gate.mjs +5 -1
  170. package/pipeline/scripts/token-budget-report.mjs +44 -21
  171. package/pipeline/scripts/triage-memory.mjs +142 -32
  172. package/pipeline/scripts/uninstall.mjs +75 -31
  173. package/pipeline/scripts/validate-analysis-doc.mjs +15 -5
  174. package/pipeline/scripts/validate-diff-risk.mjs +32 -18
  175. package/pipeline/scripts/validate-test-gap.mjs +17 -7
  176. package/pipeline/scripts/validate-triage.mjs +17 -5
  177. package/pipeline/scripts/write-state.mjs +32 -9
  178. package/pipeline/skills/.skill-manifest.json +24 -8
  179. package/pipeline/skills/.skills-index.json +125 -89
  180. package/pipeline/skills/shared/README.md +65 -61
  181. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +10 -8
  182. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +1 -1
  183. package/pipeline/skills/shared/core/multi-agent/SKILL.md +26 -279
  184. package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +1 -1
  185. package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +1 -1
  186. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +1 -1
  187. package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
  188. package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
  189. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +248 -0
  190. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +1 -1
  191. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +1 -1
  192. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +1 -1
  193. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +1 -1
  194. package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +1 -1
  195. package/pipeline/skills/shared/core/multi-agent-finish/SKILL.md +1 -1
  196. package/pipeline/skills/shared/core/multi-agent-forget/SKILL.md +1 -1
  197. package/pipeline/skills/shared/core/multi-agent-garbage-collect/SKILL.md +1 -1
  198. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +23 -1
  199. package/pipeline/skills/shared/core/multi-agent-issue/SKILL.md +1 -1
  200. package/pipeline/skills/shared/core/multi-agent-jira/SKILL.md +1 -1
  201. package/pipeline/skills/shared/core/multi-agent-kill/SKILL.md +1 -1
  202. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +1 -1
  203. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +1 -1
  204. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +1 -1
  205. package/pipeline/skills/shared/core/multi-agent-log/SKILL.md +1 -1
  206. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +1 -1
  207. package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +1 -1
  208. package/pipeline/skills/shared/core/multi-agent-purge/SKILL.md +1 -1
  209. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +82 -18
  210. package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +1 -1
  211. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +2 -2
  212. package/pipeline/skills/shared/core/multi-agent-review-issue/SKILL.md +1 -1
  213. package/pipeline/skills/shared/core/multi-agent-review-jira/SKILL.md +1 -1
  214. package/pipeline/skills/shared/core/multi-agent-routines/SKILL.md +1 -1
  215. package/pipeline/skills/shared/core/multi-agent-save/SKILL.md +1 -1
  216. package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +1 -1
  217. package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +1 -1
  218. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
  219. package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +3 -3
  220. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +1 -1
  221. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +51 -9
  222. package/pipeline/skills/shared/core/multi-agent-test/SKILL.md +2 -2
  223. package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +1 -1
  224. package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +1 -1
  225. package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +1 -1
  226. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +4 -4
  227. package/pipeline/skills/shared/external/agentflow/SKILL.md +1 -1
  228. package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +1 -1
  229. package/pipeline/skills/shared/external/android_ui_verification/SKILL.md +1 -1
  230. package/pipeline/skills/shared/external/api-patterns/SKILL.md +1 -1
  231. package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +1 -1
  232. package/pipeline/skills/shared/external/app-store-changelog/SKILL.md +1 -1
  233. package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
  234. package/pipeline/skills/shared/external/backlog/SKILL.md +12 -12
  235. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -1
  236. package/pipeline/skills/shared/external/context-compression/SKILL.md +1 -1
  237. package/pipeline/skills/shared/external/council/SKILL.md +3 -3
  238. package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -1
  239. package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -1
  240. package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +1 -1
  241. package/pipeline/skills/shared/external/docker-expert/SKILL.md +1 -1
  242. package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +1 -1
  243. package/pipeline/skills/shared/external/firebase/SKILL.md +1 -1
  244. package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +1 -1
  245. package/pipeline/skills/shared/external/help-skills/SKILL.md +1 -1
  246. package/pipeline/skills/shared/external/hig-components-content/SKILL.md +1 -1
  247. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +1 -1
  248. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +1 -1
  249. package/pipeline/skills/shared/external/hig-components-system/SKILL.md +1 -1
  250. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +1 -1
  251. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +1 -1
  252. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +1 -1
  253. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +1 -1
  254. package/pipeline/skills/shared/external/hig-technologies/SKILL.md +1 -1
  255. package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -1
  256. package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -1
  257. package/pipeline/skills/shared/external/ios-debugger-agent/SKILL.md +1 -1
  258. package/pipeline/skills/shared/external/ios-developer/SKILL.md +1 -1
  259. package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +1 -1
  260. package/pipeline/skills/shared/external/macos-menubar-tuist-app/SKILL.md +1 -1
  261. package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +1 -1
  262. package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +1 -1
  263. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -1
  264. package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +1 -1
  265. package/pipeline/skills/shared/external/observability-engineer/SKILL.md +1 -1
  266. package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -1
  267. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -1
  268. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -1
  269. package/pipeline/skills/shared/external/search-first/SKILL.md +2 -2
  270. package/pipeline/skills/shared/external/skill-creator/SKILL.md +12 -12
  271. package/pipeline/skills/shared/external/skill-creator/audit.md +21 -21
  272. package/pipeline/skills/shared/external/skill-creator/checklist.md +3 -3
  273. package/pipeline/skills/shared/external/skill-creator/examples.md +10 -10
  274. package/pipeline/skills/shared/external/skill-creator/label-check.md +17 -17
  275. package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +86 -50
  276. package/pipeline/skills/shared/external/skill-creator/template.md +9 -9
  277. package/pipeline/skills/shared/external/swift-concurrency-expert/SKILL.md +1 -1
  278. package/pipeline/skills/shared/external/swiftui-performance-audit/SKILL.md +1 -1
  279. package/pipeline/skills/shared/external/swiftui-ui-patterns/SKILL.md +1 -1
  280. package/pipeline/skills/shared/external/swiftui-view-refactor/SKILL.md +1 -1
  281. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -1
  282. package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -1
  283. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -1
  284. package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -1
  285. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -1
  286. package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -1
  287. package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -1
  288. package/pipeline/skills/shared/external/xcode-build-benchmark/schemas/build-benchmark.schema.json +9 -49
  289. package/pipeline/skills/skills-index.md +63 -59
  290. package/pipeline/scripts/smoke-plugin-validate.sh +0 -64
@@ -7,20 +7,30 @@
7
7
  # Phase 1; any entry in state.telemetry.mcpCalls[] with phase >= 2 is a
8
8
  # violation.
9
9
  #
10
- # Inputs
11
- # --state-file <path> Optional override (default: ~/.claude/state/run/latest.json)
10
+ # Two modes.
12
11
  #
13
- # Outcomes
14
- # - State file missing -> WARN, exit 0 (no run to check)
12
+ # CHECK MODE (--state-file <path>): audit that one state file.
15
13
  # - telemetry.mcpCalls field absent -> PASS, exit 0 (older run, no telemetry)
16
14
  # - All entries phase < 2 -> PASS, exit 0
17
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.
18
27
  #
19
28
  # stdout: human-readable summary only. stderr: empty in normal operation.
20
29
 
21
30
  set -euo pipefail
22
31
 
23
32
  STATE_FILE="${HOME}/.claude/state/run/latest.json"
33
+ STATE_FILE_EXPLICIT=0
24
34
 
25
35
  while [ "$#" -gt 0 ]; do
26
36
  case "$1" in
@@ -31,16 +41,19 @@ while [ "$#" -gt 0 ]; do
31
41
  exit 2
32
42
  fi
33
43
  STATE_FILE="$1"
44
+ STATE_FILE_EXPLICIT=1
34
45
  shift
35
46
  ;;
36
47
  --state-file=*)
37
48
  STATE_FILE="${1#--state-file=}"
49
+ STATE_FILE_EXPLICIT=1
38
50
  shift
39
51
  ;;
40
52
  -h|--help)
41
53
  echo "Usage: $0 [--state-file <path>]"
42
54
  echo ""
43
- echo "Default state file: ~/.claude/state/run/latest.json"
55
+ echo "No arguments: run the fixture self-test suite."
56
+ echo "Default state file when checking live: ~/.claude/state/run/latest.json"
44
57
  exit 0
45
58
  ;;
46
59
  *)
@@ -55,9 +68,75 @@ if ! command -v jq >/dev/null 2>&1; then
55
68
  exit 2
56
69
  fi
57
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 ─────────────────────────────────────────────────────────────
58
137
  if [ ! -f "$STATE_FILE" ]; then
59
- echo "WARN: state file not found at $STATE_FILE - no run to check"
60
- exit 0
138
+ echo "FAIL: state file not found at $STATE_FILE"
139
+ exit 2
61
140
  fi
62
141
 
63
142
  # Validate JSON parse first; corrupt state cannot be silently passed.
@@ -0,0 +1,103 @@
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
@@ -35,7 +35,7 @@ repo="$sandbox/repo"
35
35
  mkdir -p "$repo"
36
36
 
37
37
  # --- 1: pref off → silent exit 0 ---
38
- printf '{"global":{"perRepoMemory":false}}' > "$HOME/.claude/preferences.json"
38
+ printf '{"global":{"perRepoMemory":false}}' > "$HOME/.claude/multi-agent-preferences.json"
39
39
  out=$(bash "$LOAD" "$repo" 2>&1 || true)
40
40
  if [ -z "$out" ]; then
41
41
  record_pass "load exits silent when pref off"
@@ -44,7 +44,7 @@ else
44
44
  fi
45
45
 
46
46
  # --- 2: pref on + no memory → silent exit 0 ---
47
- printf '{"global":{"perRepoMemory":true}}' > "$HOME/.claude/preferences.json"
47
+ printf '{"global":{"perRepoMemory":true}}' > "$HOME/.claude/multi-agent-preferences.json"
48
48
  out=$(bash "$LOAD" "$repo" 2>&1 || true)
49
49
  if [ -z "$out" ]; then
50
50
  record_pass "load exits silent when pref on but no memory present"
@@ -21,6 +21,7 @@ SK_J="$ROOT/pipeline/skills/shared/core/multi-agent-review-jira/SKILL.md"
21
21
  SK_I="$ROOT/pipeline/skills/shared/core/multi-agent-review-issue/SKILL.md"
22
22
  DISPATCHER="$ROOT/pipeline/commands/multi-agent/SKILL.md"
23
23
  CONTRACT="$ROOT/pipeline/multi-agent-refs/cross-cli-contract.md"
24
+ CMD_COUNT=$(find "$ROOT/pipeline/commands/multi-agent" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')
24
25
  HELP="$ROOT/pipeline/commands/multi-agent/help/SKILL.md"
25
26
 
26
27
  PASS=0; FAIL=0
@@ -61,12 +62,12 @@ grep -Fqi "confirm" "$REF" && pass "ref has confirm-before-post gate" || fail "r
61
62
  grep -Fqi "rubric" "$REF" && pass "ref has readiness rubric" || fail "ref missing rubric"
62
63
 
63
64
  echo ""
64
- echo "→ 5. registration + count (38)"
65
+ echo "→ 5. registration + count (derived)"
65
66
  grep -Fq '| `review-jira ' "$DISPATCHER" && pass "dispatcher desc: review-jira" || fail "dispatcher desc missing review-jira"
66
67
  grep -Fq '| `review-issue ' "$DISPATCHER" && pass "dispatcher desc: review-issue" || fail "dispatcher desc missing review-issue"
67
68
  grep -Fq "review-jira/SKILL.md" "$DISPATCHER" && pass "dispatcher routing: review-jira" || fail "dispatcher routing missing review-jira"
68
69
  grep -Fq "review-issue/SKILL.md" "$DISPATCHER" && pass "dispatcher routing: review-issue" || fail "dispatcher routing missing review-issue"
69
- grep -Fq "## 1. Command Inventory (41 commands)" "$CONTRACT" && pass "contract count = 41" || fail "contract count wrong"
70
+ grep -Fq "## 1. Command Inventory ($CMD_COUNT commands)" "$CONTRACT" && pass "contract count = $CMD_COUNT (derived)" || fail "contract count wrong"
70
71
  grep -Eq '(^|[ ,])review-jira([ ,]|$)' "$CONTRACT" && pass "contract inventory has review-jira" || fail "contract missing review-jira"
71
72
  grep -Eq '(^|[ ,])review-issue([ ,]|$)' "$CONTRACT" && pass "contract inventory has review-issue" || fail "contract missing review-issue"
72
73
  grep -Fq "multi-agent:review-jira" "$HELP" && pass "help lists review-jira" || fail "help missing review-jira"
@@ -87,17 +87,31 @@ done
87
87
 
88
88
  # ──────────────────────────────────────────────────────────────────────────
89
89
  echo ""
90
- echo "→ 4. prefs.schema.json - keychainMapping has 12 services"
90
+ echo "→ 4. prefs.schema.json - keychainMapping covers every resolved service"
91
+ # A floor, not a pin: new credentials get added over time, and pinning the count
92
+ # made every addition a false failure. What matters is that the keys shipped
93
+ # flows resolve are all declared.
91
94
  KM_COUNT=$(node -e "
92
95
  const s = JSON.parse(require('fs').readFileSync('$PREFS_SCHEMA', 'utf8'));
93
96
  const km = s.properties.global.properties.keychainMapping.properties;
94
97
  console.log(Object.keys(km).length);
95
98
  " 2>/dev/null)
96
- EXPECTED_SERVICES=13 # 12 + legacy "bitbucket" alias
97
- if [ "$KM_COUNT" = "$EXPECTED_SERVICES" ] || [ "$KM_COUNT" = "12" ]; then
98
- pass "keychainMapping has $KM_COUNT services (≥12 expected)"
99
+ if [ "${KM_COUNT:-0}" -ge 12 ] 2>/dev/null; then
100
+ pass "keychainMapping declares $KM_COUNT services (≥12 expected)"
99
101
  else
100
- fail "keychainMapping has $KM_COUNT services, expected ≥12"
102
+ fail "keychainMapping declares ${KM_COUNT:-0} services, expected ≥12"
103
+ fi
104
+
105
+ MISSING_KEYS=$(node -e "
106
+ const s = JSON.parse(require('fs').readFileSync('$PREFS_SCHEMA', 'utf8'));
107
+ const km = Object.keys(s.properties.global.properties.keychainMapping.properties);
108
+ const required = ['jira','bitbucket_token','bitbucket_user','github','confluence','figma','figma_pat','figma_mcp','firebase','npm'];
109
+ console.log(required.filter((k) => !km.includes(k)).join(','));
110
+ " 2>/dev/null)
111
+ if [ -z "$MISSING_KEYS" ]; then
112
+ pass "every credential the pipeline resolves is declared"
113
+ else
114
+ fail "keychainMapping missing declared-and-used key(s): $MISSING_KEYS"
101
115
  fi
102
116
 
103
117
  # ──────────────────────────────────────────────────────────────────────────
@@ -59,7 +59,9 @@ fi
59
59
 
60
60
  # 4-9. Lifecycle on a temp project
61
61
  TMP=$(mktemp -d)
62
- cd "$TMP"
62
+ # A failed cd here would run the relative `rm -rf DerivedData` below in the
63
+ # caller's directory instead of the sandbox.
64
+ cd "$TMP" || { record_fail "cannot cd into sandbox $TMP"; exit 1; }
63
65
  git init --quiet
64
66
  echo "node_modules/" > .gitignore
65
67
  echo "hello" > a.txt
@@ -178,7 +180,7 @@ else
178
180
  record_fail "~/.claude/state is GONE after unsafe prune attempts"
179
181
  fi
180
182
 
181
- cd - >/dev/null
183
+ cd - >/dev/null || true
182
184
  rm -rf "$TMP"
183
185
 
184
186
  # 10-12. Prefs schema
@@ -4,20 +4,38 @@
4
4
  # `.github/workflows/` with the two dedicated external linters:
5
5
  #
6
6
  # - zizmor GitHub Actions security auditor (template injection,
7
- # credential persistence, unpinned actions, ...). Runs in
8
- # offline mode so the smoke never touches the network.
7
+ # credential persistence, cache poisoning, unpinned actions,
8
+ # excessive permissions, ...). Runs offline, no network.
9
9
  # - actionlint workflow correctness linter (expression syntax, matrix
10
10
  # typos, shellcheck of run: blocks, ...).
11
11
  #
12
- # Both tools are optional: each is skipped with a SKIP line when its binary
13
- # is not on PATH (billing-friendly CI runners install neither; run locally
14
- # via `brew install zizmor actionlint` for full coverage). Structural
15
- # baseline checks that need no external binary live in
16
- # smoke-ci-workflows.sh - this smoke is the deeper, tool-backed layer.
17
- #
18
12
  # Any finding from either tool fails the smoke.
19
13
  #
20
- # Exit codes: 0=pass or all tools skipped, 1=findings, 2=workflows missing
14
+ # WHY THIS IS STRICT BY DEFAULT
15
+ #
16
+ # Until now a missing binary produced a SKIP and the smoke exited 0, so it
17
+ # reported "0 passed, 0 failed, 2 skipped" on the dev machine AND on the CI
18
+ # runner - neither had the tools. ci-lite.yml justified not installing them by
19
+ # saying to run the smoke locally "where the tools exist", but they did not
20
+ # exist there either. The audit had therefore never run anywhere, while
21
+ # appearing as a green step. When it was finally run it found nine real issues,
22
+ # one of them high severity (a dependency cache restored into the job that
23
+ # signs and publishes the release tarball).
24
+ #
25
+ # So the failure mode is inverted: absent tooling is a failure, and skipping is
26
+ # something you opt into deliberately and locally.
27
+ #
28
+ # brew install zizmor actionlint # macOS
29
+ # pipx install zizmor && go install github.com/rhysd/actionlint/cmd/actionlint@latest
30
+ #
31
+ # WORKFLOW_AUDIT_ALLOW_MISSING=1 downgrades a missing binary back to a SKIP for
32
+ # a local run. It is ignored when CI is set: CI installs both, pinned, and a
33
+ # runner that cannot must fail loudly rather than pass quietly.
34
+ #
35
+ # Structural baseline checks that need no external binary live in
36
+ # smoke-ci-workflows.sh - this smoke is the deeper, tool-backed layer.
37
+ #
38
+ # Exit codes: 0=pass, 1=findings or missing tooling, 2=workflows missing
21
39
 
22
40
  set -uo pipefail
23
41
 
@@ -26,6 +44,10 @@ WF_DIR="$REPO_ROOT/.github/workflows"
26
44
 
27
45
  [ -d "$WF_DIR" ] || { echo "FAIL: .github/workflows/ missing"; exit 2; }
28
46
 
47
+ # In CI the escape hatch does not apply, whatever the environment says.
48
+ ALLOW_MISSING="${WORKFLOW_AUDIT_ALLOW_MISSING:-0}"
49
+ [ -n "${CI:-}" ] && ALLOW_MISSING=0
50
+
29
51
  PASS=0
30
52
  FAIL=0
31
53
  SKIPPED=0
@@ -33,6 +55,16 @@ pass() { PASS=$((PASS + 1)); echo " ✓ $1"; }
33
55
  fail() { FAIL=$((FAIL + 1)); echo " ✗ $1"; }
34
56
  skip() { SKIPPED=$((SKIPPED + 1)); echo " ↷ SKIP: $1"; }
35
57
 
58
+ # A tool that is not installed is a failure unless a local run opted out.
59
+ missing_tool() {
60
+ local tool="$1" install="$2"
61
+ if [ "$ALLOW_MISSING" = "1" ]; then
62
+ skip "$tool not on PATH, WORKFLOW_AUDIT_ALLOW_MISSING=1 ($install)"
63
+ else
64
+ fail "$tool not on PATH - the audit cannot run ($install)"
65
+ fi
66
+ }
67
+
36
68
  # ──────────────────────────────────────────────────────────────────────────
37
69
  echo "→ 1. zizmor (security audit, offline)"
38
70
  if command -v zizmor >/dev/null 2>&1; then
@@ -48,7 +80,7 @@ if command -v zizmor >/dev/null 2>&1; then
48
80
  echo "$OUT" | sed 's/^/ /'
49
81
  fi
50
82
  else
51
- skip "zizmor not on PATH (brew install zizmor)"
83
+ missing_tool zizmor "brew install zizmor"
52
84
  fi
53
85
 
54
86
  # ──────────────────────────────────────────────────────────────────────────
@@ -61,7 +93,7 @@ if command -v actionlint >/dev/null 2>&1; then
61
93
  echo "$OUT" | sed 's/^/ /'
62
94
  fi
63
95
  else
64
- skip "actionlint not on PATH (brew install actionlint)"
96
+ missing_tool actionlint "brew install actionlint"
65
97
  fi
66
98
 
67
99
  echo ""
@@ -93,21 +93,65 @@ else
93
93
  fi
94
94
 
95
95
  # 10. Concurrent writers don't corrupt state
96
+ #
97
+ # Was 5 writers, which lost an update roughly one run in five on a loaded
98
+ # machine and never on an idle one, so it read as a flaky test rather than the
99
+ # data-loss bug it was. 20 writers puts enough writers in the acquire window to
100
+ # make a regression show up on an idle machine too.
96
101
  STATE2="$TMPDIR/concurrent.json"
102
+ WRITERS=20
97
103
  echo '{"counter":0}' | node "$WRITER" "$STATE2"
98
- for i in 1 2 3 4 5; do
104
+ for i in $(seq 1 "$WRITERS"); do
99
105
  echo "{\"w$i\":$i}" | node "$WRITER" "$STATE2" &
100
106
  done
101
107
  wait
102
- # All 5 keys should be present (deep merge)
103
108
  MISSING=0
104
- for i in 1 2 3 4 5; do
109
+ for i in $(seq 1 "$WRITERS"); do
105
110
  grep -q "\"w$i\": $i" "$STATE2" || MISSING=$((MISSING+1))
106
111
  done
107
112
  if [ "$MISSING" -eq 0 ]; then
108
- pass "5 concurrent writers all persisted"
113
+ pass "$WRITERS concurrent writers all persisted"
109
114
  else
110
- fail "$MISSING concurrent writes lost"
115
+ fail "$MISSING of $WRITERS concurrent writes lost"
116
+ fi
117
+
118
+ # 11. A lock with no readable PID must not be treated as stale
119
+ #
120
+ # This is the exact shape of the bug above. The lock used to be created empty
121
+ # and filled on the next line, so a writer that arrived in between read an
122
+ # unparseable PID, called the lock stale, and deleted it while its owner was
123
+ # still working. Both then wrote and one update vanished.
124
+ #
125
+ # An empty lock now has to block until the staleness window elapses, so a
126
+ # writer given a 1s acquire budget must give up with exit 2 rather than help
127
+ # itself to the lock.
128
+ STATE3="$TMPDIR/emptylock.json"
129
+ echo '{"seed":1}' | node "$WRITER" "$STATE3"
130
+ : > "$STATE3.lock"
131
+ LOCK_RC=0
132
+ echo '{"stolen":true}' | WRITE_STATE_LOCK_TIMEOUT_MS=1000 WRITE_STATE_LOCK_STALE_MS=30000 \
133
+ node "$WRITER" "$STATE3" >/dev/null 2>&1 || LOCK_RC=$?
134
+ if [ "$LOCK_RC" -eq 2 ] && ! grep -q '"stolen"' "$STATE3"; then
135
+ pass "empty lock is not stolen (exit 2, state untouched)"
136
+ else
137
+ fail "empty lock was treated as stale (exit $LOCK_RC) - the live-lock race is back"
138
+ fi
139
+ rm -f "$STATE3.lock"
140
+
141
+ # 12. A lock older than the staleness window is still reclaimed
142
+ #
143
+ # The counterpart to 11: refusing to reclaim an unreadable PID must not mean a
144
+ # genuinely abandoned lock blocks forever.
145
+ STATE4="$TMPDIR/stalelock.json"
146
+ echo '{"seed":1}' | node "$WRITER" "$STATE4"
147
+ : > "$STATE4.lock"
148
+ STALE_RC=0
149
+ echo '{"recovered":true}' | WRITE_STATE_LOCK_TIMEOUT_MS=3000 WRITE_STATE_LOCK_STALE_MS=1 \
150
+ node "$WRITER" "$STATE4" >/dev/null 2>&1 || STALE_RC=$?
151
+ if [ "$STALE_RC" -eq 0 ] && grep -q '"recovered"' "$STATE4"; then
152
+ pass "abandoned lock past the staleness window is reclaimed"
153
+ else
154
+ fail "stale lock not reclaimed (exit $STALE_RC) - writers would wedge"
111
155
  fi
112
156
 
113
157
  echo ""
@@ -5,20 +5,20 @@
5
5
  "sourceExtensions": [".kt", ".java"],
6
6
  "testPathSuffixes": ["Test.kt", "Tests.kt", "Test.java"],
7
7
  "testPathContains": ["/src/test/", "/src/androidTest/", "/test/", "/tests/"],
8
- "excludePathGlobs": [
9
- "build/", ".gradle/", "generated/",
10
- "**/BuildConfig.java", "**/R.java"
11
- ],
8
+ "excludePathGlobs": ["build/", ".gradle/", "generated/", "**/BuildConfig.java", "**/R.java"],
12
9
  "publicApiPatterns": [
13
- {"id": "open_class", "regex": "\\bopen\\s+class\\s+([A-Z][A-Za-z0-9_]*)"},
14
- {"id": "open_fun", "regex": "\\bopen\\s+fun\\s+([a-z_][A-Za-z0-9_]*)"},
15
- {"id": "public_fun", "regex": "^\\s*(?:public\\s+)?fun\\s+([a-z_][A-Za-z0-9_]*)"},
16
- {"id": "public_class", "regex": "^\\s*(?:public\\s+)?class\\s+([A-Z][A-Za-z0-9_]*)"},
17
- {"id": "jvmstatic", "regex": "@JvmStatic\\b"},
18
- {"id": "interface", "regex": "^\\s*(?:public\\s+)?interface\\s+([A-Z][A-Za-z0-9_]*)"}
10
+ { "id": "open_class", "regex": "\\bopen\\s+class\\s+([A-Z][A-Za-z0-9_]*)" },
11
+ { "id": "open_fun", "regex": "\\bopen\\s+fun\\s+([a-z_][A-Za-z0-9_]*)" },
12
+ { "id": "public_fun", "regex": "^\\s*(?:public\\s+)?fun\\s+([a-z_][A-Za-z0-9_]*)" },
13
+ { "id": "public_class", "regex": "^\\s*(?:public\\s+)?class\\s+([A-Z][A-Za-z0-9_]*)" },
14
+ { "id": "jvmstatic", "regex": "@JvmStatic\\b" },
15
+ { "id": "interface", "regex": "^\\s*(?:public\\s+)?interface\\s+([A-Z][A-Za-z0-9_]*)" }
19
16
  ],
20
17
  "composablePatterns": [
21
- {"id": "composable_fun", "regex": "@Composable\\s+(?:public\\s+|internal\\s+)?fun\\s+([A-Z][A-Za-z0-9_]*)"}
18
+ {
19
+ "id": "composable_fun",
20
+ "regex": "@Composable\\s+(?:public\\s+|internal\\s+)?fun\\s+([A-Z][A-Za-z0-9_]*)"
21
+ }
22
22
  ],
23
23
  "expectedTestKindsForComposable": ["compose-ui-test"],
24
24
  "hint": "Cover @Composable functions with createComposeRule() + onNodeWithText/Tag/ContentDescription assertions; ViewModels with runTest + Turbine."
@@ -6,22 +6,27 @@
6
6
  "testPathSuffixes": ["Tests.swift", "Test.swift"],
7
7
  "testPathContains": ["/Tests/", "/UnitTests/", "/SnapshotTests/"],
8
8
  "excludePathGlobs": [
9
- "Pods/", "Carthage/", ".build/", "DerivedData/",
10
- "Generated/", "Sourcery/", "_Generated.swift",
9
+ "Pods/",
10
+ "Carthage/",
11
+ ".build/",
12
+ "DerivedData/",
13
+ "Generated/",
14
+ "Sourcery/",
15
+ "_Generated.swift",
11
16
  "Tests/Fixtures/"
12
17
  ],
13
18
  "publicApiPatterns": [
14
- {"id": "public_func", "regex": "\\bpublic\\s+func\\s+([A-Za-z_][A-Za-z0-9_]*)"},
15
- {"id": "public_class", "regex": "\\bpublic\\s+(?:final\\s+)?class\\s+([A-Z][A-Za-z0-9_]*)"},
16
- {"id": "public_struct", "regex": "\\bpublic\\s+struct\\s+([A-Z][A-Za-z0-9_]*)"},
17
- {"id": "public_enum", "regex": "\\bpublic\\s+enum\\s+([A-Z][A-Za-z0-9_]*)"},
18
- {"id": "public_proto", "regex": "\\bpublic\\s+protocol\\s+([A-Z][A-Za-z0-9_]*)"},
19
- {"id": "public_init", "regex": "\\bpublic\\s+init\\b"},
20
- {"id": "objc_export", "regex": "@objc(?:Members)?\\s+(?:public\\s+)?(?:func|class|var)"}
19
+ { "id": "public_func", "regex": "\\bpublic\\s+func\\s+([A-Za-z_][A-Za-z0-9_]*)" },
20
+ { "id": "public_class", "regex": "\\bpublic\\s+(?:final\\s+)?class\\s+([A-Z][A-Za-z0-9_]*)" },
21
+ { "id": "public_struct", "regex": "\\bpublic\\s+struct\\s+([A-Z][A-Za-z0-9_]*)" },
22
+ { "id": "public_enum", "regex": "\\bpublic\\s+enum\\s+([A-Z][A-Za-z0-9_]*)" },
23
+ { "id": "public_proto", "regex": "\\bpublic\\s+protocol\\s+([A-Z][A-Za-z0-9_]*)" },
24
+ { "id": "public_init", "regex": "\\bpublic\\s+init\\b" },
25
+ { "id": "objc_export", "regex": "@objc(?:Members)?\\s+(?:public\\s+)?(?:func|class|var)" }
21
26
  ],
22
27
  "swiftUiViewPatterns": [
23
- {"id": "view_struct", "regex": "struct\\s+([A-Z][A-Za-z0-9_]*)\\s*:\\s*View\\b"},
24
- {"id": "config_struct", "regex": "struct\\s+([A-Z][A-Za-z0-9_]*Configuration)\\b"}
28
+ { "id": "view_struct", "regex": "struct\\s+([A-Z][A-Za-z0-9_]*)\\s*:\\s*View\\b" },
29
+ { "id": "config_struct", "regex": "struct\\s+([A-Z][A-Za-z0-9_]*Configuration)\\b" }
25
30
  ],
26
31
  "expectedTestKindsForView": ["snapshot", "viewinspector", "unit"],
27
32
  "snapshotPathHints": ["SnapshotTests", "__Snapshots__"],
@@ -3,15 +3,27 @@
3
3
  "stack": "node",
4
4
  "description": "Node.js / TypeScript test gap rules. Sources in *.ts/*.tsx/*.js/*.mjs; tests in *.test.* / *.spec.* / __tests__/.",
5
5
  "sourceExtensions": [".ts", ".tsx", ".js", ".mjs", ".cjs"],
6
- "testPathSuffixes": [".test.ts", ".test.tsx", ".test.js", ".test.mjs", ".spec.ts", ".spec.tsx", ".spec.js", ".spec.mjs"],
7
- "testPathContains": ["/__tests__/", "/test/", "/tests/"],
8
- "excludePathGlobs": [
9
- "node_modules/", "dist/", "build/", ".next/", "coverage/",
10
- "*.d.ts"
6
+ "testPathSuffixes": [
7
+ ".test.ts",
8
+ ".test.tsx",
9
+ ".test.js",
10
+ ".test.mjs",
11
+ ".spec.ts",
12
+ ".spec.tsx",
13
+ ".spec.js",
14
+ ".spec.mjs"
11
15
  ],
16
+ "testPathContains": ["/__tests__/", "/test/", "/tests/"],
17
+ "excludePathGlobs": ["node_modules/", "dist/", "build/", ".next/", "coverage/", "*.d.ts"],
12
18
  "publicApiPatterns": [
13
- {"id": "named_export", "regex": "^export\\s+(?:async\\s+)?(?:function|class|const|let|interface|type|enum)\\s+([A-Za-z_$][A-Za-z0-9_$]*)"},
14
- {"id": "default_export", "regex": "^export\\s+default\\s+(?:async\\s+)?(?:function|class)\\s+([A-Za-z_$][A-Za-z0-9_$]*)"}
19
+ {
20
+ "id": "named_export",
21
+ "regex": "^export\\s+(?:async\\s+)?(?:function|class|const|let|interface|type|enum)\\s+([A-Za-z_$][A-Za-z0-9_$]*)"
22
+ },
23
+ {
24
+ "id": "default_export",
25
+ "regex": "^export\\s+default\\s+(?:async\\s+)?(?:function|class)\\s+([A-Za-z_$][A-Za-z0-9_$]*)"
26
+ }
15
27
  ],
16
28
  "hint": "Cover named exports with vitest/jest test('<name>', ...). React components: React Testing Library (render + screen + userEvent)."
17
29
  }
@@ -7,13 +7,19 @@
7
7
  "testPathPrefixes": ["test_"],
8
8
  "testPathContains": ["/tests/", "/test/"],
9
9
  "excludePathGlobs": [
10
- "__pycache__/", ".venv/", "venv/", ".tox/", "build/", "dist/", "*.egg-info/",
10
+ "__pycache__/",
11
+ ".venv/",
12
+ "venv/",
13
+ ".tox/",
14
+ "build/",
15
+ "dist/",
16
+ "*.egg-info/",
11
17
  "**/migrations/**"
12
18
  ],
13
19
  "publicApiPatterns": [
14
- {"id": "top_def", "regex": "^def\\s+([a-z_][a-z0-9_]*)"},
15
- {"id": "top_class", "regex": "^class\\s+([A-Z][A-Za-z0-9_]*)"},
16
- {"id": "async_def", "regex": "^async\\s+def\\s+([a-z_][a-z0-9_]*)"}
20
+ { "id": "top_def", "regex": "^def\\s+([a-z_][a-z0-9_]*)" },
21
+ { "id": "top_class", "regex": "^class\\s+([A-Z][A-Za-z0-9_]*)" },
22
+ { "id": "async_def", "regex": "^async\\s+def\\s+([a-z_][a-z0-9_]*)" }
17
23
  ],
18
24
  "hint": "Cover with pytest test_<symbol> style. Async with pytest-asyncio. Avoid name collision with private helpers (prefix _)."
19
25
  }