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,329 @@
1
+ 'use strict';
2
+
3
+ const { getTypeBehavior, isTerminalStatus, normalizeRank, toEpochMillis } = require('./taxonomy-validator');
4
+
5
+ // Readiness is a DERIVED read model (D18). `ready`/`blocked` are computed on demand
6
+ // from dependencies, claims, quarantine/conflicts, gates, defer windows, and policy —
7
+ // they are NEVER stored as issue status values.
8
+ const READINESS_REASONS = Object.freeze({
9
+ DEPENDENCY: 'dependency',
10
+ // A blocker that is itself PARKED (backlog): it will never complete on its own, so it
11
+ // blocks its dependents indefinitely. Surfaced as a distinct code (not plain
12
+ // `dependency`) so a parked blocker is not mistaken for ordinary in-flight work — a
13
+ // consumer can flag it for promotion instead of waiting on it forever.
14
+ DEPENDENCY_PARKED: 'dependency_parked',
15
+ QUARANTINE: 'quarantine',
16
+ CONFLICT: 'conflict',
17
+ GATE: 'gate',
18
+ CLAIM: 'claimed',
19
+ DEFERRED: 'deferred',
20
+ POLICY: 'policy_disabled',
21
+ });
22
+
23
+ const READINESS_STATES = Object.freeze([
24
+ 'ready',
25
+ 'blocked',
26
+ 'gated',
27
+ 'deferred',
28
+ 'claimed',
29
+ 'disabled',
30
+ 'closed',
31
+ 'backlog',
32
+ ]);
33
+
34
+ const WORKABLE_STATUSES = Object.freeze(['open', 'in_progress']);
35
+
36
+ /** @returns {boolean} true if `status` is a pickable working status (open/in_progress). */
37
+ function isWorkableStatus(status) {
38
+ return WORKABLE_STATUSES.includes(status);
39
+ }
40
+
41
+ // Ready work must be claimable: epics (containers) and decisions are claimable:false, so they
42
+ // never belong in the ready queue even when unblocked. A known non-claimable type is excluded;
43
+ // an unknown/unspecified type is not penalised (callers may omit `type`).
44
+ function isReadyEligibleType(type) {
45
+ const behavior = getTypeBehavior(type);
46
+ return behavior ? behavior.claimable : true;
47
+ }
48
+
49
+ // Edge-only blocking test: keys off the dependency RELATIONSHIP (dependency_type),
50
+ // NOT the blocking issue's type. Distinct from taxonomy-validator's isBlockingDependency,
51
+ // which also falls back to `.type`; do not merge them (see readiness decision-dependency).
52
+ function isBlockingDependencyEdge(dependency) {
53
+ return (dependency.dependency_type || 'blocks') === 'blocks';
54
+ }
55
+
56
+ function isDeferred(issue, now) {
57
+ if (!issue.defer_until) return false;
58
+ const deferMillis = toEpochMillis(issue.defer_until);
59
+ if (deferMillis === null) return false; // an unparseable defer window is not a defer
60
+ const nowMillis = toEpochMillis(now);
61
+ // Fail closed: without a usable clock we cannot confirm the window elapsed, so a deferred
62
+ // issue stays deferred rather than incorrectly surfacing as ready. Keeps the model pure
63
+ // (caller supplies `now`) instead of reaching for a wall clock.
64
+ if (nowMillis === null) return true;
65
+ return deferMillis > nowMillis;
66
+ }
67
+
68
+ function findConflictingClaim(issueId, claims, actor, now) {
69
+ const nowMillis = toEpochMillis(now);
70
+ for (const claim of claims || []) {
71
+ if (claim.issue_id !== issueId) continue;
72
+ if ((claim.state || 'active') !== 'active') continue;
73
+ if (actor && claim.actor === actor) continue; // the requesting actor's own claim does not block them
74
+ // An active row whose lease has elapsed is dead (validateClaim agrees) — it must not
75
+ // keep the issue out of the ready queue for other actors. If `now` is unknown we cannot
76
+ // confirm expiry, so the claim still blocks (conservative).
77
+ const expiresMillis = toEpochMillis(claim.expires_at);
78
+ if (expiresMillis !== null && nowMillis !== null && expiresMillis <= nowMillis) continue;
79
+ return claim;
80
+ }
81
+ return null;
82
+ }
83
+
84
+ function collectDependencyBlockers(context, reasons, blockedBy) {
85
+ for (const dependency of context.dependencyStatuses || []) {
86
+ if (!isBlockingDependencyEdge(dependency)) continue;
87
+ // A terminal (done OR cancelled) blocker will never complete again, so it no longer
88
+ // blocks — a cancelled dependency must not wedge the dependent as permanently blocked.
89
+ if (isTerminalStatus(dependency.status)) continue;
90
+ blockedBy.push(dependency.id);
91
+ // A parked (backlog) blocker earns a distinct code + flag: it is stalled by design,
92
+ // not being actively worked, so consumers can call it out for promotion.
93
+ const isParked = dependency.status === 'backlog';
94
+ const code = isParked ? READINESS_REASONS.DEPENDENCY_PARKED : READINESS_REASONS.DEPENDENCY;
95
+ const reason = { code, issue_id: dependency.id, status: dependency.status };
96
+ if (isParked) {
97
+ reason.parked = true;
98
+ }
99
+ if (dependency.type === 'decision') {
100
+ reason.decision = true;
101
+ }
102
+ reasons.push(reason);
103
+ }
104
+ }
105
+
106
+ function collectConflictBlockers(issue, context, reasons) {
107
+ let hasConflict = false;
108
+ for (const conflict of context.conflicts || []) {
109
+ // Conflicts are keyed by (entity_type, entity_id). Only issue-scoped conflicts block an
110
+ // issue; a quarantined dependency/release/sprint that happens to share the id string must
111
+ // not. Absent entity_type is treated as legacy issue-scoped.
112
+ if (conflict.entity_type && conflict.entity_type !== 'issue') continue;
113
+ if (conflict.entity_id && issue.id && conflict.entity_id !== issue.id) continue;
114
+ hasConflict = true;
115
+ const status = conflict.status || 'quarantined';
116
+ const code = status === 'quarantined' ? READINESS_REASONS.QUARANTINE : READINESS_REASONS.CONFLICT;
117
+ reasons.push({ code, status });
118
+ }
119
+ return hasConflict;
120
+ }
121
+
122
+ function collectGateBlockers(context, reasons) {
123
+ let hasGate = false;
124
+ for (const gate of context.gates || []) {
125
+ if (gate.satisfied) continue;
126
+ hasGate = true;
127
+ reasons.push({ code: READINESS_REASONS.GATE, gate: gate.name });
128
+ }
129
+ return hasGate;
130
+ }
131
+
132
+ // Precedence high→low. `blocked` (dependencies/quarantine/conflict) always outranks the
133
+ // softer not-ready reasons; consumers picking next work should read the full reasons[]
134
+ // because a claim hidden behind a defer window is not reflected in this single summary.
135
+ function deriveState(flags) {
136
+ if (flags.blocked) return 'blocked';
137
+ if (flags.hasGate) return 'gated';
138
+ if (flags.deferred) return 'deferred';
139
+ if (flags.hasClaim) return 'claimed';
140
+ if (flags.policyDisabled) return 'disabled';
141
+ if (flags.ready) return 'ready';
142
+ return 'backlog';
143
+ }
144
+
145
+ /**
146
+ * Derive an issue's readiness (a read model, never stored) from its context.
147
+ * @param {object} issue stored issue fields (id, status, defer_until, ...).
148
+ * @param {object} context { now, actor, dependencyStatuses[], conflicts[], gates[], claims[], policyDisabled }.
149
+ * @returns {{id, status, ready: boolean, blocked: boolean, blocked_by: string[], reasons: object[], state: string}}
150
+ */
151
+ function deriveReadiness(issue = {}, context = {}) {
152
+ const id = issue.id;
153
+ const status = issue.status;
154
+
155
+ if (isTerminalStatus(status)) {
156
+ return { id, status, ready: false, blocked: false, blocked_by: [], reasons: [], state: 'closed' };
157
+ }
158
+
159
+ // A `backlog` (parked) issue is a first-class lifecycle state, not workable work: it
160
+ // reads as parked regardless of any dependencies/gates — never ready, never blocked —
161
+ // so parked ideas do not surface in ready/blocked queues or flag stale.
162
+ if (status === 'backlog') {
163
+ return { id, status, ready: false, blocked: false, blocked_by: [], reasons: [], state: 'backlog' };
164
+ }
165
+
166
+ const reasons = [];
167
+ const blockedBy = [];
168
+
169
+ collectDependencyBlockers(context, reasons, blockedBy);
170
+ const hasConflict = collectConflictBlockers(issue, context, reasons);
171
+ const hasGate = collectGateBlockers(context, reasons);
172
+
173
+ const deferred = isDeferred(issue, context.now);
174
+ if (deferred) {
175
+ reasons.push({ code: READINESS_REASONS.DEFERRED, until: issue.defer_until });
176
+ }
177
+
178
+ const policyDisabled = Boolean(context.policyDisabled);
179
+ if (policyDisabled) {
180
+ reasons.push({ code: READINESS_REASONS.POLICY });
181
+ }
182
+
183
+ const conflictingClaim = findConflictingClaim(id, context.claims, context.actor, context.now);
184
+ if (conflictingClaim) {
185
+ reasons.push({ code: READINESS_REASONS.CLAIM, actor: conflictingClaim.actor });
186
+ }
187
+
188
+ const blocked = blockedBy.length > 0 || hasConflict;
189
+ const ready = !blocked
190
+ && !hasGate
191
+ && !deferred
192
+ && !policyDisabled
193
+ && !conflictingClaim
194
+ && isWorkableStatus(status)
195
+ && isReadyEligibleType(issue.type);
196
+
197
+ const state = deriveState({
198
+ blocked,
199
+ hasGate,
200
+ deferred,
201
+ hasClaim: Boolean(conflictingClaim),
202
+ policyDisabled,
203
+ ready,
204
+ });
205
+
206
+ return { id, status, ready, blocked, blocked_by: blockedBy, reasons, state };
207
+ }
208
+
209
+ // BETA stop-gap for claim thundering-herd (kernel 369c43d7): concurrent `forge ready`
210
+ // callers previously all saw the identical rank-0 top pick and raced to claim it (CAS
211
+ // lets exactly one win, but the rest burn a claim_conflict write + wasted pre-claim
212
+ // reasoning, then re-collide on rank-1, etc). Randomizing which of the top-K
213
+ // same-priority issues sorts first spreads concurrent agents across different ready
214
+ // issues. Priority tier ordering stays the PRIMARY, un-randomized sort key: only the
215
+ // contiguous run of issues sharing the single highest-ranked (lowest-number) priority
216
+ // tier is shuffled, and only its first `topK` members — a lower-priority issue can
217
+ // never be promoted ahead of a ready higher-priority one. Full fix is Phase-2 per-project
218
+ // lease dispatch; this is the cheap interim mitigation only.
219
+ const DEFAULT_TOP_K_READY_PICK = 5;
220
+
221
+ // Fisher-Yates, using an injectable rng (defaults to Math.random) so callers/tests can
222
+ // seed it for deterministic assertions. Mutates and returns `array`.
223
+ function shuffleInPlace(array, rng) {
224
+ for (let i = array.length - 1; i > 0; i--) {
225
+ const j = Math.floor(rng() * (i + 1));
226
+ [array[i], array[j]] = [array[j], array[i]];
227
+ }
228
+ return array;
229
+ }
230
+
231
+ // Randomize only the top-K of the single highest-priority tier at the front of an
232
+ // already rank+id sorted `sortedIssues` array. Everything else (the tier's remainder
233
+ // beyond K, and every lower-priority tier) keeps its deterministic order untouched.
234
+ function applyTopKRandomizedPick(sortedIssues, { topK = DEFAULT_TOP_K_READY_PICK, rng = Math.random } = {}) {
235
+ if (sortedIssues.length < 2) return sortedIssues;
236
+
237
+ const topRank = normalizeRank(sortedIssues[0].priority_rank);
238
+ let tierEnd = 0;
239
+ while (tierEnd < sortedIssues.length && normalizeRank(sortedIssues[tierEnd].priority_rank) === topRank) {
240
+ tierEnd++;
241
+ }
242
+
243
+ const k = Math.max(0, Math.min(topK, tierEnd));
244
+ if (k < 2) return sortedIssues; // nothing to shuffle: <2 candidates in the randomized head
245
+
246
+ const head = shuffleInPlace(sortedIssues.slice(0, k), rng);
247
+ const rest = sortedIssues.slice(k);
248
+ return [...head, ...rest];
249
+ }
250
+
251
+ function groupBy(items, keyFn) {
252
+ const grouped = new Map();
253
+ for (const item of items || []) {
254
+ const key = keyFn(item);
255
+ if (!grouped.has(key)) grouped.set(key, []);
256
+ grouped.get(key).push(item);
257
+ }
258
+ return grouped;
259
+ }
260
+
261
+ /**
262
+ * Compute readiness for a whole board, resolving each dependency's status from the issue set.
263
+ * @param {object} input { issues[], dependencies[], conflicts[], claims[], gates[], policyDisabledIds[], now, actor,
264
+ * topK, rng }. `topK` (default {@link DEFAULT_TOP_K_READY_PICK}) and `rng` (default `Math.random`, injectable for
265
+ * deterministic tests) control the BETA top-K randomized ready pick (kernel 369c43d7): only the top-K of the
266
+ * single highest-priority ready tier is shuffled; priority ordering otherwise stays the primary sort.
267
+ * @returns {{readinessById: object, readyQueue: string[], blocked: string[]}} readyQueue is ranked by the single
268
+ * numeric rank, with the top-K of its leading priority tier randomized.
269
+ */
270
+ function buildReadinessIndex(input = {}) {
271
+ const issues = input.issues || [];
272
+ const statusById = new Map(issues.map(issue => [issue.id, issue.status]));
273
+ const typeById = new Map(issues.map(issue => [issue.id, issue.type]));
274
+
275
+ const dependencyStatusesByIssue = new Map();
276
+ for (const dependency of input.dependencies || []) {
277
+ if (!dependencyStatusesByIssue.has(dependency.issue_id)) {
278
+ dependencyStatusesByIssue.set(dependency.issue_id, []);
279
+ }
280
+ dependencyStatusesByIssue.get(dependency.issue_id).push({
281
+ id: dependency.blocks_issue_id,
282
+ status: statusById.get(dependency.blocks_issue_id),
283
+ type: typeById.get(dependency.blocks_issue_id),
284
+ dependency_type: dependency.dependency_type,
285
+ });
286
+ }
287
+
288
+ const conflictsByIssue = groupBy(input.conflicts, conflict => conflict.entity_id);
289
+ const claimsByIssue = groupBy(input.claims, claim => claim.issue_id);
290
+ const gatesByIssue = groupBy(input.gates, gate => gate.issue_id);
291
+ const policyDisabledIds = new Set(input.policyDisabledIds || []);
292
+
293
+ // Null-prototype map: issue ids are unconstrained external strings, so a literal `{}`
294
+ // keyed by them would be a prototype-pollution vector (e.g. an id of `__proto__`).
295
+ const readinessById = Object.create(null);
296
+ for (const issue of issues) {
297
+ readinessById[issue.id] = deriveReadiness(issue, {
298
+ now: input.now,
299
+ actor: input.actor,
300
+ dependencyStatuses: dependencyStatusesByIssue.get(issue.id) || [],
301
+ conflicts: conflictsByIssue.get(issue.id) || [],
302
+ claims: claimsByIssue.get(issue.id) || [],
303
+ gates: gatesByIssue.get(issue.id) || [],
304
+ policyDisabled: policyDisabledIds.has(issue.id),
305
+ });
306
+ }
307
+
308
+ const readyIssues = issues.filter(issue => readinessById[issue.id].ready);
309
+ readyIssues.sort((left, right) => (
310
+ (normalizeRank(left.priority_rank) - normalizeRank(right.priority_rank))
311
+ || String(left.id).localeCompare(String(right.id))
312
+ ));
313
+ const pickedReadyIssues = applyTopKRandomizedPick(readyIssues, { topK: input.topK, rng: input.rng });
314
+
315
+ return {
316
+ readinessById,
317
+ readyQueue: pickedReadyIssues.map(issue => issue.id),
318
+ blocked: issues.filter(issue => readinessById[issue.id].blocked).map(issue => issue.id),
319
+ };
320
+ }
321
+
322
+ module.exports = {
323
+ READINESS_REASONS,
324
+ READINESS_STATES,
325
+ DEFAULT_TOP_K_READY_PICK,
326
+ buildReadinessIndex,
327
+ deriveReadiness,
328
+ isWorkableStatus,
329
+ };
@@ -0,0 +1,356 @@
1
+ const KERNEL_STORAGE_CLASSES = Object.freeze([
2
+ 'authority',
3
+ 'read_model',
4
+ 'projection',
5
+ 'archive',
6
+ 'configuration',
7
+ 'cache',
8
+ 'external_provider',
9
+ ]);
10
+
11
+ const KERNEL_FIELD_AUTHORITIES = Object.freeze([
12
+ 'forge',
13
+ 'provider',
14
+ 'configured_provider',
15
+ 'projection_only',
16
+ ]);
17
+
18
+ function deepFreeze(value) {
19
+ if (!value || typeof value !== 'object') return value;
20
+ Object.freeze(value);
21
+ for (const nestedValue of Object.values(value)) {
22
+ deepFreeze(nestedValue);
23
+ }
24
+ return value;
25
+ }
26
+
27
+ function field(name, type, options = {}) {
28
+ return {
29
+ name,
30
+ type,
31
+ storageClass: options.storageClass,
32
+ authority: options.authority || 'forge',
33
+ primaryKey: Boolean(options.primaryKey),
34
+ notNull: Boolean(options.notNull || options.primaryKey),
35
+ default: options.default,
36
+ references: options.references,
37
+ };
38
+ }
39
+
40
+ function index(name, columns, options = {}) {
41
+ return {
42
+ name,
43
+ columns,
44
+ unique: Boolean(options.unique),
45
+ };
46
+ }
47
+
48
+ function table(name, storageClass, fields, indexes = []) {
49
+ return {
50
+ name,
51
+ sqlName: `kernel_${name}`,
52
+ storageClass,
53
+ authority: 'forge',
54
+ fields: fields.map(fieldDefinition => ({
55
+ ...fieldDefinition,
56
+ storageClass: fieldDefinition.storageClass || storageClass,
57
+ })),
58
+ indexes,
59
+ };
60
+ }
61
+
62
+ const TABLE_LIST = deepFreeze([
63
+ table('issues', 'authority', [
64
+ field('id', 'TEXT', { primaryKey: true }),
65
+ field('title', 'TEXT', { notNull: true }),
66
+ field('body', 'TEXT'),
67
+ field('type', 'TEXT', { notNull: true, default: "'task'" }),
68
+ field('status', 'TEXT', { notNull: true, default: "'open'" }),
69
+ field('priority', 'TEXT', { notNull: true, default: "'P2'" }),
70
+ field('priority_rank', 'INTEGER', { notNull: true, default: '0', storageClass: 'read_model' }),
71
+ field('created_at', 'TEXT', { notNull: true }),
72
+ field('updated_at', 'TEXT', { notNull: true }),
73
+ field('entity_revision', 'INTEGER', { notNull: true, default: '0' }),
74
+ // D18 taxonomy: hierarchy, planning buckets, and execution stage are separate
75
+ // axes from status. ready/blocked stay derived (readiness-model), never stored.
76
+ field('parent_id', 'TEXT', { references: 'issues.id' }),
77
+ field('sprint_id', 'TEXT'),
78
+ field('release_id', 'TEXT'),
79
+ field('stage_state', 'TEXT'),
80
+ field('labels', 'TEXT'),
81
+ field('acceptance_criteria', 'TEXT'),
82
+ field('estimate', 'TEXT'),
83
+ // KAP-10 (design/notes) + KAP-11 (assignee): authored content fields and a
84
+ // persistent assignee. Migration 004 backfills existing DBs; fresh DBs get them here.
85
+ field('design', 'TEXT'),
86
+ field('notes', 'TEXT'),
87
+ field('assignee', 'TEXT'),
88
+ // Beads full-fidelity import: the issue author, the close timestamp + raw close
89
+ // reason (distinct from the mapped terminal status), and a verbatim JSON blob for
90
+ // any other beads metadata. Migration 006 backfills existing DBs; fresh DBs get
91
+ // them here.
92
+ field('created_by', 'TEXT'),
93
+ field('closed_at', 'TEXT'),
94
+ field('close_reason', 'TEXT'),
95
+ field('metadata', 'TEXT'),
96
+ ], [
97
+ index('idx_kernel_issues_status_priority', ['status', 'priority_rank']),
98
+ index('idx_kernel_issues_updated_at', ['updated_at']),
99
+ index('idx_kernel_issues_parent', ['parent_id']),
100
+ index('idx_kernel_issues_sprint', ['sprint_id']),
101
+ index('idx_kernel_issues_release', ['release_id']),
102
+ ]),
103
+ table('dependencies', 'authority', [
104
+ field('id', 'TEXT', { primaryKey: true }),
105
+ field('issue_id', 'TEXT', { notNull: true, references: 'issues.id' }),
106
+ field('blocks_issue_id', 'TEXT', { notNull: true, references: 'issues.id' }),
107
+ field('dependency_type', 'TEXT', { notNull: true, default: "'blocks'" }),
108
+ field('created_at', 'TEXT', { notNull: true }),
109
+ ], [
110
+ index('idx_kernel_dependencies_issue', ['issue_id']),
111
+ index('idx_kernel_dependencies_blocks', ['blocks_issue_id']),
112
+ ]),
113
+ table('comments', 'authority', [
114
+ field('id', 'TEXT', { primaryKey: true }),
115
+ field('issue_id', 'TEXT', { notNull: true, references: 'issues.id' }),
116
+ field('body', 'TEXT', { notNull: true }),
117
+ field('actor', 'TEXT', { notNull: true }),
118
+ field('visibility', 'TEXT', { notNull: true, default: "'local'" }),
119
+ field('created_at', 'TEXT', { notNull: true }),
120
+ ], [
121
+ index('idx_kernel_comments_issue_created', ['issue_id', 'created_at']),
122
+ ]),
123
+ table('priority_events', 'authority', [
124
+ field('id', 'TEXT', { primaryKey: true }),
125
+ field('issue_id', 'TEXT', { notNull: true, references: 'issues.id' }),
126
+ field('old_priority', 'TEXT'),
127
+ field('new_priority', 'TEXT', { notNull: true }),
128
+ field('priority_rank', 'INTEGER', { notNull: true }),
129
+ field('actor', 'TEXT', { notNull: true }),
130
+ field('created_at', 'TEXT', { notNull: true }),
131
+ ], [
132
+ index('idx_kernel_priority_events_issue_created', ['issue_id', 'created_at']),
133
+ ]),
134
+ table('claims', 'authority', [
135
+ field('id', 'TEXT', { primaryKey: true }),
136
+ field('issue_id', 'TEXT', { notNull: true, references: 'issues.id' }),
137
+ field('actor', 'TEXT', { notNull: true }),
138
+ field('state', 'TEXT', { notNull: true, default: "'active'" }),
139
+ field('session_id', 'TEXT'),
140
+ field('worktree_id', 'TEXT'),
141
+ field('claimed_at', 'TEXT', { notNull: true }),
142
+ field('expires_at', 'TEXT'),
143
+ ], [
144
+ index('idx_kernel_claims_issue_state', ['issue_id', 'state']),
145
+ index('idx_kernel_claims_actor_state', ['actor', 'state']),
146
+ ]),
147
+ table('sessions', 'authority', [
148
+ field('id', 'TEXT', { primaryKey: true }),
149
+ field('actor', 'TEXT', { notNull: true }),
150
+ field('git_common_dir', 'TEXT', { notNull: true }),
151
+ field('worktree_id', 'TEXT'),
152
+ field('started_at', 'TEXT', { notNull: true }),
153
+ field('ended_at', 'TEXT'),
154
+ field('state', 'TEXT', { notNull: true, default: "'active'" }),
155
+ ], [
156
+ index('idx_kernel_sessions_common_dir_state', ['git_common_dir', 'state']),
157
+ ]),
158
+ table('worktrees', 'authority', [
159
+ field('id', 'TEXT', { primaryKey: true }),
160
+ field('git_common_dir', 'TEXT', { notNull: true }),
161
+ field('path', 'TEXT', { notNull: true, storageClass: 'configuration' }),
162
+ field('branch', 'TEXT', { notNull: true }),
163
+ field('actor', 'TEXT'),
164
+ // The kernel linkage backbone: a worktree row records which issue it serves
165
+ // (issue_id) and which work-folder that issue owns (work_folder, repo-relative).
166
+ // Both are nullable — a worktree can be registered before its issue/work-folder
167
+ // is known. Added by migration 007 (see migrations.js MIGRATION_ADDED_COLUMNS).
168
+ field('issue_id', 'TEXT'),
169
+ field('work_folder', 'TEXT', { storageClass: 'configuration' }),
170
+ field('registered_at', 'TEXT', { notNull: true }),
171
+ field('state', 'TEXT', { notNull: true, default: "'active'" }),
172
+ ], [
173
+ index('idx_kernel_worktrees_common_dir_branch', ['git_common_dir', 'branch']),
174
+ ]),
175
+ table('stage_runs', 'authority', [
176
+ field('id', 'TEXT', { primaryKey: true }),
177
+ field('issue_id', 'TEXT', { notNull: true, references: 'issues.id' }),
178
+ field('stage', 'TEXT', { notNull: true }),
179
+ field('substage', 'TEXT'),
180
+ field('status', 'TEXT', { notNull: true }),
181
+ field('started_at', 'TEXT', { notNull: true }),
182
+ field('completed_at', 'TEXT'),
183
+ field('evidence_id', 'TEXT'),
184
+ ], [
185
+ index('idx_kernel_stage_runs_issue_stage', ['issue_id', 'stage']),
186
+ index('idx_kernel_stage_runs_status', ['status']),
187
+ ]),
188
+ table('evidence', 'archive', [
189
+ field('id', 'TEXT', { primaryKey: true }),
190
+ field('issue_id', 'TEXT'),
191
+ field('run_id', 'TEXT'),
192
+ field('kind', 'TEXT', { notNull: true }),
193
+ field('uri', 'TEXT'),
194
+ field('summary', 'TEXT'),
195
+ field('created_at', 'TEXT', { notNull: true }),
196
+ ], [
197
+ index('idx_kernel_evidence_issue_kind', ['issue_id', 'kind']),
198
+ ]),
199
+ table('projections', 'projection', [
200
+ field('id', 'TEXT', { primaryKey: true }),
201
+ field('target', 'TEXT', { notNull: true, storageClass: 'external_provider', authority: 'configured_provider' }),
202
+ field('entity_type', 'TEXT', { notNull: true }),
203
+ field('entity_id', 'TEXT', { notNull: true }),
204
+ field('status', 'TEXT', { notNull: true }),
205
+ field('last_error', 'TEXT', { storageClass: 'cache', authority: 'projection_only' }),
206
+ field('updated_at', 'TEXT', { notNull: true }),
207
+ ], [
208
+ index('idx_kernel_projections_target_status', ['target', 'status']),
209
+ index('idx_kernel_projections_entity', ['entity_type', 'entity_id']),
210
+ ]),
211
+ table('conflicts', 'authority', [
212
+ field('id', 'TEXT', { primaryKey: true }),
213
+ field('entity_type', 'TEXT', { notNull: true }),
214
+ field('entity_id', 'TEXT', { notNull: true }),
215
+ field('expected_revision', 'INTEGER', { notNull: true }),
216
+ field('actual_revision', 'INTEGER', { notNull: true }),
217
+ field('status', 'TEXT', { notNull: true, default: "'quarantined'" }),
218
+ field('payload_json', 'TEXT', { notNull: true }),
219
+ field('created_at', 'TEXT', { notNull: true }),
220
+ ], [
221
+ index('idx_kernel_conflicts_status', ['status']),
222
+ index('idx_kernel_conflicts_entity', ['entity_type', 'entity_id']),
223
+ ]),
224
+ table('events', 'authority', [
225
+ field('id', 'TEXT', { primaryKey: true }),
226
+ field('entity_type', 'TEXT', { notNull: true }),
227
+ field('entity_id', 'TEXT', { notNull: true }),
228
+ field('event_type', 'TEXT', { notNull: true }),
229
+ field('idempotency_key', 'TEXT', { notNull: true }),
230
+ field('expected_revision', 'INTEGER', { notNull: true }),
231
+ field('actor', 'TEXT', { notNull: true }),
232
+ field('origin', 'TEXT', { notNull: true }),
233
+ field('payload_json', 'TEXT', { notNull: true }),
234
+ field('created_at', 'TEXT', { notNull: true }),
235
+ ], [
236
+ index('idx_kernel_events_entity_created', ['entity_type', 'entity_id', 'created_at']),
237
+ index('idx_kernel_events_idempotency', ['idempotency_key'], { unique: true }),
238
+ ]),
239
+ table('outbox', 'projection', [
240
+ field('id', 'TEXT', { primaryKey: true }),
241
+ field('event_id', 'TEXT', { notNull: true, references: 'events.id' }),
242
+ field('target', 'TEXT', { notNull: true }),
243
+ field('status', 'TEXT', { notNull: true, default: "'pending'" }),
244
+ field('attempts', 'INTEGER', { notNull: true, default: '0' }),
245
+ field('next_attempt_at', 'TEXT'),
246
+ field('created_at', 'TEXT', { notNull: true }),
247
+ ], [
248
+ index('idx_kernel_outbox_target_status', ['target', 'status']),
249
+ ]),
250
+ table('dead_letters', 'projection', [
251
+ field('id', 'TEXT', { primaryKey: true }),
252
+ field('outbox_id', 'TEXT'),
253
+ field('target', 'TEXT', { notNull: true }),
254
+ field('status', 'TEXT', { notNull: true, default: "'open'" }),
255
+ field('error', 'TEXT', { notNull: true }),
256
+ field('payload_json', 'TEXT', { notNull: true }),
257
+ field('created_at', 'TEXT', { notNull: true }),
258
+ ], [
259
+ index('idx_kernel_dead_letters_status', ['status']),
260
+ ]),
261
+ // Project memory is a Forge read model: durable key/value notes written DIRECTLY
262
+ // (NOT through the issue CAS/guarded-event path). `key` is the stable identity;
263
+ // value_json holds the serialized memory value, and the *_json columns carry the
264
+ // optional list fields. Created by migration 005 (excluded from the 001 initial
265
+ // schema), so a fresh DB creates it exactly once and an existing DB picks it up
266
+ // via the broker ledger.
267
+ table('memories', 'read_model', [
268
+ field('key', 'TEXT', { primaryKey: true }),
269
+ field('value_json', 'TEXT', { notNull: true }),
270
+ field('source_agent', 'TEXT', { notNull: true }),
271
+ field('scope', 'TEXT'),
272
+ field('confidence', 'REAL'),
273
+ field('tags_json', 'TEXT'),
274
+ field('supersedes_json', 'TEXT'),
275
+ field('beads_refs_json', 'TEXT'),
276
+ field('created_at', 'TEXT', { notNull: true }),
277
+ field('updated_at', 'TEXT', { notNull: true }),
278
+ ], [
279
+ index('idx_kernel_memories_source_agent', ['source_agent']),
280
+ ]),
281
+ ]);
282
+
283
+ const KERNEL_TABLES = deepFreeze(Object.fromEntries(TABLE_LIST.map(candidate => [candidate.name, candidate])));
284
+
285
+ function classifyKernelStorage(tableName) {
286
+ const tableDefinition = KERNEL_TABLES[tableName];
287
+ if (!tableDefinition) {
288
+ throw new Error(`Unknown Kernel table: ${tableName}`);
289
+ }
290
+
291
+ return {
292
+ table: tableDefinition.name,
293
+ sqlName: tableDefinition.sqlName,
294
+ storageClass: tableDefinition.storageClass,
295
+ authority: tableDefinition.authority,
296
+ };
297
+ }
298
+
299
+ function validateKernelSchema(schema = getKernelSchema()) {
300
+ const tableNames = new Set();
301
+ for (const tableDefinition of schema.tables) {
302
+ if (tableNames.has(tableDefinition.name)) {
303
+ throw new Error(`Duplicate Kernel table: ${tableDefinition.name}`);
304
+ }
305
+ tableNames.add(tableDefinition.name);
306
+ if (!KERNEL_STORAGE_CLASSES.includes(tableDefinition.storageClass)) {
307
+ throw new Error(`Invalid storage class for ${tableDefinition.name}: ${tableDefinition.storageClass}`);
308
+ }
309
+ if (!KERNEL_FIELD_AUTHORITIES.includes(tableDefinition.authority)) {
310
+ throw new Error(`Invalid table authority for ${tableDefinition.name}: ${tableDefinition.authority}`);
311
+ }
312
+ for (const fieldDefinition of tableDefinition.fields) {
313
+ if (!KERNEL_STORAGE_CLASSES.includes(fieldDefinition.storageClass)) {
314
+ throw new Error(`Invalid storage class for ${tableDefinition.name}.${fieldDefinition.name}: ${fieldDefinition.storageClass}`);
315
+ }
316
+ if (!KERNEL_FIELD_AUTHORITIES.includes(fieldDefinition.authority)) {
317
+ throw new Error(`Invalid field authority for ${tableDefinition.name}.${fieldDefinition.name}: ${fieldDefinition.authority}`);
318
+ }
319
+ }
320
+ }
321
+
322
+ return true;
323
+ }
324
+
325
+ /** Deep-clone a table list so callers receive isolated, mutation-safe schema definitions. */
326
+ function cloneTableList(tableList) {
327
+ return tableList.map(tableDefinition => ({
328
+ ...tableDefinition,
329
+ fields: tableDefinition.fields.map(fieldDefinition => ({ ...fieldDefinition })),
330
+ indexes: tableDefinition.indexes.map(indexDefinition => ({
331
+ ...indexDefinition,
332
+ columns: [...indexDefinition.columns],
333
+ })),
334
+ }));
335
+ }
336
+
337
+ function getKernelSchema() {
338
+ return {
339
+ version: 1,
340
+ tables: cloneTableList(TABLE_LIST),
341
+ };
342
+ }
343
+
344
+ module.exports = {
345
+ KERNEL_FIELD_AUTHORITIES,
346
+ KERNEL_STORAGE_CLASSES,
347
+ KERNEL_TABLES,
348
+ classifyKernelStorage,
349
+ cloneTableList,
350
+ deepFreeze,
351
+ field,
352
+ getKernelSchema,
353
+ index,
354
+ table,
355
+ validateKernelSchema,
356
+ };