@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
@@ -11,9 +11,12 @@
11
11
  export type HookPhase = 'before' | 'after' | 'around';
12
12
 
13
13
  /**
14
- * Hook 错误处理策略
14
+ * Hook 错误处理策略(有效集合:'block' | 'warn')
15
+ *
16
+ * #159 起原 'ignore' 退出有效面:策略的唯一声明点是 HookConfig(必填),
17
+ * 不存在「未声明 strategy」的运行时形态。
15
18
  */
16
- export type HookErrorStrategy = 'block' | 'warn' | 'ignore';
19
+ export type HookErrorStrategy = 'block' | 'warn';
17
20
 
18
21
  /**
19
22
  * Hook 配置(G7 归一后形状:consumer 侧的 per-hook 声明表)
@@ -22,6 +25,10 @@ export type HookErrorStrategy = 'block' | 'warn' | 'ignore';
22
25
  * - 声明(HookConfig)是「定义」侧:引用未注册实现 → 抛错
23
26
  * - 注册(HookDefinition)是「实现」侧:注册无对应声明 → 抛错
24
27
  *
28
+ * #159 起本表是 `enabled` / `errorStrategy` 的**唯一声明点**:
29
+ * HookDefinition 不再携带这两个字段,有效值在注册环节由本表填充
30
+ * (见 EffectiveHook),声明与实现两处值矛盾在构造上不可能出现。
31
+ *
25
32
  * 对应 studio 侧 per-hook 运行时配置(hooks/config.ts 的 DEFAULTS)语义:
26
33
  * blocking:true ↔ errorStrategy 'block'(失败阻断管线),
27
34
  * blocking:false ↔ errorStrategy 'warn'(记录警告继续)——无损映射见
@@ -33,13 +40,14 @@ export interface HookConfig {
33
40
  /** 是否启用(false 时不进入管线) */
34
41
  enabled: boolean;
35
42
  /** 错误策略:block=失败阻断管线,warn=记录警告继续 */
36
- errorStrategy: 'block' | 'warn';
43
+ errorStrategy: HookErrorStrategy;
37
44
  }
38
45
 
39
46
  /**
40
47
  * Hook 定义
41
48
  *
42
49
  * Consumer 自行选择 name(如 'beforeAgentExecute'),harness 只提供管线。
50
+ * `enabled` / `errorStrategy` 不在此处声明——唯一声明点是 HookConfig(#159)。
43
51
  */
