@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,818 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Soleur's OpenCode plugin — the OpenCode peer of `hooks/hooks.json`.
4
+ *
5
+ * LOAD SHAPE (measured — measurement §6.6 arm A). OpenCode 2.0.32 requires a **default**
6
+ * export carrying an `id` and a `setup` function. The V1 named-export form
7
+ * (`export const Soleur = async (ctx) => ({ hooks })`) is rejected outright:
8
+ * `Plugin must export a default definition with an id and an effect or setup function.`
9
+ * A plain object importing only `node:` builtins is enough to load (arms B/C), so this
10
+ * module deliberately imports nothing from `@opencode/plugin` and carries no runtime
11
+ * dependency. That is also why the pin task resolves to "nothing to pin": there is no vendor
12
+ * package on the module's import graph (measurement §6.6, "Package identity").
13
+ *
14
+ * WHY THIS FILE IS NOT GENERATED. It holds dispatch logic that has to be unit-tested by
15
+ * import, and a generator-owned template string cannot be imported. The generator therefore
16
+ * treats it as a **source file** (`ADAPTER_SOURCE_FILES` in `scripts/sync-opencode-adapter.ts`):
17
+ * it asserts the file exists, never emits it, and never deletes it. The installer copies it
18
+ * into `<configdir>/plugins/` with the `__SOLEUR_PLUGIN_ROOT__` token resolved.
19
+ *
20
+ * WHERE THE GUARDS BIND, AND WHY NOT SOMEWHERE ELSE (measurement §12). The Bash guards bind at
21
+ * `ctx.permission.hook("evaluate")`, whose event carries the tool as `action` and the command
22
+ * as `resources[0]`, with a **mutable** `effect: allow | ask | deny`. Measured live:
23
+ *
24
+ * - `effect = "deny"` + `message` ⇒ the tool ends `{status:"error", error:{type:
25
+ * "permission.rejected", message}}`, it never runs, and the model reads the message.
26
+ * - `effect = "ask"` + `message` ⇒ the tool stays `running` awaiting a decision. A real
27
+ * prompt, so a guard's `ask` is delivered as an `ask` and NOT downgraded to a refusal.
28
+ *
29
+ * `ctx.tool.hook("execute.before")` is bound **as a capture, never as a guard** (#10819). It fires
30
+ * *before* the permission hook, and its only refusal channel is a thrown error, so a `deny`-or-`ask`
31
+ * there would collapse into a throw and preempt the real ask behind it. What it *can* do without
32
+ * deciding anything is publish `input.workdir`, the one value the permission event does not carry:
33
+ * a `shell` call with `workdir=$HOME` was judged against the session directory, so `rm -rf *` was
34
+ * allowed that the destructive guard **denies** in the directory the command asked for. The
35
+ * permission hook consumes that capture by call key and passes it as the envelope `cwd`.
36
+ *
37
+ * INVARIANTS. (1) One predicate, `decidesBash`, answers "is this a call the Bash guards see" — and
38
+ * the envelope, the capture and the consume all ask it; a second copy of that question is how a
39
+ * wire-name change reopens the hole at one site only. (2) The capture runs for every Bash call,
40
+ * including one with no workdir, so a lookup miss means "the tool hook did not fire" rather than
41
+ * "no workdir". (3) A capture is never deleted on consume, so a second evaluation of the same call
42
+ * cannot fall back to the session directory. (4) A degraded correlation is announced; a captured
43
+ * workdir that cannot be used escalates to `ask` instead of judging somewhere the operator did not
44
+ * ask about.
45
+ *
46
+ * WHAT IT CANNOT DO (measurement §6.6). Every V2 hook surface communicates only by mutating
47
+ * the event object it is handed, and `ctx.event.subscribe` is a one-way iterator with no
48
+ * return channel. No hook can defer a stop, force a continuation, or veto a completion.
49
+ * The completion marker is therefore observed, never enforced.
50
+ */
51
+
52
+ import { spawnSync } from "node:child_process";
53
+ import { appendFileSync, existsSync, lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
54
+ import { join, resolve } from "node:path";
55
+
56
+ /** Replaced in place by `install-opencode-adapter.ts` with the checkout path. */
57
+ const INSTALLED_ROOT = "__SOLEUR_PLUGIN_ROOT__";
58
+
59
+ /**
60
+ * Where the guard scripts live.
61
+ *
62
+ * In an installed copy the installer has already replaced `INSTALLED_ROOT` with the checkout
63
+ * path, and this module sits at `<configdir>/plugins/`, so only the token knows where the
64
+ * checkout is. In an unresolved copy — the committed tree, a test run — the module still sits
65
+ * at `<checkout>/opencode/`, so its own parent IS the checkout. The two are told apart by the
66
+ * plugin identity file (`.claude-plugin/plugin.json`), which only a checkout carries — the
67
+ * repo's established root-resolution marker (devin/INSTRUCTIONS.md §Detection). (A module that
68
+ * landed at `<configdir>/plugins/` still carrying the literal token must not resolve to a
69
+ * *relative* token: a planted `__SOLEUR_PLUGIN_ROOT__/hooks/` directory inside the session's own
70
+ * working directory would then supply both guard scripts. It is anchored at `/` so that path
71
+ * cannot exist, and `decideBash` turns the failed spawn into a deny naming the dead path.) The
72
+ * env override exists so a test or a local run can point at a checkout explicitly.
73
+ */
74
+ function resolvePluginRoot(): string {
75
+ const override = process.env["SOLEUR_OPENCODE_PLUGIN_ROOT"];
76
+ if (override !== undefined && override !== "") return override;
77
+ const sibling = resolve(import.meta.dir, "..");
78
+ return existsSync(join(sibling, ".claude-plugin", "plugin.json")) ? sibling : join("/", INSTALLED_ROOT);
79
+ }
80
+
81
+ export const PLUGIN_ROOT = resolvePluginRoot();
82
+
83
+ /**
84
+ * OpenCode tool wire name → the Claude Code tool name the guard scripts decide on.
85
+ *
86
+ * `shell` is the measured wire name of the shell tool on 2.0.32 (§12.1): a live
87
+ * `tool.execute.before` carried `tool="shell"` and a live `permission.evaluate` carried
88
+ * `action="shell"`. `bash` is kept as well because the permission-key table in the V1 docs
89
+ * names the key `bash`; if the wire name ever tracks that table, the mapping still holds.
90
+ * An unmapped name passes through unchanged, and the guards decline a name that is not `Bash`.
91
+ */
92
+ const CLAUDE_TOOL_NAME: Readonly<Record<string, string>> = {
93
+ shell: "Bash",
94
+ bash: "Bash",
95
+ edit: "Edit",
96
+ write: "Write",
97
+ read: "Read",
98
+ grep: "Grep",
99
+ glob: "Glob",
100
+ apply_patch: "Write",
101
+ skill: "Skill",
102
+ webfetch: "WebFetch",
103
+ websearch: "WebSearch",
104
+ question: "AskUserQuestion",
105
+ task: "Task",
106
+ // The measured V2 spawn tool (§12.8): the same logical agent, named `subagent` on the wire.
107
+ subagent: "Task",
108
+ };
109
+
110
+ /**
111
+ * Guards that decide a shell call, in `hooks.json` registration order. The first guard to
112
+ * return a decision ends the walk, matching the Claude registrations, where an earlier hook's
113
+ * `deny` is already final.
114
+ */
115
+ export const BASH_GUARDS: readonly { id: string; script: string }[] = [
116
+ { id: "browser-snapshot-credential-guard", script: "hooks/browser-snapshot-credential-guard.sh" },
117
+ { id: "destructive-command-guard", script: "hooks/destructive-command-guard.sh" },
118
+ ];
119
+
120
+ /** Wall-clock bound for one guard spawn. `hooks.json` registers the same guards at 10 s. */
121
+ const GUARD_TIMEOUT_MS = 12_000;
122
+
123
+ export interface ScriptRun {
124
+ status: number | null;
125
+ stdout: string;
126
+ stderr: string;
127
+ /**
128
+ * The spawn error's code, when the process could not be started at all (`ENOENT`, `EACCES`,
129
+ * `ENOTDIR`). `spawnSync` reports these as `status: null` with the reason only in `error`, so
130
+ * discarding it makes an unenterable working directory indistinguishable from a moved checkout
131
+ * and sends the operator to re-run an installer that will answer `ok`.
132
+ */
133
+ error?: string;
134
+ }
135
+
136
+ export type ScriptRunner = (script: string, stdin: string, cwd: string) => ScriptRun;
137
+
138
+ /** Run a hook script the way the harness does: JSON envelope in on stdin, JSON out on stdout. */
139
+ export function runScript(script: string, stdin: string, cwd: string): ScriptRun {
140
+ const result = spawnSync("bash", [script], {
141
+ input: stdin,
142
+ cwd,
143
+ encoding: "utf8",
144
+ timeout: GUARD_TIMEOUT_MS,
145
+ maxBuffer: 4 * 1024 * 1024,
146
+ env: { ...process.env, CLAUDE_PROJECT_DIR: cwd },
147
+ });
148
+ return {
149
+ status: result.status,
150
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
151
+ stderr: typeof result.stderr === "string" ? result.stderr : "",
152
+ ...(result.error === undefined ? {} : { error: result.error.code ?? result.error.message }),
153
+ };
154
+ }
155
+
156
+ /** Last parseable JSON object in a stream — a script may print a diagnostic line first. */
157
+ function lastJsonObject(text: string): Record<string, unknown> | undefined {
158
+ const whole = text.trim();
159
+ if (whole !== "") {
160
+ try {
161
+ const parsed: unknown = JSON.parse(whole);
162
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
163
+ return parsed as Record<string, unknown>;
164
+ }
165
+ } catch {
166
+ // fall through to the line scan
167
+ }
168
+ }
169
+ const lines = text.split("\n");
170
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
171
+ const line = lines[i].trim();
172
+ if (!line.startsWith("{")) continue;
173
+ try {
174
+ const parsed: unknown = JSON.parse(line);
175
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
176
+ return parsed as Record<string, unknown>;
177
+ }
178
+ } catch {
179
+ // keep scanning
180
+ }
181
+ }
182
+ return undefined;
183
+ }
184
+
185
+ function objectField(source: Record<string, unknown> | undefined, key: string): Record<string, unknown> | undefined {
186
+ const value = source?.[key];
187
+ return value !== null && typeof value === "object" && !Array.isArray(value)
188
+ ? (value as Record<string, unknown>)
189
+ : undefined;
190
+ }
191
+
192
+ export function claudeToolName(tool: string): string {
193
+ return CLAUDE_TOOL_NAME[tool] ?? tool;
194
+ }
195
+
196
+ /**
197
+ * Does a call with this wire name reach the Bash guards?
198
+ *
199
+ * One answer for three askers — the envelope constructor, the `execute.before` capture and the
200
+ * `permission.evaluate` consume. Two copies of this question is the rot that would let the
201
+ * measured wire name (`shell`) or its V1 spelling (`bash`) be handled at one site only, which is
202
+ * silently the pre-fix behaviour: capture with no consume, or consume with no capture.
203
+ */
204
+ export function decidesBash(tool: string): boolean {
205
+ return claudeToolName(tool) === "Bash";
206
+ }
207
+
208
+ /**
209
+ * The Claude PreToolUse envelope the guards parse. Returns `undefined` for any tool the guards
210
+ * cannot decide, so no process is spawned for a call no guard would look at. `tool_input` is
211
+ * passed through whole rather than pre-extracted: the destructive guard's documented behavior
212
+ * on an absent or non-string `command` is to **ask**, and pre-extracting here would silently
213
+ * turn that ask into an allow.
214
+ */
215
+ export function guardEnvelope(tool: string, input: unknown, cwd: string): string | undefined {
216
+ if (!decidesBash(tool)) return undefined;
217
+ const name = claudeToolName(tool);
218
+ const toolInput = input !== null && typeof input === "object" && !Array.isArray(input) ? input : {};
219
+ return JSON.stringify({ hook_event_name: "PreToolUse", tool_name: name, tool_input: toolInput, cwd });
220
+ }
221
+
222
+ /**
223
+ * What a `tool.execute.before` event said about the directory its command will start in.
224
+ *
225
+ * `absent` and `unusable` are deliberately different: the first is the normal case (no `workdir`
226
+ * was supplied, so the session directory is correct), the second means the operator asked for a
227
+ * directory this plugin cannot vouch for — and judging in the session directory instead is exactly
228
+ * the hole #10819 reports, one order of magnitude rarer.
229
+ */
230
+ export type WorkdirCapture =
231
+ | { readonly kind: "absent" }
232
+ | { readonly kind: "usable"; readonly path: string }
233
+ | { readonly kind: "unusable"; readonly raw: string };
234
+
235
+ /**
236
+ * Resolve the `workdir` a tool call carries. Total by construction: it never throws and never
237
+ * returns a path it has not confirmed is a directory.
238
+ *
239
+ * A relative value is resolved against `baseDir` (the session directory) — measured, not assumed:
240
+ * with the session moved to a worktree while the OpenCode process kept its launch cwd in the bare
241
+ * checkout, `workdir: "plugins/soleur"` ran in the worktree. `realpathSync` then yields the physical
242
+ * directory the shell's `chdir` reaches, so `path.resolve`'s lexical answer for a symlinked or
243
+ * `..`-containing value cannot disagree with where the command actually runs.
244
+ */
245
+ export function resolveWorkdir(input: unknown, baseDir: string): WorkdirCapture {
246
+ if (input === null || typeof input !== "object" || Array.isArray(input)) return { kind: "absent" };
247
+ const raw = (input as Record<string, unknown>)["workdir"];
248
+ if (raw === undefined || raw === null) return { kind: "absent" };
249
+ // A present but non-string value is a *shape* violation, not "no workdir was given": treating it
250
+ // as absent would judge the call in the session directory without saying so, which is #10819's
251
+ // silence one shape over. `null`/absent stay the normal case because a runtime may send either.
252
+ if (typeof raw !== "string") return { kind: "unusable", raw: clip(String(raw)) };
253
+ if (raw === "") return { kind: "absent" };
254
+ try {
255
+ // `resolve` already returns an absolute argument unchanged, and `statSync` throws for a missing
256
+ // path, so neither `isAbsolute` nor `existsSync` earns a branch here.
257
+ const absolute = resolve(baseDir, raw);
258
+ if (!statSync(absolute).isDirectory()) return { kind: "unusable", raw: clip(raw) };
259
+ return { kind: "usable", path: realpathSync(absolute) };
260
+ } catch {
261
+ return { kind: "unusable", raw: clip(raw) };
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Clip wire-controlled text to a length an operator can read, after stripping what would forge a
267
+ * record: `field()` removes the control characters, and the bound removes a megabyte of it.
268
+ */
269
+ export function clip(value: string, max = 160): string {
270
+ const flat = value.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, "_");
271
+ return flat.length > max ? `${flat.slice(0, max)}…` : flat;
272
+ }
273
+
274
+ export interface GuardDecision {
275
+ guard: string;
276
+ decision: "deny" | "ask";
277
+ reason: string;
278
+ }
279
+
280
+ /** Read `hookSpecificOutput.permissionDecision` out of a guard's stdout. No decision → undefined. */
281
+ export function parseGuardOutput(stdout: string): { decision: "deny" | "ask"; reason: string } | undefined {
282
+ const specific = objectField(lastJsonObject(stdout), "hookSpecificOutput");
283
+ const decision = specific?.["permissionDecision"];
284
+ if (decision !== "deny" && decision !== "ask") return undefined;
285
+ const reason = specific?.["permissionDecisionReason"];
286
+ return { decision, reason: typeof reason === "string" ? reason : "" };
287
+ }
288
+
289
+ /** The guard id a failed guard *run* is reported under — never a guard that examined and declined. */
290
+ export const GUARD_RUN_FAILURE = "guard-did-not-run";
291
+
292
+ /**
293
+ * Run the Bash guards in order for a tool call; the first decision wins. `undefined` means
294
+ * every guard **ran and declined**, and the call proceeds unchanged.
295
+ *
296
+ * A run that did not produce a decision is not the same event. Both guard scripts print a
297
+ * decision or nothing and exit 0 in every path, so a non-zero status (missing or moved checkout,
298
+ * an unresolved `__SOLEUR_PLUGIN_ROOT__` → 127, a spawn error, the 12 s timeout → `null`) means
299
+ * the guard never spoke. That is a **deny**, with the dead path in the reason: the alternative is
300
+ * a silent fail-open of the credential and destructive-command guards — a support failure dressed
301
+ * as a feature, and the exact class this adapter's spec names as its load-bearing risk. Measured:
302
+ * `decideBash("shell", {command:"rm -rf /"}, …)` returned `undefined` for a `{status:127}` runner
303
+ * before this branch (10-seat review, 2026-10-19).
304
+ */
305
+ export function decideBash(
306
+ tool: string,
307
+ input: unknown,
308
+ cwd: string,
309
+ runner: ScriptRunner = runScript,
310
+ pluginRoot: string = PLUGIN_ROOT,
311
+ ): GuardDecision | undefined {
312
+ const envelope = guardEnvelope(tool, input, cwd);
313
+ if (envelope === undefined) return undefined;
314
+ for (const guard of BASH_GUARDS) {
315
+ const script = join(pluginRoot, guard.script);
316
+ const run = runner(script, envelope, cwd);
317
+ const parsed = parseGuardOutput(run.stdout);
318
+ if (parsed !== undefined) return { guard: guard.id, ...parsed };
319
+ if (run.status !== 0) {
320
+ // A directory that cannot be entered and a guard that could not start share `status: null`, so
321
+ // the error code decides which sentence the operator reads. Only a *directory* fault gets the
322
+ // directory sentence: `ETIMEDOUT`/`ENOBUFS` are the guard's own limits, and `ENOENT` with an
323
+ // existing cwd is a missing interpreter or script — telling that operator to create a
324
+ // directory would send them to the wrong fix.
325
+ if (run.error !== undefined && (run.error === "EACCES" || run.error === "ENOTDIR" || !existsSync(cwd))) {
326
+ return {
327
+ guard: GUARD_RUN_FAILURE,
328
+ decision: "deny",
329
+ reason: `the guard could not be started in \`${clip(cwd)}\` (${run.error}): ${script}. The working directory this call asked for is not enterable, so the command was NOT judged and NOT run. Create that directory and re-issue, or drop the \`workdir\` to be judged in the session directory.`,
330
+ };
331
+ }
332
+ const how =
333
+ run.status === null
334
+ ? run.error === undefined
335
+ ? "timed out or could not be spawned"
336
+ : `could not be spawned (${run.error})`
337
+ : `exited ${run.status}`;
338
+ return {
339
+ guard: GUARD_RUN_FAILURE,
340
+ decision: "deny",
341
+ reason: `the guard did not run (${how}): ${script}. The checkout the adapter points at may have moved or been removed — re-run \`bun "${INSTALLED_ROOT}/scripts/install-opencode-adapter.ts" --check\` from the plugin checkout. The command was NOT run, because an unrunnable guard cannot vouch for it.`,
342
+ };
343
+ }
344
+ }
345
+ return undefined;
346
+ }
347
+
348
+ /**
349
+ * The command a `permission.evaluate` event describes.
350
+ *
351
+ * Measured shape (§12.2): a shell call arrives as `action: "shell"`,
352
+ * `resources: ["echo hello-from-probe"]` — one string that IS the command. Joining rather than
353
+ * indexing is deliberate: it cannot lose text if a shell call ever arrives with more than one
354
+ * resource, and it is identical to indexing on the measured shape.
355
+ */
356
+ export function permissionCommand(resources: readonly unknown[]): string {
357
+ return resources.filter((resource): resource is string => typeof resource === "string").join("\n");
358
+ }
359
+
360
+ /**
361
+ * Decide a `permission.evaluate` event. Non-shell actions cost nothing: the tool-name gate
362
+ * inside `guardEnvelope` returns before any process is spawned.
363
+ */
364
+ export function decidePermission(
365
+ action: unknown,
366
+ resources: readonly unknown[],
367
+ cwd: string,
368
+ runner: ScriptRunner = runScript,
369
+ pluginRoot: string = PLUGIN_ROOT,
370
+ ): GuardDecision | undefined {
371
+ if (typeof action !== "string") return undefined;
372
+ return decideBash(action, { command: permissionCommand(resources) }, cwd, runner, pluginRoot);
373
+ }
374
+
375
+ /**
376
+ * The message the decision carries. The guard's own reason is already the operator-facing text
377
+ * (`This command was NOT run. …` / `Stop and tell the person …`), so it is passed through whole
378
+ * and only prefixed with which guard spoke — never rewritten, because the reason contains the
379
+ * escalation instructions a person needs.
380
+ */
381
+ export function decisionMessage(decision: GuardDecision): string {
382
+ const head = `Soleur's ${decision.guard}:`;
383
+ return decision.reason === "" ? head : `${head} ${decision.reason}`;
384
+ }
385
+
386
+ /** ADR-094: the corpus's frontmatter is stripped before injection. */
387
+ export function stripFrontmatter(text: string): string {
388
+ return text.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
389
+ }
390
+
391
+ /** A regular file that is not a symlink — the rules loader's prompt-injection defense, kept. */
392
+ function isRealFile(path: string): boolean {
393
+ try {
394
+ return lstatSync(path).isFile();
395
+ } catch {
396
+ return false;
397
+ }
398
+ }
399
+
400
+ function additionalContext(stdout: string): string | undefined {
401
+ const value = objectField(lastJsonObject(stdout), "hookSpecificOutput")?.["additionalContext"];
402
+ return typeof value === "string" && value !== "" ? value : undefined;
403
+ }
404
+
405
+ /**
406
+ * The rule corpus for a session, reuse-first.
407
+ *
408
+ * Where the repository carries the canonical loader (`.claude/hooks/session-rules-loader.sh` —
409
+ * ADR-151's frontmatter strip, over-strip guard, symlink rejection, manifest and stamps), it is
410
+ * invoked with a SessionStart envelope and its `additionalContext` is used verbatim. Otherwise
411
+ * the corpus is read directly, keeping the two properties that must never be missing: a symlink
412
+ * is refused, and frontmatter is stripped. Everything the loader adds on top of that (the
413
+ * manifest, the tmpfs alarm block, the SOC 2 session-context snapshot) is not reproduced, and
414
+ * the disposition ledger says so.
415
+ */
416
+ export function rulesInjection(cwd: string, runner: ScriptRunner = runScript): string | undefined {
417
+ const loader = join(cwd, ".claude", "hooks", "session-rules-loader.sh");
418
+ if (isRealFile(loader)) {
419
+ const envelope = JSON.stringify({ hook_event_name: "SessionStart", source: "startup", cwd });
420
+ const injected = additionalContext(runner(loader, envelope, cwd).stdout);
421
+ if (injected !== undefined) return injected;
422
+ }
423
+ const corpus = join(cwd, "AGENTS.rules.md");
424
+ if (isRealFile(corpus)) return stripFrontmatter(readFileSync(corpus, "utf8"));
425
+ return undefined;
426
+ }
427
+
428
+ /**
429
+ * The post-compaction re-read directive. `compaction-state.sh` cannot be reused: it counts
430
+ * `compact_boundary` records in a Claude Code transcript, which this harness has no equivalent
431
+ * of. The directive is reproduced; the window bookkeeping is not (disposition: `downgraded`).
432
+ */
433
+ export function compactionDirective(): string {
434
+ return [
435
+ "[soleur] A checkpoint summary has replaced part of this conversation.",
436
+ "Re-read the rule corpus (`AGENTS.rules.md`, or the copy injected at session start) before",
437
+ "continuing: a summary compresses rule bodies, and a rule you cannot quote is a rule you",
438
+ "cannot follow.",
439
+ ].join(" ");
440
+ }
441
+
442
+ /** Optional observation target. Unset means the completion observer writes nothing. */
443
+ const EVENT_LOG = process.env["SOLEUR_OPENCODE_EVENT_LOG"];
444
+
445
+ /**
446
+ * The default writer. A failed append is announced once and then shrugged off: an unwritable log
447
+ * path (a parent directory that does not exist is the common shape of that mistake) must not make
448
+ * `setup` reject, because a rejected setup leaves **no hook bound at all** — no guards, no capture,
449
+ * nothing — to protect a diagnostic channel.
450
+ */
451
+ const defaultWriter = (() => {
452
+ let broken = false;
453
+ return (line: string): void => {
454
+ if (EVENT_LOG === undefined) return;
455
+ try {
456
+ appendFileSync(EVENT_LOG, `${line}\n`);
457
+ } catch (error) {
458
+ if (broken) return;
459
+ broken = true;
460
+ console.error(
461
+ `[soleur] the event log \`${clip(String(EVENT_LOG))}\` is not writable (${clip(String(error))}); continuing without the durable record. The log's parent directory must exist.`,
462
+ );
463
+ }
464
+ };
465
+ })();
466
+
467
+ export interface PermissionEvaluateEvent {
468
+ readonly action?: unknown;
469
+ readonly resources?: readonly unknown[];
470
+ readonly agent?: unknown;
471
+ /**
472
+ * Where the decision came from. Measured (§12.2): a `shell` call carries
473
+ * `{type: "tool", messageID, id}` and that `id` equals the `tool.execute.before` event's `id`,
474
+ * which is the only channel through which the per-call `workdir` can reach this event.
475
+ */
476
+ readonly source?: { readonly type?: unknown; readonly id?: unknown; readonly messageID?: unknown };
477
+ effect: "allow" | "ask" | "deny";
478
+ message?: string;
479
+ }
480
+
481
+ /** A `tool.execute.before` event. Measured keys: `tool, sessionID, agent, messageID, id, input`. */
482
+ export interface ToolBeforeEvent {
483
+ readonly tool?: unknown;
484
+ readonly id?: unknown;
485
+ readonly messageID?: unknown;
486
+ readonly input?: unknown;
487
+ }
488
+
489
+ export interface SetupContext {
490
+ location?: { directory?: string };
491
+ permission: {
492
+ hook(
493
+ name: "evaluate",
494
+ handler: (event: PermissionEvaluateEvent) => Promise<void> | void,
495
+ ): Promise<unknown>;
496
+ };
497
+ /**
498
+ * The capture surface (#10819). Optional because a runtime that does not offer it must still
499
+ * load; absence, and a registration the runtime rejects, are both announced at setup rather than
500
+ * ignored, because every decision then silently reverts to the session directory.
501
+ */
502
+ tool?: {
503
+ hook(
504
+ name: "execute.before",
505
+ handler: (event: ToolBeforeEvent) => Promise<void> | void,
506
+ ): Promise<unknown>;
507
+ };
508
+ session: {
509
+ hook(
510
+ name: string,
511
+ handler: (event: Record<string, unknown>) => Promise<void> | void,
512
+ options?: { providerID?: string },
513
+ ): Promise<unknown>;
514
+ };
515
+ event: { subscribe(options?: { signal?: AbortSignal }): AsyncIterable<{ type?: string; data?: unknown }> };
516
+ }
517
+
518
+ /** Session-scoped injection state: inject once, then again after a compaction. */
519
+ interface SessionState {
520
+ injected: boolean;
521
+ compacted: boolean;
522
+ }
523
+
524
+ /**
525
+ * Cap on remembered sessions. A long-lived `opencode serve` sees one session per conversation and
526
+ * no session-end event was measured to hook (§6.6), so the map is bounded here and the oldest
527
+ * entry is evicted first — `Map` preserves insertion order, so this is a two-line LRU.
528
+ */
529
+ const MAX_SESSIONS = 500;
530
+
531
+ /**
532
+ * Cap on captured tool calls. One entry per Bash call, keyed by `(messageID, id)`; a long-lived
533
+ * `opencode serve` never tells the plugin a call is finished, so the map is bounded here.
534
+ *
535
+ * Eviction is **silent**, unlike `remember`'s. A capture that is lost is announced where it could
536
+ * change a verdict — at the decision, as `workdir-uncorrelated cause=no-capture` — so an eviction
537
+ * notice would be a second report of the same fact, emitted for calls whose evaluation never
538
+ * arrived at all (cancelled, auto-allowed by the ruleset). Loudness belongs at the decision.
539
+ */
540
+ const MAX_CALLS = 500;
541
+
542
+ /**
543
+ * The key a call is captured and consumed under.
544
+ *
545
+ * `id` alone was the obvious choice and the wrong one: the measured id (`call_00_s3uv…`) reads as a
546
+ * per-message counter, and one plugin instance is shared by every session an `opencode serve`
547
+ * process hosts. `messageID` is measured on both events, so the pair makes a colliding suffix
548
+ * harmless. Both ids are **required**: defaulting a missing one to `""` would collapse the key back
549
+ * to the bare counter and hand one call's workdir to another, so an absent id is uncorrelated
550
+ * (loud, session directory) rather than correlated on half a key.
551
+ */
552
+ function callKey(messageID: string, id: string): string {
553
+ return `${messageID}\u0000${id}`;
554
+ }
555
+
556
+ /**
557
+ * Build `setup`. The hooks are returned bound to injected collaborators so the unit tests can
558
+ * drive them with a fake context and a fake script runner.
559
+ */
560
+ export function createSetup(
561
+ runner: ScriptRunner = runScript,
562
+ pluginRoot: string = PLUGIN_ROOT,
563
+ write: (line: string) => void = defaultWriter,
564
+ report: (line: string) => void = (line) => console.error(line),
565
+ ): (ctx: SetupContext) => Promise<void> {
566
+ const sessions = new Map<string, SessionState>();
567
+ const calls = new Map<string, WorkdirCapture>();
568
+
569
+ /** Wire-controlled text before it goes into a TSV line: no C0/C1, no line/paragraph separators. */
570
+ const field = (value: unknown): string => String(value).replace(/[\u0000-\u001f\u007f\u2028\u2029]/g, "_");
571
+
572
+ /**
573
+ * Announce a degraded correlation on both channels. The log file is off by default
574
+ * (`SOLEUR_OPENCODE_EVENT_LOG` unset), so a log-only claim of loudness is false in the default
575
+ * configuration; stderr visibility in the TUI is not measured, so neither channel alone is
576
+ * enough. The sentence is for the operator, the TSV token for the record.
577
+ */
578
+ const announce = (token: string, detail: string, sentence: string): void => {
579
+ report(`[soleur] ${sentence}`);
580
+ // `detail` is fielded here as well as at the call sites: this is the single line assembler, so a
581
+ // future caller that passes a raw path cannot break the column count.
582
+ write(`soleur-opencode\t${token}\t${field(detail)}`);
583
+ };
584
+
585
+ /** Record a capture, keeping the map bounded. Write-once keys: no recency re-ordering. */
586
+ const recordCall = (key: string, capture: WorkdirCapture): void => {
587
+ calls.set(key, capture);
588
+ while (calls.size > MAX_CALLS) {
589
+ const oldest = calls.keys().next();
590
+ if (oldest.done === true) break;
591
+ calls.delete(oldest.value);
592
+ }
593
+ };
594
+
595
+ /** Remember a session's state, keeping the map bounded and the most recent entry last. */
596
+ const remember = (id: string, state: SessionState): void => {
597
+ sessions.delete(id);
598
+ sessions.set(id, state);
599
+ while (sessions.size > MAX_SESSIONS) {
600
+ const oldest = sessions.keys().next();
601
+ if (oldest.done === true) break;
602
+ sessions.delete(oldest.value);
603
+ }
604
+ };
605
+
606
+ return async function setup(ctx: SetupContext): Promise<void> {
607
+ const sessionCwd = ctx.location?.directory ?? process.cwd();
608
+
609
+ /**
610
+ * The one directory a `shell` decision is judged against, or the escalation to make instead.
611
+ *
612
+ * `ask` is returned only when a workdir **was** captured and cannot be used. Falling back to the
613
+ * session directory there is defect #10819 applied to the one case where the operator
614
+ * explicitly asked for a different directory — and `effect = "ask"` is a measured channel
615
+ * (§12.2), so the plugin can say "I cannot check this" instead of guessing.
616
+ */
617
+ const decisionCwd = (
618
+ event: PermissionEvaluateEvent,
619
+ ): { kind: "decide"; cwd: string } | { kind: "unusable"; raw: string; message: string } => {
620
+ const source = event.source;
621
+ const id = source?.id;
622
+ const idText = typeof id === "string" && id !== "" ? field(id) : "absent";
623
+ // `source.type` is checked as well as the ids: a rule or config source whose id happened to
624
+ // collide with a call id would otherwise apply a stale workdir and log success. A missing
625
+ // message id is uncorrelatable rather than defaulted — with `""` on both sides a per-message
626
+ // counter would collide across concurrent messages and hand call A's workdir to call B.
627
+ const cause =
628
+ source?.type !== "tool"
629
+ ? "source-not-tool"
630
+ : typeof id !== "string" || id === ""
631
+ ? "id-absent"
632
+ : typeof source.messageID !== "string"
633
+ ? "message-id-absent"
634
+ : undefined;
635
+ if (cause !== undefined) {
636
+ announce(
637
+ "workdir-uncorrelated",
638
+ `id=${idText}\tcause=${cause}`,
639
+ `a shell call's permission event does not carry the ids the capture is keyed by (${cause}), so any per-call workdir cannot be correlated — this command is judged against the session directory instead. If this repeats, the runtime changed the event shape; check the OpenCode version.`,
640
+ );
641
+ return { kind: "decide", cwd: sessionCwd };
642
+ }
643
+ const capture = calls.get(callKey(source.messageID as string, id as string));
644
+ if (capture === undefined) {
645
+ announce(
646
+ "workdir-uncorrelated",
647
+ `id=${idText}\tcause=no-capture`,
648
+ `the tool hook recorded nothing for shell call ${idText}, so any workdir it carried is unknown — this command is judged against the session directory instead. The tool hook did not fire; check the OpenCode version, then re-run \`install-opencode-adapter.ts --check\`.`,
649
+ );
650
+ return { kind: "decide", cwd: sessionCwd };
651
+ }
652
+ if (capture.kind === "absent") return { kind: "decide", cwd: sessionCwd };
653
+ if (capture.kind === "usable") {
654
+ write(`soleur-opencode\tworkdir-correlated\tcwd=${field(capture.path)}`);
655
+ return { kind: "decide", cwd: capture.path };
656
+ }
657
+ announce(
658
+ "workdir-unusable",
659
+ `cwd=${field(capture.raw)}`,
660
+ `this shell call asked to run in \`${capture.raw}\`, which is not a directory this plugin can use. The guards were run against the session directory instead and declined, so the command is not checked against the directory it asked for.`,
661
+ );
662
+ return {
663
+ kind: "unusable",
664
+ raw: capture.raw,
665
+ message: `Soleur: this shell call asked for \`${capture.raw}\`, which is not a usable directory, so it could not be checked against the directory it asked for — the guards ran against the session directory and declined. Re-issue it without \`workdir\` to be checked here, or create that directory first.`,
666
+ };
667
+ };
668
+
669
+ /** Deliver a guard decision onto the event, never relaxing an arriving `deny` into an `ask`. */
670
+ const applyDecision = (event: PermissionEvaluateEvent, decision: GuardDecision | undefined): void => {
671
+ if (decision === undefined) return;
672
+ // Only `allow` arrivals were measured (§12.2); relaxing an unmeasured deny is the
673
+ // silent-enforcement-loss direction.
674
+ if (event.effect === "deny" && decision.decision === "ask") return;
675
+ event.effect = decision.decision;
676
+ event.message = decisionMessage(decision);
677
+ // A guard that could not run is an operator-visible fault, so it is announced even when the
678
+ // observation channel was never armed.
679
+ if (decision.guard === GUARD_RUN_FAILURE) {
680
+ report(`[soleur] ${decision.reason}`);
681
+ write(`soleur-opencode\tguard-run-failure\teffect=deny\t${field(decision.reason)}`);
682
+ }
683
+ };
684
+
685
+ await ctx.permission.hook("evaluate", (event) => {
686
+ const action = event.action;
687
+ // ONE predicate for both sides (#10819). A non-Bash action returns before the map is touched:
688
+ // the measured `external_directory` event fires FIRST with the same call id, and consuming it
689
+ // there would lose the workdir before the decision that needs it.
690
+ if (typeof action !== "string" || !decidesBash(action)) return;
691
+ const target = decisionCwd(event);
692
+ if (target.kind === "decide") {
693
+ applyDecision(event, decidePermission(action, event.resources ?? [], target.cwd, runner, pluginRoot));
694
+ return;
695
+ }
696
+ // A captured workdir that cannot be used does NOT skip the guards: their cwd-independent
697
+ // classes (a home wipe, an infra destroy, a credential-bearing snapshot, a forced push to the
698
+ // default branch) are the ones that must survive, and running them costs one prompt at most.
699
+ // Only when they decline does the unresolvable directory become the decision — and an
700
+ // arriving `deny` is still never relaxed into that prompt.
701
+ applyDecision(event, decidePermission(action, event.resources ?? [], sessionCwd, runner, pluginRoot));
702
+ if (event.effect !== "allow") return;
703
+ event.effect = "ask";
704
+ event.message = target.message;
705
+ });
706
+
707
+ /**
708
+ * Capture-only (#10819): publish `input.workdir` for the decision above. Bound **after** the
709
+ * decision surface, so a runtime that rejects this registration cannot leave the guards
710
+ * unbound; wrapped, because optional chaining covers an absent `tool` and not a rejecting
711
+ * `hook()`. Never decides, never throws — a throw here would preempt the real ask behind it.
712
+ */
713
+ const capture = (event: ToolBeforeEvent): void => {
714
+ try {
715
+ const tool = event.tool;
716
+ if (typeof tool !== "string" || !decidesBash(tool)) return;
717
+ const id = event.id;
718
+ const hasId = typeof id === "string" && id !== "";
719
+ if (!hasId || typeof event.messageID !== "string") {
720
+ announce(
721
+ "workdir-uncapturable",
722
+ `id=${hasId ? field(id) : "absent"}\treason=${hasId ? "message-id-absent" : "id-absent"}`,
723
+ "a shell call reached the tool hook without the ids its permission event is keyed by, so its workdir cannot be published — the decision that follows is judged against the session directory. Check the OpenCode version.",
724
+ );
725
+ return;
726
+ }
727
+ recordCall(callKey(event.messageID, id), resolveWorkdir(event.input, sessionCwd));
728
+ } catch (error) {
729
+ // Containment for the "never throw" promise: with total helpers this is unreachable, and it
730
+ // must stay that way rather than becoming the one path that preempts the real ask.
731
+ announce(
732
+ "workdir-capture-error",
733
+ `reason=${field(String(error))}`,
734
+ `the workdir capture failed (${clip(String(error))}); this shell call is judged against the session directory and the guards still run.`,
735
+ );
736
+ }
737
+ };
738
+
739
+ const noToolSurface = (reason: string): void => {
740
+ announce(
741
+ "no-tool-hook-surface",
742
+ `workdir-unavailable\treason=${field(reason)}`,
743
+ `this runtime offers no usable \`tool.execute.before\` surface (${clip(reason)}), so a shell call's \`workdir\` cannot be captured — every command is judged against the session directory, as before this fix. The permission guards remain bound.`,
744
+ );
745
+ };
746
+
747
+ if (ctx.tool === undefined) {
748
+ noToolSurface("the surface is absent");
749
+ } else {
750
+ try {
751
+ await ctx.tool.hook("execute.before", capture);
752
+ } catch (error) {
753
+ noToolSurface(String(error));
754
+ }
755
+ }
756
+
757
+ await ctx.session.hook("context", (event) => {
758
+ const cwd = typeof event["cwd"] === "string" ? event["cwd"] : sessionCwd;
759
+ const rawSessionID = event["sessionID"];
760
+ const sessionID = typeof rawSessionID === "string" ? rawSessionID : undefined;
761
+ // A missing or non-string id gets no shared bucket: one session's `injected` must never
762
+ // silence another's. Such an event injects and is not memoized.
763
+ const state =
764
+ sessionID === undefined ? { injected: false, compacted: false } : (sessions.get(sessionID) ?? { injected: false, compacted: false });
765
+ if (state.injected && !state.compacted) return;
766
+ const system = event["system"];
767
+ if (!Array.isArray(system)) {
768
+ write(`soleur-opencode\tcontext\tno-system-array\tsession=${field(sessionID)}`);
769
+ return;
770
+ }
771
+ // The compaction hook's own `system` reaches only the summarization call, so the re-read
772
+ // directive has to ride the next real request — which is this one (measurement §12.3).
773
+ if (state.compacted) system.push({ type: "text", text: compactionDirective() });
774
+ const rules = rulesInjection(cwd, runner);
775
+ if (rules !== undefined) system.push({ type: "text", text: rules });
776
+ // One line per injection decision: whether the corpus landed must be answerable from the
777
+ // log, so a hook that stops firing — or a corpus that moved — is visible rather than
778
+ // indistinguishable from working.
779
+ const outcome = rules === undefined ? "no-corpus" : `injected:${rules.length}`;
780
+ write(`soleur-opencode\tcontext\t${outcome}\tsession=${field(sessionID)}\tcwd=${field(cwd)}`);
781
+ if (sessionID !== undefined) remember(sessionID, { injected: true, compacted: false });
782
+ });
783
+
784
+ await ctx.session.hook("compaction", (event) => {
785
+ const rawSessionID = event["sessionID"];
786
+ if (typeof rawSessionID !== "string") {
787
+ write(`soleur-opencode\tcompaction\tunkeyed\treread-not-armed`);
788
+ return;
789
+ }
790
+ const state = sessions.get(rawSessionID) ?? { injected: false, compacted: false };
791
+ remember(rawSessionID, { ...state, compacted: true });
792
+ write(`soleur-opencode\tcompaction\treread-armed\tsession=${field(rawSessionID)}`);
793
+ });
794
+
795
+ // Observation only, and it must stay that way: §6.6 measured no return channel, so an idle
796
+ // signal can be noticed and acted on by nothing. Off unless SOLEUR_OPENCODE_EVENT_LOG names
797
+ // a path.
798
+ void (async () => {
799
+ try {
800
+ for await (const event of ctx.event.subscribe()) {
801
+ const type = typeof event?.type === "string" ? event.type : "unknown";
802
+ if (type.startsWith("session.")) write(`soleur-opencode\t${field(type)}\tadvisory-completion-marker-not-enforceable`);
803
+ }
804
+ } catch (error) {
805
+ write(`soleur-opencode\tevent-stream-error\t${field(String(error))}`);
806
+ }
807
+ })();
808
+ };
809
+ }
810
+
811
+ /**
812
+ * The module's default export — a static object, because the loader reads `id` and `setup` off
813
+ * the default export directly.
814
+ */
815
+ export default {
816
+ id: "soleur",
817
+ setup: createSetup(),
818
+ };