forge-workflow 0.0.9 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +466 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/reference/TOOLCHAIN.md +658 -0
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +66 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +22 -2
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +851 -979
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +697 -0
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,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
  /**
package/lib/setup.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const fs = require('node:fs');
2
2
  const path = require('node:path');
3
+ const { cleanupDeprecatedSyncFiles } = require('./deprecated-sync-cleanup');
3
4
 
4
5
  /**
5
6
  * Save setup state to .forge/setup-state.json
@@ -110,76 +111,36 @@ async function markStepComplete(projectPath, stepName) {
110
111
  }
111
112
 
112
113
  /**
113
- * Scaffold GitHub-Beads sync files into a target project.
114
- * Copies workflow, config, and mapping template — never overwrites existing files.
114
+ * Remove generated GitHub-Beads sync scaffold from existing installs.
115
+ *
116
+ * @param {string} projectPath - Target project root
117
+ * @returns {Promise<{removed: string[], skipped: string[]}>}
118
+ */
119
+ async function cleanupDeprecatedGithubBeadsSync(projectPath, options = {}) {
120
+ return cleanupDeprecatedSyncFiles(projectPath, options);
121
+ }
122
+
123
+ /**
124
+ * Deprecated compatibility shim for the removed GitHub-Beads sync scaffold.
125
+ *
126
+ * Performs cleanup of generated legacy sync artifacts only. User-owned files
127
+ * at matching paths are preserved unless their content matches known generated
128
+ * templates.
115
129
  *
116
130
  * @param {string} projectPath - Target project root
117
131
  * @param {string} pkgDir - Forge package directory (source of template files)
118
- * @returns {Promise<{created: string[], skipped: string[]}>}
132
+ * @returns {Promise<{created: string[], skipped: string[], removed: string[], deprecated: boolean, message: string}>}
119
133
  */
120
134
  async function scaffoldGithubBeadsSync(projectPath, pkgDir) {
121
- const created = [];
122
- const skipped = [];
123
-
124
- // Sync script modules that workflows depend on at runtime
125
- const syncScripts = [
126
- 'config.mjs', 'mapping.mjs', 'comment.mjs', 'github-api.mjs',
127
- 'sanitize.mjs', 'run-bd.mjs', 'label-mapper.mjs', 'index.mjs',
128
- 'reverse-sync.mjs', 'reverse-sync-cli.mjs',
129
- ];
130
-
131
- const files = [
132
- // Phase 1: GitHub → Beads
133
- {
134
- src: path.join(pkgDir, '.github', 'workflows', 'github-to-beads.yml'),
135
- dest: path.join('.github', 'workflows', 'github-to-beads.yml'),
136
- },
137
- // Phase 2: Beads → GitHub
138
- {
139
- src: path.join(pkgDir, '.github', 'workflows', 'beads-to-github.yml'),
140
- dest: path.join('.github', 'workflows', 'beads-to-github.yml'),
141
- },
142
- {
143
- src: path.join(pkgDir, 'scripts', 'github-beads-sync.config.json'),
144
- dest: path.join('scripts', 'github-beads-sync.config.json'),
145
- },
146
- {
147
- src: null, // generated in-place (empty mapping template)
148
- dest: path.join('.github', 'beads-mapping.json'),
149
- content: '{}',
150
- },
151
- // Sync script modules
152
- ...syncScripts.map((name) => ({
153
- src: path.join(pkgDir, 'scripts', 'github-beads-sync', name),
154
- dest: path.join('scripts', 'github-beads-sync', name),
155
- })),
156
- ];
157
-
158
- for (const file of files) {
159
- const destPath = path.join(projectPath, file.dest);
160
-
161
- // Never overwrite existing files — preserve user customizations
162
- if (fs.existsSync(destPath)) {
163
- skipped.push(file.dest);
164
- continue;
165
- }
166
-
167
- // Ensure parent directory exists
168
- const destDir = path.dirname(destPath);
169
- await fs.promises.mkdir(destDir, { recursive: true });
170
-
171
- if (file.src) {
172
- // Copy from template
173
- await fs.promises.copyFile(file.src, destPath);
174
- } else {
175
- // Write generated content
176
- await fs.promises.writeFile(destPath, file.content, 'utf-8');
177
- }
178
-
179
- created.push(file.dest);
180
- }
181
-
182
- return { created, skipped };
135
+ const cleanup = await cleanupDeprecatedGithubBeadsSync(projectPath, { packageDir: pkgDir });
136
+
137
+ return {
138
+ created: [],
139
+ skipped: [],
140
+ removed: cleanup.removed,
141
+ deprecated: true,
142
+ message: 'GitHub-Beads sync scaffolding is deprecated; future GitHub issue sync must use Forge Kernel/server authority.'
143
+ };
183
144
  }
184
145
 
