@dommaker/harness 0.16.8 → 0.17.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 (344) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +33 -32
  3. package/bin/harness.js +34 -20
  4. package/dist/cli/commands/check.d.ts +3 -2
  5. package/dist/cli/commands/check.d.ts.map +1 -1
  6. package/dist/cli/commands/check.js +69 -36
  7. package/dist/cli/commands/check.js.map +1 -1
  8. package/dist/cli/commands/constraints-report.d.ts +42 -0
  9. package/dist/cli/commands/constraints-report.d.ts.map +1 -0
  10. package/dist/cli/commands/constraints-report.js +233 -0
  11. package/dist/cli/commands/constraints-report.js.map +1 -0
  12. package/dist/cli/commands/constraints-retire.d.ts +71 -0
  13. package/dist/cli/commands/constraints-retire.d.ts.map +1 -0
  14. package/dist/cli/commands/constraints-retire.js +384 -0
  15. package/dist/cli/commands/constraints-retire.js.map +1 -0
  16. package/dist/cli/commands/constraints.d.ts +1 -1
  17. package/dist/cli/commands/constraints.d.ts.map +1 -1
  18. package/dist/cli/commands/constraints.js +4 -4
  19. package/dist/cli/commands/constraints.js.map +1 -1
  20. package/dist/cli/commands/index.d.ts +2 -1
  21. package/dist/cli/commands/index.d.ts.map +1 -1
  22. package/dist/cli/commands/index.js +10 -4
  23. package/dist/cli/commands/index.js.map +1 -1
  24. package/dist/cli/commands/init.d.ts +20 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -1
  26. package/dist/cli/commands/init.js +81 -59
  27. package/dist/cli/commands/init.js.map +1 -1
  28. package/dist/cli/commands/report.js +4 -4
  29. package/dist/cli/commands/report.js.map +1 -1
  30. package/dist/cli/commands/status.d.ts.map +1 -1
  31. package/dist/cli/commands/status.js +3 -4
  32. package/dist/cli/commands/status.js.map +1 -1
  33. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +19 -2
  34. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  35. package/dist/cli/commands/sync-docs/capabilities-syncer.js +79 -19
  36. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  37. package/dist/cli/commands/sync-docs/index.d.ts +2 -0
  38. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  39. package/dist/cli/commands/sync-docs/index.js +83 -20
  40. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  41. package/dist/cli/commands/validate.d.ts.map +1 -1
  42. package/dist/cli/commands/validate.js +1 -5
  43. package/dist/cli/commands/validate.js.map +1 -1
  44. package/dist/core/constraints/capabilities-parser.d.ts +14 -0
  45. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  46. package/dist/core/constraints/capabilities-parser.js +30 -0
  47. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  48. package/dist/core/constraints/checker.d.ts +13 -5
  49. package/dist/core/constraints/checker.d.ts.map +1 -1
  50. package/dist/core/constraints/checker.js +62 -25
  51. package/dist/core/constraints/checker.js.map +1 -1
  52. package/dist/core/constraints/checkers/capability-sync.d.ts +10 -0
  53. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  54. package/dist/core/constraints/checkers/capability-sync.js +73 -11
  55. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  56. package/dist/core/constraints/checkers/context-doc-sync.d.ts +3 -0
  57. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  58. package/dist/core/constraints/checkers/context-doc-sync.js +8 -2
  59. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  60. package/dist/core/constraints/checkers/docs-freshness.d.ts +1 -0
  61. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  62. package/dist/core/constraints/checkers/docs-freshness.js +39 -0
  63. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  64. package/dist/core/constraints/checkers/index.d.ts +8 -4
  65. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  66. package/dist/core/constraints/checkers/index.js +29 -46
  67. package/dist/core/constraints/checkers/index.js.map +1 -1
  68. package/dist/core/constraints/checkers/iron-flags.d.ts +6 -7
  69. package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
  70. package/dist/core/constraints/checkers/iron-flags.js +10 -17
  71. package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
  72. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts +13 -0
  73. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts.map +1 -0
  74. package/dist/core/constraints/checkers/no-hardcoded-credentials.js +91 -0
  75. package/dist/core/constraints/checkers/no-hardcoded-credentials.js.map +1 -0
  76. package/dist/core/constraints/checkers/types.d.ts +15 -4
  77. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  78. package/dist/core/constraints/checkers/types.js +16 -4
  79. package/dist/core/constraints/checkers/types.js.map +1 -1
  80. package/dist/core/constraints/context-builder.d.ts +6 -3
  81. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  82. package/dist/core/constraints/context-builder.js +29 -20
  83. package/dist/core/constraints/context-builder.js.map +1 -1
  84. package/dist/core/constraints/definitions/guidelines.d.ts +6 -2
  85. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  86. package/dist/core/constraints/definitions/guidelines.js +33 -574
  87. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  88. package/dist/core/constraints/definitions/iron-laws.d.ts +6 -2
  89. package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
  90. package/dist/core/constraints/definitions/iron-laws.js +48 -231
  91. package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
  92. package/dist/core/constraints/definitions/prompts.d.ts +12 -0
  93. package/dist/core/constraints/definitions/prompts.d.ts.map +1 -0
  94. package/dist/core/constraints/definitions/prompts.js +405 -0
  95. package/dist/core/constraints/definitions/prompts.js.map +1 -0
  96. package/dist/core/constraints/definitions.d.ts +15 -9
  97. package/dist/core/constraints/definitions.d.ts.map +1 -1
  98. package/dist/core/constraints/definitions.js +18 -12
  99. package/dist/core/constraints/definitions.js.map +1 -1
  100. package/dist/core/constraints/index.d.ts +3 -2
  101. package/dist/core/constraints/index.d.ts.map +1 -1
  102. package/dist/core/constraints/index.js +8 -2
  103. package/dist/core/constraints/index.js.map +1 -1
  104. package/dist/core/constraints/injection-drift.d.ts +48 -0
  105. package/dist/core/constraints/injection-drift.d.ts.map +1 -0
  106. package/dist/core/constraints/injection-drift.js +140 -0
  107. package/dist/core/constraints/injection-drift.js.map +1 -0
  108. package/dist/core/constraints/injection-renderer.d.ts +23 -0
  109. package/dist/core/constraints/injection-renderer.d.ts.map +1 -0
  110. package/dist/core/constraints/injection-renderer.js +44 -0
  111. package/dist/core/constraints/injection-renderer.js.map +1 -0
  112. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  113. package/dist/core/constraints/interceptor.js +3 -2
  114. package/dist/core/constraints/interceptor.js.map +1 -1
  115. package/dist/core/constraints/usage-report.d.ts +95 -0
  116. package/dist/core/constraints/usage-report.d.ts.map +1 -0
  117. package/dist/core/constraints/usage-report.js +206 -0
  118. package/dist/core/constraints/usage-report.js.map +1 -0
  119. package/dist/core/effective-constraints.d.ts +47 -0
  120. package/dist/core/effective-constraints.d.ts.map +1 -0
  121. package/dist/core/effective-constraints.js +60 -0
  122. package/dist/core/effective-constraints.js.map +1 -0
  123. package/dist/core/index.d.ts +1 -0
  124. package/dist/core/index.d.ts.map +1 -1
  125. package/dist/core/index.js +2 -0
  126. package/dist/core/index.js.map +1 -1
  127. package/dist/core/project-config-loader.d.ts +28 -3
  128. package/dist/core/project-config-loader.d.ts.map +1 -1
  129. package/dist/core/project-config-loader.js +74 -8
  130. package/dist/core/project-config-loader.js.map +1 -1
  131. package/dist/dashboard/data.d.ts +1 -9
  132. package/dist/dashboard/data.d.ts.map +1 -1
  133. package/dist/dashboard/data.js +7 -15
  134. package/dist/dashboard/data.js.map +1 -1
  135. package/dist/dashboard/types.d.ts +13 -1
  136. package/dist/dashboard/types.d.ts.map +1 -1
  137. package/dist/index.d.ts +0 -4
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +1 -13
  140. package/dist/index.js.map +1 -1
  141. package/dist/monitoring/index.d.ts +0 -2
  142. package/dist/monitoring/index.d.ts.map +1 -1
  143. package/dist/monitoring/index.js +0 -2
  144. package/dist/monitoring/index.js.map +1 -1
  145. package/dist/monitoring/knowledge-evolver.d.ts +1 -1
  146. package/dist/monitoring/knowledge-evolver.js +1 -1
  147. package/dist/monitoring/trace-analyzer.d.ts +1 -1
  148. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  149. package/dist/monitoring/trace-analyzer.js +16 -10
  150. package/dist/monitoring/trace-analyzer.js.map +1 -1
  151. package/dist/monitoring/traces.d.ts +2 -1
  152. package/dist/monitoring/traces.d.ts.map +1 -1
  153. package/dist/monitoring/traces.js +5 -3
  154. package/dist/monitoring/traces.js.map +1 -1
  155. package/dist/presets/standard.d.ts +3 -1
  156. package/dist/presets/standard.d.ts.map +1 -1
  157. package/dist/presets/standard.js +18 -8
  158. package/dist/presets/standard.js.map +1 -1
  159. package/dist/types/constraint.d.ts +51 -11
  160. package/dist/types/constraint.d.ts.map +1 -1
  161. package/dist/types/constraint.js +8 -4
  162. package/dist/types/constraint.js.map +1 -1
  163. package/dist/types/index.d.ts +1 -1
  164. package/dist/types/index.d.ts.map +1 -1
  165. package/dist/types/index.js.map +1 -1
  166. package/dist/types/monitoring-types.d.ts +2 -63
  167. package/dist/types/monitoring-types.d.ts.map +1 -1
  168. package/dist/types/monitoring-types.js +2 -2
  169. package/dist/types/project-config.d.ts +32 -1
  170. package/dist/types/project-config.d.ts.map +1 -1
  171. package/dist/types/trace.d.ts +16 -7
  172. package/dist/types/trace.d.ts.map +1 -1
  173. package/dist/types/trace.js +8 -0
  174. package/dist/types/trace.js.map +1 -1
  175. package/package.json +1 -1
  176. package/src/CONTEXT.md +2 -2
  177. package/src/__tests__/checker-extra.test.ts +356 -331
  178. package/src/__tests__/checker.test.ts +87 -724
  179. package/src/__tests__/constraint-doctor.test.ts +5 -5
  180. package/src/__tests__/doctor-extra.test.ts +164 -0
  181. package/src/__tests__/effective-constraints.test.ts +147 -0
  182. package/src/__tests__/extend-exceptions.test.ts +3 -3
  183. package/src/__tests__/iron-laws.test.ts +115 -91
  184. package/src/__tests__/no-hardcoded-credentials.test.ts +90 -0
  185. package/src/__tests__/presets.test.ts +7 -7
  186. package/src/__tests__/project-config-loader-extra.test.ts +4 -4
  187. package/src/__tests__/project-config-loader.test.ts +40 -1
  188. package/src/__tests__/skip-semantics.test.ts +322 -0
  189. package/src/cli/commands/CONTEXT.md +5 -3
  190. package/src/cli/commands/__tests__/check-drift.test.ts +127 -0
  191. package/src/cli/commands/__tests__/check-skip-output.test.ts +128 -0
  192. package/src/cli/commands/__tests__/check.test.ts +14 -25
  193. package/src/cli/commands/__tests__/constraints-report.test.ts +327 -0
  194. package/src/cli/commands/__tests__/constraints-retire.test.ts +283 -0
  195. package/src/cli/commands/__tests__/init-injection.test.ts +234 -0
  196. package/src/cli/commands/__tests__/status-extra.test.ts +11 -11
  197. package/src/cli/commands/__tests__/status.test.ts +1 -1
  198. package/src/cli/commands/__tests__/sync-docs.test.ts +163 -0
  199. package/src/cli/commands/check.ts +72 -38
  200. package/src/cli/commands/constraints-report.ts +234 -0
  201. package/src/cli/commands/constraints-retire.ts +443 -0
  202. package/src/cli/commands/constraints.ts +5 -5
  203. package/src/cli/commands/index.ts +2 -1
  204. package/src/cli/commands/init.ts +94 -66
  205. package/src/cli/commands/report.ts +6 -6
  206. package/src/cli/commands/status.ts +3 -4
  207. package/src/cli/commands/sync-docs/capabilities-syncer.ts +90 -15
  208. package/src/cli/commands/sync-docs/index.ts +87 -24
  209. package/src/cli/commands/validate.ts +1 -5
  210. package/src/core/CONTEXT.md +7 -4
  211. package/src/core/constraints/__tests__/injection-drift.test.ts +164 -0
  212. package/src/core/constraints/__tests__/interceptor.test.ts +4 -0
  213. package/src/core/constraints/capabilities-parser.ts +28 -0
  214. package/src/core/constraints/checker.ts +73 -28
  215. package/src/core/constraints/checkers/capability-sync.ts +89 -18
  216. package/src/core/constraints/checkers/context-doc-sync.ts +9 -2
  217. package/src/core/constraints/checkers/docs-freshness.ts +43 -0
  218. package/src/core/constraints/checkers/index.ts +38 -71
  219. package/src/core/constraints/checkers/iron-flags.ts +13 -49
  220. package/src/core/constraints/checkers/no-hardcoded-credentials.ts +93 -0
  221. package/src/core/constraints/checkers/types.ts +27 -5
  222. package/src/core/constraints/context-builder.ts +28 -19
  223. package/src/core/constraints/definitions/guidelines.ts +33 -599
  224. package/src/core/constraints/definitions/iron-laws.ts +48 -239
  225. package/src/core/constraints/definitions/prompts.ts +419 -0
  226. package/src/core/constraints/definitions.ts +18 -11
  227. package/src/core/constraints/index.ts +10 -1
  228. package/src/core/constraints/injection-drift.ts +139 -0
  229. package/src/core/constraints/injection-renderer.ts +44 -0
  230. package/src/core/constraints/interceptor.ts +3 -2
  231. package/src/core/constraints/usage-report.ts +254 -0
  232. package/src/core/effective-constraints.ts +85 -0
  233. package/src/core/index.ts +4 -1
  234. package/src/core/project-config-loader.ts +82 -9
  235. package/src/dashboard/__tests__/data.test.ts +5 -12
  236. package/src/dashboard/data.ts +9 -19
  237. package/src/dashboard/types.ts +18 -1
  238. package/src/index.ts +0 -12
  239. package/src/knowledge/CONTEXT.md +2 -2
  240. package/src/monitoring/CONTEXT.md +3 -4
  241. package/src/monitoring/index.ts +0 -2
  242. package/src/monitoring/knowledge-evolver.ts +1 -1
  243. package/src/monitoring/trace-analyzer.ts +15 -10
  244. package/src/monitoring/traces.ts +10 -6
  245. package/src/presets/__tests__/standard.test.ts +11 -9
  246. package/src/presets/standard.ts +24 -15
  247. package/src/types/constraint.ts +59 -14
  248. package/src/types/index.ts +1 -3
  249. package/src/types/monitoring-types.ts +2 -84
  250. package/src/types/project-config.ts +37 -1
  251. package/src/types/trace.ts +18 -7
  252. package/src/verification/CONTEXT.md +1 -1
  253. package/dist/cli/commands/flow.d.ts +0 -22
  254. package/dist/cli/commands/flow.d.ts.map +0 -1
  255. package/dist/cli/commands/flow.js +0 -275
  256. package/dist/cli/commands/flow.js.map +0 -1
  257. package/dist/constraints/lifecycle-runner.d.ts +0 -52
  258. package/dist/constraints/lifecycle-runner.d.ts.map +0 -1
  259. package/dist/constraints/lifecycle-runner.js +0 -151
  260. package/dist/constraints/lifecycle-runner.js.map +0 -1
  261. package/dist/constraints/registry.d.ts +0 -27
  262. package/dist/constraints/registry.d.ts.map +0 -1
  263. package/dist/constraints/registry.js +0 -128
  264. package/dist/constraints/registry.js.map +0 -1
  265. package/dist/constraints/types.d.ts +0 -53
  266. package/dist/constraints/types.d.ts.map +0 -1
  267. package/dist/constraints/types.js +0 -8
  268. package/dist/constraints/types.js.map +0 -1
  269. package/dist/core/constraints/checkers/always-pass.d.ts +0 -17
  270. package/dist/core/constraints/checkers/always-pass.d.ts.map +0 -1
  271. package/dist/core/constraints/checkers/always-pass.js +0 -23
  272. package/dist/core/constraints/checkers/always-pass.js.map +0 -1
  273. package/dist/core/constraints/checkers/guideline-flags.d.ts +0 -9
  274. package/dist/core/constraints/checkers/guideline-flags.d.ts.map +0 -1
  275. package/dist/core/constraints/checkers/guideline-flags.js +0 -13
  276. package/dist/core/constraints/checkers/guideline-flags.js.map +0 -1
  277. package/dist/core/constraints/checkers/no-any-type.d.ts +0 -6
  278. package/dist/core/constraints/checkers/no-any-type.d.ts.map +0 -1
  279. package/dist/core/constraints/checkers/no-any-type.js +0 -42
  280. package/dist/core/constraints/checkers/no-any-type.js.map +0 -1
  281. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts +0 -6
  282. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts.map +0 -1
  283. package/dist/core/constraints/checkers/no-excuse-patterns.js +0 -25
  284. package/dist/core/constraints/checkers/no-excuse-patterns.js.map +0 -1
  285. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts +0 -6
  286. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts.map +0 -1
  287. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js +0 -34
  288. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js.map +0 -1
  289. package/dist/core/constraints/checkers/no-performative-agreement.d.ts +0 -8
  290. package/dist/core/constraints/checkers/no-performative-agreement.d.ts.map +0 -1
  291. package/dist/core/constraints/checkers/no-performative-agreement.js +0 -23
  292. package/dist/core/constraints/checkers/no-performative-agreement.js.map +0 -1
  293. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts +0 -6
  294. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts.map +0 -1
  295. package/dist/core/constraints/checkers/no-simplification-without-approval.js +0 -30
  296. package/dist/core/constraints/checkers/no-simplification-without-approval.js.map +0 -1
  297. package/dist/core/constraints/checkers/test-coverage.d.ts +0 -6
  298. package/dist/core/constraints/checkers/test-coverage.d.ts.map +0 -1
  299. package/dist/core/constraints/checkers/test-coverage.js +0 -33
  300. package/dist/core/constraints/checkers/test-coverage.js.map +0 -1
  301. package/dist/core/constraints/checkers/yagni.d.ts +0 -8
  302. package/dist/core/constraints/checkers/yagni.d.ts.map +0 -1
  303. package/dist/core/constraints/checkers/yagni.js +0 -96
  304. package/dist/core/constraints/checkers/yagni.js.map +0 -1
  305. package/dist/core/constraints/definitions/tips.d.ts +0 -7
  306. package/dist/core/constraints/definitions/tips.d.ts.map +0 -1
  307. package/dist/core/constraints/definitions/tips.js +0 -62
  308. package/dist/core/constraints/definitions/tips.js.map +0 -1
  309. package/dist/evolution/auto-evolve.d.ts +0 -35
  310. package/dist/evolution/auto-evolve.d.ts.map +0 -1
  311. package/dist/evolution/auto-evolve.js +0 -64
  312. package/dist/evolution/auto-evolve.js.map +0 -1
  313. package/dist/evolution/index.d.ts +0 -2
  314. package/dist/evolution/index.d.ts.map +0 -1
  315. package/dist/evolution/index.js +0 -18
  316. package/dist/evolution/index.js.map +0 -1
  317. package/dist/monitoring/constraint-evolver.d.ts +0 -120
  318. package/dist/monitoring/constraint-evolver.d.ts.map +0 -1
  319. package/dist/monitoring/constraint-evolver.js +0 -410
  320. package/dist/monitoring/constraint-evolver.js.map +0 -1
  321. package/src/__tests__/constraint-evolver.test.ts +0 -405
  322. package/src/__tests__/doctor-evolver.test.ts +0 -381
  323. package/src/cli/commands/__tests__/flow.test.ts +0 -343
  324. package/src/cli/commands/flow.ts +0 -277
  325. package/src/constraints/__tests__/lifecycle-runner.test.ts +0 -246
  326. package/src/constraints/__tests__/registry.test.ts +0 -196
  327. package/src/constraints/lifecycle-runner.ts +0 -184
  328. package/src/constraints/registry.ts +0 -134
  329. package/src/constraints/types.ts +0 -59
  330. package/src/core/constraints/checkers/always-pass.ts +0 -22
  331. package/src/core/constraints/checkers/guideline-flags.ts +0 -30
  332. package/src/core/constraints/checkers/no-any-type.ts +0 -42
  333. package/src/core/constraints/checkers/no-excuse-patterns.ts +0 -24
  334. package/src/core/constraints/checkers/no-fuzzy-completion-claim.ts +0 -33
  335. package/src/core/constraints/checkers/no-performative-agreement.ts +0 -22
  336. package/src/core/constraints/checkers/no-simplification-without-approval.ts +0 -29
  337. package/src/core/constraints/checkers/test-coverage.ts +0 -35
  338. package/src/core/constraints/checkers/yagni.ts +0 -65
  339. package/src/core/constraints/definitions/tips.ts +0 -63
  340. package/src/evolution/CONTEXT.md +0 -22
  341. package/src/evolution/__tests__/auto-evolve.test.ts +0 -319
  342. package/src/evolution/auto-evolve.ts +0 -98
  343. package/src/evolution/index.ts +0 -1
  344. package/src/monitoring/constraint-evolver.ts +0 -501
