@dommaker/harness 1.2.3 → 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 (550) hide show
  1. package/CHANGELOG.md +15 -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 +30 -215
  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 +33 -26
  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 +237 -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 +73 -77
  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 +31 -16
  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/completion-checkers/index.d.ts +3 -3
  127. package/dist/completion-checkers/index.js +3 -3
  128. package/dist/completion-checkers/phase-format.d.ts +1 -1
  129. package/dist/completion-checkers/phase-format.js +2 -2
  130. package/dist/completion-checkers/phase-format.js.map +1 -1
  131. package/dist/completion-checkers/tdd-chain.d.ts +1 -1
  132. package/dist/completion-checkers/tdd-chain.js +2 -2
  133. package/dist/completion-checkers/tdd-chain.js.map +1 -1
  134. package/dist/completion-checkers/types.d.ts +5 -5
  135. package/dist/completion-checkers/types.d.ts.map +1 -1
  136. package/dist/completion-checkers/types.js +2 -2
  137. package/dist/context/session-manager.d.ts.map +1 -1
  138. package/dist/context/session-manager.js +19 -32
  139. package/dist/context/session-manager.js.map +1 -1
  140. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +1 -1
  141. package/dist/core/constraints/agent-prompt-renderer.js +1 -4
  142. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  143. package/dist/core/constraints/capabilities-parser.d.ts +24 -7
  144. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  145. package/dist/core/constraints/capabilities-parser.js +32 -28
  146. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  147. package/dist/core/constraints/capabilities-reconcile.d.ts +67 -0
  148. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -0
  149. package/dist/core/constraints/capabilities-reconcile.js +105 -0
  150. package/dist/core/constraints/capabilities-reconcile.js.map +1 -0
  151. package/dist/core/constraints/checker.d.ts +21 -43
  152. package/dist/core/constraints/checker.d.ts.map +1 -1
  153. package/dist/core/constraints/checker.js +64 -115
  154. package/dist/core/constraints/checker.js.map +1 -1
  155. package/dist/core/constraints/checkers/capability-sync.d.ts +4 -3
  156. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  157. package/dist/core/constraints/checkers/capability-sync.js +29 -69
  158. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  159. package/dist/core/constraints/checkers/context-doc-sync.d.ts +4 -2
  160. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  161. package/dist/core/constraints/checkers/context-doc-sync.js +13 -22
  162. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  163. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  164. package/dist/core/constraints/checkers/docs-freshness.js +37 -63
  165. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  166. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -2
  167. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  168. package/dist/core/constraints/checkers/governance-presence.js +7 -28
  169. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  170. package/dist/core/constraints/checkers/no-test-simplification.d.ts.map +1 -1
  171. package/dist/core/constraints/checkers/no-test-simplification.js +4 -3
  172. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  173. package/dist/core/constraints/checkers/types.d.ts +2 -2
  174. package/dist/core/constraints/context-builder.d.ts +15 -14
  175. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  176. package/dist/core/constraints/context-builder.js +32 -96
  177. package/dist/core/constraints/context-builder.js.map +1 -1
  178. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  179. package/dist/core/constraints/doc-freshness/runner.js +2 -1
  180. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  181. package/dist/core/constraints/git-evidence.d.ts +60 -0
  182. package/dist/core/constraints/git-evidence.d.ts.map +1 -0
  183. package/dist/core/constraints/git-evidence.js +88 -0
  184. package/dist/core/constraints/git-evidence.js.map +1 -0
  185. package/dist/core/constraints/injection-drift.d.ts +3 -20
  186. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  187. package/dist/core/constraints/injection-drift.js +10 -57
  188. package/dist/core/constraints/injection-drift.js.map +1 -1
  189. package/dist/core/constraints/injection-writer.d.ts +113 -0
  190. package/dist/core/constraints/injection-writer.d.ts.map +1 -0
  191. package/dist/core/constraints/injection-writer.js +228 -0
  192. package/dist/core/constraints/injection-writer.js.map +1 -0
  193. package/dist/core/constraints/usage-report.d.ts +22 -1
  194. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  195. package/dist/core/constraints/usage-report.js +27 -20
  196. package/dist/core/constraints/usage-report.js.map +1 -1
  197. package/dist/core/index.d.ts +2 -2
  198. package/dist/core/index.d.ts.map +1 -1
  199. package/dist/core/index.js +4 -2
  200. package/dist/core/index.js.map +1 -1
  201. package/dist/core/project-config-loader.d.ts +32 -1
  202. package/dist/core/project-config-loader.d.ts.map +1 -1
  203. package/dist/core/project-config-loader.js +39 -9
  204. package/dist/core/project-config-loader.js.map +1 -1
  205. package/dist/core/validators/passes-gate.d.ts +15 -64
  206. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  207. package/dist/core/validators/passes-gate.js +84 -222
  208. package/dist/core/validators/passes-gate.js.map +1 -1
  209. package/dist/core/validators/test-output.d.ts +58 -0
  210. package/dist/core/validators/test-output.d.ts.map +1 -0
  211. package/dist/core/validators/test-output.js +109 -0
  212. package/dist/core/validators/test-output.js.map +1 -0
  213. package/dist/failure/recorder.d.ts +7 -1
  214. package/dist/failure/recorder.d.ts.map +1 -1
  215. package/dist/failure/recorder.js +13 -9
  216. package/dist/failure/recorder.js.map +1 -1
  217. package/dist/gates/acceptance.d.ts +1 -48
  218. package/dist/gates/acceptance.d.ts.map +1 -1
  219. package/dist/gates/acceptance.js +6 -38
  220. package/dist/gates/acceptance.js.map +1 -1
  221. package/dist/gates/checker-gate.d.ts.map +1 -1
  222. package/dist/gates/checker-gate.js +6 -11
  223. package/dist/gates/checker-gate.js.map +1 -1
  224. package/dist/gates/command.d.ts.map +1 -1
  225. package/dist/gates/command.js +10 -22
  226. package/dist/gates/command.js.map +1 -1
  227. package/dist/gates/contract.d.ts.map +1 -1
  228. package/dist/gates/contract.js +22 -61
  229. package/dist/gates/contract.js.map +1 -1
  230. package/dist/gates/performance.d.ts.map +1 -1
  231. package/dist/gates/performance.js +13 -31
  232. package/dist/gates/performance.js.map +1 -1
  233. package/dist/gates/review.d.ts.map +1 -1
  234. package/dist/gates/review.js +21 -53
  235. package/dist/gates/review.js.map +1 -1
  236. package/dist/gates/security.d.ts.map +1 -1
  237. package/dist/gates/security.js +14 -32
  238. package/dist/gates/security.js.map +1 -1
  239. package/dist/gates/types.d.ts +28 -5
  240. package/dist/gates/types.d.ts.map +1 -1
  241. package/dist/gates/types.js +38 -1
  242. package/dist/gates/types.js.map +1 -1
  243. package/dist/hooks/bootstrap.d.ts +2 -0
  244. package/dist/hooks/bootstrap.d.ts.map +1 -1
  245. package/dist/hooks/bootstrap.js +5 -2
  246. package/dist/hooks/bootstrap.js.map +1 -1
  247. package/dist/index.d.ts +4 -3
  248. package/dist/index.d.ts.map +1 -1
  249. package/dist/index.js +13 -4
  250. package/dist/index.js.map +1 -1
  251. package/dist/knowledge/audit.d.ts +7 -2
  252. package/dist/knowledge/audit.d.ts.map +1 -1
  253. package/dist/knowledge/audit.js +59 -79
  254. package/dist/knowledge/audit.js.map +1 -1
  255. package/dist/knowledge/flywheel-metrics.d.ts +39 -0
  256. package/dist/knowledge/flywheel-metrics.d.ts.map +1 -0
  257. package/dist/knowledge/flywheel-metrics.js +47 -0
  258. package/dist/knowledge/flywheel-metrics.js.map +1 -0
  259. package/dist/knowledge/import.d.ts.map +1 -1
  260. package/dist/knowledge/import.js +4 -14
  261. package/dist/knowledge/import.js.map +1 -1
  262. package/dist/knowledge/index-generator.d.ts.map +1 -1
  263. package/dist/knowledge/index-generator.js +18 -15
  264. package/dist/knowledge/index-generator.js.map +1 -1
  265. package/dist/knowledge/ingest.d.ts.map +1 -1
  266. package/dist/knowledge/ingest.js +9 -4
  267. package/dist/knowledge/ingest.js.map +1 -1
  268. package/dist/knowledge/migration.d.ts.map +1 -1
  269. package/dist/knowledge/migration.js +11 -8
  270. package/dist/knowledge/migration.js.map +1 -1
  271. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  272. package/dist/knowledge/reference-tracker.js +12 -9
  273. package/dist/knowledge/reference-tracker.js.map +1 -1
  274. package/dist/knowledge/store.d.ts +15 -0
  275. package/dist/knowledge/store.d.ts.map +1 -1
  276. package/dist/knowledge/store.js +59 -10
  277. package/dist/knowledge/store.js.map +1 -1
  278. package/dist/monitoring/context-tracker.d.ts.map +1 -1
  279. package/dist/monitoring/context-tracker.js +11 -20
  280. package/dist/monitoring/context-tracker.js.map +1 -1
  281. package/dist/monitoring/index.d.ts +1 -1
  282. package/dist/monitoring/index.d.ts.map +1 -1
  283. package/dist/monitoring/index.js +1 -2
  284. package/dist/monitoring/index.js.map +1 -1
  285. package/dist/monitoring/trace-analyzer.d.ts +15 -0
  286. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  287. package/dist/monitoring/trace-analyzer.js +19 -2
  288. package/dist/monitoring/trace-analyzer.js.map +1 -1
  289. package/dist/monitoring/traces.d.ts +21 -2
  290. package/dist/monitoring/traces.d.ts.map +1 -1
  291. package/dist/monitoring/traces.js +38 -29
  292. package/dist/monitoring/traces.js.map +1 -1
  293. package/dist/release/index.d.ts +3 -0
  294. package/dist/release/index.d.ts.map +1 -0
  295. package/dist/release/index.js +7 -0
  296. package/dist/release/index.js.map +1 -0
  297. package/dist/release/integrity.d.ts +65 -0
  298. package/dist/release/integrity.d.ts.map +1 -0
  299. package/dist/release/integrity.js +151 -0
  300. package/dist/release/integrity.js.map +1 -0
  301. package/dist/sdd/index-generator.js +10 -11
  302. package/dist/sdd/index-generator.js.map +1 -1
  303. package/dist/test-setup/project-fixture.d.ts +48 -0
  304. package/dist/test-setup/project-fixture.d.ts.map +1 -0
  305. package/dist/test-setup/project-fixture.js +106 -0
  306. package/dist/test-setup/project-fixture.js.map +1 -0
  307. package/dist/types/constraint.d.ts +0 -18
  308. package/dist/types/constraint.d.ts.map +1 -1
  309. package/dist/types/constraint.js.map +1 -1
  310. package/dist/types/failure.d.ts +7 -0
  311. package/dist/types/failure.d.ts.map +1 -1
  312. package/dist/types/failure.js +8 -1
  313. package/dist/types/failure.js.map +1 -1
  314. package/dist/types/passes-gate.d.ts +10 -25
  315. package/dist/types/passes-gate.d.ts.map +1 -1
  316. package/dist/types/project-config.d.ts +1 -1
  317. package/dist/types/session.d.ts +6 -5
  318. package/dist/types/session.d.ts.map +1 -1
  319. package/dist/utils/exec.d.ts +10 -0
  320. package/dist/utils/exec.d.ts.map +1 -1
  321. package/dist/utils/exec.js +12 -0
  322. package/dist/utils/exec.js.map +1 -1
  323. package/dist/utils/file-walk.d.ts +9 -1
  324. package/dist/utils/file-walk.d.ts.map +1 -1
  325. package/dist/utils/file-walk.js +10 -1
  326. package/dist/utils/file-walk.js.map +1 -1
  327. package/dist/utils/frontmatter.d.ts +73 -0
  328. package/dist/utils/frontmatter.d.ts.map +1 -0
  329. package/dist/utils/frontmatter.js +141 -0
  330. package/dist/utils/frontmatter.js.map +1 -0
  331. package/dist/utils/jsonl.d.ts +92 -0
  332. package/dist/utils/jsonl.d.ts.map +1 -0
  333. package/dist/utils/jsonl.js +166 -0
  334. package/dist/utils/jsonl.js.map +1 -0
  335. package/dist/utils/package-version.d.ts +16 -0
  336. package/dist/utils/package-version.d.ts.map +1 -0
  337. package/dist/utils/package-version.js +63 -0
  338. package/dist/utils/package-version.js.map +1 -0
  339. package/package.json +1 -1
  340. package/src/CONTEXT.md +7 -2
  341. package/src/__tests__/acceptance-gate.test.ts +3 -1
  342. package/src/__tests__/checker-extra.test.ts +7 -1218
  343. package/src/__tests__/context-files-resolution.test.ts +116 -0
  344. package/src/__tests__/effective-constraints.test.ts +10 -13
  345. package/src/__tests__/exec.test.ts +35 -1
  346. package/src/__tests__/failure-recorder.test.ts +9 -0
  347. package/src/__tests__/layering.test.ts +152 -0
  348. package/src/__tests__/passes-gate-extra.test.ts +0 -145
  349. package/src/__tests__/passes-gate.test.ts +142 -106
  350. package/src/__tests__/public-exports.test.ts +3 -0
  351. package/src/__tests__/skip-semantics.test.ts +5 -9
  352. package/src/cli/__tests__/command-contract.test.ts +170 -0
  353. package/src/cli/command-contract.ts +108 -0
  354. package/src/cli/commands/CONTEXT.md +22 -7
  355. package/src/cli/commands/__tests__/acceptance.test.ts +40 -33
  356. package/src/cli/commands/__tests__/analyze-sessions.test.ts +35 -29
  357. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +101 -0
  358. package/src/cli/commands/__tests__/check-drift.test.ts +27 -34
  359. package/src/cli/commands/__tests__/check-skip-output.test.ts +21 -24
  360. package/src/cli/commands/__tests__/check.test.ts +378 -382
  361. package/src/cli/commands/__tests__/command.test.ts +85 -70
  362. package/src/cli/commands/__tests__/constraints-report.test.ts +100 -71
  363. package/src/cli/commands/__tests__/constraints-retire.test.ts +152 -95
  364. package/src/cli/commands/__tests__/contract.test.ts +27 -34
  365. package/src/cli/commands/__tests__/failure.test.ts +31 -34
  366. package/src/cli/commands/__tests__/init-injection.test.ts +58 -28
  367. package/src/cli/commands/__tests__/init.test.ts +77 -48
  368. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +108 -0
  369. package/src/cli/commands/__tests__/knowledge.test.ts +96 -38
  370. package/src/cli/commands/__tests__/passes-gate.test.ts +43 -45
  371. package/src/cli/commands/__tests__/performance.test.ts +22 -23
  372. package/src/cli/commands/__tests__/posteval-plan.test.ts +39 -38
  373. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +157 -0
  374. package/src/cli/commands/__tests__/project-path-convention.test.ts +169 -0
  375. package/src/cli/commands/__tests__/registry.test.ts +69 -35
  376. package/src/cli/commands/__tests__/release.test.ts +224 -74
  377. package/src/cli/commands/__tests__/report-extra.test.ts +16 -16
  378. package/src/cli/commands/__tests__/report.test.ts +25 -25
  379. package/src/cli/commands/__tests__/review.test.ts +33 -31
  380. package/src/cli/commands/__tests__/security.test.ts +23 -27
  381. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +20 -20
  382. package/src/cli/commands/__tests__/spec.test.ts +41 -29
  383. package/src/cli/commands/__tests__/status-extra.test.ts +18 -18
  384. package/src/cli/commands/__tests__/status-skipped-lines.test.ts +72 -0
  385. package/src/cli/commands/__tests__/status.test.ts +34 -34
  386. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +108 -86
  387. package/src/cli/commands/__tests__/sync-docs.test.ts +72 -74
  388. package/src/cli/commands/__tests__/update-user-model.test.ts +62 -33
  389. package/src/cli/commands/__tests__/validate.test.ts +27 -29
  390. package/src/cli/commands/acceptance.ts +35 -24
  391. package/src/cli/commands/analyze-sessions.ts +25 -24
  392. package/src/cli/commands/check.ts +88 -65
  393. package/src/cli/commands/command.ts +30 -17
  394. package/src/cli/commands/constraints-report.ts +37 -34
  395. package/src/cli/commands/constraints-retire.ts +58 -55
  396. package/src/cli/commands/constraints.ts +10 -27
  397. package/src/cli/commands/contract.ts +51 -42
  398. package/src/cli/commands/definitions.ts +37 -223
  399. package/src/cli/commands/failure.ts +35 -27
  400. package/src/cli/commands/init.ts +166 -220
  401. package/src/cli/commands/knowledge.ts +250 -203
  402. package/src/cli/commands/passes-gate.ts +58 -68
  403. package/src/cli/commands/performance.ts +23 -18
  404. package/src/cli/commands/posteval-plan.ts +29 -15
  405. package/src/cli/commands/release.ts +75 -78
  406. package/src/cli/commands/report.ts +9 -5
  407. package/src/cli/commands/review.ts +39 -29
  408. package/src/cli/commands/sdd.ts +7 -5
  409. package/src/cli/commands/security.ts +35 -26
  410. package/src/cli/commands/spec-baseline-check.ts +16 -11
  411. package/src/cli/commands/spec.ts +66 -53
  412. package/src/cli/commands/status.ts +59 -53
  413. package/src/cli/commands/sync-docs/agents-syncer.ts +31 -15
  414. package/src/cli/commands/sync-docs/context-syncer.ts +3 -2
  415. package/src/cli/commands/sync-docs/index.ts +73 -100
  416. package/src/cli/commands/sync-docs/project-reader.ts +17 -18
  417. package/src/cli/commands/update-user-model.ts +33 -45
  418. package/src/cli/commands/validate.ts +31 -25
  419. package/src/cli/session-mining/__tests__/transcript.test.ts +118 -0
  420. package/src/cli/session-mining/transcript.ts +26 -8
  421. package/src/completion-checkers/CONTEXT.md +1 -1
  422. package/src/completion-checkers/__tests__/contract-presence.test.ts +1 -1
  423. package/src/completion-checkers/__tests__/phase-format.test.ts +1 -1
  424. package/src/completion-checkers/__tests__/tdd-chain.test.ts +2 -2
  425. package/src/completion-checkers/index.ts +3 -3
  426. package/src/completion-checkers/phase-format.ts +2 -2
  427. package/src/completion-checkers/tdd-chain.ts +2 -2
  428. package/src/completion-checkers/types.ts +5 -5
  429. package/src/context/session-manager.ts +19 -33
  430. package/src/core/CONTEXT.md +9 -6
  431. package/src/core/constraints/__tests__/capabilities-reconcile.test.ts +200 -0
  432. package/src/core/constraints/__tests__/capability-counts.test.ts +94 -0
  433. package/src/core/constraints/__tests__/git-evidence.test.ts +208 -0
  434. package/src/core/constraints/__tests__/injection-drift.test.ts +3 -10
  435. package/src/core/constraints/__tests__/injection-writer.test.ts +231 -0
  436. package/src/core/constraints/__tests__/trace-injection.test.ts +71 -0
  437. package/src/core/constraints/__tests__/usage-report.test.ts +112 -0
  438. package/src/core/constraints/agent-prompt-renderer.ts +2 -6
  439. package/src/core/constraints/capabilities-parser.ts +53 -35
  440. package/src/core/constraints/capabilities-reconcile.ts +166 -0
  441. package/src/core/constraints/checker.ts +75 -130
  442. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +214 -0
  443. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +77 -0
  444. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +117 -0
  445. package/src/{__tests__ → core/constraints/checkers/__tests__}/governance-presence.test.ts +83 -9
  446. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +50 -0
  447. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +76 -0
  448. package/src/{__tests__ → core/constraints/checkers/__tests__}/no-hardcoded-credentials.test.ts +4 -3
  449. package/src/core/constraints/checkers/__tests__/no-test-simplification.test.ts +58 -0
  450. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +145 -0
  451. package/src/core/constraints/checkers/capability-sync.ts +29 -88
  452. package/src/core/constraints/checkers/context-doc-sync.ts +15 -25
  453. package/src/core/constraints/checkers/docs-freshness.ts +40 -69
  454. package/src/core/constraints/checkers/governance-presence.ts +7 -25
  455. package/src/core/constraints/checkers/no-test-simplification.ts +4 -3
  456. package/src/core/constraints/checkers/types.ts +2 -2
  457. package/src/core/constraints/context-builder.ts +39 -91
  458. package/src/core/constraints/doc-freshness/runner.ts +2 -1
  459. package/src/core/constraints/git-evidence.ts +120 -0
  460. package/src/core/constraints/injection-drift.ts +13 -60
  461. package/src/core/constraints/injection-writer.ts +212 -0
  462. package/src/core/constraints/usage-report.ts +36 -17
  463. package/src/core/index.ts +4 -2
  464. package/src/core/project-config-loader.ts +49 -9
  465. package/src/core/validators/__tests__/passes-gate.test.ts +2 -56
  466. package/src/core/validators/__tests__/test-output.test.ts +372 -0
  467. package/src/core/validators/passes-gate.ts +86 -248
  468. package/src/core/validators/test-output.ts +128 -0
  469. package/src/failure/__tests__/recorder.test.ts +168 -0
  470. package/src/failure/recorder.ts +13 -11
  471. package/src/gates/CONTEXT.md +6 -3
  472. package/src/gates/__tests__/acceptance-types-single-source.test.ts +68 -0
  473. package/src/gates/__tests__/acceptance.test.ts +4 -86
  474. package/src/gates/__tests__/effective-gates.test.ts +3 -8
  475. package/src/gates/__tests__/test-verdict-consistency.test.ts +140 -0
  476. package/src/gates/acceptance.ts +19 -84
  477. package/src/gates/checker-gate.ts +8 -7
  478. package/src/gates/command.ts +10 -16
  479. package/src/gates/contract.ts +22 -61
  480. package/src/gates/performance.ts +10 -22
  481. package/src/gates/review.ts +21 -41
  482. package/src/gates/security.ts +10 -22
  483. package/src/gates/types.ts +70 -5
  484. package/src/hooks/CONTEXT.md +1 -1
  485. package/src/hooks/bootstrap.ts +5 -2
  486. package/src/index.ts +11 -6
  487. package/src/knowledge/CONTEXT.md +8 -3
  488. package/src/knowledge/__tests__/audit-labels.test.ts +44 -0
  489. package/src/knowledge/__tests__/flywheel-metrics.test.ts +135 -0
  490. package/src/knowledge/__tests__/import.test.ts +44 -0
  491. package/src/knowledge/__tests__/index-generator.test.ts +55 -2
  492. package/src/knowledge/__tests__/ingest.test.ts +85 -0
  493. package/src/knowledge/__tests__/migration.test.ts +47 -0
  494. package/src/knowledge/__tests__/reference-tracker.test.ts +16 -0
  495. package/src/knowledge/__tests__/store.test.ts +194 -0
  496. package/src/knowledge/audit.ts +67 -71
  497. package/src/knowledge/flywheel-metrics.ts +71 -0
  498. package/src/knowledge/import.ts +4 -18
  499. package/src/knowledge/index-generator.ts +20 -15
  500. package/src/knowledge/ingest.ts +9 -4
  501. package/src/knowledge/migration.ts +11 -8
  502. package/src/knowledge/reference-tracker.ts +12 -8
  503. package/src/knowledge/store.ts +64 -11
  504. package/src/monitoring/CONTEXT.md +8 -3
  505. package/src/monitoring/__tests__/skipped-lines-report.test.ts +178 -0
  506. package/src/monitoring/__tests__/traces.test.ts +57 -0
  507. package/src/monitoring/context-tracker.ts +11 -23
  508. package/src/monitoring/index.ts +1 -1
  509. package/src/monitoring/trace-analyzer.ts +21 -2
  510. package/src/monitoring/traces.ts +39 -36
  511. package/src/release/CONTEXT.md +24 -0
  512. package/src/release/__tests__/barrel.test.ts +60 -0
  513. package/src/release/__tests__/integrity.test.ts +157 -0
  514. package/src/release/index.ts +2 -0
  515. package/src/release/integrity.ts +141 -0
  516. package/src/sdd/__tests__/index-generator.test.ts +71 -0
  517. package/src/sdd/index-generator.ts +9 -9
  518. package/src/test-setup/__tests__/project-fixture.test.ts +178 -0
  519. package/src/test-setup/project-fixture.ts +87 -0
  520. package/src/tools/CONTEXT.md +6 -0
  521. package/src/tools/__tests__/registry.test.ts +221 -0
  522. package/src/types/__tests__/types.test.ts +4 -5
  523. package/src/types/constraint.ts +0 -25
  524. package/src/types/failure.ts +8 -0
  525. package/src/types/passes-gate.ts +10 -30
  526. package/src/types/project-config.ts +1 -1
  527. package/src/types/session.ts +5 -5
  528. package/src/utils/__tests__/frontmatter.test.ts +206 -0
  529. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +175 -0
  530. package/src/utils/__tests__/jsonl.test.ts +187 -0
  531. package/src/utils/__tests__/package-version.test.ts +97 -0
  532. package/src/utils/exec.ts +15 -0
  533. package/src/utils/file-walk.ts +11 -2
  534. package/src/utils/frontmatter.ts +121 -0
  535. package/src/utils/jsonl.ts +167 -0
  536. package/src/utils/package-version.ts +27 -0
  537. package/dist/cli/commands/doc-freshness-check.d.ts +0 -53
  538. package/dist/cli/commands/doc-freshness-check.d.ts.map +0 -1
  539. package/dist/cli/commands/doc-freshness-check.js +0 -352
  540. package/dist/cli/commands/doc-freshness-check.js.map +0 -1
  541. package/dist/types/index.d.ts +0 -14
  542. package/dist/types/index.d.ts.map +0 -1
  543. package/dist/types/index.js +0 -39
  544. package/dist/types/index.js.map +0 -1
  545. package/src/__tests__/long-running.test.ts +0 -17
  546. package/src/__tests__/passes-gate-extension.test.ts +0 -151
  547. package/src/cli/commands/__tests__/doc-freshness-check.test.ts +0 -168
  548. package/src/cli/commands/doc-freshness-check.ts +0 -386
  549. package/src/spec/CONTEXT.md +0 -11
  550. 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,14 +70,24 @@ 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', () => {
