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,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crew - Status Handler
|
|
3
|
+
*
|
|
4
|
+
* Shows plan progress and task status.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
8
|
+
import type { MessengerState, Dirs } from "../../lib.js";
|
|
9
|
+
import type { CrewParams } from "../types.js";
|
|
10
|
+
import { result } from "../utils/result.js";
|
|
11
|
+
import { discoverCrewAgents } from "../utils/discover.js";
|
|
12
|
+
import {
|
|
13
|
+
ensureAgentsInstalled,
|
|
14
|
+
uninstallAgents,
|
|
15
|
+
ensureSkillsInstalled,
|
|
16
|
+
uninstallSkills
|
|
17
|
+
} from "../utils/install.js";
|
|
18
|
+
import * as store from "../store.js";
|
|
19
|
+
import { autonomousState } from "../state.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Execute status action - shows plan progress.
|
|
23
|
+
*/
|
|
24
|
+
export async function execute(
|
|
25
|
+
_params: CrewParams,
|
|
26
|
+
_state: MessengerState,
|
|
27
|
+
_dirs: Dirs,
|
|
28
|
+
ctx: ExtensionContext
|
|
29
|
+
) {
|
|
30
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
31
|
+
const plan = store.getPlan(cwd);
|
|
32
|
+
|
|
33
|
+
if (!plan) {
|
|
34
|
+
return result(`# Crew Status
|
|
35
|
+
|
|
36
|
+
**No active plan.**
|
|
37
|
+
|
|
38
|
+
Create a plan from your PRD:
|
|
39
|
+
pi_messenger({ action: "plan" }) # Auto-discovers PRD.md
|
|
40
|
+
pi_messenger({ action: "plan", prd: "docs/PRD.md" }) # Explicit path`, {
|
|
41
|
+
mode: "status",
|
|
42
|
+
hasPlan: false
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const tasks = store.getTasks(cwd);
|
|
47
|
+
const done = tasks.filter(t => t.status === "done");
|
|
48
|
+
const inProgress = tasks.filter(t => t.status === "in_progress");
|
|
49
|
+
const blocked = tasks.filter(t => t.status === "blocked");
|
|
50
|
+
const ready = store.getReadyTasks(cwd);
|
|
51
|
+
const waiting = tasks.filter(t =>
|
|
52
|
+
t.status === "todo" && !ready.some(r => r.id === t.id)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const pct = tasks.length > 0 ? Math.round((done.length / tasks.length) * 100) : 0;
|
|
56
|
+
|
|
57
|
+
let text = `# Crew Status
|
|
58
|
+
|
|
59
|
+
**Plan:** ${plan.prd}
|
|
60
|
+
**Progress:** ${done.length}/${tasks.length} tasks (${pct}%)
|
|
61
|
+
|
|
62
|
+
## Tasks
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
if (done.length > 0) {
|
|
66
|
+
text += `\n✅ **Done**\n`;
|
|
67
|
+
for (const t of done) {
|
|
68
|
+
text += ` - ${t.id}: ${t.title}\n`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (inProgress.length > 0) {
|
|
73
|
+
text += `\n🔄 **In Progress**\n`;
|
|
74
|
+
for (const t of inProgress) {
|
|
75
|
+
const agent = t.assigned_to ? ` (${t.assigned_to}` : "";
|
|
76
|
+
const attempt = t.attempt_count > 1 ? `, attempt ${t.attempt_count}` : "";
|
|
77
|
+
text += ` - ${t.id}: ${t.title}${agent}${attempt}${t.assigned_to ? ")" : ""}\n`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (ready.length > 0) {
|
|
82
|
+
text += `\n⬜ **Ready**\n`;
|
|
83
|
+
for (const t of ready) {
|
|
84
|
+
text += ` - ${t.id}: ${t.title}\n`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (waiting.length > 0) {
|
|
89
|
+
text += `\n⏸️ **Waiting** (dependencies not met)\n`;
|
|
90
|
+
for (const t of waiting) {
|
|
91
|
+
const deps = t.depends_on.join(", ");
|
|
92
|
+
text += ` - ${t.id}: ${t.title} → needs: ${deps}\n`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (blocked.length > 0) {
|
|
97
|
+
text += `\n🚫 **Blocked**\n`;
|
|
98
|
+
for (const t of blocked) {
|
|
99
|
+
const reason = t.blocked_reason ? ` (${t.blocked_reason.slice(0, 40)}...)` : "";
|
|
100
|
+
text += ` - ${t.id}: ${t.title}${reason}\n`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Add autonomous status if active
|
|
105
|
+
if (autonomousState.active) {
|
|
106
|
+
text += `\n## Autonomous Mode\n`;
|
|
107
|
+
text += `Wave ${autonomousState.waveNumber} running...\n`;
|
|
108
|
+
if (autonomousState.startedAt) {
|
|
109
|
+
const startTime = new Date(autonomousState.startedAt).getTime();
|
|
110
|
+
const elapsedMs = Date.now() - startTime;
|
|
111
|
+
const minutes = Math.floor(elapsedMs / 60000);
|
|
112
|
+
const seconds = Math.floor((elapsedMs % 60000) / 1000);
|
|
113
|
+
text += `Elapsed: ${minutes}:${seconds.toString().padStart(2, "0")}\n`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Add next steps
|
|
118
|
+
text += `\n## Next`;
|
|
119
|
+
if (done.length === tasks.length) {
|
|
120
|
+
text += `\n🎉 All tasks complete!`;
|
|
121
|
+
} else if (ready.length > 0) {
|
|
122
|
+
text += `\nRun \`pi_messenger({ action: "work" })\` to execute ${ready.map(t => t.id).join(", ")}`;
|
|
123
|
+
} else if (blocked.length > 0) {
|
|
124
|
+
text += `\nUnblock tasks with \`pi_messenger({ action: "task.unblock", id: "..." })\``;
|
|
125
|
+
} else if (inProgress.length > 0) {
|
|
126
|
+
text += `\nWaiting for in-progress tasks to complete.`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result(text, {
|
|
130
|
+
mode: "status",
|
|
131
|
+
hasPlan: true,
|
|
132
|
+
prd: plan.prd,
|
|
133
|
+
progress: { done: done.length, total: tasks.length, pct },
|
|
134
|
+
tasks: {
|
|
135
|
+
done: done.map(t => t.id),
|
|
136
|
+
inProgress: inProgress.map(t => t.id),
|
|
137
|
+
ready: ready.map(t => t.id),
|
|
138
|
+
waiting: waiting.map(t => t.id),
|
|
139
|
+
blocked: blocked.map(t => t.id)
|
|
140
|
+
},
|
|
141
|
+
autonomous: autonomousState.active
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Execute crew.* actions (crew.status, crew.agents, crew.install, crew.uninstall)
|
|
147
|
+
*/
|
|
148
|
+
export async function executeCrew(
|
|
149
|
+
op: string,
|
|
150
|
+
_params: CrewParams,
|
|
151
|
+
_state: MessengerState,
|
|
152
|
+
_dirs: Dirs,
|
|
153
|
+
ctx: ExtensionContext
|
|
154
|
+
) {
|
|
155
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
156
|
+
|
|
157
|
+
switch (op) {
|
|
158
|
+
case "status": {
|
|
159
|
+
// Same as main status
|
|
160
|
+
return execute(_params, _state, _dirs, ctx);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case "agents": {
|
|
164
|
+
const agents = discoverCrewAgents(cwd);
|
|
165
|
+
if (agents.length === 0) {
|
|
166
|
+
return result("No crew agents found. Run crew.install to set up agents.", {
|
|
167
|
+
mode: "crew.agents",
|
|
168
|
+
agents: []
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const byRole: Record<string, string[]> = {};
|
|
173
|
+
for (const a of agents) {
|
|
174
|
+
const role = a.crewRole ?? "other";
|
|
175
|
+
if (!byRole[role]) byRole[role] = [];
|
|
176
|
+
byRole[role].push(`${a.name} (${a.model ?? "default"})`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let text = "# Crew Agents\n";
|
|
180
|
+
for (const [role, names] of Object.entries(byRole)) {
|
|
181
|
+
text += `\n**${role}s:** ${names.join(", ")}\n`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return result(text, {
|
|
185
|
+
mode: "crew.agents",
|
|
186
|
+
agents: agents.map(a => ({ name: a.name, role: a.crewRole, model: a.model }))
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
case "install": {
|
|
191
|
+
ensureAgentsInstalled();
|
|
192
|
+
ensureSkillsInstalled();
|
|
193
|
+
const agents = discoverCrewAgents(cwd);
|
|
194
|
+
return result(`✅ Crew installed:\n- Agents: ${agents.map(a => a.name).join(", ")}\n- Skills: pi-messenger-crew`, {
|
|
195
|
+
mode: "crew.install",
|
|
196
|
+
agents: agents.map(a => a.name),
|
|
197
|
+
skills: ["pi-messenger-crew"]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
case "uninstall": {
|
|
202
|
+
const agentResult = uninstallAgents();
|
|
203
|
+
const skillResult = uninstallSkills();
|
|
204
|
+
const errors = [...agentResult.errors, ...skillResult.errors];
|
|
205
|
+
const removed = { agents: agentResult.removed, skills: skillResult.removed };
|
|
206
|
+
|
|
207
|
+
if (errors.length > 0) {
|
|
208
|
+
return result(`⚠️ Removed with ${errors.length} error(s):\n${errors.join("\n")}`, {
|
|
209
|
+
mode: "crew.uninstall",
|
|
210
|
+
removed,
|
|
211
|
+
errors
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return result(`✅ Removed:\n- ${agentResult.removed.length} agent(s)\n- ${skillResult.removed.length} skill(s)`, {
|
|
215
|
+
mode: "crew.uninstall",
|
|
216
|
+
removed
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case "validate": {
|
|
221
|
+
const validation = store.validatePlan(cwd);
|
|
222
|
+
|
|
223
|
+
if (validation.valid && validation.warnings.length === 0) {
|
|
224
|
+
return result("✅ Plan is valid with no warnings.", {
|
|
225
|
+
mode: "crew.validate",
|
|
226
|
+
valid: true,
|
|
227
|
+
errors: [],
|
|
228
|
+
warnings: []
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let text = validation.valid ? "✅ Plan is valid" : "❌ Plan has errors";
|
|
233
|
+
|
|
234
|
+
if (validation.errors.length > 0) {
|
|
235
|
+
text += "\n\n**Errors:**\n" + validation.errors.map(e => `- ${e}`).join("\n");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (validation.warnings.length > 0) {
|
|
239
|
+
text += "\n\n**Warnings:**\n" + validation.warnings.map(w => `- ${w}`).join("\n");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return result(text, {
|
|
243
|
+
mode: "crew.validate",
|
|
244
|
+
valid: validation.valid,
|
|
245
|
+
errors: validation.errors,
|
|
246
|
+
warnings: validation.warnings
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
default:
|
|
251
|
+
return result(`Unknown crew operation: ${op}`, {
|
|
252
|
+
mode: "crew",
|
|
253
|
+
error: "unknown_operation",
|
|
254
|
+
operation: op
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crew - Sync Handler
|
|
3
|
+
*
|
|
4
|
+
* Updates downstream specs after task completion.
|
|
5
|
+
* Works with current plan's tasks.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
9
|
+
import type { MessengerState, Dirs } from "../../lib.js";
|
|
10
|
+
import type { CrewParams } from "../types.js";
|
|
11
|
+
import { result } from "../utils/result.js";
|
|
12
|
+
import { spawnAgents } from "../agents.js";
|
|
13
|
+
import { discoverCrewAgents } from "../utils/discover.js";
|
|
14
|
+
import * as store from "../store.js";
|
|
15
|
+
|
|
16
|
+
export async function execute(
|
|
17
|
+
params: CrewParams,
|
|
18
|
+
_state: MessengerState,
|
|
19
|
+
_dirs: Dirs,
|
|
20
|
+
ctx: ExtensionContext
|
|
21
|
+
) {
|
|
22
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
23
|
+
const { target } = params;
|
|
24
|
+
|
|
25
|
+
if (!target) {
|
|
26
|
+
return result("Error: target (completed task ID) required for sync action.", {
|
|
27
|
+
mode: "sync",
|
|
28
|
+
error: "missing_target"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Verify plan exists
|
|
33
|
+
const plan = store.getPlan(cwd);
|
|
34
|
+
if (!plan) {
|
|
35
|
+
return result("Error: No plan found.", {
|
|
36
|
+
mode: "sync",
|
|
37
|
+
error: "no_plan"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Verify task exists and is completed
|
|
42
|
+
const task = store.getTask(cwd, target);
|
|
43
|
+
if (!task) {
|
|
44
|
+
return result(`Error: Task ${target} not found.`, {
|
|
45
|
+
mode: "sync",
|
|
46
|
+
error: "task_not_found",
|
|
47
|
+
target
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (task.status !== "done") {
|
|
52
|
+
return result(`Error: Task ${target} is ${task.status}, not done. Sync is for completed tasks.`, {
|
|
53
|
+
mode: "sync",
|
|
54
|
+
error: "task_not_done",
|
|
55
|
+
status: task.status
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for plan-sync agent
|
|
60
|
+
const availableAgents = discoverCrewAgents(cwd);
|
|
61
|
+
const hasSyncAgent = availableAgents.some(a => a.name === "crew-plan-sync");
|
|
62
|
+
if (!hasSyncAgent) {
|
|
63
|
+
return result("Error: crew-plan-sync agent not found.", {
|
|
64
|
+
mode: "sync",
|
|
65
|
+
error: "no_sync_agent"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const allTasks = store.getTasks(cwd);
|
|
70
|
+
|
|
71
|
+
// Find dependent tasks (tasks that depend on the completed task)
|
|
72
|
+
const dependentTasks = allTasks.filter(t =>
|
|
73
|
+
t.depends_on.includes(target) && t.status === "todo"
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (dependentTasks.length === 0) {
|
|
77
|
+
return result(`No downstream tasks depend on ${target}. No sync needed.`, {
|
|
78
|
+
mode: "sync",
|
|
79
|
+
taskId: target,
|
|
80
|
+
dependentTasks: [],
|
|
81
|
+
synced: false
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Get completed task details for context
|
|
86
|
+
const taskSpec = store.getTaskSpec(cwd, target);
|
|
87
|
+
const taskSummary = task.summary ?? "No summary";
|
|
88
|
+
|
|
89
|
+
// Build task overview for dependent tasks
|
|
90
|
+
const dependentOverview = dependentTasks.map(t => {
|
|
91
|
+
const spec = store.getTaskSpec(cwd, t.id);
|
|
92
|
+
return `### ${t.id}: ${t.title}
|
|
93
|
+
|
|
94
|
+
${spec || "*No spec*"}
|
|
95
|
+
`;
|
|
96
|
+
}).join("\n");
|
|
97
|
+
|
|
98
|
+
// Build sync prompt
|
|
99
|
+
const prompt = `# Spec Sync Request
|
|
100
|
+
|
|
101
|
+
## Completed Task
|
|
102
|
+
|
|
103
|
+
**Task ID:** ${target}
|
|
104
|
+
**Title:** ${task.title}
|
|
105
|
+
**Summary:** ${taskSummary}
|
|
106
|
+
|
|
107
|
+
### Implementation Details
|
|
108
|
+
|
|
109
|
+
${taskSpec || "*No detailed spec*"}
|
|
110
|
+
|
|
111
|
+
## Dependent Tasks to Update
|
|
112
|
+
|
|
113
|
+
These tasks depend on the completed task and may need spec updates:
|
|
114
|
+
|
|
115
|
+
${dependentOverview}
|
|
116
|
+
|
|
117
|
+
## Your Task
|
|
118
|
+
|
|
119
|
+
1. Review what was implemented in the completed task
|
|
120
|
+
2. Check if any dependent task specs need updating based on the implementation
|
|
121
|
+
3. Update specs with relevant information (file locations, API details, etc.)
|
|
122
|
+
4. Output which specs were updated and why
|
|
123
|
+
|
|
124
|
+
Follow the output format in your instructions.`;
|
|
125
|
+
|
|
126
|
+
// Spawn sync agent
|
|
127
|
+
const [syncResult] = await spawnAgents([{
|
|
128
|
+
agent: "crew-plan-sync",
|
|
129
|
+
task: prompt
|
|
130
|
+
}], 1, cwd);
|
|
131
|
+
|
|
132
|
+
if (syncResult.exitCode !== 0) {
|
|
133
|
+
return result(`Error: Sync agent failed: ${syncResult.error ?? "Unknown error"}`, {
|
|
134
|
+
mode: "sync",
|
|
135
|
+
error: "sync_failed"
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Parse sync results
|
|
140
|
+
const updates = parseSyncUpdates(syncResult.output);
|
|
141
|
+
|
|
142
|
+
// Apply updates to task specs
|
|
143
|
+
let updatedCount = 0;
|
|
144
|
+
for (const update of updates) {
|
|
145
|
+
const matchingTask = dependentTasks.find(t =>
|
|
146
|
+
t.id === update.taskId ||
|
|
147
|
+
t.title.toLowerCase().includes(update.taskId.toLowerCase())
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (matchingTask && update.newContent) {
|
|
151
|
+
const currentSpec = store.getTaskSpec(cwd, matchingTask.id) ?? "";
|
|
152
|
+
|
|
153
|
+
// Append update to spec (don't replace)
|
|
154
|
+
const updatedSpec = `${currentSpec}
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
*Updated after ${target} completion:*
|
|
158
|
+
|
|
159
|
+
${update.newContent}`;
|
|
160
|
+
|
|
161
|
+
store.setTaskSpec(cwd, matchingTask.id, updatedSpec);
|
|
162
|
+
updatedCount++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const readyTasks = store.getReadyTasks(cwd);
|
|
167
|
+
const text = `# Sync Complete: ${target}
|
|
168
|
+
|
|
169
|
+
**Dependent tasks checked:** ${dependentTasks.length}
|
|
170
|
+
**Specs updated:** ${updatedCount}
|
|
171
|
+
|
|
172
|
+
${updates.length > 0 ? `## Updates\n${updates.map(u => `- **${u.taskId}**: ${u.reason}`).join("\n")}` : "## No Updates Needed\n\nDependent task specs are already up to date."}
|
|
173
|
+
|
|
174
|
+
${updatedCount > 0 ? `\n**Ready tasks:** ${readyTasks.map(t => t.id).join(", ") || "none"}` : ""}`;
|
|
175
|
+
|
|
176
|
+
return result(text, {
|
|
177
|
+
mode: "sync",
|
|
178
|
+
taskId: target,
|
|
179
|
+
dependentTasks: dependentTasks.map(t => t.id),
|
|
180
|
+
updatedCount,
|
|
181
|
+
updates: updates.map(u => ({ taskId: u.taskId, reason: u.reason }))
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// =============================================================================
|
|
186
|
+
// Sync Update Parsing
|
|
187
|
+
// =============================================================================
|
|
188
|
+
|
|
189
|
+
interface SyncUpdate {
|
|
190
|
+
taskId: string;
|
|
191
|
+
reason: string;
|
|
192
|
+
newContent?: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Parses sync updates from the sync agent output.
|
|
197
|
+
*
|
|
198
|
+
* Expected format:
|
|
199
|
+
* ### Updated: [task-id]
|
|
200
|
+
*
|
|
201
|
+
* Changes made:
|
|
202
|
+
* - Updated section X to reflect...
|
|
203
|
+
*
|
|
204
|
+
* New content:
|
|
205
|
+
* [content to add to spec]
|
|
206
|
+
*/
|
|
207
|
+
function parseSyncUpdates(output: string): SyncUpdate[] {
|
|
208
|
+
const updates: SyncUpdate[] = [];
|
|
209
|
+
|
|
210
|
+
// Match update blocks
|
|
211
|
+
const updateRegex = /###\s*Updated:\s*(.+?)\n([\s\S]*?)(?=###|$)/gi;
|
|
212
|
+
let match;
|
|
213
|
+
|
|
214
|
+
while ((match = updateRegex.exec(output)) !== null) {
|
|
215
|
+
const taskId = match[1].trim();
|
|
216
|
+
const body = match[2].trim();
|
|
217
|
+
|
|
218
|
+
// Extract reason (Changes made section)
|
|
219
|
+
const reasonMatch = body.match(/Changes made:?\s*([\s\S]*?)(?=New content:|$)/i);
|
|
220
|
+
const reason = reasonMatch
|
|
221
|
+
? reasonMatch[1].trim().replace(/^[-*]\s*/gm, "").split("\n")[0].trim()
|
|
222
|
+
: "Updated based on implementation";
|
|
223
|
+
|
|
224
|
+
// Extract new content
|
|
225
|
+
const contentMatch = body.match(/New content:?\s*([\s\S]*?)$/i);
|
|
226
|
+
const newContent = contentMatch ? contentMatch[1].trim() : undefined;
|
|
227
|
+
|
|
228
|
+
updates.push({ taskId, reason, newContent });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return updates;
|
|
232
|
+
}
|