@@ -436,4 +436,167 @@ describe('sync-docs command', () => {
436
436
  fs.rmSync(testDir, { recursive: true, force: true });
437
437
  });
438
438
  });
439
+
440
+ describe('module 模式(governance.capabilities.mode=module)', () => {
441
+ /** 写入 module 模式配置 */
442
+ const writeModuleConfig = (testDir: string) => {
443
+ const yaml = require('js-yaml');
444
+ fs.mkdirSync(path.join(testDir, '.harness'), { recursive: true });
445
+ fs.writeFileSync(
446
+ path.join(testDir, '.harness', 'config.yml'),
447
+ yaml.dump({ governance: { capabilities: { mode: 'module' } } })
448
+ );
449
+ };
450
+
451
+ it('写入模式不自动加文件行', async () => {
452
+ const testDir = path.join(tempDir, 'module-no-autorow');
453
+ writeModuleConfig(testDir);
454
+ fs.mkdirSync(path.join(testDir, 'src', 'core'), { recursive: true });
455
+ fs.mkdirSync(path.join(testDir, 'src', 'newdir'), { recursive: true });
456
+ fs.writeFileSync(path.join(testDir, 'src', 'core', 'a.ts'), 'export const a = 1;');
457
+ fs.writeFileSync(path.join(testDir, 'src', 'newdir', 'b.ts'), 'export const b = 1;');
458
+
459
+ fs.writeFileSync(
460
+ path.join(testDir, 'CAPABILITIES.md'),
461
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| 核心 | src/core/ | 核心模块 |'
462
+ );
463
+
464
+ const result = await syncDocs({ projectPath: testDir });
465
+ // 有未覆盖目录,仍报不同步
466
+ expect(result).toBe(false);
467
+ // 但不自动给 b.ts 加表格行
468
+ const content = fs.readFileSync(path.join(testDir, 'CAPABILITIES.md'), 'utf-8');
469
+ expect(content).not.toContain('b.ts');
470
+ expect(content).toContain('src/core/');
471
+
472
+ fs.rmSync(testDir, { recursive: true, force: true });
473
+ });
474
+
475
+ it('--check 模式 added 聚合为未覆盖目录', async () => {
476
+ const testDir = path.join(tempDir, 'module-check-dirs');
477
+ writeModuleConfig(testDir);
478
+ fs.mkdirSync(path.join(testDir, 'src', 'core'), { recursive: true });
479
+ fs.mkdirSync(path.join(testDir, 'src', 'newdir'), { recursive: true });
480
+ fs.writeFileSync(path.join(testDir, 'src', 'core', 'a.ts'), 'export const a = 1;');
481
+ fs.writeFileSync(path.join(testDir, 'src', 'newdir', 'b.ts'), 'export const b = 1;');
482
+
483
+ fs.writeFileSync(
484
+ path.join(testDir, 'CAPABILITIES.md'),
485
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| 核心 | src/core/ | 核心模块 |'
486
+ );
487
+
488
+ const result = await syncDocs({ projectPath: testDir, check: true });
489
+ expect(result).toBe(false);
490
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('未登记以下模块(目录)'));
491
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('src/newdir/'));
492
+
493
+ fs.rmSync(testDir, { recursive: true, force: true });
494
+ });
495
+
496
+ it('--json 模式 added 输出聚合目录', async () => {
497
+ const testDir = path.join(tempDir, 'module-json-dirs');
498
+ writeModuleConfig(testDir);
499
+ fs.mkdirSync(path.join(testDir, 'src', 'core'), { recursive: true });
500
+ fs.mkdirSync(path.join(testDir, 'src', 'newdir'), { recursive: true });
501
+ fs.writeFileSync(path.join(testDir, 'src', 'core', 'a.ts'), 'export const a = 1;');
502
+ fs.writeFileSync(path.join(testDir, 'src', 'newdir', 'b.ts'), 'export const b = 1;');
503
+
504
+ fs.writeFileSync(
505
+ path.join(testDir, 'CAPABILITIES.md'),
506
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| 核心 | src/core/ | 核心模块 |'
507
+ );
508
+
509
+ const result = await syncDocs({ projectPath: testDir, check: true, json: true });
510
+ expect(result).toBe(false);
511
+
512
+ const jsonCall = consoleSpy.mock.calls.find(
513
+ (call: unknown[]) => typeof call[0] === 'string' && call[0].startsWith('{')
514
+ );
515
+ expect(jsonCall).toBeDefined();
516
+ const parsed = JSON.parse(jsonCall![0]);
517
+ expect(parsed.added).toEqual([{ dir: 'src/newdir/' }]);
518
+ expect(parsed.resolution.some((r: any) => r.action === 'register-capability-dirs')).toBe(true);
519
+
520
+ fs.rmSync(testDir, { recursive: true, force: true });
521
+ });
522
+
523
+ it('幽灵行仍剔除', async () => {
524
+ const testDir = path.join(tempDir, 'module-ghost');
525
+ writeModuleConfig(testDir);
526
+ fs.mkdirSync(path.join(testDir, 'src', 'a'), { recursive: true });
527
+ fs.writeFileSync(path.join(testDir, 'src', 'a', 'kept.ts'), 'export const kept = 1;');
528
+
529
+ fs.writeFileSync(
530
+ path.join(testDir, 'CAPABILITIES.md'),
531
+ [
532
+ '# CAPABILITIES.md', '',
533
+ '> 最后更新: 2026-01-01', '',
534
+ '| 模块 | 文件 | 说明 |', '|------|------|------|',
535
+ '| kept | src/a/kept.ts | 真实文件 |',
536
+ '| ghost | src/a/ghost.ts | 幽灵条目 |',
537
+ '',
538
+ ].join('\n')
539
+ );
540
+
541
+ await syncDocs({ projectPath: testDir });
542
+ const content = fs.readFileSync(path.join(testDir, 'CAPABILITIES.md'), 'utf-8');
543
+ expect(content).toContain('src/a/kept.ts');
544
+ expect(content).not.toContain('ghost.ts');
545
+
546
+ fs.rmSync(testDir, { recursive: true, force: true });
547
+ });
548
+ });
549
+
550
+ describe('--compact 迁移', () => {
551
+ it('同目录 ≥2 个文件条目折叠为目录条目,且幂等', async () => {
552
+ const testDir = path.join(tempDir, 'compact-basic');
553
+ fs.mkdirSync(testDir, { recursive: true });
554
+
555
+ fs.writeFileSync(
556
+ path.join(testDir, 'CAPABILITIES.md'),
557
+ [
558
+ '# CAPABILITIES.md', '',
559
+ '> 最后更新: 2026-01-01', '',
560
+ '<!-- PRESERVE:说明 -->',
561
+ '手工说明',
562
+ '<!-- /PRESERVE:说明 -->', '',
563
+ '| 模块 | 文件 | 说明 |', '|------|------|------|',
564
+ '| foo | src/core/foo.ts | Foo 模块 |',
565
+ '| bar | src/core/bar.ts | Bar 模块 |',
566
+ '| solo | src/utils/solo.ts | 单独文件 |',
567
+ '',
568
+ ].join('\n')
569
+ );
570
+
571
+ const result = await syncDocs({ projectPath: testDir, compact: true });
572
+ expect(result).toBe(true);
573
+
574
+ const content = fs.readFileSync(path.join(testDir, 'CAPABILITIES.md'), 'utf-8');
575
+ // 折叠为目录条目(说明取组内第一行)
576
+ expect(content).toContain('| core | src/core/ | Foo 模块 |');
577
+ expect(content).not.toContain('src/core/bar.ts');
578
+ // 单独成组的文件行保留
579
+ expect(content).toContain('src/utils/solo.ts');
580
+ // 非表格内容原样保留
581
+ expect(content).toContain('<!-- PRESERVE:说明 -->');
582
+ expect(content).toContain('手工说明');
583
+
584
+ // 幂等:再跑一遍内容不变
585
+ await syncDocs({ projectPath: testDir, compact: true });
586
+ const content2 = fs.readFileSync(path.join(testDir, 'CAPABILITIES.md'), 'utf-8');
587
+ expect(content2).toBe(content);
588
+
589
+ fs.rmSync(testDir, { recursive: true, force: true });
590
+ });
591
+
592
+ it('CAPABILITIES.md 不存在时返回 false', async () => {
593
+ const testDir = path.join(tempDir, 'compact-missing');
594
+ fs.mkdirSync(testDir, { recursive: true });
595
+
596
+ const result = await syncDocs({ projectPath: testDir, compact: true });
597
+ expect(result).toBe(false);
598
+
599
+ fs.rmSync(testDir, { recursive: true, force: true });
600
+ });
601
+ });
439
602
  });
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * harness check 命令
3
3
  *
