forge-workflow 0.0.9 → 0.1.0-beta.2

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