claude-coder 1.7.1 → 1.8.1
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/README.md +80 -46
- package/bin/cli.js +41 -43
- package/package.json +7 -2
- package/src/{auth.js → commands/auth.js} +41 -46
- package/src/commands/setup-modules/helpers.js +99 -0
- package/src/commands/setup-modules/index.js +26 -0
- package/src/commands/setup-modules/mcp.js +95 -0
- package/src/commands/setup-modules/provider.js +261 -0
- package/src/commands/setup-modules/safety.js +62 -0
- package/src/commands/setup-modules/simplify.js +53 -0
- package/src/commands/setup.js +172 -0
- package/src/common/assets.js +206 -0
- package/src/{config.js → common/config.js} +17 -93
- package/src/common/constants.js +56 -0
- package/src/{indicator.js → common/indicator.js} +45 -56
- package/src/common/interaction.js +170 -0
- package/src/common/logging.js +78 -0
- package/src/common/sdk.js +51 -0
- package/src/common/tasks.js +88 -0
- package/src/common/utils.js +162 -0
- package/src/core/coding.js +55 -0
- package/src/core/context.js +117 -0
- package/src/core/harness.js +484 -0
- package/src/core/hooks.js +533 -0
- package/src/{init.js → core/init.js} +31 -12
- package/src/core/plan.js +325 -0
- package/src/core/prompts.js +226 -0
- package/src/core/query.js +50 -0
- package/src/core/repair.js +46 -0
- package/src/core/runner.js +195 -0
- package/src/core/scan.js +89 -0
- package/src/core/session.js +57 -0
- package/src/core/simplify.js +52 -0
- package/templates/bash-process.md +12 -0
- package/templates/codingSystem.md +65 -0
- package/templates/codingUser.md +17 -0
- package/templates/coreProtocol.md +29 -0
- package/templates/guidance.json +35 -0
- package/templates/planSystem.md +78 -0
- package/templates/planUser.md +9 -0
- package/templates/playwright.md +17 -0
- package/templates/requirements.example.md +4 -3
- package/templates/scanSystem.md +120 -0
- package/templates/scanUser.md +10 -0
- package/prompts/ADD_GUIDE.md +0 -98
- package/prompts/CLAUDE.md +0 -199
- package/prompts/SCAN_PROTOCOL.md +0 -118
- package/prompts/add_user.md +0 -24
- package/prompts/coding_user.md +0 -31
- package/prompts/scan_user.md +0 -17
- package/src/hooks.js +0 -166
- package/src/prompts.js +0 -295
- package/src/runner.js +0 -396
- package/src/scanner.js +0 -62
- package/src/session.js +0 -354
- package/src/setup.js +0 -579
- package/src/tasks.js +0 -172
- package/src/validator.js +0 -181
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { log, COLOR, parseEnvFile } = require('../common/config');
|
|
5
|
+
const { assets } = require('../common/assets');
|
|
6
|
+
const {
|
|
7
|
+
createInterface,
|
|
8
|
+
ask,
|
|
9
|
+
askChoice,
|
|
10
|
+
writeConfig,
|
|
11
|
+
ensureGitignore,
|
|
12
|
+
showCurrentConfig,
|
|
13
|
+
selectProvider,
|
|
14
|
+
updateApiKeyOnly,
|
|
15
|
+
configureMCP,
|
|
16
|
+
appendMcpConfig,
|
|
17
|
+
updateMCPOnly,
|
|
18
|
+
updateSafetyLimits,
|
|
19
|
+
updateSimplifyConfig,
|
|
20
|
+
} = require('./setup-modules');
|
|
21
|
+
|
|
22
|
+
const PRESERVED_KEYS = [
|
|
23
|
+
'SESSION_STALL_TIMEOUT', 'SESSION_COMPLETION_TIMEOUT',
|
|
24
|
+
'SESSION_MAX_TURNS', 'SIMPLIFY_INTERVAL', 'SIMPLIFY_COMMITS',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function preserveSafetyConfig(lines, existing) {
|
|
28
|
+
const preserved = PRESERVED_KEYS
|
|
29
|
+
.filter(k => existing[k])
|
|
30
|
+
.map(k => `${k}=${existing[k]}`);
|
|
31
|
+
if (preserved.length > 0) {
|
|
32
|
+
lines.push('', '# 保留的安全限制和审查配置');
|
|
33
|
+
lines.push(...preserved);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function setup() {
|
|
38
|
+
assets.ensureDirs();
|
|
39
|
+
const rl = createInterface();
|
|
40
|
+
|
|
41
|
+
const envPath = assets.path('env');
|
|
42
|
+
let existing = {};
|
|
43
|
+
if (fs.existsSync(envPath)) {
|
|
44
|
+
existing = parseEnvFile(envPath);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log('============================================');
|
|
49
|
+
console.log(' Claude Coder 配置');
|
|
50
|
+
console.log('============================================');
|
|
51
|
+
|
|
52
|
+
if (!fs.existsSync(envPath) || !existing.MODEL_PROVIDER) {
|
|
53
|
+
console.log('');
|
|
54
|
+
console.log(' 检测到未配置,开始初始化...');
|
|
55
|
+
console.log('');
|
|
56
|
+
|
|
57
|
+
const configResult = await selectProvider(rl, existing);
|
|
58
|
+
const mcpConfig = await configureMCP(rl);
|
|
59
|
+
|
|
60
|
+
appendMcpConfig(configResult.lines, mcpConfig);
|
|
61
|
+
writeConfig(envPath, configResult.lines);
|
|
62
|
+
ensureGitignore();
|
|
63
|
+
|
|
64
|
+
if (mcpConfig.enabled && mcpConfig.mode) {
|
|
65
|
+
const { updateMcpConfig } = require('./auth');
|
|
66
|
+
const mcpPath = assets.path('mcpConfig');
|
|
67
|
+
updateMcpConfig(mcpPath, mcpConfig.mode);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log('');
|
|
71
|
+
log('info', '配置自动代码审查(可选)');
|
|
72
|
+
await updateSimplifyConfig(rl, {});
|
|
73
|
+
|
|
74
|
+
console.log('');
|
|
75
|
+
log('ok', `配置完成!提供商: ${configResult.summary}`);
|
|
76
|
+
console.log('');
|
|
77
|
+
console.log(` 配置文件: ${envPath}`);
|
|
78
|
+
console.log(' 使用方式: claude-coder run "你的需求"');
|
|
79
|
+
console.log(' 重新配置: claude-coder setup');
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log(` ${COLOR.blue}当前默认值:${COLOR.reset}`);
|
|
82
|
+
console.log(` 停顿超时: 1200 秒 (20 分钟)`);
|
|
83
|
+
console.log(` 完成检测超时: 300 秒 (5 分钟)`);
|
|
84
|
+
console.log(` 自动审查: 每 5 个 session,审查 5 个 commit`);
|
|
85
|
+
console.log('');
|
|
86
|
+
console.log(` ${COLOR.yellow}调整方式: claude-coder setup → 配置安全限制 / 配置自动审查${COLOR.reset}`);
|
|
87
|
+
console.log('');
|
|
88
|
+
|
|
89
|
+
rl.close();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
while (true) {
|
|
94
|
+
existing = parseEnvFile(envPath);
|
|
95
|
+
showCurrentConfig(existing);
|
|
96
|
+
|
|
97
|
+
console.log('请选择要执行的操作:');
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log(' 1) 切换模型提供商');
|
|
100
|
+
console.log(' 2) 更新 API Key');
|
|
101
|
+
console.log(' 3) 配置 MCP');
|
|
102
|
+
console.log(' 4) 配置安全限制');
|
|
103
|
+
console.log(' 5) 配置自动审查');
|
|
104
|
+
console.log(' 6) 完全重新配置');
|
|
105
|
+
console.log(' 7) 退出');
|
|
106
|
+
console.log('');
|
|
107
|
+
|
|
108
|
+
const action = await askChoice(rl, '选择 [1-7]: ', 1, 7);
|
|
109
|
+
console.log('');
|
|
110
|
+
|
|
111
|
+
if (action === 7) {
|
|
112
|
+
log('info', '退出配置');
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
switch (action) {
|
|
117
|
+
case 1: {
|
|
118
|
+
log('info', '放心切换,旧配置会自动备份,安全限制和审查配置会保留');
|
|
119
|
+
const configResult = await selectProvider(rl, existing);
|
|
120
|
+
preserveSafetyConfig(configResult.lines, existing);
|
|
121
|
+
appendMcpConfig(configResult.lines, {
|
|
122
|
+
enabled: existing.MCP_PLAYWRIGHT === 'true',
|
|
123
|
+
mode: existing.MCP_PLAYWRIGHT_MODE || null,
|
|
124
|
+
});
|
|
125
|
+
writeConfig(envPath, configResult.lines);
|
|
126
|
+
log('ok', `已切换到: ${configResult.summary}`);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 2: {
|
|
130
|
+
await updateApiKeyOnly(rl, existing);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 3: {
|
|
134
|
+
await updateMCPOnly(rl);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case 4: {
|
|
138
|
+
await updateSafetyLimits(rl, existing);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case 5: {
|
|
142
|
+
await updateSimplifyConfig(rl, existing);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case 6: {
|
|
146
|
+
log('info', '放心重新配置,旧配置会自动备份,安全限制和审查配置会保留');
|
|
147
|
+
const configResult = await selectProvider(rl, existing);
|
|
148
|
+
preserveSafetyConfig(configResult.lines, existing);
|
|
149
|
+
const mcpConfig = await configureMCP(rl);
|
|
150
|
+
appendMcpConfig(configResult.lines, mcpConfig);
|
|
151
|
+
writeConfig(envPath, configResult.lines);
|
|
152
|
+
|
|
153
|
+
if (mcpConfig.enabled && mcpConfig.mode) {
|
|
154
|
+
const { updateMcpConfig } = require('./auth');
|
|
155
|
+
const mcpPath = assets.path('mcpConfig');
|
|
156
|
+
updateMcpConfig(mcpPath, mcpConfig.mode);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
log('ok', '配置已更新');
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log('');
|
|
165
|
+
const cont = await ask(rl, '继续配置其他项?(y/N) ');
|
|
166
|
+
if (!/^[Yy]/.test(cont.trim())) break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
rl.close();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = { setup };
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const BUNDLED_DIR = path.join(__dirname, '..', '..', 'templates');
|
|
7
|
+
|
|
8
|
+
// kind: 'template' — 双目录解析(用户 assets → 内置 bundled),有缓存
|
|
9
|
+
// kind: 'data' — .claude-coder/ 目录,无缓存
|
|
10
|
+
// kind: 'runtime' — .claude-coder/.runtime/ 目录,无缓存
|
|
11
|
+
// kind: 'root' — 项目根目录,无缓存
|
|
12
|
+
const REGISTRY = new Map([
|
|
13
|
+
// System Prompt Templates (per session type)
|
|
14
|
+
['coreProtocol', { file: 'coreProtocol.md', kind: 'template' }],
|
|
15
|
+
['codingSystem', { file: 'codingSystem.md', kind: 'template' }],
|
|
16
|
+
['planSystem', { file: 'planSystem.md', kind: 'template' }],
|
|
17
|
+
['scanSystem', { file: 'scanSystem.md', kind: 'template' }],
|
|
18
|
+
|
|
19
|
+
// User Prompt Templates
|
|
20
|
+
['codingUser', { file: 'codingUser.md', kind: 'template' }],
|
|
21
|
+
['scanUser', { file: 'scanUser.md', kind: 'template' }],
|
|
22
|
+
['planUser', { file: 'planUser.md', kind: 'template' }],
|
|
23
|
+
|
|
24
|
+
// Other Templates
|
|
25
|
+
['testRule', { file: 'test_rule.md', kind: 'template' }],
|
|
26
|
+
['guidance', { file: 'guidance.json', kind: 'template' }],
|
|
27
|
+
['playwright', { file: 'playwright.md', kind: 'template' }],
|
|
28
|
+
['bashProcess', { file: 'bash-process.md', kind: 'template' }],
|
|
29
|
+
['requirements', { file: 'requirements.example.md', kind: 'template' }],
|
|
30
|
+
|
|
31
|
+
// Data files (.claude-coder/)
|
|
32
|
+
['env', { file: '.env', kind: 'data' }],
|
|
33
|
+
['tasks', { file: 'tasks.json', kind: 'data' }],
|
|
34
|
+
['progress', { file: 'progress.json', kind: 'data' }],
|
|
35
|
+
['sessionResult', { file: 'session_result.json', kind: 'data' }],
|
|
36
|
+
['profile', { file: 'project_profile.json', kind: 'data' }],
|
|
37
|
+
['testEnv', { file: 'test.env', kind: 'data' }],
|
|
38
|
+
['playwrightAuth', { file: 'playwright-auth.json', kind: 'data' }],
|
|
39
|
+
|
|
40
|
+
// Runtime files (.claude-coder/.runtime/)
|
|
41
|
+
['harnessState', { file: 'harness_state.json', kind: 'runtime' }],
|
|
42
|
+
['browserProfile', { file: 'browser-profile', kind: 'runtime' }],
|
|
43
|
+
|
|
44
|
+
// Root files (project root)
|
|
45
|
+
['mcpConfig', { file: '.mcp.json', kind: 'root' }],
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const DIRS = new Map([
|
|
49
|
+
['loop', ''],
|
|
50
|
+
['assets', 'assets'],
|
|
51
|
+
['runtime', '.runtime'],
|
|
52
|
+
['logs', '.runtime/logs'],
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
function renderTemplate(template, vars = {}) {
|
|
56
|
+
return template
|
|
57
|
+
.replace(/\{\{(\w+)\}\}/g, (_, key) =>
|
|
58
|
+
Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : ''
|
|
59
|
+
)
|
|
60
|
+
.replace(/^\s+$/gm, '')
|
|
61
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
62
|
+
.trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class AssetManager {
|
|
66
|
+
constructor() {
|
|
67
|
+
this.projectRoot = null;
|
|
68
|
+
this.loopDir = null;
|
|
69
|
+
this.assetsDir = null;
|
|
70
|
+
this.bundledDir = BUNDLED_DIR;
|
|
71
|
+
this.registry = new Map(REGISTRY);
|
|
72
|
+
this.cache = new Map();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
init(projectRoot) {
|
|
76
|
+
this.projectRoot = projectRoot || process.cwd();
|
|
77
|
+
this.loopDir = path.join(this.projectRoot, '.claude-coder');
|
|
78
|
+
this.assetsDir = path.join(this.loopDir, 'assets');
|
|
79
|
+
this.cache.clear();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_ensureInit() {
|
|
83
|
+
if (!this.loopDir) this.init();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
path(name) {
|
|
87
|
+
this._ensureInit();
|
|
88
|
+
const entry = this.registry.get(name);
|
|
89
|
+
if (!entry) return null;
|
|
90
|
+
switch (entry.kind) {
|
|
91
|
+
case 'template': return this._resolveTemplate(entry.file);
|
|
92
|
+
case 'data': return path.join(this.loopDir, entry.file);
|
|
93
|
+
case 'runtime': return path.join(this.loopDir, '.runtime', entry.file);
|
|
94
|
+
case 'root': return path.join(this.projectRoot, entry.file);
|
|
95
|
+
default: return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_resolveTemplate(filename) {
|
|
100
|
+
if (this.assetsDir) {
|
|
101
|
+
const userPath = path.join(this.assetsDir, filename);
|
|
102
|
+
if (fs.existsSync(userPath)) return userPath;
|
|
103
|
+
}
|
|
104
|
+
const bundled = path.join(this.bundledDir, filename);
|
|
105
|
+
if (fs.existsSync(bundled)) return bundled;
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
dir(name) {
|
|
110
|
+
this._ensureInit();
|
|
111
|
+
const rel = DIRS.get(name);
|
|
112
|
+
if (rel === undefined) return null;
|
|
113
|
+
return rel === '' ? this.loopDir : path.join(this.loopDir, rel);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
exists(name) {
|
|
117
|
+
const p = this.path(name);
|
|
118
|
+
return p ? fs.existsSync(p) : false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
read(name) {
|
|
122
|
+
this._ensureInit();
|
|
123
|
+
const entry = this.registry.get(name);
|
|
124
|
+
if (!entry) return null;
|
|
125
|
+
|
|
126
|
+
if (entry.kind === 'template') {
|
|
127
|
+
const key = entry.file;
|
|
128
|
+
if (this.cache.has(key)) return this.cache.get(key);
|
|
129
|
+
const filePath = this._resolveTemplate(entry.file);
|
|
130
|
+
if (!filePath) return '';
|
|
131
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
132
|
+
this.cache.set(key, content);
|
|
133
|
+
return content;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const filePath = this.path(name);
|
|
137
|
+
if (!filePath || !fs.existsSync(filePath)) return null;
|
|
138
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
readJson(name, fallback = null) {
|
|
142
|
+
const content = this.read(name);
|
|
143
|
+
if (content === null || content === '') return fallback;
|
|
144
|
+
try {
|
|
145
|
+
return JSON.parse(content);
|
|
146
|
+
} catch {
|
|
147
|
+
return fallback;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
write(name, content) {
|
|
152
|
+
this._ensureInit();
|
|
153
|
+
const entry = this.registry.get(name);
|
|
154
|
+
if (!entry || entry.kind === 'template') return;
|
|
155
|
+
const filePath = this.path(name);
|
|
156
|
+
if (!filePath) return;
|
|
157
|
+
const dir = path.dirname(filePath);
|
|
158
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
159
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
writeJson(name, data) {
|
|
163
|
+
this.write(name, JSON.stringify(data, null, 2) + '\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
render(name, vars = {}) {
|
|
167
|
+
const raw = this.read(name);
|
|
168
|
+
if (!raw) return '';
|
|
169
|
+
return renderTemplate(raw, vars);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
ensureDirs() {
|
|
173
|
+
this._ensureInit();
|
|
174
|
+
for (const [, rel] of DIRS) {
|
|
175
|
+
const dir = rel === '' ? this.loopDir : path.join(this.loopDir, rel);
|
|
176
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
deployAll() {
|
|
181
|
+
this._ensureInit();
|
|
182
|
+
if (!fs.existsSync(this.assetsDir)) {
|
|
183
|
+
fs.mkdirSync(this.assetsDir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
const files = fs.readdirSync(this.bundledDir);
|
|
186
|
+
const deployed = [];
|
|
187
|
+
for (const file of files) {
|
|
188
|
+
const dest = path.join(this.assetsDir, file);
|
|
189
|
+
if (fs.existsSync(dest)) continue;
|
|
190
|
+
const src = path.join(this.bundledDir, file);
|
|
191
|
+
try {
|
|
192
|
+
fs.copyFileSync(src, dest);
|
|
193
|
+
deployed.push(file);
|
|
194
|
+
} catch { /* skip */ }
|
|
195
|
+
}
|
|
196
|
+
return deployed;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
clearCache() {
|
|
200
|
+
this.cache.clear();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const assets = new AssetManager();
|
|
205
|
+
|
|
206
|
+
module.exports = { AssetManager, assets, renderTemplate };
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
4
|
|
|
6
5
|
const COLOR = {
|
|
7
6
|
red: '\x1b[0;31m',
|
|
8
7
|
green: '\x1b[0;32m',
|
|
9
8
|
yellow: '\x1b[1;33m',
|
|
10
9
|
blue: '\x1b[0;34m',
|
|
10
|
+
magenta: '\x1b[0;35m',
|
|
11
|
+
cyan: '\x1b[0;36m',
|
|
12
|
+
bold: '\x1b[1m',
|
|
13
|
+
dim: '\x1b[2m',
|
|
11
14
|
reset: '\x1b[0m',
|
|
12
15
|
};
|
|
13
16
|
|
|
@@ -21,59 +24,6 @@ function log(level, msg) {
|
|
|
21
24
|
console.error(`${tags[level] || ''} ${msg}`);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
function getProjectRoot() {
|
|
25
|
-
return process.cwd();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getLoopDir() {
|
|
29
|
-
return path.join(getProjectRoot(), '.claude-coder');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function ensureLoopDir() {
|
|
33
|
-
const dir = getLoopDir();
|
|
34
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
35
|
-
const runtime = path.join(dir, '.runtime');
|
|
36
|
-
if (!fs.existsSync(runtime)) fs.mkdirSync(runtime, { recursive: true });
|
|
37
|
-
const logs = path.join(runtime, 'logs');
|
|
38
|
-
if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true });
|
|
39
|
-
return dir;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function getTemplatePath(name) {
|
|
43
|
-
return path.join(__dirname, '..', 'templates', name);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getPromptPath(name) {
|
|
47
|
-
return path.join(__dirname, '..', 'prompts', name);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function paths() {
|
|
51
|
-
const loopDir = getLoopDir();
|
|
52
|
-
const runtime = path.join(loopDir, '.runtime');
|
|
53
|
-
return {
|
|
54
|
-
loopDir,
|
|
55
|
-
envFile: path.join(loopDir, '.env'),
|
|
56
|
-
tasksFile: path.join(loopDir, 'tasks.json'),
|
|
57
|
-
progressFile: path.join(loopDir, 'progress.json'),
|
|
58
|
-
sessionResult: path.join(loopDir, 'session_result.json'),
|
|
59
|
-
profile: path.join(loopDir, 'project_profile.json'),
|
|
60
|
-
testsFile: path.join(loopDir, 'tests.json'),
|
|
61
|
-
testEnvFile: path.join(loopDir, 'test.env'),
|
|
62
|
-
playwrightAuth: path.join(loopDir, 'playwright-auth.json'),
|
|
63
|
-
browserProfile: path.join(runtime, 'browser-profile'),
|
|
64
|
-
mcpConfig: path.join(getProjectRoot(), '.mcp.json'),
|
|
65
|
-
claudeMd: getPromptPath('CLAUDE.md'),
|
|
66
|
-
scanProtocol: getPromptPath('SCAN_PROTOCOL.md'),
|
|
67
|
-
addGuide: getPromptPath('ADD_GUIDE.md'),
|
|
68
|
-
codingUser: getPromptPath('coding_user.md'),
|
|
69
|
-
scanUser: getPromptPath('scan_user.md'),
|
|
70
|
-
addUser: getPromptPath('add_user.md'),
|
|
71
|
-
testRuleTemplate: getTemplatePath('test_rule.md'),
|
|
72
|
-
runtime,
|
|
73
|
-
logsDir: path.join(runtime, 'logs'),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
27
|
// --------------- .env parsing ---------------
|
|
78
28
|
|
|
79
29
|
function parseEnvFile(filepath) {
|
|
@@ -94,8 +44,9 @@ function parseEnvFile(filepath) {
|
|
|
94
44
|
// --------------- Model mapping ---------------
|
|
95
45
|
|
|
96
46
|
function loadConfig() {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
47
|
+
const { assets } = require('./assets');
|
|
48
|
+
const envPath = assets.path('env');
|
|
49
|
+
const env = envPath ? parseEnvFile(envPath) : {};
|
|
99
50
|
const config = {
|
|
100
51
|
provider: env.MODEL_PROVIDER || 'claude',
|
|
101
52
|
baseUrl: env.ANTHROPIC_BASE_URL || '',
|
|
@@ -103,35 +54,29 @@ function loadConfig() {
|
|
|
103
54
|
authToken: env.ANTHROPIC_AUTH_TOKEN || '',
|
|
104
55
|
model: env.ANTHROPIC_MODEL || '',
|
|
105
56
|
timeoutMs: parseInt(env.API_TIMEOUT_MS, 10) || 3000000,
|
|
106
|
-
mcpToolTimeout: parseInt(env.MCP_TOOL_TIMEOUT, 10) || 30000,
|
|
107
57
|
mcpPlaywright: env.MCP_PLAYWRIGHT === 'true',
|
|
108
58
|
playwrightMode: env.MCP_PLAYWRIGHT_MODE || 'persistent',
|
|
109
59
|
disableNonessential: env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC || '',
|
|
110
60
|
effortLevel: env.CLAUDE_CODE_EFFORT_LEVEL || '',
|
|
111
|
-
smallFastModel: env.ANTHROPIC_SMALL_FAST_MODEL || '',
|
|
112
61
|
defaultOpus: env.ANTHROPIC_DEFAULT_OPUS_MODEL || '',
|
|
113
62
|
defaultSonnet: env.ANTHROPIC_DEFAULT_SONNET_MODEL || '',
|
|
114
63
|
defaultHaiku: env.ANTHROPIC_DEFAULT_HAIKU_MODEL || '',
|
|
115
64
|
thinkingBudget: env.ANTHROPIC_THINKING_BUDGET || '',
|
|
116
|
-
stallTimeout: parseInt(env.SESSION_STALL_TIMEOUT, 10) ||
|
|
65
|
+
stallTimeout: parseInt(env.SESSION_STALL_TIMEOUT, 10) || 600,
|
|
117
66
|
completionTimeout: parseInt(env.SESSION_COMPLETION_TIMEOUT, 10) || 300,
|
|
118
67
|
maxTurns: parseInt(env.SESSION_MAX_TURNS, 10) || 0,
|
|
119
68
|
editThreshold: parseInt(env.EDIT_THRESHOLD, 10) || 15,
|
|
69
|
+
simplifyInterval: env.SIMPLIFY_INTERVAL !== undefined ? parseInt(env.SIMPLIFY_INTERVAL, 10) : 5,
|
|
70
|
+
simplifyCommits: env.SIMPLIFY_COMMITS !== undefined ? parseInt(env.SIMPLIFY_COMMITS, 10) : 5,
|
|
120
71
|
raw: env,
|
|
121
72
|
};
|
|
122
73
|
|
|
123
|
-
//
|
|
124
|
-
if (config.baseUrl &&
|
|
125
|
-
if (!config.model) config.model = 'glm-4.7';
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// DeepSeek chat → haiku shim (prevent reasoner billing)
|
|
129
|
-
if (config.baseUrl.includes('deepseek') && config.model === 'deepseek-chat') {
|
|
74
|
+
// 以下是兼容deepseek最实惠的而改写的配置,不一定正确。只是多次调用后得出的结果。
|
|
75
|
+
if (config.baseUrl && config.baseUrl.includes('deepseek') && config.model === 'deepseek-chat') {
|
|
130
76
|
config.model = 'claude-3-haiku-20240307';
|
|
131
77
|
config.defaultOpus = 'claude-3-haiku-20240307';
|
|
132
78
|
config.defaultSonnet = 'claude-3-haiku-20240307';
|
|
133
79
|
config.defaultHaiku = 'claude-3-haiku-20240307';
|
|
134
|
-
config.smallFastModel = 'claude-3-haiku-20240307';
|
|
135
80
|
config.thinkingBudget = '0';
|
|
136
81
|
}
|
|
137
82
|
|
|
@@ -145,8 +90,6 @@ function buildEnvVars(config) {
|
|
|
145
90
|
if (config.authToken) env.ANTHROPIC_AUTH_TOKEN = config.authToken;
|
|
146
91
|
if (config.model) env.ANTHROPIC_MODEL = config.model;
|
|
147
92
|
if (config.timeoutMs) env.API_TIMEOUT_MS = String(config.timeoutMs);
|
|
148
|
-
if (config.mcpToolTimeout) env.MCP_TOOL_TIMEOUT = String(config.mcpToolTimeout);
|
|
149
|
-
if (config.smallFastModel) env.ANTHROPIC_SMALL_FAST_MODEL = config.smallFastModel;
|
|
150
93
|
if (config.disableNonessential) env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = config.disableNonessential;
|
|
151
94
|
if (config.effortLevel) env.CLAUDE_CODE_EFFORT_LEVEL = config.effortLevel;
|
|
152
95
|
if (config.defaultOpus) env.ANTHROPIC_DEFAULT_OPUS_MODEL = config.defaultOpus;
|
|
@@ -156,23 +99,11 @@ function buildEnvVars(config) {
|
|
|
156
99
|
return env;
|
|
157
100
|
}
|
|
158
101
|
|
|
159
|
-
// --------------- Allowed tools ---------------
|
|
160
|
-
|
|
161
|
-
function getAllowedTools(config) {
|
|
162
|
-
const tools = [
|
|
163
|
-
'Read', 'Edit', 'MultiEdit', 'Write',
|
|
164
|
-
'Bash', 'Glob', 'Grep', 'LS',
|
|
165
|
-
'Task',
|
|
166
|
-
'WebSearch', 'WebFetch',
|
|
167
|
-
];
|
|
168
|
-
if (config.mcpPlaywright) tools.push('mcp__playwright__*');
|
|
169
|
-
return tools;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
102
|
function updateEnvVar(key, value) {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
103
|
+
const { assets } = require('./assets');
|
|
104
|
+
const envPath = assets.path('env');
|
|
105
|
+
if (!envPath || !fs.existsSync(envPath)) return false;
|
|
106
|
+
let content = fs.readFileSync(envPath, 'utf8');
|
|
176
107
|
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
177
108
|
if (regex.test(content)) {
|
|
178
109
|
content = content.replace(regex, `${key}=${value}`);
|
|
@@ -180,22 +111,15 @@ function updateEnvVar(key, value) {
|
|
|
180
111
|
const suffix = content.endsWith('\n') ? '' : '\n';
|
|
181
112
|
content += `${suffix}${key}=${value}\n`;
|
|
182
113
|
}
|
|
183
|
-
fs.writeFileSync(
|
|
114
|
+
fs.writeFileSync(envPath, content, 'utf8');
|
|
184
115
|
return true;
|
|
185
116
|
}
|
|
186
117
|
|
|
187
118
|
module.exports = {
|
|
188
119
|
COLOR,
|
|
189
120
|
log,
|
|
190
|
-
getProjectRoot,
|
|
191
|
-
getLoopDir,
|
|
192
|
-
ensureLoopDir,
|
|
193
|
-
getTemplatePath,
|
|
194
|
-
getPromptPath,
|
|
195
|
-
paths,
|
|
196
121
|
parseEnvFile,
|
|
197
122
|
loadConfig,
|
|
198
123
|
buildEnvVars,
|
|
199
|
-
getAllowedTools,
|
|
200
124
|
updateEnvVar,
|
|
201
125
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
// 常量集中管理
|
|
5
|
+
// ─────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 任务状态
|
|
9
|
+
*/
|
|
10
|
+
const TASK_STATUSES = Object.freeze(['pending', 'in_progress', 'testing', 'done', 'failed']);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 状态迁移规则
|
|
14
|
+
*/
|
|
15
|
+
const STATUS_TRANSITIONS = Object.freeze({
|
|
16
|
+
pending: ['in_progress'],
|
|
17
|
+
in_progress: ['testing'],
|
|
18
|
+
testing: ['done', 'failed'],
|
|
19
|
+
failed: ['in_progress'],
|
|
20
|
+
done: [],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 文件名常量
|
|
25
|
+
*/
|
|
26
|
+
const FILES = Object.freeze({
|
|
27
|
+
SESSION_RESULT: 'session_result.json',
|
|
28
|
+
TASKS: 'tasks.json',
|
|
29
|
+
PROFILE: 'project_profile.json',
|
|
30
|
+
PROGRESS: 'progress.json',
|
|
31
|
+
TEST_ENV: 'test.env',
|
|
32
|
+
PLAYWRIGHT_AUTH: 'playwright-auth.json',
|
|
33
|
+
ENV: '.env',
|
|
34
|
+
MCP_CONFIG: '.mcp.json',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 重试配置
|
|
39
|
+
*/
|
|
40
|
+
const RETRY = Object.freeze({
|
|
41
|
+
MAX_ATTEMPTS: 3,
|
|
42
|
+
SCAN_ATTEMPTS: 3,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 编辑防护阈值
|
|
47
|
+
*/
|
|
48
|
+
const EDIT_THRESHOLD = 15;
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
TASK_STATUSES,
|
|
52
|
+
STATUS_TRANSITIONS,
|
|
53
|
+
FILES,
|
|
54
|
+
RETRY,
|
|
55
|
+
EDIT_THRESHOLD,
|
|
56
|
+
};
|