4
- * 检查约束是否满足(三层:Iron Laws / Guidelines / Tips)
5
- * 工单 23:触发条件与证据检测迁至 core/constraints/context-builder;--preset 接入 applyPreset
4
+ * 检查约束是否满足(check 层:Iron Laws / Guidelines;prompt 层仅注入不检查)
5
+ * 工单 23:触发条件与证据检测迁至 core/constraints/context-builder;--preset 经 mergeConstraints 生效
6
+ * ADR-0001:约束集统一走 mergeConstraints 生效集链路(preset/config 禁用/custom/scenes)
6
7
  */
7
8
 
8
9
  import chalk from 'chalk';
@@ -10,10 +11,11 @@ import * as fs from 'fs';
10
11
  import * as path from 'path';
11
12
  import { constraintChecker } from '../../core/constraints/checker';
12
13
  import { getTraceCollector } from '../../monitoring/traces';
13
- import { IRON_LAWS, GUIDELINES, TIPS } from '../../core/constraints/definitions';
14
+ import { IRON_LAWS, GUIDELINES, PROMPTS } from '../../core/constraints/definitions';
14
15
  import { ProjectConfigLoader } from '../../core/project-config-loader';
15
16
  import { buildConstraintContext } from '../../core/constraints/context-builder';
16
- import { applyPreset } from '../../presets';
17
+ import { detectInjectionDrift } from '../../core/constraints/injection-drift';
18
+ import { DEFAULT_TRACE_FILE } from '../../types/trace';
17
19
  import type { ConstraintTrigger } from '../../types/constraint';
