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,211 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `forge preflight` — fast (~2-4 min) blast-radius gate.
5
+ *
6
+ * Gives DETERMINISTIC-GATE PARITY with CI so an agent catches what CI catches
7
+ * WITHOUT waiting on the hanging full suite. Runs four gates in order, fast-fail:
8
+ *
9
+ * 1. lint — eslint --max-warnings 0 on changed files (--all → whole tree)
10
+ * 2. drift — skills mirror + registry sync + agentic-workflow structural asserts
11
+ * 3. sonar — eslint-plugin-sonarjs cognitive-complexity=15 (+ parity rules)
12
+ * 4. affected — only the tests mapped from the changed files
13
+ *
14
+ * Exit non-zero on any gate failure. Run this before `forge push`.
15
+ *
16
+ * @module commands/preflight
17
+ */
18
+
19
+ const { execFileSync } = require('node:child_process');
20
+ const { buildGates } = require('../preflight/gates');
21
+ const { runGates } = require('../preflight/runner');
22
+
23
+ /**
24
+ * Strictly resolve a base ref to diff HEAD against. Returns the ref name, or
25
+ * null when no base can be established (fresh repo: no origin/HEAD, no upstream,
26
+ * no main/master ref). Unlike test.js's resolver, this never falls back to a
27
+ * default name that doesn't exist — a missing base must be reported, not faked.
28
+ *
29
+ * @param {Function} exec - execFileSync-compatible
30
+ * @returns {string|null}
31
+ */
32
+ /** Run a git command and return trimmed stdout, or null on any failure. */
33
+ function gitTryOut(exec, args) {
34
+ try {
35
+ return String(exec('git', args, { encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'] }) || '').trim();
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ /** True when `ref` resolves to a real object in this repo. */
42
+ function gitVerifyRef(exec, ref) {
43
+ try {
44
+ exec('git', ['rev-parse', '--verify', '--quiet', ref], { stdio: 'ignore', timeout: 3000 });
45
+ return true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * True when `ref` resolves to the SAME commit as HEAD. Diffing HEAD against such
53
+ * a ref yields zero files, so it is never a usable integration base.
54
+ */
55
+ function refEqualsHead(exec, ref) {
56
+ const refSha = gitTryOut(exec, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
57
+ if (!refSha) return false;
58
+ const headSha = gitTryOut(exec, ['rev-parse', '--verify', '--quiet', 'HEAD^{commit}']);
59
+ return !!headSha && refSha === headSha;
60
+ }
61
+
62
+ function resolveBaseRef(exec) {
63
+ const originHead = gitTryOut(exec, ['rev-parse', '--abbrev-ref', 'origin/HEAD']);
64
+ if (originHead && originHead !== 'origin/HEAD') return originHead;
65
+
66
+ // `@{upstream}` is the CURRENT branch's OWN remote-tracking ref. On a fully
67
+ // pushed feature branch it points AT HEAD, so diffing against it produces 0
68
+ // changed files and every gate passes vacuously (fail-OPEN). Accept it as a
69
+ // base only when it is a real integration point ahead of us — never when it
70
+ // resolves to our own HEAD. Fall through to a real base (origin/main, …) or
71
+ // FAIL if none exists, rather than manufacture a wrong-but-'resolved' base.
72
+ const upstream = gitTryOut(exec, ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}']);
73
+ if (upstream && !refEqualsHead(exec, upstream)) return upstream;
74
+
75
+ for (const ref of ['origin/main', 'origin/master', 'main', 'master']) {
76
+ if (gitVerifyRef(exec, ref)) return ref;
77
+ }
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * Resolve the change set for preflight, distinguishing "base resolved, genuinely
83
+ * zero changes" from "no base branch could be resolved". Only the latter is a
84
+ * gate failure — a fresh repo with no base cannot have its blast radius computed,
85
+ * so preflight must NOT pass vacuously.
86
+ *
87
+ * @param {Function} [exec] - execFileSync-compatible (injectable for tests)
88
+ * @param {{ runAll?: boolean }} [opts]
89
+ * @returns {{ resolved: boolean, changedFiles: string[], reason?: string, baseRef?: string }}
90
+ */
91
+ function resolveChangeSet(exec = execFileSync, { runAll = false } = {}) {
92
+ if (runAll) {
93
+ return { resolved: true, changedFiles: [], reason: 'whole-tree scope (--all)' };
94
+ }
95
+ const baseRef = resolveBaseRef(exec);
96
+ if (!baseRef) {
97
+ return {
98
+ resolved: false,
99
+ changedFiles: [],
100
+ reason: 'could not resolve a base branch (no origin/HEAD, no upstream, no main/master ref)',
101
+ };
102
+ }
103
+ // Diff against the SAME base we just resolved. getChangedFiles() re-resolves
104
+ // its own diff ref (and can fall back to a different default branch), so use
105
+ // baseRef directly to keep the file list consistent with the resolved base.
106
+ return { resolved: true, baseRef, changedFiles: changedFilesForBase(exec, baseRef) };
107
+ }
108
+
109
+ /** Files changed between the merge-base of HEAD and `baseRef` and HEAD. */
110
+ function changedFilesForBase(exec, baseRef) {
111
+ const mergeBase = gitTryOut(exec, ['merge-base', 'HEAD', baseRef]);
112
+ const range = mergeBase ? `${mergeBase}...HEAD` : `${baseRef}...HEAD`;
113
+ const out = gitTryOut(exec, ['diff', '--name-only', range]);
114
+ if (out == null) return [];
115
+ return out.split(/\r?\n/).filter(Boolean);
116
+ }
117
+
118
+ /**
119
+ * Pick the change set: an injected resolveChangeSet wins; a legacy injected
120
+ * resolveChangedFiles vouches for resolution; otherwise resolve strictly.
121
+ */
122
+ function selectChangeSet(deps, runAll) {
123
+ if (deps.resolveChangeSet) {
124
+ return deps.resolveChangeSet({ runAll });
125
+ }
126
+ if (deps.resolveChangedFiles) {
127
+ return { resolved: true, changedFiles: deps.resolveChangedFiles() || [] };
128
+ }
129
+ return resolveChangeSet(execFileSync, { runAll });
130
+ }
131
+
132
+ /** Print the fail-closed remedy when no base branch could be resolved. */
133
+ function reportBaseUnresolved(log, reason) {
134
+ log('forge preflight — fast blast-radius gate (deterministic parity with CI)');
135
+ log('');
136
+ log(`preflight FAILED — ${reason}.`);
137
+ log(' Remedy: give preflight a base branch to diff against, e.g.');
138
+ log(' git fetch origin && git branch --set-upstream-to=origin/main # your default branch');
139
+ log(' (or ensure origin/HEAD is set: git remote set-head origin -a)');
140
+ log(' Or scan the whole tree explicitly: forge preflight --all');
141
+ }
142
+
143
+ /** Summary mark for one gate result. */
144
+ function gateMark(result) {
145
+ if (result.skipped) return 'SKIP';
146
+ return result.ok ? 'PASS' : 'FAIL';
147
+ }
148
+
149
+ module.exports = {
150
+ name: 'preflight',
151
+ description: 'Fast deterministic-gate parity with CI (lint, drift/registry, sonar, affected tests)',
152
+ usage: 'forge preflight [--all]',
153
+ flags: {
154
+ '--all': 'Lint/scan the whole tree instead of only changed files',
155
+ },
156
+
157
+ /**
158
+ * @param {string[]} _args
159
+ * @param {Object} flags
160
+ * @param {string} projectRoot
161
+ * @param {Object} [deps] - injectable { log, resolveChangedFiles, buildGates, runGates }
162
+ * @returns {Promise<{ success: boolean, results?: Array }>}
163
+ */
164
+ handler: async (_args, flags, projectRoot, deps = {}) => {
165
+ const log = deps.log || console.log;
166
+ const build = deps.buildGates || buildGates;
167
+ const run = deps.runGates || runGates;
168
+
169
+ const runAll = !!(flags && (flags['--all'] || flags.all));
170
+
171
+ // Resolve the change set AND whether a base branch could be established.
172
+ const changeSet = selectChangeSet(deps, runAll) || {};
173
+
174
+ // B2 (N1): an unresolvable base is fail-CLOSED. Computing "0 changed files"
175
+ // from a missing base and passing every gate manufactures false confidence.
176
+ // Anything not EXPLICITLY resolved (including a missing `resolved` field from
177
+ // a future override) fails closed — never fail open on an indeterminate base.
178
+ if (!runAll && !changeSet.resolved) {
179
+ const reason = changeSet.reason || 'base branch could not be resolved';
180
+ reportBaseUnresolved(log, reason);
181
+ return { success: false, results: [], reason, baseUnresolved: true };
182
+ }
183
+
184
+ const changedFiles = changeSet.changedFiles || [];
185
+
186
+ log('forge preflight — fast blast-radius gate (deterministic parity with CI)');
187
+ log(runAll
188
+ ? ' scope: whole tree (--all)'
189
+ : ` scope: ${changedFiles.length} changed file(s) vs base branch`);
190
+ log('');
191
+
192
+ const gates = build({ projectRoot, changedFiles, runAll });
193
+ const { ok, results } = await run(gates, { log: (line) => log(` ${line}`) });
194
+
195
+ log('');
196
+ log('Preflight summary:');
197
+ for (const r of results) {
198
+ const suffix = r.summary ? ` — ${r.summary}` : '';
199
+ log(` [${gateMark(r)}] ${r.name}${suffix}`);
200
+ }
201
+ log('');
202
+ log(ok
203
+ ? 'preflight passed — safe to push'
204
+ : 'preflight FAILED — fix the failing gate above before `forge push`');
205
+
206
+ return { success: ok, results };
207
+ },
208
+ };
209
+
210
+ module.exports.resolveChangeSet = resolveChangeSet;
211
+ module.exports.resolveBaseRef = resolveBaseRef;
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ buildPrime,
5
+ runOrientationCommand,
6
+ } = require('../orientation');
7
+
8
+ module.exports = {
9
+ name: 'prime',
10
+ description: 'Emit session-entry bounded orientation for agents',
11
+ usage: 'Usage: forge prime [--budget N] [--json]',
12
+ handler: (args, _flags, projectRoot) => runOrientationCommand(buildPrime, args, projectRoot),
13
+ };
@@ -54,6 +54,62 @@ function isFirstPush(branch, execFn) {
54
54
  }
55
55
  }
56
56
 
57
+ /**
58
+ * Resolve the git worktree top-level directory.
59
+ *
60
+ * The pre-push hook checks for the nonce token relative to its own cwd, which
61
+ * git sets to the worktree root during `git push`. The handler's `projectRoot`
62
+ * is derived from INIT_CWD/cwd and can point at the MAIN repo root when forge
63
+ * runs from a linked worktree, so the token must be written to the real git
64
+ * top-level instead — otherwise lefthook never finds it and re-runs the suite.
65
+ *
66
+ * @param {function} execFn - execFileSync or mock
67
+ * @param {string} fallback - projectRoot to use when git resolution fails
68
+ * @returns {string} Absolute worktree top-level path, or the fallback
69
+ */
70
+ function resolveGitToplevel(execFn, fallback) {
71
+ try {
72
+ const top = execFn('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim();
73
+ return top || fallback;
74
+ } catch (_err) { /* intentional: fall back to projectRoot outside a git worktree */ // NOSONAR S2486
75
+ return fallback;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Auto-file rail: best-effort, NON-BLOCKING ensure that the branch being pushed has a
81
+ * backing Kernel issue (kernel issue a4b8f56f) — so started work that is being pushed
82
+ * never goes unfiled. Idempotent (deduped by branch via ensureBackingIssue); a
83
+ * main/master/detached/ignore branch is skipped, and ANY failure degrades to null and
84
+ * NEVER aborts the push. Injectable via `deps._ensureBackingIssue` for tests.
85
+ *
86
+ * @param {string} projectRoot
87
+ * @param {function} execFn - execFileSync (for branch resolution)
88
+ * @param {object} deps - DI (may inject `_ensureBackingIssue`, `_kernelDriver`, `_kernelBroker`)
89
+ * @returns {Promise<object|null>}
90
+ */
91
+ async function autoFileBackingIssueForPush(projectRoot, execFn, deps = {}) {
92
+ try {
93
+ // Lazy require INSIDE the try: a module-load failure must degrade to null and
94
+ // NEVER abort the push (mirrors the worktree helper; CodeRabbit #370).
95
+ const ensure = deps._ensureBackingIssue || require('../kernel/backing-issue').ensureBackingIssue;
96
+ const branch = getCurrentBranch(execFn);
97
+ if (!branch || branch === 'unknown') return null;
98
+ let driver = deps._kernelDriver;
99
+ let broker = deps._kernelBroker;
100
+ if (!driver || !broker) {
101
+ if (!fs.existsSync(path.join(projectRoot, '.git'))) return null;
102
+ const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
103
+ const built = await buildMigratedKernelIssueDeps({ projectRoot });
104
+ driver = driver || built.kernelDriver;
105
+ broker = broker || built.kernelBroker;
106
+ }
107
+ return await ensure({ branch, projectRoot, driver, broker });
108
+ } catch (_err) { /* non-blocking: tracking must never break a push */ // NOSONAR S2486
109
+ return null;
110
+ }
111
+ }
112
+
57
113
  /**
58
114
  * Run branch protection check as a subprocess.
59
115
  * @param {function} execFn - execFileSync or mock
@@ -150,6 +206,10 @@ module.exports = {
150
206
  return { success: false, quickMode, lintPassed: false, pushed: false };
151
207
  }
152
208
 
209
+ // Auto-file rail (non-blocking): ensure the branch being pushed has a backing
210
+ // Kernel issue so started work never goes unfiled. Never aborts the push.
211
+ await autoFileBackingIssueForPush(projectRoot, execFn, deps || {});
212
+
153
213
  // Step 2: Lint
154
214
  const lintPassed = runLint(spawnFn, pkgManager, projectRoot);
155
215
  if (!lintPassed) {
@@ -163,9 +223,11 @@ module.exports = {
163
223
  return { success: false, quickMode, lintPassed: true, testsPassed: false, pushed: false };
164
224
  }
165
225
 
166
- // Step 4: Write nonce token so lefthook skips pre-push hooks
226
+ // Step 4: Write nonce token so lefthook skips pre-push hooks.
227
+ // Write to the git worktree top-level (where the hook checks), not the
228
+ // inherited projectRoot, so the skip works from linked worktrees too.
167
229
  try {
168
- tokenWrite(projectRoot);
230
+ tokenWrite(resolveGitToplevel(execFn, projectRoot));
169
231
  } catch (_err) { /* intentional: non-fatal, lefthook will re-run hooks */ // NOSONAR S2486
170
232
  log('Warning: Could not write forge push token — lefthook hooks will run again');
171
233
  }
@@ -193,4 +255,9 @@ module.exports = {
193
255
  pushed: true,
194
256
  };
195
257
  },
258
+
259
+ // Exposed for unit tests; not part of the CLI surface.
260
+ _internal: {
261
+ autoFileBackingIssueForPush,
262
+ },
196
263
  };
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { makeAliasCommand } = require('./_issue');
3
+ const { createIssueSubcommand } = require('./_issue');
4
4
 
5
- module.exports = makeAliasCommand('ready');
5
+ module.exports = createIssueSubcommand('ready');
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ const memoryRouter = require('../memory/router');
4
+ const { stripGlobalFlags } = require('../global-flags');
5
+ const { fenceUntrusted } = require('../untrusted-content');
6
+
7
+ const usage = 'Usage: forge recall [query] [--limit N] [--all] [--json]';
8
+
9
+ /**
10
+ * Separate the optional positional query from `--limit N` and `--json`.
11
+ * Global flags (e.g. `-p <dir>`, `--all`) are stripped first so they never
12
+ * corrupt the search query (kernel issue c1e090ff). `--all` is a GLOBAL boolean
13
+ * flag, so the handler reads it from its `flags` argument (or the raw args on a
14
+ * direct call) — not from this parsed query.
15
+ *
16
+ * @param {string[]} rawArgs - Raw command arguments.
17
+ * @returns {{ query: string, limit: (number|undefined), json: boolean }}
18
+ */
19
+ function parseArgs(rawArgs) {
20
+ const args = stripGlobalFlags(rawArgs);
21
+ const words = [];
22
+ let limit;
23
+ let json = false;
24
+
25
+ for (let index = 0; index < args.length; index += 1) {
26
+ const arg = args[index];
27
+ if (arg === '--json') {
28
+ json = true;
29
+ } else if (arg === '--limit') {
30
+ const value = Number(args[index + 1]);
31
+ if (Number.isInteger(value) && value > 0) {
32
+ limit = value;
33
+ index += 1;
34
+ }
35
+ } else if (arg.startsWith('--limit=')) {
36
+ const value = Number(arg.slice('--limit='.length));
37
+ if (Number.isInteger(value) && value > 0) {
38
+ limit = value;
39
+ }
40
+ } else {
41
+ words.push(arg);
42
+ }
43
+ }
44
+
45
+ return { query: words.join(' ').trim(), limit, json };
46
+ }
47
+
48
+ function formatEntry(entry) {
49
+ const date = entry.timestamp ? entry.timestamp.slice(0, 10) : '';
50
+ const prefix = date ? `${date} ` : '';
51
+ const tagSuffix = entry.tags.length > 0 ? ` [${entry.tags.join(', ')}]` : '';
52
+ // Machine/insights records are LABELED with their source so they are never mistaken for a
53
+ // plain human note; human `remember` notes render clean.
54
+ const marker = entry.machine && entry.sourceAgent ? `(${entry.sourceAgent}) ` : '';
55
+ // Stored note text is UNTRUSTED (a planted memory could carry injected directives),
56
+ // so the human/agent-facing render is provenance-fenced. The `--json` path above
57
+ // keeps the raw note so programmatic consumers/parsers are unaffected.
58
+ const note = fenceUntrusted(entry.note, { source: 'memory' });
59
+ return `- ${prefix}${marker}${note}${tagSuffix}`;
60
+ }
61
+
62
+ async function handler(args, flags, projectRoot) {
63
+ const { query, limit, json } = parseArgs(args);
64
+ // `--all` is a GLOBAL boolean flag: in production bin/forge.js strips it from
65
+ // args and sets flags.all; on a direct handler call it may still be in args.
66
+ const all = Boolean(flags && flags.all) || args.includes('--all');
67
+
68
+ const { notes, total, capped, scope } = memoryRouter.recall(projectRoot, { query, limit, all });
69
+
70
+ if (json) {
71
+ // Object (not a bare array) so programmatic consumers see the total and whether the
72
+ // result was truncated (raise --limit to page further).
73
+ return {
74
+ success: true,
75
+ output: `${JSON.stringify({ notes, total, capped, scope }, null, 2)}\n`,
76
+ };
77
+ }
78
+
79
+ if (notes.length === 0) {
80
+ const reason = query
81
+ ? `No notes match "${query}".`
82
+ : 'No notes remembered yet. Use "forge remember <note>" to add one.';
83
+ return { success: true, output: reason };
84
+ }
85
+
86
+ const noun = scope === 'all' && !query ? 'stored memory record(s)' : 'remembered note(s)';
87
+ let header;
88
+ if (query) {
89
+ // BM25 returns at most `limit`; when full, signal it is the TOP-N, not the whole set.
90
+ header = capped
91
+ ? `Top ${notes.length} note(s) matching "${query}" (raise --limit for more):`
92
+ : `${notes.length} note(s) matching "${query}":`;
93
+ } else if (capped) {
94
+ // Never a bare full dump: show the newest N and the true total.
95
+ header = `Showing ${notes.length} of ${total} ${noun} (newest first):`;
96
+ } else {
97
+ header = `${total} ${noun}:`;
98
+ }
99
+ const lines = notes.map(formatEntry);
100
+ return {
101
+ success: true,
102
+ output: [header, ...lines].join('\n'),
103
+ };
104
+ }
105
+
106
+ module.exports = {
107
+ name: 'recall',
108
+ description: 'Retrieve project-memory notes from the kernel-backed memory store',
109
+ usage,
110
+ flags: {
111
+ '--limit': 'Cap the number of notes returned',
112
+ '--all': 'Include machine/insights records in the no-query listing (query already searches all)',
113
+ '--json': 'Emit machine-readable JSON output',
114
+ },
115
+ handler,
116
+ };
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ buildIssueRecap,
5
+ formatOrientationText,
6
+ } = require('../orientation');
7
+
8
+ const usage = 'Usage: forge recap <issue> [--budget N] [--json]';
9
+
10
+ // Options that consume the following token as their value. The issue-id scan
11
+ // must skip these values so `forge recap --budget 220 <issue>` is not misread.
12
+ const OPTIONS_WITH_VALUES = new Set(['--budget']);
13
+
14
+ function readOption(args, name, fallback) {
15
+ const equals = args.find(arg => arg.startsWith(`${name}=`));
16
+ if (equals) return equals.slice(name.length + 1);
17
+ const index = args.indexOf(name);
18
+ if (index >= 0 && args[index + 1] && !args[index + 1].startsWith('-')) return args[index + 1];
19
+ return fallback;
20
+ }
21
+
22
+ function readIssueArg(args) {
23
+ for (let index = 0; index < args.length; index += 1) {
24
+ const arg = args[index];
25
+ if (!arg) continue;
26
+ if (OPTIONS_WITH_VALUES.has(arg)) {
27
+ index += 1;
28
+ continue;
29
+ }
30
+ if (arg.startsWith('-')) continue;
31
+ return arg;
32
+ }
33
+ return null;
34
+ }
35
+
36
+ // `forge recap <issue>` summarizes a single issue from the deterministic
37
+ // orientation source assembly — it is issue-scoped, not a project-wide recap.
38
+ async function handler(args, _flags, projectRoot) {
39
+ const issueId = readIssueArg(args);
40
+ if (!issueId) {
41
+ // Use `error` (not `output`) so the CLI dispatcher prints the usage line
42
+ // once via console.error instead of also appending a redundant bare
43
+ // "Command failed" (see bin/forge.js registry dispatch).
44
+ return { success: false, error: usage };
45
+ }
46
+
47
+ const recap = buildIssueRecap(projectRoot, issueId, {
48
+ budgetTokens: readOption(args, '--budget', undefined),
49
+ });
50
+ return {
51
+ success: true,
52
+ output: args.includes('--json') ? `${JSON.stringify(recap, null, 2)}\n` : formatOrientationText(recap),
53
+ };
54
+ }
55
+
56
+ module.exports = {
57
+ name: 'recap',
58
+ description: 'Summarize a single issue from deterministic orientation source files',
59
+ usage,
60
+ handler,
61
+ };
@@ -23,7 +23,6 @@ const STAGE_NAMES = {
23
23
  validate: 'Validate',
24
24
  ship: 'Ship',
25
25
  review: 'Review',
26
- premerge: 'Premerge',
27
26
  };
28
27
 
29
28
  /**
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ SUPPORTED_TARGET,
5
+ buildReadinessReport,
6
+ renderReadinessReport,
7
+ } = require('../release-readiness');
8
+ const { runIssueOperation: defaultRunIssueOperation } = require('../forge-issues');
9
+ const { normalizeArgs, normalizeIssueResult, withResolvedIssueBackend } = require('./_issue');
10
+
11
+ // `forge release <id>` releases a claimed issue; `forge release check` runs the
12
+ // release-readiness gate. The two share the top-level verb, so this command
13
+ // dispatches `check` to the gate and routes everything else through the shared
14
+ // issue dispatch (resolve backend → runIssueOperation('release') → normalize). The
15
+ // Beads backend has no release op and returns the Kernel-only contract error.
16
+ const usage = 'Usage: forge release <id> | forge release check --target 0.1.0 [--json]';
17
+
18
+ async function runReleaseIssue(args, projectRoot, opts = {}) {
19
+ const resolved = withResolvedIssueBackend(projectRoot, opts);
20
+ const runIssueOperation = resolved.runIssueOperation || defaultRunIssueOperation;
21
+ const normalizedArgs = normalizeArgs(args);
22
+ const result = await runIssueOperation('release', normalizedArgs, projectRoot,
23
+ { ...resolved, kernelBroker: resolved.kernelBroker });
24
+ return normalizeIssueResult(result, 'release', { json: normalizedArgs.includes('--json') });
25
+ }
26
+
27
+ function readOption(args, name, fallback) {
28
+ const equals = args.find(arg => arg.startsWith(`${name}=`));
29
+ if (equals) {
30
+ return equals.slice(name.length + 1);
31
+ }
32
+
33
+ const index = args.indexOf(name);
34
+ if (index >= 0 && args[index + 1] && !args[index + 1].startsWith('-')) {
35
+ return args[index + 1];
36
+ }
37
+
38
+ return fallback;
39
+ }
40
+
41
+ function parseReleaseArgs(args = []) {
42
+ const subcommand = args.find((arg, index) => {
43
+ if (arg.startsWith('-')) {
44
+ return false;
45
+ }
46
+ const previous = args[index - 1];
47
+ return previous !== '--target';
48
+ });
49
+
50
+ return {
51
+ subcommand,
52
+ target: readOption(args, '--target', SUPPORTED_TARGET),
53
+ json: args.includes('--json'),
54
+ };
55
+ }
56
+
57
+ async function handler(args, _flags, projectRoot, opts = {}) {
58
+ const parsed = parseReleaseArgs(args);
59
+
60
+ if (parsed.subcommand !== 'check') {
61
+ // forge release <id> — release a claimed issue via the shared issue dispatch.
62
+ return runReleaseIssue(args, projectRoot, opts);
63
+ }
64
+
65
+ const report = buildReadinessReport(projectRoot, { target: parsed.target });
66
+ const output = parsed.json
67
+ ? `${JSON.stringify(report, null, 2)}\n`
68
+ : renderReadinessReport(report);
69
+ const error = parsed.json
70
+ ? `Forge release readiness check failed for ${report.target}`
71
+ : output;
72
+
73
+ return {
74
+ success: report.success,
75
+ report,
76
+ output,
77
+ error: report.success ? undefined : error,
78
+ };
79
+ }
80
+
81
+ module.exports = {
82
+ name: 'release',
83
+ description: 'Release a claimed issue, or run Forge release readiness gates (forge release check)',
84
+ usage,
85
+ flags: {
86
+ '--target <version>': 'Release target to check',
87
+ '--json': 'Emit the readiness report as JSON',
88
+ },
89
+ handler,
90
+ parseReleaseArgs,
91
+ };
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const memoryRouter = require('../memory/router');
4
+ const { stripGlobalFlags } = require('../global-flags');
5
+
6
+ const usage = 'Usage: forge remember <note> [--tag <label>]... [--json]';
7
+
8
+ /**
9
+ * Split positional note words from `--tag <label>` pairs and the `--json` flag.
10
+ * Global flags (e.g. `-p <dir>`) are stripped first so they never leak into
11
+ * the stored note content (kernel issue c1e090ff).
12
+ *
13
+ * @param {string[]} rawArgs - Raw command arguments.
14
+ * @returns {{ note: string, tags: string[], json: boolean }}
15
+ */
16
+ function parseArgs(rawArgs) {
17
+ const args = stripGlobalFlags(rawArgs);
18
+ const words = [];
19
+ const tags = [];
20
+ let json = false;
21
+
22
+ for (let index = 0; index < args.length; index += 1) {
23
+ const arg = args[index];
24
+ if (arg === '--json') {
25
+ json = true;
26
+ } else if (arg === '--tag') {
27
+ const value = args[index + 1];
28
+ if (value && !value.startsWith('--')) {
29
+ tags.push(value);
30
+ index += 1;
31
+ }
32
+ } else if (arg.startsWith('--tag=')) {
33
+ tags.push(arg.slice('--tag='.length));
34
+ } else {
35
+ words.push(arg);
36
+ }
37
+ }
38
+
39
+ return { note: words.join(' ').trim(), tags, json };
40
+ }
41
+
42
+ async function handler(args, _flags, projectRoot) {
43
+ const { note, tags, json } = parseArgs(args);
44
+
45
+ if (!note) {
46
+ return {
47
+ success: false,
48
+ error: `No note provided.\n${usage}`,
49
+ };
50
+ }
51
+
52
+ const entry = memoryRouter.append(projectRoot, note, { tags });
53
+
54
+ if (json) {
55
+ return { success: true, output: `${JSON.stringify(entry, null, 2)}\n` };
56
+ }
57
+
58
+ const tagSuffix = entry.tags.length > 0 ? ` [${entry.tags.join(', ')}]` : '';
59
+ return {
60
+ success: true,
61
+ output: `Remembered: ${entry.note}${tagSuffix}`,
62
+ };
63
+ }
64
+
65
+ module.exports = {
66
+ name: 'remember',
67
+ description: 'Persist a project-memory note to the kernel-backed memory store',
68
+ usage,
69
+ flags: {
70
+ '--tag': 'Attach a search label (repeatable)',
71
+ '--json': 'Emit machine-readable JSON output',
72
+ },
73
+ handler,
74
+ };