forge-workflow 0.0.10 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +82 -5
  5. package/.forge/hooks/forge-native-hook.js +431 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +709 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +175 -0
  11. package/README.md +275 -365
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +532 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +121 -0
  29. package/docs/guides/SUPPORT.md +190 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +214 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +155 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/activation/ensure-forge-home.js +135 -0
  67. package/lib/adapter-cli.js +307 -0
  68. package/lib/adapters/beads-issue-adapter.js +127 -0
  69. package/lib/adapters/beads-kernel-compat.js +1109 -0
  70. package/lib/adapters/greptile-review-adapter.js +141 -0
  71. package/lib/adapters/kernel-issue-adapter.js +101 -0
  72. package/lib/adapters/pr-state-adapter.js +484 -0
  73. package/lib/adoption-profiles.js +139 -0
  74. package/lib/agents/README.md +2 -6
  75. package/lib/agents/claude.plugin.json +3 -8
  76. package/lib/agents/codex.plugin.json +9 -1
  77. package/lib/agents/cursor.plugin.json +2 -6
  78. package/lib/agents/hermes.plugin.json +22 -0
  79. package/lib/agents-config.js +39 -1236
  80. package/lib/audit-evidence.js +282 -0
  81. package/lib/beads-detect.js +60 -0
  82. package/lib/beads-nudge.js +91 -0
  83. package/lib/beads-setup.js +121 -0
  84. package/lib/beads-sync-scaffold.js +25 -101
  85. package/lib/codex-skills.js +51 -1
  86. package/lib/commands/_aliases.js +248 -0
  87. package/lib/commands/_issue.js +780 -77
  88. package/lib/commands/_manifest.js +93 -0
  89. package/lib/commands/_registry.js +99 -34
  90. package/lib/commands/_resolve-command-opts.js +230 -0
  91. package/lib/commands/_serve-security.js +270 -0
  92. package/lib/commands/adapter.js +12 -0
  93. package/lib/commands/add.js +118 -0
  94. package/lib/commands/audit.js +70 -0
  95. package/lib/commands/blocked.js +5 -0
  96. package/lib/commands/board.js +64 -0
  97. package/lib/commands/claim.js +21 -2
  98. package/lib/commands/claims.js +7 -0
  99. package/lib/commands/clean.js +485 -75
  100. package/lib/commands/close.js +2 -2
  101. package/lib/commands/comment.js +5 -0
  102. package/lib/commands/control.js +148 -0
  103. package/lib/commands/create.js +2 -2
  104. package/lib/commands/dev.js +185 -7
  105. package/lib/commands/doc-gate.js +336 -0
  106. package/lib/commands/doctor.js +156 -0
  107. package/lib/commands/explain.js +15 -0
  108. package/lib/commands/export.js +237 -0
  109. package/lib/commands/gate.js +209 -0
  110. package/lib/commands/hooks.js +377 -0
  111. package/lib/commands/inbox.js +118 -0
  112. package/lib/commands/init.js +604 -0
  113. package/lib/commands/insights.js +79 -0
  114. package/lib/commands/issue.js +12 -1
  115. package/lib/commands/issues.js +17 -0
  116. package/lib/commands/lint.js +5 -0
  117. package/lib/commands/list.js +2 -2
  118. package/lib/commands/memory.js +81 -0
  119. package/lib/commands/merge.js +312 -0
  120. package/lib/commands/migrate.js +362 -0
  121. package/lib/commands/new.js +12 -0
  122. package/lib/commands/options.js +241 -0
  123. package/lib/commands/orient.js +13 -0
  124. package/lib/commands/orphans.js +5 -0
  125. package/lib/commands/patch.js +67 -0
  126. package/lib/commands/plan.js +481 -29
  127. package/lib/commands/pr.js +88 -0
  128. package/lib/commands/preflight.js +211 -0
  129. package/lib/commands/prime.js +13 -0
  130. package/lib/commands/push.js +135 -2
  131. package/lib/commands/ready.js +2 -2
  132. package/lib/commands/recall.js +171 -0
  133. package/lib/commands/recap.js +75 -0
  134. package/lib/commands/recommend.js +0 -1
  135. package/lib/commands/release.js +104 -0
  136. package/lib/commands/remember.js +140 -0
  137. package/lib/commands/role.js +99 -0
  138. package/lib/commands/serve.js +581 -0
  139. package/lib/commands/setup.js +900 -971
  140. package/lib/commands/shepherd.js +501 -0
  141. package/lib/commands/ship.js +59 -1
  142. package/lib/commands/show.js +2 -2
  143. package/lib/commands/stage.js +192 -0
  144. package/lib/commands/stale.js +5 -0
  145. package/lib/commands/status.js +158 -21
  146. package/lib/commands/sync.js +34 -46
  147. package/lib/commands/team.js +4 -1
  148. package/lib/commands/test.js +43 -27
  149. package/lib/commands/update.js +2 -2
  150. package/lib/commands/upgrade.js +47 -0
  151. package/lib/commands/validate.js +43 -18
  152. package/lib/commands/worktree.js +362 -99
  153. package/lib/config-writer.js +202 -0
  154. package/lib/control-plane.js +236 -0
  155. package/lib/core/runtime-graph.js +977 -0
  156. package/lib/dep-guard/keyword-ripple.js +2 -2
  157. package/lib/deprecated-sync-cleanup.js +362 -0
  158. package/lib/detect-agent.js +2 -28
  159. package/lib/detect-worktree.js +35 -9
  160. package/lib/doc-gate/declaration.js +177 -0
  161. package/lib/doc-gate/detect.js +289 -0
  162. package/lib/doc-gate/gate.js +375 -0
  163. package/lib/doc-gate/okf-config.js +128 -0
  164. package/lib/doc-gate/okf.js +429 -0
  165. package/lib/docs-command.js +1161 -6
  166. package/lib/forge-issues.js +382 -11
  167. package/lib/forge-lock.js +262 -0
  168. package/lib/gate-events.js +192 -0
  169. package/lib/global-flags.js +104 -0
  170. package/lib/greptile-match.js +7 -63
  171. package/lib/grounding/context-events.js +230 -0
  172. package/lib/grounding/read-first.js +112 -0
  173. package/lib/harness-capability-matrix.js +380 -0
  174. package/lib/hook-global-installer.js +347 -0
  175. package/lib/hook-renderer.js +541 -0
  176. package/lib/inbox.js +391 -0
  177. package/lib/insights.js +397 -0
  178. package/lib/issue-adapter.js +156 -0
  179. package/lib/issue-backend.js +145 -0
  180. package/lib/issue-render.js +220 -0
  181. package/lib/kernel/backing-issue.js +311 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/owned-kernel.js +43 -0
  192. package/lib/kernel/planning-buckets-schema.js +109 -0
  193. package/lib/kernel/projection-jsonl-writer.js +450 -0
  194. package/lib/kernel/readiness-model.js +329 -0
  195. package/lib/kernel/schema.js +356 -0
  196. package/lib/kernel/sqlite-driver.js +2540 -0
  197. package/lib/kernel/taxonomy-validator.js +394 -0
  198. package/lib/lefthook-check.js +3 -2
  199. package/lib/lefthook-wiring.js +413 -0
  200. package/lib/mcp-config-renderer.js +288 -0
  201. package/lib/memory/graphiti-mcp.js +106 -0
  202. package/lib/memory/router.js +387 -0
  203. package/lib/memory/typed-api.js +102 -0
  204. package/lib/memory-digest.js +195 -0
  205. package/lib/merge-rules.js +395 -0
  206. package/lib/migrate-dry-run.js +466 -0
  207. package/lib/orientation.js +863 -0
  208. package/lib/package-manager-remediation.js +103 -0
  209. package/lib/package-root.js +381 -0
  210. package/lib/patch-intent.js +890 -0
  211. package/lib/plugin-catalog.js +3 -4
  212. package/lib/plugin-manager.js +0 -5
  213. package/lib/pr-bundle.js +186 -0
  214. package/lib/pr-monitor/auto-actions.js +175 -0
  215. package/lib/pr-monitor/differ.js +195 -0
  216. package/lib/pr-monitor/digest.js +206 -0
  217. package/lib/pr-monitor/events.js +0 -0
  218. package/lib/pr-monitor/gather.js +124 -0
  219. package/lib/pr-monitor/journal.js +299 -0
  220. package/lib/pr-monitor/monitor.js +146 -0
  221. package/lib/pr-monitor/render-sticky.js +192 -0
  222. package/lib/pr-monitor/upsert-sticky.js +169 -0
  223. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  224. package/lib/pr-monitor/watch.js +247 -0
  225. package/lib/pr-pull.js +1314 -0
  226. package/lib/pr-shepherd.js +494 -0
  227. package/lib/pr-state-validator.js +59 -0
  228. package/lib/preflight/gates.js +237 -0
  229. package/lib/preflight/runner.js +116 -0
  230. package/lib/project-discovery.js +0 -53
  231. package/lib/project-memory.js +99 -497
  232. package/lib/protected-path-manifest.js +281 -0
  233. package/lib/protected-state-surfaces.js +387 -0
  234. package/lib/release-readiness.js +2105 -0
  235. package/lib/reset.js +59 -45
  236. package/lib/review-adapter.js +68 -0
  237. package/lib/rules-sync.js +260 -0
  238. package/lib/runtime-health.js +241 -20
  239. package/lib/safety-config-renderer.js +268 -0
  240. package/lib/setup-action-log.js +1 -7
  241. package/lib/setup.js +27 -65
  242. package/lib/shell-utils.js +76 -6
  243. package/lib/skills-sync.js +330 -0
  244. package/lib/smart-status/scoring.js +17 -3
  245. package/lib/status/beads-snapshot.js +45 -2
  246. package/lib/status/presenter.js +169 -18
  247. package/lib/status/snapshot.js +186 -0
  248. package/lib/sync-backend.js +202 -0
  249. package/lib/untrusted-content.js +52 -0
  250. package/lib/upgrade-safety.js +251 -0
  251. package/lib/workflow/enforce-stage.js +351 -45
  252. package/lib/workflow/stage-transition.js +115 -0
  253. package/lib/workflow/stages.js +30 -6
  254. package/lib/workflow/state-manager.js +11 -22
  255. package/lib/workflow/state.js +23 -1
  256. package/lib/workflow-profiles.js +17 -5
  257. package/package.json +37 -35
  258. package/rules/documentation.md +19 -0
  259. package/rules/kernel-tracking.md +26 -0
  260. package/rules/security.md +22 -0
  261. package/rules/tdd.md +20 -0
  262. package/rules/workflow.md +27 -0
  263. package/scripts/auto-backing-issue.js +47 -0
  264. package/scripts/beads-context.sh +81 -57
  265. package/scripts/beads-upgrade-smoke.sh +24 -3
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard.sh +22 -3
  272. package/scripts/file-index.sh +3 -0
  273. package/scripts/forge-team/lib/claim.sh +34 -18
  274. package/scripts/forge-team/lib/dashboard.sh +61 -86
  275. package/scripts/forge-team/lib/epic.sh +99 -263
  276. package/scripts/forge-team/lib/hooks.sh +26 -28
  277. package/scripts/forge-team/lib/identity.sh +4 -4
  278. package/scripts/forge-team/lib/sync-github.sh +49 -84
  279. package/scripts/forge-team/lib/verify.sh +93 -83
  280. package/scripts/forge-team/lib/workload.sh +41 -65
  281. package/scripts/forge-team/tests/claim.test.sh +25 -19
  282. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  283. package/scripts/forge-team/tests/epic.test.sh +52 -71
  284. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  285. package/scripts/forge-team/tests/identity.test.sh +3 -3
  286. package/scripts/forge-team/tests/integration.test.sh +44 -66
  287. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  288. package/scripts/forge-team/tests/verify.test.sh +37 -46
  289. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  290. package/scripts/forge-team/tests/workload.test.sh +32 -66
  291. package/scripts/gen-command-manifest.js +153 -0
  292. package/scripts/gen-embedded-assets.mjs +129 -0
  293. package/scripts/install.ps1 +139 -0
  294. package/scripts/install.sh +268 -0
  295. package/scripts/lib/release-asset.mjs +84 -0
  296. package/scripts/parity-check.mjs +145 -0
  297. package/scripts/parity-check.test.mjs +58 -0
  298. package/scripts/pin-agentic-workflow-images.js +112 -0
  299. package/scripts/pr-auto-actions.js +93 -0
  300. package/scripts/pr-coordinator.sh +3 -0
  301. package/scripts/pr-verdict-label.js +50 -0
  302. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  303. package/scripts/preflight.sh +21 -94
  304. package/scripts/protected-state-check.js +104 -0
  305. package/scripts/smart-status.sh +60 -57
  306. package/scripts/spikes/config-race-bench.js +111 -0
  307. package/scripts/spikes/harness-capability-matrix.js +13 -0
  308. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  309. package/scripts/spikes/protected-path-manifest.js +20 -0
  310. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  311. package/scripts/sync-agent-skills.js +62 -0
  312. package/scripts/sync-utils.sh +3 -0
  313. package/scripts/test-ci-shard.js +13 -6
  314. package/scripts/test.js +95 -12
  315. package/skills/claim-safety/SKILL.md +102 -0
  316. package/skills/claim-safety/evals/evals.json +46 -0
  317. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  318. package/skills/dev/evals/evals.json +50 -0
  319. package/skills/hermes-forge/SKILL.md +185 -0
  320. package/skills/hermes-forge/evals/evals.json +46 -0
  321. package/skills/issue-basics/SKILL.md +111 -0
  322. package/skills/issue-basics/evals/evals.json +46 -0
  323. package/skills/kernel/SKILL.md +166 -0
  324. package/skills/kernel/evals/evals.json +50 -0
  325. package/skills/memory/SKILL.md +102 -0
  326. package/skills/parallel-deep-research/SKILL.md +14 -11
  327. package/skills/parallel-deep-research/evals/evals.json +11 -27
  328. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  329. package/skills/plan/evals/evals.json +42 -0
  330. package/skills/research/SKILL.md +195 -0
  331. package/skills/research/evals/evals.json +42 -0
  332. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  333. package/skills/review/evals/evals.json +42 -0
  334. package/skills/rollback/SKILL.md +110 -0
  335. package/skills/rollback/evals/evals.json +46 -0
  336. package/skills/rollback/references/methods.md +204 -0
  337. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  338. package/skills/shepherd/SKILL.md +66 -0
  339. package/skills/shepherd/evals/evals.json +42 -0
  340. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  341. package/skills/ship/evals/evals.json +42 -0
  342. package/skills/smith/SKILL.md +142 -0
  343. package/skills/smith/evals/evals.json +46 -0
  344. package/skills/smith/references/autonomy-and-gates.md +94 -0
  345. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  346. package/skills/sonarcloud/evals/evals.json +46 -0
  347. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  348. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  349. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  350. package/skills/status/evals/evals.json +50 -0
  351. package/skills/triage-ready/SKILL.md +121 -0
  352. package/skills/triage-ready/evals/evals.json +42 -0
  353. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  354. package/skills/validate/evals/evals.json +42 -0
  355. package/skills/verify/SKILL.md +299 -0
  356. package/skills/verify/evals/evals.json +50 -0
  357. package/.claude/commands/dev.md +0 -345
  358. package/.claude/commands/plan.md +0 -566
  359. package/.claude/commands/premerge.md +0 -186
  360. package/.claude/commands/research.md +0 -42
  361. package/.claude/commands/review.md +0 -451
  362. package/.claude/commands/rollback.md +0 -721
  363. package/.claude/commands/ship.md +0 -213
  364. package/.claude/commands/sonarcloud.md +0 -152
  365. package/.claude/commands/status.md +0 -90
  366. package/.claude/commands/validate.md +0 -288
  367. package/.claude/commands/verify.md +0 -269
  368. package/.claude/rules/workflow.md +0 -121
  369. package/.cline/workflows/dev.md +0 -342
  370. package/.cline/workflows/plan.md +0 -563
  371. package/.cline/workflows/premerge.md +0 -183
  372. package/.cline/workflows/research.md +0 -39
  373. package/.cline/workflows/review.md +0 -448
  374. package/.cline/workflows/rollback.md +0 -718
  375. package/.cline/workflows/ship.md +0 -210
  376. package/.cline/workflows/sonarcloud.md +0 -146
  377. package/.cline/workflows/status.md +0 -87
  378. package/.cline/workflows/validate.md +0 -285
  379. package/.cline/workflows/verify.md +0 -266
  380. package/.codex/config.toml +0 -11
  381. package/.codex/skills/dev/SKILL.md +0 -345
  382. package/.codex/skills/plan/SKILL.md +0 -566
  383. package/.codex/skills/premerge/SKILL.md +0 -186
  384. package/.codex/skills/research/SKILL.md +0 -42
  385. package/.codex/skills/review/SKILL.md +0 -451
  386. package/.codex/skills/rollback/SKILL.md +0 -721
  387. package/.codex/skills/ship/SKILL.md +0 -213
  388. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  389. package/.codex/skills/status/SKILL.md +0 -90
  390. package/.codex/skills/validate/SKILL.md +0 -288
  391. package/.codex/skills/verify/SKILL.md +0 -269
  392. package/.cursor/commands/dev.md +0 -342
  393. package/.cursor/commands/plan.md +0 -563
  394. package/.cursor/commands/premerge.md +0 -183
  395. package/.cursor/commands/research.md +0 -39
  396. package/.cursor/commands/review.md +0 -448
  397. package/.cursor/commands/ship.md +0 -210
  398. package/.cursor/commands/sonarcloud.md +0 -146
  399. package/.cursor/commands/status.md +0 -87
  400. package/.cursor/commands/validate.md +0 -285
  401. package/.cursor/commands/verify.md +0 -266
  402. package/.cursorrules +0 -149
  403. package/.github/prompts/premerge.prompt.md +0 -188
  404. package/.github/prompts/research.prompt.md +0 -44
  405. package/.github/prompts/rollback.prompt.md +0 -723
  406. package/.github/prompts/verify.prompt.md +0 -271
  407. package/.github/workflows/beads-to-github.yml +0 -89
  408. package/.github/workflows/github-to-beads.yml +0 -100
  409. package/.kilocode/workflows/dev.md +0 -346
  410. package/.kilocode/workflows/plan.md +0 -567
  411. package/.kilocode/workflows/premerge.md +0 -187
  412. package/.kilocode/workflows/research.md +0 -43
  413. package/.kilocode/workflows/review.md +0 -452
  414. package/.kilocode/workflows/rollback.md +0 -722
  415. package/.kilocode/workflows/ship.md +0 -214
  416. package/.kilocode/workflows/sonarcloud.md +0 -150
  417. package/.kilocode/workflows/status.md +0 -91
  418. package/.kilocode/workflows/validate.md +0 -289
  419. package/.kilocode/workflows/verify.md +0 -270
  420. package/.opencode/commands/dev.md +0 -345
  421. package/.opencode/commands/plan.md +0 -566
  422. package/.opencode/commands/premerge.md +0 -186
  423. package/.opencode/commands/research.md +0 -42
  424. package/.opencode/commands/review.md +0 -451
  425. package/.opencode/commands/rollback.md +0 -721
  426. package/.opencode/commands/ship.md +0 -213
  427. package/.opencode/commands/sonarcloud.md +0 -149
  428. package/.opencode/commands/status.md +0 -90
  429. package/.opencode/commands/validate.md +0 -288
  430. package/.opencode/commands/verify.md +0 -269
  431. package/.roo/commands/dev.md +0 -346
  432. package/.roo/commands/plan.md +0 -567
  433. package/.roo/commands/premerge.md +0 -187
  434. package/.roo/commands/research.md +0 -43
  435. package/.roo/commands/review.md +0 -452
  436. package/.roo/commands/rollback.md +0 -722
  437. package/.roo/commands/ship.md +0 -214
  438. package/.roo/commands/sonarcloud.md +0 -150
  439. package/.roo/commands/status.md +0 -91
  440. package/.roo/commands/validate.md +0 -289
  441. package/.roo/commands/verify.md +0 -270
  442. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  443. package/docs/GREPTILE_SETUP.md +0 -400
  444. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  445. package/docs/SETUP.md +0 -663
  446. package/docs/VALIDATION.md +0 -363
  447. package/lib/agents/cline.plugin.json +0 -29
  448. package/lib/agents/copilot.plugin.json +0 -24
  449. package/lib/agents/kilocode.plugin.json +0 -22
  450. package/lib/agents/opencode.plugin.json +0 -23
  451. package/lib/agents/roo.plugin.json +0 -30
  452. package/lib/beads-bootstrap.js +0 -225
  453. package/lib/beads-health-check.js +0 -188
  454. package/lib/commands/commands-reset.js +0 -147
  455. package/opencode.json +0 -67
  456. package/scripts/beads-context.test.js +0 -584
  457. package/scripts/github-beads-sync/comment.mjs +0 -64
  458. package/scripts/github-beads-sync/config.mjs +0 -148
  459. package/scripts/github-beads-sync/github-api.mjs +0 -131
  460. package/scripts/github-beads-sync/index.mjs +0 -356
  461. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  462. package/scripts/github-beads-sync/mapping.mjs +0 -132
  463. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  464. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  465. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  466. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  467. package/scripts/github-beads-sync.config.json +0 -26
  468. package/scripts/sync-commands.js +0 -600
