@soleur/opencode 0.0.0-stage → 3.354.2

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 (832) 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 +660 -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 +29 -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 +361 -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 +171 -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 +818 -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 +411 -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 +509 -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 +115 -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 +165 -0
  247. package/scripts/sync-opencode-adapter.ts +498 -0
  248. package/scripts/sync-pr-behind.sh +647 -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 +67 -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 +143 -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 +120 -0
  493. package/skills/fix-issue/references/exclude-label-jq-snippet.md +97 -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 +670 -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 +331 -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 +141 -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 +1620 -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-evidence-comment.md +36 -0
  674. package/skills/review/references/review-todo-structure.md +165 -0
  675. package/skills/review/references/risk-tier-and-fix-rounds.md +97 -0
  676. package/skills/review/references/semgrep-custom-rules.yaml +174 -0
  677. package/skills/review/references/ultrathink-phases.md +98 -0
  678. package/skills/review/references/wfs.md +6 -0
  679. package/skills/review/scripts/emit-review-trailer.sh +499 -0
  680. package/skills/review/scripts/ensure-semgrep.sh +99 -0
  681. package/skills/review/scripts/fix-round-seats.sh +197 -0
  682. package/skills/review/test/emit-review-trailer.test.sh +387 -0
  683. package/skills/review/test/ensure-semgrep.test.sh +111 -0
  684. package/skills/review/test/fix-round-seats.test.sh +237 -0
  685. package/skills/review/workflows/README.md +147 -0
  686. package/skills/review/workflows/review.workflow.js +667 -0
  687. package/skills/schedule/SKILL.md +796 -0
  688. package/skills/seo-aeo/SKILL.md +139 -0
  689. package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
  690. package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
  691. package/skills/ship/SKILL.md +3023 -0
  692. package/skills/ship/references/adr-ordinal-collision.md +47 -0
  693. package/skills/ship/references/affected-gate-contract.md +115 -0
  694. package/skills/ship/references/ci-run-diagnosis.md +29 -0
  695. package/skills/ship/references/ci-workflow-authoring.md +68 -0
  696. package/skills/ship/references/cleanup-merged-archival.md +39 -0
  697. package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
  698. package/skills/ship/references/cto-ruling-armed-push.md +5 -0
  699. package/skills/ship/references/decision-challenges-render.md +71 -0
  700. package/skills/ship/references/delegated-act-authoring-form.md +7 -0
  701. package/skills/ship/references/detached-battery-verdict.md +13 -0
  702. package/skills/ship/references/epic-close-sweep.md +62 -0
  703. package/skills/ship/references/feature-tweet-draft.md +42 -0
  704. package/skills/ship/references/followthrough-issue-body-template.md +47 -0
  705. package/skills/ship/references/followthrough-stub-template.sh +92 -0
  706. package/skills/ship/references/merge-queue-dequeue.md +39 -0
  707. package/skills/ship/references/operator-holds.md +179 -0
  708. package/skills/ship/references/pipeline-tally-render.md +43 -0
  709. package/skills/ship/references/pr-overlap.md +153 -0
  710. package/skills/ship/references/queue-mode.md +7 -0
  711. package/skills/ship/references/ready-run-wait.md +36 -0
  712. package/skills/ship/references/red-on-main-quarantine.md +110 -0
  713. package/skills/ship/references/register-update-pr-pattern.md +88 -0
  714. package/skills/ship/references/settle-then-admin-merge.md +130 -0
  715. package/skills/ship/references/supabase-column-probe.md +13 -0
  716. package/skills/ship/scripts/auto-close-scan.sh +76 -0
  717. package/skills/ship/scripts/battery-owed.sh +403 -0
  718. package/skills/ship/scripts/hold-gate-eval.sh +410 -0
  719. package/skills/ship/scripts/net-issue-flow.sh +665 -0
  720. package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
  721. package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
  722. package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
  723. package/skills/skill-creator/SKILL.md +299 -0
  724. package/skills/skill-creator/references/api-security.md +248 -0
  725. package/skills/skill-creator/references/authoring-levers.md +110 -0
  726. package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
  727. package/skills/skill-creator/references/best-practices.md +431 -0
  728. package/skills/skill-creator/references/common-patterns.md +639 -0
  729. package/skills/skill-creator/references/core-principles.md +193 -0
  730. package/skills/skill-creator/references/executable-code.md +188 -0
  731. package/skills/skill-creator/references/iteration-and-testing.md +509 -0
  732. package/skills/skill-creator/references/official-spec.md +193 -0
  733. package/skills/skill-creator/references/recommended-structure.md +180 -0
  734. package/skills/skill-creator/references/skill-structure.md +396 -0
  735. package/skills/skill-creator/references/using-scripts.md +122 -0
  736. package/skills/skill-creator/references/using-templates.md +120 -0
  737. package/skills/skill-creator/references/workflows-and-validation.md +541 -0
  738. package/skills/skill-creator/scripts/init_skill.py +322 -0
  739. package/skills/skill-creator/scripts/package_skill.py +110 -0
  740. package/skills/skill-creator/scripts/quick_validate.py +64 -0
  741. package/skills/skill-creator/templates/router-skill.md +73 -0
  742. package/skills/skill-creator/templates/simple-skill.md +34 -0
  743. package/skills/skill-creator/workflows/add-reference.md +96 -0
  744. package/skills/skill-creator/workflows/add-script.md +104 -0
  745. package/skills/skill-creator/workflows/add-template.md +83 -0
  746. package/skills/skill-creator/workflows/add-workflow.md +128 -0
  747. package/skills/skill-creator/workflows/audit-skill.md +159 -0
  748. package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
  749. package/skills/skill-creator/workflows/create-new-skill.md +207 -0
  750. package/skills/skill-creator/workflows/get-guidance.md +134 -0
  751. package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
  752. package/skills/skill-creator/workflows/verify-skill.md +226 -0
  753. package/skills/skill-security-scan/SKILL.md +145 -0
  754. package/skills/skill-security-scan/references/disclaimer.md +21 -0
  755. package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
  756. package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
  757. package/skills/skill-security-scan/references/override-mechanism.md +171 -0
  758. package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
  759. package/skills/skill-security-scan/references/regex-patterns.md +136 -0
  760. package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
  761. package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
  762. package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
  763. package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
  764. package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
  765. package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
  766. package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
  767. package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
  768. package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
  769. package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
  770. package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
  771. package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
  772. package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
  773. package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
  774. package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
  775. package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
  776. package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
  777. package/skills/skill-security-scan/scripts/lib.sh +197 -0
  778. package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
  779. package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
  780. package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
  781. package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
  782. package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
  783. package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
  784. package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
  785. package/skills/social-distribute/SKILL.md +485 -0
  786. package/skills/spec-templates/SKILL.md +274 -0
  787. package/skills/sync/SKILL.md +49 -0
  788. package/skills/test-browser/SKILL.md +464 -0
  789. package/skills/test-browser/references/feature-map.md +161 -0
  790. package/skills/test-fix-loop/SKILL.md +159 -0
  791. package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
  792. package/skills/triage/SKILL.md +402 -0
  793. package/skills/trigger-cron/SKILL.md +136 -0
  794. package/skills/trigger-cron/scripts/trigger.sh +198 -0
  795. package/skills/user-set-role/SKILL.md +147 -0
  796. package/skills/user-set-role/scripts/set-role.sh +218 -0
  797. package/skills/user-story-writer/SKILL.md +77 -0
  798. package/skills/ux-audit/SKILL.md +251 -0
  799. package/skills/ux-audit/references/finding.schema.json +62 -0
  800. package/skills/ux-audit/references/route-list.yaml +95 -0
  801. package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
  802. package/skills/ux-audit/scripts/bot-signin.ts +135 -0
  803. package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
  804. package/skills/verification-scaffold/SKILL.md +73 -0
  805. package/skills/verification-scaffold/references/gitleaks.toml.template +13 -0
  806. package/skills/verification-scaffold/references/guardrail-audit.md +156 -0
  807. package/skills/verification-scaffold/references/guards/block-stash-hook.sh.template +72 -0
  808. package/skills/verification-scaffold/references/guards/commit-on-main-hook.sh.template +82 -0
  809. package/skills/verification-scaffold/references/guards/orphan-test-check.sh.template +102 -0
  810. package/skills/verification-scaffold/references/guards/precommit-guard.sh.template +115 -0
  811. package/skills/verification-scaffold/references/guards/secret-scan-hook.sh.template +176 -0
  812. package/skills/verification-scaffold/references/guards/verification-gates.yml.template +49 -0
  813. package/skills/verification-scaffold/references/guards/wrapper-boundary.sh.template +47 -0
  814. package/skills/verification-scaffold/references/guards.tsv +21 -0
  815. package/skills/verification-scaffold/references/verification-readme.template +55 -0
  816. package/skills/verification-scaffold/scripts/verification-scaffold.sh +785 -0
  817. package/skills/verification-scaffold/test/bite-proof.test.sh +159 -0
  818. package/skills/verification-scaffold/test/generator.test.sh +319 -0
  819. package/skills/verification-scaffold/test/manifest.test.sh +144 -0
  820. package/skills/work/SKILL.md +1480 -0
  821. package/skills/work/references/end-of-work-resume.md +7 -0
  822. package/skills/work/references/hook-test-main-cwd.md +24 -0
  823. package/skills/work/references/key-principles.md +83 -0
  824. package/skills/work/references/property-testing.md +122 -0
  825. package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
  826. package/skills/work/references/work-agent-teams.md +97 -0
  827. package/skills/work/references/work-lifecycle-parallel.md +147 -0
  828. package/skills/work/references/work-lockfile-bumps.md +97 -0
  829. package/skills/work/references/work-scratch-sandboxes.md +212 -0
  830. package/skills/work/references/work-subagent-fanout.md +61 -0
  831. package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
  832. package/skills/xcode-test/SKILL.md +360 -0
