claude-coder 1.8.4 → 1.9.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 (78) hide show
  1. package/README.md +214 -167
  2. package/bin/cli.js +155 -172
  3. package/package.json +55 -53
  4. package/recipes/_shared/roles/developer.md +11 -11
  5. package/recipes/_shared/roles/product.md +12 -12
  6. package/recipes/_shared/roles/tester.md +12 -12
  7. package/recipes/_shared/test/report-format.md +86 -86
  8. package/recipes/backend/base.md +27 -27
  9. package/recipes/backend/components/auth.md +18 -18
  10. package/recipes/backend/components/crud-api.md +18 -18
  11. package/recipes/backend/components/file-service.md +15 -15
  12. package/recipes/backend/manifest.json +20 -20
  13. package/recipes/backend/test/api-test.md +25 -25
  14. package/recipes/console/base.md +37 -37
  15. package/recipes/console/components/modal-form.md +20 -20
  16. package/recipes/console/components/pagination.md +17 -17
  17. package/recipes/console/components/search.md +17 -17
  18. package/recipes/console/components/table-list.md +18 -18
  19. package/recipes/console/components/tabs.md +14 -14
  20. package/recipes/console/components/tree.md +15 -15
  21. package/recipes/console/components/upload.md +15 -15
  22. package/recipes/console/manifest.json +24 -24
  23. package/recipes/console/test/crud-e2e.md +47 -47
  24. package/recipes/h5/base.md +26 -26
  25. package/recipes/h5/components/animation.md +11 -11
  26. package/recipes/h5/components/countdown.md +11 -11
  27. package/recipes/h5/components/share.md +11 -11
  28. package/recipes/h5/components/swiper.md +11 -11
  29. package/recipes/h5/manifest.json +21 -21
  30. package/recipes/h5/test/h5-e2e.md +20 -20
  31. package/src/commands/auth.js +362 -290
  32. package/src/commands/setup-modules/helpers.js +100 -99
  33. package/src/commands/setup-modules/index.js +25 -25
  34. package/src/commands/setup-modules/mcp.js +115 -95
  35. package/src/commands/setup-modules/provider.js +260 -260
  36. package/src/commands/setup-modules/safety.js +47 -61
  37. package/src/commands/setup-modules/simplify.js +52 -52
  38. package/src/commands/setup.js +172 -172
  39. package/src/common/assets.js +245 -236
  40. package/src/common/config.js +125 -125
  41. package/src/common/constants.js +55 -55
  42. package/src/common/indicator.js +260 -222
  43. package/src/common/interaction.js +170 -170
  44. package/src/common/logging.js +77 -77
  45. package/src/common/sdk.js +50 -50
  46. package/src/common/tasks.js +88 -88
  47. package/src/common/utils.js +213 -161
  48. package/src/core/coding.js +33 -55
  49. package/src/core/go.js +264 -310
  50. package/src/core/hooks.js +500 -533
  51. package/src/core/init.js +166 -171
  52. package/src/core/plan.js +188 -325
  53. package/src/core/prompts.js +247 -227
  54. package/src/core/repair.js +36 -46
  55. package/src/core/runner.js +458 -195
  56. package/src/core/scan.js +93 -89
  57. package/src/core/session.js +271 -57
  58. package/src/core/simplify.js +74 -53
  59. package/src/core/state.js +105 -0
  60. package/src/index.js +76 -0
  61. package/templates/bash-process.md +12 -12
  62. package/templates/codingSystem.md +65 -65
  63. package/templates/codingUser.md +17 -17
  64. package/templates/coreProtocol.md +29 -29
  65. package/templates/goSystem.md +130 -130
  66. package/templates/guidance.json +72 -53
  67. package/templates/planSystem.md +78 -78
  68. package/templates/planUser.md +8 -8
  69. package/templates/requirements.example.md +57 -57
  70. package/templates/scanSystem.md +120 -120
  71. package/templates/scanUser.md +10 -10
  72. package/templates/test_rule.md +194 -194
  73. package/templates/web-testing.md +17 -0
  74. package/types/index.d.ts +217 -0
  75. package/src/core/context.js +0 -117
  76. package/src/core/harness.js +0 -484
  77. package/src/core/query.js +0 -50
  78. package/templates/playwright.md +0 -17
