@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
|
@@ -52,18 +52,6 @@ describe('report command - 补充覆盖', () => {
|
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
describe('HTML 报告完整内容', () => {
|
|
56
|
-
it('HTML 报告应该包含所有区块', async () => {
|
|
57
|
-
await report({ format: 'html' }, io);
|
|
58
|
-
const output = io.outText();
|
|
59
|
-
|
|
60
|
-
expect(output).toContain('<!DOCTYPE html>');
|
|
61
|
-
expect(output).toContain('<title>Harness 检查报告</title>');
|
|
62
|
-
expect(output).toContain('约束检查');
|
|
63
|
-
expect(output).toContain('Iron Laws');
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
55
|
describe('文件输出', () => {
|
|
68
56
|
it('Markdown 输出到文件', async () => {
|
|
69
57
|
mockFs.writeFile.mockResolvedValue(undefined);
|
|
@@ -71,13 +59,6 @@ describe('report command - 补充覆盖', () => {
|
|
|
71
59
|
await report({ format: 'markdown', output: 'report.md' }, io);
|
|
72
60
|
expect(mockFs.writeFile).toHaveBeenCalledWith('report.md', expect.stringContaining('# Harness'), 'utf-8');
|
|
73
61
|
});
|
|
74
|
-
|
|
75
|
-
it('HTML 输出到文件', async () => {
|
|
76
|
-
mockFs.writeFile.mockResolvedValue(undefined);
|
|
77
|
-
|
|
78
|
-
await report({ format: 'html', output: 'report.html' }, io);
|
|
79
|
-
expect(mockFs.writeFile).toHaveBeenCalledWith('report.html', expect.stringContaining('<!DOCTYPE html>'), 'utf-8');
|
|
80
|
-
});
|
|
81
62
|
});
|
|
82
63
|
|
|
83
64
|
describe('JSON 报告内容', () => {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* report 假绿回归(架构评审候选1)
|
|
3
|
+
*
|
|
4
|
+
* 历史 bug:report 经 executeWithCollect 调 checkConstraints,checker 在首个
|
|
5
|
+
* 铁律违规即 throw,handler 的 catch 丢弃 error.result 并合成空结果 ——
|
|
6
|
+
* 铁律违规被报成 violations: [] / passed = 全量(假绿)。
|
|
7
|
+
* 修复后 report 直调 checker.collectConstraints(不抛、全量收集),违规如实上报。
|
|
8
|
+
*
|
|
9
|
+
* fixture:CAPABILITIES.md 登记幽灵文件 → docs_freshness(file_modification
|
|
10
|
+
* 触发域内唯一铁律)必违规。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect } from '@jest/globals';
|
|
14
|
+
import { report } from '../report';
|
|
15
|
+
import { captureIO } from '../../command-contract';
|
|
16
|
+
import { createProjectFixture } from '../../../test-setup/project-fixture';
|
|
17
|
+
|
|
18
|
+
// 与 three-state-consistency.test.ts 相同的表格正本形状
|
|
19
|
+
const GHOST_CAPABILITIES =
|
|
20
|
+
'# Capabilities\n\n' +
|
|
21
|
+
'| 模块 | 文件 | 说明 |\n' +
|
|
22
|
+
'|------|------|------|\n' +
|
|
23
|
+
'| 遗魂 | src/gone.ts | 已删 |\n';
|
|
24
|
+
|
|
25
|
+
function parseJsonReport(output: string) {
|
|
26
|
+
const start = output.indexOf('{');
|
|
27
|
+
const end = output.lastIndexOf('}');
|
|
28
|
+
return JSON.parse(output.slice(start, end + 1));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('report 铁律违规如实上报(假绿回归)', () => {
|
|
32
|
+
it('docs_freshness 违规 → failed ≥ 1、violations 点名、passed < total', async () => {
|
|
33
|
+
const root = createProjectFixture({
|
|
34
|
+
name: 'report-false-green',
|
|
35
|
+
files: { 'CAPABILITIES.md': GHOST_CAPABILITIES },
|
|
36
|
+
});
|
|
37
|
+
const io = captureIO();
|
|
38
|
+
|
|
39
|
+
await report({ format: 'json', projectPath: root }, io);
|
|
40
|
+
|
|
41
|
+
const data = parseJsonReport(io.outText());
|
|
42
|
+
expect(data.constraints.failed).toBeGreaterThanOrEqual(1);
|
|
43
|
+
expect(data.constraints.violations.map((v: { id: string }) => v.id)).toContain('docs_freshness');
|
|
44
|
+
expect(data.constraints.passed).toBeLessThan(data.constraints.total);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -44,11 +44,6 @@ describe('report command', () => {
|
|
|
44
44
|
expect(io.outText()).toContain('Harness 检查报告');
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
it('应该生成 HTML 格式报告', async () => {
|
|
48
|
-
await report({ format: 'html' }, io);
|
|
49
|
-
expect(io.outText()).toContain('<!DOCTYPE html>');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
47
|
it('应该保存报告到文件', async () => {
|
|
53
48
|
mockFs.writeFile.mockResolvedValue(undefined);
|
|
54
49
|
|
|
@@ -70,15 +65,6 @@ describe('report command', () => {
|
|
|
70
65
|
expect(output).toContain('报告由 @dommaker/harness 生成');
|
|
71
66
|
});
|
|
72
67
|
|
|
73
|
-
it('HTML 报告应包含完整结构', async () => {
|
|
74
|
-
await report({ format: 'html' }, io);
|
|
75
|
-
const output = io.outText();
|
|
76
|
-
expect(output).toContain('<html lang="zh-CN">');
|
|
77
|
-
expect(output).toContain('</html>');
|
|
78
|
-
expect(output).toContain('Harness 检查报告');
|
|
79
|
-
expect(output).toContain('<table>');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
68
|
it('JSON 报告应包含约束统计', async () => {
|
|
83
69
|
await report({ format: 'json' }, io);
|
|
84
70
|
const output = io.outText();
|
|
@@ -99,11 +85,5 @@ describe('report command', () => {
|
|
|
99
85
|
await report({ format: 'markdown', output: 'report.md' }, io);
|
|
100
86
|
expect(mockFs.writeFile).toHaveBeenCalledWith('report.md', expect.stringContaining('# Harness'), 'utf-8');
|
|
101
87
|
});
|
|
102
|
-
|
|
103
|
-
it('应该支持输出到文件 (html)', async () => {
|
|
104
|
-
mockFs.writeFile.mockResolvedValue(undefined);
|
|
105
|
-
await report({ format: 'html', output: 'report.html' }, io);
|
|
106
|
-
expect(mockFs.writeFile).toHaveBeenCalledWith('report.html', expect.stringContaining('<!DOCTYPE html>'), 'utf-8');
|
|
107
|
-
});
|
|
108
88
|
});
|
|
109
89
|
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* status 的分析接线(ADR-0020)
|
|
3
|
+
*
|
|
4
|
+
* `status` 要的是「对已读出的 traces 做纯判定」,此前却为此构造
|
|
5
|
+
* `new TraceCollector({traceFile})`(构造函数带 mkdir 副作用)+ `new TraceAnalyzer(c)`,
|
|
6
|
+
* 测试端再叠一层 TraceAnalyzer mock 才能断言。ADR-0020 把判定下放为模块级纯函数后,
|
|
7
|
+
* 本命令直调函数,构造仪式退出这条路径。
|
|
8
|
+
*
|
|
9
|
+
* 用真实临时工程根跑一遍(不 mock fs),顺带钉住「换成直调后统计与异常输出口径不变」。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { createProjectFixture } from '../../../test-setup/project-fixture';
|
|
15
|
+
import { captureIO, type CapturingIO } from '../../command-contract';
|
|
16
|
+
import { status } from '../status';
|
|
17
|
+
|
|
18
|
+
const STATUS_SOURCE = fs.readFileSync(path.join(__dirname, '..', 'status.ts'), 'utf-8');
|
|
19
|
+
|
|
20
|
+
/** 1 pass + 1 fail:failRate 0.5、passRate 0.5 → 无异常;2 fail:passRate 0 → low_pass_rate */
|
|
21
|
+
const PASS = { constraintId: 'alpha', level: 'iron_law' as const, result: 'pass' as const };
|
|
22
|
+
const FAIL = { constraintId: 'beta', level: 'guideline' as const, result: 'fail' as const };
|
|
23
|
+
|
|
24
|
+
let io: CapturingIO;
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
io = captureIO();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('status 分析接线(ADR-0020)', () => {
|
|
30
|
+
it('不再构造 TraceCollector / TraceAnalyzer', () => {
|
|
31
|
+
expect(STATUS_SOURCE).not.toMatch(/new\s+TraceCollector\s*\(/);
|
|
32
|
+
expect(STATUS_SOURCE).not.toMatch(/new\s+TraceAnalyzer\s*\(/);
|
|
33
|
+
expect(STATUS_SOURCE).not.toMatch(/from\s+'[^']*monitoring\/traces'/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('直调 trace-analyzer 的模块级纯函数取统计与异常', () => {
|
|
37
|
+
expect(STATUS_SOURCE).toMatch(/summarizeTraces\s*\(/);
|
|
38
|
+
expect(STATUS_SOURCE).toMatch(/detectTraceAnomalies\s*\(/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('真实工程根跑通:按纯函数口径出统计,全过数据零异常', async () => {
|
|
42
|
+
const root = createProjectFixture({
|
|
43
|
+
name: 'status-pure-analysis',
|
|
44
|
+
config: 'project:\n name: status-pure-analysis\n',
|
|
45
|
+
traces: [PASS, { ...PASS, timestamp: 2 }],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await status({ projectPath: root, detail: true }, io);
|
|
49
|
+
|
|
50
|
+
expect(io.outText()).toContain('🔴 Iron Laws:');
|
|
51
|
+
expect(io.outText()).toContain('alpha');
|
|
52
|
+
expect(io.outText()).toContain('检查: 2 | 通过: 100% | 失败: 0%');
|
|
53
|
+
expect(io.outText()).not.toContain('个异常');
|
|
54
|
+
expect(io.outText()).toContain('继续积累数据');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('真实工程根跑通:低通过率经纯函数判为异常并列出', async () => {
|
|
58
|
+
const root = createProjectFixture({
|
|
59
|
+
name: 'status-pure-analysis',
|
|
60
|
+
traces: [FAIL, { ...FAIL, timestamp: 2 }],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await status({ projectPath: root, anomalies: true }, io);
|
|
64
|
+
|
|
65
|
+
expect(io.outText()).toContain('发现 1 个异常');
|
|
66
|
+
expect(io.outText()).toContain('类型: low_pass_rate');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* status 命令补充测试
|
|
3
|
+
*
|
|
4
|
+
* ADR-0020 起不再 mock TraceAnalyzer/TraceCollector:异常判定走真实纯函数,
|
|
5
|
+
* 用例喂能真的判出异常的 trace(passRate < 0.3 → low_pass_rate)。
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
8
|
import { status } from '../status';
|
|
6
9
|
import { captureIO, type CapturingIO } from '../../command-contract';
|
|
7
10
|
import * as fs from 'fs';
|
|
8
|
-
import {
|
|
11
|
+
import type { ExecutionTrace } from '../../../types/trace';
|
|
9
12
|
|
|
10
13
|
// Mock fs
|
|
11
14
|
jest.mock('fs', () => ({
|
|
@@ -15,22 +18,6 @@ jest.mock('fs', () => ({
|
|
|
15
18
|
writeFileSync: jest.fn(),
|
|
16
19
|
}));
|
|
17
20
|
|
|
18
|
-
// Mock TraceCollector
|
|
19
|
-
jest.mock('../../../monitoring/traces', () => ({
|
|
20
|
-
TraceCollector: jest.fn().mockImplementation(() => ({
|
|
21
|
-
record: jest.fn(),
|
|
22
|
-
read: jest.fn().mockReturnValue([]),
|
|
23
|
-
})),
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
// Mock TraceAnalyzer
|
|
27
|
-
jest.mock('../../../monitoring/trace-analyzer', () => ({
|
|
28
|
-
TraceAnalyzer: jest.fn().mockImplementation(() => ({
|
|
29
|
-
summarize: jest.fn().mockReturnValue([]),
|
|
30
|
-
detectAnomalies: jest.fn().mockReturnValue([]),
|
|
31
|
-
})),
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
21
|
// Mock chalk
|
|
35
22
|
jest.mock('chalk', () => ({
|
|
36
23
|
blue: jest.fn((str: string) => str),
|
|
@@ -41,7 +28,18 @@ jest.mock('chalk', () => ({
|
|
|
41
28
|
}));
|
|
42
29
|
|
|
43
30
|
const mockFs = fs as jest.Mocked<typeof fs>;
|
|
44
|
-
|
|
31
|
+
|
|
32
|
+
/** trace 文件内容:一 trace 一行,缺省字段补齐成合法 ExecutionTrace(timestamp 递增) */
|
|
33
|
+
function traceFile(traces: Array<Partial<ExecutionTrace>>): string {
|
|
34
|
+
return traces
|
|
35
|
+
.map((t, i) => JSON.stringify({ level: 'iron_law', timestamp: i + 1, result: 'pass', ...t }))
|
|
36
|
+
.join('\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** n 条同一约束的 pass/fail 记录 */
|
|
40
|
+
function repeats(constraintId: string, n: number, result: ExecutionTrace['result']): Array<Partial<ExecutionTrace>> {
|
|
41
|
+
return Array.from({ length: n }, () => ({ constraintId, result }));
|
|
42
|
+
}
|
|
45
43
|
|
|
46
44
|
let io: CapturingIO;
|
|
47
45
|
beforeEach(() => {
|
|
@@ -60,140 +58,89 @@ describe('status command - 补充覆盖', () => {
|
|
|
60
58
|
|
|
61
59
|
describe('异常检测完整流程', () => {
|
|
62
60
|
it('有异常时应该显示详细信息和下一步建议', async () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const mockAnomalies = [
|
|
69
|
-
{
|
|
70
|
-
constraintId: 'no_bypass_checkpoint',
|
|
71
|
-
type: 'high_failure_rate',
|
|
72
|
-
current: 0.8,
|
|
73
|
-
threshold: 0.5,
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
constraintId: 'no_completion_without_verification',
|
|
77
|
-
type: 'frequent_bypass',
|
|
78
|
-
current: 0.3,
|
|
79
|
-
threshold: 0.1,
|
|
80
|
-
},
|
|
81
|
-
];
|
|
82
|
-
const mockAnalyze = {
|
|
83
|
-
summarize: jest.fn().mockReturnValue([
|
|
84
|
-
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 0.2, failRate: 0.8, totalChecks: 10 },
|
|
85
|
-
]),
|
|
86
|
-
detectAnomalies: jest.fn().mockReturnValue(mockAnomalies),
|
|
87
|
-
};
|
|
88
|
-
(MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
|
|
61
|
+
// 两个约束各自全失败 → 各自判出一枚 low_pass_rate
|
|
62
|
+
mockFs.readFileSync.mockReturnValue(traceFile([
|
|
63
|
+
...repeats('no_bypass_checkpoint', 2, 'fail'),
|
|
64
|
+
...repeats('no_completion_without_verification', 2, 'fail'),
|
|
65
|
+
]));
|
|
89
66
|
|
|
90
67
|
await status({ anomalies: true }, io);
|
|
91
|
-
|
|
68
|
+
|
|
92
69
|
const output = io.outText();
|
|
93
|
-
expect(output).toContain('
|
|
94
|
-
expect(
|
|
70
|
+
expect(output).toContain('发现 2 个异常');
|
|
71
|
+
expect(output).toContain('no_completion_without_verification');
|
|
72
|
+
expect(output).toContain('类型: low_pass_rate');
|
|
73
|
+
expect(output).toContain('下一步建议');
|
|
95
74
|
});
|
|
96
75
|
|
|
97
76
|
it('无异常时应该显示未发现异常', async () => {
|
|
98
|
-
mockFs.readFileSync.mockReturnValue(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
summarize: jest.fn().mockReturnValue([]),
|
|
102
|
-
detectAnomalies: jest.fn().mockReturnValue([]),
|
|
103
|
-
};
|
|
104
|
-
(MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
|
|
77
|
+
mockFs.readFileSync.mockReturnValue(traceFile([
|
|
78
|
+
{ constraintId: 'test', result: 'pass' },
|
|
79
|
+
]));
|
|
105
80
|
|
|
106
81
|
await status({ anomalies: true }, io);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
expect(output).toContain('未发现异常');
|
|
82
|
+
|
|
83
|
+
expect(io.outText()).toContain('✅ 未发现异常');
|
|
110
84
|
});
|
|
111
85
|
});
|
|
112
86
|
|
|
113
87
|
describe('约束级别统计', () => {
|
|
114
88
|
it('应该显示 Iron Laws 统计', async () => {
|
|
115
|
-
|
|
116
|
-
{ constraintId: 'no_bypass_checkpoint'
|
|
117
|
-
{ constraintId: 'no_completion_without_verification'
|
|
118
|
-
];
|
|
119
|
-
mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
|
|
120
|
-
|
|
121
|
-
const mockSummaries = [
|
|
122
|
-
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 10 },
|
|
123
|
-
{ constraintId: 'no_completion_without_verification', level: 'iron_law', passRate: 0.8, failRate: 0.2, totalChecks: 5 },
|
|
124
|
-
];
|
|
125
|
-
const mockAnalyze = {
|
|
126
|
-
summarize: jest.fn().mockReturnValue(mockSummaries),
|
|
127
|
-
detectAnomalies: jest.fn().mockReturnValue([]),
|
|
128
|
-
};
|
|
129
|
-
(MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
|
|
89
|
+
mockFs.readFileSync.mockReturnValue(traceFile([
|
|
90
|
+
{ constraintId: 'no_bypass_checkpoint' },
|
|
91
|
+
{ constraintId: 'no_completion_without_verification' },
|
|
92
|
+
]));
|
|
130
93
|
|
|
131
94
|
await status({}, io);
|
|
132
|
-
|
|
95
|
+
|
|
133
96
|
const output = io.outText();
|
|
134
|
-
expect(output).toContain('
|
|
97
|
+
expect(output).toContain('📈 约束统计:');
|
|
98
|
+
expect(output).toContain('🔴 Iron Laws:');
|
|
99
|
+
expect(output).toContain('✅ no_completion_without_verification');
|
|
135
100
|
});
|
|
136
101
|
|
|
137
102
|
it('应该显示 Guidelines 统计', async () => {
|
|
138
|
-
|
|
103
|
+
mockFs.readFileSync.mockReturnValue(traceFile([
|
|
139
104
|
{ constraintId: 'capability_sync', level: 'guideline', result: 'pass' },
|
|
140
|
-
];
|
|
141
|
-
mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
|
|
142
|
-
|
|
143
|
-
const mockSummaries = [
|
|
144
|
-
{ constraintId: 'capability_sync', level: 'guideline', passRate: 0.9, failRate: 0.1, totalChecks: 20 },
|
|
145
|
-
];
|
|
146
|
-
const mockAnalyze = {
|
|
147
|
-
summarize: jest.fn().mockReturnValue(mockSummaries),
|
|
148
|
-
detectAnomalies: jest.fn().mockReturnValue([]),
|
|
149
|
-
};
|
|
150
|
-
(MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
|
|
105
|
+
]));
|
|
151
106
|
|
|
152
107
|
await status({}, io);
|
|
153
|
-
|
|
154
|
-
expect(io.outText()).
|
|
108
|
+
|
|
109
|
+
expect(io.outText()).toContain('🟡 Guidelines:');
|
|
155
110
|
});
|
|
156
111
|
});
|
|
157
112
|
|
|
158
113
|
describe('详细模式扩展', () => {
|
|
159
114
|
it('详细模式应该显示所有级别详情', async () => {
|
|
160
|
-
|
|
161
|
-
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law'
|
|
162
|
-
{ constraintId: 'capability_sync', level: 'guideline'
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const mockSummaries = [
|
|
167
|
-
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 10 },
|
|
168
|
-
{ constraintId: 'capability_sync', level: 'guideline', passRate: 0.9, failRate: 0.1, totalChecks: 20 },
|
|
169
|
-
];
|
|
170
|
-
const mockAnalyze = {
|
|
171
|
-
summarize: jest.fn().mockReturnValue(mockSummaries),
|
|
172
|
-
detectAnomalies: jest.fn().mockReturnValue([]),
|
|
173
|
-
};
|
|
174
|
-
(MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
|
|
115
|
+
mockFs.readFileSync.mockReturnValue(traceFile([
|
|
116
|
+
{ constraintId: 'no_bypass_checkpoint', level: 'iron_law' },
|
|
117
|
+
{ constraintId: 'capability_sync', level: 'guideline' },
|
|
118
|
+
{ constraintId: 'capability_sync', level: 'guideline' },
|
|
119
|
+
]));
|
|
175
120
|
|
|
176
121
|
await status({ detail: true }, io);
|
|
177
|
-
|
|
122
|
+
|
|
178
123
|
const output = io.outText();
|
|
179
|
-
expect(output).toContain('
|
|
124
|
+
expect(output).toContain('📈 约束统计:');
|
|
125
|
+
expect(output).toContain('🔴 Iron Laws:');
|
|
126
|
+
expect(output).toContain('🟡 Guidelines:');
|
|
127
|
+
expect(output).toContain('检查: 2 | 通过: 100% | 失败: 0%');
|
|
180
128
|
});
|
|
181
129
|
});
|
|
182
130
|
|
|
183
131
|
describe('JSON parse 异常处理', () => {
|
|
184
132
|
it('无效 JSON 行应该被过滤', async () => {
|
|
185
|
-
mockFs.readFileSync.mockReturnValue(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
summarize: jest.fn().mockReturnValue([]),
|
|
189
|
-
detectAnomalies: jest.fn().mockReturnValue([]),
|
|
190
|
-
};
|
|
191
|
-
(MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
|
|
133
|
+
mockFs.readFileSync.mockReturnValue(
|
|
134
|
+
['invalid json', JSON.stringify({ constraintId: 'valid', level: 'iron_law', timestamp: 1, result: 'pass' }), 'also invalid'].join('\n')
|
|
135
|
+
);
|
|
192
136
|
|
|
193
137
|
await status({}, io);
|
|
194
|
-
|
|
195
|
-
//
|
|
196
|
-
|
|
138
|
+
|
|
139
|
+
// 应该成功处理,不会抛出异常:坏行进 stderr 告知,合法行进统计
|
|
140
|
+
const output = io.outText();
|
|
141
|
+
expect(output).toContain('记录数: 3 条');
|
|
142
|
+
expect(output).toContain('✅ valid');
|
|
143
|
+
expect(io.errText()).toContain('2 行损坏');
|
|
197
144
|
});
|
|
198
145
|
});
|
|
199
|
-
});
|
|
146
|
+
});
|