forge-workflow 0.0.10 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +461 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +17 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +0 -1
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +838 -972
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +382 -11
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,863 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { fenceUntrusted } = require('./untrusted-content');
6
+
7
+ const DEFAULT_BUDGET_TOKENS = 2000;
8
+ const MIN_BUDGET_TOKENS = 40;
9
+ const APPROX_CHARS_PER_TOKEN = 4;
10
+
11
+ function estimateTokens(value) {
12
+ return Math.ceil(String(value || '').length / APPROX_CHARS_PER_TOKEN);
13
+ }
14
+
15
+ function normalizeBudgetTokens(value) {
16
+ const parsed = Number(value);
17
+ if (!Number.isFinite(parsed)) return DEFAULT_BUDGET_TOKENS;
18
+ return Math.max(MIN_BUDGET_TOKENS, Math.floor(parsed));
19
+ }
20
+
21
+ function toPosix(relativePath) {
22
+ return relativePath.replaceAll(path.sep, '/');
23
+ }
24
+
25
+ function relativePath(projectRoot, filePath) {
26
+ return toPosix(path.relative(projectRoot, filePath));
27
+ }
28
+
29
+ function readText(projectRoot, relativeFilePath) {
30
+ const filePath = path.join(projectRoot, relativeFilePath);
31
+ if (!fs.existsSync(filePath)) return null;
32
+ return fs.readFileSync(filePath, 'utf8');
33
+ }
34
+
35
+ function readJsonFile(projectRoot, relativeFilePath) {
36
+ const text = readText(projectRoot, relativeFilePath);
37
+ if (!text) return null;
38
+ try {
39
+ return JSON.parse(text);
40
+ } catch (_error) {
41
+ // Malformed JSON in a source file is treated as absent rather than fatal.
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function readJsonl(projectRoot, relativeFilePath) {
47
+ const text = readText(projectRoot, relativeFilePath);
48
+ if (!text) return [];
49
+ return text
50
+ .split(/\r?\n/)
51
+ .map(line => line.trim())
52
+ .filter(Boolean)
53
+ .map((line, index) => {
54
+ try {
55
+ return JSON.parse(line);
56
+ } catch (error) {
57
+ return {
58
+ _parseError: true,
59
+ line: index + 1,
60
+ error: error.message,
61
+ };
62
+ }
63
+ });
64
+ }
65
+
66
+ function firstLine(value) {
67
+ return String(value || '')
68
+ .split(/\r?\n/)
69
+ .map(line => line.trim())
70
+ .find(Boolean) || '';
71
+ }
72
+
73
+ function getRepositoryUrl(packageJson) {
74
+ const repository = packageJson?.repository;
75
+ if (typeof repository === 'string') return repository;
76
+ if (repository && typeof repository.url === 'string') return repository.url;
77
+ return null;
78
+ }
79
+
80
+ function buildProjectIdentity(projectRoot) {
81
+ const packageJson = readJsonFile(projectRoot, 'package.json') || {};
82
+ const designTitle = firstLine(readText(projectRoot, 'docs/PROJECT_DESIGN.md'));
83
+ const projectName = packageJson.name || path.basename(projectRoot);
84
+
85
+ return {
86
+ name: projectName,
87
+ version: packageJson.version || null,
88
+ repository: getRepositoryUrl(packageJson),
89
+ root: projectRoot,
90
+ design_title: designTitle.replace(/^#\s*/, '') || null,
91
+ };
92
+ }
93
+
94
+ function source(pathValue, kind, authority, role) {
95
+ return {
96
+ path: pathValue,
97
+ source_kind: kind,
98
+ authority,
99
+ role,
100
+ };
101
+ }
102
+
103
+ function buildSection({
104
+ id,
105
+ title,
106
+ content,
107
+ sources = [],
108
+ priority = 100,
109
+ preserve = false,
110
+ data = null,
111
+ untrustedSource = null,
112
+ }) {
113
+ return {
114
+ id,
115
+ title,
116
+ content: String(content || ''),
117
+ sources,
118
+ priority,
119
+ preserve,
120
+ truncated: false,
121
+ estimated_tokens: estimateTokens(content),
122
+ ...(data ? { data } : {}),
123
+ // Carries a provenance label for sections whose content is untrusted external
124
+ // data (e.g. remembered notes). The assembler fences these AFTER applyBudget.
125
+ ...(untrustedSource ? { untrustedSource } : {}),
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Provenance-fence any section carrying an `untrustedSource`, IN PLACE, AFTER applyBudget
131
+ * has truncated it — so the ⟦END UNTRUSTED⟧ close marker always survives the budget cut
132
+ * (fencing before truncation would let the budget sever the terminator). A planted
133
+ * memory note or issue title is DATA, not instructions, once it reaches agent context.
134
+ *
135
+ * @param {object[]} sections
136
+ * @returns {object[]} the same array, mutated.
137
+ */
138
+ function fenceUntrustedSections(sections) {
139
+ for (const section of sections) {
140
+ if (section.untrustedSource && section.content) {
141
+ section.content = fenceUntrusted(section.content, { source: section.untrustedSource });
142
+ section.estimated_tokens = estimateTokens(section.content);
143
+ }
144
+ }
145
+ return sections;
146
+ }
147
+
148
+ function extractCurrentDesignSnapshot(projectDesignText) {
149
+ if (!projectDesignText) return '';
150
+
151
+ const snapshotStart = projectDesignText.indexOf('## Current design snapshot');
152
+ if (snapshotStart === -1) return '';
153
+
154
+ const registryStart = projectDesignText.indexOf('## Decision registry', snapshotStart);
155
+ const snapshot = registryStart === -1
156
+ ? projectDesignText.slice(snapshotStart)
157
+ : projectDesignText.slice(snapshotStart, registryStart);
158
+
159
+ return snapshot.trim();
160
+ }
161
+
162
+ function parseYamlBlockValue(block, key) {
163
+ const line = block
164
+ .split(/\r?\n/)
165
+ .find(candidate => candidate.trim().startsWith(`${key}:`));
166
+ if (!line) return null;
167
+ return line.slice(line.indexOf(':') + 1).trim().replace(/^['"]|['"]$/g, '') || null;
168
+ }
169
+
170
+ function extractDecisionEntries(projectDesignText) {
171
+ if (!projectDesignText) return [];
172
+
173
+ const entries = [];
174
+ const headingRegex = /^###\s+(PD-[^\r\n]+)/gm;
175
+ const matches = [...projectDesignText.matchAll(headingRegex)];
176
+
177
+ for (let index = 0; index < matches.length; index += 1) {
178
+ const match = matches[index];
179
+ const start = match.index;
180
+ const end = matches[index + 1]?.index ?? projectDesignText.length;
181
+ const block = projectDesignText.slice(start, end);
182
+ const yaml = extractFencedYaml(block);
183
+ const decisionText = extractCurrentDecision(block);
184
+ entries.push({
185
+ id: parseYamlBlockValue(yaml, 'id') || match[1].trim(),
186
+ topic: parseYamlBlockValue(yaml, 'topic'),
187
+ status: parseYamlBlockValue(yaml, 'status'),
188
+ decision: decisionText,
189
+ });
190
+ }
191
+
192
+ return entries;
193
+ }
194
+
195
+ function buildProjectDesignSections(projectRoot) {
196
+ const projectDesignPath = 'docs/PROJECT_DESIGN.md';
197
+ const projectDesignText = readText(projectRoot, projectDesignPath);
198
+ if (!projectDesignText) return [];
199
+
200
+ const projectDesignSource = source(projectDesignPath, 'project_doc', 'project_registry', 'project_design');
201
+ const snapshot = extractCurrentDesignSnapshot(projectDesignText);
202
+ const decisions = extractDecisionEntries(projectDesignText);
203
+ const decisionContent = decisions
204
+ .map(decision => [
205
+ `- ${decision.id}`,
206
+ decision.topic ? ` topic: ${decision.topic}` : null,
207
+ decision.status ? ` status: ${decision.status}` : null,
208
+ decision.decision ? ` decision: ${decision.decision}` : null,
209
+ ].filter(Boolean).join('\n'))
210
+ .join('\n');
211
+
212
+ return [
213
+ buildSection({
214
+ id: 'current_design_snapshot',
215
+ title: 'Current Design Snapshot',
216
+ content: snapshot,
217
+ sources: [projectDesignSource],
218
+ priority: 70,
219
+ preserve: false,
220
+ }),
221
+ buildSection({
222
+ id: 'headline_decisions',
223
+ title: 'Headline Decisions',
224
+ content: decisionContent,
225
+ sources: [projectDesignSource],
226
+ priority: 10,
227
+ preserve: true,
228
+ data: { decisions },
229
+ }),
230
+ ];
231
+ }
232
+
233
+ function listWorkFolders(projectRoot) {
234
+ const docsWork = path.join(projectRoot, 'docs', 'work');
235
+ if (!fs.existsSync(docsWork)) return [];
236
+
237
+ return fs.readdirSync(docsWork, { withFileTypes: true })
238
+ .filter(entry => entry.isDirectory())
239
+ .map(entry => path.join(docsWork, entry.name))
240
+ .filter(folder => ['plan.md', 'design.md', 'tasks.md', 'decisions.md'].some(file => fs.existsSync(path.join(folder, file))))
241
+ .sort((a, b) => {
242
+ const aScore = ['plan.md', 'tasks.md', 'decisions.md'].filter(file => fs.existsSync(path.join(a, file))).length;
243
+ const bScore = ['plan.md', 'tasks.md', 'decisions.md'].filter(file => fs.existsSync(path.join(b, file))).length;
244
+ if (aScore !== bScore) return bScore - aScore;
245
+ return relativePath(projectRoot, b).localeCompare(relativePath(projectRoot, a));
246
+ });
247
+ }
248
+
249
+ // Locate a work-folder by its machine-readable `.forge-issue` marker (dropped by
250
+ // `forge worktree create --work-folder`). Deterministic folder → issue resolution that,
251
+ // unlike the "most-complete folder" heuristic, does not break under parallel features.
252
+ function findWorkFolderByIssue(projectRoot, issueId) {
253
+ for (const folder of listWorkFolders(projectRoot)) {
254
+ const markerPath = path.join(folder, '.forge-issue');
255
+ try {
256
+ if (fs.existsSync(markerPath) && fs.readFileSync(markerPath, 'utf8').trim() === issueId) {
257
+ return folder;
258
+ }
259
+ } catch { /* unreadable marker — skip */ }
260
+ }
261
+ return null;
262
+ }
263
+
264
+ // Read the current worktree's linkage row from the Kernel. Prefers an injected driver
265
+ // (tests / callers already holding one, never closed here); otherwise opens the Kernel
266
+ // DB read-only and closes it. Never throws: a missing/locked Kernel, an un-migrated DB,
267
+ // or a non-git project all return null so the caller falls back to the heuristic.
268
+ function readWorktreeLinkageRow(projectRoot, worktreePath, options) {
269
+ if (options._kernelDriver) {
270
+ try { return options._kernelDriver.getWorktreeLinkage({ path: worktreePath }) || null; }
271
+ catch { return null; }
272
+ }
273
+ let driver;
274
+ try {
275
+ if (!fs.existsSync(path.join(projectRoot, '.git'))) return null; // no repo → no Kernel
276
+ const { resolveKernelDatabasePath, buildKernelIssueDeps } = require('./kernel/cli-broker-factory');
277
+ const databasePath = resolveKernelDatabasePath({ projectRoot });
278
+ if (!databasePath || !fs.existsSync(databasePath)) return null; // no Kernel DB → heuristic
279
+ driver = buildKernelIssueDeps({ projectRoot, databasePath }).kernelDriver;
280
+ return driver.getWorktreeLinkage({ path: worktreePath }) || null;
281
+ } catch {
282
+ return null;
283
+ } finally {
284
+ if (driver && typeof driver.close === 'function') {
285
+ try { driver.close(); } catch { /* ignore */ }
286
+ }
287
+ }
288
+ }
289
+
290
+ // Resolve the work-folder the Kernel linked to the current worktree (row.work_folder,
291
+ // else row.issue_id → `.forge-issue` marker), or null when there is no usable linkage.
292
+ function resolveKernelLinkedWorkFolder(projectRoot, options = {}) {
293
+ const worktreePath = path.resolve(options.worktreePath || projectRoot);
294
+ const row = readWorktreeLinkageRow(projectRoot, worktreePath, options);
295
+ if (!row) return null;
296
+
297
+ if (row.work_folder) {
298
+ const resolved = path.resolve(projectRoot, row.work_folder);
299
+ if (fs.existsSync(resolved)) return resolved;
300
+ }
301
+ if (row.issue_id) {
302
+ const byMarker = findWorkFolderByIssue(projectRoot, row.issue_id);
303
+ if (byMarker) return byMarker;
304
+ }
305
+ return null;
306
+ }
307
+
308
+ function discoverWorkFolder(projectRoot, options = {}) {
309
+ if (options.workFolder) {
310
+ const explicit = path.resolve(projectRoot, options.workFolder);
311
+ if (fs.existsSync(explicit)) return explicit;
312
+ }
313
+ // Kernel linkage (issue → worktree → work-folder) is the backbone: when the current
314
+ // worktree has a recorded row, it wins over the filesystem heuristic. Absent a row,
315
+ // fall back to the most-complete-folder guess.
316
+ const linked = resolveKernelLinkedWorkFolder(projectRoot, options);
317
+ if (linked) return linked;
318
+ return listWorkFolders(projectRoot)[0] || null;
319
+ }
320
+
321
+ function buildWorkArtifactSection(projectRoot, workFolder, artifact) {
322
+ const filePath = path.join(workFolder, artifact.file);
323
+ if (!fs.existsSync(filePath)) return null;
324
+
325
+ const relative = relativePath(projectRoot, filePath);
326
+ const artifactSource = source(relative, artifact.sourceKind, 'work_artifact', artifact.role);
327
+ return buildSection({
328
+ id: artifact.id,
329
+ title: artifact.title,
330
+ content: fs.readFileSync(filePath, 'utf8').trim(),
331
+ sources: [artifactSource],
332
+ priority: artifact.priority,
333
+ preserve: artifact.preserve,
334
+ });
335
+ }
336
+
337
+ function buildWorkSections(projectRoot, options = {}) {
338
+ const workFolder = discoverWorkFolder(projectRoot, options);
339
+ if (!workFolder) return [];
340
+
341
+ const artifacts = [
342
+ {
343
+ file: 'decisions.md',
344
+ id: 'active_work_decisions',
345
+ title: 'Active Work Decisions',
346
+ sourceKind: 'decision_log',
347
+ role: 'active_work_decisions',
348
+ priority: 20,
349
+ preserve: true,
350
+ },
351
+ {
352
+ file: 'plan.md',
353
+ id: 'active_work_plan',
354
+ title: 'Active Work Plan',
355
+ sourceKind: 'work_plan',
356
+ role: 'active_work_plan',
357
+ priority: 30,
358
+ preserve: false,
359
+ },
360
+ {
361
+ file: 'tasks.md',
362
+ id: 'active_work_tasks',
363
+ title: 'Active Work Tasks',
364
+ sourceKind: 'task_list',
365
+ role: 'active_work_tasks',
366
+ priority: 40,
367
+ preserve: false,
368
+ },
369
+ {
370
+ file: 'design.md',
371
+ id: 'active_work_legacy_design',
372
+ title: 'Active Work Legacy Design',
373
+ sourceKind: 'legacy_work_design',
374
+ role: 'active_work_legacy_design',
375
+ priority: 80,
376
+ preserve: false,
377
+ },
378
+ ];
379
+
380
+ return artifacts
381
+ .map(artifact => buildWorkArtifactSection(projectRoot, workFolder, artifact))
382
+ .filter(Boolean)
383
+ .map(section => ({
384
+ ...section,
385
+ work_folder: relativePath(projectRoot, workFolder),
386
+ }));
387
+ }
388
+
389
+ function placeholderSection(id, title, content, role, preserve = false, priority = 90) {
390
+ return buildSection({
391
+ id,
392
+ title,
393
+ content,
394
+ sources: [source(`kernel.${id}`, 'kernel_placeholder', 'future_kernel_read_model', role)],
395
+ priority,
396
+ preserve,
397
+ });
398
+ }
399
+
400
+ // Cap on remembered notes surfaced in the orientation MEMORY section — a bounded
401
+ // nudge (applyBudget caps it further), not a full memory dump.
402
+ const MEMORY_SECTION_NOTE_LIMIT = 5;
403
+
404
+ /**
405
+ * Best-effort MEMORY section: the newest remembered notes from the kernel-backed memory
406
+ * store (the read half of `forge remember`/`recall` that was previously ORPHANED — an
407
+ * agent only saw memory if it typed `forge recall`). Recent DECISIONS are already
408
+ * surfaced by `headline_decisions` (from PROJECT_DESIGN.md), so this adds the missing
409
+ * remembered-notes half without duplicating them. Never throws and never blocks orient:
410
+ * a missing/locked kernel, or no notes, yields [] (the section is simply absent).
411
+ *
412
+ * @param {string} projectRoot
413
+ * @param {object} [options] - forwarded to the memory read path (e.g. an injected `store`).
414
+ * @returns {object[]} zero or one section.
415
+ */
416
+ function buildMemorySection(projectRoot, options = {}) {
417
+ // Own the store's lifecycle: when we open a kernel driver we MUST close it, so orient
418
+ // never leaves a lingering DB handle (which strands Windows temp cleanup and is a
419
+ // resource leak). An injected store (tests) is used as-is and never closed by us.
420
+ const injected = options.store;
421
+ const store = injected || openMemoryStore(projectRoot);
422
+ if (!store) return [];
423
+ try {
424
+ // Lazy require: keep the default orient path free of the memory stack until needed.
425
+ const memoryRouter = require('./memory/router');
426
+ const result = memoryRouter.recall(projectRoot, { limit: MEMORY_SECTION_NOTE_LIMIT }, { store });
427
+ const notes = Array.isArray(result && result.notes) ? result.notes : [];
428
+ if (notes.length === 0) return [];
429
+ return [buildSection({
430
+ id: 'remembered_notes',
431
+ title: 'Remembered Notes',
432
+ content: notes.map(formatMemoryNote).join('\n'),
433
+ sources: [source('kernel.remembered_notes', 'kernel_memory', 'project_memory', 'remembered_notes')],
434
+ priority: 45,
435
+ preserve: false,
436
+ // Remembered notes are untrusted DATA (a planted note must not read as directives).
437
+ // Raw here; the assembler fences it AFTER applyBudget truncation.
438
+ untrustedSource: 'memory',
439
+ })];
440
+ } catch {
441
+ return [];
442
+ } finally {
443
+ if (!injected && store && typeof store.close === 'function') {
444
+ try { store.close(); } catch { /* ignore */ }
445
+ }
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Open a fresh, closeable kernel driver for a read of remembered notes, or null when
451
+ * there is no reachable kernel DB (no repo / no DB file / open failure). Mirrors
452
+ * readWorktreeLinkageRow: never creates the DB, never throws.
453
+ *
454
+ * @param {string} projectRoot
455
+ * @returns {object|null}
456
+ */
457
+ function openMemoryStore(projectRoot) {
458
+ try {
459
+ if (!fs.existsSync(path.join(projectRoot, '.git'))) return null;
460
+ const { resolveKernelDatabasePath } = require('./kernel/cli-broker-factory');
461
+ const databasePath = resolveKernelDatabasePath({ projectRoot });
462
+ if (!databasePath || !fs.existsSync(databasePath)) return null;
463
+ const { createBuiltinSQLiteDriver } = require('./kernel/sqlite-driver');
464
+ return createBuiltinSQLiteDriver({ databasePath });
465
+ } catch {
466
+ return null;
467
+ }
468
+ }
469
+
470
+ /** Render one recall note as a compact `- [date] text` line. */
471
+ function formatMemoryNote(note) {
472
+ const date = typeof note.timestamp === 'string' && note.timestamp ? note.timestamp.slice(0, 10) : '';
473
+ const prefix = date ? `${date} ` : '';
474
+ return `- ${prefix}${note.note}`;
475
+ }
476
+
477
+ function buildQueueSections() {
478
+ return [
479
+ placeholderSection(
480
+ 'ready_queue',
481
+ 'Ready Queue',
482
+ 'Run `forge ready` to list work that is ready to start (issues with no open blockers).',
483
+ 'ready_queue',
484
+ false,
485
+ 50
486
+ ),
487
+ placeholderSection(
488
+ 'active_claims',
489
+ 'Active Claims',
490
+ 'Run `forge issue list --status in_progress` to see work that is currently claimed.',
491
+ 'active_claims',
492
+ true,
493
+ 60
494
+ ),
495
+ ];
496
+ }
497
+
498
+ function truncateText(content, targetTokens) {
499
+ if (estimateTokens(content) <= targetTokens) {
500
+ return { content, truncated: false };
501
+ }
502
+ const suffix = '\n[truncated deterministically by token budget]';
503
+ // If even the truncation marker cannot fit in the allowance, drop the section
504
+ // entirely rather than emit a section that exceeds the requested cap.
505
+ if (estimateTokens(suffix) > targetTokens) {
506
+ return { content: '', truncated: true };
507
+ }
508
+ const targetChars = Math.max(0, targetTokens * APPROX_CHARS_PER_TOKEN);
509
+ let nextContent = String(content).slice(0, Math.max(0, targetChars - suffix.length)).trimEnd();
510
+ let result = `${nextContent}${suffix}`;
511
+ // Guard against ceil() rounding pushing the marker-inclusive result over the cap.
512
+ while (nextContent.length > 0 && estimateTokens(result) > targetTokens) {
513
+ nextContent = nextContent.slice(0, -1).trimEnd();
514
+ result = `${nextContent}${suffix}`;
515
+ }
516
+ return { content: result, truncated: true };
517
+ }
518
+
519
+ function applyBudget(sections, budgetTokens) {
520
+ const budget = normalizeBudgetTokens(budgetTokens);
521
+ const sectionCopies = sections.map(section => ({ ...section }));
522
+ const preserved = sectionCopies.filter(section => section.preserve);
523
+ const flexible = sectionCopies.filter(section => !section.preserve)
524
+ .sort((a, b) => a.priority - b.priority || a.id.localeCompare(b.id));
525
+
526
+ let used = preserved.reduce((sum, section) => sum + estimateTokens(section.content), 0);
527
+ let remaining = Math.max(0, budget - used);
528
+ let truncated = false;
529
+
530
+ for (const section of flexible) {
531
+ const fullTokens = estimateTokens(section.content);
532
+ const allowance = Math.min(fullTokens, remaining);
533
+ const next = truncateText(section.content, allowance);
534
+ section.content = next.content;
535
+ section.truncated = next.truncated;
536
+ section.estimated_tokens = estimateTokens(section.content);
537
+ used += section.estimated_tokens;
538
+ remaining = Math.max(0, budget - used);
539
+ if (next.truncated) truncated = true;
540
+ }
541
+
542
+ if (used > budget) {
543
+ const preservedByPriority = preserved
544
+ .sort((a, b) => b.priority - a.priority || b.id.localeCompare(a.id));
545
+ for (const section of preservedByPriority) {
546
+ if (used <= budget) break;
547
+ const targetTokens = Math.max(0, estimateTokens(section.content) - (used - budget));
548
+ const next = truncateText(section.content, targetTokens);
549
+ used -= estimateTokens(section.content);
550
+ section.content = next.content;
551
+ section.truncated = next.truncated;
552
+ section.estimated_tokens = estimateTokens(section.content);
553
+ used += section.estimated_tokens;
554
+ if (next.truncated) truncated = true;
555
+ }
556
+ }
557
+
558
+ const ordered = sectionCopies.sort((a, b) => a.priority - b.priority || a.id.localeCompare(b.id));
559
+ for (const section of ordered) {
560
+ section.estimated_tokens = estimateTokens(section.content);
561
+ }
562
+
563
+ return {
564
+ sections: ordered,
565
+ token_budget: {
566
+ requested: budget,
567
+ used: Math.min(budget, ordered.reduce((sum, section) => sum + section.estimated_tokens, 0)),
568
+ approximate: true,
569
+ chars_per_token: APPROX_CHARS_PER_TOKEN,
570
+ truncated: truncated || ordered.some(section => section.truncated),
571
+ truncation_order: [
572
+ 'active_work_plan',
573
+ 'active_work_tasks',
574
+ 'active_work_legacy_design',
575
+ 'current_design_snapshot',
576
+ 'ready_queue',
577
+ 'headline_decisions',
578
+ 'active_work_decisions',
579
+ 'active_claims',
580
+ 'key_commands',
581
+ ],
582
+ },
583
+ };
584
+ }
585
+
586
+ function collectSources(sections) {
587
+ const byKey = new Map();
588
+ for (const section of sections) {
589
+ for (const sectionSource of section.sources || []) {
590
+ const key = `${sectionSource.path}|${sectionSource.source_kind}|${sectionSource.role}`;
591
+ if (!byKey.has(key)) byKey.set(key, sectionSource);
592
+ }
593
+ }
594
+ return [...byKey.values()].sort((a, b) => a.path.localeCompare(b.path) || a.role.localeCompare(b.role));
595
+ }
596
+
597
+ function cleanupSections(sections) {
598
+ return sections.map(section => {
599
+ const rest = { ...section };
600
+ delete rest.priority;
601
+ delete rest.preserve;
602
+ delete rest.untrustedSource; // internal fencing marker — not part of the emitted shape
603
+ return rest;
604
+ });
605
+ }
606
+
607
+ function buildOrientationSections(projectRoot, options = {}) {
608
+ const project = buildProjectIdentity(projectRoot);
609
+ const sections = [
610
+ buildSection({
611
+ id: 'project_identity',
612
+ title: 'Project Identity',
613
+ content: [
614
+ `name: ${project.name}`,
615
+ project.version ? `version: ${project.version}` : null,
616
+ project.repository ? `repository: ${project.repository}` : null,
617
+ project.design_title ? `design: ${project.design_title}` : null,
618
+ ].filter(Boolean).join('\n'),
619
+ sources: [
620
+ source('package.json', 'project_manifest', 'project_file', 'project_identity'),
621
+ source('docs/PROJECT_DESIGN.md', 'project_doc', 'project_registry', 'project_identity'),
622
+ ],
623
+ priority: 0,
624
+ preserve: true,
625
+ data: project,
626
+ }),
627
+ ...buildProjectDesignSections(projectRoot),
628
+ ...buildMemorySection(projectRoot, options),
629
+ ...buildWorkSections(projectRoot, options),
630
+ ...buildQueueSections(),
631
+ ];
632
+ return { project, sections };
633
+ }
634
+
635
+ function assembleOrientationResult(project, sections, options) {
636
+ const budgeted = applyBudget(sections, options.budgetTokens ?? options.budget);
637
+ fenceUntrustedSections(budgeted.sections); // provenance-fence untrusted sections post-budget
638
+
639
+ return {
640
+ schema_version: 1,
641
+ kind: 'orientation',
642
+ generated_at: new Date().toISOString(),
643
+ assembly: 'deterministic-file-assembly-v1',
644
+ project,
645
+ token_budget: budgeted.token_budget,
646
+ sections: cleanupSections(budgeted.sections),
647
+ sources: collectSources(budgeted.sections),
648
+ next_commands: [
649
+ 'forge status --json',
650
+ 'forge issue ready --json',
651
+ 'forge recap <issue> --json',
652
+ 'forge show <issue> --json',
653
+ ],
654
+ };
655
+ }
656
+
657
+ function buildOrientation(projectRoot, options = {}) {
658
+ const { project, sections } = buildOrientationSections(projectRoot, options);
659
+ return assembleOrientationResult(project, sections, options);
660
+ }
661
+
662
+ // Short, fixed reference of capabilities that are easy to miss at session entry:
663
+ // closing the remember/recall memory loop, and commands with 0 mentions elsewhere
664
+ // in the generated onboarding surface (merge, insights, upgrade, gate, role).
665
+ // Kept intentionally tiny (a handful of one-liners) and `preserve: true` with a
666
+ // low priority number so it survives prime's token budget even on data-heavy
667
+ // projects — this is a fixed reference, not a manual, so it must not grow.
668
+ const PRIME_KEY_COMMANDS_CONTENT = [
669
+ 'forge remember <note> / forge recall [query] — write a memory note, then read it back',
670
+ 'forge insights — detect recurring evidence patterns, suggest workflow follow-ups',
671
+ 'forge upgrade [--dry-run] — preview/self-heal safe Forge upgrade readiness',
672
+ 'forge gate <verb> <gate-id> — toggle a workflow gate, or approve/reject a human gate',
673
+ 'forge role <role> --use <skill> — bind a role to a skill/ideology',
674
+ 'forge merge --auto <pr> — opt-in conditional auto-merge (off by default)',
675
+ ].map(line => `- ${line}`).join('\n');
676
+
677
+ function buildPrimeKeyCommandsSection() {
678
+ return buildSection({
679
+ id: 'key_commands',
680
+ title: 'Key Commands',
681
+ content: PRIME_KEY_COMMANDS_CONTENT,
682
+ sources: [source('AGENTS.md', 'static_reference', 'cli_help_summary', 'key_commands')],
683
+ priority: 2,
684
+ preserve: true,
685
+ });
686
+ }
687
+
688
+ function findIssue(projectRoot, issueId) {
689
+ const issues = readJsonl(projectRoot, '.beads/issues.jsonl')
690
+ .filter(row => row && !row._parseError && row._type === 'issue');
691
+ return issues.find(issue => issue.id === issueId) || null;
692
+ }
693
+
694
+ function buildIssueSection(projectRoot, issueId) {
695
+ const issue = findIssue(projectRoot, issueId);
696
+ const content = issue
697
+ ? [
698
+ `id: ${issue.id}`,
699
+ issue.title ? `title: ${issue.title}` : null,
700
+ issue.status ? `status: ${issue.status}` : null,
701
+ issue.description ? `description: ${issue.description}` : null,
702
+ ].filter(Boolean).join('\n')
703
+ : `id: ${issueId}\nstatus: unknown\nIssue not found in compatibility projection.`;
704
+
705
+ return {
706
+ issue: issue ? {
707
+ id: issue.id,
708
+ title: issue.title || null,
709
+ status: issue.status || null,
710
+ } : {
711
+ id: issueId,
712
+ title: null,
713
+ status: 'unknown',
714
+ },
715
+ section: buildSection({
716
+ id: 'issue_summary',
717
+ title: 'Issue Summary',
718
+ content,
719
+ sources: [source('.beads/issues.jsonl', 'beads_compat', 'compatibility_projection', 'issue_summary')],
720
+ priority: 5,
721
+ preserve: true,
722
+ }),
723
+ };
724
+ }
725
+
726
+ function buildIssueRecap(projectRoot, issueId, options = {}) {
727
+ if (!issueId || typeof issueId !== 'string') {
728
+ throw new Error('Issue id is required for issue-scoped recap.');
729
+ }
730
+
731
+ const issueResult = buildIssueSection(projectRoot, issueId);
732
+ const sections = [
733
+ issueResult.section,
734
+ ...buildProjectDesignSections(projectRoot),
735
+ ...buildWorkSections(projectRoot, options),
736
+ ...buildQueueSections(),
737
+ ];
738
+ const budgeted = applyBudget(sections, options.budgetTokens ?? options.budget);
739
+ fenceUntrustedSections(budgeted.sections); // provenance-fence untrusted sections post-budget
740
+
741
+ return {
742
+ schema_version: 1,
743
+ kind: 'issue_recap',
744
+ generated_at: new Date().toISOString(),
745
+ assembly: 'deterministic-file-assembly-v1',
746
+ scope: {
747
+ issue_id: issueId,
748
+ },
749
+ issue: issueResult.issue,
750
+ token_budget: budgeted.token_budget,
751
+ sections: cleanupSections(budgeted.sections),
752
+ sources: collectSources(budgeted.sections),
753
+ next_commands: [
754
+ `forge show ${issueId} --json`,
755
+ `forge comment ${issueId} "<handoff note>"`,
756
+ 'forge orient --json',
757
+ 'forge status --json',
758
+ ],
759
+ };
760
+ }
761
+
762
+ function buildPrime(projectRoot, options = {}) {
763
+ const { project, sections } = buildOrientationSections(projectRoot, options);
764
+ const orientation = assembleOrientationResult(
765
+ project,
766
+ [...sections, buildPrimeKeyCommandsSection()],
767
+ options
768
+ );
769
+ return {
770
+ schema_version: 1,
771
+ kind: 'prime',
772
+ generated_at: orientation.generated_at,
773
+ purpose: 'session-entry',
774
+ token_budget: orientation.token_budget,
775
+ orientation,
776
+ sources: orientation.sources,
777
+ next_commands: [
778
+ 'forge orient --json',
779
+ 'forge status --json',
780
+ 'forge issue ready --json',
781
+ ],
782
+ };
783
+ }
784
+
785
+ function formatOrientationText(result) {
786
+ const lines = [
787
+ orientationTitle(result.kind),
788
+ `Assembly: ${result.assembly || result.orientation?.assembly || 'deterministic-file-assembly-v1'}`,
789
+ `Budget: ${result.token_budget.used}/${result.token_budget.requested} estimated tokens${result.token_budget.truncated ? ' (truncated)' : ''}`,
790
+ '',
791
+ ];
792
+
793
+ const sections = result.orientation?.sections || result.sections || [];
794
+ for (const section of sections) {
795
+ if (!section.content) continue;
796
+ lines.push(`## ${section.title}`, section.content, '');
797
+ }
798
+
799
+ lines.push('Next commands:');
800
+ for (const command of result.next_commands || []) {
801
+ lines.push(`- ${command}`);
802
+ }
803
+ return `${lines.join('\n')}\n`;
804
+ }
805
+
806
+ function orientationTitle(kind) {
807
+ if (kind === 'prime') return 'Forge prime';
808
+ if (kind === 'issue_recap') return 'Forge issue recap';
809
+ return 'Forge orientation';
810
+ }
811
+
812
+ function extractFencedYaml(block) {
813
+ const open = block.indexOf('```yaml');
814
+ if (open === -1) return '';
815
+ const start = open + '```yaml'.length;
816
+ const close = block.indexOf('```', start);
817
+ return close === -1 ? '' : block.slice(start, close).trim();
818
+ }
819
+
820
+ function extractCurrentDecision(block) {
821
+ const marker = '**Current decision:**';
822
+ const at = block.indexOf(marker);
823
+ if (at === -1) return '';
824
+ const after = block.slice(at + marker.length);
825
+ const blank = after.indexOf('\n\n');
826
+ return (blank === -1 ? after : after.slice(0, blank)).trim();
827
+ }
828
+
829
+ function readOption(args, name, fallback) {
830
+ const equals = args.find(arg => arg.startsWith(`${name}=`));
831
+ if (equals) return equals.slice(name.length + 1);
832
+ const index = args.indexOf(name);
833
+ if (index >= 0 && args[index + 1] && !args[index + 1].startsWith('-')) return args[index + 1];
834
+ return fallback;
835
+ }
836
+
837
+ function runOrientationCommand(build, args, projectRoot) {
838
+ const result = build(projectRoot, {
839
+ budgetTokens: readOption(args, '--budget', undefined),
840
+ });
841
+ return {
842
+ success: true,
843
+ output: args.includes('--json') ? `${JSON.stringify(result, null, 2)}\n` : formatOrientationText(result),
844
+ };
845
+ }
846
+
847
+ module.exports = {
848
+ DEFAULT_BUDGET_TOKENS,
849
+ applyBudget,
850
+ buildSection,
851
+ buildIssueRecap,
852
+ buildMemorySection,
853
+ buildOrientation,
854
+ buildOrientationSections,
855
+ buildPrime,
856
+ discoverWorkFolder,
857
+ estimateTokens,
858
+ formatOrientationText,
859
+ normalizeBudgetTokens,
860
+ readOption,
861
+ runOrientationCommand,
862
+ source,
863
+ };