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
@@ -12,6 +12,8 @@ const path = require('node:path');
12
12
  const { execFileSync: defaultExecFileSync } = require('node:child_process');
13
13
 
14
14
  const { checkLefthookStatus } = require('./lefthook-check');
15
+ const { resolveIssueBackend } = require('./issue-backend');
16
+ const { getInstallCommand, getAddDevCommand } = require('./package-manager-remediation');
15
17
 
16
18
  const WINDOWS_GIT_BASH_CANDIDATES = [
17
19
  String.raw`C:\Program Files\Git\bin\bash.exe`,
@@ -43,11 +45,149 @@ function normalizeHooksPath(value, platform = process.platform) {
43
45
  return normalized;
44
46
  }
45
47
 
46
- function createDiagnostic(code, subject, message, repair) {
48
+ function isAbsoluteForPlatform(value, platform = process.platform) {
49
+ const raw = toText(value).trim();
50
+ return platform === 'win32' ? path.win32.isAbsolute(raw) : path.posix.isAbsolute(raw);
51
+ }
52
+
53
+ function resolvesToExecutableHookCommand(line, matcher) {
54
+ const trimmed = line.trim();
55
+ if (!trimmed || trimmed.startsWith('#')) return false;
56
+ const firstCommand = commandName(commandTokens(trimmed)[0]);
57
+ if (firstCommand === 'echo' || firstCommand === 'printf') {
58
+ const chainIndex = trimmed.indexOf('&&');
59
+ return chainIndex === -1 ? false : matcher(trimmed.slice(chainIndex + 2).trim());
60
+ }
61
+ return matcher(trimmed);
62
+ }
63
+
64
+ function hasExecutableHookCommand(content, matcher) {
65
+ let heredocEnd = null;
66
+
67
+ for (const line of toText(content).split(/\r?\n/)) {
68
+ const trimmed = line.trim();
69
+ if (heredocEnd) {
70
+ if (trimmed === heredocEnd) heredocEnd = null;
71
+ continue;
72
+ }
73
+
74
+ if (resolvesToExecutableHookCommand(line, matcher)) return true;
75
+
76
+ const delimiter = heredocDelimiter(line);
77
+ if (delimiter) heredocEnd = delimiter;
78
+ }
79
+
80
+ return false;
81
+ }
82
+
83
+ function commandTokens(line) {
84
+ return line
85
+ .replace(/[;"'()]/g, ' ')
86
+ .split(/\s+/)
87
+ .filter(Boolean);
88
+ }
89
+
90
+ function commandName(token) {
91
+ return toText(token)
92
+ .replaceAll('\\', '/')
93
+ .split('/')
94
+ .pop()
95
+ .toLowerCase()
96
+ .replace(/\.(?:exe|cmd)$/, '');
97
+ }
98
+
99
+ function isWhitespace(char) {
100
+ return char === ' ' || char === '\t' || char === '\r' || char === '\n';
101
+ }
102
+
103
+ function isVariableNameStart(char) {
104
+ return (char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || char === '_';
105
+ }
106
+
107
+ function isVariableNameChar(char) {
108
+ return isVariableNameStart(char) || (char >= '0' && char <= '9');
109
+ }
110
+
111
+ function isAssignmentToken(token) {
112
+ const text = toText(token);
113
+ const equalsIndex = text.indexOf('=');
114
+ if (equalsIndex <= 0 || !isVariableNameStart(text[0])) return false;
115
+
116
+ for (let index = 1; index < equalsIndex; index += 1) {
117
+ if (!isVariableNameChar(text[index])) return false;
118
+ }
119
+
120
+ return true;
121
+ }
122
+
123
+ function heredocDelimiter(line) {
124
+ const markerIndex = line.indexOf('<<');
125
+ if (markerIndex === -1 || line[markerIndex + 2] === '<') return null;
126
+
127
+ let cursor = markerIndex + 2;
128
+ if (line[cursor] === '-') cursor += 1;
129
+ while (cursor < line.length && isWhitespace(line[cursor])) cursor += 1;
130
+
131
+ const quote = line[cursor];
132
+ if (quote === '"' || quote === "'") {
133
+ cursor += 1;
134
+ const endQuote = line.indexOf(quote, cursor);
135
+ return endQuote === -1 ? line.slice(cursor).trim() || null : line.slice(cursor, endQuote) || null;
136
+ }
137
+
138
+ const start = cursor;
139
+ while (cursor < line.length && !isWhitespace(line[cursor])) cursor += 1;
140
+ return line.slice(start, cursor).trim() || null;
141
+ }
142
+
143
+ function isExecutingTokenPosition(tokens, index) {
144
+ const nonExecutingPreviousTokens = new Set(['-e', '-f', '-r', '-x', '[', 'test']);
145
+ const executingPreviousCommands = new Set(['bash', 'bun', 'env', 'exec', 'if', 'node', 'sh', 'then', '&&']);
146
+
147
+ let previousIndex = index - 1;
148
+ while (previousIndex >= 0 && isAssignmentToken(tokens[previousIndex])) {
149
+ previousIndex -= 1;
150
+ }
151
+
152
+ if (previousIndex < 0) return true;
153
+
154
+ const previous = toText(tokens[previousIndex]).toLowerCase();
155
+ if (nonExecutingPreviousTokens.has(previous)) return false;
156
+ return executingPreviousCommands.has(commandName(previous));
157
+ }
158
+
159
+ function invokesLefthook(line) {
160
+ return commandTokens(line).some((token) => commandName(token) === 'lefthook');
161
+ }
162
+
163
+ function invokesForgeHook(line) {
164
+ return commandTokens(line).some((token, index, tokens) => {
165
+ if (token.includes('=')) return false;
166
+ const normalized = token.replaceAll('\\', '/').toLowerCase();
167
+ if (!normalized.endsWith('.forge/hooks/check-tdd.js') && commandName(token) !== 'check-tdd.js') {
168
+ return false;
169
+ }
170
+
171
+ return isExecutingTokenPosition(tokens, index);
172
+ });
173
+ }
174
+
175
+ function invokesBeadsHook(line, hookName) {
176
+ const tokens = commandTokens(line);
177
+ return tokens.some((token, index) => (
178
+ commandName(token) === 'bd'
179
+ && toText(tokens[index + 1]).toLowerCase() === 'hooks'
180
+ && toText(tokens[index + 2]).toLowerCase() === 'run'
181
+ && toText(tokens[index + 3]).toLowerCase() === hookName
182
+ && isExecutingTokenPosition(tokens, index)
183
+ ));
184
+ }
185
+
186
+ function createDiagnostic(code, subject, message, repair, severity = 'hard-stop') {
47
187
  return {
48
188
  code,
49
189
  subject,
50
- severity: 'hard-stop',
190
+ severity,
51
191
  message,
52
192
  ...(repair ? { repair } : {})
53
193
  };
@@ -76,7 +216,7 @@ function isUsableWindowsShellCandidate(candidate, options = {}) {
76
216
  function checkHookInstallation(projectRoot, options = {}) {
77
217
  const exec = options._exec || defaultExecFileSync;
78
218
  const platform = options.platform || process.platform;
79
- const expectedRelativeHooksPath = normalizeHooksPath('.lefthook/hooks', platform);
219
+ const expectedRelativeHooksPaths = ['.lefthook/hooks', '.beads/hooks'];
80
220
  const requiredHooks = ['pre-commit', 'pre-push'];
81
221
 
82
222
  function resolveGitRoot() {
@@ -92,7 +232,10 @@ function checkHookInstallation(projectRoot, options = {}) {
92
232
  }
93
233
 
94
234
  const gitRoot = resolveGitRoot();
95
- const expectedAbsoluteHooksPath = normalizeHooksPath(`${gitRoot}/${expectedRelativeHooksPath}`, platform);
235
+ const expectedHooksPaths = new Set(expectedRelativeHooksPaths.flatMap((hooksPath) => [
236
+ normalizeHooksPath(hooksPath, platform),
237
+ normalizeHooksPath(`${gitRoot}/${normalizeHooksPath(hooksPath, platform)}`, platform)
238
+ ]));
96
239
 
97
240
  function resolveGitHooksDir() {
98
241
  try {
@@ -108,17 +251,44 @@ function checkHookInstallation(projectRoot, options = {}) {
108
251
  }
109
252
  }
110
253
 
111
- function fileLooksLikeActiveHook(filePath) {
254
+ function classifyHookFile(filePath, hookName, visited = new Set()) {
112
255
  try {
256
+ const normalizedFilePath = path.resolve(filePath);
257
+ if (visited.has(normalizedFilePath)) {
258
+ return { active: false, provider: 'cycle' };
259
+ }
260
+ visited.add(normalizedFilePath);
261
+
113
262
  const stat = fs.statSync(filePath);
114
263
  const executable = platform === 'win32'
115
264
  || (typeof options._canExecuteHook === 'function'
116
265
  ? Boolean(options._canExecuteHook(filePath, stat))
117
266
  : (stat.mode & 0o111) !== 0);
118
- if (!stat.isFile() || stat.size === 0 || !executable) return false;
119
- return /\blefthook\b/i.test(fs.readFileSync(filePath, 'utf8'));
267
+ if (!stat.isFile() || stat.size === 0 || !executable) return { active: false, provider: 'missing' };
268
+
269
+ const content = fs.readFileSync(filePath, 'utf8');
270
+ const runsLefthook = hasExecutableHookCommand(content, invokesLefthook);
271
+ const runsForgeHook = hasExecutableHookCommand(content, invokesForgeHook);
272
+ const runsBeadsHook = hasExecutableHookCommand(content, (line) => invokesBeadsHook(line, hookName));
273
+
274
+ if (runsLefthook) return { active: true, provider: 'lefthook' };
275
+ if (runsForgeHook) return { active: true, provider: 'forge' };
276
+
277
+ if (runsBeadsHook) {
278
+ const chainedHookPath = path.join(gitRoot, '.beads', 'hooks', hookName);
279
+ if (path.resolve(chainedHookPath) !== normalizedFilePath && fs.existsSync(chainedHookPath)) {
280
+ const chained = classifyHookFile(chainedHookPath, hookName, visited);
281
+ if (chained.active) {
282
+ return { active: true, provider: `beads->${chained.provider}` };
283
+ }
284
+ }
285
+
286
+ return { active: false, provider: 'beads-unverified' };
287
+ }
288
+
289
+ return { active: false, provider: 'unknown' };
120
290
  } catch {
121
- return false;
291
+ return { active: false, provider: 'missing' };
122
292
  }
123
293
  }
124
294
 
@@ -130,11 +300,18 @@ function checkHookInstallation(projectRoot, options = {}) {
130
300
  });
131
301
 
132
302
  hooksPath = normalizeHooksPath(output, platform);
133
- const active = hooksPath === expectedRelativeHooksPath || hooksPath === expectedAbsoluteHooksPath;
303
+ const active = expectedHooksPaths.has(hooksPath);
134
304
 
135
305
  if (active) {
136
- const hooksDir = path.join(gitRoot, '.lefthook', 'hooks');
137
- const missingHooks = requiredHooks.filter(hook => !fileLooksLikeActiveHook(path.join(hooksDir, hook)));
306
+ const rawHooksPath = toText(output).trim();
307
+ const hooksDir = isAbsoluteForPlatform(rawHooksPath, platform)
308
+ ? rawHooksPath
309
+ : path.resolve(gitRoot, normalizeHooksPath(rawHooksPath, platform));
310
+ const hookChecks = requiredHooks.map((hook) => ({
311
+ hook,
312
+ ...classifyHookFile(path.join(hooksDir, hook), hook)
313
+ }));
314
+ const missingHooks = hookChecks.filter(check => !check.active).map(check => check.hook);
138
315
  const verifiedActive = missingHooks.length === 0;
139
316
  return {
140
317
  active: verifiedActive,
@@ -142,6 +319,7 @@ function checkHookInstallation(projectRoot, options = {}) {
142
319
  verification: 'core.hooksPath',
143
320
  hooksPath: hooksPath || null,
144
321
  hooksDir,
322
+ providers: Object.fromEntries(hookChecks.map(check => [check.hook, check.provider])),
145
323
  missingHooks,
146
324
  message: verifiedActive
147
325
  ? ''
@@ -155,7 +333,7 @@ function checkHookInstallation(projectRoot, options = {}) {
155
333
  state: 'inactive',
156
334
  verification: 'core.hooksPath',
157
335
  hooksPath,
158
- message: `Git core.hooksPath is set to "${hooksPath}", not ".lefthook/hooks".`
336
+ message: `Git core.hooksPath is set to "${hooksPath}", not ".lefthook/hooks" or ".beads/hooks".`
159
337
  };
160
338
  }
161
339
  } catch {
@@ -164,7 +342,11 @@ function checkHookInstallation(projectRoot, options = {}) {
164
342
 
165
343
  const hooksDir = resolveGitHooksDir();
166
344
  if (hooksDir) {
167
- const missingHooks = requiredHooks.filter(hook => !fileLooksLikeActiveHook(path.join(hooksDir, hook)));
345
+ const hookChecks = requiredHooks.map((hook) => ({
346
+ hook,
347
+ ...classifyHookFile(path.join(hooksDir, hook), hook)
348
+ }));
349
+ const missingHooks = hookChecks.filter(check => !check.active).map(check => check.hook);
168
350
  const active = missingHooks.length === 0;
169
351
  return {
170
352
  active,
@@ -172,6 +354,7 @@ function checkHookInstallation(projectRoot, options = {}) {
172
354
  verification: 'git-path-hooks',
173
355
  hooksPath: hooksPath || null,
174
356
  hooksDir,
357
+ providers: Object.fromEntries(hookChecks.map(check => [check.hook, check.provider])),
175
358
  missingHooks,
176
359
  message: active
177
360
  ? ''
@@ -300,6 +483,22 @@ function normalizeProjectRoot(projectRoot) {
300
483
  : process.cwd();
301
484
  }
302
485
 
486
+ // Resolve the active issue backend for the health gate. An already-normalized
487
+ // `options.issueBackend` (passed by enforce-stage) wins; otherwise fall back to the
488
+ // shared resolver (env > .forge/config.yaml > default 'kernel'). Never throws and
489
+ // never warns from the health path.
490
+ function resolveHealthIssueBackend(options, projectRoot) {
491
+ if (options.issueBackend === 'kernel' || options.issueBackend === 'beads') {
492
+ return options.issueBackend;
493
+ }
494
+ return resolveIssueBackend({
495
+ deps: options.backendDeps || {},
496
+ env: options.env || process.env,
497
+ projectRoot,
498
+ warn: () => {}
499
+ });
500
+ }
501
+
303
502
  function checkRuntimeHealth(projectRoot, options = {}) {
304
503
  const platform = options.platform || process.platform;
305
504
  const root = normalizeProjectRoot(projectRoot);
@@ -311,17 +510,23 @@ function checkRuntimeHealth(projectRoot, options = {}) {
311
510
  const jq = checkCommandAvailability('jq', root, options);
312
511
  const shell = normalizeShellRuntime(options.shellRuntime, platform, options);
313
512
 
513
+ const issueBackend = resolveHealthIssueBackend(options, root);
514
+
314
515
  const diagnostics = [];
516
+ const advisories = [];
315
517
 
316
518
  const lefthookMissing = lefthook.state === 'missing-dependency'
317
519
  || lefthook.state === 'missing-binary';
318
520
 
319
521
  if (lefthookMissing) {
522
+ const lefthookRepair = lefthook.state === 'missing-binary'
523
+ ? getInstallCommand(root)
524
+ : getAddDevCommand(root, 'lefthook');
320
525
  diagnostics.push(createDiagnostic(
321
526
  'LEFTHOOK_MISSING',
322
527
  'lefthook',
323
528
  lefthook.message || 'lefthook is required for hook installation.',
324
- 'bun add -D lefthook && bun install'
529
+ lefthookRepair
325
530
  ));
326
531
  }
327
532
 
@@ -335,11 +540,25 @@ function checkRuntimeHealth(projectRoot, options = {}) {
335
540
  }
336
541
 
337
542
  if (!bd.available) {
338
- diagnostics.push(createDiagnostic(
339
- 'BD_MISSING',
340
- 'bd',
341
- 'bd is required for stage-entry workflow checks.'
342
- ));
543
+ if (issueBackend === 'beads') {
544
+ // Beads is the active issue backend, so bd is a genuine hard prerequisite.
545
+ diagnostics.push(createDiagnostic(
546
+ 'BD_MISSING',
547
+ 'bd',
548
+ 'bd is required for stage-entry workflow checks when the beads issue backend is selected.'
549
+ ));
550
+ } else {
551
+ // Kernel is the default issue backend and needs no bd binary, so a missing bd
552
+ // must NOT hard-stop stage entry (the "no Beads install required" contract).
553
+ // Surface it as a non-blocking advisory instead.
554
+ advisories.push(createDiagnostic(
555
+ 'BD_MISSING',
556
+ 'bd',
557
+ 'bd is not installed. The kernel issue backend does not require it; install bd only if you switch to the beads backend.',
558
+ undefined,
559
+ 'advisory'
560
+ ));
561
+ }
343
562
  }
344
563
 
345
564
  if (!gh.available) {
@@ -373,6 +592,7 @@ function checkRuntimeHealth(projectRoot, options = {}) {
373
592
  ready: healthy,
374
593
  hardStop: !healthy,
375
594
  diagnostics,
595
+ advisories,
376
596
  checks: {
377
597
  projectRoot: root,
378
598
  lefthook,
@@ -380,7 +600,8 @@ function checkRuntimeHealth(projectRoot, options = {}) {
380
600
  bd,
381
601
  gh,
382
602
  jq,
383
- shell
603
+ shell,
604
+ issueBackend
384
605
  }
385
606
  };
386
607
  }
@@ -0,0 +1,268 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Native SAFETY-surface renderer.
5
+ *
6
+ * Renders sane, non-surprising security defaults into the two safety surfaces
7
+ * Forge can write PROJECT-LOCALLY today:
8
+ *
9
+ * - Claude Code : `.claude/settings.json` -> `permissions` (allow/deny/ask)
10
+ * - Cursor : `.cursorignore` (gitignore-style AI read/index boundary)
11
+ *
12
+ * Both use read -> merge -> write (idempotent, preserves user entries). An existing
13
+ * `.claude/settings.json` that cannot be parsed is BACKED UP and left untouched
14
+ * (mirrors the MCP renderer's data-loss guard) rather than clobbering user settings.
15
+ *
16
+ * NOT rendered here (honest deferral): the Codex execution sandbox / approvals policy
17
+ * (`sandbox_mode` / `approval_policy`). Codex honors a project-local `.codex/config.toml`
18
+ * ONLY when the project is marked trusted in the GLOBAL `$CODEX_HOME/config.toml`
19
+ * (`projects.<path>.trust_level`). Forge cannot grant that global trust during
20
+ * project-local setup, so a rendered project-local sandbox policy would not take
21
+ * effect. It is deferred to global-scope wiring and marked `not-delivered` in the
22
+ * capability matrix (kernel epic 90f2f631 / #311).
23
+ *
24
+ * Defaults follow the principle: ALLOW the routine forge/dev workflow, DENY the
25
+ * obviously-dangerous, and let everything else fall through to the harness's normal
26
+ * "ask" behavior (Claude evaluates deny -> ask -> allow; unmatched tools still prompt).
27
+ * We intentionally do NOT set `defaultMode`, so nothing is silently auto-approved.
28
+ *
29
+ * Dependency-free beyond node builtins so it runs under `bun test` and release gates.
30
+ *
31
+ * @module safety-config-renderer
32
+ */
33
+
34
+ const fs = require('node:fs');
35
+ const path = require('node:path');
36
+
37
+ const { backupFile } = require('./mcp-config-renderer');
38
+
39
+ /** Thrown when an existing settings file cannot be parsed — signals "do not overwrite". */
40
+ class SafetyConfigParseError extends Error {
41
+ constructor(message, cause) {
42
+ super(message);
43
+ this.name = 'SafetyConfigParseError';
44
+ this.cause = cause;
45
+ }
46
+ }
47
+
48
+ // Safe Claude Code permission defaults. Rule syntax per Claude Code settings docs
49
+ // (https://docs.claude.com/en/docs/claude-code/settings ,
50
+ // https://docs.claude.com/en/docs/claude-code/permissions):
51
+ // Bash(<prefix>:*) — command-prefix match; Read(<glob>) — gitignore-style path.
52
+ // Evaluated deny -> ask -> allow (deny always wins), so the deny Read(...) rules
53
+ // protect secrets even though bare Read is not blanket-allowed.
54
+ const CLAUDE_PERMISSION_DEFAULTS = Object.freeze({
55
+ // Routine, reversible forge/dev workflow — safe to run without a prompt.
56
+ allow: Object.freeze([
57
+ 'Bash(git status:*)',
58
+ 'Bash(git log:*)',
59
+ 'Bash(git diff:*)',
60
+ 'Bash(git branch:*)',
61
+ 'Bash(git show:*)',
62
+ 'Bash(git add:*)',
63
+ 'Bash(git commit:*)',
64
+ 'Bash(git stash:*)',
65
+ 'Bash(git checkout:*)',
66
+ 'Bash(git switch:*)',
67
+ 'Bash(git pull:*)',
68
+ 'Bash(git fetch:*)',
69
+ 'Bash(git push:*)',
70
+ 'Bash(ls:*)',
71
+ 'Bash(cat:*)',
72
+ 'Bash(pwd)',
73
+ 'Bash(which:*)',
74
+ 'Bash(mkdir:*)',
75
+ 'Bash(cp:*)',
76
+ 'Bash(mv:*)',
77
+ 'Bash(bun run:*)',
78
+ 'Bash(bun test:*)',
79
+ 'Bash(bun install)',
80
+ 'Bash(npm run:*)',
81
+ 'Bash(npm test:*)',
82
+ 'Bash(forge:*)',
83
+ 'Bash(gh pr view:*)',
84
+ 'Bash(gh pr list:*)',
85
+ 'Bash(gh issue list:*)',
86
+ ]),
87
+ // Careful, rewrites-history / rewinds-state — worth a confirmation, not a hard block.
88
+ ask: Object.freeze([
89
+ 'Bash(git rebase:*)',
90
+ 'Bash(git reset:*)',
91
+ 'Bash(git clean:*)',
92
+ ]),
93
+ // Obviously-dangerous commands + secret reads. deny wins over allow/ask.
94
+ deny: Object.freeze([
95
+ 'Bash(rm -rf:*)',
96
+ 'Bash(rm -fr:*)',
97
+ 'Bash(git push --force:*)',
98
+ 'Bash(git push -f:*)',
99
+ 'Bash(git reset --hard:*)',
100
+ // Read/secret boundary — private keys and secret env files are never readable.
101
+ 'Read(./.env)',
102
+ 'Read(**/.env)',
103
+ 'Read(**/.env.local)',
104
+ 'Read(**/.env.*.local)',
105
+ 'Read(**/*.pem)',
106
+ 'Read(**/*.key)',
107
+ 'Read(**/id_rsa)',
108
+ 'Read(**/id_ed25519)',
109
+ 'Read(./secrets/**)',
110
+ 'Read(**/credentials.json)',
111
+ ]),
112
+ });
113
+
114
+ // Safe `.cursorignore` defaults (gitignore-style, per
115
+ // https://cursor.com/docs/reference/ignore-file). Cursor already ignores .gitignore
116
+ // and common lock/.env files by default; listing them here makes the secret/large-dir
117
+ // boundary explicit and self-documenting. `.cursorignore` blocks Cursor AI features +
118
+ // indexing (it is not a hard security boundary — terminal/MCP tools can still reach files).
119
+ const CURSORIGNORE_DEFAULTS = Object.freeze([
120
+ '.env',
121
+ '.env.local',
122
+ '.env.*.local',
123
+ '*.pem',
124
+ '*.key',
125
+ 'id_rsa',
126
+ 'id_ed25519',
127
+ 'secrets/',
128
+ 'node_modules/',
129
+ 'dist/',
130
+ 'build/',
131
+ 'out/',
132
+ 'coverage/',
133
+ '.next/',
134
+ '*.log',
135
+ ]);
136
+
137
+ const CURSORIGNORE_HEADER = [
138
+ '# ---- Forge safe defaults (managed) ----',
139
+ '# Keeps secrets and large/generated dirs out of Cursor AI context + index.',
140
+ '# Edit freely — Forge only appends missing lines and never removes yours.',
141
+ ];
142
+
143
+ function unionPreserve(existing, defaults) {
144
+ const base = Array.isArray(existing) ? existing.filter(v => typeof v === 'string') : [];
145
+ const seen = new Set(base);
146
+ for (const rule of defaults) {
147
+ if (!seen.has(rule)) {
148
+ base.push(rule);
149
+ seen.add(rule);
150
+ }
151
+ }
152
+ return base;
153
+ }
154
+
155
+ /**
156
+ * Merge safe permission defaults into an existing `.claude/settings.json` string
157
+ * (read -> merge -> write). Preserves every other settings key and every existing
158
+ * allow/deny/ask entry (union, no duplicates). Idempotent.
159
+ * @param {string} existingText
160
+ * @returns {string} pretty JSON with a trailing newline
161
+ */
162
+ function mergeClaudePermissions(existingText) {
163
+ let obj = {};
164
+ if (existingText && existingText.trim()) {
165
+ try {
166
+ obj = JSON.parse(existingText);
167
+ } catch (err) {
168
+ // DATA-LOSS GUARD: never silently discard a populated-but-unparseable settings
169
+ // file (e.g. JSONC with comments). Signal the caller to back up and skip.
170
+ throw new SafetyConfigParseError('existing .claude/settings.json is not valid JSON', err);
171
+ }
172
+ }
173
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) obj = {};
174
+ const existingPerms = (obj.permissions && typeof obj.permissions === 'object' && !Array.isArray(obj.permissions))
175
+ ? obj.permissions
176
+ : {};
177
+ existingPerms.allow = unionPreserve(existingPerms.allow, CLAUDE_PERMISSION_DEFAULTS.allow);
178
+ existingPerms.ask = unionPreserve(existingPerms.ask, CLAUDE_PERMISSION_DEFAULTS.ask);
179
+ existingPerms.deny = unionPreserve(existingPerms.deny, CLAUDE_PERMISSION_DEFAULTS.deny);
180
+ obj.permissions = existingPerms;
181
+ return JSON.stringify(obj, null, 2) + '\n';
182
+ }
183
+
184
+ /**
185
+ * Merge safe ignore defaults into an existing `.cursorignore` string (read -> merge
186
+ * -> write). Preserves all user lines/comments verbatim and only appends default
187
+ * patterns that are not already present. Idempotent.
188
+ * @param {string} existingText
189
+ * @returns {string}
190
+ */
191
+ function mergeCursorIgnore(existingText) {
192
+ const text = typeof existingText === 'string' ? existingText : '';
193
+ const existingPatterns = new Set(
194
+ text.split(/\r?\n/).map(line => line.trim()).filter(line => line && !line.startsWith('#')),
195
+ );
196
+ const missing = CURSORIGNORE_DEFAULTS.filter(pattern => !existingPatterns.has(pattern));
197
+ const base = text.replace(/\s+$/, '');
198
+ if (missing.length === 0) {
199
+ return base ? `${base}\n` : '';
200
+ }
201
+ const lines = [];
202
+ if (base) {
203
+ lines.push(base, '');
204
+ }
205
+ // Only add the Forge header block if it isn't already present — otherwise a later
206
+ // re-run (once CURSORIGNORE_DEFAULTS gains a new entry) would duplicate the header.
207
+ if (!text.includes(CURSORIGNORE_HEADER[0])) {
208
+ lines.push(...CURSORIGNORE_HEADER);
209
+ }
210
+ lines.push(...missing);
211
+ return lines.join('\n') + '\n';
212
+ }
213
+
214
+ /**
215
+ * Render safe Claude permission defaults into `.claude/settings.json`.
216
+ * Read -> merge -> write. An unparseable existing file is BACKED UP and left
217
+ * untouched (never overwritten) to avoid destroying user settings.
218
+ * @param {object} params
219
+ * @param {string} params.targetRoot - Project root.
220
+ * @returns {{ file: string, existed: boolean, skipped: boolean, backup?: string }}
221
+ */
222
+ function renderClaudePermissions({ targetRoot }) {
223
+ const filePath = path.join(targetRoot, '.claude', 'settings.json');
224
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
225
+ const existed = fs.existsSync(filePath);
226
+ const existing = existed ? fs.readFileSync(filePath, 'utf-8') : '';
227
+
228
+ let merged;
229
+ try {
230
+ merged = mergeClaudePermissions(existing);
231
+ } catch (err) {
232
+ if (err instanceof SafetyConfigParseError && existed) {
233
+ const backup = backupFile(filePath);
234
+ return { file: filePath, existed, skipped: true, backup };
235
+ }
236
+ throw err;
237
+ }
238
+
239
+ fs.writeFileSync(filePath, merged, 'utf-8');
240
+ return { file: filePath, existed, skipped: false };
241
+ }
242
+
243
+ /**
244
+ * Render safe `.cursorignore` defaults at the project root.
245
+ * Read -> merge -> write, preserving user lines. Line-based, so there is no
246
+ * parse-failure path (never destructive).
247
+ * @param {object} params
248
+ * @param {string} params.targetRoot - Project root.
249
+ * @returns {{ file: string, existed: boolean, skipped: boolean }}
250
+ */
251
+ function renderCursorIgnore({ targetRoot }) {
252
+ const filePath = path.join(targetRoot, '.cursorignore');
253
+ const existed = fs.existsSync(filePath);
254
+ const existing = existed ? fs.readFileSync(filePath, 'utf-8') : '';
255
+ const merged = mergeCursorIgnore(existing);
256
+ fs.writeFileSync(filePath, merged, 'utf-8');
257
+ return { file: filePath, existed, skipped: false };
258
+ }
259
+
260
+ module.exports = {
261
+ SafetyConfigParseError,
262
+ CLAUDE_PERMISSION_DEFAULTS,
263
+ CURSORIGNORE_DEFAULTS,
264
+ mergeClaudePermissions,
265
+ mergeCursorIgnore,
266
+ renderClaudePermissions,
267
+ renderCursorIgnore,
268
+ };
@@ -11,15 +11,9 @@
11
11
  const AGENT_PREFIXES = {
12
12
  '.claude/': 'Claude Code',
13
13
  '.cursor/': 'Cursor',
14
- '.windsurf/': 'Windsurf',
15
- '.cline/': 'Cline',
16
14
  '.codex/': 'Codex',
17
15
  '~/.codex/': 'Codex',
18
- '$CODEX_HOME/': 'Codex',
19
- '.opencode/': 'OpenCode',
20
- '.kilocode/': 'Kilocode',
21
- '.roo/': 'Roo Code',
22
- '.github/prompts/': 'GitHub Copilot'
16
+ '$CODEX_HOME/': 'Codex'
23
17
  };
24
18
 
25
19
  /**