@dommaker/harness 1.9.0 → 1.10.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 (253) hide show
  1. package/CHANGELOG.md +6 -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.js +2 -2
  20. package/dist/cli/commands/init.d.ts +0 -32
  21. package/dist/cli/commands/init.d.ts.map +1 -1
  22. package/dist/cli/commands/init.js +7 -149
  23. package/dist/cli/commands/init.js.map +1 -1
  24. package/dist/cli/commands/report.d.ts.map +1 -1
  25. package/dist/cli/commands/report.js +14 -17
  26. package/dist/cli/commands/report.js.map +1 -1
  27. package/dist/cli/commands/scaffold-templates.d.ts +0 -2
  28. package/dist/cli/commands/scaffold-templates.d.ts.map +1 -1
  29. package/dist/cli/commands/scaffold-templates.js +2 -37
  30. package/dist/cli/commands/scaffold-templates.js.map +1 -1
  31. package/dist/cli/commands/scaffold.d.ts +0 -2
  32. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  33. package/dist/cli/commands/scaffold.js +0 -10
  34. package/dist/cli/commands/scaffold.js.map +1 -1
  35. package/dist/cli/commands/status.js +10 -10
  36. package/dist/cli/commands/status.js.map +1 -1
  37. package/dist/cli/commands/sync-docs/agents-syncer.d.ts +0 -2
  38. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  39. package/dist/cli/commands/sync-docs/agents-syncer.js +4 -23
  40. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  41. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  42. package/dist/core/constraints/capabilities-parser.js +16 -4
  43. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  44. package/dist/core/constraints/checker.d.ts +15 -24
  45. package/dist/core/constraints/checker.d.ts.map +1 -1
  46. package/dist/core/constraints/checker.js +33 -72
  47. package/dist/core/constraints/checker.js.map +1 -1
  48. package/dist/core/constraints/checkers/docs-freshness.js +1 -1
  49. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  50. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -1
  51. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  52. package/dist/core/constraints/checkers/governance-presence.js +4 -3
  53. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  54. package/dist/core/constraints/checkers/index.d.ts +1 -1
  55. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  56. package/dist/core/constraints/checkers/index.js +4 -8
  57. package/dist/core/constraints/checkers/index.js.map +1 -1
  58. package/dist/core/constraints/checkers/iron-flags.d.ts +3 -4
  59. package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
  60. package/dist/core/constraints/checkers/iron-flags.js +4 -5
  61. package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
  62. package/dist/core/constraints/checkers/no-test-simplification.js +1 -1
  63. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  64. package/dist/core/constraints/checkers/types.d.ts +1 -1
  65. package/dist/core/constraints/checkers/types.js +1 -1
  66. package/dist/core/constraints/definitions/guidelines.d.ts +4 -4
  67. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  68. package/dist/core/constraints/definitions/guidelines.js +9 -25
  69. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  70. package/dist/core/constraints/definitions/iron-laws.d.ts +4 -4
  71. package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
  72. package/dist/core/constraints/definitions/iron-laws.js +10 -81
  73. package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
  74. package/dist/core/constraints/definitions.d.ts +7 -9
  75. package/dist/core/constraints/definitions.d.ts.map +1 -1
  76. package/dist/core/constraints/definitions.js +13 -16
  77. package/dist/core/constraints/definitions.js.map +1 -1
  78. package/dist/core/constraints/index.d.ts +2 -5
  79. package/dist/core/constraints/index.d.ts.map +1 -1
  80. package/dist/core/constraints/index.js +3 -13
  81. package/dist/core/constraints/index.js.map +1 -1
  82. package/dist/core/constraints/injection-writer.d.ts +10 -38
  83. package/dist/core/constraints/injection-writer.d.ts.map +1 -1
  84. package/dist/core/constraints/injection-writer.js +17 -54
  85. package/dist/core/constraints/injection-writer.js.map +1 -1
  86. package/dist/core/constraints/run-env.d.ts +1 -10
  87. package/dist/core/constraints/run-env.d.ts.map +1 -1
  88. package/dist/core/constraints/run-env.js +1 -22
  89. package/dist/core/constraints/run-env.js.map +1 -1
  90. package/dist/core/constraints/usage-report.d.ts +5 -7
  91. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  92. package/dist/core/constraints/usage-report.js +5 -10
  93. package/dist/core/constraints/usage-report.js.map +1 -1
  94. package/dist/core/effective-constraints.d.ts +12 -22
  95. package/dist/core/effective-constraints.d.ts.map +1 -1
  96. package/dist/core/effective-constraints.js +13 -30
  97. package/dist/core/effective-constraints.js.map +1 -1
  98. package/dist/core/index.d.ts +2 -2
  99. package/dist/core/index.d.ts.map +1 -1
  100. package/dist/core/index.js +3 -9
  101. package/dist/core/index.js.map +1 -1
  102. package/dist/core/project-config-loader.d.ts +4 -42
  103. package/dist/core/project-config-loader.d.ts.map +1 -1
  104. package/dist/core/project-config-loader.js +16 -125
  105. package/dist/core/project-config-loader.js.map +1 -1
  106. package/dist/core/validators/passes-gate.d.ts +2 -2
  107. package/dist/core/validators/passes-gate.js +6 -6
  108. package/dist/core/validators/passes-gate.js.map +1 -1
  109. package/dist/index.d.ts +7 -10
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +9 -18
  112. package/dist/index.js.map +1 -1
  113. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  114. package/dist/monitoring/trace-analyzer.js +11 -12
  115. package/dist/monitoring/trace-analyzer.js.map +1 -1
  116. package/dist/monitoring/traces.d.ts +4 -4
  117. package/dist/monitoring/traces.d.ts.map +1 -1
  118. package/dist/monitoring/traces.js +8 -8
  119. package/dist/monitoring/traces.js.map +1 -1
  120. package/dist/presets/standard.d.ts +8 -10
  121. package/dist/presets/standard.d.ts.map +1 -1
  122. package/dist/presets/standard.js +10 -16
  123. package/dist/presets/standard.js.map +1 -1
  124. package/dist/types/constraint.d.ts +24 -35
  125. package/dist/types/constraint.d.ts.map +1 -1
  126. package/dist/types/constraint.js +8 -7
  127. package/dist/types/constraint.js.map +1 -1
  128. package/dist/types/passes-gate.d.ts +5 -5
  129. package/dist/types/passes-gate.d.ts.map +1 -1
  130. package/dist/types/project-config.d.ts +8 -66
  131. package/dist/types/project-config.d.ts.map +1 -1
  132. package/dist/types/project-config.js +3 -2
  133. package/dist/types/project-config.js.map +1 -1
  134. package/dist/types/trace.d.ts +8 -10
  135. package/dist/types/trace.d.ts.map +1 -1
  136. package/package.json +2 -2
  137. package/src/CONTEXT.md +2 -2
  138. package/src/__tests__/checker-extra.test.ts +37 -61
  139. package/src/__tests__/checker.test.ts +10 -115
  140. package/src/__tests__/effective-constraints.test.ts +18 -83
  141. package/src/__tests__/iron-laws.test.ts +71 -90
  142. package/src/__tests__/passes-gate-check.test.ts +2 -2
  143. package/src/__tests__/project-config-loader-extra.test.ts +22 -123
  144. package/src/__tests__/project-config-loader.test.ts +5 -33
  145. package/src/__tests__/public-exports.test.ts +1 -7
  146. package/src/__tests__/public-type-surface.test.ts +2 -5
  147. package/src/__tests__/public-value-surface.test.ts +1 -7
  148. package/src/__tests__/skip-semantics.test.ts +21 -37
  149. package/src/__tests__/trace-analyzer-extra.test.ts +14 -14
  150. package/src/__tests__/trace-analyzer.test.ts +21 -21
  151. package/src/__tests__/trace-collector.test.ts +22 -22
  152. package/src/__tests__/traces.test.ts +27 -28
  153. package/src/cli/commands/CONTEXT.md +9 -9
  154. package/src/cli/commands/__tests__/check-read-count.test.ts +4 -15
  155. package/src/cli/commands/__tests__/check-skip-output.test.ts +14 -14
  156. package/src/cli/commands/__tests__/check.test.ts +14 -31
  157. package/src/cli/commands/__tests__/constraints-report.test.ts +12 -80
  158. package/src/cli/commands/__tests__/constraints-retire.test.ts +42 -231
  159. package/src/cli/commands/__tests__/init-governance-io.test.ts +12 -58
  160. package/src/cli/commands/__tests__/init-ondisk.test.ts +12 -20
  161. package/src/cli/commands/__tests__/init.test.ts +0 -11
  162. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +2 -2
  163. package/src/cli/commands/__tests__/report-extra.test.ts +4 -4
  164. package/src/cli/commands/__tests__/report.test.ts +1 -1
  165. package/src/cli/commands/__tests__/scaffold.test.ts +4 -14
  166. package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +3 -3
  167. package/src/cli/commands/__tests__/status-extra.test.ts +14 -14
  168. package/src/cli/commands/__tests__/status.test.ts +12 -12
  169. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +1 -1
  170. package/src/cli/commands/check.ts +43 -82
  171. package/src/cli/commands/constraints-report.ts +4 -59
  172. package/src/cli/commands/constraints-retire.ts +49 -154
  173. package/src/cli/commands/constraints.ts +11 -26
  174. package/src/cli/commands/definitions.ts +2 -2
  175. package/src/cli/commands/init.ts +8 -179
  176. package/src/cli/commands/report.ts +19 -23
  177. package/src/cli/commands/scaffold-templates.ts +1 -37
  178. package/src/cli/commands/scaffold.ts +0 -11
  179. package/src/cli/commands/status.ts +10 -10
  180. package/src/cli/commands/sync-docs/agents-syncer.ts +4 -25
  181. package/src/core/CONTEXT.md +9 -9
  182. package/src/core/constraints/__tests__/capability-counts.test.ts +5 -5
  183. package/src/core/constraints/__tests__/collect-constraints.test.ts +13 -8
  184. package/src/core/constraints/__tests__/git-evidence.test.ts +4 -7
  185. package/src/core/constraints/__tests__/injection-writer.test.ts +8 -61
  186. package/src/core/constraints/__tests__/run-env.test.ts +5 -34
  187. package/src/core/constraints/__tests__/trace-injection.test.ts +1 -1
  188. package/src/core/constraints/capabilities-parser.ts +17 -5
  189. package/src/core/constraints/checker.ts +39 -88
  190. package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +5 -8
  191. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +1 -53
  192. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +5 -25
  193. package/src/core/constraints/checkers/docs-freshness.ts +1 -1
  194. package/src/core/constraints/checkers/governance-presence.ts +4 -3
  195. package/src/core/constraints/checkers/index.ts +4 -10
  196. package/src/core/constraints/checkers/iron-flags.ts +3 -12
  197. package/src/core/constraints/checkers/no-test-simplification.ts +1 -1
  198. package/src/core/constraints/checkers/types.ts +1 -1
  199. package/src/core/constraints/definitions/guidelines.ts +8 -25
  200. package/src/core/constraints/definitions/iron-laws.ts +9 -82
  201. package/src/core/constraints/definitions.ts +14 -15
  202. package/src/core/constraints/index.ts +4 -19
  203. package/src/core/constraints/injection-writer.ts +16 -61
  204. package/src/core/constraints/run-env.ts +1 -33
  205. package/src/core/constraints/usage-report.ts +8 -15
  206. package/src/core/effective-constraints.ts +14 -45
  207. package/src/core/index.ts +3 -10
  208. package/src/core/project-config-loader.ts +20 -145
  209. package/src/core/validators/passes-gate.ts +6 -6
  210. package/src/failure/CONTEXT.md +1 -1
  211. package/src/hooks/__tests__/bootstrap.test.ts +3 -5
  212. package/src/index.ts +8 -18
  213. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +1 -1
  214. package/src/monitoring/__tests__/traces.test.ts +22 -22
  215. package/src/monitoring/trace-analyzer.ts +12 -13
  216. package/src/monitoring/traces.ts +8 -8
  217. package/src/presets/__tests__/standard.test.ts +12 -16
  218. package/src/presets/standard.ts +14 -23
  219. package/src/types/__tests__/types.test.ts +3 -3
  220. package/src/types/constraint.ts +26 -40
  221. package/src/types/passes-gate.ts +5 -5
  222. package/src/types/project-config.ts +8 -77
  223. package/src/types/trace.ts +8 -11
  224. package/dist/core/constraints/agent-prompt-renderer.d.ts +0 -32
  225. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +0 -1
  226. package/dist/core/constraints/agent-prompt-renderer.js +0 -55
  227. package/dist/core/constraints/agent-prompt-renderer.js.map +0 -1
  228. package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts +0 -6
  229. package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts.map +0 -1
  230. package/dist/core/constraints/checkers/no-bypass-checkpoint.js +0 -40
  231. package/dist/core/constraints/checkers/no-bypass-checkpoint.js.map +0 -1
  232. package/dist/core/constraints/definitions/prompts.d.ts +0 -12
  233. package/dist/core/constraints/definitions/prompts.d.ts.map +0 -1
  234. package/dist/core/constraints/definitions/prompts.js +0 -400
  235. package/dist/core/constraints/definitions/prompts.js.map +0 -1
  236. package/dist/core/constraints/injection-drift.d.ts +0 -61
  237. package/dist/core/constraints/injection-drift.d.ts.map +0 -1
  238. package/dist/core/constraints/injection-drift.js +0 -131
  239. package/dist/core/constraints/injection-drift.js.map +0 -1
  240. package/dist/core/constraints/injection-renderer.d.ts +0 -23
  241. package/dist/core/constraints/injection-renderer.d.ts.map +0 -1
  242. package/dist/core/constraints/injection-renderer.js +0 -44
  243. package/dist/core/constraints/injection-renderer.js.map +0 -1
  244. package/src/cli/commands/__tests__/check-drift.test.ts +0 -158
  245. package/src/cli/commands/__tests__/init-injection.test.ts +0 -466
  246. package/src/core/constraints/__tests__/agent-prompt-renderer.test.ts +0 -129
  247. package/src/core/constraints/__tests__/injection-drift.test.ts +0 -247
  248. package/src/core/constraints/agent-prompt-renderer.ts +0 -67
  249. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +0 -76
  250. package/src/core/constraints/checkers/no-bypass-checkpoint.ts +0 -40
  251. package/src/core/constraints/definitions/prompts.ts +0 -414
  252. package/src/core/constraints/injection-drift.ts +0 -142
  253. package/src/core/constraints/injection-renderer.ts +0 -44
