forge-workflow 0.0.10 → 0.1.0-beta.3

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 (468) 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 +82 -5
  5. package/.forge/hooks/forge-native-hook.js +431 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +709 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +175 -0
  11. package/README.md +275 -365
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +532 -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 +121 -0
  29. package/docs/guides/SUPPORT.md +190 -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 +214 -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 +155 -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/activation/ensure-forge-home.js +135 -0
  67. package/lib/adapter-cli.js +307 -0
  68. package/lib/adapters/beads-issue-adapter.js +127 -0
  69. package/lib/adapters/beads-kernel-compat.js +1109 -0
  70. package/lib/adapters/greptile-review-adapter.js +141 -0
  71. package/lib/adapters/kernel-issue-adapter.js +101 -0
  72. package/lib/adapters/pr-state-adapter.js +484 -0
  73. package/lib/adoption-profiles.js +139 -0
  74. package/lib/agents/README.md +2 -6
  75. package/lib/agents/claude.plugin.json +3 -8
  76. package/lib/agents/codex.plugin.json +9 -1
  77. package/lib/agents/cursor.plugin.json +2 -6
  78. package/lib/agents/hermes.plugin.json +22 -0
  79. package/lib/agents-config.js +39 -1236
  80. package/lib/audit-evidence.js +282 -0
  81. package/lib/beads-detect.js +60 -0
  82. package/lib/beads-nudge.js +91 -0
  83. package/lib/beads-setup.js +121 -0
  84. package/lib/beads-sync-scaffold.js +25 -101
  85. package/lib/codex-skills.js +51 -1
  86. package/lib/commands/_aliases.js +248 -0
  87. package/lib/commands/_issue.js +780 -77
  88. package/lib/commands/_manifest.js +93 -0
  89. package/lib/commands/_registry.js +99 -34
  90. package/lib/commands/_resolve-command-opts.js +230 -0
  91. package/lib/commands/_serve-security.js +270 -0
  92. package/lib/commands/adapter.js +12 -0
  93. package/lib/commands/add.js +118 -0
  94. package/lib/commands/audit.js +70 -0
  95. package/lib/commands/blocked.js +5 -0
  96. package/lib/commands/board.js +64 -0
  97. package/lib/commands/claim.js +21 -2
  98. package/lib/commands/claims.js +7 -0
  99. package/lib/commands/clean.js +485 -75
  100. package/lib/commands/close.js +2 -2
  101. package/lib/commands/comment.js +5 -0
  102. package/lib/commands/control.js +148 -0
  103. package/lib/commands/create.js +2 -2
  104. package/lib/commands/dev.js +185 -7
  105. package/lib/commands/doc-gate.js +336 -0
  106. package/lib/commands/doctor.js +156 -0
  107. package/lib/commands/explain.js +15 -0
  108. package/lib/commands/export.js +237 -0
  109. package/lib/commands/gate.js +209 -0
  110. package/lib/commands/hooks.js +377 -0
  111. package/lib/commands/inbox.js +118 -0
  112. package/lib/commands/init.js +604 -0
  113. package/lib/commands/insights.js +79 -0
  114. package/lib/commands/issue.js +12 -1
  115. package/lib/commands/issues.js +17 -0
  116. package/lib/commands/lint.js +5 -0
  117. package/lib/commands/list.js +2 -2
  118. package/lib/commands/memory.js +81 -0
  119. package/lib/commands/merge.js +312 -0
  120. package/lib/commands/migrate.js +362 -0
  121. package/lib/commands/new.js +12 -0
  122. package/lib/commands/options.js +241 -0
  123. package/lib/commands/orient.js +13 -0
  124. package/lib/commands/orphans.js +5 -0
  125. package/lib/commands/patch.js +67 -0
  126. package/lib/commands/plan.js +481 -29
  127. package/lib/commands/pr.js +88 -0
  128. package/lib/commands/preflight.js +211 -0
  129. package/lib/commands/prime.js +13 -0
  130. package/lib/commands/push.js +135 -2
  131. package/lib/commands/ready.js +2 -2
  132. package/lib/commands/recall.js +171 -0
  133. package/lib/commands/recap.js +75 -0
  134. package/lib/commands/recommend.js +0 -1
  135. package/lib/commands/release.js +104 -0
  136. package/lib/commands/remember.js +140 -0
  137. package/lib/commands/role.js +99 -0
  138. package/lib/commands/serve.js +581 -0
  139. package/lib/commands/setup.js +900 -971
  140. package/lib/commands/shepherd.js +501 -0
  141. package/lib/commands/ship.js +59 -1
  142. package/lib/commands/show.js +2 -2
  143. package/lib/commands/stage.js +192 -0
  144. package/lib/commands/stale.js +5 -0
  145. package/lib/commands/status.js +158 -21
  146. package/lib/commands/sync.js +34 -46
  147. package/lib/commands/team.js +4 -1
  148. package/lib/commands/test.js +43 -27
  149. package/lib/commands/update.js +2 -2
  150. package/lib/commands/upgrade.js +47 -0
  151. package/lib/commands/validate.js +43 -18
  152. package/lib/commands/worktree.js +362 -99
  153. package/lib/config-writer.js +202 -0
  154. package/lib/control-plane.js +236 -0
  155. package/lib/core/runtime-graph.js +977 -0
  156. package/lib/dep-guard/keyword-ripple.js +2 -2
  157. package/lib/deprecated-sync-cleanup.js +362 -0
  158. package/lib/detect-agent.js +2 -28
  159. package/lib/detect-worktree.js +35 -9
  160. package/lib/doc-gate/declaration.js +177 -0
  161. package/lib/doc-gate/detect.js +289 -0
  162. package/lib/doc-gate/gate.js +375 -0
  163. package/lib/doc-gate/okf-config.js +128 -0
  164. package/lib/doc-gate/okf.js +429 -0
  165. package/lib/docs-command.js +1161 -6
  166. package/lib/forge-issues.js +382 -11
  167. package/lib/forge-lock.js +262 -0
  168. package/lib/gate-events.js +192 -0
  169. package/lib/global-flags.js +104 -0
  170. package/lib/greptile-match.js +7 -63
  171. package/lib/grounding/context-events.js +230 -0
  172. package/lib/grounding/read-first.js +112 -0
  173. package/lib/harness-capability-matrix.js +380 -0
  174. package/lib/hook-global-installer.js +347 -0
  175. package/lib/hook-renderer.js +541 -0
  176. package/lib/inbox.js +391 -0
  177. package/lib/insights.js +397 -0
  178. package/lib/issue-adapter.js +156 -0
  179. package/lib/issue-backend.js +145 -0
  180. package/lib/issue-render.js +220 -0
  181. package/lib/kernel/backing-issue.js +311 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/owned-kernel.js +43 -0
  192. package/lib/kernel/planning-buckets-schema.js +109 -0
  193. package/lib/kernel/projection-jsonl-writer.js +450 -0
  194. package/lib/kernel/readiness-model.js +329 -0
  195. package/lib/kernel/schema.js +356 -0
  196. package/lib/kernel/sqlite-driver.js +2540 -0
  197. package/lib/kernel/taxonomy-validator.js +394 -0
  198. package/lib/lefthook-check.js +3 -2
  199. package/lib/lefthook-wiring.js +413 -0
  200. package/lib/mcp-config-renderer.js +288 -0
  201. package/lib/memory/graphiti-mcp.js +106 -0
  202. package/lib/memory/router.js +387 -0
  203. package/lib/memory/typed-api.js +102 -0
  204. package/lib/memory-digest.js +195 -0
  205. package/lib/merge-rules.js +395 -0
  206. package/lib/migrate-dry-run.js +466 -0
  207. package/lib/orientation.js +863 -0
  208. package/lib/package-manager-remediation.js +103 -0
  209. package/lib/package-root.js +381 -0
  210. package/lib/patch-intent.js +890 -0
  211. package/lib/plugin-catalog.js +3 -4
  212. package/lib/plugin-manager.js +0 -5
  213. package/lib/pr-bundle.js +186 -0
  214. package/lib/pr-monitor/auto-actions.js +175 -0
  215. package/lib/pr-monitor/differ.js +195 -0
  216. package/lib/pr-monitor/digest.js +206 -0
  217. package/lib/pr-monitor/events.js +0 -0
  218. package/lib/pr-monitor/gather.js +124 -0
  219. package/lib/pr-monitor/journal.js +299 -0
  220. package/lib/pr-monitor/monitor.js +146 -0
  221. package/lib/pr-monitor/render-sticky.js +192 -0
  222. package/lib/pr-monitor/upsert-sticky.js +169 -0
  223. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  224. package/lib/pr-monitor/watch.js +247 -0
  225. package/lib/pr-pull.js +1314 -0
  226. package/lib/pr-shepherd.js +494 -0
  227. package/lib/pr-state-validator.js +59 -0
  228. package/lib/preflight/gates.js +237 -0
  229. package/lib/preflight/runner.js +116 -0
  230. package/lib/project-discovery.js +0 -53
  231. package/lib/project-memory.js +99 -497
  232. package/lib/protected-path-manifest.js +281 -0
  233. package/lib/protected-state-surfaces.js +387 -0
  234. package/lib/release-readiness.js +2105 -0
  235. package/lib/reset.js +59 -45
  236. package/lib/review-adapter.js +68 -0
  237. package/lib/rules-sync.js +260 -0
  238. package/lib/runtime-health.js +241 -20
  239. package/lib/safety-config-renderer.js +268 -0
  240. package/lib/setup-action-log.js +1 -7
  241. package/lib/setup.js +27 -65
  242. package/lib/shell-utils.js +76 -6
  243. package/lib/skills-sync.js +330 -0
  244. package/lib/smart-status/scoring.js +17 -3
  245. package/lib/status/beads-snapshot.js +45 -2
  246. package/lib/status/presenter.js +169 -18
  247. package/lib/status/snapshot.js +186 -0
  248. package/lib/sync-backend.js +202 -0
  249. package/lib/untrusted-content.js +52 -0
  250. package/lib/upgrade-safety.js +251 -0
  251. package/lib/workflow/enforce-stage.js +351 -45
  252. package/lib/workflow/stage-transition.js +115 -0
  253. package/lib/workflow/stages.js +30 -6
  254. package/lib/workflow/state-manager.js +11 -22
  255. package/lib/workflow/state.js +23 -1
  256. package/lib/workflow-profiles.js +17 -5
  257. package/package.json +37 -35
  258. package/rules/documentation.md +19 -0
  259. package/rules/kernel-tracking.md +26 -0
  260. package/rules/security.md +22 -0
  261. package/rules/tdd.md +20 -0
  262. package/rules/workflow.md +27 -0
  263. package/scripts/auto-backing-issue.js +47 -0
  264. package/scripts/beads-context.sh +81 -57
  265. package/scripts/beads-upgrade-smoke.sh +24 -3
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard.sh +22 -3
  272. package/scripts/file-index.sh +3 -0
  273. package/scripts/forge-team/lib/claim.sh +34 -18
  274. package/scripts/forge-team/lib/dashboard.sh +61 -86
  275. package/scripts/forge-team/lib/epic.sh +99 -263
  276. package/scripts/forge-team/lib/hooks.sh +26 -28
  277. package/scripts/forge-team/lib/identity.sh +4 -4
  278. package/scripts/forge-team/lib/sync-github.sh +49 -84
  279. package/scripts/forge-team/lib/verify.sh +93 -83
  280. package/scripts/forge-team/lib/workload.sh +41 -65
  281. package/scripts/forge-team/tests/claim.test.sh +25 -19
  282. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  283. package/scripts/forge-team/tests/epic.test.sh +52 -71
  284. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  285. package/scripts/forge-team/tests/identity.test.sh +3 -3
  286. package/scripts/forge-team/tests/integration.test.sh +44 -66
  287. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  288. package/scripts/forge-team/tests/verify.test.sh +37 -46
  289. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  290. package/scripts/forge-team/tests/workload.test.sh +32 -66
  291. package/scripts/gen-command-manifest.js +153 -0
  292. package/scripts/gen-embedded-assets.mjs +129 -0
  293. package/scripts/install.ps1 +139 -0
  294. package/scripts/install.sh +268 -0
  295. package/scripts/lib/release-asset.mjs +84 -0
  296. package/scripts/parity-check.mjs +145 -0
  297. package/scripts/parity-check.test.mjs +58 -0
  298. package/scripts/pin-agentic-workflow-images.js +112 -0
  299. package/scripts/pr-auto-actions.js +93 -0
  300. package/scripts/pr-coordinator.sh +3 -0
  301. package/scripts/pr-verdict-label.js +50 -0
  302. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  303. package/scripts/preflight.sh +21 -94
  304. package/scripts/protected-state-check.js +104 -0
  305. package/scripts/smart-status.sh +60 -57
  306. package/scripts/spikes/config-race-bench.js +111 -0
  307. package/scripts/spikes/harness-capability-matrix.js +13 -0
  308. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  309. package/scripts/spikes/protected-path-manifest.js +20 -0
  310. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  311. package/scripts/sync-agent-skills.js +62 -0
  312. package/scripts/sync-utils.sh +3 -0
  313. package/scripts/test-ci-shard.js +13 -6
  314. package/scripts/test.js +95 -12
  315. package/skills/claim-safety/SKILL.md +102 -0
  316. package/skills/claim-safety/evals/evals.json +46 -0
  317. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  318. package/skills/dev/evals/evals.json +50 -0
  319. package/skills/hermes-forge/SKILL.md +185 -0
  320. package/skills/hermes-forge/evals/evals.json +46 -0
  321. package/skills/issue-basics/SKILL.md +111 -0
  322. package/skills/issue-basics/evals/evals.json +46 -0
  323. package/skills/kernel/SKILL.md +166 -0
  324. package/skills/kernel/evals/evals.json +50 -0
  325. package/skills/memory/SKILL.md +102 -0
  326. package/skills/parallel-deep-research/SKILL.md +14 -11
  327. package/skills/parallel-deep-research/evals/evals.json +11 -27
  328. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  329. package/skills/plan/evals/evals.json +42 -0
  330. package/skills/research/SKILL.md +195 -0
  331. package/skills/research/evals/evals.json +42 -0
  332. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  333. package/skills/review/evals/evals.json +42 -0
  334. package/skills/rollback/SKILL.md +110 -0
  335. package/skills/rollback/evals/evals.json +46 -0
  336. package/skills/rollback/references/methods.md +204 -0
  337. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  338. package/skills/shepherd/SKILL.md +66 -0
  339. package/skills/shepherd/evals/evals.json +42 -0
  340. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  341. package/skills/ship/evals/evals.json +42 -0
  342. package/skills/smith/SKILL.md +142 -0
  343. package/skills/smith/evals/evals.json +46 -0
  344. package/skills/smith/references/autonomy-and-gates.md +94 -0
  345. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  346. package/skills/sonarcloud/evals/evals.json +46 -0
  347. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  348. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  349. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  350. package/skills/status/evals/evals.json +50 -0
  351. package/skills/triage-ready/SKILL.md +121 -0
  352. package/skills/triage-ready/evals/evals.json +42 -0
  353. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  354. package/skills/validate/evals/evals.json +42 -0
  355. package/skills/verify/SKILL.md +299 -0
  356. package/skills/verify/evals/evals.json +50 -0
  357. package/.claude/commands/dev.md +0 -345
  358. package/.claude/commands/plan.md +0 -566
  359. package/.claude/commands/premerge.md +0 -186
  360. package/.claude/commands/research.md +0 -42
  361. package/.claude/commands/review.md +0 -451
  362. package/.claude/commands/rollback.md +0 -721
  363. package/.claude/commands/ship.md +0 -213
  364. package/.claude/commands/sonarcloud.md +0 -152
  365. package/.claude/commands/status.md +0 -90
  366. package/.claude/commands/validate.md +0 -288
  367. package/.claude/commands/verify.md +0 -269
  368. package/.claude/rules/workflow.md +0 -121
  369. package/.cline/workflows/dev.md +0 -342
  370. package/.cline/workflows/plan.md +0 -563
  371. package/.cline/workflows/premerge.md +0 -183
  372. package/.cline/workflows/research.md +0 -39
  373. package/.cline/workflows/review.md +0 -448
  374. package/.cline/workflows/rollback.md +0 -718
  375. package/.cline/workflows/ship.md +0 -210
  376. package/.cline/workflows/sonarcloud.md +0 -146
  377. package/.cline/workflows/status.md +0 -87
  378. package/.cline/workflows/validate.md +0 -285
  379. package/.cline/workflows/verify.md +0 -266
  380. package/.codex/config.toml +0 -11
  381. package/.codex/skills/dev/SKILL.md +0 -345
  382. package/.codex/skills/plan/SKILL.md +0 -566
  383. package/.codex/skills/premerge/SKILL.md +0 -186
  384. package/.codex/skills/research/SKILL.md +0 -42
  385. package/.codex/skills/review/SKILL.md +0 -451
  386. package/.codex/skills/rollback/SKILL.md +0 -721
  387. package/.codex/skills/ship/SKILL.md +0 -213
  388. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  389. package/.codex/skills/status/SKILL.md +0 -90
  390. package/.codex/skills/validate/SKILL.md +0 -288
  391. package/.codex/skills/verify/SKILL.md +0 -269
  392. package/.cursor/commands/dev.md +0 -342
  393. package/.cursor/commands/plan.md +0 -563
  394. package/.cursor/commands/premerge.md +0 -183
  395. package/.cursor/commands/research.md +0 -39
  396. package/.cursor/commands/review.md +0 -448
  397. package/.cursor/commands/ship.md +0 -210
  398. package/.cursor/commands/sonarcloud.md +0 -146
  399. package/.cursor/commands/status.md +0 -87
  400. package/.cursor/commands/validate.md +0 -285
  401. package/.cursor/commands/verify.md +0 -266
  402. package/.cursorrules +0 -149
  403. package/.github/prompts/premerge.prompt.md +0 -188
  404. package/.github/prompts/research.prompt.md +0 -44
  405. package/.github/prompts/rollback.prompt.md +0 -723
  406. package/.github/prompts/verify.prompt.md +0 -271
  407. package/.github/workflows/beads-to-github.yml +0 -89
  408. package/.github/workflows/github-to-beads.yml +0 -100
  409. package/.kilocode/workflows/dev.md +0 -346
  410. package/.kilocode/workflows/plan.md +0 -567
  411. package/.kilocode/workflows/premerge.md +0 -187
  412. package/.kilocode/workflows/research.md +0 -43
  413. package/.kilocode/workflows/review.md +0 -452
  414. package/.kilocode/workflows/rollback.md +0 -722
  415. package/.kilocode/workflows/ship.md +0 -214
  416. package/.kilocode/workflows/sonarcloud.md +0 -150
  417. package/.kilocode/workflows/status.md +0 -91
  418. package/.kilocode/workflows/validate.md +0 -289
  419. package/.kilocode/workflows/verify.md +0 -270
  420. package/.opencode/commands/dev.md +0 -345
  421. package/.opencode/commands/plan.md +0 -566
  422. package/.opencode/commands/premerge.md +0 -186
  423. package/.opencode/commands/research.md +0 -42
  424. package/.opencode/commands/review.md +0 -451
  425. package/.opencode/commands/rollback.md +0 -721
  426. package/.opencode/commands/ship.md +0 -213
  427. package/.opencode/commands/sonarcloud.md +0 -149
  428. package/.opencode/commands/status.md +0 -90
  429. package/.opencode/commands/validate.md +0 -288
  430. package/.opencode/commands/verify.md +0 -269
  431. package/.roo/commands/dev.md +0 -346
  432. package/.roo/commands/plan.md +0 -567
  433. package/.roo/commands/premerge.md +0 -187
  434. package/.roo/commands/research.md +0 -43
  435. package/.roo/commands/review.md +0 -452
  436. package/.roo/commands/rollback.md +0 -722
  437. package/.roo/commands/ship.md +0 -214
  438. package/.roo/commands/sonarcloud.md +0 -150
  439. package/.roo/commands/status.md +0 -91
  440. package/.roo/commands/validate.md +0 -289
  441. package/.roo/commands/verify.md +0 -270
  442. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  443. package/docs/GREPTILE_SETUP.md +0 -400
  444. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  445. package/docs/SETUP.md +0 -663
  446. package/docs/VALIDATION.md +0 -363
  447. package/lib/agents/cline.plugin.json +0 -29
  448. package/lib/agents/copilot.plugin.json +0 -24
  449. package/lib/agents/kilocode.plugin.json +0 -22
  450. package/lib/agents/opencode.plugin.json +0 -23
  451. package/lib/agents/roo.plugin.json +0 -30
  452. package/lib/beads-bootstrap.js +0 -225
  453. package/lib/beads-health-check.js +0 -188
  454. package/lib/commands/commands-reset.js +0 -147
  455. package/opencode.json +0 -67
  456. package/scripts/beads-context.test.js +0 -584
  457. package/scripts/github-beads-sync/comment.mjs +0 -64
  458. package/scripts/github-beads-sync/config.mjs +0 -148
  459. package/scripts/github-beads-sync/github-api.mjs +0 -131
  460. package/scripts/github-beads-sync/index.mjs +0 -356
  461. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  462. package/scripts/github-beads-sync/mapping.mjs +0 -132
  463. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  464. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  465. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  466. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  467. package/scripts/github-beads-sync.config.json +0 -26
  468. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,977 @@
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
+ // Grounding gates (epic 6ef96e92, design docs/work/2026-07-16-grounding-
416
+ // enforcement/design.md) — the first gates that DENY (fd4c03b3's first real
417
+ // payment). gate.read_first: acting on an issue requires having read it —
418
+ // consulted fail-closed at the `forge claim` boundary against a
419
+ // `context.loaded` kernel event (lib/grounding/context-events.js), remedy
420
+ // `forge recap <id>`. gate.cite: shipped artifacts must cite their sources —
421
+ // registered here (togglable) but its scanner lands in P3; it denies nothing
422
+ // yet. Both phase-less, default-ON, UNLOCKED, same toggle surface as
423
+ // gate.issue_verify (`forge gate disable gate.read_first`). Master switch is
424
+ // the unlocked rail.grounding below. `requires: []` — the context.loaded
425
+ // event (not evidence) is the exit condition.
426
+ Gate({
427
+ id: 'gate.read_first',
428
+ label: 'Read the issue before acting on it',
429
+ requires: [],
430
+ }),
431
+ Gate({
432
+ id: 'gate.cite',
433
+ label: 'Cite sources in shipped artifacts',
434
+ requires: [],
435
+ }),
436
+ ],
437
+ evidence: [
438
+ Evidence({
439
+ id: 'evidence.command-docs',
440
+ kind: 'checked-in-doc',
441
+ source: 'skills/*/SKILL.md',
442
+ description: 'Skill docs define the compatibility surface.',
443
+ }),
444
+ Evidence({
445
+ id: 'evidence.tdd-tests',
446
+ kind: 'test-output',
447
+ source: 'bun test',
448
+ description: 'Task-level failing then passing test output.',
449
+ }),
450
+ Evidence({
451
+ id: 'evidence.validation-output',
452
+ kind: 'validation-output',
453
+ source: 'bun run typecheck; bun run lint; bun test; bun audit',
454
+ description: 'Fresh validation evidence for the branch.',
455
+ }),
456
+ Evidence({
457
+ id: 'evidence.dry-run-report',
458
+ kind: 'dry-run-output',
459
+ source: 'forge migrate --dry-run',
460
+ description: 'Resolved graph can be printed without side effects.',
461
+ }),
462
+ ],
463
+ // Rails are declared as a data table and mapped through Rail() so each entry
464
+ // stays a single line (Rail defaults locked: true; kernel_tracking is the one
465
+ // UNLOCKED, default-ON rail — it ingrains "file every issue/idea/bug/decision
466
+ // to the kernel immediately; nothing discussed goes missing", and a maintainer
467
+ // may opt out via `forge gate disable rail.kernel_tracking`, consumed by the
468
+ // resolver's rail-aware gate loop over workflow.gates.<id>.enabled).
469
+ rails: [
470
+ { key: 'tdd_intent', label: 'TDD intent evidence', description: 'Source changes require test intent and TDD evidence. Strong default (ON), but not a hard floor — disable with `forge gate disable rail.tdd_intent`.', locked: false },
471
+ { key: 'secret_scan', label: 'Secret scan', description: 'Validation must not knowingly ship secrets.' },
472
+ { key: 'branch_protection', label: 'Branch protection', description: 'Ship through reviewed branches instead of direct protected-branch edits.' },
473
+ { key: 'signed_commits', label: 'Signed commits', description: 'Preserve commit provenance requirements where configured.' },
474
+ { key: 'schema_integrity', label: 'Schema integrity', description: 'Keep runtime graph and config schemas internally consistent.' },
475
+ { key: 'kernel_tracking', label: 'Kernel issue tracking', description: 'Every issue, idea, bug, and decision discussed is filed to the Forge Kernel.', locked: false },
476
+ { key: 'auto_shepherd', label: 'Auto-start PR shepherd watch', description: 'On `forge ship` success, auto-start the detached, self-stopping `forge shepherd watch <pr>` monitor so a shipped PR is tended without a manual trigger. Default-ON, UNLOCKED — opt out with `forge gate disable rail.auto_shepherd`.', locked: false },
477
+ // grounding — the one-switch master toggle over gate.read_first + gate.cite.
478
+ // UNLOCKED, default-ON (like kernel_tracking): a maintainer may opt out via
479
+ // `forge gate disable rail.grounding`, which the read_first consult treats as
480
+ // "allow, logged". Ingrains "read the documented source before acting; cite it".
481
+ { key: 'grounding', label: 'Documented-grounding enforcement', description: 'Read the issue before acting on it; cite sources in shipped artifacts.', locked: false },
482
+ ].map(def => Rail({ id: `rail.${def.key}`, ...def })),
483
+ adapters: [
484
+ Adapter({
485
+ id: 'adapter.issue',
486
+ kind: 'issue',
487
+ label: 'Issue tracking adapter',
488
+ config: { primary: 'beads', mirrors: ['github'] },
489
+ }),
490
+ Adapter({
491
+ id: 'adapter.harness',
492
+ kind: 'harness',
493
+ label: 'Agent harness adapter',
494
+ config: { targets: ['codex', 'claude', 'cursor'] },
495
+ }),
496
+ ],
497
+ protectedPaths: [],
498
+ edges: [
499
+ { from: 'phase.plan', to: 'phase.dev', reason: 'design and task list feed implementation' },
500
+ { from: 'phase.dev', to: 'phase.validate', reason: 'implementation feeds validation' },
501
+ { from: 'phase.validate', to: 'phase.ship', reason: 'validated branch can be shipped' },
502
+ ],
503
+ };
504
+
505
+ function cloneJson(value) {
506
+ return JSON.parse(JSON.stringify(value));
507
+ }
508
+
509
+ function getConfigPath(projectRoot = process.cwd()) {
510
+ return path.join(projectRoot, '.forge', 'config.yaml');
511
+ }
512
+
513
+ function loadRuntimeGraphConfig({ projectRoot = process.cwd() } = {}) {
514
+ const configPath = getConfigPath(projectRoot);
515
+ if (!fs.existsSync(configPath)) {
516
+ return { config: {}, configPath, exists: false, errors: [] };
517
+ }
518
+
519
+ try {
520
+ const rawConfig = fs.readFileSync(configPath, 'utf8');
521
+ const parsed = rawConfig.trim() === '' ? {} : YAML.parse(rawConfig);
522
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
523
+ return {
524
+ config: {},
525
+ configPath,
526
+ exists: true,
527
+ errors: [{
528
+ code: 'CONFIG_ROOT_INVALID',
529
+ message: `${CONFIG_SOURCE} root must be an object.`,
530
+ }],
531
+ };
532
+ }
533
+ return {
534
+ config: parsed,
535
+ configPath,
536
+ exists: true,
537
+ errors: [],
538
+ };
539
+ } catch (err) {
540
+ return {
541
+ config: {},
542
+ configPath,
543
+ exists: true,
544
+ errors: [{
545
+ code: 'CONFIG_PARSE_ERROR',
546
+ message: `Invalid ${CONFIG_SOURCE}: ${err.message}`,
547
+ }],
548
+ };
549
+ }
550
+ }
551
+
552
+ function markConfigured(primitive, options) {
553
+ Object.assign(primitive, withRuntimeState(primitive, {
554
+ enabled: options.enabled,
555
+ locked: primitive.locked === true,
556
+ configSource: CONFIG_SOURCE,
557
+ }));
558
+ }
559
+
560
+ function readEnabledOption(options, errors, typeName, id) {
561
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
562
+ errors.push({
563
+ code: 'INVALID_PRIMITIVE_CONFIG',
564
+ message: `${typeName} '${id}' config must be an object.`,
565
+ });
566
+ return { present: false, valid: false, value: undefined };
567
+ }
568
+ if (!Object.hasOwn(options, 'enabled')) {
569
+ return { present: false, valid: true, value: undefined };
570
+ }
571
+ if (typeof options.enabled !== 'boolean') {
572
+ errors.push({
573
+ code: 'INVALID_ENABLED_VALUE',
574
+ message: `${typeName} '${id}' enabled must be a boolean.`,
575
+ });
576
+ return { present: true, valid: false, value: undefined };
577
+ }
578
+ return { present: true, valid: true, value: options.enabled };
579
+ }
580
+
581
+ function readConfigSection(parent, key, errors, path) {
582
+ if (!Object.hasOwn(parent, key)) {
583
+ return {};
584
+ }
585
+ const value = parent[key];
586
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
587
+ errors.push({
588
+ code: 'INVALID_CONFIG_SECTION',
589
+ message: `${path} must be an object.`,
590
+ });
591
+ return {};
592
+ }
593
+ return value;
594
+ }
595
+
596
+ function applyEnabledConfig(collection, id, options, errors, typeName) {
597
+ const primitive = collection.find(candidate => candidate.id === id);
598
+ if (!primitive) {
599
+ errors.push({
600
+ code: `UNKNOWN_${typeName.toUpperCase()}`,
601
+ message: `Unknown ${typeName} '${id}' in ${CONFIG_SOURCE}.`,
602
+ });
603
+ return;
604
+ }
605
+
606
+ const enabled = readEnabledOption(options, errors, typeName, id);
607
+ if (!enabled.valid) return;
608
+ if (enabled.present) {
609
+ if (enabled.value === false && primitive.locked === true) {
610
+ errors.push({
611
+ code: 'LOCKED_PRIMITIVE_DISABLED',
612
+ message: `Cannot disable locked ${typeName} '${id}'.`,
613
+ });
614
+ return;
615
+ }
616
+ markConfigured(primitive, { enabled: enabled.value });
617
+ }
618
+ }
619
+
620
+ function applyRailConfig(graph, config, errors) {
621
+ const rails = readConfigSection(config, 'rails', errors, 'rails');
622
+ for (const [key, options] of Object.entries(rails)) {
623
+ const rail = graph.rails.find(candidate => candidate.key === key || candidate.id === key);
624
+ if (!rail) {
625
+ errors.push({
626
+ code: 'UNKNOWN_RAIL',
627
+ message: `Unknown rail '${key}' in ${CONFIG_SOURCE}.`,
628
+ });
629
+ continue;
630
+ }
631
+ const enabled = readEnabledOption(options, errors, 'rail', key);
632
+ if (!enabled.valid) {
633
+ continue;
634
+ }
635
+ if (enabled.value === false && rail.locked === true) {
636
+ // Locked L1 rails remain a non-negotiable floor; only unlocked rails
637
+ // (e.g. tdd_intent, kernel_tracking) may be disabled via config.
638
+ errors.push({
639
+ code: 'LOCKED_L1_RAIL_DISABLED',
640
+ message: `Cannot disable locked L1 rail '${key}'.`,
641
+ });
642
+ continue;
643
+ }
644
+ if (enabled.present) {
645
+ markConfigured(rail, { enabled: enabled.value });
646
+ } else if (options && Object.keys(options).length > 0) {
647
+ rail.configSource = CONFIG_SOURCE;
648
+ }
649
+ }
650
+ }
651
+
652
+ function applyWorkflowConfig(graph, config, errors) {
653
+ const workflow = readConfigSection(config, 'workflow', errors, 'workflow');
654
+ const phases = readConfigSection(workflow, 'phases', errors, 'workflow.phases');
655
+ for (const [id, options] of Object.entries(phases)) {
656
+ applyEnabledConfig(graph.phases, id, options, errors, 'stage');
657
+ }
658
+
659
+ // The `workflow.gates.<id>.enabled` surface (driven by `forge gate enable|disable`)
660
+ // governs gates AND unlocked toggleable rails (e.g. rail.kernel_tracking): the id
661
+ // namespaces are disjoint (gate.* vs rail.*), so a single combined lookup routes each
662
+ // id to its primitive. applyEnabledConfig still honors each primitive's `locked` flag,
663
+ // so a locked L1 rail cannot be disabled through this surface either.
664
+ const gates = readConfigSection(workflow, 'gates', errors, 'workflow.gates');
665
+ const toggleable = [...graph.gates, ...graph.rails];
666
+ for (const [id, options] of Object.entries(gates)) {
667
+ applyEnabledConfig(toggleable, id, options, errors, 'gate');
668
+ }
669
+ }
670
+
671
+ function applyAdapterConfig(graph, config, errors) {
672
+ const adapters = readConfigSection(config, 'adapters', errors, 'adapters');
673
+ for (const [kind, options] of Object.entries(adapters)) {
674
+ const adapter = graph.adapters.find(candidate => candidate.kind === kind || candidate.id === kind);
675
+ if (!adapter) {
676
+ errors.push({
677
+ code: 'UNKNOWN_ADAPTER',
678
+ message: `Unknown adapter '${kind}' in ${CONFIG_SOURCE}.`,
679
+ });
680
+ continue;
681
+ }
682
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
683
+ errors.push({
684
+ code: 'INVALID_ADAPTER_CONFIG',
685
+ message: `adapter '${kind}' config must be an object.`,
686
+ });
687
+ continue;
688
+ }
689
+ const enabled = readEnabledOption(options, errors, 'adapter', kind);
690
+ if (!enabled.valid) {
691
+ continue;
692
+ }
693
+ const nextConfig = { ...options };
694
+ delete nextConfig.enabled;
695
+ if (enabled.present) {
696
+ markConfigured(adapter, { enabled: enabled.value });
697
+ }
698
+ adapter.config = { ...adapter.config };
699
+ Object.assign(adapter.config, nextConfig);
700
+ if (enabled.present || Object.keys(nextConfig).length > 0) {
701
+ adapter.configSource = CONFIG_SOURCE;
702
+ }
703
+ }
704
+ }
705
+
706
+ function readRoleStringField(options, field, role, errors) {
707
+ if (!Object.hasOwn(options, field)) {
708
+ return { present: false, value: undefined };
709
+ }
710
+ const value = options[field];
711
+ if (typeof value !== 'string' || value.trim() === '') {
712
+ errors.push({
713
+ code: `INVALID_ROLE_${field.toUpperCase()}`,
714
+ message: `role '${role}' ${field} must be a non-empty string.`,
715
+ });
716
+ return { present: true, value: undefined };
717
+ }
718
+ return { present: true, value: value.trim() };
719
+ }
720
+
721
+ // Reader for the `roles` section (sibling to `workflow.gates`). Role names are
722
+ // the KNOWN closed set (ROLE_IDS); the bound `skill` / `ideology` names are
723
+ // OPEN-WORLD — validated only as non-empty strings here, never against
724
+ // PLAN_SUBSKILL_IDS (that closed enum stays scoped to
725
+ // planning.template.partialInvocation). Skill EXISTENCE is enforced at write
726
+ // time by the `forge role` verb, not mid-run.
727
+ function applyRoleConfig(graph, config, errors) {
728
+ const roles = readConfigSection(config, 'roles', errors, 'roles');
729
+ for (const [role, options] of Object.entries(roles)) {
730
+ const entry = graph.roles.find(candidate => candidate.role === role || candidate.id === role);
731
+ if (!entry) {
732
+ errors.push({
733
+ code: 'UNKNOWN_ROLE',
734
+ message: `Unknown role '${role}' in ${CONFIG_SOURCE}.`,
735
+ });
736
+ continue;
737
+ }
738
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
739
+ errors.push({
740
+ code: 'INVALID_ROLE_CONFIG',
741
+ message: `role '${role}' config must be an object.`,
742
+ });
743
+ continue;
744
+ }
745
+
746
+ let changed = false;
747
+ const skill = readRoleStringField(options, 'skill', role, errors);
748
+ if (skill.present && skill.value !== undefined) {
749
+ entry.skill = skill.value;
750
+ changed = true;
751
+ }
752
+ const ideology = readRoleStringField(options, 'ideology', role, errors);
753
+ if (ideology.present && ideology.value !== undefined) {
754
+ entry.ideology = ideology.value;
755
+ changed = true;
756
+ }
757
+ if (changed) {
758
+ entry.configSource = CONFIG_SOURCE;
759
+ }
760
+ }
761
+ }
762
+
763
+ function assertStringList(value, errors, code, message) {
764
+ if (!Array.isArray(value)) {
765
+ errors.push({ code, message });
766
+ return false;
767
+ }
768
+
769
+ let valid = true;
770
+ for (const item of value) {
771
+ if (typeof item !== 'string' || item.trim() === '') {
772
+ errors.push({ code, message });
773
+ valid = false;
774
+ break;
775
+ }
776
+ }
777
+ return valid;
778
+ }
779
+
780
+ function validatePlanSubSkillList(value, errors, path) {
781
+ if (!assertStringList(
782
+ value,
783
+ errors,
784
+ 'INVALID_PLAN_SUBSKILL_LIST',
785
+ `${path} must be a list of planning sub-skill IDs.`
786
+ )) {
787
+ return undefined;
788
+ }
789
+
790
+ const normalized = value.map(item => item.trim());
791
+ let hasUnknown = false;
792
+ for (const id of normalized) {
793
+ if (!PLAN_SUBSKILL_IDS.has(id)) {
794
+ hasUnknown = true;
795
+ errors.push({
796
+ code: 'UNKNOWN_PLAN_SUBSKILL',
797
+ message: `Unknown planning sub-skill '${id}' in ${CONFIG_SOURCE}.`,
798
+ });
799
+ }
800
+ }
801
+ if (hasUnknown) {
802
+ return undefined;
803
+ }
804
+ return normalized;
805
+ }
806
+
807
+ function applyPlanningMode(template, nextTemplate, errors) {
808
+ if (!Object.hasOwn(template, 'mode')) return;
809
+ if (!['full', 'partial'].includes(template.mode)) {
810
+ errors.push({
811
+ code: 'INVALID_PLANNING_MODE',
812
+ message: 'planning.template.mode must be full or partial.',
813
+ });
814
+ return;
815
+ }
816
+ nextTemplate.mode = template.mode;
817
+ }
818
+
819
+ function applyConvergenceThreshold(template, nextTemplate, errors) {
820
+ if (!Object.hasOwn(template, 'convergenceThreshold')) return;
821
+ if (!Number.isFinite(template.convergenceThreshold)
822
+ || template.convergenceThreshold < 0
823
+ || template.convergenceThreshold > 1) {
824
+ errors.push({
825
+ code: 'INVALID_CONVERGENCE_THRESHOLD',
826
+ message: 'planning.template.convergenceThreshold must be a number from 0 to 1.',
827
+ });
828
+ return;
829
+ }
830
+ nextTemplate.convergenceThreshold = template.convergenceThreshold;
831
+ }
832
+
833
+ function applyCriticSet(template, nextTemplate, errors) {
834
+ if (!Object.hasOwn(template, 'criticSet')) return;
835
+ if (assertStringList(template.criticSet, errors, 'INVALID_CRITIC', 'planning.template.criticSet must contain non-empty strings.')) {
836
+ nextTemplate.criticSet = template.criticSet.map(item => item.trim());
837
+ }
838
+ }
839
+
840
+ function applyPartialInvocation(template, nextTemplate, errors) {
841
+ if (!Object.hasOwn(template, 'partialInvocation')) return;
842
+ const partial = readConfigSection(template, 'partialInvocation', errors, 'planning.template.partialInvocation');
843
+ if (Object.hasOwn(partial, 'only')) {
844
+ const only = validatePlanSubSkillList(partial.only, errors, 'planning.template.partialInvocation.only');
845
+ if (only) nextTemplate.partialInvocation.only = only;
846
+ }
847
+ if (Object.hasOwn(partial, 'skip')) {
848
+ const skip = validatePlanSubSkillList(partial.skip, errors, 'planning.template.partialInvocation.skip');
849
+ if (skip) nextTemplate.partialInvocation.skip = skip;
850
+ }
851
+ }
852
+
853
+ function applyPlanningConfig(graph, config, errors) {
854
+ const planning = readConfigSection(config, 'planning', errors, 'planning');
855
+ const template = readConfigSection(planning, 'template', errors, 'planning.template');
856
+ if (Object.keys(template).length === 0) {
857
+ return;
858
+ }
859
+
860
+ const nextTemplate = { ...graph.planning.template, partialInvocation: { ...graph.planning.template.partialInvocation } };
861
+ applyPlanningMode(template, nextTemplate, errors);
862
+ applyConvergenceThreshold(template, nextTemplate, errors);
863
+ applyCriticSet(template, nextTemplate, errors);
864
+ applyPartialInvocation(template, nextTemplate, errors);
865
+ nextTemplate.configSource = CONFIG_SOURCE;
866
+ graph.planning.template = nextTemplate;
867
+ }
868
+
869
+ function validateProtectedPaths(config, errors) {
870
+ if (!Object.hasOwn(config, 'protectedPaths')) {
871
+ return [];
872
+ }
873
+ if (!Array.isArray(config.protectedPaths)) {
874
+ errors.push({
875
+ code: 'PROTECTED_PATHS_INVALID',
876
+ message: 'protectedPaths must be a list of path patterns.',
877
+ });
878
+ return [];
879
+ }
880
+
881
+ const protectedPaths = [];
882
+ for (const [index, entry] of config.protectedPaths.entries()) {
883
+ if (typeof entry !== 'string' || entry.trim() === '') {
884
+ errors.push({
885
+ code: 'PROTECTED_PATH_INVALID',
886
+ message: `protectedPaths[${index}] must be a non-empty string.`,
887
+ });
888
+ continue;
889
+ }
890
+ const pattern = entry.trim();
891
+ if (['*', '**', '**/*', '.', './', '/'].includes(pattern)) {
892
+ errors.push({
893
+ code: 'PROTECTED_PATH_TOO_BROAD',
894
+ message: `protectedPaths[${index}] is too broad: ${pattern}`,
895
+ });
896
+ continue;
897
+ }
898
+ protectedPaths.push(pattern);
899
+ }
900
+ return protectedPaths;
901
+ }
902
+
903
+ function resolveRuntimeGraph({ projectRoot = process.cwd(), throwOnError = true } = {}) {
904
+ const graph = cloneJson(RESOLVED_RUNTIME_GRAPH);
905
+ const loaded = loadRuntimeGraphConfig({ projectRoot });
906
+ const errors = [...loaded.errors];
907
+
908
+ if (loaded.errors.length === 0) {
909
+ applyRailConfig(graph, loaded.config, errors);
910
+ applyWorkflowConfig(graph, loaded.config, errors);
911
+ applyAdapterConfig(graph, loaded.config, errors);
912
+ applyRoleConfig(graph, loaded.config, errors);
913
+ applyPlanningConfig(graph, loaded.config, errors);
914
+ graph.protectedPaths = validateProtectedPaths(loaded.config, errors);
915
+ }
916
+
917
+ graph.config = {
918
+ path: CONFIG_SOURCE,
919
+ loaded: loaded.exists,
920
+ errors,
921
+ };
922
+
923
+ if (throwOnError && errors.length > 0) {
924
+ throw new Error(errors.map(error => error.message).join('\n'));
925
+ }
926
+
927
+ return { graph, errors };
928
+ }
929
+
930
+ function getResolvedRuntimeGraph(options = {}) {
931
+ return resolveRuntimeGraph(options).graph;
932
+ }
933
+
934
+ function getDefaultRuntimeGraph() {
935
+ return cloneJson(RESOLVED_RUNTIME_GRAPH);
936
+ }
937
+
938
+ function lintRuntimeGraphConfig(options = {}) {
939
+ const { graph, errors } = resolveRuntimeGraph({ ...options, throwOnError: false });
940
+ return {
941
+ ok: errors.length === 0,
942
+ errors,
943
+ warnings: [],
944
+ graph,
945
+ };
946
+ }
947
+
948
+ function buildRuntimeGraphEnvelope(graph = getResolvedRuntimeGraph()) {
949
+ return {
950
+ schemaVersion: RUNTIME_GRAPH_SCHEMA_VERSION,
951
+ kind: 'forge.runtimeGraph',
952
+ schema: cloneJson(RUNTIME_GRAPH_SCHEMA),
953
+ graph: cloneJson(graph),
954
+ };
955
+ }
956
+
957
+ module.exports = {
958
+ RUNTIME_GRAPH_SCHEMA_VERSION,
959
+ RUNTIME_GRAPH_SCHEMA,
960
+ Phase,
961
+ Action,
962
+ Artifact,
963
+ EvaluatorRegion,
964
+ Gate,
965
+ Evidence,
966
+ Rail,
967
+ Adapter,
968
+ PlanningSubSkill,
969
+ Role,
970
+ ROLE_IDS,
971
+ loadRuntimeGraphConfig,
972
+ lintRuntimeGraphConfig,
973
+ resolveRuntimeGraph,
974
+ getDefaultRuntimeGraph,
975
+ buildRuntimeGraphEnvelope,
976
+ getResolvedRuntimeGraph,
977
+ };