claude-coder 1.0.5 → 1.0.8
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/bin/cli.js +2 -14
- package/docs/ARCHITECTURE.md +19 -12
- package/docs/README.en.md +1 -1
- package/package.json +1 -1
- package/src/config.js +6 -1
- package/src/indicator.js +18 -7
- package/src/prompts.js +37 -16
- package/src/runner.js +3 -14
- package/src/scanner.js +32 -1
- package/src/session.js +6 -39
- package/templates/CLAUDE.md +5 -3
package/bin/cli.js
CHANGED
|
@@ -7,7 +7,6 @@ const COMMANDS = {
|
|
|
7
7
|
run: { desc: '自动编码循环', usage: 'claude-coder run [需求] [--max N] [--pause N] [--dry-run]' },
|
|
8
8
|
setup: { desc: '交互式模型配置', usage: 'claude-coder setup' },
|
|
9
9
|
init: { desc: '初始化项目环境', usage: 'claude-coder init' },
|
|
10
|
-
view: { desc: '观测模式(交互式单次)', usage: 'claude-coder view [需求]' },
|
|
11
10
|
add: { desc: '追加任务到 tasks.json', usage: 'claude-coder add "指令"' },
|
|
12
11
|
validate: { desc: '手动校验上次 session', usage: 'claude-coder validate' },
|
|
13
12
|
status: { desc: '查看任务进度和成本', usage: 'claude-coder status' },
|
|
@@ -24,6 +23,7 @@ function showHelp() {
|
|
|
24
23
|
console.log('\n示例:');
|
|
25
24
|
console.log(' claude-coder setup 配置模型和 API Key');
|
|
26
25
|
console.log(' claude-coder run "实现用户登录" 开始自动编码');
|
|
26
|
+
console.log(' claude-coder run --max 1 单次执行(替代旧 view 模式)');
|
|
27
27
|
console.log(' claude-coder run --max 5 --dry-run 预览模式');
|
|
28
28
|
console.log(' claude-coder status 查看进度和成本');
|
|
29
29
|
console.log(`\n前置条件: npm install -g @anthropic-ai/claude-agent-sdk`);
|
|
@@ -46,9 +46,6 @@ function parseArgs(argv) {
|
|
|
46
46
|
case '--dry-run':
|
|
47
47
|
opts.dryRun = true;
|
|
48
48
|
break;
|
|
49
|
-
case '--view':
|
|
50
|
-
opts.viewMode = true;
|
|
51
|
-
break;
|
|
52
49
|
case '--help':
|
|
53
50
|
case '-h':
|
|
54
51
|
showHelp();
|
|
@@ -81,11 +78,7 @@ async function main() {
|
|
|
81
78
|
switch (command) {
|
|
82
79
|
case 'run': {
|
|
83
80
|
const runner = require('../src/runner');
|
|
84
|
-
|
|
85
|
-
await runner.view(positional[0] || null, opts);
|
|
86
|
-
} else {
|
|
87
|
-
await runner.run(positional[0] || null, opts);
|
|
88
|
-
}
|
|
81
|
+
await runner.run(positional[0] || null, opts);
|
|
89
82
|
break;
|
|
90
83
|
}
|
|
91
84
|
case 'setup': {
|
|
@@ -98,11 +91,6 @@ async function main() {
|
|
|
98
91
|
await init();
|
|
99
92
|
break;
|
|
100
93
|
}
|
|
101
|
-
case 'view': {
|
|
102
|
-
const runner = require('../src/runner');
|
|
103
|
-
await runner.view(positional[0] || null, opts);
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
94
|
case 'add': {
|
|
107
95
|
if (!positional[0]) {
|
|
108
96
|
console.error('用法: claude-coder add "任务描述"');
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -60,9 +60,6 @@ flowchart TB
|
|
|
60
60
|
flowchart LR
|
|
61
61
|
start(["claude-coder run ..."]) --> mode{模式?}
|
|
62
62
|
|
|
63
|
-
mode -->|view| view["runViewSession()"]
|
|
64
|
-
view --> exit_view([exit 0])
|
|
65
|
-
|
|
66
63
|
mode -->|"add 指令"| add["runAddSession()"]
|
|
67
64
|
add --> exit_add([exit 0])
|
|
68
65
|
|
|
@@ -165,7 +162,6 @@ flowchart TB
|
|
|
165
162
|
coding_p["buildCodingPrompt()<br/>编码 session prompt"]
|
|
166
163
|
task_g["buildTaskGuide()<br/>任务分解指导"]
|
|
167
164
|
scan_p["buildScanPrompt()<br/>扫描 session prompt"]
|
|
168
|
-
view_p["buildViewPrompt()"]
|
|
169
165
|
add_p["buildAddPrompt()"]
|
|
170
166
|
end
|
|
171
167
|
|
|
@@ -180,7 +176,6 @@ flowchart TB
|
|
|
180
176
|
task_g --> scan_p
|
|
181
177
|
task_g --> add_p
|
|
182
178
|
scan_p --> query
|
|
183
|
-
view_p --> query
|
|
184
179
|
add_p --> query
|
|
185
180
|
```
|
|
186
181
|
|
|
@@ -188,23 +183,24 @@ flowchart TB
|
|
|
188
183
|
|
|
189
184
|
| Session 类型 | systemPrompt | user prompt | 触发条件 |
|
|
190
185
|
|---|---|---|---|
|
|
191
|
-
| **编码** | CLAUDE.md | `buildCodingPrompt()` +
|
|
192
|
-
| **扫描** | CLAUDE.md + SCAN_PROTOCOL.md | `buildScanPrompt()` + 任务分解指导 | 首次运行 |
|
|
193
|
-
| **观测** | CLAUDE.md (± SCAN_PROTOCOL.md) | `buildViewPrompt()` | `claude-coder view` |
|
|
186
|
+
| **编码** | CLAUDE.md | `buildCodingPrompt()` + 10 个条件 hint | 主循环每次迭代 |
|
|
187
|
+
| **扫描** | CLAUDE.md + SCAN_PROTOCOL.md | `buildScanPrompt()` + 任务分解指导 + profile 质量要求 | 首次运行 |
|
|
194
188
|
| **追加** | CLAUDE.md | `buildAddPrompt()` + 任务分解指导 | `claude-coder add` |
|
|
195
189
|
|
|
196
|
-
### 编码 Session 的
|
|
190
|
+
### 编码 Session 的 10 个条件 Hint
|
|
197
191
|
|
|
198
192
|
| # | Hint | 触发条件 | 影响 |
|
|
199
193
|
|---|---|---|---|
|
|
200
194
|
| 1 | `reqSyncHint` | 需求 hash 变化 | Step 1:追加新任务 |
|
|
201
195
|
| 2 | `mcpHint` | MCP_PLAYWRIGHT=true | Step 5:可用 Playwright |
|
|
202
196
|
| 3 | `testHint` | tests.json 有记录 | Step 5:避免重复验证 |
|
|
203
|
-
| 4 | `docsHint` | profile.existing_docs
|
|
197
|
+
| 4 | `docsHint` | profile.existing_docs 非空或 profile 有缺陷 | Step 4:读文档后再编码;profile 缺陷时提示 Agent 在 Step 6 补全 services/docs |
|
|
204
198
|
| 5 | `envHint` | 连续成功且 session>1 | Step 2:跳过 init |
|
|
205
199
|
| 6 | `retryContext` | 上次校验失败 | 全局:避免同样错误 |
|
|
206
|
-
| 7 | `taskHint` | tasks.json 存在且有待办任务 | Step 1:跳过读取 tasks.json,harness 已注入当前任务上下文 |
|
|
200
|
+
| 7 | `taskHint` | tasks.json 存在且有待办任务 | Step 1:跳过读取 tasks.json,harness 已注入当前任务上下文 + .claude-coder/ 路径提示 |
|
|
207
201
|
| 8 | `memoryHint` | session_result.json 存在且有历史记录 | Step 1:跳过读取 session_result.json,harness 已注入上次会话摘要 |
|
|
202
|
+
| 9 | `serviceHint` | 始终注入 | Step 6:单次模式停止服务,连续模式保持服务运行 |
|
|
203
|
+
| 10 | `toolGuidance` | 始终注入 | 全局:工具使用规范(Grep/Glob/Read/LS/MultiEdit/Task 替代 bash 命令),非 Claude 模型必需 |
|
|
208
204
|
|
|
209
205
|
---
|
|
210
206
|
|
|
@@ -272,7 +268,7 @@ sequenceDiagram
|
|
|
272
268
|
| 维度 | 评分 | 说明 |
|
|
273
269
|
|------|------|------|
|
|
274
270
|
| **CLAUDE.md 系统提示** | 8/10 | U 型注意力设计;铁律清晰;状态机和 6 步流程是核心竞争力 |
|
|
275
|
-
| **动态 prompt** |
|
|
271
|
+
| **动态 prompt** | 9/10 | 10 个条件 hint 精准注入,含 task/memory 上下文注入 + 服务管理 + 工具使用指导,减少 Agent 冗余操作 |
|
|
276
272
|
| **SCAN_PROTOCOL.md** | 8.5/10 | 新旧项目分支完整,profile 格式全面 |
|
|
277
273
|
| **tests.json 设计** | 7.5/10 | 精简字段,核心目的(防反复测试)明确 |
|
|
278
274
|
| **注入时机** | 9/10 | 静态规则 vs 动态上下文分离干净 |
|
|
@@ -429,3 +425,14 @@ query({
|
|
|
429
425
|
5. **跨平台**:纯 Node.js + `child_process` 调用 git,无平台特定脚本
|
|
430
426
|
6. **运行时隔离**:每个项目的 `.claude-coder/` 独立,不同项目互不干扰
|
|
431
427
|
7. **Prompt 架构分离**:静态规则在 `templates/`,动态上下文在 `src/prompts.js`
|
|
428
|
+
8. **文档即上下文**:文档在 harness 中分两层角色——Blueprint(`project_profile.json`,给 harness 的结构化元数据)和 Context Docs(`docs/ARCHITECTURE.md` 等,给 Agent 的人类可读文档)。Harness 通过 Hint 6 动态提醒 Agent 读取相关文档,并在 profile 有缺陷时提示补全
|
|
429
|
+
|
|
430
|
+
### 文档架构的学术依据
|
|
431
|
+
|
|
432
|
+
| 来源 | 核心概念 | 本项目映射 |
|
|
433
|
+
|------|----------|-----------|
|
|
434
|
+
| DeepCode (arXiv 2512.07921) | Blueprint Distillation — 源文档压缩为结构化蓝图 | `project_profile.json` 是项目蓝图 |
|
|
435
|
+
| CodeMem (arXiv 2512.15813) | Procedural Memory — 验证过的逻辑持久化为可索引技能库 | 架构文档记录"决策"供 Agent 按需检索 |
|
|
436
|
+
| Anthropic Memory Tool | Just-in-time 检索 — 按需从文件系统拉取 | Hint 6 按需提示 Agent 读文档 |
|
|
437
|
+
| ContextBench (arXiv 2602.05892) | 复杂脚手架边际收益递减 | 不过度设计文档体系,关键信息必须准确 |
|
|
438
|
+
| LangChain Harness Engineering | Build-Verify + Context on behalf of Agent | Harness 准备上下文,Agent 专注编码 |
|
package/docs/README.en.md
CHANGED
|
@@ -50,9 +50,9 @@ Each session, the agent autonomously follows 6 steps: restore context → env ch
|
|
|
50
50
|
|---------|-------------|
|
|
51
51
|
| `claude-coder setup` | Interactive model configuration |
|
|
52
52
|
| `claude-coder run [requirement]` | Auto-coding loop |
|
|
53
|
+
| `claude-coder run --max 1` | Single session (replaces old view mode) |
|
|
53
54
|
| `claude-coder run --dry-run` | Preview mode |
|
|
54
55
|
| `claude-coder init` | Initialize project environment |
|
|
55
|
-
| `claude-coder view [requirement]` | Observation mode (single session) |
|
|
56
56
|
| `claude-coder add "instruction"` | Append tasks |
|
|
57
57
|
| `claude-coder validate` | Manually validate last session |
|
|
58
58
|
| `claude-coder status` | View progress and costs |
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -148,7 +148,12 @@ function buildEnvVars(config) {
|
|
|
148
148
|
// --------------- Allowed tools ---------------
|
|
149
149
|
|
|
150
150
|
function getAllowedTools(config) {
|
|
151
|
-
const tools = [
|
|
151
|
+
const tools = [
|
|
152
|
+
'Read', 'Edit', 'MultiEdit', 'Write',
|
|
153
|
+
'Bash', 'Glob', 'Grep', 'LS',
|
|
154
|
+
'Task',
|
|
155
|
+
'WebSearch', 'WebFetch',
|
|
156
|
+
];
|
|
152
157
|
if (config.mcpPlaywright) tools.push('mcp__playwright__*');
|
|
153
158
|
return tools;
|
|
154
159
|
}
|
package/src/indicator.js
CHANGED
|
@@ -58,24 +58,29 @@ class Indicator {
|
|
|
58
58
|
try { fs.writeFileSync(paths().stepFile, this.step, 'utf8'); } catch { /* ignore */ }
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
getStatusLine() {
|
|
62
62
|
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
|
|
63
63
|
const mm = String(Math.floor(elapsed / 60)).padStart(2, '0');
|
|
64
64
|
const ss = String(elapsed % 60).padStart(2, '0');
|
|
65
65
|
const spinner = SPINNERS[this.spinnerIndex % SPINNERS.length];
|
|
66
|
-
this.spinnerIndex++;
|
|
67
66
|
|
|
68
67
|
const phaseLabel = this.phase === 'thinking'
|
|
69
68
|
? `${COLOR.yellow}思考中${COLOR.reset}`
|
|
70
69
|
: `${COLOR.green}编码中${COLOR.reset}`;
|
|
71
70
|
|
|
72
|
-
let line =
|
|
71
|
+
let line = `${spinner} [Session ${this.sessionNum}] ${phaseLabel} ${mm}:${ss}`;
|
|
73
72
|
if (this.step) line += ` | ${this.step}`;
|
|
73
|
+
return line;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_render() {
|
|
77
|
+
this.spinnerIndex++;
|
|
78
|
+
const line = this.getStatusLine();
|
|
74
79
|
|
|
75
80
|
const maxWidth = process.stderr.columns || 80;
|
|
76
|
-
|
|
81
|
+
const truncated = line.length > maxWidth + 20 ? line.slice(0, maxWidth + 20) : line;
|
|
77
82
|
|
|
78
|
-
process.stderr.write(`\r\x1b[K${
|
|
83
|
+
process.stderr.write(`\r\x1b[K${truncated}`);
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -83,7 +88,7 @@ class Indicator {
|
|
|
83
88
|
function inferPhaseStep(indicator, toolName, toolInput) {
|
|
84
89
|
const name = (toolName || '').toLowerCase();
|
|
85
90
|
|
|
86
|
-
if (name === 'write' || name === 'edit' || name === 'str_replace_editor' || name === 'strreplace') {
|
|
91
|
+
if (name === 'write' || name === 'edit' || name === 'multiedit' || name === 'str_replace_editor' || name === 'strreplace') {
|
|
87
92
|
indicator.updatePhase('coding');
|
|
88
93
|
} else if (name === 'bash' || name === 'shell') {
|
|
89
94
|
const cmd = typeof toolInput === 'object' ? (toolInput.command || '') : String(toolInput || '');
|
|
@@ -97,9 +102,15 @@ function inferPhaseStep(indicator, toolName, toolInput) {
|
|
|
97
102
|
} else {
|
|
98
103
|
indicator.updatePhase('coding');
|
|
99
104
|
}
|
|
100
|
-
} else if (name === 'read' || name === 'glob' || name === 'grep') {
|
|
105
|
+
} else if (name === 'read' || name === 'glob' || name === 'grep' || name === 'ls') {
|
|
101
106
|
indicator.updatePhase('thinking');
|
|
102
107
|
indicator.updateStep('读取文件');
|
|
108
|
+
} else if (name === 'task') {
|
|
109
|
+
indicator.updatePhase('thinking');
|
|
110
|
+
indicator.updateStep('子 Agent 搜索');
|
|
111
|
+
} else if (name === 'websearch' || name === 'webfetch') {
|
|
112
|
+
indicator.updatePhase('thinking');
|
|
113
|
+
indicator.updateStep('查阅文档');
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
const summary = typeof toolInput === 'object'
|
package/src/prompts.js
CHANGED
|
@@ -78,7 +78,7 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
78
78
|
} catch { /* ignore */ }
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// Hint 6: Project documentation awareness
|
|
81
|
+
// Hint 6: Project documentation awareness + profile quality check
|
|
82
82
|
let docsHint = '';
|
|
83
83
|
if (fs.existsSync(p.profile)) {
|
|
84
84
|
try {
|
|
@@ -87,6 +87,13 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
87
87
|
if (docs.length > 0) {
|
|
88
88
|
docsHint = `项目文档: ${docs.join(', ')}。Step 4 编码前先读与任务相关的文档,了解接口约定和编码规范。完成后若新增了模块或 API,更新对应文档。`;
|
|
89
89
|
}
|
|
90
|
+
if (profile.tech_stack?.backend?.framework &&
|
|
91
|
+
(!profile.services || profile.services.length === 0)) {
|
|
92
|
+
docsHint += ' 注意:project_profile.json 的 services 为空,请在本次 session 末尾补全 services 数组(command, port, health_check)。';
|
|
93
|
+
}
|
|
94
|
+
if (!docs.length) {
|
|
95
|
+
docsHint += ' 注意:project_profile.json 的 existing_docs 为空,请在 Step 6 收尾时补全文档列表。';
|
|
96
|
+
}
|
|
90
97
|
} catch { /* ignore */ }
|
|
91
98
|
}
|
|
92
99
|
|
|
@@ -101,6 +108,7 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
101
108
|
taskHint = `任务上下文: ${next.id} "${next.description}" (${next.status}), ` +
|
|
102
109
|
`category=${next.category}, steps=${next.steps.length}步。` +
|
|
103
110
|
`进度: ${stats.done}/${stats.total} done, ${stats.failed} failed。` +
|
|
111
|
+
`运行时目录: .claude-coder/(隐藏目录,ls -a 可见,所有 tasks.json/profile 等文件均在此目录下)。` +
|
|
104
112
|
`第一步无需读取 tasks.json(已注入),直接确认任务后进入 Step 2。`;
|
|
105
113
|
}
|
|
106
114
|
}
|
|
@@ -119,6 +127,25 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
119
127
|
} catch { /* ignore */ }
|
|
120
128
|
}
|
|
121
129
|
|
|
130
|
+
// Hint 9: Service management (continuous vs single-shot mode)
|
|
131
|
+
const maxSessions = opts.maxSessions || 50;
|
|
132
|
+
const serviceHint = maxSessions === 1
|
|
133
|
+
? '单次模式:收尾时停止所有后台服务。'
|
|
134
|
+
: '连续模式:收尾时不要停止后台服务,保持服务运行以便下个 session 继续使用。';
|
|
135
|
+
|
|
136
|
+
// Hint 10: Tool usage guidance (critical for non-Claude models)
|
|
137
|
+
const toolGuidance = [
|
|
138
|
+
'可用工具与使用规范(严格遵守):',
|
|
139
|
+
'- 搜索文件名: Glob(如 **/*.ts),禁止 bash find',
|
|
140
|
+
'- 搜索文件内容: Grep(正则,基于 ripgrep),禁止 bash grep',
|
|
141
|
+
'- 读文件: Read(支持批量多文件同时读取),禁止 bash cat/head/tail',
|
|
142
|
+
'- 列目录: LS,禁止 bash ls',
|
|
143
|
+
'- 编辑文件: 同一文件多处修改用 MultiEdit(一次原子调用),单处用 Edit',
|
|
144
|
+
'- 复杂搜索: Task(启动子 Agent 并行搜索,不消耗主 context),适合开放式探索',
|
|
145
|
+
'- 查文档/API: WebSearch + WebFetch',
|
|
146
|
+
'- 效率: 多个 Read/Glob/Grep 尽量合并为一次批量调用,减少工具轮次',
|
|
147
|
+
].join('\n');
|
|
148
|
+
|
|
122
149
|
return [
|
|
123
150
|
`Session ${sessionNum}。执行 6 步流程。`,
|
|
124
151
|
'效率要求:先规划后编码,完成全部编码后再统一测试,禁止编码-测试反复跳转。后端任务用 curl 验证,不启动浏览器。',
|
|
@@ -129,6 +156,8 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
129
156
|
envHint,
|
|
130
157
|
taskHint,
|
|
131
158
|
memoryHint,
|
|
159
|
+
serviceHint,
|
|
160
|
+
toolGuidance,
|
|
132
161
|
`完成后写入 session_result.json。${retryContext}`,
|
|
133
162
|
].filter(Boolean).join('\n');
|
|
134
163
|
}
|
|
@@ -176,6 +205,13 @@ function buildScanPrompt(projectType, requirement) {
|
|
|
176
205
|
`用户需求: ${requirement || '(无指定需求)'}`,
|
|
177
206
|
'',
|
|
178
207
|
'步骤 1-2:按「项目扫描协议」扫描项目、生成 project_profile.json。',
|
|
208
|
+
'',
|
|
209
|
+
'profile 质量要求(必须遵守,harness 会校验):',
|
|
210
|
+
'- services 数组必须包含所有可启动服务(command、port、health_check),不得为空',
|
|
211
|
+
'- existing_docs 必须列出所有实际存在的文档路径',
|
|
212
|
+
'- 前后端分离项目必须生成 docs/ARCHITECTURE.md(模块职责、数据流、API 路由),并加入 existing_docs',
|
|
213
|
+
'- scan_files_checked 必须列出所有实际扫描过的文件',
|
|
214
|
+
'',
|
|
179
215
|
'步骤 3:根据以下指导分解任务到 tasks.json(格式见 CLAUDE.md):',
|
|
180
216
|
'',
|
|
181
217
|
taskGuide,
|
|
@@ -184,20 +220,6 @@ function buildScanPrompt(projectType, requirement) {
|
|
|
184
220
|
].join('\n');
|
|
185
221
|
}
|
|
186
222
|
|
|
187
|
-
/**
|
|
188
|
-
* Build user prompt for view sessions.
|
|
189
|
-
* @param {Object} opts - { needsScan, projectType, requirement, allDone }
|
|
190
|
-
*/
|
|
191
|
-
function buildViewPrompt(opts = {}) {
|
|
192
|
-
if (opts.needsScan) {
|
|
193
|
-
return `你是项目初始化 Agent。项目类型: ${opts.projectType}。用户需求: ${opts.requirement || ''}。按照「项目扫描协议」执行。`;
|
|
194
|
-
}
|
|
195
|
-
if (opts.allDone) {
|
|
196
|
-
return '所有任务已完成,无需执行 6 步流程。直接与用户对话,按需回答问题或执行临时请求。';
|
|
197
|
-
}
|
|
198
|
-
return '执行 6 步流程,完成下一个任务。';
|
|
199
|
-
}
|
|
200
|
-
|
|
201
223
|
/**
|
|
202
224
|
* Build user prompt for add sessions.
|
|
203
225
|
*/
|
|
@@ -226,6 +248,5 @@ module.exports = {
|
|
|
226
248
|
buildCodingPrompt,
|
|
227
249
|
buildTaskGuide,
|
|
228
250
|
buildScanPrompt,
|
|
229
|
-
buildViewPrompt,
|
|
230
251
|
buildAddPrompt,
|
|
231
252
|
};
|
package/src/runner.js
CHANGED
|
@@ -8,7 +8,7 @@ const { paths, log, COLOR, loadConfig, ensureLoopDir, getProjectRoot, getRequire
|
|
|
8
8
|
const { loadTasks, saveTasks, getFeatures, getStats, findNextTask } = require('./tasks');
|
|
9
9
|
const { validate } = require('./validator');
|
|
10
10
|
const { scan } = require('./scanner');
|
|
11
|
-
const { runCodingSession,
|
|
11
|
+
const { runCodingSession, runAddSession } = require('./session');
|
|
12
12
|
|
|
13
13
|
const MAX_RETRY = 3;
|
|
14
14
|
|
|
@@ -269,6 +269,7 @@ async function run(requirement, opts = {}) {
|
|
|
269
269
|
const sessionResult = await runCodingSession(session, {
|
|
270
270
|
projectRoot,
|
|
271
271
|
consecutiveFailures,
|
|
272
|
+
maxSessions,
|
|
272
273
|
lastValidateLog: consecutiveFailures > 0 ? '上次校验失败' : '',
|
|
273
274
|
});
|
|
274
275
|
|
|
@@ -338,18 +339,6 @@ async function run(requirement, opts = {}) {
|
|
|
338
339
|
printStats();
|
|
339
340
|
}
|
|
340
341
|
|
|
341
|
-
async function view(requirement, opts = {}) {
|
|
342
|
-
await requireSdk();
|
|
343
|
-
const projectRoot = getProjectRoot();
|
|
344
|
-
ensureLoopDir();
|
|
345
|
-
|
|
346
|
-
log('info', '观测模式:交互式运行,实时显示工具调用和决策过程');
|
|
347
|
-
log('info', '退出:Ctrl+C');
|
|
348
|
-
console.log('--------------------------------------------');
|
|
349
|
-
|
|
350
|
-
await runViewSession(requirement, { projectRoot, ...opts });
|
|
351
|
-
}
|
|
352
|
-
|
|
353
342
|
async function add(instruction, opts = {}) {
|
|
354
343
|
await requireSdk();
|
|
355
344
|
const p = paths();
|
|
@@ -365,4 +354,4 @@ async function add(instruction, opts = {}) {
|
|
|
365
354
|
printStats();
|
|
366
355
|
}
|
|
367
356
|
|
|
368
|
-
module.exports = { run,
|
|
357
|
+
module.exports = { run, add };
|
package/src/scanner.js
CHANGED
|
@@ -4,6 +4,33 @@ const fs = require('fs');
|
|
|
4
4
|
const { paths, log, ensureLoopDir } = require('./config');
|
|
5
5
|
const { runScanSession } = require('./session');
|
|
6
6
|
|
|
7
|
+
function validateProfile() {
|
|
8
|
+
const p = paths();
|
|
9
|
+
if (!fs.existsSync(p.profile)) return { valid: false, issues: ['profile 不存在'] };
|
|
10
|
+
|
|
11
|
+
let profile;
|
|
12
|
+
try {
|
|
13
|
+
profile = JSON.parse(fs.readFileSync(p.profile, 'utf8'));
|
|
14
|
+
} catch {
|
|
15
|
+
return { valid: false, issues: ['profile JSON 格式错误'] };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const issues = [];
|
|
19
|
+
|
|
20
|
+
if (!profile.tech_stack?.backend?.framework && !profile.tech_stack?.frontend?.framework) {
|
|
21
|
+
issues.push('tech_stack 缺少 backend 或 frontend 框架');
|
|
22
|
+
}
|
|
23
|
+
if (profile.tech_stack?.backend?.framework &&
|
|
24
|
+
(!profile.services || profile.services.length === 0)) {
|
|
25
|
+
issues.push('有后端框架但 services 为空(缺少启动命令和端口)');
|
|
26
|
+
}
|
|
27
|
+
if (!profile.existing_docs || profile.existing_docs.length === 0) {
|
|
28
|
+
issues.push('existing_docs 为空(至少需要 README.md)');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { valid: issues.length === 0, issues };
|
|
32
|
+
}
|
|
33
|
+
|
|
7
34
|
async function scan(requirement, opts = {}) {
|
|
8
35
|
const p = paths();
|
|
9
36
|
ensureLoopDir();
|
|
@@ -15,6 +42,10 @@ async function scan(requirement, opts = {}) {
|
|
|
15
42
|
const result = await runScanSession(requirement, opts);
|
|
16
43
|
|
|
17
44
|
if (fs.existsSync(p.profile) && fs.existsSync(p.tasksFile)) {
|
|
45
|
+
const profileCheck = validateProfile();
|
|
46
|
+
if (!profileCheck.valid) {
|
|
47
|
+
log('warn', `profile 质量问题: ${profileCheck.issues.join('; ')}`);
|
|
48
|
+
}
|
|
18
49
|
log('ok', '初始化完成');
|
|
19
50
|
return { success: true, cost: result.cost };
|
|
20
51
|
}
|
|
@@ -28,4 +59,4 @@ async function scan(requirement, opts = {}) {
|
|
|
28
59
|
return { success: false, cost: null };
|
|
29
60
|
}
|
|
30
61
|
|
|
31
|
-
module.exports = { scan };
|
|
62
|
+
module.exports = { scan, validateProfile };
|
package/src/session.js
CHANGED
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { paths, loadConfig, buildEnvVars, getAllowedTools, log } = require('./config');
|
|
6
6
|
const { Indicator, inferPhaseStep } = require('./indicator');
|
|
7
|
-
const { buildSystemPrompt, buildCodingPrompt, buildScanPrompt,
|
|
7
|
+
const { buildSystemPrompt, buildCodingPrompt, buildScanPrompt, buildAddPrompt } = require('./prompts');
|
|
8
8
|
|
|
9
9
|
let _sdkModule = null;
|
|
10
10
|
async function loadSDK() {
|
|
@@ -66,7 +66,11 @@ function logMessage(message, logStream, indicator) {
|
|
|
66
66
|
if (message.type === 'assistant' && message.message?.content) {
|
|
67
67
|
for (const block of message.message.content) {
|
|
68
68
|
if (block.type === 'text' && block.text) {
|
|
69
|
-
if (indicator)
|
|
69
|
+
if (indicator) {
|
|
70
|
+
const statusLine = indicator.getStatusLine();
|
|
71
|
+
process.stderr.write('\r\x1b[K');
|
|
72
|
+
if (statusLine) process.stderr.write(statusLine + '\n');
|
|
73
|
+
}
|
|
70
74
|
process.stdout.write(block.text);
|
|
71
75
|
if (logStream) logStream.write(block.text);
|
|
72
76
|
}
|
|
@@ -204,42 +208,6 @@ async function runScanSession(requirement, opts = {}) {
|
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
async function runViewSession(requirement, opts = {}) {
|
|
208
|
-
const sdk = await loadSDK();
|
|
209
|
-
const p = paths();
|
|
210
|
-
const config = loadConfig();
|
|
211
|
-
applyEnvConfig(config);
|
|
212
|
-
|
|
213
|
-
let systemPrompt;
|
|
214
|
-
let prompt;
|
|
215
|
-
|
|
216
|
-
if (!fs.existsSync(p.profile) || !fs.existsSync(p.tasksFile)) {
|
|
217
|
-
systemPrompt = buildSystemPrompt(true);
|
|
218
|
-
const projectType = hasCodeFiles(opts.projectRoot || process.cwd()) ? 'existing' : 'new';
|
|
219
|
-
prompt = buildViewPrompt({ needsScan: true, projectType, requirement });
|
|
220
|
-
} else {
|
|
221
|
-
systemPrompt = buildSystemPrompt(false);
|
|
222
|
-
const { loadTasks, getFeatures } = require('./tasks');
|
|
223
|
-
const data = loadTasks();
|
|
224
|
-
const features = getFeatures(data);
|
|
225
|
-
const allDone = features.length > 0 && features.every(f => f.status === 'done');
|
|
226
|
-
prompt = buildViewPrompt({ allDone });
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
const queryOpts = buildQueryOptions(config, opts);
|
|
231
|
-
queryOpts.systemPrompt = systemPrompt;
|
|
232
|
-
|
|
233
|
-
const session = sdk.query({ prompt, options: queryOpts });
|
|
234
|
-
|
|
235
|
-
for await (const message of session) {
|
|
236
|
-
logMessage(message, null);
|
|
237
|
-
}
|
|
238
|
-
} catch (err) {
|
|
239
|
-
log('error', `观测模式错误: ${err.message}`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
211
|
async function runAddSession(instruction, opts = {}) {
|
|
244
212
|
const sdk = await loadSDK();
|
|
245
213
|
const config = loadConfig();
|
|
@@ -289,7 +257,6 @@ function hasCodeFiles(projectRoot) {
|
|
|
289
257
|
module.exports = {
|
|
290
258
|
runCodingSession,
|
|
291
259
|
runScanSession,
|
|
292
|
-
runViewSession,
|
|
293
260
|
runAddSession,
|
|
294
261
|
hasCodeFiles,
|
|
295
262
|
};
|
package/templates/CLAUDE.md
CHANGED
|
@@ -210,7 +210,8 @@ pending ──→ in_progress ──→ testing ──→ done
|
|
|
210
210
|
- 确认方案完整后,**一次性**完成所有编码
|
|
211
211
|
- **禁止边写边试**:完成全部编码后再进入第五步统一测试
|
|
212
212
|
4. **高效执行**:禁止碎片化操作(读一个文件、思考、再读一个),批量读取、批量修改、减少工具调用轮次
|
|
213
|
-
5.
|
|
213
|
+
5. **工具优先**:用 Grep/Glob 替代 bash grep/find,用 Read/LS 替代 bash cat/ls,同一文件多处修改用 MultiEdit
|
|
214
|
+
6. **跳过已完成的步骤**:文件已存在且内容正确的步骤直接跳过
|
|
214
215
|
|
|
215
216
|
### 第五步:测试验证
|
|
216
217
|
|
|
@@ -241,10 +242,11 @@ pending ──→ in_progress ──→ testing ──→ done
|
|
|
241
242
|
|
|
242
243
|
### 第六步:收尾(每次会话必须执行)
|
|
243
244
|
|
|
244
|
-
1.
|
|
245
|
-
2.
|
|
245
|
+
1. **后台服务管理**:根据 prompt 提示决定——单次模式(`--max 1`)时停止所有后台服务(`lsof -ti :端口 | xargs kill`);连续模式时保持服务运行,下个 session 继续使用
|
|
246
|
+
2. **按需更新文档和 profile**:
|
|
246
247
|
- **README / 用户文档**:仅当对外行为变化(新增功能、API 变更、使用方式变化)时更新
|
|
247
248
|
- **架构 / API 文档**:如果本次新增了模块、改变了模块职责或新增了 API 端点,更新 `existing_docs` 中对应的架构或 API 文档。同时更新 `project_profile.json` 的 `existing_docs` 列表(若新增了文档文件)
|
|
249
|
+
- **profile 补全**:如果 prompt 中提示 `project_profile.json` 有缺陷(如 services 为空、existing_docs 为空),在此步骤补全。Harness 依赖 profile 做环境初始化和上下文注入
|
|
248
250
|
3. **Git 提交**:`git add -A && git commit -m "feat(task-id): 功能描述"`
|
|
249
251
|
4. **写入 session_result.json**(notes 要充分记录上下文供下次恢复):
|
|
250
252
|
```json
|