@@ -1,466 +0,0 @@
1
- /**
2
- * init 注入重写测试(ADR-0001 P3)
3
- *
4
- * - renderConstraintsSection 纯函数
5
- * - setupClaudeMdConstraints:消费生效集、幂等、尊重 config.yml 禁用与 scenes
6
- * - setupClaudeMdOutputStyle:标记化三种情形(无段插入 / 旧 harness 段迁移 / 用户自有段跳过)
7
- */
8
-
9
- import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
10
- import { captureIO, type CapturingIO } from '../../command-contract';
11
- import * as fs from 'fs';
12
- import * as path from 'path';
13
- import {
14
- renderConstraintsSection,
15
- CONSTRAINTS_START_MARKER,
16
- CONSTRAINTS_END_MARKER,
17
- } from '../../../core/constraints/injection-renderer';
18
- import {
19
- setupClaudeMdConstraints,
20
- setupClaudeMdOutputStyle,
21
- setupAgentsMdConstraints,
22
- setupGovernanceConstraints,
23
- } from '../init';
24
- import { getEffectiveConstraints } from '../../../core/effective-constraints';
25
- import { getAllConstraints } from '../../../core/constraints/definitions';
26
- import type { Constraint } from '../../../types/constraint';
27
-
28
- const OUTPUT_STYLE_START = '<!-- HARNESS_OUTPUT_STYLE_START -->';
29
- const OUTPUT_STYLE_END = '<!-- HARNESS_OUTPUT_STYLE_END -->';
30
-
31
- const makeConstraint = (overrides: Partial<Constraint>): Constraint => ({
32
- id: 'x',
33
- kind: 'prompt',
34
- level: 'prompt',
35
- rule: 'R',
36
- message: 'M',
37
- trigger: 'manual',
38
- enforcement: 'none',
39
- ...overrides,
40
- });
41
-
42
- let io: CapturingIO;
43
- beforeEach(() => {
44
- io = captureIO();
45
- });
46
-
47
- describe('renderConstraintsSection(纯函数)', () => {
48
- it('按 level 分组渲染,含标记与版本行', () => {
49
- const constraints = [
50
- makeConstraint({ id: 'iron_a', kind: 'check', level: 'iron_law', promptInjection: 'iron 注入' }),
51
- makeConstraint({ id: 'guide_a', kind: 'check', level: 'guideline', promptInjection: 'guide 注入' }),
52
- makeConstraint({ id: 'prompt_a', level: 'prompt', promptInjection: 'prompt 注入' }),
53
- ];
54
-
55
- const section = renderConstraintsSection(constraints, '9.9.9');
56
-
57
- expect(section.startsWith(`${CONSTRAINTS_START_MARKER}\n<!-- version: 9.9.9 -->\n`)).toBe(true);
58
- expect(section.endsWith(`${CONSTRAINTS_END_MARKER}\n`)).toBe(true);
59
- expect(section).toContain('### Iron Laws');
60
- expect(section).toContain('### Guidelines');
61
- expect(section).toContain('### Prompts');
62
- expect(section).toContain('- **iron_a**: iron 注入');
63
- expect(section).toContain('- **guide_a**: guide 注入');
64
- expect(section).toContain('- **prompt_a**: prompt 注入');
65
- // 分组顺序:Iron Laws → Guidelines → Prompts
66
- expect(section.indexOf('### Iron Laws')).toBeLessThan(section.indexOf('### Guidelines'));
67
- expect(section.indexOf('### Guidelines')).toBeLessThan(section.indexOf('### Prompts'));
68
- });
69
-
70
- it('无 promptInjection 的 check 条目(如 docs_freshness)不出现在注入段', () => {
71
- const constraints = [
72
- makeConstraint({ id: 'docs_freshness', kind: 'check', level: 'iron_law' }),
73
- makeConstraint({ id: 'iron_a', kind: 'check', level: 'iron_law', promptInjection: 'iron 注入' }),
74
- ];
75
-
76
- const section = renderConstraintsSection(constraints, '1.0.0');
77
-
78
- expect(section).not.toContain('docs_freshness');
79
- expect(section).toContain('- **iron_a**: iron 注入');
80
- });
81
-
82
- it('内置全集渲染:docs_freshness 不出现,场景 prompt 由调用方过滤后传入', () => {
83
- const section = renderConstraintsSection(getAllConstraints(), '1.0.0');
84
- expect(section).not.toContain('docs_freshness');
85
- expect(section).toContain('- **no_fuzzy_completion_claim**');
86
- });
87
- });
88
-
89
- describe('setupClaudeMdConstraints(消费生效集)', () => {
90
- let tempDir: string;
91
- let claudeMdPath: string;
92
-
93
- const writeConfig = (yaml: string) => {
94
- fs.mkdirSync(path.join(tempDir, '.harness'), { recursive: true });
95
- fs.writeFileSync(path.join(tempDir, '.harness', 'config.yml'), yaml);
96
- };
97
-
98
- beforeEach(() => {
99
- tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-init-'));
100
- claudeMdPath = path.join(tempDir, 'CLAUDE.md');
101
- jest.spyOn(console, 'log').mockImplementation(() => {});
102
- });
103
-
104
- afterEach(() => {
105
- jest.restoreAllMocks();
106
- fs.rmSync(tempDir, { recursive: true, force: true });
107
- });
108
-
109
- it('CLAUDE.md 不存在时创建并写入约束段', async () => {
110
- await setupClaudeMdConstraints(tempDir, io);
111
-
112
- const content = fs.readFileSync(claudeMdPath, 'utf-8');
113
- expect(content).toContain('## Governance Rules');
114
- expect(content).toContain(CONSTRAINTS_START_MARKER);
115
- expect(content).toContain(CONSTRAINTS_END_MARKER);
116
- expect(content).toContain('### Iron Laws');
117
- expect(content).toContain('### Prompts (行为约束)');
118
- });
119
-
120
- it('连跑两次输出一致(幂等)', async () => {
121
- await setupClaudeMdConstraints(tempDir, io);
122
- const first = fs.readFileSync(claudeMdPath, 'utf-8');
123
- await setupClaudeMdConstraints(tempDir, io);
124
- const second = fs.readFileSync(claudeMdPath, 'utf-8');
125
- expect(second).toBe(first);
126
- });
127
-
128
- it('标记区间外内容保持不动', async () => {
129
- fs.writeFileSync(claudeMdPath, '# My Project\n\n用户自定义内容\n');
130
- await setupClaudeMdConstraints(tempDir, io);
131
- const content = fs.readFileSync(claudeMdPath, 'utf-8');
132
- expect(content).toContain('# My Project');
133
- expect(content).toContain('用户自定义内容');
134
- });
135
-
136
- it('标记残缺(只有单边):拒写不追加并告警(writer 半标记守护,候选1)', async () => {
137
- const broken = `# My Project\n\n${CONSTRAINTS_START_MARKER}\n半残段\n`;
138
- fs.writeFileSync(claudeMdPath, broken);
139
- await setupClaudeMdConstraints(tempDir, io);
140
- expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(broken);
141
- expect(io.outText()).toContain('残缺');
142
- });
143
-
144
- it('尊重 config.yml 禁用:注入段不含被禁条目', async () => {
145
- writeConfig('constraints:\n no_fuzzy_completion_claim:\n enabled: false\n');
146
- await setupClaudeMdConstraints(tempDir, io);
147
-
148
- const content = fs.readFileSync(claudeMdPath, 'utf-8');
149
- expect(content).not.toContain('no_fuzzy_completion_claim');
150
- expect(content).toContain('- **no_fix_without_root_cause**');
151
- });
152
-
153
- it('scenes 缺省不注入场景专属 prompt;配置 scenes 后注入', async () => {
154
- writeConfig('preset: standard\n');
155
- await setupClaudeMdConstraints(tempDir, io);
156
- let content = fs.readFileSync(claudeMdPath, 'utf-8');
157
- expect(content).not.toContain('no_skill_without_test');
158
-
159
- writeConfig('preset: standard\nscenes:\n - agent-skill\n');
160
- await setupClaudeMdConstraints(tempDir, io);
161
- content = fs.readFileSync(claudeMdPath, 'utf-8');
162
- expect(content).toContain('- **no_skill_without_test**');
163
- expect(content).not.toContain('no_model_for_deterministic');
164
- });
165
-
166
- it('注入段文本与 renderConstraintsSection(getEffectiveConstraints()) 一致', async () => {
167
- writeConfig('constraints:\n no_bypass_checkpoint:\n enabled: false\n');
168
- await setupClaudeMdConstraints(tempDir, io);
169
-
170
- const content = fs.readFileSync(claudeMdPath, 'utf-8');
171
- const start = content.indexOf(CONSTRAINTS_START_MARKER);
172
- const end = content.indexOf(CONSTRAINTS_END_MARKER) + CONSTRAINTS_END_MARKER.length + 1;
173
- const actual = content.slice(start, end);
174
- const version = /<!-- version: ([^ ]+) -->/.exec(content)![1];
175
- // P6 漂移校验同款比对:期望段(生效集渲染) vs 实际段
176
- expect(actual).toBe(renderConstraintsSection(getEffectiveConstraints(tempDir), version));
177
- });
178
- });
179
-
180
- describe('setupClaudeMdOutputStyle(标记化)', () => {
181
- let tempDir: string;
182
- let claudeMdPath: string;
183
-
184
- beforeEach(() => {
185
- tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-output-style-'));
186
- claudeMdPath = path.join(tempDir, 'CLAUDE.md');
187
- jest.spyOn(console, 'log').mockImplementation(() => {});
188
- });
189
-
190
- afterEach(() => {
191
- jest.restoreAllMocks();
192
- fs.rmSync(tempDir, { recursive: true, force: true });
193
- });
194
-
195
- it('CLAUDE.md 不存在时不创建(由 constraints 注入负责创建)', async () => {
196
- await setupClaudeMdOutputStyle(tempDir, io);
197
- expect(fs.existsSync(claudeMdPath)).toBe(false);
198
- });
199
-
200
- it('无 Output Style 段:插入带标记段到文件顶部,二次运行幂等', async () => {
201
- fs.writeFileSync(claudeMdPath, '# My Project\n\n正文\n');
202
- await setupClaudeMdOutputStyle(tempDir, io);
203
-
204
- const content = fs.readFileSync(claudeMdPath, 'utf-8');
205
- expect(content.startsWith(OUTPUT_STYLE_START)).toBe(true);
206
- expect(content).toContain('## Output Style');
207
- expect(content).toContain(OUTPUT_STYLE_END);
208
- expect(content).toContain('# My Project');
209
-
210
- await setupClaudeMdOutputStyle(tempDir, io);
211
- expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(content);
212
- });
213
-
214
- it('旧版 harness 无标记段(特征串匹配):迁移为标记版', async () => {
215
- const legacy = [
216
- '## Output Style',
217
- '',
218
- 'Terse like caveman. Technical substance exact. Only fluff die.',
219
- 'Drop: articles, filler (just/really/basically), pleasantries (sure/certainly/happy to), hedging.',
220
- '',
221
- '# My Project',
222
- '',
223
- ].join('\n');
224
- fs.writeFileSync(claudeMdPath, legacy);
225
-
226
- await setupClaudeMdOutputStyle(tempDir, io);
227
- const content = fs.readFileSync(claudeMdPath, 'utf-8');
228
-
229
- expect(content).toContain(OUTPUT_STYLE_START);
230
- expect(content).toContain(OUTPUT_STYLE_END);
231
- expect(content).toContain('Terse like caveman');
232
- expect(content).toContain('# My Project');
233
- // 迁移后只剩一个 Output Style 标题
234
- expect(content.match(/^## Output Style$/gm)).toHaveLength(1);
235
- // 幂等
236
- await setupClaudeMdOutputStyle(tempDir, io);
237
- expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(content);
238
- });
239
-
240
- it('用户自写 Output Style 段(特征串不匹配):不动、不追加、console 提示', async () => {
241
- const userContent = '# My Project\n\n## Output Style\n\n用户自己的输出风格要求。\n';
242
- fs.writeFileSync(claudeMdPath, userContent);
243
-
244
- await setupClaudeMdOutputStyle(tempDir, io);
245
-
246
- expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(userContent);
247
- expect(io.outText()).toContain('跳过');
248
- });
249
- });
250
-
251
- const GOVERNANCE_PRESERVE_BEGIN = '<!-- PRESERVE:governance -->';
252
- const GOVERNANCE_PRESERVE_END = '<!-- /PRESERVE:governance -->';
253
-
254
- describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.md PRESERVE:governance 段)', () => {
255
- let tempDir: string;
256
- let agentsMdPath: string;
257
-
258
- beforeEach(() => {
259
- tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-init-agents-'));
260
- agentsMdPath = path.join(tempDir, 'AGENTS.md');
261
- jest.spyOn(console, 'log').mockImplementation(() => {});
262
- });
263
-
264
- afterEach(() => {
265
- jest.restoreAllMocks();
266
- fs.rmSync(tempDir, { recursive: true, force: true });
267
- });
268
-
269
- it('AGENTS.md 不存在时创建骨架并写入 PRESERVE:governance 段', async () => {
270
- await setupAgentsMdConstraints(tempDir, io);
271
-
272
- const content = fs.readFileSync(agentsMdPath, 'utf-8');
273
- expect(content).toContain('# AGENTS.md');
274
- expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
275
- expect(content).toContain(GOVERNANCE_PRESERVE_END);
276
- expect(content).toContain('## Governance Rules');
277
- expect(content).toContain(CONSTRAINTS_START_MARKER);
278
- expect(content).toContain(CONSTRAINTS_END_MARKER);
279
- expect(content).toContain('### Iron Laws');
280
- // PRESERVE 标记在约束段之外(sync-docs 重新生成时整段保留)
281
- expect(content.indexOf(GOVERNANCE_PRESERVE_BEGIN)).toBeLessThan(content.indexOf(CONSTRAINTS_START_MARKER));
282
- expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
283
- });
284
-
285
- it('连跑两次输出一致(幂等)', async () => {
286
- await setupAgentsMdConstraints(tempDir, io);
287
- const first = fs.readFileSync(agentsMdPath, 'utf-8');
288
- await setupAgentsMdConstraints(tempDir, io);
289
- expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(first);
290
- });
291
-
292
- it('已有 PRESERVE:governance 段(含标记+段内手写内容):只换标记区间,手写内容原样保留', async () => {
293
- // 回归:曾把 PRESERVE 段内 HARNESS 标记之外的手写内容(治理契约引言/流程等)清空
294
- fs.writeFileSync(agentsMdPath, [
295
- '# AGENTS.md',
296
- '',
297
- '机器生成导读(保持不变)',
298
- '',
299
- GOVERNANCE_PRESERVE_BEGIN,
300
- '## Governance Rules',
301
- CONSTRAINTS_START_MARKER,
302
- '<!-- version: 0.0.0 -->',
303
- '过期的旧约束内容',
304
- CONSTRAINTS_END_MARKER,
305
- '',
306
- '治理契约引言(手写,保留)',
307
- '',
308
- '### 治理变更流程(手写,保留)',
309
- GOVERNANCE_PRESERVE_END,
310
- '',
311
- '<!-- PRESERVE:other -->',
312
- '另一个手写段(保持不动)',
313
- '<!-- /PRESERVE:other -->',
314
- '',
315
- ].join('\n'));
316
-
317
- await setupAgentsMdConstraints(tempDir, io);
318
-
319
- const content = fs.readFileSync(agentsMdPath, 'utf-8');
320
- expect(content).toContain('机器生成导读(保持不变)');
321
- expect(content).toContain('另一个手写段(保持不动)');
322
- expect(content).not.toContain('过期的旧约束内容');
323
- expect(content).toContain(CONSTRAINTS_START_MARKER);
324
- // 段内标记之外的手写内容原样保留
325
- expect(content).toContain('治理契约引言(手写,保留)');
326
- expect(content).toContain('### 治理变更流程(手写,保留)');
327
- expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf('治理契约引言(手写,保留)'));
328
- expect(content.indexOf('### 治理变更流程(手写,保留)')).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
329
- // 幂等
330
- await setupAgentsMdConstraints(tempDir, io);
331
- expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(content);
332
- });
333
-
334
- it('已有 PRESERVE:governance 段但无 HARNESS 标记(纯手写段):段尾追加注入段,手写内容不动', async () => {
335
- fs.writeFileSync(agentsMdPath, [
336
- '# AGENTS.md',
337
- '',
338
- GOVERNANCE_PRESERVE_BEGIN,
339
- '治理契约引言(手写,保留)',
340
- '',
341
- '### 发布纪律(手写,保留)',
342
- GOVERNANCE_PRESERVE_END,
343
- '',
344
- ].join('\n'));
345
-
346
- await setupAgentsMdConstraints(tempDir, io);
347
-
348
- const content = fs.readFileSync(agentsMdPath, 'utf-8');
349
- expect(content).toContain('治理契约引言(手写,保留)');
350
- expect(content).toContain('### 发布纪律(手写,保留)');
351
- expect(content).toContain(CONSTRAINTS_START_MARKER);
352
- // 注入段追加在手写内容之后、PRESERVE 段内
353
- expect(content.indexOf('### 发布纪律(手写,保留)')).toBeLessThan(content.indexOf(CONSTRAINTS_START_MARKER));
354
- expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
355
- // 幂等
356
- await setupAgentsMdConstraints(tempDir, io);
357
- expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(content);
358
- });
359
-
360
- it('AGENTS.md 存在但无 PRESERVE:governance 段:末尾追加,既有内容不动', async () => {
361
- fs.writeFileSync(agentsMdPath, '# AGENTS.md\n\n既有导读内容\n');
362
-
363
- await setupAgentsMdConstraints(tempDir, io);
364
-
365
- const content = fs.readFileSync(agentsMdPath, 'utf-8');
366
- expect(content).toContain('既有导读内容');
367
- expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
368
- expect(content.indexOf('既有导读内容')).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_BEGIN));
369
- });
370
-
371
- it('PRESERVE:governance 标记残缺(只有单边):不写入并告警', async () => {
372
- const original = `# AGENTS.md\n\n${GOVERNANCE_PRESERVE_BEGIN}\n残缺的段\n`;
373
- fs.writeFileSync(agentsMdPath, original);
374
-
375
- await setupAgentsMdConstraints(tempDir, io);
376
-
377
- expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(original);
378
- expect(io.outText()).toContain('残缺');
379
- });
380
-
381
- it('PRESERVE 段内 HARNESS 标记残缺(单边):拒写并告警(候选1 守护,旧行为会再追加一份注入段)', async () => {
382
- const broken = [
383
- '# AGENTS.md',
384
- '',
385
- GOVERNANCE_PRESERVE_BEGIN,
386
- '手写契约',
387
- '',
388
- CONSTRAINTS_START_MARKER,
389
- '半残注入',
390
- GOVERNANCE_PRESERVE_END,
391
- '',
392
- ].join('\n');
393
- fs.writeFileSync(agentsMdPath, broken);
394
- await setupAgentsMdConstraints(tempDir, io);
395
- expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(broken);
396
- expect(io.outText()).toContain('残缺');
397
- });
398
-
399
- it('段内注入文本与 renderConstraintsSection(getEffectiveConstraints()) 一致', async () => {
400
- await setupAgentsMdConstraints(tempDir, io);
401
-
402
- const content = fs.readFileSync(agentsMdPath, 'utf-8');
403
- const start = content.indexOf(CONSTRAINTS_START_MARKER);
404
- const end = content.indexOf(CONSTRAINTS_END_MARKER) + CONSTRAINTS_END_MARKER.length + 1;
405
- const actual = content.slice(start, end);
406
- const version = /<!-- version: ([^ ]+) -->/.exec(content)![1];
407
- expect(actual).toBe(renderConstraintsSection(getEffectiveConstraints(tempDir), version));
408
- });
409
- });
410
-
411
- describe('setupGovernanceConstraints(落点路由)', () => {
412
- let tempDir: string;
413
-
414
- beforeEach(() => {
415
- tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-init-routing-'));
416
- jest.spyOn(console, 'log').mockImplementation(() => {});
417
- });
418
-
419
- afterEach(() => {
420
- jest.restoreAllMocks();
421
- fs.rmSync(tempDir, { recursive: true, force: true });
422
- });
423
-
424
- it('新仓(无 CLAUDE.md):治理契约写向 AGENTS.md,不创建 CLAUDE.md', async () => {
425
- await setupGovernanceConstraints(tempDir, io);
426
-
427
- expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(true);
428
- expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(false);
429
- const content = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
430
- expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
431
- expect(content).toContain(CONSTRAINTS_START_MARKER);
432
- });
433
-
434
- it('旧模型仓(CLAUDE.md 已有 HARNESS_CONSTRAINTS 标记):继续写 CLAUDE.md,不动 AGENTS.md', async () => {
435
- fs.writeFileSync(path.join(tempDir, 'CLAUDE.md'), [
436
- '# CLAUDE.md',
437
- '',
438
- '## Governance Rules',
439
- CONSTRAINTS_START_MARKER,
440
- '<!-- version: 0.0.0 -->',
441
- '旧约束',
442
- CONSTRAINTS_END_MARKER,
443
- '',
444
- ].join('\n'));
445
- fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), '# AGENTS.md\n\n既有导读\n');
446
-
447
- await setupGovernanceConstraints(tempDir, io);
448
-
449
- const claude = fs.readFileSync(path.join(tempDir, 'CLAUDE.md'), 'utf-8');
450
- expect(claude).not.toContain('旧约束');
451
- expect(claude).toContain('### Iron Laws');
452
- // AGENTS.md 不被写入治理段
453
- expect(fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8')).toBe('# AGENTS.md\n\n既有导读\n');
454
- });
455
-
456
- it('旧模型仓(CLAUDE.md 有无标记 Governance Rules 块):路由到 CLAUDE.md', async () => {
457
- fs.writeFileSync(path.join(tempDir, 'CLAUDE.md'), '# CLAUDE.md\n\n## Governance Rules\n\n手写条款\n');
458
-
459
- await setupGovernanceConstraints(tempDir, io);
460
-
461
- const claude = fs.readFileSync(path.join(tempDir, 'CLAUDE.md'), 'utf-8');
462
- expect(claude).toContain('手写条款');
463
- expect(claude).toContain(CONSTRAINTS_START_MARKER);
464
- expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(false);
465
- });
466
- });
@@ -1,129 +0,0 @@
1
- /**
2
- * trigger 参数化约束分组渲染 API 测试(H6/G6,收编 studio prompt-injection.ts)
3
- *
4
- * 语义:
5
- * - 入参 = 触发条件(单 trigger 或数组),无 role 概念(role→trigger 路由留 studio)
6
- * - 数据源 = getEffectiveConstraints(内置 → preset → config.yml 禁用 → custom 追加 → scenes 过滤)
7
- * - 输出按层级分组:铁律 → 指导原则 → 行为提示;无 promptInjection 的约束不渲染
8
- */
9
-
10
- import * as fs from 'fs';
11
- import * as os from 'os';
12
- import * as path from 'path';
13
- import { renderConstraintsByTrigger } from '../agent-prompt-renderer';
14
- import { renderConstraintsByTrigger as PublicRenderConstraintsByTrigger } from '../../../index';
15
-
16
- describe('公开导出(根 barrel)', () => {
17
- test('renderConstraintsByTrigger 经 src/index.ts 公开导出', () => {
18
- expect(PublicRenderConstraintsByTrigger).toBe(renderConstraintsByTrigger);
19
- });
20
- });
21
-
22
- /** 创建临时项目目录,可按相对路径写入 .harness/config.yml 等夹具文件 */
23
- function makeProject(files: Record<string, string>): string {
24
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'h6-render-'));
25
- for (const [rel, content] of Object.entries(files)) {
26
- const target = path.join(dir, rel);
27
- fs.mkdirSync(path.dirname(target), { recursive: true });
28
- fs.writeFileSync(target, content, 'utf-8');
29
- }
30
- return dir;
31
- }
32
-
33
- describe('renderConstraintsByTrigger', () => {
34
- test('内置约束按层级分组渲染:铁律 → 指导原则 → 行为提示', () => {
35
- const projectRoot = makeProject({});
36
- const out = renderConstraintsByTrigger('code_implementation', { projectRoot });
37
-
38
- expect(out).toContain('## 行为约束(前置声明)');
39
- expect(out).toContain('### 铁律(绝对禁止,无例外)');
40
- expect(out).toContain('- **no_completion_without_verification**:');
41
- expect(out).toContain('### 指导原则(优先建议)');
42
- expect(out).toContain('- **no_hardcoded_credentials**:');
43
- expect(out).toContain('### 行为提示');
44
- expect(out).toContain('- **no_code_without_test**:');
45
-
46
- // 分组顺序:铁律 < 指导原则 < 行为提示
47
- const ironIdx = out.indexOf('### 铁律(绝对禁止,无例外)');
48
- const guideIdx = out.indexOf('### 指导原则(优先建议)');
49
- const promptIdx = out.indexOf('### 行为提示');
50
- expect(ironIdx).toBeGreaterThanOrEqual(0);
51
- expect(guideIdx).toBeGreaterThan(ironIdx);
52
- expect(promptIdx).toBeGreaterThan(guideIdx);
53
- });
54
-
55
- test('无 promptInjection 的约束不渲染(即使 trigger 匹配)', () => {
56
- const projectRoot = makeProject({});
57
- // docs_freshness / capability_sync / context_doc_sync 无 promptInjection
58
- const out = renderConstraintsByTrigger('module_modification', { projectRoot });
59
-
60
- expect(out).not.toContain('- **docs_freshness**:');
61
- expect(out).not.toContain('- **capability_sync**:');
62
- expect(out).not.toContain('- **context_doc_sync**:');
63
- });
64
-
65
- test('支持 trigger 数组入参,仅匹配交集约束', () => {
66
- const projectRoot = makeProject({});
67
- const out = renderConstraintsByTrigger(['test_creation'], { projectRoot });
68
-
69
- expect(out).toContain('- **no_test_simplification**:');
70
- // no_completion_without_verification 的 trigger 为 code_implementation,不匹配
71
- expect(out).not.toContain('- **no_completion_without_verification**:');
72
- });
73
-
74
- test('无匹配约束返回空字符串', () => {
75
- const projectRoot = makeProject({});
76
- expect(renderConstraintsByTrigger('nonexistent_trigger_xyz', { projectRoot })).toBe('');
77
- expect(renderConstraintsByTrigger([], { projectRoot })).toBe('');
78
- });
79
-
80
- test('config.yml 禁用的约束不进入渲染(生效集数据源)', () => {
81
- const projectRoot = makeProject({
82
- '.harness/config.yml': [
83
- 'constraints:',
84
- ' no_hardcoded_credentials:',
85
- ' enabled: false',
86
- '',
87
- ].join('\n'),
88
- });
89
- const out = renderConstraintsByTrigger('code_implementation', { projectRoot });
90
-
91
- expect(out).not.toContain('- **no_hardcoded_credentials**:');
92
- // 同 trigger 的其它约束仍渲染
93
- expect(out).toContain('- **no_completion_without_verification**:');
94
- });
95
-
96
- test('custom 约束追加进生效集并参与分组渲染', () => {
97
- const projectRoot = makeProject({
98
- '.harness/config.yml': [
99
- 'custom_constraints:',
100
- ' my_custom_rule:',
101
- ' level: prompt',
102
- ' trigger: code_implementation',
103
- ' rule: CUSTOM RULE',
104
- ' message: 自定义约束消息',
105
- ' promptInjection: 自定义行为提示文本',
106
- '',
107
- ].join('\n'),
108
- });
109
- const out = renderConstraintsByTrigger('code_implementation', { projectRoot });
110
-
111
- expect(out).toContain('- **my_custom_rule**: 自定义行为提示文本');
112
- const promptIdx = out.indexOf('### 行为提示');
113
- const customIdx = out.indexOf('- **my_custom_rule**:');
114
- expect(customIdx).toBeGreaterThan(promptIdx);
115
- });
116
-
117
- test('scenes 过滤:带 appliesTo 的场景专属 prompt 默认不渲染,声明场景后渲染', () => {
118
- // no_skill_without_test:appliesTo ['agent-skill'],trigger module_creation
119
- const plain = makeProject({});
120
- expect(renderConstraintsByTrigger('module_creation', { projectRoot: plain }))
121
- .not.toContain('- **no_skill_without_test**:');
122
-
123
- const withScene = makeProject({
124
- '.harness/config.yml': 'scenes:\n - agent-skill\n',
125
- });
126
- expect(renderConstraintsByTrigger('module_creation', { projectRoot: withScene }))
127
- .toContain('- **no_skill_without_test**:');
128
- });
129
- });