forge-workflow 0.0.10 → 0.1.0-beta.3

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 (468) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +82 -5
  5. package/.forge/hooks/forge-native-hook.js +431 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +709 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +175 -0
  11. package/README.md +275 -365
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +532 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +121 -0
  29. package/docs/guides/SUPPORT.md +190 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +214 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +155 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/activation/ensure-forge-home.js +135 -0
  67. package/lib/adapter-cli.js +307 -0
  68. package/lib/adapters/beads-issue-adapter.js +127 -0
  69. package/lib/adapters/beads-kernel-compat.js +1109 -0
  70. package/lib/adapters/greptile-review-adapter.js +141 -0
  71. package/lib/adapters/kernel-issue-adapter.js +101 -0
  72. package/lib/adapters/pr-state-adapter.js +484 -0
  73. package/lib/adoption-profiles.js +139 -0
  74. package/lib/agents/README.md +2 -6
  75. package/lib/agents/claude.plugin.json +3 -8
  76. package/lib/agents/codex.plugin.json +9 -1
  77. package/lib/agents/cursor.plugin.json +2 -6
  78. package/lib/agents/hermes.plugin.json +22 -0
  79. package/lib/agents-config.js +39 -1236
  80. package/lib/audit-evidence.js +282 -0
  81. package/lib/beads-detect.js +60 -0
  82. package/lib/beads-nudge.js +91 -0
  83. package/lib/beads-setup.js +121 -0
  84. package/lib/beads-sync-scaffold.js +25 -101
  85. package/lib/codex-skills.js +51 -1
  86. package/lib/commands/_aliases.js +248 -0
  87. package/lib/commands/_issue.js +780 -77
  88. package/lib/commands/_manifest.js +93 -0
  89. package/lib/commands/_registry.js +99 -34
  90. package/lib/commands/_resolve-command-opts.js +230 -0
  91. package/lib/commands/_serve-security.js +270 -0
  92. package/lib/commands/adapter.js +12 -0
  93. package/lib/commands/add.js +118 -0
  94. package/lib/commands/audit.js +70 -0
  95. package/lib/commands/blocked.js +5 -0
  96. package/lib/commands/board.js +64 -0
  97. package/lib/commands/claim.js +21 -2
  98. package/lib/commands/claims.js +7 -0
  99. package/lib/commands/clean.js +485 -75
  100. package/lib/commands/close.js +2 -2
  101. package/lib/commands/comment.js +5 -0
  102. package/lib/commands/control.js +148 -0
  103. package/lib/commands/create.js +2 -2
  104. package/lib/commands/dev.js +185 -7
  105. package/lib/commands/doc-gate.js +336 -0
  106. package/lib/commands/doctor.js +156 -0
  107. package/lib/commands/explain.js +15 -0
  108. package/lib/commands/export.js +237 -0
  109. package/lib/commands/gate.js +209 -0
  110. package/lib/commands/hooks.js +377 -0
  111. package/lib/commands/inbox.js +118 -0
  112. package/lib/commands/init.js +604 -0
  113. package/lib/commands/insights.js +79 -0
  114. package/lib/commands/issue.js +12 -1
  115. package/lib/commands/issues.js +17 -0
  116. package/lib/commands/lint.js +5 -0
  117. package/lib/commands/list.js +2 -2
  118. package/lib/commands/memory.js +81 -0
  119. package/lib/commands/merge.js +312 -0
  120. package/lib/commands/migrate.js +362 -0
  121. package/lib/commands/new.js +12 -0
  122. package/lib/commands/options.js +241 -0
  123. package/lib/commands/orient.js +13 -0
  124. package/lib/commands/orphans.js +5 -0
  125. package/lib/commands/patch.js +67 -0
  126. package/lib/commands/plan.js +481 -29
  127. package/lib/commands/pr.js +88 -0
  128. package/lib/commands/preflight.js +211 -0
  129. package/lib/commands/prime.js +13 -0
  130. package/lib/commands/push.js +135 -2
  131. package/lib/commands/ready.js +2 -2
  132. package/lib/commands/recall.js +171 -0
  133. package/lib/commands/recap.js +75 -0
  134. package/lib/commands/recommend.js +0 -1
  135. package/lib/commands/release.js +104 -0
  136. package/lib/commands/remember.js +140 -0
  137. package/lib/commands/role.js +99 -0
  138. package/lib/commands/serve.js +581 -0
  139. package/lib/commands/setup.js +900 -971
  140. package/lib/commands/shepherd.js +501 -0
  141. package/lib/commands/ship.js +59 -1
  142. package/lib/commands/show.js +2 -2
  143. package/lib/commands/stage.js +192 -0
  144. package/lib/commands/stale.js +5 -0
  145. package/lib/commands/status.js +158 -21
  146. package/lib/commands/sync.js +34 -46
  147. package/lib/commands/team.js +4 -1
  148. package/lib/commands/test.js +43 -27
  149. package/lib/commands/update.js +2 -2
  150. package/lib/commands/upgrade.js +47 -0
  151. package/lib/commands/validate.js +43 -18
  152. package/lib/commands/worktree.js +362 -99
  153. package/lib/config-writer.js +202 -0
  154. package/lib/control-plane.js +236 -0
  155. package/lib/core/runtime-graph.js +977 -0
  156. package/lib/dep-guard/keyword-ripple.js +2 -2
  157. package/lib/deprecated-sync-cleanup.js +362 -0
  158. package/lib/detect-agent.js +2 -28
  159. package/lib/detect-worktree.js +35 -9
  160. package/lib/doc-gate/declaration.js +177 -0
  161. package/lib/doc-gate/detect.js +289 -0
  162. package/lib/doc-gate/gate.js +375 -0
  163. package/lib/doc-gate/okf-config.js +128 -0
  164. package/lib/doc-gate/okf.js +429 -0
  165. package/lib/docs-command.js +1161 -6
  166. package/lib/forge-issues.js +382 -11
  167. package/lib/forge-lock.js +262 -0
  168. package/lib/gate-events.js +192 -0
  169. package/lib/global-flags.js +104 -0
  170. package/lib/greptile-match.js +7 -63
  171. package/lib/grounding/context-events.js +230 -0
  172. package/lib/grounding/read-first.js +112 -0
  173. package/lib/harness-capability-matrix.js +380 -0
  174. package/lib/hook-global-installer.js +347 -0
  175. package/lib/hook-renderer.js +541 -0
  176. package/lib/inbox.js +391 -0
  177. package/lib/insights.js +397 -0
  178. package/lib/issue-adapter.js +156 -0
  179. package/lib/issue-backend.js +145 -0
  180. package/lib/issue-render.js +220 -0
  181. package/lib/kernel/backing-issue.js +311 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/owned-kernel.js +43 -0
  192. package/lib/kernel/planning-buckets-schema.js +109 -0
  193. package/lib/kernel/projection-jsonl-writer.js +450 -0
  194. package/lib/kernel/readiness-model.js +329 -0
  195. package/lib/kernel/schema.js +356 -0
  196. package/lib/kernel/sqlite-driver.js +2540 -0
  197. package/lib/kernel/taxonomy-validator.js +394 -0
  198. package/lib/lefthook-check.js +3 -2
  199. package/lib/lefthook-wiring.js +413 -0
  200. package/lib/mcp-config-renderer.js +288 -0
  201. package/lib/memory/graphiti-mcp.js +106 -0
  202. package/lib/memory/router.js +387 -0
  203. package/lib/memory/typed-api.js +102 -0
  204. package/lib/memory-digest.js +195 -0
  205. package/lib/merge-rules.js +395 -0
  206. package/lib/migrate-dry-run.js +466 -0
  207. package/lib/orientation.js +863 -0
  208. package/lib/package-manager-remediation.js +103 -0
  209. package/lib/package-root.js +381 -0
  210. package/lib/patch-intent.js +890 -0
  211. package/lib/plugin-catalog.js +3 -4
  212. package/lib/plugin-manager.js +0 -5
  213. package/lib/pr-bundle.js +186 -0
  214. package/lib/pr-monitor/auto-actions.js +175 -0
  215. package/lib/pr-monitor/differ.js +195 -0
  216. package/lib/pr-monitor/digest.js +206 -0
  217. package/lib/pr-monitor/events.js +0 -0
  218. package/lib/pr-monitor/gather.js +124 -0
  219. package/lib/pr-monitor/journal.js +299 -0
  220. package/lib/pr-monitor/monitor.js +146 -0
  221. package/lib/pr-monitor/render-sticky.js +192 -0
  222. package/lib/pr-monitor/upsert-sticky.js +169 -0
  223. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  224. package/lib/pr-monitor/watch.js +247 -0
  225. package/lib/pr-pull.js +1314 -0
  226. package/lib/pr-shepherd.js +494 -0
  227. package/lib/pr-state-validator.js +59 -0
  228. package/lib/preflight/gates.js +237 -0
  229. package/lib/preflight/runner.js +116 -0
  230. package/lib/project-discovery.js +0 -53
  231. package/lib/project-memory.js +99 -497
  232. package/lib/protected-path-manifest.js +281 -0
  233. package/lib/protected-state-surfaces.js +387 -0
  234. package/lib/release-readiness.js +2105 -0
  235. package/lib/reset.js +59 -45
  236. package/lib/review-adapter.js +68 -0
  237. package/lib/rules-sync.js +260 -0
  238. package/lib/runtime-health.js +241 -20
  239. package/lib/safety-config-renderer.js +268 -0
  240. package/lib/setup-action-log.js +1 -7
  241. package/lib/setup.js +27 -65
  242. package/lib/shell-utils.js +76 -6
  243. package/lib/skills-sync.js +330 -0
  244. package/lib/smart-status/scoring.js +17 -3
  245. package/lib/status/beads-snapshot.js +45 -2
  246. package/lib/status/presenter.js +169 -18
  247. package/lib/status/snapshot.js +186 -0
  248. package/lib/sync-backend.js +202 -0
  249. package/lib/untrusted-content.js +52 -0
  250. package/lib/upgrade-safety.js +251 -0
  251. package/lib/workflow/enforce-stage.js +351 -45
  252. package/lib/workflow/stage-transition.js +115 -0
  253. package/lib/workflow/stages.js +30 -6
  254. package/lib/workflow/state-manager.js +11 -22
  255. package/lib/workflow/state.js +23 -1
  256. package/lib/workflow-profiles.js +17 -5
  257. package/package.json +37 -35
  258. package/rules/documentation.md +19 -0
  259. package/rules/kernel-tracking.md +26 -0
  260. package/rules/security.md +22 -0
  261. package/rules/tdd.md +20 -0
  262. package/rules/workflow.md +27 -0
  263. package/scripts/auto-backing-issue.js +47 -0
  264. package/scripts/beads-context.sh +81 -57
  265. package/scripts/beads-upgrade-smoke.sh +24 -3
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard.sh +22 -3
  272. package/scripts/file-index.sh +3 -0
  273. package/scripts/forge-team/lib/claim.sh +34 -18
  274. package/scripts/forge-team/lib/dashboard.sh +61 -86
  275. package/scripts/forge-team/lib/epic.sh +99 -263
  276. package/scripts/forge-team/lib/hooks.sh +26 -28
  277. package/scripts/forge-team/lib/identity.sh +4 -4
  278. package/scripts/forge-team/lib/sync-github.sh +49 -84
  279. package/scripts/forge-team/lib/verify.sh +93 -83
  280. package/scripts/forge-team/lib/workload.sh +41 -65
  281. package/scripts/forge-team/tests/claim.test.sh +25 -19
  282. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  283. package/scripts/forge-team/tests/epic.test.sh +52 -71
  284. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  285. package/scripts/forge-team/tests/identity.test.sh +3 -3
  286. package/scripts/forge-team/tests/integration.test.sh +44 -66
  287. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  288. package/scripts/forge-team/tests/verify.test.sh +37 -46
  289. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  290. package/scripts/forge-team/tests/workload.test.sh +32 -66
  291. package/scripts/gen-command-manifest.js +153 -0
  292. package/scripts/gen-embedded-assets.mjs +129 -0
  293. package/scripts/install.ps1 +139 -0
  294. package/scripts/install.sh +268 -0
  295. package/scripts/lib/release-asset.mjs +84 -0
  296. package/scripts/parity-check.mjs +145 -0
  297. package/scripts/parity-check.test.mjs +58 -0
  298. package/scripts/pin-agentic-workflow-images.js +112 -0
  299. package/scripts/pr-auto-actions.js +93 -0
  300. package/scripts/pr-coordinator.sh +3 -0
  301. package/scripts/pr-verdict-label.js +50 -0
  302. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  303. package/scripts/preflight.sh +21 -94
  304. package/scripts/protected-state-check.js +104 -0
  305. package/scripts/smart-status.sh +60 -57
  306. package/scripts/spikes/config-race-bench.js +111 -0
  307. package/scripts/spikes/harness-capability-matrix.js +13 -0
  308. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  309. package/scripts/spikes/protected-path-manifest.js +20 -0
  310. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  311. package/scripts/sync-agent-skills.js +62 -0
  312. package/scripts/sync-utils.sh +3 -0
  313. package/scripts/test-ci-shard.js +13 -6
  314. package/scripts/test.js +95 -12
  315. package/skills/claim-safety/SKILL.md +102 -0
  316. package/skills/claim-safety/evals/evals.json +46 -0
  317. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  318. package/skills/dev/evals/evals.json +50 -0
  319. package/skills/hermes-forge/SKILL.md +185 -0
  320. package/skills/hermes-forge/evals/evals.json +46 -0
  321. package/skills/issue-basics/SKILL.md +111 -0
  322. package/skills/issue-basics/evals/evals.json +46 -0
  323. package/skills/kernel/SKILL.md +166 -0
  324. package/skills/kernel/evals/evals.json +50 -0
  325. package/skills/memory/SKILL.md +102 -0
  326. package/skills/parallel-deep-research/SKILL.md +14 -11
  327. package/skills/parallel-deep-research/evals/evals.json +11 -27
  328. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  329. package/skills/plan/evals/evals.json +42 -0
  330. package/skills/research/SKILL.md +195 -0
  331. package/skills/research/evals/evals.json +42 -0
  332. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  333. package/skills/review/evals/evals.json +42 -0
  334. package/skills/rollback/SKILL.md +110 -0
  335. package/skills/rollback/evals/evals.json +46 -0
  336. package/skills/rollback/references/methods.md +204 -0
  337. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  338. package/skills/shepherd/SKILL.md +66 -0
  339. package/skills/shepherd/evals/evals.json +42 -0
  340. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  341. package/skills/ship/evals/evals.json +42 -0
  342. package/skills/smith/SKILL.md +142 -0
  343. package/skills/smith/evals/evals.json +46 -0
  344. package/skills/smith/references/autonomy-and-gates.md +94 -0
  345. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  346. package/skills/sonarcloud/evals/evals.json +46 -0
  347. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  348. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  349. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  350. package/skills/status/evals/evals.json +50 -0
  351. package/skills/triage-ready/SKILL.md +121 -0
  352. package/skills/triage-ready/evals/evals.json +42 -0
  353. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  354. package/skills/validate/evals/evals.json +42 -0
  355. package/skills/verify/SKILL.md +299 -0
  356. package/skills/verify/evals/evals.json +50 -0
  357. package/.claude/commands/dev.md +0 -345
  358. package/.claude/commands/plan.md +0 -566
  359. package/.claude/commands/premerge.md +0 -186
  360. package/.claude/commands/research.md +0 -42
  361. package/.claude/commands/review.md +0 -451
  362. package/.claude/commands/rollback.md +0 -721
  363. package/.claude/commands/ship.md +0 -213
  364. package/.claude/commands/sonarcloud.md +0 -152
  365. package/.claude/commands/status.md +0 -90
  366. package/.claude/commands/validate.md +0 -288
  367. package/.claude/commands/verify.md +0 -269
  368. package/.claude/rules/workflow.md +0 -121
  369. package/.cline/workflows/dev.md +0 -342
  370. package/.cline/workflows/plan.md +0 -563
  371. package/.cline/workflows/premerge.md +0 -183
  372. package/.cline/workflows/research.md +0 -39
  373. package/.cline/workflows/review.md +0 -448
  374. package/.cline/workflows/rollback.md +0 -718
  375. package/.cline/workflows/ship.md +0 -210
  376. package/.cline/workflows/sonarcloud.md +0 -146
  377. package/.cline/workflows/status.md +0 -87
  378. package/.cline/workflows/validate.md +0 -285
  379. package/.cline/workflows/verify.md +0 -266
  380. package/.codex/config.toml +0 -11
  381. package/.codex/skills/dev/SKILL.md +0 -345
  382. package/.codex/skills/plan/SKILL.md +0 -566
  383. package/.codex/skills/premerge/SKILL.md +0 -186
  384. package/.codex/skills/research/SKILL.md +0 -42
  385. package/.codex/skills/review/SKILL.md +0 -451
  386. package/.codex/skills/rollback/SKILL.md +0 -721
  387. package/.codex/skills/ship/SKILL.md +0 -213
  388. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  389. package/.codex/skills/status/SKILL.md +0 -90
  390. package/.codex/skills/validate/SKILL.md +0 -288
  391. package/.codex/skills/verify/SKILL.md +0 -269
  392. package/.cursor/commands/dev.md +0 -342
  393. package/.cursor/commands/plan.md +0 -563
  394. package/.cursor/commands/premerge.md +0 -183
  395. package/.cursor/commands/research.md +0 -39
  396. package/.cursor/commands/review.md +0 -448
  397. package/.cursor/commands/ship.md +0 -210
  398. package/.cursor/commands/sonarcloud.md +0 -146
  399. package/.cursor/commands/status.md +0 -87
  400. package/.cursor/commands/validate.md +0 -285
  401. package/.cursor/commands/verify.md +0 -266
  402. package/.cursorrules +0 -149
  403. package/.github/prompts/premerge.prompt.md +0 -188
  404. package/.github/prompts/research.prompt.md +0 -44
  405. package/.github/prompts/rollback.prompt.md +0 -723
  406. package/.github/prompts/verify.prompt.md +0 -271
  407. package/.github/workflows/beads-to-github.yml +0 -89
  408. package/.github/workflows/github-to-beads.yml +0 -100
  409. package/.kilocode/workflows/dev.md +0 -346
  410. package/.kilocode/workflows/plan.md +0 -567
  411. package/.kilocode/workflows/premerge.md +0 -187
  412. package/.kilocode/workflows/research.md +0 -43
  413. package/.kilocode/workflows/review.md +0 -452
  414. package/.kilocode/workflows/rollback.md +0 -722
  415. package/.kilocode/workflows/ship.md +0 -214
  416. package/.kilocode/workflows/sonarcloud.md +0 -150
  417. package/.kilocode/workflows/status.md +0 -91
  418. package/.kilocode/workflows/validate.md +0 -289
  419. package/.kilocode/workflows/verify.md +0 -270
  420. package/.opencode/commands/dev.md +0 -345
  421. package/.opencode/commands/plan.md +0 -566
  422. package/.opencode/commands/premerge.md +0 -186
  423. package/.opencode/commands/research.md +0 -42
  424. package/.opencode/commands/review.md +0 -451
  425. package/.opencode/commands/rollback.md +0 -721
  426. package/.opencode/commands/ship.md +0 -213
  427. package/.opencode/commands/sonarcloud.md +0 -149
  428. package/.opencode/commands/status.md +0 -90
  429. package/.opencode/commands/validate.md +0 -288
  430. package/.opencode/commands/verify.md +0 -269
  431. package/.roo/commands/dev.md +0 -346
  432. package/.roo/commands/plan.md +0 -567
  433. package/.roo/commands/premerge.md +0 -187
  434. package/.roo/commands/research.md +0 -43
  435. package/.roo/commands/review.md +0 -452
  436. package/.roo/commands/rollback.md +0 -722
  437. package/.roo/commands/ship.md +0 -214
  438. package/.roo/commands/sonarcloud.md +0 -150
  439. package/.roo/commands/status.md +0 -91
  440. package/.roo/commands/validate.md +0 -289
  441. package/.roo/commands/verify.md +0 -270
  442. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  443. package/docs/GREPTILE_SETUP.md +0 -400
  444. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  445. package/docs/SETUP.md +0 -663
  446. package/docs/VALIDATION.md +0 -363
  447. package/lib/agents/cline.plugin.json +0 -29
  448. package/lib/agents/copilot.plugin.json +0 -24
  449. package/lib/agents/kilocode.plugin.json +0 -22
  450. package/lib/agents/opencode.plugin.json +0 -23
  451. package/lib/agents/roo.plugin.json +0 -30
  452. package/lib/beads-bootstrap.js +0 -225
  453. package/lib/beads-health-check.js +0 -188
  454. package/lib/commands/commands-reset.js +0 -147
  455. package/opencode.json +0 -67
  456. package/scripts/beads-context.test.js +0 -584
  457. package/scripts/github-beads-sync/comment.mjs +0 -64
  458. package/scripts/github-beads-sync/config.mjs +0 -148
  459. package/scripts/github-beads-sync/github-api.mjs +0 -131
  460. package/scripts/github-beads-sync/index.mjs +0 -356
  461. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  462. package/scripts/github-beads-sync/mapping.mjs +0 -132
  463. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  464. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  465. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  466. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  467. package/scripts/github-beads-sync.config.json +0 -26
  468. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,541 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Per-harness native HOOK config renderer.
