@dommaker/harness 1.5.0 → 1.6.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 +11 -0
- package/README.md +0 -2
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +1 -32
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/passes-gate.d.ts +0 -2
- package/dist/cli/commands/passes-gate.d.ts.map +1 -1
- package/dist/cli/commands/passes-gate.js +0 -1
- package/dist/cli/commands/passes-gate.js.map +1 -1
- package/dist/cli/commands/performance.d.ts +0 -4
- package/dist/cli/commands/performance.d.ts.map +1 -1
- package/dist/cli/commands/performance.js +10 -20
- package/dist/cli/commands/performance.js.map +1 -1
- package/dist/cli/commands/report.d.ts +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +2 -55
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +4 -8
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/context/index.d.ts +2 -5
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +2 -8
- package/dist/context/index.js.map +1 -1
- package/dist/context/token-budget.d.ts +0 -29
- package/dist/context/token-budget.d.ts.map +1 -1
- package/dist/context/token-budget.js +1 -65
- package/dist/context/token-budget.js.map +1 -1
- package/dist/context/types.d.ts +0 -1
- package/dist/context/types.d.ts.map +1 -1
- package/dist/context/types.js +0 -9
- package/dist/context/types.js.map +1 -1
- package/dist/core/constraints/checker.d.ts +14 -0
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +22 -2
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/git-evidence.d.ts +6 -3
- package/dist/core/constraints/git-evidence.d.ts.map +1 -1
- package/dist/core/constraints/git-evidence.js +6 -3
- package/dist/core/constraints/git-evidence.js.map +1 -1
- package/dist/core/index.d.ts +1 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -9
- package/dist/core/index.js.map +1 -1
- package/dist/core/spec/validator.d.ts +2 -0
- package/dist/core/spec/validator.d.ts.map +1 -1
- package/dist/core/spec/validator.js +9 -11
- package/dist/core/spec/validator.js.map +1 -1
- package/dist/core/validators/index.d.ts +1 -1
- package/dist/core/validators/index.d.ts.map +1 -1
- package/dist/core/validators/passes-gate.d.ts +1 -15
- package/dist/core/validators/passes-gate.d.ts.map +1 -1
- package/dist/core/validators/passes-gate.js +0 -125
- package/dist/core/validators/passes-gate.js.map +1 -1
- package/dist/failure/index.d.ts +0 -1
- package/dist/failure/index.d.ts.map +1 -1
- package/dist/failure/index.js +1 -7
- package/dist/failure/index.js.map +1 -1
- package/dist/gates/definitions.d.ts.map +1 -1
- package/dist/gates/definitions.js +0 -4
- package/dist/gates/definitions.js.map +1 -1
- package/dist/gates/performance.d.ts +7 -22
- package/dist/gates/performance.d.ts.map +1 -1
- package/dist/gates/performance.js +7 -89
- package/dist/gates/performance.js.map +1 -1
- package/dist/gates/types.d.ts +2 -8
- package/dist/gates/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -22
- package/dist/index.js.map +1 -1
- package/dist/knowledge/index.d.ts +0 -1
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +1 -3
- package/dist/knowledge/index.js.map +1 -1
- package/dist/monitoring/trace-analyzer.d.ts +25 -10
- package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
- package/dist/monitoring/trace-analyzer.js +147 -116
- package/dist/monitoring/trace-analyzer.js.map +1 -1
- package/dist/types/passes-gate.d.ts +0 -19
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/passes-gate-check.test.ts +20 -4
- package/src/__tests__/passes-gate.test.ts +5 -99
- package/src/__tests__/performance-gate.test.ts +3 -11
- package/src/__tests__/public-exports.test.ts +46 -16
- package/src/__tests__/trace-analyzer.test.ts +52 -2
- package/src/cli/commands/CONTEXT.md +3 -2
- package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +70 -0
- package/src/cli/commands/__tests__/passes-gate.test.ts +7 -3
- package/src/cli/commands/__tests__/performance.test.ts +5 -16
- package/src/cli/commands/__tests__/registry.test.ts +46 -3
- package/src/cli/commands/__tests__/report-extra.test.ts +0 -19
- package/src/cli/commands/__tests__/report-false-green.test.ts +46 -0
- package/src/cli/commands/__tests__/report.test.ts +0 -20
- package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +68 -0
- package/src/cli/commands/__tests__/status-extra.test.ts +63 -116
- package/src/cli/commands/__tests__/status.test.ts +66 -148
- package/src/cli/commands/definitions.ts +1 -32
- package/src/cli/commands/passes-gate.ts +0 -3
- package/src/cli/commands/performance.ts +13 -27
- package/src/cli/commands/report.ts +3 -61
- package/src/cli/commands/status.ts +5 -10
- package/src/context/CONTEXT.md +2 -3
- package/src/context/__tests__/token-budget.test.ts +1 -81
- package/src/context/index.ts +2 -7
- package/src/context/token-budget.ts +0 -73
- package/src/context/types.ts +0 -9
- package/src/core/CONTEXT.md +5 -6
- package/src/core/constraints/__tests__/collect-constraints.test.ts +66 -0
- package/src/core/constraints/checker.ts +33 -2
- package/src/core/constraints/git-evidence.ts +6 -3
- package/src/core/index.ts +0 -11
- package/src/core/spec/__tests__/validator.test.ts +24 -0
- package/src/core/spec/validator.ts +9 -10
- package/src/core/validators/__tests__/passes-gate.test.ts +2 -9
- package/src/core/validators/index.ts +0 -1
- package/src/core/validators/passes-gate.ts +1 -149
- package/src/failure/CONTEXT.md +8 -6
- package/src/failure/index.ts +0 -10
- package/src/gates/CONTEXT.md +3 -2
- package/src/gates/__tests__/performance.test.ts +6 -74
- package/src/gates/definitions.ts +0 -4
- package/src/gates/performance.ts +9 -111
- package/src/gates/types.ts +2 -8
- package/src/index.ts +1 -36
- package/src/knowledge/CONTEXT.md +1 -2
- package/src/knowledge/index.ts +0 -1
- package/src/monitoring/CONTEXT.md +2 -2
- package/src/monitoring/trace-analyzer.ts +160 -126
- package/src/types/passes-gate.ts +0 -26
- package/src/utils/__tests__/jsonl-skip-disposition.test.ts +0 -1
- package/dist/cli/commands/analyze-sessions.d.ts +0 -20
- package/dist/cli/commands/analyze-sessions.d.ts.map +0 -1
- package/dist/cli/commands/analyze-sessions.js +0 -290
- package/dist/cli/commands/analyze-sessions.js.map +0 -1
- package/dist/cli/commands/update-user-model.d.ts +0 -30
- package/dist/cli/commands/update-user-model.d.ts.map +0 -1
- package/dist/cli/commands/update-user-model.js +0 -406
- package/dist/cli/commands/update-user-model.js.map +0 -1
- package/dist/cli/session-mining/corrections.d.ts +0 -17
- package/dist/cli/session-mining/corrections.d.ts.map +0 -1
- package/dist/cli/session-mining/corrections.js +0 -58
- package/dist/cli/session-mining/corrections.js.map +0 -1
- package/dist/cli/session-mining/index.d.ts +0 -13
- package/dist/cli/session-mining/index.d.ts.map +0 -1
- package/dist/cli/session-mining/index.js +0 -29
- package/dist/cli/session-mining/index.js.map +0 -1
- package/dist/cli/session-mining/text.d.ts +0 -31
- package/dist/cli/session-mining/text.d.ts.map +0 -1
- package/dist/cli/session-mining/text.js +0 -126
- package/dist/cli/session-mining/text.js.map +0 -1
- package/dist/cli/session-mining/transcript.d.ts +0 -34
- package/dist/cli/session-mining/transcript.d.ts.map +0 -1
- package/dist/cli/session-mining/transcript.js +0 -130
- package/dist/cli/session-mining/transcript.js.map +0 -1
- package/dist/context/compaction.d.ts +0 -57
- package/dist/context/compaction.d.ts.map +0 -1
- package/dist/context/compaction.js +0 -165
- package/dist/context/compaction.js.map +0 -1
- package/dist/core/session/clean-state.d.ts +0 -17
- package/dist/core/session/clean-state.d.ts.map +0 -1
- package/dist/core/session/clean-state.js +0 -192
- package/dist/core/session/clean-state.js.map +0 -1
- package/dist/core/session/index.d.ts +0 -6
- package/dist/core/session/index.d.ts.map +0 -1
- package/dist/core/session/index.js +0 -15
- package/dist/core/session/index.js.map +0 -1
- package/dist/core/session/startup.d.ts +0 -47
- package/dist/core/session/startup.d.ts.map +0 -1
- package/dist/core/session/startup.js +0 -263
- package/dist/core/session/startup.js.map +0 -1
- package/dist/failure/constraint-handler.d.ts +0 -83
- package/dist/failure/constraint-handler.d.ts.map +0 -1
- package/dist/failure/constraint-handler.js +0 -138
- package/dist/failure/constraint-handler.js.map +0 -1
- package/dist/knowledge/lifecycle-hooks.d.ts +0 -62
- package/dist/knowledge/lifecycle-hooks.d.ts.map +0 -1
- package/dist/knowledge/lifecycle-hooks.js +0 -143
- package/dist/knowledge/lifecycle-hooks.js.map +0 -1
- package/dist/types/session.d.ts +0 -113
- package/dist/types/session.d.ts.map +0 -1
- package/dist/types/session.js +0 -6
- package/dist/types/session.js.map +0 -1
- package/src/__tests__/clean-state-extra.test.ts +0 -301
- package/src/__tests__/clean-state.test.ts +0 -169
- package/src/__tests__/constraint-handler.test.ts +0 -132
- package/src/__tests__/passes-gate-extra.test.ts +0 -93
- package/src/__tests__/session-startup.test.ts +0 -334
- package/src/cli/commands/__tests__/analyze-sessions.test.ts +0 -184
- package/src/cli/commands/__tests__/update-user-model.test.ts +0 -268
- package/src/cli/commands/analyze-sessions.ts +0 -335
- package/src/cli/commands/update-user-model.ts +0 -476
- package/src/cli/session-mining/__tests__/transcript.test.ts +0 -118
- package/src/cli/session-mining/corrections.ts +0 -56
- package/src/cli/session-mining/index.ts +0 -13
- package/src/cli/session-mining/text.ts +0 -117
- package/src/cli/session-mining/transcript.ts +0 -125
- package/src/context/__tests__/compaction.test.ts +0 -169
- package/src/context/compaction.ts +0 -201
- package/src/core/session/__tests__/startup.test.ts +0 -281
- package/src/core/session/clean-state.ts +0 -178
- package/src/core/session/index.ts +0 -15
- package/src/core/session/startup.ts +0 -253
- package/src/failure/constraint-handler.ts +0 -171
- package/src/knowledge/__tests__/lifecycle-hooks.test.ts +0 -140
- package/src/knowledge/lifecycle-hooks.ts +0 -202
- package/src/types/session.ts +0 -132
package/package.json
CHANGED
|
@@ -95,12 +95,28 @@ describe('PassesGate.check()', () => {
|
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
* AC-007
|
|
98
|
+
* AC-007(ADR-0022 改写):零消费者方法已从公共面删除
|
|
99
|
+
*
|
|
100
|
+
* 原用例断言「保留 setPasses() 向后兼容」——兼容对象经 harness+studio 双仓
|
|
101
|
+
* 核实不存在,ADR-0022 明确推翻该 AC。`@ts-expect-error` 在编译期钉住删除,
|
|
102
|
+
* 运行期断言同时钉住形状(方法不存在于原型)。
|
|
99
103
|
*/
|
|
100
|
-
it('AC-007:
|
|
104
|
+
it('AC-007: setPasses()/getTestResult()/checkTestFileChanges() are removed from the public surface', () => {
|
|
101
105
|
const gate = new PassesGate();
|
|
102
|
-
|
|
103
|
-
expect
|
|
106
|
+
|
|
107
|
+
// @ts-expect-error setPasses 已随 ADR-0022 删除
|
|
108
|
+
expect(gate.setPasses).toBeUndefined();
|
|
109
|
+
// @ts-expect-error getTestResult 已随 ADR-0022 删除
|
|
110
|
+
expect(gate.getTestResult).toBeUndefined();
|
|
111
|
+
// @ts-expect-error checkTestFileChanges 已随 ADR-0022 删除
|
|
112
|
+
expect(gate.checkTestFileChanges).toBeUndefined();
|
|
113
|
+
expect(PassesGate.prototype).not.toHaveProperty('setPasses');
|
|
114
|
+
expect(PassesGate.prototype).not.toHaveProperty('getTestResult');
|
|
115
|
+
expect(PassesGate.prototype).not.toHaveProperty('checkTestFileChanges');
|
|
116
|
+
|
|
117
|
+
// 保留面:真实消费的方法不动
|
|
118
|
+
expect(typeof gate.check).toBe('function');
|
|
119
|
+
expect(typeof gate.runTests).toBe('function');
|
|
104
120
|
});
|
|
105
121
|
|
|
106
122
|
/**
|
|
@@ -33,34 +33,6 @@ describe('PassesGate', () => {
|
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
describe('setPasses', () => {
|
|
37
|
-
it('设置为 false 应该直接允许', async () => {
|
|
38
|
-
const result = await gate.setPasses('task-1', false, tempDir);
|
|
39
|
-
|
|
40
|
-
expect(result.allowed).toBe(true);
|
|
41
|
-
expect(result.testResult?.passed).toBe(false);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('设置为 true 应该运行测试', async () => {
|
|
45
|
-
const result = await gate.setPasses('task-2', true, tempDir, {
|
|
46
|
-
id: 'task-2',
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// 测试命令是 echo "1 passed",所以应该通过
|
|
50
|
-
expect(result.allowed).toBe(true);
|
|
51
|
-
expect(result.testResult?.command).toContain('npm test');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('禁用时应该允许所有设置', async () => {
|
|
55
|
-
const disabledGate = createPassesGate({ enabled: false });
|
|
56
|
-
|
|
57
|
-
const result = await disabledGate.setPasses('task-3', true, tempDir);
|
|
58
|
-
|
|
59
|
-
expect(result.allowed).toBe(true);
|
|
60
|
-
expect(result.attempts).toBe(0);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
36
|
describe('runTests', () => {
|
|
65
37
|
// 执行根由调用方传入(#95):tempDir 的 package.json 测试命令是 echo,不再跑真实套件
|
|
66
38
|
it('应该返回测试结果', async () => {
|
|
@@ -72,15 +44,6 @@ describe('PassesGate', () => {
|
|
|
72
44
|
});
|
|
73
45
|
});
|
|
74
46
|
|
|
75
|
-
describe('detectTestCommand', () => {
|
|
76
|
-
it('应该检测 npm test', async () => {
|
|
77
|
-
// package.json 有 test script
|
|
78
|
-
const result = await gate.setPasses('task-4', true, tempDir);
|
|
79
|
-
|
|
80
|
-
expect(result.testResult?.command).toContain('npm');
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
47
|
// 探测正本行为钉住(架构评审 A2:CLI/core 双版收口后的统一语义,
|
|
85
48
|
// 此前 e2e 优先级、echo 排除、兜底均无测试钉住)
|
|
86
49
|
describe('detectTestCommand 正本(统一探测顺序)', () => {
|
|
@@ -153,70 +116,13 @@ describe('PassesGate', () => {
|
|
|
153
116
|
expect(await detectTestCommand(emptyDir)).toBeUndefined();
|
|
154
117
|
});
|
|
155
118
|
|
|
156
|
-
it('探测失败时 runTests
|
|
119
|
+
it('探测失败时 runTests fail-closed', async () => {
|
|
157
120
|
const emptyDir = join(detectDir, 'fail-closed');
|
|
158
121
|
mkdirSync(emptyDir, { recursive: true });
|
|
159
122
|
|
|
160
123
|
const runResult = await gate.runTests(emptyDir);
|
|
161
124
|
expect(runResult.passed).toBe(false);
|
|
162
125
|
expect(runResult.message).toBe('未检测到测试命令');
|
|
163
|
-
|
|
164
|
-
const setResult = await gate.setPasses('task-nocmd', true, emptyDir);
|
|
165
|
-
expect(setResult.allowed).toBe(false);
|
|
166
|
-
expect(setResult.testResult?.passed).toBe(false);
|
|
167
|
-
expect(setResult.testResult?.output).toBe('未检测到测试命令');
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
describe('checkTestFileChanges', () => {
|
|
172
|
-
it('应该检测测试文件变更', async () => {
|
|
173
|
-
// 无 git 变更时返回空数组
|
|
174
|
-
const changes = await gate.checkTestFileChanges(tempDir);
|
|
175
|
-
|
|
176
|
-
expect(Array.isArray(changes)).toBe(true);
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
describe('getTestResult', () => {
|
|
181
|
-
it('应该返回已存储的测试结果', async () => {
|
|
182
|
-
const result = await gate.setPasses('stored-task', true, tempDir);
|
|
183
|
-
|
|
184
|
-
if (result.allowed && result.testResult) {
|
|
185
|
-
const stored = gate.getTestResult('stored-task');
|
|
186
|
-
expect(stored).toBeDefined();
|
|
187
|
-
expect(stored?.command).toContain('npm');
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('未存储任务应返回 undefined', () => {
|
|
192
|
-
const stored = gate.getTestResult('nonexistent-task');
|
|
193
|
-
expect(stored).toBeUndefined();
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('测试失败重试', () => {
|
|
198
|
-
it('测试失败后应重试', async () => {
|
|
199
|
-
const retryDir = join(process.cwd(), 'temp-test-retry');
|
|
200
|
-
mkdirSync(retryDir, { recursive: true });
|
|
201
|
-
|
|
202
|
-
writeFileSync(join(retryDir, 'package.json'), JSON.stringify({
|
|
203
|
-
name: 'retry-project',
|
|
204
|
-
scripts: { test: 'exit 1' }, // 总是失败
|
|
205
|
-
}));
|
|
206
|
-
|
|
207
|
-
const retryGate = createPassesGate({
|
|
208
|
-
enabled: true,
|
|
209
|
-
maxRetries: 1,
|
|
210
|
-
retryDelay: 100,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
const result = await retryGate.setPasses('retry-task', true, retryDir);
|
|
214
|
-
|
|
215
|
-
expect(result.allowed).toBe(false);
|
|
216
|
-
expect(result.attempts).toBeGreaterThan(1);
|
|
217
|
-
expect(result.error).toBeDefined();
|
|
218
|
-
|
|
219
|
-
rmSync(retryDir, { recursive: true, force: true });
|
|
220
126
|
});
|
|
221
127
|
});
|
|
222
128
|
|
|
@@ -237,13 +143,13 @@ describe('PassesGate', () => {
|
|
|
237
143
|
},
|
|
238
144
|
});
|
|
239
145
|
|
|
240
|
-
it('
|
|
146
|
+
it('runTests 结果不带 coverage 字段', async () => {
|
|
241
147
|
const covGate = createPassesGate({ enabled: true, requireEvidence: false });
|
|
242
148
|
|
|
243
|
-
const result = await covGate.
|
|
149
|
+
const result = await covGate.runTests(covDir);
|
|
244
150
|
|
|
245
|
-
expect(result.
|
|
246
|
-
expect(result
|
|
151
|
+
expect(result.passed).toBe(true);
|
|
152
|
+
expect(result).not.toHaveProperty('coverage');
|
|
247
153
|
});
|
|
248
154
|
|
|
249
155
|
it('TaskTestResult 公开类型面不再接受 coverage 字段', () => {
|
|
@@ -38,7 +38,7 @@ describe('PerformanceGate', () => {
|
|
|
38
38
|
const gate = new PerformanceGate({
|
|
39
39
|
enabled: true,
|
|
40
40
|
thresholds: {
|
|
41
|
-
|
|
41
|
+
maxBundleSize: 1000,
|
|
42
42
|
},
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -55,20 +55,12 @@ describe('PerformanceGate', () => {
|
|
|
55
55
|
it('应该支持自定义阈值', () => {
|
|
56
56
|
const gate = new PerformanceGate({
|
|
57
57
|
thresholds: {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
minCoverage: 80,
|
|
59
|
+
maxBundleSize: 1000,
|
|
60
60
|
},
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
expect(gate).toBeDefined();
|
|
64
64
|
});
|
|
65
|
-
|
|
66
|
-
it('应该支持基准测试命令', () => {
|
|
67
|
-
const gate = new PerformanceGate({
|
|
68
|
-
benchmarkCommand: 'npm run benchmark',
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
expect(gate).toBeDefined();
|
|
72
|
-
});
|
|
73
65
|
});
|
|
74
66
|
});
|
|
@@ -5,29 +5,28 @@
|
|
|
5
5
|
* 改动 src/index.ts 导出清单时同步更新本文件的 EXPECTED_RUNTIME_EXPORTS,
|
|
6
6
|
* diff 即 PR 评审材料。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* 两个面两道闸:运行时(值)导出由 Object.keys 逐字冻结;纯类型导出在运行时不可见,
|
|
9
|
+
* 由下方「包根类型面」闸把关——编译期 `@ts-expect-error` 钉已删类型 + 源形状钉整条 barrel 链。
|
|
10
|
+
* 类型面曾有此空缺时 `PassesGateResult` 漏删未被抓到(review A2),钉子据此补上。
|
|
10
11
|
*/
|
|
11
12
|
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
|
|
12
16
|
const EXPECTED_RUNTIME_EXPORTS = [
|
|
13
|
-
'AdaptiveTokenBudget',
|
|
14
17
|
'AgentLifecycle',
|
|
15
18
|
'CONSTRAINTS_END_MARKER',
|
|
16
19
|
'CONSTRAINTS_START_MARKER',
|
|
17
20
|
'CSOValidator',
|
|
18
21
|
'CheckCache',
|
|
19
22
|
'CheckpointValidator',
|
|
20
|
-
'CleanStateManager',
|
|
21
23
|
'ColdStartImporter',
|
|
22
24
|
'CommandGate',
|
|
23
25
|
'ConstraintViolationError',
|
|
24
|
-
'ConstraintViolationHandler',
|
|
25
26
|
'ContextTracker',
|
|
26
27
|
'ContractGate',
|
|
27
28
|
'DEFAULT_CLASSIFICATION_RULES',
|
|
28
|
-
'DEFAULT_CODE_CHECKPOINTS',
|
|
29
29
|
'DEFAULT_COMMAND_BLACKLIST',
|
|
30
|
-
'DEFAULT_COMPACTION_CONFIG',
|
|
31
30
|
'DEFAULT_DECAY_CONFIG',
|
|
32
31
|
'DEFAULT_FAILURE_LOG_FILE',
|
|
33
32
|
'DEFAULT_LEVEL_MAPPING',
|
|
@@ -49,10 +48,8 @@ const EXPECTED_RUNTIME_EXPORTS = [
|
|
|
49
48
|
'KnowledgeIngest',
|
|
50
49
|
'KnowledgeInjector',
|
|
51
50
|
'KnowledgeLifecycle',
|
|
52
|
-
'KnowledgeLifecycleHooks',
|
|
53
51
|
'KnowledgeLinter',
|
|
54
52
|
'KnowledgeQuery',
|
|
55
|
-
'MINIMAL_CHECKPOINTS',
|
|
56
53
|
'PHASE_SUBJECT_RE',
|
|
57
54
|
'PROMPTS',
|
|
58
55
|
'PassesGate',
|
|
@@ -60,9 +57,7 @@ const EXPECTED_RUNTIME_EXPORTS = [
|
|
|
60
57
|
'ReferenceTracker',
|
|
61
58
|
'ReviewGate',
|
|
62
59
|
'SecurityGate',
|
|
63
|
-
'SessionCompaction',
|
|
64
60
|
'SessionManager',
|
|
65
|
-
'SessionStartup',
|
|
66
61
|
'SpecAcceptanceGate',
|
|
67
62
|
'SpecValidator',
|
|
68
63
|
'TESTED_BY_RE',
|
|
@@ -83,7 +78,6 @@ const EXPECTED_RUNTIME_EXPORTS = [
|
|
|
83
78
|
'configureTraceCollector',
|
|
84
79
|
'createAnalyzer',
|
|
85
80
|
'createCheckerGate',
|
|
86
|
-
'createCleanStateManager',
|
|
87
81
|
'createCommandGate',
|
|
88
82
|
'createContractGate',
|
|
89
83
|
'createErrorClassifier',
|
|
@@ -92,12 +86,8 @@ const EXPECTED_RUNTIME_EXPORTS = [
|
|
|
92
86
|
'createPerformanceGate',
|
|
93
87
|
'createReviewGate',
|
|
94
88
|
'createSecurityGate',
|
|
95
|
-
'createSessionStartup',
|
|
96
89
|
'createSpecAcceptanceGate',
|
|
97
90
|
'decisionFromResult',
|
|
98
|
-
'executeWithBlock',
|
|
99
|
-
'executeWithCollect',
|
|
100
|
-
'executeWithSafeBoolean',
|
|
101
91
|
'extractCodeStructure',
|
|
102
92
|
'findConstraintsByTrigger',
|
|
103
93
|
'getAllConstraints',
|
|
@@ -139,3 +129,43 @@ describe('公共导出清单(ADR-0003)', () => {
|
|
|
139
129
|
expect(Object.keys(mod).sort()).toEqual([...EXPECTED_RUNTIME_EXPORTS].sort());
|
|
140
130
|
});
|
|
141
131
|
});
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 包根类型面闸(review A2)
|
|
135
|
+
*
|
|
136
|
+
* 上一闸的 `Object.keys(mod)` 只看得到运行时(值)导出,类型面历史上无对等闸——
|
|
137
|
+
* `PassesGateResult`(已删 `setPasses` 的返回形状)正是这样从 #125 的「关联类型导出随迁」
|
|
138
|
+
* 里漏掉、经四级 barrel 存活为公开类型。手法同 #125 的 AC-007:编译期 `@ts-expect-error`
|
|
139
|
+
* 钉住删除 + 源形状钉住整条链,活类型正钉防删多。
|
|
140
|
+
*/
|
|
141
|
+
describe('包根类型面(ADR-0022 关联类型随迁)', () => {
|
|
142
|
+
const BARREL_CHAIN = [
|
|
143
|
+
'../types/passes-gate.ts',
|
|
144
|
+
'../core/validators/index.ts',
|
|
145
|
+
'../core/index.ts',
|
|
146
|
+
'../index.ts',
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
it('PassesGateResult 在整条 barrel 链四级都已消失', () => {
|
|
150
|
+
for (const rel of BARREL_CHAIN) {
|
|
151
|
+
const source = fs.readFileSync(path.join(__dirname, rel), 'utf-8');
|
|
152
|
+
expect({ file: rel, mentions: /PassesGateResult/.test(source) }).toEqual({
|
|
153
|
+
file: rel,
|
|
154
|
+
mentions: false,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('PassesGateResult 不再是包根可导入类型(编译期钉)', () => {
|
|
160
|
+
// @ts-expect-error 该类型是已删 setPasses 的返回形状,随 ADR-0022 关联类型口径删除
|
|
161
|
+
const removedType: import('../index').PassesGateResult | undefined = undefined;
|
|
162
|
+
expect(removedType).toBeUndefined();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('活类型仍可经包根导入(防止删多)', () => {
|
|
166
|
+
const liveCheck: import('../index').PassesGateCheckResult = { allowed: true };
|
|
167
|
+
const liveTask: import('../index').TaskTestResult = { passed: true, command: 'npm test' };
|
|
168
|
+
expect(liveCheck.allowed).toBe(true);
|
|
169
|
+
expect(liveTask.command).toBe('npm test');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
TraceAnalyzer,
|
|
8
|
+
summarizeTraces,
|
|
9
|
+
detectTraceAnomalies,
|
|
10
|
+
} from '../monitoring/trace-analyzer';
|
|
7
11
|
import { TraceCollector } from '../monitoring/traces';
|
|
8
12
|
import * as fs from 'fs';
|
|
9
13
|
import * as path from 'path';
|
|
10
|
-
import type { ExecutionTrace } from '../types/trace';
|
|
14
|
+
import type { ExecutionTrace, TraceSummary } from '../types/trace';
|
|
11
15
|
|
|
12
16
|
describe('TraceAnalyzer', () => {
|
|
13
17
|
const tempDir = path.join(process.cwd(), 'temp-test-analyzer');
|
|
@@ -127,4 +131,50 @@ describe('TraceAnalyzer', () => {
|
|
|
127
131
|
expect(customAnalyzer).toBeDefined();
|
|
128
132
|
});
|
|
129
133
|
});
|
|
134
|
+
|
|
135
|
+
// ADR-0020:summarize / detectAnomalies 的判定本体住在模块级纯函数上,
|
|
136
|
+
// 类壳只是转发。下面两枚用例直调模块级函数,钉住这个 interface——
|
|
137
|
+
// 经类壳的既有覆盖(上文)删不掉它,反之它也不依赖任何 collector/文件 IO。
|
|
138
|
+
describe('模块级纯函数', () => {
|
|
139
|
+
it('summarizeTraces 不构造 collector 即可汇总,skip 不进 pass/fail 分母', () => {
|
|
140
|
+
const traces: ExecutionTrace[] = [
|
|
141
|
+
{ constraintId: 'pure', level: 'iron_law', timestamp: 1000, result: 'pass' },
|
|
142
|
+
{ constraintId: 'pure', level: 'iron_law', timestamp: 2000, result: 'fail' },
|
|
143
|
+
{ constraintId: 'pure', level: 'iron_law', timestamp: 3000, result: 'skip' },
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const summaries = summarizeTraces(traces);
|
|
147
|
+
|
|
148
|
+
expect(summaries).toHaveLength(1);
|
|
149
|
+
const summary = summaries[0];
|
|
150
|
+
expect(summary.constraintId).toBe('pure');
|
|
151
|
+
expect(summary.totalChecks).toBe(3);
|
|
152
|
+
expect(summary.skipCount).toBe(1);
|
|
153
|
+
expect(summary.passRate).toBeCloseTo(0.5);
|
|
154
|
+
expect(summary.failRate).toBeCloseTo(0.5);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('detectTraceAnomalies 的阈值经参数传入,默认与自定义各判一次', () => {
|
|
158
|
+
// failRate 6/14 ≈ 0.43 且趋势上升:默认阈值 0.5 不触发,0.3 触发
|
|
159
|
+
const summary: TraceSummary = {
|
|
160
|
+
constraintId: 'param_threshold',
|
|
161
|
+
level: 'iron_law',
|
|
162
|
+
timeRange: { start: 1000, end: 2400 },
|
|
163
|
+
totalChecks: 14,
|
|
164
|
+
passCount: 8,
|
|
165
|
+
failCount: 6,
|
|
166
|
+
ignoreCount: 0,
|
|
167
|
+
passRate: 8 / 14,
|
|
168
|
+
failRate: 6 / 14,
|
|
169
|
+
recentTrend: 'rising',
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
expect(detectTraceAnomalies([summary])).toEqual([]);
|
|
173
|
+
|
|
174
|
+
const anomalies = detectTraceAnomalies([summary], { thresholds: { failRate: 0.3 } });
|
|
175
|
+
const rising = anomalies.find(a => a.type === 'rising_fail_rate');
|
|
176
|
+
expect(rising?.constraintId).toBe('param_threshold');
|
|
177
|
+
expect(rising?.data.threshold).toBe(0.3);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
130
180
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# cli/commands/
|
|
2
2
|
|
|
3
3
|
## 职责
|
|
4
|
-
CLI 子命令实现:
|
|
4
|
+
CLI 子命令实现:21 个顶层命令 + constraints 治理子命令 report/retire,覆盖约束检查/门禁验证/文档同步/知识管理/失败诊断/发布等。
|
|
5
5
|
|
|
6
6
|
H5(#44)起:
|
|
7
7
|
- **命令定义即注册**:`definitions.ts` 的 `COMMAND_DEFINITIONS` 是命令形状(名称/别名/位置参数/选项/子命令/实现引用)的单一来源;bin/harness.js 遍历本表注册表驱动生成,不再手写 commander 命令块,不再有 index.ts barrel 两处同步(R6)
|
|
@@ -10,7 +10,7 @@ H5(#44)起:
|
|
|
10
10
|
## 核心导出
|
|
11
11
|
- `COMMAND_DEFINITIONS`(definitions.ts)— 全部非门禁命令定义(纯数据模块、零闭包,禁止 import 命令实现;ADR-0010)
|
|
12
12
|
- 命令契约(`src/cli/command-contract.ts`,上层目录)— `CommandResult` / `CommandKind` / `CommandIO` / `processIO` / `captureIO` / `lastJsonOutput` / `log` / `logError`
|
|
13
|
-
- 各命令文件:check / validate / passes-gate / init / report / status / spec / sync-docs / knowledge / sdd / failure / posteval-plan / release /
|
|
13
|
+
- 各命令文件:check / validate / passes-gate / init / report / status / spec / sync-docs / knowledge / sdd / failure / posteval-plan / release / constraints / spec-baseline-check
|
|
14
14
|
- 6 门禁命令实现在 `acceptance` / `command` / `contract` / `performance` / `review` / `security`(其 CLI 元数据在 `src/gates/definitions.ts`,形状同为 `CommandDefinition`,ADR-0007)
|
|
15
15
|
|
|
16
16
|
`constraints` 下挂治理子命令:`constraints report`(使用统计 + 退役候选诊断 + 配置健康 + 注入漂移,`--export` 脱敏)、`constraints retire`(交互选择 + 人确认退役;带 id 直达需显式 `--yes`(#24 人确认闸门),无 `--yes` 报错 + 非零退出码且不落盘;内置落 config.yml retired 元数据,custom 落 custom-constraints.yml 条目 retired 段(studio#82 D6 一处真相)+ KnowledgeStore 沉淀 + 治理注入段同步)。
|
|
@@ -29,6 +29,7 @@ H5(#44)起:
|
|
|
29
29
|
- **definitions.ts 是纯数据模块(ADR-0010 后零闭包)**:禁止 import 任何命令实现/运行时依赖(保 --help/--version 懒加载);子命令别名是数据(条目 `aliases: string[]`,不复印整块),实参编组(缺参闸门/强转/兜底)归各命令模块的具名导出(如 `knowledgeSearchCommand`/`coverageCheck`);实现引用可解析性由 `__tests__/registry.test.ts` 构建/测试期断言
|
|
30
30
|
- 命令选项类型命名规范:XxxOptions
|
|
31
31
|
- **本层是上行数据的注入方(harness#88)**:core 对 cli/gates/monitoring 零值导入,故 `check`/`report` 自己 `new ConstraintChecker(getTraceCollector())`(组合根接线 trace 记录器;用 `constraintChecker`/`getInstance()` 拿到的默认实例不写 trace),`sync-docs` 把 `COMMAND_DEFINITIONS` + `GATE_DEFINITIONS` 组装成 `CapabilityDefinitionSource` 注入 `capabilities-parser` 供能力清单计数
|
|
32
|
+
- **数据已在手就不再构造带 IO 副作用的实例(ADR-0020)**:`status` 按 projectPath 直读出 traces 后,直调 `monitoring/trace-analyzer` 的模块级纯函数 `summarizeTraces()`/`detectTraceAnomalies()`,不再 `new TraceCollector({traceFile})` + `new TraceAnalyzer(c)`——collector 构造函数带 mkdir 副作用,而这条路径上它的读方法从未被调用,实例纯属构造仪式。机器可检:`__tests__/status-analysis-wiring.test.ts`(源形状闸:命令体不得出现 `new TraceCollector`/`new TraceAnalyzer`,也不 import `monitoring/traces`)
|
|
32
33
|
- **projectPath 只在入口兜底一次,并传到每个 IO/执行点(harness#95)**:`-p/--project-path` 的 cwd 兜底唯一落点是命令模块入口(`const projectPath = options.projectPath || process.cwd()`);往下每层只能收参数,**禁止再取一次 `process.cwd()`、也禁止用相对路径默认值**——两者都让 `-p` 半失效:执行/读写位置悄悄回到调用方 cwd,而 acceptance 的「无 tasks.yml 即跳过」本身就是 `passed:true`,于是失效表现为假绿。落地形状:执行/IO 根用**必传形参**表达(`PassesGate.runTests(workDir)`),门禁内的相对子路径锚到 projectPath:`ContractGate` 一直是 `path.join(context.projectPath, contractPath)`,`SpecAcceptanceGate` 的 tasksPath 随 #95 对齐成 `path.resolve(projectPath, 相对值)`(绝对值原样,缺省 `<projectPath>/tasks.yml`)
|
|
33
34
|
- 机器可检:`__tests__/project-path-convention.test.ts` 三道闸——CLI 层 cwd 必须是 `xxx || process.cwd()` 兜底形状、下游层(= src 减 cli)cwd 站点**逐行**冻结(#98,豁免文件内新增行/行变形同样失败)、相对路径默认值冻结(#98 收紧:`xxxPath: '相对值'` 对象字面量之外,参数默认值形/模板字面量/双引号形同罪,扫描域 = 整个下游层);豁免逐条带理由,站点消失却不删条目同样失败
|
|
34
35
|
- 行为可检:`__tests__/project-path-anchoring.test.ts` 前提统一 **cwd ≠ projectPath**、不 mock 任何 IO,断言测试真在 B 执行、证据落 `B/.harness/evidence/`、acceptance 读 `B/tasks.yml`。「根已传入却又取 cwd」这类静态看不出的漂移由它守,冻结表守的是不再新增站点
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* passes-gate 重试选项死旗帜闸(code-review A1)
|
|
3
|
+
*
|
|
4
|
+
* 重试实现原住 `setPasses`,随 ADR-0022 删除后 `PassesGateConfig.maxRetries` /
|
|
5
|
+
* `retryDelay` 整条装配链只剩写入方、全仓无读取方——`harness passes-gate
|
|
6
|
+
* --max-retries N` 是恒不生效选项。ADR-0018 删 performance 幽灵旗帜的判据
|
|
7
|
+
* (只执法有真实现的维度)据此收口。
|
|
8
|
+
*
|
|
9
|
+
* 手法同 #125 的 AC-007:编译期 `@ts-expect-error` 钉字段不存在 + 运行期钉定义表与源形状。
|
|
10
|
+
* `src/agents/types.ts` 的 `maxRetries` 是 AgentLifecycle 自有字段(`lifecycle.ts` 真读取),
|
|
11
|
+
* 不在本闸范围内。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { COMMAND_DEFINITIONS } from '../definitions';
|
|
17
|
+
import type { PassesGateConfig } from '../../../types/passes-gate';
|
|
18
|
+
import type { PassesGateOptions } from '../passes-gate';
|
|
19
|
+
|
|
20
|
+
const passesGateCommand = COMMAND_DEFINITIONS.find(d => d.command === 'passes-gate');
|
|
21
|
+
const cliSource = fs.readFileSync(path.join(__dirname, '..', 'passes-gate.ts'), 'utf-8');
|
|
22
|
+
const validatorSource = fs.readFileSync(
|
|
23
|
+
path.join(__dirname, '..', '..', '..', 'core', 'validators', 'passes-gate.ts'),
|
|
24
|
+
'utf-8',
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
describe('passes-gate 重试选项已删除(A1)', () => {
|
|
28
|
+
it('定义表已注册 passes-gate(前提)', () => {
|
|
29
|
+
expect(passesGateCommand).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('passes-gate 旗帜面逐字冻结,不含 --max-retries', () => {
|
|
33
|
+
expect((passesGateCommand?.options || []).map(o => o.flags)).toEqual([
|
|
34
|
+
'-t, --test-command <command>',
|
|
35
|
+
'-p, --project-path <path>',
|
|
36
|
+
'--allow-partial',
|
|
37
|
+
'--coverage',
|
|
38
|
+
'--coverage-threshold <n>',
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('命令定义表任意层级都不含 retry 旗帜(防换个命令位复活)', () => {
|
|
43
|
+
expect(JSON.stringify(COMMAND_DEFINITIONS)).not.toMatch(/retry/i);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('CLI 装配链与 validator 默认配置都不再写重试字段', () => {
|
|
47
|
+
expect(cliSource).not.toMatch(/maxRetries|retryDelay/);
|
|
48
|
+
expect(validatorSource).not.toMatch(/maxRetries|retryDelay/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('PassesGateConfig / PassesGateOptions 的重试字段已从类型面消失(编译期钉)', () => {
|
|
52
|
+
// @ts-expect-error maxRetries 只写不读,随 A1 从 PassesGateConfig 删除
|
|
53
|
+
const removedMaxRetries: PassesGateConfig = { maxRetries: 2 };
|
|
54
|
+
// @ts-expect-error retryDelay 只写不读,随 A1 从 PassesGateConfig 删除
|
|
55
|
+
const removedRetryDelay: PassesGateConfig = { retryDelay: 1000 };
|
|
56
|
+
// @ts-expect-error CLI 侧同名字段随旗帜与装配一并删除
|
|
57
|
+
const removedOption: PassesGateOptions = { maxRetries: 2 };
|
|
58
|
+
expect([removedMaxRetries, removedRetryDelay, removedOption]).toHaveLength(3);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('活字段仍可赋值(防止删多)', () => {
|
|
62
|
+
const live: PassesGateConfig = {
|
|
63
|
+
enabled: true,
|
|
64
|
+
testCommand: 'npm test',
|
|
65
|
+
requireEvidence: false,
|
|
66
|
+
allowPartialPass: true,
|
|
67
|
+
};
|
|
68
|
+
expect(Object.keys(live)).toHaveLength(4);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -130,9 +130,13 @@ describe('passes-gate command', () => {
|
|
|
130
130
|
}));
|
|
131
131
|
|
|
132
132
|
await runPassesGate({ testCommand: 'pytest' }, io);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
// 精确形状(A1):装配面只剩四个活字段,只写不读的重试字段已随旗帜一并删除
|
|
134
|
+
expect(MockPassesGate).toHaveBeenCalledWith({
|
|
135
|
+
enabled: true,
|
|
136
|
+
testCommand: 'pytest',
|
|
137
|
+
requireEvidence: false,
|
|
138
|
+
allowPartialPass: false,
|
|
139
|
+
});
|
|
136
140
|
});
|
|
137
141
|
});
|
|
138
142
|
|
|
@@ -35,7 +35,7 @@ describe('performance command', () => {
|
|
|
35
35
|
passed: true,
|
|
36
36
|
message: 'ok',
|
|
37
37
|
details: {
|
|
38
|
-
metrics: { coverage: 85.5, bundleSize: 204800
|
|
38
|
+
metrics: { coverage: 85.5, bundleSize: 204800 },
|
|
39
39
|
},
|
|
40
40
|
});
|
|
41
41
|
MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
|
|
@@ -76,7 +76,7 @@ describe('performance command', () => {
|
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
it('
|
|
79
|
+
it('bundleThreshold 直传 KB 到 maxBundleSize(不再按字节错位换算)', async () => {
|
|
80
80
|
const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
|
|
81
81
|
MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
|
|
82
82
|
|
|
@@ -84,23 +84,12 @@ describe('performance command', () => {
|
|
|
84
84
|
|
|
85
85
|
expect(MockGate).toHaveBeenCalledWith(
|
|
86
86
|
expect.objectContaining({
|
|
87
|
-
thresholds: expect.objectContaining({
|
|
87
|
+
thresholds: expect.objectContaining({ maxBundleSize: 500 }),
|
|
88
88
|
}),
|
|
89
89
|
);
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
-
it('
|
|
93
|
-
const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
|
|
94
|
-
MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
|
|
95
|
-
|
|
96
|
-
await performance({ benchmarkTimeout: 30 }, io);
|
|
97
|
-
|
|
98
|
-
expect(MockGate).toHaveBeenCalledWith(
|
|
99
|
-
expect.objectContaining({ benchmarkTimeout: 30000 }),
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should set coverage threshold when both flags provided', async () => {
|
|
92
|
+
it('coverage 旗帜对齐 minCoverage 字段(原 coverage 错位键名恒不生效)', async () => {
|
|
104
93
|
const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
|
|
105
94
|
MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
|
|
106
95
|
|
|
@@ -108,7 +97,7 @@ describe('performance command', () => {
|
|
|
108
97
|
|
|
109
98
|
expect(MockGate).toHaveBeenCalledWith(
|
|
110
99
|
expect.objectContaining({
|
|
111
|
-
thresholds: expect.objectContaining({
|
|
100
|
+
thresholds: expect.objectContaining({ minCoverage: 90 }),
|
|
112
101
|
}),
|
|
113
102
|
);
|
|
114
103
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - 定义表内每个实现引用(module+export)都必须可解析(构建/测试期断言,
|
|
5
5
|
* 取代 commands barrel 的编译期保护——R6 消灭手工同步后的闭环兜底)
|
|
6
6
|
* - definitions 是纯数据模块:require 它不加载任何命令实现(保 --help/--version 懒加载)
|
|
7
|
-
* - 命令面与 CAPABILITIES.md 的
|
|
7
|
+
* - 命令面与 CAPABILITIES.md 的 21 顶层命令一致(防误删回归)
|
|
8
8
|
* - bin/harness.js 端到端冒烟(dist 存在时):--version/--help 零命令实现模块,
|
|
9
9
|
* 单命令执行只加载该命令模块(O2 per-command 懒加载)
|
|
10
10
|
*/
|
|
@@ -35,7 +35,7 @@ function collectRefs(defs: CommandDefinition[]): CommandImplRef[] {
|
|
|
35
35
|
const EXPECTED_TOP_LEVEL_COMMANDS = [
|
|
36
36
|
'check', 'validate', 'passes-gate', 'init', 'report', 'status', 'spec',
|
|
37
37
|
'sync-docs', 'knowledge', 'sdd', 'failure', 'posteval-plan',
|
|
38
|
-
'
|
|
38
|
+
'release', 'constraints',
|
|
39
39
|
'spec-baseline-check',
|
|
40
40
|
// 6 门禁命令(GATE_DEFINITIONS 驱动)
|
|
41
41
|
'acceptance', 'command', 'contract', 'performance', 'review', 'security',
|
|
@@ -88,7 +88,7 @@ describe('命令注册表闭环', () => {
|
|
|
88
88
|
}
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
it('顶层命令面与预期
|
|
91
|
+
it('顶层命令面与预期 21 命令一致(含 6 门禁)', () => {
|
|
92
92
|
const names = [
|
|
93
93
|
...COMMAND_DEFINITIONS.map(d => d.command.split(' ')[0]),
|
|
94
94
|
...GATE_DEFINITIONS.map(d => d.cli.command.split(' ')[0]),
|
|
@@ -97,6 +97,49 @@ describe('命令注册表闭环', () => {
|
|
|
97
97
|
expect(names.sort()).toEqual([...EXPECTED_TOP_LEVEL_COMMANDS].sort());
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* 文档手抄面根因闸(review A5)
|
|
102
|
+
*
|
|
103
|
+
* `CLAUDE.md` 的「N CLI subcommands (…)」与 `cli/commands/CONTEXT.md` 的命令清单/计数
|
|
104
|
+
* 都是注册表实面的人工抄本,无闸时必然漂移:#122 机械 −2 后 CLAUDE.md 仍列着早已按
|
|
105
|
+
* ADR-0009 删除的 `doc-freshness-check`,声明 22 而实面 21。三向钉死——计数等于条数、
|
|
106
|
+
* 无幽灵条目、无漏列条目。
|
|
107
|
+
*/
|
|
108
|
+
it('CLAUDE.md 与 cli/commands/CONTEXT.md 的命令抄本与定义表实面双向一致', () => {
|
|
109
|
+
const actual = [
|
|
110
|
+
...COMMAND_DEFINITIONS.map(d => d.command.split(' ')[0]),
|
|
111
|
+
...GATE_DEFINITIONS.map(d => d.cli.command.split(' ')[0]),
|
|
112
|
+
].sort();
|
|
113
|
+
|
|
114
|
+
const claudeLine = fs
|
|
115
|
+
.readFileSync(path.join(repoRoot, 'CLAUDE.md'), 'utf-8')
|
|
116
|
+
.split('\n')
|
|
117
|
+
.find(line => line.includes('| `src/cli/commands/` |'));
|
|
118
|
+
expect(claudeLine).toBeDefined();
|
|
119
|
+
const claudeMatch = (claudeLine || '').match(/(\d+) CLI subcommands \(([^)]*)\)/);
|
|
120
|
+
expect(claudeMatch).toBeDefined();
|
|
121
|
+
const claudeNames = claudeMatch![2]
|
|
122
|
+
.split(',')
|
|
123
|
+
.map(name => name.trim())
|
|
124
|
+
.filter(Boolean)
|
|
125
|
+
.sort();
|
|
126
|
+
expect(Number(claudeMatch![1])).toBe(actual.length);
|
|
127
|
+
expect(claudeNames).toEqual(actual);
|
|
128
|
+
|
|
129
|
+
const contextDoc = fs.readFileSync(path.join(repoCommandsDir, 'CONTEXT.md'), 'utf-8');
|
|
130
|
+
const contextCount = contextDoc.match(/(\d+) 个顶层命令/);
|
|
131
|
+
expect(contextCount).toBeDefined();
|
|
132
|
+
expect(Number(contextCount![1])).toBe(actual.length);
|
|
133
|
+
const contextNames = [
|
|
134
|
+
...(contextDoc.match(/各命令文件:([^\n]+)/)?.[1] || '').split('/'),
|
|
135
|
+
...(contextDoc.match(/门禁命令实现在 ([^(]+)(/)?.[1] || '').split('/'),
|
|
136
|
+
]
|
|
137
|
+
.map(name => name.replace(/`/g, '').trim())
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
.sort();
|
|
140
|
+
expect(contextNames).toEqual(actual);
|
|
141
|
+
});
|
|
142
|
+
|
|
100
143
|
it('命令定义无重复注册名', () => {
|
|
101
144
|
const names = COMMAND_DEFINITIONS.map(d => d.command);
|
|
102
145
|
expect(new Set(names).size).toBe(names.length);
|