forge-workflow 0.0.10 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +461 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +17 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +0 -1
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +838 -972
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +382 -11
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. package/scripts/sync-commands.js +0 -600
@@ -13,30 +13,51 @@ const fs = require('node:fs');
13
13
  const os = require('node:os');
14
14
  const path = require('node:path');
15
15
  const readline = require('node:readline');
16
- const { execSync, execFileSync } = require('node:child_process');
16
+ const { execSync } = require('node:child_process');
17
17
 
18
- // Compute packageDir relative to this file (lib/commands/setup.js -> project root)
18
+ // Compute packageDir relative to this file (lib/commands/setup.js -> project root).
19
+ // packageDir stays a runtime value for asset copying; requires use static
20
+ // relative paths so `bun build --compile` can bundle the module graph.
19
21
  const packageDir = path.resolve(__dirname, '..', '..');
20
- const packageJson = require(path.join(packageDir, 'package.json'));
22
+ const packageJson = require('../../package.json');
21
23
  const VERSION = packageJson.version;
22
24
 
25
+ // Dual-channel asset root. Under npm/npx this returns `packageDir` unchanged;
26
+ // inside a `bun build --compile` binary it lazily extracts the embedded runtime
27
+ // assets (skills/rules/docs/hooks/scripts) to a temp dir and returns that. Every
28
+ // packaged-ASSET read below goes through `getPackageRoot(packageDir)`; the
29
+ // module-`require` paths above keep `packageDir` (bundler handles those).
30
+ const { getPackageRoot } = require('../package-root');
31
+
23
32
  // Load PluginManager for discoverable agent architecture
24
33
  const PluginManager = require('../plugin-manager');
25
- const { scaffoldGithubBeadsSync } = require('../setup');
34
+ const { populateAgentSkills, listCanonicalSkills, listFilesRecursive } = require('../skills-sync');
35
+ const { renderMcpConfig } = require('../mcp-config-renderer');
36
+ const { renderClaudePermissions, renderCursorIgnore } = require('../safety-config-renderer');
37
+ const { renderHookConfig } = require('../hook-renderer');
26
38
  const { copyEssentialDocs } = require('../docs-copy');
39
+
40
+ // Baseline MCP server Forge ships. Uses the generic descriptor contract consumed
41
+ // by lib/mcp-config-renderer.js (envRefs are '${VAR}' references, never secrets).
42
+ const CONTEXT7_MCP_DESCRIPTOR = {
43
+ name: 'context7',
44
+ transport: 'stdio',
45
+ command: 'npx',
46
+ args: ['-y', '@upstash/context7-mcp@latest'],
47
+ envRefs: {},
48
+ };
27
49
  const { secureExecFileSync } = require('../shell-utils');
28
50
  const { askYesNo: _askYesNoBase } = require('../ui-utils');
29
51
 
30
- // Load enhanced onboarding modules
31
- const contextMerge = require(path.join(packageDir, 'lib', 'context-merge'));
32
- const projectDiscovery = require(path.join(packageDir, 'lib', 'project-discovery'));
52
+ // Load enhanced onboarding modules (static relative requires — bundleable)
53
+ const contextMerge = require('../context-merge');
54
+ const projectDiscovery = require('../project-discovery');
33
55
 
34
56
  // Load lib modules for symlink, beads, and PAT setup
35
- const { createSymlinkOrCopy: libCreateSymlinkOrCopy } = require(path.join(packageDir, 'lib', 'symlink-utils'));
36
- const beadsSetupLib = require(path.join(packageDir, 'lib', 'beads-setup'));
37
- const { beadsHealthCheck } = require(path.join(packageDir, 'lib', 'beads-health-check'));
38
- const { setupPAT } = require(path.join(packageDir, 'lib', 'pat-setup'));
39
- const { DEFAULT_BEADS_VERSION, detectDefaultBranch, templateWorkflows, scaffoldBeadsSync } = require(path.join(packageDir, 'lib', 'beads-sync-scaffold'));
57
+ const { createSymlinkOrCopy: libCreateSymlinkOrCopy } = require('../symlink-utils');
58
+ const { scaffoldBeadsSync } = require('../beads-sync-scaffold');
59
+ const { resolveSyncBackend } = require('../sync-backend');
60
+ const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
40
61
 
41
62
  // Load incremental setup modules
42
63
  const { detectEnvironment } = require('../detect-agent');
@@ -46,18 +67,25 @@ const { ActionCollector } = require('../setup-utils');
46
67
  const { renderSetupSummary } = require('../setup-summary-renderer');
47
68
  const { smartMergeAgentsMd } = require('../smart-merge');
48
69
  const { checkLefthookStatus } = require('../lefthook-check');
70
+ const {
71
+ FORGE_USER_LEFTHOOK_YML,
72
+ forgeShouldWriteLefthookConfig,
73
+ installNativeGitHooks,
74
+ verifyHooksActive,
75
+ resolveGitHooksDir,
76
+ } = require('../lefthook-wiring');
49
77
  const { resolveShellRuntime } = require('../runtime-health');
50
78
  const {
51
79
  buildCodexSkillInstallPlan,
52
80
  formatCodexSkillsInstallDir,
53
81
  listCodexSkillEntries,
82
+ populateCodexRepoSkills,
83
+ CODEX_REPO_SKILLS_DIR,
54
84
  } = require('../codex-skills');
55
85
  const {
56
- generateCopilotConfig,
57
86
  generateCursorConfig,
58
- generateKiloConfig,
59
- generateOpenCodeConfig,
60
87
  } = require('../agents-config');
88
+ const initCommand = require('./init');
61
89
  const fileUtils = require('../file-utils');
62
90
  const detectionUtils = require('../detection-utils');
63
91
  const { detectHusky, migrateHusky } = require('../husky-migration');
@@ -76,6 +104,11 @@ let actionLog = new SetupActionLog();
76
104
  let PKG_MANAGER = 'npm';
77
105
  let SETUP_NOTES = [];
78
106
  let CODEX_SETUP_REPORT = null;
107
+ // Tracks whether the last ensureKernelIssueStore() run provisioned the store.
108
+ // The setup summary reads this so a provisioning failure isn't masked by a
109
+ // hardcoded "✓ Kernel issue store" line. Defaults true: when the ensure step
110
+ // never ran, the kernel still auto-provisions on first use.
111
+ let KERNEL_STORE_READY = true;
79
112
 
80
113
  /**
81
114
  * Load agent definitions from plugin architecture
@@ -96,7 +129,8 @@ function loadAgentsFromPlugins() {
96
129
  supportStatus: plugin.support?.status || 'supported',
97
130
  needsConversion: plugin.setup?.needsConversion || false,
98
131
  copyCommands: plugin.setup?.copyCommands || false,
99
- promptFormat: plugin.setup?.promptFormat || false
132
+ promptFormat: plugin.setup?.promptFormat || false,
133
+ skillsDir: plugin.directories?.skills || null
100
134
  };
101
135
  });
102
136
  return agents;
@@ -138,29 +172,6 @@ function detectPackageManager() {
138
172
  return 'npm';
139
173
  }
140
174
 
141
- /**
142
- * Reads workflow command names from commands/*.md in the package directory.
143
- * Falls back to .claude/commands/ if commands/ does not exist (backwards compat).
144
- * @returns {string[]} Command names (filenames without .md extension)
145
- */
146
- function getWorkflowCommands() {
147
- const canonicalDir = path.join(packageDir, 'commands');
148
- const commandsDir = fs.existsSync(canonicalDir)
149
- ? canonicalDir
150
- : path.join(packageDir, '.claude', 'commands');
151
- try {
152
- return fs.readdirSync(commandsDir)
153
- .filter(f => f.endsWith('.md'))
154
- .map(f => f.replace(/\.md$/, ''));
155
- } catch (err) {
156
- if (err.code === 'ENOENT') {
157
- console.warn(`Warning: commands directory not found at ${commandsDir}`);
158
- } else {
159
- console.warn(`Warning: failed to read commands — ${err.code}: ${err.message}`);
160
- }
161
- return [];
162
- }
163
- }
164
175
 