18
20
 
19
21
  export interface CheckOptions {
@@ -41,13 +43,22 @@ export async function check(options: CheckOptions): Promise<void> {
41
43
  configLoader.load();
42
44
  constraintChecker.setTraceRecorder(getTraceCollector());
43
45
 
44
- if (configLoader.hasCustomConfig()) {
45
- const merged = configLoader.mergeConstraints();
46
- constraintChecker.setCustomConfig(merged);
46
+ // 生效约束集(ADR-0001):内置 → preset → config.yml 禁用 → custom 追加 → scenes 过滤。
47
+ // --preset 仅在没有项目自定义配置时覆盖 config.yml 的 preset(保持工单 23 语义:
48
+ // 项目自定义配置优先于 CLI 预设)。
49
+ const merged = configLoader.hasCustomConfig()
50
+ ? configLoader.mergeConstraints()
51
+ : configLoader.mergeConstraints({ preset: options.preset || 'standard' });
52
+ constraintChecker.setCustomConfig(merged);
53
+ if (merged.custom.length > 0) {
47
54
  console.log(chalk.gray(`自定义约束: ${merged.custom.length} 条`));
48
- if (merged.disabled.length > 0) {
49
- console.log(chalk.gray(`已禁用约束: ${merged.disabled.join(', ')}`));
50
- }
55
+ }
56
+ if (merged.disabled.length > 0) {
57
+ console.log(chalk.gray(`已禁用约束: ${merged.disabled.join(', ')}`));
58
+ }
59
+ const unknownIds = merged.unknownIds ?? [];
60
+ if (unknownIds.length > 0) {
61
+ console.log(chalk.yellow(`⚠️ 配置中存在未知约束 id(已忽略,可清理): ${unknownIds.join(', ')}`));
51
62
  }
52
63
 
53
64
  // 构建上下文(工单 23:触发条件与证据检测收敛至 core/constraints/context-builder)
@@ -60,28 +71,26 @@ export async function check(options: CheckOptions): Promise<void> {
60
71
  if (changedFiles.length > 0) {
61
72
  console.log(chalk.gray(`变更文件: ${changedFiles.length} 个`));
62
73
  }
63
- console.log(chalk.gray(`触发条件: ${context.operation}`));
64
-
65
- // 预设(工单 23:--preset 真正生效;项目自定义配置优先于预设)
66
- let presetConfig;
67
- if (!configLoader.hasCustomConfig()) {
68
- try {
69
- presetConfig = applyPreset(options.preset || 'standard');
70
- } catch {
71
- presetConfig = undefined; // 未知预设名回退内置全集
72
- }
73
- }
74
+ console.log(chalk.gray(`触发条件: ${[context.operation, ...(context.extraTriggers ?? [])].join(', ')}`));
74
75
 
75
76
  // 执行三层检查
76
- const result = await constraintChecker.checkConstraints(context, presetConfig);
77
+ const result = await constraintChecker.checkConstraints(context);
77
78
 
78
79
  // 输出结果
79
80
  console.log();
80
81
 
82
+ // skipped(约定未采用/证据未接线)单独列示,不进 pass/fail 统计(ADR-0001)
83
+ const skippedResults = [
84
+ ...result.ironLaws,
85
+ ...result.guidelines,
86
+ ...result.tips,
87
+ ].filter(r => r.skipped);
88
+
81
89
  // Iron Laws
82
- const ironLawViolations = result.ironLaws.filter(r => !r.satisfied);
83
- if (ironLawViolations.length === 0 && result.ironLaws.length > 0) {
84
- console.log(chalk.green(`✅ 铁律: 全部通过 (${result.ironLaws.length} 条)`));
90
+ const evaluatedIronLaws = result.ironLaws.filter(r => !r.skipped);
91
+ const ironLawViolations = evaluatedIronLaws.filter(r => !r.satisfied);
92
+ if (ironLawViolations.length === 0 && evaluatedIronLaws.length > 0) {
93
+ console.log(chalk.green(`✅ 铁律: 全部通过 (${evaluatedIronLaws.length} 条)`));
85
94
  } else if (ironLawViolations.length > 0) {
86
95
  console.log(chalk.red(`❌ 铁律违规: ${ironLawViolations.length} 条`));
87
96
  ironLawViolations.forEach(r => {
@@ -104,8 +113,9 @@ export async function check(options: CheckOptions): Promise<void> {
104
113
  }
105
114
  });
106
115
  } else if (result.guidelines.length > 0) {
107
- const passedGuidelines = result.guidelines.filter(r => r.satisfied).length;
108
- console.log(chalk.green(`✅ 指导原则: ${passedGuidelines}/${result.guidelines.length} 通过`));
116
+ const evaluatedGuidelines = result.guidelines.filter(r => !r.skipped);
117
+ const passedGuidelines = evaluatedGuidelines.filter(r => r.satisfied).length;
118
+ console.log(chalk.green(`✅ 指导原则: ${passedGuidelines}/${evaluatedGuidelines.length} 通过`));
109
119
  }
110
120
 
111
121
  // Tips
@@ -118,6 +128,36 @@ export async function check(options: CheckOptions): Promise<void> {
118
128
  });
119
129
  }
120
130
 
131
+ // Skipped:约定未采用 / 证据未接线,未评估(不计通过/失败)
132
+ if (skippedResults.length > 0) {
133
+ console.log(chalk.gray(`⏭️ 跳过评估: ${skippedResults.length} 条(约定未采用或证据未接线,不计通过/失败)`));
134
+ skippedResults.forEach(r => {
135
+ console.log(chalk.gray(` - ${r.id}`));
136
+ });
137
+ }
138
+
139
+ // 注入漂移校验(ADR-0001 决策 7):黄色警告块,不改 exit code、不影响门禁结果。
140
+ // 无漂移/未注入零输出;漂移检测自身异常静默吞掉,绝不影响 check。
141
+ try {
142
+ const drift = detectInjectionDrift(projectPath);
143
+ if (drift.hasDrift) {
144
+ console.log();
145
+ console.log(chalk.yellow('⚠️ 检测到 CLAUDE.md 约束注入漂移(仅警告,不阻断):'));
146
+ if (drift.versionDrift) {
147
+ console.log(chalk.yellow(` ⚠️⚠️ 注入段版本 (${drift.versionDrift.actual}) ≠ 已安装 harness 版本 (${drift.versionDrift.expected}):agent 上下文中的规则与已安装 harness 版本不一致`));
148
+ }
149
+ if (drift.contentDrift) {
150
+ console.log(chalk.yellow(` 内容漂移: 缺失 ${drift.contentDrift.missing.length} 条 / 多余 ${drift.contentDrift.extra.length} 条(条目级差异见 harness constraints report)`));
151
+ }
152
+ if (drift.duplicateHeading) {
153
+ console.log(chalk.yellow(' 检测到重复的 "## Governance Rules" 章节'));
154
+ }
155
+ console.log(chalk.yellow(` 修复: ${drift.fixHint}`));
156
+ }
157
+ } catch {
158
+ // 漂移检测失败不影响 check 结果
159
+ }
160
+
121
161
  console.log();
122
162
  console.log(chalk.green('✅ 约束检查通过'));
123
163
 
@@ -140,7 +180,7 @@ export async function check(options: CheckOptions): Promise<void> {
140
180
  * 智能提示:检查是否需要提示用户下一步操作
141
181
  */
142
182
  async function getSmartHint(projectPath: string): Promise<string | null> {
143
- const tracesPath = path.join(projectPath, '.harness', 'traces', 'execution.log');
183
+ const tracesPath = path.join(projectPath, DEFAULT_TRACE_FILE);
144
184
  const statePath = path.join(projectPath, '.harness', '.state.json');
145
185
 
146
186
  // 检查 trace 文件是否存在
@@ -172,13 +212,7 @@ async function getSmartHint(projectPath: string): Promise<string | null> {
172
212
  state.shownHints.push('trace_50');
173
213
  }
174
214
 
175
- // 条件 2: 记录数达到 100 且从未运行过诊断
176
- if (traceCount >= 100 && !state.lastDiagnoseRun && !state.shownHints.includes('flow_suggest')) {
177
- hints.push('💡 数据充足,建议运行 harness flow 查看诊断');
178
- state.shownHints.push('flow_suggest');
179
- }
180
-
181
- // 条件 3: 检查异常趋势(简单检查绕过率)
215
+ // 条件 2: 检查异常趋势(简单检查绕过率)
182
216
  const bypassCount = lines.filter(line => {
183
217
  try {
184
218
  const trace = JSON.parse(line);
@@ -231,9 +265,9 @@ export function listLaws(): void {
231
265
  console.log();
232
266
  });
233
267
 
234
- // Tips
235
- console.log(chalk.blue('🔵 提示 (Tips) - 信息性,可忽略:\n'));
236
- Object.values(TIPS).forEach(constraint => {
268
+ // Prompts(ADR-0001:纯注入层,不执行检查)
269
+ console.log(chalk.blue('🔵 提示 (Prompts) - 纯文本注入,不参与检查:\n'));
270
+ Object.values(PROMPTS).forEach(constraint => {
237
271
  console.log(chalk.blue(` ${constraint.id}`));
238
272
  console.log(chalk.gray(` ${constraint.rule}`));
239
273
  console.log(chalk.gray(` ${constraint.message}`));
@@ -0,0 +1,234 @@
1
+ /**
2
+ * harness constraints report —— 约束使用报告(ADR-0001 决策 3/4)
3
+ *
4
+ * 只读。面向"人审约束"场景:
5
+ * - check 层统计表(total/pass/fail/skip、fail 率、首次/最近触发)
6
+ * - 退役候选诊断(零触发/零拦截/不可评估/高噪),retire 交互模式的数据源
7
+ * - 当前生效 prompt 注入清单
8
+ * - 配置健康(unknownIds 残留提示)
9
+ * - --export [file]:脱敏 markdown 摘要(不含项目路径)
10
+ */
11
+
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import chalk from 'chalk';
15
+ import {
16
+ buildConstraintsUsageReport,
17
+ DEFAULT_DIAGNOSE_THRESHOLDS,
18
+ type ConstraintsUsageReport,
19
+ type ConstraintUsageStats,
20
+ type DiagnoseThresholds,
21
+ type RetireCandidate,
22
+ } from '../../core/constraints/usage-report';
23
+ import { getConstraintsMeta } from './constraints';
24
+ import { detectInjectionDrift, type InjectionDrift } from '../../core/constraints/injection-drift';
25
+
26
+ export interface ConstraintsReportOptions {
27
+ projectPath?: string;
28
+ /** --export [file]:true = 默认路径;string = 指定文件 */
29
+ export?: boolean | string;
30
+ json?: boolean;
31
+ /** 零拦截候选的最小评估样本数 */
32
+ zeroInterceptMin?: number;
33
+ /** 高噪候选 fail 率阈值 */
34
+ noiseFailRate?: number;
35
+ /** 高噪候选最小评估样本数 */
36
+ noiseMinTotal?: number;
37
+ }
38
+
39
+ const CANDIDATE_KIND_LABEL: Record<RetireCandidate['kind'], string> = {
40
+ zero_trigger: '零触发',
41
+ unevaluable: '不可评估',
42
+ high_noise: '高噪',
43
+ zero_intercept: '零拦截',
44
+ };
45
+
46
+ function formatTime(ts: number | undefined): string {
47
+ return ts === undefined ? '-' : new Date(ts).toISOString().slice(0, 10);
48
+ }
49
+
50
+ function formatPct(rate: number): string {
51
+ return `${Math.round(rate * 100)}%`;
52
+ }
53
+
54
+ /**
55
+ * 渲染统计表(纯文本行,供 console 与 export 共用单行内容)
56
+ */
57
+ function renderStatsRow(s: ConstraintUsageStats): string {
58
+ return `${s.id} total=${s.total} pass=${s.pass} fail=${s.fail} skip=${s.skip} fail率=${formatPct(s.failRate)} 首次=${formatTime(s.firstAt)} 最近=${formatTime(s.lastAt)}`;
59
+ }
60
+
61
+ /**
62
+ * 渲染脱敏 markdown 摘要(ADR-0001 决策 4)
63
+ *
64
+ * 脱敏约束:不含 projectPath 等本地敏感信息;
65
+ * 内容为 harness 版本、日期、统计表、候选清单、配置健康。
66
+ */
67
+ export function renderExportMarkdown(
68
+ report: ConstraintsUsageReport,
69
+ version: string,
70
+ now: Date = new Date()
71
+ ): string {
72
+ const lines: string[] = [];
73
+ lines.push('# Harness Constraints Report');
74
+ lines.push('');
75
+ lines.push(`- harness 版本: ${version}`);
76
+ lines.push(`- 日期: ${now.toISOString().slice(0, 10)}`);
77
+ lines.push('');
78
+
79
+ lines.push('## 统计表(check 层)');
80
+ lines.push('');
81
+ lines.push('| id | total | pass | fail | skip | fail 率 | 首次触发 | 最近触发 |');
82
+ lines.push('| --- | ---: | ---: | ---: | ---: | ---: | --- | --- |');
83
+ for (const s of report.stats) {
84
+ lines.push(
85
+ `| ${s.id} | ${s.total} | ${s.pass} | ${s.fail} | ${s.skip} | ${formatPct(s.failRate)} | ${formatTime(s.firstAt)} | ${formatTime(s.lastAt)} |`
86
+ );
87
+ }
88
+ lines.push('');
89
+
90
+ lines.push('## 退役候选');
91
+ lines.push('');
92
+ if (report.candidates.length === 0) {
93
+ lines.push('(无候选)');
94
+ } else {
95
+ for (const c of report.candidates) {
96
+ lines.push(`- [${CANDIDATE_KIND_LABEL[c.kind]}] \`${c.id}\` — ${c.reason}`);
97
+ }
98
+ }
99
+ lines.push('');
100
+
101
+ if (report.lint.unknownIds.length > 0) {
102
+ lines.push('## 配置健康');
103
+ lines.push('');
104
+ for (const id of report.lint.unknownIds) {
105
+ lines.push(`- config.yml 中禁用了不存在/已移除的约束 id: \`${id}\``);
106
+ }
107
+ lines.push('');
108
+ }
109
+
110
+ return lines.join('\n');
111
+ }
112
+
113
+ /**
114
+ * 渲染"注入漂移"小节的文本行(ADR-0001 决策 7:详细条目级差异进 report)
115
+ *
116
+ * 返回纯文本行数组(不含 chalk 着色),调用方逐行 console.log。
117
+ */
118
+ export function renderInjectionDriftLines(drift: InjectionDrift): string[] {
119
+ const lines: string[] = [];
120
+ if (drift.notInjected) {
121
+ lines.push(' CLAUDE.md 无约束注入标记段(未注入,不算漂移);可运行 npx harness init 注入');
122
+ if (drift.duplicateHeading) {
123
+ lines.push(' 另检测到多个 "## Governance Rules" 标题(历史遗留重复章节),建议手工清理后重跑 npx harness init');
124
+ }
125
+ return lines;
126
+ }
127
+ if (!drift.hasDrift) {
128
+ lines.push(' 无漂移');
129
+ return lines;
130
+ }
131
+ if (drift.versionDrift) {
132
+ lines.push(` 版本漂移: 注入段版本 (${drift.versionDrift.actual}) ≠ 当前 harness 版本 (${drift.versionDrift.expected})`);
133
+ }
134
+ if (drift.contentDrift) {
135
+ lines.push(' 内容漂移(条目级差异):');
136
+ for (const l of drift.contentDrift.missing) {
137
+ lines.push(` 缺失: ${l}`);
138
+ }
139
+ for (const l of drift.contentDrift.extra) {
140
+ lines.push(` 多余: ${l}`);
141
+ }
142
+ }
143
+ if (drift.duplicateHeading) {
144
+ lines.push(' 重复章节: 标记段之外还存在另一个 "## Governance Rules" 标题');
145
+ }
146
+ lines.push(` 修复: ${drift.fixHint}`);
147
+ return lines;
148
+ }
149
+
150
+ /**
151
+ * CLI handler: harness constraints report
152
+ */
153
+ export async function constraintsReport(options: ConstraintsReportOptions = {}): Promise<void> {
154
+ const projectRoot = options.projectPath || process.cwd();
155
+ const thresholds: Partial<DiagnoseThresholds> = {};
156
+ if (options.zeroInterceptMin !== undefined) thresholds.zeroInterceptMinEvaluated = options.zeroInterceptMin;
157
+ if (options.noiseFailRate !== undefined) thresholds.highNoiseFailRate = options.noiseFailRate;
158
+ if (options.noiseMinTotal !== undefined) thresholds.highNoiseMinEvaluated = options.noiseMinTotal;
159
+
160
+ const report = buildConstraintsUsageReport(projectRoot, thresholds);
161
+
162
+ if (options.json) {
163
+ console.log(JSON.stringify(report, null, 2));
164
+ return;
165
+ }
166
+
167
+ console.log(chalk.blue('📊 约束使用报告'));
168
+ if (!report.traceFileExists) {
169
+ console.log(chalk.gray(' (trace 文件不存在,全部按零触发统计)'));
170
+ }
171
+ console.log();
172
+
173
+ // 统计表
174
+ console.log(chalk.bold('统计表(check 层):'));
175
+ for (const s of report.stats) {
176
+ console.log(` ${renderStatsRow(s)}`);
177
+ }
178
+ console.log();
179
+
180
+ // 候选诊断
181
+ console.log(chalk.bold(`退役候选(${report.candidates.length} 条,阈值: 零拦截≥${thresholds.zeroInterceptMinEvaluated ?? DEFAULT_DIAGNOSE_THRESHOLDS.zeroInterceptMinEvaluated} 次评估, 高噪 fail率>${Math.round((thresholds.highNoiseFailRate ?? DEFAULT_DIAGNOSE_THRESHOLDS.highNoiseFailRate) * 100)}% 且 ≥${thresholds.highNoiseMinEvaluated ?? DEFAULT_DIAGNOSE_THRESHOLDS.highNoiseMinEvaluated} 次评估):`));
182
+ if (report.candidates.length === 0) {
183
+ console.log(chalk.green(' (无候选)'));
184
+ } else {
185
+ for (const c of report.candidates) {
186
+ console.log(` [${CANDIDATE_KIND_LABEL[c.kind]}] ${c.id} — ${c.reason}`);
187
+ }
188
+ console.log(chalk.gray(' 可运行 `harness constraints retire` 交互式处理候选'));
189
+ }
190
+ console.log();
191
+
192
+ // 注入清单
193
+ console.log(chalk.bold(`当前生效 prompt 注入(${report.activePromptIds.length} 条):`));
194
+ for (const id of report.activePromptIds) {
195
+ console.log(` ${id}`);
196
+ }
197
+ console.log();
198
+
199
+ // 配置健康
200
+ if (report.lint.unknownIds.length > 0) {
201
+ console.log(chalk.yellow('⚠️ 配置健康:'));
202
+ for (const id of report.lint.unknownIds) {
203
+ console.log(chalk.yellow(` config.yml 中禁用了不存在/已移除的约束 id: ${id}(残留配置,建议清理)`));
204
+ }
205
+ console.log();
206
+ }
207
+
208
+ // 注入漂移(ADR-0001 决策 7:条目级差异 + 修复指引;--export 摘要不含本地漂移状态)
209
+ console.log(chalk.bold('注入漂移:'));
210
+ const drift = detectInjectionDrift(projectRoot);
211
+ for (const line of renderInjectionDriftLines(drift)) {
212
+ console.log(drift.hasDrift ? chalk.yellow(line) : line);
213
+ }
214
+ console.log();
215
+
216
+ // --export
217
+ if (options.export !== undefined && options.export !== false) {
218
+ const version = getConstraintsMeta().version;
219
+ const now = new Date();
220
+ const markdown = renderExportMarkdown(report, version, now);
221
+
222
+ let exportPath: string;
223
+ if (typeof options.export === 'string' && options.export.length > 0) {
224
+ exportPath = path.isAbsolute(options.export) ? options.export : path.join(projectRoot, options.export);
225
+ } else {
226
+ const date = now.toISOString().slice(0, 10).replace(/-/g, '');
227
+ exportPath = path.join(projectRoot, '.harness', 'reports', `constraints-${date}.md`);
228
+ }
229
+
230
+ fs.mkdirSync(path.dirname(exportPath), { recursive: true });
231
+ fs.writeFileSync(exportPath, markdown, 'utf-8');
232
+ console.log(chalk.green(`✅ 脱敏报告已导出: ${exportPath}`));
233
+ }
234
+ }