@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
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * - 三类漂移各自检出:版本 / 内容(条目级 missing/extra)/ 重复章节
5
5
  * - 无漂移静默;无标记段 = 未注入不算漂移;CLAUDE.md 不存在不算漂移
6
+ * - 落点路由(studio #307):AGENTS.md PRESERVE:governance 内注入段同样校验;
7
+ * 两文件均有标记段时 CLAUDE.md 优先(旧模型仓豁免)
6
8
  * - config.yml 变更后未重跑 init → 内容漂移(extra)
7
9
  *
8
10
  * 使用真实临时目录(getEffectiveConstraints 读真实 fs)。
@@ -33,6 +35,17 @@ function writeSyncedClaudeMd(root: string, extra = ''): string {
33
35
  return content;
34
36
  }
35
37
 
38
+ /** 新模型仓注入段:AGENTS.md `PRESERVE:governance` 段内含标记段(版本 TEST_VERSION) */
39
+ function writeSyncedAgentsMd(root: string, extra = ''): string {
40
+ const section =
41
+ '<!-- PRESERVE:governance -->\n## Governance Rules\n' +
42
+ renderConstraintsSection(getEffectiveConstraints(root), TEST_VERSION) +
43
+ '<!-- /PRESERVE:governance -->\n';
44
+ const content = `# AGENTS.md\n\n${section}${extra}`;
45
+ fs.writeFileSync(path.join(root, 'AGENTS.md'), content, 'utf-8');
46
+ return content;
47
+ }
48
+
36
49
  describe('detectInjectionDrift', () => {
37
50
  it('无漂移:注入段与期望渲染一致 → hasDrift=false', () => {
38
51
  const root = makeTmpProject();
@@ -161,4 +174,81 @@ describe('detectInjectionDrift', () => {
161
174
 
162
175
  expect(drift.versionDrift).toEqual({ expected: TEST_VERSION, actual: '(缺失)' });
163
176
  });
177
+
178
+ it('新模型仓:AGENTS.md PRESERVE:governance 注入段一致 → 无漂移,injectionFile=AGENTS.md', () => {
179
+ const root = makeTmpProject();
180
+ writeSyncedAgentsMd(root);
181
+
182
+ const drift = detectInjectionDrift(root, TEST_VERSION);
183
+
184
+ expect(drift.notInjected).toBe(false);
185
+ expect(drift.injectionFile).toBe('AGENTS.md');
186
+ expect(drift.hasDrift).toBe(false);
187
+ expect(drift.duplicateHeading).toBe(false);
188
+ });
189
+
190
+ it('新模型仓:版本漂移与内容漂移照常检出', () => {
191
+ const root = makeTmpProject();
192
+ const synced = writeSyncedAgentsMd(root);
193
+ const originalLine = synced.split('\n').find(l => l.startsWith('- **'))!;
194
+ const editedLine = originalLine.replace(/: .+$/, ': 手工篡改的注入文本');
195
+ fs.writeFileSync(
196
+ path.join(root, 'AGENTS.md'),
197
+ synced
198
+ .replace(`<!-- version: ${TEST_VERSION} -->`, '<!-- version: 0.0.1-old -->')
199
+ .replace(originalLine, editedLine),
200
+ 'utf-8'
201
+ );
202
+
203
+ const drift = detectInjectionDrift(root, TEST_VERSION);
204
+
205
+ expect(drift.injectionFile).toBe('AGENTS.md');
206
+ expect(drift.hasDrift).toBe(true);
207
+ expect(drift.versionDrift).toEqual({ expected: TEST_VERSION, actual: '0.0.1-old' });
208
+ expect(drift.contentDrift!.missing).toEqual([originalLine]);
209
+ expect(drift.contentDrift!.extra).toEqual([editedLine]);
210
+ });
211
+
212
+ it('新模型仓:PRESERVE 段之外还有 ## Governance Rules 标题 → duplicateHeading', () => {
213
+ const root = makeTmpProject();
214
+ writeSyncedAgentsMd(root, '\n## Governance Rules\n\n旧版遗留的同名章节\n');
215
+
216
+ const drift = detectInjectionDrift(root, TEST_VERSION);
217
+
218
+ expect(drift.injectionFile).toBe('AGENTS.md');
219
+ expect(drift.duplicateHeading).toBe(true);
220
+ expect(drift.hasDrift).toBe(true);
221
+ });
222
+
223
+ it('两文件均有标记段:旧模型仓豁免,CLAUDE.md 优先(AGENTS.md 漂移不影响判定)', () => {
224
+ const root = makeTmpProject();
225
+ writeSyncedClaudeMd(root);
226
+ const agents = writeSyncedAgentsMd(root);
227
+ // AGENTS.md 注入段手改出漂移,CLAUDE.md 保持一致
228
+ fs.writeFileSync(
229
+ path.join(root, 'AGENTS.md'),
230
+ agents.replace(`<!-- version: ${TEST_VERSION} -->`, '<!-- version: 0.0.1-old -->'),
231
+ 'utf-8'
232
+ );
233
+
234
+ const drift = detectInjectionDrift(root, TEST_VERSION);
235
+
236
+ expect(drift.injectionFile).toBe('CLAUDE.md');
237
+ expect(drift.hasDrift).toBe(false);
238
+ });
239
+
240
+ it('两处均无标记段但 AGENTS.md 有重复 Governance Rules 标题 → notInjected + duplicateHeading', () => {
241
+ const root = makeTmpProject();
242
+ fs.writeFileSync(
243
+ path.join(root, 'AGENTS.md'),
244
+ '# AGENTS.md\n\n## Governance Rules\n\n甲\n\n## Governance Rules\n\n乙\n',
245
+ 'utf-8'
246
+ );
247
+
248
+ const drift = detectInjectionDrift(root, TEST_VERSION);
249
+
250
+ expect(drift.notInjected).toBe(true);
251
+ expect(drift.hasDrift).toBe(false);
252
+ expect(drift.duplicateHeading).toBe(true);
253
+ });
164
254
  });
@@ -9,9 +9,16 @@
9
9
 
10
10
  import * as fs from 'fs';
11
11
  import { IRON_LAWS, GUIDELINES } from './definitions';
12
+ import { COMMAND_DEFINITIONS } from '../../cli/commands/definitions';
13
+ import { GATE_DEFINITIONS } from '../../gates/definitions';
12
14
  import { FreshnessRunner } from './doc-freshness/runner';
13
15
  import type { DocFreshnessCheck } from '../../types/project-config';
14
16
 
17
+ /** CLI 命令数 = 非门禁命令定义 + 门禁命令定义(ADR-0002:定义表是命令形状单一来源) */
18
+ function cliCommandCount(): number {
19
+ return COMMAND_DEFINITIONS.length + GATE_DEFINITIONS.filter(g => g.cli).length;
20
+ }
21
+
15
22
  /** 匹配表格单元格中的源码文件条目(如 `src/foo.ts`、`foo.tsx`) */
16
23
  const FILE_CELL_REGEX = /\|\s*([^|]+?\.(?:ts|tsx|js|jsx))\s*\|/g;
17
24
 
@@ -104,41 +111,40 @@ export function aggregateToSourceSubdir(root: string, file: string): string {
104
111
 
105
112
  // ── 能力清单计数 ────────────────────────────────────────────
106
113
 
114
+ /**
115
+ * 能力清单统计规则单份定义(ADR-0008:check/write 两方向共用,
116
+ * 此前 label+pattern 在 buildCapabilityChecks 与 updateCapabilityCounts 各写一遍)
117
+ *
118
+ * - label:CAPABILITIES.md 计数行 label(check 的 DocFreshnessCheck.label +
119
+ * write 的替换文本前缀,如 `Quality Gates (6)`)
120
+ * - pattern:同时供 check(doc_regex_count 字符串 pattern,捕获组为文档计数)
121
+ * 与 write(编译为替换 regex)
122
+ * - actual:定义表实际计数(ADR-0002 单一来源)
123
+ */
124
+ interface CapabilityCountRule {
125
+ label: string;
126
+ pattern: string;
127
+ actual: () => number;
128
+ }
129
+
130
+ const CAPABILITY_COUNT_RULES: CapabilityCountRule[] = [
131
+ { label: 'CLI Commands', pattern: 'CLI Commands\\s*\\((\\d+)\\)', actual: cliCommandCount },
132
+ { label: 'Quality Gates', pattern: 'Quality Gates?\\s*\\((\\d+)\\)', actual: () => GATE_DEFINITIONS.length },
133
+ { label: 'Iron Laws', pattern: 'Iron Laws?\\s*\\((\\d+)\\)', actual: () => Object.keys(IRON_LAWS).length },
134
+ { label: 'Guidelines', pattern: 'Guidelines?\\s*\\((\\d+)\\)', actual: () => Object.keys(GUIDELINES).length },
135
+ ];
136
+
107
137
  /**
108
138
  * 构建 CAPABILITIES.md 能力清单格式的检查配置
109
139
  */
110
140
  function buildCapabilityChecks(): DocFreshnessCheck[] {
111
- return [
112
- {
113
- type: 'doc_regex_count',
114
- doc: 'CAPABILITIES.md',
115
- label: 'CLI Commands',
116
- pattern: 'CLI Commands\\s*\\((\\d+)\\)',
117
- // index.ts(已删 barrel)与 definitions.ts(命令注册表纯数据)不是命令实现,不计入
118
- actual: { kind: 'dir_count', path: 'src/cli/commands', extension: '.ts', exclude: ['index.ts', 'definitions.ts'] },
119
- },
120
- {
121
- type: 'doc_regex_count',
122
- doc: 'CAPABILITIES.md',
123
- label: 'Quality Gates',
124
- pattern: 'Quality Gates?\\s*\\((\\d+)\\)',
125
- actual: { kind: 'dir_count', path: 'src/gates', extension: '.ts', exclude: ['index.ts', 'types.ts'] },
126
- },
127
- {
128
- type: 'doc_regex_count',
129
- doc: 'CAPABILITIES.md',
130
- label: 'Iron Laws',
131
- pattern: 'Iron Laws?\\s*\\((\\d+)\\)',
132
- actual: { kind: 'const_count', value: Object.keys(IRON_LAWS).length },
133
- },
134
- {
135
- type: 'doc_regex_count',
136
- doc: 'CAPABILITIES.md',
137
- label: 'Guidelines',
138
- pattern: 'Guidelines?\\s*\\((\\d+)\\)',
139
- actual: { kind: 'const_count', value: Object.keys(GUIDELINES).length },
140
- },
141
- ];
141
+ return CAPABILITY_COUNT_RULES.map(rule => ({
142
+ type: 'doc_regex_count' as const,
143
+ doc: 'CAPABILITIES.md',
144
+ label: rule.label,
145
+ pattern: rule.pattern,
146
+ actual: { kind: 'const_count' as const, value: rule.actual() },
147
+ }));
142
148
  }
143
149
 
144
150
  /**
@@ -164,26 +170,14 @@ export function checkCapabilityCounts(
164
170
  /**
165
171
  * 更新 CAPABILITIES.md 中的能力清单计数(write 模式)
166
172
  *
167
- * 使用 FreshnessRunner 获取实际计数,然后 regex 替换文档计数行。
173
+ * 命令/门禁计数取自定义表(ADR-0002 单一来源),regex 替换文档计数行;
174
+ * 规则与 check 方向共用 CAPABILITY_COUNT_RULES(ADR-0008)。
168
175
  */
169
- export function updateCapabilityCounts(content: string, projectPath: string): string {
170
- const runner = new FreshnessRunner();
171
-
172
- const cliCount = runner.countFromDir('src/cli/commands', '.ts', ['index.ts', 'definitions.ts'], projectPath);
173
- const gateCount = runner.countFromDir('src/gates', '.ts', ['index.ts', 'types.ts'], projectPath);
174
- const ironCount = Object.keys(IRON_LAWS).length;
175
- const guideCount = Object.keys(GUIDELINES).length;
176
-
177
- const replacements: [RegExp, string][] = [
178
- [/CLI Commands\s*\(\d+\)/, `CLI Commands (${cliCount})`],
179
- [/Quality Gates?\s*\(\d+\)/, `Quality Gates (${gateCount})`],
180
- [/Iron Laws?\s*\(\d+\)/, `Iron Laws (${ironCount})`],
181
- [/Guidelines?\s*\(\d+\)/, `Guidelines (${guideCount})`],
182
- ];
183
-
184
- for (const [regex, replacement] of replacements) {
176
+ export function updateCapabilityCounts(content: string, _projectPath: string): string {
177
+ for (const rule of CAPABILITY_COUNT_RULES) {
178
+ const regex = new RegExp(rule.pattern);
185
179
  if (regex.test(content)) {
186
- content = content.replace(regex, replacement);
180
+ content = content.replace(regex, `${rule.label} (${rule.actual()})`);
187
181
  }
188
182
  }
189
183
 
@@ -17,6 +17,7 @@ import {
17
17
  ConstraintViolationError,
18
18
  } from '../../types/constraint';
19
19
  import type { ExecutionTrace } from '../../types/trace';
20
+ import { getTraceCollector } from '../../monitoring/traces';
20
21
  import { IRON_LAWS, GUIDELINES, PROMPTS } from './definitions';
21
22
  import type { MergedConstraintsConfig } from '../../types/project-config';
22
23
  import { normalizeTriggers } from '../../utils/exec';
@@ -24,50 +25,19 @@ import { runCommand } from '../../utils/exec';
24
25
  import { join, relative } from 'path';
25
26
  import { CheckCache } from './check-cache';
26
27
  import { findTsSourceFiles } from '../../utils/file-walk';
27
- import { getConstraintCheck, type CheckEnv, type CheckOutcome } from './checkers';
28
+ import { getConstraintCheck, buildCheckEnv, type CheckOutcome } from './checkers';
28
29
 
29
30
  /**
30
- * 例外名称ConstraintContext 中布尔字段的映射
31
- */
32
- const EXCEPTION_FIELD_MAP: Record<string, keyof ConstraintContext> = {
33
- scalability_required: 'scalabilityRequired',
34
- security_required: 'securityRequired',
35
- performance_required: 'performanceRequired',
36
- reliability_required: 'reliabilityRequired',
37
- simple_typo: 'isSimpleTypo',
38
- config_value_error: 'isConfigValueError',
39
- missing_config: 'isMissingConfig',
40
- config_file: 'isConfigFile',
41
- type_definition: 'isTypeDefinition',
42
- simple_accessor: 'isSimpleAccessor',
43
- pure_display_component: 'isPureDisplayComponent',
44
- json_parse_result: 'isJsonParseResult',
45
- third_party_no_types: 'isThirdPartyNoTypes',
46
- legacy_migration: 'isLegacyMigration',
47
- internal_refactor: 'isInternalRefactor',
48
- bug_fix_only: 'isBugFixOnly',
49
- performance_optimization: 'isPerformanceOptimization',
50
- redundant_code_cleanup: 'isRedundantCodeCleanup',
51
- same_effect_refactor: 'isSameEffectRefactor',
52
- unused_code_removal: 'isUnusedCodeRemoval',
53
- external_dependency: 'isExternalDependency',
54
- explicit_instruction: 'isExplicitInstruction',
55
- emergency_fix: 'isEmergencyFix',
56
- existing_design: 'isExistingDesign',
57
- };
58
-
59
- /**
60
- * trace 记录器最小接口(工单 15:core→monitoring 循环消除)
31
+ * trace 记录器最小接口(原工单 15 为消除 coremonitoring 循环;现 monitoring
32
+ * 不反向依赖 core,方向单向、无环,checker 可直接值引用 getTraceCollector)
61
33
  *
62
- * checker 不再值导入 monitoring/traces 的单例;生产调用方经
63
- * setTraceRecorder 注入真实收集器,默认 no-op。
34
+ * checker 不构造收集器;生产路径首次记录时惰性接线 getTraceCollector()
35
+ * (幂等单点,ADR-0003),测试可经 setTraceRecorder 注入替身。
64
36
  */
65
37
  export interface TraceRecorder {
66
38
  record(trace: ExecutionTrace): void;
67
39
  }
68
40
 
69
- const NOOP_TRACE_RECORDER: TraceRecorder = { record: () => undefined };
70
-
71
41
  /**
72
42
  * 约束检查器
73
43
  */
@@ -78,16 +48,26 @@ export class ConstraintChecker {
78
48
  private cache: CheckCache = new CheckCache({ ttlMs: 1000 });
79
49
 
80
50
  /**
81
- * run 级 memo(工单 18):一次 checkConstraints/checkConstraintsSafe
51
+ * run 级 memo(工单 18):一次 checkConstraints 内
82
52
  * git diff 等命令只执行一次;run 外直接调用私有检查方法时不 memo(恒新鲜)。
83
53
  */
84
54
  private runCache: Map<string, Promise<string>> | null = null;
85
55
 
86
- /** trace 记录器(注入式,默认 no-op;生产入口经 setTraceRecorder 接入真实收集器) */
87
- private traceRecorder: TraceRecorder = NOOP_TRACE_RECORDER;
56
+ /** trace 记录器(注入式;null = 未显式注入,首次记录时惰性接线全局收集器) */
57
+ private traceRecorder: TraceRecorder | null = null;
88
58
 
89
59
  private constructor() {}
90
60
 
61
+ /**
62
+ * 取 trace 记录器:未显式注入时惰性接线全局收集器(幂等,ADR-0003 副作用收敛)
63
+ */
64
+ private getRecorder(): TraceRecorder {
65
+ if (!this.traceRecorder) {
66
+ this.traceRecorder = getTraceCollector();
67
+ }
68
+ return this.traceRecorder;
69
+ }
70
+
91
71
  /**
92
72
  * run 内 memoize;无活动 run 时直接执行(工单 18)
93
73
  */
@@ -130,7 +110,7 @@ export class ConstraintChecker {
130
110
  }
131
111
 
132
112
  /**
133
- * 注入 trace 记录器(工单 15:解耦 core 对 monitoring 的值依赖)
113
+ * 注入 trace 记录器(测试/定制场景;不调用则首次记录时惰性接线全局收集器)
134
114
  */
135
115
  setTraceRecorder(recorder: TraceRecorder): void {
136
116
  this.traceRecorder = recorder;
@@ -139,29 +119,25 @@ export class ConstraintChecker {
139
119
  /**
140
120
  * 获取当前的约束集合(内置 + 自定义)
141
121
  *
122
+ * 纯查询,无副作用(ADR-0006:原 `.check` 装配已随唯一消费者
123
+ * interceptor 的删除一并移除,ADR-0004)。
124
+ *
142
125
  * @param customConfig 可选,per-request 自定义配置;不传则返回内置约束集
143
126
  */
144
127
  getConstraints(customConfig?: MergedConstraintsConfig | null): {
145
- ironLaws: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
146
- guidelines: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
147
- prompts: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
128
+ ironLaws: Record<string, Constraint>;
129
+ guidelines: Record<string, Constraint>;
130
+ prompts: Record<string, Constraint>;
148
131
  } {
149
132
  const config = customConfig;
150
133
  const source = config
151
134
  ? { ironLaws: config.ironLaws, guidelines: config.guidelines, prompts: config.prompts ?? PROMPTS }
152
135
  : { ironLaws: IRON_LAWS, guidelines: GUIDELINES, prompts: PROMPTS };
153
136
 
154
- // Wire unified check() method on every constraint
155
- const wire = (constraints: Record<string, Constraint>) => {
156
- for (const c of Object.values(constraints)) {
157
- (c as any).check = (ctx: ConstraintContext) => this.check(c, ctx);
158
- }
159
- return constraints as Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
160
- };
161
137
  return {
162
- ironLaws: wire(source.ironLaws),
163
- guidelines: wire(source.guidelines),
164
- prompts: wire(source.prompts),
138
+ ironLaws: source.ironLaws,
139
+ guidelines: source.guidelines,
140
+ prompts: source.prompts,
165
141
  };
166
142
  }
167
143
 
@@ -183,20 +159,6 @@ export class ConstraintChecker {
183
159
  };
184
160
  }
185
161
 
186
- // 检查例外条件(仅 Guidelines 有效)
187
- if (constraint.level === 'guideline' && constraint.exceptions) {
188
- if (this.checkException(constraint, context)) {
189
- return {
190
- id: constraint.id,
191
- level: constraint.level,
192
- satisfied: true,
193
- constraint,
194
- message: `指导原则 ${constraint.id} 因例外条件被豁免`,
195
- checkedAt: new Date(),
196
- };
197
- }
198
- }
199
-
200
162
  // 检查前置条件('skip' = 约定未采用/证据未接线:satisfied 置 true 但不计 pass/fail)
201
163
  const outcome = await this.checkPrecondition(constraint, context);
202
164
 
@@ -225,16 +187,6 @@ export class ConstraintChecker {
225
187
  };
226
188
  }
227
189
 
228
- /**
229
- * 检查例外条件
230
- */
231
- private checkException(constraint: Constraint, context: ConstraintContext): boolean {
232
- if (!constraint.exceptions) return false;
233
- return constraint.exceptions.some(
234
- (ex) => context[EXCEPTION_FIELD_MAP[ex]] === true
235
- );
236
- }
237
-
238
190
  /**
239
191
  * 根据约束层级获取严重性
240
192
  */
@@ -279,9 +231,6 @@ export class ConstraintChecker {
279
231
  result: checkResult.skipped ? 'skip' : checkResult.satisfied ? 'pass' : 'fail',
280
232
  operation: context.operation,
281
233
  severity: this.getSeverity(constraint.level),
282
- exceptionApplied: checkResult.message?.includes('豁免')
283
- ? constraint.exceptions?.[0]
284
- : undefined,
285
234
  projectPath: context.projectPath,
286
235
  sessionId: context.sessionId,
287
236
  });
@@ -306,9 +255,7 @@ export class ConstraintChecker {
306
255
  }
307
256
 
308
257
  const projectPath = context.projectPath || process.cwd();
309
- const env: CheckEnv = {
310
- context,
311
- projectPath,
258
+ const env = buildCheckEnv(context, {
312
259
  stagedDiff: () => this.getStagedDiff(projectPath),
313
260
  stagedDiffNames: () => this.getStagedDiffNames(projectPath),
314
261
  srcScan: (root: string) =>
@@ -317,7 +264,7 @@ export class ConstraintChecker {
317
264
  relative(projectPath, f)
318
265
  )
319
266
  ),
320
- };
267
+ });
321
268
 
322
269
  return await impl.evaluate(env);
323
270
  }
@@ -380,7 +327,7 @@ export class ConstraintChecker {
380
327
  warningCount: 0,
381
328
  };
382
329
 
383
- const traceCollector = this.traceRecorder;
330
+ const traceCollector = this.getRecorder();
384
331
  const constraints = this.getConstraints(customConfig);
385
332
 
386
333
  // 1. Iron Laws: 必须全部通过
@@ -416,53 +363,6 @@ export class ConstraintChecker {
416
363
  }
417
364
  }
418
365
 
419
- /**
420
- * S11: 安全全量检查 — 全部约束检查,不抛异常
421
- *
422
- * 区别 checkConstraints(): Iron Law 违规收集在结果中而不是抛异常。
423
- * 调用方通过 result.passed + result.warningCount 判断状态。
424
- */
425
- async checkConstraintsSafe(
426
- context: ConstraintContext,
427
- customConfig?: MergedConstraintsConfig | null
428
- ): Promise<ConstraintCheckResult> {
429
- // run 起始:重置 run 级缓存(工单 18)
430
- this.cache.invalidate();
431
- this.runCache = new Map();
432
-
433
- try {
434
- const result: ConstraintCheckResult = {
435
- ironLaws: [],
436
- guidelines: [],
437
- passed: true,
438
- warningCount: 0,
439
- };
440
-
441
- const traceCollector = this.traceRecorder;
442
- const constraints = this.getConstraints(customConfig);
443
-
444
- for (const constraint of Object.values(constraints.ironLaws)) {
445
- if (!this.matchesTrigger(constraint, context)) continue;
446
- const checkResult = await this.check(constraint, context);
447
- result.ironLaws.push(checkResult);
448
- this.recordTrace(traceCollector, constraint, checkResult, context);
449
- if (!checkResult.satisfied) result.passed = false;
450
- }
451
-
452
- for (const constraint of Object.values(constraints.guidelines)) {
453
- if (!this.matchesTrigger(constraint, context)) continue;
454
- const checkResult = await this.check(constraint, context);
455
- result.guidelines.push(checkResult);
456
- this.recordTrace(traceCollector, constraint, checkResult, context);
457
- if (!checkResult.satisfied) result.warningCount++;
458
- }
459
-
460
- return result;
461
- } finally {
462
- this.runCache = null;
463
- }
464
- }
465
-
466
366
  /**
467
367
  * 执行前检查(仅检查 Iron Laws)
468
368
  *
@@ -524,26 +424,32 @@ export async function checkConstraint(
524
424
  }
525
425
 
526
426
  /**
527
- * 快捷函数:执行三层检查
528
- *
529
- * @param context 约束上下文
530
- * @param customConfig 可选,per-request 自定义配置
427
+ * checkConstraints 选项(ADR-0003:包根/子路径统一 options 对象签名)
531
428
  */
532
- export async function checkConstraints(
533
- context: ConstraintContext,
534
- customConfig?: MergedConstraintsConfig | null
535
- ): Promise<ConstraintCheckResult> {
536
- return ConstraintChecker.getInstance().checkConstraints(context, customConfig);
429
+ export interface CheckConstraintsOptions {
430
+ /** 每条约束检查后的回调(用于记录 trace 到外部存储) */
431
+ onTrace?: (result: ConstraintResult) => void;
432
+ /** 可选,per-request 自定义约束配置;不传则用内置约束集 */
433
+ customConfig?: MergedConstraintsConfig | null;
537
434
  }
538
435
 
539
436
  /**
540
- * S11: 快捷函数 — 安全全量检查(不抛异常)
437
+ * 快捷函数:执行三层检查
438
+ *
439
+ * @param context 约束上下文
440
+ * @param options.onTrace 每条约束检查后的回调(用于记录 trace 到外部存储)
441
+ * @param options.customConfig 可选,per-request 自定义约束配置;不传则用内置约束集
541
442
  */
542
- export async function checkConstraintsSafe(
443
+ export async function checkConstraints(
543
444
  context: ConstraintContext,
544
- customConfig?: MergedConstraintsConfig | null
445
+ options?: CheckConstraintsOptions
545
446
  ): Promise<ConstraintCheckResult> {
546
- return ConstraintChecker.getInstance().checkConstraintsSafe(context, customConfig);
447
+ const result = await ConstraintChecker.getInstance().checkConstraints(context, options?.customConfig ?? null);
448
+ if (options?.onTrace) {
449
+ for (const r of result.ironLaws) options.onTrace(r);
450
+ for (const r of result.guidelines) options.onTrace(r);
451
+ }
452
+ return result;
547
453
  }
548
454
 
549
455
  /**
@@ -0,0 +1,74 @@
1
+ /**
2
+ * governance_presence:治理契约在场守护(studio #302,ADR 2026-08-21 落点模型)
3
+ *
4
+ * 治理契约正本 = AGENTS.md 手写 `PRESERVE:governance` 段(旧模型仓 = CLAUDE.md
5
+ * Governance Rules / HARNESS_CONSTRAINTS 注入段)。PRESERVE 只保「存在」不保「在场」:
6
+ * 段被删除/掏空后 sync-docs 重新生成会静默丢失,此处补「在场」校验。
7
+ *
8
+ * ADR-0001 存在性探测:项目未采用 harness 治理(无 .harness/config.yml)→ skip。
9
+ * 向后兼容:旧模型仓(CLAUDE.md 有治理块、AGENTS.md 无 PRESERVE:governance)→ pass,
10
+ * 不强制迁移;两处都没有才报违规。
11
+ */
12
+
13
+ import { readFileSync } from 'fs';
14
+ import { join } from 'path';
15
+ import { loadRawProjectConfig } from '../../project-config-loader';
16
+ import type { ConstraintCheck } from './types';
17
+
18
+ /** 治理契约 PRESERVE 段名(ADR 落点模型约定) */
19
+ export const GOVERNANCE_PRESERVE_NAME = 'governance';
20
+
21
+ const GOVERNANCE_BEGIN_RE = /^<!-- PRESERVE:governance -->\s*$/m;
22
+
23
+ /** AGENTS.md 是否存在非空 PRESERVE:governance 块(标记须独占一行,与 preserve-block.ts 同语义) */
24
+ export function hasGovernancePreserveBlock(agentsMdPath: string): boolean {
25
+ let content: string;
26
+ try {
27
+ content = readFileSync(agentsMdPath, 'utf-8');
28
+ } catch {
29
+ return false;
30
+ }
31
+ const beginMatch = GOVERNANCE_BEGIN_RE.exec(content);
32
+ if (!beginMatch) return false;
33
+ const rest = content.slice(beginMatch.index + beginMatch[0].length);
34
+ const endIdx = rest.indexOf(`<!-- /PRESERVE:${GOVERNANCE_PRESERVE_NAME} -->`);
35
+ if (endIdx === -1) return false;
36
+ // 块体(首尾标记之间)去空白后非空才算「在场」
37
+ return rest.slice(0, endIdx).trim().length > 0;
38
+ }
39
+
40
+ /** CLAUDE.md 是否存在治理块(与 agents-syncer getGovernanceInfo 同判定,旧模型仓豁免) */
41
+ export function hasClaudeGovernance(claudeMdPath: string): boolean {
42
+ let content: string;
43
+ try {
44
+ content = readFileSync(claudeMdPath, 'utf-8');
45
+ } catch {
46
+ return false;
47
+ }
48
+ return /^##\s+Governance Rules/m.test(content) || content.includes('HARNESS_CONSTRAINTS_START');
49
+ }
50
+
51
+ export const governancePresence: ConstraintCheck = {
52
+ id: 'governance_presence',
53
+ async evaluate(env) {
54
+ const projectPath = env.projectPath;
55
+
56
+ // 存在性探测:无 harness 配置 = 未采用治理约定 → skip(不计 pass/fail)
57
+ let adopted = false;
58
+ try {
59
+ adopted = loadRawProjectConfig(projectPath) !== undefined;
60
+ } catch {
61
+ adopted = false;
62
+ }
63
+ if (!adopted) return 'skip';
64
+
65
+ if (hasGovernancePreserveBlock(join(projectPath, 'AGENTS.md'))) return true;
66
+ if (hasClaudeGovernance(join(projectPath, 'CLAUDE.md'))) return true;
67
+
68
+ console.error(
69
+ '[governance_presence] 治理契约缺失:AGENTS.md 无非空 PRESERVE:governance 段,' +
70
+ '且 CLAUDE.md 无 Governance Rules 块——约束正本静默丢失,请恢复其一'
71
+ );
72
+ return false;
73
+ },
74
+ };
@@ -22,6 +22,7 @@ import { capabilitySync } from './capability-sync';
22
22
  import { contextDocSync } from './context-doc-sync';
23
23
  import { docsFreshness } from './docs-freshness';
24
24
  import { noHardcodedCredentials } from './no-hardcoded-credentials';
25
+ import { governancePresence } from './governance-presence';
25
26
 
26
27
  const CHECKS: ConstraintCheck[] = [
27
28
  // Iron Laws
@@ -35,6 +36,7 @@ const CHECKS: ConstraintCheck[] = [
35
36
  noBypassCheckpoint,
36
37
  capabilitySync,
37
38
  contextDocSync,
39
+ governancePresence,
38
40
  ];
39
41
 
40
42
  const registry = new Map<string, ConstraintCheck>(CHECKS.map(c => [c.id, c]));
@@ -78,4 +80,5 @@ export function registeredCheckCount(): number {
78
80
  return registry.size;
79
81
  }
80
82
 
81
- export type { ConstraintCheck, CheckEnv, CheckOutcome } from './types';
83
+ export { buildCheckEnv } from './types';
84
+ export type { ConstraintCheck, CheckEnv, CheckOutcome, EvidenceProviders } from './types';