79
+ const fs = require('fs');
71
80
  const os = require('os');
72
81
  const path = require('path');
73
82
  const storeModule = require('../../../knowledge/store');
74
83
  let storeCtorSpy: jest.SpyInstance;
84
+ let homedirSpy: jest.SpyInstance;
85
+ let tmpHome: string;
75
86
 
76
87
  beforeEach(() => {
77
88
  delete process.env.KNOWLEDGE_BASE_DIR;
89
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-kdir-'));
90
+ homedirSpy = jest.spyOn(os, 'homedir').mockReturnValue(tmpHome);
78
91
  storeCtorSpy = jest.spyOn(storeModule, 'FileKnowledgeStore').mockImplementation(() => ({
79
92
  list: jest.fn().mockReturnValue([]),
80
93
  }));
@@ -82,27 +95,51 @@ describe('getKnowledgeDir', () => {
82
95
 
83
96
  afterEach(() => {
84
97
  storeCtorSpy.mockRestore();
98
+ homedirSpy.mockRestore();
85
99
  delete process.env.KNOWLEDGE_BASE_DIR;
100
+ fs.rmSync(tmpHome, { recursive: true, force: true });
86
101
  });
87
102
 
88
103
  it('should use KNOWLEDGE_BASE_DIR env var when set', async () => {
89
104
  process.env.KNOWLEDGE_BASE_DIR = '/custom/knowledge';
90
- await knowledgeStats({ json: true });
105
+ await knowledgeStats({ json: true }, io);
91
106
  expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
92
107
  baseDir: '/custom/knowledge',
93
108
  }));
94
109
  });
95
110
 
96
- it('should default to ~/.studio/knowledge when no env var', async () => {
97
- const expected = path.join(os.homedir(), '.studio', 'knowledge');
98
- await knowledgeStats({ json: true });
111
+ it('should default to ~/.harness/knowledge when no env var and no legacy data', async () => {
112
+ await knowledgeStats({ json: true }, io);
113
+ expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
114
+ baseDir: path.join(tmpHome, '.harness', 'knowledge'),
115
+ }));
116
+ });
117
+
118
+ it('should keep legacy ~/.studio/knowledge when it still has data, with deprecation notice shown once', async () => {
119
+ const legacyDir = path.join(tmpHome, '.studio', 'knowledge');
120
+ fs.mkdirSync(legacyDir, { recursive: true });
121
+ fs.writeFileSync(path.join(legacyDir, 'guideline-x.md'), 'data');
122
+ await knowledgeStats({ json: true }, io);
99
123
  expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
100
- baseDir: expected,
124
+ baseDir: legacyDir,
125
+ }));
126
+ expect(io.errText()).toContain('旧目录');
127
+ // 闩锁:同进程重复解析只提示一次
128
+ await knowledgeStats({ json: true }, io);
129
+ const notices = io.errLines().filter(line => line.includes('旧目录'));
130
+ expect(notices).toHaveLength(1);
131
+ });
132
+
133
+ it('should ignore legacy dir when it exists but is empty', async () => {
134
+ fs.mkdirSync(path.join(tmpHome, '.studio', 'knowledge'), { recursive: true });
135
+ await knowledgeStats({ json: true }, io);
136
+ expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
137
+ baseDir: path.join(tmpHome, '.harness', 'knowledge'),
101
138
  }));
