@soleur/opencode 0.0.0-stage → 1.0.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 (812) hide show
  1. package/.claude-plugin/agents.manifest.json +476 -0
  2. package/.claude-plugin/plugin.json +42 -0
  3. package/README.md +44 -2
  4. package/agents/community/.gitkeep +0 -0
  5. package/agents/engineering/cto.md +87 -0
  6. package/agents/engineering/design/ddd-architect.md +95 -0
  7. package/agents/engineering/discovery/agent-finder.md +214 -0
  8. package/agents/engineering/discovery/functional-discovery.md +182 -0
  9. package/agents/engineering/infra/infra-security.md +151 -0
  10. package/agents/engineering/infra/platform-strategist.md +109 -0
  11. package/agents/engineering/infra/terraform-architect.md +171 -0
  12. package/agents/engineering/prompt-engineer.md +37 -0
  13. package/agents/engineering/research/best-practices-researcher.md +115 -0
  14. package/agents/engineering/research/framework-docs-researcher.md +93 -0
  15. package/agents/engineering/research/git-history-analyzer.md +53 -0
  16. package/agents/engineering/research/learnings-researcher.md +285 -0
  17. package/agents/engineering/research/repo-research-analyst.md +118 -0
  18. package/agents/engineering/review/agent-native-reviewer.md +279 -0
  19. package/agents/engineering/review/architecture-strategist.md +61 -0
  20. package/agents/engineering/review/code-quality-analyst.md +92 -0
  21. package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
  22. package/agents/engineering/review/data-integrity-guardian.md +82 -0
  23. package/agents/engineering/review/data-migration-expert.md +98 -0
  24. package/agents/engineering/review/deployment-verification-agent.md +183 -0
  25. package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
  26. package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
  27. package/agents/engineering/review/legacy-code-expert.md +55 -0
  28. package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
  29. package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
  30. package/agents/engineering/review/performance-oracle.md +130 -0
  31. package/agents/engineering/review/security-sentinel.md +167 -0
  32. package/agents/engineering/review/semgrep-sast.md +36 -0
  33. package/agents/engineering/review/test-design-reviewer.md +150 -0
  34. package/agents/engineering/review/user-impact-reviewer.md +70 -0
  35. package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
  36. package/agents/finance/budget-analyst.md +29 -0
  37. package/agents/finance/cfo.md +44 -0
  38. package/agents/finance/financial-reporter.md +30 -0
  39. package/agents/finance/revenue-analyst.md +30 -0
  40. package/agents/legal/clo.md +115 -0
  41. package/agents/legal/legal-compliance-auditor.md +110 -0
  42. package/agents/legal/legal-document-generator.md +134 -0
  43. package/agents/marketing/analytics-analyst.md +25 -0
  44. package/agents/marketing/brand-architect.md +181 -0
  45. package/agents/marketing/cmo.md +76 -0
  46. package/agents/marketing/conversion-optimizer.md +19 -0
  47. package/agents/marketing/copywriter.md +20 -0
  48. package/agents/marketing/fact-checker.md +70 -0
  49. package/agents/marketing/growth-strategist.md +145 -0
  50. package/agents/marketing/paid-media-strategist.md +28 -0
  51. package/agents/marketing/pricing-strategist.md +17 -0
  52. package/agents/marketing/programmatic-seo-specialist.md +25 -0
  53. package/agents/marketing/retention-strategist.md +23 -0
  54. package/agents/marketing/seo-aeo-analyst.md +164 -0
  55. package/agents/operations/coo.md +50 -0
  56. package/agents/operations/ops-advisor.md +78 -0
  57. package/agents/operations/ops-provisioner.md +101 -0
  58. package/agents/operations/ops-research.md +52 -0
  59. package/agents/operations/service-automator.md +278 -0
  60. package/agents/product/business-validator.md +206 -0
  61. package/agents/product/competitive-intelligence.md +89 -0
  62. package/agents/product/cpo.md +63 -0
  63. package/agents/product/design/ux-design-lead.md +209 -0
  64. package/agents/product/spec-flow-analyzer.md +122 -0
  65. package/agents/sales/cro.md +44 -0
  66. package/agents/sales/deal-architect.md +29 -0
  67. package/agents/sales/outbound-strategist.md +29 -0
  68. package/agents/sales/pipeline-analyst.md +29 -0
  69. package/agents/support/cco.md +48 -0
  70. package/agents/support/community-manager.md +570 -0
  71. package/agents/support/ticket-triage.md +70 -0
  72. package/bin/soleur-opencode.js +29 -0
  73. package/commands/go.md +659 -0
  74. package/commands/help.md +273 -0
  75. package/commands/sync.md +977 -0
  76. package/commands/version.md +103 -0
  77. package/hooks/antigravity-hooks.json +84 -0
  78. package/hooks/antigravity-session-start.sh +15 -0
  79. package/hooks/bare-root-cwd-guard.sh +131 -0
  80. package/hooks/browser-snapshot-credential-guard.sh +200 -0
  81. package/hooks/codex-session-start.sh +9 -0
  82. package/hooks/compaction-state.sh +525 -0
  83. package/hooks/destructive-command-guard.sh +1387 -0
  84. package/hooks/devin-session-start.sh +90 -0
  85. package/hooks/ext-filter-detect.sh +171 -0
  86. package/hooks/hooks.json +133 -0
  87. package/hooks/lib/hook-tool-kind.sh +24 -0
  88. package/hooks/lib/log-rotation.sh +176 -0
  89. package/hooks/lib/shell-argv.pl +576 -0
  90. package/hooks/memory-backstop.sh +1114 -0
  91. package/hooks/operator-stage-approval.sh +391 -0
  92. package/hooks/stop-hook.sh +367 -0
  93. package/hooks/unkept-promise-hook.sh +383 -0
  94. package/hooks/version-banner.sh +127 -0
  95. package/hooks/welcome-hook.sh +50 -0
  96. package/install.js +108 -0
  97. package/lib/agent-registry.ts +154 -0
  98. package/lib/c4-canonical-cli.mjs +34 -0
  99. package/lib/c4-canonical.mjs +46 -0
  100. package/lib/c4-from-components.ts +554 -0
  101. package/lib/go-routing.ts +81 -0
  102. package/lib/grok-inspect-contract.ts +311 -0
  103. package/lib/harness-model-map.ts +101 -0
  104. package/lib/harness-parity.ts +726 -0
  105. package/lib/harness-tool-map.ts +180 -0
  106. package/lib/harness.ts +716 -0
  107. package/lib/heartbeat-live-reconcile.ts +1153 -0
  108. package/lib/heartbeat-manifest.ts +342 -0
  109. package/lib/kb-coverage.ts +252 -0
  110. package/lib/pr-merge-poll.ts +103 -0
  111. package/lib/workflow-fidelity.ts +388 -0
  112. package/opencode/INSTRUCTIONS.md +152 -0
  113. package/opencode/agents/engineering/cto.md +10 -0
  114. package/opencode/agents/engineering/design/ddd-architect.md +10 -0
  115. package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
  116. package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
  117. package/opencode/agents/engineering/infra/infra-security.md +10 -0
  118. package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
  119. package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
  120. package/opencode/agents/engineering/prompt-engineer.md +10 -0
  121. package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
  122. package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
  123. package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
  124. package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
  125. package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
  126. package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
  127. package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
  128. package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
  129. package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
  130. package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
  131. package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
  132. package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
  133. package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
  134. package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
  135. package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
  136. package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
  137. package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
  138. package/opencode/agents/engineering/review/performance-oracle.md +10 -0
  139. package/opencode/agents/engineering/review/security-sentinel.md +10 -0
  140. package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
  141. package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
  142. package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
  143. package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
  144. package/opencode/agents/finance/budget-analyst.md +10 -0
  145. package/opencode/agents/finance/cfo.md +10 -0
  146. package/opencode/agents/finance/financial-reporter.md +10 -0
  147. package/opencode/agents/finance/revenue-analyst.md +10 -0
  148. package/opencode/agents/legal/clo.md +10 -0
  149. package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
  150. package/opencode/agents/legal/legal-document-generator.md +10 -0
  151. package/opencode/agents/marketing/analytics-analyst.md +10 -0
  152. package/opencode/agents/marketing/brand-architect.md +10 -0
  153. package/opencode/agents/marketing/cmo.md +10 -0
  154. package/opencode/agents/marketing/conversion-optimizer.md +10 -0
  155. package/opencode/agents/marketing/copywriter.md +10 -0
  156. package/opencode/agents/marketing/fact-checker.md +10 -0
  157. package/opencode/agents/marketing/growth-strategist.md +10 -0
  158. package/opencode/agents/marketing/paid-media-strategist.md +10 -0
  159. package/opencode/agents/marketing/pricing-strategist.md +10 -0
  160. package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
  161. package/opencode/agents/marketing/retention-strategist.md +10 -0
  162. package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
  163. package/opencode/agents/operations/coo.md +10 -0
  164. package/opencode/agents/operations/ops-advisor.md +10 -0
  165. package/opencode/agents/operations/ops-provisioner.md +10 -0
  166. package/opencode/agents/operations/ops-research.md +10 -0
  167. package/opencode/agents/operations/service-automator.md +10 -0
  168. package/opencode/agents/product/business-validator.md +10 -0
  169. package/opencode/agents/product/competitive-intelligence.md +10 -0
  170. package/opencode/agents/product/cpo.md +10 -0
  171. package/opencode/agents/product/design/ux-design-lead.md +10 -0
  172. package/opencode/agents/product/spec-flow-analyzer.md +10 -0
  173. package/opencode/agents/sales/cro.md +10 -0
  174. package/opencode/agents/sales/deal-architect.md +10 -0
  175. package/opencode/agents/sales/outbound-strategist.md +10 -0
  176. package/opencode/agents/sales/pipeline-analyst.md +10 -0
  177. package/opencode/agents/support/cco.md +10 -0
  178. package/opencode/agents/support/community-manager.md +10 -0
  179. package/opencode/agents/support/ticket-triage.md +10 -0
  180. package/opencode/commands/go.md +10 -0
  181. package/opencode/commands/help.md +10 -0
  182. package/opencode/commands/sync.md +10 -0
  183. package/opencode/commands/version.md +11 -0
  184. package/opencode/opencode.json +8 -0
  185. package/opencode/plugin.ts +493 -0
  186. package/package.json +36 -4
  187. package/scripts/admin-merge-ready.sh +545 -0
  188. package/scripts/admin-merge-ready.test.sh +753 -0
  189. package/scripts/affected-tests.sh +908 -0
  190. package/scripts/affected-tests.test.sh +1797 -0
  191. package/scripts/alpha-metrics.sh +78 -0
  192. package/scripts/alpha-metrics.test.sh +115 -0
  193. package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
  194. package/scripts/audit-flag-flip.sh +107 -0
  195. package/scripts/check-red-on-main.sh +316 -0
  196. package/scripts/ci-head-verdict.sh +363 -0
  197. package/scripts/cloud-detect.sh +193 -0
  198. package/scripts/delegation-verify.sh +657 -0
  199. package/scripts/delegation-verify.test.sh +1424 -0
  200. package/scripts/deploy-arm.sh +475 -0
  201. package/scripts/domain-model-drift.sh +419 -0
  202. package/scripts/emit-decision.sh +144 -0
  203. package/scripts/emit-decision.test.sh +144 -0
  204. package/scripts/ext-filter-divergence-corpus.txt +15 -0
  205. package/scripts/ext-filter-divergence.sh +189 -0
  206. package/scripts/generate-c4-from-components.ts +442 -0
  207. package/scripts/grok-fidelity-gate.sh +68 -0
  208. package/scripts/grok-pre-push-gate.sh +232 -0
  209. package/scripts/harness-discovery-smoke.ts +906 -0
  210. package/scripts/harness-parity-census.ts +92 -0
  211. package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
  212. package/scripts/install-opencode-adapter.ts +520 -0
  213. package/scripts/lib/affected-tests/common.sh +365 -0
  214. package/scripts/lib/affected-tests/go.sh +157 -0
  215. package/scripts/lib/affected-tests/js.sh +188 -0
  216. package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
  217. package/scripts/lib/affected-tests/python.sh +185 -0
  218. package/scripts/lib/context-sanitize.sh +49 -0
  219. package/scripts/lib/domain-model-lib.sh +180 -0
  220. package/scripts/lib/ext-filter-grammar.tsv +34 -0
  221. package/scripts/lib/operator-script.sh +1236 -0
  222. package/scripts/lib/proc.sh +580 -0
  223. package/scripts/lib/session-state.sh +748 -0
  224. package/scripts/lib/tmp-classify.sh +843 -0
  225. package/scripts/lib/wrapper-boundary.sh +47 -0
  226. package/scripts/lint-ratchet.py +1547 -0
  227. package/scripts/monitor-pr-checks.sh +441 -0
  228. package/scripts/pipeline-tally.sh +756 -0
  229. package/scripts/pipeline-tally.test.sh +740 -0
  230. package/scripts/plugin-version.sh +528 -0
  231. package/scripts/precommit-guard.sh +113 -0
  232. package/scripts/precommit-guard.test.sh +254 -0
  233. package/scripts/reconcile-live-heartbeats.ts +705 -0
  234. package/scripts/render-c4-model.sh +217 -0
  235. package/scripts/resolve-git-root.sh +55 -0
  236. package/scripts/resolve-regenerable-conflicts.sh +363 -0
  237. package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
  238. package/scripts/ruff-requirements.txt +22 -0
  239. package/scripts/run-in-pid-namespace.sh +98 -0
  240. package/scripts/run-in-pid-namespace.test.sh +290 -0
  241. package/scripts/scan-ancestor-signal-helpers.py +556 -0
  242. package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
  243. package/scripts/setup-ralph-loop.sh +238 -0
  244. package/scripts/sync-cursor-name-map.ts +316 -0
  245. package/scripts/sync-grok-agent-compat.ts +188 -0
  246. package/scripts/sync-npm-package.ts +178 -0
  247. package/scripts/sync-opencode-adapter.ts +479 -0
  248. package/scripts/sync-pr-behind.sh +534 -0
  249. package/scripts/sync-pr-behind.test.sh +252 -0
  250. package/scripts/taste-profile-update.sh +180 -0
  251. package/scripts/taste-profile-update.test.sh +167 -0
  252. package/scripts/write-budget-marker.sh +23 -0
  253. package/scripts/write-kb-coverage.ts +113 -0
  254. package/skills/admin-ip-refresh/SKILL.md +81 -0
  255. package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
  256. package/skills/agent-browser/SKILL.md +698 -0
  257. package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
  258. package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
  259. package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
  260. package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
  261. package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
  262. package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
  263. package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
  264. package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
  265. package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
  266. package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
  267. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
  268. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
  269. package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
  270. package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
  271. package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
  272. package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
  273. package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
  274. package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
  275. package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
  276. package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
  277. package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
  278. package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
  279. package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
  280. package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
  281. package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
  282. package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
  283. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
  284. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
  285. package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
  286. package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
  287. package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
  288. package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
  289. package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
  290. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
  291. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
  292. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
  293. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
  294. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
  295. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
  296. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
  297. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
  298. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
  299. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
  300. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
  301. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
  302. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
  303. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
  304. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
  305. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
  306. package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
  307. package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
  308. package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
  309. package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
  310. package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
  311. package/skills/agent-native-architecture/SKILL.md +479 -0
  312. package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
  313. package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
  314. package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
  315. package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
  316. package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
  317. package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
  318. package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
  319. package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
  320. package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
  321. package/skills/agent-native-architecture/references/product-implications.md +478 -0
  322. package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
  323. package/skills/agent-native-architecture/references/self-modification.md +294 -0
  324. package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
  325. package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
  326. package/skills/agent-native-audit/SKILL.md +300 -0
  327. package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
  328. package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
  329. package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
  330. package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
  331. package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
  332. package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
  333. package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
  334. package/skills/architecture/SKILL.md +436 -0
  335. package/skills/architecture/references/adr-template.md +154 -0
  336. package/skills/architecture/references/likec4-reference.md +136 -0
  337. package/skills/architecture/references/nfr-reference.md +126 -0
  338. package/skills/archive-kb/SKILL.md +86 -0
  339. package/skills/archive-kb/scripts/archive-kb.sh +300 -0
  340. package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
  341. package/skills/atdd-developer/SKILL.md +65 -0
  342. package/skills/brainstorm/SKILL.md +701 -0
  343. package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
  344. package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
  345. package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
  346. package/skills/brainstorm/references/ui-surface-terms.md +42 -0
  347. package/skills/brainstorm-techniques/SKILL.md +231 -0
  348. package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
  349. package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
  350. package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
  351. package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
  352. package/skills/campaign-calendar/SKILL.md +145 -0
  353. package/skills/cf-token-scope/SKILL.md +164 -0
  354. package/skills/cf-token-scope/references/widen-playbook.md +114 -0
  355. package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
  356. package/skills/changelog/SKILL.md +142 -0
  357. package/skills/code-to-prd/SKILL.md +66 -0
  358. package/skills/code-to-prd/references/banner-template.md +12 -0
  359. package/skills/code-to-prd/references/prd-template.md +41 -0
  360. package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
  361. package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
  362. package/skills/code-to-prd/test/fixture/.env.example +7 -0
  363. package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
  364. package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
  365. package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
  366. package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
  367. package/skills/code-to-prd/test/fixture/package.json +6 -0
  368. package/skills/cohort-status/SKILL.md +62 -0
  369. package/skills/community/SKILL.md +210 -0
  370. package/skills/community/scripts/bsky-community.sh +644 -0
  371. package/skills/community/scripts/bsky-setup.sh +631 -0
  372. package/skills/community/scripts/community-router.sh +93 -0
  373. package/skills/community/scripts/discord-community.sh +487 -0
  374. package/skills/community/scripts/discord-setup.sh +784 -0
  375. package/skills/community/scripts/github-community.sh +706 -0
  376. package/skills/community/scripts/hn-community.sh +233 -0
  377. package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
  378. package/skills/community/scripts/linkedin-community.sh +750 -0
  379. package/skills/community/scripts/linkedin-setup.sh +758 -0
  380. package/skills/community/scripts/x-community.sh +890 -0
  381. package/skills/community/scripts/x-setup.sh +713 -0
  382. package/skills/community/test/community-argv.test.sh +2922 -0
  383. package/skills/community/test/github-community.test.sh +981 -0
  384. package/skills/competitive-analysis/SKILL.md +69 -0
  385. package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
  386. package/skills/compound/SKILL.md +673 -0
  387. package/skills/compound/scripts/token-efficiency-report.sh +334 -0
  388. package/skills/compound/test/phase-16.test.sh +379 -0
  389. package/skills/compound-capture/SKILL.md +791 -0
  390. package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
  391. package/skills/compound-capture/assets/resolution-template.md +116 -0
  392. package/skills/compound-capture/references/yaml-schema.md +67 -0
  393. package/skills/compound-capture/schema.yaml +181 -0
  394. package/skills/constraint-scaffold/SKILL.md +175 -0
  395. package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
  396. package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
  397. package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
  398. package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
  399. package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
  400. package/skills/constraint-scaffold/references/shared-runner.template +135 -0
  401. package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
  402. package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
  403. package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
  404. package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
  405. package/skills/constraint-scaffold/test/generator.test.sh +93 -0
  406. package/skills/constraint-scaffold/test/parity.test.sh +285 -0
  407. package/skills/content-writer/SKILL.md +242 -0
  408. package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
  409. package/skills/cron-delete/SKILL.md +56 -0
  410. package/skills/cron-list/SKILL.md +54 -0
  411. package/skills/deepen-plan/SKILL.md +937 -0
  412. package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
  413. package/skills/deploy/SKILL.md +115 -0
  414. package/skills/deploy/references/hetzner-setup.md +65 -0
  415. package/skills/deploy/scripts/deploy.sh +37 -0
  416. package/skills/deploy-docs/SKILL.md +95 -0
  417. package/skills/dhh-rails-style/SKILL.md +203 -0
  418. package/skills/dhh-rails-style/references/architecture.md +717 -0
  419. package/skills/dhh-rails-style/references/controllers.md +323 -0
  420. package/skills/dhh-rails-style/references/frontend.md +556 -0
  421. package/skills/dhh-rails-style/references/gems.md +316 -0
  422. package/skills/dhh-rails-style/references/models.md +388 -0
  423. package/skills/dhh-rails-style/references/testing.md +354 -0
  424. package/skills/discord-content/SKILL.md +126 -0
  425. package/skills/docs-site/SKILL.md +214 -0
  426. package/skills/drain-labeled-backlog/SKILL.md +203 -0
  427. package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
  428. package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
  429. package/skills/drain-prs/SKILL.md +142 -0
  430. package/skills/drain-prs/scripts/triage-prs.sh +192 -0
  431. package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
  432. package/skills/dspy-ruby/SKILL.md +634 -0
  433. package/skills/dspy-ruby/assets/config-template.rb +359 -0
  434. package/skills/dspy-ruby/assets/module-template.rb +326 -0
  435. package/skills/dspy-ruby/assets/signature-template.rb +143 -0
  436. package/skills/dspy-ruby/references/core-concepts.md +268 -0
  437. package/skills/dspy-ruby/references/optimization.md +623 -0
  438. package/skills/dspy-ruby/references/providers.md +335 -0
  439. package/skills/eval-harness/README.md +194 -0
  440. package/skills/eval-harness/SKILL.md +157 -0
  441. package/skills/eval-harness/enums/go-routes.json +13 -0
  442. package/skills/eval-harness/enums/incident-threshold.json +5 -0
  443. package/skills/eval-harness/enums/lane.json +5 -0
  444. package/skills/eval-harness/enums/tool-selection.json +20 -0
  445. package/skills/eval-harness/enums/triage-levels.json +5 -0
  446. package/skills/eval-harness/gated-skills.json +34 -0
  447. package/skills/eval-harness/models.generated.json +5 -0
  448. package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
  449. package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
  450. package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
  451. package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
  452. package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
  453. package/skills/eval-harness/prompts/go-baseline.txt +6 -0
  454. package/skills/eval-harness/prompts/go-skill.txt +21 -0
  455. package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
  456. package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
  457. package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
  458. package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
  459. package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
  460. package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
  461. package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
  462. package/skills/eval-harness/prompts/triage-skill.txt +10 -0
  463. package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
  464. package/skills/eval-harness/scripts/extract-block.cjs +87 -0
  465. package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
  466. package/skills/eval-harness/scripts/gen-models.sh +41 -0
  467. package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
  468. package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
  469. package/skills/eval-harness/scripts/parse-label.cjs +93 -0
  470. package/skills/eval-harness/scripts/verdict.cjs +132 -0
  471. package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
  472. package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
  473. package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
  474. package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
  475. package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
  476. package/skills/eval-harness/test/eval-gate.test.sh +132 -0
  477. package/skills/eval-harness/test/extract-block.test.sh +87 -0
  478. package/skills/eval-harness/test/gate-classification.test.sh +77 -0
  479. package/skills/eval-harness/test/gen-models.test.sh +75 -0
  480. package/skills/eval-harness/test/measure-classification.test.sh +74 -0
  481. package/skills/eval-harness/test/parse-label.test.sh +129 -0
  482. package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
  483. package/skills/eval-harness/test/verdict.test.sh +123 -0
  484. package/skills/every-style-editor/SKILL.md +142 -0
  485. package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
  486. package/skills/feature-tweet/SKILL.md +199 -0
  487. package/skills/feature-video/SKILL.md +412 -0
  488. package/skills/feature-video/scripts/check_deps.sh +281 -0
  489. package/skills/file-todos/SKILL.md +267 -0
  490. package/skills/file-todos/assets/todo-template.md +166 -0
  491. package/skills/fix-issue/SKILL.md +278 -0
  492. package/skills/fix-issue/references/agent-authored-exclusion.md +118 -0
  493. package/skills/fix-issue/references/exclude-label-jq-snippet.md +96 -0
  494. package/skills/flag-bootstrap/SETUP.md +142 -0
  495. package/skills/flag-create/SKILL.md +145 -0
  496. package/skills/flag-create/scripts/create.sh +273 -0
  497. package/skills/flag-delete/SKILL.md +167 -0
  498. package/skills/flag-delete/scripts/delete.sh +288 -0
  499. package/skills/flag-list/SKILL.md +99 -0
  500. package/skills/flag-list/scripts/list.sh +275 -0
  501. package/skills/flag-set-role/SKILL.md +229 -0
  502. package/skills/flag-set-role/scripts/flip.sh +774 -0
  503. package/skills/frontend-anti-slop/SKILL.md +106 -0
  504. package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
  505. package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
  506. package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
  507. package/skills/frontend-design/SKILL.md +77 -0
  508. package/skills/gdpr-gate/NOTICE +136 -0
  509. package/skills/gdpr-gate/SKILL.md +341 -0
  510. package/skills/gdpr-gate/references/fields.md +160 -0
  511. package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
  512. package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
  513. package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
  514. package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
  515. package/skills/gdpr-gate/references/layers/frontend.md +249 -0
  516. package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
  517. package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
  518. package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
  519. package/skills/gdpr-gate/references/legal-consent.md +140 -0
  520. package/skills/gdpr-gate/references/non-negotiables.md +60 -0
  521. package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
  522. package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
  523. package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
  524. package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
  525. package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
  526. package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
  527. package/skills/gemini-imagegen/SKILL.md +283 -0
  528. package/skills/gemini-imagegen/requirements.txt +5 -0
  529. package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
  530. package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
  531. package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
  532. package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
  533. package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
  534. package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
  535. package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
  536. package/skills/git-worktree/SKILL.md +534 -0
  537. package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
  538. package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
  539. package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
  540. package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
  541. package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
  542. package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
  543. package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
  544. package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
  545. package/skills/go/SKILL.md +54 -0
  546. package/skills/growth/SKILL.md +164 -0
  547. package/skills/harvest-debt/SKILL.md +78 -0
  548. package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
  549. package/skills/heal-skill/SKILL.md +173 -0
  550. package/skills/help/SKILL.md +46 -0
  551. package/skills/incident/NOTICE +39 -0
  552. package/skills/incident/SKILL.md +395 -0
  553. package/skills/incident/scripts/dry-run.sh +448 -0
  554. package/skills/incident/scripts/redact-engine.py +412 -0
  555. package/skills/incident/scripts/redact-sentinel.sh +44 -0
  556. package/skills/incident/templates/pir.md +139 -0
  557. package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
  558. package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
  559. package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
  560. package/skills/incident/test/fixtures/positive-corpus.md +87 -0
  561. package/skills/incident/test/redact-sentinel.test.sh +1420 -0
  562. package/skills/invoice/SKILL.md +206 -0
  563. package/skills/kb-glossary/SKILL.md +71 -0
  564. package/skills/kb-glossary/references/glossary-format.md +111 -0
  565. package/skills/kb-glossary/references/rejected-request-register.md +115 -0
  566. package/skills/kb-search/SKILL.md +276 -0
  567. package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
  568. package/skills/legal-audit/SKILL.md +161 -0
  569. package/skills/legal-generate/NOTICE +242 -0
  570. package/skills/legal-generate/SKILL.md +244 -0
  571. package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
  572. package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
  573. package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
  574. package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
  575. package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
  576. package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
  577. package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
  578. package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
  579. package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
  580. package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
  581. package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
  582. package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
  583. package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
  584. package/skills/linear-fetch/SKILL.md +247 -0
  585. package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
  586. package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
  587. package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
  588. package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
  589. package/skills/linear-fetch/test/parity.test.sh +112 -0
  590. package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
  591. package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
  592. package/skills/merge-pr/SKILL.md +668 -0
  593. package/skills/model-launch-review/SKILL.md +255 -0
  594. package/skills/model-launch-review/scripts/audit-models.sh +501 -0
  595. package/skills/one-shot/SKILL.md +316 -0
  596. package/skills/one-shot/references/token-discipline.md +74 -0
  597. package/skills/operator-bootstrap/SKILL.md +412 -0
  598. package/skills/operator-bootstrap/template.sh +439 -0
  599. package/skills/operator-digest/SKILL.md +287 -0
  600. package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
  601. package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
  602. package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
  603. package/skills/operator-rephrase/SKILL.md +98 -0
  604. package/skills/pencil-setup/SKILL.md +201 -0
  605. package/skills/pencil-setup/scripts/check_deps.sh +556 -0
  606. package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
  607. package/skills/pencil-setup/scripts/package-lock.json +1172 -0
  608. package/skills/pencil-setup/scripts/package.json +14 -0
  609. package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
  610. package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
  611. package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
  612. package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
  613. package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
  614. package/skills/plan/SKILL.md +1140 -0
  615. package/skills/plan/references/differential-migration-testing.md +185 -0
  616. package/skills/plan/references/plan-community-discovery.md +42 -0
  617. package/skills/plan/references/plan-founder-check.md +137 -0
  618. package/skills/plan/references/plan-functional-overlap.md +25 -0
  619. package/skills/plan/references/plan-host-fanout.md +51 -0
  620. package/skills/plan/references/plan-issue-templates.md +779 -0
  621. package/skills/plan/references/plan-network-outage-checklist.md +131 -0
  622. package/skills/plan/references/plan-scope-check.md +126 -0
  623. package/skills/plan/references/plan-sharp-edges.md +325 -0
  624. package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
  625. package/skills/plan/references/webapp-cwv-observability.md +143 -0
  626. package/skills/plan-review/SKILL.md +91 -0
  627. package/skills/plan-review/lib/named-panel.mjs +58 -0
  628. package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
  629. package/skills/postmerge/SKILL.md +642 -0
  630. package/skills/postmerge/references/deploy-status-debugging.md +138 -0
  631. package/skills/postmerge/references/graceful-degradation.md +20 -0
  632. package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
  633. package/skills/preflight/SKILL.md +1553 -0
  634. package/skills/preflight/references/check-13-founder-check.md +286 -0
  635. package/skills/preflight/scripts/founder-check.py +1640 -0
  636. package/skills/preflight/scripts/parse-form-a.awk +107 -0
  637. package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
  638. package/skills/product-roadmap/SKILL.md +357 -0
  639. package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
  640. package/skills/provision-cloudflare/SKILL.md +67 -0
  641. package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
  642. package/skills/provision-doppler/SKILL.md +58 -0
  643. package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
  644. package/skills/provision-github/SKILL.md +59 -0
  645. package/skills/provision-github/scripts/provision-github.sh +300 -0
  646. package/skills/provision-hetzner/SKILL.md +76 -0
  647. package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
  648. package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
  649. package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
  650. package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
  651. package/skills/qa/SKILL.md +263 -0
  652. package/skills/qa/references/browser-preflight.md +22 -0
  653. package/skills/questionnaire-generate/SKILL.md +328 -0
  654. package/skills/questionnaire-generate/references/questionnaire.template +60 -0
  655. package/skills/rclone/SKILL.md +162 -0
  656. package/skills/rclone/scripts/check_setup.sh +63 -0
  657. package/skills/release-announce/SKILL.md +65 -0
  658. package/skills/release-docs/SKILL.md +92 -0
  659. package/skills/reproduce-bug/SKILL.md +358 -0
  660. package/skills/resolve-debt/SKILL.md +85 -0
  661. package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
  662. package/skills/resolve-parallel/SKILL.md +45 -0
  663. package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
  664. package/skills/resolve-pr-parallel/SKILL.md +68 -0
  665. package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
  666. package/skills/resolve-todo-parallel/SKILL.md +57 -0
  667. package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
  668. package/skills/review/SKILL.md +1621 -0
  669. package/skills/review/references/anti-slop-scanner-hook.md +40 -0
  670. package/skills/review/references/defect-classes-migrations.md +9 -0
  671. package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
  672. package/skills/review/references/review-e2e-testing.md +116 -0
  673. package/skills/review/references/review-todo-structure.md +165 -0
  674. package/skills/review/references/risk-tier-and-fix-rounds.md +95 -0
  675. package/skills/review/references/semgrep-custom-rules.yaml +174 -0
  676. package/skills/review/references/ultrathink-phases.md +98 -0
  677. package/skills/review/references/wfs.md +6 -0
  678. package/skills/review/scripts/emit-review-trailer.sh +499 -0
  679. package/skills/review/scripts/ensure-semgrep.sh +99 -0
  680. package/skills/review/scripts/fix-round-seats.sh +197 -0
  681. package/skills/review/test/emit-review-trailer.test.sh +387 -0
  682. package/skills/review/test/ensure-semgrep.test.sh +111 -0
  683. package/skills/review/test/fix-round-seats.test.sh +237 -0
  684. package/skills/review/workflows/README.md +147 -0
  685. package/skills/review/workflows/review.workflow.js +667 -0
  686. package/skills/schedule/SKILL.md +796 -0
  687. package/skills/seo-aeo/SKILL.md +139 -0
  688. package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
  689. package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
  690. package/skills/ship/SKILL.md +3022 -0
  691. package/skills/ship/references/adr-ordinal-collision.md +47 -0
  692. package/skills/ship/references/affected-gate-contract.md +115 -0
  693. package/skills/ship/references/ci-run-diagnosis.md +29 -0
  694. package/skills/ship/references/ci-workflow-authoring.md +68 -0
  695. package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
  696. package/skills/ship/references/cto-ruling-armed-push.md +5 -0
  697. package/skills/ship/references/decision-challenges-render.md +71 -0
  698. package/skills/ship/references/detached-battery-verdict.md +13 -0
  699. package/skills/ship/references/feature-tweet-draft.md +42 -0
  700. package/skills/ship/references/followthrough-issue-body-template.md +47 -0
  701. package/skills/ship/references/followthrough-stub-template.sh +92 -0
  702. package/skills/ship/references/merge-queue-dequeue.md +39 -0
  703. package/skills/ship/references/operator-holds.md +149 -0
  704. package/skills/ship/references/pipeline-tally-render.md +43 -0
  705. package/skills/ship/references/pr-overlap.md +153 -0
  706. package/skills/ship/references/queue-mode.md +7 -0
  707. package/skills/ship/references/ready-run-wait.md +36 -0
  708. package/skills/ship/references/red-on-main-quarantine.md +110 -0
  709. package/skills/ship/references/register-update-pr-pattern.md +88 -0
  710. package/skills/ship/references/settle-then-admin-merge.md +130 -0
  711. package/skills/ship/references/supabase-column-probe.md +13 -0
  712. package/skills/ship/scripts/auto-close-scan.sh +76 -0
  713. package/skills/ship/scripts/battery-owed.sh +403 -0
  714. package/skills/ship/scripts/hold-gate-eval.sh +410 -0
  715. package/skills/ship/scripts/net-issue-flow.sh +665 -0
  716. package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
  717. package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
  718. package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
  719. package/skills/skill-creator/SKILL.md +299 -0
  720. package/skills/skill-creator/references/api-security.md +248 -0
  721. package/skills/skill-creator/references/authoring-levers.md +110 -0
  722. package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
  723. package/skills/skill-creator/references/best-practices.md +431 -0
  724. package/skills/skill-creator/references/common-patterns.md +639 -0
  725. package/skills/skill-creator/references/core-principles.md +193 -0
  726. package/skills/skill-creator/references/executable-code.md +188 -0
  727. package/skills/skill-creator/references/iteration-and-testing.md +509 -0
  728. package/skills/skill-creator/references/official-spec.md +193 -0
  729. package/skills/skill-creator/references/recommended-structure.md +180 -0
  730. package/skills/skill-creator/references/skill-structure.md +396 -0
  731. package/skills/skill-creator/references/using-scripts.md +122 -0
  732. package/skills/skill-creator/references/using-templates.md +120 -0
  733. package/skills/skill-creator/references/workflows-and-validation.md +541 -0
  734. package/skills/skill-creator/scripts/init_skill.py +322 -0
  735. package/skills/skill-creator/scripts/package_skill.py +110 -0
  736. package/skills/skill-creator/scripts/quick_validate.py +64 -0
  737. package/skills/skill-creator/templates/router-skill.md +73 -0
  738. package/skills/skill-creator/templates/simple-skill.md +34 -0
  739. package/skills/skill-creator/workflows/add-reference.md +96 -0
  740. package/skills/skill-creator/workflows/add-script.md +104 -0
  741. package/skills/skill-creator/workflows/add-template.md +83 -0
  742. package/skills/skill-creator/workflows/add-workflow.md +128 -0
  743. package/skills/skill-creator/workflows/audit-skill.md +159 -0
  744. package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
  745. package/skills/skill-creator/workflows/create-new-skill.md +207 -0
  746. package/skills/skill-creator/workflows/get-guidance.md +134 -0
  747. package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
  748. package/skills/skill-creator/workflows/verify-skill.md +226 -0
  749. package/skills/skill-security-scan/SKILL.md +145 -0
  750. package/skills/skill-security-scan/references/disclaimer.md +21 -0
  751. package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
  752. package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
  753. package/skills/skill-security-scan/references/override-mechanism.md +171 -0
  754. package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
  755. package/skills/skill-security-scan/references/regex-patterns.md +136 -0
  756. package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
  757. package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
  758. package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
  759. package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
  760. package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
  761. package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
  762. package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
  763. package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
  764. package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
  765. package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
  766. package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
  767. package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
  768. package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
  769. package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
  770. package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
  771. package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
  772. package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
  773. package/skills/skill-security-scan/scripts/lib.sh +197 -0
  774. package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
  775. package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
  776. package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
  777. package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
  778. package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
  779. package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
  780. package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
  781. package/skills/social-distribute/SKILL.md +485 -0
  782. package/skills/spec-templates/SKILL.md +274 -0
  783. package/skills/sync/SKILL.md +49 -0
  784. package/skills/test-browser/SKILL.md +464 -0
  785. package/skills/test-browser/references/feature-map.md +161 -0
  786. package/skills/test-fix-loop/SKILL.md +159 -0
  787. package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
  788. package/skills/triage/SKILL.md +402 -0
  789. package/skills/trigger-cron/SKILL.md +136 -0
  790. package/skills/trigger-cron/scripts/trigger.sh +198 -0
  791. package/skills/user-set-role/SKILL.md +147 -0
  792. package/skills/user-set-role/scripts/set-role.sh +218 -0
  793. package/skills/user-story-writer/SKILL.md +77 -0
  794. package/skills/ux-audit/SKILL.md +251 -0
  795. package/skills/ux-audit/references/finding.schema.json +62 -0
  796. package/skills/ux-audit/references/route-list.yaml +95 -0
  797. package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
  798. package/skills/ux-audit/scripts/bot-signin.ts +135 -0
  799. package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
  800. package/skills/work/SKILL.md +1479 -0
  801. package/skills/work/references/end-of-work-resume.md +7 -0
  802. package/skills/work/references/hook-test-main-cwd.md +24 -0
  803. package/skills/work/references/key-principles.md +83 -0
  804. package/skills/work/references/property-testing.md +122 -0
  805. package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
  806. package/skills/work/references/work-agent-teams.md +97 -0
  807. package/skills/work/references/work-lifecycle-parallel.md +147 -0
  808. package/skills/work/references/work-lockfile-bumps.md +97 -0
  809. package/skills/work/references/work-scratch-sandboxes.md +212 -0
  810. package/skills/work/references/work-subagent-fanout.md +61 -0
  811. package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
  812. package/skills/xcode-test/SKILL.md +360 -0
