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