@dommaker/harness 0.16.9 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +33 -32
  3. package/bin/harness.js +33 -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 +5 -1
  34. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  35. package/dist/cli/commands/sync-docs/capabilities-syncer.js +2 -10
  36. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  37. package/dist/cli/commands/validate.d.ts.map +1 -1
  38. package/dist/cli/commands/validate.js +1 -5
  39. package/dist/cli/commands/validate.js.map +1 -1
  40. package/dist/core/constraints/capabilities-parser.d.ts +7 -0
  41. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  42. package/dist/core/constraints/capabilities-parser.js +13 -0
  43. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  44. package/dist/core/constraints/checker.d.ts +13 -5
  45. package/dist/core/constraints/checker.d.ts.map +1 -1
  46. package/dist/core/constraints/checker.js +62 -25
  47. package/dist/core/constraints/checker.js.map +1 -1
  48. package/dist/core/constraints/checkers/capability-sync.d.ts +9 -2
  49. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  50. package/dist/core/constraints/checkers/capability-sync.js +55 -22
  51. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  52. package/dist/core/constraints/checkers/context-doc-sync.d.ts +3 -0
  53. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  54. package/dist/core/constraints/checkers/context-doc-sync.js +8 -2
  55. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  56. package/dist/core/constraints/checkers/docs-freshness.d.ts +1 -0
  57. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  58. package/dist/core/constraints/checkers/docs-freshness.js +39 -0
  59. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  60. package/dist/core/constraints/checkers/index.d.ts +8 -4
  61. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  62. package/dist/core/constraints/checkers/index.js +29 -46
  63. package/dist/core/constraints/checkers/index.js.map +1 -1
  64. package/dist/core/constraints/checkers/iron-flags.d.ts +6 -7
  65. package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
  66. package/dist/core/constraints/checkers/iron-flags.js +10 -17
  67. package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
  68. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts +13 -0
  69. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts.map +1 -0
  70. package/dist/core/constraints/checkers/no-hardcoded-credentials.js +91 -0
  71. package/dist/core/constraints/checkers/no-hardcoded-credentials.js.map +1 -0
  72. package/dist/core/constraints/checkers/types.d.ts +15 -4
  73. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  74. package/dist/core/constraints/checkers/types.js +16 -4
  75. package/dist/core/constraints/checkers/types.js.map +1 -1
  76. package/dist/core/constraints/context-builder.d.ts +6 -3
  77. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  78. package/dist/core/constraints/context-builder.js +29 -20
  79. package/dist/core/constraints/context-builder.js.map +1 -1
  80. package/dist/core/constraints/definitions/guidelines.d.ts +6 -2
  81. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  82. package/dist/core/constraints/definitions/guidelines.js +29 -572
  83. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  84. package/dist/core/constraints/definitions/iron-laws.d.ts +6 -2
  85. package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
  86. package/dist/core/constraints/definitions/iron-laws.js +48 -231
  87. package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
  88. package/dist/core/constraints/definitions/prompts.d.ts +12 -0
  89. package/dist/core/constraints/definitions/prompts.d.ts.map +1 -0
  90. package/dist/core/constraints/definitions/prompts.js +405 -0
  91. package/dist/core/constraints/definitions/prompts.js.map +1 -0
  92. package/dist/core/constraints/definitions.d.ts +15 -9
  93. package/dist/core/constraints/definitions.d.ts.map +1 -1
  94. package/dist/core/constraints/definitions.js +18 -12
  95. package/dist/core/constraints/definitions.js.map +1 -1
  96. package/dist/core/constraints/index.d.ts +3 -2
  97. package/dist/core/constraints/index.d.ts.map +1 -1
  98. package/dist/core/constraints/index.js +8 -2
  99. package/dist/core/constraints/index.js.map +1 -1
  100. package/dist/core/constraints/injection-drift.d.ts +48 -0
  101. package/dist/core/constraints/injection-drift.d.ts.map +1 -0
  102. package/dist/core/constraints/injection-drift.js +140 -0
  103. package/dist/core/constraints/injection-drift.js.map +1 -0
  104. package/dist/core/constraints/injection-renderer.d.ts +23 -0
  105. package/dist/core/constraints/injection-renderer.d.ts.map +1 -0
  106. package/dist/core/constraints/injection-renderer.js +44 -0
  107. package/dist/core/constraints/injection-renderer.js.map +1 -0
  108. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  109. package/dist/core/constraints/interceptor.js +3 -2
  110. package/dist/core/constraints/interceptor.js.map +1 -1
  111. package/dist/core/constraints/usage-report.d.ts +95 -0
  112. package/dist/core/constraints/usage-report.d.ts.map +1 -0
  113. package/dist/core/constraints/usage-report.js +206 -0
  114. package/dist/core/constraints/usage-report.js.map +1 -0
  115. package/dist/core/effective-constraints.d.ts +47 -0
  116. package/dist/core/effective-constraints.d.ts.map +1 -0
  117. package/dist/core/effective-constraints.js +60 -0
  118. package/dist/core/effective-constraints.js.map +1 -0
  119. package/dist/core/index.d.ts +1 -0
  120. package/dist/core/index.d.ts.map +1 -1
  121. package/dist/core/index.js +2 -0
  122. package/dist/core/index.js.map +1 -1
  123. package/dist/core/project-config-loader.d.ts +17 -2
  124. package/dist/core/project-config-loader.d.ts.map +1 -1
  125. package/dist/core/project-config-loader.js +55 -8
  126. package/dist/core/project-config-loader.js.map +1 -1
  127. package/dist/dashboard/data.d.ts +1 -9
  128. package/dist/dashboard/data.d.ts.map +1 -1
  129. package/dist/dashboard/data.js +7 -15
  130. package/dist/dashboard/data.js.map +1 -1
  131. package/dist/dashboard/types.d.ts +13 -1
  132. package/dist/dashboard/types.d.ts.map +1 -1
  133. package/dist/index.d.ts +0 -4
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +1 -13
  136. package/dist/index.js.map +1 -1
  137. package/dist/monitoring/index.d.ts +0 -2
  138. package/dist/monitoring/index.d.ts.map +1 -1
  139. package/dist/monitoring/index.js +0 -2
  140. package/dist/monitoring/index.js.map +1 -1
  141. package/dist/monitoring/knowledge-evolver.d.ts +1 -1
  142. package/dist/monitoring/knowledge-evolver.js +1 -1
  143. package/dist/monitoring/trace-analyzer.d.ts +1 -1
  144. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  145. package/dist/monitoring/trace-analyzer.js +16 -10
  146. package/dist/monitoring/trace-analyzer.js.map +1 -1
  147. package/dist/monitoring/traces.d.ts +2 -1
  148. package/dist/monitoring/traces.d.ts.map +1 -1
  149. package/dist/monitoring/traces.js +5 -3
  150. package/dist/monitoring/traces.js.map +1 -1
  151. package/dist/presets/standard.d.ts +3 -1
  152. package/dist/presets/standard.d.ts.map +1 -1
  153. package/dist/presets/standard.js +18 -8
  154. package/dist/presets/standard.js.map +1 -1
  155. package/dist/types/constraint.d.ts +51 -11
  156. package/dist/types/constraint.d.ts.map +1 -1
  157. package/dist/types/constraint.js +8 -4
  158. package/dist/types/constraint.js.map +1 -1
  159. package/dist/types/index.d.ts +1 -1
  160. package/dist/types/index.d.ts.map +1 -1
  161. package/dist/types/index.js.map +1 -1
  162. package/dist/types/monitoring-types.d.ts +2 -63
  163. package/dist/types/monitoring-types.d.ts.map +1 -1
  164. package/dist/types/monitoring-types.js +2 -2
  165. package/dist/types/project-config.d.ts +26 -1
  166. package/dist/types/project-config.d.ts.map +1 -1
  167. package/dist/types/trace.d.ts +16 -7
  168. package/dist/types/trace.d.ts.map +1 -1
  169. package/dist/types/trace.js +8 -0
  170. package/dist/types/trace.js.map +1 -1
  171. package/package.json +1 -1
  172. package/src/CONTEXT.md +2 -2
  173. package/src/__tests__/checker-extra.test.ts +228 -333
  174. package/src/__tests__/checker.test.ts +87 -724
  175. package/src/__tests__/constraint-doctor.test.ts +5 -5
  176. package/src/__tests__/doctor-extra.test.ts +164 -0
  177. package/src/__tests__/effective-constraints.test.ts +147 -0
  178. package/src/__tests__/extend-exceptions.test.ts +3 -3
  179. package/src/__tests__/iron-laws.test.ts +115 -91
  180. package/src/__tests__/no-hardcoded-credentials.test.ts +90 -0
  181. package/src/__tests__/presets.test.ts +7 -7
  182. package/src/__tests__/project-config-loader-extra.test.ts +41 -4
  183. package/src/__tests__/skip-semantics.test.ts +322 -0
  184. package/src/cli/commands/CONTEXT.md +5 -3
  185. package/src/cli/commands/__tests__/check-drift.test.ts +127 -0
  186. package/src/cli/commands/__tests__/check-skip-output.test.ts +128 -0
  187. package/src/cli/commands/__tests__/check.test.ts +14 -25
  188. package/src/cli/commands/__tests__/constraints-report.test.ts +327 -0
  189. package/src/cli/commands/__tests__/constraints-retire.test.ts +283 -0
  190. package/src/cli/commands/__tests__/init-injection.test.ts +234 -0
  191. package/src/cli/commands/__tests__/status-extra.test.ts +11 -11
  192. package/src/cli/commands/__tests__/status.test.ts +1 -1
  193. package/src/cli/commands/check.ts +72 -38
  194. package/src/cli/commands/constraints-report.ts +234 -0
  195. package/src/cli/commands/constraints-retire.ts +443 -0
  196. package/src/cli/commands/constraints.ts +5 -5
  197. package/src/cli/commands/index.ts +2 -1
  198. package/src/cli/commands/init.ts +94 -66
  199. package/src/cli/commands/report.ts +6 -6
  200. package/src/cli/commands/status.ts +3 -4
  201. package/src/cli/commands/sync-docs/capabilities-syncer.ts +5 -7
  202. package/src/cli/commands/validate.ts +1 -5
  203. package/src/core/CONTEXT.md +7 -4
  204. package/src/core/constraints/__tests__/injection-drift.test.ts +164 -0
  205. package/src/core/constraints/__tests__/interceptor.test.ts +4 -0
  206. package/src/core/constraints/capabilities-parser.ts +13 -0
  207. package/src/core/constraints/checker.ts +73 -28
  208. package/src/core/constraints/checkers/capability-sync.ts +72 -29
  209. package/src/core/constraints/checkers/context-doc-sync.ts +9 -2
  210. package/src/core/constraints/checkers/docs-freshness.ts +43 -0
  211. package/src/core/constraints/checkers/index.ts +38 -71
  212. package/src/core/constraints/checkers/iron-flags.ts +13 -49
  213. package/src/core/constraints/checkers/no-hardcoded-credentials.ts +93 -0
  214. package/src/core/constraints/checkers/types.ts +27 -5
  215. package/src/core/constraints/context-builder.ts +28 -19
  216. package/src/core/constraints/definitions/guidelines.ts +29 -597
  217. package/src/core/constraints/definitions/iron-laws.ts +48 -239
  218. package/src/core/constraints/definitions/prompts.ts +419 -0
  219. package/src/core/constraints/definitions.ts +18 -11
  220. package/src/core/constraints/index.ts +10 -1
  221. package/src/core/constraints/injection-drift.ts +139 -0
  222. package/src/core/constraints/injection-renderer.ts +44 -0
  223. package/src/core/constraints/interceptor.ts +3 -2
  224. package/src/core/constraints/usage-report.ts +254 -0
  225. package/src/core/effective-constraints.ts +85 -0
  226. package/src/core/index.ts +4 -1
  227. package/src/core/project-config-loader.ts +59 -9
  228. package/src/dashboard/__tests__/data.test.ts +5 -12
  229. package/src/dashboard/data.ts +9 -19
  230. package/src/dashboard/types.ts +18 -1
  231. package/src/index.ts +0 -12
  232. package/src/knowledge/CONTEXT.md +2 -2
  233. package/src/monitoring/CONTEXT.md +3 -4
  234. package/src/monitoring/index.ts +0 -2
  235. package/src/monitoring/knowledge-evolver.ts +1 -1
  236. package/src/monitoring/trace-analyzer.ts +15 -10
  237. package/src/monitoring/traces.ts +10 -6
  238. package/src/presets/__tests__/standard.test.ts +11 -9
  239. package/src/presets/standard.ts +24 -15
  240. package/src/types/constraint.ts +59 -14
  241. package/src/types/index.ts +1 -3
  242. package/src/types/monitoring-types.ts +2 -84
  243. package/src/types/project-config.ts +30 -1
  244. package/src/types/trace.ts +18 -7
  245. package/src/verification/CONTEXT.md +1 -1
  246. package/dist/cli/commands/flow.d.ts +0 -22
  247. package/dist/cli/commands/flow.d.ts.map +0 -1
  248. package/dist/cli/commands/flow.js +0 -275
  249. package/dist/cli/commands/flow.js.map +0 -1
  250. package/dist/constraints/lifecycle-runner.d.ts +0 -52
  251. package/dist/constraints/lifecycle-runner.d.ts.map +0 -1
  252. package/dist/constraints/lifecycle-runner.js +0 -151
  253. package/dist/constraints/lifecycle-runner.js.map +0 -1
  254. package/dist/constraints/registry.d.ts +0 -27
  255. package/dist/constraints/registry.d.ts.map +0 -1
  256. package/dist/constraints/registry.js +0 -128
  257. package/dist/constraints/registry.js.map +0 -1
  258. package/dist/constraints/types.d.ts +0 -53
  259. package/dist/constraints/types.d.ts.map +0 -1
  260. package/dist/constraints/types.js +0 -8
  261. package/dist/constraints/types.js.map +0 -1
  262. package/dist/core/constraints/checkers/always-pass.d.ts +0 -17
  263. package/dist/core/constraints/checkers/always-pass.d.ts.map +0 -1
  264. package/dist/core/constraints/checkers/always-pass.js +0 -23
  265. package/dist/core/constraints/checkers/always-pass.js.map +0 -1
  266. package/dist/core/constraints/checkers/guideline-flags.d.ts +0 -9
  267. package/dist/core/constraints/checkers/guideline-flags.d.ts.map +0 -1
  268. package/dist/core/constraints/checkers/guideline-flags.js +0 -13
  269. package/dist/core/constraints/checkers/guideline-flags.js.map +0 -1
  270. package/dist/core/constraints/checkers/no-any-type.d.ts +0 -6
  271. package/dist/core/constraints/checkers/no-any-type.d.ts.map +0 -1
  272. package/dist/core/constraints/checkers/no-any-type.js +0 -42
  273. package/dist/core/constraints/checkers/no-any-type.js.map +0 -1
  274. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts +0 -6
  275. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts.map +0 -1
  276. package/dist/core/constraints/checkers/no-excuse-patterns.js +0 -25
  277. package/dist/core/constraints/checkers/no-excuse-patterns.js.map +0 -1
  278. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts +0 -6
  279. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts.map +0 -1
  280. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js +0 -34
  281. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js.map +0 -1
  282. package/dist/core/constraints/checkers/no-performative-agreement.d.ts +0 -8
  283. package/dist/core/constraints/checkers/no-performative-agreement.d.ts.map +0 -1
  284. package/dist/core/constraints/checkers/no-performative-agreement.js +0 -23
  285. package/dist/core/constraints/checkers/no-performative-agreement.js.map +0 -1
  286. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts +0 -6
  287. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts.map +0 -1
  288. package/dist/core/constraints/checkers/no-simplification-without-approval.js +0 -30
  289. package/dist/core/constraints/checkers/no-simplification-without-approval.js.map +0 -1
  290. package/dist/core/constraints/checkers/test-coverage.d.ts +0 -6
  291. package/dist/core/constraints/checkers/test-coverage.d.ts.map +0 -1
  292. package/dist/core/constraints/checkers/test-coverage.js +0 -33
  293. package/dist/core/constraints/checkers/test-coverage.js.map +0 -1
  294. package/dist/core/constraints/checkers/yagni.d.ts +0 -8
  295. package/dist/core/constraints/checkers/yagni.d.ts.map +0 -1
  296. package/dist/core/constraints/checkers/yagni.js +0 -96
  297. package/dist/core/constraints/checkers/yagni.js.map +0 -1
  298. package/dist/core/constraints/definitions/tips.d.ts +0 -7
  299. package/dist/core/constraints/definitions/tips.d.ts.map +0 -1
  300. package/dist/core/constraints/definitions/tips.js +0 -62
  301. package/dist/core/constraints/definitions/tips.js.map +0 -1
  302. package/dist/evolution/auto-evolve.d.ts +0 -35
  303. package/dist/evolution/auto-evolve.d.ts.map +0 -1
  304. package/dist/evolution/auto-evolve.js +0 -64
  305. package/dist/evolution/auto-evolve.js.map +0 -1
  306. package/dist/evolution/index.d.ts +0 -2
  307. package/dist/evolution/index.d.ts.map +0 -1
  308. package/dist/evolution/index.js +0 -18
  309. package/dist/evolution/index.js.map +0 -1
  310. package/dist/monitoring/constraint-evolver.d.ts +0 -120
  311. package/dist/monitoring/constraint-evolver.d.ts.map +0 -1
  312. package/dist/monitoring/constraint-evolver.js +0 -410
  313. package/dist/monitoring/constraint-evolver.js.map +0 -1
  314. package/src/__tests__/constraint-evolver.test.ts +0 -405
  315. package/src/__tests__/doctor-evolver.test.ts +0 -381
  316. package/src/cli/commands/__tests__/flow.test.ts +0 -343
  317. package/src/cli/commands/flow.ts +0 -277
  318. package/src/constraints/__tests__/lifecycle-runner.test.ts +0 -246
  319. package/src/constraints/__tests__/registry.test.ts +0 -196
  320. package/src/constraints/lifecycle-runner.ts +0 -184
  321. package/src/constraints/registry.ts +0 -134
  322. package/src/constraints/types.ts +0 -59
  323. package/src/core/constraints/checkers/always-pass.ts +0 -22
  324. package/src/core/constraints/checkers/guideline-flags.ts +0 -30
  325. package/src/core/constraints/checkers/no-any-type.ts +0 -42
  326. package/src/core/constraints/checkers/no-excuse-patterns.ts +0 -24
  327. package/src/core/constraints/checkers/no-fuzzy-completion-claim.ts +0 -33
  328. package/src/core/constraints/checkers/no-performative-agreement.ts +0 -22
  329. package/src/core/constraints/checkers/no-simplification-without-approval.ts +0 -29
  330. package/src/core/constraints/checkers/test-coverage.ts +0 -35
  331. package/src/core/constraints/checkers/yagni.ts +0 -65
  332. package/src/core/constraints/definitions/tips.ts +0 -63
  333. package/src/evolution/CONTEXT.md +0 -22
  334. package/src/evolution/__tests__/auto-evolve.test.ts +0 -319
  335. package/src/evolution/auto-evolve.ts +0 -98
  336. package/src/evolution/index.ts +0 -1
  337. package/src/monitoring/constraint-evolver.ts +0 -501