102
139
  });
103
140
 
104
141
  it('should use projectPath when provided', async () => {
105
- await knowledgeStats({ projectPath: '/my/project', json: true });
142
+ await knowledgeStats({ projectPath: '/my/project', json: true }, io);
106
143
  expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
107
144
  baseDir: '/my/project/.harness/knowledge',
108
145
  }));
@@ -110,81 +147,105 @@ describe('getKnowledgeDir', () => {
110
147
  });
111
148
 
112
149
  describe('knowledgeAudit CLI', () => {
113
- let consoleSpy: jest.SpyInstance;
114
150
 
115
151
  beforeEach(() => {
116
152
  jest.clearAllMocks();
117
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
118
153
  mockRun.mockReturnValue(MOCK_REPORT);
119
154
  });
120
155
 
121
- afterEach(() => {
122
- consoleSpy.mockRestore();
123
- });
124
-
125
156
  it('should output JSON when --json is set', async () => {
126
- await knowledgeAudit({ json: true });
127
- 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');
128
160
  });
129
161
 
130
162
  it('should call audit.run with autoFix=true when --fix is set', async () => {
131
- await knowledgeAudit({ fix: true });
163
+ await knowledgeAudit({ fix: true }, io);
132
164
  expect(mockRun).toHaveBeenCalledWith({ autoFix: true });
133
165
  });
134
166
 
135
167
  it('should call audit.run with autoFix falsy when --dry-run is set', async () => {
136
- await knowledgeAudit({ dryRun: true });
168
+ await knowledgeAudit({ dryRun: true }, io);
137
169
  expect(mockRun).toHaveBeenCalledWith({ autoFix: undefined });
138
170
  });
139
171
 
140
172
  it('should call audit.run with autoFix falsy by default', async () => {
141
- await knowledgeAudit({});
173
+ await knowledgeAudit({}, io);
142
174
  expect(mockRun).toHaveBeenCalledWith({ autoFix: undefined });
143
175
  });
144
176
 
145
177
  it('should display total entries and health score', async () => {
146
- await knowledgeAudit({});
147
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
178
+ await knowledgeAudit({}, io);
179
+ const output = io.outText();
148
180
  expect(output).toContain('总条目: 100');
149
181
  expect(output).toContain('95/100');
150
182
  });
151
183
 
152
184
  it('should display dimension scores', async () => {
153
- await knowledgeAudit({});
154
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
185
+ await knowledgeAudit({}, io);
186
+ const output = io.outText();
155
187
  expect(output).toContain('D1 结构完整性');
156
188
  expect(output).toContain('D6 飞轮验证');
157
189
  });
158
190
 
159
191
  it('should display autoFixed count when > 0', async () => {
160
192
  mockRun.mockReturnValue({ ...MOCK_REPORT, autoFixed: 5, healthScore: { before: 90, after: 95 } });
161
- await knowledgeAudit({ fix: true });
162
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
193
+ await knowledgeAudit({ fix: true }, io);
194
+ const output = io.outText();
163
195
  expect(output).toContain('自动修复: 5');
164
196
  expect(output).toContain('修复后: 95/100');
165
197
  });
166
198
 
167
199
  it('should pass custom dir to KnowledgeAudit', async () => {
168
200
  const { KnowledgeAudit } = require('../../../knowledge/audit');
169
- await knowledgeAudit({ dir: '/custom/path' });
201
+ await knowledgeAudit({ dir: '/custom/path' }, io);
170
202
  expect(KnowledgeAudit).toHaveBeenCalledWith(expect.objectContaining({
171
203
  baseDir: '/custom/path',
172
204
  }));
173
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
+ });
174
238
  });