@@ -1,320 +1,37 @@
1
- const fs = require('node:fs');
2
- const path = require('node:path');
3
- const crypto = require('node:crypto');
1
+ 'use strict';
4
2
 
5
- const DEFAULT_MEMORY_FILE = path.join('.forge', 'memory', 'entries.jsonl');
6
- const DEFAULT_LOCK_TIMEOUT_MS = 5000;
7
- const DEFAULT_LOCK_RETRY_MS = 10;
8
- const DEFAULT_DEAD_LOCK_GRACE_MS = 1000;
9
- const INVALID_LOCK_INITIALIZATION_BUFFER_MS = 1000;
3
+ const { resolveKernelDatabasePath } = require('./kernel/cli-broker-factory');
4
+ const { createBuiltinSQLiteDriver } = require('./kernel/sqlite-driver');
10
5
 
11
- function assertInsideProjectRoot(root, targetPath) {
12
- const relative = path.relative(root, targetPath);
13
- if (relative === '' || relative.startsWith('..') || path.isAbsolute(relative)) {
14
- throw new Error('project memory filePath must stay within projectRoot');
15
- }
16
- }
17
-
18
- function nearestExistingPath(targetPath) {
19
- let current = targetPath;
20
- while (!fs.existsSync(current)) {
21
- const next = path.dirname(current);
22
- if (next === current) break;
23
- current = next;
24
- }
25
-
26
- return current;
27
- }
28
-
29
- function assertRealPathInsideProjectRoot(root, targetPath) {
30
- const realRoot = fs.realpathSync.native(root);
31
- const existingPath = fs.existsSync(targetPath)
32
- ? targetPath
33
- : nearestExistingPath(path.dirname(targetPath));
34
- const realExistingPath = fs.realpathSync.native(existingPath);
35
-
36
- if (realExistingPath === realRoot) {
37
- return;
38
- }
39
-
40
- assertInsideProjectRoot(realRoot, realExistingPath);
41
- }
42
-
43
- function memoryPath(projectRoot, options = {}) {
44
- const root = path.resolve(projectRoot);
45
- if (!options.filePath) {
46
- const defaultPath = path.join(root, DEFAULT_MEMORY_FILE);
47
- assertRealPathInsideProjectRoot(root, defaultPath);
48
- return defaultPath;
49
- }
50
-
51
- const candidate = path.isAbsolute(options.filePath)
52
- ? options.filePath
53
- : path.join(root, options.filePath);
54
- const resolved = path.resolve(candidate);
55
- assertInsideProjectRoot(root, resolved);
56
- assertRealPathInsideProjectRoot(root, resolved);
57
-
58
- return resolved;
59
- }
60
-
61
- function sleep(ms) {
62
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
63
- }
64
-
65
- function isProcessAlive(pid) {
66
- if (!Number.isInteger(pid) || pid <= 0) return false;
67
-
68
- try {
69
- process.kill(pid, 0);
70
- return true;
71
- } catch (err) {
72
- return err.code === 'EPERM';
73
- }
74
- }
75
-
76
- function readLock(lockPath) {
77
- try {
78
- const lockStat = fs.statSync(lockPath);
79
- const metadataPath = lockStat.isDirectory()
80
- ? path.join(lockPath, 'owner.json')
81
- : lockPath;
82
- return JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
83
- } catch (err) {
84
- return {
85
- pid: null,
86
- createdAt: null,
87
- invalid: true,
88
- readError: err.message,
89
- };
90
- }
91
- }
92
-
93
- function isLockDirectory(lockPath) {
94
- try {
95
- return fs.statSync(lockPath).isDirectory();
96
- } catch (err) {
97
- if (err.code !== 'ENOENT') {
98
- throw err;
99
- }
100
- return false;
101
- }
102
- }
103
-
104
- function lockPathAgeMs(lockPath) {
105
- try {
106
- return Date.now() - fs.statSync(lockPath).mtimeMs;
107
- } catch (err) {
108
- if (err.code !== 'ENOENT') {
109
- throw err;
110
- }
111
- return null;
112
- }
113
- }
114
-
115
- function lockAgeMs(lock) {
116
- const createdAt = Date.parse(lock?.createdAt);
117
- if (Number.isNaN(createdAt)) return null;
118
- return Date.now() - createdAt;
119
- }
120
-
121
- function isWithinGraceWindow(ageMs, graceMs) {
122
- return ageMs !== null && Math.abs(ageMs) < graceMs;
123
- }
124
-
125
- function debugSuppressedLockError(message, err) {
126
- if (process.env.FORGE_DEBUG_LOCKS) {
127
- console.debug(`${message}: ${err.message}`);
128
- }
129
- }
130
-
131
- function shouldKeepInvalidLock(lockPath, lock, options) {
132
- if (!lock.invalid) {
133
- return false;
134
- }
135
-
136
- const lockTimeoutMs = options.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
137
- const lockRetryMs = options.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;
138
- const invalidLockGraceMs = lockTimeoutMs + Math.max(lockRetryMs, INVALID_LOCK_INITIALIZATION_BUFFER_MS);
139
- const lockPathAge = lockPathAgeMs(lockPath);
140
- return isWithinGraceWindow(lockPathAge, invalidLockGraceMs);
141
- }
6
+ // Project memory is a Forge read model persisted in the kernel store (kernel_memories),
7
+ // written DIRECTLY rather than through the issue CAS/guarded-event path. The store seam
8
+ // (options.store) is a driver-like object { recordMemory, loadMemory, searchMemories,
9
+ // listMemories } so hermetic tests stay in-memory. The default seam resolves the
10
+ // per-repo kernel database path and reuses one driver per database path (the CLI process
11
+ // is short-lived, so the connection closing on exit is sufficient cleanup).
142
12
 