44
52
  export interface HookDefinition<C = unknown, R = unknown> {
45
53
  /** Hook 唯一名称(consumer 定义语义) */
@@ -48,16 +56,25 @@ export interface HookDefinition<C = unknown, R = unknown> {
48
56
  phase: HookPhase;
49
57
  /** 优先级(越小越先执行,默认 100) */
50
58
  priority?: number;
51
- /** 错误策略:block=阻断管线, warn=记录警告继续, ignore=静默跳过 */
52
- errorStrategy?: HookErrorStrategy;
53
59
  /** 采样率 0-1(1=100% 执行,0.1=10% 采样) */
54
60
  sampleRate?: number;
55
- /** 是否启用 */
56
- enabled?: boolean;
57
61
  /** Hook 执行函数 */
58
62
  execute: (context: C) => Promise<HookResult<R>>;
59
63
  }
60
64
 
65
+ /**
66
+ * 有效 hook = HookDefinition + 注册环节由 HookConfig 填充的有效值
67
+ *
68
+ * 管线与注册表的判定(启用过滤、block/warn 分派)只读本类型,
69
+ * 值来源单一(HookConfig),不存在第二声明点。
70
+ */
71
+ export interface EffectiveHook<C = unknown, R = unknown> extends HookDefinition<C, R> {
72
+ /** 是否启用(来自 HookConfig.enabled) */
73
+ enabled: boolean;
74
+ /** 错误策略(来自 HookConfig.errorStrategy) */
75
+ errorStrategy: HookErrorStrategy;
76
+ }
77
+
61
78
  /**
62
79
  * Hook 执行结果
63
80
  */
@@ -84,6 +101,8 @@ export interface HookExecutionRecord {
84
101
  passed: boolean;
85
102
  error?: string;
86
103
  sampled?: boolean;
104
+ /** 未执行标记之二:enabled:false 被 runOne 跳过(实现体零调用,镜像 sampled 先例) */
105
+ skipped?: boolean;
87
106
  }
88
107
 
89
108
  /**
package/src/index.ts CHANGED
@@ -152,7 +152,6 @@ export {
152
152
  registeredGateCount,
153
153
  assertGateRegistryClosed,
154
154
  runGates,
155
- getEffectiveGates,
156
155
  createCheckerGate,
157
156
  } from './gates';
158
157
  export type {
@@ -163,7 +162,6 @@ export type {
163
162
  GateDecisionStatus,
164
163
  GateDefinition,
165
164
  GateRunResult,
166
- GatesConfig,
167
165
  PerformanceThresholds,
168
166
  ReviewGateConfig,
169
167
  SecurityGateConfig,
@@ -357,6 +355,7 @@ export {
357
355
  export type {
358
356
  HookDefinition,
359
357
  HookConfig,
358
+ EffectiveHook,
360
359
  HookErrorStrategy,
361
360
  HookExecutionRecord,
362
361
  HookPhase,
@@ -17,13 +17,16 @@
17
17
  - 所有模式支持 `decayAt` 硬过期
18
18
 
19
19
  ## 核心导出
20
- - `KnowledgeStore` 知识条目 CRUD + 结构化存储;`saveAll()` 批量写入(循环内只更新内存索引、结束一次 writeIndex,harness#107)
20
+ - `KnowledgeStore`(类型面)/ `FileKnowledgeStore`(`store.ts`,barrel 与包根交出的**值**符号——store 的文件系统实现,构造收 `{ baseDir }`;仓内构造点两处:`cli/commands/knowledge-view.ts` 的 `openKnowledgeStore()`(knowledge 各子命令的取数口)与 `cli/commands/constraints-retire.ts` 的退役沉淀写入)— 知识条目 CRUD + 结构化存储;`saveAll()` 全量批量写入(循环内只更新内存索引、结束一次 writeIndex,harness#107)、`applyAll()` 按 id 部分更新的批量出口(`update(id, partial)` 的批量形,语义含未知 id 逐条跳过与空批零读写,harness#134)、`getConsumptionStats()` 读 `.consumption-stats.json` 的当日消费计数(打分核心唯一的消费统计取数口,harness#134
21
21
  - `KnowledgeQuery` — 语义搜索 + 类型/标签过滤 + `queryByMode()` + `consume(taskContext)`;`query()` 是 budget 截断管线(仅用于 prompt 注入),`search()` 是全语料文本搜索(先匹配→排序→limit,交互搜索专用,勿用 query() 代替)
22
- - `KnowledgeLifecycle` — per-mode 生命周期管理
22
+ - `KnowledgeLifecycle` — per-mode 生命周期管理;缺省阈值常量 `DEFAULT_DECAY_CONFIG`(`provenDecayMonths: 12` / `verifiedDecayMonths: 6` / `draftDecayMonths: 3` / `autoPromoteSources: []`,正本 `types.ts`)经构造器 `{...DEFAULT_DECAY_CONFIG, ...config}` 合并,barrel 与包根一并交出
23
+ - `extractCodeStructure(dir)`(`primitives/code-structure.ts`,配 `CodeStructure`/`DeclarationInfo`/`ImportInfo` 三类型对外)— 目录代码结构提取;仓内零调用方,是留给下游的公开面
23
24
  - `KnowledgeIngest` — 知识摄取引擎(含 ingest gate 质量门)+ `ingestExternal()` 外部内容摄入
24
25
  - `sanitizeExternalContent` — 外部内容安全清洗(过滤注入模式 + 长度限制)
25
- - `migrateKnowledgeEntries` — AS-021 迁移工具(为现有条目添加 consumptionMode/origin
26
- - `KnowledgeAudit` — 7 维度质量审计引擎。per-entry 规则记录带 `scope: 'active' | 'all'`(active = 不判定 archived 条目);人口过滤由 `ruleDetail()` 统一执行,规则内禁止再写 `entry.maturity === 'archived'` 早返回。规则中文 label 正本在规则定义的 `label` 字段(harness#109,ADR-0002 定义即注册),`AUDIT_RULE_LABELS` 派生导出、与 `AuditRuleName` 编译期闭环,CLI 展示层直接消费、禁止另建 label 表
26
+ - `migrateKnowledgeEntries` — AS-021 迁移工具(为现有条目添加 consumptionMode/origin);条目人口按 `tree-walker` 口径判定,`_index.md` 等树生成物不计入 `total`/`errors`(harness#134 修此前假阳性)
27
+ - `KnowledgeAudit` — 7 维度质量审计**引擎**:构造收 `KnowledgeStore`(与 lint / doctor / query / lifecycle / ingest 同形,harness#134——此前收目录并自建 store,致使打分判定无法脱离文件系统测试)+ 可选阈值形参(三个数值槽显式传 NaN/±Infinity/负数时构造期抛 `TypeError`(message 含槽名与实参),不放行到打分层静默关掉 short-content/stale-entry/promotion-blocked 三条判定;未传/显式 `undefined` 走缺省、显式 `0` 合法按 0 生效,harness#163)。职责只剩装配:取条目、经 store 取环境数据、把整批修复动作交 `store.applyAll()`。per-entry 规则记录带 `scope: 'active' | 'all'`(active = 不判定 archived 条目);人口过滤由 `ruleDetail()` 统一执行,规则内禁止再写 `entry.maturity === 'archived'` 早返回。规则中文 label 正本在规则定义的 `label` 字段(harness#109,ADR-0002 定义即注册),`AUDIT_RULE_LABELS` 派生导出、与 `AuditRuleName` 编译期闭环,CLI 展示层直接消费、禁止另建 label 表
28
+ - `audit-scoring`(**值面**不进导出面;类型面是例外——`AuditRuleName`/`AuditAction`/`AuditIssue`/`AuditReport`/`AuditOptions` 经 `index.ts` 再导出并上包根,ADR-0003 显式清单)— D1–D7 打分**纯模块**(harness#134):规则表、`scanEntries()` / `summarizeIssues()` / `computeDimensions()` / `calculateHealthScore()`,零 fs——环境数据(消费统计、快照存活)由调用方经 store 取好喂入。审计判定的唯一正本在此,可脱离合建目录测试
29
+ - `tree-walker`(包内,不进导出面)— 知识树排除口径单点(harness#134):`isEntryFile()` / `isInfraDir()` / `INDEX_MD_FILE` / `SNAPSHOTS_DIR`,由 store 顶层扫描、migration 顶层扫描、index-generator 递归扫描三处共同消费
27
30
  - `flywheel-metrics`(包内,不进导出面)— 知识飞轮指标唯一实现:`evaluateFlywheel(env)` 出 canonical 比例、`genuineRefs()` 出 synthetic 过滤口径,audit D6 / `knowledge stats` / `knowledge health` 三处共消费(ADR-0013)
28
31
  - `ReferenceTracker` — 知识引用关系图谱
29
32
  - `KnowledgeLinter` — 知识质量检查(完整性/一致性/时效性)
@@ -31,19 +34,22 @@
31
34
  - `KnowledgeHealthScorer` — 知识健康评分(doctor.ts)
32
35
 
33
36
  ## 依赖关系
34
- - 依赖 `src/types/` 知识相关类型
37
+ - 知识类型正本在**本模块** `src/knowledge/types.ts`(`KnowledgeEntry`/`MaturityLevel`/`ConsumptionMode`/`KnowledgeOrigin`/`DecayConfig`…);本目录对 `src/types/` **零 import**——旧条目「依赖 `src/types/` 知识相关类型」不实(harness#142 逐条核对)
35
38
  - 依赖 `src/utils/frontmatter` — markdown frontmatter 解析/序列化的唯一正本(harness#89:store、migration、index-generator、ingest 共用同一套语法判定,不再各写正则)
36
39
  - 不依赖 `src/context/`(原 `lifecycle-hooks.ts` 是唯一 import `context/types` 的文件,已随 ADR-0022 删除),也不依赖 monitoring——知识健康评分由本目录 doctor.ts 的 KnowledgeHealthScorer 承载
37
40
  - 被 `src/cli/commands/knowledge.ts` CLI 消费(含 `migrate` 子命令)
38
41
 
39
42
  ## 约定
40
- - 知识条目文件的 frontmatter 语法只由 `src/utils/frontmatter` 定义(harness#89):缺头/空 meta = `absent`(合法输入,按非条目静默处理);未闭合/YAML 非法 = `malformed`(必须显式上报后按消费方语义恢复——migration 落 `errors`、store 与 index-generator 打一行 stderr 后跳过或走 best-effort),禁止静默丢条目;canonical 字段序是 `store.toFrontmatter` 的私有策略,`join` 只管包裹格式
43
+ - 知识条目文件的 frontmatter 语法只由 `src/utils/frontmatter` 定义(harness#89):缺头/空 meta = `absent`(合法输入,按非条目静默处理、不上报——migration 对 `absent` 不计 `errors`、不迁移,只计 `skipped` 保证 `total` 计数闭环,harness#161 对齐 #89 裁决 2);未闭合/YAML 非法 = `malformed`(必须显式上报后按消费方语义恢复——migration 落 `errors`、store 与 index-generator 打一行 stderr 后跳过或走 best-effort),禁止静默丢条目;canonical 字段序是 `store.toFrontmatter` 的私有策略,`join` 只管包裹格式
44
+ - **知识树的排除口径只有一个正本 `tree-walker`**(harness#134):`_index.md`(索引生成物)、`.snapshots`、`.archive` / `archived`、`resolutions` 是树基建、不是条目人口。store 与 migration 的顶层扫描、index-generator 的递归扫描三处都走它;新增 walker 禁止另立排除清单。统一的是**排除口径**不是遍历深度(store/migration 顶层、index-generator 递归)。例外须原地记名理由:`import.ts` 的 docs 扫描吃的是**项目文档树**,本口径在它那里没有对应物
45
+ - **循环内禁止逐条 `store.update()`**(harness#134):`update()` 的形状是 get→save、`save()` 每次全量重写 index.json,N 条修复 = N 次全量重写。批量形是 `applyAll(id → partial)`;一次 `audit --fix`、一轮 `runDecayCycle()`、一次 `updateReferencedBy()` 各只重写一次索引,计数闸见 `audit-write-count.test.ts` 与 `lifecycle.test.ts` / `reference-tracker.test.ts` 的 stringify 计数项
46
+ - **审计判定脱离文件系统可测**(harness#134):规则表与 D1–D7 打分住 `audit-scoring.ts`,零 fs(源形状闸钉在 `audit-scoring.test.ts`);引擎 `audit.ts` 只做 store 装配,环境数据经 `store.getConsumptionStats()` / `getSurvivalRate()` 取好喂入
41
47
  - 知识条目有明确的生命周期状态(按 consumptionMode 分化)
42
48
  - 约束退役(`harness constraints retire`,人确认)时写入 KnowledgeStore:规则原文 + 退役原因 + 历史统计
43
49
  - Linter 检查完整性/一致性/时效性三个维度
44
50
  - Audit 7 维度评分:D1结构 D2内容 D3去重 D4成熟度 D5新鲜度 D6飞轮 D7增量存活
45
51
  - Ingest gate: ingestEntry() 先经 audit.validate() 检查,reject 不入库
46
- - 外部内容三层防御:ingest sanitization + retrieval marking + prompt constraint
52
+ - 外部内容三层防御:ingest sanitization + retrieval marking + prompt constraint。第二层 marking 已接线(harness#161):标记正本是 `knowledge/query.ts` 的 `EXTERNAL_SOURCE_MARKER` 常量(`[External Source — verify before acting]`),`KnowledgeQuery.formatForPrompt()` 与注入路径 `context/knowledge-injector.ts` 的 `formatEntry()`/`formatEntrySummary()` 共用同一常量——`origin === 'external'` 条目注入 prompt 时带来源前缀,注入 source 的 metadata 带 `origin`。第三层(prompt constraint)在本仓不可核(属 studio/agent 侧提示词),未核不改
47
53
  - 消费饱和度替代固定 TTL 用于 signal 过期判断
48
54
  - **飞轮指标只有一个实现**(ADR-0013):`refCoverage` / `avgRefs` / `consumptionHitRate` 一律出自 `flywheel-metrics.evaluateFlywheel()`,canonical 分子 = `genuineRefs()` 过滤后的真实消费引用(`search|test-agent|prompt-inject|monitor|analyst|...:<date>` 自动化按天记账键不算消费,`unknown:` 注入键算消费)。audit D6 / `knowledge stats` / `knowledge health` 只在展示层做单位与字段名映射(百分比取整、一位小数、`avgRefs`→`avgRefCount`),人口筛选留在各调用方。新增消费方禁止自行数 `referencedBy.length`。
49
55
 
@@ -53,3 +59,4 @@
53
59
  - `MaturityLevel` 包含 6 个值: draft/verified/proven/archived/active/deprecated
54
60
  - `excludeArchived` 同时排除 archived 和 deprecated
55
61
  - 仍有两处按**原始** `referencedBy.length` 判定,不属飞轮指标、ADR-0013 明确列为范围外:`lifecycle.ts` 的 signal 饱和 / reference 激活(退役阈值调整另票)、`knowledge health` 的 D1「verified 零引用」线索提示(逐条 issue 线索,非聚合分子)
62
+ - #134 收口后仍在的逐条写点(同型问题,本票裁决点名的循环之外,需要时另票):`lint.ts` 的三处 autoFix `store.update()`、`lifecycle.recordReference()`(逐事件调用,返回更新后条目并触发回调,批量化会改它的契约)、`ingest.mergeEntries()`;`store.list()` 每条一次 `findFile()` 线性扫索引的 O(N²) 按 #134 裁决**未动**,待把知识树实际规模重新量一次再判是否单开票
@@ -1,15 +1,18 @@
1
1
  /**
2
2
  * 审计规则 label 闭环测试(架构评审 A6,#109)
3
3
  *
4
- * label 正本在 audit.ts 规则定义上(定义即注册),AUDIT_RULE_LABELS 派生自规则表。
5
- * 新增规则无 label → 编译期/本测试期即失败,CLI 不再静默回落英文键名。
4
+ * label 正本在规则定义上(定义即注册,#134 后随规则表住在 audit-scoring.ts),
5
+ * AUDIT_RULE_LABELS 派生自规则表。新增规则无 label → 编译期/本测试期即失败,
6
+ * CLI 不再静默回落英文键名。
6
7
  */
7
8
 
8
9
  import * as fs from 'fs';
9
10
  import * as os from 'os';
10
11
  import * as path from 'path';
11
- import { KnowledgeAudit, AUDIT_RULE_LABELS } from '../audit';
12
- import type { AuditRuleName } from '../audit';
12
+ import { KnowledgeAudit } from '../audit';
13
+ import { AUDIT_RULE_LABELS } from '../audit-scoring';
14
+ import type { AuditRuleName } from '../audit-scoring';
15
+ import { FileKnowledgeStore } from '../store';
13
16
 
14
17
  function makeTmpDir(): string {
15
18
  return fs.mkdtempSync(path.join(os.tmpdir(), 'audit-labels-test-'));
@@ -27,7 +30,7 @@ describe('AUDIT_RULE_LABELS', () => {
27
30
  });
28
31
 
29
32
  it('应与运行期 summary 的规则键集完全一致(双向闭环)', () => {
30
- const report = new KnowledgeAudit({ baseDir: tmpDir }).run();
33
+ const report = new KnowledgeAudit(new FileKnowledgeStore({ baseDir: tmpDir })).run();
31
34
  const summaryKeys = Object.keys(report.summary).sort();
32
35
  const labelKeys = (Object.keys(AUDIT_RULE_LABELS) as AuditRuleName[]).sort();
33
36
  expect(labelKeys).toEqual(summaryKeys);
@@ -0,0 +1,230 @@
1
+ /**
2
+ * audit-scoring 纯模块测试(harness#134)
3
+ *
4
+ * 本模块的立身之本是**零 IO**:判定只吃条目数组与已取好的环境数据。
5
+ * 因此这里既有行为断言,也有一道源形状闸——有人往打分核心塞回 fs,就跑红。
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import {
11
+ AUDIT_RULE_LABELS,
12
+ DEFAULT_PROMOTION_BLOCK_DAYS,
13
+ DEFAULT_SHORT_CONTENT_THRESHOLD,
14
+ DEFAULT_STALE_DAYS,
15
+ MAX_SOURCE_REFS,
16
+ calculateHealthScore,
17
+ computeDimensions,
18
+ resolveThresholds,
19
+ scanEntries,
20
+ summarizeIssues,
21
+ } from '../audit-scoring';
22
+ import type { AuditIssue } from '../audit-scoring';
23
+ import type { KnowledgeEntry } from '../types';
24
+
25
+ const SOURCE = fs.readFileSync(path.join(__dirname, '../audit-scoring.ts'), 'utf-8');
26
+
27
+ function makeEntry(overrides: Partial<KnowledgeEntry> = {}): KnowledgeEntry {
28
+ return {
29
+ id: 'SCORE-001',
30
+ type: 'guideline',
31
+ title: 'Scoring Entry',
32
+ content: 'Some meaningful content that is long enough to pass thresholds',
33
+ maturity: 'draft',
34
+ layer: 'project',
35
+ created: new Date().toISOString(),
36
+ lastReferenced: new Date().toISOString(),
37
+ contributors: ['tester'],
38
+ projects: ['probe'],
39
+ tags: [],
40
+ applicablePhases: [],
41
+ sourceReferences: [],
42
+ referencedBy: [],
43
+ executionResults: [],
44
+ consumptionMode: 'reference',
45
+ origin: 'agent',
46
+ ...overrides,
47
+ };
48
+ }
49
+
50
+ describe('源形状闸:打分核心零 IO', () => {
51
+ it('不 import fs/path,也不直读 store 之外的任何东西', () => {
52
+ expect(SOURCE).not.toMatch(/from '(node:)?fs'/);
53
+ expect(SOURCE).not.toMatch(/from 'path'/);
54
+ expect(SOURCE).not.toMatch(/\bfs\.(readFileSync|existsSync|writeFileSync|readdirSync)\b/);
55
+ expect(SOURCE).not.toMatch(/new FileKnowledgeStore/);
56
+ });
57
+
58
+ it('条目集合外唯一的入参是 env(消费统计与存活率由调用方取好喂入)', () => {
59
+ expect(SOURCE).toContain('env: AuditEnv');
60
+ expect(SOURCE).toContain('dailyConsumptionEvents?: number');
61
+ });
62
+ });
63
+
64
+ describe('resolveThresholds', () => {
65
+ it('未给阈值时落三张缺省值', () => {
66
+ expect(resolveThresholds()).toEqual({
67
+ shortContentThreshold: DEFAULT_SHORT_CONTENT_THRESHOLD,
68
+ staleDays: DEFAULT_STALE_DAYS,
69
+ promotionBlockDays: DEFAULT_PROMOTION_BLOCK_DAYS,
70
+ });
71
+ });
72
+
73
+ it('显式值原样生效(含 0 不被缺省兜掉)', () => {
74
+ expect(resolveThresholds({ shortContentThreshold: 0, staleDays: 7 }).shortContentThreshold).toBe(0);
75
+ expect(resolveThresholds({ staleDays: 7 }).staleDays).toBe(7);
76
+ });
77
+ });
78
+
79
+ describe('scanEntries 的人口语义', () => {
80
+ it('单条目模式(不传人口)下跨条目规则不判定', () => {
81
+ const dupes = [
82
+ makeEntry({ id: 'P-1', title: 'Same', type: 'guideline' }),
83
+ makeEntry({ id: 'P-2', title: 'Same', type: 'guideline' }),
84
+ ];
85
+ for (const entry of dupes) {
86
+ expect(scanEntries([entry], {}).some(i => i.rule === 'title-duplicate')).toBe(false);
87
+ }
88
+ });
89
+
90
+ it('传入人口时同一对条目互相判重(两条都报)', () => {
91
+ const dupes = [
92
+ makeEntry({ id: 'P-1', title: 'Same', type: 'guideline' }),
93
+ makeEntry({ id: 'P-2', title: 'Same', type: 'guideline' }),
94
+ ];
95
+ const issues = scanEntries(dupes, {}, dupes);
96
+ expect(issues.filter(i => i.rule === 'title-duplicate')).toHaveLength(2);
97
+ });
98
+
99
+ it('人口与扫描集合可不同:autoFix 重扫沿用修复前人口的现状口径', () => {
100
+ const before = [
101
+ makeEntry({ id: 'Q-1', title: 'Dup', type: 'guideline' }),
102
+ makeEntry({ id: 'Q-2', title: 'Dup', type: 'guideline' }),
103
+ ];
104
+ const after = before.map(e => ({ ...e, maturity: 'archived' as const }));
105
+ // 扫描已归档的集合、对照未归档的人口:active 规则不判定 archived,故零问题
106
+ expect(scanEntries(after, {}, before)).toHaveLength(0);
107
+ // 对照集合同为已归档:同样零问题(人口过滤唯一落点在 ruleDetail)
108
+ expect(scanEntries(after, {}, after)).toHaveLength(0);
109
+ });
110
+ });
111
+
112
+ describe('summarizeIssues', () => {
113
+ it('未命中的规则也出零值键,键集与 label 正本表一致', () => {
114
+ const summary = summarizeIssues([]);
115
+ expect(Object.keys(summary).sort()).toEqual(Object.keys(AUDIT_RULE_LABELS).sort());
116
+ expect(Object.values(summary).every(n => n === 0)).toBe(true);
117
+ });
118
+
119
+ it('命中计数逐条累加', () => {
120
+ const issues: AuditIssue[] = [
121
+ { rule: 'short-content', entryId: 'a', title: 'A', severity: 'medium', action: 'flag', detail: '' },
122
+ { rule: 'short-content', entryId: 'b', title: 'B', severity: 'medium', action: 'flag', detail: '' },
123
+ { rule: 'event-noise', entryId: 'c', title: 'C', severity: 'critical', action: 'archive', detail: '' },
124
+ ];
125
+ const summary = summarizeIssues(issues);
126
+ expect(summary['short-content']).toBe(2);
127
+ expect(summary['event-noise']).toBe(1);
128
+ });
129
+ });
130
+
131
+ describe('computeDimensions 的环境数据入参', () => {
132
+ const active = [
133
+ makeEntry({ id: 'D-1', maturity: 'verified', referencedBy: ['x:2026-09-01'] }),
134
+ makeEntry({ id: 'D-2', maturity: 'verified' }),
135
+ ];
136
+
137
+ it('D6 消费事件数来自 env,透传到报告明细', () => {
138
+ const withOut = computeDimensions(active, [], {});
139
+ const withEnv = computeDimensions(active, [], { dailyConsumptionEvents: 8 });
140
+ expect(withOut.flywheel.details.dailyConsumptionEvents).toBe(0);
141
+ expect(withEnv.flywheel.details.dailyConsumptionEvents).toBe(8);
142
+ expect(withEnv.flywheel.score).toBeGreaterThan(withOut.flywheel.score);
143
+ });
144
+
145
+ it('D7 由 env.survival 供给:无快照 = 满分带说明,有快照 = 分数即存活率', () => {
146
+ expect(computeDimensions(active, [], {}).incremental).toEqual({
147
+ score: 100,
148
+ issues: 0,
149
+ details: { note: 'no 30d snapshot available' },
150
+ });
151
+ expect(computeDimensions(active, [], {
152
+ survival: { rate: 60, survived: 6, total: 10, snapshotDate: '2026-08-10' },
153
+ }).incremental).toEqual({
154
+ score: 60,
155
+ issues: 4,
156
+ details: { survivalRate: 60, survived: 6, total: 10, snapshotDate: '2026-08-10' },
157
+ });
158
+ });
159
+
160
+ it('空人口:结构与成熟度满分、飞轮零分(无活动可测)', () => {
161
+ const dims = computeDimensions([], [], {});
162
+ expect(dims.structure.score).toBe(100);
163
+ expect(dims.maturity.score).toBe(100);
164
+ expect(dims.flywheel.score).toBe(0);
165
+ });
166
+ });
167
+
168
+ describe('calculateHealthScore', () => {
169
+ const base = [makeEntry({ id: 'H-1', maturity: 'verified' }), makeEntry({ id: 'H-2', maturity: 'verified' })];
170
+ const issue = (severity: AuditIssue['severity']): AuditIssue => ({
171
+ rule: 'short-content', entryId: 'H-1', title: 'H', severity, action: 'flag', detail: '',
172
+ });
173
+
174
+ it('无问题 = 100', () => {
175
+ expect(calculateHealthScore(base, [])).toBe(100);
176
+ });
177
+
178
+ it('惩罚按严重度分级:critical > high > medium > low', () => {
179
+ const critical = calculateHealthScore(base, [issue('critical')]);
180
+ const low = calculateHealthScore(base, [issue('low')]);
181
+ expect(critical).toBeLessThan(low);
182
+ expect(low).toBeLessThan(100);
183
+ });
184
+
185
+ it('四档严重度各自的罚分权重(满分 100,人口 2 → 每条满罚 10 分权重)', () => {
186
+ // penalty / (entries × 10) → critical 10、high 5、medium 2、low 1
187
+ expect(calculateHealthScore(base, [issue('critical')])).toBe(50);
188
+ expect(calculateHealthScore(base, [issue('high')])).toBe(75);
189
+ expect(calculateHealthScore(base, [issue('medium')])).toBe(90);
190
+ expect(calculateHealthScore(base, [issue('low')])).toBe(95);
191
+ });
192
+
193
+ it('条目为空时恒 100(无人口不判健康度)', () => {
194
+ expect(calculateHealthScore([], [issue('critical')])).toBe(100);
195
+ });
196
+ });
197
+
198
+ describe('fragment-cluster(跨条目规则)', () => {
199
+ it('同 type + 共享 ≥2 标签 + 正文均 <100 字符且 peers≥2 才成簇', () => {
200
+ const short = (id: string, tags: string[]): KnowledgeEntry =>
201
+ makeEntry({ id, type: 'guideline', maturity: 'verified', content: '短'.repeat(20), tags });
202
+ const population = [short('FC-1', ['auth', 'db']), short('FC-2', ['auth', 'db']), short('FC-3', ['auth', 'db'])];
203
+ const issues = scanEntries(population, {}, population);
204
+ expect(issues.filter(i => i.rule === 'fragment-cluster').map(i => i.entryId).sort())
205
+ .toEqual(['FC-1', 'FC-2', 'FC-3']);
206
+ });
207
+
208
+ it('peers 只有 1 个时不成簇', () => {
209
+ const short = (id: string): KnowledgeEntry =>
210
+ makeEntry({ id, type: 'guideline', maturity: 'verified', content: '短'.repeat(20), tags: ['auth', 'db'] });
211
+ const population = [short('FC-1'), short('FC-2'), makeEntry({ id: 'FC-LONG', type: 'guideline', maturity: 'verified', content: '长'.repeat(120), tags: ['auth', 'db'] })];
212
+ const issues = scanEntries(population, {}, population);
213
+ expect(issues.filter(i => i.rule === 'fragment-cluster')).toHaveLength(0);
214
+ });
215
+ });
216
+
217
+ describe('MAX_SOURCE_REFS', () => {
218
+ it('正好上限不报,超一条才报', () => {
219
+ const atLimit = makeEntry({
220
+ id: 'R-1',
221
+ sourceReferences: Array.from({ length: MAX_SOURCE_REFS }, (_, i) => ({ workflow: `w${i}`, timestamp: '' })),
222
+ });
223
+ const overLimit = makeEntry({
224
+ id: 'R-2',
225
+ sourceReferences: Array.from({ length: MAX_SOURCE_REFS + 1 }, (_, i) => ({ workflow: `w${i}`, timestamp: '' })),
226
+ });
227
+ expect(scanEntries([atLimit], {}).some(i => i.rule === 'source-refs-bloat')).toBe(false);
228
+ expect(scanEntries([overLimit], {}).some(i => i.rule === 'source-refs-bloat')).toBe(true);
229
+ });
230
+ });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * 一次 `audit --fix` 的索引重写计数闸(harness#134 验收 2)
3
+ *
4
+ * 手法照 `src/cli/commands/__tests__/check-read-count.test.ts`:真执行、只在中间记一笔,
5
+ * 不改任何行为。数的是**底层 index.json 被重写了几次**——`store.writeIndex()` 的唯一出口
6
+ * 就是 `fs.writeFileSync(index.json)`,而 fs 属性不可 spyOn,故按该文件的做法整体 mock。
7
+ *
8
+ * 收口前:一次修复 = 一次 `store.update()` = 一次全量索引重写 → 计数 = 问题条数。
9
+ * 收口后:整批经 `store.applyAll()` → 计数 = 1。夹具必须让四种修复动作都真发生,
10
+ * 否则闸门是空跑的。
11
+ */
12
+
13
+ import * as fs from 'fs';
14
+ import * as os from 'os';
15
+ import * as path from 'path';
16
+ import { KnowledgeAudit } from '../audit';
17
+ import { FileKnowledgeStore } from '../store';
18
+ import type { KnowledgeEntry } from '../types';
19
+
20
+ type WriteCall = { file: string };
21
+
22
+ // 计数器挂在 globalThis:jest.mock 工厂会被提前调用,闭包外提会撞 TDZ
23
+ jest.mock('fs', () => {
24
+ const actual = jest.requireActual<typeof import('fs')>('fs');
25
+ const g = globalThis as unknown as { __H134_INDEX_WRITES__?: WriteCall[] };
26
+ if (!g.__H134_INDEX_WRITES__) g.__H134_INDEX_WRITES__ = [];
27
+ const bucket = g.__H134_INDEX_WRITES__;
28
+ const name = (p: unknown): string => (Buffer.isBuffer(p) ? p.toString('utf8') : String(p));
29
+
30
+ return {
31
+ ...actual,
32
+ writeFileSync: jest.fn((p: unknown, ...rest: unknown[]) => {
33
+ bucket.push({ file: name(p) });
34
+ return (actual.writeFileSync as (...args: unknown[]) => unknown)(p, ...rest);
35
+ }),
36
+ };
37
+ });
38
+
39
+ const writes = () =>
40
+ ((globalThis as unknown as { __H134_INDEX_WRITES__?: WriteCall[] }).__H134_INDEX_WRITES__ ||= []);
41
+
42
+ function makeEntry(overrides: Partial<KnowledgeEntry> = {}): KnowledgeEntry {
43
+ return {
44
+ id: 'WC-001',
45
+ type: 'guideline',
46
+ title: 'Write Count Entry',
47
+ content: 'Some meaningful content that is long enough to pass thresholds',
48
+ maturity: 'verified',
49
+ layer: 'project',
50
+ created: new Date().toISOString(),
51
+ lastReferenced: new Date().toISOString(),
52
+ contributors: ['tester'],
53
+ projects: ['probe'],
54
+ tags: [],
55
+ applicablePhases: [],
56
+ sourceReferences: [],
57
+ referencedBy: [],
58
+ executionResults: [],
59
+ consumptionMode: 'reference',
60
+ origin: 'agent',
61
+ ...overrides,
62
+ };
63
+ }
64
+
65
+ /** 四种修复动作各一条:archive(测试标签)/ demote(零内容 proven)/ flag(短内容)/ trim(refs 膨胀) */
66
+ function fixtureEntries(): KnowledgeEntry[] {
67
+ return [
68
+ makeEntry({ id: 'WC-ARCH', title: 'Archive Me', maturity: 'verified', tags: ['test-scope-wc'] }),
69
+ makeEntry({ id: 'WC-DEMOTE', title: 'Demote Me', maturity: 'proven', content: 'tiny' }),
70
+ makeEntry({ id: 'WC-FLAG', title: 'Flag Me', maturity: 'verified', content: 'a'.repeat(30) }),
71
+ makeEntry({
72
+ id: 'WC-TRIM',
73
+ title: 'Trim Me',
74
+ maturity: 'verified',
75
+ sourceReferences: Array.from({ length: 25 }, (_, i) => ({ workflow: `wf-${i}`, timestamp: '' })),
76
+ }),
77
+ ];
78
+ }
79
+
80
+ describe('一次 audit --fix 的 index.json 重写计数(harness#134)', () => {
81
+ let dir: string;
82
+
83
+ beforeEach(() => {
84
+ writes().length = 0;
85
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'audit-write-count-'));
86
+ const seed = new FileKnowledgeStore({ baseDir: dir });
87
+ for (const entry of fixtureEntries()) seed.save(entry);
88
+ writes().length = 0; // 只数审计这一段
89
+ });
90
+
91
+ afterEach(() => {
92
+ fs.rmSync(dir, { recursive: true, force: true });
93
+ });
94
+
95
+ it('4 条修复只重写一次 index.json,条目文件各重写一次', () => {
96
+ const audit = new KnowledgeAudit(new FileKnowledgeStore({ baseDir: dir }));
97
+ const report = audit.run({ autoFix: true });
98
+
99
+ // 正对照:四种动作全部真发生
100
+ expect(report.autoFixed).toBe(4);
101
+
102
+ const indexWrites = writes().filter(w => path.basename(w.file) === 'index.json');
103
+ const entryWrites = writes().filter(w => w.file.endsWith('.md'));
104
+ expect(indexWrites).toHaveLength(1);
105
+ expect(entryWrites.map(w => path.basename(w.file)).sort()).toEqual([
106
+ 'guideline-WC-ARCH.md',
107
+ 'guideline-WC-DEMOTE.md',
108
+ 'guideline-WC-FLAG.md',
109
+ 'guideline-WC-TRIM.md',
110
+ ]);
111
+ });
112
+
113
+ it('无修复项时零索引重写(applyAll 空批不落盘)', () => {
114
+ const cleanDir = fs.mkdtempSync(path.join(os.tmpdir(), 'audit-write-count-clean-'));
115
+ const seed = new FileKnowledgeStore({ baseDir: cleanDir });
116
+ seed.save(makeEntry({
117
+ id: 'WC-CLEAN',
118
+ title: 'Perfectly Ordinary Title',
119
+ maturity: 'verified',
120
+ content: 'x'.repeat(200),
121
+ referencedBy: ['someone:2026-09-14'],
122
+ }));
123
+ writes().length = 0;
124
+
125
+ const report = new KnowledgeAudit(new FileKnowledgeStore({ baseDir: cleanDir })).run({ autoFix: true });
126
+
127
+ expect(report.autoFixed).toBe(0); // 正对照:确实无可修项
128
+ expect(writes().filter(w => path.basename(w.file) === 'index.json')).toHaveLength(0);
129
+ fs.rmSync(cleanDir, { recursive: true, force: true });
130
+ });
131
+ });