@dommaker/harness 0.16.9 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +33 -32
  3. package/bin/harness.js +33 -20
  4. package/dist/cli/commands/check.d.ts +3 -2
  5. package/dist/cli/commands/check.d.ts.map +1 -1
  6. package/dist/cli/commands/check.js +69 -36
  7. package/dist/cli/commands/check.js.map +1 -1
  8. package/dist/cli/commands/constraints-report.d.ts +42 -0
  9. package/dist/cli/commands/constraints-report.d.ts.map +1 -0
  10. package/dist/cli/commands/constraints-report.js +233 -0
  11. package/dist/cli/commands/constraints-report.js.map +1 -0
  12. package/dist/cli/commands/constraints-retire.d.ts +71 -0
  13. package/dist/cli/commands/constraints-retire.d.ts.map +1 -0
  14. package/dist/cli/commands/constraints-retire.js +384 -0
  15. package/dist/cli/commands/constraints-retire.js.map +1 -0
  16. package/dist/cli/commands/constraints.d.ts +1 -1
  17. package/dist/cli/commands/constraints.d.ts.map +1 -1
  18. package/dist/cli/commands/constraints.js +4 -4
  19. package/dist/cli/commands/constraints.js.map +1 -1
  20. package/dist/cli/commands/index.d.ts +2 -1
  21. package/dist/cli/commands/index.d.ts.map +1 -1
  22. package/dist/cli/commands/index.js +10 -4
  23. package/dist/cli/commands/index.js.map +1 -1
  24. package/dist/cli/commands/init.d.ts +20 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -1
  26. package/dist/cli/commands/init.js +81 -59
  27. package/dist/cli/commands/init.js.map +1 -1
  28. package/dist/cli/commands/report.js +4 -4
  29. package/dist/cli/commands/report.js.map +1 -1
  30. package/dist/cli/commands/status.d.ts.map +1 -1
  31. package/dist/cli/commands/status.js +3 -4
  32. package/dist/cli/commands/status.js.map +1 -1
  33. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +5 -1
  34. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  35. package/dist/cli/commands/sync-docs/capabilities-syncer.js +2 -10
  36. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  37. package/dist/cli/commands/validate.d.ts.map +1 -1
  38. package/dist/cli/commands/validate.js +1 -5
  39. package/dist/cli/commands/validate.js.map +1 -1
  40. package/dist/core/constraints/capabilities-parser.d.ts +7 -0
  41. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  42. package/dist/core/constraints/capabilities-parser.js +13 -0
  43. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  44. package/dist/core/constraints/checker.d.ts +13 -5
  45. package/dist/core/constraints/checker.d.ts.map +1 -1
  46. package/dist/core/constraints/checker.js +62 -25
  47. package/dist/core/constraints/checker.js.map +1 -1
  48. package/dist/core/constraints/checkers/capability-sync.d.ts +9 -2
  49. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  50. package/dist/core/constraints/checkers/capability-sync.js +55 -22
  51. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  52. package/dist/core/constraints/checkers/context-doc-sync.d.ts +3 -0
  53. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  54. package/dist/core/constraints/checkers/context-doc-sync.js +8 -2
  55. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  56. package/dist/core/constraints/checkers/docs-freshness.d.ts +1 -0
  57. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  58. package/dist/core/constraints/checkers/docs-freshness.js +39 -0
  59. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  60. package/dist/core/constraints/checkers/index.d.ts +8 -4
  61. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  62. package/dist/core/constraints/checkers/index.js +29 -46
  63. package/dist/core/constraints/checkers/index.js.map +1 -1
  64. package/dist/core/constraints/checkers/iron-flags.d.ts +6 -7
  65. package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
  66. package/dist/core/constraints/checkers/iron-flags.js +10 -17
  67. package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
  68. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts +13 -0
  69. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts.map +1 -0
  70. package/dist/core/constraints/checkers/no-hardcoded-credentials.js +91 -0
  71. package/dist/core/constraints/checkers/no-hardcoded-credentials.js.map +1 -0
  72. package/dist/core/constraints/checkers/types.d.ts +15 -4
  73. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  74. package/dist/core/constraints/checkers/types.js +16 -4
  75. package/dist/core/constraints/checkers/types.js.map +1 -1
  76. package/dist/core/constraints/context-builder.d.ts +6 -3
  77. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  78. package/dist/core/constraints/context-builder.js +29 -20
  79. package/dist/core/constraints/context-builder.js.map +1 -1
  80. package/dist/core/constraints/definitions/guidelines.d.ts +6 -2
  81. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  82. package/dist/core/constraints/definitions/guidelines.js +29 -572
  83. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  84. package/dist/core/constraints/definitions/iron-laws.d.ts +6 -2
  85. package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
  86. package/dist/core/constraints/definitions/iron-laws.js +48 -231
  87. package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
  88. package/dist/core/constraints/definitions/prompts.d.ts +12 -0
  89. package/dist/core/constraints/definitions/prompts.d.ts.map +1 -0
  90. package/dist/core/constraints/definitions/prompts.js +405 -0
  91. package/dist/core/constraints/definitions/prompts.js.map +1 -0
  92. package/dist/core/constraints/definitions.d.ts +15 -9
  93. package/dist/core/constraints/definitions.d.ts.map +1 -1
  94. package/dist/core/constraints/definitions.js +18 -12
  95. package/dist/core/constraints/definitions.js.map +1 -1
  96. package/dist/core/constraints/index.d.ts +3 -2
  97. package/dist/core/constraints/index.d.ts.map +1 -1
  98. package/dist/core/constraints/index.js +8 -2
  99. package/dist/core/constraints/index.js.map +1 -1
  100. package/dist/core/constraints/injection-drift.d.ts +48 -0
  101. package/dist/core/constraints/injection-drift.d.ts.map +1 -0
  102. package/dist/core/constraints/injection-drift.js +140 -0
  103. package/dist/core/constraints/injection-drift.js.map +1 -0
  104. package/dist/core/constraints/injection-renderer.d.ts +23 -0
  105. package/dist/core/constraints/injection-renderer.d.ts.map +1 -0
  106. package/dist/core/constraints/injection-renderer.js +44 -0
  107. package/dist/core/constraints/injection-renderer.js.map +1 -0
  108. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  109. package/dist/core/constraints/interceptor.js +3 -2
  110. package/dist/core/constraints/interceptor.js.map +1 -1
  111. package/dist/core/constraints/usage-report.d.ts +95 -0
  112. package/dist/core/constraints/usage-report.d.ts.map +1 -0
  113. package/dist/core/constraints/usage-report.js +206 -0
  114. package/dist/core/constraints/usage-report.js.map +1 -0
  115. package/dist/core/effective-constraints.d.ts +47 -0
  116. package/dist/core/effective-constraints.d.ts.map +1 -0
  117. package/dist/core/effective-constraints.js +60 -0
  118. package/dist/core/effective-constraints.js.map +1 -0
  119. package/dist/core/index.d.ts +1 -0
  120. package/dist/core/index.d.ts.map +1 -1
  121. package/dist/core/index.js +2 -0
  122. package/dist/core/index.js.map +1 -1
  123. package/dist/core/project-config-loader.d.ts +17 -2
  124. package/dist/core/project-config-loader.d.ts.map +1 -1
  125. package/dist/core/project-config-loader.js +54 -8
  126. package/dist/core/project-config-loader.js.map +1 -1
  127. package/dist/dashboard/data.d.ts +1 -9
  128. package/dist/dashboard/data.d.ts.map +1 -1
  129. package/dist/dashboard/data.js +7 -15
  130. package/dist/dashboard/data.js.map +1 -1
  131. package/dist/dashboard/types.d.ts +13 -1
  132. package/dist/dashboard/types.d.ts.map +1 -1
  133. package/dist/index.d.ts +0 -4
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +1 -13
  136. package/dist/index.js.map +1 -1
  137. package/dist/monitoring/index.d.ts +0 -2
  138. package/dist/monitoring/index.d.ts.map +1 -1
  139. package/dist/monitoring/index.js +0 -2
  140. package/dist/monitoring/index.js.map +1 -1
  141. package/dist/monitoring/knowledge-evolver.d.ts +1 -1
  142. package/dist/monitoring/knowledge-evolver.js +1 -1
  143. package/dist/monitoring/trace-analyzer.d.ts +1 -1
  144. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  145. package/dist/monitoring/trace-analyzer.js +16 -10
  146. package/dist/monitoring/trace-analyzer.js.map +1 -1
  147. package/dist/monitoring/traces.d.ts +2 -1
  148. package/dist/monitoring/traces.d.ts.map +1 -1
  149. package/dist/monitoring/traces.js +5 -3
  150. package/dist/monitoring/traces.js.map +1 -1
  151. package/dist/presets/standard.d.ts +3 -1
  152. package/dist/presets/standard.d.ts.map +1 -1
  153. package/dist/presets/standard.js +18 -8
  154. package/dist/presets/standard.js.map +1 -1
  155. package/dist/types/constraint.d.ts +51 -11
  156. package/dist/types/constraint.d.ts.map +1 -1
  157. package/dist/types/constraint.js +8 -4
  158. package/dist/types/constraint.js.map +1 -1
  159. package/dist/types/index.d.ts +1 -1
  160. package/dist/types/index.d.ts.map +1 -1
  161. package/dist/types/index.js.map +1 -1
  162. package/dist/types/monitoring-types.d.ts +2 -63
  163. package/dist/types/monitoring-types.d.ts.map +1 -1
  164. package/dist/types/monitoring-types.js +2 -2
  165. package/dist/types/project-config.d.ts +18 -1
  166. package/dist/types/project-config.d.ts.map +1 -1
  167. package/dist/types/trace.d.ts +16 -7
  168. package/dist/types/trace.d.ts.map +1 -1
  169. package/dist/types/trace.js +8 -0
  170. package/dist/types/trace.js.map +1 -1
  171. package/package.json +1 -1
  172. package/src/CONTEXT.md +2 -2
  173. package/src/__tests__/checker-extra.test.ts +228 -333
  174. package/src/__tests__/checker.test.ts +87 -724
  175. package/src/__tests__/constraint-doctor.test.ts +5 -5
  176. package/src/__tests__/doctor-extra.test.ts +164 -0
  177. package/src/__tests__/effective-constraints.test.ts +147 -0
  178. package/src/__tests__/extend-exceptions.test.ts +3 -3
  179. package/src/__tests__/iron-laws.test.ts +115 -91
  180. package/src/__tests__/no-hardcoded-credentials.test.ts +90 -0
  181. package/src/__tests__/presets.test.ts +7 -7
  182. package/src/__tests__/project-config-loader-extra.test.ts +4 -4
  183. package/src/__tests__/skip-semantics.test.ts +322 -0
  184. package/src/cli/commands/CONTEXT.md +5 -3
  185. package/src/cli/commands/__tests__/check-drift.test.ts +127 -0
  186. package/src/cli/commands/__tests__/check-skip-output.test.ts +128 -0
  187. package/src/cli/commands/__tests__/check.test.ts +14 -25
  188. package/src/cli/commands/__tests__/constraints-report.test.ts +327 -0
  189. package/src/cli/commands/__tests__/constraints-retire.test.ts +283 -0
  190. package/src/cli/commands/__tests__/init-injection.test.ts +234 -0
  191. package/src/cli/commands/__tests__/status-extra.test.ts +11 -11
  192. package/src/cli/commands/__tests__/status.test.ts +1 -1
  193. package/src/cli/commands/check.ts +72 -38
  194. package/src/cli/commands/constraints-report.ts +234 -0
  195. package/src/cli/commands/constraints-retire.ts +443 -0
  196. package/src/cli/commands/constraints.ts +5 -5
  197. package/src/cli/commands/index.ts +2 -1
  198. package/src/cli/commands/init.ts +94 -66
  199. package/src/cli/commands/report.ts +6 -6
  200. package/src/cli/commands/status.ts +3 -4
  201. package/src/cli/commands/sync-docs/capabilities-syncer.ts +5 -7
  202. package/src/cli/commands/validate.ts +1 -5
  203. package/src/core/CONTEXT.md +7 -4
  204. package/src/core/constraints/__tests__/injection-drift.test.ts +164 -0
  205. package/src/core/constraints/__tests__/interceptor.test.ts +4 -0
  206. package/src/core/constraints/capabilities-parser.ts +13 -0
  207. package/src/core/constraints/checker.ts +73 -28
  208. package/src/core/constraints/checkers/capability-sync.ts +72 -29
  209. package/src/core/constraints/checkers/context-doc-sync.ts +9 -2
  210. package/src/core/constraints/checkers/docs-freshness.ts +43 -0
  211. package/src/core/constraints/checkers/index.ts +38 -71
  212. package/src/core/constraints/checkers/iron-flags.ts +13 -49
  213. package/src/core/constraints/checkers/no-hardcoded-credentials.ts +93 -0
  214. package/src/core/constraints/checkers/types.ts +27 -5
  215. package/src/core/constraints/context-builder.ts +28 -19
  216. package/src/core/constraints/definitions/guidelines.ts +29 -597
  217. package/src/core/constraints/definitions/iron-laws.ts +48 -239
  218. package/src/core/constraints/definitions/prompts.ts +419 -0
  219. package/src/core/constraints/definitions.ts +18 -11
  220. package/src/core/constraints/index.ts +10 -1
  221. package/src/core/constraints/injection-drift.ts +139 -0
  222. package/src/core/constraints/injection-renderer.ts +44 -0
  223. package/src/core/constraints/interceptor.ts +3 -2
  224. package/src/core/constraints/usage-report.ts +254 -0
  225. package/src/core/effective-constraints.ts +85 -0
  226. package/src/core/index.ts +4 -1
  227. package/src/core/project-config-loader.ts +58 -9
  228. package/src/dashboard/__tests__/data.test.ts +5 -12
  229. package/src/dashboard/data.ts +9 -19
  230. package/src/dashboard/types.ts +18 -1
  231. package/src/index.ts +0 -12
  232. package/src/knowledge/CONTEXT.md +2 -2
  233. package/src/monitoring/CONTEXT.md +3 -4
  234. package/src/monitoring/index.ts +0 -2
  235. package/src/monitoring/knowledge-evolver.ts +1 -1
  236. package/src/monitoring/trace-analyzer.ts +15 -10
  237. package/src/monitoring/traces.ts +10 -6
  238. package/src/presets/__tests__/standard.test.ts +11 -9
  239. package/src/presets/standard.ts +24 -15
  240. package/src/types/constraint.ts +59 -14
  241. package/src/types/index.ts +1 -3
  242. package/src/types/monitoring-types.ts +2 -84
  243. package/src/types/project-config.ts +21 -1
  244. package/src/types/trace.ts +18 -7
  245. package/src/verification/CONTEXT.md +1 -1
  246. package/dist/cli/commands/flow.d.ts +0 -22
  247. package/dist/cli/commands/flow.d.ts.map +0 -1
  248. package/dist/cli/commands/flow.js +0 -275
  249. package/dist/cli/commands/flow.js.map +0 -1
  250. package/dist/constraints/lifecycle-runner.d.ts +0 -52
  251. package/dist/constraints/lifecycle-runner.d.ts.map +0 -1
  252. package/dist/constraints/lifecycle-runner.js +0 -151
  253. package/dist/constraints/lifecycle-runner.js.map +0 -1
  254. package/dist/constraints/registry.d.ts +0 -27
  255. package/dist/constraints/registry.d.ts.map +0 -1
  256. package/dist/constraints/registry.js +0 -128
  257. package/dist/constraints/registry.js.map +0 -1
  258. package/dist/constraints/types.d.ts +0 -53
  259. package/dist/constraints/types.d.ts.map +0 -1
  260. package/dist/constraints/types.js +0 -8
  261. package/dist/constraints/types.js.map +0 -1
  262. package/dist/core/constraints/checkers/always-pass.d.ts +0 -17
  263. package/dist/core/constraints/checkers/always-pass.d.ts.map +0 -1
  264. package/dist/core/constraints/checkers/always-pass.js +0 -23
  265. package/dist/core/constraints/checkers/always-pass.js.map +0 -1
  266. package/dist/core/constraints/checkers/guideline-flags.d.ts +0 -9
  267. package/dist/core/constraints/checkers/guideline-flags.d.ts.map +0 -1
  268. package/dist/core/constraints/checkers/guideline-flags.js +0 -13
  269. package/dist/core/constraints/checkers/guideline-flags.js.map +0 -1
  270. package/dist/core/constraints/checkers/no-any-type.d.ts +0 -6
  271. package/dist/core/constraints/checkers/no-any-type.d.ts.map +0 -1
  272. package/dist/core/constraints/checkers/no-any-type.js +0 -42
  273. package/dist/core/constraints/checkers/no-any-type.js.map +0 -1
  274. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts +0 -6
  275. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts.map +0 -1
  276. package/dist/core/constraints/checkers/no-excuse-patterns.js +0 -25
  277. package/dist/core/constraints/checkers/no-excuse-patterns.js.map +0 -1
  278. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts +0 -6
  279. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts.map +0 -1
  280. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js +0 -34
  281. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js.map +0 -1
  282. package/dist/core/constraints/checkers/no-performative-agreement.d.ts +0 -8
  283. package/dist/core/constraints/checkers/no-performative-agreement.d.ts.map +0 -1
  284. package/dist/core/constraints/checkers/no-performative-agreement.js +0 -23
  285. package/dist/core/constraints/checkers/no-performative-agreement.js.map +0 -1
  286. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts +0 -6
  287. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts.map +0 -1
  288. package/dist/core/constraints/checkers/no-simplification-without-approval.js +0 -30
  289. package/dist/core/constraints/checkers/no-simplification-without-approval.js.map +0 -1
  290. package/dist/core/constraints/checkers/test-coverage.d.ts +0 -6
  291. package/dist/core/constraints/checkers/test-coverage.d.ts.map +0 -1
  292. package/dist/core/constraints/checkers/test-coverage.js +0 -33
  293. package/dist/core/constraints/checkers/test-coverage.js.map +0 -1
  294. package/dist/core/constraints/checkers/yagni.d.ts +0 -8
  295. package/dist/core/constraints/checkers/yagni.d.ts.map +0 -1
  296. package/dist/core/constraints/checkers/yagni.js +0 -96
  297. package/dist/core/constraints/checkers/yagni.js.map +0 -1
  298. package/dist/core/constraints/definitions/tips.d.ts +0 -7
  299. package/dist/core/constraints/definitions/tips.d.ts.map +0 -1
  300. package/dist/core/constraints/definitions/tips.js +0 -62
  301. package/dist/core/constraints/definitions/tips.js.map +0 -1
  302. package/dist/evolution/auto-evolve.d.ts +0 -35
  303. package/dist/evolution/auto-evolve.d.ts.map +0 -1
  304. package/dist/evolution/auto-evolve.js +0 -64
  305. package/dist/evolution/auto-evolve.js.map +0 -1
  306. package/dist/evolution/index.d.ts +0 -2
  307. package/dist/evolution/index.d.ts.map +0 -1
  308. package/dist/evolution/index.js +0 -18
  309. package/dist/evolution/index.js.map +0 -1
  310. package/dist/monitoring/constraint-evolver.d.ts +0 -120
  311. package/dist/monitoring/constraint-evolver.d.ts.map +0 -1
  312. package/dist/monitoring/constraint-evolver.js +0 -410
  313. package/dist/monitoring/constraint-evolver.js.map +0 -1
  314. package/src/__tests__/constraint-evolver.test.ts +0 -405
  315. package/src/__tests__/doctor-evolver.test.ts +0 -381
  316. package/src/cli/commands/__tests__/flow.test.ts +0 -343
  317. package/src/cli/commands/flow.ts +0 -277
  318. package/src/constraints/__tests__/lifecycle-runner.test.ts +0 -246
  319. package/src/constraints/__tests__/registry.test.ts +0 -196
  320. package/src/constraints/lifecycle-runner.ts +0 -184
  321. package/src/constraints/registry.ts +0 -134
  322. package/src/constraints/types.ts +0 -59
  323. package/src/core/constraints/checkers/always-pass.ts +0 -22
  324. package/src/core/constraints/checkers/guideline-flags.ts +0 -30
  325. package/src/core/constraints/checkers/no-any-type.ts +0 -42
  326. package/src/core/constraints/checkers/no-excuse-patterns.ts +0 -24
  327. package/src/core/constraints/checkers/no-fuzzy-completion-claim.ts +0 -33
  328. package/src/core/constraints/checkers/no-performative-agreement.ts +0 -22
  329. package/src/core/constraints/checkers/no-simplification-without-approval.ts +0 -29
  330. package/src/core/constraints/checkers/test-coverage.ts +0 -35
  331. package/src/core/constraints/checkers/yagni.ts +0 -65
  332. package/src/core/constraints/definitions/tips.ts +0 -63
  333. package/src/evolution/CONTEXT.md +0 -22
  334. package/src/evolution/__tests__/auto-evolve.test.ts +0 -319
  335. package/src/evolution/auto-evolve.ts +0 -98
  336. package/src/evolution/index.ts +0 -1
  337. package/src/monitoring/constraint-evolver.ts +0 -501