175
239
 
176
240
  describe('knowledgeHealth CLI', () => {
177
241
  const storeModule = require('../../../knowledge/store');
178
- let consoleSpy: jest.SpyInstance;
179
242
  let storeSpy: jest.SpyInstance;
180
243
 
181
244
  beforeEach(() => {
182
245
  jest.clearAllMocks();
183
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
184
246
  });
185
247
 
186
248
  afterEach(() => {
187
- consoleSpy.mockRestore();
188
249
  if (storeSpy) storeSpy.mockRestore();
189
250
  });
190
251
 
@@ -197,8 +258,8 @@ describe('knowledgeHealth CLI', () => {
197
258
  getBaseDir: jest.fn().mockReturnValue('/tmp/knowledge'),
198
259
  }));
199
260
 
200
- await knowledgeHealth({ json: true });
201
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
261
+ await knowledgeHealth({ json: true }, io);
262
+ const output = io.outText();
202
263
  const parsed = JSON.parse(output);
203
264
  expect(parsed).toHaveProperty('healthScore');
204
265
  expect(parsed.summary).toHaveProperty('total', 2);
@@ -212,8 +273,8 @@ describe('knowledgeHealth CLI', () => {
212
273
  getBaseDir: jest.fn().mockReturnValue('/tmp/knowledge'),
213
274
  }));
