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
package/bin/forge.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * bunx forge setup --agents claude,cursor
14
14
  *
15
15
  * CLI Flags:
16
- * --path, -p <dir> Target project directory (creates if needed)
16
+ * --path, -p <dir> Target project directory (docs verify|detect require existing dir)
17
17
  * --quick, -q Use all defaults, minimal prompts
18
18
  * --skip-external Skip external services configuration
19
19
  * --agents <list> Specify agents (--agents claude cursor OR --agents=claude,cursor)
@@ -38,33 +38,52 @@
38
38
  const fs = require('node:fs');
39
39
  const path = require('node:path');
40
40
  const readline = require('node:readline');
41
- const { execSync, execFileSync, spawnSync } = require('node:child_process');
41
+ const { execSync } = require('node:child_process');
42
42
 
43
- // Get version from package.json (single source of truth)
43
+ // Get version from package.json (single source of truth). packageDir stays a
44
+ // runtime value for asset copying, but the require is a static relative path so
45
+ // `bun build --compile` can bundle package.json.
44
46
  const packageDir = path.dirname(__dirname);
45
- const packageJson = require(path.join(packageDir, 'package.json'));
47
+ const packageJson = require('../package.json');
46
48
  const VERSION = packageJson.version;
47
49
 
48
50
  // Load PluginManager for discoverable agent architecture
49
51
  const PluginManager = require('../lib/plugin-manager');
50
- const { scaffoldGithubBeadsSync } = require('../lib/setup');
52
+ const { populateAgentSkills, listCanonicalSkills } = require('../lib/skills-sync');
51
53
  const { copyEssentialDocs } = require('../lib/docs-copy');
52
- const { listTopics, getTopicContent } = require('../lib/docs-command');
54
+ const {
55
+ listTopics,
56
+ getTopicContent,
57
+ validateDocs,
58
+ formatDocsValidation,
59
+ writeDocsBaseline,
60
+ } = require('../lib/docs-command');
53
61
  const { resetSoft, resetHard, reinstall } = require('../lib/reset');
54
62
  const { loadCommands, executeCommand } = require('../lib/commands/_registry');
63
+ const {
64
+ isAlias,
65
+ isHiddenAlias,
66
+ isVisibleAlias,
67
+ resolveAlias,
68
+ resolveDispatch,
69
+ maybeWarnDeprecation,
70
+ passthroughAliasNames,
71
+ visibleAliasNames,
72
+ } = require('../lib/commands/_aliases');
73
+ const { resolveCommandOpts } = require('../lib/commands/_resolve-command-opts');
74
+ const { getPackageRoot } = require('../lib/package-root');
55
75
  const { enforceStageEntry } = require('../lib/workflow/enforce-stage');
56
76
  const { normalizeStageId } = require('../lib/workflow/stages');
77
+ const { firstPositionalIndex } = require('../lib/global-flags');
57
78
 
58
- // Load enhanced onboarding modules
59
- const contextMerge = require(path.join(packageDir, 'lib', 'context-merge'));
60
- const projectDiscovery = require(path.join(packageDir, 'lib', 'project-discovery'));
79
+ // Load enhanced onboarding modules (static relative requires — bundleable)
80
+ const contextMerge = require('../lib/context-merge');
81
+ const projectDiscovery = require('../lib/project-discovery');
61
82
 
62
83
  // Load lib modules for symlink, beads, and PAT setup
63
- const { createSymlinkOrCopy: libCreateSymlinkOrCopy } = require(path.join(packageDir, 'lib', 'symlink-utils'));
64
- const beadsSetupLib = require(path.join(packageDir, 'lib', 'beads-setup'));
65
- const { beadsHealthCheck } = require(path.join(packageDir, 'lib', 'beads-health-check'));
66
- const { setupPAT } = require(path.join(packageDir, 'lib', 'pat-setup'));
67
- const { DEFAULT_BEADS_VERSION, detectDefaultBranch, templateWorkflows, scaffoldBeadsSync } = require(path.join(packageDir, 'lib', 'beads-sync-scaffold'));
84
+ const { createSymlinkOrCopy: libCreateSymlinkOrCopy } = require('../lib/symlink-utils');
85
+ const beadsSetupLib = require('../lib/beads-setup');
86
+ const { scaffoldBeadsSync } = require('../lib/beads-sync-scaffold');
68
87
 
69
88
  // Load incremental setup modules
70
89
  const { detectEnvironment } = require('../lib/detect-agent');
@@ -74,6 +93,12 @@ const { ActionCollector, isNonInteractive } = require('../lib/setup-utils');
74
93
  const { renderSetupSummary } = require('../lib/setup-summary-renderer');
75
94
  const { smartMergeAgentsMd } = require('../lib/smart-merge');
76
95
  const { checkLefthookStatus } = require('../lib/lefthook-check');
96
+ const {
97
+ FORGE_USER_LEFTHOOK_YML,
98
+ forgeShouldWriteLefthookConfig,
99
+ installNativeGitHooks,
100
+ verifyHooksActive,
101
+ } = require('../lib/lefthook-wiring');
77
102
  const { detectHusky, migrateHusky } = require('../lib/husky-migration');
78
103
  // workflowProfiles: module exists and is tested but not yet wired into setup flow
79
104
  // Will be activated when workflow profile selection is added to interactive setup
@@ -94,37 +119,10 @@ let actionLog = new SetupActionLog();
94
119
  // Detected package manager
95
120
  let PKG_MANAGER = 'npm';
96
121
 
97
- /**
98
- * Securely execute a command with PATH validation
99
- * Mitigates SonarCloud S4036: Ensures executables are from trusted locations
100
- * @param {string} command - The command to execute
101
- * @param {string[]} args - Command arguments
102
- * @param {object} options - execFileSync options
103
- */
104
- function secureExecFileSync(command, args = [], options = {}) {
105
- try {
106
- // Resolve command's full path to validate it's in a trusted location
107
- const isWindows = process.platform === 'win32';
108
- const pathResolver = isWindows ? 'where.exe' : 'which';
109
-
110
- const result = spawnSync(pathResolver, [command], {
111
- encoding: 'utf8',
112
- stdio: ['ignore', 'pipe', 'ignore']
113
- });
114
-
115
- if (result.status === 0 && result.stdout) {
116
- // Command found - use resolved path for execution
117
- // Handle both CRLF (Windows) and LF (Unix) line endings
118
- const resolvedPath = result.stdout.trim().split(/\r?\n/)[0].trim();
119
- return execFileSync(resolvedPath, args, options);
120
- }
121
- } catch (_err) { // NOSONAR - S2486: Intentionally ignored; falls back to direct command execution below
122
- }
123
-
124
- // Fallback: execute with command name (maintains compatibility)
125
- // This is safe for our use case as we only execute known, hardcoded commands
126
- return execFileSync(command, args, options);
127
- }
122
+ // Securely execute a command with PATH validation (SonarCloud S4036).
123
+ // Delegates to the shared helper, which also handles Windows npm/npx/lefthook
124
+ // cmd shims that cannot be spawned directly (kernel issue 9997d516).
125
+ const { secureExecFileSync } = require('../lib/shell-utils');
128
126
 
129
127
  /**
130
128
  * Load agent definitions from plugin architecture
@@ -146,7 +144,8 @@ function loadAgentsFromPlugins() {
146
144
  customSetup: plugin.setup?.customSetup || '',
147
145
  needsConversion: plugin.setup?.needsConversion || false,
148
146
  copyCommands: plugin.setup?.copyCommands || false,
149
- promptFormat: plugin.setup?.promptFormat || false
147
+ promptFormat: plugin.setup?.promptFormat || false,
148
+ skillsDir: plugin.directories?.skills || null
150
149
  };
151
150
  });
152
151
 
@@ -295,25 +294,6 @@ function _checkWritePermission(filePath) {
295
294
  }
296
295
  }
297
296
 
298
- /**
299
- * Reads workflow command names from .claude/commands/*.md in the package directory.
300
- * @returns {string[]} Command names (filenames without .md extension)
301
- */
302
- function getWorkflowCommands() {
303
- const commandsDir = path.join(packageDir, '.claude', 'commands');
304
- try {
305
- return fs.readdirSync(commandsDir)
306
- .filter(f => f.endsWith('.md'))
307
- .map(f => f.replace(/\.md$/, ''));
308
- } catch (err) {
309
- if (err.code === 'ENOENT') {
310
- console.warn(`Warning: .claude/commands directory not found at ${commandsDir}`);
311
- } else {
312
- console.warn(`Warning: failed to read .claude/commands — ${err.code}: ${err.message}`);
313
- }
314
- return [];
315
- }
316
- }
317
297
 
318
298
 
319
299
  // Helper function to safely execute commands (no user input)
@@ -432,72 +412,26 @@ function checkPrerequisites() {
432
412
  return { errors, warnings };
433
413
  }
434
414
 
435
- // Universal SKILL.md content
436
- const SKILL_CONTENT = `---
437
- name: forge-workflow
438
- description: 7-stage TDD-first workflow for feature development. Use when building features, fixing bugs, or shipping PRs.
439
- category: Development Workflow
440
- tags: [tdd, workflow, pr, git, testing]
441
- tools: [Bash, Read, Write, Edit, Grep, Glob]
442
- ---
443
-
444
- # Forge Workflow Skill
445
-
446
- A TDD-first workflow for AI coding agents. Ship features with confidence.
447
-
448
- ## When to Use
449
-
450
- Automatically invoke this skill when the user wants to:
451
- - Build a new feature
452
- - Fix a bug
453
- - Create a pull request
454
- - Run the development workflow
455
-
456
- ## 7 Stages
457
-
458
- | Stage | Command | Description |
459
- |-------|---------|-------------|
460
- | utility | \`/status\` | Check current context, active work, recent completions |
461
- | 1 | \`/plan\` | Design intent -> research -> branch + worktree + task list |
462
- | 2 | \`/dev\` | TDD development (implementer -> spec review -> quality review) |
463
- | 3 | \`/validate\` | Type check, lint, security, tests - all fresh output |
464
- | 4 | \`/ship\` | Push branch and create PR with full documentation |
465
- | 5 | \`/review\` | Address ALL PR feedback (GitHub Actions, Greptile, SonarCloud) |
466
- | 6 | \`/premerge\` | Update docs, hand off PR to user |
467
- | 7 | \`/verify\` | Post-merge health check (CI on main, close Beads) |
468
-
469
- ## Workflow Flow
470
-
471
- \`\`\`
472
- /status -> /plan -> /dev -> /validate -> /ship -> /review -> /premerge -> /verify
473
- \`\`\`
474
-
475
- ## Core Principles
476
-
477
- - **TDD-First**: Write tests BEFORE implementation (RED-GREEN-REFACTOR)
478
- - **Research-First**: Understand before building, document decisions
479
- - **Security Built-In**: OWASP Top 10 analysis for every feature
480
- - **Documentation Progressive**: Update at each stage, verify at end
481
- `;
482
415
 
