@phi-code-admin/phi-code 0.73.0 → 0.74.1
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/agents/test.md +3 -0
- package/extensions/phi/memory.ts +2 -1
- package/extensions/phi/orchestrator.ts +40 -335
- package/package.json +1 -1
package/agents/test.md
CHANGED
|
@@ -33,6 +33,9 @@ Use implementation results to know which files were created/modified and what be
|
|
|
33
33
|
- **Realistic assertions**: Test what matters, not trivial details
|
|
34
34
|
- **Match conventions**: Use the project's test framework, directory structure, and naming patterns
|
|
35
35
|
- **Clean test code**: Tests are documentation — use descriptive names that explain expected behavior
|
|
36
|
+
- Prefer targeted `edit` calls over full file rewrites. When a test fails, fix ONLY the failing test function, not the entire file
|
|
37
|
+
- Maximum 1 full file rewrite per test file. After that, use `edit` for surgical fixes
|
|
38
|
+
- When debugging test failures: read the error → locate the exact failing assertion → fix that specific line
|
|
36
39
|
|
|
37
40
|
## Test Writing
|
|
38
41
|
|
package/extensions/phi/memory.ts
CHANGED
|
@@ -43,9 +43,10 @@ export default function memoryExtension(pi: ExtensionAPI) {
|
|
|
43
43
|
description: "Search for content in memory using unified search (notes + ontology + vector search)",
|
|
44
44
|
promptSnippet: "Search project memory (notes, ontology, vector search). ALWAYS call before answering questions about prior work, decisions, or project context.",
|
|
45
45
|
promptGuidelines: [
|
|
46
|
-
"Before
|
|
46
|
+
"MANDATORY: Before starting ANY task, call memory_search with relevant keywords. This is not optional.",
|
|
47
47
|
"When starting work on a topic, search memory for existing notes and learnings.",
|
|
48
48
|
"After completing important work or learning something new, use memory_write to save it.",
|
|
49
|
+
"MANDATORY: After completing any significant work, call memory_write to save what you did and what you learned.",
|
|
49
50
|
"When a command fails or produces an unexpected error, document the error and fix in memory_write (self-improvement).",
|
|
50
51
|
"When the user corrects you, save the correction in memory_write so you never repeat the mistake.",
|
|
51
52
|
"After a significant debugging session, write a summary of root cause and solution to memory.",
|
|
@@ -21,7 +21,6 @@ import type { ExtensionAPI } from "phi-code";
|
|
|
21
21
|
import { writeFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
22
22
|
import { join } from "node:path";
|
|
23
23
|
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
-
// execFile removed — tasks now execute in-session, no subprocess
|
|
25
24
|
import { homedir } from "node:os";
|
|
26
25
|
|
|
27
26
|
// ─── Types ───────────────────────────────────────────────────────────────
|
|
@@ -35,22 +34,6 @@ interface TaskDef {
|
|
|
35
34
|
subtasks?: string[];
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
interface TaskResult {
|
|
39
|
-
taskIndex: number;
|
|
40
|
-
title: string;
|
|
41
|
-
agent: string;
|
|
42
|
-
status: "success" | "error" | "skipped";
|
|
43
|
-
output: string;
|
|
44
|
-
durationMs: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface AgentDef {
|
|
48
|
-
name: string;
|
|
49
|
-
description: string;
|
|
50
|
-
tools: string;
|
|
51
|
-
systemPrompt: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
37
|
// ─── Extension ───────────────────────────────────────────────────────────
|
|
55
38
|
|
|
56
39
|
export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
@@ -64,218 +47,7 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
64
47
|
return new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
65
48
|
}
|
|
66
49
|
|
|
67
|
-
// ───
|
|
68
|
-
|
|
69
|
-
function loadAgentDefs(): Map<string, AgentDef> {
|
|
70
|
-
const agents = new Map<string, AgentDef>();
|
|
71
|
-
const dirs = [
|
|
72
|
-
join(process.cwd(), ".phi", "agents"),
|
|
73
|
-
join(homedir(), ".phi", "agent", "agents"),
|
|
74
|
-
join(__dirname, "..", "..", "..", "agents"),
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
for (const dir of dirs) {
|
|
78
|
-
if (!existsSync(dir)) continue;
|
|
79
|
-
try {
|
|
80
|
-
const files = require("fs").readdirSync(dir) as string[];
|
|
81
|
-
for (const file of files) {
|
|
82
|
-
if (!file.endsWith(".md")) continue;
|
|
83
|
-
const name = file.replace(".md", "");
|
|
84
|
-
if (agents.has(name)) continue;
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const content = readFileSync(join(dir, file), "utf-8");
|
|
88
|
-
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
89
|
-
if (!fmMatch) continue;
|
|
90
|
-
|
|
91
|
-
const frontmatter = fmMatch[1];
|
|
92
|
-
const body = fmMatch[2].trim();
|
|
93
|
-
const desc = frontmatter.match(/description:\s*(.+)/)?.[1] || "";
|
|
94
|
-
const tools = frontmatter.match(/tools:\s*(.+)/)?.[1] || "";
|
|
95
|
-
|
|
96
|
-
agents.set(name, { name, description: desc, tools, systemPrompt: body });
|
|
97
|
-
} catch { /* skip */ }
|
|
98
|
-
}
|
|
99
|
-
} catch { /* skip */ }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return agents;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function resolveAgentModel(agentType: string): string | null {
|
|
106
|
-
const routingPath = join(homedir(), ".phi", "agent", "routing.json");
|
|
107
|
-
try {
|
|
108
|
-
const config = JSON.parse(readFileSync(routingPath, "utf-8"));
|
|
109
|
-
for (const [_cat, route] of Object.entries(config.routes || {})) {
|
|
110
|
-
const r = route as any;
|
|
111
|
-
if (r.agent === agentType) return r.preferredModel || null;
|
|
112
|
-
}
|
|
113
|
-
// Map agent type to route category
|
|
114
|
-
const categoryMap: Record<string, string> = {
|
|
115
|
-
code: "code", explore: "explore", plan: "plan",
|
|
116
|
-
test: "test", review: "review", debug: "debug",
|
|
117
|
-
};
|
|
118
|
-
const category = categoryMap[agentType];
|
|
119
|
-
if (category && config.routes?.[category]) {
|
|
120
|
-
return config.routes[category].preferredModel || null;
|
|
121
|
-
}
|
|
122
|
-
return config.default?.model || null;
|
|
123
|
-
} catch {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function findPhiBinary(): string {
|
|
129
|
-
// Try the bundled CLI relative to extensions dir
|
|
130
|
-
const bundledCli = join(__dirname, "..", "..", "..", "dist", "cli.js");
|
|
131
|
-
if (existsSync(bundledCli)) return bundledCli;
|
|
132
|
-
|
|
133
|
-
// Try npm global install paths
|
|
134
|
-
const npmGlobalPaths = [
|
|
135
|
-
join(homedir(), "AppData", "Roaming", "npm", "node_modules", "@phi-code-admin", "phi-code", "dist", "cli.js"), // Windows
|
|
136
|
-
join(homedir(), ".npm-global", "lib", "node_modules", "@phi-code-admin", "phi-code", "dist", "cli.js"), // Linux custom
|
|
137
|
-
"/usr/local/lib/node_modules/@phi-code-admin/phi-code/dist/cli.js", // Linux/Mac default
|
|
138
|
-
"/usr/lib/node_modules/@phi-code-admin/phi-code/dist/cli.js", // Some Linux
|
|
139
|
-
];
|
|
140
|
-
for (const p of npmGlobalPaths) {
|
|
141
|
-
if (existsSync(p)) return p;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Try `which phi` (Linux/Mac) or `where phi` (Windows)
|
|
145
|
-
try {
|
|
146
|
-
const isWin = process.platform === "win32";
|
|
147
|
-
const cmd = isWin ? "where" : "which";
|
|
148
|
-
const result = require("child_process").execSync(`${cmd} phi 2>${isWin ? "NUL" : "/dev/null"}`, { encoding: "utf-8" }).trim();
|
|
149
|
-
if (result) {
|
|
150
|
-
const firstLine = result.split("\n")[0].trim();
|
|
151
|
-
// On Windows, `where phi` returns the .cmd shim; we need the actual JS
|
|
152
|
-
if (isWin && firstLine.endsWith(".cmd")) {
|
|
153
|
-
const npmPrefix = require("child_process").execSync("npm prefix -g", { encoding: "utf-8" }).trim();
|
|
154
|
-
const jsPath = join(npmPrefix, "node_modules", "@phi-code-admin", "phi-code", "dist", "cli.js");
|
|
155
|
-
if (existsSync(jsPath)) return jsPath;
|
|
156
|
-
}
|
|
157
|
-
return firstLine;
|
|
158
|
-
}
|
|
159
|
-
} catch { /* not in PATH */ }
|
|
160
|
-
|
|
161
|
-
// Last resort: assume phi is in PATH (works with shell:true on Windows)
|
|
162
|
-
return "phi";
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ─── Task Execution (in-session, no subprocess) ─────────────────
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Execute a task by sending it as a user message to the current session.
|
|
169
|
-
* The LLM handles it directly — no subprocess spawning, no cold boot.
|
|
170
|
-
* Much faster and more reliable than spawning phi --print processes.
|
|
171
|
-
*/
|
|
172
|
-
function executeTaskInSession(
|
|
173
|
-
task: TaskDef,
|
|
174
|
-
sharedContext: {
|
|
175
|
-
projectTitle: string;
|
|
176
|
-
projectDescription: string;
|
|
177
|
-
specSummary: string;
|
|
178
|
-
completedTasks: Array<{ index: number; title: string; agent: string; output: string }>;
|
|
179
|
-
},
|
|
180
|
-
): { taskPrompt: string } {
|
|
181
|
-
const agentType = task.agent || "code";
|
|
182
|
-
|
|
183
|
-
// Build prompt with shared context
|
|
184
|
-
let taskPrompt = `## 🔧 Task: ${task.title} [${agentType}]\n\n`;
|
|
185
|
-
|
|
186
|
-
taskPrompt += `**Project:** ${sharedContext.projectTitle}\n\n`;
|
|
187
|
-
|
|
188
|
-
if (sharedContext.specSummary) {
|
|
189
|
-
taskPrompt += `**Spec:** ${sharedContext.specSummary}\n\n`;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Inject results from dependency tasks
|
|
193
|
-
const deps = task.dependencies || [];
|
|
194
|
-
if (deps.length > 0) {
|
|
195
|
-
const depResults = sharedContext.completedTasks.filter(ct => deps.includes(ct.index));
|
|
196
|
-
if (depResults.length > 0) {
|
|
197
|
-
taskPrompt += `**Previous results:**\n`;
|
|
198
|
-
for (const dep of depResults) {
|
|
199
|
-
const truncated = dep.output.length > 500 ? dep.output.slice(0, 500) + "..." : dep.output;
|
|
200
|
-
taskPrompt += `- Task ${dep.index} (${dep.title}): ${truncated}\n`;
|
|
201
|
-
}
|
|
202
|
-
taskPrompt += "\n";
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// The actual task
|
|
207
|
-
taskPrompt += `### What to do\n\n${task.description}\n`;
|
|
208
|
-
if (task.subtasks && task.subtasks.length > 0) {
|
|
209
|
-
taskPrompt += "\n**Sub-tasks:**\n" + task.subtasks.map((st, i) => `${i + 1}. ${st}`).join("\n") + "\n";
|
|
210
|
-
}
|
|
211
|
-
taskPrompt += `\n**Instructions:** Execute this task completely. Create/edit all necessary files. Report what you did.\n`;
|
|
212
|
-
|
|
213
|
-
return { taskPrompt };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// ─── Execute All Tasks (parallel with dependency resolution) ─────
|
|
217
|
-
|
|
218
|
-
async function executePlan(
|
|
219
|
-
tasks: TaskDef[],
|
|
220
|
-
todoFile: string,
|
|
221
|
-
notify: (msg: string, type: "info" | "error" | "warning") => void,
|
|
222
|
-
projectContext?: { title: string; description: string; specSummary: string },
|
|
223
|
-
): Promise<{ results: TaskResult[]; progressFile: string }> {
|
|
224
|
-
const progressFile = todoFile.replace("todo-", "progress-");
|
|
225
|
-
const progressPath = join(plansDir, progressFile);
|
|
226
|
-
const totalTasks = tasks.length;
|
|
227
|
-
|
|
228
|
-
const sharedContext = {
|
|
229
|
-
projectTitle: projectContext?.title || "Project",
|
|
230
|
-
projectDescription: projectContext?.description || "",
|
|
231
|
-
specSummary: projectContext?.specSummary || "",
|
|
232
|
-
completedTasks: [] as Array<{ index: number; title: string; agent: string; output: string }>,
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
notify(`🚀 Executing ${totalTasks} tasks in-session...`, "info");
|
|
236
|
-
|
|
237
|
-
// Build a single comprehensive prompt with ALL tasks
|
|
238
|
-
let megaPrompt = `# 📋 Project: ${sharedContext.projectTitle}\n\n`;
|
|
239
|
-
megaPrompt += `${sharedContext.projectDescription}\n\n`;
|
|
240
|
-
if (sharedContext.specSummary) {
|
|
241
|
-
megaPrompt += `## Spec\n${sharedContext.specSummary}\n\n`;
|
|
242
|
-
}
|
|
243
|
-
megaPrompt += `## Tasks (execute ALL in order)\n\n`;
|
|
244
|
-
|
|
245
|
-
const results: TaskResult[] = [];
|
|
246
|
-
|
|
247
|
-
for (let i = 0; i < tasks.length; i++) {
|
|
248
|
-
const task = tasks[i];
|
|
249
|
-
const { taskPrompt } = executeTaskInSession(task, sharedContext);
|
|
250
|
-
megaPrompt += `---\n\n${taskPrompt}\n\n`;
|
|
251
|
-
results.push({
|
|
252
|
-
taskIndex: i + 1, title: task.title,
|
|
253
|
-
agent: task.agent || "code", status: "success",
|
|
254
|
-
output: "(in-session)", durationMs: 0,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
megaPrompt += `---\n\n## ⚠️ Instructions\n\n`;
|
|
259
|
-
megaPrompt += `Execute ALL ${totalTasks} tasks above **sequentially**. For each task:\n`;
|
|
260
|
-
megaPrompt += `1. Create/edit the required files using your tools\n`;
|
|
261
|
-
megaPrompt += `2. Report what you did briefly\n`;
|
|
262
|
-
megaPrompt += `3. Move to the next task\n\n`;
|
|
263
|
-
megaPrompt += `Do NOT skip any task. Complete the entire project.\n`;
|
|
264
|
-
|
|
265
|
-
// Write progress file
|
|
266
|
-
let progress = `# Progress: ${todoFile}\n\n`;
|
|
267
|
-
progress += `**Started:** ${new Date().toLocaleString()}\n`;
|
|
268
|
-
progress += `**Tasks:** ${totalTasks} | **Mode:** in-session\n\n`;
|
|
269
|
-
for (const r of results) {
|
|
270
|
-
progress += `- Task ${r.taskIndex}: ${r.title} [${r.agent}]\n`;
|
|
271
|
-
}
|
|
272
|
-
await writeFile(progressPath, progress, "utf-8");
|
|
273
|
-
|
|
274
|
-
// Return the mega-prompt as tool result — LLM sees it and executes
|
|
275
|
-
return { results, progressFile, megaPrompt };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// ─── Generate Plan Files ─────────────────────────────────────────
|
|
50
|
+
// ─── Plan File Generators ────────────────────────────────────────
|
|
279
51
|
|
|
280
52
|
function generateSpec(p: {
|
|
281
53
|
title: string; description: string; goals: string[]; requirements: string[];
|
|
@@ -331,20 +103,21 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
331
103
|
return todo;
|
|
332
104
|
}
|
|
333
105
|
|
|
334
|
-
// ─── Orchestrate Tool
|
|
106
|
+
// ─── Orchestrate Tool — Structured planning with prompt-architect pattern ───
|
|
335
107
|
|
|
336
108
|
pi.registerTool({
|
|
337
109
|
name: "orchestrate",
|
|
338
110
|
label: "Project Orchestrator",
|
|
339
|
-
description: "Create a project plan
|
|
340
|
-
promptSnippet: "Plan +
|
|
111
|
+
description: "Create a structured project plan with spec, todo, and task breakdown. Forces the model to decompose the project into goals, requirements, architecture, constraints, and success criteria before execution.",
|
|
112
|
+
promptSnippet: "Plan + structure projects using the prompt-architect pattern. Each task gets [CONTEXT] → [TASK] → [FORMAT] → [CONSTRAINTS].",
|
|
341
113
|
promptGuidelines: [
|
|
342
|
-
"When asked to plan or build a project
|
|
343
|
-
|
|
114
|
+
// NOTE: The old guideline "When asked to plan or build a project, call the orchestrate tool"
|
|
115
|
+
// was REMOVED because it hijacked the /plan command flow. The orchestrate tool is now
|
|
116
|
+
// only called explicitly or when the model decides structured planning is needed outside of /plan.
|
|
117
|
+
"IMPORTANT: Do NOT call orchestrate during /plan orchestration phases. The /plan command handles its own workflow.",
|
|
344
118
|
"Structure each task description using the prompt-architect pattern: [CONTEXT] what exists and why → [TASK] what to do specifically → [FORMAT] expected output → [CONSTRAINTS] rules and limitations.",
|
|
345
|
-
"Assign agent types strategically: 'explore' (read-only analysis
|
|
346
|
-
"Set dependencies to maximize parallelism: tasks without dependencies run simultaneously
|
|
347
|
-
"Order tasks logically: explore → plan → code → test → review. But allow independent tasks at each stage to run in parallel.",
|
|
119
|
+
"Assign agent types strategically: 'explore' (read-only analysis), 'plan' (architecture), 'code' (implementation), 'test' (write+run tests), 'review' (quality audit).",
|
|
120
|
+
"Set dependencies to maximize parallelism: tasks without dependencies run simultaneously.",
|
|
348
121
|
"Set priority=high for critical-path tasks, medium for standard work, low for nice-to-haves.",
|
|
349
122
|
],
|
|
350
123
|
parameters: Type.Object({
|
|
@@ -356,22 +129,29 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
356
129
|
tasks: Type.Array(
|
|
357
130
|
Type.Object({
|
|
358
131
|
title: Type.String({ description: "Clear, action-oriented task title" }),
|
|
359
|
-
description: Type.String({ description: "SELF-CONTAINED task description. Include ALL context the sub-agent needs
|
|
360
|
-
agent: Type.Optional(Type.String({ description: "Agent type: explore
|
|
361
|
-
priority: Type.Optional(Type.String({ description: "high
|
|
362
|
-
dependencies: Type.Optional(Type.Array(Type.Number(), { description: "Task numbers this depends on (1-indexed)
|
|
132
|
+
description: Type.String({ description: "SELF-CONTAINED task description. Include ALL context the sub-agent needs." }),
|
|
133
|
+
agent: Type.Optional(Type.String({ description: "Agent type: explore, plan, code, test, review" })),
|
|
134
|
+
priority: Type.Optional(Type.String({ description: "high, medium, low" })),
|
|
135
|
+
dependencies: Type.Optional(Type.Array(Type.Number(), { description: "Task numbers this depends on (1-indexed)" })),
|
|
363
136
|
subtasks: Type.Optional(Type.Array(Type.String(), { description: "Specific sub-steps within this task" })),
|
|
364
137
|
}),
|
|
365
|
-
{ description: "Ordered list of tasks
|
|
138
|
+
{ description: "Ordered list of tasks" }
|
|
366
139
|
),
|
|
367
|
-
constraints: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Hard constraints:
|
|
368
|
-
successCriteria: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "How to verify the project is complete
|
|
140
|
+
constraints: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Hard constraints: things to avoid, rules" })),
|
|
141
|
+
successCriteria: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "How to verify the project is complete" })),
|
|
369
142
|
}),
|
|
370
143
|
|
|
371
|
-
async execute(_toolCallId, params, _signal, _onUpdate,
|
|
144
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
145
|
+
// Block if /plan orchestration is active — prevent hijacking
|
|
146
|
+
if (orchestrationActive) {
|
|
147
|
+
return {
|
|
148
|
+
content: [{ type: "text", text: "⚠️ Orchestration is already active via /plan. Do NOT call orchestrate during /plan phases. Follow the phase instructions instead." }],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
372
152
|
const raw = params as any;
|
|
373
153
|
|
|
374
|
-
// Normalize string fields to arrays
|
|
154
|
+
// Normalize string fields to arrays
|
|
375
155
|
const toArray = (v: any): string[] => {
|
|
376
156
|
if (!v) return [];
|
|
377
157
|
if (Array.isArray(v)) return v;
|
|
@@ -396,48 +176,22 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
396
176
|
const specFile = `spec-${ts}.md`;
|
|
397
177
|
const todoFile = `todo-${ts}.md`;
|
|
398
178
|
|
|
399
|
-
// Generate and write plan files
|
|
179
|
+
// Generate and write structured plan files
|
|
400
180
|
const spec = generateSpec(p);
|
|
401
181
|
const todo = generateTodo(p.title, p.tasks);
|
|
402
182
|
await writeFile(join(plansDir, specFile), spec, "utf-8");
|
|
403
183
|
await writeFile(join(plansDir, todoFile), todo, "utf-8");
|
|
404
184
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
notify(`📋 Plan created: **${p.title}** (${p.tasks.length} tasks)\nNow executing with sub-agents...`, "info");
|
|
185
|
+
const summary = `📋 **Project "${p.title}" — Structured plan created!**\n\n` +
|
|
186
|
+
`**Spec:** \`${specFile}\` | **Todo:** \`${todoFile}\`\n\n` +
|
|
187
|
+
`**Goals:** ${p.goals.length} | **Requirements:** ${p.requirements.length} | **Tasks:** ${p.tasks.length}\n\n` +
|
|
188
|
+
`The spec and todo files are ready in \`.phi/plans/\`. ` +
|
|
189
|
+
`Use \`/plan ${p.description.slice(0, 100)}\` to execute with the 5-phase orchestrator, ` +
|
|
190
|
+
`or work through the tasks manually.`;
|
|
414
191
|
|
|
415
|
-
// Auto-execute all tasks
|
|
416
|
-
// Build spec summary for shared context
|
|
417
|
-
const specSummary = [
|
|
418
|
-
`Goals: ${p.goals.join("; ")}`,
|
|
419
|
-
`Requirements: ${p.requirements.join("; ")}`,
|
|
420
|
-
p.architecture?.length ? `Architecture: ${p.architecture.join("; ")}` : "",
|
|
421
|
-
p.constraints?.length ? `Constraints: ${p.constraints.join("; ")}` : "",
|
|
422
|
-
].filter(Boolean).join("\n");
|
|
423
|
-
|
|
424
|
-
const { results, progressFile, megaPrompt } = await executePlan(
|
|
425
|
-
p.tasks, todoFile, notify,
|
|
426
|
-
{ title: p.title, description: p.description, specSummary },
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
const header = `**📋 Project "${p.title}" — ${p.tasks.length} tasks planned!**\n` +
|
|
430
|
-
`Plan: \`${specFile}\`, \`${todoFile}\` | Progress: \`${progressFile}\`\n\n` +
|
|
431
|
-
`---\n\n`;
|
|
432
|
-
|
|
433
|
-
// Return the mega-prompt as tool result
|
|
434
|
-
// The LLM sees this and executes all tasks in its current turn
|
|
435
192
|
return {
|
|
436
|
-
content: [{ type: "text", text:
|
|
437
|
-
details: {
|
|
438
|
-
specFile, todoFile, progressFile,
|
|
439
|
-
taskCount: p.tasks.length,
|
|
440
|
-
},
|
|
193
|
+
content: [{ type: "text", text: summary }],
|
|
194
|
+
details: { specFile, todoFile, taskCount: p.tasks.length },
|
|
441
195
|
};
|
|
442
196
|
} catch (error) {
|
|
443
197
|
return {
|
|
@@ -1089,61 +843,12 @@ Tag the note with relevant keywords for vector search.
|
|
|
1089
843
|
// ─── /run Command — Re-execute existing plan ─────────────────────
|
|
1090
844
|
|
|
1091
845
|
pi.registerCommand("run", {
|
|
1092
|
-
description: "Re-execute an existing plan
|
|
1093
|
-
handler: async (
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
const files = (await readdir(plansDir)).sort().reverse();
|
|
1100
|
-
const todoFiles = files.filter(f => f.startsWith("todo-") && f.endsWith(".md"));
|
|
1101
|
-
|
|
1102
|
-
if (todoFiles.length === 0) {
|
|
1103
|
-
ctx.ui.notify("No todo files found. Use `/plan <description>` first.", "warning");
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
const todoFile = todoFiles[0];
|
|
1108
|
-
const todoContent = await readFile(join(plansDir, todoFile), "utf-8");
|
|
1109
|
-
|
|
1110
|
-
// Parse tasks
|
|
1111
|
-
const tasks: TaskDef[] = [];
|
|
1112
|
-
const sections = todoContent.split(/## Task \d+:/);
|
|
1113
|
-
for (let i = 1; i < sections.length; i++) {
|
|
1114
|
-
const section = sections[i];
|
|
1115
|
-
const titleMatch = section.match(/^(.+?)(?:\s*🔴|\s*🟡|\s*🟢)/);
|
|
1116
|
-
const agentMatch = section.match(/\[(\w+)\]/);
|
|
1117
|
-
const descMatch = section.match(/- \[ \] (.+)/);
|
|
1118
|
-
const subtasks: string[] = [];
|
|
1119
|
-
const stMatches = section.matchAll(/ - \[ \] (.+)/g);
|
|
1120
|
-
for (const m of stMatches) subtasks.push(m[1]);
|
|
1121
|
-
|
|
1122
|
-
if (titleMatch && descMatch) {
|
|
1123
|
-
tasks.push({
|
|
1124
|
-
title: titleMatch[1].trim(),
|
|
1125
|
-
agent: agentMatch?.[1] || "code",
|
|
1126
|
-
description: descMatch[1].trim(),
|
|
1127
|
-
subtasks: subtasks.length > 0 ? subtasks : undefined,
|
|
1128
|
-
});
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
if (tasks.length === 0) {
|
|
1133
|
-
ctx.ui.notify("Could not parse tasks from todo file.", "error");
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
const confirmed = await ctx.ui.confirm(
|
|
1138
|
-
"Re-execute Plan",
|
|
1139
|
-
`${tasks.length} tasks found in \`${todoFile}\`.\nEach will spawn an isolated sub-agent.\n\nProceed?`
|
|
846
|
+
description: "Re-execute an existing plan (deprecated — use /plan instead)",
|
|
847
|
+
handler: async (_args, ctx) => {
|
|
848
|
+
ctx.ui.notify(
|
|
849
|
+
"⚠️ `/run` is deprecated. Use `/plan <description>` to create and execute a new plan with the 5-phase orchestrator.",
|
|
850
|
+
"warning",
|
|
1140
851
|
);
|
|
1141
|
-
if (!confirmed) {
|
|
1142
|
-
ctx.ui.notify("Cancelled.", "info");
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
await executePlan(tasks, todoFile, (msg, type) => ctx.ui.notify(msg, type));
|
|
1147
852
|
},
|
|
1148
853
|
});
|
|
1149
854
|
|