claude-coder 1.8.1 → 1.8.2

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 CHANGED
@@ -45,6 +45,9 @@ claude-coder run "实现用户注册和登录功能"
45
45
  | `claude-coder plan -r [file]` | 从需求文件生成计划 |
46
46
  | `claude-coder plan --planOnly` | 仅生成计划文档,不分解任务 |
47
47
  | `claude-coder plan -i "需求"` | 交互模式,允许模型提问 |
48
+ | `claude-coder go` | AI 对话式需求收集与方案组装 |
49
+ | `claude-coder go "需求"` | AI 自动分析需求并组装方案 |
50
+ | `claude-coder go -r file` | 从文件读取需求并自动组装 |
48
51
  | `claude-coder run [需求]` | 自动编码循环 |
49
52
  | `claude-coder run --max 1` | 单次执行 |
50
53
  | `claude-coder run --dry-run` | 预览模式(查看任务队列) |
@@ -83,6 +86,7 @@ Harness 在 session 结束后自动校验 `session_result.json` + git 进度。
83
86
  | [技术架构](design/ARCHITECTURE.md) | 核心设计规则、Harness 类职责、模块关系、Prompt 注入架构 |
84
87
  | [Hook 注入机制](design/hook-mechanism.md) | SDK Hook 调研、GuidanceInjector 三级匹配、配置格式、副作用评估 |
85
88
  | [Session 守护机制](design/session-guard.md) | 中断策略、倒计时活跃度检测、工具运行状态追踪、防刷屏 |
89
+ | [Go 指令流程](design/go-flow.md) | AI 驱动的需求组装、食谱系统、与 plan 衔接 |
86
90
  | [测试凭证方案](docs/PLAYWRIGHT_CREDENTIALS.md) | Playwright 登录态导出、API Key 持久化 |
87
91
  | [SDK 使用指南](docs/CLAUDE_AGENT_SDK_GUIDE.md) | Claude Agent SDK 接口参考 |
88
92
 
@@ -94,6 +98,8 @@ Harness 在 session 结束后自动校验 `session_result.json` + git 进度。
94
98
 
95
99
  **需求文档驱动**:在项目根目录创建 `requirements.md`,运行 `claude-coder run`。需求变更后 `claude-coder add -r` 同步新任务。
96
100
 
101
+ **AI 驱动需求组装**:`claude-coder go "用户管理页面"` — AI 扫描内置食谱库,自动组装完整需求方案,一键进入 plan 分解任务。支持对话模式 (`go`) 逐步引导非技术人员。
102
+
97
103
  **自动测试 + 凭证持久化**:`claude-coder auth http://localhost:3000` — 导出浏览器登录态,Agent 测试时自动使用。详见 [测试凭证方案](docs/PLAYWRIGHT_CREDENTIALS.md)。
98
104
 
99
105
  ## 模型支持
@@ -135,6 +141,8 @@ your-project/
135
141
  session_result.json # 上次 session 结果
136
142
  progress.json # 会话历史 + 成本
137
143
  test.env # 测试凭证(可选)
144
+ go/ # go 指令输出的方案文件
145
+ recipes/ # 食谱库(init 时从内置模板部署)
138
146
  .runtime/
139
147
  harness_state.json # Harness 状态(session 计数等)
140
148
  logs/ # 每 session 独立日志
package/bin/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
4
  const pkg = require('../package.json');
@@ -11,6 +11,7 @@ const COMMANDS = {
11
11
  simplify: { desc: '代码审查和简化', usage: 'claude-coder simplify [focus]' },
12
12
  auth: { desc: '导出 Playwright 登录状态', usage: 'claude-coder auth [url]' },
13
13
  status: { desc: '查看任务进度和成本', usage: 'claude-coder status' },
14
+ go: { desc: 'AI 驱动的需求组装', usage: 'claude-coder go ["需求"] [-r file] [--reset]' },
14
15
  };
15
16
 
