@soleur/opencode 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (812) hide show
  1. package/.claude-plugin/agents.manifest.json +476 -0
  2. package/.claude-plugin/plugin.json +42 -0
  3. package/README.md +44 -2
  4. package/agents/community/.gitkeep +0 -0
  5. package/agents/engineering/cto.md +87 -0
  6. package/agents/engineering/design/ddd-architect.md +95 -0
  7. package/agents/engineering/discovery/agent-finder.md +214 -0
  8. package/agents/engineering/discovery/functional-discovery.md +182 -0
  9. package/agents/engineering/infra/infra-security.md +151 -0
  10. package/agents/engineering/infra/platform-strategist.md +109 -0
  11. package/agents/engineering/infra/terraform-architect.md +171 -0
  12. package/agents/engineering/prompt-engineer.md +37 -0
  13. package/agents/engineering/research/best-practices-researcher.md +115 -0
  14. package/agents/engineering/research/framework-docs-researcher.md +93 -0
  15. package/agents/engineering/research/git-history-analyzer.md +53 -0
  16. package/agents/engineering/research/learnings-researcher.md +285 -0
  17. package/agents/engineering/research/repo-research-analyst.md +118 -0
  18. package/agents/engineering/review/agent-native-reviewer.md +279 -0
  19. package/agents/engineering/review/architecture-strategist.md +61 -0
  20. package/agents/engineering/review/code-quality-analyst.md +92 -0
  21. package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
  22. package/agents/engineering/review/data-integrity-guardian.md +82 -0
  23. package/agents/engineering/review/data-migration-expert.md +98 -0
  24. package/agents/engineering/review/deployment-verification-agent.md +183 -0
  25. package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
  26. package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
  27. package/agents/engineering/review/legacy-code-expert.md +55 -0
  28. package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
  29. package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
  30. package/agents/engineering/review/performance-oracle.md +130 -0
  31. package/agents/engineering/review/security-sentinel.md +167 -0
  32. package/agents/engineering/review/semgrep-sast.md +36 -0
  33. package/agents/engineering/review/test-design-reviewer.md +150 -0
  34. package/agents/engineering/review/user-impact-reviewer.md +70 -0
  35. package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
  36. package/agents/finance/budget-analyst.md +29 -0
  37. package/agents/finance/cfo.md +44 -0
  38. package/agents/finance/financial-reporter.md +30 -0
  39. package/agents/finance/revenue-analyst.md +30 -0
  40. package/agents/legal/clo.md +115 -0
  41. package/agents/legal/legal-compliance-auditor.md +110 -0
  42. package/agents/legal/legal-document-generator.md +134 -0
  43. package/agents/marketing/analytics-analyst.md +25 -0
  44. package/agents/marketing/brand-architect.md +181 -0
  45. package/agents/marketing/cmo.md +76 -0
  46. package/agents/marketing/conversion-optimizer.md +19 -0
  47. package/agents/marketing/copywriter.md +20 -0
  48. package/agents/marketing/fact-checker.md +70 -0
  49. package/agents/marketing/growth-strategist.md +145 -0
  50. package/agents/marketing/paid-media-strategist.md +28 -0
  51. package/agents/marketing/pricing-strategist.md +17 -0
  52. package/agents/marketing/programmatic-seo-specialist.md +25 -0
  53. package/agents/marketing/retention-strategist.md +23 -0
  54. package/agents/marketing/seo-aeo-analyst.md +164 -0
  55. package/agents/operations/coo.md +50 -0
  56. package/agents/operations/ops-advisor.md +78 -0
  57. package/agents/operations/ops-provisioner.md +101 -0
  58. package/agents/operations/ops-research.md +52 -0
  59. package/agents/operations/service-automator.md +278 -0
  60. package/agents/product/business-validator.md +206 -0
  61. package/agents/product/competitive-intelligence.md +89 -0
  62. package/agents/product/cpo.md +63 -0
  63. package/agents/product/design/ux-design-lead.md +209 -0
  64. package/agents/product/spec-flow-analyzer.md +122 -0
  65. package/agents/sales/cro.md +44 -0
  66. package/agents/sales/deal-architect.md +29 -0
  67. package/agents/sales/outbound-strategist.md +29 -0
  68. package/agents/sales/pipeline-analyst.md +29 -0
  69. package/agents/support/cco.md +48 -0
  70. package/agents/support/community-manager.md +570 -0
  71. package/agents/support/ticket-triage.md +70 -0
  72. package/bin/soleur-opencode.js +29 -0
  73. package/commands/go.md +659 -0
  74. package/commands/help.md +273 -0
  75. package/commands/sync.md +977 -0
  76. package/commands/version.md +103 -0
  77. package/hooks/antigravity-hooks.json +84 -0
  78. package/hooks/antigravity-session-start.sh +15 -0
  79. package/hooks/bare-root-cwd-guard.sh +131 -0
  80. package/hooks/browser-snapshot-credential-guard.sh +200 -0
  81. package/hooks/codex-session-start.sh +9 -0
  82. package/hooks/compaction-state.sh +525 -0
  83. package/hooks/destructive-command-guard.sh +1387 -0
  84. package/hooks/devin-session-start.sh +90 -0
  85. package/hooks/ext-filter-detect.sh +171 -0
  86. package/hooks/hooks.json +133 -0
  87. package/hooks/lib/hook-tool-kind.sh +24 -0
  88. package/hooks/lib/log-rotation.sh +176 -0
  89. package/hooks/lib/shell-argv.pl +576 -0
  90. package/hooks/memory-backstop.sh +1114 -0
  91. package/hooks/operator-stage-approval.sh +391 -0
  92. package/hooks/stop-hook.sh +367 -0
  93. package/hooks/unkept-promise-hook.sh +383 -0
  94. package/hooks/version-banner.sh +127 -0
  95. package/hooks/welcome-hook.sh +50 -0
  96. package/install.js +108 -0
  97. package/lib/agent-registry.ts +154 -0
  98. package/lib/c4-canonical-cli.mjs +34 -0
  99. package/lib/c4-canonical.mjs +46 -0
  100. package/lib/c4-from-components.ts +554 -0
  101. package/lib/go-routing.ts +81 -0
  102. package/lib/grok-inspect-contract.ts +311 -0
  103. package/lib/harness-model-map.ts +101 -0
  104. package/lib/harness-parity.ts +726 -0
  105. package/lib/harness-tool-map.ts +180 -0
  106. package/lib/harness.ts +716 -0
  107. package/lib/heartbeat-live-reconcile.ts +1153 -0
  108. package/lib/heartbeat-manifest.ts +342 -0
  109. package/lib/kb-coverage.ts +252 -0
  110. package/lib/pr-merge-poll.ts +103 -0
  111. package/lib/workflow-fidelity.ts +388 -0
  112. package/opencode/INSTRUCTIONS.md +152 -0
  113. package/opencode/agents/engineering/cto.md +10 -0
  114. package/opencode/agents/engineering/design/ddd-architect.md +10 -0
  115. package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
  116. package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
  117. package/opencode/agents/engineering/infra/infra-security.md +10 -0
  118. package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
  119. package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
  120. package/opencode/agents/engineering/prompt-engineer.md +10 -0
  121. package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
  122. package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
  123. package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
  124. package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
  125. package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
  126. package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
  127. package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
  128. package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
  129. package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
  130. package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
  131. package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
  132. package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
  133. package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
  134. package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
  135. package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
  136. package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
  137. package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
  138. package/opencode/agents/engineering/review/performance-oracle.md +10 -0
  139. package/opencode/agents/engineering/review/security-sentinel.md +10 -0
  140. package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
  141. package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
  142. package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
  143. package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
  144. package/opencode/agents/finance/budget-analyst.md +10 -0
  145. package/opencode/agents/finance/cfo.md +10 -0
  146. package/opencode/agents/finance/financial-reporter.md +10 -0
  147. package/opencode/agents/finance/revenue-analyst.md +10 -0
  148. package/opencode/agents/legal/clo.md +10 -0
  149. package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
  150. package/opencode/agents/legal/legal-document-generator.md +10 -0
  151. package/opencode/agents/marketing/analytics-analyst.md +10 -0
  152. package/opencode/agents/marketing/brand-architect.md +10 -0
  153. package/opencode/agents/marketing/cmo.md +10 -0
  154. package/opencode/agents/marketing/conversion-optimizer.md +10 -0
  155. package/opencode/agents/marketing/copywriter.md +10 -0
  156. package/opencode/agents/marketing/fact-checker.md +10 -0
  157. package/opencode/agents/marketing/growth-strategist.md +10 -0
  158. package/opencode/agents/marketing/paid-media-strategist.md +10 -0
  159. package/opencode/agents/marketing/pricing-strategist.md +10 -0
  160. package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
  161. package/opencode/agents/marketing/retention-strategist.md +10 -0
  162. package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
  163. package/opencode/agents/operations/coo.md +10 -0
  164. package/opencode/agents/operations/ops-advisor.md +10 -0
  165. package/opencode/agents/operations/ops-provisioner.md +10 -0
  166. package/opencode/agents/operations/ops-research.md +10 -0
  167. package/opencode/agents/operations/service-automator.md +10 -0
  168. package/opencode/agents/product/business-validator.md +10 -0
  169. package/opencode/agents/product/competitive-intelligence.md +10 -0
  170. package/opencode/agents/product/cpo.md +10 -0
  171. package/opencode/agents/product/design/ux-design-lead.md +10 -0
  172. package/opencode/agents/product/spec-flow-analyzer.md +10 -0
  173. package/opencode/agents/sales/cro.md +10 -0
  174. package/opencode/agents/sales/deal-architect.md +10 -0
  175. package/opencode/agents/sales/outbound-strategist.md +10 -0
  176. package/opencode/agents/sales/pipeline-analyst.md +10 -0
  177. package/opencode/agents/support/cco.md +10 -0
  178. package/opencode/agents/support/community-manager.md +10 -0
  179. package/opencode/agents/support/ticket-triage.md +10 -0
  180. package/opencode/commands/go.md +10 -0
  181. package/opencode/commands/help.md +10 -0
  182. package/opencode/commands/sync.md +10 -0
  183. package/opencode/commands/version.md +11 -0
  184. package/opencode/opencode.json +8 -0
  185. package/opencode/plugin.ts +493 -0
  186. package/package.json +36 -4
  187. package/scripts/admin-merge-ready.sh +545 -0
  188. package/scripts/admin-merge-ready.test.sh +753 -0
  189. package/scripts/affected-tests.sh +908 -0
  190. package/scripts/affected-tests.test.sh +1797 -0
  191. package/scripts/alpha-metrics.sh +78 -0
  192. package/scripts/alpha-metrics.test.sh +115 -0
  193. package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
  194. package/scripts/audit-flag-flip.sh +107 -0
  195. package/scripts/check-red-on-main.sh +316 -0
  196. package/scripts/ci-head-verdict.sh +363 -0
  197. package/scripts/cloud-detect.sh +193 -0
  198. package/scripts/delegation-verify.sh +657 -0
  199. package/scripts/delegation-verify.test.sh +1424 -0
  200. package/scripts/deploy-arm.sh +475 -0
  201. package/scripts/domain-model-drift.sh +419 -0
  202. package/scripts/emit-decision.sh +144 -0
  203. package/scripts/emit-decision.test.sh +144 -0
  204. package/scripts/ext-filter-divergence-corpus.txt +15 -0
  205. package/scripts/ext-filter-divergence.sh +189 -0
  206. package/scripts/generate-c4-from-components.ts +442 -0
  207. package/scripts/grok-fidelity-gate.sh +68 -0
  208. package/scripts/grok-pre-push-gate.sh +232 -0
  209. package/scripts/harness-discovery-smoke.ts +906 -0
  210. package/scripts/harness-parity-census.ts +92 -0
  211. package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
  212. package/scripts/install-opencode-adapter.ts +520 -0
  213. package/scripts/lib/affected-tests/common.sh +365 -0
  214. package/scripts/lib/affected-tests/go.sh +157 -0
  215. package/scripts/lib/affected-tests/js.sh +188 -0
  216. package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
  217. package/scripts/lib/affected-tests/python.sh +185 -0
  218. package/scripts/lib/context-sanitize.sh +49 -0
  219. package/scripts/lib/domain-model-lib.sh +180 -0
  220. package/scripts/lib/ext-filter-grammar.tsv +34 -0
  221. package/scripts/lib/operator-script.sh +1236 -0
  222. package/scripts/lib/proc.sh +580 -0
  223. package/scripts/lib/session-state.sh +748 -0
  224. package/scripts/lib/tmp-classify.sh +843 -0
  225. package/scripts/lib/wrapper-boundary.sh +47 -0
  226. package/scripts/lint-ratchet.py +1547 -0
  227. package/scripts/monitor-pr-checks.sh +441 -0
  228. package/scripts/pipeline-tally.sh +756 -0
  229. package/scripts/pipeline-tally.test.sh +740 -0
  230. package/scripts/plugin-version.sh +528 -0
  231. package/scripts/precommit-guard.sh +113 -0
  232. package/scripts/precommit-guard.test.sh +254 -0
  233. package/scripts/reconcile-live-heartbeats.ts +705 -0
  234. package/scripts/render-c4-model.sh +217 -0
  235. package/scripts/resolve-git-root.sh +55 -0
  236. package/scripts/resolve-regenerable-conflicts.sh +363 -0
  237. package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
  238. package/scripts/ruff-requirements.txt +22 -0
  239. package/scripts/run-in-pid-namespace.sh +98 -0
  240. package/scripts/run-in-pid-namespace.test.sh +290 -0
  241. package/scripts/scan-ancestor-signal-helpers.py +556 -0
  242. package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
  243. package/scripts/setup-ralph-loop.sh +238 -0
  244. package/scripts/sync-cursor-name-map.ts +316 -0
  245. package/scripts/sync-grok-agent-compat.ts +188 -0
  246. package/scripts/sync-npm-package.ts +178 -0
  247. package/scripts/sync-opencode-adapter.ts +479 -0
  248. package/scripts/sync-pr-behind.sh +534 -0
  249. package/scripts/sync-pr-behind.test.sh +252 -0
  250. package/scripts/taste-profile-update.sh +180 -0
  251. package/scripts/taste-profile-update.test.sh +167 -0
  252. package/scripts/write-budget-marker.sh +23 -0
  253. package/scripts/write-kb-coverage.ts +113 -0
  254. package/skills/admin-ip-refresh/SKILL.md +81 -0
  255. package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
  256. package/skills/agent-browser/SKILL.md +698 -0
  257. package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
  258. package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
  259. package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
  260. package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
  261. package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
  262. package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
  263. package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
  264. package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
  265. package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
  266. package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
  267. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
  268. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
  269. package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
  270. package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
  271. package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
  272. package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
  273. package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
  274. package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
  275. package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
  276. package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
  277. package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
  278. package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
  279. package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
  280. package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
  281. package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
  282. package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
  283. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
  284. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
  285. package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
  286. package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
  287. package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
  288. package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
  289. package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
  290. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
  291. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
  292. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
  293. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
  294. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
  295. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
  296. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
  297. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
  298. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
  299. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
  300. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
  301. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
  302. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
  303. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
  304. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
  305. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
  306. package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
  307. package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
  308. package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
  309. package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
  310. package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
  311. package/skills/agent-native-architecture/SKILL.md +479 -0
  312. package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
  313. package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
  314. package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
  315. package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
  316. package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
  317. package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
  318. package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
  319. package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
  320. package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
  321. package/skills/agent-native-architecture/references/product-implications.md +478 -0
  322. package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
  323. package/skills/agent-native-architecture/references/self-modification.md +294 -0
  324. package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
  325. package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
  326. package/skills/agent-native-audit/SKILL.md +300 -0
  327. package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
  328. package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
  329. package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
  330. package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
  331. package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
  332. package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
  333. package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
  334. package/skills/architecture/SKILL.md +436 -0
  335. package/skills/architecture/references/adr-template.md +154 -0
  336. package/skills/architecture/references/likec4-reference.md +136 -0
  337. package/skills/architecture/references/nfr-reference.md +126 -0
  338. package/skills/archive-kb/SKILL.md +86 -0
  339. package/skills/archive-kb/scripts/archive-kb.sh +300 -0
  340. package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
  341. package/skills/atdd-developer/SKILL.md +65 -0
  342. package/skills/brainstorm/SKILL.md +701 -0
  343. package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
  344. package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
  345. package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
  346. package/skills/brainstorm/references/ui-surface-terms.md +42 -0
  347. package/skills/brainstorm-techniques/SKILL.md +231 -0
  348. package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
  349. package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
  350. package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
  351. package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
  352. package/skills/campaign-calendar/SKILL.md +145 -0
  353. package/skills/cf-token-scope/SKILL.md +164 -0
  354. package/skills/cf-token-scope/references/widen-playbook.md +114 -0
  355. package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
  356. package/skills/changelog/SKILL.md +142 -0
  357. package/skills/code-to-prd/SKILL.md +66 -0
  358. package/skills/code-to-prd/references/banner-template.md +12 -0
  359. package/skills/code-to-prd/references/prd-template.md +41 -0
  360. package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
  361. package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
  362. package/skills/code-to-prd/test/fixture/.env.example +7 -0
  363. package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
  364. package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
  365. package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
  366. package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
  367. package/skills/code-to-prd/test/fixture/package.json +6 -0
  368. package/skills/cohort-status/SKILL.md +62 -0
  369. package/skills/community/SKILL.md +210 -0
  370. package/skills/community/scripts/bsky-community.sh +644 -0
  371. package/skills/community/scripts/bsky-setup.sh +631 -0
  372. package/skills/community/scripts/community-router.sh +93 -0
  373. package/skills/community/scripts/discord-community.sh +487 -0
  374. package/skills/community/scripts/discord-setup.sh +784 -0
  375. package/skills/community/scripts/github-community.sh +706 -0
  376. package/skills/community/scripts/hn-community.sh +233 -0
  377. package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
  378. package/skills/community/scripts/linkedin-community.sh +750 -0
  379. package/skills/community/scripts/linkedin-setup.sh +758 -0
  380. package/skills/community/scripts/x-community.sh +890 -0
  381. package/skills/community/scripts/x-setup.sh +713 -0
  382. package/skills/community/test/community-argv.test.sh +2922 -0
  383. package/skills/community/test/github-community.test.sh +981 -0
  384. package/skills/competitive-analysis/SKILL.md +69 -0
  385. package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
  386. package/skills/compound/SKILL.md +673 -0
  387. package/skills/compound/scripts/token-efficiency-report.sh +334 -0
  388. package/skills/compound/test/phase-16.test.sh +379 -0
  389. package/skills/compound-capture/SKILL.md +791 -0
  390. package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
  391. package/skills/compound-capture/assets/resolution-template.md +116 -0
  392. package/skills/compound-capture/references/yaml-schema.md +67 -0
  393. package/skills/compound-capture/schema.yaml +181 -0
  394. package/skills/constraint-scaffold/SKILL.md +175 -0
  395. package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
  396. package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
  397. package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
  398. package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
  399. package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
  400. package/skills/constraint-scaffold/references/shared-runner.template +135 -0
  401. package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
  402. package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
  403. package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
  404. package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
  405. package/skills/constraint-scaffold/test/generator.test.sh +93 -0
  406. package/skills/constraint-scaffold/test/parity.test.sh +285 -0
  407. package/skills/content-writer/SKILL.md +242 -0
  408. package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
  409. package/skills/cron-delete/SKILL.md +56 -0
  410. package/skills/cron-list/SKILL.md +54 -0
  411. package/skills/deepen-plan/SKILL.md +937 -0
  412. package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
  413. package/skills/deploy/SKILL.md +115 -0
  414. package/skills/deploy/references/hetzner-setup.md +65 -0
  415. package/skills/deploy/scripts/deploy.sh +37 -0
  416. package/skills/deploy-docs/SKILL.md +95 -0
  417. package/skills/dhh-rails-style/SKILL.md +203 -0
  418. package/skills/dhh-rails-style/references/architecture.md +717 -0
  419. package/skills/dhh-rails-style/references/controllers.md +323 -0
  420. package/skills/dhh-rails-style/references/frontend.md +556 -0
  421. package/skills/dhh-rails-style/references/gems.md +316 -0
  422. package/skills/dhh-rails-style/references/models.md +388 -0
  423. package/skills/dhh-rails-style/references/testing.md +354 -0
  424. package/skills/discord-content/SKILL.md +126 -0
  425. package/skills/docs-site/SKILL.md +214 -0
  426. package/skills/drain-labeled-backlog/SKILL.md +203 -0
  427. package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
  428. package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
  429. package/skills/drain-prs/SKILL.md +142 -0
  430. package/skills/drain-prs/scripts/triage-prs.sh +192 -0
  431. package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
  432. package/skills/dspy-ruby/SKILL.md +634 -0
  433. package/skills/dspy-ruby/assets/config-template.rb +359 -0
  434. package/skills/dspy-ruby/assets/module-template.rb +326 -0
  435. package/skills/dspy-ruby/assets/signature-template.rb +143 -0
  436. package/skills/dspy-ruby/references/core-concepts.md +268 -0
  437. package/skills/dspy-ruby/references/optimization.md +623 -0
  438. package/skills/dspy-ruby/references/providers.md +335 -0
  439. package/skills/eval-harness/README.md +194 -0
  440. package/skills/eval-harness/SKILL.md +157 -0
  441. package/skills/eval-harness/enums/go-routes.json +13 -0
  442. package/skills/eval-harness/enums/incident-threshold.json +5 -0
  443. package/skills/eval-harness/enums/lane.json +5 -0
  444. package/skills/eval-harness/enums/tool-selection.json +20 -0
  445. package/skills/eval-harness/enums/triage-levels.json +5 -0
  446. package/skills/eval-harness/gated-skills.json +34 -0
  447. package/skills/eval-harness/models.generated.json +5 -0
  448. package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
  449. package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
  450. package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
  451. package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
  452. package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
  453. package/skills/eval-harness/prompts/go-baseline.txt +6 -0
  454. package/skills/eval-harness/prompts/go-skill.txt +21 -0
  455. package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
  456. package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
  457. package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
  458. package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
  459. package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
  460. package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
  461. package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
  462. package/skills/eval-harness/prompts/triage-skill.txt +10 -0
  463. package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
  464. package/skills/eval-harness/scripts/extract-block.cjs +87 -0
  465. package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
  466. package/skills/eval-harness/scripts/gen-models.sh +41 -0
  467. package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
  468. package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
  469. package/skills/eval-harness/scripts/parse-label.cjs +93 -0
  470. package/skills/eval-harness/scripts/verdict.cjs +132 -0
  471. package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
  472. package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
  473. package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
  474. package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
  475. package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
  476. package/skills/eval-harness/test/eval-gate.test.sh +132 -0
  477. package/skills/eval-harness/test/extract-block.test.sh +87 -0
  478. package/skills/eval-harness/test/gate-classification.test.sh +77 -0
  479. package/skills/eval-harness/test/gen-models.test.sh +75 -0
  480. package/skills/eval-harness/test/measure-classification.test.sh +74 -0
  481. package/skills/eval-harness/test/parse-label.test.sh +129 -0
  482. package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
  483. package/skills/eval-harness/test/verdict.test.sh +123 -0
  484. package/skills/every-style-editor/SKILL.md +142 -0
  485. package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
  486. package/skills/feature-tweet/SKILL.md +199 -0
  487. package/skills/feature-video/SKILL.md +412 -0
  488. package/skills/feature-video/scripts/check_deps.sh +281 -0
  489. package/skills/file-todos/SKILL.md +267 -0
  490. package/skills/file-todos/assets/todo-template.md +166 -0
  491. package/skills/fix-issue/SKILL.md +278 -0
  492. package/skills/fix-issue/references/agent-authored-exclusion.md +118 -0
  493. package/skills/fix-issue/references/exclude-label-jq-snippet.md +96 -0
  494. package/skills/flag-bootstrap/SETUP.md +142 -0
  495. package/skills/flag-create/SKILL.md +145 -0
  496. package/skills/flag-create/scripts/create.sh +273 -0
  497. package/skills/flag-delete/SKILL.md +167 -0
  498. package/skills/flag-delete/scripts/delete.sh +288 -0
  499. package/skills/flag-list/SKILL.md +99 -0
  500. package/skills/flag-list/scripts/list.sh +275 -0
  501. package/skills/flag-set-role/SKILL.md +229 -0
  502. package/skills/flag-set-role/scripts/flip.sh +774 -0
  503. package/skills/frontend-anti-slop/SKILL.md +106 -0
  504. package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
  505. package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
  506. package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
  507. package/skills/frontend-design/SKILL.md +77 -0
  508. package/skills/gdpr-gate/NOTICE +136 -0
  509. package/skills/gdpr-gate/SKILL.md +341 -0
  510. package/skills/gdpr-gate/references/fields.md +160 -0
  511. package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
  512. package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
  513. package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
  514. package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
  515. package/skills/gdpr-gate/references/layers/frontend.md +249 -0
  516. package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
  517. package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
  518. package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
  519. package/skills/gdpr-gate/references/legal-consent.md +140 -0
  520. package/skills/gdpr-gate/references/non-negotiables.md +60 -0
  521. package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
  522. package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
  523. package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
  524. package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
  525. package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
  526. package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
  527. package/skills/gemini-imagegen/SKILL.md +283 -0
  528. package/skills/gemini-imagegen/requirements.txt +5 -0
  529. package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
  530. package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
  531. package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
  532. package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
  533. package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
  534. package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
  535. package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
  536. package/skills/git-worktree/SKILL.md +534 -0
  537. package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
  538. package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
  539. package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
  540. package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
  541. package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
  542. package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
  543. package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
  544. package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
  545. package/skills/go/SKILL.md +54 -0
  546. package/skills/growth/SKILL.md +164 -0
  547. package/skills/harvest-debt/SKILL.md +78 -0
  548. package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
  549. package/skills/heal-skill/SKILL.md +173 -0
  550. package/skills/help/SKILL.md +46 -0
  551. package/skills/incident/NOTICE +39 -0
  552. package/skills/incident/SKILL.md +395 -0
  553. package/skills/incident/scripts/dry-run.sh +448 -0
  554. package/skills/incident/scripts/redact-engine.py +412 -0
  555. package/skills/incident/scripts/redact-sentinel.sh +44 -0
  556. package/skills/incident/templates/pir.md +139 -0
  557. package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
  558. package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
  559. package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
  560. package/skills/incident/test/fixtures/positive-corpus.md +87 -0
  561. package/skills/incident/test/redact-sentinel.test.sh +1420 -0
  562. package/skills/invoice/SKILL.md +206 -0
  563. package/skills/kb-glossary/SKILL.md +71 -0
  564. package/skills/kb-glossary/references/glossary-format.md +111 -0
  565. package/skills/kb-glossary/references/rejected-request-register.md +115 -0
  566. package/skills/kb-search/SKILL.md +276 -0
  567. package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
  568. package/skills/legal-audit/SKILL.md +161 -0
  569. package/skills/legal-generate/NOTICE +242 -0
  570. package/skills/legal-generate/SKILL.md +244 -0
  571. package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
  572. package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
  573. package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
  574. package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
  575. package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
  576. package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
  577. package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
  578. package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
  579. package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
  580. package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
  581. package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
  582. package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
  583. package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
  584. package/skills/linear-fetch/SKILL.md +247 -0
  585. package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
  586. package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
  587. package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
  588. package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
  589. package/skills/linear-fetch/test/parity.test.sh +112 -0
  590. package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
  591. package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
  592. package/skills/merge-pr/SKILL.md +668 -0
  593. package/skills/model-launch-review/SKILL.md +255 -0
  594. package/skills/model-launch-review/scripts/audit-models.sh +501 -0
  595. package/skills/one-shot/SKILL.md +316 -0
  596. package/skills/one-shot/references/token-discipline.md +74 -0
  597. package/skills/operator-bootstrap/SKILL.md +412 -0
  598. package/skills/operator-bootstrap/template.sh +439 -0
  599. package/skills/operator-digest/SKILL.md +287 -0
  600. package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
  601. package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
  602. package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
  603. package/skills/operator-rephrase/SKILL.md +98 -0
  604. package/skills/pencil-setup/SKILL.md +201 -0
  605. package/skills/pencil-setup/scripts/check_deps.sh +556 -0
  606. package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
  607. package/skills/pencil-setup/scripts/package-lock.json +1172 -0
  608. package/skills/pencil-setup/scripts/package.json +14 -0
  609. package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
  610. package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
  611. package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
  612. package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
  613. package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
  614. package/skills/plan/SKILL.md +1140 -0
  615. package/skills/plan/references/differential-migration-testing.md +185 -0
  616. package/skills/plan/references/plan-community-discovery.md +42 -0
  617. package/skills/plan/references/plan-founder-check.md +137 -0
  618. package/skills/plan/references/plan-functional-overlap.md +25 -0
  619. package/skills/plan/references/plan-host-fanout.md +51 -0
  620. package/skills/plan/references/plan-issue-templates.md +779 -0
  621. package/skills/plan/references/plan-network-outage-checklist.md +131 -0
  622. package/skills/plan/references/plan-scope-check.md +126 -0
  623. package/skills/plan/references/plan-sharp-edges.md +325 -0
  624. package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
  625. package/skills/plan/references/webapp-cwv-observability.md +143 -0
  626. package/skills/plan-review/SKILL.md +91 -0
  627. package/skills/plan-review/lib/named-panel.mjs +58 -0
  628. package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
  629. package/skills/postmerge/SKILL.md +642 -0
  630. package/skills/postmerge/references/deploy-status-debugging.md +138 -0
  631. package/skills/postmerge/references/graceful-degradation.md +20 -0
  632. package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
  633. package/skills/preflight/SKILL.md +1553 -0
  634. package/skills/preflight/references/check-13-founder-check.md +286 -0
  635. package/skills/preflight/scripts/founder-check.py +1640 -0
  636. package/skills/preflight/scripts/parse-form-a.awk +107 -0
  637. package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
  638. package/skills/product-roadmap/SKILL.md +357 -0
  639. package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
  640. package/skills/provision-cloudflare/SKILL.md +67 -0
  641. package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
  642. package/skills/provision-doppler/SKILL.md +58 -0
  643. package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
  644. package/skills/provision-github/SKILL.md +59 -0
  645. package/skills/provision-github/scripts/provision-github.sh +300 -0
  646. package/skills/provision-hetzner/SKILL.md +76 -0
  647. package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
  648. package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
  649. package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
  650. package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
  651. package/skills/qa/SKILL.md +263 -0
  652. package/skills/qa/references/browser-preflight.md +22 -0
  653. package/skills/questionnaire-generate/SKILL.md +328 -0
  654. package/skills/questionnaire-generate/references/questionnaire.template +60 -0
  655. package/skills/rclone/SKILL.md +162 -0
  656. package/skills/rclone/scripts/check_setup.sh +63 -0
  657. package/skills/release-announce/SKILL.md +65 -0
  658. package/skills/release-docs/SKILL.md +92 -0
  659. package/skills/reproduce-bug/SKILL.md +358 -0
  660. package/skills/resolve-debt/SKILL.md +85 -0
  661. package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
  662. package/skills/resolve-parallel/SKILL.md +45 -0
  663. package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
  664. package/skills/resolve-pr-parallel/SKILL.md +68 -0
  665. package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
  666. package/skills/resolve-todo-parallel/SKILL.md +57 -0
  667. package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
  668. package/skills/review/SKILL.md +1621 -0
  669. package/skills/review/references/anti-slop-scanner-hook.md +40 -0
  670. package/skills/review/references/defect-classes-migrations.md +9 -0
  671. package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
  672. package/skills/review/references/review-e2e-testing.md +116 -0
  673. package/skills/review/references/review-todo-structure.md +165 -0
  674. package/skills/review/references/risk-tier-and-fix-rounds.md +95 -0
  675. package/skills/review/references/semgrep-custom-rules.yaml +174 -0
  676. package/skills/review/references/ultrathink-phases.md +98 -0
  677. package/skills/review/references/wfs.md +6 -0
  678. package/skills/review/scripts/emit-review-trailer.sh +499 -0
  679. package/skills/review/scripts/ensure-semgrep.sh +99 -0
  680. package/skills/review/scripts/fix-round-seats.sh +197 -0
  681. package/skills/review/test/emit-review-trailer.test.sh +387 -0
  682. package/skills/review/test/ensure-semgrep.test.sh +111 -0
  683. package/skills/review/test/fix-round-seats.test.sh +237 -0
  684. package/skills/review/workflows/README.md +147 -0
  685. package/skills/review/workflows/review.workflow.js +667 -0
  686. package/skills/schedule/SKILL.md +796 -0
  687. package/skills/seo-aeo/SKILL.md +139 -0
  688. package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
  689. package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
  690. package/skills/ship/SKILL.md +3022 -0
  691. package/skills/ship/references/adr-ordinal-collision.md +47 -0
  692. package/skills/ship/references/affected-gate-contract.md +115 -0
  693. package/skills/ship/references/ci-run-diagnosis.md +29 -0
  694. package/skills/ship/references/ci-workflow-authoring.md +68 -0
  695. package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
  696. package/skills/ship/references/cto-ruling-armed-push.md +5 -0
  697. package/skills/ship/references/decision-challenges-render.md +71 -0
  698. package/skills/ship/references/detached-battery-verdict.md +13 -0
  699. package/skills/ship/references/feature-tweet-draft.md +42 -0
  700. package/skills/ship/references/followthrough-issue-body-template.md +47 -0
  701. package/skills/ship/references/followthrough-stub-template.sh +92 -0
  702. package/skills/ship/references/merge-queue-dequeue.md +39 -0
  703. package/skills/ship/references/operator-holds.md +149 -0
  704. package/skills/ship/references/pipeline-tally-render.md +43 -0
  705. package/skills/ship/references/pr-overlap.md +153 -0
  706. package/skills/ship/references/queue-mode.md +7 -0
  707. package/skills/ship/references/ready-run-wait.md +36 -0
  708. package/skills/ship/references/red-on-main-quarantine.md +110 -0
  709. package/skills/ship/references/register-update-pr-pattern.md +88 -0
  710. package/skills/ship/references/settle-then-admin-merge.md +130 -0
  711. package/skills/ship/references/supabase-column-probe.md +13 -0
  712. package/skills/ship/scripts/auto-close-scan.sh +76 -0
  713. package/skills/ship/scripts/battery-owed.sh +403 -0
  714. package/skills/ship/scripts/hold-gate-eval.sh +410 -0
  715. package/skills/ship/scripts/net-issue-flow.sh +665 -0
  716. package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
  717. package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
  718. package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
  719. package/skills/skill-creator/SKILL.md +299 -0
  720. package/skills/skill-creator/references/api-security.md +248 -0
  721. package/skills/skill-creator/references/authoring-levers.md +110 -0
  722. package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
  723. package/skills/skill-creator/references/best-practices.md +431 -0
  724. package/skills/skill-creator/references/common-patterns.md +639 -0
  725. package/skills/skill-creator/references/core-principles.md +193 -0
  726. package/skills/skill-creator/references/executable-code.md +188 -0
  727. package/skills/skill-creator/references/iteration-and-testing.md +509 -0
  728. package/skills/skill-creator/references/official-spec.md +193 -0
  729. package/skills/skill-creator/references/recommended-structure.md +180 -0
  730. package/skills/skill-creator/references/skill-structure.md +396 -0
  731. package/skills/skill-creator/references/using-scripts.md +122 -0
  732. package/skills/skill-creator/references/using-templates.md +120 -0
  733. package/skills/skill-creator/references/workflows-and-validation.md +541 -0
  734. package/skills/skill-creator/scripts/init_skill.py +322 -0
  735. package/skills/skill-creator/scripts/package_skill.py +110 -0
  736. package/skills/skill-creator/scripts/quick_validate.py +64 -0
  737. package/skills/skill-creator/templates/router-skill.md +73 -0
  738. package/skills/skill-creator/templates/simple-skill.md +34 -0
  739. package/skills/skill-creator/workflows/add-reference.md +96 -0
  740. package/skills/skill-creator/workflows/add-script.md +104 -0
  741. package/skills/skill-creator/workflows/add-template.md +83 -0
  742. package/skills/skill-creator/workflows/add-workflow.md +128 -0
  743. package/skills/skill-creator/workflows/audit-skill.md +159 -0
  744. package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
  745. package/skills/skill-creator/workflows/create-new-skill.md +207 -0
  746. package/skills/skill-creator/workflows/get-guidance.md +134 -0
  747. package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
  748. package/skills/skill-creator/workflows/verify-skill.md +226 -0
  749. package/skills/skill-security-scan/SKILL.md +145 -0
  750. package/skills/skill-security-scan/references/disclaimer.md +21 -0
  751. package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
  752. package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
  753. package/skills/skill-security-scan/references/override-mechanism.md +171 -0
  754. package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
  755. package/skills/skill-security-scan/references/regex-patterns.md +136 -0
  756. package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
  757. package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
  758. package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
  759. package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
  760. package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
  761. package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
  762. package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
  763. package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
  764. package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
  765. package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
  766. package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
  767. package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
  768. package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
  769. package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
  770. package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
  771. package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
  772. package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
  773. package/skills/skill-security-scan/scripts/lib.sh +197 -0
  774. package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
  775. package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
  776. package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
  777. package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
  778. package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
  779. package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
  780. package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
  781. package/skills/social-distribute/SKILL.md +485 -0
  782. package/skills/spec-templates/SKILL.md +274 -0
  783. package/skills/sync/SKILL.md +49 -0
  784. package/skills/test-browser/SKILL.md +464 -0
  785. package/skills/test-browser/references/feature-map.md +161 -0
  786. package/skills/test-fix-loop/SKILL.md +159 -0
  787. package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
  788. package/skills/triage/SKILL.md +402 -0
  789. package/skills/trigger-cron/SKILL.md +136 -0
  790. package/skills/trigger-cron/scripts/trigger.sh +198 -0
  791. package/skills/user-set-role/SKILL.md +147 -0
  792. package/skills/user-set-role/scripts/set-role.sh +218 -0
  793. package/skills/user-story-writer/SKILL.md +77 -0
  794. package/skills/ux-audit/SKILL.md +251 -0
  795. package/skills/ux-audit/references/finding.schema.json +62 -0
  796. package/skills/ux-audit/references/route-list.yaml +95 -0
  797. package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
  798. package/skills/ux-audit/scripts/bot-signin.ts +135 -0
  799. package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
  800. package/skills/work/SKILL.md +1479 -0
  801. package/skills/work/references/end-of-work-resume.md +7 -0
  802. package/skills/work/references/hook-test-main-cwd.md +24 -0
  803. package/skills/work/references/key-principles.md +83 -0
  804. package/skills/work/references/property-testing.md +122 -0
  805. package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
  806. package/skills/work/references/work-agent-teams.md +97 -0
  807. package/skills/work/references/work-lifecycle-parallel.md +147 -0
  808. package/skills/work/references/work-lockfile-bumps.md +97 -0
  809. package/skills/work/references/work-scratch-sandboxes.md +212 -0
  810. package/skills/work/references/work-subagent-fanout.md +61 -0
  811. package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
  812. package/skills/xcode-test/SKILL.md +360 -0
