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
package/scripts/test.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Cross-platform test runner for the lefthook pre-push hook.
3
+ * Cross-platform test runner shared by the pre-push hook and local validation.
4
4
  *
5
- * Runs only the tests affected by the changes being pushed when possible.
6
- * Falls back to the full suite only for package-level changes.
5
+ * It runs only the tests affected by known changes when possible and falls back
6
+ * to the full suite for package-level or unknown-file changes.
7
7
  */
8
8
 
9
9
  const { execFileSync: defaultExecFileSync, spawnSync: defaultSpawnSync } = require('node:child_process');
@@ -22,8 +22,102 @@ const PACKAGE_LEVEL_PATHS = new Set([
22
22
  'package-lock.json',
23
23
  ]);
24
24
 
25
+ const KNOWN_TARGETABLE_PREFIXES = [
26
+ '.cursor/',
27
+ '.codex/',
28
+ '.forge/',
29
+ '.github/agentic-workflows/',
30
+ '.github/workflows/',
31
+ 'test/',
32
+ ];
33
+
34
+ const ALWAYS_RUN_RISK_TEST_TARGETS = [
35
+ // Windows + concurrent filesystem locking has failed post-merge; keep this
36
+ // in the fast PR lane until enough full-matrix runs prove it stable.
37
+ 'test/project-memory.test.js',
38
+ ];
39
+
25
40
  const isWindows = process.platform === 'win32';
26
41
 
