@dommaker/harness 1.7.0 → 1.8.1

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 (563) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/bin/harness.js +17 -1
  3. package/dist/agents/index.d.ts +6 -2
  4. package/dist/agents/index.d.ts.map +1 -1
  5. package/dist/agents/index.js +6 -16
  6. package/dist/agents/index.js.map +1 -1
  7. package/dist/cli/commands/__tests__/gate-decision.d.ts +18 -0
  8. package/dist/cli/commands/__tests__/gate-decision.d.ts.map +1 -0
  9. package/dist/cli/commands/__tests__/gate-decision.js +26 -0
  10. package/dist/cli/commands/__tests__/gate-decision.js.map +1 -0
  11. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  12. package/dist/cli/commands/acceptance.js +17 -28
  13. package/dist/cli/commands/acceptance.js.map +1 -1
  14. package/dist/cli/commands/check.d.ts +19 -3
  15. package/dist/cli/commands/check.d.ts.map +1 -1
  16. package/dist/cli/commands/check.js +36 -22
  17. package/dist/cli/commands/check.js.map +1 -1
  18. package/dist/cli/commands/command.d.ts +0 -1
  19. package/dist/cli/commands/command.d.ts.map +1 -1
  20. package/dist/cli/commands/command.js +8 -6
  21. package/dist/cli/commands/command.js.map +1 -1
  22. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  23. package/dist/cli/commands/constraints-retire.js +36 -57
  24. package/dist/cli/commands/constraints-retire.js.map +1 -1
  25. package/dist/cli/commands/contract.d.ts.map +1 -1
  26. package/dist/cli/commands/contract.js +36 -42
  27. package/dist/cli/commands/contract.js.map +1 -1
  28. package/dist/cli/commands/definitions.d.ts +2 -0
  29. package/dist/cli/commands/definitions.d.ts.map +1 -1
  30. package/dist/cli/commands/definitions.js +11 -6
  31. package/dist/cli/commands/definitions.js.map +1 -1
  32. package/dist/cli/commands/failure.d.ts.map +1 -1
  33. package/dist/cli/commands/failure.js +16 -2
  34. package/dist/cli/commands/failure.js.map +1 -1
  35. package/dist/cli/commands/init.d.ts +14 -8
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +172 -364
  38. package/dist/cli/commands/init.js.map +1 -1
  39. package/dist/cli/commands/knowledge-view.d.ts +90 -0
  40. package/dist/cli/commands/knowledge-view.d.ts.map +1 -0
  41. package/dist/cli/commands/knowledge-view.js +198 -0
  42. package/dist/cli/commands/knowledge-view.js.map +1 -0
  43. package/dist/cli/commands/knowledge.d.ts +137 -52
  44. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  45. package/dist/cli/commands/knowledge.js +534 -400
  46. package/dist/cli/commands/knowledge.js.map +1 -1
  47. package/dist/cli/commands/passes-gate.d.ts +4 -1
  48. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  49. package/dist/cli/commands/passes-gate.js +11 -2
  50. package/dist/cli/commands/passes-gate.js.map +1 -1
  51. package/dist/cli/commands/performance.d.ts.map +1 -1
  52. package/dist/cli/commands/performance.js +19 -31
  53. package/dist/cli/commands/performance.js.map +1 -1
  54. package/dist/cli/commands/report.d.ts.map +1 -1
  55. package/dist/cli/commands/report.js +2 -1
  56. package/dist/cli/commands/report.js.map +1 -1
  57. package/dist/cli/commands/review.d.ts.map +1 -1
  58. package/dist/cli/commands/review.js +13 -25
  59. package/dist/cli/commands/review.js.map +1 -1
  60. package/dist/cli/commands/scaffold-templates.d.ts +73 -0
  61. package/dist/cli/commands/scaffold-templates.d.ts.map +1 -0
  62. package/dist/cli/commands/scaffold-templates.js +424 -0
  63. package/dist/cli/commands/scaffold-templates.js.map +1 -0
  64. package/dist/cli/commands/scaffold.d.ts +105 -0
  65. package/dist/cli/commands/scaffold.d.ts.map +1 -0
  66. package/dist/cli/commands/scaffold.js +263 -0
  67. package/dist/cli/commands/scaffold.js.map +1 -0
  68. package/dist/cli/commands/security.d.ts.map +1 -1
  69. package/dist/cli/commands/security.js +22 -24
  70. package/dist/cli/commands/security.js.map +1 -1
  71. package/dist/cli/commands/spec-baseline-check.d.ts +43 -0
  72. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  73. package/dist/cli/commands/spec-baseline-check.js +109 -34
  74. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  75. package/dist/cli/commands/status.d.ts +8 -0
  76. package/dist/cli/commands/status.d.ts.map +1 -1
  77. package/dist/cli/commands/status.js +5 -7
  78. package/dist/cli/commands/status.js.map +1 -1
  79. package/dist/cli/commands/sync-docs/context-syncer.d.ts +21 -2
  80. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  81. package/dist/cli/commands/sync-docs/context-syncer.js +112 -28
  82. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  83. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  84. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  85. package/dist/cli/commands/sync-docs/index.js +86 -27
  86. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  87. package/dist/cli/commands/sync-docs/project-reader.d.ts +24 -1
  88. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  89. package/dist/cli/commands/sync-docs/project-reader.js +33 -7
  90. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  91. package/dist/cli/commands/validate.d.ts +0 -16
  92. package/dist/cli/commands/validate.d.ts.map +1 -1
  93. package/dist/cli/commands/validate.js +2 -97
  94. package/dist/cli/commands/validate.js.map +1 -1
  95. package/dist/cli/gate-command.d.ts +37 -0
  96. package/dist/cli/gate-command.d.ts.map +1 -0
  97. package/dist/cli/gate-command.js +64 -0
  98. package/dist/cli/gate-command.js.map +1 -0
  99. package/dist/cli/state-io.d.ts +34 -0
  100. package/dist/cli/state-io.d.ts.map +1 -0
  101. package/dist/cli/state-io.js +71 -0
  102. package/dist/cli/state-io.js.map +1 -0
  103. package/dist/completion-checkers/index.d.ts +1 -1
  104. package/dist/completion-checkers/index.d.ts.map +1 -1
  105. package/dist/completion-checkers/index.js +0 -15
  106. package/dist/completion-checkers/index.js.map +1 -1
  107. package/dist/context/knowledge-injector.d.ts +4 -2
  108. package/dist/context/knowledge-injector.d.ts.map +1 -1
  109. package/dist/context/knowledge-injector.js +13 -6
  110. package/dist/context/knowledge-injector.js.map +1 -1
  111. package/dist/core/constraints/agent-prompt-renderer.js +2 -2
  112. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  113. package/dist/core/constraints/capabilities-reconcile.d.ts +20 -0
  114. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -1
  115. package/dist/core/constraints/capabilities-reconcile.js +26 -4
  116. package/dist/core/constraints/capabilities-reconcile.js.map +1 -1
  117. package/dist/core/constraints/check-cache.d.ts +1 -1
  118. package/dist/core/constraints/check-cache.js +1 -1
  119. package/dist/core/constraints/checker.d.ts +8 -3
  120. package/dist/core/constraints/checker.d.ts.map +1 -1
  121. package/dist/core/constraints/checker.js +30 -19
  122. package/dist/core/constraints/checker.js.map +1 -1
  123. package/dist/core/constraints/checkers/capability-sync.d.ts +3 -1
  124. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  125. package/dist/core/constraints/checkers/capability-sync.js +17 -24
  126. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  127. package/dist/core/constraints/checkers/context-doc-sync.d.ts +1 -1
  128. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  129. package/dist/core/constraints/checkers/context-doc-sync.js +9 -6
  130. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  131. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  132. package/dist/core/constraints/checkers/docs-freshness.js +24 -30
  133. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  134. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  135. package/dist/core/constraints/checkers/governance-presence.js +10 -4
  136. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  137. package/dist/core/constraints/checkers/types.d.ts +10 -6
  138. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  139. package/dist/core/constraints/checkers/types.js +7 -3
  140. package/dist/core/constraints/checkers/types.js.map +1 -1
  141. package/dist/core/constraints/context-builder.d.ts +11 -10
  142. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  143. package/dist/core/constraints/context-builder.js +31 -27
  144. package/dist/core/constraints/context-builder.js.map +1 -1
  145. package/dist/core/constraints/context-reconcile.d.ts +75 -0
  146. package/dist/core/constraints/context-reconcile.d.ts.map +1 -0
  147. package/dist/core/constraints/context-reconcile.js +161 -0
  148. package/dist/core/constraints/context-reconcile.js.map +1 -0
  149. package/dist/core/constraints/doc-freshness/runner.d.ts +7 -0
  150. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  151. package/dist/core/constraints/doc-freshness/runner.js +8 -3
  152. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  153. package/dist/core/constraints/git-evidence.js +1 -1
  154. package/dist/core/constraints/git-evidence.js.map +1 -1
  155. package/dist/core/constraints/injection-drift.d.ts +7 -2
  156. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  157. package/dist/core/constraints/injection-drift.js +13 -7
  158. package/dist/core/constraints/injection-drift.js.map +1 -1
  159. package/dist/core/constraints/run-env.d.ts +92 -0
  160. package/dist/core/constraints/run-env.d.ts.map +1 -0
  161. package/dist/core/constraints/run-env.js +175 -0
  162. package/dist/core/constraints/run-env.js.map +1 -0
  163. package/dist/core/constraints/triggers.d.ts +22 -0
  164. package/dist/core/constraints/triggers.d.ts.map +1 -0
  165. package/dist/core/constraints/triggers.js +31 -0
  166. package/dist/core/constraints/triggers.js.map +1 -0
  167. package/dist/core/effective-constraints.d.ts +13 -4
  168. package/dist/core/effective-constraints.d.ts.map +1 -1
  169. package/dist/core/effective-constraints.js +16 -6
  170. package/dist/core/effective-constraints.js.map +1 -1
  171. package/dist/core/effective-set.d.ts +8 -15
  172. package/dist/core/effective-set.d.ts.map +1 -1
  173. package/dist/core/effective-set.js +7 -12
  174. package/dist/core/effective-set.js.map +1 -1
  175. package/dist/core/index.d.ts +1 -0
  176. package/dist/core/index.d.ts.map +1 -1
  177. package/dist/core/index.js.map +1 -1
  178. package/dist/core/project-config-loader.d.ts +39 -17
  179. package/dist/core/project-config-loader.d.ts.map +1 -1
  180. package/dist/core/project-config-loader.js +51 -122
  181. package/dist/core/project-config-loader.js.map +1 -1
  182. package/dist/core/validators/check-handlers/http.d.ts +2 -2
  183. package/dist/core/validators/check-handlers/http.d.ts.map +1 -1
  184. package/dist/core/validators/check-handlers/http.js +12 -7
  185. package/dist/core/validators/check-handlers/http.js.map +1 -1
  186. package/dist/failure/classifier.d.ts +1 -1
  187. package/dist/failure/classifier.d.ts.map +1 -1
  188. package/dist/failure/classifier.js +8 -8
  189. package/dist/failure/classifier.js.map +1 -1
  190. package/dist/failure/index.d.ts +2 -1
  191. package/dist/failure/index.d.ts.map +1 -1
  192. package/dist/failure/index.js +9 -17
  193. package/dist/failure/index.js.map +1 -1
  194. package/dist/failure/recorder.d.ts +1 -1
  195. package/dist/failure/recorder.d.ts.map +1 -1
  196. package/dist/gates/checker-gate.d.ts +2 -1
  197. package/dist/gates/checker-gate.d.ts.map +1 -1
  198. package/dist/gates/checker-gate.js +3 -2
  199. package/dist/gates/checker-gate.js.map +1 -1
  200. package/dist/gates/command.d.ts +10 -4
  201. package/dist/gates/command.d.ts.map +1 -1
  202. package/dist/gates/command.js +36 -62
  203. package/dist/gates/command.js.map +1 -1
  204. package/dist/gates/contract.d.ts.map +1 -1
  205. package/dist/gates/contract.js +0 -4
  206. package/dist/gates/contract.js.map +1 -1
  207. package/dist/gates/definitions.d.ts +2 -1
  208. package/dist/gates/definitions.d.ts.map +1 -1
  209. package/dist/gates/definitions.js +6 -5
  210. package/dist/gates/definitions.js.map +1 -1
  211. package/dist/gates/index.d.ts +0 -4
  212. package/dist/gates/index.d.ts.map +1 -1
  213. package/dist/gates/index.js +1 -6
  214. package/dist/gates/index.js.map +1 -1
  215. package/dist/gates/performance.d.ts.map +1 -1
  216. package/dist/gates/performance.js +0 -4
  217. package/dist/gates/performance.js.map +1 -1
  218. package/dist/gates/runner.d.ts +1 -1
  219. package/dist/gates/runner.js +1 -1
  220. package/dist/gates/security.d.ts +1 -1
  221. package/dist/gates/security.d.ts.map +1 -1
  222. package/dist/gates/security.js +5 -6
  223. package/dist/gates/security.js.map +1 -1
  224. package/dist/gates/types.d.ts +14 -17
  225. package/dist/gates/types.d.ts.map +1 -1
  226. package/dist/gates/types.js.map +1 -1
  227. package/dist/hooks/bootstrap.d.ts +6 -4
  228. package/dist/hooks/bootstrap.d.ts.map +1 -1
  229. package/dist/hooks/bootstrap.js +25 -13
  230. package/dist/hooks/bootstrap.js.map +1 -1
  231. package/dist/hooks/config.d.ts +6 -2
  232. package/dist/hooks/config.d.ts.map +1 -1
  233. package/dist/hooks/config.js +5 -1
  234. package/dist/hooks/config.js.map +1 -1
  235. package/dist/hooks/index.d.ts +1 -1
  236. package/dist/hooks/index.d.ts.map +1 -1
  237. package/dist/hooks/index.js +0 -15
  238. package/dist/hooks/index.js.map +1 -1
  239. package/dist/hooks/pipeline.d.ts +19 -1
  240. package/dist/hooks/pipeline.d.ts.map +1 -1
  241. package/dist/hooks/pipeline.js +42 -4
  242. package/dist/hooks/pipeline.js.map +1 -1
  243. package/dist/hooks/registry.d.ts +11 -8
  244. package/dist/hooks/registry.d.ts.map +1 -1
  245. package/dist/hooks/registry.js +22 -7
  246. package/dist/hooks/registry.js.map +1 -1
  247. package/dist/hooks/types.d.ts +25 -7
  248. package/dist/hooks/types.d.ts.map +1 -1
  249. package/dist/index.d.ts +3 -3
  250. package/dist/index.d.ts.map +1 -1
  251. package/dist/index.js +3 -4
  252. package/dist/index.js.map +1 -1
  253. package/dist/knowledge/audit-scoring.d.ts +87 -0
  254. package/dist/knowledge/audit-scoring.d.ts.map +1 -0
  255. package/dist/knowledge/audit-scoring.js +470 -0
  256. package/dist/knowledge/audit-scoring.js.map +1 -0
  257. package/dist/knowledge/audit.d.ts +11 -59
  258. package/dist/knowledge/audit.d.ts.map +1 -1
  259. package/dist/knowledge/audit.js +47 -522
  260. package/dist/knowledge/audit.js.map +1 -1
  261. package/dist/knowledge/import.d.ts.map +1 -1
  262. package/dist/knowledge/import.js +2 -0
  263. package/dist/knowledge/import.js.map +1 -1
  264. package/dist/knowledge/index-generator.d.ts.map +1 -1
  265. package/dist/knowledge/index-generator.js +4 -5
  266. package/dist/knowledge/index-generator.js.map +1 -1
  267. package/dist/knowledge/index.d.ts +3 -2
  268. package/dist/knowledge/index.d.ts.map +1 -1
  269. package/dist/knowledge/index.js +3 -16
  270. package/dist/knowledge/index.js.map +1 -1
  271. package/dist/knowledge/ingest.js +1 -1
  272. package/dist/knowledge/ingest.js.map +1 -1
  273. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  274. package/dist/knowledge/lifecycle.js +4 -2
  275. package/dist/knowledge/lifecycle.js.map +1 -1
  276. package/dist/knowledge/migration.d.ts +3 -0
  277. package/dist/knowledge/migration.d.ts.map +1 -1
  278. package/dist/knowledge/migration.js +8 -2
  279. package/dist/knowledge/migration.js.map +1 -1
  280. package/dist/knowledge/query.d.ts +6 -0
  281. package/dist/knowledge/query.d.ts.map +1 -1
  282. package/dist/knowledge/query.js +8 -2
  283. package/dist/knowledge/query.js.map +1 -1
  284. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  285. package/dist/knowledge/reference-tracker.js +4 -6
  286. package/dist/knowledge/reference-tracker.js.map +1 -1
  287. package/dist/knowledge/store.d.ts +22 -16
  288. package/dist/knowledge/store.d.ts.map +1 -1
  289. package/dist/knowledge/store.js +53 -12
  290. package/dist/knowledge/store.js.map +1 -1
  291. package/dist/knowledge/tree-walker.d.ts +19 -0
  292. package/dist/knowledge/tree-walker.d.ts.map +1 -0
  293. package/dist/knowledge/tree-walker.js +30 -0
  294. package/dist/knowledge/tree-walker.js.map +1 -0
  295. package/dist/knowledge/types.d.ts +16 -0
  296. package/dist/knowledge/types.d.ts.map +1 -1
  297. package/dist/monitoring/traces.d.ts +5 -3
  298. package/dist/monitoring/traces.d.ts.map +1 -1
  299. package/dist/monitoring/traces.js +19 -5
  300. package/dist/monitoring/traces.js.map +1 -1
  301. package/dist/test-setup/jsonl-fake-fs.d.ts +30 -0
  302. package/dist/test-setup/jsonl-fake-fs.d.ts.map +1 -0
  303. package/dist/test-setup/jsonl-fake-fs.js +49 -0
  304. package/dist/test-setup/jsonl-fake-fs.js.map +1 -0
  305. package/dist/tools/index.d.ts +4 -1
  306. package/dist/tools/index.d.ts.map +1 -1
  307. package/dist/tools/index.js +7 -15
  308. package/dist/tools/index.js.map +1 -1
  309. package/dist/types/checkpoint.d.ts +5 -0
  310. package/dist/types/checkpoint.d.ts.map +1 -1
  311. package/dist/types/project-config.d.ts +18 -0
  312. package/dist/types/project-config.d.ts.map +1 -1
  313. package/dist/types/trace.d.ts +13 -4
  314. package/dist/types/trace.d.ts.map +1 -1
  315. package/dist/types/trace.js +4 -3
  316. package/dist/types/trace.js.map +1 -1
  317. package/dist/utils/exec.d.ts +4 -27
  318. package/dist/utils/exec.d.ts.map +1 -1
  319. package/dist/utils/exec.js +4 -94
  320. package/dist/utils/exec.js.map +1 -1
  321. package/dist/utils/jsonl.d.ts +17 -7
  322. package/dist/utils/jsonl.d.ts.map +1 -1
  323. package/dist/utils/jsonl.js +144 -17
  324. package/dist/utils/jsonl.js.map +1 -1
  325. package/dist/utils/numeric-flag.d.ts +44 -0
  326. package/dist/utils/numeric-flag.d.ts.map +1 -0
  327. package/dist/utils/numeric-flag.js +62 -0
  328. package/dist/utils/numeric-flag.js.map +1 -0
  329. package/package.json +1 -1
  330. package/src/CONTEXT.md +5 -2
  331. package/src/__tests__/acceptance-gate.test.ts +7 -4
  332. package/src/__tests__/checker-extra.test.ts +3 -2
  333. package/src/__tests__/checker.test.ts +4 -3
  334. package/src/__tests__/checkpoint.test.ts +26 -9
  335. package/src/__tests__/context-files-resolution.test.ts +29 -15
  336. package/src/__tests__/contract-gate.test.ts +19 -40
  337. package/src/__tests__/error-classifier.test.ts +1 -1
  338. package/src/__tests__/exec.test.ts +45 -107
  339. package/src/__tests__/failure-recorder.test.ts +7 -5
  340. package/src/__tests__/hooks-pipeline.test.ts +34 -47
  341. package/src/__tests__/layering.test.ts +217 -25
  342. package/src/__tests__/passes-gate.test.ts +9 -4
  343. package/src/__tests__/performance-gate.test.ts +3 -15
  344. package/src/__tests__/project-config-loader-extra.test.ts +31 -57
  345. package/src/__tests__/project-config-loader.test.ts +6 -3
  346. package/src/__tests__/public-exports.test.ts +0 -1
  347. package/src/__tests__/public-type-surface.test.ts +5 -4
  348. package/src/__tests__/public-value-surface.test.ts +182 -0
  349. package/src/__tests__/review-gate.test.ts +4 -6
  350. package/src/__tests__/security-gate.test.ts +5 -20
  351. package/src/__tests__/skip-semantics.test.ts +5 -3
  352. package/src/__tests__/spec-validator.test.ts +3 -2
  353. package/src/__tests__/sub-barrels-explicit.test.ts +87 -0
  354. package/src/__tests__/test-fixture-hygiene.test.ts +53 -0
  355. package/src/__tests__/trace-analyzer-extra.test.ts +5 -3
  356. package/src/__tests__/trace-analyzer.test.ts +5 -3
  357. package/src/__tests__/trace-collector.test.ts +5 -3
  358. package/src/agents/index.ts +13 -2
  359. package/src/cli/__tests__/gate-command.test.ts +162 -0
  360. package/src/cli/__tests__/state-io.test.ts +50 -0
  361. package/src/cli/commands/CONTEXT.md +30 -5
  362. package/src/cli/commands/__tests__/acceptance.test.ts +24 -25
  363. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +15 -0
  364. package/src/cli/commands/__tests__/check-drift.test.ts +42 -1
  365. package/src/cli/commands/__tests__/check-read-count.test.ts +170 -0
  366. package/src/cli/commands/__tests__/check-skip-output.test.ts +4 -1
  367. package/src/cli/commands/__tests__/check.test.ts +63 -44
  368. package/src/cli/commands/__tests__/command-gate-dead-options.test.ts +60 -0
  369. package/src/cli/commands/__tests__/command.test.ts +33 -17
  370. package/src/cli/commands/__tests__/constraints-retire.test.ts +52 -0
  371. package/src/cli/commands/__tests__/context-syncer.test.ts +141 -0
  372. package/src/cli/commands/__tests__/contract.test.ts +20 -20
  373. package/src/cli/commands/__tests__/failure.test.ts +5 -4
  374. package/src/cli/commands/__tests__/gate-decision.ts +30 -0
  375. package/src/cli/commands/__tests__/init-dead-options.test.ts +50 -0
  376. package/src/cli/commands/__tests__/init-governance-io.test.ts +139 -0
  377. package/src/cli/commands/__tests__/init-injection.test.ts +3 -3
  378. package/src/cli/commands/__tests__/init-ondisk.test.ts +867 -0
  379. package/src/cli/commands/__tests__/init.test.ts +82 -6
  380. package/src/cli/commands/__tests__/knowledge-audit-threshold.test.ts +146 -0
  381. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +1 -1
  382. package/src/cli/commands/__tests__/knowledge-tone-mapping.test.ts +244 -0
  383. package/src/cli/commands/__tests__/knowledge-view.test.ts +839 -0
  384. package/src/cli/commands/__tests__/knowledge.test.ts +33 -15
  385. package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +368 -0
  386. package/src/cli/commands/__tests__/performance.test.ts +19 -21
  387. package/src/cli/commands/__tests__/posteval-plan.test.ts +5 -3
  388. package/src/cli/commands/__tests__/pre-push-hook.test.ts +181 -0
  389. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +95 -2
  390. package/src/cli/commands/__tests__/project-path-convention.test.ts +103 -25
  391. package/src/cli/commands/__tests__/registry.test.ts +2 -0
  392. package/src/cli/commands/__tests__/report.test.ts +23 -1
  393. package/src/cli/commands/__tests__/review.test.ts +10 -17
  394. package/src/cli/commands/__tests__/scaffold.test.ts +505 -0
  395. package/src/cli/commands/__tests__/security-default-scan.test.ts +75 -0
  396. package/src/cli/commands/__tests__/security.test.ts +21 -24
  397. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +3 -2
  398. package/src/cli/commands/__tests__/spec-baseline-index-cap.test.ts +131 -0
  399. package/src/cli/commands/__tests__/spec-baseline-read-count.test.ts +325 -0
  400. package/src/cli/commands/__tests__/status-extra.test.ts +25 -8
  401. package/src/cli/commands/__tests__/status.test.ts +46 -18
  402. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +19 -4
  403. package/src/cli/commands/__tests__/sync-docs-context-drift.test.ts +232 -0
  404. package/src/cli/commands/__tests__/sync-docs-module-order.test.ts +111 -0
  405. package/src/cli/commands/__tests__/sync-docs-read-count.test.ts +192 -0
  406. package/src/cli/commands/__tests__/sync-docs.test.ts +3 -2
  407. package/src/cli/commands/__tests__/validate.test.ts +1 -48
  408. package/src/cli/commands/acceptance.ts +24 -29
  409. package/src/cli/commands/check.ts +60 -35
  410. package/src/cli/commands/command.ts +9 -8
  411. package/src/cli/commands/constraints-retire.ts +57 -70
  412. package/src/cli/commands/contract.ts +44 -47
  413. package/src/cli/commands/definitions.ts +12 -6
  414. package/src/cli/commands/failure.ts +16 -3
  415. package/src/cli/commands/init.ts +242 -389
  416. package/src/cli/commands/knowledge-view.ts +224 -0
  417. package/src/cli/commands/knowledge.ts +643 -448
  418. package/src/cli/commands/passes-gate.ts +12 -3
  419. package/src/cli/commands/performance.ts +31 -38
  420. package/src/cli/commands/report.ts +3 -2
  421. package/src/cli/commands/review.ts +22 -28
  422. package/src/cli/commands/scaffold-templates.ts +406 -0
  423. package/src/cli/commands/scaffold.ts +302 -0
  424. package/src/cli/commands/security.ts +27 -23
  425. package/src/cli/commands/spec-baseline-check.ts +158 -33
  426. package/src/cli/commands/status.ts +12 -7
  427. package/src/cli/commands/sync-docs/__tests__/context-export-surface.test.ts +102 -0
  428. package/src/cli/commands/sync-docs/__tests__/module-descriptions.test.ts +196 -0
  429. package/src/cli/commands/sync-docs/context-syncer.ts +137 -31
  430. package/src/cli/commands/sync-docs/index.ts +103 -28
  431. package/src/cli/commands/sync-docs/project-reader.ts +51 -7
  432. package/src/cli/commands/validate.ts +1 -104
  433. package/src/cli/gate-command.ts +87 -0
  434. package/src/cli/state-io.ts +53 -0
  435. package/src/completion-checkers/index.ts +10 -1
  436. package/src/context/CONTEXT.md +2 -1
  437. package/src/context/__tests__/knowledge-injector.test.ts +27 -0
  438. package/src/context/__tests__/session-manager.test.ts +19 -12
  439. package/src/context/knowledge-injector.ts +14 -7
  440. package/src/core/CONTEXT.md +10 -7
  441. package/src/core/__tests__/effective-set.test.ts +36 -60
  442. package/src/core/constraints/__tests__/context-reconcile.test.ts +246 -0
  443. package/src/core/constraints/__tests__/run-env.test.ts +377 -0
  444. package/src/core/constraints/__tests__/triggers.test.ts +70 -0
  445. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  446. package/src/core/constraints/capabilities-reconcile.ts +28 -4
  447. package/src/core/constraints/check-cache.ts +1 -1
  448. package/src/core/constraints/checker.ts +30 -14
  449. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +8 -3
  450. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +16 -7
  451. package/src/core/constraints/checkers/__tests__/no-hardcoded-credentials.test.ts +2 -1
  452. package/src/core/constraints/checkers/capability-sync.ts +27 -28
  453. package/src/core/constraints/checkers/context-doc-sync.ts +9 -7
  454. package/src/core/constraints/checkers/docs-freshness.ts +28 -33
  455. package/src/core/constraints/checkers/governance-presence.ts +10 -6
  456. package/src/core/constraints/checkers/types.ts +14 -8
  457. package/src/core/constraints/context-builder.ts +35 -26
  458. package/src/core/constraints/context-reconcile.ts +200 -0
  459. package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +25 -0
  460. package/src/core/constraints/doc-freshness/runner.ts +18 -4
  461. package/src/core/constraints/git-evidence.ts +1 -1
  462. package/src/core/constraints/injection-drift.ts +19 -8
  463. package/src/core/constraints/run-env.ts +207 -0
  464. package/src/core/constraints/triggers.ts +33 -0
  465. package/src/core/effective-constraints.ts +17 -6
  466. package/src/core/effective-set.ts +8 -24
  467. package/src/core/index.ts +2 -0
  468. package/src/core/project-config-loader.ts +54 -102
  469. package/src/core/validators/__tests__/http-retry-sleep.test.ts +231 -0
  470. package/src/core/validators/check-handlers/http.ts +18 -7
  471. package/src/failure/CONTEXT.md +2 -1
  472. package/src/failure/classifier.ts +2 -2
  473. package/src/failure/index.ts +14 -2
  474. package/src/failure/recorder.ts +1 -1
  475. package/src/gates/CONTEXT.md +13 -7
  476. package/src/gates/__tests__/checker-gate.test.ts +1 -1
  477. package/src/gates/__tests__/command-single-predicate.test.ts +135 -0
  478. package/src/gates/__tests__/command.test.ts +8 -15
  479. package/src/gates/__tests__/contract.test.ts +5 -5
  480. package/src/gates/__tests__/gate-interface.test.ts +0 -17
  481. package/src/gates/__tests__/performance.test.ts +0 -13
  482. package/src/gates/__tests__/review.test.ts +0 -1
  483. package/src/gates/__tests__/runner.test.ts +1 -1
  484. package/src/gates/__tests__/security-default-scan.test.ts +97 -0
  485. package/src/gates/__tests__/security.test.ts +0 -13
  486. package/src/gates/checker-gate.ts +3 -2
  487. package/src/gates/command.ts +51 -74
  488. package/src/gates/contract.ts +0 -5
  489. package/src/gates/definitions.ts +6 -5
  490. package/src/gates/index.ts +0 -5
  491. package/src/gates/performance.ts +1 -6
  492. package/src/gates/runner.ts +1 -1
  493. package/src/gates/security.ts +9 -11
  494. package/src/gates/types.ts +20 -23
  495. package/src/hooks/CONTEXT.md +10 -9
  496. package/src/hooks/__tests__/bootstrap.test.ts +29 -6
  497. package/src/hooks/__tests__/pipeline.test.ts +133 -17
  498. package/src/hooks/__tests__/registry.test.ts +51 -3
  499. package/src/hooks/bootstrap.ts +35 -15
  500. package/src/hooks/config.ts +6 -2
  501. package/src/hooks/index.ts +10 -1
  502. package/src/hooks/pipeline.ts +51 -6
  503. package/src/hooks/registry.ts +31 -12
  504. package/src/hooks/types.ts +26 -7
  505. package/src/index.ts +1 -2
  506. package/src/knowledge/CONTEXT.md +14 -7
  507. package/src/knowledge/__tests__/audit-labels.test.ts +8 -5
  508. package/src/knowledge/__tests__/audit-scoring.test.ts +230 -0
  509. package/src/knowledge/__tests__/audit-write-count.test.ts +131 -0
  510. package/src/knowledge/__tests__/audit.test.ts +291 -208
  511. package/src/knowledge/__tests__/doctor.test.ts +3 -2
  512. package/src/knowledge/__tests__/ingest.test.ts +3 -2
  513. package/src/knowledge/__tests__/lifecycle.test.ts +57 -2
  514. package/src/knowledge/__tests__/migration.test.ts +35 -6
  515. package/src/knowledge/__tests__/query.test.ts +3 -2
  516. package/src/knowledge/__tests__/reference-tracker.test.ts +23 -2
  517. package/src/knowledge/__tests__/store.test.ts +149 -4
  518. package/src/knowledge/__tests__/tree-walker.test.ts +132 -0
  519. package/src/knowledge/audit-scoring.ts +589 -0
  520. package/src/knowledge/audit.ts +55 -599
  521. package/src/knowledge/import.ts +2 -0
  522. package/src/knowledge/index-generator.ts +4 -6
  523. package/src/knowledge/index.ts +27 -2
  524. package/src/knowledge/ingest.ts +1 -1
  525. package/src/knowledge/lifecycle.ts +5 -2
  526. package/src/knowledge/migration.ts +8 -2
  527. package/src/knowledge/query.ts +8 -1
  528. package/src/knowledge/reference-tracker.ts +5 -7
  529. package/src/knowledge/store.ts +75 -16
  530. package/src/knowledge/tree-walker.ts +29 -0
  531. package/src/knowledge/types.ts +19 -0
  532. package/src/monitoring/CONTEXT.md +5 -2
  533. package/src/monitoring/__tests__/context-tracker.test.ts +16 -8
  534. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +80 -0
  535. package/src/monitoring/traces.ts +19 -5
  536. package/src/release/CONTEXT.md +1 -1
  537. package/src/release/__tests__/integrity.test.ts +21 -12
  538. package/src/test-setup/__tests__/jsonl-fake-fs.test.ts +98 -0
  539. package/src/test-setup/jsonl-fake-fs.ts +59 -0
  540. package/src/tools/index.ts +5 -1
  541. package/src/types/checkpoint.ts +6 -0
  542. package/src/types/project-config.ts +21 -0
  543. package/src/types/trace.ts +14 -4
  544. package/src/utils/__tests__/file-walk.test.ts +3 -2
  545. package/src/utils/__tests__/jsonl-bounded-read.test.ts +106 -0
  546. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +10 -6
  547. package/src/utils/__tests__/jsonl.test.ts +61 -14
  548. package/src/utils/__tests__/numeric-flag.test.ts +80 -0
  549. package/src/utils/__tests__/package-version.test.ts +2 -2
  550. package/src/utils/exec.ts +4 -64
  551. package/src/utils/jsonl.ts +159 -20
  552. package/src/utils/numeric-flag.ts +64 -0
  553. package/dist/failure/types.d.ts +0 -9
  554. package/dist/failure/types.d.ts.map +0 -1
  555. package/dist/failure/types.js +0 -25
  556. package/dist/failure/types.js.map +0 -1
  557. package/dist/gates/effective-gates.d.ts +0 -30
  558. package/dist/gates/effective-gates.d.ts.map +0 -1
  559. package/dist/gates/effective-gates.js +0 -81
  560. package/dist/gates/effective-gates.js.map +0 -1
  561. package/src/failure/types.ts +0 -9
  562. package/src/gates/__tests__/effective-gates.test.ts +0 -77
  563. package/src/gates/effective-gates.ts +0 -99