143
- function shouldKeepDeadOwnerLock(lock, options) {
144
- if (lock.invalid) {
145
- return false;
146
- }
147
-
148
- const configuredGraceMs = options.deadLockGraceMs ?? DEFAULT_DEAD_LOCK_GRACE_MS;
149
- const lockTimeoutMs = options.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
150
- const deadLockGraceMs = lockTimeoutMs <= configuredGraceMs ? 0 : configuredGraceMs;
151
- const age = lockAgeMs(lock);
152
- const tokenizedLock = typeof lock?.token === 'string' && lock.token !== '';
153
- const tokenRecoveryGraceMs = Math.max(deadLockGraceMs, Math.floor(lockTimeoutMs * 0.9));
154
-
155
- if (tokenizedLock && age !== null && age >= 0 && age < tokenRecoveryGraceMs) {
156
- return true;
157
- }
158
-
159
- return age !== null && age >= 0 && age < deadLockGraceMs;
160
- }
161
-
162
- function removeLockPath(lockPath) {
163
- try {
164
- fs.rmSync(lockPath, { recursive: true, force: true });
165
- return true;
166
- } catch (err) {
167
- debugSuppressedLockError(`project memory lock cleanup skipped for ${lockPath}`, err);
168
- return false;
169
- }
170
- }
171
-
172
- function removeStaleLock(lockPath, options = {}) {
173
- const lock = readLock(lockPath);
174
- if (shouldKeepInvalidLock(lockPath, lock, options)) {
175
- return false;
176
- }
177
-
178
- if (isProcessAlive(lock?.pid)) {
179
- return false;
180
- }
181
-
182
- if (shouldKeepDeadOwnerLock(lock, options)) {
183
- return false;
184
- }
185
-
186
- return removeLockPath(lockPath);
187
- }
188
-
189
- function isExistingLockError(err, lockPath) {
190
- if (err.code === 'EEXIST' || err.code === 'EISDIR') {
191
- return true;
192
- }
193
-
194
- return err.code === 'EPERM' && isLockDirectory(lockPath);
195
- }
196
-
197
- function cleanupCreatedLockFile(lockPath) {
198
- try {
199
- fs.unlinkSync(lockPath);
200
- } catch (err) {
201
- if (err.code !== 'ENOENT') {
202
- debugSuppressedLockError(`project memory lock init cleanup skipped for ${lockPath}`, err);
203
- }
204
- }
205
- }
206
-
207
- function closeLockFd(lockFd, lockPath) {
208
- try {
209
- fs.closeSync(lockFd);
210
- } catch (err) {
211
- debugSuppressedLockError(`project memory lock fd close failed for ${lockPath}`, err);
212
- throw err;
213
- }
214
- }
215
-
216
- function closeLockFdAfterFailure(lockFd, lockPath) {
217
- try {
218
- closeLockFd(lockFd, lockPath);
219
- } catch (err) {
220
- debugSuppressedLockError(`project memory lock fd close skipped after failure for ${lockPath}`, err);
221
- }
222
- }
223
-
224
- function writeLockMetadata(lockFd, token) {
225
- fs.writeFileSync(lockFd, JSON.stringify({
226
- pid: process.pid,
227
- createdAt: new Date().toISOString(),
228
- token,
229
- }), 'utf8');
230
- fs.fsyncSync(lockFd);
231
- }
13
+ const storeCache = new Map();
232
14
 
