@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
@@ -10,8 +10,8 @@
10
10
  * studio/.harness/knowledge/ (知识新鲜度)
11
11
  * ~/.claude/projects/-root-projects/memory/ (规则库)
12
12
  *
13
- * 模型状态: ~/.claude/user-model-state.json
14
- * 画像输出: ~/.claude/projects/-root-projects/memory/user_profile.md
13
+ * 模型状态: $HARNESS_UUM_STATE_FILE,默认 ~/.claude/user-model-state.json
14
+ * 画像输出: $HARNESS_UUM_PROFILE_FILE,默认 ~/.claude/projects/-root-projects/memory/user_profile.md
15
15
  *
16
16
  * 工单 19-C:transcript 解析/纠正模式/相似度收敛至 cli/session-mining/。
17
17
  */
@@ -22,10 +22,12 @@ import * as path from 'path';
22
22
  import * as os from 'os';
23
23
  import {
24
24
  readTranscriptSessions,
25
+ type TranscriptFilter,
25
26
  extractCorrectionMatches,
26
27
  cleanCorrectionConcept,
27
28
  jaccardChinese,
28
29
  } from '../session-mining';
30
+ import { log, processIO, type CommandIO, type CommandResult } from '../command-contract';
29
31
 
30
32
  export interface UpdateUserModelOptions {
31
33
  days?: number; // 只处理最近 N 天(自然日,含今天)的会话;缺省不过滤(向后兼容)
@@ -48,21 +50,36 @@ interface ModelState {
48
50
  evolutionLog: Array<{ date: string; change: string }>;
49
51
  }
50
52
 
51
- const STATE_FILE = path.join(os.homedir(), '.claude', 'user-model-state.json');
52
- const PROFILE_FILE = path.join(os.homedir(), '.claude', 'projects', '-root-projects', 'memory', 'user_profile.md');
53
+ // 路径集中解析(harness#116):默认值保持原行为,env 可覆盖(多工作区/测试隔离)。
54
+ // 运行时解析而非模块加载期常量,保证 env 设置后生效。
55
+ export interface UserModelPaths {
56
+ stateFile: string;
57
+ profileFile: string;
58
+ }
53
59
 
54
- export async function updateUserModel(options: UpdateUserModelOptions): Promise<void> {
60
+ export function resolveUserModelPaths(env: NodeJS.ProcessEnv = process.env): UserModelPaths {
61
+ const home = os.homedir();
62
+ return {
63
+ stateFile: env.HARNESS_UUM_STATE_FILE
64
+ || path.join(home, '.claude', 'user-model-state.json'),
65
+ profileFile: env.HARNESS_UUM_PROFILE_FILE
66
+ || path.join(home, '.claude', 'projects', '-root-projects', 'memory', 'user_profile.md'),
67
+ };
68
+ }
69
+
70
+ export async function updateUserModel(options: UpdateUserModelOptions, io: CommandIO = processIO): Promise<CommandResult> {
55
71
  const transcriptDir = process.env.CLAUDE_TRANSCRIPTS_DIR
56
72
  || path.join(os.homedir(), '.claude', 'projects', '-root--claude');
57
73
 
58
74
  // 1. Load previous state
59
- const state = loadState();
75
+ const paths = resolveUserModelPaths();
76
+ const state = loadState(paths);
60
77
 
61
78
  // 2. Scan new data
62
79
  const newSessions = findNewSessions(transcriptDir, state.sessionsProcessed, options.days);
63
80
  if (newSessions.length === 0) {
64
- console.log(chalk.gray('No new sessions to process'));
65
- return;
81
+ log(io, chalk.gray('No new sessions to process'));
82
+ return { kind: 'skip', reason: '没有新会话可处理' };
66
83
  }
67
84
 
68
85
  // 3. Extract signals from new data
@@ -88,26 +105,27 @@ export async function updateUserModel(options: UpdateUserModelOptions): Promise<
88
105
  state.sessionsProcessed = state.sessionsProcessed.slice(-200);
89
106
  }
90
107
 
91
- saveState(state);
92
- updateProfile(state);
108
+ saveState(state, paths);
109
+ updateProfile(state, paths);
93
110
  }
94
111
 
95
112
  // 6. Output
96
113
  if (options.json) {
97
- console.log(JSON.stringify({ newSessions: newSessions.length, changes }, null, 2));
98
- return;
114
+ log(io, JSON.stringify({ newSessions: newSessions.length, changes }, null, 2));
115
+ return { kind: 'ok' };
99
116
  }
100
117
 
101
- console.log(chalk.blue(`📊 Processed ${newSessions.length} new sessions\n`));
102
- printChanges(changes, signals);
118
+ log(io, chalk.blue(`📊 Processed ${newSessions.length} new sessions\n`));
119
+ printChanges(changes, signals, io);
120
+ return { kind: 'ok' };
103
121
  }
104
122
 
105
123
  // ── State I/O ──
106
124
 
107
- function loadState(): ModelState {
125
+ function loadState(paths: UserModelPaths): ModelState {
108
126
  try {
109
- if (fs.existsSync(STATE_FILE)) {
110
- return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
127
+ if (fs.existsSync(paths.stateFile)) {
128
+ return JSON.parse(fs.readFileSync(paths.stateFile, 'utf-8'));
111
129
  }
112
130
  } catch {}
113
131
  return {
@@ -120,11 +138,11 @@ function loadState(): ModelState {
120
138
  };
121
139
  }
122
140
 
123
- function saveState(state: ModelState): void {
141
+ function saveState(state: ModelState, paths: UserModelPaths): void {
124
142
  try {
125
- const dir = path.dirname(STATE_FILE);
143
+ const dir = path.dirname(paths.stateFile);
126
144
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
127
- fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf-8');
145
+ fs.writeFileSync(paths.stateFile, JSON.stringify(state, null, 2), 'utf-8');
128
146
  } catch {}
129
147
  }
130
148
 
@@ -139,17 +157,20 @@ interface SimpleSession {
139
157
  }
140
158
 
141
159
  function findNewSessions(dir: string, processed: string[], days?: number): SimpleSession[] {
142
- let sessions = readTranscriptSessions(dir)
143
- .filter(s => !processed.includes(s.id));
160
+ // 过滤下推 seam(harness#112):excludeIds 走文件名、since 走 stat,未命中不 parse
161
+ const filter: TranscriptFilter = { excludeIds: processed };
144
162
 
145
163
  // 「最近 N 天」:自然日窗口(含今天)。days<=0 视为不过滤,与缺省一致。
164
+ // date 是 mtime 的 UTC 日,date >= cutoff ⟺ mtimeMs >= cutoff 日 UTC 零点
146
165
  if (days !== undefined && days > 0) {
147
166
  const cutoff = new Date(Date.now() - (days - 1) * 86_400_000)
148
167
  .toISOString()
149
168
  .slice(0, 10);
150
- sessions = sessions.filter(s => s.date >= cutoff);
169
+ filter.since = Date.parse(cutoff);
151
170
  }
152
171
 
172
+ const sessions = readTranscriptSessions(dir, filter);
173
+
153
174
  sessions.sort((a, b) => a.date.localeCompare(b.date));
154
175
 
155
176
  return sessions.map(s => ({
@@ -329,6 +350,8 @@ function applySignals(state: ModelState, signals: SessionSignals[], mergedConcep
329
350
  const now = new Date().toISOString().slice(0, 10);
330
351
 
331
352
  // Update patterns from merged concept clusters
353
+ // occurrences 唯一来源:mergedConcepts 聚合值(含 correction phrase 的 ×3 加权,
354
+ // 见 buildMergedConcepts)。不再叠加 per-session 原始 count —— 那是 #113 裁决的双计 bug。
332
355
  for (const [concept, agg] of Object.entries(mergedConcepts)) {
333
356
  if (!state.patterns[concept]) {
334
357
  state.patterns[concept] = {
@@ -344,26 +367,6 @@ function applySignals(state: ModelState, signals: SessionSignals[], mergedConcep
344
367
  p.trend = p.occurrences >= 5 ? 'stable' : 'rising';
345
368
  }
346
369
 
347
- // Also track per-session concepts
348
- for (const sig of signals) {
349
- for (const [concept, count] of Object.entries(sig.concepts)) {
350
- if (!state.patterns[concept]) {
351
- state.patterns[concept] = {
352
- firstSeen: now,
353
- occurrences: 0,
354
- sessions: [],
355
- trend: 'new',
356
- lastSeen: now,
357
- };
358
- }
359
- const p = state.patterns[concept];
360
- p.occurrences += count;
361
- if (!p.sessions.includes(sig.sessionId)) p.sessions.push(sig.sessionId);
362
- p.lastSeen = now;
363
- p.trend = p.occurrences >= 5 ? 'stable' : 'rising';
364
- }
365
- }
366
-
367
370
  // Update lens weights from correction signals
368
371
  for (const sig of signals) {
369
372
  if (sig.correctionPhrases.length > 0) {
@@ -385,9 +388,9 @@ function applySignals(state: ModelState, signals: SessionSignals[], mergedConcep
385
388
 
386
389
  // ── Profile update ──
387
390
 
388
- function updateProfile(state: ModelState): void {
391
+ function updateProfile(state: ModelState, paths: UserModelPaths): void {
389
392
  try {
390
- let content = fs.readFileSync(PROFILE_FILE, 'utf-8');
393
+ let content = fs.readFileSync(paths.profileFile, 'utf-8');
391
394
 
392
395
  // Replace Derived Rules section
393
396
  const derivedStart = '## Derived Rules';
@@ -422,7 +425,7 @@ function updateProfile(state: ModelState): void {
422
425
  }
423
426
  }
424
427
 
425
- fs.writeFileSync(PROFILE_FILE, content, 'utf-8');
428
+ fs.writeFileSync(paths.profileFile, content, 'utf-8');
426
429
  } catch (e) {
427
430
  // Profile file might not exist yet — skip
428
431
  }
@@ -430,9 +433,9 @@ function updateProfile(state: ModelState): void {
430
433
 
431
434
  // ── Output ──
432
435
 
433
- function printChanges(changes: Change[], signals: SessionSignals[]): void {
436
+ function printChanges(changes: Change[], signals: SessionSignals[], io: CommandIO): void {
434
437
  if (changes.length === 0) {
435
- console.log(chalk.green('No significant changes detected'));
438
+ log(io, chalk.green('No significant changes detected'));
436
439
  return;
437
440
  }
438
441
 
@@ -443,31 +446,31 @@ function printChanges(changes: Change[], signals: SessionSignals[]): void {
443
446
  };
444
447
 
445
448
  if (byType.new_pattern.length > 0) {
446
- console.log(chalk.yellow(`🌱 New patterns: ${byType.new_pattern.length}`));
449
+ log(io, chalk.yellow(`🌱 New patterns: ${byType.new_pattern.length}`));
447
450
  for (const c of byType.new_pattern.slice(0, 5)) {
448
- console.log(chalk.gray(` ${c.key}: ${c.detail}`));
451
+ log(io, chalk.gray(` ${c.key}: ${c.detail}`));
449
452
  }
450
- console.log();
453
+ log(io);
451
454
  }
452
455
 
453
456
  if (byType.rising.length > 0) {
454
- console.log(chalk.green(`📈 Re-emerging: ${byType.rising.length}`));
457
+ log(io, chalk.green(`📈 Re-emerging: ${byType.rising.length}`));
455
458
  for (const c of byType.rising.slice(0, 5)) {
456
- console.log(chalk.gray(` ${c.key}: ${c.detail}`));
459
+ log(io, chalk.gray(` ${c.key}: ${c.detail}`));
457
460
  }
458
- console.log();
461
+ log(io);
459
462
  }
460
463
 
461
464
  if (byType.lens_shift.length > 0) {
462
- console.log(chalk.cyan(`🎯 Lens shifts:`));
465
+ log(io, chalk.cyan(`🎯 Lens shifts:`));
463
466
  for (const c of byType.lens_shift) {
464
- console.log(chalk.gray(` ${c.key}: ${c.detail}`));
467
+ log(io, chalk.gray(` ${c.key}: ${c.detail}`));
465
468
  }
466
- console.log();
469
+ log(io);
467
470
  }
468
471
 
469
- console.log(chalk.bold(`Total signals processed:`));
470
- console.log(` Sessions: ${signals.length}`);
471
- console.log(` Correction phrases: ${signals.reduce((s, sig) => s + sig.correctionPhrases.length, 0)}`);
472
- console.log(` Concepts extracted: ${signals.reduce((s, sig) => s + Object.keys(sig.concepts).length, 0)}`);
472
+ log(io, chalk.bold(`Total signals processed:`));
473
+ log(io, ` Sessions: ${signals.length}`);
474
+ log(io, ` Correction phrases: ${signals.reduce((s, sig) => s + sig.correctionPhrases.length, 0)}`);
475
+ log(io, ` Concepts extracted: ${signals.reduce((s, sig) => s + Object.keys(sig.concepts).length, 0)}`);
473
476
  }
@@ -10,6 +10,7 @@ import * as path from 'path';
10
10
  import * as yaml from 'js-yaml';
11
11
  import { CheckpointValidator } from '../../core/validators/checkpoint';
12
12
  import type { Checkpoint, CheckpointContext } from '../../types/checkpoint';
13
+ import { log, processIO, type CommandIO, type CommandResult } from '../command-contract';
13
14
 
14
15
  export interface ValidateOptions {
15
16
  /** 检查点文件路径 */
@@ -28,13 +29,13 @@ const DEFAULT_CHECKPOINT_FILE = '.harness/checkpoints.yml';
28
29
  /**
29
30
  * 加载检查点配置
30
31
  */
31
- async function loadCheckpoints(filePath: string): Promise<Checkpoint[]> {
32
+ async function loadCheckpoints(filePath: string, io: CommandIO): Promise<Checkpoint[]> {
32
33
  try {
33
34
  const content = await fs.readFile(filePath, 'utf-8');
34
35
  const data = yaml.load(content) as { checkpoints?: Checkpoint[] };
35
36
  return data.checkpoints || [];
36
37
  } catch (error) {
37
- console.log(chalk.yellow(`⚠️ 未找到检查点文件: ${filePath}`));
38
+ log(io, chalk.yellow(`⚠️ 未找到检查点文件: ${filePath}`));
38
39
  return [];
39
40
  }
40
41
  }
@@ -42,23 +43,26 @@ async function loadCheckpoints(filePath: string): Promise<Checkpoint[]> {
42
43
  /**
43
44
  * 执行检查点验证
44
45
  */
45
- export async function validate(options: ValidateOptions): Promise<void> {
46
- console.log(chalk.blue('🔍 验证检查点...'));
46
+ export async function validate(
47
+ options: ValidateOptions,
48
+ io: CommandIO = processIO,
49
+ ): Promise<CommandResult> {
50
+ log(io, chalk.blue('🔍 验证检查点...'));
47
51
 
48
52
  const projectPath = options.projectPath || process.cwd();
49
53
  const checkpointFile = options.file || path.join(projectPath, DEFAULT_CHECKPOINT_FILE);
50
54
 
51
55
  // 加载检查点
52
- const checkpoints = await loadCheckpoints(checkpointFile);
56
+ const checkpoints = await loadCheckpoints(checkpointFile, io);
53
57
 
54
58
  if (checkpoints.length === 0) {
55
- console.log(chalk.gray('没有定义检查点,跳过验证'));
56
- return;
59
+ log(io, chalk.gray('没有定义检查点,跳过验证'));
60
+ return { kind: 'skip', reason: `没有定义检查点: ${checkpointFile}` };
57
61
  }
58
62
 
59
- console.log(chalk.gray(`检查点文件: ${checkpointFile}`));
60
- console.log(chalk.gray(`检查点数量: ${checkpoints.length}`));
61
- console.log();
63
+ log(io, chalk.gray(`检查点文件: ${checkpointFile}`));
64
+ log(io, chalk.gray(`检查点数量: ${checkpoints.length}`));
65
+ log(io);
62
66
 
63
67
  // 构建上下文
64
68
  const context: CheckpointContext = {
@@ -69,20 +73,22 @@ export async function validate(options: ValidateOptions): Promise<void> {
69
73
  // 执行验证
70
74
  const validator = CheckpointValidator.getInstance();
71
75
  const results = [];
76
+ const failedIds: string[] = [];
72
77
 
73
78
  for (const checkpoint of checkpoints) {
74
79
  const result = await validator.validate(checkpoint, context);
75
80
  results.push(result);
76
81
 
77
82
  if (result.passed) {
78
- console.log(chalk.green(`✅ ${checkpoint.id}: 通过`));
83
+ log(io, chalk.green(`✅ ${checkpoint.id}: 通过`));
79
84
  } else {
80
- console.log(chalk.red(`❌ ${checkpoint.id}: 失败`));
85
+ log(io, chalk.red(`❌ ${checkpoint.id}: 失败`));
81
86
  result.checks.forEach(check => {
82
87
  if (!check.passed) {
83
- console.log(chalk.red(` - ${check.checkId}: ${check.message || check.error}`));
88
+ log(io, chalk.red(` - ${check.checkId}: ${check.message || check.error}`));
84
89
  }
85
90
  });
91
+ failedIds.push(checkpoint.id);
86
92
  }
87
93
  }
88
94
 
@@ -90,16 +96,16 @@ export async function validate(options: ValidateOptions): Promise<void> {
90
96
  const passed = results.filter(r => r.passed).length;
91
97
  const failed = results.filter(r => !r.passed).length;
92
98
 
93
- console.log();
94
- console.log(chalk.gray(`通过: ${passed}/${results.length}`));
99
+ log(io);
100
+ log(io, chalk.gray(`通过: ${passed}/${results.length}`));
95
101
 
96
102
  if (failed > 0) {
97
- console.log(chalk.red(`\n🛑 ${failed} 个检查点未通过`));
103
+ log(io, chalk.red(`\n🛑 ${failed} 个检查点未通过`));
98
104
  // 工单 23:门控语义——检查点失败一律非零退出(此前仅 --strict 退出,钩子/CI 形同虚设)
99
- process.exit(1);
100
- } else {
101
- console.log(chalk.green('\n✅ 所有检查点验证通过'));
105
+ return { kind: 'fail', reason: `${failed} 个检查点未通过: ${failedIds.join(', ')}` };
102
106
  }
107
+ log(io, chalk.green('\n✅ 所有检查点验证通过'));
108
+ return { kind: 'ok' };
103
109
  }
104
110
 
105
111
  /**
@@ -138,14 +144,14 @@ export const DEFAULT_CHECKPOINTS: Checkpoint[] = [
138
144
  /**
139
145
  * 创建示例检查点文件
140
146
  */
141
- export async function createExampleCheckpoint(projectPath: string): Promise<void> {
147
+ export async function createExampleCheckpoint(projectPath: string, io: CommandIO = processIO): Promise<void> {
142
148
  const filePath = path.join(projectPath, DEFAULT_CHECKPOINT_FILE);
143
149
  const dir = path.dirname(filePath);
144
150
 
145
151
  // 如果已存在,不覆盖
146
152
  try {
147
153
  await fs.access(filePath);
148
- console.log(chalk.gray(`checkpoints.yml 已存在,跳过`));
154
+ log(io, chalk.gray(`checkpoints.yml 已存在,跳过`));
149
155
  return;
150
156
  } catch {
151
157
  // 文件不存在,创建
@@ -156,7 +162,7 @@ export async function createExampleCheckpoint(projectPath: string): Promise<void
156
162
  const content = yaml.dump({ checkpoints: DEFAULT_CHECKPOINTS }, { indent: 2 });
157
163
  await fs.writeFile(filePath, content, 'utf-8');
158
164
 
159
- console.log(chalk.green(`✅ 已创建示例检查点文件: ${filePath}`));
165
+ log(io, chalk.green(`✅ 已创建示例检查点文件: ${filePath}`));
160
166
  }
161
167
 
162
168
  /**
@@ -180,14 +186,14 @@ const DEFAULT_RESOLUTIONS = {
180
186
  /**
181
187
  * 创建示例 Resolutions 文件(RKB dogfood)
182
188
  */
183
- export async function createExampleResolutions(projectPath: string): Promise<void> {
189
+ export async function createExampleResolutions(projectPath: string, io: CommandIO = processIO): Promise<void> {
184
190
  const harnessDir = path.join(projectPath, '.harness');
185
191
  const filePath = path.join(harnessDir, 'resolutions.json');
186
192
 
187
193
  // 如果已存在,不覆盖
188
194
  try {
189
195
  await fs.access(filePath);
190
- console.log(chalk.gray(`resolutions.json 已存在,跳过`));
196
+ log(io, chalk.gray(`resolutions.json 已存在,跳过`));
191
197
  return;
192
198
  } catch {
193
199
  // 文件不存在,创建
@@ -198,5 +204,5 @@ export async function createExampleResolutions(projectPath: string): Promise<voi
198
204
  const content = JSON.stringify(DEFAULT_RESOLUTIONS, null, 2);
199
205
  await fs.writeFile(filePath, content, 'utf-8');
200
206
 
201
- console.log(chalk.green(`✅ 已创建 Resolutions 文件: ${filePath}`));
207
+ log(io, chalk.green(`✅ 已创建 Resolutions 文件: ${filePath}`));
202
208
  }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * readTranscriptSessions 坏行/null 记录容错测试(harness#82)
3
+ *
4
+ * transcript 行可 parse 但值为 null(或提取异常)时,
5
+ * 与原逐行 catch 实现一致:该条跳过,不影响其余记录。
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as os from 'os';
10
+ import * as path from 'path';
11
+ import { readTranscriptSessions } from '../transcript';
12
+
13
+ describe('readTranscriptSessions 坏行容错(harness#82)', () => {
14
+ let dir: string;
15
+
16
+ beforeEach(() => {
17
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-transcript-'));
18
+ });
19
+
20
+ afterEach(() => {
21
+ fs.rmSync(dir, { recursive: true, force: true });
22
+ });
23
+
24
+ it('null 行与坏行只丢该行,合法 turn 照常提取', () => {
25
+ const lines = [
26
+ JSON.stringify({ message: { role: 'user', content: 'hello' } }),
27
+ 'null', // parse 合法但值为 null,原实现经逐行 catch 跳过
28
+ '{"broken',
29
+ JSON.stringify({ message: { role: 'assistant', content: [{ type: 'text', text: 'hi' }] } }),
30
+ ];
31
+ fs.writeFileSync(path.join(dir, 'session-a.jsonl'), lines.join('\n') + '\n', 'utf-8');
32
+
33
+ const sessions = readTranscriptSessions(dir);
34
+ expect(sessions).toHaveLength(1);
35
+ expect(sessions[0].turns.map(t => t.role)).toEqual(['user', 'assistant']);
36
+ expect(sessions[0].turns[1].content).toBe('hi');
37
+ });
38
+
39
+ it('目录不可读返回空数组', () => {
40
+ expect(readTranscriptSessions(path.join(dir, 'missing'))).toEqual([]);
41
+ });
42
+ });
43
+
44
+ describe('readTranscriptSessions 过滤(harness#112)', () => {
45
+ let dir: string;
46
+
47
+ beforeEach(() => {
48
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-transcript-filter-'));
49
+ });
50
+
51
+ afterEach(() => {
52
+ fs.rmSync(dir, { recursive: true, force: true });
53
+ });
54
+
55
+ function writeSession(name: string, text: string, mtime?: Date): void {
56
+ const p = path.join(dir, name);
57
+ fs.writeFileSync(
58
+ p,
59
+ JSON.stringify({ message: { role: 'user', content: text } }) + '\n',
60
+ 'utf-8',
61
+ );
62
+ if (mtime) fs.utimesSync(p, mtime, mtime);
63
+ }
64
+
65
+ it('excludeIds:命中的会话不进结果(文件名级过滤,不 parse)', () => {
66
+ writeSession('session-a.jsonl', '甲会话内容');
67
+ writeSession('session-b.jsonl', '乙会话内容');
68
+
69
+ const sessions = readTranscriptSessions(dir, { excludeIds: ['session-a'] });
70
+ expect(sessions.map(s => s.id)).toEqual(['session-b']);
71
+ });
72
+
73
+ it('excludeIds 按截断 40 字符的会话 ID 匹配', () => {
74
+ const longName = 'a'.repeat(50);
75
+ writeSession(`${longName}.jsonl`, '长名会话内容');
76
+ writeSession('session-b.jsonl', '乙会话内容');
77
+
78
+ const sessions = readTranscriptSessions(dir, { excludeIds: [longName.slice(0, 40)] });
79
+ expect(sessions.map(s => s.id)).toEqual(['session-b']);
80
+ });
81
+
82
+ it('since:mtimeMs 早于窗口的文件不进结果(stat 级过滤,不 parse)', () => {
83
+ writeSession('old.jsonl', '旧会话内容', new Date(Date.now() - 30 * 86_400_000));
84
+ writeSession('recent.jsonl', '新会话内容');
85
+
86
+ const since = Date.now() - 7 * 86_400_000;
87
+ const sessions = readTranscriptSessions(dir, { since });
88
+ expect(sessions.map(s => s.id)).toEqual(['recent']);
89
+ });
90
+
91
+ it('since 边界含等于:mtimeMs == since 仍计入', () => {
92
+ const t = new Date('2026-08-01T12:00:00.000Z');
93
+ writeSession('edge.jsonl', '边界会话内容', t);
94
+
95
+ const sessions = readTranscriptSessions(dir, { since: t.getTime() });
96
+ expect(sessions.map(s => s.id)).toEqual(['edge']);
97
+ });
98
+
99
+ it('since 与 excludeIds 可叠加', () => {
100
+ writeSession('old.jsonl', '旧会话内容', new Date(Date.now() - 30 * 86_400_000));
101
+ writeSession('excluded.jsonl', '被排除会话');
102
+ writeSession('kept.jsonl', '保留会话内容');
103
+
104
+ const sessions = readTranscriptSessions(dir, {
105
+ since: Date.now() - 7 * 86_400_000,
106
+ excludeIds: ['excluded'],
107
+ });
108
+ expect(sessions.map(s => s.id)).toEqual(['kept']);
109
+ });
110
+
111
+ it('无 filter:全量读取(向后兼容)', () => {
112
+ writeSession('a.jsonl', '甲会话内容', new Date(Date.now() - 30 * 86_400_000));
113
+ writeSession('b.jsonl', '乙会话内容');
114
+
115
+ const sessions = readTranscriptSessions(dir);
116
+ expect(sessions).toHaveLength(2);
117
+ });
118
+ });
@@ -7,6 +7,7 @@
7
7
 
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
+ import { readJsonl } from '../../utils/jsonl';
10
11
 
11
12
  export interface MinedTurn {
12
13
  role: string;
@@ -41,8 +42,18 @@ function extractText(content: unknown): string {
41
42
  *
42
43
  * 不可读/损坏的行静默跳过(与原实现一致)。
43
44
  */
44
- export function readTranscriptSessions(dir: string): MinedSession[] {
45
+ export interface TranscriptFilter {
46
+ /** 只读 mtimeMs >= since 的文件(stat 级过滤,命中即不 parse);缺省不过滤 */
47
+ since?: number;
48
+ /** 按会话 ID(文件名去 .jsonl、截断 40 字符)排除(文件名级过滤,不 stat 不 parse);缺省不排除 */
49
+ excludeIds?: string[];
50
+ }
51
+
52
+ export function readTranscriptSessions(dir: string, filter: TranscriptFilter = {}): MinedSession[] {
45
53
  const sessions: MinedSession[] = [];
54
+ const exclude = filter.excludeIds && filter.excludeIds.length > 0
55
+ ? new Set(filter.excludeIds)
56
+ : undefined;
46
57
 
47
58
  let files: string[];
48
59
  try {
@@ -53,6 +64,8 @@ export function readTranscriptSessions(dir: string): MinedSession[] {
53
64
 
54
65
  for (const file of files) {
55
66
  if (!file.endsWith('.jsonl')) continue;
67
+ const id = file.replace('.jsonl', '').slice(0, 40);
68
+ if (exclude?.has(id)) continue;
56
69
  const filePath = path.join(dir, file);
57
70
 
58
71
  let stat: fs.Stats;
@@ -61,21 +74,26 @@ export function readTranscriptSessions(dir: string): MinedSession[] {
61
74
  } catch {
62
75
  continue;
63
76
  }
77
+ if (filter.since !== undefined && stat.mtimeMs < filter.since) continue;
64
78
 
65
79
  const turns: MinedTurn[] = [];
66
80
  const toolCalls: string[] = [];
67
81
 
68
- let content: string;
82
+ let records: Record<string, any>[];
69
83
  try {
70
- content = fs.readFileSync(filePath, 'utf-8');
84
+ // 损坏行静默跳过(坏行策略 skip,与原实现一致,harness#82 走 jsonl 正本);
85
+ // 外层 catch 兜文件不可读,沿用原「跳过该文件」语义
86
+ // 计数去向:豁免(harness#100)——读的是外部 Claude Code transcript(非 harness 写入面)。
87
+ // 影响方向如实:坏行使该会话少几条 turn,而 analyze-sessions 的候选按次数阈值筛
88
+ // (frequency>=4 / 跨会话>=2 / confidence=len/10)→ 只会少出候选,不会造出候选,且候选须人审;
89
+ // 告知需改 MinedSession 形状并波及 analyze-sessions/update-user-model 两个调用方,另票收口
90
+ records = readJsonl<Record<string, any>>(filePath, 'skip').records;
71
91
  } catch {
72
92
  continue;
73
93
  }
74
94
 
75
- for (const line of content.split('\n')) {
76
- if (!line.trim()) continue;
95
+ for (const entry of records) {
77
96
  try {
78
- const entry = JSON.parse(line);
79
97
  const msg = entry.message;
80
98
  if (msg?.role && (msg.content || entry.type === 'user')) {
81
99
  turns.push({ role: msg.role, content: extractText(msg.content) });
@@ -88,13 +106,13 @@ export function readTranscriptSessions(dir: string): MinedSession[] {
88
106
  }
89
107
  }
90
108
  } catch {
91
- // 损坏行跳过
109
+ // 单条记录提取异常跳过(原逐行 catch 语义:parse 已由 module skip,此处兜提取)
92
110
  }
93
111
  }
94
112
 
95
113
  if (turns.length > 0) {
96
114
  sessions.push({
97
- id: file.replace('.jsonl', '').slice(0, 40),
115
+ id,
98
116
  date: stat.mtime.toISOString().slice(0, 10),
99
117
  mtimeMs: stat.mtimeMs,
100
118
  turns,