@dommaker/harness 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (517) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +0 -2
  3. package/bin/harness.js +86 -32
  4. package/dist/cli/command-contract.d.ts +83 -0
  5. package/dist/cli/command-contract.d.ts.map +1 -0
  6. package/dist/cli/command-contract.js +66 -0
  7. package/dist/cli/command-contract.js.map +1 -0
  8. package/dist/cli/commands/acceptance.d.ts +3 -2
  9. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  10. package/dist/cli/commands/acceptance.js +28 -24
  11. package/dist/cli/commands/acceptance.js.map +1 -1
  12. package/dist/cli/commands/analyze-sessions.d.ts +2 -1
  13. package/dist/cli/commands/analyze-sessions.d.ts.map +1 -1
  14. package/dist/cli/commands/analyze-sessions.js +25 -24
  15. package/dist/cli/commands/analyze-sessions.js.map +1 -1
  16. package/dist/cli/commands/check.d.ts +14 -2
  17. package/dist/cli/commands/check.d.ts.map +1 -1
  18. package/dist/cli/commands/check.js +76 -61
  19. package/dist/cli/commands/check.js.map +1 -1
  20. package/dist/cli/commands/command.d.ts +5 -1
  21. package/dist/cli/commands/command.d.ts.map +1 -1
  22. package/dist/cli/commands/command.js +26 -17
  23. package/dist/cli/commands/command.js.map +1 -1
  24. package/dist/cli/commands/constraints-report.d.ts +2 -1
  25. package/dist/cli/commands/constraints-report.d.ts.map +1 -1
  26. package/dist/cli/commands/constraints-report.js +37 -33
  27. package/dist/cli/commands/constraints-report.js.map +1 -1
  28. package/dist/cli/commands/constraints-retire.d.ts +6 -5
  29. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  30. package/dist/cli/commands/constraints-retire.js +50 -52
  31. package/dist/cli/commands/constraints-retire.js.map +1 -1
  32. package/dist/cli/commands/constraints.d.ts +2 -1
  33. package/dist/cli/commands/constraints.d.ts.map +1 -1
  34. package/dist/cli/commands/constraints.js +10 -27
  35. package/dist/cli/commands/constraints.js.map +1 -1
  36. package/dist/cli/commands/contract.d.ts +3 -2
  37. package/dist/cli/commands/contract.d.ts.map +1 -1
  38. package/dist/cli/commands/contract.js +45 -42
  39. package/dist/cli/commands/contract.js.map +1 -1
  40. package/dist/cli/commands/definitions.d.ts +11 -11
  41. package/dist/cli/commands/definitions.d.ts.map +1 -1
  42. package/dist/cli/commands/definitions.js +29 -214
  43. package/dist/cli/commands/definitions.js.map +1 -1
  44. package/dist/cli/commands/failure.d.ts +5 -4
  45. package/dist/cli/commands/failure.d.ts.map +1 -1
  46. package/dist/cli/commands/failure.js +31 -25
  47. package/dist/cli/commands/failure.js.map +1 -1
  48. package/dist/cli/commands/init.d.ts +7 -5
  49. package/dist/cli/commands/init.d.ts.map +1 -1
  50. package/dist/cli/commands/init.js +147 -211
  51. package/dist/cli/commands/init.js.map +1 -1
  52. package/dist/cli/commands/knowledge.d.ts +21 -15
  53. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  54. package/dist/cli/commands/knowledge.js +214 -199
  55. package/dist/cli/commands/knowledge.js.map +1 -1
  56. package/dist/cli/commands/passes-gate.d.ts +8 -2
  57. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  58. package/dist/cli/commands/passes-gate.js +44 -66
  59. package/dist/cli/commands/passes-gate.js.map +1 -1
  60. package/dist/cli/commands/performance.d.ts +2 -1
  61. package/dist/cli/commands/performance.d.ts.map +1 -1
  62. package/dist/cli/commands/performance.js +20 -18
  63. package/dist/cli/commands/performance.js.map +1 -1
  64. package/dist/cli/commands/posteval-plan.d.ts +5 -1
  65. package/dist/cli/commands/posteval-plan.d.ts.map +1 -1
  66. package/dist/cli/commands/posteval-plan.js +26 -15
  67. package/dist/cli/commands/posteval-plan.js.map +1 -1
  68. package/dist/cli/commands/release.d.ts +6 -1
  69. package/dist/cli/commands/release.d.ts.map +1 -1
  70. package/dist/cli/commands/release.js +69 -59
  71. package/dist/cli/commands/release.js.map +1 -1
  72. package/dist/cli/commands/report.d.ts +2 -1
  73. package/dist/cli/commands/report.d.ts.map +1 -1
  74. package/dist/cli/commands/report.js +9 -5
  75. package/dist/cli/commands/report.js.map +1 -1
  76. package/dist/cli/commands/review.d.ts +3 -2
  77. package/dist/cli/commands/review.d.ts.map +1 -1
  78. package/dist/cli/commands/review.js +33 -29
  79. package/dist/cli/commands/review.js.map +1 -1
  80. package/dist/cli/commands/sdd.d.ts +2 -1
  81. package/dist/cli/commands/sdd.d.ts.map +1 -1
  82. package/dist/cli/commands/sdd.js +7 -5
  83. package/dist/cli/commands/sdd.js.map +1 -1
  84. package/dist/cli/commands/security.d.ts +3 -2
  85. package/dist/cli/commands/security.d.ts.map +1 -1
  86. package/dist/cli/commands/security.js +29 -26
  87. package/dist/cli/commands/security.js.map +1 -1
  88. package/dist/cli/commands/spec-baseline-check.d.ts +2 -1
  89. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  90. package/dist/cli/commands/spec-baseline-check.js +15 -11
  91. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  92. package/dist/cli/commands/spec.d.ts +7 -2
  93. package/dist/cli/commands/spec.d.ts.map +1 -1
  94. package/dist/cli/commands/spec.js +61 -51
  95. package/dist/cli/commands/spec.js.map +1 -1
  96. package/dist/cli/commands/status.d.ts +2 -1
  97. package/dist/cli/commands/status.d.ts.map +1 -1
  98. package/dist/cli/commands/status.js +57 -53
  99. package/dist/cli/commands/status.js.map +1 -1
  100. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  101. package/dist/cli/commands/sync-docs/agents-syncer.js +5 -2
  102. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  103. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  104. package/dist/cli/commands/sync-docs/context-syncer.js +3 -2
  105. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  106. package/dist/cli/commands/sync-docs/index.d.ts +2 -1
  107. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  108. package/dist/cli/commands/sync-docs/index.js +68 -88
  109. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  110. package/dist/cli/commands/sync-docs/project-reader.d.ts +6 -1
  111. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  112. package/dist/cli/commands/sync-docs/project-reader.js +14 -16
  113. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  114. package/dist/cli/commands/update-user-model.d.ts +2 -1
  115. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  116. package/dist/cli/commands/update-user-model.js +31 -45
  117. package/dist/cli/commands/update-user-model.js.map +1 -1
  118. package/dist/cli/commands/validate.d.ts +4 -3
  119. package/dist/cli/commands/validate.d.ts.map +1 -1
  120. package/dist/cli/commands/validate.js +28 -26
  121. package/dist/cli/commands/validate.js.map +1 -1
  122. package/dist/cli/session-mining/transcript.d.ts +7 -1
  123. package/dist/cli/session-mining/transcript.d.ts.map +1 -1
  124. package/dist/cli/session-mining/transcript.js +21 -14
  125. package/dist/cli/session-mining/transcript.js.map +1 -1
  126. package/dist/context/session-manager.d.ts.map +1 -1
  127. package/dist/context/session-manager.js +19 -32
  128. package/dist/context/session-manager.js.map +1 -1
  129. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +1 -1
  130. package/dist/core/constraints/agent-prompt-renderer.js +1 -4
  131. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  132. package/dist/core/constraints/capabilities-parser.d.ts +24 -7
  133. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  134. package/dist/core/constraints/capabilities-parser.js +32 -28
  135. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  136. package/dist/core/constraints/capabilities-reconcile.d.ts +67 -0
  137. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -0
  138. package/dist/core/constraints/capabilities-reconcile.js +105 -0
  139. package/dist/core/constraints/capabilities-reconcile.js.map +1 -0
  140. package/dist/core/constraints/checker.d.ts +21 -43
  141. package/dist/core/constraints/checker.d.ts.map +1 -1
  142. package/dist/core/constraints/checker.js +64 -115
  143. package/dist/core/constraints/checker.js.map +1 -1
  144. package/dist/core/constraints/checkers/capability-sync.d.ts +4 -3
  145. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  146. package/dist/core/constraints/checkers/capability-sync.js +29 -69
  147. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  148. package/dist/core/constraints/checkers/context-doc-sync.d.ts +4 -2
  149. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  150. package/dist/core/constraints/checkers/context-doc-sync.js +13 -22
  151. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  152. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  153. package/dist/core/constraints/checkers/docs-freshness.js +37 -63
  154. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  155. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -2
  156. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  157. package/dist/core/constraints/checkers/governance-presence.js +7 -28
  158. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  159. package/dist/core/constraints/checkers/no-test-simplification.d.ts.map +1 -1
  160. package/dist/core/constraints/checkers/no-test-simplification.js +4 -3
  161. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  162. package/dist/core/constraints/checkers/types.d.ts +2 -2
  163. package/dist/core/constraints/context-builder.d.ts +15 -14
  164. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  165. package/dist/core/constraints/context-builder.js +32 -96
  166. package/dist/core/constraints/context-builder.js.map +1 -1
  167. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  168. package/dist/core/constraints/doc-freshness/runner.js +2 -1
  169. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  170. package/dist/core/constraints/git-evidence.d.ts +60 -0
  171. package/dist/core/constraints/git-evidence.d.ts.map +1 -0
  172. package/dist/core/constraints/git-evidence.js +88 -0
  173. package/dist/core/constraints/git-evidence.js.map +1 -0
  174. package/dist/core/constraints/injection-drift.d.ts +3 -20
  175. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  176. package/dist/core/constraints/injection-drift.js +10 -57
  177. package/dist/core/constraints/injection-drift.js.map +1 -1
  178. package/dist/core/constraints/injection-writer.d.ts +113 -0
  179. package/dist/core/constraints/injection-writer.d.ts.map +1 -0
  180. package/dist/core/constraints/injection-writer.js +228 -0
  181. package/dist/core/constraints/injection-writer.js.map +1 -0
  182. package/dist/core/constraints/usage-report.d.ts +22 -1
  183. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  184. package/dist/core/constraints/usage-report.js +27 -20
  185. package/dist/core/constraints/usage-report.js.map +1 -1
  186. package/dist/core/index.d.ts +2 -2
  187. package/dist/core/index.d.ts.map +1 -1
  188. package/dist/core/index.js +4 -2
  189. package/dist/core/index.js.map +1 -1
  190. package/dist/core/project-config-loader.d.ts +32 -1
  191. package/dist/core/project-config-loader.d.ts.map +1 -1
  192. package/dist/core/project-config-loader.js +39 -9
  193. package/dist/core/project-config-loader.js.map +1 -1
  194. package/dist/core/validators/passes-gate.d.ts +15 -64
  195. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  196. package/dist/core/validators/passes-gate.js +84 -222
  197. package/dist/core/validators/passes-gate.js.map +1 -1
  198. package/dist/core/validators/test-output.d.ts +58 -0
  199. package/dist/core/validators/test-output.d.ts.map +1 -0
  200. package/dist/core/validators/test-output.js +109 -0
  201. package/dist/core/validators/test-output.js.map +1 -0
  202. package/dist/failure/recorder.d.ts +6 -0
  203. package/dist/failure/recorder.d.ts.map +1 -1
  204. package/dist/failure/recorder.js +12 -8
  205. package/dist/failure/recorder.js.map +1 -1
  206. package/dist/gates/acceptance.d.ts +1 -48
  207. package/dist/gates/acceptance.d.ts.map +1 -1
  208. package/dist/gates/acceptance.js +6 -38
  209. package/dist/gates/acceptance.js.map +1 -1
  210. package/dist/gates/checker-gate.d.ts.map +1 -1
  211. package/dist/gates/checker-gate.js +6 -11
  212. package/dist/gates/checker-gate.js.map +1 -1
  213. package/dist/gates/command.d.ts.map +1 -1
  214. package/dist/gates/command.js +10 -22
  215. package/dist/gates/command.js.map +1 -1
  216. package/dist/gates/contract.d.ts.map +1 -1
  217. package/dist/gates/contract.js +22 -61
  218. package/dist/gates/contract.js.map +1 -1
  219. package/dist/gates/performance.d.ts.map +1 -1
  220. package/dist/gates/performance.js +13 -31
  221. package/dist/gates/performance.js.map +1 -1
  222. package/dist/gates/review.d.ts.map +1 -1
  223. package/dist/gates/review.js +21 -53
  224. package/dist/gates/review.js.map +1 -1
  225. package/dist/gates/security.d.ts.map +1 -1
  226. package/dist/gates/security.js +14 -32
  227. package/dist/gates/security.js.map +1 -1
  228. package/dist/gates/types.d.ts +28 -5
  229. package/dist/gates/types.d.ts.map +1 -1
  230. package/dist/gates/types.js +38 -1
  231. package/dist/gates/types.js.map +1 -1
  232. package/dist/hooks/bootstrap.d.ts +2 -0
  233. package/dist/hooks/bootstrap.d.ts.map +1 -1
  234. package/dist/hooks/bootstrap.js +5 -2
  235. package/dist/hooks/bootstrap.js.map +1 -1
  236. package/dist/index.d.ts +1 -2
  237. package/dist/index.d.ts.map +1 -1
  238. package/dist/index.js.map +1 -1
  239. package/dist/knowledge/audit.d.ts +7 -2
  240. package/dist/knowledge/audit.d.ts.map +1 -1
  241. package/dist/knowledge/audit.js +59 -79
  242. package/dist/knowledge/audit.js.map +1 -1
  243. package/dist/knowledge/flywheel-metrics.d.ts +39 -0
  244. package/dist/knowledge/flywheel-metrics.d.ts.map +1 -0
  245. package/dist/knowledge/flywheel-metrics.js +47 -0
  246. package/dist/knowledge/flywheel-metrics.js.map +1 -0
  247. package/dist/knowledge/import.d.ts.map +1 -1
  248. package/dist/knowledge/import.js +4 -14
  249. package/dist/knowledge/import.js.map +1 -1
  250. package/dist/knowledge/index-generator.d.ts.map +1 -1
  251. package/dist/knowledge/index-generator.js +17 -14
  252. package/dist/knowledge/index-generator.js.map +1 -1
  253. package/dist/knowledge/ingest.d.ts.map +1 -1
  254. package/dist/knowledge/ingest.js +9 -4
  255. package/dist/knowledge/ingest.js.map +1 -1
  256. package/dist/knowledge/migration.d.ts.map +1 -1
  257. package/dist/knowledge/migration.js +11 -8
  258. package/dist/knowledge/migration.js.map +1 -1
  259. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  260. package/dist/knowledge/reference-tracker.js +12 -9
  261. package/dist/knowledge/reference-tracker.js.map +1 -1
  262. package/dist/knowledge/store.d.ts +15 -0
  263. package/dist/knowledge/store.d.ts.map +1 -1
  264. package/dist/knowledge/store.js +59 -10
  265. package/dist/knowledge/store.js.map +1 -1
  266. package/dist/monitoring/context-tracker.d.ts.map +1 -1
  267. package/dist/monitoring/context-tracker.js +11 -20
  268. package/dist/monitoring/context-tracker.js.map +1 -1
  269. package/dist/monitoring/index.d.ts +1 -1
  270. package/dist/monitoring/index.d.ts.map +1 -1
  271. package/dist/monitoring/index.js +1 -2
  272. package/dist/monitoring/index.js.map +1 -1
  273. package/dist/monitoring/trace-analyzer.d.ts +15 -0
  274. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  275. package/dist/monitoring/trace-analyzer.js +19 -2
  276. package/dist/monitoring/trace-analyzer.js.map +1 -1
  277. package/dist/monitoring/traces.d.ts +21 -2
  278. package/dist/monitoring/traces.d.ts.map +1 -1
  279. package/dist/monitoring/traces.js +38 -29
  280. package/dist/monitoring/traces.js.map +1 -1
  281. package/dist/release/index.d.ts +2 -2
  282. package/dist/release/index.d.ts.map +1 -1
  283. package/dist/release/index.js +1 -5
  284. package/dist/release/index.js.map +1 -1
  285. package/dist/sdd/index-generator.js +10 -11
  286. package/dist/sdd/index-generator.js.map +1 -1
  287. package/dist/test-setup/project-fixture.d.ts +48 -0
  288. package/dist/test-setup/project-fixture.d.ts.map +1 -0
  289. package/dist/test-setup/project-fixture.js +106 -0
  290. package/dist/test-setup/project-fixture.js.map +1 -0
  291. package/dist/types/constraint.d.ts +0 -18
  292. package/dist/types/constraint.d.ts.map +1 -1
  293. package/dist/types/constraint.js.map +1 -1
  294. package/dist/types/passes-gate.d.ts +10 -25
  295. package/dist/types/passes-gate.d.ts.map +1 -1
  296. package/dist/types/project-config.d.ts +1 -1
  297. package/dist/types/session.d.ts +6 -5
  298. package/dist/types/session.d.ts.map +1 -1
  299. package/dist/utils/exec.d.ts +10 -0
  300. package/dist/utils/exec.d.ts.map +1 -1
  301. package/dist/utils/exec.js +12 -0
  302. package/dist/utils/exec.js.map +1 -1
  303. package/dist/utils/file-walk.d.ts +9 -1
  304. package/dist/utils/file-walk.d.ts.map +1 -1
  305. package/dist/utils/file-walk.js +10 -1
  306. package/dist/utils/file-walk.js.map +1 -1
  307. package/dist/utils/frontmatter.d.ts +73 -0
  308. package/dist/utils/frontmatter.d.ts.map +1 -0
  309. package/dist/utils/frontmatter.js +141 -0
  310. package/dist/utils/frontmatter.js.map +1 -0
  311. package/dist/utils/jsonl.d.ts +92 -0
  312. package/dist/utils/jsonl.d.ts.map +1 -0
  313. package/dist/utils/jsonl.js +166 -0
  314. package/dist/utils/jsonl.js.map +1 -0
  315. package/dist/utils/package-version.d.ts +16 -0
  316. package/dist/utils/package-version.d.ts.map +1 -0
  317. package/dist/utils/package-version.js +63 -0
  318. package/dist/utils/package-version.js.map +1 -0
  319. package/package.json +1 -1
  320. package/src/CONTEXT.md +7 -2
  321. package/src/__tests__/acceptance-gate.test.ts +3 -1
  322. package/src/__tests__/checker-extra.test.ts +7 -1218
  323. package/src/__tests__/context-files-resolution.test.ts +116 -0
  324. package/src/__tests__/effective-constraints.test.ts +10 -13
  325. package/src/__tests__/exec.test.ts +35 -1
  326. package/src/__tests__/layering.test.ts +152 -0
  327. package/src/__tests__/passes-gate-extra.test.ts +0 -145
  328. package/src/__tests__/passes-gate.test.ts +142 -106
  329. package/src/__tests__/skip-semantics.test.ts +5 -9
  330. package/src/cli/__tests__/command-contract.test.ts +170 -0
  331. package/src/cli/command-contract.ts +108 -0
  332. package/src/cli/commands/CONTEXT.md +22 -7
  333. package/src/cli/commands/__tests__/acceptance.test.ts +40 -33
  334. package/src/cli/commands/__tests__/analyze-sessions.test.ts +35 -29
  335. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +101 -0
  336. package/src/cli/commands/__tests__/check-drift.test.ts +27 -34
  337. package/src/cli/commands/__tests__/check-skip-output.test.ts +21 -24
  338. package/src/cli/commands/__tests__/check.test.ts +378 -382
  339. package/src/cli/commands/__tests__/command.test.ts +85 -70
  340. package/src/cli/commands/__tests__/constraints-report.test.ts +100 -71
  341. package/src/cli/commands/__tests__/constraints-retire.test.ts +152 -95
  342. package/src/cli/commands/__tests__/contract.test.ts +27 -34
  343. package/src/cli/commands/__tests__/failure.test.ts +31 -34
  344. package/src/cli/commands/__tests__/init-injection.test.ts +58 -28
  345. package/src/cli/commands/__tests__/init.test.ts +77 -48
  346. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +108 -0
  347. package/src/cli/commands/__tests__/knowledge.test.ts +69 -43
  348. package/src/cli/commands/__tests__/passes-gate.test.ts +43 -45
  349. package/src/cli/commands/__tests__/performance.test.ts +22 -23
  350. package/src/cli/commands/__tests__/posteval-plan.test.ts +39 -38
  351. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +157 -0
  352. package/src/cli/commands/__tests__/project-path-convention.test.ts +169 -0
  353. package/src/cli/commands/__tests__/registry.test.ts +69 -35
  354. package/src/cli/commands/__tests__/release.test.ts +209 -80
  355. package/src/cli/commands/__tests__/report-extra.test.ts +16 -16
  356. package/src/cli/commands/__tests__/report.test.ts +25 -25
  357. package/src/cli/commands/__tests__/review.test.ts +33 -31
  358. package/src/cli/commands/__tests__/security.test.ts +23 -27
  359. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +20 -20
  360. package/src/cli/commands/__tests__/spec.test.ts +41 -29
  361. package/src/cli/commands/__tests__/status-extra.test.ts +18 -18
  362. package/src/cli/commands/__tests__/status-skipped-lines.test.ts +72 -0
  363. package/src/cli/commands/__tests__/status.test.ts +34 -34
  364. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +77 -78
  365. package/src/cli/commands/__tests__/sync-docs.test.ts +72 -74
  366. package/src/cli/commands/__tests__/update-user-model.test.ts +62 -33
  367. package/src/cli/commands/__tests__/validate.test.ts +27 -29
  368. package/src/cli/commands/acceptance.ts +35 -24
  369. package/src/cli/commands/analyze-sessions.ts +25 -24
  370. package/src/cli/commands/check.ts +88 -65
  371. package/src/cli/commands/command.ts +30 -17
  372. package/src/cli/commands/constraints-report.ts +37 -34
  373. package/src/cli/commands/constraints-retire.ts +58 -55
  374. package/src/cli/commands/constraints.ts +10 -27
  375. package/src/cli/commands/contract.ts +51 -42
  376. package/src/cli/commands/definitions.ts +36 -222
  377. package/src/cli/commands/failure.ts +33 -26
  378. package/src/cli/commands/init.ts +166 -220
  379. package/src/cli/commands/knowledge.ts +225 -203
  380. package/src/cli/commands/passes-gate.ts +58 -68
  381. package/src/cli/commands/performance.ts +23 -18
  382. package/src/cli/commands/posteval-plan.ts +29 -15
  383. package/src/cli/commands/release.ts +70 -59
  384. package/src/cli/commands/report.ts +9 -5
  385. package/src/cli/commands/review.ts +39 -29
  386. package/src/cli/commands/sdd.ts +7 -5
  387. package/src/cli/commands/security.ts +35 -26
  388. package/src/cli/commands/spec-baseline-check.ts +16 -11
  389. package/src/cli/commands/spec.ts +66 -53
  390. package/src/cli/commands/status.ts +59 -53
  391. package/src/cli/commands/sync-docs/agents-syncer.ts +5 -2
  392. package/src/cli/commands/sync-docs/context-syncer.ts +3 -2
  393. package/src/cli/commands/sync-docs/index.ts +73 -100
  394. package/src/cli/commands/sync-docs/project-reader.ts +17 -18
  395. package/src/cli/commands/update-user-model.ts +33 -45
  396. package/src/cli/commands/validate.ts +31 -25
  397. package/src/cli/session-mining/__tests__/transcript.test.ts +118 -0
  398. package/src/cli/session-mining/transcript.ts +26 -8
  399. package/src/context/session-manager.ts +19 -33
  400. package/src/core/CONTEXT.md +9 -6
  401. package/src/core/constraints/__tests__/capabilities-reconcile.test.ts +200 -0
  402. package/src/core/constraints/__tests__/capability-counts.test.ts +94 -0
  403. package/src/core/constraints/__tests__/git-evidence.test.ts +208 -0
  404. package/src/core/constraints/__tests__/injection-drift.test.ts +3 -10
  405. package/src/core/constraints/__tests__/injection-writer.test.ts +231 -0
  406. package/src/core/constraints/__tests__/trace-injection.test.ts +71 -0
  407. package/src/core/constraints/__tests__/usage-report.test.ts +112 -0
  408. package/src/core/constraints/agent-prompt-renderer.ts +2 -6
  409. package/src/core/constraints/capabilities-parser.ts +53 -35
  410. package/src/core/constraints/capabilities-reconcile.ts +166 -0
  411. package/src/core/constraints/checker.ts +75 -130
  412. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +214 -0
  413. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +77 -0
  414. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +117 -0
  415. package/src/{__tests__ → core/constraints/checkers/__tests__}/governance-presence.test.ts +83 -9
  416. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +50 -0
  417. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +76 -0
  418. package/src/{__tests__ → core/constraints/checkers/__tests__}/no-hardcoded-credentials.test.ts +4 -3
  419. package/src/core/constraints/checkers/__tests__/no-test-simplification.test.ts +58 -0
  420. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +145 -0
  421. package/src/core/constraints/checkers/capability-sync.ts +29 -88
  422. package/src/core/constraints/checkers/context-doc-sync.ts +15 -25
  423. package/src/core/constraints/checkers/docs-freshness.ts +40 -69
  424. package/src/core/constraints/checkers/governance-presence.ts +7 -25
  425. package/src/core/constraints/checkers/no-test-simplification.ts +4 -3
  426. package/src/core/constraints/checkers/types.ts +2 -2
  427. package/src/core/constraints/context-builder.ts +39 -91
  428. package/src/core/constraints/doc-freshness/runner.ts +2 -1
  429. package/src/core/constraints/git-evidence.ts +120 -0
  430. package/src/core/constraints/injection-drift.ts +13 -60
  431. package/src/core/constraints/injection-writer.ts +212 -0
  432. package/src/core/constraints/usage-report.ts +36 -17
  433. package/src/core/index.ts +4 -2
  434. package/src/core/project-config-loader.ts +49 -9
  435. package/src/core/validators/__tests__/passes-gate.test.ts +2 -56
  436. package/src/core/validators/__tests__/test-output.test.ts +372 -0
  437. package/src/core/validators/passes-gate.ts +86 -248
  438. package/src/core/validators/test-output.ts +128 -0
  439. package/src/failure/__tests__/recorder.test.ts +168 -0
  440. package/src/failure/recorder.ts +12 -10
  441. package/src/gates/CONTEXT.md +6 -3
  442. package/src/gates/__tests__/acceptance-types-single-source.test.ts +68 -0
  443. package/src/gates/__tests__/acceptance.test.ts +4 -86
  444. package/src/gates/__tests__/effective-gates.test.ts +3 -8
  445. package/src/gates/__tests__/test-verdict-consistency.test.ts +140 -0
  446. package/src/gates/acceptance.ts +19 -84
  447. package/src/gates/checker-gate.ts +8 -7
  448. package/src/gates/command.ts +10 -16
  449. package/src/gates/contract.ts +22 -61
  450. package/src/gates/performance.ts +10 -22
  451. package/src/gates/review.ts +21 -41
  452. package/src/gates/security.ts +10 -22
  453. package/src/gates/types.ts +70 -5
  454. package/src/hooks/CONTEXT.md +1 -1
  455. package/src/hooks/bootstrap.ts +5 -2
  456. package/src/index.ts +0 -4
  457. package/src/knowledge/CONTEXT.md +8 -3
  458. package/src/knowledge/__tests__/audit-labels.test.ts +44 -0
  459. package/src/knowledge/__tests__/flywheel-metrics.test.ts +135 -0
  460. package/src/knowledge/__tests__/import.test.ts +44 -0
  461. package/src/knowledge/__tests__/index-generator.test.ts +53 -0
  462. package/src/knowledge/__tests__/ingest.test.ts +85 -0
  463. package/src/knowledge/__tests__/migration.test.ts +47 -0
  464. package/src/knowledge/__tests__/reference-tracker.test.ts +16 -0
  465. package/src/knowledge/__tests__/store.test.ts +194 -0
  466. package/src/knowledge/audit.ts +67 -71
  467. package/src/knowledge/flywheel-metrics.ts +71 -0
  468. package/src/knowledge/import.ts +4 -18
  469. package/src/knowledge/index-generator.ts +19 -14
  470. package/src/knowledge/ingest.ts +9 -4
  471. package/src/knowledge/migration.ts +11 -8
  472. package/src/knowledge/reference-tracker.ts +12 -8
  473. package/src/knowledge/store.ts +64 -11
  474. package/src/monitoring/CONTEXT.md +8 -3
  475. package/src/monitoring/__tests__/skipped-lines-report.test.ts +178 -0
  476. package/src/monitoring/__tests__/traces.test.ts +57 -0
  477. package/src/monitoring/context-tracker.ts +11 -23
  478. package/src/monitoring/index.ts +1 -1
  479. package/src/monitoring/trace-analyzer.ts +21 -2
  480. package/src/monitoring/traces.ts +39 -36
  481. package/src/release/CONTEXT.md +1 -0
  482. package/src/release/__tests__/barrel.test.ts +60 -0
  483. package/src/release/index.ts +2 -13
  484. package/src/sdd/__tests__/index-generator.test.ts +71 -0
  485. package/src/sdd/index-generator.ts +9 -9
  486. package/src/test-setup/__tests__/project-fixture.test.ts +178 -0
  487. package/src/test-setup/project-fixture.ts +87 -0
  488. package/src/tools/CONTEXT.md +6 -0
  489. package/src/tools/__tests__/registry.test.ts +221 -0
  490. package/src/types/__tests__/types.test.ts +4 -5
  491. package/src/types/constraint.ts +0 -25
  492. package/src/types/passes-gate.ts +10 -30
  493. package/src/types/project-config.ts +1 -1
  494. package/src/types/session.ts +5 -5
  495. package/src/utils/__tests__/frontmatter.test.ts +206 -0
  496. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +175 -0
  497. package/src/utils/__tests__/jsonl.test.ts +187 -0
  498. package/src/utils/__tests__/package-version.test.ts +97 -0
  499. package/src/utils/exec.ts +15 -0
  500. package/src/utils/file-walk.ts +11 -2
  501. package/src/utils/frontmatter.ts +121 -0
  502. package/src/utils/jsonl.ts +167 -0
  503. package/src/utils/package-version.ts +27 -0
  504. package/dist/cli/commands/doc-freshness-check.d.ts +0 -53
  505. package/dist/cli/commands/doc-freshness-check.d.ts.map +0 -1
  506. package/dist/cli/commands/doc-freshness-check.js +0 -352
  507. package/dist/cli/commands/doc-freshness-check.js.map +0 -1
  508. package/dist/types/index.d.ts +0 -14
  509. package/dist/types/index.d.ts.map +0 -1
  510. package/dist/types/index.js +0 -39
  511. package/dist/types/index.js.map +0 -1
  512. package/src/__tests__/long-running.test.ts +0 -17
  513. package/src/__tests__/passes-gate-extension.test.ts +0 -151
  514. package/src/cli/commands/__tests__/doc-freshness-check.test.ts +0 -168
  515. package/src/cli/commands/doc-freshness-check.ts +0 -386
  516. package/src/spec/CONTEXT.md +0 -11
  517. package/src/types/index.ts +0 -47
