@dommaker/harness 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (532) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +0 -2
  3. package/bin/harness.js +86 -32
  4. package/dist/cli/command-contract.d.ts +83 -0
  5. package/dist/cli/command-contract.d.ts.map +1 -0
  6. package/dist/cli/command-contract.js +66 -0
  7. package/dist/cli/command-contract.js.map +1 -0
  8. package/dist/cli/commands/acceptance.d.ts +3 -2
  9. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  10. package/dist/cli/commands/acceptance.js +28 -24
  11. package/dist/cli/commands/acceptance.js.map +1 -1
  12. package/dist/cli/commands/analyze-sessions.d.ts +2 -1
  13. package/dist/cli/commands/analyze-sessions.d.ts.map +1 -1
  14. package/dist/cli/commands/analyze-sessions.js +25 -24
  15. package/dist/cli/commands/analyze-sessions.js.map +1 -1
  16. package/dist/cli/commands/check.d.ts +14 -2
  17. package/dist/cli/commands/check.d.ts.map +1 -1
  18. package/dist/cli/commands/check.js +104 -61
  19. package/dist/cli/commands/check.js.map +1 -1
  20. package/dist/cli/commands/command.d.ts +5 -1
  21. package/dist/cli/commands/command.d.ts.map +1 -1
  22. package/dist/cli/commands/command.js +26 -17
  23. package/dist/cli/commands/command.js.map +1 -1
  24. package/dist/cli/commands/constraints-report.d.ts +2 -1
  25. package/dist/cli/commands/constraints-report.d.ts.map +1 -1
  26. package/dist/cli/commands/constraints-report.js +37 -33
  27. package/dist/cli/commands/constraints-report.js.map +1 -1
  28. package/dist/cli/commands/constraints-retire.d.ts +6 -5
  29. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  30. package/dist/cli/commands/constraints-retire.js +50 -52
  31. package/dist/cli/commands/constraints-retire.js.map +1 -1
  32. package/dist/cli/commands/constraints.d.ts +2 -1
  33. package/dist/cli/commands/constraints.d.ts.map +1 -1
  34. package/dist/cli/commands/constraints.js +10 -27
  35. package/dist/cli/commands/constraints.js.map +1 -1
  36. package/dist/cli/commands/contract.d.ts +3 -2
  37. package/dist/cli/commands/contract.d.ts.map +1 -1
  38. package/dist/cli/commands/contract.js +45 -42
  39. package/dist/cli/commands/contract.js.map +1 -1
  40. package/dist/cli/commands/definitions.d.ts +11 -11
  41. package/dist/cli/commands/definitions.d.ts.map +1 -1
  42. package/dist/cli/commands/definitions.js +27 -219
  43. package/dist/cli/commands/definitions.js.map +1 -1
  44. package/dist/cli/commands/failure.d.ts +5 -4
  45. package/dist/cli/commands/failure.d.ts.map +1 -1
  46. package/dist/cli/commands/failure.js +31 -25
  47. package/dist/cli/commands/failure.js.map +1 -1
  48. package/dist/cli/commands/init.d.ts +7 -5
  49. package/dist/cli/commands/init.d.ts.map +1 -1
  50. package/dist/cli/commands/init.js +147 -211
  51. package/dist/cli/commands/init.js.map +1 -1
  52. package/dist/cli/commands/knowledge.d.ts +17 -30
  53. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  54. package/dist/cli/commands/knowledge.js +183 -273
  55. package/dist/cli/commands/knowledge.js.map +1 -1
  56. package/dist/cli/commands/passes-gate.d.ts +8 -2
  57. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  58. package/dist/cli/commands/passes-gate.js +44 -66
  59. package/dist/cli/commands/passes-gate.js.map +1 -1
  60. package/dist/cli/commands/performance.d.ts +2 -1
  61. package/dist/cli/commands/performance.d.ts.map +1 -1
  62. package/dist/cli/commands/performance.js +20 -18
  63. package/dist/cli/commands/performance.js.map +1 -1
  64. package/dist/cli/commands/posteval-plan.d.ts +5 -1
  65. package/dist/cli/commands/posteval-plan.d.ts.map +1 -1
  66. package/dist/cli/commands/posteval-plan.js +26 -15
  67. package/dist/cli/commands/posteval-plan.js.map +1 -1
  68. package/dist/cli/commands/release.d.ts +6 -1
  69. package/dist/cli/commands/release.d.ts.map +1 -1
  70. package/dist/cli/commands/release.js +69 -59
  71. package/dist/cli/commands/release.js.map +1 -1
  72. package/dist/cli/commands/report.d.ts +2 -1
  73. package/dist/cli/commands/report.d.ts.map +1 -1
  74. package/dist/cli/commands/report.js +9 -5
  75. package/dist/cli/commands/report.js.map +1 -1
  76. package/dist/cli/commands/review.d.ts +3 -2
  77. package/dist/cli/commands/review.d.ts.map +1 -1
  78. package/dist/cli/commands/review.js +33 -29
  79. package/dist/cli/commands/review.js.map +1 -1
  80. package/dist/cli/commands/sdd.d.ts +2 -1
  81. package/dist/cli/commands/sdd.d.ts.map +1 -1
  82. package/dist/cli/commands/sdd.js +7 -5
  83. package/dist/cli/commands/sdd.js.map +1 -1
  84. package/dist/cli/commands/security.d.ts +3 -2
  85. package/dist/cli/commands/security.d.ts.map +1 -1
  86. package/dist/cli/commands/security.js +29 -26
  87. package/dist/cli/commands/security.js.map +1 -1
  88. package/dist/cli/commands/spec-baseline-check.d.ts +2 -1
  89. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  90. package/dist/cli/commands/spec-baseline-check.js +15 -11
  91. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  92. package/dist/cli/commands/spec.d.ts +7 -2
  93. package/dist/cli/commands/spec.d.ts.map +1 -1
  94. package/dist/cli/commands/spec.js +61 -51
  95. package/dist/cli/commands/spec.js.map +1 -1
  96. package/dist/cli/commands/status.d.ts +2 -1
  97. package/dist/cli/commands/status.d.ts.map +1 -1
  98. package/dist/cli/commands/status.js +57 -53
  99. package/dist/cli/commands/status.js.map +1 -1
  100. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  101. package/dist/cli/commands/sync-docs/agents-syncer.js +5 -2
  102. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  103. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  104. package/dist/cli/commands/sync-docs/context-syncer.js +3 -2
  105. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  106. package/dist/cli/commands/sync-docs/index.d.ts +2 -1
  107. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  108. package/dist/cli/commands/sync-docs/index.js +68 -88
  109. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  110. package/dist/cli/commands/sync-docs/project-reader.d.ts +6 -1
  111. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  112. package/dist/cli/commands/sync-docs/project-reader.js +14 -16
  113. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  114. package/dist/cli/commands/update-user-model.d.ts +9 -3
  115. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  116. package/dist/cli/commands/update-user-model.js +56 -61
  117. package/dist/cli/commands/update-user-model.js.map +1 -1
  118. package/dist/cli/commands/validate.d.ts +4 -3
  119. package/dist/cli/commands/validate.d.ts.map +1 -1
  120. package/dist/cli/commands/validate.js +28 -26
  121. package/dist/cli/commands/validate.js.map +1 -1
  122. package/dist/cli/session-mining/transcript.d.ts +7 -1
  123. package/dist/cli/session-mining/transcript.d.ts.map +1 -1
  124. package/dist/cli/session-mining/transcript.js +21 -14
  125. package/dist/cli/session-mining/transcript.js.map +1 -1
  126. package/dist/context/session-manager.d.ts.map +1 -1
  127. package/dist/context/session-manager.js +19 -32
  128. package/dist/context/session-manager.js.map +1 -1
  129. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +1 -1
  130. package/dist/core/constraints/agent-prompt-renderer.js +1 -4
  131. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  132. package/dist/core/constraints/capabilities-parser.d.ts +24 -7
  133. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  134. package/dist/core/constraints/capabilities-parser.js +32 -28
  135. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  136. package/dist/core/constraints/capabilities-reconcile.d.ts +67 -0
  137. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -0
  138. package/dist/core/constraints/capabilities-reconcile.js +105 -0
  139. package/dist/core/constraints/capabilities-reconcile.js.map +1 -0
  140. package/dist/core/constraints/checker.d.ts +21 -43
  141. package/dist/core/constraints/checker.d.ts.map +1 -1
  142. package/dist/core/constraints/checker.js +68 -117
  143. package/dist/core/constraints/checker.js.map +1 -1
  144. package/dist/core/constraints/checkers/capability-sync.d.ts +20 -8
  145. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  146. package/dist/core/constraints/checkers/capability-sync.js +66 -77
  147. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  148. package/dist/core/constraints/checkers/context-doc-sync.d.ts +4 -2
  149. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  150. package/dist/core/constraints/checkers/context-doc-sync.js +13 -22
  151. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  152. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  153. package/dist/core/constraints/checkers/docs-freshness.js +49 -66
  154. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  155. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -2
  156. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  157. package/dist/core/constraints/checkers/governance-presence.js +7 -28
  158. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  159. package/dist/core/constraints/checkers/index.d.ts +2 -2
  160. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  161. package/dist/core/constraints/checkers/index.js +2 -1
  162. package/dist/core/constraints/checkers/index.js.map +1 -1
  163. package/dist/core/constraints/checkers/no-test-simplification.d.ts.map +1 -1
  164. package/dist/core/constraints/checkers/no-test-simplification.js +4 -3
  165. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  166. package/dist/core/constraints/checkers/types.d.ts +43 -4
  167. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  168. package/dist/core/constraints/checkers/types.js +33 -0
  169. package/dist/core/constraints/checkers/types.js.map +1 -1
  170. package/dist/core/constraints/context-builder.d.ts +15 -14
  171. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  172. package/dist/core/constraints/context-builder.js +32 -96
  173. package/dist/core/constraints/context-builder.js.map +1 -1
  174. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  175. package/dist/core/constraints/doc-freshness/runner.js +2 -1
  176. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  177. package/dist/core/constraints/git-evidence.d.ts +60 -0
  178. package/dist/core/constraints/git-evidence.d.ts.map +1 -0
  179. package/dist/core/constraints/git-evidence.js +88 -0
  180. package/dist/core/constraints/git-evidence.js.map +1 -0
  181. package/dist/core/constraints/injection-drift.d.ts +3 -20
  182. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  183. package/dist/core/constraints/injection-drift.js +10 -57
  184. package/dist/core/constraints/injection-drift.js.map +1 -1
  185. package/dist/core/constraints/injection-writer.d.ts +113 -0
  186. package/dist/core/constraints/injection-writer.d.ts.map +1 -0
  187. package/dist/core/constraints/injection-writer.js +228 -0
  188. package/dist/core/constraints/injection-writer.js.map +1 -0
  189. package/dist/core/constraints/usage-report.d.ts +22 -1
  190. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  191. package/dist/core/constraints/usage-report.js +27 -20
  192. package/dist/core/constraints/usage-report.js.map +1 -1
  193. package/dist/core/index.d.ts +2 -2
  194. package/dist/core/index.d.ts.map +1 -1
  195. package/dist/core/index.js +4 -2
  196. package/dist/core/index.js.map +1 -1
  197. package/dist/core/project-config-loader.d.ts +32 -1
  198. package/dist/core/project-config-loader.d.ts.map +1 -1
  199. package/dist/core/project-config-loader.js +39 -9
  200. package/dist/core/project-config-loader.js.map +1 -1
  201. package/dist/core/validators/passes-gate.d.ts +15 -64
  202. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  203. package/dist/core/validators/passes-gate.js +84 -222
  204. package/dist/core/validators/passes-gate.js.map +1 -1
  205. package/dist/core/validators/test-output.d.ts +58 -0
  206. package/dist/core/validators/test-output.d.ts.map +1 -0
  207. package/dist/core/validators/test-output.js +109 -0
  208. package/dist/core/validators/test-output.js.map +1 -0
  209. package/dist/failure/recorder.d.ts +6 -0
  210. package/dist/failure/recorder.d.ts.map +1 -1
  211. package/dist/failure/recorder.js +12 -8
  212. package/dist/failure/recorder.js.map +1 -1
  213. package/dist/gates/acceptance.d.ts +1 -48
  214. package/dist/gates/acceptance.d.ts.map +1 -1
  215. package/dist/gates/acceptance.js +6 -38
  216. package/dist/gates/acceptance.js.map +1 -1
  217. package/dist/gates/checker-gate.d.ts +2 -1
  218. package/dist/gates/checker-gate.d.ts.map +1 -1
  219. package/dist/gates/checker-gate.js +11 -13
  220. package/dist/gates/checker-gate.js.map +1 -1
  221. package/dist/gates/command.d.ts.map +1 -1
  222. package/dist/gates/command.js +10 -22
  223. package/dist/gates/command.js.map +1 -1
  224. package/dist/gates/contract.d.ts.map +1 -1
  225. package/dist/gates/contract.js +22 -61
  226. package/dist/gates/contract.js.map +1 -1
  227. package/dist/gates/performance.d.ts.map +1 -1
  228. package/dist/gates/performance.js +13 -31
  229. package/dist/gates/performance.js.map +1 -1
  230. package/dist/gates/review.d.ts.map +1 -1
  231. package/dist/gates/review.js +21 -53
  232. package/dist/gates/review.js.map +1 -1
  233. package/dist/gates/security.d.ts.map +1 -1
  234. package/dist/gates/security.js +14 -32
  235. package/dist/gates/security.js.map +1 -1
  236. package/dist/gates/types.d.ts +28 -5
  237. package/dist/gates/types.d.ts.map +1 -1
  238. package/dist/gates/types.js +38 -1
  239. package/dist/gates/types.js.map +1 -1
  240. package/dist/hooks/bootstrap.d.ts +2 -0
  241. package/dist/hooks/bootstrap.d.ts.map +1 -1
  242. package/dist/hooks/bootstrap.js +5 -2
  243. package/dist/hooks/bootstrap.js.map +1 -1
  244. package/dist/index.d.ts +1 -2
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js.map +1 -1
  247. package/dist/knowledge/audit.d.ts +7 -2
  248. package/dist/knowledge/audit.d.ts.map +1 -1
  249. package/dist/knowledge/audit.js +59 -79
  250. package/dist/knowledge/audit.js.map +1 -1
  251. package/dist/knowledge/flywheel-metrics.d.ts +39 -0
  252. package/dist/knowledge/flywheel-metrics.d.ts.map +1 -0
  253. package/dist/knowledge/flywheel-metrics.js +47 -0
  254. package/dist/knowledge/flywheel-metrics.js.map +1 -0
  255. package/dist/knowledge/import.d.ts.map +1 -1
  256. package/dist/knowledge/import.js +4 -14
  257. package/dist/knowledge/import.js.map +1 -1
  258. package/dist/knowledge/index-generator.d.ts.map +1 -1
  259. package/dist/knowledge/index-generator.js +17 -14
  260. package/dist/knowledge/index-generator.js.map +1 -1
  261. package/dist/knowledge/ingest.d.ts.map +1 -1
  262. package/dist/knowledge/ingest.js +9 -4
  263. package/dist/knowledge/ingest.js.map +1 -1
  264. package/dist/knowledge/migration.d.ts.map +1 -1
  265. package/dist/knowledge/migration.js +11 -8
  266. package/dist/knowledge/migration.js.map +1 -1
  267. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  268. package/dist/knowledge/reference-tracker.js +12 -9
  269. package/dist/knowledge/reference-tracker.js.map +1 -1
  270. package/dist/knowledge/store.d.ts +15 -0
  271. package/dist/knowledge/store.d.ts.map +1 -1
  272. package/dist/knowledge/store.js +59 -10
  273. package/dist/knowledge/store.js.map +1 -1
  274. package/dist/monitoring/context-tracker.d.ts.map +1 -1
  275. package/dist/monitoring/context-tracker.js +11 -20
  276. package/dist/monitoring/context-tracker.js.map +1 -1
  277. package/dist/monitoring/index.d.ts +1 -1
  278. package/dist/monitoring/index.d.ts.map +1 -1
  279. package/dist/monitoring/index.js +1 -2
  280. package/dist/monitoring/index.js.map +1 -1
  281. package/dist/monitoring/trace-analyzer.d.ts +15 -0
  282. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  283. package/dist/monitoring/trace-analyzer.js +19 -2
  284. package/dist/monitoring/trace-analyzer.js.map +1 -1
  285. package/dist/monitoring/traces.d.ts +21 -2
  286. package/dist/monitoring/traces.d.ts.map +1 -1
  287. package/dist/monitoring/traces.js +38 -29
  288. package/dist/monitoring/traces.js.map +1 -1
  289. package/dist/release/index.d.ts +2 -2
  290. package/dist/release/index.d.ts.map +1 -1
  291. package/dist/release/index.js +1 -5
  292. package/dist/release/index.js.map +1 -1
  293. package/dist/sdd/index-generator.js +10 -11
  294. package/dist/sdd/index-generator.js.map +1 -1
  295. package/dist/test-setup/project-fixture.d.ts +48 -0
  296. package/dist/test-setup/project-fixture.d.ts.map +1 -0
  297. package/dist/test-setup/project-fixture.js +106 -0
  298. package/dist/test-setup/project-fixture.js.map +1 -0
  299. package/dist/types/constraint.d.ts +8 -18
  300. package/dist/types/constraint.d.ts.map +1 -1
  301. package/dist/types/constraint.js +6 -1
  302. package/dist/types/constraint.js.map +1 -1
  303. package/dist/types/passes-gate.d.ts +10 -25
  304. package/dist/types/passes-gate.d.ts.map +1 -1
  305. package/dist/types/project-config.d.ts +1 -1
  306. package/dist/types/session.d.ts +6 -5
  307. package/dist/types/session.d.ts.map +1 -1
  308. package/dist/types/trace.d.ts +9 -0
  309. package/dist/types/trace.d.ts.map +1 -1
  310. package/dist/utils/exec.d.ts +10 -0
  311. package/dist/utils/exec.d.ts.map +1 -1
  312. package/dist/utils/exec.js +12 -0
  313. package/dist/utils/exec.js.map +1 -1
  314. package/dist/utils/file-walk.d.ts +9 -1
  315. package/dist/utils/file-walk.d.ts.map +1 -1
  316. package/dist/utils/file-walk.js +10 -1
  317. package/dist/utils/file-walk.js.map +1 -1
  318. package/dist/utils/frontmatter.d.ts +73 -0
  319. package/dist/utils/frontmatter.d.ts.map +1 -0
  320. package/dist/utils/frontmatter.js +141 -0
  321. package/dist/utils/frontmatter.js.map +1 -0
  322. package/dist/utils/jsonl.d.ts +92 -0
  323. package/dist/utils/jsonl.d.ts.map +1 -0
  324. package/dist/utils/jsonl.js +166 -0
  325. package/dist/utils/jsonl.js.map +1 -0
  326. package/dist/utils/package-version.d.ts +16 -0
  327. package/dist/utils/package-version.d.ts.map +1 -0
  328. package/dist/utils/package-version.js +63 -0
  329. package/dist/utils/package-version.js.map +1 -0
  330. package/package.json +1 -1
  331. package/src/CONTEXT.md +8 -2
  332. package/src/__tests__/acceptance-gate.test.ts +3 -1
  333. package/src/__tests__/checker-extra.test.ts +7 -1218
  334. package/src/__tests__/context-files-resolution.test.ts +116 -0
  335. package/src/__tests__/effective-constraints.test.ts +10 -13
  336. package/src/__tests__/exec.test.ts +35 -1
  337. package/src/__tests__/layering.test.ts +152 -0
  338. package/src/__tests__/passes-gate-extra.test.ts +0 -145
  339. package/src/__tests__/passes-gate.test.ts +130 -101
  340. package/src/__tests__/skip-semantics.test.ts +5 -9
  341. package/src/cli/__tests__/command-contract.test.ts +170 -0
  342. package/src/cli/command-contract.ts +108 -0
  343. package/src/cli/commands/CONTEXT.md +23 -8
  344. package/src/cli/commands/__tests__/acceptance.test.ts +40 -33
  345. package/src/cli/commands/__tests__/analyze-sessions.test.ts +35 -29
  346. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +107 -0
  347. package/src/cli/commands/__tests__/check-drift.test.ts +27 -34
  348. package/src/cli/commands/__tests__/check-skip-output.test.ts +21 -24
  349. package/src/cli/commands/__tests__/check.test.ts +448 -378
  350. package/src/cli/commands/__tests__/command.test.ts +85 -70
  351. package/src/cli/commands/__tests__/constraints-report.test.ts +100 -71
  352. package/src/cli/commands/__tests__/constraints-retire.test.ts +152 -95
  353. package/src/cli/commands/__tests__/contract.test.ts +27 -34
  354. package/src/cli/commands/__tests__/failure.test.ts +31 -34
  355. package/src/cli/commands/__tests__/init-injection.test.ts +58 -28
  356. package/src/cli/commands/__tests__/init.test.ts +77 -48
  357. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +108 -0
  358. package/src/cli/commands/__tests__/knowledge.test.ts +69 -43
  359. package/src/cli/commands/__tests__/passes-gate.test.ts +43 -45
  360. package/src/cli/commands/__tests__/performance.test.ts +22 -23
  361. package/src/cli/commands/__tests__/posteval-plan.test.ts +39 -38
  362. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +157 -0
  363. package/src/cli/commands/__tests__/project-path-convention.test.ts +231 -0
  364. package/src/cli/commands/__tests__/registry.test.ts +75 -35
  365. package/src/cli/commands/__tests__/release.test.ts +209 -80
  366. package/src/cli/commands/__tests__/report-extra.test.ts +16 -16
  367. package/src/cli/commands/__tests__/report.test.ts +25 -25
  368. package/src/cli/commands/__tests__/review.test.ts +33 -31
  369. package/src/cli/commands/__tests__/security.test.ts +23 -27
  370. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +20 -20
  371. package/src/cli/commands/__tests__/spec.test.ts +41 -29
  372. package/src/cli/commands/__tests__/status-extra.test.ts +18 -18
  373. package/src/cli/commands/__tests__/status-skipped-lines.test.ts +72 -0
  374. package/src/cli/commands/__tests__/status.test.ts +34 -34
  375. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +77 -78
  376. package/src/cli/commands/__tests__/sync-docs.test.ts +72 -74
  377. package/src/cli/commands/__tests__/update-user-model.test.ts +97 -34
  378. package/src/cli/commands/__tests__/validate.test.ts +27 -29
  379. package/src/cli/commands/acceptance.ts +35 -24
  380. package/src/cli/commands/analyze-sessions.ts +25 -24
  381. package/src/cli/commands/check.ts +127 -66
  382. package/src/cli/commands/command.ts +30 -17
  383. package/src/cli/commands/constraints-report.ts +37 -34
  384. package/src/cli/commands/constraints-retire.ts +58 -55
  385. package/src/cli/commands/constraints.ts +10 -27
  386. package/src/cli/commands/contract.ts +51 -42
  387. package/src/cli/commands/definitions.ts +34 -227
  388. package/src/cli/commands/failure.ts +33 -26
  389. package/src/cli/commands/init.ts +166 -220
  390. package/src/cli/commands/knowledge.ts +192 -289
  391. package/src/cli/commands/passes-gate.ts +58 -68
  392. package/src/cli/commands/performance.ts +23 -18
  393. package/src/cli/commands/posteval-plan.ts +29 -15
  394. package/src/cli/commands/release.ts +70 -59
  395. package/src/cli/commands/report.ts +9 -5
  396. package/src/cli/commands/review.ts +39 -29
  397. package/src/cli/commands/sdd.ts +7 -5
  398. package/src/cli/commands/security.ts +35 -26
  399. package/src/cli/commands/spec-baseline-check.ts +16 -11
  400. package/src/cli/commands/spec.ts +66 -53
  401. package/src/cli/commands/status.ts +59 -53
  402. package/src/cli/commands/sync-docs/agents-syncer.ts +5 -2
  403. package/src/cli/commands/sync-docs/context-syncer.ts +3 -2
  404. package/src/cli/commands/sync-docs/index.ts +73 -100
  405. package/src/cli/commands/sync-docs/project-reader.ts +17 -18
  406. package/src/cli/commands/update-user-model.ts +64 -61
  407. package/src/cli/commands/validate.ts +31 -25
  408. package/src/cli/session-mining/__tests__/transcript.test.ts +118 -0
  409. package/src/cli/session-mining/transcript.ts +26 -8
  410. package/src/context/session-manager.ts +19 -33
  411. package/src/core/CONTEXT.md +10 -6
  412. package/src/core/constraints/__tests__/capabilities-reconcile.test.ts +200 -0
  413. package/src/core/constraints/__tests__/capability-counts.test.ts +94 -0
  414. package/src/core/constraints/__tests__/git-evidence.test.ts +208 -0
  415. package/src/core/constraints/__tests__/injection-drift.test.ts +3 -10
  416. package/src/core/constraints/__tests__/injection-writer.test.ts +231 -0
  417. package/src/core/constraints/__tests__/trace-injection.test.ts +71 -0
  418. package/src/core/constraints/__tests__/usage-report.test.ts +112 -0
  419. package/src/core/constraints/agent-prompt-renderer.ts +2 -6
  420. package/src/core/constraints/capabilities-parser.ts +53 -35
  421. package/src/core/constraints/capabilities-reconcile.ts +166 -0
  422. package/src/core/constraints/checker.ts +82 -133
  423. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +273 -0
  424. package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +176 -0
  425. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +77 -0
  426. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +126 -0
  427. package/src/{__tests__ → core/constraints/checkers/__tests__}/governance-presence.test.ts +83 -9
  428. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +50 -0
  429. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +76 -0
  430. package/src/{__tests__ → core/constraints/checkers/__tests__}/no-hardcoded-credentials.test.ts +4 -3
  431. package/src/core/constraints/checkers/__tests__/no-test-simplification.test.ts +58 -0
  432. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +153 -0
  433. package/src/core/constraints/checkers/capability-sync.ts +75 -96
  434. package/src/core/constraints/checkers/context-doc-sync.ts +15 -25
  435. package/src/core/constraints/checkers/docs-freshness.ts +54 -70
  436. package/src/core/constraints/checkers/governance-presence.ts +7 -25
  437. package/src/core/constraints/checkers/index.ts +9 -2
  438. package/src/core/constraints/checkers/no-test-simplification.ts +4 -3
  439. package/src/core/constraints/checkers/types.ts +60 -4
  440. package/src/core/constraints/context-builder.ts +39 -91
  441. package/src/core/constraints/doc-freshness/runner.ts +2 -1
  442. package/src/core/constraints/git-evidence.ts +120 -0
  443. package/src/core/constraints/injection-drift.ts +13 -60
  444. package/src/core/constraints/injection-writer.ts +212 -0
  445. package/src/core/constraints/usage-report.ts +36 -17
  446. package/src/core/index.ts +4 -2
  447. package/src/core/project-config-loader.ts +49 -9
  448. package/src/core/validators/__tests__/passes-gate.test.ts +2 -56
  449. package/src/core/validators/__tests__/test-output.test.ts +372 -0
  450. package/src/core/validators/passes-gate.ts +86 -248
  451. package/src/core/validators/test-output.ts +128 -0
  452. package/src/failure/__tests__/recorder.test.ts +168 -0
  453. package/src/failure/recorder.ts +12 -10
  454. package/src/gates/CONTEXT.md +7 -4
  455. package/src/gates/__tests__/acceptance-types-single-source.test.ts +68 -0
  456. package/src/gates/__tests__/acceptance.test.ts +4 -86
  457. package/src/gates/__tests__/checker-gate.test.ts +24 -0
  458. package/src/gates/__tests__/effective-gates.test.ts +3 -8
  459. package/src/gates/__tests__/test-verdict-consistency.test.ts +140 -0
  460. package/src/gates/acceptance.ts +19 -84
  461. package/src/gates/checker-gate.ts +12 -15
  462. package/src/gates/command.ts +10 -16
  463. package/src/gates/contract.ts +22 -61
  464. package/src/gates/performance.ts +10 -22
  465. package/src/gates/review.ts +21 -41
  466. package/src/gates/security.ts +10 -22
  467. package/src/gates/types.ts +70 -5
  468. package/src/hooks/CONTEXT.md +1 -1
  469. package/src/hooks/bootstrap.ts +5 -2
  470. package/src/index.ts +0 -4
  471. package/src/knowledge/CONTEXT.md +8 -3
  472. package/src/knowledge/__tests__/audit-labels.test.ts +44 -0
  473. package/src/knowledge/__tests__/flywheel-metrics.test.ts +135 -0
  474. package/src/knowledge/__tests__/import.test.ts +44 -0
  475. package/src/knowledge/__tests__/index-generator.test.ts +53 -0
  476. package/src/knowledge/__tests__/ingest.test.ts +85 -0
  477. package/src/knowledge/__tests__/migration.test.ts +47 -0
  478. package/src/knowledge/__tests__/reference-tracker.test.ts +16 -0
  479. package/src/knowledge/__tests__/store.test.ts +194 -0
  480. package/src/knowledge/audit.ts +67 -71
  481. package/src/knowledge/flywheel-metrics.ts +71 -0
  482. package/src/knowledge/import.ts +4 -18
  483. package/src/knowledge/index-generator.ts +19 -14
  484. package/src/knowledge/ingest.ts +9 -4
  485. package/src/knowledge/migration.ts +11 -8
  486. package/src/knowledge/reference-tracker.ts +12 -8
  487. package/src/knowledge/store.ts +64 -11
  488. package/src/monitoring/CONTEXT.md +8 -3
  489. package/src/monitoring/__tests__/skipped-lines-report.test.ts +178 -0
  490. package/src/monitoring/__tests__/traces.test.ts +57 -0
  491. package/src/monitoring/context-tracker.ts +11 -23
  492. package/src/monitoring/index.ts +1 -1
  493. package/src/monitoring/trace-analyzer.ts +21 -2
  494. package/src/monitoring/traces.ts +39 -36
  495. package/src/release/CONTEXT.md +1 -0
  496. package/src/release/__tests__/barrel.test.ts +60 -0
  497. package/src/release/index.ts +2 -13
  498. package/src/sdd/__tests__/index-generator.test.ts +71 -0
  499. package/src/sdd/index-generator.ts +9 -9
  500. package/src/test-setup/__tests__/project-fixture.test.ts +178 -0
  501. package/src/test-setup/project-fixture.ts +87 -0
  502. package/src/tools/CONTEXT.md +6 -0
  503. package/src/tools/__tests__/registry.test.ts +221 -0
  504. package/src/types/__tests__/types.test.ts +4 -5
  505. package/src/types/constraint.ts +16 -27
  506. package/src/types/passes-gate.ts +10 -30
  507. package/src/types/project-config.ts +1 -1
  508. package/src/types/session.ts +5 -5
  509. package/src/types/trace.ts +10 -0
  510. package/src/utils/__tests__/frontmatter.test.ts +206 -0
  511. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +175 -0
  512. package/src/utils/__tests__/jsonl.test.ts +187 -0
  513. package/src/utils/__tests__/package-version.test.ts +97 -0
  514. package/src/utils/exec.ts +15 -0
  515. package/src/utils/file-walk.ts +11 -2
  516. package/src/utils/frontmatter.ts +121 -0
  517. package/src/utils/jsonl.ts +167 -0
  518. package/src/utils/package-version.ts +27 -0
  519. package/dist/cli/commands/doc-freshness-check.d.ts +0 -53
  520. package/dist/cli/commands/doc-freshness-check.d.ts.map +0 -1
  521. package/dist/cli/commands/doc-freshness-check.js +0 -352
  522. package/dist/cli/commands/doc-freshness-check.js.map +0 -1
  523. package/dist/types/index.d.ts +0 -14
  524. package/dist/types/index.d.ts.map +0 -1
  525. package/dist/types/index.js +0 -39
  526. package/dist/types/index.js.map +0 -1
  527. package/src/__tests__/long-running.test.ts +0 -17
  528. package/src/__tests__/passes-gate-extension.test.ts +0 -151
  529. package/src/cli/commands/__tests__/doc-freshness-check.test.ts +0 -168
  530. package/src/cli/commands/doc-freshness-check.ts +0 -386
  531. package/src/spec/CONTEXT.md +0 -11
  532. package/src/types/index.ts +0 -47
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { 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,107 @@
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
+ // CI 上 dist 缺失时显式失败而非静默 skip(harness#99):#86 退出语义的
71
+ // 唯一端到端证据在下面的 smoke 块,CI 必须先 build 再 test,否则主证据隐形。
72
+ if (!hasDist && process.env.CI) {
73
+ throw new Error('dist 未构建:bin 端到端 smoke 不得静默 skip —— CI 必须先 npm run build 再 npm test(harness#99)');
74
+ }
75
+
76
+ const smoke = hasDist ? describe : describe.skip;
77
+
78
+ smoke('端到端:真实命令经 bin 映射后的对外退出码', () => {
79
+ const run = (argv: string[]) => spawnSync(process.execPath, [distBin, ...argv], {
80
+ cwd: repoRoot,
81
+ encoding: 'utf-8',
82
+ timeout: 60000,
83
+ });
84
+
85
+ it('放行命令 → 0(返回 ok,进程自然退出)', () => {
86
+ const r = run(['command', 'ls']);
87
+ expect(r.status).toBe(0);
88
+ });
89
+
90
+ it('门禁否决 → 1(返回 fail,bin 映射)', () => {
91
+ const r = run(['command', 'rm -rf /']);
92
+ expect(r.status).toBe(1);
93
+ expect(r.stdout).toContain('✗');
94
+ });
95
+
96
+ it('用法错误 → 1(返回 usage-error)', () => {
97
+ const r = run(['kb', 's']);
98
+ expect(r.status).toBe(1);
99
+ expect(r.stderr).toContain('请提供搜索关键词');
100
+ });
101
+
102
+ it('bin 自身子命令路由错误 → 1', () => {
103
+ const r = run(['knowledge', 'bogus']);
104
+ expect(r.status).toBe(1);
105
+ expect(r.stderr).toContain('未知子命令: bogus');
106
+ });
107
+ });
@@ -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
  });
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { check } from '../check';
8
- import { constraintChecker } from '../../../core/constraints/checker';
8
+ import { captureIO, type CapturingIO } from '../../command-contract';
9
9
 
