@dommaker/harness 1.1.1 → 1.2.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 (373) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -4
  3. package/bin/harness.js +12 -37
  4. package/dist/cli/commands/check.d.ts +2 -2
  5. package/dist/cli/commands/check.d.ts.map +1 -1
  6. package/dist/cli/commands/check.js +8 -33
  7. package/dist/cli/commands/check.js.map +1 -1
  8. package/dist/cli/commands/constraints-report.js +1 -1
  9. package/dist/cli/commands/constraints-report.js.map +1 -1
  10. package/dist/cli/commands/constraints-retire.d.ts +6 -3
  11. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  12. package/dist/cli/commands/constraints-retire.js +34 -25
  13. package/dist/cli/commands/constraints-retire.js.map +1 -1
  14. package/dist/cli/commands/definitions.d.ts +2 -0
  15. package/dist/cli/commands/definitions.d.ts.map +1 -1
  16. package/dist/cli/commands/definitions.js.map +1 -1
  17. package/dist/cli/commands/init.d.ts +21 -1
  18. package/dist/cli/commands/init.d.ts.map +1 -1
  19. package/dist/cli/commands/init.js +118 -40
  20. package/dist/cli/commands/init.js.map +1 -1
  21. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  22. package/dist/cli/commands/knowledge.js +1 -12
  23. package/dist/cli/commands/knowledge.js.map +1 -1
  24. package/dist/cli/commands/report.d.ts.map +1 -1
  25. package/dist/cli/commands/report.js +0 -2
  26. package/dist/cli/commands/report.js.map +1 -1
  27. package/dist/cli/commands/status.js +2 -2
  28. package/dist/cli/commands/status.js.map +1 -1
  29. package/dist/context/index.d.ts +9 -6
  30. package/dist/context/index.d.ts.map +1 -1
  31. package/dist/context/index.js +18 -20
  32. package/dist/context/index.js.map +1 -1
  33. package/dist/core/constraints/capabilities-parser.d.ts +3 -2
  34. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  35. package/dist/core/constraints/capabilities-parser.js +25 -47
  36. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  37. package/dist/core/constraints/checker.d.ts +29 -32
  38. package/dist/core/constraints/checker.d.ts.map +1 -1
  39. package/dist/core/constraints/checker.js +34 -126
  40. package/dist/core/constraints/checker.js.map +1 -1
  41. package/dist/core/constraints/checkers/governance-presence.d.ts +20 -0
  42. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -0
  43. package/dist/core/constraints/checkers/governance-presence.js +76 -0
  44. package/dist/core/constraints/checkers/governance-presence.js.map +1 -0
  45. package/dist/core/constraints/checkers/index.d.ts +2 -1
  46. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  47. package/dist/core/constraints/checkers/index.js +5 -0
  48. package/dist/core/constraints/checkers/index.js.map +1 -1
  49. package/dist/core/constraints/checkers/types.d.ts +25 -0
  50. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  51. package/dist/core/constraints/checkers/types.js +24 -0
  52. package/dist/core/constraints/checkers/types.js.map +1 -1
  53. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  54. package/dist/core/constraints/definitions/guidelines.js +21 -2
  55. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  56. package/dist/core/constraints/definitions/prompts.d.ts.map +1 -1
  57. package/dist/core/constraints/definitions/prompts.js +0 -5
  58. package/dist/core/constraints/definitions/prompts.js.map +1 -1
  59. package/dist/core/constraints/index.d.ts +1 -2
  60. package/dist/core/constraints/index.d.ts.map +1 -1
  61. package/dist/core/constraints/index.js +1 -7
  62. package/dist/core/constraints/index.js.map +1 -1
  63. package/dist/core/constraints/injection-drift.d.ts +29 -4
  64. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  65. package/dist/core/constraints/injection-drift.js +52 -20
  66. package/dist/core/constraints/injection-drift.js.map +1 -1
  67. package/dist/core/constraints/usage-report.d.ts +1 -2
  68. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  69. package/dist/core/constraints/usage-report.js +2 -5
  70. package/dist/core/constraints/usage-report.js.map +1 -1
  71. package/dist/core/effective-constraints.d.ts +24 -4
  72. package/dist/core/effective-constraints.d.ts.map +1 -1
  73. package/dist/core/effective-constraints.js +28 -7
  74. package/dist/core/effective-constraints.js.map +1 -1
  75. package/dist/core/effective-set.d.ts +30 -0
  76. package/dist/core/effective-set.d.ts.map +1 -0
  77. package/dist/core/effective-set.js +35 -0
  78. package/dist/core/effective-set.js.map +1 -0
  79. package/dist/core/index.d.ts +13 -6
  80. package/dist/core/index.d.ts.map +1 -1
  81. package/dist/core/index.js +44 -21
  82. package/dist/core/index.js.map +1 -1
  83. package/dist/core/project-config-loader.d.ts +3 -6
  84. package/dist/core/project-config-loader.d.ts.map +1 -1
  85. package/dist/core/project-config-loader.js +47 -61
  86. package/dist/core/project-config-loader.js.map +1 -1
  87. package/dist/failure/constraint-handler.d.ts +1 -1
  88. package/dist/failure/constraint-handler.js +1 -1
  89. package/dist/gates/checker-gate.d.ts +1 -4
  90. package/dist/gates/checker-gate.d.ts.map +1 -1
  91. package/dist/gates/checker-gate.js +3 -11
  92. package/dist/gates/checker-gate.js.map +1 -1
  93. package/dist/gates/definitions.d.ts +9 -38
  94. package/dist/gates/definitions.d.ts.map +1 -1
  95. package/dist/gates/definitions.js +24 -13
  96. package/dist/gates/definitions.js.map +1 -1
  97. package/dist/gates/effective-gates.d.ts.map +1 -1
  98. package/dist/gates/effective-gates.js +14 -18
  99. package/dist/gates/effective-gates.js.map +1 -1
  100. package/dist/gates/index.d.ts +1 -1
  101. package/dist/gates/index.d.ts.map +1 -1
  102. package/dist/gates/index.js.map +1 -1
  103. package/dist/hooks/bootstrap.d.ts.map +1 -1
  104. package/dist/hooks/bootstrap.js +0 -3
  105. package/dist/hooks/bootstrap.js.map +1 -1
  106. package/dist/index.d.ts +43 -60
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +180 -143
  109. package/dist/index.js.map +1 -1
  110. package/dist/knowledge/query.d.ts +15 -0
  111. package/dist/knowledge/query.d.ts.map +1 -1
  112. package/dist/knowledge/query.js +23 -0
  113. package/dist/knowledge/query.js.map +1 -1
  114. package/dist/monitoring/analyzer-base.d.ts +0 -4
  115. package/dist/monitoring/analyzer-base.d.ts.map +1 -1
  116. package/dist/monitoring/analyzer-base.js +0 -21
  117. package/dist/monitoring/analyzer-base.js.map +1 -1
  118. package/dist/monitoring/index.d.ts +6 -12
  119. package/dist/monitoring/index.d.ts.map +1 -1
  120. package/dist/monitoring/index.js +13 -29
  121. package/dist/monitoring/index.js.map +1 -1
  122. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  123. package/dist/monitoring/trace-analyzer.js +2 -68
  124. package/dist/monitoring/trace-analyzer.js.map +1 -1
  125. package/dist/monitoring/traces.d.ts +0 -4
  126. package/dist/monitoring/traces.d.ts.map +1 -1
  127. package/dist/monitoring/traces.js +0 -14
  128. package/dist/monitoring/traces.js.map +1 -1
  129. package/dist/presets/index.d.ts +2 -2
  130. package/dist/presets/index.d.ts.map +1 -1
  131. package/dist/presets/index.js +3 -4
  132. package/dist/presets/index.js.map +1 -1
  133. package/dist/presets/standard.d.ts +10 -22
  134. package/dist/presets/standard.d.ts.map +1 -1
  135. package/dist/presets/standard.js +15 -75
  136. package/dist/presets/standard.js.map +1 -1
  137. package/dist/types/constraint.d.ts +1 -29
  138. package/dist/types/constraint.d.ts.map +1 -1
  139. package/dist/types/constraint.js +1 -1
  140. package/dist/types/constraint.js.map +1 -1
  141. package/dist/types/index.d.ts +0 -3
  142. package/dist/types/index.d.ts.map +1 -1
  143. package/dist/types/index.js +0 -4
  144. package/dist/types/index.js.map +1 -1
  145. package/dist/types/project-config.d.ts +4 -8
  146. package/dist/types/project-config.d.ts.map +1 -1
  147. package/dist/types/trace.d.ts +5 -18
  148. package/dist/types/trace.d.ts.map +1 -1
  149. package/package.json +2 -2
  150. package/src/CONTEXT.md +4 -5
  151. package/src/__tests__/checker-extra.test.ts +33 -139
  152. package/src/__tests__/checker.test.ts +0 -376
  153. package/src/__tests__/governance-presence.test.ts +129 -0
  154. package/src/__tests__/iron-laws.test.ts +7 -7
  155. package/src/__tests__/project-config-loader-extra.test.ts +42 -29
  156. package/src/__tests__/public-exports.test.ts +138 -0
  157. package/src/__tests__/trace-analyzer-extra.test.ts +6 -86
  158. package/src/__tests__/trace-analyzer.test.ts +4 -5
  159. package/src/__tests__/trace-collector.test.ts +0 -11
  160. package/src/__tests__/traces.test.ts +3 -22
  161. package/src/cli/commands/CONTEXT.md +6 -3
  162. package/src/cli/commands/__tests__/check-skip-output.test.ts +10 -7
  163. package/src/cli/commands/__tests__/check.test.ts +29 -69
  164. package/src/cli/commands/__tests__/constraints-report.test.ts +3 -3
  165. package/src/cli/commands/__tests__/constraints-retire.test.ts +45 -4
  166. package/src/cli/commands/__tests__/init-injection.test.ts +202 -0
  167. package/src/cli/commands/__tests__/knowledge.test.ts +90 -1
  168. package/src/cli/commands/__tests__/registry.test.ts +8 -6
  169. package/src/cli/commands/__tests__/status-extra.test.ts +6 -6
  170. package/src/cli/commands/__tests__/status.test.ts +6 -6
  171. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +28 -0
  172. package/src/cli/commands/check.ts +9 -35
  173. package/src/cli/commands/constraints-report.ts +1 -1
  174. package/src/cli/commands/constraints-retire.ts +38 -27
  175. package/src/cli/commands/definitions.ts +2 -0
  176. package/src/cli/commands/init.ts +129 -40
  177. package/src/cli/commands/knowledge.ts +1 -16
  178. package/src/cli/commands/report.ts +0 -2
  179. package/src/cli/commands/status.ts +2 -2
  180. package/src/context/index.ts +29 -6
  181. package/src/core/CONTEXT.md +4 -3
  182. package/src/core/__tests__/effective-set.test.ts +80 -0
  183. package/src/core/constraints/__tests__/injection-drift.test.ts +90 -0
  184. package/src/core/constraints/capabilities-parser.ts +43 -49
  185. package/src/core/constraints/checker.ts +51 -145
  186. package/src/core/constraints/checkers/governance-presence.ts +74 -0
  187. package/src/core/constraints/checkers/index.ts +4 -1
  188. package/src/core/constraints/checkers/types.ts +42 -0
  189. package/src/core/constraints/definitions/guidelines.ts +22 -2
  190. package/src/core/constraints/definitions/prompts.ts +0 -5
  191. package/src/core/constraints/index.ts +2 -17
  192. package/src/core/constraints/injection-drift.ts +60 -21
  193. package/src/core/constraints/usage-report.ts +5 -8
  194. package/src/core/effective-constraints.ts +35 -7
  195. package/src/core/effective-set.ts +56 -0
  196. package/src/core/index.ts +80 -6
  197. package/src/core/project-config-loader.ts +53 -68
  198. package/src/failure/CONTEXT.md +1 -1
  199. package/src/failure/constraint-handler.ts +1 -1
  200. package/src/gates/CONTEXT.md +3 -3
  201. package/src/gates/checker-gate.ts +4 -12
  202. package/src/gates/definitions.ts +27 -50
  203. package/src/gates/effective-gates.ts +14 -21
  204. package/src/gates/index.ts +1 -1
  205. package/src/hooks/bootstrap.ts +0 -3
  206. package/src/index.ts +384 -148
  207. package/src/knowledge/CONTEXT.md +2 -2
  208. package/src/knowledge/__tests__/query.test.ts +64 -0
  209. package/src/knowledge/query.ts +28 -0
  210. package/src/monitoring/CONTEXT.md +7 -13
  211. package/src/monitoring/__tests__/traces.test.ts +0 -10
  212. package/src/monitoring/analyzer-base.ts +0 -22
  213. package/src/monitoring/index.ts +6 -12
  214. package/src/monitoring/trace-analyzer.ts +2 -73
  215. package/src/monitoring/traces.ts +0 -20
  216. package/src/presets/__tests__/standard.test.ts +48 -69
  217. package/src/presets/index.ts +3 -5
  218. package/src/presets/standard.ts +14 -79
  219. package/src/spec/CONTEXT.md +5 -13
  220. package/src/types/constraint.ts +1 -51
  221. package/src/types/index.ts +0 -12
  222. package/src/types/project-config.ts +4 -10
  223. package/src/types/trace.ts +5 -30
  224. package/dist/core/constraints/interceptor.d.ts +0 -39
  225. package/dist/core/constraints/interceptor.d.ts.map +0 -1
  226. package/dist/core/constraints/interceptor.js +0 -166
  227. package/dist/core/constraints/interceptor.js.map +0 -1
  228. package/dist/dashboard/data.d.ts +0 -31
  229. package/dist/dashboard/data.d.ts.map +0 -1
  230. package/dist/dashboard/data.js +0 -81
  231. package/dist/dashboard/data.js.map +0 -1
  232. package/dist/dashboard/index.d.ts +0 -4
  233. package/dist/dashboard/index.d.ts.map +0 -1
  234. package/dist/dashboard/index.js +0 -20
  235. package/dist/dashboard/index.js.map +0 -1
  236. package/dist/dashboard/stats.d.ts +0 -20
  237. package/dist/dashboard/stats.d.ts.map +0 -1
  238. package/dist/dashboard/stats.js +0 -115
  239. package/dist/dashboard/stats.js.map +0 -1
  240. package/dist/dashboard/types.d.ts +0 -110
  241. package/dist/dashboard/types.d.ts.map +0 -1
  242. package/dist/dashboard/types.js +0 -8
  243. package/dist/dashboard/types.js.map +0 -1
  244. package/dist/monitoring/constraint-doctor.d.ts +0 -76
  245. package/dist/monitoring/constraint-doctor.d.ts.map +0 -1
  246. package/dist/monitoring/constraint-doctor.js +0 -189
  247. package/dist/monitoring/constraint-doctor.js.map +0 -1
  248. package/dist/monitoring/diagnosis-rules.d.ts +0 -18
  249. package/dist/monitoring/diagnosis-rules.d.ts.map +0 -1
  250. package/dist/monitoring/diagnosis-rules.js +0 -119
  251. package/dist/monitoring/diagnosis-rules.js.map +0 -1
  252. package/dist/monitoring/knowledge-doctor.d.ts +0 -78
  253. package/dist/monitoring/knowledge-doctor.d.ts.map +0 -1
  254. package/dist/monitoring/knowledge-doctor.js +0 -266
  255. package/dist/monitoring/knowledge-doctor.js.map +0 -1
  256. package/dist/monitoring/knowledge-evolver.d.ts +0 -72
  257. package/dist/monitoring/knowledge-evolver.d.ts.map +0 -1
  258. package/dist/monitoring/knowledge-evolver.js +0 -205
  259. package/dist/monitoring/knowledge-evolver.js.map +0 -1
  260. package/dist/monitoring/performance-analyzer.d.ts +0 -92
  261. package/dist/monitoring/performance-analyzer.d.ts.map +0 -1
  262. package/dist/monitoring/performance-analyzer.js +0 -343
  263. package/dist/monitoring/performance-analyzer.js.map +0 -1
  264. package/dist/monitoring/performance-collector.d.ts +0 -103
  265. package/dist/monitoring/performance-collector.d.ts.map +0 -1
  266. package/dist/monitoring/performance-collector.js +0 -298
  267. package/dist/monitoring/performance-collector.js.map +0 -1
  268. package/dist/safety/index.d.ts +0 -6
  269. package/dist/safety/index.d.ts.map +0 -1
  270. package/dist/safety/index.js +0 -22
  271. package/dist/safety/index.js.map +0 -1
  272. package/dist/safety/input-guardrail.d.ts +0 -36
  273. package/dist/safety/input-guardrail.d.ts.map +0 -1
  274. package/dist/safety/input-guardrail.js +0 -118
  275. package/dist/safety/input-guardrail.js.map +0 -1
  276. package/dist/safety/output-guardrail.d.ts +0 -39
  277. package/dist/safety/output-guardrail.d.ts.map +0 -1
  278. package/dist/safety/output-guardrail.js +0 -147
  279. package/dist/safety/output-guardrail.js.map +0 -1
  280. package/dist/safety/sandbox.d.ts +0 -45
  281. package/dist/safety/sandbox.d.ts.map +0 -1
  282. package/dist/safety/sandbox.js +0 -134
  283. package/dist/safety/sandbox.js.map +0 -1
  284. package/dist/safety/tool-guardrail.d.ts +0 -51
  285. package/dist/safety/tool-guardrail.d.ts.map +0 -1
  286. package/dist/safety/tool-guardrail.js +0 -147
  287. package/dist/safety/tool-guardrail.js.map +0 -1
  288. package/dist/safety/types.d.ts +0 -104
  289. package/dist/safety/types.d.ts.map +0 -1
  290. package/dist/safety/types.js +0 -6
  291. package/dist/safety/types.js.map +0 -1
  292. package/dist/spec/annotation-checker.d.ts +0 -47
  293. package/dist/spec/annotation-checker.d.ts.map +0 -1
  294. package/dist/spec/annotation-checker.js +0 -279
  295. package/dist/spec/annotation-checker.js.map +0 -1
  296. package/dist/types/enforcement.d.ts +0 -42
  297. package/dist/types/enforcement.d.ts.map +0 -1
  298. package/dist/types/enforcement.js +0 -6
  299. package/dist/types/enforcement.js.map +0 -1
  300. package/dist/types/monitoring-types.d.ts +0 -53
  301. package/dist/types/monitoring-types.d.ts.map +0 -1
  302. package/dist/types/monitoring-types.js +0 -10
  303. package/dist/types/monitoring-types.js.map +0 -1
  304. package/dist/types/performance.d.ts +0 -204
  305. package/dist/types/performance.d.ts.map +0 -1
  306. package/dist/types/performance.js +0 -12
  307. package/dist/types/performance.js.map +0 -1
  308. package/dist/verification/index.d.ts +0 -4
  309. package/dist/verification/index.d.ts.map +0 -1
  310. package/dist/verification/index.js +0 -20
  311. package/dist/verification/index.js.map +0 -1
  312. package/dist/verification/loop.d.ts +0 -30
  313. package/dist/verification/loop.d.ts.map +0 -1
  314. package/dist/verification/loop.js +0 -114
  315. package/dist/verification/loop.js.map +0 -1
  316. package/dist/verification/rules-based.d.ts +0 -40
  317. package/dist/verification/rules-based.d.ts.map +0 -1
  318. package/dist/verification/rules-based.js +0 -130
  319. package/dist/verification/rules-based.js.map +0 -1
  320. package/dist/verification/types.d.ts +0 -69
  321. package/dist/verification/types.d.ts.map +0 -1
  322. package/dist/verification/types.js +0 -6
  323. package/dist/verification/types.js.map +0 -1
  324. package/src/__tests__/annotation-checker.test.ts +0 -183
  325. package/src/__tests__/constraint-doctor.test.ts +0 -271
  326. package/src/__tests__/doctor-extra.test.ts +0 -155
  327. package/src/__tests__/extend-exceptions.test.ts +0 -149
  328. package/src/__tests__/interceptor.test.ts +0 -89
  329. package/src/__tests__/performance-analyzer.test.ts +0 -499
  330. package/src/__tests__/performance-collector.test.ts +0 -276
  331. package/src/__tests__/presets.test.ts +0 -65
  332. package/src/core/constraints/__tests__/interceptor.test.ts +0 -267
  333. package/src/core/constraints/interceptor.ts +0 -217
  334. package/src/dashboard/CONTEXT.md +0 -21
  335. package/src/dashboard/__tests__/data.test.ts +0 -111
  336. package/src/dashboard/__tests__/stats.test.ts +0 -159
  337. package/src/dashboard/data.ts +0 -103
  338. package/src/dashboard/index.ts +0 -3
  339. package/src/dashboard/stats.ts +0 -127
  340. package/src/dashboard/types.ts +0 -130
  341. package/src/monitoring/__tests__/knowledge-doctor.test.ts +0 -278
  342. package/src/monitoring/__tests__/knowledge-evolver.test.ts +0 -368
  343. package/src/monitoring/__tests__/performance-analyzer.test.ts +0 -137
  344. package/src/monitoring/__tests__/performance-collector.test.ts +0 -331
  345. package/src/monitoring/constraint-doctor.ts +0 -228
  346. package/src/monitoring/diagnosis-rules.ts +0 -132
  347. package/src/monitoring/knowledge-doctor.ts +0 -322
  348. package/src/monitoring/knowledge-evolver.ts +0 -289
  349. package/src/monitoring/performance-analyzer.ts +0 -400
  350. package/src/monitoring/performance-collector.ts +0 -326
  351. package/src/safety/CONTEXT.md +0 -23
  352. package/src/safety/__tests__/input-guardrail.test.ts +0 -105
  353. package/src/safety/__tests__/output-guardrail.test.ts +0 -108
  354. package/src/safety/__tests__/sandbox.test.ts +0 -158
  355. package/src/safety/__tests__/tool-guardrail.test.ts +0 -127
  356. package/src/safety/index.ts +0 -5
  357. package/src/safety/input-guardrail.ts +0 -133
  358. package/src/safety/output-guardrail.ts +0 -170
  359. package/src/safety/sandbox.ts +0 -148
  360. package/src/safety/tool-guardrail.ts +0 -176
  361. package/src/safety/types.ts +0 -124
  362. package/src/spec/__tests__/annotation-checker.test.ts +0 -294
  363. package/src/spec/annotation-checker.ts +0 -320
  364. package/src/types/enforcement.ts +0 -48
  365. package/src/types/monitoring-types.ts +0 -68
  366. package/src/types/performance.ts +0 -295
  367. package/src/verification/CONTEXT.md +0 -23
  368. package/src/verification/__tests__/loop.test.ts +0 -303
  369. package/src/verification/__tests__/rules-based.test.ts +0 -240
  370. package/src/verification/index.ts +0 -3
  371. package/src/verification/loop.ts +0 -137
  372. package/src/verification/rules-based.ts +0 -152
  373. package/src/verification/types.ts +0 -81