@@ -12,10 +12,13 @@ import { FileKnowledgeStore as KnowledgeStore } from '../../knowledge/store';
12
12
  import { KnowledgeQuery } from '../../knowledge/query';
13
13
  import { KnowledgeLifecycle } from '../../knowledge/lifecycle';
14
14
  import { ColdStartImporter } from '../../knowledge/import';
15
- import { KnowledgeAudit } from '../../knowledge/audit';
15
+ import { KnowledgeAudit, AUDIT_RULE_LABELS } from '../../knowledge/audit';
16
+ import type { AuditReport, AuditRuleName } from '../../knowledge/audit';
17
+ import { evaluateFlywheel } from '../../knowledge/flywheel-metrics';
16
18
  import { migrateKnowledgeEntries } from '../../knowledge/migration';
17
19
  import { KnowledgeIndexGenerator } from '../../knowledge/index-generator';
18
20
  import type { KnowledgeSubsystem, MaturityLevel, QueryFilter } from '../../knowledge/types';
21
+ import { log, logError, processIO, type CommandIO, type CommandResult } from '../command-contract';
19
22
 
20
23
  export interface KnowledgeOptions {
21
24
  /** 项目路径 */
@@ -31,8 +34,8 @@ export async function knowledgeList(options: KnowledgeOptions & {
31
34
  type?: string;
32
35
  maturity?: string;
33
36
  tag?: string;
34
- }): Promise<void> {
35
- const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath) });
37
+ }, io: CommandIO = processIO): Promise<CommandResult> {
38
+ const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath, io) });
36
39
 