5
+ *
6
+ * Projects Forge's lifecycle enforcement — the TDD gate (source changes require
7
+ * tests) and the protected-path guard — onto each harness's REAL native hook
8
+ * surface, using read → merge → write (idempotent, preserves user hooks). This is
9
+ * the hook analogue of lib/mcp-config-renderer.js and closes the honesty gap that
10
+ * #311 recorded: the capability matrix advertised native hooks that no renderer
11
+ * actually wrote.
12
+ *
13
+ * Verified native hook surfaces:
14
+ * - Claude : `.claude/settings.json` → a `hooks` block
15
+ * (PreToolUse matcher groups → { type:'command', command })
16
+ * - Cursor : `.cursor/hooks.json` → { version: 1, hooks: { <event>: [ { command } ] } }
17
+ * (Cursor 1.7+; only `before*` events can DENY — there is NO pre-edit
18
+ * deny event, so protected-path write-blocking + commit gating run on
19
+ * `beforeShellExecution`, and `afterFileEdit` is an observational audit.)
20
+ * - Codex : `.codex/config.toml` → `[hooks]` matcher groups. Codex reads the
21
+ * GLOBAL `~/.codex/config.toml` (per #311 / lib/agents-config.js), so
22
+ * project `forge setup` MUST NOT write it. Rendered + tested here for a
23
+ * global-config follow-up only.
24
+ * - Hermes : `~/.hermes/config.yaml` → a `hooks:` block of shell hooks (matcher +
25
+ * command; JSON-stdin/stdout wire protocol). A `pre_tool_call` hook CAN
26
+ * deny a tool call (it even accepts Claude's {decision:block} shape). Lives
27
+ * in GLOBAL (home) config, so project `forge setup` MUST NOT write it —
28
+ * rendered + tested here for a global-config follow-up only.
29
+ *
30
+ * The rendered `command` invokes Forge's installed native-hook adapter
31
+ * (`.forge/hooks/forge-native-hook.js`), which the setup flow installs alongside
32
+ * `.forge/hooks/check-tdd.js`. The adapter reads the harness's hook stdin, enforces
33
+ * the protected-path set, and delegates the TDD gate to the real `check-tdd.js`.
34
+ *
35
+ * Dependency-free (JSON only; no TOML lib) so it runs under `bun test` and the
36
+ * release gates. Reuses the MCP renderer's `backupFile` for the data-loss guard.
37
+ *
38
+ * @module hook-renderer
39
+ */
40
+
41
+ const fs = require('node:fs');
42
+ const path = require('node:path');
43
+ const { backupFile } = require('./mcp-config-renderer');
44
+
45
+ const HARNESS_HOOK_FILES = {
46
+ claude: '.claude/settings.json',
47
+ cursor: '.cursor/hooks.json',
48
+ codex: '.codex/config.toml',
49
+ hermes: '~/.hermes/config.yaml',
50
+ };
51
+
52
+ // The adapter Forge installs into every project (see lib/commands/setup.js). The
53
+ // stable `forge-native-hook.js` token also MARKS Forge-owned hook entries so a
54
+ // re-merge replaces them in place instead of duplicating (idempotency) and never
55
+ // clobbers a user's own hooks.
56
+ const FORGE_HOOK_ADAPTER_REL = '.forge/hooks/forge-native-hook.js';
57
+ const FORGE_HOOK_ADAPTER = `node ${FORGE_HOOK_ADAPTER_REL}`;
58
+ const FORGE_HOOK_MARKER = 'forge-native-hook.js';
59
+
60
+ // CONTEXT intents route to the `forge` CLI (which loads lib/), NOT the self-contained
61
+ // adapter — memory injection needs kernel/FTS access and must FAIL OPEN. The marker
62
+ // token below stamps Forge-owned context-hook entries so a re-merge replaces them in
63
+ // place (idempotency), the same role FORGE_HOOK_MARKER plays for enforcement entries.
64
+ //
65
+ // The command is a RESOLVED `node <abs bin/forge.js>` invocation — NOT a bare `forge`.
66
+ // A bare `forge` on a hook's minimal PATH either does not resolve (feature silently
67
+ // never fires) or resolves to the WRONG binary (e.g. a global `forge` that misroutes),
68
+ // whose stdout would then be injected as session context. Resolving the exact CLI that
69
+ // rendered the hook (via __dirname, works in the repo and under node_modules) removes
70
+ // both failure modes. FORGE_CONTEXT_MARKER is the stable idempotency token.
71
+ const FORGE_CLI_BIN = path.join(__dirname, '..', 'bin', 'forge.js');
72
+ const FORGE_CLI = `node "${FORGE_CLI_BIN}"`;
73
+ const FORGE_CONTEXT_MARKER = 'hooks session-start';
74
+ // The inbox-pickup context hook (UserPromptSubmit tier). A SECOND context marker so a
75
+ // re-merge recognizes + replaces the Forge-owned UserPromptSubmit entry in place, exactly
76
+ // as FORGE_CONTEXT_MARKER does for the SessionStart entry.
77
+ const FORGE_INBOX_CONTEXT_MARKER = 'hooks inbox-pickup';
78
+ // The PR-shepherd events context hook (a SECOND UserPromptSubmit-tier hook). Its own
79
+ // marker so a re-merge recognizes + replaces the Forge-owned entry in place. The whole
80
+ // Forge UserPromptSubmit group is already recognized via the inbox marker, but this keeps
81
+ // the shepherd-events command independently identifiable (symmetry with the other tiers).
82
+ const FORGE_SHEPHERD_EVENTS_MARKER = 'hooks shepherd-events';
83
+ // The capture-on-exit context hook (PreCompact + Stop tier). A THIRD context marker so a
84
+ // re-merge recognizes + replaces the Forge-owned PreCompact/Stop entries in place. Both
85
+ // events share this one marker (they differ only by a --trigger suffix on the command).
86
+ //
87
+ // It matches the FULL resolved Forge invocation (`node "<abs bin/forge.js>" hooks capture`),
88
+ // NOT the bare `hooks capture` verb: a bare-substring check would treat ANY user hook command
89
+ // that merely mentions "hooks capture" as Forge-owned and DELETE it on re-merge (data-integrity
90
+ // bug, CodeRabbit on #397). Only Forge's own rendered capture command contains this prefix, so
91
+ // the merge replaces exactly Forge's group and preserves the user's.
92
+ const FORGE_CAPTURE_CONTEXT_MARKER = `${FORGE_CLI} hooks capture`;
93
+
94
+ // Per-harness SessionStart context-injection capability. Honest capability matrix —
95
+ // only Claude exposes a native session-start surface that can inject additionalContext.
96
+ // Cursor's 1.7 hooks are deny-oriented (no session-start context surface); Codex and
97
+ // Hermes hooks live in GLOBAL home config that project setup never writes. We NEVER
98
+ // fake parity: each non-Claude harness carries an explicit, tested skip reason.
99
+ const SESSION_START_SUPPORT = Object.freeze({
100
+ claude: Object.freeze({ rendered: true }),
101
+ cursor: Object.freeze({ rendered: false, reason: 'no-session-start-surface' }),
102
+ codex: Object.freeze({ rendered: false, reason: 'global-config' }),
103
+ hermes: Object.freeze({ rendered: false, reason: 'global-config' }),
104
+ });
105
+
106
+ // Per-harness UserPromptSubmit context-injection capability (the near-real-time inbox
107
+ // tier). Only Claude has a verified UserPromptSubmit surface that injects
108
+ // additionalContext alongside the submitted prompt. Same honesty rule as SessionStart:
109
+ // every non-Claude harness carries an explicit, tested skip reason — no faked parity.
110
+ const USER_PROMPT_SUBMIT_SUPPORT = Object.freeze({
111
+ claude: Object.freeze({ rendered: true }),
112
+ cursor: Object.freeze({ rendered: false, reason: 'no-user-prompt-surface' }),
113
+ codex: Object.freeze({ rendered: false, reason: 'global-config' }),
114
+ hermes: Object.freeze({ rendered: false, reason: 'global-config' }),
115
+ });
116
+
117
+ // Per-harness session-END (capture-on-exit) capability. Only Claude exposes native
118
+ // PreCompact + Stop hook surfaces where Forge can snapshot session learnings BEFORE
119
+ // context is compacted or the session ends. Cursor 1.7 hooks are deny-oriented with no
120
+ // session-end surface; Codex and Hermes hooks live in GLOBAL home config project setup
121
+ // never writes. Same honesty rule as the other context tiers — no faked parity.
122
+ const SESSION_END_SUPPORT = Object.freeze({
123
+ claude: Object.freeze({ rendered: true }),
124
+ cursor: Object.freeze({ rendered: false, reason: 'no-session-end-surface' }),
125
+ codex: Object.freeze({ rendered: false, reason: 'global-config' }),
126
+ hermes: Object.freeze({ rendered: false, reason: 'global-config' }),
127
+ });
128
+
129
+ // Claude exposes $CLAUDE_PROJECT_DIR (absolute project root) to hook commands and
130
+ // documents it as THE cwd-independent way to reference project-local hook scripts —
131
+ // a bare relative path breaks whenever Claude runs the hook from another cwd. Cursor
132
+ // and Codex execute hooks from the workspace root, so a repo-relative path resolves.
133
+ function adapterInvocation(harness) {
134
+ if (harness === 'claude') return `node "$CLAUDE_PROJECT_DIR/${FORGE_HOOK_ADAPTER_REL}"`;
135
+ return FORGE_HOOK_ADAPTER;
136
+ }
137
+
138
+ /**
139
+ * The frozen Forge hook contract: the two enforcement intents projected onto each
140
+ * harness. Order matters — protected-path (per-write) is listed before tdd-gate
141
+ * (per-commit) so the rendered Claude PreToolUse groups read write-guard first.
142
+ */
143
+ const FORGE_HOOK_CONTRACT = Object.freeze({
144
+ schemaVersion: '1.2.0',
145
+ kind: 'forge.hookContract',
146
+ adapter: FORGE_HOOK_ADAPTER,
147
+ intents: Object.freeze([
148
+ Object.freeze({
149
+ id: 'protected-path',
150
+ kind: 'enforcement',
151
+ enforces: 'Protected-path guard: block writes/edits to Forge-protected paths (.forge/, .git/, AGENTS.md, secrets, generated artifacts).',
152
+ lifecycle: 'pre-write',
153
+ command: `${FORGE_HOOK_ADAPTER} --intent protected-path`,
154
+ }),
155
+ Object.freeze({
156
+ id: 'tdd-gate',
157
+ kind: 'enforcement',
158
+ enforces: 'TDD gate: source changes must ship with accompanying tests (blocks bare git commit).',
159
+ lifecycle: 'pre-commit',
160
+ command: `${FORGE_HOOK_ADAPTER} --intent tdd-gate`,
161
+ }),
162
+ Object.freeze({
163
+ id: 'memory-inject',
164
+ kind: 'context',
165
+ cliAction: 'session-start',
166
+ enforces: 'Memory push: inject a bounded, token-capped digest (remembered notes + top open issues) at session start. Additive and FAIL-OPEN — a missing digest never blocks a session.',
167
+ lifecycle: 'session-start',
168
+ command: `${FORGE_CLI} hooks session-start`,
169
+ }),
170
+ Object.freeze({
171
+ id: 'inbox-pickup',
172
+ kind: 'context',
173
+ cliAction: 'inbox-pickup',
174
+ // COMPLIANT COMMENT-BACK: surfaces pending targeted dashboard instruction comments
175
+ // (kernel DATA, fenced) on each prompt. Reads the user's own kernel data via a
176
+ // supported hook — NEVER injects into a running session's stdin, never drives the
177
+ // agent programmatically (Anthropic Usage Policy, kernel issue 6d10c1a1).
178
+ enforces: 'Comment-back pickup: surface a compact count+pointer nudge for pending targeted dashboard instruction comments on each UserPromptSubmit (compact to avoid additionalContext accumulation; full fenced digest is at SessionStart + `forge inbox`). Additive and FAIL-OPEN — a missing nudge never blocks a prompt.',
179
+ lifecycle: 'user-prompt-submit',
180
+ command: `${FORGE_CLI} hooks inbox-pickup`,
181
+ }),
182
+ Object.freeze({
183
+ id: 'shepherd-events',
184
+ kind: 'context',
185
+ cliAction: 'shepherd-events',
186
+ // PR-SHEPHERD DELTAS: surfaces a compact, capped digest of NEW PR-monitor events
187
+ // (verdict changes, failed checks, new threads, merged/closed) since the last read,
188
+ // then advances the per-PR consumer cursor. This is the CONSUMER side of the constant
189
+ // watcher — the watch loop writes the journal, this pushes the deltas each turn. Reads
190
+ // the user's OWN local journal via a supported hook — NEVER stdin injection, never
191
+ // drives the agent (Anthropic Usage Policy).
192
+ enforces: 'PR shepherd events: on each UserPromptSubmit, surface a compact, capped digest of NEW PR-monitor events (verdict changes, failed checks, new threads, merged/closed) since the last read across open-PR journals, then advance the cursor. Compact to avoid additionalContext accumulation. Additive and FAIL-OPEN — a missing digest never blocks a prompt.',
193
+ lifecycle: 'user-prompt-submit',
194
+ command: `${FORGE_CLI} hooks shepherd-events`,
195
+ }),
196
+ Object.freeze({
197
+ id: 'memory-capture',
198
+ kind: 'context',
199
+ cliAction: 'capture',
200
+ // CAPTURE-ON-EXIT: the write half of Forge memory. SessionStart INJECTS remembered
201
+ // notes but nothing ever CAPTURES on the way out, so long sessions lose learnings and
202
+ // memory stays orphaned (the eval scored memory pull-only). PreCompact + Stop fire this
203
+ // to snapshot a bounded session-summary note BEFORE context is compacted / the session
204
+ // ends. Persists to the memory store — it NEVER injects into the turn and never drives
205
+ // the agent (Anthropic Usage Policy). The trigger (precompact|stop) rides as a --trigger
206
+ // suffix stamped by the rendered hook, so the CLI never has to read hook stdin.
207
+ enforces: 'Capture-on-exit: snapshot a bounded session-summary note (trigger + in-progress issues) into the memory store on PreCompact and Stop, BEFORE context is compacted or the session ends. Additive and FAIL-OPEN — a capture failure never blocks a session.',
208
+ lifecycle: 'session-end',
209
+ command: `${FORGE_CLI} hooks capture`,
210
+ }),
211
+ ]),
212
+ });
213
+
214
+ /** Thrown when an existing hook config cannot be parsed — signals "do not overwrite". */
215
+ class HookConfigParseError extends Error {
216
+ constructor(message, cause) {
217
+ super(message);
218
+ this.name = 'HookConfigParseError';
219
+ this.cause = cause;
220
+ }
221
+ }
222
+
223
+ function intentById(contract, id) {
224
+ const intent = contract.intents.find(i => i.id === id);
225
+ if (!intent) throw new Error(`Forge hook contract is missing intent '${id}'`);
226
+ return intent;
227
+ }
228
+
229
+ function harnessCommand(contract, id, harness) {
230
+ const intent = intentById(contract, id);
231
+ // CONTEXT intents route to the `forge` CLI (fail-open memory injection), NOT the
232
+ // self-contained enforcement adapter. Hooks often run with a minimal PATH; if `forge`
233
+ // does not resolve the command simply fails and the harness ignores it (fail-open).
234
+ if (intent.kind === 'context') {
235
+ return `${FORGE_CLI} hooks ${intent.cliAction} --harness ${harness}`;
236
+ }
237
+ // ENFORCEMENT intents build from the harness-specific adapter invocation (Claude →
238
+ // $CLAUDE_PROJECT_DIR; Cursor/Codex → repo-relative). intent.id is the `--intent` value.
239
+ return `${adapterInvocation(harness)} --intent ${intent.id} --harness ${harness}`;
240
+ }
241
+
242
+ /**
243
+ * Report the per-harness SessionStart context-injection capability (the honest matrix).
244
+ * @param {string} harness
245
+ * @returns {{ rendered: boolean, reason?: string }}
246
+ */
247
+ function sessionStartCapability(harness) {
248
+ return SESSION_START_SUPPORT[harness] || { rendered: false, reason: 'unknown-harness' };
249
+ }
250
+
251
+ /**
252
+ * Report the per-harness UserPromptSubmit context-injection capability (the honest matrix
253
+ * for the near-real-time inbox tier).
254
+ * @param {string} harness
255
+ * @returns {{ rendered: boolean, reason?: string }}
256
+ */
257
+ function userPromptSubmitCapability(harness) {
258
+ return USER_PROMPT_SUBMIT_SUPPORT[harness] || { rendered: false, reason: 'unknown-harness' };
259
+ }
260
+
261
+ /**
262
+ * Report the per-harness session-END (capture-on-exit) capability (the honest matrix for
263
+ * the PreCompact + Stop capture tier).
264
+ * @param {string} harness
265
+ * @returns {{ rendered: boolean, reason?: string }}
266
+ */
267
+ function sessionEndCapability(harness) {
268
+ return SESSION_END_SUPPORT[harness] || { rendered: false, reason: 'unknown-harness' };
269
+ }
270
+
271
+ /**
272
+ * Render the Claude `.claude/settings.json` `hooks` block (PreToolUse groups only).
273
+ * Write/Edit/MultiEdit/NotebookEdit → protected-path deny; Bash → TDD gate.
274
+ * @param {object} contract
275
+ * @returns {{ PreToolUse: object[] }}
276
+ */
277
+ function renderClaudeHooks(contract) {
278
+ return {
279
+ PreToolUse: [
280
+ {
281
+ matcher: 'Write|Edit|MultiEdit|NotebookEdit',
282
+ hooks: [{ type: 'command', command: harnessCommand(contract, 'protected-path', 'claude') }],
283
+ },
284
+ {
285
+ matcher: 'Bash',
286
+ hooks: [{ type: 'command', command: harnessCommand(contract, 'tdd-gate', 'claude') }],
287
+ },
288
+ ],
289
+ // SessionStart context injection (memory push). No matcher → applies to every
290
+ // session source; the command emits { hookSpecificOutput.additionalContext }.
291
+ SessionStart: [
292
+ { hooks: [{ type: 'command', command: harnessCommand(contract, 'memory-inject', 'claude') }] },
293
+ ],
294
+ // UserPromptSubmit context injection (compliant comment-back — near-real-time tier).
295
+ // Surfaces pending targeted dashboard instruction comments (fenced kernel DATA) on each
296
+ // prompt; the command emits { hookSpecificOutput.additionalContext }. Reads the user's
297
+ // own kernel data via a supported hook — NEVER stdin injection (Anthropic Usage Policy).
298
+ // Both UserPromptSubmit context hooks share ONE Forge-owned group (inbox-pickup +
299
+ // PR-shepherd deltas). Claude runs every hook in the group and appends each hook's
300
+ // additionalContext; keeping them in one group means a re-merge replaces the pair
301
+ // atomically (the group is Forge-owned via either marker). Both are compact + fail-open.
302
+ UserPromptSubmit: [
303
+ {
304
+ hooks: [
305
+ { type: 'command', command: harnessCommand(contract, 'inbox-pickup', 'claude') },
306
+ { type: 'command', command: harnessCommand(contract, 'shepherd-events', 'claude') },
307
+ ],
308
+ },
309
+ ],
310
+ // Capture-on-exit (memory capture). PreCompact fires before context is compacted; Stop
311
+ // fires when the agent finishes. Both call the same capture command; the event stamps the
312
+ // --trigger so the CLI never reads hook stdin. The command persists a bounded session
313
+ // summary — it emits NO stdout (a Stop hook that printed text would inject into the turn).
314
+ PreCompact: [
315
+ { hooks: [{ type: 'command', command: `${harnessCommand(contract, 'memory-capture', 'claude')} --trigger precompact` }] },
316
+ ],
317
+ Stop: [
318
+ { hooks: [{ type: 'command', command: `${harnessCommand(contract, 'memory-capture', 'claude')} --trigger stop` }] },
319
+ ],
320
+ };
321
+ }
322
+
323
+ /**
324
+ * Render the Cursor `.cursor/hooks.json` config. Cursor 1.7+ has NO pre-edit deny
325
+ * event, so write-blocking + commit gating run on `beforeShellExecution` (git /
326
+ * redirects), and `afterFileEdit` carries an observational protected-path audit.
327
+ * @param {object} contract
328
+ * @returns {{ version: number, hooks: object }}
329
+ */
330
+ function renderCursorHooks(contract) {
331
+ return {
332
+ version: 1,
333
+ hooks: {
334
+ beforeShellExecution: [
335
+ { command: harnessCommand(contract, 'tdd-gate', 'cursor') },
336
+ { command: harnessCommand(contract, 'protected-path', 'cursor') },
337
+ ],
338
+ afterFileEdit: [
339
+ { command: harnessCommand(contract, 'protected-path', 'cursor') },
340
+ ],
341
+ },
342
+ };
343
+ }
344
+
345
+ /**
346
+ * Render a Codex `[hooks]` TOML block (mirrors the Claude matcher-group model).
347
+ * Returned for a GLOBAL-config follow-up ONLY — never written by project setup.
348
+ * @param {object} contract
349
+ * @returns {string}
350
+ */
351
+ function renderCodexHooksToml(contract) {
352
+ const groups = [
353
+ { matcher: 'Write|Edit', command: harnessCommand(contract, 'protected-path', 'codex') },
354
+ { matcher: 'Bash', command: harnessCommand(contract, 'tdd-gate', 'codex') },
355
+ ];
356
+ const blocks = groups.map(g =>
357
+ `[[hooks.PreToolUse]]\nmatcher = ${tomlString(g.matcher)}\n\n`
358
+ + `[[hooks.PreToolUse.hooks]]\ntype = "command"\ncommand = ${tomlString(g.command)}\n`,
359
+ );
360
+ return blocks.join('\n');
361
+ }
362
+
363
+ function tomlString(value) {
364
+ return '"' + String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
365
+ }
366
+
367
+ /**
368
+ * Render a Hermes shell-hooks YAML block (the `hooks:` section of ~/.hermes/config.yaml).
369
+ * Hermes runs shell hooks as subprocesses over a JSON-stdin/stdout wire protocol; a
370
+ * `pre_tool_call` hook can DENY a tool call. Matchers are regexes over the Hermes
371
+ * tool_name (write_file/patch = edits → protected-path; terminal = shell → tdd-gate).
372
+ * Returned for a GLOBAL-config follow-up ONLY — Hermes reads ~/.hermes/config.yaml
373
+ * (home dir), so project setup never writes it (mirrors renderCodexHooksToml).
374
+ * @param {object} contract
375
+ * @returns {string}
376
+ */
377
+ function renderHermesHooksYaml(contract) {
378
+ const groups = [
379
+ { matcher: 'write_file|patch', command: harnessCommand(contract, 'protected-path', 'hermes') },
380
+ { matcher: 'terminal', command: harnessCommand(contract, 'tdd-gate', 'hermes') },
381
+ ];
382
+ const lines = ['hooks:', ' pre_tool_call:'];
383
+ for (const g of groups) {
384
+ lines.push(` - matcher: ${yamlString(g.matcher)}`);
385
+ lines.push(` command: ${yamlString(g.command)}`);
386
+ }
387
+ return lines.join('\n') + '\n';
388
+ }
389
+
390
+ function yamlString(value) {
391
+ return '"' + String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
392
+ }
393
+
394
+ /** True when a command is Forge-owned — the enforcement adapter OR a context CLI hook. */
395
+ function isForgeCommand(command) {
396
+ return typeof command === 'string'
397
+ && (command.includes(FORGE_HOOK_MARKER)
398
+ || command.includes(FORGE_CONTEXT_MARKER)
399
+ || command.includes(FORGE_INBOX_CONTEXT_MARKER)
400
+ || command.includes(FORGE_SHEPHERD_EVENTS_MARKER)
401
+ || command.includes(FORGE_CAPTURE_CONTEXT_MARKER));
402
+ }
403
+
404
+ /** True when a hook group/entry is Forge-owned (any inner command is Forge-owned). */
405
+ function isForgeClaudeGroup(group) {
406
+ const hooks = Array.isArray(group?.hooks) ? group.hooks : [];
407
+ return hooks.some(h => isForgeCommand(h?.command));
408
+ }
409
+
410
+ function isForgeCursorEntry(entry) {
411
+ return isForgeCommand(entry?.command);
412
+ }
413
+
414
+ function parseJsonConfig(existingText) {
415
+ if (!existingText || !existingText.trim()) return {};
416
+ let obj;
417
+ try {
418
+ obj = JSON.parse(existingText);
419
+ } catch (err) {
420
+ // DATA-LOSS GUARD: never silently discard a populated-but-unparseable config
421
+ // (JSONC comments / trailing commas). Signal the caller to back up + skip.
422
+ throw new HookConfigParseError('existing hook config is not valid JSON', err);
423
+ }
424
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return {};
425
+ return obj;
426
+ }
427
+
428
+ /**
429
+ * Merge Forge's hooks into an existing `.claude/settings.json` string.
430
+ * Preserves all other settings keys, all non-Forge events, and the user's own
431
+ * matcher-groups; replaces only Forge-owned groups (idempotent).
432
+ * @param {string} existingText
433
+ * @param {object} contract
434
+ * @returns {string}
435
+ */
436
+ function mergeClaudeSettings(existingText, contract) {
437
+ const obj = parseJsonConfig(existingText);
438
+ if (!obj.hooks || typeof obj.hooks !== 'object' || Array.isArray(obj.hooks)) obj.hooks = {};
439
+ const rendered = renderClaudeHooks(contract);
440
+ for (const [event, forgeGroups] of Object.entries(rendered)) {
441
+ const existingGroups = Array.isArray(obj.hooks[event]) ? obj.hooks[event] : [];
442
+ const userGroups = existingGroups.filter(group => !isForgeClaudeGroup(group));
443
+ obj.hooks[event] = [...userGroups, ...forgeGroups];
444
+ }
445
+ return JSON.stringify(obj, null, 2) + '\n';
446
+ }
447
+
448
+ /**
449
+ * Merge Forge's hooks into an existing `.cursor/hooks.json` string.
450
+ * Forces `version: 1`, preserves all non-Forge events and the user's own entries;
451
+ * replaces only Forge-owned entries (idempotent).
452
+ * @param {string} existingText
453
+ * @param {object} contract
454
+ * @returns {string}
455
+ */
456
+ function mergeCursorHooks(existingText, contract) {
457
+ const obj = parseJsonConfig(existingText);
458
+ obj.version = 1;
459
+ if (!obj.hooks || typeof obj.hooks !== 'object' || Array.isArray(obj.hooks)) obj.hooks = {};
460
+ const rendered = renderCursorHooks(contract);
461
+ for (const [event, forgeEntries] of Object.entries(rendered.hooks)) {
462
+ const existingEntries = Array.isArray(obj.hooks[event]) ? obj.hooks[event] : [];
463
+ const userEntries = existingEntries.filter(entry => !isForgeCursorEntry(entry));
464
+ obj.hooks[event] = [...userEntries, ...forgeEntries];
465
+ }
466
+ return JSON.stringify(obj, null, 2) + '\n';
467
+ }
468
+
469
+ const MERGERS = {
470
+ claude: mergeClaudeSettings,
471
+ cursor: mergeCursorHooks,
472
+ };
473
+
474
+ /**
475
+ * Render (merge) Forge's native hooks into one harness's native config on disk.
476
+ * Read → merge → write. Unparseable existing file → BACKED UP + left untouched
477
+ * (never overwritten), mirroring the MCP renderer's data-loss safety.
478
+ *
479
+ * Codex is GLOBAL-config scope: project setup cannot write it, so this returns a
480
+ * `scope: 'global-config'` skip WITHOUT touching disk (keeps Codex honest).
481
+ *
482
+ * @param {object} params
483
+ * @param {'claude'|'cursor'|'codex'} params.harness
484
+ * @param {string} params.targetRoot - Project root.
485
+ * @param {object} [params.contract] - Forge hook contract (defaults to FORGE_HOOK_CONTRACT).
486
+ * @returns {{ file?: string, existed?: boolean, skipped: boolean, wrote: boolean, backup?: string, scope?: string }}
487
+ */
488
+ function renderHookConfig({ harness, targetRoot, contract = FORGE_HOOK_CONTRACT }) {
489
+ if (harness === 'codex' || harness === 'hermes') {
490
+ // Honest: Codex (~/.codex/config.toml) and Hermes (~/.hermes/config.yaml) hooks
491
+ // both live in GLOBAL (home) config; project setup must not write global config.
492
+ // Their renderers are kept + tested for a global-config follow-up.
493
+ return { harness, scope: 'global-config', skipped: true, wrote: false };
494
+ }
495
+ const merge = MERGERS[harness];
496
+ const rel = HARNESS_HOOK_FILES[harness];
497
+ if (!merge || !rel) throw new Error(`Unknown hook harness: ${harness}`);
498
+
499
+ const filePath = path.join(targetRoot, rel);
500
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
501
+ const existed = fs.existsSync(filePath);
502
+ const existing = existed ? fs.readFileSync(filePath, 'utf-8') : '';
503
+
504
+ let merged;
505
+ try {
506
+ merged = merge(existing, contract);
507
+ } catch (err) {
508
+ if (err instanceof HookConfigParseError && existed) {
509
+ const backup = backupFile(filePath);
510
+ return { file: filePath, existed, skipped: true, wrote: false, backup };
511
+ }
512
+ throw err;
513
+ }
514
+
515
+ fs.writeFileSync(filePath, merged, 'utf-8');
516
+ return { file: filePath, existed, skipped: false, wrote: true };
517
+ }
518
+
519
+ module.exports = {
520
+ FORGE_HOOK_CONTRACT,
521
+ HARNESS_HOOK_FILES,
522
+ FORGE_HOOK_ADAPTER,
523
+ FORGE_HOOK_MARKER,
524
+ FORGE_CONTEXT_MARKER,
525
+ FORGE_INBOX_CONTEXT_MARKER,
526
+ FORGE_CAPTURE_CONTEXT_MARKER,
527
+ SESSION_START_SUPPORT,
528
+ USER_PROMPT_SUBMIT_SUPPORT,
529
+ SESSION_END_SUPPORT,
530
+ sessionStartCapability,
531
+ userPromptSubmitCapability,
532
+ sessionEndCapability,
533
+ HookConfigParseError,
534
+ renderClaudeHooks,
535
+ renderCursorHooks,
536
+ renderCodexHooksToml,
537
+ renderHermesHooksYaml,
538
+ mergeClaudeSettings,
539
+ mergeCursorHooks,
540
+ renderHookConfig,
541
+ };