@dommaker/harness 1.3.0 → 1.4.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 (517) hide show
  1. package/CHANGELOG.md +8 -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 +76 -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 +29 -214
  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 +21 -15
  53. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  54. package/dist/cli/commands/knowledge.js +214 -199
  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 +2 -1
  115. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  116. package/dist/cli/commands/update-user-model.js +31 -45
  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 +64 -115
  143. package/dist/core/constraints/checker.js.map +1 -1
  144. package/dist/core/constraints/checkers/capability-sync.d.ts +4 -3
  145. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  146. package/dist/core/constraints/checkers/capability-sync.js +29 -69
  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 +37 -63
  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/no-test-simplification.d.ts.map +1 -1
  160. package/dist/core/constraints/checkers/no-test-simplification.js +4 -3
  161. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  162. package/dist/core/constraints/checkers/types.d.ts +2 -2
  163. package/dist/core/constraints/context-builder.d.ts +15 -14
  164. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  165. package/dist/core/constraints/context-builder.js +32 -96
  166. package/dist/core/constraints/context-builder.js.map +1 -1
  167. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  168. package/dist/core/constraints/doc-freshness/runner.js +2 -1
  169. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  170. package/dist/core/constraints/git-evidence.d.ts +60 -0
  171. package/dist/core/constraints/git-evidence.d.ts.map +1 -0
  172. package/dist/core/constraints/git-evidence.js +88 -0
  173. package/dist/core/constraints/git-evidence.js.map +1 -0
  174. package/dist/core/constraints/injection-drift.d.ts +3 -20
  175. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  176. package/dist/core/constraints/injection-drift.js +10 -57
  177. package/dist/core/constraints/injection-drift.js.map +1 -1
  178. package/dist/core/constraints/injection-writer.d.ts +113 -0
  179. package/dist/core/constraints/injection-writer.d.ts.map +1 -0
  180. package/dist/core/constraints/injection-writer.js +228 -0
  181. package/dist/core/constraints/injection-writer.js.map +1 -0
  182. package/dist/core/constraints/usage-report.d.ts +22 -1
  183. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  184. package/dist/core/constraints/usage-report.js +27 -20
  185. package/dist/core/constraints/usage-report.js.map +1 -1
  186. package/dist/core/index.d.ts +2 -2
  187. package/dist/core/index.d.ts.map +1 -1
  188. package/dist/core/index.js +4 -2
  189. package/dist/core/index.js.map +1 -1
  190. package/dist/core/project-config-loader.d.ts +32 -1
  191. package/dist/core/project-config-loader.d.ts.map +1 -1
  192. package/dist/core/project-config-loader.js +39 -9
  193. package/dist/core/project-config-loader.js.map +1 -1
  194. package/dist/core/validators/passes-gate.d.ts +15 -64
  195. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  196. package/dist/core/validators/passes-gate.js +84 -222
  197. package/dist/core/validators/passes-gate.js.map +1 -1
  198. package/dist/core/validators/test-output.d.ts +58 -0
  199. package/dist/core/validators/test-output.d.ts.map +1 -0
  200. package/dist/core/validators/test-output.js +109 -0
  201. package/dist/core/validators/test-output.js.map +1 -0
  202. package/dist/failure/recorder.d.ts +6 -0
  203. package/dist/failure/recorder.d.ts.map +1 -1
  204. package/dist/failure/recorder.js +12 -8
  205. package/dist/failure/recorder.js.map +1 -1
  206. package/dist/gates/acceptance.d.ts +1 -48
  207. package/dist/gates/acceptance.d.ts.map +1 -1
  208. package/dist/gates/acceptance.js +6 -38
  209. package/dist/gates/acceptance.js.map +1 -1
  210. package/dist/gates/checker-gate.d.ts.map +1 -1
  211. package/dist/gates/checker-gate.js +6 -11
  212. package/dist/gates/checker-gate.js.map +1 -1
  213. package/dist/gates/command.d.ts.map +1 -1
  214. package/dist/gates/command.js +10 -22
  215. package/dist/gates/command.js.map +1 -1
  216. package/dist/gates/contract.d.ts.map +1 -1
  217. package/dist/gates/contract.js +22 -61
  218. package/dist/gates/contract.js.map +1 -1
  219. package/dist/gates/performance.d.ts.map +1 -1
  220. package/dist/gates/performance.js +13 -31
  221. package/dist/gates/performance.js.map +1 -1
  222. package/dist/gates/review.d.ts.map +1 -1
  223. package/dist/gates/review.js +21 -53
  224. package/dist/gates/review.js.map +1 -1
  225. package/dist/gates/security.d.ts.map +1 -1
  226. package/dist/gates/security.js +14 -32
  227. package/dist/gates/security.js.map +1 -1
  228. package/dist/gates/types.d.ts +28 -5
  229. package/dist/gates/types.d.ts.map +1 -1
  230. package/dist/gates/types.js +38 -1
  231. package/dist/gates/types.js.map +1 -1
  232. package/dist/hooks/bootstrap.d.ts +2 -0
  233. package/dist/hooks/bootstrap.d.ts.map +1 -1
  234. package/dist/hooks/bootstrap.js +5 -2
  235. package/dist/hooks/bootstrap.js.map +1 -1
  236. package/dist/index.d.ts +1 -2
  237. package/dist/index.d.ts.map +1 -1
  238. package/dist/index.js.map +1 -1
  239. package/dist/knowledge/audit.d.ts +7 -2
  240. package/dist/knowledge/audit.d.ts.map +1 -1
  241. package/dist/knowledge/audit.js +59 -79
  242. package/dist/knowledge/audit.js.map +1 -1
  243. package/dist/knowledge/flywheel-metrics.d.ts +39 -0
  244. package/dist/knowledge/flywheel-metrics.d.ts.map +1 -0
  245. package/dist/knowledge/flywheel-metrics.js +47 -0
  246. package/dist/knowledge/flywheel-metrics.js.map +1 -0
  247. package/dist/knowledge/import.d.ts.map +1 -1
  248. package/dist/knowledge/import.js +4 -14
  249. package/dist/knowledge/import.js.map +1 -1
  250. package/dist/knowledge/index-generator.d.ts.map +1 -1
  251. package/dist/knowledge/index-generator.js +17 -14
  252. package/dist/knowledge/index-generator.js.map +1 -1
  253. package/dist/knowledge/ingest.d.ts.map +1 -1
  254. package/dist/knowledge/ingest.js +9 -4
  255. package/dist/knowledge/ingest.js.map +1 -1
  256. package/dist/knowledge/migration.d.ts.map +1 -1
  257. package/dist/knowledge/migration.js +11 -8
  258. package/dist/knowledge/migration.js.map +1 -1
  259. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  260. package/dist/knowledge/reference-tracker.js +12 -9
  261. package/dist/knowledge/reference-tracker.js.map +1 -1
  262. package/dist/knowledge/store.d.ts +15 -0
  263. package/dist/knowledge/store.d.ts.map +1 -1
  264. package/dist/knowledge/store.js +59 -10
  265. package/dist/knowledge/store.js.map +1 -1
  266. package/dist/monitoring/context-tracker.d.ts.map +1 -1
  267. package/dist/monitoring/context-tracker.js +11 -20
  268. package/dist/monitoring/context-tracker.js.map +1 -1
  269. package/dist/monitoring/index.d.ts +1 -1
  270. package/dist/monitoring/index.d.ts.map +1 -1
  271. package/dist/monitoring/index.js +1 -2
  272. package/dist/monitoring/index.js.map +1 -1
  273. package/dist/monitoring/trace-analyzer.d.ts +15 -0
  274. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  275. package/dist/monitoring/trace-analyzer.js +19 -2
  276. package/dist/monitoring/trace-analyzer.js.map +1 -1
  277. package/dist/monitoring/traces.d.ts +21 -2
  278. package/dist/monitoring/traces.d.ts.map +1 -1
  279. package/dist/monitoring/traces.js +38 -29
  280. package/dist/monitoring/traces.js.map +1 -1
  281. package/dist/release/index.d.ts +2 -2
  282. package/dist/release/index.d.ts.map +1 -1
  283. package/dist/release/index.js +1 -5
  284. package/dist/release/index.js.map +1 -1
  285. package/dist/sdd/index-generator.js +10 -11
  286. package/dist/sdd/index-generator.js.map +1 -1
  287. package/dist/test-setup/project-fixture.d.ts +48 -0
  288. package/dist/test-setup/project-fixture.d.ts.map +1 -0
  289. package/dist/test-setup/project-fixture.js +106 -0
  290. package/dist/test-setup/project-fixture.js.map +1 -0
  291. package/dist/types/constraint.d.ts +0 -18
  292. package/dist/types/constraint.d.ts.map +1 -1
  293. package/dist/types/constraint.js.map +1 -1
  294. package/dist/types/passes-gate.d.ts +10 -25
  295. package/dist/types/passes-gate.d.ts.map +1 -1
  296. package/dist/types/project-config.d.ts +1 -1
  297. package/dist/types/session.d.ts +6 -5
  298. package/dist/types/session.d.ts.map +1 -1
  299. package/dist/utils/exec.d.ts +10 -0
  300. package/dist/utils/exec.d.ts.map +1 -1
  301. package/dist/utils/exec.js +12 -0
  302. package/dist/utils/exec.js.map +1 -1
  303. package/dist/utils/file-walk.d.ts +9 -1
  304. package/dist/utils/file-walk.d.ts.map +1 -1
  305. package/dist/utils/file-walk.js +10 -1
  306. package/dist/utils/file-walk.js.map +1 -1
  307. package/dist/utils/frontmatter.d.ts +73 -0
  308. package/dist/utils/frontmatter.d.ts.map +1 -0
  309. package/dist/utils/frontmatter.js +141 -0
  310. package/dist/utils/frontmatter.js.map +1 -0
  311. package/dist/utils/jsonl.d.ts +92 -0
  312. package/dist/utils/jsonl.d.ts.map +1 -0
  313. package/dist/utils/jsonl.js +166 -0
  314. package/dist/utils/jsonl.js.map +1 -0
  315. package/dist/utils/package-version.d.ts +16 -0
  316. package/dist/utils/package-version.d.ts.map +1 -0
  317. package/dist/utils/package-version.js +63 -0
  318. package/dist/utils/package-version.js.map +1 -0
  319. package/package.json +1 -1
  320. package/src/CONTEXT.md +7 -2
  321. package/src/__tests__/acceptance-gate.test.ts +3 -1
  322. package/src/__tests__/checker-extra.test.ts +7 -1218
  323. package/src/__tests__/context-files-resolution.test.ts +116 -0
  324. package/src/__tests__/effective-constraints.test.ts +10 -13
  325. package/src/__tests__/exec.test.ts +35 -1
  326. package/src/__tests__/layering.test.ts +152 -0
  327. package/src/__tests__/passes-gate-extra.test.ts +0 -145
  328. package/src/__tests__/passes-gate.test.ts +142 -106
  329. package/src/__tests__/skip-semantics.test.ts +5 -9
  330. package/src/cli/__tests__/command-contract.test.ts +170 -0
  331. package/src/cli/command-contract.ts +108 -0
  332. package/src/cli/commands/CONTEXT.md +22 -7
  333. package/src/cli/commands/__tests__/acceptance.test.ts +40 -33
  334. package/src/cli/commands/__tests__/analyze-sessions.test.ts +35 -29
  335. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +101 -0
  336. package/src/cli/commands/__tests__/check-drift.test.ts +27 -34
  337. package/src/cli/commands/__tests__/check-skip-output.test.ts +21 -24
  338. package/src/cli/commands/__tests__/check.test.ts +378 -382
  339. package/src/cli/commands/__tests__/command.test.ts +85 -70
  340. package/src/cli/commands/__tests__/constraints-report.test.ts +100 -71
  341. package/src/cli/commands/__tests__/constraints-retire.test.ts +152 -95
  342. package/src/cli/commands/__tests__/contract.test.ts +27 -34
  343. package/src/cli/commands/__tests__/failure.test.ts +31 -34
  344. package/src/cli/commands/__tests__/init-injection.test.ts +58 -28
  345. package/src/cli/commands/__tests__/init.test.ts +77 -48
  346. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +108 -0
  347. package/src/cli/commands/__tests__/knowledge.test.ts +69 -43
  348. package/src/cli/commands/__tests__/passes-gate.test.ts +43 -45
  349. package/src/cli/commands/__tests__/performance.test.ts +22 -23
  350. package/src/cli/commands/__tests__/posteval-plan.test.ts +39 -38
  351. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +157 -0
  352. package/src/cli/commands/__tests__/project-path-convention.test.ts +169 -0
  353. package/src/cli/commands/__tests__/registry.test.ts +69 -35
  354. package/src/cli/commands/__tests__/release.test.ts +209 -80
  355. package/src/cli/commands/__tests__/report-extra.test.ts +16 -16
  356. package/src/cli/commands/__tests__/report.test.ts +25 -25
  357. package/src/cli/commands/__tests__/review.test.ts +33 -31
  358. package/src/cli/commands/__tests__/security.test.ts +23 -27
  359. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +20 -20
  360. package/src/cli/commands/__tests__/spec.test.ts +41 -29
  361. package/src/cli/commands/__tests__/status-extra.test.ts +18 -18
  362. package/src/cli/commands/__tests__/status-skipped-lines.test.ts +72 -0
  363. package/src/cli/commands/__tests__/status.test.ts +34 -34
  364. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +77 -78
  365. package/src/cli/commands/__tests__/sync-docs.test.ts +72 -74
  366. package/src/cli/commands/__tests__/update-user-model.test.ts +62 -33
  367. package/src/cli/commands/__tests__/validate.test.ts +27 -29
  368. package/src/cli/commands/acceptance.ts +35 -24
  369. package/src/cli/commands/analyze-sessions.ts +25 -24
  370. package/src/cli/commands/check.ts +88 -65
  371. package/src/cli/commands/command.ts +30 -17
  372. package/src/cli/commands/constraints-report.ts +37 -34
  373. package/src/cli/commands/constraints-retire.ts +58 -55
  374. package/src/cli/commands/constraints.ts +10 -27
  375. package/src/cli/commands/contract.ts +51 -42
  376. package/src/cli/commands/definitions.ts +36 -222
  377. package/src/cli/commands/failure.ts +33 -26
  378. package/src/cli/commands/init.ts +166 -220
  379. package/src/cli/commands/knowledge.ts +225 -203
  380. package/src/cli/commands/passes-gate.ts +58 -68
  381. package/src/cli/commands/performance.ts +23 -18
  382. package/src/cli/commands/posteval-plan.ts +29 -15
  383. package/src/cli/commands/release.ts +70 -59
  384. package/src/cli/commands/report.ts +9 -5
  385. package/src/cli/commands/review.ts +39 -29
  386. package/src/cli/commands/sdd.ts +7 -5
  387. package/src/cli/commands/security.ts +35 -26
  388. package/src/cli/commands/spec-baseline-check.ts +16 -11
  389. package/src/cli/commands/spec.ts +66 -53
  390. package/src/cli/commands/status.ts +59 -53
  391. package/src/cli/commands/sync-docs/agents-syncer.ts +5 -2
  392. package/src/cli/commands/sync-docs/context-syncer.ts +3 -2
  393. package/src/cli/commands/sync-docs/index.ts +73 -100
  394. package/src/cli/commands/sync-docs/project-reader.ts +17 -18
  395. package/src/cli/commands/update-user-model.ts +33 -45
  396. package/src/cli/commands/validate.ts +31 -25
  397. package/src/cli/session-mining/__tests__/transcript.test.ts +118 -0
  398. package/src/cli/session-mining/transcript.ts +26 -8
  399. package/src/context/session-manager.ts +19 -33
  400. package/src/core/CONTEXT.md +9 -6
  401. package/src/core/constraints/__tests__/capabilities-reconcile.test.ts +200 -0
  402. package/src/core/constraints/__tests__/capability-counts.test.ts +94 -0
  403. package/src/core/constraints/__tests__/git-evidence.test.ts +208 -0
  404. package/src/core/constraints/__tests__/injection-drift.test.ts +3 -10
  405. package/src/core/constraints/__tests__/injection-writer.test.ts +231 -0
  406. package/src/core/constraints/__tests__/trace-injection.test.ts +71 -0
  407. package/src/core/constraints/__tests__/usage-report.test.ts +112 -0
  408. package/src/core/constraints/agent-prompt-renderer.ts +2 -6
  409. package/src/core/constraints/capabilities-parser.ts +53 -35
  410. package/src/core/constraints/capabilities-reconcile.ts +166 -0
  411. package/src/core/constraints/checker.ts +75 -130
  412. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +214 -0
  413. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +77 -0
  414. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +117 -0
  415. package/src/{__tests__ → core/constraints/checkers/__tests__}/governance-presence.test.ts +83 -9
  416. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +50 -0
  417. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +76 -0
  418. package/src/{__tests__ → core/constraints/checkers/__tests__}/no-hardcoded-credentials.test.ts +4 -3
  419. package/src/core/constraints/checkers/__tests__/no-test-simplification.test.ts +58 -0
  420. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +145 -0
  421. package/src/core/constraints/checkers/capability-sync.ts +29 -88
  422. package/src/core/constraints/checkers/context-doc-sync.ts +15 -25
  423. package/src/core/constraints/checkers/docs-freshness.ts +40 -69
  424. package/src/core/constraints/checkers/governance-presence.ts +7 -25
  425. package/src/core/constraints/checkers/no-test-simplification.ts +4 -3
  426. package/src/core/constraints/checkers/types.ts +2 -2
  427. package/src/core/constraints/context-builder.ts +39 -91
  428. package/src/core/constraints/doc-freshness/runner.ts +2 -1
  429. package/src/core/constraints/git-evidence.ts +120 -0
  430. package/src/core/constraints/injection-drift.ts +13 -60
  431. package/src/core/constraints/injection-writer.ts +212 -0
  432. package/src/core/constraints/usage-report.ts +36 -17
  433. package/src/core/index.ts +4 -2
  434. package/src/core/project-config-loader.ts +49 -9
  435. package/src/core/validators/__tests__/passes-gate.test.ts +2 -56
  436. package/src/core/validators/__tests__/test-output.test.ts +372 -0
  437. package/src/core/validators/passes-gate.ts +86 -248
  438. package/src/core/validators/test-output.ts +128 -0
  439. package/src/failure/__tests__/recorder.test.ts +168 -0
  440. package/src/failure/recorder.ts +12 -10
  441. package/src/gates/CONTEXT.md +6 -3
  442. package/src/gates/__tests__/acceptance-types-single-source.test.ts +68 -0
  443. package/src/gates/__tests__/acceptance.test.ts +4 -86
  444. package/src/gates/__tests__/effective-gates.test.ts +3 -8
  445. package/src/gates/__tests__/test-verdict-consistency.test.ts +140 -0
  446. package/src/gates/acceptance.ts +19 -84
  447. package/src/gates/checker-gate.ts +8 -7
  448. package/src/gates/command.ts +10 -16
  449. package/src/gates/contract.ts +22 -61
  450. package/src/gates/performance.ts +10 -22
  451. package/src/gates/review.ts +21 -41
  452. package/src/gates/security.ts +10 -22
  453. package/src/gates/types.ts +70 -5
  454. package/src/hooks/CONTEXT.md +1 -1
  455. package/src/hooks/bootstrap.ts +5 -2
  456. package/src/index.ts +0 -4
  457. package/src/knowledge/CONTEXT.md +8 -3
  458. package/src/knowledge/__tests__/audit-labels.test.ts +44 -0
  459. package/src/knowledge/__tests__/flywheel-metrics.test.ts +135 -0
  460. package/src/knowledge/__tests__/import.test.ts +44 -0
  461. package/src/knowledge/__tests__/index-generator.test.ts +53 -0
  462. package/src/knowledge/__tests__/ingest.test.ts +85 -0
  463. package/src/knowledge/__tests__/migration.test.ts +47 -0
  464. package/src/knowledge/__tests__/reference-tracker.test.ts +16 -0
  465. package/src/knowledge/__tests__/store.test.ts +194 -0
  466. package/src/knowledge/audit.ts +67 -71
  467. package/src/knowledge/flywheel-metrics.ts +71 -0
  468. package/src/knowledge/import.ts +4 -18
  469. package/src/knowledge/index-generator.ts +19 -14
  470. package/src/knowledge/ingest.ts +9 -4
  471. package/src/knowledge/migration.ts +11 -8
  472. package/src/knowledge/reference-tracker.ts +12 -8
  473. package/src/knowledge/store.ts +64 -11
  474. package/src/monitoring/CONTEXT.md +8 -3
  475. package/src/monitoring/__tests__/skipped-lines-report.test.ts +178 -0
  476. package/src/monitoring/__tests__/traces.test.ts +57 -0
  477. package/src/monitoring/context-tracker.ts +11 -23
  478. package/src/monitoring/index.ts +1 -1
  479. package/src/monitoring/trace-analyzer.ts +21 -2
  480. package/src/monitoring/traces.ts +39 -36
  481. package/src/release/CONTEXT.md +1 -0
  482. package/src/release/__tests__/barrel.test.ts +60 -0
  483. package/src/release/index.ts +2 -13
  484. package/src/sdd/__tests__/index-generator.test.ts +71 -0
  485. package/src/sdd/index-generator.ts +9 -9
  486. package/src/test-setup/__tests__/project-fixture.test.ts +178 -0
  487. package/src/test-setup/project-fixture.ts +87 -0
  488. package/src/tools/CONTEXT.md +6 -0
  489. package/src/tools/__tests__/registry.test.ts +221 -0
  490. package/src/types/__tests__/types.test.ts +4 -5
  491. package/src/types/constraint.ts +0 -25
  492. package/src/types/passes-gate.ts +10 -30
  493. package/src/types/project-config.ts +1 -1
  494. package/src/types/session.ts +5 -5
  495. package/src/utils/__tests__/frontmatter.test.ts +206 -0
  496. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +175 -0
  497. package/src/utils/__tests__/jsonl.test.ts +187 -0
  498. package/src/utils/__tests__/package-version.test.ts +97 -0
  499. package/src/utils/exec.ts +15 -0
  500. package/src/utils/file-walk.ts +11 -2
  501. package/src/utils/frontmatter.ts +121 -0
  502. package/src/utils/jsonl.ts +167 -0
  503. package/src/utils/package-version.ts +27 -0
  504. package/dist/cli/commands/doc-freshness-check.d.ts +0 -53
  505. package/dist/cli/commands/doc-freshness-check.d.ts.map +0 -1
  506. package/dist/cli/commands/doc-freshness-check.js +0 -352
  507. package/dist/cli/commands/doc-freshness-check.js.map +0 -1
  508. package/dist/types/index.d.ts +0 -14
  509. package/dist/types/index.d.ts.map +0 -1
  510. package/dist/types/index.js +0 -39
  511. package/dist/types/index.js.map +0 -1
  512. package/src/__tests__/long-running.test.ts +0 -17
  513. package/src/__tests__/passes-gate-extension.test.ts +0 -151
  514. package/src/cli/commands/__tests__/doc-freshness-check.test.ts +0 -168
  515. package/src/cli/commands/doc-freshness-check.ts +0 -386
  516. package/src/spec/CONTEXT.md +0 -11
  517. package/src/types/index.ts +0 -47
