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