@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 { knowledgeAudit, knowledgeStats, knowledgeHealth, knowledgeSearch } from '../knowledge';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
 
7
8
  // Mock chalk
8
9
  jest.mock('chalk', () => ({
@@ -20,6 +21,8 @@ jest.mock('../../../knowledge/audit', () => ({
20
21
  KnowledgeAudit: jest.fn().mockImplementation(() => ({
21
22
  run: mockRun,
22
23
  })),
24
+ // label 正本在 audit.ts 规则定义上,CLI 直接消费(#109)
25
+ AUDIT_RULE_LABELS: jest.requireActual('../../../knowledge/audit').AUDIT_RULE_LABELS,
23
26
  }));
24
27
 
25
28
  // Mock KnowledgeIndexGenerator (avoids real fs writes in CLI tests)
@@ -67,6 +70,11 @@ const MOCK_REPORT = {
67
70
  healthScore: { before: 95, after: 95 },
68
71
  };
69
72
 
73
+ let io: CapturingIO;
74
+ beforeEach(() => {
75
+ io = captureIO();
76
+ });
77
+
70
78
  describe('getKnowledgeDir', () => {
71
79
  const fs = require('fs');
72
80
  const os = require('os');
@@ -74,14 +82,12 @@ describe('getKnowledgeDir', () => {
74
82
  const storeModule = require('../../../knowledge/store');
75
83
  let storeCtorSpy: jest.SpyInstance;
76
84
  let homedirSpy: jest.SpyInstance;
77
- let errorSpy: jest.SpyInstance;
78
85
  let tmpHome: string;
79
86
 
80
87
  beforeEach(() => {
81
88
  delete process.env.KNOWLEDGE_BASE_DIR;
82
89
  tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-kdir-'));
83
90
  homedirSpy = jest.spyOn(os, 'homedir').mockReturnValue(tmpHome);
84
- errorSpy = jest.spyOn(console, 'error').mockImplementation();
85
91
  storeCtorSpy = jest.spyOn(storeModule, 'FileKnowledgeStore').mockImplementation(() => ({
86
92
  list: jest.fn().mockReturnValue([]),
87
93
  }));
@@ -90,21 +96,20 @@ describe('getKnowledgeDir', () => {
90
96
  afterEach(() => {
91
97
  storeCtorSpy.mockRestore();
92
98
  homedirSpy.mockRestore();
93
- errorSpy.mockRestore();
94
99
  delete process.env.KNOWLEDGE_BASE_DIR;
95
100
  fs.rmSync(tmpHome, { recursive: true, force: true });
96
101
  });
97
102
 
98
103
  it('should use KNOWLEDGE_BASE_DIR env var when set', async () => {
99
104
  process.env.KNOWLEDGE_BASE_DIR = '/custom/knowledge';
100
- await knowledgeStats({ json: true });
105
+ await knowledgeStats({ json: true }, io);
101
106
  expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
102
107
  baseDir: '/custom/knowledge',
103
108
  }));
104
109
  });
105
110
 
106
111
  it('should default to ~/.harness/knowledge when no env var and no legacy data', async () => {
107
- await knowledgeStats({ json: true });
112
+ await knowledgeStats({ json: true }, io);
108
113
  expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
109
114
  baseDir: path.join(tmpHome, '.harness', 'knowledge'),
110
115
  }));
@@ -114,27 +119,27 @@ describe('getKnowledgeDir', () => {
114
119
  const legacyDir = path.join(tmpHome, '.studio', 'knowledge');
115
120
  fs.mkdirSync(legacyDir, { recursive: true });
116
121
  fs.writeFileSync(path.join(legacyDir, 'guideline-x.md'), 'data');
117
- await knowledgeStats({ json: true });
122
+ await knowledgeStats({ json: true }, io);
118
123
  expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
119
124
  baseDir: legacyDir,
120
125
  }));
121
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('旧目录'));
126
+ expect(io.errText()).toContain('旧目录');
122
127
  // 闩锁:同进程重复解析只提示一次
123
- await knowledgeStats({ json: true });
124
- const notices = errorSpy.mock.calls.filter((c: any[]) => typeof c[0] === 'string' && c[0].includes('旧目录'));
128
+ await knowledgeStats({ json: true }, io);
129
+ const notices = io.errLines().filter(line => line.includes('旧目录'));
125
130
  expect(notices).toHaveLength(1);
126
131
  });
127
132
 
128
133
  it('should ignore legacy dir when it exists but is empty', async () => {
129
134
  fs.mkdirSync(path.join(tmpHome, '.studio', 'knowledge'), { recursive: true });
130
- await knowledgeStats({ json: true });
135
+ await knowledgeStats({ json: true }, io);
131
136
  expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
132
137
  baseDir: path.join(tmpHome, '.harness', 'knowledge'),
133
138
  }));
134
139
  });
