@dommaker/harness 1.4.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 +23 -0
- package/README.md +0 -2
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +28 -0
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +1 -39
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/knowledge.d.ts +0 -19
- package/dist/cli/commands/knowledge.d.ts.map +1 -1
- package/dist/cli/commands/knowledge.js +0 -105
- package/dist/cli/commands/knowledge.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 +27 -5
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/checkers/capability-sync.d.ts +17 -6
- package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
- package/dist/core/constraints/checkers/capability-sync.js +48 -19
- package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js +15 -6
- package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
- package/dist/core/constraints/checkers/index.d.ts +2 -2
- package/dist/core/constraints/checkers/index.d.ts.map +1 -1
- package/dist/core/constraints/checkers/index.js +2 -1
- package/dist/core/constraints/checkers/index.js.map +1 -1
- package/dist/core/constraints/checkers/types.d.ts +41 -2
- package/dist/core/constraints/checkers/types.d.ts.map +1 -1
- package/dist/core/constraints/checkers/types.js +33 -0
- package/dist/core/constraints/checkers/types.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/checker-gate.d.ts +2 -1
- package/dist/gates/checker-gate.d.ts.map +1 -1
- package/dist/gates/checker-gate.js +10 -7
- package/dist/gates/checker-gate.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/constraint.d.ts +8 -0
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js +6 -1
- package/dist/types/constraint.js.map +1 -1
- package/dist/types/passes-gate.d.ts +0 -19
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/dist/types/trace.d.ts +9 -0
- package/dist/types/trace.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/CONTEXT.md +1 -0
- package/src/__tests__/passes-gate-check.test.ts +20 -4
- package/src/__tests__/passes-gate.test.ts +18 -119
- 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 +5 -4
- package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +6 -0
- package/src/cli/commands/__tests__/check.test.ts +74 -0
- 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__/project-path-convention.test.ts +85 -23
- package/src/cli/commands/__tests__/registry.test.ts +52 -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/check.ts +39 -1
- package/src/cli/commands/definitions.ts +1 -39
- package/src/cli/commands/knowledge.ts +0 -119
- 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 +6 -6
- package/src/core/constraints/__tests__/collect-constraints.test.ts +66 -0
- package/src/core/constraints/checker.ts +41 -6
- package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +90 -31
- package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +176 -0
- package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +18 -9
- package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +12 -4
- package/src/core/constraints/checkers/capability-sync.ts +56 -18
- package/src/core/constraints/checkers/docs-freshness.ts +18 -5
- package/src/core/constraints/checkers/index.ts +9 -2
- package/src/core/constraints/checkers/types.ts +58 -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 +4 -3
- package/src/gates/__tests__/checker-gate.test.ts +24 -0
- package/src/gates/__tests__/performance.test.ts +6 -74
- package/src/gates/checker-gate.ts +11 -15
- 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/constraint.ts +16 -2
- package/src/types/passes-gate.ts +0 -26
- package/src/types/trace.ts +10 -0
- 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 -25
- package/dist/cli/commands/update-user-model.d.ts.map +0 -1
- package/dist/cli/commands/update-user-model.js +0 -397
- 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 -234
- package/src/cli/commands/analyze-sessions.ts +0 -335
- package/src/cli/commands/update-user-model.ts +0 -461
- 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
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SessionStartup - Session 启动检查点验证
|
|
3
|
-
*
|
|
4
|
-
* 确保 Agent 启动时必须先读上下文,防止"失忆"乱搞。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { execAsync } from '../../utils/exec';
|
|
8
|
-
import * as fs from 'fs/promises';
|
|
9
|
-
import * as path from 'path';
|
|
10
|
-
import * as yaml from 'js-yaml';
|
|
11
|
-
import type {
|
|
12
|
-
StartupCheckpoints,
|
|
13
|
-
StartupCheckpointResult,
|
|
14
|
-
StartupCheckpointType,
|
|
15
|
-
TaskListJson,
|
|
16
|
-
} from '../../types/session';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const DEFAULT_TIMEOUT = 30000; // 30 seconds
|
|
20
|
-
|
|
21
|
-
export class SessionStartup {
|
|
22
|
-
private checkpoints: StartupCheckpoints;
|
|
23
|
-
private workDir: string;
|
|
24
|
-
|
|
25
|
-
constructor(workDir: string, checkpoints: StartupCheckpoints) {
|
|
26
|
-
this.workDir = workDir;
|
|
27
|
-
this.checkpoints = checkpoints;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async run(): Promise<{ success: boolean; results: StartupCheckpointResult[]; errors: string[] }> {
|
|
31
|
-
const results: StartupCheckpointResult[] = [];
|
|
32
|
-
const errors: string[] = [];
|
|
33
|
-
|
|
34
|
-
// 1. Run required checkpoints
|
|
35
|
-
for (const type of this.checkpoints.required) {
|
|
36
|
-
const result = await this.runCheckpoint(type);
|
|
37
|
-
results.push(result);
|
|
38
|
-
if (!result.success) {
|
|
39
|
-
errors.push(`Required checkpoint failed: ${type} - ${result.error}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// 2. Run optional checkpoints (don't fail on error)
|
|
44
|
-
for (const type of this.checkpoints.optional || []) {
|
|
45
|
-
const result = await this.runCheckpoint(type);
|
|
46
|
-
results.push(result);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
success: errors.length === 0,
|
|
51
|
-
results,
|
|
52
|
-
errors,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private async runCheckpoint(type: StartupCheckpointType): Promise<StartupCheckpointResult> {
|
|
57
|
-
const start = Date.now();
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
let data: any;
|
|
61
|
-
|
|
62
|
-
switch (type) {
|
|
63
|
-
case 'pwd':
|
|
64
|
-
data = await this.checkPwd();
|
|
65
|
-
break;
|
|
66
|
-
case 'git_log':
|
|
67
|
-
data = await this.checkGitLog();
|
|
68
|
-
break;
|
|
69
|
-
case 'git_status':
|
|
70
|
-
data = await this.checkGitStatus();
|
|
71
|
-
break;
|
|
72
|
-
case 'read_progress':
|
|
73
|
-
data = await this.checkProgress();
|
|
74
|
-
break;
|
|
75
|
-
case 'read_task_list':
|
|
76
|
-
data = await this.checkTaskList();
|
|
77
|
-
break;
|
|
78
|
-
case 'init_sh':
|
|
79
|
-
data = await this.checkInitSh();
|
|
80
|
-
break;
|
|
81
|
-
case 'basic_verification':
|
|
82
|
-
data = await this.checkBasicVerification();
|
|
83
|
-
break;
|
|
84
|
-
case 'load_context':
|
|
85
|
-
data = await this.checkLoadContext();
|
|
86
|
-
break;
|
|
87
|
-
default:
|
|
88
|
-
throw new Error(`Unknown checkpoint type: ${type}`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return { type, success: true, data, duration: Date.now() - start };
|
|
92
|
-
} catch (error: any) {
|
|
93
|
-
return { type, success: false, error: error.message, duration: Date.now() - start };
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private async checkPwd(): Promise<string> {
|
|
98
|
-
return this.workDir;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private async checkGitLog(): Promise<string[]> {
|
|
102
|
-
try {
|
|
103
|
-
const { stdout } = await execAsync('git log --oneline -10', { cwd: this.workDir });
|
|
104
|
-
return stdout.split('\n').filter(Boolean);
|
|
105
|
-
} catch {
|
|
106
|
-
return [];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private async checkGitStatus(): Promise<{ staged: string[]; unstaged: string[]; untracked: string[] }> {
|
|
111
|
-
try {
|
|
112
|
-
const { stdout } = await execAsync('git status --porcelain', { cwd: this.workDir });
|
|
113
|
-
const staged: string[] = [], unstaged: string[] = [], untracked: string[] = [];
|
|
114
|
-
|
|
115
|
-
for (const line of stdout.split('\n').filter(Boolean)) {
|
|
116
|
-
const status = line.substring(0, 2);
|
|
117
|
-
const file = line.substring(3);
|
|
118
|
-
if (status.includes('??')) untracked.push(file);
|
|
119
|
-
else if (status.match(/[MADRC]/)) staged.push(file);
|
|
120
|
-
else unstaged.push(file);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return { staged, unstaged, untracked };
|
|
124
|
-
} catch {
|
|
125
|
-
return { staged: [], unstaged: [], untracked: [] };
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private async checkProgress(): Promise<any> {
|
|
130
|
-
const progressPath = path.join(this.workDir, '.harness', 'progress.yml');
|
|
131
|
-
try {
|
|
132
|
-
const content = await fs.readFile(progressPath, 'utf-8');
|
|
133
|
-
return yaml.load(content) as any;
|
|
134
|
-
} catch {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
private async checkTaskList(): Promise<TaskListJson | null> {
|
|
140
|
-
// Try tasks.yml first (existing format)
|
|
141
|
-
const tasksPath = path.join(this.workDir, 'tasks.yml');
|
|
142
|
-
try {
|
|
143
|
-
const content = await fs.readFile(tasksPath, 'utf-8');
|
|
144
|
-
return yaml.load(content) as TaskListJson;
|
|
145
|
-
} catch {
|
|
146
|
-
// Try tasks.json
|
|
147
|
-
const jsonPath = path.join(this.workDir, 'tasks.json');
|
|
148
|
-
try {
|
|
149
|
-
const content = await fs.readFile(jsonPath, 'utf-8');
|
|
150
|
-
return JSON.parse(content);
|
|
151
|
-
} catch {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private async checkInitSh(): Promise<boolean> {
|
|
158
|
-
const initPath = path.join(this.workDir, 'init.sh');
|
|
159
|
-
try {
|
|
160
|
-
await fs.access(initPath);
|
|
161
|
-
return true;
|
|
162
|
-
} catch {
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private async checkBasicVerification(): Promise<{ hasPackageJson: boolean; hasTests: boolean; hasReadme: boolean }> {
|
|
168
|
-
const [hasPackageJson, hasTests, hasReadme] = await Promise.all([
|
|
169
|
-
fs.access(path.join(this.workDir, 'package.json')).then(() => true).catch(() => false),
|
|
170
|
-
this.hasTestFiles(),
|
|
171
|
-
fs.access(path.join(this.workDir, 'README.md')).then(() => true).catch(() => false),
|
|
172
|
-
]);
|
|
173
|
-
return { hasPackageJson, hasTests, hasReadme };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
private async hasTestFiles(): Promise<boolean> {
|
|
177
|
-
const testPatterns = ['test', 'spec', '__tests__'];
|
|
178
|
-
try {
|
|
179
|
-
const files = await fs.readdir(this.workDir);
|
|
180
|
-
return files.some(f => testPatterns.some(p => f.toLowerCase().includes(p)));
|
|
181
|
-
} catch {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private async checkLoadContext(): Promise<any> {
|
|
187
|
-
const context: Record<string, string> = {};
|
|
188
|
-
const files = this.checkpoints.contextFiles || [];
|
|
189
|
-
|
|
190
|
-
for (const file of files) {
|
|
191
|
-
try {
|
|
192
|
-
context[file] = await fs.readFile(path.join(this.workDir, file), 'utf-8');
|
|
193
|
-
} catch {}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return context;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Get current task from task list
|
|
201
|
-
*/
|
|
202
|
-
async getCurrentTask(): Promise<{ task: any; index: number } | null> {
|
|
203
|
-
const taskList = await this.checkTaskList();
|
|
204
|
-
if (!taskList?.tasks) return null;
|
|
205
|
-
|
|
206
|
-
for (let i = 0; i < taskList.tasks.length; i++) {
|
|
207
|
-
const task = taskList.tasks[i];
|
|
208
|
-
if (task && !task.passes) {
|
|
209
|
-
return { task, index: i };
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Generate startup report
|
|
218
|
-
*/
|
|
219
|
-
generateReport(results: StartupCheckpointResult[]): string {
|
|
220
|
-
const lines: string[] = ['## Session Startup Report\n'];
|
|
221
|
-
|
|
222
|
-
for (const result of results) {
|
|
223
|
-
const icon = result.success ? '✅' : '❌';
|
|
224
|
-
lines.push(`${icon} ${result.type} (${result.duration}ms)`);
|
|
225
|
-
if (result.error) {
|
|
226
|
-
lines.push(` Error: ${result.error}`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return lines.join('\n');
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export const createSessionStartup = (workDir: string, checkpoints: StartupCheckpoints) =>
|
|
235
|
-
new SessionStartup(workDir, checkpoints);
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Default startup checkpoints for code workflows
|
|
239
|
-
*/
|
|
240
|
-
export const DEFAULT_CODE_CHECKPOINTS: StartupCheckpoints = {
|
|
241
|
-
required: ['pwd', 'git_log', 'read_task_list'],
|
|
242
|
-
optional: ['read_progress', 'init_sh', 'basic_verification'],
|
|
243
|
-
timeout: DEFAULT_TIMEOUT,
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Minimal startup checkpoints
|
|
248
|
-
*/
|
|
249
|
-
export const MINIMAL_CHECKPOINTS: StartupCheckpoints = {
|
|
250
|
-
required: ['pwd', 'read_task_list'],
|
|
251
|
-
optional: [],
|
|
252
|
-
timeout: DEFAULT_TIMEOUT,
|
|
253
|
-
};
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ConstraintViolationHandler — 约束违规统一处理(S4)
|
|
3
|
-
*
|
|
4
|
-
* 问题:ConstraintViolationError 在 4 个调用点有 4 种行为:
|
|
5
|
-
* 1. 抛异常阻断(checker.ts)
|
|
6
|
-
* 2. 返回 null(report.ts catch 后返回空数据)
|
|
7
|
-
* 3. 返回 403 HTTP 状态
|
|
8
|
-
* 4. console.warn 静默吞掉
|
|
9
|
-
*
|
|
10
|
-
* 解决:提供 3 种标准化处理策略,消除 ad-hoc catch 行为。
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { ConstraintCheckResult } from '../types/constraint';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 违规处理策略
|
|
17
|
-
*/
|
|
18
|
-
export type ViolationStrategy = 'BLOCK' | 'COLLECT' | 'SAFE_BOOLEAN';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 违规处理结果
|
|
22
|
-
*/
|
|
23
|
-
export interface ViolationHandlingResult {
|
|
24
|
-
/** 策略 */
|
|
25
|
-
strategy: ViolationStrategy;
|
|
26
|
-
/** 检查结果(COLLECT 策略时总是有值) */
|
|
27
|
-
checkResult?: ConstraintCheckResult;
|
|
28
|
-
/** 是否有违规(SAFE_BOOLEAN 策略时为 false) */
|
|
29
|
-
hasViolation: boolean;
|
|
30
|
-
/** 违规数量 */
|
|
31
|
-
violationCount: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 约束违规处理器
|
|
36
|
-
*
|
|
37
|
-
* 用法:
|
|
38
|
-
* ```typescript
|
|
39
|
-
* const handler = new ConstraintViolationHandler();
|
|
40
|
-
*
|
|
41
|
-
* // 策略 1: BLOCK — 抛异常阻断(Iron Laws 默认)
|
|
42
|
-
* await handler.execute(checkFn, 'BLOCK');
|
|
43
|
-
*
|
|
44
|
-
* // 策略 2: COLLECT — 收集违规不抛异常(用于报告生成)
|
|
45
|
-
* const result = await handler.execute(checkFn, 'COLLECT');
|
|
46
|
-
* // result.checkResult 总是有值
|
|
47
|
-
*
|
|
48
|
-
* // 策略 3: SAFE_BOOLEAN — 返回真/假(用于 canProceed 类方法)
|
|
49
|
-
* const ok = await handler.execute(checkFn, 'SAFE_BOOLEAN');
|
|
50
|
-
* // ok.hasViolation 为 true/false
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
export class ConstraintViolationHandler {
|
|
54
|
-
/**
|
|
55
|
-
* 执行约束检查并统一处理结果
|
|
56
|
-
*
|
|
57
|
-
* @param checkFn 约束检查函数(可能抛出 ConstraintViolationError)
|
|
58
|
-
* @param strategy 处理策略
|
|
59
|
-
* @returns 标准化结果
|
|
60
|
-
*/
|
|
61
|
-
async execute(
|
|
62
|
-
checkFn: () => Promise<ConstraintCheckResult>,
|
|
63
|
-
strategy: ViolationStrategy
|
|
64
|
-
): Promise<ViolationHandlingResult> {
|
|
65
|
-
switch (strategy) {
|
|
66
|
-
case 'BLOCK':
|
|
67
|
-
return this.handleBlock(checkFn);
|
|
68
|
-
case 'COLLECT':
|
|
69
|
-
return this.handleCollect(checkFn);
|
|
70
|
-
case 'SAFE_BOOLEAN':
|
|
71
|
-
return this.handleSafeBoolean(checkFn);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* BLOCK 策略:违规即抛出,无违规返回结果
|
|
77
|
-
*/
|
|
78
|
-
private async handleBlock(
|
|
79
|
-
checkFn: () => Promise<ConstraintCheckResult>
|
|
80
|
-
): Promise<ViolationHandlingResult> {
|
|
81
|
-
const result = await checkFn();
|
|
82
|
-
return {
|
|
83
|
-
strategy: 'BLOCK',
|
|
84
|
-
checkResult: result,
|
|
85
|
-
hasViolation: !result.passed,
|
|
86
|
-
violationCount: result.ironLaws.filter(r => !r.satisfied).length,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* COLLECT 策略:捕获异常,收集所有违规,不抛出
|
|
92
|
-
*/
|
|
93
|
-
private async handleCollect(
|
|
94
|
-
checkFn: () => Promise<ConstraintCheckResult>
|
|
95
|
-
): Promise<ViolationHandlingResult> {
|
|
96
|
-
try {
|
|
97
|
-
const result = await checkFn();
|
|
98
|
-
const ironViolations = result.ironLaws.filter(r => !r.satisfied).length;
|
|
99
|
-
const guideViolations = result.warningCount;
|
|
100
|
-
return {
|
|
101
|
-
strategy: 'COLLECT',
|
|
102
|
-
checkResult: result,
|
|
103
|
-
hasViolation: !result.passed || guideViolations > 0,
|
|
104
|
-
violationCount: ironViolations + guideViolations,
|
|
105
|
-
};
|
|
106
|
-
} catch {
|
|
107
|
-
// 捕获 ConstraintViolationError,返回失败结果
|
|
108
|
-
const empty: ConstraintCheckResult = {
|
|
109
|
-
ironLaws: [],
|
|
110
|
-
guidelines: [],
|
|
111
|
-
passed: false,
|
|
112
|
-
warningCount: 0,
|
|
113
|
-
};
|
|
114
|
-
return {
|
|
115
|
-
strategy: 'COLLECT',
|
|
116
|
-
checkResult: empty,
|
|
117
|
-
hasViolation: true,
|
|
118
|
-
violationCount: 1,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* SAFE_BOOLEAN 策略:不抛异常,只返回 boolean
|
|
125
|
-
*/
|
|
126
|
-
private async handleSafeBoolean(
|
|
127
|
-
checkFn: () => Promise<ConstraintCheckResult>
|
|
128
|
-
): Promise<ViolationHandlingResult> {
|
|
129
|
-
try {
|
|
130
|
-
const result = await checkFn();
|
|
131
|
-
return {
|
|
132
|
-
strategy: 'SAFE_BOOLEAN',
|
|
133
|
-
hasViolation: !result.passed,
|
|
134
|
-
violationCount: result.ironLaws.filter(r => !r.satisfied).length,
|
|
135
|
-
};
|
|
136
|
-
} catch {
|
|
137
|
-
return {
|
|
138
|
-
strategy: 'SAFE_BOOLEAN',
|
|
139
|
-
hasViolation: true,
|
|
140
|
-
violationCount: 1,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* 快捷:BLOCK 策略执行检查
|
|
148
|
-
*/
|
|
149
|
-
export async function executeWithBlock(
|
|
150
|
-
checkFn: () => Promise<ConstraintCheckResult>
|
|
151
|
-
): Promise<ViolationHandlingResult> {
|
|
152
|
-
return new ConstraintViolationHandler().execute(checkFn, 'BLOCK');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 快捷:COLLECT 策略执行检查
|
|
157
|
-
*/
|
|
158
|
-
export async function executeWithCollect(
|
|
159
|
-
checkFn: () => Promise<ConstraintCheckResult>
|
|
160
|
-
): Promise<ViolationHandlingResult> {
|
|
161
|
-
return new ConstraintViolationHandler().execute(checkFn, 'COLLECT');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* 快捷:SAFE_BOOLEAN 策略执行检查
|
|
166
|
-
*/
|
|
167
|
-
export async function executeWithSafeBoolean(
|
|
168
|
-
checkFn: () => Promise<ConstraintCheckResult>
|
|
169
|
-
): Promise<ViolationHandlingResult> {
|
|
170
|
-
return new ConstraintViolationHandler().execute(checkFn, 'SAFE_BOOLEAN');
|
|
171
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* KnowledgeLifecycleHooks 测试
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { KnowledgeLifecycleHooks } from '../lifecycle-hooks';
|
|
6
|
-
import { FileKnowledgeStore as KnowledgeStore } from '../store';
|
|
7
|
-
import { KnowledgeQuery } from '../query';
|
|
8
|
-
import * as fs from 'fs';
|
|
9
|
-
import * as path from 'path';
|
|
10
|
-
import * as os from 'os';
|
|
11
|
-
|
|
12
|
-
describe('KnowledgeLifecycleHooks', () => {
|
|
13
|
-
let store: KnowledgeStore;
|
|
14
|
-
let query: KnowledgeQuery;
|
|
15
|
-
let hooks: KnowledgeLifecycleHooks;
|
|
16
|
-
let tmpDir: string;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hooks-test-'));
|
|
20
|
-
store = new KnowledgeStore({ baseDir: path.join(tmpDir, 'knowledge') });
|
|
21
|
-
query = new KnowledgeQuery(store);
|
|
22
|
-
hooks = new KnowledgeLifecycleHooks({ store, query });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
function saveEntry(overrides: any = {}) {
|
|
30
|
-
store.save({
|
|
31
|
-
id: overrides.id || `test-${Math.random().toString(36).slice(2, 6)}`,
|
|
32
|
-
type: 'decision',
|
|
33
|
-
title: 'Test Decision',
|
|
34
|
-
content: 'Some content',
|
|
35
|
-
maturity: 'verified',
|
|
36
|
-
layer: 'project',
|
|
37
|
-
created: new Date().toISOString(),
|
|
38
|
-
lastReferenced: new Date().toISOString(),
|
|
39
|
-
contributors: [],
|
|
40
|
-
projects: [],
|
|
41
|
-
tags: [],
|
|
42
|
-
applicablePhases: [],
|
|
43
|
-
sourceReferences: [],
|
|
44
|
-
referencedBy: [],
|
|
45
|
-
executionResults: [],
|
|
46
|
-
consumptionMode: 'reference',
|
|
47
|
-
origin: 'agent',
|
|
48
|
-
...overrides,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
describe('onSessionStart', () => {
|
|
53
|
-
it('应该返回空数组当无知识条目', () => {
|
|
54
|
-
const sources = hooks.onSessionStart();
|
|
55
|
-
expect(sources.length).toBe(0);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('应该注入知识条目为 ContextSource', () => {
|
|
59
|
-
saveEntry({ id: 'dec-001', title: 'Use TypeScript', content: 'Always use TS', tags: ['typescript', 'best-practice'] });
|
|
60
|
-
const sources = hooks.onSessionStart({ budget: 8000 });
|
|
61
|
-
expect(sources.length).toBe(1);
|
|
62
|
-
expect(sources[0].type).toBe('knowledge');
|
|
63
|
-
expect(sources[0].priority).toBe(3);
|
|
64
|
-
expect(sources[0].content).toContain('Use TypeScript');
|
|
65
|
-
expect(sources[0].content).toContain('typescript');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('应该排除指定条目', () => {
|
|
69
|
-
saveEntry({ id: 'ex-1', title: 'Excluded' });
|
|
70
|
-
const sources = hooks.onSessionStart({ budget: 8000, exclude: ['ex-1'] });
|
|
71
|
-
expect(sources.length).toBe(0);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe('onTaskComplete', () => {
|
|
76
|
-
it('应该从错误中提取 pitfall', () => {
|
|
77
|
-
const result = hooks.onTaskComplete({
|
|
78
|
-
taskDescription: 'Fix login bug',
|
|
79
|
-
errors: ['TypeError: Cannot read property of undefined'],
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
expect(result.entries.length).toBe(1);
|
|
83
|
-
expect(result.entries[0].type).toBe('pitfall');
|
|
84
|
-
expect(result.entries[0].tags).toContain('auto-extract');
|
|
85
|
-
expect(result.source).toBe('archive');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('应该从决策中提取 decision', () => {
|
|
89
|
-
const result = hooks.onTaskComplete({
|
|
90
|
-
taskDescription: 'Choose architecture',
|
|
91
|
-
decisions: ['使用微服务架构因为需要独立部署'],
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
expect(result.entries.length).toBe(1);
|
|
95
|
-
expect(result.entries[0].type).toBe('decision');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('应该限制提取数量', () => {
|
|
99
|
-
const errors = Array.from({ length: 10 }, (_, i) => `Error ${i}`);
|
|
100
|
-
const result = hooks.onTaskComplete({
|
|
101
|
-
taskDescription: 'Test',
|
|
102
|
-
errors,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
expect(result.entries.length).toBeLessThanOrEqual(3);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('应该处理空上下文', () => {
|
|
109
|
-
const result = hooks.onTaskComplete({ taskDescription: 'Empty' });
|
|
110
|
-
expect(result.entries.length).toBe(0);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('onError', () => {
|
|
115
|
-
it('应该从错误中提取知识', () => {
|
|
116
|
-
const result = hooks.onError(new Error('Connection timeout'), {
|
|
117
|
-
taskDescription: 'API call',
|
|
118
|
-
stackTrace: 'at line 42',
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
expect(result.entries.length).toBe(1);
|
|
122
|
-
expect(result.entries[0].type).toBe('pitfall');
|
|
123
|
-
expect(result.entries[0].content).toContain('Connection timeout');
|
|
124
|
-
expect(result.entries[0].content).toContain('API call');
|
|
125
|
-
expect(result.source).toBe('error');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('应该保存到知识库', () => {
|
|
129
|
-
hooks.onError(new Error('Test error'));
|
|
130
|
-
const entries = store.list({ excludeArchived: false });
|
|
131
|
-
expect(entries.length).toBe(1);
|
|
132
|
-
expect(entries[0].title).toContain('Test error');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('应该处理无上下文的错误', () => {
|
|
136
|
-
const result = hooks.onError(new Error('Simple error'));
|
|
137
|
-
expect(result.entries.length).toBe(1);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
});
|