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,377 @@
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, sessionEndCapability } = require('../hook-renderer');
31
+ const { collectDigestData, buildMemoryDigest, defaultFetchIssues, defaultFetchNotes } = require('../memory-digest');
32
+ const { collectInbox, buildInboxNudge } = require('../inbox');
33
+ const { collectDigest } = require('../pr-monitor/digest');
34
+
35
+ function usage() {
36
+ return 'Usage: forge hooks install --global [--harness codex|hermes|all] [--dry-run]\n'
37
+ + ' forge hooks session-start --harness <claude> (machine-facing; emits SessionStart context)\n'
38
+ + ' forge hooks inbox-pickup --harness <claude> (machine-facing; emits UserPromptSubmit context)\n'
39
+ + ' forge hooks shepherd-events --harness <claude> (machine-facing; emits UserPromptSubmit PR-monitor deltas)\n'
40
+ + ' forge hooks capture --harness <claude> --trigger <precompact|stop> (machine-facing; captures a session summary on exit)';
41
+ }
42
+
43
+ /** Parse `--harness <h>` (defaults to claude) from a session-start arg slice. */
44
+ function parseHarness(rest) {
45
+ for (let i = 0; i < rest.length; i += 1) {
46
+ if (rest[i] === '--harness') return rest[i + 1] || 'claude';
47
+ if (rest[i].startsWith('--harness=')) return rest[i].slice('--harness='.length);
48
+ }
49
+ return 'claude';
50
+ }
51
+
52
+ /** Parse `--trigger <t>` (defaults to stop) from a capture arg slice. */
53
+ function parseTrigger(rest) {
54
+ for (let i = 0; i < rest.length; i += 1) {
55
+ if (rest[i] === '--trigger') return rest[i + 1] || 'stop';
56
+ if (rest[i].startsWith('--trigger=')) return rest[i].slice('--trigger='.length);
57
+ }
58
+ return 'stop';
59
+ }
60
+
61
+ // Capture bounds — the snapshot is a small NUDGE, not a manual. A hard issue cap + per-title
62
+ // cap + overall body cap keep the note (which is re-injected at the NEXT session's SessionStart
63
+ // digest) token-bounded. Tags mark it a session-summary typed note AND a Forge auto-capture
64
+ // (the latter is the dedupe/idempotency key that stops per-turn Stop flooding).
65
+ const CAPTURE_ISSUE_CAP = 5;
66
+ const CAPTURE_TITLE_CAP = 80;
67
+ const CAPTURE_NOTE_CAP = 1000;
68
+ const CAPTURE_AUTO_TAG = 'forge:auto-capture';
69
+ const CAPTURE_TAGS = ['type:session-summary', CAPTURE_AUTO_TAG];
70
+
71
+ /**
72
+ * Build the deterministic, token-bounded capture note body. PURE. The body deliberately
73
+ * carries NO timestamp (the store stamps its own) so an unchanged session state yields a
74
+ * byte-identical body across repeated Stops — that identity is what the dedupe keys on.
75
+ * @param {string} trigger - 'precompact' | 'stop'
76
+ * @param {object[]} issues - in-progress issues (title/id defensively resolved)
77
+ * @returns {string}
78
+ */
79
+ function buildCaptureNote(trigger, issues) {
80
+ const capped = issues.slice(0, CAPTURE_ISSUE_CAP);
81
+ const lines = capped.map(issue => {
82
+ const title = String((issue && (issue.title || issue.id)) || 'untitled').replace(/\s+/g, ' ').trim();
83
+ return `- ${title.length > CAPTURE_TITLE_CAP ? `${title.slice(0, CAPTURE_TITLE_CAP)}…` : title}`;
84
+ });
85
+ const more = issues.length > capped.length ? `\n- …and ${issues.length - capped.length} more` : '';
86
+ const body = lines.length
87
+ ? `Session boundary (${trigger}) — in-progress:\n${lines.join('\n')}${more}`
88
+ : `Session boundary (${trigger}) — no in-progress issues.`;
89
+ // Reserve one char for the appended ellipsis so the FINAL note (incl. '…') is ≤ the cap,
90
+ // never CAPTURE_NOTE_CAP + 1.
91
+ return body.length > CAPTURE_NOTE_CAP ? `${body.slice(0, CAPTURE_NOTE_CAP - 1)}…` : body;
92
+ }
93
+
94
+ /** Wrap a digest into a harness-native SessionStart payload, or '' when unsupported. */
95
+ function formatSessionStart(harness, text) {
96
+ if (harness === 'claude') {
97
+ return JSON.stringify({
98
+ hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: text },
99
+ });
100
+ }
101
+ // No other harness has a verified session-start context surface — emit nothing.
102
+ return '';
103
+ }
104
+
105
+ /**
106
+ * `forge hooks session-start --harness <h>` — the CONTEXT hook Forge PUSHES to an agent
107
+ * at session start. Machine-facing plumbing: emits harness-native SessionStart JSON on
108
+ * stdout (Claude: { hookSpecificOutput.additionalContext }). FAIL-OPEN by construction —
109
+ * any failure, an unsupported harness, or an empty digest yields '' (the harness then
110
+ * injects nothing). NEVER throws and NEVER emits malformed JSON.
111
+ *
112
+ * @param {string[]} rest - args after the `session-start` action.
113
+ * @param {string} projectRoot
114
+ * @param {object} [opts] - injectable digest fetchers ({ fetchNotes, fetchIssues }).
115
+ * @returns {Promise<{ success: boolean, output: string }>}
116
+ */
117
+ async function handleSessionStart(rest, projectRoot, opts = {}) {
118
+ try {
119
+ const harness = parseHarness(rest);
120
+ if (!sessionStartCapability(harness).rendered) return { success: true, output: '' };
121
+ const data = await collectDigestData(projectRoot, opts);
122
+ const digest = buildMemoryDigest(data, opts);
123
+ if (digest.empty) return { success: true, output: '' };
124
+ return { success: true, output: formatSessionStart(harness, digest.text) };
125
+ } catch {
126
+ // Fail-open: a context hook must never break a session.
127
+ return { success: true, output: '' };
128
+ }
129
+ }
130
+
131
+ /** Wrap a digest into a harness-native UserPromptSubmit payload, or '' when unsupported. */
132
+ function formatUserPromptSubmit(harness, text) {
133
+ if (harness === 'claude') {
134
+ return JSON.stringify({
135
+ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: text },
136
+ });
137
+ }
138
+ // No other harness has a verified UserPromptSubmit context surface — emit nothing.
139
+ return '';
140
+ }
141
+
142
+ /**
143
+ * `forge hooks inbox-pickup --harness <h>` — the COMPLIANT comment-back CONTEXT hook. On each
144
+ * prompt it emits harness-native UserPromptSubmit JSON carrying a COMPACT count+pointer nudge
145
+ * (Claude: { hookSpecificOutput.additionalContext }). It deliberately does NOT re-emit the full
146
+ * fenced bodies: Claude APPENDS UserPromptSubmit additionalContext to history on every prompt
147
+ * (it does not replace prior injections — a documented context-bloat limitation), so a compact
148
+ * nudge keeps per-prompt accumulation negligible while still flagging "you have pending items,
149
+ * run `forge inbox`". The full fenced digest surfaces once at SessionStart and on demand via
150
+ * `forge inbox`. It reads the user's OWN kernel data via a supported hook — it NEVER injects
151
+ * into a running session's stdin and NEVER drives the agent programmatically (Anthropic Usage
152
+ * Policy, kernel issue 6d10c1a1). FAIL-OPEN: any failure, an unsupported harness, or nothing
153
+ * pending yields '' (the harness injects nothing). NEVER throws, NEVER emits malformed JSON.
154
+ *
155
+ * @param {string[]} rest - args after the `inbox-pickup` action.
156
+ * @param {string} projectRoot
157
+ * @param {object} [opts] - injectable inbox fetchers ({ fetchClaims, fetchComments, ... }).
158
+ * @returns {Promise<{ success: boolean, output: string }>}
159
+ */
160
+ async function handleInboxPickup(rest, projectRoot, opts = {}) {
161
+ try {
162
+ const harness = parseHarness(rest);
163
+ if (!userPromptSubmitCapability(harness).rendered) return { success: true, output: '' };
164
+ const pending = await collectInbox(projectRoot, opts);
165
+ const nudge = buildInboxNudge(pending);
166
+ if (nudge.empty) return { success: true, output: '' };
167
+ return { success: true, output: formatUserPromptSubmit(harness, nudge.text) };
168
+ } catch {
169
+ // Fail-open: a context hook must never break a prompt.
170
+ return { success: true, output: '' };
171
+ }
172
+ }
173
+
174
+ /**
175
+ * `forge hooks shepherd-events --harness <h>` — the PR-shepherd CONTEXT hook. On each
176
+ * prompt it emits harness-native UserPromptSubmit JSON carrying a COMPACT, capped digest
177
+ * of NEW PR-monitor events (verdict changes, failed checks, new threads, merged/closed)
178
+ * since the last read across all open-PR journals, then advances the per-PR consumer
179
+ * cursor so nothing re-surfaces. This is the CONSUMER side of the constant watcher: the
180
+ * watch loop writes the journal, this pushes the deltas to the working agent. It reads the
181
+ * user's OWN local journal via a supported hook — it NEVER injects into stdin and NEVER
182
+ * drives the agent (Anthropic Usage Policy). FAIL-OPEN: any failure, an unsupported
183
+ * harness, or no new events yields '' (the harness injects nothing). NEVER throws.
184
+ *
185
+ * @param {string[]} rest - args after the `shepherd-events` action.
186
+ * @param {string} projectRoot
187
+ * @param {object} [opts] - injectable digest collector ({ collectDigest }).
188
+ * @returns {{ success: boolean, output: string }}
189
+ */
190
+ function handleShepherdEvents(rest, projectRoot, opts = {}) {
191
+ try {
192
+ const harness = parseHarness(rest);
193
+ const capability = userPromptSubmitCapability(harness);
194
+ // Honest capability matrix: a non-Claude harness gets an explicit skip
195
+ // reason as surface-only result metadata (for callers/telemetry) but NEVER
196
+ // any injected output — `reason` must not drive the agent.
197
+ if (!capability.rendered) return { success: true, output: '', reason: capability.reason };
198
+ const collect = opts.collectDigest || collectDigest;
199
+ const { text } = collect({ root: projectRoot });
200
+ if (!text) return { success: true, output: '' };
201
+ return { success: true, output: formatUserPromptSubmit(harness, text) };
202
+ } catch {
203
+ // Fail-open: a context hook must never break a prompt.
204
+ return { success: true, output: '' };
205
+ }
206
+ }
207
+
208
+ /**
209
+ * `forge hooks capture --harness <h> --trigger <precompact|stop>` — the CAPTURE-on-exit hook.
210
+ * PreCompact (before context compaction) and Stop (turn end) fire it; it snapshots a bounded
211
+ * session-summary note into the memory store BEFORE learnings are lost. This is the WRITE half
212
+ * of Forge memory (SessionStart only INJECTS). It PERSISTS to the store and emits NO stdout — a
213
+ * Stop hook that printed text would inject into the turn, and it never drives the agent
214
+ * (Anthropic Usage Policy). FAIL-OPEN: any failure, an unsupported harness, or nothing worth
215
+ * capturing yields '' and no write. NEVER throws.
216
+ *
217
+ * Flooding guard: a plain Stop with nothing in progress is skipped (Stop fires every turn), and
218
+ * a byte-identical repeat of the newest auto-capture note is skipped — so only meaningful,
219
+ * changed session state is written. PreCompact records a boundary even when nothing is in
220
+ * progress (unlike Stop), but it still goes through the same dedupe — a byte-identical
221
+ * PreCompact repeat is skipped too.
222
+ *
223
+ * @param {string[]} rest - args after the `capture` action.
224
+ * @param {string} projectRoot
225
+ * @param {object} [opts] - injectable { fetchIssues, fetchNotes, append } for tests.
226
+ * @returns {Promise<{ success: boolean, output: string }>}
227
+ */
228
+ async function handleCapture(rest, projectRoot, opts = {}) {
229
+ try {
230
+ const harness = parseHarness(rest);
231
+ if (!sessionEndCapability(harness).rendered) return { success: true, output: '' };
232
+ const trigger = parseTrigger(rest);
233
+
234
+ const fetchIssues = opts.fetchIssues || defaultFetchIssues;
235
+ const claimed = await fetchIssues(projectRoot, 'in_progress', opts);
236
+ const issues = Array.isArray(claimed) ? claimed : [];
237
+
238
+ // Stop fires every turn; a plain Stop with nothing in progress is not worth a note.
239
+ // PreCompact is rare and precedes real context loss, so it records a boundary even with
240
+ // nothing in progress — but it is NOT exempt from the byte-identical dedupe below.
241
+ if (trigger !== 'precompact' && issues.length === 0) return { success: true, output: '' };
242
+
243
+ const body = buildCaptureNote(trigger, issues);
244
+
245
+ // Content dedupe: if the newest auto-capture note is byte-identical, this is a repeat of
246
+ // an unchanged session — skip the write so the store never floods with duplicates.
247
+ const fetchNotes = opts.fetchNotes || defaultFetchNotes;
248
+ const recent = await fetchNotes(projectRoot, { ...opts, noteLimit: 10 });
249
+ const lastCapture = (Array.isArray(recent) ? recent : [])
250
+ .find(note => Array.isArray(note && note.tags) && note.tags.includes(CAPTURE_AUTO_TAG));
251
+ if (lastCapture && lastCapture.note === body) return { success: true, output: '' };
252
+
253
+ const append = opts.append || require('../memory/router').append;
254
+ append(projectRoot, body, { tags: CAPTURE_TAGS });
255
+ return { success: true, output: '' };
256
+ } catch {
257
+ // Fail-open: a capture hook must never break a session.
258
+ return { success: true, output: '' };
259
+ }
260
+ }
261
+
262
+ function parseInstallArgs(rest) {
263
+ const parsed = { global: false, dryRun: false, harness: 'all', unknown: [] };
264
+ for (let i = 0; i < rest.length; i += 1) {
265
+ const arg = rest[i];
266
+ if (arg === '--global') parsed.global = true;
267
+ else if (arg === '--dry-run') parsed.dryRun = true;
268
+ else if (arg === '--harness') { parsed.harness = rest[i + 1]; i += 1; }
269
+ else if (arg.startsWith('--harness=')) parsed.harness = arg.slice('--harness='.length);
270
+ else parsed.unknown.push(arg);
271
+ }
272
+ return parsed;
273
+ }
274
+
275
+ function indent(text, prefix) {
276
+ return String(text).replace(/\s+$/, '').split('\n').map(l => prefix + l).join('\n');
277
+ }
278
+
279
+ const GLOBAL_CONSENT_ERROR = [
280
+ 'forge hooks install writes GLOBAL (home-directory) harness config:',
281
+ ' - Codex : $CODEX_HOME/config.toml (default ~/.codex/config.toml)',
282
+ ' - Hermes: ~/.hermes/config.yaml',
283
+ 'Global config affects EVERY project on this machine, so Forge never writes it',
284
+ 'silently — re-run with the explicit --global flag to consent, and add --dry-run',
285
+ 'first to preview exactly what would be written.',
286
+ usage(),
287
+ ].join('\n');
288
+
289
+ const INSTALL_FOOTER = [
290
+ '',
291
+ 'Note: the hook commands invoke `node .forge/hooks/forge-native-hook.js` relative',
292
+ 'to the workspace root (Codex and Hermes run hooks from there), so enforcement',
293
+ 'applies inside Forge-initialized projects; elsewhere the adapter is absent and',
294
+ 'the hook fails open (no deny decision is emitted), leaving tool calls untouched.',
295
+ ];
296
+
297
+ /** Validate parsed install args; returns an error string or null when valid. */
298
+ function validateInstallArgs(parsed) {
299
+ if (parsed.unknown.length > 0) return `Unknown argument(s): ${parsed.unknown.join(' ')}\n${usage()}`;
300
+ if (!parsed.global) return GLOBAL_CONSENT_ERROR;
301
+ const harnesses = parsed.harness === 'all' ? GLOBAL_HOOK_HARNESSES : [parsed.harness];
302
+ if (!harnesses.every(h => GLOBAL_HOOK_HARNESSES.includes(h))) {
303
+ return `Unknown --harness '${parsed.harness}'. Allowed: codex, hermes, all.\n${usage()}`;
304
+ }
305
+ return null;
306
+ }
307
+
308
+ /** Format the output lines for a single install result. */
309
+ function renderInstallResult(res, dryRun) {
310
+ const lines = ['', `${res.harness} -> ${res.file}`];
311
+ if (res.skipped) {
312
+ lines.push(` SKIPPED (left untouched): ${res.reason}`);
313
+ if (res.backup) lines.push(` Backed up existing file to: ${res.backup} (.bak)`);
314
+ else if (dryRun) lines.push(' (dry-run: existing file would be backed up to a .bak and skipped)');
315
+ return lines;
316
+ }
317
+ lines.push(indent(renderGlobalHookBlock(res.harness), ' '));
318
+ if (dryRun) {
319
+ lines.push(res.changed === false
320
+ ? ' [dry-run] already up to date — a real run would change nothing'
321
+ : ' [dry-run] would merge the block above into this file');
322
+ } else {
323
+ lines.push(res.changed === false
324
+ ? ' Already up to date (no changes written).'
325
+ : ` Merged Forge hooks into ${res.existed ? 'existing' : 'new'} config.`);
326
+ }
327
+ return lines;
328
+ }
329
+
330
+ /** The `install` action — consent-guarded GLOBAL hook install (Codex/Hermes). */
331
+ function handleInstall(args, flags, opts) {
332
+ const parsed = parseInstallArgs(args.slice(1));
333
+ const dryRun = parsed.dryRun || Boolean(flags.dryRun);
334
+
335
+ const validationError = validateInstallArgs(parsed);
336
+ if (validationError) return { success: false, error: validationError };
337
+
338
+ const harnesses = parsed.harness === 'all' ? GLOBAL_HOOK_HARNESSES : [parsed.harness];
339
+ // env/homeDir are injectable through the command opts so tests never touch the
340
+ // real home directory; real dispatch passes neither and gets the defaults.
341
+ const results = installGlobalHooks({ harnesses, dryRun, env: opts.env || process.env, homeDir: opts.homeDir });
342
+
343
+ const out = [
344
+ dryRun ? 'forge hooks install --global (dry-run — nothing will be written)' : 'forge hooks install --global',
345
+ '',
346
+ 'This merges the following Forge hook block into each GLOBAL config,',
347
+ 'preserving all existing user config (idempotent re-runs):',
348
+ ...results.flatMap(res => renderInstallResult(res, dryRun)),
349
+ ...INSTALL_FOOTER,
350
+ ];
351
+ return { success: true, output: out.join('\n'), results };
352
+ }
353
+
354
+ async function handler(args, flags = {}, projectRoot, opts = {}) {
355
+ const action = args[0];
356
+ if (action === 'session-start') return handleSessionStart(args.slice(1), projectRoot, opts);
357
+ if (action === 'inbox-pickup') return handleInboxPickup(args.slice(1), projectRoot, opts);
358
+ if (action === 'shepherd-events') return handleShepherdEvents(args.slice(1), projectRoot, opts);
359
+ if (action === 'capture') return handleCapture(args.slice(1), projectRoot, opts);
360
+ if (action === 'install') return handleInstall(args, flags, opts);
361
+ return {
362
+ success: false,
363
+ error: `forge hooks supports: install, session-start, inbox-pickup, shepherd-events, capture.\n${usage()}`,
364
+ };
365
+ }
366
+
367
+ module.exports = {
368
+ name: 'hooks',
369
+ description: 'Opt-in install of Forge native hooks into GLOBAL harness config (Codex/Hermes)',
370
+ usage: usage(),
371
+ flags: {
372
+ '--global': 'Required consent flag — this command writes home-directory config',
373
+ '--harness': 'codex | hermes | all (default: all)',
374
+ '--dry-run': 'Preview the merge without writing anything',
375
+ },
376
+ handler,
377
+ };
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `forge inbox` — the SUPPORTED, Anthropic-compliant "comment back to the agent" surface.
5
+ *
6
+ * COMPLIANCE BOUNDARY (verified Anthropic Usage Policy + Claude Code hooks docs, kernel
7
+ * issue 6d10c1a1, 2026-07-13): this reads/writes KERNEL DATA only. The dashboard EDITS the
8
+ * kernel (comments via the broker); the user's OWN human-driven session reads them via
9
+ * supported hooks (SessionStart / UserPromptSubmit) on its next natural turn. There is NO
10
+ * stdin/tty injection anywhere here and the agent is NEVER driven programmatically.
11
+ *
12
+ * forge inbox List unacked, targeted instruction comments.
13
+ * forge inbox --json Machine-readable envelope for the dashboard / scripts.
14
+ * forge inbox ack <id> Post an `ack:<id>` reply on the instruction's issue.
15
+ */
16
+
17
+ const {
18
+ collectInbox,
19
+ ackBody,
20
+ DASHBOARD_INBOX_TITLE,
21
+ DASHBOARD_INBOX_TYPE,
22
+ } = require('../inbox');
23
+ const { fenceUntrusted, neutralize } = require('../untrusted-content');
24
+
25
+ function usage() {
26
+ return 'Usage: forge inbox [--json]\n'
27
+ + ' forge inbox ack <comment_id>';
28
+ }
29
+
30
+ /** True when the arg list requested JSON output (flag or FORGE_JSON=1). */
31
+ function wantsJson(args, opts) {
32
+ return args.includes('--json') || (opts.env || process.env).FORGE_JSON === '1';
33
+ }
34
+
35
+ /** Resolve the shared issue runner (injectable for tests). */
36
+ function issueRunner(opts) {
37
+ return opts.runIssueOperation || require('../forge-issues').runIssueOperation;
38
+ }
39
+
40
+ /**
41
+ * One human-readable inbox line. The comment BODY + author are untrusted external content
42
+ * (MINOR-1), so both are neutralize()'d — a planted fence glyph cannot forge a marker — and
43
+ * the whole block is fenced in handleList for parity with the SessionStart/UserPromptSubmit
44
+ * digests (this list is read by the agent too).
45
+ */
46
+ function renderItem(item) {
47
+ const from = item.actor ? ` — from ${neutralize(item.actor)}` : '';
48
+ return ` [${item.basis}] ${item.comment_id}${from}\n ${neutralize(item.text)}`;
49
+ }
50
+
51
+ /** `forge inbox` (list). */
52
+ async function handleList(args, projectRoot, opts) {
53
+ const pending = await collectInbox(projectRoot, opts);
54
+ if (wantsJson(args, opts)) {
55
+ return { success: true, output: JSON.stringify({ ok: true, count: pending.length, inbox: pending }, null, 2) };
56
+ }
57
+ if (!pending.length) {
58
+ return { success: true, output: 'No pending dashboard instructions.' };
59
+ }
60
+ // The item block is untrusted external content (comment bodies), so it is provenance-fenced
61
+ // for parity with the digests — the agent that reads `forge inbox` gets the same DATA-only
62
+ // signal. Bodies are already neutralize()'d in renderItem, so no glyph can forge the fence.
63
+ const body = pending.map(renderItem).join('\n');
64
+ const lines = [
65
+ `${pending.length} pending dashboard instruction(s) — act, then \`forge inbox ack <id>\`:`,
66
+ '',
67
+ fenceUntrusted(body, { source: 'dashboard-comment' }),
68
+ ];
69
+ return { success: true, output: lines.join('\n') };
70
+ }
71
+
72
+ /** `forge inbox ack <comment_id>` — post the ack reply on the instruction's issue. */
73
+ async function handleAck(rest, projectRoot, opts) {
74
+ const commentId = (rest || []).find(arg => typeof arg === 'string' && !arg.startsWith('-'));
75
+ if (!commentId) {
76
+ return { success: false, error: `Missing <comment_id>.\n${usage()}`, exitCode: 6 };
77
+ }
78
+ const pending = await collectInbox(projectRoot, opts);
79
+ const target = pending.find(item => item.comment_id === commentId);
80
+ if (!target) {
81
+ return {
82
+ success: false,
83
+ error: `No pending instruction with id ${commentId} is targeted at this session `
84
+ + '(already acked, or not addressed here). Run `forge inbox` to see pending items.',
85
+ exitCode: 1,
86
+ };
87
+ }
88
+ const runIssueOperation = issueRunner(opts);
89
+ const result = await runIssueOperation('comment', [target.issue_id, ackBody(commentId)], projectRoot);
90
+ if (!result || (result.ok !== true && result.success !== true)) {
91
+ return { success: false, error: `Failed to post ack for ${commentId}.`, exitCode: 1 };
92
+ }
93
+ return { success: true, output: `Acked ${commentId} on ${target.issue_id}.` };
94
+ }
95
+
96
+ async function handler(args, _flags = {}, projectRoot, opts = {}) {
97
+ const action = args[0];
98
+ if (action === '--help' || action === '-h') {
99
+ return { success: true, output: usage() };
100
+ }
101
+ if (action === 'ack') {
102
+ return handleAck(args.slice(1), projectRoot, opts);
103
+ }
104
+ return handleList(args, projectRoot, opts);
105
+ }
106
+
107
+ module.exports = {
108
+ name: 'inbox',
109
+ description: 'List and ack targeted dashboard instruction comments (compliant comment-back)',
110
+ usage: usage(),
111
+ flags: {
112
+ '--json': 'Machine-readable envelope for the dashboard / scripts',
113
+ },
114
+ handler,
115
+ // exported for tests / reuse
116
+ DASHBOARD_INBOX_TITLE,
117
+ DASHBOARD_INBOX_TYPE,
118
+ };