@@ -0,0 +1,90 @@
1
+ /**
2
+ * no_hardcoded_credentials checker 测试(ADR-0001)
3
+ *
4
+ * 注意:测试夹具中的假凭证一律用字符串拼接构造,
5
+ * 避免本文件自身被凭证扫描(含 harness 自身 staged diff)命中。
6
+ */
7
+
8
+ import { describe, it, expect } from '@jest/globals';
9
+ import {
10
+ noHardcodedCredentials,
11
+ containsHardcodedCredential,
12
+ } from '../core/constraints/checkers/no-hardcoded-credentials';
13
+ import type { CheckEnv } from '../core/constraints/checkers';
14
+
15
+ function makeEnv(stagedDiff: string): CheckEnv {
16
+ return {
17
+ context: { operation: 'commit', projectPath: '/nonexistent' },
18
+ projectPath: '/nonexistent',
19
+ stagedDiff: async () => stagedDiff,
20
+ stagedDiffNames: async () => '',
21
+ srcScan: () => [],
22
+ };
23
+ }
24
+
25
+ const AWS_KEY = 'AKIA' + '1234567890ABCDEF';
26
+ const PEM_HEADER = '-----BEGIN ' + 'RSA PRIVATE KEY-----';
27
+ const PASSWORD_ASSIGN = 'const dbPass' + 'word = "sup3r-s3cret-value";';
28
+
29
+ describe('containsHardcodedCredential', () => {
30
+ it('命中 PEM 私钥头', () => {
31
+ expect(containsHardcodedCredential(PEM_HEADER + '\nMIIE...')).toBe(true);
32
+ });
33
+
34
+ it('命中 AWS Access Key', () => {
35
+ expect(containsHardcodedCredential(`const key = "${AWS_KEY}";`)).toBe(true);
36
+ });
37
+
38
+ it('命中硬编码 password 赋值', () => {
39
+ expect(containsHardcodedCredential(PASSWORD_ASSIGN)).toBe(true);
40
+ });
41
+
42
+ it('命中硬编码 api_key 赋值', () => {
43
+ expect(containsHardcodedCredential('api' + '_key: "abcdef123456789"')).toBe(true);
44
+ });
45
+
46
+ it('豁免环境变量引用', () => {
47
+ expect(
48
+ containsHardcodedCredential('const pass' + 'word = process.env.DB_PASSWORD;')
49
+ ).toBe(false);
50
+ });
51
+
52
+ it('豁免占位符值', () => {
53
+ expect(
54
+ containsHardcodedCredential('const pass' + 'word = "your-password-here";')
55
+ ).toBe(false);
56
+ });
57
+
58
+ it('普通代码不误报', () => {
59
+ expect(
60
+ containsHardcodedCredential('export function add(a: number, b: number) { return a + b; }')
61
+ ).toBe(false);
62
+ });
63
+ });
64
+
65
+ describe('noHardcodedCredentials checker', () => {
66
+ it('staged diff 新增行命中凭证 → fail', async () => {
67
+ const diff = [
68
+ 'diff --git a/src/config.ts b/src/config.ts',
69
+ '--- a/src/config.ts',
70
+ '+++ b/src/config.ts',
71
+ '@@ -0,0 +1 @@',
72
+ '+' + PASSWORD_ASSIGN,
73
+ ].join('\n');
74
+ expect(await noHardcodedCredentials.evaluate(makeEnv(diff))).toBe(false);
75
+ });
76
+
77
+ it('staged diff 仅删除行含凭证 → pass(删除凭证是好事)', async () => {
78
+ const diff = ['--- a/src/config.ts', '+++ /dev/null', '-' + PASSWORD_ASSIGN].join('\n');
79
+ expect(await noHardcodedCredentials.evaluate(makeEnv(diff))).toBe(true);
80
+ });
81
+
82
+ it('staged diff 无凭证 → pass', async () => {
83
+ const diff = '+++ b/src/a.ts\n+export const x = 1;\n';
84
+ expect(await noHardcodedCredentials.evaluate(makeEnv(diff))).toBe(true);
85
+ });
86
+
87
+ it('staged diff 为空且无可读变更文件 → pass', async () => {
88
+ expect(await noHardcodedCredentials.evaluate(makeEnv(''))).toBe(true);
89
+ });
90
+ });
@@ -11,7 +11,7 @@ describe('Presets', () => {
11
11
  expect(STRICT_PRESET.name).toBe('strict');
12
12
  expect(STRICT_PRESET.ironLaws).toBeNull();
13
13
  expect(STRICT_PRESET.guidelines).toBeNull();
14
- expect(STRICT_PRESET.tips).toBeNull();
14
+ expect(STRICT_PRESET.prompts).toBeNull();
15
15
  });