37
40
  const filter: QueryFilter = { excludeArchived: false };
38
41
  if (options.type) {
@@ -48,27 +51,28 @@ export async function knowledgeList(options: KnowledgeOptions & {
48
51
  const entries = store.list(filter);
49
52
 
50
53
  if (options.json) {
51
- console.log(JSON.stringify({ total: entries.length, entries }, null, 2));
52
- return;
54
+ log(io, JSON.stringify({ total: entries.length, entries }, null, 2));
55
+ return { kind: 'ok' };
53
56
  }
54
57
 
55
58
  if (entries.length === 0) {
56
- console.log(chalk.yellow('知识库为空'));
57
- return;
59
+ log(io, chalk.yellow('知识库为空'));
60
+ return { kind: 'ok' };
58
61
  }
59
62
 
60
- console.log(chalk.blue(`📚 知识库 (${entries.length} 条)\n`));
63
+ log(io, chalk.blue(`📚 知识库 (${entries.length} 条)\n`));
61
64
  for (const entry of entries) {
62
65
  const maturityColor = entry.maturity === 'proven' ? chalk.green
63
66
  : entry.maturity === 'verified' ? chalk.cyan
64
67
  : entry.maturity === 'archived' ? chalk.gray
65
68
  : chalk.yellow;
66
- console.log(` ${maturityColor(`[${entry.maturity}]`)} ${chalk.bold(entry.title)}`);
67
- console.log(` ${chalk.gray(`id: ${entry.id} | type: ${entry.type} | layer: ${entry.layer}`)}`);
69
+ log(io, ` ${maturityColor(`[${entry.maturity}]`)} ${chalk.bold(entry.title)}`);
70
+ log(io, ` ${chalk.gray(`id: ${entry.id} | type: ${entry.type} | layer: ${entry.layer}`)}`);
68
71
  if (entry.tags.length > 0) {
69
- console.log(` ${chalk.gray(`tags: ${entry.tags.join(', ')}`)}`);
72
+ log(io, ` ${chalk.gray(`tags: ${entry.tags.join(', ')}`)}`);
70
73
  }
71
74
  }
75
+ return { kind: 'ok' };
72
76
  }
73
77
 
74
78
  /**
@@ -77,28 +81,51 @@ export async function knowledgeList(options: KnowledgeOptions & {
77
81
  export async function knowledgeSearch(
78
82
  query: string,
79
83
  options: KnowledgeOptions & { limit?: number },
80
- ): Promise<void> {
81
- const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath) });
84
+ io: CommandIO = processIO,
85
+ ): Promise<CommandResult> {
86
+ const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath, io) });
82
87
  const queryEngine = new KnowledgeQuery(store);
83
88
 
84
89
  const matched = queryEngine.search(query, { limit: options.limit || 20 });
85
90
 
86
91
  if (options.json) {
87
- console.log(JSON.stringify({ query, total: matched.length, entries: matched }, null, 2));
88
- return;
92
+ log(io, JSON.stringify({ query, total: matched.length, entries: matched }, null, 2));
93
+ return { kind: 'ok' };
89
94
  }
90
95
 
91
96
  if (matched.length === 0) {
92
- console.log(chalk.yellow(`未找到匹配 "${query}" 的知识条目`));
93
- return;
97
+ log(io, chalk.yellow(`未找到匹配 "${query}" 的知识条目`));
98
+ return { kind: 'ok' };
94
99
  }
95
100
 
96
- console.log(chalk.blue(`🔍 搜索 "${query}" (${matched.length} 条结果)\n`));
101
+ log(io, chalk.blue(`🔍 搜索 "${query}" (${matched.length} 条结果)\n`));
97
102
  for (const entry of matched) {
98
- console.log(` ${chalk.bold(entry.title)} ${chalk.gray(`[${entry.maturity}]`)}`);
103
+ log(io, ` ${chalk.bold(entry.title)} ${chalk.gray(`[${entry.maturity}]`)}`);
99
104
  const preview = entry.content.slice(0, 100).replace(/\n/g, ' ');
100
- console.log(` ${chalk.gray(preview)}${entry.content.length > 100 ? '...' : ''}`);
105
+ log(io, ` ${chalk.gray(preview)}${entry.content.length > 100 ? '...' : ''}`);
101
106
  }
107
+ return { kind: 'ok' };
108
+ }
109
+
110
+ /**
111
+ * knowledge search 子命令入口(候选7):缺参闸门 + limit 强转编组,
112
+ * 自 definitions.ts 的 args 复印块移回命令模块(interface/测试面所在)。
113
+ */
114
+ export async function knowledgeSearchCommand(
115
+ positionals: (string | undefined)[],
116
+ options: Record<string, unknown>,
117
+ io: CommandIO = processIO,
118
+ ): Promise<CommandResult> {
119
+ const query = positionals[0];
120
+ if (!query) {
121
+ logError(io, '请提供搜索关键词');
122
+ return { kind: 'usage-error', reason: 'knowledge search 缺少关键词位置参数' };
123
+ }
124
+ return knowledgeSearch(String(query), {
125
+ projectPath: options.projectPath as string | undefined,
126
+ json: options.json as boolean | undefined,
127
+ limit: parseInt(String(options.limit), 10),
128
+ });
102
129
  }
103
130
 
104
131
  /**
@@ -106,9 +133,10 @@ export async function knowledgeSearch(
106
133
  */
107
134
  export async function knowledgeImport(
108
135
  options: KnowledgeOptions & { sources?: string; reset?: boolean },
109
- ): Promise<void> {
136
+ io: CommandIO = processIO,
137
+ ): Promise<CommandResult> {
110
138
  const projectPath = options.projectPath || process.cwd();
111
- const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath) });
139
+ const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath, io) });
112
140
 
