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 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
 
@@ -188,23 +183,24 @@ flowchart TB
188
183
 
189
184
  | Session 类型 | systemPrompt | user prompt | 触发条件 |
190
185
  |---|---|---|---|
191
- | **编码** | CLAUDE.md | `buildCodingPrompt()` + 8 个条件 hint | 主循环每次迭代 |
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 的 8 个条件 Hint
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 非空 | 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
- | 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** | 8.5/10 | 8 个条件 hint 精准注入,含 task/memory 上下文注入,减少 Agent 冗余 Read 调用 |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-coder",
3
- "version": "1.0.5",
3
+ "version": "1.0.8",
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/config.js CHANGED
@@ -148,7 +148,12 @@ function buildEnvVars(config) {
148
148
  // --------------- Allowed tools ---------------
149
149
 
150
150
  function getAllowedTools(config) {
151
- const tools = ['Read', 'Edit', 'Write', 'Bash', 'Glob', 'Grep'];
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
- _render() {
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 = `\r${spinner} [Session ${this.sessionNum}] ${phaseLabel} ${mm}:${ss}`;
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
- if (line.length > maxWidth + 20) line = line.slice(0, maxWidth + 20);
81
+ const truncated = line.length > maxWidth + 20 ? line.slice(0, maxWidth + 20) : line;
77
82
 
78
- process.stderr.write(`\r\x1b[K${line}`);
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, runViewSession, runAddSession } = require('./session');
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, view, add };
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, buildViewPrompt, buildAddPrompt } = require('./prompts');
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) process.stderr.write('\r\x1b[K');
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
  };
@@ -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. **停止本次启动的后台服务**:`lsof -ti :端口 | xargs kill`,避免下次 session 端口冲突
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