forge-workflow 0.0.9 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +466 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/reference/TOOLCHAIN.md +658 -0
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +66 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +22 -2
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +851 -979
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +697 -0
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. package/scripts/sync-commands.js +0 -600
@@ -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 { detectDefaultBranch, detectBeadsVersion, 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
-
1871
-
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;
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 };
1897
1779
 
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
+ );
1971
1928
  return;
1972
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
+ );
1951
+ return;
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,20 +2782,24 @@ 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
- } catch (err) {
2738
- console.warn('Lefthook installation failed:', err.message);
2739
- console.log(' Lefthook not found. Install it:');
2740
- console.log(' bun add -d lefthook (recommended)');
2741
- console.log(' OR: bun add -g lefthook (global)');
2742
- console.log(' Then run: bunx lefthook install');
2743
- }
2794
+ } catch (err) {
2795
+ console.warn('Lefthook installation failed:', err.message);
2796
+ console.warn(' Lefthook hooks were not installed; raw git push remains unsafe in this worktree.');
2797
+ console.log(' Lefthook not found. Install it:');
2798
+ console.log(' bun add -d lefthook (recommended)');
2799
+ console.log(' OR: bun add -g lefthook (global)');
2800
+ console.log(' Then run: bunx lefthook install');
2801
+ console.log(` Run ${PKG_MANAGER} install in this worktree, then rerun setup.`);
2802
+ }
2744
2803
 
2745
2804
  console.log('');
2746
2805
 
@@ -2749,6 +2808,48 @@ function installGitHooks() { // NOSONAR — Extracted as-is from bin/forge.js; c
2749
2808
  console.log(' You can install manually later with: lefthook install');
2750
2809
  console.log('');
2751
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
+ }
2752
2853
  }
2753
2854
 
2754
2855
  // Check if lefthook is already installed in project (delegates to lib/lefthook-check)
@@ -2785,136 +2886,42 @@ function repairDeclaredLefthookDependency(selectedAgents) {
2785
2886
  return { attempted: true, repaired: true };
2786
2887
  } catch (err) {
2787
2888
  console.warn('Lefthook install failed:', err.message);
2889
+ console.warn(' ⚠ Lefthook repair failed; raw git push remains unsafe in this worktree.');
2788
2890
  console.log(` ⚠ ${status.message}`);
2891
+ console.log(` Run ${PKG_MANAGER} install in this worktree, then rerun setup.`);
2789
2892
  console.log('');
2790
2893
  return { attempted: true, repaired: false, error: err };
2791
2894
  }
2792
2895
  }
2793
2896
 
