pi-messenger 0.7.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/ARCHITECTURE.md +244 -0
- package/CHANGELOG.md +418 -0
- package/README.md +394 -0
- package/banner.png +0 -0
- package/config-overlay.ts +172 -0
- package/config.ts +178 -0
- package/crew/agents/crew-docs-scout.md +55 -0
- package/crew/agents/crew-gap-analyst.md +105 -0
- package/crew/agents/crew-github-scout.md +111 -0
- package/crew/agents/crew-interview-generator.md +79 -0
- package/crew/agents/crew-plan-sync.md +64 -0
- package/crew/agents/crew-practice-scout.md +62 -0
- package/crew/agents/crew-repo-scout.md +65 -0
- package/crew/agents/crew-reviewer.md +58 -0
- package/crew/agents/crew-web-scout.md +85 -0
- package/crew/agents/crew-worker.md +95 -0
- package/crew/agents.ts +200 -0
- package/crew/handlers/interview.ts +211 -0
- package/crew/handlers/plan.ts +358 -0
- package/crew/handlers/review.ts +341 -0
- package/crew/handlers/status.ts +257 -0
- package/crew/handlers/sync.ts +232 -0
- package/crew/handlers/task.ts +511 -0
- package/crew/handlers/work.ts +289 -0
- package/crew/id-allocator.ts +44 -0
- package/crew/index.ts +229 -0
- package/crew/state.ts +116 -0
- package/crew/store.ts +480 -0
- package/crew/types.ts +164 -0
- package/crew/utils/artifacts.ts +65 -0
- package/crew/utils/config.ts +104 -0
- package/crew/utils/discover.ts +170 -0
- package/crew/utils/install.ts +373 -0
- package/crew/utils/progress.ts +107 -0
- package/crew/utils/result.ts +16 -0
- package/crew/utils/truncate.ts +79 -0
- package/crew-overlay.ts +259 -0
- package/handlers.ts +799 -0
- package/index.ts +591 -0
- package/lib.ts +232 -0
- package/overlay.ts +687 -0
- package/package.json +20 -0
- package/skills/pi-messenger-crew/SKILL.md +140 -0
- package/store.ts +1068 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crew - Task Handlers
|
|
3
|
+
*
|
|
4
|
+
* Operations: create, show, list, start, done, block, unblock, ready, reset
|
|
5
|
+
* Simplified: tasks belong to the plan, not an epic
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
9
|
+
import type { MessengerState, Dirs } from "../../lib.js";
|
|
10
|
+
import type { CrewParams, TaskEvidence } from "../types.js";
|
|
11
|
+
import { result } from "../utils/result.js";
|
|
12
|
+
import * as store from "../store.js";
|
|
13
|
+
|
|
14
|
+
export async function execute(
|
|
15
|
+
op: string,
|
|
16
|
+
params: CrewParams,
|
|
17
|
+
state: MessengerState,
|
|
18
|
+
_dirs: Dirs,
|
|
19
|
+
ctx: ExtensionContext
|
|
20
|
+
) {
|
|
21
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
22
|
+
|
|
23
|
+
switch (op) {
|
|
24
|
+
case "create":
|
|
25
|
+
return taskCreate(cwd, params);
|
|
26
|
+
case "show":
|
|
27
|
+
return taskShow(cwd, params);
|
|
28
|
+
case "list":
|
|
29
|
+
return taskList(cwd);
|
|
30
|
+
case "start":
|
|
31
|
+
return taskStart(cwd, params, state);
|
|
32
|
+
case "done":
|
|
33
|
+
return taskDone(cwd, params);
|
|
34
|
+
case "block":
|
|
35
|
+
return taskBlock(cwd, params);
|
|
36
|
+
case "unblock":
|
|
37
|
+
return taskUnblock(cwd, params);
|
|
38
|
+
case "ready":
|
|
39
|
+
return taskReady(cwd);
|
|
40
|
+
case "reset":
|
|
41
|
+
return taskReset(cwd, params);
|
|
42
|
+
default:
|
|
43
|
+
return result(`Unknown task operation: ${op}`, { mode: "task", error: "unknown_operation", operation: op });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// task.create
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
function taskCreate(cwd: string, params: CrewParams) {
|
|
52
|
+
if (!params.title) {
|
|
53
|
+
return result("Error: title required for task.create", { mode: "task.create", error: "missing_title" });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Verify plan exists
|
|
57
|
+
const plan = store.getPlan(cwd);
|
|
58
|
+
if (!plan) {
|
|
59
|
+
return result("Error: No plan exists. Create one first with pi_messenger({ action: \"plan\" })", {
|
|
60
|
+
mode: "task.create", error: "no_plan"
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Validate dependencies exist
|
|
65
|
+
if (params.dependsOn && params.dependsOn.length > 0) {
|
|
66
|
+
for (const depId of params.dependsOn) {
|
|
67
|
+
const dep = store.getTask(cwd, depId);
|
|
68
|
+
if (!dep) {
|
|
69
|
+
return result(`Error: Dependency ${depId} not found`, { mode: "task.create", error: "dependency_not_found", dependency: depId });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const task = store.createTask(cwd, params.title, params.content, params.dependsOn);
|
|
75
|
+
|
|
76
|
+
const depsText = task.depends_on.length > 0
|
|
77
|
+
? `\n**Depends on:** ${task.depends_on.join(", ")}`
|
|
78
|
+
: "";
|
|
79
|
+
|
|
80
|
+
const text = `✅ Created task **${task.id}**
|
|
81
|
+
|
|
82
|
+
**Title:** ${task.title}
|
|
83
|
+
**Status:** ${task.status}${depsText}
|
|
84
|
+
|
|
85
|
+
Start with: \`pi_messenger({ action: "task.start", id: "${task.id}" })\``;
|
|
86
|
+
|
|
87
|
+
return result(text, {
|
|
88
|
+
mode: "task.create",
|
|
89
|
+
task: {
|
|
90
|
+
id: task.id,
|
|
91
|
+
title: task.title,
|
|
92
|
+
status: task.status,
|
|
93
|
+
depends_on: task.depends_on,
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// task.show
|
|
100
|
+
// =============================================================================
|
|
101
|
+
|
|
102
|
+
function taskShow(cwd: string, params: CrewParams) {
|
|
103
|
+
const id = params.id;
|
|
104
|
+
if (!id) {
|
|
105
|
+
return result("Error: id required for task.show", { mode: "task.show", error: "missing_id" });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const task = store.getTask(cwd, id);
|
|
109
|
+
if (!task) {
|
|
110
|
+
return result(`Error: Task ${id} not found`, { mode: "task.show", error: "not_found", id });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const spec = store.getTaskSpec(cwd, id);
|
|
114
|
+
|
|
115
|
+
// Build status details
|
|
116
|
+
let statusDetails = "";
|
|
117
|
+
switch (task.status) {
|
|
118
|
+
case "in_progress":
|
|
119
|
+
statusDetails = `\n**Assigned to:** ${task.assigned_to ?? "unknown"}\n**Started:** ${task.started_at}`;
|
|
120
|
+
if (task.base_commit) statusDetails += `\n**Base commit:** ${task.base_commit.slice(0, 8)}`;
|
|
121
|
+
break;
|
|
122
|
+
case "done":
|
|
123
|
+
statusDetails = `\n**Completed:** ${task.completed_at}`;
|
|
124
|
+
if (task.summary) statusDetails += `\n**Summary:** ${task.summary}`;
|
|
125
|
+
break;
|
|
126
|
+
case "blocked":
|
|
127
|
+
statusDetails = `\n**Blocked reason:** ${task.blocked_reason ?? "unknown"}`;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const depsText = task.depends_on.length > 0
|
|
132
|
+
? `\n**Depends on:** ${task.depends_on.join(", ")}`
|
|
133
|
+
: "";
|
|
134
|
+
|
|
135
|
+
// Spec preview
|
|
136
|
+
let specPreview = "";
|
|
137
|
+
if (spec && !spec.includes("*Spec pending*")) {
|
|
138
|
+
const truncated = spec.length > 800 ? spec.slice(0, 800) + "..." : spec;
|
|
139
|
+
specPreview = `\n\n## Spec\n\`\`\`\n${truncated}\n\`\`\``;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const statusIcon = {
|
|
143
|
+
todo: "⬜",
|
|
144
|
+
in_progress: "🔄",
|
|
145
|
+
done: "✅",
|
|
146
|
+
blocked: "🚫",
|
|
147
|
+
}[task.status];
|
|
148
|
+
|
|
149
|
+
const text = `# Task ${task.id}: ${task.title}
|
|
150
|
+
|
|
151
|
+
${statusIcon} **Status:** ${task.status}${statusDetails}
|
|
152
|
+
**Attempts:** ${task.attempt_count}${depsText}${specPreview}`;
|
|
153
|
+
|
|
154
|
+
return result(text, {
|
|
155
|
+
mode: "task.show",
|
|
156
|
+
task,
|
|
157
|
+
hasSpec: spec && !spec.includes("*Spec pending*"),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// =============================================================================
|
|
162
|
+
// task.list
|
|
163
|
+
// =============================================================================
|
|
164
|
+
|
|
165
|
+
function taskList(cwd: string) {
|
|
166
|
+
const plan = store.getPlan(cwd);
|
|
167
|
+
if (!plan) {
|
|
168
|
+
return result("No plan found. Create one with: pi_messenger({ action: \"plan\" })", {
|
|
169
|
+
mode: "task.list", tasks: [], hasPlan: false
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const tasks = store.getTasks(cwd);
|
|
174
|
+
if (tasks.length === 0) {
|
|
175
|
+
return result(`No tasks in plan. Create with: \`pi_messenger({ action: "task.create", title: "..." })\``, {
|
|
176
|
+
mode: "task.list",
|
|
177
|
+
tasks: [],
|
|
178
|
+
prd: plan.prd,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const lines: string[] = [`# Tasks for ${plan.prd}\n`];
|
|
183
|
+
|
|
184
|
+
for (const task of tasks) {
|
|
185
|
+
const icon = { todo: "⬜", in_progress: "🔄", done: "✅", blocked: "🚫" }[task.status];
|
|
186
|
+
const deps = task.depends_on.length > 0 ? ` → deps: ${task.depends_on.join(", ")}` : "";
|
|
187
|
+
const assignee = task.assigned_to ? ` [${task.assigned_to}]` : "";
|
|
188
|
+
lines.push(`${icon} **${task.id}**: ${task.title}${assignee}${deps}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const done = tasks.filter(t => t.status === "done").length;
|
|
192
|
+
lines.push(`\n**Progress:** ${done}/${tasks.length}`);
|
|
193
|
+
|
|
194
|
+
return result(lines.join("\n"), {
|
|
195
|
+
mode: "task.list",
|
|
196
|
+
prd: plan.prd,
|
|
197
|
+
tasks: tasks.map(t => ({
|
|
198
|
+
id: t.id,
|
|
199
|
+
title: t.title,
|
|
200
|
+
status: t.status,
|
|
201
|
+
depends_on: t.depends_on,
|
|
202
|
+
})),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// =============================================================================
|
|
207
|
+
// task.start
|
|
208
|
+
// =============================================================================
|
|
209
|
+
|
|
210
|
+
function taskStart(cwd: string, params: CrewParams, state: MessengerState) {
|
|
211
|
+
const id = params.id;
|
|
212
|
+
if (!id) {
|
|
213
|
+
return result("Error: id required for task.start", { mode: "task.start", error: "missing_id" });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const task = store.getTask(cwd, id);
|
|
217
|
+
if (!task) {
|
|
218
|
+
return result(`Error: Task ${id} not found`, { mode: "task.start", error: "not_found", id });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (task.status !== "todo") {
|
|
222
|
+
return result(`Error: Task ${id} is ${task.status}, not todo`, {
|
|
223
|
+
mode: "task.start", error: "invalid_status", id, status: task.status
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check dependencies are done
|
|
228
|
+
if (task.depends_on.length > 0) {
|
|
229
|
+
const notDone: string[] = [];
|
|
230
|
+
for (const depId of task.depends_on) {
|
|
231
|
+
const dep = store.getTask(cwd, depId);
|
|
232
|
+
if (dep && dep.status !== "done") {
|
|
233
|
+
notDone.push(`${depId} (${dep.status})`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (notDone.length > 0) {
|
|
237
|
+
return result(`Error: Task ${id} has unmet dependencies: ${notDone.join(", ")}`, {
|
|
238
|
+
mode: "task.start",
|
|
239
|
+
error: "unmet_dependencies",
|
|
240
|
+
id,
|
|
241
|
+
unmetDependencies: notDone,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const agentName = state.agentName || "unknown";
|
|
247
|
+
const started = store.startTask(cwd, id, agentName);
|
|
248
|
+
|
|
249
|
+
if (!started) {
|
|
250
|
+
return result(`Error: Failed to start task ${id}`, { mode: "task.start", error: "start_failed", id });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const spec = store.getTaskSpec(cwd, id);
|
|
254
|
+
const specPreview = spec && !spec.includes("*Spec pending*")
|
|
255
|
+
? `\n\n**Spec:**\n\`\`\`\n${spec.length > 1000 ? spec.slice(0, 1000) + "..." : spec}\n\`\`\``
|
|
256
|
+
: "";
|
|
257
|
+
|
|
258
|
+
const text = `🔄 Started task **${id}**
|
|
259
|
+
|
|
260
|
+
**Title:** ${started.title}
|
|
261
|
+
**Assigned to:** ${agentName}
|
|
262
|
+
**Attempt:** ${started.attempt_count}
|
|
263
|
+
${started.base_commit ? `**Base commit:** ${started.base_commit.slice(0, 8)}` : ""}${specPreview}
|
|
264
|
+
|
|
265
|
+
When done: \`pi_messenger({ action: "task.done", id: "${id}", summary: "..." })\`
|
|
266
|
+
If blocked: \`pi_messenger({ action: "task.block", id: "${id}", reason: "..." })\``;
|
|
267
|
+
|
|
268
|
+
return result(text, {
|
|
269
|
+
mode: "task.start",
|
|
270
|
+
task: {
|
|
271
|
+
id: started.id,
|
|
272
|
+
title: started.title,
|
|
273
|
+
status: started.status,
|
|
274
|
+
assigned_to: started.assigned_to,
|
|
275
|
+
attempt_count: started.attempt_count,
|
|
276
|
+
base_commit: started.base_commit,
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// =============================================================================
|
|
282
|
+
// task.done
|
|
283
|
+
// =============================================================================
|
|
284
|
+
|
|
285
|
+
function taskDone(cwd: string, params: CrewParams) {
|
|
286
|
+
const id = params.id;
|
|
287
|
+
if (!id) {
|
|
288
|
+
return result("Error: id required for task.done", { mode: "task.done", error: "missing_id" });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const task = store.getTask(cwd, id);
|
|
292
|
+
if (!task) {
|
|
293
|
+
return result(`Error: Task ${id} not found`, { mode: "task.done", error: "not_found", id });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (task.status !== "in_progress") {
|
|
297
|
+
return result(`Error: Task ${id} is ${task.status}, not in_progress`, {
|
|
298
|
+
mode: "task.done", error: "invalid_status", id, status: task.status
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const summary = params.summary ?? "Task completed";
|
|
303
|
+
const evidence: TaskEvidence | undefined = params.evidence;
|
|
304
|
+
|
|
305
|
+
const completed = store.completeTask(cwd, id, summary, evidence);
|
|
306
|
+
if (!completed) {
|
|
307
|
+
return result(`Error: Failed to complete task ${id}`, { mode: "task.done", error: "complete_failed", id });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Check progress
|
|
311
|
+
const plan = store.getPlan(cwd);
|
|
312
|
+
const tasks = store.getTasks(cwd);
|
|
313
|
+
const remaining = tasks.filter(t => t.status !== "done");
|
|
314
|
+
|
|
315
|
+
let nextSteps = "";
|
|
316
|
+
if (remaining.length === 0) {
|
|
317
|
+
nextSteps = `\n\n🎉 **All tasks complete!** Plan is finished.`;
|
|
318
|
+
} else {
|
|
319
|
+
const ready = store.getReadyTasks(cwd);
|
|
320
|
+
if (ready.length > 0) {
|
|
321
|
+
nextSteps = `\n\n**Ready tasks:** ${ready.map(t => t.id).join(", ")}`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const text = `✅ Completed task **${id}**
|
|
326
|
+
|
|
327
|
+
**Summary:** ${summary}
|
|
328
|
+
**Progress:** ${plan?.completed_count}/${plan?.task_count}${nextSteps}`;
|
|
329
|
+
|
|
330
|
+
return result(text, {
|
|
331
|
+
mode: "task.done",
|
|
332
|
+
task: {
|
|
333
|
+
id: completed.id,
|
|
334
|
+
title: completed.title,
|
|
335
|
+
status: completed.status,
|
|
336
|
+
summary: completed.summary,
|
|
337
|
+
},
|
|
338
|
+
progress: {
|
|
339
|
+
completed: plan?.completed_count ?? 0,
|
|
340
|
+
total: plan?.task_count ?? 0,
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// =============================================================================
|
|
346
|
+
// task.block
|
|
347
|
+
// =============================================================================
|
|
348
|
+
|
|
349
|
+
function taskBlock(cwd: string, params: CrewParams) {
|
|
350
|
+
const id = params.id;
|
|
351
|
+
if (!id) {
|
|
352
|
+
return result("Error: id required for task.block", { mode: "task.block", error: "missing_id" });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!params.reason) {
|
|
356
|
+
return result("Error: reason required for task.block", { mode: "task.block", error: "missing_reason" });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const task = store.getTask(cwd, id);
|
|
360
|
+
if (!task) {
|
|
361
|
+
return result(`Error: Task ${id} not found`, { mode: "task.block", error: "not_found", id });
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const blocked = store.blockTask(cwd, id, params.reason);
|
|
365
|
+
if (!blocked) {
|
|
366
|
+
return result(`Error: Failed to block task ${id}`, { mode: "task.block", error: "block_failed", id });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const text = `🚫 Blocked task **${id}**
|
|
370
|
+
|
|
371
|
+
**Reason:** ${params.reason}
|
|
372
|
+
|
|
373
|
+
Unblock with: \`pi_messenger({ action: "task.unblock", id: "${id}" })\``;
|
|
374
|
+
|
|
375
|
+
return result(text, {
|
|
376
|
+
mode: "task.block",
|
|
377
|
+
task: {
|
|
378
|
+
id: blocked.id,
|
|
379
|
+
title: blocked.title,
|
|
380
|
+
status: blocked.status,
|
|
381
|
+
blocked_reason: blocked.blocked_reason,
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// =============================================================================
|
|
387
|
+
// task.unblock
|
|
388
|
+
// =============================================================================
|
|
389
|
+
|
|
390
|
+
function taskUnblock(cwd: string, params: CrewParams) {
|
|
391
|
+
const id = params.id;
|
|
392
|
+
if (!id) {
|
|
393
|
+
return result("Error: id required for task.unblock", { mode: "task.unblock", error: "missing_id" });
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const task = store.getTask(cwd, id);
|
|
397
|
+
if (!task) {
|
|
398
|
+
return result(`Error: Task ${id} not found`, { mode: "task.unblock", error: "not_found", id });
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (task.status !== "blocked") {
|
|
402
|
+
return result(`Error: Task ${id} is ${task.status}, not blocked`, {
|
|
403
|
+
mode: "task.unblock", error: "invalid_status", id, status: task.status
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const unblocked = store.unblockTask(cwd, id);
|
|
408
|
+
if (!unblocked) {
|
|
409
|
+
return result(`Error: Failed to unblock task ${id}`, { mode: "task.unblock", error: "unblock_failed", id });
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const text = `⬜ Unblocked task **${id}**
|
|
413
|
+
|
|
414
|
+
Task is now ready to start: \`pi_messenger({ action: "task.start", id: "${id}" })\``;
|
|
415
|
+
|
|
416
|
+
return result(text, {
|
|
417
|
+
mode: "task.unblock",
|
|
418
|
+
task: {
|
|
419
|
+
id: unblocked.id,
|
|
420
|
+
title: unblocked.title,
|
|
421
|
+
status: unblocked.status,
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// =============================================================================
|
|
427
|
+
// task.ready
|
|
428
|
+
// =============================================================================
|
|
429
|
+
|
|
430
|
+
function taskReady(cwd: string) {
|
|
431
|
+
const plan = store.getPlan(cwd);
|
|
432
|
+
if (!plan) {
|
|
433
|
+
return result("No plan found. Create one with: pi_messenger({ action: \"plan\" })", {
|
|
434
|
+
mode: "task.ready", ready: [], hasPlan: false
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const ready = store.getReadyTasks(cwd);
|
|
439
|
+
|
|
440
|
+
if (ready.length === 0) {
|
|
441
|
+
const tasks = store.getTasks(cwd);
|
|
442
|
+
const inProgress = tasks.filter(t => t.status === "in_progress");
|
|
443
|
+
const blocked = tasks.filter(t => t.status === "blocked");
|
|
444
|
+
const done = tasks.filter(t => t.status === "done");
|
|
445
|
+
|
|
446
|
+
let reason = "";
|
|
447
|
+
if (done.length === tasks.length) {
|
|
448
|
+
reason = "All tasks are done!";
|
|
449
|
+
} else if (inProgress.length > 0) {
|
|
450
|
+
reason = `${inProgress.length} task(s) in progress: ${inProgress.map(t => t.id).join(", ")}`;
|
|
451
|
+
} else if (blocked.length > 0) {
|
|
452
|
+
reason = `${blocked.length} task(s) blocked: ${blocked.map(t => t.id).join(", ")}`;
|
|
453
|
+
} else {
|
|
454
|
+
reason = "All remaining tasks have unmet dependencies.";
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return result(`No ready tasks.\n\n${reason}`, {
|
|
458
|
+
mode: "task.ready",
|
|
459
|
+
ready: [],
|
|
460
|
+
reason,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const lines: string[] = [`# Ready Tasks\n`];
|
|
465
|
+
for (const task of ready) {
|
|
466
|
+
lines.push(`⬜ **${task.id}**: ${task.title}`);
|
|
467
|
+
}
|
|
468
|
+
lines.push(`\nStart one: \`pi_messenger({ action: "task.start", id: "${ready[0].id}" })\``);
|
|
469
|
+
lines.push(`Or run all: \`pi_messenger({ action: "work" })\``);
|
|
470
|
+
|
|
471
|
+
return result(lines.join("\n"), {
|
|
472
|
+
mode: "task.ready",
|
|
473
|
+
ready: ready.map(t => ({
|
|
474
|
+
id: t.id,
|
|
475
|
+
title: t.title,
|
|
476
|
+
})),
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// =============================================================================
|
|
481
|
+
// task.reset
|
|
482
|
+
// =============================================================================
|
|
483
|
+
|
|
484
|
+
function taskReset(cwd: string, params: CrewParams) {
|
|
485
|
+
const id = params.id;
|
|
486
|
+
if (!id) {
|
|
487
|
+
return result("Error: id required for task.reset", { mode: "task.reset", error: "missing_id" });
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const task = store.getTask(cwd, id);
|
|
491
|
+
if (!task) {
|
|
492
|
+
return result(`Error: Task ${id} not found`, { mode: "task.reset", error: "not_found", id });
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const cascade = params.cascade ?? false;
|
|
496
|
+
const resetTasks = store.resetTask(cwd, id, cascade);
|
|
497
|
+
|
|
498
|
+
if (resetTasks.length === 0) {
|
|
499
|
+
return result(`Error: Failed to reset task ${id}`, { mode: "task.reset", error: "reset_failed", id });
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const text = cascade && resetTasks.length > 1
|
|
503
|
+
? `🔄 Reset ${resetTasks.length} tasks:\n${resetTasks.map(t => ` - ${t.id}`).join("\n")}`
|
|
504
|
+
: `🔄 Reset task **${id}**`;
|
|
505
|
+
|
|
506
|
+
return result(text + `\n\nStart with: \`pi_messenger({ action: "task.start", id: "${id}" })\``, {
|
|
507
|
+
mode: "task.reset",
|
|
508
|
+
reset: resetTasks.map(t => t.id),
|
|
509
|
+
cascade,
|
|
510
|
+
});
|
|
511
|
+
}
|