@dommaker/harness 1.6.0 → 1.8.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 (560) hide show
  1. package/CHANGELOG.md +52 -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 +11 -3
  15. package/dist/cli/commands/check.d.ts.map +1 -1
  16. package/dist/cli/commands/check.js +26 -11
  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/sync-docs/context-syncer.d.ts +21 -2
  76. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  77. package/dist/cli/commands/sync-docs/context-syncer.js +112 -28
  78. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  79. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  80. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  81. package/dist/cli/commands/sync-docs/index.js +86 -27
  82. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  83. package/dist/cli/commands/sync-docs/project-reader.d.ts +24 -1
  84. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  85. package/dist/cli/commands/sync-docs/project-reader.js +33 -7
  86. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  87. package/dist/cli/commands/validate.d.ts +0 -16
  88. package/dist/cli/commands/validate.d.ts.map +1 -1
  89. package/dist/cli/commands/validate.js +2 -97
  90. package/dist/cli/commands/validate.js.map +1 -1
  91. package/dist/cli/gate-command.d.ts +37 -0
  92. package/dist/cli/gate-command.d.ts.map +1 -0
  93. package/dist/cli/gate-command.js +64 -0
  94. package/dist/cli/gate-command.js.map +1 -0
  95. package/dist/completion-checkers/index.d.ts +1 -1
  96. package/dist/completion-checkers/index.d.ts.map +1 -1
  97. package/dist/completion-checkers/index.js +0 -15
  98. package/dist/completion-checkers/index.js.map +1 -1
  99. package/dist/context/knowledge-injector.d.ts +4 -2
  100. package/dist/context/knowledge-injector.d.ts.map +1 -1
  101. package/dist/context/knowledge-injector.js +13 -6
  102. package/dist/context/knowledge-injector.js.map +1 -1
  103. package/dist/core/constraints/agent-prompt-renderer.js +2 -2
  104. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  105. package/dist/core/constraints/capabilities-reconcile.d.ts +20 -0
  106. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -1
  107. package/dist/core/constraints/capabilities-reconcile.js +26 -4
  108. package/dist/core/constraints/capabilities-reconcile.js.map +1 -1
  109. package/dist/core/constraints/check-cache.d.ts +1 -1
  110. package/dist/core/constraints/check-cache.js +1 -1
  111. package/dist/core/constraints/checker.d.ts +8 -3
  112. package/dist/core/constraints/checker.d.ts.map +1 -1
  113. package/dist/core/constraints/checker.js +30 -19
  114. package/dist/core/constraints/checker.js.map +1 -1
  115. package/dist/core/constraints/checkers/capability-sync.d.ts +3 -1
  116. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  117. package/dist/core/constraints/checkers/capability-sync.js +17 -24
  118. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  119. package/dist/core/constraints/checkers/context-doc-sync.d.ts +1 -1
  120. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  121. package/dist/core/constraints/checkers/context-doc-sync.js +9 -6
  122. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  123. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  124. package/dist/core/constraints/checkers/docs-freshness.js +24 -30
  125. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  126. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  127. package/dist/core/constraints/checkers/governance-presence.js +10 -4
  128. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  129. package/dist/core/constraints/checkers/types.d.ts +10 -6
  130. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  131. package/dist/core/constraints/checkers/types.js +7 -3
  132. package/dist/core/constraints/checkers/types.js.map +1 -1
  133. package/dist/core/constraints/context-builder.d.ts +11 -10
  134. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  135. package/dist/core/constraints/context-builder.js +31 -27
  136. package/dist/core/constraints/context-builder.js.map +1 -1
  137. package/dist/core/constraints/context-reconcile.d.ts +75 -0
  138. package/dist/core/constraints/context-reconcile.d.ts.map +1 -0
  139. package/dist/core/constraints/context-reconcile.js +161 -0
  140. package/dist/core/constraints/context-reconcile.js.map +1 -0
  141. package/dist/core/constraints/doc-freshness/runner.d.ts +7 -0
  142. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  143. package/dist/core/constraints/doc-freshness/runner.js +8 -3
  144. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  145. package/dist/core/constraints/git-evidence.js +1 -1
  146. package/dist/core/constraints/git-evidence.js.map +1 -1
  147. package/dist/core/constraints/injection-drift.d.ts +7 -2
  148. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  149. package/dist/core/constraints/injection-drift.js +13 -7
  150. package/dist/core/constraints/injection-drift.js.map +1 -1
  151. package/dist/core/constraints/run-env.d.ts +92 -0
  152. package/dist/core/constraints/run-env.d.ts.map +1 -0
  153. package/dist/core/constraints/run-env.js +175 -0
  154. package/dist/core/constraints/run-env.js.map +1 -0
  155. package/dist/core/constraints/triggers.d.ts +22 -0
  156. package/dist/core/constraints/triggers.d.ts.map +1 -0
  157. package/dist/core/constraints/triggers.js +31 -0
  158. package/dist/core/constraints/triggers.js.map +1 -0
  159. package/dist/core/effective-constraints.d.ts +13 -4
  160. package/dist/core/effective-constraints.d.ts.map +1 -1
  161. package/dist/core/effective-constraints.js +16 -6
  162. package/dist/core/effective-constraints.js.map +1 -1
  163. package/dist/core/effective-set.d.ts +8 -15
  164. package/dist/core/effective-set.d.ts.map +1 -1
  165. package/dist/core/effective-set.js +7 -12
  166. package/dist/core/effective-set.js.map +1 -1
  167. package/dist/core/index.d.ts +2 -1
  168. package/dist/core/index.d.ts.map +1 -1
  169. package/dist/core/index.js.map +1 -1
  170. package/dist/core/project-config-loader.d.ts +39 -17
  171. package/dist/core/project-config-loader.d.ts.map +1 -1
  172. package/dist/core/project-config-loader.js +51 -122
  173. package/dist/core/project-config-loader.js.map +1 -1
  174. package/dist/core/validators/check-handlers/http.d.ts +2 -2
  175. package/dist/core/validators/check-handlers/http.d.ts.map +1 -1
  176. package/dist/core/validators/check-handlers/http.js +12 -7
  177. package/dist/core/validators/check-handlers/http.js.map +1 -1
  178. package/dist/core/validators/index.d.ts +1 -1
  179. package/dist/core/validators/index.d.ts.map +1 -1
  180. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  181. package/dist/core/validators/passes-gate.js +2 -2
  182. package/dist/core/validators/passes-gate.js.map +1 -1
  183. package/dist/failure/classifier.d.ts +1 -1
  184. package/dist/failure/classifier.d.ts.map +1 -1
  185. package/dist/failure/classifier.js +8 -8
  186. package/dist/failure/classifier.js.map +1 -1
  187. package/dist/failure/index.d.ts +2 -1
  188. package/dist/failure/index.d.ts.map +1 -1
  189. package/dist/failure/index.js +9 -17
  190. package/dist/failure/index.js.map +1 -1
  191. package/dist/failure/recorder.d.ts +1 -1
  192. package/dist/failure/recorder.d.ts.map +1 -1
  193. package/dist/gates/checker-gate.d.ts +2 -1
  194. package/dist/gates/checker-gate.d.ts.map +1 -1
  195. package/dist/gates/checker-gate.js +3 -2
  196. package/dist/gates/checker-gate.js.map +1 -1
  197. package/dist/gates/command.d.ts +10 -4
  198. package/dist/gates/command.d.ts.map +1 -1
  199. package/dist/gates/command.js +36 -62
  200. package/dist/gates/command.js.map +1 -1
  201. package/dist/gates/contract.d.ts.map +1 -1
  202. package/dist/gates/contract.js +0 -4
  203. package/dist/gates/contract.js.map +1 -1
  204. package/dist/gates/definitions.d.ts +2 -1
  205. package/dist/gates/definitions.d.ts.map +1 -1
  206. package/dist/gates/definitions.js +6 -5
  207. package/dist/gates/definitions.js.map +1 -1
  208. package/dist/gates/index.d.ts +0 -4
  209. package/dist/gates/index.d.ts.map +1 -1
  210. package/dist/gates/index.js +1 -6
  211. package/dist/gates/index.js.map +1 -1
  212. package/dist/gates/performance.d.ts.map +1 -1
  213. package/dist/gates/performance.js +0 -4
  214. package/dist/gates/performance.js.map +1 -1
  215. package/dist/gates/runner.d.ts +1 -1
  216. package/dist/gates/runner.js +1 -1
  217. package/dist/gates/security.d.ts +1 -1
  218. package/dist/gates/security.d.ts.map +1 -1
  219. package/dist/gates/security.js +5 -6
  220. package/dist/gates/security.js.map +1 -1
  221. package/dist/gates/types.d.ts +14 -17
  222. package/dist/gates/types.d.ts.map +1 -1
  223. package/dist/gates/types.js.map +1 -1
  224. package/dist/hooks/bootstrap.d.ts +6 -4
  225. package/dist/hooks/bootstrap.d.ts.map +1 -1
  226. package/dist/hooks/bootstrap.js +25 -13
  227. package/dist/hooks/bootstrap.js.map +1 -1
  228. package/dist/hooks/config.d.ts +6 -2
  229. package/dist/hooks/config.d.ts.map +1 -1
  230. package/dist/hooks/config.js +5 -1
  231. package/dist/hooks/config.js.map +1 -1
  232. package/dist/hooks/index.d.ts +1 -1
  233. package/dist/hooks/index.d.ts.map +1 -1
  234. package/dist/hooks/index.js +0 -15
  235. package/dist/hooks/index.js.map +1 -1
  236. package/dist/hooks/pipeline.d.ts.map +1 -1
  237. package/dist/hooks/pipeline.js +2 -4
  238. package/dist/hooks/pipeline.js.map +1 -1
  239. package/dist/hooks/registry.d.ts +11 -8
  240. package/dist/hooks/registry.d.ts.map +1 -1
  241. package/dist/hooks/registry.js +22 -7
  242. package/dist/hooks/registry.js.map +1 -1
  243. package/dist/hooks/types.d.ts +23 -7
  244. package/dist/hooks/types.d.ts.map +1 -1
  245. package/dist/index.d.ts +4 -4
  246. package/dist/index.d.ts.map +1 -1
  247. package/dist/index.js +3 -4
  248. package/dist/index.js.map +1 -1
  249. package/dist/knowledge/audit-scoring.d.ts +87 -0
  250. package/dist/knowledge/audit-scoring.d.ts.map +1 -0
  251. package/dist/knowledge/audit-scoring.js +470 -0
  252. package/dist/knowledge/audit-scoring.js.map +1 -0
  253. package/dist/knowledge/audit.d.ts +11 -59
  254. package/dist/knowledge/audit.d.ts.map +1 -1
  255. package/dist/knowledge/audit.js +47 -522
  256. package/dist/knowledge/audit.js.map +1 -1
  257. package/dist/knowledge/import.d.ts.map +1 -1
  258. package/dist/knowledge/import.js +2 -0
  259. package/dist/knowledge/import.js.map +1 -1
  260. package/dist/knowledge/index-generator.d.ts.map +1 -1
  261. package/dist/knowledge/index-generator.js +4 -5
  262. package/dist/knowledge/index-generator.js.map +1 -1
  263. package/dist/knowledge/index.d.ts +3 -2
  264. package/dist/knowledge/index.d.ts.map +1 -1
  265. package/dist/knowledge/index.js +3 -16
  266. package/dist/knowledge/index.js.map +1 -1
  267. package/dist/knowledge/ingest.js +1 -1
  268. package/dist/knowledge/ingest.js.map +1 -1
  269. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  270. package/dist/knowledge/lifecycle.js +4 -2
  271. package/dist/knowledge/lifecycle.js.map +1 -1
  272. package/dist/knowledge/migration.d.ts +3 -0
  273. package/dist/knowledge/migration.d.ts.map +1 -1
  274. package/dist/knowledge/migration.js +8 -2
  275. package/dist/knowledge/migration.js.map +1 -1
  276. package/dist/knowledge/query.d.ts +6 -0
  277. package/dist/knowledge/query.d.ts.map +1 -1
  278. package/dist/knowledge/query.js +8 -2
  279. package/dist/knowledge/query.js.map +1 -1
  280. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  281. package/dist/knowledge/reference-tracker.js +4 -6
  282. package/dist/knowledge/reference-tracker.js.map +1 -1
  283. package/dist/knowledge/store.d.ts +22 -16
  284. package/dist/knowledge/store.d.ts.map +1 -1
  285. package/dist/knowledge/store.js +53 -12
  286. package/dist/knowledge/store.js.map +1 -1
  287. package/dist/knowledge/tree-walker.d.ts +19 -0
  288. package/dist/knowledge/tree-walker.d.ts.map +1 -0
  289. package/dist/knowledge/tree-walker.js +30 -0
  290. package/dist/knowledge/tree-walker.js.map +1 -0
  291. package/dist/knowledge/types.d.ts +16 -0
  292. package/dist/knowledge/types.d.ts.map +1 -1
  293. package/dist/monitoring/traces.d.ts +5 -3
  294. package/dist/monitoring/traces.d.ts.map +1 -1
  295. package/dist/monitoring/traces.js +19 -5
  296. package/dist/monitoring/traces.js.map +1 -1
  297. package/dist/test-setup/jsonl-fake-fs.d.ts +30 -0
  298. package/dist/test-setup/jsonl-fake-fs.d.ts.map +1 -0
  299. package/dist/test-setup/jsonl-fake-fs.js +49 -0
  300. package/dist/test-setup/jsonl-fake-fs.js.map +1 -0
  301. package/dist/tools/index.d.ts +4 -1
  302. package/dist/tools/index.d.ts.map +1 -1
  303. package/dist/tools/index.js +7 -15
  304. package/dist/tools/index.js.map +1 -1
  305. package/dist/types/checkpoint.d.ts +5 -0
  306. package/dist/types/checkpoint.d.ts.map +1 -1
  307. package/dist/types/passes-gate.d.ts +0 -13
  308. package/dist/types/passes-gate.d.ts.map +1 -1
  309. package/dist/types/project-config.d.ts +18 -0
  310. package/dist/types/project-config.d.ts.map +1 -1
  311. package/dist/types/trace.d.ts +13 -4
  312. package/dist/types/trace.d.ts.map +1 -1
  313. package/dist/types/trace.js +4 -3
  314. package/dist/types/trace.js.map +1 -1
  315. package/dist/utils/exec.d.ts +4 -27
  316. package/dist/utils/exec.d.ts.map +1 -1
  317. package/dist/utils/exec.js +4 -94
  318. package/dist/utils/exec.js.map +1 -1
  319. package/dist/utils/jsonl.d.ts +17 -7
  320. package/dist/utils/jsonl.d.ts.map +1 -1
  321. package/dist/utils/jsonl.js +144 -17
  322. package/dist/utils/jsonl.js.map +1 -1
  323. package/dist/utils/numeric-flag.d.ts +44 -0
  324. package/dist/utils/numeric-flag.d.ts.map +1 -0
  325. package/dist/utils/numeric-flag.js +62 -0
  326. package/dist/utils/numeric-flag.js.map +1 -0
  327. package/package.json +1 -1
  328. package/src/CONTEXT.md +5 -2
  329. package/src/__tests__/acceptance-gate.test.ts +7 -4
  330. package/src/__tests__/checker-extra.test.ts +3 -2
  331. package/src/__tests__/checker.test.ts +4 -3
  332. package/src/__tests__/checkpoint.test.ts +26 -9
  333. package/src/__tests__/context-files-resolution.test.ts +29 -15
  334. package/src/__tests__/contract-gate.test.ts +19 -40
  335. package/src/__tests__/error-classifier.test.ts +1 -1
  336. package/src/__tests__/exec.test.ts +45 -107
  337. package/src/__tests__/failure-recorder.test.ts +7 -5
  338. package/src/__tests__/hooks-pipeline.test.ts +34 -47
  339. package/src/__tests__/layering.test.ts +217 -25
  340. package/src/__tests__/passes-gate.test.ts +9 -4
  341. package/src/__tests__/performance-gate.test.ts +3 -15
  342. package/src/__tests__/project-config-loader-extra.test.ts +31 -57
  343. package/src/__tests__/project-config-loader.test.ts +6 -3
  344. package/src/__tests__/public-exports.test.ts +27 -6
  345. package/src/__tests__/public-type-surface.test.ts +360 -0
  346. package/src/__tests__/public-value-surface.test.ts +182 -0
  347. package/src/__tests__/review-gate.test.ts +4 -6
  348. package/src/__tests__/security-gate.test.ts +5 -20
  349. package/src/__tests__/skip-semantics.test.ts +5 -3
  350. package/src/__tests__/spec-validator.test.ts +3 -2
  351. package/src/__tests__/sub-barrels-explicit.test.ts +87 -0
  352. package/src/__tests__/test-fixture-hygiene.test.ts +53 -0
  353. package/src/__tests__/trace-analyzer-extra.test.ts +5 -3
  354. package/src/__tests__/trace-analyzer.test.ts +5 -3
  355. package/src/__tests__/trace-collector.test.ts +5 -3
  356. package/src/agents/index.ts +13 -2
  357. package/src/cli/__tests__/gate-command.test.ts +162 -0
  358. package/src/cli/commands/CONTEXT.md +29 -5
  359. package/src/cli/commands/__tests__/acceptance.test.ts +24 -25
  360. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +15 -0
  361. package/src/cli/commands/__tests__/check-drift.test.ts +42 -1
  362. package/src/cli/commands/__tests__/check-read-count.test.ts +167 -0
  363. package/src/cli/commands/__tests__/check-skip-output.test.ts +4 -1
  364. package/src/cli/commands/__tests__/check.test.ts +43 -36
  365. package/src/cli/commands/__tests__/command-gate-dead-options.test.ts +60 -0
  366. package/src/cli/commands/__tests__/command.test.ts +33 -17
  367. package/src/cli/commands/__tests__/constraints-retire.test.ts +52 -0
  368. package/src/cli/commands/__tests__/context-syncer.test.ts +141 -0
  369. package/src/cli/commands/__tests__/contract.test.ts +20 -20
  370. package/src/cli/commands/__tests__/failure.test.ts +5 -4
  371. package/src/cli/commands/__tests__/gate-decision.ts +30 -0
  372. package/src/cli/commands/__tests__/init-dead-options.test.ts +50 -0
  373. package/src/cli/commands/__tests__/init-governance-io.test.ts +139 -0
  374. package/src/cli/commands/__tests__/init-injection.test.ts +3 -3
  375. package/src/cli/commands/__tests__/init-ondisk.test.ts +867 -0
  376. package/src/cli/commands/__tests__/init.test.ts +82 -6
  377. package/src/cli/commands/__tests__/knowledge-audit-threshold.test.ts +146 -0
  378. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +1 -1
  379. package/src/cli/commands/__tests__/knowledge-tone-mapping.test.ts +244 -0
  380. package/src/cli/commands/__tests__/knowledge-view.test.ts +839 -0
  381. package/src/cli/commands/__tests__/knowledge.test.ts +33 -15
  382. package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +368 -0
  383. package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +89 -1
  384. package/src/cli/commands/__tests__/performance.test.ts +19 -21
  385. package/src/cli/commands/__tests__/posteval-plan.test.ts +5 -3
  386. package/src/cli/commands/__tests__/pre-push-hook.test.ts +181 -0
  387. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +95 -2
  388. package/src/cli/commands/__tests__/project-path-convention.test.ts +103 -25
  389. package/src/cli/commands/__tests__/registry.test.ts +2 -0
  390. package/src/cli/commands/__tests__/report.test.ts +23 -1
  391. package/src/cli/commands/__tests__/review.test.ts +10 -17
  392. package/src/cli/commands/__tests__/scaffold.test.ts +505 -0
  393. package/src/cli/commands/__tests__/security-default-scan.test.ts +75 -0
  394. package/src/cli/commands/__tests__/security.test.ts +21 -24
  395. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +3 -2
  396. package/src/cli/commands/__tests__/spec-baseline-index-cap.test.ts +131 -0
  397. package/src/cli/commands/__tests__/spec-baseline-read-count.test.ts +325 -0
  398. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +19 -4
  399. package/src/cli/commands/__tests__/sync-docs-context-drift.test.ts +232 -0
  400. package/src/cli/commands/__tests__/sync-docs-module-order.test.ts +111 -0
  401. package/src/cli/commands/__tests__/sync-docs-read-count.test.ts +192 -0
  402. package/src/cli/commands/__tests__/sync-docs.test.ts +3 -2
  403. package/src/cli/commands/__tests__/validate.test.ts +1 -48
  404. package/src/cli/commands/acceptance.ts +24 -29
  405. package/src/cli/commands/check.ts +40 -17
  406. package/src/cli/commands/command.ts +9 -8
  407. package/src/cli/commands/constraints-retire.ts +57 -70
  408. package/src/cli/commands/contract.ts +44 -47
  409. package/src/cli/commands/definitions.ts +12 -6
  410. package/src/cli/commands/failure.ts +16 -3
  411. package/src/cli/commands/init.ts +242 -389
  412. package/src/cli/commands/knowledge-view.ts +224 -0
  413. package/src/cli/commands/knowledge.ts +643 -448
  414. package/src/cli/commands/passes-gate.ts +12 -3
  415. package/src/cli/commands/performance.ts +31 -38
  416. package/src/cli/commands/report.ts +3 -2
  417. package/src/cli/commands/review.ts +22 -28
  418. package/src/cli/commands/scaffold-templates.ts +406 -0
  419. package/src/cli/commands/scaffold.ts +302 -0
  420. package/src/cli/commands/security.ts +27 -23
  421. package/src/cli/commands/spec-baseline-check.ts +158 -33
  422. package/src/cli/commands/sync-docs/__tests__/context-export-surface.test.ts +102 -0
  423. package/src/cli/commands/sync-docs/__tests__/module-descriptions.test.ts +196 -0
  424. package/src/cli/commands/sync-docs/context-syncer.ts +137 -31
  425. package/src/cli/commands/sync-docs/index.ts +103 -28
  426. package/src/cli/commands/sync-docs/project-reader.ts +51 -7
  427. package/src/cli/commands/validate.ts +1 -104
  428. package/src/cli/gate-command.ts +87 -0
  429. package/src/completion-checkers/index.ts +10 -1
  430. package/src/context/CONTEXT.md +2 -1
  431. package/src/context/__tests__/knowledge-injector.test.ts +27 -0
  432. package/src/context/__tests__/session-manager.test.ts +19 -12
  433. package/src/context/knowledge-injector.ts +14 -7
  434. package/src/core/CONTEXT.md +10 -7
  435. package/src/core/__tests__/effective-set.test.ts +36 -60
  436. package/src/core/constraints/__tests__/context-reconcile.test.ts +246 -0
  437. package/src/core/constraints/__tests__/run-env.test.ts +377 -0
  438. package/src/core/constraints/__tests__/triggers.test.ts +70 -0
  439. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  440. package/src/core/constraints/capabilities-reconcile.ts +28 -4
  441. package/src/core/constraints/check-cache.ts +1 -1
  442. package/src/core/constraints/checker.ts +30 -14
  443. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +8 -3
  444. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +16 -7
  445. package/src/core/constraints/checkers/__tests__/no-hardcoded-credentials.test.ts +2 -1
  446. package/src/core/constraints/checkers/capability-sync.ts +27 -28
  447. package/src/core/constraints/checkers/context-doc-sync.ts +9 -7
  448. package/src/core/constraints/checkers/docs-freshness.ts +28 -33
  449. package/src/core/constraints/checkers/governance-presence.ts +10 -6
  450. package/src/core/constraints/checkers/types.ts +14 -8
  451. package/src/core/constraints/context-builder.ts +35 -26
  452. package/src/core/constraints/context-reconcile.ts +200 -0
  453. package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +25 -0
  454. package/src/core/constraints/doc-freshness/runner.ts +18 -4
  455. package/src/core/constraints/git-evidence.ts +1 -1
  456. package/src/core/constraints/injection-drift.ts +19 -8
  457. package/src/core/constraints/run-env.ts +207 -0
  458. package/src/core/constraints/triggers.ts +33 -0
  459. package/src/core/effective-constraints.ts +17 -6
  460. package/src/core/effective-set.ts +8 -24
  461. package/src/core/index.ts +2 -1
  462. package/src/core/project-config-loader.ts +54 -102
  463. package/src/core/validators/__tests__/http-retry-sleep.test.ts +231 -0
  464. package/src/core/validators/check-handlers/http.ts +18 -7
  465. package/src/core/validators/index.ts +0 -1
  466. package/src/core/validators/passes-gate.ts +2 -3
  467. package/src/failure/CONTEXT.md +2 -1
  468. package/src/failure/classifier.ts +2 -2
  469. package/src/failure/index.ts +14 -2
  470. package/src/failure/recorder.ts +1 -1
  471. package/src/gates/CONTEXT.md +13 -7
  472. package/src/gates/__tests__/checker-gate.test.ts +1 -1
  473. package/src/gates/__tests__/command-single-predicate.test.ts +135 -0
  474. package/src/gates/__tests__/command.test.ts +8 -15
  475. package/src/gates/__tests__/contract.test.ts +5 -5
  476. package/src/gates/__tests__/gate-interface.test.ts +0 -17
  477. package/src/gates/__tests__/performance.test.ts +0 -13
  478. package/src/gates/__tests__/review.test.ts +0 -1
  479. package/src/gates/__tests__/runner.test.ts +1 -1
  480. package/src/gates/__tests__/security-default-scan.test.ts +97 -0
  481. package/src/gates/__tests__/security.test.ts +0 -13
  482. package/src/gates/checker-gate.ts +3 -2
  483. package/src/gates/command.ts +51 -74
  484. package/src/gates/contract.ts +0 -5
  485. package/src/gates/definitions.ts +6 -5
  486. package/src/gates/index.ts +0 -5
  487. package/src/gates/performance.ts +1 -6
  488. package/src/gates/runner.ts +1 -1
  489. package/src/gates/security.ts +9 -11
  490. package/src/gates/types.ts +20 -23
  491. package/src/hooks/CONTEXT.md +10 -9
  492. package/src/hooks/__tests__/bootstrap.test.ts +29 -6
  493. package/src/hooks/__tests__/pipeline.test.ts +28 -17
  494. package/src/hooks/__tests__/registry.test.ts +51 -3
  495. package/src/hooks/bootstrap.ts +35 -15
  496. package/src/hooks/config.ts +6 -2
  497. package/src/hooks/index.ts +10 -1
  498. package/src/hooks/pipeline.ts +4 -6
  499. package/src/hooks/registry.ts +31 -12
  500. package/src/hooks/types.ts +24 -7
  501. package/src/index.ts +1 -3
  502. package/src/knowledge/CONTEXT.md +14 -7
  503. package/src/knowledge/__tests__/audit-labels.test.ts +8 -5
  504. package/src/knowledge/__tests__/audit-scoring.test.ts +230 -0
  505. package/src/knowledge/__tests__/audit-write-count.test.ts +131 -0
  506. package/src/knowledge/__tests__/audit.test.ts +291 -208
  507. package/src/knowledge/__tests__/doctor.test.ts +3 -2
  508. package/src/knowledge/__tests__/ingest.test.ts +3 -2
  509. package/src/knowledge/__tests__/lifecycle.test.ts +57 -2
  510. package/src/knowledge/__tests__/migration.test.ts +35 -6
  511. package/src/knowledge/__tests__/query.test.ts +3 -2
  512. package/src/knowledge/__tests__/reference-tracker.test.ts +23 -2
  513. package/src/knowledge/__tests__/store.test.ts +149 -4
  514. package/src/knowledge/__tests__/tree-walker.test.ts +132 -0
  515. package/src/knowledge/audit-scoring.ts +589 -0
  516. package/src/knowledge/audit.ts +55 -599
  517. package/src/knowledge/import.ts +2 -0
  518. package/src/knowledge/index-generator.ts +4 -6
  519. package/src/knowledge/index.ts +27 -2
  520. package/src/knowledge/ingest.ts +1 -1
  521. package/src/knowledge/lifecycle.ts +5 -2
  522. package/src/knowledge/migration.ts +8 -2
  523. package/src/knowledge/query.ts +8 -1
  524. package/src/knowledge/reference-tracker.ts +5 -7
  525. package/src/knowledge/store.ts +75 -16
  526. package/src/knowledge/tree-walker.ts +29 -0
  527. package/src/knowledge/types.ts +19 -0
  528. package/src/monitoring/CONTEXT.md +5 -2
  529. package/src/monitoring/__tests__/context-tracker.test.ts +16 -8
  530. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +80 -0
  531. package/src/monitoring/traces.ts +19 -5
  532. package/src/release/CONTEXT.md +1 -1
  533. package/src/release/__tests__/integrity.test.ts +21 -12
  534. package/src/test-setup/__tests__/jsonl-fake-fs.test.ts +98 -0
  535. package/src/test-setup/jsonl-fake-fs.ts +59 -0
  536. package/src/tools/index.ts +5 -1
  537. package/src/types/checkpoint.ts +6 -0
  538. package/src/types/passes-gate.ts +0 -17
  539. package/src/types/project-config.ts +21 -0
  540. package/src/types/trace.ts +14 -4
  541. package/src/utils/__tests__/file-walk.test.ts +3 -2
  542. package/src/utils/__tests__/jsonl-bounded-read.test.ts +106 -0
  543. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +10 -6
  544. package/src/utils/__tests__/jsonl.test.ts +61 -14
  545. package/src/utils/__tests__/numeric-flag.test.ts +80 -0
  546. package/src/utils/__tests__/package-version.test.ts +2 -2
  547. package/src/utils/exec.ts +4 -64
  548. package/src/utils/jsonl.ts +159 -20
  549. package/src/utils/numeric-flag.ts +64 -0
  550. package/dist/failure/types.d.ts +0 -9
  551. package/dist/failure/types.d.ts.map +0 -1
  552. package/dist/failure/types.js +0 -25
  553. package/dist/failure/types.js.map +0 -1
  554. package/dist/gates/effective-gates.d.ts +0 -30
  555. package/dist/gates/effective-gates.d.ts.map +0 -1
  556. package/dist/gates/effective-gates.js +0 -81
  557. package/dist/gates/effective-gates.js.map +0 -1
  558. package/src/failure/types.ts +0 -9
  559. package/src/gates/__tests__/effective-gates.test.ts +0 -77
  560. package/src/gates/effective-gates.ts +0 -99
