forge-workflow 0.0.10 → 0.1.0-beta.3

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