@dommaker/harness 1.3.0 → 1.5.0

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 (532) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +0 -2
  3. package/bin/harness.js +86 -32
  4. package/dist/cli/command-contract.d.ts +83 -0
  5. package/dist/cli/command-contract.d.ts.map +1 -0
  6. package/dist/cli/command-contract.js +66 -0
  7. package/dist/cli/command-contract.js.map +1 -0
  8. package/dist/cli/commands/acceptance.d.ts +3 -2
  9. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  10. package/dist/cli/commands/acceptance.js +28 -24
  11. package/dist/cli/commands/acceptance.js.map +1 -1
  12. package/dist/cli/commands/analyze-sessions.d.ts +2 -1
  13. package/dist/cli/commands/analyze-sessions.d.ts.map +1 -1
  14. package/dist/cli/commands/analyze-sessions.js +25 -24
  15. package/dist/cli/commands/analyze-sessions.js.map +1 -1
  16. package/dist/cli/commands/check.d.ts +14 -2
  17. package/dist/cli/commands/check.d.ts.map +1 -1
  18. package/dist/cli/commands/check.js +104 -61
  19. package/dist/cli/commands/check.js.map +1 -1
  20. package/dist/cli/commands/command.d.ts +5 -1
  21. package/dist/cli/commands/command.d.ts.map +1 -1
  22. package/dist/cli/commands/command.js +26 -17
  23. package/dist/cli/commands/command.js.map +1 -1
  24. package/dist/cli/commands/constraints-report.d.ts +2 -1
  25. package/dist/cli/commands/constraints-report.d.ts.map +1 -1
  26. package/dist/cli/commands/constraints-report.js +37 -33
  27. package/dist/cli/commands/constraints-report.js.map +1 -1
  28. package/dist/cli/commands/constraints-retire.d.ts +6 -5
  29. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  30. package/dist/cli/commands/constraints-retire.js +50 -52
  31. package/dist/cli/commands/constraints-retire.js.map +1 -1
  32. package/dist/cli/commands/constraints.d.ts +2 -1
  33. package/dist/cli/commands/constraints.d.ts.map +1 -1
  34. package/dist/cli/commands/constraints.js +10 -27
  35. package/dist/cli/commands/constraints.js.map +1 -1
  36. package/dist/cli/commands/contract.d.ts +3 -2
  37. package/dist/cli/commands/contract.d.ts.map +1 -1
  38. package/dist/cli/commands/contract.js +45 -42
  39. package/dist/cli/commands/contract.js.map +1 -1
  40. package/dist/cli/commands/definitions.d.ts +11 -11
  41. package/dist/cli/commands/definitions.d.ts.map +1 -1
  42. package/dist/cli/commands/definitions.js +27 -219
  43. package/dist/cli/commands/definitions.js.map +1 -1
  44. package/dist/cli/commands/failure.d.ts +5 -4
  45. package/dist/cli/commands/failure.d.ts.map +1 -1
  46. package/dist/cli/commands/failure.js +31 -25
  47. package/dist/cli/commands/failure.js.map +1 -1
  48. package/dist/cli/commands/init.d.ts +7 -5
  49. package/dist/cli/commands/init.d.ts.map +1 -1
  50. package/dist/cli/commands/init.js +147 -211
  51. package/dist/cli/commands/init.js.map +1 -1
  52. package/dist/cli/commands/knowledge.d.ts +17 -30
  53. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  54. package/dist/cli/commands/knowledge.js +183 -273
  55. package/dist/cli/commands/knowledge.js.map +1 -1
  56. package/dist/cli/commands/passes-gate.d.ts +8 -2
  57. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  58. package/dist/cli/commands/passes-gate.js +44 -66
  59. package/dist/cli/commands/passes-gate.js.map +1 -1
  60. package/dist/cli/commands/performance.d.ts +2 -1
  61. package/dist/cli/commands/performance.d.ts.map +1 -1
  62. package/dist/cli/commands/performance.js +20 -18
  63. package/dist/cli/commands/performance.js.map +1 -1
  64. package/dist/cli/commands/posteval-plan.d.ts +5 -1
  65. package/dist/cli/commands/posteval-plan.d.ts.map +1 -1
  66. package/dist/cli/commands/posteval-plan.js +26 -15
  67. package/dist/cli/commands/posteval-plan.js.map +1 -1
  68. package/dist/cli/commands/release.d.ts +6 -1
  69. package/dist/cli/commands/release.d.ts.map +1 -1
  70. package/dist/cli/commands/release.js +69 -59
  71. package/dist/cli/commands/release.js.map +1 -1
  72. package/dist/cli/commands/report.d.ts +2 -1
  73. package/dist/cli/commands/report.d.ts.map +1 -1
  74. package/dist/cli/commands/report.js +9 -5
  75. package/dist/cli/commands/report.js.map +1 -1
  76. package/dist/cli/commands/review.d.ts +3 -2
  77. package/dist/cli/commands/review.d.ts.map +1 -1
  78. package/dist/cli/commands/review.js +33 -29
  79. package/dist/cli/commands/review.js.map +1 -1
  80. package/dist/cli/commands/sdd.d.ts +2 -1
  81. package/dist/cli/commands/sdd.d.ts.map +1 -1
  82. package/dist/cli/commands/sdd.js +7 -5
  83. package/dist/cli/commands/sdd.js.map +1 -1
  84. package/dist/cli/commands/security.d.ts +3 -2
  85. package/dist/cli/commands/security.d.ts.map +1 -1
  86. package/dist/cli/commands/security.js +29 -26
  87. package/dist/cli/commands/security.js.map +1 -1
  88. package/dist/cli/commands/spec-baseline-check.d.ts +2 -1
  89. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  90. package/dist/cli/commands/spec-baseline-check.js +15 -11
  91. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  92. package/dist/cli/commands/spec.d.ts +7 -2
  93. package/dist/cli/commands/spec.d.ts.map +1 -1
  94. package/dist/cli/commands/spec.js +61 -51
  95. package/dist/cli/commands/spec.js.map +1 -1
  96. package/dist/cli/commands/status.d.ts +2 -1
  97. package/dist/cli/commands/status.d.ts.map +1 -1
  98. package/dist/cli/commands/status.js +57 -53
  99. package/dist/cli/commands/status.js.map +1 -1
  100. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  101. package/dist/cli/commands/sync-docs/agents-syncer.js +5 -2
  102. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  103. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  104. package/dist/cli/commands/sync-docs/context-syncer.js +3 -2
  105. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  106. package/dist/cli/commands/sync-docs/index.d.ts +2 -1
  107. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  108. package/dist/cli/commands/sync-docs/index.js +68 -88
  109. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  110. package/dist/cli/commands/sync-docs/project-reader.d.ts +6 -1
  111. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  112. package/dist/cli/commands/sync-docs/project-reader.js +14 -16
  113. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  114. package/dist/cli/commands/update-user-model.d.ts +9 -3
  115. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  116. package/dist/cli/commands/update-user-model.js +56 -61
  117. package/dist/cli/commands/update-user-model.js.map +1 -1
  118. package/dist/cli/commands/validate.d.ts +4 -3
  119. package/dist/cli/commands/validate.d.ts.map +1 -1
  120. package/dist/cli/commands/validate.js +28 -26
  121. package/dist/cli/commands/validate.js.map +1 -1
  122. package/dist/cli/session-mining/transcript.d.ts +7 -1
  123. package/dist/cli/session-mining/transcript.d.ts.map +1 -1
  124. package/dist/cli/session-mining/transcript.js +21 -14
  125. package/dist/cli/session-mining/transcript.js.map +1 -1
  126. package/dist/context/session-manager.d.ts.map +1 -1
  127. package/dist/context/session-manager.js +19 -32
  128. package/dist/context/session-manager.js.map +1 -1
  129. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +1 -1
  130. package/dist/core/constraints/agent-prompt-renderer.js +1 -4
  131. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  132. package/dist/core/constraints/capabilities-parser.d.ts +24 -7
  133. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  134. package/dist/core/constraints/capabilities-parser.js +32 -28
  135. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  136. package/dist/core/constraints/capabilities-reconcile.d.ts +67 -0
  137. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -0
  138. package/dist/core/constraints/capabilities-reconcile.js +105 -0
  139. package/dist/core/constraints/capabilities-reconcile.js.map +1 -0
  140. package/dist/core/constraints/checker.d.ts +21 -43
  141. package/dist/core/constraints/checker.d.ts.map +1 -1
  142. package/dist/core/constraints/checker.js +68 -117
  143. package/dist/core/constraints/checker.js.map +1 -1
  144. package/dist/core/constraints/checkers/capability-sync.d.ts +20 -8
  145. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  146. package/dist/core/constraints/checkers/capability-sync.js +66 -77
  147. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  148. package/dist/core/constraints/checkers/context-doc-sync.d.ts +4 -2
  149. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  150. package/dist/core/constraints/checkers/context-doc-sync.js +13 -22
  151. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  152. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  153. package/dist/core/constraints/checkers/docs-freshness.js +49 -66
  154. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  155. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -2
  156. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  157. package/dist/core/constraints/checkers/governance-presence.js +7 -28
  158. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  159. package/dist/core/constraints/checkers/index.d.ts +2 -2
  160. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  161. package/dist/core/constraints/checkers/index.js +2 -1
  162. package/dist/core/constraints/checkers/index.js.map +1 -1
  163. package/dist/core/constraints/checkers/no-test-simplification.d.ts.map +1 -1
  164. package/dist/core/constraints/checkers/no-test-simplification.js +4 -3
  165. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  166. package/dist/core/constraints/checkers/types.d.ts +43 -4
  167. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  168. package/dist/core/constraints/checkers/types.js +33 -0
  169. package/dist/core/constraints/checkers/types.js.map +1 -1
  170. package/dist/core/constraints/context-builder.d.ts +15 -14
  171. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  172. package/dist/core/constraints/context-builder.js +32 -96
  173. package/dist/core/constraints/context-builder.js.map +1 -1
  174. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  175. package/dist/core/constraints/doc-freshness/runner.js +2 -1
  176. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  177. package/dist/core/constraints/git-evidence.d.ts +60 -0
  178. package/dist/core/constraints/git-evidence.d.ts.map +1 -0
  179. package/dist/core/constraints/git-evidence.js +88 -0
  180. package/dist/core/constraints/git-evidence.js.map +1 -0
  181. package/dist/core/constraints/injection-drift.d.ts +3 -20
  182. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  183. package/dist/core/constraints/injection-drift.js +10 -57
  184. package/dist/core/constraints/injection-drift.js.map +1 -1
  185. package/dist/core/constraints/injection-writer.d.ts +113 -0
  186. package/dist/core/constraints/injection-writer.d.ts.map +1 -0
  187. package/dist/core/constraints/injection-writer.js +228 -0
  188. package/dist/core/constraints/injection-writer.js.map +1 -0
  189. package/dist/core/constraints/usage-report.d.ts +22 -1
  190. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  191. package/dist/core/constraints/usage-report.js +27 -20
  192. package/dist/core/constraints/usage-report.js.map +1 -1
  193. package/dist/core/index.d.ts +2 -2
  194. package/dist/core/index.d.ts.map +1 -1
  195. package/dist/core/index.js +4 -2
  196. package/dist/core/index.js.map +1 -1
  197. package/dist/core/project-config-loader.d.ts +32 -1
  198. package/dist/core/project-config-loader.d.ts.map +1 -1
  199. package/dist/core/project-config-loader.js +39 -9
  200. package/dist/core/project-config-loader.js.map +1 -1
  201. package/dist/core/validators/passes-gate.d.ts +15 -64
  202. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  203. package/dist/core/validators/passes-gate.js +84 -222
  204. package/dist/core/validators/passes-gate.js.map +1 -1
  205. package/dist/core/validators/test-output.d.ts +58 -0
  206. package/dist/core/validators/test-output.d.ts.map +1 -0
  207. package/dist/core/validators/test-output.js +109 -0
  208. package/dist/core/validators/test-output.js.map +1 -0
  209. package/dist/failure/recorder.d.ts +6 -0
  210. package/dist/failure/recorder.d.ts.map +1 -1
  211. package/dist/failure/recorder.js +12 -8
  212. package/dist/failure/recorder.js.map +1 -1
  213. package/dist/gates/acceptance.d.ts +1 -48
  214. package/dist/gates/acceptance.d.ts.map +1 -1
  215. package/dist/gates/acceptance.js +6 -38
  216. package/dist/gates/acceptance.js.map +1 -1
  217. package/dist/gates/checker-gate.d.ts +2 -1
  218. package/dist/gates/checker-gate.d.ts.map +1 -1
  219. package/dist/gates/checker-gate.js +11 -13
  220. package/dist/gates/checker-gate.js.map +1 -1
  221. package/dist/gates/command.d.ts.map +1 -1
  222. package/dist/gates/command.js +10 -22
  223. package/dist/gates/command.js.map +1 -1
  224. package/dist/gates/contract.d.ts.map +1 -1
  225. package/dist/gates/contract.js +22 -61
  226. package/dist/gates/contract.js.map +1 -1
  227. package/dist/gates/performance.d.ts.map +1 -1
  228. package/dist/gates/performance.js +13 -31
  229. package/dist/gates/performance.js.map +1 -1
  230. package/dist/gates/review.d.ts.map +1 -1
  231. package/dist/gates/review.js +21 -53
  232. package/dist/gates/review.js.map +1 -1
  233. package/dist/gates/security.d.ts.map +1 -1
  234. package/dist/gates/security.js +14 -32
  235. package/dist/gates/security.js.map +1 -1
  236. package/dist/gates/types.d.ts +28 -5
  237. package/dist/gates/types.d.ts.map +1 -1
  238. package/dist/gates/types.js +38 -1
  239. package/dist/gates/types.js.map +1 -1
  240. package/dist/hooks/bootstrap.d.ts +2 -0
  241. package/dist/hooks/bootstrap.d.ts.map +1 -1
  242. package/dist/hooks/bootstrap.js +5 -2
  243. package/dist/hooks/bootstrap.js.map +1 -1
  244. package/dist/index.d.ts +1 -2
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js.map +1 -1
  247. package/dist/knowledge/audit.d.ts +7 -2
  248. package/dist/knowledge/audit.d.ts.map +1 -1
  249. package/dist/knowledge/audit.js +59 -79
  250. package/dist/knowledge/audit.js.map +1 -1
  251. package/dist/knowledge/flywheel-metrics.d.ts +39 -0
  252. package/dist/knowledge/flywheel-metrics.d.ts.map +1 -0
  253. package/dist/knowledge/flywheel-metrics.js +47 -0
  254. package/dist/knowledge/flywheel-metrics.js.map +1 -0
  255. package/dist/knowledge/import.d.ts.map +1 -1
  256. package/dist/knowledge/import.js +4 -14
  257. package/dist/knowledge/import.js.map +1 -1
  258. package/dist/knowledge/index-generator.d.ts.map +1 -1
  259. package/dist/knowledge/index-generator.js +17 -14
  260. package/dist/knowledge/index-generator.js.map +1 -1
  261. package/dist/knowledge/ingest.d.ts.map +1 -1
  262. package/dist/knowledge/ingest.js +9 -4
  263. package/dist/knowledge/ingest.js.map +1 -1
  264. package/dist/knowledge/migration.d.ts.map +1 -1
  265. package/dist/knowledge/migration.js +11 -8
  266. package/dist/knowledge/migration.js.map +1 -1
  267. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  268. package/dist/knowledge/reference-tracker.js +12 -9
  269. package/dist/knowledge/reference-tracker.js.map +1 -1
  270. package/dist/knowledge/store.d.ts +15 -0
  271. package/dist/knowledge/store.d.ts.map +1 -1
  272. package/dist/knowledge/store.js +59 -10
  273. package/dist/knowledge/store.js.map +1 -1
  274. package/dist/monitoring/context-tracker.d.ts.map +1 -1
  275. package/dist/monitoring/context-tracker.js +11 -20
  276. package/dist/monitoring/context-tracker.js.map +1 -1
  277. package/dist/monitoring/index.d.ts +1 -1
  278. package/dist/monitoring/index.d.ts.map +1 -1
  279. package/dist/monitoring/index.js +1 -2
  280. package/dist/monitoring/index.js.map +1 -1
  281. package/dist/monitoring/trace-analyzer.d.ts +15 -0
  282. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  283. package/dist/monitoring/trace-analyzer.js +19 -2
  284. package/dist/monitoring/trace-analyzer.js.map +1 -1
  285. package/dist/monitoring/traces.d.ts +21 -2
  286. package/dist/monitoring/traces.d.ts.map +1 -1
  287. package/dist/monitoring/traces.js +38 -29
  288. package/dist/monitoring/traces.js.map +1 -1
  289. package/dist/release/index.d.ts +2 -2
  290. package/dist/release/index.d.ts.map +1 -1
  291. package/dist/release/index.js +1 -5
  292. package/dist/release/index.js.map +1 -1
  293. package/dist/sdd/index-generator.js +10 -11
  294. package/dist/sdd/index-generator.js.map +1 -1
  295. package/dist/test-setup/project-fixture.d.ts +48 -0
  296. package/dist/test-setup/project-fixture.d.ts.map +1 -0
  297. package/dist/test-setup/project-fixture.js +106 -0
  298. package/dist/test-setup/project-fixture.js.map +1 -0
  299. package/dist/types/constraint.d.ts +8 -18
  300. package/dist/types/constraint.d.ts.map +1 -1
  301. package/dist/types/constraint.js +6 -1
  302. package/dist/types/constraint.js.map +1 -1
  303. package/dist/types/passes-gate.d.ts +10 -25
  304. package/dist/types/passes-gate.d.ts.map +1 -1
  305. package/dist/types/project-config.d.ts +1 -1
  306. package/dist/types/session.d.ts +6 -5
  307. package/dist/types/session.d.ts.map +1 -1
  308. package/dist/types/trace.d.ts +9 -0
  309. package/dist/types/trace.d.ts.map +1 -1
  310. package/dist/utils/exec.d.ts +10 -0
  311. package/dist/utils/exec.d.ts.map +1 -1
  312. package/dist/utils/exec.js +12 -0
  313. package/dist/utils/exec.js.map +1 -1
  314. package/dist/utils/file-walk.d.ts +9 -1
  315. package/dist/utils/file-walk.d.ts.map +1 -1
  316. package/dist/utils/file-walk.js +10 -1
  317. package/dist/utils/file-walk.js.map +1 -1
  318. package/dist/utils/frontmatter.d.ts +73 -0
  319. package/dist/utils/frontmatter.d.ts.map +1 -0
  320. package/dist/utils/frontmatter.js +141 -0
  321. package/dist/utils/frontmatter.js.map +1 -0
  322. package/dist/utils/jsonl.d.ts +92 -0
  323. package/dist/utils/jsonl.d.ts.map +1 -0
  324. package/dist/utils/jsonl.js +166 -0
  325. package/dist/utils/jsonl.js.map +1 -0
  326. package/dist/utils/package-version.d.ts +16 -0
  327. package/dist/utils/package-version.d.ts.map +1 -0
  328. package/dist/utils/package-version.js +63 -0
  329. package/dist/utils/package-version.js.map +1 -0
  330. package/package.json +1 -1
  331. package/src/CONTEXT.md +8 -2
  332. package/src/__tests__/acceptance-gate.test.ts +3 -1
  333. package/src/__tests__/checker-extra.test.ts +7 -1218
  334. package/src/__tests__/context-files-resolution.test.ts +116 -0
  335. package/src/__tests__/effective-constraints.test.ts +10 -13
  336. package/src/__tests__/exec.test.ts +35 -1
  337. package/src/__tests__/layering.test.ts +152 -0
  338. package/src/__tests__/passes-gate-extra.test.ts +0 -145
  339. package/src/__tests__/passes-gate.test.ts +130 -101
  340. package/src/__tests__/skip-semantics.test.ts +5 -9
  341. package/src/cli/__tests__/command-contract.test.ts +170 -0
  342. package/src/cli/command-contract.ts +108 -0
  343. package/src/cli/commands/CONTEXT.md +23 -8
  344. package/src/cli/commands/__tests__/acceptance.test.ts +40 -33
  345. package/src/cli/commands/__tests__/analyze-sessions.test.ts +35 -29
  346. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +107 -0
  347. package/src/cli/commands/__tests__/check-drift.test.ts +27 -34
  348. package/src/cli/commands/__tests__/check-skip-output.test.ts +21 -24
  349. package/src/cli/commands/__tests__/check.test.ts +448 -378
  350. package/src/cli/commands/__tests__/command.test.ts +85 -70
  351. package/src/cli/commands/__tests__/constraints-report.test.ts +100 -71
  352. package/src/cli/commands/__tests__/constraints-retire.test.ts +152 -95
  353. package/src/cli/commands/__tests__/contract.test.ts +27 -34
  354. package/src/cli/commands/__tests__/failure.test.ts +31 -34
  355. package/src/cli/commands/__tests__/init-injection.test.ts +58 -28
  356. package/src/cli/commands/__tests__/init.test.ts +77 -48
  357. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +108 -0
  358. package/src/cli/commands/__tests__/knowledge.test.ts +69 -43
  359. package/src/cli/commands/__tests__/passes-gate.test.ts +43 -45
  360. package/src/cli/commands/__tests__/performance.test.ts +22 -23
  361. package/src/cli/commands/__tests__/posteval-plan.test.ts +39 -38
  362. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +157 -0
  363. package/src/cli/commands/__tests__/project-path-convention.test.ts +231 -0
  364. package/src/cli/commands/__tests__/registry.test.ts +75 -35
  365. package/src/cli/commands/__tests__/release.test.ts +209 -80
  366. package/src/cli/commands/__tests__/report-extra.test.ts +16 -16
  367. package/src/cli/commands/__tests__/report.test.ts +25 -25
  368. package/src/cli/commands/__tests__/review.test.ts +33 -31
  369. package/src/cli/commands/__tests__/security.test.ts +23 -27
  370. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +20 -20
  371. package/src/cli/commands/__tests__/spec.test.ts +41 -29
  372. package/src/cli/commands/__tests__/status-extra.test.ts +18 -18
  373. package/src/cli/commands/__tests__/status-skipped-lines.test.ts +72 -0
  374. package/src/cli/commands/__tests__/status.test.ts +34 -34
  375. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +77 -78
  376. package/src/cli/commands/__tests__/sync-docs.test.ts +72 -74
  377. package/src/cli/commands/__tests__/update-user-model.test.ts +97 -34
  378. package/src/cli/commands/__tests__/validate.test.ts +27 -29
  379. package/src/cli/commands/acceptance.ts +35 -24
  380. package/src/cli/commands/analyze-sessions.ts +25 -24
  381. package/src/cli/commands/check.ts +127 -66
  382. package/src/cli/commands/command.ts +30 -17
  383. package/src/cli/commands/constraints-report.ts +37 -34
  384. package/src/cli/commands/constraints-retire.ts +58 -55
  385. package/src/cli/commands/constraints.ts +10 -27
  386. package/src/cli/commands/contract.ts +51 -42
  387. package/src/cli/commands/definitions.ts +34 -227
  388. package/src/cli/commands/failure.ts +33 -26
  389. package/src/cli/commands/init.ts +166 -220
  390. package/src/cli/commands/knowledge.ts +192 -289
  391. package/src/cli/commands/passes-gate.ts +58 -68
  392. package/src/cli/commands/performance.ts +23 -18
  393. package/src/cli/commands/posteval-plan.ts +29 -15
  394. package/src/cli/commands/release.ts +70 -59
  395. package/src/cli/commands/report.ts +9 -5
  396. package/src/cli/commands/review.ts +39 -29
  397. package/src/cli/commands/sdd.ts +7 -5
  398. package/src/cli/commands/security.ts +35 -26
  399. package/src/cli/commands/spec-baseline-check.ts +16 -11
  400. package/src/cli/commands/spec.ts +66 -53
  401. package/src/cli/commands/status.ts +59 -53
  402. package/src/cli/commands/sync-docs/agents-syncer.ts +5 -2
  403. package/src/cli/commands/sync-docs/context-syncer.ts +3 -2
  404. package/src/cli/commands/sync-docs/index.ts +73 -100
  405. package/src/cli/commands/sync-docs/project-reader.ts +17 -18
  406. package/src/cli/commands/update-user-model.ts +64 -61
  407. package/src/cli/commands/validate.ts +31 -25
  408. package/src/cli/session-mining/__tests__/transcript.test.ts +118 -0
  409. package/src/cli/session-mining/transcript.ts +26 -8
  410. package/src/context/session-manager.ts +19 -33
  411. package/src/core/CONTEXT.md +10 -6
  412. package/src/core/constraints/__tests__/capabilities-reconcile.test.ts +200 -0
  413. package/src/core/constraints/__tests__/capability-counts.test.ts +94 -0
  414. package/src/core/constraints/__tests__/git-evidence.test.ts +208 -0
  415. package/src/core/constraints/__tests__/injection-drift.test.ts +3 -10
  416. package/src/core/constraints/__tests__/injection-writer.test.ts +231 -0
  417. package/src/core/constraints/__tests__/trace-injection.test.ts +71 -0
  418. package/src/core/constraints/__tests__/usage-report.test.ts +112 -0
  419. package/src/core/constraints/agent-prompt-renderer.ts +2 -6
  420. package/src/core/constraints/capabilities-parser.ts +53 -35
  421. package/src/core/constraints/capabilities-reconcile.ts +166 -0
  422. package/src/core/constraints/checker.ts +82 -133
  423. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +273 -0
  424. package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +176 -0
  425. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +77 -0
  426. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +126 -0
  427. package/src/{__tests__ → core/constraints/checkers/__tests__}/governance-presence.test.ts +83 -9
  428. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +50 -0
  429. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +76 -0
  430. package/src/{__tests__ → core/constraints/checkers/__tests__}/no-hardcoded-credentials.test.ts +4 -3
  431. package/src/core/constraints/checkers/__tests__/no-test-simplification.test.ts +58 -0
  432. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +153 -0
  433. package/src/core/constraints/checkers/capability-sync.ts +75 -96
  434. package/src/core/constraints/checkers/context-doc-sync.ts +15 -25
  435. package/src/core/constraints/checkers/docs-freshness.ts +54 -70
  436. package/src/core/constraints/checkers/governance-presence.ts +7 -25
  437. package/src/core/constraints/checkers/index.ts +9 -2
  438. package/src/core/constraints/checkers/no-test-simplification.ts +4 -3
  439. package/src/core/constraints/checkers/types.ts +60 -4
  440. package/src/core/constraints/context-builder.ts +39 -91
  441. package/src/core/constraints/doc-freshness/runner.ts +2 -1
  442. package/src/core/constraints/git-evidence.ts +120 -0
  443. package/src/core/constraints/injection-drift.ts +13 -60
  444. package/src/core/constraints/injection-writer.ts +212 -0
  445. package/src/core/constraints/usage-report.ts +36 -17
  446. package/src/core/index.ts +4 -2
  447. package/src/core/project-config-loader.ts +49 -9
  448. package/src/core/validators/__tests__/passes-gate.test.ts +2 -56
  449. package/src/core/validators/__tests__/test-output.test.ts +372 -0
  450. package/src/core/validators/passes-gate.ts +86 -248
  451. package/src/core/validators/test-output.ts +128 -0
  452. package/src/failure/__tests__/recorder.test.ts +168 -0
  453. package/src/failure/recorder.ts +12 -10
  454. package/src/gates/CONTEXT.md +7 -4
  455. package/src/gates/__tests__/acceptance-types-single-source.test.ts +68 -0
  456. package/src/gates/__tests__/acceptance.test.ts +4 -86
  457. package/src/gates/__tests__/checker-gate.test.ts +24 -0
  458. package/src/gates/__tests__/effective-gates.test.ts +3 -8
  459. package/src/gates/__tests__/test-verdict-consistency.test.ts +140 -0
  460. package/src/gates/acceptance.ts +19 -84
  461. package/src/gates/checker-gate.ts +12 -15
  462. package/src/gates/command.ts +10 -16
  463. package/src/gates/contract.ts +22 -61
  464. package/src/gates/performance.ts +10 -22
  465. package/src/gates/review.ts +21 -41
  466. package/src/gates/security.ts +10 -22
  467. package/src/gates/types.ts +70 -5
  468. package/src/hooks/CONTEXT.md +1 -1
  469. package/src/hooks/bootstrap.ts +5 -2
  470. package/src/index.ts +0 -4
  471. package/src/knowledge/CONTEXT.md +8 -3
  472. package/src/knowledge/__tests__/audit-labels.test.ts +44 -0
  473. package/src/knowledge/__tests__/flywheel-metrics.test.ts +135 -0
  474. package/src/knowledge/__tests__/import.test.ts +44 -0
  475. package/src/knowledge/__tests__/index-generator.test.ts +53 -0
  476. package/src/knowledge/__tests__/ingest.test.ts +85 -0
  477. package/src/knowledge/__tests__/migration.test.ts +47 -0
  478. package/src/knowledge/__tests__/reference-tracker.test.ts +16 -0
  479. package/src/knowledge/__tests__/store.test.ts +194 -0
  480. package/src/knowledge/audit.ts +67 -71
  481. package/src/knowledge/flywheel-metrics.ts +71 -0
  482. package/src/knowledge/import.ts +4 -18
  483. package/src/knowledge/index-generator.ts +19 -14
  484. package/src/knowledge/ingest.ts +9 -4
  485. package/src/knowledge/migration.ts +11 -8
  486. package/src/knowledge/reference-tracker.ts +12 -8
  487. package/src/knowledge/store.ts +64 -11
  488. package/src/monitoring/CONTEXT.md +8 -3
  489. package/src/monitoring/__tests__/skipped-lines-report.test.ts +178 -0
  490. package/src/monitoring/__tests__/traces.test.ts +57 -0
  491. package/src/monitoring/context-tracker.ts +11 -23
  492. package/src/monitoring/index.ts +1 -1
  493. package/src/monitoring/trace-analyzer.ts +21 -2
  494. package/src/monitoring/traces.ts +39 -36
  495. package/src/release/CONTEXT.md +1 -0
  496. package/src/release/__tests__/barrel.test.ts +60 -0
  497. package/src/release/index.ts +2 -13
  498. package/src/sdd/__tests__/index-generator.test.ts +71 -0
  499. package/src/sdd/index-generator.ts +9 -9
  500. package/src/test-setup/__tests__/project-fixture.test.ts +178 -0
  501. package/src/test-setup/project-fixture.ts +87 -0
  502. package/src/tools/CONTEXT.md +6 -0
  503. package/src/tools/__tests__/registry.test.ts +221 -0
  504. package/src/types/__tests__/types.test.ts +4 -5
  505. package/src/types/constraint.ts +16 -27
  506. package/src/types/passes-gate.ts +10 -30
  507. package/src/types/project-config.ts +1 -1
  508. package/src/types/session.ts +5 -5
  509. package/src/types/trace.ts +10 -0
  510. package/src/utils/__tests__/frontmatter.test.ts +206 -0
  511. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +175 -0
  512. package/src/utils/__tests__/jsonl.test.ts +187 -0
  513. package/src/utils/__tests__/package-version.test.ts +97 -0
  514. package/src/utils/exec.ts +15 -0
  515. package/src/utils/file-walk.ts +11 -2
  516. package/src/utils/frontmatter.ts +121 -0
  517. package/src/utils/jsonl.ts +167 -0
  518. package/src/utils/package-version.ts +27 -0
  519. package/dist/cli/commands/doc-freshness-check.d.ts +0 -53
  520. package/dist/cli/commands/doc-freshness-check.d.ts.map +0 -1
  521. package/dist/cli/commands/doc-freshness-check.js +0 -352
  522. package/dist/cli/commands/doc-freshness-check.js.map +0 -1
  523. package/dist/types/index.d.ts +0 -14
  524. package/dist/types/index.d.ts.map +0 -1
  525. package/dist/types/index.js +0 -39
  526. package/dist/types/index.js.map +0 -1
  527. package/src/__tests__/long-running.test.ts +0 -17
  528. package/src/__tests__/passes-gate-extension.test.ts +0 -151
  529. package/src/cli/commands/__tests__/doc-freshness-check.test.ts +0 -168
  530. package/src/cli/commands/doc-freshness-check.ts +0 -386
  531. package/src/spec/CONTEXT.md +0 -11
  532. package/src/types/index.ts +0 -47
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { report } from '../report';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import * as fs from 'fs/promises';
7
8
 
