forge-workflow 0.0.10 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +461 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +17 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +0 -1
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +838 -972
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +382 -11
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. package/scripts/sync-commands.js +0 -600
@@ -9,8 +9,12 @@ const {
9
9
  } = require('../workflow/state');
10
10
  const { STAGE_IDS } = require('../workflow/stages');
11
11
  const { detectWorktree } = require('../detect-worktree');
12
- const { readBeadsSnapshot } = require('../status/beads-snapshot');
13
- const { formatZeroArgStatus } = require('../status/presenter');
12
+ const { readStatusSnapshot } = require('../status/snapshot');
13
+ const {
14
+ buildPersonalStatusJson,
15
+ formatRunNextLines,
16
+ formatZeroArgStatus,
17
+ } = require('../status/presenter');
14
18
  const {
15
19
  loadState,
16
20
  extractWorkflowStateFromComments,
@@ -18,6 +22,12 @@ const {
18
22
  } = require('../workflow/state-manager');
19
23
  const { secureExecFileSync } = require('../shell-utils.js');
20
24
 
25
+ // Lifecycle progression used for status display. Stage 8 'Merge' is the merge
26
+ // EVENT (a human GitHub action — Forge exposes no `merge` command) that lands after
27
+ // the review stage and before post-merge /verify. Pre-merge doc/CI/handoff checks
28
+ // are an embedded gate inside /ship and /review, not a standalone stage, so this
29
+ // ladder never gains a premerge stage. (AUTHORITATIVE_STAGE_NAMES below still maps
30
+ // the legacy 'premerge' id purely so historical workflow state round-trips.)
21
31
  const WORKFLOW_STAGES = {
22
32
  1: { name: 'Fresh Start', nextCommand: 'research' },
23
33
  2: { name: 'Research', nextCommand: 'research' },
@@ -30,6 +40,10 @@ const WORKFLOW_STAGES = {
30
40
  9: { name: 'Verification', nextCommand: 'verify' },
31
41
  };
32
42
 
43
+ // Legacy read-path name map. `premerge` is retired as a standalone stage (it is now
44
+ // an embedded gate in /ship and /review) but is intentionally KEPT here so a stored
45
+ // `currentStage: 'premerge'` from a schemaVersion 1 workflow still resolves a name
46
+ // on read — deleting it would break upgraders (see test/status-command.test.js).
33
47
  const AUTHORITATIVE_STAGE_NAMES = {
34
48
  plan: 'Planning',
35
49
  dev: 'Development',
@@ -329,10 +343,15 @@ function detectStage(context) {
329
343
 
330
344
  function parseStatusInputs(args = [], flags = {}) {
331
345
  const statusArgs = Array.isArray(args) ? args : [];
346
+ const firstDefined = (...values) => values.find(value => value !== undefined && value !== null);
332
347
  const getNextValue = (flagName) => {
333
348
  const index = statusArgs.indexOf(flagName);
334
349
  if (index !== -1 && index + 1 < statusArgs.length) {
335
- return statusArgs[index + 1];
350
+ const candidate = statusArgs[index + 1];
351
+ if (typeof candidate === 'string' && candidate.startsWith('--')) {
352
+ return null;
353
+ }
354
+ return candidate;
336
355
  }
337
356
  return null;
338
357
  };
@@ -343,10 +362,23 @@ function parseStatusInputs(args = [], flags = {}) {
343
362
  };
344
363
 
345
364
  return {
346
- issueId: flags.issueId || flags['--issue-id'] || getInlineValue('--issue-id') || getNextValue('--issue-id'),
347
- workflowState: flags.workflowState || flags['--workflow-state'] || getInlineValue('--workflow-state') || getNextValue('--workflow-state'),
348
- bdComments: flags.bdComments || flags['--bd-comments'] || getInlineValue('--bd-comments') || getNextValue('--bd-comments'),
349
- projectRoot: flags.projectRoot || flags['--project-root'] || getInlineValue('--project-root') || getNextValue('--project-root') || null,
365
+ issueId: firstDefined(flags.issueId, flags['--issue-id'], getInlineValue('--issue-id'), getNextValue('--issue-id')),
366
+ issueBackend: firstDefined(flags.issueBackend, flags['--issue-backend'], getInlineValue('--issue-backend'), getNextValue('--issue-backend')) || null,
367
+ workflowState: firstDefined(flags.workflowState, flags['--workflow-state'], getInlineValue('--workflow-state'), getNextValue('--workflow-state')),
368
+ bdComments: firstDefined(flags.bdComments, flags['--bd-comments'], getInlineValue('--bd-comments'), getNextValue('--bd-comments')),
369
+ projectRoot: firstDefined(flags.projectRoot, flags['--project-root'], getInlineValue('--project-root'), getNextValue('--project-root')) || null,
370
+ json: flags.json === true || flags['--json'] === true || statusArgs.includes('--json'),
371
+ full: flags.full === true || flags['--full'] === true || statusArgs.includes('--full'),
372
+ now: firstDefined(flags.now, flags['--now'], getInlineValue('--now'), getNextValue('--now')),
373
+ staleAfterDays: firstDefined(
374
+ flags.staleAfterDays,
375
+ flags['--stale-after-days'],
376
+ flags['--staleAfterDays'],
377
+ getInlineValue('--stale-after-days'),
378
+ getInlineValue('--staleAfterDays'),
379
+ getNextValue('--stale-after-days'),
380
+ getNextValue('--staleAfterDays')
381
+ ),
350
382
  };
351
383
  }
352
384
 
@@ -408,17 +440,104 @@ function readIssueDetails(issueId, projectRoot = process.cwd()) {
408
440
  }
409
441
  }
410
442
 
443
+ function stripDatePrefix(value) {
444
+ const datePrefixLength = 11;
445
+ const datePrefix =
446
+ value.length > datePrefixLength &&
447
+ value[4] === '-' &&
448
+ value[7] === '-' &&
449
+ value[10] === '-';
450
+ return datePrefix ? value.slice(datePrefixLength) : value;
451
+ }
452
+
453
+ function trimTrailingPathPunctuation(value) {
454
+ const trailingPunctuation = '.,;:';
455
+ let end = value.length;
456
+ while (end > 0 && trailingPunctuation.includes(value[end - 1])) {
457
+ end -= 1;
458
+ }
459
+ return value.slice(0, end);
460
+ }
461
+
462
+ function stripMarkdownSuffixes(value) {
463
+ let stripped = value.endsWith('.md') ? value.slice(0, -3) : value;
464
+ for (const suffix of ['-design', '-plan', '-tasks', '-decisions']) {
465
+ if (stripped.endsWith(suffix)) {
466
+ stripped = stripped.slice(0, -suffix.length);
467
+ break;
468
+ }
469
+ }
470
+ return stripped;
471
+ }
472
+
473
+ function extractSlugFromDesignPath(candidatePath) {
474
+ const parts = trimTrailingPathPunctuation(candidatePath).replaceAll('\\', '/').split('/').filter(Boolean);
475
+ const docsIndex = parts.indexOf('docs');
476
+ if (docsIndex === -1) {
477
+ return '';
478
+ }
479
+ const root = parts[docsIndex + 1];
480
+
481
+ if (root === 'plans') {
482
+ const fileName = parts[docsIndex + 2] || '';
483
+ return stripDatePrefix(stripMarkdownSuffixes(fileName));
484
+ }
485
+
486
+ if (root === 'work') {
487
+ return stripDatePrefix(parts[docsIndex + 2] || '');
488
+ }
489
+
490
+ return '';
491
+ }
492
+
493
+ function isPathCandidateChar(char) {
494
+ return (
495
+ (char >= 'a' && char <= 'z') ||
496
+ (char >= 'A' && char <= 'Z') ||
497
+ (char >= '0' && char <= '9') ||
498
+ char === '/' ||
499
+ char === '\\' ||
500
+ char === '.' ||
501
+ char === '-' ||
502
+ char === '_'
503
+ );
504
+ }
505
+
506
+ function extractPathCandidates(text) {
507
+ const candidates = [];
508
+ let index = 0;
509
+
510
+ while (index < text.length) {
511
+ const hasForwardPath = text.startsWith('docs/', index);
512
+ const hasBackwardPath = text.startsWith('docs\\', index);
513
+
514
+ if (!hasForwardPath && !hasBackwardPath) {
515
+ index += 1;
516
+ continue;
517
+ }
518
+
519
+ let end = index;
520
+ while (end < text.length && isPathCandidateChar(text[end])) {
521
+ end += 1;
522
+ }
523
+ candidates.push(text.slice(index, end));
524
+ index = end;
525
+ }
526
+
527
+ return candidates;
528
+ }
529
+
411
530
  function extractDesignSlugs(design) {
412
531
  if (typeof design !== 'string' || design.trim() === '') {
413
532
  return [];
414
533
  }
415
534
 
416
535
  const slugs = new Set();
417
- const pattern = /docs[\\/]+plans[\\/]+\d{4}-\d{2}-\d{2}-([a-z0-9-]+?)-(?:design|tasks|decisions)\.md/g;
418
- let match = pattern.exec(design);
419
- while (match) {
420
- slugs.add(match[1]);
421
- match = pattern.exec(design);
536
+ for (const candidate of extractPathCandidates(design)) {
537
+ const slug = extractSlugFromDesignPath(candidate);
538
+ if (slug) {
539
+ slugs.add(slug);
540
+ }
422
541
  }
423
542
 
424
543
  return [...slugs];
@@ -450,7 +569,8 @@ function discoverCurrentIssue(snapshot, context, projectRoot = process.cwd()) {
450
569
  return hydratedIssues.get(issue.id);
451
570
  }
452
571
 
453
- const details = issue.design && issue.comments ? issue : readIssueDetails(issue.id, projectRoot);
572
+ const hasSufficientDetails = Boolean(issue.comments) && Boolean(issue.design);
573
+ const details = hasSufficientDetails ? issue : readIssueDetails(issue.id, projectRoot);
454
574
  const hydratedIssue = details ? { ...issue, ...details } : issue;
455
575
  hydratedIssues.set(issue.id, hydratedIssue);
456
576
  return hydratedIssue;
@@ -578,11 +698,8 @@ function collectCompletedChecks(result) {
578
698
  function formatAuthoritativeStatus(result) {
579
699
  const completedStages = result.workflowState.completedStages.map(stageId => stageId);
580
700
  const allowedCommands = result.nextStages.map(stageId => '/' + stageId).join(', ');
581
- const nextLines = [`Run now: /${result.runCommand}`];
701
+ const nextLines = formatRunNextLines(result);
582
702
 
583
- if (result.nextCommand) {
584
- nextLines.push(`Next after this: /${result.nextCommand}`);
585
- }
586
703
  if (allowedCommands) {
587
704
  nextLines.push(`Allowed after this: ${allowedCommands}`);
588
705
  }
@@ -633,7 +750,12 @@ function formatStatus(result) {
633
750
 
634
751
  module.exports = {
635
752
  name: 'status',
636
- description: 'Intelligent stage detection with confidence scoring',
753
+ description: 'One-glance orientation: where you are, what to run next, and your work',
754
+ usage: 'forge status [--full] [--json]',
755
+ flags: {
756
+ '--full': 'Also show blocked, stale, and recently completed issues',
757
+ '--json': 'Emit the machine-readable status envelope',
758
+ },
637
759
  handler: async (args, flags, projectRoot) => {
638
760
  const inputs = parseStatusInputs(args, flags);
639
761
  const isZeroArgStatus = !inputs.issueId && !inputs.workflowState && !inputs.bdComments;
@@ -647,7 +769,16 @@ module.exports = {
647
769
 
648
770
  if (isZeroArgStatus) {
649
771
  context = detectRepoContext(effectiveProjectRoot);
650
- snapshot = readBeadsSnapshot(effectiveProjectRoot);
772
+ // Read from the active issue backend (Kernel by default; Beads only when
773
+ // explicitly selected). Previously hard-wired to the retired Beads
774
+ // issues.jsonl, which is empty on a kernel-default repo — so the flagship
775
+ // view showed "Ready: none" despite a populated kernel (bug 40f35797).
776
+ snapshot = await readStatusSnapshot(effectiveProjectRoot, {
777
+ now: inputs.now,
778
+ staleAfterDays: inputs.staleAfterDays,
779
+ issueBackend: inputs.issueBackend,
780
+ env: process.env,
781
+ });
651
782
  const discoveredIssue = discoverCurrentIssue(snapshot, context, effectiveProjectRoot);
652
783
  if (discoveredIssue) {
653
784
  inputs.issueId = discoveredIssue.id;
@@ -660,23 +791,29 @@ module.exports = {
660
791
  if (isZeroArgStatus) {
661
792
  if (workflowState) {
662
793
  const result = buildAuthoritativeStatus(workflowState);
794
+ const output = inputs.json
795
+ ? JSON.stringify(buildPersonalStatusJson({ context, snapshot, workflowResult: result }), null, 2)
796
+ : formatZeroArgStatus({ context, snapshot, workflowResult: result, full: inputs.full });
663
797
  return {
664
798
  success: true,
665
799
  context,
666
800
  snapshot,
667
801
  issueId: inputs.issueId,
668
- output: formatZeroArgStatus({ context, snapshot, workflowResult: result }),
802
+ output,
669
803
  ...result,
670
804
  };
671
805
  }
672
806
 
807
+ const output = inputs.json
808
+ ? JSON.stringify(buildPersonalStatusJson({ context, snapshot }), null, 2)
809
+ : formatZeroArgStatus({ context, snapshot, full: inputs.full });
673
810
  return {
674
811
  success: true,
675
812
  context,
676
813
  snapshot,
677
814
  issueId: inputs.issueId,
678
815
  missingWorkflowState: true,
679
- output: formatZeroArgStatus({ context, snapshot }),
816
+ output,
680
817
  ...(fallbackReason ? { fallbackReason } : {}),
681
818
  };
682
819
  }
@@ -1,73 +1,61 @@
1
1
  'use strict';
2
2
 
3
- const { execFileSync } = require('node:child_process');
4
-
5
- function isRecoverableBeadsSyncError(error) {
6
- const message = error?.message ?? String(error ?? '');
7
- return (
8
- message.includes('failed to open database') ||
9
- message.includes('database not found') ||
10
- message.includes('no beads configuration found') ||
11
- message.includes('remote \'origin\' not found') ||
12
- message.includes('remote "origin" not found')
13
- );
14
- }
3
+ const { createSyncBackend } = require('../sync-backend');
15
4
 
16
5
  /**
17
6
  * Forge Sync Command
18
- * Syncs Beads issue data by running dolt pull + push.
19
- * Uses execFileSync (not execSync) to prevent command injection (OWASP A03).
7
+ * Syncs Kernel issue state with the configured sync backend.
8
+ *
9
+ * The local kernel is single-machine authority, so the default `local-noop`
10
+ * backend is a graceful no-op that names the model instead of moving data.
11
+ * Selecting `git-jsonl`/`server` (a future swap) is a backend change with zero
12
+ * call-site churn — see docs/work/2026-06-26-sync-authority/plan.md.
20
13
  *
21
14
  * @module commands/sync
22
15
  */
23
16
  module.exports = {
24
17
  name: 'sync',
25
- description: 'Sync Beads issue data (dolt pull + push)',
18
+ description: 'Sync Kernel issue state with the configured sync backend (local-noop until a server/remote is configured)',
26
19
  usage: 'forge sync',
27
20
  flags: {},
28
21
 
29
22
  /**
30
- * Run beads dolt pull + push to sync issue data.
23
+ * Resolve a SyncBackend (default `local-noop`) and run its one-shot sync.
31
24
  * @param {string[]} _args - Positional arguments (unused)
32
25
  * @param {object} _flags - CLI flags (unused)
33
- * @param {string} _projectRoot - Project root path (unused)
26
+ * @param {string} projectRoot - Project root path (used for backend resolution)
34
27
  * @param {object} [opts] - Options for dependency injection
35
- * @param {Function} [opts._exec] - Override for execFileSync (testing)
28
+ * @param {object} [opts._backend] - Override for the resolved SyncBackend (testing)
29
+ * @param {object} [opts.deps] - Explicit backend selection (precedence over env/config)
30
+ * @param {object} [opts.env] - Environment override (testing)
36
31
  * @returns {Promise<{success: boolean, synced: boolean, message?: string, error?: string}>}
37
32
  */
38
- handler: async (_args, _flags, _projectRoot, opts = {}) => {
39
- const exec = opts._exec || execFileSync;
40
-
41
- // Step 1: Check if bd binary exists
33
+ handler: async (_args, _flags, projectRoot, opts = {}) => {
34
+ let backend;
42
35
  try {
43
- exec('bd', ['--version'], { stdio: 'pipe' });
44
- } catch { /* intentional: bd not installed, skip sync gracefully */ // NOSONAR S2486
45
- return {
46
- success: true,
47
- synced: false,
48
- message: 'Beads not installed — nothing to sync',
49
- };
36
+ backend = opts._backend || createSyncBackend({
37
+ projectRoot,
38
+ deps: opts.deps,
39
+ env: opts.env,
40
+ });
41
+ } catch (resolveErr) {
42
+ return { success: false, synced: false, error: resolveErr.message };
50
43
  }
51
44
 
52
- // Step 2: Run dolt pull + push
45
+ let result;
53
46
  try {
54
- exec('bd', ['dolt', 'pull'], { stdio: 'pipe' });
55
- exec('bd', ['dolt', 'push'], { stdio: 'pipe' });
47
+ result = await backend.sync({ ...opts, projectRoot });
56
48
  } catch (syncErr) {
57
- if (isRecoverableBeadsSyncError(syncErr)) {
58
- return {
59
- success: true,
60
- synced: false,
61
- message: 'Beads is installed but not initialized for sync in this worktree — skipping sync',
62
- };
63
- }
64
- return {
65
- success: false,
66
- synced: false,
67
- error: syncErr.message,
68
- };
49
+ console.error(`forge sync failed: ${syncErr.message}`);
50
+ return { success: false, synced: false, error: syncErr.message };
69
51
  }
70
-
71
- return { success: true, synced: true };
52
+ // Surface the outcome to the user — otherwise `forge sync` runs silently and
53
+ // a user can't tell whether it synced, no-op'd (local-noop), or failed.
54
+ if (result && result.message) {
55
+ console.log(result.message);
56
+ } else if (result && !result.success && result.error) {
57
+ console.error(`forge sync failed: ${result.error}`);
58
+ }
59
+ return result;
72
60
  },
73
61
  };
@@ -1,6 +1,7 @@
1
1
  const { existsSync } = require('node:fs');
2
2
  const { execFileSync } = require('node:child_process');
3
3
  const path = require('node:path');
4
+ const { getPackageRoot } = require('../package-root');
4
5
 
5
6
  // Resolve bash binary from fixed, known locations only (no PATH search).
6
7
  // SonarCloud S4036: PATH must not be searched for OS commands.
@@ -22,7 +23,9 @@ function _resolveBash() {
22
23
  }
23
24
 
24
25
  function handleTeam(args) {
25
- const scriptPath = path.join(__dirname, '..', '..', 'scripts', 'forge-team', 'index.sh');
26
+ // Route through the asset root so `forge team` finds the script in BOTH channels:
27
+ // the on-disk package (npm/npx) and a compiled binary (extracted embed dir).
28
+ const scriptPath = path.join(getPackageRoot(), 'scripts', 'forge-team', 'index.sh');
26
29
  const bashPath = _resolveBash();
27
30
 
28
31
  try {
@@ -27,6 +27,18 @@ const LOCKFILE_MAP = [
27
27
 
28
28
  const DEFAULT_TIMEOUT = 120000;
29
29
  const BEADS_CHECK_TIMEOUT = 3000;
30
+ const DIRECT_TEST_CANDIDATES = Object.freeze({
31
+ 'bin/forge.js': [
32
+ 'test/cli-flags.test.js',
33
+ 'test/forge-cli-registry.test.js',
34
+ 'test/setup-github-sync.test.js',
35
+ 'test/setup-runtime-flags.test.js',
36
+ ],
37
+ 'lib/lefthook-check.js': ['test/lefthook-check.test.js', 'test/runtime-health.test.js'],
38
+ 'lib/runtime-health.js': ['test/runtime-health.test.js'],
39
+ 'lib/upgrade-safety.js': ['test/commands/upgrade.test.js'],
40
+ 'scripts/test.js': ['test/scripts/test-runner.test.js'],
41
+ });
30
42
 
31
43
  /**
32
44
  * Detect the package manager by checking which lockfile exists.
@@ -115,7 +127,17 @@ function getChangedFiles(execFileSync, options = {}) {
115
127
  encoding: 'utf8',
116
128
  timeout: 5000,
117
129
  }).trim();
118
- } catch (_e) { // NOSONAR S2486
130
+ } catch (err) { // NOSONAR S2486
131
+ // A git-diff FAILURE is NOT the same as "no files changed": we could not
132
+ // determine the change set at all. Callers that must fail closed (preflight)
133
+ // pass { strict: true } so the error surfaces instead of masquerading as an
134
+ // empty list. The default path keeps the historical empty-list fallback that
135
+ // `forge test --affected` and the pre-push mapping rely on to run the full
136
+ // suite, so their semantics are unchanged.
137
+ if (options.strict) {
138
+ const reason = err && err.message ? err.message : String(err);
139
+ throw new Error(`git diff failed while computing changed files: ${reason}`);
140
+ }
119
141
  /* intentional: git diff failed, no affected files to report */
120
142
  return [];
121
143
  }
@@ -152,22 +174,29 @@ function getAffectedTestFiles(projectRoot, execFileSync, fs = defaultFs, options
152
174
  function getTestCandidatesForChangedFile(file) {
153
175
  if (!file) return [];
154
176
 
155
- if (file === 'lib/lefthook-check.js') {
156
- return ['test/lefthook-check.test.js', 'test/runtime-health.test.js'];
157
- }
158
-
159
- if (file === 'lib/runtime-health.js') {
160
- return ['test/runtime-health.test.js'];
161
- }
162
-
163
- if (file === 'scripts/test.js') {
164
- return ['test/scripts/test-runner.test.js'];
177
+ const directCandidates = DIRECT_TEST_CANDIDATES[file];
178
+ if (directCandidates) {
179
+ return directCandidates;
165
180
  }
166
181
 
167
182
  if (file.startsWith('test/') && file.endsWith('.test.js')) {
168
183
  return [file];
169
184
  }
170
185
 
186
+ if (file === 'README.md'
187
+ || file === 'QUICKSTART.md'
188
+ || file === 'CHANGELOG.md'
189
+ || file === 'AGENTS.md'
190
+ || file === 'DEVELOPMENT.md'
191
+ || file === 'docs/INDEX.md'
192
+ || file === 'docs/PROJECT_DESIGN.md'
193
+ || file.startsWith('docs/forge/')
194
+ || file.startsWith('docs/guides/')
195
+ || file.startsWith('docs/reference/')
196
+ || file.startsWith('docs/work/')) {
197
+ return ['test/docs-consistency.test.js'];
198
+ }
199
+
171
200
  if (file.startsWith('lib/') && file.endsWith('.js')) {
172
201
  const relative = file.slice('lib/'.length);
173
202
  return [`test/${relative.replace(/\.js$/, '.test.js')}`];
@@ -196,26 +225,13 @@ function getTestCandidatesForChangedFile(file) {
196
225
  ];
197
226
  }
198
227
 
199
- if (file.startsWith('.claude/commands/') && file.endsWith('.md')) {
200
- return [
201
- 'test/command-sync-check.test.js',
202
- 'test/structural/command-sync.test.js',
203
- ];
204
- }
205
-
206
228
  if (file.startsWith('.forge/')
207
229
  || file.startsWith('.cursor/')
208
- || file.startsWith('.cline/')
209
- || file.startsWith('.roo/')
210
- || file.startsWith('.kilocode/')
211
- || file.startsWith('.opencode/')
212
- || file.startsWith('.codex/skills/')
213
- || file.startsWith('.github/prompts/')) {
230
+ || file.startsWith('.codex/')) {
214
231
  return [
215
232
  'test/agent-gaps.test.js',
216
- 'test/command-sync-check.test.js',
217
233
  'test/scripts/check-agents.test.js',
218
- 'test/structural/command-sync.test.js',
234
+ 'test/structural/skills-sync-drift.test.js',
219
235
  ];
220
236
  }
221
237
 
@@ -227,7 +243,7 @@ module.exports = {
227
243
  getAffectedTestFiles,
228
244
  getTestCandidatesForChangedFile,
229
245
  name: 'test',
230
- description: 'Run tests with smart defaults (timeout, Beads skip, affected-only)',
246
+ description: 'Run tests with smart defaults (timeout, affected-only)',
231
247
  usage: 'forge test [--affected]',
232
248
  flags: {
233
249
  '--affected': 'Run only tests for changed files',
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { makeAliasCommand } = require('./_issue');
3
+ const { createIssueSubcommand } = require('./_issue');
4
4
 
5
- module.exports = makeAliasCommand('update');
5
+ module.exports = createIssueSubcommand('update');
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ applySelfHeal,
5
+ buildUpgradeDryRunReport,
6
+ renderUpgradeDryRunReport,
7
+ } = require('../upgrade-safety');
8
+
9
+ function hasArg(args, name) {
10
+ return Array.isArray(args) && args.includes(name);
11
+ }
12
+
13
+ const usage = 'Usage: forge upgrade [--dry-run] [--self-heal]';
14
+
15
+ async function handler(args, flags, projectRoot) {
16
+ const dryRun = flags?.dryRun === true || flags?.['dry-run'] === true || hasArg(args, '--dry-run');
17
+ const selfHeal = flags?.selfHeal === true || flags?.['self-heal'] === true || hasArg(args, '--self-heal');
18
+
19
+ if (!dryRun && !selfHeal) {
20
+ return {
21
+ success: false,
22
+ error: usage,
23
+ };
24
+ }
25
+
26
+ const report = buildUpgradeDryRunReport(projectRoot);
27
+ const selfHealResult = selfHeal ? applySelfHeal(report.projectRoot, report) : null;
28
+ const output = renderUpgradeDryRunReport(report, selfHealResult);
29
+ const success = report.ok && !selfHealResult?.refused;
30
+
31
+ return {
32
+ success,
33
+ output,
34
+ error: success ? undefined : output,
35
+ };
36
+ }
37
+
38
+ module.exports = {
39
+ name: 'upgrade',
40
+ description: 'Preview and self-heal safe Forge upgrade readiness',
41
+ usage,
42
+ flags: {
43
+ '--dry-run': 'Preview upgrade readiness without writing files',
44
+ '--self-heal': 'Apply safe metadata repairs only; does not snapshot or restore',
45
+ },
46
+ handler,
47
+ };