113
141
  const sources = options.sources
114
142
  ? options.sources.split(',') as Array<'code' | 'git' | 'docs' | 'manual'>
@@ -123,12 +151,12 @@ export async function knowledgeImport(
123
151
  if (options.reset) {
124
152
  importer.resetState();
125
153
  if (!options.json) {
126
- console.log(chalk.yellow('🔄 已重置导入状态'));
154
+ log(io, chalk.yellow('🔄 已重置导入状态'));
127
155
  }
128
156
  }
129
157
 
130
158
  if (!options.json) {
131
- console.log(chalk.blue(`📥 开始导入知识 (源: ${sources.join(', ')})...`));
159
+ log(io, chalk.blue(`📥 开始导入知识 (源: ${sources.join(', ')})...`));
132
160
  }
133
161
 
134
162
  const results = await importer.importAll();
@@ -137,59 +165,61 @@ export async function knowledgeImport(
137
165
  const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
138
166
 
139
167
  if (options.json) {
140
- console.log(JSON.stringify({ totalImported, totalErrors, results }, null, 2));
141
- return;
168
+ log(io, JSON.stringify({ totalImported, totalErrors, results }, null, 2));
169
+ return { kind: 'ok' };
142
170
  }
143
171
 
144
172
  for (const result of results) {
145
173
  if (result.entries.length > 0) {
146
- console.log(chalk.green(` ✅ ${result.source.type}: ${result.entries.length} 条`));
174
+ log(io, chalk.green(` ✅ ${result.source.type}: ${result.entries.length} 条`));
147
175
  }
148
176
  if (result.errors.length > 0) {
149
177
  for (const err of result.errors) {
150
- console.log(chalk.red(` ❌ ${result.source.type}: ${err.message}`));
178
+ log(io, chalk.red(` ❌ ${result.source.type}: ${err.message}`));
151
179
  }
152
180
  }
153
181
  }
154
182
 
155
- console.log(chalk.green(`\n✅ 导入完成: ${totalImported} 条,${totalErrors} 个错误`));
183
+ log(io, chalk.green(`\n✅ 导入完成: ${totalImported} 条,${totalErrors} 个错误`));
184
+ return { kind: 'ok' };
156
185
  }
157
186
 
158
187
  /**
159
188
  * 知识库衰减周期
160
189
  */
161
- export async function knowledgeDecay(options: KnowledgeOptions): Promise<void> {
162
- const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath) });
190
+ export async function knowledgeDecay(options: KnowledgeOptions, io: CommandIO = processIO): Promise<CommandResult> {
191
+ const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath, io) });
163
192
  const lifecycle = new KnowledgeLifecycle(store);