16
17
  function showHelp() {
@@ -32,6 +33,10 @@ function showHelp() {
32
33
  console.log(' claude-coder run --dry-run 预览模式');
33
34
  console.log(' claude-coder simplify 代码审查和简化');
34
35
  console.log(' claude-coder simplify "内存效率" 聚焦特定领域审查');
36
+ console.log(' claude-coder go 对话式需求收集和方案组装');
37
+ console.log(' claude-coder go "用户管理页面" AI 自动分析需求并组装方案');
38
+ console.log(' claude-coder go -r requirements.md 从文件读取需求并自动组装');
39
+ console.log(' claude-coder go --reset 重置 Go 记忆');
35
40
  console.log(' claude-coder auth 导出 Playwright 登录状态');
36
41
  console.log(' claude-coder auth http://localhost:8080 指定登录 URL');
37
42
  console.log(' claude-coder status 查看进度和成本');
@@ -75,6 +80,9 @@ function parseArgs(argv) {
75
80
  case '--planOnly':
76
81
  opts.planOnly = true;
77
82
  break;
83
+ case '--reset':
84
+ opts.reset = true;
85
+ break;
78
86
  case '-i':
79
87
  case '--interactive':
80
88
  opts.interactive = true;
@@ -140,6 +148,11 @@ async function main() {
140
148
  await auth(positional[0] || null);
141
149
  break;
142
150
  }
151
+ case 'go': {
152
+ const { run: goRun } = require('../src/core/go');
153
+ await goRun(positional[0] || '', opts);
154
+ break;
155
+ }
143
156
  case 'status': {
144
157
  const tasks = require('../src/common/tasks');
145
158
  tasks.showStatus();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-coder",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "Claude Coder — Autonomous coding agent harness powered by Claude Code SDK. Scan, plan, code, validate, git-commit in a loop.",
5
5
  "bin": {
6
6
  "claude-coder": "bin/cli.js"
@@ -4,6 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  const BUNDLED_DIR = path.join(__dirname, '..', '..', 'templates');
7
+ const BUNDLED_RECIPES_DIR = path.join(__dirname, '..', '..', 'recipes');
7
8
 
8
9
  // kind: 'template' — 双目录解析(用户 assets → 内置 bundled),有缓存
9
10
  // kind: 'data' — .claude-coder/ 目录,无缓存
@@ -15,6 +16,7 @@ const REGISTRY = new Map([
15
16
  ['codingSystem', { file: 'codingSystem.md', kind: 'template' }],
16
17
  ['planSystem', { file: 'planSystem.md', kind: 'template' }],
17
18
  ['scanSystem', { file: 'scanSystem.md', kind: 'template' }],
19
+ ['goSystem', { file: 'goSystem.md', kind: 'template' }],
18
20
 
19
21
  // User Prompt Templates
20
22
  ['codingUser', { file: 'codingUser.md', kind: 'template' }],
@@ -196,6 +198,34 @@ class AssetManager {
196
198
  return deployed;
197
199
  }
198
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
+
199
229
  clearCache() {
200
230
  this.cache.clear();
201
231
  }
package/src/core/go.js ADDED
@@ -0,0 +1,310 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+ const { runSession } = require('./session');
7
+ const { buildQueryOptions } = require('./query');
8
+ const { buildSystemPrompt } = require('./prompts');
9
+ const { log, loadConfig } = require('../common/config');
10
+ const { assets } = require('../common/assets');
11
+ const { extractResultText } = require('../common/logging');
12
+ const { loadState } = require('./harness');
13
+
14
+ const GO_DIR_NAME = 'go';
15
+
16
+ function getRecipesDir() {
17
+ return path.join(assets.projectRoot || process.cwd(), '.claude-coder', 'recipes');
18
+ }
19
+
20
+ // ─── Go State (harness_state.json → go section) ──────────
21
+
22
+ function loadGoState() {
23
+ const state = loadState();
24
+ return state.go || {};
25
+ }
26
+
27
+ function saveGoState(goData) {
28
+ const state = loadState();
29
+ state.go = { ...state.go, ...goData };
30
+ assets.writeJson('harnessState', state);
31
+ }
32
+
33
+ // ─── Prompt Builder ───────────────────────────────────────
34
+
35
+ function buildGoPrompt(instruction, opts = {}) {
36
+ const recipesAbsPath = getRecipesDir();
37
+ const goState = loadGoState();
38
+
39
+ const inputSection = opts.reqFile
40
+ ? `用户需求文件路径: ${opts.reqFile}\n先读取该文件了解用户需求。`
41
+ : instruction
42
+ ? `用户需求:\n${instruction}`
43
+ : '用户未提供需求,使用对话模式收集。';
44
+
45
+ const modeSection = (instruction || opts.reqFile)
46
+ ? '【自动模式】用户已提供需求,直接分析并组装方案,不要提问。'
47
+ : '【对话模式】使用 askUserQuestion 工具,按协议中的顺序向用户提问收集需求。';
48
+
49
+ let memorySection = '';
50
+ if (goState.lastDomain || goState.lastFile) {
51
+ const parts = [];
52
+ if (goState.lastDomain) parts.push(`上次领域: ${goState.lastDomain}`);
53
+ if (goState.lastComponents) parts.push(`上次组件: ${goState.lastComponents.join(', ')}`);
54
+ if (goState.lastTimestamp) parts.push(`时间: ${goState.lastTimestamp}`);
55
+ memorySection = `上次使用记录(仅供参考):${parts.join(' | ')}`;
56
+ }
57
+
58
+ return [
59
+ inputSection,
60
+ '',
61
+ modeSection,
62
+ '',
63
+ `食谱目录绝对路径: ${recipesAbsPath}`,
64
+ '',
65
+ memorySection,
66
+ ].filter(Boolean).join('\n');
67
+ }
68
+
69
+ // ─── Content Extraction ──────────────────────────────────
70
+
71
+ function extractGoContent(collected) {
72
+ let fullText = '';
73
+ for (const msg of collected) {
74
+ if (msg.type === 'assistant' && msg.message?.content) {
75
+ for (const block of msg.message.content) {
76
+ if (block.type === 'text' && block.text) fullText += block.text;
77
+ }
78
+ }
79
+ }
80
+
81
+ const match = fullText.match(/GO_CONTENT_START\s*\n([\s\S]*?)\nGO_CONTENT_END/);
82
+ if (match) return match[1].trim();
83
+
84
+ const resultText = extractResultText(collected);
85
+ if (resultText) {
86
+ const m = resultText.match(/GO_CONTENT_START\s*\n([\s\S]*?)\nGO_CONTENT_END/);
87
+ if (m) return m[1].trim();
88
+ }
89
+
90
+ return null;
91
+ }
92
+
93
+ function extractDomainFromContent(content) {
94
+ if (!content) return null;
95
+ const match = content.match(/##\s*开发领域\s*\n\s*(\S+)/);
96
+ if (match) {
97
+ const name = match[1];
98
+ const domainMap = { '管理后台': 'console', 'H5': 'h5', '后端': 'backend' };
99
+ for (const [key, val] of Object.entries(domainMap)) {
100
+ if (name.includes(key)) return val;
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+
106
+ function extractComponentsFromContent(content) {
107
+ if (!content) return [];
108
+ const section = content.match(/##\s*功能组件\s*\n([\s\S]*?)(?=\n##|$)/);
109
+ if (!section) return [];
110
+ const items = section[1].match(/[-*]\s+\*\*(.+?)\*\*/g) || [];
111
+ return items.map(i => i.replace(/[-*]\s+\*\*|\*\*/g, '').trim());
112
+ }
113
+
114
+ // ─── Preview & Confirm ───────────────────────────────────
115
+
116
+ async function previewAndConfirm(content) {
117
+ const lines = content.split('\n');
118
+ const previewLines = Math.min(lines.length, 25);
119
+
120
+ console.log('');
121
+ console.log('┌─ 需求方案预览 ────────────────────────────────┐');
122
+ for (let i = 0; i < previewLines; i++) {
123
+ const line = lines[i].length > 52 ? lines[i].slice(0, 49) + '...' : lines[i];
124
+ console.log(`│ ${line.padEnd(52)}│`);
125
+ }
126
+ if (lines.length > previewLines) {
127
+ const msg = `... 共 ${lines.length} 行,完整内容将写入文件`;
128
+ console.log(`│ ${msg.padEnd(52)}│`);
129
+ }
130
+ console.log('└───────────────────────────────────────────────┘');
131
+
132
+ if (!process.stdin.isTTY) return { confirmed: true, supplement: '' };
133
+
134
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
135
+ return new Promise(resolve => {
136
+ rl.question('\n有什么要补充的?(直接回车确认 / 输入补充内容 / 输入 cancel 取消)\n> ', answer => {
137
+ rl.close();
138
+ const trimmed = answer.trim();
139
+ if (trimmed.toLowerCase() === 'cancel') {
140
+ resolve({ confirmed: false, supplement: '' });
141
+ } else {
142
+ resolve({ confirmed: true, supplement: trimmed });
143
+ }
144
+ });
145
+ });
146
+ }
147
+
148
+ async function promptProceedToPlan() {
149
+ if (!process.stdin.isTTY) return true;
150
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
151
+ return new Promise(resolve => {
152
+ rl.question('是否继续生成计划并分解任务?(y/n) ', answer => {
153
+ rl.close();
154
+ resolve(/^[Yy]/.test(answer.trim()));
155
+ });
156
+ });
157
+ }
158
+
159
+ // ─── Go Output ────────────────────────────────────────────
160
+
161
+ function ensureGoDir() {
162
+ const goDir = path.join(assets.projectRoot, '.claude-coder', GO_DIR_NAME);
163
+ if (!fs.existsSync(goDir)) fs.mkdirSync(goDir, { recursive: true });
164
+ return goDir;
165
+ }
166
+
167
+ function writeGoFile(content) {
168
+ const goDir = ensureGoDir();
169
+ const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 12);
170
+ const fileName = `go_${ts}.md`;
171
+ const filePath = path.join(goDir, fileName);
172
+ fs.writeFileSync(filePath, content, 'utf8');
173
+ return filePath;
174
+ }
175
+
176
+ // ─── Session Execution ───────────────────────────────────
177
+
178
+ async function _executeGoSession(instruction, opts = {}) {
179
+ const isAutoMode = !!(instruction || opts.reqFile);
180
+ const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 12);
181
+
182
+ return runSession('go', {
183
+ opts,
184
+ sessionNum: 0,
185
+ logFileName: `go_${ts}.log`,
186
+ label: isAutoMode ? 'go_auto' : 'go_dialogue',
187
+
188
+ async execute(sdk, ctx) {
189
+ log('info', isAutoMode ? '正在分析需求并组装方案...' : '正在启动对话式需求收集...');
190
+
191
+ const prompt = buildGoPrompt(instruction, opts);
192
+ const queryOpts = buildQueryOptions(ctx.config, opts);
193
+ queryOpts.systemPrompt = buildSystemPrompt('go');
194
+ queryOpts.permissionMode = 'plan';
195
+ queryOpts.hooks = ctx.hooks;
196
+ queryOpts.abortController = ctx.abortController;
197
+
198
+ if (isAutoMode) {
199
+ queryOpts.disallowedTools = ['askUserQuestion'];
200
+ }
201
+
202
+ const collected = await ctx.runQuery(sdk, prompt, queryOpts);
203
+ const content = extractGoContent(collected);
204
+
205
+ return { content, collected };
206
+ },
207
+ });
208
+ }
209
+
210
+ // ─── Main Entry ──────────────────────────────────────────
211
+
212
+ async function run(input, opts = {}) {
213
+ const instruction = input || '';
214
+
215
+ assets.ensureDirs();
216
+
217
+ const config = loadConfig();
218
+ if (!opts.model) {
219
+ opts.model = config.defaultOpus || config.model;
220
+ }
221
+
222
+ const displayModel = opts.model || config.model || '(default)';
223
+ log('ok', `模型配置已加载: ${config.provider || 'claude'} (go 使用: ${displayModel})`);
224
+
225
+ const recipesDir = getRecipesDir();
226
+ if (!fs.existsSync(recipesDir) || fs.readdirSync(recipesDir).length === 0) {
227
+ log('error', `食谱目录为空或不存在: ${recipesDir}`);
228
+ log('info', '请先运行 claude-coder init 初始化项目(会自动部署食谱)');
229
+ process.exit(1);
230
+ }
231
+
232
+ // --reset: 清空 go 记忆
233
+ if (opts.reset) {
234
+ saveGoState({});
235
+ log('ok', 'Go 记忆已重置');
236
+ return;
237
+ }
238
+
239
+ // -r: 读取需求文件,自动模式
240
+ if (opts.readFile) {
241
+ const reqPath = path.resolve(assets.projectRoot, opts.readFile);
242
+ if (!fs.existsSync(reqPath)) {
243
+ log('error', `文件不存在: ${reqPath}`);
244
+ process.exit(1);
245
+ }
246
+ opts.reqFile = reqPath;
247
+ log('info', `需求文件: ${reqPath}`);
248
+ }
249
+
250
+ // 确定模式
251
+ const mode = (instruction || opts.reqFile) ? '自动' : '对话';
252
+ log('info', `Go 模式: ${mode}`);
253
+
254
+ // 执行 go session
255
+ const result = await _executeGoSession(instruction, opts);
256
+
257
+ if (!result.content) {
258
+ log('error', '无法从 AI 输出中提取方案内容');
259
+ log('info', '请检查日志文件了解详情');
260
+ return;
261
+ }
262
+
263
+ // 预览 + 确认 + 补充
264
+ const { confirmed, supplement } = await previewAndConfirm(result.content);
265
+ if (!confirmed) {
266
+ log('info', '已取消');
267
+ return;
268
+ }
269
+
270
+ let finalContent = result.content;
271
+ if (supplement) {
272
+ finalContent += `\n\n## 补充要求\n\n${supplement}`;
273
+ }
274
+
275
+ // 写入 .claude-coder/go/
276
+ const filePath = writeGoFile(finalContent);
277
+ log('ok', `方案已保存: ${filePath}`);
278
+
279
+ // 保存记忆到 harness_state.json
280
+ const domain = extractDomainFromContent(finalContent);
281
+ const components = extractComponentsFromContent(finalContent);
282
+ const history = (loadGoState().history || []).slice(-9);
283
+ history.push({
284
+ timestamp: new Date().toISOString(),
285
+ requirement: instruction || opts.reqFile || '(对话收集)',
286
+ file: filePath,
287
+ domain,
288
+ });
289
+
290
+ saveGoState({
291
+ lastFile: filePath,
292
+ lastDomain: domain,
293
+ lastComponents: components,
294
+ lastTimestamp: new Date().toISOString(),
295
+ history,
296
+ });
297
+
298
+ // 询问是否继续到 plan
299
+ console.log('');
300
+ const shouldPlan = await promptProceedToPlan();
301
+ if (shouldPlan) {
302
+ log('info', '开始生成计划并分解任务...');
303
+ const { run: planRun } = require('./plan');
304
+ await planRun('', { reqFile: filePath, ...opts });
305
+ } else {
306
+ log('info', `方案已保存,稍后可使用: claude-coder plan -r ${filePath}`);
307
+ }
308
+ }
309
+
310
+ module.exports = { run };
package/src/core/hooks.js CHANGED
@@ -434,6 +434,7 @@ const FEATURE_MAP = {
434
434
  scan: [FEATURES.STALL],
435
435
  add: [FEATURES.STALL],
436
436
  simplify: [FEATURES.STALL, FEATURES.INTERACTION],
437
+ go: [FEATURES.STALL, FEATURES.INTERACTION],
437
438
  custom: null
438
439
  };
439
440
 
package/src/core/init.js CHANGED
@@ -59,6 +59,13 @@ function deployAssets() {
59
59
  }
60
60
  }
61
61
 
62
+ function deployRecipes() {
63
+ const deployed = assets.deployRecipes();
64
+ if (deployed.length > 0) {
65
+ log('ok', `已部署 ${deployed.length} 个食谱文件 → .claude-coder/recipes/`);
66
+ }
67
+ }
68
+
62
69
  async function init() {
63
70
  assets.ensureDirs();
64
71
  const projectRoot = assets.projectRoot;
@@ -76,6 +83,7 @@ async function init() {
76
83
  let stepCount = 0;
77
84
 
78
85
  deployAssets();
86
+ deployRecipes();
79
87
 
80
88
  const envSetup = profile.env_setup || {};
81
89
  if (envSetup.python_env && envSetup.python_env !== 'system' && envSetup.python_env !== 'none') {
@@ -16,6 +16,7 @@ function buildSystemPrompt(type) {
16
16
  case 'scan': specific = assets.read('scanSystem') || ''; break;
17
17
  case 'coding': specific = assets.read('codingSystem') || ''; break;
18
18
  case 'plan': specific = assets.read('planSystem') || ''; break;
19
+ case 'go': specific = assets.read('goSystem') || ''; break;
19
20
  }
20
21
  return specific ? `${specific}\n\n${core}` : core;
21
22
  }
@@ -0,0 +1,130 @@
1
+ <!--
2
+ Go Session System Prompt.
3
+ Prepended after coreProtocol.md by buildSystemPrompt('go').
4
+ AI 扫描 recipes/ → 对话/自动分析 → 组装完整需求方案文档。
5
+ -->
6
+
7
+ # 需求组装会话协议
8
+
9
+ ## 你是谁
10
+
11
+ 你是 claude-coder 的需求分析与方案组装 Agent。唯一职责:扫描食谱目录 → 理解用户需求 → 组装一份完整的需求方案文档。
12
+ 你**不实现代码**、不启动服务、不运行测试、不修改任何项目文件。
13
+
14
+ ## 铁律
15
+
16
+ 1. **只组装不编码** — 禁止写任何业务代码或修改项目文件
17
+ 2. **食谱为参考素材** — recipes/ 是最佳实践库,可自由引用、裁剪、增补;没有匹配食谱的部分,凭专业能力自行补充
18
+ 3. **必须输出 GO_CONTENT** — 最终必须输出方案文档,格式见下方
19
+ 4. **先扫描后提问** — 必须先用工具扫描 recipes/ 目录了解可用选项,再与用户交互
20
+
21
+ ## 工作流程
22
+
23
+ ### Step 1 — 扫描食谱目录
24
+
25
+ 使用 LS 和 Read 工具扫描 prompt 中指定的 recipes 绝对路径:
26
+
27
+ 1. `LS recipes/` → 发现所有领域目录(跳过 `_shared`)
28
+ 2. 读取每个领域的 `manifest.json` → 了解可用领域、组件、默认值
29
+ 3. 读取 `recipes/_shared/roles/` → 了解可用角色
30
+
31
+ ### Step 2 — 需求收集
32
+
33
+ **对话模式**(用户未提供需求时):
34
+
35
+ 使用 askUserQuestion 工具按以下顺序提问:
36
+
37
+ 1. **领域选择** — 展示所有可用领域(从 manifest 获取 name + description),让用户选择
38
+ 2. **组件选择** — 展示选中领域的所有组件(从 manifest.components 获取),标注默认项,让用户多选
39
+ 3. **角色确认** — 展示可用角色(产品经理/开发者/测试),让用户选择
40
+ 4. **具体需求** — 询问具体的业务需求(如"做一个用户管理页面,需要用户名、邮箱、角色字段")
41
+ 5. **测试需求** — 询问是否需要 E2E 测试
42
+ 6. **补充信息** — 询问是否有技术约束或其他补充(组件库偏好、API 风格等)
43
+
44
+ 提问规则:
45
+ - 每次只问一个主题(不要一次问太多)
46
+ - 展示清晰的选项(编号 + 描述)
47
+ - 标注默认推荐值
48
+ - 用户可以说"回退"或"修改上一个"来修改之前的选择
49
+
50
+ **自动模式**(用户已提供需求文本或文件时):
51
+
52
+ 1. 分析用户需求文本
53
+ 2. 匹配最合适的领域(根据关键词和 manifest 描述)
54
+ 3. 选择必要的组件(参考 manifest defaults + 需求分析)
55
+ 4. 推断角色(默认 developer,需求风格偏产品化则选 product)
56
+ 5. 不调用 askUserQuestion,直接输出方案
57
+
58
+ ### Step 3 — 阅读选中的食谱
59
+
60
+ 根据用户选择/AI 判断,使用 Read 工具阅读:
61
+ 1. 选中领域的 `base.md`
62
+ 2. 选中组件的 `.md` 文件
63
+ 3. 选中角色的 `.md` 文件
64
+ 4. 测试食谱(如需要)
65
+ 5. `_shared/test/report-format.md`(如需要测试)
66
+
67
+ ### Step 4 — 组装方案文档
68
+
69
+ 综合食谱内容 + 用户需求 + 专业判断,组装一份 Markdown 格式的完整需求方案文档。
70
+
71
+ 组装原则:
72
+ - **有食谱覆盖的部分**:以食谱为基线,结合具体需求适配和裁剪
73
+ - **食谱未覆盖的部分**:凭专业能力自主补充(如性能优化、安全防护、UX 建议等)
74
+ - **可跨领域组合**:如需要,从多个领域食谱中取片段混合使用
75
+ - **无食谱兜底**:即使 recipes/ 目录为空或无匹配,也能基于需求独立输出完整方案
76
+
77
+ 文档结构:
78
+
79
+ ```
80
+ # 需求方案 — {简短标题}
81
+
82
+ ## 项目概述
83
+ {用户的需求描述}
84
+
85
+ ## 开发领域
86
+ {领域名称} — {领域描述}
87
+
88
+ ## 功能组件
89
+ {按选中的组件列出功能点,结合用户具体需求}
90
+
91
+ ## 技术指导
92
+ {从食谱 .md 中提取的实现要点和验证策略}
93
+
94
+ ## 角色提示
95
+ {角色 .md 的内容}
96
+
97
+ ## 任务分解建议
98
+ {从 base.md 提取的分解模式}
99
+
100
+ ## 测试要求(如适用)
101
+ {测试步骤模板 + 报告格式要求}
102
+
103
+ ## 用户补充
104
+ {用户在对话中提到的额外要求}
105
+ ```
106
+
107
+ ### Step 5 — 输出
108
+
109
+ 输出方案文档时,使用以下标记(标记各独占一行):
110
+
111
+ GO_CONTENT_START
112
+ {完整的方案文档 Markdown 内容}
113
+ GO_CONTENT_END
114
+
115
+ 输出标记后,用 1-2 句话简要总结方案要点。
116
+
117
+ ## 工具规范
118
+
119
+ - 扫描/读取:Glob/LS/Read — 扫描 recipes/ 目录
120
+ - 交互:askUserQuestion — 收集用户需求(对话模式)
121
+ - 输出方式:将方案内容放在 GO_CONTENT_START/END 标记中(文本输出),harness 会自动写入 `.claude-coder/go/` 目录
122
+ - 不需要使用 Write/Edit/Bash/MultiEdit 工具
123
+
124
+ ## 注意事项
125
+
126
+ - 食谱目录位于用户项目的 `.claude-coder/recipes/`,由 `claude-coder init` 从内置模板部署
127
+ - 用户可修改或新增食谱文件,下次扫描会自动发现
128
+ - manifest.json 的 defaults 字段表示推荐默认值
129
+ - 组件的 default: true 表示该组件在此领域中常用
130
+ - _shared/ 目录包含跨领域共享的角色和测试模板
@@ -19,6 +19,24 @@
19
19
  }
20
20
  }
21
21
  },
22
+ {
23
+ "name": "frontend-component",
24
+ "matcher": "^(Edit|MultiEdit|Write)$",
25
+ "condition": {
26
+ "any": [
27
+ { "field": "tool_input.file_path", "pattern": "\\.(tsx|jsx|vue|svelte)$" },
28
+ { "field": "tool_input.path", "pattern": "\\.(tsx|jsx|vue|svelte)$" }
29
+ ]
30
+ },
31
+ "toolTips": {
32
+ "injectOnce": false,
33
+ "items": {
34
+ "Edit": "编辑前端组件前,先 Grep 搜索项目中同类组件的写法,保持代码风格一致。",
35
+ "Write": "新建组件时,遵循项目现有的目录结构和命名规范(如 PascalCase 组件名)。",
36
+ "MultiEdit": "批量编辑组件时,确认所有改动保持一致的代码风格。"
37
+ }
38
+ }
39
+ },
22
40
  {
23
41
  "name": "bash-process",
24
42
  "matcher": "Bash",