forge-workflow 0.0.9 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +466 -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/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/reference/TOOLCHAIN.md +658 -0
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +66 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +22 -2
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +851 -979
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +697 -0
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -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/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  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-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. 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,41 @@
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 { resolveCommandOpts } = require('../lib/commands/_resolve-command-opts');
64
+ const { getPackageRoot } = require('../lib/package-root');
55
65
  const { enforceStageEntry } = require('../lib/workflow/enforce-stage');
56
66
  const { normalizeStageId } = require('../lib/workflow/stages');
57
67
 
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'));
68
+ // Load enhanced onboarding modules (static relative requires — bundleable)
69
+ const contextMerge = require('../lib/context-merge');
70
+ const projectDiscovery = require('../lib/project-discovery');
61
71
 
62
72
  // 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 { detectDefaultBranch, detectBeadsVersion, templateWorkflows, scaffoldBeadsSync } = require(path.join(packageDir, 'lib', 'beads-sync-scaffold'));
73
+ const { createSymlinkOrCopy: libCreateSymlinkOrCopy } = require('../lib/symlink-utils');
74
+ const beadsSetupLib = require('../lib/beads-setup');
75
+ const { scaffoldBeadsSync } = require('../lib/beads-sync-scaffold');
68
76
 
69
77
  // Load incremental setup modules
70
78
  const { detectEnvironment } = require('../lib/detect-agent');
@@ -74,6 +82,12 @@ const { ActionCollector, isNonInteractive } = require('../lib/setup-utils');
74
82
  const { renderSetupSummary } = require('../lib/setup-summary-renderer');
75
83
  const { smartMergeAgentsMd } = require('../lib/smart-merge');
76
84
  const { checkLefthookStatus } = require('../lib/lefthook-check');
85
+ const {
86
+ FORGE_USER_LEFTHOOK_YML,
87
+ forgeShouldWriteLefthookConfig,
88
+ installNativeGitHooks,
89
+ verifyHooksActive,
90
+ } = require('../lib/lefthook-wiring');
77
91
  const { detectHusky, migrateHusky } = require('../lib/husky-migration');
78
92
  // workflowProfiles: module exists and is tested but not yet wired into setup flow
79
93
  // Will be activated when workflow profile selection is added to interactive setup
@@ -94,37 +108,10 @@ let actionLog = new SetupActionLog();
94
108
  // Detected package manager
95
109
  let PKG_MANAGER = 'npm';
96
110
 
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
- }
111
+ // Securely execute a command with PATH validation (SonarCloud S4036).
112
+ // Delegates to the shared helper, which also handles Windows npm/npx/lefthook
113
+ // cmd shims that cannot be spawned directly (kernel issue 9997d516).
114
+ const { secureExecFileSync } = require('../lib/shell-utils');
128
115
 
