forge-workflow 0.0.10 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) 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 +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +461 -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 +118 -0
  29. package/docs/guides/SUPPORT.md +185 -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 +205 -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 +115 -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/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +17 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +0 -1
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +838 -972
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +382 -11
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,237 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const {
5
+ runJsonlProjectionConsumer,
6
+ readProjection,
7
+ resolveProjectionDir,
8
+ writeProjection,
9
+ DEFAULT_PROJECTION_TARGET,
10
+ } = require('../kernel/projection-jsonl-writer');
11
+
12
+ // Full set the consumer may call — including the retry (recordProjectionFailure)
13
+ // and dead-letter (deadLetterProjection) paths — so a partially-implemented
14
+ // broker is skipped cleanly instead of failing mid-run.
15
+ const PROJECTION_BROKER_METHODS = [
16
+ 'listProjectionOutbox',
17
+ 'loadProjectionModel',
18
+ 'markProjectionDelivered',
19
+ 'recordProjectionFailure',
20
+ 'deadLetterProjection',
21
+ ];
22
+
23
+ /**
24
+ * Parse `forge export` options from positional args (and a parsed flags object
25
+ * as a fallback). Supported: --import, --dry-run, --json, --dir=<path> / --dir <path>.
26
+ */
27
+ function parseExportOptions(args = [], flags = {}) {
28
+ const options = {
29
+ import: Boolean(flags.import),
30
+ dryRun: Boolean(flags['dry-run'] || flags.dryRun),
31
+ json: Boolean(flags.json),
32
+ dir: flags.dir,
33
+ };
34
+
35
+ for (let i = 0; i < args.length; i += 1) {
36
+ const arg = args[i];
37
+ if (arg === '--import') options.import = true;
38
+ else if (arg === '--dry-run') options.dryRun = true;
39
+ else if (arg === '--json') options.json = true;
40
+ else if (arg.startsWith('--dir=')) options.dir = arg.slice('--dir='.length);
41
+ else if (arg === '--dir') {
42
+ // only consume the next token when it is a real value, not another flag
43
+ const next = args[i + 1];
44
+ if (next && !next.startsWith('--')) {
45
+ options.dir = next;
46
+ i += 1;
47
+ }
48
+ }
49
+ }
50
+
51
+ return options;
52
+ }
53
+
54
+ function brokerSupportsProjection(broker) {
55
+ return Boolean(broker) && PROJECTION_BROKER_METHODS.every(method => typeof broker[method] === 'function');
56
+ }
57
+
58
+ function resolveDirArg(dirArg, projectRoot) {
59
+ if (!dirArg) return undefined;
60
+ return path.resolve(projectRoot || process.cwd(), dirArg);
61
+ }
62
+
63
+ function renderHuman(payload) {
64
+ if (payload.error) return `forge export failed: ${payload.error}`;
65
+ if (payload.imported === true) {
66
+ const { issues, comments, dependencies } = payload.counts;
67
+ const skipped = issues.skipped + comments.skipped + dependencies.skipped;
68
+ // Report "Imported N" ONLY for records actually written; a re-import that
69
+ // applies nothing (everything already present) must not claim it imported them.
70
+ if (payload.applied === 0) {
71
+ return `Kernel projection at ${payload.dir} is already hydrated — nothing new imported`
72
+ + ` (${skipped} record${skipped === 1 ? '' : 's'} already present)`;
73
+ }
74
+ return `Imported Kernel projection from ${payload.dir}`
75
+ + ` (${issues.inserted} issues, ${comments.inserted} comments, ${dependencies.inserted} dependencies`
76
+ + (skipped ? `; ${skipped} already present` : '') + ')';
77
+ }
78
+ if (payload.imported === false) return payload.message;
79
+ if (payload.dryRun) return `${payload.pending} pending projection entr${payload.pending === 1 ? 'y' : 'ies'} → ${payload.dir} (dry run, nothing written)`;
80
+ if (payload.skipped) return payload.message;
81
+ if (payload.exported) return `Exported Kernel projection to ${payload.dir} (drained ${payload.drained})`;
82
+ return payload.message || 'Nothing to export';
83
+ }
84
+
85
+ function finalize(options, payload) {
86
+ const output = options.json ? JSON.stringify(payload, null, 2) : renderHuman(payload);
87
+ return { ...payload, output, json: Boolean(options.json) };
88
+ }
89
+
90
+ /**
91
+ * Forge Export Command (D16 — Kernel JSONL portability projection).
92
+ *
93
+ * Explicit, Kernel-owned projection of issues/comments/dependencies to
94
+ * deterministic git-tracked JSONL under `.forge/kernel/`. This is NOT auto-run on
95
+ * mutation/push (D16 forbids that); it is an on-demand portability/bootstrap
96
+ * surface. The `--import` path reads a committed snapshot back from disk (no
97
+ * broker required) and verifies its manifest integrity.
98
+ *
99
+ * @module commands/export
100
+ */
101
+ module.exports = {
102
+ name: 'export',
103
+ description: 'Export the Kernel backlog to deterministic git-tracked JSONL (D16 portability projection)',
104
+ usage: 'forge export [--dir <path>] [--dry-run] [--json] [--import]',
105
+ flags: {},
106
+
107
+ /**
108
+ * @param {string[]} args - Positional arguments / flags
109
+ * @param {object} flags - Parsed CLI flags (fallback source)
110
+ * @param {string} projectRoot - Project root path
111
+ * @param {object} [opts] - Dependency injection: _broker, _writer, _now, _fs
112
+ */
113
+ async handler(args = [], flags = {}, projectRoot, opts = {}) {
114
+ const options = parseExportOptions(args, flags || {});
115
+ const now = opts._now || new Date().toISOString();
116
+ const writer = opts._writer || writeProjection;
117
+ const fsImpl = opts._fs;
118
+ const projectionDir = resolveDirArg(options.dir, projectRoot);
119
+
120
+ // --- Import / hydrate (read committed JSONL → write kernel.sqlite) ---
121
+ // readProjection verifies snapshot integrity (sha256, per-file kind, manifest
122
+ // counts, and schema_version compatibility) and throws on any mismatch, so an
123
+ // incompatible/tampered snapshot never reaches the kernel writer.
124
+ if (options.import) {
125
+ try {
126
+ const snapshot = readProjection({ projectionDir, projectRoot, fsImpl });
127
+ if (!snapshot) {
128
+ return finalize(options, {
129
+ success: true,
130
+ imported: false,
131
+ dir: resolveProjectionDir(projectionDir, projectRoot),
132
+ message: 'No projection snapshot found to import',
133
+ });
134
+ }
135
+
136
+ const read = {
137
+ issues: snapshot.model.issues.length,
138
+ comments: snapshot.model.comments.length,
139
+ dependencies: snapshot.model.dependencies.length,
140
+ };
141
+
142
+ // Hydration requires a projection-capable Kernel broker (importIssues).
143
+ // The CLI injects one for `export` (a Kernel-tool command); without it we
144
+ // validated the snapshot but cannot write — say so instead of claiming an
145
+ // import happened.
146
+ const importBroker = opts._broker || null;
147
+ if (!importBroker || typeof importBroker.importIssues !== 'function') {
148
+ return finalize(options, {
149
+ success: true,
150
+ imported: false,
151
+ dir: snapshot.dir,
152
+ read,
153
+ message: `Read ${read.issues} issues from ${snapshot.dir} but no Kernel broker is `
154
+ + 'available to import into — nothing was written',
155
+ });
156
+ }
157
+
158
+ // importIssues requires the schema/migrations to exist; the CLI broker is
159
+ // pre-initialized, but initialize() is idempotent so calling it is safe.
160
+ if (typeof importBroker.initialize === 'function') {
161
+ await importBroker.initialize();
162
+ }
163
+
164
+ // Idempotent + transactional upsert-by-id (ON CONFLICT DO NOTHING).
165
+ const summary = await importBroker.importIssues(snapshot.model, { now });
166
+ const applied = summary.issues.inserted + summary.comments.inserted + summary.dependencies.inserted;
167
+
168
+ return finalize(options, {
169
+ success: true,
170
+ imported: true,
171
+ dir: snapshot.dir,
172
+ counts: summary,
173
+ applied,
174
+ });
175
+ } catch (error) {
176
+ return finalize(options, { success: false, imported: false, error: error.message });
177
+ }
178
+ }
179
+
180
+ // --- Export (requires a projection-capable Kernel broker) ------------
181
+ const broker = opts._broker || null;
182
+ if (!brokerSupportsProjection(broker)) {
183
+ return finalize(options, {
184
+ success: true,
185
+ exported: false,
186
+ skipped: true,
187
+ message: 'No Kernel broker available — nothing to export',
188
+ });
189
+ }
190
+
191
+ if (options.dryRun) {
192
+ const pending = (await broker.listProjectionOutbox({
193
+ target: DEFAULT_PROJECTION_TARGET,
194
+ status: 'pending',
195
+ now,
196
+ })) || [];
197
+ return finalize(options, {
198
+ success: true,
199
+ exported: false,
200
+ dryRun: true,
201
+ pending: pending.length,
202
+ dir: resolveProjectionDir(projectionDir, projectRoot),
203
+ });
204
+ }
205
+
206
+ try {
207
+ const run = await runJsonlProjectionConsumer({ broker, projectionDir, projectRoot, now, writer });
208
+
209
+ // A write failure leaves entries retried/dead-lettered without a snapshot;
210
+ // surface it as a failure rather than silently reporting success.
211
+ if (!run.written && run.error) {
212
+ return finalize(options, {
213
+ success: false,
214
+ exported: false,
215
+ error: run.error,
216
+ drained: run.drained,
217
+ retried: run.retried,
218
+ dead: run.dead,
219
+ dir: resolveProjectionDir(projectionDir, projectRoot),
220
+ });
221
+ }
222
+
223
+ return finalize(options, {
224
+ success: true,
225
+ exported: run.written,
226
+ drained: run.drained,
227
+ delivered: run.delivered,
228
+ retried: run.retried,
229
+ dead: run.dead,
230
+ dir: run.write ? run.write.dir : resolveProjectionDir(projectionDir, projectRoot),
231
+ files: run.write ? run.write.files : [],
232
+ });
233
+ } catch (error) {
234
+ return finalize(options, { success: false, exported: false, error: error.message });
235
+ }
236
+ },
237
+ };
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `forge gate <enable|disable|approve|reject|status|check> ...`
5
+ *
6
+ * Two families over the same known-gate set:
7
+ *
8
+ * TOGGLE (config surface): `enable|disable <gate-id>` set
9
+ * `workflow.gates.<gate-id>.enabled` true/false in `.forge/config.yaml`. The shipped
10
+ * resolver (`applyEnabledConfig`) already consumes this field, so `forge options
11
+ * gates --json` reflects the flip with zero new read code. Write-time validation:
12
+ * an unknown gate id (or a locked gate being disabled) errors BEFORE anything is
13
+ * written — never mid-run.
14
+ *
15
+ * EVENTS (gates-as-kernel-events — Fable's insight): `approve|reject <issue> <gate>`
16
+ * record a durable `gate.approved` / `gate.rejected` event on the issue; `status`
17
+ * lists them (resume-safe after a compaction/crash); `check` exits 0 iff the gate is
18
+ * DISABLED or an approval event exists — the reusable enforcement primitive a stage
19
+ * skill calls. See lib/gate-events.js and docs/work/2026-07-04-kernel-native-skills/
20
+ * decisions.md.
21
+ */
22
+
23
+ const path = require('node:path');
24
+ const { setConfigOverride } = require('../config-writer');
25
+ const { getDefaultRuntimeGraph, getResolvedRuntimeGraph } = require('../core/runtime-graph');
26
+ const {
27
+ recordGateEvent,
28
+ listGateEvents,
29
+ isGateApproved,
30
+ } = require('../gate-events');
31
+
32
+ const TOGGLE_ACTIONS = new Set(['enable', 'disable']);
33
+ const EVENT_ACTIONS = new Set(['approve', 'reject', 'status', 'check']);
34
+
35
+ function usage() {
36
+ return 'Usage: forge gate <enable|disable|approve|reject|status|check> [<issue-id>] <gate-id> [--reason <text>] [--json]';
37
+ }
38
+
39
+ // The known-toggle set is gates PLUS unlocked toggleable rails (e.g.
40
+ // rail.kernel_tracking): both are governed through the same `forge gate
41
+ // enable|disable` surface and the resolver's rail-aware workflow.gates loop.
42
+ // The gate.* / rail.* id namespaces are disjoint, so one flat map is unambiguous.
43
+ function knownGates() {
44
+ const graph = getDefaultRuntimeGraph();
45
+ return new Map([...graph.gates, ...graph.rails].map(primitive => [primitive.id, primitive]));
46
+ }
47
+
48
+ // Kernel deps + env are threaded through the command opts (4th handler arg) so tests
49
+ // and the orchestrator can inject a shared, already-migrated kernel.
50
+ function kernelDeps(opts = {}) {
51
+ return { kernelBroker: opts.kernelBroker, kernelDriver: opts.kernelDriver };
52
+ }
53
+
54
+ function validateKnownGate(gateId) {
55
+ const gates = knownGates();
56
+ const gate = gates.get(gateId);
57
+ if (!gate) {
58
+ return {
59
+ ok: false,
60
+ error: `Unknown gate '${gateId}'. Known gates: ${[...gates.keys()].join(', ')}`,
61
+ };
62
+ }
63
+ return { ok: true, gate };
64
+ }
65
+
66
+ function handleToggle(action, gateId, projectRoot) {
67
+ if (!gateId) {
68
+ return { success: false, error: `Missing gate id.\n${usage()}` };
69
+ }
70
+ const known = validateKnownGate(gateId);
71
+ if (!known.ok) return { success: false, error: known.error };
72
+ if (action === 'disable' && known.gate.locked === true) {
73
+ return { success: false, error: `Cannot disable locked gate '${gateId}'.` };
74
+ }
75
+
76
+ const enabled = action === 'enable';
77
+ const { configPath } = setConfigOverride(
78
+ projectRoot,
79
+ ['workflow', 'gates', gateId, 'enabled'],
80
+ enabled,
81
+ );
82
+ const where = path.relative(projectRoot, configPath) || configPath;
83
+ return {
84
+ success: true,
85
+ output: `${action}d gate '${gateId}' (workflow.gates.${gateId}.enabled=${enabled}) in ${where}`,
86
+ };
87
+ }
88
+
89
+ async function handleDecision(action, issueId, gateId, flags, projectRoot, opts) {
90
+ if (!issueId || !gateId) {
91
+ return { success: false, error: `Missing issue id or gate id.\n${usage()}` };
92
+ }
93
+ const known = validateKnownGate(gateId);
94
+ if (!known.ok) return { success: false, error: known.error };
95
+
96
+ const decision = action === 'approve' ? 'approved' : 'rejected';
97
+ const reason = typeof flags.reason === 'string' ? flags.reason : undefined;
98
+ const result = await recordGateEvent(projectRoot, {
99
+ issueId,
100
+ gateId,
101
+ decision,
102
+ reason,
103
+ env: opts.env,
104
+ deps: kernelDeps(opts),
105
+ });
106
+
107
+ if (result.issueMissing) {
108
+ return { success: false, error: `Issue '${issueId}' not found.` };
109
+ }
110
+
111
+ const verb = action === 'approve' ? 'approved' : 'rejected';
112
+ const suffix = result.duplicate ? ' (already recorded)' : '';
113
+ const reasonNote = reason ? ` — ${reason}` : '';
114
+ return {
115
+ success: true,
116
+ duplicate: result.duplicate === true,
117
+ actor: result.actor,
118
+ output: `${verb} gate '${gateId}' for ${issueId} (actor ${result.actor})${reasonNote}${suffix}`,
119
+ };
120
+ }
121
+
122
+ async function handleStatus(issueId, flags, projectRoot, opts) {
123
+ if (!issueId) {
124
+ return { success: false, error: `Missing issue id.\n${usage()}` };
125
+ }
126
+ const events = await listGateEvents(projectRoot, issueId, { deps: kernelDeps(opts) });
127
+
128
+ if (flags.json) {
129
+ return { success: true, output: JSON.stringify({ issue: issueId, events }, null, 2) };
130
+ }
131
+
132
+ if (events.length === 0) {
133
+ return { success: true, output: `No gate events for ${issueId}.` };
134
+ }
135
+ const lines = events.map(event => {
136
+ const label = event.event_type === 'gate.approved' ? 'APPROVED' : 'REJECTED';
137
+ const reason = event.reason ? ` — ${event.reason}` : '';
138
+ return `${label} ${event.gate} by ${event.actor} at ${event.created_at}${reason}`;
139
+ });
140
+ return { success: true, output: `Gate events for ${issueId}:\n${lines.join('\n')}` };
141
+ }
142
+
143
+ async function handleCheck(issueId, gateId, projectRoot, opts) {
144
+ if (!issueId || !gateId) {
145
+ return { success: false, error: `Missing issue id or gate id.\n${usage()}` };
146
+ }
147
+ const known = validateKnownGate(gateId);
148
+ if (!known.ok) return { success: false, error: known.error };
149
+
150
+ // Disabled gate/rail → satisfied without any approval event (read via the shipped
151
+ // resolver). Rails share the workflow.gates toggle surface, so check both collections.
152
+ const resolved = getResolvedRuntimeGraph({ projectRoot });
153
+ const resolvedGate = [...resolved.gates, ...resolved.rails].find(gate => gate.id === gateId);
154
+ if (resolvedGate && resolvedGate.enabled === false) {
155
+ return { success: true, output: `gate ${gateId} is disabled — satisfied for ${issueId}` };
156
+ }
157
+
158
+ const approved = await isGateApproved(projectRoot, issueId, gateId, { deps: kernelDeps(opts) });
159
+ if (approved) {
160
+ return { success: true, output: `gate ${gateId} approved for ${issueId}` };
161
+ }
162
+ return { success: false, error: `gate ${gateId} not approved for ${issueId}` };
163
+ }
164
+
165
+ async function handler(args, flags = {}, projectRoot = process.cwd(), opts = {}) {
166
+ const [action, ...rest] = args;
167
+
168
+ if (TOGGLE_ACTIONS.has(action)) {
169
+ return handleToggle(action, rest[0], projectRoot);
170
+ }
171
+ if (EVENT_ACTIONS.has(action)) {
172
+ if (action === 'status') {
173
+ return handleStatus(rest[0], flags, projectRoot, opts);
174
+ }
175
+ if (action === 'check') {
176
+ return handleCheck(rest[0], rest[1], projectRoot, opts);
177
+ }
178
+ return handleDecision(action, rest[0], rest[1], flags, projectRoot, opts);
179
+ }
180
+
181
+ return {
182
+ success: false,
183
+ error: `Expected 'enable', 'disable', 'approve', 'reject', 'status', or 'check'.\n${usage()}`,
184
+ };
185
+ }
186
+
187
+ module.exports = {
188
+ name: 'gate',
189
+ description: 'Toggle a workflow gate, or record/query human-gate approval events',
190
+ usage: usage(),
191
+ handler,
192
+ };
@@ -0,0 +1,242 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `forge hooks install --global [--harness codex|hermes|all] [--dry-run]`
5
+ *
6
+ * The opt-in, CONSENT-GUARDED delivery path for the last native-hooks gap
7
+ * (kernel issue 66dd5a1f, epics 90f2f631 + 1390e1d1): Codex and Hermes have
8
+ * real native hook surfaces, but both live in GLOBAL (home-dir) config —
9
+ * `$CODEX_HOME/config.toml` and `~/.hermes/config.yaml` — which project
10
+ * `forge setup` intentionally never writes.
11
+ *
12
+ * Consent model:
13
+ * - the explicit `--global` flag IS the consent — without it the command
14
+ * refuses with guidance and writes nothing;
15
+ * - the command prints exactly which files are written and the exact Forge
16
+ * hook block merged into each, BEFORE reporting results;
17
+ * - `--dry-run` shows the same plan without touching disk;
18
+ * - existing user config is preserved (read → merge → write, idempotent via
19
+ * the forge-native-hook.js marker); unmergeable config is backed up and
20
+ * skipped, never overwritten.
21
+ *
22
+ * This command is deliberately NOT wired into project `forge setup`.
23
+ */
24
+
25
+ const {
26
+ GLOBAL_HOOK_HARNESSES,
27
+ renderGlobalHookBlock,
28
+ installGlobalHooks,
29
+ } = require('../hook-global-installer');
30
+ const { sessionStartCapability, userPromptSubmitCapability } = require('../hook-renderer');
31
+ const { collectDigestData, buildMemoryDigest } = require('../memory-digest');
32
+ const { collectInbox, buildInboxNudge } = require('../inbox');
33
+
34
+ function usage() {
35
+ return 'Usage: forge hooks install --global [--harness codex|hermes|all] [--dry-run]\n'
36
+ + ' forge hooks session-start --harness <claude> (machine-facing; emits SessionStart context)\n'
37
+ + ' forge hooks inbox-pickup --harness <claude> (machine-facing; emits UserPromptSubmit context)';
38
+ }
39
+
40
+ /** Parse `--harness <h>` (defaults to claude) from a session-start arg slice. */
41
+ function parseHarness(rest) {
42
+ for (let i = 0; i < rest.length; i += 1) {
43
+ if (rest[i] === '--harness') return rest[i + 1] || 'claude';
44
+ if (rest[i].startsWith('--harness=')) return rest[i].slice('--harness='.length);
45
+ }
46
+ return 'claude';
47
+ }
48
+
49
+ /** Wrap a digest into a harness-native SessionStart payload, or '' when unsupported. */
50
+ function formatSessionStart(harness, text) {
51
+ if (harness === 'claude') {
52
+ return JSON.stringify({
53
+ hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: text },
54
+ });
55
+ }
56
+ // No other harness has a verified session-start context surface — emit nothing.
57
+ return '';
58
+ }
59
+
60
+ /**
61
+ * `forge hooks session-start --harness <h>` — the CONTEXT hook Forge PUSHES to an agent
62
+ * at session start. Machine-facing plumbing: emits harness-native SessionStart JSON on
63
+ * stdout (Claude: { hookSpecificOutput.additionalContext }). FAIL-OPEN by construction —
64
+ * any failure, an unsupported harness, or an empty digest yields '' (the harness then
65
+ * injects nothing). NEVER throws and NEVER emits malformed JSON.
66
+ *
67
+ * @param {string[]} rest - args after the `session-start` action.
68
+ * @param {string} projectRoot
69
+ * @param {object} [opts] - injectable digest fetchers ({ fetchNotes, fetchIssues }).
70
+ * @returns {Promise<{ success: boolean, output: string }>}
71
+ */
72
+ async function handleSessionStart(rest, projectRoot, opts = {}) {
73
+ try {
74
+ const harness = parseHarness(rest);
75
+ if (!sessionStartCapability(harness).rendered) return { success: true, output: '' };
76
+ const data = await collectDigestData(projectRoot, opts);
77
+ const digest = buildMemoryDigest(data, opts);
78
+ if (digest.empty) return { success: true, output: '' };
79
+ return { success: true, output: formatSessionStart(harness, digest.text) };
80
+ } catch {
81
+ // Fail-open: a context hook must never break a session.
82
+ return { success: true, output: '' };
83
+ }
84
+ }
85
+
86
+ /** Wrap a digest into a harness-native UserPromptSubmit payload, or '' when unsupported. */
87
+ function formatUserPromptSubmit(harness, text) {
88
+ if (harness === 'claude') {
89
+ return JSON.stringify({
90
+ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: text },
91
+ });
92
+ }
93
+ // No other harness has a verified UserPromptSubmit context surface — emit nothing.
94
+ return '';
95
+ }
96
+
97
+ /**
98
+ * `forge hooks inbox-pickup --harness <h>` — the COMPLIANT comment-back CONTEXT hook. On each
99
+ * prompt it emits harness-native UserPromptSubmit JSON carrying a COMPACT count+pointer nudge
100
+ * (Claude: { hookSpecificOutput.additionalContext }). It deliberately does NOT re-emit the full
101
+ * fenced bodies: Claude APPENDS UserPromptSubmit additionalContext to history on every prompt
102
+ * (it does not replace prior injections — a documented context-bloat limitation), so a compact
103
+ * nudge keeps per-prompt accumulation negligible while still flagging "you have pending items,
104
+ * run `forge inbox`". The full fenced digest surfaces once at SessionStart and on demand via
105
+ * `forge inbox`. It reads the user's OWN kernel data via a supported hook — it NEVER injects
106
+ * into a running session's stdin and NEVER drives the agent programmatically (Anthropic Usage
107
+ * Policy, kernel issue 6d10c1a1). FAIL-OPEN: any failure, an unsupported harness, or nothing
108
+ * pending yields '' (the harness injects nothing). NEVER throws, NEVER emits malformed JSON.
109
+ *
110
+ * @param {string[]} rest - args after the `inbox-pickup` action.
111
+ * @param {string} projectRoot
112
+ * @param {object} [opts] - injectable inbox fetchers ({ fetchClaims, fetchComments, ... }).
113
+ * @returns {Promise<{ success: boolean, output: string }>}
114
+ */
115
+ async function handleInboxPickup(rest, projectRoot, opts = {}) {
116
+ try {
117
+ const harness = parseHarness(rest);
118
+ if (!userPromptSubmitCapability(harness).rendered) return { success: true, output: '' };
119
+ const pending = await collectInbox(projectRoot, opts);
120
+ const nudge = buildInboxNudge(pending);
121
+ if (nudge.empty) return { success: true, output: '' };
122
+ return { success: true, output: formatUserPromptSubmit(harness, nudge.text) };
123
+ } catch {
124
+ // Fail-open: a context hook must never break a prompt.
125
+ return { success: true, output: '' };
126
+ }
127
+ }
128
+
129
+ function parseInstallArgs(rest) {
130
+ const parsed = { global: false, dryRun: false, harness: 'all', unknown: [] };
131
+ for (let i = 0; i < rest.length; i += 1) {
132
+ const arg = rest[i];
133
+ if (arg === '--global') parsed.global = true;
134
+ else if (arg === '--dry-run') parsed.dryRun = true;
135
+ else if (arg === '--harness') { parsed.harness = rest[i + 1]; i += 1; }
136
+ else if (arg.startsWith('--harness=')) parsed.harness = arg.slice('--harness='.length);
137
+ else parsed.unknown.push(arg);
138
+ }
139
+ return parsed;
140
+ }
141
+
142
+ function indent(text, prefix) {
143
+ return String(text).replace(/\s+$/, '').split('\n').map(l => prefix + l).join('\n');
144
+ }
145
+
146
+ const GLOBAL_CONSENT_ERROR = [
147
+ 'forge hooks install writes GLOBAL (home-directory) harness config:',
148
+ ' - Codex : $CODEX_HOME/config.toml (default ~/.codex/config.toml)',
149
+ ' - Hermes: ~/.hermes/config.yaml',
150
+ 'Global config affects EVERY project on this machine, so Forge never writes it',
151
+ 'silently — re-run with the explicit --global flag to consent, and add --dry-run',
152
+ 'first to preview exactly what would be written.',
153
+ usage(),
154
+ ].join('\n');
155
+
156
+ const INSTALL_FOOTER = [
157
+ '',
158
+ 'Note: the hook commands invoke `node .forge/hooks/forge-native-hook.js` relative',
159
+ 'to the workspace root (Codex and Hermes run hooks from there), so enforcement',
160
+ 'applies inside Forge-initialized projects; elsewhere the adapter is absent and',
161
+ 'the hook fails open (no deny decision is emitted), leaving tool calls untouched.',
162
+ ];
163
+
164
+ /** Validate parsed install args; returns an error string or null when valid. */
165
+ function validateInstallArgs(parsed) {
166
+ if (parsed.unknown.length > 0) return `Unknown argument(s): ${parsed.unknown.join(' ')}\n${usage()}`;
167
+ if (!parsed.global) return GLOBAL_CONSENT_ERROR;
168
+ const harnesses = parsed.harness === 'all' ? GLOBAL_HOOK_HARNESSES : [parsed.harness];
169
+ if (!harnesses.every(h => GLOBAL_HOOK_HARNESSES.includes(h))) {
170
+ return `Unknown --harness '${parsed.harness}'. Allowed: codex, hermes, all.\n${usage()}`;
171
+ }
172
+ return null;
173
+ }
174
+
175
+ /** Format the output lines for a single install result. */
176
+ function renderInstallResult(res, dryRun) {
177
+ const lines = ['', `${res.harness} -> ${res.file}`];
178
+ if (res.skipped) {
179
+ lines.push(` SKIPPED (left untouched): ${res.reason}`);
180
+ if (res.backup) lines.push(` Backed up existing file to: ${res.backup} (.bak)`);
181
+ else if (dryRun) lines.push(' (dry-run: existing file would be backed up to a .bak and skipped)');
182
+ return lines;
183
+ }
184
+ lines.push(indent(renderGlobalHookBlock(res.harness), ' '));
185
+ if (dryRun) {
186
+ lines.push(res.changed === false
187
+ ? ' [dry-run] already up to date — a real run would change nothing'
188
+ : ' [dry-run] would merge the block above into this file');
189
+ } else {
190
+ lines.push(res.changed === false
191
+ ? ' Already up to date (no changes written).'
192
+ : ` Merged Forge hooks into ${res.existed ? 'existing' : 'new'} config.`);
193
+ }
194
+ return lines;
195
+ }
196
+
197
+ /** The `install` action — consent-guarded GLOBAL hook install (Codex/Hermes). */
198
+ function handleInstall(args, flags, opts) {
199
+ const parsed = parseInstallArgs(args.slice(1));
200
+ const dryRun = parsed.dryRun || Boolean(flags.dryRun);
201
+
202
+ const validationError = validateInstallArgs(parsed);
203
+ if (validationError) return { success: false, error: validationError };
204
+
205
+ const harnesses = parsed.harness === 'all' ? GLOBAL_HOOK_HARNESSES : [parsed.harness];
206
+ // env/homeDir are injectable through the command opts so tests never touch the
207
+ // real home directory; real dispatch passes neither and gets the defaults.
208
+ const results = installGlobalHooks({ harnesses, dryRun, env: opts.env || process.env, homeDir: opts.homeDir });
209
+
210
+ const out = [
211
+ dryRun ? 'forge hooks install --global (dry-run — nothing will be written)' : 'forge hooks install --global',
212
+ '',
213
+ 'This merges the following Forge hook block into each GLOBAL config,',
214
+ 'preserving all existing user config (idempotent re-runs):',
215
+ ...results.flatMap(res => renderInstallResult(res, dryRun)),
216
+ ...INSTALL_FOOTER,
217
+ ];
218
+ return { success: true, output: out.join('\n'), results };
219
+ }
220
+
221
+ async function handler(args, flags = {}, projectRoot, opts = {}) {
222
+ const action = args[0];
223
+ if (action === 'session-start') return handleSessionStart(args.slice(1), projectRoot, opts);
224
+ if (action === 'inbox-pickup') return handleInboxPickup(args.slice(1), projectRoot, opts);
225
+ if (action === 'install') return handleInstall(args, flags, opts);
226
+ return {
227
+ success: false,
228
+ error: `forge hooks supports: install, session-start, inbox-pickup.\n${usage()}`,
229
+ };
230
+ }
231
+
232
+ module.exports = {
233
+ name: 'hooks',
234
+ description: 'Opt-in install of Forge native hooks into GLOBAL harness config (Codex/Hermes)',
235
+ usage: usage(),
236
+ flags: {
237
+ '--global': 'Required consent flag — this command writes home-directory config',
238
+ '--harness': 'codex | hermes | all (default: all)',
239
+ '--dry-run': 'Preview the merge without writing anything',
240
+ },
241
+ handler,
242
+ };