forge-workflow 0.0.9 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +466 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/reference/TOOLCHAIN.md +658 -0
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +66 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +22 -2
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +851 -979
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +697 -0
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. package/scripts/sync-commands.js +0 -600
@@ -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
+ };