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
@@ -0,0 +1,946 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const YAML = require('yaml');
6
+
7
+ const RUNTIME_GRAPH_SCHEMA_VERSION = '0.0.17';
8
+ const CONFIG_SOURCE = '.forge/config.yaml';
9
+
10
+ const RUNTIME_GRAPH_SCHEMA = {
11
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
12
+ $id: 'https://forge.local/schemas/runtime-graph-0.0.17.json',
13
+ title: 'Forge Runtime Graph Artifact',
14
+ type: 'object',
15
+ required: ['schemaVersion', 'kind', 'schema', 'graph'],
16
+ properties: {
17
+ schemaVersion: { const: RUNTIME_GRAPH_SCHEMA_VERSION },
18
+ kind: { const: 'forge.runtimeGraph' },
19
+ schema: {
20
+ type: 'object',
21
+ required: ['$schema', '$id'],
22
+ },
23
+ graph: {
24
+ type: 'object',
25
+ required: [
26
+ 'primitives',
27
+ 'phases',
28
+ 'actions',
29
+ 'artifacts',
30
+ 'evaluatorRegions',
31
+ 'gates',
32
+ 'evidence',
33
+ 'rails',
34
+ 'adapters',
35
+ 'edges',
36
+ ],
37
+ },
38
+ },
39
+ };
40
+
41
+ function withRuntimeState(primitive, { enabled = true, locked = false, configSource = 'package-defaults' } = {}) {
42
+ return {
43
+ ...primitive,
44
+ enabled,
45
+ disabled: enabled === false,
46
+ locked,
47
+ configSource,
48
+ };
49
+ }
50
+
51
+ function Phase({ id, label, command, actions, artifacts, gates, evidence, enabled = true, locked = false }) {
52
+ return withRuntimeState({ id, label, command, actions, artifacts, gates, evidence }, { enabled, locked });
53
+ }
54
+
55
+ function Action({ id, kind, command, label, reads = [], writes = [], evidence = [], enabled = true, locked = false }) {
56
+ return withRuntimeState({ id, kind, command, label, reads, writes, evidence }, { enabled, locked });
57
+ }
58
+
59
+ function Artifact({ id, kind, path, description }) {
60
+ return { id, kind, path, description };
61
+ }
62
+
63
+ function EvaluatorRegion({ id, label, phase, description }) {
64
+ return { id, label, phase, description };
65
+ }
66
+
67
+ function Gate({ id, phase, label, requires, enabled = true, locked = false }) {
68
+ return withRuntimeState({ id, phase, label, requires }, { enabled, locked });
69
+ }
70
+
71
+ function Evidence({ id, kind, source, description }) {
72
+ return { id, kind, source, description };
73
+ }
74
+
75
+ function Rail({ id, key, label, description, locked = true, enabled = true }) {
76
+ return withRuntimeState({ id, key, label, description, layer: 'L1' }, { enabled, locked });
77
+ }
78
+
79
+ function Adapter({ id, kind, label, config = {}, enabled = true }) {
80
+ return withRuntimeState({ id, kind, label, config }, { enabled });
81
+ }
82
+
83
+ function PlanningSubSkill({ id, label, inputs, outputs, evidence, gate, enabled = true }) {
84
+ return withRuntimeState({ id, label, inputs, outputs, evidence, gate }, { enabled });
85
+ }
86
+
87
+ function Role({ id, role, skill, ideology, configSource = 'package-defaults' }) {
88
+ const primitive = { id, role, skill, configSource };
89
+ if (ideology !== undefined) primitive.ideology = ideology;
90
+ return primitive;
91
+ }
92
+
93
+ // Role -> default skill binding. Role names are the canonical stage ids
94
+ // (lib/workflow/stages.js STAGE_IDS); each ships bound to its like-named skill.
95
+ // This is the closed set of KNOWN roles; the bound skill name is open-world
96
+ // (resolved via .skills/ > skills/), never checked against PLAN_SUBSKILL_IDS.
97
+ // Additive to the graph — see docs/work/2026-07-04-kernel-native-skills/
98
+ // extensibility-architecture.md.
99
+ const ROLE_DEFINITIONS = [
100
+ { role: 'plan', skill: 'plan' },
101
+ { role: 'dev', skill: 'dev' },
102
+ { role: 'validate', skill: 'validate' },
103
+ { role: 'ship', skill: 'ship' },
104
+ { role: 'review', skill: 'review' },
105
+ { role: 'verify', skill: 'verify' },
106
+ ];
107
+
108
+ const ROLE_IDS = new Set(ROLE_DEFINITIONS.map(definition => definition.role));
109
+
110
+ function roleFromDefinition(definition) {
111
+ return Role({ id: `role.${definition.role}`, role: definition.role, skill: definition.skill });
112
+ }
113
+
114
+ const PLAN_SUBSKILL_DEFINITIONS = [
115
+ {
116
+ id: 'plan.intent_capture',
117
+ label: 'Intent capture',
118
+ inputs: ['feature request', 'constraints'],
119
+ outputs: ['purpose', 'success criteria', 'out of scope'],
120
+ actionLabel: 'Capture planning intent',
121
+ reads: [],
122
+ writes: ['artifact.design-doc'],
123
+ },
124
+ {
125
+ id: 'plan.parallel_research',
126
+ label: 'Parallel research',
127
+ inputs: ['intent capture'],
128
+ outputs: ['technical approach options', 'risk notes'],
129
+ actionLabel: 'Research planning options',
130
+ reads: ['artifact.design-doc'],
131
+ writes: ['artifact.design-doc'],
132
+ },
133
+ {
134
+ id: 'plan.parallel_critics',
135
+ label: 'Parallel critics',
136
+ inputs: ['approach options'],
137
+ outputs: ['spec critique', 'risk critique', 'test critique'],
138
+ actionLabel: 'Critique planning options',
139
+ reads: ['artifact.design-doc'],
140
+ writes: ['artifact.design-doc'],
141
+ },
142
+ {
143
+ id: 'plan.synthesis',
144
+ label: 'Synthesis',
145
+ inputs: ['research', 'critic output'],
146
+ outputs: ['selected approach', 'tradeoffs'],
147
+ actionLabel: 'Synthesize selected plan',
148
+ reads: ['artifact.design-doc'],
149
+ writes: ['artifact.design-doc'],
150
+ },
151
+ {
152
+ id: 'plan.final_lock',
153
+ label: 'Final lock',
154
+ inputs: ['selected approach'],
155
+ outputs: ['design doc', 'task list'],
156
+ actionLabel: 'Lock planning artifacts',
157
+ reads: ['artifact.design-doc'],
158
+ writes: ['artifact.design-doc', 'artifact.task-list'],
159
+ },
160
+ ];
161
+
162
+ const PLAN_SUBSKILL_IDS = new Set(PLAN_SUBSKILL_DEFINITIONS.map(definition => definition.id));
163
+
164
+ function planningSubSkillFromDefinition(definition) {
165
+ return PlanningSubSkill({
166
+ id: definition.id,
167
+ label: definition.label,
168
+ inputs: definition.inputs,
169
+ outputs: definition.outputs,
170
+ evidence: ['evidence.command-docs'],
171
+ gate: 'gate.plan-exit',
172
+ });
173
+ }
174
+
175
+ function planningActionFromDefinition(definition) {
176
+ return Action({
177
+ id: `action.${definition.id}`,
178
+ kind: 'plan-subskill',
179
+ command: definition.id,
180
+ label: definition.actionLabel,
181
+ reads: definition.reads,
182
+ writes: definition.writes,
183
+ evidence: ['evidence.command-docs'],
184
+ });
185
+ }
186
+
187
+ const RESOLVED_RUNTIME_GRAPH = {
188
+ id: 'forge.runtimeGraph.currentCommandFlow',
189
+ version: RUNTIME_GRAPH_SCHEMA_VERSION,
190
+ description: 'Resolved graph for the current /plan -> /dev -> /validate -> /ship command flow.',
191
+ primitives: {
192
+ Phase: 'phases',
193
+ Action: 'actions',
194
+ PlanningSubSkill: 'planning.subSkills',
195
+ Artifact: 'artifacts',
196
+ EvaluatorRegion: 'evaluatorRegions',
197
+ Gate: 'gates',
198
+ Evidence: 'evidence',
199
+ Rail: 'rails',
200
+ Adapter: 'adapters',
201
+ },
202
+ phases: [
203
+ Phase({
204
+ id: 'plan',
205
+ label: '/plan',
206
+ command: 'plan',
207
+ actions: PLAN_SUBSKILL_DEFINITIONS.map(definition => `action.${definition.id}`),
208
+ artifacts: ['artifact.design-doc', 'artifact.task-list'],
209
+ gates: ['gate.plan-exit'],
210
+ evidence: ['evidence.command-docs'],
211
+ }),
212
+ Phase({
213
+ id: 'dev',
214
+ label: '/dev',
215
+ command: 'dev',
216
+ actions: ['action.dev.command'],
217
+ artifacts: ['artifact.changed-files'],
218
+ gates: ['gate.dev-exit'],
219
+ evidence: ['evidence.tdd-tests'],
220
+ }),
221
+ Phase({
222
+ id: 'validate',
223
+ label: '/validate',
224
+ command: 'validate',
225
+ actions: ['action.validate.command'],
226
+ artifacts: ['artifact.validation-output'],
227
+ gates: ['gate.validate-exit'],
228
+ evidence: ['evidence.validation-output'],
229
+ }),
230
+ Phase({
231
+ id: 'ship',
232
+ label: '/ship',
233
+ command: 'ship',
234
+ actions: ['action.ship.command'],
235
+ artifacts: ['artifact.pull-request'],
236
+ gates: ['gate.ship-entry'],
237
+ evidence: ['evidence.dry-run-report'],
238
+ }),
239
+ ],
240
+ planning: {
241
+ template: {
242
+ mode: 'full',
243
+ convergenceThreshold: 0.75,
244
+ criticSet: ['spec', 'risk', 'test'],
245
+ partialInvocation: {
246
+ only: [],
247
+ skip: [],
248
+ },
249
+ configSource: 'package-defaults',
250
+ },
251
+ subSkills: PLAN_SUBSKILL_DEFINITIONS.map(planningSubSkillFromDefinition),
252
+ },
253
+ roles: ROLE_DEFINITIONS.map(roleFromDefinition),
254
+ actions: [
255
+ Action({
256
+ id: 'action.plan.command',
257
+ kind: 'command',
258
+ command: 'plan',
259
+ label: 'Capture design intent and task list',
260
+ writes: ['artifact.design-doc', 'artifact.task-list'],
261
+ evidence: ['evidence.command-docs'],
262
+ }),
263
+ ...PLAN_SUBSKILL_DEFINITIONS.map(planningActionFromDefinition),
264
+ Action({
265
+ id: 'action.dev.command',
266
+ kind: 'command',
267
+ command: 'dev',
268
+ label: 'Implement tasks with TDD evidence',
269
+ reads: ['artifact.design-doc', 'artifact.task-list'],
270
+ writes: ['artifact.changed-files'],
271
+ evidence: ['evidence.tdd-tests'],
272
+ }),
273
+ Action({
274
+ id: 'action.validate.command',
275
+ kind: 'command',
276
+ command: 'validate',
277
+ label: 'Run type, lint, test, and security checks',
278
+ reads: ['artifact.changed-files'],
279
+ writes: ['artifact.validation-output'],
280
+ evidence: ['evidence.validation-output'],
281
+ }),
282
+ Action({
283
+ id: 'action.ship.command',
284
+ kind: 'command',
285
+ command: 'ship',
286
+ label: 'Push branch and open a pull request',
287
+ reads: ['artifact.validation-output'],
288
+ writes: ['artifact.pull-request'],
289
+ evidence: ['evidence.dry-run-report'],
290
+ }),
291
+ ],
292
+ artifacts: [
293
+ Artifact({
294
+ id: 'artifact.design-doc',
295
+ kind: 'markdown',
296
+ path: 'docs/work/YYYY-MM-DD-<slug>/plan.md',
297
+ description: 'Design intent, constraints, and research.',
298
+ }),
299
+ Artifact({
300
+ id: 'artifact.task-list',
301
+ kind: 'markdown',
302
+ path: 'docs/work/YYYY-MM-DD-<slug>/tasks.md',
303
+ description: 'TDD task list consumed by /dev.',
304
+ }),
305
+ Artifact({
306
+ id: 'artifact.changed-files',
307
+ kind: 'git-diff',
308
+ path: '<worktree>',
309
+ description: 'Source and test changes produced during /dev.',
310
+ }),
311
+ Artifact({
312
+ id: 'artifact.validation-output',
313
+ kind: 'terminal-output',
314
+ path: '<session>',
315
+ description: 'Fresh validation command output.',
316
+ }),
317
+ Artifact({
318
+ id: 'artifact.pull-request',
319
+ kind: 'github-pr',
320
+ path: '<pr-url>',
321
+ description: 'Pull request created by /ship.',
322
+ }),
323
+ ],
324
+ evaluatorRegions: [
325
+ EvaluatorRegion({
326
+ id: 'evaluator.plan-entry',
327
+ label: 'Plan entry and exit gates',
328
+ phase: 'plan',
329
+ description: 'Checks worktree isolation, design artifacts, and task handoff.',
330
+ }),
331
+ EvaluatorRegion({
332
+ id: 'evaluator.tdd-loop',
333
+ label: 'TDD implementation loop',
334
+ phase: 'dev',
335
+ description: 'Checks RED/GREEN/REFACTOR evidence and task completion.',
336
+ }),
337
+ EvaluatorRegion({
338
+ id: 'evaluator.validation-suite',
339
+ label: 'Validation suite',
340
+ phase: 'validate',
341
+ description: 'Checks type, lint, tests, and security scan evidence.',
342
+ }),
343
+ EvaluatorRegion({
344
+ id: 'evaluator.ship-readiness',
345
+ label: 'Ship readiness',
346
+ phase: 'ship',
347
+ description: 'Checks branch freshness, PR body, and issue linkage.',
348
+ }),
349
+ ],
350
+ gates: [
351
+ Gate({
352
+ id: 'gate.plan-exit',
353
+ phase: 'plan',
354
+ label: '/plan exit',
355
+ requires: ['artifact.design-doc', 'artifact.task-list', 'evidence.command-docs'],
356
+ }),
357
+ Gate({
358
+ id: 'gate.dev-exit',
359
+ phase: 'dev',
360
+ label: '/dev exit',
361
+ requires: ['artifact.changed-files', 'evidence.tdd-tests'],
362
+ }),
363
+ Gate({
364
+ id: 'gate.validate-exit',
365
+ phase: 'validate',
366
+ label: '/validate exit',
367
+ requires: ['artifact.validation-output', 'evidence.validation-output'],
368
+ }),
369
+ Gate({
370
+ id: 'gate.ship-entry',
371
+ phase: 'ship',
372
+ label: '/ship entry',
373
+ requires: ['artifact.validation-output'],
374
+ }),
375
+ // Human gates — satisfied by a `gate.approved` kernel EVENT on the issue, not by
376
+ // an artifact (see lib/gate-events.js + docs/work/2026-07-04-kernel-native-skills/
377
+ // decisions.md). Additive + backward-compatible: they widen the known-gate set so
378
+ // `forge gate approve|reject|status|check` recognize them and each is toggleable
379
+ // via `workflow.gates.<id>.enabled`. `requires: []` because approval, not evidence,
380
+ // is the exit condition. Default three: intent · plan-approval · merge.
381
+ Gate({
382
+ id: 'gate.intent',
383
+ phase: 'plan',
384
+ label: 'Intent approval',
385
+ requires: [],
386
+ }),
387
+ Gate({
388
+ id: 'gate.plan-approval',
389
+ phase: 'plan',
390
+ label: 'Plan approval',
391
+ requires: [],
392
+ }),
393
+ Gate({
394
+ id: 'gate.merge',
395
+ phase: 'ship',
396
+ label: 'Merge approval',
397
+ requires: [],
398
+ }),
399
+ // gate.issue_verify — check-after-write verification on kernel issue mutations
400
+ // (create|update|close|claim|comment) at the lib/commands/_issue.js boundary:
401
+ // after a successful write, re-read through the same broker and confirm the
402
+ // intended delta landed (verified/mismatches in the envelope; warn-only — the
403
+ // write's ok is never overturned). Default ON and UNLOCKED, toggled via
404
+ // `forge gate disable gate.issue_verify` through the same workflow.gates
405
+ // surface as rail.kernel_tracking. Justified by two proven "ok:true lied"
406
+ // bugs: 145d9ad1 (close --reason/closed_at dropped in the projection) and
407
+ // d71a824b (idempotent claim replay telling a losing agent it won). No phase:
408
+ // it guards the issue-command boundary, not a workflow stage exit.
409
+ // `requires: []` — the read-back itself, not evidence, is the check.
410
+ Gate({
411
+ id: 'gate.issue_verify',
412
+ label: 'Issue write verification (check-after-write)',
413
+ requires: [],
414
+ }),
415
+ ],
416
+ evidence: [
417
+ Evidence({
418
+ id: 'evidence.command-docs',
419
+ kind: 'checked-in-doc',
420
+ source: 'skills/*/SKILL.md',
421
+ description: 'Skill docs define the compatibility surface.',
422
+ }),
423
+ Evidence({
424
+ id: 'evidence.tdd-tests',
425
+ kind: 'test-output',
426
+ source: 'bun test',
427
+ description: 'Task-level failing then passing test output.',
428
+ }),
429
+ Evidence({
430
+ id: 'evidence.validation-output',
431
+ kind: 'validation-output',
432
+ source: 'bun run typecheck; bun run lint; bun test; bun audit',
433
+ description: 'Fresh validation evidence for the branch.',
434
+ }),
435
+ Evidence({
436
+ id: 'evidence.dry-run-report',
437
+ kind: 'dry-run-output',
438
+ source: 'forge migrate --dry-run',
439
+ description: 'Resolved graph can be printed without side effects.',
440
+ }),
441
+ ],
442
+ // Rails are declared as a data table and mapped through Rail() so each entry
443
+ // stays a single line (Rail defaults locked: true; kernel_tracking is the one
444
+ // UNLOCKED, default-ON rail — it ingrains "file every issue/idea/bug/decision
445
+ // to the kernel immediately; nothing discussed goes missing", and a maintainer
446
+ // may opt out via `forge gate disable rail.kernel_tracking`, consumed by the
447
+ // resolver's rail-aware gate loop over workflow.gates.<id>.enabled).
448
+ rails: [
449
+ { key: 'tdd_intent', label: 'TDD intent evidence', description: 'Source changes require test intent and TDD evidence.' },
450
+ { key: 'secret_scan', label: 'Secret scan', description: 'Validation must not knowingly ship secrets.' },
451
+ { key: 'branch_protection', label: 'Branch protection', description: 'Ship through reviewed branches instead of direct protected-branch edits.' },
452
+ { key: 'signed_commits', label: 'Signed commits', description: 'Preserve commit provenance requirements where configured.' },
453
+ { key: 'schema_integrity', label: 'Schema integrity', description: 'Keep runtime graph and config schemas internally consistent.' },
454
+ { key: 'kernel_tracking', label: 'Kernel issue tracking', description: 'Every issue, idea, bug, and decision discussed is filed to the Forge Kernel.', locked: false },
455
+ ].map(def => Rail({ id: `rail.${def.key}`, ...def })),
456
+ adapters: [
457
+ Adapter({
458
+ id: 'adapter.issue',
459
+ kind: 'issue',
460
+ label: 'Issue tracking adapter',
461
+ config: { primary: 'beads', mirrors: ['github'] },
462
+ }),
463
+ Adapter({
464
+ id: 'adapter.harness',
465
+ kind: 'harness',
466
+ label: 'Agent harness adapter',
467
+ config: { targets: ['codex', 'claude', 'cursor'] },
468
+ }),
469
+ ],
470
+ protectedPaths: [],
471
+ edges: [
472
+ { from: 'phase.plan', to: 'phase.dev', reason: 'design and task list feed implementation' },
473
+ { from: 'phase.dev', to: 'phase.validate', reason: 'implementation feeds validation' },
474
+ { from: 'phase.validate', to: 'phase.ship', reason: 'validated branch can be shipped' },
475
+ ],
476
+ };
477
+
478
+ function cloneJson(value) {
479
+ return JSON.parse(JSON.stringify(value));
480
+ }
481
+
482
+ function getConfigPath(projectRoot = process.cwd()) {
483
+ return path.join(projectRoot, '.forge', 'config.yaml');
484
+ }
485
+
486
+ function loadRuntimeGraphConfig({ projectRoot = process.cwd() } = {}) {
487
+ const configPath = getConfigPath(projectRoot);
488
+ if (!fs.existsSync(configPath)) {
489
+ return { config: {}, configPath, exists: false, errors: [] };
490
+ }
491
+
492
+ try {
493
+ const rawConfig = fs.readFileSync(configPath, 'utf8');
494
+ const parsed = rawConfig.trim() === '' ? {} : YAML.parse(rawConfig);
495
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
496
+ return {
497
+ config: {},
498
+ configPath,
499
+ exists: true,
500
+ errors: [{
501
+ code: 'CONFIG_ROOT_INVALID',
502
+ message: `${CONFIG_SOURCE} root must be an object.`,
503
+ }],
504
+ };
505
+ }
506
+ return {
507
+ config: parsed,
508
+ configPath,
509
+ exists: true,
510
+ errors: [],
511
+ };
512
+ } catch (err) {
513
+ return {
514
+ config: {},
515
+ configPath,
516
+ exists: true,
517
+ errors: [{
518
+ code: 'CONFIG_PARSE_ERROR',
519
+ message: `Invalid ${CONFIG_SOURCE}: ${err.message}`,
520
+ }],
521
+ };
522
+ }
523
+ }
524
+
525
+ function markConfigured(primitive, options) {
526
+ Object.assign(primitive, withRuntimeState(primitive, {
527
+ enabled: options.enabled,
528
+ locked: primitive.locked === true,
529
+ configSource: CONFIG_SOURCE,
530
+ }));
531
+ }
532
+
533
+ function readEnabledOption(options, errors, typeName, id) {
534
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
535
+ errors.push({
536
+ code: 'INVALID_PRIMITIVE_CONFIG',
537
+ message: `${typeName} '${id}' config must be an object.`,
538
+ });
539
+ return { present: false, valid: false, value: undefined };
540
+ }
541
+ if (!Object.hasOwn(options, 'enabled')) {
542
+ return { present: false, valid: true, value: undefined };
543
+ }
544
+ if (typeof options.enabled !== 'boolean') {
545
+ errors.push({
546
+ code: 'INVALID_ENABLED_VALUE',
547
+ message: `${typeName} '${id}' enabled must be a boolean.`,
548
+ });
549
+ return { present: true, valid: false, value: undefined };
550
+ }
551
+ return { present: true, valid: true, value: options.enabled };
552
+ }
553
+
554
+ function readConfigSection(parent, key, errors, path) {
555
+ if (!Object.hasOwn(parent, key)) {
556
+ return {};
557
+ }
558
+ const value = parent[key];
559
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
560
+ errors.push({
561
+ code: 'INVALID_CONFIG_SECTION',
562
+ message: `${path} must be an object.`,
563
+ });
564
+ return {};
565
+ }
566
+ return value;
567
+ }
568
+
569
+ function applyEnabledConfig(collection, id, options, errors, typeName) {
570
+ const primitive = collection.find(candidate => candidate.id === id);
571
+ if (!primitive) {
572
+ errors.push({
573
+ code: `UNKNOWN_${typeName.toUpperCase()}`,
574
+ message: `Unknown ${typeName} '${id}' in ${CONFIG_SOURCE}.`,
575
+ });
576
+ return;
577
+ }
578
+
579
+ const enabled = readEnabledOption(options, errors, typeName, id);
580
+ if (!enabled.valid) return;
581
+ if (enabled.present) {
582
+ if (enabled.value === false && primitive.locked === true) {
583
+ errors.push({
584
+ code: 'LOCKED_PRIMITIVE_DISABLED',
585
+ message: `Cannot disable locked ${typeName} '${id}'.`,
586
+ });
587
+ return;
588
+ }
589
+ markConfigured(primitive, { enabled: enabled.value });
590
+ }
591
+ }
592
+
593
+ function applyRailConfig(graph, config, errors) {
594
+ const rails = readConfigSection(config, 'rails', errors, 'rails');
595
+ for (const [key, options] of Object.entries(rails)) {
596
+ const rail = graph.rails.find(candidate => candidate.key === key || candidate.id === key);
597
+ if (!rail) {
598
+ errors.push({
599
+ code: 'UNKNOWN_RAIL',
600
+ message: `Unknown rail '${key}' in ${CONFIG_SOURCE}.`,
601
+ });
602
+ continue;
603
+ }
604
+ const enabled = readEnabledOption(options, errors, 'rail', key);
605
+ if (!enabled.valid) {
606
+ continue;
607
+ }
608
+ if (enabled.value === false) {
609
+ errors.push({
610
+ code: 'LOCKED_L1_RAIL_DISABLED',
611
+ message: `Cannot disable locked L1 rail '${key}'.`,
612
+ });
613
+ continue;
614
+ }
615
+ if (options && Object.keys(options).length > 0) {
616
+ rail.configSource = CONFIG_SOURCE;
617
+ }
618
+ }
619
+ }
620
+
621
+ function applyWorkflowConfig(graph, config, errors) {
622
+ const workflow = readConfigSection(config, 'workflow', errors, 'workflow');
623
+ const phases = readConfigSection(workflow, 'phases', errors, 'workflow.phases');
624
+ for (const [id, options] of Object.entries(phases)) {
625
+ applyEnabledConfig(graph.phases, id, options, errors, 'stage');
626
+ }
627
+
628
+ // The `workflow.gates.<id>.enabled` surface (driven by `forge gate enable|disable`)
629
+ // governs gates AND unlocked toggleable rails (e.g. rail.kernel_tracking): the id
630
+ // namespaces are disjoint (gate.* vs rail.*), so a single combined lookup routes each
631
+ // id to its primitive. applyEnabledConfig still honors each primitive's `locked` flag,
632
+ // so a locked L1 rail cannot be disabled through this surface either.
633
+ const gates = readConfigSection(workflow, 'gates', errors, 'workflow.gates');
634
+ const toggleable = [...graph.gates, ...graph.rails];
635
+ for (const [id, options] of Object.entries(gates)) {
636
+ applyEnabledConfig(toggleable, id, options, errors, 'gate');
637
+ }
638
+ }
639
+
640
+ function applyAdapterConfig(graph, config, errors) {
641
+ const adapters = readConfigSection(config, 'adapters', errors, 'adapters');
642
+ for (const [kind, options] of Object.entries(adapters)) {
643
+ const adapter = graph.adapters.find(candidate => candidate.kind === kind || candidate.id === kind);
644
+ if (!adapter) {
645
+ errors.push({
646
+ code: 'UNKNOWN_ADAPTER',
647
+ message: `Unknown adapter '${kind}' in ${CONFIG_SOURCE}.`,
648
+ });
649
+ continue;
650
+ }
651
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
652
+ errors.push({
653
+ code: 'INVALID_ADAPTER_CONFIG',
654
+ message: `adapter '${kind}' config must be an object.`,
655
+ });
656
+ continue;
657
+ }
658
+ const enabled = readEnabledOption(options, errors, 'adapter', kind);
659
+ if (!enabled.valid) {
660
+ continue;
661
+ }
662
+ const nextConfig = { ...options };
663
+ delete nextConfig.enabled;
664
+ if (enabled.present) {
665
+ markConfigured(adapter, { enabled: enabled.value });
666
+ }
667
+ adapter.config = { ...adapter.config };
668
+ Object.assign(adapter.config, nextConfig);
669
+ if (enabled.present || Object.keys(nextConfig).length > 0) {
670
+ adapter.configSource = CONFIG_SOURCE;
671
+ }
672
+ }
673
+ }
674
+
675
+ function readRoleStringField(options, field, role, errors) {
676
+ if (!Object.hasOwn(options, field)) {
677
+ return { present: false, value: undefined };
678
+ }
679
+ const value = options[field];
680
+ if (typeof value !== 'string' || value.trim() === '') {
681
+ errors.push({
682
+ code: `INVALID_ROLE_${field.toUpperCase()}`,
683
+ message: `role '${role}' ${field} must be a non-empty string.`,
684
+ });
685
+ return { present: true, value: undefined };
686
+ }
687
+ return { present: true, value: value.trim() };
688
+ }
689
+
690
+ // Reader for the `roles` section (sibling to `workflow.gates`). Role names are
691
+ // the KNOWN closed set (ROLE_IDS); the bound `skill` / `ideology` names are
692
+ // OPEN-WORLD — validated only as non-empty strings here, never against
693
+ // PLAN_SUBSKILL_IDS (that closed enum stays scoped to
694
+ // planning.template.partialInvocation). Skill EXISTENCE is enforced at write
695
+ // time by the `forge role` verb, not mid-run.
696
+ function applyRoleConfig(graph, config, errors) {
697
+ const roles = readConfigSection(config, 'roles', errors, 'roles');
698
+ for (const [role, options] of Object.entries(roles)) {
699
+ const entry = graph.roles.find(candidate => candidate.role === role || candidate.id === role);
700
+ if (!entry) {
701
+ errors.push({
702
+ code: 'UNKNOWN_ROLE',
703
+ message: `Unknown role '${role}' in ${CONFIG_SOURCE}.`,
704
+ });
705
+ continue;
706
+ }
707
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
708
+ errors.push({
709
+ code: 'INVALID_ROLE_CONFIG',
710
+ message: `role '${role}' config must be an object.`,
711
+ });
712
+ continue;
713
+ }
714
+
715
+ let changed = false;
716
+ const skill = readRoleStringField(options, 'skill', role, errors);
717
+ if (skill.present && skill.value !== undefined) {
718
+ entry.skill = skill.value;
719
+ changed = true;
720
+ }
721
+ const ideology = readRoleStringField(options, 'ideology', role, errors);
722
+ if (ideology.present && ideology.value !== undefined) {
723
+ entry.ideology = ideology.value;
724
+ changed = true;
725
+ }
726
+ if (changed) {
727
+ entry.configSource = CONFIG_SOURCE;
728
+ }
729
+ }
730
+ }
731
+
732
+ function assertStringList(value, errors, code, message) {
733
+ if (!Array.isArray(value)) {
734
+ errors.push({ code, message });
735
+ return false;
736
+ }
737
+
738
+ let valid = true;
739
+ for (const item of value) {
740
+ if (typeof item !== 'string' || item.trim() === '') {
741
+ errors.push({ code, message });
742
+ valid = false;
743
+ break;
744
+ }
745
+ }
746
+ return valid;
747
+ }
748
+
749
+ function validatePlanSubSkillList(value, errors, path) {
750
+ if (!assertStringList(
751
+ value,
752
+ errors,
753
+ 'INVALID_PLAN_SUBSKILL_LIST',
754
+ `${path} must be a list of planning sub-skill IDs.`
755
+ )) {
756
+ return undefined;
757
+ }
758
+
759
+ const normalized = value.map(item => item.trim());
760
+ let hasUnknown = false;
761
+ for (const id of normalized) {
762
+ if (!PLAN_SUBSKILL_IDS.has(id)) {
763
+ hasUnknown = true;
764
+ errors.push({
765
+ code: 'UNKNOWN_PLAN_SUBSKILL',
766
+ message: `Unknown planning sub-skill '${id}' in ${CONFIG_SOURCE}.`,
767
+ });
768
+ }
769
+ }
770
+ if (hasUnknown) {
771
+ return undefined;
772
+ }
773
+ return normalized;
774
+ }
775
+
776
+ function applyPlanningMode(template, nextTemplate, errors) {
777
+ if (!Object.hasOwn(template, 'mode')) return;
778
+ if (!['full', 'partial'].includes(template.mode)) {
779
+ errors.push({
780
+ code: 'INVALID_PLANNING_MODE',
781
+ message: 'planning.template.mode must be full or partial.',
782
+ });
783
+ return;
784
+ }
785
+ nextTemplate.mode = template.mode;
786
+ }
787
+
788
+ function applyConvergenceThreshold(template, nextTemplate, errors) {
789
+ if (!Object.hasOwn(template, 'convergenceThreshold')) return;
790
+ if (!Number.isFinite(template.convergenceThreshold)
791
+ || template.convergenceThreshold < 0
792
+ || template.convergenceThreshold > 1) {
793
+ errors.push({
794
+ code: 'INVALID_CONVERGENCE_THRESHOLD',
795
+ message: 'planning.template.convergenceThreshold must be a number from 0 to 1.',
796
+ });
797
+ return;
798
+ }
799
+ nextTemplate.convergenceThreshold = template.convergenceThreshold;
800
+ }
801
+
802
+ function applyCriticSet(template, nextTemplate, errors) {
803
+ if (!Object.hasOwn(template, 'criticSet')) return;
804
+ if (assertStringList(template.criticSet, errors, 'INVALID_CRITIC', 'planning.template.criticSet must contain non-empty strings.')) {
805
+ nextTemplate.criticSet = template.criticSet.map(item => item.trim());
806
+ }
807
+ }
808
+
809
+ function applyPartialInvocation(template, nextTemplate, errors) {
810
+ if (!Object.hasOwn(template, 'partialInvocation')) return;
811
+ const partial = readConfigSection(template, 'partialInvocation', errors, 'planning.template.partialInvocation');
812
+ if (Object.hasOwn(partial, 'only')) {
813
+ const only = validatePlanSubSkillList(partial.only, errors, 'planning.template.partialInvocation.only');
814
+ if (only) nextTemplate.partialInvocation.only = only;
815
+ }
816
+ if (Object.hasOwn(partial, 'skip')) {
817
+ const skip = validatePlanSubSkillList(partial.skip, errors, 'planning.template.partialInvocation.skip');
818
+ if (skip) nextTemplate.partialInvocation.skip = skip;
819
+ }
820
+ }
821
+
822
+ function applyPlanningConfig(graph, config, errors) {
823
+ const planning = readConfigSection(config, 'planning', errors, 'planning');
824
+ const template = readConfigSection(planning, 'template', errors, 'planning.template');
825
+ if (Object.keys(template).length === 0) {
826
+ return;
827
+ }
828
+
829
+ const nextTemplate = { ...graph.planning.template, partialInvocation: { ...graph.planning.template.partialInvocation } };
830
+ applyPlanningMode(template, nextTemplate, errors);
831
+ applyConvergenceThreshold(template, nextTemplate, errors);
832
+ applyCriticSet(template, nextTemplate, errors);
833
+ applyPartialInvocation(template, nextTemplate, errors);
834
+ nextTemplate.configSource = CONFIG_SOURCE;
835
+ graph.planning.template = nextTemplate;
836
+ }
837
+
838
+ function validateProtectedPaths(config, errors) {
839
+ if (!Object.hasOwn(config, 'protectedPaths')) {
840
+ return [];
841
+ }
842
+ if (!Array.isArray(config.protectedPaths)) {
843
+ errors.push({
844
+ code: 'PROTECTED_PATHS_INVALID',
845
+ message: 'protectedPaths must be a list of path patterns.',
846
+ });
847
+ return [];
848
+ }
849
+
850
+ const protectedPaths = [];
851
+ for (const [index, entry] of config.protectedPaths.entries()) {
852
+ if (typeof entry !== 'string' || entry.trim() === '') {
853
+ errors.push({
854
+ code: 'PROTECTED_PATH_INVALID',
855
+ message: `protectedPaths[${index}] must be a non-empty string.`,
856
+ });
857
+ continue;
858
+ }
859
+ const pattern = entry.trim();
860
+ if (['*', '**', '**/*', '.', './', '/'].includes(pattern)) {
861
+ errors.push({
862
+ code: 'PROTECTED_PATH_TOO_BROAD',
863
+ message: `protectedPaths[${index}] is too broad: ${pattern}`,
864
+ });
865
+ continue;
866
+ }
867
+ protectedPaths.push(pattern);
868
+ }
869
+ return protectedPaths;
870
+ }
871
+
872
+ function resolveRuntimeGraph({ projectRoot = process.cwd(), throwOnError = true } = {}) {
873
+ const graph = cloneJson(RESOLVED_RUNTIME_GRAPH);
874
+ const loaded = loadRuntimeGraphConfig({ projectRoot });
875
+ const errors = [...loaded.errors];
876
+
877
+ if (loaded.errors.length === 0) {
878
+ applyRailConfig(graph, loaded.config, errors);
879
+ applyWorkflowConfig(graph, loaded.config, errors);
880
+ applyAdapterConfig(graph, loaded.config, errors);
881
+ applyRoleConfig(graph, loaded.config, errors);
882
+ applyPlanningConfig(graph, loaded.config, errors);
883
+ graph.protectedPaths = validateProtectedPaths(loaded.config, errors);
884
+ }
885
+
886
+ graph.config = {
887
+ path: CONFIG_SOURCE,
888
+ loaded: loaded.exists,
889
+ errors,
890
+ };
891
+
892
+ if (throwOnError && errors.length > 0) {
893
+ throw new Error(errors.map(error => error.message).join('\n'));
894
+ }
895
+
896
+ return { graph, errors };
897
+ }
898
+
899
+ function getResolvedRuntimeGraph(options = {}) {
900
+ return resolveRuntimeGraph(options).graph;
901
+ }
902
+
903
+ function getDefaultRuntimeGraph() {
904
+ return cloneJson(RESOLVED_RUNTIME_GRAPH);
905
+ }
906
+
907
+ function lintRuntimeGraphConfig(options = {}) {
908
+ const { graph, errors } = resolveRuntimeGraph({ ...options, throwOnError: false });
909
+ return {
910
+ ok: errors.length === 0,
911
+ errors,
912
+ warnings: [],
913
+ graph,
914
+ };
915
+ }
916
+
917
+ function buildRuntimeGraphEnvelope(graph = getResolvedRuntimeGraph()) {
918
+ return {
919
+ schemaVersion: RUNTIME_GRAPH_SCHEMA_VERSION,
920
+ kind: 'forge.runtimeGraph',
921
+ schema: cloneJson(RUNTIME_GRAPH_SCHEMA),
922
+ graph: cloneJson(graph),
923
+ };
924
+ }
925
+
926
+ module.exports = {
927
+ RUNTIME_GRAPH_SCHEMA_VERSION,
928
+ RUNTIME_GRAPH_SCHEMA,
929
+ Phase,
930
+ Action,
931
+ Artifact,
932
+ EvaluatorRegion,
933
+ Gate,
934
+ Evidence,
935
+ Rail,
936
+ Adapter,
937
+ PlanningSubSkill,
938
+ Role,
939
+ ROLE_IDS,
940
+ loadRuntimeGraphConfig,
941
+ lintRuntimeGraphConfig,
942
+ resolveRuntimeGraph,
943
+ getDefaultRuntimeGraph,
944
+ buildRuntimeGraphEnvelope,
945
+ getResolvedRuntimeGraph,
946
+ };