8
9
  // Mock fs/promises
@@ -19,60 +20,59 @@ jest.mock('chalk', () => ({
19
20
 
20
21
  const mockFs = fs as jest.Mocked<typeof fs>;
21
22
 
23
+ let io: CapturingIO;
24
+ beforeEach(() => {
25
+ io = captureIO();
26
+ });
27
+
22
28
  describe('report command', () => {
23
- let consoleSpy: jest.SpyInstance;
24
29
 
25
30
  beforeEach(() => {
26
31
  jest.clearAllMocks();
27
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
28
- });
29
-
30
- afterEach(() => {
31
- consoleSpy.mockRestore();
32
32
  });
33
33
 
34
34
  describe('report', () => {
35
35
  it('应该生成 JSON 格式报告', async () => {
36
- await report({ format: 'json' });
37
- expect(consoleSpy).toHaveBeenCalled();
38
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
36
+ await report({ format: 'json' }, io);
37
+ expect(io.outText()).not.toBe('');
38
+ const output = io.outText();
39
39
  expect(output).toContain('timestamp');
40
40
  });
41
41
 
42
42
  it('应该生成 Markdown 格式报告', async () => {
43
- await report({ format: 'markdown' });
44
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Harness 检查报告'));
43
+ await report({ format: 'markdown' }, io);
44
+ expect(io.outText()).toContain('Harness 检查报告');
45
45
  });
46
46
 
47
47
  it('应该生成 HTML 格式报告', async () => {
48
- await report({ format: 'html' });
49
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('<!DOCTYPE html>'));
48
+ await report({ format: 'html' }, io);
49
+ expect(io.outText()).toContain('<!DOCTYPE html>');
50
50
  });
51
51
 
52
52
  it('应该保存报告到文件', async () => {
53
53
  mockFs.writeFile.mockResolvedValue(undefined);
54
54
 
55
- await report({ format: 'json', output: 'report.json' });
55
+ await report({ format: 'json', output: 'report.json' }, io);
56
56
  expect(mockFs.writeFile).toHaveBeenCalledWith('report.json', expect.any(String), 'utf-8');
57
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('报告已保存'));
57
+ expect(io.outText()).toContain('报告已保存');
58
58
  });
59
59
 
60
60
  it('应该使用自定义项目路径', async () => {
61
- await report({ format: 'json', projectPath: '/custom/path' });
62
- expect(consoleSpy).toHaveBeenCalled();
61
+ await report({ format: 'json', projectPath: '/custom/path' }, io);
62
+ expect(io.outText()).not.toBe('');
63
63
  });
64
64
 
65
65
  it('Markdown 报告应包含表格结构', async () => {
66
- await report({ format: 'markdown' });
67
- const output = consoleSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n');
66
+ await report({ format: 'markdown' }, io);
67
+ const output = io.outText();
68
68
  expect(output).toContain('| 指标 | 数值 |');
69
69
  expect(output).toContain('| 总约束 |');
70
70
  expect(output).toContain('报告由 @dommaker/harness 生成');
71
71
  });
72
72
 
73
73
  it('HTML 报告应包含完整结构', async () => {
74
- await report({ format: 'html' });
75
- const output = consoleSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n');
74
+ await report({ format: 'html' }, io);
75
+ const output = io.outText();
76
76
  expect(output).toContain('<html lang="zh-CN">');
77
77
  expect(output).toContain('</html>');
78
78
  expect(output).toContain('Harness 检查报告');
@@ -80,8 +80,8 @@ describe('report command', () => {
80
80
  });
81
81
 
82
82
  it('JSON 报告应包含约束统计', async () => {
83
- await report({ format: 'json' });
84
- const output = consoleSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n');
83
+ await report({ format: 'json' }, io);
84
+ const output = io.outText();
85
85
  // 过滤掉 chalk 的 emoji 行,只取 JSON 部分
86
86
  const jsonStart = output.indexOf('{');
87
87
  const jsonEnd = output.lastIndexOf('}');
@@ -96,13 +96,13 @@ describe('report command', () => {
96
96
 
97
97
  it('应该支持输出到文件 (markdown)', async () => {
98
98
  mockFs.writeFile.mockResolvedValue(undefined);
99
- await report({ format: 'markdown', output: 'report.md' });
99
+ await report({ format: 'markdown', output: 'report.md' }, io);
100
100
  expect(mockFs.writeFile).toHaveBeenCalledWith('report.md', expect.stringContaining('# Harness'), 'utf-8');
101
101
  });
102
102
 
103
103
  it('应该支持输出到文件 (html)', async () => {
104
104
  mockFs.writeFile.mockResolvedValue(undefined);
105
- await report({ format: 'html', output: 'report.html' });
105
+ await report({ format: 'html', output: 'report.html' }, io);
106
106
  expect(mockFs.writeFile).toHaveBeenCalledWith('report.html', expect.stringContaining('<!DOCTYPE html>'), 'utf-8');
107
107
  });
108
108
  });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { review, reviewStatus } from '../review';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import { execAsync } from '../../../utils/exec';
7
8
  import { ReviewGate } from '../../../gates/review';
8
9
 
@@ -29,20 +30,12 @@ const mockExec = execAsync as jest.MockedFunction<typeof execAsync>;
29
30
  const MockGate = ReviewGate as jest.MockedClass<typeof ReviewGate>;
30
31
 
31
32
  describe('review command', () => {
32
- let consoleSpy: jest.SpyInstance;
33
- let exitSpy: jest.SpyInstance;
33
+ let io: CapturingIO;
34
34
 
35
35
  beforeEach(() => {
36
- jest.clearAllMocks();
37
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
38
- exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
39
- process.exitCode = 0;
40
- });
41
36
 
42
- afterEach(() => {
43
- consoleSpy.mockRestore();
44
- exitSpy.mockRestore();
45
- process.exitCode = 0;
37
+ io = captureIO();
38
+ jest.clearAllMocks();
46
39
  });
47
40
 
48
41
  describe('review', () => {
@@ -55,10 +48,10 @@ describe('review command', () => {
55
48
  });
56
49
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
57
50
 
58
- await review({});
51
+ const result = await review({}, io);
59
52
 
60
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('代码审查门控检查通过'));
61
- expect(exitSpy).not.toHaveBeenCalled();
53
+ expect(io.outText()).toContain('代码审查门控检查通过');
54
+ expect(result.kind).toBe('ok');
62
55
  });
63
56
 
64
57
  it('should print failure and exit 1 when check fails', async () => {
@@ -70,10 +63,13 @@ describe('review command', () => {
70
63
  });
71
64
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
72
65
 
73
- await review({});
66
+ const result = await review({}, io);
74
67
 
75
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('代码审查门控检查失败'));
76
- expect(exitSpy).toHaveBeenCalledWith(1);
68
+ expect(io.outText()).toContain('代码审查门控检查失败');
69
+ expect(result).toEqual({
70
+ kind: 'fail',
71
+ reason: 'review gate denied: needs approval',
72
+ });
77
73
  });
78
74
 
79
75
  it('should handle errors and exit 1', async () => {
@@ -81,19 +77,25 @@ describe('review command', () => {
81
77
  const mockCheck = jest.fn().mockRejectedValue(new Error('gate error'));
82
78
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
83
79
 
84
- await review({});
80
+ const result = await review({}, io);
85
81
 
86
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('代码审查门控检查出错'));
87
- expect(exitSpy).toHaveBeenCalledWith(1);
82
+ expect(io.outText()).toContain('代码审查门控检查出错');
83
+ expect(result).toEqual({
84
+ kind: 'fail',
85
+ reason: 'review gate error: gate error',
86
+ });
88
87
  });
89
88
 
90
89
  it('should show hint when not in a git repo', async () => {
91
90
  mockExec.mockRejectedValue(new Error('not a git repository'));
92
91
 
93
- await review({});
92
+ const result = await review({}, io);
94
93
 
95
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('需要在 Git 仓库中运行'));
96
- expect(exitSpy).toHaveBeenCalledWith(1);
94
+ expect(io.outText()).toContain('需要在 Git 仓库中运行');
95
+ expect(result).toEqual({
96
+ kind: 'fail',
97
+ reason: 'review gate error: not a git repository',
98
+ });
97
99
  });
98
100
 
99
101
  it('should parse allowedReviewers from comma-separated string', async () => {
@@ -101,7 +103,7 @@ describe('review command', () => {
101
103
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
102
104
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
103
105
 
104
- await review({ allowedReviewers: 'alice, bob, charlie' });
106
+ await review({ allowedReviewers: 'alice, bob, charlie' }, io);
105
107
 
106
108
  expect(MockGate).toHaveBeenCalledWith(
107
109
  expect.objectContaining({
@@ -126,10 +128,10 @@ describe('review command', () => {
126
128
  stderr: '',
127
129
  });
128
130
 
129
- await reviewStatus({});
131
+ await reviewStatus({}, io);
130
132
 
131
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('PR #42'));
132
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('APPROVED'));
133
+ expect(io.outText()).toContain('PR #42');
134
+ expect(io.outText()).toContain('APPROVED');
133
135
  });
134
136
 
135
137
  it('should handle no PR found', async () => {
@@ -137,17 +139,17 @@ describe('review command', () => {
137
139
  .mockResolvedValueOnce({ stdout: 'feature-branch\n', stderr: '' })
138
140
  .mockRejectedValueOnce(new Error('no PR'));
139
141
 
140
- await reviewStatus({});
142
+ await reviewStatus({}, io);
141
143
 
142
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('未找到关联的 PR'));
144
+ expect(io.outText()).toContain('未找到关联的 PR');
143
145
  });
144
146
 
145
147
  it('should handle git errors', async () => {
146
148
  mockExec.mockRejectedValue(new Error('not a git repository'));
147
149
 
148
- await reviewStatus({});
150
+ await reviewStatus({}, io);
149
151
 
150
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('获取审查状态失败'));
152
+ expect(io.outText()).toContain('获取审查状态失败');
151
153
  });
152
154
  });
153
155
  });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { security, auditDetails } from '../security';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import { SecurityGate } from '../../../gates/security';
7
8
 
8
9
  jest.mock('../../../gates/security', () => ({
@@ -22,20 +23,12 @@ jest.mock('chalk', () => ({
22
23
  const MockGate = SecurityGate as jest.MockedClass<typeof SecurityGate>;
23
24
 
24
25
  describe('security command', () => {
25
- let consoleSpy: jest.SpyInstance;
26
- let exitSpy: jest.SpyInstance;
26
+ let io: CapturingIO;
27
27
 
28
28
  beforeEach(() => {
29
- jest.clearAllMocks();
30
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
31
- exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
32
- process.exitCode = 0;
33
- });
34
29
 
35
- afterEach(() => {
36
- consoleSpy.mockRestore();
37
- exitSpy.mockRestore();
38
- process.exitCode = 0;
30
+ io = captureIO();
31
+ jest.clearAllMocks();
39
32
  });
40
33
 
41
34
  describe('security', () => {
@@ -47,10 +40,10 @@ describe('security command', () => {
47
40
  });
48
41
  MockGate.mockImplementation(() => ({ scan: mockScan }) as any);
49
42
 
50
- await security({});
43
+ const result = await security({}, io);
51
44
 
52
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('安全门控检查通过'));
53
- expect(exitSpy).not.toHaveBeenCalled();
45
+ expect(io.outText()).toContain('安全门控检查通过');
46
+ expect(result.kind).toBe('ok');
54
47
  });
55
48
 
56
49
  it('should print failure and exit 1 when scan fails', async () => {
@@ -68,19 +61,22 @@ describe('security command', () => {
68
61
  });
69
62
  MockGate.mockImplementation(() => ({ scan: mockScan }) as any);
70
63
 
71
- await security({});
64
+ const result = await security({}, io);
72
65
 
73
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('安全门控检查失败'));
74
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('pkg-a'));
75
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('pkg-b'));
76
- expect(exitSpy).toHaveBeenCalledWith(1);
66
+ expect(io.outText()).toContain('安全门控检查失败');
67
+ expect(io.outText()).toContain('pkg-a');
68
+ expect(io.outText()).toContain('pkg-b');
69
+ expect(result).toEqual({
70
+ kind: 'fail',
71
+ reason: 'security gate denied (threshold high): vulnerabilities found',
72
+ });
77
73
  });
78
74
 
79
75
  it('should default severity to high', async () => {
80
76
  const mockScan = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
81
77
  MockGate.mockImplementation(() => ({ scan: mockScan }) as any);
82
78
 
83
- await security({});
79
+ await security({}, io);
84
80
 
85
81
  expect(MockGate).toHaveBeenCalledWith(
86
82
  expect.objectContaining({ severityThreshold: 'high' }),
@@ -91,7 +87,7 @@ describe('security command', () => {
91
87
  const mockScan = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
92
88
  MockGate.mockImplementation(() => ({ scan: mockScan }) as any);
93
89
 
94
- await security({ severity: 'critical' });
90
+ await security({ severity: 'critical' }, io);
95
91
 
96
92
  expect(MockGate).toHaveBeenCalledWith(
97
93
  expect.objectContaining({ severityThreshold: 'critical' }),
@@ -108,9 +104,9 @@ describe('security command', () => {
108
104
  });
109
105
  MockGate.mockImplementation(() => ({ scan: mockScan }) as any);
110
106
 
111
- await auditDetails({});
107
+ await auditDetails({}, io);
112
108
 
113
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('未发现安全漏洞'));
109
+ expect(io.outText()).toContain('未发现安全漏洞');
114
110
  });
115
111
 
116
112
  it('should list vulnerabilities when present', async () => {
@@ -127,17 +123,17 @@ describe('security command', () => {
127
123
  });
128
124
  MockGate.mockImplementation(() => ({ scan: mockScan }) as any);
129
125
 
130
- await auditDetails({});
126
+ await auditDetails({}, io);
131
127
 
132
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('发现 2 个漏洞'));
133
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('pkg-a'));
128
+ expect(io.outText()).toContain('发现 2 个漏洞');
129
+ expect(io.outText()).toContain('pkg-a');
134
130
  });
135
131
 
136
132
  it('should default severity to low for audit', async () => {
137
133
  const mockScan = jest.fn().mockResolvedValue({ passed: true, message: 'ok', details: {} });
138
134
  MockGate.mockImplementation(() => ({ scan: mockScan }) as any);
139
135
 
140
- await auditDetails({});
136
+ await auditDetails({}, io);
141
137
 
142
138
  expect(MockGate).toHaveBeenCalledWith(
143
139
  expect.objectContaining({ severityThreshold: 'low' }),
@@ -3,13 +3,17 @@
3
3
  */
4
4
 
5
5
  import * as fs from 'fs';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import * as path from 'path';
7
8
  import { extractBaselineSection, specBaselineCheck } from '../spec-baseline-check';
8
9
 
10
+ let io: CapturingIO;
11
+ beforeEach(() => {
12
+ io = captureIO();
13
+ });
14
+
9
15
  describe('spec-baseline-check', () => {
10
16
  const tempDir = path.join(process.cwd(), 'temp-test-spec-baseline');
11
- let consoleSpy: jest.SpyInstance;
12
- let errorSpy: jest.SpyInstance;
13
17
  let originalExitCode: string | number | undefined;
14
18
 
15
19
  beforeAll(() => {
@@ -26,15 +30,10 @@ describe('spec-baseline-check', () => {
26
30
 
27
31
  beforeEach(() => {
28
32
  jest.clearAllMocks();
29
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
30
- errorSpy = jest.spyOn(console, 'error').mockImplementation();
31
33
  originalExitCode = process.exitCode;
32
- process.exitCode = 0;
33
34
  });
34
35
 
35
36
  afterEach(() => {
36
- consoleSpy.mockRestore();
37
- errorSpy.mockRestore();
38
37
  process.exitCode = originalExitCode;
39
38
  });
40
39
 
@@ -153,19 +152,20 @@ describe('spec-baseline-check', () => {
153
152
 
154
153
  describe('specBaselineCheck CLI', () => {
155
154
  it('应该在文件不存在时报错', async () => {
156
- await specBaselineCheck('/nonexistent/spec.md');
155
+ const result = await specBaselineCheck('/nonexistent/spec.md', {}, io);
157
156
 
158
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('文件不存在'));
159
- expect(process.exitCode).toBe(1);
157
+ expect(io.errText()).toContain('文件不存在');
158
+ expect(result).toEqual({ kind: 'usage-error', reason: expect.stringContaining('spec 文件不存在') });
160
159
  });
161
160
 
162
161
  it('没有 Baseline section 应该提示', async () => {
163
162
  const specPath = path.join(tempDir, 'no-baseline.md');
164
163
  fs.writeFileSync(specPath, '# Spec\n## Overview\nNo baseline here.');
165
164
 
166
- await specBaselineCheck(specPath, { projectPath: tempDir });
165
+ const result = await specBaselineCheck(specPath, { projectPath: tempDir }, io);
167
166
 
168
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('未找到'));
167
+ expect(io.outText()).toContain('未找到');
168
+ expect(result).toEqual({ kind: 'skip', reason: expect.stringContaining('无可判定项') });
169
169
  });
170
170
 
171
171
  it('应该输出 table 格式结果', async () => {
@@ -182,9 +182,9 @@ describe('spec-baseline-check', () => {
182
182
  '- 普通文本描述',
183
183
  ].join('\n'));
184
184
 
185
- await specBaselineCheck(specPath, { projectPath: subDir });
185
+ await specBaselineCheck(specPath, { projectPath: subDir }, io);
186
186
 
187
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
187
+ const output = io.outText();
188
188
  expect(output).toContain('前置条件检查');
189
189
  });
190
190
 
@@ -196,9 +196,9 @@ describe('spec-baseline-check', () => {
196
196
  '- `package.json` exists',
197
197
  ].join('\n'));
198
198
 
199
- await specBaselineCheck(specPath, { json: true, projectPath: tempDir });
199
+ await specBaselineCheck(specPath, { json: true, projectPath: tempDir }, io);
200
200
 
201
- const jsonCall = consoleSpy.mock.calls.find((c: any[]) => {
201
+ const jsonCall = io.outRecords().map(chunk => [chunk] as string[]).find((c: any[]) => {
202
202
  try {
203
203
  const parsed = JSON.parse(c[0]);
204
204
  return Array.isArray(parsed);
@@ -207,7 +207,7 @@ describe('spec-baseline-check', () => {
207
207
  }
208
208
  });
209
209
  expect(jsonCall).toBeTruthy();
210
- const parsed = JSON.parse(jsonCall[0]);
210
+ const parsed = JSON.parse(jsonCall![0]);
211
211
  expect(parsed.length).toBe(1);
212
212
  expect(parsed[0]).toHaveProperty('prerequisite');
213
213
  expect(parsed[0]).toHaveProperty('satisfied');
@@ -218,9 +218,9 @@ describe('spec-baseline-check', () => {
218
218
  const specPath = path.join(tempDir, 'empty-baseline.md');
219
219
  fs.writeFileSync(specPath, '# Test\n## Baseline\n\n## Next');
220
220
 
221
- await specBaselineCheck(specPath, { json: true, projectPath: tempDir });
221
+ await specBaselineCheck(specPath, { json: true, projectPath: tempDir }, io);
222
222
 
223
- const jsonCall = consoleSpy.mock.calls.find((c: any[]) => {
223
+ const jsonCall = io.outRecords().map(chunk => [chunk] as string[]).find((c: any[]) => {
224
224
  try {
225
225
  const parsed = JSON.parse(c[0]);
226
226
  return parsed.prerequisites !== undefined;
@@ -229,7 +229,7 @@ describe('spec-baseline-check', () => {
229
229
  }
230
230
  });
231
231
  expect(jsonCall).toBeTruthy();
232
- expect(JSON.parse(jsonCall[0]).prerequisites).toHaveLength(0);
232
+ expect(JSON.parse(jsonCall![0]).prerequisites).toHaveLength(0);
233
233
  });
234
234
  });
235
235
  });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { specValidate, listSpecTypes } from '../spec';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import { SpecValidator, validateAllSpecs } from '../../../core/spec/validator';
7
8
  import type { BatchSpecValidationResult, SpecValidationResult } from '../../../types/spec';
8
9
 
@@ -27,8 +28,12 @@ jest.mock('chalk', () => ({
27
28
  const MockSpecValidator = SpecValidator as jest.Mocked<typeof SpecValidator>;
28
29
  const mockValidateAllSpecs = validateAllSpecs as jest.MockedFunction<typeof validateAllSpecs>;
29
30
 
31
+ let io: CapturingIO;
32
+ beforeEach(() => {
33
+ io = captureIO();
34
+ });
35
+
30
36
  describe('spec command', () => {
31
- let consoleSpy: jest.SpyInstance;
32
37
  let mockValidator: {
33
38
  setConfig: jest.Mock;
34
39
  validateFile: jest.Mock;
@@ -36,8 +41,6 @@ describe('spec command', () => {
36
41
 
37
42
  beforeEach(() => {
38
43
  jest.clearAllMocks();
39
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
40
- process.exitCode = 0;
41
44
 
42
45
  mockValidator = {
43
46
  setConfig: jest.fn(),
@@ -46,11 +49,6 @@ describe('spec command', () => {
46
49
  (MockSpecValidator.getInstance as jest.Mock).mockReturnValue(mockValidator);
47
50
  });
48
51
 
49
- afterEach(() => {
50
- consoleSpy.mockRestore();
51
- process.exitCode = 0;
52
- });
53
-
54
52
  describe('specValidate', () => {
55
53
  describe('单文件验证', () => {
56
54
  it('应该验证单个文件', async () => {
@@ -63,8 +61,8 @@ describe('spec command', () => {
63
61
  };
64
62
  mockValidator.validateFile.mockResolvedValue(mockResult);
65
63
 
66
- await specValidate({ file: 'test.yml' });
67
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('验证通过'));
64
+ await specValidate({ file: 'test.yml' }, io);
65
+ expect(io.outText()).toContain('验证通过');
68
66
  });
69
67
 
70
68
  it('应该显示验证错误', async () => {
@@ -77,9 +75,9 @@ describe('spec command', () => {
77
75
  };
78
76
  mockValidator.validateFile.mockResolvedValue(mockResult);
79
77
 
80
- await specValidate({ file: 'test.yml' });
81
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('验证失败'));
82
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('错误'));
78
+ await specValidate({ file: 'test.yml' }, io);
79
+ expect(io.outText()).toContain('验证失败');
80
+ expect(io.outText()).toContain('错误');
83
81
  });
84
82
 
85
83
  it('应该显示警告', async () => {
@@ -92,8 +90,8 @@ describe('spec command', () => {
92
90
  };
93
91
  mockValidator.validateFile.mockResolvedValue(mockResult);
94
92
 
95
- await specValidate({ file: 'test.yml' });
96
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('警告'));
93
+ await specValidate({ file: 'test.yml' }, io);
94
+ expect(io.outText()).toContain('警告');
97
95
  });
98
96
 
99
97
  it('应该显示详细指标', async () => {
@@ -107,8 +105,8 @@ describe('spec command', () => {
107
105
  };
108
106
  mockValidator.validateFile.mockResolvedValue(mockResult);
109
107
 
110
- await specValidate({ file: 'test.yml', verbose: true });
111
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('指标'));
108
+ await specValidate({ file: 'test.yml', verbose: true }, io);
109
+ expect(io.outText()).toContain('指标');
112
110
  });
113
111
  });
114
112
 
@@ -123,8 +121,8 @@ describe('spec command', () => {
123
121
  };
124
122
  mockValidateAllSpecs.mockResolvedValue(mockResult);
125
123
 
126
- await specValidate({});
127
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('所有 Spec 文件验证通过'));
124
+ await specValidate({}, io);
125
+ expect(io.outText()).toContain('所有 Spec 文件验证通过');
128
126
  });
129
127
 
130
128
  it('应该显示失败统计', async () => {
@@ -139,8 +137,8 @@ describe('spec command', () => {
139
137
  };
140
138
  mockValidateAllSpecs.mockResolvedValue(mockResult);
141
139
 
142
- await specValidate({});
143
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('失败: 2'));
140
+ await specValidate({}, io);
141
+ expect(io.outText()).toContain('失败: 2');
144
142
  });
145
143
 
146
144
  it('应该处理无 Spec 文件情况', async () => {
@@ -153,11 +151,25 @@ describe('spec command', () => {
153
151
  };
154
152
  mockValidateAllSpecs.mockResolvedValue(mockResult);
155
153
 
156
- await specValidate({});
157
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('没有找到 Spec 文件'));
154
+ await specValidate({}, io);
155
+ expect(io.outText()).toContain('没有找到 Spec 文件');
156
+ });
157
+
158
+ it('批量验证有失败:fail + 失败数原因', async () => {
159
+ const mockResult: BatchSpecValidationResult = {
160
+ total: 2,
161
+ passed: 1,
162
+ failed: 1,
163
+ warnings: 0,
164
+ results: [],
165
+ };
166
+ mockValidateAllSpecs.mockResolvedValue(mockResult);
167
+
168
+ const result = await specValidate({}, io);
169
+ expect(result).toEqual({ kind: 'fail', reason: '1 个 Spec 文件验证失败' });
158
170
  });
159
171
 
160
- it('应该设置退出码当有失败', async () => {
172
+ it('--staged 有失败:ok(历史退出码面 0,冻结不变)', async () => {
161
173
  const mockResult: BatchSpecValidationResult = {
162
174
  total: 2,
163
175
  passed: 1,
@@ -167,8 +179,8 @@ describe('spec command', () => {
167
179
  };
168
180
  mockValidateAllSpecs.mockResolvedValue(mockResult);
169
181
 
170
- await specValidate({});
171
- expect(process.exitCode).toBe(1);
182
+ const result = await specValidate({ staged: true }, io);
183
+ expect(result).toEqual({ kind: 'ok' });
172
184
  });
173
185
  });
174
186
 
@@ -178,7 +190,7 @@ describe('spec command', () => {
178
190
  total: 0, passed: 0, failed: 0, warnings: 0, results: [],
179
191
  });
180
192
 
181
- await specValidate({ schema: 'custom-schema.ts' });
193
+ await specValidate({ schema: 'custom-schema.ts' }, io);
182
194
  expect(mockValidator.setConfig).toHaveBeenCalled();
183
195
  });
184
196
  });
@@ -186,8 +198,8 @@ describe('spec command', () => {
186
198
 
187
199
  describe('listSpecTypes', () => {
188
200
  it('应该列出支持的 Spec 类型', () => {
189
- listSpecTypes();
190
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('支持的 Spec 类型'));
201
+ listSpecTypes({}, io);
202
+ expect(io.outText()).toContain('支持的 Spec 类型');
191
203
  });
192
204
  });
193
205
  });