483
416
  // Cursor MDC rule content
484
417
  const CURSOR_RULE = `---
485
- description: Forge 7-Stage TDD Workflow
418
+ description: Forge TDD Workflow Template
486
419
  alwaysApply: true
487
420
  ---
488
421
 
489
422
  # Forge Workflow Commands
490
423
 
491
- Use these commands via \`/command-name\`:
424
+ Use these default-template commands via \`/command-name\`:
492
425
 
493
426
  - \`/status\` (utility) - Check current context, active work, recent completions
494
- 1. \`/plan\` - Design intent Q&A, research, branch + task list
495
- 2. \`/dev\` - Subagent-driven TDD per task (spec + quality review)
496
- 3. \`/validate\` - Type check, lint, security, tests (HARD-GATE)
497
- 4. \`/ship\` - Push and create PR with design doc reference
498
- 5. \`/review\` - Handle ALL PR issues (Actions, Greptile, SonarCloud)
499
- 6. \`/premerge\` - Complete docs on feature branch, hand off PR to user
500
- 7. \`/verify\` - Post-merge health check (CI on main, close Beads)
427
+ - \`/plan\` - Design intent Q&A, research, branch + task list
428
+ - \`/dev\` - Subagent-driven TDD per task (spec + quality review)
429
+ - \`/validate\` - Type check, lint, security, tests (HARD-GATE)
430
+ - \`/ship\` - Push and create PR with design doc reference
431
+ - \`/review\` - Handle ALL PR issues (Actions, Greptile, SonarCloud)
432
+ - \`/verify\` - Post-merge health check (CI on main, close Beads)
433
+
434
+ Pre-merge gate (not a numbered stage): finish docs + confirm CI green + hand off the PR — embedded in the /ship and /review stages.
501
435
 
502
436
  See AGENTS.md for full workflow details.
503
437
  `;
@@ -543,17 +477,6 @@ function writeFile(filePath, content) {
543
477
  }
544
478
  }
545
479
 