@@ -11,7 +11,12 @@ import * as path from 'path';
11
11
  import * as yaml from 'js-yaml';
12
12
  import { createExampleCheckpoint, createExampleResolutions } from './validate';
13
13
  import { detectSourceRoots } from '../../utils/detect-source-roots';
14
- import { IRON_LAWS, GUIDELINES, TIPS } from '../../core/constraints/definitions';
14
+ import { getEffectiveConstraints } from '../../core/effective-constraints';
15
+ import {
16
+ CONSTRAINTS_START_MARKER,
17
+ CONSTRAINTS_END_MARKER,
18
+ renderConstraintsSection,
19
+ } from '../../core/constraints/injection-renderer';
15
20
 
16
21
  export interface InitOptions {
17
22
  /** 项目路径 */
@@ -507,18 +512,15 @@ custom_constraints:
507
512
  }
508
513
 
509
514
  /**
510
- * CLAUDE.md 中写入/更新 Governance Rules 约束段
511
- *
512
- * - 如果 CLAUDE.md 不存在,创建并写入完整约束段
513
- * - 如果存在 HARNESS_CONSTRAINTS_START/END 标记,替换标记间内容
514
- * - 如果不存在标记,在文件末尾追加约束段
515
- *
516
- /**
517
- * 在 CLAUDE.md 顶部写入 Output Style 段(仅首次创建,后续不覆盖)
515
+ * Output Style 段标记(ADR-0001:init 只写标记区间内,标记外只读)
518
516
  */