@@ -2,7 +2,7 @@
2
2
  * harness constraints retire 测试(ADR-0001 P5)
3
3
  *
4
4
  * 执行逻辑(retireConstraint,纯函数化):config.yml 写入形态、
5
- * KnowledgeStore 记录、CLAUDE.md 注入段同步、重复 retire / 未知 id 保护。
5
+ * KnowledgeStore 记录、治理注入段同步(CLAUDE.md / AGENTS.md 落点路由)、重复 retire / 未知 id 保护。
6
6
  * 交互流程(runRetireInteractive):注入 stdin 流测核心分支
7
7
  * (候选选择 + iron 二次确认拒绝;无候选手动输入 + 确认执行)。
8
8
  *
@@ -145,7 +145,7 @@ describe('retireConstraint 执行逻辑', () => {
145
145
 
146
146
  const result = retireConstraint(root, 'no_bypass_checkpoint', { now: FIXED_NOW });
147
147
  expect(result.status).toBe('retired');
148
- expect(result.claudeMdSynced).toBe(true);
148
+ expect(result.injectionSynced).toBe(true);
149
149
 
150
150
  const after = fs.readFileSync(path.join(root, 'CLAUDE.md'), 'utf-8');
151
151
  expect(after).toContain('HARNESS_CONSTRAINTS_START');
@@ -164,10 +164,51 @@ describe('retireConstraint 执行逻辑', () => {
164
164
  fs.writeFileSync(path.join(root, 'CLAUDE.md'), '# 用户自写\n', 'utf-8');
165
165
  const result = retireConstraint(root, 'capability_sync', { now: FIXED_NOW });
166
166
  expect(result.status).toBe('retired');
167
- expect(result.claudeMdSynced).toBe(false);
167
+ expect(result.injectionSynced).toBe(false);
168
168
  expect(fs.readFileSync(path.join(root, 'CLAUDE.md'), 'utf-8')).toBe('# 用户自写\n');
169
169
  });
170
170
 
171
+ it('新模型仓:AGENTS.md PRESERVE:governance 内含标记段时同步重渲染(无 CLAUDE.md)', () => {
172
+ const root = makeTmpProject();
173
+ const before = renderConstraintsSection(getEffectiveConstraints(root), '0.0.0-test');
174
+ expect(before).toContain('no_bypass_checkpoint');
175
+ fs.writeFileSync(
176
+ path.join(root, 'AGENTS.md'),
177
+ `# AGENTS.md\n\n<!-- PRESERVE:governance -->\n## Governance Rules\n${before}<!-- /PRESERVE:governance -->\n\n其他内容\n`,
178
+ 'utf-8'
179
+ );
180
+
181
+ const result = retireConstraint(root, 'no_bypass_checkpoint', { now: FIXED_NOW });
182
+ expect(result.status).toBe('retired');
183
+ expect(result.injectionSynced).toBe(true);
184
+ expect(result.injectionFile).toBe('AGENTS.md');
185
+
186
+ const after = fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf-8');
187
+ expect(after).toContain('HARNESS_CONSTRAINTS_START');
188
+ expect(after).toContain('HARNESS_CONSTRAINTS_END');
189
+ expect(after).not.toContain('**no_bypass_checkpoint**');
190
+ expect(after).toContain('PRESERVE:governance');
191
+ expect(after).toContain('其他内容');
192
+ // 不制造 CLAUDE.md
193
+ expect(fs.existsSync(path.join(root, 'CLAUDE.md'))).toBe(false);
194
+ });
195
+
196
+ it('两文件均有标记段时旧模型仓豁免:只同步 CLAUDE.md,AGENTS.md 不动', () => {
197
+ const root = makeTmpProject();
198
+ const before = renderConstraintsSection(getEffectiveConstraints(root), '0.0.0-test');
199
+ fs.writeFileSync(path.join(root, 'CLAUDE.md'), `# 项目\n\n## Governance Rules\n${before}`, 'utf-8');
200
+ const agentsMd = `# AGENTS.md\n\n<!-- PRESERVE:governance -->\n## Governance Rules\n${before}<!-- /PRESERVE:governance -->\n`;
201
+ fs.writeFileSync(path.join(root, 'AGENTS.md'), agentsMd, 'utf-8');
202
+
203
+ const result = retireConstraint(root, 'no_bypass_checkpoint', { now: FIXED_NOW });
204
+ expect(result.status).toBe('retired');
205
+ expect(result.injectionSynced).toBe(true);
206
+ expect(result.injectionFile).toBe('CLAUDE.md');
207
+
208
+ expect(fs.readFileSync(path.join(root, 'CLAUDE.md'), 'utf-8')).not.toContain('**no_bypass_checkpoint**');
209
+ expect(fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf-8')).toBe(agentsMd);
210
+ });
211
+
171
212
  it('重复 retire:already_retired,不覆盖原 retired 元数据', () => {
172
213
  const root = makeTmpProject();
173
214
  const first = retireConstraint(root, 'capability_sync', { reason: '第一次', now: FIXED_NOW });
@@ -214,7 +255,7 @@ describe('retireConstraint 执行逻辑', () => {
214
255
 
215
256
  expect(result.status).toBe('retired');
216
257
  expect(result.landing).toBe('custom-constraints.yml');
217
- expect(result.claudeMdSynced).toBe(true);
258
+ expect(result.injectionSynced).toBe(true);
218
259
  // config.yml 不产生该 id 段
219
260
  expect(fs.existsSync(path.join(root, '.harness', 'config.yml'))).toBe(false);
220
261
  // yml 保留规则原文 + retired 元数据
@@ -17,6 +17,8 @@ import {
17
17
  import {
18
18
  setupClaudeMdConstraints,
19
19
  setupClaudeMdOutputStyle,
20
+ setupAgentsMdConstraints,
21
+ setupGovernanceConstraints,
20
22
  } from '../init';
21
23
  import { getEffectiveConstraints } from '../../../core/effective-constraints';
22
24
  import { getAllConstraints } from '../../../core/constraints/definitions';
@@ -232,3 +234,203 @@ describe('setupClaudeMdOutputStyle(标记化)', () => {
232
234
  expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('跳过'));
233
235
  });
234
236
  });
237
+
238
+ const GOVERNANCE_PRESERVE_BEGIN = '<!-- PRESERVE:governance -->';
239
+ const GOVERNANCE_PRESERVE_END = '<!-- /PRESERVE:governance -->';
240
+
241
+ describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.md PRESERVE:governance 段)', () => {
242
+ let tempDir: string;
243
+ let agentsMdPath: string;
244
+
245
+ beforeEach(() => {
246
+ tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-init-agents-'));
247
+ agentsMdPath = path.join(tempDir, 'AGENTS.md');
248
+ jest.spyOn(console, 'log').mockImplementation(() => {});
249
+ });
250
+
251
+ afterEach(() => {
252
+ jest.restoreAllMocks();
253
+ fs.rmSync(tempDir, { recursive: true, force: true });
254
+ });
255
+
256
+ it('AGENTS.md 不存在时创建骨架并写入 PRESERVE:governance 段', async () => {
257
+ await setupAgentsMdConstraints(tempDir);
258
+
259
+ const content = fs.readFileSync(agentsMdPath, 'utf-8');
260
+ expect(content).toContain('# AGENTS.md');
261
+ expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
262
+ expect(content).toContain(GOVERNANCE_PRESERVE_END);
263
+ expect(content).toContain('## Governance Rules');
264
+ expect(content).toContain(CONSTRAINTS_START_MARKER);
265
+ expect(content).toContain(CONSTRAINTS_END_MARKER);
266
+ expect(content).toContain('### Iron Laws');
267
+ // PRESERVE 标记在约束段之外(sync-docs 重新生成时整段保留)
268
+ expect(content.indexOf(GOVERNANCE_PRESERVE_BEGIN)).toBeLessThan(content.indexOf(CONSTRAINTS_START_MARKER));
269
+ expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
270
+ });
271
+
272
+ it('连跑两次输出一致(幂等)', async () => {
273
+ await setupAgentsMdConstraints(tempDir);
274
+ const first = fs.readFileSync(agentsMdPath, 'utf-8');
275
+ await setupAgentsMdConstraints(tempDir);
276
+ expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(first);
277
+ });
278
+
279
+ it('已有 PRESERVE:governance 段(含标记+段内手写内容):只换标记区间,手写内容原样保留', async () => {
280
+ // 回归:曾把 PRESERVE 段内 HARNESS 标记之外的手写内容(治理契约引言/流程等)清空
281
+ fs.writeFileSync(agentsMdPath, [
282
+ '# AGENTS.md',
283
+ '',
284
+ '机器生成导读(保持不变)',
285
+ '',
286
+ GOVERNANCE_PRESERVE_BEGIN,
287
+ '## Governance Rules',
288
+ CONSTRAINTS_START_MARKER,
289
+ '<!-- version: 0.0.0 -->',
290
+ '过期的旧约束内容',
291
+ CONSTRAINTS_END_MARKER,
292
+ '',
293
+ '治理契约引言(手写,保留)',
294
+ '',
295
+ '### 治理变更流程(手写,保留)',
296
+ GOVERNANCE_PRESERVE_END,
297
+ '',
298
+ '<!-- PRESERVE:other -->',
299
+ '另一个手写段(保持不动)',
300
+ '<!-- /PRESERVE:other -->',
301
+ '',
302
+ ].join('\n'));
303
+
304
+ await setupAgentsMdConstraints(tempDir);
305
+
306
+ const content = fs.readFileSync(agentsMdPath, 'utf-8');
307
+ expect(content).toContain('机器生成导读(保持不变)');
308
+ expect(content).toContain('另一个手写段(保持不动)');
309
+ expect(content).not.toContain('过期的旧约束内容');
310
+ expect(content).toContain(CONSTRAINTS_START_MARKER);
311
+ // 段内标记之外的手写内容原样保留
312
+ expect(content).toContain('治理契约引言(手写,保留)');
313
+ expect(content).toContain('### 治理变更流程(手写,保留)');
314
+ expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf('治理契约引言(手写,保留)'));
315
+ expect(content.indexOf('### 治理变更流程(手写,保留)')).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
316
+ // 幂等
317
+ await setupAgentsMdConstraints(tempDir);
318
+ expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(content);
319
+ });
320
+
321
+ it('已有 PRESERVE:governance 段但无 HARNESS 标记(纯手写段):段尾追加注入段,手写内容不动', async () => {
322
+ fs.writeFileSync(agentsMdPath, [
323
+ '# AGENTS.md',
324
+ '',
325
+ GOVERNANCE_PRESERVE_BEGIN,
326
+ '治理契约引言(手写,保留)',
327
+ '',
328
+ '### 发布纪律(手写,保留)',
329
+ GOVERNANCE_PRESERVE_END,
330
+ '',
331
+ ].join('\n'));
332
+
333
+ await setupAgentsMdConstraints(tempDir);
334
+
335
+ const content = fs.readFileSync(agentsMdPath, 'utf-8');
336
+ expect(content).toContain('治理契约引言(手写,保留)');
337
+ expect(content).toContain('### 发布纪律(手写,保留)');
338
+ expect(content).toContain(CONSTRAINTS_START_MARKER);
339
+ // 注入段追加在手写内容之后、PRESERVE 段内
340
+ expect(content.indexOf('### 发布纪律(手写,保留)')).toBeLessThan(content.indexOf(CONSTRAINTS_START_MARKER));
341
+ expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
342
+ // 幂等
343
+ await setupAgentsMdConstraints(tempDir);
344
+ expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(content);
345
+ });
346
+
347
+ it('AGENTS.md 存在但无 PRESERVE:governance 段:末尾追加,既有内容不动', async () => {
348
+ fs.writeFileSync(agentsMdPath, '# AGENTS.md\n\n既有导读内容\n');
349
+
350
+ await setupAgentsMdConstraints(tempDir);
351
+
352
+ const content = fs.readFileSync(agentsMdPath, 'utf-8');
353
+ expect(content).toContain('既有导读内容');
354
+ expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
355
+ expect(content.indexOf('既有导读内容')).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_BEGIN));
356
+ });
357
+
358
+ it('PRESERVE:governance 标记残缺(只有单边):不写入并告警', async () => {
359
+ const original = `# AGENTS.md\n\n${GOVERNANCE_PRESERVE_BEGIN}\n残缺的段\n`;
360
+ fs.writeFileSync(agentsMdPath, original);
361
+ const logSpy = jest.spyOn(console, 'log');
362
+
363
+ await setupAgentsMdConstraints(tempDir);
364
+
365
+ expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(original);
366
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('残缺'));
367
+ });
368
+
369
+ it('段内注入文本与 renderConstraintsSection(getEffectiveConstraints()) 一致', async () => {
370
+ await setupAgentsMdConstraints(tempDir);
371
+
372
+ const content = fs.readFileSync(agentsMdPath, 'utf-8');
373
+ const start = content.indexOf(CONSTRAINTS_START_MARKER);
374
+ const end = content.indexOf(CONSTRAINTS_END_MARKER) + CONSTRAINTS_END_MARKER.length + 1;
375
+ const actual = content.slice(start, end);
376
+ const version = /<!-- version: ([^ ]+) -->/.exec(content)![1];
377
+ expect(actual).toBe(renderConstraintsSection(getEffectiveConstraints(tempDir), version));
378
+ });
379
+ });
380
+
381
+ describe('setupGovernanceConstraints(落点路由)', () => {
382
+ let tempDir: string;
383
+
384
+ beforeEach(() => {
385
+ tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-init-routing-'));
386
+ jest.spyOn(console, 'log').mockImplementation(() => {});
387
+ });
388
+
389
+ afterEach(() => {
390
+ jest.restoreAllMocks();
391
+ fs.rmSync(tempDir, { recursive: true, force: true });
392
+ });
393
+
394
+ it('新仓(无 CLAUDE.md):治理契约写向 AGENTS.md,不创建 CLAUDE.md', async () => {
395
+ await setupGovernanceConstraints(tempDir);
396
+
397
+ expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(true);
398
+ expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(false);
399
+ const content = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
400
+ expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
401
+ expect(content).toContain(CONSTRAINTS_START_MARKER);
402
+ });
403
+
404
+ it('旧模型仓(CLAUDE.md 已有 HARNESS_CONSTRAINTS 标记):继续写 CLAUDE.md,不动 AGENTS.md', async () => {
405
+ fs.writeFileSync(path.join(tempDir, 'CLAUDE.md'), [
406
+ '# CLAUDE.md',
407
+ '',
408
+ '## Governance Rules',
409
+ CONSTRAINTS_START_MARKER,
410
+ '<!-- version: 0.0.0 -->',
411
+ '旧约束',
412
+ CONSTRAINTS_END_MARKER,
413
+ '',
414
+ ].join('\n'));
415
+ fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), '# AGENTS.md\n\n既有导读\n');
416
+
417
+ await setupGovernanceConstraints(tempDir);
418
+
419
+ const claude = fs.readFileSync(path.join(tempDir, 'CLAUDE.md'), 'utf-8');
420
+ expect(claude).not.toContain('旧约束');
421
+ expect(claude).toContain('### Iron Laws');
422
+ // AGENTS.md 不被写入治理段
423
+ expect(fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8')).toBe('# AGENTS.md\n\n既有导读\n');
424
+ });
425
+
426
+ it('旧模型仓(CLAUDE.md 有无标记 Governance Rules 块):路由到 CLAUDE.md', async () => {
427
+ fs.writeFileSync(path.join(tempDir, 'CLAUDE.md'), '# CLAUDE.md\n\n## Governance Rules\n\n手写条款\n');
428
+
429
+ await setupGovernanceConstraints(tempDir);
430
+
431
+ const claude = fs.readFileSync(path.join(tempDir, 'CLAUDE.md'), 'utf-8');
432
+ expect(claude).toContain('手写条款');
433
+ expect(claude).toContain(CONSTRAINTS_START_MARKER);
434
+ expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(false);
435
+ });
436
+ });
@@ -2,7 +2,7 @@
2
2
  * knowledge 命令测试 — knowledgeAudit
