@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.
@@ -1,411 +1,296 @@
1
1
  /**
2
- * Orchestrator Extension - High-level project planning and task orchestration
2
+ * Orchestrator Extension - Project planning and task management
3
3
  *
4
- * Provides tools to break down high-level project descriptions into structured
5
- * specifications and actionable task lists. Helps manage complex projects by
6
- * creating organized plans and tracking progress.
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
- * Features:
9
- * - /plan command for interactive planning
10
- * - orchestrate tool callable by LLM
11
- * - Automatic directory structure creation
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
- * Usage:
16
- * 1. Copy to packages/coding-agent/extensions/phi/orchestrator.ts
17
- * 2. Use /plan <description> to create project plans
18
- * 3. Plans are stored in .phi/plans/ directory
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
- * Ensure plans directory exists
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
- ${plan.requirements.length > 0
163
- ? plan.requirements.map(req => `- ${req}`).join('\n')
164
- : '- To be defined based on project analysis'
165
- }
166
-
167
- ## Architecture
168
-
169
- ${plan.architecture.length > 0
170
- ? plan.architecture.map(arch => `- ${arch}`).join('\n')
171
- : '- To be designed during implementation planning'
172
- }
173
-
174
- ## Implementation Notes
175
-
176
- - This specification was generated automatically from the project description
177
- - Review and refine requirements based on detailed analysis
178
- - Update architecture section with technical decisions
179
- - Use the corresponding TODO file to track progress
180
-
181
- ---
182
-
183
- *Generated by Phi Code Orchestrator*
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
- * Generate TODO markdown content
189
- */
190
- function generateTodoContent(plan: ProjectPlan): string {
191
- const timestamp = new Date(plan.timestamp).toLocaleString();
192
-
193
- let todoContent = `# TODO: ${plan.title}
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
- **Created:** ${timestamp}
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
- ## Tasks
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
- plan.tasks.forEach(task => {
202
- const checkbox = task.status === "done" ? "[x]" : "[ ]";
203
- todoContent += `${checkbox} **Task ${task.id}:** ${task.description}\n`;
204
-
205
- if (task.subtasks) {
206
- task.subtasks.forEach(subtask => {
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
- ## Notes
136
+ spec += `---\n*Generated by Phi Code Orchestrator*\n`;
222
137
 
223
- - Update task status by changing [ ] to [x] when completed
224
- - Add subtasks under main tasks as needed
225
- - Use this file to track detailed progress
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
- *Generated by Phi Code Orchestrator*
230
- `;
148
+ todo += `## Task ${i + 1}: ${t.title}${prioTag}${agentTag}${depsTag}\n\n`;
149
+ todo += `- [ ] ${t.description}\n`;
231
150
 
232
- return todoContent;
233
- }
234
-
235
- /**
236
- * Create project plan files
237
- */
238
- async function createPlanFiles(description: string): Promise<{ specFile: string; todoFile: string; plan: ProjectPlan }> {
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
- * Orchestrate tool - Create project plans programmatically
265
- */
266
- pi.registerTool({
267
- name: "orchestrate",
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
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
277
- const { description } = params as { description: string };
165
+ // Write files
166
+ await writeFile(join(plansDir, specFile), spec, "utf-8");
167
+ await writeFile(join(plansDir, todoFile), todo, "utf-8");
278
168
 
279
- try {
280
- const result = await createPlanFiles(description);
169
+ const summary = `**✅ Project plan created!**
281
170
 
282
- const summary = `**Project orchestration completed!**
171
+ 📋 **${p.title}**
283
172
 
284
- **Generated files:**
285
- - Specification: ${result.specFile}
286
- - Task list: ${result.todoFile}
173
+ **Files:**
174
+ - \`${specFile}\` — Full specification (goals, requirements, architecture, constraints)
175
+ - \`${todoFile}\` — Actionable task list with priorities and agent assignments
287
176
 
288
- **Project:** ${result.plan.title}
289
- **Tasks created:** ${result.plan.tasks.length}
290
- **Goals identified:** ${result.plan.goals.length}
291
- **Requirements:** ${result.plan.requirements.length}
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 refine the specification
295
- 2. Update task details in the TODO file
296
- 3. Begin implementation following the task order
297
- 4. Mark tasks as completed by updating the TODO file
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 are saved in .phi/plans/ directory.`;
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
- * /plan command - Interactive project planning
322
- */
204
+ // ─── /plan Command ───────────────────────────────────────────────
205
+
323
206
  pi.registerCommand("plan", {
324
- description: "Create a structured project plan from a description",
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("Usage: /plan <project description>", "warning");
330
- return;
331
- }
212
+ ctx.ui.notify(`**Usage:** \`/plan <project description>\`
332
213
 
333
- try {
334
- ctx.ui.notify("Creating project plan...", "info");
335
-
336
- const result = await createPlanFiles(description);
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
- const message = `✅ Project plan created!
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
- 📋 **${result.plan.title}**
341
- 📁 Spec: ${result.specFile}
342
- 📋 TODO: ${result.todoFile}
343
- 🎯 ${result.plan.tasks.length} tasks identified
225
+ 💡 The more detail you provide, the better the plan.`, "info");
226
+ return;
227
+ }
344
228
 
345
- Open .phi/plans/ to review your project files.`;
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
- ctx.ui.notify(message, "info");
233
+ Project description: ${description}
348
234
 
349
- } catch (error) {
350
- ctx.ui.notify(`Failed to create plan: ${error}`, "error");
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
- * /plans command - List existing plans
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 specFiles = files.filter(f => f.startsWith('spec-') && f.endsWith('.md'));
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 (specFiles.length === 0) {
367
- ctx.ui.notify("No project plans found. Use /plan <description> to create one.", "info");
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 message = `📁 **Project Plans** (${specFiles.length} found)\n\n`;
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
- planPairs.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
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 specContent = await readFile(join(plansDir, pair.spec), 'utf-8');
391
- const titleMatch = specContent.match(/^# (.+)$/m);
392
- const title = titleMatch ? titleMatch[1] : pair.spec;
393
-
394
- const date = pair.timestamp.replace(/_/g, ' ').replace(/-/g, ':');
395
- message += `📋 **${title}**\n`;
396
- message += ` 📄 ${pair.spec}\n`;
397
- message += ` ✅ ${pair.todo}\n`;
398
- message += ` 🕒 ${date}\n\n`;
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
- message += `📋 ${pair.spec} / ${pair.todo}\n\n`;
285
+ output += `📋 \`${specFile}\`\n\n`;
401
286
  }
402
287
  }
403
288
 
404
- ctx.ui.notify(message, "info");
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.56.6",
3
+ "version": "0.57.1",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {