@dommaker/harness 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (517) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +0 -2
  3. package/bin/harness.js +86 -32
  4. package/dist/cli/command-contract.d.ts +83 -0
  5. package/dist/cli/command-contract.d.ts.map +1 -0
  6. package/dist/cli/command-contract.js +66 -0
  7. package/dist/cli/command-contract.js.map +1 -0
  8. package/dist/cli/commands/acceptance.d.ts +3 -2
  9. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  10. package/dist/cli/commands/acceptance.js +28 -24
  11. package/dist/cli/commands/acceptance.js.map +1 -1
  12. package/dist/cli/commands/analyze-sessions.d.ts +2 -1
  13. package/dist/cli/commands/analyze-sessions.d.ts.map +1 -1
  14. package/dist/cli/commands/analyze-sessions.js +25 -24
  15. package/dist/cli/commands/analyze-sessions.js.map +1 -1
  16. package/dist/cli/commands/check.d.ts +14 -2
  17. package/dist/cli/commands/check.d.ts.map +1 -1
  18. package/dist/cli/commands/check.js +76 -61
  19. package/dist/cli/commands/check.js.map +1 -1
  20. package/dist/cli/commands/command.d.ts +5 -1
  21. package/dist/cli/commands/command.d.ts.map +1 -1
  22. package/dist/cli/commands/command.js +26 -17
  23. package/dist/cli/commands/command.js.map +1 -1
  24. package/dist/cli/commands/constraints-report.d.ts +2 -1
  25. package/dist/cli/commands/constraints-report.d.ts.map +1 -1
  26. package/dist/cli/commands/constraints-report.js +37 -33
  27. package/dist/cli/commands/constraints-report.js.map +1 -1
  28. package/dist/cli/commands/constraints-retire.d.ts +6 -5
  29. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  30. package/dist/cli/commands/constraints-retire.js +50 -52
  31. package/dist/cli/commands/constraints-retire.js.map +1 -1
  32. package/dist/cli/commands/constraints.d.ts +2 -1
  33. package/dist/cli/commands/constraints.d.ts.map +1 -1
  34. package/dist/cli/commands/constraints.js +10 -27
  35. package/dist/cli/commands/constraints.js.map +1 -1
  36. package/dist/cli/commands/contract.d.ts +3 -2
  37. package/dist/cli/commands/contract.d.ts.map +1 -1
  38. package/dist/cli/commands/contract.js +45 -42
  39. package/dist/cli/commands/contract.js.map +1 -1
  40. package/dist/cli/commands/definitions.d.ts +11 -11
  41. package/dist/cli/commands/definitions.d.ts.map +1 -1
  42. package/dist/cli/commands/definitions.js +29 -214
  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 +21 -15
  53. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  54. package/dist/cli/commands/knowledge.js +214 -199
  55. package/dist/cli/commands/knowledge.js.map +1 -1
  56. package/dist/cli/commands/passes-gate.d.ts +8 -2
  57. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  58. package/dist/cli/commands/passes-gate.js +44 -66
  59. package/dist/cli/commands/passes-gate.js.map +1 -1
  60. package/dist/cli/commands/performance.d.ts +2 -1
  61. package/dist/cli/commands/performance.d.ts.map +1 -1
  62. package/dist/cli/commands/performance.js +20 -18
  63. package/dist/cli/commands/performance.js.map +1 -1
  64. package/dist/cli/commands/posteval-plan.d.ts +5 -1
  65. package/dist/cli/commands/posteval-plan.d.ts.map +1 -1
  66. package/dist/cli/commands/posteval-plan.js +26 -15
  67. package/dist/cli/commands/posteval-plan.js.map +1 -1
  68. package/dist/cli/commands/release.d.ts +6 -1
  69. package/dist/cli/commands/release.d.ts.map +1 -1
  70. package/dist/cli/commands/release.js +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 +2 -1
  115. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  116. package/dist/cli/commands/update-user-model.js +31 -45
  117. package/dist/cli/commands/update-user-model.js.map +1 -1
  118. package/dist/cli/commands/validate.d.ts +4 -3
  119. package/dist/cli/commands/validate.d.ts.map +1 -1
  120. package/dist/cli/commands/validate.js +28 -26
  121. package/dist/cli/commands/validate.js.map +1 -1
  122. package/dist/cli/session-mining/transcript.d.ts +7 -1
  123. package/dist/cli/session-mining/transcript.d.ts.map +1 -1
  124. package/dist/cli/session-mining/transcript.js +21 -14
  125. package/dist/cli/session-mining/transcript.js.map +1 -1
  126. package/dist/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 +64 -115
  143. package/dist/core/constraints/checker.js.map +1 -1
  144. package/dist/core/constraints/checkers/capability-sync.d.ts +4 -3
  145. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  146. package/dist/core/constraints/checkers/capability-sync.js +29 -69
  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 +37 -63
  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/no-test-simplification.d.ts.map +1 -1
  160. package/dist/core/constraints/checkers/no-test-simplification.js +4 -3
  161. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  162. package/dist/core/constraints/checkers/types.d.ts +2 -2
  163. package/dist/core/constraints/context-builder.d.ts +15 -14
  164. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  165. package/dist/core/constraints/context-builder.js +32 -96
  166. package/dist/core/constraints/context-builder.js.map +1 -1
  167. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  168. package/dist/core/constraints/doc-freshness/runner.js +2 -1
  169. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  170. package/dist/core/constraints/git-evidence.d.ts +60 -0
  171. package/dist/core/constraints/git-evidence.d.ts.map +1 -0
  172. package/dist/core/constraints/git-evidence.js +88 -0
  173. package/dist/core/constraints/git-evidence.js.map +1 -0
  174. package/dist/core/constraints/injection-drift.d.ts +3 -20
  175. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  176. package/dist/core/constraints/injection-drift.js +10 -57
  177. package/dist/core/constraints/injection-drift.js.map +1 -1
  178. package/dist/core/constraints/injection-writer.d.ts +113 -0
  179. package/dist/core/constraints/injection-writer.d.ts.map +1 -0
  180. package/dist/core/constraints/injection-writer.js +228 -0
  181. package/dist/core/constraints/injection-writer.js.map +1 -0
  182. package/dist/core/constraints/usage-report.d.ts +22 -1
  183. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  184. package/dist/core/constraints/usage-report.js +27 -20
  185. package/dist/core/constraints/usage-report.js.map +1 -1
  186. package/dist/core/index.d.ts +2 -2
  187. package/dist/core/index.d.ts.map +1 -1
  188. package/dist/core/index.js +4 -2
  189. package/dist/core/index.js.map +1 -1
  190. package/dist/core/project-config-loader.d.ts +32 -1
  191. package/dist/core/project-config-loader.d.ts.map +1 -1
  192. package/dist/core/project-config-loader.js +39 -9
  193. package/dist/core/project-config-loader.js.map +1 -1
  194. package/dist/core/validators/passes-gate.d.ts +15 -64
  195. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  196. package/dist/core/validators/passes-gate.js +84 -222
  197. package/dist/core/validators/passes-gate.js.map +1 -1
  198. package/dist/core/validators/test-output.d.ts +58 -0
  199. package/dist/core/validators/test-output.d.ts.map +1 -0
  200. package/dist/core/validators/test-output.js +109 -0
  201. package/dist/core/validators/test-output.js.map +1 -0
  202. package/dist/failure/recorder.d.ts +6 -0
  203. package/dist/failure/recorder.d.ts.map +1 -1
  204. package/dist/failure/recorder.js +12 -8
  205. package/dist/failure/recorder.js.map +1 -1
  206. package/dist/gates/acceptance.d.ts +1 -48
  207. package/dist/gates/acceptance.d.ts.map +1 -1
  208. package/dist/gates/acceptance.js +6 -38
  209. package/dist/gates/acceptance.js.map +1 -1
  210. package/dist/gates/checker-gate.d.ts.map +1 -1
  211. package/dist/gates/checker-gate.js +6 -11
  212. package/dist/gates/checker-gate.js.map +1 -1
  213. package/dist/gates/command.d.ts.map +1 -1
  214. package/dist/gates/command.js +10 -22
  215. package/dist/gates/command.js.map +1 -1
  216. package/dist/gates/contract.d.ts.map +1 -1
  217. package/dist/gates/contract.js +22 -61
  218. package/dist/gates/contract.js.map +1 -1
  219. package/dist/gates/performance.d.ts.map +1 -1
  220. package/dist/gates/performance.js +13 -31
  221. package/dist/gates/performance.js.map +1 -1
  222. package/dist/gates/review.d.ts.map +1 -1
  223. package/dist/gates/review.js +21 -53
  224. package/dist/gates/review.js.map +1 -1
  225. package/dist/gates/security.d.ts.map +1 -1
  226. package/dist/gates/security.js +14 -32
  227. package/dist/gates/security.js.map +1 -1
  228. package/dist/gates/types.d.ts +28 -5
  229. package/dist/gates/types.d.ts.map +1 -1
  230. package/dist/gates/types.js +38 -1
  231. package/dist/gates/types.js.map +1 -1
  232. package/dist/hooks/bootstrap.d.ts +2 -0
  233. package/dist/hooks/bootstrap.d.ts.map +1 -1
  234. package/dist/hooks/bootstrap.js +5 -2
  235. package/dist/hooks/bootstrap.js.map +1 -1
  236. package/dist/index.d.ts +1 -2
  237. package/dist/index.d.ts.map +1 -1
  238. package/dist/index.js.map +1 -1
  239. package/dist/knowledge/audit.d.ts +7 -2
  240. package/dist/knowledge/audit.d.ts.map +1 -1
  241. package/dist/knowledge/audit.js +59 -79
  242. package/dist/knowledge/audit.js.map +1 -1
  243. package/dist/knowledge/flywheel-metrics.d.ts +39 -0
  244. package/dist/knowledge/flywheel-metrics.d.ts.map +1 -0
  245. package/dist/knowledge/flywheel-metrics.js +47 -0
  246. package/dist/knowledge/flywheel-metrics.js.map +1 -0
  247. package/dist/knowledge/import.d.ts.map +1 -1
  248. package/dist/knowledge/import.js +4 -14
  249. package/dist/knowledge/import.js.map +1 -1
  250. package/dist/knowledge/index-generator.d.ts.map +1 -1
  251. package/dist/knowledge/index-generator.js +17 -14
  252. package/dist/knowledge/index-generator.js.map +1 -1
  253. package/dist/knowledge/ingest.d.ts.map +1 -1
  254. package/dist/knowledge/ingest.js +9 -4
  255. package/dist/knowledge/ingest.js.map +1 -1
  256. package/dist/knowledge/migration.d.ts.map +1 -1
  257. package/dist/knowledge/migration.js +11 -8
  258. package/dist/knowledge/migration.js.map +1 -1
  259. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  260. package/dist/knowledge/reference-tracker.js +12 -9
  261. package/dist/knowledge/reference-tracker.js.map +1 -1
  262. package/dist/knowledge/store.d.ts +15 -0
  263. package/dist/knowledge/store.d.ts.map +1 -1
  264. package/dist/knowledge/store.js +59 -10
  265. package/dist/knowledge/store.js.map +1 -1
  266. package/dist/monitoring/context-tracker.d.ts.map +1 -1
  267. package/dist/monitoring/context-tracker.js +11 -20
  268. package/dist/monitoring/context-tracker.js.map +1 -1
  269. package/dist/monitoring/index.d.ts +1 -1
  270. package/dist/monitoring/index.d.ts.map +1 -1
  271. package/dist/monitoring/index.js +1 -2
  272. package/dist/monitoring/index.js.map +1 -1
  273. package/dist/monitoring/trace-analyzer.d.ts +15 -0
  274. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  275. package/dist/monitoring/trace-analyzer.js +19 -2
  276. package/dist/monitoring/trace-analyzer.js.map +1 -1
  277. package/dist/monitoring/traces.d.ts +21 -2
  278. package/dist/monitoring/traces.d.ts.map +1 -1
  279. package/dist/monitoring/traces.js +38 -29
  280. package/dist/monitoring/traces.js.map +1 -1
  281. package/dist/release/index.d.ts +2 -2
  282. package/dist/release/index.d.ts.map +1 -1
  283. package/dist/release/index.js +1 -5
  284. package/dist/release/index.js.map +1 -1
  285. package/dist/sdd/index-generator.js +10 -11
  286. package/dist/sdd/index-generator.js.map +1 -1
  287. package/dist/test-setup/project-fixture.d.ts +48 -0
  288. package/dist/test-setup/project-fixture.d.ts.map +1 -0
  289. package/dist/test-setup/project-fixture.js +106 -0
  290. package/dist/test-setup/project-fixture.js.map +1 -0
  291. package/dist/types/constraint.d.ts +0 -18
  292. package/dist/types/constraint.d.ts.map +1 -1
  293. package/dist/types/constraint.js.map +1 -1
  294. package/dist/types/passes-gate.d.ts +10 -25
  295. package/dist/types/passes-gate.d.ts.map +1 -1
  296. package/dist/types/project-config.d.ts +1 -1
  297. package/dist/types/session.d.ts +6 -5
  298. package/dist/types/session.d.ts.map +1 -1
  299. package/dist/utils/exec.d.ts +10 -0
  300. package/dist/utils/exec.d.ts.map +1 -1
  301. package/dist/utils/exec.js +12 -0
  302. package/dist/utils/exec.js.map +1 -1
  303. package/dist/utils/file-walk.d.ts +9 -1
  304. package/dist/utils/file-walk.d.ts.map +1 -1
  305. package/dist/utils/file-walk.js +10 -1
  306. package/dist/utils/file-walk.js.map +1 -1
  307. package/dist/utils/frontmatter.d.ts +73 -0
  308. package/dist/utils/frontmatter.d.ts.map +1 -0
  309. package/dist/utils/frontmatter.js +141 -0
  310. package/dist/utils/frontmatter.js.map +1 -0
  311. package/dist/utils/jsonl.d.ts +92 -0
  312. package/dist/utils/jsonl.d.ts.map +1 -0
  313. package/dist/utils/jsonl.js +166 -0
  314. package/dist/utils/jsonl.js.map +1 -0
  315. package/dist/utils/package-version.d.ts +16 -0
  316. package/dist/utils/package-version.d.ts.map +1 -0
  317. package/dist/utils/package-version.js +63 -0
  318. package/dist/utils/package-version.js.map +1 -0
  319. package/package.json +1 -1
  320. package/src/CONTEXT.md +7 -2
  321. package/src/__tests__/acceptance-gate.test.ts +3 -1
  322. package/src/__tests__/checker-extra.test.ts +7 -1218
  323. package/src/__tests__/context-files-resolution.test.ts +116 -0
  324. package/src/__tests__/effective-constraints.test.ts +10 -13
  325. package/src/__tests__/exec.test.ts +35 -1
  326. package/src/__tests__/layering.test.ts +152 -0
  327. package/src/__tests__/passes-gate-extra.test.ts +0 -145
  328. package/src/__tests__/passes-gate.test.ts +142 -106
  329. package/src/__tests__/skip-semantics.test.ts +5 -9
  330. package/src/cli/__tests__/command-contract.test.ts +170 -0
  331. package/src/cli/command-contract.ts +108 -0
  332. package/src/cli/commands/CONTEXT.md +22 -7
  333. package/src/cli/commands/__tests__/acceptance.test.ts +40 -33
  334. package/src/cli/commands/__tests__/analyze-sessions.test.ts +35 -29
  335. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +101 -0
  336. package/src/cli/commands/__tests__/check-drift.test.ts +27 -34
  337. package/src/cli/commands/__tests__/check-skip-output.test.ts +21 -24
  338. package/src/cli/commands/__tests__/check.test.ts +378 -382
  339. package/src/cli/commands/__tests__/command.test.ts +85 -70
  340. package/src/cli/commands/__tests__/constraints-report.test.ts +100 -71
  341. package/src/cli/commands/__tests__/constraints-retire.test.ts +152 -95
  342. package/src/cli/commands/__tests__/contract.test.ts +27 -34
  343. package/src/cli/commands/__tests__/failure.test.ts +31 -34
  344. package/src/cli/commands/__tests__/init-injection.test.ts +58 -28
  345. package/src/cli/commands/__tests__/init.test.ts +77 -48
  346. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +108 -0
  347. package/src/cli/commands/__tests__/knowledge.test.ts +69 -43
  348. package/src/cli/commands/__tests__/passes-gate.test.ts +43 -45
  349. package/src/cli/commands/__tests__/performance.test.ts +22 -23
  350. package/src/cli/commands/__tests__/posteval-plan.test.ts +39 -38
  351. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +157 -0
  352. package/src/cli/commands/__tests__/project-path-convention.test.ts +169 -0
  353. package/src/cli/commands/__tests__/registry.test.ts +69 -35
  354. package/src/cli/commands/__tests__/release.test.ts +209 -80
  355. package/src/cli/commands/__tests__/report-extra.test.ts +16 -16
  356. package/src/cli/commands/__tests__/report.test.ts +25 -25
  357. package/src/cli/commands/__tests__/review.test.ts +33 -31
  358. package/src/cli/commands/__tests__/security.test.ts +23 -27
  359. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +20 -20
  360. package/src/cli/commands/__tests__/spec.test.ts +41 -29
  361. package/src/cli/commands/__tests__/status-extra.test.ts +18 -18
  362. package/src/cli/commands/__tests__/status-skipped-lines.test.ts +72 -0
  363. package/src/cli/commands/__tests__/status.test.ts +34 -34
  364. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +77 -78
  365. package/src/cli/commands/__tests__/sync-docs.test.ts +72 -74
  366. package/src/cli/commands/__tests__/update-user-model.test.ts +62 -33
  367. package/src/cli/commands/__tests__/validate.test.ts +27 -29
  368. package/src/cli/commands/acceptance.ts +35 -24
  369. package/src/cli/commands/analyze-sessions.ts +25 -24
  370. package/src/cli/commands/check.ts +88 -65
  371. package/src/cli/commands/command.ts +30 -17
  372. package/src/cli/commands/constraints-report.ts +37 -34
  373. package/src/cli/commands/constraints-retire.ts +58 -55
  374. package/src/cli/commands/constraints.ts +10 -27
  375. package/src/cli/commands/contract.ts +51 -42
  376. package/src/cli/commands/definitions.ts +36 -222
  377. package/src/cli/commands/failure.ts +33 -26
  378. package/src/cli/commands/init.ts +166 -220
  379. package/src/cli/commands/knowledge.ts +225 -203
  380. package/src/cli/commands/passes-gate.ts +58 -68
  381. package/src/cli/commands/performance.ts +23 -18
  382. package/src/cli/commands/posteval-plan.ts +29 -15
  383. package/src/cli/commands/release.ts +70 -59
  384. package/src/cli/commands/report.ts +9 -5
  385. package/src/cli/commands/review.ts +39 -29
  386. package/src/cli/commands/sdd.ts +7 -5
  387. package/src/cli/commands/security.ts +35 -26
  388. package/src/cli/commands/spec-baseline-check.ts +16 -11
  389. package/src/cli/commands/spec.ts +66 -53
  390. package/src/cli/commands/status.ts +59 -53
  391. package/src/cli/commands/sync-docs/agents-syncer.ts +5 -2
  392. package/src/cli/commands/sync-docs/context-syncer.ts +3 -2
  393. package/src/cli/commands/sync-docs/index.ts +73 -100
  394. package/src/cli/commands/sync-docs/project-reader.ts +17 -18
  395. package/src/cli/commands/update-user-model.ts +33 -45
  396. package/src/cli/commands/validate.ts +31 -25
  397. package/src/cli/session-mining/__tests__/transcript.test.ts +118 -0
  398. package/src/cli/session-mining/transcript.ts +26 -8
  399. package/src/context/session-manager.ts +19 -33
  400. package/src/core/CONTEXT.md +9 -6
  401. package/src/core/constraints/__tests__/capabilities-reconcile.test.ts +200 -0
  402. package/src/core/constraints/__tests__/capability-counts.test.ts +94 -0
  403. package/src/core/constraints/__tests__/git-evidence.test.ts +208 -0
  404. package/src/core/constraints/__tests__/injection-drift.test.ts +3 -10
  405. package/src/core/constraints/__tests__/injection-writer.test.ts +231 -0
  406. package/src/core/constraints/__tests__/trace-injection.test.ts +71 -0
  407. package/src/core/constraints/__tests__/usage-report.test.ts +112 -0
  408. package/src/core/constraints/agent-prompt-renderer.ts +2 -6
  409. package/src/core/constraints/capabilities-parser.ts +53 -35
  410. package/src/core/constraints/capabilities-reconcile.ts +166 -0
  411. package/src/core/constraints/checker.ts +75 -130
  412. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +214 -0
  413. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +77 -0
  414. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +117 -0
  415. package/src/{__tests__ → core/constraints/checkers/__tests__}/governance-presence.test.ts +83 -9
  416. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +50 -0
  417. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +76 -0
  418. package/src/{__tests__ → core/constraints/checkers/__tests__}/no-hardcoded-credentials.test.ts +4 -3
  419. package/src/core/constraints/checkers/__tests__/no-test-simplification.test.ts +58 -0
  420. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +145 -0
  421. package/src/core/constraints/checkers/capability-sync.ts +29 -88
  422. package/src/core/constraints/checkers/context-doc-sync.ts +15 -25
  423. package/src/core/constraints/checkers/docs-freshness.ts +40 -69
  424. package/src/core/constraints/checkers/governance-presence.ts +7 -25
  425. package/src/core/constraints/checkers/no-test-simplification.ts +4 -3
  426. package/src/core/constraints/checkers/types.ts +2 -2
  427. package/src/core/constraints/context-builder.ts +39 -91
  428. package/src/core/constraints/doc-freshness/runner.ts +2 -1
  429. package/src/core/constraints/git-evidence.ts +120 -0
  430. package/src/core/constraints/injection-drift.ts +13 -60
  431. package/src/core/constraints/injection-writer.ts +212 -0
  432. package/src/core/constraints/usage-report.ts +36 -17
  433. package/src/core/index.ts +4 -2
  434. package/src/core/project-config-loader.ts +49 -9
  435. package/src/core/validators/__tests__/passes-gate.test.ts +2 -56
  436. package/src/core/validators/__tests__/test-output.test.ts +372 -0
  437. package/src/core/validators/passes-gate.ts +86 -248
  438. package/src/core/validators/test-output.ts +128 -0
  439. package/src/failure/__tests__/recorder.test.ts +168 -0
  440. package/src/failure/recorder.ts +12 -10
  441. package/src/gates/CONTEXT.md +6 -3
  442. package/src/gates/__tests__/acceptance-types-single-source.test.ts +68 -0
  443. package/src/gates/__tests__/acceptance.test.ts +4 -86
  444. package/src/gates/__tests__/effective-gates.test.ts +3 -8
  445. package/src/gates/__tests__/test-verdict-consistency.test.ts +140 -0
  446. package/src/gates/acceptance.ts +19 -84
  447. package/src/gates/checker-gate.ts +8 -7
  448. package/src/gates/command.ts +10 -16
  449. package/src/gates/contract.ts +22 -61
  450. package/src/gates/performance.ts +10 -22
  451. package/src/gates/review.ts +21 -41
  452. package/src/gates/security.ts +10 -22
  453. package/src/gates/types.ts +70 -5
  454. package/src/hooks/CONTEXT.md +1 -1
  455. package/src/hooks/bootstrap.ts +5 -2
  456. package/src/index.ts +0 -4
  457. package/src/knowledge/CONTEXT.md +8 -3
  458. package/src/knowledge/__tests__/audit-labels.test.ts +44 -0
  459. package/src/knowledge/__tests__/flywheel-metrics.test.ts +135 -0
  460. package/src/knowledge/__tests__/import.test.ts +44 -0
  461. package/src/knowledge/__tests__/index-generator.test.ts +53 -0
  462. package/src/knowledge/__tests__/ingest.test.ts +85 -0
  463. package/src/knowledge/__tests__/migration.test.ts +47 -0
  464. package/src/knowledge/__tests__/reference-tracker.test.ts +16 -0
  465. package/src/knowledge/__tests__/store.test.ts +194 -0
  466. package/src/knowledge/audit.ts +67 -71
  467. package/src/knowledge/flywheel-metrics.ts +71 -0
  468. package/src/knowledge/import.ts +4 -18
  469. package/src/knowledge/index-generator.ts +19 -14
  470. package/src/knowledge/ingest.ts +9 -4
  471. package/src/knowledge/migration.ts +11 -8
  472. package/src/knowledge/reference-tracker.ts +12 -8
  473. package/src/knowledge/store.ts +64 -11
  474. package/src/monitoring/CONTEXT.md +8 -3
  475. package/src/monitoring/__tests__/skipped-lines-report.test.ts +178 -0
  476. package/src/monitoring/__tests__/traces.test.ts +57 -0
  477. package/src/monitoring/context-tracker.ts +11 -23
  478. package/src/monitoring/index.ts +1 -1
  479. package/src/monitoring/trace-analyzer.ts +21 -2
  480. package/src/monitoring/traces.ts +39 -36
  481. package/src/release/CONTEXT.md +1 -0
  482. package/src/release/__tests__/barrel.test.ts +60 -0
  483. package/src/release/index.ts +2 -13
  484. package/src/sdd/__tests__/index-generator.test.ts +71 -0
  485. package/src/sdd/index-generator.ts +9 -9
  486. package/src/test-setup/__tests__/project-fixture.test.ts +178 -0
  487. package/src/test-setup/project-fixture.ts +87 -0
  488. package/src/tools/CONTEXT.md +6 -0
  489. package/src/tools/__tests__/registry.test.ts +221 -0
  490. package/src/types/__tests__/types.test.ts +4 -5
  491. package/src/types/constraint.ts +0 -25
  492. package/src/types/passes-gate.ts +10 -30
  493. package/src/types/project-config.ts +1 -1
  494. package/src/types/session.ts +5 -5
  495. package/src/utils/__tests__/frontmatter.test.ts +206 -0
  496. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +175 -0
  497. package/src/utils/__tests__/jsonl.test.ts +187 -0
  498. package/src/utils/__tests__/package-version.test.ts +97 -0
  499. package/src/utils/exec.ts +15 -0
  500. package/src/utils/file-walk.ts +11 -2
  501. package/src/utils/frontmatter.ts +121 -0
  502. package/src/utils/jsonl.ts +167 -0
  503. package/src/utils/package-version.ts +27 -0
  504. package/dist/cli/commands/doc-freshness-check.d.ts +0 -53
  505. package/dist/cli/commands/doc-freshness-check.d.ts.map +0 -1
  506. package/dist/cli/commands/doc-freshness-check.js +0 -352
  507. package/dist/cli/commands/doc-freshness-check.js.map +0 -1
  508. package/dist/types/index.d.ts +0 -14
  509. package/dist/types/index.d.ts.map +0 -1
  510. package/dist/types/index.js +0 -39
  511. package/dist/types/index.js.map +0 -1
  512. package/src/__tests__/long-running.test.ts +0 -17
  513. package/src/__tests__/passes-gate-extension.test.ts +0 -151
  514. package/src/cli/commands/__tests__/doc-freshness-check.test.ts +0 -168
  515. package/src/cli/commands/doc-freshness-check.ts +0 -386
  516. package/src/spec/CONTEXT.md +0 -11
  517. package/src/types/index.ts +0 -47
@@ -8,7 +8,7 @@
8
8
 
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
- import * as yaml from 'js-yaml';
11
+ import { splitFrontmatter, joinFrontmatter } from '../utils/frontmatter';
12
12
  import type { KnowledgeEntry, IndexEntry, QueryFilter } from './types';
13
13
 
14
14
  const DEFAULT_DIR = '.harness/knowledge';
@@ -30,6 +30,11 @@ export interface KnowledgeStore {
30
30
  get(id: string): KnowledgeEntry | undefined;
31
31
  list(filter?: QueryFilter): KnowledgeEntry[];
32
32
  save(entry: KnowledgeEntry): void;
33
+ /**
34
+ * 批量保存(harness#107):循环内只更新内存索引,结束一次 writeIndex,
35
+ * 消除逐条 save 的 O(K·N) 全量索引重写。空批为零读写 no-op。
36
+ */
37
+ saveAll(entries: KnowledgeEntry[]): void;
33
38
  delete(id: string): boolean;
34
39
  update(id: string, partial: Partial<KnowledgeEntry>): KnowledgeEntry | undefined;
35
40
  rebuildIndex(): void;
@@ -47,6 +52,14 @@ export interface KnowledgeStore {
47
52
  */
48
53
  export class FileKnowledgeStore implements KnowledgeStore {
49
54
  private baseDir: string;
55
+ /**
56
+ * index.json 解析结果的实例级缓存(harness#106)
57
+ *
58
+ * mtimeMs+size 指纹(先例:project-config-loader.ts rawConfigCache):
59
+ * 文件未变则复用解析结果,消除 list() 一次调用内 N+1 次全量重读;
60
+ * 文件变更(含外部进程改写、删除)自动失效。
61
+ */
62
+ private indexCache: { mtimeMs: number; size: number; entries: IndexEntry[] } | undefined;
50
63
 
51
64
  constructor(config?: Partial<StoreConfig>) {
52
65
  this.baseDir = config?.baseDir || DEFAULT_CONFIG.baseDir;
@@ -78,12 +91,31 @@ export class FileKnowledgeStore implements KnowledgeStore {
78
91
 
79
92
  save(entry: KnowledgeEntry): void {
80
93
  const filePath = this.entryPath(entry);
81
- const frontmatter = this.toFrontmatter(entry);
82
- const content = `---\n${frontmatter}---\n\n${entry.content}`;
94
+ const content = joinFrontmatter(this.toFrontmatter(entry), entry.content);
83
95
  fs.writeFileSync(filePath, content, 'utf-8');
84
96
  this.updateIndexEntry(entry);
85
97
  }
86
98
 
99
+ saveAll(entries: KnowledgeEntry[]): void {
100
+ if (entries.length === 0) return;
101
+ const index = this.readIndex();
102
+ const position = new Map(index.map((e, i) => [e.id, i]));
103
+ for (const entry of entries) {
104
+ const filePath = this.entryPath(entry);
105
+ const content = joinFrontmatter(this.toFrontmatter(entry), entry.content);
106
+ fs.writeFileSync(filePath, content, 'utf-8');
107
+ const indexEntry = this.toIndexEntry(entry);
108
+ const idx = position.get(entry.id);
109
+ if (idx !== undefined) {
110
+ index[idx] = indexEntry;
111
+ } else {
112
+ position.set(entry.id, index.length);
113
+ index.push(indexEntry);
114
+ }
115
+ }
116
+ this.writeIndex(index);
117
+ }
118
+
87
119
  delete(id: string): boolean {
88
120
  const filePath = this.findFile(id);
89
121
  if (!filePath) return false;
@@ -179,11 +211,16 @@ export class FileKnowledgeStore implements KnowledgeStore {
179
211
  }
180
212
 
181
213
  private parseFile(raw: string, filePath: string): KnowledgeEntry | undefined {
182
- const match = raw.match(/^---\n([\s\S]*?)\n---\n\n?([\s\S]*)$/);
183
- if (!match) return undefined;
214
+ const fm = splitFrontmatter(raw);
215
+ if (fm.state === 'malformed') {
216
+ // 统一口径(harness#89):损坏的 frontmatter 必须显式上报,不再静默丢条目
217
+ console.error(`[harness] 知识条目 frontmatter ${fm.reason}(${fm.detail}),已跳过 ${filePath}`);
218
+ return undefined;
219
+ }
220
+ if (fm.state === 'absent') return undefined;
184
221
 
185
- const meta = yaml.load(match[1]) as Record<string, unknown>;
186
- const content = match[2];
222
+ const meta = fm.meta;
223
+ const content = fm.body;
187
224
 
188
225
  return {
189
226
  id: meta.id as string || path.basename(filePath, '.md'),
@@ -209,7 +246,8 @@ export class FileKnowledgeStore implements KnowledgeStore {
209
246
  };
210
247
  }
211
248
 
212
- private toFrontmatter(entry: KnowledgeEntry): string {
249
+ /** canonical 字段序是 store 的私有策略(harness#89 裁决 3),包裹格式交给 joinFrontmatter */
250
+ private toFrontmatter(entry: KnowledgeEntry): Record<string, unknown> {
213
251
  const meta: Record<string, unknown> = {
214
252
  id: entry.id,
215
253
  type: entry.type,
@@ -231,7 +269,7 @@ export class FileKnowledgeStore implements KnowledgeStore {
231
269
  if (entry.decayAt) meta.decayAt = entry.decayAt;
232
270
  if (entry.fullContentPath) meta.fullContentPath = entry.fullContentPath;
233
271
  if (entry.skillId) meta.skillId = entry.skillId;
234
- return yaml.dump(meta, { lineWidth: 120 });
272
+ return meta;
235
273
  }
236
274
 
237
275
  private toIndexEntry(entry: KnowledgeEntry): IndexEntry {
@@ -348,10 +386,25 @@ export class FileKnowledgeStore implements KnowledgeStore {
348
386
 
349
387
  readIndex(): IndexEntry[] {
350
388
  const indexPath = path.join(this.baseDir, INDEX_FILE);
351
- if (!fs.existsSync(indexPath)) return [];
389
+ let stat: fs.Stats | undefined;
390
+ try {
391
+ stat = fs.statSync(indexPath);
392
+ } catch {
393
+ stat = undefined;
394
+ }
395
+ if (!stat) {
396
+ this.indexCache = undefined;
397
+ return [];
398
+ }
399
+ const cached = this.indexCache;
400
+ if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
401
+ return cached.entries;
402
+ }
352
403
  try {
353
404
  const raw = fs.readFileSync(indexPath, 'utf-8');
354
- return JSON.parse(raw) as IndexEntry[];
405
+ const entries = JSON.parse(raw) as IndexEntry[];
406
+ this.indexCache = { mtimeMs: stat.mtimeMs, size: stat.size, entries };
407
+ return entries;
355
408
  } catch {
356
409
  return [];
357
410
  }
@@ -4,19 +4,24 @@
4
4
  运行时监控:Execution Trace(收集/分析)+ 上下文使用追踪。
5
5
 
6
6
  ## 核心导出
7
- - `TraceCollector` — 执行追踪收集(append-only JSONL,`.harness/logs/traces.log`)
8
- - `TraceAnalyzer` — 追踪统计分析 + 异常检测(共用 analyzer-base 纯函数)
7
+ - `TraceCollector` — 执行追踪收集(append-only JSONL,`.harness/logs/traces.log`)。读入口两个:`readReport(filter?)` 报告入口(返回 `{ traces, skippedLines }`,harness#100)与 `read()/readRecent()/readByConstraint()` 兼容包装(#82 裁决 4 冻结的签名,丢计数)
8
+ - `TraceAnalyzer` — 追踪统计分析 + 异常检测(共用 analyzer-base 纯函数)。`analyzeRecentReport(hours)` 带坏行数,`analyzeRecent()/analyzeConstraint()` 仍返回 `TraceSummary[]`
9
9
  - `ContextTracker` — LLM 调用上下文使用快照记录
10
10
 
11
11
  ## 依赖关系
12
12
  - 依赖 `src/types/trace` ExecutionTrace 类型
13
- - 被 `src/core/constraints/checker`(惰性接线 getTraceCollector,ADR-0003)与 `src/cli/` 命令消费
13
+ - 消费者:`src/cli/commands/{check,report}` `src/hooks/bootstrap`——组合根经 `getTraceCollector()` 把收集器注入 `ConstraintChecker` 构造参数(harness#88);`status` 另按 projectPath 自建收集器/分析器
14
+ - core 不再依赖本模块(方向由 eslint no-restricted-imports 锁死,`src/__tests__/layering.test.ts` 守卫)
14
15
 
15
16
  ## 约定
16
17
  - Trace 文件存储在 .harness/ 目录(不提交)
17
18
  - 追加写入,单行 JSON,自动滚动
18
19
  - 零 Token 成本:不调用 LLM
20
+ - **凡以 `skip` 策略读 JSONL,坏行计数必须到达该消费面的用户可见输出,或在该调用点显式记名豁免并写明理由(harness#100)**——静默吞掉「数据不全」不是一种可选项。承载方式纯增量:新报告入口带计数,旧入口退化为丢计数的薄包装(#82 裁决 4 的兼容约束继续成立),不另立第二份计数概念、不把 `JsonlReadResult` 提升进包根(ADR-0003)
21
+ - 机器可检:`src/utils/__tests__/jsonl-skip-disposition.test.ts` 三道闸——每个 `readJsonl` 调用点必须判得出策略(判不出即形状逃逸,失败)、skip 读点集合冻结(新增未声明即失败,站点消失却不删条目也失败)、每个读点上方 6 行内有 `计数去向:` 声明且邻居声明不得顶替
22
+ - 三类去向(逐条对得上代码):**报告体内部的降级位**随报告体走 stdout——`constraints report` 的坏行提示紧挨既有的 `traceFileExists` 说明;**不挤动既有输出的诊断告知**走 stderr——`status`(`logError`,stdout 的 `记录数` 口径与字节不变)、`failure list`(#96 定稿的 `console.error`)、`constraints retire`(交互与 `--yes` 直达两条路径各自告知);**结构化面用字段**——`constraints report --json` 的 `skippedLines`、`--export` markdown 的警示行、studio 端点响应体(配套票 studio#451)
19
23
 
20
24
  ## 注意事项
21
25
  - 追踪数据供 `harness constraints report` 统计与退役候选诊断消费(观测用途,不做自动降级)
26
+ - **坏行计数是文件级口径**:`tail`/`head` 截断与时间窗过滤都发生在 parse 之前/之上,坏行既没有 timestamp 也没有 constraintId 可归窗,因此 `readReport()`/`analyzeRecentReport()` 返回的 `skippedLines` 不受过滤影响。消费方**无法**用「原始行数 − 统计条数」反推(两者分不开被窗过滤掉的合法行与损坏行),必须读 harness 透传的计数(#100 写死这条)
22
27
  - ADR-0003 起移除 recordBypass 链(绕过观测后续单独立项)与断链的诊断/性能/知识医生文件
@@ -0,0 +1,178 @@
1
+ /**
2
+ * 坏行计数跨层透传(harness#100,承接 #82 裁决 4 的范围外项)
3
+ *
4
+ * #82 统一了 JSONL 读协议并让读「不抛 + 返回部分结果」,但计数在 TraceCollector
5
+ * 这一层被 `read()` 的兼容签名吞掉:studio 端点拿到部分结果却零提示。
6
+ * 本套件钉住收口后的形状——
7
+ * - 新报告入口 `readReport()` / `analyzeRecentReport()` 把计数带到消费面
8
+ * - 旧入口 `read()` / `readRecent()` / `readByConstraint()` / `getStats()` /
9
+ * `analyzeRecent()` / `analyzeConstraint()` 的签名与返回类型逐条不变(编译期断言)
10
+ * - 跨层对账:module 计数 == collector 报告入口计数 == analyzer 报告入口计数
11
+ * - 时间窗只过滤合法行(坏行无 timestamp 可归窗),计数始终是文件级口径
12
+ */
13
+
14
+ import * as path from 'path';
15
+ import { createProjectFixture } from '../../test-setup/project-fixture';
16
+ import { readJsonl } from '../../utils/jsonl';
17
+ import { TraceCollector } from '../traces';
18
+ import { TraceAnalyzer } from '../trace-analyzer';
19
+ import type { ExecutionTrace, TraceSummary } from '../../types/trace';
20
+
21
+ const BAD_LINE_1 = '{"constraintId":"broken"';
22
+ const BAD_LINE_2 = 'not json at all';
23
+ const BAD_LINE_3 = '{"constraintId":"half"';
24
+
25
+ const NOW = 1700000000000;
26
+ /** 覆盖全部 fixture 时间戳的窗口(100 年),语义 = "不按时间过滤" */
27
+ const ALL_HOURS = 24 * 365 * 100;
28
+
29
+ function validLine(constraintId: string, timestamp: number, result: ExecutionTrace['result'] = 'pass'): string {
30
+ return JSON.stringify({ constraintId, level: 'iron_law', timestamp, result });
31
+ }
32
+
33
+ /** 落一个只有 traces.log 的临时工程根 */
34
+ function fixtureWith(lines: string[], name: string): string {
35
+ return createProjectFixture({
36
+ name,
37
+ files: { [path.join('.harness', 'logs', 'traces.log')]: lines.join('\n') + '\n' },
38
+ });
39
+ }
40
+
41
+ function collectorFor(root: string): TraceCollector {
42
+ return new TraceCollector({ traceFile: path.join(root, '.harness', 'logs', 'traces.log') });
43
+ }
44
+
45
+ describe('TraceCollector.readReport / TraceAnalyzer.analyzeRecentReport(harness#100)', () => {
46
+ describe.each([
47
+ { badCount: 1, badLines: [BAD_LINE_1] },
48
+ { badCount: 3, badLines: [BAD_LINE_1, BAD_LINE_2, BAD_LINE_3] },
49
+ ])('fixture 塞 $badCount 行坏数据', ({ badLines }) => {
50
+ const lines = [
51
+ validLine('alpha', NOW),
52
+ badLines[0],
53
+ validLine('beta', NOW + 1000, 'fail'),
54
+ ...(badLines.length > 1 ? [badLines[1], badLines[2]] : []),
55
+ validLine('alpha', NOW + 2000),
56
+ ];
57
+ const expectedSkipped = badLines.length;
58
+
59
+ it('跨层对账:module 计数 == collector 报告入口计数 == analyzer 报告入口计数', () => {
60
+ const root = fixtureWith(lines, 'skiplines-cross-layer');
61
+ const collector = collectorFor(root);
62
+ const analyzer = new TraceAnalyzer(collector);
63
+ const traceFile = path.join(root, '.harness', 'logs', 'traces.log');
64
+
65
+ const moduleResult = readJsonl<ExecutionTrace>(traceFile, 'skip');
66
+ const collectorReport = collector.readReport();
67
+ const analyzerReport = analyzer.analyzeRecentReport(ALL_HOURS);
68
+
69
+ expect(moduleResult.skippedLines).toBe(expectedSkipped);
70
+ expect(collectorReport.skippedLines).toBe(moduleResult.skippedLines);
71
+ expect(analyzerReport.skippedLines).toBe(moduleResult.skippedLines);
72
+ // 计数只有一份正本:合法记录集合在三层完全一致
73
+ expect(collectorReport.traces).toEqual(moduleResult.records);
74
+ expect(collectorReport.traces.map(t => t.constraintId)).toEqual(['alpha', 'beta', 'alpha']);
75
+ });
76
+
77
+ it('报告入口带过滤条件时,计数不受过滤影响(坏行无法归窗)', () => {
78
+ const root = fixtureWith(lines, 'skiplines-filtered');
79
+
80
+ const filtered = collectorFor(root).readReport({ constraintId: 'beta' });
81
+
82
+ expect(filtered.traces.map(t => t.constraintId)).toEqual(['beta']);
83
+ expect(filtered.skippedLines).toBe(expectedSkipped);
84
+ });
85
+
86
+ it('旧入口逐条不变:read()/readRecent()/readByConstraint() 仍只返回合法记录', () => {
87
+ const root = fixtureWith(lines, 'skiplines-compat');
88
+ const collector = collectorFor(root);
89
+
90
+ expect(collector.read().map(t => t.constraintId)).toEqual(['alpha', 'beta', 'alpha']);
91
+ expect(collector.readByConstraint('alpha')).toHaveLength(2);
92
+ expect(collector.readRecent(ALL_HOURS)).toHaveLength(3);
93
+ });
94
+
95
+ it('getStats().totalLines 保持原始行数口径(合法 + 坏行),与改动前一致', () => {
96
+ const root = fixtureWith(lines, 'skiplines-stats');
97
+
98
+ expect(collectorFor(root).getStats().totalLines).toBe(lines.length);
99
+ });
100
+ });
101
+
102
+ it('无损坏时报告入口计数为 0(零噪声前提)', () => {
103
+ const root = fixtureWith([validLine('alpha', NOW), validLine('beta', NOW + 1)], 'skiplines-clean');
104
+ const collector = collectorFor(root);
105
+
106
+ expect(collector.readReport()).toEqual({ traces: collector.read(), skippedLines: 0 });
107
+ expect(new TraceAnalyzer(collector).analyzeRecentReport(1).skippedLines).toBe(0);
108
+ });
109
+
110
+ it('缺 trace 文件时报告入口返回空记录 + 0,不抛', () => {
111
+ const collector = collectorFor(createProjectFixture({ name: 'skiplines-missing-file' }));
112
+
113
+ expect(collector.readReport()).toEqual({ traces: [], skippedLines: 0 });
114
+ });
115
+
116
+ it('时间窗内被丢掉的合法行不减计数:文件级口径(studio 无法自算的根因)', () => {
117
+ const lines = [
118
+ validLine('old', NOW - 10 * 3600 * 1000),
119
+ BAD_LINE_1,
120
+ BAD_LINE_2,
121
+ validLine('new', Date.now()),
122
+ ];
123
+ const root = fixtureWith(lines, 'skiplines-window');
124
+ const analyzer = new TraceAnalyzer(collectorFor(root));
125
+
126
+ const report = analyzer.analyzeRecentReport(1);
127
+
128
+ // 1 小时窗只剩 1 条合法 trace,但两条坏行仍要告知
129
+ expect(report.summaries.map(s => s.constraintId)).toEqual(['new']);
130
+ expect(report.summaries.reduce((n, s) => n + s.totalChecks, 0)).toBe(1);
131
+ expect(report.skippedLines).toBe(2);
132
+ });
133
+
134
+ describe('签名与返回类型冻结(#82 裁决 4 兼容约束,编译期固定)', () => {
135
+ it('旧入口返回类型逐条不变,新入口就地返回形状', () => {
136
+ const root = fixtureWith([validLine('alpha', NOW), BAD_LINE_1], 'skiplines-signature');
137
+ const collector = collectorFor(root);
138
+ const analyzer = new TraceAnalyzer(collector);
139
+
140
+ // 旧入口:返回类型逐条不变
141
+ const traces: ExecutionTrace[] = collector.read();
142
+ const recent: ExecutionTrace[] = collector.readRecent(ALL_HOURS);
143
+ const byConstraint: ExecutionTrace[] = collector.readByConstraint('alpha');
144
+ const stats: {
145
+ fileExists: boolean;
146
+ fileSize: number;
147
+ totalLines: number;
148
+ oldestTrace?: number;
149
+ newestTrace?: number;
150
+ } = collector.getStats();
151
+ const summaries: TraceSummary[] = analyzer.analyzeRecent(ALL_HOURS);
152
+ const constraintSummaries: TraceSummary[] = analyzer.analyzeConstraint('alpha');
153
+ expect(traces).toHaveLength(1);
154
+ expect(recent).toHaveLength(1);
155
+ expect(byConstraint).toHaveLength(1);
156
+ expect(stats.totalLines).toBe(2);
157
+ expect(summaries).toHaveLength(1);
158
+ expect(constraintSummaries).toHaveLength(1);
159
+
160
+ // 新入口:就地返回形状(不另立第二份计数概念,也不经包根导出的类型)
161
+ const collectorReport: { traces: ExecutionTrace[]; skippedLines: number } = collector.readReport();
162
+ const analyzerReport: { summaries: TraceSummary[]; skippedLines: number } = analyzer.analyzeRecentReport(
163
+ ALL_HOURS
164
+ );
165
+ expect(collectorReport.traces).toHaveLength(1);
166
+ expect(analyzerReport.summaries).toHaveLength(1);
167
+
168
+ // 兼容签名不会漂成报告对象:赋给报告类型是编译期错误(@ts-expect-error 消化),
169
+ // 运行期返回的仍是数组
170
+ // @ts-expect-error read() 仍返回 ExecutionTrace[],不含坏行数
171
+ const readNoCount: { skippedLines: number } = collector.read();
172
+ // @ts-expect-error analyzeRecent() 仍返回 TraceSummary[],不含坏行数
173
+ const analyzeNoCount: { skippedLines: number } = analyzer.analyzeRecent(ALL_HOURS);
174
+ expect(Array.isArray(readNoCount)).toBe(true);
175
+ expect(Array.isArray(analyzeNoCount)).toBe(true);
176
+ });
177
+ });
178
+ });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { TraceCollector, getTraceCollector, configureTraceCollector } from '../traces';
6
+ import type { ExecutionTrace, TraceFilter } from '../../types/trace';
6
7
  import * as fs from 'fs';
7
8
 
8
9
  // Mock fs
@@ -161,6 +162,62 @@ describe('TraceCollector', () => {
161
162
  expect(stats.fileExists).toBe(true);
162
163
  });
163
164
  });
165
+
166
+ describe('坏行容错(harness#82 裁决 4:原「抛」改 skip,签名与返回类型不变)', () => {
167
+ it('read() 单行损坏只丢该行,合法 trace 照常返回', () => {
168
+ mockFs.readFileSync.mockReturnValueOnce(
169
+ '{"constraintId":"a","level":"iron_law","timestamp":100,"result":"pass"}\n' +
170
+ '{"constraintId":"b","broken"\n' +
171
+ '{"constraintId":"c","level":"guideline","timestamp":200,"result":"fail"}\n'
172
+ );
173
+ const traces = collector.read();
174
+ expect(traces.map(t => t.constraintId)).toEqual(['a', 'c']);
175
+ });
176
+
177
+ it('read() 坏行不阻断过滤', () => {
178
+ mockFs.readFileSync.mockReturnValueOnce(
179
+ '{"constraintId":"a","level":"iron_law","timestamp":100,"result":"pass"}\n' +
180
+ 'not-json\n' +
181
+ '{"constraintId":"a","level":"iron_law","timestamp":200,"result":"fail"}\n'
182
+ );
183
+ const traces = collector.read({ constraintId: 'a' });
184
+ expect(traces).toHaveLength(2);
185
+ });
186
+
187
+ it('getStats() 首末行坏时不抛:totalLines 保持原始行数,时间戳取合法记录', () => {
188
+ mockFs.readFileSync.mockReturnValueOnce(
189
+ 'not-json\n' +
190
+ '{"constraintId":"a","level":"iron_law","timestamp":100,"result":"pass"}\n' +
191
+ '{"constraintId":"b","level":"guideline","timestamp":200,"result":"fail"}\n' +
192
+ 'also-bad\n'
193
+ );
194
+ mockFs.statSync.mockReturnValueOnce({ size: 2048 } as any);
195
+ const stats = collector.getStats();
196
+ expect(stats.fileExists).toBe(true);
197
+ expect(stats.totalLines).toBe(4);
198
+ expect(stats.oldestTrace).toBe(100);
199
+ expect(stats.newestTrace).toBe(200);
200
+ });
201
+
202
+ it('四个读方法签名与返回类型编译期固定(studio analyzeRecent 消费面零改动证据)', () => {
203
+ const read: (filter?: TraceFilter) => ExecutionTrace[] = collector.read.bind(collector);
204
+ const readRecent: (hours: number) => ExecutionTrace[] = collector.readRecent.bind(collector);
205
+ const readByConstraint: (constraintId: string) => ExecutionTrace[] =
206
+ collector.readByConstraint.bind(collector);
207
+ const getStats: () => {
208
+ fileExists: boolean;
209
+ fileSize: number;
210
+ totalLines: number;
211
+ oldestTrace?: number;
212
+ newestTrace?: number;
213
+ } = collector.getStats.bind(collector);
214
+
215
+ expect(read).toBeDefined();
216
+ expect(readRecent).toBeDefined();
217
+ expect(readByConstraint).toBeDefined();
218
+ expect(getStats).toBeDefined();
219
+ });
220
+ });
164
221
  });
165
222
 
166
223
  describe('getTraceCollector()', () => {
@@ -7,6 +7,7 @@
7
7
 
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
+ import { readJsonl, appendJsonl } from '../utils/jsonl';
10
11
  import type { ContextUsageSnapshot } from '../context/types';
11
12
 
12
13
  export interface ContextAverages {
@@ -28,13 +29,7 @@ export class ContextTracker {
28
29
  * 记录上下文使用快照
29
30
  */
30
31
  record(snapshot: ContextUsageSnapshot): void {
31
- const dir = path.dirname(this.logPath);
32
-
33
32
  try {
34
- if (!fs.existsSync(dir)) {
35
- fs.mkdirSync(dir, { recursive: true });
36
- }
37
-
38
33
  // 检查文件大小,超过限制时轮转
39
34
  if (fs.existsSync(this.logPath)) {
40
35
  const stat = fs.statSync(this.logPath);
@@ -43,8 +38,8 @@ export class ContextTracker {
43
38
  }
44
39
  }
45
40
 
46
- const line = JSON.stringify(snapshot) + '\n';
47
- fs.appendFileSync(this.logPath, line, 'utf-8');
41
+ // 写链收口:ensureDir + append(harness#82)
42
+ appendJsonl(this.logPath, snapshot);
48
43
  } catch {
49
44
  // 静默失败,不影响主流程
50
45
  }
@@ -55,21 +50,14 @@ export class ContextTracker {
55
50
  */
56
51
  getRecent(n: number): ContextUsageSnapshot[] {
57
52
  try {
58
- if (!fs.existsSync(this.logPath)) {
59
- return [];
60
- }
61
-
62
- const content = fs.readFileSync(this.logPath, 'utf-8');
63
- const lines = content.trim().split('\n').filter(Boolean);
64
- const recent = lines.slice(-n);
65
-
66
- return recent.map(line => {
67
- try {
68
- return JSON.parse(line) as ContextUsageSnapshot;
69
- } catch {
70
- return null;
71
- }
72
- }).filter((s): s is ContextUsageSnapshot => s !== null);
53
+ // 坏行策略:skip(原逐行 null-filter 语义不变);只 parse 最近 n 行;
54
+ // !== null 沿用原过滤口径:parse 成功但为 null 的记录不返回
55
+ // 计数去向:豁免(harness#100)——尾部快照读取(tail 在 parse 前截断,坏行占尾部槽位),
56
+ // 坏行只让返回条数少于 n;消费方是同模块 getAverages()/generateReport()(无 CLI 接线),
57
+ // 给它们加计数行属新能力,不在本票
58
+ return readJsonl<ContextUsageSnapshot>(this.logPath, 'skip', { tail: n })
59
+ .records
60
+ .filter((s): s is ContextUsageSnapshot => s !== null);
73
61
  } catch {
74
62
  return [];
75
63
  }
@@ -9,7 +9,7 @@
9
9
  * - ContextTracker:LLM 调用上下文快照记录
10
10
  */
11
11
 
12
- export { TraceCollector, getTraceCollector, configureTraceCollector, DEFAULT_TRACE_FILE } from './traces';
12
+ export { TraceCollector, getTraceCollector, configureTraceCollector } from './traces';
13
13
  export { TraceAnalyzer, createAnalyzer } from './trace-analyzer';
14
14
  export { ContextTracker } from './context-tracker';
15
15
  export type { ContextAverages } from './context-tracker';
@@ -109,16 +109,35 @@ export class TraceAnalyzer {
109
109
  return summaries;
110
110
  }
111
111
 
112
+ /**
113
+ * 分析最近 N 小时的 traces,并带出坏行计数(harness#100 报告入口)
114
+ *
115
+ * `skippedLines` 是**文件级口径**:坏行没有 timestamp 归不进时间窗,
116
+ * 既不能按窗丢弃、也无法由消费方从 summaries 反推(客户端分不开
117
+ * 「被窗过滤掉的合法行」与「损坏行」)。
118
+ */
119
+ analyzeRecentReport(hours: number): { summaries: TraceSummary[]; skippedLines: number } {
120
+ const start = Date.now() - hours * 3600 * 1000;
121
+ const { traces, skippedLines } = this.collector.readReport({
122
+ timeRange: { start, end: Date.now() },
123
+ });
124
+
125
+ return { summaries: this.summarize(traces), skippedLines };
126
+ }
127
+
112
128
  /**
113
129
  * 分析最近 N 小时的 traces
130
+ *
131
+ * 兼容签名(#82 裁决 4 冻结):丢坏行计数,要计数用 `analyzeRecentReport()`。
114
132
  */
115
133
  analyzeRecent(hours: number): TraceSummary[] {
116
- const traces = this.collector.readRecent(hours);
117
- return this.summarize(traces);
134
+ return this.analyzeRecentReport(hours).summaries;
118
135
  }
119
136
 
120
137
  /**
121
138
  * 分析特定约束
139
+ *
140
+ * 兼容签名同上:坏行计数由 `collector.readReport({ constraintId })` 承载。
122
141
  */
123
142
  analyzeConstraint(constraintId: string): TraceSummary[] {
124
143
  const traces = this.collector.readByConstraint(constraintId);
@@ -11,6 +11,7 @@
11
11
 
12
12
  import * as fs from 'fs';
13
13
  import * as path from 'path';
14
+ import { readJsonl, readJsonlEnds, appendJsonl } from '../utils/jsonl';
14
15
  import {
15
16
  DEFAULT_TRACE_FILE,
16
17
  type ExecutionTrace,
@@ -18,9 +19,6 @@ import {
18
19
  type TraceCollectorConfig,
19
20
  } from '../types/trace';
20
21
 
21
- // 路径常量再导出,保持既有从 monitoring/traces 导入的用法不破
22
- export { DEFAULT_TRACE_FILE };
23
-
24
22
  /**
25
23
  * 默认配置
26
24
  */
@@ -78,9 +76,8 @@ export class TraceCollector {
78
76
  // 检查文件大小,必要时滚动
79
77
  this.checkFileSize();
80
78
 
81
- // 追加写入
82
- const line = JSON.stringify(trace);
83
- fs.appendFileSync(this.traceFile, line + '\n', 'utf-8');
79
+ // 追加写入(写链收口:ensureDir + append,harness#82)
80
+ appendJsonl(this.traceFile, trace);
84
81
  }
85
82
 
86
83
  /**
@@ -117,6 +114,24 @@ export class TraceCollector {
117
114
  });
118
115
  }
119
116
 
117
+ /**
118
+ * 批量读取 traces 并带出坏行计数(harness#100 报告入口)
119
+ *
120
+ * 支持过滤条件(同 `read()`):时间范围 / 约束 ID / 结果类型 / 项目路径 / 会话 ID。
121
+ *
122
+ * **计数是文件级口径**:过滤只作用于合法记录——坏行没有 timestamp/约束 ID 可归窗,
123
+ * 丢不得。这正是消费方(studio 端点)无法自己算出坏行数的原因。
124
+ */
125
+ readReport(filter?: TraceFilter): { traces: ExecutionTrace[]; skippedLines: number } {
126
+ // 坏行策略:skip(harness#82 裁决 4:原裸 parse「抛」改 skip);
127
+ // 计数去向:透传——skippedLines 随本方法返回,消费面是 TraceAnalyzer.analyzeRecentReport()
128
+ // (结构化)与包外调用方(studio 端点经配套票 #451 读它,见 studio#451);
129
+ // `harness status` 另按 projectPath 直读 jsonl 正本,不经过本类
130
+ const { records, skippedLines } = readJsonl<ExecutionTrace>(this.traceFile, 'skip');
131
+
132
+ return { traces: filter ? this.applyFilter(records, filter) : records, skippedLines };
133
+ }
134
+
120
135
  /**
121
136
  * 批量读取 traces
122
137
  *
@@ -124,27 +139,19 @@ export class TraceCollector {
124
139
  * - 时间范围
125
140
  * - 约束 ID
126
141
  * - 结果类型
142
+ *
143
+ * 兼容签名(#82 裁决 4 冻结):只返回记录数组、丢掉了坏行计数。
144
+ * 需要告知用户「数据不全」的消费方走 `readReport()`。
127
145
  */
128
146
  read(filter?: TraceFilter): ExecutionTrace[] {
129
- if (!fs.existsSync(this.traceFile)) {
130
- return [];
131
- }
132
-
133
- const content = fs.readFileSync(this.traceFile, 'utf-8');
134
- const lines = content.split('\n').filter(l => l.trim());
135
-
136
- let traces = lines.map(line => JSON.parse(line) as ExecutionTrace);
137
-
138
- // 应用过滤条件
139
- if (filter) {
140
- traces = this.applyFilter(traces, filter);
141
- }
142
-
143
- return traces;
147
+ return this.readReport(filter).traces;
144
148
  }
145
149
 
146
150
  /**
147
151
  * 读取最近 N 小时的 traces
152
+ *
153
+ * 经 `read()` 的兼容包装,丢计数;要带计数用 `readReport({ timeRange })`
154
+ * 或 `TraceAnalyzer.analyzeRecentReport()`。
148
155
  */
149
156
  readRecent(hours: number): ExecutionTrace[] {
150
157
  const start = Date.now() - hours * 3600 * 1000;
@@ -153,6 +160,8 @@ export class TraceCollector {
153
160
 
154
161
  /**
155
162
  * 读取特定约束的 traces
163
+ *
164
+ * 同 `readRecent()`:兼容包装,计数走 `readReport({ constraintId })`。
156
165
  */
157
166
  readByConstraint(constraintId: string): ExecutionTrace[] {
158
167
  return this.read({ constraintId });
@@ -277,25 +286,19 @@ export class TraceCollector {
277
286
  }
278
287
 
279
288
  const stats = fs.statSync(this.traceFile);
280
- const content = fs.readFileSync(this.traceFile, 'utf-8');
281
- const lines = content.split('\n').filter(l => l.trim());
282
-
283
- let oldest: number | undefined;
284
- let newest: number | undefined;
285
-
286
- if (lines.length > 0) {
287
- const firstTrace = JSON.parse(lines[0]) as ExecutionTrace;
288
- const lastTrace = JSON.parse(lines[lines.length - 1]) as ExecutionTrace;
289
- oldest = firstTrace.timestamp;
290
- newest = lastTrace.timestamp;
291
- }
289
+ // 坏行策略:skip(同 read,裁决 4);首/末时间戳取首/末条合法记录;
290
+ // totalLines 保持原始非空行数口径(合法 + 坏行)
291
+ // 计数去向:并进 totalLines 的原始行数口径(裁决 4 冻结,改口径属行为变更);
292
+ // 本方法只出统计不出告警,要单列坏行数的消费方走 readReport()
293
+ // 实现经 readJsonlEnds parse 两端(harness#114):签名与口径逐字节不变
294
+ const { totalLines, first, last } = readJsonlEnds<ExecutionTrace>(this.traceFile, 'skip');
292
295
 
293
296
  return {
294
297
  fileExists: true,
295
298
  fileSize: stats.size,
296
- totalLines: lines.length,
297
- oldestTrace: oldest,
298
- newestTrace: newest,
299
+ totalLines,
300
+ oldestTrace: first?.timestamp,
301
+ newestTrace: last?.timestamp,
299
302
  };
300
303
  }
301
304
  }
@@ -4,6 +4,7 @@
4
4
  发布物完整性自检:harness 自己声明并校验自己发布了什么。关键发布物清单的单一来源,替代外部(studio publishPackage)与本仓 release 命令各自的 dist 文件硬编码(harness#77,#75 N4 收编)。
5
5
 
6
6
  ## 核心导出
7
+ 下列符号都由 `integrity.ts` 就地导出;`index.ts` barrel 只再导出标「公开 API」的那一组,其余属模块内部 seam——仓内消费方直接 `from './integrity'`,导出面由 `__tests__/barrel.test.ts` 守卫(harness#92)。
7
8
  - `deriveCriticalArtifacts(manifest)`(`integrity.ts`)——从包声明面(package.json `main`/`exports`/`bin`)推导脚本入口清单(.js/.cjs/.mjs/.d.ts,去 `./` 前缀、剔除非脚本叶子、去重排序);纯函数
8
9
  - `EXTRA_CRITICAL_ARTIFACTS`——声明面推导之外的运行时关键发布物(bin/harness.js 引导期 require 的命令/门禁定义表、`dist/tools/definitions` 数据目录);与其描述的文件同仓同评审,删改须同步
9
10
  - `resolvePackageRoot(fromDir?)`——从模块位置向上按包名解析本包根(源码仓与 node_modules 安装态均可达)