546
- function readFile(filePath) {
547
- try {
548
- return fs.readFileSync(filePath, 'utf8');
549
- } catch (err) {
550
- if (process.env.DEBUG) {
551
- console.warn(` ⚠ Could not read ${filePath}: ${err.message}`);
552
- }
553
- return null;
554
- }
555
- }
556
-
557
480
  function copyFile(src, dest) {
558
481
  try {
559
482
  const destPath = path.resolve(projectRoot, dest);
@@ -612,11 +535,6 @@ function createSymlinkOrCopy(source, target, options = {}) {
612
535
  return libCreateSymlinkOrCopy(fullSource, fullTarget, options);
613
536
  }
614
537
 
615
- function stripFrontmatter(content) {
616
- const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n([\s\S]*)$/);
617
- return match ? match[1] : content;
618
- }
619
-
620
538
  // Read existing .env.local
621
539
  function readEnvFile() {
622
540
  const envPath = path.join(projectRoot, '.env.local');
@@ -781,7 +699,6 @@ async function detectProjectStatus() {
781
699
  type: 'fresh', // 'fresh', 'upgrade', or 'partial'
782
700
  hasAgentsMd: fs.existsSync(path.join(projectRoot, 'AGENTS.md')),
783
701
  hasClaudeMd: fs.existsSync(path.join(projectRoot, 'CLAUDE.md')),
784
- hasClaudeCommands: fs.existsSync(path.join(projectRoot, '.claude/commands')),
785
702
  hasEnvLocal: fs.existsSync(path.join(projectRoot, '.env.local')),
786
703
  existingEnvVars: {},
787
704
  agentsMdSize: 0,
@@ -815,9 +732,9 @@ async function detectProjectStatus() {
815
732
  }
816
733
 
817
734
  // Determine installation type
818
- if (status.hasAgentsMd && status.hasClaudeCommands) {
735
+ if (status.hasAgentsMd) {
819
736
  status.type = 'upgrade'; // Full forge installation exists
820
- } else if (status.hasClaudeCommands || status.hasEnvLocal) {
737
+ } else if (status.hasEnvLocal) {
821
738
  status.type = 'partial'; // Agent-specific files exist (not just base files from postinstall)
822
739
  }
823
740
  // else: 'fresh' - new installation (or just postinstall baseline with AGENTS.md)
@@ -1665,7 +1582,6 @@ function displayMcpStatus(selectedAgents) {
1665
1582
  // Show manual setup instructions for GUI-based agents
1666
1583
  const manualMcpMap = {
1667
1584
  cursor: 'Cursor: Configure via Cursor Settings > MCP',
1668
- cline: 'Cline: Install via MCP Marketplace',
1669
1585
  };
1670
1586
  const needsManualMcp = Object.entries(manualMcpMap)
1671
1587
  .filter(([key]) => selectedAgents.includes(key))
@@ -1748,39 +1664,6 @@ async function configureExternalServices(rl, question, selectedAgents = [], proj
1748
1664
  const { added, preserved } = writeEnvTokens(tokens, true);
1749
1665
  displayEnvTokenResults(added, preserved);
1750
1666
 
1751
- // GitHub-Beads issue sync setup
1752
- console.log('');
1753
- const enableSync = await askYesNo(question, 'Enable GitHub ↔ Beads issue sync?', true);
1754
- if (enableSync) {
1755
- try {
1756
- const result = await scaffoldGithubBeadsSync(projectRoot, packageDir);
1757
- for (const f of result.created) {
1758
- console.log(` Created: ${f}`);
1759
- }
1760
- for (const f of result.skipped) {
1761
- console.log(` Skipped: ${f} (already exists)`);
1762
- }
1763
-
1764
- // PAT setup guidance for Beads sync (non-fatal)
1765
- // Skip if --sync flag is set — handleSyncScaffold will handle PAT setup
1766
- if (!SYNC_ENABLED) {
1767
- try {
1768
- const patResult = setupPAT(projectRoot, { interactive: !NON_INTERACTIVE });
1769
- if (patResult.success) {
1770
- console.log(' ✓ Beads sync PAT configured');
1771
- } else if (patResult.reminder) {
1772
- console.log(` ℹ ${patResult.reminder}`);
1773
- } else if (patResult.instructions) {
1774
- console.log(` ℹ ${patResult.instructions.split('\n')[0]}`);
1775
- }
1776
- } catch (_patErr) {
1777
- // PAT setup is best-effort — don't block sync scaffold
1778
- }
1779
- }
1780
- } catch (err) {
1781
- console.error(` Error scaffolding GitHub-Beads sync: ${err.message}`);
1782
- }
1783
- }
1784
1667
  }
1785
1668
 
1786
1669
  // Display the Forge banner
@@ -1891,26 +1774,20 @@ function minimalInstall() {
1891
1774
  console.log('');
1892
1775
  }
1893
1776
 
1894
- // Helper: Setup Claude agent
1895
- function setupClaudeAgent(skipFiles = {}) {
1896
- // Copy commands from package (unless skipped)
1897
- if (skipFiles.claudeCommands) {
1898
- console.log(' Skipped: .claude/commands/ (keeping existing)');
1899
- } else {
1900
- const cmds = getWorkflowCommands();
1901
- let copied = 0;
1902
- cmds.forEach(cmd => {
1903
- const src = path.join(packageDir, `.claude/commands/${cmd}.md`);
1904
- if (copyFile(src, `.claude/commands/${cmd}.md`)) copied++;
1905
- });
1906
- console.log(` Copied: ${copied} workflow commands`);
1907
- }
1777
+ // ⚠️ LEGACY / DEAD SETUP PATH ⚠️
1778
+ // The functions below (setupClaudeAgent, setupCursorAgent, copyAgentRules,
1779
+ // setupClaudeMcpConfig, setupAgent, quickSetup, dryRunSetup, …) are the OLD
1780
+ // inline setup implementation. `forge setup` is now the registry command in
1781
+ // lib/commands/setup.js, which takes priority in the dispatcher and `return`s
1782
+ // before the inline `if (command === 'setup')` block is ever reached. This copy
1783
+ // is kept only until the inline setup subsystem is fully removed — the authority
1784
+ // is lib/commands/setup.js. Do not add behavior here; edit lib/commands/setup.js.
1908
1785
 
1909
- // Copy rules
1910
- const rulesSrc = path.join(packageDir, '.claude/rules/workflow.md');
1911
- copyFile(rulesSrc, '.claude/rules/workflow.md');
1912
-
1913
- // Copy scripts
1786
+ // Helper: Setup Claude agent
1787
+ function setupClaudeAgent() {
1788
+ // Skills-only surface: per-skill SKILL.md dirs are populated by createAgentSkill.
1789
+ // Claude receives policy via CLAUDE.md → AGENTS.md, NOT an always-on
1790
+ // `.claude/rules/workflow.md` (that canonical file was removed). Only scripts copy.
1914
1791
  const scriptSrc = path.join(packageDir, '.claude/scripts/load-env.sh');
1915
1792
  copyFile(scriptSrc, '.claude/scripts/load-env.sh');
1916
1793
  }
@@ -1921,61 +1798,23 @@ function setupCursorAgent() {
1921
1798
  console.log(' Created: .cursor/rules/forge-workflow.mdc');
1922
1799
  }
1923
1800
 
1924
- // Helper: Convert command to agent-specific format
1925
- function convertCommandToAgentFormat(cmd, content, agent) {
1926
- let targetContent = content;
1927
- let targetFile = cmd;
1928
-
1929
- if (agent.needsConversion) {
1930
- targetContent = stripFrontmatter(content);
1931
- }
1932
-
1933
- if (agent.promptFormat) {
1934
- targetFile = cmd.replace('.md', '.prompt.md');
1935
- targetContent = stripFrontmatter(content);
1801
+ // Helper: Create skill file for agent
1802
+ function createAgentSkill(agent, agentKey) {
1803
+ if (agentKey === 'codex') {
1804
+ // Codex global skill install is handled via buildCodexSkillInstallPlan in setup.js
1805
+ return;
1936
1806
  }
1937
1807
 
1938
- return { targetFile, targetContent };
1939
- }
1940
-
1941
- // Helper: Copy commands for agent
1942
- function copyAgentCommands(agent, claudeCommands) {
1943
- if (!claudeCommands) return;
1944
- if (!agent.needsConversion && !agent.copyCommands && !agent.promptFormat) return;
1808
+ if (!agent.hasSkill || !agent.skillsDir) return;
1945
1809
 
1946
- Object.entries(claudeCommands).forEach(([cmd, content]) => {
1947
- const { targetFile, targetContent } = convertCommandToAgentFormat(cmd, content, agent);
1948
- const targetDir = agent.dirs[0]; // First dir is commands/workflows
1949
- writeFile(`${targetDir}/${targetFile}`, targetContent);
1810
+ // Skills-only surface: populate every canonical skill into the agent skills dir
1811
+ // (.claude/skills, .cursor/skills) from the packaged canonical `skills/` source.
1812
+ const { written } = populateAgentSkills({
1813
+ sourceRoot: packageDir,
1814
+ targetSkillsDir: path.join(projectRoot, agent.skillsDir),
1815
+ clean: true,
1950
1816
  });
1951
- console.log(` Converted: ${Object.keys(claudeCommands).length} workflow commands`);
1952
- }
1953
-
1954
- // Helper: Copy rules for agent
1955
- function copyAgentRules(agent) {
1956
- if (!agent.needsConversion) return;
1957
-
1958
- const workflowMdPath = path.join(projectRoot, '.claude/rules/workflow.md');
1959
- if (!fs.existsSync(workflowMdPath)) return;
1960
-
1961
- const rulesDir = agent.dirs.find(d => d.includes('/rules'));
1962
- if (!rulesDir) return;
1963
-
1964
- const ruleContent = readFile(workflowMdPath);
1965
- if (ruleContent) {
1966
- writeFile(`${rulesDir}/workflow.md`, ruleContent);
1967
- }
1968
- }
1969
-
1970
- // Helper: Create skill file for agent
1971
- function createAgentSkill(agent) {
1972
- if (!agent.hasSkill) return;
1973
-
1974
- const skillDir = agent.dirs.find(d => d.includes('/skills/'));
1975
- if (skillDir) {
1976
- writeFile(`${skillDir}/SKILL.md`, SKILL_CONTENT);
1977
- console.log(' Created: forge-workflow skill');
1978
- }
1817
+ console.log(` Created: ${written.length} skills in ${agent.skillsDir}/`);
1979
1818
  }
1980
1819
 
1981
1820
  // Helper: Setup MCP config for Claude
@@ -2010,7 +1849,7 @@ function createAgentLinkFile(agent, symlinkOnly = false) {
2010
1849
  }
2011
1850
 
2012
1851
  // Setup specific agent
2013
- function setupAgent(agentKey, claudeCommands, skipFiles = {}) {
1852
+ function setupAgent(agentKey, skipFiles = {}) {
2014
1853
  const agent = AGENTS[agentKey];
2015
1854
  if (!agent) return;
2016
1855
 
@@ -2028,14 +1867,8 @@ function setupAgent(agentKey, claudeCommands, skipFiles = {}) {
2028
1867
  setupCursorAgent();
2029
1868
  }
2030
1869
 
2031
- // Convert/copy commands
2032
- copyAgentCommands(agent, claudeCommands);
2033
-
2034
- // Copy rules if needed
2035
- copyAgentRules(agent);
2036
-
2037
1870
  // Create SKILL.md
2038
- createAgentSkill(agent);
1871
+ createAgentSkill(agent, agentKey);
2039
1872
 
2040
1873
  // Setup MCP configs
2041
1874
  if (agentKey === 'claude') {
@@ -2069,7 +1902,6 @@ function displayInstallationStatus(projectStatus) {
2069
1902
  }
2070
1903
 
2071
1904
  if (projectStatus.hasAgentsMd) console.log(' - AGENTS.md');
2072
- if (projectStatus.hasClaudeCommands) console.log(' - .claude/commands/');
2073
1905
  if (projectStatus.hasEnvLocal) console.log(' - .env.local');
2074
1906
  console.log('');
2075
1907
  }
@@ -2133,7 +1965,6 @@ async function promptForFileOverwrite(question, fileType, exists, skipFiles) {
2133
1965
 
2134
1966
  const fileLabels = {
2135
1967
  agentsMd: { prompt: 'Found existing AGENTS.md. Overwrite?', message: 'AGENTS.md', key: 'agentsMd' },
2136
- claudeCommands: { prompt: 'Found existing .claude/commands/. Overwrite?', message: '.claude/commands/', key: 'claudeCommands' }
2137
1968
  };
2138
1969
 
2139
1970
  const config = fileLabels[fileType];
@@ -2308,35 +2139,12 @@ async function installAgentsMd(skipFiles) {
2308
2139
  }
2309
2140
  }
2310
2141
 
2311
- /**
2312
- * Load Claude commands for conversion
2313
- */
2314
- function loadClaudeCommands(selectedAgents) {
2315
- const claudeCommands = {};
2316
- const needsClaudeCommands = selectedAgents.includes('claude') ||
2317
- selectedAgents.some(a => AGENTS[a].needsConversion || AGENTS[a].copyCommands);
2318
-
2319
- if (!needsClaudeCommands) {
2320
- return claudeCommands;
2321
- }
2322
-
2323
- getWorkflowCommands().forEach(cmd => {
2324
- const cmdPath = path.join(projectRoot, `.claude/commands/${cmd}.md`);
2325
- const content = readFile(cmdPath);
2326
- if (content) {
2327
- claudeCommands[`${cmd}.md`] = content;
2328
- }
2329
- });
2330
-
2331
- return claudeCommands;
2332
- }
2333
-
2334
2142
  /**
2335
2143
  * Setup agents with progress indication
2336
2144
  * Delegates to setupSelectedAgents to avoid duplicate implementations (S4144)
2337
2145
  */
2338
- function setupAgentsWithProgress(selectedAgents, claudeCommands, skipFiles) {
2339
- setupSelectedAgents(selectedAgents, claudeCommands, skipFiles);
2146
+ function setupAgentsWithProgress(selectedAgents, skipFiles) {
2147
+ setupSelectedAgents(selectedAgents, skipFiles);
2340
2148
  }
2341
2149
 
2342
2150
  /**
@@ -2351,15 +2159,11 @@ function displaySetupSummary(selectedAgents) {
2351
2159
  console.log('What\'s installed:');
2352
2160
  console.log(' - AGENTS.md (universal instructions)');
2353
2161
 
2354
- const workflowCount = getWorkflowCommands().length;
2355
2162
  selectedAgents.forEach(key => {
2356
2163
  const agent = AGENTS[key];
2357
2164
  if (agent.linkFile) {
2358
2165
  console.log(` - ${agent.linkFile} (${agent.name})`);
2359
2166
  }
2360
- if (agent.hasCommands) {
2361
- console.log(` - .claude/commands/ (${workflowCount} workflow commands)`);
2362
- }
2363
2167
  if (agent.hasSkill) {
2364
2168
  const skillDir = agent.dirs.find(d => d.includes('/skills/'));
2365
2169
  if (skillDir) {
@@ -2464,12 +2268,10 @@ async function _interactiveSetup() {
2464
2268
  // Track which files to skip based on user choices
2465
2269
  const skipFiles = {
2466
2270
  agentsMd: false,
2467
- claudeCommands: false
2468
2271
  };
2469
2272
 
2470
2273
  // Ask about overwriting existing files
2471
2274
  await promptForFileOverwrite(question, 'agentsMd', projectStatus.hasAgentsMd, skipFiles);
2472
- await promptForFileOverwrite(question, 'claudeCommands', projectStatus.hasClaudeCommands, skipFiles);
2473
2275
 
2474
2276
  if (projectStatus.type !== 'fresh') {
2475
2277
  console.log('');
@@ -2492,19 +2294,13 @@ async function _interactiveSetup() {
2492
2294
  setupCoreDocs();
2493
2295
  console.log('');
2494
2296
 
2495
- // Load Claude commands if needed
2496
- let claudeCommands = {};
2497
- if (selectedAgents.includes('claude') || selectedAgents.some(a => AGENTS[a].needsConversion || AGENTS[a].copyCommands)) {
2498
- // First ensure Claude is set up
2499
- if (selectedAgents.includes('claude')) {
2500
- setupAgent('claude', null, skipFiles);
2501
- }
2502
- // Then load the commands
2503
- claudeCommands = loadClaudeCommands(selectedAgents);
2297
+ // Setup Claude first if selected, then remaining agents
2298
+ if (selectedAgents.includes('claude')) {
2299
+ setupAgent('claude', skipFiles);
2504
2300
  }
2505
2301
 
2506
2302
  // Setup each selected agent with progress indication
2507
- setupAgentsWithProgress(selectedAgents, claudeCommands, skipFiles);
2303
+ setupAgentsWithProgress(selectedAgents, skipFiles);
2508
2304
 
2509
2305
  // =============================================
2510
2306
  // STEP 2: Project Tools Setup
@@ -2530,6 +2326,12 @@ async function _interactiveSetup() {
2530
2326
  }
2531
2327
 
2532
2328
  // Parse CLI flags
2329
+ // The curated back-compat alias allowlist (formerly the inline
2330
+ // ISSUE_ALIAS_COMMANDS array) now lives in lib/commands/_aliases.js as the single
2331
+ // declarative source of truth. `aliasNames()` returns the bare verbs that stay
2332
+ // registered + routable but are hidden from `forge --help` (kernel issue 450c6e34),
2333
+ // so `forge issue` reads as the single canonical issue surface. The canonical
2334
+ // `issue` is deliberately not an alias — it stays documented.
2533
2335
  function parseFlags() {
2534
2336
  const flags = {
2535
2337
  quick: false,
@@ -2554,7 +2356,11 @@ function parseFlags() {
2554
2356
 
2555
2357
  // Issue passthrough commands delegate all flags to bd.
2556
2358
  // Skip global parsing so flags like --type, -p, --help reach the handler intact.
2557
- const issuePassthroughCommands = ['create', 'update', 'claim', 'close', 'show', 'list', 'ready', 'issue', 'issues'];
2359
+ // Canonical `issue` plus the ISSUE-canonical back-compat aliases only. Non-issue
2360
+ // aliases (e.g. the memory shortcuts remember/recall/insights) are excluded so
2361
+ // their global flags still parse exactly as their standalone commands' did —
2362
+ // keeping bare `recall -p <dir>` / `recall --help` byte-identical.
2363
+ const issuePassthroughCommands = [...passthroughAliasNames(), 'issue'];
2558
2364
  if (issuePassthroughCommands.includes(args[0])) {
2559
2365
  return flags;
2560
2366
  }
@@ -2757,6 +2563,7 @@ function showHelp() {
2757
2563
  console.log('Options:');
2758
2564
  console.log(' --path, -p <dir> Target project directory (default: current directory)');
2759
2565
  console.log(' Creates the directory if it doesn\'t exist');
2566
+ console.log(' Exception: docs verify|detect --path requires an existing directory');
2760
2567
  console.log(' --quick, -q Use all defaults, minimal prompts');
2761
2568
  console.log(' Auto-selects: all agents, GitHub Code Quality, ESLint');
2762
2569
  console.log(' --skip-external Skip external services configuration');
@@ -2802,9 +2609,44 @@ function showHelp() {
2802
2609
  console.log('Also works with bun:');
2803
2610
  console.log(' bunx forge setup --quick');
2804
2611
  console.log('');
2612
+ // Adoption profile + gate/classification config is owned by `forge init`, not
2613
+ // `forge setup`. The --minimal/--standard/--full profile flags are `forge init`
2614
+ // shortcuts (setup only reads them for `--dry-run` preview), so point there.
2615
+ console.log('Adoption profile, gates & classification (configured by `forge init`, not setup):');
2616
+ console.log(' forge init --minimal | --standard | --full # adoption-profile shortcut');
2617
+ console.log(' # (or: --profile minimal|standard|full)');
2618
+ console.log(' forge init --classification critical|standard|refactor');
2619
+ console.log(' `forge init` writes `.forge/config.yaml` (gate + classification config).');
2620
+ console.log(' Run `forge init --help` for all profile/classification/harness flags.');
2621
+ console.log('');
2622
+
2623
+ // Shortcuts block: VISIBLE back-compat aliases for a canonical `<noun> <sub>`
2624
+ // form (e.g. `remember` -> `forge memory add`). The bare verb keeps working and
2625
+ // is documented here; the noun form is canonical. Rendered as its own block ABOVE
2626
+ // "Additional commands" (and trimmed from that enumeration below) so the noun
2627
+ // surface reads clean.
2628
+ const shortcutNames = visibleAliasNames();
2629
+ if (shortcutNames.length > 0) {
2630
+ console.log('Shortcuts (bare aliases for canonical noun subcommands):');
2631
+ const shortcutWidth = Math.max(...shortcutNames.map(name => name.length));
2632
+ for (const name of shortcutNames) {
2633
+ console.log(` ${name.padEnd(shortcutWidth)} -> forge ${resolveAlias(name).canonical}`);
2634
+ }
2635
+ console.log('');
2636
+ }
2805
2637
 
2806
- // Append auto-discovered registry commands
2638
+ // Append auto-discovered registry commands. Hidden issue aliases (the bare
2639
+ // passthroughs + plural `issues`, plus any command self-declaring `hidden: true`)
2640
+ // AND the visible noun shortcuts rendered above are trimmed from this enumeration
2641
+ // so the canonical noun surface reads clean; both remain routable. This registry
2642
+ // instance is loaded solely to render help, so deleting from its Map only affects
2643
+ // the printed list — command dispatch (main) uses a separate registry.
2807
2644
  const helpRegistry = loadCommands(path.join(__dirname, '..', 'lib', 'commands'));
2645
+ for (const [name, cmd] of [...helpRegistry.commands]) {
2646
+ if (isHiddenAlias(name) || isVisibleAlias(name) || cmd.hidden === true) {
2647
+ helpRegistry.commands.delete(name);
2648
+ }
2649
+ }
2808
2650
  const registryHelp = helpRegistry.getHelp();
2809
2651
  if (registryHelp) {
2810
2652
  console.log('Additional commands:');
@@ -2813,6 +2655,26 @@ function showHelp() {
2813
2655
  }
2814
2656
  }
2815
2657
 
2658
+ // Per-command help. When `--help` follows a known subcommand (e.g. `forge status
2659
+ // --help`), render THAT command's usage/description/flags instead of the global
2660
+ // setup banner — a newcomer asking for a command's help should not get the
2661
+ // installer's help.
2662
+ function printCommandHelp(cmd) {
2663
+ console.log(`forge ${cmd.name} — ${cmd.description}`);
2664
+ if (cmd.usage) {
2665
+ console.log('');
2666
+ console.log(/^(Usage:|forge )/.test(cmd.usage) ? cmd.usage : `Usage: ${cmd.usage}`);
2667
+ }
2668
+ const flagEntries = cmd.flags && typeof cmd.flags === 'object' ? Object.entries(cmd.flags) : [];
2669
+ if (flagEntries.length > 0) {
2670
+ console.log('');
2671
+ console.log('Flags:');
2672
+ for (const [flag, desc] of flagEntries) {
2673
+ console.log(` ${flag.padEnd(16)} ${desc}`);
2674
+ }
2675
+ }
2676
+ }
2677
+
2816
2678
  // Detect Husky and offer migration to Lefthook
2817
2679
  // Called before installGitHooks() in setup flows
2818
2680
  async function handleHuskyMigration() {
@@ -2863,80 +2725,104 @@ async function handleHuskyMigration() {
2863
2725
  function installGitHooks() {
2864
2726
  console.log('Installing git hooks (TDD enforcement)...');
2865
2727
 
2866
- // Skip lefthook.yml creation if binary is not available
2867
- const lefthookStatus = checkLefthookStatus(projectRoot);
2868
- if (!lefthookStatus.binaryAvailable) {
2869
- if (lefthookStatus.message) {
2870
- console.warn(` \u26A0 Skipping lefthook setup: ${lefthookStatus.message}`);
2871
- } else {
2872
- console.warn(' \u26A0 Skipping lefthook setup: binary not available');
2873
- }
2874
- return;
2875
- }
2728
+ // Install the Forge hook SCRIPTS first, UNCONDITIONALLY - they back BOTH the lefthook
2729
+ // pre-commit job AND the native .git/hooks fallback below, so they must exist whether
2730
+ // or not the lefthook binary is available.
2731
+ installForgeHookScripts();
2876
2732
 
2877
- // Check if lefthook.yml exists (it should, as it's in the package)
2878
- const lefthookConfig = path.join(packageDir, 'lefthook.yml');
2879
- const targetHooks = path.join(projectRoot, '.forge/hooks');
2733
+ const lefthookStatus = checkLefthookStatus(projectRoot);
2734
+ let lefthookInstalled = false;
2880
2735
 
2881
- try {
2882
- // Copy lefthook.yml to project root
2883
- const lefthookTarget = path.join(projectRoot, 'lefthook.yml');
2884
- if (!fs.existsSync(lefthookTarget)) {
2885
- if (copyFile(lefthookConfig, 'lefthook.yml')) {
2736
+ if (lefthookStatus.binaryAvailable) {
2737
+ try {
2738
+ // Write the REAL user-facing lefthook.yml - never the repo's own dev config (which
2739
+ // references repo-internal scripts/ a user project lacks), and never leave
2740
+ // lefthook's stock commented-out example in place. Overwrite only a missing file
2741
+ // or a fully-commented stub, never a config with active jobs (kernel e452422c).
2742
+ const lefthookTarget = path.join(projectRoot, 'lefthook.yml');
2743
+ if (forgeShouldWriteLefthookConfig(lefthookTarget)) {
2744
+ fs.writeFileSync(lefthookTarget, FORGE_USER_LEFTHOOK_YML, 'utf8');
2886
2745
  console.log(' ✓ Created lefthook.yml');
2887
2746
  }
2888
- }
2889
-
2890
- // Copy check-tdd.js hook script
2891
- const hookSource = path.join(packageDir, '.forge/hooks/check-tdd.js');
2892
- if (fs.existsSync(hookSource)) {
2893
- // Ensure .forge/hooks directory exists
2894
- if (!fs.existsSync(targetHooks)) {
2895
- fs.mkdirSync(targetHooks, { recursive: true });
2896
- }
2897
2747
 
2898
- const hookTarget = path.join(targetHooks, 'check-tdd.js');
2899
- if (copyFile(hookSource, hookTarget)) {
2900
- console.log(' ✓ Created .forge/hooks/check-tdd.js');
2901
-
2902
- // Make hook executable (Unix systems)
2903
- try {
2904
- fs.chmodSync(hookTarget, 0o755);
2905
- } catch (err) {
2906
- // Windows doesn't need chmod
2907
- console.warn('chmod not available (Windows):', err.message);
2908
- }
2909
- }
2910
- }
2911
-
2912
- // Try to install lefthook hooks
2913
- // SECURITY: Using execFileSync with hardcoded commands (no user input)
2914
- try {
2915
- // Try npx first (local install), fallback to global
2748
+ // Install lefthook's git hooks. Try local (npx) first, then a global binary.
2749
+ // SECURITY: execFileSync with hardcoded commands (no user input).
2916
2750
  try {
2917
2751
  secureExecFileSync('npx', ['lefthook', 'install'], { stdio: 'inherit', cwd: projectRoot });
2918
2752
  console.log(' ✓ Lefthook hooks installed (local)');
2753
+ lefthookInstalled = true;
2919
2754
  } catch (error_) {
2920
- // Fallback to global lefthook
2921
2755
  console.warn('npx lefthook failed, trying global:', error_.message);
2922
- execFileSync('lefthook', ['version'], { stdio: 'ignore' });
2923
- execFileSync('lefthook', ['install'], { stdio: 'inherit', cwd: projectRoot });
2756
+ secureExecFileSync('lefthook', ['version'], { stdio: 'ignore' });
2757
+ secureExecFileSync('lefthook', ['install'], { stdio: 'inherit', cwd: projectRoot });
2924
2758
  console.log(' ✓ Lefthook hooks installed (global)');
2759
+ lefthookInstalled = true;
2925
2760
  }
2926
2761
  } catch (err) {
2927
2762
  console.warn('Lefthook installation failed:', err.message);
2928
- console.log(' ℹ Lefthook not found. Install it:');
2929
- console.log(' bun add -d lefthook (recommended)');
2930
- console.log(' OR: bun add -g lefthook (global)');
2931
- console.log(' Then run: bunx lefthook install');
2932
2763
  }
2764
+ } else if (lefthookStatus.message) {
2765
+ console.warn(` ⚠ lefthook binary unavailable: ${lefthookStatus.message}`);
2766
+ }
2767
+
2768
+ // Native fallback: when lefthook is unavailable or its install failed, wire native
2769
+ // .git/hooks so raw `git commit` / `git push` still enforce the TDD gate - the moat is
2770
+ // never silently inert (B3). Runs regardless of whether a package.json exists.
2771
+ if (!lefthookInstalled) {
2772
+ const native = installNativeGitHooks(projectRoot);
2773
+ if (native.installed) {
2774
+ console.log(` ✓ Native git hooks installed (${native.written.join(', ')}) - lefthook fallback`);
2775
+ } else if (native.skipped && native.skipped.length > 0) {
2776
+ console.warn(` ⚠ Native hook(s) skipped to preserve existing hooks: ${native.skipped.join(', ')}`);
2777
+ } else {
2778
+ console.warn(` ⚠ Could not install native git hooks: ${native.reason || 'unknown'}`);
2779
+ }
2780
+ }
2933
2781
 
2934
- console.log('');
2782
+ // VERIFY LOUDLY: never let setup silently no-op. If pre-commit enforcement is not
2783
+ // actually active after all of the above, say so unmistakably and set a failure code.
2784
+ const verdict = verifyHooksActive(projectRoot);
2785
+ if (verdict.active) {
2786
+ console.log(` ✓ Git hook enforcement active (${verdict.method}).`);
2787
+ } else {
2788
+ const addCmd = PKG_MANAGER === 'bun'
2789
+ ? 'bun add -d'
2790
+ : PKG_MANAGER === 'npm'
2791
+ ? 'npm install --save-dev'
2792
+ : `${PKG_MANAGER} add -D`;
2793
+ console.error('');
2794
+ console.error(' ============================================================');
2795
+ console.error(' ⚠ TDD ENFORCEMENT IS NOT ACTIVE');
2796
+ console.error(` ${verdict.reason || 'no pre-commit hook is installed'}.`);
2797
+ console.error(' `forge ship` will block until hooks are active. To fix:');
2798
+ console.error(` ${addCmd} lefthook && npx lefthook install`);
2799
+ console.error(' (or re-run `forge setup` in the repo root).');
2800
+ console.error(' ============================================================');
2801
+ console.error('');
2802
+ process.exitCode = 1;
2803
+ }
2935
2804
 
2936
- } catch (error) {
2937
- console.log(' ⚠ Failed to install hooks:', error.message);
2938
- console.log(' You can install manually later with: lefthook install');
2939
- console.log('');
2805
+ console.log('');
2806
+ }
2807
+
2808
+ // Copy the Forge hook scripts (check-tdd.js + the native-hook adapter) into the
2809
+ // project's .forge/hooks/. Runs UNCONDITIONALLY - independent of the lefthook binary -
2810
+ // because both the lefthook pre-commit job and the native fallback invoke them.
2811
+ function installForgeHookScripts() {
2812
+ const targetHooks = path.join(projectRoot, '.forge/hooks');
2813
+ for (const name of ['check-tdd.js', 'forge-native-hook.js']) {
2814
+ const src = path.join(packageDir, '.forge/hooks', name);
2815
+ if (!fs.existsSync(src)) continue;
2816
+ if (!fs.existsSync(targetHooks)) fs.mkdirSync(targetHooks, { recursive: true });
2817
+ const dest = path.join(targetHooks, name);
2818
+ if (copyFile(src, dest)) {
2819
+ console.log(` ✓ Created .forge/hooks/${name}`);
2820
+ try {
2821
+ fs.chmodSync(dest, 0o755); // NOSONAR - hook scripts must be executable
2822
+ } catch (err) {
2823
+ console.warn('chmod not available (Windows):', err.message);
2824
+ }
2825
+ }
2940
2826
  }
2941
2827
  }
2942
2828
 
@@ -2987,6 +2873,18 @@ function isBeadsInitialized() {
2987
2873
  return beadsSetupLib.isBeadsInitialized(projectRoot);
2988
2874
  }
2989
2875
 
2876
+ function migrateExistingBeadsLocalState() {
2877
+ if (!fs.existsSync(path.join(projectRoot, '.beads'))) {
2878
+ return;
2879
+ }
2880
+
2881
+ try {
2882
+ beadsSetupLib.ensureBeadsGitExclude(projectRoot);
2883
+ } catch (err) {
2884
+ console.warn(` Warning: failed to migrate Beads local state: ${err.message}`);
2885
+ }
2886
+ }
2887
+
2990
2888
  // Initialize Beads in the project using the defensive safeBeadsInit wrapper
2991
2889
  // Handles config/gitignore writes, hook snapshot/restore, and JSONL pre-seeding
2992
2890
  function initializeBeads(installType) {
@@ -3044,18 +2942,6 @@ function initializeBeads(installType) {
3044
2942
  }
3045
2943
  console.log(' ✓ Beads initialized');
3046
2944
 
3047
- // Run post-init health check (non-fatal)
3048
- try {
3049
- const health = beadsHealthCheck(projectRoot);
3050
- if (health.healthy) {
3051
- console.log(' ✓ Beads health check passed');
3052
- } else {
3053
- console.log(` ⚠ Beads health check failed at ${health.failedStep}: ${health.error}`);
3054
- }
3055
- } catch (_healthErr) {
3056
- // Health check is best-effort — don't block setup
3057
- }
3058
-
3059
2945
  return true;
3060
2946
  } catch (err) {
3061
2947
  console.log(' ⚠ Failed to initialize Beads:', err.message);
@@ -3131,6 +3017,7 @@ async function promptBeadsSetup(question) {
3131
3017
  const beadsStatus = checkForBeads();
3132
3018
 
3133
3019
  if (beadsInitialized) {
3020
+ migrateExistingBeadsLocalState();
3134
3021
  console.log('✓ Beads is already initialized in this project');
3135
3022
  console.log('');
3136
3023
  return;
@@ -3364,6 +3251,10 @@ function autoSetupBeadsInQuickMode() {
3364
3251
  const beadsStatus = checkForBeads();
3365
3252
  const beadsInitialized = isBeadsInitialized();
3366
3253
 
3254
+ if (beadsInitialized) {
3255
+ migrateExistingBeadsLocalState();
3256
+ }
3257
+
3367
3258
  if (!beadsInitialized && beadsStatus) {
3368
3259
  console.log('📦 Initializing Beads...');
3369
3260
  initializeBeads(beadsStatus);
@@ -3415,7 +3306,19 @@ function autoInstallLefthook() {
3415
3306
  return;
3416
3307
  }
3417
3308
 
3418
- // Not in package.json at all — full install
3309
+ // Not in package.json at all — full install.
3310
+ // GUARD (kernel 22e33dbf): with no package.json in projectRoot, an
3311
+ // `npm install --save-dev lefthook` / `bun add lefthook` resolves against the nearest
3312
+ // ANCESTOR package.json and installs lefthook into the WRONG project (or fails). Skip
3313
+ // the package-manager install entirely — installGitHooks() then wires native
3314
+ // .git/hooks, which need no package.json, so enforcement is still live.
3315
+ if (!fs.existsSync(path.join(projectRoot, 'package.json'))) {
3316
+ console.log(' ℹ No package.json here — skipping lefthook npm install (would target an ancestor).');
3317
+ console.log(' Git hook enforcement will use the native .git/hooks fallback instead.');
3318
+ console.log('');
3319
+ return;
3320
+ }
3321
+
3419
3322
  console.log('📦 Installing lefthook for git hooks...');
3420
3323
  try {
3421
3324
  // SECURITY: secureExecFileSync with PKG_MANAGER — cross-platform support
@@ -3463,11 +3366,10 @@ function autoSetupToolsInQuickMode() {
3463
3366
  console.log('📦 Initializing Skills...');
3464
3367
  initializeSkills(skillsStatus);
3465
3368
  console.log('');
3466
- } else if (!skillsStatus) {
3467
- const installCmd = PKG_MANAGER === 'bun' ? 'bun add -g' : 'npm install -g';
3468
- console.log(` ℹ Skills not found — install with: ${installCmd} @forge/skills`);
3469
- console.log('');
3470
3369
  }
3370
+ // No hint when the optional Skills CLI is absent: setup bundles and renders
3371
+ // Forge's skills itself, and the previously advertised "@forge/skills"
3372
+ // package does not exist on npm (kernel issue 6e554b41).
3471
3373
  }
3472
3374
 
3473
3375
  // Helper: Configure default external services in quick mode - extracted to reduce cognitive complexity
@@ -3522,9 +3424,11 @@ async function quickSetup(selectedAgents, skipExternal) {
3522
3424
  // Auto-setup project tools (Beads, Skills)
3523
3425
  autoSetupToolsInQuickMode();
3524
3426
 
3525
- // Load Claude commands and setup agents (reuse existing helpers)
3526
- const claudeCommands = loadAndSetupClaudeCommands(selectedAgents);
3527
- setupSelectedAgents(selectedAgents, claudeCommands);
3427
+ // Setup Claude first if selected, then remaining agents
3428
+ if (selectedAgents.includes('claude')) {
3429
+ setupAgent('claude');
3430
+ }
3431
+ setupSelectedAgents(selectedAgents);
3528
3432
 
3529
3433
  // Detect Husky and migrate before installing Lefthook hooks
3530
3434
  await handleHuskyMigration();
@@ -3649,7 +3553,6 @@ function displayExistingInstallation(projectStatus) {
3649
3553
  : 'Found partial installation:');
3650
3554
 
3651
3555
  if (projectStatus.hasAgentsMd) console.log(' - AGENTS.md');
3652
- if (projectStatus.hasClaudeCommands) console.log(' - .claude/commands/');
3653
3556
  if (projectStatus.hasEnvLocal) console.log(' - .env.local');
3654
3557
  console.log('');
3655
3558
  }
@@ -3658,7 +3561,6 @@ function displayExistingInstallation(projectStatus) {
3658
3561
  async function promptForOverwriteDecisions(question, projectStatus) {
3659
3562
  const skipFiles = {
3660
3563
  agentsMd: false,
3661
- claudeCommands: false
3662
3564
  };
3663
3565
 
3664
3566
  if (projectStatus.hasAgentsMd) {
@@ -3667,12 +3569,6 @@ async function promptForOverwriteDecisions(question, projectStatus) {
3667
3569
  console.log(overwriteAgents ? ' Will overwrite AGENTS.md' : ' Keeping existing AGENTS.md');
3668
3570
  }
3669
3571
 
3670
- if (projectStatus.hasClaudeCommands) {
3671
- const overwriteCommands = await askYesNo(question, 'Found existing .claude/commands/. Overwrite?', true);
3672
- skipFiles.claudeCommands = !overwriteCommands;
3673
- console.log(overwriteCommands ? ' Will overwrite .claude/commands/' : ' Keeping existing .claude/commands/');
3674
- }
3675
-
3676
3572
  if (projectStatus.type !== 'fresh') {
3677
3573
  console.log('');
3678
3574
  }
@@ -3680,41 +3576,14 @@ async function promptForOverwriteDecisions(question, projectStatus) {
3680
3576
  return skipFiles;
3681
3577
  }
3682
3578
 
3683
- // Helper: Load and setup Claude commands - extracted to reduce cognitive complexity
3684
- function loadAndSetupClaudeCommands(selectedAgents, skipFiles) {
3685
- const claudeCommands = {};
3686
- const needsClaudeCommands = selectedAgents.includes('claude') ||
3687
- selectedAgents.some(a => AGENTS[a].needsConversion || AGENTS[a].copyCommands);
3688
-
3689
- if (!needsClaudeCommands) {
3690
- return claudeCommands;
3691
- }
3692
-
3693
- // First ensure Claude is set up
3694
- if (selectedAgents.includes('claude')) {
3695
- setupAgent('claude', null, skipFiles);
3696
- }
3697
-
3698
- // Then load the commands (from existing or newly created)
3699
- getWorkflowCommands().forEach(cmd => {
3700
- const cmdPath = path.join(projectRoot, `.claude/commands/${cmd}.md`);
3701
- const content = readFile(cmdPath);
3702
- if (content) {
3703
- claudeCommands[`${cmd}.md`] = content;
3704
- }
3705
- });
3706
-
3707
- return claudeCommands;
3708
- }
3709
-
3710
3579
  // Helper: Setup all selected agents - extracted to reduce cognitive complexity
3711
- function setupSelectedAgents(selectedAgents, claudeCommands, skipFiles) {
3580
+ function setupSelectedAgents(selectedAgents, skipFiles) {
3712
3581
  const totalAgents = selectedAgents.length;
3713
3582
  selectedAgents.forEach((agentKey, index) => {
3714
3583
  const agent = AGENTS[agentKey];
3715
3584
  console.log(`\n[${index + 1}/${totalAgents}] Setting up ${agent.name}...`);
3716
3585
  if (agentKey !== 'claude') { // Claude already done above
3717
- setupAgent(agentKey, claudeCommands, skipFiles);
3586
+ setupAgent(agentKey, skipFiles);
3718
3587
  }
3719
3588
  });
3720
3589
 
@@ -3814,11 +3683,13 @@ async function interactiveSetupWithFlags(flags) {
3814
3683
  setupCoreDocs();
3815
3684
  console.log('');
3816
3685
 
3817
- // Load Claude commands if needed (delegated to helper)
3818
- const claudeCommands = loadAndSetupClaudeCommands(selectedAgents, skipFiles);
3686
+ // Setup Claude first if selected (delegated to helper), then remaining agents
3687
+ if (selectedAgents.includes('claude')) {
3688
+ setupAgent('claude', skipFiles);
3689
+ }
3819
3690
 
3820
3691
  // Setup each selected agent with progress indication (delegated to helper)
3821
- setupSelectedAgents(selectedAgents, claudeCommands, skipFiles);
3692
+ setupSelectedAgents(selectedAgents, skipFiles);
3822
3693
 
3823
3694
  // Handle external services step (delegated to helper)
3824
3695
  await handleExternalServicesStep(flags, rl, question, selectedAgents, projectStatus);
@@ -3832,14 +3703,20 @@ async function interactiveSetupWithFlags(flags) {
3832
3703
 
3833
3704
  // Main
3834
3705
  // Helper: Handle --path setup
3835
- function handlePathSetup(targetPath) {
3706
+ function handlePathSetup(targetPath, options = {}) {
3836
3707
  const resolvedPath = path.resolve(targetPath);
3837
3708
 
3838
3709
  // Create directory if it doesn't exist
3839
3710
  if (!fs.existsSync(resolvedPath)) {
3711
+ if (options.create === false) {
3712
+ console.error(`Error: ${resolvedPath} does not exist`);
3713
+ process.exit(1);
3714
+ }
3840
3715
  try {
3841
- fs.mkdirSync(resolvedPath, { recursive: true });
3716
+ fs.mkdirSync(resolvedPath, { recursive: true });
3717
+ if (!options.quiet) {
3842
3718
  console.log(`Created directory: ${resolvedPath}`);
3719
+ }
3843
3720
  } catch (err) {
3844
3721
  console.error(`Error creating directory: ${err.message}`);
3845
3722
  process.exit(1);
@@ -3855,8 +3732,10 @@ function handlePathSetup(targetPath) {
3855
3732
  // Change to target directory
3856
3733
  try {
3857
3734
  process.chdir(resolvedPath);
3858
- console.log(`Working directory: ${resolvedPath}`);
3859
- console.log('');
3735
+ if (!options.quiet) {
3736
+ console.log(`Working directory: ${resolvedPath}`);
3737
+ console.log('');
3738
+ }
3860
3739
  } catch (err) {
3861
3740
  console.error(`Error changing to directory: ${err.message}`);
3862
3741
  process.exit(1);
@@ -3866,6 +3745,15 @@ function handlePathSetup(targetPath) {
3866
3745
  return resolvedPath;
3867
3746
  }
3868
3747
 
3748
+ function resolvePathForDryRun(targetPath) {
3749
+ const resolvedPath = path.resolve(targetPath);
3750
+ if (fs.existsSync(resolvedPath) && !fs.statSync(resolvedPath).isDirectory()) {
3751
+ console.error(`Error: ${resolvedPath} is not a directory`);
3752
+ process.exit(1);
3753
+ }
3754
+ return resolvedPath;
3755
+ }
3756
+
3869
3757
  // Helper: Determine selected agents from flags
3870
3758
  function determineSelectedAgents(flags) {
3871
3759
  if (flags.all) {
@@ -3914,15 +3802,10 @@ function dryRunSetup(agents) {
3914
3802
  addFileAction(dir + '/', 'Create agent directory');
3915
3803
  }
3916
3804
 
3917
- // Claude-specific files
3805
+ // Claude-specific files (skills are listed by the per-skill block below)
3918
3806
  if (agentKey === 'claude') {
3919
- const cmds = getWorkflowCommands();
3920
- for (const cmd of cmds) {
3921
- addFileAction(`.claude/commands/${cmd}.md`, 'Workflow command');
3922
- }
3923
3807
  addFileAction('.claude/rules/workflow.md', 'Workflow rules');
3924
3808
  addFileAction('.claude/scripts/load-env.sh', 'Environment loader script');
3925
- addFileAction('.claude/skills/forge-workflow/SKILL.md', 'Forge workflow skill');
3926
3809
  addFileAction('.mcp.json', 'MCP server configuration');
3927
3810
  addFileAction('CLAUDE.md', 'Claude root config (links to AGENTS.md)');
3928
3811
  }
@@ -3932,15 +3815,7 @@ function dryRunSetup(agents) {
3932
3815
  addFileAction('.cursor/rules/forge-workflow.mdc', 'Cursor workflow rule');
3933
3816
  }
3934
3817
 
3935
- // Agent commands (converted from Claude format)
3936
- if (agent.needsConversion || agent.copyCommands || agent.promptFormat) {
3937
- const cmds = getWorkflowCommands();
3938
- const targetDir = agent.dirs[0];
3939
- for (const cmd of cmds) {
3940
- const ext = agent.promptFormat ? '.prompt.md' : '.md';
3941
- addFileAction(`${targetDir}/${cmd}${ext}`, 'Converted workflow command');
3942
- }
3943
- }
3818
+
3944
3819
 
3945
3820
  // Agent rules (copied from Claude)
3946
3821
  if (agent.needsConversion) {
@@ -3950,11 +3825,10 @@ function dryRunSetup(agents) {
3950
3825
  }
3951
3826
  }
3952
3827
 
3953
- // Agent skill
3954
- if (agent.hasSkill) {
3955
- const skillDir = agent.dirs.find(d => d.includes('/skills/'));
3956
- if (skillDir) {
3957
- addFileAction(`${skillDir}/SKILL.md`, 'Forge workflow skill');
3828
+ // Agent skill — enumerate canonical skills per-agent (codex handled globally elsewhere)
3829
+ if (agentKey !== 'codex' && agent.hasSkill && agent.skillsDir) {
3830
+ for (const skill of listCanonicalSkills(packageDir)) {
3831
+ addFileAction(`${agent.skillsDir}/${skill.name}/SKILL.md`, 'Forge stage skill');
3958
3832
  }
3959
3833
  }
3960
3834
 
@@ -3988,6 +3862,8 @@ async function executeSetup(config) {
3988
3862
  checkPrerequisites();
3989
3863
  console.log('');
3990
3864
 
3865
+ migrateExistingBeadsLocalState();
3866
+
3991
3867
  // Copy AGENTS.md (only if not exists — preserve user customizations; actionLog tracks it)
3992
3868
  const agentsDest = path.join(projectRoot, 'AGENTS.md');
3993
3869
  if (fs.existsSync(agentsDest)) {
@@ -4002,15 +3878,11 @@ async function executeSetup(config) {
4002
3878
  setupCoreDocs();
4003
3879
  console.log('');
4004
3880
 
4005
- // Load Claude commands use loadAndSetupClaudeCommands when claude is selected
4006
- // so that .claude/commands/ are seeded before reading them
4007
- const claudeCommands = agents.includes('claude')
4008
- ? loadAndSetupClaudeCommands(agents)
4009
- : loadClaudeCommands(agents);
4010
-
4011
- // Setup agents with progress output (setupSelectedAgents skips claude internally
4012
- // since loadAndSetupClaudeCommands already handled it above)
4013
- setupSelectedAgents(agents, claudeCommands);
3881
+ // Setup Claude first if selected, then remaining agents
3882
+ if (agents.includes('claude')) {
3883
+ setupAgent('claude');
3884
+ }
3885
+ setupSelectedAgents(agents);
4014
3886
 
4015
3887
  // Detect Husky and migrate before installing Lefthook hooks
4016
3888
  await handleHuskyMigration();
@@ -4036,36 +3908,12 @@ async function executeSetup(config) {
4036
3908
  // Helper: Scaffold Beads GitHub sync when --sync flag is provided
4037
3909
  async function handleSyncScaffold() {
4038
3910
  console.log('');
4039
- console.log('Scaffolding Beads GitHub sync workflows (--sync)...');
3911
+ console.log('Beads GitHub sync scaffolding is deprecated (--sync).');
4040
3912
  try {
4041
- // Scaffold sync files using the new lib module
4042
3913
  const result = scaffoldBeadsSync(projectRoot, packageDir);
4043
- for (const f of (result.filesCreated || [])) {
4044
- console.log(` Created: ${f}`);
4045
- }
4046
- for (const f of (result.filesSkipped || [])) {
4047
- console.log(` Skipped: ${f} (already exists)`);
4048
- }
4049
-
4050
- // Detect default branch and pin forge-managed workflows to the repo baseline Beads version.
4051
- const branch = detectDefaultBranch(projectRoot);
4052
- const beadsVersion = DEFAULT_BEADS_VERSION;
4053
- const workflowDir = path.join(projectRoot, '.github', 'workflows');
4054
- templateWorkflows(workflowDir, branch, beadsVersion, result.filesCreated || []);
4055
- console.log(` Branch: ${branch}, Beads version: ${beadsVersion}`);
4056
-
4057
- // PAT setup: interactive when possible, reminder otherwise
4058
- try {
4059
- const patResult = setupPAT(projectRoot, { interactive: !NON_INTERACTIVE });
4060
- if (patResult.success) {
4061
- console.log(' PAT configured for Beads sync');
4062
- } else if (patResult.reminder) {
4063
- console.log(` ${patResult.reminder}`);
4064
- } else if (patResult.instructions) {
4065
- console.log(` ${patResult.instructions.split('\n')[0]}`);
4066
- }
4067
- } catch (_patErr) {
4068
- // PAT setup is best-effort — don't block sync scaffold
3914
+ console.log(` ${result.message}`);
3915
+ for (const f of result.filesRemoved || []) {
3916
+ console.log(` Removed deprecated sync file: ${f}`);
4069
3917
  }
4070
3918
  } catch (err) {
4071
3919
  console.error(` Error scaffolding GitHub-Beads sync: ${err.message}`);
@@ -4089,6 +3937,11 @@ async function handleSetupCommand(selectedAgents, flags) {
4089
3937
  }
4090
3938
  }
4091
3939
 
3940
+ async function runInteractiveSetupFallback(flags, interactiveSetup = interactiveSetupWithFlags) {
3941
+ migrateExistingBeadsLocalState();
3942
+ return interactiveSetup(flags);
3943
+ }
3944
+
4092
3945
  // Helper: Handle external services configuration
4093
3946
  async function handleExternalServices(skipExternal, selectedAgents) {
4094
3947
  if (skipExternal) {
@@ -4117,8 +3970,19 @@ async function handleExternalServices(skipExternal, selectedAgents) {
4117
3970
  }
4118
3971
 
4119
3972
  async function main() {
4120
- const command = args[0];
3973
+ let command = args[0];
4121
3974
  const flags = parseFlags();
3975
+ const suppressJsonIntrospectionOutput = ['options', 'explain'].includes(command) && args.includes('--json');
3976
+ const suppressCommandJsonOutput = args.includes('--json');
3977
+ const suppressDocsDetectOutput = command === 'docs' && args[1] === 'detect';
3978
+ const profileDryRunArgs = ['--minimal', '--standard', '--full'];
3979
+ const suppressAdoptionDryRunOutput = flags.dryRun && (
3980
+ command === 'init' || (command === 'setup' && profileDryRunArgs.some(arg => args.includes(arg)))
3981
+ );
3982
+ const suppressStructuredOutput = suppressJsonIntrospectionOutput ||
3983
+ suppressCommandJsonOutput ||
3984
+ suppressDocsDetectOutput ||
3985
+ suppressAdoptionDryRunOutput;
4122
3986
 
4123
3987
  // Wire up incremental setup state from parsed flags
4124
3988
  FORCE_MODE = flags.force;
@@ -4128,20 +3992,33 @@ async function main() {
4128
3992
  SYNC_ENABLED = flags.sync;
4129
3993
  actionLog = new SetupActionLog();
4130
3994
 
4131
- if (NON_INTERACTIVE) {
3995
+ // The non-interactive agent-selection notice is DEBUG-ONLY (kernel issue
3996
+ // a9bbd065): it used to print on stderr for EVERY command run by a non-TTY
3997
+ // agent/CI, polluting otherwise-clean command output. It only matters when
3998
+ // setup-style agent selection actually happens, so it now requires --verbose
3999
+ // (or FORGE_DEBUG=1) to appear.
4000
+ if (NON_INTERACTIVE && (VERBOSE_MODE || process.env.FORGE_DEBUG === '1') && !suppressStructuredOutput) {
4132
4001
  const agentFlag = flags.agents;
4133
4002
  if (agentFlag && agentFlag.length > 0) {
4134
4003
  // flags.agents is a comma-separated string (e.g. "claude,cursor")
4135
4004
  const agentDisplay = agentFlag.replace(/,/g, ', ');
4136
- console.log(`Non-interactive mode: using provided agent selection (${agentDisplay})`);
4005
+ console.error(`Non-interactive mode: using provided agent selection (${agentDisplay})`);
4137
4006
  } else {
4138
- console.log('Non-interactive mode: using default agent selection (all)');
4007
+ console.error('Non-interactive mode: using default agent selection (all)');
4139
4008
  }
4140
4009
  }
4141
4010
 
4142
- // Show help
4011
+ // Show help. `--help` after a known registry subcommand (e.g. `forge status
4012
+ // --help`) renders that command's usage; bare `forge --help` keeps the global
4013
+ // banner. `setup` is special-cased OUT: the global banner IS setup's detailed
4014
+ // help, so `forge setup --help` must keep showing it (not the terse registry line).
4143
4015
  if (flags.help) {
4144
- showHelp();
4016
+ const helpRegistry = loadCommands(path.join(__dirname, '..', 'lib', 'commands'));
4017
+ if (command && command !== 'setup' && helpRegistry.commands.has(command)) {
4018
+ printCommandHelp(helpRegistry.commands.get(command));
4019
+ } else {
4020
+ showHelp();
4021
+ }
4145
4022
  return;
4146
4023
  }
4147
4024
 
@@ -4153,8 +4030,11 @@ async function main() {
4153
4030
 
4154
4031
  // Handle --path option: change to target directory
4155
4032
  if (flags.path) {
4033
+ const createTargetPath = !(command === 'docs' && ['verify', 'detect'].includes(args[1]));
4156
4034
  // Update projectRoot after changing directory to maintain state consistency
4157
- projectRoot = handlePathSetup(flags.path);
4035
+ projectRoot = suppressAdoptionDryRunOutput
4036
+ ? resolvePathForDryRun(flags.path)
4037
+ : handlePathSetup(flags.path, { quiet: suppressStructuredOutput, create: createTargetPath });
4158
4038
  }
4159
4039
 
4160
4040
  // Load command registry (auto-discovered commands from lib/commands/)
@@ -4177,27 +4057,100 @@ async function main() {
4177
4057
  }
4178
4058
  }
4179
4059
 
4060
+ // Back-compat alias handling (lib/commands/_aliases.js, the single declarative
4061
+ // source of truth generalised from the former ISSUE_ALIAS_COMMANDS array). Emit
4062
+ // an opt-in deprecation hint (stderr only, gated on FORGE_DEPRECATION_WARNINGS +
4063
+ // the alias being marked deprecated — never on stdout, so `--json` stays clean),
4064
+ // then resolve any bare alias whose name is NOT itself a registered command to
4065
+ // its canonical `<noun> <sub>` handler. For P0 every alias is also a registered
4066
+ // command file and none are deprecated, so nothing warns and nothing is
4067
+ // rewritten — dispatch stays byte-identical. Registered commands skip the
4068
+ // rewrite via the first clause; this activates only when a later phase folds a
4069
+ // bare verb into a noun handler.
4070
+ maybeWarnDeprecation(command);
4071
+ let dispatchArgv = args;
4072
+ if (!registry.commands.has(command) && isAlias(command)) {
4073
+ const resolved = resolveDispatch(command, args, (name) => registry.commands.has(name));
4074
+ command = resolved.command;
4075
+ dispatchArgv = resolved.args;
4076
+ }
4077
+
4078
+ // A workflow stage reached through its canonical noun form (e.g. `pr ship`,
4079
+ // whose bare alias `ship` IS a stage) must dispatch through the SAME top-level
4080
+ // stage path as the bare verb — stage-entry enforcement, kernel stage-run
4081
+ // recording, and ensureForgeHome all key on the stage token, which sits at the
4082
+ // subcommand position under a noun. Rewrite `<noun> <stage> [args]` to the
4083
+ // top-level `<stage> [args]` so behavior is byte-identical to the bare stage
4084
+ // alias. Detected precisely via the declarative alias map: the subcommand is a
4085
+ // stage AND its bare alias's canonical is exactly `<command> <sub>` (so only a
4086
+ // genuine noun→stage form matches; non-stage pr/gate subcommands route via
4087
+ // their noun handler as usual). Only `pr ship` matches today.
4088
+ //
4089
+ // The stage token is located as the first POSITIONAL after the noun (scanning
4090
+ // past any global flags and their values), so `forge pr --path /tmp ship ...`
4091
+ // shares the same stage path as `forge pr ship ...` instead of falling through
4092
+ // to pr.handler and missing stage-entry enforcement. Intervening global flags
4093
+ // are preserved into the rewritten argv so the stage handler still sees them.
4094
+ const stageIdx = firstPositionalIndex(dispatchArgv, 1);
4095
+ const nounSub = stageIdx >= 0 ? dispatchArgv[stageIdx] : undefined;
4096
+ if (nounSub && normalizeStageId(nounSub) && registry.commands.has(nounSub)) {
4097
+ const bareAlias = resolveAlias(nounSub);
4098
+ if (bareAlias && bareAlias.canonical === `${command} ${nounSub}`) {
4099
+ command = nounSub;
4100
+ // Drop only the consumed stage token (the noun at index 0 is dropped by the
4101
+ // registry dispatch's own `.slice(1)`); keep the intervening global flags.
4102
+ dispatchArgv = [nounSub, ...dispatchArgv.slice(1, stageIdx), ...dispatchArgv.slice(stageIdx + 1)];
4103
+ }
4104
+ }
4105
+
4180
4106
  // Registry command dispatch — auto-discovered commands take priority
4181
4107
  if (registry.commands.has(command)) {
4182
4108
  try {
4109
+ // Resolve the issue backend (flag > env > config > default) and, for issue
4110
+ // commands, strip the selector tokens (--kernel / --issue-backend) from the
4111
+ // args so they never reach the handler or bd. For the kernel backend this
4112
+ // also assembles the driver + migrated broker (B1/B2).
4113
+ const { commandOpts, args: dispatchArgs } = await resolveCommandOpts(
4114
+ command,
4115
+ dispatchArgv.slice(1),
4116
+ { env: process.env, projectRoot },
4117
+ );
4183
4118
  const result = await executeCommand(
4184
4119
  registry.commands,
4185
4120
  command,
4186
- args.slice(1),
4121
+ dispatchArgs,
4187
4122
  flags,
4188
4123
  projectRoot,
4189
4124
  {
4125
+ // Pass interactivity so issue writes render a human confirmation at a real
4126
+ // terminal but stay machine-parseable JSON when piped/scripted (842a8be7).
4127
+ commandOpts: { ...(commandOpts || {}), isInteractive: Boolean(process.stdout.isTTY) },
4190
4128
  enforceStage: (context) => enforceStageEntry({
4191
4129
  commandName: context.commandName,
4192
4130
  args: context.args,
4193
4131
  flags: context.flags,
4194
4132
  projectRoot: context.projectRoot,
4133
+ // Kernel is the source of truth for workflow stage state: resolve the
4134
+ // active worktree's issue and persist/read currentStage in the kernel
4135
+ // so `ship` is reachable from a pure-CLI plan->dev->validate flow.
4136
+ autoResolveKernel: true,
4195
4137
  }),
4196
4138
  }
4197
4139
  );
4198
4140
  if (result && !result.success) {
4141
+ if (
4142
+ typeof result.output === 'string' &&
4143
+ result.output.length > 0 &&
4144
+ result.output !== result.error
4145
+ ) {
4146
+ process.stdout.write(result.output.endsWith('\n') ? result.output : `${result.output}\n`);
4147
+ }
4199
4148
  console.error(result.error || result.message || 'Command failed');
4200
- process.exit(1);
4149
+ // Issue commands surface the issue-command-contract exit_code as
4150
+ // `result.exitCode` (e.g. not-found=3, validation=6). Honor it so failed
4151
+ // commands no longer collapse to exit 1; other registry commands omit
4152
+ // `exitCode` and keep the historical exit 1.
4153
+ process.exit(Number.isInteger(result.exitCode) ? result.exitCode : 1);
4201
4154
  }
4202
4155
  if (result && typeof result.output === 'string' && result.output.length > 0) {
4203
4156
  process.stdout.write(result.output.endsWith('\n') ? result.output : `${result.output}\n`);
@@ -4217,6 +4170,7 @@ async function main() {
4217
4170
  args: args.slice(1),
4218
4171
  flags,
4219
4172
  projectRoot,
4173
+ autoResolveKernel: true,
4220
4174
  });
4221
4175
  } catch (err) {
4222
4176
  console.error(`Error running '${command}':`, err.message);
@@ -4257,6 +4211,12 @@ async function main() {
4257
4211
  return;
4258
4212
  }
4259
4213
 
4214
+ if (flags.sync && selectedAgents.length === 0) {
4215
+ migrateExistingBeadsLocalState();
4216
+ await handleSyncScaffold();
4217
+ return;
4218
+ }
4219
+
4260
4220
  // Agents specified via flag or --yes default (non-quick mode)
4261
4221
  if (selectedAgents.length > 0) {
4262
4222
  await handleSetupCommand(selectedAgents, flags);
@@ -4264,7 +4224,7 @@ async function main() {
4264
4224
  }
4265
4225
 
4266
4226
  // Interactive setup (skip-external still applies)
4267
- await interactiveSetupWithFlags(flags);
4227
+ await runInteractiveSetupFallback(flags);
4268
4228
  } else if (command === 'recommend') {
4269
4229
  const { handleRecommend, formatRecommendations } = require('../lib/commands/recommend');
4270
4230
  const result = handleRecommend(flags, projectRoot);
@@ -4285,9 +4245,68 @@ async function main() {
4285
4245
  }
4286
4246
  console.log('');
4287
4247
  console.log(' Usage: forge docs <topic>');
4248
+ console.log(' forge docs verify [--path <dir>] [--json] [--baseline <file>] [--write-baseline <file>] [--min-docstring-coverage <percent>]');
4249
+ console.log(' forge docs detect [--path <dir>] [--json]');
4288
4250
  console.log('');
4251
+ console.log(' Note: forge docs verify|detect --path requires an existing directory.');
4252
+ console.log('');
4253
+ } else if (topic === 'verify' || topic === 'detect') {
4254
+ const minCoverageIndex = args.indexOf('--min-docstring-coverage');
4255
+ let minDocstringCoverage = 0;
4256
+ if (minCoverageIndex >= 0) {
4257
+ const rawMinCoverage = args[minCoverageIndex + 1];
4258
+ const parsedMinCoverage = Number(rawMinCoverage);
4259
+ if (!rawMinCoverage || !Number.isFinite(parsedMinCoverage) || parsedMinCoverage < 0 || parsedMinCoverage > 100) {
4260
+ console.error('Error: --min-docstring-coverage must be a number between 0 and 100');
4261
+ process.exitCode = 1;
4262
+ return;
4263
+ }
4264
+ minDocstringCoverage = parsedMinCoverage;
4265
+ }
4266
+ const baselineIndex = args.indexOf('--baseline');
4267
+ const writeBaselineIndex = args.indexOf('--write-baseline');
4268
+ const baselinePath = baselineIndex >= 0 ? args[baselineIndex + 1] : null;
4269
+ const writeBaselinePath = writeBaselineIndex >= 0 ? args[writeBaselineIndex + 1] : null;
4270
+ if (topic === 'detect' && writeBaselineIndex >= 0) {
4271
+ console.error('Error: --write-baseline is only supported with `forge docs verify`');
4272
+ process.exitCode = 1;
4273
+ return;
4274
+ }
4275
+ if (baselineIndex >= 0 && (!baselinePath || baselinePath.startsWith('-'))) {
4276
+ console.error('Error: --baseline requires a file path');
4277
+ process.exitCode = 1;
4278
+ return;
4279
+ }
4280
+ if (writeBaselineIndex >= 0 && (!writeBaselinePath || writeBaselinePath.startsWith('-'))) {
4281
+ console.error('Error: --write-baseline requires a file path');
4282
+ process.exitCode = 1;
4283
+ return;
4284
+ }
4285
+ let result;
4286
+ try {
4287
+ result = validateDocs(projectRoot, { baselinePath, minDocstringCoverage });
4288
+ if (writeBaselinePath) {
4289
+ writeDocsBaseline(projectRoot, writeBaselinePath, result);
4290
+ }
4291
+ } catch (error) {
4292
+ console.error(`Error: ${error.message}`);
4293
+ process.exitCode = 1;
4294
+ return;
4295
+ }
4296
+ if (args.includes('--json') || topic === 'detect') {
4297
+ console.log(JSON.stringify(result, null, 2));
4298
+ } else {
4299
+ console.log(formatDocsValidation(result));
4300
+ }
4301
+ const hasUnbaselinedFailure = result.failures.some((failure) => failure.type !== 'broken-link');
4302
+ if ((!writeBaselinePath && !result.ok) || (writeBaselinePath && hasUnbaselinedFailure)) {
4303
+ process.exitCode = 1;
4304
+ }
4289
4305
  } else {
4290
- const result = getTopicContent(topic, packageDir);
4306
+ // `forge docs` reads packaged docs; route through the asset root so any
4307
+ // embedded topic resolves in a compiled binary too. (Full docs/ embedding
4308
+ // for arbitrary topics is deferred to step 3 — see the step-2 plan note.)
4309
+ const result = getTopicContent(topic, getPackageRoot(packageDir));
4291
4310
  if (result.error) {
4292
4311
  console.error(` Error: ${result.error}`);
4293
4312
  process.exitCode = 1;
@@ -4389,9 +4408,34 @@ async function main() {
4389
4408
  console.log(' To set up in your project:');
4390
4409
  console.log(` ${runCmd} forge setup`);
4391
4410
  console.log('');
4392
- } else {
4393
- // Explicit invocation with no command: run minimal install
4411
+ } else if (command === undefined && fs.existsSync(path.join(projectRoot, 'AGENTS.md'))) {
4412
+ // Bare `forge` in an INITIALIZED project orients instead of mutating: render the
4413
+ // same read-only one-glance view as `forge status`. Newcomers typing `forge` to
4414
+ // get their bearings should never trigger the minimal install.
4415
+ try {
4416
+ const { commandOpts, args: dispatchArgs } = await resolveCommandOpts(
4417
+ 'status',
4418
+ [],
4419
+ { env: process.env, projectRoot },
4420
+ );
4421
+ const result = await executeCommand(registry.commands, 'status', dispatchArgs, flags, projectRoot, { commandOpts });
4422
+ if (result && typeof result.output === 'string' && result.output.length > 0) {
4423
+ process.stdout.write(result.output.endsWith('\n') ? result.output : `${result.output}\n`);
4424
+ }
4425
+ } catch (err) {
4426
+ console.error(`Error running 'status':`, err.message);
4427
+ process.exit(1);
4428
+ }
4429
+ } else if (command === undefined) {
4430
+ // Explicit invocation with no command in an UNINITIALIZED project: run minimal install.
4394
4431
  minimalInstall();
4432
+ } else {
4433
+ // Defined but unrecognized command → fail honestly instead of printing the
4434
+ // installer banner with exit 0 (kernel b3bae4f3). A typo like `forge bogusxyz`
4435
+ // must be detectable by scripts (non-zero exit) and humans (a real error).
4436
+ console.error(`Error: Unknown command '${command}'`);
4437
+ console.error("Run 'forge --help' to see the available commands.");
4438
+ process.exit(1);
4395
4439
  }
4396
4440
  }
4397
4441
 
@@ -4815,7 +4859,6 @@ if (require.main === module) {
4815
4859
 
4816
4860
  module.exports = {
4817
4861
  ensureDirWithNote,
4818
- getWorkflowCommands,
4819
4862
  validateDirectoryPathInput,
4820
4863
  validateUserInput,
4821
4864
  };