claude-coder 1.5.2 → 1.5.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-coder",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
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/indicator.js CHANGED
@@ -16,6 +16,8 @@ class Indicator {
16
16
  this.lastToolTime = Date.now();
17
17
  this.sessionNum = 0;
18
18
  this.startTime = Date.now();
19
+ this._lastContentKey = '';
20
+ this._lastRenderTime = 0;
19
21
  }
20
22
 
21
23
  start(sessionNum) {
@@ -86,8 +88,16 @@ class Indicator {
86
88
 
87
89
  _render() {
88
90
  this.spinnerIndex++;
89
- const line = this.getStatusLine();
91
+ const contentKey = `${this.phase}|${this.step}|${this.toolTarget}`;
92
+ const now = Date.now();
93
+
94
+ if (contentKey === this._lastContentKey && now - this._lastRenderTime < 5000) {
95
+ return;
96
+ }
97
+ this._lastContentKey = contentKey;
98
+ this._lastRenderTime = now;
90
99
 
100
+ const line = this.getStatusLine();
91
101
  const maxWidth = process.stderr.columns || 80;
92
102
  const truncated = line.length > maxWidth + 20 ? line.slice(0, maxWidth + 20) : line;
93
103
 
@@ -95,41 +105,60 @@ class Indicator {
95
105
  }
96
106
  }
97
107
 
98
- // Phase-signal logic: infer phase/step from tool calls
108
+ function extractFileTarget(toolInput) {
109
+ const raw = typeof toolInput === 'object'
110
+ ? (toolInput.file_path || toolInput.path || '')
111
+ : '';
112
+ if (!raw) return '';
113
+ return raw.split('/').slice(-2).join('/').slice(0, 40);
114
+ }
115
+
116
+ function extractBashLabel(cmd) {
117
+ if (cmd.includes('git ')) return 'Git 操作';
118
+ if (cmd.includes('npm ') || cmd.includes('pip ') || cmd.includes('pnpm ') || cmd.includes('yarn ')) return '安装依赖';
119
+ if (cmd.includes('curl') || cmd.includes('pytest') || cmd.includes('jest') || /\btest\b/.test(cmd)) return '测试验证';
120
+ if (cmd.includes('python ') || cmd.includes('node ')) return '执行脚本';
121
+ return '执行命令';
122
+ }
123
+
124
+ function extractBashTarget(cmd) {
125
+ let clean = cmd.replace(/^(?:cd\s+\S+\s*&&\s*)+/g, '').trim();
126
+ clean = clean.split(/\s*(?:\|{1,2}|;|&&|2>&1|>\s*\/dev\/null)\s*/)[0].trim();
127
+ return clean.slice(0, 40);
128
+ }
129
+
99
130
  function inferPhaseStep(indicator, toolName, toolInput) {
100
131
  const name = (toolName || '').toLowerCase();
101
132
 
102
133
  indicator.lastToolTime = Date.now();
103
134
 
104
- const rawTarget = typeof toolInput === 'object'
105
- ? (toolInput.file_path || toolInput.path || toolInput.command || toolInput.pattern || '')
106
- : String(toolInput || '');
107
- const shortTarget = rawTarget.split('/').slice(-2).join('/').slice(0, 40);
108
- indicator.toolTarget = shortTarget;
109
-
110
135
  if (name === 'write' || name === 'edit' || name === 'multiedit' || name === 'str_replace_editor' || name === 'strreplace') {
111
136
  indicator.updatePhase('coding');
137
+ indicator.updateStep('编辑文件');
138
+ indicator.toolTarget = extractFileTarget(toolInput);
112
139
  } else if (name === 'bash' || name === 'shell') {
113
140
  const cmd = typeof toolInput === 'object' ? (toolInput.command || '') : String(toolInput || '');
114
- if (cmd.includes('git ')) {
115
- indicator.updateStep('Git 操作');
116
- } else if (cmd.includes('npm ') || cmd.includes('pip ') || cmd.includes('pnpm ')) {
117
- indicator.updateStep('安装依赖');
118
- } else if (cmd.includes('test') || cmd.includes('curl') || cmd.includes('pytest')) {
119
- indicator.updateStep('测试验证');
120
- indicator.updatePhase('coding');
121
- } else {
141
+ const label = extractBashLabel(cmd);
142
+ indicator.updateStep(label);
143
+ indicator.toolTarget = extractBashTarget(cmd);
144
+ if (label === '测试验证' || label === '执行脚本' || label === '执行命令') {
122
145
  indicator.updatePhase('coding');
123
146
  }
124
147
  } else if (name === 'read' || name === 'glob' || name === 'grep' || name === 'ls') {
125
148
  indicator.updatePhase('thinking');
126
149
  indicator.updateStep('读取文件');
150
+ indicator.toolTarget = extractFileTarget(toolInput);
127
151
  } else if (name === 'task') {
128
152
  indicator.updatePhase('thinking');
129
153
  indicator.updateStep('子 Agent 搜索');
154
+ indicator.toolTarget = '';
130
155
  } else if (name === 'websearch' || name === 'webfetch') {
131
156
  indicator.updatePhase('thinking');
132
157
  indicator.updateStep('查阅文档');
158
+ indicator.toolTarget = '';
159
+ } else {
160
+ indicator.updateStep('工具调用');
161
+ indicator.toolTarget = '';
133
162
  }
134
163
 
135
164
  let summary;
@@ -137,15 +166,7 @@ function inferPhaseStep(indicator, toolName, toolInput) {
137
166
  const target = toolInput.file_path || toolInput.path || '';
138
167
  const cmd = toolInput.command || '';
139
168
  const pattern = toolInput.pattern || '';
140
- if (target) {
141
- summary = target;
142
- } else if (cmd) {
143
- summary = cmd.slice(0, 200);
144
- } else if (pattern) {
145
- summary = `pattern: ${pattern}`;
146
- } else {
147
- summary = JSON.stringify(toolInput).slice(0, 200);
148
- }
169
+ summary = target || (cmd ? cmd.slice(0, 200) : '') || (pattern ? `pattern: ${pattern}` : JSON.stringify(toolInput).slice(0, 200));
149
170
  } else {
150
171
  summary = String(toolInput || '').slice(0, 200);
151
172
  }
package/src/session.js CHANGED
@@ -73,14 +73,20 @@ function writeSessionSeparator(logStream, sessionNum, label) {
73
73
  logStream.write(`\n${sep}\n[Session ${sessionNum}] ${label} ${new Date().toISOString()}\n${sep}\n`);
74
74
  }
75
75
 
76
+ let _lastPrintedStatusKey = '';
77
+
76
78
  function logMessage(message, logStream, indicator) {
77
79
  if (message.type === 'assistant' && message.message?.content) {
78
80
  for (const block of message.message.content) {
79
81
  if (block.type === 'text' && block.text) {
80
82
  if (indicator) {
81
- const statusLine = indicator.getStatusLine();
82
83
  process.stderr.write('\r\x1b[K');
83
- if (statusLine) process.stderr.write(statusLine + '\n');
84
+ const contentKey = `${indicator.phase}|${indicator.step}|${indicator.toolTarget}`;
85
+ if (contentKey !== _lastPrintedStatusKey) {
86
+ _lastPrintedStatusKey = contentKey;
87
+ const statusLine = indicator.getStatusLine();
88
+ if (statusLine) process.stderr.write(statusLine + '\n');
89
+ }
84
90
  }
85
91
  process.stdout.write(block.text);
86
92
  if (logStream) logStream.write(block.text);