16
16
  });
17
17
 
@@ -20,7 +20,7 @@ describe('Presets', () => {
20
20
  expect(STANDARD_PRESET.name).toBe('standard');
21
21
  expect(STANDARD_PRESET.ironLaws).toBeNull();
22
22
  expect(STANDARD_PRESET.guidelines).toBeNull();
23
- expect(STANDARD_PRESET.tips).toBeNull();
23
+ expect(STANDARD_PRESET.prompts).toBeNull();
24
24
  });
25
25
  });
26
26
 
@@ -29,15 +29,15 @@ describe('Presets', () => {
29
29
  expect(RELAXED_PRESET.name).toBe('relaxed');
30
30
  expect(RELAXED_PRESET.ironLaws).not.toBeNull();
31
31
  expect(RELAXED_PRESET.guidelines).not.toBeNull();
32
- expect(RELAXED_PRESET.tips).toEqual([]);
32
+ expect(RELAXED_PRESET.prompts).toEqual([]);
33
33
  });
34
34
 
35
- it('应该包含 no_bypass_checkpoint', () => {
36
- expect(RELAXED_PRESET.ironLaws).toContain('no_bypass_checkpoint');
35
+ it('应该包含 no_completion_without_verification', () => {
36
+ expect(RELAXED_PRESET.ironLaws).toContain('no_completion_without_verification');
37
37
  });
38
38
 
39
- it('应该包含 no_self_approval', () => {
40
- expect(RELAXED_PRESET.ironLaws).toContain('no_self_approval');
39
+ it('应该包含 no_bypass_checkpoint', () => {
40
+ expect(RELAXED_PRESET.guidelines).toContain('no_bypass_checkpoint');
41
41
  });
42
42
  });
43
43
 
@@ -29,9 +29,9 @@ describe('ProjectConfigLoader - 补充覆盖', () => {
29
29
  path.join(harnessDir, 'config.yml'),
30
30
  `
31
31
  constraints:
32
- no_any_type:
32
+ legacy_disabled_a:
33
33
  enabled: false
34
- no_self_approval:
34
+ legacy_disabled_b:
35
35
  enabled: false
36
36
  `
37
37
  );
@@ -40,8 +40,8 @@ constraints:
40
40
  loader.load();
41
41
  const merged = loader.mergeConstraints();
42
42
 
43
- expect(merged.disabled).toContain('no_any_type');
44
- expect(merged.disabled).toContain('no_self_approval');
43
+ expect(merged.disabled).toContain('legacy_disabled_a');
44
+ expect(merged.disabled).toContain('legacy_disabled_b');
45
45
  });
46
46
  });
