pi-skill-playbook 0.1.2 → 0.1.4
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 +17 -16
- package/extensions/index.ts +114 -43
- package/package.json +1 -1
- package/src/auto-advance.ts +6 -6
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@ Pi Skill Playbook is a Pi extension that guides Agent Skill usage flows with vis
|
|
|
4
4
|
|
|
5
5
|
MVP scope:
|
|
6
6
|
|
|
7
|
-
- `/playbook
|
|
8
|
-
- `/playbook
|
|
9
|
-
- `/playbook
|
|
10
|
-
- `/playbook
|
|
11
|
-
- `/playbook
|
|
12
|
-
- `/playbook
|
|
7
|
+
- `/playbook:list`
|
|
8
|
+
- `/playbook:start <playbook-id> [--run <name>]`
|
|
9
|
+
- `/playbook:resume <run-id>`
|
|
10
|
+
- `/playbook:status [run-id]`
|
|
11
|
+
- `/playbook:done`
|
|
12
|
+
- `/playbook:choose <outcome>`
|
|
13
13
|
- strict YAML validation
|
|
14
14
|
- active run widget below the editor
|
|
15
15
|
- marker-based auto advance for single-outcome steps
|
|
@@ -17,8 +17,8 @@ MVP scope:
|
|
|
17
17
|
|
|
18
18
|
Deferred after scaffold:
|
|
19
19
|
|
|
20
|
-
- `/playbook
|
|
21
|
-
- `/playbook
|
|
20
|
+
- `/playbook:import-web`
|
|
21
|
+
- `/playbook:record`
|
|
22
22
|
|
|
23
23
|
## Install from npm
|
|
24
24
|
|
|
@@ -26,7 +26,6 @@ Deferred after scaffold:
|
|
|
26
26
|
pi install npm:pi-skill-playbook
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Package version: `0.1.1`
|
|
30
29
|
|
|
31
30
|
## Install from GitHub
|
|
32
31
|
|
|
@@ -75,11 +74,11 @@ Add personal run state to the target repo's `.gitignore`:
|
|
|
75
74
|
## Use
|
|
76
75
|
|
|
77
76
|
```text
|
|
78
|
-
/playbook
|
|
79
|
-
/playbook
|
|
80
|
-
/playbook
|
|
81
|
-
/playbook
|
|
82
|
-
/playbook
|
|
77
|
+
/playbook:list
|
|
78
|
+
/playbook:start feature-development --run my-feature
|
|
79
|
+
/playbook:done
|
|
80
|
+
/playbook:choose pass
|
|
81
|
+
/playbook:status
|
|
83
82
|
```
|
|
84
83
|
|
|
85
84
|
The widget displays the current step, exact skill command, completion criteria, and outcome labels.
|
|
@@ -94,12 +93,14 @@ When a user explicitly runs the current step skill with `/skill:<name>`, Pi Skil
|
|
|
94
93
|
PLAYBOOK_OUTCOME: ready-for-prd
|
|
95
94
|
```
|
|
96
95
|
|
|
97
|
-
If the current step has exactly one outcome, a valid marker advances the run automatically. If the step has multiple outcomes, the marker is shown as a suggestion and the user must confirm with `/playbook
|
|
96
|
+
If the current step has exactly one outcome, a valid marker advances the run automatically. If the step has multiple outcomes, the marker is shown as a suggestion and the user must confirm with `/playbook:choose <outcome>`.
|
|
97
|
+
|
|
98
|
+
Legacy space-separated forms such as `/playbook start` remain available for one release.
|
|
98
99
|
|
|
99
100
|
Optional playbook setting:
|
|
100
101
|
|
|
101
102
|
```yaml
|
|
102
103
|
autoAdvance: auto # default: marker can advance single-outcome steps
|
|
103
|
-
autoAdvance: suggest # marker only suggests /playbook
|
|
104
|
+
autoAdvance: suggest # marker only suggests /playbook:done or /playbook:choose
|
|
104
105
|
autoAdvance: off # no prompt injection or completion detection
|
|
105
106
|
```
|
package/extensions/index.ts
CHANGED
|
@@ -9,6 +9,30 @@ import type { LoadedPlaybook, PlaybookRunState } from "../src/types.js";
|
|
|
9
9
|
|
|
10
10
|
const WIDGET_ID = "pi-skill-playbook";
|
|
11
11
|
|
|
12
|
+
const COMMANDS = [
|
|
13
|
+
["list", "list available playbooks"],
|
|
14
|
+
["start", "start a playbook run"],
|
|
15
|
+
["resume", "resume an active playbook run"],
|
|
16
|
+
["status", "show playbook run status"],
|
|
17
|
+
["done", "complete the current step"],
|
|
18
|
+
["choose", "choose a step outcome"],
|
|
19
|
+
["cancel", "cancel an active playbook run"],
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
const COLON_COMMAND_ALIASES = COMMANDS.map(([command, description]) => ({
|
|
23
|
+
name: `playbook:${command}`,
|
|
24
|
+
command,
|
|
25
|
+
description,
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
const COLON_COMPLETION_COMMANDS = new Set(["start", "resume", "status", "cancel", "choose"]);
|
|
29
|
+
|
|
30
|
+
type CommandContext = {
|
|
31
|
+
cwd: string;
|
|
32
|
+
hasUI: boolean;
|
|
33
|
+
ui?: UiLike;
|
|
34
|
+
};
|
|
35
|
+
|
|
12
36
|
export default function piSkillPlaybook(pi: ExtensionAPI) {
|
|
13
37
|
let completionCwd = process.cwd();
|
|
14
38
|
let pendingSkillInvocation: string | undefined;
|
|
@@ -65,44 +89,70 @@ export default function piSkillPlaybook(pi: ExtensionAPI) {
|
|
|
65
89
|
handler: async (args, ctx) => {
|
|
66
90
|
const parsed = parseArgs(args);
|
|
67
91
|
const command = parsed.shift() ?? "status";
|
|
68
|
-
|
|
69
92
|
try {
|
|
70
|
-
|
|
71
|
-
case "list":
|
|
72
|
-
await listPlaybooks(pi, ctx.cwd, ctx.hasUI ? ctx.ui : undefined);
|
|
73
|
-
return;
|
|
74
|
-
case "start":
|
|
75
|
-
await startPlaybook(pi, ctx.cwd, parsed, ctx.hasUI ? ctx.ui : undefined);
|
|
76
|
-
return;
|
|
77
|
-
case "resume":
|
|
78
|
-
await resumeRun(ctx.cwd, parsed, ctx.hasUI ? ctx.ui : undefined);
|
|
79
|
-
return;
|
|
80
|
-
case "status":
|
|
81
|
-
await showStatus(ctx.cwd, parsed[0], ctx.hasUI ? ctx.ui : undefined);
|
|
82
|
-
return;
|
|
83
|
-
case "done":
|
|
84
|
-
await completeCurrentStep(ctx.cwd, ctx.hasUI ? ctx.ui : undefined);
|
|
85
|
-
return;
|
|
86
|
-
case "choose":
|
|
87
|
-
await chooseOutcome(ctx.cwd, parsed[0], ctx.hasUI ? ctx.ui : undefined);
|
|
88
|
-
return;
|
|
89
|
-
case "cancel":
|
|
90
|
-
case "stop":
|
|
91
|
-
case "abort":
|
|
92
|
-
await cancelRun(ctx.cwd, parsed[0], ctx.hasUI ? ctx.ui : undefined);
|
|
93
|
-
return;
|
|
94
|
-
case "import-web":
|
|
95
|
-
case "record":
|
|
96
|
-
notify(ctx.hasUI ? ctx.ui : undefined, `/${command} is deferred after the Core 6 MVP scaffold.` , "warning");
|
|
97
|
-
return;
|
|
98
|
-
default:
|
|
99
|
-
notify(ctx.hasUI ? ctx.ui : undefined, usage(), "error");
|
|
100
|
-
}
|
|
93
|
+
await handlePlaybookCommand(pi, command, parsed, ctx);
|
|
101
94
|
} catch (error) {
|
|
102
95
|
notify(ctx.hasUI ? ctx.ui : undefined, error instanceof Error ? error.message : String(error), "error");
|
|
103
96
|
}
|
|
104
97
|
},
|
|
105
98
|
});
|
|
99
|
+
|
|
100
|
+
for (const alias of COLON_COMMAND_ALIASES) {
|
|
101
|
+
pi.registerCommand(alias.name, {
|
|
102
|
+
description: `Playbook: ${alias.description}. Alias for /playbook ${alias.command}.`,
|
|
103
|
+
...(COLON_COMPLETION_COMMANDS.has(alias.command)
|
|
104
|
+
? { getArgumentCompletions: (prefix) => getPlaybookColonArgumentCompletions(completionCwd, alias.command, prefix) }
|
|
105
|
+
: {}),
|
|
106
|
+
handler: async (args, ctx) => {
|
|
107
|
+
try {
|
|
108
|
+
await handlePlaybookCommand(pi, alias.command, parseArgs(args), ctx);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
notify(ctx.hasUI ? ctx.ui : undefined, error instanceof Error ? error.message : String(error), "error");
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function handlePlaybookCommand(
|
|
118
|
+
pi: ExtensionAPI,
|
|
119
|
+
command: string,
|
|
120
|
+
args: string[],
|
|
121
|
+
ctx: CommandContext,
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
const ui = ctx.hasUI ? ctx.ui : undefined;
|
|
124
|
+
|
|
125
|
+
switch (command) {
|
|
126
|
+
case "list":
|
|
127
|
+
await listPlaybooks(pi, ctx.cwd, ui);
|
|
128
|
+
return;
|
|
129
|
+
case "start":
|
|
130
|
+
await startPlaybook(pi, ctx.cwd, args, ui);
|
|
131
|
+
return;
|
|
132
|
+
case "resume":
|
|
133
|
+
await resumeRun(ctx.cwd, args, ui);
|
|
134
|
+
return;
|
|
135
|
+
case "status":
|
|
136
|
+
await showStatus(ctx.cwd, args[0], ui);
|
|
137
|
+
return;
|
|
138
|
+
case "done":
|
|
139
|
+
await completeCurrentStep(ctx.cwd, ui);
|
|
140
|
+
return;
|
|
141
|
+
case "choose":
|
|
142
|
+
await chooseOutcome(ctx.cwd, args[0], ui);
|
|
143
|
+
return;
|
|
144
|
+
case "cancel":
|
|
145
|
+
case "stop":
|
|
146
|
+
case "abort":
|
|
147
|
+
await cancelRun(ctx.cwd, args[0], ui);
|
|
148
|
+
return;
|
|
149
|
+
case "import-web":
|
|
150
|
+
case "record":
|
|
151
|
+
notify(ui, `/playbook:${command} is deferred after the Core 6 MVP scaffold.`, "warning");
|
|
152
|
+
return;
|
|
153
|
+
default:
|
|
154
|
+
notify(ui, usage(), "error");
|
|
155
|
+
}
|
|
106
156
|
}
|
|
107
157
|
|
|
108
158
|
async function listPlaybooks(pi: ExtensionAPI, cwd: string, ui: UiLike | undefined): Promise<void> {
|
|
@@ -125,7 +175,7 @@ async function listPlaybooks(pi: ExtensionAPI, cwd: string, ui: UiLike | undefin
|
|
|
125
175
|
|
|
126
176
|
async function startPlaybook(pi: ExtensionAPI, cwd: string, args: string[], ui: UiLike | undefined): Promise<void> {
|
|
127
177
|
const playbookId = args[0];
|
|
128
|
-
if (!playbookId) throw new Error("Usage: /playbook
|
|
178
|
+
if (!playbookId) throw new Error("Usage: /playbook:start <playbook-id> [--run <name>]");
|
|
129
179
|
|
|
130
180
|
const playbook = await findPlaybook(cwd, playbookId);
|
|
131
181
|
if (!playbook) throw new Error(`Playbook '${playbookId}' not found in .pi/playbooks/.`);
|
|
@@ -157,7 +207,7 @@ async function startPlaybook(pi: ExtensionAPI, cwd: string, args: string[], ui:
|
|
|
157
207
|
|
|
158
208
|
async function resumeRun(cwd: string, args: string[], ui: UiLike | undefined): Promise<void> {
|
|
159
209
|
const runId = args[0];
|
|
160
|
-
if (!runId) throw new Error("Usage: /playbook
|
|
210
|
+
if (!runId) throw new Error("Usage: /playbook:resume <run-id>");
|
|
161
211
|
const run = await loadRun(cwd, runId);
|
|
162
212
|
if (!run) throw new Error(`Run '${runId}' not found.`);
|
|
163
213
|
if (run.status !== "active") throw new Error(`Run '${runId}' is ${run.status}.`);
|
|
@@ -170,7 +220,7 @@ async function resumeRun(cwd: string, args: string[], ui: UiLike | undefined): P
|
|
|
170
220
|
|
|
171
221
|
async function cancelRun(cwd: string, explicitRunId: string | undefined, ui: UiLike | undefined): Promise<void> {
|
|
172
222
|
const runId = explicitRunId ?? (await loadActiveRunId(cwd));
|
|
173
|
-
if (!runId) throw new Error("Usage: /playbook
|
|
223
|
+
if (!runId) throw new Error("Usage: /playbook:cancel [run-id]");
|
|
174
224
|
const run = await loadRun(cwd, runId);
|
|
175
225
|
if (!run) throw new Error(`Run '${runId}' not found.`);
|
|
176
226
|
|
|
@@ -231,7 +281,7 @@ async function completeCurrentStep(cwd: string, ui: UiLike | undefined): Promise
|
|
|
231
281
|
}
|
|
232
282
|
|
|
233
283
|
async function chooseOutcome(cwd: string, outcome: string | undefined, ui: UiLike | undefined): Promise<void> {
|
|
234
|
-
if (!outcome) throw new Error("Usage: /playbook
|
|
284
|
+
if (!outcome) throw new Error("Usage: /playbook:choose <outcome>");
|
|
235
285
|
const { run, playbook } = await loadActive(cwd);
|
|
236
286
|
await advanceRun(cwd, playbook, run, outcome, ui);
|
|
237
287
|
}
|
|
@@ -333,13 +383,14 @@ function notify(ui: UiLike | undefined, message: string, level: "info" | "warnin
|
|
|
333
383
|
function usage(): string {
|
|
334
384
|
return [
|
|
335
385
|
"Usage:",
|
|
336
|
-
"/playbook
|
|
337
|
-
"/playbook
|
|
338
|
-
"/playbook
|
|
339
|
-
"/playbook
|
|
340
|
-
"/playbook
|
|
341
|
-
"/playbook
|
|
342
|
-
"/playbook
|
|
386
|
+
"/playbook:list",
|
|
387
|
+
"/playbook:start <playbook-id> [--run <name>]",
|
|
388
|
+
"/playbook:resume <run-id>",
|
|
389
|
+
"/playbook:status [run-id]",
|
|
390
|
+
"/playbook:done",
|
|
391
|
+
"/playbook:choose <outcome>",
|
|
392
|
+
"/playbook:cancel [run-id]",
|
|
393
|
+
"Legacy space-separated /playbook <subcommand> forms remain available for compatibility.",
|
|
343
394
|
].join("\n");
|
|
344
395
|
}
|
|
345
396
|
|
|
@@ -353,6 +404,26 @@ export async function getPlaybookArgumentCompletions(cwd: string, prefix: string
|
|
|
353
404
|
}
|
|
354
405
|
}
|
|
355
406
|
|
|
407
|
+
export async function getPlaybookColonArgumentCompletions(
|
|
408
|
+
cwd: string,
|
|
409
|
+
command: string,
|
|
410
|
+
prefix: string,
|
|
411
|
+
): Promise<CompletionItem[] | null> {
|
|
412
|
+
try {
|
|
413
|
+
const normalizedPrefix = prefix.trimStart();
|
|
414
|
+
const legacyPrefix = normalizedPrefix ? `${command} ${normalizedPrefix}` : command;
|
|
415
|
+
const items = await getPlaybookArgumentCompletionsUnsafe(cwd, legacyPrefix);
|
|
416
|
+
if (!items) return null;
|
|
417
|
+
const legacyToken = `${command} `;
|
|
418
|
+
return items.map((item) => ({
|
|
419
|
+
...item,
|
|
420
|
+
value: item.value.startsWith(legacyToken) ? item.value.slice(legacyToken.length) : item.value,
|
|
421
|
+
}));
|
|
422
|
+
} catch {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
356
427
|
async function getPlaybookArgumentCompletionsUnsafe(cwd: string, prefix: string): Promise<CompletionItem[] | null> {
|
|
357
428
|
const parsed = parseCompletionPrefix(prefix);
|
|
358
429
|
const command = parsed.command;
|
package/package.json
CHANGED
package/src/auto-advance.ts
CHANGED
|
@@ -91,7 +91,7 @@ export function planCompletion(
|
|
|
91
91
|
kind: "suggest",
|
|
92
92
|
outcome: resolved.outcome,
|
|
93
93
|
to: resolved.to,
|
|
94
|
-
message: `Completion marked for step '${run.currentStep}'. Confirm outcome: /playbook
|
|
94
|
+
message: `Completion marked for step '${run.currentStep}'. Confirm outcome: /playbook:choose ${resolved.outcome}`,
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -101,8 +101,8 @@ export function planCompletion(
|
|
|
101
101
|
outcome: resolved.outcome,
|
|
102
102
|
to: resolved.to,
|
|
103
103
|
message: resolved.outcome === "complete"
|
|
104
|
-
? `Completion marked for final step '${run.currentStep}'. Run /playbook
|
|
105
|
-
: `Completion marked for step '${run.currentStep}'. Run /playbook
|
|
104
|
+
? `Completion marked for final step '${run.currentStep}'. Run /playbook:done to complete.`
|
|
105
|
+
: `Completion marked for step '${run.currentStep}'. Run /playbook:done to advance to '${resolved.to}'.`,
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -129,7 +129,7 @@ export function lastAssistantText(messages: unknown[]): string {
|
|
|
129
129
|
|
|
130
130
|
function suggestPlan(step: PlaybookStep, stepId: string): CompletionPlan {
|
|
131
131
|
if (step.transitions.length === 0) {
|
|
132
|
-
return { kind: "suggest", outcome: "complete", to: "complete", message: `Completion suspected for final step '${stepId}'. Run /playbook
|
|
132
|
+
return { kind: "suggest", outcome: "complete", to: "complete", message: `Completion suspected for final step '${stepId}'. Run /playbook:done to complete.` };
|
|
133
133
|
}
|
|
134
134
|
if (step.transitions.length === 1) {
|
|
135
135
|
const transition = step.transitions[0]!;
|
|
@@ -137,12 +137,12 @@ function suggestPlan(step: PlaybookStep, stepId: string): CompletionPlan {
|
|
|
137
137
|
kind: "suggest",
|
|
138
138
|
outcome: transition.outcome,
|
|
139
139
|
to: transition.to,
|
|
140
|
-
message: `Completion suspected for step '${stepId}'. Run /playbook
|
|
140
|
+
message: `Completion suspected for step '${stepId}'. Run /playbook:done to advance to '${transition.to}'.`,
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
return {
|
|
144
144
|
kind: "suggest",
|
|
145
|
-
message: `Completion suspected for step '${stepId}'. Choose outcome: ${step.transitions.map((transition) => `/playbook
|
|
145
|
+
message: `Completion suspected for step '${stepId}'. Choose outcome: ${step.transitions.map((transition) => `/playbook:choose ${transition.outcome}`).join(" | ")}`,
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
|