519
- const OUTPUT_STYLE_SECTION = [
520
- '## Output Style',
521
- '',
517
+ const OUTPUT_STYLE_START = '<!-- HARNESS_OUTPUT_STYLE_START -->';
518
+ const OUTPUT_STYLE_END = '<!-- HARNESS_OUTPUT_STYLE_END -->';
519
+
520
+ /** 旧版 harness 无标记 Output Style 段的特征串(用于区分 harness 写入 vs 用户自写) */
521
+ const LEGACY_OUTPUT_STYLE_FINGERPRINT = 'Terse like caveman';
522
+
523
+ const OUTPUT_STYLE_BODY = [
522
524
  'Terse like caveman. Technical substance exact. Only fluff die.',
523
525
  'Drop: articles, filler (just/really/basically), pleasantries (sure/certainly/happy to), hedging.',
524
526
  'Fragments OK. Short synonyms. Code blocks unchanged. Error messages quoted exact.',
@@ -527,25 +529,87 @@ const OUTPUT_STYLE_SECTION = [
527
529
  'Read existing files before writing. Don\'t re-read unless changed.',
528
530
  'Skip files over 100KB unless required.',
529
531
  'Don\'t guess APIs, versions, flags, commit SHAs, or package names. Verify before asserting.',
530
- '',
531
532
  ].join('\n');
532
533
 
533
- async function setupClaudeMdOutputStyle(projectPath: string): Promise<void> {
534
+ /** 渲染带标记的 Output Style 段(含 `## Output Style` 标题,以换行结尾) */
535
+ function renderOutputStyleSection(): string {
536
+ return `${OUTPUT_STYLE_START}\n## Output Style\n\n${OUTPUT_STYLE_BODY}\n${OUTPUT_STYLE_END}\n`;
537
+ }
538
+
539
+ /**
540
+ * 在 CLAUDE.md 顶部写入 Output Style 段(标记化、幂等)
541
+ *
542
+ * - 已有 HARNESS_OUTPUT_STYLE 标记:替换标记间内容
543
+ * - 无标记但存在旧版 harness 写入的无标记段(特征串匹配):原位置换为标记版
544
+ * - 无标记且 `## Output Style` 段为用户自写(特征串不匹配):跳过并提示,不重复追加
545
+ * - 完全没有该段:在文件顶部插入标记版
546
+ */
547
+ export async function setupClaudeMdOutputStyle(projectPath: string): Promise<void> {
534
548
  const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
549
+ let content: string;
535
550
  try {
536
- const content = await fs.readFile(claudeMdPath, 'utf-8');
537
- if (content.includes('## Output Style')) return; // 已存在,不覆盖
538
- const newContent = OUTPUT_STYLE_SECTION + '\n' + content;
539
- await fs.writeFile(claudeMdPath, newContent, 'utf-8');
551
+ content = await fs.readFile(claudeMdPath, 'utf-8');
540
552
  } catch {
541
553
  // CLAUDE.md 不存在——setupClaudeMdConstraints 会创建它
554
+ return;
555
+ }
556
+
557
+ const section = renderOutputStyleSection();
558
+ const startIdx = content.indexOf(OUTPUT_STYLE_START);
559
+ const endIdx = content.indexOf(OUTPUT_STYLE_END);
560
+
561
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
562
+ // 替换标记间内容(标记本身也替换,保持最新文本)。
563
+ // 尾部换行规范化:标记后恰好保留一个换行分隔,保证重复运行幂等。
564
+ const after = content.slice(endIdx + OUTPUT_STYLE_END.length).replace(/^\n+/, '');
565
+ const newContent = content.slice(0, startIdx) + section + (after ? '\n' + after : '');
566
+ if (newContent !== content) {
567
+ await fs.writeFile(claudeMdPath, newContent, 'utf-8');
568
+ console.log(chalk.green('✅ 已更新 CLAUDE.md Output Style 段'));
569
+ }
570
+ return;
571
+ }
572
+
573
+ // 检测旧版无标记 `## Output Style` 段
574
+ const legacyMatch = /^## Output Style[ \t]*$/m.exec(content);
575
+ if (legacyMatch) {
576
+ const sectionStart = legacyMatch.index;
577
+ const afterHeading = sectionStart + legacyMatch[0].length;
578
+ const nextHeadingOffset = content.slice(afterHeading).search(/^#{1,6} /m);
579
+ const sectionEnd = nextHeadingOffset === -1 ? content.length : afterHeading + nextHeadingOffset;
580
+ const legacySection = content.slice(sectionStart, sectionEnd);
581
+
582
+ if (!legacySection.includes(LEGACY_OUTPUT_STYLE_FINGERPRINT)) {
583
+ // 用户自写的同名 section:不动,也不追加(避免重复)
584
+ console.log(chalk.yellow('⚠️ CLAUDE.md 已存在自定义 "## Output Style" 段,跳过 harness Output Style 注入'));
585
+ return;
586
+ }
587
+
588
+ // 旧版 harness 写入:原位置换为标记版
589
+ const before = content.slice(0, sectionStart);
590
+ const after = content.slice(sectionEnd);
591
+ const newContent = before + section + (after.length > 0 ? '\n' + after : '');
592
+ await fs.writeFile(claudeMdPath, newContent, 'utf-8');
593
+ console.log(chalk.green('✅ 已将 CLAUDE.md Output Style 段迁移为标记化管理'));
594
+ return;
542
595
  }
596
+
597
+ // 完全没有该段:插入文件顶部
598
+ await fs.writeFile(claudeMdPath, section + '\n' + content, 'utf-8');
599
+ console.log(chalk.green('✅ 已在 CLAUDE.md 顶部写入 Output Style 段'));
543
600
  }
544
601
 
545
602
  /**
546
- * 只写入包含 promptInjection 文本的约束
603
+ * CLAUDE.md 中写入/更新 Governance Rules 约束段
604
+ *
605
+ * - 约束集来自 getEffectiveConstraints(ADR-0001):preset 裁剪、config.yml
606
+ * 禁用、custom 追加、scenes 过滤全部反映在注入文本里
607
+ * - 期望段文本由纯函数 renderConstraintsSection 渲染(P6 漂移校验复用)
608
+ * - 如果 CLAUDE.md 不存在,创建并写入完整约束段
609
+ * - 如果存在 HARNESS_CONSTRAINTS_START/END 标记,替换标记间内容
610
+ * - 如果不存在标记,在文件末尾追加约束段
547
611
  */
548
- async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
612
+ export async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
549
613
  const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
550
614
 
551
615
  // 读取 harness 版本
@@ -558,44 +622,10 @@ async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
558
622
  // fallback to 'unknown'
559
623
  }
560
624
 
561
- // 构建约束段正文(不含 ## Governance Rules 标题,因为替换时标题已在外部)
562
- const bodyLines: string[] = [];
563
- bodyLines.push('<!-- HARNESS_CONSTRAINTS_START -->');
564
- bodyLines.push(`<!-- version: ${version} -->`);
565
-
566
- // Iron Laws
567
- const ironLawItems = Object.entries(IRON_LAWS).filter(([, c]) => c.promptInjection);
568
- if (ironLawItems.length > 0) {
569
- bodyLines.push('### Iron Laws (违反将阻断)');
570
- for (const [id, c] of ironLawItems) {
571
- bodyLines.push(`- **${id}**: ${c.promptInjection}`);
572
- }
573
- }
574
-
575
- // Guidelines
576
- const guidelineItems = Object.entries(GUIDELINES).filter(([, c]) => c.promptInjection);
577
- if (guidelineItems.length > 0) {
578
- bodyLines.push('');
579
- bodyLines.push('### Guidelines (应遵循)');
580
- for (const [id, c] of guidelineItems) {
581
- bodyLines.push(`- **${id}**: ${c.promptInjection}`);
582
- }
583
- }
584
-
585
- // Tips
586
- const tipItems = Object.entries(TIPS).filter(([, c]) => c.promptInjection);
587
- if (tipItems.length > 0) {
588
- bodyLines.push('');
589
- bodyLines.push('### Tips');
590
- for (const [id, c] of tipItems) {
591
- bodyLines.push(`- **${id}**: ${c.promptInjection}`);
592
- }
593
- }
594
-
595
- bodyLines.push('<!-- HARNESS_CONSTRAINTS_END -->');
596
-
597
- const fullSection = '## Governance Rules\n' + bodyLines.join('\n') + '\n';
598
- const bodyOnly = bodyLines.join('\n') + '\n';
625
+ // 生效约束集 渲染期望段(纯函数,与写文件分离)
626
+ const constraints = getEffectiveConstraints(projectPath);
627
+ const bodyOnly = renderConstraintsSection(constraints, version);
628
+ const fullSection = '## Governance Rules\n' + bodyOnly;
599
629
 
600
630
  // 检查 CLAUDE.md 是否存在
601
631
  let existingContent: string;
@@ -607,9 +637,6 @@ async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
607
637
  existingContent = '';
608
638
  }
609
639
 
610
- const startMarker = '<!-- HARNESS_CONSTRAINTS_START -->';
611
- const endMarker = '<!-- HARNESS_CONSTRAINTS_END -->';
612
-
613
640
  if (!fileExists) {
614
641
  // 创建新文件
615
642
  await fs.writeFile(claudeMdPath, fullSection, 'utf-8');
@@ -617,14 +644,15 @@ async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
617
644
  return;
618
645
  }
619
646
 
620
- const startIdx = existingContent.indexOf(startMarker);
621
- const endIdx = existingContent.indexOf(endMarker);
647
+ const startIdx = existingContent.indexOf(CONSTRAINTS_START_MARKER);
648
+ const endIdx = existingContent.indexOf(CONSTRAINTS_END_MARKER);
622
649
 
623
650
  if (startIdx !== -1 && endIdx !== -1) {
624
- // 替换标记间内容(标记本身也替换,保持包括最新版本号)
651
+ // 替换标记间内容(标记本身也替换,保持包括最新版本号)。
652
+ // 尾部换行规范化:标记后恰好保留一个换行分隔,保证重复运行幂等。
625
653
  const before = existingContent.slice(0, startIdx);
626
- const after = existingContent.slice(endIdx + endMarker.length);
627
- const newContent = before + bodyOnly + after;
654
+ const after = existingContent.slice(endIdx + CONSTRAINTS_END_MARKER.length).replace(/^\n+/, '');
655
+ const newContent = before + bodyOnly + (after ? '\n' + after : '');
628
656
  await fs.writeFile(claudeMdPath, newContent, 'utf-8');
629
657
  console.log(chalk.green(`✅ 已更新 CLAUDE.md 治理约束 (v${version})`));
630
658
  } else {
@@ -8,7 +8,7 @@ import chalk from 'chalk';
8
8
  import * as fs from 'fs/promises';
9
9
  import { ConstraintChecker } from '../../core/constraints/checker';
10
10
  import { getTraceCollector } from '../../monitoring/traces';
11
- import { IRON_LAWS, GUIDELINES, TIPS } from '../../core/constraints/definitions';
11
+ import { IRON_LAWS, GUIDELINES, PROMPTS } from '../../core/constraints/definitions';
12
12
  import { executeWithCollect } from '../../failure/constraint-handler';
13
13
  import type { ConstraintContext } from '../../types/constraint';
14
14
 
@@ -28,7 +28,7 @@ interface ReportData {
28
28
  total: number;
29
29
  ironLaws: number;
30
30
  guidelines: number;
31
- tips: number;
31
+ prompts: number;
32
32
  passed: number;
33
33
  failed: number;
34
34
  warnings: number;
@@ -50,7 +50,7 @@ export async function report(options: ReportOptions): Promise<void> {
50
50
  const checker = ConstraintChecker.getInstance();
51
51
  checker.setTraceRecorder(getTraceCollector());
52
52
 
53
- const allConstraints = { ...IRON_LAWS, ...GUIDELINES, ...TIPS };
53
+ const allConstraints = { ...IRON_LAWS, ...GUIDELINES, ...PROMPTS };
54
54
  const totalConstraints = Object.keys(allConstraints).length;
55
55
 
56
56
  const context: ConstraintContext = {
@@ -81,7 +81,7 @@ export async function report(options: ReportOptions): Promise<void> {
81
81
  total: totalConstraints,
82
82
  ironLaws: Object.keys(IRON_LAWS).length,
83
83
  guidelines: Object.keys(GUIDELINES).length,
84
- tips: Object.keys(TIPS).length,
84
+ prompts: Object.keys(PROMPTS).length,
85
85
  passed: result.passed ? totalConstraints : totalConstraints - violations.length,
86
86
  failed: failedIronLaws.length,
87
87
  warnings: failedGuidelines.length,
@@ -129,7 +129,7 @@ function generateMarkdownReport(data: ReportData): string {
129
129
  `| 总约束 | ${data.constraints.total} |`,
130
130
  `| Iron Laws | ${data.constraints.ironLaws} |`,
131
131
  `| Guidelines | ${data.constraints.guidelines} |`,
132
- `| Tips | ${data.constraints.tips} |`,
132
+ `| Prompts | ${data.constraints.prompts} |`,
133
133
  `| 通过 | ${data.constraints.passed} |`,
134
134
  `| 失败 (error) | ${data.constraints.failed} |`,
135
135
  `| 警告 (warning) | ${data.constraints.warnings} |`,
@@ -184,7 +184,7 @@ function generateHtmlReport(data: ReportData): string {
184
184
  <tr><td>总约束</td><td>${data.constraints.total}</td></tr>
185
185
  <tr><td>Iron Laws</td><td>${data.constraints.ironLaws}</td></tr>
186
186
  <tr><td>Guidelines</td><td>${data.constraints.guidelines}</td></tr>
187
- <tr><td>Tips</td><td>${data.constraints.tips}</td></tr>
187
+ <tr><td>Prompts</td><td>${data.constraints.prompts}</td></tr>
188
188
  <tr><td>通过</td><td class="passed">${data.constraints.passed}</td></tr>
189
189
  <tr><td>失败</td><td class="failed">${data.constraints.failed}</td></tr>
190
190
  <tr><td>警告</td><td>${data.constraints.warnings}</td></tr>
@@ -9,6 +9,7 @@ import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import { TraceCollector } from '../../monitoring/traces';
11
11
  import { TraceAnalyzer } from '../../monitoring/trace-analyzer';
12
+ import { DEFAULT_TRACE_FILE } from '../../types/trace';
12
13
  import type { TraceSummary, TraceAnomaly } from '../../types/trace';
13
14
 
14
15
  export interface StatusOptions {
@@ -28,7 +29,7 @@ export interface StatusOptions {
28
29
  export async function status(options: StatusOptions): Promise<void> {
29
30
  const projectPath = options.projectPath || process.cwd();
30
31
  const harnessDir = path.join(projectPath, '.harness');
31
- const tracesPath = path.join(harnessDir, 'traces', 'execution.log');
32
+ const tracesPath = path.join(projectPath, DEFAULT_TRACE_FILE);
32
33
  const statePath = path.join(harnessDir, '.state.json');
33
34
 
34
35
  console.log(chalk.blue('📊 Harness 状态'));
@@ -91,8 +92,7 @@ export async function status(options: StatusOptions): Promise<void> {
91
92
 
92
93
  // 下一步建议
93
94
  console.log(chalk.blue('💡 下一步建议:'));
94
- console.log(chalk.gray(' 1. 运行 harness flow 一键诊断 + 提案'));
95
- console.log(chalk.gray(' 2. 运行 harness status --detail 查看详情'));
95
+ console.log(chalk.gray(' 运行 harness status --detail 查看详情'));
96
96
  }
97
97
  return;
98
98
  }
@@ -159,7 +159,6 @@ export async function status(options: StatusOptions): Promise<void> {
159
159
  // 下一步建议
160
160
  console.log(chalk.blue('💡 下一步建议:'));
161
161
  if (anomalies.length > 0) {
162
- console.log(chalk.gray(' harness flow # 一键执行诊断+提案'));
163
162
  console.log(chalk.gray(' harness status --detail # 查看详细状态'));
164
163
  } else if (traceCount >= 100) {
165
164
  console.log(chalk.gray(' • 状态良好,继续保持!'));
@@ -9,7 +9,7 @@
9
9
  import * as fs from 'fs/promises';
10
10
  import * as path from 'path';
11
11
  import { IRON_LAWS, GUIDELINES, TIPS } from '../../../core/constraints/definitions';
12
- import { readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
12
+ import { isCapabilityListingFormat, readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
13
13
  import { FreshnessRunner } from '../../../core/constraints/doc-freshness/runner';
14
14
  import type { CapabilitiesMode } from '../../../core/project-config-loader';
15
15
  import type { DocFreshnessCheck } from '../../../types/project-config';
@@ -35,13 +35,11 @@ export async function parseCapabilitiesFiles(capabilitiesPath: string): Promise<
35
35
 
36
36
  /**
37
37
  * 检测 CAPABILITIES.md 是否使用能力清单格式(计数行),而非文件表格格式
38
+ *
39
+ * @deprecated 实现已收敛到 core/constraints/capabilities-parser(与工单 19-B 同方向),
40
+ * 此处保留 re-export 兼容现有调用方
38
41
  */
39
- export function isCapabilityListingFormat(content: string): boolean {
40
- // 能力清单格式特征:包含 "CLI Commands (N)" / "Iron Laws (N)" 等计数行
41
- return /CLI Commands\s*\(\d+\)/.test(content) ||
42
- /Iron Laws?\s*\(\d+\)/.test(content) ||
43
- /Guidelines?\s*\(\d+\)/.test(content);
44
- }
42
+ export { isCapabilityListingFormat };
45
43
 
46
44
  /**
47
45
  * 构建 CAPABILITIES.md 能力清单格式的检查配置
@@ -163,7 +163,7 @@ export async function createExampleCheckpoint(projectPath: string): Promise<void
163
163
  * 默认 Resolutions(RKB — 约束 → 已知解法映射)
164
164
  */
165
165
  const DEFAULT_RESOLUTIONS = {
166
- no_claim_without_evidence: {
166
+ no_fuzzy_completion_claim: {
167
167
  title: 'commit message 缺少验证证据',
168
168
  fix: '在 commit message body 中附上验证输出:\n`npx harness check --staged` | `npx harness validate` | `npm test -- --coverage`\n确认全部通过后重新 commit。',
169
169
  },
@@ -175,10 +175,6 @@ const DEFAULT_RESOLUTIONS = {
175
175
  title: '关键目录缺少 CONTEXT.md',
176
176
  fix: '在 required_dirs 目录下创建 CONTEXT.md。运行 `npx harness sync-docs` 可自动生成模板。',
177
177
  },
178
- no_coverage_decrease: {
179
- title: '测试覆盖率下降',
180
- fix: '运行 `npm test -- --coverage` 查看当前覆盖率。新增文件必须有对应的测试文件。',
181
- },
182
178
  };
183
179
 
184
180
  /**
@@ -1,10 +1,11 @@
1
1
  # core/
2
2
 
3
3
  ## 职责
4
- 约束引擎核心:三层约束系统(Iron Laws / Guidelines / Tips)、检查点验证器(CSO/passes-gate)、会话管理、Spec 验证器、项目配置加载。
4
+ 约束引擎核心:check/prompt 二元约束系统(ADR-0001)、生效集合并(effective-constraints)、检查点验证器(CSO/passes-gate)、会话管理、Spec 验证器、项目配置加载。
5
5
 
6
6
  ## 核心导出
7
- - `constraints/` — 约束定义(IRON_LAWS/GUIDELINES/TIPS) + 检查引擎(ConstraintChecker) + 拦截器(ConstraintInterceptor) + 缓存(CheckCache)
7
+ - `constraints/` — 约束定义(IRON_LAWS/GUIDELINES/PROMPTS;TIPS 已退役为空表) + 检查引擎(ConstraintChecker) + 拦截器(ConstraintInterceptor) + 缓存(CheckCache) + 注入渲染(injection-renderer)/漂移校验(injection-drift)/使用统计(usage-report)
8
+ - `effective-constraints.ts` — `getEffectiveConstraints(projectRoot)`:全仓唯一生效集来源(内置 → preset → config.yml 禁用 → custom 追加 → scenes 过滤);`lintEffectiveConfig` 配置诊断
8
9
  - `validators/` — checkpoints、passes-gate、CSO 验证器
9
10
  - `session/` — 会话管理
10
11
  - `spec/validator` — SpecValidator
@@ -17,10 +18,12 @@
17
18
  - 依赖 `src/monitoring/traces` TraceCollector
18
19
 
19
20
  ## 约定
20
- - 约束定义在 `constraints/definitions.ts`,不在运行时代码中定义
21
+ - 约束定义在 `constraints/definitions/{iron-laws,guidelines,prompts}.ts`,不在运行时代码中定义
22
+ - check 层必须带真实 checker(注册表闭环,引用未注册 checker 构建报错)
21
23
  - Iron Law 违规必须 throw ConstraintViolationError
22
24
  - 检查器使用单例模式(ConstraintChecker.getInstance())
23
25
 
24
26
  ## 注意事项
25
27
  - 零 Token 成本:所有分析纯文件操作,无 LLM 调用
26
- - 约束配置支持 .harness/config.yml 自定义合并
28
+ - 约束配置支持 .harness/config.yml 自定义合并(preset 真实生效)
29
+ - 存在性探测约束(capability_sync/docs_freshness/context_doc_sync)在约定文件缺失时 skip,不计 pass/fail
@@ -0,0 +1,164 @@
1
+ /**
2
+ * 注入漂移校验测试(ADR-0001 决策 7,P6)
3
+ *
4
+ * - 三类漂移各自检出:版本 / 内容(条目级 missing/extra)/ 重复章节
5
+ * - 无漂移静默;无标记段 = 未注入不算漂移;CLAUDE.md 不存在不算漂移
6
+ * - config.yml 变更后未重跑 init → 内容漂移(extra)
7
+ *
8
+ * 使用真实临时目录(getEffectiveConstraints 读真实 fs)。
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as os from 'os';
13
+ import * as path from 'path';
14
+ import { detectInjectionDrift, INJECTION_DRIFT_FIX_HINT } from '../injection-drift';
15
+ import {
16
+ renderConstraintsSection,
17
+ CONSTRAINTS_START_MARKER,
18
+ } from '../injection-renderer';
19
+ import { getEffectiveConstraints } from '../../effective-constraints';
20
+
21
+ const TEST_VERSION = '9.9.9-test';
22
+
23
+ function makeTmpProject(): string {
24
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'harness-drift-test-'));
25
+ }
26
+
27
+ /** 写入与当前生效集完全一致的注入段(版本 TEST_VERSION) */
28
+ function writeSyncedClaudeMd(root: string, extra = ''): string {
29
+ const section =
30
+ '## Governance Rules\n' + renderConstraintsSection(getEffectiveConstraints(root), TEST_VERSION);
31
+ const content = `# Test Project\n\n${section}${extra}`;
32
+ fs.writeFileSync(path.join(root, 'CLAUDE.md'), content, 'utf-8');
33
+ return content;
34
+ }
35
+
36
+ describe('detectInjectionDrift', () => {
37
+ it('无漂移:注入段与期望渲染一致 → hasDrift=false', () => {
38
+ const root = makeTmpProject();
39
+ writeSyncedClaudeMd(root);
40
+
41
+ const drift = detectInjectionDrift(root, TEST_VERSION);
42
+
43
+ expect(drift.notInjected).toBe(false);
44
+ expect(drift.hasDrift).toBe(false);
45
+ expect(drift.versionDrift).toBeUndefined();
46
+ expect(drift.contentDrift).toBeUndefined();
47
+ expect(drift.duplicateHeading).toBe(false);
48
+ expect(drift.fixHint).toContain('npx harness init');
49
+ expect(drift.fixHint).toBe(INJECTION_DRIFT_FIX_HINT);
50
+ });
51
+
52
+ it('版本漂移:标记段版本号改旧 → versionDrift', () => {
53
+ const root = makeTmpProject();
54
+ const synced = writeSyncedClaudeMd(root);
55
+ fs.writeFileSync(
56
+ path.join(root, 'CLAUDE.md'),
57
+ synced.replace(`<!-- version: ${TEST_VERSION} -->`, '<!-- version: 0.0.1-old -->'),
58
+ 'utf-8'
59
+ );
60
+
61
+ const drift = detectInjectionDrift(root, TEST_VERSION);
62
+
63
+ expect(drift.hasDrift).toBe(true);
64
+ expect(drift.versionDrift).toEqual({ expected: TEST_VERSION, actual: '0.0.1-old' });
65
+ expect(drift.contentDrift).toBeUndefined();
66
+ });
67
+
68
+ it('内容漂移:手改标记段内一条 → 原条目 missing、改后条目 extra', () => {
69
+ const root = makeTmpProject();
70
+ const synced = writeSyncedClaudeMd(root);
71
+ const originalLine = synced.split('\n').find(l => l.startsWith('- **'))!;
72
+ const editedLine = originalLine.replace(/: .+$/, ': 手工篡改的注入文本');
73
+ fs.writeFileSync(
74
+ path.join(root, 'CLAUDE.md'),
75
+ synced.replace(originalLine, editedLine),
76
+ 'utf-8'
77
+ );
78
+
79
+ const drift = detectInjectionDrift(root, TEST_VERSION);
80
+
81
+ expect(drift.hasDrift).toBe(true);
82
+ expect(drift.versionDrift).toBeUndefined();
83
+ expect(drift.contentDrift).toBeDefined();
84
+ expect(drift.contentDrift!.missing).toEqual([originalLine]);
85
+ expect(drift.contentDrift!.extra).toEqual([editedLine]);
86
+ });
87
+
88
+ it('内容漂移:config.yml 变更后未重跑 init → 被禁条目成为 extra', () => {
89
+ const root = makeTmpProject();
90
+ writeSyncedClaudeMd(root);
91
+ // 注入后禁用一条 prompt,但不重跑 init
92
+ fs.mkdirSync(path.join(root, '.harness'), { recursive: true });
93
+ fs.writeFileSync(
94
+ path.join(root, '.harness', 'config.yml'),
95
+ 'constraints:\n no_fuzzy_completion_claim:\n enabled: false\n',
96
+ 'utf-8'
97
+ );
98
+
99
+ const drift = detectInjectionDrift(root, TEST_VERSION);
100
+
101
+ expect(drift.hasDrift).toBe(true);
102
+ expect(drift.contentDrift).toBeDefined();
103
+ expect(drift.contentDrift!.missing).toEqual([]);
104
+ expect(drift.contentDrift!.extra).toHaveLength(1);
105
+ expect(drift.contentDrift!.extra[0]).toContain('no_fuzzy_completion_claim');
106
+ });
107
+
108
+ it('重复章节:标记段之外还有一个 ## Governance Rules 标题 → duplicateHeading', () => {
109
+ const root = makeTmpProject();
110
+ writeSyncedClaudeMd(root, '\n## Governance Rules\n\n旧版遗留的同名章节\n');
111
+
112
+ const drift = detectInjectionDrift(root, TEST_VERSION);
113
+
114
+ expect(drift.duplicateHeading).toBe(true);
115
+ expect(drift.hasDrift).toBe(true);
116
+ });
117
+
118
+ it('无标记段 = 未注入,不算漂移', () => {
119
+ const root = makeTmpProject();
120
+ fs.writeFileSync(path.join(root, 'CLAUDE.md'), '# Test Project\n\n## Governance Rules\n\n用户自写内容\n', 'utf-8');
121
+
122
+ const drift = detectInjectionDrift(root, TEST_VERSION);
123
+
124
+ expect(drift.notInjected).toBe(true);
125
+ expect(drift.hasDrift).toBe(false);
126
+ });
127
+
128
+ it('CLAUDE.md 不存在 = 未注入,不算漂移', () => {
129
+ const root = makeTmpProject();
130
+
131
+ const drift = detectInjectionDrift(root, TEST_VERSION);
132
+
133
+ expect(drift.notInjected).toBe(true);
134
+ expect(drift.hasDrift).toBe(false);
135
+ });
136
+
137
+ it('默认版本参数读 package.json:与真实版本一致的注入段无版本漂移', () => {
138
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
139
+ const realVersion = require('../../../../package.json').version as string;
140
+ const root = makeTmpProject();
141
+ const section =
142
+ '## Governance Rules\n' + renderConstraintsSection(getEffectiveConstraints(root), realVersion);
143
+ fs.writeFileSync(path.join(root, 'CLAUDE.md'), section, 'utf-8');
144
+
145
+ const drift = detectInjectionDrift(root);
146
+
147
+ expect(drift.versionDrift).toBeUndefined();
148
+ expect(drift.hasDrift).toBe(false);
149
+ });
150
+
151
+ it('标记段含 ' + CONSTRAINTS_START_MARKER + ' 但版本行缺失 → versionDrift actual=(缺失)', () => {
152
+ const root = makeTmpProject();
153
+ const synced = writeSyncedClaudeMd(root);
154
+ fs.writeFileSync(
155
+ path.join(root, 'CLAUDE.md'),
156
+ synced.replace(`<!-- version: ${TEST_VERSION} -->\n`, ''),
157
+ 'utf-8'
158
+ );
159
+
160
+ const drift = detectInjectionDrift(root, TEST_VERSION);
161
+
162
+ expect(drift.versionDrift).toEqual({ expected: TEST_VERSION, actual: '(缺失)' });
163
+ });
164
+ });
@@ -24,6 +24,7 @@ describe('ConstraintInterceptor', () => {
24
24
 
25
25
  const mockIronLaw = {
26
26
  id: 'test_iron_law',
27
+ kind: 'check' as const,
27
28
  rule: 'Test Iron Law',
28
29
  message: 'Test iron law message',
29
30
  level: 'iron_law' as const,
@@ -34,6 +35,7 @@ describe('ConstraintInterceptor', () => {
34
35
 
35
36
  const mockGuideline = {
36
37
  id: 'test_guideline',
38
+ kind: 'check' as const,
37
39
  rule: 'Test Guideline',
38
40
  message: 'Test guideline message',
39
41
  level: 'guideline' as const,
@@ -63,6 +65,7 @@ describe('ConstraintInterceptor', () => {
63
65
  ironLaws: { test_iron_law: mockIronLaw },
64
66
  guidelines: { test_guideline: mockGuideline },
65
67
  tips: {},
68
+ prompts: {},
66
69
  });
67
70
  });
68
71
 
@@ -200,6 +203,7 @@ describe('ConstraintInterceptor', () => {
200
203
  ironLaws: {},
201
204
  guidelines: {},
202
205
  tips: {},
206
+ prompts: {},
203
207
  });
204
208
 
205
209
  const result = await interceptor.intercept('code_implementation', mockContext);
@@ -69,6 +69,19 @@ export function readCapabilitiesEntries(capabilitiesPath: string, options: Capab
69
69
  }
70
70
  }
71
71
 
72
+ /**
73
+ * 检测 CAPABILITIES.md 是否使用能力清单格式(计数行),而非文件表格格式
74
+ *
75
+ * 清单格式没有文件表可核对,计数由 sync-docs 自动维护,
76
+ * checker 与 sync-docs 都应按此跳过条目级比对。
77
+ */
78
+ export function isCapabilityListingFormat(content: string): boolean {
79
+ // 能力清单格式特征:包含 "CLI Commands (N)" / "Iron Laws (N)" 等计数行
80
+ return /CLI Commands\s*\(\d+\)/.test(content) ||
81
+ /Iron Laws?\s*\(\d+\)/.test(content) ||
82
+ /Guidelines?\s*\(\d+\)/.test(content);
83
+ }
84
+
72
85
  /**
73
86
  * 未覆盖文件的聚合目录(module 模式)
74
87
  *