claude-coder 1.6.2 → 1.7.0

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/src/tasks.js CHANGED
@@ -1,172 +1,172 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const { paths, log, COLOR } = require('./config');
5
-
6
- const VALID_STATUSES = ['pending', 'in_progress', 'testing', 'done', 'failed'];
7
-
8
- const TRANSITIONS = {
9
- pending: ['in_progress'],
10
- in_progress: ['testing'],
11
- testing: ['done', 'failed'],
12
- failed: ['in_progress'],
13
- done: [],
14
- };
15
-
16
- function loadTasks() {
17
- const p = paths();
18
- if (!fs.existsSync(p.tasksFile)) return null;
19
- try {
20
- return JSON.parse(fs.readFileSync(p.tasksFile, 'utf8'));
21
- } catch (err) {
22
- log('error', `tasks.json 解析失败: ${err.message}`);
23
- return null;
24
- }
25
- }
26
-
27
- function saveTasks(data) {
28
- const p = paths();
29
- fs.writeFileSync(p.tasksFile, JSON.stringify(data, null, 2) + '\n', 'utf8');
30
- }
31
-
32
- function getFeatures(data) {
33
- return data?.features || [];
34
- }
35
-
36
- function findNextTask(data) {
37
- const features = getFeatures(data);
38
- const failed = features.filter(f => f.status === 'failed')
39
- .sort((a, b) => (a.priority || 999) - (b.priority || 999));
40
- if (failed.length > 0) return failed[0];
41
-
42
- const pending = features.filter(f => f.status === 'pending')
43
- .filter(f => {
44
- const deps = f.depends_on || [];
45
- return deps.every(depId => {
46
- const dep = features.find(x => x.id === depId);
47
- return dep && dep.status === 'done';
48
- });
49
- })
50
- .sort((a, b) => (a.priority || 999) - (b.priority || 999));
51
- if (pending.length > 0) return pending[0];
52
-
53
- const inProgress = features.filter(f => f.status === 'in_progress')
54
- .sort((a, b) => (a.priority || 999) - (b.priority || 999));
55
- return inProgress[0] || null;
56
- }
57
-
58
- function setStatus(data, taskId, newStatus) {
59
- const features = getFeatures(data);
60
- const task = features.find(f => f.id === taskId);
61
- if (!task) throw new Error(`任务不存在: ${taskId}`);
62
- if (!VALID_STATUSES.includes(newStatus)) throw new Error(`无效状态: ${newStatus}`);
63
-
64
- const allowed = TRANSITIONS[task.status];
65
- if (!allowed || !allowed.includes(newStatus)) {
66
- throw new Error(`非法状态迁移: ${task.status} → ${newStatus}`);
67
- }
68
-
69
- task.status = newStatus;
70
- saveTasks(data);
71
- return task;
72
- }
73
-
74
- /**
75
- * Harness-level forced status change (bypasses TRANSITIONS validation).
76
- * Used when harness needs to mark tasks failed after max retries.
77
- */
78
- function forceStatus(data, status) {
79
- const features = getFeatures(data);
80
- for (const f of features) {
81
- if (f.status === 'in_progress') {
82
- f.status = status;
83
- saveTasks(data);
84
- return f;
85
- }
86
- }
87
- return null;
88
- }
89
-
90
- function addTask(data, task) {
91
- if (!data) {
92
- data = { project: '', created_at: new Date().toISOString().slice(0, 10), features: [] };
93
- }
94
- data.features.push(task);
95
- saveTasks(data);
96
- return data;
97
- }
98
-
99
- function getStats(data) {
100
- const features = getFeatures(data);
101
- return {
102
- total: features.length,
103
- done: features.filter(f => f.status === 'done').length,
104
- failed: features.filter(f => f.status === 'failed').length,
105
- in_progress: features.filter(f => f.status === 'in_progress').length,
106
- testing: features.filter(f => f.status === 'testing').length,
107
- pending: features.filter(f => f.status === 'pending').length,
108
- };
109
- }
110
-
111
- function showStatus() {
112
- const p = paths();
113
- const data = loadTasks();
114
- if (!data) {
115
- log('warn', '未找到 .claude-coder/tasks.json,请先运行 claude-coder run');
116
- return;
117
- }
118
-
119
- const stats = getStats(data);
120
- const features = getFeatures(data);
121
-
122
- console.log(`\n${COLOR.blue}═══════════════════════════════════════════════${COLOR.reset}`);
123
- console.log(` ${COLOR.blue}📋 任务状态${COLOR.reset} 项目: ${data.project || '(未命名)'}`);
124
- console.log(`${COLOR.blue}═══════════════════════════════════════════════${COLOR.reset}`);
125
-
126
- const bar = stats.total > 0
127
- ? `[${'█'.repeat(Math.floor(stats.done / stats.total * 30))}${'░'.repeat(30 - Math.floor(stats.done / stats.total * 30))}]`
128
- : '[░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░]';
129
- console.log(` 进度: ${bar} ${stats.done}/${stats.total}`);
130
-
131
- console.log(`\n ${COLOR.green}✔ done: ${stats.done}${COLOR.reset} ${COLOR.yellow}⏳ pending: ${stats.pending}${COLOR.reset} ${COLOR.red}✘ failed: ${stats.failed}${COLOR.reset}`);
132
-
133
- if (stats.in_progress > 0 || stats.testing > 0) {
134
- console.log(` ▸ in_progress: ${stats.in_progress} ▸ testing: ${stats.testing}`);
135
- }
136
-
137
- // Cost summary from progress.json (harness records SDK cost per session)
138
- if (fs.existsSync(p.progressFile)) {
139
- try {
140
- const progress = JSON.parse(fs.readFileSync(p.progressFile, 'utf8'));
141
- const sessions = (progress.sessions || []).filter(s => typeof s.cost === 'number');
142
- if (sessions.length > 0) {
143
- const totalCost = sessions.reduce((sum, s) => sum + s.cost, 0);
144
- console.log(`\n ${COLOR.blue}💰 累计成本${COLOR.reset}: $${totalCost.toFixed(4)} (${sessions.length} sessions)`);
145
- }
146
- } catch { /* ignore parse errors */ }
147
- }
148
-
149
- // Task list
150
- console.log(`\n ${'─'.repeat(45)}`);
151
- for (const f of features) {
152
- const icon = { done: '✔', pending: '○', in_progress: '▸', testing: '⟳', failed: '✘' }[f.status] || '?';
153
- const color = { done: COLOR.green, failed: COLOR.red, in_progress: COLOR.blue, testing: COLOR.yellow }[f.status] || '';
154
- console.log(` ${color}${icon}${COLOR.reset} [${f.id}] ${f.description} (${f.status})`);
155
- }
156
-
157
- console.log(`${COLOR.blue}═══════════════════════════════════════════════${COLOR.reset}\n`);
158
- }
159
-
160
- module.exports = {
161
- VALID_STATUSES,
162
- TRANSITIONS,
163
- loadTasks,
164
- saveTasks,
165
- getFeatures,
166
- findNextTask,
167
- setStatus,
168
- forceStatus,
169
- addTask,
170
- getStats,
171
- showStatus,
172
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const { paths, log, COLOR } = require('./config');
5
+
6
+ const VALID_STATUSES = ['pending', 'in_progress', 'testing', 'done', 'failed'];
7
+
8
+ const TRANSITIONS = {
9
+ pending: ['in_progress'],
10
+ in_progress: ['testing'],
11
+ testing: ['done', 'failed'],
12
+ failed: ['in_progress'],
13
+ done: [],
14
+ };
15
+
16
+ function loadTasks() {
17
+ const p = paths();
18
+ if (!fs.existsSync(p.tasksFile)) return null;
19
+ try {
20
+ return JSON.parse(fs.readFileSync(p.tasksFile, 'utf8'));
21
+ } catch (err) {
22
+ log('error', `tasks.json 解析失败: ${err.message}`);
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function saveTasks(data) {
28
+ const p = paths();
29
+ fs.writeFileSync(p.tasksFile, JSON.stringify(data, null, 2) + '\n', 'utf8');
30
+ }
31
+
32
+ function getFeatures(data) {
33
+ return data?.features || [];
34
+ }
35
+
36
+ function findNextTask(data) {
37
+ const features = getFeatures(data);
38
+ const failed = features.filter(f => f.status === 'failed')
39
+ .sort((a, b) => (a.priority || 999) - (b.priority || 999));
40
+ if (failed.length > 0) return failed[0];
41
+
42
+ const pending = features.filter(f => f.status === 'pending')
43
+ .filter(f => {
44
+ const deps = f.depends_on || [];
45
+ return deps.every(depId => {
46
+ const dep = features.find(x => x.id === depId);
47
+ return dep && dep.status === 'done';
48
+ });
49
+ })
50
+ .sort((a, b) => (a.priority || 999) - (b.priority || 999));
51
+ if (pending.length > 0) return pending[0];
52
+
53
+ const inProgress = features.filter(f => f.status === 'in_progress')
54
+ .sort((a, b) => (a.priority || 999) - (b.priority || 999));
55
+ return inProgress[0] || null;
56
+ }
57
+
58
+ function setStatus(data, taskId, newStatus) {
59
+ const features = getFeatures(data);
60
+ const task = features.find(f => f.id === taskId);
61
+ if (!task) throw new Error(`任务不存在: ${taskId}`);
62
+ if (!VALID_STATUSES.includes(newStatus)) throw new Error(`无效状态: ${newStatus}`);
63
+
64
+ const allowed = TRANSITIONS[task.status];
65
+ if (!allowed || !allowed.includes(newStatus)) {
66
+ throw new Error(`非法状态迁移: ${task.status} → ${newStatus}`);
67
+ }
68
+
69
+ task.status = newStatus;
70
+ saveTasks(data);
71
+ return task;
72
+ }
73
+
74
+ /**
75
+ * Harness-level forced status change (bypasses TRANSITIONS validation).
76
+ * Used when harness needs to mark tasks failed after max retries.
77
+ */
78
+ function forceStatus(data, status) {
79
+ const features = getFeatures(data);
80
+ for (const f of features) {
81
+ if (f.status === 'in_progress') {
82
+ f.status = status;
83
+ saveTasks(data);
84
+ return f;
85
+ }
86
+ }
87
+ return null;
88
+ }
89
+
90
+ function addTask(data, task) {
91
+ if (!data) {
92
+ data = { project: '', created_at: new Date().toISOString().slice(0, 10), features: [] };
93
+ }
94
+ data.features.push(task);
95
+ saveTasks(data);
96
+ return data;
97
+ }
98
+
99
+ function getStats(data) {
100
+ const features = getFeatures(data);
101
+ return {
102
+ total: features.length,
103
+ done: features.filter(f => f.status === 'done').length,
104
+ failed: features.filter(f => f.status === 'failed').length,
105
+ in_progress: features.filter(f => f.status === 'in_progress').length,
106
+ testing: features.filter(f => f.status === 'testing').length,
107
+ pending: features.filter(f => f.status === 'pending').length,
108
+ };
109
+ }
110
+
111
+ function showStatus() {
112
+ const p = paths();
113
+ const data = loadTasks();
114
+ if (!data) {
115
+ log('warn', '未找到 .claude-coder/tasks.json,请先运行 claude-coder run');
116
+ return;
117
+ }
118
+
119
+ const stats = getStats(data);
120
+ const features = getFeatures(data);
121
+
122
+ console.log(`\n${COLOR.blue}═══════════════════════════════════════════════${COLOR.reset}`);
123
+ console.log(` ${COLOR.blue}📋 任务状态${COLOR.reset} 项目: ${data.project || '(未命名)'}`);
124
+ console.log(`${COLOR.blue}═══════════════════════════════════════════════${COLOR.reset}`);
125
+
126
+ const bar = stats.total > 0
127
+ ? `[${'█'.repeat(Math.floor(stats.done / stats.total * 30))}${'░'.repeat(30 - Math.floor(stats.done / stats.total * 30))}]`
128
+ : '[░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░]';
129
+ console.log(` 进度: ${bar} ${stats.done}/${stats.total}`);
130
+
131
+ console.log(`\n ${COLOR.green}✔ done: ${stats.done}${COLOR.reset} ${COLOR.yellow}⏳ pending: ${stats.pending}${COLOR.reset} ${COLOR.red}✘ failed: ${stats.failed}${COLOR.reset}`);
132
+
133
+ if (stats.in_progress > 0 || stats.testing > 0) {
134
+ console.log(` ▸ in_progress: ${stats.in_progress} ▸ testing: ${stats.testing}`);
135
+ }
136
+
137
+ // Cost summary from progress.json (harness records SDK cost per session)
138
+ if (fs.existsSync(p.progressFile)) {
139
+ try {
140
+ const progress = JSON.parse(fs.readFileSync(p.progressFile, 'utf8'));
141
+ const sessions = (progress.sessions || []).filter(s => typeof s.cost === 'number');
142
+ if (sessions.length > 0) {
143
+ const totalCost = sessions.reduce((sum, s) => sum + s.cost, 0);
144
+ console.log(`\n ${COLOR.blue}💰 累计成本${COLOR.reset}: $${totalCost.toFixed(4)} (${sessions.length} sessions)`);
145
+ }
146
+ } catch { /* ignore parse errors */ }
147
+ }
148
+
149
+ // Task list
150
+ console.log(`\n ${'─'.repeat(45)}`);
151
+ for (const f of features) {
152
+ const icon = { done: '✔', pending: '○', in_progress: '▸', testing: '⟳', failed: '✘' }[f.status] || '?';
153
+ const color = { done: COLOR.green, failed: COLOR.red, in_progress: COLOR.blue, testing: COLOR.yellow }[f.status] || '';
154
+ console.log(` ${color}${icon}${COLOR.reset} [${f.id}] ${f.description} (${f.status})`);
155
+ }
156
+
157
+ console.log(`${COLOR.blue}═══════════════════════════════════════════════${COLOR.reset}\n`);
158
+ }
159
+
160
+ module.exports = {
161
+ VALID_STATUSES,
162
+ TRANSITIONS,
163
+ loadTasks,
164
+ saveTasks,
165
+ getFeatures,
166
+ findNextTask,
167
+ setStatus,
168
+ forceStatus,
169
+ addTask,
170
+ getStats,
171
+ showStatus,
172
+ };