@@ -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
  });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { status } from '../status';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import * as fs from 'fs';
7
8
  import { TraceAnalyzer } from '../../../monitoring/trace-analyzer';
8
9
 
@@ -42,22 +43,21 @@ jest.mock('chalk', () => ({
42
43
  const mockFs = fs as jest.Mocked<typeof fs>;
43
44
  const MockTraceAnalyzer = TraceAnalyzer as jest.MockedClass<typeof TraceAnalyzer>;
44
45
 
46
+ let io: CapturingIO;
47
+ beforeEach(() => {
48
+ io = captureIO();
49
+ });
50
+
45
51
  describe('status command - 补充覆盖', () => {
46
- let consoleSpy: jest.SpyInstance;
47
52
 
48
53
  beforeEach(() => {
49
54
  jest.clearAllMocks();
50
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
51
55
  mockFs.existsSync.mockReturnValue(true);
52
56
  mockFs.readFileSync.mockReturnValue('');
53
57
  mockFs.writeFileSync.mockImplementation();
54
58
  mockFs.mkdirSync.mockImplementation();
55
59
  });
56
60
 
57
- afterEach(() => {
58
- consoleSpy.mockRestore();
59
- });
60
-
61
61
  describe('异常检测完整流程', () => {
62
62
  it('有异常时应该显示详细信息和下一步建议', async () => {
63
63
  const mockTraces = [
@@ -87,9 +87,9 @@ describe('status command - 补充覆盖', () => {
87
87
  };
88
88
  (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
89
89
 
90
- await status({ anomalies: true });
90
+ await status({ anomalies: true }, io);
91
91
 
92
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
92
+ const output = io.outText();
93
93
  expect(output).toContain('异常');
94
94
  expect(mockAnalyze.detectAnomalies).toHaveBeenCalled();
95
95
  });
@@ -103,9 +103,9 @@ describe('status command - 补充覆盖', () => {
103
103
  };
104
104
  (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
105
105
 
106
- await status({ anomalies: true });
106
+ await status({ anomalies: true }, io);
107
107
 
108
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
108
+ const output = io.outText();
109
109
  expect(output).toContain('未发现异常');
110
110
  });
111
111
  });
@@ -128,9 +128,9 @@ describe('status command - 补充覆盖', () => {
128
128
  };
129
129
  (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
130
130
 
131
- await status({});
131
+ await status({}, io);
132
132
 
133
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
133
+ const output = io.outText();
134
134
  expect(output).toContain('约束统计');
135
135
  });
136
136
 
@@ -149,9 +149,9 @@ describe('status command - 补充覆盖', () => {
149
149
  };
150
150
  (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
151
151
 
152
- await status({});
152
+ await status({}, io);
153
153
 
154
- expect(consoleSpy).toHaveBeenCalled();
154
+ expect(io.outText()).not.toBe('');
155
155
  });
156
156
  });
157
157
 
@@ -173,9 +173,9 @@ describe('status command - 补充覆盖', () => {
173
173
  };
174
174
  (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
175
175
 
176
- await status({ detail: true });
176
+ await status({ detail: true }, io);
177
177
 
178
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
178
+ const output = io.outText();
179
179
  expect(output).toContain('约束统计');
180
180
  });
181
181
  });
@@ -190,10 +190,10 @@ describe('status command - 补充覆盖', () => {
190
190
  };
191
191
  (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
192
192
 
193
- await status({});
193
+ await status({}, io);
194
194
 
195
195
  // 应该成功处理,不会抛出异常
196
- expect(consoleSpy).toHaveBeenCalled();
196
+ expect(io.outText()).not.toBe('');
197
197
  });
198
198
  });
199
199
  });