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,282 @@
1
+ const { execFileSync } = require('node:child_process');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+
5
+ const VERDICT_LABELS = {
6
+ PASS: 'good',
7
+ FAIL: 'bad',
8
+ };
9
+
10
+ const REVIEWER_ROLES = new Set(['spec_reviewer', 'quality_reviewer']);
11
+ const SECRET_KEY_PATTERN = /(api(?:[_-]|\s)?key|authorization|credential|password|private(?:[_-]|\s)?key|secret|token)/i;
12
+ const SECRET_TEXT_KEY_PATTERN = String.raw`(?:api(?:[_-]|\s)?key|authorization|credential|password|private(?:[_-]|\s)?key|secret|token)`;
13
+ const SECRET_TEXT_PATTERNS = [
14
+ {
15
+ pattern: /\bBearer\s+\S+/gi,
16
+ replace: () => 'Bearer [REDACTED]',
17
+ },
18
+ {
19
+ pattern: new RegExp(`(\\b${SECRET_TEXT_KEY_PATTERN}\\b\\s*=\\s*)([^&,\r\n]+)`, 'gi'),
20
+ replace: (_match, prefix) => `${prefix}[REDACTED]`,
21
+ },
22
+ {
23
+ pattern: new RegExp(`(\\b${SECRET_TEXT_KEY_PATTERN}\\b\\s+is\\s+)([^&,\r\n]+)`, 'gi'),
24
+ replace: (_match, prefix) => `${prefix}[REDACTED]`,
25
+ },
26
+ {
27
+ pattern: new RegExp(
28
+ `(\\b${SECRET_TEXT_KEY_PATTERN}\\b\\s+)(?!is\\b)([^&,\r\n]*?)(?=\\s+\\b${SECRET_TEXT_KEY_PATTERN}\\b\\s+|[&,\r\n]|$)`,
29
+ 'gi'
30
+ ),
31
+ replace: (_match, prefix) => `${prefix}[REDACTED]`,
32
+ },
33
+ {
34
+ pattern: new RegExp(
35
+ `((?:"|')?${SECRET_TEXT_KEY_PATTERN}(?:"|')?\\s*:\\s*)(?:"[^"]*"|'[^']*'|[^,}\\]\r\n]+)`,
36
+ 'gi'
37
+ ),
38
+ replace: (_match, prefix) => `${prefix}"[REDACTED]"`,
39
+ },
40
+ /\bsk-[A-Za-z0-9_-]{8,}\b/g,
41
+ ];
42
+
43
+ function defaultRunCommand(command, args, options = {}) {
44
+ return execFileSync(command, args, {
45
+ cwd: options.cwd || process.cwd(),
46
+ encoding: 'utf8',
47
+ input: options.input,
48
+ timeout: options.timeout || 120000,
49
+ });
50
+ }
51
+
52
+ function redactString(value) {
53
+ return SECRET_TEXT_PATTERNS.reduce(
54
+ (current, entry) => {
55
+ if (entry && typeof entry === 'object' && !(entry instanceof RegExp)) {
56
+ return current.replace(entry.pattern, entry.replace);
57
+ }
58
+ return current.replace(entry, () => '[REDACTED]');
59
+ },
60
+ value,
61
+ );
62
+ }
63
+
64
+ function redact(value, key = '') {
65
+ if (SECRET_KEY_PATTERN.test(key)) {
66
+ return '[REDACTED]';
67
+ }
68
+
69
+ if (typeof value === 'string') {
70
+ return redactString(value);
71
+ }
72
+
73
+ if (Array.isArray(value)) {
74
+ return value.map(item => redact(item));
75
+ }
76
+
77
+ if (value && typeof value === 'object') {
78
+ return Object.fromEntries(
79
+ Object.entries(value).map(([entryKey, entryValue]) => [entryKey, redact(entryValue, entryKey)]),
80
+ );
81
+ }
82
+
83
+ return value;
84
+ }
85
+
86
+ function asJsonString(value) {
87
+ if (typeof value === 'string') return redactString(value);
88
+ return JSON.stringify(redact(value));
89
+ }
90
+
91
+ function buildSubagentAuditPayload(event) {
92
+ if (!event || typeof event !== 'object') {
93
+ throw new TypeError('Audit event must be an object');
94
+ }
95
+ if (!event.command) {
96
+ throw new TypeError('Audit event command is required');
97
+ }
98
+ if (!event.role) {
99
+ throw new TypeError('Audit event role is required');
100
+ }
101
+
102
+ const metadata =
103
+ event.metadata && typeof event.metadata === 'object' && !Array.isArray(event.metadata)
104
+ ? event.metadata
105
+ : {};
106
+
107
+ return {
108
+ kind: 'llm_call',
109
+ command: event.command,
110
+ issueId: event.issueId,
111
+ role: event.role,
112
+ phase: event.phase,
113
+ taskId: event.taskId,
114
+ taskTitle: event.taskTitle,
115
+ model: event.model || 'unknown',
116
+ prompt: asJsonString({
117
+ command: event.command,
118
+ role: event.role,
119
+ phase: event.phase,
120
+ taskId: event.taskId,
121
+ taskTitle: event.taskTitle,
122
+ content: event.prompt || '',
123
+ }),
124
+ response: asJsonString({
125
+ command: event.command,
126
+ role: event.role,
127
+ verdict: event.verdict || 'UNKNOWN',
128
+ content: event.response || '',
129
+ }),
130
+ metadata: redact(metadata),
131
+ verdict: event.verdict,
132
+ };
133
+ }
134
+
135
+ function parseRecordId(output, { requireJson = false } = {}) {
136
+ if (!output) return null;
137
+ try {
138
+ const parsed = JSON.parse(output);
139
+ return typeof parsed.id === 'string' ? parsed.id : null;
140
+ } catch (_error) {
141
+ if (requireJson) return null;
142
+ const match = /\bint-[A-Za-z0-9_-]+\b/.exec(String(output));
143
+ return match ? match[0] : null;
144
+ }
145
+ }
146
+
147
+ function hasAuditMetaJsonSupport(runCommand = defaultRunCommand, options = {}) {
148
+ const output = runCommand('bd', ['audit', 'record', '--help'], options);
149
+ return String(output).includes('--meta-json');
150
+ }
151
+
152
+ function writeFallbackMetadata(payload, entryId, options) {
153
+ if (!entryId || !payload.metadata || Object.keys(payload.metadata).length === 0) {
154
+ return null;
155
+ }
156
+
157
+ const cwd = options.cwd || process.cwd();
158
+ const fsImpl = options.fs || fs;
159
+ const forgeDir = path.join(cwd, '.forge').replace(/\\/g, '/');
160
+ const logPath = path.join(forgeDir, 'log.jsonl').replace(/\\/g, '/');
161
+ const line = {
162
+ kind: 'forge.auditEvidence',
163
+ sourceOfTruth: 'beads',
164
+ beadsEntryId: entryId,
165
+ command: redact(payload.command),
166
+ role: redact(payload.role),
167
+ phase: redact(payload.phase),
168
+ taskId: redact(payload.taskId),
169
+ taskTitle: redact(payload.taskTitle),
170
+ metadata: redact(payload.metadata),
171
+ };
172
+
173
+ try {
174
+ fsImpl.mkdirSync(forgeDir, { recursive: true });
175
+ fsImpl.appendFileSync(logPath, `${JSON.stringify(line)}\n`);
176
+ return { path: logPath, line };
177
+ } catch (error) {
178
+ return { path: logPath, line, skipped: true, error: error.message };
179
+ }
180
+ }
181
+
182
+ function recordSubagentAuditEvent(event, options = {}) {
183
+ const payload = buildSubagentAuditPayload(event);
184
+ const runCommand = options.runCommand || defaultRunCommand;
185
+ const metaJsonSupported =
186
+ typeof options.metaJsonSupported === 'boolean'
187
+ ? options.metaJsonSupported
188
+ : hasAuditMetaJsonSupport(runCommand, { cwd: options.cwd || process.cwd() });
189
+ const args = [
190
+ 'audit',
191
+ 'record',
192
+ '--json',
193
+ '--kind',
194
+ 'llm_call',
195
+ ];
196
+
197
+ if (payload.issueId) {
198
+ args.push('--issue-id', payload.issueId);
199
+ }
200
+
201
+ args.push(
202
+ '--model',
203
+ payload.model,
204
+ '--prompt',
205
+ payload.prompt,
206
+ '--response',
207
+ payload.response,
208
+ );
209
+
210
+ if (metaJsonSupported && Object.keys(payload.metadata).length > 0) {
211
+ args.push('--meta-json', JSON.stringify(payload.metadata));
212
+ }
213
+
214
+ const output = runCommand('bd', args, { cwd: options.cwd || process.cwd() });
215
+ const entryId = parseRecordId(output, { requireJson: true });
216
+ const fallback = metaJsonSupported ? null : writeFallbackMetadata(payload, entryId, options);
217
+
218
+ return {
219
+ success: Boolean(entryId),
220
+ entryId,
221
+ output,
222
+ payload,
223
+ fallback,
224
+ };
225
+ }
226
+
227
+ function labelSubagentAuditEvent(entryId, event, options = {}) {
228
+ const verdict = String(event?.verdict || '').toUpperCase();
229
+ const role = event?.role;
230
+ const label = VERDICT_LABELS[verdict];
231
+
232
+ if (!entryId || !REVIEWER_ROLES.has(role) || !label) {
233
+ return { skipped: true };
234
+ }
235
+
236
+ const runCommand = options.runCommand || defaultRunCommand;
237
+ const reason = `${role} verdict: ${verdict}`;
238
+ let output;
239
+ try {
240
+ output = runCommand('bd', [
241
+ 'audit',
242
+ 'label',
243
+ entryId,
244
+ '--json',
245
+ '--label',
246
+ label,
247
+ '--reason',
248
+ reason,
249
+ ], { cwd: options.cwd || process.cwd() });
250
+ } catch (error) {
251
+ return {
252
+ success: false,
253
+ label,
254
+ error: error.message,
255
+ };
256
+ }
257
+
258
+ const labeledEntryId = parseRecordId(output, { requireJson: true });
259
+
260
+ return {
261
+ success: labeledEntryId === entryId,
262
+ label,
263
+ entryId: labeledEntryId,
264
+ output,
265
+ };
266
+ }
267
+
268
+ function recordAndLabelSubagentAuditEvent(event, options = {}) {
269
+ const record = recordSubagentAuditEvent(event, options);
270
+ const label = labelSubagentAuditEvent(record.entryId, event, options);
271
+ return { record, label };
272
+ }
273
+
274
+ module.exports = {
275
+ VERDICT_LABELS,
276
+ buildSubagentAuditPayload,
277
+ recordSubagentAuditEvent,
278
+ labelSubagentAuditEvent,
279
+ recordAndLabelSubagentAuditEvent,
280
+ hasAuditMetaJsonSupport,
281
+ redact,
282
+ };
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ // Single-source, side-effect-free Beads-store detector (kernel issue a5399f3d).
7
+ //
8
+ // Both upgrade-safety surfaces reuse this ONE definition: the issue-path
9
+ // unmigrated-beads nudge (lib/commands/_issue.js) and the `forge upgrade`
10
+ // advisory (lib/upgrade-safety.js). It deliberately lives in a neutral module,
11
+ // NOT in lib/commands/migrate.js — re-exporting a `detectBeadsJsonlSource` name
12
+ // from the migrate command would revive the identifier the a7e1443c
13
+ // implicit-auto-migrate tombstone pins gone (test/commands/runtime-no-auto-migrate).
14
+ // This detector only READS the filesystem and never triggers a migration, so it
15
+ // honors that tombstone's spirit while giving both nudges a shared source.
16
+
17
+ function dirHasJsonl(dir) {
18
+ try {
19
+ return fs.readdirSync(dir).some(entry => entry.endsWith('.jsonl'));
20
+ } catch (_err) { // NOSONAR S2486 — line-scoped; must sit on the catch line
21
+ /* intentional: an unreadable directory has no usable jsonl */
22
+ return false;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Return the absolute path of the directory holding a returning user's Beads
28
+ * JSONL under `projectRoot`, or null when none is found. Checks the top-level
29
+ * `.beads/` first, then the split-store `.beads/backup/` layout (jsonl there and
30
+ * nowhere else — a layout the migrator itself reads), so neither surface misses
31
+ * it. Never throws.
32
+ *
33
+ * @param {string} [projectRoot]
34
+ * @returns {string|null}
35
+ */
36
+ function detectBeadsJsonlSource(projectRoot) {
37
+ const root = projectRoot || process.cwd();
38
+ const beadsDir = path.join(root, '.beads');
39
+ try {
40
+ if (!fs.existsSync(beadsDir)) {
41
+ return null;
42
+ }
43
+ if (dirHasJsonl(beadsDir)) {
44
+ return beadsDir;
45
+ }
46
+ const backupDir = path.join(beadsDir, 'backup');
47
+ if (fs.existsSync(backupDir) && dirHasJsonl(backupDir)) {
48
+ return backupDir;
49
+ }
50
+ return null;
51
+ } catch (_err) { // NOSONAR S2486 — line-scoped; must sit on the catch line
52
+ /* intentional: an unreadable project root has no detectable beads source */
53
+ return null;
54
+ }
55
+ }
56
+
57
+ module.exports = {
58
+ detectBeadsJsonlSource,
59
+ dirHasJsonl,
60
+ };
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ const { resolveIssueBackend } = require('./issue-backend');
4
+ const { detectBeadsJsonlSource } = require('./beads-detect');
5
+
6
+ // Unmigrated legacy-store nudge (kernel issue a5399f3d — upgrade-safety
7
+ // 0.0.10 -> current).
8
+ //
9
+ // The 0.0.10 -> current upgrade flipped the DEFAULT issue backend to the Kernel,
10
+ // but the legacy -> kernel migration fires ONLY from `forge setup`/`init`, never
11
+ // lazily on the issue path. So a user who runs `bun update` then `forge list` /
12
+ // `forge ready` reads an EMPTY kernel and their 0.0.10 issues APPEAR GONE — the
13
+ // data is safe on disk, just invisible, with no hint. This helper closes that
14
+ // footgun: when the resolved backend is the (default) Kernel, a read comes back
15
+ // empty, AND a legacy jsonl store still exists, it prints a one-time guided hint.
16
+ //
17
+ // It lives in this NEUTRAL module (not lib/commands/_issue.js) on purpose: the
18
+ // message text names the retired backend + its `.beads` store, and _issue.js is a
19
+ // D20 release-readiness hot-path surface that must stay free of those tokens (the
20
+ // bd-call-site audit + kernel-backed checks scan it). Keeping the strings here
21
+ // lets the hot path call a token-free helper.
22
+ //
23
+ // Best-effort and non-blocking: it NEVER throws and never alters the read result.
24
+ // Fires at most once per project root per process to avoid spam.
25
+ const NUDGE_READS = new Set(['list', 'ready']);
26
+ const warnedRoots = new Set();
27
+
28
+ // A kernel read looks empty when its contract data carries no issues. Handles the
29
+ // issue.list/issue.ready shape ({ issues, count }) plus a bare array / null, and
30
+ // is deliberately conservative: anything it cannot confirm as empty is treated as
31
+ // NON-empty so a user with real kernel issues is never nagged.
32
+ function kernelReadLooksEmpty(result) {
33
+ if (!result || typeof result !== 'object' || result.ok !== true) {
34
+ return false;
35
+ }
36
+ const data = result.data;
37
+ if (data === null || data === undefined) {
38
+ return true;
39
+ }
40
+ if (Array.isArray(data)) {
41
+ return data.length === 0;
42
+ }
43
+ if (Array.isArray(data.issues)) {
44
+ return data.issues.length === 0;
45
+ }
46
+ if (Array.isArray(data.items)) {
47
+ return data.items.length === 0;
48
+ }
49
+ if (typeof data.count === 'number') {
50
+ return data.count === 0;
51
+ }
52
+ return false;
53
+ }
54
+
55
+ function maybeWarnUnmigratedBeads(subcommand, result, projectRoot, rawOpts = {}) {
56
+ try {
57
+ if (!NUDGE_READS.has(subcommand) || !projectRoot) {
58
+ return;
59
+ }
60
+ if (warnedRoots.has(projectRoot)) {
61
+ return;
62
+ }
63
+ const env = rawOpts.env || process.env;
64
+ // Resolve the EFFECTIVE backend (default kernel). An explicit opt-in to the
65
+ // retired backend means the user chose it deliberately — nothing to nudge.
66
+ const backend = resolveIssueBackend({ deps: rawOpts, env, projectRoot, warn: () => {} });
67
+ if (backend !== 'kernel' || !kernelReadLooksEmpty(result)) {
68
+ return;
69
+ }
70
+ if (!detectBeadsJsonlSource(projectRoot)) {
71
+ return;
72
+ }
73
+ warnedRoots.add(projectRoot);
74
+ console.error(
75
+ '\n[forge] Legacy issue data detected (.beads/*.jsonl) but the Kernel issue store is empty.\n'
76
+ + 'Forge now defaults to the Kernel backend (breaking change since 0.0.10). Your legacy\n'
77
+ + 'issues are safe on disk but will not appear until migrated. To migrate:\n'
78
+ + ' forge migrate --from beads # import your legacy issues into the Kernel\n'
79
+ + ' forge setup # (re)wire hooks + provision the Kernel store\n'
80
+ + 'Prefer to stay on the legacy backend? Set `issueBackend: beads` in .forge/config.yaml '
81
+ + '(or FORGE_ISSUE_BACKEND=beads).\n',
82
+ );
83
+ } catch (_err) { // NOSONAR S2486 — line-scoped; must sit on the catch line
84
+ /* the nudge is a best-effort hint; it must never break a read */
85
+ }
86
+ }
87
+
88
+ module.exports = {
89
+ maybeWarnUnmigratedBeads,
90
+ kernelReadLooksEmpty,
91
+ };
@@ -12,6 +12,13 @@ const path = require('node:path');
12
12
  const { spawnSync } = require('node:child_process');
13
13
  const { secureExecFileSync } = require('./shell-utils');
14
14
 
15
+ const BEADS_LOCAL_EXCLUDE_LINES = [
16
+ '# Forge local Beads state',
17
+ '.beads/',
18
+ '.dolt/',
19
+ '.beads-credential-key'
20
+ ];
21
+
15
22
  function isIgnorableFsError(error) {
16
23
  return ['EACCES', 'ENOENT', 'ENOTDIR', 'EPERM'].includes(error?.code);
17
24
  }
@@ -103,6 +110,10 @@ function writeBeadsGitignore(projectRoot) {
103
110
  fs.mkdirSync(beadsDir, { recursive: true });
104
111
 
105
112
  const gitignoreContent = [
113
+ '# Beads runtime, export, and backup state is local',
114
+ '*',
115
+ '!.gitignore',
116
+ '',
106
117
  '# Dolt database files (binary, not suitable for git)',
107
118
  'dolt/',
108
119
  '*.db',
@@ -117,6 +128,108 @@ function writeBeadsGitignore(projectRoot) {
117
128
  );
118
129
  }
119
130
 
131
+ function resolveGitInfoExcludePath(projectRoot) {
132
+ try {
133
+ const gitExcludePath = secureExecFileSync(resolveCommand('git'), ['rev-parse', '--git-path', 'info/exclude'], {
134
+ cwd: projectRoot,
135
+ encoding: 'utf8',
136
+ stdio: 'pipe'
137
+ }).trim();
138
+
139
+ if (gitExcludePath) {
140
+ return path.isAbsolute(gitExcludePath)
141
+ ? gitExcludePath
142
+ : path.resolve(projectRoot, gitExcludePath);
143
+ }
144
+ } catch (error) {
145
+ if (!isRecoverableCommandError(error)) {
146
+ throw error;
147
+ }
148
+ }
149
+
150
+ return path.join(projectRoot, '.git', 'info', 'exclude');
151
+ }
152
+
153
+ function untrackExistingBeadsState(projectRoot) {
154
+ let tracked;
155
+ try {
156
+ tracked = secureExecFileSync(resolveCommand('git'), [
157
+ 'ls-files',
158
+ '-z',
159
+ '--',
160
+ '.beads',
161
+ '.dolt',
162
+ '.beads-credential-key'
163
+ ], {
164
+ cwd: projectRoot,
165
+ encoding: 'utf8',
166
+ stdio: 'pipe'
167
+ });
168
+ } catch (error) {
169
+ if (!isRecoverableCommandError(error)) {
170
+ throw error;
171
+ }
172
+ return;
173
+ }
174
+
175
+ if (!tracked) {
176
+ return;
177
+ }
178
+
179
+ try {
180
+ secureExecFileSync(resolveCommand('git'), [
181
+ 'rm',
182
+ '--cached',
183
+ '-r',
184
+ '-f',
185
+ '--ignore-unmatch',
186
+ '--',
187
+ '.beads',
188
+ '.dolt',
189
+ '.beads-credential-key'
190
+ ], {
191
+ cwd: projectRoot,
192
+ encoding: 'utf8',
193
+ stdio: 'pipe'
194
+ });
195
+ } catch (error) {
196
+ if (!isRecoverableCommandError(error)) {
197
+ throw error;
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Add local-only Git exclude rules for Beads state.
204
+ *
205
+ * Uses `.git/info/exclude` instead of the tracked `.gitignore` so `forge setup`
206
+ * does not dirty downstream repositories just to keep Beads runtime files local.
207
+ * Existing tracked Beads state is removed from the Git index only, leaving local
208
+ * files in place for the Beads runtime.
209
+ *
210
+ * @param {string} projectRoot - Absolute path to the project root
211
+ */
212
+ function ensureBeadsGitExclude(projectRoot) {
213
+ const excludePath = resolveGitInfoExcludePath(projectRoot);
214
+ fs.mkdirSync(path.dirname(excludePath), { recursive: true });
215
+
216
+ const existing = fs.existsSync(excludePath)
217
+ ? fs.readFileSync(excludePath, 'utf8')
218
+ : '';
219
+ const existingLines = new Set(existing.split(/\r?\n/));
220
+ const missingLines = BEADS_LOCAL_EXCLUDE_LINES.filter((line) => !existingLines.has(line));
221
+
222
+ if (missingLines.length === 0) {
223
+ untrackExistingBeadsState(projectRoot);
224
+ return;
225
+ }
226
+
227
+ const prefix = existing && !existing.endsWith('\n') ? '\n' : '';
228
+ const spacer = existing.trim() ? '\n' : '';
229
+ fs.appendFileSync(excludePath, `${prefix}${spacer}${missingLines.join('\n')}\n`, 'utf8');
230
+ untrackExistingBeadsState(projectRoot);
231
+ }
232
+
120
233
  /**
121
234
  * Check whether Beads is properly initialized in a project.
122
235
  *
@@ -334,6 +447,13 @@ function safeBeadsInit(projectRoot, options = {}) {
334
447
  const warnings = [];
335
448
  const errors = [];
336
449
 
450
+ // Keep Beads runtime/export state local even when the tracker already exists.
451
+ try {
452
+ ensureBeadsGitExclude(projectRoot);
453
+ } catch (err) {
454
+ warnings.push(`Failed to write local Beads git exclude rules: ${err.message}`);
455
+ }
456
+
337
457
  // (a) Check if already initialized — idempotent skip
338
458
  if (isBeadsInitialized(projectRoot)) {
339
459
  return {
@@ -410,6 +530,7 @@ module.exports = {
410
530
  sanitizePrefix,
411
531
  writeBeadsConfig,
412
532
  writeBeadsGitignore,
533
+ ensureBeadsGitExclude,
413
534
  isBeadsInitialized,
414
535
  readBeadsDatabaseName,
415
536
  preSeedJsonl,