47
47
 
@@ -0,0 +1,322 @@
1
+ /**
2
+ * skip 三态语义测试(ADR-0001 P4)
3
+ *
4
+ * 覆盖:
5
+ * - 三条存在性探测(capability_sync / context_doc_sync / docs_freshness)有/无约定两路
6
+ * - flag 型铁律 undefined → skip(显式 false 仍 fail,true 仍 pass)
7
+ * - Iron Law skip 不阻断 checkConstraints
8
+ * - detectTrigger 代码文件 → 附加 code_implementation 推断
9
+ * - trace 记录 result: 'skip';TraceAnalyzer 的 pass/fail 率分母不计 skip
10
+ */
11
+
12
+ import { describe, it, expect, beforeAll, afterAll, afterEach } from '@jest/globals';
13
+ import { ConstraintChecker } from '../core/constraints/checker';
14
+ import { detectTrigger, buildConstraintContext } from '../core/constraints/context-builder';
15
+ import { IRON_LAWS } from '../core/constraints/definitions';
16
+ import { TraceAnalyzer } from '../monitoring/trace-analyzer';
17
+ import type { Constraint, ConstraintContext } from '../types/constraint';
18
+ import type { ExecutionTrace } from '../types/trace';
19
+ import * as fs from 'fs';
20
+ import * as path from 'path';
21
+ import { execSync } from 'child_process';
22
+
23
+ const checker = ConstraintChecker.getInstance();
24
+
25
+ function makeConstraint(
26
+ id: string,
27
+ level: 'iron_law' | 'guideline',
28
+ trigger: string = 'commit'
29
+ ): Constraint {
30
+ return { id, kind: 'check', level, rule: 'TEST', message: 'test', trigger, enforcement: 'test' };
31
+ }
32
+
33
+ describe('skip 三态语义(ADR-0001)', () => {
34
+ const tempDir = path.join(process.cwd(), 'temp-test-skip-semantics');
35
+
36
+ beforeAll(() => {
37
+ fs.mkdirSync(tempDir, { recursive: true });
38
+ execSync('git init', { cwd: tempDir });
39
+ execSync('git config user.email "test@test.com"', { cwd: tempDir });
40
+ execSync('git config user.name "Test"', { cwd: tempDir });
41
+ fs.writeFileSync(path.join(tempDir, 'initial.txt'), 'initial');
42
+ execSync('git add .', { cwd: tempDir });
43
+ execSync('git commit -m "init"', { cwd: tempDir });
44
+ });
45
+
46
+ afterAll(() => {
47
+ try {
48
+ fs.rmSync(tempDir, { recursive: true, force: true });
49
+ } catch {
50
+ // ignore
51
+ }
52
+ });
53
+
54
+ afterEach(() => {
55
+ // 复位 trace recorder,避免泄漏到其他用例
56
+ checker.setTraceRecorder({ record: () => undefined });
57
+ });
58
+
59
+ describe('capability_sync 存在性探测', () => {
60
+ it('无 CAPABILITIES.md(未采用约定)→ skip,不计 fail', async () => {
61
+ const dir = path.join(tempDir, 'cap-skip');
62
+ fs.mkdirSync(dir, { recursive: true });
63
+
64
+ const result = await checker.check(makeConstraint('capability_sync', 'guideline'), {
65
+ operation: 'commit',
66
+ projectPath: dir,
67
+ } as ConstraintContext);
68
+
69
+ expect(result.skipped).toBe(true);
70
+ expect(result.satisfied).toBe(true); // fail-open:skip 不产生违规
71
+
72
+ fs.rmSync(dir, { recursive: true, force: true });
73
+ });
74
+
75
+ it('有 CAPABILITIES.md(采用约定)→ 正常评估,非 skip', async () => {
76
+ const dir = path.join(tempDir, 'cap-adopted');
77
+ fs.mkdirSync(dir, { recursive: true });
78
+ // 散文文档(无表格)历史放行;此处只验证"进入评估"而非 skip
79
+ fs.writeFileSync(path.join(dir, 'CAPABILITIES.md'), '# Capabilities\n\n- Feature: test');
80
+
81
+ const result = await checker.check(makeConstraint('capability_sync', 'guideline'), {
82
+ operation: 'commit',
83
+ projectPath: dir,
84
+ } as ConstraintContext);
85
+
86
+ expect(result.skipped).toBeUndefined();
87
+ expect(result.satisfied).toBe(true);
88
+
89
+ fs.rmSync(dir, { recursive: true, force: true });
90
+ });
91
+ });
92
+
93
+ describe('context_doc_sync 存在性探测', () => {
94
+ it('无 context_files 配置(未采用约定)→ skip', async () => {
95
+ const dir = path.join(tempDir, 'ctx-skip');
96
+ fs.mkdirSync(dir, { recursive: true });
97
+
98
+ const result = await checker.check(makeConstraint('context_doc_sync', 'guideline'), {
99
+ operation: 'module_modification',
100
+ projectPath: dir,
101
+ } as ConstraintContext);
102
+
103
+ expect(result.skipped).toBe(true);
104
+ expect(result.satisfied).toBe(true);
105
+
106
+ fs.rmSync(dir, { recursive: true, force: true });
107
+ });
108
+
109
+ it('配置了 required_dirs 且缺少 CONTEXT.md → fail(非 skip)', async () => {
110
+ const dir = path.join(tempDir, 'ctx-fail');
111
+ fs.mkdirSync(path.join(dir, '.harness'), { recursive: true });
112
+ fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
113
+
114
+ const yaml = require('js-yaml');
115
+ fs.writeFileSync(
116
+ path.join(dir, '.harness', 'config.yml'),
117
+ yaml.dump({ governance: { context_files: { enabled: true, required_dirs: ['src'] } } })
118
+ );
119
+
120
+ const result = await checker.check(makeConstraint('context_doc_sync', 'guideline'), {
121
+ operation: 'module_modification',
122
+ projectPath: dir,
123
+ } as ConstraintContext);
124
+
125
+ expect(result.skipped).toBeUndefined();
126
+ expect(result.satisfied).toBe(false);
127
+
128
+ fs.rmSync(dir, { recursive: true, force: true });
129
+ });
130
+ });
131
+
132
+ describe('docs_freshness 存在性探测', () => {
133
+ it('无任何 freshness 配置/目标 → skip', async () => {
134
+ const dir = path.join(tempDir, 'fresh-skip');
135
+ fs.mkdirSync(dir, { recursive: true });
136
+
137
+ const result = await checker.check(makeConstraint('docs_freshness', 'iron_law', 'file_modification'), {
138
+ operation: 'file_modification',
139
+ projectPath: dir,
140
+ } as ConstraintContext);
141
+
142
+ expect(result.skipped).toBe(true);
143
+ expect(result.satisfied).toBe(true);
144
+
145
+ fs.rmSync(dir, { recursive: true, force: true });
146
+ });
147
+
148
+ it('有 CAPABILITIES.md 目标 → 正常评估(幽灵条目仍 fail,非 skip)', async () => {
149
+ const dir = path.join(tempDir, 'fresh-adopted');
150
+ fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
151
+ fs.writeFileSync(
152
+ path.join(dir, 'CAPABILITIES.md'),
153
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| ghost | src/ghost.ts | 不存在 |'
154
+ );
155
+
156
+ const result = await checker.check(makeConstraint('docs_freshness', 'iron_law', 'file_modification'), {
157
+ operation: 'file_modification',
158
+ projectPath: dir,
159
+ } as ConstraintContext);
160
+
161
+ expect(result.skipped).toBeUndefined();
162
+ expect(result.satisfied).toBe(false);
163
+
164
+ fs.rmSync(dir, { recursive: true, force: true });
165
+ });
166
+ });
167
+
168
+ describe('flag 型铁律:undefined → skip', () => {
169
+ it('hasVerificationEvidence undefined → skip;false → fail;true → pass', async () => {
170
+ const law = IRON_LAWS['no_completion_without_verification'];
171
+
172
+ const skipped = await checker.check(law, { operation: 'code_implementation' });
173
+ expect(skipped.skipped).toBe(true);
174
+ expect(skipped.satisfied).toBe(true);
175
+
176
+ const failed = await checker.check(law, {
177
+ operation: 'code_implementation',
178
+ hasVerificationEvidence: false,
179
+ });
180
+ expect(failed.skipped).toBeUndefined();
181
+ expect(failed.satisfied).toBe(false);
182
+
183
+ const passed = await checker.check(law, {
184
+ operation: 'code_implementation',
185
+ hasVerificationEvidence: true,
186
+ });
187
+ expect(passed.skipped).toBeUndefined();
188
+ expect(passed.satisfied).toBe(true);
189
+ });
190
+
191
+ it('hasSingleTask / hasRequirement undefined → skip', async () => {
192
+ const single = await checker.check(IRON_LAWS['incremental_progress'], {
193
+ operation: 'code_implementation',
194
+ });
195
+ expect(single.skipped).toBe(true);
196
+
197
+ const req = await checker.check(IRON_LAWS['no_implementation_without_requirement'], {
198
+ operation: 'code_implementation',
199
+ });
200
+ expect(req.skipped).toBe(true);
201
+ });
202
+
203
+ it('Iron Law skip 不阻断 checkConstraints', async () => {
204
+ // 三个 flag 全部未接线(CLI pre-commit 路径的典型形态)
205
+ const result = await checker.checkConstraints({
206
+ operation: 'code_implementation',
207
+ projectPath: path.join(tempDir, 'empty'),
208
+ });
209
+
210
+ expect(result.passed).toBe(true);
211
+ const skippedIds = result.ironLaws.filter(r => r.skipped).map(r => r.id);
212
+ expect(skippedIds).toEqual(
213
+ expect.arrayContaining([
214
+ 'no_completion_without_verification',
215
+ 'incremental_progress',
216
+ 'no_implementation_without_requirement',
217
+ ])
218
+ );
219
+ });
220
+
221
+ it('显式 false 仍按 fail 阻断(skip 语义不改变 fail-closed)', async () => {
222
+ await expect(
223
+ checker.checkConstraints({
224
+ operation: 'code_implementation',
225
+ hasVerificationEvidence: false,
226
+ hasSingleTask: true,
227
+ hasRequirement: true,
228
+ })
229
+ ).rejects.toThrow();
230
+ });
231
+ });
232
+
233
+ describe('detectTrigger 补 code_implementation 推断', () => {
234
+ const triggerDir = path.join(tempDir, 'trigger-probe');
235
+
236
+ beforeAll(() => {
237
+ // src/ 下有已提交 .ts 文件 → detectSourceRoots 命中 'src'
238
+ fs.mkdirSync(path.join(triggerDir, 'src'), { recursive: true });
239
+ fs.writeFileSync(path.join(triggerDir, 'src', 'existing.ts'), 'export const x = 1;');
240
+ execSync('git init', { cwd: triggerDir });
241
+ execSync('git config user.email "test@test.com"', { cwd: triggerDir });
242
+ execSync('git config user.name "Test"', { cwd: triggerDir });
243
+ execSync('git add .', { cwd: triggerDir });
244
+ execSync('git commit -m "init"', { cwd: triggerDir });
245
+ });
246
+
247
+ it('源码根内代码变更 → [module_modification, code_implementation]', () => {
248
+ const result = detectTrigger(['src/existing.ts'], { projectPath: triggerDir });
249
+ expect(result).toEqual(['module_modification', 'code_implementation']);
250
+ });
251
+
252
+ it('纯文档/配置变更 → 仅 file_modification,不附加 code_implementation', () => {
253
+ const result = detectTrigger(['README.md', 'docs/design.md', '.harness/config.yml'], {
254
+ projectPath: triggerDir,
255
+ });
256
+ expect(result).toBe('file_modification');
257
+ });
258
+
259
+ it('源码根外的代码文件 → file_modification + code_implementation', () => {
260
+ const result = detectTrigger(['scripts/build.js'], { projectPath: triggerDir });
261
+ expect(result).toEqual(['file_modification', 'code_implementation']);
262
+ });
263
+
264
+ it('仅测试变更 → test_creation + code_implementation', () => {
265
+ const result = detectTrigger(['src/__tests__/existing.test.ts'], { projectPath: triggerDir });
266
+ expect(result).toEqual(['test_creation', 'code_implementation']);
267
+ });
268
+
269
+ it('显式 trigger 优先,不做推断', () => {
270
+ const result = detectTrigger(['src/existing.ts'], {
271
+ trigger: 'design_request',
272
+ projectPath: triggerDir,
273
+ });
274
+ expect(result).toBe('design_request');
275
+ });
276
+
277
+ it('buildConstraintContext:代码变更 → operation 主推断 + extraTriggers 携带 code_implementation', async () => {
278
+ execSync('git add src/existing.ts', { cwd: triggerDir });
279
+ fs.writeFileSync(path.join(triggerDir, 'src', 'existing.ts'), 'export const x = 2;');
280
+ execSync('git add src/existing.ts', { cwd: triggerDir });
281
+
282
+ const ctx = await buildConstraintContext({ projectPath: triggerDir, staged: true });
283
+
284
+ expect(ctx.operation).toBe('module_modification');
285
+ expect(ctx.extraTriggers).toEqual(['code_implementation']);
286
+ });
287
+ });
288
+
289
+ describe('trace 的 skip 形态', () => {
290
+ it('recordTrace:skip 记录为 result: skip', async () => {
291
+ const records: ExecutionTrace[] = [];
292
+ checker.setTraceRecorder({ record: (t) => records.push(t) });
293
+
294
+ await checker.checkConstraints({
295
+ operation: 'code_implementation',
296
+ projectPath: path.join(tempDir, 'empty'),
297
+ });
298
+
299
+ const skipTraces = records.filter(t => t.result === 'skip');
300
+ expect(skipTraces.map(t => t.constraintId)).toEqual(
301
+ expect.arrayContaining(['incremental_progress', 'no_implementation_without_requirement'])
302
+ );
303
+ });
304
+
305
+ it('TraceAnalyzer:skip 单独计数,不计入 pass/fail 率分母', () => {
306
+ const analyzer = new TraceAnalyzer(null as any);
307
+ const traces: ExecutionTrace[] = [
308
+ { constraintId: 'c1', level: 'iron_law', timestamp: 1, result: 'pass' },
309
+ { constraintId: 'c1', level: 'iron_law', timestamp: 2, result: 'skip' },
310
+ { constraintId: 'c1', level: 'iron_law', timestamp: 3, result: 'skip' },
311
+ { constraintId: 'c1', level: 'iron_law', timestamp: 4, result: 'fail' },
312
+ ];
313
+
314
+ const [summary] = analyzer.summarize(traces);
315
+ expect(summary.totalChecks).toBe(4);
316
+ expect(summary.skipCount).toBe(2);
317
+ // 分母 = 4 - 2 = 2(仅实际评估次数)
318
+ expect(summary.passRate).toBe(0.5);
319
+ expect(summary.failRate).toBe(0.5);
320
+ });
321
+ });
322
+ });
@@ -1,10 +1,12 @@
1
1
  # cli/commands/
