claude-coder 1.7.0 → 1.8.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.
Files changed (52) hide show
  1. package/README.md +177 -125
  2. package/bin/cli.js +159 -161
  3. package/package.json +52 -47
  4. package/src/commands/auth.js +294 -0
  5. package/src/commands/setup-modules/helpers.js +105 -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 +192 -0
  13. package/src/{config.js → common/config.js} +138 -201
  14. package/src/common/constants.js +57 -0
  15. package/src/{indicator.js → common/indicator.js} +222 -217
  16. package/src/common/interaction.js +170 -0
  17. package/src/common/logging.js +77 -0
  18. package/src/common/sdk.js +51 -0
  19. package/src/{tasks.js → common/tasks.js} +157 -172
  20. package/src/common/utils.js +147 -0
  21. package/src/core/base.js +54 -0
  22. package/src/core/coding.js +55 -0
  23. package/src/core/context.js +132 -0
  24. package/src/core/hooks.js +529 -0
  25. package/src/{init.js → core/init.js} +163 -144
  26. package/src/core/plan.js +318 -0
  27. package/src/core/prompts.js +253 -0
  28. package/src/core/query.js +48 -0
  29. package/src/core/repair.js +58 -0
  30. package/src/{runner.js → core/runner.js} +352 -420
  31. package/src/core/scan.js +89 -0
  32. package/src/core/simplify.js +59 -0
  33. package/src/core/validator.js +138 -0
  34. package/{prompts/ADD_GUIDE.md → templates/addGuide.md} +98 -98
  35. package/templates/addUser.md +26 -0
  36. package/{prompts/CLAUDE.md → templates/agentProtocol.md} +195 -199
  37. package/templates/bash-process.md +5 -0
  38. package/{prompts/coding_user.md → templates/codingUser.md} +31 -23
  39. package/templates/guidance.json +35 -0
  40. package/templates/playwright.md +17 -0
  41. package/templates/requirements.example.md +56 -56
  42. package/{prompts/SCAN_PROTOCOL.md → templates/scanProtocol.md} +118 -118
  43. package/{prompts/scan_user.md → templates/scanUser.md} +17 -17
  44. package/templates/test_rule.md +194 -194
  45. package/prompts/add_user.md +0 -24
  46. package/src/auth.js +0 -245
  47. package/src/hooks.js +0 -160
  48. package/src/prompts.js +0 -295
  49. package/src/scanner.js +0 -62
  50. package/src/session.js +0 -352
  51. package/src/setup.js +0 -579
  52. package/src/validator.js +0 -181