@@ -8,14 +8,25 @@
8
8
  * D4: promotion-blocked, orphan-draft
9
9
  * D5: stale-entry
10
10
  * D6: flywheel (computed from referencedBy)
11
+ *
12
+ * 构造收 store(harness#134):打分离开文件系统即可测——本文件除了「修复动作是否真落盘」
13
+ * 那几条用真 FileKnowledgeStore(落盘可见性就是它的判定面),其余全部走内存 store 双。
11
14
  */
12
15
 
13
16
  import * as fs from 'fs';
14
17
  import * as path from 'path';
15
18
  import * as os from 'os';
16
19
  import { KnowledgeAudit } from '../audit';
17
- import { FileKnowledgeStore as KnowledgeStore } from '../store';
18
- import type { KnowledgeEntry } from '../types';
20
+ import { FileKnowledgeStore } from '../store';
21
+ import type { KnowledgeStore } from '../store';
22
+ import type {
23
+ ConsumptionStats,
24
+ IndexEntry,
25
+ KnowledgeEntry,
26
+ QueryFilter,
27
+ SnapshotSurvival,
28
+ StoreUpdate,
29
+ } from '../types';
19
30
 
20
31
  function makeTmpDir(): string {
21
32
  return fs.mkdtempSync(path.join(os.tmpdir(), 'audit-test-'));
@@ -44,8 +55,58 @@ function makeEntry(overrides: Partial<KnowledgeEntry> = {}): KnowledgeEntry {
44
55
  };
45
56
  }
46
57
 
47
- function setupStore(dir: string, entries: Partial<KnowledgeEntry>[]): KnowledgeStore {
48
- const store = new KnowledgeStore({ baseDir: dir });
58
+ /**
59
+ * 内存 store 双:只服务 audit 的取数形状(list / get / applyAll / 环境数据),
60
+ * 与 FileKnowledgeStore 同实现 KnowledgeStore 接口——接口变动即编译期报错。
61
+ */
62
+ class MemoryKnowledgeStore implements KnowledgeStore {
63
+ private entries = new Map<string, KnowledgeEntry>();
64
+ consumptionStats?: ConsumptionStats;
65
+ survival?: SnapshotSurvival;
66
+ applyAllCalls = 0;
67
+
68
+ constructor(entries: KnowledgeEntry[] = []) {
69
+ for (const e of entries) this.entries.set(e.id, e);
70
+ }
71
+
72
+ getBaseDir(): string { return 'memory://knowledge'; }
73
+ get(id: string): KnowledgeEntry | undefined { return this.entries.get(id); }
74
+
75
+ list(filter?: QueryFilter): KnowledgeEntry[] {
76
+ const all = [...this.entries.values()];
77
+ if (filter?.excludeArchived === false) return all;
78
+ return all.filter(e => e.maturity !== 'archived' && e.maturity !== 'deprecated');
79
+ }
80
+
81
+ save(entry: KnowledgeEntry): void { this.entries.set(entry.id, entry); }
82
+ saveAll(entries: KnowledgeEntry[]): void { for (const e of entries) this.save(e); }
83
+ applyAll(updates: StoreUpdate[]): void {
84
+ if (updates.length === 0) return; // 接口契约:空批零读写(真实现由 store.test.ts 钉住)
85
+ this.applyAllCalls++;
86
+ for (const { id, partial } of updates) {
87
+ const existing = this.entries.get(id);
88
+ if (existing) this.entries.set(id, { ...existing, ...partial, id });
89
+ }
90
+ }
91
+ delete(id: string): boolean { return this.entries.delete(id); }
92
+ update(id: string, partial: Partial<KnowledgeEntry>): KnowledgeEntry | undefined {
93
+ const existing = this.entries.get(id);
94
+ if (!existing) return undefined;
95
+ const updated = { ...existing, ...partial, id };
96
+ this.entries.set(id, updated);
97
+ return updated;
98
+ }
99
+ rebuildIndex(): void { /* 内存双无索引 */ }
100
+ readIndex(): IndexEntry[] { return []; }
101
+ readEntriesFromDisk(): KnowledgeEntry[] { return this.list({ excludeArchived: false }); }
102
+ snapshot(): string { return 'memory://knowledge/.snapshots'; }
103
+ getSnapshot(): IndexEntry[] | undefined { return undefined; }
104
+ getSurvivalRate(): SnapshotSurvival | undefined { return this.survival; }
105
+ getConsumptionStats(): ConsumptionStats | undefined { return this.consumptionStats; }
106
+ }
107
+
108
+ function setupStore(dir: string, entries: Partial<KnowledgeEntry>[]): FileKnowledgeStore {
109
+ const store = new FileKnowledgeStore({ baseDir: dir });
49
110
  for (const partial of entries) {
50
111
  const entry = makeEntry(partial);
51
112
  store.save(entry);
@@ -53,36 +114,39 @@ function setupStore(dir: string, entries: Partial<KnowledgeEntry>[]): KnowledgeS
53
114
  return store;
54
115
  }
55
116
 
117
+ /** 打分用例的共用形状:内存 store + 已落条目 + 阈值 */
118
+ function memoryAudit(
119
+ entries: KnowledgeEntry[],
120
+ options?: { shortContentThreshold?: number; staleDays?: number; promotionBlockDays?: number }
121
+ ): { audit: KnowledgeAudit; store: MemoryKnowledgeStore } {
122
+ const store = new MemoryKnowledgeStore(entries);
123
+ return { audit: new KnowledgeAudit(store, options), store };
124
+ }
125
+
56
126
  // ── D1: frontmatter-missing ─────────────────────────────
57
127
 
58
128
  describe('D1: frontmatter-missing', () => {
59
129
  it('rejects entry with missing id', () => {
60
- const dir = makeTmpDir();
61
- const audit = new KnowledgeAudit({ baseDir: dir });
130
+ const { audit } = memoryAudit([]);
62
131
  const entry = makeEntry({ id: '' as any });
63
132
  const issues = audit.validate(entry);
64
133
  const fm = issues.filter(i => i.rule === 'frontmatter-missing');
65
134
  expect(fm.length).toBe(1);
66
135
  expect(fm[0].action).toBe('reject');
67
- fs.rmSync(dir, { recursive: true });
68
136
  });
69
137
 
70
138
  it('rejects entry with missing type', () => {
71
- const dir = makeTmpDir();
72
- const audit = new KnowledgeAudit({ baseDir: dir });
139
+ const { audit } = memoryAudit([]);
73
140
  const entry = makeEntry({ type: undefined as any });
74
141
  const issues = audit.validate(entry);
75
142
  expect(issues.some(i => i.rule === 'frontmatter-missing')).toBe(true);
76
- fs.rmSync(dir, { recursive: true });
77
143
  });
78
144
 
79
145
  it('passes with all required fields present', () => {
80
- const dir = makeTmpDir();
81
- const audit = new KnowledgeAudit({ baseDir: dir });
146
+ const { audit } = memoryAudit([]);
82
147
  const entry = makeEntry();
83
148
  const issues = audit.validate(entry);
84
149
  expect(issues.filter(i => i.rule === 'frontmatter-missing')).toHaveLength(0);
85
- fs.rmSync(dir, { recursive: true });
86
150
  });
87
151
  });
88
152
 
@@ -90,48 +154,38 @@ describe('D1: frontmatter-missing', () => {
90
154
 
91
155
  describe('D2: test-data-pollution', () => {
92
156
  it('archives entries with test-scope tags', () => {
93
- const dir = makeTmpDir();
94
- const audit = new KnowledgeAudit({ baseDir: dir });
157
+ const { audit } = memoryAudit([]);
95
158
  const entry = makeEntry({ tags: ['test-scope-abc'] });
96
159
  const issues = audit.validate(entry);
97
160
  expect(issues.some(i => i.rule === 'test-data-pollution' && i.action === 'archive')).toBe(true);
98
- fs.rmSync(dir, { recursive: true });
99
161
  });
100
162
 
101
163
  it('archives entries with test title', () => {
102
- const dir = makeTmpDir();
103
- const audit = new KnowledgeAudit({ baseDir: dir });
164
+ const { audit } = memoryAudit([]);
104
165
  const entry = makeEntry({ title: 'Test Entry' });
105
166
  const issues = audit.validate(entry);
106
167
  expect(issues.some(i => i.rule === 'test-data-pollution')).toBe(true);
107
- fs.rmSync(dir, { recursive: true });
108
168
  });
109
169
 
110
170
  it('archives entries with test-* ID', () => {
111
- const dir = makeTmpDir();
112
- const audit = new KnowledgeAudit({ baseDir: dir });
171
+ const { audit } = memoryAudit([]);
113
172
  const entry = makeEntry({ id: 'test-search-1780414709970-pattern' });
114
173
  const issues = audit.validate(entry);
115
174
  expect(issues.some(i => i.rule === 'test-data-pollution' && i.detail.includes('test-search'))).toBe(true);
116
- fs.rmSync(dir, { recursive: true });
117
175
  });
118
176
 
119
177
  it('archives entries with inj-test-* ID', () => {
120
- const dir = makeTmpDir();
121
- const audit = new KnowledgeAudit({ baseDir: dir });
178
+ const { audit } = memoryAudit([]);
122
179
  const entry = makeEntry({ id: 'inj-test-1780414711344' });
123
180
  const issues = audit.validate(entry);
124
181
  expect(issues.some(i => i.rule === 'test-data-pollution' && i.detail.includes('inj-test'))).toBe(true);
125
- fs.rmSync(dir, { recursive: true });
126
182
  });
127
183
 
128
184
  it('skips archived entries', () => {
129
- const dir = makeTmpDir();
130
- const audit = new KnowledgeAudit({ baseDir: dir });
185
+ const { audit } = memoryAudit([]);
131
186
  const entry = makeEntry({ maturity: 'archived', tags: ['test-scope-abc'] });
132
187
  const issues = audit.validate(entry);
133
188
  expect(issues.filter(i => i.rule === 'test-data-pollution')).toHaveLength(0);
134
- fs.rmSync(dir, { recursive: true });
135
189
  });
136
190
  });
137
191
 
@@ -139,21 +193,17 @@ describe('D2: test-data-pollution', () => {
139
193
 
140
194
  describe('D2: daily-audit-noise', () => {
141
195
  it('archives entries with [Auditor] Daily audit title', () => {
142
- const dir = makeTmpDir();
143
- const audit = new KnowledgeAudit({ baseDir: dir });
196
+ const { audit } = memoryAudit([]);
144
197
  const entry = makeEntry({ title: '[Auditor] Daily audit 2026-06-01' });
145
198
  const issues = audit.validate(entry);
146
199
  expect(issues.some(i => i.rule === 'daily-audit-noise' && i.action === 'archive')).toBe(true);
147
- fs.rmSync(dir, { recursive: true });
148
200
  });
149
201
 
150
202
  it('passes for normal titles', () => {
151
- const dir = makeTmpDir();
152
- const audit = new KnowledgeAudit({ baseDir: dir });
203
+ const { audit } = memoryAudit([]);
153
204
  const entry = makeEntry({ title: 'Normal knowledge entry' });
154
205
  const issues = audit.validate(entry);
155
206
  expect(issues.filter(i => i.rule === 'daily-audit-noise')).toHaveLength(0);
156
- fs.rmSync(dir, { recursive: true });
157
207
  });
158
208
  });
159
209
 
@@ -161,57 +211,45 @@ describe('D2: daily-audit-noise', () => {
161
211
 
162
212
  describe('D2: event-noise', () => {
163
213
  it('rejects [Monitor] event titles', () => {
164
- const dir = makeTmpDir();
165
- const audit = new KnowledgeAudit({ baseDir: dir });
214
+ const { audit } = memoryAudit([]);
166
215
  const entry = makeEntry({ title: '[Monitor] stuck_goals: GoalExecution abc123' });
167
216
  const issues = audit.validate(entry);
168
217
  expect(issues.some(i => i.rule === 'event-noise' && i.action === 'archive')).toBe(true);
169
- fs.rmSync(dir, { recursive: true });
170
218
  });
171
219
 
172
220
  it('rejects KnowledgeSync cycle titles', () => {
173
- const dir = makeTmpDir();
174
- const audit = new KnowledgeAudit({ baseDir: dir });
221
+ const { audit } = memoryAudit([]);
175
222
  const entry = makeEntry({ title: 'KnowledgeSync cycle: 0 stale, 0 unmonitored' });
176
223
  const issues = audit.validate(entry);
177
224
  expect(issues.some(i => i.rule === 'event-noise' && i.action === 'archive')).toBe(true);
178
- fs.rmSync(dir, { recursive: true });
179
225
  });
180
226
 
181
227
  it('rejects [Triage Fix] titles', () => {
182
- const dir = makeTmpDir();
183
- const audit = new KnowledgeAudit({ baseDir: dir });
228
+ const { audit } = memoryAudit([]);
184
229
  const entry = makeEntry({ title: '[Triage Fix] timeout (critical)' });
185
230
  const issues = audit.validate(entry);
186
231
  expect(issues.some(i => i.rule === 'event-noise' && i.action === 'archive')).toBe(true);
187
- fs.rmSync(dir, { recursive: true });
188
232
  });
189
233
 
190
234
  it('rejects [Session Feature] titles', () => {
191
- const dir = makeTmpDir();
192
- const audit = new KnowledgeAudit({ baseDir: dir });
235
+ const { audit } = memoryAudit([]);
193
236
  const entry = makeEntry({ title: '[Session Feature] feat: studio release' });
194
237
  const issues = audit.validate(entry);
195
238
  expect(issues.some(i => i.rule === 'event-noise' && i.action === 'archive')).toBe(true);
196
- fs.rmSync(dir, { recursive: true });
197
239
  });
198
240
 
199
241
  it('passes for normal knowledge titles', () => {
200
- const dir = makeTmpDir();
201
- const audit = new KnowledgeAudit({ baseDir: dir });
242
+ const { audit } = memoryAudit([]);
202
243
  const entry = makeEntry({ title: 'SQLite WAL mode pattern' });
203
244
  const issues = audit.validate(entry);
204
245
  expect(issues.filter(i => i.rule === 'event-noise')).toHaveLength(0);
205
- fs.rmSync(dir, { recursive: true });
206
246
  });
207
247
 
208
248
  it('skips archived entries', () => {
209
- const dir = makeTmpDir();
210
- const audit = new KnowledgeAudit({ baseDir: dir });
249
+ const { audit } = memoryAudit([]);
211
250
  const entry = makeEntry({ maturity: 'archived', title: '[Monitor] stuck_goals: test' });
212
251
  const issues = audit.validate(entry);
213
252
  expect(issues.filter(i => i.rule === 'event-noise')).toHaveLength(0);
214
- fs.rmSync(dir, { recursive: true });
215
253
  });
216
254
  });
217
255
 
@@ -219,21 +257,17 @@ describe('D2: event-noise', () => {
219
257
 
220
258
  describe('D2: zero-content-proven', () => {
221
259
  it('demotes proven entries with <20 chars content', () => {
222
- const dir = makeTmpDir();
223
- const audit = new KnowledgeAudit({ baseDir: dir });
260
+ const { audit } = memoryAudit([]);
224
261
  const entry = makeEntry({ maturity: 'proven', content: 'short' });
225
262
  const issues = audit.validate(entry);
226
263
  expect(issues.some(i => i.rule === 'zero-content-proven' && i.action === 'demote')).toBe(true);
227
- fs.rmSync(dir, { recursive: true });
228
264
  });
229
265
 
230
266
  it('passes for proven entries with sufficient content', () => {
231
- const dir = makeTmpDir();
232
- const audit = new KnowledgeAudit({ baseDir: dir });
267
+ const { audit } = memoryAudit([]);
233
268
  const entry = makeEntry({ maturity: 'proven', content: 'a'.repeat(50) });
234
269
  const issues = audit.validate(entry);
235
270
  expect(issues.filter(i => i.rule === 'zero-content-proven')).toHaveLength(0);
236
- fs.rmSync(dir, { recursive: true });
237
271
  });
238
272
  });
239
273
 
@@ -241,12 +275,10 @@ describe('D2: zero-content-proven', () => {
241
275
 
242
276
  describe('D2: maturity-inflation', () => {
243
277
  it('demotes verified entries with <20 chars content', () => {
244
- const dir = makeTmpDir();
245
- const audit = new KnowledgeAudit({ baseDir: dir });
278
+ const { audit } = memoryAudit([]);
246
279
  const entry = makeEntry({ maturity: 'verified', content: 'tiny' });
247
280
  const issues = audit.validate(entry);
248
281
  expect(issues.some(i => i.rule === 'maturity-inflation' && i.action === 'demote')).toBe(true);
249
- fs.rmSync(dir, { recursive: true });
250
282
  });
251
283
  });
252
284
 
@@ -254,22 +286,18 @@ describe('D2: maturity-inflation', () => {
254
286
 
255
287
  describe('D2: short-content', () => {
256
288
  it('flags entries with content between 20-50 chars', () => {
257
- const dir = makeTmpDir();
258
- const audit = new KnowledgeAudit({ baseDir: dir });
289
+ const { audit } = memoryAudit([]);
259
290
  // 25 chars — above zero threshold (20) but below short threshold (50)
260
291
  const entry = makeEntry({ content: 'a'.repeat(25), maturity: 'draft' });
261
292
  const issues = audit.validate(entry);
262
293
  expect(issues.some(i => i.rule === 'short-content' && i.action === 'flag')).toBe(true);
263
- fs.rmSync(dir, { recursive: true });
264
294
  });
265
295
 
266
296
  it('respects custom threshold', () => {
267
- const dir = makeTmpDir();
268
- const audit = new KnowledgeAudit({ baseDir: dir, shortContentThreshold: 100 });
297
+ const { audit } = memoryAudit([], { shortContentThreshold: 100 });
269
298
  const entry = makeEntry({ content: 'a'.repeat(60), maturity: 'draft' });
270
299
  const issues = audit.validate(entry);
271
300
  expect(issues.some(i => i.rule === 'short-content')).toBe(true);
272
- fs.rmSync(dir, { recursive: true });
273
301
  });
274
302
  });
275
303
 
@@ -277,60 +305,49 @@ describe('D2: short-content', () => {
277
305
 
278
306
  describe('D3: title-duplicate', () => {
279
307
  it('flags entries with duplicate titles of same type', () => {
280
- const dir = makeTmpDir();
281
- setupStore(dir, [
282
- { id: 'A-001', title: 'Same Title', type: 'guideline' },
283
- { id: 'A-002', title: 'Same Title', type: 'guideline' },
308
+ const { audit } = memoryAudit([
309
+ makeEntry({ id: 'A-001', title: 'Same Title', type: 'guideline' }),
310
+ makeEntry({ id: 'A-002', title: 'Same Title', type: 'guideline' }),
284
311
  ]);
285
- const audit = new KnowledgeAudit({ baseDir: dir });
286
312
  const report = audit.run();
287
313
  const dupes = report.issues.filter(i => i.rule === 'title-duplicate');
288
314
  expect(dupes.length).toBe(2);
289
- fs.rmSync(dir, { recursive: true });
290
315
  });
291
316
 
292
317
  it('ignores archived entries in dedup', () => {
293
- const dir = makeTmpDir();
294
- setupStore(dir, [
295
- { id: 'A-001', title: 'Same Title', type: 'guideline', maturity: 'archived' },
296
- { id: 'A-002', title: 'Same Title', type: 'guideline' },
318
+ const { audit } = memoryAudit([
319
+ makeEntry({ id: 'A-001', title: 'Same Title', type: 'guideline', maturity: 'archived' }),
320
+ makeEntry({ id: 'A-002', title: 'Same Title', type: 'guideline' }),
297
321
  ]);
298
- const audit = new KnowledgeAudit({ baseDir: dir });
299
322
  const report = audit.run();
300
323
  const dupes = report.issues.filter(i => i.rule === 'title-duplicate');
301
324
  expect(dupes.length).toBe(0);
302
- fs.rmSync(dir, { recursive: true });
303
325
  });
304
326
  });
305
327
 
306
328
  // ── D3: source-refs-bloat ──────────────────────────────
307
329
 
308
330
  describe('D3: source-refs-bloat', () => {
331
+ const bloatRefs = () => Array.from({ length: 25 }, (_, i) => ({
332
+ workflow: `wf-${i}`,
333
+ timestamp: new Date().toISOString(),
334
+ }));
335
+
309
336
  it('flags entries with >20 source references', () => {
310
- const dir = makeTmpDir();
311
- const refs = Array.from({ length: 25 }, (_, i) => ({
312
- workflow: `wf-${i}`,
313
- timestamp: new Date().toISOString(),
314
- }));
315
- setupStore(dir, [{ id: 'B-001', sourceReferences: refs }]);
316
- const audit = new KnowledgeAudit({ baseDir: dir });
337
+ const { audit } = memoryAudit([makeEntry({ id: 'B-001', sourceReferences: bloatRefs() })]);
317
338
  const report = audit.run();
318
339
  expect(report.issues.some(i => i.rule === 'source-refs-bloat')).toBe(true);
319
- fs.rmSync(dir, { recursive: true });
320
340
  });
321
341
 
322
342
  it('trims source references on auto-fix', () => {
323
343
  const dir = makeTmpDir();
324
- const refs = Array.from({ length: 25 }, (_, i) => ({
325
- workflow: `wf-${i}`,
326
- timestamp: new Date().toISOString(),
327
- }));
328
- setupStore(dir, [{ id: 'B-001', sourceReferences: refs, title: 'Valid Knowledge Title' }]);
329
- const audit = new KnowledgeAudit({ baseDir: dir });
344
+ setupStore(dir, [{ id: 'B-001', sourceReferences: bloatRefs(), title: 'Valid Knowledge Title' }]);
345
+ const store = new FileKnowledgeStore({ baseDir: dir });
346
+ const audit = new KnowledgeAudit(store);
330
347
  const report = audit.run({ autoFix: true });
331
348
  expect(report.autoFixed).toBeGreaterThan(0);
332
349
  // re-read from disk after fix
333
- const store2 = new KnowledgeStore({ baseDir: dir });
350
+ const store2 = new FileKnowledgeStore({ baseDir: dir });
334
351
  const updated = store2.get('B-001');
335
352
  expect(updated!.sourceReferences.length).toBe(20);
336
353
  fs.rmSync(dir, { recursive: true });
@@ -341,33 +358,22 @@ describe('D3: source-refs-bloat', () => {
341
358
 
342
359
  describe('D4: promotion-blocked', () => {
343
360
  it('flags draft entries older than 30 days with no references', () => {
344
- const dir = makeTmpDir();
345
361
  const oldDate = new Date(Date.now() - 60 * 24 * 3600_000).toISOString();
346
- setupStore(dir, [{
347
- id: 'C-001',
348
- maturity: 'draft',
349
- created: oldDate,
350
- lastReferenced: '',
351
- }]);
352
- const audit = new KnowledgeAudit({ baseDir: dir, promotionBlockDays: 30 });
362
+ const { audit } = memoryAudit(
363
+ [makeEntry({ id: 'C-001', maturity: 'draft', created: oldDate, lastReferenced: '' })],
364
+ { promotionBlockDays: 30 }
365
+ );
353
366
  const report = audit.run();
354
367
  expect(report.issues.some(i => i.rule === 'promotion-blocked')).toBe(true);
355
- fs.rmSync(dir, { recursive: true });
356
368
  });
357
369
 
358
370
  it('ignores non-draft entries', () => {
359
- const dir = makeTmpDir();
360
371
  const oldDate = new Date(Date.now() - 60 * 24 * 3600_000).toISOString();
361
- setupStore(dir, [{
362
- id: 'C-002',
363
- maturity: 'verified',
364
- created: oldDate,
365
- lastReferenced: '',
366
- }]);
367
- const audit = new KnowledgeAudit({ baseDir: dir });
372
+ const { audit } = memoryAudit([
373
+ makeEntry({ id: 'C-002', maturity: 'verified', created: oldDate, lastReferenced: '' }),
374
+ ]);
368
375
  const report = audit.run();
369
376
  expect(report.issues.filter(i => i.rule === 'promotion-blocked')).toHaveLength(0);
370
- fs.rmSync(dir, { recursive: true });
371
377
  });
372
378
  });
373
379
 
@@ -375,33 +381,19 @@ describe('D4: promotion-blocked', () => {
375
381
 
376
382
  describe('D4: orphan-draft', () => {
377
383
  it('flags drafts with no contributors/projects/references', () => {
378
- const dir = makeTmpDir();
379
- setupStore(dir, [{
380
- id: 'D-001',
381
- maturity: 'draft',
382
- contributors: [],
383
- projects: [],
384
- referencedBy: [],
385
- }]);
386
- const audit = new KnowledgeAudit({ baseDir: dir });
384
+ const { audit } = memoryAudit([
385
+ makeEntry({ id: 'D-001', maturity: 'draft', contributors: [], projects: [], referencedBy: [] }),
386
+ ]);
387
387
  const report = audit.run();
388
388
  expect(report.issues.some(i => i.rule === 'orphan-draft')).toBe(true);
389
- fs.rmSync(dir, { recursive: true });
390
389
  });
391
390
 
392
391
  it('passes for drafts with contributors', () => {
393
- const dir = makeTmpDir();
394
- setupStore(dir, [{
395
- id: 'D-002',
396
- maturity: 'draft',
397
- contributors: ['user-a'],
398
- projects: [],
399
- referencedBy: [],
400
- }]);
401
- const audit = new KnowledgeAudit({ baseDir: dir });
392
+ const { audit } = memoryAudit([
393
+ makeEntry({ id: 'D-002', maturity: 'draft', contributors: ['user-a'], projects: [], referencedBy: [] }),
394
+ ]);
402
395
  const report = audit.run();
403
396
  expect(report.issues.filter(i => i.rule === 'orphan-draft')).toHaveLength(0);
404
- fs.rmSync(dir, { recursive: true });
405
397
  });
406
398
  });
407
399
 
@@ -409,31 +401,22 @@ describe('D4: orphan-draft', () => {
409
401
 
410
402
  describe('D5: stale-entry', () => {
411
403
  it('flags entries older than staleDays threshold', () => {
412
- const dir = makeTmpDir();
413
404
  const oldDate = new Date(Date.now() - 120 * 24 * 3600_000).toISOString();
414
- setupStore(dir, [{
415
- id: 'E-001',
416
- maturity: 'verified',
417
- lastReferenced: oldDate,
418
- }]);
419
- const audit = new KnowledgeAudit({ baseDir: dir, staleDays: 90 });
405
+ const { audit } = memoryAudit(
406
+ [makeEntry({ id: 'E-001', maturity: 'verified', lastReferenced: oldDate })],
407
+ { staleDays: 90 }
408
+ );
420
409
  const report = audit.run();
421
410
  expect(report.issues.some(i => i.rule === 'stale-entry')).toBe(true);
422
- fs.rmSync(dir, { recursive: true });
423
411
  });
424
412
 
425
413
  it('ignores archived entries', () => {
426
- const dir = makeTmpDir();
427
414
  const oldDate = new Date(Date.now() - 120 * 24 * 3600_000).toISOString();
428
- setupStore(dir, [{
429
- id: 'E-002',
430
- maturity: 'archived',
431
- lastReferenced: oldDate,
432
- }]);
433
- const audit = new KnowledgeAudit({ baseDir: dir });
415
+ const { audit } = memoryAudit([
416
+ makeEntry({ id: 'E-002', maturity: 'archived', lastReferenced: oldDate }),
417
+ ]);
434
418
  const report = audit.run();
435
419
  expect(report.issues.filter(i => i.rule === 'stale-entry')).toHaveLength(0);
436
- fs.rmSync(dir, { recursive: true });
437
420
  });
438
421
  });
439
422
 
@@ -441,23 +424,20 @@ describe('D5: stale-entry', () => {
441
424
 
442
425
  describe('D6: flywheel dimension', () => {
443
426
  it('computes refCoverage from referencedBy', () => {
444
- const dir = makeTmpDir();
445
- setupStore(dir, [
446
- { id: 'F-001', maturity: 'verified', referencedBy: ['agent-1', 'agent-2'] },
447
- { id: 'F-002', maturity: 'verified', referencedBy: [] },
448
- { id: 'F-003', maturity: 'verified', referencedBy: ['agent-1'] },
427
+ const { audit } = memoryAudit([
428
+ makeEntry({ id: 'F-001', maturity: 'verified', referencedBy: ['agent-1', 'agent-2'] }),
429
+ makeEntry({ id: 'F-002', maturity: 'verified', referencedBy: [] }),
430
+ makeEntry({ id: 'F-003', maturity: 'verified', referencedBy: ['agent-1'] }),
449
431
  ]);
450
- const audit = new KnowledgeAudit({ baseDir: dir });
451
432
  const report = audit.run();
452
433
  // 2/3 entries have refs → 67% coverage
453
434
  expect(report.dimensions.flywheel.details.refCoverage).toBe(67);
454
435
  expect(report.dimensions.flywheel.score).toBeGreaterThan(0);
455
- fs.rmSync(dir, { recursive: true });
456
436
  });
457
437
 
458
438
  it('gives 0 score for empty store (no entries to measure)', () => {
459
439
  const dir = makeTmpDir();
460
- const audit = new KnowledgeAudit({ baseDir: dir });
440
+ const audit = new KnowledgeAudit(new FileKnowledgeStore({ baseDir: dir }));
461
441
  const report = audit.run();
462
442
  // 0/0 = no data → score 0 (no flywheel activity)
463
443
  expect(report.dimensions.flywheel.score).toBe(0);
@@ -473,7 +453,7 @@ describe('D6: flywheel dimension', () => {
473
453
  ]);
474
454
 
475
455
  // Without consumption stats → low D6 (no refs)
476
- const audit1 = new KnowledgeAudit({ baseDir: dir });
456
+ const audit1 = new KnowledgeAudit(new FileKnowledgeStore({ baseDir: dir }));
477
457
  const report1 = audit1.run();
478
458
  const baseD6 = report1.dimensions.flywheel.score;
479
459
 
@@ -483,8 +463,8 @@ describe('D6: flywheel dimension', () => {
483
463
  JSON.stringify({ date: '2026-06-02', dailyEvents: 10, searchHits: 5 }),
484
464
  );
485
465
 
486
- // With consumption stats → higher D6
487
- const audit2 = new KnowledgeAudit({ baseDir: dir });
466
+ // With consumption stats → higher D6(取数口是 store.getConsumptionStats,#134)
467
+ const audit2 = new KnowledgeAudit(new FileKnowledgeStore({ baseDir: dir }));
488
468
  const report2 = audit2.run();
489
469
  expect(report2.dimensions.flywheel.score).toBeGreaterThan(baseD6);
490
470
  expect(report2.dimensions.flywheel.details.dailyConsumptionEvents).toBe(10);
@@ -493,30 +473,49 @@ describe('D6: flywheel dimension', () => {
493
473
  });
494
474
  });
495
475
 
476
+ // ── D7: incremental dimension ──────────────────────────
477
+
478
+ describe('D7: incremental dimension(环境数据经 store 供给,#134)', () => {
479
+ it('无可用快照 → 满分并说明无数据', () => {
480
+ const { audit } = memoryAudit([makeEntry({ id: 'S-001', maturity: 'verified' })]);
481
+ const report = audit.run();
482
+ expect(report.dimensions.incremental).toEqual({
483
+ score: 100,
484
+ issues: 0,
485
+ details: { note: 'no 30d snapshot available' },
486
+ });
487
+ });
488
+
489
+ it('有 30 天快照 → 分数即存活率,问题数即流失数', () => {
490
+ const { audit, store } = memoryAudit([makeEntry({ id: 'S-001', maturity: 'verified' })]);
491
+ store.survival = { rate: 75, survived: 3, total: 4, snapshotDate: '2026-08-15' };
492
+ const report = audit.run();
493
+ expect(report.dimensions.incremental).toEqual({
494
+ score: 75,
495
+ issues: 1,
496
+ details: { survivalRate: 75, survived: 3, total: 4, snapshotDate: '2026-08-15' },
497
+ });
498
+ });
499
+ });
500
+
496
501
  // ── Health score ────────────────────────────────────────
497
502
 
498
503
  describe('health score', () => {
499
504
  it('returns 100 for clean store', () => {
500
- const dir = makeTmpDir();
501
- setupStore(dir, [
502
- { id: 'G-001', maturity: 'verified', content: 'a'.repeat(100), title: 'Unique Title A' },
503
- { id: 'G-002', maturity: 'proven', content: 'b'.repeat(100), title: 'Unique Title B' },
505
+ const { audit } = memoryAudit([
506
+ makeEntry({ id: 'G-001', maturity: 'verified', content: 'a'.repeat(100), title: 'Unique Title A' }),
507
+ makeEntry({ id: 'G-002', maturity: 'proven', content: 'b'.repeat(100), title: 'Unique Title B' }),
504
508
  ]);
505
- const audit = new KnowledgeAudit({ baseDir: dir });
506
509
  const report = audit.run();
507
510
  expect(report.healthScore.before).toBe(100);
508
- fs.rmSync(dir, { recursive: true });
509
511
  });
510
512
 
511
513
  it('decreases with critical issues', () => {
512
- const dir = makeTmpDir();
513
- setupStore(dir, [
514
- { id: 'G-003', maturity: 'proven', content: 'tiny', title: 'Test Entry' },
514
+ const { audit } = memoryAudit([
515
+ makeEntry({ id: 'G-003', maturity: 'proven', content: 'tiny', title: 'Test Entry' }),
515
516
  ]);
516
- const audit = new KnowledgeAudit({ baseDir: dir });
517
517
  const report = audit.run();
518
518
  expect(report.healthScore.before).toBeLessThan(100);
519
- fs.rmSync(dir, { recursive: true });
520
519
  });
521
520
  });
522
521
 
@@ -524,37 +523,58 @@ describe('health score', () => {
524
523
 
525
524
  describe('auto-fix', () => {
526
525
  it('archives test data entries', () => {
527
- const dir = makeTmpDir();
528
- const store = setupStore(dir, [
529
- { id: 'H-001', tags: ['test-scope-abc'], maturity: 'verified' },
526
+ const { audit, store } = memoryAudit([
527
+ makeEntry({ id: 'H-001', tags: ['test-scope-abc'], maturity: 'verified' }),
530
528
  ]);
531
- const audit = new KnowledgeAudit({ baseDir: dir });
532
529
  const report = audit.run({ autoFix: true });
533
530
  expect(report.autoFixed).toBeGreaterThan(0);
534
531
  expect(store.get('H-001')!.maturity).toBe('archived');
535
- fs.rmSync(dir, { recursive: true });
536
532
  });
537
533
 
538
534
  it('demotes zero-content proven entries', () => {
539
- const dir = makeTmpDir();
540
- const store = setupStore(dir, [
541
- { id: 'H-002', maturity: 'proven', content: 'tiny', title: 'Normal Title' },
535
+ const { audit, store } = memoryAudit([
536
+ makeEntry({ id: 'H-002', maturity: 'proven', content: 'tiny', title: 'Normal Title' }),
542
537
  ]);
543
- const audit = new KnowledgeAudit({ baseDir: dir });
544
538
  audit.run({ autoFix: true });
545
539
  expect(store.get('H-002')!.maturity).toBe('draft');
546
- fs.rmSync(dir, { recursive: true });
547
540
  });
548
541
 
549
542
  it('flags short content entries with low_quality tag', () => {
550
- const dir = makeTmpDir();
551
- const store = setupStore(dir, [
552
- { id: 'H-003', maturity: 'draft', content: 'a'.repeat(30), title: 'Normal Title 2' },
543
+ const { audit, store } = memoryAudit([
544
+ makeEntry({ id: 'H-003', maturity: 'draft', content: 'a'.repeat(30), title: 'Normal Title 2' }),
553
545
  ]);
554
- const audit = new KnowledgeAudit({ baseDir: dir });
555
546
  audit.run({ autoFix: true });
556
547
  expect(store.get('H-003')!.tags).toContain('low_quality');
557
- fs.rmSync(dir, { recursive: true });
548
+ });
549
+
550
+ it('整批修复走一次 applyAll(不再逐条 update)', () => {
551
+ const { audit, store } = memoryAudit([
552
+ makeEntry({ id: 'H-004', tags: ['test-scope-a'], maturity: 'verified', title: 'Noise One' }),
553
+ makeEntry({ id: 'H-005', tags: ['test-scope-b'], maturity: 'verified', title: 'Noise Two' }),
554
+ makeEntry({ id: 'H-006', tags: ['test-scope-c'], maturity: 'verified', title: 'Noise Three' }),
555
+ ]);
556
+ const report = audit.run({ autoFix: true });
557
+ expect(report.autoFixed).toBe(3); // 正对照:3 条真修复
558
+ expect(store.applyAllCalls).toBe(1);
559
+ });
560
+
561
+ it('无修复项时不触发批量写', () => {
562
+ const { audit, store } = memoryAudit([
563
+ makeEntry({ id: 'H-007', maturity: 'verified', content: 'a'.repeat(100), title: 'Clean Title' }),
564
+ ]);
565
+ const report = audit.run({ autoFix: true });
566
+ expect(report.autoFixed).toBe(0);
567
+ expect(store.applyAllCalls).toBe(0);
568
+ });
569
+
570
+ it('reject 类问题(frontmatter 缺失)只报告、不产生任何修复动作', () => {
571
+ const { audit, store } = memoryAudit([
572
+ makeEntry({ id: '' as any, maturity: 'verified', title: 'Blank Id Title' }),
573
+ ]);
574
+ const report = audit.run({ autoFix: true });
575
+ expect(report.issues.some(i => i.action === 'reject')).toBe(true);
576
+ expect(report.autoFixed).toBe(0);
577
+ expect(store.applyAllCalls).toBe(0);
558
578
  });
559
579
  });
560
580
 
@@ -562,54 +582,72 @@ describe('auto-fix', () => {
562
582
 
563
583
  describe('D2b: deprecated-domain', () => {
564
584
  it('flags entry with "pipeline" tag', () => {
565
- const dir = makeTmpDir();
566
- const audit = new KnowledgeAudit({ baseDir: dir });
585
+ const { audit } = memoryAudit([]);
567
586
  const entry = makeEntry({ tags: ['pipeline', 'deployment'] });
568
587
  const issues = audit.validate(entry);
569
588
  const dd = issues.filter(i => i.rule === 'deprecated-domain');
570
589
  expect(dd.length).toBe(1);
571
590
  expect(dd[0].action).toBe('archive');
572
591
  expect(dd[0].severity).toBe('high');
573
- fs.rmSync(dir, { recursive: true });
574
592
  });
575
593
 
576
594
  it('flags entry with "pipeline" in title', () => {
577
- const dir = makeTmpDir();
578
- const audit = new KnowledgeAudit({ baseDir: dir });
595
+ const { audit } = memoryAudit([]);
579
596
  const entry = makeEntry({ title: 'Pipeline 工作单元不可见导致协作效率低' });
580
597
  const issues = audit.validate(entry);
581
598
  const dd = issues.filter(i => i.rule === 'deprecated-domain');
582
599
  expect(dd.length).toBe(1);
583
- fs.rmSync(dir, { recursive: true });
584
600
  });
585
601
 
586
602
  it('flags entry with "管线" in title', () => {
587
- const dir = makeTmpDir();
588
- const audit = new KnowledgeAudit({ baseDir: dir });
603
+ const { audit } = memoryAudit([]);
589
604
  const entry = makeEntry({ title: 'B51 管线端到端验证常量与测试' });
590
605
  const issues = audit.validate(entry);
591
606
  const dd = issues.filter(i => i.rule === 'deprecated-domain');
592
607
  expect(dd.length).toBe(1);
593
- fs.rmSync(dir, { recursive: true });
594
608
  });
595
609
 
596
610
  it('does not flag entry without deprecated domain references', () => {
597
- const dir = makeTmpDir();
598
- const audit = new KnowledgeAudit({ baseDir: dir });
611
+ const { audit } = memoryAudit([]);
599
612
  const entry = makeEntry({ title: 'Agent Network 核心概念', tags: ['agent', 'architecture'] });
600
613
  const issues = audit.validate(entry);
601
614
  const dd = issues.filter(i => i.rule === 'deprecated-domain');
602
615
  expect(dd.length).toBe(0);
603
- fs.rmSync(dir, { recursive: true });
604
616
  });
605
617
 
606
618
  it('skips already archived entries', () => {
607
- const dir = makeTmpDir();
608
- const audit = new KnowledgeAudit({ baseDir: dir });
619
+ const { audit } = memoryAudit([]);
609
620
  const entry = makeEntry({ title: 'Pipeline legacy entry', maturity: 'archived', tags: ['pipeline'] });
610
621
  const issues = audit.validate(entry);
611
622
  const dd = issues.filter(i => i.rule === 'deprecated-domain');
612
623
  expect(dd.length).toBe(0);
624
+ });
625
+ });
626
+
627
+ // ── 构造形状(harness#134 验收 3)───────────────────────
628
+
629
+ describe('构造收 store', () => {
630
+ it('validate() 全程不碰存储(判定不依赖 fs)', () => {
631
+ const forbidden = new Proxy({} as KnowledgeStore, {
632
+ get(_target, prop) {
633
+ throw new Error(`validate() 不该读存储(碰了 ${String(prop)})`);
634
+ },
635
+ });
636
+ const audit = new KnowledgeAudit(forbidden);
637
+ const issues = audit.validate(makeEntry({ title: 'Normal knowledge entry' }));
638
+ expect(Array.isArray(issues)).toBe(true);
639
+ });
640
+
641
+ it('与兄弟模块同形:lint / doctor / query / lifecycle / ingest 一样收 store', async () => {
642
+ const dir = makeTmpDir();
643
+ const store = setupStore(dir, [{ id: 'W-001', title: 'Wiring Entry' }]);
644
+ const { KnowledgeLifecycle } = await import('../lifecycle');
645
+ const { KnowledgeQuery } = await import('../query');
646
+ expect(() => new KnowledgeAudit(store)).not.toThrow();
647
+ expect(new KnowledgeAudit(store).run().totalEntries).toBe(1);
648
+ // 兄弟模块的构造第一形参就是 store
649
+ expect(new KnowledgeLifecycle(store)).toBeInstanceOf(KnowledgeLifecycle);
650
+ expect(new KnowledgeQuery(store)).toBeInstanceOf(KnowledgeQuery);
613
651
  fs.rmSync(dir, { recursive: true });
614
652
  });
615
653
  });
@@ -618,9 +656,7 @@ describe('D2b: deprecated-domain', () => {
618
656
 
619
657
  describe('run() report', () => {
620
658
  it('returns correct report structure', () => {
621
- const dir = makeTmpDir();
622
- setupStore(dir, [{ id: 'I-001' }]);
623
- const audit = new KnowledgeAudit({ baseDir: dir });
659
+ const { audit } = memoryAudit([makeEntry({ id: 'I-001' })]);
624
660
  const report = audit.run();
625
661
  expect(report).toHaveProperty('timestamp');
626
662
  expect(report).toHaveProperty('totalEntries');
@@ -637,18 +673,65 @@ describe('run() report', () => {
637
673
  expect(report.dimensions).toHaveProperty('maturity');
638
674
  expect(report.dimensions).toHaveProperty('freshness');
639
675
  expect(report.dimensions).toHaveProperty('flywheel');
640
- fs.rmSync(dir, { recursive: true });
641
676
  });
642
677
 
643
678
  it('summary counts match issue counts', () => {
644
- const dir = makeTmpDir();
645
- setupStore(dir, [
646
- { id: 'I-002', title: 'Test Entry', maturity: 'proven', content: 'tiny' },
679
+ const { audit } = memoryAudit([
680
+ makeEntry({ id: 'I-002', title: 'Test Entry', maturity: 'proven', content: 'tiny' }),
647
681
  ]);
648
- const audit = new KnowledgeAudit({ baseDir: dir });
649
682
  const report = audit.run();
650
683
  const totalFromSummary = Object.values(report.summary).reduce((a, b) => a + b, 0);
651
684
  expect(totalFromSummary).toBe(report.issues.length);
652
- fs.rmSync(dir, { recursive: true });
685
+ });
686
+ });
687
+
688
+ // ── 构造器数值槽守卫(harness#163)─────────────────────────
689
+ //
690
+ // 三个数值槽显式传入 NaN / ±Infinity / 负数时构造期抛 TypeError(含槽名与实参),
691
+ // 不再放行到打分层静默关掉 short-content / stale-entry / promotion-blocked 三条判定。
692
+ // 未传 / 显式 undefined → 缺省;显式 0 → 合法且按 0 生效(不被兜成缺省)。
693
+
694
+ describe('构造器数值槽守卫(harness#163)', () => {
695
+ const slots = ['shortContentThreshold', 'staleDays', 'promotionBlockDays'] as const;
696
+ const dirtyValues = [NaN, Infinity, -Infinity, -1];
697
+
698
+ for (const slot of slots) {
699
+ for (const value of dirtyValues) {
700
+ it(`${slot} 显式传 ${String(value)} → 构造抛 TypeError,message 含槽名与实参`, () => {
701
+ expect(() => memoryAudit([], { [slot]: value })).toThrow(TypeError);
702
+ expect(() => memoryAudit([], { [slot]: value })).toThrow(new RegExp(`${slot}.*${String(value).replace(/[+-]/g, '\\$&')}`));
703
+ });
704
+ }
705
+ }
706
+
707
+ it('不传 options → 缺省生效(短内容阈值 50)', () => {
708
+ const { audit } = memoryAudit([]);
709
+ const entry = makeEntry({ content: 'a'.repeat(25), maturity: 'draft' });
710
+ expect(audit.validate(entry).some(i => i.rule === 'short-content')).toBe(true);
711
+ });
712
+
713
+ it('显式 undefined → 与未传同果,走缺省', () => {
714
+ const { audit } = memoryAudit([], { shortContentThreshold: undefined, staleDays: undefined, promotionBlockDays: undefined });
715
+ const entry = makeEntry({ content: 'a'.repeat(25), maturity: 'draft' });
716
+ expect(audit.validate(entry).some(i => i.rule === 'short-content')).toBe(true);
717
+ });
718
+
719
+ it('显式 0 → 合法且按 0 生效:short-content 不触发(不被兜回 50)', () => {
720
+ const { audit } = memoryAudit([], { shortContentThreshold: 0 });
721
+ const entry = makeEntry({ content: 'a'.repeat(25), maturity: 'draft' });
722
+ expect(audit.validate(entry).some(i => i.rule === 'short-content')).toBe(false);
723
+ });
724
+
725
+ it('staleDays 显式 0 → 按 0 生效(昨天引用的条目即判 stale,不被兜回 90)', () => {
726
+ const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
727
+ const { audit } = memoryAudit([], { staleDays: 0 });
728
+ const entry = makeEntry({ lastReferenced: yesterday, maturity: 'proven' });
729
+ expect(audit.validate(entry).some(i => i.rule === 'stale-entry')).toBe(true);
730
+ });
731
+
732
+ it('合法正有限值(含非整数)→ 行为不变', () => {
733
+ const { audit } = memoryAudit([], { shortContentThreshold: 30.5 });
734
+ expect(audit.validate(makeEntry({ content: 'a'.repeat(30), maturity: 'draft' })).some(i => i.rule === 'short-content')).toBe(true);
735
+ expect(audit.validate(makeEntry({ content: 'a'.repeat(40), maturity: 'draft' })).some(i => i.rule === 'short-content')).toBe(false);
653
736
  });
654
737
  });