@@ -0,0 +1,843 @@
1
+ #!/usr/bin/env bash
2
+ # tmp-classify.sh — the single "safe to move" classifier for Soleur scratch dirs.
3
+ #
4
+ # WHY
5
+ # ---
6
+ # /tmp and /var/tmp accumulate tens of thousands of orphaned session dirs from
7
+ # Soleur runs (27 GB / ~67k entries measured 2026-09-24). Deleting the wrong
8
+ # entry destroys live session work or authored files, so EVERY consumer —
9
+ # soleur-tmp-purge.sh, tmpfs-guard Reaper 3, and the worktree-manager
10
+ # session-start sweep — sources THIS module rather than re-implementing
11
+ # "safe to move". Three divergent copies of a destructive predicate is the
12
+ # defect class this file exists to prevent.
13
+ #
14
+ # USAGE
15
+ # -----
16
+ # source "<repo>/plugins/soleur/scripts/lib/tmp-classify.sh" # repo-side
17
+ # source "$SCRIPT_DIR/../../../scripts/lib/tmp-classify.sh" # shipped plugin
18
+ #
19
+ # class=$(tc_classify_entry "$dir") # attribution ladder, below
20
+ # tc_entry_is_live "$dir" <pid> # conjunctive liveness
21
+ # tc_quarantine_move "$dir" "$base" <class-subdir>
22
+ #
23
+ # ATTRIBUTION LADDER (first match wins)
24
+ # worktree .git FILE present → owning repo's registry classification —
25
+ # verified attribution, evaluated BEFORE protected/prefix so
26
+ # a signature collision can never steal a registered tree
27
+ # protected never a candidate (systemd-private-*, skill-security-scan-*,
28
+ # bare tmp.*, quarantine artifacts, claude-<uid>, ...)
29
+ # marker <dir>/.soleur-owned valid marker → declared-owned; a marker
30
+ # that exists but fails verification (foreign/absent
31
+ # namespace, owner_root cycle) VETOES the schema rung below —
32
+ # it classifies unattributable, never schema
33
+ # schema soleur-run.<pid>.XXXXXXXX → declared-owned
34
+ # prefix:<name> allowlisted prefix + required subpath signature present —
35
+ # evaluated BEFORE standalone-clone so `.git/config`-signature
36
+ # fixture classes (pirgate-*, deploygap-gate-*) stay reachable
37
+ # standalone-clone .git DIRECTORY present → report-only, never a candidate
38
+ # empty empty dir + long random-looking name → rmdir candidate
39
+ # file:<name> allowlisted regular-file name-shape
40
+ # unattributable report only — never moved
41
+ #
42
+ # LIVENESS (conjunctive — every check must pass for "dead")
43
+ # owner pid absent from procfs AND no process fd/cwd beneath the dir AND no
44
+ # process environ contains the dir path AND the marker/schema's recorded
45
+ # pid-namespace matches the caller's. PPID is never consulted: a dead
46
+ # owner's children reparent to init. environ is exec-time, so it can only
47
+ # ever show the path for processes exec'd AFTER the export — it is a
48
+ # descendant scan, not owner evidence. pid-present-but-foreign (reuse) is
49
+ # retentive: spare on ambiguity.
50
+ #
51
+ # SEAMS (env overrides, for tests and future multi-base callers)
52
+ # TMP_CLASSIFY_PROC procfs root (default /proc)
53
+ # TMP_CLASSIFY_AGE_FLOOR_MIN min tree age, minutes (default 1440)
54
+ # TMP_CLASSIFY_WT_FLOOR_MIN worktree age floor (default 4320)
55
+ # TMP_CLASSIFY_RETAIN_FLOOR_MIN retain-since hard floor (default 10080)
56
+ # TMP_CLASSIFY_RETAIN_DIR retain-since stamp dir
57
+ # TMP_CLASSIFY_UID uid scope (default $(id -u))
58
+ # TMP_CLASSIFY_LEDGER disposition ledger (~/.local/state/soleur/tmp-purge-ledger.log;
59
+ # SOLEUR_PURGE_LEDGER also honored)
60
+ # TMP_CLASSIFY_INUSE_TOKEN_CAP environ-token cap (default 50000; on
61
+ # truncation map-misses fall back to a
62
+ # per-candidate walk — never a false dead)
63
+ # TMP_CLASSIFY_INUSE_TTL_S map staleness bound (default 120; consumers
64
+ # call tc_inuse_map_refresh before actions)
65
+ #
66
+ # PORTABILITY: GNU find -printf and /proc are Linux-isms; on hosts without them
67
+ # the affected rungs fail CLOSED (entry reads as fresh/live → retained).
68
+
69
+ [[ -n "${_SOLEUR_TMP_CLASSIFY_SOURCED:-}" ]] && return 0
70
+ _SOLEUR_TMP_CLASSIFY_SOURCED=1
71
+
72
+ TC_PROC="${TMP_CLASSIFY_PROC:-/proc}"
73
+ TC_AGE_FLOOR_MIN="${TMP_CLASSIFY_AGE_FLOOR_MIN:-1440}"
74
+ TC_WT_FLOOR_MIN="${TMP_CLASSIFY_WT_FLOOR_MIN:-4320}"
75
+ TC_RETAIN_FLOOR_MIN="${TMP_CLASSIFY_RETAIN_FLOOR_MIN:-10080}"
76
+ TC_RETAIN_DIR="${TMP_CLASSIFY_RETAIN_DIR:-${XDG_STATE_HOME:-${HOME:-/nonexistent}/.local/state}/soleur/tmp-retain}"
77
+ TC_UID="${TMP_CLASSIFY_UID:-$(id -u)}"
78
+ TC_LEDGER="${SOLEUR_PURGE_LEDGER:-${TMP_CLASSIFY_LEDGER:-${XDG_STATE_HOME:-${HOME:-/nonexistent}/.local/state}/soleur/tmp-purge-ledger.log}}"
79
+ TC_INUSE_TTL_S="${TMP_CLASSIFY_INUSE_TTL_S:-120}"
80
+
81
+ # tc_uid_of <path> — portable owner uid (GNU stat -c, BSD stat -f).
82
+ tc_uid_of() { stat -c %u -- "$1" 2>/dev/null || stat -f %u -- "$1" 2>/dev/null; }
83
+
84
+ # Ambient git state must never leak into the safety conjuncts: a poisoned
85
+ # GIT_DIR/GIT_CONFIG_COUNT in the caller's environment would make a candidate's
86
+ # clean/merged/upstream verdicts answer for a DIFFERENT repo, and a candidate-
87
+ # controlled gitdir could run core.fsmonitor hooks. Every conjunct-side git
88
+ # call goes through this.
89
+ _tc_git() {
90
+ env -u GIT_DIR -u GIT_WORK_TREE -u GIT_COMMON_DIR -u GIT_CONFIG_COUNT \
91
+ -u GIT_CONFIG_NOSYSTEM -u GIT_CONFIG_GLOBAL -u GIT_CONFIG_SYSTEM \
92
+ git -c core.fsmonitor=false "$@"
93
+ }
94
+
95
+ # --- Protected names ---------------------------------------------------------
96
+ # Evaluated BEFORE every ladder rung: a future allowlist row must never outrank
97
+ # a protection. Quarantine artifacts are excluded from enumeration entirely
98
+ # here as a second layer beneath the caller's own exclusion.
99
+
100
+ # Takes a path OR a basename; ${1##*/} avoids a spawn per candidate — sweep
101
+ # callers enumerate tens of thousands of entries on a shared base.
102
+ tc_is_protected() {
103
+ local name="${1##*/}"
104
+ case "$name" in
105
+ soleur-quarantine|soleur-quarantine.*|*.quarantine-meta) return 0 ;;
106
+ tmp|tmp.*) return 0 ;; # bare mktemp — unattributable, tmpfiles' job
107
+ plan-*|shared-*|vbcr*) return 0 ;;
108
+ skill-security-scan-*) return 0 ;; # #6760 bounded by the scanner's own startup reap; generic sweepers must not out-race the evidence window
109
+ systemd-private-*) return 0 ;;
110
+ playwright*|node-compile-cache|claude-*) return 0 ;;
111
+ .|..) return 0 ;;
112
+ *) return 1 ;;
113
+ esac
114
+ }
115
+
116
+ # --- Marker / schema parsing ---------------------------------------------------
117
+
118
+ # tc_marker_owner_pid <dir> [depth] → prints owner pid AND sets TC_PID.
119
+ # rc 0 = verified owner; rc 1 = no marker (caller may try the schema rung);
120
+ # rc 2 = marker PRESENT but unverifiable (wrong uid, foreign/absent/unknown
121
+ # pid-namespace, owner_root cycle) — a VETO: callers must not fall back to
122
+ # the schema rung, because a rejected declaration means the dir's provenance
123
+ # is in doubt and every recorded identity is untrustworthy.
124
+ # Marker contract: regular file, not a symlink, owned by TC_UID, carrying
125
+ # `pid=<top-level-harness-pid>` OR `owner_root=<soleur-run.* dir>`, plus
126
+ # `schema=` and `ns=pid:[<inode>]`. `ns=` must parse AND equal our namespace:
127
+ # `pid:[unknown]` (a writer whose readlink failed) and absent-ns markers are
128
+ # both unverifiable — never silently trusted.
129
+ tc_marker_owner_pid() {
130
+ local dir="$1" depth="${2:-0}" m="$1/.soleur-owned" pid="" oroot="" ns="" myns
131
+ (( depth <= 4 )) || return 2 # owner_root cycle bound — unverifiable
132
+ [[ -f "$m" && ! -L "$m" ]] || return 1
133
+ [[ "$(tc_uid_of "$m")" == "$TC_UID" ]] || return 2
134
+ ns="$(sed -n 's/^ns=\(pid:\[[0-9]*\]\)$/\1/p' "$m" 2>/dev/null | head -1)"
135
+ myns="$(tc_my_pid_ns)"
136
+ if [[ -z "$ns" || "$ns" != "$myns" || "$myns" == "pid:[unknown]" ]]; then
137
+ return 2 # namespace absent, unparseable, foreign, or unverifiable
138
+ fi
139
+ pid="$(sed -n 's/^pid=\([0-9][0-9]*\)$/\1/p' "$m" 2>/dev/null | head -1)"
140
+ if [[ -z "$pid" ]]; then
141
+ oroot="$(sed -n 's/^owner_root=\([^[:space:]]*\)$/\1/p' "$m" 2>/dev/null | head -1)"
142
+ [[ -n "$oroot" ]] || return 2
143
+ if tc_schema_owner_pid "${oroot##*/}" >/dev/null 2>&1; then pid="$TC_PID"; fi
144
+ if [[ -z "$pid" ]] && tc_marker_owner_pid "$oroot" "$((depth + 1))" >/dev/null 2>&1; then pid="$TC_PID"; fi
145
+ [[ -n "$pid" ]] || return 2
146
+ fi
147
+ TC_PID="$pid"
148
+ printf '%s' "$pid"
149
+ }
150
+
151
+ # tc_schema_owner_pid <basename> → prints pid AND sets TC_PID from
152
+ # soleur-run.<pid>.XXXXXXXX.
153
+ tc_schema_owner_pid() {
154
+ local n="$1"
155
+ [[ "$n" =~ ^soleur-run\.([0-9]+)\.[A-Za-z0-9_-]{8,}$ ]] || return 1
156
+ TC_PID="${BASH_REMATCH[1]}"
157
+ printf '%s' "$TC_PID"
158
+ }
159
+
160
+ # tc_my_pid_ns → `pid:[<inode>]` of the caller's own pid namespace.
161
+ tc_my_pid_ns() {
162
+ readlink "$TC_PROC/self/ns/pid" 2>/dev/null || printf 'pid:[unknown]'
163
+ }
164
+
165
+ # --- Liveness ------------------------------------------------------------------
166
+
167
+ # tc_owner_alive <pid> — /proc presence AND same-uid. A live pid owned by a
168
+ # DIFFERENT uid cannot be our marker's owner — the declaring writer runs as
169
+ # TC_UID — so `pid=1`-style claims (deliberate anti-reap or namespace
170
+ # confusion) do not count as a live owner. Genuine same-uid pid reuse stays
171
+ # retentive through the fd/environ conjuncts below.
172
+ tc_owner_alive() {
173
+ local pid="$1"
174
+ [[ "$pid" =~ ^[0-9]+$ && -d "$TC_PROC/$pid" ]] || return 1
175
+ [[ "$(tc_uid_of "$TC_PROC/$pid")" == "$TC_UID" ]]
176
+ }
177
+
178
+ # tc_owner_pid_verify <dir> — resolve the declared owner honoring precedence:
179
+ # marker FIRST (it carries the pid-namespace discriminator), schema name only
180
+ # when NO marker exists. rc: 0 = owner pid in TC_PID (TC_DECL_KIND set to
181
+ # marker|schema), 1 = no declaration, 2 = declaration present but
182
+ # unverifiable — consumers must RETAIN rc 2 and never fall through to schema.
183
+ tc_owner_pid_verify() {
184
+ local dir="$1" rc
185
+ TC_DECL_KIND=""
186
+ if [[ -e "$dir/.soleur-owned" || -L "$dir/.soleur-owned" ]]; then
187
+ # `if` context is load-bearing: a plain call's nonzero rc aborts under
188
+ # set -e before it can be inspected.
189
+ if tc_marker_owner_pid "$dir" >/dev/null 2>&1; then rc=0; else rc=$?; fi
190
+ if (( rc == 0 )); then TC_DECL_KIND="marker"; return 0; fi
191
+ (( rc == 2 )) && return 2
192
+ # rc 1 with a marker path existing is impossible here — belt: veto.
193
+ return 2
194
+ fi
195
+ if tc_schema_owner_pid "${dir##*/}" >/dev/null 2>&1; then
196
+ TC_DECL_KIND="schema"; return 0
197
+ fi
198
+ return 1
199
+ }
200
+
201
+ # tc_build_inuse_map <base>... — ONE /proc pass marking every top-level entry
202
+ # under the given bases that any live process touches via cwd, an open fd, a
203
+ # memory mapping, a bound unix socket, or an environ token. Callers sweeping
204
+ # many candidates (the session-start sweep) build it once and get O(1)
205
+ # membership from tc_tree_has_live_handles instead of paying a full procfs
206
+ # walk per candidate. map_files and net/unix are included so the coverage is
207
+ # the UNION of every handle class the guard's reapers know (JVM
208
+ # open-mmap-close, Chrome SingletonSocket) — the maps must never diverge on
209
+ # what counts as "live".
210
+ declare -gA TC_INUSE_MAP=()
211
+ declare -ga _TC_MAP_BASES=()
212
+
213
+ # Internal marker — dynamic-scoped over the caller's `_tc_bases` array.
214
+ _tc_mark_inuse() {
215
+ local t="$1" rest top bb
216
+ for bb in "${_tc_bases[@]}"; do
217
+ case "$t" in
218
+ "$bb"/*)
219
+ rest="${t#"$bb"/}"
220
+ top="${rest%%/*}"
221
+ [[ -n "$top" ]] && TC_INUSE_MAP["$bb/$top"]=1
222
+ ;;
223
+ esac
224
+ done
225
+ }
226
+
227
+ tc_build_inuse_map() {
228
+ TC_INUSE_MAP=()
229
+ _TC_MAP_BASES=("$@")
230
+ [[ -d "$TC_PROC" ]] || return 1 # sentinel stays unset → per-candidate fallback
231
+ local -a _tc_bases=("$@")
232
+ local target tok
233
+
234
+ # cwd + fd + map_files targets in ONE find: `-printf '%l'` hands back link
235
+ # targets with no per-entry readlink spawn — ~30k syscalls collapse to a
236
+ # single walk. map_files is load-bearing: open()+mmap(MAP_SHARED)+close()
237
+ # leaves no fd to find — the JVM hsperfdata class — and its path appears
238
+ # only here. Pseudo links (`socket:[…]`, `anon_inode:…`) are not absolute
239
+ # and never match a base.
240
+ while IFS= read -r target; do
241
+ [[ "$target" == /* ]] && _tc_mark_inuse "$target"
242
+ done < <(find "$TC_PROC"/[0-9]*/fd "$TC_PROC"/[0-9]*/cwd \
243
+ "$TC_PROC"/[0-9]*/map_files -type l -printf '%l\n' 2>/dev/null)
244
+
245
+ # Bound unix-domain sockets — a socket fd readlinks to `socket:[inode]`,
246
+ # never its filesystem path, so a dir held only by a listener is invisible
247
+ # to the walk above. /proc/net/unix is the only place the path appears; one
248
+ # file read, same 7-column strip the guard's map uses.
249
+ if [[ -r "$TC_PROC/net/unix" ]]; then
250
+ while IFS= read -r target; do
251
+ [[ -n "$target" ]] && _tc_mark_inuse "$target"
252
+ done < <(
253
+ awk '{ line = $0
254
+ if (sub(/^([^ ]+[ ]+){7}/, "", line) && substr(line, 1, 1) == "/") print line }' \
255
+ "$TC_PROC/net/unix" 2>/dev/null || true
256
+ )
257
+ fi
258
+
259
+ # environ values are NUL-separated VAR=val with colon-joined path lists; a
260
+ # descendant of a dead owner can still carry TMPDIR=<root>. One concatenated
261
+ # pass rather than a grep per pid (a per-pid spawn measured ~10s for a
262
+ # single candidate on a busy host). The cap bounds pathological environ
263
+ # volume — on truncation a marker is set and map MISSES fall back to the
264
+ # per-candidate walk, so a capped map can never prove a false "dead".
265
+ local cap="${TMP_CLASSIFY_INUSE_TOKEN_CAP:-50000}"
266
+ while IFS= read -r tok; do
267
+ if [[ "$tok" == "/__TC_TRUNC__" ]]; then TC_INUSE_MAP["__truncated__"]=1; continue; fi
268
+ [[ -n "$tok" ]] && _tc_mark_inuse "$tok"
269
+ done < <(cat "$TC_PROC"/[0-9]*/environ 2>/dev/null \
270
+ | tr '\0:' '\n' \
271
+ | grep -oE '/[^[:space:]"'"'"']+' 2>/dev/null \
272
+ | awk -v cap="$cap" -v sent="/__TC_TRUNC__" \
273
+ 'NR <= cap { print; next } { print sent; exit }')
274
+
275
+ TC_INUSE_MAP["__built_ts__"]="${EPOCHSECONDS:-$(date +%s)}"
276
+ TC_INUSE_MAP["__built__"]=1 # set LAST — presence proves the build ran
277
+ }
278
+
279
+ # tc_inuse_map_refresh [ttl_s] — rebuild TC_INUSE_MAP when older than the
280
+ # staleness bound. The map is a snapshot; a multi-minute apply must not treat
281
+ # scan-start evidence as action-time truth (a dir can acquire a live handle
282
+ # mid-run). Cheap when fresh: one assoc read.
283
+ tc_inuse_map_refresh() {
284
+ local ttl="${1:-$TC_INUSE_TTL_S}" now
285
+ [[ -n "${TC_INUSE_MAP[__built__]:-}" ]] || return 0
286
+ now="${EPOCHSECONDS:-$(date +%s)}"
287
+ if (( now - ${TC_INUSE_MAP[__built_ts__]:-0} > ttl )); then
288
+ tc_build_inuse_map "${_TC_MAP_BASES[@]}" || true
289
+ fi
290
+ }
291
+
292
+ # _tc_walk_handles <dir> — the per-candidate procfs walk: cwd, fd, map_files,
293
+ # environ substring, and bound unix-socket paths. Fails CLOSED (returns
294
+ # "live") when procfs is unreadable — a degraded host must never prove "dead".
295
+ _tc_walk_handles() {
296
+ local dir="$1" p fd target
297
+ [[ -d "$TC_PROC" ]] || return 0
298
+ for p in "$TC_PROC"/[0-9]*; do
299
+ [[ -d "$p" ]] || continue
300
+ if target="$(readlink "$p/cwd" 2>/dev/null)" && [[ "$target" == "$dir" || "$target" == "$dir"/* ]]; then
301
+ return 0
302
+ fi
303
+ for fd in "$p"/fd/* "$p"/map_files/*; do
304
+ [[ -e "$fd" || -L "$fd" ]] || continue
305
+ target="$(readlink "$fd" 2>/dev/null)" || continue
306
+ if [[ "$target" == "$dir" || "$target" == "$dir"/* ]]; then return 0; fi
307
+ done
308
+ if grep -aqF -- "$dir" "$p/environ" 2>/dev/null; then return 0; fi
309
+ done
310
+ # Bound unix sockets: fd links never carry the path — /proc/net/unix does.
311
+ if [[ -r "$TC_PROC/net/unix" ]] && grep -aqF -- "$dir" "$TC_PROC/net/unix" 2>/dev/null; then
312
+ return 0
313
+ fi
314
+ return 1
315
+ }
316
+
317
+ # tc_tree_has_live_handles <dir> — map-fast-path over _tc_walk_handles.
318
+ # A truncated map can never prove a negative: on miss it walks instead.
319
+ tc_tree_has_live_handles() {
320
+ local dir="$1"
321
+ [[ -d "$TC_PROC" ]] || return 0
322
+ if [[ -n "${TC_INUSE_MAP[__built__]:-}" ]]; then
323
+ local a="$dir"
324
+ while [[ "$a" == */* && "$a" != "/" ]]; do
325
+ [[ -n "${TC_INUSE_MAP[$a]:-}" ]] && return 0
326
+ a="${a%/*}"
327
+ done
328
+ [[ -z "${TC_INUSE_MAP[__truncated__]:-}" ]] && return 1
329
+ # truncated — a map miss is not evidence of death; walk it.
330
+ fi
331
+ _tc_walk_handles "$dir"
332
+ }
333
+
334
+ # tc_tree_has_live_handles_now <dir> — the walk, ALWAYS, ignoring the map.
335
+ # For action-time re-verification right before a TERMINAL delete: the map can
336
+ # be seconds stale mid-scan and tmpfs deletion has no quarantine to appeal to.
337
+ tc_tree_has_live_handles_now() {
338
+ _tc_walk_handles "$1"
339
+ }
340
+
341
+ # tc_entry_is_live <dir> <owner-pid|""> — the full conjunct. rc 0 = live (retain).
342
+ tc_entry_is_live() {
343
+ local dir="$1" pid="${2:-}"
344
+ if [[ -n "$pid" ]] && tc_owner_alive "$pid"; then return 0; fi
345
+ if tc_tree_has_live_handles "$dir"; then return 0; fi
346
+ return 1
347
+ }
348
+
349
+ # --- Freshness -------------------------------------------------------------------
350
+
351
+ # tc_tree_age_min <dir> — minutes since the NEWEST mtime anywhere in the tree.
352
+ # A top-level dir mtime does not advance on nested writes (the _FRESH_TOP
353
+ # defect class). Missing find -printf (BSD find) → prints 0 (fresh →
354
+ # retained): a top-level-stat fallback would miss nested writes and read a
355
+ # fresh tree as stale — the fail direction here is deletion, so the probe
356
+ # fails closed.
357
+ tc_tree_age_min() {
358
+ local dir="$1" newest now
359
+ newest="$(find "$dir" -printf '%T@\n' 2>/dev/null \
360
+ | awk 'NR==1 || $1 > m { m = $1 } END { if (NR) print m }' \
361
+ | cut -d. -f1)"
362
+ # awk max-scan: same IO as sort|head but O(1) memory and no full-buffer sort.
363
+ [[ "$newest" =~ ^[0-9]+$ ]] || { echo 0; return 0; }
364
+ now="$(date +%s)"
365
+ if (( newest <= 0 || newest > now )); then echo 0; else echo $(( (now - newest) / 60 )); fi
366
+ }
367
+
368
+ # --- Empty-dir rung ----------------------------------------------------------------
369
+
370
+ # tc_is_reapable_empty <dir> — empty AND a random-looking ≥15-char basename
371
+ # AND not a mountpoint (device-id compare against parent doubles as the
372
+ # BSD-portable mountpoint check).
373
+ tc_is_reapable_empty() {
374
+ local dir="$1" name
375
+ name="${dir##*/}"
376
+ [[ "$name" =~ ^[A-Za-z0-9_.-]{15,}$ ]] || return 1
377
+ [[ -d "$dir" ]] || return 1
378
+ # Emptiness: a caller sweeping a whole base may precompute TC_NONEMPTY
379
+ # (one `find -mindepth 2` pass marking every dir that has children) — the
380
+ # per-candidate `ls` spawn is the same cost class as the basename spawn this
381
+ # function just dropped. Falls back to the per-dir check when unset.
382
+ if [[ -n "${TC_NONEMPTY_BUILT:-}" ]]; then
383
+ [[ -n "${TC_NONEMPTY[$dir]:-}" ]] && return 1
384
+ else
385
+ [[ -z "$(ls -A -- "$dir" 2>/dev/null)" ]] || return 1
386
+ fi
387
+ local dev_self dev_parent
388
+ dev_self="$(stat -c %d -- "$dir" 2>/dev/null || stat -f %d -- "$dir" 2>/dev/null || echo x)"
389
+ dev_parent="$(stat -c %d -- "$dir/.." 2>/dev/null || stat -f %d -- "$dir/.." 2>/dev/null || echo y)"
390
+ [[ "$dev_self" != "x" && "$dev_self" == "$dev_parent" ]]
391
+ }
392
+
393
+ # --- Git / worktree arm ---------------------------------------------------------
394
+
395
+ # tc_classify_git_dir <dir> → one of:
396
+ # standalone-clone (.git is a real directory — report-only, never candidate)
397
+ # not-git (no .git at all)
398
+ # unverifiable (gitfile/gitdir/porcelain could not be resolved — RETAIN)
399
+ # unregistered (owning repo's registry read OK, path absent — quarantine)
400
+ # registered (registry read OK, path present — further gates apply)
401
+ # Prints the verdict AND sets TC_GITCLASS — hot-path callers invoke it directly
402
+ # (`tc_classify_git_dir "$d" >/dev/null; v="$TC_GITCLASS"`) to skip the fork.
403
+ # _tc_realpath <path> — canonicalize; empty output = unavailable (callers must
404
+ # treat that as UNVERIFIABLE, never fall back to the raw path: a non-
405
+ # canonicalized compare against git's canonical porcelain output misreads a
406
+ # registered worktree as unregistered → quarantine-move → registry damage).
407
+ _tc_realpath() {
408
+ command -v realpath >/dev/null 2>&1 || return 1
409
+ realpath -m -- "$1" 2>/dev/null
410
+ }
411
+
412
+ tc_classify_git_dir() {
413
+ local dir="$1" gf="$1/.git" gitdir main_gitdir
414
+ _gc() { TC_GITCLASS="$1"; printf '%s\n' "$1"; }
415
+ if [[ -d "$gf" && ! -L "$gf" ]]; then _gc "standalone-clone"; return 0; fi
416
+ [[ -f "$gf" && ! -L "$gf" ]] || { _gc "not-git"; return 0; }
417
+ gitdir="$(sed -n 's/^gitdir:[[:space:]]*//p' "$gf" 2>/dev/null | head -1)"
418
+ [[ -n "$gitdir" ]] || { _gc "unverifiable"; return 0; }
419
+ [[ "$gitdir" != /* ]] && gitdir="$dir/$gitdir"
420
+ gitdir="$(_tc_realpath "$gitdir")"
421
+ [[ -n "$gitdir" && -d "$gitdir" ]] || { _gc "unverifiable"; return 0; }
422
+ # gitdir shape is <main>/.git/worktrees/<name> — main repo gitdir is two up.
423
+ main_gitdir="${gitdir%/*}"; main_gitdir="${main_gitdir%/*}"
424
+ [[ -d "$main_gitdir/objects" || -f "$main_gitdir/HEAD" ]] || { _gc "unverifiable"; return 0; }
425
+ local real_dir listing
426
+ real_dir="$(_tc_realpath "$dir")"
427
+ [[ -n "$real_dir" ]] || { _gc "unverifiable"; return 0; }
428
+ if ! listing="$(_tc_git --git-dir="$main_gitdir" worktree list --porcelain 2>/dev/null)"; then
429
+ _gc "unverifiable"; return 0
430
+ fi
431
+ [[ -n "$listing" ]] || { _gc "unverifiable"; return 0; }
432
+ local wt found=0
433
+ while IFS= read -r wt; do
434
+ [[ -n "$wt" ]] || continue
435
+ if [[ "$(_tc_realpath "$wt")" == "$real_dir" ]]; then
436
+ found=1; break
437
+ fi
438
+ done <<< "$(printf '%s\n' "$listing" | sed -n 's/^worktree //p')"
439
+ if (( found == 1 )); then _gc "registered"; else _gc "unregistered"; fi
440
+ }
441
+
442
+ # tc_git_main_dir <dir> → prints the owning repo's gitdir (rc 1 if unverifiable).
443
+ tc_git_main_dir() {
444
+ local dir="$1" gf="$1/.git" gitdir
445
+ [[ -f "$gf" && ! -L "$gf" ]] || return 1
446
+ gitdir="$(sed -n 's/^gitdir:[[:space:]]*//p' "$gf" 2>/dev/null | head -1)"
447
+ [[ -n "$gitdir" ]] || return 1
448
+ [[ "$gitdir" != /* ]] && gitdir="$dir/$gitdir"
449
+ gitdir="$(_tc_realpath "$gitdir")"
450
+ [[ -n "$gitdir" && -d "$gitdir" ]] || return 1
451
+ dirname "$(dirname "$gitdir")"
452
+ }
453
+
454
+ # tc_worktree_clean <dir> — no modified AND no untracked AND no ignored files.
455
+ # `--ignored` is load-bearing: porcelain omits ignored paths, so a worktree
456
+ # holding only `.env.local` must NOT read clean.
457
+ tc_worktree_clean() {
458
+ local dir="$1" out
459
+ out="$(_tc_git -C "$dir" status --porcelain --ignored 2>/dev/null)" || return 1
460
+ [[ -z "$out" ]]
461
+ }
462
+
463
+ # tc_default_ref <main_gitdir> — the owning repo's default ref AS LAST FETCHED:
464
+ # origin/HEAD → the main worktree's checked-out branch → main/master. No fetch.
465
+ tc_default_ref() {
466
+ local main="$1" sym
467
+ sym="$(_tc_git --git-dir="$main" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null)"
468
+ [[ -n "$sym" ]] && { printf '%s' "$sym"; return 0; }
469
+ sym="$(_tc_git --git-dir="$main" symbolic-ref --quiet --short HEAD 2>/dev/null)"
470
+ [[ -n "$sym" ]] && { printf '%s' "$sym"; return 0; }
471
+ for sym in main master; do
472
+ _tc_git --git-dir="$main" rev-parse --verify --quiet "$sym" >/dev/null 2>&1 && { printf '%s' "$sym"; return 0; }
473
+ done
474
+ return 1
475
+ }
476
+
477
+ # tc_worktree_safe_to_remove <dir> — registered + clean + merged + no-unpushed
478
+ # + no live process inside. Caller applies the age floor separately.
479
+ tc_worktree_safe_to_remove() {
480
+ local dir="$1" main head upstream
481
+ main="$(tc_git_main_dir "$dir")" || return 1
482
+ tc_worktree_clean "$dir" || return 1
483
+ head="$(_tc_git -C "$dir" rev-parse HEAD 2>/dev/null)" || return 1
484
+ local defref
485
+ defref="$(tc_default_ref "$main")" || return 1
486
+ _tc_git --git-dir="$main" merge-base --is-ancestor "$head" "$defref" 2>/dev/null || return 1
487
+ upstream="$(_tc_git -C "$dir" rev-parse --abbrev-ref '@{upstream}' 2>/dev/null || true)"
488
+ if [[ -n "$upstream" ]]; then
489
+ local ahead
490
+ ahead="$(_tc_git -C "$dir" rev-list --count "${upstream}..HEAD" 2>/dev/null || echo 999)"
491
+ [[ "$ahead" == "0" ]] || return 1
492
+ fi
493
+ # A worktree with no recorded upstream and a detached-or-merged HEAD is fine;
494
+ # a named branch with no upstream is retained by the caller's risk floor.
495
+ if tc_tree_has_live_handles "$dir"; then return 1; fi
496
+ return 0
497
+ }
498
+
499
+ # --- Prefix allowlist (purge-only rung) ------------------------------------------
500
+ # Each row: prefix|required-subpath-glob|writer-citation. The subpath is the
501
+ # content signature that makes prefix matching attributable rather than the
502
+ # rejected name-only heuristic. FROZEN — new producers get markers, not rows.
503
+ # All globs are matched against paths RELATIVE to the candidate dir.
504
+
505
+ TC_DIR_ALLOWLIST=(
506
+ "rung2-archive.*|git-data-bootstrap.sh|tests/scripts/lib/git-data-birth-readiness-gate.sh"
507
+ "gdboot.*|rows|scripts/lib/git-data-boot-signal-poll.sh"
508
+ "infra-suites.*|_*.test.sh.meta|apps/web-platform/infra/run-registered-suites.sh"
509
+ "soleur-inc-*|.claude|.claude/hooks/lib/test-incident-sandbox.sh"
510
+ "mutbat.*|plugins|fixture producers (mutbat)"
511
+ "mutbat2.*|pristine|fixture producers (mutbat2)"
512
+ "pirgate-*|.git/config|plugins/soleur/test/ship-incident-pir-gate.test.ts"
513
+ "kbcov-*|engineering|plugins/soleur/test/kb-coverage.test.ts"
514
+ "gdpr-gate-incidents-*|.claude/.rule-incidents.jsonl|plugins/soleur/test/gdpr-gate.test.ts"
515
+ "deploygap-gate-*|.git/config|deploygap test fixtures"
516
+ "harness-discovery-*|plugin.json|harness discovery fixtures"
517
+ "cron-filing-fixture-*|justified.md|cron filing fixtures"
518
+ "luks-escrow.*|state/escrow.state|luks fixtures"
519
+ )
520
+
521
+ # Regular-file classes — signature is the name shape itself (no content reads).
522
+ TC_FILE_ALLOWLIST=(
523
+ "inngest-ci-*.sh|scripts/inngest fixtures"
524
+ "inngest-arm-*|inngest arm fixtures"
525
+ "pr-*-body.md|pr body scratch files"
526
+ )
527
+
528
+ # tc_prefix_class <dir> → prints `prefix:<prefix>` AND sets TC_PREFIX_CLASS
529
+ # when the basename matches an allowlist prefix AND the required subpath
530
+ # exists. rc 1 otherwise.
531
+ tc_prefix_class() {
532
+ local dir="$1" name row prefix sig
533
+ name="${dir##*/}"
534
+ for row in "${TC_DIR_ALLOWLIST[@]}"; do
535
+ prefix="${row%%|*}"; sig="${row#*|}"; sig="${sig%%|*}"
536
+ # shellcheck disable=SC2254 # prefix is a deliberate glob from the frozen list
537
+ case "$name" in $prefix)
538
+ compgen -G "$dir/$sig" >/dev/null 2>&1 && { TC_PREFIX_CLASS="prefix:$prefix"; printf '%s\n' "$TC_PREFIX_CLASS"; return 0; }
539
+ ;;
540
+ esac
541
+ done
542
+ return 1
543
+ }
544
+
545
+ # tc_file_class <file> → prints `file:<shape>` AND sets TC_FILE_CLASS for
546
+ # allowlisted regular files.
547
+ tc_file_class() {
548
+ local f="$1" name row prefix
549
+ [[ -f "$f" && ! -L "$f" ]] || return 1
550
+ [[ "$(tc_uid_of "$f")" == "$TC_UID" ]] || return 1
551
+ name="${f##*/}"
552
+ for row in "${TC_FILE_ALLOWLIST[@]}"; do
553
+ prefix="${row%%|*}"
554
+ # shellcheck disable=SC2254
555
+ case "$name" in $prefix) TC_FILE_CLASS="file:$prefix"; printf 'file:%s\n' "$prefix"; return 0 ;; esac
556
+ done
557
+ return 1
558
+ }
559
+
560
+ # --- Top-level classification -----------------------------------------------------
561
+
562
+ # tc_classify_entry <path> → prints the class; never mutates.
563
+ # ALSO sets the TC_CLASS global — a whole-base caller can invoke it as
564
+ # `tc_classify_entry "$p" >/dev/null; cls="$TC_CLASS"` and skip the
565
+ # command-substitution fork (81k entries × ~0.5ms is the difference between a
566
+ # ~20s scan and a ~60s one).
567
+ TC_CLASS=""
568
+ tc_classify_entry() {
569
+ local p="$1" name rc gc
570
+ name="${p##*/}"
571
+ _class() { TC_CLASS="$1"; printf '%s\n' "$1"; }
572
+ # Git-pointer rung FIRST: a `.git` FILE resolves the entry through the
573
+ # OWNING repo's worktree registry — verified attribution that must outrank
574
+ # every name heuristic INCLUDING the prefix allowlist (a signature collision
575
+ # inside a registered worktree — e.g. `soleur-inc-*` containing `.claude/` —
576
+ # must never hijack the tree out of the registry-aware arm). Without this
577
+ # rung the measured backlog class (`tmp.*` dirs bearing `.git`, ~1.5k on the
578
+ # reference host) would be invisible to every consumer. `.git` DIRECTORIES
579
+ # (standalone clones) keep protected-first ordering — report-only either way.
580
+ if [[ -d "$p" && -f "$p/.git" && ! -L "$p/.git" ]]; then
581
+ tc_classify_git_dir "$p" >/dev/null; gc="$TC_GITCLASS"
582
+ case "$gc" in
583
+ registered|unregistered|unverifiable) _class "worktree:$gc"; return 0 ;;
584
+ esac
585
+ fi
586
+ tc_is_protected "$p" && { _class "protected"; return 0; }
587
+ if [[ -f "$p" && ! -d "$p" ]]; then
588
+ if tc_file_class "$p" >/dev/null 2>&1; then _class "$TC_FILE_CLASS"; else _class "unattributable"; fi
589
+ return 0
590
+ fi
591
+ [[ -d "$p" ]] || { _class "unattributable"; return 0; }
592
+ # marker rung — rc 2 (marker present but unverifiable) VETOES the schema
593
+ # rung: a rejected declaration means every recorded identity is untrusted.
594
+ # The `if` wrapper is load-bearing: a plain call returning nonzero aborts
595
+ # under a caller's set -e before the rc can be read.
596
+ if tc_marker_owner_pid "$p" >/dev/null 2>&1; then rc=0; else rc=$?; fi
597
+ case "$rc" in
598
+ 0) _class "marker:$TC_PID"; return 0 ;;
599
+ 2) _class "unattributable"; return 0 ;;
600
+ esac
601
+ # schema rung — only when NO marker exists (a vetoed marker already returned)
602
+ if tc_schema_owner_pid "$name" >/dev/null 2>&1; then _class "schema:$TC_PID"; return 0; fi
603
+ # prefix rung — BEFORE standalone-clone: `.git/config`-signature fixture
604
+ # classes (pirgate-*, deploygap-gate-*) are attributable by content
605
+ # signature; letting the clone rung preempt them made those rows dead code.
606
+ if tc_prefix_class "$p" >/dev/null 2>&1; then _class "$TC_PREFIX_CLASS"; return 0; fi
607
+ # standalone-clone rung (.git dir; report-only)
608
+ tc_classify_git_dir "$p" >/dev/null; gc="$TC_GITCLASS"
609
+ [[ "$gc" == "standalone-clone" ]] && { _class "standalone-clone"; return 0; }
610
+ # empty rung
611
+ tc_is_reapable_empty "$p" && { _class "empty"; return 0; }
612
+ _class "unattributable"
613
+ }
614
+
615
+ # --- Retain-since stamps ----------------------------------------------------------
616
+ # Bound the retentive arm: an entry retained as unverifiable gets a stamp; once
617
+ # the stamp is older than TC_RETAIN_FLOOR_MIN the caller escalates it to the
618
+ # operator-decision list instead of re-reporting forever.
619
+
620
+ tc_retain_stamp() { # <path> — record/refresh first-seen; prints age in min
621
+ local p="$1" sf now mtime
622
+ sf="$TC_RETAIN_DIR/$(printf '%s' "$p" | sha256sum 2>/dev/null | cut -d' ' -f1 || printf '%s' "$p" | md5sum | cut -d' ' -f1)"
623
+ now="$(date +%s)"
624
+ if [[ ! -f "$sf" ]]; then
625
+ mkdir -p -- "$TC_RETAIN_DIR" 2>/dev/null || { echo 0; return 0; }
626
+ printf '%s\t%s\n' "$now" "$p" > "$sf" 2>/dev/null || { echo 0; return 0; }
627
+ echo 0; return 0
628
+ fi
629
+ mtime="$(stat -c %Y -- "$sf" 2>/dev/null || stat -f %m -- "$sf" 2>/dev/null || echo "$now")"
630
+ echo $(( (now - mtime) / 60 ))
631
+ }
632
+
633
+ # --- Quarantine move ---------------------------------------------------------------
634
+
635
+ # tc_tree_has_gitref <dir> — any `.git` file OR dir beneath the tree's top
636
+ # level. A nested worktree or clone makes the whole tree registry-relevant:
637
+ # deleting or quarantining it leaves the owning repo's .git/worktrees/<id>
638
+ # metadata dangling (prunable) and destroys dirty content no conjunct
639
+ # inspected. Reapers must retain such trees — the worktree arm owns them.
640
+ tc_tree_has_gitref() {
641
+ [[ -d "$1" ]] || return 1
642
+ # mindepth 2: the candidate's OWN .git (a worktree pointer file) is at depth
643
+ # 1 — registry-handled by the worktree arm, not a nested ref. Nested refs
644
+ # live at depth >= 2. head -1 short-circuits; find may take SIGPIPE — rc
645
+ # is ignored anyway.
646
+ [[ -n "$(find "$1" -mindepth 2 -name .git -print 2>/dev/null | head -1)" ]]
647
+ }
648
+
649
+ # tc_tree_has_mount <dir> — any entry on a DIFFERENT device than the tree's
650
+ # root. A bind/loop mount inside a candidate is live foreign filesystem
651
+ # content: `find -delete` would empty it, `mv` would relocate the mountpoint
652
+ # into quarantine where the drain later deletes through it. Fail CLOSED: a
653
+ # probe that can't see devices answers "has mount" (retain).
654
+ tc_tree_has_mount() {
655
+ local dir="$1" dev lines
656
+ [[ -d "$dir" ]] || return 1 # a regular file can't contain a mount
657
+ dev="$(stat -c %d -- "$dir" 2>/dev/null || stat -f %d -- "$dir" 2>/dev/null)"
658
+ [[ -n "$dev" ]] || return 0
659
+ # %D is the device number (find's %d is depth — same letter, wrong field).
660
+ lines="$(find "$dir" -mindepth 1 -printf '%D\n' 2>/dev/null)"
661
+ if [[ -z "$lines" ]]; then
662
+ # Empty tree → no mount possible. Non-empty tree with a probe that
663
+ # produced nothing (no -printf) → can't verify → treat as mounted.
664
+ [[ -n "$(ls -A -- "$dir" 2>/dev/null)" ]]
665
+ return
666
+ fi
667
+ grep -qvx -- "$dev" <<<"$lines"
668
+ }
669
+
670
+ # tc_quar_age_min <entry> — minutes since the entry entered quarantine.
671
+ # rename(2) bumps ctime, so %Z records quarantine-ARRIVAL. Aging on content
672
+ # mtime instead (mv preserves it) collapses the recovery window to ~0 for the
673
+ # stale backlog that is quarantine's main population. Missing/unreadable ctime
674
+ # → 0 (fresh → retained; the recovery window errs long, never short).
675
+ tc_quar_age_min() {
676
+ local e="$1" ct now
677
+ ct="$(stat -c %Z -- "$e" 2>/dev/null || stat -f %c -- "$e" 2>/dev/null || echo 0)"
678
+ now="$(date +%s)"
679
+ [[ "$ct" =~ ^[0-9]+$ && "$ct" -gt 0 && "$ct" -le "$now" ]] || { echo 0; return 0; }
680
+ echo $(( (now - ct) / 60 ))
681
+ }
682
+
683
+ # tc_ledger_append <action> <class> <orig> <dest> — ONE ledger for every
684
+ # destructive action across every consumer (purge, Reaper 3, session sweep),
685
+ # so --restore's replay picture is complete. TSV fields: a path containing a
686
+ # tab or newline cannot be recorded faithfully — drop loudly rather than
687
+ # corrupt the file's row structure.
688
+ tc_ledger_append() {
689
+ local line
690
+ case "$1$2$3$4" in
691
+ *$'\t'*|*$'\n'*)
692
+ printf 'LEDGER-DROP: tab/newline in field — action NOT recorded: %s %s\n' "$1" "$3" >&2
693
+ return 0 ;;
694
+ esac
695
+ mkdir -p -- "$(dirname "$TC_LEDGER")" 2>/dev/null || true
696
+ line="$(date -u +%Y-%m-%dT%H:%M:%SZ)"$'\t'"$1"$'\t'"$2"$'\t'"$3"$'\t'"$4"
697
+ if ! printf '%s\n' "$line" >> "$TC_LEDGER" 2>/dev/null; then
698
+ printf 'LEDGER-DROP: cannot append %s — action NOT recorded: %s\n' "$TC_LEDGER" "$line" >&2
699
+ printf 'LEDGER-DROP: %s\n' "$line"
700
+ fi
701
+ }
702
+
703
+ # tc_drain_quarantine <base> <dry> <scratch_ttl_min> <wt_ttl_min> [<deadline_epoch> [<max_entries>]] — the ONLY
704
+ # delete permitted inside soleur-quarantine.<uid>/: entries whose QUARANTINE
705
+ # dwell exceeds the class TTL (subdir name encodes it). A symlinked or
706
+ # foreign-owned quarantine root/class dir refuses outright — a squatter on a
707
+ # world-writable base must not aim the drain at an arbitrary tree. Sets
708
+ # TC_DRAINED and TC_DRAINED_BYTES (both RESET on every call — a caller looping over
709
+ # bases sums them itself).
710
+ #
711
+ # Optional trailing arguments (#9677), both 0 = unbounded so the guard and the purge, which pass
712
+ # four arguments, are unchanged: <deadline_epoch> stops the drain between top-level entries once
713
+ # the clock reaches it, and <max_entries> caps how many entries one call may delete. Both are checked
714
+ # BETWEEN entries only: the mount probe, `du -sk` and `find -delete` of one entry cannot be interrupted,
715
+ # so a single very large entry can overrun the deadline. The enumeration is pre-filtered with
716
+ # `find -cmin +(ttl-1)` (looser than the authoritative tc_quar_age_min re-check below), so unexpired
717
+ # entries cost one find, not a fork each (measured 12 ms/entry otherwise). An entry whose delete is cut
718
+ # short is NOT retried promptly: deleting its children advances its own ctime, so its TTL restarts and
719
+ # the next call sees it as fresh (no data loss — the error direction is retention).
720
+ TC_DRAINED=0
721
+ TC_DRAINED_BYTES=0
722
+ tc_drain_quarantine() {
723
+ local base="$1" dry="${2:-0}" sttl="${3:-10080}" wttl="${4:-43200}" deadline="${5:-0}" cap="${6:-0}"
724
+ local qroot cls ttl e kb attempts=0
725
+ TC_DRAINED=0; TC_DRAINED_BYTES=0
726
+ [[ "$deadline" =~ ^[0-9]+$ ]] || deadline=0
727
+ [[ "$cap" =~ ^[0-9]+$ ]] || cap=0
728
+ # TTLs arrive from env (SOLEUR_PURGE_QUAR_*_TTL_MIN, TMPFS_GUARD_QUAR_*_TTL_MIN) and are used in
729
+ # arithmetic below: `-1`, `abc` (evaluates as an unset variable = 0) and `7d` (arithmetic error)
730
+ # all DRAIN MORE or abort. Only plain digits are valid; anything else falls back to the default
731
+ # (never a shorter window) with a WARN. `10#` pins decimal so `08` is not an octal error.
732
+ [[ "$sttl" =~ ^[0-9]{1,9}$ ]] || { printf 'WARN tc_drain_quarantine: invalid scratch TTL %q — using the default 10080 min\n' "$sttl" >&2; sttl=10080; }
733
+ [[ "$wttl" =~ ^[0-9]{1,9}$ ]] || { printf 'WARN tc_drain_quarantine: invalid worktrees TTL %q — using the default 43200 min\n' "$wttl" >&2; wttl=43200; }
734
+ sttl=$((10#$sttl)); wttl=$((10#$wttl))
735
+ qroot="$base/soleur-quarantine.$TC_UID"
736
+ [[ -d "$qroot" && ! -L "$qroot" ]] || return 0
737
+ [[ "$(tc_uid_of "$qroot")" == "$TC_UID" ]] || return 0
738
+ for cls in "$qroot"/*/; do
739
+ cls="${cls%/}"
740
+ [[ -d "$cls" && ! -L "$cls" ]] || continue
741
+ [[ "$(tc_uid_of "$cls")" == "$TC_UID" ]] || continue
742
+ case "${cls##*/}" in worktrees) ttl="$wttl" ;; *) ttl="$sttl" ;; esac
743
+ while IFS= read -r -d '' e; do
744
+ [[ -e "$e" && ! -L "$e" ]] || continue # planted link — never follow
745
+ if (( deadline > 0 )) && (( "${EPOCHSECONDS:-$(date +%s)}" >= deadline )); then return 0; fi
746
+ if (( cap > 0 )) && (( attempts >= cap )); then return 0; fi
747
+ (( "$(tc_quar_age_min "$e")" >= ttl )) || continue
748
+ tc_tree_has_mount "$e" && continue # never delete into a mount
749
+ if [[ "$dry" == "1" ]]; then
750
+ printf 'would drain %s\n' "$e"; continue
751
+ fi
752
+ attempts=$((attempts + 1))
753
+ # du -sk (portable; -b is GNU-only) BEFORE the delete — the bytes are gone afterwards. An
754
+ # unreadable size counts as 0 rather than aborting the drain.
755
+ # `|| true` is load-bearing: du exits 1 on any unreadable subtree or a file that vanishes mid-walk,
756
+ # and under pipefail that fails the assignment — the guard and the purge call this function bare
757
+ # under `set -e`, so without it one such entry would abort them on every run (#9677 review).
758
+ kb="$(du -skx -- "$e" 2>/dev/null | cut -f1)" || true
759
+ [[ "$kb" =~ ^[0-9]+$ ]] || kb=0
760
+ if find "$e" -xdev -depth -delete 2>/dev/null; then
761
+ tc_ledger_append "drain" "${cls##*/}" "$e" "-"
762
+ TC_DRAINED=$((TC_DRAINED + 1))
763
+ TC_DRAINED_BYTES=$((TC_DRAINED_BYTES + kb * 1024))
764
+ fi
765
+ done < <(find "$cls" -mindepth 1 -maxdepth 1 -cmin "+$(( ttl > 0 ? ttl - 1 : 0 ))" -print0 2>/dev/null)
766
+ done
767
+ return 0
768
+ }
769
+
770
+ # tc_file_in_use <file> — a regular FILE held open: any /proc/*/fd symlink or
771
+ # environ value naming it. For the allowlisted file classes a >24h-old file
772
+ # can still be mid-write (pr-*-body.md consumed by `gh -F`); the top-level map
773
+ # marks directories, so files need their own probe.
774
+ tc_file_in_use() {
775
+ local f="$1"
776
+ [[ -d "$TC_PROC" ]] || return 0 # can't check → assume live (fail closed)
777
+ [[ -n "$(find "$TC_PROC"/[0-9]*/fd -lname "$f" -print -quit 2>/dev/null)" ]] && return 0
778
+ grep -lqsaF -- "$f" "$TC_PROC"/[0-9]*/environ 2>/dev/null && return 0
779
+ return 1
780
+ }
781
+
782
+ # tc_reap_decide <dir> <age_floor_min> — the SHARED "safe to reap" conjunct
783
+ # chain for declared-owner candidates; single-sourced so consumers cannot
784
+ # diverge on the safety gates. Prints a verdict token:
785
+ # reap every conjunct passed — caller may delete/quarantine
786
+ # retain:<why> a conjunct failed or evidence was unverifiable
787
+ # Sets TC_DECL_KIND=marker|schema on success — consumers use it for the
788
+ # disposition split (schema-named roots may direct-delete on tmpfs; marker
789
+ # dirs quarantine on every base — a marker is a self-declared file, weaker
790
+ # attribution than the creation-time schema).
791
+ tc_reap_decide() {
792
+ local dir="$1" floor="$2" rc
793
+ TC_DECL_KIND=""
794
+ # `if` context is load-bearing: a plain call's nonzero rc aborts under
795
+ # set -e before it can be inspected.
796
+ if tc_owner_pid_verify "$dir" >/dev/null 2>&1; then rc=0; else rc=$?; fi
797
+ if (( rc == 2 )); then printf 'retain:unverifiable'; return 0
798
+ elif (( rc != 0 )); then printf 'retain:no-owner'; return 0
799
+ fi
800
+ tc_owner_alive "$TC_PID" && { printf 'retain:owner-live'; return 0; }
801
+ tc_inuse_map_refresh || true
802
+ tc_tree_has_live_handles "$dir" && { printf 'retain:live-handles'; return 0; }
803
+ (( "$(tc_tree_age_min "$dir")" >= floor )) || { printf 'retain:fresh'; return 0; }
804
+ tc_tree_has_gitref "$dir" && { printf 'retain:nested-git'; return 0; }
805
+ tc_tree_has_mount "$dir" && { printf 'retain:nested-mount'; return 0; }
806
+ printf 'reap'
807
+ }
808
+
809
+ # --- Quarantine move ---------------------------------------------------------------
810
+
811
+ # tc_quarantine_move <path> <base> <class-subdir>
812
+ # mv into <base>/soleur-quarantine.<uid>/<class>/ with the pre-move checks:
813
+ # quarantine root not a symlink, owned by TC_UID, same-device (EXDEV refuses —
814
+ # mv, never copy), basename collision → numeric suffix with `mv -n` closing the
815
+ # check-then-act window. Prints the final quarantine path. rc 1 = refused
816
+ # (nothing moved).
817
+ tc_quarantine_move() {
818
+ local src="$1" base="$2" cls="$3"
819
+ local qroot qdir
820
+ qroot="$base/soleur-quarantine.$TC_UID"; qdir="$qroot/$cls"
821
+ local src_dev base_dev
822
+ src_dev="$(stat -c %d -- "$src" 2>/dev/null || stat -f %d -- "$src" 2>/dev/null || echo x)"
823
+ base_dev="$(stat -c %d -- "$base" 2>/dev/null || stat -f %d -- "$base" 2>/dev/null || echo y)"
824
+ [[ "$src_dev" != "x" && "$src_dev" == "$base_dev" ]] || return 1 # EXDEV → refuse
825
+ [[ ! -L "$qroot" && ! -L "$qdir" ]] || return 1
826
+ # A foreign-owned qroot means a squatter pre-created our quarantine dir —
827
+ # moved trees (which can carry .env-local-class secrets) would land inside
828
+ # an attacker-owned root, and restore would replay tampered content.
829
+ if [[ -d "$qroot" ]] && [[ "$(tc_uid_of "$qroot")" != "$TC_UID" ]]; then return 1; fi
830
+ mkdir -p -- "$qdir" 2>/dev/null || return 1
831
+ chmod 0700 "$qroot" "$qdir" 2>/dev/null || return 1
832
+ local dest name i=0
833
+ name="${src##*/}"
834
+ dest="$qdir/$name"
835
+ while [[ -e "$dest" || -L "$dest" ]]; do
836
+ i=$((i + 1)); dest="$qdir/$name.$i"
837
+ done
838
+ # `mv -n` returns success even when it skips (a racer claimed $dest in the
839
+ # check-then-act gap) — verify the move actually happened.
840
+ mv -n -- "$src" "$dest" 2>/dev/null || return 1
841
+ [[ ! -e "$src" && -e "$dest" ]] || return 1
842
+ printf '%s' "$dest"
843
+ }