@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,748 @@
1
+ #!/usr/bin/env bash
2
+ # Soleur session state: cross-session locks + leases + headless visibility.
3
+ #
4
+ # Designed for N parallel CC sessions (foreground or `claude --bg`). Pays the
5
+ # 2026-04-21 concurrency bill so worktree reaping, fetch races, and merge-main
6
+ # collisions become safe side-effects.
7
+ #
8
+ # Plan: knowledge-base/project/plans/2026-05-12-feat-bg-readiness-concurrency-hardening-plan.md
9
+ # Canonical flock idiom: the `flock -w 5` append block in agent-token-tee.sh
10
+ # (search that file for `Append under flock`). Cited by content, not by line
11
+ # number: the previous `:160-170` range pointed at the jq_fail block, and a
12
+ # repo-relative line citation is unresolvable from the plugin cache anyway —
13
+ # which is the class of defect this file's relocation exists to fix.
14
+ # Location: ships INSIDE the plugin (ADR-178, #7409) so a marketplace install
15
+ # resolves it; .claude/hooks/** consumers reach in from the repo side.
16
+
17
+ # Guard against double-source within a single shell.
18
+ if [[ "${_SOLEUR_SESSION_STATE_LOADED:-}" == "1" ]]; then
19
+ return 0 2>/dev/null || true
20
+ fi
21
+ _SOLEUR_SESSION_STATE_LOADED=1
22
+
23
+ # Kill switch (matches SOLEUR_DISABLE_* idiom). When set to 1, every function
24
+ # in this module short-circuits to a no-op so operators can disable the lock
25
+ # layer in emergencies without surgery.
26
+ _session_state_disabled() {
27
+ [[ "${SOLEUR_DISABLE_SESSION_STATE:-}" == "1" ]]
28
+ }
29
+
30
+ # Path resolution. Tests override via SOLEUR_SESSION_STATE_ROOT; production
31
+ # anchors to git-common-dir so all worktrees of one repo share state.
32
+ _session_state_root() {
33
+ if [[ -n "${SOLEUR_SESSION_STATE_ROOT:-}" ]]; then
34
+ printf '%s\n' "$SOLEUR_SESSION_STATE_ROOT"
35
+ return 0
36
+ fi
37
+ local common
38
+ common=$(git rev-parse --git-common-dir 2>/dev/null) || {
39
+ printf '/tmp/soleur-session-state-orphan\n'
40
+ return 0
41
+ }
42
+ # Canonicalize per existing idiom.
43
+ ( cd -P "$common" 2>/dev/null && printf '%s/soleur-session-state\n' "$(pwd -P)" )
44
+ }
45
+
46
+ # Initialize state dirs (idempotent). Called at module load and on demand.
47
+ _session_state_init_dirs() {
48
+ local root
49
+ root=$(_session_state_root)
50
+ LOCK_DIR="$root/locks"
51
+ LEASE_DIR="$root/leases"
52
+ LOG_DIR="$root/logs"
53
+ mkdir -p "$LOCK_DIR" "$LEASE_DIR" "$LOG_DIR" 2>/dev/null || true
54
+ }
55
+
56
+ # Hard-fail predicate. flock from util-linux is required. macOS polyfill
57
+ # deferred per plan §Risks #1.
58
+ _session_state_require_flock() {
59
+ if ! command -v flock >/dev/null 2>&1; then
60
+ echo "session-state: flock(1) not found. Soleur requires util-linux flock for cross-session locking." >&2
61
+ echo " macOS: brew install util-linux && add \$(brew --prefix util-linux)/sbin to PATH" >&2
62
+ return 99
63
+ fi
64
+ return 0
65
+ }
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # Locks
69
+ # ---------------------------------------------------------------------------
70
+ #
71
+ # Lock state is stashed in module-level associative-style vars. Because flock
72
+ # fd 9 must remain open for the lifetime of the critical section, we open it
73
+ # inline and stash the FD on a per-name basis so release_lock can close it.
74
+ # Bash does not let us programmatically open a numbered FD other than 9 in a
75
+ # portable way, so we serialize lock acquisitions in-process and hold fd 9
76
+ # for the duration. Callers that need nested locks open them in the natural
77
+ # (acquire-A, acquire-B, release-B, release-A) order.
78
+
79
+ # Per-name FD tracking. bash auto-assigns FDs via `exec {fd}>>file` which
80
+ # lets us hold multiple advisory locks (different files) at once. flock
81
+ # semantics are inode-bound, so cross-shell mutual exclusion is still
82
+ # correct against the same lock_file.
83
+ declare -gA _SESSION_LOCK_FDS 2>/dev/null || true
84
+ declare -gA _SESSION_LOCK_FILES 2>/dev/null || true
85
+ declare -gA _SESSION_LOCK_ACQUIRED 2>/dev/null || true
86
+ # Stash the started_at this shell wrote into each lease so `release_lease`
87
+ # can verify "I'm the recorded owner" before deleting. Closes the PID-reuse
88
+ # window where a new shell inheriting our pid would otherwise release.
89
+ declare -gA _LEASE_ACQUIRED_STARTED_AT 2>/dev/null || true
90
+
91
+ _acquire_lock_impl() {
92
+ local name="$1"
93
+ local timeout_s="${2:-30}"
94
+ local mode="${3:-x}" # x | s
95
+
96
+ _session_state_disabled && return 0
97
+
98
+ _session_state_require_flock || return 99
99
+ _session_state_init_dirs
100
+
101
+ # Idempotent: already held by this shell.
102
+ if [[ -n "${_SESSION_LOCK_FDS[$name]:-}" ]]; then
103
+ return 0
104
+ fi
105
+
106
+ # The name reaches two filesystem paths (.lock and .holder) — constrain it
107
+ # before either is formed (the charset also rules out `/` traversal).
108
+ [[ "$name" =~ ^[A-Za-z0-9._-]+$ ]] || return 99
109
+
110
+ local lock_file="$LOCK_DIR/$name.lock"
111
+ # A non-regular lock path (fifo, dir) blocks in open(2) BEFORE the bounded
112
+ # flock below is ever exec'd — the exec boundary cannot cover a wait that
113
+ # precedes it. A symlink is refused outright: dangling, `exec {fd}>>` would
114
+ # create the linked-to path; resolved, the named lock would alias a foreign
115
+ # inode — both plantable on the cross-user /tmp orphan-fallback LOCK_DIR.
116
+ [[ -L "$lock_file" ]] && return 99
117
+ [[ -e "$lock_file" && ! -f "$lock_file" ]] && return 99
118
+ local fd
119
+ exec {fd}>>"$lock_file" || return 99
120
+
121
+ local flag="-x"
122
+ [[ "$mode" == "s" ]] && flag="-s"
123
+
124
+ # #7697: bound the wait at the EXEC boundary, not only inside flock. flock's
125
+ # own -w expiry is delivered via SIGALRM; a caller that inherited that
126
+ # signal as BLOCKED (an outer process sigprocmask'd it before exec —
127
+ # measured: `flock -w 3` still waiting at 6 s, a live holder then kept the
128
+ # repo-wide lock for 4.6 days) defeats it silently. `timeout -k` wraps the
129
+ # flock call: its deadline does not depend on this process's signal mask,
130
+ # and the trailing SIGKILL needs no cooperation at all. The flock CHILD
131
+ # shares this shell's fd, and flock(2) attaches to the open file
132
+ # description — an acquisition lands on our fd even though flock ran in a
133
+ # child, and a timeout kill releases nothing we hold.
134
+ local _to_cmd="" _slack="${SOLEUR_LOCK_BOUND_SLACK_S:-60}"
135
+ [[ "$_slack" =~ ^[0-9]+$ ]] || _slack=60
136
+ # timeout_s reaches $(( )) arithmetic AND `flock -w` — a non-digit value is
137
+ # an arithmetic name under `set -u` (unbound abort) or a silent 0 bound;
138
+ # normalize like test-contention's tc_acquire does for its copy.
139
+ [[ "$timeout_s" =~ ^[0-9]+$ ]] || timeout_s=30
140
+ if command -v timeout >/dev/null 2>&1; then _to_cmd=timeout
141
+ elif command -v gtimeout >/dev/null 2>&1; then _to_cmd=gtimeout; fi
142
+
143
+ local _flock_rc=0
144
+ if [[ -n "$_to_cmd" ]]; then
145
+ # Outer bound = declared wait + slack; a normal -w expiry arrives first as
146
+ # flock's own rc=1, so rc=124 from timeout means the inner expiry never
147
+ # fired — the #7697 blocked-mask case. 137 is the same event one rung
148
+ # later: timeout had to deliver the -k SIGKILL.
149
+ "$_to_cmd" -k 5 "$(( timeout_s + _slack ))" flock -w "$timeout_s" "$flag" "$fd" || _flock_rc=$?
150
+ if [[ "$_flock_rc" == "124" || "$_flock_rc" == "137" ]]; then
151
+ echo "[contention] LOCK_BOUND_FORCED: '$name' — flock -w ${timeout_s}s never returned; the exec-boundary bound fired at ${timeout_s}s+${_slack}s. A blocked signal mask (SIGALRM) defeats flock's own expiry — proceeding contended." >&2
152
+ _flock_rc=1
153
+ fi
154
+ else
155
+ [[ -z "${_SESSION_LOCK_BOUND_NOTED:-}" ]] && {
156
+ _SESSION_LOCK_BOUND_NOTED=1
157
+ echo "[note] session-state: timeout(1) absent — lock waits bounded only by flock -w (a blocked SIGALRM mask can still defeat it; #7697)" >&2
158
+ }
159
+ flock -w "$timeout_s" "$flag" "$fd" || _flock_rc=$?
160
+ fi
161
+ if [[ "$_flock_rc" != "0" ]]; then
162
+ eval "exec ${fd}>&-" 2>/dev/null || true
163
+ return 99
164
+ fi
165
+
166
+ _SESSION_LOCK_FDS[$name]="$fd"
167
+ _SESSION_LOCK_FILES[$name]="$lock_file"
168
+ _SESSION_LOCK_ACQUIRED[$name]="${EPOCHSECONDS:-0}"
169
+
170
+ # Advisory holder marker (#7697): waiters' heartbeats read this to tell a
171
+ # progressing holder from a wedged one. The reader treats a torn or absent
172
+ # file as holder=unreadable/none, never an error. Written via mktemp+mv:
173
+ # a plain `>` follows a planted symlink — and on the /tmp orphan-fallback
174
+ # LOCK_DIR a predictable name is cross-user plantable — and is non-atomic.
175
+ # Shared (-s) holders have no single holder identity, so they mint none.
176
+ if [[ "$mode" != "s" ]]; then
177
+ local _hf_tmp
178
+ # lint-trap-ownership: ok sourced library — an EXIT trap would clobber the consumer's; every path removes _hf_tmp and the SIGKILL-window residual is one ~150B file
179
+ _hf_tmp=$(mktemp "$LOCK_DIR/.holder.XXXXXXXX" 2>/dev/null) && {
180
+ {
181
+ printf 'pid=%s\n' "$$"
182
+ printf 'ppid=%s\n' "$PPID"
183
+ printf 'acquired_epoch=%s\n' "${_SESSION_LOCK_ACQUIRED[$name]}"
184
+ printf 'progress_epoch=%s\n' "${_SESSION_LOCK_ACQUIRED[$name]}"
185
+ } > "$_hf_tmp" 2>/dev/null \
186
+ && mv -f "$_hf_tmp" "$LOCK_DIR/$name.holder" 2>/dev/null
187
+ } || true
188
+ [[ -n "${_hf_tmp:-}" && -e "$_hf_tmp" ]] && rm -f "$_hf_tmp" 2>/dev/null || true
189
+ fi
190
+ return 0
191
+ }
192
+
193
+ acquire_lock() {
194
+ _acquire_lock_impl "$1" "${2:-30}" "x"
195
+ }
196
+
197
+ acquire_lock_shared() {
198
+ _acquire_lock_impl "$1" "${2:-30}" "s"
199
+ }
200
+
201
+ release_lock() {
202
+ local name="$1"
203
+ _session_state_disabled && return 0
204
+ local fd="${_SESSION_LOCK_FDS[$name]:-}"
205
+ [[ -z "$fd" ]] && return 0
206
+ local lock_file="${_SESSION_LOCK_FILES[$name]:-}"
207
+ # The advisory holder marker (#7697) dies BEFORE our hold does: removing
208
+ # it while the fd is still open is provably removing OUR marker — after
209
+ # the close, a queued successor could already have minted its own and we
210
+ # would unlink that instead, leaving its hold markerless until release.
211
+ [[ -n "$lock_file" ]] && rm -f "${lock_file%.lock}.holder" 2>/dev/null || true
212
+ eval "exec ${fd}>&-" 2>/dev/null || true
213
+ unset "_SESSION_LOCK_FDS[$name]"
214
+ unset "_SESSION_LOCK_ACQUIRED[$name]"
215
+ unset "_SESSION_LOCK_FILES[$name]"
216
+ return 0
217
+ }
218
+
219
+ # stamp_lock_holder <name> [key=value ...] — refresh the holder marker's
220
+ # progress_epoch and set progress fields, in place. Call sites are the
221
+ # consumers' own progress points (test-all stamps once per suite boundary).
222
+ # NEVER mints: a marker is born at acquire and dies at release — stamping a
223
+ # lock this shell does not hold would fabricate holder identity, so it is a
224
+ # silent no-op. All failures are silent too: the marker is advisory.
225
+ stamp_lock_holder() {
226
+ # Guard BEFORE shift: a zero-arg call must be a silent no-op (the marker is
227
+ # advisory), and a bare `shift` with no args aborts a `set -e` caller.
228
+ [[ -n "${1:-}" ]] || return 0
229
+ local name="$1"
230
+ shift
231
+ _session_state_disabled && return 0
232
+ [[ -n "${_SESSION_LOCK_FDS[$name]:-}" ]] || return 0
233
+ [[ "$name" =~ ^[A-Za-z0-9._-]+$ ]] || return 0
234
+ local hf="$LOCK_DIR/$name.holder"
235
+ # Regular-file + not-a-link: a planted symlink must not be stamped through
236
+ # (the mint is mktemp+mv for the same reason), and anything that is not a
237
+ # marker we minted is left alone.
238
+ [[ -f "$hf" && ! -L "$hf" ]] || return 0
239
+ local _hf_tmp
240
+ # lint-trap-ownership: ok sourced library — same as the mint above; every path removes _hf_tmp, SIGKILL-window residual is a single file
241
+ _hf_tmp=$(mktemp "$LOCK_DIR/.holder.XXXXXXXX" 2>/dev/null) || return 0
242
+ {
243
+ printf 'pid=%s\n' "$$"
244
+ printf 'ppid=%s\n' "$PPID"
245
+ printf 'acquired_epoch=%s\n' "${_SESSION_LOCK_ACQUIRED[$name]:-}"
246
+ printf 'progress_epoch=%s\n' "${EPOCHSECONDS:-0}"
247
+ local kv
248
+ for kv in "$@"; do printf '%s\n' "${kv//$'\n'/ }"; done
249
+ } > "$_hf_tmp" 2>/dev/null && mv -f "$_hf_tmp" "$hf" 2>/dev/null || true
250
+ [[ -e "$_hf_tmp" ]] && rm -f "$_hf_tmp" 2>/dev/null || true
251
+ return 0
252
+ }
253
+
254
+ # with_lock <name> <timeout_s> -- <command> [args...]
255
+ #
256
+ # CLI-friendly wrapper for SKILL.md callers. Each `bash session-state.sh
257
+ # acquire_lock` invocation is a separate process whose fd 9 closes on exit,
258
+ # so the standalone acquire/release pattern doesn't serialize anything when
259
+ # invoked separately. This wrapper acquires the lock and runs the command
260
+ # in the same shell so the lock outlives the critical section.
261
+ #
262
+ # Returns the command's exit code on success, 99 on lock-acquire timeout.
263
+ with_lock() {
264
+ local name="$1"; shift
265
+ local timeout_s="$1"; shift
266
+ if [[ "$1" == "--" ]]; then shift; fi
267
+ if ! acquire_lock "$name" "$timeout_s"; then
268
+ headless_or_stderr warn "lock '$name' contended after ${timeout_s}s; aborting"
269
+ return 99
270
+ fi
271
+ local rc=0
272
+ "$@" || rc=$?
273
+ release_lock "$name"
274
+ return "$rc"
275
+ }
276
+
277
+ # ---------------------------------------------------------------------------
278
+ # Leases (durable, key=value, atomic-write)
279
+ # ---------------------------------------------------------------------------
280
+
281
+ # Validate that a worktree name is safe for filesystem-path interpolation.
282
+ # Rejects `..`, slashes, and any character outside [A-Za-z0-9._-]. Required
283
+ # because the CLI shim at the bottom of this file accepts arbitrary worktree
284
+ # names from operator/agent input; in-shell callers pass `$(basename "$PWD")`
285
+ # which is also safe but worth defense-in-depth.
286
+ _validate_worktree_name() {
287
+ local n="$1"
288
+ [[ "$n" =~ ^[A-Za-z0-9._-]+$ ]] || return 1
289
+ [[ "$n" == "." || "$n" == ".." ]] && return 1
290
+ return 0
291
+ }
292
+
293
+ _lease_file() {
294
+ _session_state_init_dirs
295
+ if ! _validate_worktree_name "$1"; then
296
+ headless_or_stderr warn "invalid worktree name for lease: $1"
297
+ return 1
298
+ fi
299
+ printf '%s/%s.lease\n' "$LEASE_DIR" "$1"
300
+ }
301
+
302
+ _lease_read_field() {
303
+ local lease_file="$1"
304
+ local key="$2"
305
+ [[ -f "$lease_file" ]] || return 1
306
+ # Simple key=value extractor. Values do not contain `=`.
307
+ grep "^${key}=" "$lease_file" 2>/dev/null | head -1 | cut -d= -f2-
308
+ }
309
+
310
+ # The parameter is named `worktree`, and since #8400 that name is narrower than the KEY
311
+ # SPACE. `cleanup_merged_worktrees` consults `is_lease_active` twice per candidate: once on
312
+ # `_safe_worktree_name "$branch"` and once on `basename "$worktree_path"`. Those coincide for
313
+ # the standard producer, but the branch-keyed probe is the one that runs for a MERGED BRANCH
314
+ # WITH NO WORKTREE — the cohort every worktree-path-gated guard used to skip. So a key here is
315
+ # an opaque reservation name that a caller chooses, not an assertion that a directory exists;
316
+ # `_validate_worktree_name` is what bounds it, and nothing in this file should start deriving
317
+ # it from the filesystem.
318
+ acquire_lease() {
319
+ local worktree="$1"
320
+ local skill="${2:-unknown}"
321
+ local expected_duration_min="${3:-240}"
322
+
323
+ _session_state_disabled && return 0
324
+ _session_state_init_dirs
325
+
326
+ local lease_file
327
+ lease_file=$(_lease_file "$worktree") || return 1
328
+ # Sanitize skill/duration on write so `_lease_read_field` (grep+cut) sees
329
+ # safe values. `=` and newline would corrupt the key=value parser; today
330
+ # only `=` is plausible (operator-supplied SOLEUR_SKILL_NAME), but the
331
+ # guard is cheap defense-in-depth and matches the documented invariant.
332
+ skill=$(printf '%s' "$skill" | tr -d '=\n')
333
+ if ! [[ "$expected_duration_min" =~ ^[0-9]+$ ]]; then
334
+ expected_duration_min=240
335
+ fi
336
+
337
+ local tmp
338
+ # Surfaced by #7409, PRE-EXISTING and deliberately left as-is. Moving this file
339
+ # made it a "new entrant" for lint-trap-tempfile-ownership rule (c), which is
340
+ # scoped to files changed vs the merge base — the pre-existing population is
341
+ # fenced by the high-water instead. So the finding is new; the code is not.
342
+ #
343
+ # The remedy rule (c) proposes — "add a single owning trap ... EXIT" — is
344
+ # ACTIVELY HARMFUL in this file specifically, for the reason recorded at
345
+ # `_register_lease_release_trap` below: EXIT is deliberately excluded there
346
+ # because `create_worktree` acquires the lease and registers its trap IN THE
347
+ # SAME shell that then exits normally on success, so an armed EXIT trap fired
348
+ # on that success and released the lease immediately. That is what made the
349
+ # lease layer unreachable in production. Arming one here to satisfy a linter
350
+ # would re-introduce it.
351
+ #
352
+ # The residual, stated honestly rather than waved away: `sweep_orphan_leases`
353
+ # globs `*.lease`, so a leaked `*.lease.XXXXXX` does NOT match it and nothing
354
+ # else reaps it. A SIGKILL (or power loss) in the window between this mktemp
355
+ # and the `mv` below — one `date` subshell and one heredoc write — therefore
356
+ # strands a single ~150-byte file in the repo's own scratch dir, permanently.
357
+ # Bounded and non-destructive, but not zero.
358
+ #
359
+ # An earlier draft of this comment said INT/TERM/HUP were "already covered by
360
+ # the multi-signal trap; only SIGKILL reaches this window." Both halves were
361
+ # false. `_register_lease_release_trap` is invoked by the CALLER, after this
362
+ # function has already returned, and only in trap mode — so during the window
363
+ # below no trap is armed at all. And even when one is, its body releases
364
+ # `<name>.lease`; it has never known anything about `<name>.lease.XXXXXX`. So
365
+ # ANY signal strands the file, and Ctrl-C during `create` is a routine
366
+ # operator action, which makes the accumulation rate higher than "power loss"
367
+ # implies. Still bounded, still non-destructive; just not as rare as claimed.
368
+ # lint-trap-ownership: ok an EXIT trap is documented-harmful here (see _register_lease_release_trap) — it would re-arm the release-on-success bug; residual is one ~150B stranded file per SIGKILL in a two-statement window
369
+ tmp=$(mktemp "${lease_file}.XXXXXX") || return 1
370
+
371
+ local started_at
372
+ started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
373
+
374
+ cat > "$tmp" <<EOF
375
+ pid=$$
376
+ ppid=$PPID
377
+ skill=$skill
378
+ started_at=$started_at
379
+ expected_duration_min=$expected_duration_min
380
+ hostname=$HOSTNAME
381
+ EOF
382
+ # Atomic rename on same filesystem.
383
+ #
384
+ # CHECKED, and that is load-bearing. Every caller is the left side of an
385
+ # `acquire_lease ... || warn` list, which suspends errexit for the whole call,
386
+ # so an unchecked `mv` here meant ENOSPC / EROFS / EACCES returned SUCCESS
387
+ # with no lease on disk: the caller believed it was protected, the `|| warn`
388
+ # arm never fired, and cleanup-merged later found no file and reaped. That is
389
+ # a fail-open on an irreversible path (worktree + local branch + remote branch
390
+ # + the PR), reached without anything anywhere reporting a problem.
391
+ #
392
+ # Clean up the temp on failure too — sweep_orphan_leases globs `*.lease` and
393
+ # would never collect a stranded `*.lease.XXXXXX`.
394
+ if ! mv "$tmp" "$lease_file"; then
395
+ rm -f "$tmp"
396
+ return 1
397
+ fi
398
+ _LEASE_ACQUIRED_STARTED_AT[$worktree]="$started_at"
399
+ return 0
400
+ }
401
+
402
+ release_lease() {
403
+ local worktree="$1"
404
+ _session_state_disabled && return 0
405
+ local lease_file
406
+ lease_file=$(_lease_file "$worktree") || return 0
407
+ [[ -f "$lease_file" ]] || return 0
408
+
409
+ # Flohr rule: only release the lease if THIS shell is the recorded owner.
410
+ # Compares pid + hostname + started_at — the started_at field protects
411
+ # against PID-reuse after a crash (new shell inherits the dead shell's
412
+ # numeric pid; without started_at it would release someone else's lease).
413
+ local lease_pid lease_host lease_started
414
+ lease_pid=$(_lease_read_field "$lease_file" pid)
415
+ lease_host=$(_lease_read_field "$lease_file" hostname)
416
+ lease_started=$(_lease_read_field "$lease_file" started_at)
417
+ # Same host is a hard requirement: a shared LEASE_DIR can carry other machines'
418
+ # leases and this process cannot reason about their liveness at all.
419
+ [[ "$lease_host" == "$HOSTNAME" ]] || return 0
420
+ [[ -n "$lease_started" ]] || return 0
421
+
422
+ # TWO owners can release, and the second one is why this function used to do
423
+ # NOTHING. Measured before this change: the DOCUMENTED call —
424
+ # bash <plugin-root>/scripts/lib/session-state.sh release_lease "$(basename "$PWD")"
425
+ # which both one-shot and work tell you to run at the end — returned rc=0 and
426
+ # deleted no file, because a fresh process has a different `$$` and an empty
427
+ # `_LEASE_ACQUIRED_STARTED_AT`, so the in-process conjunction could never
428
+ # match. It reported success while leaking the lease. That is not a stricter
429
+ # guard; it is an unreachable one.
430
+ #
431
+ # in-process — pid == $$ AND the started_at we recorded at acquire time.
432
+ # This is the trap path, and started_at still guards PID reuse.
433
+ # post-exit — the recorded pid is DEAD. The session that held it is gone,
434
+ # so releasing is correct and is what the CLI call needs.
435
+ #
436
+ # A lease held by a DIFFERENT, still-LIVE process on this host matches neither
437
+ # arm and is refused — which is the property the original guard was reaching
438
+ # for and is the only one worth keeping.
439
+ local owner=""
440
+ if [[ "$lease_pid" == "$$" ]] \
441
+ && [[ -n "${_LEASE_ACQUIRED_STARTED_AT[$worktree]:-}" ]] \
442
+ && [[ "$lease_started" == "${_LEASE_ACQUIRED_STARTED_AT[$worktree]}" ]]; then
443
+ owner="in-process"
444
+ elif [[ -n "$lease_pid" ]] && ! kill -0 "$lease_pid" 2>/dev/null; then
445
+ owner="post-exit"
446
+ fi
447
+
448
+ if [[ -n "$owner" ]]; then
449
+ rm -f "$lease_file"
450
+ unset "_LEASE_ACQUIRED_STARTED_AT[$worktree]"
451
+ fi
452
+ return 0
453
+ }
454
+
455
+ # Returns 0 (active) iff hostname matches and age is inside the window computed
456
+ # by _lease_window_seconds (declared duration, clamped to [4h, 24h]). PID
457
+ # liveness is deliberately NOT required — see the note in is_lease_active.
458
+ # How long a lease is honoured, in seconds, from a raw expected_duration_min
459
+ # field. ONE definition, because is_lease_active and sweep_orphan_leases must
460
+ # agree exactly — and when this logic was duplicated they did not:
461
+ #
462
+ # * FLOOR 4h — a short declared duration should not make a lease reapable
463
+ # while the session is plainly still working.
464
+ # * CEILING 24h — the field is operator-supplied via SOLEUR_EXPECTED_DURATION_MIN
465
+ # and validated only as "digits". Unbounded, a typo (240000 for
466
+ # 240) silently disables cleanup-merged for MONTHS: the lease
467
+ # reads ACTIVE forever and, in the cleanup path, nothing else
468
+ # reaps it. Pinning the ceiling to the same 24h as the mtime
469
+ # sweep is what makes "the 24h sweep is the backstop" TRUE
470
+ # rather than merely claimed. Measured before the clamp: a
471
+ # 20-day-old lease with expected_duration_min=525600 read ACTIVE.
472
+ # * NON-NUMERIC -> 240 (the documented default), never bare arithmetic. Under
473
+ # `set -u`, `$(( abc * 60 ))` treats `abc` as an unset variable
474
+ # name and ERRORS; is_lease_active is called from inside an `if`,
475
+ # so `set -e` is suspended and that error was read as "no active
476
+ # lease" — i.e. a fresh lease with one corrupt field became
477
+ # REAPABLE. Fail closed on corrupt input, never open.
478
+ _lease_window_seconds() { # $1 raw expected_duration_min
479
+ local mins="${1:-}"
480
+ [[ "$mins" =~ ^[0-9]+$ ]] || mins=240
481
+ # `10#` forces base 10. WITHOUT it this fails OPEN, which is the whole class
482
+ # this function exists to close: `^[0-9]+$` ACCEPTS a leading zero, bash reads
483
+ # a leading-zero token as OCTAL, and 8/9 are not octal digits — so `08`/`09`/
484
+ # `090` pass validation and then ERROR in the arithmetic. `cap` is never
485
+ # assigned, the function prints nothing, and both callers read an empty window:
486
+ # `(( age < "" ))` is false (INACTIVE -> reapable) and `(( age >= "" ))` is
487
+ # true (the sweep deletes it). Measured on a 0-second-old lease: INACTIVE and
488
+ # SWEPT in the same pass — #5454's exact signature through a different door.
489
+ # `0700` does not error and is worse for being quiet: octal 448 min, not 700.
490
+ # T12 missed this because `abc` is REJECTED by the regex; the escaping input
491
+ # is one the regex ACCEPTS. Pinned by T12's `090` case.
492
+ local cap=$(( 10#$mins * 60 ))
493
+ (( cap < 14400 )) && cap=14400
494
+ (( cap > 86400 )) && cap=86400
495
+ printf '%s\n' "$cap"
496
+ }
497
+
498
+ is_lease_active() {
499
+ local worktree="$1"
500
+ _session_state_disabled && return 1
501
+ local lease_file
502
+ lease_file=$(_lease_file "$worktree")
503
+ [[ -f "$lease_file" ]] || return 1
504
+
505
+ local lease_pid lease_host lease_started lease_expected
506
+ lease_pid=$(_lease_read_field "$lease_file" pid)
507
+ lease_host=$(_lease_read_field "$lease_file" hostname)
508
+ lease_started=$(_lease_read_field "$lease_file" started_at)
509
+ lease_expected=$(_lease_read_field "$lease_file" expected_duration_min)
510
+
511
+ [[ -n "$lease_pid" ]] || return 1
512
+ [[ "$lease_host" == "$HOSTNAME" ]] || return 1
513
+
514
+ # NO `kill -0 "$lease_pid" || return 1` HERE — that line reaped two live
515
+ # worktrees on 2026-08-06 (#5454), and it could never have worked.
516
+ #
517
+ # `acquire_lease` records `pid=$$`, and every DOCUMENTED entry point is a
518
+ # short-lived process:
519
+ #
520
+ # bash <plugin-root>/scripts/lib/session-state.sh acquire_lease <worktree>
521
+ # bash .../worktree-manager.sh --yes create <branch>
522
+ #
523
+ # so `$$` is a bash that exits within milliseconds of writing the file.
524
+ # A pid-liveness gate therefore reported INACTIVE for every CLI-acquired
525
+ # lease the instant it was taken — the file existed, carried its full
526
+ # remaining duration, and protected nothing. cleanup-merged then removed
527
+ # the worktree, deleted the branch locally AND on origin, and closed the PR.
528
+ #
529
+ # A lease is a TIME-BOXED RESERVATION; the age cap below is the authority.
530
+ # PID liveness is only an optimisation for releasing early, and a session
531
+ # that finishes cleanly calls `release_lease` (which still verifies pid +
532
+ # hostname + started_at before releasing). The cost of this change is that
533
+ # a CRASHED session holds its worktree until the window closes, bounded by
534
+ # max(expected_duration, 4h) here and by the 24h mtime sweep below. That is
535
+ # the correct direction to fail: a worktree kept slightly too long is
536
+ # recoverable, a worktree reaped mid-run is not.
537
+ #
538
+ # The bound is the window computed by _lease_window_seconds, which is CLAMPED
539
+ # to 24h. Do not restate it as "and the 24h mtime sweep backstops this" without
540
+ # checking: an earlier draft of this comment said exactly that and it was FALSE
541
+ # in the path that matters — sweep_orphan_leases was reachable only from the
542
+ # `feature|feat` subcommand (create_for_feature), never from `create` and
543
+ # never from cleanup_merged_worktrees. (This sentence said "the `create`
544
+ # subcommand" until PR #7373; it was wrong in the same direction as the
545
+ # _register_lease_release_trap comment below — both assumed `create` did
546
+ # lease-work it did not do. As of PR #7373 `create` does sweep and acquire, so
547
+ # the corrected statement describes the pre-#5454 world.) That is now also
548
+ # wired (worktree-manager.sh calls the sweep at the top of cleanup), but the
549
+ # clamp is what makes the bound hold regardless.
550
+ #
551
+ # Pinned by T9/T9b (a dead acquirer stays active + unswept inside the
552
+ # window) and T10 (an expired lease is still inactive, so this is not an
553
+ # immortality bug) in session-state.test.sh.
554
+
555
+ # Age cap: see _lease_window_seconds. Computed there, not inline, because
556
+ # sweep_orphan_leases needs the IDENTICAL number and two copies already
557
+ # drifted once (one validated the field, one did not — the unvalidated copy
558
+ # fail-OPENED under `set -u`, which on this path means "reap it").
559
+ local cap
560
+ cap=$(_lease_window_seconds "$lease_expected")
561
+
562
+ # Anchor started_at to the strict ISO-8601-Z format we always write
563
+ # ourselves (date -u +%Y-%m-%dT%H:%M:%SZ). A malformed or natural-language
564
+ # value (e.g., "next year") fed to `date -d` would otherwise resolve to a
565
+ # future epoch — combined with the clock-skew "fresh" branch below that
566
+ # turns into a forever-active lease and a permanent reap block.
567
+ if [[ ! "$lease_started" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]]; then
568
+ # Malformed or missing — treat as inactive; let sweep_orphan_leases
569
+ # reap by mtime instead of forever-blocking cleanup-merged.
570
+ return 1
571
+ fi
572
+ local started_epoch now_epoch age
573
+ started_epoch=$(date -d "$lease_started" +%s 2>/dev/null || echo "")
574
+ now_epoch=$(date +%s)
575
+ if [[ -z "$started_epoch" ]]; then
576
+ return 1
577
+ fi
578
+ age=$(( now_epoch - started_epoch ))
579
+ # Negative age = clock skew or future-stamped lease. Treat as inactive
580
+ # (not forever-fresh) to avoid the DoS vector.
581
+ if (( age < 0 )); then
582
+ return 1
583
+ fi
584
+ (( age < cap )) && return 0
585
+ return 1
586
+ }
587
+
588
+ # Sweep orphan leases. Deletes on: mtime > 24h, OR (pid dead AND same host AND
589
+ # past its own window). The pid-dead arm is deliberately NOT sufficient on its
590
+ # own — see is_lease_active for why a dead pid is not orphan evidence here.
591
+ sweep_orphan_leases() {
592
+ _session_state_disabled && return 0
593
+ _session_state_init_dirs
594
+
595
+ local now_epoch
596
+ now_epoch=$(date +%s)
597
+ local f mtime age lease_pid
598
+ shopt -s nullglob
599
+ for f in "$LEASE_DIR"/*.lease; do
600
+ mtime=$(stat -c %Y "$f" 2>/dev/null || echo 0)
601
+ age=$(( now_epoch - mtime ))
602
+ if (( age > 86400 )); then
603
+ rm -f "$f"
604
+ continue
605
+ fi
606
+ lease_pid=$(_lease_read_field "$f" pid) || true
607
+ if [[ -n "$lease_pid" ]] && ! kill -0 "$lease_pid" 2>/dev/null; then
608
+ # Dead PID — but only sweep if same hostname (don't reap remote-host
609
+ # leases that share the LEASE_DIR via shared filesystem).
610
+ local lease_host
611
+ lease_host=$(_lease_read_field "$f" hostname) || true
612
+ if [[ "$lease_host" == "$HOSTNAME" ]]; then
613
+ # ...AND only once the lease is past its own window. A dead pid alone
614
+ # is NOT orphan evidence: `acquire_lease` records `pid=$$`, which for
615
+ # every documented CLI entry point belongs to a process that exits
616
+ # immediately (see the long note in is_lease_active). Sweeping on a
617
+ # dead pid alone deleted the lease file BEFORE worktree-manager
618
+ # consulted is_lease_active — so the downstream check found no file
619
+ # and the reap proceeded. That is #5454, twice, on 2026-08-06.
620
+ #
621
+ # is_lease_active is the single source of truth for "still held";
622
+ # the 24h mtime cap above remains the backstop for a truly abandoned
623
+ # file. Pinned by T9b.
624
+ # `|| true` on every field read: `_lease_read_field` returns 1 for a
625
+ # missing field AND for a file that has vanished since the glob, and a
626
+ # sibling releasing its lease mid-sweep is an ordinary race, not an
627
+ # error. Bare assignments here abort the caller under `set -e` — and the
628
+ # caller is cleanup-merged, so the blast radius is every maintenance
629
+ # step after it. An empty value is handled below; a dead caller is not.
630
+ local lease_started lease_expected lease_cap started_epoch lease_age
631
+ lease_started=$(_lease_read_field "$f" started_at) || true
632
+ lease_expected=$(_lease_read_field "$f" expected_duration_min) || true
633
+ # Same window as is_lease_active, from the same function — these two
634
+ # MUST agree, and when the arithmetic was duplicated here they did not.
635
+ lease_cap=$(_lease_window_seconds "$lease_expected")
636
+ started_epoch=""
637
+ if [[ "$lease_started" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]]; then
638
+ started_epoch=$(date -d "$lease_started" +%s 2>/dev/null || echo "")
639
+ fi
640
+ if [[ -z "$started_epoch" ]]; then
641
+ # Unparseable started_at: fall back to mtime, which the 24h cap
642
+ # above already governs. Leave it for that cap rather than guessing.
643
+ continue
644
+ fi
645
+ lease_age=$(( now_epoch - started_epoch ))
646
+ if (( lease_age >= lease_cap )); then
647
+ rm -f "$f"
648
+ fi
649
+ fi
650
+ fi
651
+ done
652
+ shopt -u nullglob
653
+ return 0
654
+ }
655
+
656
+ # Multi-signal trap helper. Body is unset-variable safe via local set +u.
657
+ _register_lease_release_trap() {
658
+ local worktree="$1"
659
+ # NOT `EXIT` — that is what made the lease layer unreachable in production.
660
+ #
661
+ # `create_worktree` acquires the lease and registers this trap IN THE SAME
662
+ # PROCESS (as of PR #7373 — before that it did NEITHER; only `create_for_feature`
663
+ # acquired, and `create` is what the pipeline invokes), and that process exits
664
+ # normally on success. With EXIT armed, the trap fired on that success and
665
+ # deleted the lease before the caller had even printed its path.
666
+ #
667
+ # Measured: the leases directory is EMPTY the instant
668
+ # `worktree-manager.sh --yes create` returns. That measurement was real, but
669
+ # it had TWO independent causes and this comment originally named only one:
670
+ #
671
+ # 1. EXIT released whatever was acquired (fixed by the signal list below).
672
+ # 2. `create` never acquired anything in the first place (fixed separately
673
+ # by adding this block to `create_worktree`).
674
+ #
675
+ # Fixing the trap alone left the pipeline path dark — the empty directory
676
+ # looked identical under both causes, which is exactly how cause 2 survived
677
+ # the fix for cause 1. So a worktree made the DOCUMENTED way (what one-shot
678
+ # and work both invoke) carried no lease at all, and `is_lease_active`
679
+ # returned at its `[[ -f "$lease_file" ]]` guard — every protection below that
680
+ # line was dead code for the path that matters.
681
+ #
682
+ # Normal process exit is not session end. That is this change's whole thesis,
683
+ # applied to the write side: the CLI process is short-lived BY DESIGN, and the
684
+ # session it acquired for outlives it by hours.
685
+ #
686
+ # The abnormal signals stay: those do mean the holder died, and releasing then
687
+ # is correct. Clean release is the caller's explicit `release_lease` (now
688
+ # reachable post-exit — see the owner arms there), plus the window as backstop.
689
+ # shellcheck disable=SC2064
690
+ trap "_lease_release_safe '$worktree'" INT TERM HUP
691
+ }
692
+
693
+ _lease_release_safe() {
694
+ # Unset-var safe — traps fire in odd scopes.
695
+ set +u
696
+ local worktree="${1:-}"
697
+ [[ -n "$worktree" ]] || return 0
698
+ release_lease "$worktree" 2>/dev/null || true
699
+ }
700
+
701
+ # ---------------------------------------------------------------------------
702
+ # Headless visibility helper (sub-PR 2 will reuse this).
703
+ # ---------------------------------------------------------------------------
704
+ # If fd 2 is not a TTY AND CLAUDECODE is set (we are running under `claude --bg`
705
+ # or another headless harness), append a single timestamped line to
706
+ # $LOG_DIR/$PPID.log. Otherwise echo to stderr. POSIX-atomic append for lines
707
+ # < 4KB.
708
+ headless_or_stderr() {
709
+ local level="${1:-warn}"
710
+ local msg="${2:-}"
711
+ _session_state_disabled && { echo "[$level] $msg" >&2; return 0; }
712
+
713
+ local hook="${SOLEUR_HOOK_NAME:-$(basename "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}" .sh)}"
714
+
715
+ if [[ ! -t 2 ]] && [[ -n "${CLAUDECODE:-}" ]]; then
716
+ _session_state_init_dirs
717
+ local ts
718
+ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
719
+ printf '[%s] [%s] [%s] %s\n' "$ts" "$level" "$hook" "$msg" >> "$LOG_DIR/${PPID}.log"
720
+ else
721
+ echo "[$level] $msg" >&2
722
+ fi
723
+ return 0
724
+ }
725
+
726
+ # Initialize state dirs once at source-time so consumers can rely on LOG_DIR
727
+ # and friends being readable.
728
+ _session_state_init_dirs
729
+
730
+ # Allow `bash session-state.sh <fn> <args>` as a CLI shim (used by SKILL.md
731
+ # Phase Exit hooks and ad-hoc operator invocations).
732
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
733
+ fn="${1:-}"
734
+ shift || true
735
+ case "$fn" in
736
+ acquire_lock|release_lock|acquire_lock_shared|acquire_lease|release_lease|is_lease_active|sweep_orphan_leases|headless_or_stderr|with_lock)
737
+ "$fn" "$@"
738
+ ;;
739
+ "")
740
+ echo "usage: $0 <function> [args...]" >&2
741
+ exit 2
742
+ ;;
743
+ *)
744
+ echo "session-state: unknown function: $fn" >&2
745
+ exit 2
746
+ ;;
747
+ esac
748
+ fi