@selesai/code 0.5.12 → 0.5.14
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/README.md +186 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/extensions/caveman/caveman-instructions.cjs +6 -34
- package/dist/extensions/caveman/test/helpers.test.js +7 -13
- package/dist/extensions/package.json +4 -3
- package/dist/extensions/pi-powerline-footer/README.md +1 -1
- package/dist/extensions/pi-powerline-footer/guide.ts +227 -0
- package/dist/extensions/pi-powerline-footer/index.ts +88 -55
- package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -78
- package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +102 -0
- package/dist/extensions/pi-powerline-footer/welcome.ts +65 -30
- package/dist/extensions/ponytail/index.js +1 -1
- package/dist/extensions/ponytail/ponytail-instructions.cjs +15 -1
- package/dist/extensions/ponytail/test/extension.test.js +20 -4
- package/dist/extensions/ponytail/test/helpers.test.js +32 -0
- package/dist/extensions/workflow/adapter.ts +0 -19
- package/dist/skills/caveman/SKILL.md +1 -0
- package/dist/skills/handoff/SKILL.md +1 -0
- package/dist/skills/handoff-text/SKILL.md +1 -0
- package/dist/skills/implanger/SKILL.md +1 -0
- package/dist/skills/improve-codebase/SKILL.md +1 -0
- package/dist/skills/planger/SKILL.md +1 -0
- package/dist/skills/ponytail/SKILL.md +1 -0
- package/dist/skills/ponytail-audit/SKILL.md +1 -0
- package/dist/skills/ponytail-debt/SKILL.md +1 -0
- package/dist/skills/ponytail-gain/SKILL.md +1 -0
- package/dist/skills/ponytail-help/SKILL.md +1 -0
- package/dist/skills/ponytail-review/SKILL.md +1 -0
- package/dist/skills/workflow-creation/SKILL.md +1 -0
- package/package.json +23 -2
- package/dist/agents/architect.md +0 -217
- package/dist/agents/builder.md +0 -120
- package/dist/agents/commentator.md +0 -130
- package/dist/agents/explorer.md +0 -51
- package/dist/agents/recapper.md +0 -26
- package/dist/extensions/gitignore-guard.ts +0 -132
- package/dist/extensions/hooks/claude-codex-hooks.json +0 -44
- package/dist/extensions/hooks/copilot-hooks.json +0 -21
- package/dist/extensions/hooks/ponytail-activate.js +0 -91
- package/dist/extensions/hooks/ponytail-config.js +0 -122
- package/dist/extensions/hooks/ponytail-instructions.js +0 -94
- package/dist/extensions/hooks/ponytail-mode-tracker.js +0 -55
- package/dist/extensions/hooks/ponytail-runtime.js +0 -68
- package/dist/extensions/hooks/ponytail-statusline.ps1 +0 -21
- package/dist/extensions/hooks/ponytail-statusline.sh +0 -12
- package/dist/extensions/hooks/ponytail-subagent.js +0 -22
- package/dist/extensions/pi-extension/index.js +0 -189
- package/dist/extensions/pi-extension/package.json +0 -8
- package/dist/extensions/pi-extension/test/extension.test.js +0 -167
- package/dist/extensions/pi-extension/test/helpers.test.js +0 -92
- package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
- package/dist/extensions/pi-subagents/agents/context-builder.md +0 -46
- package/dist/extensions/pi-subagents/agents/delegate.md +0 -12
- package/dist/extensions/pi-subagents/agents/oracle.md +0 -73
- package/dist/extensions/pi-subagents/agents/planner.md +0 -55
- package/dist/extensions/pi-subagents/agents/reviewer.md +0 -79
- package/dist/extensions/pi-subagents/agents/scout.md +0 -50
- package/dist/extensions/pi-subagents/agents/worker.md +0 -55
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-timing.ts +0 -46
- package/dist/extensions/pi-subagents/test/unit/spawn-timing.test.ts +0 -45
- package/dist/extensions/pi-subagents/tsconfig.runner.json +0 -17
- package/dist/extensions/ponytail-pi-extension/index.js +0 -189
- package/dist/extensions/ponytail-pi-extension/package.json +0 -8
- package/dist/extensions/ponytail-pi-extension/test/extension.test.js +0 -167
- package/dist/extensions/ponytail-pi-extension/test/helpers.test.js +0 -92
- package/dist/extensions/prototype.ts +0 -713
- package/dist/extensions/question.ts +0 -350
- package/dist/extensions/quick.ts +0 -630
- package/dist/extensions/tps-tracker.ts +0 -280
- package/dist/extensions/workflow/task-validators.ts +0 -69
- package/dist/skills/selesai-default/SKILL.md +0 -16
package/dist/extensions/quick.ts
DELETED
|
@@ -1,630 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
ExtensionCommandContext,
|
|
4
|
-
ExtensionContext,
|
|
5
|
-
ToolDefinition,
|
|
6
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
8
|
-
import { Type } from "typebox";
|
|
9
|
-
import { access, mkdir } from "node:fs/promises";
|
|
10
|
-
|
|
11
|
-
// ponytail: quick workflow — same tool-driven loop as prototype, but shorter:
|
|
12
|
-
// grill (max 4 questions) → plan → reuse → handoff → loop → audit.
|
|
13
|
-
// No research phase. Handoff and audit stay unchanged.
|
|
14
|
-
|
|
15
|
-
type Phase =
|
|
16
|
-
| "grilling"
|
|
17
|
-
| "plan"
|
|
18
|
-
| "reuse"
|
|
19
|
-
| "handoff"
|
|
20
|
-
| "loop"
|
|
21
|
-
| "audit";
|
|
22
|
-
|
|
23
|
-
const PHASE_ORDER: Phase[] = [
|
|
24
|
-
"grilling",
|
|
25
|
-
"plan",
|
|
26
|
-
"reuse",
|
|
27
|
-
"handoff",
|
|
28
|
-
"loop",
|
|
29
|
-
"audit",
|
|
30
|
-
];
|
|
31
|
-
const PHASE_STEP: Record<Phase, number> = {
|
|
32
|
-
grilling: 1,
|
|
33
|
-
plan: 2,
|
|
34
|
-
reuse: 3,
|
|
35
|
-
handoff: 4,
|
|
36
|
-
loop: 5,
|
|
37
|
-
audit: 6,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const STATUS_KEY = "quick";
|
|
41
|
-
const ENTRY_TYPE = "quick-phase";
|
|
42
|
-
|
|
43
|
-
const ARTIFACTS_BASE = "./.selesai/artifacts";
|
|
44
|
-
|
|
45
|
-
let active = false;
|
|
46
|
-
let phase: Phase = "grilling";
|
|
47
|
-
let userPrompt = "";
|
|
48
|
-
let artifactDir = "";
|
|
49
|
-
let autoArmed = false;
|
|
50
|
-
let advancing = false;
|
|
51
|
-
|
|
52
|
-
const DEBUG = false;
|
|
53
|
-
function dbg(tag: string, data: Record<string, unknown>): void {
|
|
54
|
-
if (!DEBUG) return;
|
|
55
|
-
console.log(`[quick] ${tag}`, data);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function slugify(s: string): string {
|
|
59
|
-
const slug = s
|
|
60
|
-
.toLowerCase()
|
|
61
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
62
|
-
.replace(/^-+|-+$/g, "")
|
|
63
|
-
.slice(0, 40);
|
|
64
|
-
return slug || "workflow";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function timestamp(): string {
|
|
68
|
-
const d = new Date();
|
|
69
|
-
const p = (n: number) => String(n).padStart(2, "0");
|
|
70
|
-
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function artifactPathFor(goal: string): string {
|
|
74
|
-
return `${ARTIFACTS_BASE}/${timestamp()}-${slugify(goal)}`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function setPhase(pi: ExtensionAPI, p: Phase): void {
|
|
78
|
-
phase = p;
|
|
79
|
-
autoArmed = true;
|
|
80
|
-
pi.appendEntry(ENTRY_TYPE, {
|
|
81
|
-
mode: "quick",
|
|
82
|
-
phase: p,
|
|
83
|
-
step: PHASE_STEP[p],
|
|
84
|
-
done: false,
|
|
85
|
-
userPrompt,
|
|
86
|
-
artifactDir,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function updateFooter(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
91
|
-
if (!active) {
|
|
92
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const step = PHASE_STEP[phase];
|
|
96
|
-
ctx.ui.setStatus(
|
|
97
|
-
STATUS_KEY,
|
|
98
|
-
ctx.ui.theme.fg("warning", `● quick · ${step}/6 ${phase}`),
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const PHASE_ARTIFACT: Partial<Record<Phase, string>> = {
|
|
103
|
-
grilling: "requirements.md",
|
|
104
|
-
plan: "plan.md",
|
|
105
|
-
reuse: "reuse.md",
|
|
106
|
-
handoff: "handoff.md",
|
|
107
|
-
loop: "loop-complete.md",
|
|
108
|
-
audit: "review.md",
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
async function fileExists(path: string): Promise<boolean> {
|
|
112
|
-
try {
|
|
113
|
-
await access(path);
|
|
114
|
-
return true;
|
|
115
|
-
} catch {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function phaseArtifactExists(p: Phase): Promise<boolean> {
|
|
121
|
-
const file = PHASE_ARTIFACT[p];
|
|
122
|
-
if (!file) return true;
|
|
123
|
-
return fileExists(`${artifactDir}/${file}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function nextPhase(p: Phase): Phase | null {
|
|
127
|
-
const i = PHASE_ORDER.indexOf(p);
|
|
128
|
-
if (i < 0 || i >= PHASE_ORDER.length - 1) return null;
|
|
129
|
-
return PHASE_ORDER[i + 1];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
type AdvanceOutcome =
|
|
133
|
-
| { status: "advanced"; phase: Phase; prompt: string }
|
|
134
|
-
| { status: "blocked"; phase: Phase; missing: string }
|
|
135
|
-
| { status: "terminal"; phase: "audit"; missing?: string }
|
|
136
|
-
| { status: "idle"; phase: Phase };
|
|
137
|
-
|
|
138
|
-
async function advancePhase(
|
|
139
|
-
pi: ExtensionAPI,
|
|
140
|
-
ctx: ExtensionContext,
|
|
141
|
-
): Promise<AdvanceOutcome> {
|
|
142
|
-
if (!active) return { status: "idle", phase };
|
|
143
|
-
const expected = PHASE_ARTIFACT[phase];
|
|
144
|
-
if (expected && !(await phaseArtifactExists(phase))) {
|
|
145
|
-
return { status: "blocked", phase, missing: expected };
|
|
146
|
-
}
|
|
147
|
-
const next = nextPhase(phase);
|
|
148
|
-
if (!next) {
|
|
149
|
-
const reviewExists = await phaseArtifactExists("audit");
|
|
150
|
-
const auditExists = await fileExists(`${artifactDir}/audit.md`);
|
|
151
|
-
if (!reviewExists || !auditExists) {
|
|
152
|
-
return {
|
|
153
|
-
status: "terminal",
|
|
154
|
-
phase: "audit",
|
|
155
|
-
missing: !reviewExists ? "review.md" : "audit.md",
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
return { status: "terminal", phase: "audit" };
|
|
159
|
-
}
|
|
160
|
-
if (next === "reuse" && (await isEmptyProject(pi))) {
|
|
161
|
-
setPhase(pi, "handoff");
|
|
162
|
-
updateFooter(pi, ctx);
|
|
163
|
-
return {
|
|
164
|
-
status: "advanced",
|
|
165
|
-
phase: "handoff",
|
|
166
|
-
prompt: phasePrompt("handoff"),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
setPhase(pi, next);
|
|
170
|
-
updateFooter(pi, ctx);
|
|
171
|
-
return { status: "advanced", phase: next, prompt: phasePrompt(next) };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async function endWorkflow(
|
|
175
|
-
pi: ExtensionAPI,
|
|
176
|
-
ctx: ExtensionContext,
|
|
177
|
-
): Promise<{
|
|
178
|
-
ok: boolean;
|
|
179
|
-
text: string;
|
|
180
|
-
closed?: boolean;
|
|
181
|
-
artifactDir?: string;
|
|
182
|
-
phase?: Phase;
|
|
183
|
-
}> {
|
|
184
|
-
if (!active) {
|
|
185
|
-
return { ok: false, text: "No active quick workflow to end." };
|
|
186
|
-
}
|
|
187
|
-
if (phase !== "audit") {
|
|
188
|
-
return {
|
|
189
|
-
ok: false,
|
|
190
|
-
text: `Cannot end workflow from phase ${phase}. end_workflow is only allowed from audit.`,
|
|
191
|
-
phase,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
const reviewExists = await phaseArtifactExists("audit");
|
|
195
|
-
const auditExists = await fileExists(`${artifactDir}/audit.md`);
|
|
196
|
-
if (!reviewExists || !auditExists) {
|
|
197
|
-
return {
|
|
198
|
-
ok: false,
|
|
199
|
-
text: `Audit step is not complete. Write ${artifactDir}/review.md and ${artifactDir}/audit.md first.`,
|
|
200
|
-
phase,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
active = false;
|
|
204
|
-
pi.appendEntry(ENTRY_TYPE, {
|
|
205
|
-
mode: "quick",
|
|
206
|
-
phase,
|
|
207
|
-
step: PHASE_STEP[phase],
|
|
208
|
-
done: true,
|
|
209
|
-
userPrompt,
|
|
210
|
-
artifactDir,
|
|
211
|
-
});
|
|
212
|
-
updateFooter(pi, ctx);
|
|
213
|
-
ctx.ui.notify(
|
|
214
|
-
`Quick workflow complete. Artifacts: ${artifactDir}`,
|
|
215
|
-
"info",
|
|
216
|
-
);
|
|
217
|
-
return {
|
|
218
|
-
ok: true,
|
|
219
|
-
closed: true,
|
|
220
|
-
phase,
|
|
221
|
-
artifactDir,
|
|
222
|
-
text: `Workflow ended and closed. It can no longer be used. Artifacts saved at ${artifactDir}.`,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function phasePrompt(p: Phase): string {
|
|
227
|
-
switch (p) {
|
|
228
|
-
case "grilling":
|
|
229
|
-
return `You are entering the GRILLING phase of a QUICK workflow.
|
|
230
|
-
|
|
231
|
-
User's initial request:
|
|
232
|
-
${userPrompt}
|
|
233
|
-
|
|
234
|
-
This phase is short: ask AT MOST 4 focused clarifying questions. Ask ONE question at a time, adapt to user answers, and dig into scope, constraints, success criteria, and edge cases.
|
|
235
|
-
|
|
236
|
-
When you have enough clarity OR when 4 questions have been asked, stop grilling. Show the user a concise requirements summary and ask explicitly whether they approve it. Only once the user approves, write the approved summary to ${artifactDir}/requirements.md. The workflow advances automatically once requirements.md exists.`;
|
|
237
|
-
case "plan":
|
|
238
|
-
return `You are entering the PLAN phase.
|
|
239
|
-
|
|
240
|
-
This is a QUICK workflow — no separate research phase. Based on ${artifactDir}/requirements.md, produce the concrete build plan: what to build, how, in order, components, and what the finished prototype looks like.
|
|
241
|
-
|
|
242
|
-
Spawn the ARCHITECT SUB-AGENT to produce the plan. Use the task tool with subagent_type "architect". Do NOT pass a model parameter — let the agent use its configured model. Craft a tailored prompt from ${artifactDir}/requirements.md so the architect knows exactly what to plan for THIS task. The architect writes to ${artifactDir}/plan.md.
|
|
243
|
-
|
|
244
|
-
The workflow advances automatically once plan.md exists.`;
|
|
245
|
-
case "reuse":
|
|
246
|
-
return `You are entering the REUSE phase.
|
|
247
|
-
|
|
248
|
-
Purpose: explore the existing codebase for reusable components, patterns, and knowledge this quick prototype should build on.
|
|
249
|
-
|
|
250
|
-
Spawn an EXPLORER SUB-AGENT to do the exploration. Use the task tool with subagent_type "explorer". Do NOT pass a model parameter — let the agent use its configured model. Craft a specific prompt tailored to THIS task from ${artifactDir}/requirements.md and ${artifactDir}/plan.md, telling the explorer which areas, patterns, and dependencies matter. Instruct the explorer to write findings to ${artifactDir}/reuse.md.
|
|
251
|
-
|
|
252
|
-
When the explorer returns, synthesize its findings into ${artifactDir}/reuse.md: what is reusable, where, and how the prototype should leverage it.
|
|
253
|
-
|
|
254
|
-
The workflow advances automatically once reuse.md exists.`;
|
|
255
|
-
case "handoff":
|
|
256
|
-
return `You are entering the HANDOFF phase.
|
|
257
|
-
|
|
258
|
-
Purpose: compile everything learned so far into a self-contained handoff document so loop-phase sub-agents can understand full project context without re-grilling.
|
|
259
|
-
|
|
260
|
-
Draw from ALL prior phases:
|
|
261
|
-
- ${artifactDir}/requirements.md
|
|
262
|
-
- ${artifactDir}/plan.md
|
|
263
|
-
- ${artifactDir}/reuse.md
|
|
264
|
-
|
|
265
|
-
Spawn the RECAPPER SUB-AGENT to compile the handoff. Use the task tool with subagent_type "recapper". Do NOT pass a model parameter — let the agent use its configured model. Craft a tailored prompt that points the recapper at all three artifact files and tells it what the prototype is about, so it writes a coherent handoff tailored to this task. The recapper writes to ${artifactDir}/handoff.md.
|
|
266
|
-
|
|
267
|
-
The workflow advances automatically once handoff.md exists.`;
|
|
268
|
-
case "loop":
|
|
269
|
-
return `You are entering the LOOP (orchestration) phase. Your job is to DELEGATE the implementation to sub-agents — do not implement the code yourself.
|
|
270
|
-
|
|
271
|
-
Read ${artifactDir}/plan.md, ${artifactDir}/handoff.md, and ${artifactDir}/reuse.md. Using that full context, GENERATE YOUR OWN delegation prompts:
|
|
272
|
-
1. Dispatch ONE builder sub-agent (task tool, subagent_type "builder"). Do NOT pass a model parameter — let the agent use its configured model. Give it the plan + handoff + reuse context, tailored to this specific task, and instruct it to implement every task in plan.md in order. All code changes go in the workspace, never in ${artifactDir}.
|
|
273
|
-
2. Dispatch ONE commentator sub-agent (task tool, subagent_type "commentator"). Do NOT pass a model parameter. Review the builder's diff against plan.md. Generate the review prompt YOURSELF.
|
|
274
|
-
3. If the commentator reports blocking issues, dispatch the builder again with the issues to fix, then re-run the commentator. Repeat until no issues.
|
|
275
|
-
4. Write ${artifactDir}/loop-complete.md with a one-line summary of the finished plan.
|
|
276
|
-
|
|
277
|
-
The workflow advances to audit automatically once loop-complete.md exists.`;
|
|
278
|
-
case "audit":
|
|
279
|
-
return `You are entering the AUDIT phase.
|
|
280
|
-
|
|
281
|
-
Purpose: review loop-phase changes and audit for over-engineering across the repo — ACT on findings.
|
|
282
|
-
|
|
283
|
-
Two sub-agent rounds:
|
|
284
|
-
|
|
285
|
-
1. Spawn the COMMENTATOR SUB-AGENT (task tool, subagent_type "commentator"). Do NOT pass a model parameter. Craft a tailored prompt from the full diff and plan.md so the commentator reviews correctness and plan-adherence (writing ${artifactDir}/review.md), then runs a whole-repo over-engineering audit (writing ${artifactDir}/audit.md).
|
|
286
|
-
|
|
287
|
-
2. If review.md or audit.md lists actionable issues, spawn the BUILDER SUB-AGENT (task tool, subagent_type "builder"). Do NOT pass a model parameter. Craft a tailored prompt containing the findings. Instruct the builder to FIX every issue. All fixes go in the workspace, never in ${artifactDir}.
|
|
288
|
-
|
|
289
|
-
3. After the builder returns, re-dispatch the commentator to confirm fixes resolved findings. Repeat until clean.
|
|
290
|
-
|
|
291
|
-
4. Update ${artifactDir}/audit.md with the final resolved state (what was found, what the builder fixed, what remains — should be none).
|
|
292
|
-
|
|
293
|
-
The workflow closes automatically once both review.md and audit.md exist and the commentator reports no outstanding issues.`;
|
|
294
|
-
default:
|
|
295
|
-
return "";
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function continueWorkflow(
|
|
300
|
-
pi: ExtensionAPI,
|
|
301
|
-
ctx: ExtensionContext,
|
|
302
|
-
prompt: string,
|
|
303
|
-
): void {
|
|
304
|
-
dbg("continuing agent", { phase, prompt: prompt.slice(0, 60) });
|
|
305
|
-
if (ctx.isIdle()) {
|
|
306
|
-
pi.sendUserMessage(prompt);
|
|
307
|
-
} else {
|
|
308
|
-
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
async function beginWorkflow(
|
|
313
|
-
pi: ExtensionAPI,
|
|
314
|
-
ctx: ExtensionContext,
|
|
315
|
-
goal: string,
|
|
316
|
-
): Promise<string> {
|
|
317
|
-
userPrompt = goal.trim();
|
|
318
|
-
artifactDir = artifactPathFor(userPrompt);
|
|
319
|
-
await mkdir(artifactDir, { recursive: true });
|
|
320
|
-
active = true;
|
|
321
|
-
setPhase(pi, "grilling");
|
|
322
|
-
updateFooter(pi, ctx);
|
|
323
|
-
return phasePrompt("grilling");
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
async function isEmptyProject(pi: ExtensionAPI): Promise<boolean> {
|
|
327
|
-
try {
|
|
328
|
-
const result = await pi.exec("git", ["log", "--oneline", "-1"]);
|
|
329
|
-
return result.code !== 0 || !result.stdout.trim();
|
|
330
|
-
} catch {
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export default function quickExtension(pi: ExtensionAPI): void {
|
|
336
|
-
pi.registerTool({
|
|
337
|
-
name: "start_quick_workflow",
|
|
338
|
-
label: "Start Quick Workflow",
|
|
339
|
-
description:
|
|
340
|
-
"Start the quick workflow for the given goal. Sets up grilling as the first phase and returns the grilling prompt. Do not call if a workflow is already active.",
|
|
341
|
-
promptSnippet:
|
|
342
|
-
"start_quick_workflow(goal) - start the quick workflow; goal is what the user wants to build.",
|
|
343
|
-
promptGuidelines: [
|
|
344
|
-
"Call start_quick_workflow only when the user explicitly asks to start a quick workflow and none is active.",
|
|
345
|
-
"Pass the user's full goal as the goal parameter.",
|
|
346
|
-
],
|
|
347
|
-
parameters: Type.Object({
|
|
348
|
-
goal: Type.String({
|
|
349
|
-
description:
|
|
350
|
-
"What the user wants the quick prototype to build or accomplish.",
|
|
351
|
-
}),
|
|
352
|
-
}),
|
|
353
|
-
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
354
|
-
if (active) {
|
|
355
|
-
return {
|
|
356
|
-
content: [
|
|
357
|
-
{
|
|
358
|
-
type: "text",
|
|
359
|
-
text: `A quick workflow is already active (phase: ${phase}). Use next_quick_step or end_quick_workflow, not start_quick_workflow.`,
|
|
360
|
-
},
|
|
361
|
-
],
|
|
362
|
-
details: { phase, alreadyActive: true },
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
const prompt = await beginWorkflow(pi, ctx, params.goal);
|
|
366
|
-
return {
|
|
367
|
-
content: [{ type: "text", text: `Quick workflow started. ${prompt}` }],
|
|
368
|
-
details: { phase: "grilling" },
|
|
369
|
-
};
|
|
370
|
-
},
|
|
371
|
-
renderResult(_result, _options, theme) {
|
|
372
|
-
return new Text(
|
|
373
|
-
theme.fg("warning", `● quick workflow started · 1/6 grilling`),
|
|
374
|
-
0,
|
|
375
|
-
0,
|
|
376
|
-
);
|
|
377
|
-
},
|
|
378
|
-
} satisfies ToolDefinition);
|
|
379
|
-
|
|
380
|
-
pi.registerTool({
|
|
381
|
-
name: "next_quick_step",
|
|
382
|
-
label: "Next Quick Step",
|
|
383
|
-
description:
|
|
384
|
-
"Advance the quick workflow one step after the current step is complete. Returns the next step's instructions. Do not call until the current step is fully complete.",
|
|
385
|
-
promptSnippet:
|
|
386
|
-
"next_quick_step() - advance the quick workflow one step once the current step is done.",
|
|
387
|
-
promptGuidelines: [
|
|
388
|
-
"Call next_quick_step only when the current quick workflow step is genuinely complete.",
|
|
389
|
-
"From the final step (audit), call end_quick_workflow instead.",
|
|
390
|
-
],
|
|
391
|
-
parameters: Type.Object({}),
|
|
392
|
-
async execute(_id, _params, _signal, _onUpdate, ctx) {
|
|
393
|
-
if (!active) {
|
|
394
|
-
return {
|
|
395
|
-
content: [
|
|
396
|
-
{
|
|
397
|
-
type: "text",
|
|
398
|
-
text: "No active quick workflow. Call start_quick_workflow first.",
|
|
399
|
-
},
|
|
400
|
-
],
|
|
401
|
-
details: { active: false },
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
const out = await advancePhase(pi, ctx);
|
|
405
|
-
if (out.status === "idle") {
|
|
406
|
-
return {
|
|
407
|
-
content: [
|
|
408
|
-
{
|
|
409
|
-
type: "text",
|
|
410
|
-
text: "No active quick workflow. Call start_quick_workflow first.",
|
|
411
|
-
},
|
|
412
|
-
],
|
|
413
|
-
details: { active: false },
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
if (out.status === "blocked") {
|
|
417
|
-
return {
|
|
418
|
-
content: [
|
|
419
|
-
{
|
|
420
|
-
type: "text",
|
|
421
|
-
text: `Current step (${out.phase}) is not complete. Write ${artifactDir}/${out.missing} first, then call next_quick_step again.`,
|
|
422
|
-
},
|
|
423
|
-
],
|
|
424
|
-
details: { phase: out.phase, blocked: out.missing },
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
if (out.status === "terminal") {
|
|
428
|
-
if (out.missing) {
|
|
429
|
-
return {
|
|
430
|
-
content: [
|
|
431
|
-
{
|
|
432
|
-
type: "text",
|
|
433
|
-
text: `Audit step is not complete. Write ${artifactDir}/${out.missing} first, then call end_quick_workflow.`,
|
|
434
|
-
},
|
|
435
|
-
],
|
|
436
|
-
details: { phase: "audit", blocked: out.missing },
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
content: [
|
|
441
|
-
{
|
|
442
|
-
type: "text",
|
|
443
|
-
text: `Already at the final phase (audit). Call end_quick_workflow to close the workflow.`,
|
|
444
|
-
},
|
|
445
|
-
],
|
|
446
|
-
details: { phase: "audit" },
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
const label = `Phase advanced to ${out.phase}. ${out.prompt}`;
|
|
450
|
-
return {
|
|
451
|
-
content: [{ type: "text", text: label }],
|
|
452
|
-
details: { phase: out.phase },
|
|
453
|
-
};
|
|
454
|
-
},
|
|
455
|
-
renderResult(result, _options, theme) {
|
|
456
|
-
const d = result.details as {
|
|
457
|
-
phase?: string;
|
|
458
|
-
active?: boolean;
|
|
459
|
-
blocked?: string;
|
|
460
|
-
};
|
|
461
|
-
if (d.active === false) {
|
|
462
|
-
return new Text(theme.fg("dim", "○ no active workflow"), 0, 0);
|
|
463
|
-
}
|
|
464
|
-
if (d.blocked) {
|
|
465
|
-
return new Text(
|
|
466
|
-
theme.fg("warning", `✋ ${d.phase} blocked · missing ${d.blocked}`),
|
|
467
|
-
0,
|
|
468
|
-
0,
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
|
-
const label = `${PHASE_STEP[d.phase as Phase]}/6 ${d.phase}`;
|
|
472
|
-
return new Text(theme.fg("warning", `▲ ${label}`), 0, 0);
|
|
473
|
-
},
|
|
474
|
-
} satisfies ToolDefinition);
|
|
475
|
-
|
|
476
|
-
pi.registerTool({
|
|
477
|
-
name: "end_quick_workflow",
|
|
478
|
-
label: "End Quick Workflow",
|
|
479
|
-
description:
|
|
480
|
-
"Close the quick workflow. Must be called when the final phase (audit) is complete. Marks the workflow finished and stops the agent loop.",
|
|
481
|
-
promptSnippet:
|
|
482
|
-
"end_quick_workflow() - close the finished quick workflow; no further phase transitions allowed.",
|
|
483
|
-
promptGuidelines: [
|
|
484
|
-
"Call end_quick_workflow exactly once, from the audit phase, when both review and audit are complete.",
|
|
485
|
-
],
|
|
486
|
-
parameters: Type.Object({}),
|
|
487
|
-
async execute(_id, _params, _signal, _onUpdate, ctx) {
|
|
488
|
-
const r = await endWorkflow(pi, ctx);
|
|
489
|
-
if (!r.ok) {
|
|
490
|
-
return {
|
|
491
|
-
content: [{ type: "text", text: r.text }],
|
|
492
|
-
details: r.phase
|
|
493
|
-
? { phase: r.phase, blocked: "audit artifacts" }
|
|
494
|
-
: { active: false },
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
return {
|
|
498
|
-
content: [{ type: "text", text: r.text }],
|
|
499
|
-
details: { closed: true, phase: r.phase, artifactDir: r.artifactDir },
|
|
500
|
-
terminate: true,
|
|
501
|
-
};
|
|
502
|
-
},
|
|
503
|
-
renderResult(result, _options, theme) {
|
|
504
|
-
const d = result.details as { closed?: boolean; artifactDir?: string };
|
|
505
|
-
if (!d.closed) {
|
|
506
|
-
return new Text(theme.fg("dim", "○ no workflow to end"), 0, 0);
|
|
507
|
-
}
|
|
508
|
-
return new Text(
|
|
509
|
-
theme.fg("success", `✓ quick closed · ${d.artifactDir}`),
|
|
510
|
-
0,
|
|
511
|
-
0,
|
|
512
|
-
);
|
|
513
|
-
},
|
|
514
|
-
} satisfies ToolDefinition);
|
|
515
|
-
|
|
516
|
-
pi.on("session_start", async (_event: any, ctx: ExtensionContext) => {
|
|
517
|
-
const entries = ctx.sessionManager.getEntries();
|
|
518
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
519
|
-
const e = entries[i] as { type: string; customType?: string; data?: any };
|
|
520
|
-
if (e.type === "custom" && e.customType === ENTRY_TYPE && e.data) {
|
|
521
|
-
if (e.data.done) {
|
|
522
|
-
active = false;
|
|
523
|
-
} else {
|
|
524
|
-
active = true;
|
|
525
|
-
phase = e.data.phase as Phase;
|
|
526
|
-
autoArmed = true;
|
|
527
|
-
userPrompt = e.data.userPrompt ?? "";
|
|
528
|
-
artifactDir = e.data.artifactDir ?? "";
|
|
529
|
-
}
|
|
530
|
-
updateFooter(pi, ctx);
|
|
531
|
-
break;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
pi.on("tool_result", async (event: any, ctx: ExtensionContext) => {
|
|
537
|
-
if (advancing) return;
|
|
538
|
-
if (!active || !autoArmed) return;
|
|
539
|
-
if (
|
|
540
|
-
event.toolName !== "write" &&
|
|
541
|
-
event.toolName !== "edit" &&
|
|
542
|
-
event.toolName !== "bash"
|
|
543
|
-
)
|
|
544
|
-
return;
|
|
545
|
-
const expected = PHASE_ARTIFACT[phase];
|
|
546
|
-
if (!expected) return;
|
|
547
|
-
advancing = true;
|
|
548
|
-
try {
|
|
549
|
-
if (!(await phaseArtifactExists(phase))) return;
|
|
550
|
-
autoArmed = false;
|
|
551
|
-
dbg("artifact detected", { phase, artifactDir, expected });
|
|
552
|
-
const from = phase;
|
|
553
|
-
const out = await advancePhase(pi, ctx);
|
|
554
|
-
if (out.status === "advanced") {
|
|
555
|
-
dbg("advancing", { from, to: out.phase });
|
|
556
|
-
continueWorkflow(pi, ctx, out.prompt);
|
|
557
|
-
} else if (out.status === "terminal") {
|
|
558
|
-
if (out.missing) {
|
|
559
|
-
continueWorkflow(
|
|
560
|
-
pi,
|
|
561
|
-
ctx,
|
|
562
|
-
`Audit phase still needs ${artifactDir}/${out.missing}. Write it, then the workflow closes automatically.`,
|
|
563
|
-
);
|
|
564
|
-
} else {
|
|
565
|
-
await endWorkflow(pi, ctx);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
} finally {
|
|
569
|
-
advancing = false;
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
pi.registerCommand("quick", {
|
|
574
|
-
description:
|
|
575
|
-
"Run the quick workflow (grill → plan → reuse → handoff → loop → audit)",
|
|
576
|
-
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
577
|
-
if (!ctx.isIdle()) {
|
|
578
|
-
ctx.ui.notify(
|
|
579
|
-
"Agent is busy. Wait for it to finish before calling /quick.",
|
|
580
|
-
"warning",
|
|
581
|
-
);
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
if (active) {
|
|
586
|
-
let goal = args.trim();
|
|
587
|
-
const choice = ctx.hasUI
|
|
588
|
-
? await ctx.ui.select(
|
|
589
|
-
`Workflow already active (phase: ${phase}). What do you want to do?`,
|
|
590
|
-
[
|
|
591
|
-
`Continue current workflow at ${phase}`,
|
|
592
|
-
"Close current and start a new one",
|
|
593
|
-
],
|
|
594
|
-
)
|
|
595
|
-
: undefined;
|
|
596
|
-
if (choice === undefined) return;
|
|
597
|
-
if (choice.startsWith("Continue")) {
|
|
598
|
-
continueWorkflow(pi, ctx, phasePrompt(phase));
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
if (!goal && ctx.hasUI) {
|
|
602
|
-
goal = (await ctx.ui.input("Goal for the new workflow:")) ?? "";
|
|
603
|
-
}
|
|
604
|
-
if (!goal.trim()) {
|
|
605
|
-
ctx.ui.notify(
|
|
606
|
-
"No goal provided. Keeping current workflow active.",
|
|
607
|
-
"warning",
|
|
608
|
-
);
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
pi.appendEntry(ENTRY_TYPE, {
|
|
612
|
-
mode: "quick",
|
|
613
|
-
phase,
|
|
614
|
-
step: PHASE_STEP[phase],
|
|
615
|
-
done: true,
|
|
616
|
-
userPrompt,
|
|
617
|
-
artifactDir,
|
|
618
|
-
});
|
|
619
|
-
ctx.ui.notify(`Closed quick workflow at ${artifactDir}.`, "info");
|
|
620
|
-
args = goal;
|
|
621
|
-
} else if (!args.trim()) {
|
|
622
|
-
ctx.ui.notify("Usage: /quick <what to build>", "warning");
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const prompt = await beginWorkflow(pi, ctx, args);
|
|
627
|
-
pi.sendUserMessage(prompt);
|
|
628
|
-
},
|
|
629
|
-
});
|
|
630
|
-
}
|