10
10
  // Mock fs
11
11
  jest.mock('fs', () => ({
@@ -26,12 +26,16 @@ jest.mock('child_process', () => ({
26
26
  execSync: jest.fn(() => Buffer.from('')),
27
27
  }));
28
28
 
29
- // Mock constraintChecker
29
+ // Mock checker:命令 per-run 构造 checker(harness#88),构造替身即控 checkConstraints 返回值
30
+ const mockChecker = { checkConstraints: jest.fn() };
30
31
  jest.mock('../../../core/constraints/checker', () => ({
31
- constraintChecker: {
32
- setTraceRecorder: jest.fn(),
33
- checkConstraints: jest.fn(),
34
- },
32
+ ConstraintChecker: jest.fn(function () {
33
+ return mockChecker;
34
+ }),
35
+ }));
36
+
37
+ jest.mock('../../../monitoring/traces', () => ({
38
+ getTraceCollector: jest.fn(() => ({ record: jest.fn() })),
35
39
  }));
36
40
 
37
41
  // Mock effective-constraints(ADR-0001:check 经 getMergedConstraintsConfig 走生效集链路)
@@ -55,8 +59,6 @@ jest.mock('chalk', () => ({
55
59
  red: jest.fn((str: string) => str),
56
60
  }));
57
61
 
58
- const mockChecker = constraintChecker as jest.Mocked<typeof constraintChecker>;
59
-
60
62
  function fakeConstraint(id: string, level: 'iron_law' | 'guideline') {
61
63
  return {
62
64
  kind: 'check' as const,
@@ -70,17 +72,12 @@ function fakeConstraint(id: string, level: 'iron_law' | 'guideline') {
70
72
  }
71
73
 
72
74
  describe('check 命令 skip 输出', () => {
73
- let consoleSpy: jest.SpyInstance;
75
+ let io: CapturingIO;
74
76
 
75
77
  beforeEach(() => {
76
- jest.clearAllMocks();
77
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
78
- process.exitCode = 0;
79
- });
80
78
 
81
- afterEach(() => {
82
- consoleSpy.mockRestore();
83
- process.exitCode = 0;
79
+ io = captureIO();
80
+ jest.clearAllMocks();
84
81
  });
85
82
 
86
83
  it('skipped 单独列示,不计入铁律通过条数', async () => {
@@ -94,15 +91,15 @@ describe('check 命令 skip 输出', () => {
94
91
  warningCount: 0,
95
92
  });
96
93
 
97
- await check({ preset: 'default', staged: false });
94
+ await check({ preset: 'default', staged: false }, io);
98
95
 
99
96
  // 通过条数只计实际评估的 1 条
100
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('铁律: 全部通过 (1 条)'));
97
+ expect(io.outText()).toContain('铁律: 全部通过 (1 条)');
101
98
  // skipped 单独列示
102
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('跳过评估: 1 条'));
103
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('- law_skip'));
99
+ expect(io.outText()).toContain('跳过评估: 1 条');
100
+ expect(io.outText()).toContain('- law_skip');
104
101
  // skip 不影响整体通过
105
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('约束检查通过'));
102
+ expect(io.outText()).toContain('约束检查通过');
106
103
  });
107
104
 
108
105
  it('指导原则通过计数排除 skipped', async () => {
@@ -116,9 +113,9 @@ describe('check 命令 skip 输出', () => {
116
113
  warningCount: 0,
117
114
  });
118
115
 
119
- await check({ preset: 'default', staged: false });
116
+ await check({ preset: 'default', staged: false }, io);
120
117
 
121
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('指导原则: 1/1 通过'));
122
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('跳过评估: 1 条'));
118
+ expect(io.outText()).toContain('指导原则: 1/1 通过');
119
+ expect(io.outText()).toContain('跳过评估: 1 条');
123
120
  });
124
121
  });