135
140
 
136
141
  it('should use projectPath when provided', async () => {
137
- await knowledgeStats({ projectPath: '/my/project', json: true });
142
+ await knowledgeStats({ projectPath: '/my/project', json: true }, io);
138
143
  expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
139
144
  baseDir: '/my/project/.harness/knowledge',
140
145
  }));
@@ -142,81 +147,105 @@ describe('getKnowledgeDir', () => {
142
147
  });
143
148
 
144
149
  describe('knowledgeAudit CLI', () => {
145
- let consoleSpy: jest.SpyInstance;
146
150
 
147
151
  beforeEach(() => {
148
152
  jest.clearAllMocks();
149
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
150
153
  mockRun.mockReturnValue(MOCK_REPORT);
151
154
  });
152
155
 
153
- afterEach(() => {
154
- consoleSpy.mockRestore();
155
- });
156
-
157
156
  it('should output JSON when --json is set', async () => {
158
- await knowledgeAudit({ json: true });
159
- expect(consoleSpy).toHaveBeenCalledWith(JSON.stringify(MOCK_REPORT, null, 2));
157
+ await knowledgeAudit({ json: true }, io);
158
+
159
+ expect(io.outText()).toBe(JSON.stringify(MOCK_REPORT, null, 2) + '\n');
160
160
  });
161
161
 
162
162
  it('should call audit.run with autoFix=true when --fix is set', async () => {
163
- await knowledgeAudit({ fix: true });
163
+ await knowledgeAudit({ fix: true }, io);
164
164
  expect(mockRun).toHaveBeenCalledWith({ autoFix: true });
165
165
  });
166
166
 
167
167
  it('should call audit.run with autoFix falsy when --dry-run is set', async () => {
168
- await knowledgeAudit({ dryRun: true });
168
+ await knowledgeAudit({ dryRun: true }, io);
169
169
  expect(mockRun).toHaveBeenCalledWith({ autoFix: undefined });
170
170
  });
171
171
 
172
172
  it('should call audit.run with autoFix falsy by default', async () => {
173
- await knowledgeAudit({});
173
+ await knowledgeAudit({}, io);
174
174
  expect(mockRun).toHaveBeenCalledWith({ autoFix: undefined });
175
175
  });
176
176
 
177
177
  it('should display total entries and health score', async () => {
178
- await knowledgeAudit({});
179
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
178
+ await knowledgeAudit({}, io);
179
+ const output = io.outText();
180
180
  expect(output).toContain('总条目: 100');
181
181
  expect(output).toContain('95/100');
182
182
  });
183
183
 
184
184
  it('should display dimension scores', async () => {
185
- await knowledgeAudit({});
186
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
185
+ await knowledgeAudit({}, io);
186
+ const output = io.outText();
187
187
  expect(output).toContain('D1 结构完整性');
188
188
  expect(output).toContain('D6 飞轮验证');
189
189
  });
190
190
 
191
191
  it('should display autoFixed count when > 0', async () => {
192
192
  mockRun.mockReturnValue({ ...MOCK_REPORT, autoFixed: 5, healthScore: { before: 90, after: 95 } });
193
- await knowledgeAudit({ fix: true });
194
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
193
+ await knowledgeAudit({ fix: true }, io);
194
+ const output = io.outText();
195
195
  expect(output).toContain('自动修复: 5');
196
196
  expect(output).toContain('修复后: 95/100');
197
197
  });
198
198
 
199
199
  it('should pass custom dir to KnowledgeAudit', async () => {
200
200
  const { KnowledgeAudit } = require('../../../knowledge/audit');
201
- await knowledgeAudit({ dir: '/custom/path' });
201
+ await knowledgeAudit({ dir: '/custom/path' }, io);
202
202
  expect(KnowledgeAudit).toHaveBeenCalledWith(expect.objectContaining({
203
203
  baseDir: '/custom/path',
204
204
  }));
205
205
  });
206
+
207
+ it('规则 label 闭环:event-noise / deprecated-domain 显示中文 label,不回落英文键名(#109)', async () => {
208
+ mockRun.mockReturnValue({
209
+ ...MOCK_REPORT,
210
+ issues: [],
211
+ summary: {
212
+ ...MOCK_REPORT.summary,
213
+ 'event-noise': 2,
214
+ 'deprecated-domain': 1,
215
+ },
216
+ });
217
+ await knowledgeAudit({}, io);
218
+ const output = io.outText();
219
+ expect(output).toContain('运维事件噪音: 2');
220
+ expect(output).toContain('废弃领域残留: 1');
221
+ expect(output).not.toContain('event-noise');
222
+ expect(output).not.toContain('deprecated-domain');
223
+ });
224
+
225
+ it('维度 label 闭环:incremental 维度显示中文 label,不回落英文键名(#109)', async () => {
226
+ mockRun.mockReturnValue({
227
+ ...MOCK_REPORT,
228
+ dimensions: {
229
+ ...MOCK_REPORT.dimensions,
230
+ incremental: { score: 80, issues: 0, details: {} },
231
+ },
232
+ });
233
+ await knowledgeAudit({}, io);
234
+ const output = io.outText();
235
+ expect(output).toContain('D7 增量存活');
236
+ expect(output).not.toContain('incremental');
237
+ });
206
238
  });
