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
@@ -66,10 +66,25 @@ else
66
66
  INPUT="$(cat)"
67
67
  fi
68
68
 
69
- # Extract plan_output text if input is JSON, otherwise use raw input
70
- PLAN_TEXT="$INPUT"
71
- if command -v python3 >/dev/null 2>&1; then
72
- EXTRACTED=$(echo "$INPUT" | python3 -c "
69
+ extract_plan_text() {
70
+ if command -v node >/dev/null 2>&1; then
71
+ node -e "
72
+ let input = '';
73
+ process.stdin.setEncoding('utf8');
74
+ process.stdin.on('data', chunk => { input += chunk; });
75
+ process.stdin.on('end', () => {
76
+ try {
77
+ const parsed = JSON.parse(input);
78
+ const value = parsed && parsed.plan_output;
79
+ if (value) process.stdout.write(String(value));
80
+ } catch (_) {}
81
+ });
82
+ " 2>/dev/null
83
+ return $?
84
+ fi
85
+
86
+ if command -v python3 >/dev/null 2>&1; then
87
+ python3 -c "
73
88
  import sys, json
74
89
  try:
75
90
  d = json.load(sys.stdin)
@@ -78,10 +93,16 @@ try:
78
93
  print(val)
79
94
  except:
80
95
  pass
81
- " 2>/dev/null) || true
82
- if [ -n "$EXTRACTED" ]; then
83
- PLAN_TEXT="$EXTRACTED"
96
+ " 2>/dev/null
97
+ return $?
84
98
  fi
99
+ }
100
+
101
+ # Extract plan_output text if input is JSON, otherwise use raw input
102
+ PLAN_TEXT="$INPUT"
103
+ EXTRACTED=$(printf '%s' "$INPUT" | extract_plan_text) || true
104
+ if [ -n "$EXTRACTED" ]; then
105
+ PLAN_TEXT="$EXTRACTED"
85
106
  fi
86
107
 
87
108
  # ─── Judge prompt ────────────────────────────────────────────────────────────
@@ -158,7 +179,22 @@ parse_scores() {
158
179
  local response="$1"
159
180
  # Extract the JSON object from the LLM response content
160
181
  local content
161
- content=$(echo "$response" | python3 -c "
182
+ if command -v node >/dev/null 2>&1; then
183
+ content=$(printf '%s' "$response" | node -e "
184
+ let input = '';
185
+ process.stdin.setEncoding('utf8');
186
+ process.stdin.on('data', chunk => { input += chunk; });
187
+ process.stdin.on('end', () => {
188
+ try {
189
+ const data = JSON.parse(input);
190
+ const choice = (data.choices || [])[0] || {};
191
+ const message = choice.message || {};
192
+ process.stdout.write(String(message.content || ''));
193
+ } catch (_) {}
194
+ });
195
+ " 2>/dev/null) || true
196
+ elif command -v python3 >/dev/null 2>&1; then
197
+ content=$(printf '%s' "$response" | python3 -c "
162
198
  import sys, json
163
199
  try:
164
200
  data = json.load(sys.stdin)
@@ -170,6 +206,9 @@ try:
170
206
  except Exception as e:
171
207
  pass
172
208
  " 2>/dev/null) || true
209
+ else
210
+ content=""
211
+ fi
173
212
 
174
213
  if [ -z "$content" ]; then
175
214
  echo ""
@@ -178,7 +217,37 @@ except Exception as e:
178
217
 
179
218
  # Parse scores from content JSON
180
219
  local scores
181
- scores=$(echo "$content" | python3 -c "
220
+ if command -v node >/dev/null 2>&1; then
221
+ scores=$(printf '%s' "$content" | node -e "
222
+ let text = '';
223
+ process.stdin.setEncoding('utf8');
224
+ process.stdin.on('data', chunk => { text += chunk; });
225
+ process.stdin.on('end', () => {
226
+ function emit(value) {
227
+ const security = Number.parseInt(value.security, 10);
228
+ const tdd = Number.parseInt(value.tdd, 10);
229
+ const design = Number.parseInt(value.design, 10);
230
+ const structural = Number.parseInt(value.structural, 10);
231
+ if ([security, tdd, design, structural].every(score => Number.isInteger(score) && score >= 0 && score <= 5)) {
232
+ console.log([security, tdd, design, structural].join(' '));
233
+ return true;
234
+ }
235
+ return false;
236
+ }
237
+ try {
238
+ if (emit(JSON.parse(text.trim()))) return;
239
+ } catch (_) {}
240
+ const match = text.match(/\{[^}]+\}/s);
241
+ if (match) {
242
+ try {
243
+ if (emit(JSON.parse(match[0]))) return;
244
+ } catch (_) {}
245
+ }
246
+ process.exit(1);
247
+ });
248
+ " 2>/dev/null) || { echo ""; return 1; }
249
+ elif command -v python3 >/dev/null 2>&1; then
250
+ scores=$(printf '%s' "$content" | python3 -c "
182
251
  import sys, json, re
183
252
  text = sys.stdin.read().strip()
184
253
  # Try direct JSON parse first
@@ -209,6 +278,10 @@ if m:
209
278
  pass
210
279
  sys.exit(1)
211
280
  " 2>/dev/null) || { echo ""; return 1; }
281
+ else
282
+ echo ""
283
+ return 1
284
+ fi
212
285
 
213
286
  echo "$scores"
214
287
  }
@@ -221,10 +294,23 @@ call_openrouter() {
221
294
 
222
295
  # Escape prompt for JSON
223
296
  local escaped_prompt
224
- escaped_prompt=$(echo "$prompt" | python3 -c "
297
+ if command -v node >/dev/null 2>&1; then
298
+ escaped_prompt=$(printf '%s' "$prompt" | node -e "
299
+ let input = '';
300
+ process.stdin.setEncoding('utf8');
301
+ process.stdin.on('data', chunk => { input += chunk; });
302
+ process.stdin.on('end', () => {
303
+ process.stdout.write(JSON.stringify(input));
304
+ });
305
+ " 2>/dev/null) || escaped_prompt='""'
306
+ elif command -v python3 >/dev/null 2>&1; then
307
+ escaped_prompt=$(printf '%s' "$prompt" | python3 -c "
225
308
  import sys, json
226
309
  print(json.dumps(sys.stdin.read()))
227
310
  " 2>/dev/null) || escaped_prompt='""'
311
+ else
312
+ escaped_prompt='""'
313
+ fi
228
314
 
229
315
  local body
230
316
  body=$(cat <<JSON
@@ -295,7 +381,22 @@ if [ "${BEHAVIORAL_JUDGE_TEST_MODE}" = "1" ]; then
295
381
  fi
296
382
 
297
383
  # Parse mock scores
298
- scores=$(echo "$mock_scores" | python3 -c "
384
+ if command -v node >/dev/null 2>&1; then
385
+ scores=$(printf '%s' "$mock_scores" | node -e "
386
+ let input = '';
387
+ process.stdin.setEncoding('utf8');
388
+ process.stdin.on('data', chunk => { input += chunk; });
389
+ process.stdin.on('end', () => {
390
+ const scores = JSON.parse(input);
391
+ const security = Number.parseInt(scores.security ?? 3, 10);
392
+ const tdd = Number.parseInt(scores.tdd ?? 3, 10);
393
+ const design = Number.parseInt(scores.design ?? 3, 10);
394
+ const structural = Number.parseInt(scores.structural ?? 3, 10);
395
+ console.log([security, tdd, design, structural].join(' '));
396
+ });
397
+ " 2>/dev/null) || scores="3 3 3 3"
398
+ elif command -v python3 >/dev/null 2>&1; then
399
+ scores=$(printf '%s' "$mock_scores" | python3 -c "
299
400
  import sys, json
300
401
  d = json.loads(sys.stdin.read())
301
402
  s = int(d.get('security', 3))
@@ -304,6 +405,9 @@ de = int(d.get('design', 3))
304
405
  st = int(d.get('structural', 3))
305
406
  print(f'{s} {t} {de} {st}')
306
407
  " 2>/dev/null) || scores="3 3 3 3"
408
+ else
409
+ scores="3 3 3 3"
410
+ fi
307
411
 
308
412
  read -r sec tdd_s des str <<< "$scores"
309
413
  compute_result "$sec" "$tdd_s" "$des" "$str" "$judge_model" "$judge_calls"
@@ -1,85 +1,371 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /**
4
- * Performance benchmark script for Forge CLI.
5
- * Measures CLI startup time, autoDetect, and detectFramework performance.
6
- *
7
- * Usage:
8
- * node scripts/benchmark.js # Human-readable output
9
- * node scripts/benchmark.js --json # JSON output (for CI/tests)
10
- */
11
-
12
- const { execFileSync } = require('node:child_process');
13
- const { performance } = require('node:perf_hooks');
14
- const path = require('node:path');
3
+ const { spawnSync: defaultSpawnSync } = require('node:child_process');
15
4
  const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { performance } = require('node:perf_hooks');
7
+
8
+ const { buildProfile, parseJUnitFiles, walk } = require('./test-profile');
16
9
 
17
10
  const rootDir = path.join(__dirname, '..');
18
- const ITERATIONS = 3;
11
+ const DEFAULT_OUTPUT = path.join(rootDir, 'test-results', 'benchmark-results.json');
12
+ const DEFAULT_PROFILE_DIR = path.join(rootDir, 'test-results', 'benchmark-profiles');
13
+ const DEFAULT_SAMPLES = 3;
14
+ const DEFAULT_JUNIT_PATH = path.join(rootDir, 'test-results', 'test-results.xml');
19
15
 
20
- function benchmarkCLIStartup() {
21
- const times = [];
22
- for (let i = 0; i < ITERATIONS; i++) {
23
- const start = performance.now();
24
- execFileSync('node', [path.join(rootDir, 'bin', 'forge.js'), '--help'], {
25
- cwd: rootDir,
26
- encoding: 'utf-8',
27
- timeout: 10000,
28
- stdio: 'pipe'
29
- });
30
- times.push(performance.now() - start);
16
+ const BENCHMARK_GROUPS = [
17
+ {
18
+ id: 'full-suite',
19
+ label: 'Whole suite',
20
+ command: ['bun', 'test'],
21
+ },
22
+ {
23
+ id: 'pre-push-runner',
24
+ label: 'Pre-push runner slice',
25
+ command: ['bun', 'test', 'test/scripts/test-runner.test.js'],
26
+ },
27
+ {
28
+ id: 'validation-core',
29
+ label: 'Validation core slice',
30
+ command: [
31
+ 'bun',
32
+ 'test',
33
+ 'test/scripts/test-profile.test.js',
34
+ 'test/scripts/test-ci-shard.test.js',
35
+ 'test/scripts/test-runner.test.js',
36
+ ],
37
+ },
38
+ {
39
+ id: 'hotspot-shell',
40
+ label: 'Hotspot shell slice',
41
+ command: [
42
+ 'bun',
43
+ 'test',
44
+ 'test/scripts/smart-status.conflicts.files.test.js',
45
+ 'test/scripts/smart-status.conflicts.merge-tree.test.js',
46
+ 'test/scripts/dep-guard.check-ripple.basic.test.js',
47
+ ],
48
+ },
49
+ {
50
+ id: 'validate',
51
+ label: 'Validate command',
52
+ command: ['bash', 'scripts/validate.sh'],
53
+ },
54
+ ];
55
+
56
+ function parseArgs(argv) {
57
+ const args = {
58
+ groups: [],
59
+ json: false,
60
+ output: DEFAULT_OUTPUT,
61
+ profileDir: DEFAULT_PROFILE_DIR,
62
+ samples: DEFAULT_SAMPLES,
63
+ };
64
+
65
+ for (let index = 0; index < argv.length; index += 1) {
66
+ const current = argv[index];
67
+ const next = argv[index + 1];
68
+
69
+ if (current === '--json') args.json = true;
70
+ if (current === '--output') args.output = next;
71
+ if (current === '--profile-dir') args.profileDir = next;
72
+ if (current === '--samples') args.samples = Number.parseInt(next, 10) || DEFAULT_SAMPLES;
73
+ if (current === '--group') args.groups.push(next);
31
74
  }
32
- const mean = times.reduce((a, b) => a + b, 0) / times.length;
33
- return { name: 'CLI startup (--help)', mean: Math.round(mean), unit: 'ms', samples: ITERATIONS };
75
+
76
+ return args;
34
77
  }
35
78
 
36
- function benchmarkAutoDetect() {
37
- const { autoDetect } = require(path.join(rootDir, 'lib', 'project-discovery.js'));
38
- const times = [];
39
- for (let i = 0; i < ITERATIONS; i++) {
40
- const start = performance.now();
41
- autoDetect(rootDir);
42
- times.push(performance.now() - start);
79
+ function resolveGroups(groupIds = []) {
80
+ if (groupIds.length === 0) {
81
+ return BENCHMARK_GROUPS.map((group) => ({ ...group, command: [...group.command] }));
82
+ }
83
+
84
+ const byId = new Map(BENCHMARK_GROUPS.map((group) => [group.id, group]));
85
+ const resolved = [];
86
+ for (const id of groupIds) {
87
+ const match = byId.get(id);
88
+ if (!match) {
89
+ throw new Error(`Unknown benchmark group: ${id}`);
90
+ }
91
+ resolved.push({ ...match, command: [...match.command] });
92
+ }
93
+ return resolved;
94
+ }
95
+
96
+ function roundMs(value) {
97
+ return Math.round(value);
98
+ }
99
+
100
+ function calculateMedian(values) {
101
+ if (values.length === 0) return 0;
102
+ const sorted = [...values].sort((left, right) => left - right);
103
+ const middle = Math.floor(sorted.length / 2);
104
+ if (sorted.length % 2 === 0) {
105
+ return roundMs((sorted[middle - 1] + sorted[middle]) / 2);
106
+ }
107
+ return sorted[middle];
108
+ }
109
+
110
+ function summarizeSamples(samplesMs) {
111
+ if (samplesMs.length === 0) {
112
+ return {
113
+ maxMs: 0,
114
+ meanMs: 0,
115
+ medianMs: 0,
116
+ minMs: 0,
117
+ samplesMs: [],
118
+ };
119
+ }
120
+ const total = samplesMs.reduce((sum, value) => sum + value, 0);
121
+ return {
122
+ maxMs: Math.max(...samplesMs),
123
+ meanMs: roundMs(total / samplesMs.length),
124
+ medianMs: calculateMedian(samplesMs),
125
+ minMs: Math.min(...samplesMs),
126
+ samplesMs,
127
+ };
128
+ }
129
+
130
+ function buildJUnitCommand(command, junitPath) {
131
+ const [binary, subcommand, ...rest] = command;
132
+ if (binary === 'bun' && subcommand === 'test') {
133
+ return [
134
+ binary,
135
+ subcommand,
136
+ ...rest,
137
+ '--reporter=junit',
138
+ '--reporter-outfile',
139
+ junitPath,
140
+ ];
141
+ }
142
+ return [...command];
143
+ }
144
+
145
+ function runCommand(command, options = {}, spawnSync = defaultSpawnSync) {
146
+ const [binary, ...args] = command;
147
+ const result = spawnSync(binary, args, {
148
+ cwd: rootDir,
149
+ encoding: 'utf8',
150
+ stdio: 'pipe',
151
+ ...options,
152
+ });
153
+
154
+ if (result.error) {
155
+ throw result.error;
156
+ }
157
+
158
+ if ((result.status ?? 1) !== 0) {
159
+ throw new Error(result.stderr || `Command failed: ${command.join(' ')}`);
160
+ }
161
+
162
+ return result;
163
+ }
164
+
165
+ function materializeJUnitFile(expectedPath) {
166
+ if (fs.existsSync(expectedPath)) {
167
+ return expectedPath;
43
168
  }
44
- const mean = times.reduce((a, b) => a + b, 0) / times.length;
45
- return { name: 'autoDetect()', mean: Math.round(mean), unit: 'ms', samples: ITERATIONS };
169
+
170
+ if (fs.existsSync(DEFAULT_JUNIT_PATH)) {
171
+ fs.mkdirSync(path.dirname(expectedPath), { recursive: true });
172
+ fs.copyFileSync(DEFAULT_JUNIT_PATH, expectedPath);
173
+ return expectedPath;
174
+ }
175
+
176
+ throw new Error(`Benchmark run did not produce JUnit output at ${expectedPath}`);
177
+ }
178
+
179
+ function removeMaterializedOutputs(expectedPath) {
180
+ const directory = path.dirname(expectedPath);
181
+ const extension = path.extname(expectedPath);
182
+ const basename = path.basename(expectedPath, extension);
183
+
184
+ if (!fs.existsSync(directory)) {
185
+ return;
186
+ }
187
+
188
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
189
+ if (!entry.isFile() || !entry.name.endsWith(extension)) {
190
+ continue;
191
+ }
192
+
193
+ if (entry.name === `${basename}${extension}` || entry.name.startsWith(`${basename}.`)) {
194
+ fs.rmSync(path.join(directory, entry.name), { force: true });
195
+ }
196
+ }
197
+ }
198
+
199
+ function indexXmlFiles(directory) {
200
+ return new Map(
201
+ walk(directory, '.xml').map((filePath) => {
202
+ const stats = fs.statSync(filePath);
203
+ return [filePath, `${stats.size}:${stats.mtimeMs}`];
204
+ }),
205
+ );
206
+ }
207
+
208
+ function listChangedXmlFiles(directory, beforeIndex) {
209
+ return walk(directory, '.xml')
210
+ .filter((filePath) => {
211
+ const stats = fs.statSync(filePath);
212
+ const fingerprint = `${stats.size}:${stats.mtimeMs}`;
213
+ return beforeIndex.get(filePath) !== fingerprint;
214
+ })
215
+ .sort((left, right) => left.localeCompare(right));
46
216
  }
47
217
 
48
- function benchmarkDetectFramework() {
49
- const { detectFramework } = require(path.join(rootDir, 'lib', 'project-discovery.js'));
50
- const times = [];
51
- for (let i = 0; i < ITERATIONS; i++) {
218
+ function materializeJUnitFiles(expectedPath, sourceFiles) {
219
+ const uniqueSources = [];
220
+ const seen = new Set();
221
+
222
+ for (const sourceFile of sourceFiles) {
223
+ if (typeof sourceFile !== 'string' || seen.has(sourceFile) || !fs.existsSync(sourceFile)) {
224
+ continue;
225
+ }
226
+ seen.add(sourceFile);
227
+ uniqueSources.push(sourceFile);
228
+ }
229
+
230
+ if (uniqueSources.length === 0) {
231
+ return [materializeJUnitFile(expectedPath)];
232
+ }
233
+
234
+ return uniqueSources.map((sourceFile, index) => {
235
+ const destination = index === 0
236
+ ? expectedPath
237
+ : expectedPath.replace(/\.xml$/, `.${index + 1}.xml`);
238
+
239
+ if (sourceFile === destination) {
240
+ return destination;
241
+ }
242
+
243
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
244
+ fs.copyFileSync(sourceFile, destination);
245
+ return destination;
246
+ });
247
+ }
248
+
249
+ function buildGroupProfile(group, xmlFiles) {
250
+ const metrics = parseJUnitFiles(xmlFiles);
251
+ return buildProfile({
252
+ integrationSkipped: true,
253
+ label: `benchmark-${group.id}`,
254
+ }, metrics);
255
+ }
256
+
257
+ function runBenchmarkGroup(group, options = {}) {
258
+ const profileDir = path.resolve(rootDir, options.profileDir || DEFAULT_PROFILE_DIR);
259
+ const samples = options.samples || DEFAULT_SAMPLES;
260
+ const spawnSync = options.spawnSync || defaultSpawnSync;
261
+
262
+ fs.mkdirSync(profileDir, { recursive: true });
263
+
264
+ const xmlFiles = [];
265
+ const samplesMs = [];
266
+
267
+ for (let sampleIndex = 0; sampleIndex < samples; sampleIndex += 1) {
268
+ const junitPath = path.join(profileDir, `${group.id}.sample-${sampleIndex + 1}.xml`);
269
+ removeMaterializedOutputs(junitPath);
270
+ fs.rmSync(DEFAULT_JUNIT_PATH, { force: true });
271
+ const beforeXmlFiles = indexXmlFiles(path.join(rootDir, 'test-results'));
272
+ const command = buildJUnitCommand(group.command, junitPath);
52
273
  const start = performance.now();
53
- detectFramework(rootDir);
54
- times.push(performance.now() - start);
274
+ runCommand(command, {}, spawnSync);
275
+ samplesMs.push(roundMs(performance.now() - start));
276
+ const changedXmlFiles = listChangedXmlFiles(path.join(rootDir, 'test-results'), beforeXmlFiles);
277
+ xmlFiles.push(...materializeJUnitFiles(junitPath, [
278
+ fs.existsSync(junitPath) ? junitPath : null,
279
+ ...changedXmlFiles,
280
+ ]));
55
281
  }
56
- const mean = times.reduce((a, b) => a + b, 0) / times.length;
57
- return { name: 'detectFramework()', mean: Math.round(mean), unit: 'ms', samples: ITERATIONS };
282
+
283
+ const summary = summarizeSamples(samplesMs);
284
+ const profile = buildGroupProfile(group, xmlFiles);
285
+ const profilePath = path.join(profileDir, `${group.id}.profile.json`);
286
+ fs.writeFileSync(profilePath, JSON.stringify(profile, null, 2));
287
+
288
+ return {
289
+ command: group.command.join(' '),
290
+ groupId: group.id,
291
+ groupLabel: group.label,
292
+ profilePath: path.relative(rootDir, profilePath).replace(/\\/g, '/'),
293
+ samples,
294
+ ...summary,
295
+ };
58
296
  }
59
297
 
60
- const results = [
61
- benchmarkCLIStartup(),
62
- benchmarkAutoDetect(),
63
- benchmarkDetectFramework()
64
- ];
298
+ function buildBenchmarkResults(groups, results, timestamp = new Date().toISOString()) {
299
+ const totalMedianMs = results.reduce((sum, result) => sum + result.medianMs, 0);
300
+ const slowestGroup = [...results]
301
+ .sort((left, right) => right.medianMs - left.medianMs || left.groupId.localeCompare(right.groupId))[0] || null;
65
302
 
66
- const jsonMode = process.argv.includes('--json');
303
+ return {
304
+ groups: results,
305
+ requestedGroups: groups.map((group) => group.id),
306
+ samples: results[0]?.samples || DEFAULT_SAMPLES,
307
+ slowestGroup: slowestGroup
308
+ ? { groupId: slowestGroup.groupId, groupLabel: slowestGroup.groupLabel, medianMs: slowestGroup.medianMs }
309
+ : null,
310
+ timestamp,
311
+ totalMedianMs,
312
+ };
313
+ }
314
+
315
+ function formatResultLine(result) {
316
+ return ` ${result.groupLabel}: median ${result.medianMs}ms (min ${result.minMs}ms, max ${result.maxMs}ms)`;
317
+ }
318
+
319
+ function main(argv = process.argv.slice(2), deps = {}) {
320
+ const args = parseArgs(argv);
321
+ const groups = resolveGroups(args.groups);
322
+ const results = groups.map((group) => runBenchmarkGroup(group, {
323
+ profileDir: args.profileDir,
324
+ samples: args.samples,
325
+ spawnSync: deps.spawnSync,
326
+ }));
67
327
 
68
- if (jsonMode) {
69
- process.stdout.write(JSON.stringify(results));
70
- } else {
71
- console.log('\n Forge Performance Benchmarks');
72
- console.log(' ===========================\n');
73
- for (const r of results) {
74
- const status = r.mean < 1000 ? 'PASS' : r.mean < 3000 ? 'WARN' : 'SLOW';
75
- console.log(` ${status} ${r.name}: ${r.mean}${r.unit} (${r.samples} samples)`);
328
+ const payload = buildBenchmarkResults(groups, results);
329
+ const outputPath = path.resolve(rootDir, args.output || DEFAULT_OUTPUT);
330
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
331
+ fs.writeFileSync(outputPath, JSON.stringify(payload, null, 2));
332
+
333
+ if (args.json) {
334
+ process.stdout.write(JSON.stringify(payload));
335
+ return payload;
336
+ }
337
+
338
+ console.log('\n Forge Test Benchmark Baselines');
339
+ console.log(' =============================\n');
340
+ for (const result of results) {
341
+ console.log(formatResultLine(result));
76
342
  }
77
- console.log('');
343
+ console.log(`\n Total median: ${payload.totalMedianMs}ms`);
344
+ console.log(` Results saved to: ${path.relative(rootDir, outputPath).replace(/\\/g, '/')}\n`);
345
+ return payload;
346
+ }
78
347
 
79
- // Write results file
80
- const outputPath = path.join(rootDir, 'benchmark-results.json');
81
- fs.writeFileSync(outputPath, JSON.stringify(results, null, 2));
82
- console.log(` Results saved to: benchmark-results.json\n`);
348
+ if (require.main === module) {
349
+ main();
83
350
  }
84
351
 
85
- module.exports = results;
352
+ module.exports = {
353
+ BENCHMARK_GROUPS,
354
+ DEFAULT_OUTPUT,
355
+ DEFAULT_PROFILE_DIR,
356
+ DEFAULT_SAMPLES,
357
+ buildBenchmarkResults,
358
+ buildGroupProfile,
359
+ buildJUnitCommand,
360
+ calculateMedian,
361
+ formatResultLine,
362
+ main,
363
+ parseArgs,
364
+ resolveGroups,
365
+ roundMs,
366
+ materializeJUnitFile,
367
+ materializeJUnitFiles,
368
+ runBenchmarkGroup,
369
+ runCommand,
370
+ summarizeSamples,
371
+ };