@@ -0,0 +1,1338 @@
1
+ #!/usr/bin/env bash
2
+ # Install-time bite-proof + README/pointer coverage for constraint-scaffold.sh (#8288, ADR-071
3
+ # amendment 2026-09-19). Proves that every scaffold run that reports success has observed the
4
+ # emitted gate, on a detached worktree of the target's HEAD, go pass -> fail -> pass — naming
5
+ # `no-client-to-server-secret` on the direct probe edge AND `no-client-to-server-secret-transitive`
6
+ # on the one-hop probe edge — and that a run which cannot prove it leaves the founder's tree as it
7
+ # found it. Also covers the append-once README block and the agent-instructions pointer, which are
8
+ # the LAST writes of a default-mode run (after the bite), never a refresh-mode write.
9
+ #
10
+ # Suite order (test-design): source pins -> stub-driven -> locate-or-install -> toolchain probe ->
11
+ # real. An `npm install` failure in the scripts shard therefore cannot `exit 2` before the
12
+ # toolchain-free segments have run and carried their own floor.
13
+ #
14
+ # ZERO PRODUCTION SEAMS. The script honours no CONSTRAINT_SCAFFOLD_TEST_* variable (a pin below
15
+ # asserts the literal is absent). Every failure arm is driven by a FIXTURE-OWNED stub `depcruise`
16
+ # reached through the fixture's `apps/web-platform/node_modules` symlink. The stub implements the
17
+ # three-branch table from the plan (D6): `--output-type baseline` -> `[]`; `--output-type json` ->
18
+ # `{"modules":[]}`; `--output-type err` -> the probe arm iff
19
+ # `components/__constraint_scaffold_bite_probe__/direct.tsx` exists in cwd (the runner `cd`s to
20
+ # the app dir), else the clean arm. It appends its argv to `calls.log` and exits 64 on an argv
21
+ # shape it does not recognise, and its `dependency-cruiser/package.json` carries the distinctive
22
+ # version `0.0.0-stub` so the real-toolchain case can prove it did NOT run the stub.
23
+ #
24
+ # Every fixture is synthesized under a mktemp root (cq-test-fixtures-synthesized-only); nothing
25
+ # is written into the real apps/web-platform tree or into this repository's CLAUDE.md.
26
+ set -uo pipefail
27
+ export TMPDIR="${TMPDIR:-/var/tmp}"
28
+
29
+ # Refuses an empty, relative, root or synthetic-fs fixture dir (byte-identical copy; the
30
+ # fixture-dir-operand-assert suite pins every tracked copy).
31
+ assert_fixture_dir() {
32
+ case "${1-}" in
33
+ "") printf 'FATAL: fixture dir is EMPTY; git -C "" would operate on %s\n' "$PWD" >&2; exit 2 ;;
34
+ */../*|*/..) printf 'FATAL: fixture dir %s contains ..; refusing\n' "$1" >&2; exit 2 ;;
35
+ /proc/*|/sys/*|/dev/*) printf 'FATAL: fixture dir %s is a synthetic-fs path; refusing\n' "$1" >&2; exit 2 ;;
36
+ /|//|/.) printf 'FATAL: fixture dir resolves to the filesystem root; refusing\n' >&2; exit 2 ;;
37
+ /*) : ;;
38
+ *) printf 'FATAL: fixture dir %s is RELATIVE; refusing\n' "$1" >&2; exit 2 ;;
39
+ esac
40
+ }
41
+
42
+ REPO_ROOT="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)"
43
+ assert_fixture_dir "$REPO_ROOT"
44
+ SKILL="$REPO_ROOT/plugins/soleur/skills/constraint-scaffold"
45
+ GEN="$SKILL/scripts/constraint-scaffold.sh"
46
+ REF="$SKILL/references"
47
+ APP="$REPO_ROOT/apps/web-platform"
48
+
49
+ # Hermetic git environment for every fixture (the repo's chokepoint, #7849): sweeps inherited
50
+ # GIT_*, pins discovery at $TMPROOT's parent, GIT_CONFIG_GLOBAL=/dev/null (a developer
51
+ # commit.gpgsign=true or signing key would otherwise reach 20+ fixture commits), synthesized identity.
52
+ # shellcheck source=../../../test/lib/git-fixture-env.sh
53
+ source "$REPO_ROOT/plugins/soleur/test/lib/git-fixture-env.sh"
54
+
55
+ RULE_DIRECT="no-client-to-server-secret"
56
+ RULE_TRANSITIVE="no-client-to-server-secret-transitive"
57
+ PROBE_DIR="components/__constraint_scaffold_bite_probe__"
58
+ POINTER_MARKER="<!-- constraint-scaffold:pointer -->"
59
+ README_START="<!-- constraint-scaffold:readme:start -->"
60
+ README_END="<!-- constraint-scaffold:readme:end -->"
61
+ VERDICT_RE="^constraint-scaffold: bite-proof pass -> fail\(${RULE_DIRECT}@direct, ${RULE_TRANSITIVE}@via-hop\) -> pass depcruise=[^ ]+$"
62
+
63
+ pass=0
64
+ fail=0
65
+ # The INDEPENDENT case counter (ADR-193 #2). Incremented at every CALL SITE, and NEVER inside
66
+ # ok()/bad() — the VERDICT helpers, which touch only the verdict counters. A counter bumped inside
67
+ # the verdict helpers moves WITH the verdict, so stubbing bad() drops the row and its count together
68
+ # and `pass + fail == cases` still holds under the exact fault it exists to catch. Incremented at
69
+ # top level only, never inside a `$( … )` command substitution.
70
+ cases=0
71
+ # Anti-vacuity thresholds (TOOLCHAIN_FREE_MIN_ASSERTIONS at the toolchain-SKIP door,
72
+ # MIN_ASSERTIONS at the trailer). Each literal is bound on the line DIRECTLY above its `if`,
73
+ # because guard-vacuity-floor's backward slice carries only contiguous simple assignments into
74
+ # its neutered-machinery mutant; bound anywhere else, the mutant dies unbound and the floor
75
+ # scores CONSTRUCTION instead of FIRES.
76
+ #
77
+ # Derived at commit time, never hand-summed: each constant is
78
+ # `grep -cE '^\s*cases=\$\(\(cases \+ 1\)\)$'` over its segment of THIS file (the anchored form
79
+ # skips the two prose mentions inside the accounting messages), minus the sites that are
80
+ # legitimately conditional, so the floor is a genuine LOWER BOUND rather than a snapshot:
81
+ # TOOLCHAIN_FREE_MIN_ASSERTIONS = 78 call sites above the locate-or-install block, minus the 2
82
+ # C-chmod sites that only run when not root = 76
83
+ # MIN_ASSERTIONS = that + the 18 call sites of the real segment = 94
84
+ ok() { printf 'ok - %s\n' "$1"; pass=$((pass + 1)); }
85
+ bad() { printf 'FAIL - %s\n' "$1"; fail=$((fail + 1)); }
86
+ # Instrument self-test — DIRECTION, not only presence: conservation (`pass + fail == cases`) and
87
+ # both floors count verdicts, so a bad() misrouted to the pass counter keeps every gate green while
88
+ # printing FAIL rows. Drive each helper once, require the RIGHT counter moved, reset. Reported via
89
+ # printf >&2 + exit 1, never through the helper it checks (ADR-193).
90
+ bad "instrument self-test (expected — proves bad() records a FAILURE)" >/dev/null
91
+ ok "instrument self-test (expected — proves ok() records a PASS)" >/dev/null
92
+ if [[ "$fail" != "1" || "$pass" != "1" ]]; then
93
+ printf '[FATAL] HELPER CONTROL BROKEN: after one bad() and one ok(), fail=%s pass=%s (want 1/1)\n' "$fail" "$pass" >&2
94
+ exit 1
95
+ fi
96
+ pass=0; fail=0
97
+
98
+ [[ -f "$GEN" ]] || { echo "fatal: missing $GEN"; exit 2; }
99
+ [[ -f "$REF/depcruise-config.template" ]] || { echo "fatal: missing depcruise-config.template"; exit 2; }
100
+ command -v node >/dev/null 2>&1 || { echo "fatal: node is required (the emitted runner parses JSON with it)"; exit 2; }
101
+
102
+ TMPROOT="$(mktemp -d)" || { echo "FATAL: cannot create scratch root" >&2; exit 1; }
103
+ : "${TMPROOT:?scratch root must be non-empty}"
104
+ [[ "$TMPROOT" == /* && -d "$TMPROOT" && ! -L "$TMPROOT" ]] || {
105
+ echo "FATAL: scratch root is not a plain absolute directory: '$TMPROOT'" >&2; exit 1; }
106
+ # Every fixture lives under $TMPROOT; one anchor dir gives the ceiling ($TMPROOT's parent) and the
107
+ # hermetic config to the whole suite, the scaffold under test included (it inherits the env).
108
+ mkdir -p "$TMPROOT/anchor"
109
+ git_fixture_env "$TMPROOT/anchor" || { echo "FATAL: git_fixture_env refused $TMPROOT/anchor" >&2; exit 1; }
110
+ # EXIT **INT TERM**: a ^C during the on-demand `npm install` below must still remove the root.
111
+ trap 'rm -rf -- "${TMPROOT:-}"' EXIT INT TERM
112
+ assert_fixture_dir "$TMPROOT"
113
+
114
+ # =============================================================================================
115
+ # Segment 1 — SOURCE PINS (no fixture run). Shape facts about constraint-scaffold.sh that the
116
+ # behavioural cases below rely on and that one diff could otherwise silently move.
117
+ # =============================================================================================
118
+ echo "# --- source pins ---"
119
+
120
+ # Pin: the default-mode failure-cleanup list names exactly the artifacts the refuse-if-exists
121
+ # guard protects (the script owns one list; a new artifact cannot be left behind on a failed bite
122
+ # without also being left out of the refuse loop, and vice-versa).
123
+ vars_of() { grep -oE '\$[A-Z_]+' | sort -u | tr '\n' ' '; }
124
+ REFUSE_VARS="$( { grep -E '^for f in .*"\$CFG".*; do$' "$GEN"; grep -oE '^\[\[ -e "\$BASELINE" \]\] && die "refuse-if-exists' "$GEN"; } | vars_of )"
125
+ CLEANUP_VARS="$( awk '/^remove_emitted_artifacts\(\)/{f=1} f && /^}/{f=0} f' "$GEN" | grep -E '^\s*for f in .*"\$CFG"' | vars_of )"
126
+ cases=$((cases + 1))
127
+ if [[ -n "$CLEANUP_VARS" && "$CLEANUP_VARS" == "$REFUSE_VARS" && "$REFUSE_VARS" == *'$BASELINE'* && "$REFUSE_VARS" == *'$FIXWORKFLOW_B'* ]]; then
128
+ ok "pin: default-mode cleanup list == refuse-if-exists list ($CLEANUP_VARS)"
129
+ else
130
+ bad "pin: cleanup list [$CLEANUP_VARS] != refuse-if-exists list [$REFUSE_VARS]"
131
+ fi
132
+
133
+ # Pin: inside with_detached_worktree(), the `trap '_wt_cleanup` line PRECEDES `worktree add
134
+ # --detach` (ADR-129 — the trap owns the dir before the operation that can fail). Matched on the
135
+ # specific handler so a `trap - EXIT` line cannot satisfy it.
136
+ # Comment-STRIPPED: the script documents the very constructs these pins look for.
137
+ WT_BODY="$(awk '/^with_detached_worktree\(\)/{f=1} f{print} f && /^}/{exit}' "$GEN" | grep -vE '^\s*#')"
138
+ TRAP_LN="$(printf '%s\n' "$WT_BODY" | grep -nF "trap '_wt_cleanup" | head -1 | cut -d: -f1)"
139
+ ADD_LN="$(printf '%s\n' "$WT_BODY" | grep -n 'worktree add --detach' | head -1 | cut -d: -f1)"
140
+ cases=$((cases + 1))
141
+ if [[ -n "$TRAP_LN" && -n "$ADD_LN" && "$TRAP_LN" -lt "$ADD_LN" ]]; then
142
+ ok "pin: trap '_wt_cleanup' (line $TRAP_LN of the helper) precedes worktree add --detach (line $ADD_LN)"
143
+ else
144
+ bad "pin: trap '_wt_cleanup' must precede worktree add --detach inside with_detached_worktree (trap=$TRAP_LN add=$ADD_LN)"
145
+ fi
146
+
147
+ # Pin: the INT/TERM arm clears EXIT inside itself and exits 143 (cleanup runs once; a TERM is a
148
+ # property, not an accident).
149
+ cases=$((cases + 1))
150
+ if printf '%s\n' "$WT_BODY" | grep -cF >/dev/null "trap '_wt_cleanup interrupted; trap - EXIT; exit 143' INT TERM"; then
151
+ ok "pin: INT/TERM arm is '_wt_cleanup interrupted; trap - EXIT; exit 143'"
152
+ else
153
+ bad "pin: INT/TERM arm missing or not the prescribed shape"
154
+ fi
155
+
156
+ # Pin: the two rule-name literals in the script equal the `name:` fields of the config template,
157
+ # in order (a rename in the template reds here before it reds in a founder repo).
158
+ TEMPLATE_RULES="$(grep -oE 'name: "[^"]+"' "$REF/depcruise-config.template" | sed -E 's/name: "([^"]+)"/\1/' | tr '\n' ' ')"
159
+ SCRIPT_RULES="$( { grep -E '^RULE_DIRECT=' "$GEN"; grep -E '^RULE_TRANSITIVE=' "$GEN"; } | sed -E 's/^[A-Z_]+="([^"]+)"$/\1/' | tr '\n' ' ')"
160
+ cases=$((cases + 1))
161
+ if [[ -n "$SCRIPT_RULES" && "$SCRIPT_RULES" == "$TEMPLATE_RULES" && "$TEMPLATE_RULES" == "$RULE_DIRECT $RULE_TRANSITIVE " ]]; then
162
+ ok "pin: script rule literals == template name: fields ($TEMPLATE_RULES)"
163
+ else
164
+ bad "pin: script rule literals [$SCRIPT_RULES] != template name: fields [$TEMPLATE_RULES]"
165
+ fi
166
+
167
+ # Pin: verdict_fail prints its stdout line BEFORE it dies (agent runtimes surface stdout and
168
+ # swallow stderr), and the stdout line is not itself redirected to stderr.
169
+ VF_BODY="$(awk '/^verdict_fail\(\)/{f=1} f{print} f && /^}/{exit}' "$GEN" | grep -vE '^\s*#')"
170
+ VF_PRINT_LN="$(printf '%s\n' "$VF_BODY" | grep -n "bite-proof FAILED" | grep -v '>&2' | head -1 | cut -d: -f1)"
171
+ VF_DIE_LN="$(printf '%s\n' "$VF_BODY" | grep -nE '^\s*(die |exit ")' | head -1 | cut -d: -f1)"
172
+ cases=$((cases + 1))
173
+ if [[ -n "$VF_PRINT_LN" && -n "$VF_DIE_LN" && "$VF_PRINT_LN" -lt "$VF_DIE_LN" ]]; then
174
+ ok "pin: verdict_fail prints 'bite-proof FAILED' on stdout (line $VF_PRINT_LN) before it exits (line $VF_DIE_LN)"
175
+ else
176
+ bad "pin: verdict_fail must print on stdout before it exits (print=$VF_PRINT_LN exit=$VF_DIE_LN)"
177
+ fi
178
+
179
+ # Pin: the default-mode tail ARMS the cleanup handler (top-level `trap '_wt_cleanup' EXIT`) before
180
+ # the first write (`mkdir -p "$TARGET/scripts"`) and DISARMS it (top-level `trap - EXIT INT TERM`)
181
+ # after the last prove_bite and before emit_readme — the window every 66..74 / mktemp / set -e
182
+ # failure falls inside. Column-0 call forms on a comment-stripped copy; the helper's own (indented)
183
+ # trap lines cannot satisfy them.
184
+ GEN_NOCOMMENT="$(grep -vE '^\s*#' "$GEN")"
185
+ ARM_LN="$(printf '%s\n' "$GEN_NOCOMMENT" | grep -nF "trap '_wt_cleanup' EXIT" | grep -vE '^[0-9]+:\s' | head -1 | cut -d: -f1)"
186
+ MKDIR_LN="$(printf '%s\n' "$GEN_NOCOMMENT" | grep -nE '^mkdir -p "\$TARGET/scripts"' | head -1 | cut -d: -f1)"
187
+ NC_PB_LN="$(printf '%s\n' "$GEN_NOCOMMENT" | grep -nE '^prove_bite\s*$' | tail -1 | cut -d: -f1)"
188
+ DISARM_LN="$(printf '%s\n' "$GEN_NOCOMMENT" | grep -nE '^trap - EXIT INT TERM$' | head -1 | cut -d: -f1)"
189
+ NC_ER_LN="$(printf '%s\n' "$GEN_NOCOMMENT" | grep -nE '^emit_readme\s*$' | head -1 | cut -d: -f1)"
190
+ cases=$((cases + 1))
191
+ if [[ -n "$ARM_LN" && -n "$MKDIR_LN" && -n "$NC_PB_LN" && -n "$DISARM_LN" && -n "$NC_ER_LN" \
192
+ && "$ARM_LN" -lt "$MKDIR_LN" && "$MKDIR_LN" -lt "$NC_PB_LN" && "$NC_PB_LN" -lt "$DISARM_LN" && "$DISARM_LN" -lt "$NC_ER_LN" ]]; then
193
+ ok "pin: cleanup armed ($ARM_LN) < first write ($MKDIR_LN) < prove_bite ($NC_PB_LN) < disarmed ($DISARM_LN) < emit_readme ($NC_ER_LN)"
194
+ else
195
+ bad "pin: default-mode cleanup window wrong: arm=$ARM_LN mkdir=$MKDIR_LN prove_bite=$NC_PB_LN disarm=$DISARM_LN emit_readme=$NC_ER_LN (want ascending)"
196
+ fi
197
+
198
+ # Pin: no test seam — the script honours no CONSTRAINT_SCAFFOLD_TEST_* variable.
199
+ SEAMS="$(grep -c 'CONSTRAINT_SCAFFOLD_TEST_' "$GEN" || true)"
200
+ cases=$((cases + 1))
201
+ if [[ "$SEAMS" == "0" ]]; then
202
+ ok "pin: no CONSTRAINT_SCAFFOLD_TEST_ seam in the script"
203
+ else
204
+ bad "pin: $SEAMS CONSTRAINT_SCAFFOLD_TEST_ occurrence(s) in the script (must be 0)"
205
+ fi
206
+
207
+ # Pin: prove_bite is called at BOTH mode exits, and the README/pointer emits come AFTER the
208
+ # default-mode prove_bite (R1 — written last so the clean-tree guard never sees them).
209
+ PB_CALLS="$(grep -cE '^\s*prove_bite\s*(#|$)' "$GEN" || true)"
210
+ PB_LAST_LN="$(grep -nE '^\s*prove_bite\s*(#|$)' "$GEN" | tail -1 | cut -d: -f1)"
211
+ ER_LN="$(grep -nE '^\s*emit_readme\s*(#|$)' "$GEN" | head -1 | cut -d: -f1)"
212
+ EP_LN="$(grep -nE '^\s*emit_pointer\s*(#|$)' "$GEN" | head -1 | cut -d: -f1)"
213
+ cases=$((cases + 1))
214
+ if [[ "$PB_CALLS" == "2" && -n "$ER_LN" && -n "$EP_LN" && "$PB_LAST_LN" -lt "$ER_LN" && "$ER_LN" -lt "$EP_LN" ]]; then
215
+ ok "pin: prove_bite called twice; emit_readme ($ER_LN) and emit_pointer ($EP_LN) follow the default-mode prove_bite ($PB_LAST_LN)"
216
+ else
217
+ bad "pin: prove_bite calls=$PB_CALLS (want 2); prove_bite=$PB_LAST_LN emit_readme=$ER_LN emit_pointer=$EP_LN (want ascending)"
218
+ fi
219
+
220
+ # Pin: the header exit matrix lists 71, 72, 73 and 74.
221
+ MATRIX_OK=1
222
+ for code in 71 72 73 74; do
223
+ grep -qE "^# $code " "$GEN" || MATRIX_OK=0
224
+ done
225
+ cases=$((cases + 1))
226
+ if [[ "$MATRIX_OK" -eq 1 ]]; then
227
+ ok "pin: header exit matrix lists 71/72/73/74"
228
+ else
229
+ bad "pin: header exit matrix is missing one of 71/72/73/74"
230
+ fi
231
+
232
+ # Pin: the emitter's strip/substitute expression is the one literal expression the parity suite
233
+ # also pins (one transform, two copies).
234
+ # Anchored on the ASSIGNMENT form (`content="$(sed …`), so a stale comment quoting the expression
235
+ # cannot stand in for the live one.
236
+ STRIP_EXPR="sed -e '1{/^<!-- Inspired by /d;}' -e \"s|__TARGET_DIR__|\$TARGET_REL|g\""
237
+ STRIP_CT="$(grep -E '^\s*content="\$\(sed ' "$GEN" | grep -cF -- "$STRIP_EXPR" || true)"
238
+ cases=$((cases + 1))
239
+ if [[ "$STRIP_CT" == "1" ]]; then
240
+ ok "pin: the README strip/substitute sed expression appears exactly once, on the content= assignment"
241
+ else
242
+ bad "pin: README strip/substitute sed expression on a content= assignment count=$STRIP_CT (want 1)"
243
+ fi
244
+
245
+ # =============================================================================================
246
+ # Fixture + stub builders.
247
+ # =============================================================================================
248
+
249
+ # make_repo <name> [opts...] -> prints the fixture repo root. A throwaway git repo carrying an
250
+ # apps/web-platform-shaped Next.js app, committed clean, with refs/remotes/origin/main == HEAD so
251
+ # the merge-base capture resolves. Options:
252
+ # --no-components omit components/ (drives the three-dirs precondition, 65)
253
+ # --claude commit a CLAUDE.md with prior content
254
+ # --agents commit an AGENTS.md with prior content
255
+ # --readme commit a server/README.md with prior content
256
+ # --populated an unrelated "use client" module with a TYPE-ONLY server import plus a
257
+ # client module value-importing a VALUE-SAFE server module (the direct rule
258
+ # baselines it; the transitive rule excludes it via pathNot)
259
+ # --claude-symlink commit CLAUDE.md as a symlink to a file OUTSIDE the repo
260
+ make_repo() {
261
+ local name="$1"; shift
262
+ local fx="$TMPROOT/$name"
263
+ assert_fixture_dir "$fx"
264
+ local app="$fx/apps/web-platform"
265
+ local want_components=1 claude=0 agents=0 readme=0 populated=0 claude_symlink=0
266
+ local o
267
+ for o in "$@"; do
268
+ case "$o" in
269
+ --no-components) want_components=0 ;;
270
+ --claude) claude=1 ;;
271
+ --agents) agents=1 ;;
272
+ --readme) readme=1 ;;
273
+ --populated) populated=1 ;;
274
+ --claude-symlink) claude_symlink=1 ;;
275
+ *) echo "make_repo: unknown option $o" >&2; exit 2 ;;
276
+ esac
277
+ done
278
+ mkdir -p "$app/app" "$app/server"
279
+ [[ "$want_components" -eq 1 ]] && mkdir -p "$app/components"
280
+ git -C "$fx" init -q
281
+ git -C "$fx" config user.email "test@example.com"
282
+ git -C "$fx" config user.name "test"
283
+ printf 'node_modules\n' > "$fx/.gitignore"
284
+ printf 'module.exports = {};\n' > "$app/next.config.js"
285
+ printf '{ "name": "fixture", "private": true, "dependencies": { "next": "15.0.0" } }\n' > "$app/package.json"
286
+ cat > "$app/tsconfig.json" <<'JSON'
287
+ {
288
+ "compilerOptions": {
289
+ "baseUrl": ".",
290
+ "module": "esnext",
291
+ "moduleResolution": "bundler",
292
+ "jsx": "preserve",
293
+ "paths": { "@/*": ["./*"] }
294
+ },
295
+ "include": ["**/*.ts", "**/*.tsx"],
296
+ "exclude": ["node_modules"]
297
+ }
298
+ JSON
299
+ printf 'export default function Page() { return null; }\n' > "$app/app/page.tsx"
300
+ printf 'export const APP_NAME = "fixture";\nexport type Shape = { id: string };\n' > "$app/server/config.ts"
301
+ if [[ "$want_components" -eq 1 ]]; then
302
+ printf '"use client";\nexport const Greeting = () => "hello";\n' > "$app/components/greeting.tsx"
303
+ fi
304
+ if [[ "$populated" -eq 1 ]]; then
305
+ printf '"use client";\nimport type { Shape } from "@/server/config";\nexport const Typed = (_: Shape) => null;\n' > "$app/components/typed.tsx"
306
+ printf 'export const LEADERS = ["a"] as const;\n' > "$app/server/domain-leaders.ts"
307
+ printf '"use client";\nimport { LEADERS } from "@/server/domain-leaders";\nexport const Leaders = () => LEADERS;\n' > "$app/components/leaders.tsx"
308
+ fi
309
+ [[ "$claude" -eq 1 ]] && printf '# Fixture project\n\nPrior instructions.\n' > "$fx/CLAUDE.md"
310
+ [[ "$agents" -eq 1 ]] && printf '# Agents\n\nPrior agent notes.\n' > "$fx/AGENTS.md"
311
+ [[ "$readme" -eq 1 ]] && printf '# Server\n\nPrior README prose.\n' > "$app/server/README.md"
312
+ if [[ "$claude_symlink" -eq 1 ]]; then
313
+ mkdir -p "$TMPROOT/elsewhere-$name"
314
+ printf '# Global instructions (must stay untouched)\n' > "$TMPROOT/elsewhere-$name/CLAUDE.md"
315
+ ln -s "$TMPROOT/elsewhere-$name/CLAUDE.md" "$fx/CLAUDE.md"
316
+ fi
317
+ git -C "$fx" add -A
318
+ git -C "$fx" commit -q -m "seed app"
319
+ git -C "$fx" update-ref refs/remotes/origin/main HEAD
320
+ printf '%s' "$fx"
321
+ }
322
+
323
+ # point_node_modules <fixture-root> <node_modules-dir>: (re)point the untracked symlink.
324
+ point_node_modules() {
325
+ local fx="$1" nm="$2"
326
+ assert_fixture_dir "$fx"
327
+ rm -f -- "$fx/apps/web-platform/node_modules"
328
+ ln -s "$nm" "$fx/apps/web-platform/node_modules"
329
+ }
330
+
331
+ # make_stub_depcruise <dir> <clean-out> <clean-rc> <probe-out> <probe-rc> [<kill-pid-file>]
332
+ # Builds a node_modules-shaped dir whose `.bin/depcruise` implements the three-branch table (D6).
333
+ # <clean-out>/<probe-out> are files whose bytes the stub cats on the `err` clean/probe arms. With
334
+ # <kill-pid-file>, the `err`-without-probe arm first sends TERM to the pid in that file (the
335
+ # pre-probe pass runs after the scaffold's trap is installed; the `baseline` call precedes it).
336
+ # Two flag FILES a case may touch in <dir> after building: `stateful` makes the clean arm fail
337
+ # (probe bytes + probe rc) once a probe arm has run — a gate that stays red after the revert
338
+ # (drives 73); `reach-baseline` makes the `baseline` arm emit a reachability entry, which the
339
+ # emitted RUNNER refuses on its pre-probe pass (drives 71 through runner logic, not stub output).
340
+ make_stub_depcruise() {
341
+ local dir="$1" clean_out="$2" clean_rc="$3" probe_out="$4" probe_rc="$5" pidfile="${6:-}"
342
+ assert_fixture_dir "$dir"
343
+ mkdir -p "$dir/.bin" "$dir/dependency-cruiser"
344
+ cp "$clean_out" "$dir/clean.out"
345
+ cp "$probe_out" "$dir/probe.out"
346
+ printf '{ "name": "dependency-cruiser", "version": "0.0.0-stub" }\n' > "$dir/dependency-cruiser/package.json"
347
+ : > "$dir/calls.log"
348
+ {
349
+ printf '#!/usr/bin/env bash\n'
350
+ printf 'STUB_DIR=%q\n' "$dir"
351
+ printf 'CLEAN_RC=%q\nPROBE_RC=%q\nPIDFILE=%q\n' "$clean_rc" "$probe_rc" "$pidfile"
352
+ cat <<'STUB'
353
+ printf '%s\n' "$*" >> "$STUB_DIR/calls.log"
354
+ case " $* " in
355
+ *" --output-type json "*) printf '{"modules":[]}\n' ;;
356
+ *" --output-type baseline "*)
357
+ if [[ -e "$STUB_DIR/reach-baseline" ]]; then
358
+ printf '[{"type":"reachability","rule":{"name":"no-client-to-server-secret-transitive"}}]\n'
359
+ else
360
+ printf '[]\n'
361
+ fi ;;
362
+ *" --output-type err "*)
363
+ if [[ -e components/__constraint_scaffold_bite_probe__/direct.tsx ]]; then
364
+ touch "$STUB_DIR/probed"
365
+ cat "$STUB_DIR/probe.out"; exit "$PROBE_RC"
366
+ fi
367
+ if [[ -e "$STUB_DIR/stateful" && -e "$STUB_DIR/probed" ]]; then
368
+ cat "$STUB_DIR/probe.out"; exit "$PROBE_RC"
369
+ fi
370
+ if [[ -n "$PIDFILE" && -s "$PIDFILE" ]]; then kill -TERM "$(cat "$PIDFILE")" 2>/dev/null || true; fi
371
+ cat "$STUB_DIR/clean.out"; exit "$CLEAN_RC" ;;
372
+ *) echo "stub depcruise: unhandled argv: $*" >&2; exit 64 ;;
373
+ esac
374
+ STUB
375
+ } > "$dir/.bin/depcruise"
376
+ chmod +x "$dir/.bin/depcruise"
377
+ }
378
+
379
+ # run_gen <fixture-root> <tag> [args...] -> RC, and stdout/stderr in $TMPROOT/<tag>.out / .err.
380
+ # RUN_TMPDIR (optional) is exported as TMPDIR for the scaffold so a leftover mktemp dir is observable.
381
+ run_gen() {
382
+ local root="$1" tag="$2"; shift 2
383
+ assert_fixture_dir "$root"
384
+ RC=0
385
+ CONSTRAINT_SCAFFOLD_REPO_ROOT="$root" TMPDIR="${RUN_TMPDIR:-$TMPDIR}" bash "$GEN" "$@" \
386
+ >"$TMPROOT/$tag.out" 2>"$TMPROOT/$tag.err" || RC=$?
387
+ }
388
+ worktree_count() { git -C "$1" worktree list 2>/dev/null | grep -c . || true; }
389
+ porcelain() { git -C "$1" status --porcelain 2>/dev/null || true; }
390
+ count_marker() { grep -cF -- "$2" "$1" 2>/dev/null || true; }
391
+
392
+ # Canned depcruise outputs — copied VERBATIM from the one-hop measurement on a scratch fixture with
393
+ # the real toolchain (dependency-cruiser 16.10.4, task 0.5), then edited per case.
394
+ CANNED="$TMPROOT/canned"
395
+ mkdir -p "$CANNED"
396
+ printf '\n\xe2\x9c\x94 no dependency violations found (1 modules, 0 dependencies cruised)\n\n' > "$CANNED/clean.txt"
397
+ cat > "$CANNED/real.txt" <<'EOF'
398
+
399
+ error no-client-to-server-secret-transitive: components/__constraint_scaffold_bite_probe__/via-hop.tsx → server/__constraint_scaffold_bite_probe__.ts
400
+ components/__constraint_scaffold_bite_probe__/hop.ts →
401
+ server/__constraint_scaffold_bite_probe__.ts
402
+ error no-client-to-server-secret-transitive: components/__constraint_scaffold_bite_probe__/direct.tsx → server/__constraint_scaffold_bite_probe__.ts
403
+ server/__constraint_scaffold_bite_probe__.ts
404
+ error no-client-to-server-secret: components/__constraint_scaffold_bite_probe__/direct.tsx → server/__constraint_scaffold_bite_probe__.ts
405
+
406
+ x 3 dependency violations (3 errors, 0 warnings). 5 modules, 3 dependencies cruised.
407
+ EOF
408
+ # S-real-minus-direct-transitive: today's real output minus the direct-edge transitive block (the
409
+ # shape a reachability-semantics change would produce). Must still PASS the bite.
410
+ cat > "$CANNED/real-minus-direct-transitive.txt" <<'EOF'
411
+
412
+ error no-client-to-server-secret-transitive: components/__constraint_scaffold_bite_probe__/via-hop.tsx → server/__constraint_scaffold_bite_probe__.ts
413
+ components/__constraint_scaffold_bite_probe__/hop.ts →
414
+ server/__constraint_scaffold_bite_probe__.ts
415
+ error no-client-to-server-secret: components/__constraint_scaffold_bite_probe__/direct.tsx → server/__constraint_scaffold_bite_probe__.ts
416
+
417
+ x 2 dependency violations (2 errors, 0 warnings). 5 modules, 3 dependencies cruised.
418
+ EOF
419
+ # S-direct-only: only the direct-rule line on direct.tsx — the transitive rule is silent.
420
+ cat > "$CANNED/direct-only.txt" <<'EOF'
421
+
422
+ error no-client-to-server-secret: components/__constraint_scaffold_bite_probe__/direct.tsx → server/__constraint_scaffold_bite_probe__.ts
423
+
424
+ x 1 dependency violations (1 errors, 0 warnings). 5 modules, 3 dependencies cruised.
425
+ EOF
426
+ # S-bare-tokens: both rule names and both probe filenames, WITHOUT the `error <rule>: ` call-form.
427
+ cat > "$CANNED/bare-tokens.txt" <<'EOF'
428
+
429
+ no-client-to-server-secret no-client-to-server-secret-transitive components/__constraint_scaffold_bite_probe__/direct.tsx components/__constraint_scaffold_bite_probe__/via-hop.tsx
430
+
431
+ x 2 dependency violations (2 errors, 0 warnings). 5 modules, 3 dependencies cruised.
432
+ EOF
433
+ # S-ansi: today's real output as a FORCE_COLOR environment decorates it (chalk: red `error`, bold
434
+ # from-path) — a correct gate whose stream carries escapes. Must still PASS the bite.
435
+ sed -e 's/^ error \(no-client-to-server-secret[a-z-]*\): \([^ ]*\)/ \x1b[31merror\x1b[39m \1: \x1b[1m\2\x1b[22m/' "$CANNED/real.txt" > "$CANNED/real-ansi.txt"
436
+ # S-preprobe-violation: the CLEAN arm carries a real violation on a non-probe file.
437
+ cat > "$CANNED/preprobe-violation.txt" <<'EOF'
438
+
439
+ error no-client-to-server-secret: components/other.tsx → server/secret.ts
440
+
441
+ x 1 dependency violations (1 errors, 0 warnings). 4 modules, 2 dependencies cruised.
442
+ EOF
443
+
444
+ STUB_CLEAN="$TMPROOT/stub-clean"
445
+ make_stub_depcruise "$STUB_CLEAN" "$CANNED/clean.txt" 0 "$CANNED/real.txt" 3
446
+
447
+ # =============================================================================================
448
+ # Segment 2 — STUB-DRIVEN (needs node + git, no depcruise — runs in every shard).
449
+ # =============================================================================================
450
+ echo "# --- stub-driven: S-clean ---"
451
+
452
+ # --- C2: three-dirs precondition -> 65, nothing emitted ---------------------------------------
453
+ FX="$(make_repo c2-nocomponents --no-components)"
454
+ assert_fixture_dir "$FX"
455
+ point_node_modules "$FX" "$STUB_CLEAN"
456
+ run_gen "$FX" c2
457
+ cases=$((cases + 1))
458
+ if [[ "$RC" == "65" ]]; then
459
+ ok "C2: fixture without components/ exits 65"
460
+ else
461
+ bad "C2: expected 65 for a target lacking components/, got rc=$RC"
462
+ fi
463
+ cases=$((cases + 1))
464
+ if [[ ! -e "$FX/apps/web-platform/.dependency-cruiser.cjs" && ! -e "$FX/apps/web-platform/scripts" && -z "$(porcelain "$FX")" ]]; then
465
+ ok "C2: nothing emitted before the precondition (tree clean)"
466
+ else
467
+ bad "C2: artifacts present after 65: $(porcelain "$FX")"
468
+ fi
469
+ cases=$((cases + 1))
470
+ if grep -q 'lacks components/ (the emitted runner cruises app/ components/ server/' "$TMPROOT/c2.out"; then
471
+ ok "C2: the 65 message names the MISSING dir and the three required ones on STDOUT"
472
+ else
473
+ bad "C2: 65 message not on stdout (stdout: $(head -c 200 "$TMPROOT/c2.out"))"
474
+ fi
475
+
476
+ # --- C3a: CLAUDE.md present -> pointer appended there only; README block emitted -------------
477
+ FX_A="$(make_repo c3a-claude --claude)"
478
+ assert_fixture_dir "$FX_A"
479
+ point_node_modules "$FX_A" "$STUB_CLEAN"
480
+ run_gen "$FX_A" c3a
481
+ cases=$((cases + 1))
482
+ if [[ "$RC" == "0" ]]; then
483
+ ok "C3a: default mode exits 0 under the clean stub"
484
+ else
485
+ bad "C3a: expected 0, got rc=$RC (stdout: $(tail -3 "$TMPROOT/c3a.out" | tr '\n' ' ')) (stderr: $(tail -3 "$TMPROOT/c3a.err" | tr '\n' ' '))"
486
+ fi
487
+ cases=$((cases + 1))
488
+ if grep -qE "$VERDICT_RE" "$TMPROOT/c3a.out" && grep -qF 'depcruise=0.0.0-stub' "$TMPROOT/c3a.out"; then
489
+ ok "C3a: verdict line on stdout carries the stub version (read through the node_modules symlink)"
490
+ else
491
+ bad "C3a: verdict line missing or wrong version: $(grep -F 'bite-proof' "$TMPROOT/c3a.out" | head -2 | tr '\n' ' ')"
492
+ fi
493
+ cases=$((cases + 1))
494
+ if [[ "$(count_marker "$FX_A/CLAUDE.md" "$POINTER_MARKER")" == "1" && ! -e "$FX_A/AGENTS.md" ]]; then
495
+ ok "C3a: pointer appended to CLAUDE.md exactly once; AGENTS.md not created"
496
+ else
497
+ bad "C3a: pointer count=$(count_marker "$FX_A/CLAUDE.md" "$POINTER_MARKER") AGENTS.md exists=$([[ -e "$FX_A/AGENTS.md" ]] && echo yes || echo no)"
498
+ fi
499
+ cases=$((cases + 1))
500
+ if head -1 "$FX_A/CLAUDE.md" | grep -cF >/dev/null '# Fixture project' && grep -qF 'Prior instructions.' "$FX_A/CLAUDE.md"; then
501
+ ok "C3a: prior CLAUDE.md content preserved (append, not replace)"
502
+ else
503
+ bad "C3a: prior CLAUDE.md content lost"
504
+ fi
505
+ POINTER_LINE="$(grep -F -- "$POINTER_MARKER" "$FX_A/CLAUDE.md" | head -1)"
506
+ cases=$((cases + 1))
507
+ if [[ "$POINTER_LINE" == *"apps/web-platform/server/README.md"* && "$POINTER_LINE" != *"@apps/"* && "$POINTER_LINE" != @* && "$POINTER_LINE" == *"$POINTER_MARKER" ]]; then
508
+ ok "C3a: pointer line is plain prose naming apps/web-platform/server/README.md, no @-import, marker at end"
509
+ else
510
+ bad "C3a: pointer line shape wrong: '$POINTER_LINE'"
511
+ fi
512
+ cases=$((cases + 1))
513
+ if [[ "$(count_marker "$FX_A/apps/web-platform/server/README.md" "$README_START")" == "1" && "$(count_marker "$FX_A/apps/web-platform/server/README.md" "$README_END")" == "1" ]]; then
514
+ ok "C3a: README block emitted once (start + end markers)"
515
+ else
516
+ bad "C3a: README block markers start=$(count_marker "$FX_A/apps/web-platform/server/README.md" "$README_START") end=$(count_marker "$FX_A/apps/web-platform/server/README.md" "$README_END")"
517
+ fi
518
+ cases=$((cases + 1))
519
+ if [[ -s "$FX_A/apps/web-platform/server/README.md" ]] && ! grep -q 'Inspired by' "$FX_A/apps/web-platform/server/README.md" && ! grep -q '__TARGET_DIR__' "$FX_A/apps/web-platform/server/README.md" && grep -qF 'apps/web-platform' "$FX_A/apps/web-platform/server/README.md"; then
520
+ ok "C3a: emitted README has no attribution line, no __TARGET_DIR__ placeholder, names the target dir"
521
+ else
522
+ bad "C3a: emitted README carries the attribution line or an unsubstituted placeholder"
523
+ fi
524
+ EXPECTED_README="$(sed -e '1{/^<!-- Inspired by /d;}' -e "s|__TARGET_DIR__|apps/web-platform|g" "$REF/boundary-readme.template" 2>/dev/null)"
525
+ cases=$((cases + 1))
526
+ if [[ -n "$EXPECTED_README" ]] && diff -q <(printf '%s\n' "$EXPECTED_README") "$FX_A/apps/web-platform/server/README.md" >/dev/null 2>&1; then
527
+ ok "C3a: emitted README == emitter transform of boundary-readme.template"
528
+ else
529
+ bad "C3a: emitted README differs from the template transform"
530
+ fi
531
+ cases=$((cases + 1))
532
+ if [[ -f "$FX_A/apps/web-platform/.dependency-cruiser-known-violations.json" && "$(worktree_count "$FX_A")" == "1" && ! -e "$FX_A/apps/web-platform/$PROBE_DIR" ]]; then
533
+ ok "C3a: baseline present, worktree list clean, no probe dir in the founder tree"
534
+ else
535
+ bad "C3a: baseline=$([[ -f "$FX_A/apps/web-platform/.dependency-cruiser-known-violations.json" ]] && echo y || echo n) worktrees=$(worktree_count "$FX_A") probe-dir=$([[ -e "$FX_A/apps/web-platform/$PROBE_DIR" ]] && echo present || echo absent)"
536
+ fi
537
+ # The README/pointer are written LAST: the clean-tree guard inside baseline capture must never
538
+ # see them. Observable: the run exited 0 (a pre-capture write would have produced 67).
539
+ cases=$((cases + 1))
540
+ if ! grep -q 'working tree is dirty' "$TMPROOT/c3a.err"; then
541
+ ok "C3a: no 67 (README/pointer were not written before the clean-tree guard)"
542
+ else
543
+ bad "C3a: clean-tree guard saw an early write (67)"
544
+ fi
545
+ # The stub recorded the runner's three passes: baseline + (err, json) + err + (err, json).
546
+ ERR_CALLS="$(grep -c -- '--output-type err' "$STUB_CLEAN/calls.log" || true)"
547
+ cases=$((cases + 1))
548
+ if [[ "$ERR_CALLS" -ge 3 ]]; then
549
+ ok "C3a: stub argv log shows >= 3 'err' passes (pass -> fail -> pass) — got $ERR_CALLS"
550
+ else
551
+ bad "C3a: stub argv log shows only $ERR_CALLS 'err' passes (want >= 3)"
552
+ fi
553
+
554
+ # --- C4: idempotency — re-install after removing the gate; README block + pointer stay at 1 ----
555
+ rm -f -- "$FX_A/apps/web-platform/.dependency-cruiser.cjs" "$FX_A/apps/web-platform/scripts/constraint-gates.sh" \
556
+ "$FX_A/apps/web-platform/.github/workflows/constraint-gates.yml" "$FX_A/apps/web-platform/.github/workflows/fix-constraints-stage-a.yml" \
557
+ "$FX_A/apps/web-platform/.github/workflows/fix-constraints-stage-b.yml" "$FX_A/apps/web-platform/.dependency-cruiser-known-violations.json"
558
+ git -C "$FX_A" add -A
559
+ git -C "$FX_A" commit -q -m "keep README + pointer, drop the gate"
560
+ git -C "$FX_A" update-ref refs/remotes/origin/main HEAD
561
+ run_gen "$FX_A" c4
562
+ cases=$((cases + 1))
563
+ if [[ "$RC" == "0" && "$(count_marker "$FX_A/CLAUDE.md" "$POINTER_MARKER")" == "1" && "$(count_marker "$FX_A/apps/web-platform/server/README.md" "$README_START")" == "1" ]]; then
564
+ ok "C4: second default-mode run (rc=$RC) leaves pointer and README block at exactly 1 each"
565
+ else
566
+ bad "C4: rc=$RC pointer=$(count_marker "$FX_A/CLAUDE.md" "$POINTER_MARKER") readme-start=$(count_marker "$FX_A/apps/web-platform/server/README.md" "$README_START")"
567
+ fi
568
+
569
+ # --- C6: commit, then --refresh-baseline -> rc 0, verdict, README/pointer byte-identical --------
570
+ git -C "$FX_A" add -A
571
+ git -C "$FX_A" commit -q -m "install gate"
572
+ git -C "$FX_A" update-ref refs/remotes/origin/main HEAD
573
+ cp "$FX_A/CLAUDE.md" "$TMPROOT/c6-claude.before"
574
+ cp "$FX_A/apps/web-platform/server/README.md" "$TMPROOT/c6-readme.before"
575
+ run_gen "$FX_A" c6 --refresh-baseline
576
+ cases=$((cases + 1))
577
+ if [[ "$RC" == "0" ]] && grep -qE "$VERDICT_RE" "$TMPROOT/c6.out"; then
578
+ ok "C6: --refresh-baseline exits 0 and prints the verdict line (bite runs in refresh mode too)"
579
+ else
580
+ bad "C6: rc=$RC verdict=$(grep -cE "$VERDICT_RE" "$TMPROOT/c6.out" || true) (stderr: $(tail -2 "$TMPROOT/c6.err" | tr '\n' ' '))"
581
+ fi
582
+ cases=$((cases + 1))
583
+ if cmp -s "$FX_A/CLAUDE.md" "$TMPROOT/c6-claude.before" && cmp -s "$FX_A/apps/web-platform/server/README.md" "$TMPROOT/c6-readme.before"; then
584
+ ok "C6: refresh mode touched neither CLAUDE.md nor server/README.md"
585
+ else
586
+ bad "C6: refresh mode modified CLAUDE.md or server/README.md"
587
+ fi
588
+ cases=$((cases + 1))
589
+ if [[ "$(worktree_count "$FX_A")" == "1" ]]; then
590
+ ok "C6: worktree list clean after refresh"
591
+ else
592
+ bad "C6: $(worktree_count "$FX_A") worktrees registered after refresh"
593
+ fi
594
+
595
+ # --- C3b: only AGENTS.md -> appended there; CLAUDE.md not created --------------------------------
596
+ FX="$(make_repo c3b-agents --agents)"
597
+ assert_fixture_dir "$FX"
598
+ point_node_modules "$FX" "$STUB_CLEAN"
599
+ run_gen "$FX" c3b
600
+ cases=$((cases + 1))
601
+ if [[ "$RC" == "0" && "$(count_marker "$FX/AGENTS.md" "$POINTER_MARKER")" == "1" && ! -e "$FX/CLAUDE.md" ]]; then
602
+ ok "C3b: pointer appended to AGENTS.md once; CLAUDE.md not created (rc=$RC)"
603
+ else
604
+ bad "C3b: rc=$RC AGENTS pointer=$(count_marker "$FX/AGENTS.md" "$POINTER_MARKER") CLAUDE.md exists=$([[ -e "$FX/CLAUDE.md" ]] && echo yes || echo no)"
605
+ fi
606
+ cases=$((cases + 1))
607
+ if grep -qF 'Prior agent notes.' "$FX/AGENTS.md"; then
608
+ ok "C3b: prior AGENTS.md content preserved"
609
+ else
610
+ bad "C3b: prior AGENTS.md content lost"
611
+ fi
612
+
613
+ # --- C3c: neither -> AGENTS.md created with exactly the one line ---------------------------------
614
+ FX="$(make_repo c3c-neither)"
615
+ assert_fixture_dir "$FX"
616
+ point_node_modules "$FX" "$STUB_CLEAN"
617
+ run_gen "$FX" c3c
618
+ AGENTS_LINES="$(grep -c . "$FX/AGENTS.md" 2>/dev/null || true)"
619
+ cases=$((cases + 1))
620
+ if [[ "$RC" == "0" && -f "$FX/AGENTS.md" && "$AGENTS_LINES" == "1" && "$(count_marker "$FX/AGENTS.md" "$POINTER_MARKER")" == "1" && ! -e "$FX/CLAUDE.md" ]]; then
621
+ ok "C3c: AGENTS.md created carrying exactly the pointer line (rc=$RC)"
622
+ else
623
+ bad "C3c: rc=$RC AGENTS.md lines=$AGENTS_LINES pointer=$(count_marker "$FX/AGENTS.md" "$POINTER_MARKER")"
624
+ fi
625
+
626
+ # --- C-symlink: CLAUDE.md -> elsewhere: refused, nothing written through the link, rc 0 -----------
627
+ FX="$(make_repo csymlink --claude-symlink)"
628
+ assert_fixture_dir "$FX"
629
+ point_node_modules "$FX" "$STUB_CLEAN"
630
+ cp "$TMPROOT/elsewhere-csymlink/CLAUDE.md" "$TMPROOT/csymlink-global.before"
631
+ run_gen "$FX" csymlink
632
+ cases=$((cases + 1))
633
+ if [[ "$RC" == "0" ]] && cmp -s "$TMPROOT/elsewhere-csymlink/CLAUDE.md" "$TMPROOT/csymlink-global.before" && [[ ! -e "$FX/AGENTS.md" ]]; then
634
+ ok "C-symlink: run exits 0; nothing written through the CLAUDE.md symlink; no AGENTS.md fallback"
635
+ else
636
+ bad "C-symlink: rc=$RC link-target-changed=$(cmp -s "$TMPROOT/elsewhere-csymlink/CLAUDE.md" "$TMPROOT/csymlink-global.before" && echo no || echo YES) AGENTS.md=$([[ -e "$FX/AGENTS.md" ]] && echo created || echo absent)"
637
+ fi
638
+ cases=$((cases + 1))
639
+ if grep -qiE 'WARNING.*symlink' "$TMPROOT/csymlink.out"; then
640
+ ok "C-symlink: refusal is a WARNING on stdout naming the symlink"
641
+ else
642
+ bad "C-symlink: no symlink warning on stdout: $(grep -i warn "$TMPROOT/csymlink.out" | head -1)"
643
+ fi
644
+ cases=$((cases + 1))
645
+ if grep -qE "$VERDICT_RE" "$TMPROOT/csymlink.out" && [[ -f "$FX/apps/web-platform/.dependency-cruiser.cjs" ]]; then
646
+ ok "C-symlink: the gate stayed installed and proven (verdict line present, prose is not the gate)"
647
+ else
648
+ bad "C-symlink: gate not installed/proven after the pointer refusal"
649
+ fi
650
+
651
+ # --- C-tmpdir: TMPDIR inside the fixture repo -> 69 before any worktree ---------------------------
652
+ FX="$(make_repo ctmpdir)"
653
+ assert_fixture_dir "$FX"
654
+ point_node_modules "$FX" "$STUB_CLEAN"
655
+ mkdir -p "$FX/tmp-inside"
656
+ RUN_TMPDIR="$FX/tmp-inside" run_gen "$FX" ctmpdir
657
+ cases=$((cases + 1))
658
+ if [[ "$RC" == "69" && "$(worktree_count "$FX")" == "1" ]]; then
659
+ ok "C-tmpdir: TMPDIR inside the repo is refused with 69 before any worktree is created"
660
+ else
661
+ bad "C-tmpdir: rc=$RC worktrees=$(worktree_count "$FX") (want 69 / 1)"
662
+ fi
663
+ cases=$((cases + 1))
664
+ if [[ -z "$(porcelain "$FX")" && -z "$(ls -A "$FX/tmp-inside" 2>/dev/null)" ]]; then
665
+ ok "C-tmpdir: founder tree clean afterwards (emitted artifacts removed, no mktemp residue inside the repo)"
666
+ else
667
+ bad "C-tmpdir: residue after 69: $(porcelain "$FX" | tr '\n' ' ') tmp-inside=[$(ls -A "$FX/tmp-inside" | tr '\n' ' ')]"
668
+ fi
669
+
670
+ # --- C-chmod (non-root): worktree add fails -> 69, no mktemp dir left ----------------------------
671
+ if [[ "$(id -u)" != "0" ]]; then
672
+ FX="$(make_repo cchmod)"
673
+ assert_fixture_dir "$FX"
674
+ point_node_modules "$FX" "$STUB_CLEAN"
675
+ mkdir -p "$TMPROOT/cchmod-tmp"
676
+ chmod a-w "$FX/.git"
677
+ RUN_TMPDIR="$TMPROOT/cchmod-tmp" run_gen "$FX" cchmod
678
+ chmod u+w "$FX/.git"
679
+ cases=$((cases + 1))
680
+ if [[ "$RC" == "69" ]]; then
681
+ ok "C-chmod: a failing worktree add exits 69"
682
+ else
683
+ bad "C-chmod: expected 69 when worktree add fails, got rc=$RC"
684
+ fi
685
+ cases=$((cases + 1))
686
+ if [[ -z "$(ls -A "$TMPROOT/cchmod-tmp" 2>/dev/null)" ]]; then
687
+ ok "C-chmod: no mktemp dir left behind (trap installed BEFORE worktree add)"
688
+ else
689
+ bad "C-chmod: mktemp residue after a failed worktree add: $(ls -A "$TMPROOT/cchmod-tmp" | tr '\n' ' ')"
690
+ fi
691
+ else
692
+ echo "# C-chmod skipped (running as root: chmod a-w does not block writes)"
693
+ fi
694
+
695
+ echo "# --- stub-driven: per-case stubs ---"
696
+
697
+ # stub_case <name> <clean-out> <clean-rc> <probe-out> <probe-rc> -> FX, RC, files in $TMPROOT/<name>.*
698
+ stub_case() {
699
+ local name="$1"
700
+ local stub="$TMPROOT/stub-$name"
701
+ make_stub_depcruise "$stub" "$2" "$3" "$4" "$5"
702
+ FX="$(make_repo "$name")"
703
+ assert_fixture_dir "$FX"
704
+ point_node_modules "$FX" "$stub"
705
+ run_gen "$FX" "$name"
706
+ }
707
+
708
+ # --- S-direct-only -> 72 (the transitive assertion is live on its own edge) ----------------------
709
+ stub_case s-direct-only "$CANNED/clean.txt" 0 "$CANNED/direct-only.txt" 1
710
+ cases=$((cases + 1))
711
+ if [[ "$RC" == "72" ]]; then
712
+ ok "S-direct-only: only the direct-rule line -> 72"
713
+ else
714
+ bad "S-direct-only: expected 72, got rc=$RC"
715
+ fi
716
+ cases=$((cases + 1))
717
+ if grep -qF 'bite-proof FAILED (72)' "$TMPROOT/s-direct-only.out" && grep -qF 'depcruise=0.0.0-stub' "$TMPROOT/s-direct-only.out"; then
718
+ ok "S-direct-only: failure line on STDOUT names the code and the depcruise version"
719
+ else
720
+ bad "S-direct-only: stdout lacks 'bite-proof FAILED (72)' + version: $(grep -F FAILED "$TMPROOT/s-direct-only.out" | head -1)"
721
+ fi
722
+ cases=$((cases + 1))
723
+ if grep -qF 'no-client-to-server-secret@direct' "$TMPROOT/s-direct-only.out" && ! grep -qF 'no-client-to-server-secret-transitive@via-hop' "$TMPROOT/s-direct-only.out"; then
724
+ ok "S-direct-only: the failure names which rule WAS seen on its edge (direct) and not the missing one"
725
+ else
726
+ bad "S-direct-only: rules-named list wrong: $(grep -F 'rules named' "$TMPROOT/s-direct-only.out" | head -1)"
727
+ fi
728
+ # S-fail72 (same stub, default mode): the founder tree is as it was.
729
+ cases=$((cases + 1))
730
+ if [[ -z "$(porcelain "$FX")" ]]; then
731
+ ok "S-fail72: git status --porcelain empty after a 72 in default mode (every emitted artifact removed)"
732
+ else
733
+ bad "S-fail72: residue after 72: $(porcelain "$FX" | tr '\n' ' ')"
734
+ fi
735
+ cases=$((cases + 1))
736
+ if [[ ! -e "$FX/apps/web-platform/scripts" && ! -e "$FX/apps/web-platform/.github/workflows" ]]; then
737
+ ok "S-fail72: scripts/ and .github/workflows/ dirs removed (rmdir of the two mkdir -p dirs)"
738
+ else
739
+ bad "S-fail72: scripts=$([[ -e "$FX/apps/web-platform/scripts" ]] && echo present || echo gone) workflows=$([[ -e "$FX/apps/web-platform/.github/workflows" ]] && echo present || echo gone)"
740
+ fi
741
+ cases=$((cases + 1))
742
+ if grep -qF 'removed the artifacts this run emitted' "$TMPROOT/s-direct-only.out" && grep -qF 'git worktree prune' "$TMPROOT/s-direct-only.out"; then
743
+ ok "S-fail72: stdout says the artifacts were removed and names 'git worktree prune'"
744
+ else
745
+ bad "S-fail72: cleanup message missing on stdout"
746
+ fi
747
+ cases=$((cases + 1))
748
+ if [[ "$(worktree_count "$FX")" == "1" ]]; then
749
+ ok "S-fail72: worktree removed after the failed bite"
750
+ else
751
+ bad "S-fail72: $(worktree_count "$FX") worktrees registered after a 72"
752
+ fi
753
+ cases=$((cases + 1))
754
+ if grep -qE '^\s*error no-client-to-server-secret: ' "$TMPROOT/s-direct-only.out"; then
755
+ ok "S-fail72: the captured runner log tail is echoed to stdout as evidence"
756
+ else
757
+ bad "S-fail72: no log tail on stdout"
758
+ fi
759
+
760
+ # --- S-lines-rc0 -> 72 (runner stopped failing on rc>0: both lines printed, exit 0) --------------
761
+ stub_case s-lines-rc0 "$CANNED/clean.txt" 0 "$CANNED/real.txt" 0
762
+ cases=$((cases + 1))
763
+ if [[ "$RC" == "72" ]]; then
764
+ ok "S-lines-rc0: both call-form lines but rc 0 -> 72 (the rc check is live)"
765
+ else
766
+ bad "S-lines-rc0: expected 72, got rc=$RC"
767
+ fi
768
+ cases=$((cases + 1))
769
+ if grep -qE 'bite-proof FAILED \(72\).*rc=0' "$TMPROOT/s-lines-rc0.out"; then
770
+ ok "S-lines-rc0: failure line reports rc=0"
771
+ else
772
+ bad "S-lines-rc0: failure line does not report rc=0: $(grep -F FAILED "$TMPROOT/s-lines-rc0.out" | head -1)"
773
+ fi
774
+
775
+ # --- S-bare-tokens -> 72 (bare rule names + filenames without the call-form) ---------------------
776
+ stub_case s-bare-tokens "$CANNED/clean.txt" 0 "$CANNED/bare-tokens.txt" 1
777
+ cases=$((cases + 1))
778
+ if [[ "$RC" == "72" ]]; then
779
+ ok "S-bare-tokens: bare tokens without 'error <rule>: ' -> 72 (call-form anchored)"
780
+ else
781
+ bad "S-bare-tokens: expected 72, got rc=$RC"
782
+ fi
783
+
784
+ # --- S-real-minus-direct-transitive -> verdict, rc 0 ---------------------------------------------
785
+ stub_case s-real-minus "$CANNED/clean.txt" 0 "$CANNED/real-minus-direct-transitive.txt" 2
786
+ cases=$((cases + 1))
787
+ if [[ "$RC" == "0" ]] && grep -qE "$VERDICT_RE" "$TMPROOT/s-real-minus.out"; then
788
+ ok "S-real-minus-direct-transitive: the direct-edge transitive line is neither required nor forbidden (rc=0, verdict)"
789
+ else
790
+ bad "S-real-minus-direct-transitive: rc=$RC verdict=$(grep -cE "$VERDICT_RE" "$TMPROOT/s-real-minus.out" || true)"
791
+ fi
792
+ cases=$((cases + 1))
793
+ if grep -qF 'depcruise=0.0.0-stub' "$TMPROOT/s-real-minus.out"; then
794
+ ok "S-real-minus-direct-transitive: verdict version read through the symlink chain (0.0.0-stub)"
795
+ else
796
+ bad "S-real-minus-direct-transitive: verdict does not carry the stub version"
797
+ fi
798
+
799
+ # --- S-preprobe-violation -> 74 naming the file ----------------------------------------------------
800
+ stub_case s-preprobe "$CANNED/preprobe-violation.txt" 1 "$CANNED/real.txt" 3
801
+ cases=$((cases + 1))
802
+ if [[ "$RC" == "74" ]]; then
803
+ ok "S-preprobe-violation: a real violation on the clean pass -> 74 (not 71)"
804
+ else
805
+ bad "S-preprobe-violation: expected 74, got rc=$RC"
806
+ fi
807
+ cases=$((cases + 1))
808
+ if grep -qF 'bite-proof FAILED (74)' "$TMPROOT/s-preprobe.out" && grep -qF 'components/other.tsx' "$TMPROOT/s-preprobe.out"; then
809
+ ok "S-preprobe-violation: stdout lists the violating file (components/other.tsx)"
810
+ else
811
+ bad "S-preprobe-violation: stdout does not list the file: $(grep -F FAILED "$TMPROOT/s-preprobe.out" | head -1)"
812
+ fi
813
+ cases=$((cases + 1))
814
+ if grep -qE '1 real client->server-secret violation' "$TMPROOT/s-preprobe.out"; then
815
+ ok "S-preprobe-violation: the count of violations is reported"
816
+ else
817
+ bad "S-preprobe-violation: violation count missing"
818
+ fi
819
+ cases=$((cases + 1))
820
+ if [[ -z "$(porcelain "$FX")" ]]; then
821
+ ok "S-preprobe-violation: default-mode 74 also leaves the tree clean"
822
+ else
823
+ bad "S-preprobe-violation: residue after 74: $(porcelain "$FX" | tr '\n' ' ')"
824
+ fi
825
+
826
+ # --- S-term: stub TERMs the scaffold during the pre-probe pass -> 143, cleanup, one worktree -----
827
+ STUB_TERM="$TMPROOT/stub-term"
828
+ PIDFILE="$TMPROOT/s-term.pid"
829
+ : > "$PIDFILE"
830
+ make_stub_depcruise "$STUB_TERM" "$CANNED/clean.txt" 0 "$CANNED/real.txt" 3 "$PIDFILE"
831
+ FX="$(make_repo s-term)"
832
+ assert_fixture_dir "$FX"
833
+ point_node_modules "$FX" "$STUB_TERM"
834
+ mkdir -p "$TMPROOT/s-term-tmp"
835
+ CONSTRAINT_SCAFFOLD_REPO_ROOT="$FX" TMPDIR="$TMPROOT/s-term-tmp" bash "$GEN" >"$TMPROOT/s-term.out" 2>"$TMPROOT/s-term.err" &
836
+ TERM_PID=$!
837
+ printf '%s' "$TERM_PID" > "$PIDFILE"
838
+ RC=0
839
+ wait "$TERM_PID" || RC=$?
840
+ cases=$((cases + 1))
841
+ if [[ "$RC" == "143" ]]; then
842
+ ok "S-term: TERM during the pre-probe pass exits 143"
843
+ else
844
+ bad "S-term: expected 143, got rc=$RC (stdout: $(tail -2 "$TMPROOT/s-term.out" | tr '\n' ' '))"
845
+ fi
846
+ cases=$((cases + 1))
847
+ if grep -qF 'interrupted; removed:' "$TMPROOT/s-term.out"; then
848
+ ok "S-term: stdout carries the 'interrupted; removed: <list>' line"
849
+ else
850
+ bad "S-term: no 'interrupted; removed:' line on stdout"
851
+ fi
852
+ cases=$((cases + 1))
853
+ if [[ "$(worktree_count "$FX")" == "1" && -z "$(porcelain "$FX")" ]]; then
854
+ ok "S-term: one worktree registered, git status --porcelain empty"
855
+ else
856
+ bad "S-term: worktrees=$(worktree_count "$FX") porcelain=[$(porcelain "$FX" | tr '\n' ' ')]"
857
+ fi
858
+ cases=$((cases + 1))
859
+ if [[ -z "$(ls -A "$TMPROOT/s-term-tmp" 2>/dev/null)" ]]; then
860
+ ok "S-term: no leftover mktemp dir"
861
+ else
862
+ bad "S-term: mktemp residue: $(ls -A "$TMPROOT/s-term-tmp" | tr '\n' ' ')"
863
+ fi
864
+ cases=$((cases + 1))
865
+ if grep -q -- '--output-type baseline' "$STUB_TERM/calls.log" && grep -q -- '--output-type err' "$STUB_TERM/calls.log"; then
866
+ ok "S-term: the stub saw the baseline call and then the err call that delivered the TERM"
867
+ else
868
+ bad "S-term: stub argv log unexpected: $(tr '\n' '|' < "$STUB_TERM/calls.log")"
869
+ fi
870
+
871
+ # --- S-ansi: ANSI-decorated real output on the probe arm -> still PASSES (the anchors read a
872
+ # stripped stream; the runner exports NO_COLOR=1 as the first line of defence) ---------------------
873
+ stub_case s-ansi "$CANNED/clean.txt" 0 "$CANNED/real-ansi.txt" 3
874
+ cases=$((cases + 1))
875
+ if [[ "$RC" == "0" ]] && grep -qE "$VERDICT_RE" "$TMPROOT/s-ansi.out"; then
876
+ ok "S-ansi: colour-decorated \`error <rule>:\` lines still satisfy both anchors (rc 0, verdict line present)"
877
+ else
878
+ bad "S-ansi: rc=$RC verdict=$(grep -cE "$VERDICT_RE" "$TMPROOT/s-ansi.out" || true) — an ANSI-decorated stream must not read as 72 (stdout: $(grep -F FAILED "$TMPROOT/s-ansi.out" | head -1))"
879
+ fi
880
+
881
+ # --- S-73: the gate stays red after the revert -> 73, tree clean ---------------------------------
882
+ STUB_73="$TMPROOT/stub-s-73"
883
+ make_stub_depcruise "$STUB_73" "$CANNED/clean.txt" 0 "$CANNED/real.txt" 3
884
+ touch "$STUB_73/stateful"
885
+ FX="$(make_repo s-73)"
886
+ assert_fixture_dir "$FX"
887
+ point_node_modules "$FX" "$STUB_73"
888
+ run_gen "$FX" s-73
889
+ cases=$((cases + 1))
890
+ if [[ "$RC" == "73" ]] && grep -qF 'bite-proof FAILED (73)' "$TMPROOT/s-73.out"; then
891
+ ok "S-73: a gate that does not return to green after the probes are removed exits 73 (verdict on stdout)"
892
+ else
893
+ bad "S-73: expected 73 + stdout verdict, got rc=$RC (stdout: $(grep -F FAILED "$TMPROOT/s-73.out" | head -1))"
894
+ fi
895
+ cases=$((cases + 1))
896
+ if [[ -z "$(porcelain "$FX")" ]]; then
897
+ ok "S-73: default-mode 73 leaves the tree clean"
898
+ else
899
+ bad "S-73: residue after 73: $(porcelain "$FX" | tr '\n' ' ')"
900
+ fi
901
+
902
+ # --- S-71: the emitted RUNNER refuses a reachability baseline entry on the pre-probe pass -> 71 ---
903
+ # (Runner logic, not stub bytes: the only stub shape that distinguishes "ran the emitted runner"
904
+ # from "called depcruise directly with the runner's argv".)
905
+ STUB_71="$TMPROOT/stub-s-71"
906
+ make_stub_depcruise "$STUB_71" "$CANNED/clean.txt" 0 "$CANNED/real.txt" 3
907
+ touch "$STUB_71/reach-baseline"
908
+ FX="$(make_repo s-71)"
909
+ assert_fixture_dir "$FX"
910
+ point_node_modules "$FX" "$STUB_71"
911
+ run_gen "$FX" s-71
912
+ cases=$((cases + 1))
913
+ if [[ "$RC" == "71" ]] && grep -qF 'bite-proof FAILED (71)' "$TMPROOT/s-71.out" && grep -qF 'suppress the transitive rule' "$TMPROOT/s-71.out"; then
914
+ ok "S-71: the runner's own pre-probe refusal surfaces as 71 with the runner's reason in the stdout log tail"
915
+ else
916
+ bad "S-71: expected 71 + runner reason on stdout, got rc=$RC (stdout: $(grep -F FAILED "$TMPROOT/s-71.out" | head -1))"
917
+ fi
918
+ cases=$((cases + 1))
919
+ if [[ -z "$(porcelain "$FX")" ]]; then
920
+ ok "S-71: default-mode 71 leaves the tree clean"
921
+ else
922
+ bad "S-71: residue after 71: $(porcelain "$FX" | tr '\n' ' ')"
923
+ fi
924
+
925
+ # --- S-int130: the runner exits 130 (terminal Ctrl-C reached the node child) -> treated as an
926
+ # interrupt, never as a 71/72 verdict; cleanup; exit 130 ----------------------------------------------
927
+ stub_case s-int130 "$CANNED/clean.txt" 130 "$CANNED/real.txt" 3
928
+ cases=$((cases + 1))
929
+ if [[ "$RC" == "130" ]] && grep -qF 'interrupted; removed:' "$TMPROOT/s-int130.out" && ! grep -qF 'bite-proof FAILED' "$TMPROOT/s-int130.out"; then
930
+ ok "S-int130: runner rc 130 exits 130 with the interrupted line and NO verdict"
931
+ else
932
+ bad "S-int130: rc=$RC (want 130) stdout: $(tail -2 "$TMPROOT/s-int130.out" | tr '\n' ' ')"
933
+ fi
934
+ cases=$((cases + 1))
935
+ if [[ -z "$(porcelain "$FX")" && "$(worktree_count "$FX")" == "1" ]]; then
936
+ ok "S-int130: tree clean, no worktree left registered"
937
+ else
938
+ bad "S-int130: porcelain=[$(porcelain "$FX" | tr '\n' ' ')] worktrees=$(worktree_count "$FX")"
939
+ fi
940
+
941
+ # --- S-noorigin: neither origin/main nor origin/HEAD -> 69 BEFORE any write; re-run is not 66 ------
942
+ FX="$(make_repo s-noorigin)"
943
+ assert_fixture_dir "$FX"
944
+ point_node_modules "$FX" "$STUB_CLEAN"
945
+ git -C "$FX" update-ref -d refs/remotes/origin/main
946
+ run_gen "$FX" s-noorigin
947
+ cases=$((cases + 1))
948
+ if [[ "$RC" == "69" ]] && grep -qF 'FAILED (69): no origin/main and no origin/HEAD' "$TMPROOT/s-noorigin.out"; then
949
+ ok "S-noorigin: no base ref exits 69 with the fetch instruction on STDOUT"
950
+ else
951
+ bad "S-noorigin: rc=$RC (want 69) stdout: $(tail -2 "$TMPROOT/s-noorigin.out" | tr '\n' ' ')"
952
+ fi
953
+ cases=$((cases + 1))
954
+ if [[ -z "$(porcelain "$FX")" && ! -e "$FX/apps/web-platform/scripts" ]]; then
955
+ ok "S-noorigin: nothing was written (porcelain empty, no scripts/ dir)"
956
+ else
957
+ bad "S-noorigin: residue after 69: $(porcelain "$FX" | tr '\n' ' ')"
958
+ fi
959
+ run_gen "$FX" s-noorigin-2
960
+ cases=$((cases + 1))
961
+ if [[ "$RC" == "69" ]]; then
962
+ ok "S-noorigin: the re-run is the same 69, not a 66 on a half-installed gate"
963
+ else
964
+ bad "S-noorigin: re-run rc=$RC (want 69; 66 means a half-installed gate was left behind)"
965
+ fi
966
+
967
+ # --- S-originhead: a founder repo on master (origin/HEAD -> origin/master, no origin/main) -> 0 ---
968
+ FX="$(make_repo s-originhead)"
969
+ assert_fixture_dir "$FX"
970
+ point_node_modules "$FX" "$STUB_CLEAN"
971
+ git -C "$FX" update-ref refs/remotes/origin/master HEAD
972
+ git -C "$FX" update-ref -d refs/remotes/origin/main
973
+ git -C "$FX" symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/master
974
+ run_gen "$FX" s-originhead
975
+ cases=$((cases + 1))
976
+ if [[ "$RC" == "0" ]] && grep -qF 'against origin/master merge-base' "$TMPROOT/s-originhead.err"; then
977
+ ok "S-originhead: falls back to the remote default branch (origin/master) and completes"
978
+ else
979
+ bad "S-originhead: rc=$RC (want 0) stderr: $(grep -F 'merge-base' "$TMPROOT/s-originhead.err" | head -1)"
980
+ fi
981
+
982
+ # --- S-mktemp: TMPDIR that does not exist -> mktemp fails -> 69, tree clean (not a raw set -e abort) --
983
+ FX="$(make_repo s-mktemp)"
984
+ assert_fixture_dir "$FX"
985
+ point_node_modules "$FX" "$STUB_CLEAN"
986
+ RUN_TMPDIR="$TMPROOT/does-not-exist/nested" run_gen "$FX" s-mktemp
987
+ cases=$((cases + 1))
988
+ if [[ "$RC" == "69" ]] && grep -qF 'FAILED (69): cannot create a temp dir' "$TMPROOT/s-mktemp.out"; then
989
+ ok "S-mktemp: an unusable TMPDIR exits 69 with the cause on STDOUT"
990
+ else
991
+ bad "S-mktemp: rc=$RC (want 69) stdout: $(tail -2 "$TMPROOT/s-mktemp.out" | tr '\n' ' ')"
992
+ fi
993
+ cases=$((cases + 1))
994
+ if [[ -z "$(porcelain "$FX")" && ! -e "$FX/apps/web-platform/scripts" ]]; then
995
+ ok "S-mktemp: every emitted artifact removed (the cleanup was armed before the first write)"
996
+ else
997
+ bad "S-mktemp: residue after 69: $(porcelain "$FX" | tr '\n' ' ')"
998
+ fi
999
+
1000
+ # --- S-symlink-server: server/ is a committed symlink to a dir OUTSIDE the repo -> the bite refuses
1001
+ # to write probes through it (71), tree clean, external dir untouched ---------------------------------
1002
+ FX="$(make_repo s-symlink-server)"
1003
+ assert_fixture_dir "$FX"
1004
+ point_node_modules "$FX" "$STUB_CLEAN"
1005
+ mkdir -p "$TMPROOT/ext-server"
1006
+ printf 'export const X = 1;\n' > "$TMPROOT/ext-server/config.ts"
1007
+ rm -rf -- "$FX/apps/web-platform/server"
1008
+ ln -s "$TMPROOT/ext-server" "$FX/apps/web-platform/server"
1009
+ git -C "$FX" add -A && git -C "$FX" commit -q -m "server as symlink" && git -C "$FX" update-ref refs/remotes/origin/main HEAD
1010
+ run_gen "$FX" s-symlink-server
1011
+ cases=$((cases + 1))
1012
+ if [[ "$RC" == "71" ]] && grep -qF 'server is a symlink' "$TMPROOT/s-symlink-server.out"; then
1013
+ ok "S-symlink-server: probes are never written through a symlinked server/ (71, reason on stdout)"
1014
+ else
1015
+ bad "S-symlink-server: rc=$RC (want 71) stdout: $(grep -F FAILED "$TMPROOT/s-symlink-server.out" | head -1)"
1016
+ fi
1017
+ cases=$((cases + 1))
1018
+ if [[ -z "$(porcelain "$FX")" && "$(ls -A "$TMPROOT/ext-server" | tr '\n' ' ')" == "config.ts " ]]; then
1019
+ ok "S-symlink-server: tree clean and the external directory holds only its own file"
1020
+ else
1021
+ bad "S-symlink-server: porcelain=[$(porcelain "$FX" | tr '\n' ' ')] ext=[$(ls -A "$TMPROOT/ext-server" | tr '\n' ' ')]"
1022
+ fi
1023
+
1024
+ # --- S-lock: a second run while another is in progress -> 69 naming the pid, nothing written; a
1025
+ # STALE lock (owner gone) is taken over and released after the run ------------------------------
1026
+ FX="$(make_repo s-lock)"
1027
+ assert_fixture_dir "$FX"
1028
+ point_node_modules "$FX" "$STUB_CLEAN"
1029
+ LOCK_DIR="$FX/.git/constraint-scaffold.lock"
1030
+ mkdir -p "$LOCK_DIR"
1031
+ printf '%s\n' "$$" > "$LOCK_DIR/pid" # this suite's own pid: alive for the whole run
1032
+ run_gen "$FX" s-lock-held
1033
+ cases=$((cases + 1))
1034
+ if [[ "$RC" == "69" ]] && grep -qF "FAILED (69): another constraint-scaffold run (pid $$) is in progress" "$TMPROOT/s-lock-held.out" && [[ -z "$(porcelain "$FX")" ]]; then
1035
+ ok "S-lock: a live lock is a 69 naming the owner pid on STDOUT, nothing written"
1036
+ else
1037
+ bad "S-lock: rc=$RC (want 69) stdout: $(grep -F FAILED "$TMPROOT/s-lock-held.out" | head -1) porcelain=[$(porcelain "$FX" | tr '\n' ' ')]"
1038
+ fi
1039
+ cases=$((cases + 1))
1040
+ if [[ -d "$LOCK_DIR" && "$(cat "$LOCK_DIR/pid")" == "$$" ]]; then
1041
+ ok "S-lock: the refused run left the live lock in place (it was not its to release)"
1042
+ else
1043
+ bad "S-lock: the refused run removed or rewrote a lock it did not own"
1044
+ fi
1045
+ sleep 0.01 & DEAD_PID=$!; wait "$DEAD_PID" 2>/dev/null || true
1046
+ printf '%s\n' "$DEAD_PID" > "$LOCK_DIR/pid" # a pid that has exited: a stale lock
1047
+ run_gen "$FX" s-lock-stale
1048
+ cases=$((cases + 1))
1049
+ if [[ "$RC" == "0" && ! -e "$LOCK_DIR" ]] && grep -qE "$VERDICT_RE" "$TMPROOT/s-lock-stale.out"; then
1050
+ ok "S-lock: a stale lock is taken over, the run completes, and the lock is released"
1051
+ else
1052
+ bad "S-lock: stale-lock run rc=$RC lock_present=$([[ -e "$LOCK_DIR" ]] && echo yes || echo no)"
1053
+ fi
1054
+
1055
+ # --- locate (or install) the dependency-cruiser binary -----------------------
1056
+ # Fast path: the installed web-platform binary (present locally + in the
1057
+ # test-webplat CI shard). Fallback: the test-scripts shard has node+npm but no
1058
+ # web-platform deps, so install dependency-cruiser on demand into a temp dir.
1059
+ DEPCRUISE="$APP/node_modules/.bin/depcruise"
1060
+ if [[ ! -x "$DEPCRUISE" ]]; then
1061
+ echo "# dependency-cruiser not installed in apps/web-platform — installing on demand..." >&2
1062
+ # Seed an empty package.json so npm installs INTO this temp dir. Without it,
1063
+ # `npm install` in a bare dir walks UP the tree, finds a parent project, reports
1064
+ # "up to date", and installs nothing → binary missing (env-dependent fragility).
1065
+ printf '{"name":"constraint-scaffold-test","private":true}\n' > "$TMPROOT/package.json"
1066
+ # typescript is REQUIRED alongside dependency-cruiser: depcruise cannot parse
1067
+ # .ts/.tsx (nor honor tsConfig alias resolution + tsPreCompilationDeps) without
1068
+ # the TS compiler. Omitting it makes depcruise emit ZERO edges from the .tsx
1069
+ # fixtures → every positive-detection assertion silently fails (the scripts-shard
1070
+ # CI failure on PR #5770). The non-vacuity guard below fails loud if this regresses.
1071
+ ( cd "$TMPROOT" && npm install --no-audit --no-fund dependency-cruiser@^16 typescript >/dev/null 2>&1 ) \
1072
+ || { echo "fatal: could not install dependency-cruiser + typescript"; exit 2; }
1073
+ DEPCRUISE="$TMPROOT/node_modules/.bin/depcruise"
1074
+ [[ -x "$DEPCRUISE" ]] || { echo "fatal: dependency-cruiser binary still missing after install"; exit 2; }
1075
+ fi
1076
+ NODE_MODULES="$(cd "$(dirname "$DEPCRUISE")/.." && pwd)"
1077
+ REAL_VERSION="$(node -p 'require(process.argv[1] + "/dependency-cruiser/package.json").version' "$NODE_MODULES" 2>/dev/null || true)"
1078
+
1079
+ # --- Toolchain probe: can this depcruise actually PARSE the .tsx fixtures? -----
1080
+ # Same probe as boundary.test.sh: when depcruise parses ZERO modules from a fixture that
1081
+ # demonstrably contains .tsx client files, the TOOLCHAIN is the problem, not the gate — SKIP
1082
+ # cleanly (exit 0). Everything above this point is toolchain-FREE and carries its own floor.
1083
+ PROBE_FX="$(make_repo toolchain-probe --populated)"
1084
+ assert_fixture_dir "$PROBE_FX"
1085
+ point_node_modules "$PROBE_FX" "$NODE_MODULES"
1086
+ cp "$REF/depcruise-config.template" "$PROBE_FX/apps/web-platform/.dependency-cruiser.cjs"
1087
+ PARSED_COMPONENTS="$( cd "$PROBE_FX/apps/web-platform" && "$DEPCRUISE" --config .dependency-cruiser.cjs --output-type json components server 2>/dev/null | node -e '
1088
+ let s=""; process.stdin.on("data",d=>s+=d); process.stdin.on("end",()=>{
1089
+ let n=0; try { const j=JSON.parse(s);
1090
+ for (const m of j.modules||[]) if (/^components\//.test(m.source||"")) n++;
1091
+ } catch(e){ n=0; } process.stdout.write(String(n)); });' 2>/dev/null )"
1092
+ if ! [[ "$PARSED_COMPONENTS" =~ ^[0-9]+$ ]] || [[ "$PARSED_COMPONENTS" -lt 1 ]]; then
1093
+ echo "SKIP: depcruise parsed 0 component modules from the .tsx fixtures — toolchain"
1094
+ echo " unavailable in this shard. The bite-proof is validated end-to-end by the"
1095
+ echo " real-toolchain cases in the test-webplat shard + local runs."
1096
+ # This `exit 0` is a SECOND clean-exit door, so the trailer's conservation check and floor
1097
+ # are repeated here rather than referenced: a guarantee that only guards one of two exits is
1098
+ # not a guarantee. Everything above this point is toolchain-FREE and runs in EVERY shard, so
1099
+ # it carries its own floor. Conservation FIRST (ADR-193 #4), both reported directly.
1100
+ if [[ $((pass + fail)) -ne "$cases" ]]; then
1101
+ printf '\n[FATAL] accounting: pass+fail (%d) != cases (%d).\n' "$((pass + fail))" "$cases" >&2
1102
+ if [[ $((pass + fail)) -lt "$cases" ]]; then
1103
+ printf ' An assertion was counted but its verdict was not recorded — that is what a neutered ok()/bad() looks like.\n' >&2
1104
+ else
1105
+ printf ' A verdict was recorded at a call site with no `cases=$((cases + 1))` before it. This is a harness bug, not a product failure: add the increment at that call site.\n' >&2
1106
+ fi
1107
+ echo "bite-proof.test.sh: $pass passed, $fail failed ($cases assertions, SKIPPED at the toolchain probe)"
1108
+ exit 1
1109
+ fi
1110
+ TOOLCHAIN_FREE_MIN_ASSERTIONS=76
1111
+ if [[ "$cases" -lt "$TOOLCHAIN_FREE_MIN_ASSERTIONS" ]]; then
1112
+ printf '\n[FATAL] anti-vacuity floor (toolchain-free half): only %d assertion(s) ran, expected >= %d.\n' \
1113
+ "$cases" "$TOOLCHAIN_FREE_MIN_ASSERTIONS" >&2
1114
+ printf ' Arms were deleted or skipped before the toolchain probe; skipping the depcruise half is sanctioned, asserting nothing at all is not.\n' >&2
1115
+ echo "bite-proof.test.sh: $pass passed, $fail failed ($cases assertions, SKIPPED at the toolchain probe)"
1116
+ exit 1
1117
+ fi
1118
+ echo "bite-proof.test.sh: $pass passed, $fail failed ($cases assertions, SKIPPED at the toolchain probe)"
1119
+ [[ "$fail" -eq 0 ]]
1120
+ exit
1121
+ fi
1122
+
1123
+ # =============================================================================================
1124
+ # Segment 3 — REAL TOOLCHAIN.
1125
+ # =============================================================================================
1126
+ echo "# --- real toolchain (dependency-cruiser $REAL_VERSION) ---"
1127
+
1128
+ # --- C1: default mode, full run against the real depcruise ----------------------------------------
1129
+ FX="$(make_repo c1-real --claude)"
1130
+ assert_fixture_dir "$FX"
1131
+ point_node_modules "$FX" "$NODE_MODULES"
1132
+ run_gen "$FX" c1
1133
+ cases=$((cases + 1))
1134
+ if [[ "$RC" == "0" ]]; then
1135
+ ok "C1: default mode exits 0 with the real toolchain"
1136
+ else
1137
+ bad "C1: expected 0, got rc=$RC (stdout: $(tail -3 "$TMPROOT/c1.out" | tr '\n' ' ')) (stderr: $(tail -3 "$TMPROOT/c1.err" | tr '\n' ' '))"
1138
+ fi
1139
+ # C1-color: the same run with FORCE_COLOR=1 in the environment (some CI images, some shells set
1140
+ # it) — the real depcruise decorates its `error <rule>:` lines; the runner's NO_COLOR=1 plus the
1141
+ # scaffold's strip must keep this a pass, never a 72 that rolls a correct install back.
1142
+ FX_COLOR="$(make_repo c1-color)"
1143
+ assert_fixture_dir "$FX_COLOR"
1144
+ point_node_modules "$FX_COLOR" "$NODE_MODULES"
1145
+ FORCE_COLOR=1 run_gen "$FX_COLOR" c1-color
1146
+ cases=$((cases + 1))
1147
+ if [[ "$RC" == "0" ]] && grep -qE "$VERDICT_RE" "$TMPROOT/c1-color.out"; then
1148
+ ok "C1-color: FORCE_COLOR=1 in the environment still proves the bite (rc 0, verdict line)"
1149
+ else
1150
+ bad "C1-color: rc=$RC verdict=$(grep -cE "$VERDICT_RE" "$TMPROOT/c1-color.out" || true) (stdout: $(grep -F FAILED "$TMPROOT/c1-color.out" | head -1))"
1151
+ fi
1152
+ C1_VERDICT="$(grep -E "$VERDICT_RE" "$TMPROOT/c1.out" | head -1)"
1153
+ cases=$((cases + 1))
1154
+ if [[ -n "$C1_VERDICT" && "$C1_VERDICT" == *"${RULE_DIRECT}@direct"* && "$C1_VERDICT" == *"${RULE_TRANSITIVE}@via-hop"* ]]; then
1155
+ ok "C1: verdict line names both rules on their edges: $C1_VERDICT"
1156
+ else
1157
+ bad "C1: verdict line missing or malformed: $(grep -F 'bite-proof' "$TMPROOT/c1.out" | head -1)"
1158
+ fi
1159
+ C1_VER="${C1_VERDICT##*depcruise=}"
1160
+ cases=$((cases + 1))
1161
+ if [[ -n "$REAL_VERSION" && "$C1_VER" == "$REAL_VERSION" && "$C1_VER" != "0.0.0-stub" && "$C1_VER" =~ ^[0-9]+\. ]]; then
1162
+ ok "C1: depcruise=$C1_VER equals the real dependency-cruiser/package.json version (not the stub)"
1163
+ else
1164
+ bad "C1: verdict version '$C1_VER' != real '$REAL_VERSION' (or looks like the stub)"
1165
+ fi
1166
+ cases=$((cases + 1))
1167
+ if [[ "$(count_marker "$FX/apps/web-platform/server/README.md" "$README_START")" == "1" && "$(count_marker "$FX/CLAUDE.md" "$POINTER_MARKER")" == "1" ]]; then
1168
+ ok "C1: README block once, pointer once"
1169
+ else
1170
+ bad "C1: readme-start=$(count_marker "$FX/apps/web-platform/server/README.md" "$README_START") pointer=$(count_marker "$FX/CLAUDE.md" "$POINTER_MARKER")"
1171
+ fi
1172
+ cases=$((cases + 1))
1173
+ if [[ -f "$FX/apps/web-platform/.dependency-cruiser-known-violations.json" ]] && node -e 'const b=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.exit(Array.isArray(b)?0:1)' "$FX/apps/web-platform/.dependency-cruiser-known-violations.json"; then
1174
+ ok "C1: baseline present and a JSON array"
1175
+ else
1176
+ bad "C1: baseline missing or not a JSON array"
1177
+ fi
1178
+ cases=$((cases + 1))
1179
+ if [[ "$(worktree_count "$FX")" == "1" && ! -e "$FX/apps/web-platform/$PROBE_DIR" && ! -e "$FX/apps/web-platform/server/__constraint_scaffold_bite_probe__.ts" ]]; then
1180
+ ok "C1: worktree list clean; no probe file in the founder tree"
1181
+ else
1182
+ bad "C1: worktrees=$(worktree_count "$FX") probe residue=$(ls -d "$FX/apps/web-platform/$PROBE_DIR" "$FX/apps/web-platform/server/__constraint_scaffold_bite_probe__.ts" 2>/dev/null | tr '\n' ' ')"
1183
+ fi
1184
+ # The emitted gate is green on the founder tree afterwards (the runner's own invocation).
1185
+ C1_GATE_RC=0
1186
+ CONSTRAINT_GATES_DIR="$FX/apps/web-platform" bash "$FX/apps/web-platform/scripts/constraint-gates.sh" >"$TMPROOT/c1-gate.log" 2>&1 || C1_GATE_RC=$?
1187
+ cases=$((cases + 1))
1188
+ if [[ "$C1_GATE_RC" -eq 0 ]]; then
1189
+ ok "C1: the emitted runner is green on the founder tree after install"
1190
+ else
1191
+ bad "C1: emitted runner rc=$C1_GATE_RC on the founder tree: $(tail -2 "$TMPROOT/c1-gate.log" | tr '\n' ' ')"
1192
+ fi
1193
+
1194
+ # --- C8: populated tree (harness row d) ----------------------------------------------------------
1195
+ # An unrelated "use client" module with a TYPE-ONLY server import, plus a client module
1196
+ # value-importing a VALUE-SAFE server module: the direct rule baselines it (one `dependency`
1197
+ # entry captured at the merge-base), the transitive rule excludes it via pathNot, and the bite
1198
+ # must still report pass -> fail(both) -> pass on top of that baseline.
1199
+ FX="$(make_repo c8-populated --populated)"
1200
+ assert_fixture_dir "$FX"
1201
+ point_node_modules "$FX" "$NODE_MODULES"
1202
+ run_gen "$FX" c8
1203
+ cases=$((cases + 1))
1204
+ if [[ "$RC" == "0" ]] && grep -qE "$VERDICT_RE" "$TMPROOT/c8.out"; then
1205
+ ok "C8: populated tree -> rc 0 with the verdict line"
1206
+ else
1207
+ bad "C8: rc=$RC verdict=$(grep -cE "$VERDICT_RE" "$TMPROOT/c8.out" || true) (stdout: $(grep -F FAILED "$TMPROOT/c8.out" | head -1))"
1208
+ fi
1209
+ C8_DEP_CT="$(node -e 'const b=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.stdout.write(String((Array.isArray(b)?b:[]).filter(e=>e&&e.type==="dependency").length))' "$FX/apps/web-platform/.dependency-cruiser-known-violations.json" 2>/dev/null || echo -1)"
1210
+ C8_REACH_CT="$(node -e 'const b=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.stdout.write(String((Array.isArray(b)?b:[]).filter(e=>e&&e.type==="reachability").length))' "$FX/apps/web-platform/.dependency-cruiser-known-violations.json" 2>/dev/null || echo -1)"
1211
+ cases=$((cases + 1))
1212
+ if [[ "$C8_DEP_CT" == "1" && "$C8_REACH_CT" == "0" ]]; then
1213
+ ok "C8: baseline carries exactly one direct value-safe entry and zero reachability entries"
1214
+ else
1215
+ bad "C8: baseline dependency entries=$C8_DEP_CT reachability entries=$C8_REACH_CT (want 1 / 0)"
1216
+ fi
1217
+
1218
+ # --- C-e: committed CLAUDE.md + server/README.md (harness row e — the P0 fixture) ----------------
1219
+ FX="$(make_repo ce-instructions --claude --readme)"
1220
+ assert_fixture_dir "$FX"
1221
+ point_node_modules "$FX" "$NODE_MODULES"
1222
+ run_gen "$FX" ce
1223
+ cases=$((cases + 1))
1224
+ if [[ "$RC" == "0" ]]; then
1225
+ ok "C-e: default mode succeeds with a committed CLAUDE.md and server/README.md (no 67)"
1226
+ else
1227
+ bad "C-e: rc=$RC (stderr: $(tail -2 "$TMPROOT/ce.err" | tr '\n' ' '))"
1228
+ fi
1229
+ cases=$((cases + 1))
1230
+ if [[ "$(count_marker "$FX/apps/web-platform/server/README.md" "$README_START")" == "1" && "$(count_marker "$FX/CLAUDE.md" "$POINTER_MARKER")" == "1" ]] \
1231
+ && grep -qF 'Prior README prose.' "$FX/apps/web-platform/server/README.md" && grep -qF 'Prior instructions.' "$FX/CLAUDE.md"; then
1232
+ ok "C-e: appended once to each, prior content intact"
1233
+ else
1234
+ bad "C-e: readme-start=$(count_marker "$FX/apps/web-platform/server/README.md" "$README_START") pointer=$(count_marker "$FX/CLAUDE.md" "$POINTER_MARKER")"
1235
+ fi
1236
+ # Both committed files lacked no trailing newline; check the append started on its own line.
1237
+ cases=$((cases + 1))
1238
+ if grep -qE "^$README_START\$" "$FX/apps/web-platform/server/README.md" && grep -qE "^The client/server import boundary.*$POINTER_MARKER\$" "$FX/CLAUDE.md"; then
1239
+ ok "C-e: each appended block starts on its own line"
1240
+ else
1241
+ bad "C-e: an append landed mid-line"
1242
+ fi
1243
+ # Re-running refresh after committing must be a no-op on both prose files.
1244
+ git -C "$FX" add -A
1245
+ git -C "$FX" commit -q -m "install gate"
1246
+ git -C "$FX" update-ref refs/remotes/origin/main HEAD
1247
+ cp "$FX/CLAUDE.md" "$TMPROOT/ce-claude.before"
1248
+ run_gen "$FX" ce-refresh --refresh-baseline
1249
+ cases=$((cases + 1))
1250
+ if [[ "$RC" == "0" ]] && cmp -s "$FX/CLAUDE.md" "$TMPROOT/ce-claude.before" && grep -qE "$VERDICT_RE" "$TMPROOT/ce-refresh.out"; then
1251
+ ok "C-e: --refresh-baseline afterwards is rc 0 with the verdict and leaves CLAUDE.md untouched"
1252
+ else
1253
+ bad "C-e: refresh rc=$RC claude-changed=$(cmp -s "$FX/CLAUDE.md" "$TMPROOT/ce-claude.before" && echo no || echo YES)"
1254
+ fi
1255
+
1256
+ # --- C-warn: committed config with the transitive rule at severity "warn" -> 72 (real) -----------
1257
+ FX="$(make_repo cwarn)"
1258
+ assert_fixture_dir "$FX"
1259
+ point_node_modules "$FX" "$NODE_MODULES"
1260
+ mkdir -p "$FX/apps/web-platform/scripts"
1261
+ node -e '
1262
+ const fs=require("fs"); let s=fs.readFileSync(process.argv[1],"utf8");
1263
+ const i=s.indexOf("name: \"no-client-to-server-secret-transitive\"");
1264
+ const j=s.indexOf("severity: \"error\"", i);
1265
+ if (i<0||j<0) process.exit(3);
1266
+ s=s.slice(0,j)+"severity: \"warn\""+s.slice(j+"severity: \"error\"".length);
1267
+ fs.writeFileSync(process.argv[2], s);' "$REF/depcruise-config.template" "$FX/apps/web-platform/.dependency-cruiser.cjs"
1268
+ cp "$REF/shared-runner.template" "$FX/apps/web-platform/scripts/constraint-gates.sh"
1269
+ chmod +x "$FX/apps/web-platform/scripts/constraint-gates.sh"
1270
+ printf '[]\n' > "$FX/apps/web-platform/.dependency-cruiser-known-violations.json"
1271
+ git -C "$FX" add -A
1272
+ git -C "$FX" commit -q -m "gate with the transitive rule softened to warn"
1273
+ git -C "$FX" update-ref refs/remotes/origin/main HEAD
1274
+ run_gen "$FX" cwarn --refresh-baseline
1275
+ cases=$((cases + 1))
1276
+ if [[ "$RC" == "72" ]]; then
1277
+ ok "C-warn: a 'warn' transitive line is rejected by the '^\\s*error ' anchor -> 72 with the real toolchain"
1278
+ else
1279
+ bad "C-warn: expected 72, got rc=$RC (stdout: $(grep -F 'bite-proof' "$TMPROOT/cwarn.out" | head -1))"
1280
+ fi
1281
+ cases=$((cases + 1))
1282
+ if grep -qE '^\s*warn no-client-to-server-secret-transitive: .*via-hop\.tsx' "$TMPROOT/cwarn.out"; then
1283
+ ok "C-warn: the echoed log tail shows the 'warn' call-form on via-hop.tsx (the real output shape)"
1284
+ else
1285
+ bad "C-warn: log tail does not show the warn line"
1286
+ fi
1287
+ cases=$((cases + 1))
1288
+ if grep -qF 'refresh mode' "$TMPROOT/cwarn.out" && [[ -f "$FX/apps/web-platform/.dependency-cruiser.cjs" && -f "$FX/apps/web-platform/scripts/constraint-gates.sh" ]]; then
1289
+ ok "C-warn: refresh mode removes nothing (artifacts are committed) and says the baseline was rewritten"
1290
+ else
1291
+ bad "C-warn: refresh-mode failure message or artifacts wrong"
1292
+ fi
1293
+ cases=$((cases + 1))
1294
+ if [[ "$(worktree_count "$FX")" == "1" ]]; then
1295
+ ok "C-warn: worktree removed after the failed bite"
1296
+ else
1297
+ bad "C-warn: $(worktree_count "$FX") worktrees registered"
1298
+ fi
1299
+
1300
+ echo "---"
1301
+
1302
+ # --- Accounting conservation (ADR-193 #3) --------------------------------------------------
1303
+ # Ordered BEFORE the floor (ADR-193 #4): a neutered bad()/ok() deflates the verdict counters, so
1304
+ # the floor below would ALSO trip and would report the misleading "arms were deleted". This says
1305
+ # "a verdict was discarded" instead. Reported with `printf >&2` + `exit 1` DIRECTLY, never
1306
+ # through bad(): a check that reports by calling the verdict helper increments the very counter
1307
+ # the exit status reads, so neutering bad() silences the rows AND the check meant to notice the
1308
+ # silence. Every assertion records exactly one verdict, so pass+fail MUST equal cases; because
1309
+ # `cases` moves at the CALL SITE and not inside the verdict helpers, the identity is a real
1310
+ # constraint rather than a tautology. The literal `[FATAL] accounting` is load-bearing —
1311
+ # guard-vacuity-floor's ARM 10 builds its conservation population by grepping that exact string.
1312
+ if [[ $((pass + fail)) -ne "$cases" ]]; then
1313
+ printf '\n[FATAL] accounting: pass+fail (%d) != cases (%d).\n' "$((pass + fail))" "$cases" >&2
1314
+ if [[ $((pass + fail)) -lt "$cases" ]]; then
1315
+ printf ' An assertion was counted but its verdict was not recorded — that is what a neutered ok()/bad() looks like.\n' >&2
1316
+ else
1317
+ printf ' A verdict was recorded at a call site with no `cases=$((cases + 1))` before it. This is a harness bug, not a product failure: add the increment at that call site.\n' >&2
1318
+ fi
1319
+ echo "bite-proof.test.sh: $pass passed, $fail failed ($cases assertions)"
1320
+ exit 1
1321
+ fi
1322
+
1323
+ # --- Anti-vacuity floor (ADR-193 #1) -------------------------------------------------------
1324
+ # Reads the INDEPENDENT `cases` counter, and reports with `printf >&2` + `exit 1` DIRECTLY.
1325
+ # A harness that silently asserted nothing (a fixture generator that no-ops, an editing slip that
1326
+ # drops a block) would otherwise print a clean smaller total and exit 0. See MIN_ASSERTIONS above
1327
+ # for how the number is derived and when to ratchet it.
1328
+ MIN_ASSERTIONS=94
1329
+ if [[ "$cases" -lt "$MIN_ASSERTIONS" ]]; then
1330
+ printf '\n[FATAL] anti-vacuity floor: only %d assertion(s) ran, expected >= %d.\n' \
1331
+ "$cases" "$MIN_ASSERTIONS" >&2
1332
+ printf ' Arms were deleted or skipped; a green run here would be a coverage loss.\n' >&2
1333
+ echo "bite-proof.test.sh: $pass passed, $fail failed ($cases assertions)"
1334
+ exit 1
1335
+ fi
1336
+
1337
+ echo "bite-proof.test.sh: $pass passed, $fail failed ($cases assertions)"
1338
+ [[ "$fail" -eq 0 ]]