@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
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * command 命令测试
3
+ *
4
+ * 判定经返回值断言(架构评审候选7):退出码不再靠 spyOn(process,'exit'),
5
+ * 输出文本走注入 captureIO。
3
6
  */
4
7
 
5
8
  import { executeCommand } from '../command';
6
9
  import { createCommandGate, getCommandRiskLevel } from '../../../gates';
10
+ import { captureIO, type CapturingIO } from '../../command-contract';
7
11
 
8
12
  jest.mock('../../../gates', () => ({
9
13
  createCommandGate: jest.fn(),
@@ -25,125 +29,136 @@ jest.mock('chalk', () => ({
25
29
  const mockCreateGate = createCommandGate as jest.MockedFunction<typeof createCommandGate>;
26
30
  const mockGetRisk = getCommandRiskLevel as jest.MockedFunction<typeof getCommandRiskLevel>;
27
31
 
32
+ function mockGate(check: jest.Mock): void {
33
+ mockCreateGate.mockReturnValue({ check } as any);
34
+ }
35
+
36
+ /** 从捕获输出里取第一条可解析为该形状的 JSON 行 */
37
+ function parseJsonLine(lines: string[], predicate: (parsed: any) => boolean): any {
38
+ for (const line of lines.join('\n').split('\n\n')) {
39
+ try {
40
+ const parsed = JSON.parse(line);
41
+ if (predicate(parsed)) return parsed;
42
+ } catch { /* 非 JSON 行 */ }
43
+ }
44
+ return undefined;
45
+ }
46
+
28
47
  describe('command command', () => {
29
- let consoleSpy: jest.SpyInstance;
30
- let errorSpy: jest.SpyInstance;
31
- let exitSpy: jest.SpyInstance;
48
+ let io: CapturingIO;
32
49
 
33
50
  beforeEach(() => {
34
51
  jest.clearAllMocks();
35
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
36
- errorSpy = jest.spyOn(console, 'error').mockImplementation();
37
- exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
38
- process.exitCode = 0;
39
- });
40
-
41
- afterEach(() => {
42
- consoleSpy.mockRestore();
43
- errorSpy.mockRestore();
44
- exitSpy.mockRestore();
45
- process.exitCode = 0;
52
+ io = captureIO();
46
53
  });
47
54
 
48
55
  describe('--list', () => {
49
- it('should list rules grouped by category', async () => {
50
- await executeCommand(undefined, { list: true });
51
-
52
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('命令黑名单规则'));
53
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('[destructive]'));
54
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('[database]'));
56
+ it('按类别分组列出规则并返回 ok', async () => {
57
+ const result = await executeCommand(undefined, { list: true }, io);
58
+
59
+ expect(result).toEqual({ kind: 'ok' });
60
+ expect(io.outText()).toContain('命令黑名单规则');
61
+ expect(io.outLines()).toEqual(
62
+ expect.arrayContaining([expect.stringContaining('[destructive]'), expect.stringContaining('[database]')]),
63
+ );
55
64
  });
56
65
 
57
- it('should list rules as JSON when --json', async () => {
58
- await executeCommand(undefined, { list: true, json: true });
66
+ it('--json 列出规则数组', async () => {
67
+ const result = await executeCommand(undefined, { list: true, json: true }, io);
59
68
 
60
- const jsonCall = consoleSpy.mock.calls.find((c: any[]) => {
61
- try { return Array.isArray(JSON.parse(c[0])); } catch { return false; }
62
- });
63
- expect(jsonCall).toBeTruthy();
64
- const parsed = JSON.parse(jsonCall[0]);
65
- expect(parsed).toHaveLength(2);
69
+ expect(result).toEqual({ kind: 'ok' });
70
+ expect(parseJsonLine(io.outLines(), Array.isArray)).toHaveLength(2);
66
71
  });
67
72
  });
68
73
 
69
74
  describe('no command', () => {
70
- it('should print error and exit 1 when no command provided', async () => {
71
- const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'OK' });
72
- mockCreateGate.mockReturnValue({ check: mockCheck } as any);
75
+ it('缺参数:usage-error + 原因,并提示用法', async () => {
76
+ const result = await executeCommand(undefined, {}, io);
73
77
 
74
- await executeCommand(undefined, {});
75
-
76
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('请提供要检查的命令'));
77
- expect(exitSpy).toHaveBeenCalledWith(1);
78
+ expect(result).toEqual({
79
+ kind: 'usage-error',
80
+ reason: expect.stringContaining('缺少要检查的命令参数'),
81
+ });
82
+ expect(io.errText()).toContain('请提供要检查的命令');
83
+ expect(io.outText()).toBe('');
78
84
  });
79
85
  });
80
86
 
81
87
  describe('default mode', () => {
82
- it('should print pass when result passes', async () => {
83
- const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'OK' });
84
- mockCreateGate.mockReturnValue({ check: mockCheck } as any);
88
+ it('放行:ok,打印 ', async () => {
89
+ mockGate(jest.fn().mockResolvedValue({ passed: true, message: 'OK' }));
85
90
 
86
- await executeCommand('safe-cmd', {});
91
+ const result = await executeCommand('safe-cmd', {}, io);
87
92
 
88
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining(''));
93
+ expect(result).toEqual({ kind: 'ok' });
94
+ expect(io.outText()).toContain('✓');
89
95
  });
90
96
 
91
- it('should print fail and exit 1 when result fails', async () => {
92
- const mockCheck = jest.fn().mockResolvedValue({ passed: false, message: 'Blocked' });
93
- mockCreateGate.mockReturnValue({ check: mockCheck } as any);
97
+ it('拒绝:fail + 裁决原因,打印 ', async () => {
98
+ mockGate(jest.fn().mockResolvedValue({ passed: false, message: 'Blocked' }));
94
99
 
95
- await executeCommand('rm -rf /', {});
100
+ const result = await executeCommand('rm -rf /', {}, io);
96
101
 
97
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining(''));
98
- expect(exitSpy).toHaveBeenCalledWith(1);
102
+ expect(result).toEqual({ kind: 'fail', reason: expect.stringContaining('Blocked') });
103
+ expect(io.outText()).toContain('✗');
99
104
  });
100
105
 
101
- it('should output JSON when --json', async () => {
102
- const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'OK', details: {} });
103
- mockCreateGate.mockReturnValue({ check: mockCheck } as any);
106
+ it('--json 输出裁决详情', async () => {
107
+ mockGate(jest.fn().mockResolvedValue({ passed: true, message: 'OK', details: {} }));
104
108
 
105
- await executeCommand('cmd', { json: true });
109
+ await executeCommand('cmd', { json: true }, io);
106
110
 
107
- const jsonCall = consoleSpy.mock.calls.find((c: any[]) => {
108
- try { const p = JSON.parse(c[0]); return p.command !== undefined; } catch { return false; }
111
+ expect(parseJsonLine(io.outLines(), (p: any) => p.command === 'cmd')).toEqual({
112
+ command: 'cmd',
113
+ passed: true,
114
+ message: 'OK',
115
+ details: {},
109
116
  });
110
- expect(jsonCall).toBeTruthy();
111
117
  });
112
118
  });
113
119
 
114
120
  describe('--level', () => {
115
- it('should print risk level', async () => {
121
+ it('low:ok,打印等级', async () => {
116
122
  mockGetRisk.mockReturnValue('low');
117
- const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'OK' });
118
- mockCreateGate.mockReturnValue({ check: mockCheck } as any);
123
+ mockGate(jest.fn().mockResolvedValue({ passed: true, message: 'OK' }));
119
124
 
120
- await executeCommand('safe-cmd', { level: true });
125
+ const result = await executeCommand('safe-cmd', { level: true }, io);
121
126
 
122
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('low'));
123
- expect(exitSpy).toHaveBeenCalledWith(0);
127
+ expect(result).toEqual({ kind: 'ok' });
128
+ expect(io.outText()).toContain('low');
124
129
  });
125
130
 
126
- it('should exit 1 for high risk', async () => {
131
+ it('high:fail + 等级原因(严重级条件式译成 kind)', async () => {
127
132
  mockGetRisk.mockReturnValue('high');
133
+ mockGate(jest.fn().mockResolvedValue({ passed: true, message: 'OK' }));
134
+
135
+ const result = await executeCommand('rm -rf /', { level: true }, io);
136
+
137
+ expect(result).toEqual({ kind: 'fail', reason: expect.stringContaining('risk level is high') });
138
+ });
139
+
140
+ it('high 等级不再执行黑名单裁决分支', async () => {
128
141
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'OK' });
129
- mockCreateGate.mockReturnValue({ check: mockCheck } as any);
142
+ mockGetRisk.mockReturnValue('high');
143
+ mockGate(mockCheck);
130
144
 
131
- await executeCommand('rm -rf /', { level: true });
145
+ const result = await executeCommand('rm -rf /', { level: true }, io);
132
146
 
133
- expect(exitSpy).toHaveBeenCalledWith(1);
147
+ // 历史行为:--level 命中即退出,不再打印 pass/fail 行
148
+ expect(result.kind).toBe('fail');
149
+ expect(io.outText()).not.toContain('✓');
134
150
  });
135
151
 
136
- it('should output JSON when --json', async () => {
152
+ it('--json 输出等级', async () => {
137
153
  mockGetRisk.mockReturnValue('medium');
138
- const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'OK' });
139
- mockCreateGate.mockReturnValue({ check: mockCheck } as any);
154
+ mockGate(jest.fn().mockResolvedValue({ passed: true, message: 'OK' }));
140
155
 
141
- await executeCommand('cmd', { level: true, json: true });
156
+ await executeCommand('cmd', { level: true, json: true }, io);
142
157
 
143
- const jsonCall = consoleSpy.mock.calls.find((c: any[]) => {
144
- try { const p = JSON.parse(c[0]); return p.level !== undefined; } catch { return false; }
158
+ expect(parseJsonLine(io.outLines(), (p: any) => p.level !== undefined)).toEqual({
159
+ level: 'medium',
160
+ command: 'cmd',
145
161
  });
146
- expect(jsonCall).toBeTruthy();
147
162
  });
148
163
  });
149
164
  });
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import * as fs from 'fs';
11
- import * as os from 'os';
11
+ import { captureIO, type CapturingIO } from '../../command-contract';
12
12
  import * as path from 'path';
13
13
  import type { ExecutionTrace } from '../../../types/trace';
14
14
  import {
@@ -17,43 +17,24 @@ import {
17
17
  readProjectTraces,
18
18
  } from '../../../core/constraints/usage-report';
19
19
  import { constraintsReport, renderExportMarkdown } from '../constraints-report';
20
-
21
- function makeTmpProject(): string {
22
- return fs.mkdtempSync(path.join(os.tmpdir(), 'harness-report-test-'));
23
- }
24
-
25
- function writeTraces(root: string, traces: Partial<ExecutionTrace>[]): void {
26
- const dir = path.join(root, '.harness', 'logs');
27
- fs.mkdirSync(dir, { recursive: true });
28
- const lines = traces.map(t =>
29
- JSON.stringify({
30
- level: 'guideline',
31
- timestamp: 1700000000000,
32
- result: 'pass',
33
- ...t,
34
- })
35
- );
36
- fs.writeFileSync(path.join(dir, 'traces.log'), lines.join('\n') + '\n', 'utf-8');
37
- }
20
+ import { createProjectFixture, writeProjectTraces } from '../../../test-setup/project-fixture';
38
21
 
39
22
  /** 生成 N 条同结果 trace */
40
23
  function tracesOf(id: string, result: ExecutionTrace['result'], n: number, startTs = 1700000000000): Partial<ExecutionTrace>[] {
41
24
  return Array.from({ length: n }, (_, i) => ({ constraintId: id, result, timestamp: startTs + i * 1000 }));
42
25
  }
43
26
 
44
- let logSpy: jest.SpyInstance;
45
-
46
27
  beforeEach(() => {
47
- logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
48
28
  });
49
29
 
50
- afterEach(() => {
51
- logSpy.mockRestore();
30
+ let io: CapturingIO;
31
+ beforeEach(() => {
32
+ io = captureIO();
52
33
  });
53
34
 
54
35
  describe('buildConstraintsUsageReport', () => {
55
36
  it('无 trace 文件:生效集 check 约束全部列出且 total=0,全部判为零触发候选', () => {
56
- const root = makeTmpProject();
37
+ const root = createProjectFixture({ name: 'harness-report-test' });
57
38
  const report = buildConstraintsUsageReport(root);
58
39
 
59
40
  expect(report.traceFileExists).toBe(false);
@@ -72,8 +53,7 @@ describe('buildConstraintsUsageReport', () => {
72
53
  });
73
54
 
74
55
  it('空 trace 文件:与无文件一致,但 traceFileExists=true', () => {
75
- const root = makeTmpProject();
76
- writeTraces(root, []);
56
+ const root = createProjectFixture({ name: 'harness-report-test', traces: [] });
77
57
  const report = buildConstraintsUsageReport(root);
78
58
 
79
59
  expect(report.traceFileExists).toBe(true);
@@ -82,8 +62,8 @@ describe('buildConstraintsUsageReport', () => {
82
62
  });
83
63
 
84
64
  it('含 skip 的统计:skip 不计入 fail 率分母,首次/最近时间正确', () => {
85
- const root = makeTmpProject();
86
- writeTraces(root, [
65
+ const root = createProjectFixture({ name: 'harness-report-test' });
66
+ writeProjectTraces(root, [
87
67
  ...tracesOf('no_hardcoded_credentials', 'pass', 3, 1700000000000),
88
68
  ...tracesOf('no_hardcoded_credentials', 'fail', 1, 1700000100000),
89
69
  ...tracesOf('no_hardcoded_credentials', 'skip', 2, 1700000200000),
@@ -104,7 +84,7 @@ describe('buildConstraintsUsageReport', () => {
104
84
  });
105
85
 
106
86
  it('坏行容错:单行 JSON 损坏不影响其他行统计', () => {
107
- const root = makeTmpProject();
87
+ const root = createProjectFixture({ name: 'harness-report-test' });
108
88
  const dir = path.join(root, '.harness', 'logs');
109
89
  fs.mkdirSync(dir, { recursive: true });
110
90
  fs.writeFileSync(
@@ -116,8 +96,8 @@ describe('buildConstraintsUsageReport', () => {
116
96
  });
117
97
 
118
98
  it('四类候选诊断:零触发/不可评估(flag)/不可评估(探测)/高噪/零拦截', () => {
119
- const root = makeTmpProject();
120
- writeTraces(root, [
99
+ const root = createProjectFixture({ name: 'harness-report-test' });
100
+ writeProjectTraces(root, [
121
101
  // 不可评估(flag 未接线):全部 skip
122
102
  ...tracesOf('no_completion_without_verification', 'skip', 3),
123
103
  // 不可评估(存在性探测未命中):全部 skip
@@ -156,8 +136,10 @@ describe('buildConstraintsUsageReport', () => {
156
136
  });
157
137
 
158
138
  it('阈值可配:放宽零拦截样本阈值后 10 次全 pass 也成为候选', () => {
159
- const root = makeTmpProject();
160
- writeTraces(root, tracesOf('no_hardcoded_credentials', 'pass', 10));
139
+ const root = createProjectFixture({
140
+ name: 'harness-report-test',
141
+ traces: tracesOf('no_hardcoded_credentials', 'pass', 10),
142
+ });
161
143
 
162
144
  const strict = buildConstraintsUsageReport(root);
163
145
  expect(strict.candidates.find(c => c.id === 'no_hardcoded_credentials')).toBeUndefined();
@@ -179,13 +161,10 @@ describe('buildConstraintsUsageReport', () => {
179
161
  });
180
162
 
181
163
  it('配置健康:config.yml 中未知 id 进入 unknownIds', () => {
182
- const root = makeTmpProject();
183
- fs.mkdirSync(path.join(root, '.harness'), { recursive: true });
184
- fs.writeFileSync(
185
- path.join(root, '.harness', 'config.yml'),
186
- 'constraints:\n ghost_constraint:\n enabled: false\n',
187
- 'utf-8'
188
- );
164
+ const root = createProjectFixture({
165
+ name: 'harness-report-test',
166
+ config: 'constraints:\n ghost_constraint:\n enabled: false\n',
167
+ });
189
168
  const report = buildConstraintsUsageReport(root);
190
169
  expect(report.lint.unknownIds).toContain('ghost_constraint');
191
170
  });
@@ -193,18 +172,15 @@ describe('buildConstraintsUsageReport', () => {
193
172
 
194
173
  describe('constraintsReport CLI', () => {
195
174
  it('console 输出包含统计表、候选、注入清单、unknownIds 提示', async () => {
196
- const root = makeTmpProject();
197
- fs.mkdirSync(path.join(root, '.harness'), { recursive: true });
198
- fs.writeFileSync(
199
- path.join(root, '.harness', 'config.yml'),
200
- 'constraints:\n ghost_constraint:\n enabled: false\n',
201
- 'utf-8'
202
- );
203
- writeTraces(root, tracesOf('no_hardcoded_credentials', 'pass', 3));
175
+ const root = createProjectFixture({
176
+ name: 'harness-report-test',
177
+ config: 'constraints:\n ghost_constraint:\n enabled: false\n',
178
+ traces: tracesOf('no_hardcoded_credentials', 'pass', 3),
179
+ });
204
180
 
205
- await constraintsReport({ projectPath: root });
181
+ await constraintsReport({ projectPath: root }, io);
206
182
 
207
- const output = logSpy.mock.calls.map(c => String(c[0])).join('\n');
183
+ const output = io.outText();
208
184
  expect(output).toContain('约束使用报告');
209
185
  expect(output).toContain('no_hardcoded_credentials');
210
186
  expect(output).toContain('total=3');
@@ -214,14 +190,14 @@ describe('constraintsReport CLI', () => {
214
190
  });
215
191
 
216
192
  it('--export 缺省路径:写入 .harness/reports/constraints-<YYYYMMDD>.md 且内容脱敏', async () => {
217
- const root = makeTmpProject();
218
- writeTraces(root, [
193
+ const root = createProjectFixture({ name: 'harness-report-test' });
194
+ writeProjectTraces(root, [
219
195
  ...tracesOf('no_bypass_checkpoint', 'fail', 20),
220
196
  // projectPath 字段进 trace,但不得进 export
221
197
  { constraintId: 'no_bypass_checkpoint', result: 'fail', projectPath: root, timestamp: 1700001000000 },
222
198
  ]);
223
199
 
224
- await constraintsReport({ projectPath: root, export: true });
200
+ await constraintsReport({ projectPath: root, export: true }, io);
225
201
 
226
202
  const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
227
203
  const exportPath = path.join(root, '.harness', 'reports', `constraints-${date}.md`);
@@ -237,19 +213,19 @@ describe('constraintsReport CLI', () => {
237
213
  expect(content).not.toContain('projectPath');
238
214
 
239
215
  // 打印了导出路径
240
- const output = logSpy.mock.calls.map(c => String(c[0])).join('\n');
216
+ const output = io.outText();
241
217
  expect(output).toContain(exportPath);
242
218
  });
243
219
 
244
220
  it('--export 指定文件:写到给定路径', async () => {
245
- const root = makeTmpProject();
246
- await constraintsReport({ projectPath: root, export: 'my-report.md' });
221
+ const root = createProjectFixture({ name: 'harness-report-test' });
222
+ await constraintsReport({ projectPath: root, export: 'my-report.md' }, io);
247
223
  expect(fs.existsSync(path.join(root, 'my-report.md'))).toBe(true);
248
224
  });
249
225
 
250
226
  it('renderExportMarkdown:无候选时显式标注', () => {
251
- const root = makeTmpProject();
252
- writeTraces(root, [
227
+ const root = createProjectFixture({ name: 'harness-report-test' });
228
+ writeProjectTraces(root, [
253
229
  // 所有 check 约束给少量健康数据(低于一切阈值)
254
230
  ...buildConstraintsUsageReport(root).stats.flatMap(s => tracesOf(s.id, 'pass', 5)),
255
231
  ]);
@@ -262,6 +238,59 @@ describe('constraintsReport CLI', () => {
262
238
  });
263
239
  });
264
240
 
241
+ describe('constraintsReport 坏行数透传(harness#100)', () => {
242
+ /** 直接落原始行(含坏行)——writeProjectTraces 只写合法记录,构造不出损坏 fixture */
243
+ function writeRawTraces(root: string, lines: string[]): void {
244
+ const dir = path.join(root, '.harness', 'logs');
245
+ fs.mkdirSync(dir, { recursive: true });
246
+ fs.writeFileSync(path.join(dir, 'traces.log'), lines.join('\n') + '\n', 'utf-8');
247
+ }
248
+
249
+ const healthyTrace = (id: string) =>
250
+ JSON.stringify({ constraintId: id, level: 'iron_law', timestamp: 1700000000000, result: 'pass' });
251
+ const BAD = '{"constraintId":"ghost","broken';
252
+
253
+ it('坏行 fixture:文本输出出现坏行数提示,--json 报告体带 skippedLines', async () => {
254
+ const root = createProjectFixture({ name: 'harness-report-test' });
255
+ writeRawTraces(root, [healthyTrace('no_hardcoded_credentials'), BAD, BAD]);
256
+
257
+ await constraintsReport({ projectPath: root }, io);
258
+ expect(io.outText()).toContain('2 行损坏');
259
+
260
+ const jsonIo = captureIO();
261
+ await constraintsReport({ projectPath: root, json: true }, jsonIo);
262
+ expect(JSON.parse(jsonIo.outText()).skippedLines).toBe(2);
263
+ });
264
+
265
+ it('traceFileExists=true 且无损坏:零噪声(无坏行提示,既有文案不变)', async () => {
266
+ const root = createProjectFixture({ name: 'harness-report-test' });
267
+ writeRawTraces(root, [healthyTrace('no_hardcoded_credentials')]);
268
+
269
+ await constraintsReport({ projectPath: root }, io);
270
+
271
+ const output = io.outText();
272
+ expect(output).toContain('约束使用报告');
273
+ expect(output).not.toContain('损坏');
274
+ expect(output).not.toContain('trace 文件不存在');
275
+ });
276
+
277
+ it('--export 摘要同样带坏行数(脱敏:只报条数,不报路径与坏行内容)', async () => {
278
+ const dirty = createProjectFixture({ name: 'harness-report-test' });
279
+ writeRawTraces(dirty, [healthyTrace('no_hardcoded_credentials'), BAD]);
280
+ await constraintsReport({ projectPath: dirty, export: 'report.md' }, captureIO());
281
+
282
+ const dirtyMd = fs.readFileSync(path.join(dirty, 'report.md'), 'utf-8');
283
+ expect(dirtyMd).toContain('1 行损坏');
284
+ expect(dirtyMd).not.toContain(BAD);
285
+ expect(dirtyMd).not.toContain(dirty);
286
+
287
+ const clean = createProjectFixture({ name: 'harness-report-test' });
288
+ writeRawTraces(clean, [healthyTrace('no_hardcoded_credentials')]);
289
+ await constraintsReport({ projectPath: clean, export: 'report.md' }, captureIO());
290
+ expect(fs.readFileSync(path.join(clean, 'report.md'), 'utf-8')).not.toContain('损坏');
291
+ });
292
+ });
293
+
265
294
  describe('constraintsReport 注入漂移小节(ADR-0001 决策 7)', () => {
266
295
  // eslint-disable-next-line @typescript-eslint/no-var-requires
267
296
  const realVersion = require('../../../../package.json').version as string;
@@ -274,7 +303,7 @@ describe('constraintsReport 注入漂移小节(ADR-0001 决策 7)', () => {
274
303
  };
275
304
 
276
305
  it('有漂移:输出条目级差异(缺失/多余)与修复指引', async () => {
277
- const root = makeTmpProject();
306
+ const root = createProjectFixture({ name: 'harness-report-test' });
278
307
  writeSyncedClaudeMd(root, '0.0.1-old');
279
308
  const claudeMdPath = path.join(root, 'CLAUDE.md');
280
309
  const content = fs.readFileSync(claudeMdPath, 'utf-8');
@@ -282,9 +311,9 @@ describe('constraintsReport 注入漂移小节(ADR-0001 决策 7)', () => {
282
311
  const editedLine = originalLine.replace(/: .+$/, ': 手工篡改');
283
312
  fs.writeFileSync(claudeMdPath, content.replace(originalLine, editedLine), 'utf-8');
284
313
 
285
- await constraintsReport({ projectPath: root });
314
+ await constraintsReport({ projectPath: root }, io);
286
315
 
287
- const output = logSpy.mock.calls.map(c => String(c[0])).join('\n');
316
+ const output = io.outText();
288
317
  expect(output).toContain('注入漂移');
289
318
  expect(output).toContain('版本漂移');
290
319
  expect(output).toContain('0.0.1-old');
@@ -294,34 +323,34 @@ describe('constraintsReport 注入漂移小节(ADR-0001 决策 7)', () => {
294
323
  });
295
324
 
296
325
  it('重复章节:输出重复章节提示', async () => {
297
- const root = makeTmpProject();
326
+ const root = createProjectFixture({ name: 'harness-report-test' });
298
327
  writeSyncedClaudeMd(root);
299
328
  fs.appendFileSync(path.join(root, 'CLAUDE.md'), '\n## Governance Rules\n\n旧版遗留\n', 'utf-8');
300
329
 
301
- await constraintsReport({ projectPath: root });
330
+ await constraintsReport({ projectPath: root }, io);
302
331
 
303
- const output = logSpy.mock.calls.map(c => String(c[0])).join('\n');
332
+ const output = io.outText();
304
333
  expect(output).toContain('重复章节');
305
334
  });
306
335
 
307
336
  it('无漂移:小节显示无漂移', async () => {
308
- const root = makeTmpProject();
337
+ const root = createProjectFixture({ name: 'harness-report-test' });
309
338
  writeSyncedClaudeMd(root);
310
339
 
311
- await constraintsReport({ projectPath: root });
340
+ await constraintsReport({ projectPath: root }, io);
312
341
 
313
- const output = logSpy.mock.calls.map(c => String(c[0])).join('\n');
342
+ const output = io.outText();
314
343
  expect(output).toContain('注入漂移');
315
344
  expect(output).toContain('无漂移');
316
345
  });
317
346
 
318
347
  it('未注入(无标记段):一句话提示,不算漂移', async () => {
319
- const root = makeTmpProject();
348
+ const root = createProjectFixture({ name: 'harness-report-test' });
320
349
  fs.writeFileSync(path.join(root, 'CLAUDE.md'), '# Test Project\n', 'utf-8');
321
350
 
322
- await constraintsReport({ projectPath: root });
351
+ await constraintsReport({ projectPath: root }, io);
323
352
 
324
- const output = logSpy.mock.calls.map(c => String(c[0])).join('\n');
353
+ const output = io.outText();
325
354
  expect(output).toContain('未注入');
326
355
  });
327
356
  });