@dommaker/harness 1.9.0 → 1.11.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 (285) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +9 -12
  3. package/dist/cli/commands/check.d.ts +3 -3
  4. package/dist/cli/commands/check.d.ts.map +1 -1
  5. package/dist/cli/commands/check.js +41 -78
  6. package/dist/cli/commands/check.js.map +1 -1
  7. package/dist/cli/commands/constraints-report.d.ts +2 -10
  8. package/dist/cli/commands/constraints-report.d.ts.map +1 -1
  9. package/dist/cli/commands/constraints-report.js +4 -57
  10. package/dist/cli/commands/constraints-report.js.map +1 -1
  11. package/dist/cli/commands/constraints-retire.d.ts +9 -23
  12. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  13. package/dist/cli/commands/constraints-retire.js +39 -121
  14. package/dist/cli/commands/constraints-retire.js.map +1 -1
  15. package/dist/cli/commands/constraints.d.ts +3 -8
  16. package/dist/cli/commands/constraints.d.ts.map +1 -1
  17. package/dist/cli/commands/constraints.js +8 -23
  18. package/dist/cli/commands/constraints.js.map +1 -1
  19. package/dist/cli/commands/definitions.d.ts.map +1 -1
  20. package/dist/cli/commands/definitions.js +7 -12
  21. package/dist/cli/commands/definitions.js.map +1 -1
  22. package/dist/cli/commands/init.d.ts +0 -32
  23. package/dist/cli/commands/init.d.ts.map +1 -1
  24. package/dist/cli/commands/init.js +7 -149
  25. package/dist/cli/commands/init.js.map +1 -1
  26. package/dist/cli/commands/report.d.ts.map +1 -1
  27. package/dist/cli/commands/report.js +14 -17
  28. package/dist/cli/commands/report.js.map +1 -1
  29. package/dist/cli/commands/scaffold-templates.d.ts +1 -3
  30. package/dist/cli/commands/scaffold-templates.d.ts.map +1 -1
  31. package/dist/cli/commands/scaffold-templates.js +2 -53
  32. package/dist/cli/commands/scaffold-templates.js.map +1 -1
  33. package/dist/cli/commands/scaffold.d.ts +0 -2
  34. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  35. package/dist/cli/commands/scaffold.js +0 -10
  36. package/dist/cli/commands/scaffold.js.map +1 -1
  37. package/dist/cli/commands/status.js +10 -10
  38. package/dist/cli/commands/status.js.map +1 -1
  39. package/dist/cli/commands/sync-docs/agents-syncer.d.ts +0 -2
  40. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  41. package/dist/cli/commands/sync-docs/agents-syncer.js +4 -23
  42. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  43. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  44. package/dist/core/constraints/capabilities-parser.js +16 -4
  45. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  46. package/dist/core/constraints/checker.d.ts +15 -24
  47. package/dist/core/constraints/checker.d.ts.map +1 -1
  48. package/dist/core/constraints/checker.js +33 -72
  49. package/dist/core/constraints/checker.js.map +1 -1
  50. package/dist/core/constraints/checkers/docs-freshness.js +1 -1
  51. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  52. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -1
  53. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  54. package/dist/core/constraints/checkers/governance-presence.js +4 -3
  55. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  56. package/dist/core/constraints/checkers/index.d.ts +1 -1
  57. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  58. package/dist/core/constraints/checkers/index.js +4 -8
  59. package/dist/core/constraints/checkers/index.js.map +1 -1
  60. package/dist/core/constraints/checkers/iron-flags.d.ts +3 -4
  61. package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
  62. package/dist/core/constraints/checkers/iron-flags.js +4 -5
  63. package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
  64. package/dist/core/constraints/checkers/no-test-simplification.js +1 -1
  65. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  66. package/dist/core/constraints/checkers/types.d.ts +1 -1
  67. package/dist/core/constraints/checkers/types.js +1 -1
  68. package/dist/core/constraints/definitions/guidelines.d.ts +4 -4
  69. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  70. package/dist/core/constraints/definitions/guidelines.js +9 -25
  71. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  72. package/dist/core/constraints/definitions/iron-laws.d.ts +4 -4
  73. package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
  74. package/dist/core/constraints/definitions/iron-laws.js +10 -81
  75. package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
  76. package/dist/core/constraints/definitions.d.ts +7 -9
  77. package/dist/core/constraints/definitions.d.ts.map +1 -1
  78. package/dist/core/constraints/definitions.js +13 -16
  79. package/dist/core/constraints/definitions.js.map +1 -1
  80. package/dist/core/constraints/index.d.ts +2 -5
  81. package/dist/core/constraints/index.d.ts.map +1 -1
  82. package/dist/core/constraints/index.js +3 -13
  83. package/dist/core/constraints/index.js.map +1 -1
  84. package/dist/core/constraints/injection-writer.d.ts +10 -38
  85. package/dist/core/constraints/injection-writer.d.ts.map +1 -1
  86. package/dist/core/constraints/injection-writer.js +17 -54
  87. package/dist/core/constraints/injection-writer.js.map +1 -1
  88. package/dist/core/constraints/run-env.d.ts +1 -10
  89. package/dist/core/constraints/run-env.d.ts.map +1 -1
  90. package/dist/core/constraints/run-env.js +1 -22
  91. package/dist/core/constraints/run-env.js.map +1 -1
  92. package/dist/core/constraints/usage-report.d.ts +6 -8
  93. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  94. package/dist/core/constraints/usage-report.js +6 -11
  95. package/dist/core/constraints/usage-report.js.map +1 -1
  96. package/dist/core/effective-constraints.d.ts +12 -22
  97. package/dist/core/effective-constraints.d.ts.map +1 -1
  98. package/dist/core/effective-constraints.js +13 -30
  99. package/dist/core/effective-constraints.js.map +1 -1
  100. package/dist/core/index.d.ts +2 -2
  101. package/dist/core/index.d.ts.map +1 -1
  102. package/dist/core/index.js +3 -9
  103. package/dist/core/index.js.map +1 -1
  104. package/dist/core/project-config-loader.d.ts +4 -42
  105. package/dist/core/project-config-loader.d.ts.map +1 -1
  106. package/dist/core/project-config-loader.js +16 -125
  107. package/dist/core/project-config-loader.js.map +1 -1
  108. package/dist/core/validators/passes-gate.d.ts +2 -2
  109. package/dist/core/validators/passes-gate.js +6 -6
  110. package/dist/core/validators/passes-gate.js.map +1 -1
  111. package/dist/index.d.ts +9 -10
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +21 -18
  114. package/dist/index.js.map +1 -1
  115. package/dist/knowledge/lint.d.ts +17 -0
  116. package/dist/knowledge/lint.d.ts.map +1 -1
  117. package/dist/knowledge/lint.js +52 -0
  118. package/dist/knowledge/lint.js.map +1 -1
  119. package/dist/knowledge/store.d.ts +7 -0
  120. package/dist/knowledge/store.d.ts.map +1 -1
  121. package/dist/knowledge/store.js +48 -13
  122. package/dist/knowledge/store.js.map +1 -1
  123. package/dist/knowledge/types.d.ts +5 -1
  124. package/dist/knowledge/types.d.ts.map +1 -1
  125. package/dist/knowledge/types.js +5 -1
  126. package/dist/knowledge/types.js.map +1 -1
  127. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  128. package/dist/monitoring/trace-analyzer.js +11 -12
  129. package/dist/monitoring/trace-analyzer.js.map +1 -1
  130. package/dist/monitoring/traces.d.ts +4 -4
  131. package/dist/monitoring/traces.d.ts.map +1 -1
  132. package/dist/monitoring/traces.js +8 -8
  133. package/dist/monitoring/traces.js.map +1 -1
  134. package/dist/presets/standard.d.ts +8 -10
  135. package/dist/presets/standard.d.ts.map +1 -1
  136. package/dist/presets/standard.js +10 -16
  137. package/dist/presets/standard.js.map +1 -1
  138. package/dist/types/constraint.d.ts +24 -35
  139. package/dist/types/constraint.d.ts.map +1 -1
  140. package/dist/types/constraint.js +8 -7
  141. package/dist/types/constraint.js.map +1 -1
  142. package/dist/types/passes-gate.d.ts +5 -5
  143. package/dist/types/passes-gate.d.ts.map +1 -1
  144. package/dist/types/project-config.d.ts +8 -66
  145. package/dist/types/project-config.d.ts.map +1 -1
  146. package/dist/types/project-config.js +3 -2
  147. package/dist/types/project-config.js.map +1 -1
  148. package/dist/types/trace.d.ts +8 -10
  149. package/dist/types/trace.d.ts.map +1 -1
  150. package/package.json +2 -2
  151. package/src/CONTEXT.md +2 -2
  152. package/src/__tests__/checker-extra.test.ts +37 -61
  153. package/src/__tests__/checker.test.ts +10 -115
  154. package/src/__tests__/effective-constraints.test.ts +18 -83
  155. package/src/__tests__/iron-laws.test.ts +71 -90
  156. package/src/__tests__/passes-gate-check.test.ts +2 -2
  157. package/src/__tests__/project-config-loader-extra.test.ts +22 -123
  158. package/src/__tests__/project-config-loader.test.ts +5 -33
  159. package/src/__tests__/public-exports.test.ts +8 -7
  160. package/src/__tests__/public-type-surface.test.ts +7 -5
  161. package/src/__tests__/public-value-surface.test.ts +1 -7
  162. package/src/__tests__/skip-semantics.test.ts +21 -37
  163. package/src/__tests__/trace-analyzer-extra.test.ts +14 -14
  164. package/src/__tests__/trace-analyzer.test.ts +21 -21
  165. package/src/__tests__/trace-collector.test.ts +22 -22
  166. package/src/__tests__/traces.test.ts +27 -28
  167. package/src/__tests__/usage-report-public-export.test.ts +21 -0
  168. package/src/cli/commands/CONTEXT.md +12 -12
  169. package/src/cli/commands/__tests__/check-read-count.test.ts +4 -15
  170. package/src/cli/commands/__tests__/check-skip-output.test.ts +14 -14
  171. package/src/cli/commands/__tests__/check.test.ts +14 -31
  172. package/src/cli/commands/__tests__/constraints-report-json-flag.test.ts +101 -0
  173. package/src/cli/commands/__tests__/constraints-report.test.ts +12 -80
  174. package/src/cli/commands/__tests__/constraints-retire.test.ts +42 -231
  175. package/src/cli/commands/__tests__/init-governance-io.test.ts +12 -58
  176. package/src/cli/commands/__tests__/init-ondisk.test.ts +13 -21
  177. package/src/cli/commands/__tests__/init.test.ts +1 -16
  178. package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +1 -1
  179. package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +1 -1
  180. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +2 -2
  181. package/src/cli/commands/__tests__/registry.test.ts +1 -1
  182. package/src/cli/commands/__tests__/report-extra.test.ts +4 -4
  183. package/src/cli/commands/__tests__/report.test.ts +1 -1
  184. package/src/cli/commands/__tests__/scaffold.test.ts +4 -14
  185. package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +3 -3
  186. package/src/cli/commands/__tests__/status-extra.test.ts +14 -14
  187. package/src/cli/commands/__tests__/status.test.ts +12 -12
  188. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +1 -1
  189. package/src/cli/commands/check.ts +43 -82
  190. package/src/cli/commands/constraints-report.ts +4 -59
  191. package/src/cli/commands/constraints-retire.ts +49 -154
  192. package/src/cli/commands/constraints.ts +11 -26
  193. package/src/cli/commands/definitions.ts +7 -12
  194. package/src/cli/commands/init.ts +8 -179
  195. package/src/cli/commands/report.ts +19 -23
  196. package/src/cli/commands/scaffold-templates.ts +1 -53
  197. package/src/cli/commands/scaffold.ts +0 -11
  198. package/src/cli/commands/status.ts +10 -10
  199. package/src/cli/commands/sync-docs/agents-syncer.ts +4 -25
  200. package/src/core/CONTEXT.md +9 -9
  201. package/src/core/constraints/__tests__/capability-counts.test.ts +5 -5
  202. package/src/core/constraints/__tests__/collect-constraints.test.ts +13 -8
  203. package/src/core/constraints/__tests__/git-evidence.test.ts +4 -7
  204. package/src/core/constraints/__tests__/injection-writer.test.ts +8 -61
  205. package/src/core/constraints/__tests__/run-env.test.ts +5 -34
  206. package/src/core/constraints/__tests__/trace-injection.test.ts +1 -1
  207. package/src/core/constraints/capabilities-parser.ts +17 -5
  208. package/src/core/constraints/checker.ts +39 -88
  209. package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +5 -8
  210. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +1 -53
  211. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +5 -25
  212. package/src/core/constraints/checkers/docs-freshness.ts +1 -1
  213. package/src/core/constraints/checkers/governance-presence.ts +4 -3
  214. package/src/core/constraints/checkers/index.ts +4 -10
  215. package/src/core/constraints/checkers/iron-flags.ts +3 -12
  216. package/src/core/constraints/checkers/no-test-simplification.ts +1 -1
  217. package/src/core/constraints/checkers/types.ts +1 -1
  218. package/src/core/constraints/definitions/guidelines.ts +8 -25
  219. package/src/core/constraints/definitions/iron-laws.ts +9 -82
  220. package/src/core/constraints/definitions.ts +14 -15
  221. package/src/core/constraints/index.ts +4 -19
  222. package/src/core/constraints/injection-writer.ts +16 -61
  223. package/src/core/constraints/run-env.ts +1 -33
  224. package/src/core/constraints/usage-report.ts +9 -16
  225. package/src/core/effective-constraints.ts +14 -45
  226. package/src/core/index.ts +3 -10
  227. package/src/core/project-config-loader.ts +20 -145
  228. package/src/core/validators/passes-gate.ts +6 -6
  229. package/src/failure/CONTEXT.md +1 -1
  230. package/src/hooks/__tests__/bootstrap.test.ts +3 -5
  231. package/src/index.ts +28 -18
  232. package/src/knowledge/CONTEXT.md +4 -3
  233. package/src/knowledge/__tests__/lint.test.ts +84 -0
  234. package/src/knowledge/__tests__/store.test.ts +70 -0
  235. package/src/knowledge/lint.ts +60 -1
  236. package/src/knowledge/store.ts +48 -11
  237. package/src/knowledge/types.ts +7 -1
  238. package/src/monitoring/CONTEXT.md +1 -1
  239. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +1 -1
  240. package/src/monitoring/__tests__/traces.test.ts +22 -22
  241. package/src/monitoring/trace-analyzer.ts +12 -13
  242. package/src/monitoring/traces.ts +8 -8
  243. package/src/presets/__tests__/standard.test.ts +12 -16
  244. package/src/presets/standard.ts +14 -23
  245. package/src/types/__tests__/types.test.ts +3 -3
  246. package/src/types/constraint.ts +26 -40
  247. package/src/types/passes-gate.ts +5 -5
  248. package/src/types/project-config.ts +8 -77
  249. package/src/types/trace.ts +8 -11
  250. package/dist/cli/commands/posteval-plan.d.ts +0 -15
  251. package/dist/cli/commands/posteval-plan.d.ts.map +0 -1
  252. package/dist/cli/commands/posteval-plan.js +0 -86
  253. package/dist/cli/commands/posteval-plan.js.map +0 -1
  254. package/dist/core/constraints/agent-prompt-renderer.d.ts +0 -32
  255. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +0 -1
  256. package/dist/core/constraints/agent-prompt-renderer.js +0 -55
  257. package/dist/core/constraints/agent-prompt-renderer.js.map +0 -1
  258. package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts +0 -6
  259. package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts.map +0 -1
  260. package/dist/core/constraints/checkers/no-bypass-checkpoint.js +0 -40
  261. package/dist/core/constraints/checkers/no-bypass-checkpoint.js.map +0 -1
  262. package/dist/core/constraints/definitions/prompts.d.ts +0 -12
  263. package/dist/core/constraints/definitions/prompts.d.ts.map +0 -1
  264. package/dist/core/constraints/definitions/prompts.js +0 -400
  265. package/dist/core/constraints/definitions/prompts.js.map +0 -1
  266. package/dist/core/constraints/injection-drift.d.ts +0 -61
  267. package/dist/core/constraints/injection-drift.d.ts.map +0 -1
  268. package/dist/core/constraints/injection-drift.js +0 -131
  269. package/dist/core/constraints/injection-drift.js.map +0 -1
  270. package/dist/core/constraints/injection-renderer.d.ts +0 -23
  271. package/dist/core/constraints/injection-renderer.d.ts.map +0 -1
  272. package/dist/core/constraints/injection-renderer.js +0 -44
  273. package/dist/core/constraints/injection-renderer.js.map +0 -1
  274. package/src/cli/commands/__tests__/check-drift.test.ts +0 -158
  275. package/src/cli/commands/__tests__/init-injection.test.ts +0 -466
  276. package/src/cli/commands/__tests__/posteval-plan.test.ts +0 -204
  277. package/src/cli/commands/posteval-plan.ts +0 -98
  278. package/src/core/constraints/__tests__/agent-prompt-renderer.test.ts +0 -129
  279. package/src/core/constraints/__tests__/injection-drift.test.ts +0 -247
  280. package/src/core/constraints/agent-prompt-renderer.ts +0 -67
  281. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +0 -76
  282. package/src/core/constraints/checkers/no-bypass-checkpoint.ts +0 -40
  283. package/src/core/constraints/definitions/prompts.ts +0 -414
  284. package/src/core/constraints/injection-drift.ts +0 -142
  285. package/src/core/constraints/injection-renderer.ts +0 -44