129
116
  /**
130
117
  * Load agent definitions from plugin architecture
@@ -146,7 +133,8 @@ function loadAgentsFromPlugins() {
146
133
  customSetup: plugin.setup?.customSetup || '',
147
134
  needsConversion: plugin.setup?.needsConversion || false,
148
135
  copyCommands: plugin.setup?.copyCommands || false,
149
- promptFormat: plugin.setup?.promptFormat || false
136
+ promptFormat: plugin.setup?.promptFormat || false,
137
+ skillsDir: plugin.directories?.skills || null
150
138
  };
151
139
  });
152
140
 
@@ -295,25 +283,6 @@ function _checkWritePermission(filePath) {
295
283
  }
296
284
  }
297
285
 
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
286
 
318
287
 
319
288
  // Helper function to safely execute commands (no user input)
@@ -432,72 +401,26 @@ function checkPrerequisites() {
432
401
  return { errors, warnings };
433
402
  }
434
403
 
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
404
 
483
405
  // Cursor MDC rule content
484
406
  const CURSOR_RULE = `---
485
- description: Forge 7-Stage TDD Workflow
407
+ description: Forge TDD Workflow Template
486
408
  alwaysApply: true
487
409
  ---
488
410
 
489
411
  # Forge Workflow Commands
490
412
 
491
- Use these commands via \`/command-name\`:
413
+ Use these default-template commands via \`/command-name\`:
492
414
 
493
415
  - \`/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)
416
+ - \`/plan\` - Design intent Q&A, research, branch + task list
417
+ - \`/dev\` - Subagent-driven TDD per task (spec + quality review)
418
+ - \`/validate\` - Type check, lint, security, tests (HARD-GATE)
419
+ - \`/ship\` - Push and create PR with design doc reference
420
+ - \`/review\` - Handle ALL PR issues (Actions, Greptile, SonarCloud)
421
+ - \`/verify\` - Post-merge health check (CI on main, close Beads)
422
+
423
+ Pre-merge gate (not a numbered stage): finish docs + confirm CI green + hand off the PR — embedded in the /ship and /review stages.
501
424
 
502
425
  See AGENTS.md for full workflow details.
503
426
  `;
@@ -543,17 +466,6 @@ function writeFile(filePath, content) {
543
466
  }
544
467
  }
545
468
 
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
469
  function copyFile(src, dest) {
558
470
  try {
559
471
  const destPath = path.resolve(projectRoot, dest);
@@ -612,11 +524,6 @@ function createSymlinkOrCopy(source, target, options = {}) {
612
524
  return libCreateSymlinkOrCopy(fullSource, fullTarget, options);
613
525
  }
614
526
 
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
527
  // Read existing .env.local
621
528
  function readEnvFile() {
622
529
  const envPath = path.join(projectRoot, '.env.local');
@@ -781,7 +688,6 @@ async function detectProjectStatus() {
781
688
  type: 'fresh', // 'fresh', 'upgrade', or 'partial'
782
689
  hasAgentsMd: fs.existsSync(path.join(projectRoot, 'AGENTS.md')),
783
690
  hasClaudeMd: fs.existsSync(path.join(projectRoot, 'CLAUDE.md')),
784
- hasClaudeCommands: fs.existsSync(path.join(projectRoot, '.claude/commands')),
785
691
  hasEnvLocal: fs.existsSync(path.join(projectRoot, '.env.local')),
786
692
  existingEnvVars: {},
787
693
  agentsMdSize: 0,
@@ -815,9 +721,9 @@ async function detectProjectStatus() {
815
721
  }
816
722
 
817
723
  // Determine installation type
818
- if (status.hasAgentsMd && status.hasClaudeCommands) {
724
+ if (status.hasAgentsMd) {
819
725
  status.type = 'upgrade'; // Full forge installation exists
820
- } else if (status.hasClaudeCommands || status.hasEnvLocal) {
726
+ } else if (status.hasEnvLocal) {
821
727
  status.type = 'partial'; // Agent-specific files exist (not just base files from postinstall)
822
728
  }
823
729
  // else: 'fresh' - new installation (or just postinstall baseline with AGENTS.md)
@@ -1665,7 +1571,6 @@ function displayMcpStatus(selectedAgents) {
1665
1571
  // Show manual setup instructions for GUI-based agents
1666
1572
  const manualMcpMap = {
1667
1573
  cursor: 'Cursor: Configure via Cursor Settings > MCP',
1668
- cline: 'Cline: Install via MCP Marketplace',
1669
1574
  };
1670
1575
  const needsManualMcp = Object.entries(manualMcpMap)
1671
1576
  .filter(([key]) => selectedAgents.includes(key))
@@ -1748,39 +1653,6 @@ async function configureExternalServices(rl, question, selectedAgents = [], proj
1748
1653
  const { added, preserved } = writeEnvTokens(tokens, true);
1749
1654
  displayEnvTokenResults(added, preserved);
1750
1655
 
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
1656
  }
1785
1657
 
1786
1658
  // Display the Forge banner
@@ -1891,26 +1763,20 @@ function minimalInstall() {
1891
1763
  console.log('');
1892
1764
  }
1893
1765
 
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
- }
1766
+ // ⚠️ LEGACY / DEAD SETUP PATH ⚠️
1767
+ // The functions below (setupClaudeAgent, setupCursorAgent, copyAgentRules,
1768
+ // setupClaudeMcpConfig, setupAgent, quickSetup, dryRunSetup, …) are the OLD
1769
+ // inline setup implementation. `forge setup` is now the registry command in
1770
+ // lib/commands/setup.js, which takes priority in the dispatcher and `return`s
1771
+ // before the inline `if (command === 'setup')` block is ever reached. This copy
1772
+ // is kept only until the inline setup subsystem is fully removed — the authority
1773
+ // is lib/commands/setup.js. Do not add behavior here; edit lib/commands/setup.js.
1908
1774
 
1909
- // Copy rules
1910
- const rulesSrc = path.join(packageDir, '.claude/rules/workflow.md');
1911
- copyFile(rulesSrc, '.claude/rules/workflow.md');
1912
-
1913
- // Copy scripts
1775
+ // Helper: Setup Claude agent
1776
+ function setupClaudeAgent() {
1777
+ // Skills-only surface: per-skill SKILL.md dirs are populated by createAgentSkill.
1778
+ // Claude receives policy via CLAUDE.md → AGENTS.md, NOT an always-on
1779
+ // `.claude/rules/workflow.md` (that canonical file was removed). Only scripts copy.
1914
1780
  const scriptSrc = path.join(packageDir, '.claude/scripts/load-env.sh');
1915
1781
  copyFile(scriptSrc, '.claude/scripts/load-env.sh');
1916
1782
  }
@@ -1921,61 +1787,23 @@ function setupCursorAgent() {
1921
1787
  console.log(' Created: .cursor/rules/forge-workflow.mdc');
1922
1788
  }
1923
1789
 
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);
1790
+ // Helper: Create skill file for agent
1791
+ function createAgentSkill(agent, agentKey) {
1792
+ if (agentKey === 'codex') {
1793
+ // Codex global skill install is handled via buildCodexSkillInstallPlan in setup.js
1794
+ return;
1931
1795
  }
1932
1796
 
1933
- if (agent.promptFormat) {
1934
- targetFile = cmd.replace('.md', '.prompt.md');
1935
- targetContent = stripFrontmatter(content);
1936
- }
1797
+ if (!agent.hasSkill || !agent.skillsDir) return;
1937
1798
 
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;
1945
-
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);
1799
+ // Skills-only surface: populate every canonical skill into the agent skills dir
1800
+ // (.claude/skills, .cursor/skills) from the packaged canonical `skills/` source.
1801
+ const { written } = populateAgentSkills({
1802
+ sourceRoot: packageDir,
1803
+ targetSkillsDir: path.join(projectRoot, agent.skillsDir),
1804
+ clean: true,
1950
1805
  });
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
- }
1806
+ console.log(` Created: ${written.length} skills in ${agent.skillsDir}/`);
1979
1807
  }
1980
1808
 
1981
1809
  // Helper: Setup MCP config for Claude
@@ -2010,7 +1838,7 @@ function createAgentLinkFile(agent, symlinkOnly = false) {
2010
1838
  }
2011
1839
 
2012
1840
  // Setup specific agent
2013
- function setupAgent(agentKey, claudeCommands, skipFiles = {}) {
1841
+ function setupAgent(agentKey, skipFiles = {}) {
2014
1842
  const agent = AGENTS[agentKey];
2015
1843
  if (!agent) return;
2016
1844
 
@@ -2028,14 +1856,8 @@ function setupAgent(agentKey, claudeCommands, skipFiles = {}) {
2028
1856
  setupCursorAgent();
2029
1857
  }
2030
1858
 
2031
- // Convert/copy commands
2032
- copyAgentCommands(agent, claudeCommands);
2033
-
2034
- // Copy rules if needed
2035
- copyAgentRules(agent);
2036
-
2037
1859
  // Create SKILL.md
2038
- createAgentSkill(agent);
1860
+ createAgentSkill(agent, agentKey);
2039
1861
 
2040
1862
  // Setup MCP configs
2041
1863
  if (agentKey === 'claude') {
@@ -2069,7 +1891,6 @@ function displayInstallationStatus(projectStatus) {
2069
1891
  }
2070
1892
 
2071
1893
  if (projectStatus.hasAgentsMd) console.log(' - AGENTS.md');
2072
- if (projectStatus.hasClaudeCommands) console.log(' - .claude/commands/');
2073
1894
  if (projectStatus.hasEnvLocal) console.log(' - .env.local');
2074
1895
  console.log('');
2075
1896
  }
@@ -2133,7 +1954,6 @@ async function promptForFileOverwrite(question, fileType, exists, skipFiles) {
2133
1954
 
2134
1955
  const fileLabels = {
2135
1956
  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
1957
  };
2138
1958
 
2139
1959
  const config = fileLabels[fileType];
@@ -2308,35 +2128,12 @@ async function installAgentsMd(skipFiles) {
2308
2128
  }
2309
2129
  }
2310
2130
 
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
2131
  /**
2335
2132
  * Setup agents with progress indication
2336
2133
  * Delegates to setupSelectedAgents to avoid duplicate implementations (S4144)
2337
2134
  */