3
3
  */
4
4
 
5
- import { knowledgeAudit, knowledgeStats, knowledgeHealth } from '../knowledge';
5
+ import { knowledgeAudit, knowledgeStats, knowledgeHealth, knowledgeSearch } from '../knowledge';
6
6
 
7
7
  // Mock chalk
8
8
  jest.mock('chalk', () => ({
@@ -233,3 +233,92 @@ describe('knowledgeHealth CLI', () => {
233
233
  expect(parsed.summary.staleEntries).toBe(1);
234
234
  });
235
235
  });
236
+
237
+ /**
238
+ * knowledgeSearch 回归测试 — harness#63
239
+ * bug:先按 token budget 截断再文本匹配,新条目(lastReferenced 为空、排序队尾)
240
+ * 在语料超过 budget 时永远进不了匹配阶段。
241
+ */
242
+ describe('knowledgeSearch CLI (harness#63)', () => {
243
+ const fs = require('fs');
244
+ const os = require('os');
245
+ const path = require('path');
246
+ const { FileKnowledgeStore } = require('../../../knowledge/store');
247
+
248
+ let tmpProject: string;
249
+ let knowledgeDir: string;
250
+ let consoleSpy: jest.SpyInstance;
251
+
252
+ const FILLER_COUNT = 25; // 25 × ~800 tokens ≈ 20000 > maxTokens 10000
253
+ const TARGET_ID = 'PIT-020';
254
+
255
+ function makeEntry(overrides: Record<string, any>): any {
256
+ return {
257
+ id: 'X-000',
258
+ type: 'pitfall',
259
+ title: '条目',
260
+ content: '内容',
261
+ maturity: 'proven',
262
+ layer: 'project',
263
+ created: '2026-08-01T00:00:00.000Z',
264
+ lastReferenced: '2026-08-01T00:00:00.000Z',
265
+ contributors: [],
266
+ projects: [],
267
+ tags: [],
268
+ applicablePhases: [],
269
+ sourceReferences: [],
270
+ referencedBy: [],
271
+ executionResults: [],
272
+ consumptionMode: 'reference',
273
+ origin: 'agent',
274
+ ...overrides,
275
+ };
276
+ }
277
+
278
+ beforeEach(() => {
279
+ tmpProject = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-k63-'));
280
+ knowledgeDir = path.join(tmpProject, '.harness', 'knowledge');
281
+ fs.mkdirSync(knowledgeDir, { recursive: true });
282
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation();
283
+
284
+ const store = new FileKnowledgeStore({ baseDir: knowledgeDir });
285
+ // 高成熟度 + 有 lastReferenced 的填充条目排在前面,吃满 token budget
286
+ for (let i = 0; i < FILLER_COUNT; i++) {
287
+ store.save(makeEntry({
288
+ id: `FILL-${String(i).padStart(3, '0')}`,
289
+ title: `填充条目 ${i}`,
290
+ content: '填'.repeat(399) + '共同词',
291
+ }));
292
+ }
293
+ // 目标条目:新条目,draft + lastReferenced 为空,排在队尾
294
+ store.save(makeEntry({
295
+ id: TARGET_ID,
296
+ title: '新条目',
297
+ maturity: 'draft',
298
+ lastReferenced: '',
299
+ content: 'autolink 相关条目 共同词',
300
+ }));
301
+ });
302
+
303
+ afterEach(() => {
304
+ consoleSpy.mockRestore();
305
+ fs.rmSync(tmpProject, { recursive: true, force: true });
306
+ });
307
+
308
+ async function searchJson(query: string, limit: number): Promise<any> {
309
+ await knowledgeSearch(query, { projectPath: tmpProject, json: true, limit });
310
+ const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
311
+ return JSON.parse(output);
312
+ }
313
+
314
+ it('语料超过 token budget 时,排在队尾的新条目仍可被搜到', async () => {
315
+ const result = await searchJson('autolink', 200);
316
+ expect(result.total).toBe(1);
317
+ expect(result.entries[0].id).toBe(TARGET_ID);
318
+ });
319
+
320
+ it('超高频关键词的返回数 = 实际匹配数(截断只发生在匹配之后)', async () => {
321
+ const result = await searchJson('共同词', 200);
322
+ expect(result.total).toBe(FILLER_COUNT + 1);
323
+ });
324
+ });
@@ -51,11 +51,10 @@ describe('命令注册表闭环', () => {
51
51
  });
52
52
 
53
53
  it('全部实现引用可解析(module 存在且 export 是函数)', () => {
54
- const refs = collectRefs(COMMAND_DEFINITIONS);
55
- for (const def of GATE_DEFINITIONS) {
56
- refs.push(def.cli.action);
57
- for (const entry of Object.values(def.cli.subcommands || {})) refs.push(entry);
58
- }
54
+ const refs = collectRefs([
55
+ ...COMMAND_DEFINITIONS,
56
+ ...GATE_DEFINITIONS.map(d => d.cli),
57
+ ]);
59
58
  expect(refs.length).toBeGreaterThan(0);
60
59
 
61
60
  for (const ref of refs) {
@@ -107,7 +106,10 @@ describe('命令注册表闭环', () => {
107
106
  }
108
107
  for (const child of def.children || []) walk(child);
109
108
  };
110
- for (const def of COMMAND_DEFINITIONS) walk(def);
109
+ for (const def of [
110
+ ...COMMAND_DEFINITIONS,
111
+ ...GATE_DEFINITIONS.map(d => d.cli),
112
+ ]) walk(def);
111
113
  // sync-docs --check 失败路径确实会请求退出
112
114
  expect(exitSpy).toHaveBeenCalled();
113
115
  } finally {
@@ -81,7 +81,7 @@ describe('status command - 补充覆盖', () => {
81
81
  ];
82
82
  const mockAnalyze = {
83
83
  summarize: jest.fn().mockReturnValue([
84
- { constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 0.2, totalChecks: 10, bypassRate: 0.1 },
84
+ { constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 0.2, failRate: 0.8, totalChecks: 10 },
85
85
  ]),
86
86
  detectAnomalies: jest.fn().mockReturnValue(mockAnomalies),
87
87
  };
@@ -119,8 +119,8 @@ describe('status command - 补充覆盖', () => {
119
119
  mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
120
120
 
121
121
  const mockSummaries = [
122
- { constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, totalChecks: 10, bypassRate: 0 },
123
- { constraintId: 'no_completion_without_verification', level: 'iron_law', passRate: 0.8, totalChecks: 5, bypassRate: 0 },
122
+ { constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 10 },
123
+ { constraintId: 'no_completion_without_verification', level: 'iron_law', passRate: 0.8, failRate: 0.2, totalChecks: 5 },
124
124
  ];
125
125
  const mockAnalyze = {
126
126
  summarize: jest.fn().mockReturnValue(mockSummaries),
@@ -141,7 +141,7 @@ describe('status command - 补充覆盖', () => {
141
141
  mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
142
142
 
143
143
  const mockSummaries = [
144
- { constraintId: 'capability_sync', level: 'guideline', passRate: 0.9, totalChecks: 20, bypassRate: 0.1 },
144
+ { constraintId: 'capability_sync', level: 'guideline', passRate: 0.9, failRate: 0.1, totalChecks: 20 },
145
145
  ];
146
146
  const mockAnalyze = {
147
147
  summarize: jest.fn().mockReturnValue(mockSummaries),
@@ -164,8 +164,8 @@ describe('status command - 补充覆盖', () => {
164
164
  mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
165
165
 
166
166
  const mockSummaries = [
167
- { constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, totalChecks: 10, bypassRate: 0 },
168
- { constraintId: 'capability_sync', level: 'guideline', passRate: 0.9, totalChecks: 20, bypassRate: 0.1 },
167
+ { constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 10 },
168
+ { constraintId: 'capability_sync', level: 'guideline', passRate: 0.9, failRate: 0.1, totalChecks: 20 },
169
169
  ];
170
170
  const mockAnalyze = {
171
171
  summarize: jest.fn().mockReturnValue(mockSummaries),
@@ -87,7 +87,7 @@ describe('status command', () => {
87
87
  mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
88
88
 
89
89
  const mockSummarize = jest.fn().mockReturnValue([
90
- { constraintId: 'test1', level: 'iron_law', passRate: 1, totalChecks: 1, bypassRate: 0 },
90
+ { constraintId: 'test1', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 1 },
91
91
  ]);
92
92
  const mockAnalyze = {
93
93
  summarize: mockSummarize,
@@ -106,7 +106,7 @@ describe('status command', () => {
106
106
  mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
107
107
 
108
108
  const mockSummarize = jest.fn().mockReturnValue([
109
- { constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, totalChecks: 5, bypassRate: 0 },
109
+ { constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 5 },
110
110
  ]);
111
111
  const mockAnalyze = {
112
112
  summarize: mockSummarize,
@@ -131,7 +131,7 @@ describe('status command', () => {
131
131
  ];
132
132
  const mockAnalyze = {
133
133
  summarize: jest.fn().mockReturnValue([
134
- { constraintId: 'test1', level: 'iron_law', passRate: 0.2, totalChecks: 5, bypassRate: 0 },
134
+ { constraintId: 'test1', level: 'iron_law', passRate: 0.2, failRate: 0.8, totalChecks: 5 },
135
135
  ]),
136
136
  detectAnomalies: jest.fn().mockReturnValue(mockAnomalies),
137
137
  };
@@ -150,7 +150,7 @@ describe('status command', () => {
150
150
  mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
151
151
 
152
152
  const mockSummarize = jest.fn().mockReturnValue([
153
- { constraintId: 'test1', level: 'iron_law', passRate: 0.8, totalChecks: 10, bypassRate: 0.1 },
153
+ { constraintId: 'test1', level: 'iron_law', passRate: 0.8, failRate: 0.2, totalChecks: 10 },
154
154
  ]);
155
155
  const mockAnalyze = {
156
156
  summarize: mockSummarize,
@@ -184,7 +184,7 @@ describe('status command', () => {
184
184
 
185
185
  const mockAnalyze = {
186
186
  summarize: jest.fn().mockReturnValue([
187
- { constraintId: 'test_guide', level: 'guideline', passRate: 0.9, totalChecks: 10, bypassRate: 0 },
187
+ { constraintId: 'test_guide', level: 'guideline', passRate: 0.9, failRate: 0.1, totalChecks: 10 },
188
188
  ]),
189
189
  detectAnomalies: jest.fn().mockReturnValue([]),
190
190
  };
@@ -276,7 +276,7 @@ describe('status command', () => {
276
276
 
277
277
  const mockAnalyze = {
278
278
  summarize: jest.fn().mockReturnValue([
279
- { constraintId: 'test_guide', level: 'guideline', passRate: 0.6, totalChecks: 10, bypassRate: 0.1 },
279
+ { constraintId: 'test_guide', level: 'guideline', passRate: 0.6, failRate: 0.4, totalChecks: 10 },
280
280
  ]),
281
281
  detectAnomalies: jest.fn().mockReturnValue([]),
282
282
  };
@@ -592,5 +592,33 @@ describe('sync-docs --agents', () => {
592
592
 
593
593
  fs.rmSync(testDir, { recursive: true, force: true });
594
594
  });
595
+
596
+ it('PRESERVE:governance 治理契约段开箱即用:重新生成保留、--check 通过(studio #302)', async () => {
597
+ const testDir = path.join(tempDir, 'preserve-governance');
598
+ createFixture(testDir, { pnpm: true, withCapabilities: true });
599
+
600
+ const GOVERNANCE_BLOCK = [
601
+ '<!-- PRESERVE:governance -->',
602
+ '## Governance Rules',
603
+ '',
604
+ '<!-- HARNESS_CONSTRAINTS_START -->',
605
+ '- **law_a**: 治理契约条款',
606
+ '<!-- HARNESS_CONSTRAINTS_END -->',
607
+ '<!-- /PRESERVE:governance -->',
608
+ ].join('\n');
609
+
610
+ await syncDocs({ projectPath: testDir, agents: true });
611
+ appendBlock(testDir, GOVERNANCE_BLOCK);
612
+ await syncDocs({ projectPath: testDir, agents: true });
613
+
614
+ const content = fs.readFileSync(path.join(testDir, 'AGENTS.md'), 'utf-8');
615
+ expect(content).toContain(GOVERNANCE_BLOCK);
616
+
617
+ // 幂等 + --check 通过(块内含 HARNESS_CONSTRAINTS 标记不影响漂移比对)
618
+ expect(await syncDocs({ projectPath: testDir, agents: true })).toBe(true);
619
+ expect(await syncDocs({ projectPath: testDir, agents: true, check: true })).toBe(true);
620
+
621
+ fs.rmSync(testDir, { recursive: true, force: true });
622
+ });
595
623
  });
596
624
  });
@@ -2,17 +2,16 @@
2
2
  * harness check 命令
3
3
  *
4
4
  * 检查约束是否满足(check 层:Iron Laws / Guidelines;prompt 层仅注入不检查)
5
- * 工单 23:触发条件与证据检测迁至 core/constraints/context-builder;--preset 经 mergeConstraints 生效
6
- * ADR-0001:约束集统一走 mergeConstraints 生效集链路(preset/config 禁用/custom/scenes)
5
+ * 工单 23:触发条件与证据检测迁至 core/constraints/context-builder
6
+ * ADR-0001:约束集统一走 getMergedConstraintsConfig 生效集链路(preset/config 禁用/custom/scenes)
7
7
  */
8
8
 
9
9
  import chalk from 'chalk';
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
12
  import { constraintChecker } from '../../core/constraints/checker';
13
- import { getTraceCollector } from '../../monitoring/traces';
14
13
  import { IRON_LAWS, GUIDELINES, PROMPTS } from '../../core/constraints/definitions';
15
- import { ProjectConfigLoader } from '../../core/project-config-loader';
14
+ import { getMergedConstraintsConfig } from '../../core/effective-constraints';
16
15
  import { buildConstraintContext } from '../../core/constraints/context-builder';
17
16
  import { detectInjectionDrift } from '../../core/constraints/injection-drift';
18
17
  import { DEFAULT_TRACE_FILE } from '../../types/trace';
@@ -37,18 +36,12 @@ export async function check(options: CheckOptions): Promise<void> {
37
36
  console.log(chalk.gray(`预设: ${options.preset}`));
38
37
 
39
38
  try {
40
- // 加载项目级自定义约束
41
39
  const projectPath = options.projectPath || process.cwd();
42
- const configLoader = new ProjectConfigLoader(projectPath);
43
- configLoader.load();
44
- constraintChecker.setTraceRecorder(getTraceCollector());
45
40
 
46
41
  // 生效约束集(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' });
42
+ // --preset 仅在没有项目自定义配置时覆盖 config.yml 的 preset(工单 23 语义:
43
+ // 项目自定义配置优先于 CLI 预设),优先级规则收在 getMergedConstraintsConfig 一处。
44
+ const merged = getMergedConstraintsConfig(projectPath, { preset: options.preset });
52
45
  if (merged.custom.length > 0) {
53
46
  console.log(chalk.gray(`自定义约束: ${merged.custom.length} 条`));
54
47
  }
@@ -130,7 +123,7 @@ export async function check(options: CheckOptions): Promise<void> {
130
123
  const drift = detectInjectionDrift(projectPath);
131
124
  if (drift.hasDrift) {
132
125
  console.log();
133
- console.log(chalk.yellow('⚠️ 检测到 CLAUDE.md 约束注入漂移(仅警告,不阻断):'));
126
+ console.log(chalk.yellow(`⚠️ 检测到 ${drift.injectionFile ?? '治理文档'} 约束注入漂移(仅警告,不阻断):`));
134
127
  if (drift.versionDrift) {
135
128
  console.log(chalk.yellow(` ⚠️⚠️ 注入段版本 (${drift.versionDrift.actual}) ≠ 已安装 harness 版本 (${drift.versionDrift.expected}):agent 上下文中的规则与已安装 harness 版本不一致`));
136
129
  }
@@ -199,23 +192,7 @@ async function getSmartHint(projectPath: string): Promise<string | null> {
199
192
  hints.push('📊 记录已足够,运行 harness status 查看统计');
200
193
  state.shownHints.push('trace_50');
201
194
  }
202
-
203
- // 条件 2: 检查异常趋势(简单检查绕过率)
204
- const bypassCount = lines.filter(line => {
205
- try {
206
- const trace = JSON.parse(line);
207
- return trace.result === 'bypassed';
208
- } catch {
209
- return false;
210
- }
211
- }).length;
212
-
213
- const bypassRate = traceCount > 10 ? bypassCount / traceCount : 0;
214
- if (bypassRate > 0.3 && !state.shownHints.includes('high_bypass')) {
215
- hints.push('⚠️ 发现异常趋势,建议运行 harness status 查看详情');
216
- state.shownHints.push('high_bypass');
217
- }
218
-
195
+
219
196
  // 保存状态
220
197
  if (hints.length > 0) {
221
198
  fs.mkdirSync(path.dirname(statePath), { recursive: true });
@@ -242,14 +219,11 @@ export function listLaws(): void {
242
219
  });
243
220
 
244
221
  // Guidelines
245
- console.log(chalk.yellow('🟡 指导原则 (Guidelines) - 优先建议,有例外:\n'));
222
+ console.log(chalk.yellow('🟡 指导原则 (Guidelines) - 优先建议,违背发警告但不阻止:\n'));
246
223
  Object.values(GUIDELINES).forEach(constraint => {
247
224
  console.log(chalk.yellow(` ${constraint.id}`));
248
225
  console.log(chalk.gray(` ${constraint.rule}`));
249
226
  console.log(chalk.gray(` ${constraint.message}`));
250
- if (constraint.exceptions && constraint.exceptions.length > 0) {
251
- console.log(chalk.gray(` 例外: ${constraint.exceptions.join(', ')}`));
252
- }
253
227
  console.log();
254
228
  });
255
229
 
@@ -118,7 +118,7 @@ export function renderExportMarkdown(
118
118
  export function renderInjectionDriftLines(drift: InjectionDrift): string[] {
119
119
  const lines: string[] = [];
120
120
  if (drift.notInjected) {
121
- lines.push(' CLAUDE.md 无约束注入标记段(未注入,不算漂移);可运行 npx @dommaker/harness init 注入');
121
+ lines.push(' CLAUDE.md / AGENTS.md 均无约束注入标记段(未注入,不算漂移);可运行 npx @dommaker/harness init 注入');
122
122
  if (drift.duplicateHeading) {
123
123
  lines.push(' 另检测到多个 "## Governance Rules" 标题(历史遗留重复章节),建议手工清理后重跑 npx @dommaker/harness init');
124
124
  }