207
239
 
208
240
  describe('knowledgeHealth CLI', () => {
209
241
  const storeModule = require('../../../knowledge/store');
210
- let consoleSpy: jest.SpyInstance;
211
242
  let storeSpy: jest.SpyInstance;
212
243
 
213
244
  beforeEach(() => {
214
245
  jest.clearAllMocks();
215
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
216
246
  });
217
247
 
218
248
  afterEach(() => {
219
- consoleSpy.mockRestore();
220
249
  if (storeSpy) storeSpy.mockRestore();
221
250
  });
222
251
 
@@ -229,8 +258,8 @@ describe('knowledgeHealth CLI', () => {
229
258
  getBaseDir: jest.fn().mockReturnValue('/tmp/knowledge'),
230
259
  }));
231
260
 
232
- await knowledgeHealth({ json: true });
233
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
261
+ await knowledgeHealth({ json: true }, io);
262
+ const output = io.outText();
234
263
  const parsed = JSON.parse(output);
235
264
  expect(parsed).toHaveProperty('healthScore');
236
265
  expect(parsed.summary).toHaveProperty('total', 2);
@@ -244,8 +273,8 @@ describe('knowledgeHealth CLI', () => {
244
273
  getBaseDir: jest.fn().mockReturnValue('/tmp/knowledge'),
245
274
  }));
246
275
 
247
- await knowledgeHealth({ json: true });
248
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
276
+ await knowledgeHealth({ json: true }, io);
277
+ const output = io.outText();
249
278
  const parsed = JSON.parse(output);
250
279
  expect(parsed.summary.lowRefEntries).toBe(1);
251
280
  });
@@ -259,8 +288,8 @@ describe('knowledgeHealth CLI', () => {
259
288
  getBaseDir: jest.fn().mockReturnValue('/tmp/knowledge'),
260
289
  }));
261
290
 