2
2
 
3
3
  ## 职责
4
- CLI 子命令实现:25 个命令覆盖约束检查/门禁验证/文档同步/知识管理/失败诊断/发布/会话分析等。
4
+ CLI 子命令实现:24 个顶层命令 + constraints 治理子命令 report/retire(共 26 个命令文件),覆盖约束检查/门禁验证/文档同步/知识管理/失败诊断/发布/会话分析等。
5
5
 
6
- ## 核心导出(25 命令)
7
- check / validate / passes-gate / init / report / status / flow / spec / acceptance / performance / security / contract / review / command / sync-docs / knowledge / sdd / failure / posteval-plan / release / analyze-sessions / update-user-model / constraints / doc-freshness-check / spec-baseline-check
6
+ ## 核心导出(24 顶层命令)
7
+ check / validate / passes-gate / init / report / status / spec / acceptance / performance / security / contract / review / command / sync-docs / knowledge / sdd / failure / posteval-plan / release / analyze-sessions / update-user-model / constraints / doc-freshness-check / spec-baseline-check
8
+
9
+ `constraints` 下挂治理子命令:`constraints report`(使用统计 + 退役候选诊断 + 配置健康 + 注入漂移,`--export` 脱敏)、`constraints retire`(交互选择 + 人确认退役,config.yml retired 元数据 + KnowledgeStore 沉淀 + CLAUDE.md 注入段同步)。
8
10
 