165
176
  const WORKFLOW_RUNTIME_ASSETS = Object.freeze([
166
177
  'scripts/beads-context.sh',
@@ -184,7 +195,7 @@ const WORKFLOW_RUNTIME_ASSETS = Object.freeze([
184
195
  'scripts/forge-team/lib/sync-github.sh',
185
196
  'scripts/forge-team/lib/verify.sh',
186
197
  'scripts/forge-team/lib/workload.sh',
187
- '.claude/scripts/greptile-resolve.sh'
198
+ '.claude/scripts/review-resolve.sh'
188
199
  ]);
189
200
 
190
201
  /**
@@ -205,112 +216,11 @@ function validateAgents(agentList) {
205
216
  return valid;
206
217
  }
207
218
 
208
- function parseDoltRemoteNames(remoteListOutput) {
209
- const output = String(remoteListOutput || '').trim();
210
- if (!output || /^No remotes configured\.?$/i.test(output)) {
211
- return [];
212
- }
213
-
214
- return output
215
- .split(/\r?\n/)
216
- .map(line => line.trim())
217
- .filter(Boolean)
218
- .map(line => line.split(/\s+/)[0])
219
- .filter(Boolean);
220
- }
221
-
222
- function readBeadsSyncRemoteConfig(projectDir = projectRoot) {
223
- const jsonConfigPath = path.join(projectDir, '.beads', 'config.json');
224
- if (fs.existsSync(jsonConfigPath)) {
225
- try {
226
- const parsed = JSON.parse(fs.readFileSync(jsonConfigPath, 'utf8'));
227
- if (typeof parsed.sync_remote === 'string' && parsed.sync_remote.trim()) {
228
- return parsed.sync_remote.trim();
229
- }
230
- } catch (_error) {
231
- // Ignore malformed config and fall back to other sources.
232
- }
233
- }
234
-
235
- const yamlConfigPath = path.join(projectDir, '.beads', 'config.yaml');
236
- if (fs.existsSync(yamlConfigPath)) {
237
- const configuredRemote = parseBeadsYamlSyncRemote(fs.readFileSync(yamlConfigPath, 'utf8'));
238
- if (configuredRemote) {
239
- return configuredRemote;
240
- }
241
- }
242
-
243
- return '';
244
- }
245
-
246
- function parseBeadsYamlSyncRemote(content) {
247
- for (const rawLine of String(content || '').split(/\r?\n/)) {
248
- const trimmedLine = rawLine.trimStart();
249
- if (!trimmedLine.startsWith('sync-remote:')) {
250
- continue;
251
- }
252
-
253
- const rawValue = trimmedLine.slice('sync-remote:'.length).trim();
254
- if (!rawValue) {
255
- return '';
256
- }
257
-
258
- const quote = rawValue[0];
259
- if ((quote === '"' || quote === '\'') && rawValue.length > 1) {
260
- const closingQuoteIndex = rawValue.indexOf(quote, 1);
261
- if (closingQuoteIndex > 1) {
262
- return rawValue.slice(1, closingQuoteIndex).trim();
263
- }
264
- }
265
-
266
- const commentIndex = rawValue.indexOf('#');
267
- const unquotedValue = commentIndex === -1 ? rawValue : rawValue.slice(0, commentIndex);
268
- return unquotedValue.trim();
269
- }
270
-
271
- return '';
272
- }
273
-
274
- function resolveExpectedBeadsRemote(options = {}) {
275
- const projectDir = options.projectDir || projectRoot;
276
- const env = options.env || process.env;
277
- const configuredRemote = readBeadsSyncRemoteConfig(projectDir);
278
- if (configuredRemote) {
279
- return configuredRemote;
280
- }
281
-
282
- if (typeof env.BD_SYNC_REMOTE === 'string' && env.BD_SYNC_REMOTE.trim()) {
283
- return env.BD_SYNC_REMOTE.trim();
284
- }
285
-
286
- const gitRemoteProbe = options.gitRemoteProbe
287
- || ((remoteName) => safeExec(`git -C "${projectDir}" remote get-url ${remoteName}`));
288
- if (gitRemoteProbe('upstream')) {
289
- return 'upstream';
290
- }
291
-
292
- return 'origin';
293
- }
294
-
295
- function hasBeadsDoltRemote(commandRunner, remoteName = 'origin') {
296
- const output = commandRunner('bd dolt remote list');
297
- if (!output) {
298
- return null;
299
- }
300
-
301
- return parseDoltRemoteNames(output).includes(remoteName);
302
- }
303
-
304
219
  // Prerequisite check function
305
220
  function checkPrerequisites(options = {}) {
306
221
  const requireGithubCli = options.requireGithubCli !== false;
307
222
  const requireBeadsCli = options.requireBeadsCli === true;
308
223
  const requireJq = options.requireJq === true;
309
- const expectedBeadsRemote = options.expectedBeadsRemote || resolveExpectedBeadsRemote({
310
- env: options.env,
311
- gitRemoteProbe: options.gitRemoteProbe,
312
- projectDir: options.projectDir,
313
- });
314
224
  const commandRunner = options.commandRunner || safeExec;
315
225
  const errors = [];
316
226
  const warnings = [];
@@ -346,25 +256,15 @@ function checkPrerequisites(options = {}) {
346
256
  }
347
257
  }
348
258
 
349
- const bdVersion = commandRunner('bd --version');
350
- if (bdVersion) {
351
- console.log(` ✓ ${bdVersion.split('\n')[0]}`);
352
- if (requireBeadsCli) {
353
- const hasRemote = hasBeadsDoltRemote(commandRunner, expectedBeadsRemote);
354
- if (hasRemote === false) {
355
- warnings.push(
356
- `Beads Dolt remote '${expectedBeadsRemote}' is not configured. ` +
357
- `Sync will remain local until you run: bd dolt remote add ${expectedBeadsRemote} <url>`
358
- );
359
- } else if (hasRemote === null) {
360
- warnings.push(
361
- `Unable to inspect Beads Dolt remotes. ` +
362
- `Sync may remain local until '${expectedBeadsRemote}' is configured and bd dolt remote list succeeds.`
363
- );
364
- }
259
+ if (requireBeadsCli) {
260
+ // Issue tracking runs on the local Forge Kernel store, which auto-provisions
261
+ // on first use — there is no external CLI to install. Surface only whether
262
+ // team sync is wired up yet.
263
+ if (resolveSyncBackend({ projectRoot, env: options.env }) === 'local-noop') {
264
+ console.log(' ✓ Kernel issue store (local-noop sync: single-machine until a sync server is configured)');
265
+ } else {
266
+ console.log(' ✓ Kernel issue store');
365
267
  }
366
- } else if (requireBeadsCli) {
367
- errors.push('bd (Beads CLI) - Install from https://github.com/steveyegge/beads');
368
268
  }
369
269
 
370
270
  // Check Node.js version
@@ -379,7 +279,11 @@ function checkPrerequisites(options = {}) {
379
279
  if (jqVersion) {
380
280
  console.log(` ✓ ${jqVersion.split('\n')[0]}`);
381
281
  } else if (requireJq) {
382
- errors.push('jq - Install from https://jqlang.org/download/');
282
+ // jq is optional at setup time. Some workflow helper scripts shell out to jq,
283
+ // but they degrade or defer when it is absent — a clean box (esp. Windows)
284
+ // must not abort setup just because jq is not installed yet. Surface a
285
+ // warning instead of a fatal error (kernel issue 01468e44).
286
+ warnings.push('jq not found - some workflow helper scripts will be skipped until you install it (https://jqlang.org/download/)');
383
287
  }
384
288
 
385
289
  // Detect package manager
@@ -405,60 +309,121 @@ function checkPrerequisites(options = {}) {
405
309
  console.log('');
406
310
  console.log(` Package manager: ${PKG_MANAGER}`);
407
311
 
408
- return { errors, warnings };
312
+ // Return a structured result so embedding agents can inspect prerequisite
313
+ // state instead of only observing a process exit. Genuinely-fatal prereqs
314
+ // (git, node version, required gh) still hard-exit above; soft prereqs like
315
+ // jq surface via `warnings` while `ok` stays true.
316
+ return { errors, warnings, ok: errors.length === 0 };
409
317
  }
410
318
 
411
- function requiresGithubCliForSetup(selectedAgents, options = {}) {
412
- return needsWorkflowRuntimeAssets(selectedAgents) || options.syncEnabled === true;
319
+ function requiresGithubCliForSetup(selectedAgents) {
320
+ return needsWorkflowRuntimeAssets(selectedAgents);
413
321
  }
414
322
 
415
- // Universal SKILL.md content
416
- const SKILL_CONTENT = `---
417
- name: forge-workflow
418
- description: 7-stage TDD-first workflow for feature development. Use when building features, fixing bugs, or shipping PRs.
419
- category: Development Workflow
420
- tags: [tdd, workflow, pr, git, testing]
421
- tools: [Bash, Read, Write, Edit, Grep, Glob]
422
- ---
423
-
424
- # Forge Workflow Skill
425
-
426
- A TDD-first workflow for AI coding agents. Ship features with confidence.
427
-
428
- ## When to Use
429
-
430
- Automatically invoke this skill when the user wants to:
431
- - Build a new feature
432
- - Fix a bug
433
- - Create a pull request
434
- - Run the development workflow
435
-
436
- ## 7 Stages
437
-
438
- | Stage | Command | Description |
439
- |-------|---------|-------------|
440
- | utility | \`/status\` | Check current context, active work, recent completions |
441
- | 1 | \`/plan\` | Design intent -> research -> branch + worktree + task list |
442
- | 2 | \`/dev\` | TDD development (implementer -> spec review -> quality review) |
443
- | 3 | \`/validate\` | Type check, lint, security, tests - all fresh output |
444
- | 4 | \`/ship\` | Push branch and create PR with full documentation |
445
- | 5 | \`/review\` | Address ALL PR feedback (GitHub Actions, Greptile, SonarCloud) |
446
- | 6 | \`/premerge\` | Update docs, hand off PR to user |
447
- | 7 | \`/verify\` | Post-merge health check (CI on main, close Beads) |
323
+ /**
324
+ * After agent setup, `.forge/config.yaml` may not exist yet — only the
325
+ * --minimal/--standard/--full routes run `forge init`. Point the user at
326
+ * `forge init` so first-run always ends with a clear next step that creates
327
+ * the workflow config (gates + change classification). No-op when the config
328
+ * already exists (kernel issue 5bdc91d3).
329
+ */
330
+ function printForgeInitNextStep() {
331
+ const configPath = path.join(projectRoot, '.forge', 'config.yaml');
332
+ if (fs.existsSync(configPath)) {
333
+ return;
334
+ }
335
+ console.log('');
336
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
337
+ console.log('▶ NEXT: run `forge init` to configure your workflow');
338
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
339
+ console.log('');
340
+ console.log(' forge init # creates .forge/config.yaml');
341
+ console.log('');
342
+ console.log(' Sets up workflow gates and change classification.');
343
+ console.log(' Safe to re-run (existing config is preserved).');
344
+ console.log('');
345
+ }
448
346
 
449
- ## Workflow Flow
347
+ /**
348
+ * One-step onboarding (kernel issue ac0b38c7): a fresh `forge setup` used to end
349
+ * by telling the user to ALSO run `forge init`. Instead, run init's real handler
350
+ * here (standard profile, non-interactive) so setup finishes with a working
351
+ * `.forge/config.yaml` in one command. No-op when config already exists; on any
352
+ * init failure fall back to the printed `forge init` guidance so setup never
353
+ * hard-fails. `forge init` stays fully usable standalone. Inject `runInit` for
354
+ * tests.
355
+ */
356
+ async function finalizeWorkflowConfig(options = {}) {
357
+ const configPath = path.join(projectRoot, '.forge', 'config.yaml');
358
+ if (fs.existsSync(configPath)) {
359
+ return;
360
+ }
361
+ const skipSideEffects = options.hooksAlreadyInstalled === true;
362
+ const runInit = options.runInit || defaultRunInit;
363
+ try {
364
+ const result = await runInit(projectRoot, skipSideEffects);
365
+ if (!result || result.success === false) {
366
+ printForgeInitNextStep();
367
+ return;
368
+ }
369
+ console.log('');
370
+ console.log('✓ Workflow configured: .forge/config.yaml (standard profile)');
371
+ console.log(' Change it anytime: forge init --profile <minimal|standard|full> --force');
372
+ console.log('');
373
+ } catch (err) {
374
+ console.warn(`Warning: automatic workflow init skipped: ${err.message}`);
375
+ printForgeInitNextStep();
376
+ }
377
+ }
450
378
 
451
- \`\`\`
452
- /status -> /plan -> /dev -> /validate -> /ship -> /review -> /premerge -> /verify
453
- \`\`\`
379
+ /**
380
+ * Default init runner for finalizeWorkflowConfig. When `skipSideEffects` is set
381
+ * (setup paths that already installed git hooks and migrated Beads), pass no-op
382
+ * deps so init only writes `.forge/config.yaml` instead of re-doing that work —
383
+ * avoids duplicate side effects and warning noise (CodeRabbit on PR #368).
384
+ */
385
+ function defaultRunInit(root, skipSideEffects) {
386
+ const deps = skipSideEffects
387
+ ? { installHooks: () => {}, autoMigrateBeads: () => {} }
388
+ : {};
389
+ return initCommand.handler(['--yes'], {}, root, deps);
390
+ }
454
391
 
455
- ## Core Principles
392
+ /**
393
+ * Guard against silent data loss when a non-interactive setup path overwrites
394
+ * an existing AGENTS.md that predates Forge's USER/FORGE merge markers. Old
395
+ * hand-edited files have no markers, so a plain copy would clobber them with no
396
+ * way to recover. Before that happens, snapshot the file to AGENTS.md.bak and
397
+ * warn (kernel issue a5399f3d). Returns true when a backup was written.
398
+ */
399
+ function backupMarkerlessAgentsMd() {
400
+ const agentsPath = path.join(projectRoot, 'AGENTS.md');
401
+ if (!fs.existsSync(agentsPath)) {
402
+ return false;
403
+ }
404
+ const existingContent = fs.readFileSync(agentsPath, 'utf8');
405
+ const hasUserMarkers = existingContent.includes('<!-- USER:START');
406
+ const hasForgeMarkers = existingContent.includes('<!-- FORGE:START');
407
+ if (hasUserMarkers || hasForgeMarkers) {
408
+ return false;
409
+ }
410
+ // Never clobber an earlier snapshot: keep the original AGENTS.md.bak stable
411
+ // and fall back to numbered AGENTS.md.bak.1, .2, ... so a repeated markerless
412
+ // overwrite (e.g. re-running --quick after markers were stripped) preserves
413
+ // every prior backup instead of losing it (CodeRabbit review on PR #300).
414
+ let backupPath = path.join(projectRoot, 'AGENTS.md.bak');
415
+ if (fs.existsSync(backupPath)) {
416
+ let suffix = 1;
417
+ while (fs.existsSync(path.join(projectRoot, `AGENTS.md.bak.${suffix}`))) {
418
+ suffix += 1;
419
+ }
420
+ backupPath = path.join(projectRoot, `AGENTS.md.bak.${suffix}`);
421
+ }
422
+ fs.writeFileSync(backupPath, existingContent, 'utf8');
423
+ console.log(` ⚠ Existing AGENTS.md has no Forge markers - backed up to ${path.basename(backupPath)} before overwrite`);
424
+ return true;
425
+ }
456
426
 
457
- - **TDD-First**: Write tests BEFORE implementation (RED-GREEN-REFACTOR)
458
- - **Research-First**: Understand before building, document decisions
459
- - **Security Built-In**: OWASP Top 10 analysis for every feature
460
- - **Documentation Progressive**: Update at each stage, verify at end
461
- `;
462
427
 
463
428
  // Helper functions
464
429
 
@@ -472,10 +437,6 @@ function ensureDir(dir) {
472
437
 
473
438
 
474
439
 
475
- function writeFile(filePath, content) {
476
- return fileUtils.writeFile(filePath, content, projectRoot);
477
- }
478
-
479
440
  function writeManagedAbsoluteFile(absolutePath, content, displayPath) {
480
441
  try {
481
442
  if (!FORCE_MODE && fileMatchesContent(absolutePath, content)) {
@@ -500,13 +461,10 @@ function writeManagedAbsoluteFile(absolutePath, content, displayPath) {
500
461
 
501
462
 
502
463
 
503
- function readFile(filePath) {
504
- return fileUtils.readFile(filePath);
505
- }
506
-
507
464
  function resetSetupNotes() {
508
465
  SETUP_NOTES = [];
509
466
  CODEX_SETUP_REPORT = null;
467
+ KERNEL_STORE_READY = true;
510
468
  }
511
469
 
512
470
  function addSetupNote(message) {
@@ -579,7 +537,7 @@ function copyFile(src, dest) { // NOSONAR — Extracted as-is from bin/forge.js;
579
537
  function needsWorkflowRuntimeAssets(selectedAgents) {
580
538
  return selectedAgents.some((agentKey) => {
581
539
  const agent = AGENTS[agentKey];
582
- return Boolean(agent && (agent.hasCommands || agent.needsConversion || agent.copyCommands || agent.promptFormat));
540
+ return Boolean(agent && (agent.hasCommands || agent.hasSkill || agent.needsConversion || agent.copyCommands || agent.promptFormat));
583
541
  });
584
542
  }
585
543
 
@@ -618,7 +576,7 @@ function scaffoldWorkflowRuntimeAssets(selectedAgents) {
618
576
  }
619
577
 
620
578
  for (const relativePath of WORKFLOW_RUNTIME_ASSETS) {
621
- const sourcePath = path.join(packageDir, relativePath);
579
+ const sourcePath = path.join(getPackageRoot(packageDir), relativePath);
622
580
  copyFile(sourcePath, relativePath);
623
581
  }
624
582
 
@@ -722,13 +680,40 @@ function resolveWorkflowShellPolicy(selectedAgents, options = {}) {
722
680
  };
723
681
  }
724
682
 
683
+ /**
684
+ * Print install guidance when Git Bash is absent on Windows. Kept separate so
685
+ * the graceful-degrade branch in ensureWorkflowShellPolicy stays trivial.
686
+ */
687
+ function printGitBashGuidance(shellPolicy) {
688
+ console.log('');
689
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
690
+ console.log('⚠ Git Bash not found — continuing in reduced-capability mode');
691
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
692
+ console.log('');
693
+ console.log(` ${shellPolicy.message || 'Git Bash is required on Windows for helper-backed flows.'}`);
694
+ console.log('');
695
+ console.log(' Core setup will finish. The shell scripts behind some /plan, /ship,');
696
+ console.log(' and /review helper steps are UNAVAILABLE until Git Bash is installed.');
697
+ console.log(' Agent slash-skills and typeable `forge` CLI verbs keep working.');
698
+ console.log('');
699
+ console.log(' Install Git Bash: https://git-scm.com/download/win');
700
+ console.log(' Then re-run `forge setup` to re-enable helper-backed flows.');
701
+ console.log('');
702
+ }
703
+
725
704
  function ensureWorkflowShellPolicy(selectedAgents, options = {}) {
726
705
  const shellPolicy = resolveWorkflowShellPolicy(selectedAgents, options);
727
706
 
728
- if (shellPolicy.required && shellPolicy.platform === 'win32' && !shellPolicy.available) {
729
- throw new Error(
730
- shellPolicy.message || 'Git Bash is required on Windows for Forge workflow helper scripts.'
731
- );
707
+ const gitBashMissing = shellPolicy.required
708
+ && shellPolicy.platform === 'win32'
709
+ && !shellPolicy.available;
710
+
711
+ if (gitBashMissing) {
712
+ // Graceful degrade (kernel issue 048c1e6d): a Windows newcomer without Git
713
+ // Bash must not be dead on arrival. Print install guidance and continue in a
714
+ // reduced-capability mode instead of hard-throwing and aborting all of setup.
715
+ printGitBashGuidance(shellPolicy);
716
+ return { ...shellPolicy, available: false, degraded: true };
732
717
  }
733
718
 
734
719
  return shellPolicy;
@@ -756,16 +741,11 @@ function createSymlinkOrCopy(source, target, options = {}) {
756
741
  }
757
742
 
758
743
  function shouldLinkAgentsMd(agent) {
759
- if (!agent?.linkFile) return false;
760
- return !['copilot', 'opencode'].includes(agent.customSetup);
744
+ return Boolean(agent?.linkFile);
761
745
  }
762
746
 
763
747
 
764
748
 
765
- function stripFrontmatter(content) {
766
- return fileUtils.stripFrontmatter(content);
767
- }
768
-
769
749
  // Read existing .env.local
770
750
 
771
751
 
@@ -813,17 +793,17 @@ async function detectProjectStatus() {
813
793
  type: 'fresh', // 'fresh', 'upgrade', or 'partial'
814
794
  hasAgentsMd: fs.existsSync(path.join(projectRoot, 'AGENTS.md')),
815
795
  hasClaudeMd: fs.existsSync(path.join(projectRoot, 'CLAUDE.md')),
816
- hasClaudeCommands: fs.existsSync(path.join(projectRoot, '.claude/commands')),
817
796
  hasEnvLocal: fs.existsSync(path.join(projectRoot, '.env.local')),
818
797
  existingEnvVars: {},
819
798
  agentsMdSize: 0,
820
799
  claudeMdSize: 0,
821
800
  agentsMdLines: 0,
822
801
  claudeMdLines: 0,
823
- // Project tools status
824
- hasBeads: isBeadsInitialized(),
802
+ // Project tools status — the Kernel issue store is always present (it
803
+ // auto-provisions on first use), so issue tracking needs no install probe.
804
+ hasBeads: true,
825
805
  hasSkills: isSkillsInitialized(),
826
- beadsInstallType: checkForBeads(),
806
+ beadsInstallType: 'kernel',
827
807
  skillsInstallType: checkForSkills(),
828
808
  // Enhanced: Auto-detected project context
829
809
  autoDetected: null
@@ -847,9 +827,9 @@ async function detectProjectStatus() {
847
827
  }
848
828
 
849
829
  // Determine installation type
850
- if (status.hasAgentsMd && status.hasClaudeCommands) {
830
+ if (status.hasAgentsMd) {
851
831
  status.type = 'upgrade'; // Full forge installation exists
852
- } else if (status.hasClaudeCommands || status.hasEnvLocal) {
832
+ } else if (status.hasEnvLocal) {
853
833
  status.type = 'partial'; // Agent-specific files exist (not just base files from postinstall)
854
834
  }
855
835
  // else: 'fresh' - new installation (or just postinstall baseline with AGENTS.md)
@@ -1544,24 +1524,13 @@ function displayMcpStatus(selectedAgents) {
1544
1524
  console.log('Provides up-to-date library docs for AI coding agents.');
1545
1525
  console.log('');
1546
1526
 
1547
- // Show what was/will be auto-installed
1527
+ // Show what was/will be auto-installed. Both Claude and Cursor read a
1528
+ // project-local MCP config, so Forge auto-wires both (no manual step needed).
1548
1529
  if (selectedAgents.includes('claude')) {
1549
1530
  console.log(' ✓ Auto-installed for Claude Code (.mcp.json)');
1550
1531
  }
1551
- // Show manual setup instructions for GUI-based agents
1552
- const manualMcpMap = {
1553
- cursor: 'Cursor: Configure via Cursor Settings > MCP',
1554
- cline: 'Cline: Install via MCP Marketplace',
1555
- };
1556
- const needsManualMcp = Object.entries(manualMcpMap)
1557
- .filter(([key]) => selectedAgents.includes(key))
1558
- .map(([, msg]) => msg);
1559
-
1560
- if (needsManualMcp.length > 0) {
1561
- needsManualMcp.forEach(msg => console.log(` ! ${msg}`));
1562
- console.log('');
1563
- console.log(' Package: @upstash/context7-mcp@latest');
1564
- console.log(' Docs: https://github.com/upstash/context7-mcp');
1532
+ if (selectedAgents.includes('cursor')) {
1533
+ console.log(' Auto-installed for Cursor (.cursor/mcp.json)');
1565
1534
  }
1566
1535
  }
1567
1536
 
@@ -1640,39 +1609,6 @@ async function configureExternalServices(rl, question, selectedAgents = [], proj
1640
1609
  const { added, preserved } = writeEnvTokens(tokens, true);
1641
1610
  displayEnvTokenResults(added, preserved);
1642
1611
 
1643
- // GitHub-Beads issue sync setup
1644
- console.log('');
1645
- const enableSync = await askYesNo(question, 'Enable GitHub ↔ Beads issue sync?', true);
1646
- if (enableSync) {
1647
- try {
1648
- const result = await scaffoldGithubBeadsSync(projectRoot, packageDir);
1649
- for (const f of result.created) {
1650
- console.log(` Created: ${f}`);
1651
- }
1652
- for (const f of result.skipped) {
1653
- console.log(` Skipped: ${f} (already exists)`);
1654
- }
1655
-
1656
- // PAT setup guidance for Beads sync (non-fatal)
1657
- // Skip if --sync flag is set — handleSyncScaffold will handle PAT setup
1658
- if (!SYNC_ENABLED) {
1659
- try {
1660
- const patResult = setupPAT(projectRoot, { interactive: !NON_INTERACTIVE });
1661
- if (patResult.success) {
1662
- console.log(' ✓ Beads sync PAT configured');
1663
- } else if (patResult.reminder) {
1664
- console.log(` ℹ ${patResult.reminder}`);
1665
- } else if (patResult.instructions) {
1666
- console.log(` ℹ ${patResult.instructions.split('\n')[0]}`);
1667
- }
1668
- } catch (_patErr) { // NOSONAR — best-effort PAT setup, non-fatal
1669
- // PAT setup is best-effort — don't block sync scaffold
1670
- }
1671
- }
1672
- } catch (err) {
1673
- console.error(` Error scaffolding GitHub-Beads sync: ${err.message}`);
1674
- }
1675
- }
1676
1612
  }
1677
1613
 
1678
1614
  // Display the Forge banner
@@ -1724,7 +1660,7 @@ function setupCoreDocs() {
1724
1660
  // TEMPLATE.md and PROGRESS.md are also deferred to first use.
1725
1661
 
1726
1662
  // Copy essential docs (TOOLCHAIN.md, VALIDATION.md) to consumer's docs/forge/
1727
- const result = copyEssentialDocs(projectRoot, packageDir);
1663
+ const result = copyEssentialDocs(projectRoot, getPackageRoot(packageDir));
1728
1664
  for (const f of result.created) {
1729
1665
  console.log(` Created: ${f}`);
1730
1666
  }
@@ -1768,7 +1704,7 @@ function minimalInstall() {
1768
1704
  if (fs.existsSync(agentsPath)) {
1769
1705
  console.log(' Skipped: AGENTS.md (already exists)');
1770
1706
  } else {
1771
- const agentsSrc = path.join(packageDir, 'AGENTS.md');
1707
+ const agentsSrc = path.join(getPackageRoot(packageDir), 'AGENTS.md');
1772
1708
  if (copyFile(agentsSrc, 'AGENTS.md')) {
1773
1709
  console.log(' Created: AGENTS.md (universal standard)');
1774
1710
 
@@ -1799,26 +1735,19 @@ function minimalInstall() {
1799
1735
 
1800
1736
 
1801
1737
  // Helper: Setup Claude agent
1802
- function setupClaudeAgent(skipFiles = {}) {
1803
- // Copy commands from package (unless skipped)
1804
- if (skipFiles.claudeCommands) {
1805
- console.log(' Skipped: .claude/commands/ (keeping existing)');
1806
- } else {
1807
- const cmds = getWorkflowCommands();
1808
- let copied = 0;
1809
- cmds.forEach(cmd => {
1810
- const src = path.join(packageDir, `.claude/commands/${cmd}.md`);
1811
- if (copyFile(src, `.claude/commands/${cmd}.md`)) copied++;
1812
- });
1813
- console.log(` Copied: ${copied} workflow commands`);
1814
- }
1815
-
1816
- // Copy rules
1817
- const rulesSrc = path.join(packageDir, '.claude/rules/workflow.md');
1818
- copyFile(rulesSrc, '.claude/rules/workflow.md');
1738
+ function setupClaudeAgent(_skipFiles) {
1739
+ // _skipFiles is accepted for call-site arity parity only; it is unused because
1740
+ // this skills-only surface writes scripts unconditionally (no skip prompts).
1741
+ // Skills-only surface: per-skill SKILL.md dirs are populated by createAgentSkill.
1742
+ //
1743
+ // Claude receives workflow/TDD/security/documentation policy through the
1744
+ // CLAUDE.md AGENTS.md instruction projection, NOT always-on `.claude/rules/*`
1745
+ // files — those would triple-deliver the same policy into every session as
1746
+ // token bloat. See lib/rules-sync.js and lib/harness-capability-matrix.js.
1747
+ // Only Cursor has a first-class native rule surface (rendered by setupCursorAgent).
1819
1748
 
1820
1749
  // Copy scripts
1821
- const scriptSrc = path.join(packageDir, '.claude/scripts/load-env.sh');
1750
+ const scriptSrc = path.join(getPackageRoot(packageDir), '.claude/scripts/load-env.sh');
1822
1751
  copyFile(scriptSrc, '.claude/scripts/load-env.sh');
1823
1752
  }
1824
1753
 
@@ -1827,78 +1756,38 @@ function setupClaudeAgent(skipFiles = {}) {
1827
1756
 
1828
1757
  // Helper: Setup Cursor agent
1829
1758
  async function setupCursorAgent() {
1759
+ // Drop the deprecated root config, but NEVER destroy a user's hand-authored
1760
+ // `.cursorrules` — it predates AGENTS.md, so real users have curated ones.
1761
+ const { removed, backupPath } = backupAndRemoveLegacyCursorRules(projectRoot);
1762
+ if (removed) {
1763
+ console.log(
1764
+ ` Removed: .cursorrules (deprecated — Cursor reads AGENTS.md + .cursor/rules/*.mdc); ` +
1765
+ `backed up to ${path.basename(backupPath)}`,
1766
+ );
1767
+ }
1830
1768
  await generateCursorConfig(projectRoot, { overwrite: false });
1831
1769
  console.log(' Created: Cursor native rules');
1832
1770
  }
1833
1771
 
1834
- async function setupKiloAgent() {
1835
- await generateKiloConfig(projectRoot, { overwrite: false });
1836
- console.log(' Created: Kilo native workflow files');
1837
- }
1838
-
1839
- async function setupCopilotAgent() {
1840
- await generateCopilotConfig(projectRoot, { overwrite: false });
1841
- console.log(' Created: Copilot native config');
1842
- }
1843
-
1844
- async function setupOpenCodeAgent() {
1845
- await generateOpenCodeConfig(projectRoot, { overwrite: false });
1846
- console.log(' Created: OpenCode native config');
1847
- }
1848
-
1849
- // Helper: Convert command to agent-specific format
1850
-
1851
-
1852
- // Helper: Convert command to agent-specific format
1853
- function convertCommandToAgentFormat(cmd, content, agent) {
1854
- let targetContent = content;
1855
- let targetFile = cmd;
1856
-
1857
- if (agent.needsConversion) {
1858
- targetContent = stripFrontmatter(content);
1859
- }
1860
-
1861
- if (agent.promptFormat) {
1862
- targetFile = cmd.replace('.md', '.prompt.md');
1863
- targetContent = stripFrontmatter(content);
1864
- }
1865
-
1866
- return { targetFile, targetContent };
1867
- }
1868
-
1869
- // Helper: Copy commands for agent
1870
-
1772
+ // Helper: back up + remove a deprecated `.cursorrules` without data loss.
1773
+ // Copies to `.cursorrules.bak` (then numbered `.bak.1`, `.2`, … so a repeat never
1774
+ // clobbers an earlier snapshot) before deleting. Mirrors the markerless AGENTS.md
1775
+ // backup above. Exported for direct testing.
1776
+ function backupAndRemoveLegacyCursorRules(root) {
1777
+ const legacy = path.join(root, '.cursorrules');
1778
+ if (!fs.existsSync(legacy)) return { removed: false };
1871
1779
 
1872
- // Helper: Copy commands for agent
1873
- function copyAgentCommands(agent, claudeCommands) {
1874
- if (!claudeCommands) return;
1875
- if (!agent.needsConversion && !agent.copyCommands && !agent.promptFormat) return;
1876
-
1877
- Object.entries(claudeCommands).forEach(([cmd, content]) => {
1878
- const { targetFile, targetContent } = convertCommandToAgentFormat(cmd, content, agent);
1879
- const targetDir = agent.dirs[0]; // First dir is commands/workflows
1880
- writeFile(`${targetDir}/${targetFile}`, targetContent);
1881
- });
1882
- console.log(` Converted: ${Object.keys(claudeCommands).length} workflow commands`);
1883
- }
1884
-
1885
- // Helper: Copy rules for agent
1886
-
1887
-
1888
- // Helper: Copy rules for agent
1889
- function copyAgentRules(agent) {
1890
- if (!agent.needsConversion) return;
1891
-
1892
- const workflowMdPath = path.join(projectRoot, '.claude/rules/workflow.md');
1893
- if (!fs.existsSync(workflowMdPath)) return;
1894
-
1895
- const rulesDir = agent.dirs.find(d => d.includes('/rules'));
1896
- if (!rulesDir) return;
1897
-
1898
- const ruleContent = readFile(workflowMdPath);
1899
- if (ruleContent) {
1900
- writeFile(`${rulesDir}/workflow.md`, ruleContent);
1780
+ let backupPath = path.join(root, '.cursorrules.bak');
1781
+ if (fs.existsSync(backupPath)) {
1782
+ let suffix = 1;
1783
+ while (fs.existsSync(path.join(root, `.cursorrules.bak.${suffix}`))) {
1784
+ suffix += 1;
1785
+ }
1786
+ backupPath = path.join(root, `.cursorrules.bak.${suffix}`);
1901
1787
  }
1788
+ fs.copyFileSync(legacy, backupPath);
1789
+ fs.rmSync(legacy, { force: true });
1790
+ return { removed: true, backupPath };
1902
1791
  }
1903
1792
 
1904
1793
  // Helper: Create skill file for agent
@@ -1911,27 +1800,57 @@ function createAgentSkill(agent, agentKey) {
1911
1800
  return;
1912
1801
  }
1913
1802
 
1914
- if (!agent.hasSkill) return;
1803
+ if (!agent.hasSkill || !agent.skillsDir) return;
1915
1804
 
1916
- const skillDir = agent.dirs.find(d => d.includes('/skills/'));
1917
- if (skillDir) {
1918
- writeFile(`${skillDir}/SKILL.md`, SKILL_CONTENT);
1919
- console.log(' Created: forge-workflow skill');
1920
- }
1805
+ // Skills-only surface: populate every canonical skill into the agent skills dir
1806
+ // (.claude/skills, .cursor/skills) from the packaged canonical `skills/` source.
1807
+ // clean:false so a rerun/upgrade overwrites Forge's skills without deleting
1808
+ // user-authored or third-party skills that share these shared agent dirs.
1809
+ const { written } = populateAgentSkills({
1810
+ sourceRoot: getPackageRoot(packageDir),
1811
+ targetSkillsDir: path.join(projectRoot, agent.skillsDir),
1812
+ clean: false,
1813
+ });
1814
+ console.log(` Created: ${written.length} skills in ${agent.skillsDir}/`);
1921
1815
  }
1922
1816
 
1923
1817
  // Helper: Create Codex per-stage skills from canonical commands
1924
1818
  function createCodexSkills() {
1925
- const installPlan = buildCodexSkillInstallPlan(packageDir, { env: process.env, homeDir: os.homedir() });
1819
+ const installPlan = buildCodexSkillInstallPlan(getPackageRoot(packageDir), { env: process.env, homeDir: os.homedir() });
1926
1820
  const installRoot = formatCodexSkillsInstallDir({ env: process.env, homeDir: os.homedir() });
1927
1821
 
1928
1822
  CODEX_SETUP_REPORT = {
1929
1823
  installRoot,
1930
1824
  skillCount: installPlan.length,
1825
+ repoSkillsDir: CODEX_REPO_SKILLS_DIR,
1826
+ repoSkillCount: 0,
1931
1827
  status: 'complete',
1932
1828
  message: '',
1933
1829
  };
1934
1830
 
1831
+ // Repo-local discovery mirror: generate `.agents/skills/<name>/SKILL.md` from
1832
+ // the canonical skills/ source. This is Codex's documented repo-scope discovery
1833
+ // path (scanned cwd → repo root) and is committed, so a teammate who clones the
1834
+ // repo WITHOUT running `forge setup` still gets Forge skills/stages discovered.
1835
+ // Independent of the GLOBAL $CODEX_HOME install below (which needs canonical
1836
+ // packaging templates) — so it runs before the install-plan early return.
1837
+ try {
1838
+ const { written } = populateCodexRepoSkills({ sourceRoot: getPackageRoot(packageDir), projectRoot });
1839
+ CODEX_SETUP_REPORT.repoSkillCount = written.length;
1840
+ console.log(` Created: ${written.length} repo-local Codex skills in ${CODEX_REPO_SKILLS_DIR}/ (commit for teammate discovery)`);
1841
+ } catch (error) {
1842
+ addSetupNote(`Codex repo-local skill generation failed for ${CODEX_REPO_SKILLS_DIR}: ${error.message}`);
1843
+ console.log(` Warning: could not generate repo-local Codex skills in ${CODEX_REPO_SKILLS_DIR} (${error.message})`);
1844
+ }
1845
+
1846
+ // Codex is NOT given a committed project-local `.codex/skills` mirror. Its
1847
+ // repo-scope discovery path is the committed `.agents/skills` (generated above),
1848
+ // and its stage skills install GLOBALLY into `$CODEX_HOME/skills` (below). A
1849
+ // committed `.codex/skills` copy would feed nothing at runtime, so setup does
1850
+ // not create one and it stays gitignored (#342). See the long-standing invariant
1851
+ // in test/setup-runtime-flags.test.js ("setup installs Codex stage skills into
1852
+ // CODEX_HOME/skills/<stage>/SKILL.md").
1853
+
1935
1854
  if (installPlan.length === 0) {
1936
1855
  CODEX_SETUP_REPORT.status = 'partial';
1937
1856
  CODEX_SETUP_REPORT.message = 'Codex setup could not find the packaged stage skill templates.';
@@ -1960,27 +1879,178 @@ function createCodexSkills() {
1960
1879
  return CODEX_SETUP_REPORT;
1961
1880
  }
1962
1881
 
1963
- // Helper: Setup MCP config for Claude
1964
-
1882
+ // Helper: opt-in Graphiti memory MCP descriptor(s).
1883
+ //
1884
+ // Included ONLY when the memory backend resolves to `graphiti` (same precedence
1885
+ // as the memory router: deps > FORGE_MEMORY_BACKEND > .forge/config.yaml) AND
1886
+ // the config passes the same strict validity check `forge doctor` uses
1887
+ // (assertMemoryConfigValid). Local backend — the default — returns [] so the
1888
+ // rendered MCP config stays byte-identical to the Context7-only output. An
1889
+ // invalid/incomplete graphiti config prints a one-line notice and is skipped;
1890
+ // it never crashes setup.
1891
+ function graphitiMcpDescriptors() {
1892
+ const { assertMemoryConfigValid } = require('../memory/router');
1893
+ try {
1894
+ const { backend, graphiti } = assertMemoryConfigValid({ projectRoot });
1895
+ if (backend !== 'graphiti') return [];
1896
+ const { buildGraphitiServerDescriptor } = require('../memory/graphiti-mcp');
1897
+ return [buildGraphitiServerDescriptor(graphiti)];
1898
+ } catch (err) {
1899
+ // Surface the validator's specific message (it names the missing key and
1900
+ // where to configure it) instead of a generic notice that hides the cause.
1901
+ console.log(
1902
+ ` Notice: Graphiti MCP server not wired — ${err && err.message ? err.message : 'memory config invalid'}`
1903
+ + ' Run `forge doctor` for details.',
1904
+ );
1905
+ return [];
1906
+ }
1907
+ }
1965
1908
 
1966
1909
  // Helper: Setup MCP config for Claude
1910
+ //
1911
+ // Read → merge → write (idempotent). A pre-existing `.mcp.json` is MERGED, not
1912
+ // skipped: the old skip-if-exists behavior silently refused to add the Context7
1913
+ // server whenever any config already existed. User/other servers are preserved.
1967
1914
  function setupClaudeMcpConfig() {
1968
- const mcpPath = path.join(projectRoot, '.mcp.json');
1969
- if (fs.existsSync(mcpPath)) {
1970
- console.log(' Skipped: .mcp.json already exists');
1915
+ const { existed, skipped, backup } = renderMcpConfig({
1916
+ harness: 'claude',
1917
+ targetRoot: projectRoot,
1918
+ descriptors: [CONTEXT7_MCP_DESCRIPTOR, ...graphitiMcpDescriptors()],
1919
+ });
1920
+ if (skipped) {
1921
+ // Existing .mcp.json was unparseable (JSONC/trailing comma): renderMcpConfig
1922
+ // left it untouched and backed it up rather than clobber the user's servers.
1923
+ // Report that honestly instead of a false "merged" success.
1924
+ console.log(
1925
+ ` Skipped: .mcp.json is not valid JSON — left untouched to avoid data loss`
1926
+ + `${backup ? ` (backed up to ${backup})` : ''}. Add the Context7 MCP server manually.`,
1927
+ );
1928
+ return;
1929
+ }
1930
+ console.log(
1931
+ existed
1932
+ ? ' Updated: .mcp.json (merged Context7 MCP, preserved existing servers)'
1933
+ : ' Created: .mcp.json with Context7 MCP',
1934
+ );
1935
+ }
1936
+
1937
+ // Helper: Setup MCP config for Cursor (project-local .cursor/mcp.json).
1938
+ // Cursor reads a project-local MCP config, so this is a real native delivery
1939
+ // (mirrors setupClaudeMcpConfig). Read → merge → write, preserving user servers.
1940
+ function setupCursorMcpConfig() {
1941
+ const { existed, skipped, backup } = renderMcpConfig({
1942
+ harness: 'cursor',
1943
+ targetRoot: projectRoot,
1944
+ descriptors: [CONTEXT7_MCP_DESCRIPTOR, ...graphitiMcpDescriptors()],
1945
+ });
1946
+ if (skipped) {
1947
+ console.log(
1948
+ ` Skipped: .cursor/mcp.json is not valid JSON — left untouched to avoid data loss`
1949
+ + `${backup ? ` (backed up to ${backup})` : ''}. Add the Context7 MCP server manually.`,
1950
+ );
1971
1951
  return;
1972
1952
  }
1953
+ console.log(
1954
+ existed
1955
+ ? ' Updated: .cursor/mcp.json (merged Context7 MCP, preserved existing servers)'
1956
+ : ' Created: .cursor/mcp.json with Context7 MCP',
1957
+ );
1958
+ }
1973
1959
 
1974
- const mcpConfig = {
1975
- mcpServers: {
1976
- context7: {
1977
- command: 'npx',
1978
- args: ['-y', '@upstash/context7-mcp@latest']
1979
- }
1980
- }
1981
- };
1982
- writeFile('.mcp.json', JSON.stringify(mcpConfig, null, 2));
1983
- console.log(' Created: .mcp.json with Context7 MCP');
1960
+ // Safe SAFETY defaults are ON by default (non-surprising) but fully opt-out-able.
1961
+ // Set FORGE_SKIP_SAFETY_DEFAULTS=1 (or `true`) to skip rendering the permission /
1962
+ // ignore defaults — Forge then leaves those surfaces entirely to the user.
1963
+ function safetyDefaultsEnabled() {
1964
+ const flag = String(process.env.FORGE_SKIP_SAFETY_DEFAULTS || '').trim().toLowerCase();
1965
+ return !(flag === '1' || flag === 'true' || flag === 'yes');
1966
+ }
1967
+
1968
+ // Helper: Render safe Claude tool-permission defaults into .claude/settings.json.
1969
+ // Read -> merge -> write (idempotent). Preserves the user's existing settings and
1970
+ // allow/deny/ask entries; an unparseable file is backed up and left untouched.
1971
+ function setupClaudePermissions() {
1972
+ if (!safetyDefaultsEnabled()) {
1973
+ console.log(' Skipped: .claude/settings.json permissions (FORGE_SKIP_SAFETY_DEFAULTS set)');
1974
+ return;
1975
+ }
1976
+ const { existed, skipped, backup } = renderClaudePermissions({ targetRoot: projectRoot });
1977
+ if (skipped) {
1978
+ console.log(
1979
+ ' Skipped: .claude/settings.json is not valid JSON — left untouched to avoid data loss'
1980
+ + `${backup ? ` (backed up to ${backup})` : ''}. Add safe permissions manually.`,
1981
+ );
1982
+ return;
1983
+ }
1984
+ console.log(
1985
+ existed
1986
+ ? ' Updated: .claude/settings.json (merged safe permission defaults, preserved your entries)'
1987
+ : ' Created: .claude/settings.json with safe permission defaults',
1988
+ );
1989
+ }
1990
+
1991
+ // Helper: Render safe .cursorignore defaults (AI read/index boundary).
1992
+ // Read -> merge -> write (idempotent). Preserves user lines; only appends missing
1993
+ // default patterns (secrets/.env/node_modules/build artifacts).
1994
+ function setupCursorIgnore() {
1995
+ if (!safetyDefaultsEnabled()) {
1996
+ console.log(' Skipped: .cursorignore defaults (FORGE_SKIP_SAFETY_DEFAULTS set)');
1997
+ return;
1998
+ }
1999
+ const { existed } = renderCursorIgnore({ targetRoot: projectRoot });
2000
+ console.log(
2001
+ existed
2002
+ ? ' Updated: .cursorignore (appended safe defaults, preserved your entries)'
2003
+ : ' Created: .cursorignore with safe defaults',
2004
+ );
2005
+ }
2006
+
2007
+ // Helper: Setup native HOOK config for Claude (project-local .claude/settings.json).
2008
+ // Projects Forge's TDD-gate + protected-path enforcement onto Claude's native hook
2009
+ // surface (a `hooks` block). Read → merge → write, preserving user hooks; an
2010
+ // unparseable settings.json is backed up and left untouched (data-loss safe).
2011
+ function setupClaudeHooksConfig() {
2012
+ const { existed, skipped, backup, wrote } = renderHookConfig({
2013
+ harness: 'claude',
2014
+ targetRoot: projectRoot,
2015
+ });
2016
+ if (skipped) {
2017
+ console.log(
2018
+ ` Skipped: .claude/settings.json is not valid JSON — left untouched to avoid data loss`
2019
+ + `${backup ? ` (backed up to ${backup})` : ''}. Add the Forge hooks block manually.`,
2020
+ );
2021
+ return;
2022
+ }
2023
+ if (wrote) {
2024
+ console.log(
2025
+ existed
2026
+ ? ' Updated: .claude/settings.json (merged Forge hooks, preserved existing hooks)'
2027
+ : ' Created: .claude/settings.json with Forge hooks',
2028
+ );
2029
+ }
2030
+ }
2031
+
2032
+ // Helper: Setup native HOOK config for Cursor (project-local .cursor/hooks.json,
2033
+ // Cursor 1.7+). Mirrors setupClaudeHooksConfig. Read → merge → write, preserving
2034
+ // user hooks; unparseable config is backed up and left untouched.
2035
+ function setupCursorHooksConfig() {
2036
+ const { existed, skipped, backup, wrote } = renderHookConfig({
2037
+ harness: 'cursor',
2038
+ targetRoot: projectRoot,
2039
+ });
2040
+ if (skipped) {
2041
+ console.log(
2042
+ ` Skipped: .cursor/hooks.json is not valid JSON — left untouched to avoid data loss`
2043
+ + `${backup ? ` (backed up to ${backup})` : ''}. Add the Forge hooks manually.`,
2044
+ );
2045
+ return;
2046
+ }
2047
+ if (wrote) {
2048
+ console.log(
2049
+ existed
2050
+ ? ' Updated: .cursor/hooks.json (merged Forge hooks, preserved existing hooks)'
2051
+ : ' Created: .cursor/hooks.json with Forge hooks',
2052
+ );
2053
+ }
1984
2054
  }
1985
2055
 
1986
2056
  // Helper: Create agent link file
@@ -2002,13 +2072,13 @@ function createAgentLinkFile(agent, symlinkOnly = false) {
2002
2072
 
2003
2073
 
2004
2074
  // Setup specific agent
2005
- async function setupAgent(agentKey, claudeCommands, skipFiles = {}) {
2075
+ async function setupAgent(agentKey, skipFiles = {}) {
2006
2076
  const agent = AGENTS[agentKey];
2007
2077
  if (!agent) return;
2008
2078
 
2009
2079
  console.log(`\nSetting up ${agent.name}...`);
2010
2080
  if (agent.supportStatus === 'deprecated') {
2011
- console.log(` Warning: ${agent.name} is in deprecated compatibility mode; Forge will scaffold converted workflow files only.`);
2081
+ console.log(` Warning: ${agent.name} is in deprecated compatibility mode; Forge will scaffold skill files only.`);
2012
2082
  }
2013
2083
 
2014
2084
  // Create directories
@@ -2023,30 +2093,30 @@ async function setupAgent(agentKey, claudeCommands, skipFiles = {}) {
2023
2093
  await setupCursorAgent();
2024
2094
  }
2025
2095
 
2026
- if (agentKey === 'kilocode') {
2027
- await setupKiloAgent();
2028
- }
2029
-
2030
- if (agentKey === 'copilot') {
2031
- await setupCopilotAgent();
2032
- }
2033
-
2034
- if (agentKey === 'opencode') {
2035
- await setupOpenCodeAgent();
2036
- }
2037
-
2038
- // Convert/copy commands
2039
- copyAgentCommands(agent, claudeCommands);
2040
-
2041
- // Copy rules if needed
2042
- copyAgentRules(agent);
2043
-
2044
2096
  // Create SKILL.md or Codex stage skills
2045
2097
  createAgentSkill(agent, agentKey);
2046
2098
 
2047
- // Setup MCP configs
2099
+ // Setup MCP configs (project-local, native for both harnesses)
2048
2100
  if (agentKey === 'claude') {
2049
2101
  setupClaudeMcpConfig();
2102
+ // Native safety surface: declarative tool-permission allowlist.
2103
+ setupClaudePermissions();
2104
+ }
2105
+ if (agent.customSetup === 'cursor') {
2106
+ setupCursorMcpConfig();
2107
+ // Native safety surface: AI read/index ignore boundary.
2108
+ setupCursorIgnore();
2109
+ }
2110
+
2111
+ // Setup native HOOK configs (project-local, native for both harnesses).
2112
+ // Projects Forge's TDD-gate + protected-path enforcement onto each harness's
2113
+ // native hook surface. Codex hooks are GLOBAL-config scope and intentionally
2114
+ // not written at project setup (see lib/hook-renderer.js).
2115
+ if (agentKey === 'claude') {
2116
+ setupClaudeHooksConfig();
2117
+ }
2118
+ if (agent.customSetup === 'cursor') {
2119
+ setupCursorHooksConfig();
2050
2120
  }
2051
2121
 
2052
2122
  // Create link file (SYMLINK_ONLY = --symlink flag disables copy fallback)
@@ -2086,7 +2156,6 @@ function displayInstallationStatus(projectStatus) {
2086
2156
  }
2087
2157
 
2088
2158
  if (projectStatus.hasAgentsMd) console.log(' - AGENTS.md');
2089
- if (projectStatus.hasClaudeCommands) console.log(' - .claude/commands/');
2090
2159
  if (projectStatus.hasEnvLocal) console.log(' - .env.local');
2091
2160
  console.log('');
2092
2161
  }
@@ -2162,7 +2231,6 @@ async function promptForFileOverwrite(question, fileType, exists, skipFiles) {
2162
2231
 
2163
2232
  const fileLabels = {
2164
2233
  agentsMd: { prompt: 'Found existing AGENTS.md. Overwrite?', message: 'AGENTS.md', key: 'agentsMd' },
2165
- claudeCommands: { prompt: 'Found existing .claude/commands/. Overwrite?', message: '.claude/commands/', key: 'claudeCommands' }
2166
2234
  };
2167
2235
 
2168
2236
  const config = fileLabels[fileType];
@@ -2182,7 +2250,7 @@ async function promptForFileOverwrite(question, fileType, exists, skipFiles) {
2182
2250
  }
2183
2251
  }
2184
2252
 
2185
- // Default behavior: Binary y/n for files with markers or .claude/commands
2253
+ // Default behavior: Binary y/n for files with markers
2186
2254
  const overwrite = await askYesNo(question, config.prompt, true);
2187
2255
  if (overwrite) {
2188
2256
  console.log(` Will overwrite ${config.message}`);
@@ -2334,7 +2402,7 @@ async function installAgentsMd(skipFiles) {
2334
2402
  return;
2335
2403
  }
2336
2404
 
2337
- const agentsSrc = path.join(packageDir, 'AGENTS.md');
2405
+ const agentsSrc = path.join(getPackageRoot(packageDir), 'AGENTS.md');
2338
2406
  const agentsDest = path.join(projectRoot, 'AGENTS.md');
2339
2407
 
2340
2408
  // Try smart merge if file exists
@@ -2372,28 +2440,6 @@ async function installAgentsMd(skipFiles) {
2372
2440
  */
2373
2441
 
2374
2442
 
2375
- /**
2376
- * Load Claude commands for conversion
2377
- */
2378
- function loadClaudeCommands(selectedAgents) {
2379
- const claudeCommands = {};
2380
- const needsClaudeCommands = selectedAgents.includes('claude') ||
2381
- selectedAgents.some(a => AGENTS[a].needsConversion || AGENTS[a].copyCommands);
2382
-
2383
- if (!needsClaudeCommands) {
2384
- return claudeCommands;
2385
- }
2386
-
2387
- getWorkflowCommands().forEach(cmd => {
2388
- const cmdPath = path.join(projectRoot, `.claude/commands/${cmd}.md`);
2389
- const content = readFile(cmdPath);
2390
- if (content) {
2391
- claudeCommands[`${cmd}.md`] = content;
2392
- }
2393
- });
2394
-
2395
- return claudeCommands;
2396
- }
2397
2443
 
2398
2444
  /**
2399
2445
  * Setup agents with progress indication
@@ -2405,8 +2451,8 @@ function loadClaudeCommands(selectedAgents) {
2405
2451
  * Setup agents with progress indication
2406
2452
  * Delegates to setupSelectedAgents to avoid duplicate implementations (S4144)
2407
2453
  */
2408
- async function setupAgentsWithProgress(selectedAgents, claudeCommands, skipFiles) {
2409
- await setupSelectedAgents(selectedAgents, claudeCommands, skipFiles);
2454
+ async function setupAgentsWithProgress(selectedAgents, skipFiles) {
2455
+ await setupSelectedAgents(selectedAgents, skipFiles);
2410
2456
  }
2411
2457
 
2412
2458
  /**
@@ -2427,26 +2473,19 @@ function displaySetupSummary(selectedAgents) {
2427
2473
  console.log('What\'s installed:');
2428
2474
  console.log(' - AGENTS.md (universal instructions)');
2429
2475
 
2430
- const workflowCount = getWorkflowCommands().length;
2431
2476
  selectedAgents.forEach(key => {
2432
2477
  const agent = AGENTS[key];
2433
2478
  if (agent.linkFile) {
2434
2479
  console.log(` - ${agent.linkFile} (${agent.name})`);
2435
2480
  }
2436
- if (agent.hasCommands && key === 'claude') {
2437
- console.log(` - .claude/commands/ (${workflowCount} workflow commands)`);
2438
- } else if (agent.hasCommands && key !== 'codex' && agent.dirs[0]) {
2439
- console.log(` - ${agent.dirs[0]}/ (${workflowCount} workflow commands)`);
2440
- }
2441
2481
  if (key === 'codex') {
2442
- const skillCount = CODEX_SETUP_REPORT?.skillCount ?? listCodexSkillEntries(packageDir).length;
2482
+ const skillCount = CODEX_SETUP_REPORT?.skillCount ?? listCodexSkillEntries(getPackageRoot(packageDir)).length;
2443
2483
  const codexRoot = CODEX_SETUP_REPORT?.installRoot || formatCodexSkillsInstallDir({ env: process.env, homeDir: os.homedir() });
2444
2484
  console.log(` - ${codexRoot}/<stage>/SKILL.md (${skillCount} stage skills)`);
2445
- } else if (agent.hasSkill) {
2446
- const skillDir = agent.dirs.find(d => d.includes('/skills/'));
2447
- if (skillDir) {
2448
- console.log(` - ${skillDir}/SKILL.md`);
2449
- }
2485
+ const repoSkillCount = CODEX_SETUP_REPORT?.repoSkillCount ?? 0;
2486
+ console.log(` - ${CODEX_REPO_SKILLS_DIR}/<skill>/SKILL.md (${repoSkillCount} repo-local skills — commit for teammate discovery)`);
2487
+ } else if (agent.hasSkill && agent.skillsDir) {
2488
+ console.log(` - ${agent.skillsDir}/<skill>/SKILL.md`);
2450
2489
  }
2451
2490
  });
2452
2491
 
@@ -2475,13 +2514,11 @@ function displaySetupSummary(selectedAgents) {
2475
2514
  console.log('');
2476
2515
  printSetupNotes();
2477
2516
 
2478
- // Beads status
2479
- if (isBeadsInitialized()) {
2480
- console.log(' ✓ Beads initialized - Track work: forge ready');
2481
- } else if (checkForBeads()) {
2482
- console.log(' ! Beads available - Run: bd init');
2517
+ // Issue store status — the Kernel store is always present (auto-provisioned)
2518
+ if (KERNEL_STORE_READY) {
2519
+ console.log(' ✓ Kernel issue store - Track work: forge ready');
2483
2520
  } else {
2484
- console.log(` - Beads not installed - Run: ${PKG_MANAGER} install -g @beads/bd && bd init`);
2521
+ console.log(' Kernel issue store - not provisioned; run: forge doctor');
2485
2522
  }
2486
2523
 
2487
2524
  // Skills status
@@ -2552,12 +2589,10 @@ async function _interactiveSetup() {
2552
2589
  // Track which files to skip based on user choices
2553
2590
  const skipFiles = {
2554
2591
  agentsMd: false,
2555
- claudeCommands: false
2556
2592
  };
2557
2593
 
2558
2594
  // Ask about overwriting existing files
2559
2595
  await promptForFileOverwrite(question, 'agentsMd', projectStatus.hasAgentsMd, skipFiles);
2560
- await promptForFileOverwrite(question, 'claudeCommands', projectStatus.hasClaudeCommands, skipFiles);
2561
2596
 
2562
2597
  if (projectStatus.type !== 'fresh') {
2563
2598
  console.log('');
@@ -2580,19 +2615,13 @@ async function _interactiveSetup() {
2580
2615
  setupCoreDocs();
2581
2616
  console.log('');
2582
2617
 
2583
- // Load Claude commands if needed
2584
- let claudeCommands = {};
2585
- if (selectedAgents.includes('claude') || selectedAgents.some(a => AGENTS[a].needsConversion || AGENTS[a].copyCommands)) {
2586
- // First ensure Claude is set up
2587
- if (selectedAgents.includes('claude')) {
2588
- await setupAgent('claude', null, skipFiles);
2589
- }
2590
- // Then load the commands
2591
- claudeCommands = loadClaudeCommands(selectedAgents);
2618
+ // Setup Claude first if selected, then setup remaining agents
2619
+ if (selectedAgents.includes('claude')) {
2620
+ await setupAgent('claude', skipFiles);
2592
2621
  }
2593
2622
 
2594
2623
  // Setup each selected agent with progress indication
2595
- await setupAgentsWithProgress(selectedAgents, claudeCommands, skipFiles);
2624
+ await setupAgentsWithProgress(selectedAgents, skipFiles);
2596
2625
 
2597
2626
  // =============================================
2598
2627
  // STEP 2: Project Tools Setup
@@ -2615,6 +2644,9 @@ async function _interactiveSetup() {
2615
2644
  // Final Summary
2616
2645
  // =============================================
2617
2646
  displaySetupSummary(selectedAgents);
2647
+
2648
+ // One-step onboarding: run `forge init` when config is still absent (ac0b38c7).
2649
+ await finalizeWorkflowConfig();
2618
2650
  }
2619
2651
 
2620
2652
  // Parse CLI flags
@@ -2671,55 +2703,78 @@ async function handleHuskyMigration() {
2671
2703
 
2672
2704
  // Install git hooks via lefthook
2673
2705
  // SECURITY: Uses execSync with HARDCODED strings only (no user input)
2674
- function installGitHooks() { // NOSONAR Extracted as-is from bin/forge.js; complexity reduction deferred
2675
- console.log('Installing git hooks (TDD enforcement)...');
2676
-
2677
- // Skip lefthook.yml creation if binary is not available
2678
- const lefthookStatus = checkLefthookStatus(projectRoot);
2679
- if (!lefthookStatus.binaryAvailable) {
2680
- if (lefthookStatus.message) {
2681
- console.warn(` \u26A0 Skipping lefthook setup: ${lefthookStatus.message}`);
2682
- } else {
2683
- console.warn(' \u26A0 Skipping lefthook setup: binary not available');
2684
- }
2685
- return;
2686
- }
2687
-
2688
- // Check if lefthook.yml exists (it should, as it's in the package)
2689
- const lefthookConfig = path.join(packageDir, 'lefthook.yml');
2706
+ // Copy the Forge hook scripts (check-tdd.js + the native-hook adapter) into the
2707
+ // project's .forge/hooks/. Runs UNCONDITIONALLY — independent of the lefthook binary.
2708
+ // The native harness hooks rendered by lib/hook-renderer.js invoke forge-native-hook.js,
2709
+ // which delegates the TDD gate to check-tdd.js, so BOTH must always be installed; if we
2710
+ // only copied them on the lefthook path, a machine without lefthook would get native
2711
+ // hooks pointing at an adapter that was never installed.
2712
+ function installForgeHookScripts() {
2690
2713
  const targetHooks = path.join(projectRoot, '.forge/hooks');
2691
-
2692
- try {
2693
- // Copy lefthook.yml to project root
2694
- const lefthookTarget = path.join(projectRoot, 'lefthook.yml');
2695
- if (!fs.existsSync(lefthookTarget)) {
2696
- if (copyFile(lefthookConfig, 'lefthook.yml')) {
2697
- console.log(' ✓ Created lefthook.yml');
2714
+ for (const name of ['check-tdd.js', 'forge-native-hook.js']) {
2715
+ const src = path.join(getPackageRoot(packageDir), '.forge/hooks', name);
2716
+ if (!fs.existsSync(src)) continue;
2717
+ if (!fs.existsSync(targetHooks)) fs.mkdirSync(targetHooks, { recursive: true });
2718
+ const dest = path.join(targetHooks, name);
2719
+ if (copyFile(src, dest)) {
2720
+ console.log(` ✓ Created .forge/hooks/${name}`);
2721
+ try {
2722
+ fs.chmodSync(dest, 0o755); // NOSONAR — 755 is intentional: hook scripts must be executable
2723
+ } catch (err) {
2724
+ console.warn('chmod not available (Windows):', err.message);
2698
2725
  }
2699
2726
  }
2727
+ }
2728
+ }
2700
2729
 
2701
- // Copy check-tdd.js hook script
2702
- const hookSource = path.join(packageDir, '.forge/hooks/check-tdd.js');
2703
- if (fs.existsSync(hookSource)) {
2704
- // Ensure .forge/hooks directory exists
2705
- if (!fs.existsSync(targetHooks)) {
2706
- fs.mkdirSync(targetHooks, { recursive: true });
2707
- }
2730
+ // FORGE_USER_LEFTHOOK_YML and forgeShouldWriteLefthookConfig are the single source of
2731
+ // truth in lib/lefthook-wiring.js, shared with bin/forge.js so the live `forge setup`
2732
+ // path and this repair path can never drift again — that drift is exactly what let the
2733
+ // stub-shadow fix (kernel e452422c / c713fce7) live in this module while `forge setup`
2734
+ // kept shipping the broken behaviour. They are imported at the top of this file and
2735
+ // re-exported below for the existing test surface.
2708
2736
 
2709
- const hookTarget = path.join(targetHooks, 'check-tdd.js');
2710
- if (copyFile(hookSource, hookTarget)) {
2711
- console.log(' Created .forge/hooks/check-tdd.js');
2737
+ function installGitHooks(options = {}) { // NOSONAR — Extracted as-is from bin/forge.js; complexity reduction deferred
2738
+ // loud=true (the `forge setup` handlers — quickSetup + executeSetup) surfaces an
2739
+ // inert-hooks result as a HARD failure — a banner + non-zero exit — so setup never ends
2740
+ // green with TDD enforcement silently off (B3). loud=false (the default: mid-stage
2741
+ // repairRuntimeReadiness AND `forge init` via ensureGitHooksInstalled) only warns, since
2742
+ // init deliberately degrades to a warning rather than failing, and repair runs inside
2743
+ // another command's flow.
2744
+ const loud = options.loud === true;
2745
+ console.log('Installing git hooks (TDD enforcement)...');
2712
2746
 
2713
- // Make hook executable (Unix systems)
2714
- try {
2715
- fs.chmodSync(hookTarget, 0o755); // NOSONAR 755 is intentional: git hooks must be executable
2716
- } catch (err) {
2717
- // Windows doesn't need chmod
2718
- console.warn('chmod not available (Windows):', err.message);
2719
- }
2720
- }
2747
+ // Install the Forge hook SCRIPTS first, unconditionally: they back BOTH the lefthook
2748
+ // pre-commit gate AND the native harness hooks that forge setup renders regardless of
2749
+ // whether the lefthook binary is present.
2750
+ installForgeHookScripts();
2751
+
2752
+ const lefthookStatus = checkLefthookStatus(projectRoot);
2753
+ let lefthookInstalled = false;
2754
+ if (!lefthookStatus.binaryAvailable && lefthookStatus.message) {
2755
+ console.warn(` \u26A0 lefthook binary unavailable: ${lefthookStatus.message}`);
2756
+ }
2757
+
2758
+ // Only drive lefthook when its binary is actually present. When it is not, fall
2759
+ // straight through to the native .git/hooks fallback below rather than triggering an
2760
+ // on-demand `npx lefthook` fetch \u2014 that is network-dependent and can hang on Windows,
2761
+ // and it keeps this repair path byte-for-byte consistent with bin/forge.js.
2762
+ if (lefthookStatus.binaryAvailable) {
2763
+ try {
2764
+ // Write the user-facing lefthook.yml (references only .forge/hooks/check-tdd.js and
2765
+ // the project's own tests — never the repo's internal scripts/). Overwrite lefthook's
2766
+ // own disposable stub so Forge's pre-commit/pre-push wiring actually lands; never
2767
+ // clobber a config that already has active jobs (kernel c713fce7).
2768
+ const lefthookTarget = path.join(projectRoot, 'lefthook.yml');
2769
+ if (forgeShouldWriteLefthookConfig(lefthookTarget)) {
2770
+ fs.writeFileSync(lefthookTarget, FORGE_USER_LEFTHOOK_YML, 'utf8');
2771
+ actionLog.add('lefthook.yml', 'created');
2772
+ console.log(' ✓ Created lefthook.yml');
2721
2773
  }
2722
2774
 
2775
+ // (The Forge hook scripts — check-tdd.js + forge-native-hook.js — are installed
2776
+ // unconditionally by installForgeHookScripts() above, before this lefthook path.)
2777
+
2723
2778
  // Try to install lefthook hooks
2724
2779
  // SECURITY: Using execFileSync with hardcoded commands (no user input)
2725
2780
  try {
@@ -2727,12 +2782,14 @@ function installGitHooks() { // NOSONAR — Extracted as-is from bin/forge.js; c
2727
2782
  try {
2728
2783
  secureExecFileSync('npx', ['lefthook', 'install'], { stdio: 'inherit', cwd: projectRoot });
2729
2784
  console.log(' ✓ Lefthook hooks installed (local)');
2785
+ lefthookInstalled = true;
2730
2786
  } catch (error_) {
2731
2787
  // Fallback to global lefthook
2732
2788
  console.warn('npx lefthook failed, trying global:', error_.message);
2733
- execFileSync('lefthook', ['version'], { stdio: 'ignore' });
2734
- execFileSync('lefthook', ['install'], { stdio: 'inherit', cwd: projectRoot });
2789
+ secureExecFileSync('lefthook', ['version'], { stdio: 'ignore' });
2790
+ secureExecFileSync('lefthook', ['install'], { stdio: 'inherit', cwd: projectRoot });
2735
2791
  console.log(' ✓ Lefthook hooks installed (global)');
2792
+ lefthookInstalled = true;
2736
2793
  }
2737
2794
  } catch (err) {
2738
2795
  console.warn('Lefthook installation failed:', err.message);
@@ -2751,6 +2808,48 @@ function installGitHooks() { // NOSONAR — Extracted as-is from bin/forge.js; c
2751
2808
  console.log(' You can install manually later with: lefthook install');
2752
2809
  console.log('');
2753
2810
  }
2811
+ }
2812
+
2813
+ // Native fallback: when lefthook could not be installed, wire native .git/hooks so
2814
+ // raw git commit / git push still enforce the TDD gate — enforcement is never silently
2815
+ // inert (B3). This repair path only warns (never sets a failure exit code) since it can
2816
+ // run mid-stage under enforce-stage's repairWorkflowRuntimeAssets().
2817
+ if (!lefthookInstalled) {
2818
+ const native = installNativeGitHooks(projectRoot);
2819
+ if (native.installed) {
2820
+ console.log(` ✓ Native git hooks installed (${native.written.join(', ')}) - lefthook fallback`);
2821
+ } else if (native.skipped && native.skipped.length > 0) {
2822
+ console.warn(` ⚠ Native hook(s) skipped to preserve existing hooks: ${native.skipped.join(', ')}`);
2823
+ } else {
2824
+ console.warn(` ⚠ Could not install native git hooks: ${native.reason || 'unknown'}`);
2825
+ }
2826
+ }
2827
+
2828
+ const verdict = verifyHooksActive(projectRoot);
2829
+ if (verdict.active) {
2830
+ console.log(` ✓ Git hook enforcement active (${verdict.method}).`);
2831
+ } else if (loud && resolveGitHooksDir(projectRoot)) {
2832
+ // In a git repo but enforcement is inert — the exact silent-inert bug B3 kills.
2833
+ // Fail LOUDLY and non-zero so `forge setup` never ends green with hooks off.
2834
+ // (A non-git dir has nothing to hook into, so it only warns — see the else.)
2835
+ const addCmd = PKG_MANAGER === 'bun'
2836
+ ? 'bun add -d'
2837
+ : PKG_MANAGER === 'npm'
2838
+ ? 'npm install --save-dev'
2839
+ : `${PKG_MANAGER} add -D`;
2840
+ console.error('');
2841
+ console.error(' ============================================================');
2842
+ console.error(' ⚠ TDD ENFORCEMENT IS NOT ACTIVE');
2843
+ console.error(` ${verdict.reason || 'no pre-commit hook is installed'}.`);
2844
+ console.error(' `forge ship` will block until hooks are active. To fix:');
2845
+ console.error(` ${addCmd} lefthook && npx lefthook install`);
2846
+ console.error(' (or re-run `forge setup` in the repo root).');
2847
+ console.error(' ============================================================');
2848
+ console.error('');
2849
+ process.exitCode = 1;
2850
+ } else {
2851
+ console.warn(` ⚠ TDD enforcement is NOT active: ${verdict.reason || 'no pre-commit hook installed'}.`);
2852
+ }
2754
2853
  }
2755
2854
 
2756
2855
  // Check if lefthook is already installed in project (delegates to lib/lefthook-check)
@@ -2795,130 +2894,34 @@ function repairDeclaredLefthookDependency(selectedAgents) {
2795
2894
  }
2796
2895
  }
2797
2896
 
2798
- // Check if Beads is installed (global, local, or bunx-capable)
2799
-
2800
-
2801
- // Check if Beads is installed (global, local, or bunx-capable)
2802
- function checkForBeads() {
2803
- // Try global install first
2804
- try {
2805
- secureExecFileSync('bd', ['version'], { stdio: 'ignore' });
2806
- return 'global';
2807
- } catch (err) {
2808
- // Not global
2809
- console.warn('Beads not found globally:', err.message);
2810
- }
2811
-
2812
- // Check if bunx can run it
2897
+ // Ensure the local Forge Kernel issue store exists and is migrated.
2898
+ //
2899
+ // The Kernel is a single-machine SQLite store in the git common dir; building
2900
+ // the migrated deps runs broker.initialize() (idempotent), so the DB + schema
2901
+ // exist before first use. There is no external CLI to install — issue tracking
2902
+ // ships with Forge. Best-effort: never throws, so setup proceeds even if the
2903
+ // SQLite runtime is unavailable (the first kernel command would migrate it
2904
+ // later anyway).
2905
+ async function ensureKernelIssueStore() {
2813
2906
  try {
2814
- secureExecFileSync('bunx', ['@beads/bd', 'version'], { stdio: 'ignore' });
2815
- return 'bunx';
2816
- } catch (err) {
2817
- // Not bunx-capable
2818
- console.warn('Beads not available via bunx:', err.message);
2819
- }
2820
-
2821
- // Check local project installation
2822
- const pkgPath = path.join(projectRoot, 'package.json');
2823
- if (!fs.existsSync(pkgPath)) return null;
2824
-
2825
- try {
2826
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
2827
- const isInstalled = pkg.devDependencies?.['@beads/bd'] || pkg.dependencies?.['@beads/bd'];
2828
- return isInstalled ? 'local' : null;
2829
- } catch (err) {
2830
- console.warn('Failed to check Beads in package.json:', err.message);
2831
- return null;
2832
- }
2833
- }
2834
- // Check if Beads is initialized in project — delegates to lib/beads-setup
2835
-
2836
- // Check if Beads is initialized in project — delegates to lib/beads-setup
2837
- function isBeadsInitialized() {
2838
- return beadsSetupLib.isBeadsInitialized(projectRoot);
2839
- }
2840
-
2841
- // Initialize Beads in the project using the defensive safeBeadsInit wrapper
2842
- // Handles config/gitignore writes, hook snapshot/restore, and JSONL pre-seeding
2843
-
2844
-
2845
- // Initialize Beads in the project using the defensive safeBeadsInit wrapper
2846
- // Handles config/gitignore writes, hook snapshot/restore, and JSONL pre-seeding
2847
- function initializeBeads(installType) {
2848
- console.log('Initializing Beads in project...');
2849
-
2850
- // Build the execBdInit function based on installType
2851
- const execBdInit = (root) => {
2852
- // SECURITY: execFileSync with hardcoded commands
2853
- if (installType === 'global') {
2854
- secureExecFileSync('bd', ['init'], { stdio: 'inherit', cwd: root });
2855
- } else if (installType === 'bunx') {
2856
- secureExecFileSync('bunx', ['@beads/bd', 'init'], { stdio: 'inherit', cwd: root });
2857
- } else if (installType === 'local') {
2858
- secureExecFileSync('npx', ['bd', 'init'], { stdio: 'inherit', cwd: root });
2859
- }
2860
- };
2861
-
2862
- // Derive prefix from package.json name or directory name
2863
- let prefix;
2864
- try {
2865
- const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
2866
- prefix = pkg.name || path.basename(projectRoot);
2867
- } catch (_e) { // NOSONAR — fallback to directory name if package.json unreadable
2868
- prefix = path.basename(projectRoot);
2869
- }
2870
-
2871
- try {
2872
- const result = beadsSetupLib.safeBeadsInit(projectRoot, {
2873
- prefix,
2874
- execBdInit,
2875
- restoreLefthook: (root) => {
2876
- try {
2877
- secureExecFileSync('lefthook', ['install'], { stdio: 'ignore', cwd: root });
2878
- } catch (_e) { // NOSONAR — lefthook may not be installed yet, non-fatal
2879
- // lefthook may not be installed yet — non-fatal
2880
- }
2881
- }
2882
- });
2883
-
2884
- if (result.skipped) {
2885
- console.log(' ✓ Beads already initialized');
2886
- return true;
2887
- }
2888
-
2889
- if (!result.success) {
2890
- for (const e of result.errors) {
2891
- console.log(` ⚠ ${e}`);
2892
- }
2893
- console.log(' Run manually: bd init');
2894
- return false;
2895
- }
2896
-
2897
- for (const w of result.warnings) {
2898
- console.warn(` ⚠ ${w}`);
2899
- }
2900
- console.log(' ✓ Beads initialized');
2901
-
2902
- // Run post-init health check (non-fatal)
2903
- try {
2904
- const health = beadsHealthCheck(projectRoot);
2905
- if (health.healthy) {
2906
- console.log(' ✓ Beads health check passed');
2907
- } else {
2908
- console.log(` ⚠ Beads health check failed at ${health.failedStep}: ${health.error}`);
2907
+ const deps = await buildMigratedKernelIssueDeps({ projectRoot });
2908
+ const handle = deps.kernelBroker || deps.kernelDriver;
2909
+ if (handle && typeof handle.close === 'function') {
2910
+ try {
2911
+ await handle.close();
2912
+ } catch (_closeErr) { // NOSONAR — best-effort cleanup of the migration handle
2913
+ // Closing the migration handle is best-effort; the process is short-lived.
2909
2914
  }
2910
- } catch (_healthErr) { // NOSONAR — health check is best-effort, non-fatal
2911
- // Health check is best-effort — don't block setup
2912
2915
  }
2913
-
2916
+ KERNEL_STORE_READY = true;
2914
2917
  return true;
2915
2918
  } catch (err) {
2916
- console.log(' ⚠ Failed to initialize Beads:', err.message);
2917
- console.log(' Run manually: bd init');
2919
+ KERNEL_STORE_READY = false;
2920
+ console.log(` Could not provision the Kernel issue store: ${err.message}`);
2921
+ addSetupNote(`Kernel issue store not provisioned: ${err.message}. Run \`forge doctor\` to inspect.`);
2918
2922
  return false;
2919
2923
  }
2920
2924
  }
2921
-
2922
2925
  // Check if Skills CLI is installed
2923
2926
 
2924
2927
 
@@ -2984,59 +2987,18 @@ function initializeSkills(installType) {
2984
2987
  }
2985
2988
  }
2986
2989
 
2987
- // Prompt for Beads setup - extracted to reduce cognitive complexity
2988
-
2989
-
2990
- // Prompt for Beads setup - extracted to reduce cognitive complexity
2991
- async function promptBeadsSetup(question) {
2990
+ // Ensure the issue store during interactive setup. No prompt: the Forge Kernel
2991
+ // ships with Forge and auto-provisions, so there is nothing to install or pick.
2992
+ async function promptBeadsSetup(_question) {
2992
2993
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2993
- console.log('Beads Setup (Recommended)');
2994
+ console.log('Issue Store (Forge Kernel)');
2994
2995
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2995
2996
  console.log('');
2996
2997
 
2997
- const beadsInitialized = isBeadsInitialized();
2998
- const beadsStatus = checkForBeads();
2999
-
3000
- if (beadsInitialized) {
3001
- console.log('✓ Beads is already initialized in this project');
3002
- console.log('');
3003
- return;
3004
- }
3005
-
3006
- if (beadsStatus) {
3007
- // Already installed, just need to initialize
3008
- console.log(`ℹ Beads is installed (${beadsStatus}), but not initialized`);
3009
- const initBeads = await question('Initialize Beads in this project? (y/n): ');
3010
-
3011
- if (initBeads.toLowerCase() === 'y') {
3012
- initializeBeads(beadsStatus);
3013
- } else {
3014
- console.log('Skipped Beads initialization. Run manually: bd init');
3015
- }
3016
- console.log('');
3017
- return;
3018
- }
3019
-
3020
- // Not installed
3021
- console.log('ℹ Beads is not installed');
3022
- const installBeads = await question('Install Beads? (y/n): ');
3023
-
3024
- if (installBeads.toLowerCase() !== 'y') {
3025
- console.log('Skipped Beads installation');
3026
- console.log('');
3027
- return;
2998
+ const ready = await ensureKernelIssueStore();
2999
+ if (ready) {
3000
+ console.log('✓ Kernel issue store ready (single-machine; team sync not configured)');
3028
3001
  }
3029
-
3030
- console.log('');
3031
- console.log('Choose installation method:');
3032
- console.log(' 1. Global (recommended) - Available system-wide');
3033
- console.log(' 2. Local - Project-specific devDependency');
3034
- console.log(' 3. Bunx - Use via bunx (requires bun)');
3035
- console.log('');
3036
- const method = await question('Choose method (1-3): ');
3037
-
3038
- console.log('');
3039
- installBeadsWithMethod(method);
3040
3002
  console.log('');
3041
3003
  }
3042
3004
 
@@ -3056,72 +3018,6 @@ function installViaBunx(packageName, versionArgs, initFn, toolName) {
3056
3018
  }
3057
3019
  }
3058
3020
 
3059
- // Helper: Install Beads with chosen method - extracted to reduce cognitive complexity
3060
- // SECURITY NOTE: Downloads and executes a remote PowerShell script.
3061
- // The npm @beads/bd package is broken on Windows (GitHub Issue #1031, closed "not planned"),
3062
- // so the official PowerShell installer is the only supported path.
3063
- // Mitigations: HTTPS transport (prevents MITM), official beads repo, user-visible URL.
3064
- // Follow-up: pin to a versioned release tag once beads publishes tagged releases (for example v0.49.1).
3065
- const BEADS_INSTALL_PS1_URL = 'https://raw.githubusercontent.com/steveyegge/beads/main/install.ps1';
3066
-
3067
-
3068
-
3069
- function installBeadsOnWindows() {
3070
- console.log(' (Windows detected: using PowerShell installer)');
3071
- console.log(` Downloading: ${BEADS_INSTALL_PS1_URL}`);
3072
- secureExecFileSync('powershell.exe', [
3073
- '-NoProfile', '-NonInteractive', '-Command',
3074
- `irm ${BEADS_INSTALL_PS1_URL} | iex`
3075
- ], { stdio: 'inherit' });
3076
- }
3077
-
3078
-
3079
-
3080
- function installBeadsWithMethod(method) { // NOSONAR — Extracted as-is from bin/forge.js; complexity reduction deferred
3081
- try {
3082
- // SECURITY: secureExecFileSync with hardcoded commands
3083
- if (method === '1') {
3084
- console.log('Installing Beads globally...');
3085
- if (process.platform === 'win32') {
3086
- installBeadsOnWindows();
3087
- } else {
3088
- const pkgManager = PKG_MANAGER === 'bun' ? 'bun' : 'npm';
3089
- secureExecFileSync(pkgManager, ['install', '-g', '@beads/bd'], { stdio: 'inherit' });
3090
- }
3091
- console.log(' ✓ Beads installed globally');
3092
- initializeBeads('global');
3093
- } else if (method === '2') {
3094
- console.log('Installing Beads locally...');
3095
- // On Windows, npm postinstall for @beads/bd runs Expand-Archive which has EPERM file-locking
3096
- // (GitHub Issue #1031, closed "not planned") — same root cause as global install.
3097
- // Redirect Windows users to the global PowerShell installer instead.
3098
- if (process.platform === 'win32') {
3099
- console.log(' ⚠ Local install not supported on Windows (npm @beads/bd EPERM issue).');
3100
- console.log(' Falling back to global PowerShell installer...');
3101
- installBeadsOnWindows();
3102
- } else {
3103
- const pkgManager = PKG_MANAGER === 'bun' ? 'bun' : 'npm';
3104
- secureExecFileSync(pkgManager, ['install', '-D', '@beads/bd'], { stdio: 'inherit', cwd: projectRoot });
3105
- }
3106
- console.log(' ✓ Beads installed');
3107
- // On Windows the fallback was global (PowerShell installer), so init as 'global'
3108
- initializeBeads(process.platform === 'win32' ? 'global' : 'local');
3109
- } else if (method === '3') {
3110
- installViaBunx('@beads/bd', ['version'], initializeBeads, 'Beads');
3111
- } else {
3112
- console.log('Invalid choice. Skipping Beads installation.');
3113
- }
3114
- } catch (err) {
3115
- console.warn('Beads installation failed:', err.message);
3116
- console.log(' ⚠ Failed to install Beads:', err.message);
3117
- if (process.platform === 'win32') {
3118
- console.log(` Run manually: irm ${BEADS_INSTALL_PS1_URL} | iex`);
3119
- } else {
3120
- console.log(` Run manually: ${PKG_MANAGER === 'bun' ? 'bun add -g' : 'npm install -g'} @beads/bd && bd init`);
3121
- }
3122
- }
3123
- }
3124
-
3125
3021
  // Helper: Get package-manager-specific install args for Skills
3126
3022
 
3127
3023
 
@@ -3231,7 +3127,7 @@ async function setupProjectTools(rl, question) {
3231
3127
  console.log('');
3232
3128
  console.log('Forge recommends three tools for enhanced workflows:');
3233
3129
  console.log('');
3234
- console.log('• Beads - Git-backed issue tracking');
3130
+ console.log('• Issue tracking (Forge Kernel) - zero-install, git-backed');
3235
3131
  console.log(' Persists tasks across sessions, tracks dependencies.');
3236
3132
  console.log(' Command: forge ready, forge create, forge close');
3237
3133
  console.log('');
@@ -3245,44 +3141,6 @@ async function setupProjectTools(rl, question) {
3245
3141
  await promptSkillsSetup(question);
3246
3142
  }
3247
3143
 
3248
- // Auto-setup Beads in quick mode - extracted to reduce cognitive complexity
3249
-
3250
-
3251
- // Auto-setup Beads in quick mode - extracted to reduce cognitive complexity
3252
- function autoSetupBeadsInQuickMode() { // NOSONAR — Extracted as-is from bin/forge.js; complexity reduction deferred
3253
- const beadsStatus = checkForBeads();
3254
- const beadsInitialized = isBeadsInitialized();
3255
-
3256
- if (!beadsInitialized && beadsStatus) {
3257
- console.log('📦 Initializing Beads...');
3258
- initializeBeads(beadsStatus);
3259
- console.log('');
3260
- } else if (!beadsInitialized && !beadsStatus) {
3261
- console.log('📦 Installing Beads globally...');
3262
- try {
3263
- // SECURITY: use PowerShell on Windows (npm @beads/bd is broken on Windows - Issue #1031)
3264
- if (process.platform === 'win32') {
3265
- installBeadsOnWindows();
3266
- } else {
3267
- const pkgManager = PKG_MANAGER === 'bun' ? 'bun' : 'npm';
3268
- secureExecFileSync(pkgManager, ['install', '-g', '@beads/bd'], { stdio: 'inherit' });
3269
- }
3270
- console.log(' ✓ Beads installed globally');
3271
- initializeBeads('global');
3272
- } catch (err) {
3273
- // Installation failed - provide manual instructions
3274
- console.log(' ⚠ Could not install Beads automatically');
3275
- console.log(` Error: ${err.message}`);
3276
- if (process.platform === 'win32') {
3277
- console.log(` Run manually: irm ${BEADS_INSTALL_PS1_URL} | iex`);
3278
- } else {
3279
- console.log(` Run manually: ${PKG_MANAGER === 'bun' ? 'bun add -g' : 'npm install -g'} @beads/bd && bd init`);
3280
- }
3281
- }
3282
- console.log('');
3283
- }
3284
- }
3285
-
3286
3144
  // Helper: Auto-install lefthook if not present - extracted to reduce cognitive complexity
3287
3145
 
3288
3146
 
@@ -3326,6 +3184,16 @@ function autoInstallLefthook() { // NOSONAR — Extracted as-is from bin/forge.j
3326
3184
  return;
3327
3185
  }
3328
3186
 
3187
+ // Not in package.json at all. GUARD (kernel 22e33dbf): with no package.json in
3188
+ // projectRoot, `<pkg-mgr> install lefthook` resolves against the nearest ANCESTOR
3189
+ // package.json and installs into the WRONG project (or fails). Skip it — installGitHooks
3190
+ // then wires native .git/hooks, which need no package.json, so enforcement is still live.
3191
+ if (!fs.existsSync(path.join(projectRoot, 'package.json'))) {
3192
+ console.log(' ℹ No package.json here — skipping lefthook install (would target an ancestor); native hooks will back enforcement.');
3193
+ console.log('');
3194
+ return;
3195
+ }
3196
+
3329
3197
  // Not in package.json at all — full install
3330
3198
  console.log('📦 Installing lefthook for git hooks...');
3331
3199
  try {
@@ -3341,32 +3209,13 @@ function autoInstallLefthook() { // NOSONAR — Extracted as-is from bin/forge.j
3341
3209
  console.log('');
3342
3210
  }
3343
3211
 
3344
- // Helper: Verify a tool is callable after install - extracted to reduce cognitive complexity
3345
-
3346
-
3347
- // Helper: Verify a tool is callable after install - extracted to reduce cognitive complexity
3348
- function verifyToolInstall(command, args, toolName) {
3349
- try {
3350
- secureExecFileSync(command, args, { stdio: 'ignore' });
3351
- return true;
3352
- } catch (_err) { // NOSONAR - S2486: Intentionally ignored; verification failure is handled by caller
3353
- console.log(` ⚠ ${toolName} installed but not callable. Check your PATH.`);
3354
- return false;
3355
- }
3356
- }
3357
-
3358
3212
  // Helper: Auto-setup tools (Skills) in quick mode - extracted to reduce cognitive complexity
3359
3213
 
3360
3214
 
3361
3215
  // Helper: Auto-setup tools (Skills) in quick mode - extracted to reduce cognitive complexity
3362
- function autoSetupToolsInQuickMode() {
3363
- // Beads: auto-install or initialize
3364
- autoSetupBeadsInQuickMode();
3365
-
3366
- // Post-install verification for Beads
3367
- if (isBeadsInitialized()) {
3368
- verifyToolInstall('bd', ['version'], 'Beads');
3369
- }
3216
+ async function autoSetupToolsInQuickMode() {
3217
+ // Issue store: ensure the Forge Kernel store exists (auto-provisioned, no CLI)
3218
+ await ensureKernelIssueStore();
3370
3219
 
3371
3220
  // Skills: only initialize if already installed (recommended tool)
3372
3221
  const skillsStatus = checkForSkills();
@@ -3374,11 +3223,10 @@ function autoSetupToolsInQuickMode() {
3374
3223
  console.log('📦 Initializing Skills...');
3375
3224
  initializeSkills(skillsStatus);
3376
3225
  console.log('');
3377
- } else if (!skillsStatus) {
3378
- const installCmd = PKG_MANAGER === 'bun' ? 'bun add -g' : 'npm install -g';
3379
- console.log(` ℹ Skills not found — install with: ${installCmd} @forge/skills`);
3380
- console.log('');
3381
3226
  }
3227
+ // No hint when the optional Skills CLI is absent: setup bundles and renders
3228
+ // Forge's skills itself, and the previously advertised "@forge/skills"
3229
+ // package does not exist on npm (kernel issue 6e554b41).
3382
3230
  }
3383
3231
 
3384
3232
  // Helper: Configure default external services in quick mode - extracted to reduce cognitive complexity
@@ -3410,6 +3258,73 @@ function configureDefaultExternalServices(skipExternal) {
3410
3258
  console.log('Configuration saved to .env.local');
3411
3259
  }
3412
3260
 
3261
+ // Auto-import an existing Beads store into the Kernel during setup.
3262
+ // Idempotent and CLI-free: reuses the `forge migrate --from beads` spine, which
3263
+ // reads the committed Beads jsonl sidecars directly (no external issue-tracker
3264
+ // binary), so it works even when the legacy SQL backend is offline. Failures
3265
+ // degrade to a setup note rather than aborting setup. Returns the migrate
3266
+ // outcome for callers/tests.
3267
+ async function autoMigrateBeadsToKernel(opts = {}) {
3268
+ const migrateModule = require('./migrate');
3269
+ let outcome;
3270
+ try {
3271
+ outcome = await migrateModule.autoMigrateBeadsIfPresent(projectRoot, opts);
3272
+ } catch (err) {
3273
+ addSetupNote(`Beads → Kernel auto-migration failed: ${err.message}`);
3274
+ return { migrated: false };
3275
+ }
3276
+
3277
+ if (!outcome.migrated) {
3278
+ if (outcome.result && outcome.result.success === false) {
3279
+ addSetupNote(`Beads → Kernel auto-migration skipped: ${outcome.result.error}`);
3280
+ }
3281
+ return outcome;
3282
+ }
3283
+
3284
+ const { imported, gaps } = outcome.result;
3285
+ const inserted = imported.issues.inserted;
3286
+ const skipped = imported.issues.skipped;
3287
+ if (inserted > 0) {
3288
+ let line = ` ✓ Migrated ${inserted} issue(s) from Beads to the Kernel`;
3289
+ if (gaps && gaps.count > 0) {
3290
+ line += ` (${gaps.count} field gap(s): ${gaps.items.map(g => g.field).join(', ')})`;
3291
+ }
3292
+ console.log(line);
3293
+ } else {
3294
+ console.log(` ✓ Beads store already present in the Kernel (${skipped} issue(s))`);
3295
+ }
3296
+ return outcome;
3297
+ }
3298
+
3299
+ // Install git hooks for a target project root without a full setup run.
3300
+ // Reuses the same lefthook install path setup performs so `forge init` can
3301
+ // reach a hook-active state (closing the init → HOOKS_NOT_ACTIVE catch-22).
3302
+ // Skips silently when there is no package.json to attach hooks to — keeps
3303
+ // `forge init` lightweight for bare/non-node repos and avoids surprise installs.
3304
+ // Restores mutated module state afterward.
3305
+ async function ensureGitHooksInstalled(targetRoot = projectRoot) {
3306
+ // No early bail on a missing package.json (kernel 22e33dbf / B3): the native
3307
+ // .git/hooks fallback needs no package.json, so `forge init` on a bare repo must
3308
+ // still wire enforcement instead of silently skipping it. autoInstallLefthook
3309
+ // guards the package-manager install itself for the no-package.json case.
3310
+ const previousRoot = projectRoot;
3311
+ const previousInteractive = NON_INTERACTIVE;
3312
+ const previousPkgManager = PKG_MANAGER;
3313
+ projectRoot = targetRoot;
3314
+ NON_INTERACTIVE = true;
3315
+ PKG_MANAGER = detectPackageManager();
3316
+ try {
3317
+ autoInstallLefthook();
3318
+ await handleHuskyMigration();
3319
+ installGitHooks();
3320
+ return { installed: true };
3321
+ } finally {
3322
+ projectRoot = previousRoot;
3323
+ NON_INTERACTIVE = previousInteractive;
3324
+ PKG_MANAGER = previousPkgManager;
3325
+ }
3326
+ }
3327
+
3413
3328
  // Quick setup with defaults
3414
3329
 
3415
3330
 
@@ -3428,8 +3343,11 @@ async function quickSetup(selectedAgents, skipExternal) {
3428
3343
  });
3429
3344
  console.log('');
3430
3345
 
3431
- // Copy AGENTS.md (actionLog tracks it via copyFile)
3432
- const agentsSrc = path.join(packageDir, 'AGENTS.md');
3346
+ // Copy AGENTS.md (actionLog tracks it via copyFile). Quick mode is
3347
+ // non-interactive and overwrites unconditionally, so back up a markerless
3348
+ // (pre-Forge) AGENTS.md first to avoid silent data loss (kernel issue a5399f3d).
3349
+ const agentsSrc = path.join(getPackageRoot(packageDir), 'AGENTS.md');
3350
+ backupMarkerlessAgentsMd();
3433
3351
  copyFile(agentsSrc, 'AGENTS.md');
3434
3352
  console.log('');
3435
3353
 
@@ -3442,20 +3360,26 @@ async function quickSetup(selectedAgents, skipExternal) {
3442
3360
  // Auto-install lefthook if missing
3443
3361
  autoInstallLefthook();
3444
3362
 
3445
- // Auto-setup project tools (Beads, Skills)
3446
- autoSetupToolsInQuickMode();
3363
+ // Auto-setup project tools (Kernel issue store, Skills)
3364
+ await autoSetupToolsInQuickMode();
3447
3365
 
3448
- // Load canonical commands and setup agents (reuse existing helpers)
3449
- const claudeCommands = await loadAndSetupCanonicalCommands(selectedAgents);
3450
- await setupSelectedAgents(selectedAgents, claudeCommands);
3366
+ // Auto-import an existing Beads store into the Kernel (idempotent, CLI-free)
3367
+ await autoMigrateBeadsToKernel();
3368
+
3369
+ // Setup Claude first if selected, then setup remaining agents
3370
+ if (selectedAgents.includes('claude')) {
3371
+ await setupAgent('claude');
3372
+ }
3373
+ await setupSelectedAgents(selectedAgents);
3451
3374
  ensureWorkflowRuntimeAssets(selectedAgents);
3452
3375
 
3453
3376
  // Detect Husky and migrate before installing Lefthook hooks
3454
3377
  await handleHuskyMigration();
3455
3378
 
3456
- // Install git hooks for TDD enforcement
3379
+ // Install git hooks for TDD enforcement. LOUD: this is the `forge setup` handler,
3380
+ // so an inert-hooks result must fail non-zero (B3), not end green.
3457
3381
  console.log('');
3458
- installGitHooks();
3382
+ installGitHooks({ loud: true });
3459
3383
 
3460
3384
  // Configure external services with defaults (unless skipped)
3461
3385
  configureDefaultExternalServices(skipExternal);
@@ -3469,6 +3393,9 @@ async function quickSetup(selectedAgents, skipExternal) {
3469
3393
  console.log('');
3470
3394
  console.log(renderSetupSummary(actionLog, selectedAgents, VERBOSE_MODE, { status: getSetupSummaryStatus() }));
3471
3395
  printSetupNotes();
3396
+ // One-step onboarding: run `forge init` when config is still absent (ac0b38c7).
3397
+ // Hooks + Beads already handled above, so init skips those side effects.
3398
+ await finalizeWorkflowConfig({ hooksAlreadyInstalled: true });
3472
3399
  console.log('');
3473
3400
  }
3474
3401
 
@@ -3509,7 +3436,7 @@ function setupAgentsMdFile(flags, skipFiles) {
3509
3436
  return;
3510
3437
  }
3511
3438
 
3512
- const agentsSrc = path.join(packageDir, 'AGENTS.md');
3439
+ const agentsSrc = path.join(getPackageRoot(packageDir), 'AGENTS.md');
3513
3440
  const agentsDest = path.join(projectRoot, 'AGENTS.md');
3514
3441
  const mergeStrategy = flags.merge || 'smart';
3515
3442
 
@@ -3589,7 +3516,6 @@ function displayExistingInstallation(projectStatus) {
3589
3516
  : 'Found partial installation:');
3590
3517
 
3591
3518
  if (projectStatus.hasAgentsMd) console.log(' - AGENTS.md');
3592
- if (projectStatus.hasClaudeCommands) console.log(' - .claude/commands/');
3593
3519
  if (projectStatus.hasEnvLocal) console.log(' - .env.local');
3594
3520
  console.log('');
3595
3521
  }
@@ -3601,7 +3527,6 @@ function displayExistingInstallation(projectStatus) {
3601
3527
  async function promptForOverwriteDecisions(question, projectStatus, flags = {}) {
3602
3528
  const skipFiles = {
3603
3529
  agentsMd: false,
3604
- claudeCommands: false
3605
3530
  };
3606
3531
 
3607
3532
  if (flags.keep) {
@@ -3609,10 +3534,6 @@ async function promptForOverwriteDecisions(question, projectStatus, flags = {})
3609
3534
  skipFiles.agentsMd = true;
3610
3535
  console.log(' Keeping existing AGENTS.md (--keep)');
3611
3536
  }
3612
- if (projectStatus.hasClaudeCommands) {
3613
- skipFiles.claudeCommands = true;
3614
- console.log(' Keeping existing .claude/commands/ (--keep)');
3615
- }
3616
3537
  return skipFiles;
3617
3538
  }
3618
3539
 
@@ -3622,12 +3543,6 @@ async function promptForOverwriteDecisions(question, projectStatus, flags = {})
3622
3543
  console.log(overwriteAgents ? ' Will overwrite AGENTS.md' : ' Keeping existing AGENTS.md');
3623
3544
  }
3624
3545
 
3625
- if (projectStatus.hasClaudeCommands) {
3626
- const overwriteCommands = await askYesNo(question, 'Found existing .claude/commands/. Overwrite?', true);
3627
- skipFiles.claudeCommands = !overwriteCommands;
3628
- console.log(overwriteCommands ? ' Will overwrite .claude/commands/' : ' Keeping existing .claude/commands/');
3629
- }
3630
-
3631
3546
  if (projectStatus.type !== 'fresh') {
3632
3547
  console.log('');
3633
3548
  }
@@ -3635,47 +3550,17 @@ async function promptForOverwriteDecisions(question, projectStatus, flags = {})
3635
3550
  return skipFiles;
3636
3551
  }
3637
3552
 
3638
- // Helper: Load and setup canonical commands - extracted to reduce cognitive complexity
3639
-
3640
-
3641
- // Helper: Load and setup canonical commands - extracted to reduce cognitive complexity
3642
- async function loadAndSetupCanonicalCommands(selectedAgents, skipFiles) {
3643
- const claudeCommands = {};
3644
- const needsClaudeCommands = selectedAgents.includes('claude') ||
3645
- selectedAgents.some(a => AGENTS[a].needsConversion || AGENTS[a].copyCommands);
3646
-
3647
- if (!needsClaudeCommands) {
3648
- return claudeCommands;
3649
- }
3650
-
3651
- // First ensure Claude is set up
3652
- if (selectedAgents.includes('claude')) {
3653
- await setupAgent('claude', null, skipFiles);
3654
- }
3655
-
3656
- // Then load the commands (from existing or newly created)
3657
- getWorkflowCommands().forEach(cmd => {
3658
- const cmdPath = path.join(projectRoot, `.claude/commands/${cmd}.md`);
3659
- const content = readFile(cmdPath);
3660
- if (content) {
3661
- claudeCommands[`${cmd}.md`] = content;
3662
- }
3663
- });
3664
-
3665
- return claudeCommands;
3666
- }
3667
-
3668
3553
  // Helper: Setup all selected agents - extracted to reduce cognitive complexity
3669
3554
 
3670
3555
 
3671
3556
  // Helper: Setup all selected agents - extracted to reduce cognitive complexity
3672
- async function setupSelectedAgents(selectedAgents, claudeCommands, skipFiles) {
3557
+ async function setupSelectedAgents(selectedAgents, skipFiles) {
3673
3558
  const totalAgents = selectedAgents.length;
3674
3559
  for (const [index, agentKey] of selectedAgents.entries()) {
3675
3560
  const agent = AGENTS[agentKey];
3676
3561
  console.log(`\n[${index + 1}/${totalAgents}] Setting up ${agent.name}...`);
3677
3562
  if (agentKey !== 'claude') { // Claude already done above
3678
- await setupAgent(agentKey, claudeCommands, skipFiles);
3563
+ await setupAgent(agentKey, skipFiles);
3679
3564
  }
3680
3565
  }
3681
3566
 
@@ -3775,7 +3660,7 @@ async function interactiveSetupWithFlags(flags) {
3775
3660
  const selectedAgents = await promptForAgentSelection(question, agentKeys);
3776
3661
 
3777
3662
  // Check GitHub CLI prerequisite now that selectedAgents is known
3778
- if (requiresGithubCliForSetup(selectedAgents, { syncEnabled: SYNC_ENABLED })) {
3663
+ if (requiresGithubCliForSetup(selectedAgents)) {
3779
3664
  checkPrerequisites({ requireGithubCli: true });
3780
3665
  }
3781
3666
 
@@ -3790,11 +3675,13 @@ async function interactiveSetupWithFlags(flags) {
3790
3675
  setupCoreDocs();
3791
3676
  console.log('');
3792
3677
 
3793
- // Load Claude commands if needed (delegated to helper)
3794
- const claudeCommands = await loadAndSetupCanonicalCommands(selectedAgents, skipFiles);
3678
+ // Setup Claude first if selected (delegated to helper), then remaining agents
3679
+ if (selectedAgents.includes('claude')) {
3680
+ await setupAgent('claude', skipFiles);
3681
+ }
3795
3682
 
3796
3683
  // Setup each selected agent with progress indication (delegated to helper)
3797
- await setupSelectedAgents(selectedAgents, claudeCommands, skipFiles);
3684
+ await setupSelectedAgents(selectedAgents, skipFiles);
3798
3685
  ensureWorkflowRuntimeAssets(selectedAgents);
3799
3686
 
3800
3687
  // Handle external services step (delegated to helper)
@@ -3805,6 +3692,10 @@ async function interactiveSetupWithFlags(flags) {
3805
3692
 
3806
3693
  // Display final summary (delegated to helper)
3807
3694
  displaySetupSummary(selectedAgents);
3695
+
3696
+ // One-step onboarding: this path never writes .forge/config.yaml itself, so
3697
+ // run `forge init` when the config is still absent (ac0b38c7).
3698
+ await finalizeWorkflowConfig();
3808
3699
  }
3809
3700
 
3810
3701
  // Main
@@ -3903,15 +3794,10 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3903
3794
  addFileAction(dir + '/', 'Create agent directory');
3904
3795
  }
3905
3796
 
3906
- // Claude-specific files
3797
+ // Claude-specific files (skills are listed by the per-skill block below).
3798
+ // Claude gets policy via CLAUDE.md → AGENTS.md, not always-on .claude/rules/* files.
3907
3799
  if (agentKey === 'claude') {
3908
- const cmds = getWorkflowCommands();
3909
- for (const cmd of cmds) {
3910
- addFileAction(`.claude/commands/${cmd}.md`, 'Workflow command');
3911
- }
3912
- addFileAction('.claude/rules/workflow.md', 'Workflow rules');
3913
3800
  addFileAction('.claude/scripts/load-env.sh', 'Environment loader script');
3914
- addFileAction('.claude/skills/forge-workflow/SKILL.md', 'Forge workflow skill');
3915
3801
  addFileAction('.mcp.json', 'MCP server configuration');
3916
3802
  addFileAction('CLAUDE.md', 'Claude root config (links to AGENTS.md)');
3917
3803
  }
@@ -3922,7 +3808,7 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3922
3808
  }
3923
3809
  }
3924
3810
 
3925
- // Cursor-specific files
3811
+ // Cursor-specific files (rendered from the canonical rules/ source)
3926
3812
  if (agent.customSetup === 'cursor') {
3927
3813
  addFileAction('.cursor/rules/forge-workflow.mdc', 'Cursor workflow rule');
3928
3814
  addFileAction('.cursor/rules/tdd-enforcement.mdc', 'Cursor TDD rule');
@@ -3930,54 +3816,19 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3930
3816
  addFileAction('.cursor/rules/documentation.mdc', 'Cursor documentation rule');
3931
3817
  }
3932
3818
 
3933
- if (agentKey === 'kilocode') {
3934
- addFileAction('.kilocode/workflows/forge-workflow.md', 'Kilo native workflow');
3935
- addFileAction('.kilocode/rules/workflow.md', 'Kilo native rules');
3936
- addFileAction('.kilocode/skills/forge-workflow/SKILL.md', 'Kilo native skill');
3937
- }
3938
-
3939
- if (agent.customSetup === 'copilot') {
3940
- addFileAction('.github/copilot-instructions.md', 'Copilot root instructions');
3941
- addFileAction('.github/instructions/typescript.instructions.md', 'Copilot TypeScript instructions');
3942
- addFileAction('.github/instructions/testing.instructions.md', 'Copilot testing instructions');
3943
- addFileAction('.github/prompts/red.prompt.md', 'Copilot RED prompt');
3944
- addFileAction('.github/prompts/green.prompt.md', 'Copilot GREEN prompt');
3945
- }
3946
-
3947
- if (agent.customSetup === 'opencode') {
3948
- addFileAction('opencode.json', 'OpenCode root config');
3949
- addFileAction('.opencode/agents/plan-review.md', 'OpenCode plan-review agent');
3950
- addFileAction('.opencode/agents/tdd-build.md', 'OpenCode tdd-build agent');
3951
- }
3952
-
3953
- // Agent commands (converted from Claude format)
3954
- if (agent.needsConversion || agent.copyCommands || agent.promptFormat) {
3955
- const cmds = getWorkflowCommands();
3956
- const targetDir = agent.dirs[0];
3957
- for (const cmd of cmds) {
3958
- const ext = agent.promptFormat ? '.prompt.md' : '.md';
3959
- addFileAction(`${targetDir}/${cmd}${ext}`, 'Converted workflow command');
3960
- }
3961
- }
3962
-
3963
- // Agent rules (copied from Claude)
3964
- if (agent.needsConversion) {
3965
- const rulesDir = agent.dirs.find(d => d.includes('/rules'));
3966
- if (rulesDir) {
3967
- addFileAction(`${rulesDir}/workflow.md`, 'Workflow rules');
3968
- }
3969
- }
3970
-
3971
3819
  // Agent skill
3972
3820
  if (agentKey === 'codex') {
3973
- const skillEntries = buildCodexSkillInstallPlan(packageDir, { env: process.env, homeDir: os.homedir() });
3821
+ const skillEntries = buildCodexSkillInstallPlan(getPackageRoot(packageDir), { env: process.env, homeDir: os.homedir() });
3974
3822
  for (const entry of skillEntries) {
3975
3823
  addFileAction(entry.displayPath, 'Codex stage skill');
3976
3824
  }
3977
- } else if (agent.hasSkill) {
3978
- const skillDir = agent.dirs.find(d => d.includes('/skills/'));
3979
- if (skillDir) {
3980
- addFileAction(`${skillDir}/SKILL.md`, 'Forge workflow skill');
3825
+ } else if (agent.hasSkill && agent.skillsDir) {
3826
+ // Skills ship whole directories (SKILL.md + any nested assets); list every
3827
+ // file so --dry-run reflects the real filesystem changes, not just SKILL.md.
3828
+ for (const skill of listCanonicalSkills(getPackageRoot(packageDir))) {
3829
+ for (const rel of listFilesRecursive(skill.sourcePath)) {
3830
+ addFileAction(`${agent.skillsDir}/${skill.name}/${rel}`, 'Forge skill');
3831
+ }
3981
3832
  }
3982
3833
  }
3983
3834
 
@@ -3990,6 +3841,7 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3990
3841
  // Git hooks
3991
3842
  addFileAction('lefthook.yml', 'Git hook configuration');
3992
3843
  addFileAction('.forge/hooks/check-tdd.js', 'TDD enforcement hook');
3844
+ addFileAction('.forge/hooks/forge-native-hook.js', 'Native-hook enforcement adapter (Claude/Cursor)');
3993
3845
 
3994
3846
  // Print dry-run summary
3995
3847
  console.log('');
@@ -4012,7 +3864,7 @@ async function executeSetup(config) {
4012
3864
  // Check prerequisites
4013
3865
  checkPrerequisites({
4014
3866
  requireBeadsCli: true,
4015
- requireGithubCli: requiresGithubCliForSetup(agents, { syncEnabled: SYNC_ENABLED }),
3867
+ requireGithubCli: requiresGithubCliForSetup(agents),
4016
3868
  requireJq: true,
4017
3869
  commandRunner,
4018
3870
  });
@@ -4023,7 +3875,7 @@ async function executeSetup(config) {
4023
3875
  if (fs.existsSync(agentsDest)) {
4024
3876
  actionLog.add('AGENTS.md', 'skipped', 'already exists');
4025
3877
  } else {
4026
- const agentsSrc = path.join(packageDir, 'AGENTS.md');
3878
+ const agentsSrc = path.join(getPackageRoot(packageDir), 'AGENTS.md');
4027
3879
  copyFile(agentsSrc, 'AGENTS.md');
4028
3880
  }
4029
3881
  console.log('');
@@ -4034,22 +3886,13 @@ async function executeSetup(config) {
4034
3886
 
4035
3887
  const skipFiles = {
4036
3888
  agentsMd: keepExisting && fs.existsSync(path.join(projectRoot, 'AGENTS.md')),
4037
- claudeCommands: keepExisting && fs.existsSync(path.join(projectRoot, '.claude', 'commands'))
4038
3889
  };
4039
3890
 
4040
- if (skipFiles.claudeCommands) {
4041
- console.log(' Keeping existing .claude/commands/ (--keep)');
3891
+ // Setup Claude first if selected, then remaining agents
3892
+ if (agents.includes('claude')) {
3893
+ await setupAgent('claude', skipFiles);
4042
3894
  }
4043
-
4044
- // Load canonical commands — use loadAndSetupCanonicalCommands when claude is selected
4045
- // so that .claude/commands/ are seeded before reading them
4046
- const claudeCommands = agents.includes('claude')
4047
- ? await loadAndSetupCanonicalCommands(agents, skipFiles)
4048
- : loadClaudeCommands(agents);
4049
-
4050
- // Setup agents with progress output (setupSelectedAgents skips claude internally
4051
- // since loadAndSetupCanonicalCommands already handled it above)
4052
- await setupSelectedAgents(agents, claudeCommands, skipFiles);
3895
+ await setupSelectedAgents(agents, skipFiles);
4053
3896
  ensureWorkflowRuntimeAssets(agents);
4054
3897
  ensureWorkflowShellPolicy(agents);
4055
3898
  repairDeclaredLefthookDependency(agents);
@@ -4057,9 +3900,13 @@ async function executeSetup(config) {
4057
3900
  // Detect Husky and migrate before installing Lefthook hooks
4058
3901
  await handleHuskyMigration();
4059
3902
 
4060
- // Install git hooks for TDD enforcement
3903
+ // Install git hooks for TDD enforcement. LOUD: this is the `forge setup` handler,
3904
+ // so an inert-hooks result must fail non-zero (B3), not end green.
4061
3905
  console.log('');
4062
- installGitHooks();
3906
+ installGitHooks({ loud: true });
3907
+
3908
+ // Auto-import an existing Beads store into the Kernel (idempotent, CLI-free)
3909
+ await autoMigrateBeadsToKernel();
4063
3910
 
4064
3911
  // External services (unless skipped)
4065
3912
  await handleExternalServices(skipExternal, agents);
@@ -4073,6 +3920,9 @@ async function executeSetup(config) {
4073
3920
  console.log('');
4074
3921
  console.log(renderSetupSummary(actionLog, agents, VERBOSE_MODE, { status: getSetupSummaryStatus() }));
4075
3922
  printSetupNotes();
3923
+ // One-step onboarding: run `forge init` when config is still absent (ac0b38c7).
3924
+ // Hooks + Beads already handled above, so init skips those side effects.
3925
+ await finalizeWorkflowConfig({ hooksAlreadyInstalled: true });
4076
3926
  console.log('');
4077
3927
  }
4078
3928
 
@@ -4082,36 +3932,12 @@ async function executeSetup(config) {
4082
3932
  // Helper: Scaffold Beads GitHub sync when --sync flag is provided
4083
3933
  async function handleSyncScaffold() {
4084
3934
  console.log('');
4085
- console.log('Scaffolding Beads GitHub sync workflows (--sync)...');
3935
+ console.log('Beads GitHub sync scaffolding is deprecated (--sync).');
4086
3936
  try {
4087
- // Scaffold sync files using the new lib module
4088
- const result = scaffoldBeadsSync(projectRoot, packageDir);
4089
- for (const f of (result.filesCreated || [])) {
4090
- console.log(` Created: ${f}`);
4091
- }
4092
- for (const f of (result.filesSkipped || [])) {
4093
- console.log(` Skipped: ${f} (already exists)`);
4094
- }
4095
-
4096
- // Detect default branch and pin forge-managed workflows to the repo baseline Beads version.
4097
- const branch = detectDefaultBranch(projectRoot);
4098
- const beadsVersion = DEFAULT_BEADS_VERSION;
4099
- const workflowDir = path.join(projectRoot, '.github', 'workflows');
4100
- templateWorkflows(workflowDir, branch, beadsVersion, result.filesCreated || []);
4101
- console.log(` Branch: ${branch}, Beads version: ${beadsVersion}`);
4102
-
4103
- // PAT setup: interactive when possible, reminder otherwise
4104
- try {
4105
- const patResult = setupPAT(projectRoot, { interactive: !NON_INTERACTIVE });
4106
- if (patResult.success) {
4107
- console.log(' PAT configured for Beads sync');
4108
- } else if (patResult.reminder) {
4109
- console.log(` ${patResult.reminder}`);
4110
- } else if (patResult.instructions) {
4111
- console.log(` ${patResult.instructions.split('\n')[0]}`);
4112
- }
4113
- } catch (_patErr) { // NOSONAR — best-effort PAT setup, non-fatal
4114
- // PAT setup is best-effort — don't block sync scaffold
3937
+ const result = scaffoldBeadsSync(projectRoot, getPackageRoot(packageDir));
3938
+ console.log(` ${result.message}`);
3939
+ for (const f of result.filesRemoved || []) {
3940
+ console.log(` Removed deprecated sync file: ${f}`);
4115
3941
  }
4116
3942
  } catch (err) {
4117
3943
  console.error(` Error scaffolding GitHub-Beads sync: ${err.message}`);
@@ -4124,7 +3950,7 @@ async function handleSyncScaffold() {
4124
3950
  // Helper: Handle setup command in non-quick mode
4125
3951
  async function handleSetupCommand(selectedAgents, flags) {
4126
3952
  if (!Array.isArray(selectedAgents) || selectedAgents.length === 0) {
4127
- return interactiveSetupWithFlags(flags);
3953
+ return runInteractiveSetupFallback(flags);
4128
3954
  }
4129
3955
 
4130
3956
  // Allow callers (e.g. reinstall) to override projectRoot without process.chdir()
@@ -4144,6 +3970,10 @@ async function handleSetupCommand(selectedAgents, flags) {
4144
3970
  }
4145
3971
  }
4146
3972
 
3973
+ async function runInteractiveSetupFallback(flags, interactiveSetup = interactiveSetupWithFlags) {
3974
+ return interactiveSetup(flags);
3975
+ }
3976
+
4147
3977
  // Helper: Handle external services configuration
4148
3978
 
4149
3979
 
@@ -4179,7 +4009,7 @@ async function handleExternalServices(skipExternal, selectedAgents) {
4179
4009
  * Detect which agents are already configured in a project directory.
4180
4010
  * Checks for the presence of each agent's configured directories/files.
4181
4011
  * Returns the external-facing setup IDs used by the setup UX, including
4182
- * legacy aliases such as `claude-code`, `github-copilot`, and `roo-code`.
4012
+ * legacy aliases such as `claude-code`.
4183
4013
  * Callers that need raw plugin IDs for internal lookup must normalize first.
4184
4014
  *
4185
4015
  * @param {string} dir - Project directory to scan
@@ -4190,8 +4020,6 @@ function detectConfiguredAgents(dir) {
4190
4020
  const detected = [];
4191
4021
  const legacyAgentIds = {
4192
4022
  claude: 'claude-code',
4193
- copilot: 'github-copilot',
4194
- roo: 'roo-code',
4195
4023
  };
4196
4024
 
4197
4025
  pluginManager.getAllPlugins().forEach((plugin, id) => {
@@ -4213,7 +4041,7 @@ function detectConfiguredAgents(dir) {
4213
4041
  * Used during setup --clean or reset flows.
4214
4042
  *
4215
4043
  * @param {string} dir - Project directory
4216
- * @param {string} agentName - Agent slug (e.g. 'cursor', 'cline')
4044
+ * @param {string} agentName - Agent slug (e.g. 'cursor', 'codex')
4217
4045
  * @param {object} [manifest] - Optional sync manifest with file paths to remove
4218
4046
  * @returns {{ removed: string[], errors: string[] }}
4219
4047
  */
@@ -4273,6 +4101,9 @@ const SETUP_FLAG_DEFAULTS = Object.freeze({
4273
4101
  verbose: false,
4274
4102
  dryRun: false,
4275
4103
  quick: false,
4104
+ minimal: false,
4105
+ standard: false,
4106
+ full: false,
4276
4107
  skipExternal: false,
4277
4108
  sync: false,
4278
4109
  symlink: false,
@@ -4287,6 +4118,9 @@ const SIMPLE_SETUP_FLAG_UPDATES = Object.freeze({
4287
4118
  '--verbose': { verbose: true },
4288
4119
  '--dry-run': { dryRun: true },
4289
4120
  '--quick': { quick: true },
4121
+ '--minimal': { minimal: true },
4122
+ '--standard': { standard: true },
4123
+ '--full': { full: true },
4290
4124
  '--skip-external': { skipExternal: true },
4291
4125
  '--sync': { sync: true },
4292
4126
  '--symlink': { symlink: true },
@@ -4364,6 +4198,9 @@ function mergeSetupFlags(flags, argv) {
4364
4198
  verbose: Boolean(flags.verbose || setupFlags.verbose),
4365
4199
  dryRun: Boolean(flags.dryRun || setupFlags.dryRun),
4366
4200
  quick: Boolean(flags.quick || setupFlags.quick),
4201
+ minimal: Boolean(flags.minimal || setupFlags.minimal),
4202
+ standard: Boolean(flags.standard || setupFlags.standard),
4203
+ full: Boolean(flags.full || setupFlags.full),
4367
4204
  skipExternal: Boolean(flags.skipExternal || setupFlags.skipExternal),
4368
4205
  sync: Boolean(flags.sync || setupFlags.sync),
4369
4206
  symlink: Boolean(flags.symlink || setupFlags.symlink),
@@ -4374,9 +4211,6 @@ function mergeSetupFlags(flags, argv) {
4374
4211
  function normalizeDetectedAgent(agentName) {
4375
4212
  const aliases = {
4376
4213
  'claude-code': 'claude',
4377
- 'github-copilot': 'copilot',
4378
- 'kilo-code': 'kilocode',
4379
- 'roo-code': 'roo',
4380
4214
  };
4381
4215
  return aliases[agentName] || agentName;
4382
4216
  }
@@ -4420,6 +4254,22 @@ module.exports = {
4420
4254
  resetSetupNotes();
4421
4255
  PKG_MANAGER = detectPackageManager();
4422
4256
 
4257
+ if (flags.minimal || flags.standard || flags.full) {
4258
+ const selectedProfiles = [];
4259
+ if (flags.minimal) selectedProfiles.push('minimal');
4260
+ if (flags.standard) selectedProfiles.push('standard');
4261
+ if (flags.full) selectedProfiles.push('full');
4262
+ if (selectedProfiles.length > 1) {
4263
+ return {
4264
+ success: false,
4265
+ error: `Conflicting profile flags: ${selectedProfiles.join(', ')}. Choose exactly one of --minimal, --standard, or --full.`,
4266
+ };
4267
+ }
4268
+
4269
+ const [profile] = selectedProfiles;
4270
+ return initCommand.handler([`--profile=${profile}`, '--yes', ...(flags.force ? ['--force'] : [])], flags, projectRoot);
4271
+ }
4272
+
4423
4273
  // Determine agents to install
4424
4274
  let selectedAgents = determineSelectedAgents(flags);
4425
4275
 
@@ -4454,12 +4304,17 @@ module.exports = {
4454
4304
  return { success: true };
4455
4305
  }
4456
4306
 
4307
+ if (flags.sync && selectedAgents.length === 0) {
4308
+ await handleSyncScaffold();
4309
+ return { success: true };
4310
+ }
4311
+
4457
4312
  if (selectedAgents.length > 0) {
4458
4313
  await handleSetupCommand(selectedAgents, flags);
4459
4314
  return { success: true };
4460
4315
  }
4461
4316
 
4462
- await interactiveSetupWithFlags(flags);
4317
+ await runInteractiveSetupFallback(flags);
4463
4318
  return { success: true };
4464
4319
  },
4465
4320
 
@@ -4467,9 +4322,13 @@ module.exports = {
4467
4322
  checkPrerequisites,
4468
4323
  setupCoreDocs,
4469
4324
  displaySetupSummary,
4325
+ printForgeInitNextStep,
4326
+ finalizeWorkflowConfig,
4327
+ backupMarkerlessAgentsMd,
4470
4328
  setupAgent,
4471
4329
  quickSetup,
4472
4330
  interactiveSetupWithFlags,
4331
+ _runInteractiveSetupFallback: runInteractiveSetupFallback,
4473
4332
  dryRunSetup,
4474
4333
  handleSetupCommand,
4475
4334
  executeSetup,
@@ -4477,29 +4336,36 @@ module.exports = {
4477
4336
  _interactiveSetup,
4478
4337
  configureExternalServices,
4479
4338
  configureDefaultExternalServices,
4480
- installBeadsWithMethod,
4481
4339
  installSkillsWithMethod,
4482
4340
  installViaBunx,
4483
4341
  autoInstallLefthook,
4484
4342
  autoSetupToolsInQuickMode,
4343
+ autoMigrateBeadsToKernel,
4344
+ ensureGitHooksInstalled,
4345
+ forgeShouldWriteLefthookConfig,
4346
+ FORGE_USER_LEFTHOOK_YML,
4485
4347
  setupClaudeMcpConfig,
4348
+ setupCursorMcpConfig,
4349
+ setupClaudePermissions,
4350
+ setupCursorIgnore,
4351
+ setupClaudeHooksConfig,
4352
+ setupCursorHooksConfig,
4486
4353
  displayMcpStatus,
4487
4354
  displayEnvTokenResults,
4488
4355
  minimalInstall,
4489
4356
  determineSelectedAgents,
4490
4357
  handlePathSetup,
4491
- loadAndSetupCanonicalCommands,
4492
4358
  detectConfiguredAgents,
4493
4359
  removeAgentFiles,
4494
4360
  parseSetupFlags,
4495
4361
  mergeSetupFlags,
4496
- getWorkflowCommands,
4497
4362
  getWorkflowRuntimeAssets,
4498
4363
  findMissingWorkflowRuntimeAssets,
4499
4364
  ensureWorkflowShellPolicy,
4500
4365
  repairWorkflowRuntimeAssets,
4501
4366
  repairRuntimeReadiness,
4502
4367
  _showBanner: showBanner,
4368
+ backupAndRemoveLegacyCursorRules,
4503
4369
 
4504
4370
  // State accessors for testing
4505
4371
  _getState: () => ({ projectRoot, FORCE_MODE, VERBOSE_MODE, NON_INTERACTIVE, SYMLINK_ONLY, SYNC_ENABLED, PKG_MANAGER }),