@@ -0,0 +1,192 @@
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
+ // Templates
14
+ ['agentProtocol', { file: 'agentProtocol.md', kind: 'template' }],
15
+ ['scanProtocol', { file: 'scanProtocol.md', kind: 'template' }],
16
+ ['addGuide', { file: 'addGuide.md', kind: 'template' }],
17
+ ['codingUser', { file: 'codingUser.md', kind: 'template' }],
18
+ ['scanUser', { file: 'scanUser.md', kind: 'template' }],
19
+ ['addUser', { file: 'addUser.md', kind: 'template' }],
20
+ ['testRule', { file: 'test_rule.md', kind: 'template' }],
21
+ ['guidance', { file: 'guidance.json', kind: 'template' }],
22
+ ['playwright', { file: 'playwright.md', kind: 'template' }],
23
+ ['bashProcess', { file: 'bash-process.md', kind: 'template' }],
24
+ ['requirements', { file: 'requirements.example.md', kind: 'template' }],
25
+
26
+ // Data files (.claude-coder/)
27
+ ['env', { file: '.env', kind: 'data' }],
28
+ ['tasks', { file: 'tasks.json', kind: 'data' }],
29
+ ['progress', { file: 'progress.json', kind: 'data' }],
30
+ ['sessionResult', { file: 'session_result.json', kind: 'data' }],
31
+ ['profile', { file: 'project_profile.json', kind: 'data' }],
32
+ ['tests', { file: 'tests.json', kind: 'data' }],
33
+ ['testEnv', { file: 'test.env', kind: 'data' }],
34
+ ['playwrightAuth', { file: 'playwright-auth.json', kind: 'data' }],
35
+
36
+ // Runtime files (.claude-coder/.runtime/)
37
+ ['browserProfile', { file: 'browser-profile', kind: 'runtime' }],
38
+
39
+ // Root files (project root)
40
+ ['mcpConfig', { file: '.mcp.json', kind: 'root' }],
41
+ ]);
42
+
43
+ const DIRS = new Map([
44
+ ['loop', ''],
45
+ ['assets', 'assets'],
46
+ ['runtime', '.runtime'],
47
+ ['logs', '.runtime/logs'],
48
+ ]);
49
+
50
+ function renderTemplate(template, vars = {}) {
51
+ return template
52
+ .replace(/\{\{(\w+)\}\}/g, (_, key) =>
53
+ Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : ''
54
+ )
55
+ .replace(/^\s+$/gm, '')
56
+ .replace(/\n{3,}/g, '\n\n')
57
+ .trim();
58
+ }
59
+
60
+ class AssetManager {
61
+ constructor() {
62
+ this.projectRoot = null;
63
+ this.loopDir = null;
64
+ this.assetsDir = null;
65
+ this.bundledDir = BUNDLED_DIR;
66
+ this.registry = new Map(REGISTRY);
67
+ }
68
+
69
+ init(projectRoot) {
70
+ this.projectRoot = projectRoot || process.cwd();
71
+ this.loopDir = path.join(this.projectRoot, '.claude-coder');
72
+ this.assetsDir = path.join(this.loopDir, 'assets');
73
+ }
74
+
75
+ _ensureInit() {
76
+ if (!this.loopDir) this.init();
77
+ }
78
+
79
+ path(name) {
80
+ this._ensureInit();
81
+ const entry = this.registry.get(name);
82
+ if (!entry) return null;
83
+ switch (entry.kind) {
84
+ case 'template': return this._resolveTemplate(entry.file);
85
+ case 'data': return path.join(this.loopDir, entry.file);
86
+ case 'runtime': return path.join(this.loopDir, '.runtime', entry.file);
87
+ case 'root': return path.join(this.projectRoot, entry.file);
88
+ default: return null;
89
+ }
90
+ }
91
+
92
+ _resolveTemplate(filename) {
93
+ if (this.assetsDir) {
94
+ const userPath = path.join(this.assetsDir, filename);
95
+ if (fs.existsSync(userPath)) return userPath;
96
+ }
97
+ const bundled = path.join(this.bundledDir, filename);
98
+ if (fs.existsSync(bundled)) return bundled;
99
+ return null;
100
+ }
101
+
102
+ dir(name) {
103
+ this._ensureInit();
104
+ const rel = DIRS.get(name);
105
+ if (rel === undefined) return null;
106
+ return rel === '' ? this.loopDir : path.join(this.loopDir, rel);
107
+ }
108
+
109
+ exists(name) {
110
+ const p = this.path(name);
111
+ return p ? fs.existsSync(p) : false;
112
+ }
113
+
114
+ read(name) {
115
+ this._ensureInit();
116
+ const entry = this.registry.get(name);
117
+ if (!entry) return null;
118
+
119
+ if (entry.kind === 'template') {
120
+ const filePath = this._resolveTemplate(entry.file);
121
+ if (!filePath) return '';
122
+ return fs.readFileSync(filePath, 'utf8');
123
+ }
124
+
125
+ const filePath = this.path(name);
126
+ if (!filePath || !fs.existsSync(filePath)) return null;
127
+ return fs.readFileSync(filePath, 'utf8');
128
+ }
129
+
130
+ readJson(name, fallback = null) {
131
+ const content = this.read(name);
132
+ if (content === null || content === '') return fallback;
133
+ try {
134
+ return JSON.parse(content);
135
+ } catch {
136
+ return fallback;
137
+ }
138
+ }
139
+
140
+ write(name, content) {
141
+ this._ensureInit();
142
+ const entry = this.registry.get(name);
143
+ if (!entry || entry.kind === 'template') return;
144
+ const filePath = this.path(name);
145
+ if (!filePath) return;
146
+ const dir = path.dirname(filePath);
147
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
148
+ fs.writeFileSync(filePath, content, 'utf8');
149
+ }
150
+
151
+ writeJson(name, data) {
152
+ this.write(name, JSON.stringify(data, null, 2) + '\n');
153
+ }
154
+
155
+ render(name, vars = {}) {
156
+ const raw = this.read(name);
157
+ if (!raw) return '';
158
+ return renderTemplate(raw, vars);
159
+ }
160
+
161
+ ensureDirs() {
162
+ this._ensureInit();
163
+ for (const [, rel] of DIRS) {
164
+ const dir = rel === '' ? this.loopDir : path.join(this.loopDir, rel);
165
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
166
+ }
167
+ }
168
+
169
+ deployAll() {
170
+ this._ensureInit();
171
+ if (!fs.existsSync(this.assetsDir)) {
172
+ fs.mkdirSync(this.assetsDir, { recursive: true });
173
+ }
174
+ const files = fs.readdirSync(this.bundledDir);
175
+ const deployed = [];
176
+ for (const file of files) {
177
+ const dest = path.join(this.assetsDir, file);
178
+ if (fs.existsSync(dest)) continue;
179
+ const src = path.join(this.bundledDir, file);
180
+ try {
181
+ fs.copyFileSync(src, dest);
182
+ deployed.push(file);
183
+ } catch { /* skip */ }
184
+ }
185
+ return deployed;
186
+ }
187
+
188
+ }
189
+
190
+ const assets = new AssetManager();
191
+
192
+ module.exports = { AssetManager, assets, renderTemplate };
@@ -1,201 +1,138 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- const COLOR = {
7
- red: '\x1b[0;31m',
8
- green: '\x1b[0;32m',
9
- yellow: '\x1b[1;33m',
10
- blue: '\x1b[0;34m',
11
- reset: '\x1b[0m',
12
- };
13
-
14
- function log(level, msg) {
15
- const tags = {
16
- info: `${COLOR.blue}[INFO]${COLOR.reset} `,
17
- ok: `${COLOR.green}[OK]${COLOR.reset} `,
18
- warn: `${COLOR.yellow}[WARN]${COLOR.reset} `,
19
- error: `${COLOR.red}[ERROR]${COLOR.reset}`,
20
- };
21
- console.error(`${tags[level] || ''} ${msg}`);
22
- }
23
-
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
- // --------------- .env parsing ---------------
78
-
79
- function parseEnvFile(filepath) {
80
- if (!fs.existsSync(filepath)) return {};
81
- const content = fs.readFileSync(filepath, 'utf8');
82
- const vars = {};
83
- for (const line of content.split('\n')) {
84
- const trimmed = line.trim();
85
- if (!trimmed || trimmed.startsWith('#')) continue;
86
- const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
87
- if (match) {
88
- vars[match[1]] = match[2].trim().replace(/^["']|["']$/g, '');
89
- }
90
- }
91
- return vars;
92
- }
93
-
94
- // --------------- Model mapping ---------------
95
-
96
- function loadConfig() {
97
- const p = paths();
98
- const env = parseEnvFile(p.envFile);
99
- const config = {
100
- provider: env.MODEL_PROVIDER || 'claude',
101
- baseUrl: env.ANTHROPIC_BASE_URL || '',
102
- apiKey: env.ANTHROPIC_API_KEY || '',
103
- authToken: env.ANTHROPIC_AUTH_TOKEN || '',
104
- model: env.ANTHROPIC_MODEL || '',
105
- timeoutMs: parseInt(env.API_TIMEOUT_MS, 10) || 3000000,
106
- mcpToolTimeout: parseInt(env.MCP_TOOL_TIMEOUT, 10) || 30000,
107
- mcpPlaywright: env.MCP_PLAYWRIGHT === 'true',
108
- playwrightMode: env.MCP_PLAYWRIGHT_MODE || 'persistent',
109
- disableNonessential: env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC || '',
110
- effortLevel: env.CLAUDE_CODE_EFFORT_LEVEL || '',
111
- smallFastModel: env.ANTHROPIC_SMALL_FAST_MODEL || '',
112
- defaultOpus: env.ANTHROPIC_DEFAULT_OPUS_MODEL || '',
113
- defaultSonnet: env.ANTHROPIC_DEFAULT_SONNET_MODEL || '',
114
- defaultHaiku: env.ANTHROPIC_DEFAULT_HAIKU_MODEL || '',
115
- thinkingBudget: env.ANTHROPIC_THINKING_BUDGET || '',
116
- stallTimeout: parseInt(env.SESSION_STALL_TIMEOUT, 10) || 1200,
117
- completionTimeout: parseInt(env.SESSION_COMPLETION_TIMEOUT, 10) || 300,
118
- maxTurns: parseInt(env.SESSION_MAX_TURNS, 10) || 0,
119
- editThreshold: parseInt(env.EDIT_THRESHOLD, 10) || 15,
120
- raw: env,
121
- };
122
-
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') {
130
- config.model = 'claude-3-haiku-20240307';
131
- config.defaultOpus = 'claude-3-haiku-20240307';
132
- config.defaultSonnet = 'claude-3-haiku-20240307';
133
- config.defaultHaiku = 'claude-3-haiku-20240307';
134
- config.smallFastModel = 'claude-3-haiku-20240307';
135
- config.thinkingBudget = '0';
136
- }
137
-
138
- return config;
139
- }
140
-
141
- function buildEnvVars(config) {
142
- const env = { ...process.env };
143
- if (config.baseUrl) env.ANTHROPIC_BASE_URL = config.baseUrl;
144
- if (config.apiKey) env.ANTHROPIC_API_KEY = config.apiKey;
145
- if (config.authToken) env.ANTHROPIC_AUTH_TOKEN = config.authToken;
146
- if (config.model) env.ANTHROPIC_MODEL = config.model;
147
- 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
- if (config.disableNonessential) env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = config.disableNonessential;
151
- if (config.effortLevel) env.CLAUDE_CODE_EFFORT_LEVEL = config.effortLevel;
152
- if (config.defaultOpus) env.ANTHROPIC_DEFAULT_OPUS_MODEL = config.defaultOpus;
153
- if (config.defaultSonnet) env.ANTHROPIC_DEFAULT_SONNET_MODEL = config.defaultSonnet;
154
- if (config.defaultHaiku) env.ANTHROPIC_DEFAULT_HAIKU_MODEL = config.defaultHaiku;
155
- if (config.thinkingBudget) env.ANTHROPIC_THINKING_BUDGET = config.thinkingBudget;
156
- return env;
157
- }
158
-
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
- function updateEnvVar(key, value) {
173
- const p = paths();
174
- if (!fs.existsSync(p.envFile)) return false;
175
- let content = fs.readFileSync(p.envFile, 'utf8');
176
- const regex = new RegExp(`^${key}=.*$`, 'm');
177
- if (regex.test(content)) {
178
- content = content.replace(regex, `${key}=${value}`);
179
- } else {
180
- const suffix = content.endsWith('\n') ? '' : '\n';
181
- content += `${suffix}${key}=${value}\n`;
182
- }
183
- fs.writeFileSync(p.envFile, content, 'utf8');
184
- return true;
185
- }
186
-
187
- module.exports = {
188
- COLOR,
189
- log,
190
- getProjectRoot,
191
- getLoopDir,
192
- ensureLoopDir,
193
- getTemplatePath,
194
- getPromptPath,
195
- paths,
196
- parseEnvFile,
197
- loadConfig,
198
- buildEnvVars,
199
- getAllowedTools,
200
- updateEnvVar,
201
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+
5
+ const COLOR = {
6
+ red: '\x1b[0;31m',
7
+ green: '\x1b[0;32m',
8
+ yellow: '\x1b[1;33m',
9
+ blue: '\x1b[0;34m',
10
+ magenta: '\x1b[0;35m',
11
+ cyan: '\x1b[0;36m',
12
+ bold: '\x1b[1m',
13
+ dim: '\x1b[2m',
14
+ reset: '\x1b[0m',
15
+ };
16
+
17
+ function log(level, msg) {
18
+ const tags = {
19
+ info: `${COLOR.blue}[INFO]${COLOR.reset} `,
20
+ ok: `${COLOR.green}[OK]${COLOR.reset} `,
21
+ warn: `${COLOR.yellow}[WARN]${COLOR.reset} `,
22
+ error: `${COLOR.red}[ERROR]${COLOR.reset}`,
23
+ };
24
+ console.error(`${tags[level] || ''} ${msg}`);
25
+ }
26
+
27
+ // --------------- .env parsing ---------------
28
+
29
+ function parseEnvFile(filepath) {
30
+ if (!fs.existsSync(filepath)) return {};
31
+ const content = fs.readFileSync(filepath, 'utf8');
32
+ const vars = {};
33
+ for (const line of content.split('\n')) {
34
+ const trimmed = line.trim();
35
+ if (!trimmed || trimmed.startsWith('#')) continue;
36
+ const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
37
+ if (match) {
38
+ vars[match[1]] = match[2].trim().replace(/^["']|["']$/g, '');
39
+ }
40
+ }
41
+ return vars;
42
+ }
43
+
44
+ // --------------- Model mapping ---------------
45
+
46
+ function loadConfig() {
47
+ const { assets } = require('./assets');
48
+ const envPath = assets.path('env');
49
+ const env = envPath ? parseEnvFile(envPath) : {};
50
+ const config = {
51
+ provider: env.MODEL_PROVIDER || 'claude',
52
+ baseUrl: env.ANTHROPIC_BASE_URL || '',
53
+ apiKey: env.ANTHROPIC_API_KEY || '',
54
+ authToken: env.ANTHROPIC_AUTH_TOKEN || '',
55
+ model: env.ANTHROPIC_MODEL || '',
56
+ timeoutMs: parseInt(env.API_TIMEOUT_MS, 10) || 3000000,
57
+ mcpPlaywright: env.MCP_PLAYWRIGHT === 'true',
58
+ playwrightMode: env.MCP_PLAYWRIGHT_MODE || 'persistent',
59
+ disableNonessential: env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC || '',
60
+ effortLevel: env.CLAUDE_CODE_EFFORT_LEVEL || '',
61
+ defaultOpus: env.ANTHROPIC_DEFAULT_OPUS_MODEL || '',
62
+ defaultSonnet: env.ANTHROPIC_DEFAULT_SONNET_MODEL || '',
63
+ defaultHaiku: env.ANTHROPIC_DEFAULT_HAIKU_MODEL || '',
64
+ thinkingBudget: env.ANTHROPIC_THINKING_BUDGET || '',
65
+ stallTimeout: parseInt(env.SESSION_STALL_TIMEOUT, 10) || 600,
66
+ completionTimeout: parseInt(env.SESSION_COMPLETION_TIMEOUT, 10) || 300,
67
+ maxTurns: parseInt(env.SESSION_MAX_TURNS, 10) || 0,
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,
71
+ raw: env,
72
+ };
73
+
74
+ if (config.baseUrl && config.baseUrl.includes('deepseek') && config.model === 'deepseek-chat') {
75
+ config.model = 'claude-3-haiku-20240307';
76
+ config.defaultOpus = 'claude-3-haiku-20240307';
77
+ config.defaultSonnet = 'claude-3-haiku-20240307';
78
+ config.defaultHaiku = 'claude-3-haiku-20240307';
79
+ config.thinkingBudget = '0';
80
+ }
81
+
82
+ return config;
83
+ }
84
+
85
+ function buildEnvVars(config) {
86
+ const env = { ...process.env };
87
+ if (config.baseUrl) env.ANTHROPIC_BASE_URL = config.baseUrl;
88
+ if (config.apiKey) env.ANTHROPIC_API_KEY = config.apiKey;
89
+ if (config.authToken) env.ANTHROPIC_AUTH_TOKEN = config.authToken;
90
+ if (config.model) env.ANTHROPIC_MODEL = config.model;
91
+ if (config.timeoutMs) env.API_TIMEOUT_MS = String(config.timeoutMs);
92
+ if (config.disableNonessential) env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = config.disableNonessential;
93
+ if (config.effortLevel) env.CLAUDE_CODE_EFFORT_LEVEL = config.effortLevel;
94
+ if (config.defaultOpus) env.ANTHROPIC_DEFAULT_OPUS_MODEL = config.defaultOpus;
95
+ if (config.defaultSonnet) env.ANTHROPIC_DEFAULT_SONNET_MODEL = config.defaultSonnet;
96
+ if (config.defaultHaiku) env.ANTHROPIC_DEFAULT_HAIKU_MODEL = config.defaultHaiku;
97
+ if (config.thinkingBudget) env.ANTHROPIC_THINKING_BUDGET = config.thinkingBudget;
98
+ return env;
99
+ }
100
+
101
+ // --------------- Allowed tools ---------------
102
+
103
+ function getAllowedTools(config) {
104
+ const tools = [
105
+ 'Read', 'Edit', 'MultiEdit', 'Write',
106
+ 'Bash', 'Glob', 'Grep', 'LS',
107
+ 'Task',
108
+ 'WebSearch', 'WebFetch',
109
+ ];
110
+ if (config.mcpPlaywright) tools.push('mcp__playwright__*');
111
+ return tools;
112
+ }
113
+
114
+ function updateEnvVar(key, value) {
115
+ const { assets } = require('./assets');
116
+ const envPath = assets.path('env');
117
+ if (!envPath || !fs.existsSync(envPath)) return false;
118
+ let content = fs.readFileSync(envPath, 'utf8');
119
+ const regex = new RegExp(`^${key}=.*$`, 'm');
120
+ if (regex.test(content)) {
121
+ content = content.replace(regex, `${key}=${value}`);
122
+ } else {
123
+ const suffix = content.endsWith('\n') ? '' : '\n';
124
+ content += `${suffix}${key}=${value}\n`;
125
+ }
126
+ fs.writeFileSync(envPath, content, 'utf8');
127
+ return true;
128
+ }
129
+
130
+ module.exports = {
131
+ COLOR,
132
+ log,
133
+ parseEnvFile,
134
+ loadConfig,
135
+ buildEnvVars,
136
+ getAllowedTools,
137
+ updateEnvVar,
138
+ };
@@ -0,0 +1,57 @@
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
+ TESTS: 'tests.json',
32
+ TEST_ENV: 'test.env',
33
+ PLAYWRIGHT_AUTH: 'playwright-auth.json',
34
+ ENV: '.env',
35
+ MCP_CONFIG: '.mcp.json',
36
+ });
37
+
38
+ /**
39
+ * 重试配置
40
+ */
41
+ const RETRY = Object.freeze({
42
+ MAX_ATTEMPTS: 3,
43
+ SCAN_ATTEMPTS: 3,
44
+ });
45
+
46
+ /**
47
+ * 编辑防护阈值
48
+ */
49
+ const EDIT_THRESHOLD = 15;
50
+
51
+ module.exports = {
52
+ TASK_STATUSES,
53
+ STATUS_TRANSITIONS,
54
+ FILES,
55
+ RETRY,
56
+ EDIT_THRESHOLD,
57
+ };