@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
@@ -1,18 +1,19 @@
1
1
  # cli/commands/
2
2
 
3
3
  ## 职责
4
- CLI 子命令实现:24 个顶层命令 + constraints 治理子命令 report/retire,覆盖约束检查/门禁验证/文档同步/知识管理/失败诊断/发布/会话分析等。
4
+ CLI 子命令实现:23 个顶层命令 + constraints 治理子命令 report/retire,覆盖约束检查/门禁验证/文档同步/知识管理/失败诊断/发布/会话分析等。
5
5
 
6
6
  H5(#44)起:
7
7
  - **命令定义即注册**:`definitions.ts` 的 `COMMAND_DEFINITIONS` 是命令形状(名称/别名/位置参数/选项/子命令/实现引用)的单一来源;bin/harness.js 遍历本表注册表驱动生成,不再手写 commander 命令块,不再有 index.ts barrel 两处同步(R6)
8
8
  - **per-command 懒加载**(O2):定义表存实现引用 `{ module, export }`,bin 只在 action 执行时 require 对应命令模块,任一命令执行不再加载全部命令实现
9
9
 
10
10
  ## 核心导出
11
- - `COMMAND_DEFINITIONS`(definitions.ts)— 全部非门禁命令定义(纯数据模块,禁止 import 命令实现)
12
- - 各命令文件:check / validate / passes-gate / init / report / status / spec / sync-docs / knowledge / sdd / failure / posteval-plan / release / analyze-sessions / update-user-model / constraints / doc-freshness-check / spec-baseline-check
11
+ - `COMMAND_DEFINITIONS`(definitions.ts)— 全部非门禁命令定义(纯数据模块、零闭包,禁止 import 命令实现;ADR-0010)
12
+ - 命令契约(`src/cli/command-contract.ts`,上层目录)— `CommandResult` / `CommandKind` / `CommandIO` / `processIO` / `captureIO` / `lastJsonOutput` / `log` / `logError`
13
+ - 各命令文件:check / validate / passes-gate / init / report / status / spec / sync-docs / knowledge / sdd / failure / posteval-plan / release / analyze-sessions / update-user-model / constraints / spec-baseline-check
13
14
  - 6 门禁命令实现在 `acceptance` / `command` / `contract` / `performance` / `review` / `security`(其 CLI 元数据在 `src/gates/definitions.ts`,形状同为 `CommandDefinition`,ADR-0007)
14
15
 
15
- `constraints` 下挂治理子命令:`constraints report`(使用统计 + 退役候选诊断 + 配置健康 + 注入漂移,`--export` 脱敏)、`constraints retire`(交互选择 + 人确认退役;带 id 直达需显式 `--yes`(#24 人确认闸门),无 `--yes` 报错 + 非零退出码且不落盘;内置落 config.yml retired 元数据,custom 落 custom-constraints.yml 条目 retired 段(#82 D6 一处真相)+ KnowledgeStore 沉淀 + 治理注入段同步)。
16
+ `constraints` 下挂治理子命令:`constraints report`(使用统计 + 退役候选诊断 + 配置健康 + 注入漂移,`--export` 脱敏)、`constraints retire`(交互选择 + 人确认退役;带 id 直达需显式 `--yes`(#24 人确认闸门),无 `--yes` 报错 + 非零退出码且不落盘;内置落 config.yml retired 元数据,custom 落 custom-constraints.yml 条目 retired 段(studio#82 D6 一处真相)+ KnowledgeStore 沉淀 + 治理注入段同步)。
16
17
 
17
18
  ## 依赖关系
18
19
  - 依赖 `src/core/constraints/` 约束引擎
@@ -25,13 +26,27 @@ H5(#44)起:
25
26
  ## 约定
26
27
  - 每个命令一个实现文件,命名与命令名一致(sync-docs 为模块族目录)
27
28
  - **新增命令 = 命令实现文件 + definitions.ts 一条定义(含 CLI 元数据与实现引用)+ 测试**,不再改 bin/harness.js
28
- - **definitions.ts 是纯数据模块**:禁止 import 任何命令实现/运行时依赖(保 --help/--version 懒加载);实现引用可解析性由 `__tests__/registry.test.ts` 构建/测试期断言
29
+ - **definitions.ts 是纯数据模块(ADR-0010 后零闭包)**:禁止 import 任何命令实现/运行时依赖(保 --help/--version 懒加载);子命令别名是数据(条目 `aliases: string[]`,不复印整块),实参编组(缺参闸门/强转/兜底)归各命令模块的具名导出(如 `knowledgeSearchCommand`/`coverageCheck`);实现引用可解析性由 `__tests__/registry.test.ts` 构建/测试期断言
29
30
  - 命令选项类型命名规范:XxxOptions
31
+ - **本层是上行数据的注入方(harness#88)**:core 对 cli/gates/monitoring 零值导入,故 `check`/`report` 自己 `new ConstraintChecker(getTraceCollector())`(组合根接线 trace 记录器;用 `constraintChecker`/`getInstance()` 拿到的默认实例不写 trace),`sync-docs` 把 `COMMAND_DEFINITIONS` + `GATE_DEFINITIONS` 组装成 `CapabilityDefinitionSource` 注入 `capabilities-parser` 供能力清单计数
32
+ - **projectPath 只在入口兜底一次,并传到每个 IO/执行点(harness#95)**:`-p/--project-path` 的 cwd 兜底唯一落点是命令模块入口(`const projectPath = options.projectPath || process.cwd()`);往下每层只能收参数,**禁止再取一次 `process.cwd()`、也禁止用相对路径默认值**——两者都让 `-p` 半失效:执行/读写位置悄悄回到调用方 cwd,而 acceptance 的「无 tasks.yml 即跳过」本身就是 `passed:true`,于是失效表现为假绿。落地形状:执行/IO 根用**必传形参**表达(`PassesGate.runTests(workDir)`),门禁内的相对子路径锚到 projectPath:`ContractGate` 一直是 `path.join(context.projectPath, contractPath)`,`SpecAcceptanceGate` 的 tasksPath 随 #95 对齐成 `path.resolve(projectPath, 相对值)`(绝对值原样,缺省 `<projectPath>/tasks.yml`)
33
+ - 机器可检:`__tests__/project-path-convention.test.ts` 三道闸——CLI 层 cwd 必须是 `xxx || process.cwd()` 兜底形状、下游层(core/gates/context/monitoring/hooks)cwd 站点集合冻结、`xxxPath: '相对值'` 默认值冻结;豁免逐条带理由,站点消失却不删条目同样失败
34
+ - 行为可检:`__tests__/project-path-anchoring.test.ts` 前提统一 **cwd ≠ projectPath**、不 mock 任何 IO,断言测试真在 B 执行、证据落 `B/.harness/evidence/`、acceptance 读 `B/tasks.yml`。「根已传入却又取 cwd」这类静态看不出的漂移由它守,冻结表守的是不再新增站点
35
+ - **读到部分结果必须告知(harness#100)**:命令面以 `skip` 策略读 JSONL(traces.log / failures.log)时,坏行计数要到用户可见输出,**不允许默默丢弃**。共同约束:无损坏零噪声(计数为 0 不出行)、不改任何既有计数口径(`status` 的 `记录数` 仍是原始非空行数,逐字节不变)、计数一律取自 `src/utils/jsonl` 读链返回的 `skippedLines`(经报告入口透传或在读点就地取用),**禁止**用「原始行数 − 统计条数」反推(窗过滤掉的合法行与坏行分不开)。三处落点各自对得上一行代码:
36
+ - `status`:告知**走 stderr**(`logError(io, …)`),因为 stdout 是报告体、其字节已被 `记录数` 那一行定死,挤进去等于改输出
37
+ - `constraints report`:提示属于**报告体内的降级位**,与既有的 `traceFileExists` 说明同在 stdout;结构化面另两份——`--json` 的 `skippedLines` 字段、`--export` markdown 的警示行(只报条数,不报路径与坏行内容)
38
+ - `constraints retire`:两条路径都**走 stderr**——交互模式在候选列表前一行(沿用本函数既有的 console 例外,`console.error`),`--yes` 直达分支在落盘前 `logError(io, …)`(`retireConstraint` 本身按契约「纯执行无交互」不打印)
39
+ - 原则正本与逐点去向见 `src/monitoring/CONTEXT.md`「约定」段;机器可检面是 `src/utils/__tests__/jsonl-skip-disposition.test.ts`(冻结全仓 skip 读点集合 + 要求每个读点记名声明 `计数去向:`)
30
40
 
31
41
  ## 注意事项
32
42
  - 新增命令需同步更新 CLAUDE.md / CAPABILITIES.md / src/CONTEXT.md
33
43
  - init 治理约束段写入落点(studio #302,ADR 2026-08-21 落点模型):新仓 → AGENTS.md `PRESERVE:governance` 段(sync-docs 重新生成时保留);旧模型仓(CLAUDE.md 已有 HARNESS_CONSTRAINTS 标记或 `## Governance Rules` 块)→ 续写 CLAUDE.md,幂等重跑不制造双份正本。治理段「在场守护」由 core 侧 governance_presence checker 承担(段缺失/为空时 check 报警)
34
44
  - PRESERVE:governance 段内写入纪律:机器管理的只有 HARNESS_CONSTRAINTS 标记区间——有标记只换标记区间,无标记(纯手写段)在段尾追加注入段;段内其余手写内容(治理契约引言/流程/纪律等)必须原样保留(曾整段替换清空手写内容的回归,init-injection.test 有防回归用例)
35
- - 注入段读取侧同一路由(studio #307):`resolveInjectionTarget`(core/constraints/injection-drift)按「CLAUDE.md 有标记优先,否则 AGENTS.md」定位注入段,`detectInjectionDrift` 与 `constraints retire` 的注入段同步共用;未注入 = 两处均无完整标记段
45
+ - 注入段落点路由与 marker-range 替换收口在 `core/constraints/injection-writer`(ADR-0011):读侧 `resolveInjectionTarget`(漂移检测/retire 同步共用「CLAUDE.md 有标记优先,否则 AGENTS.md」)+ 写侧 `resolveGovernanceLanding`(init 落点选择);init 三个治理段 writer 与 retire 注入同步均为「渲染 body + 调 writer」,半标记(单边/乱序)一律拒写告警;未注入 = 两处均无完整标记段
36
46
  - knowledge 命令包含 13 个子操作(list/search/import/decay/stats/upsert/sync-status/sync-rag/audit/snapshot/migrate/index/health)
37
- - 特殊路由(选项条件、子命令兜底、退出码处理、裸跑语义)表达在定义表的 optionRoutes / subcommands / subcommandStrict / bareRunsAction / afterRun 字段,bin 是纯通用引擎、不含单命令知识
47
+ - `stats` / `health` 的飞轮数字不在 CLI 内计算:分子口径唯一实现是 `knowledge/flywheel-metrics.ts`(`evaluateFlywheel`,ADR-0013),本层只做百分比取整/一位小数与字段名映射;`.consumption-stats.json` 的读取留在各命令(module IO)
48
+ - 特殊路由(选项条件、子命令兜底、裸跑语义)表达在定义表的 optionRoutes / subcommands / subcommandStrict / bareRunsAction 字段,bin 是纯通用引擎、不含单命令知识
49
+ - **命令 interface = `CommandResult` + 注入 io(架构评审候选7)**:命令实现一律声明 `Promise<CommandResult>`(判别联合 `ok|skip|fail|usage-error`,`fail`/`usage-error` 必附可定位的 `reason`,多闸门命令 reason 含 `gate <id>`),末位可选形参 `io: CommandIO = processIO`;类型与写入面在 `src/cli/command-contract.ts`
50
+ - **本目录零 `process.exit` / `process.exitCode`**:kind → 退出码的唯一映射在 `bin/harness.js`(`ok`/`skip` → 0,`fail`/`usage-error` → 1,未知 kind fail-closed)。历史上两处条件式(`command --level` 按严重级、门禁按 passed)已由命令侧译成 kind;`sync-docs --check` 的漂移改由实现返回 `fail`(定义表的 `afterRun` 逃生门随之废除)
51
+ - **输出不得直接用 console**:流式打印走 `log(io, …)` / `logError(io, …)`(`util.format` + 换行,与 console 逐字节等价,见 `src/cli/__tests__/command-contract.test.ts`);测试断言输出用 `captureIO()`,不再 `spyOn(process, 'exit')`;`--json` 输出断言用 `lastJsonOutput(io)`(JSON.parse 正本,harness#108)。例外:`constraints retire` 交互正文仍走 console(其 `RetireIO` 只注入 readline 流),退役结果打印已接注入流
52
+ - **#95 同型扫荡结论**:两门禁站点已修(`passes-gate` 执行/证据落 projectPath、`acceptance` 去掉 `tasksPath: './tasks.yml'` 相对默认值)。逐点判定后的豁免三处,理由与站点原文一起冻结在 `__tests__/project-path-convention.test.ts` 的豁免表:`release` 的 `pkgPath`(定义表本无 `-p`,pkgPath 就是该命令唯一的根且已逐个传给每条 `run(cmd, pkgPath)`,不构成半失效;给发布流水线新增 `-p` 属新能力)、`constraints` 的 `join(process.cwd(), 'package.json')`(报的是 harness 自身包版本,主锚 `__dirname`,cwd 仅兜底)、`core/spec/validator.ts` 的 `schemaPath: './specs/schemas'`(**确是同型病灶**:`validateAll(projectPath)` 用 projectPath 找 spec 文件、却用 cwd 找 schema;但 `validateFile`/`loadSchema` 签名里没有根,补齐要穿透整个 spec 域,留待 spec 单票收口)
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { acceptance, listAcceptanceCriteria } from '../acceptance';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import * as fs from 'fs/promises';
7
8
  import { SpecAcceptanceGate } from '../../../gates/acceptance';
8
9
 
@@ -35,20 +36,12 @@ const MockGate = SpecAcceptanceGate as jest.MockedClass<typeof SpecAcceptanceGat
35
36
  const yaml = require('js-yaml');
36
37
 
37
38
  describe('acceptance command', () => {
38
- let consoleSpy: jest.SpyInstance;
39
- let exitSpy: jest.SpyInstance;
39
+ let io: CapturingIO;
40
40
 
41
41
  beforeEach(() => {
42
- jest.clearAllMocks();
43
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
44
- exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
45
- process.exitCode = 0;
46
- });
47
42
 
48
- afterEach(() => {
49
- consoleSpy.mockRestore();
50
- exitSpy.mockRestore();
51
- process.exitCode = 0;
43
+ io = captureIO();
44
+ jest.clearAllMocks();
52
45
  });
53
46
 
54
47
  describe('acceptance', () => {
@@ -60,10 +53,10 @@ describe('acceptance command', () => {
60
53
  });
61
54
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
62
55
 
63
- await acceptance({});
56
+ const result = await acceptance({}, io);
64
57
 
65
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('验收标准检查通过'));
66
- expect(exitSpy).not.toHaveBeenCalled();
58
+ expect(io.outText()).toContain('验收标准检查通过');
59
+ expect(result.kind).toBe('ok');
67
60
  });
68
61
 
69
62
  it('should print failure and exit 1 when check fails', async () => {
@@ -74,27 +67,33 @@ describe('acceptance command', () => {
74
67
  });
75
68
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
76
69
 
77
- await acceptance({});
70
+ const result = await acceptance({}, io);
78
71
 
79
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('验收标准检查失败'));
80
- expect(exitSpy).toHaveBeenCalledWith(1);
72
+ expect(io.outText()).toContain('验收标准检查失败');
73
+ expect(result).toEqual({
74
+ kind: 'fail',
75
+ reason: 'acceptance gate denied: criteria not met',
76
+ });
81
77
  });
82
78
 
83
79
  it('should handle errors and exit 1', async () => {
84
80
  const mockCheck = jest.fn().mockRejectedValue(new Error('gate error'));
85
81
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
86
82
 
87
- await acceptance({});
83
+ const result = await acceptance({}, io);
88
84
 
89
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('验收标准检查出错'));
90
- expect(exitSpy).toHaveBeenCalledWith(1);
85
+ expect(io.outText()).toContain('验收标准检查出错');
86
+ expect(result).toEqual({
87
+ kind: 'fail',
88
+ reason: 'acceptance gate error: gate error',
89
+ });
91
90
  });
92
91
 
93
92
  it('should use cwd as default projectPath', async () => {
94
93
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
95
94
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
96
95
 
97
- await acceptance({});
96
+ await acceptance({}, io);
98
97
 
99
98
  expect(mockCheck).toHaveBeenCalledWith(
100
99
  expect.objectContaining({ projectPath: process.cwd() }),
@@ -105,7 +104,7 @@ describe('acceptance command', () => {
105
104
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
106
105
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
107
106
 
108
- await acceptance({ taskId: 'TASK-001' });
107
+ await acceptance({ taskId: 'TASK-001' }, io);
109
108
 
110
109
  expect(mockCheck).toHaveBeenCalledWith(
111
110
  expect.objectContaining({ taskId: 'TASK-001' }),
@@ -116,7 +115,7 @@ describe('acceptance command', () => {
116
115
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
117
116
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
118
117
 
119
- await acceptance({ runE2e: true });
118
+ await acceptance({ runE2e: true }, io);
120
119
 
121
120
  expect(MockGate).toHaveBeenCalledWith(
122
121
  expect.objectContaining({ e2eTestCommand: 'npx playwright test' }),
@@ -127,7 +126,7 @@ describe('acceptance command', () => {
127
126
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
128
127
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
129
128
 
130
- await acceptance({});
129
+ await acceptance({}, io);
131
130
 
132
131
  expect(MockGate).toHaveBeenCalledWith(
133
132
  expect.objectContaining({ e2eTestCommand: undefined }),
@@ -143,10 +142,10 @@ describe('acceptance command', () => {
143
142
  'task-2': { acceptanceCriteria: ['Criteria A'] },
144
143
  });
145
144
 
146
- await listAcceptanceCriteria({});
145
+ await listAcceptanceCriteria({}, io);
147
146
 
148
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('task-1'));
149
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Criteria 1'));
147
+ expect(io.outText()).toContain('task-1');
148
+ expect(io.outText()).toContain('Criteria 1');
150
149
  });
151
150
 
152
151
  it('should handle task with no criteria', async () => {
@@ -155,26 +154,34 @@ describe('acceptance command', () => {
155
154
  'task-1': {},
156
155
  });
157
156
 
158
- await listAcceptanceCriteria({});
157
+ await listAcceptanceCriteria({}, io);
159
158
 
160
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('无验收标准'));
159
+ expect(io.outText()).toContain('无验收标准');
161
160
  });
162
161
 
163
162
  it('should handle empty tasks object', async () => {
164
163
  mockFs.readFile.mockResolvedValue('yaml-content');
165
164
  yaml.load.mockReturnValue(null);
166
165
 
167
- await listAcceptanceCriteria({});
166
+ const result = await listAcceptanceCriteria({}, io);
168
167
 
169
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('未找到任务定义'));
168
+ expect(io.outText()).toContain('未找到任务定义');
169
+ expect(result).toEqual({
170
+ kind: 'skip',
171
+ reason: expect.stringContaining('未找到任务定义'),
172
+ });
170
173
  });
171
174
 
172
175
  it('should handle file read error', async () => {
173
176
  mockFs.readFile.mockRejectedValue(new Error('ENOENT'));
174
177
 
175
- await listAcceptanceCriteria({});
178
+ const result = await listAcceptanceCriteria({}, io);
176
179
 
177
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('读取 tasks.yml 失败'));
180
+ expect(io.outText()).toContain('读取 tasks.yml 失败');
181
+ expect(result).toEqual({
182
+ kind: 'skip',
183
+ reason: expect.stringContaining('读取 tasks.yml 失败'),
184
+ });
178
185
  });
179
186
  });
180
187
  });
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import * as fs from 'fs';
12
+ import { captureIO, lastJsonOutput, type CapturingIO } from '../../command-contract';
12
13
  import * as path from 'path';
13
14
  import { analyzeSessions } from '../analyze-sessions';
14
15
  import { readTranscriptSessions, type MinedSession } from '../../session-mining';
@@ -69,13 +70,12 @@ function mkSession(partial: Partial<MinedSession> = {}): MinedSession {
69
70
  };
70
71
  }
71
72
 
72
- function lastJsonOutput(consoleSpy: jest.SpyInstance): Record<string, unknown> {
73
- const jsonLine = consoleSpy.mock.calls.map(c => c[0]).join('\n');
74
- return JSON.parse(jsonLine);
75
- }
73
+ let io: CapturingIO;
74
+ beforeEach(() => {
75
+ io = captureIO();
76
+ });
76
77
 
77
78
  describe('analyze-sessions command', () => {
78
- let consoleSpy: jest.SpyInstance;
79
79
 
80
80
  beforeEach(() => {
81
81
  jest.clearAllMocks();
@@ -83,42 +83,48 @@ describe('analyze-sessions command', () => {
83
83
  fs.mkdirSync(path.join(TEST_HOME, '.claude', 'projects', '-root-projects', 'memory'), { recursive: true });
84
84
  fs.mkdirSync(TRANSCRIPTS_DIR, { recursive: true });
85
85
  process.env.CLAUDE_TRANSCRIPTS_DIR = TRANSCRIPTS_DIR;
86
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
87
86
  });
88
87
 
89
88
  afterEach(() => {
90
- consoleSpy.mockRestore();
91
89
  delete process.env.CLAUDE_TRANSCRIPTS_DIR;
92
90
  });
93
91
 
94
92
  test('transcripts 目录不存在:提示 No transcripts directory found', async () => {
95
93
  process.env.CLAUDE_TRANSCRIPTS_DIR = path.join(TEST_HOME, 'missing-dir');
96
94
 
97
- await analyzeSessions({});
95
+ const result = await analyzeSessions({}, io);
98
96
 
99
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No transcripts directory found'));
97
+ expect(io.outText()).toContain('No transcripts directory found');
98
+ expect(result).toEqual({ kind: 'skip', reason: expect.stringContaining('会话记录目录不存在') });
100
99
  expect(mockReadTranscriptSessions).not.toHaveBeenCalled();
101
100
  });
102
101
 
103
102
  test('窗口内无会话:提示 No sessions found(默认最近 7 天)', async () => {
104
- mockReadTranscriptSessions.mockReturnValue([
105
- mkSession({ id: 'old', mtimeMs: Date.now() - 30 * 86_400_000 }),
106
- ]);
107
-
108
- await analyzeSessions({});
109
-
110
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No sessions found in the last 7 days'));
103
+ const before = Date.now() - 7 * 86_400_000;
104
+ mockReadTranscriptSessions.mockReturnValue([]);
105
+
106
+ const result = await analyzeSessions({}, io);
107
+ const after = Date.now() - 7 * 86_400_000;
108
+
109
+ expect(io.outText()).toContain('No sessions found in the last 7 days');
110
+ expect(result).toEqual({ kind: 'skip', reason: '最近 7 天没有会话' });
111
+ // since 过滤下推到 seam:默认窗口 7 天
112
+ const filter = mockReadTranscriptSessions.mock.calls[0][1] as { since?: number };
113
+ expect(filter.since).toBeGreaterThanOrEqual(before);
114
+ expect(filter.since).toBeLessThanOrEqual(after);
111
115
  });
112
116
 
113
- test('--days 1:只统计最近 1 天(mtimeMs 窗口过滤)', async () => {
114
- mockReadTranscriptSessions.mockReturnValue([
115
- mkSession({ id: 'today' }),
116
- mkSession({ id: 'two-days-ago', mtimeMs: Date.now() - 2 * 86_400_000 }),
117
- ]);
117
+ test('--days 1:since 过滤下推到 seam(mtimeMs 窗口)', async () => {
118
+ const before = Date.now() - 86_400_000;
119
+ mockReadTranscriptSessions.mockReturnValue([mkSession({ id: 'today' })]);
118
120
 
119
- await analyzeSessions({ days: 1 });
121
+ await analyzeSessions({ days: 1 }, io);
122
+ const after = Date.now() - 86_400_000;
120
123
 
121
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Analyzing 1 sessions (last 1 days)'));
124
+ expect(io.outText()).toContain('Analyzing 1 sessions (last 1 days)');
125
+ const filter = mockReadTranscriptSessions.mock.calls[0][1] as { since?: number };
126
+ expect(filter.since).toBeGreaterThanOrEqual(before);
127
+ expect(filter.since).toBeLessThanOrEqual(after);
122
128
  });
123
129
 
124
130
  test('缺省 --days:窗口为 7 天,两天前会话仍计入', async () => {
@@ -127,9 +133,9 @@ describe('analyze-sessions command', () => {
127
133
  mkSession({ id: 'two-days-ago', mtimeMs: Date.now() - 2 * 86_400_000 }),
128
134
  ]);
129
135
 
130
- await analyzeSessions({});
136
+ await analyzeSessions({}, io);
131
137
 
132
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Analyzing 2 sessions (last 7 days)'));
138
+ expect(io.outText()).toContain('Analyzing 2 sessions (last 7 days)');
133
139
  });
134
140
 
135
141
  test('--json:纠正句跨 3 会话聚合为 correction 候选', async () => {
@@ -139,9 +145,9 @@ describe('analyze-sessions command', () => {
139
145
  mkSession({ id: 's3' }),
140
146
  ]);
141
147
 
142
- await analyzeSessions({ json: true });
148
+ await analyzeSessions({ json: true }, io);
143
149
 
144
- const output = lastJsonOutput(consoleSpy);
150
+ const output = lastJsonOutput(io);
145
151
  expect(output.sessions).toBe(3);
146
152
  expect(output.corrections).toBe(3);
147
153
  expect(output.candidates).toEqual(
@@ -168,9 +174,9 @@ describe('analyze-sessions command', () => {
168
174
  }),
169
175
  ]);
170
176
 
171
- await analyzeSessions({ json: true });
177
+ await analyzeSessions({ json: true }, io);
172
178
 
173
- const output = lastJsonOutput(consoleSpy);
179
+ const output = lastJsonOutput(io);
174
180
  const ngramCandidates = (output.candidates as Array<{ source: string }>)
175
181
  .filter(c => c.source === 'ngram');
176
182
  expect(ngramCandidates.length).toBeGreaterThan(0);
@@ -0,0 +1,101 @@
1
+ /**
2
+ * bin 侧退出码映射测试(架构评审候选7)
3
+ *
4
+ * 命令实现只返回 CommandResult,`process.exit` 的唯一出口在 bin/harness.js。
5
+ * 两层证据:
6
+ * 1. exitCodeFor 纯映射:四种 kind + 契约违规(未知/缺失 kind)fail-closed;
7
+ * 2. 端到端 spawn bin/harness.js(dist 存在时):真实命令的对外退出码
8
+ * ——断言进程 status,不用 spyOn(process,'exit')。
9
+ * 另加零 process.exit 的源码护栏(命令实现/定义表目录)。
10
+ */
11
+
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import { spawnSync } from 'child_process';
15
+
16
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
17
+ const { exitCodeFor } = require('../../../../bin/harness.js');
18
+
19
+ const repoRoot = path.join(__dirname, '..', '..', '..', '..');
20
+ const distBin = path.join(repoRoot, 'bin', 'harness.js');
21
+ const distCommands = path.join(repoRoot, 'dist', 'cli', 'commands');
22
+ const hasDist = fs.existsSync(distCommands);
23
+
24
+ describe('exitCodeFor:kind → 退出码唯一映射', () => {
25
+ it('ok / skip → 0(现状对外码面只有 0/1,skip 不新增码值)', () => {
26
+ expect(exitCodeFor({ kind: 'ok' })).toBe(0);
27
+ expect(exitCodeFor({ kind: 'skip' })).toBe(0);
28
+ expect(exitCodeFor({ kind: 'skip', reason: '没有定义检查点' })).toBe(0);
29
+ });
30
+
31
+ it('fail / usage-error → 1', () => {
32
+ expect(exitCodeFor({ kind: 'fail', reason: 'gate branch: 非 master' })).toBe(1);
33
+ expect(exitCodeFor({ kind: 'usage-error', reason: '缺少关键词' })).toBe(1);
34
+ });
35
+
36
+ it('未知或缺失 kind → fail-closed(命令契约违规不得静默通过)', () => {
37
+ const errSpy = jest.spyOn(console, 'error').mockImplementation();
38
+ try {
39
+ expect(exitCodeFor({ kind: 'exploded' })).toBe(1);
40
+ expect(exitCodeFor(undefined)).toBe(1);
41
+ expect(exitCodeFor({})).toBe(1);
42
+ expect(errSpy).toHaveBeenCalledTimes(3);
43
+ } finally {
44
+ errSpy.mockRestore();
45
+ }
46
+ });
47
+ });
48
+
49
+ describe('命令实现/定义表目录零 process.exit(唯一出口在 bin)', () => {
50
+ const dirs = [
51
+ path.join(repoRoot, 'src', 'cli', 'commands'),
52
+ path.join(repoRoot, 'src', 'cli', 'commands', 'sync-docs'),
53
+ ];
54
+
55
+ it.each(dirs.map(d => [d]))('%s', (dir: string) => {
56
+ const offenders = fs.readdirSync(dir)
57
+ .filter(f => f.endsWith('.ts') && !f.includes('.test.'))
58
+ .filter(f => fs.readFileSync(path.join(dir, f), 'utf-8').includes('process.exit'))
59
+ .map(f => `${dir}/${f}`);
60
+ expect(offenders).toEqual([]);
61
+ });
62
+
63
+ it('bin/harness.js 只有一处 process.exit 调用点', () => {
64
+ const source = fs.readFileSync(distBin, 'utf-8');
65
+ const calls = [...source.matchAll(/process\.exit\(/g)];
66
+ expect(calls).toHaveLength(1);
67
+ });
68
+ });
69
+
70
+ const smoke = hasDist ? describe : describe.skip;
71
+
72
+ smoke('端到端:真实命令经 bin 映射后的对外退出码', () => {
73
+ const run = (argv: string[]) => spawnSync(process.execPath, [distBin, ...argv], {
74
+ cwd: repoRoot,
75
+ encoding: 'utf-8',
76
+ timeout: 60000,
77
+ });
78
+
79
+ it('放行命令 → 0(返回 ok,进程自然退出)', () => {
80
+ const r = run(['command', 'ls']);
81
+ expect(r.status).toBe(0);
82
+ });
83
+
84
+ it('门禁否决 → 1(返回 fail,bin 映射)', () => {
85
+ const r = run(['command', 'rm -rf /']);
86
+ expect(r.status).toBe(1);
87
+ expect(r.stdout).toContain('✗');
88
+ });
89
+
90
+ it('用法错误 → 1(返回 usage-error)', () => {
91
+ const r = run(['kb', 's']);
92
+ expect(r.status).toBe(1);
93
+ expect(r.stderr).toContain('请提供搜索关键词');
94
+ });
95
+
96
+ it('bin 自身子命令路由错误 → 1', () => {
97
+ const r = run(['knowledge', 'bogus']);
98
+ expect(r.status).toBe(1);
99
+ expect(r.stderr).toContain('未知子命令: bogus');
100
+ });
101
+ });
@@ -7,18 +7,23 @@
7
7
  */
8
8
 
9
9
  import * as fs from 'fs';
10
- import * as os from 'os';
10
+ import { captureIO, type CapturingIO } from '../../command-contract';
11
11
  import * as path from 'path';
12
12
  import { check } from '../check';
13
- import { constraintChecker } from '../../../core/constraints/checker';
14
13
  import { renderConstraintsSection } from '../../../core/constraints/injection-renderer';
15
14
  import { getEffectiveConstraints } from '../../../core/effective-constraints';
15
+ import { createProjectFixture } from '../../../test-setup/project-fixture';
16
16
 
17
+ // 命令 per-run 构造 checker(harness#88):构造替身即控制 checkConstraints 返回值
18
+ const mockChecker = { checkConstraints: jest.fn() };
17
19
  jest.mock('../../../core/constraints/checker', () => ({
18
- constraintChecker: {
19
- setTraceRecorder: jest.fn(),
20
- checkConstraints: jest.fn(),
21
- },
20
+ ConstraintChecker: jest.fn(function () {
21
+ return mockChecker;
22
+ }),
23
+ }));
24
+
25
+ jest.mock('../../../monitoring/traces', () => ({
26
+ getTraceCollector: jest.fn(() => ({ record: jest.fn() })),
22
27
  }));
23
28
 
24
29
  jest.mock('../../../utils/exec', () => ({
@@ -30,15 +35,9 @@ jest.mock('child_process', () => ({
30
35
  execSync: jest.fn(() => Buffer.from('')),
31
36
  }));
32
37
 
33
- const mockChecker = constraintChecker as jest.Mocked<typeof constraintChecker>;
34
-
35
38
  // eslint-disable-next-line @typescript-eslint/no-var-requires
36
39
  const REAL_VERSION = require('../../../../package.json').version as string;
37
40
 
38
- function makeTmpProject(): string {
39
- return fs.mkdtempSync(path.join(os.tmpdir(), 'harness-check-drift-test-'));
40
- }
41
-
42
41
  function writeClaudeMd(root: string, version: string): void {
43
42
  const section =
44
43
  '## Governance Rules\n' + renderConstraintsSection(getEffectiveConstraints(root), version);
@@ -46,13 +45,12 @@ function writeClaudeMd(root: string, version: string): void {
46
45
  }
47
46
 
48
47
  describe('check 命令注入漂移警告', () => {
49
- let consoleSpy: jest.SpyInstance;
50
- let exitSpy: jest.SpyInstance;
48
+ let io: CapturingIO;
51
49
 
52
50
  beforeEach(() => {
51
+
52
+ io = captureIO();
53
53
  jest.clearAllMocks();
54
- consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
55
- exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
56
54
  mockChecker.checkConstraints.mockResolvedValue({
57
55
  passed: true,
58
56
  ironLaws: [],
@@ -61,18 +59,13 @@ describe('check 命令注入漂移警告', () => {
61
59
  });
62
60
  });
63
61
 
64
- afterEach(() => {
65
- consoleSpy.mockRestore();
66
- exitSpy.mockRestore();
67
- });
68
-
69
- const outputText = () => consoleSpy.mock.calls.map(c => String(c[0])).join('\n');
62
+ const outputText = () => io.outText();
70
63
 
71
64
  it('版本漂移:黄色警告块 + ⚠️⚠️ 版本行,但不阻断(exit 未调用,仍判通过)', async () => {
72
- const root = makeTmpProject();
65
+ const root = createProjectFixture({ name: 'harness-check-drift-test' });
73
66
  writeClaudeMd(root, '0.0.1-old');
74
67
 
75
- await check({ preset: 'standard', staged: false, projectPath: root });
68
+ const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
76
69
 
77
70
  const output = outputText();
78
71
  expect(output).toContain('约束注入漂移');
@@ -82,43 +75,43 @@ describe('check 命令注入漂移警告', () => {
82
75
  expect(output).toContain('npx @dommaker/harness init');
83
76
  // 不阻断:检查仍通过、未调用 process.exit
84
77
  expect(output).toContain('约束检查通过');
85
- expect(exitSpy).not.toHaveBeenCalled();
78
+ expect(result.kind).toBe('ok');
86
79
  });
87
80
 
88
81
  it('内容漂移:手改一条 → 警告块含缺失/多余计数,exit 未调用', async () => {
89
- const root = makeTmpProject();
82
+ const root = createProjectFixture({ name: 'harness-check-drift-test' });
90
83
  writeClaudeMd(root, REAL_VERSION);
91
84
  const claudeMdPath = path.join(root, 'CLAUDE.md');
92
85
  const content = fs.readFileSync(claudeMdPath, 'utf-8');
93
86
  const originalLine = content.split('\n').find(l => l.startsWith('- **'))!;
94
87
  fs.writeFileSync(claudeMdPath, content.replace(originalLine, originalLine.replace(/: .+$/, ': 篡改')), 'utf-8');
95
88
 
96
- await check({ preset: 'standard', staged: false, projectPath: root });
89
+ const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
97
90
 
98
91
  const output = outputText();
99
92
  expect(output).toContain('内容漂移: 缺失 1 条 / 多余 1 条');
100
93
  expect(output).toContain('约束检查通过');
101
- expect(exitSpy).not.toHaveBeenCalled();
94
+ expect(result.kind).toBe('ok');
102
95
  });
103
96
 
104
97
  it('无漂移:零警告输出(不增加噪音)', async () => {
105
- const root = makeTmpProject();
98
+ const root = createProjectFixture({ name: 'harness-check-drift-test' });
106
99
  writeClaudeMd(root, REAL_VERSION);
107
100
 
108
- await check({ preset: 'standard', staged: false, projectPath: root });
101
+ const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
109
102
 
110
103
  const output = outputText();
111
104
  expect(output).not.toContain('注入漂移');
112
105
  expect(output).toContain('约束检查通过');
113
- expect(exitSpy).not.toHaveBeenCalled();
106
+ expect(result.kind).toBe('ok');
114
107
  });
115
108
 
116
109
  it('未注入(无 CLAUDE.md):不警告', async () => {
117
- const root = makeTmpProject();
110
+ const root = createProjectFixture({ name: 'harness-check-drift-test' });
118
111
 
119
- await check({ preset: 'standard', staged: false, projectPath: root });
112
+ const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
120
113
 
121
114
  expect(outputText()).not.toContain('注入漂移');
122
- expect(exitSpy).not.toHaveBeenCalled();
115
+ expect(result.kind).toBe('ok');
123
116
  });
124
117
  });