@phi-code-admin/phi-code 0.56.6 → 0.57.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/extensions/phi/README.md +34 -214
- package/extensions/phi/agents.ts +202 -0
- package/extensions/phi/benchmark.ts +611 -398
- package/extensions/phi/init.ts +447 -312
- package/extensions/phi/memory.ts +36 -18
- package/extensions/phi/orchestrator.ts +210 -325
- package/package.json +1 -1
|
@@ -1,411 +1,296 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Orchestrator Extension -
|
|
2
|
+
* Orchestrator Extension - Project planning and task management
|
|
3
3
|
*
|
|
4
|
-
* Provides tools
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Provides tools for the LLM to create structured project plans:
|
|
5
|
+
* - /plan: Interactive planning command
|
|
6
|
+
* - /plans: List and manage existing plans
|
|
7
|
+
* - orchestrate tool: Create spec.md + todo.md from structured input
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* - Timestamped spec and todo files
|
|
13
|
-
* - Task status tracking (pending/done)
|
|
9
|
+
* Architecture:
|
|
10
|
+
* The LLM analyzes the user's request (using prompt-architect skill patterns
|
|
11
|
+
* if available), then calls the orchestrate tool with structured data.
|
|
12
|
+
* The tool writes files to disk. The LLM does the thinking, the tool does the I/O.
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* Integration with Prompt Architect skill:
|
|
15
|
+
* When the prompt-architect skill is loaded, the LLM uses its patterns
|
|
16
|
+
* (ROLE/CONTEXT/TASK/FORMAT/CONSTRAINTS) to structure the spec.
|
|
17
|
+
* The skill-loader extension handles this automatically.
|
|
19
18
|
*/
|
|
20
19
|
|
|
21
20
|
import { Type } from "@sinclair/typebox";
|
|
22
21
|
import type { ExtensionAPI } from "phi-code";
|
|
23
|
-
import { writeFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
22
|
+
import { writeFile, mkdir, readdir, readFile, access } from "node:fs/promises";
|
|
24
23
|
import { join } from "node:path";
|
|
25
|
-
|
|
26
|
-
interface TaskItem {
|
|
27
|
-
id: number;
|
|
28
|
-
description: string;
|
|
29
|
-
status: "pending" | "done";
|
|
30
|
-
subtasks?: TaskItem[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface ProjectPlan {
|
|
34
|
-
title: string;
|
|
35
|
-
description: string;
|
|
36
|
-
timestamp: string;
|
|
37
|
-
goals: string[];
|
|
38
|
-
requirements: string[];
|
|
39
|
-
architecture: string[];
|
|
40
|
-
tasks: TaskItem[];
|
|
41
|
-
}
|
|
24
|
+
import { existsSync } from "node:fs";
|
|
42
25
|
|
|
43
26
|
export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
44
27
|
const plansDir = join(process.cwd(), ".phi", "plans");
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*/
|
|
49
|
-
async function ensurePlansDirectory() {
|
|
50
|
-
try {
|
|
51
|
-
await mkdir(plansDir, { recursive: true });
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.warn("Failed to create plans directory:", error);
|
|
54
|
-
}
|
|
29
|
+
async function ensurePlansDir() {
|
|
30
|
+
await mkdir(plansDir, { recursive: true });
|
|
55
31
|
}
|
|
56
32
|
|
|
57
|
-
|
|
58
|
-
* Generate timestamp for file naming
|
|
59
|
-
*/
|
|
60
|
-
function getTimestamp(): string {
|
|
33
|
+
function timestamp(): string {
|
|
61
34
|
return new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
62
35
|
}
|
|
63
36
|
|
|
64
|
-
|
|
65
|
-
* Analyze description and create structured project plan
|
|
66
|
-
*/
|
|
67
|
-
function analyzeProject(description: string): ProjectPlan {
|
|
68
|
-
const timestamp = new Date().toISOString();
|
|
69
|
-
|
|
70
|
-
// Extract title from first sentence or use generic title
|
|
71
|
-
let title = "Project Plan";
|
|
72
|
-
const firstSentence = description.split('.')[0].trim();
|
|
73
|
-
if (firstSentence.length > 10 && firstSentence.length < 100) {
|
|
74
|
-
title = firstSentence;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Basic analysis to extract goals, requirements, and tasks
|
|
78
|
-
const lines = description.split('\n').filter(line => line.trim());
|
|
79
|
-
const goals: string[] = [];
|
|
80
|
-
const requirements: string[] = [];
|
|
81
|
-
const architecture: string[] = [];
|
|
82
|
-
const tasks: TaskItem[] = [];
|
|
83
|
-
|
|
84
|
-
// Simple pattern matching for structure
|
|
85
|
-
let taskId = 1;
|
|
86
|
-
for (const line of lines) {
|
|
87
|
-
const trimmed = line.trim();
|
|
88
|
-
|
|
89
|
-
// Look for action words to identify tasks
|
|
90
|
-
if (trimmed.match(/^(create|build|implement|develop|add|setup|configure|install|write|design|test)/i)) {
|
|
91
|
-
tasks.push({
|
|
92
|
-
id: taskId++,
|
|
93
|
-
description: trimmed,
|
|
94
|
-
status: "pending"
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
// Look for requirements keywords
|
|
98
|
-
else if (trimmed.match(/(require|need|must|should have|depend)/i)) {
|
|
99
|
-
requirements.push(trimmed);
|
|
100
|
-
}
|
|
101
|
-
// Look for architecture/tech keywords
|
|
102
|
-
else if (trimmed.match(/(using|with|technology|framework|database|api|service)/i)) {
|
|
103
|
-
architecture.push(trimmed);
|
|
104
|
-
}
|
|
105
|
-
// Everything else could be goals
|
|
106
|
-
else if (trimmed.length > 10) {
|
|
107
|
-
goals.push(trimmed);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// If no explicit tasks found, break down description into logical steps
|
|
112
|
-
if (tasks.length === 0) {
|
|
113
|
-
const generalTasks = [
|
|
114
|
-
"Analyze requirements and define scope",
|
|
115
|
-
"Design system architecture",
|
|
116
|
-
"Set up development environment",
|
|
117
|
-
"Implement core functionality",
|
|
118
|
-
"Add tests and documentation",
|
|
119
|
-
"Deploy and validate solution"
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
generalTasks.forEach((task, index) => {
|
|
123
|
-
tasks.push({
|
|
124
|
-
id: index + 1,
|
|
125
|
-
description: task,
|
|
126
|
-
status: "pending"
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
title,
|
|
133
|
-
description,
|
|
134
|
-
timestamp,
|
|
135
|
-
goals: goals.length > 0 ? goals : [description],
|
|
136
|
-
requirements,
|
|
137
|
-
architecture,
|
|
138
|
-
tasks
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Generate specification markdown content
|
|
144
|
-
*/
|
|
145
|
-
function generateSpecContent(plan: ProjectPlan): string {
|
|
146
|
-
const timestamp = new Date(plan.timestamp).toLocaleString();
|
|
147
|
-
|
|
148
|
-
return `# ${plan.title}
|
|
149
|
-
|
|
150
|
-
**Created:** ${timestamp}
|
|
151
|
-
|
|
152
|
-
## Description
|
|
153
|
-
|
|
154
|
-
${plan.description}
|
|
155
|
-
|
|
156
|
-
## Goals
|
|
157
|
-
|
|
158
|
-
${plan.goals.map(goal => `- ${goal}`).join('\n')}
|
|
159
|
-
|
|
160
|
-
## Requirements
|
|
37
|
+
// ─── Orchestrate Tool ────────────────────────────────────────────
|
|
161
38
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
39
|
+
pi.registerTool({
|
|
40
|
+
name: "orchestrate",
|
|
41
|
+
label: "Project Orchestrator",
|
|
42
|
+
description: "Create structured project plan files (spec.md + todo.md) from analyzed project requirements. Call this AFTER you have analyzed the user's request and structured it into goals, requirements, architecture decisions, and tasks.",
|
|
43
|
+
promptSnippet: "Create spec.md + todo.md project plans. Use prompt-architect patterns to structure specs before calling.",
|
|
44
|
+
promptGuidelines: [
|
|
45
|
+
"When asked to plan a project: first analyze the request thoroughly, then call orchestrate with structured data.",
|
|
46
|
+
"Use the prompt-architect skill patterns (ROLE/CONTEXT/TASK/FORMAT/CONSTRAINTS) when structuring specifications.",
|
|
47
|
+
"Break tasks into small, actionable items. Each task should be completable by a single sub-agent.",
|
|
48
|
+
"Assign agent types to tasks: 'explore' for analysis, 'plan' for design, 'code' for implementation, 'test' for validation, 'review' for quality.",
|
|
49
|
+
],
|
|
50
|
+
parameters: Type.Object({
|
|
51
|
+
title: Type.String({ description: "Project title" }),
|
|
52
|
+
description: Type.String({ description: "Full project description with context" }),
|
|
53
|
+
goals: Type.Array(Type.String(), { description: "List of project goals" }),
|
|
54
|
+
requirements: Type.Array(Type.String(), { description: "Technical and functional requirements" }),
|
|
55
|
+
architecture: Type.Optional(Type.Array(Type.String(), { description: "Architecture decisions and tech stack" })),
|
|
56
|
+
tasks: Type.Array(
|
|
57
|
+
Type.Object({
|
|
58
|
+
title: Type.String({ description: "Task title" }),
|
|
59
|
+
description: Type.String({ description: "Detailed task description" }),
|
|
60
|
+
agent: Type.Optional(Type.String({ description: "Recommended agent: explore, plan, code, test, or review" })),
|
|
61
|
+
priority: Type.Optional(Type.String({ description: "high, medium, or low" })),
|
|
62
|
+
dependencies: Type.Optional(Type.Array(Type.Number(), { description: "IDs of tasks this depends on" })),
|
|
63
|
+
subtasks: Type.Optional(Type.Array(Type.String(), { description: "Sub-task descriptions" })),
|
|
64
|
+
}),
|
|
65
|
+
{ description: "Ordered list of tasks" }
|
|
66
|
+
),
|
|
67
|
+
constraints: Type.Optional(Type.Array(Type.String(), { description: "Project constraints and limitations" })),
|
|
68
|
+
successCriteria: Type.Optional(Type.Array(Type.String(), { description: "How to verify the project is done" })),
|
|
69
|
+
}),
|
|
186
70
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
71
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
72
|
+
const p = params as {
|
|
73
|
+
title: string;
|
|
74
|
+
description: string;
|
|
75
|
+
goals: string[];
|
|
76
|
+
requirements: string[];
|
|
77
|
+
architecture?: string[];
|
|
78
|
+
tasks: Array<{
|
|
79
|
+
title: string;
|
|
80
|
+
description: string;
|
|
81
|
+
agent?: string;
|
|
82
|
+
priority?: string;
|
|
83
|
+
dependencies?: number[];
|
|
84
|
+
subtasks?: string[];
|
|
85
|
+
}>;
|
|
86
|
+
constraints?: string[];
|
|
87
|
+
successCriteria?: string[];
|
|
88
|
+
};
|
|
194
89
|
|
|
195
|
-
|
|
90
|
+
try {
|
|
91
|
+
await ensurePlansDir();
|
|
92
|
+
const ts = timestamp();
|
|
93
|
+
const specFile = `spec-${ts}.md`;
|
|
94
|
+
const todoFile = `todo-${ts}.md`;
|
|
95
|
+
|
|
96
|
+
// ─── Generate spec.md ─────────────────────────────────
|
|
97
|
+
let spec = `# ${p.title}\n\n`;
|
|
98
|
+
spec += `**Created:** ${new Date().toLocaleString()}\n\n`;
|
|
99
|
+
spec += `## Description\n\n${p.description}\n\n`;
|
|
100
|
+
|
|
101
|
+
spec += `## Goals\n\n`;
|
|
102
|
+
p.goals.forEach((g, i) => { spec += `${i + 1}. ${g}\n`; });
|
|
103
|
+
spec += "\n";
|
|
104
|
+
|
|
105
|
+
spec += `## Requirements\n\n`;
|
|
106
|
+
p.requirements.forEach(r => { spec += `- ${r}\n`; });
|
|
107
|
+
spec += "\n";
|
|
108
|
+
|
|
109
|
+
if (p.architecture && p.architecture.length > 0) {
|
|
110
|
+
spec += `## Architecture\n\n`;
|
|
111
|
+
p.architecture.forEach(a => { spec += `- ${a}\n`; });
|
|
112
|
+
spec += "\n";
|
|
113
|
+
}
|
|
196
114
|
|
|
197
|
-
|
|
115
|
+
if (p.constraints && p.constraints.length > 0) {
|
|
116
|
+
spec += `## Constraints\n\n`;
|
|
117
|
+
p.constraints.forEach(c => { spec += `- ${c}\n`; });
|
|
118
|
+
spec += "\n";
|
|
119
|
+
}
|
|
198
120
|
|
|
199
|
-
|
|
121
|
+
if (p.successCriteria && p.successCriteria.length > 0) {
|
|
122
|
+
spec += `## Success Criteria\n\n`;
|
|
123
|
+
p.successCriteria.forEach(s => { spec += `- [ ] ${s}\n`; });
|
|
124
|
+
spec += "\n";
|
|
125
|
+
}
|
|
200
126
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const subCheckbox = subtask.status === "done" ? "[x]" : "[ ]";
|
|
208
|
-
todoContent += ` ${subCheckbox} ${subtask.description}\n`;
|
|
127
|
+
spec += `## Task Overview\n\n`;
|
|
128
|
+
spec += `| # | Task | Agent | Priority | Dependencies |\n`;
|
|
129
|
+
spec += `|---|------|-------|----------|-------------|\n`;
|
|
130
|
+
p.tasks.forEach((t, i) => {
|
|
131
|
+
const deps = t.dependencies?.map(d => `#${d}`).join(", ") || "—";
|
|
132
|
+
spec += `| ${i + 1} | ${t.title} | ${t.agent || "code"} | ${t.priority || "medium"} | ${deps} |\n`;
|
|
209
133
|
});
|
|
210
|
-
|
|
211
|
-
todoContent += '\n';
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
todoContent += `
|
|
215
|
-
## Progress
|
|
216
|
-
|
|
217
|
-
- Total tasks: ${plan.tasks.length}
|
|
218
|
-
- Completed: ${plan.tasks.filter(t => t.status === "done").length}
|
|
219
|
-
- Remaining: ${plan.tasks.filter(t => t.status === "pending").length}
|
|
134
|
+
spec += "\n";
|
|
220
135
|
|
|
221
|
-
|
|
136
|
+
spec += `---\n*Generated by Phi Code Orchestrator*\n`;
|
|
222
137
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
138
|
+
// ─── Generate todo.md ─────────────────────────────────
|
|
139
|
+
let todo = `# TODO: ${p.title}\n\n`;
|
|
140
|
+
todo += `**Created:** ${new Date().toLocaleString()}\n`;
|
|
141
|
+
todo += `**Tasks:** ${p.tasks.length}\n\n`;
|
|
226
142
|
|
|
227
|
-
|
|
143
|
+
p.tasks.forEach((t, i) => {
|
|
144
|
+
const agentTag = t.agent ? ` [${t.agent}]` : "";
|
|
145
|
+
const prioTag = t.priority === "high" ? " 🔴" : t.priority === "low" ? " 🟢" : " 🟡";
|
|
146
|
+
const depsTag = t.dependencies?.length ? ` (after #${t.dependencies.join(", #")})` : "";
|
|
228
147
|
|
|
229
|
-
|
|
230
|
-
`;
|
|
148
|
+
todo += `## Task ${i + 1}: ${t.title}${prioTag}${agentTag}${depsTag}\n\n`;
|
|
149
|
+
todo += `- [ ] ${t.description}\n`;
|
|
231
150
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
await ensurePlansDirectory();
|
|
240
|
-
|
|
241
|
-
const plan = analyzeProject(description);
|
|
242
|
-
const timestamp = getTimestamp();
|
|
243
|
-
|
|
244
|
-
const specFilename = `spec-${timestamp}.md`;
|
|
245
|
-
const todoFilename = `todo-${timestamp}.md`;
|
|
246
|
-
|
|
247
|
-
const specPath = join(plansDir, specFilename);
|
|
248
|
-
const todoPath = join(plansDir, todoFilename);
|
|
249
|
-
|
|
250
|
-
const specContent = generateSpecContent(plan);
|
|
251
|
-
const todoContent = generateTodoContent(plan);
|
|
252
|
-
|
|
253
|
-
await writeFile(specPath, specContent, 'utf-8');
|
|
254
|
-
await writeFile(todoPath, todoContent, 'utf-8');
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
specFile: specFilename,
|
|
258
|
-
todoFile: todoFilename,
|
|
259
|
-
plan
|
|
260
|
-
};
|
|
261
|
-
}
|
|
151
|
+
if (t.subtasks) {
|
|
152
|
+
t.subtasks.forEach(st => {
|
|
153
|
+
todo += ` - [ ] ${st}\n`;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
todo += "\n";
|
|
157
|
+
});
|
|
262
158
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
label: "Project Orchestrator",
|
|
269
|
-
description: "Break down high-level project descriptions into structured specifications and task lists",
|
|
270
|
-
parameters: Type.Object({
|
|
271
|
-
description: Type.String({
|
|
272
|
-
description: "High-level project description to analyze and plan"
|
|
273
|
-
}),
|
|
274
|
-
}),
|
|
159
|
+
todo += `---\n\n## Progress\n\n`;
|
|
160
|
+
todo += `- Total: ${p.tasks.length} tasks\n`;
|
|
161
|
+
todo += `- High priority: ${p.tasks.filter(t => t.priority === "high").length}\n`;
|
|
162
|
+
todo += `- Agents needed: ${[...new Set(p.tasks.map(t => t.agent || "code"))].join(", ")}\n\n`;
|
|
163
|
+
todo += `*Update [ ] to [x] when tasks are completed.*\n`;
|
|
275
164
|
|
|
276
|
-
|
|
277
|
-
|
|
165
|
+
// Write files
|
|
166
|
+
await writeFile(join(plansDir, specFile), spec, "utf-8");
|
|
167
|
+
await writeFile(join(plansDir, todoFile), todo, "utf-8");
|
|
278
168
|
|
|
279
|
-
|
|
280
|
-
const result = await createPlanFiles(description);
|
|
169
|
+
const summary = `**✅ Project plan created!**
|
|
281
170
|
|
|
282
|
-
|
|
171
|
+
📋 **${p.title}**
|
|
283
172
|
|
|
284
|
-
**
|
|
285
|
-
-
|
|
286
|
-
-
|
|
173
|
+
**Files:**
|
|
174
|
+
- \`${specFile}\` — Full specification (goals, requirements, architecture, constraints)
|
|
175
|
+
- \`${todoFile}\` — Actionable task list with priorities and agent assignments
|
|
287
176
|
|
|
288
|
-
**
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
177
|
+
**Summary:**
|
|
178
|
+
- ${p.goals.length} goals
|
|
179
|
+
- ${p.requirements.length} requirements
|
|
180
|
+
- ${p.tasks.length} tasks (${p.tasks.filter(t => t.priority === "high").length} high priority)
|
|
181
|
+
- Agents: ${[...new Set(p.tasks.map(t => t.agent || "code"))].join(", ")}
|
|
292
182
|
|
|
293
183
|
**Next steps:**
|
|
294
|
-
1. Review and
|
|
295
|
-
2.
|
|
296
|
-
3.
|
|
297
|
-
4. Mark tasks
|
|
184
|
+
1. Review the spec and todo files
|
|
185
|
+
2. Start with high-priority tasks
|
|
186
|
+
3. Follow dependency order
|
|
187
|
+
4. Mark tasks [x] when done
|
|
298
188
|
|
|
299
|
-
Files
|
|
189
|
+
Files saved in \`.phi/plans/\``;
|
|
300
190
|
|
|
301
191
|
return {
|
|
302
192
|
content: [{ type: "text", text: summary }],
|
|
303
|
-
details: {
|
|
304
|
-
specFile: result.specFile,
|
|
305
|
-
todoFile: result.todoFile,
|
|
306
|
-
taskCount: result.plan.tasks.length,
|
|
307
|
-
project: result.plan.title
|
|
308
|
-
}
|
|
193
|
+
details: { specFile, todoFile, taskCount: p.tasks.length, title: p.title },
|
|
309
194
|
};
|
|
310
|
-
|
|
311
195
|
} catch (error) {
|
|
312
196
|
return {
|
|
313
197
|
content: [{ type: "text", text: `Orchestration failed: ${error}` }],
|
|
314
|
-
details: { error: String(error) }
|
|
198
|
+
details: { error: String(error) },
|
|
315
199
|
};
|
|
316
200
|
}
|
|
317
201
|
},
|
|
318
202
|
});
|
|
319
203
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
*/
|
|
204
|
+
// ─── /plan Command ───────────────────────────────────────────────
|
|
205
|
+
|
|
323
206
|
pi.registerCommand("plan", {
|
|
324
|
-
description: "Create a structured project plan
|
|
207
|
+
description: "Create a structured project plan (the LLM analyzes your description, then creates spec + todo files)",
|
|
325
208
|
handler: async (args, ctx) => {
|
|
326
209
|
const description = args.trim();
|
|
327
210
|
|
|
328
211
|
if (!description) {
|
|
329
|
-
ctx.ui.notify(
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
212
|
+
ctx.ui.notify(`**Usage:** \`/plan <project description>\`
|
|
332
213
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
214
|
+
**Examples:**
|
|
215
|
+
/plan Build a REST API for user authentication with JWT tokens
|
|
216
|
+
/plan Migrate the frontend from React to Next.js App Router
|
|
217
|
+
/plan Add comprehensive test coverage to the payment module
|
|
337
218
|
|
|
338
|
-
|
|
219
|
+
The LLM will:
|
|
220
|
+
1. Analyze your description using prompt-architect patterns
|
|
221
|
+
2. Identify goals, requirements, architecture decisions
|
|
222
|
+
3. Break down into tasks with agent assignments and priorities
|
|
223
|
+
4. Create spec.md + todo.md files in .phi/plans/
|
|
339
224
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
🎯 ${result.plan.tasks.length} tasks identified
|
|
225
|
+
💡 The more detail you provide, the better the plan.`, "info");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
344
228
|
|
|
345
|
-
|
|
229
|
+
// Send as user message to trigger the LLM to analyze and call orchestrate
|
|
230
|
+
ctx.sendUserMessage(
|
|
231
|
+
`Please analyze this project request and create a structured plan using the orchestrate tool.
|
|
346
232
|
|
|
347
|
-
|
|
233
|
+
Project description: ${description}
|
|
348
234
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
235
|
+
Instructions:
|
|
236
|
+
- Identify clear goals, requirements, and architecture decisions
|
|
237
|
+
- Break the work into small, actionable tasks (each doable by one agent)
|
|
238
|
+
- Assign the best agent type to each task: explore (analysis), plan (design), code (implementation), test (validation), review (quality)
|
|
239
|
+
- Set priorities (high/medium/low) and dependencies between tasks
|
|
240
|
+
- Define success criteria for the project
|
|
241
|
+
- If the prompt-architect skill is available, use its ROLE/CONTEXT/TASK/FORMAT patterns to structure the specification`
|
|
242
|
+
);
|
|
352
243
|
},
|
|
353
244
|
});
|
|
354
245
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
*/
|
|
246
|
+
// ─── /plans Command ──────────────────────────────────────────────
|
|
247
|
+
|
|
358
248
|
pi.registerCommand("plans", {
|
|
359
249
|
description: "List existing project plans",
|
|
360
250
|
handler: async (_args, ctx) => {
|
|
361
251
|
try {
|
|
252
|
+
if (!existsSync(plansDir)) {
|
|
253
|
+
ctx.ui.notify("No plans yet. Use `/plan <description>` to create one.", "info");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
362
257
|
const files = await readdir(plansDir);
|
|
363
|
-
const
|
|
364
|
-
const todoFiles = files.filter(f => f.startsWith('todo-') && f.endsWith('.md'));
|
|
258
|
+
const specs = files.filter(f => f.startsWith("spec-") && f.endsWith(".md")).sort().reverse();
|
|
365
259
|
|
|
366
|
-
if (
|
|
367
|
-
ctx.ui.notify("No
|
|
260
|
+
if (specs.length === 0) {
|
|
261
|
+
ctx.ui.notify("No plans found. Use `/plan <description>` to create one.", "info");
|
|
368
262
|
return;
|
|
369
263
|
}
|
|
370
264
|
|
|
371
|
-
let
|
|
372
|
-
|
|
373
|
-
// Group by timestamp
|
|
374
|
-
const planPairs: Array<{ spec: string; todo: string; timestamp: string }> = [];
|
|
375
|
-
|
|
376
|
-
for (const specFile of specFiles) {
|
|
377
|
-
const timestamp = specFile.replace('spec-', '').replace('.md', '');
|
|
378
|
-
const todoFile = `todo-${timestamp}.md`;
|
|
379
|
-
|
|
380
|
-
if (todoFiles.includes(todoFile)) {
|
|
381
|
-
planPairs.push({ spec: specFile, todo: todoFile, timestamp });
|
|
382
|
-
}
|
|
383
|
-
}
|
|
265
|
+
let output = `📁 **Project Plans** (${specs.length})\n\n`;
|
|
384
266
|
|
|
385
|
-
|
|
267
|
+
for (const specFile of specs) {
|
|
268
|
+
const ts = specFile.replace("spec-", "").replace(".md", "");
|
|
269
|
+
const todoFile = `todo-${ts}.md`;
|
|
386
270
|
|
|
387
|
-
for (const pair of planPairs) {
|
|
388
|
-
// Try to read spec title
|
|
389
271
|
try {
|
|
390
|
-
const
|
|
391
|
-
const titleMatch =
|
|
392
|
-
const title = titleMatch ? titleMatch[1] :
|
|
393
|
-
|
|
394
|
-
const date =
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
272
|
+
const content = await readFile(join(plansDir, specFile), "utf-8");
|
|
273
|
+
const titleMatch = content.match(/^# (.+)$/m);
|
|
274
|
+
const title = titleMatch ? titleMatch[1] : specFile;
|
|
275
|
+
const taskCount = (content.match(/\| \d+ \|/g) || []).length;
|
|
276
|
+
const date = ts.replace(/_/g, " ").substring(0, 10);
|
|
277
|
+
|
|
278
|
+
const hasTodo = files.includes(todoFile);
|
|
279
|
+
|
|
280
|
+
output += `📋 **${title}** (${date})\n`;
|
|
281
|
+
output += ` Spec: \`${specFile}\`${hasTodo ? ` | Todo: \`${todoFile}\`` : ""}\n`;
|
|
282
|
+
if (taskCount > 0) output += ` Tasks: ${taskCount}\n`;
|
|
283
|
+
output += "\n";
|
|
399
284
|
} catch {
|
|
400
|
-
|
|
285
|
+
output += `📋 \`${specFile}\`\n\n`;
|
|
401
286
|
}
|
|
402
287
|
}
|
|
403
288
|
|
|
404
|
-
|
|
405
|
-
|
|
289
|
+
output += `_Use \`read .phi/plans/<file>\` to view a plan._`;
|
|
290
|
+
ctx.ui.notify(output, "info");
|
|
406
291
|
} catch (error) {
|
|
407
292
|
ctx.ui.notify(`Failed to list plans: ${error}`, "error");
|
|
408
293
|
}
|
|
409
294
|
},
|
|
410
295
|
});
|
|
411
|
-
}
|
|
296
|
+
}
|