@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,1056 @@
1
+ #!/usr/bin/env bash
2
+ # 2026-04-21 reproducer: cleanup_merged_worktrees must NOT reap a worktree
3
+ # whose `session-state.sh` lease is still active.
4
+ #
5
+ # Plan: knowledge-base/project/plans/2026-05-12-feat-bg-readiness-concurrency-hardening-plan.md
6
+ # Seed: knowledge-base/project/learnings/2026-04-21-concurrent-cleanup-merged-wipes-active-worktree.md
7
+ #
8
+ # Run via: bash plugins/soleur/skills/git-worktree/test/lease-protects-active.test.sh
9
+
10
+ set -uo pipefail
11
+
12
+ # Git-location tripwire (#7833). These suites drive worktree-manager.sh, which runs
13
+ # `git worktree remove`, `git branch -D` and `git reset --hard` -- the highest-damage git writes
14
+ # in the corpus. An inherited GIT_DIR aims all of them at the developer's real repository, so
15
+ # this file aborts rather than proceeding. Inline rather than sourcing test-helpers.sh, which
16
+ # would also import an assertion framework these suites do not use.
17
+ for _v in GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR GIT_OBJECT_DIRECTORY \
18
+ GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_NAMESPACE GIT_TEMPLATE_DIR GIT_EXEC_PATH; do
19
+ if [[ -n "${!_v:-}" && "${SOLEUR_GIT_TRIPWIRE_ALLOW:-0}" != "1" ]]; then
20
+ printf 'FATAL: %s started with an inherited git-location environment (%s=%s).\n' \
21
+ "${BASH_SOURCE[0]}" "$_v" "${!_v}" >&2
22
+ printf 'Fix the ENTRY POINT: unset %s && <runner>\n' "$_v" >&2
23
+ exit 97
24
+ fi
25
+ done
26
+ unset _v
27
+
28
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../.." && pwd)"
29
+ WM="$REPO_ROOT/plugins/soleur/skills/git-worktree/scripts/worktree-manager.sh"
30
+ SS="$REPO_ROOT/plugins/soleur/scripts/lib/session-state.sh"
31
+
32
+ PASS=0; FAIL=0
33
+ fail() { echo " FAIL: $1"; FAIL=$((FAIL+1)); }
34
+ pass() { echo " pass: $1"; PASS=$((PASS+1)); }
35
+
36
+ TMP=$(mktemp -d) || { echo "FATAL: mktemp -d failed — refusing to run with an empty \$TMP, every fixture path would resolve against /" >&2; exit 2; }
37
+ trap 'rm -rf "$TMP"' EXIT
38
+
39
+ # Resolved ONCE, with `pwd -P`: TMPDIR is routinely a symlink, and comparing an
40
+ # unresolved $TMP against a resolved toplevel false-FAILS every call.
41
+ TMP_REAL="$(cd "$TMP" && pwd -P)" || TMP_REAL=""
42
+ # An EMPTY TMP_REAL would silently disable the containment check below, because the
43
+ # pattern `"$TMP_REAL"/*` degrades to `/*`, which matches every absolute path — the
44
+ # guard would return 0 for a live repository. That is the same fail-open shape this
45
+ # whole helper exists to close, so it is asserted rather than assumed. `readonly`
46
+ # stops a later assignment from reintroducing it.
47
+ case "$TMP_REAL" in
48
+ /*) : ;;
49
+ *) echo "FATAL: could not resolve the fixture root '$TMP' to an absolute path — refusing, an empty root makes the containment check match everything" >&2
50
+ exit 2 ;;
51
+ esac
52
+ readonly TMP_REAL
53
+
54
+ # cd into a fixture, or refuse. Two arms, and the second is why `cd X || exit` is
55
+ # not enough:
56
+ # (a) the directory is absent -> cd fails.
57
+ # (b) the directory EXISTS but is not a repository -> cd SUCCEEDS, and git then
58
+ # walks UP to the enclosing repository. On 2026-08-20 that enclosing repo was
59
+ # a live worktree: `git worktree add` had created the path and then failed, so
60
+ # every guard keyed on cd's exit status passed and the fixture's commits landed
61
+ # on a real branch. Measured on main at 58486e1ae: HEAD 10b74df57 -> f5fd37bd6,
62
+ # carrying "victim", "victim2", "v9 change", "v12 change".
63
+ # So containment is asserted on the RESOLVED git toplevel, not on cd's exit status.
64
+ cdx() {
65
+ local target="$1" top
66
+ if ! cd "$target" 2>/dev/null; then
67
+ printf ' FAIL: fixture directory absent: %s\n' "$target" >&2
68
+ printf ' refusing to run fixture commands in %s — that is a LIVE repository.\n' "$PWD" >&2
69
+ : > "$TMP/.cdx-failed"
70
+ exit 90
71
+ fi
72
+ top="$(git rev-parse --show-toplevel 2>/dev/null)" || top=""
73
+ case "${top:-<none>}" in
74
+ "$TMP_REAL"|"$TMP_REAL"/*) return 0 ;;
75
+ esac
76
+ printf ' FAIL: %s exists, but its git toplevel is %s\n' "$target" "${top:-<not a git repository>}" >&2
77
+ printf ' which is OUTSIDE the fixture root %s — refusing, git would write to that repository.\n' "$TMP_REAL" >&2
78
+ : > "$TMP/.cdx-failed"
79
+ exit 90
80
+ }
81
+
82
+ # MERGE-RESOLUTION SELF-CHECK. This file was ALREADY partially hardened once — two sites carried
83
+ # explicit "Guarded cd" comments while five did not — and it regressed anyway; the reaper sites
84
+ # carry no fixture-commit symptom, so a regression there is silent. Two independent fixes to this
85
+ # file are in flight simultaneously, which makes "a merge drops one site back to a bare cd" a live
86
+ # possibility rather than a hypothetical. Assert the PROPERTY here instead of trusting review to
87
+ # notice. Excludes the `&&` forms, which short-circuit and are safe.
88
+ # Keyed on STRUCTURE, never on any guard WORDING, and evaluated on LOGICAL lines.
89
+ #
90
+ # Three corrections, each found by mutation rather than by reading:
91
+ # 1. Wording. A check greping for `cdx(` flags a site guarded correctly some other way; one
92
+ # greping for a guard PHRASE clears a site guarded with different wording. Not hypothetical
93
+ # -- a sibling session counted its own guarded sites with `grep -c <its guard phrase>`, got
94
+ # 5, and the answer was 7: it had worded its two reaper guards differently.
95
+ # 2. Reach. Anchoring at line start inspected 7 of this file 25 `cd "` sites -- the dominant
96
+ # `if ( cd "` form was invisible, while the message below claimed to refuse on ANY bare cd.
97
+ # A guard narrower than the claim it carries is this suite own subject, one level up.
98
+ # 3. Line shape. Most sites here are backslash-continued, so the `&&` that handles the failure
99
+ # sits on the NEXT physical line. Judging physical lines both misses a real defect and
100
+ # false-flags a correctly guarded two-line site.
101
+ #
102
+ # So: strip comments (prose naming `cd "` must not be inspected -- this block itself would
103
+ # match), join continuations into one logical line, then flag any `cd "` whose logical line
104
+ # neither carries `&&`/`||` nor makes cd itself the tested command. `if ( cd "$X" ... )` is NOT
105
+ # exempt merely for containing `if`: that tests the SUBSHELL, so a resolution dropping the `&&`
106
+ # leaves cd failure swallowed and the next command running in the wrong directory, which parses
107
+ # cleanly and is silent. That case is mutation-proven below.
108
+ _bare_cd="$(awk '
109
+ BEGIN { start_ln = 1 }
110
+ {
111
+ l = $0
112
+ sub(/(^|[[:space:]])#.*$/, "", l)
113
+ line = line l
114
+ if (sub(/\\[[:space:]]*$/, " ", line)) next
115
+ if (line ~ /(^|[^[:alnum:]_])cd "/ \
116
+ && line !~ /&&/ && line !~ /\|\|/ \
117
+ && line !~ /(^|[[:space:]])(if|while|until)[[:space:]]+!?[[:space:]]*cd[[:space:]]"/ \
118
+ && line !~ /(^|[^[:alnum:]_])cdx[[:space:]]/) print start_ln ": " line
119
+ line = ""; start_ln = NR + 1
120
+ }
121
+ ' "${BASH_SOURCE[0]}")"
122
+ if [[ -n "$_bare_cd" ]]; then
123
+ printf ' FAIL: unguarded cd site(s) in this suite — every fixture cd must go through cdx():\n' >&2
124
+ printf '%s\n' "$_bare_cd" >&2
125
+ printf ' A bare cd here runs git in the CALLER CWD when the fixture is missing.\n' >&2
126
+ exit 1
127
+ fi
128
+
129
+ # ---------------------------------------------------------------------------
130
+ # Stand up a fake bare repo with two branches: main + feat-victim. Merge
131
+ # feat-victim into main so it qualifies as "merged" for cleanup_merged_worktrees.
132
+ # ---------------------------------------------------------------------------
133
+ BARE="$TMP/repo.git"
134
+ git init --bare -b main "$BARE" >/dev/null
135
+
136
+ # Seed a commit on main via a temporary clone
137
+ SEED="$TMP/seed"
138
+ git clone "$BARE" "$SEED" >/dev/null 2>&1
139
+ ( cdx "$SEED"
140
+ git -c user.email=t@t -c user.name=t commit --allow-empty -m "seed" >/dev/null
141
+ git push origin main >/dev/null 2>&1
142
+ )
143
+ rm -rf "$SEED"
144
+
145
+ # Create a worktree directory simulating the layout worktree-manager.sh expects.
146
+ # We deliberately do NOT call `worktree-manager.sh feature` because that path
147
+ # performs network operations (git push -u origin) once Phase 4 wiring lands —
148
+ # and we want this test to exercise cleanup independently. Build the worktree
149
+ # state manually.
150
+ WT_PARENT="$TMP/wt-parent"
151
+ mkdir -p "$WT_PARENT/.worktrees"
152
+
153
+ # Anchor a fake "victim" checkout — the worktree that holds an active lease
154
+ # and which a sibling cleanup-merged invocation must NOT reap.
155
+ git -C "$BARE" worktree add -b feat-victim "$WT_PARENT/.worktrees/feat-victim" main >/dev/null 2>&1
156
+ ( cdx "$WT_PARENT/.worktrees/feat-victim"
157
+ echo hi > a.txt
158
+ git -c user.email=t@t -c user.name=t add a.txt
159
+ # Date the commit older than the 10-min recent-commit grace so the lease
160
+ # is the ONLY protection — guarantees the test fails if the lease guard
161
+ # is removed (avoids vacuous green via the recent-commit code path).
162
+ GIT_COMMITTER_DATE="2025-01-01T00:00:00Z" \
163
+ git -c user.email=t@t -c user.name=t commit \
164
+ --date "2025-01-01T00:00:00Z" -m "victim change" >/dev/null
165
+ )
166
+ # Fast-forward main to the victim commit so `git branch --merged main` lists it.
167
+ VICTIM_SHA=$(git -C "$BARE" rev-parse refs/heads/feat-victim)
168
+ git -C "$BARE" update-ref refs/heads/main "$VICTIM_SHA"
169
+
170
+ # Anchor a sibling "actor" worktree — the session that runs cleanup-merged
171
+ # concurrently. This is what reproduces 2026-04-21: cleanup runs from
172
+ # session A while session B holds the lease on the victim.
173
+ git -C "$BARE" worktree add -b feat-actor "$WT_PARENT/.worktrees/feat-actor" main >/dev/null 2>&1
174
+
175
+ # ---------------------------------------------------------------------------
176
+ # Acquire a lease for feat-victim. The lease lives under git-common-dir,
177
+ # which for our fake bare repo is $BARE itself.
178
+ # ---------------------------------------------------------------------------
179
+ LEASE_ROOT="$BARE/soleur-session-state"
180
+ mkdir -p "$LEASE_ROOT/leases" "$LEASE_ROOT/locks" "$LEASE_ROOT/logs"
181
+ # Pre-stamp the #7409 first-run arming hold. Every scenario below asserts what
182
+ # the reaper does with a lease present or absent; the hold suppresses reaping
183
+ # entirely on a store's first armed run, so without this stamp "the victim
184
+ # survived" would pass for the wrong reason in every one of them. Scenario 12
185
+ # is where the hold itself is tested.
186
+ : > "$LEASE_ROOT/reaper-armed"
187
+
188
+ # Hold a real, alive PID for the lease. Background a long sleep.
189
+ sleep 300 &
190
+ HOLDER_PID=$!
191
+ trap 'kill "$HOLDER_PID" 2>/dev/null || true; rm -rf "$TMP"' EXIT
192
+
193
+ cat > "$LEASE_ROOT/leases/feat-victim.lease" <<EOF
194
+ pid=$HOLDER_PID
195
+ ppid=$$
196
+ skill=one-shot
197
+ started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
198
+ expected_duration_min=240
199
+ hostname=$HOSTNAME
200
+ EOF
201
+
202
+ # ---------------------------------------------------------------------------
203
+ # Invoke worktree-manager.sh cleanup-merged from inside one of the fake
204
+ # worktrees. The worktree-manager.sh sources from $REPO_ROOT — that's the
205
+ # session-state.sh under test, even though GIT_ROOT will resolve to our
206
+ # fake bare repo via git rev-parse.
207
+ # ---------------------------------------------------------------------------
208
+ WT_VICTIM="$WT_PARENT/.worktrees/feat-victim"
209
+ WT_ACTOR="$WT_PARENT/.worktrees/feat-actor"
210
+
211
+ # Run cleanup-merged from the ACTOR worktree (a sibling). The existing
212
+ # "currently active" guard at worktree-manager.sh:795 only skips when PWD
213
+ # matches the worktree being considered — so feat-victim is NOT protected
214
+ # by that guard from a sibling session. Only the new lease guard protects it.
215
+ (
216
+ # `|| exit` IS LOAD-BEARING (#7546 review). A bare `cd` that FAILS leaves the subshell in the
217
+ # INHERITED cwd -- the real worktree `test-all.sh` was invoked from -- and the cleanup-merged
218
+ # below then runs against it. All seven `worktree add` calls in this file swallow failure with
219
+ # `>/dev/null 2>&1`, so $WT_ACTOR being absent is a reachable state (a leftover branch from a
220
+ # crashed run is enough). Measured 2026-08-20: this escaped the sandbox and committed
221
+ # `victim change`, `victim2 change`, `v9 change` and `v12 change` onto a live feature branch in
222
+ # another session's worktree, then checked that worktree out to main and pulled. The fixture
223
+ # names map one-to-one onto the four cd-failure sites in this file.
224
+ cdx "$WT_ACTOR"
225
+ SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT" \
226
+ bash "$WM" cleanup-merged >/tmp/cleanup-out.$$ 2>&1 || true
227
+ )
228
+
229
+ if [[ -d "$WT_VICTIM" ]]; then
230
+ pass "lease protected feat-victim worktree from sibling reap"
231
+ else
232
+ fail "feat-victim worktree was reaped by sibling despite active lease (output: $(cat /tmp/cleanup-out.$$ 2>/dev/null))"
233
+ fi
234
+ rm -f /tmp/cleanup-out.$$
235
+
236
+ kill "$HOLDER_PID" 2>/dev/null || true
237
+
238
+ # ---------------------------------------------------------------------------
239
+ # SCENARIO 2 (#5454, 2026-08-06): the acquirer has EXITED.
240
+ #
241
+ # The scenario above holds a live `sleep 300` as the lease pid. That is the one
242
+ # shape real usage never has — and it is why this suite was green for months
243
+ # while the guard protected nothing.
244
+ #
245
+ # `acquire_lease` records `pid=$$`, and every documented entry point is a
246
+ # short-lived process that exits within milliseconds of writing the file:
247
+ #
248
+ # bash <plugin-root>/scripts/lib/session-state.sh acquire_lease <worktree>
249
+ # bash .../worktree-manager.sh --yes create <branch>
250
+ #
251
+ # So in production the lease pid is ALWAYS dead moments after acquisition.
252
+ # `is_lease_active` gated on `kill -0`, `sweep_orphan_leases` deleted the file
253
+ # on a dead pid, and cleanup-merged consequently reaped the worktree, deleted
254
+ # the branch locally AND on origin, and closed the PR — twice in one afternoon,
255
+ # mid-run, on a leased worktree with hours of duration remaining.
256
+ #
257
+ # A fixture that instantiates only the passing member of a set is a sample, not
258
+ # a proof. This arm instantiates the member that actually occurs.
259
+ # ---------------------------------------------------------------------------
260
+ git -C "$BARE" worktree add -b feat-victim2 "$WT_PARENT/.worktrees/feat-victim2" main >/dev/null 2>&1
261
+ WT_VICTIM2="$WT_PARENT/.worktrees/feat-victim2"
262
+ ( cdx "$WT_VICTIM2"
263
+ echo hi2 > b.txt
264
+ git -c user.email=t@t -c user.name=t add b.txt
265
+ # Older than the 10-minute recent-commit grace, so the LEASE is the only
266
+ # thing that can protect it — otherwise this passes via the grace window and
267
+ # proves nothing about the lease.
268
+ GIT_COMMITTER_DATE="2025-01-01T00:00:00Z" \
269
+ git -c user.email=t@t -c user.name=t commit \
270
+ --date "2025-01-01T00:00:00Z" -m "victim2 change" >/dev/null
271
+ )
272
+ VICTIM2_SHA=$(git -C "$BARE" rev-parse refs/heads/feat-victim2)
273
+ git -C "$BARE" update-ref refs/heads/main "$VICTIM2_SHA"
274
+
275
+ # Acquire through the REAL entry point in a child process, then let it exit —
276
+ # reproducing production rather than simulating it.
277
+ bash -c "
278
+ export SOLEUR_SESSION_STATE_ROOT='$LEASE_ROOT'
279
+ # shellcheck source=/dev/null
280
+ source '$SS'
281
+ acquire_lease feat-victim2 one-shot 240
282
+ " >/dev/null 2>&1
283
+
284
+ DEAD_PID=$(grep '^pid=' "$LEASE_ROOT/leases/feat-victim2.lease" 2>/dev/null | cut -d= -f2)
285
+ if [[ -z "$DEAD_PID" ]]; then
286
+ fail "scenario 2 setup: no lease file written for feat-victim2"
287
+ elif kill -0 "$DEAD_PID" 2>/dev/null; then
288
+ # Asserted, not assumed: a live pid here would exercise scenario 1 again and
289
+ # pass vacuously against the very defect this arm exists to pin.
290
+ fail "scenario 2 precondition: recorded pid $DEAD_PID is still alive — cannot reach the dead-acquirer path"
291
+ else
292
+ pass "scenario 2 precondition: the acquiring process has exited (pid $DEAD_PID is dead)"
293
+ (
294
+ # `|| exit` load-bearing -- see the note at the first cd site above.
295
+ cdx "$WT_ACTOR"
296
+ SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT" \
297
+ bash "$WM" cleanup-merged >/tmp/cleanup2-out.$$ 2>&1 || true
298
+ )
299
+ if [[ -d "$WT_VICTIM2" ]]; then
300
+ pass "a lease whose acquirer exited STILL protects the worktree from a sibling reap"
301
+ else
302
+ fail "worktree reaped despite a valid in-window lease whose acquirer had exited — this is #5454 (output: $(cat /tmp/cleanup2-out.$$ 2>/dev/null))"
303
+ fi
304
+ rm -f /tmp/cleanup2-out.$$
305
+ fi
306
+
307
+ # ---------------------------------------------------------------------------
308
+ # SCENARIO 3 (PR #7373, second half): `create` must ACQUIRE a lease.
309
+ #
310
+ # Scenarios 1-2 prove a lease PROTECTS a worktree. Neither proves one is ever
311
+ # WRITTEN by the entry point the autonomous pipeline actually calls. It was not:
312
+ # `create_worktree` (dispatched from `create`) had zero acquire_lease calls,
313
+ # while `create_for_feature` (dispatched from `feature|feat`) had one. one-shot
314
+ # Step 0b and work Phase 1 both invoke `--yes create`, so every worktree the
315
+ # pipeline made carried no lease at all — and the documented SOLEUR_SKILL_NAME /
316
+ # SOLEUR_EXPECTED_DURATION_MIN env vars were read by nobody on that path.
317
+ #
318
+ # A lease layer that protects but is never acquired protects nothing. The header
319
+ # on scenario 2 ("a fixture that instantiates only the passing member of a set is
320
+ # a sample, not a proof") applied to this file itself until this arm landed.
321
+ #
322
+ # Fixture note: `create_worktree` resolves its base through `resolve_base_ref`,
323
+ # which needs origin/<from> — so this arm needs a local bare repo WITH an origin
324
+ # remote, not the bare-repo-only shape scenarios 1 and 2 use.
325
+ # ---------------------------------------------------------------------------
326
+ UP3="$TMP/up3.git"; git init --bare -b main "$UP3" >/dev/null
327
+ S3="$TMP/s3"; git clone "$UP3" "$S3" >/dev/null 2>&1
328
+ ( cd "$S3" && git -c user.email=t@t -c user.name=t commit --allow-empty -m seed >/dev/null \
329
+ && git push origin main >/dev/null 2>&1 )
330
+ rm -rf "$S3"
331
+ LOCAL3="$TMP/local3.git"; git init --bare -b main "$LOCAL3" >/dev/null
332
+ ( cd "$LOCAL3" && git remote add origin "$UP3" && git fetch --no-tags origin main:main >/dev/null 2>&1 )
333
+
334
+ LEASE_ROOT3="$LOCAL3/soleur-session-state"
335
+ # `if ( cd … && … )` rather than `&& pass || fail` (SC2015) or a bare `cd`
336
+ # (SC2164). The guarded cd is load-bearing, not style: this suite runs
337
+ # `set -uo pipefail` WITHOUT -e, so an unguarded `cd "$LOCAL3"` that fails would
338
+ # let the subshell continue and run `--yes create` against the DEVELOPER'S REAL
339
+ # REPO, creating a real worktree, branch and lease.
340
+ if ( cd "$LOCAL3" \
341
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
342
+ SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
343
+ bash "$WM" --yes create feat-probe >"$TMP/create3.log" 2>&1 ); then
344
+ pass "scenario 3: --yes create exited 0"
345
+ else
346
+ fail "scenario 3: --yes create failed (output: $(cat "$TMP/create3.log"))"
347
+ fi
348
+
349
+ LEASE3="$LEASE_ROOT3/leases/feat-probe.lease"
350
+ # Assert the FILE, never the directory: session-state.sh mkdir -p's leases/ at
351
+ # source time regardless of whether anything is written, so a `[[ -d ]]` check
352
+ # false-passes against the bug (measured during plan deepening).
353
+ if [[ -f "$LEASE3" ]]; then
354
+ pass "scenario 3: --yes create wrote $LEASE3"
355
+ # The no-op stubs worktree-manager.sh installs when session-state.sh is absent
356
+ # return 0 and write nothing, which would satisfy an exit-0 check; a `pid=`
357
+ # line can only come from a real acquire_lease.
358
+ if grep -q '^pid=' "$LEASE3"; then
359
+ pass "scenario 3: lease carries a pid= line (real acquire_lease, not a stub)"
360
+ else
361
+ fail "scenario 3: lease file exists but has no pid= line"
362
+ fi
363
+ # The env vars must be READ, not merely set by this test. Half the point of
364
+ # #5454 is that SOLEUR_SKILL_NAME / SOLEUR_EXPECTED_DURATION_MIN were
365
+ # documented on this path and consumed by nobody — so asserting the file
366
+ # exists proves the fix only halfway. Measured: replacing both reads with the
367
+ # literals "unknown"/"240" left the suite fully GREEN without these two.
368
+ if grep -q '^skill=one-shot$' "$LEASE3"; then
369
+ pass "scenario 3: SOLEUR_SKILL_NAME reached the lease (skill=one-shot)"
370
+ else
371
+ fail "scenario 3: lease does not carry skill=one-shot — the env var is not being read \
372
+ (got: $(grep '^skill=' "$LEASE3" 2>/dev/null || echo NONE))"
373
+ fi
374
+ if grep -q '^expected_duration_min=137$' "$LEASE3"; then
375
+ pass "scenario 3: SOLEUR_EXPECTED_DURATION_MIN reached the lease (137, a NON-default value)"
376
+ else
377
+ fail "scenario 3: lease does not carry expected_duration_min=137 — the env var is not being read \
378
+ (got: $(grep '^expected_duration_min=' "$LEASE3" 2>/dev/null || echo NONE))"
379
+ fi
380
+ else
381
+ fail "scenario 3: NO lease written by --yes create — this is the second half (PR #7373) \
382
+ (dir: $(ls -A "$LEASE_ROOT3/leases" 2>/dev/null || echo MISSING))"
383
+ fi
384
+
385
+ # ---------------------------------------------------------------------------
386
+ # SCENARIO 4 (PR #7373, re-entry arm): `create` on an EXISTING worktree must also
387
+ # acquire.
388
+ #
389
+ # Scenario 3 covers fresh creation. But both creating functions have an early
390
+ # return for "worktree already exists" that skips the whole tail of the
391
+ # function — including the lease block. `--yes create` sets response=y there,
392
+ # so a RESUMED one-shot/work run takes exactly this path and, without the fix,
393
+ # holds no lease for its entire duration while a sibling cleanup-merged is free
394
+ # to reap it.
395
+ #
396
+ # The lease belongs to "worked in", not to "created". This arm pins that.
397
+ # ---------------------------------------------------------------------------
398
+ rm -f "$LEASE3"
399
+ # ASSERT the setup landed. This `rm` carries the entire discriminating power of
400
+ # scenario 4 — it reuses scenario 3's repo, lease root AND branch name, so if the
401
+ # removal silently stops happening the arm reads scenario 3's lease and reports
402
+ # "re-entry re-acquired" while the re-entry acquire is absent. Measured: with the
403
+ # rm neutered and the re-entry block deleted, the suite went fully GREEN.
404
+ if [[ ! -f "$LEASE3" ]]; then
405
+ pass "scenario 4 fixture: scenario 3's lease was removed before the re-entry run"
406
+ else
407
+ fail "scenario 4 fixture: lease still present after rm — the arm cannot discriminate"
408
+ fi
409
+
410
+ if ( cd "$LOCAL3" \
411
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
412
+ SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
413
+ bash "$WM" --yes create feat-probe >"$TMP/create4.log" 2>&1 ); then
414
+ pass "scenario 4: --yes create on an existing worktree exited 0"
415
+ else
416
+ fail "scenario 4: re-entry create failed (output: $(cat "$TMP/create4.log"))"
417
+ fi
418
+
419
+ # Prove we actually took the early-return path rather than re-creating, else
420
+ # this arm silently duplicates scenario 3 and pins nothing new.
421
+ #
422
+ # Anchored on create_worktree's OWN literal, not the bare token 'already exists'
423
+ # (cq-assert-anchor-not-bare-token). git itself prints "fatal: a branch named
424
+ # 'feat-probe' already exists" when the fresh-create path runs, and copy_env_files
425
+ # prints "$env_file already exists, backing up" — so the bare token passes on the
426
+ # exact path this assertion exists to exclude. Measured: forcing the fresh-create
427
+ # path made the bare-token form emit a false `pass:`.
428
+ # The negative half is what pins the direction: the fresh-create banner must be ABSENT.
429
+ if grep -q 'Worktree already exists at:' "$TMP/create4.log" \
430
+ && ! grep -q 'Creating worktree from' "$TMP/create4.log"; then
431
+ pass "scenario 4 precondition: took the worktree-already-exists early return"
432
+ else
433
+ fail "scenario 4 precondition: did NOT take the early return (output: $(cat "$TMP/create4.log"))"
434
+ fi
435
+
436
+ if [[ -f "$LEASE3" ]] && grep -q '^pid=' "$LEASE3"; then
437
+ pass "scenario 4: re-entry re-acquired the lease — a resumed session is protected"
438
+ else
439
+ fail "scenario 4: NO lease written on re-entry — a resumed session runs unleased and reapable \
440
+ (dir: $(ls -A "$LEASE_ROOT3/leases" 2>/dev/null || echo MISSING))"
441
+ fi
442
+
443
+ # ---------------------------------------------------------------------------
444
+ # SCENARIO 5 (PR #7373): the `feature` verb's arms too — BOTH of them.
445
+ #
446
+ # Parity with create_for_feature is this fix's whole thesis, and half of it was
447
+ # unpinned: deleting create_for_feature's early-return acquire left the suite
448
+ # fully GREEN, because no fixture exercised `feature|feat` at all.
449
+ # ---------------------------------------------------------------------------
450
+ if ( cd "$LOCAL3" \
451
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
452
+ SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
453
+ bash "$WM" --yes feature probe5 >"$TMP/feature5.log" 2>&1 ); then
454
+ : # exit status is asserted via the lease below; a push to the fixture origin
455
+ # can legitimately warn without meaning the lease failed
456
+ fi
457
+ LEASE5="$LEASE_ROOT3/leases/feat-probe5.lease"
458
+ if [[ -f "$LEASE5" ]] && grep -q '^pid=' "$LEASE5"; then
459
+ pass "scenario 5: feature (fresh) acquired a lease"
460
+ else
461
+ fail "scenario 5: feature (fresh) wrote NO lease \
462
+ (dir: $(ls -A "$LEASE_ROOT3/leases" 2>/dev/null || echo MISSING))"
463
+ fi
464
+
465
+ rm -f "$LEASE5"
466
+ if ( cd "$LOCAL3" \
467
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
468
+ SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
469
+ bash "$WM" --yes feature probe5 >"$TMP/feature5b.log" 2>&1 ); then
470
+ :
471
+ fi
472
+ if [[ -f "$LEASE5" ]] && grep -q '^pid=' "$LEASE5"; then
473
+ pass "scenario 5: feature (re-entry) re-acquired a lease"
474
+ else
475
+ fail "scenario 5: feature (re-entry) wrote NO lease — a resumed feature session is reapable \
476
+ (output: $(cat "$TMP/feature5b.log"))"
477
+ fi
478
+
479
+ # ---------------------------------------------------------------------------
480
+ # SCENARIO 6 (PR #7373, re-pointed by #7408): a name the lease layer cannot key
481
+ # on must SAY SO.
482
+ #
483
+ # _validate_worktree_name rejects anything outside [A-Za-z0-9._-]. Measured
484
+ # before this arm: `--yes create feat/probe` exited 0, wrote ZERO leases, and
485
+ # emitted nothing an orchestrating agent could see — #5454's exact signature.
486
+ #
487
+ # RE-POINTED by #7408. This arm used `feat/probe6`, and the premise it rested on
488
+ # — "the lease cannot be made to work for that shape here" — is now FALSE:
489
+ # #7408 derives the lease key through `_safe_worktree_name`, so `feat/probe6`
490
+ # keys as `feat-probe6` and leases cleanly. Left as written, this arm asserted
491
+ # that a FIXED bug was still broken, and it went red the moment the fix landed.
492
+ #
493
+ # The scenario's real subject is the RESIDUAL class: a legal git refname that is
494
+ # still unkeyable AFTER slugification. `+` is legal in a refname (`git
495
+ # check-ref-format` accepts `feat+probe6`) and is outside the validator's
496
+ # character set, so it survives the transform unkeyable — which is exactly the
497
+ # class #7408's new `reason=name-not-keyable` discriminator exists to report.
498
+ # Asserting the reason= value here also gives that discriminator its only test.
499
+ # ---------------------------------------------------------------------------
500
+ if ( cd "$LOCAL3" \
501
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
502
+ SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
503
+ bash "$WM" --yes create feat+probe6 >"$TMP/create6.log" 2>&1 ); then
504
+ :
505
+ fi
506
+ if grep -q 'SOLEUR_WORKTREE_LEASE_ACQUIRE_FAILED' "$TMP/create6.log"; then
507
+ pass "scenario 6: an unleasable branch name emits the ACQUIRE_FAILED marker"
508
+ else
509
+ fail "scenario 6: unleasable name ran UNLEASED and SILENT — no marker on stdout \
510
+ (output: $(cat "$TMP/create6.log"))"
511
+ fi
512
+ if grep -q 'reason=name-not-keyable' "$TMP/create6.log"; then
513
+ pass "scenario 6b: the marker discriminates the residual class (reason=name-not-keyable)"
514
+ else
515
+ fail "scenario 6b: marker did not carry reason=name-not-keyable — the #7408 discriminator \
516
+ is unreported for a name that survives slugification unkeyable \
517
+ (output: $(cat "$TMP/create6.log"))"
518
+ fi
519
+ # A slash-bearing name, by contrast, must now lease CLEANLY — the #7408 fix.
520
+ # Without this the arm above could be satisfied by a transform that does nothing.
521
+ if ( cd "$LOCAL3" \
522
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
523
+ SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
524
+ bash "$WM" --yes create feat/probe6 >"$TMP/create6c.log" 2>&1 ); then
525
+ :
526
+ fi
527
+ if grep -q 'SOLEUR_WORKTREE_LEASE_ACQUIRE_FAILED' "$TMP/create6c.log"; then
528
+ fail "scenario 6c: a SLASH-bearing branch still failed to lease — #7408's fix is not in effect \
529
+ (output: $(cat "$TMP/create6c.log"))"
530
+ else
531
+ pass "scenario 6c: a slash-bearing branch leases cleanly under its slug"
532
+ fi
533
+
534
+ # ---------------------------------------------------------------------------
535
+ # SCENARIO 7 (PR #7373): `switch|go` must lease too.
536
+ #
537
+ # The last entry point that took an EXISTING worktree and leased nothing. A
538
+ # session that switches in and works for hours held no lease for any of them,
539
+ # which is the same hole the two early-return arms had — `switch_worktree` just
540
+ # lives in a different function, so fixing the arms did not reach it.
541
+ #
542
+ # Reuses scenario 3's worktree: delete the lease, switch in, assert it returns.
543
+ # ---------------------------------------------------------------------------
544
+ rm -f "$LEASE3"
545
+ if [[ ! -f "$LEASE3" ]]; then
546
+ pass "scenario 7 fixture: lease removed before the switch"
547
+ else
548
+ fail "scenario 7 fixture: lease still present after rm — the arm cannot discriminate"
549
+ fi
550
+
551
+ if ( cd "$LOCAL3" \
552
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
553
+ SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
554
+ bash "$WM" --yes switch feat-probe >"$TMP/switch7.log" 2>&1 ); then
555
+ :
556
+ fi
557
+ if [[ -f "$LEASE3" ]] && grep -q '^pid=' "$LEASE3"; then
558
+ pass "scenario 7: switch acquired a lease — entering a worktree is working in it"
559
+ else
560
+ fail "scenario 7: switch wrote NO lease — a switched-into session runs unleased and reapable \
561
+ (output: $(cat "$TMP/switch7.log"))"
562
+ fi
563
+
564
+ # ---------------------------------------------------------------------------
565
+ # SCENARIO 8 (#7409): the MARKETPLACE-INSTALL layout.
566
+ #
567
+ # Every scenario above runs `worktree-manager.sh` out of THIS repo, where
568
+ # `../../../../..` reaches a tree that happens to contain the lease library. A
569
+ # marketplace user has no such tree: the plugin is copied alone into
570
+ # ~/.claude/plugins/cache/<mkt>/soleur/<ver>/, so a path walk that leaves the
571
+ # plugin root resolves to nothing, the no-op stubs load, and the entire lock and
572
+ # lease layer is silently absent for that entire population (#7409).
573
+ #
574
+ # FIXTURE DISCIPLINE: copy ONLY plugins/soleur. Copying the repo makes this
575
+ # vacuous, because the bug *is* the absence of everything outside that
576
+ # directory — asserted below as a precondition, not assumed.
577
+ # ---------------------------------------------------------------------------
578
+ CACHE_ROOT="$TMP/cache/soleur-mkt/soleur/9.9.9"
579
+ mkdir -p "$CACHE_ROOT"
580
+ cp -r "$REPO_ROOT/plugins/soleur/." "$CACHE_ROOT/" 2>/dev/null
581
+ CACHE_WM="$CACHE_ROOT/skills/git-worktree/scripts/worktree-manager.sh"
582
+
583
+ if [[ -z "$(find "$CACHE_ROOT" -name '.claude' -type d -print -quit 2>/dev/null)" ]]; then
584
+ pass "scenario 8 fixture: the cache tree contains no .claude/ — a real marketplace layout"
585
+ else
586
+ fail "scenario 8 fixture: cache tree contains a .claude/ directory — the fixture cannot \
587
+ reproduce #7409 and every assertion below would pass vacuously"
588
+ fi
589
+
590
+ # A throwaway repo with an origin remote (create_worktree resolves its base
591
+ # through resolve_base_ref, which needs origin/<from>).
592
+ UP8="$TMP/up8.git"; git init --bare -b main "$UP8" >/dev/null
593
+ S8="$TMP/s8"; git clone "$UP8" "$S8" >/dev/null 2>&1
594
+ ( cd "$S8" && git -c user.email=t@t -c user.name=t commit --allow-empty -m seed >/dev/null \
595
+ && git push origin main >/dev/null 2>&1 )
596
+ rm -rf "$S8"
597
+ LOCAL8="$TMP/local8.git"; git init --bare -b main "$LOCAL8" >/dev/null
598
+ ( cd "$LOCAL8" && git remote add origin "$UP8" && git fetch --no-tags origin main:main >/dev/null 2>&1 )
599
+ LEASE_ROOT8="$LOCAL8/soleur-session-state"
600
+
601
+ # Guarded cd — this suite runs `set -uo pipefail` WITHOUT -e, so an unguarded
602
+ # `cd` failure would run `--yes create` against the developer's real repo.
603
+ if ( cd "$LOCAL8" \
604
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT8" \
605
+ SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
606
+ bash "$CACHE_WM" --yes create feat-cacheprobe >"$TMP/create8.log" 2>&1 ); then
607
+ : # Deliberately NO exit-code assertion: `--yes create` exits 0 in BOTH the
608
+ # broken and the fixed state (_acquire_worktree_lease early-returns 0 when the
609
+ # library is missing, and every call site is `|| true`). The exit code carries
610
+ # no information here; the lease FILE is the only discriminator.
611
+ fi
612
+
613
+ LEASE8="$LEASE_ROOT8/leases/feat-cacheprobe.lease"
614
+ if [[ -f "$LEASE8" ]] && grep -q '^pid=' "$LEASE8"; then
615
+ pass "scenario 8: a cache-install worktree-manager.sh resolved the lease library and wrote a lease"
616
+ else
617
+ fail "scenario 8: NO lease written from a marketplace-install layout — this is #7409; every \
618
+ installed user runs with no lock/lease layer (output: $(cat "$TMP/create8.log" 2>/dev/null))"
619
+ fi
620
+ # 137, never 240: 240 is the default at BOTH worktree-manager.sh's
621
+ # ${SOLEUR_EXPECTED_DURATION_MIN:-240} and session-state.sh's ${3:-240}, so
622
+ # asserting it passes whether the env var is read or ignored (the #5454 vacuity
623
+ # class). skill=one-shot is discriminating too — the default is `unknown`.
624
+ if grep -q '^skill=one-shot$' "$LEASE8" 2>/dev/null; then
625
+ pass "scenario 8: SOLEUR_SKILL_NAME reached the lease from a cache install"
626
+ else
627
+ fail "scenario 8: lease does not carry skill=one-shot \
628
+ (got: $(grep '^skill=' "$LEASE8" 2>/dev/null || echo NONE))"
629
+ fi
630
+ if grep -q '^expected_duration_min=137$' "$LEASE8" 2>/dev/null; then
631
+ pass "scenario 8: SOLEUR_EXPECTED_DURATION_MIN reached the lease (137, a NON-default value)"
632
+ else
633
+ fail "scenario 8: lease does not carry expected_duration_min=137 \
634
+ (got: $(grep '^expected_duration_min=' "$LEASE8" 2>/dev/null || echo NONE))"
635
+ fi
636
+
637
+ # --- Scenario 8b (T2): the mutation arm -----------------------------------
638
+ # Delete the library from the cache fixture. Scenario 8 must become
639
+ # unsatisfiable — otherwise scenario 8 is passing for some reason other than
640
+ # the library resolving, and pins nothing.
641
+ CACHE_LIB="$(find "$CACHE_ROOT" -name 'session-state.sh' -print -quit 2>/dev/null)"
642
+ if [[ -n "$CACHE_LIB" ]]; then
643
+ mv "$CACHE_LIB" "$CACHE_LIB.disabled"
644
+ if ( cd "$LOCAL8" \
645
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT8" \
646
+ SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
647
+ bash "$CACHE_WM" --yes create feat-cachemutant >"$TMP/create8b.log" 2>&1 ); then
648
+ :
649
+ fi
650
+ if [[ -f "$LEASE_ROOT8/leases/feat-cachemutant.lease" ]]; then
651
+ fail "scenario 8b (mutation): a lease was written with the library REMOVED — scenario 8 is \
652
+ vacuous, it is not measuring library resolution"
653
+ else
654
+ pass "scenario 8b (mutation): removing the library from the cache fixture stops the lease \
655
+ being written — scenario 8 is non-vacuous"
656
+ fi
657
+ if grep -q 'SOLEUR_WORKTREE_LEASE_LIB_MISSING' "$TMP/create8b.log"; then
658
+ pass "scenario 8b (mutation): the absence is reported on stdout, not silent"
659
+ else
660
+ fail "scenario 8b (mutation): library absent but NO marker on stdout \
661
+ (output: $(cat "$TMP/create8b.log" 2>/dev/null))"
662
+ fi
663
+ # The OK marker's DIRECTION, not just its presence. Scenario 10 asserts it
664
+ # fires when the library resolves; without this arm nothing asserts it does
665
+ # NOT fire when the library is gone — so hoisting the echo above the
666
+ # `if [[ -f "$_SS_LIB" ]]` (making it unconditional) left the whole suite
667
+ # green while the marker claimed protection was on with the layer absent.
668
+ # That is the worst possible failure for a signal whose entire purpose is to
669
+ # make silence unambiguous, and it is the marketplace user's only channel.
670
+ if grep -q 'SOLEUR_WORKTREE_LEASE_LIB_OK' "$TMP/create8b.log"; then
671
+ fail "scenario 8b (mutation): the ..._LIB_OK marker fired with the library ABSENT — \
672
+ it is not conditioned on resolution and reports protection that is not there"
673
+ else
674
+ pass "scenario 8b (mutation): ..._LIB_OK is absent when the library is — the marker \
675
+ discriminates rather than always firing"
676
+ fi
677
+ mv "$CACHE_LIB.disabled" "$CACHE_LIB"
678
+ else
679
+ fail "scenario 8b (mutation): no session-state.sh found anywhere under the cache fixture — \
680
+ the library does not ship in the plugin, which is the #7409 defect itself"
681
+ fi
682
+
683
+ # ---------------------------------------------------------------------------
684
+ # SCENARIO 9 (#7409, T3): the DESTRUCTIVE direction, in a cache layout.
685
+ #
686
+ # This PR ARMS THE REAPER for the marketplace population. Pre-fix their library
687
+ # is missing, so `is_lease_active(){ return 0; }` makes cleanup-merged refuse to
688
+ # reap anything, ever. Post-fix the library resolves and an unrecoverable
689
+ # operation — delete the worktree, the local branch, the remote branch, close
690
+ # the PR — goes live for them for the first time. Scenarios 8/8b cover
691
+ # ACQUISITION; nothing covers REFUSAL from a cache layout. That asymmetry is the
692
+ # whole risk profile of this change.
693
+ # ---------------------------------------------------------------------------
694
+ BARE9="$TMP/repo9.git"; git init --bare -b main "$BARE9" >/dev/null
695
+ SEED9="$TMP/seed9"; git clone "$BARE9" "$SEED9" >/dev/null 2>&1
696
+ ( cd "$SEED9" && git -c user.email=t@t -c user.name=t commit --allow-empty -m seed >/dev/null \
697
+ && git push origin main >/dev/null 2>&1 )
698
+ rm -rf "$SEED9"
699
+ WT9="$TMP/wt9"; mkdir -p "$WT9/.worktrees"
700
+ git -C "$BARE9" worktree add -b feat-v9 "$WT9/.worktrees/feat-v9" main >/dev/null 2>&1
701
+ ( cdx "$WT9/.worktrees/feat-v9"
702
+ echo hi9 > c.txt
703
+ git -c user.email=t@t -c user.name=t add c.txt
704
+ # Older than the 10-minute recent-commit grace, so the LEASE is the only thing
705
+ # that can protect it — otherwise this passes via the grace window and proves
706
+ # nothing about lease resolution from a cache install.
707
+ GIT_COMMITTER_DATE="2025-01-01T00:00:00Z" \
708
+ git -c user.email=t@t -c user.name=t commit \
709
+ --date "2025-01-01T00:00:00Z" -m "v9 change" >/dev/null
710
+ )
711
+ git -C "$BARE9" update-ref refs/heads/main "$(git -C "$BARE9" rev-parse refs/heads/feat-v9)"
712
+ git -C "$BARE9" worktree add -b feat-a9 "$WT9/.worktrees/feat-a9" main >/dev/null 2>&1
713
+
714
+ LEASE_ROOT9="$BARE9/soleur-session-state"
715
+ mkdir -p "$LEASE_ROOT9/leases"
716
+ : > "$LEASE_ROOT9/reaper-armed" # see the note at LEASE_ROOT above
717
+ cat > "$LEASE_ROOT9/leases/feat-v9.lease" <<EOF
718
+ pid=$$
719
+ ppid=$$
720
+ skill=one-shot
721
+ started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
722
+ expected_duration_min=137
723
+ hostname=$HOSTNAME
724
+ EOF
725
+
726
+ if ( cd "$WT9/.worktrees/feat-a9" \
727
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT9" \
728
+ bash "$CACHE_WM" cleanup-merged >"$TMP/cleanup9.log" 2>&1 ); then
729
+ :
730
+ fi
731
+ if [[ -d "$WT9/.worktrees/feat-v9" ]]; then
732
+ pass "scenario 9: from a CACHE install, the reaper refused to reap a leased worktree"
733
+ else
734
+ fail "scenario 9: a cache-install cleanup-merged REAPED a leased worktree — this PR arms an \
735
+ unrecoverable operation for every marketplace user (output: $(cat "$TMP/cleanup9.log" 2>/dev/null))"
736
+ fi
737
+
738
+ # Mutation arm: clear the lease and the SAME victim must now be reaped. Without
739
+ # this, scenario 9 is satisfied by cleanup-merged doing nothing for any reason at
740
+ # all — an early return, a lock it could not take, a fail-closed stub.
741
+ rm -f "$LEASE_ROOT9/leases/feat-v9.lease"
742
+ if ( cd "$WT9/.worktrees/feat-a9" \
743
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT9" \
744
+ bash "$CACHE_WM" cleanup-merged >"$TMP/cleanup9b.log" 2>&1 ); then
745
+ :
746
+ fi
747
+ if [[ -d "$WT9/.worktrees/feat-v9" ]]; then
748
+ fail "scenario 9b (mutation): the victim survived with NO lease — scenario 9 is vacuous, the \
749
+ reaper is not reaping for some unrelated reason (output: $(cat "$TMP/cleanup9b.log" 2>/dev/null))"
750
+ else
751
+ pass "scenario 9b (mutation): with the lease cleared the victim IS reaped — scenario 9's \
752
+ refusal is attributable to the lease, not to an inert reaper"
753
+ fi
754
+
755
+ # ---------------------------------------------------------------------------
756
+ # SCENARIO 10 (#7409, T3b): the SKILL.md anchor hop.
757
+ #
758
+ # Every other scenario in this file invokes worktree-manager.sh by absolute path
759
+ # inside a fixture. A marketplace user never does that — their agent executes the
760
+ # `"${CLAUDE_PLUGIN_ROOT}/…"` form written in SKILL.md (bare since #7453). That hop is the link the
761
+ # whole fix has to traverse, and nothing tested it: the suite could be fully
762
+ # green while delivering nothing to the population #7409 is about.
763
+ # ---------------------------------------------------------------------------
764
+ NONSOLEUR="$TMP/not-soleur"; mkdir -p "$NONSOLEUR"
765
+ git init -b main "$NONSOLEUR" >/dev/null 2>&1
766
+ ( cd "$NONSOLEUR" && git -c user.email=t@t -c user.name=t commit --allow-empty -m seed >/dev/null 2>&1 )
767
+ if [[ ! -e "$NONSOLEUR/plugins/soleur" ]]; then
768
+ pass "scenario 10 fixture: cwd is a non-Soleur repo (no ./plugins/soleur to fall back to)"
769
+ else
770
+ fail "scenario 10 fixture: cwd contains ./plugins/soleur — the anchor's default arm would \
771
+ resolve and the hop under test is bypassed"
772
+ fi
773
+ # `export` on its own line, NOT a `CLAUDE_PLUGIN_ROOT=… bash "${CLAUDE_PLUGIN_ROOT}/…"`
774
+ # prefix assignment: a command-prefix assignment populates the COMMAND's
775
+ # environment, but every expansion on that same command line is performed first,
776
+ # against the current shell — so the anchor would expand EMPTY and this scenario
777
+ # would silently test a root-anchored `/skills/…` path instead of the hop.
778
+ # Measured: written that way, the run failed with `bash: ./plugins/…: No such
779
+ # file or directory`, and the negative assertion below still reported `pass`.
780
+ if ( cd "$NONSOLEUR" \
781
+ && export CLAUDE_PLUGIN_ROOT="$CACHE_ROOT" \
782
+ && bash "${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh" \
783
+ list >"$TMP/anchor10.log" 2>&1 ); then
784
+ :
785
+ fi
786
+ # The anchor must have resolved to a REAL script. Without this, every assertion
787
+ # below is satisfied by the interpreter failing to find the file at all — which
788
+ # is precisely how the fixture bug above stayed invisible.
789
+ if grep -q 'No such file or directory' "$TMP/anchor10.log"; then
790
+ fail "scenario 10 precondition: the anchor did not resolve to a script — assertions below \
791
+ cannot discriminate (output: $(cat "$TMP/anchor10.log" 2>/dev/null))"
792
+ else
793
+ pass "scenario 10 precondition: the anchor resolved to a real script"
794
+ fi
795
+ if grep -q 'SOLEUR_WORKTREE_LEASE_LIB_OK' "$TMP/anchor10.log"; then
796
+ pass "scenario 10: reached through the SKILL.md anchor form, the library resolves (\
797
+ ..._LIB_OK on stdout)"
798
+ else
799
+ fail "scenario 10: through the SKILL.md anchor form from a non-Soleur cwd the library did NOT \
800
+ resolve — the hop a marketplace user actually traverses is broken \
801
+ (output: $(cat "$TMP/anchor10.log" 2>/dev/null))"
802
+ fi
803
+ if grep -q 'SOLEUR_WORKTREE_LEASE_LIB_MISSING' "$TMP/anchor10.log"; then
804
+ fail "scenario 10: the MISSING marker was emitted through the anchor hop"
805
+ else
806
+ pass "scenario 10: no MISSING marker through the anchor hop"
807
+ fi
808
+
809
+ # ---------------------------------------------------------------------------
810
+ # SCENARIO 10b (#7453, ADR-179 A18): the same hop with the root UNSET, from a tree
811
+ # that plants its own `./plugins/soleur/…/worktree-manager.sh` decoy. The bare anchor
812
+ # must fail closed on a root-anchored `/skills/…` path and never run the decoy. The
813
+ # twin runs the PRE-migration default-arm hop from the same directory and MUST run
814
+ # the decoy — proving the decoy is reachable, so the empty ledger above means something.
815
+ # The default arm is assembled from pieces so this file never spells the rejected form.
816
+ # ---------------------------------------------------------------------------
817
+ DECOYTREE="$TMP/decoy-tree"; mkdir -p "$DECOYTREE/plugins/soleur/skills/git-worktree/scripts"
818
+ DECOY_LEDGER="$(mktemp -p "$TMP" decoy-ledger.XXXXXX)"
819
+ printf '#!/usr/bin/env bash\necho decoy-ran >> "%s"\n' "$DECOY_LEDGER" \
820
+ > "$DECOYTREE/plugins/soleur/skills/git-worktree/scripts/worktree-manager.sh"
821
+ chmod +x "$DECOYTREE/plugins/soleur/skills/git-worktree/scripts/worktree-manager.sh"
822
+ # The hop is the SHIPPED line, read out of SKILL.md — a hardcoded copy would stay green
823
+ # if SKILL.md reverted to the default arm. All four emissions must be byte-identical.
824
+ WT_SKILL="$REPO_ROOT/plugins/soleur/skills/git-worktree/SKILL.md"
825
+ LIST_LINES="$(grep -E '^bash "[$][{]CLAUDE_PLUGIN_ROOT[}]/skills/git-worktree/scripts/worktree-manager[.]sh" list$' "$WT_SKILL")"
826
+ if [[ "$(printf '%s\n' "$LIST_LINES" | grep -c .)" -ne 4 || "$(printf '%s\n' "$LIST_LINES" | sort -u | grep -c .)" -ne 1 ]]; then
827
+ printf '[FATAL] scenario 10b: expected 4 identical bare-anchor list lines in %s, got:\n%s\n' "$WT_SKILL" "$LIST_LINES" >&2; exit 1
828
+ fi
829
+ SHIPPED_LIST="$(printf '%s\n' "$LIST_LINES" | head -1)"
830
+ ( cd "$DECOYTREE" && env -u CLAUDE_PLUGIN_ROOT -u GROK_PLUGIN_ROOT \
831
+ bash -c "$SHIPPED_LIST" \
832
+ >"$TMP/anchor10b.log" 2>&1 )
833
+ rc10b=$?
834
+ if [[ "$rc10b" -ne 0 ]] && grep -q '/skills/git-worktree/scripts/worktree-manager.sh: No such file' "$TMP/anchor10b.log" \
835
+ && [[ ! -s "$DECOY_LEDGER" ]]; then
836
+ pass "scenario 10b: root unset — the bare anchor fails closed and the planted decoy never runs"
837
+ else
838
+ fail "scenario 10b: root unset — rc=$rc10b ledger=$(cat "$DECOY_LEDGER") out=$(cat "$TMP/anchor10b.log")"
839
+ fi
840
+ DEF=':-'
841
+ ( cd "$DECOYTREE" && env -u CLAUDE_PLUGIN_ROOT -u GROK_PLUGIN_ROOT \
842
+ bash -c "bash \"\${CLAUDE_PLUGIN_ROOT${DEF}./plugins/soleur}/skills/git-worktree/scripts/worktree-manager.sh\" list" \
843
+ >/dev/null 2>&1 )
844
+ if grep -qx 'decoy-ran' "$DECOY_LEDGER"; then
845
+ pass "scenario 10b twin: the pre-migration default-arm hop DOES run the decoy"
846
+ else
847
+ fail "scenario 10b twin: the decoy did not run — scenario 10b's empty ledger proves nothing"
848
+ fi
849
+
850
+ # ---------------------------------------------------------------------------
851
+ # SCENARIO 11 (#7409, T6): OLD-tree / NEW-tree interoperation.
852
+ #
853
+ # During rollout, one worktree on disk predates the move and sources the library
854
+ # from .claude/hooks/lib/, while a sibling sources it from the plugin. Both
855
+ # anchor their lease store to `git rev-parse --git-common-dir`, so they share one
856
+ # set of lease files. If the two copies disagreed about the lease FORMAT, a new
857
+ # worktree would read an old worktree's lease as absent and reap it.
858
+ #
859
+ # AC10's byte-identity check proves the file is unchanged; it does NOT prove
860
+ # interop, which is the property the rollout actually depends on. This arm runs
861
+ # the ORIGIN/MAIN copy (genuinely old code) against the post-move copy.
862
+ # ---------------------------------------------------------------------------
863
+ # PINNED TO AN IMMUTABLE BLOB, never to a moving ref.
864
+ #
865
+ # The first draft resolved the pre-move path out of `origin/main`'s tree. That is
866
+ # correct for exactly as long as this change is unmerged: the moment it lands,
867
+ # `origin/main` IS this tree, `hooks/lib/session-state.sh` matches nothing, and
868
+ # all three assertions below fail — permanently, on main, for every branch cut
869
+ # from it. A proof-of-red pinned to a moving ref consumes its own fix, and it
870
+ # fires when the rollout SUCCEEDS. (The prior comment defended the empty lookup
871
+ # as anti-vacuity; loudness with no expiry is not a regression detector.)
872
+ #
873
+ # A blob SHA is reachable from history forever, so `git cat-file` keeps working
874
+ # after the path is gone. It also removes the `.claude/hooks/lib/session-state.sh`
875
+ # literal, which AC6 asserts is absent everywhere under plugins/soleur/skills/.
876
+ OLD_BLOB="864f3865590ee92b1fc9348c0b2bc6371091b8fa" # .claude/hooks/lib/session-state.sh @ pre-move main
877
+ OLD_LIB="$TMP/oldtree/legacy-session-state.sh"
878
+ mkdir -p "$(dirname "$OLD_LIB")"
879
+ if git -C "$REPO_ROOT" cat-file -e "$OLD_BLOB" 2>/dev/null \
880
+ && git -C "$REPO_ROOT" cat-file blob "$OLD_BLOB" > "$OLD_LIB" 2>/dev/null \
881
+ && [[ -s "$OLD_LIB" ]]; then
882
+ pass "scenario 11 fixture: recovered the pre-move library from blob ${OLD_BLOB:0:12}"
883
+ elif [[ -n "${CI:-}" ]]; then
884
+ # Under CI the checkout pins full depth, so reachability is contractual and an
885
+ # absent object is a real failure. Locally a blobless/shallow clone is ordinary,
886
+ # and degrading to a hard FAIL there would train people to ignore this suite.
887
+ fail "scenario 11 fixture: pre-move blob $OLD_BLOB unreachable under CI — \
888
+ the checkout is not full-depth, so old/new interop is unverified"
889
+ else
890
+ pass "scenario 11: SKIPPED — pre-move blob $OLD_BLOB not in this clone (blobless/shallow); \
891
+ interop unverified locally, enforced under CI"
892
+ fi
893
+ NEW_LIB="$REPO_ROOT/plugins/soleur/scripts/lib/session-state.sh"
894
+ SHARED11="$TMP/shared11"; mkdir -p "$SHARED11/leases"
895
+ # The two arms below are only meaningful when the fixture actually materialized.
896
+ # Without this guard a legitimate blobless-clone SKIP would report as two
897
+ # failures — the arm reporting a fixture problem as a product defect.
898
+ if [[ -s "$OLD_LIB" ]]; then
899
+ # OLD code acquires...
900
+ bash -c "
901
+ export SOLEUR_SESSION_STATE_ROOT='$SHARED11'
902
+ # shellcheck source=/dev/null
903
+ source '$OLD_LIB'
904
+ acquire_lease feat-interop one-shot 137
905
+ " >/dev/null 2>&1
906
+ if [[ -f "$SHARED11/leases/feat-interop.lease" ]]; then
907
+ pass "scenario 11: the pre-move library wrote a lease into the shared store"
908
+ else
909
+ fail "scenario 11: the pre-move library wrote no lease — fixture cannot discriminate"
910
+ fi
911
+ else
912
+ pass "scenario 11: acquire arm skipped (no pre-move fixture)"
913
+ fi
914
+ # ...and NEW code must honour it. `is_lease_active` is only trustworthy here
915
+ # because NEW_LIB genuinely exists: when it does not, worktree-manager.sh's
916
+ # fail-closed STUB also returns 0, so this would pass vacuously against the exact
917
+ # bug. Guard on the file, then call the real function.
918
+ if [[ ! -s "$OLD_LIB" ]]; then
919
+ pass "scenario 11: honour arm skipped (no pre-move fixture)"
920
+ elif [[ -f "$NEW_LIB" ]] && bash -c "
921
+ export SOLEUR_SESSION_STATE_ROOT='$SHARED11'
922
+ # shellcheck source=/dev/null
923
+ source '$NEW_LIB'
924
+ is_lease_active feat-interop
925
+ " >/dev/null 2>&1; then
926
+ pass "scenario 11: the post-move library honours a lease written by the pre-move library"
927
+ else
928
+ fail "scenario 11: the post-move library does NOT honour a pre-move lease — a mid-rollout \
929
+ worktree would be reaped by a sibling (new lib present: $([[ -f "$NEW_LIB" ]] && echo yes || echo NO))"
930
+ fi
931
+
932
+ # ---------------------------------------------------------------------------
933
+ # SCENARIO 12 (#7409): the one-time arming hold.
934
+ #
935
+ # This PR makes the reaper reachable for a population that never had it. Every
936
+ # worktree already on such a machine was created by a `create` that could not
937
+ # acquire a lease, so none of them can hold one — and cleanup-merged runs at
938
+ # session start. Without a hold, the first post-upgrade session sweeps the whole
939
+ # accumulated backlog at once, and the user's first notice of the feature is its
940
+ # aftermath.
941
+ #
942
+ # Both directions matter and they fail differently: a hold that never fires
943
+ # leaves the bulk reap live; a hold that never CLEARS makes cleanup dead
944
+ # forever, which is worse than not having it.
945
+ # ---------------------------------------------------------------------------
946
+ BARE12="$TMP/repo12.git"; git init --bare -b main "$BARE12" >/dev/null
947
+ S12="$TMP/s12"; git clone "$BARE12" "$S12" >/dev/null 2>&1
948
+ ( cd "$S12" && git -c user.email=t@t -c user.name=t commit --allow-empty -m seed >/dev/null \
949
+ && git push origin main >/dev/null 2>&1 )
950
+ rm -rf "$S12"
951
+ WT12="$TMP/wt12"; mkdir -p "$WT12/.worktrees"
952
+ git -C "$BARE12" worktree add -b feat-v12 "$WT12/.worktrees/feat-v12" main >/dev/null 2>&1
953
+ ( cdx "$WT12/.worktrees/feat-v12"
954
+ echo hi12 > d.txt
955
+ git -c user.email=t@t -c user.name=t add d.txt
956
+ GIT_COMMITTER_DATE="2025-01-01T00:00:00Z" \
957
+ git -c user.email=t@t -c user.name=t commit \
958
+ --date "2025-01-01T00:00:00Z" -m "v12 change" >/dev/null
959
+ )
960
+ git -C "$BARE12" update-ref refs/heads/main "$(git -C "$BARE12" rev-parse refs/heads/feat-v12)"
961
+ git -C "$BARE12" worktree add -b feat-a12 "$WT12/.worktrees/feat-a12" main >/dev/null 2>&1
962
+
963
+ # Deliberately NOT pre-stamped: this is the only scenario that exercises a
964
+ # genuinely first-armed store, which is the state every marketplace user is in
965
+ # the moment they update.
966
+ LEASE_ROOT12="$BARE12/soleur-session-state"
967
+ mkdir -p "$LEASE_ROOT12/leases"
968
+ if [[ ! -f "$LEASE_ROOT12/reaper-armed" ]]; then
969
+ pass "scenario 12 fixture: the store has never armed the reaper (no stamp)"
970
+ else
971
+ fail "scenario 12 fixture: stamp already present — cannot exercise the first armed run"
972
+ fi
973
+
974
+ if ( cd "$WT12/.worktrees/feat-a12" \
975
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT12" \
976
+ bash "$CACHE_WM" cleanup-merged >"$TMP/hold12.log" 2>&1 ); then
977
+ :
978
+ fi
979
+ # The victim is unleased and stale — without the hold it would be reaped here.
980
+ if [[ -d "$WT12/.worktrees/feat-v12" ]]; then
981
+ pass "scenario 12: the first armed run reaped nothing, even though the victim holds no lease"
982
+ else
983
+ fail "scenario 12: an UNLEASED stale worktree was reaped on the very first armed run — \
984
+ the bulk-reap-on-upgrade hold is not in effect (output: $(cat "$TMP/hold12.log" 2>/dev/null))"
985
+ fi
986
+ if grep -q 'SOLEUR_WORKTREE_REAPER_ARMED' "$TMP/hold12.log"; then
987
+ pass "scenario 12: the hold announces itself rather than silently doing nothing"
988
+ else
989
+ fail "scenario 12: the reaper armed with no marker — indistinguishable from a run \
990
+ that found nothing to do (output: $(cat "$TMP/hold12.log" 2>/dev/null))"
991
+ fi
992
+ if [[ -f "$LEASE_ROOT12/reaper-armed" ]]; then
993
+ pass "scenario 12: the run stamped the store"
994
+ else
995
+ fail "scenario 12: no stamp written — the hold would repeat forever and cleanup would \
996
+ never reap anything again"
997
+ fi
998
+
999
+ # SECOND run: the hold must be CLEARED. A hold that cannot clear is a worse
1000
+ # defect than the bulk reap it prevents, and nothing else in this suite would
1001
+ # notice — every other fixture is pre-stamped.
1002
+ if ( cd "$WT12/.worktrees/feat-a12" \
1003
+ && SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT12" \
1004
+ bash "$CACHE_WM" cleanup-merged >"$TMP/hold12b.log" 2>&1 ); then
1005
+ :
1006
+ fi
1007
+ if [[ -d "$WT12/.worktrees/feat-v12" ]]; then
1008
+ fail "scenario 12b: the victim survived the SECOND run too — the hold never clears, so \
1009
+ cleanup-merged is now permanently inert (output: $(cat "$TMP/hold12b.log" 2>/dev/null))"
1010
+ else
1011
+ pass "scenario 12b: the second run reaps — the hold is one-shot, not a permanent disable"
1012
+ fi
1013
+
1014
+ echo
1015
+ echo "=== Results ==="
1016
+ echo "PASS: $PASS"
1017
+ echo "FAIL: $FAIL"
1018
+
1019
+ # ANTI-VACUITY FLOOR — same reasoning as session-state.test.sh. `FAIL -eq 0` is
1020
+ # a pure zero-check, so a run that asserts NOTHING exits 0. That is not
1021
+ # hypothetical here: both scenarios assert worktree SURVIVAL, which is satisfied
1022
+ # whenever cleanup-merged does nothing for any reason at all — an early `return`
1023
+ # on the fetch-prune path, a non-zero sweep aborting under `set -e`, a lock it
1024
+ # could not take. A floor cannot detect a no-op reap loop by itself, but it does
1025
+ # catch the case where the assertions were never reached.
1026
+ MIN_ASSERTIONS=42 # 3 -> 6 -> 9 -> 15 -> 17 (PR #7373 sc. 3-7) -> 40 (#7409 sc. 8-12) -> 42 (#7453 sc. 10b)
1027
+ # Calibrated to the MEASURED count, not to a round number below it. At 32 against a
1028
+ # 34-dispatch suite the floor carried exactly two assertions of slack — and scenario 9
1029
+ # + 9b is exactly two dispatches, so deleting the reaper-refusal arm (the one guarding
1030
+ # an unrecoverable delete) left the suite green. A floor with slack is a floor sized
1031
+ # to the block it fails to notice.
1032
+ # Count DISPATCHES (PASS + FAIL), not wins. Counting PASS alone conflates two
1033
+ # different things: "the suite did not run" and "the suite ran and found bugs".
1034
+ # It printed "only N assertions ran — the suite did not execute what it claims
1035
+ # to cover" on every genuine regression, which is false and misdirects whoever
1036
+ # reads the failure. It also let a miswired `fail()` (one that increments PASS)
1037
+ # slip through whenever the defect happened to produce the expected dispatch
1038
+ # count. Matches scripts/lint-diagnosis-claims.test.sh and
1039
+ # scripts/lint-workflow-step-env-refs.test.sh, which both count PASS + FAIL.
1040
+ # `exit 90` inside `( cdx ... )` ends only the SUBSHELL, so without this marker the
1041
+ # suite would carry on against a fixture it never built and report whatever the
1042
+ # surviving assertions happened to say. Checked BEFORE the vacuity floor: a run that
1043
+ # refused is not a run with too few assertions, and must not be described as one.
1044
+ if [[ -f "$TMP/.cdx-failed" ]]; then
1045
+ echo "FAIL: at least one fixture directory was absent or outside the fixture root."
1046
+ echo " The suite did not run as designed; its verdicts are not evidence."
1047
+ exit 1
1048
+ fi
1049
+
1050
+ if [[ $(( PASS + FAIL )) -lt "$MIN_ASSERTIONS" ]]; then
1051
+ echo "FAIL: only $(( PASS + FAIL )) assertions ran, expected >= $MIN_ASSERTIONS — the suite did not execute what it claims to cover."
1052
+ exit 1
1053
+ fi
1054
+
1055
+ [[ "$FAIL" -eq 0 ]] || exit 1
1056
+ exit 0