claude-coder 1.0.5 → 1.0.6

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 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
- if (opts.viewMode) {
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 "任务描述"');
@@ -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
 
@@ -189,8 +184,7 @@ flowchart TB
189
184
  | Session 类型 | systemPrompt | user prompt | 触发条件 |
190
185
  |---|---|---|---|
191
186
  | **编码** | CLAUDE.md | `buildCodingPrompt()` + 8 个条件 hint | 主循环每次迭代 |
192
- | **扫描** | CLAUDE.md + SCAN_PROTOCOL.md | `buildScanPrompt()` + 任务分解指导 | 首次运行 |
193
- | **观测** | CLAUDE.md (± SCAN_PROTOCOL.md) | `buildViewPrompt()` | `claude-coder view` |
187
+ | **扫描** | CLAUDE.md + SCAN_PROTOCOL.md | `buildScanPrompt()` + 任务分解指导 + profile 质量要求 | 首次运行 |
194
188
  | **追加** | CLAUDE.md | `buildAddPrompt()` + 任务分解指导 | `claude-coder add` |
195
189
 
196
190
  ### 编码 Session 的 8 个条件 Hint
@@ -200,7 +194,7 @@ flowchart TB
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 非空 | Step 4:读文档后再编码,完成后更新文档 |
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
200
  | 7 | `taskHint` | tasks.json 存在且有待办任务 | Step 1:跳过读取 tasks.json,harness 已注入当前任务上下文 |
@@ -429,3 +423,14 @@ query({
429
423
  5. **跨平台**:纯 Node.js + `child_process` 调用 git,无平台特定脚本
430
424
  6. **运行时隔离**:每个项目的 `.claude-coder/` 独立,不同项目互不干扰
431
425
  7. **Prompt 架构分离**:静态规则在 `templates/`,动态上下文在 `src/prompts.js`
426
+ 8. **文档即上下文**:文档在 harness 中分两层角色——Blueprint(`project_profile.json`,给 harness 的结构化元数据)和 Context Docs(`docs/ARCHITECTURE.md` 等,给 Agent 的人类可读文档)。Harness 通过 Hint 6 动态提醒 Agent 读取相关文档,并在 profile 有缺陷时提示补全
427
+
428
+ ### 文档架构的学术依据
429
+
430
+ | 来源 | 核心概念 | 本项目映射 |
431
+ |------|----------|-----------|
432
+ | DeepCode (arXiv 2512.07921) | Blueprint Distillation — 源文档压缩为结构化蓝图 | `project_profile.json` 是项目蓝图 |
433
+ | CodeMem (arXiv 2512.15813) | Procedural Memory — 验证过的逻辑持久化为可索引技能库 | 架构文档记录"决策"供 Agent 按需检索 |
434
+ | Anthropic Memory Tool | Just-in-time 检索 — 按需从文件系统拉取 | Hint 6 按需提示 Agent 读文档 |
435
+ | ContextBench (arXiv 2602.05892) | 复杂脚手架边际收益递减 | 不过度设计文档体系,关键信息必须准确 |
436
+ | LangChain Harness Engineering | Build-Verify + Context on behalf of Agent | Harness 准备上下文,Agent 专注编码 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-coder",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
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"
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
 
@@ -176,6 +183,13 @@ function buildScanPrompt(projectType, requirement) {
176
183
  `用户需求: ${requirement || '(无指定需求)'}`,
177
184
  '',
178
185
  '步骤 1-2:按「项目扫描协议」扫描项目、生成 project_profile.json。',
186
+ '',
187
+ 'profile 质量要求(必须遵守,harness 会校验):',
188
+ '- services 数组必须包含所有可启动服务(command、port、health_check),不得为空',
189
+ '- existing_docs 必须列出所有实际存在的文档路径',
190
+ '- 前后端分离项目必须生成 docs/ARCHITECTURE.md(模块职责、数据流、API 路由),并加入 existing_docs',
191
+ '- scan_files_checked 必须列出所有实际扫描过的文件',
192
+ '',
179
193
  '步骤 3:根据以下指导分解任务到 tasks.json(格式见 CLAUDE.md):',
180
194
  '',
181
195
  taskGuide,
@@ -184,20 +198,6 @@ function buildScanPrompt(projectType, requirement) {
184
198
  ].join('\n');
185
199
  }
186
200
 
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
201
  /**
202
202
  * Build user prompt for add sessions.
203
203
  */
@@ -226,6 +226,5 @@ module.exports = {
226
226
  buildCodingPrompt,
227
227
  buildTaskGuide,
228
228
  buildScanPrompt,
229
- buildViewPrompt,
230
229
  buildAddPrompt,
231
230
  };
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, runViewSession, runAddSession } = require('./session');
11
+ const { runCodingSession, runAddSession } = require('./session');
12
12
 
13
13
  const MAX_RETRY = 3;
14
14
 
@@ -338,18 +338,6 @@ async function run(requirement, opts = {}) {
338
338
  printStats();
339
339
  }
340
340
 
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
341
  async function add(instruction, opts = {}) {
354
342
  await requireSdk();
355
343
  const p = paths();
@@ -365,4 +353,4 @@ async function add(instruction, opts = {}) {
365
353
  printStats();
366
354
  }
367
355
 
368
- module.exports = { run, view, add };
356
+ 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, buildViewPrompt, buildAddPrompt } = require('./prompts');
7
+ const { buildSystemPrompt, buildCodingPrompt, buildScanPrompt, buildAddPrompt } = require('./prompts');
8
8
 
9
9
  let _sdkModule = null;
10
10
  async function loadSDK() {
@@ -204,42 +204,6 @@ async function runScanSession(requirement, opts = {}) {
204
204
  }
205
205
  }
206
206
 
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
207
  async function runAddSession(instruction, opts = {}) {
244
208
  const sdk = await loadSDK();
245
209
  const config = loadConfig();
@@ -289,7 +253,6 @@ function hasCodeFiles(projectRoot) {
289
253
  module.exports = {
290
254
  runCodingSession,
291
255
  runScanSession,
292
- runViewSession,
293
256
  runAddSession,
294
257
  hasCodeFiles,
295
258
  };
@@ -242,9 +242,10 @@ pending ──→ in_progress ──→ testing ──→ done
242
242
  ### 第六步:收尾(每次会话必须执行)
243
243
 
244
244
  1. **停止本次启动的后台服务**:`lsof -ti :端口 | xargs kill`,避免下次 session 端口冲突
245
- 2. **按需更新文档**:
245
+ 2. **按需更新文档和 profile**:
246
246
  - **README / 用户文档**:仅当对外行为变化(新增功能、API 变更、使用方式变化)时更新
247
247
  - **架构 / API 文档**:如果本次新增了模块、改变了模块职责或新增了 API 端点,更新 `existing_docs` 中对应的架构或 API 文档。同时更新 `project_profile.json` 的 `existing_docs` 列表(若新增了文档文件)
248
+ - **profile 补全**:如果 prompt 中提示 `project_profile.json` 有缺陷(如 services 为空、existing_docs 为空),在此步骤补全。Harness 依赖 profile 做环境初始化和上下文注入
248
249
  3. **Git 提交**:`git add -A && git commit -m "feat(task-id): 功能描述"`
249
250
  4. **写入 session_result.json**(notes 要充分记录上下文供下次恢复):
250
251
  ```json