@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.
- package/CHANGELOG.md +17 -0
- package/README.md +9 -12
- package/dist/cli/commands/check.d.ts +3 -3
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +41 -78
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/constraints-report.d.ts +2 -10
- package/dist/cli/commands/constraints-report.d.ts.map +1 -1
- package/dist/cli/commands/constraints-report.js +4 -57
- package/dist/cli/commands/constraints-report.js.map +1 -1
- package/dist/cli/commands/constraints-retire.d.ts +9 -23
- package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
- package/dist/cli/commands/constraints-retire.js +39 -121
- package/dist/cli/commands/constraints-retire.js.map +1 -1
- package/dist/cli/commands/constraints.d.ts +3 -8
- package/dist/cli/commands/constraints.d.ts.map +1 -1
- package/dist/cli/commands/constraints.js +8 -23
- package/dist/cli/commands/constraints.js.map +1 -1
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +7 -12
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/init.d.ts +0 -32
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +7 -149
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +14 -17
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/scaffold-templates.d.ts +1 -3
- package/dist/cli/commands/scaffold-templates.d.ts.map +1 -1
- package/dist/cli/commands/scaffold-templates.js +2 -53
- package/dist/cli/commands/scaffold-templates.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +0 -2
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +0 -10
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/commands/status.js +10 -10
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.d.ts +0 -2
- package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.js +4 -23
- package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
- package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
- package/dist/core/constraints/capabilities-parser.js +16 -4
- package/dist/core/constraints/capabilities-parser.js.map +1 -1
- package/dist/core/constraints/checker.d.ts +15 -24
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +33 -72
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
- package/dist/core/constraints/checkers/governance-presence.d.ts +2 -1
- package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
- package/dist/core/constraints/checkers/governance-presence.js +4 -3
- package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
- package/dist/core/constraints/checkers/index.d.ts +1 -1
- package/dist/core/constraints/checkers/index.d.ts.map +1 -1
- package/dist/core/constraints/checkers/index.js +4 -8
- package/dist/core/constraints/checkers/index.js.map +1 -1
- package/dist/core/constraints/checkers/iron-flags.d.ts +3 -4
- package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
- package/dist/core/constraints/checkers/iron-flags.js +4 -5
- package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
- package/dist/core/constraints/checkers/no-test-simplification.js +1 -1
- package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
- package/dist/core/constraints/checkers/types.d.ts +1 -1
- package/dist/core/constraints/checkers/types.js +1 -1
- package/dist/core/constraints/definitions/guidelines.d.ts +4 -4
- package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
- package/dist/core/constraints/definitions/guidelines.js +9 -25
- package/dist/core/constraints/definitions/guidelines.js.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.d.ts +4 -4
- package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.js +10 -81
- package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
- package/dist/core/constraints/definitions.d.ts +7 -9
- package/dist/core/constraints/definitions.d.ts.map +1 -1
- package/dist/core/constraints/definitions.js +13 -16
- package/dist/core/constraints/definitions.js.map +1 -1
- package/dist/core/constraints/index.d.ts +2 -5
- package/dist/core/constraints/index.d.ts.map +1 -1
- package/dist/core/constraints/index.js +3 -13
- package/dist/core/constraints/index.js.map +1 -1
- package/dist/core/constraints/injection-writer.d.ts +10 -38
- package/dist/core/constraints/injection-writer.d.ts.map +1 -1
- package/dist/core/constraints/injection-writer.js +17 -54
- package/dist/core/constraints/injection-writer.js.map +1 -1
- package/dist/core/constraints/run-env.d.ts +1 -10
- package/dist/core/constraints/run-env.d.ts.map +1 -1
- package/dist/core/constraints/run-env.js +1 -22
- package/dist/core/constraints/run-env.js.map +1 -1
- package/dist/core/constraints/usage-report.d.ts +6 -8
- package/dist/core/constraints/usage-report.d.ts.map +1 -1
- package/dist/core/constraints/usage-report.js +6 -11
- package/dist/core/constraints/usage-report.js.map +1 -1
- package/dist/core/effective-constraints.d.ts +12 -22
- package/dist/core/effective-constraints.d.ts.map +1 -1
- package/dist/core/effective-constraints.js +13 -30
- package/dist/core/effective-constraints.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -9
- package/dist/core/index.js.map +1 -1
- package/dist/core/project-config-loader.d.ts +4 -42
- package/dist/core/project-config-loader.d.ts.map +1 -1
- package/dist/core/project-config-loader.js +16 -125
- package/dist/core/project-config-loader.js.map +1 -1
- package/dist/core/validators/passes-gate.d.ts +2 -2
- package/dist/core/validators/passes-gate.js +6 -6
- package/dist/core/validators/passes-gate.js.map +1 -1
- package/dist/index.d.ts +9 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -18
- package/dist/index.js.map +1 -1
- package/dist/knowledge/lint.d.ts +17 -0
- package/dist/knowledge/lint.d.ts.map +1 -1
- package/dist/knowledge/lint.js +52 -0
- package/dist/knowledge/lint.js.map +1 -1
- package/dist/knowledge/store.d.ts +7 -0
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +48 -13
- package/dist/knowledge/store.js.map +1 -1
- package/dist/knowledge/types.d.ts +5 -1
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js +5 -1
- package/dist/knowledge/types.js.map +1 -1
- package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
- package/dist/monitoring/trace-analyzer.js +11 -12
- package/dist/monitoring/trace-analyzer.js.map +1 -1
- package/dist/monitoring/traces.d.ts +4 -4
- package/dist/monitoring/traces.d.ts.map +1 -1
- package/dist/monitoring/traces.js +8 -8
- package/dist/monitoring/traces.js.map +1 -1
- package/dist/presets/standard.d.ts +8 -10
- package/dist/presets/standard.d.ts.map +1 -1
- package/dist/presets/standard.js +10 -16
- package/dist/presets/standard.js.map +1 -1
- package/dist/types/constraint.d.ts +24 -35
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js +8 -7
- package/dist/types/constraint.js.map +1 -1
- package/dist/types/passes-gate.d.ts +5 -5
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/dist/types/project-config.d.ts +8 -66
- package/dist/types/project-config.d.ts.map +1 -1
- package/dist/types/project-config.js +3 -2
- package/dist/types/project-config.js.map +1 -1
- package/dist/types/trace.d.ts +8 -10
- package/dist/types/trace.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/CONTEXT.md +2 -2
- package/src/__tests__/checker-extra.test.ts +37 -61
- package/src/__tests__/checker.test.ts +10 -115
- package/src/__tests__/effective-constraints.test.ts +18 -83
- package/src/__tests__/iron-laws.test.ts +71 -90
- package/src/__tests__/passes-gate-check.test.ts +2 -2
- package/src/__tests__/project-config-loader-extra.test.ts +22 -123
- package/src/__tests__/project-config-loader.test.ts +5 -33
- package/src/__tests__/public-exports.test.ts +8 -7
- package/src/__tests__/public-type-surface.test.ts +7 -5
- package/src/__tests__/public-value-surface.test.ts +1 -7
- package/src/__tests__/skip-semantics.test.ts +21 -37
- package/src/__tests__/trace-analyzer-extra.test.ts +14 -14
- package/src/__tests__/trace-analyzer.test.ts +21 -21
- package/src/__tests__/trace-collector.test.ts +22 -22
- package/src/__tests__/traces.test.ts +27 -28
- package/src/__tests__/usage-report-public-export.test.ts +21 -0
- package/src/cli/commands/CONTEXT.md +12 -12
- package/src/cli/commands/__tests__/check-read-count.test.ts +4 -15
- package/src/cli/commands/__tests__/check-skip-output.test.ts +14 -14
- package/src/cli/commands/__tests__/check.test.ts +14 -31
- package/src/cli/commands/__tests__/constraints-report-json-flag.test.ts +101 -0
- package/src/cli/commands/__tests__/constraints-report.test.ts +12 -80
- package/src/cli/commands/__tests__/constraints-retire.test.ts +42 -231
- package/src/cli/commands/__tests__/init-governance-io.test.ts +12 -58
- package/src/cli/commands/__tests__/init-ondisk.test.ts +13 -21
- package/src/cli/commands/__tests__/init.test.ts +1 -16
- package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +1 -1
- package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +1 -1
- package/src/cli/commands/__tests__/project-path-anchoring.test.ts +2 -2
- package/src/cli/commands/__tests__/registry.test.ts +1 -1
- package/src/cli/commands/__tests__/report-extra.test.ts +4 -4
- package/src/cli/commands/__tests__/report.test.ts +1 -1
- package/src/cli/commands/__tests__/scaffold.test.ts +4 -14
- package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +3 -3
- package/src/cli/commands/__tests__/status-extra.test.ts +14 -14
- package/src/cli/commands/__tests__/status.test.ts +12 -12
- package/src/cli/commands/__tests__/sync-docs-agents.test.ts +1 -1
- package/src/cli/commands/check.ts +43 -82
- package/src/cli/commands/constraints-report.ts +4 -59
- package/src/cli/commands/constraints-retire.ts +49 -154
- package/src/cli/commands/constraints.ts +11 -26
- package/src/cli/commands/definitions.ts +7 -12
- package/src/cli/commands/init.ts +8 -179
- package/src/cli/commands/report.ts +19 -23
- package/src/cli/commands/scaffold-templates.ts +1 -53
- package/src/cli/commands/scaffold.ts +0 -11
- package/src/cli/commands/status.ts +10 -10
- package/src/cli/commands/sync-docs/agents-syncer.ts +4 -25
- package/src/core/CONTEXT.md +9 -9
- package/src/core/constraints/__tests__/capability-counts.test.ts +5 -5
- package/src/core/constraints/__tests__/collect-constraints.test.ts +13 -8
- package/src/core/constraints/__tests__/git-evidence.test.ts +4 -7
- package/src/core/constraints/__tests__/injection-writer.test.ts +8 -61
- package/src/core/constraints/__tests__/run-env.test.ts +5 -34
- package/src/core/constraints/__tests__/trace-injection.test.ts +1 -1
- package/src/core/constraints/capabilities-parser.ts +17 -5
- package/src/core/constraints/checker.ts +39 -88
- package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +5 -8
- package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +1 -53
- package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +5 -25
- package/src/core/constraints/checkers/docs-freshness.ts +1 -1
- package/src/core/constraints/checkers/governance-presence.ts +4 -3
- package/src/core/constraints/checkers/index.ts +4 -10
- package/src/core/constraints/checkers/iron-flags.ts +3 -12
- package/src/core/constraints/checkers/no-test-simplification.ts +1 -1
- package/src/core/constraints/checkers/types.ts +1 -1
- package/src/core/constraints/definitions/guidelines.ts +8 -25
- package/src/core/constraints/definitions/iron-laws.ts +9 -82
- package/src/core/constraints/definitions.ts +14 -15
- package/src/core/constraints/index.ts +4 -19
- package/src/core/constraints/injection-writer.ts +16 -61
- package/src/core/constraints/run-env.ts +1 -33
- package/src/core/constraints/usage-report.ts +9 -16
- package/src/core/effective-constraints.ts +14 -45
- package/src/core/index.ts +3 -10
- package/src/core/project-config-loader.ts +20 -145
- package/src/core/validators/passes-gate.ts +6 -6
- package/src/failure/CONTEXT.md +1 -1
- package/src/hooks/__tests__/bootstrap.test.ts +3 -5
- package/src/index.ts +28 -18
- package/src/knowledge/CONTEXT.md +4 -3
- package/src/knowledge/__tests__/lint.test.ts +84 -0
- package/src/knowledge/__tests__/store.test.ts +70 -0
- package/src/knowledge/lint.ts +60 -1
- package/src/knowledge/store.ts +48 -11
- package/src/knowledge/types.ts +7 -1
- package/src/monitoring/CONTEXT.md +1 -1
- package/src/monitoring/__tests__/trace-file-anchoring.test.ts +1 -1
- package/src/monitoring/__tests__/traces.test.ts +22 -22
- package/src/monitoring/trace-analyzer.ts +12 -13
- package/src/monitoring/traces.ts +8 -8
- package/src/presets/__tests__/standard.test.ts +12 -16
- package/src/presets/standard.ts +14 -23
- package/src/types/__tests__/types.test.ts +3 -3
- package/src/types/constraint.ts +26 -40
- package/src/types/passes-gate.ts +5 -5
- package/src/types/project-config.ts +8 -77
- package/src/types/trace.ts +8 -11
- package/dist/cli/commands/posteval-plan.d.ts +0 -15
- package/dist/cli/commands/posteval-plan.d.ts.map +0 -1
- package/dist/cli/commands/posteval-plan.js +0 -86
- package/dist/cli/commands/posteval-plan.js.map +0 -1
- package/dist/core/constraints/agent-prompt-renderer.d.ts +0 -32
- package/dist/core/constraints/agent-prompt-renderer.d.ts.map +0 -1
- package/dist/core/constraints/agent-prompt-renderer.js +0 -55
- package/dist/core/constraints/agent-prompt-renderer.js.map +0 -1
- package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts +0 -6
- package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts.map +0 -1
- package/dist/core/constraints/checkers/no-bypass-checkpoint.js +0 -40
- package/dist/core/constraints/checkers/no-bypass-checkpoint.js.map +0 -1
- package/dist/core/constraints/definitions/prompts.d.ts +0 -12
- package/dist/core/constraints/definitions/prompts.d.ts.map +0 -1
- package/dist/core/constraints/definitions/prompts.js +0 -400
- package/dist/core/constraints/definitions/prompts.js.map +0 -1
- package/dist/core/constraints/injection-drift.d.ts +0 -61
- package/dist/core/constraints/injection-drift.d.ts.map +0 -1
- package/dist/core/constraints/injection-drift.js +0 -131
- package/dist/core/constraints/injection-drift.js.map +0 -1
- package/dist/core/constraints/injection-renderer.d.ts +0 -23
- package/dist/core/constraints/injection-renderer.d.ts.map +0 -1
- package/dist/core/constraints/injection-renderer.js +0 -44
- package/dist/core/constraints/injection-renderer.js.map +0 -1
- package/src/cli/commands/__tests__/check-drift.test.ts +0 -158
- package/src/cli/commands/__tests__/init-injection.test.ts +0 -466
- package/src/cli/commands/__tests__/posteval-plan.test.ts +0 -204
- package/src/cli/commands/posteval-plan.ts +0 -98
- package/src/core/constraints/__tests__/agent-prompt-renderer.test.ts +0 -129
- package/src/core/constraints/__tests__/injection-drift.test.ts +0 -247
- package/src/core/constraints/agent-prompt-renderer.ts +0 -67
- package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +0 -76
- package/src/core/constraints/checkers/no-bypass-checkpoint.ts +0 -40
- package/src/core/constraints/definitions/prompts.ts +0 -414
- package/src/core/constraints/injection-drift.ts +0 -142
- 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(
|
|
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(
|
|
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","
|
|
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('
|
|
107
|
-
...tracesOf('
|
|
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('
|
|
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',
|
|
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
|
|
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('
|
|
191
|
+
...tracesOf('no_test_simplification', 'fail', 20),
|
|
196
192
|
// projectPath 字段进 trace,但不得进 export
|
|
197
|
-
{ constraintId: '
|
|
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('
|
|
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,
|
|
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
|
|
5
|
+
* KnowledgeStore 记录、重复 retire / 未知 id 保护。
|
|
6
6
|
* 交互流程(runRetireInteractive):注入 stdin 流测核心分支
|
|
7
|
-
* (候选选择 +
|
|
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.
|
|
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\
|
|
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.
|
|
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: '
|
|
83
|
+
traces: [{ constraintId: 'no_completion_without_verification', result: 'fail' }],
|
|
103
84
|
});
|
|
104
85
|
|
|
105
|
-
const result = retireConstraint(root, '
|
|
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-
|
|
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-
|
|
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:
|
|
116
|
-
expect(entry!.title).toContain('
|
|
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('
|
|
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('
|
|
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('
|
|
139
|
+
it('severity=error:isError 标记为 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.
|
|
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('
|
|
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
|
-
|
|
305
|
-
|
|
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
|
|
309
|
-
delete
|
|
310
|
-
fs.writeFileSync(path.join(root, '.harness', '
|
|
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 === '
|
|
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\
|
|
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\
|
|
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('
|
|
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, '
|
|
193
|
+
retireConstraint(root, 'capability_sync', { now: FIXED_NOW });
|
|
359
194
|
expect(loadSpy).toHaveBeenCalledTimes(1);
|
|
360
195
|
|
|
361
196
|
loadSpy.mockClear();
|
|
362
|
-
retireConstraint(root, '
|
|
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 直达
|
|
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('
|
|
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
|
|
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('
|
|
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('候选编号选择 +
|
|
280
|
+
it('候选编号选择 + error 级二次确认拒绝 → 不落盘', async () => {
|
|
446
281
|
const root = createProjectFixture({ name: 'harness-retire-test' });
|
|
447
|
-
// 无 trace → 候选全部为零触发,1 号候选是第一条
|
|
448
|
-
const streams = makeIo(['1', 'n']); // 选 1 号 →
|
|
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","
|
|
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","
|
|
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
|
|