233
- function initializeLockFile(lockPath, token) {
234
- const lockFd = fs.openSync(lockPath, 'wx');
235
- try {
236
- writeLockMetadata(lockFd, token);
237
- closeLockFd(lockFd, lockPath);
238
- } catch (err) {
239
- closeLockFdAfterFailure(lockFd, lockPath);
240
- cleanupCreatedLockFile(lockPath);
241
- throw err;
242
- }
243
- }
244
-
245
- function releaseLockFile(lockPath, token) {
246
- const lock = readLock(lockPath);
247
- if (!lock.invalid && lock.pid === process.pid && lock.token === token) {
248
- fs.rmSync(lockPath, { force: true });
249
- }
250
- }
251
-
252
- function acquireLock(filePath, options = {}) {
253
- const lockPath = `${filePath}.lock`;
254
- const timeoutMs = options.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
255
- const retryMs = options.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;
256
- const startedAt = Date.now();
257
- const token = `${process.pid}-${Date.now()}-${crypto.randomUUID()}`;
258
-
259
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
260
-
261
- while (true) {
262
- try {
263
- initializeLockFile(lockPath, token);
264
- return () => releaseLockFile(lockPath, token);
265
- } catch (err) {
266
- if (!isExistingLockError(err, lockPath)) {
267
- throw err;
268
- }
269
- if (removeStaleLock(lockPath, options)) {
270
- continue;
271
- }
272
- if (Date.now() - startedAt >= timeoutMs) {
273
- throw err;
274
- }
275
- sleep(retryMs);
276
- }
15
+ function defaultStore(projectRoot, options = {}) {
16
+ const databasePath = resolveKernelDatabasePath({
17
+ projectRoot,
18
+ gitCommonDir: options.gitCommonDir,
19
+ databasePath: options.databasePath,
20
+ });
21
+ let store = storeCache.get(databasePath);
22
+ if (!store) {
23
+ store = createBuiltinSQLiteDriver({ databasePath });
24
+ storeCache.set(databasePath, store);
277
25
  }
26
+ return store;
278
27
  }
279
28
 
280
- function toApiEntry(entry) {
281
- const apiEntry = {
282
- key: entry.key,
283
- value: entry.value,
284
- sourceAgent: entry['source-agent'],
285
- timestamp: entry.timestamp,
286
- tags: Array.isArray(entry.tags) ? [...entry.tags] : [],
287
- };
288
-
289
- if (entry.scope !== undefined) apiEntry.scope = entry.scope;
290
- if (entry.confidence !== undefined) apiEntry.confidence = entry.confidence;
291
- if (entry.supersedes !== undefined) apiEntry.supersedes = [...entry.supersedes];
292
- if (entry['beads-refs'] !== undefined) apiEntry.beadsRefs = [...entry['beads-refs']];
293
-
294
- return apiEntry;
295
- }
296
-
297
- function toDiskEntry(entry) {
298
- const diskEntry = {
299
- key: entry.key,
300
- value: entry.value,
301
- 'source-agent': entry.sourceAgent || entry['source-agent'],
302
- timestamp: entry.timestamp || new Date().toISOString(),
303
- tags: Array.isArray(entry.tags) ? [...entry.tags] : [],
304
- };
305
-
306
- if (entry.scope !== undefined) diskEntry.scope = entry.scope;
307
- if (entry.confidence !== undefined) diskEntry.confidence = entry.confidence;
308
- if (entry.supersedes !== undefined) diskEntry.supersedes = [...entry.supersedes];
309
- if (entry.beadsRefs !== undefined || entry['beads-refs'] !== undefined) {
310
- diskEntry['beads-refs'] = [...(entry.beadsRefs || entry['beads-refs'])];
311
- }
312
-
313
- return diskEntry;
29
+ function resolveStore(projectRoot, options = {}) {
30
+ return options.store ?? defaultStore(projectRoot, options);
314
31
  }
315
32
 
316
33
  function assertEntryObject(entry) {
317
- if (!entry || typeof entry !== 'object') {
34
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
318
35
  throw new TypeError('project memory entry must be an object');
319
36
  }
320
37
  }
@@ -326,234 +43,115 @@ function assertRequiredString(value, fieldName) {
326
43
  }
327
44
 
328
45
  function assertStringArray(value, fieldName) {
329
- if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
46
+ if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {
330
47
  throw new TypeError(`project memory entry ${fieldName} must be an array of strings`);
331
48
  }
332
49
  }
333
50
 
334
- function validateRequiredFields(entry) {
335
- if (typeof entry.key !== 'string' || entry.key.trim() === '') {
336
- throw new TypeError('project memory entry key is required');
51
+ function assertOptionalConfidence(value) {
52
+ if (value !== undefined && (!Number.isFinite(value) || value < 0 || value > 1)) {
53
+ throw new TypeError('project memory entry confidence must be a number from 0 to 1');
337
54
  }
55
+ }
56
+
57
+ function validateEntry(entry) {
58
+ assertEntryObject(entry);
59
+ assertRequiredString(entry.key, 'key');
338
60
  if (!Object.hasOwn(entry, 'value') || entry.value === undefined) {
339
61
  throw new TypeError('project memory entry value is required');
340
62
  }
341
-
342
- const sourceAgent = entry.sourceAgent || entry['source-agent'];
343
- assertRequiredString(sourceAgent, 'sourceAgent');
344
- }
345
-
346
- function validateTimestamp(entry) {
347
- if (entry.timestamp !== undefined) {
348
- const parsed = Date.parse(entry.timestamp);
349
- if (typeof entry.timestamp !== 'string' || Number.isNaN(parsed)) {
350
- throw new TypeError('project memory entry timestamp must be an ISO-compatible string');
351
- }
63
+ assertRequiredString(entry.sourceAgent || entry['source-agent'], 'sourceAgent');
64
+ if (entry.timestamp !== undefined
65
+ && (typeof entry.timestamp !== 'string' || Number.isNaN(Date.parse(entry.timestamp)))) {
66
+ throw new TypeError('project memory entry timestamp must be an ISO timestamp string');
352
67
  }
353
- }
354
-
355
- function validateScope(entry) {
356
68
  if (entry.scope !== undefined && (typeof entry.scope !== 'string' || entry.scope.trim() === '')) {
357
69
  throw new TypeError('project memory entry scope must be a non-empty string');
358
70
  }
359
- }
360
-
361
- function validateConfidence(entry) {
362
- if (entry.confidence !== undefined) {
363
- if (typeof entry.confidence !== 'number' || entry.confidence < 0 || entry.confidence > 1) {
364
- throw new TypeError('project memory entry confidence must be a number from 0 to 1');
365
- }
366
- }
367
- }
368
-
369
- function validateOptionalArrays(entry) {
71
+ assertOptionalConfidence(entry.confidence);
370
72
  if (entry.tags !== undefined) assertStringArray(entry.tags, 'tags');
371
73
  if (entry.supersedes !== undefined) assertStringArray(entry.supersedes, 'supersedes');
372
- const beadsRefs = entry.beadsRefs || entry['beads-refs'];
373
- if (beadsRefs !== undefined) assertStringArray(beadsRefs, 'beadsRefs');
374
- }
375
-
376
- function validateEntry(entry) {
377
- assertEntryObject(entry);
378
- validateRequiredFields(entry);
379
- validateTimestamp(entry);
380
- validateScope(entry);
381
- validateConfidence(entry);
382
- validateOptionalArrays(entry);
383
- }
384
-
385
- function validateStoredEntry(entry) {
386
- validateEntry(entry);
387
- assertRequiredString(entry['source-agent'], 'source-agent');
388
- assertStringArray(entry.tags, 'tags');
389
- }
390
-
391
- function readEntries(projectRoot, options = {}) {
392
- const filePath = memoryPath(projectRoot, options);
393
- if (!fs.existsSync(filePath)) {
394
- return [];
395
- }
396
-
397
- const content = fs.readFileSync(filePath, 'utf8');
398
- if (content.trim() === '') return [];
399
-
400
- const hasTerminatingNewline = /\r?\n$/.test(content);
401
- const lines = content.split(/\r?\n/);
402
- if (hasTerminatingNewline && lines.at(-1) === '') {
403
- lines.pop();
404
- }
405
-
406
- const entries = [];
407
- lines.forEach((line, index) => {
408
- const trailingIncompleteLine = !hasTerminatingNewline && index === lines.length - 1;
409
- if (trailingIncompleteLine) return;
410
-
411
- let parsed;
412
- try {
413
- parsed = JSON.parse(line);
414
- } catch (err) {
415
- if (trailingIncompleteLine) return;
416
- throw new Error(`invalid project memory JSONL at line ${index + 1}: ${err.message}`);
417
- }
418
- try {
419
- validateStoredEntry(parsed);
420
- } catch (err) {
421
- if (trailingIncompleteLine) return;
422
- throw new Error(`invalid project memory entry at line ${index + 1}: ${err.message}`);
423
- }
424
- entries.push(parsed);
425
- });
426
-
427
- return dedupeEntries(entries);
74
+ if (entry.beadsRefs !== undefined) assertStringArray(entry.beadsRefs, 'beadsRefs');
75
+ if (entry['beads-refs'] !== undefined) assertStringArray(entry['beads-refs'], 'beads-refs');
428
76
  }
429
77
 
430
- function dedupeEntries(entries) {
431
- const positions = new Map();
432
- const deduped = [];
78
+ // Canonicalize an entry for storage: resolve the snake-case input aliases, default the
79
+ // timestamp and tags, and carry the optional fields only when present (so the persisted
80
+ // shape matches the legacy entry exactly).
81
+ function normalizeEntry(key, entry) {
82
+ const normalized = {
83
+ key,
84
+ value: entry.value,
85
+ sourceAgent: entry.sourceAgent || entry['source-agent'],
86
+ tags: Array.isArray(entry.tags) ? [...entry.tags] : [],
87
+ timestamp: entry.timestamp ?? new Date().toISOString(),
88
+ };
433
89
 
434
- for (const entry of entries) {
435
- const existingIndex = positions.get(entry.key);
436
- if (existingIndex === undefined) {
437
- positions.set(entry.key, deduped.length);
438
- deduped.push(entry);
439
- } else {
440
- deduped[existingIndex] = entry;
441
- }
90
+ if (entry.scope !== undefined) normalized.scope = entry.scope;
91
+ if (entry.confidence !== undefined) normalized.confidence = entry.confidence;
92
+ if (entry.supersedes !== undefined) normalized.supersedes = [...entry.supersedes];
93
+ if (entry.beadsRefs !== undefined || entry['beads-refs'] !== undefined) {
94
+ normalized.beadsRefs = [...(entry.beadsRefs || entry['beads-refs'])];
442
95
  }
443
96
 
444
- return deduped;
97
+ return normalized;
445
98
  }
446
99
 
447
- function writeAllSync(fd, buffer) {
448
- let offset = 0;
449
- while (offset < buffer.length) {
450
- offset += fs.writeSync(fd, buffer, offset, buffer.length - offset);
451
- }
100
+ function write(projectRoot, entry, options = {}) {
101
+ validateEntry(entry);
102
+ const normalized = normalizeEntry(entry.key.trim(), entry);
103
+ resolveStore(projectRoot, options).recordMemory(normalized);
104
+ return normalized;
452
105
  }
453
106
 
454
- function normalizeJsonlTail(filePath) {
455
- if (!fs.existsSync(filePath)) {
456
- return false;
457
- }
458
-
459
- const content = fs.readFileSync(filePath, 'utf8');
460
- if (content === '' || /\r?\n$/.test(content)) {
461
- return false;
462
- }
463
-
464
- const lastLineStart = Math.max(content.lastIndexOf('\n'), content.lastIndexOf('\r')) + 1;
465
- fs.truncateSync(filePath, Buffer.byteLength(content.slice(0, lastLineStart), 'utf8'));
466
- return false;
107
+ function read(projectRoot, key, options = {}) {
108
+ assertRequiredString(key, 'read key');
109
+ return resolveStore(projectRoot, options).loadMemory(key.trim());
467
110
  }
468
111
 
469
- function appendEntry(projectRoot, entry, options = {}) {
470
- const filePath = memoryPath(projectRoot, options);
471
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
472
-
473
- const needsLineBreak = normalizeJsonlTail(filePath);
474
- const record = Buffer.from(`${needsLineBreak ? '\n' : ''}${JSON.stringify(entry)}\n`, 'utf8');
475
- const rollbackSize = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0;
476
- const fd = fs.openSync(filePath, 'a');
477
- let fdClosed = false;
478
- try {
479
- writeAllSync(fd, record);
480
- fs.fsyncSync(fd);
481
- } catch (err) {
482
- try {
483
- fs.closeSync(fd);
484
- fdClosed = true;
485
- } catch (_rollbackCloseErr) {
486
- // Preserve the original append/fsync error.
487
- }
488
- try {
489
- fs.truncateSync(filePath, rollbackSize);
490
- } catch (_rollbackErr) {
491
- // Preserve the original append/fsync error.
492
- }
493
- throw err;
494
- } finally {
495
- if (!fdClosed) {
496
- fs.closeSync(fd);
497
- }
112
+ function search(projectRoot, query, options = {}) {
113
+ if (typeof query !== 'string' || query.trim() === '') {
114
+ return [];
498
115
  }
116
+ return resolveStore(projectRoot, options).searchMemories(query.trim());
499
117
  }
500
118
 
501
119
  function list(projectRoot, options = {}) {
502
- return readEntries(projectRoot, options).map(toApiEntry);
120
+ return resolveStore(projectRoot, options).listMemories();
503
121
  }
504
122
 
505
- function read(projectRoot, key, options = {}) {
506
- if (typeof key !== 'string' || key.trim() === '') {
507
- throw new Error('project memory read key is required');
508
- }
509
- const normalizedKey = key.trim();
510
-
511
- const entry = readEntries(projectRoot, options).find((candidate) => candidate.key === normalizedKey);
512
- return entry ? toApiEntry(entry) : null;
123
+ // The newest `limit` entries (default recall with no query). Delegates to the FTS-backed
124
+ // driver so recall never loads and re-sorts the whole table. `options.agents` (a
125
+ // source_agent allow-list) scopes the read, e.g. to human `remember` notes only.
126
+ function recent(projectRoot, limit, options = {}) {
127
+ return resolveStore(projectRoot, options).recentMemories(limit, { agents: options.agents });
513
128
  }
514
129
 
515
- function write(projectRoot, entry, options = {}) {
516
- validateEntry(entry);
517
- const filePath = memoryPath(projectRoot, options);
518
- const releaseLock = acquireLock(filePath, options);
519
-
520
- try {
521
- const normalized = toDiskEntry({
522
- ...entry,
523
- key: entry.key.trim(),
524
- sourceAgent: (entry.sourceAgent || entry['source-agent']).trim(),
525
- tags: entry.tags || [],
526
- });
527
-
528
- appendEntry(projectRoot, normalized, options);
529
- return toApiEntry(normalized);
530
- } finally {
531
- releaseLock();
532
- }
130
+ // Total stored memories (optionally scoped by `options.agents`) paired with `recent` so
131
+ // recall can report "showing N of TOTAL".
132
+ function count(projectRoot, options = {}) {
133
+ return resolveStore(projectRoot, options).countMemories({ agents: options.agents });
533
134
  }
534
135
 
535
- function searchableText(entry) {
536
- return [
537
- entry.key,
538
- JSON.stringify(entry.value),
539
- entry['source-agent'],
540
- entry.scope,
541
- String(entry.confidence ?? ''),
542
- ...(Array.isArray(entry.tags) ? entry.tags : []),
543
- ...(Array.isArray(entry.supersedes) ? entry.supersedes : []),
544
- ...(Array.isArray(entry['beads-refs']) ? entry['beads-refs'] : []),
545
- ].join('\n').toLowerCase();
136
+ // BM25 top-N recall over the FTS5 index (token-AND). Unlike `search` (the legacy LIKE
137
+ // helper) this does not short-circuit an empty query — the driver falls back to recent so
138
+ // recall stays capped either way.
139
+ function searchRanked(projectRoot, query, limit, options = {}) {
140
+ return resolveStore(projectRoot, options).searchMemoriesRanked(query, limit);
546
141
  }
547
142
 
548
- function search(projectRoot, query, options = {}) {
549
- if (typeof query !== 'string' || query.trim() === '') {
550
- return [];
143
+ // Close and forget every cached default store. The CLI process is short-lived (the OS
144
+ // closes the handle on exit), so this is mainly a lifecycle helper for long-lived hosts and
145
+ // tests — it releases the SQLite/WAL handle before a temp dir is removed.
146
+ function closeAll() {
147
+ for (const store of storeCache.values()) {
148
+ try {
149
+ if (store && typeof store.close === 'function') store.close();
150
+ } catch {
151
+ // best-effort close
152
+ }
551
153
  }
552
-
553
- const needle = query.trim().toLowerCase();
554
- return readEntries(projectRoot, options)
555
- .filter((entry) => searchableText(entry).includes(needle))
556
- .map(toApiEntry);
154
+ storeCache.clear();
557
155
  }
558
156
 
559
157
  module.exports = {
@@ -561,4 +159,8 @@ module.exports = {
561
159
  write,
562
160
  search,
563
161
  list,
162
+ recent,
163
+ count,
164
+ searchRanked,
165
+ closeAll,
564
166
  };