forge-workflow 0.0.9 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +466 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/reference/TOOLCHAIN.md +658 -0
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +66 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +22 -2
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +851 -979
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +697 -0
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { spawnSync } = require('node:child_process');
6
+
7
+ const rootDir = path.join(__dirname, '..');
8
+ const reportDir = path.join(rootDir, 'test-results');
9
+ const PROFILE_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000;
10
+
11
+ const DEFAULT_SMOKE_FILES = [
12
+ 'test/cli-flags.test.js',
13
+ 'test/commands/test.test.js',
14
+ 'test/ci-workflow.test.js',
15
+ 'test/detect-agent.test.js',
16
+ 'test/scripts/test-runner.test.js',
17
+ 'test/workflow-profiles.test.js',
18
+ ];
19
+
20
+ function parseArgs(argv) {
21
+ const args = { label: 'unit', mode: 'shard' };
22
+
23
+ for (let index = 0; index < argv.length; index += 1) {
24
+ const current = argv[index];
25
+ const next = argv[index + 1];
26
+
27
+ if (current === '--mode') args.mode = next;
28
+ if (current === '--label') args.label = next;
29
+ if (current === '--shard-index') args.shardIndex = Number.parseInt(next, 10);
30
+ if (current === '--shard-total') args.shardTotal = Number.parseInt(next, 10);
31
+ }
32
+
33
+ return args;
34
+ }
35
+
36
+ function getUnitTestRoots() {
37
+ const roots = [path.join(rootDir, 'test')];
38
+ const packagesDir = path.join(rootDir, 'packages');
39
+ if (!fs.existsSync(packagesDir)) {
40
+ return roots.filter((dir) => fs.existsSync(dir));
41
+ }
42
+
43
+ for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {
44
+ if (!entry.isDirectory()) continue;
45
+ const testRoot = path.join(packagesDir, entry.name, 'test');
46
+ if (fs.existsSync(testRoot)) {
47
+ roots.push(testRoot);
48
+ }
49
+ }
50
+
51
+ return roots;
52
+ }
53
+
54
+ function walkTests(dir) {
55
+ const results = [];
56
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
57
+ const absolute = path.join(dir, entry.name);
58
+ if (entry.isDirectory()) {
59
+ if (absolute === path.join(rootDir, 'test', 'e2e')) continue;
60
+ results.push(...walkTests(absolute));
61
+ continue;
62
+ }
63
+
64
+ if (!entry.name.endsWith('.test.js') && !entry.name.endsWith('.spec.js')) continue;
65
+ results.push(path.relative(rootDir, absolute).replace(/\\/g, '/'));
66
+ }
67
+ return results;
68
+ }
69
+
70
+ function listAllUnitTests() {
71
+ return getUnitTestRoots()
72
+ .flatMap((dir) => walkTests(dir))
73
+ .sort((left, right) => left.localeCompare(right));
74
+ }
75
+
76
+ function ensureFilesExist(files) {
77
+ return files.filter((file) => fs.existsSync(path.join(rootDir, file)));
78
+ }
79
+
80
+ function normalizePath(file) {
81
+ return (file || '').replace(/\\/g, '/').replace(/\/+/g, '/').replace(/^\.\//, '');
82
+ }
83
+
84
+ function selectModuloShard(files, shardIndex, shardTotal) {
85
+ if (!Number.isInteger(shardIndex) || !Number.isInteger(shardTotal) || shardTotal < 1) {
86
+ throw new Error(`Invalid shard args: index=${shardIndex} total=${shardTotal}`);
87
+ }
88
+ return files.filter((_, index) => index % shardTotal === shardIndex);
89
+ }
90
+
91
+ function listProfileFiles(dir) {
92
+ if (!fs.existsSync(dir)) return [];
93
+ return fs.readdirSync(dir)
94
+ .filter((entry) => entry.endsWith('.profile.json'))
95
+ .map((entry) => path.join(dir, entry));
96
+ }
97
+
98
+ function readNewestProfile(dir, { now = Date.now(), maxAgeMs = PROFILE_MAX_AGE_MS } = {}) {
99
+ const candidates = [];
100
+ for (const file of listProfileFiles(dir)) {
101
+ try {
102
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
103
+ const timestamp = Date.parse(parsed.timestamp || '');
104
+ if (!Number.isFinite(timestamp)) continue;
105
+ if ((now - timestamp) > maxAgeMs) continue;
106
+ candidates.push({ file, profile: parsed, timestamp });
107
+ } catch (_error) {
108
+ // Ignore invalid profile files and fall back to deterministic sharding.
109
+ }
110
+ }
111
+
112
+ candidates.sort((left, right) => right.timestamp - left.timestamp || left.file.localeCompare(right.file));
113
+ return candidates[0]?.profile || null;
114
+ }
115
+
116
+ function createDurationMap(profile) {
117
+ const durationMap = new Map();
118
+ const entries = Array.isArray(profile?.allFileDurations) && profile.allFileDurations.length > 0
119
+ ? profile.allFileDurations
120
+ : profile?.slowestFiles || [];
121
+ for (const entry of entries) {
122
+ if (!entry || typeof entry.file !== 'string') continue;
123
+ durationMap.set(normalizePath(entry.file), Number(entry.durationMs) || 0);
124
+ }
125
+ return durationMap;
126
+ }
127
+
128
+ function selectRuntimeBalancedShard(files, shardIndex, shardTotal, durationMap) {
129
+ if (!Number.isInteger(shardIndex) || !Number.isInteger(shardTotal) || shardTotal < 1) {
130
+ throw new Error(`Invalid shard args: index=${shardIndex} total=${shardTotal}`);
131
+ }
132
+
133
+ const shards = Array.from({ length: shardTotal }, (_, index) => ({
134
+ files: [],
135
+ index,
136
+ totalDurationMs: 0,
137
+ }));
138
+ const weightedFiles = files
139
+ .map((file) => ({
140
+ durationMs: durationMap.get(normalizePath(file)) || 0,
141
+ file,
142
+ }))
143
+ .sort((left, right) => right.durationMs - left.durationMs || left.file.localeCompare(right.file));
144
+
145
+ for (const entry of weightedFiles) {
146
+ const targetShard = shards
147
+ .slice()
148
+ .sort((left, right) => left.totalDurationMs - right.totalDurationMs
149
+ || left.files.length - right.files.length
150
+ || left.index - right.index)[0];
151
+ targetShard.files.push(entry.file);
152
+ targetShard.totalDurationMs += entry.durationMs;
153
+ }
154
+
155
+ return shards[shardIndex].files.sort((left, right) => left.localeCompare(right));
156
+ }
157
+
158
+ function selectShard(files, shardIndex, shardTotal, durationMap = new Map()) {
159
+ if (durationMap.size === 0) {
160
+ return selectModuloShard(files, shardIndex, shardTotal);
161
+ }
162
+ return selectRuntimeBalancedShard(files, shardIndex, shardTotal, durationMap);
163
+ }
164
+
165
+ function getShardPlan(args, { allUnitTests, durationMap }) {
166
+ if (args.mode === 'smoke') {
167
+ return {
168
+ files: ensureFilesExist(DEFAULT_SMOKE_FILES),
169
+ source: 'smoke',
170
+ };
171
+ }
172
+
173
+ const effectiveDurationMap = durationMap || createDurationMap(readNewestProfile(reportDir));
174
+ return {
175
+ files: selectShard(allUnitTests, args.shardIndex, args.shardTotal, effectiveDurationMap),
176
+ source: effectiveDurationMap && effectiveDurationMap.size > 0 ? 'runtime-balanced' : 'modulo',
177
+ };
178
+ }
179
+
180
+ function buildBunTestArgs({ junitPath, files }) {
181
+ return [
182
+ 'test',
183
+ '--timeout',
184
+ '15000',
185
+ '--reporter=junit',
186
+ '--reporter-outfile',
187
+ junitPath,
188
+ ...files,
189
+ ];
190
+ }
191
+
192
+ function runTests(label, files) {
193
+ fs.mkdirSync(reportDir, { recursive: true });
194
+ const junitPath = path.join(reportDir, `${label}.xml`);
195
+ const bunCommand = process.env.BUN_EXE || 'bun';
196
+ const result = spawnSync(bunCommand, buildBunTestArgs({ junitPath, files }), {
197
+ cwd: rootDir,
198
+ stdio: 'inherit',
199
+ shell: process.platform === 'win32',
200
+ });
201
+
202
+ if (result.error) {
203
+ throw result.error;
204
+ }
205
+
206
+ return result.status ?? 1;
207
+ }
208
+
209
+ function main(argv = process.argv.slice(2)) {
210
+ const args = parseArgs(argv);
211
+ const allUnitTests = listAllUnitTests();
212
+ const plan = getShardPlan(args, { allUnitTests });
213
+ const files = plan.files;
214
+
215
+ if (files.length === 0) {
216
+ console.log(`No test files selected for ${args.label}`);
217
+ return 0;
218
+ }
219
+
220
+ console.log(`Selected ${files.length} test file(s) for ${args.label} via ${plan.source}`);
221
+ for (const file of files) {
222
+ console.log(` - ${file}`);
223
+ }
224
+
225
+ return runTests(args.label, files);
226
+ }
227
+
228
+ if (require.main === module) {
229
+ process.exit(main());
230
+ }
231
+
232
+ module.exports = {
233
+ DEFAULT_SMOKE_FILES,
234
+ PROFILE_MAX_AGE_MS,
235
+ buildBunTestArgs,
236
+ createDurationMap,
237
+ ensureFilesExist,
238
+ getShardPlan,
239
+ listProfileFiles,
240
+ main,
241
+ normalizePath,
242
+ parseArgs,
243
+ readNewestProfile,
244
+ runTests,
245
+ getUnitTestRoots,
246
+ listAllUnitTests,
247
+ selectModuloShard,
248
+ selectRuntimeBalancedShard,
249
+ selectShard,
250
+ walkTests,
251
+ };
@@ -4,35 +4,59 @@
4
4
  * Test Quality Dashboard — collects test metrics into a JSON summary.
5
5
  *
6
6
  * Usage:
7
- * node scripts/test-dashboard.js # Human-readable output
8
- * node scripts/test-dashboard.js --json # JSON output (for CI/tests)
7
+ * node scripts/test-dashboard.js
8
+ * node scripts/test-dashboard.js --json
9
+ * node scripts/test-dashboard.js --profiles-dir test-results/profiles
9
10
  */
10
11
 
11
12
  const fs = require('node:fs');
12
13
  const path = require('node:path');
13
- const { execFileSync } = require('node:child_process');
14
14
 
15
15
  const rootDir = path.join(__dirname, '..');
16
16
 
17
+ function parseArgs(argv) {
18
+ const args = {
19
+ json: argv.includes('--json'),
20
+ profilesDir: path.join(rootDir, 'test-results'),
21
+ };
22
+
23
+ for (let index = 0; index < argv.length; index += 1) {
24
+ if (argv[index] === '--profiles-dir') {
25
+ args.profilesDir = path.resolve(rootDir, argv[index + 1]);
26
+ }
27
+ }
28
+
29
+ return args;
30
+ }
31
+
32
+ function walk(dir, extension) {
33
+ if (!fs.existsSync(dir)) return [];
34
+ const results = [];
35
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
36
+ const absolute = path.join(dir, entry.name);
37
+ if (entry.isDirectory()) {
38
+ results.push(...walk(absolute, extension));
39
+ continue;
40
+ }
41
+ if (absolute.endsWith(extension)) {
42
+ results.push(absolute);
43
+ }
44
+ }
45
+ return results;
46
+ }
47
+
17
48
  function getTestCount() {
18
- // Count test cases by scanning test files for test() calls
19
- // This avoids running the full test suite (which would be slow/recursive)
20
49
  const testDirs = [
21
50
  path.join(rootDir, 'test'),
22
51
  path.join(rootDir, 'test-env'),
23
- path.join(rootDir, 'packages', 'skills', 'test')
52
+ path.join(rootDir, 'packages', 'skills', 'test'),
24
53
  ];
25
54
  let count = 0;
26
55
  for (const dir of testDirs) {
27
- if (!fs.existsSync(dir)) continue;
28
- const files = fs.readdirSync(dir, { recursive: true });
29
- for (const file of files) {
30
- const filePath = path.join(dir, String(file));
31
- if (!filePath.endsWith('.test.js') && !filePath.endsWith('.spec.js')) continue;
32
- if (!fs.statSync(filePath).isFile()) continue;
33
- const content = fs.readFileSync(filePath, 'utf-8');
34
- // Count test() and it() calls (excluding test.skip)
35
- const testMatches = content.match(/\btest\s*\(/g) || [];
56
+ for (const file of walk(dir, '.js')) {
57
+ if (!file.endsWith('.test.js') && !file.endsWith('.spec.js')) continue;
58
+ const content = fs.readFileSync(file, 'utf-8');
59
+ const testMatches = content.match(/\btest(?:\.skip|\.todo|\.if|\.skipIf)?\s*\(/g) || [];
36
60
  const itMatches = content.match(/\bit\s*\(/g) || [];
37
61
  const skipMatches = content.match(/\btest\.skip\s*\(/g) || [];
38
62
  count += testMatches.length + itMatches.length - skipMatches.length;
@@ -46,24 +70,19 @@ function getCoverageThreshold() {
46
70
  return pkg.c8 ? pkg.c8.lines : 0;
47
71
  }
48
72
 
49
- function getEslintWarnings() {
50
- // We enforce --max-warnings 0 in pre-push hooks (lefthook.yml),
51
- // so this is always 0 when code passes CI. Report 0 to avoid
52
- // slow ESLint execution during dashboard generation.
53
- return 0;
54
- }
55
-
56
73
  function getSkippedTestCount() {
57
- try {
58
- const output = execFileSync('grep', ['-r', 'test.skip', 'test/', 'test-env/'], {
59
- cwd: rootDir,
60
- encoding: 'utf-8',
61
- stdio: ['pipe', 'pipe', 'pipe']
62
- });
63
- return output.trim().split('\n').filter(Boolean).length;
64
- } catch (_e) {
65
- return 0;
74
+ const testDirs = [
75
+ path.join(rootDir, 'test'),
76
+ path.join(rootDir, 'test-env'),
77
+ path.join(rootDir, 'packages', 'skills', 'test'),
78
+ ];
79
+ let skipped = 0;
80
+ for (const file of testDirs.flatMap((dir) => walk(dir, '.js'))) {
81
+ if (!file.endsWith('.test.js') && !file.endsWith('.spec.js')) continue;
82
+ const content = fs.readFileSync(file, 'utf8');
83
+ skipped += (content.match(/\btest\.skip\s*\(/g) || []).length;
66
84
  }
85
+ return skipped;
67
86
  }
68
87
 
69
88
  function getMutationScore() {
@@ -71,37 +90,137 @@ function getMutationScore() {
71
90
  if (!fs.existsSync(reportPath)) return null;
72
91
  try {
73
92
  const report = JSON.parse(fs.readFileSync(reportPath, 'utf-8'));
74
- if (report.schemaVersion) {
75
- // Stryker v8+ JSON report format
76
- const files = Object.values(report.files || {});
77
- let killed = 0;
78
- let total = 0;
79
- for (const file of files) {
80
- for (const mutant of (file.mutants || [])) {
81
- total++;
82
- if (mutant.status === 'Killed') killed++;
83
- }
93
+ const files = Object.values(report.files || {});
94
+ let killed = 0;
95
+ let total = 0;
96
+ for (const file of files) {
97
+ for (const mutant of (file.mutants || [])) {
98
+ total += 1;
99
+ if (mutant.status === 'Killed') killed += 1;
84
100
  }
85
- return total > 0 ? Math.round((killed / total) * 100) : null;
86
101
  }
102
+ return total > 0 ? Math.round((killed / total) * 100) : null;
103
+ } catch (_error) {
87
104
  return null;
88
- } catch (_e) {
89
- return null;
90
105
  }
91
106
  }
92
107
 
108
+ function getProfileMetrics(profilesDir) {
109
+ const files = walk(profilesDir, '.json').filter((file) => file.endsWith('.profile.json'));
110
+ if (files.length === 0) {
111
+ return {
112
+ suiteDurationMs: 0,
113
+ slowestFiles: [],
114
+ timedOutFiles: [],
115
+ integrationSkipped: true,
116
+ };
117
+ }
118
+
119
+ const slowest = new Map();
120
+ const timedOut = new Set();
121
+ let suiteDurationMs = 0;
122
+ let integrationSkipped = false;
123
+
124
+ for (const file of files) {
125
+ let profile;
126
+ try {
127
+ profile = JSON.parse(fs.readFileSync(file, 'utf8'));
128
+ } catch (error) {
129
+ console.warn(`Skipping malformed profile ${path.relative(rootDir, file)}: ${error.message}`);
130
+ continue;
131
+ }
132
+
133
+ if (!profile || typeof profile !== 'object') {
134
+ console.warn(`Skipping invalid profile ${path.relative(rootDir, file)}: expected object`);
135
+ continue;
136
+ }
137
+
138
+ suiteDurationMs += profile.suiteDurationMs || 0;
139
+ integrationSkipped = integrationSkipped || Boolean(profile.integrationSkipped);
140
+
141
+ const entries = Array.isArray(profile.allFileDurations) && profile.allFileDurations.length > 0
142
+ ? profile.allFileDurations
143
+ : Array.isArray(profile.slowestFiles) ? profile.slowestFiles : [];
144
+ for (const entry of entries) {
145
+ if (!entry || typeof entry !== 'object' || typeof entry.file !== 'string') {
146
+ continue;
147
+ }
148
+ slowest.set(entry.file, Math.max(slowest.get(entry.file) || 0, entry.durationMs || 0));
149
+ }
150
+
151
+ for (const timedOutFile of Array.isArray(profile.timedOutFiles) ? profile.timedOutFiles : []) {
152
+ if (typeof timedOutFile !== 'string' || !timedOutFile) {
153
+ continue;
154
+ }
155
+ timedOut.add(timedOutFile);
156
+ }
157
+ }
158
+
159
+ return {
160
+ suiteDurationMs,
161
+ slowestFiles: Array.from(slowest.entries())
162
+ .map(([file, durationMs]) => ({ file, durationMs }))
163
+ .sort((left, right) => right.durationMs - left.durationMs)
164
+ .slice(0, 10),
165
+ timedOutFiles: Array.from(timedOut).sort((left, right) => left.localeCompare(right)),
166
+ integrationSkipped,
167
+ };
168
+ }
169
+
170
+ function getBenchmarkMetrics(profilesDir) {
171
+ const files = walk(profilesDir, '.json').filter((file) => path.basename(file) === 'benchmark-results.json');
172
+ if (files.length === 0) {
173
+ return {
174
+ groupCount: 0,
175
+ groups: [],
176
+ slowestGroup: null,
177
+ totalMedianMs: 0,
178
+ };
179
+ }
180
+
181
+ try {
182
+ const benchmark = JSON.parse(fs.readFileSync(files.sort((left, right) => right.localeCompare(left))[0], 'utf8'));
183
+ const groups = Array.isArray(benchmark.groups)
184
+ ? benchmark.groups.map((group) => ({
185
+ groupId: group.groupId,
186
+ groupLabel: group.groupLabel,
187
+ medianMs: group.medianMs || 0,
188
+ }))
189
+ : [];
190
+ return {
191
+ groupCount: groups.length,
192
+ groups,
193
+ slowestGroup: benchmark.slowestGroup || null,
194
+ totalMedianMs: benchmark.totalMedianMs || groups.reduce((sum, group) => sum + group.medianMs, 0),
195
+ };
196
+ } catch (_error) {
197
+ return {
198
+ groupCount: 0,
199
+ groups: [],
200
+ slowestGroup: null,
201
+ totalMedianMs: 0,
202
+ };
203
+ }
204
+ }
205
+
206
+ const args = parseArgs(process.argv.slice(2));
207
+ const profileMetrics = getProfileMetrics(args.profilesDir);
208
+ const benchmarkMetrics = getBenchmarkMetrics(args.profilesDir);
93
209
  const dashboard = {
210
+ benchmarks: benchmarkMetrics,
94
211
  testCount: getTestCount(),
95
212
  coverageThreshold: getCoverageThreshold(),
96
- eslintWarnings: getEslintWarnings(),
213
+ eslintWarnings: 0,
97
214
  skippedTests: getSkippedTestCount(),
98
215
  mutationScore: getMutationScore(),
99
- timestamp: new Date().toISOString()
216
+ suiteDurationMs: profileMetrics.suiteDurationMs,
217
+ slowestFiles: profileMetrics.slowestFiles,
218
+ timedOutFiles: profileMetrics.timedOutFiles,
219
+ integrationSkipped: profileMetrics.integrationSkipped,
220
+ timestamp: new Date().toISOString(),
100
221
  };
101
222
 
102
- const jsonMode = process.argv.includes('--json');
103
-
104
- if (jsonMode) {
223
+ if (args.json) {
105
224
  process.stdout.write(JSON.stringify(dashboard));
106
225
  } else {
107
226
  console.log('\n Test Quality Dashboard');
@@ -110,14 +229,31 @@ if (jsonMode) {
110
229
  console.log(` Coverage threshold: ${dashboard.coverageThreshold}%`);
111
230
  console.log(` ESLint warnings: ${dashboard.eslintWarnings}`);
112
231
  console.log(` Skipped tests: ${dashboard.skippedTests}`);
113
- console.log(` Mutation score: ${dashboard.mutationScore !== null ? dashboard.mutationScore + '%' : 'N/A (run test:mutation first)'}`);
232
+ console.log(` Suite duration: ${dashboard.suiteDurationMs}ms`);
233
+ console.log(` Timed out files: ${dashboard.timedOutFiles.length}`);
234
+ console.log(` Integration skipped:${dashboard.integrationSkipped ? ' yes' : ' no'}`);
235
+ console.log(` Mutation score: ${dashboard.mutationScore !== null ? `${dashboard.mutationScore}%` : 'N/A (run test:mutation first)'}`);
236
+ if (dashboard.benchmarks.groupCount > 0) {
237
+ console.log(` Benchmarks: ${dashboard.benchmarks.groupCount} groups (${dashboard.benchmarks.totalMedianMs}ms median total)`);
238
+ }
114
239
  console.log(` Generated: ${dashboard.timestamp}`);
240
+ if (dashboard.slowestFiles.length > 0) {
241
+ console.log('\n Slowest files:');
242
+ for (const entry of dashboard.slowestFiles) {
243
+ console.log(` - ${entry.file}: ${entry.durationMs}ms`);
244
+ }
245
+ }
246
+ if (dashboard.benchmarks.groupCount > 0) {
247
+ console.log('\n Benchmark medians:');
248
+ for (const entry of dashboard.benchmarks.groups) {
249
+ console.log(` - ${entry.groupLabel}: ${entry.medianMs}ms`);
250
+ }
251
+ }
115
252
  console.log('');
116
253
 
117
- // Write results file
118
254
  const outputPath = path.join(rootDir, 'test-dashboard.json');
119
255
  fs.writeFileSync(outputPath, JSON.stringify(dashboard, null, 2));
120
- console.log(` Results saved to: test-dashboard.json\n`);
256
+ console.log(' Results saved to: test-dashboard.json\n');
121
257
  }
122
258
 
123
259
  module.exports = dashboard;