2794
- // Check if Beads is installed (global, local, or bunx-capable)
2795
-
2796
-
2797
- // Check if Beads is installed (global, local, or bunx-capable)
2798
- function checkForBeads() {
2799
- // Try global install first
2800
- try {
2801
- secureExecFileSync('bd', ['version'], { stdio: 'ignore' });
2802
- return 'global';
2803
- } catch (err) {
2804
- // Not global
2805
- console.warn('Beads not found globally:', err.message);
2806
- }
2807
-
2808
- // Check if bunx can run it
2809
- try {
2810
- secureExecFileSync('bunx', ['@beads/bd', 'version'], { stdio: 'ignore' });
2811
- return 'bunx';
2812
- } catch (err) {
2813
- // Not bunx-capable
2814
- console.warn('Beads not available via bunx:', err.message);
2815
- }
2816
-
2817
- // Check local project installation
2818
- const pkgPath = path.join(projectRoot, 'package.json');
2819
- if (!fs.existsSync(pkgPath)) return null;
2820
-
2821
- try {
2822
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
2823
- const isInstalled = pkg.devDependencies?.['@beads/bd'] || pkg.dependencies?.['@beads/bd'];
2824
- return isInstalled ? 'local' : null;
2825
- } catch (err) {
2826
- console.warn('Failed to check Beads in package.json:', err.message);
2827
- return null;
2828
- }
2829
- }
2830
- // Check if Beads is initialized in project — delegates to lib/beads-setup
2831
-
2832
- // Check if Beads is initialized in project — delegates to lib/beads-setup
2833
- function isBeadsInitialized() {
2834
- return beadsSetupLib.isBeadsInitialized(projectRoot);
2835
- }
2836
-
2837
- // Initialize Beads in the project using the defensive safeBeadsInit wrapper
2838
- // Handles config/gitignore writes, hook snapshot/restore, and JSONL pre-seeding
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
- function initializeBeads(installType) {
2844
- console.log('Initializing Beads in project...');
2845
-
2846
- // Build the execBdInit function based on installType
2847
- const execBdInit = (root) => {
2848
- // SECURITY: execFileSync with hardcoded commands
2849
- if (installType === 'global') {
2850
- secureExecFileSync('bd', ['init'], { stdio: 'inherit', cwd: root });
2851
- } else if (installType === 'bunx') {
2852
- secureExecFileSync('bunx', ['@beads/bd', 'init'], { stdio: 'inherit', cwd: root });
2853
- } else if (installType === 'local') {
2854
- secureExecFileSync('npx', ['bd', 'init'], { stdio: 'inherit', cwd: root });
2855
- }
2856
- };
2857
-
2858
- // Derive prefix from package.json name or directory name
2859
- let prefix;
2860
- try {
2861
- const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
2862
- prefix = pkg.name || path.basename(projectRoot);
2863
- } catch (_e) { // NOSONAR — fallback to directory name if package.json unreadable
2864
- prefix = path.basename(projectRoot);
2865
- }
2866
-
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() {
2867
2906
  try {
2868
- const result = beadsSetupLib.safeBeadsInit(projectRoot, {
2869
- prefix,
2870
- execBdInit,
2871
- restoreLefthook: (root) => {
2872
- try {
2873
- secureExecFileSync('lefthook', ['install'], { stdio: 'ignore', cwd: root });
2874
- } catch (_e) { // NOSONAR lefthook may not be installed yet, non-fatal
2875
- // lefthook may not be installed yet — non-fatal
2876
- }
2877
- }
2878
- });
2879
-
2880
- if (result.skipped) {
2881
- console.log(' ✓ Beads already initialized');
2882
- return true;
2883
- }
2884
-
2885
- if (!result.success) {
2886
- for (const e of result.errors) {
2887
- console.log(` ⚠ ${e}`);
2888
- }
2889
- console.log(' Run manually: bd init');
2890
- return false;
2891
- }
2892
-
2893
- for (const w of result.warnings) {
2894
- console.warn(` ⚠ ${w}`);
2895
- }
2896
- console.log(' ✓ Beads initialized');
2897
-
2898
- // Run post-init health check (non-fatal)
2899
- try {
2900
- const health = beadsHealthCheck(projectRoot);
2901
- if (health.healthy) {
2902
- console.log(' ✓ Beads health check passed');
2903
- } else {
2904
- 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.
2905
2914
  }
2906
- } catch (_healthErr) { // NOSONAR — health check is best-effort, non-fatal
2907
- // Health check is best-effort — don't block setup
2908
2915
  }
2909
-
2916
+ KERNEL_STORE_READY = true;
2910
2917
  return true;
2911
2918
  } catch (err) {
2912
- console.log(' ⚠ Failed to initialize Beads:', err.message);
2913
- 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.`);
2914
2922
  return false;
2915
2923
  }
2916
2924
  }
2917
-
2918
2925
  // Check if Skills CLI is installed
2919
2926
 
2920
2927
 
@@ -2980,59 +2987,18 @@ function initializeSkills(installType) {
2980
2987
  }
2981
2988
  }
2982
2989
 
2983
- // Prompt for Beads setup - extracted to reduce cognitive complexity
2984
-
2985
-
2986
- // Prompt for Beads setup - extracted to reduce cognitive complexity
2987
- 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) {
2988
2993
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2989
- console.log('Beads Setup (Recommended)');
2994
+ console.log('Issue Store (Forge Kernel)');
2990
2995
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2991
2996
  console.log('');
2992
2997
 
2993
- const beadsInitialized = isBeadsInitialized();
2994
- const beadsStatus = checkForBeads();
2995
-
2996
- if (beadsInitialized) {
2997
- console.log('✓ Beads is already initialized in this project');
2998
- console.log('');
2999
- return;
3000
- }
3001
-
3002
- if (beadsStatus) {
3003
- // Already installed, just need to initialize
3004
- console.log(`ℹ Beads is installed (${beadsStatus}), but not initialized`);
3005
- const initBeads = await question('Initialize Beads in this project? (y/n): ');
3006
-
3007
- if (initBeads.toLowerCase() === 'y') {
3008
- initializeBeads(beadsStatus);
3009
- } else {
3010
- console.log('Skipped Beads initialization. Run manually: bd init');
3011
- }
3012
- console.log('');
3013
- return;
2998
+ const ready = await ensureKernelIssueStore();
2999
+ if (ready) {
3000
+ console.log('✓ Kernel issue store ready (single-machine; team sync not configured)');
3014
3001
  }
3015
-
3016
- // Not installed
3017
- console.log('ℹ Beads is not installed');
3018
- const installBeads = await question('Install Beads? (y/n): ');
3019
-
3020
- if (installBeads.toLowerCase() !== 'y') {
3021
- console.log('Skipped Beads installation');
3022
- console.log('');
3023
- return;
3024
- }
3025
-
3026
- console.log('');
3027
- console.log('Choose installation method:');
3028
- console.log(' 1. Global (recommended) - Available system-wide');
3029
- console.log(' 2. Local - Project-specific devDependency');
3030
- console.log(' 3. Bunx - Use via bunx (requires bun)');
3031
- console.log('');
3032
- const method = await question('Choose method (1-3): ');
3033
-
3034
- console.log('');
3035
- installBeadsWithMethod(method);
3036
3002
  console.log('');
3037
3003
  }
3038
3004
 
@@ -3052,72 +3018,6 @@ function installViaBunx(packageName, versionArgs, initFn, toolName) {
3052
3018
  }
3053
3019
  }
3054
3020
 
3055
- // Helper: Install Beads with chosen method - extracted to reduce cognitive complexity
3056
- // SECURITY NOTE: Downloads and executes a remote PowerShell script.
3057
- // The npm @beads/bd package is broken on Windows (GitHub Issue #1031, closed "not planned"),
3058
- // so the official PowerShell installer is the only supported path.
3059
- // Mitigations: HTTPS transport (prevents MITM), official beads repo, user-visible URL.
3060
- // Follow-up: pin to a versioned release tag once beads publishes tagged releases (for example v0.49.1).
3061
- const BEADS_INSTALL_PS1_URL = 'https://raw.githubusercontent.com/steveyegge/beads/main/install.ps1';
3062
-
3063
-
3064
-
3065
- function installBeadsOnWindows() {
3066
- console.log(' (Windows detected: using PowerShell installer)');
3067
- console.log(` Downloading: ${BEADS_INSTALL_PS1_URL}`);
3068
- secureExecFileSync('powershell.exe', [
3069
- '-NoProfile', '-NonInteractive', '-Command',
3070
- `irm ${BEADS_INSTALL_PS1_URL} | iex`
3071
- ], { stdio: 'inherit' });
3072
- }
3073
-
3074
-
3075
-
3076
- function installBeadsWithMethod(method) { // NOSONAR — Extracted as-is from bin/forge.js; complexity reduction deferred
3077
- try {
3078
- // SECURITY: secureExecFileSync with hardcoded commands
3079
- if (method === '1') {
3080
- console.log('Installing Beads globally...');
3081
- if (process.platform === 'win32') {
3082
- installBeadsOnWindows();
3083
- } else {
3084
- const pkgManager = PKG_MANAGER === 'bun' ? 'bun' : 'npm';
3085
- secureExecFileSync(pkgManager, ['install', '-g', '@beads/bd'], { stdio: 'inherit' });
3086
- }
3087
- console.log(' ✓ Beads installed globally');
3088
- initializeBeads('global');
3089
- } else if (method === '2') {
3090
- console.log('Installing Beads locally...');
3091
- // On Windows, npm postinstall for @beads/bd runs Expand-Archive which has EPERM file-locking
3092
- // (GitHub Issue #1031, closed "not planned") — same root cause as global install.
3093
- // Redirect Windows users to the global PowerShell installer instead.
3094
- if (process.platform === 'win32') {
3095
- console.log(' ⚠ Local install not supported on Windows (npm @beads/bd EPERM issue).');
3096
- console.log(' Falling back to global PowerShell installer...');
3097
- installBeadsOnWindows();
3098
- } else {
3099
- const pkgManager = PKG_MANAGER === 'bun' ? 'bun' : 'npm';
3100
- secureExecFileSync(pkgManager, ['install', '-D', '@beads/bd'], { stdio: 'inherit', cwd: projectRoot });
3101
- }
3102
- console.log(' ✓ Beads installed');
3103
- // On Windows the fallback was global (PowerShell installer), so init as 'global'
3104
- initializeBeads(process.platform === 'win32' ? 'global' : 'local');
3105
- } else if (method === '3') {
3106
- installViaBunx('@beads/bd', ['version'], initializeBeads, 'Beads');
3107
- } else {
3108
- console.log('Invalid choice. Skipping Beads installation.');
3109
- }
3110
- } catch (err) {
3111
- console.warn('Beads installation failed:', err.message);
3112
- console.log(' ⚠ Failed to install Beads:', err.message);
3113
- if (process.platform === 'win32') {
3114
- console.log(` Run manually: irm ${BEADS_INSTALL_PS1_URL} | iex`);
3115
- } else {
3116
- console.log(` Run manually: ${PKG_MANAGER === 'bun' ? 'bun add -g' : 'npm install -g'} @beads/bd && bd init`);
3117
- }
3118
- }
3119
- }
3120
-
3121
3021
  // Helper: Get package-manager-specific install args for Skills
3122
3022
 
3123
3023
 
@@ -3227,7 +3127,7 @@ async function setupProjectTools(rl, question) {
3227
3127
  console.log('');
3228
3128
  console.log('Forge recommends three tools for enhanced workflows:');
3229
3129
  console.log('');
3230
- console.log('• Beads - Git-backed issue tracking');
3130
+ console.log('• Issue tracking (Forge Kernel) - zero-install, git-backed');
3231
3131
  console.log(' Persists tasks across sessions, tracks dependencies.');
3232
3132
  console.log(' Command: forge ready, forge create, forge close');
3233
3133
  console.log('');
@@ -3241,44 +3141,6 @@ async function setupProjectTools(rl, question) {
3241
3141
  await promptSkillsSetup(question);
3242
3142
  }
3243
3143
 
3244
- // Auto-setup Beads in quick mode - extracted to reduce cognitive complexity
3245
-
3246
-
3247
- // Auto-setup Beads in quick mode - extracted to reduce cognitive complexity
3248
- function autoSetupBeadsInQuickMode() { // NOSONAR — Extracted as-is from bin/forge.js; complexity reduction deferred
3249
- const beadsStatus = checkForBeads();
3250
- const beadsInitialized = isBeadsInitialized();
3251
-
3252
- if (!beadsInitialized && beadsStatus) {
3253
- console.log('📦 Initializing Beads...');
3254
- initializeBeads(beadsStatus);
3255
- console.log('');
3256
- } else if (!beadsInitialized && !beadsStatus) {
3257
- console.log('📦 Installing Beads globally...');
3258
- try {
3259
- // SECURITY: use PowerShell on Windows (npm @beads/bd is broken on Windows - Issue #1031)
3260
- if (process.platform === 'win32') {
3261
- installBeadsOnWindows();
3262
- } else {
3263
- const pkgManager = PKG_MANAGER === 'bun' ? 'bun' : 'npm';
3264
- secureExecFileSync(pkgManager, ['install', '-g', '@beads/bd'], { stdio: 'inherit' });
3265
- }
3266
- console.log(' ✓ Beads installed globally');
3267
- initializeBeads('global');
3268
- } catch (err) {
3269
- // Installation failed - provide manual instructions
3270
- console.log(' ⚠ Could not install Beads automatically');
3271
- console.log(` Error: ${err.message}`);
3272
- if (process.platform === 'win32') {
3273
- console.log(` Run manually: irm ${BEADS_INSTALL_PS1_URL} | iex`);
3274
- } else {
3275
- console.log(` Run manually: ${PKG_MANAGER === 'bun' ? 'bun add -g' : 'npm install -g'} @beads/bd && bd init`);
3276
- }
3277
- }
3278
- console.log('');
3279
- }
3280
- }
3281
-
3282
3144
  // Helper: Auto-install lefthook if not present - extracted to reduce cognitive complexity
3283
3145
 
3284
3146
 
@@ -3314,12 +3176,24 @@ function autoInstallLefthook() { // NOSONAR — Extracted as-is from bin/forge.j
3314
3176
  console.log(' ✓ Lefthook binary restored');
3315
3177
  } catch (err) {
3316
3178
  console.warn('Lefthook install failed:', err.message);
3179
+ console.warn(' ⚠ Lefthook repair failed; raw git push remains unsafe in this worktree.');
3317
3180
  console.log(` ⚠ ${status.message}`);
3181
+ console.log(` Run ${PKG_MANAGER} install in this worktree, then rerun setup.`);
3318
3182
  }
3319
3183
  console.log('');
3320
3184
  return;
3321
3185
  }
3322
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
+
3323
3197
  // Not in package.json at all — full install
3324
3198
  console.log('📦 Installing lefthook for git hooks...');
3325
3199
  try {
@@ -3335,32 +3209,13 @@ function autoInstallLefthook() { // NOSONAR — Extracted as-is from bin/forge.j
3335
3209
  console.log('');
3336
3210
  }
3337
3211
 
3338
- // Helper: Verify a tool is callable after install - extracted to reduce cognitive complexity
3339
-
3340
-
3341
- // Helper: Verify a tool is callable after install - extracted to reduce cognitive complexity
3342
- function verifyToolInstall(command, args, toolName) {
3343
- try {
3344
- secureExecFileSync(command, args, { stdio: 'ignore' });
3345
- return true;
3346
- } catch (_err) { // NOSONAR - S2486: Intentionally ignored; verification failure is handled by caller
3347
- console.log(` ⚠ ${toolName} installed but not callable. Check your PATH.`);
3348
- return false;
3349
- }
3350
- }
3351
-
3352
3212
  // Helper: Auto-setup tools (Skills) in quick mode - extracted to reduce cognitive complexity
3353
3213
 
3354
3214
 
3355
3215
  // Helper: Auto-setup tools (Skills) in quick mode - extracted to reduce cognitive complexity
3356
- function autoSetupToolsInQuickMode() {
3357
- // Beads: auto-install or initialize
3358
- autoSetupBeadsInQuickMode();
3359
-
3360
- // Post-install verification for Beads
3361
- if (isBeadsInitialized()) {
3362
- verifyToolInstall('bd', ['version'], 'Beads');
3363
- }
3216
+ async function autoSetupToolsInQuickMode() {
3217
+ // Issue store: ensure the Forge Kernel store exists (auto-provisioned, no CLI)
3218
+ await ensureKernelIssueStore();
3364
3219
 
3365
3220
  // Skills: only initialize if already installed (recommended tool)
3366
3221
  const skillsStatus = checkForSkills();
@@ -3368,11 +3223,10 @@ function autoSetupToolsInQuickMode() {
3368
3223
  console.log('📦 Initializing Skills...');
3369
3224
  initializeSkills(skillsStatus);
3370
3225
  console.log('');
3371
- } else if (!skillsStatus) {
3372
- const installCmd = PKG_MANAGER === 'bun' ? 'bun add -g' : 'npm install -g';
3373
- console.log(` ℹ Skills not found — install with: ${installCmd} @forge/skills`);
3374
- console.log('');
3375
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).
3376
3230
  }
3377
3231
 
3378
3232
  // Helper: Configure default external services in quick mode - extracted to reduce cognitive complexity
@@ -3404,6 +3258,73 @@ function configureDefaultExternalServices(skipExternal) {
3404
3258
  console.log('Configuration saved to .env.local');
3405
3259
  }
3406
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
+
3407
3328
  // Quick setup with defaults
3408
3329
 
3409
3330
 
@@ -3422,8 +3343,11 @@ async function quickSetup(selectedAgents, skipExternal) {
3422
3343
  });
3423
3344
  console.log('');
3424
3345
 
3425
- // Copy AGENTS.md (actionLog tracks it via copyFile)
3426
- 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();
3427
3351
  copyFile(agentsSrc, 'AGENTS.md');
3428
3352
  console.log('');
3429
3353
 
@@ -3436,20 +3360,26 @@ async function quickSetup(selectedAgents, skipExternal) {
3436
3360
  // Auto-install lefthook if missing
3437
3361
  autoInstallLefthook();
3438
3362
 
3439
- // Auto-setup project tools (Beads, Skills)
3440
- autoSetupToolsInQuickMode();
3363
+ // Auto-setup project tools (Kernel issue store, Skills)
3364
+ await autoSetupToolsInQuickMode();
3441
3365
 
3442
- // Load canonical commands and setup agents (reuse existing helpers)
3443
- const claudeCommands = await loadAndSetupCanonicalCommands(selectedAgents);
3444
- 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);
3445
3374
  ensureWorkflowRuntimeAssets(selectedAgents);
3446
3375
 
3447
3376
  // Detect Husky and migrate before installing Lefthook hooks
3448
3377
  await handleHuskyMigration();
3449
3378
 
3450
- // 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.
3451
3381
  console.log('');
3452
- installGitHooks();
3382
+ installGitHooks({ loud: true });
3453
3383
 
3454
3384
  // Configure external services with defaults (unless skipped)
3455
3385
  configureDefaultExternalServices(skipExternal);
@@ -3463,6 +3393,9 @@ async function quickSetup(selectedAgents, skipExternal) {
3463
3393
  console.log('');
3464
3394
  console.log(renderSetupSummary(actionLog, selectedAgents, VERBOSE_MODE, { status: getSetupSummaryStatus() }));
3465
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 });
3466
3399
  console.log('');
3467
3400
  }
3468
3401
 
@@ -3503,7 +3436,7 @@ function setupAgentsMdFile(flags, skipFiles) {
3503
3436
  return;
3504
3437
  }
3505
3438
 
3506
- const agentsSrc = path.join(packageDir, 'AGENTS.md');
3439
+ const agentsSrc = path.join(getPackageRoot(packageDir), 'AGENTS.md');
3507
3440
  const agentsDest = path.join(projectRoot, 'AGENTS.md');
3508
3441
  const mergeStrategy = flags.merge || 'smart';
3509
3442
 
@@ -3583,7 +3516,6 @@ function displayExistingInstallation(projectStatus) {
3583
3516
  : 'Found partial installation:');
3584
3517
 
3585
3518
  if (projectStatus.hasAgentsMd) console.log(' - AGENTS.md');
3586
- if (projectStatus.hasClaudeCommands) console.log(' - .claude/commands/');
3587
3519
  if (projectStatus.hasEnvLocal) console.log(' - .env.local');
3588
3520
  console.log('');
3589
3521
  }
@@ -3595,7 +3527,6 @@ function displayExistingInstallation(projectStatus) {
3595
3527
  async function promptForOverwriteDecisions(question, projectStatus, flags = {}) {
3596
3528
  const skipFiles = {
3597
3529
  agentsMd: false,
3598
- claudeCommands: false
3599
3530
  };
3600
3531
 
3601
3532
  if (flags.keep) {
@@ -3603,10 +3534,6 @@ async function promptForOverwriteDecisions(question, projectStatus, flags = {})
3603
3534
  skipFiles.agentsMd = true;
3604
3535
  console.log(' Keeping existing AGENTS.md (--keep)');
3605
3536
  }
3606
- if (projectStatus.hasClaudeCommands) {
3607
- skipFiles.claudeCommands = true;
3608
- console.log(' Keeping existing .claude/commands/ (--keep)');
3609
- }
3610
3537
  return skipFiles;
3611
3538
  }
3612
3539
 
@@ -3616,12 +3543,6 @@ async function promptForOverwriteDecisions(question, projectStatus, flags = {})
3616
3543
  console.log(overwriteAgents ? ' Will overwrite AGENTS.md' : ' Keeping existing AGENTS.md');
3617
3544
  }
3618
3545
 
3619
- if (projectStatus.hasClaudeCommands) {
3620
- const overwriteCommands = await askYesNo(question, 'Found existing .claude/commands/. Overwrite?', true);
3621
- skipFiles.claudeCommands = !overwriteCommands;
3622
- console.log(overwriteCommands ? ' Will overwrite .claude/commands/' : ' Keeping existing .claude/commands/');
3623
- }
3624
-
3625
3546
  if (projectStatus.type !== 'fresh') {
3626
3547
  console.log('');
3627
3548
  }
@@ -3629,47 +3550,17 @@ async function promptForOverwriteDecisions(question, projectStatus, flags = {})
3629
3550
  return skipFiles;
3630
3551
  }
3631
3552
 
3632
- // Helper: Load and setup canonical commands - extracted to reduce cognitive complexity
3633
-
3634
-
3635
- // Helper: Load and setup canonical commands - extracted to reduce cognitive complexity
3636
- async function loadAndSetupCanonicalCommands(selectedAgents, skipFiles) {
3637
- const claudeCommands = {};
3638
- const needsClaudeCommands = selectedAgents.includes('claude') ||
3639
- selectedAgents.some(a => AGENTS[a].needsConversion || AGENTS[a].copyCommands);
3640
-
3641
- if (!needsClaudeCommands) {
3642
- return claudeCommands;
3643
- }
3644
-
3645
- // First ensure Claude is set up
3646
- if (selectedAgents.includes('claude')) {
3647
- await setupAgent('claude', null, skipFiles);
3648
- }
3649
-
3650
- // Then load the commands (from existing or newly created)
3651
- getWorkflowCommands().forEach(cmd => {
3652
- const cmdPath = path.join(projectRoot, `.claude/commands/${cmd}.md`);
3653
- const content = readFile(cmdPath);
3654
- if (content) {
3655
- claudeCommands[`${cmd}.md`] = content;
3656
- }
3657
- });
3658
-
3659
- return claudeCommands;
3660
- }
3661
-
3662
3553
  // Helper: Setup all selected agents - extracted to reduce cognitive complexity
3663
3554
 
3664
3555
 
3665
3556
  // Helper: Setup all selected agents - extracted to reduce cognitive complexity
3666
- async function setupSelectedAgents(selectedAgents, claudeCommands, skipFiles) {
3557
+ async function setupSelectedAgents(selectedAgents, skipFiles) {
3667
3558
  const totalAgents = selectedAgents.length;
3668
3559
  for (const [index, agentKey] of selectedAgents.entries()) {
3669
3560
  const agent = AGENTS[agentKey];
3670
3561
  console.log(`\n[${index + 1}/${totalAgents}] Setting up ${agent.name}...`);
3671
3562
  if (agentKey !== 'claude') { // Claude already done above
3672
- await setupAgent(agentKey, claudeCommands, skipFiles);
3563
+ await setupAgent(agentKey, skipFiles);
3673
3564
  }
3674
3565
  }
3675
3566
 
@@ -3769,7 +3660,7 @@ async function interactiveSetupWithFlags(flags) {
3769
3660
  const selectedAgents = await promptForAgentSelection(question, agentKeys);
3770
3661
 
3771
3662
  // Check GitHub CLI prerequisite now that selectedAgents is known
3772
- if (requiresGithubCliForSetup(selectedAgents, { syncEnabled: SYNC_ENABLED })) {
3663
+ if (requiresGithubCliForSetup(selectedAgents)) {
3773
3664
  checkPrerequisites({ requireGithubCli: true });
3774
3665
  }
3775
3666
 
@@ -3784,11 +3675,13 @@ async function interactiveSetupWithFlags(flags) {
3784
3675
  setupCoreDocs();
3785
3676
  console.log('');
3786
3677
 
3787
- // Load Claude commands if needed (delegated to helper)
3788
- 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
+ }
3789
3682
 
3790
3683
  // Setup each selected agent with progress indication (delegated to helper)
3791
- await setupSelectedAgents(selectedAgents, claudeCommands, skipFiles);
3684
+ await setupSelectedAgents(selectedAgents, skipFiles);
3792
3685
  ensureWorkflowRuntimeAssets(selectedAgents);
3793
3686
 
3794
3687
  // Handle external services step (delegated to helper)
@@ -3799,6 +3692,10 @@ async function interactiveSetupWithFlags(flags) {
3799
3692
 
3800
3693
  // Display final summary (delegated to helper)
3801
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();
3802
3699
  }
3803
3700
 
3804
3701
  // Main
@@ -3897,15 +3794,10 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3897
3794
  addFileAction(dir + '/', 'Create agent directory');
3898
3795
  }
3899
3796
 
3900
- // 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.
3901
3799
  if (agentKey === 'claude') {
3902
- const cmds = getWorkflowCommands();
3903
- for (const cmd of cmds) {
3904
- addFileAction(`.claude/commands/${cmd}.md`, 'Workflow command');
3905
- }
3906
- addFileAction('.claude/rules/workflow.md', 'Workflow rules');
3907
3800
  addFileAction('.claude/scripts/load-env.sh', 'Environment loader script');
3908
- addFileAction('.claude/skills/forge-workflow/SKILL.md', 'Forge workflow skill');
3909
3801
  addFileAction('.mcp.json', 'MCP server configuration');
3910
3802
  addFileAction('CLAUDE.md', 'Claude root config (links to AGENTS.md)');
3911
3803
  }
@@ -3916,7 +3808,7 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3916
3808
  }
3917
3809
  }
3918
3810
 
3919
- // Cursor-specific files
3811
+ // Cursor-specific files (rendered from the canonical rules/ source)
3920
3812
  if (agent.customSetup === 'cursor') {
3921
3813
  addFileAction('.cursor/rules/forge-workflow.mdc', 'Cursor workflow rule');
3922
3814
  addFileAction('.cursor/rules/tdd-enforcement.mdc', 'Cursor TDD rule');
@@ -3924,54 +3816,19 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3924
3816
  addFileAction('.cursor/rules/documentation.mdc', 'Cursor documentation rule');
3925
3817
  }
3926
3818
 
3927
- if (agentKey === 'kilocode') {
3928
- addFileAction('.kilocode/workflows/forge-workflow.md', 'Kilo native workflow');
3929
- addFileAction('.kilocode/rules/workflow.md', 'Kilo native rules');
3930
- addFileAction('.kilocode/skills/forge-workflow/SKILL.md', 'Kilo native skill');
3931
- }
3932
-
3933
- if (agent.customSetup === 'copilot') {
3934
- addFileAction('.github/copilot-instructions.md', 'Copilot root instructions');
3935
- addFileAction('.github/instructions/typescript.instructions.md', 'Copilot TypeScript instructions');
3936
- addFileAction('.github/instructions/testing.instructions.md', 'Copilot testing instructions');
3937
- addFileAction('.github/prompts/red.prompt.md', 'Copilot RED prompt');
3938
- addFileAction('.github/prompts/green.prompt.md', 'Copilot GREEN prompt');
3939
- }
3940
-
3941
- if (agent.customSetup === 'opencode') {
3942
- addFileAction('opencode.json', 'OpenCode root config');
3943
- addFileAction('.opencode/agents/plan-review.md', 'OpenCode plan-review agent');
3944
- addFileAction('.opencode/agents/tdd-build.md', 'OpenCode tdd-build agent');
3945
- }
3946
-
3947
- // Agent commands (converted from Claude format)
3948
- if (agent.needsConversion || agent.copyCommands || agent.promptFormat) {
3949
- const cmds = getWorkflowCommands();
3950
- const targetDir = agent.dirs[0];
3951
- for (const cmd of cmds) {
3952
- const ext = agent.promptFormat ? '.prompt.md' : '.md';
3953
- addFileAction(`${targetDir}/${cmd}${ext}`, 'Converted workflow command');
3954
- }
3955
- }
3956
-
3957
- // Agent rules (copied from Claude)
3958
- if (agent.needsConversion) {
3959
- const rulesDir = agent.dirs.find(d => d.includes('/rules'));
3960
- if (rulesDir) {
3961
- addFileAction(`${rulesDir}/workflow.md`, 'Workflow rules');
3962
- }
3963
- }
3964
-
3965
3819
  // Agent skill
3966
3820
  if (agentKey === 'codex') {
3967
- const skillEntries = buildCodexSkillInstallPlan(packageDir, { env: process.env, homeDir: os.homedir() });
3821
+ const skillEntries = buildCodexSkillInstallPlan(getPackageRoot(packageDir), { env: process.env, homeDir: os.homedir() });
3968
3822
  for (const entry of skillEntries) {
3969
3823
  addFileAction(entry.displayPath, 'Codex stage skill');
3970
3824
  }
3971
- } else if (agent.hasSkill) {
3972
- const skillDir = agent.dirs.find(d => d.includes('/skills/'));
3973
- if (skillDir) {
3974
- 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
+ }
3975
3832
  }
3976
3833
  }
3977
3834
 
@@ -3984,6 +3841,7 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3984
3841
  // Git hooks
3985
3842
  addFileAction('lefthook.yml', 'Git hook configuration');
3986
3843
  addFileAction('.forge/hooks/check-tdd.js', 'TDD enforcement hook');
3844
+ addFileAction('.forge/hooks/forge-native-hook.js', 'Native-hook enforcement adapter (Claude/Cursor)');
3987
3845
 
3988
3846
  // Print dry-run summary
3989
3847
  console.log('');
@@ -4006,7 +3864,7 @@ async function executeSetup(config) {
4006
3864
  // Check prerequisites
4007
3865
  checkPrerequisites({
4008
3866
  requireBeadsCli: true,
4009
- requireGithubCli: requiresGithubCliForSetup(agents, { syncEnabled: SYNC_ENABLED }),
3867
+ requireGithubCli: requiresGithubCliForSetup(agents),
4010
3868
  requireJq: true,
4011
3869
  commandRunner,
4012
3870
  });
@@ -4017,7 +3875,7 @@ async function executeSetup(config) {
4017
3875
  if (fs.existsSync(agentsDest)) {
4018
3876
  actionLog.add('AGENTS.md', 'skipped', 'already exists');
4019
3877
  } else {
4020
- const agentsSrc = path.join(packageDir, 'AGENTS.md');
3878
+ const agentsSrc = path.join(getPackageRoot(packageDir), 'AGENTS.md');
4021
3879
  copyFile(agentsSrc, 'AGENTS.md');
4022
3880
  }
4023
3881
  console.log('');
@@ -4028,22 +3886,13 @@ async function executeSetup(config) {
4028
3886
 
4029
3887
  const skipFiles = {
4030
3888
  agentsMd: keepExisting && fs.existsSync(path.join(projectRoot, 'AGENTS.md')),
4031
- claudeCommands: keepExisting && fs.existsSync(path.join(projectRoot, '.claude', 'commands'))
4032
3889
  };
4033
3890
 
4034
- if (skipFiles.claudeCommands) {
4035
- 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);
4036
3894
  }
4037
-
4038
- // Load canonical commands — use loadAndSetupCanonicalCommands when claude is selected
4039
- // so that .claude/commands/ are seeded before reading them
4040
- const claudeCommands = agents.includes('claude')
4041
- ? await loadAndSetupCanonicalCommands(agents, skipFiles)
4042
- : loadClaudeCommands(agents);
4043
-
4044
- // Setup agents with progress output (setupSelectedAgents skips claude internally
4045
- // since loadAndSetupCanonicalCommands already handled it above)
4046
- await setupSelectedAgents(agents, claudeCommands, skipFiles);
3895
+ await setupSelectedAgents(agents, skipFiles);
4047
3896
  ensureWorkflowRuntimeAssets(agents);
4048
3897
  ensureWorkflowShellPolicy(agents);
4049
3898
  repairDeclaredLefthookDependency(agents);
@@ -4051,9 +3900,13 @@ async function executeSetup(config) {
4051
3900
  // Detect Husky and migrate before installing Lefthook hooks
4052
3901
  await handleHuskyMigration();
4053
3902
 
4054
- // 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.
4055
3905
  console.log('');
4056
- installGitHooks();
3906
+ installGitHooks({ loud: true });
3907
+
3908
+ // Auto-import an existing Beads store into the Kernel (idempotent, CLI-free)
3909
+ await autoMigrateBeadsToKernel();
4057
3910
 
4058
3911
  // External services (unless skipped)
4059
3912
  await handleExternalServices(skipExternal, agents);
@@ -4067,6 +3920,9 @@ async function executeSetup(config) {
4067
3920
  console.log('');
4068
3921
  console.log(renderSetupSummary(actionLog, agents, VERBOSE_MODE, { status: getSetupSummaryStatus() }));
4069
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 });
4070
3926
  console.log('');
4071
3927
  }
4072
3928
 
@@ -4076,36 +3932,12 @@ async function executeSetup(config) {
4076
3932
  // Helper: Scaffold Beads GitHub sync when --sync flag is provided
4077
3933
  async function handleSyncScaffold() {
4078
3934
  console.log('');
4079
- console.log('Scaffolding Beads GitHub sync workflows (--sync)...');
3935
+ console.log('Beads GitHub sync scaffolding is deprecated (--sync).');
4080
3936
  try {
4081
- // Scaffold sync files using the new lib module
4082
- const result = scaffoldBeadsSync(projectRoot, packageDir);
4083
- for (const f of (result.filesCreated || [])) {
4084
- console.log(` Created: ${f}`);
4085
- }
4086
- for (const f of (result.filesSkipped || [])) {
4087
- console.log(` Skipped: ${f} (already exists)`);
4088
- }
4089
-
4090
- // Detect default branch and Beads version, then template workflows
4091
- const branch = detectDefaultBranch(projectRoot);
4092
- const beadsVersion = detectBeadsVersion();
4093
- const workflowDir = path.join(projectRoot, '.github', 'workflows');
4094
- templateWorkflows(workflowDir, branch, beadsVersion, result.filesCreated || []);
4095
- console.log(` Branch: ${branch}, Beads version: ${beadsVersion}`);
4096
-
4097
- // PAT setup: interactive when possible, reminder otherwise
4098
- try {
4099
- const patResult = setupPAT(projectRoot, { interactive: !NON_INTERACTIVE });
4100
- if (patResult.success) {
4101
- console.log(' PAT configured for Beads sync');
4102
- } else if (patResult.reminder) {
4103
- console.log(` ${patResult.reminder}`);
4104
- } else if (patResult.instructions) {
4105
- console.log(` ${patResult.instructions.split('\n')[0]}`);
4106
- }
4107
- } catch (_patErr) { // NOSONAR — best-effort PAT setup, non-fatal
4108
- // 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}`);
4109
3941
  }
4110
3942
  } catch (err) {
4111
3943
  console.error(` Error scaffolding GitHub-Beads sync: ${err.message}`);
@@ -4118,7 +3950,7 @@ async function handleSyncScaffold() {
4118
3950
  // Helper: Handle setup command in non-quick mode
4119
3951
  async function handleSetupCommand(selectedAgents, flags) {
4120
3952
  if (!Array.isArray(selectedAgents) || selectedAgents.length === 0) {
4121
- return interactiveSetupWithFlags(flags);
3953
+ return runInteractiveSetupFallback(flags);
4122
3954
  }
4123
3955
 
4124
3956
  // Allow callers (e.g. reinstall) to override projectRoot without process.chdir()
@@ -4138,6 +3970,10 @@ async function handleSetupCommand(selectedAgents, flags) {
4138
3970
  }
4139
3971
  }
4140
3972
 
3973
+ async function runInteractiveSetupFallback(flags, interactiveSetup = interactiveSetupWithFlags) {
3974
+ return interactiveSetup(flags);
3975
+ }
3976
+
4141
3977
  // Helper: Handle external services configuration
4142
3978
 
4143
3979
 
@@ -4173,7 +4009,7 @@ async function handleExternalServices(skipExternal, selectedAgents) {
4173
4009
  * Detect which agents are already configured in a project directory.
4174
4010
  * Checks for the presence of each agent's configured directories/files.
4175
4011
  * Returns the external-facing setup IDs used by the setup UX, including
4176
- * legacy aliases such as `claude-code`, `github-copilot`, and `roo-code`.
4012
+ * legacy aliases such as `claude-code`.
4177
4013
  * Callers that need raw plugin IDs for internal lookup must normalize first.
4178
4014
  *
4179
4015
  * @param {string} dir - Project directory to scan
@@ -4184,8 +4020,6 @@ function detectConfiguredAgents(dir) {
4184
4020
  const detected = [];
4185
4021
  const legacyAgentIds = {
4186
4022
  claude: 'claude-code',
4187
- copilot: 'github-copilot',
4188
- roo: 'roo-code',
4189
4023
  };
4190
4024
 
4191
4025
  pluginManager.getAllPlugins().forEach((plugin, id) => {
@@ -4207,7 +4041,7 @@ function detectConfiguredAgents(dir) {
4207
4041
  * Used during setup --clean or reset flows.
4208
4042
  *
4209
4043
  * @param {string} dir - Project directory
4210
- * @param {string} agentName - Agent slug (e.g. 'cursor', 'cline')
4044
+ * @param {string} agentName - Agent slug (e.g. 'cursor', 'codex')
4211
4045
  * @param {object} [manifest] - Optional sync manifest with file paths to remove
4212
4046
  * @returns {{ removed: string[], errors: string[] }}
4213
4047
  */
@@ -4267,6 +4101,9 @@ const SETUP_FLAG_DEFAULTS = Object.freeze({
4267
4101
  verbose: false,
4268
4102
  dryRun: false,
4269
4103
  quick: false,
4104
+ minimal: false,
4105
+ standard: false,
4106
+ full: false,
4270
4107
  skipExternal: false,
4271
4108
  sync: false,
4272
4109
  symlink: false,
@@ -4281,6 +4118,9 @@ const SIMPLE_SETUP_FLAG_UPDATES = Object.freeze({
4281
4118
  '--verbose': { verbose: true },
4282
4119
  '--dry-run': { dryRun: true },
4283
4120
  '--quick': { quick: true },
4121
+ '--minimal': { minimal: true },
4122
+ '--standard': { standard: true },
4123
+ '--full': { full: true },
4284
4124
  '--skip-external': { skipExternal: true },
4285
4125
  '--sync': { sync: true },
4286
4126
  '--symlink': { symlink: true },
@@ -4358,6 +4198,9 @@ function mergeSetupFlags(flags, argv) {
4358
4198
  verbose: Boolean(flags.verbose || setupFlags.verbose),
4359
4199
  dryRun: Boolean(flags.dryRun || setupFlags.dryRun),
4360
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),
4361
4204
  skipExternal: Boolean(flags.skipExternal || setupFlags.skipExternal),
4362
4205
  sync: Boolean(flags.sync || setupFlags.sync),
4363
4206
  symlink: Boolean(flags.symlink || setupFlags.symlink),
@@ -4368,9 +4211,6 @@ function mergeSetupFlags(flags, argv) {
4368
4211
  function normalizeDetectedAgent(agentName) {
4369
4212
  const aliases = {
4370
4213
  'claude-code': 'claude',
4371
- 'github-copilot': 'copilot',
4372
- 'kilo-code': 'kilocode',
4373
- 'roo-code': 'roo',
4374
4214
  };
4375
4215
  return aliases[agentName] || agentName;
4376
4216
  }
@@ -4414,6 +4254,22 @@ module.exports = {
4414
4254
  resetSetupNotes();
4415
4255
  PKG_MANAGER = detectPackageManager();
4416
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
+
4417
4273
  // Determine agents to install
4418
4274
  let selectedAgents = determineSelectedAgents(flags);
4419
4275
 
@@ -4448,12 +4304,17 @@ module.exports = {
4448
4304
  return { success: true };
4449
4305
  }
4450
4306
 
4307
+ if (flags.sync && selectedAgents.length === 0) {
4308
+ await handleSyncScaffold();
4309
+ return { success: true };
4310
+ }
4311
+
4451
4312
  if (selectedAgents.length > 0) {
4452
4313
  await handleSetupCommand(selectedAgents, flags);
4453
4314
  return { success: true };
4454
4315
  }
4455
4316
 
4456
- await interactiveSetupWithFlags(flags);
4317
+ await runInteractiveSetupFallback(flags);
4457
4318
  return { success: true };
4458
4319
  },
4459
4320
 
@@ -4461,9 +4322,13 @@ module.exports = {
4461
4322
  checkPrerequisites,
4462
4323
  setupCoreDocs,
4463
4324
  displaySetupSummary,
4325
+ printForgeInitNextStep,
4326
+ finalizeWorkflowConfig,
4327
+ backupMarkerlessAgentsMd,
4464
4328
  setupAgent,
4465
4329
  quickSetup,
4466
4330
  interactiveSetupWithFlags,
4331
+ _runInteractiveSetupFallback: runInteractiveSetupFallback,
4467
4332
  dryRunSetup,
4468
4333
  handleSetupCommand,
4469
4334
  executeSetup,
@@ -4471,29 +4336,36 @@ module.exports = {
4471
4336
  _interactiveSetup,
4472
4337
  configureExternalServices,
4473
4338
  configureDefaultExternalServices,
4474
- installBeadsWithMethod,
4475
4339
  installSkillsWithMethod,
4476
4340
  installViaBunx,
4477
4341
  autoInstallLefthook,
4478
4342
  autoSetupToolsInQuickMode,
4343
+ autoMigrateBeadsToKernel,
4344
+ ensureGitHooksInstalled,
4345
+ forgeShouldWriteLefthookConfig,
4346
+ FORGE_USER_LEFTHOOK_YML,
4479
4347
  setupClaudeMcpConfig,
4348
+ setupCursorMcpConfig,
4349
+ setupClaudePermissions,
4350
+ setupCursorIgnore,
4351
+ setupClaudeHooksConfig,
4352
+ setupCursorHooksConfig,
4480
4353
  displayMcpStatus,
4481
4354
  displayEnvTokenResults,
4482
4355
  minimalInstall,
4483
4356
  determineSelectedAgents,
4484
4357
  handlePathSetup,
4485
- loadAndSetupCanonicalCommands,
4486
4358
  detectConfiguredAgents,
4487
4359
  removeAgentFiles,
4488
4360
  parseSetupFlags,
4489
4361
  mergeSetupFlags,
4490
- getWorkflowCommands,
4491
4362
  getWorkflowRuntimeAssets,
4492
4363
  findMissingWorkflowRuntimeAssets,
4493
4364
  ensureWorkflowShellPolicy,
4494
4365
  repairWorkflowRuntimeAssets,
4495
4366
  repairRuntimeReadiness,
4496
4367
  _showBanner: showBanner,
4368
+ backupAndRemoveLegacyCursorRules,
4497
4369
 
4498
4370
  // State accessors for testing
4499
4371
  _getState: () => ({ projectRoot, FORCE_MODE, VERBOSE_MODE, NON_INTERACTIVE, SYMLINK_ONLY, SYNC_ENABLED, PKG_MANAGER }),