185
146
  module.exports = {
@@ -188,5 +149,6 @@ module.exports = {
188
149
  isSetupComplete,
189
150
  getNextStep,
190
151
  markStepComplete,
152
+ cleanupDeprecatedGithubBeadsSync,
191
153
  scaffoldGithubBeadsSync
192
154
  };
@@ -5,10 +5,62 @@
5
5
  */
6
6
 
7
7
  const { execFileSync, spawnSync } = require('node:child_process');
8
+ const path = require('node:path');
9
+
10
+ // Extensions Windows can spawn directly via CreateProcess. Anything else that
11
+ // `where.exe` returns — the extensionless POSIX shim (`npm`) or the batch shim
12
+ // (`npm.cmd`) — cannot be handed to execFileSync: the former is a bash script
13
+ // (spawnSync ENOENT, kernel issue 9997d516) and patched Node refuses .cmd/.bat
14
+ // without shell:true (CVE-2024-27980).
15
+ const WINDOWS_DIRECT_SPAWN_EXTS = new Set(['.exe', '.com']);
16
+
17
+ // Conservative allowlist for tokens executed through cmd.exe. Blocks every cmd
18
+ // metacharacter (& | < > ^ % ! " ' ` ( ) ; and whitespace) so shell execution
19
+ // cannot be turned into injection even if a caller ever passes dynamic input.
20
+ const SAFE_SHELL_TOKEN = /^[A-Za-z0-9@_+=:,./\\-]+$/;
21
+
22
+ /**
23
+ * Throw when any token is unsafe to pass through cmd.exe.
24
+ * @param {string} command - The command name
25
+ * @param {string[]} args - Command arguments
26
+ */
27
+ function assertShellSafeTokens(command, args) {
28
+ for (const token of [command, ...args]) {
29
+ if (typeof token !== 'string' || !SAFE_SHELL_TOKEN.test(token)) {
30
+ throw new Error(
31
+ `Refusing to run "${command}" via shell: unsafe token ${JSON.stringify(token)}`
32
+ );
33
+ }
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Decide how to spawn a command on Windows from `where.exe` candidates.
39
+ * Prefers a directly spawnable .exe/.com — including when the caller already
40
+ * passed an absolute .exe/.com path (which `where.exe` may not re-enumerate);
41
+ * otherwise runs the BARE command name through cmd.exe (shell:true), which
42
+ * resolves npm.cmd/npx.cmd/lefthook.cmd via PATH/PATHEXT — so paths with spaces
43
+ * never hit shell parsing or the shell-safe token allowlist.
44
+ *
45
+ * @param {string} command - Original command name or absolute path
46
+ * @param {string[]} candidates - Resolved paths from `where.exe`, best first
47
+ * @returns {{ file: string, shell: boolean }}
48
+ */
49
+ function resolveWindowsSpawnSpec(command, candidates) {
50
+ const direct = [command, ...candidates].find(
51
+ (candidate) => WINDOWS_DIRECT_SPAWN_EXTS.has(path.extname(candidate).toLowerCase())
52
+ );
53
+ if (direct) {
54
+ return { file: direct, shell: false };
55
+ }
56
+ return { file: command, shell: true };
57
+ }
8
58
 
9
59
  /**
10
60
  * Securely execute a command with PATH validation.
11
61
  * Mitigates SonarCloud S4036: Ensures executables are from trusted locations.
62
+ * On Windows, handles npm/npx/lefthook-style cmd shims that cannot be spawned
63
+ * directly (see resolveWindowsSpawnSpec).
12
64
  * @param {string} command - The command to execute
13
65
  * @param {string[]} [args=[]] - Command arguments
14
66
  * @param {object} [options={}] - execFileSync options
@@ -18,16 +70,16 @@ function secureExecFileSync(command, args = [], options = {}) {
18
70
  const {
19
71
  _execFileSync = execFileSync,
20
72
  _spawnSync = spawnSync,
73
+ _platform = process.platform,
21
74
  ...execOptions
22
75
  } = options;
23
76
 
24
- let resolvedPath = null;
77
+ const isWindows = _platform === 'win32';
78
+ const pathResolver = isWindows ? 'where.exe' : 'which';
25
79
 
80
+ let candidates = [];
26
81
  try {
27
82
  // Resolve command's full path to validate it's in a trusted location
28
- const isWindows = process.platform === 'win32';
29
- const pathResolver = isWindows ? 'where.exe' : 'which';
30
-
31
83
  const result = _spawnSync(pathResolver, [command], {
32
84
  encoding: 'utf8',
33
85
  stdio: ['ignore', 'pipe', 'ignore']
@@ -35,14 +87,32 @@ function secureExecFileSync(command, args = [], options = {}) {
35
87
 
36
88
  if (result.status === 0 && result.stdout) {
37
89
  // Handle both CRLF (Windows) and LF (Unix) line endings
38
- resolvedPath = result.stdout.trim().split(/\r?\n/)[0].trim();
90
+ candidates = result.stdout
91
+ .trim()
92
+ .split(/\r?\n/)
93
+ .map((line) => line.trim())
94
+ .filter(Boolean);
39
95
  }
40
96
  } catch (_err) { // NOSONAR - S2486: Intentionally ignored; falls back to direct command execution below
41
97
  }
42
98
 
99
+ if (isWindows) {
100
+ const spec = resolveWindowsSpawnSpec(command, candidates);
101
+ if (spec.shell) {
102
+ assertShellSafeTokens(command, args);
103
+ // Node 22 (DEP0190) deprecates passing an args array together with shell:true.
104
+ // assertShellSafeTokens has already guaranteed every token is free of shell
105
+ // metacharacters, so fold them into a single command line and pass no args array —
106
+ // identical execution through cmd.exe (npm.cmd/npx.cmd shims), no deprecation warning.
107
+ const commandLine = [command, ...args].join(' ');
108
+ return _execFileSync(commandLine, [], { ...execOptions, shell: true });
109
+ }
110
+ return _execFileSync(spec.file, args, execOptions);
111
+ }
112
+
43
113
  // Fall back only when resolution failed. If execution of the resolved binary
44
114
  // throws, propagate that error instead of retrying with the unresolved name.
45
- return _execFileSync(resolvedPath || command, args, execOptions);
115
+ return _execFileSync(candidates[0] || command, args, execOptions);
46
116
  }
47
117
 
48
118
  module.exports = {