@phi-code-admin/phi-code 0.74.0 → 0.74.2
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/orchestrator.ts +177 -3
- package/package.json +1 -1
|
@@ -16,12 +16,24 @@
|
|
|
16
16
|
* /plans — List plans and their execution status
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { Type } from "@sinclair/typebox";
|
|
19
20
|
import type { ExtensionAPI } from "phi-code";
|
|
20
21
|
import { writeFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
21
22
|
import { join } from "node:path";
|
|
22
23
|
import { existsSync, readFileSync } from "node:fs";
|
|
23
24
|
import { homedir } from "node:os";
|
|
24
25
|
|
|
26
|
+
// ─── Types ───────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
interface TaskDef {
|
|
29
|
+
title: string;
|
|
30
|
+
description: string;
|
|
31
|
+
agent?: string;
|
|
32
|
+
priority?: string;
|
|
33
|
+
dependencies?: number[];
|
|
34
|
+
subtasks?: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
// ─── Extension ───────────────────────────────────────────────────────────
|
|
26
38
|
|
|
27
39
|
export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
@@ -35,6 +47,161 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
35
47
|
return new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
36
48
|
}
|
|
37
49
|
|
|
50
|
+
// ─── Plan File Generators ────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
function generateSpec(p: {
|
|
53
|
+
title: string; description: string; goals: string[]; requirements: string[];
|
|
54
|
+
architecture?: string[]; constraints?: string[]; successCriteria?: string[]; tasks: TaskDef[];
|
|
55
|
+
}): string {
|
|
56
|
+
let spec = `# ${p.title}\n\n`;
|
|
57
|
+
spec += `**Created:** ${new Date().toLocaleString()}\n\n`;
|
|
58
|
+
spec += `## Description\n\n${p.description}\n\n`;
|
|
59
|
+
spec += `## Goals\n\n`;
|
|
60
|
+
p.goals.forEach((g, i) => { spec += `${i + 1}. ${g}\n`; });
|
|
61
|
+
spec += "\n## Requirements\n\n";
|
|
62
|
+
p.requirements.forEach(r => { spec += `- ${r}\n`; });
|
|
63
|
+
spec += "\n";
|
|
64
|
+
if (p.architecture?.length) {
|
|
65
|
+
spec += `## Architecture\n\n`;
|
|
66
|
+
p.architecture.forEach(a => { spec += `- ${a}\n`; });
|
|
67
|
+
spec += "\n";
|
|
68
|
+
}
|
|
69
|
+
if (p.constraints?.length) {
|
|
70
|
+
spec += `## Constraints\n\n`;
|
|
71
|
+
p.constraints.forEach(c => { spec += `- ${c}\n`; });
|
|
72
|
+
spec += "\n";
|
|
73
|
+
}
|
|
74
|
+
if (p.successCriteria?.length) {
|
|
75
|
+
spec += `## Success Criteria\n\n`;
|
|
76
|
+
p.successCriteria.forEach(s => { spec += `- [ ] ${s}\n`; });
|
|
77
|
+
spec += "\n";
|
|
78
|
+
}
|
|
79
|
+
spec += `## Task Overview\n\n| # | Task | Agent | Priority | Dependencies |\n|---|------|-------|----------|-------------|\n`;
|
|
80
|
+
p.tasks.forEach((t, i) => {
|
|
81
|
+
const deps = t.dependencies?.map(d => `#${d}`).join(", ") || "—";
|
|
82
|
+
spec += `| ${i + 1} | ${t.title} | ${t.agent || "code"} | ${t.priority || "medium"} | ${deps} |\n`;
|
|
83
|
+
});
|
|
84
|
+
spec += `\n---\n*Generated by Phi Code Orchestrator*\n`;
|
|
85
|
+
return spec;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function generateTodo(title: string, tasks: TaskDef[]): string {
|
|
89
|
+
let todo = `# TODO: ${title}\n\n`;
|
|
90
|
+
todo += `**Created:** ${new Date().toLocaleString()}\n`;
|
|
91
|
+
todo += `**Tasks:** ${tasks.length}\n**Status:** executing\n\n`;
|
|
92
|
+
tasks.forEach((t, i) => {
|
|
93
|
+
const agentTag = t.agent ? ` [${t.agent}]` : "";
|
|
94
|
+
const prioTag = t.priority === "high" ? " 🔴" : t.priority === "low" ? " 🟢" : " 🟡";
|
|
95
|
+
const depsTag = t.dependencies?.length ? ` (after #${t.dependencies.join(", #")})` : "";
|
|
96
|
+
todo += `## Task ${i + 1}: ${t.title}${prioTag}${agentTag}${depsTag}\n\n- [ ] ${t.description}\n`;
|
|
97
|
+
if (t.subtasks) t.subtasks.forEach(st => { todo += ` - [ ] ${st}\n`; });
|
|
98
|
+
todo += "\n";
|
|
99
|
+
});
|
|
100
|
+
todo += `---\n\n## Progress\n\n- Total: ${tasks.length} tasks\n`;
|
|
101
|
+
todo += `- High priority: ${tasks.filter(t => t.priority === "high").length}\n`;
|
|
102
|
+
todo += `- Agents: ${[...new Set(tasks.map(t => t.agent || "code"))].join(", ")}\n`;
|
|
103
|
+
return todo;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Orchestrate Tool — Structured planning with prompt-architect pattern ───
|
|
107
|
+
|
|
108
|
+
pi.registerTool({
|
|
109
|
+
name: "orchestrate",
|
|
110
|
+
label: "Project Orchestrator",
|
|
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].",
|
|
113
|
+
promptGuidelines: [
|
|
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.",
|
|
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.",
|
|
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.",
|
|
121
|
+
"Set priority=high for critical-path tasks, medium for standard work, low for nice-to-haves.",
|
|
122
|
+
],
|
|
123
|
+
parameters: Type.Object({
|
|
124
|
+
title: Type.String({ description: "Concise project title" }),
|
|
125
|
+
description: Type.String({ description: "Full project description: what to build, why, and any relevant context" }),
|
|
126
|
+
goals: Type.Union([Type.Array(Type.String()), Type.String()], { description: "Measurable project goals (what success looks like)" }),
|
|
127
|
+
requirements: Type.Union([Type.Array(Type.String()), Type.String()], { description: "Technical and functional requirements" }),
|
|
128
|
+
architecture: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Architecture decisions, tech stack choices, trade-offs" })),
|
|
129
|
+
tasks: Type.Array(
|
|
130
|
+
Type.Object({
|
|
131
|
+
title: Type.String({ description: "Clear, action-oriented task title" }),
|
|
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)" })),
|
|
136
|
+
subtasks: Type.Optional(Type.Array(Type.String(), { description: "Specific sub-steps within this task" })),
|
|
137
|
+
}),
|
|
138
|
+
{ description: "Ordered list of tasks" }
|
|
139
|
+
),
|
|
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" })),
|
|
142
|
+
}),
|
|
143
|
+
|
|
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
|
+
|
|
152
|
+
const raw = params as any;
|
|
153
|
+
|
|
154
|
+
// Normalize string fields to arrays
|
|
155
|
+
const toArray = (v: any): string[] => {
|
|
156
|
+
if (!v) return [];
|
|
157
|
+
if (Array.isArray(v)) return v;
|
|
158
|
+
if (typeof v === "string") return v.split("\n").map((s: string) => s.replace(/^[-•*]\s*/, "").trim()).filter(Boolean);
|
|
159
|
+
return [];
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const p = {
|
|
163
|
+
title: raw.title as string,
|
|
164
|
+
description: raw.description as string,
|
|
165
|
+
goals: toArray(raw.goals),
|
|
166
|
+
requirements: toArray(raw.requirements),
|
|
167
|
+
architecture: raw.architecture ? toArray(raw.architecture) : undefined,
|
|
168
|
+
tasks: raw.tasks as TaskDef[],
|
|
169
|
+
constraints: raw.constraints ? toArray(raw.constraints) : undefined,
|
|
170
|
+
successCriteria: raw.successCriteria ? toArray(raw.successCriteria) : undefined,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
await ensurePlansDir();
|
|
175
|
+
const ts = timestamp();
|
|
176
|
+
const specFile = `spec-${ts}.md`;
|
|
177
|
+
const todoFile = `todo-${ts}.md`;
|
|
178
|
+
|
|
179
|
+
// Generate and write structured plan files
|
|
180
|
+
const spec = generateSpec(p);
|
|
181
|
+
const todo = generateTodo(p.title, p.tasks);
|
|
182
|
+
await writeFile(join(plansDir, specFile), spec, "utf-8");
|
|
183
|
+
await writeFile(join(plansDir, todoFile), todo, "utf-8");
|
|
184
|
+
|
|
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.`;
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
content: [{ type: "text", text: summary }],
|
|
194
|
+
details: { specFile, todoFile, taskCount: p.tasks.length },
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return {
|
|
198
|
+
content: [{ type: "text", text: `Orchestration failed: ${error}` }],
|
|
199
|
+
details: { error: String(error) },
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
38
205
|
// ─── Orchestration State ─────────────────────────────────────────
|
|
39
206
|
|
|
40
207
|
interface AgentDef {
|
|
@@ -119,7 +286,10 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
119
286
|
|
|
120
287
|
const ts = timestamp();
|
|
121
288
|
// Inject runtime info so agents can adapt to the host OS
|
|
122
|
-
const
|
|
289
|
+
const shellNote = process.platform === 'win32'
|
|
290
|
+
? `\nShell: bash (Git Bash), NOT cmd.exe. Always use Unix syntax: rm not del, test -f not if exist, / not \\\\`
|
|
291
|
+
: '';
|
|
292
|
+
const runtimeInfo = `\n\nRuntime: ${process.platform} (${process.arch})${shellNote}`;
|
|
123
293
|
|
|
124
294
|
return [
|
|
125
295
|
{
|
|
@@ -144,7 +314,7 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
144
314
|
- Requirements: specific features needed
|
|
145
315
|
- Tech decisions: frameworks, patterns to use
|
|
146
316
|
- Constraints: what to NOT break
|
|
147
|
-
5. Write your findings to \`.phi/plans/explore-${ts}.md\`
|
|
317
|
+
5. **MANDATORY:** Write your findings to \`.phi/plans/explore-${ts}.md\` using the \`write\` tool. This file is READ by Phase 2 PLAN — if you skip it, the plan agent has no context. Do NOT skip this step.
|
|
148
318
|
|
|
149
319
|
**LAST ACTION (MANDATORY):** Call \`memory_write\` to save your exploration findings for downstream agents.
|
|
150
320
|
|
|
@@ -261,6 +431,8 @@ Before finishing, use \`memory_write\` to save your plan summary with relevant t
|
|
|
261
431
|
|
|
262
432
|
After implementation, use \`memory_write\` to save a summary of what was built, patterns used, and any issues encountered.
|
|
263
433
|
|
|
434
|
+
**Ontology update:** Use \`ontology_add\` to update the project status (e.g., entity "implementation" type "Phase" with properties {status: "complete", files: "N"}) and add a relation to the project entity.
|
|
435
|
+
|
|
264
436
|
**CRITICAL RULES:**
|
|
265
437
|
- Write ONE file per tool call — NEVER combine multiple files in a single response
|
|
266
438
|
- Keep each file under 500 lines. If longer, split into modules
|
|
@@ -318,7 +490,9 @@ After implementation, use \`memory_write\` to save a summary of what was built,
|
|
|
318
490
|
|
|
319
491
|
**Anti-loop rule:** If the SAME test fails 3 times in a row with the same error after your fixes, STOP trying to fix it. Write the failure in your test report as "UNRESOLVED" and move on. Do not waste more than 3 iterations on the same issue.
|
|
320
492
|
|
|
321
|
-
After testing, use \`memory_write\` to save test results, bugs found, and lessons learned
|
|
493
|
+
After testing, use \`memory_write\` to save test results, bugs found, and lessons learned.
|
|
494
|
+
|
|
495
|
+
**Ontology update:** Use \`ontology_add\` to update the project status (e.g., entity "test-results" type "Phase" with properties {passed: "N", failed: "M", coverage: "X%"}) and add a relation to the project entity.` + runtimeInfo,
|
|
322
496
|
},
|
|
323
497
|
{
|
|
324
498
|
key: "review", label: "🔍 Phase 5 — REVIEW", model: review.preferred, fallback: review.fallback,
|