42
+ // Wall-clock ceiling for a single spawned test lane. Bun's per-test `--timeout`
43
+ // races a JS timer and CANNOT preempt a synchronous blocking spawn (e.g. an
44
+ // `execFileSync` of git/bash that hangs during git mid-push state). Without this
45
+ // ceiling such a hang blocks `forge push` indefinitely (observed ~50 min on
46
+ // Windows, issue 8aef79e8). This kills the lane process so the push fails fast
47
+ // instead of hanging forever.
48
+ //
49
+ // The default is a practical 5 minutes for a single TARGETED lane: those run a
50
+ // small, mapped subset that completes in seconds, so 5 min is comfortably a
51
+ // fail-fast ceiling (not the old 15-min wait that made `forge push` feel
52
+ // wedged). Raise it with FORGE_TEST_TIMEOUT_MS for slow machines.
53
+ const DEFAULT_TEST_COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
54
+
55
+ // Wall-clock budget for the FULL-SUITE fallback lane (`scripts/test-full-suite.js`),
56
+ // which runs on package-level, unmapped, or zero-resolved changes. Unlike a
57
+ // targeted lane, a healthy full suite legitimately takes 5-10 min, so the 5-min
58
+ // fail-fast ceiling would kill a good-but-slow full run. Kept at 10 min to stay
59
+ // aligned with the local-validation budget (VALIDATION_COMMAND_TIMEOUT_MS = 600000
60
+ // in lib/commands/validate.js and its "long enough subprocess timeout for the
61
+ // full local suite" regression test). FORGE_TEST_TIMEOUT_MS still overrides.
62
+ const DEFAULT_FULL_SUITE_TIMEOUT_MS = 10 * 60 * 1000;
63
+
64
+ // Conventional shell exit code for a command terminated by a timeout.
65
+ const TIMEOUT_EXIT_CODE = 124;
66
+
67
+ /**
68
+ * Reads and validates the FORGE_TEST_TIMEOUT_MS override, if any.
69
+ *
70
+ * @param {NodeJS.ProcessEnv} env Environment to read the override from.
71
+ * @returns {number|null} The positive integer override, or null when unset/invalid.
72
+ */
73
+ function readTimeoutOverride(env) {
74
+ const parsed = Number.parseInt(env.FORGE_TEST_TIMEOUT_MS, 10);
75
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
76
+ }
77
+
78
+ /**
79
+ * Resolves the wall-clock timeout for a targeted/e2e/edge-case lane, honoring
80
+ * FORGE_TEST_TIMEOUT_MS.
81
+ *
82
+ * @param {NodeJS.ProcessEnv} [env=process.env] Environment to read the override from.
83
+ * @returns {number} Timeout in milliseconds (defaults to DEFAULT_TEST_COMMAND_TIMEOUT_MS).
84
+ */
85
+ function resolveCommandTimeoutMs(env = process.env) {
86
+ return readTimeoutOverride(env) ?? DEFAULT_TEST_COMMAND_TIMEOUT_MS;
87
+ }
88
+
89
+ /**
90
+ * Resolves the wall-clock budget for the full-suite fallback lane. An explicit
91
+ * FORGE_TEST_TIMEOUT_MS override wins; otherwise it uses the larger,
92
+ * validation-aligned budget so a healthy-but-slow full run is not failed fast.
93
+ *
94
+ * @param {NodeJS.ProcessEnv} [env=process.env] Environment to read the override from.
95
+ * @returns {number} Timeout in milliseconds (defaults to DEFAULT_FULL_SUITE_TIMEOUT_MS).
96
+ */
97
+ function resolveFullSuiteTimeoutMs(env = process.env) {
98
+ return readTimeoutOverride(env) ?? DEFAULT_FULL_SUITE_TIMEOUT_MS;
99
+ }
100
+
101
+ /**
102
+ * @typedef {Object} TestExecutionPlan
103
+ * @property {string[]} changedFiles
104
+ * @property {boolean} hasUnmappedFiles
105
+ * @property {boolean} hasUnknownChangedFiles
106
+ * @property {boolean} hasZeroResolvedTests
107
+ * @property {'targeted'|'full'} mode
108
+ * @property {string} reason
109
+ * @property {boolean} runE2E
110
+ * @property {boolean} runFullSuite
111
+ * @property {boolean} runTestEnv
112
+ * @property {boolean} runWorkflowTests
113
+ * @property {string[]} testTargets
114
+ */
115
+
116
+ /**
117
+ * Detects the package manager to use for running test commands in this checkout.
118
+ *
119
+ * @returns {'bun'|'pnpm'|'yarn'|'npm'} The package manager inferred from lockfiles.
120
+ */
27
121
  function detectPackageManager() {
28
122
  if (fs.existsSync('bun.lockb') || fs.existsSync('bun.lock')) return 'bun';
29
123
  if (fs.existsSync('pnpm-lock.yaml')) return 'pnpm';
@@ -31,6 +125,12 @@ function detectPackageManager() {
31
125
  return 'npm';
32
126
  }
33
127
 
128
+ /**
129
+ * Removes Git hook-only environment variables before spawning nested Git-aware commands.
130
+ *
131
+ * @param {NodeJS.ProcessEnv} [sourceEnv=process.env] Environment variables to sanitize.
132
+ * @returns {NodeJS.ProcessEnv} A copy of the environment without hook-specific Git variables.
133
+ */
34
134
  function stripGitHookEnv(sourceEnv = process.env) {
35
135
  const env = { ...sourceEnv };
36
136
  for (const key of Object.keys(env)) {
@@ -43,21 +143,90 @@ function stripGitHookEnv(sourceEnv = process.env) {
43
143
  return env;
44
144
  }
45
145
 
46
- function classifyPushTests(projectRoot, execFileSync = defaultExecFileSync) {
47
- const changedFiles = getChangedFiles(execFileSync, { sinceUpstream: true });
48
- const testTargets = getAffectedTestFiles(projectRoot, execFileSync, fs, { sinceUpstream: true });
146
+ /**
147
+ * Checks whether a changed path belongs to a known test-targetable area.
148
+ *
149
+ * @param {string} file Repository-relative changed file path.
150
+ * @returns {boolean} True when the path can be handled by targeted test selection.
151
+ */
152
+ function isKnownTargetablePath(file) {
153
+ if (file === '.gitignore') {
154
+ return true;
155
+ }
156
+
157
+ if (file === 'README.md'
158
+ || file === 'bin/forge.js'
159
+ || file === 'QUICKSTART.md'
160
+ || file === 'CHANGELOG.md'
161
+ || file === 'AGENTS.md'
162
+ || file === 'DEVELOPMENT.md'
163
+ || file === 'docs/INDEX.md'
164
+ || file === 'docs/PROJECT_DESIGN.md'
165
+ || file.startsWith('docs/forge/')
166
+ || file.startsWith('docs/guides/')
167
+ || file.startsWith('docs/plans/')
168
+ || file.startsWith('docs/reference/')
169
+ || file.startsWith('docs/work/')) {
170
+ return true;
171
+ }
172
+
173
+ return KNOWN_TARGETABLE_PREFIXES.some((prefix) => file.startsWith(prefix));
174
+ }
175
+
176
+ /**
177
+ * Determines whether the resolved test targets require workflow-specific validation.
178
+ *
179
+ * @param {string[]} testTargets Repository-relative test file paths.
180
+ * @returns {boolean} True when workflow tests are part of the target set.
181
+ */
182
+ function includesWorkflowTarget(testTargets) {
183
+ return testTargets.some((target) => target === 'test/ci-workflow.test.js'
184
+ || target === 'test/structural/agentic-workflow-sync.test.js'
185
+ || target.startsWith('test/workflows/'));
186
+ }
187
+
188
+ /**
189
+ * Deduplicates test targets while preserving the original execution order.
190
+ *
191
+ * @param {string[]} testTargets Repository-relative test file paths.
192
+ * @returns {string[]} Unique test targets in first-seen order.
193
+ */
194
+ function uniqueTestTargets(testTargets) {
195
+ return [...new Set(testTargets)];
196
+ }
197
+
198
+ function isExtraLaneOnlyPath(file) {
199
+ return file.startsWith('test/e2e/') || file.startsWith('test-env/');
200
+ }
201
+
202
+ /**
203
+ * Builds the execution plan used by PR, pre-push, and local validation test lanes.
204
+ *
205
+ * @param {string} projectRoot Absolute or relative repository root.
206
+ * @param {typeof defaultExecFileSync} [execFileSync=defaultExecFileSync] Command runner used to inspect Git state.
207
+ * @param {{sinceUpstream?: boolean}} [options={}] Test selection options.
208
+ * @returns {TestExecutionPlan} The computed test execution plan.
209
+ */
210
+ function buildTestExecutionPlan(projectRoot, execFileSync = defaultExecFileSync, options = {}) {
211
+ const diffOptions = {
212
+ sinceUpstream: options.sinceUpstream !== false,
213
+ };
214
+ const changedFiles = getChangedFiles(execFileSync, diffOptions);
215
+ const affectedTestTargets = getAffectedTestFiles(projectRoot, execFileSync, fs, diffOptions);
49
216
 
50
217
  let runFullSuite = false;
51
218
  let runTestEnv = false;
52
219
  let runE2E = false;
220
+ let runWorkflowTests = includesWorkflowTarget(affectedTestTargets);
53
221
  let hasUnmappedFiles = false;
54
- const hasUnknownChangedFiles = changedFiles.length === 0 && testTargets.length === 0;
222
+ const hasUnknownChangedFiles = changedFiles.length === 0 && affectedTestTargets.length === 0;
55
223
 
56
224
  for (const file of changedFiles) {
57
225
  if (PACKAGE_LEVEL_PATHS.has(file) || file.startsWith('packages/')) {
58
226
  runFullSuite = true;
59
227
  runTestEnv = true;
60
228
  runE2E = true;
229
+ runWorkflowTests = true;
61
230
  break;
62
231
  }
63
232
 
@@ -71,29 +240,80 @@ function classifyPushTests(projectRoot, execFileSync = defaultExecFileSync) {
71
240
  continue;
72
241
  }
73
242
 
74
- if ((file.startsWith('lib/') || file.startsWith('scripts/')) && file.endsWith('.js')) {
243
+ if ((file.startsWith('lib/') || file.startsWith('scripts/')) && (file.endsWith('.js') || file.endsWith('.sh'))) {
75
244
  runTestEnv = true;
245
+ if (file === 'scripts/behavioral-judge.sh') {
246
+ runWorkflowTests = true;
247
+ }
76
248
  continue;
77
249
  }
78
250
 
79
- if (file.startsWith('test/') || file.startsWith('.github/workflows/') || file.startsWith('.claude/commands/')) {
251
+ if (file.startsWith('.github/workflows/') || file.startsWith('.github/agentic-workflows/')) {
252
+ runWorkflowTests = true;
253
+ }
254
+
255
+ if (isKnownTargetablePath(file)) {
80
256
  continue;
81
257
  }
82
258
 
83
259
  hasUnmappedFiles = true;
84
260
  }
85
261
 
262
+ const hasOnlyExtraLaneChanges = changedFiles.length > 0
263
+ && changedFiles.every(isExtraLaneOnlyPath);
264
+ const hasZeroResolvedTests = changedFiles.length > 0
265
+ && affectedTestTargets.length === 0
266
+ && !runFullSuite
267
+ && !hasOnlyExtraLaneChanges;
268
+ const shouldRunFullSuite = runFullSuite || hasUnmappedFiles || hasUnknownChangedFiles || hasZeroResolvedTests;
269
+
270
+ const reason = hasUnmappedFiles
271
+ ? 'unmapped pushed files require full unit coverage'
272
+ : hasUnknownChangedFiles
273
+ ? 'changed files could not be resolved safely'
274
+ : hasZeroResolvedTests
275
+ ? 'known changes did not resolve runnable tests'
276
+ : runFullSuite
277
+ ? 'package-level changes detected'
278
+ : 'known changes mapped to targeted tests';
279
+
86
280
  return {
87
281
  changedFiles,
88
282
  hasUnmappedFiles,
89
283
  hasUnknownChangedFiles,
284
+ hasZeroResolvedTests,
285
+ mode: shouldRunFullSuite ? 'full' : 'targeted',
286
+ reason,
90
287
  runE2E,
91
- runFullSuite: runFullSuite || hasUnmappedFiles || hasUnknownChangedFiles,
288
+ runFullSuite: shouldRunFullSuite,
92
289
  runTestEnv,
93
- testTargets,
290
+ runWorkflowTests,
291
+ testTargets: shouldRunFullSuite
292
+ ? affectedTestTargets
293
+ : uniqueTestTargets([...affectedTestTargets, ...ALWAYS_RUN_RISK_TEST_TARGETS]),
94
294
  };
95
295
  }
96
296
 
297
+ /**
298
+ * Classifies the pushed changes into the test plan enforced by the pre-push hook.
299
+ *
300
+ * @param {string} projectRoot Absolute or relative repository root.
301
+ * @param {typeof defaultExecFileSync} [execFileSync=defaultExecFileSync] Command runner used to inspect Git state.
302
+ * @returns {TestExecutionPlan} The pre-push test execution plan.
303
+ */
304
+ function classifyPushTests(projectRoot, execFileSync = defaultExecFileSync) {
305
+ return buildTestExecutionPlan(projectRoot, execFileSync, { sinceUpstream: true });
306
+ }
307
+
308
+ /**
309
+ * Runs a command and returns its exit status, throwing only when process spawning fails.
310
+ *
311
+ * @param {string} command Executable name.
312
+ * @param {string[]} args Command arguments.
313
+ * @param {import('node:child_process').SpawnSyncOptions} [options={}] Spawn options.
314
+ * @param {typeof defaultSpawnSync} [spawnSync=defaultSpawnSync] Process runner.
315
+ * @returns {number} Process exit status, or 1 when no status is reported.
316
+ */
97
317
  function runCommand(command, args, options = {}, spawnSync = defaultSpawnSync) {
98
318
  const result = spawnSync(command, args, {
99
319
  stdio: 'inherit',
@@ -102,46 +322,63 @@ function runCommand(command, args, options = {}, spawnSync = defaultSpawnSync) {
102
322
  });
103
323
 
104
324
  if (result.error) {
325
+ if (result.error.code === 'ETIMEDOUT') {
326
+ console.error('');
327
+ console.error('Test lane exceeded its wall-clock ceiling and was terminated.');
328
+ console.error('A single test likely hung (e.g. a spawned git/bash call that never returns).');
329
+ console.error('Adjust the ceiling with FORGE_TEST_TIMEOUT_MS. Failing the run instead of');
330
+ console.error('blocking the push. See issue 8aef79e8.');
331
+ console.error('');
332
+ return TIMEOUT_EXIT_CODE;
333
+ }
105
334
  throw result.error;
106
335
  }
107
336
 
108
337
  return result.status ?? 1;
109
338
  }
110
339
 
111
- function runPrePushTests(projectRoot = process.cwd(), deps = {}) {
340
+ /**
341
+ * Executes a computed test plan and any extra targeted validation lanes.
342
+ *
343
+ * @param {TestExecutionPlan} plan Test plan to execute.
344
+ * @param {Object} [deps={}] Runtime dependencies for tests.
345
+ * @returns {number} Exit status for the executed plan.
346
+ */
347
+ function runTestExecutionPlan(plan, deps = {}) {
112
348
  const spawnSync = deps.spawnSync || defaultSpawnSync;
113
- const execFileSync = deps.execFileSync || defaultExecFileSync;
114
349
  const pkgManager = deps.pkgManager || detectPackageManager();
115
350
  const env = deps.env || stripGitHookEnv(process.env);
116
- const plan = classifyPushTests(projectRoot, execFileSync);
351
+ const bunCommand = deps.bunCommand || env.BUN_EXE || process.env.BUN_EXE || 'bun';
352
+ const label = deps.label || 'tests';
353
+ const timeout = resolveCommandTimeoutMs(env);
354
+ const laneOptions = { env, killSignal: 'SIGKILL', timeout };
355
+ // The full-suite fallback gets a larger, validation-aligned budget so a
356
+ // healthy-but-slow full run is not failed fast by the targeted-lane ceiling.
357
+ const fullSuiteOptions = { env, killSignal: 'SIGKILL', timeout: resolveFullSuiteTimeoutMs(env) };
117
358
 
118
- console.log(`Running pre-push tests (${pkgManager})...`);
359
+ console.log(`Running ${label} (${pkgManager})...`);
119
360
 
120
361
  try {
121
362
  if (plan.runFullSuite) {
122
- const reason = plan.hasUnmappedFiles
123
- ? 'unmapped pushed files require full unit coverage'
124
- : plan.hasUnknownChangedFiles
125
- ? 'changed files could not be resolved safely'
126
- : 'package-level changes detected';
127
- console.log(` Mode: full suite (${reason})`);
128
- const status = runCommand(pkgManager, ['run', 'test'], { env }, spawnSync);
363
+ console.log(` Mode: full suite (${plan.reason})`);
364
+ const status = runCommand('node', ['scripts/test-full-suite.js'], fullSuiteOptions, spawnSync);
129
365
  if (status !== 0) return status;
130
366
  } else if (plan.testTargets.length > 0) {
131
367
  console.log(` Mode: targeted (${plan.testTargets.length} test file${plan.testTargets.length === 1 ? '' : 's'})`);
132
- const status = runCommand(pkgManager, ['run', 'test', ...plan.testTargets], { env }, spawnSync);
368
+ const command = pkgManager === 'bun' ? bunCommand : pkgManager;
369
+ const status = runCommand(command, ['run', 'test', ...plan.testTargets], laneOptions, spawnSync);
133
370
  if (status !== 0) return status;
134
371
  }
135
372
 
136
373
  if (plan.runE2E) {
137
374
  console.log(' Extra: running affected e2e tests');
138
- const status = runCommand('bun', ['test', 'test/e2e/'], { env }, spawnSync);
375
+ const status = runCommand(bunCommand, ['test', '--timeout', '15000', 'test/e2e/'], laneOptions, spawnSync);
139
376
  if (status !== 0) return status;
140
377
  }
141
378
 
142
- if (plan.runTestEnv) {
379
+ if (!plan.runFullSuite && plan.runTestEnv) {
143
380
  console.log(' Extra: running affected edge-case tests');
144
- const status = runCommand('bun', ['test', 'test-env/'], { env }, spawnSync);
381
+ const status = runCommand(bunCommand, ['test', '--timeout', '15000', 'test-env/'], laneOptions, spawnSync);
145
382
  if (status !== 0) return status;
146
383
  }
147
384
 
@@ -149,19 +386,56 @@ function runPrePushTests(projectRoot = process.cwd(), deps = {}) {
149
386
  return 0;
150
387
  } catch (error) {
151
388
  console.error('');
152
- console.error(`Failed to run tests: ${error.message}`);
389
+ console.error(`Failed to run ${label}: ${error.message}`);
153
390
  console.error('');
154
391
  return 1;
155
392
  }
156
393
  }
157
394
 
395
+ /**
396
+ * Runs the pre-push test plan for the current checkout.
397
+ *
398
+ * @param {string} [projectRoot=process.cwd()] Repository root.
399
+ * @param {Object} [deps={}] Runtime dependencies for tests.
400
+ * @returns {number} Exit status for pre-push tests.
401
+ */
402
+ function runPrePushTests(projectRoot = process.cwd(), deps = {}) {
403
+ const execFileSync = deps.execFileSync || defaultExecFileSync;
404
+ const plan = classifyPushTests(projectRoot, execFileSync);
405
+ return runTestExecutionPlan(plan, { ...deps, label: 'pre-push tests' });
406
+ }
407
+
408
+ /**
409
+ * Runs the local validation test plan for the current checkout.
410
+ *
411
+ * @param {string} [projectRoot=process.cwd()] Repository root.
412
+ * @param {Object} [deps={}] Runtime dependencies for tests.
413
+ * @returns {number} Exit status for local validation tests.
414
+ */
415
+ function runLocalValidationTests(projectRoot = process.cwd(), deps = {}) {
416
+ const execFileSync = deps.execFileSync || defaultExecFileSync;
417
+ const plan = buildTestExecutionPlan(projectRoot, execFileSync, { sinceUpstream: true });
418
+ return runTestExecutionPlan(plan, { ...deps, label: 'local validation tests' });
419
+ }
420
+
158
421
  if (require.main === module) {
159
- process.exit(runPrePushTests());
422
+ const exitCode = process.argv.includes('--validate')
423
+ ? runLocalValidationTests()
424
+ : runPrePushTests();
425
+ process.exit(exitCode);
160
426
  }
161
427
 
162
428
  module.exports = {
429
+ ALWAYS_RUN_RISK_TEST_TARGETS,
430
+ DEFAULT_FULL_SUITE_TIMEOUT_MS,
431
+ DEFAULT_TEST_COMMAND_TIMEOUT_MS,
432
+ buildTestExecutionPlan,
163
433
  classifyPushTests,
164
434
  detectPackageManager,
435
+ resolveCommandTimeoutMs,
436
+ resolveFullSuiteTimeoutMs,
437
+ runLocalValidationTests,
165
438
  runPrePushTests,
439
+ runTestExecutionPlan,
166
440
  stripGitHookEnv,
167
441
  };
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawnSync } = require('node:child_process');
5
+
6
+ const bunCommand = process.env.BUN_EXE || 'bun';
7
+
8
+ function color(code, text) {
9
+ return process.stdout.isTTY ? `\u001b[${code}m${text}\u001b[0m` : text;
10
+ }
11
+
12
+ function printHeader(title) {
13
+ const line = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
14
+ console.log('');
15
+ console.log(color('0;34', line));
16
+ console.log(color('0;34', `▶ ${title}`));
17
+ console.log(color('0;34', line));
18
+ }
19
+
20
+ function printStatus(kind, message) {
21
+ const palette = {
22
+ error: ['0;31', '✗'],
23
+ success: ['0;32', '✓'],
24
+ warning: ['1;33', '⚠'],
25
+ };
26
+ const [code, prefix] = palette[kind];
27
+ console.log(color(code, `${prefix} ${message}`));
28
+ }
29
+
30
+ function run(command, args, options = {}) {
31
+ const result = spawnSync(command, args, {
32
+ cwd: process.cwd(),
33
+ encoding: 'utf8',
34
+ stdio: options.captureOutput ? ['inherit', 'pipe', 'pipe'] : 'inherit',
35
+ shell: false,
36
+ env: options.env || process.env,
37
+ });
38
+
39
+ if (result.error) {
40
+ throw result.error;
41
+ }
42
+
43
+ return result;
44
+ }
45
+
46
+ function combinedOutput(result) {
47
+ return [result.stdout, result.stderr].filter(Boolean).join('');
48
+ }
49
+
50
+ function hasBlockingAuditSeverity(output = '') {
51
+ if (typeof output !== 'string' || output.length === 0) {
52
+ return false;
53
+ }
54
+
55
+ return [
56
+ /\b(?:critical|high)\s+severity\b/i,
57
+ /\bseverity\s*:?\s*(?:critical|high)\b/i,
58
+ ].some((pattern) => pattern.test(output));
59
+ }
60
+
61
+ function main(options = {}) {
62
+ const runCommand = options.runCommand ?? run;
63
+ const activeBunCommand = options.bunCommand ?? bunCommand;
64
+ console.log('');
65
+ console.log(color('0;34', '╔═══════════════════════════════════════════╗'));
66
+ console.log(color('0;34', '║ Forge Quality Gate - Running Checks ║'));
67
+ console.log(color('0;34', '╚═══════════════════════════════════════════╝'));
68
+
69
+ printHeader('1/4: Type Check');
70
+ const typecheck = runCommand(activeBunCommand, ['run', 'typecheck'], { captureOutput: true });
71
+ const typecheckOutput = combinedOutput(typecheck);
72
+ if (typecheckOutput) {
73
+ process.stdout.write(typecheckOutput);
74
+ if (!typecheckOutput.endsWith('\n')) {
75
+ process.stdout.write('\n');
76
+ }
77
+ }
78
+ if ((typecheck.status ?? 1) !== 0) {
79
+ printStatus('error', 'Type check failed');
80
+ return 1;
81
+ }
82
+ if (/no typescript in project|skipping type check/i.test(typecheckOutput)) {
83
+ printStatus('warning', 'SKIPPED (no TypeScript in project)');
84
+ } else {
85
+ printStatus('success', 'Type check passed');
86
+ }
87
+
88
+ printHeader('2/4: Lint');
89
+ const lint = runCommand(activeBunCommand, ['run', 'lint']);
90
+ if ((lint.status ?? 1) !== 0) {
91
+ printStatus('error', 'Lint failed');
92
+ return 1;
93
+ }
94
+ printStatus('success', 'Lint passed');
95
+
96
+ printHeader('3/4: Security Audit');
97
+ const audit = runCommand(activeBunCommand, ['audit'], { captureOutput: true });
98
+ const auditOutput = combinedOutput(audit);
99
+ if (auditOutput) {
100
+ process.stdout.write(auditOutput);
101
+ if (!auditOutput.endsWith('\n')) {
102
+ process.stdout.write('\n');
103
+ }
104
+ }
105
+ if (hasBlockingAuditSeverity(auditOutput)) {
106
+ printStatus('error', 'Security audit found critical/high vulnerabilities');
107
+ return 1;
108
+ }
109
+ if ((audit.status ?? 1) === 0) {
110
+ printStatus('success', 'Security audit passed');
111
+ } else {
112
+ printStatus('warning', 'Security audit found issues (moderate/low — non-blocking)');
113
+ }
114
+
115
+ printHeader('4/4: Tests');
116
+ const tests = runCommand('node', ['scripts/test.js', '--validate']);
117
+ if ((tests.status ?? 1) !== 0) {
118
+ printStatus('error', 'Tests failed');
119
+ return 1;
120
+ }
121
+ printStatus('success', 'All tests passed');
122
+
123
+ console.log('');
124
+ console.log(color('0;32', '╔═══════════════════════════════════════════╗'));
125
+ console.log(color('0;32', '║ ✓ All Checks Passed Successfully ║'));
126
+ console.log(color('0;32', '╚═══════════════════════════════════════════╝'));
127
+ console.log('');
128
+ return 0;
129
+ }
130
+
131
+ if (require.main === module) {
132
+ process.exit(main());
133
+ }
134
+
135
+ module.exports = {
136
+ color,
137
+ combinedOutput,
138
+ hasBlockingAuditSeverity,
139
+ main,
140
+ printHeader,
141
+ printStatus,
142
+ run,
143
+ };
@@ -44,6 +44,23 @@ print_warning() {
44
44
  echo -e "${YELLOW}⚠ $1${NC}"
45
45
  }
46
46
 
47
+ run_validate_tests() {
48
+ if [[ "${OS:-}" == "Windows_NT" && ( -n "${MSYSTEM:-}" || "${OSTYPE:-}" == msys* || "${OSTYPE:-}" == cygwin* ) ]]; then
49
+ local bun_exe
50
+ bun_exe="$(where.exe bun 2>/dev/null | tr -d '\r' | grep -i 'bun\.exe$' | head -n 1)"
51
+
52
+ if [[ -n "$bun_exe" ]]; then
53
+ cmd.exe /d /c "set \"BUN_EXE=$bun_exe\" && node scripts\\test.js --validate"
54
+ return $?
55
+ fi
56
+
57
+ cmd.exe /d /c "node scripts\\test.js --validate"
58
+ return $?
59
+ fi
60
+
61
+ node scripts/test.js --validate
62
+ }
63
+
47
64
  echo ""
48
65
  echo -e "${BLUE}╔═══════════════════════════════════════════╗${NC}"
49
66
  echo -e "${BLUE}║ Forge Quality Gate - Running Checks ║${NC}"
@@ -78,7 +95,7 @@ fi
78
95
 
79
96
  # Step 4: Tests
80
97
  print_header "4/4: Tests"
81
- if bun test; then
98
+ if run_validate_tests; then
82
99
  print_success "All tests passed"
83
100
  else
84
101
  print_error "Tests failed"