@@ -1,236 +1,245 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- const BUNDLED_DIR = path.join(__dirname, '..', '..', 'templates');
7
- const BUNDLED_RECIPES_DIR = path.join(__dirname, '..', '..', 'recipes');
8
-
9
- // kind: 'template' — 双目录解析(用户 assets → 内置 bundled),有缓存
10
- // kind: 'data' — .claude-coder/ 目录,无缓存
11
- // kind: 'runtime' — .claude-coder/.runtime/ 目录,无缓存
12
- // kind: 'root' — 项目根目录,无缓存
13
- const REGISTRY = new Map([
14
- // System Prompt Templates (per session type)
15
- ['coreProtocol', { file: 'coreProtocol.md', kind: 'template' }],
16
- ['codingSystem', { file: 'codingSystem.md', kind: 'template' }],
17
- ['planSystem', { file: 'planSystem.md', kind: 'template' }],
18
- ['scanSystem', { file: 'scanSystem.md', kind: 'template' }],
19
- ['goSystem', { file: 'goSystem.md', kind: 'template' }],
20
-
21
- // User Prompt Templates
22
- ['codingUser', { file: 'codingUser.md', kind: 'template' }],
23
- ['scanUser', { file: 'scanUser.md', kind: 'template' }],
24
- ['planUser', { file: 'planUser.md', kind: 'template' }],
25
-
26
- // Other Templates
27
- ['testRule', { file: 'test_rule.md', kind: 'template' }],
28
- ['guidance', { file: 'guidance.json', kind: 'template' }],
29
- ['playwright', { file: 'playwright.md', kind: 'template' }],
30
- ['bashProcess', { file: 'bash-process.md', kind: 'template' }],
31
- ['requirements', { file: 'requirements.example.md', kind: 'template' }],
32
-
33
- // Data files (.claude-coder/)
34
- ['env', { file: '.env', kind: 'data' }],
35
- ['tasks', { file: 'tasks.json', kind: 'data' }],
36
- ['progress', { file: 'progress.json', kind: 'data' }],
37
- ['sessionResult', { file: 'session_result.json', kind: 'data' }],
38
- ['profile', { file: 'project_profile.json', kind: 'data' }],
39
- ['testEnv', { file: 'test.env', kind: 'data' }],
40
- ['playwrightAuth', { file: 'playwright-auth.json', kind: 'data' }],
41
-
42
- // Runtime files (.claude-coder/.runtime/)
43
- ['harnessState', { file: 'harness_state.json', kind: 'runtime' }],
44
- ['browserProfile', { file: 'browser-profile', kind: 'runtime' }],
45
-
46
- // Root files (project root)
47
- ['mcpConfig', { file: '.mcp.json', kind: 'root' }],
48
- ]);
49
-
50
- const DIRS = new Map([
51
- ['loop', ''],
52
- ['assets', 'assets'],
53
- ['runtime', '.runtime'],
54
- ['logs', '.runtime/logs'],
55
- ]);
56
-
57
- function renderTemplate(template, vars = {}) {
58
- return template
59
- .replace(/\{\{(\w+)\}\}/g, (_, key) =>
60
- Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : ''
61
- )
62
- .replace(/^\s+$/gm, '')
63
- .replace(/\n{3,}/g, '\n\n')
64
- .trim();
65
- }
66
-
67
- class AssetManager {
68
- constructor() {
69
- this.projectRoot = null;
70
- this.loopDir = null;
71
- this.assetsDir = null;
72
- this.bundledDir = BUNDLED_DIR;
73
- this.registry = new Map(REGISTRY);
74
- this.cache = new Map();
75
- }
76
-
77
- init(projectRoot) {
78
- this.projectRoot = projectRoot || process.cwd();
79
- this.loopDir = path.join(this.projectRoot, '.claude-coder');
80
- this.assetsDir = path.join(this.loopDir, 'assets');
81
- this.cache.clear();
82
- }
83
-
84
- _ensureInit() {
85
- if (!this.loopDir) this.init();
86
- }
87
-
88
- path(name) {
89
- this._ensureInit();
90
- const entry = this.registry.get(name);
91
- if (!entry) return null;
92
- switch (entry.kind) {
93
- case 'template': return this._resolveTemplate(entry.file);
94
- case 'data': return path.join(this.loopDir, entry.file);
95
- case 'runtime': return path.join(this.loopDir, '.runtime', entry.file);
96
- case 'root': return path.join(this.projectRoot, entry.file);
97
- default: return null;
98
- }
99
- }
100
-
101
- _resolveTemplate(filename) {
102
- if (this.assetsDir) {
103
- const userPath = path.join(this.assetsDir, filename);
104
- if (fs.existsSync(userPath)) return userPath;
105
- }
106
- const bundled = path.join(this.bundledDir, filename);
107
- if (fs.existsSync(bundled)) return bundled;
108
- return null;
109
- }
110
-
111
- dir(name) {
112
- this._ensureInit();
113
- const rel = DIRS.get(name);
114
- if (rel === undefined) return null;
115
- return rel === '' ? this.loopDir : path.join(this.loopDir, rel);
116
- }
117
-
118
- exists(name) {
119
- const p = this.path(name);
120
- return p ? fs.existsSync(p) : false;
121
- }
122
-
123
- read(name) {
124
- this._ensureInit();
125
- const entry = this.registry.get(name);
126
- if (!entry) return null;
127
-
128
- if (entry.kind === 'template') {
129
- const key = entry.file;
130
- if (this.cache.has(key)) return this.cache.get(key);
131
- const filePath = this._resolveTemplate(entry.file);
132
- if (!filePath) return '';
133
- const content = fs.readFileSync(filePath, 'utf8');
134
- this.cache.set(key, content);
135
- return content;
136
- }
137
-
138
- const filePath = this.path(name);
139
- if (!filePath || !fs.existsSync(filePath)) return null;
140
- return fs.readFileSync(filePath, 'utf8');
141
- }
142
-
143
- readJson(name, fallback = null) {
144
- const content = this.read(name);
145
- if (content === null || content === '') return fallback;
146
- try {
147
- return JSON.parse(content);
148
- } catch {
149
- return fallback;
150
- }
151
- }
152
-
153
- write(name, content) {
154
- this._ensureInit();
155
- const entry = this.registry.get(name);
156
- if (!entry || entry.kind === 'template') return;
157
- const filePath = this.path(name);
158
- if (!filePath) return;
159
- const dir = path.dirname(filePath);
160
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
161
- fs.writeFileSync(filePath, content, 'utf8');
162
- }
163
-
164
- writeJson(name, data) {
165
- this.write(name, JSON.stringify(data, null, 2) + '\n');
166
- }
167
-
168
- render(name, vars = {}) {
169
- const raw = this.read(name);
170
- if (!raw) return '';
171
- return renderTemplate(raw, vars);
172
- }
173
-
174
- ensureDirs() {
175
- this._ensureInit();
176
- for (const [, rel] of DIRS) {
177
- const dir = rel === '' ? this.loopDir : path.join(this.loopDir, rel);
178
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
179
- }
180
- }
181
-
182
- deployAll() {
183
- this._ensureInit();
184
- if (!fs.existsSync(this.assetsDir)) {
185
- fs.mkdirSync(this.assetsDir, { recursive: true });
186
- }
187
- const files = fs.readdirSync(this.bundledDir);
188
- const deployed = [];
189
- for (const file of files) {
190
- const dest = path.join(this.assetsDir, file);
191
- if (fs.existsSync(dest)) continue;
192
- const src = path.join(this.bundledDir, file);
193
- try {
194
- fs.copyFileSync(src, dest);
195
- deployed.push(file);
196
- } catch { /* skip */ }
197
- }
198
- return deployed;
199
- }
200
-
201
- deployRecipes() {
202
- this._ensureInit();
203
- const destDir = path.join(this.loopDir, 'recipes');
204
- if (!fs.existsSync(BUNDLED_RECIPES_DIR)) return [];
205
- if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
206
-
207
- const deployed = [];
208
- const walk = (srcBase, destBase) => {
209
- const entries = fs.readdirSync(srcBase, { withFileTypes: true });
210
- for (const entry of entries) {
211
- const srcPath = path.join(srcBase, entry.name);
212
- const destPath = path.join(destBase, entry.name);
213
- if (entry.isDirectory()) {
214
- if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
215
- walk(srcPath, destPath);
216
- } else {
217
- if (fs.existsSync(destPath)) continue;
218
- try {
219
- fs.copyFileSync(srcPath, destPath);
220
- deployed.push(path.relative(destDir, destPath));
221
- } catch { /* skip */ }
222
- }
223
- }
224
- };
225
- walk(BUNDLED_RECIPES_DIR, destDir);
226
- return deployed;
227
- }
228
-
229
- clearCache() {
230
- this.cache.clear();
231
- }
232
- }
233
-
234
- const assets = new AssetManager();
235
-
236
- module.exports = { AssetManager, assets, renderTemplate };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const BUNDLED_DIR = path.join(__dirname, '..', '..', 'templates');
7
+ const BUNDLED_RECIPES_DIR = path.join(__dirname, '..', '..', 'recipes');
8
+
9
+ // kind: 'template' — 双目录解析(用户 assets → 内置 bundled),有缓存
10
+ // kind: 'data' — .claude-coder/ 目录,无缓存
11
+ // kind: 'runtime' — .claude-coder/.runtime/ 目录,无缓存
12
+ // kind: 'root' — 项目根目录,无缓存
13
+ const REGISTRY = new Map([
14
+ // System Prompt Templates (per session type)
15
+ ['coreProtocol', { file: 'coreProtocol.md', kind: 'template' }],
16
+ ['codingSystem', { file: 'codingSystem.md', kind: 'template' }],
17
+ ['planSystem', { file: 'planSystem.md', kind: 'template' }],
18
+ ['scanSystem', { file: 'scanSystem.md', kind: 'template' }],
19
+ ['goSystem', { file: 'goSystem.md', kind: 'template' }],
20
+
21
+ // User Prompt Templates
22
+ ['codingUser', { file: 'codingUser.md', kind: 'template' }],
23
+ ['scanUser', { file: 'scanUser.md', kind: 'template' }],
24
+ ['planUser', { file: 'planUser.md', kind: 'template' }],
25
+
26
+ // Other Templates
27
+ ['testRule', { file: 'test_rule.md', kind: 'template' }],
28
+ ['guidance', { file: 'guidance.json', kind: 'template' }],
29
+ ['webTesting', { file: 'web-testing.md', kind: 'template' }],
30
+ ['bashProcess', { file: 'bash-process.md', kind: 'template' }],
31
+ ['requirements', { file: 'requirements.example.md', kind: 'template' }],
32
+
33
+ // Data files (.claude-coder/)
34
+ ['env', { file: '.env', kind: 'data' }],
35
+ ['tasks', { file: 'tasks.json', kind: 'data' }],
36
+ ['progress', { file: 'progress.json', kind: 'data' }],
37
+ ['sessionResult', { file: 'session_result.json', kind: 'data' }],
38
+ ['profile', { file: 'project_profile.json', kind: 'data' }],
39
+ ['testEnv', { file: 'test.env', kind: 'data' }],
40
+ ['playwrightAuth', { file: 'playwright-auth.json', kind: 'data' }],
41
+
42
+ // Runtime files (.claude-coder/.runtime/)
43
+ ['harnessState', { file: 'harness_state.json', kind: 'runtime' }],
44
+ ['browserProfile', { file: 'browser-profile', kind: 'runtime' }],
45
+
46
+ // Root files (project root)
47
+ ['mcpConfig', { file: '.mcp.json', kind: 'root' }],
48
+ ]);
49
+
50
+ const DIRS = new Map([
51
+ ['loop', ''],
52
+ ['assets', 'assets'],
53
+ ['runtime', '.runtime'],
54
+ ['logs', '.runtime/logs'],
55
+ ]);
56
+
57
+ function renderTemplate(template, vars = {}) {
58
+ return template
59
+ .replace(/\{\{(\w+)\}\}/g, (_, key) =>
60
+ Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : ''
61
+ )
62
+ .replace(/^\s+$/gm, '')
63
+ .replace(/\n{3,}/g, '\n\n')
64
+ .trim();
65
+ }
66
+
67
+ class AssetManager {
68
+ constructor() {
69
+ this.projectRoot = null;
70
+ this.loopDir = null;
71
+ this.assetsDir = null;
72
+ this.bundledDir = BUNDLED_DIR;
73
+ this.registry = new Map(REGISTRY);
74
+ this.cache = new Map();
75
+ }
76
+
77
+ init(projectRoot) {
78
+ this.projectRoot = projectRoot || process.cwd();
79
+ this.loopDir = path.join(this.projectRoot, '.claude-coder');
80
+ this.assetsDir = path.join(this.loopDir, 'assets');
81
+ this.cache.clear();
82
+ }
83
+
84
+ _ensureInit() {
85
+ if (!this.loopDir) this.init();
86
+ }
87
+
88
+ path(name) {
89
+ this._ensureInit();
90
+ const entry = this.registry.get(name);
91
+ if (!entry) return null;
92
+ switch (entry.kind) {
93
+ case 'template': return this._resolveTemplate(entry.file);
94
+ case 'data': return path.join(this.loopDir, entry.file);
95
+ case 'runtime': return path.join(this.loopDir, '.runtime', entry.file);
96
+ case 'root': return path.join(this.projectRoot, entry.file);
97
+ default: return null;
98
+ }
99
+ }
100
+
101
+ _resolveTemplate(filename) {
102
+ if (this.assetsDir) {
103
+ const userPath = path.join(this.assetsDir, filename);
104
+ if (fs.existsSync(userPath)) return userPath;
105
+ }
106
+ const bundled = path.join(this.bundledDir, filename);
107
+ if (fs.existsSync(bundled)) return bundled;
108
+ return null;
109
+ }
110
+
111
+ dir(name) {
112
+ this._ensureInit();
113
+ const rel = DIRS.get(name);
114
+ if (rel === undefined) return null;
115
+ return rel === '' ? this.loopDir : path.join(this.loopDir, rel);
116
+ }
117
+
118
+ exists(name) {
119
+ const p = this.path(name);
120
+ return p ? fs.existsSync(p) : false;
121
+ }
122
+
123
+ read(name) {
124
+ this._ensureInit();
125
+ const entry = this.registry.get(name);
126
+ if (!entry) return null;
127
+
128
+ if (entry.kind === 'template') {
129
+ const key = entry.file;
130
+ if (this.cache.has(key)) return this.cache.get(key);
131
+ const filePath = this._resolveTemplate(entry.file);
132
+ if (!filePath) return '';
133
+ const content = fs.readFileSync(filePath, 'utf8');
134
+ this.cache.set(key, content);
135
+ return content;
136
+ }
137
+
138
+ const filePath = this.path(name);
139
+ if (!filePath || !fs.existsSync(filePath)) return null;
140
+ return fs.readFileSync(filePath, 'utf8');
141
+ }
142
+
143
+ readJson(name, fallback = null) {
144
+ const content = this.read(name);
145
+ if (content === null || content === '') return fallback;
146
+ try {
147
+ return JSON.parse(content);
148
+ } catch {
149
+ return fallback;
150
+ }
151
+ }
152
+
153
+ write(name, content) {
154
+ this._ensureInit();
155
+ const entry = this.registry.get(name);
156
+ if (!entry || entry.kind === 'template') return;
157
+ const filePath = this.path(name);
158
+ if (!filePath) return;
159
+ const dir = path.dirname(filePath);
160
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
161
+ fs.writeFileSync(filePath, content, 'utf8');
162
+ }
163
+
164
+ writeJson(name, data) {
165
+ this.write(name, JSON.stringify(data, null, 2) + '\n');
166
+ }
167
+
168
+ render(name, vars = {}) {
169
+ const raw = this.read(name);
170
+ if (!raw) return '';
171
+ return renderTemplate(raw, vars);
172
+ }
173
+
174
+ ensureDirs() {
175
+ this._ensureInit();
176
+ for (const [, rel] of DIRS) {
177
+ const dir = rel === '' ? this.loopDir : path.join(this.loopDir, rel);
178
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
179
+ }
180
+ }
181
+
182
+ deployAll() {
183
+ this._ensureInit();
184
+ if (!fs.existsSync(this.assetsDir)) {
185
+ fs.mkdirSync(this.assetsDir, { recursive: true });
186
+ }
187
+ const files = fs.readdirSync(this.bundledDir);
188
+ const deployed = [];
189
+ for (const file of files) {
190
+ const dest = path.join(this.assetsDir, file);
191
+ if (fs.existsSync(dest)) continue;
192
+ const src = path.join(this.bundledDir, file);
193
+ try {
194
+ fs.copyFileSync(src, dest);
195
+ deployed.push(file);
196
+ } catch { /* skip */ }
197
+ }
198
+ return deployed;
199
+ }
200
+
201
+ deployRecipes() {
202
+ this._ensureInit();
203
+ const destDir = path.join(this.loopDir, 'recipes');
204
+ if (!fs.existsSync(BUNDLED_RECIPES_DIR)) return [];
205
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
206
+
207
+ const deployed = [];
208
+ const walk = (srcBase, destBase) => {
209
+ const entries = fs.readdirSync(srcBase, { withFileTypes: true });
210
+ for (const entry of entries) {
211
+ const srcPath = path.join(srcBase, entry.name);
212
+ const destPath = path.join(destBase, entry.name);
213
+ if (entry.isDirectory()) {
214
+ if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
215
+ walk(srcPath, destPath);
216
+ } else {
217
+ if (fs.existsSync(destPath)) continue;
218
+ try {
219
+ fs.copyFileSync(srcPath, destPath);
220
+ deployed.push(path.relative(destDir, destPath));
221
+ } catch { /* skip */ }
222
+ }
223
+ }
224
+ };
225
+ walk(BUNDLED_RECIPES_DIR, destDir);
226
+ return deployed;
227
+ }
228
+
229
+ recipesDir() {
230
+ this._ensureInit();
231
+ const projectRecipes = path.join(this.loopDir, 'recipes');
232
+ if (fs.existsSync(projectRecipes) && fs.readdirSync(projectRecipes).length > 0) {
233
+ return projectRecipes;
234
+ }
235
+ return BUNDLED_RECIPES_DIR;
236
+ }
237
+
238
+ clearCache() {
239
+ this.cache.clear();
240
+ }
241
+ }
242
+
243
+ const assets = new AssetManager();
244
+
245
+ module.exports = { AssetManager, assets, renderTemplate };