164
193
 
165
194
  if (!options.json) {
166
- console.log(chalk.blue('🔄 运行衰减周期...'));
195
+ log(io, chalk.blue('🔄 运行衰减周期...'));
167
196
  }
168
197
 
169
198
  const changes = lifecycle.runDecayCycle();
170
199
 
171
200
  if (options.json) {
172
- console.log(JSON.stringify({ changes }, null, 2));
173
- return;
201
+ log(io, JSON.stringify({ changes }, null, 2));
202
+ return { kind: 'ok' };
174
203
  }
175
204
 
176
205
  if (changes.length === 0) {
177
- console.log(chalk.green('✅ 没有需要衰减的知识条目'));
178
- return;
206
+ log(io, chalk.green('✅ 没有需要衰减的知识条目'));
207
+ return { kind: 'ok' };
179
208
  }
180
209
 
181
- console.log(chalk.yellow(`📉 ${changes.length} 条知识发生衰减:\n`));
210
+ log(io, chalk.yellow(`📉 ${changes.length} 条知识发生衰减:\n`));
182
211
  for (const change of changes) {
183
- console.log(` ${change.entryId}: ${chalk.red(change.from)} → ${chalk.green(change.to)}`);
184
- console.log(` ${chalk.gray(change.reason)}`);
212
+ log(io, ` ${change.entryId}: ${chalk.red(change.from)} → ${chalk.green(change.to)}`);
213
+ log(io, ` ${chalk.gray(change.reason)}`);
185
214
  }
215
+ return { kind: 'ok' };
186
216
  }
187
217
 
188
218
  /**
189
219
  * 知识库统计
190
220
  */
191
- export async function knowledgeStats(options: KnowledgeOptions): Promise<void> {
192
- const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath) });
221
+ export async function knowledgeStats(options: KnowledgeOptions, io: CommandIO = processIO): Promise<CommandResult> {
222
+ const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath, io) });
193
223
  const entries = store.list({ excludeArchived: false });
194
224
  const active = entries.filter(e => e.maturity !== 'archived');
195
225
 
@@ -203,57 +233,57 @@ export async function knowledgeStats(options: KnowledgeOptions): Promise<void> {
203
233
  byLayer[entry.layer] = (byLayer[entry.layer] || 0) + 1;
204
234
  }
205
235
 
206
- // D6 flywheel metrics
207
- const withRefs = active.filter(e => e.referencedBy.length > 0).length;
208
- const refCoverage = active.length > 0 ? Math.round(withRefs / active.length * 100) : 0;
209
- const avgRefs = active.length > 0
210
- ? Math.round(active.reduce((sum, e) => sum + e.referencedBy.length, 0) / active.length * 10) / 10
211
- : 0;
212
- let consumptionHitRate = 0;
236
+ // 飞轮指标:计算唯一实现在 knowledge/flywheel-metrics,此处只做 fs 读取 + 展示层单位映射
237
+ let dailyConsumptionEvents = 0;
213
238
  try {
214
239
  const statsPath = path.join(store.getBaseDir(), '.consumption-stats.json');
215
240
  if (fs.existsSync(statsPath)) {
216
241
  const stats = JSON.parse(fs.readFileSync(statsPath, 'utf-8'));
217
- const dailyEvents = stats.dailyEvents || 0;
218
- consumptionHitRate = active.length > 0 ? Math.min(Math.round(dailyEvents / active.length * 100), 100) : 0;
242
+ dailyConsumptionEvents = stats.dailyEvents || 0;
219
243
  }
220
244
  } catch { /* best-effort */ }
221
245
 
246
+ const metrics = evaluateFlywheel({ entries: active, dailyConsumptionEvents });
247
+ const refCoverage = Math.round(metrics.refCoverage * 100);
248
+ const avgRefs = Math.round(metrics.avgRefs * 10) / 10;
249
+ const consumptionHitRate = Math.round(metrics.consumptionHitRate * 100);
250
+
222
251
  const flywheel = { refCoverage, avgRefs, consumptionHitRate };
223
252
 
224
253
  if (options.json) {
225
- console.log(JSON.stringify({ total: entries.length, byType, byMaturity, byLayer, flywheel }, null, 2));
226
- return;
254
+ log(io, JSON.stringify({ total: entries.length, byType, byMaturity, byLayer, flywheel }, null, 2));
255
+ return { kind: 'ok' };
227
256
  }
228
257
 
229
- console.log(chalk.blue(`📊 知识库统计\n`));
230
- console.log(chalk.bold(` 总计: ${entries.length} 条 (活跃: ${active.length})\n`));
258
+ log(io, chalk.blue(`📊 知识库统计\n`));
259
+ log(io, chalk.bold(` 总计: ${entries.length} 条 (活跃: ${active.length})\n`));
231
260
 
232
- console.log(chalk.bold(' 按类型:'));
261
+ log(io, chalk.bold(' 按类型:'));
233
262
  for (const [type, count] of Object.entries(byType)) {
234
- console.log(` ${type}: ${count}`);
263
+ log(io, ` ${type}: ${count}`);
235
264
  }
236
265
 
237
- console.log(chalk.bold('\n 按成熟度:'));
266
+ log(io, chalk.bold('\n 按成熟度:'));
238
267
  for (const [maturity, count] of Object.entries(byMaturity)) {
239
268
  const color = maturity === 'proven' ? chalk.green : maturity === 'archived' ? chalk.gray : chalk.yellow;
240
- console.log(` ${color(maturity)}: ${count}`);
269
+ log(io, ` ${color(maturity)}: ${count}`);
241
270
  }
242
271
 
243
- console.log(chalk.bold('\n 按层级:'));
272
+ log(io, chalk.bold('\n 按层级:'));
244
273
  for (const [layer, count] of Object.entries(byLayer)) {
245
- console.log(` ${layer}: ${count}`);
274
+ log(io, ` ${layer}: ${count}`);
246
275
  }
247
276
 
248
- console.log(chalk.bold('\n 飞轮指标:'));
249
- console.log(` 引用覆盖: ${refCoverage}%`);
250
- console.log(` 平均引用: ${avgRefs}`);
251
- console.log(` 消费命中率: ${consumptionHitRate}%`);
277
+ log(io, chalk.bold('\n 飞轮指标:'));
278
+ log(io, ` 引用覆盖: ${refCoverage}%`);
279
+ log(io, ` 平均引用: ${avgRefs}`);
280
+ log(io, ` 消费命中率: ${consumptionHitRate}%`);
281
+ return { kind: 'ok' };
252
282
  }
253
283
 