214
275
 
215
- await knowledgeHealth({ json: true });
216
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
276
+ await knowledgeHealth({ json: true }, io);
277
+ const output = io.outText();
217
278
  const parsed = JSON.parse(output);
218
279
  expect(parsed.summary.lowRefEntries).toBe(1);
219
280
  });
@@ -227,8 +288,8 @@ describe('knowledgeHealth CLI', () => {
227
288
  getBaseDir: jest.fn().mockReturnValue('/tmp/knowledge'),
228
289
  }));
229
290
 
230
- await knowledgeHealth({ json: true });
231
- const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
291
+ await knowledgeHealth({ json: true }, io);
292
+ const output = io.outText();
232
293
  const parsed = JSON.parse(output);
233
294
  expect(parsed.summary.staleEntries).toBe(1);
234
295
  });
@@ -247,7 +308,6 @@ describe('knowledgeSearch CLI (harness#63)', () => {
247
308
 
248
309
  let tmpProject: string;
249
310
  let knowledgeDir: string;
250
- let consoleSpy: jest.SpyInstance;
251
311
 
252
312
  const FILLER_COUNT = 25; // 25 × ~800 tokens ≈ 20000 > maxTokens 10000
253
313
  const TARGET_ID = 'PIT-020';
@@ -279,7 +339,6 @@ describe('knowledgeSearch CLI (harness#63)', () => {
279
339
  tmpProject = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-k63-'));
280
340
  knowledgeDir = path.join(tmpProject, '.harness', 'knowledge');
281
341
  fs.mkdirSync(knowledgeDir, { recursive: true });
282
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
283
342
 
284
343
  const store = new FileKnowledgeStore({ baseDir: knowledgeDir });
285
344
  // 高成熟度 + 有 lastReferenced 的填充条目排在前面,吃满 token budget
@@ -301,13 +360,12 @@ describe('knowledgeSearch CLI (harness#63)', () => {
301
360
  });
302
361
 
303
362
  afterEach(() => {
304
- consoleSpy.mockRestore();
305
363
  fs.rmSync(tmpProject, { recursive: true, force: true });
306
364
  });
307
365
 
308
366
  async function searchJson(query: string, limit: number): Promise<any> {
309
- await knowledgeSearch(query, { projectPath: tmpProject, json: true, limit });
310
- 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();
311
369
  return JSON.parse(output);
312
370
  }
313
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({