@@ -38,18 +38,15 @@ describe('buildConstraintsUsageReport', () => {
38
38
  const report = buildConstraintsUsageReport(root);
39
39
 
40
40
  expect(report.traceFileExists).toBe(false);
41
- expect(report.stats.length).toBe(10); // 5 iron + 5 guideline
41
+ expect(report.stats.length).toBe(7); // 3 error + 4 warning
42
42
  for (const s of report.stats) {
43
43
  expect(s.total).toBe(0);
44
44
  expect(s.evaluated).toBe(0);
45
45
  expect(s.failRate).toBe(0);
46
46
  expect(s.firstAt).toBeUndefined();
47
47
  }
48
- expect(report.candidates.length).toBe(10);
48
+ expect(report.candidates.length).toBe(7);
49
49
  expect(report.candidates.every(c => c.kind === 'zero_trigger')).toBe(true);
50
- // prompt 注入清单(standard preset、无 scenes → 14 条通用 prompt)
51
- expect(report.activePromptIds.length).toBe(14);
52
- expect(report.activePromptIds).toContain('no_fuzzy_completion_claim');
53
50
  });
54
51
 
55
52
  it('空 trace 文件:与无文件一致,但 traceFileExists=true', () => {
@@ -89,7 +86,7 @@ describe('buildConstraintsUsageReport', () => {
89
86
  fs.mkdirSync(dir, { recursive: true });
90
87
  fs.writeFileSync(
91
88
  path.join(dir, 'traces.log'),
92
- '{"constraintId":"docs_freshness","timestamp":1,"result":"pass","level":"iron_law"}\n{bad json\n',
89
+ '{"constraintId":"docs_freshness","timestamp":1,"result":"pass","severity":"error"}\n{bad json\n',
93
90
  'utf-8'
94
91
  );
95
92
  expect(readProjectTraces(root).length).toBe(1);
@@ -103,8 +100,8 @@ describe('buildConstraintsUsageReport', () => {
103
100
  // 不可评估(存在性探测未命中):全部 skip
104
101
  ...tracesOf('capability_sync', 'skip', 5),
105
102
  // 高噪:24 次评估 fail 率 83% > 80%
106
- ...tracesOf('no_bypass_checkpoint', 'fail', 20),
107
- ...tracesOf('no_bypass_checkpoint', 'pass', 4, 1700001000000),
103
+ ...tracesOf('no_test_simplification', 'fail', 20),
104
+ ...tracesOf('no_test_simplification', 'pass', 4, 1700001000000),
108
105
  // 零拦截:50 次评估 0 fail
109
106
  ...tracesOf('no_hardcoded_credentials', 'pass', 50, 1700010000000),
110
107
  ]);
@@ -125,7 +122,7 @@ describe('buildConstraintsUsageReport', () => {
125
122
  expect(probeC?.reason).toContain('约定未采用');
126
123
 
127
124
  // 高噪
128
- const noiseC = byId.get('no_bypass_checkpoint');
125
+ const noiseC = byId.get('no_test_simplification');
129
126
  expect(noiseC?.kind).toBe('high_noise');
130
127
  expect(noiseC?.reason).toContain('83%');
131
128
 
@@ -151,7 +148,7 @@ describe('buildConstraintsUsageReport', () => {
151
148
  it('diagnoseRetireCandidates 优先级:全 skip 不重复计入零拦截', () => {
152
149
  const candidates = diagnoseRetireCandidates([
153
150
  {
154
- id: 'x', level: 'guideline',
151
+ id: 'x', severity: 'warning',
155
152
  total: 60, pass: 0, fail: 0, skip: 60,
156
153
  evaluated: 0, failRate: 0,
157
154
  },
@@ -171,7 +168,7 @@ describe('buildConstraintsUsageReport', () => {
171
168
  });
172
169
 
173
170
  describe('constraintsReport CLI', () => {
174
- it('console 输出包含统计表、候选、注入清单、unknownIds 提示', async () => {
171
+ it('console 输出包含统计表、候选、unknownIds 提示', async () => {
175
172
  const root = createProjectFixture({
176
173
  name: 'harness-report-test',
177
174
  config: 'constraints:\n ghost_constraint:\n enabled: false\n',
@@ -185,16 +182,15 @@ describe('constraintsReport CLI', () => {
185
182
  expect(output).toContain('no_hardcoded_credentials');
186
183
  expect(output).toContain('total=3');
187
184
  expect(output).toContain('退役候选');
188
- expect(output).toContain('当前生效 prompt 注入');
189
185
  expect(output).toContain('ghost_constraint');
190
186
  });
191
187
 
192
188
  it('--export 缺省路径:写入 .harness/reports/constraints-<YYYYMMDD>.md 且内容脱敏', async () => {
193
189
  const root = createProjectFixture({ name: 'harness-report-test' });
194
190
  writeProjectTraces(root, [
195
- ...tracesOf('no_bypass_checkpoint', 'fail', 20),
191
+ ...tracesOf('no_test_simplification', 'fail', 20),
196
192
  // projectPath 字段进 trace,但不得进 export
197
- { constraintId: 'no_bypass_checkpoint', result: 'fail', projectPath: root, timestamp: 1700001000000 },
193
+ { constraintId: 'no_test_simplification', result: 'fail', projectPath: root, timestamp: 1700001000000 },
198
194
  ]);
199
195
 
200
196
  await constraintsReport({ projectPath: root, export: true }, io);
@@ -206,7 +202,7 @@ describe('constraintsReport CLI', () => {
206
202
  const content = fs.readFileSync(exportPath, 'utf-8');
207
203
  expect(content).toContain('harness 版本');
208
204
  expect(content).toContain('| id | total | pass | fail | skip |');
209
- expect(content).toContain('no_bypass_checkpoint');
205
+ expect(content).toContain('no_test_simplification');
210
206
  expect(content).toContain('高噪');
211
207
  // 脱敏:不包含项目路径
212
208
  expect(content).not.toContain(root);
@@ -247,7 +243,7 @@ describe('constraintsReport 坏行数透传(harness#100)', () => {
247
243
  }
248
244
 
249
245
  const healthyTrace = (id: string) =>
250
- JSON.stringify({ constraintId: id, level: 'iron_law', timestamp: 1700000000000, result: 'pass' });
246
+ JSON.stringify({ constraintId: id, severity: 'error', timestamp: 1700000000000, result: 'pass' });
251
247
  const BAD = '{"constraintId":"ghost","broken';
252
248
 
253
249
  it('坏行 fixture:文本输出出现坏行数提示,--json 报告体带 skippedLines', async () => {
@@ -290,67 +286,3 @@ describe('constraintsReport 坏行数透传(harness#100)', () => {
290
286
  expect(fs.readFileSync(path.join(clean, 'report.md'), 'utf-8')).not.toContain('损坏');
291
287
  });
292
288
  });
293
-
294
- describe('constraintsReport 注入漂移小节(ADR-0001 决策 7)', () => {
295
- // eslint-disable-next-line @typescript-eslint/no-var-requires
296
- const realVersion = require('../../../../package.json').version as string;
297
-
298
- const writeSyncedClaudeMd = (root: string, version = realVersion) => {
299
- const { renderConstraintsSection } = require('../../../core/constraints/injection-renderer');
300
- const { getEffectiveConstraints } = require('../../../core/effective-constraints');
301
- const section = '## Governance Rules\n' + renderConstraintsSection(getEffectiveConstraints(root), version);
302
- fs.writeFileSync(path.join(root, 'CLAUDE.md'), `# Test Project\n\n${section}`, 'utf-8');
303
- };
304
-
305
- it('有漂移:输出条目级差异(缺失/多余)与修复指引', async () => {
306
- const root = createProjectFixture({ name: 'harness-report-test' });
307
- writeSyncedClaudeMd(root, '0.0.1-old');
308
- const claudeMdPath = path.join(root, 'CLAUDE.md');
309
- const content = fs.readFileSync(claudeMdPath, 'utf-8');
310
- const originalLine = content.split('\n').find((l: string) => l.startsWith('- **'))!;
311
- const editedLine = originalLine.replace(/: .+$/, ': 手工篡改');
312
- fs.writeFileSync(claudeMdPath, content.replace(originalLine, editedLine), 'utf-8');
313
-
314
- await constraintsReport({ projectPath: root }, io);
315
-
316
- const output = io.outText();
317
- expect(output).toContain('注入漂移');
318
- expect(output).toContain('版本漂移');
319
- expect(output).toContain('0.0.1-old');
320
- expect(output).toContain(`缺失: ${originalLine}`);
321
- expect(output).toContain(`多余: ${editedLine}`);
322
- expect(output).toContain('npx @dommaker/harness init');
323
- });
324
-
325
- it('重复章节:输出重复章节提示', async () => {
326
- const root = createProjectFixture({ name: 'harness-report-test' });
327
- writeSyncedClaudeMd(root);
328
- fs.appendFileSync(path.join(root, 'CLAUDE.md'), '\n## Governance Rules\n\n旧版遗留\n', 'utf-8');
329
-
330
- await constraintsReport({ projectPath: root }, io);
331
-
332
- const output = io.outText();
333
- expect(output).toContain('重复章节');
334
- });
335
-
336
- it('无漂移:小节显示无漂移', async () => {
337
- const root = createProjectFixture({ name: 'harness-report-test' });
338
- writeSyncedClaudeMd(root);
339
-
340
- await constraintsReport({ projectPath: root }, io);
341
-
342
- const output = io.outText();
343
- expect(output).toContain('注入漂移');
344
- expect(output).toContain('无漂移');
345
- });
346
-
347
- it('未注入(无标记段):一句话提示,不算漂移', async () => {
348
- const root = createProjectFixture({ name: 'harness-report-test' });
349
- fs.writeFileSync(path.join(root, 'CLAUDE.md'), '# Test Project\n', 'utf-8');
350
-
351
- await constraintsReport({ projectPath: root }, io);
352
-
353
- const output = io.outText();
354
- expect(output).toContain('未注入');
355
- });
356
- });
@@ -1,10 +1,13 @@
1
1
  /**
2
- * harness constraints retire 测试(ADR-0001 P5
2
+ * harness constraints retire 测试(ADR-0001 P5,ADR-0029 收窄)
3
3
  *
4
4
  * 执行逻辑(retireConstraint,纯函数化):config.yml 写入形态、
5
- * KnowledgeStore 记录、治理注入段同步(CLAUDE.md / AGENTS.md 落点路由)、重复 retire / 未知 id 保护。
5
+ * KnowledgeStore 记录、重复 retire / 未知 id 保护。
6
6
  * 交互流程(runRetireInteractive):注入 stdin 流测核心分支
7
- * (候选选择 + iron 二次确认拒绝;无候选手动输入 + 确认执行)。
7
+ * (候选选择 + error 级二次确认拒绝;无候选手动输入 + 确认执行)。
8
+ *
9
+ * ADR-0029:custom 纯文本约束与治理注入段同步已随文本注入层关停一并退役,
10
+ * 相关用例移除。
8
11
  *
9
12
  * 使用真实临时目录。
10
13
  */
@@ -16,7 +19,6 @@ import { PassThrough, Writable } from 'stream';
16
19
  import * as yaml from 'js-yaml';
17
20
  import { getConstraint } from '../../../core/constraints/definitions';
18
21
  import { getEffectiveConstraints } from '../../../core/effective-constraints';
19
- import { renderConstraintsSection } from '../../../core/constraints/injection-renderer';
20
22
  import { FileKnowledgeStore } from '../../../knowledge/store';
21
23
  import { retireConstraint, constraintsRetire, runRetireInteractive, printRetireResult } from '../constraints-retire';
22
24
  import { createProjectFixture, writeProjectTraces } from '../../../test-setup/project-fixture';
@@ -27,27 +29,6 @@ function readConfig(root: string): any {
27
29
  return yaml.load(fs.readFileSync(path.join(root, '.harness', 'config.yml'), 'utf-8'));
28
30
  }
29
31
 
30
- const CUSTOM_YML = `
31
- custom_constraints:
32
- my_custom_rule:
33
- level: iron_law
34
- rule: 禁止引入 X
35
- message: X 已由平台能力替代
36
- promptInjection: 禁止引入 X
37
- `;
38
-
39
- function writeCustom(root: string, yml: string): void {
40
- fs.mkdirSync(path.join(root, '.harness'), { recursive: true });
41
- fs.writeFileSync(path.join(root, '.harness', 'custom-constraints.yml'), yml, 'utf-8');
42
- }
43
-
44
- function readCustom(root: string): any {
45
- return yaml.load(fs.readFileSync(path.join(root, '.harness', 'custom-constraints.yml'), 'utf-8'));
46
- }
47
-
48
- beforeEach(() => {
49
- });
50
-
51
32
  let io: CapturingIO;
52
33
  beforeEach(() => {
53
34
  io = captureIO();
@@ -68,7 +49,7 @@ describe('retireConstraint 执行逻辑', () => {
68
49
  });
69
50
 
70
51
  expect(result.status).toBe('retired');
71
- expect(result.isIronLaw).toBe(false);
52
+ expect(result.isError).toBe(false);
72
53
  expect(result.stats).toEqual({ total: 3, fail: 1, failRate: 1 / 3 });
73
54
 
74
55
  const config = readConfig(root);
@@ -84,14 +65,14 @@ describe('retireConstraint 执行逻辑', () => {
84
65
  it('config.yml 不存在时创建;已有其他配置时保留', () => {
85
66
  const root = createProjectFixture({ name: 'harness-retire-test' });
86
67
  fs.mkdirSync(path.join(root, '.harness'), { recursive: true });
87
- fs.writeFileSync(path.join(root, '.harness', 'config.yml'), 'preset: strict\nscenes:\n - llm-app\n', 'utf-8');
68
+ fs.writeFileSync(path.join(root, '.harness', 'config.yml'), 'preset: strict\nci:\n platform: gitlab\n', 'utf-8');
88
69
 
89
70
  const result = retireConstraint(root, 'capability_sync', { now: FIXED_NOW });
90
71
  expect(result.status).toBe('retired');
91
72
 
92
73
  const config = readConfig(root);
93
74
  expect(config.preset).toBe('strict');
94
- expect(config.scenes).toEqual(['llm-app']);
75
+ expect(config.ci.platform).toBe('gitlab');
95
76
  expect(config.constraints.capability_sync.enabled).toBe(false);
96
77
  expect(config.constraints.capability_sync.retired.reason).toBe('');
97
78
  });
@@ -99,23 +80,23 @@ describe('retireConstraint 执行逻辑', () => {
99
80
  it('KnowledgeStore 写入退役记录:规则原文 + 原因 + 统计 + signal 模式', () => {
100
81
  const root = createProjectFixture({
101
82
  name: 'harness-retire-test',
102
- traces: [{ constraintId: 'no_bypass_checkpoint', result: 'fail' }],
83
+ traces: [{ constraintId: 'no_completion_without_verification', result: 'fail' }],
103
84
  });
104
85
 
105
- const result = retireConstraint(root, 'no_bypass_checkpoint', { reason: '流程已内置门禁', now: FIXED_NOW });
86
+ const result = retireConstraint(root, 'no_completion_without_verification', { reason: '流程已内置门禁', now: FIXED_NOW });
106
87
  expect(result.status).toBe('retired');
107
- expect(result.knowledgeEntryId).toBe('constraint-retired-no_bypass_checkpoint');
88
+ expect(result.knowledgeEntryId).toBe('constraint-retired-no_completion_without_verification');
108
89
 
109
90
  const store = new FileKnowledgeStore({ baseDir: path.join(root, '.harness', 'knowledge') });
110
- const entry = store.get('constraint-retired-no_bypass_checkpoint');
91
+ const entry = store.get('constraint-retired-no_completion_without_verification');
111
92
  expect(entry).toBeDefined();
112
93
  expect(entry!.consumptionMode).toBe('signal');
113
94
  expect(entry!.origin).toBe('human');
114
95
  expect(entry!.tags).toContain('constraint-retired');
115
- expect(entry!.tags).toContain('constraint:no_bypass_checkpoint');
116
- expect(entry!.title).toContain('no_bypass_checkpoint');
96
+ expect(entry!.tags).toContain('constraint:no_completion_without_verification');
97
+ expect(entry!.title).toContain('no_completion_without_verification');
117
98
 
118
- const def = getConstraint('no_bypass_checkpoint')!;
99
+ const def = getConstraint('no_completion_without_verification')!;
119
100
  expect(entry!.content).toContain(def.description!);
120
101
  expect(entry!.content).toContain('流程已内置门禁');
121
102
  expect(entry!.content).toContain('total: 1');
@@ -123,77 +104,15 @@ describe('retireConstraint 执行逻辑', () => {
123
104
  expect(entry!.content).toContain(FIXED_NOW.toISOString());
124
105
  });
125
106
 
126
- it('CLAUDE.md 含标记段时同步重渲染(退役条目消失、标记保留)', () => {
107
+ it('退役结果打印含回滚语义提示(恢复 = 删 config.yml 段)', () => {
127
108
  const root = createProjectFixture({ name: 'harness-retire-test' });
128
- // 先用当前生效集渲染一个合法注入段
129
- const before = renderConstraintsSection(getEffectiveConstraints(root), '0.0.0-test');
130
- expect(before).toContain('no_bypass_checkpoint');
131
- fs.writeFileSync(path.join(root, 'CLAUDE.md'), `# 项目\n\n## Governance Rules\n${before}\n其他内容\n`, 'utf-8');
132
-
133
- const result = retireConstraint(root, 'no_bypass_checkpoint', { now: FIXED_NOW });
109
+ const result = retireConstraint(root, 'capability_sync', { now: FIXED_NOW });
134
110
  expect(result.status).toBe('retired');
135
- expect(result.injectionSynced).toBe(true);
136
-
137
- const after = fs.readFileSync(path.join(root, 'CLAUDE.md'), 'utf-8');
138
- expect(after).toContain('HARNESS_CONSTRAINTS_START');
139
- expect(after).toContain('HARNESS_CONSTRAINTS_END');
140
- expect(after).not.toContain('**no_bypass_checkpoint**');
141
- expect(after).toContain('其他内容');
142
111
 
143
112
  printRetireResult(result, io);
144
113
  const output = io.outText();
145
- expect(output).toContain('已同步 CLAUDE.md 注入段');
146
114
  expect(output).toContain('恢复方法');
147
- });
148
-
149
- it('CLAUDE.md 无标记段或不存在时不创建、不同步', () => {
150
- const root = createProjectFixture({ name: 'harness-retire-test' });
151
- fs.writeFileSync(path.join(root, 'CLAUDE.md'), '# 用户自写\n', 'utf-8');
152
- const result = retireConstraint(root, 'capability_sync', { now: FIXED_NOW });
153
- expect(result.status).toBe('retired');
154
- expect(result.injectionSynced).toBe(false);
155
- expect(fs.readFileSync(path.join(root, 'CLAUDE.md'), 'utf-8')).toBe('# 用户自写\n');
156
- });
157
-
158
- it('新模型仓:AGENTS.md PRESERVE:governance 内含标记段时同步重渲染(无 CLAUDE.md)', () => {
159
- const root = createProjectFixture({ name: 'harness-retire-test' });
160
- const before = renderConstraintsSection(getEffectiveConstraints(root), '0.0.0-test');
161
- expect(before).toContain('no_bypass_checkpoint');
162
- fs.writeFileSync(
163
- path.join(root, 'AGENTS.md'),
164
- `# AGENTS.md\n\n<!-- PRESERVE:governance -->\n## Governance Rules\n${before}<!-- /PRESERVE:governance -->\n\n其他内容\n`,
165
- 'utf-8'
166
- );
167
-
168
- const result = retireConstraint(root, 'no_bypass_checkpoint', { now: FIXED_NOW });
169
- expect(result.status).toBe('retired');
170
- expect(result.injectionSynced).toBe(true);
171
- expect(result.injectionFile).toBe('AGENTS.md');
172
-
173
- const after = fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf-8');
174
- expect(after).toContain('HARNESS_CONSTRAINTS_START');
175
- expect(after).toContain('HARNESS_CONSTRAINTS_END');
176
- expect(after).not.toContain('**no_bypass_checkpoint**');
177
- expect(after).toContain('PRESERVE:governance');
178
- expect(after).toContain('其他内容');
179
- // 不制造 CLAUDE.md
180
- expect(fs.existsSync(path.join(root, 'CLAUDE.md'))).toBe(false);
181
- });
182
-
183
- it('两文件均有标记段时旧模型仓豁免:只同步 CLAUDE.md,AGENTS.md 不动', () => {
184
- const root = createProjectFixture({ name: 'harness-retire-test' });
185
- const before = renderConstraintsSection(getEffectiveConstraints(root), '0.0.0-test');
186
- fs.writeFileSync(path.join(root, 'CLAUDE.md'), `# 项目\n\n## Governance Rules\n${before}`, 'utf-8');
187
- const agentsMd = `# AGENTS.md\n\n<!-- PRESERVE:governance -->\n## Governance Rules\n${before}<!-- /PRESERVE:governance -->\n`;
188
- fs.writeFileSync(path.join(root, 'AGENTS.md'), agentsMd, 'utf-8');
189
-
190
- const result = retireConstraint(root, 'no_bypass_checkpoint', { now: FIXED_NOW });
191
- expect(result.status).toBe('retired');
192
- expect(result.injectionSynced).toBe(true);
193
- expect(result.injectionFile).toBe('CLAUDE.md');
194
-
195
- expect(fs.readFileSync(path.join(root, 'CLAUDE.md'), 'utf-8')).not.toContain('**no_bypass_checkpoint**');
196
- expect(fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf-8')).toBe(agentsMd);
115
+ expect(output).toContain('constraints.capability_sync');
197
116
  });
198
117
 
199
118
  it('重复 retire:already_retired,不覆盖原 retired 元数据', () => {
@@ -217,11 +136,11 @@ describe('retireConstraint 执行逻辑', () => {
217
136
  expect(fs.existsSync(path.join(root, '.harness'))).toBe(false);
218
137
  });
219
138
 
220
- it('check 层 ironisIronLaw 标记为 true(供交互模式二次确认)', () => {
139
+ it('severity=errorisError 标记为 true(供交互模式二次确认)', () => {
221
140
  const root = createProjectFixture({ name: 'harness-retire-test' });
222
141
  const result = retireConstraint(root, 'docs_freshness', { now: FIXED_NOW });
223
142
  expect(result.status).toBe('retired');
224
- expect(result.isIronLaw).toBe(true);
143
+ expect(result.isError).toBe(true);
225
144
  });
226
145
 
227
146
  it('退役后生效集不再包含该约束', () => {
@@ -231,85 +150,16 @@ describe('retireConstraint 执行逻辑', () => {
231
150
  expect(getEffectiveConstraints(root).some(c => c.id === 'capability_sync')).toBe(false);
232
151
  });
233
152
 
234
- it('custom 约束退役:落 custom-constraints.yml retired 段(不写 config.yml),生效集与注入段移除', () => {
235
- const root = createProjectFixture({ name: 'harness-retire-test' });
236
- writeCustom(root, CUSTOM_YML);
237
- const before = renderConstraintsSection(getEffectiveConstraints(root), '0.0.0-test');
238
- expect(before).toContain('**my_custom_rule**');
239
- fs.writeFileSync(path.join(root, 'CLAUDE.md'), `# 项目\n\n## Governance Rules\n${before}\n其他内容\n`, 'utf-8');
240
-
241
- const result = retireConstraint(root, 'my_custom_rule', { reason: '作用对象已从代码库消失', now: FIXED_NOW });
242
-
243
- expect(result.status).toBe('retired');
244
- expect(result.landing).toBe('custom-constraints.yml');
245
- expect(result.injectionSynced).toBe(true);
246
- // config.yml 不产生该 id 段
247
- expect(fs.existsSync(path.join(root, '.harness', 'config.yml'))).toBe(false);
248
- // yml 保留规则原文 + retired 元数据
249
- const custom = readCustom(root).custom_constraints.my_custom_rule;
250
- expect(custom.rule).toBe('禁止引入 X');
251
- expect(custom.promptInjection).toBe('禁止引入 X');
252
- expect(custom.retired.at).toBe(FIXED_NOW.toISOString());
253
- expect(custom.retired.reason).toBe('作用对象已从代码库消失');
254
- // 生效集移除
255
- expect(getEffectiveConstraints(root).some(c => c.id === 'my_custom_rule')).toBe(false);
256
- // 注入段同步移除
257
- const after = fs.readFileSync(path.join(root, 'CLAUDE.md'), 'utf-8');
258
- expect(after).not.toContain('**my_custom_rule**');
259
- expect(after).toContain('其他内容');
260
- // 打印的恢复提示指向 yml
261
- printRetireResult(result, io);
262
- const output = io.outText();
263
- expect(output).toContain('custom_constraints.my_custom_rule.retired');
264
- });
265
-
266
- it('custom 重复退役:yml 已带 retired → already_retired,不覆盖原元数据', () => {
267
- const root = createProjectFixture({ name: 'harness-retire-test' });
268
- writeCustom(root, CUSTOM_YML);
269
- const first = retireConstraint(root, 'my_custom_rule', { reason: '第一次', now: FIXED_NOW });
270
- expect(first.status).toBe('retired');
271
-
272
- const later = new Date('2026-08-09T00:00:00.000Z');
273
- const second = retireConstraint(root, 'my_custom_rule', { reason: '第二次', now: later });
274
- expect(second.status).toBe('already_retired');
275
-
276
- const custom = readCustom(root).custom_constraints.my_custom_rule;
277
- expect(custom.retired.at).toBe(FIXED_NOW.toISOString());
278
- expect(custom.retired.reason).toBe('第一次');
279
- });
280
-
281
- it('custom 历史落点(config.yml enabled:false)仍判定 already_retired', () => {
282
- const root = createProjectFixture({ name: 'harness-retire-test' });
283
- writeCustom(root, CUSTOM_YML);
284
- fs.writeFileSync(path.join(root, '.harness', 'config.yml'), 'constraints:\n my_custom_rule:\n enabled: false\n', 'utf-8');
285
- const result = retireConstraint(root, 'my_custom_rule', { now: FIXED_NOW });
286
- expect(result.status).toBe('already_retired');
287
- });
288
-
289
- it('custom 退役 KnowledgeStore 记录包含 promptInjection(规则原文完整)', () => {
290
- const root = createProjectFixture({ name: 'harness-retire-test' });
291
- writeCustom(root, CUSTOM_YML);
292
- const result = retireConstraint(root, 'my_custom_rule', { reason: '由新机制覆盖', now: FIXED_NOW });
293
- expect(result.status).toBe('retired');
294
-
295
- const store = new FileKnowledgeStore({ baseDir: path.join(root, '.harness', 'knowledge') });
296
- const entry = store.get('constraint-retired-my_custom_rule');
297
- expect(entry).toBeDefined();
298
- expect(entry!.content).toContain('禁止引入 X');
299
- expect(entry!.content).toContain('promptInjection: 禁止引入 X');
300
- });
301
-
302
- it('custom 恢复:删除 yml retired 段后回到生效集', () => {
153
+ it('恢复:删除 config.yml constraints.<id> 段后回到生效集', () => {
303
154
  const root = createProjectFixture({ name: 'harness-retire-test' });
304
- writeCustom(root, CUSTOM_YML);
305
- retireConstraint(root, 'my_custom_rule', { now: FIXED_NOW });
306
- expect(getEffectiveConstraints(root).some(c => c.id === 'my_custom_rule')).toBe(false);
155
+ retireConstraint(root, 'capability_sync', { now: FIXED_NOW });
156
+ expect(getEffectiveConstraints(root).some(c => c.id === 'capability_sync')).toBe(false);
307
157
 
308
- const custom = readCustom(root);
309
- delete custom.custom_constraints.my_custom_rule.retired;
310
- fs.writeFileSync(path.join(root, '.harness', 'custom-constraints.yml'), yaml.dump(custom), 'utf-8');
158
+ const config = readConfig(root);
159
+ delete config.constraints.capability_sync;
160
+ fs.writeFileSync(path.join(root, '.harness', 'config.yml'), yaml.dump(config), 'utf-8');
311
161
 
312
- expect(getEffectiveConstraints(root).some(c => c.id === 'my_custom_rule')).toBe(true);
162
+ expect(getEffectiveConstraints(root).some(c => c.id === 'capability_sync')).toBe(true);
313
163
  });
314
164
  });
315
165
 
@@ -322,44 +172,29 @@ describe('落盘字节与装载次数冻结(harness#137)', () => {
322
172
  it('内置退役落 config.yml:逐字节冻结', () => {
323
173
  const root = createProjectFixture({
324
174
  name: 'harness-retire-test',
325
- config: '# 项目配置\npreset: standard\nscenes:\n - llm-app\n',
175
+ config: '# 项目配置\npreset: standard\nci:\n platform: gitlab\n',
326
176
  });
327
177
 
328
178
  retireConstraint(root, 'capability_sync', { reason: '逐字节冻结', now: FIXED_NOW });
329
179
 
330
180
  expect(fs.readFileSync(path.join(root, '.harness', 'config.yml'), 'utf-8')).toBe(
331
- 'preset: standard\nscenes:\n - llm-app\nconstraints:\n capability_sync:\n enabled: false\n' +
181
+ 'preset: standard\nci:\n platform: gitlab\nconstraints:\n capability_sync:\n enabled: false\n' +
332
182
  " retired:\n at: '2026-08-08T12:00:00.000Z'\n reason: 逐字节冻结\n" +
333
183
  ' stats:\n total: 0\n fail: 0\n failRate: 0\n'
334
184
  );
335
185
  });
336
186
 
337
- it('custom 退役落 custom-constraints.yml:逐字节冻结(规则原文保留、retired 段追加在末尾)', () => {
338
- const root = createProjectFixture({ name: 'harness-retire-test' });
339
- writeCustom(root, `# 自定义约束\n${CUSTOM_YML}`);
340
-
341
- retireConstraint(root, 'my_custom_rule', { reason: '逐字节冻结', now: FIXED_NOW });
342
-
343
- expect(fs.readFileSync(path.join(root, '.harness', 'custom-constraints.yml'), 'utf-8')).toBe(
344
- 'custom_constraints:\n my_custom_rule:\n level: iron_law\n rule: 禁止引入 X\n' +
345
- ' message: X 已由平台能力替代\n promptInjection: 禁止引入 X\n' +
346
- " retired:\n at: '2026-08-08T12:00:00.000Z'\n reason: 逐字节冻结\n" +
347
- ' stats:\n total: 0\n fail: 0\n failRate: 0\n'
348
- );
349
- });
350
-
351
- it('一次 retireConstraint 只装载一次项目配置(此前 custom 路径走 3 次 load)', async () => {
187
+ it('一次 retireConstraint 只装载一次项目配置', async () => {
352
188
  const { ProjectConfigLoader } = await import('../../../core/project-config-loader');
353
189
  const loadSpy = jest.spyOn(ProjectConfigLoader.prototype, 'load');
354
190
  const root = createProjectFixture({ name: 'harness-retire-test' });
355
- writeCustom(root, CUSTOM_YML);
356
191
  loadSpy.mockClear();
357
192
 
358
- retireConstraint(root, 'my_custom_rule', { now: FIXED_NOW });
193
+ retireConstraint(root, 'capability_sync', { now: FIXED_NOW });
359
194
  expect(loadSpy).toHaveBeenCalledTimes(1);
360
195
 
361
196
  loadSpy.mockClear();
362
- retireConstraint(root, 'my_custom_rule', { now: new Date('2026-08-09T00:00:00.000Z') });
197
+ retireConstraint(root, 'capability_sync', { now: new Date('2026-08-09T00:00:00.000Z') });
363
198
  expect(loadSpy).toHaveBeenCalledTimes(1);
364
199
  loadSpy.mockRestore();
365
200
  });
@@ -380,24 +215,24 @@ describe('constraintsRetire 非交互直达', () => {
380
215
  }
381
216
  });
382
217
 
383
- it('--yes 直达 iron 退役:打印额外警示并落盘', async () => {
218
+ it('--yes 直达 error 级退役:打印额外警示并落盘', async () => {
384
219
  const root = createProjectFixture({ name: 'harness-retire-test' });
385
220
  await constraintsRetire('docs_freshness', { projectPath: root, reason: '直接退役', yes: true }, io);
386
221
 
387
222
  const output = io.outText();
388
- expect(output).toContain('Iron Law');
223
+ expect(output).toContain('error 级约束');
389
224
  expect(output).toContain('已退役');
390
225
 
391
226
  const config = readConfig(root);
392
227
  expect(config.constraints.docs_freshness.enabled).toBe(false);
393
228
  });
394
229
 
395
- it('--yes 直达非 iron(guideline)退役:不打印 iron 警示并落盘', async () => {
230
+ it('--yes 直达 warning 级退役:不打印 error 级警示并落盘', async () => {
396
231
  const root = createProjectFixture({ name: 'harness-retire-test' });
397
232
  await constraintsRetire('no_hardcoded_credentials', { projectPath: root, reason: '直接退役', yes: true }, io);
398
233
 
399
234
  const output = io.outText();
400
- expect(output).not.toContain('Iron Law');
235
+ expect(output).not.toContain('是一条 error 级约束');
401
236
  expect(output).toContain('已退役');
402
237
 
403
238
  const config = readConfig(root);
@@ -442,10 +277,10 @@ describe('runRetireInteractive 交互流程(注入 IO 流)', () => {
442
277
  return { input, output, text: () => chunks.join(''), done: () => clearInterval(timer) };
443
278
  }
444
279
 
445
- it('候选编号选择 + iron 二次确认拒绝 → 不落盘', async () => {
280
+ it('候选编号选择 + error 级二次确认拒绝 → 不落盘', async () => {
446
281
  const root = createProjectFixture({ name: 'harness-retire-test' });
447
- // 无 trace → 候选全部为零触发,1 号候选是第一条 iron(no_completion_without_verification)
448
- const streams = makeIo(['1', 'n']); // 选 1 号 → iron 确认拒绝
282
+ // 无 trace → 候选全部为零触发,1 号候选是第一条 error 级(no_completion_without_verification)
283
+ const streams = makeIo(['1', 'n']); // 选 1 号 → error 级确认拒绝
449
284
  const printed = captureLog();
450
285
 
451
286
  await runRetireInteractive(root, streams);
@@ -463,7 +298,6 @@ describe('runRetireInteractive 交互流程(注入 IO 流)', () => {
463
298
  const root = createProjectFixture({ name: 'harness-retire-test' });
464
299
  // 所有 check 约束给少量健康数据(低于一切候选阈值)
465
300
  const healthy = getEffectiveConstraints(root)
466
- .filter(c => c.kind === 'check')
467
301
  .flatMap(c => [
468
302
  { constraintId: c.id, result: 'pass' as const },
469
303
  { constraintId: c.id, result: 'pass' as const, timestamp: 1700000001000 },
@@ -492,7 +326,7 @@ describe('runRetireInteractive 交互流程(注入 IO 流)', () => {
492
326
  fs.mkdirSync(dir, { recursive: true });
493
327
  fs.writeFileSync(
494
328
  path.join(dir, 'traces.log'),
495
- '{"constraintId":"a","level":"iron_law","timestamp":1700000000000,"result":"pass"}\n{bad json\n{also bad\n',
329
+ '{"constraintId":"a","severity":"error","timestamp":1700000000000,"result":"pass"}\n{bad json\n{also bad\n',
496
330
  'utf-8'
497
331
  );
498
332
 
@@ -538,7 +372,7 @@ describe('runRetireInteractive 交互流程(注入 IO 流)', () => {
538
372
  fs.mkdirSync(dir, { recursive: true });
539
373
  fs.writeFileSync(
540
374
  path.join(dir, 'traces.log'),
541
- '{"constraintId":"a","level":"iron_law","timestamp":1700000000000,"result":"pass"}\n{bad json\n',
375
+ '{"constraintId":"a","severity":"error","timestamp":1700000000000,"result":"pass"}\n{bad json\n',
542
376
  'utf-8'
543
377
  );
544
378
 
@@ -551,32 +385,9 @@ describe('runRetireInteractive 交互流程(注入 IO 流)', () => {
551
385
  expect(readConfig(root).constraints.no_hardcoded_credentials.enabled).toBe(false);
552
386
  });
553
387
 
554
- it('无候选 → 手动输入 custom id → 确认执行 → 落 custom-constraints.yml', async () => {
555
- const root = createProjectFixture({ name: 'harness-retire-test' });
556
- writeCustom(root, CUSTOM_YML);
557
- // 所有 check 约束给少量健康数据(低于一切候选阈值)
558
- const healthy = getEffectiveConstraints(root)
559
- .filter(c => c.kind === 'check')
560
- .flatMap(c => [
561
- { constraintId: c.id, result: 'pass' as const },
562
- { constraintId: c.id, result: 'pass' as const, timestamp: 1700000001000 },
563
- { constraintId: c.id, result: 'fail' as const, timestamp: 1700000002000 },
564
- ]);
565
- writeProjectTraces(root, healthy);
566
-
567
- const streams = makeIo(['my_custom_rule', '作用对象消失', 'y']);
568
- await runRetireInteractive(root, streams);
569
- streams.done();
570
-
571
- const custom = readCustom(root).custom_constraints.my_custom_rule;
572
- expect(custom.retired.reason).toBe('作用对象消失');
573
- expect(fs.existsSync(path.join(root, '.harness', 'config.yml'))).toBe(false);
574
- });
575
-
576
388
  it('无候选 → 手动输入未知 id → 提示不存在并取消', async () => {
577
389
  const root = createProjectFixture({ name: 'harness-retire-test' });
578
390
  const healthy = getEffectiveConstraints(root)
579
- .filter(c => c.kind === 'check')
580
391
  .flatMap(c => [{ constraintId: c.id, result: 'pass' as const }]);
581
392
  writeProjectTraces(root, healthy);
582
393