2338
- function setupAgentsWithProgress(selectedAgents, claudeCommands, skipFiles) {
2339
- setupSelectedAgents(selectedAgents, claudeCommands, skipFiles);
2135
+ function setupAgentsWithProgress(selectedAgents, skipFiles) {
2136
+ setupSelectedAgents(selectedAgents, skipFiles);
2340
2137
  }
2341
2138
 
2342
2139
  /**
@@ -2351,15 +2148,11 @@ function displaySetupSummary(selectedAgents) {
2351
2148
  console.log('What\'s installed:');
2352
2149
  console.log(' - AGENTS.md (universal instructions)');
2353
2150
 
2354
- const workflowCount = getWorkflowCommands().length;
2355
2151
  selectedAgents.forEach(key => {
2356
2152
  const agent = AGENTS[key];
2357
2153
  if (agent.linkFile) {
2358
2154
  console.log(` - ${agent.linkFile} (${agent.name})`);
2359
2155
  }
2360
- if (agent.hasCommands) {
2361
- console.log(` - .claude/commands/ (${workflowCount} workflow commands)`);
2362
- }
2363
2156
  if (agent.hasSkill) {
2364
2157
  const skillDir = agent.dirs.find(d => d.includes('/skills/'));
2365
2158
  if (skillDir) {
@@ -2464,12 +2257,10 @@ async function _interactiveSetup() {
2464
2257
  // Track which files to skip based on user choices
2465
2258
  const skipFiles = {
2466
2259
  agentsMd: false,
2467
- claudeCommands: false
2468
2260
  };
2469
2261
 
2470
2262
  // Ask about overwriting existing files
2471
2263
  await promptForFileOverwrite(question, 'agentsMd', projectStatus.hasAgentsMd, skipFiles);
2472
- await promptForFileOverwrite(question, 'claudeCommands', projectStatus.hasClaudeCommands, skipFiles);
2473
2264
 
2474
2265
  if (projectStatus.type !== 'fresh') {
2475
2266
  console.log('');
@@ -2492,19 +2283,13 @@ async function _interactiveSetup() {
2492
2283
  setupCoreDocs();
2493
2284
  console.log('');
2494
2285
 
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);
2286
+ // Setup Claude first if selected, then remaining agents
2287
+ if (selectedAgents.includes('claude')) {
2288
+ setupAgent('claude', skipFiles);
2504
2289
  }
2505
2290
 
2506
2291
  // Setup each selected agent with progress indication
2507
- setupAgentsWithProgress(selectedAgents, claudeCommands, skipFiles);
2292
+ setupAgentsWithProgress(selectedAgents, skipFiles);
2508
2293
 
2509
2294
  // =============================================
2510
2295
  // STEP 2: Project Tools Setup
@@ -2530,6 +2315,16 @@ async function _interactiveSetup() {
2530
2315
  }
2531
2316
 
2532
2317
  // Parse CLI flags
2318
+ // Issue command aliases hidden from `forge --help`: the bare passthroughs that
2319
+ // duplicate `forge issue <sub>` plus the plural `issues`. They stay registered and
2320
+ // routable (back-compat), but are omitted from help so `forge issue` reads as the
2321
+ // single canonical issue surface (kernel issue 450c6e34). The canonical `issue` is
2322
+ // deliberately NOT listed here — it stays documented.
2323
+ const ISSUE_ALIAS_COMMANDS = [
2324
+ 'create', 'update', 'claim', 'close', 'show', 'list',
2325
+ 'ready', 'blocked', 'stale', 'orphans', 'lint', 'claims', 'issues',
2326
+ ];
2327
+
2533
2328
  function parseFlags() {
2534
2329
  const flags = {
2535
2330
  quick: false,
@@ -2554,7 +2349,8 @@ function parseFlags() {
2554
2349
 
2555
2350
  // Issue passthrough commands delegate all flags to bd.
2556
2351
  // Skip global parsing so flags like --type, -p, --help reach the handler intact.
2557
- const issuePassthroughCommands = ['create', 'update', 'claim', 'close', 'show', 'list', 'ready', 'issue'];
2352
+ // Canonical `issue` plus the hidden back-compat aliases (single source of truth).
2353
+ const issuePassthroughCommands = [...ISSUE_ALIAS_COMMANDS, 'issue'];
2558
2354
  if (issuePassthroughCommands.includes(args[0])) {
2559
2355
  return flags;
2560
2356
  }
@@ -2757,6 +2553,7 @@ function showHelp() {
2757
2553
  console.log('Options:');
2758
2554
  console.log(' --path, -p <dir> Target project directory (default: current directory)');
2759
2555
  console.log(' Creates the directory if it doesn\'t exist');
2556
+ console.log(' Exception: docs verify|detect --path requires an existing directory');
2760
2557
  console.log(' --quick, -q Use all defaults, minimal prompts');
2761
2558
  console.log(' Auto-selects: all agents, GitHub Code Quality, ESLint');
2762
2559
  console.log(' --skip-external Skip external services configuration');
@@ -2802,9 +2599,29 @@ function showHelp() {
2802
2599
  console.log('Also works with bun:');
2803
2600
  console.log(' bunx forge setup --quick');
2804
2601
  console.log('');
2805
-
2806
- // Append auto-discovered registry commands
2602
+ // Adoption profile + gate/classification config is owned by `forge init`, not
2603
+ // `forge setup`. The --minimal/--standard/--full profile flags are `forge init`
2604
+ // shortcuts (setup only reads them for `--dry-run` preview), so point there.
2605
+ console.log('Adoption profile, gates & classification (configured by `forge init`, not setup):');
2606
+ console.log(' forge init --minimal | --standard | --full # adoption-profile shortcut');
2607
+ console.log(' # (or: --profile minimal|standard|full)');
2608
+ console.log(' forge init --classification critical|standard|refactor');
2609
+ console.log(' `forge init` writes `.forge/config.yaml` (gate + classification config).');
2610
+ console.log(' Run `forge init --help` for all profile/classification/harness flags.');
2611
+ console.log('');
2612
+
2613
+ // Append auto-discovered registry commands. Hidden issue aliases (the bare
2614
+ // passthroughs + plural `issues`, plus any command self-declaring `hidden: true`)
2615
+ // still route and execute, but are trimmed from this enumeration so `forge issue`
2616
+ // reads as the single canonical issue surface. This registry instance is loaded
2617
+ // solely to render help, so deleting from its Map only affects the printed list —
2618
+ // command dispatch (main) uses a separate registry and is unaffected.
2807
2619
  const helpRegistry = loadCommands(path.join(__dirname, '..', 'lib', 'commands'));
2620
+ for (const [name, cmd] of [...helpRegistry.commands]) {
2621
+ if (ISSUE_ALIAS_COMMANDS.includes(name) || cmd.hidden === true) {
2622
+ helpRegistry.commands.delete(name);
2623
+ }
2624
+ }
2808
2625
  const registryHelp = helpRegistry.getHelp();
2809
2626
  if (registryHelp) {
2810
2627
  console.log('Additional commands:');
@@ -2813,6 +2630,26 @@ function showHelp() {
2813
2630
  }
2814
2631
  }
2815
2632
 
2633
+ // Per-command help. When `--help` follows a known subcommand (e.g. `forge status
2634
+ // --help`), render THAT command's usage/description/flags instead of the global
2635
+ // setup banner — a newcomer asking for a command's help should not get the
2636
+ // installer's help.
2637
+ function printCommandHelp(cmd) {
2638
+ console.log(`forge ${cmd.name} — ${cmd.description}`);
2639
+ if (cmd.usage) {
2640
+ console.log('');
2641
+ console.log(/^(Usage:|forge )/.test(cmd.usage) ? cmd.usage : `Usage: ${cmd.usage}`);
2642
+ }
2643
+ const flagEntries = cmd.flags && typeof cmd.flags === 'object' ? Object.entries(cmd.flags) : [];
2644
+ if (flagEntries.length > 0) {
2645
+ console.log('');
2646
+ console.log('Flags:');
2647
+ for (const [flag, desc] of flagEntries) {
2648
+ console.log(` ${flag.padEnd(16)} ${desc}`);
2649
+ }
2650
+ }
2651
+ }
2652
+
2816
2653
  // Detect Husky and offer migration to Lefthook
2817
2654
  // Called before installGitHooks() in setup flows
2818
2655
  async function handleHuskyMigration() {
@@ -2863,80 +2700,104 @@ async function handleHuskyMigration() {
2863
2700
  function installGitHooks() {
2864
2701
  console.log('Installing git hooks (TDD enforcement)...');
2865
2702
 
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
- }
2703
+ // Install the Forge hook SCRIPTS first, UNCONDITIONALLY - they back BOTH the lefthook
2704
+ // pre-commit job AND the native .git/hooks fallback below, so they must exist whether
2705
+ // or not the lefthook binary is available.
2706
+ installForgeHookScripts();
2876
2707
 
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');
2708
+ const lefthookStatus = checkLefthookStatus(projectRoot);
2709
+ let lefthookInstalled = false;
2880
2710
 
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')) {
2711
+ if (lefthookStatus.binaryAvailable) {
2712
+ try {
2713
+ // Write the REAL user-facing lefthook.yml - never the repo's own dev config (which
2714
+ // references repo-internal scripts/ a user project lacks), and never leave
2715
+ // lefthook's stock commented-out example in place. Overwrite only a missing file
2716
+ // or a fully-commented stub, never a config with active jobs (kernel e452422c).
2717
+ const lefthookTarget = path.join(projectRoot, 'lefthook.yml');
2718
+ if (forgeShouldWriteLefthookConfig(lefthookTarget)) {
2719
+ fs.writeFileSync(lefthookTarget, FORGE_USER_LEFTHOOK_YML, 'utf8');
2886
2720
  console.log(' ✓ Created lefthook.yml');
2887
2721
  }
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
2722
 
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
2723
+ // Install lefthook's git hooks. Try local (npx) first, then a global binary.
2724
+ // SECURITY: execFileSync with hardcoded commands (no user input).
2916
2725
  try {
2917
2726
  secureExecFileSync('npx', ['lefthook', 'install'], { stdio: 'inherit', cwd: projectRoot });
2918
2727
  console.log(' ✓ Lefthook hooks installed (local)');
2728
+ lefthookInstalled = true;
2919
2729
  } catch (error_) {
2920
- // Fallback to global lefthook
2921
2730
  console.warn('npx lefthook failed, trying global:', error_.message);
2922
- execFileSync('lefthook', ['version'], { stdio: 'ignore' });
2923
- execFileSync('lefthook', ['install'], { stdio: 'inherit', cwd: projectRoot });
2731
+ secureExecFileSync('lefthook', ['version'], { stdio: 'ignore' });
2732
+ secureExecFileSync('lefthook', ['install'], { stdio: 'inherit', cwd: projectRoot });
2924
2733
  console.log(' ✓ Lefthook hooks installed (global)');
2734
+ lefthookInstalled = true;
2925
2735
  }
2926
2736
  } catch (err) {
2927
2737
  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
2738
  }
2739
+ } else if (lefthookStatus.message) {
2740
+ console.warn(` ⚠ lefthook binary unavailable: ${lefthookStatus.message}`);
2741
+ }
2742
+
2743
+ // Native fallback: when lefthook is unavailable or its install failed, wire native
2744
+ // .git/hooks so raw `git commit` / `git push` still enforce the TDD gate - the moat is
2745
+ // never silently inert (B3). Runs regardless of whether a package.json exists.
2746
+ if (!lefthookInstalled) {
2747
+ const native = installNativeGitHooks(projectRoot);
2748
+ if (native.installed) {
2749
+ console.log(` ✓ Native git hooks installed (${native.written.join(', ')}) - lefthook fallback`);
2750
+ } else if (native.skipped && native.skipped.length > 0) {
2751
+ console.warn(` ⚠ Native hook(s) skipped to preserve existing hooks: ${native.skipped.join(', ')}`);
2752
+ } else {
2753
+ console.warn(` ⚠ Could not install native git hooks: ${native.reason || 'unknown'}`);
2754
+ }
2755
+ }
2933
2756
 
2934
- console.log('');
2757
+ // VERIFY LOUDLY: never let setup silently no-op. If pre-commit enforcement is not
2758
+ // actually active after all of the above, say so unmistakably and set a failure code.
2759
+ const verdict = verifyHooksActive(projectRoot);
2760
+ if (verdict.active) {
2761
+ console.log(` ✓ Git hook enforcement active (${verdict.method}).`);
2762
+ } else {
2763
+ const addCmd = PKG_MANAGER === 'bun'
2764
+ ? 'bun add -d'
2765
+ : PKG_MANAGER === 'npm'
2766
+ ? 'npm install --save-dev'
2767
+ : `${PKG_MANAGER} add -D`;
2768
+ console.error('');
2769
+ console.error(' ============================================================');
2770
+ console.error(' ⚠ TDD ENFORCEMENT IS NOT ACTIVE');
2771
+ console.error(` ${verdict.reason || 'no pre-commit hook is installed'}.`);
2772
+ console.error(' `forge ship` will block until hooks are active. To fix:');
2773
+ console.error(` ${addCmd} lefthook && npx lefthook install`);
2774
+ console.error(' (or re-run `forge setup` in the repo root).');
2775
+ console.error(' ============================================================');
2776
+ console.error('');
2777
+ process.exitCode = 1;
2778
+ }
2935
2779
 
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('');
2780
+ console.log('');
2781
+ }
2782
+
2783
+ // Copy the Forge hook scripts (check-tdd.js + the native-hook adapter) into the
2784
+ // project's .forge/hooks/. Runs UNCONDITIONALLY - independent of the lefthook binary -
2785
+ // because both the lefthook pre-commit job and the native fallback invoke them.
2786
+ function installForgeHookScripts() {
2787
+ const targetHooks = path.join(projectRoot, '.forge/hooks');
2788
+ for (const name of ['check-tdd.js', 'forge-native-hook.js']) {
2789
+ const src = path.join(packageDir, '.forge/hooks', name);
2790
+ if (!fs.existsSync(src)) continue;
2791
+ if (!fs.existsSync(targetHooks)) fs.mkdirSync(targetHooks, { recursive: true });
2792
+ const dest = path.join(targetHooks, name);
2793
+ if (copyFile(src, dest)) {
2794
+ console.log(` ✓ Created .forge/hooks/${name}`);
2795
+ try {
2796
+ fs.chmodSync(dest, 0o755); // NOSONAR - hook scripts must be executable
2797
+ } catch (err) {
2798
+ console.warn('chmod not available (Windows):', err.message);
2799
+ }
2800
+ }
2940
2801
  }
2941
2802
  }
2942
2803
 
@@ -2987,6 +2848,18 @@ function isBeadsInitialized() {
2987
2848
  return beadsSetupLib.isBeadsInitialized(projectRoot);
2988
2849
  }
2989
2850
 
2851
+ function migrateExistingBeadsLocalState() {
2852
+ if (!fs.existsSync(path.join(projectRoot, '.beads'))) {
2853
+ return;
2854
+ }
2855
+
2856
+ try {
2857
+ beadsSetupLib.ensureBeadsGitExclude(projectRoot);
2858
+ } catch (err) {
2859
+ console.warn(` Warning: failed to migrate Beads local state: ${err.message}`);
2860
+ }
2861
+ }
2862
+
2990
2863
  // Initialize Beads in the project using the defensive safeBeadsInit wrapper
2991
2864
  // Handles config/gitignore writes, hook snapshot/restore, and JSONL pre-seeding
2992
2865
  function initializeBeads(installType) {
@@ -3044,18 +2917,6 @@ function initializeBeads(installType) {
3044
2917
  }
3045
2918
  console.log(' ✓ Beads initialized');
3046
2919
 
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
2920
  return true;
3060
2921
  } catch (err) {
3061
2922
  console.log(' ⚠ Failed to initialize Beads:', err.message);
@@ -3131,6 +2992,7 @@ async function promptBeadsSetup(question) {
3131
2992
  const beadsStatus = checkForBeads();
3132
2993
 
3133
2994
  if (beadsInitialized) {
2995
+ migrateExistingBeadsLocalState();
3134
2996
  console.log('✓ Beads is already initialized in this project');
3135
2997
  console.log('');
3136
2998
  return;
@@ -3364,6 +3226,10 @@ function autoSetupBeadsInQuickMode() {
3364
3226
  const beadsStatus = checkForBeads();
3365
3227
  const beadsInitialized = isBeadsInitialized();
3366
3228
 
3229
+ if (beadsInitialized) {
3230
+ migrateExistingBeadsLocalState();
3231
+ }
3232
+
3367
3233
  if (!beadsInitialized && beadsStatus) {
3368
3234
  console.log('📦 Initializing Beads...');
3369
3235
  initializeBeads(beadsStatus);
@@ -3415,7 +3281,19 @@ function autoInstallLefthook() {
3415
3281
  return;
3416
3282
  }
3417
3283
 
3418
- // Not in package.json at all — full install
3284
+ // Not in package.json at all — full install.
3285
+ // GUARD (kernel 22e33dbf): with no package.json in projectRoot, an
3286
+ // `npm install --save-dev lefthook` / `bun add lefthook` resolves against the nearest
3287
+ // ANCESTOR package.json and installs lefthook into the WRONG project (or fails). Skip
3288
+ // the package-manager install entirely — installGitHooks() then wires native
3289
+ // .git/hooks, which need no package.json, so enforcement is still live.
3290
+ if (!fs.existsSync(path.join(projectRoot, 'package.json'))) {
3291
+ console.log(' ℹ No package.json here — skipping lefthook npm install (would target an ancestor).');
3292
+ console.log(' Git hook enforcement will use the native .git/hooks fallback instead.');
3293
+ console.log('');
3294
+ return;
3295
+ }
3296
+
3419
3297
  console.log('📦 Installing lefthook for git hooks...');
3420
3298
  try {
3421
3299
  // SECURITY: secureExecFileSync with PKG_MANAGER — cross-platform support
@@ -3463,11 +3341,10 @@ function autoSetupToolsInQuickMode() {
3463
3341
  console.log('📦 Initializing Skills...');
3464
3342
  initializeSkills(skillsStatus);
3465
3343
  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
3344
  }
3345
+ // No hint when the optional Skills CLI is absent: setup bundles and renders
3346
+ // Forge's skills itself, and the previously advertised "@forge/skills"
3347
+ // package does not exist on npm (kernel issue 6e554b41).
3471
3348
  }
3472
3349
 
3473
3350
  // Helper: Configure default external services in quick mode - extracted to reduce cognitive complexity
@@ -3522,9 +3399,11 @@ async function quickSetup(selectedAgents, skipExternal) {
3522
3399
  // Auto-setup project tools (Beads, Skills)
3523
3400
  autoSetupToolsInQuickMode();
3524
3401
 
3525
- // Load Claude commands and setup agents (reuse existing helpers)
3526
- const claudeCommands = loadAndSetupClaudeCommands(selectedAgents);
3527
- setupSelectedAgents(selectedAgents, claudeCommands);
3402
+ // Setup Claude first if selected, then remaining agents
3403
+ if (selectedAgents.includes('claude')) {
3404
+ setupAgent('claude');
3405
+ }
3406
+ setupSelectedAgents(selectedAgents);
3528
3407
 
3529
3408
  // Detect Husky and migrate before installing Lefthook hooks
3530
3409
  await handleHuskyMigration();
@@ -3649,7 +3528,6 @@ function displayExistingInstallation(projectStatus) {
3649
3528
  : 'Found partial installation:');
3650
3529
 
3651
3530
  if (projectStatus.hasAgentsMd) console.log(' - AGENTS.md');
3652
- if (projectStatus.hasClaudeCommands) console.log(' - .claude/commands/');
3653
3531
  if (projectStatus.hasEnvLocal) console.log(' - .env.local');
3654
3532
  console.log('');
3655
3533
  }
@@ -3658,7 +3536,6 @@ function displayExistingInstallation(projectStatus) {
3658
3536
  async function promptForOverwriteDecisions(question, projectStatus) {
3659
3537
  const skipFiles = {
3660
3538
  agentsMd: false,
3661
- claudeCommands: false
3662
3539
  };
3663
3540
 
3664
3541
  if (projectStatus.hasAgentsMd) {
@@ -3667,12 +3544,6 @@ async function promptForOverwriteDecisions(question, projectStatus) {
3667
3544
  console.log(overwriteAgents ? ' Will overwrite AGENTS.md' : ' Keeping existing AGENTS.md');
3668
3545
  }
3669
3546
 
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
3547
  if (projectStatus.type !== 'fresh') {
3677
3548
  console.log('');
3678
3549
  }
@@ -3680,41 +3551,14 @@ async function promptForOverwriteDecisions(question, projectStatus) {
3680
3551
  return skipFiles;
3681
3552
  }
3682
3553
 
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
3554
  // Helper: Setup all selected agents - extracted to reduce cognitive complexity
3711
- function setupSelectedAgents(selectedAgents, claudeCommands, skipFiles) {
3555
+ function setupSelectedAgents(selectedAgents, skipFiles) {
3712
3556
  const totalAgents = selectedAgents.length;
3713
3557
  selectedAgents.forEach((agentKey, index) => {
3714
3558
  const agent = AGENTS[agentKey];
3715
3559
  console.log(`\n[${index + 1}/${totalAgents}] Setting up ${agent.name}...`);
3716
3560
  if (agentKey !== 'claude') { // Claude already done above
3717
- setupAgent(agentKey, claudeCommands, skipFiles);
3561
+ setupAgent(agentKey, skipFiles);
3718
3562
  }
3719
3563
  });
3720
3564
 
@@ -3814,11 +3658,13 @@ async function interactiveSetupWithFlags(flags) {
3814
3658
  setupCoreDocs();
3815
3659
  console.log('');
3816
3660
 
3817
- // Load Claude commands if needed (delegated to helper)
3818
- const claudeCommands = loadAndSetupClaudeCommands(selectedAgents, skipFiles);
3661
+ // Setup Claude first if selected (delegated to helper), then remaining agents
3662
+ if (selectedAgents.includes('claude')) {
3663
+ setupAgent('claude', skipFiles);
3664
+ }
3819
3665
 
3820
3666
  // Setup each selected agent with progress indication (delegated to helper)
3821
- setupSelectedAgents(selectedAgents, claudeCommands, skipFiles);
3667
+ setupSelectedAgents(selectedAgents, skipFiles);
3822
3668
 
3823
3669
  // Handle external services step (delegated to helper)
3824
3670
  await handleExternalServicesStep(flags, rl, question, selectedAgents, projectStatus);
@@ -3832,14 +3678,20 @@ async function interactiveSetupWithFlags(flags) {
3832
3678
 
3833
3679
  // Main
3834
3680
  // Helper: Handle --path setup
3835
- function handlePathSetup(targetPath) {
3681
+ function handlePathSetup(targetPath, options = {}) {
3836
3682
  const resolvedPath = path.resolve(targetPath);
3837
3683
 
3838
3684
  // Create directory if it doesn't exist
3839
3685
  if (!fs.existsSync(resolvedPath)) {
3686
+ if (options.create === false) {
3687
+ console.error(`Error: ${resolvedPath} does not exist`);
3688
+ process.exit(1);
3689
+ }
3840
3690
  try {
3841
- fs.mkdirSync(resolvedPath, { recursive: true });
3691
+ fs.mkdirSync(resolvedPath, { recursive: true });
3692
+ if (!options.quiet) {
3842
3693
  console.log(`Created directory: ${resolvedPath}`);
3694
+ }
3843
3695
  } catch (err) {
3844
3696
  console.error(`Error creating directory: ${err.message}`);
3845
3697
  process.exit(1);
@@ -3855,8 +3707,10 @@ function handlePathSetup(targetPath) {
3855
3707
  // Change to target directory
3856
3708
  try {
3857
3709
  process.chdir(resolvedPath);
3858
- console.log(`Working directory: ${resolvedPath}`);
3859
- console.log('');
3710
+ if (!options.quiet) {
3711
+ console.log(`Working directory: ${resolvedPath}`);
3712
+ console.log('');
3713
+ }
3860
3714
  } catch (err) {
3861
3715
  console.error(`Error changing to directory: ${err.message}`);
3862
3716
  process.exit(1);
@@ -3866,6 +3720,15 @@ function handlePathSetup(targetPath) {
3866
3720
  return resolvedPath;
3867
3721
  }
3868
3722
 
3723
+ function resolvePathForDryRun(targetPath) {
3724
+ const resolvedPath = path.resolve(targetPath);
3725
+ if (fs.existsSync(resolvedPath) && !fs.statSync(resolvedPath).isDirectory()) {
3726
+ console.error(`Error: ${resolvedPath} is not a directory`);
3727
+ process.exit(1);
3728
+ }
3729
+ return resolvedPath;
3730
+ }
3731
+
3869
3732
  // Helper: Determine selected agents from flags
3870
3733
  function determineSelectedAgents(flags) {
3871
3734
  if (flags.all) {
@@ -3914,15 +3777,10 @@ function dryRunSetup(agents) {
3914
3777
  addFileAction(dir + '/', 'Create agent directory');
3915
3778
  }
3916
3779
 
3917
- // Claude-specific files
3780
+ // Claude-specific files (skills are listed by the per-skill block below)
3918
3781
  if (agentKey === 'claude') {
3919
- const cmds = getWorkflowCommands();
3920
- for (const cmd of cmds) {
3921
- addFileAction(`.claude/commands/${cmd}.md`, 'Workflow command');
3922
- }
3923
3782
  addFileAction('.claude/rules/workflow.md', 'Workflow rules');
3924
3783
  addFileAction('.claude/scripts/load-env.sh', 'Environment loader script');
3925
- addFileAction('.claude/skills/forge-workflow/SKILL.md', 'Forge workflow skill');
3926
3784
  addFileAction('.mcp.json', 'MCP server configuration');
3927
3785
  addFileAction('CLAUDE.md', 'Claude root config (links to AGENTS.md)');
3928
3786
  }
@@ -3932,15 +3790,7 @@ function dryRunSetup(agents) {
3932
3790
  addFileAction('.cursor/rules/forge-workflow.mdc', 'Cursor workflow rule');
3933
3791
  }
3934
3792
 
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
- }
3793
+
3944
3794
 
3945
3795
  // Agent rules (copied from Claude)
3946
3796
  if (agent.needsConversion) {
@@ -3950,11 +3800,10 @@ function dryRunSetup(agents) {
3950
3800
  }
3951
3801
  }
3952
3802
 
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');
3803
+ // Agent skill — enumerate canonical skills per-agent (codex handled globally elsewhere)
3804
+ if (agentKey !== 'codex' && agent.hasSkill && agent.skillsDir) {
3805
+ for (const skill of listCanonicalSkills(packageDir)) {
3806
+ addFileAction(`${agent.skillsDir}/${skill.name}/SKILL.md`, 'Forge stage skill');
3958
3807
  }
3959
3808
  }
3960
3809
 
@@ -3988,6 +3837,8 @@ async function executeSetup(config) {
3988
3837
  checkPrerequisites();
3989
3838
  console.log('');
3990
3839
 
3840
+ migrateExistingBeadsLocalState();
3841
+
3991
3842
  // Copy AGENTS.md (only if not exists — preserve user customizations; actionLog tracks it)
3992
3843
  const agentsDest = path.join(projectRoot, 'AGENTS.md');
3993
3844
  if (fs.existsSync(agentsDest)) {
@@ -4002,15 +3853,11 @@ async function executeSetup(config) {
4002
3853
  setupCoreDocs();
4003
3854
  console.log('');
4004
3855
 
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);
3856
+ // Setup Claude first if selected, then remaining agents
3857
+ if (agents.includes('claude')) {
3858
+ setupAgent('claude');
3859
+ }
3860
+ setupSelectedAgents(agents);
4014
3861
 
4015
3862
  // Detect Husky and migrate before installing Lefthook hooks
4016
3863
  await handleHuskyMigration();
@@ -4036,36 +3883,12 @@ async function executeSetup(config) {
4036
3883
  // Helper: Scaffold Beads GitHub sync when --sync flag is provided
4037
3884
  async function handleSyncScaffold() {
4038
3885
  console.log('');
4039
- console.log('Scaffolding Beads GitHub sync workflows (--sync)...');
3886
+ console.log('Beads GitHub sync scaffolding is deprecated (--sync).');
4040
3887
  try {
4041
- // Scaffold sync files using the new lib module
4042
3888
  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 Beads version, then template workflows
4051
- const branch = detectDefaultBranch(projectRoot);
4052
- const beadsVersion = detectBeadsVersion();
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
3889
+ console.log(` ${result.message}`);
3890
+ for (const f of result.filesRemoved || []) {
3891
+ console.log(` Removed deprecated sync file: ${f}`);
4069
3892
  }
4070
3893
  } catch (err) {
4071
3894
  console.error(` Error scaffolding GitHub-Beads sync: ${err.message}`);
@@ -4089,6 +3912,11 @@ async function handleSetupCommand(selectedAgents, flags) {
4089
3912
  }
4090
3913
  }
4091
3914
 
3915
+ async function runInteractiveSetupFallback(flags, interactiveSetup = interactiveSetupWithFlags) {
3916
+ migrateExistingBeadsLocalState();
3917
+ return interactiveSetup(flags);
3918
+ }
3919
+
4092
3920
  // Helper: Handle external services configuration
4093
3921
  async function handleExternalServices(skipExternal, selectedAgents) {
4094
3922
  if (skipExternal) {
@@ -4119,6 +3947,17 @@ async function handleExternalServices(skipExternal, selectedAgents) {
4119
3947
  async function main() {
4120
3948
  const command = args[0];
4121
3949
  const flags = parseFlags();
3950
+ const suppressJsonIntrospectionOutput = ['options', 'explain'].includes(command) && args.includes('--json');
3951
+ const suppressCommandJsonOutput = args.includes('--json');
3952
+ const suppressDocsDetectOutput = command === 'docs' && args[1] === 'detect';
3953
+ const profileDryRunArgs = ['--minimal', '--standard', '--full'];
3954
+ const suppressAdoptionDryRunOutput = flags.dryRun && (
3955
+ command === 'init' || (command === 'setup' && profileDryRunArgs.some(arg => args.includes(arg)))
3956
+ );
3957
+ const suppressStructuredOutput = suppressJsonIntrospectionOutput ||
3958
+ suppressCommandJsonOutput ||
3959
+ suppressDocsDetectOutput ||
3960
+ suppressAdoptionDryRunOutput;
4122
3961
 
4123
3962
  // Wire up incremental setup state from parsed flags
4124
3963
  FORCE_MODE = flags.force;
@@ -4128,20 +3967,33 @@ async function main() {
4128
3967
  SYNC_ENABLED = flags.sync;
4129
3968
  actionLog = new SetupActionLog();
4130
3969
 
4131
- if (NON_INTERACTIVE) {
3970
+ // The non-interactive agent-selection notice is DEBUG-ONLY (kernel issue
3971
+ // a9bbd065): it used to print on stderr for EVERY command run by a non-TTY
3972
+ // agent/CI, polluting otherwise-clean command output. It only matters when
3973
+ // setup-style agent selection actually happens, so it now requires --verbose
3974
+ // (or FORGE_DEBUG=1) to appear.
3975
+ if (NON_INTERACTIVE && (VERBOSE_MODE || process.env.FORGE_DEBUG === '1') && !suppressStructuredOutput) {
4132
3976
  const agentFlag = flags.agents;
4133
3977
  if (agentFlag && agentFlag.length > 0) {
4134
3978
  // flags.agents is a comma-separated string (e.g. "claude,cursor")
4135
3979
  const agentDisplay = agentFlag.replace(/,/g, ', ');
4136
- console.log(`Non-interactive mode: using provided agent selection (${agentDisplay})`);
3980
+ console.error(`Non-interactive mode: using provided agent selection (${agentDisplay})`);
4137
3981
  } else {
4138
- console.log('Non-interactive mode: using default agent selection (all)');
3982
+ console.error('Non-interactive mode: using default agent selection (all)');
4139
3983
  }
4140
3984
  }
4141
3985
 
4142
- // Show help
3986
+ // Show help. `--help` after a known registry subcommand (e.g. `forge status
3987
+ // --help`) renders that command's usage; bare `forge --help` keeps the global
3988
+ // banner. `setup` is special-cased OUT: the global banner IS setup's detailed
3989
+ // help, so `forge setup --help` must keep showing it (not the terse registry line).
4143
3990
  if (flags.help) {
4144
- showHelp();
3991
+ const helpRegistry = loadCommands(path.join(__dirname, '..', 'lib', 'commands'));
3992
+ if (command && command !== 'setup' && helpRegistry.commands.has(command)) {
3993
+ printCommandHelp(helpRegistry.commands.get(command));
3994
+ } else {
3995
+ showHelp();
3996
+ }
4145
3997
  return;
4146
3998
  }
4147
3999
 
@@ -4153,8 +4005,11 @@ async function main() {
4153
4005
 
4154
4006
  // Handle --path option: change to target directory
4155
4007
  if (flags.path) {
4008
+ const createTargetPath = !(command === 'docs' && ['verify', 'detect'].includes(args[1]));
4156
4009
  // Update projectRoot after changing directory to maintain state consistency
4157
- projectRoot = handlePathSetup(flags.path);
4010
+ projectRoot = suppressAdoptionDryRunOutput
4011
+ ? resolvePathForDryRun(flags.path)
4012
+ : handlePathSetup(flags.path, { quiet: suppressStructuredOutput, create: createTargetPath });
4158
4013
  }
4159
4014
 
4160
4015
  // Load command registry (auto-discovered commands from lib/commands/)
@@ -4180,24 +4035,51 @@ async function main() {
4180
4035
  // Registry command dispatch — auto-discovered commands take priority
4181
4036
  if (registry.commands.has(command)) {
4182
4037
  try {
4038
+ // Resolve the issue backend (flag > env > config > default) and, for issue
4039
+ // commands, strip the selector tokens (--kernel / --issue-backend) from the
4040
+ // args so they never reach the handler or bd. For the kernel backend this
4041
+ // also assembles the driver + migrated broker (B1/B2).
4042
+ const { commandOpts, args: dispatchArgs } = await resolveCommandOpts(
4043
+ command,
4044
+ args.slice(1),
4045
+ { env: process.env, projectRoot },
4046
+ );
4183
4047
  const result = await executeCommand(
4184
4048
  registry.commands,
4185
4049
  command,
4186
- args.slice(1),
4050
+ dispatchArgs,
4187
4051
  flags,
4188
4052
  projectRoot,
4189
4053
  {
4054
+ // Pass interactivity so issue writes render a human confirmation at a real
4055
+ // terminal but stay machine-parseable JSON when piped/scripted (842a8be7).
4056
+ commandOpts: { ...(commandOpts || {}), isInteractive: Boolean(process.stdout.isTTY) },
4190
4057
  enforceStage: (context) => enforceStageEntry({
4191
4058
  commandName: context.commandName,
4192
4059
  args: context.args,
4193
4060
  flags: context.flags,
4194
4061
  projectRoot: context.projectRoot,
4062
+ // Kernel is the source of truth for workflow stage state: resolve the
4063
+ // active worktree's issue and persist/read currentStage in the kernel
4064
+ // so `ship` is reachable from a pure-CLI plan->dev->validate flow.
4065
+ autoResolveKernel: true,
4195
4066
  }),
4196
4067
  }
4197
4068
  );
4198
4069
  if (result && !result.success) {
4070
+ if (
4071
+ typeof result.output === 'string' &&
4072
+ result.output.length > 0 &&
4073
+ result.output !== result.error
4074
+ ) {
4075
+ process.stdout.write(result.output.endsWith('\n') ? result.output : `${result.output}\n`);
4076
+ }
4199
4077
  console.error(result.error || result.message || 'Command failed');
4200
- process.exit(1);
4078
+ // Issue commands surface the issue-command-contract exit_code as
4079
+ // `result.exitCode` (e.g. not-found=3, validation=6). Honor it so failed
4080
+ // commands no longer collapse to exit 1; other registry commands omit
4081
+ // `exitCode` and keep the historical exit 1.
4082
+ process.exit(Number.isInteger(result.exitCode) ? result.exitCode : 1);
4201
4083
  }
4202
4084
  if (result && typeof result.output === 'string' && result.output.length > 0) {
4203
4085
  process.stdout.write(result.output.endsWith('\n') ? result.output : `${result.output}\n`);
@@ -4217,6 +4099,7 @@ async function main() {
4217
4099
  args: args.slice(1),
4218
4100
  flags,
4219
4101
  projectRoot,
4102
+ autoResolveKernel: true,
4220
4103
  });
4221
4104
  } catch (err) {
4222
4105
  console.error(`Error running '${command}':`, err.message);
@@ -4257,6 +4140,12 @@ async function main() {
4257
4140
  return;
4258
4141
  }
4259
4142
 
4143
+ if (flags.sync && selectedAgents.length === 0) {
4144
+ migrateExistingBeadsLocalState();
4145
+ await handleSyncScaffold();
4146
+ return;
4147
+ }
4148
+
4260
4149
  // Agents specified via flag or --yes default (non-quick mode)
4261
4150
  if (selectedAgents.length > 0) {
4262
4151
  await handleSetupCommand(selectedAgents, flags);
@@ -4264,7 +4153,7 @@ async function main() {
4264
4153
  }
4265
4154
 
4266
4155
  // Interactive setup (skip-external still applies)
4267
- await interactiveSetupWithFlags(flags);
4156
+ await runInteractiveSetupFallback(flags);
4268
4157
  } else if (command === 'recommend') {
4269
4158
  const { handleRecommend, formatRecommendations } = require('../lib/commands/recommend');
4270
4159
  const result = handleRecommend(flags, projectRoot);
@@ -4285,9 +4174,68 @@ async function main() {
4285
4174
  }
4286
4175
  console.log('');
4287
4176
  console.log(' Usage: forge docs <topic>');
4177
+ console.log(' forge docs verify [--path <dir>] [--json] [--baseline <file>] [--write-baseline <file>] [--min-docstring-coverage <percent>]');
4178
+ console.log(' forge docs detect [--path <dir>] [--json]');
4288
4179
  console.log('');
4180
+ console.log(' Note: forge docs verify|detect --path requires an existing directory.');
4181
+ console.log('');
4182
+ } else if (topic === 'verify' || topic === 'detect') {
4183
+ const minCoverageIndex = args.indexOf('--min-docstring-coverage');
4184
+ let minDocstringCoverage = 0;
4185
+ if (minCoverageIndex >= 0) {
4186
+ const rawMinCoverage = args[minCoverageIndex + 1];
4187
+ const parsedMinCoverage = Number(rawMinCoverage);
4188
+ if (!rawMinCoverage || !Number.isFinite(parsedMinCoverage) || parsedMinCoverage < 0 || parsedMinCoverage > 100) {
4189
+ console.error('Error: --min-docstring-coverage must be a number between 0 and 100');
4190
+ process.exitCode = 1;
4191
+ return;
4192
+ }
4193
+ minDocstringCoverage = parsedMinCoverage;
4194
+ }
4195
+ const baselineIndex = args.indexOf('--baseline');
4196
+ const writeBaselineIndex = args.indexOf('--write-baseline');
4197
+ const baselinePath = baselineIndex >= 0 ? args[baselineIndex + 1] : null;
4198
+ const writeBaselinePath = writeBaselineIndex >= 0 ? args[writeBaselineIndex + 1] : null;
4199
+ if (topic === 'detect' && writeBaselineIndex >= 0) {
4200
+ console.error('Error: --write-baseline is only supported with `forge docs verify`');
4201
+ process.exitCode = 1;
4202
+ return;
4203
+ }
4204
+ if (baselineIndex >= 0 && (!baselinePath || baselinePath.startsWith('-'))) {
4205
+ console.error('Error: --baseline requires a file path');
4206
+ process.exitCode = 1;
4207
+ return;
4208
+ }
4209
+ if (writeBaselineIndex >= 0 && (!writeBaselinePath || writeBaselinePath.startsWith('-'))) {
4210
+ console.error('Error: --write-baseline requires a file path');
4211
+ process.exitCode = 1;
4212
+ return;
4213
+ }
4214
+ let result;
4215
+ try {
4216
+ result = validateDocs(projectRoot, { baselinePath, minDocstringCoverage });
4217
+ if (writeBaselinePath) {
4218
+ writeDocsBaseline(projectRoot, writeBaselinePath, result);
4219
+ }
4220
+ } catch (error) {
4221
+ console.error(`Error: ${error.message}`);
4222
+ process.exitCode = 1;
4223
+ return;
4224
+ }
4225
+ if (args.includes('--json') || topic === 'detect') {
4226
+ console.log(JSON.stringify(result, null, 2));
4227
+ } else {
4228
+ console.log(formatDocsValidation(result));
4229
+ }
4230
+ const hasUnbaselinedFailure = result.failures.some((failure) => failure.type !== 'broken-link');
4231
+ if ((!writeBaselinePath && !result.ok) || (writeBaselinePath && hasUnbaselinedFailure)) {
4232
+ process.exitCode = 1;
4233
+ }
4289
4234
  } else {
4290
- const result = getTopicContent(topic, packageDir);
4235
+ // `forge docs` reads packaged docs; route through the asset root so any
4236
+ // embedded topic resolves in a compiled binary too. (Full docs/ embedding
4237
+ // for arbitrary topics is deferred to step 3 — see the step-2 plan note.)
4238
+ const result = getTopicContent(topic, getPackageRoot(packageDir));
4291
4239
  if (result.error) {
4292
4240
  console.error(` Error: ${result.error}`);
4293
4241
  process.exitCode = 1;
@@ -4389,9 +4337,34 @@ async function main() {
4389
4337
  console.log(' To set up in your project:');
4390
4338
  console.log(` ${runCmd} forge setup`);
4391
4339
  console.log('');
4392
- } else {
4393
- // Explicit invocation with no command: run minimal install
4340
+ } else if (command === undefined && fs.existsSync(path.join(projectRoot, 'AGENTS.md'))) {
4341
+ // Bare `forge` in an INITIALIZED project orients instead of mutating: render the
4342
+ // same read-only one-glance view as `forge status`. Newcomers typing `forge` to
4343
+ // get their bearings should never trigger the minimal install.
4344
+ try {
4345
+ const { commandOpts, args: dispatchArgs } = await resolveCommandOpts(
4346
+ 'status',
4347
+ [],
4348
+ { env: process.env, projectRoot },
4349
+ );
4350
+ const result = await executeCommand(registry.commands, 'status', dispatchArgs, flags, projectRoot, { commandOpts });
4351
+ if (result && typeof result.output === 'string' && result.output.length > 0) {
4352
+ process.stdout.write(result.output.endsWith('\n') ? result.output : `${result.output}\n`);
4353
+ }
4354
+ } catch (err) {
4355
+ console.error(`Error running 'status':`, err.message);
4356
+ process.exit(1);
4357
+ }
4358
+ } else if (command === undefined) {
4359
+ // Explicit invocation with no command in an UNINITIALIZED project: run minimal install.
4394
4360
  minimalInstall();
4361
+ } else {
4362
+ // Defined but unrecognized command → fail honestly instead of printing the
4363
+ // installer banner with exit 0 (kernel b3bae4f3). A typo like `forge bogusxyz`
4364
+ // must be detectable by scripts (non-zero exit) and humans (a real error).
4365
+ console.error(`Error: Unknown command '${command}'`);
4366
+ console.error("Run 'forge --help' to see the available commands.");
4367
+ process.exit(1);
4395
4368
  }
4396
4369
  }
4397
4370
 
@@ -4813,4 +4786,8 @@ if (require.main === module) {
4813
4786
  })();
4814
4787
  }
4815
4788
 
4816
- module.exports = { getWorkflowCommands, ensureDirWithNote };
4789
+ module.exports = {
4790
+ ensureDirWithNote,
4791
+ validateDirectoryPathInput,
4792
+ validateUserInput,
4793
+ };