@@ -0,0 +1,780 @@
1
+ #!/usr/bin/env bash
2
+ # Guard 1 (#8377, ADR-235) — resolve-regenerable-conflicts.sh fails CLOSED.
3
+ #
4
+ # ── THE PROPERTY ────────────────────────────────────────────────────────────────────────────
5
+ # The resolver commits a merge ONLY when every conflicted path is a CONTENT conflict on a
6
+ # resolvable path, the renderer rendered each one from the merged tree's tracked sources, and
7
+ # nothing else moved. On any other input the tree is byte-identical to entry and nothing is
8
+ # committed. Every non-happy row asserts BOTH a non-zero exit AND an unchanged tree fingerprint.
9
+ #
10
+ # ── THE ARM CONTRACT (ADR-235 amendment 2026-09-23) ─────────────────────────────────────────
11
+ # An arm is an argv PREFIX; the resolver appends `--root <staging> --out <file>`. <staging>
12
+ # holds ONLY the tracked LikeC4 sources copied out of the merged tree's objects; the arm writes
13
+ # its artifact to <file>, never to the worktree. The stubs below refuse any other argv shape.
14
+ #
15
+ # ── WHY THE SUT RUNS FROM A SANDBOX PLUGIN COPY ─────────────────────────────────────────────
16
+ # The production arm runs render-c4-model.sh from BESIDE THE RESOLVER. A stub is injected the
17
+ # way a real install is laid out: a copy of the SUT in <root>/scripts/ with the stub as its
18
+ # sibling and a <root>/.claude-plugin/plugin.json. Copies are taken from $SUT at run time, so a
19
+ # mutation battery that edits $SUT is measured by every row; the ratchet rows read $SUT itself.
20
+ #
21
+ # tree_fp = HEAD + porcelain status (incl. untracked) + every index entry + MERGE_HEAD presence.
22
+ export TMPDIR="${TMPDIR:-/var/tmp}"
23
+
24
+ set -uo pipefail
25
+
26
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ SUT="$SCRIPT_DIR/resolve-regenerable-conflicts.sh"
28
+
29
+ passes=0; fails=0; CASES_RUN=0; FAILED=()
30
+ pass() { passes=$((passes + 1)); echo " PASS: $1"; }
31
+ fail() { fails=$((fails + 1)); FAILED+=("$1"); echo " FAIL: $1" >&2; }
32
+
33
+ _iv_p="$passes"; _iv_f="$fails"; _iv_n="${#FAILED[@]}"
34
+ { pass "self-test"; fail "self-test"; } >/dev/null 2>&1
35
+ if [[ "$passes" -ne $((_iv_p + 1)) || "$fails" -ne $((_iv_f + 1)) || "${#FAILED[@]}" -ne $((_iv_n + 1)) ]]; then
36
+ printf '[FATAL] instrument self-test: the verdict helpers did not both record\n' >&2; exit 1
37
+ fi
38
+ passes=0; fails=0; FAILED=(); CASES_RUN=0
39
+
40
+ [[ -f "$SUT" ]] || { echo "[FATAL] SUT not found at $SUT" >&2; exit 1; }
41
+
42
+ assert_fixture_dir() {
43
+ case "${1-}" in
44
+ "") printf 'FATAL: fixture dir is EMPTY; git -C "" would operate on %s\n' "$PWD" >&2; exit 2 ;;
45
+ */../*|*/..) printf 'FATAL: fixture dir %s contains ..; refusing\n' "$1" >&2; exit 2 ;;
46
+ /proc/*|/sys/*|/dev/*) printf 'FATAL: fixture dir %s is a synthetic-fs path; refusing\n' "$1" >&2; exit 2 ;;
47
+ /|//|/.) printf 'FATAL: fixture dir resolves to the filesystem root; refusing\n' >&2; exit 2 ;;
48
+ /*) : ;;
49
+ *) printf 'FATAL: fixture dir %s is RELATIVE; refusing\n' "$1" >&2; exit 2 ;;
50
+ esac
51
+ }
52
+
53
+ SANDBOX="$(mktemp -d)"; assert_fixture_dir "$SANDBOX"
54
+ SANDBOX="$(cd "$SANDBOX" && pwd -P)"
55
+ trap 'rm -rf "$SANDBOX"' EXIT
56
+ # The resolver's staging dir lives under XDG_CACHE_HOME; keep it inside the sandbox.
57
+ export XDG_CACHE_HOME="$SANDBOX/xdg-cache"
58
+ # The shared fixture env: a prefix sweep of every inherited GIT_* (so a hook-exported GIT_DIR cannot
59
+ # aim these writes at the caller's repo), a discovery ceiling, hermetic config and a pinned
60
+ # identity. Load-bearing here beyond hygiene: scripts/test-all.sh arms core.hooksPath through
61
+ # GIT_CONFIG_* for the whole run, and without the sweep no fixture hook would run in CI.
62
+ # shellcheck source=../test/lib/git-fixture-env.sh
63
+ source "$SCRIPT_DIR/../test/lib/git-fixture-env.sh"
64
+ git_fixture_env "$SANDBOX" || { printf '[FATAL] git_fixture_env refused %s\n' "$SANDBOX" >&2; exit 1; }
65
+
66
+ # Control: a fixture repo's own .git/hooks must RUN. Several rows (a commit hook that rewrites the
67
+ # merge, a signal sent from inside git commit) pass vacuously when hooks are silently disabled —
68
+ # which an inherited `core.hooksPath` in GIT_CONFIG_* does, and outranks the fixture's local config.
69
+ _hk="$SANDBOX/hook-control"; assert_fixture_dir "$_hk"
70
+ git init -q "$_hk" && mkdir -p "$_hk/.git/hooks"
71
+ printf '#!/bin/sh
72
+ : > "%s/hook.ran"
73
+ ' "$SANDBOX" > "$_hk/.git/hooks/pre-commit"; chmod +x "$_hk/.git/hooks/pre-commit"
74
+ git -C "$_hk" -c user.email=t@t -c user.name=t commit -q --allow-empty -m control >/dev/null 2>&1
75
+ CASES_RUN=$((CASES_RUN + 1))
76
+ [[ -e "$SANDBOX/hook.ran" ]] && pass "control: a fixture repo's pre-commit hook runs" \
77
+ || fail "control: fixture hooks do NOT run (an inherited core.hooksPath?) — every hook row below would pass vacuously"
78
+
79
+ DIAG="knowledge-base/engineering/architecture/diagrams"
80
+ MODEL="$DIAG/model.likec4.json"
81
+ SRC="$DIAG/model.c4"
82
+ SECOND="$DIAG/second.json"
83
+
84
+ _git() { git -C "$1" -c user.email=t@t -c user.name=t "${@:2}"; }
85
+
86
+ tree_fp() {
87
+ local r="$1"
88
+ {
89
+ _git "$r" rev-parse HEAD 2>/dev/null || echo "NOHEAD"
90
+ _git "$r" status --porcelain --untracked-files=all 2>/dev/null
91
+ _git "$r" ls-files -s 2>/dev/null
92
+ [[ -f "$r/.git/MERGE_HEAD" ]] && echo "MERGE_IN_PROGRESS"
93
+ } | git hash-object --stdin
94
+ }
95
+
96
+ # ── SANDBOX PLUGIN ROOTS ────────────────────────────────────────────────────────────────────
97
+ # mkplugin <name> <renderer-mode|none> [plugin-name] — echoes the plugin root.
98
+ mkplugin() {
99
+ local root="$SANDBOX/plugins-$1/soleur" mode="$2" pname="${3:-soleur}"; assert_fixture_dir "$root"
100
+ mkdir -p "$root/scripts" "$root/.claude-plugin"
101
+ printf '{\n "name": "%s",\n "description": "fixture"\n}\n' "$pname" > "$root/.claude-plugin/plugin.json"
102
+ cp "$SUT" "$root/scripts/resolve-regenerable-conflicts.sh"
103
+ [[ "$mode" == "none" ]] || _write_renderer "$root/scripts/render-c4-model.sh" "$mode"
104
+ (cd "$root" && pwd -P)
105
+ }
106
+
107
+ # _write_renderer <file> <mode> — a stub with the real renderer's CONTRACT as the resolver
108
+ # calls it: exactly `--root <existing dir> --out <path>` (exit 64 otherwise). The `ok` output
109
+ # DERIVES from the staged model.c4 and names its author, so the committed artifact shows WHICH
110
+ # tree it saw and WHICH renderer wrote it.
111
+ _write_renderer() {
112
+ local f="$1" mode="$2"; assert_fixture_dir "$f"
113
+ {
114
+ printf '#!/usr/bin/env bash\nset -euo pipefail\n'
115
+ printf '[[ "$#" -eq 4 && "$1" == "--root" && -d "$2" && "$3" == "--out" ]] || { echo "stub renderer: bad argv: $*" >&2; exit 64; }\n'
116
+ printf 'R="$2"; O="$4"\n'
117
+ case "$mode" in
118
+ ok) _emit_ok_body "plugin" ;;
119
+ ok-alt) _emit_ok_body "alt-plugin" ;;
120
+ second) printf 'echo "{\\"second\\":\\"regenerated\\"}" | tee "$O" >/dev/null\n' ;;
121
+ fail) printf 'echo "Invalid model.c4"; echo " Line 3: boom (stub diagnostic)"; echo "ERROR: stub renderer refused" >&2; exit 9\n' ;;
122
+ noop) printf 'exit 0\n' ;;
123
+ markers) printf 'printf "%%s\\n" "<<<<<<< ours" "x" "=======" "y" ">>>>>>> theirs" | tee "$O" >/dev/null\n' ;;
124
+ slow) printf 'touch "%s/slow.started"; sleep 5\n' "$SANDBOX"; _emit_ok_body "plugin" ;;
125
+ listroot) printf '(cd "$R" && find . -mindepth 1 -print) | tee "%s/listroot.txt" >/dev/null\n' "$SANDBOX"; _emit_ok_body "plugin" ;;
126
+ *) echo "[FATAL] unknown renderer mode $mode" >&2; exit 2 ;;
127
+ esac
128
+ true
129
+ } > "$f"
130
+ chmod +x "$f"
131
+ }
132
+ _emit_ok_body() { # <author>
133
+ printf 'f="$R/%s"\n' "$SRC"
134
+ printf 'printf %s "$(head -1 "$f")" "$(tail -1 "$f")" | tee "$O" >/dev/null\n' "'{\"from\":\"%s|%s\",\"by\":\"$1\"}\\n'"
135
+ }
136
+
137
+ # mkrepo <name> [bare|wrapper|decoy] — two branches whose .c4 SOURCE text-merges cleanly while
138
+ # the compiled single-line JSON conflicts (the real shape).
139
+ # bare a self-hosted repo: no scripts/, no plugins/ (AC1).
140
+ # wrapper a committed scripts/regenerate-c4-model.sh: not an arm (must never run).
141
+ # decoy a merged tree shipping its own plugins/soleur renderer + genuine-looking plugin.json.
142
+ # Extra files go in the BASE commit, so they are neither untracked nor a conflict of their own.
143
+ mkrepo() {
144
+ local r="$SANDBOX/$1" shape="${2:-bare}"; assert_fixture_dir "$r"
145
+ mkdir -p "$r/$DIAG"
146
+ printf 'first\nx\nx\nx\nx\nx\nx\nx\nx\nlast\n' > "$r/$SRC"
147
+ printf '{"v":"base"}\n' > "$r/$MODEL"
148
+ printf '{"v":"base2"}\n' > "$r/$SECOND"
149
+ printf 'readme base\n' > "$r/README.md"
150
+ case "$shape" in
151
+ bare) : ;;
152
+ wrapper)
153
+ mkdir -p "$r/scripts"
154
+ printf '#!/usr/bin/env bash\ntouch "%s/WRAPPER_RAN"\n' "$SANDBOX" > "$r/scripts/regenerate-c4-model.sh"
155
+ chmod +x "$r/scripts/regenerate-c4-model.sh" ;;
156
+ decoy)
157
+ mkdir -p "$r/plugins/soleur/scripts" "$r/plugins/soleur/.claude-plugin"
158
+ printf '{\n "name": "soleur"\n}\n' > "$r/plugins/soleur/.claude-plugin/plugin.json"
159
+ printf '#!/usr/bin/env bash\ntouch "%s/DECOY_RAN"\n' "$SANDBOX" > "$r/plugins/soleur/scripts/render-c4-model.sh"
160
+ chmod +x "$r/plugins/soleur/scripts/render-c4-model.sh" ;;
161
+ *) echo "[FATAL] unknown repo shape $shape" >&2; exit 2 ;;
162
+ esac
163
+ _git "$r" init -q -b main
164
+ # Identity in LOCAL config: the SUT runs its own merge and commit, which read config
165
+ # (#8384: 14 rows failed on a CI runner without a global identity).
166
+ git -C "$r" config user.email t@t
167
+ git -C "$r" config user.name t
168
+ _git "$r" add -A >/dev/null; _git "$r" commit -q -m base
169
+
170
+ _git "$r" checkout -q -b feature
171
+ sed -i 's/^last$/last-FEATURE/' "$r/$SRC"
172
+ printf '{"v":"feature"}\n' > "$r/$MODEL"
173
+ _git "$r" add -A >/dev/null; _git "$r" commit -q -m feature
174
+
175
+ _git "$r" checkout -q main
176
+ sed -i 's/^first$/first-MAIN/' "$r/$SRC"
177
+ printf '{"v":"main"}\n' > "$r/$MODEL"
178
+ _git "$r" add -A >/dev/null; _git "$r" commit -q -m main
179
+
180
+ _git "$r" checkout -q feature
181
+ printf '%s' "$r"
182
+ }
183
+ # on_main <repo> <cmd…> — run <cmd…> <repo> on the base side (main), commit, return to feature.
184
+ on_main() {
185
+ local r="$1"; shift
186
+ _git "$r" checkout -q main; "$@" "$r"; _git "$r" add -A >/dev/null; _git "$r" commit -q -m "main extra"
187
+ _git "$r" checkout -q feature
188
+ }
189
+
190
+ P_OK="$(mkplugin ok ok)"
191
+ P_FAIL="$(mkplugin fail fail)"
192
+ P_NOOP="$(mkplugin noop noop)"
193
+ P_MARK="$(mkplugin markers markers)"
194
+ P_SLOW="$(mkplugin slow slow)"
195
+ P_LIST="$(mkplugin listroot listroot)"
196
+ P_SECOND="$(mkplugin second second)"
197
+ P_LONELY="$(mkplugin lonely none)"
198
+ P_NOID="$(mkplugin noid ok not-soleur)"
199
+ P_ALT="$(mkplugin alt ok-alt)"
200
+ for _p in "$P_OK" "$P_FAIL" "$P_NOOP" "$P_MARK" "$P_SLOW" "$P_LIST" "$P_SECOND" "$P_LONELY" "$P_NOID" "$P_ALT"; do
201
+ [[ -f "$_p/scripts/resolve-regenerable-conflicts.sh" ]] || { echo "[FATAL] sandbox plugin not built: $_p" >&2; exit 2; }
202
+ done
203
+
204
+ # run_at <plugin-root> <repo> <base-ref> [resolvable-tsv] — CLAUDE_PLUGIN_ROOT is UNSET unless a
205
+ # row sets it on purpose, so an ambient value cannot rescue a broken sibling lookup.
206
+ run_at() {
207
+ local pr="$1" r="$2" base="$3" tsv="${4:-}"
208
+ local out rc=0
209
+ if [[ -n "$tsv" ]]; then
210
+ out="$(cd "$r" && env -u CLAUDE_PLUGIN_ROOT bash "$pr/scripts/resolve-regenerable-conflicts.sh" --test-resolvable "$tsv" "$base" 2>&1)" || rc=$?
211
+ else
212
+ out="$(cd "$r" && env -u CLAUDE_PLUGIN_ROOT bash "$pr/scripts/resolve-regenerable-conflicts.sh" "$base" 2>&1)" || rc=$?
213
+ fi
214
+ printf '%s\n%s' "$out" "$rc"
215
+ }
216
+ run_sut() { run_at "$P_OK" "$@"; }
217
+ sut_rc() { tail -n1 <<<"$1"; }
218
+ sut_out() { sed '$d' <<<"$1"; }
219
+ # refused <name> <res> <fp_before> <repo> [expect-substring] — the fail-closed triple.
220
+ refused() {
221
+ local name="$1" res="$2" fpb="$3" r="$4" want="${5:-}"
222
+ CASES_RUN=$((CASES_RUN + 1))
223
+ [[ "$(sut_rc "$res")" != "0" ]] && pass "$name -> non-zero" \
224
+ || fail "$name -> exited 0; the resolver committed something it should have refused"
225
+ CASES_RUN=$((CASES_RUN + 1))
226
+ [[ "$(tree_fp "$r")" == "$fpb" ]] && pass "$name -> tree byte-identical to entry" \
227
+ || fail "$name -> the tree CHANGED (rc=$(sut_rc "$res")): $(_git "$r" status --porcelain --untracked-files=all | tr '\n' ' ') — $(sut_out "$res")"
228
+ if [[ -n "$want" ]]; then
229
+ CASES_RUN=$((CASES_RUN + 1))
230
+ grep -qF -- "$want" <<<"$(sut_out "$res")" && pass "$name -> says '$want'" \
231
+ || fail "$name -> did not say '$want': $(sut_out "$res")"
232
+ fi
233
+ }
234
+
235
+ echo "=== resolve-regenerable-conflicts (Guard 1) ==="
236
+
237
+ # ── HAPPY PATH (AC1/AC2) — a bare self-hosted repo, the plugin's own renderer ───────────────
238
+ echo ""
239
+ echo "--- happy path: a lone resolvable content conflict is merged and committed ---"
240
+ r="$(mkrepo happy)"; assert_fixture_dir "$r"
241
+ before_head="$(_git "$r" rev-parse HEAD)"
242
+ res="$(run_sut "$r" main)"
243
+ CASES_RUN=$((CASES_RUN + 1))
244
+ [[ "$(sut_rc "$res")" == "0" ]] && pass "happy: exit 0" || fail "happy: exit $(sut_rc "$res") — $(sut_out "$res")"
245
+ CASES_RUN=$((CASES_RUN + 1))
246
+ if [[ "$(_git "$r" rev-parse HEAD^1 2>/dev/null)" == "$before_head" && "$(_git "$r" rev-parse HEAD^2 2>/dev/null)" == "$(_git "$r" rev-parse main)" ]]; then
247
+ pass "happy: a two-parent merge commit (HEAD, main) exists"
248
+ else
249
+ fail "happy: no merge commit with parents (HEAD, main)"
250
+ fi
251
+ CASES_RUN=$((CASES_RUN + 1))
252
+ [[ -z "$(_git "$r" status --porcelain --untracked-files=all)" ]] && pass "happy (AC2): the tree is clean after the commit" \
253
+ || fail "happy: tree dirty after commit: $(_git "$r" status --porcelain --untracked-files=all)"
254
+ CASES_RUN=$((CASES_RUN + 1))
255
+ committed="$(_git "$r" show HEAD:"$MODEL")"
256
+ if [[ "$committed" == *'first-MAIN|last-FEATURE'*'"by":"plugin"'* ]]; then
257
+ pass "happy (AC1): the artifact was rendered by the PLUGIN renderer from the MERGED sources"
258
+ else
259
+ fail "happy: unexpected artifact content: $committed"
260
+ fi
261
+ CASES_RUN=$((CASES_RUN + 1))
262
+ if grep -qxF "SOLEUR_REGEN_ON_CONFLICT paths=$MODEL arm=plugin root_src=sibling rc=0" <<<"$(sut_out "$res")"; then
263
+ pass "happy: the success marker names the path, arm=plugin and root_src=sibling (no filesystem path)"
264
+ else
265
+ fail "happy: success marker missing or reshaped: $(sut_out "$res")"
266
+ fi
267
+ CASES_RUN=$((CASES_RUN + 1))
268
+ grep -q '\[regen-on-conflict\] regenerating ' <<<"$(sut_out "$res")" \
269
+ && pass "happy: a progress line precedes the (minutes-long) render" \
270
+ || fail "happy: no progress line before the render"
271
+ CASES_RUN=$((CASES_RUN + 1))
272
+ [[ -z "$(find "$XDG_CACHE_HOME/soleur" -mindepth 1 -maxdepth 1 2>/dev/null)" ]] \
273
+ && pass "happy: the private staging dir was removed" \
274
+ || fail "happy: staging left behind: $(ls "$XDG_CACHE_HOME/soleur")"
275
+
276
+ # A relative XDG_CACHE_HOME is invalid (XDG spec) and ignored: staging goes under $HOME/.cache,
277
+ # never into a directory relative to the repository being merged.
278
+ r="$(mkrepo relxdg)"; assert_fixture_dir "$r"
279
+ _home="$SANDBOX/relxdg-home"; assert_fixture_dir "$_home"; mkdir -p "$_home"
280
+ res="$(XDG_CACHE_HOME=relcache HOME="$_home" run_sut "$r" main)"
281
+ CASES_RUN=$((CASES_RUN + 1))
282
+ if [[ "$(sut_rc "$res")" == "0" && ! -e "$r/relcache" && -d "$_home/.cache/soleur" ]]; then
283
+ pass "a relative XDG_CACHE_HOME is ignored; staging goes under \$HOME/.cache"
284
+ else
285
+ fail "relative XDG_CACHE_HOME: rc=$(sut_rc "$res"), in-repo relcache=$([[ -e "$r/relcache" ]] && echo yes || echo no) — $(sut_out "$res")"
286
+ fi
287
+
288
+ # P1 (must-PASS): the base ref given as a raw SHA.
289
+ r="$(mkrepo p1)"; assert_fixture_dir "$r"
290
+ res="$(run_sut "$r" "$(_git "$r" rev-parse main)")"
291
+ CASES_RUN=$((CASES_RUN + 1))
292
+ [[ "$(sut_rc "$res")" == "0" ]] && pass "P1: a SHA base ref exits 0 and merges" \
293
+ || fail "P1: a SHA base ref exited $(sut_rc "$res") — $(sut_out "$res")"
294
+
295
+ # Two resolvable members, each regenerated by its own arm (arm-agnostic success).
296
+ _main_second() { printf '{"v":"main2"}\n' > "$1/$SECOND"; }
297
+ r="$(mkrepo twomembers)"; assert_fixture_dir "$r"
298
+ on_main "$r" _main_second
299
+ printf '{"v":"feature2"}\n' > "$r/$SECOND"; _git "$r" commit -q -am f2
300
+ printf '%s\tbash\t%s\n%s\tbash\t%s\n' "$MODEL" "$P_OK/scripts/render-c4-model.sh" \
301
+ "$SECOND" "$P_SECOND/scripts/render-c4-model.sh" > "$r.tsv"
302
+ res="$(run_sut "$r" main "$r.tsv")"
303
+ CASES_RUN=$((CASES_RUN + 1))
304
+ if [[ "$(sut_rc "$res")" == "0" && "$(_git "$r" show HEAD:"$MODEL")" == *'"by":"plugin"'* \
305
+ && "$(_git "$r" show HEAD:"$SECOND")" == *'"second":"regenerated"'* ]]; then
306
+ pass "two members: each path is written by ITS OWN arm and both are committed"
307
+ else
308
+ fail "two members: rc=$(sut_rc "$res") — $(sut_out "$res")"
309
+ fi
310
+
311
+ # ── FAILURES OF THE RENDER: all happen before the worktree is touched ──────────────────────
312
+ echo ""
313
+ echo "--- a failing / empty / marker-laden render refuses with the tree untouched ---"
314
+ r="$(mkrepo row2)"; assert_fixture_dir "$r"; fpb="$(tree_fp "$r")"; res="$(run_at "$P_FAIL" "$r" main)"
315
+ refused "row 2: the renderer fails" "$res" "$fpb" "$r" "regen failed"
316
+ CASES_RUN=$((CASES_RUN + 1))
317
+ grep -q 'Line 3: boom' <<<"$(sut_out "$res")" \
318
+ && pass "AC6: the refusal carries the renderer's diagnostic text, not only an exit code" \
319
+ || fail "AC6: the diagnostic was swallowed: $(sut_out "$res")"
320
+ CASES_RUN=$((CASES_RUN + 1))
321
+ grep -qx 'SOLEUR_REGEN_ON_CONFLICT rc=1 class=failed reason=render-failed' <<<"$(sut_out "$res")" \
322
+ && pass "row 2: the stdout marker says class=failed reason=render-failed" \
323
+ || fail "row 2: no machine-readable refusal marker: $(sut_out "$res")"
324
+
325
+ r="$(mkrepo noop)"; assert_fixture_dir "$r"; fpb="$(tree_fp "$r")"; res="$(run_at "$P_NOOP" "$r" main)"
326
+ refused "a renderer that writes nothing" "$res" "$fpb" "$r" "did not produce"
327
+
328
+ r="$(mkrepo markers)"; assert_fixture_dir "$r"; fpb="$(tree_fp "$r")"; res="$(run_at "$P_MARK" "$r" main)"
329
+ refused "a renderer that emits conflict markers" "$res" "$fpb" "$r" "still contains conflict markers"
330
+
331
+ # ── FAIL-CLOSED TABLE: inputs that are not a lone content conflict on a resolvable path ────
332
+ echo ""
333
+ echo "--- fail-closed table ---"
334
+ _main_readme() { printf 'readme MAIN\n' > "$1/README.md"; }
335
+ r="$(mkrepo row1)"; assert_fixture_dir "$r"; on_main "$r" _main_readme
336
+ printf 'readme FEATURE\n' > "$r/README.md"; _git "$r" commit -q -am readme
337
+ fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main)"
338
+ refused "row 1: a non-resolvable path also conflicts" "$res" "$fpb" "$r" "not regenerable: README.md"
339
+
340
+ r="$(mkrepo row4)"; assert_fixture_dir "$r"; : > "$r.tsv"; fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main "$r.tsv")"
341
+ refused "row 4: the resolvable set is empty" "$res" "$fpb" "$r" "reason=not-regenerable"
342
+
343
+ r="$(mkrepo row6)"; assert_fixture_dir "$r"
344
+ _git "$r" checkout -q main; _git "$r" rm -q "$MODEL"; _git "$r" commit -q -m del; _git "$r" checkout -q feature
345
+ fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main)"
346
+ refused "row 6: modify/delete, not content" "$res" "$fpb" "$r" "reason=unresolvable-kind"
347
+
348
+ r="$(mkrepo row7)"; assert_fixture_dir "$r"
349
+ printf 'DO NOT WRITE THROUGH ME\n' > "$r/victim.txt"
350
+ rm -f "$r/$MODEL"; ln -s "../../../../victim.txt" "$r/$MODEL"; _git "$r" add -A >/dev/null; _git "$r" commit -q -m symlink
351
+ fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main)"
352
+ refused "row 7: the resolvable path is a symlink on one side (distinct types)" "$res" "$fpb" "$r"
353
+ CASES_RUN=$((CASES_RUN + 1))
354
+ [[ "$(cat "$r/victim.txt")" == "DO NOT WRITE THROUGH ME" ]] && pass "row 7: the link target was not written through" \
355
+ || fail "row 7: the link target was modified"
356
+
357
+ r="$(mkrepo row7b)"; assert_fixture_dir "$r"
358
+ printf 'victim\n' > "$r/$DIAG/victim.txt"; printf 'other\n' > "$r/$DIAG/other.txt"
359
+ rm -f "$r/$MODEL"; ln -s "victim.txt" "$r/$MODEL"; _git "$r" add -A >/dev/null; _git "$r" commit -q -m v
360
+ _git "$r" checkout -q main; rm -f "$r/$MODEL"; ln -s "other.txt" "$r/$MODEL"; _git "$r" add -A >/dev/null; _git "$r" commit -q -m o
361
+ _git "$r" checkout -q feature
362
+ fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main)"
363
+ refused "row 7b: both sides symlink the artifact to different targets" "$res" "$fpb" "$r" "symlink"
364
+ CASES_RUN=$((CASES_RUN + 1))
365
+ [[ "$(cat "$r/$DIAG/victim.txt")" == "victim" ]] && pass "row 7b: the link target was not written through" \
366
+ || fail "row 7b: the regen wrote through the symlink"
367
+
368
+ r="$(mkrepo row8)"; assert_fixture_dir "$r"; _git "$r" merge main --no-edit >/dev/null 2>&1 || true
369
+ fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main)"
370
+ refused "row 8: a merge is already in progress" "$res" "$fpb" "$r" "merge is already in progress"
371
+
372
+ r="$(mkrepo dirty)"; assert_fixture_dir "$r"; printf 'operator work\n' > "$r/scratch.txt"; _git "$r" add scratch.txt >/dev/null
373
+ fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main)"
374
+ refused "a dirty (staged) tree" "$res" "$fpb" "$r" "not clean"
375
+
376
+ r="$(mkrepo untrackedhidden)"; assert_fixture_dir "$r"; git -C "$r" config status.showUntrackedFiles no
377
+ printf 'operator notes\n' > "$r/NOTES.txt"
378
+ fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main)"
379
+ refused "status.showUntrackedFiles=no cannot hide an untracked file" "$res" "$fpb" "$r" "not clean (NOTES.txt)"
380
+ CASES_RUN=$((CASES_RUN + 1))
381
+ [[ "$(cat "$r/NOTES.txt" 2>/dev/null)" == "operator notes" ]] && pass "the operator's untracked file survives" \
382
+ || fail "the operator's untracked file was lost"
383
+
384
+ r="$(mkrepo noconflict)"; assert_fixture_dir "$r"; _git "$r" checkout -q -B feature main >/dev/null 2>&1
385
+ fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main)"
386
+ refused "no conflict at all" "$res" "$fpb" "$r" "reason=no-conflict"
387
+
388
+ r="$(mkrepo args)"; assert_fixture_dir "$r"; fpb="$(tree_fp "$r")"
389
+ res="$(run_sut "$r" "")"; refused "an empty base ref" "$res" "$fpb" "$r" "no base ref"
390
+ res="$(run_sut "$r" "no/such/ref")"; refused "an unresolvable base ref" "$res" "$fpb" "$r" "does not resolve"
391
+
392
+ # ── STAGING: only tracked regular LikeC4 sources from the merged tree reach the render ─────
393
+ echo ""
394
+ echo "--- staging: what reaches the renderer ---"
395
+ _main_junk() {
396
+ mkdir -p "$1/$DIAG/node_modules/.bin" "$1/$DIAG/nested"
397
+ printf '#!/bin/sh\n' > "$1/$DIAG/node_modules/.bin/likec4"
398
+ printf 'script-shell=./evil.sh\n' > "$1/$DIAG/.npmrc"
399
+ printf 'readme\n' > "$1/$DIAG/README.md"
400
+ printf 'model {}\n' > "$1/$DIAG/extra.like-c4"
401
+ printf 'model {}\n' > "$1/$DIAG/nested/café.c4"
402
+ printf 'model {}\n' > "$1/$DIAG/nested/views.likec4"
403
+ }
404
+ r="$(mkrepo stagedroot)"; assert_fixture_dir "$r"; on_main "$r" _main_junk
405
+ printf 'machine-local\n' > "$r/$DIAG/local.c4"; printf 'local.c4\n' >> "$r/.git/info/exclude"
406
+ rm -f "$SANDBOX/listroot.txt"
407
+ res="$(run_at "$P_LIST" "$r" main)"
408
+ _list="$(cat "$SANDBOX/listroot.txt" 2>/dev/null)"
409
+ CASES_RUN=$((CASES_RUN + 1))
410
+ [[ "$(sut_rc "$res")" == "0" ]] && pass "staging: a tree carrying node_modules, .npmrc and odd names still merges" \
411
+ || fail "staging: rc=$(sut_rc "$res") — $(sut_out "$res")"
412
+ for _want in "./$DIAG/model.c4" "./$DIAG/extra.like-c4" "./$DIAG/nested/café.c4" "./$DIAG/nested/views.likec4"; do
413
+ CASES_RUN=$((CASES_RUN + 1))
414
+ grep -qxF -- "$_want" <<<"$_list" && pass "staging: $_want is rendered" || fail "staging: $_want is missing from the render root: $_list"
415
+ done
416
+ for _never in node_modules .npmrc README.md local.c4 model.likec4.json; do
417
+ CASES_RUN=$((CASES_RUN + 1))
418
+ grep -qF -- "$_never" <<<"$_list" && fail "staging: $_never reached the render root" || pass "staging: $_never never reaches the render root"
419
+ done
420
+
421
+ _refuse_tree() { # <name> <want> <setup-fn> — a base-side addition that must be refused
422
+ local name="$1" want="$2" setup="$3" rr fpb res
423
+ rr="$(mkrepo "$(printf '%s' "$name" | tr -cd 'a-z0-9')")"
424
+ on_main "$rr" "$setup"
425
+ rm -f "$SANDBOX/listroot.txt"
426
+ fpb="$(tree_fp "$rr")"
427
+ res="$(run_at "$P_LIST" "$rr" main)"
428
+ refused "$name" "$res" "$fpb" "$rr" "$want"
429
+ CASES_RUN=$((CASES_RUN + 1))
430
+ [[ ! -e "$SANDBOX/listroot.txt" ]] && pass "$name -> refused BEFORE the renderer ran" \
431
+ || fail "$name -> the renderer ran anyway"
432
+ }
433
+ _add_link_file() { ln -s /etc/hostname "$1/$DIAG/leak.c4"; }
434
+ _add_link_dir() { mkdir -p "$1/outside"; printf 'model {}\n' > "$1/outside/o.c4"; ln -s ../../../../outside "$1/$DIAG/ext"; }
435
+ _add_config() { printf 'import "node:fs"; export default {}\n' > "$1/$DIAG/likec4.config.mjs"; }
436
+ _add_rc() { printf '{}\n' > "$1/$DIAG/.likec4rc"; }
437
+ _add_gitlink() { assert_fixture_dir "$1"; mkdir -p "$1/$DIAG/sub"; git -C "$1" update-index --add --cacheinfo "160000,$(git -C "$1" rev-parse HEAD),$DIAG/sub"; }
438
+ _refuse_tree "a tracked symlinked source file" "reason=symlink-source" _add_link_file
439
+ _refuse_tree "a tracked symlinked source directory" "reason=symlink-source" _add_link_dir
440
+ _refuse_tree "a likec4 js config" "reason=likec4-config" _add_config
441
+ _refuse_tree "a likec4rc config" "reason=likec4-config" _add_rc
442
+ _refuse_tree "a submodule under the diagrams dir" "reason=gitlink-source" _add_gitlink
443
+
444
+ # A path the merge ADDS that exists here ignored would be overwritten by `git merge`.
445
+ _main_env() { printf 'from main\n' > "$1/local.env"; }
446
+ r="$(mkrepo collide)"; assert_fixture_dir "$r"; on_main "$r" _main_env
447
+ printf 'OPERATOR SECRET\n' > "$r/local.env"; printf 'local.env\n' >> "$r/.git/info/exclude"
448
+ fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main)"
449
+ refused "the merge would overwrite an ignored local file" "$res" "$fpb" "$r" "reason=would-overwrite"
450
+ CASES_RUN=$((CASES_RUN + 1))
451
+ [[ "$(cat "$r/local.env")" == "OPERATOR SECRET" ]] && pass "the ignored local file is untouched" \
452
+ || fail "the ignored local file was overwritten"
453
+
454
+ # ── THE RENDER WINDOW: the operator keeps working; nothing of theirs is lost or committed ──
455
+ echo ""
456
+ echo "--- the render window ---"
457
+ _slow_run() { # <repo> <action-fn> — runs the resolver on P_SLOW, calls <action-fn> <repo> <pid> mid-render
458
+ local rr="$1" action="$2" pid rc=0
459
+ assert_fixture_dir "$SANDBOX"; rm -f "$SANDBOX/slow.started"
460
+ ( cd "$rr" && exec env -u CLAUDE_PLUGIN_ROOT bash "$P_SLOW/scripts/resolve-regenerable-conflicts.sh" main ) \
461
+ > "$SANDBOX/slow.out" 2>&1 &
462
+ pid=$!
463
+ for _ in $(seq 1 100); do [[ -e "$SANDBOX/slow.started" ]] && break; sleep 0.1; done
464
+ "$action" "$rr" "$pid"
465
+ wait "$pid" || rc=$?
466
+ printf '%s\n%s' "$(cat "$SANDBOX/slow.out")" "$rc"
467
+ }
468
+ _act_edit() { printf 'mid-render edit\n' >> "$1/README.md"; printf 'new\n' > "$1/NEW.txt"; }
469
+ _act_stage() { assert_fixture_dir "$1"; printf 'x\n' > "$1/staged.txt"; git -C "$1" add staged.txt; }
470
+ _act_commit() { assert_fixture_dir "$1"; git -C "$1" -c user.email=t@t -c user.name=t commit -q --allow-empty -m mid; }
471
+ _act_term() { kill -TERM "$2" 2>/dev/null; }
472
+ _act_kill() { kill -KILL "$2" 2>/dev/null; }
473
+
474
+ r="$(mkrepo editduring)"; assert_fixture_dir "$r"
475
+ res="$(_slow_run "$r" _act_edit)"
476
+ CASES_RUN=$((CASES_RUN + 1))
477
+ [[ "$(sut_rc "$res")" != "0" ]] && grep -q 'reason=dirty-tree' <<<"$(sut_out "$res")" \
478
+ && pass "an edit made during the render refuses the merge" \
479
+ || fail "edit during render: rc=$(sut_rc "$res") — $(sut_out "$res")"
480
+ CASES_RUN=$((CASES_RUN + 1))
481
+ if [[ "$(tail -1 "$r/README.md")" == "mid-render edit" && -f "$r/NEW.txt" && ! -f "$r/.git/MERGE_HEAD" \
482
+ && "$(_git "$r" rev-parse HEAD)" == "$(_git "$r" rev-parse feature)" ]]; then
483
+ pass "the operator's mid-render edit and new file both survive, nothing was merged"
484
+ else
485
+ fail "operator work lost or a merge happened: $(_git "$r" status --porcelain --untracked-files=all | tr '\n' ' ')"
486
+ fi
487
+
488
+ r="$(mkrepo stageduring)"; assert_fixture_dir "$r"
489
+ res="$(_slow_run "$r" _act_stage)"
490
+ CASES_RUN=$((CASES_RUN + 1))
491
+ if [[ "$(sut_rc "$res")" != "0" ]] && ! _git "$r" log -1 --name-only --format= | grep -c >/dev/null staged.txt \
492
+ && _git "$r" diff --cached --name-only | grep -cx >/dev/null staged.txt; then
493
+ pass "a file staged during the render is never committed and stays staged"
494
+ else
495
+ fail "a mid-render staged file was committed or lost: $(sut_out "$res")"
496
+ fi
497
+
498
+ r="$(mkrepo headmoved)"; assert_fixture_dir "$r"
499
+ res="$(_slow_run "$r" _act_commit)"
500
+ CASES_RUN=$((CASES_RUN + 1))
501
+ [[ "$(sut_rc "$res")" != "0" ]] && grep -q 'reason=head-moved' <<<"$(sut_out "$res")" && [[ ! -f "$r/.git/MERGE_HEAD" ]] \
502
+ && pass "a commit made during the render refuses the (now stale) merge" \
503
+ || fail "HEAD moved during render: rc=$(sut_rc "$res") — $(sut_out "$res")"
504
+
505
+ r="$(mkrepo sigterm)"; assert_fixture_dir "$r"; fpb="$(tree_fp "$r")"
506
+ _t0=$SECONDS
507
+ res="$(_slow_run "$r" _act_term)"
508
+ _dt=$((SECONDS - _t0))
509
+ refused "SIGTERM during the render" "$res" "$fpb" "$r" "reason=signal"
510
+ CASES_RUN=$((CASES_RUN + 1))
511
+ [[ "$_dt" -lt 5 ]] && pass "SIGTERM is handled at once, not after the render finishes (${_dt}s)" \
512
+ || fail "SIGTERM waited for the render (${_dt}s)"
513
+ CASES_RUN=$((CASES_RUN + 1))
514
+ [[ -z "$(find "$XDG_CACHE_HOME/soleur" -mindepth 1 -maxdepth 1 2>/dev/null)" ]] \
515
+ && pass "SIGTERM: the staging dir was removed" || fail "SIGTERM left staging behind"
516
+
517
+ r="$(mkrepo sigkill)"; assert_fixture_dir "$r"; fpb="$(tree_fp "$r")"
518
+ res="$(_slow_run "$r" _act_kill)"
519
+ CASES_RUN=$((CASES_RUN + 1))
520
+ [[ "$(tree_fp "$r")" == "$fpb" ]] && pass "SIGKILL during the render leaves the tree byte-identical (no MERGE_HEAD)" \
521
+ || fail "SIGKILL left the tree changed: $(_git "$r" status --porcelain | tr '\n' ' ')"
522
+ sleep 5; rm -rf "${XDG_CACHE_HOME:?}/soleur"
523
+
524
+ # ── A COMMIT HOOK THAT REWRITES THE MERGE is undone and refused ─────────────────────────────
525
+ r="$(mkrepo hook)"; assert_fixture_dir "$r"; mkdir -p "$r/.git/hooks"
526
+ printf '#!/bin/sh\necho hooked >> README.md\ngit add README.md\n' > "$r/.git/hooks/pre-commit"; chmod +x "$r/.git/hooks/pre-commit"
527
+ fpb="$(tree_fp "$r")"; res="$(run_sut "$r" main)"
528
+ refused "a pre-commit hook that changes the merge commit" "$res" "$fpb" "$r" "reason=commit-hook-changed-tree"
529
+
530
+ # The undo is VERIFIED, not asserted: a hook that also leaves an unstaged edit makes
531
+ # `reset --keep` refuse, and the resolver must say so rather than claim it undid the commit.
532
+ r="$(mkrepo hookkeep)"; assert_fixture_dir "$r"; mkdir -p "$r/.git/hooks"
533
+ printf '#!/bin/sh\necho hooked >> README.md\ngit add README.md\necho unstaged >> README.md\n' > "$r/.git/hooks/pre-commit"; chmod +x "$r/.git/hooks/pre-commit"
534
+ _head0="$(_git "$r" rev-parse HEAD)"; res="$(run_sut "$r" main)"
535
+ CASES_RUN=$((CASES_RUN + 1))
536
+ if [[ "$(sut_rc "$res")" != "0" && "$(sut_out "$res")" == *"reason=commit-hook-changed-tree"* \
537
+ && "$(sut_out "$res")" == *"could NOT undo"* && "$(_git "$r" rev-parse HEAD)" != "$_head0" ]]; then
538
+ pass "an undo that reset --keep refuses is reported as NOT undone"
539
+ else
540
+ fail "unverified undo: rc=$(sut_rc "$res") head-moved=$([[ "$(_git "$r" rev-parse HEAD)" != "$_head0" ]] && echo yes || echo no) — $(sut_out "$res")"
541
+ fi
542
+
543
+ # Signals that land after the merge started. The helper TERMs the OUTERMOST ancestor running the
544
+ # resolver (a command substitution forks a copy with the same argv). Linux /proc only.
545
+ # HAZARD: the helper TERMs the outermost ancestor whose argv matches its pattern. A MUTANT of the match line (-v, a dropped -x, a catch-all
546
+ # pattern) matches every ancestor and walks up to the top of the user's session. The walk is bounded at this suite's own PID, but run any
547
+ # mutant of this block ONLY as `unshare -Urpf --kill-child --mount-proc bash <this file>` (the test shell becomes PID 1; the walk stops there).
548
+ if [[ -r /proc/self/stat ]]; then
549
+ export SOLEUR_TEST_SUITE_PID=$$ # read by the helper below; unset or empty means the helper signals nothing (the mid-merge row then fails; the commit row cannot tell)
550
+ _killer="$SANDBOX/kill-resolver.sh"; assert_fixture_dir "$SANDBOX"
551
+ cat > "$_killer" <<'EOF'
552
+ #!/bin/sh
553
+ # Walk up to the TOP of the contiguous run of ancestors whose argv holds the resolver script as a
554
+ # whole argument, and stop at the first non-matching ancestor above it — never further, or any
555
+ # outer shell whose command line merely mentions the path would be signalled too.
556
+ # The walk is also bounded at SOLEUR_TEST_SUITE_PID. A mutant of the grep line below (-v, no -x, a catch-all) matches every ancestor:
557
+ # run such a mutant ONLY under `unshare -Urpf --kill-child --mount-proc` (see the HAZARD note above the block).
558
+ p=$PPID; target=""
559
+ [ -n "${SOLEUR_TEST_SUITE_PID:-}" ] || exit 0
560
+ while [ -n "$p" ] && [ "$p" -gt 1 ] && [ "$p" != "$SOLEUR_TEST_SUITE_PID" ]; do
561
+ if tr '\0' '\n' < "/proc/$p/cmdline" 2>/dev/null | grep -cx >/dev/null '.*/scripts/resolve-regenerable-conflicts\.sh'; then
562
+ target=$p
563
+ elif [ -n "$target" ]; then
564
+ break
565
+ fi
566
+ p=$(awk '{print $4}' "/proc/$p/stat" 2>/dev/null)
567
+ done
568
+ [ -n "$target" ] && kill -TERM "$target"
569
+ exit 0
570
+ EOF
571
+ chmod +x "$_killer"
572
+
573
+ # During `git commit`: the commit completes, so the success marker is the truth.
574
+ r="$(mkrepo sigcommit)"; assert_fixture_dir "$r"; mkdir -p "$r/.git/hooks"
575
+ printf '#!/bin/sh\n%s\n' "$_killer" > "$r/.git/hooks/pre-commit"; chmod +x "$r/.git/hooks/pre-commit"
576
+ res="$(run_sut "$r" main)"
577
+ CASES_RUN=$((CASES_RUN + 1))
578
+ if [[ "$(sut_rc "$res")" == "0" && "$(sut_out "$res")" == *"SOLEUR_REGEN_ON_CONFLICT paths="*"rc=0"* \
579
+ && "$(_git "$r" rev-parse HEAD^2 2>/dev/null)" == "$(_git "$r" rev-parse main)" ]]; then
580
+ pass "a signal during git commit still ends with a marker that matches the repo (committed)"
581
+ else
582
+ fail "signal during commit: rc=$(sut_rc "$res") — $(sut_out "$res")"
583
+ fi
584
+
585
+ # While the merge is in progress (a clean filter on the artifact fires once MERGE_HEAD exists):
586
+ # unwound, and the marker class is `interrupted`, the same class as a pre-merge signal.
587
+ r="$(mkrepo sigmerge)"; assert_fixture_dir "$r"
588
+ printf '#!/bin/sh\n[ -f "$(git rev-parse --git-dir)/MERGE_HEAD" ] && [ ! -e %s/sig.once ] && { : > %s/sig.once; %s; }\ncat\n' \
589
+ "$SANDBOX" "$SANDBOX" "$_killer" > "$SANDBOX/sig-clean.sh"; chmod +x "$SANDBOX/sig-clean.sh"
590
+ rm -f "$SANDBOX/sig.once"
591
+ printf '%s filter=sig\n' "$MODEL" > "$r/.git/info/attributes"
592
+ git -C "$r" config filter.sig.clean "$SANDBOX/sig-clean.sh"
593
+ _head0="$(_git "$r" rev-parse HEAD)"; res="$(run_sut "$r" main)"
594
+ CASES_RUN=$((CASES_RUN + 1))
595
+ if [[ -e "$SANDBOX/sig.once" && "$(sut_out "$res")" == *"rc=1 class=interrupted reason=interrupted"* \
596
+ && ! -e "$r/.git/MERGE_HEAD" && "$(_git "$r" rev-parse HEAD)" == "$_head0" ]]; then
597
+ pass "a signal mid-merge unwinds and reports class=interrupted"
598
+ else
599
+ fail "signal mid-merge: fired=$([[ -e "$SANDBOX/sig.once" ]] && echo yes || echo no) rc=$(sut_rc "$res") — $(sut_out "$res")"
600
+ fi
601
+ else
602
+ echo " SKIP: no /proc — the in-commit and mid-merge signal rows need it"
603
+ CASES_RUN=$((CASES_RUN + 2)); passes=$((passes + 2))
604
+ fi
605
+
606
+ # An absolute XDG_CACHE_HOME the staging guard refuses still ends with a marker (rc 1, not a bare exit 2).
607
+ r="$(mkrepo procxdg)"; assert_fixture_dir "$r"
608
+ res="$(XDG_CACHE_HOME=/proc/soleur-no-such-dir run_sut "$r" main)"
609
+ CASES_RUN=$((CASES_RUN + 1))
610
+ [[ "$(sut_rc "$res")" == "1" && "$(sut_out "$res")" == *"reason=no-staging"* ]] \
611
+ && pass "an unusable absolute XDG_CACHE_HOME is refused with a marker" \
612
+ || fail "unusable XDG_CACHE_HOME: rc=$(sut_rc "$res") — $(sut_out "$res")"
613
+
614
+ # ══ PLUGIN ROOT: where the renderer comes from ═════════════════════════════════════════════
615
+ echo ""
616
+ echo "--- plugin root: self-hosted, wrapper, decoy, CLAUDE_PLUGIN_ROOT ---"
617
+ rm -f "$SANDBOX/WRAPPER_RAN" "$SANDBOX/DECOY_RAN"
618
+ r="$(mkrepo withwrapper wrapper)"; assert_fixture_dir "$r"; res="$(run_sut "$r" main)"
619
+ CASES_RUN=$((CASES_RUN + 1))
620
+ [[ "$(sut_rc "$res")" == "0" && ! -e "$SANDBOX/WRAPPER_RAN" && "$(_git "$r" show HEAD:"$MODEL")" == *'"by":"plugin"'* ]] \
621
+ && pass "a repo carrying scripts/regenerate-c4-model.sh still renders through the plugin (the wrapper is not an arm)" \
622
+ || fail "the repo wrapper ran or the merge failed: rc=$(sut_rc "$res")"
623
+
624
+ r="$(mkrepo decoy decoy)"; assert_fixture_dir "$r"; res="$(run_sut "$r" main)"
625
+ CASES_RUN=$((CASES_RUN + 1))
626
+ [[ "$(sut_rc "$res")" == "0" && ! -e "$SANDBOX/DECOY_RAN" && "$(_git "$r" show HEAD:"$MODEL")" == *'"by":"plugin"'* ]] \
627
+ && pass "AC4: a decoy renderer inside the merged tree is never executed" \
628
+ || fail "AC4: the decoy ran or the merge failed: rc=$(sut_rc "$res")"
629
+
630
+ # m1 — the plugin dir is absolutized BEFORE the cd; invoked by a RELATIVE path from a subdir.
631
+ r="$(mkrepo relative)"; assert_fixture_dir "$r"
632
+ _rel="$(realpath --relative-to="$r/knowledge-base" "$P_OK/scripts/resolve-regenerable-conflicts.sh")"
633
+ _rel_rc=0; _rel_out="$(cd "$r/knowledge-base" && env -u CLAUDE_PLUGIN_ROOT bash "$_rel" main 2>&1)" || _rel_rc=$?
634
+ CASES_RUN=$((CASES_RUN + 1))
635
+ [[ "$_rel_rc" -eq 0 ]] && pass "m1: a relative invocation from a subdirectory still finds the sibling renderer" \
636
+ || fail "m1: relative invocation rc=$_rel_rc — $_rel_out"
637
+
638
+ _cpr() { # <plugin-for-resolver> <repo> <CLAUDE_PLUGIN_ROOT value>
639
+ local rc=0 out; out="$(cd "$2" && CLAUDE_PLUGIN_ROOT="$3" bash "$1/scripts/resolve-regenerable-conflicts.sh" main 2>&1)" || rc=$?
640
+ printf '%s\n%s' "$out" "$rc"
641
+ }
642
+ r="$(mkrepo cprfallback)"; assert_fixture_dir "$r"; res="$(_cpr "$P_LONELY" "$r" "$P_OK")"
643
+ CASES_RUN=$((CASES_RUN + 1))
644
+ [[ "$(sut_rc "$res")" == "0" && "$(sut_out "$res")" == *"root_src=env rc=0"* ]] \
645
+ && pass "no sibling renderer: an absolute CLAUDE_PLUGIN_ROOT supplies the plugin root" \
646
+ || fail "CLAUDE_PLUGIN_ROOT fallback: $(sut_out "$res")"
647
+
648
+ r="$(mkrepo cprorder)"; assert_fixture_dir "$r"; res="$(_cpr "$P_OK" "$r" "$P_ALT")"
649
+ CASES_RUN=$((CASES_RUN + 1))
650
+ [[ "$(sut_rc "$res")" == "0" && "$(_git "$r" show HEAD:"$MODEL")" == *'"by":"plugin"'* && "$(sut_out "$res")" == *"root_src=sibling"* ]] \
651
+ && pass "a sibling renderer wins over CLAUDE_PLUGIN_ROOT when both resolve" \
652
+ || fail "CLAUDE_PLUGIN_ROOT displaced the sibling: $(sut_out "$res")"
653
+
654
+ # A RELATIVE CLAUDE_PLUGIN_ROOT resolves against the repo being merged: never used. The fixture
655
+ # makes it resolve to a VALID plugin inside the repo (ignored, so the tree stays clean), so a
656
+ # resolver that accepted it would render and commit — the refusal is the only thing in the way.
657
+ r="$(mkrepo cprrelative)"; assert_fixture_dir "$r"
658
+ mkdir -p "$r/rel"; cp -R "$P_OK/." "$r/rel/"; printf 'rel/\n' >> "$r/.git/info/exclude"
659
+ fpb="$(tree_fp "$r")"
660
+ res="$(_cpr "$P_LONELY" "$r" "rel")"
661
+ refused "a RELATIVE CLAUDE_PLUGIN_ROOT (resolving to a valid plugin inside the repo)" "$res" "$fpb" "$r" "reason=no-plugin-root"
662
+
663
+ r="$(mkrepo noroot)"; assert_fixture_dir "$r"; fpb="$(tree_fp "$r")"; res="$(run_at "$P_LONELY" "$r" main)"
664
+ refused "no plugin root at all" "$res" "$fpb" "$r" "reason=no-plugin-root"
665
+ CASES_RUN=$((CASES_RUN + 1))
666
+ grep -q 'set CLAUDE_PLUGIN_ROOT' <<<"$(sut_out "$res")" && pass "the no-root refusal names the remedy" \
667
+ || fail "the no-root refusal is not actionable: $(sut_out "$res")"
668
+
669
+ r="$(mkrepo noid)"; assert_fixture_dir "$r"; fpb="$(tree_fp "$r")"; res="$(run_at "$P_NOID" "$r" main)"
670
+ refused "a sibling plugin.json that does not name soleur" "$res" "$fpb" "$r" "does not name soleur"
671
+ r="$(mkrepo noidenv)"; assert_fixture_dir "$r"; fpb="$(tree_fp "$r")"; res="$(_cpr "$P_LONELY" "$r" "$P_NOID")"
672
+ refused "a CLAUDE_PLUGIN_ROOT whose plugin.json does not name soleur" "$res" "$fpb" "$r" "does not name soleur"
673
+
674
+ # A no-conflict run never needs a plugin root: classification comes first.
675
+ r="$(mkrepo rootlast)"; assert_fixture_dir "$r"; _git "$r" checkout -q -B feature main >/dev/null 2>&1
676
+ res="$(run_at "$P_LONELY" "$r" main)"
677
+ CASES_RUN=$((CASES_RUN + 1))
678
+ grep -q 'reason=no-conflict' <<<"$(sut_out "$res")" && pass "a missing plugin root does not mask 'no conflict' (classification first)" \
679
+ || fail "classification did not come first: $(sut_out "$res")"
680
+
681
+ # ── THE SEAM IS NOT REACHABLE FROM THE ENVIRONMENT (and the seam itself does execute) ──────
682
+ assert_fixture_dir "$SANDBOX"
683
+ printf '%s\tbash\t-c\ttouch "$0/PWNED"\t%s\n' "$MODEL" "$SANDBOX" > "$SANDBOX/evil.tsv"
684
+ rm -f "$SANDBOX/PWNED"
685
+ r="$(mkrepo envseam)"; assert_fixture_dir "$r"
686
+ (cd "$r" && RESOLVABLE_OVERRIDE="$SANDBOX/evil.tsv" bash "$P_OK/scripts/resolve-regenerable-conflicts.sh" main >/dev/null 2>&1)
687
+ CASES_RUN=$((CASES_RUN + 1))
688
+ [[ ! -e "$SANDBOX/PWNED" ]] && pass "the resolvable set is not reachable from the environment (argv-only seam)" \
689
+ || fail "RESOLVABLE_OVERRIDE still directs the resolver from the environment"
690
+ r="$(mkrepo envseamctl)"; assert_fixture_dir "$r"
691
+ res="$(run_sut "$r" main "$SANDBOX/evil.tsv")"
692
+ CASES_RUN=$((CASES_RUN + 1))
693
+ [[ -e "$SANDBOX/PWNED" ]] && pass "control: the same TSV through --test-resolvable DOES execute (the row above can fail)" \
694
+ || fail "control: the test seam did not execute — the env-seam row proves nothing: $(sut_out "$res")"
695
+
696
+ # ── RATCHETS ────────────────────────────────────────────────────────────────────────────────
697
+ # Every assignment to the resolvable set or its argvs must carry a named marker, and the marker
698
+ # set is pinned. Structural: a new assignment site WITHOUT a marker fails too.
699
+ _sites="$(grep -nE '^[^#]*RESOLVABLE_(PATHS|ARGVS)(\[[^]]*\])?\+?=' "$SUT" | grep -vE 'RESOLVABLE_(PATHS|ARGVS)=\(\)')"
700
+ _n_sites="$(grep -c . <<<"$_sites")"
701
+ _unmarked="$(grep -vE '# (ARGV-SOURCE|RESOLVABLE-SET): [a-z-]+[[:space:]]*$' <<<"$_sites" || true)"
702
+ CASES_RUN=$((CASES_RUN + 1))
703
+ if [[ "$_n_sites" -eq 3 && -z "$_unmarked" ]]; then
704
+ pass "every resolvable-set/argv assignment site carries a marker (3 sites)"
705
+ else
706
+ fail "resolvable-set/argv assignment sites changed ($_n_sites; unmarked: $_unmarked) — an ADR-235 amendment is required"
707
+ fi
708
+ _default_paths="$(grep -E 'RESOLVABLE-SET: default[[:space:]]*$' "$SUT" | sed -n 's/^[^#]*RESOLVABLE_PATHS=(\(.*\))[[:space:]]*#.*$/\1/p')"
709
+ CASES_RUN=$((CASES_RUN + 1))
710
+ [[ "$(grep -c 'RESOLVABLE-SET: default' "$SUT")" -eq 1 && "$_default_paths" == "\"$MODEL\"" ]] \
711
+ && pass "resolvable set: exactly one member, model.likec4.json (grow it deliberately + amend ADR-235)" \
712
+ || fail "resolvable set changed: [$_default_paths]"
713
+ _argv_sources="$(grep -E '# ARGV-SOURCE: [a-z-]+[[:space:]]*$' "$SUT" | sed -E 's/.*# ARGV-SOURCE: ([a-z-]+)[[:space:]]*$/\1/' | LC_ALL=C sort | tr '\n' ' ')"
714
+ CASES_RUN=$((CASES_RUN + 1))
715
+ [[ "$_argv_sources" == "plugin test-seam " ]] && pass "argv sources: exactly {plugin, test-seam}" \
716
+ || fail "argv sources changed: [$_argv_sources]"
717
+
718
+ # The resolver must NEVER push — in any git spelling (options before the subcommand count).
719
+ _PUSH_RE='(^|[^-[:alnum:]])git([[:space:]]+(-[Cc][[:space:]]+[^[:space:]]+|--?[[:alnum:]-]+(=[^[:space:]]+)?))*[[:space:]]+push([[:space:]]|$)'
720
+ CASES_RUN=$((CASES_RUN + 1))
721
+ if grep -v '^[[:space:]]*#' "$SUT" | grep -cE >/dev/null "$_PUSH_RE"; then
722
+ fail "the resolver contains a git push — callers own the push"
723
+ else
724
+ pass "the resolver never pushes"
725
+ fi
726
+ CASES_RUN=$((CASES_RUN + 1))
727
+ grep -qE "$_PUSH_RE" <<<'git -C /srv/repo push -q origin HEAD' && pass "control: the no-push pattern sees an option-prefixed push" \
728
+ || fail "control: the no-push pattern misses git -C <dir> push"
729
+
730
+ # ── tree_fp POSITIVE CONTROL ────────────────────────────────────────────────────────────────
731
+ _fpctl="$(mkrepo fpcontrol)"; assert_fixture_dir "$_fpctl"; _fp0="$(tree_fp "$_fpctl")"
732
+ printf 'dirty\n' >> "$_fpctl/$SRC"
733
+ CASES_RUN=$((CASES_RUN + 1))
734
+ [[ "$(tree_fp "$_fpctl")" != "$_fp0" ]] && pass "tree_fp control: a modified tracked file changes the fingerprint" \
735
+ || fail "tree_fp is not observing — every 'tree untouched' verdict is vacuous"
736
+ _git "$_fpctl" checkout -q -- "$SRC"; printf 'u\n' > "$_fpctl/untracked.txt"
737
+ CASES_RUN=$((CASES_RUN + 1))
738
+ [[ "$(tree_fp "$_fpctl")" != "$_fp0" ]] && pass "tree_fp control: an untracked file changes the fingerprint" \
739
+ || fail "tree_fp cannot see untracked files"
740
+
741
+ # ── AC9: NO DOCUMENTED INVOCATION IS REPO-RELATIVE ─────────────────────────────────────────
742
+ # Scoped to what an agent or hook executes; ADRs, plans and learnings quote history.
743
+ _REPO="$(cd "$SCRIPT_DIR/../../.." && pwd)"
744
+ _AC9_RE='(bash|sh|source|\.)[[:space:]]+["'"'"']?(\./)?(plugins/soleur/scripts/(resolve-regenerable-conflicts|render-c4-model)|scripts/regenerate-c4-model)\.sh'
745
+ _ac9_rc=0
746
+ _ac9="$(git -C "$_REPO" grep -nE "$_AC9_RE" -- plugins/soleur/skills plugins/soleur/commands plugins/soleur/agents \
747
+ plugins/soleur/hooks AGENTS.rules.md ':!*.test.sh' ':!*.test.ts' 2>&1)" || _ac9_rc=$?
748
+ CASES_RUN=$((CASES_RUN + 1))
749
+ if [[ "$_ac9_rc" -eq 1 && -z "$_ac9" ]]; then
750
+ pass "AC9: no skill, command, agent, plugin hook or rule invokes the resolver or renderer repo-relatively"
751
+ elif [[ "$_ac9_rc" -eq 0 ]]; then
752
+ fail "AC9: repo-relative invocation(s) remain — use \"\${CLAUDE_PLUGIN_ROOT}/scripts/…\": $(tr '\n' ' ' <<<"$_ac9")"
753
+ else
754
+ fail "AC9: git grep could not run (rc=$_ac9_rc): $_ac9"
755
+ fi
756
+ for _ctl in 'bash plugins/soleur/scripts/resolve-regenerable-conflicts.sh origin/main' \
757
+ 'bash "plugins/soleur/scripts/render-c4-model.sh"' 'bash scripts/regenerate-c4-model.sh'; do
758
+ CASES_RUN=$((CASES_RUN + 1))
759
+ grep -qE "$_AC9_RE" <<<"$_ctl" && pass "AC9 control: the sweep sees: $_ctl" \
760
+ || fail "AC9 control: the sweep cannot match: $_ctl"
761
+ done
762
+
763
+ echo ""
764
+ echo "cases_run=$CASES_RUN passes=$passes fails=$fails ledger=${#FAILED[@]}"
765
+
766
+ _min_cases=140
767
+ if [[ "$CASES_RUN" -lt "$_min_cases" ]]; then
768
+ printf '[FATAL] assertion floor: only %s case(s) ran, floor is %s\n' "$CASES_RUN" "$_min_cases" >&2; exit 1
769
+ fi
770
+ if [[ $((passes + fails)) -ne "$CASES_RUN" ]]; then
771
+ printf '[FATAL] %s verdicts for %s cases — a case decided nothing\n' "$((passes + fails))" "$CASES_RUN" >&2; exit 1
772
+ fi
773
+ if [[ "${#FAILED[@]}" -ne "$fails" ]]; then
774
+ printf '[FATAL] ledger/counter disagree: %s vs %s\n' "${#FAILED[@]}" "$fails" >&2; exit 1
775
+ fi
776
+ if [[ "${#FAILED[@]}" -ne 0 ]]; then
777
+ printf '[FATAL] %s failing assertion(s):\n' "${#FAILED[@]}" >&2
778
+ printf ' - %s\n' "${FAILED[@]}" >&2; exit 1
779
+ fi
780
+ echo "resolve-regenerable-conflicts: all $passes assertions passed"