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.
Files changed (58) hide show
  1. package/README.md +80 -46
  2. package/bin/cli.js +41 -43
  3. package/package.json +7 -2
  4. package/src/{auth.js → commands/auth.js} +41 -46
  5. package/src/commands/setup-modules/helpers.js +99 -0
  6. package/src/commands/setup-modules/index.js +26 -0
  7. package/src/commands/setup-modules/mcp.js +95 -0
  8. package/src/commands/setup-modules/provider.js +261 -0
  9. package/src/commands/setup-modules/safety.js +62 -0
  10. package/src/commands/setup-modules/simplify.js +53 -0
  11. package/src/commands/setup.js +172 -0
  12. package/src/common/assets.js +206 -0
  13. package/src/{config.js → common/config.js} +17 -93
  14. package/src/common/constants.js +56 -0
  15. package/src/{indicator.js → common/indicator.js} +45 -56
  16. package/src/common/interaction.js +170 -0
  17. package/src/common/logging.js +78 -0
  18. package/src/common/sdk.js +51 -0
  19. package/src/common/tasks.js +88 -0
  20. package/src/common/utils.js +162 -0
  21. package/src/core/coding.js +55 -0
  22. package/src/core/context.js +117 -0
  23. package/src/core/harness.js +484 -0
  24. package/src/core/hooks.js +533 -0
  25. package/src/{init.js → core/init.js} +31 -12
  26. package/src/core/plan.js +325 -0
  27. package/src/core/prompts.js +226 -0
  28. package/src/core/query.js +50 -0
  29. package/src/core/repair.js +46 -0
  30. package/src/core/runner.js +195 -0
  31. package/src/core/scan.js +89 -0
  32. package/src/core/session.js +57 -0
  33. package/src/core/simplify.js +52 -0
  34. package/templates/bash-process.md +12 -0
  35. package/templates/codingSystem.md +65 -0
  36. package/templates/codingUser.md +17 -0
  37. package/templates/coreProtocol.md +29 -0
  38. package/templates/guidance.json +35 -0
  39. package/templates/planSystem.md +78 -0
  40. package/templates/planUser.md +9 -0
  41. package/templates/playwright.md +17 -0
  42. package/templates/requirements.example.md +4 -3
  43. package/templates/scanSystem.md +120 -0
  44. package/templates/scanUser.md +10 -0
  45. package/prompts/ADD_GUIDE.md +0 -98
  46. package/prompts/CLAUDE.md +0 -199
  47. package/prompts/SCAN_PROTOCOL.md +0 -118
  48. package/prompts/add_user.md +0 -24
  49. package/prompts/coding_user.md +0 -31
  50. package/prompts/scan_user.md +0 -17
  51. package/src/hooks.js +0 -166
  52. package/src/prompts.js +0 -295
  53. package/src/runner.js +0 -396
  54. package/src/scanner.js +0 -62
  55. package/src/session.js +0 -354
  56. package/src/setup.js +0 -579
  57. package/src/tasks.js +0 -172
  58. 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 p = paths();
98
- const env = parseEnvFile(p.envFile);
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) || 1200,
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
- // GLM: default model if not set
124
- if (config.baseUrl && (config.baseUrl.includes('bigmodel.cn') || config.baseUrl.includes('z.ai'))) {
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 p = paths();
174
- if (!fs.existsSync(p.envFile)) return false;
175
- let content = fs.readFileSync(p.envFile, 'utf8');
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(p.envFile, content, 'utf8');
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
+ };