9
11
  ## 依赖关系
10
12
  - 依赖 `src/core/constraints/` 约束引擎
@@ -0,0 +1,127 @@
1
+ /**
2
+ * check 命令注入漂移警告测试(ADR-0001 决策 7,P6)
3
+ *
4
+ * 警告不阻断:漂移项目跑 check 仍 0 退出;版本漂移单独显眼警告;
5
+ * 无漂移零输出。使用真实临时目录(漂移检测读真实 fs),
6
+ * 仅 mock checker(直接判过)与 git 调用。
7
+ */
8
+
9
+ import * as fs from 'fs';
10
+ import * as os from 'os';
11
+ import * as path from 'path';
12
+ import { check } from '../check';
13
+ import { constraintChecker } from '../../../core/constraints/checker';
14
+ import { renderConstraintsSection } from '../../../core/constraints/injection-renderer';
15
+ import { getEffectiveConstraints } from '../../../core/effective-constraints';
16
+
17
+ jest.mock('../../../core/constraints/checker', () => ({
18
+ constraintChecker: {
19
+ setCustomConfig: jest.fn(),
20
+ setTraceRecorder: jest.fn(),
21
+ checkConstraints: jest.fn(),
22
+ },
23
+ }));
24
+
25
+ jest.mock('../../../utils/exec', () => ({
26
+ execAsync: jest.fn().mockResolvedValue({ stdout: '', stderr: '' }),
27
+ }));
28
+
29
+ jest.mock('child_process', () => ({
30
+ exec: jest.fn(),
31
+ execSync: jest.fn(() => Buffer.from('')),
32
+ }));
33
+
34
+ const mockChecker = constraintChecker as jest.Mocked<typeof constraintChecker>;
35
+
36
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
37
+ const REAL_VERSION = require('../../../../package.json').version as string;
38
+
39
+ function makeTmpProject(): string {
40
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'harness-check-drift-test-'));
41
+ }
42
+
43
+ function writeClaudeMd(root: string, version: string): void {
44
+ const section =
45
+ '## Governance Rules\n' + renderConstraintsSection(getEffectiveConstraints(root), version);
46
+ fs.writeFileSync(path.join(root, 'CLAUDE.md'), `# Test Project\n\n${section}`, 'utf-8');
47
+ }
48
+
49
+ describe('check 命令注入漂移警告', () => {
50
+ let consoleSpy: jest.SpyInstance;
51
+ let exitSpy: jest.SpyInstance;
52
+
53
+ beforeEach(() => {
54
+ jest.clearAllMocks();
55
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
56
+ exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
57
+ mockChecker.checkConstraints.mockResolvedValue({
58
+ passed: true,
59
+ ironLaws: [],
60
+ guidelines: [],
61
+ tips: [],
62
+ warningCount: 0,
63
+ tipCount: 0,
64
+ });
65
+ });
66
+
67
+ afterEach(() => {
68
+ consoleSpy.mockRestore();
69
+ exitSpy.mockRestore();
70
+ });
71
+
72
+ const outputText = () => consoleSpy.mock.calls.map(c => String(c[0])).join('\n');
73
+
74
+ it('版本漂移:黄色警告块 + ⚠️⚠️ 版本行,但不阻断(exit 未调用,仍判通过)', async () => {
75
+ const root = makeTmpProject();
76
+ writeClaudeMd(root, '0.0.1-old');
77
+
78
+ await check({ preset: 'standard', staged: false, projectPath: root });
79
+
80
+ const output = outputText();
81
+ expect(output).toContain('约束注入漂移');
82
+ expect(output).toContain('⚠️⚠️');
83
+ expect(output).toContain('agent 上下文中的规则与已安装 harness 版本不一致');
84
+ expect(output).toContain('0.0.1-old');
85
+ expect(output).toContain('npx harness init');
86
+ // 不阻断:检查仍通过、未调用 process.exit
87
+ expect(output).toContain('约束检查通过');
88
+ expect(exitSpy).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it('内容漂移:手改一条 → 警告块含缺失/多余计数,exit 未调用', async () => {
92
+ const root = makeTmpProject();
93
+ writeClaudeMd(root, REAL_VERSION);
94
+ const claudeMdPath = path.join(root, 'CLAUDE.md');
95
+ const content = fs.readFileSync(claudeMdPath, 'utf-8');
96
+ const originalLine = content.split('\n').find(l => l.startsWith('- **'))!;
97
+ fs.writeFileSync(claudeMdPath, content.replace(originalLine, originalLine.replace(/: .+$/, ': 篡改')), 'utf-8');
98
+
99
+ await check({ preset: 'standard', staged: false, projectPath: root });
100
+
101
+ const output = outputText();
102
+ expect(output).toContain('内容漂移: 缺失 1 条 / 多余 1 条');
103
+ expect(output).toContain('约束检查通过');
104
+ expect(exitSpy).not.toHaveBeenCalled();
105
+ });
106
+
107
+ it('无漂移:零警告输出(不增加噪音)', async () => {
108
+ const root = makeTmpProject();
109
+ writeClaudeMd(root, REAL_VERSION);
110
+
111
+ await check({ preset: 'standard', staged: false, projectPath: root });
112
+
113
+ const output = outputText();
114
+ expect(output).not.toContain('注入漂移');
115
+ expect(output).toContain('约束检查通过');
116
+ expect(exitSpy).not.toHaveBeenCalled();
117
+ });
118
+
119
+ it('未注入(无 CLAUDE.md):不警告', async () => {
120
+ const root = makeTmpProject();
121
+
122
+ await check({ preset: 'standard', staged: false, projectPath: root });
123
+
124
+ expect(outputText()).not.toContain('注入漂移');
125
+ expect(exitSpy).not.toHaveBeenCalled();
126
+ });
127
+ });