262
- await knowledgeHealth({ json: true });
263
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
291
+ await knowledgeHealth({ json: true }, io);
292
+ const output = io.outText();
264
293
  const parsed = JSON.parse(output);
265
294
  expect(parsed.summary.staleEntries).toBe(1);
266
295
  });
@@ -279,7 +308,6 @@ describe('knowledgeSearch CLI (harness#63)', () => {
279
308
 
280
309
  let tmpProject: string;
281
310
  let knowledgeDir: string;
282
- let consoleSpy: jest.SpyInstance;
283
311
 
284
312
  const FILLER_COUNT = 25; // 25 × ~800 tokens ≈ 20000 > maxTokens 10000
285
313
  const TARGET_ID = 'PIT-020';
@@ -311,7 +339,6 @@ describe('knowledgeSearch CLI (harness#63)', () => {
311
339
  tmpProject = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-k63-'));
312
340
  knowledgeDir = path.join(tmpProject, '.harness', 'knowledge');
313
341
  fs.mkdirSync(knowledgeDir, { recursive: true });
314
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
315
342
 
316
343
  const store = new FileKnowledgeStore({ baseDir: knowledgeDir });
317
344
  // 高成熟度 + 有 lastReferenced 的填充条目排在前面,吃满 token budget
@@ -333,13 +360,12 @@ describe('knowledgeSearch CLI (harness#63)', () => {
333
360
  });
334
361
 
335
362
  afterEach(() => {
336
- consoleSpy.mockRestore();
337
363
  fs.rmSync(tmpProject, { recursive: true, force: true });
338
364
  });
339
365
 
340
366
  async function searchJson(query: string, limit: number): Promise<any> {
341
- await knowledgeSearch(query, { projectPath: tmpProject, json: true, limit });
342
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
367
+ await knowledgeSearch(query, { projectPath: tmpProject, json: true, limit }, io);
368
+ const output = io.outText();
343
369
  return JSON.parse(output);
344
370
  }
345
371
 
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { runPassesGate, checkCoverage } from '../passes-gate';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import * as fs from 'fs/promises';
7
8
  import { PassesGate } from '../../../core/validators/passes-gate';
8
9
  import { execAsync } from '../../../utils/exec';
@@ -18,9 +19,10 @@ jest.mock('../../../utils/exec', () => ({
18
19
  execAsync: jest.fn(),
19
20
  }));
20
21
 
21
- // Mock PassesGate
22
+ // Mock PassesGate(detectTestCommand 用真实实现——它就是收口后的正本,fs 已被 mock 喂料)
22
23
  jest.mock('../../../core/validators/passes-gate', () => ({
23
24
  PassesGate: jest.fn(),
25
+ detectTestCommand: jest.requireActual('../../../core/validators/passes-gate').detectTestCommand,
24
26
  }));
25
27
 
26
28
  // Mock chalk
@@ -37,17 +39,12 @@ const MockPassesGate = PassesGate as jest.MockedClass<typeof PassesGate>;
37
39
  const mockExecAsync = execAsync as jest.MockedFunction<typeof execAsync>;
38
40
 
39
41
  describe('passes-gate command', () => {
40
- let consoleSpy: jest.SpyInstance;
42
+ let io: CapturingIO;
41
43
 
42
44
  beforeEach(() => {
43
- jest.clearAllMocks();
44
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
45
- process.exitCode = 0;
46
- });
47
45
 
48
- afterEach(() => {
49
- consoleSpy.mockRestore();
50
- process.exitCode = 0;
46
+ io = captureIO();
47
+ jest.clearAllMocks();
51
48
  });
52
49
 
53
50
  describe('runPassesGate', () => {
@@ -55,8 +52,8 @@ describe('passes-gate command', () => {
55
52
  mockFs.readFile.mockRejectedValue(new Error('no package.json'));
56
53
  mockFs.access.mockRejectedValue(new Error('no file'));
57
54
 
58
- await runPassesGate({});
59
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('未检测到测试命令'));
55
+ await runPassesGate({}, io);
56
+ expect(io.outText()).toContain('未检测到测试命令');
60
57
  });
61
58
 
62
59
  it('应该通过测试门控', async () => {
@@ -76,8 +73,8 @@ describe('passes-gate command', () => {
76
73
  runTests: mockRunTests,
77
74
  }));
78
75
 
79
- await runPassesGate({});
80
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('测试门控通过'));
76
+ await runPassesGate({}, io);
77
+ expect(io.outText()).toContain('测试门控通过');
81
78
  });
82
79
 
83
80
  it('应该失败测试门控', async () => {
@@ -98,11 +95,10 @@ describe('passes-gate command', () => {
98
95
  }));
99
96
 
100
97
  // Mock process.exit
101
- const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
102
98
 
103
- await runPassesGate({});
104
- expect(mockExit).toHaveBeenCalledWith(1);
105
- mockExit.mockRestore();
99
+ const result = await runPassesGate({}, io);
100
+
101
+ expect(result).toEqual({ kind: 'fail', reason: 'passes-gate denied: 2/10 个测试失败' });
106
102
  });
107
103
 
108
104
  it('应该处理测试执行错误', async () => {
@@ -115,11 +111,9 @@ describe('passes-gate command', () => {
115
111
  runTests: mockRunTests,
116
112
  }));
117
113
 
118
- const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
114
+ const result = await runPassesGate({}, io);
119
115
 
120
- await runPassesGate({});
121
- expect(mockExit).toHaveBeenCalledWith(1);
122
- mockExit.mockRestore();
116
+ expect(result).toEqual({ kind: 'fail', reason: expect.stringContaining('测试执行异常: test failed') });
123
117
  });
124
118
 
125
119
  it('应该使用自定义测试命令', async () => {
@@ -135,7 +129,7 @@ describe('passes-gate command', () => {
135
129
  runTests: mockRunTests,
136
130
  }));
137
131
 
138
- await runPassesGate({ testCommand: 'pytest' });
132
+ await runPassesGate({ testCommand: 'pytest' }, io);
139
133
  expect(MockPassesGate).toHaveBeenCalledWith(
140
134
  expect.objectContaining({ testCommand: 'pytest' })
141
135
  );
@@ -143,34 +137,37 @@ describe('passes-gate command', () => {
143
137
  });
144
138
 
145
139
  describe('checkCoverage', () => {
146
- it('应该返回 true 当覆盖率达标', async () => {
140
+ it('覆盖率达标:ok', async () => {
147
141
  mockExecAsync.mockResolvedValue({ stdout: '', stderr: '' });
148
142
  mockFs.readFile.mockResolvedValue(JSON.stringify({
149
143
  total: { lines: { pct: 85 } },
150
144
  }));
151
145
 
152
- const result = await checkCoverage('/project', 80);
153
- expect(result).toBe(true);
154
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('覆盖率达标'));
146
+ const result = await checkCoverage('/project', 80, io);
147
+
148
+ expect(result).toEqual({ kind: 'ok' });
149
+ expect(io.outText()).toContain('覆盖率达标');
155
150
  });
156
151
 
157
- it('应该返回 false 当覆盖率不足', async () => {
152
+ it('覆盖率不足:skip(历史面:--coverage 路由不改退出码)', async () => {
158
153
  mockExecAsync.mockResolvedValue({ stdout: '', stderr: '' });
159
154
  mockFs.readFile.mockResolvedValue(JSON.stringify({
160
155
  total: { lines: { pct: 60 } },
161
156
  }));
162
157
 
163
- const result = await checkCoverage('/project', 80);
164
- expect(result).toBe(false);
165
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('覆盖率不足'));
158
+ const result = await checkCoverage('/project', 80, io);
159
+
160
+ expect(result).toEqual({ kind: 'skip', reason: '覆盖率不足: 60% < 80%' });
161
+ expect(io.outText()).toContain('覆盖率不足');
166
162
  });
167
163
 
168
- it('应该返回 true 当执行出错时', async () => {
164
+ it('取不到覆盖率:skip(不阻断)', async () => {
169
165
  mockExecAsync.mockRejectedValue(new Error('command failed'));
170
166
 
171
- const result = await checkCoverage('/project');
172
- expect(result).toBe(true);
173
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('无法获取覆盖率信息'));
167
+ const result = await checkCoverage('/project', 80, io);
168
+
169
+ expect(result).toEqual({ kind: 'skip', reason: expect.stringContaining('无法获取覆盖率信息') });
170
+ expect(io.outText()).toContain('无法获取覆盖率信息');
174
171
  });
175
172
 
176
173
  it('应该使用默认阈值 80', async () => {
@@ -180,7 +177,8 @@ describe('passes-gate command', () => {
180
177
  }));
181
178
 
182
179
  const result = await checkCoverage('/project');
183
- expect(result).toBe(true);
180
+
181
+ expect(result).toEqual({ kind: 'ok' });
184
182
  });
185
183
 
186
184
  it('应该处理 coverage.total 为 undefined', async () => {
@@ -188,7 +186,8 @@ describe('passes-gate command', () => {
188
186
  mockFs.readFile.mockResolvedValue(JSON.stringify({}));
189
187
 
190
188
  const result = await checkCoverage('/project', 80);
191
- expect(result).toBe(false);
189
+
190
+ expect(result).toEqual({ kind: 'skip', reason: '覆盖率不足: 0% < 80%' });
192
191
  });
193
192
  });
194
193
 
@@ -196,17 +195,15 @@ describe('passes-gate command', () => {
196
195
  it('应该检测 pytest 项目', async () => {
197
196
  // package.json 不存在
198
197
  mockFs.readFile.mockRejectedValue(new Error('no file'));
199
- // pytest.ini 存在
200
- mockFs.access
201
- .mockResolvedValueOnce(undefined) // pytest.ini
202
- .mockRejectedValueOnce(new Error('no file')); // go.mod
198
+ // pyproject.toml 存在(Python 标记任一命中即 pytest)
199
+ mockFs.access.mockResolvedValueOnce(undefined); // pyproject.toml
203
200
 
204
201
  const mockRunTests = jest.fn().mockResolvedValue({
205
202
  passed: true, passedTests: 5, failedTests: 0, totalTests: 5, duration: 500, failures: [],
206
203
  });
207
204
  (MockPassesGate as any).mockImplementation(() => ({ runTests: mockRunTests }));
208
205
 
209
- await runPassesGate({});
206
+ await runPassesGate({}, io);
210
207
  expect(MockPassesGate).toHaveBeenCalledWith(expect.objectContaining({ testCommand: 'pytest' }));
211
208
  });
212
209
 
@@ -215,6 +212,7 @@ describe('passes-gate command', () => {
215
212
  // Reset access mock to avoid leaking from previous tests
216
213
  mockFs.access.mockReset();
217
214
  mockFs.access
215
+ .mockRejectedValueOnce(new Error('no file')) // pyproject.toml
218
216
  .mockRejectedValueOnce(new Error('no file')) // pytest.ini
219
217
  .mockResolvedValueOnce(undefined); // go.mod
220
218
 
@@ -223,7 +221,7 @@ describe('passes-gate command', () => {
223
221
  });
224
222
  (MockPassesGate as any).mockImplementation(() => ({ runTests: mockRunTests }));
225
223
 
226
- await runPassesGate({});
224
+ await runPassesGate({}, io);
227
225
  expect(MockPassesGate).toHaveBeenCalledWith(expect.objectContaining({ testCommand: 'go test ./...' }));
228
226
  });
229
227
 
@@ -237,7 +235,7 @@ describe('passes-gate command', () => {
237
235
  });
238
236
  (MockPassesGate as any).mockImplementation(() => ({ runTests: mockRunTests }));
239
237
 
240
- await runPassesGate({});
238
+ await runPassesGate({}, io);
241
239
  expect(MockPassesGate).toHaveBeenCalledWith(expect.objectContaining({ testCommand: 'npm run test:ci' }));
242
240
  });
243
241
 
@@ -249,8 +247,8 @@ describe('passes-gate command', () => {
249
247
  mockFs.access.mockReset();
250
248
  mockFs.access.mockRejectedValue(new Error('no file'));
251
249
 
252
- await runPassesGate({});
253
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('未检测到测试命令'));
250
+ await runPassesGate({}, io);
251
+ expect(io.outText()).toContain('未检测到测试命令');
254
252
  });
255
253
  });
256
254
  });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { performance } from '../performance';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import { PerformanceGate } from '../../../gates/performance';
7
8
 
8
9
  jest.mock('../../../gates/performance', () => ({
@@ -21,20 +22,12 @@ jest.mock('chalk', () => ({
21
22
  const MockGate = PerformanceGate as jest.MockedClass<typeof PerformanceGate>;
22
23
 
23
24
  describe('performance command', () => {
24
- let consoleSpy: jest.SpyInstance;
25
- let exitSpy: jest.SpyInstance;
25
+ let io: CapturingIO;
26
26
 
27
27
  beforeEach(() => {
28
- jest.clearAllMocks();
29
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
30
- exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
31
- process.exitCode = 0;
32
- });
33
28
 
34
- afterEach(() => {
35
- consoleSpy.mockRestore();
36
- exitSpy.mockRestore();
37
- process.exitCode = 0;
29
+ io = captureIO();
30
+ jest.clearAllMocks();
38
31
  });
39
32
 
40
33
  it('should print success when check passes', async () => {
@@ -47,10 +40,10 @@ describe('performance command', () => {
47
40
  });
48
41
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
49
42
 
50
- await performance({});
43
+ const result = await performance({}, 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 check fails', async () => {
@@ -61,27 +54,33 @@ describe('performance command', () => {
61
54
  });
62
55
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
63
56
 
64
- await performance({});
57
+ const result = await performance({}, io);
65
58
 
66
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('性能门控检查失败'));
67
- expect(exitSpy).toHaveBeenCalledWith(1);
59
+ expect(io.outText()).toContain('性能门控检查失败');
60
+ expect(result).toEqual({
61
+ kind: 'fail',
62
+ reason: 'performance gate denied: threshold exceeded',
63
+ });
68
64
  });
69
65
 
70
66
  it('should handle errors and exit 1', async () => {
71
67
  const mockCheck = jest.fn().mockRejectedValue(new Error('gate error'));
72
68
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
73
69
 
74
- await performance({});
70
+ const result = await performance({}, io);
75
71
 
76
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('性能门控检查出错'));
77
- expect(exitSpy).toHaveBeenCalledWith(1);
72
+ expect(io.outText()).toContain('性能门控检查出错');
73
+ expect(result).toEqual({
74
+ kind: 'fail',
75
+ reason: 'performance gate error: gate error',
76
+ });
78
77
  });
79
78
 
80
79
  it('should convert bundleThreshold from KB to bytes', async () => {
81
80
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
82
81
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
83
82
 
84
- await performance({ bundleThreshold: 500 });
83
+ await performance({ bundleThreshold: 500 }, io);
85
84
 
86
85
  expect(MockGate).toHaveBeenCalledWith(
87
86
  expect.objectContaining({
@@ -94,7 +93,7 @@ describe('performance command', () => {
94
93
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
95
94
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
96
95
 
97
- await performance({ benchmarkTimeout: 30 });
96
+ await performance({ benchmarkTimeout: 30 }, io);
98
97
 
99
98
  expect(MockGate).toHaveBeenCalledWith(
100
99
  expect.objectContaining({ benchmarkTimeout: 30000 }),
@@ -105,7 +104,7 @@ describe('performance command', () => {
105
104
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
106
105
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
107
106
 
108
- await performance({ coverage: true, coverageThreshold: 90 });
107
+ await performance({ coverage: true, coverageThreshold: 90 }, io);
109
108
 
110
109
  expect(MockGate).toHaveBeenCalledWith(
111
110
  expect.objectContaining({