254
284
  export interface KnowledgeUpsertOptions {
255
- scope: string;
256
- title: string;
285
+ scope?: string;
286
+ title?: string;
257
287
  content?: string;
258
288
  file?: string;
259
289
  type?: string;
@@ -265,7 +295,7 @@ export interface KnowledgeUpsertOptions {
265
295
  *
266
296
  * 调用 Studio API POST /api/knowledge/upsert(内部端点,无 auth)
267
297
  */
268
- export async function knowledgeUpsert(options: KnowledgeUpsertOptions): Promise<void> {
298
+ export async function knowledgeUpsert(options: KnowledgeUpsertOptions, io: CommandIO = processIO): Promise<CommandResult> {
269
299
  const apiPort = process.env.API_PORT || '3001';
270
300
  const url = `http://localhost:${apiPort}/api/knowledge/upsert`;
271
301
 
@@ -275,15 +305,15 @@ export async function knowledgeUpsert(options: KnowledgeUpsertOptions): Promise<
275
305
  try {
276
306
  content = fs.readFileSync(options.file, 'utf-8');
277
307
  } catch (e: any) {
278
- console.error(chalk.red(`Failed to read file: ${options.file}`));
279
- console.error(chalk.red(String(e)));
280
- process.exit(1);
308
+ logError(io, chalk.red(`Failed to read file: ${options.file}`));
309
+ logError(io, chalk.red(String(e)));
310
+ return { kind: 'fail', reason: `无法读取 --file ${options.file}: ${String(e)}` };
281
311
  }
282
312
  }
283
313
 
284
314
  if (!options.scope || !options.title || !content) {
285
- console.error(chalk.red('--scope, --title, and --content (or --file) are required'));
286
- process.exit(1);
315
+ logError(io, chalk.red('--scope, --title, and --content (or --file) are required'));
316
+ return { kind: 'usage-error', reason: '--scope/--title/--content(--file) 缺一不可' };
287
317
  }
288
318
 
289
319
  try {
@@ -301,21 +331,22 @@ export async function knowledgeUpsert(options: KnowledgeUpsertOptions): Promise<
301
331
 
302
332
  if (!res.ok) {
303
333
  const err: any = await res.json().catch(() => ({ error: res.statusText }));
304
- console.error(chalk.red(`API error ${res.status}: ${err.error || res.statusText}`));
305
- process.exit(1);
334
+ logError(io, chalk.red(`API error ${res.status}: ${err.error || res.statusText}`));
335
+ return { kind: 'fail', reason: `upsert API error ${res.status}: ${err.error || res.statusText}` };
306
336
  }
307
337
 
308
338
  const result: any = await res.json();
309
- console.log(chalk.green(`✅ Knowledge upserted`));
310
- console.log(` KnowledgeStore: ${result.knowledgeStore?.action} → ${result.knowledgeStore?.entryId}`);
311
- console.log(` Studio UI: ${result.prismaDocument?.action} → ${result.prismaDocument?.docId || 'skipped'}`);
339
+ log(io, chalk.green(`✅ Knowledge upserted`));
340
+ log(io, ` KnowledgeStore: ${result.knowledgeStore?.action} → ${result.knowledgeStore?.entryId}`);
341
+ log(io, ` Studio UI: ${result.prismaDocument?.action} → ${result.prismaDocument?.docId || 'skipped'}`);
342
+ return { kind: 'ok' };
312
343
  } catch (e: any) {
313
344
  if (e?.code === 'ECONNREFUSED') {
314
- console.error(chalk.red(`Cannot reach Studio API at ${url}. Is the API running?`));
345
+ logError(io, chalk.red(`Cannot reach Studio API at ${url}. Is the API running?`));
315
346
  } else {
316
- console.error(chalk.red(`Upsert failed: ${e.message}`));
347
+ logError(io, chalk.red(`Upsert failed: ${e.message}`));
317
348
  }
318
- process.exit(1);
349
+ return { kind: 'fail', reason: `knowledge upsert 失败: ${e?.code === 'ECONNREFUSED' ? `Studio API 不可达 ${url}` : e.message}` };
319
350
  }
320
351
  }
321
352
 
@@ -323,38 +354,39 @@ export async function knowledgeUpsert(options: KnowledgeUpsertOptions): Promise<
323
354
  /**
324
355
  * RAG 同步:扫描 .harness/knowledge-docs/ 输出需要 ingest 的文件列表
325
356
  */
326
- export async function knowledgeSyncRag(options: KnowledgeOptions): Promise<void> {
357
+ export async function knowledgeSyncRag(options: KnowledgeOptions, io: CommandIO = processIO): Promise<CommandResult> {
327
358
  const projectPath = options.projectPath || process.cwd();
328
359
  const docsDir = `${projectPath}/.harness/knowledge-docs`;
329
360
 
330
361
  if (!fs.existsSync(docsDir)) {
331
- console.log(chalk.yellow('No .harness/knowledge-docs/ directory'));
332
- return;
362
+ log(io, chalk.yellow('No .harness/knowledge-docs/ directory'));
363
+ return { kind: 'ok' };
333
364
  }
334
365
 
335
366
  const files = fs.readdirSync(docsDir).filter(f => f.endsWith('.md'));
336
367
  if (files.length === 0) {
337
- console.log(chalk.gray('No knowledge docs found'));
338
- return;
368
+ log(io, chalk.gray('No knowledge docs found'));
369
+ return { kind: 'ok' };
339
370
  }
340
371
 
341
372
  if (options.json) {
342
- console.log(JSON.stringify({ directory: docsDir, files }, null, 2));
343
- return;
373
+ log(io, JSON.stringify({ directory: docsDir, files }, null, 2));
374
+ return { kind: 'ok' };
344
375
  }
345
376
 
346
- console.log(chalk.blue(`📄 RAG sync candidates: ${files.length} files in ${docsDir}\n`));
377
+ log(io, chalk.blue(`📄 RAG sync candidates: ${files.length} files in ${docsDir}\n`));
347
378
  for (const file of files) {
348
- console.log(chalk.cyan(` ${file}`));
379
+ log(io, chalk.cyan(` ${file}`));
349
380
  }
350
- console.log();
351
- console.log(chalk.gray('Run `mcp__local-rag__ingest_file` for each to sync to RAG'));
381
+ log(io);
382
+ log(io, chalk.gray('Run `mcp__local-rag__ingest_file` for each to sync to RAG'));
383
+ return { kind: 'ok' };
352
384
  }
353
385
 
354
386
  /**
355
387
  * 知识同步状态:检测所有 tracked scope 的新鲜度
356
388
  */
357
- export async function knowledgeSyncStatus(options: KnowledgeOptions): Promise<void> {
389
+ export async function knowledgeSyncStatus(options: KnowledgeOptions, io: CommandIO = processIO): Promise<CommandResult> {
358
390
  const apiPort = process.env.API_PORT || '3001';
359
391
  const url = `http://localhost:${apiPort}/api/knowledge/sync-status`;
360
392
 
@@ -362,48 +394,49 @@ export async function knowledgeSyncStatus(options: KnowledgeOptions): Promise<vo
362
394
  const res = await fetch(url);
363
395
  if (!res.ok) {
364
396
  const err: any = await res.json().catch(() => ({ error: res.statusText }));
365
- console.error(chalk.red(`API error ${res.status}: ${err.error || res.statusText}`));
366
- process.exit(1);
397
+ logError(io, chalk.red(`API error ${res.status}: ${err.error || res.statusText}`));
398
+ return { kind: 'fail', reason: `sync-status API error ${res.status}: ${err.error || res.statusText}` };
367
399
  }
368
400
 
369
401
  const data: any = await res.json();
370
402
 
371
403
  if (options.json) {
372
- console.log(JSON.stringify(data, null, 2));
373
- return;
404
+ log(io, JSON.stringify(data, null, 2));
405
+ return { kind: 'ok' };
374
406
  }
375
407
 
376
- console.log(chalk.blue(`🔄 KnowledgeSync Status\n`));
377
- console.log(chalk.bold(`Tracked scopes: ${data.trackedScopes?.join(', ') || 'none'}`));
378
- console.log(chalk.bold(`Stale entries: ${data.stale?.length || 0}`));
408
+ log(io, chalk.blue(`🔄 KnowledgeSync Status\n`));
409
+ log(io, chalk.bold(`Tracked scopes: ${data.trackedScopes?.join(', ') || 'none'}`));
410
+ log(io, chalk.bold(`Stale entries: ${data.stale?.length || 0}`));
379
411
 
380
412
  if (data.stale?.length > 0) {
381
- console.log(chalk.yellow(`\n⚠️ Stale knowledge:\n`));
413
+ log(io, chalk.yellow(`\n⚠️ Stale knowledge:\n`));
382
414
  for (const s of data.stale) {
383
- console.log(chalk.yellow(` ${s.scope} (${s.title}): ${s.stalenessHours}h old, files changed: ${s.changedFiles.join(', ')}`));
415
+ log(io, chalk.yellow(` ${s.scope} (${s.title}): ${s.stalenessHours}h old, files changed: ${s.changedFiles.join(', ')}`));
384
416
  }
385
417
  } else {
386
- console.log(chalk.green('\n✅ All knowledge fresh'));
418
+ log(io, chalk.green('\n✅ All knowledge fresh'));
387
419
  }
388
420
 
389
421
  if (data.healed?.length > 0) {
390
- console.log(chalk.cyan(`\n🩹 Auto-healed: ${data.healed.join(', ')}`));
422
+ log(io, chalk.cyan(`\n🩹 Auto-healed: ${data.healed.join(', ')}`));
391
423
  }
392
424
  } catch (e: any) {
393
425
  if (e?.code === 'ECONNREFUSED') {
394
- console.error(chalk.red(`Cannot reach Studio API at ${url}`));
426
+ logError(io, chalk.red(`Cannot reach Studio API at ${url}`));
395
427
  } else {
396
- console.error(chalk.red(`Sync check failed: ${e.message}`));
428
+ logError(io, chalk.red(`Sync check failed: ${e.message}`));
397
429
  }
398
- process.exit(1);
430
+ return { kind: 'fail', reason: `knowledge sync status 检查失败: ${e?.code === 'ECONNREFUSED' ? `Studio API 不可达 ${url}` : e.message}` };
399
431
  }
432
+ return { kind: 'ok' };
400
433
  }
401
434
 
402
435
  /**
403
436
  * 飞轮健康检查 — 零 token 检测知识飞轮数据流状态
404
437
  */
405
- export async function knowledgeHealth(options: KnowledgeOptions & { dir?: string }): Promise<void> {
406
- const baseDir = options.dir || getKnowledgeDir(options.projectPath);
438
+ export async function knowledgeHealth(options: KnowledgeOptions & { dir?: string }, io: CommandIO = processIO): Promise<CommandResult> {
439
+ const baseDir = options.dir || getKnowledgeDir(options.projectPath, io);
407
440
  const store = new KnowledgeStore({ baseDir });
408
441
  const entries = store.list({ excludeArchived: true });
409
442
 
@@ -439,12 +472,10 @@ export async function knowledgeHealth(options: KnowledgeOptions & { dir?: string
439
472
  issues.push({ severity: 'info', entry: '-', detail: `消费追踪数据不存在(${statsPath})` });
440
473
  }
441
474
 
442
- // D4: 飞轮指标
443
- const withRefs = entries.filter(e => e.referencedBy.length > 0).length;
444
- const refCoverage = entries.length > 0 ? Math.round(withRefs / entries.length * 100) : 0;
445
- const avgRefs = entries.length > 0
446
- ? Math.round(entries.reduce((sum, e) => sum + e.referencedBy.length, 0) / entries.length * 10) / 10
447
- : 0;
475
+ // D4: 飞轮指标(计算唯一实现在 knowledge/flywheel-metrics;人口为本命令的 excludeArchived 切片)
476
+ const metrics = evaluateFlywheel({ entries });
477
+ const refCoverage = Math.round(metrics.refCoverage * 100);
478
+ const avgRefs = Math.round(metrics.avgRefs * 10) / 10;
448
479
 
449
480
  // 健康分计算
450
481
  const totalIssues = issues.filter(i => i.severity === 'error').length * 3
@@ -452,7 +483,7 @@ export async function knowledgeHealth(options: KnowledgeOptions & { dir?: string
452
483
  const healthScore = Math.max(0, 100 - totalIssues);
453
484
 
454
485
  if (options.json) {
455
- console.log(JSON.stringify({
486
+ log(io, JSON.stringify({
456
487
  healthScore,
457
488
  summary: {
458
489
  total: entries.length,
@@ -464,39 +495,40 @@ export async function knowledgeHealth(options: KnowledgeOptions & { dir?: string
464
495
  },
465
496
  issues: issues.slice(0, 50),
466
497
  }, null, 2));
467
- return;
498
+ return { kind: 'ok' };
468
499
  }
469
500
 
470
- console.log(chalk.blue('🏥 飞轮健康检查\n'));
471
- console.log(chalk.bold(` 健康分: ${healthScore >= 80 ? chalk.green(healthScore) : healthScore >= 60 ? chalk.yellow(healthScore) : chalk.red(healthScore)}/100`));
472
- console.log(chalk.bold(` 活跃条目: ${entries.length}`));
473
- console.log();
501
+ log(io, chalk.blue('🏥 飞轮健康检查\n'));
502
+ log(io, chalk.bold(` 健康分: ${healthScore >= 80 ? chalk.green(healthScore) : healthScore >= 60 ? chalk.yellow(healthScore) : chalk.red(healthScore)}/100`));
503
+ log(io, chalk.bold(` 活跃条目: ${entries.length}`));
504
+ log(io);
474
505
 
475
- console.log(chalk.bold(' 数据流状态:'));
476
- console.log(` 引用密度: ${lowRefEntries === 0 ? chalk.green('✓') : chalk.yellow(`⚠ ${lowRefEntries} 个零引用 verified`)}`);
477
- console.log(` 新鲜度: ${staleEntries === 0 ? chalk.green('✓') : chalk.yellow(`⚠ ${staleEntries} 个过期 draft`)}`);
478
- console.log(` 消费追踪: ${consumptionData ? chalk.green('✓') : chalk.gray('○ 未启用')}`);
479
- console.log();
506
+ log(io, chalk.bold(' 数据流状态:'));
507
+ log(io, ` 引用密度: ${lowRefEntries === 0 ? chalk.green('✓') : chalk.yellow(`⚠ ${lowRefEntries} 个零引用 verified`)}`);
508
+ log(io, ` 新鲜度: ${staleEntries === 0 ? chalk.green('✓') : chalk.yellow(`⚠ ${staleEntries} 个过期 draft`)}`);
509
+ log(io, ` 消费追踪: ${consumptionData ? chalk.green('✓') : chalk.gray('○ 未启用')}`);
510
+ log(io);
480
511
 
481
- console.log(chalk.bold(' 飞轮指标:'));
482
- console.log(` 引用覆盖: ${refCoverage}%`);
483
- console.log(` 平均引用: ${avgRefs}`);
484
- console.log();
512
+ log(io, chalk.bold(' 飞轮指标:'));
513
+ log(io, ` 引用覆盖: ${refCoverage}%`);
514
+ log(io, ` 平均引用: ${avgRefs}`);
515
+ log(io);
485
516
 
486
517
  if (issues.length > 0) {
487
- console.log(chalk.bold(` 问题 (前 20):`));
518
+ log(io, chalk.bold(` 问题 (前 20):`));
488
519
  for (const issue of issues.slice(0, 20)) {
489
520
  const sevColor = issue.severity === 'error' ? chalk.red
490
521
  : issue.severity === 'warn' ? chalk.yellow
491
522
  : chalk.gray;
492
- console.log(` ${sevColor(`[${issue.severity}]`)} ${issue.entry}: ${issue.detail}`);
523
+ log(io, ` ${sevColor(`[${issue.severity}]`)} ${issue.entry}: ${issue.detail}`);
493
524
  }
494
525
  if (issues.length > 20) {
495
- console.log(chalk.gray(` ... 还有 ${issues.length - 20} 条`));
526
+ log(io, chalk.gray(` ... 还有 ${issues.length - 20} 条`));
496
527
  }
497
528
  } else {
498
- console.log(chalk.green(' ✓ 无问题'));
529
+ log(io, chalk.green(' ✓ 无问题'));
499
530
  }
531
+ return { kind: 'ok' };
500
532
  }
501
533
 
502
534
  /** 缺省知识库数据根(相对用户 home) */
@@ -514,7 +546,7 @@ function hasKnowledgeData(dir: string): boolean {
514
546
  }
515
547
  }
516
548
 
517
- function getKnowledgeDir(projectPath?: string): string {
549
+ function getKnowledgeDir(projectPath: string | undefined, io: CommandIO): string {
518
550
  if (projectPath) return `${projectPath}/.harness/knowledge`;
519
551
  if (process.env.KNOWLEDGE_BASE_DIR) return process.env.KNOWLEDGE_BASE_DIR;
520
552
  const defaultDir = path.join(os.homedir(), KNOWLEDGE_DATA_DIR);
@@ -523,7 +555,7 @@ function getKnowledgeDir(projectPath?: string): string {
523
555
  if (!legacyKnowledgeDirNotified) {
524
556
  legacyKnowledgeDirNotified = true;
525
557
  // stderr:不污染 --json 的 stdout 输出
526
- console.error(chalk.yellow(`⚠ 缺省知识库根已改为 ${defaultDir};旧目录 ${legacyDir} 仍有数据,本次沿用(免迁移,可用 KNOWLEDGE_BASE_DIR 覆盖)`));
558
+ logError(io, chalk.yellow(`⚠ 缺省知识库根已改为 ${defaultDir};旧目录 ${legacyDir} 仍有数据,本次沿用(免迁移,可用 KNOWLEDGE_BASE_DIR 覆盖)`));
527
559
  }
528
560
  return legacyDir;
529
561
  }
@@ -538,154 +570,143 @@ export async function knowledgeAudit(options: KnowledgeOptions & {
538
570
  dryRun?: boolean;
539
571
  threshold?: number;
540
572
  dir?: string;
541
- }): Promise<void> {
573
+ }, io: CommandIO = processIO): Promise<CommandResult> {
542
574
  const audit = new KnowledgeAudit({
543
- baseDir: options.dir || getKnowledgeDir(options.projectPath),
575
+ baseDir: options.dir || getKnowledgeDir(options.projectPath, io),
544
576
  shortContentThreshold: options.threshold ? parseInt(options.threshold as any, 10) : undefined,
545
577
  });
546
578
 
547
579
  const isDryRun = options.dryRun && !options.fix;
548
580
 
549
581
  if (!options.json && !isDryRun) {
550
- console.log(chalk.blue('🔍 知识库质量审计...\n'));
582
+ log(io, chalk.blue('🔍 知识库质量审计...\n'));
551
583
  }
552
584
 
553
585
  const report = audit.run({ autoFix: options.fix && !isDryRun });
554
586
 
555
587
  if (options.json) {
556
- console.log(JSON.stringify(report, null, 2));
557
- return;
588
+ log(io, JSON.stringify(report, null, 2));
589
+ return { kind: 'ok' };
558
590
  }
559
591
 
560
592
  // Summary
561
- console.log(chalk.bold(` 总条目: ${report.totalEntries}`));
562
- console.log(chalk.bold(` 健康分: ${report.healthScore.before}/100`));
593
+ log(io, chalk.bold(` 总条目: ${report.totalEntries}`));
594
+ log(io, chalk.bold(` 健康分: ${report.healthScore.before}/100`));
563
595
  if (report.autoFixed > 0) {
564
- console.log(chalk.green(` 自动修复: ${report.autoFixed} 条`));
565
- console.log(chalk.bold(` 修复后: ${report.healthScore.after}/100`));
596
+ log(io, chalk.green(` 自动修复: ${report.autoFixed} 条`));
597
+ log(io, chalk.bold(` 修复后: ${report.healthScore.after}/100`));
566
598
  }
567
- console.log();
599
+ log(io);
568
600
 
569
- // Dimension scores
570
- const dimLabels: Record<string, string> = {
601
+ // Dimension scores(Record 全键标注 → 编译期闭环,#109)
602
+ const dimLabels: Record<keyof AuditReport['dimensions'], string> = {
571
603
  structure: 'D1 结构完整性',
572
604
  content: 'D2 内容质量',
573
605
  dedup: 'D3 去重有效性',
574
606
  maturity: 'D4 成熟度健康',
575
607
  freshness: 'D5 新鲜度',
576
608
  flywheel: 'D6 飞轮验证',
609
+ incremental: 'D7 增量存活',
577
610
  };
578
- console.log(chalk.bold(' 维度评分:'));
611
+ log(io, chalk.bold(' 维度评分:'));
579
612
  for (const [key, dim] of Object.entries(report.dimensions)) {
580
- const label = dimLabels[key] || key;
613
+ const label = dimLabels[key as keyof AuditReport['dimensions']];
581
614
  const scoreColor = dim.score >= 80 ? chalk.green : dim.score >= 60 ? chalk.yellow : chalk.red;
582
- console.log(` ${label}: ${scoreColor(`${dim.score}/100`)} (${dim.issues} 问题)`);
583
- }
584
- console.log();
585
-
586
- // Issues by rule
587
- const ruleLabels: Record<string, string> = {
588
- 'frontmatter-missing': 'frontmatter 缺失',
589
- 'test-data-pollution': '测试数据污染',
590
- 'daily-audit-noise': '每日审计噪音',
591
- 'zero-content-proven': '零内容 proven',
592
- 'short-content': '短内容',
593
- 'maturity-inflation': '成熟度虚高',
594
- 'title-duplicate': '标题重复',
595
- 'source-refs-bloat': 'sourceReferences 膨胀',
596
- 'fragment-cluster': '碎片集群',
597
- 'promotion-blocked': 'promotion 受阻',
598
- 'orphan-draft': '孤儿 draft',
599
- 'stale-entry': '过期条目',
600
- };
615
+ log(io, ` ${label}: ${scoreColor(`${dim.score}/100`)} (${dim.issues} 问题)`);
616
+ }
617
+ log(io);
601
618
 
619
+ // Issues by rule(label 正本在 audit.ts 规则定义,AUDIT_RULE_LABELS 编译期闭环,#109)
602
620
  for (const [rule, count] of Object.entries(report.summary)) {
603
621
  if (count === 0) continue;
604
- const label = ruleLabels[rule] || rule;
605
- console.log(` ${chalk.red(`${label}: ${count}`)}`);
622
+ const label = AUDIT_RULE_LABELS[rule as AuditRuleName];
623
+ log(io, ` ${chalk.red(`${label}: ${count}`)}`);
606
624
  }
607
625
 
608
626
  // Detail (first 20)
609
627
  if (report.issues.length > 0) {
610
- console.log(chalk.bold(`\n 问题详情 (前 20 条):\n`));
628
+ log(io, chalk.bold(`\n 问题详情 (前 20 条):\n`));
611
629
  for (const issue of report.issues.slice(0, 20)) {
612
630
  const severityColor = issue.severity === 'critical' ? chalk.red
613
631
  : issue.severity === 'high' ? chalk.yellow
614
632
  : chalk.gray;
615
- console.log(` ${severityColor(`[${issue.severity}]`)} ${issue.entryId}: ${issue.title}`);
616
- console.log(` ${chalk.gray(issue.detail)} → ${issue.action}`);
633
+ log(io, ` ${severityColor(`[${issue.severity}]`)} ${issue.entryId}: ${issue.title}`);
634
+ log(io, ` ${chalk.gray(issue.detail)} → ${issue.action}`);
617
635
  }
618
636
  if (report.issues.length > 20) {
619
- console.log(chalk.gray(`\n ... 还有 ${report.issues.length - 20} 条`));
637
+ log(io, chalk.gray(`\n ... 还有 ${report.issues.length - 20} 条`));
620
638
  }
621
639
  }
622
640
 
623
641
  if (!options.fix && report.issues.length > 0) {
624
- console.log(chalk.yellow(`\n 使用 --fix 自动修复`));
642
+ log(io, chalk.yellow(`\n 使用 --fix 自动修复`));
625
643
  }
626
644
 
627
645
  // Auto-rebuild index after audit (files may have changed)
628
- const baseDir = options.dir || getKnowledgeDir(options.projectPath);
646
+ const baseDir = options.dir || getKnowledgeDir(options.projectPath, io);
629
647
  const idxGen = new KnowledgeIndexGenerator(baseDir);
630
648
  idxGen.regenerate();
631
649
  if (!options.json) {
632
- console.log(chalk.gray(` 📇 索引已重建`));
650
+ log(io, chalk.gray(` 📇 索引已重建`));
633
651
  }
652
+ return { kind: 'ok' };
634
653
  }
635
654
 
636
655
  /**
637
656
  * 知识库快照(KR4 存活率追踪)
638
657
  */
639
- export function knowledgeSnapshot(options: KnowledgeOptions & { dir?: string }): void {
640
- const store = new KnowledgeStore({ baseDir: options.dir || getKnowledgeDir(options.projectPath) });
658
+ export function knowledgeSnapshot(options: KnowledgeOptions & { dir?: string }, io: CommandIO = processIO): CommandResult {
659
+ const store = new KnowledgeStore({ baseDir: options.dir || getKnowledgeDir(options.projectPath, io) });
641
660
  const snapPath = store.snapshot();
642
661
 
643
662
  if (options.json) {
644
- console.log(JSON.stringify({ snapshotPath: snapPath }));
645
- return;
663
+ log(io, JSON.stringify({ snapshotPath: snapPath }));
664
+ return { kind: 'ok' };
646
665
  }
647
666
 
648
- console.log(chalk.green(`✅ 快照已保存: ${snapPath}`));
667
+ log(io, chalk.green(`✅ 快照已保存: ${snapPath}`));
668
+ return { kind: 'ok' };
649
669
  }
650
670
 
651
671
  /**
652
672
  * 知识库迁移 — 为现有条目添加 AS-021 新字段
653
673
  */
654
- export function knowledgeMigrate(options: KnowledgeOptions & { dir?: string }): void {
655
- const baseDir = options.dir || getKnowledgeDir(options.projectPath);
674
+ export function knowledgeMigrate(options: KnowledgeOptions & { dir?: string }, io: CommandIO = processIO): CommandResult {
675
+ const baseDir = options.dir || getKnowledgeDir(options.projectPath, io);
656
676
 
657
677
  if (options.json) {
658
678
  const result = migrateKnowledgeEntries(baseDir);
659
- console.log(JSON.stringify(result, null, 2));
660
- return;
679
+ log(io, JSON.stringify(result, null, 2));
680
+ return { kind: 'ok' };
661
681
  }
662
682
 
663
- console.log(chalk.blue('🔄 迁移知识条目(添加 consumptionMode/origin 字段)\n'));
683
+ log(io, chalk.blue('🔄 迁移知识条目(添加 consumptionMode/origin 字段)\n'));
664
684
  const result = migrateKnowledgeEntries(baseDir);
665
685
 
666
- console.log(chalk.green(` 总计: ${result.total} 条`));
667
- console.log(chalk.green(` 已迁移: ${result.migrated} 条`));
668
- console.log(chalk.gray(` 已跳过: ${result.skipped} 条`));
686
+ log(io, chalk.green(` 总计: ${result.total} 条`));
687
+ log(io, chalk.green(` 已迁移: ${result.migrated} 条`));
688
+ log(io, chalk.gray(` 已跳过: ${result.skipped} 条`));
669
689
 
670
690
  if (result.errors.length > 0) {
671
- console.log(chalk.red(`\n 错误: ${result.errors.length} 条`));
691
+ log(io, chalk.red(`\n 错误: ${result.errors.length} 条`));
672
692
  for (const err of result.errors.slice(0, 5)) {
673
- console.log(chalk.red(` ${err}`));
693
+ log(io, chalk.red(` ${err}`));
674
694
  }
675
695
  }
676
696
 
677
697
  if (result.migrated === 0 && result.errors.length === 0) {
678
- console.log(chalk.green('\n✅ 所有条目已是最新,无需迁移'));
698
+ log(io, chalk.green('\n✅ 所有条目已是最新,无需迁移'));
679
699
  } else if (result.migrated > 0) {
680
- console.log(chalk.green(`\n✅ 迁移完成`));
700
+ log(io, chalk.green(`\n✅ 迁移完成`));
681
701
  }
702
+ return { kind: 'ok' };
682
703
  }
683
704
 
684
705
  /**
685
706
  * 知识库索引重建 — 生成 _index.md 供 Agent grep 使用,同时同步 index.json
686
707
  */
687
- export function knowledgeIndex(options: KnowledgeOptions & { dir?: string }): void {
688
- const baseDir = options.dir || getKnowledgeDir(options.projectPath);
708
+ export function knowledgeIndex(options: KnowledgeOptions & { dir?: string }, io: CommandIO = processIO): CommandResult {
709
+ const baseDir = options.dir || getKnowledgeDir(options.projectPath, io);
689
710
  const gen = new KnowledgeIndexGenerator(baseDir);
690
711
 
691
712
  const beforeSize = (() => {
@@ -703,20 +724,21 @@ export function knowledgeIndex(options: KnowledgeOptions & { dir?: string }): vo
703
724
  const lineCount = output.split('\n').filter(l => !l.startsWith('#')).length;
704
725
 
705
726
  if (options.json) {
706
- console.log(JSON.stringify({
727
+ log(io, JSON.stringify({
707
728
  path: path.join(baseDir, '_index.md'),
708
729
  entries: lineCount,
709
730
  size: afterSize,
710
731
  previousSize: beforeSize,
711
732
  }));
712
- return;
733
+ return { kind: 'ok' };
713
734
  }
714
735
 
715
- console.log(chalk.blue(`📇 索引已重建`));
716
- console.log(chalk.gray(` 路径: ${path.join(baseDir, '_index.md')}`));
717
- console.log(chalk.green(` 条目: ${lineCount}`));
718
- console.log(chalk.green(` 大小: ${(afterSize / 1024).toFixed(1)} KB`));
736
+ log(io, chalk.blue(`📇 索引已重建`));
737
+ log(io, chalk.gray(` 路径: ${path.join(baseDir, '_index.md')}`));
738
+ log(io, chalk.green(` 条目: ${lineCount}`));
739
+ log(io, chalk.green(` 大小: ${(afterSize / 1024).toFixed(1)} KB`));
719
740
  if (beforeSize > 0) {
720
- console.log(chalk.gray(` 旧大小: ${(beforeSize / 1024).toFixed(1)} KB`));
741
+ log(io, chalk.gray(` 旧大小: ${(beforeSize / 1024).toFixed(1)} KB`));
721
742
  }
743
+ return { kind: 'ok' };
722
744
  }