@nicknisi/pi-workflows 0.2.1 → 0.3.0
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 +15 -3
- package/dist/engine.d.ts +26 -2
- package/dist/engine.js +64 -6
- package/dist/examples.test.js +9 -2
- package/dist/index.js +125 -16
- package/dist/workflow.test.js +89 -1
- package/engine.ts +100 -6
- package/examples/autoimplement.js +80 -0
- package/examples/autoplan.js +123 -0
- package/examples/bake-off.js +107 -0
- package/examples/gates.js +106 -0
- package/examples/lanes.js +70 -0
- package/examples/sanity-check.js +85 -0
- package/index.ts +168 -14
- package/package.json +8 -3
- package/skills/autoplan/SKILL.md +29 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// sanity-check.js — read-only review of a contribution: necessary? duplicated? proportionate?
|
|
2
|
+
//
|
|
3
|
+
// Ported from osolmaz/pi-workflows' sanity-check. Four area reviewers run in
|
|
4
|
+
// parallel, then a verifier REFUTES the findings — every concern must survive
|
|
5
|
+
// a skeptic with exact file:symbol evidence or it's dropped. The verdict is
|
|
6
|
+
// strict: keep | simplify | refactor | drop | needs-evidence.
|
|
7
|
+
//
|
|
8
|
+
// THE REASONING SHAPE (the part worth stealing):
|
|
9
|
+
// 1. Evidence first: one agent collects the diff (base..HEAD + working tree
|
|
10
|
+
// + untracked) so reviewers judge facts, not the PR description.
|
|
11
|
+
// 2. Four reviewers in PARALLEL, one area each: necessity (is this needed
|
|
12
|
+
// at all?), duplication (does the codebase already do this?), contracts
|
|
13
|
+
// (are new abstractions justified?), scope+tests (proportionate?).
|
|
14
|
+
// 3. A verifier that tries to REFUTE every concern. Confirmation bias is
|
|
15
|
+
// the default failure mode of review; a skeptic pass is the fix.
|
|
16
|
+
// 4. Read-only: reviewers get read tools only; nothing edits, posts, or fixes.
|
|
17
|
+
//
|
|
18
|
+
// HOW TO ADAPT: pass { baseRef } via args — /wf run sanity-check {"baseRef":"origin/main"}
|
|
19
|
+
|
|
20
|
+
export const meta = {
|
|
21
|
+
name: 'sanity-check',
|
|
22
|
+
description: 'Parallel necessity/duplication/contracts/scope review, then a verifier that refutes every finding',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const BASE = (args && args.baseRef) || 'origin/main';
|
|
26
|
+
|
|
27
|
+
phase('evidence');
|
|
28
|
+
const evidence = await agent(
|
|
29
|
+
`Collect the contribution under review in this repo: \`git diff ${BASE}...HEAD\`, the working-tree ` +
|
|
30
|
+
`diff (\`git diff\`), untracked files (\`git status --porcelain\`), and the matching PR title/body ` +
|
|
31
|
+
`(\`gh pr view\`) if one exists. Return the full raw evidence. Do not editorialize.`,
|
|
32
|
+
{ label: 'evidence', tools: ['read', 'bash', 'grep', 'find'] },
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
phase('reviewers');
|
|
36
|
+
const AREAS = [
|
|
37
|
+
[
|
|
38
|
+
'necessity',
|
|
39
|
+
'Is this contribution NECESSARY? What breaks or stays broken without it? Could the need be met without new code?',
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
'duplication',
|
|
43
|
+
'Does the codebase already have this? Search for existing helpers, patterns, and near-misses that cover the same ground.',
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
'contracts',
|
|
47
|
+
'Are new abstractions (interfaces, config, public API surface) justified by more than one caller? Flag speculative generality.',
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
'scope-tests',
|
|
51
|
+
'Is the scope proportionate to the problem? Are tests present, focused, and actually exercising the change?',
|
|
52
|
+
],
|
|
53
|
+
];
|
|
54
|
+
const findings = await parallel(
|
|
55
|
+
AREAS.map(([area, brief]) => async () => {
|
|
56
|
+
return agent(
|
|
57
|
+
`You are the ${area} reviewer. ${brief}\n\nReport pass | concern | unclear per point, with EXACT ` +
|
|
58
|
+
`file:symbol evidence for every claim. No evidence = do not claim it.\n\nEVIDENCE:\n${evidence}`,
|
|
59
|
+
{ label: `review-${area}` },
|
|
60
|
+
);
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
phase('verify');
|
|
65
|
+
const verdict = await agent(
|
|
66
|
+
`You are a hostile verifier. Below are four reviewers' findings about a contribution. Try to REFUTE ` +
|
|
67
|
+
`every concern: check the cited file:symbol yourself and drop anything unsupported or wrong. Then ` +
|
|
68
|
+
`issue one verdict: keep | simplify | refactor | drop | needs-evidence.\n\n` +
|
|
69
|
+
`FINDINGS:\n${findings.map((f, i) => `--- ${AREAS[i][0]} ---\n${f}`).join('\n\n')}`,
|
|
70
|
+
{
|
|
71
|
+
label: 'verifier',
|
|
72
|
+
schema: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
verdict: { type: 'string', enum: ['keep', 'simplify', 'refactor', 'drop', 'needs-evidence'] },
|
|
76
|
+
survivingConcerns: { type: 'array', items: { type: 'string' } },
|
|
77
|
+
refutedClaims: { type: 'array', items: { type: 'string' } },
|
|
78
|
+
rationale: { type: 'string' },
|
|
79
|
+
},
|
|
80
|
+
required: ['verdict', 'survivingConcerns', 'rationale'],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return verdict;
|
package/index.ts
CHANGED
|
@@ -40,10 +40,13 @@ import {
|
|
|
40
40
|
} from '@nicknisi/pi-shared';
|
|
41
41
|
import { Type } from 'typebox';
|
|
42
42
|
import {
|
|
43
|
+
createRunGate,
|
|
43
44
|
runScript,
|
|
45
|
+
type EngineAskFn,
|
|
44
46
|
type EngineSpawnFn,
|
|
45
47
|
type EngineSpawnOptions,
|
|
46
48
|
type EngineSpawnResult,
|
|
49
|
+
type RunGate,
|
|
47
50
|
type RunScriptResult,
|
|
48
51
|
} from './engine.js';
|
|
49
52
|
|
|
@@ -63,6 +66,96 @@ const MAX_LOG_CHARS = 2000;
|
|
|
63
66
|
// process must never share (or cross-abort) each other's runs.
|
|
64
67
|
type Cancellables = Map<string, AbortController>;
|
|
65
68
|
|
|
69
|
+
// ── Active workflow runs (this session), for pause/resume + footer status. ──
|
|
70
|
+
// A run has no id of its own (runIds belong to child spawns), so pause/resume
|
|
71
|
+
// are session-scoped: they apply to every in-flight run. In practice one.
|
|
72
|
+
interface ActiveRun {
|
|
73
|
+
label: string;
|
|
74
|
+
gate: RunGate;
|
|
75
|
+
/** Refresh the footer status line to reflect pause/phase state. */
|
|
76
|
+
refresh: () => void;
|
|
77
|
+
}
|
|
78
|
+
type ActiveRuns = Set<ActiveRun>;
|
|
79
|
+
|
|
80
|
+
function pauseAll(activeRuns: ActiveRuns): number {
|
|
81
|
+
for (const run of activeRuns) {
|
|
82
|
+
run.gate.pause();
|
|
83
|
+
run.refresh();
|
|
84
|
+
}
|
|
85
|
+
return activeRuns.size;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function resumeAll(activeRuns: ActiveRuns): number {
|
|
89
|
+
for (const run of activeRuns) {
|
|
90
|
+
run.gate.resume();
|
|
91
|
+
run.refresh();
|
|
92
|
+
}
|
|
93
|
+
return activeRuns.size;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface UiLike {
|
|
97
|
+
setStatus: (id: string, text: string | undefined) => void;
|
|
98
|
+
confirm: (title: string, message: string) => Promise<boolean>;
|
|
99
|
+
select: (title: string, options: string[]) => Promise<string | undefined>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Per-run scaffolding: gate (pause/resume/abort), footer status that tracks
|
|
104
|
+
* phase() markers, and the checkpoint/ask host functions backed by pi's UI.
|
|
105
|
+
* checkpoint is a confirm dialog — aborting it rejects so a dismissed gate
|
|
106
|
+
* stops the run instead of silently continuing.
|
|
107
|
+
*/
|
|
108
|
+
function makeRunControls(
|
|
109
|
+
label: string,
|
|
110
|
+
ui: UiLike,
|
|
111
|
+
activeRuns: ActiveRuns,
|
|
112
|
+
): {
|
|
113
|
+
gate: RunGate;
|
|
114
|
+
onLog: (line: string) => void;
|
|
115
|
+
checkpoint: (checkpointLabel?: string) => Promise<void>;
|
|
116
|
+
ask: EngineAskFn;
|
|
117
|
+
dispose: () => void;
|
|
118
|
+
} {
|
|
119
|
+
const gate = createRunGate();
|
|
120
|
+
let lastPhase = '';
|
|
121
|
+
const run: ActiveRun = {
|
|
122
|
+
label,
|
|
123
|
+
gate,
|
|
124
|
+
refresh: () => {
|
|
125
|
+
const state = gate.paused ? 'paused' : 'running';
|
|
126
|
+
ui.setStatus('workflows', `wf ${label} [${state}]${lastPhase ? ` ${lastPhase}` : ''}`);
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
const checkpoint = async (checkpointLabel?: string): Promise<void> => {
|
|
130
|
+
const ok = await ui.confirm(
|
|
131
|
+
'Workflow checkpoint',
|
|
132
|
+
checkpointLabel ? `${checkpointLabel} — continue?` : 'Continue?',
|
|
133
|
+
);
|
|
134
|
+
if (!ok) throw new Error(`checkpoint${checkpointLabel ? ` '${checkpointLabel}'` : ''} rejected`);
|
|
135
|
+
};
|
|
136
|
+
const ask: EngineAskFn = async (question, options) => {
|
|
137
|
+
if (options && options.length > 0) return ui.select(question, options);
|
|
138
|
+
return ui.confirm('Workflow', question);
|
|
139
|
+
};
|
|
140
|
+
activeRuns.add(run);
|
|
141
|
+
run.refresh();
|
|
142
|
+
return {
|
|
143
|
+
gate,
|
|
144
|
+
checkpoint,
|
|
145
|
+
ask,
|
|
146
|
+
onLog: (line) => {
|
|
147
|
+
if (line.startsWith('── ')) {
|
|
148
|
+
lastPhase = line.slice(3);
|
|
149
|
+
run.refresh();
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
dispose: () => {
|
|
153
|
+
activeRuns.delete(run);
|
|
154
|
+
ui.setStatus('workflows', undefined);
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
66
159
|
function spawnCancellable(
|
|
67
160
|
cancellables: Cancellables,
|
|
68
161
|
runtime: SubagentRuntime,
|
|
@@ -236,6 +329,7 @@ function formatRunResult(result: RunScriptResult, label: string): string {
|
|
|
236
329
|
|
|
237
330
|
export default function workflows(pi: ExtensionAPI): void {
|
|
238
331
|
const cancellables: Cancellables = new Map();
|
|
332
|
+
const activeRuns: ActiveRuns = new Set();
|
|
239
333
|
const runtime = createSubagentRuntime({ namespace: NAMESPACE, artifactsDir: ARTIFACTS_ROOT });
|
|
240
334
|
sweepRunArtifactsOnce(ARTIFACTS_ROOT);
|
|
241
335
|
|
|
@@ -246,14 +340,21 @@ export default function workflows(pi: ExtensionAPI): void {
|
|
|
246
340
|
'Run a JavaScript workflow script that orchestrates subagents over the first-party runtime,',
|
|
247
341
|
"or manage runs. Actions: 'run' (compile a script in a vm and execute it with injected",
|
|
248
342
|
'globals), "list" (saved workflow files), "status" (a run record by runId), "stop" (cancel a',
|
|
249
|
-
|
|
343
|
+
'run by runId), "pause"/"resume" (hold active runs before their next agent step, then',
|
|
344
|
+
"continue them). For 'run', pass EITHER `script` (inline JS) OR `name` (a saved workflow file",
|
|
250
345
|
'stem from ~/.pi/agent/workflows/*.js or .pi/workflows/*.js). Optional `args` (any JSON value)',
|
|
251
346
|
"is passed in as the script's `args` global.",
|
|
252
347
|
'',
|
|
253
348
|
'Script contract — injected globals: agent(prompt, opts), parallel(thunks),',
|
|
254
349
|
'pipeline(items, ...stages), phase(name), log(...args), args, budget ({total, spent,',
|
|
255
|
-
"remaining}), cwd. The script's FIRST statement
|
|
256
|
-
'description }` (rewritten so the vm compiles;
|
|
350
|
+
"remaining}), cwd, checkpoint(label?), ask(question, options?). The script's FIRST statement",
|
|
351
|
+
'SHOULD be `export const meta = { name, description }` (rewritten so the vm compiles;',
|
|
352
|
+
'meta.name/description surface in the result).',
|
|
353
|
+
'',
|
|
354
|
+
'checkpoint(label?) gates the run on a human confirm (dismiss/reject throws and stops the',
|
|
355
|
+
'run); ask(question, options?) asks the human mid-run — select when options are given,',
|
|
356
|
+
'yes/no confirm otherwise, undefined when dismissed. Use them before destructive or',
|
|
357
|
+
'expensive steps.',
|
|
257
358
|
'The script returns a value via a trailing expression or a top-level `return` (the body is',
|
|
258
359
|
'wrapped in an async function).',
|
|
259
360
|
'',
|
|
@@ -279,9 +380,17 @@ export default function workflows(pi: ExtensionAPI): void {
|
|
|
279
380
|
'Keep the returned value small — summaries, counts, key findings — never raw file contents.',
|
|
280
381
|
],
|
|
281
382
|
parameters: Type.Object({
|
|
282
|
-
action: Type.Union(
|
|
283
|
-
|
|
284
|
-
|
|
383
|
+
action: Type.Union(
|
|
384
|
+
[
|
|
385
|
+
Type.Literal('run'),
|
|
386
|
+
Type.Literal('list'),
|
|
387
|
+
Type.Literal('status'),
|
|
388
|
+
Type.Literal('stop'),
|
|
389
|
+
Type.Literal('pause'),
|
|
390
|
+
Type.Literal('resume'),
|
|
391
|
+
],
|
|
392
|
+
{ description: 'Action: run | list | status | stop | pause | resume' },
|
|
393
|
+
),
|
|
285
394
|
script: Type.Optional(Type.String({ description: 'Inline JS workflow script (action: run).' })),
|
|
286
395
|
name: Type.Optional(Type.String({ description: 'Saved workflow file stem (action: run).' })),
|
|
287
396
|
args: Type.Optional(
|
|
@@ -335,6 +444,15 @@ export default function workflows(pi: ExtensionAPI): void {
|
|
|
335
444
|
};
|
|
336
445
|
}
|
|
337
446
|
|
|
447
|
+
if (action === 'pause' || action === 'resume') {
|
|
448
|
+
const n = action === 'pause' ? pauseAll(activeRuns) : resumeAll(activeRuns);
|
|
449
|
+
const text =
|
|
450
|
+
n === 0
|
|
451
|
+
? 'No active workflow run in this session.'
|
|
452
|
+
: `${action === 'pause' ? 'Paused' : 'Resumed'} ${n} active run${n === 1 ? '' : 's'}.`;
|
|
453
|
+
return { content: [{ type: 'text' as const, text }], details: { affected: n } };
|
|
454
|
+
}
|
|
455
|
+
|
|
338
456
|
if (action === 'stop') {
|
|
339
457
|
const runId = params.runId;
|
|
340
458
|
if (!runId) {
|
|
@@ -421,6 +539,10 @@ export default function workflows(pi: ExtensionAPI): void {
|
|
|
421
539
|
ctx.sessionManager.getSessionFile(),
|
|
422
540
|
controller.signal,
|
|
423
541
|
);
|
|
542
|
+
const controls = makeRunControls(label, ctx.ui, activeRuns);
|
|
543
|
+
// A parked run (gate.wait / checkpoint) must not leak when the tool is
|
|
544
|
+
// aborted or times out — aborting the gate rejects its waiters.
|
|
545
|
+
controller.signal.addEventListener('abort', () => controls.gate.abort(), { once: true });
|
|
424
546
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
425
547
|
controller.signal.addEventListener(
|
|
426
548
|
'abort',
|
|
@@ -442,7 +564,10 @@ export default function workflows(pi: ExtensionAPI): void {
|
|
|
442
564
|
args: params.args,
|
|
443
565
|
spawn: spawnFn,
|
|
444
566
|
cwd: ctx.cwd,
|
|
445
|
-
onLog:
|
|
567
|
+
onLog: controls.onLog,
|
|
568
|
+
gate: controls.gate,
|
|
569
|
+
checkpoint: controls.checkpoint,
|
|
570
|
+
ask: controls.ask,
|
|
446
571
|
}),
|
|
447
572
|
timeoutPromise,
|
|
448
573
|
]);
|
|
@@ -466,6 +591,7 @@ export default function workflows(pi: ExtensionAPI): void {
|
|
|
466
591
|
} finally {
|
|
467
592
|
clearTimeout(timer);
|
|
468
593
|
signal?.removeEventListener('abort', onToolAbort);
|
|
594
|
+
controls.dispose();
|
|
469
595
|
}
|
|
470
596
|
},
|
|
471
597
|
});
|
|
@@ -473,16 +599,16 @@ export default function workflows(pi: ExtensionAPI): void {
|
|
|
473
599
|
// ── /wf — thin human-facing wrapper ───────────────────────────────────
|
|
474
600
|
pi.registerCommand('wf', {
|
|
475
601
|
description:
|
|
476
|
-
'Workflows: /wf list | /wf run <name> [argsJson] | /wf status <runId> | /wf stop <runId
|
|
602
|
+
'Workflows: /wf list | /wf run <name> [argsJson] | /wf status <runId> | /wf stop <runId> | /wf pause | /wf resume. Saved workflows live in ~/.pi/agent/workflows/*.js (global) and .pi/workflows/*.js (project, trusted only).',
|
|
477
603
|
getArgumentCompletions: (argumentPrefix) => {
|
|
478
604
|
if (argumentPrefix.includes(' ')) return null;
|
|
479
605
|
const prefix = argumentPrefix.trim();
|
|
480
|
-
const subs = ['list', 'run', 'status', 'stop'].filter((s) => s.startsWith(prefix));
|
|
606
|
+
const subs = ['list', 'run', 'status', 'stop', 'pause', 'resume'].filter((s) => s.startsWith(prefix));
|
|
481
607
|
if (subs.length === 0) return null;
|
|
482
608
|
return subs.map((s) => ({ value: s + ' ', label: s }));
|
|
483
609
|
},
|
|
484
610
|
handler: async (args, ctx) => {
|
|
485
|
-
await cmdWf(args, ctx, runtime, cancellables);
|
|
611
|
+
await cmdWf(args, ctx, runtime, cancellables, activeRuns);
|
|
486
612
|
},
|
|
487
613
|
});
|
|
488
614
|
}
|
|
@@ -494,6 +620,7 @@ async function cmdWf(
|
|
|
494
620
|
ctx: ExtensionCommandContext,
|
|
495
621
|
runtime: SubagentRuntime,
|
|
496
622
|
cancellables: Cancellables,
|
|
623
|
+
activeRuns: ActiveRuns,
|
|
497
624
|
): Promise<void> {
|
|
498
625
|
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
499
626
|
const sub = parts[0];
|
|
@@ -537,7 +664,11 @@ async function cmdWf(
|
|
|
537
664
|
parsedArgs = argsJson;
|
|
538
665
|
}
|
|
539
666
|
}
|
|
540
|
-
ctx.ui
|
|
667
|
+
const controls = makeRunControls(name, ctx.ui, activeRuns);
|
|
668
|
+
if (ctx.signal) {
|
|
669
|
+
if (ctx.signal.aborted) controls.gate.abort();
|
|
670
|
+
else ctx.signal.addEventListener('abort', () => controls.gate.abort(), { once: true });
|
|
671
|
+
}
|
|
541
672
|
try {
|
|
542
673
|
const spawnFn = makeSpawnFn(
|
|
543
674
|
cancellables,
|
|
@@ -546,16 +677,36 @@ async function cmdWf(
|
|
|
546
677
|
ctx.sessionManager.getSessionFile(),
|
|
547
678
|
ctx.signal ?? undefined,
|
|
548
679
|
);
|
|
549
|
-
const result = await runScript({
|
|
680
|
+
const result = await runScript({
|
|
681
|
+
script: src,
|
|
682
|
+
args: parsedArgs,
|
|
683
|
+
spawn: spawnFn,
|
|
684
|
+
cwd: ctx.cwd,
|
|
685
|
+
onLog: controls.onLog,
|
|
686
|
+
gate: controls.gate,
|
|
687
|
+
checkpoint: controls.checkpoint,
|
|
688
|
+
ask: controls.ask,
|
|
689
|
+
});
|
|
550
690
|
ctx.ui.notify(formatRunResult(result, name), 'info');
|
|
551
691
|
} catch (err) {
|
|
552
692
|
ctx.ui.notify(`${name} failed: ${err instanceof Error ? err.message : String(err)}`, 'error');
|
|
553
693
|
} finally {
|
|
554
|
-
|
|
694
|
+
controls.dispose();
|
|
555
695
|
}
|
|
556
696
|
return;
|
|
557
697
|
}
|
|
558
698
|
|
|
699
|
+
if (sub === 'pause' || sub === 'resume') {
|
|
700
|
+
const n = sub === 'pause' ? pauseAll(activeRuns) : resumeAll(activeRuns);
|
|
701
|
+
ctx.ui.notify(
|
|
702
|
+
n === 0
|
|
703
|
+
? 'No active workflow run in this session.'
|
|
704
|
+
: `${sub === 'pause' ? 'Paused' : 'Resumed'} ${n} run${n === 1 ? '' : 's'}.`,
|
|
705
|
+
n === 0 ? 'warning' : 'info',
|
|
706
|
+
);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
559
710
|
if (sub === 'status') {
|
|
560
711
|
const runId = parts[1];
|
|
561
712
|
if (!runId) {
|
|
@@ -593,7 +744,10 @@ async function cmdWf(
|
|
|
593
744
|
return;
|
|
594
745
|
}
|
|
595
746
|
|
|
596
|
-
ctx.ui.notify(
|
|
747
|
+
ctx.ui.notify(
|
|
748
|
+
'Usage: /wf list | /wf run <name> [argsJson] | /wf status <runId> | /wf stop <runId> | /wf pause | /wf resume',
|
|
749
|
+
'warning',
|
|
750
|
+
);
|
|
597
751
|
}
|
|
598
752
|
|
|
599
753
|
// ── Run lookup / cancellation resolution ──────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nicknisi/pi-workflows",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Model-facing front door to the first-party workflow engine — run JS workflow scripts over the subagent runtime, replacing the third-party @quintinshaw/pi-dynamic-workflows extension",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
19
|
"index.ts",
|
|
20
|
-
"engine.ts"
|
|
20
|
+
"engine.ts",
|
|
21
|
+
"examples",
|
|
22
|
+
"skills"
|
|
21
23
|
],
|
|
22
24
|
"type": "module",
|
|
23
25
|
"exports": {
|
|
@@ -28,7 +30,7 @@
|
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
30
32
|
"typebox": "^1.1.0",
|
|
31
|
-
"@nicknisi/pi-shared": "0.5.
|
|
33
|
+
"@nicknisi/pi-shared": "0.5.1"
|
|
32
34
|
},
|
|
33
35
|
"peerDependencies": {
|
|
34
36
|
"@earendil-works/pi-coding-agent": "*"
|
|
@@ -36,6 +38,9 @@
|
|
|
36
38
|
"pi": {
|
|
37
39
|
"extensions": [
|
|
38
40
|
"./index.ts"
|
|
41
|
+
],
|
|
42
|
+
"skills": [
|
|
43
|
+
"./skills"
|
|
39
44
|
]
|
|
40
45
|
}
|
|
41
46
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: autoplan
|
|
3
|
+
description: Use when the user says "autoplan this", "autoplan", or wants an open-ended design/bugfix problem mined into a multiple-choice decision. Runs the saved autoplan workflow — parallel candidates, holy-grail check, advisor recommendation, human decision gate, plan write.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Autoplan
|
|
7
|
+
|
|
8
|
+
Mine the agent for ideas mechanically instead of debating design in the open: the `autoplan`
|
|
9
|
+
workflow fans out candidate solutions, checks the holy grail, ranks them with a recommendation,
|
|
10
|
+
and stops at a human decision gate. The user decides AFTER the mining, never during.
|
|
11
|
+
|
|
12
|
+
## Run it
|
|
13
|
+
|
|
14
|
+
Call the `workflow` tool: action `run`, name `autoplan`, with `args` derived from the current
|
|
15
|
+
conversation. Do not ask the user to restate what "this" means — derive it:
|
|
16
|
+
|
|
17
|
+
- `problem`: the decision or planning problem and its observable end state.
|
|
18
|
+
- `scope`: repos, systems, and interfaces that may change, plus important exclusions.
|
|
19
|
+
- `constraints`: array of user/repo/safety/authority limits; `[]` when none apply.
|
|
20
|
+
|
|
21
|
+
If `workflow` `list` shows no `autoplan`, install it first: copy `examples/autoplan.js` from the
|
|
22
|
+
`@nicknisi/pi-workflows` package into `~/.pi/agent/workflows/autoplan.js`, then run.
|
|
23
|
+
|
|
24
|
+
## After the run
|
|
25
|
+
|
|
26
|
+
- `decided: false` — the user rejected every option or dismissed the gate. Present the options and
|
|
27
|
+
the advisor's recommendation; offer to re-mine with new constraints or a proposed new option.
|
|
28
|
+
Never write a plan for an undecided run.
|
|
29
|
+
- `decided: true` — present `choice` and the `plan`. Elaborate sections on request.
|