@@ -269,6 +269,8 @@ export class ColdStartImporter {
269
269
 
270
270
  if (stat.isDirectory()) {
271
271
  // 扫描目录下的 markdown 文件
272
+ // 口径记名(harness#134):本 walker 吃的是**项目文档树**(README/docs/),不是知识库树,
273
+ // 故不适用 tree-walker 的条目排除口径——知识树的 `_index.md`/`.snapshots` 在这里没有对应物。
272
274
  const files = fs.readdirSync(fullPath)
273
275
  .filter(f => f.endsWith('.md'))
274
276
  .slice(0, 10); // 最多 10 个
@@ -10,9 +10,7 @@
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
12
  import { splitFrontmatter } from '../utils/frontmatter';
13
-
14
- const EXCLUDED_DIRS = ['.archive', 'archived', '.snapshots', 'resolutions'];
15
- const INDEX_FILENAME = '_index.md';
13
+ import { INDEX_MD_FILE, isEntryFile, isInfraDir } from './tree-walker';
16
14
 
17
15
  interface IndexEntry {
18
16
  filename: string;
@@ -37,7 +35,7 @@ export class KnowledgeIndexGenerator {
37
35
  regenerate(): string {
38
36
  const output = this.generate();
39
37
  fs.mkdirSync(this.baseDir, { recursive: true });
40
- fs.writeFileSync(path.join(this.baseDir, INDEX_FILENAME), output, 'utf-8');
38
+ fs.writeFileSync(path.join(this.baseDir, INDEX_MD_FILE), output, 'utf-8');
41
39
  return output;
42
40
  }
43
41
 
@@ -112,10 +110,10 @@ export class KnowledgeIndexGenerator {
112
110
  const relPath = relative ? path.join(relative, name) : name;
113
111
 
114
112
  if (item.isDirectory()) {
115
- if (EXCLUDED_DIRS.includes(name)) continue;
113
+ if (isInfraDir(name)) continue;
116
114
  if (name.startsWith('.')) continue;
117
115
  results.push(...this.scanFiles(fullPath, relPath));
118
- } else if (name.endsWith('.md') && name !== INDEX_FILENAME) {
116
+ } else if (isEntryFile(name)) {
119
117
  results.push(fullPath);
120
118
  }
121
119
  }
@@ -4,7 +4,32 @@
4
4
  * Re-exports all knowledge module components.
5
5
  */
6
6
 
7
- export * from './types';
7
+ export { DEFAULT_DECAY_CONFIG } from './types';
8
+ export type {
9
+ KnowledgeSubsystem,
10
+ MaturityLevel,
11
+ StorageLayer,
12
+ ConsumptionMode,
13
+ KnowledgeOrigin,
14
+ KnowledgeEntry,
15
+ SourceRef,
16
+ ExecutionResult,
17
+ KnowledgeReference,
18
+ QueryBudget,
19
+ QueryResult,
20
+ QueryFilter,
21
+ LintIssueType,
22
+ LintIssue,
23
+ IngestOptions,
24
+ MaturityChange,
25
+ DecayConfig,
26
+ DecisionRecord,
27
+ ReferenceRecord,
28
+ IndexEntry,
29
+ SnapshotSurvival,
30
+ ConsumptionStats,
31
+ StoreUpdate,
32
+ } from './types';
8
33
  export type { KnowledgeStore } from './store';
9
34
  export { FileKnowledgeStore } from './store';
10
35
  export { KnowledgeQuery } from './query';
@@ -16,7 +41,7 @@ export { KnowledgeLinter } from './lint';
16
41
  export { ColdStartImporter } from './import';
17
42
  export { KnowledgeHealthScorer } from './doctor';
18
43
  export { KnowledgeAudit } from './audit';
19
- export type { AuditRuleName, AuditAction, AuditIssue, AuditReport, AuditOptions } from './audit';
44
+ export type { AuditRuleName, AuditAction, AuditIssue, AuditReport, AuditOptions } from './audit-scoring';
20
45
  export { migrateKnowledgeEntries } from './migration';
21
46
  export { extractCodeStructure } from './primitives/code-structure';
22
47
  export type { CodeStructure, DeclarationInfo, ImportInfo } from './primitives/code-structure';
@@ -71,7 +71,7 @@ export class KnowledgeIngest {
71
71
  const entry = this.buildEntry(partial, options);
72
72
 
73
73
  // Quality gate: audit before saving
74
- const audit = new KnowledgeAudit({ baseDir: this.store.getBaseDir() });
74
+ const audit = new KnowledgeAudit(this.store);
75
75
  let issues = audit.validate(entry);
76
76
  const critical = issues.filter(i => i.action === 'reject');
77
77
  if (critical.length > 0) {
@@ -10,6 +10,7 @@ import type {
10
10
  MaturityLevel,
11
11
  MaturityChange,
12
12
  DecayConfig,
13
+ StoreUpdate,
13
14
  } from './types';
14
15
  import { DEFAULT_DECAY_CONFIG } from './types';
15
16
  import type { KnowledgeStore } from './store';
@@ -159,6 +160,7 @@ export class KnowledgeLifecycle {
159
160
  runDecayCycle(): MaturityChange[] {
160
161
  const entries = this.store.list({ excludeArchived: false });
161
162
  const changes: MaturityChange[] = [];
163
+ const updates: StoreUpdate[] = [];
162
164
 
163
165
  for (const entry of entries) {
164
166
  const targetMaturity = this.checkEntryDecay(entry.id);
@@ -169,11 +171,12 @@ export class KnowledgeLifecycle {
169
171
  to: targetMaturity,
170
172
  reason: `Auto-decay: ${entry.maturity} → ${targetMaturity} (unreferenced for threshold)`,
171
173
  };
172
- this.store.update(entry.id, { maturity: targetMaturity });
174
+ updates.push({ id: entry.id, partial: { maturity: targetMaturity } });
173
175
  changes.push(change);
174
176
  }
175
177
  }
176
178
 
179
+ this.store.applyAll(updates);
177
180
  return changes;
178
181
  }
179
182
 
@@ -350,7 +353,7 @@ export class KnowledgeLifecycle {
350
353
  private checkRuleDecay(entry: KnowledgeEntry): MaturityLevel | undefined {
351
354
  if (entry.maturity !== 'active') return undefined;
352
355
  const rate = this.getExecutionSuccessRate(entry.id);
353
- if (rate && rate.total >= RULE_MIN_RESULTS_FOR_DECAY && rate.rate < RULE_FAIL_THRESHOLD) return 'deprecated';
356
+ if (rate && rate.total >= RULE_MIN_RESULTS_FOR_DECAY && rate.rate <= RULE_FAIL_THRESHOLD) return 'deprecated';
354
357
  return undefined;
355
358
  }
356
359
 
@@ -8,6 +8,7 @@
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import { splitFrontmatter, joinFrontmatter } from '../utils/frontmatter';
11
+ import { isEntryFile } from './tree-walker';
11
12
 
12
13
  interface MigrationResult {
13
14
  total: number;
@@ -20,6 +21,9 @@ interface MigrationResult {
20
21
  * Migrate all knowledge .md files in a directory to include
21
22
  * consumptionMode and origin fields if missing.
22
23
  *
24
+ * 条目人口按 `tree-walker` 的排除口径判定(harness#134):`_index.md` 这类树生成物
25
+ * 既不是迁移对象,也不再计入 `total`/`errors`(此前被报成「no frontmatter found」假阳性)。
26
+ *
23
27
  * Existing files without these fields get defaults:
24
28
  * - consumptionMode: 'reference'
25
29
  * - origin: 'agent'
@@ -33,7 +37,7 @@ export function migrateKnowledgeEntries(baseDir: string): MigrationResult {
33
37
  return result;
34
38
  }
35
39
 
36
- const files = fs.readdirSync(baseDir).filter(f => f.endsWith('.md'));
40
+ const files = fs.readdirSync(baseDir).filter(isEntryFile);
37
41
  result.total = files.length;
38
42
 
39
43
  for (const file of files) {
@@ -42,7 +46,9 @@ export function migrateKnowledgeEntries(baseDir: string): MigrationResult {
42
46
  const raw = fs.readFileSync(filePath, 'utf-8');
43
47
  const fm = splitFrontmatter(raw);
44
48
  if (fm.state === 'absent') {
45
- result.errors.push(`${file}: no frontmatter found`);
49
+ // 统一口径(harness#89 裁决 2 / harness#161):'absent' 是合法输入、不上报——
50
+ // 按非条目语义静默跳过,计入 skipped(非损坏、非迁移对象,仅保证 total 计数闭环)
51
+ result.skipped++;
46
52
  continue;
47
53
  }
48
54
  if (fm.state === 'malformed') {
@@ -19,6 +19,13 @@ import { KnowledgeLifecycle } from './lifecycle';
19
19
  // ── Constants ──────────────────────────────────────────────
20
20
 
21
21
  const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
22
+
23
+ /**
24
+ * 外部来源条目的 prompt 标记(harness#161:三层防御第二层 marking 的唯一正本)。
25
+ * `KnowledgeQuery.formatForPrompt()` 与 `KnowledgeInjector` 的格式化共用,
26
+ * 仓内不允许存在第二份字面量。
27
+ */
28
+ export const EXTERNAL_SOURCE_MARKER = '[External Source — verify before acting]';
22
29
  const MATURITY_RANK: Record<MaturityLevel, number> = {
23
30
  proven: 3,
24
31
  verified: 2,
@@ -180,7 +187,7 @@ export class KnowledgeQuery {
180
187
  */
181
188
  static formatForPrompt(entry: KnowledgeEntry): string {
182
189
  const prefix = entry.origin === 'external'
183
- ? '[External Source — verify before acting]\n'
190
+ ? `${EXTERNAL_SOURCE_MARKER}\n`
184
191
  : '';
185
192
  return `${prefix}${entry.title}: ${entry.content}`;
186
193
  }
@@ -7,7 +7,7 @@
7
7
 
8
8
  import * as path from 'path';
9
9
  import { appendJsonl, readJsonl } from '../utils/jsonl';
10
- import type { ReferenceRecord } from './types';
10
+ import type { ReferenceRecord, StoreUpdate } from './types';
11
11
  import type { KnowledgeStore } from './store';
12
12
 
13
13
  const REFERENCES_FILE = 'references.jsonl';
@@ -63,6 +63,7 @@ export class ReferenceTracker {
63
63
  updateReferencedBy(): void {
64
64
  const records = this.readAll();
65
65
  const entryToDecisions = new Map<string, Set<string>>();
66
+ const updates: StoreUpdate[] = [];
66
67
 
67
68
  for (const record of records) {
68
69
  for (const entryId of record.entryIds) {
@@ -74,13 +75,10 @@ export class ReferenceTracker {
74
75
  }
75
76
 
76
77
  for (const [entryId, decisions] of entryToDecisions) {
77
- const entry = this.store.get(entryId);
78
- if (entry) {
79
- this.store.update(entryId, {
80
- referencedBy: [...decisions],
81
- });
82
- }
78
+ updates.push({ id: entryId, partial: { referencedBy: [...decisions] } });
83
79
  }
80
+ // 未知 id 由 applyAll 逐条跳过(与原先的 store.get 存在性判定同语义)
81
+ this.store.applyAll(updates);
84
82
  }
85
83
 
86
84
  // ── Internal ───────────────────────────────────────────────
@@ -9,10 +9,19 @@
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import { splitFrontmatter, joinFrontmatter } from '../utils/frontmatter';
12
- import type { KnowledgeEntry, IndexEntry, QueryFilter } from './types';
12
+ import { isEntryFile, SNAPSHOTS_DIR } from './tree-walker';
13
+ import type {
14
+ KnowledgeEntry,
15
+ IndexEntry,
16
+ QueryFilter,
17
+ ConsumptionStats,
18
+ SnapshotSurvival,
19
+ StoreUpdate,
20
+ } from './types';
13
21
 
14
22
  const DEFAULT_DIR = '.harness/knowledge';
15
23
  const INDEX_FILE = 'index.json';
24
+ const CONSUMPTION_STATS_FILE = '.consumption-stats.json';
16
25
 
17
26
  interface StoreConfig {
18
27
  baseDir: string;
@@ -35,6 +44,11 @@ export interface KnowledgeStore {
35
44
  * 消除逐条 save 的 O(K·N) 全量索引重写。空批为零读写 no-op。
36
45
  */
37
46
  saveAll(entries: KnowledgeEntry[]): void;
47
+ /**
48
+ * 按 id 批量部分更新(harness#134):`update(id, partial)` 的批量形,语义与逐条一致
49
+ * (读不到该 id 即跳过),循环内只更新内存索引、结束一次 writeIndex。空批零读写。
50
+ */
51
+ applyAll(updates: StoreUpdate[]): void;
38
52
  delete(id: string): boolean;
39
53
  update(id: string, partial: Partial<KnowledgeEntry>): KnowledgeEntry | undefined;
40
54
  rebuildIndex(): void;
@@ -42,7 +56,12 @@ export interface KnowledgeStore {
42
56
  readEntriesFromDisk(): KnowledgeEntry[];
43
57
  snapshot(): string;
44
58
  getSnapshot(date: string): IndexEntry[] | undefined;
45
- getSurvivalRate(daysAgo: number): { rate: number; survived: number; total: number; snapshotDate: string } | undefined;
59
+ getSurvivalRate(daysAgo: number): SnapshotSurvival | undefined;
60
+ /**
61
+ * 读 `.consumption-stats.json` 的当日消费计数(harness#134:打分核心的 IO 归 store 供给);
62
+ * 文件缺失或损坏 = undefined(无数据,不是零消费)
63
+ */
64
+ getConsumptionStats(): ConsumptionStats | undefined;
46
65
  }
47
66
 
48
67
  /**
@@ -55,8 +74,8 @@ export class FileKnowledgeStore implements KnowledgeStore {
55
74
  /**
56
75
  * index.json 解析结果的实例级缓存(harness#106)
57
76
  *
58
- * mtimeMs+size 指纹(先例:project-config-loader.ts rawConfigCache):
59
- * 文件未变则复用解析结果,消除 list() 一次调用内 N+1 次全量重读;
77
+ * mtimeMs+size 指纹(实例级,作用域限于本 store 的一次 list()):
78
+ * 文件未变则复用解析结果,消除一次调用内 N+1 次全量重读;
60
79
  * 文件变更(含外部进程改写、删除)自动失效。
61
80
  */
62
81
  private indexCache: { mtimeMs: number; size: number; entries: IndexEntry[] } | undefined;
@@ -90,9 +109,7 @@ export class FileKnowledgeStore implements KnowledgeStore {
90
109
  }
91
110
 
92
111
  save(entry: KnowledgeEntry): void {
93
- const filePath = this.entryPath(entry);
94
- const content = joinFrontmatter(this.toFrontmatter(entry), entry.content);
95
- fs.writeFileSync(filePath, content, 'utf-8');
112
+ this.writeEntryFile(entry);
96
113
  this.updateIndexEntry(entry);
97
114
  }
98
115
 
@@ -101,9 +118,7 @@ export class FileKnowledgeStore implements KnowledgeStore {
101
118
  const index = this.readIndex();
102
119
  const position = new Map(index.map((e, i) => [e.id, i]));
103
120
  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');
121
+ this.writeEntryFile(entry);
107
122
  const indexEntry = this.toIndexEntry(entry);
108
123
  const idx = position.get(entry.id);
109
124
  if (idx !== undefined) {
@@ -116,6 +131,30 @@ export class FileKnowledgeStore implements KnowledgeStore {
116
131
  this.writeIndex(index);
117
132
  }
118
133
 
134
+ applyAll(updates: StoreUpdate[]): void {
135
+ if (updates.length === 0) return;
136
+ const index = this.readIndex();
137
+ const position = new Map(index.map((e, i) => [e.id, i]));
138
+ let applied = 0;
139
+ for (const { id, partial } of updates) {
140
+ const existing = this.get(id);
141
+ if (!existing) continue;
142
+ const updated: KnowledgeEntry = { ...existing, ...partial, id };
143
+ this.writeEntryFile(updated);
144
+ const indexEntry = this.toIndexEntry(updated);
145
+ const idx = position.get(id);
146
+ if (idx !== undefined) {
147
+ index[idx] = indexEntry;
148
+ } else {
149
+ position.set(id, index.length);
150
+ index.push(indexEntry);
151
+ }
152
+ applied++;
153
+ }
154
+ if (applied === 0) return;
155
+ this.writeIndex(index);
156
+ }
157
+
119
158
  delete(id: string): boolean {
120
159
  const filePath = this.findFile(id);
121
160
  if (!filePath) return false;
@@ -176,6 +215,15 @@ export class FileKnowledgeStore implements KnowledgeStore {
176
215
  return path.join(this.baseDir, `${safeType}-${safeId}.md`);
177
216
  }
178
217
 
218
+ /** 条目文件正文的唯一落盘写法(save / saveAll / applyAll 三条路径共用,#134) */
219
+ private writeEntryFile(entry: KnowledgeEntry): void {
220
+ fs.writeFileSync(
221
+ this.entryPath(entry),
222
+ joinFrontmatter(this.toFrontmatter(entry), entry.content),
223
+ 'utf-8'
224
+ );
225
+ }
226
+
179
227
  private findFile(id: string): string | undefined {
180
228
  // 1. Fast path: index has id→type mapping, construct exact filename
181
229
  const index = this.readIndex();
@@ -197,7 +245,7 @@ export class FileKnowledgeStore implements KnowledgeStore {
197
245
  private listFiles(): string[] {
198
246
  if (!fs.existsSync(this.baseDir)) return [];
199
247
  return fs.readdirSync(this.baseDir)
200
- .filter(f => f.endsWith('.md'))
248
+ .filter(isEntryFile)
201
249
  .map(f => path.join(this.baseDir, f));
202
250
  }
203
251
 
@@ -324,13 +372,11 @@ export class FileKnowledgeStore implements KnowledgeStore {
324
372
 
325
373
  // ── Snapshots ────────────────────────────────────────────
326
374
 
327
- private static readonly SNAPSHOTS_DIR = '.snapshots';
328
-
329
375
  /**
330
376
  * 保存当日 index.json 快照
331
377
  */
332
378
  snapshot(): string {
333
- const snapDir = path.join(this.baseDir, FileKnowledgeStore.SNAPSHOTS_DIR);
379
+ const snapDir = path.join(this.baseDir, SNAPSHOTS_DIR);
334
380
  if (!fs.existsSync(snapDir)) {
335
381
  fs.mkdirSync(snapDir, { recursive: true });
336
382
  }
@@ -347,7 +393,7 @@ export class FileKnowledgeStore implements KnowledgeStore {
347
393
  * 读取指定日期的快照
348
394
  */
349
395
  getSnapshot(date: string): IndexEntry[] | undefined {
350
- const snapPath = path.join(this.baseDir, FileKnowledgeStore.SNAPSHOTS_DIR, `index-${date}.json`);
396
+ const snapPath = path.join(this.baseDir, SNAPSHOTS_DIR, `index-${date}.json`);
351
397
  if (!fs.existsSync(snapPath)) return undefined;
352
398
  try {
353
399
  return JSON.parse(fs.readFileSync(snapPath, 'utf-8')) as IndexEntry[];
@@ -360,7 +406,7 @@ export class FileKnowledgeStore implements KnowledgeStore {
360
406
  * 计算 N 天前快照中条目的存活率
361
407
  * 存活 = 当前 index 中存在且 maturity !== 'archived'
362
408
  */
363
- getSurvivalRate(daysAgo: number): { rate: number; survived: number; total: number; snapshotDate: string } | undefined {
409
+ getSurvivalRate(daysAgo: number): SnapshotSurvival | undefined {
364
410
  const target = new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
365
411
  const snapshot = this.getSnapshot(target);
366
412
  if (!snapshot || snapshot.length === 0) return undefined;
@@ -382,6 +428,19 @@ export class FileKnowledgeStore implements KnowledgeStore {
382
428
  };
383
429
  }
384
430
 
431
+ /**
432
+ * 读 `.consumption-stats.json` 的当日消费计数(打分核心唯一的消费统计取数口,harness#134)
433
+ */
434
+ getConsumptionStats(): ConsumptionStats | undefined {
435
+ const statsPath = path.join(this.baseDir, CONSUMPTION_STATS_FILE);
436
+ try {
437
+ const stats = JSON.parse(fs.readFileSync(statsPath, 'utf-8')) as { dailyEvents?: number };
438
+ return { dailyEvents: stats.dailyEvents ?? 0 };
439
+ } catch {
440
+ return undefined;
441
+ }
442
+ }
443
+
385
444
  // ── Index I/O ────────────────────────────────────────────
386
445
 
387
446
  readIndex(): IndexEntry[] {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 知识树布局正本(harness#134:walker 排除口径单点化)
3
+ *
4
+ * 「什么算条目」的唯一判定。此前 store 顶层扫描、migration 顶层扫描、index-generator
5
+ * 递归扫描各有各的排除口径,后果是 `knowledge index` 落在 baseDir 的生成物 `_index.md`
6
+ * 被 `knowledge migrate` 判成「no frontmatter found」计入 errors——同一份树,两个命令两套人口。
7
+ *
8
+ * 本模块只管**排除口径**,不管遍历深度:store 与 migration 是顶层扫描器,index-generator
9
+ * 会下钻业务子目录(`skills/`、`arch-patterns/`),那是各自的功能而非口径分歧。
10
+ */
11
+
12
+ /** 索引生成物:由 `harness knowledge index` 落在 baseDir,是树的基建不是条目 */
13
+ export const INDEX_MD_FILE = '_index.md';
14
+
15
+ /** 快照目录:`store.snapshot()` 的落点 */
16
+ export const SNAPSHOTS_DIR = '.snapshots';
17
+
18
+ /** 树基建目录:归档区 / 快照区 / 决议区,任何 walker 都不把它们的内容当条目人口 */
19
+ const INFRA_DIRS = ['.archive', 'archived', SNAPSHOTS_DIR, 'resolutions'];
20
+
21
+ /** 目录名是否属于树基建 */
22
+ export function isInfraDir(name: string): boolean {
23
+ return INFRA_DIRS.includes(name);
24
+ }
25
+
26
+ /** 文件名是否是知识条目(.md 且非生成物) */
27
+ export function isEntryFile(name: string): boolean {
28
+ return name.endsWith('.md') && name !== INDEX_MD_FILE;
29
+ }
@@ -188,3 +188,22 @@ export interface IndexEntry {
188
188
  consumptionMode: ConsumptionMode;
189
189
  origin: KnowledgeOrigin;
190
190
  }
191
+
192
+ /** `getSurvivalRate()` 的返回面:N 天前快照条目在当前索引里的存活计数 */
193
+ export interface SnapshotSurvival {
194
+ rate: number;
195
+ survived: number;
196
+ total: number;
197
+ snapshotDate: string;
198
+ }
199
+
200
+ /** `getConsumptionStats()` 的返回面:`.consumption-stats.json` 里打分要用的计数(harness#134) */
201
+ export interface ConsumptionStats {
202
+ dailyEvents: number;
203
+ }
204
+
205
+ /** `applyAll()` 的一条按 id 部分更新(harness#134:`update(id, partial)` 的批量形) */
206
+ export interface StoreUpdate {
207
+ id: string;
208
+ partial: Partial<KnowledgeEntry>;
209
+ }
@@ -4,17 +4,20 @@
4
4
  运行时监控:Execution Trace(收集/分析)+ 上下文使用追踪。
5
5
 
6
6
  ## 核心导出
7
- - `TraceCollector` — 执行追踪收集(append-only JSONL,`.harness/logs/traces.log`)。读入口两个:`readReport(filter?)` 报告入口(返回 `{ traces, skippedLines }`,harness#100)与 `read()/readRecent()/readByConstraint()` 兼容包装(#82 裁决 4 冻结的签名,丢计数)
7
+ - `TraceCollector` — 执行追踪收集(append-only JSONL,`.harness/logs/traces.log`)。读入口三个:`readReport(filter?)` 报告入口(返回 `{ traces, skippedLines }`,harness#100)、`read()/readRecent()/readByConstraint()` 兼容包装(#82 裁决 4 冻结的签名,丢计数)、`getStats()` 文件级统计(`{ fileExists, fileSize, totalLines, oldestTrace?, newestTrace? }`,harness#114 起经 `readJsonlEnds` 只 parse 两端;坏行并进 `totalLines` 的原始行数口径,要单列坏行数用 `readReport()`)
8
+ - 模块级出口:`getTraceCollector()` / `configureTraceCollector(config)`——进程级单例面,**cwd 锚定**(构造不带 projectPath),本仓生产代码零消费、保留仅为跨仓兼容(锚根约定见「约定」第一条);`createAnalyzer(config?)` 同理,等价 `new TraceAnalyzer(new TraceCollector(), config)`
8
9
  - `TraceAnalyzer` — 追踪统计分析 + 异常检测(共用 analyzer-base 纯函数)。`analyzeRecentReport(hours)` 带坏行数,`analyzeRecent()/analyzeConstraint()` 仍返回 `TraceSummary[]`。**ADR-0020**:`summarize`/`detectAnomalies` 的判定本体是 trace-analyzer.ts 的模块级纯函数 `summarizeTraces(traces)` / `detectTraceAnomalies(summaries, config?)`(阈值经参数传入,不再是实例状态),类壳只转发——类壳是 studio 的运行时消费面(`new TraceAnalyzer(c)` + `analyzeRecentReport`/`detectAnomalies`,签名逐字不动),已持有数据的消费端直调纯函数。两函数**不进包根导出**(ADR-0003 零扩张),仓内经相对 import
9
10
  - `ContextTracker` — LLM 调用上下文使用快照记录
10
11
 
11
12
  ## 依赖关系
12
13
  - 依赖 `src/types/trace` ExecutionTrace 类型
13
- - 消费者:`src/cli/commands/{check,report}` 与 `src/hooks/bootstrap`——组合根经 `getTraceCollector()` 把收集器注入 `ConstraintChecker` 构造参数(harness#88);`status` 按 projectPath 直读读链正本后**直调模块级纯函数**,不构造 collector/类壳(ADR-0020:collector 构造函数的 mkdir 副作用就此退出该路径)
14
+ - 消费者:`src/cli/commands/{check,report}` 与 `src/hooks/bootstrap`——组合根**锚根构造**收集器 `new TraceCollector({ projectPath })` 并注入 `ConstraintChecker` 构造参数(harness#88 接线,#139 收根:cwd 锚定的 `getTraceCollector()` 单例在本仓生产代码已零消费,保留仅为跨仓兼容面);`status` 按 projectPath 直读读链正本后**直调模块级纯函数**,不构造 collector/类壳(ADR-0020:collector 构造函数的 mkdir 副作用就此退出该路径)
15
+ - `ContextTracker` 的仓内持有者是 `src/context/session-manager`:构造时收 `basePath` 并经 `getTracker()` 交出。仓内**无** `record()` 调用点,`getRecent()/getAverages()/detectIssues()` 也无 CLI 接线(`getTracker()` 零仓内调用方),快照的写入方与读方都是库消费者(经包根 `ContextTracker` 出口,ADR-0003)——harness#142 核对补上这条消费边,此前只列了 trace 侧消费者
14
16
  - core 不再依赖本模块(方向由 eslint no-restricted-imports 锁死,`src/__tests__/layering.test.ts` 守卫)
15
17
 
16
18
  ## 约定
17
19
  - Trace 文件存储在 .harness/ 目录(不提交)
20
+ - **落点锚在 projectPath 上,不锚 cwd(harness#139,#95 约定的 trace 侧)**:`DEFAULT_TRACE_FILE` 是**项目相对片段**、不是可直接打开的路径,`TraceCollector` 构造收可选 `projectPath`——给了则缺省/相对的 `traceFile` 一律 `path.resolve(projectPath, …)`,没给则保持 cwd 解析(`getTraceCollector()`/`configureTraceCollector()` 是留给跨仓消费者的 cwd 锚定兼容面,本仓生产代码零消费)。写侧锚根与读侧(`status`/`constraints report` 按 projectPath 直读 jsonl 正本)必须同锚,否则从别处带 `--project-path` 跑一次 check 就是「评估真跑了 B、trace 写进 A」的假绿。可检面:`__tests__/trace-file-anchoring.test.ts`(两态落点 + 读侧同锚)、`src/cli/commands/__tests__/project-path-anchoring.test.ts` 站点 3(命令面写-读闭环)、`src/cli/commands/__tests__/project-path-convention.test.ts` 闸 3(`xxxPath|File|Log` 相对默认值冻结,含常量引用臂)与闸 4(组合根不复活单例取用口)
18
21
  - 追加写入,单行 JSON,自动滚动
19
22
  - 零 Token 成本:不调用 LLM
20
23
  - **凡以 `skip` 策略读 JSONL,坏行计数必须到达该消费面的用户可见输出,或在该调用点显式记名豁免并写明理由(harness#100)**——静默吞掉「数据不全」不是一种可选项。承载方式纯增量:新报告入口带计数,旧入口退化为丢计数的薄包装(#82 裁决 4 的兼容约束继续成立),不另立第二份计数概念、不把 `JsonlReadResult` 提升进包根(ADR-0003)
@@ -6,14 +6,22 @@ import { ContextTracker } from '../context-tracker';
6
6
  import * as fs from 'fs';
7
7
  import type { ContextUsageSnapshot } from '../../context/types';
8
8
 
9
- jest.mock('fs', () => ({
10
- existsSync: jest.fn().mockReturnValue(true),
11
- mkdirSync: jest.fn(),
12
- readFileSync: jest.fn().mockReturnValue(''),
13
- appendFileSync: jest.fn(),
14
- statSync: jest.fn().mockReturnValue({ size: 0 }),
15
- renameSync: jest.fn(),
16
- }));
9
+ jest.mock('fs', () => {
10
+ // 两条读入口必须同源:utils/jsonl 的 tail 走倒读分块(openSync/fstatSync/readSync,
11
+ // ADR-0023 决策 3),head 与全文走 readFileSync。假件收在 test-setup/jsonl-fake-fs
12
+ // 承接两处消费(jest.mock 工厂被提升,只能 require)。
13
+ const { jsonlBoundedReadChain } = require('../../test-setup/jsonl-fake-fs');
14
+ const readFileSync = jest.fn().mockReturnValue('');
15
+ return {
16
+ existsSync: jest.fn().mockReturnValue(true),
17
+ mkdirSync: jest.fn(),
18
+ readFileSync,
19
+ appendFileSync: jest.fn(),
20
+ statSync: jest.fn().mockReturnValue({ size: 0 }),
21
+ renameSync: jest.fn(),
22
+ ...jsonlBoundedReadChain((p: string) => readFileSync(p, 'utf-8')),
23
+ };
24
+ });
17
25
 
18
26
  const mockFs = fs as jest.Mocked<typeof fs>;
19
27
 
@@ -0,0 +1,80 @@
1
+ /**
2
+ * TraceCollector 落点锚根(harness#139,#95 约定的 trace 侧)
3
+ *
4
+ * 不 mock IO:落点本身就是被测行为,只有 **cwd ≠ projectPath** 才看得出锚到了哪一侧
5
+ * (前提形状照 `src/cli/commands/__tests__/project-path-anchoring.test.ts`)。
6
+ * 两态都要钉:给了 projectPath → 锚项目根;没给 → 保持 cwd 解析(跨仓消费者的兼容面)。
7
+ */
8
+
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { TraceCollector } from '../traces';
12
+ import { DEFAULT_TRACE_FILE, type ExecutionTrace } from '../../types/trace';
13
+ import { createProjectFixture } from '../../test-setup/project-fixture';
14
+
15
+ const trace = (constraintId: string): ExecutionTrace => ({
16
+ constraintId,
17
+ level: 'guideline',
18
+ timestamp: 1700000000000,
19
+ result: 'pass',
20
+ });
21
+
22
+ const traceLines = (projectRoot: string): string[] =>
23
+ fs.readFileSync(path.join(projectRoot, DEFAULT_TRACE_FILE), 'utf-8').split('\n').filter(Boolean);
24
+
25
+ describe('TraceCollector 落点锚根(harness#139)', () => {
26
+ const originalCwd = process.cwd();
27
+ let cwdSide: string;
28
+ let projectSide: string;
29
+
30
+ beforeEach(() => {
31
+ cwdSide = createProjectFixture({ name: 'traceanchor-cwd' });
32
+ projectSide = createProjectFixture({ name: 'traceanchor-project' });
33
+ process.chdir(cwdSide);
34
+ });
35
+
36
+ afterEach(() => {
37
+ process.chdir(originalCwd);
38
+ });
39
+
40
+ it('给了 projectPath → 缺省落点锚到 <projectPath> 下,cwd 侧不产生文件', () => {
41
+ new TraceCollector({ projectPath: projectSide }).record(trace('anchored'));
42
+
43
+ expect(traceLines(projectSide).map(l => JSON.parse(l).constraintId)).toEqual(['anchored']);
44
+ expect(fs.existsSync(path.join(cwdSide, DEFAULT_TRACE_FILE))).toBe(false);
45
+ });
46
+
47
+ it('没给 projectPath → 缺省落点仍按 cwd 解析(兼容语义,行为逐字不变)', () => {
48
+ new TraceCollector().record(trace('cwd_default'));
49
+
50
+ expect(traceLines(cwdSide).map(l => JSON.parse(l).constraintId)).toEqual(['cwd_default']);
51
+ expect(fs.existsSync(path.join(projectSide, DEFAULT_TRACE_FILE))).toBe(false);
52
+ });
53
+
54
+ it('显式绝对 traceFile 优先,不被 projectPath 改写', () => {
55
+ const custom = path.join(cwdSide, 'custom.log');
56
+
57
+ new TraceCollector({ projectPath: projectSide, traceFile: custom }).record(trace('explicit'));
58
+
59
+ expect(fs.existsSync(custom)).toBe(true);
60
+ expect(fs.existsSync(path.join(projectSide, DEFAULT_TRACE_FILE))).toBe(false);
61
+ });
62
+
63
+ it('projectPath + 相对 traceFile → 相对落点在 projectPath 之下(自定义片段的锚定形状)', () => {
64
+ new TraceCollector({ projectPath: projectSide, traceFile: 'logs/custom.log' }).record(
65
+ trace('relative')
66
+ );
67
+
68
+ // 自定义片段跟缺省片段同一条锚定规则:给了根就锚根,不给仍按 cwd(兼容面逐字不变)
69
+ expect(fs.existsSync(path.join(projectSide, 'logs', 'custom.log'))).toBe(true);
70
+ expect(fs.existsSync(path.join(cwdSide, 'logs', 'custom.log'))).toBe(false);
71
+ });
72
+
73
+ it('读侧同锚:另一实例按同一 projectPath 构造,read()/getStats() 读到刚写的记录', () => {
74
+ new TraceCollector({ projectPath: projectSide }).record(trace('roundtrip'));
75
+
76
+ const reader = new TraceCollector({ projectPath: projectSide });
77
+ expect(reader.read().map(t => t.constraintId)).toEqual(['roundtrip']);
78
+ expect(reader.getStats().totalLines).toBe(1);
79
+ });
80
+ });