@gonrocca/zero-pi 0.1.58 β 0.1.60
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 +34 -11
- package/extensions/autotune-extension.ts +47 -3
- package/extensions/autotune.ts +67 -0
- package/extensions/sdd-agents.ts +48 -8
- package/extensions/working-phrases.ts +2 -0
- package/extensions/zero-banner.ts +1 -1
- package/extensions/zero-checkpoint-extension.ts +62 -0
- package/extensions/zero-checkpoint.ts +92 -0
- package/extensions/zero-cost.ts +5 -4
- package/extensions/zero-models-picker.ts +2 -2
- package/extensions/zero-models.ts +18 -6
- package/extensions/zero-validate.ts +23 -2
- package/package.json +5 -2
- package/prompts/forge.md +15 -8
- package/prompts/orchestrator.md +118 -37
- package/prompts/phases/analyze.md +73 -0
- package/prompts/phases/build.md +1 -2
- package/prompts/phases/clarify.md +60 -0
- package/prompts/phases/plan.md +8 -0
package/README.md
CHANGED
|
@@ -42,15 +42,26 @@ Needs Node β₯ 20.6. Restart pi after an upgrade.
|
|
|
42
42
|
## π `/forge` β the SDD pipeline
|
|
43
43
|
|
|
44
44
|
The core of zero-pi. Run **`/forge <feature>`** and the orchestrator drives the
|
|
45
|
-
work through
|
|
45
|
+
work through six phases β the automatic
|
|
46
|
+
**clarify β explore β plan β analyze β build β veredicto** flow β each delegated
|
|
47
|
+
to its own sub-agent:
|
|
46
48
|
|
|
47
49
|
| Phase | Does |
|
|
48
50
|
| ----- | ---- |
|
|
51
|
+
| **clarify** | Record de-risking assumptions before exploration; stop only on blocking ambiguity. |
|
|
49
52
|
| **explore** | Investigate the codebase read-only; produce findings. |
|
|
50
53
|
| **plan** | Write requirements, design, and an ordered task list. |
|
|
54
|
+
| **analyze** | Review plan readiness after `/zero-validate`; decide continue or replan. |
|
|
51
55
|
| **build** | Implement the plan. |
|
|
52
56
|
| **veredicto** | Review it adversarially and record a verdict. |
|
|
53
57
|
|
|
58
|
+
The **clarify** and **analyze** gates are automatic inside `/forge` β no extra
|
|
59
|
+
slash command in the normal flow. `clarify` writes `.sdd/<slug>/clarifications.md`
|
|
60
|
+
and asks only when proceeding would risk the wrong product; `analyze` writes
|
|
61
|
+
`.sdd/<slug>/checklist.md` after the structural `/zero-validate` gate and returns
|
|
62
|
+
`continue` (build) or `replan` (re-run plan with concrete defects). Neither gate
|
|
63
|
+
counts as a build/veredicto round.
|
|
64
|
+
|
|
54
65
|
The verdict is `pasa` (done), `corregir` (re-run build), or `replantear`
|
|
55
66
|
(re-run plan). A hard iteration cap bounds the buildβveredicto loop β reached
|
|
56
67
|
without a `pasa`, the run stops and is reported as **not verified**.
|
|
@@ -74,10 +85,13 @@ into `/forge` for you.
|
|
|
74
85
|
| Feature | What it does |
|
|
75
86
|
| ------- | ------------ |
|
|
76
87
|
| **Strict TDD** | The build phase drives RED β GREEN β TRIANGULATE β REFACTOR with a TDD Cycle Evidence table; veredicto audits it. On by default, runtime-gated on a test runner; `tdd.mode: "off"` disables it. |
|
|
77
|
-
| **`/zero-models`** | Pick the model + provider + thinking level for each SDD
|
|
78
|
-
| **
|
|
88
|
+
| **`/zero-models`** | Pick the model + provider + thinking level for each of the six SDD phases β a boxed-window picker, or set one directly. Direct assignments are validated against pi's model registry when available. |
|
|
89
|
+
| **Phase tool gating** | Generated `zero-*` sub-agents get phase-specific tool allowlists: explore/veredicto are read-only, clarify/analyze/plan write only `.sdd` artifacts, build edits code. |
|
|
90
|
+
| **Dependency-aware tasks** | `tasks.md` is validated as a task graph with mandatory `depends:` edges, topological ordering, and review workload totals. |
|
|
91
|
+
| **Autotune** | Learns which model fits each phase from your run history and re-tunes itself; optional `autotuneBudget.maxPhaseCostUsd` suppresses costly step-ups. |
|
|
79
92
|
| **`/zero-doctor`** | Preflight diagnostics for zero-pi: package install, Node version, pi-subagents, generated phase agents, model config, `.sdd/config`, run history, git, and `gh` auth. |
|
|
80
93
|
| **`/zero-cost`** | Aggregates a run's sub-agent `meta.json` files into a per-phase token/cost/duration report β no schema change, reads what pi already writes. |
|
|
94
|
+
| **`/zero-checkpoint`** | Writes patch-based worktree checkpoints under `.sdd/<slug>/checkpoints/` before risky build batches. |
|
|
81
95
|
| **`/zero-sync` / `/zero-archive`** | Folds each run's spec delta into a canonical, project-wide spec store and archives approved runs. |
|
|
82
96
|
| **Git / PR / Issues** | `/zero-branch`, `/zero-git-validate`, `/zero-pr`, and `/zero-issue` keep branches and GitHub links audit-ready. |
|
|
83
97
|
| **Run memory** | Every run recalls and saves traces to Cortex, so runs learn from each other. |
|
|
@@ -102,6 +116,7 @@ into `/forge` for you.
|
|
|
102
116
|
| `/zero-validate <slug>` | Validate proposal/spec/design/tasks artifacts, including task schema and per-domain specs. |
|
|
103
117
|
| `/zero-status` | Show each `.sdd/` run's artifact, sync, latest-verdict, and GitHub-link status. |
|
|
104
118
|
| `/zero-cost [<slug>]` | Report tokens (in/out/cache), USD cost, duration, and tool-count per SDD phase for a run β `<slug>` for a specific run, no argument for the most recent. |
|
|
119
|
+
| `/zero-checkpoint [<slug>] [--json]` | Save `diff.patch`, `status.txt`, `head.txt`, `meta.json`, and a review-before-running `restore.sh` under `.sdd/<slug>/checkpoints/<id>/`. |
|
|
105
120
|
| `/zero-branch <slug>` | Create/reuse the configured SDD Git branch and persist `branch`/`baseBranch`. |
|
|
106
121
|
| `/zero-git-validate <slug>` | Check worktree, branch, remote, `gh auth`, and verdict gating before PR/archive. |
|
|
107
122
|
| `/zero-pr <slug>` | Create an audit-ready GitHub PR from a run that already has verdict `pasa`. |
|
|
@@ -111,7 +126,7 @@ into `/forge` for you.
|
|
|
111
126
|
|
|
112
127
|
### Git / PR / Issues
|
|
113
128
|
|
|
114
|
-
Recommended flow: `/zero-branch <slug>` β `/zero-issue <slug>` β `/forge <slug>` β `/zero-git-validate <slug> --for=pr` β `/zero-pr <slug>` β `/zero-archive <slug>`.
|
|
129
|
+
Recommended flow: `/zero-branch <slug>` β `/zero-issue <slug>` β `/forge <slug>` (the orchestrator validates plan artifacts and can create `/zero-checkpoint` before build) β `/zero-git-validate <slug> --for=pr` β `/zero-pr <slug>` β `/zero-archive <slug>`.
|
|
115
130
|
|
|
116
131
|
`links.json` is forward-compatible and may contain:
|
|
117
132
|
|
|
@@ -175,21 +190,29 @@ zero-pi keeps its state in `~/.pi/zero.json` (per-phase `models`, `providers`,
|
|
|
175
190
|
per-project artifacts live under `.sdd/`. Set `ZERO_RESUME=off` to disable the
|
|
176
191
|
conversation-resume note.
|
|
177
192
|
|
|
178
|
-
`~/.pi/zero.json` stores three parallel per-phase maps plus the autotune mode
|
|
193
|
+
`~/.pi/zero.json` stores three parallel per-phase maps plus the autotune mode.
|
|
194
|
+
The configurable phases are `clarify`, `explore`, `plan`, `analyze`, `build`,
|
|
195
|
+
and `veredicto`; a pre-gates file that lists only the original four stays valid
|
|
196
|
+
and the missing `clarify`/`analyze` fall back to defaults (cheap/fast clarify,
|
|
197
|
+
strong analyze):
|
|
179
198
|
|
|
180
199
|
```json
|
|
181
200
|
{
|
|
182
|
-
"models": { "
|
|
183
|
-
"providers": { "
|
|
184
|
-
"thinking": { "
|
|
185
|
-
"autotune": "ask"
|
|
201
|
+
"models": { "clarify": "claude-haiku-4-5", "analyze": "claude-opus-4-8", "build": "claude-sonnet-4-6" },
|
|
202
|
+
"providers": { "clarify": "anthropic", "analyze": "anthropic", "build": "anthropic" },
|
|
203
|
+
"thinking": { "analyze": "high", "build": "high" },
|
|
204
|
+
"autotune": "ask",
|
|
205
|
+
"autotuneBudget": { "maxPhaseCostUsd": 4, "minSamples": 3 }
|
|
186
206
|
}
|
|
187
207
|
```
|
|
188
208
|
|
|
189
209
|
The `thinking` map sets each phase's pi effort level β one of `off`, `minimal`,
|
|
190
210
|
`low`, `medium`, `high`, `xhigh`. A phase with no entry gets no `thinking:` line
|
|
191
|
-
in its generated sub-agent (no aggressive default).
|
|
192
|
-
|
|
211
|
+
in its generated sub-agent (no aggressive default). `autotuneBudget` is optional:
|
|
212
|
+
when `maxPhaseCostUsd` is set, autotune will not step a blamed phase up to a more
|
|
213
|
+
expensive tier if that phase/model is already at or above the average USD
|
|
214
|
+
ceiling with enough cost samples (`minSamples`, default 3). Set thinking from
|
|
215
|
+
the picker's after-model thinking screen, or directly:
|
|
193
216
|
|
|
194
217
|
```
|
|
195
218
|
/zero-models build=anthropic/claude-sonnet-4-6 thinking=high
|
|
@@ -19,19 +19,28 @@
|
|
|
19
19
|
// dependency-free: `node:fs`/`node:os`/`node:path`
|
|
20
20
|
// only, plus minimal local interfaces for the pi API.
|
|
21
21
|
|
|
22
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
23
23
|
import { homedir } from "node:os";
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
|
|
26
26
|
import {
|
|
27
27
|
aggregate,
|
|
28
|
+
aggregateCostStats,
|
|
28
29
|
decideAdjustments,
|
|
29
30
|
readAutotuneMode,
|
|
30
31
|
readRunRecords,
|
|
31
32
|
type Adjustment,
|
|
33
|
+
type AutotuneCostGuard,
|
|
32
34
|
} from "./autotune.ts";
|
|
35
|
+
import { parseMeta, type PhaseMeta } from "./zero-cost.ts";
|
|
33
36
|
|
|
34
|
-
/** The SDD phases
|
|
37
|
+
/** The four core SDD phases autotune reads models for and attributes blame to,
|
|
38
|
+
* in pipeline order. This is deliberately NOT the full six-phase list in
|
|
39
|
+
* `zero-models.ts`: the `clarify`/`analyze` gates are never verdict-attributable
|
|
40
|
+
* (only `corregir`βbuild and `replantear`βplan drive adjustments), and the
|
|
41
|
+
* run-record schema keeps these four as its required phases. Reading only these
|
|
42
|
+
* four means a configured gate model in `zero.json` is never turned into a
|
|
43
|
+
* pending adjustment. */
|
|
35
44
|
const PHASES = ["explore", "plan", "build", "veredicto"] as const;
|
|
36
45
|
type Phase = (typeof PHASES)[number];
|
|
37
46
|
|
|
@@ -104,6 +113,39 @@ function readCurrentModels(data: Record<string, unknown>): Partial<Record<Phase,
|
|
|
104
113
|
return models;
|
|
105
114
|
}
|
|
106
115
|
|
|
116
|
+
function readCostGuard(data: Record<string, unknown>): AutotuneCostGuard {
|
|
117
|
+
const raw = isObject(data.autotuneBudget) ? data.autotuneBudget : {};
|
|
118
|
+
const guard: AutotuneCostGuard = {};
|
|
119
|
+
if (typeof raw.maxPhaseCostUsd === "number" && Number.isFinite(raw.maxPhaseCostUsd) && raw.maxPhaseCostUsd > 0) {
|
|
120
|
+
guard.maxPhaseCostUsd = raw.maxPhaseCostUsd;
|
|
121
|
+
}
|
|
122
|
+
if (typeof raw.minSamples === "number" && Number.isFinite(raw.minSamples) && raw.minSamples > 0) {
|
|
123
|
+
guard.minSamples = raw.minSamples;
|
|
124
|
+
}
|
|
125
|
+
return guard;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readAllPhaseMetas(): PhaseMeta[] {
|
|
129
|
+
const root = join(homedir(), ".pi", "agent", "sessions");
|
|
130
|
+
if (!existsSync(root)) return [];
|
|
131
|
+
const out: PhaseMeta[] = [];
|
|
132
|
+
for (const session of readdirSync(root, { withFileTypes: true })) {
|
|
133
|
+
if (!session.isDirectory()) continue;
|
|
134
|
+
const artifacts = join(root, session.name, "subagent-artifacts");
|
|
135
|
+
if (!existsSync(artifacts)) continue;
|
|
136
|
+
for (const f of readdirSync(artifacts)) {
|
|
137
|
+
if (!f.endsWith("_meta.json")) continue;
|
|
138
|
+
try {
|
|
139
|
+
const meta = parseMeta(JSON.parse(readFileSync(join(artifacts, f), "utf8")));
|
|
140
|
+
if (meta) out.push(meta);
|
|
141
|
+
} catch {
|
|
142
|
+
// skip unreadable / malformed meta
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
|
|
107
149
|
/**
|
|
108
150
|
* Derive `knownModels` β the distinct, non-empty model ids the autotune logic
|
|
109
151
|
* may step toward. Per the design this is the union of every model seen in the
|
|
@@ -177,7 +219,9 @@ function evaluateAndTune(ctx: PiSessionContext): void {
|
|
|
177
219
|
const knownModels = deriveKnownModels(currentModels, loggedModels);
|
|
178
220
|
|
|
179
221
|
const stats = aggregate(records);
|
|
180
|
-
const
|
|
222
|
+
const costGuard = readCostGuard(data);
|
|
223
|
+
const costStats = costGuard.maxPhaseCostUsd ? aggregateCostStats(readAllPhaseMetas()) : new Map();
|
|
224
|
+
const adjustments: Adjustment[] = decideAdjustments(stats, currentModels, knownModels, costStats, costGuard);
|
|
181
225
|
if (adjustments.length === 0) return; // nothing to do β return silently.
|
|
182
226
|
|
|
183
227
|
if (mode === "auto") {
|
package/extensions/autotune.ts
CHANGED
|
@@ -265,6 +265,29 @@ export interface PhaseModelStat {
|
|
|
265
265
|
avgReplantear: number | null;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
export interface PhaseCostSample {
|
|
269
|
+
phase: unknown;
|
|
270
|
+
model: unknown;
|
|
271
|
+
usage: unknown;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export interface PhaseCostStat {
|
|
275
|
+
phase: (typeof RECORD_PHASES)[number];
|
|
276
|
+
model: string;
|
|
277
|
+
samples: number;
|
|
278
|
+
totalCost: number;
|
|
279
|
+
avgCost: number;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface AutotuneCostGuard {
|
|
283
|
+
/** Optional per-phase average cost ceiling in USD. Absent/invalid disables the guard. */
|
|
284
|
+
maxPhaseCostUsd?: number;
|
|
285
|
+
/** Minimum cost samples before the ceiling can suppress a step-up. Default: MIN_COST_SAMPLES. */
|
|
286
|
+
minSamples?: number;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export const MIN_COST_SAMPLES = 3;
|
|
290
|
+
|
|
268
291
|
/** Map key for a `(phase, model)` bucket. */
|
|
269
292
|
function statKey(phase: string, model: string): string {
|
|
270
293
|
return `${phase} ${model}`;
|
|
@@ -287,6 +310,38 @@ function statKey(phase: string, model: string): string {
|
|
|
287
310
|
* `avgReplantear` accumulator. `avgCorregir`/`avgReplantear` are `null` when a
|
|
288
311
|
* pair has no v2 evidence. An empty input yields an empty map.
|
|
289
312
|
*/
|
|
313
|
+
export function aggregateCostStats(samples: readonly PhaseCostSample[]): Map<string, PhaseCostStat> {
|
|
314
|
+
const acc = new Map<string, { phase: (typeof RECORD_PHASES)[number]; model: string; samples: number; totalCost: number }>();
|
|
315
|
+
for (const sample of samples) {
|
|
316
|
+
const phase = sample.phase;
|
|
317
|
+
if (typeof phase !== "string" || !(RECORD_PHASES as readonly string[]).includes(phase)) continue;
|
|
318
|
+
const model = sample.model;
|
|
319
|
+
if (typeof model !== "string" || model === "") continue;
|
|
320
|
+
const usage = isObject(sample.usage) ? sample.usage : {};
|
|
321
|
+
const cost = usage.cost;
|
|
322
|
+
if (typeof cost !== "number" || !Number.isFinite(cost)) continue;
|
|
323
|
+
const key = statKey(phase, model);
|
|
324
|
+
let bucket = acc.get(key);
|
|
325
|
+
if (!bucket) {
|
|
326
|
+
bucket = { phase: phase as (typeof RECORD_PHASES)[number], model, samples: 0, totalCost: 0 };
|
|
327
|
+
acc.set(key, bucket);
|
|
328
|
+
}
|
|
329
|
+
bucket.samples += 1;
|
|
330
|
+
bucket.totalCost += cost;
|
|
331
|
+
}
|
|
332
|
+
const stats = new Map<string, PhaseCostStat>();
|
|
333
|
+
for (const [key, bucket] of acc) {
|
|
334
|
+
stats.set(key, {
|
|
335
|
+
phase: bucket.phase,
|
|
336
|
+
model: bucket.model,
|
|
337
|
+
samples: bucket.samples,
|
|
338
|
+
totalCost: bucket.totalCost,
|
|
339
|
+
avgCost: bucket.samples > 0 ? bucket.totalCost / bucket.samples : 0,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return stats;
|
|
343
|
+
}
|
|
344
|
+
|
|
290
345
|
export function aggregate(records: RunRecord[]): Map<string, PhaseModelStat> {
|
|
291
346
|
interface Acc {
|
|
292
347
|
samples: number;
|
|
@@ -505,6 +560,8 @@ export function decideAdjustments(
|
|
|
505
560
|
stats: Map<string, PhaseModelStat>,
|
|
506
561
|
currentModels: Partial<Record<(typeof RECORD_PHASES)[number], string>>,
|
|
507
562
|
knownModels: readonly string[],
|
|
563
|
+
costStats: Map<string, PhaseCostStat> = new Map(),
|
|
564
|
+
costGuard: AutotuneCostGuard = {},
|
|
508
565
|
): Adjustment[] {
|
|
509
566
|
const adjustments: Adjustment[] = [];
|
|
510
567
|
|
|
@@ -522,6 +579,16 @@ export function decideAdjustments(
|
|
|
522
579
|
const threshold = phase === "build" ? HIGH_AVG_CORREGIR : HIGH_AVG_REPLANTEAR;
|
|
523
580
|
if (measure === null || !(measure > threshold)) continue;
|
|
524
581
|
|
|
582
|
+
const maxPhaseCostUsd = costGuard.maxPhaseCostUsd;
|
|
583
|
+
if (typeof maxPhaseCostUsd === "number" && Number.isFinite(maxPhaseCostUsd) && maxPhaseCostUsd > 0) {
|
|
584
|
+
const minCostSamples =
|
|
585
|
+
typeof costGuard.minSamples === "number" && Number.isFinite(costGuard.minSamples) && costGuard.minSamples > 0
|
|
586
|
+
? Math.floor(costGuard.minSamples)
|
|
587
|
+
: MIN_COST_SAMPLES;
|
|
588
|
+
const cost = costStats.get(statKey(phase, currentModel));
|
|
589
|
+
if (cost && cost.samples >= minCostSamples && cost.avgCost >= maxPhaseCostUsd) continue;
|
|
590
|
+
}
|
|
591
|
+
|
|
525
592
|
const to = stepUp(currentModel, knownModels);
|
|
526
593
|
if (to === null) continue; // already at top tier, or untierable
|
|
527
594
|
|
package/extensions/sdd-agents.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// zero-pi β SDD sub-agent provisioning.
|
|
2
2
|
//
|
|
3
|
-
// `/forge` delegates each phase to a dedicated sub-agent β `zero-
|
|
4
|
-
// `zero-plan`, `zero-build`, `zero-veredicto`.
|
|
5
|
-
// from `~/.pi/agent/agents/**/*.md`, but a
|
|
6
|
-
// the phase *prompts* (`prompts/phases/*.md`),
|
|
7
|
-
// so `/forge` had nothing to delegate to and
|
|
3
|
+
// `/forge` delegates each phase to a dedicated sub-agent β `zero-clarify`,
|
|
4
|
+
// `zero-explore`, `zero-plan`, `zero-analyze`, `zero-build`, `zero-veredicto`.
|
|
5
|
+
// pi-subagents discovers agents from `~/.pi/agent/agents/**/*.md`, but a
|
|
6
|
+
// `pi install` of zero-pi ships only the phase *prompts* (`prompts/phases/*.md`),
|
|
7
|
+
// never the agent definitions β so `/forge` had nothing to delegate to and
|
|
8
|
+
// stalled.
|
|
8
9
|
//
|
|
9
|
-
// This extension closes that gap: at load it generates the
|
|
10
|
+
// This extension closes that gap: at load it generates the six agent files
|
|
10
11
|
// under `~/.pi/agent/agents/zero/` from the package's own phase prompts and
|
|
11
12
|
// the per-phase models in `~/.pi/zero.json`. The files are regenerated every
|
|
12
13
|
// load, so they stay in sync with the prompts and with `/zero-models`.
|
|
@@ -19,8 +20,10 @@ import { fileURLToPath } from "node:url";
|
|
|
19
20
|
import { isThinkingLevel } from "./zero-models.ts";
|
|
20
21
|
import type { ThinkingLevel } from "./zero-models.ts";
|
|
21
22
|
|
|
22
|
-
/** The
|
|
23
|
-
|
|
23
|
+
/** The six SDD phases, in pipeline order, each backed by a
|
|
24
|
+
* `prompts/phases/<phase>.md`. `clarify` is the pre-explore assumption gate and
|
|
25
|
+
* `analyze` is the post-plan readiness gate; both write only `.sdd` artifacts. */
|
|
26
|
+
export const PHASES = ["clarify", "explore", "plan", "analyze", "build", "veredicto"] as const;
|
|
24
27
|
export type Phase = (typeof PHASES)[number];
|
|
25
28
|
|
|
26
29
|
/**
|
|
@@ -32,6 +35,41 @@ export type Phase = (typeof PHASES)[number];
|
|
|
32
35
|
*/
|
|
33
36
|
export const SUPPORT_MODULES = ["strict-tdd.md", "strict-tdd-verify.md"] as const;
|
|
34
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Phase-specific builtin tool allowlists for generated pi-subagents agents.
|
|
40
|
+
*
|
|
41
|
+
* Explore and veredicto are intentionally read-only at the mutation-tool layer:
|
|
42
|
+
* they can inspect files and run scoped verification commands, but they cannot
|
|
43
|
+
* edit. The `clarify` and `analyze` gates can write β but only their own
|
|
44
|
+
* `.sdd/<slug>/` artifacts (`clarifications.md`, `checklist.md`); the tool
|
|
45
|
+
* allowlist cannot enforce paths, so their prompts state the `.sdd`-only write
|
|
46
|
+
* boundary. Plan can write SDD artifacts; build is the only phase with the full
|
|
47
|
+
* mutation set for product code. This is enforced by pi-subagents' `tools:`
|
|
48
|
+
* frontmatter key, so an accidental phase prompt drift does not silently grant
|
|
49
|
+
* broad editing power to every child.
|
|
50
|
+
*/
|
|
51
|
+
export const PHASE_TOOLS: Record<Phase, readonly string[]> = {
|
|
52
|
+
clarify: ["read", "bash", "write", "edit"],
|
|
53
|
+
explore: ["read", "bash"],
|
|
54
|
+
plan: ["read", "bash", "write", "edit"],
|
|
55
|
+
analyze: ["read", "bash", "write", "edit"],
|
|
56
|
+
build: ["read", "bash", "write", "edit"],
|
|
57
|
+
veredicto: ["read", "bash"],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Non-build phases may mention implementation terms while using `bash`; do not
|
|
61
|
+
* let pi-subagents' implementation completion guard classify them as writers.
|
|
62
|
+
* Only `build` is a real implementation phase β the gates and read phases all
|
|
63
|
+
* disable the guard. */
|
|
64
|
+
export const PHASE_COMPLETION_GUARD: Record<Phase, boolean> = {
|
|
65
|
+
clarify: false,
|
|
66
|
+
explore: false,
|
|
67
|
+
plan: false,
|
|
68
|
+
analyze: false,
|
|
69
|
+
build: true,
|
|
70
|
+
veredicto: false,
|
|
71
|
+
};
|
|
72
|
+
|
|
35
73
|
/** Absolute path of the runtime support dir the phase prompts reference. */
|
|
36
74
|
export function supportModulesDir(): string {
|
|
37
75
|
return join(homedir(), ".pi", "agent", "agents", "zero", "support");
|
|
@@ -75,6 +113,8 @@ export function buildAgentFile(
|
|
|
75
113
|
// defensive: callers already validate, but the file builder must never write
|
|
76
114
|
// a bad level into agent frontmatter.
|
|
77
115
|
if (thinking && isThinkingLevel(thinking)) front.push(`thinking: ${thinking}`);
|
|
116
|
+
front.push(`tools: ${PHASE_TOOLS[phase].join(", ")}`);
|
|
117
|
+
if (!PHASE_COMPLETION_GUARD[phase]) front.push("completionGuard: false");
|
|
78
118
|
front.push(
|
|
79
119
|
"systemPromptMode: replace",
|
|
80
120
|
"inheritProjectContext: true",
|
|
@@ -65,8 +65,10 @@ const THINKING_SDD = [
|
|
|
65
65
|
|
|
66
66
|
/** SDD phase labels keyed by the zero sub-agent that owns the phase. */
|
|
67
67
|
const SDD_PHASE: Record<string, string> = {
|
|
68
|
+
"zero-clarify": "Aclarando supuestos",
|
|
68
69
|
"zero-explore": "Explorando el cΓ³digo",
|
|
69
70
|
"zero-plan": "Planeando la soluciΓ³n",
|
|
71
|
+
"zero-analyze": "Analizando el plan",
|
|
70
72
|
"zero-build": "Construyendo la implementaciΓ³n",
|
|
71
73
|
"zero-veredicto": "Revisando el veredicto",
|
|
72
74
|
};
|
|
@@ -156,7 +156,7 @@ export function bannerBlock(width: number): string[] {
|
|
|
156
156
|
if (width < 64) {
|
|
157
157
|
return [center(fg(PEACH, "ZERO SDD"), width), center(fg(MUTED, "pi.dev Β· spec-driven work"), width)];
|
|
158
158
|
}
|
|
159
|
-
const tag = fg(PEACH, "ZERO SDD") + fg(MUTED, " explore β plan β build β veredicto");
|
|
159
|
+
const tag = fg(PEACH, "ZERO SDD") + fg(MUTED, " clarify β explore β plan β analyze β build β veredicto");
|
|
160
160
|
return [ornament(width), ...renderLogo(width), center(tag, width), ornament(width)];
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
import { createCheckpoint, formatCheckpointReport, parseCheckpointArgs, type CheckpointRunner } from "./zero-checkpoint.ts";
|
|
6
|
+
|
|
7
|
+
const SDD_DIR = ".sdd";
|
|
8
|
+
|
|
9
|
+
type NotifyType = "info" | "warning" | "error";
|
|
10
|
+
interface PiCommandContext { ui: { notify(message: string, type?: NotifyType): void } }
|
|
11
|
+
interface PiExtensionAPI { registerCommand(name: string, options: { description?: string; handler: (args: string, ctx: PiCommandContext) => void | Promise<void> }): void }
|
|
12
|
+
|
|
13
|
+
function resolveSlug(explicit: string | null): string | null {
|
|
14
|
+
if (explicit) return explicit;
|
|
15
|
+
try {
|
|
16
|
+
const candidates = readdirSync(SDD_DIR, { withFileTypes: true })
|
|
17
|
+
.filter((e) => e.isDirectory() && e.name !== "specs" && e.name !== "archive" && existsSync(join(SDD_DIR, e.name)))
|
|
18
|
+
.map((e) => e.name);
|
|
19
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function defaultRunner(cwd: string): CheckpointRunner {
|
|
26
|
+
return {
|
|
27
|
+
run(command, args) {
|
|
28
|
+
try {
|
|
29
|
+
const r = spawnSync(command, [...args], { cwd, encoding: "utf8" });
|
|
30
|
+
return {
|
|
31
|
+
status: typeof r.status === "number" ? r.status : 1,
|
|
32
|
+
stdout: r.stdout ?? "",
|
|
33
|
+
stderr: r.stderr ?? "",
|
|
34
|
+
error: r.error?.message,
|
|
35
|
+
};
|
|
36
|
+
} catch (err) {
|
|
37
|
+
return { status: 1, stdout: "", stderr: "", error: err instanceof Error ? err.message : String(err) };
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function runCheckpoint(args: string, ctx: PiCommandContext, runner = defaultRunner(process.cwd())): void {
|
|
44
|
+
const notify = (m: string, t?: NotifyType) => { try { ctx.ui.notify(m, t); } catch {} };
|
|
45
|
+
const parsed = parseCheckpointArgs(args ?? "");
|
|
46
|
+
const slug = resolveSlug(parsed.slug);
|
|
47
|
+
if (!slug) { notify("zero-checkpoint: no hay un ΓΊnico run β corrΓ© /zero-checkpoint <slug>", "warning"); return; }
|
|
48
|
+
const result = createCheckpoint(slug, process.cwd(), runner);
|
|
49
|
+
if (parsed.json) notify(JSON.stringify(result, null, 2), result.ok ? "info" : "error");
|
|
50
|
+
else notify(formatCheckpointReport(result), result.ok ? "info" : "error");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default function register(pi?: PiExtensionAPI): void {
|
|
54
|
+
if (!pi || typeof pi.registerCommand !== "function") return;
|
|
55
|
+
pi.registerCommand("zero-checkpoint", {
|
|
56
|
+
description: "Crea un checkpoint patch-based del worktree actual dentro de .sdd/<slug>/checkpoints/",
|
|
57
|
+
handler: (args: string, ctx: PiCommandContext): void => {
|
|
58
|
+
try { if (ctx?.ui?.notify) runCheckpoint(args ?? "", ctx); }
|
|
59
|
+
catch (err) { try { ctx.ui.notify(`zero-checkpoint: ${err instanceof Error ? err.message : String(err)}`, "error"); } catch {} }
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface CheckpointRunner {
|
|
5
|
+
run(command: string, args: readonly string[]): { status: number; stdout: string; stderr: string; error?: string };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CheckpointResult {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
slug: string;
|
|
11
|
+
id: string;
|
|
12
|
+
dir: string;
|
|
13
|
+
head: string;
|
|
14
|
+
dirty: boolean;
|
|
15
|
+
untracked: string[];
|
|
16
|
+
message?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function checkpointId(date = new Date()): string {
|
|
20
|
+
const pad = (n: number): string => String(n).padStart(2, "0");
|
|
21
|
+
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function parseCheckpointArgs(args: string): { slug: string | null; json: boolean } {
|
|
25
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
26
|
+
let slug: string | null = null;
|
|
27
|
+
let json = false;
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
if (part === "--json") json = true;
|
|
30
|
+
else if (!slug) slug = part;
|
|
31
|
+
}
|
|
32
|
+
return { slug, json };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function untrackedFromStatus(status: string): string[] {
|
|
36
|
+
return status
|
|
37
|
+
.split(/\r?\n/)
|
|
38
|
+
.filter((line) => line.startsWith("?? "))
|
|
39
|
+
.map((line) => line.slice(3).trim())
|
|
40
|
+
.filter(Boolean);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function formatCheckpointReport(result: CheckpointResult): string {
|
|
44
|
+
if (!result.ok) return `zero-checkpoint: ${result.message ?? "fallΓ³"}`;
|
|
45
|
+
const lines = [
|
|
46
|
+
`zero-checkpoint: ${result.slug} @ ${result.id}`,
|
|
47
|
+
` dir: ${result.dir}`,
|
|
48
|
+
` head: ${result.head}`,
|
|
49
|
+
result.dirty ? " estado: cambios capturados en diff.patch" : " estado: worktree limpio (checkpoint base)",
|
|
50
|
+
];
|
|
51
|
+
if (result.untracked.length > 0) {
|
|
52
|
+
lines.push(` aviso: ${result.untracked.length} untracked no se incluyen en diff.patch (${result.untracked.slice(0, 5).join(", ")}${result.untracked.length > 5 ? ", β¦" : ""})`);
|
|
53
|
+
}
|
|
54
|
+
lines.push(" restore: revisΓ‘ restore.sh antes de ejecutarlo");
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createCheckpoint(
|
|
59
|
+
slug: string,
|
|
60
|
+
cwd: string,
|
|
61
|
+
runner: CheckpointRunner,
|
|
62
|
+
now = new Date(),
|
|
63
|
+
): CheckpointResult {
|
|
64
|
+
const root = runner.run("git", ["rev-parse", "--show-toplevel"]);
|
|
65
|
+
if (root.status !== 0) return { ok: false, slug, id: "", dir: "", head: "", dirty: false, untracked: [], message: "este cwd no parece estar dentro de un repo git" };
|
|
66
|
+
const repoRoot = root.stdout.trim() || cwd;
|
|
67
|
+
|
|
68
|
+
const head = runner.run("git", ["rev-parse", "--short", "HEAD"]);
|
|
69
|
+
if (head.status !== 0) return { ok: false, slug, id: "", dir: "", head: "", dirty: false, untracked: [], message: "no pude resolver HEAD" };
|
|
70
|
+
|
|
71
|
+
const status = runner.run("git", ["status", "--short"]);
|
|
72
|
+
const diff = runner.run("git", ["diff", "--binary", "HEAD"]);
|
|
73
|
+
if (status.status !== 0 || diff.status !== 0) return { ok: false, slug, id: "", dir: "", head: head.stdout.trim(), dirty: false, untracked: [], message: "no pude leer el estado git" };
|
|
74
|
+
|
|
75
|
+
const id = checkpointId(now);
|
|
76
|
+
const dir = join(repoRoot, ".sdd", slug, "checkpoints", id);
|
|
77
|
+
mkdirSync(dir, { recursive: true });
|
|
78
|
+
const untracked = untrackedFromStatus(status.stdout);
|
|
79
|
+
const dirty = status.stdout.trim() !== "";
|
|
80
|
+
|
|
81
|
+
writeFileSync(join(dir, "head.txt"), `${head.stdout.trim()}\n`, "utf8");
|
|
82
|
+
writeFileSync(join(dir, "status.txt"), status.stdout, "utf8");
|
|
83
|
+
writeFileSync(join(dir, "diff.patch"), diff.stdout, "utf8");
|
|
84
|
+
writeFileSync(join(dir, "meta.json"), `${JSON.stringify({ slug, id, head: head.stdout.trim(), dirty, untracked, createdAt: now.toISOString() }, null, 2)}\n`, "utf8");
|
|
85
|
+
writeFileSync(
|
|
86
|
+
join(dir, "restore.sh"),
|
|
87
|
+
`#!/usr/bin/env bash\nset -euo pipefail\n# Review before running. This restores tracked files to checkpoint ${id}.\ngit reset --hard ${head.stdout.trim()}\ngit apply --3way ${JSON.stringify(join(dir, "diff.patch"))}\n`,
|
|
88
|
+
"utf8",
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return { ok: true, slug, id, dir, head: head.stdout.trim(), dirty, untracked };
|
|
92
|
+
}
|
package/extensions/zero-cost.ts
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
import { formatTokens } from "./format-tokens.ts";
|
|
15
15
|
|
|
16
|
-
/** The SDD phases a run is composed of, in pipeline order
|
|
17
|
-
|
|
16
|
+
/** The SDD phases a run is composed of, in pipeline order β the `clarify` and
|
|
17
|
+
* `analyze` gate sub-agents write cost meta like any other phase. */
|
|
18
|
+
export const COST_PHASES = ["clarify", "explore", "plan", "analyze", "build", "veredicto"] as const;
|
|
18
19
|
export type CostPhase = (typeof COST_PHASES)[number];
|
|
19
20
|
|
|
20
21
|
/** Token + cost usage of a single sub-agent run. */
|
|
@@ -57,12 +58,12 @@ export interface RunCost {
|
|
|
57
58
|
total: PhaseUsage & { durationMs: number; toolCount: number; subAgents: number };
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
const PHASE_INDEX: Record<CostPhase, number> = {
|
|
61
|
+
const PHASE_INDEX: Record<CostPhase, number> = { clarify: 0, explore: 1, plan: 2, analyze: 3, build: 4, veredicto: 5 };
|
|
61
62
|
|
|
62
63
|
/** Map a sub-agent name `zero-<phase>` to its phase, or `null`. */
|
|
63
64
|
export function phaseFromAgent(agent: unknown): CostPhase | null {
|
|
64
65
|
if (typeof agent !== "string") return null;
|
|
65
|
-
const m = /^zero-(explore|plan|build|veredicto)$/.exec(agent);
|
|
66
|
+
const m = /^zero-(clarify|explore|plan|analyze|build|veredicto)$/.exec(agent);
|
|
66
67
|
return m ? (m[1] as CostPhase) : null;
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -18,8 +18,8 @@ import type { AutotunePending } from "./autotune-extension.ts";
|
|
|
18
18
|
|
|
19
19
|
/** The SDD phases, in pipeline order β re-stated locally so the pure module
|
|
20
20
|
* carries no value import. Must stay in lockstep with `PHASES` in
|
|
21
|
-
* `zero-models.ts
|
|
22
|
-
const PHASES = ["explore", "plan", "build", "veredicto"] as const;
|
|
21
|
+
* `zero-models.ts` (the `clarify` gate leads, `analyze` sits after `plan`). */
|
|
22
|
+
const PHASES = ["clarify", "explore", "plan", "analyze", "build", "veredicto"] as const;
|
|
23
23
|
|
|
24
24
|
/** The three autotune modes offered on the autotune screen. */
|
|
25
25
|
const AUTOTUNE_MODES = ["auto", "ask", "off"] as const;
|
|
@@ -51,8 +51,10 @@ import {
|
|
|
51
51
|
type PickerState,
|
|
52
52
|
} from "./zero-models-picker.ts";
|
|
53
53
|
|
|
54
|
-
/** The SDD phases, in pipeline order.
|
|
55
|
-
|
|
54
|
+
/** The SDD phases, in pipeline order. `clarify` is the pre-explore assumption
|
|
55
|
+
* gate and `analyze` is the post-plan readiness gate; both are configurable
|
|
56
|
+
* here like any other phase. */
|
|
57
|
+
export const PHASES = ["clarify", "explore", "plan", "analyze", "build", "veredicto"] as const;
|
|
56
58
|
export type Phase = (typeof PHASES)[number];
|
|
57
59
|
|
|
58
60
|
/** The per-phase model map. */
|
|
@@ -74,11 +76,14 @@ export function isThinkingLevel(value: unknown): value is ThinkingLevel {
|
|
|
74
76
|
return typeof value === "string" && (THINKING_LEVELS as readonly string[]).includes(value);
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
/** Fallback models when `~/.pi/zero.json` has none β cheap to explore,
|
|
78
|
-
* to plan and review.
|
|
79
|
+
/** Fallback models when `~/.pi/zero.json` has none β cheap to clarify/explore,
|
|
80
|
+
* strong to plan, analyze and review. A pre-gates zero.json missing `clarify`/
|
|
81
|
+
* `analyze` keeps its four phases and gets these gate defaults in memory. */
|
|
79
82
|
const DEFAULT_MODELS: PhaseModels = {
|
|
83
|
+
clarify: "claude-haiku-4-5",
|
|
80
84
|
explore: "claude-haiku-4-5",
|
|
81
85
|
plan: "claude-opus-4-8",
|
|
86
|
+
analyze: "claude-opus-4-8",
|
|
82
87
|
build: "claude-sonnet-4-6",
|
|
83
88
|
veredicto: "claude-opus-4-8",
|
|
84
89
|
};
|
|
@@ -144,7 +149,14 @@ function stripThinkingSuffix(model: string): string {
|
|
|
144
149
|
*/
|
|
145
150
|
export function readProviders(data: Record<string, unknown>): PhaseProviders {
|
|
146
151
|
const raw = (data.providers ?? {}) as Record<string, unknown>;
|
|
147
|
-
const providers: PhaseProviders = {
|
|
152
|
+
const providers: PhaseProviders = {
|
|
153
|
+
clarify: "",
|
|
154
|
+
explore: "",
|
|
155
|
+
plan: "",
|
|
156
|
+
analyze: "",
|
|
157
|
+
build: "",
|
|
158
|
+
veredicto: "",
|
|
159
|
+
};
|
|
148
160
|
for (const phase of PHASES) {
|
|
149
161
|
if (typeof raw[phase] === "string") providers[phase] = raw[phase] as string;
|
|
150
162
|
}
|
|
@@ -778,7 +790,7 @@ export default function register(pi?: PiExtensionAPI): void {
|
|
|
778
790
|
if (!assignment) {
|
|
779
791
|
ctx.ui.notify(
|
|
780
792
|
"uso: /zero-models βoβ /zero-models <fase>=[<provider>/]<modelo> [thinking=<nivel>] " +
|
|
781
|
-
"(fase: explore | plan | build | veredicto Β· " +
|
|
793
|
+
"(fase: clarify | explore | plan | analyze | build | veredicto Β· " +
|
|
782
794
|
"nivel: off | minimal | low | medium | high | xhigh) βoβ " +
|
|
783
795
|
"/zero-models autotune=<modo>",
|
|
784
796
|
"warning",
|
|
@@ -17,6 +17,7 @@ export interface TaskRecord {
|
|
|
17
17
|
id: string;
|
|
18
18
|
title: string;
|
|
19
19
|
files: { path: string; isNew: boolean }[];
|
|
20
|
+
depends: string[] | null;
|
|
20
21
|
evidence: string | null;
|
|
21
22
|
review: number | null;
|
|
22
23
|
reviewRaw: string | null;
|
|
@@ -27,6 +28,13 @@ export interface WorkloadSection {
|
|
|
27
28
|
declaredTotal: number | null;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
export function parseDepends(raw: string): string[] {
|
|
32
|
+
const value = raw.trim();
|
|
33
|
+
if (value === "" || value === "[]" || /^(none|n\/a|na)$/i.test(value)) return [];
|
|
34
|
+
const ids = [...value.matchAll(/\bT\d+\b/g)].map((m) => m[0]);
|
|
35
|
+
return [...new Set(ids)];
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
export function parseTasks(text: string): { tasks: TaskRecord[]; workload: WorkloadSection | null; defects: ValidationDefect[] } {
|
|
31
39
|
const tasks: TaskRecord[] = [];
|
|
32
40
|
const defects: ValidationDefect[] = [];
|
|
@@ -43,7 +51,7 @@ export function parseTasks(text: string): { tasks: TaskRecord[]; workload: Workl
|
|
|
43
51
|
for (const line of lines) {
|
|
44
52
|
const task = line.match(/^\s*- \[[ xX]\]\s+\*\*(T\d+)\.\s+([^*]+)\*\*/) ?? line.match(/^###\s+(T\d+)\s+[β-]\s+(.+)$/) ?? line.match(/^##\s+\[[ xX]\]\s+(T\d+)\s*(?:[β-]\s*(.+))?$/);
|
|
45
53
|
if (task) {
|
|
46
|
-
current = { id: task[1], title: (task[2] ?? "").trim(), files: [], evidence: null, review: null, reviewRaw: null };
|
|
54
|
+
current = { id: task[1], title: (task[2] ?? "").trim(), files: [], depends: null, evidence: null, review: null, reviewRaw: null };
|
|
47
55
|
tasks.push(current);
|
|
48
56
|
collectingFiles = false;
|
|
49
57
|
continue;
|
|
@@ -54,6 +62,11 @@ export function parseTasks(text: string): { tasks: TaskRecord[]; workload: Workl
|
|
|
54
62
|
pushFile(line);
|
|
55
63
|
continue;
|
|
56
64
|
}
|
|
65
|
+
if (/^\s*-\s+depends:/.test(line)) {
|
|
66
|
+
collectingFiles = false;
|
|
67
|
+
current.depends = parseDepends(line.replace(/^\s*-\s+depends:\s*/, ""));
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
57
70
|
if (/^\s*-\s+evidence:/.test(line)) {
|
|
58
71
|
collectingFiles = false;
|
|
59
72
|
current.evidence = line.replace(/^\s*-\s+evidence:\s*/, "").trim();
|
|
@@ -73,8 +86,16 @@ export function parseTasks(text: string): { tasks: TaskRecord[]; workload: Workl
|
|
|
73
86
|
}
|
|
74
87
|
}
|
|
75
88
|
|
|
76
|
-
|
|
89
|
+
const order = new Map(tasks.map((task, index) => [task.id, index] as const));
|
|
90
|
+
for (const [index, task] of tasks.entries()) {
|
|
77
91
|
if (task.files.length === 0) defects.push({ kind: "missing-files", task: task.id, message: `${task.id} is missing files list` });
|
|
92
|
+
if (task.depends === null) defects.push({ kind: "missing-depends", task: task.id, message: `${task.id} is missing depends list` });
|
|
93
|
+
else for (const dep of task.depends) {
|
|
94
|
+
const depIndex = order.get(dep);
|
|
95
|
+
if (dep === task.id) defects.push({ kind: "self-depends", task: task.id, message: `${task.id} cannot depend on itself` });
|
|
96
|
+
else if (depIndex === undefined) defects.push({ kind: "unknown-depends", task: task.id, message: `${task.id} depends on unknown ${dep}` });
|
|
97
|
+
else if (depIndex > index) defects.push({ kind: "forward-depends", task: task.id, message: `${task.id} depends on later task ${dep}` });
|
|
98
|
+
}
|
|
78
99
|
if (task.evidence === null || task.evidence === "") defects.push({ kind: "missing-evidence", task: task.id, message: `${task.id} is missing evidence` });
|
|
79
100
|
if (task.reviewRaw === null) defects.push({ kind: "missing-review", task: task.id, message: `${task.id} is missing review estimate` });
|
|
80
101
|
else if (task.review === null) defects.push({ kind: "non-integer-review", task: task.id, message: `${task.id} review estimate is not an integer` });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gonrocca/zero-pi",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "zero-pi β an installable layer for pi (pi.dev): the zero spec-driven development workflow (explore β plan β build β veredicto) with per-phase model autotune and token-efficient batched builds. Adds capability to pi without modifying pi.",
|
|
3
|
+
"version": "0.1.60",
|
|
4
|
+
"description": "zero-pi β an installable layer for pi (pi.dev): the zero spec-driven development workflow (clarify β explore β plan β analyze β build β veredicto) with automatic clarify/analyze gates, per-phase model autotune, and token-efficient batched builds. Adds capability to pi without modifying pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"./extensions/zero-status-extension.ts",
|
|
38
38
|
"./extensions/zero-doctor-extension.ts",
|
|
39
39
|
"./extensions/zero-cost-extension.ts",
|
|
40
|
+
"./extensions/zero-checkpoint-extension.ts",
|
|
40
41
|
"./extensions/zero-pr-extension.ts",
|
|
41
42
|
"./extensions/zero-branch-extension.ts",
|
|
42
43
|
"./extensions/zero-git-validate-extension.ts",
|
|
@@ -72,6 +73,8 @@
|
|
|
72
73
|
"extensions/zero-doctor-extension.ts",
|
|
73
74
|
"extensions/zero-cost.ts",
|
|
74
75
|
"extensions/zero-cost-extension.ts",
|
|
76
|
+
"extensions/zero-checkpoint.ts",
|
|
77
|
+
"extensions/zero-checkpoint-extension.ts",
|
|
75
78
|
"extensions/format-tokens.ts",
|
|
76
79
|
"extensions/pr-body.ts",
|
|
77
80
|
"extensions/sdd-links.ts",
|
package/prompts/forge.md
CHANGED
|
@@ -3,13 +3,20 @@ description: Run the zero spec-driven development pipeline for a feature request
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
Run the zero SDD pipeline for the feature request below β you are the
|
|
6
|
-
orchestrator. Drive it through
|
|
7
|
-
veredicto**, delegating each to
|
|
8
|
-
`zero-
|
|
9
|
-
`
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
orchestrator. Drive it through the phases in order:
|
|
7
|
+
**clarify β explore β plan β analyze β build β veredicto**, delegating each to
|
|
8
|
+
its sub-agent (`zero-clarify`, `zero-explore`, `zero-plan`, `zero-analyze`,
|
|
9
|
+
`zero-build`, `zero-veredicto`). The `clarify` and `analyze` gates run
|
|
10
|
+
**automatically** inside `/forge` β there is no slash command a user must run
|
|
11
|
+
by hand for the normal flow. `clarify` records assumptions in
|
|
12
|
+
`.sdd/<slug>/clarifications.md` and stops only on genuinely blocking ambiguity;
|
|
13
|
+
`analyze` writes `.sdd/<slug>/checklist.md` after `plan`/`/zero-validate` and
|
|
14
|
+
returns `continue` (proceed to build) or `replan` (re-run plan with its
|
|
15
|
+
defects). A `corregir` verdict re-runs `build`; a `replantear` verdict re-runs
|
|
16
|
+
`plan`; after a few rounds with no `pasa`, stop and report the result as not
|
|
17
|
+
verified. Ask the user for interactive or automatic mode up front; in
|
|
18
|
+
interactive mode pause after each phase for approval. Never claim success unless
|
|
19
|
+
veredicto returned `pasa`.
|
|
13
20
|
|
|
14
21
|
**Parse the arguments first.** If the request begins with `--continue`, this is
|
|
15
22
|
a **resume** run, not a fresh one:
|
|
@@ -31,7 +38,7 @@ veredicto audits that evidence and returns `corregir` if it fails. Set
|
|
|
31
38
|
**Output and language.** Follow the orchestrator's `## Language Boundary` and
|
|
32
39
|
`## Output Contract`: user-facing chat in Spanish (natural Rioplatense voseo),
|
|
33
40
|
the bounded per-phase summary, no raw tool output, no agent listings, no
|
|
34
|
-
step-by-step narration β progress and the final verdict, never a log. The
|
|
41
|
+
step-by-step narration β progress and the final verdict, never a log. The six
|
|
35
42
|
`zero-*` sub-agents already exist; delegate to them directly without running a
|
|
36
43
|
`subagent` listing.
|
|
37
44
|
|
package/prompts/orchestrator.md
CHANGED
|
@@ -1,28 +1,43 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: zero SDD orchestrator β drives the explore β plan β build β veredicto pipeline
|
|
2
|
+
description: zero SDD orchestrator β drives the clarify β explore β plan β analyze β build β veredicto pipeline
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# zero β SDD Orchestrator
|
|
6
6
|
|
|
7
7
|
You are the orchestrator of a spec-driven development (SDD) run. You COORDINATE
|
|
8
8
|
the work; you decide what runs next β never let the loop drift on the model's
|
|
9
|
-
whim. Drive the run through
|
|
10
|
-
|
|
11
|
-
1. **
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
whim. Drive the run through the phases, in order:
|
|
10
|
+
|
|
11
|
+
1. **clarify** β record de-risking assumptions before exploration; stop only on
|
|
12
|
+
genuinely blocking ambiguity. Writes `.sdd/<slug>/clarifications.md`.
|
|
13
|
+
2. **explore** β investigate the codebase read-only; produce findings.
|
|
14
|
+
3. **plan** β write a plan: requirements, design, and an ordered task list.
|
|
15
|
+
4. **analyze** β review plan readiness after `/zero-validate`; decide
|
|
16
|
+
`continue` or `replan`. Writes `.sdd/<slug>/checklist.md`.
|
|
17
|
+
5. **build** β implement the plan.
|
|
18
|
+
6. **veredicto** β review the build adversarially with a fresh perspective and
|
|
15
19
|
record a verdict: `pasa`, `corregir`, or `replantear`.
|
|
16
20
|
|
|
21
|
+
The full automatic flow is
|
|
22
|
+
**clarify β explore β plan β /zero-validate β analyze β build β veredicto**.
|
|
23
|
+
Both gates run automatically inside `/forge`; a user never runs them by a
|
|
24
|
+
separate slash command in the normal flow. (Any manual slash form is a debug
|
|
25
|
+
override only, never part of the automatic run.)
|
|
26
|
+
|
|
17
27
|
## Phase order and the iteration cap
|
|
18
28
|
|
|
19
29
|
- A `pasa` verdict finishes the run successfully.
|
|
20
30
|
- A `corregir` verdict re-runs **build**.
|
|
21
|
-
- A `replantear` verdict re-runs **plan**, then
|
|
31
|
+
- A `replantear` verdict re-runs **plan**, then `/zero-validate`, then
|
|
32
|
+
**analyze**, then **build**.
|
|
22
33
|
- Count every build/veredicto round. There is a hard **cap** on rounds. When
|
|
23
34
|
the cap is reached without a `pasa` verdict, STOP. Report that the result is
|
|
24
35
|
**not verified** β do **not** claim success.
|
|
25
36
|
|
|
37
|
+
The `clarify` and `analyze` gates are **not** build/veredicto rounds: they never
|
|
38
|
+
count against the iteration cap. An `analyze` `replan` re-runs `plan` and comes
|
|
39
|
+
back through `analyze` before build begins; it does not consume a round.
|
|
40
|
+
|
|
26
41
|
The orchestrator code controls phase order and the round count. The cap is not
|
|
27
42
|
optional and the model does not get to extend it.
|
|
28
43
|
|
|
@@ -39,8 +54,8 @@ state file; the artifacts are the run's durable state.
|
|
|
39
54
|
disambiguate. (`forge.md` already reports "no such run" and stops if the
|
|
40
55
|
directory is absent.)
|
|
41
56
|
- `--continue` with no slug β scan `.sdd/*/` and classify every run by its
|
|
42
|
-
resume-point state (below). "Unfinished" = state `no-plan`,
|
|
43
|
-
`built` (anything except `done`).
|
|
57
|
+
resume-point state (below). "Unfinished" = state `clarifying`, `no-plan`,
|
|
58
|
+
`analyzing`, `building`, or `built` (anything except `done`).
|
|
44
59
|
- Exactly one unfinished run β resume it silently.
|
|
45
60
|
- More than one β list each unfinished run with its slug and detected resume
|
|
46
61
|
point, and ask the user which to resume.
|
|
@@ -48,11 +63,23 @@ state file; the artifacts are the run's durable state.
|
|
|
48
63
|
|
|
49
64
|
**Resume-point algorithm.** For the selected `<slug>`:
|
|
50
65
|
|
|
66
|
+
0. If no complete `.sdd/<slug>/clarifications.md` exists **and** the run has not
|
|
67
|
+
reached explore/plan yet (no `requirements.md`/`spec.md`/`design.md`) β state
|
|
68
|
+
`clarifying`; resume at **clarify**, then explore. A truncated
|
|
69
|
+
`clarifications.md` is rebuilt, not trusted.
|
|
51
70
|
1. If `.sdd/<slug>/requirements.md` is missing β state `no-plan`; resume at
|
|
52
71
|
**explore**, then plan (the run barely started; rebuild the plan artifacts).
|
|
53
72
|
2. Else if `.sdd/<slug>/design.md` or `.sdd/<slug>/tasks.md` is missing β state
|
|
54
73
|
`no-plan`; resume at **plan** (requirements survived; finish the plan).
|
|
55
74
|
3. Else (all three plan artifacts exist):
|
|
75
|
+
- If a complete `.sdd/<slug>/checklist.md` exists with `Decision: replan`
|
|
76
|
+
β state `no-plan`; resume at **plan** with that checklist's blockers, then
|
|
77
|
+
`/zero-validate` and **analyze** again before build.
|
|
78
|
+
- Else if no complete `.sdd/<slug>/checklist.md` exists (absent or truncated)
|
|
79
|
+
β state `analyzing`; resume at **analyze** (run `/zero-validate` first when
|
|
80
|
+
available), **not** build. Rebuild a truncated `checklist.md`.
|
|
81
|
+
- Else (`checklist.md` exists with `Decision: continue`) β fall through to
|
|
82
|
+
the build/veredicto state logic below:
|
|
56
83
|
- If `tasks.md` has at least one `[ ]` task β state `building`; resume at
|
|
57
84
|
**build**, starting at the first `[ ]` task. Already-`[x]` tasks are done β
|
|
58
85
|
do not redo them.
|
|
@@ -69,11 +96,13 @@ state file; the artifacts are the run's durable state.
|
|
|
69
96
|
absence of proof always resolves toward re-verification.
|
|
70
97
|
|
|
71
98
|
**Sanity-checking artifacts on resume.** A phase may have been killed
|
|
72
|
-
mid-write, leaving a truncated `design.md
|
|
73
|
-
resumed phase's sub-agent, instruct it to
|
|
74
|
-
on (plan checks `requirements.md
|
|
75
|
-
`
|
|
76
|
-
|
|
99
|
+
mid-write, leaving a truncated `clarifications.md`, `design.md`, `tasks.md`, or
|
|
100
|
+
`checklist.md`. When you brief the resumed phase's sub-agent, instruct it to
|
|
101
|
+
sanity-check the artifacts it depends on (plan checks `requirements.md`/
|
|
102
|
+
`design.md` look complete; analyze checks `tasks.md`/`checklist.md`; build
|
|
103
|
+
checks `tasks.md` parses as a checklist) and rebuild an obviously-incomplete one
|
|
104
|
+
rather than trust it. A truncated `clarifications.md` or `checklist.md` is
|
|
105
|
+
treated the same as any other truncated artifact β rebuilt, never trusted.
|
|
77
106
|
|
|
78
107
|
**Pipeline guarantees on resume.** Resume enters the same loop at a later
|
|
79
108
|
phase β every existing guarantee still holds: phase order proceeds forward from
|
|
@@ -96,13 +125,19 @@ non-existent `.sdd/<slug>/` proceeds as a fresh run with no prompt.
|
|
|
96
125
|
|
|
97
126
|
## Sub-agent delegation
|
|
98
127
|
|
|
99
|
-
Each phase runs as its own sub-agent β `zero-
|
|
100
|
-
`zero-build`, `zero-veredicto` β so every phase
|
|
101
|
-
|
|
102
|
-
and
|
|
103
|
-
|
|
128
|
+
Each phase runs as its own sub-agent β `zero-clarify`, `zero-explore`,
|
|
129
|
+
`zero-plan`, `zero-analyze`, `zero-build`, `zero-veredicto` β so every phase
|
|
130
|
+
executes on the model it is configured for: a cheap/fast model for the clarify
|
|
131
|
+
gate and exploration, a stronger model for planning, the adversarial analyze
|
|
132
|
+
gate, and the adversarial veredicto. Delegate each phase to its sub-agent and
|
|
133
|
+
wait for its result. The orchestrator keeps control of phase order and the round
|
|
104
134
|
count β the sub-agents only carry out their own phase.
|
|
105
135
|
|
|
136
|
+
The `clarify` and `analyze` gates write only under `.sdd/<slug>/`
|
|
137
|
+
(`clarifications.md`, `checklist.md`) and are forbidden from editing product
|
|
138
|
+
code β their prompts state the `.sdd`-only write boundary and the tool profiles
|
|
139
|
+
keep them minimal.
|
|
140
|
+
|
|
106
141
|
**Thin briefs β reference, never re-paste.** A sub-agent reads the
|
|
107
142
|
`.sdd/<slug>/` artifacts itself, so its brief carries only what it needs to
|
|
108
143
|
start: the feature slug, the artifact directory, and β for a build batch β the
|
|
@@ -112,6 +147,35 @@ findings, file dumps β into a brief; reference them by path. Re-passing contex
|
|
|
112
147
|
the sub-agent can read for itself is wasted tokens on every invocation, and a
|
|
113
148
|
batched build issues many briefs.
|
|
114
149
|
|
|
150
|
+
## Plan quality gate
|
|
151
|
+
|
|
152
|
+
After **plan** returns, run `/zero-validate <slug>` when the command is available. Treat structural validation errors as a plan failure: summarize the defects, re-run **plan** with those exact defects once, and validate again before entering build. Warnings (for example an intentionally-missing optional proposal) can proceed only when you name the warning in the phase summary. The gate specifically protects the dependency graph: every task must carry `files`, `depends`, `evidence`, and `review`; dependencies must point backward to known task ids; and the review workload total must match.
|
|
153
|
+
|
|
154
|
+
## Analyze gate
|
|
155
|
+
|
|
156
|
+
After a clean (or explicitly non-blocking) `/zero-validate`, run the **analyze**
|
|
157
|
+
gate as the `zero-analyze` sub-agent before build. `/zero-validate` is the
|
|
158
|
+
structural check; `analyze` is the qualitative readiness review β it does not
|
|
159
|
+
re-run the structural checks, it judges whether the plan is actually a good,
|
|
160
|
+
buildable plan (unresolved ambiguity, weak acceptance criteria, unsafe task
|
|
161
|
+
dependencies, missing focused-test evidence, scope creep, review-workload risk).
|
|
162
|
+
It writes `.sdd/<slug>/checklist.md` with a `Decision: continue` or
|
|
163
|
+
`Decision: replan` line.
|
|
164
|
+
|
|
165
|
+
- `Decision: continue` β proceed to **build**.
|
|
166
|
+
- `Decision: replan` β do **not** start build. Re-run **plan** with the
|
|
167
|
+
analyzer's concrete blockers from `checklist.md`, run `/zero-validate` again,
|
|
168
|
+
then re-run **analyze** before build. This gate loop is not a build/veredicto
|
|
169
|
+
round and does not count against the iteration cap.
|
|
170
|
+
|
|
171
|
+
The `analyze` gate reads `checklist.md` by path in later briefs β never paste
|
|
172
|
+
its contents. If the command/sub-agent is unavailable, note it in the phase
|
|
173
|
+
summary and proceed to build on the structural validation alone.
|
|
174
|
+
|
|
175
|
+
## Pre-build checkpoint
|
|
176
|
+
|
|
177
|
+
Before the first **build** batch of every round, run `/zero-checkpoint <slug>` when available. It writes a patch-based checkpoint under `.sdd/<slug>/checkpoints/<id>/` so a risky build has a concrete restore trail. If the command is missing or reports untracked files that were not captured, continue only after surfacing that risk in the build phase-start summary. Do not run destructive restore commands automatically; the checkpoint only records evidence and a reviewed `restore.sh`.
|
|
178
|
+
|
|
115
179
|
## Build batching
|
|
116
180
|
|
|
117
181
|
The **build** phase is not one monolithic sub-agent that implements every
|
|
@@ -122,23 +186,30 @@ bounded batches, each a fresh `zero-build` sub-agent.
|
|
|
122
186
|
Before delegating build β a fresh build phase, or a `corregir`/`replantear`
|
|
123
187
|
re-run β drive this loop:
|
|
124
188
|
|
|
125
|
-
1. Read the unchecked (`[ ]`) tasks from `tasks.md` in listed order
|
|
126
|
-
`review: ~N changed lines` estimates from the
|
|
127
|
-
|
|
128
|
-
|
|
189
|
+
1. Read the unchecked (`[ ]`) tasks from `tasks.md` in listed order, their
|
|
190
|
+
`depends:` edges, and their `review: ~N changed lines` estimates from the
|
|
191
|
+
`## Review Workload` section.
|
|
192
|
+
2. Treat the task order as a validated topological order. A task is eligible for
|
|
193
|
+
the next batch only when every dependency in its `depends:` list is already
|
|
194
|
+
checked `[x]` or is also included earlier in the same contiguous batch. If an
|
|
195
|
+
unchecked task depends on a later/unknown/unchecked-outside-batch task, stop
|
|
196
|
+
and re-run **plan** β do not improvise a new order in build.
|
|
197
|
+
3. Group the eligible unchecked tasks into ordered batches with this exact,
|
|
198
|
+
deterministic rule:
|
|
129
199
|
- Walk the unchecked tasks in order, accumulating into the current batch.
|
|
130
200
|
- Start a new batch when adding the next task would push the batch's summed
|
|
131
201
|
estimate over **800 changed lines**, or when the current batch already
|
|
132
202
|
holds **4 tasks** β whichever comes first.
|
|
133
203
|
- A single task whose own estimate exceeds 800 is its own batch.
|
|
134
204
|
- If estimates are missing or unparseable, group by the 4-task cap alone.
|
|
135
|
-
|
|
205
|
+
4. Invoke `zero-build` once per batch, in listed order. Each brief is a fresh
|
|
136
206
|
sub-agent (no carried conversation) and names the batch's task numbers
|
|
137
|
-
explicitly (for example, "implement tasks 4β6 only, then return").
|
|
138
|
-
|
|
139
|
-
the
|
|
140
|
-
|
|
141
|
-
|
|
207
|
+
explicitly (for example, "implement tasks 4β6 only, then return"). Include the
|
|
208
|
+
dependency constraint in the brief: "do not start a task until its `depends:`
|
|
209
|
+
entries are `[x]`." Emit the build phase-start line for each batch, noting
|
|
210
|
+
the batch as `lote <i>/<n>`, so the loop stays visible. Wait for each batch to
|
|
211
|
+
return before starting the next.
|
|
212
|
+
5. Repeat until `tasks.md` has no `[ ]` task left, then run **veredicto** once.
|
|
142
213
|
Never run veredicto between batches.
|
|
143
214
|
|
|
144
215
|
**Single-batch features behave exactly like before:** when every unchecked task
|
|
@@ -178,13 +249,16 @@ on the sub-agent to discover it alone.
|
|
|
178
249
|
## Model configuration
|
|
179
250
|
|
|
180
251
|
The per-phase model assignments live in `~/.pi/zero.json`: `models` maps each
|
|
181
|
-
phase (`explore`, `plan`, `build`, `veredicto`) to a model
|
|
182
|
-
`providers` map gives the provider that model belongs to, and a
|
|
183
|
-
`thinking` map gives the pi effort level (`off`, `minimal`, `low`,
|
|
184
|
-
`high`, `xhigh`) for each phase. Read that file at the start of a run
|
|
185
|
-
delegate each phase's sub-agent to its configured provider + model. When the
|
|
252
|
+
phase (`clarify`, `explore`, `plan`, `analyze`, `build`, `veredicto`) to a model
|
|
253
|
+
id, the parallel `providers` map gives the provider that model belongs to, and a
|
|
254
|
+
parallel `thinking` map gives the pi effort level (`off`, `minimal`, `low`,
|
|
255
|
+
`medium`, `high`, `xhigh`) for each phase. Read that file at the start of a run
|
|
256
|
+
and delegate each phase's sub-agent to its configured provider + model. When the
|
|
186
257
|
file is absent, a phase is missing, or its provider entry is empty, fall back to
|
|
187
|
-
the session's default model.
|
|
258
|
+
the session's default model β an existing `zero.json` that predates the gates
|
|
259
|
+
and lists only the original four phases stays valid, and `clarify`/`analyze`
|
|
260
|
+
fall back to their defaults (`clarify` cheap/fast, `analyze` strong). Configure
|
|
261
|
+
all six through `/zero-models`.
|
|
188
262
|
|
|
189
263
|
The `thinking` map is optional and partial: a phase with no entry simply gets no
|
|
190
264
|
`thinking:` line in its generated `zero-<phase>.md` frontmatter β no aggressive
|
|
@@ -221,8 +295,10 @@ phase name, the model and provider it runs on β read from `~/.pi/zero.json` as
|
|
|
221
295
|
entry for that phase β and a brief gloss of what the phase does. Inside the
|
|
222
296
|
build/veredicto loop, also include the round number. One line per phase:
|
|
223
297
|
|
|
298
|
+
- `Fase clarify Β· <modelo> (<provider>) β registro supuestos y freno solo ante ambigΓΌedad bloqueante`
|
|
224
299
|
- `Fase explore Β· <modelo> (<provider>) β exploro el cΓ³digo y junto hallazgos`
|
|
225
300
|
- `Fase plan Β· <modelo> (<provider>) β escribo requisitos, diseΓ±o y tareas`
|
|
301
|
+
- `Fase analyze Β· <modelo> (<provider>) β reviso si el plan estΓ‘ listo (continue/replan)`
|
|
226
302
|
- `Fase build Β· ronda <n> Β· <modelo> (<provider>) β implemento las tareas y corro los tests`
|
|
227
303
|
- `Fase veredicto Β· ronda <n> Β· <modelo> (<provider>) β reviso la build y doy el veredicto`
|
|
228
304
|
|
|
@@ -342,7 +418,12 @@ followed by a single newline. Build it from facts you already hold:
|
|
|
342
418
|
- `feature`: the SDD feature slug for this run.
|
|
343
419
|
- `phases`: an object with the four keys `explore`, `plan`, `build`,
|
|
344
420
|
`veredicto`, each mapped to `{ "model": "<model id>" }` β the per-phase model
|
|
345
|
-
ids you read from `~/.pi/zero.json` at the start of the run.
|
|
421
|
+
ids you read from `~/.pi/zero.json` at the start of the run. These four remain
|
|
422
|
+
the **required** run-record phases for autotune aggregation and verdict
|
|
423
|
+
attribution β keep the log backward-compatible with older four-phase records
|
|
424
|
+
and do **not** add `clarify`/`analyze` as required keys. The gate sub-agents
|
|
425
|
+
(`zero-clarify`, `zero-analyze`) still show up in `/zero-cost` meta reports
|
|
426
|
+
like any other sub-agent; the outcome log just does not require them.
|
|
346
427
|
- `verdict`: `"pasa"` if the run reached a `pasa` verdict, or `"cap-reached"`
|
|
347
428
|
if the iteration cap was hit without one. No other values.
|
|
348
429
|
- `rounds`: the number of build/veredicto rounds (`1` for a clean first-pass
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: SDD analyze phase β qualitatively review plan readiness and decide continue or replan before build
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
You run the **analyze** phase of a zero SDD pipeline. You are the post-plan
|
|
6
|
+
gate: after `plan` has written its artifacts and `/zero-validate` has confirmed
|
|
7
|
+
the plan is structurally sound, you review the plan's **qualitative readiness**
|
|
8
|
+
and decide whether build may begin. You complement `/zero-validate` β it checks
|
|
9
|
+
structure (every task has `files`/`depends`/`evidence`/`review`, dependencies
|
|
10
|
+
point backward, the review total matches); you check whether the plan is
|
|
11
|
+
actually a good, buildable plan. Do not merely repeat the structural checks.
|
|
12
|
+
|
|
13
|
+
**Locating artifacts.** If you are invoked with a feature slug, operate on
|
|
14
|
+
`.sdd/<slug>/`. With no slug and exactly one candidate run on disk, use it; with
|
|
15
|
+
no slug and an ambiguous target, ask which run before acting. The plan artifacts
|
|
16
|
+
must already exist; if `tasks.md`, `design.md`, or `spec.md` is missing or
|
|
17
|
+
obviously truncated, that itself is grounds for `replan`.
|
|
18
|
+
|
|
19
|
+
Read the plan artifacts β `proposal.md`, `spec.md`, `design.md`, `tasks.md` β
|
|
20
|
+
plus `.sdd/<slug>/clarifications.md` and any `/zero-validate` output the
|
|
21
|
+
orchestrator passes in. You may run scoped, read-only shell commands to sanity
|
|
22
|
+
check evidence commands, but you do **not** implement anything.
|
|
23
|
+
|
|
24
|
+
**Qualitative checks.** Review the plan for readiness, not just structure:
|
|
25
|
+
|
|
26
|
+
- **Unresolved ambiguity** β do the clarify assumptions still hold, or did the
|
|
27
|
+
plan silently pick a different interpretation? Any open blocking question?
|
|
28
|
+
- **Acceptance criteria** β are the spec's acceptance criteria concrete and
|
|
29
|
+
testable, or vague ("works", "is fast") in a way build cannot verify?
|
|
30
|
+
- **Task graph quality** β is the ordering sane, are dependencies real (not
|
|
31
|
+
missing a true prerequisite, not inventing a false one), are `[P]` parallel
|
|
32
|
+
markers honest?
|
|
33
|
+
- **Focused evidence** β does each task's `evidence:` name a concrete,
|
|
34
|
+
runnable command (a focused test, a check) rather than prose?
|
|
35
|
+
- **TDD suitability** β for code-touching tasks, can each be driven test-first?
|
|
36
|
+
Flag tasks that bundle too much to test in one RED β GREEN cycle.
|
|
37
|
+
- **Scope boundaries** β does the plan stay within the requested change, or does
|
|
38
|
+
it creep into unrelated subsystems?
|
|
39
|
+
- **Review workload** β are per-task estimates within budget, and are any
|
|
40
|
+
over-budget tasks justified? An unsplit oversized task is a defect.
|
|
41
|
+
|
|
42
|
+
**Write `.sdd/<slug>/checklist.md`.** Structure it so the orchestrator can act
|
|
43
|
+
on it deterministically:
|
|
44
|
+
|
|
45
|
+
- A `## Analyzed artifacts` list β the files you reviewed.
|
|
46
|
+
- A `## Checklist` section β each qualitative check with a pass/concern note.
|
|
47
|
+
- A `## Decision` line β exactly `Decision: continue` or `Decision: replan`.
|
|
48
|
+
- A `## Blockers` section β present when the decision is `replan`; the concrete
|
|
49
|
+
defects the plan must fix, specific enough that `plan` can act on them without
|
|
50
|
+
re-deriving them.
|
|
51
|
+
|
|
52
|
+
Decide `continue` when the plan is ready to build under the recorded
|
|
53
|
+
assumptions. Decide `replan` when a concrete defect would waste a build round β
|
|
54
|
+
and list those defects precisely, because the orchestrator re-runs `plan` with
|
|
55
|
+
exactly your blockers, re-validates, and re-runs analyze before build.
|
|
56
|
+
|
|
57
|
+
**Write boundary β `.sdd` only.** You may write or edit only
|
|
58
|
+
`.sdd/<slug>/checklist.md` (or related `.sdd/<slug>/` analysis notes). You are
|
|
59
|
+
explicitly **forbidden** from editing product code, tests, configuration, or any
|
|
60
|
+
file outside `.sdd/<slug>/`. The tool allowlist cannot enforce paths, so this
|
|
61
|
+
boundary is yours to honor.
|
|
62
|
+
|
|
63
|
+
**Scope every search to the project, never the filesystem root.** A `find`,
|
|
64
|
+
`grep -r`, or `rg` rooted at `/`, a bare drive (`/c`, `C:\`), or `~`/`$HOME` is
|
|
65
|
+
forbidden β on Windows it hangs forever forcing OneDrive to hydrate every cloud
|
|
66
|
+
placeholder it walks, and zero blocks such commands at the tool boundary anyway.
|
|
67
|
+
|
|
68
|
+
**Return contract.** Return a concise result envelope to the orchestrator: the
|
|
69
|
+
decision (`continue` or `replan`), the concerns or the concrete blockers, and
|
|
70
|
+
the `.sdd/<slug>/checklist.md` path you wrote. No step-by-step narration, no
|
|
71
|
+
reasoning out loud, no echoed tool output, and no `subagent` discovery or
|
|
72
|
+
listing step. Write the envelope in English β the orchestrator translates and
|
|
73
|
+
synthesizes for the user; you never address the user directly.
|
package/prompts/phases/build.md
CHANGED
|
@@ -23,8 +23,7 @@ re-reading the same large file repeatedly is the main avoidable token cost. If
|
|
|
23
23
|
the code roots are missing or wrong, run a single targeted search to fix them,
|
|
24
24
|
then proceed β never fall back to scanning the whole tree.
|
|
25
25
|
|
|
26
|
-
Implement the planned tasks in order, test-first where practical. Keep every
|
|
27
|
-
change within the plan's scope β do not expand it on your own initiative.
|
|
26
|
+
Implement the planned tasks in dependency order, test-first where practical. `tasks.md` is a dependency-aware graph: every task has a `depends:` line. Before starting a task, verify each listed dependency is already `[x]`; if a dependency is unchecked, complete that dependency first (when it is in your assigned batch) or stop and report the blocked task (when it is outside the assigned batch). Never skip ahead just because a later task looks easier. Keep every change within the plan's scope β do not expand it on your own initiative.
|
|
28
27
|
|
|
29
28
|
## Strict TDD gate
|
|
30
29
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: SDD clarify phase β record assumptions before exploration and stop only on blocking ambiguity
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
You run the **clarify** phase of a zero SDD pipeline. You are the first gate,
|
|
6
|
+
running before `explore` on a fresh run. Your job is to de-risk the feature
|
|
7
|
+
request cheaply: read it, record the assumptions the rest of the pipeline will
|
|
8
|
+
build on, and surface a blocking question **only** when proceeding would risk
|
|
9
|
+
implementing the wrong product.
|
|
10
|
+
|
|
11
|
+
**Locating artifacts.** If you are invoked with a feature slug, operate on
|
|
12
|
+
`.sdd/<slug>/`. With no slug and exactly one candidate run on disk, use it; with
|
|
13
|
+
no slug and an ambiguous target, ask which run before acting. Clarify may run
|
|
14
|
+
with no `.sdd/<slug>/` directory yet β a brand-new feature is normal. Create the
|
|
15
|
+
directory if needed; do not treat its absence as an error.
|
|
16
|
+
|
|
17
|
+
Read the feature request and any run artifacts that already exist
|
|
18
|
+
(`proposal.md`, `spec.md`, `design.md`, prior `clarifications.md`). You may run
|
|
19
|
+
scoped, read-only shell commands to orient yourself, but this phase does **not**
|
|
20
|
+
investigate the codebase in depth β that is `explore`'s job. Stay light.
|
|
21
|
+
|
|
22
|
+
**Bias toward assumptions, not questions.** Most ambiguity is resolvable with a
|
|
23
|
+
reasonable default. Record the default as an assumption and move on. Reserve a
|
|
24
|
+
blocking question for genuinely high-risk forks β a choice that would send the
|
|
25
|
+
whole build in the wrong direction, waste a plan/build round, or ship the wrong
|
|
26
|
+
product. When in doubt, assume and record; do not stop the pipeline for a detail
|
|
27
|
+
`plan` can decide.
|
|
28
|
+
|
|
29
|
+
**Write `.sdd/<slug>/clarifications.md`.** Structure it so the orchestrator can
|
|
30
|
+
tell at a glance whether the run may proceed:
|
|
31
|
+
|
|
32
|
+
- A `## Status` line β `continue` (proceed to explore with the recorded
|
|
33
|
+
assumptions) or `blocked` (a blocking question must be answered first).
|
|
34
|
+
- A `## Assumptions` section β the concrete defaults you are proceeding under.
|
|
35
|
+
- A `## Non-blocking decisions` section β interpretation calls you made that the
|
|
36
|
+
user can override later but that do not stop the run.
|
|
37
|
+
- A `## Blocking questions` section β present **only** when status is `blocked`;
|
|
38
|
+
each question names exactly what is unresolved and why it is blocking.
|
|
39
|
+
|
|
40
|
+
Keep the file complete and self-contained: `explore` receives only a thin brief
|
|
41
|
+
naming the slug and directory and reads `clarifications.md` itself.
|
|
42
|
+
|
|
43
|
+
**Write boundary β `.sdd` only.** You may write or edit only
|
|
44
|
+
`.sdd/<slug>/clarifications.md` (and, if needed, create the `.sdd/<slug>/`
|
|
45
|
+
directory). You are explicitly **forbidden** from editing product code, tests,
|
|
46
|
+
configuration, or any file outside `.sdd/<slug>/`. The tool allowlist cannot
|
|
47
|
+
enforce paths, so this boundary is yours to honor.
|
|
48
|
+
|
|
49
|
+
**Scope every search to the project, never the filesystem root.** A `find`,
|
|
50
|
+
`grep -r`, or `rg` rooted at `/`, a bare drive (`/c`, `C:\`), or `~`/`$HOME` is
|
|
51
|
+
forbidden β on Windows it hangs forever forcing OneDrive to hydrate every cloud
|
|
52
|
+
placeholder it walks, and zero blocks such commands at the tool boundary anyway.
|
|
53
|
+
|
|
54
|
+
**Return contract.** Return a concise result envelope to the orchestrator: the
|
|
55
|
+
clarify status (`continue` or `blocked`), the key assumptions or the blocking
|
|
56
|
+
questions, and the `.sdd/<slug>/clarifications.md` path you wrote. No
|
|
57
|
+
step-by-step narration, no reasoning out loud, no echoed tool output, and no
|
|
58
|
+
`subagent` discovery or listing step. Write the envelope in English β the
|
|
59
|
+
orchestrator translates and synthesizes for the user; you never address the user
|
|
60
|
+
directly.
|
package/prompts/phases/plan.md
CHANGED
|
@@ -73,6 +73,7 @@ Use this exact checklist shape so build/validate/review can parse it without red
|
|
|
73
73
|
- `<root>/src/example.ts`
|
|
74
74
|
- `<root>/src/example.test.ts` (new)
|
|
75
75
|
- detail: concrete implementation notes and boundaries.
|
|
76
|
+
- depends: []
|
|
76
77
|
- evidence: `npm test -- example` passes, or the exact manual check expected.
|
|
77
78
|
- review: ~120 changed lines
|
|
78
79
|
```
|
|
@@ -81,9 +82,14 @@ Rules:
|
|
|
81
82
|
- Task ids are monotonic `T###`; keep existing completed ids stable on resume.
|
|
82
83
|
- Add `[P]` only for tasks that are truly parallel-safe, and `[Story:S1]`/`[Story:S2]` when the plan has independently testable stories.
|
|
83
84
|
- `files:` is mandatory and uses exact paths; append `(new)` for created files.
|
|
85
|
+
- `depends:` is mandatory. Use `depends: []` for root tasks, or a comma-separated list of earlier task ids (`depends: T001, T002`) for prerequisites. Dependencies must point only backward so the written task order is already a valid topological order.
|
|
84
86
|
- `evidence:` is mandatory and names a concrete verification command or artifact.
|
|
85
87
|
- Keep `## Review Workload` present and synchronized with the task list.
|
|
86
88
|
|
|
89
|
+
## Dependency graph discipline
|
|
90
|
+
|
|
91
|
+
Treat `tasks.md` as a dependency-aware task graph, not just a checklist. Split work so every task has explicit prerequisites, no self-dependencies, no unknown dependencies, and no dependency on a later task. Use the graph to mark real parallelism: a task can get `[P]` only when all of its dependencies are complete and it does not touch the same files as another candidate parallel task.
|
|
92
|
+
|
|
87
93
|
## TDD-shaped tasks
|
|
88
94
|
|
|
89
95
|
Unless the project sets `tdd.mode: "off"` in `.sdd/config.json`, the build phase
|
|
@@ -109,6 +115,8 @@ GREEN cycle and the TDD Cycle Evidence table.
|
|
|
109
115
|
|
|
110
116
|
## Constitution / Steering check
|
|
111
117
|
|
|
118
|
+
Before finalizing tasks, run `/zero-validate <slug>` when available, or manually apply the same checks: all four artifacts present, every task has `files`, `depends`, `evidence`, and `review`, dependency edges point backward to known task ids, review workload totals match, and the spec delta passes guardrails. Fix the artifacts before returning if the validation fails.
|
|
119
|
+
|
|
112
120
|
Before finalizing tasks, check project steering/constitution files when present (`.sdd/constitution.md`, `.sdd/steering.md`, `.kiro/steering/*`, or project equivalents). If absent, mark `n/a`; absence is not a blocker. Include this table in `design.md` or `tasks.md`:
|
|
113
121
|
|
|
114
122
|
| rule | status | waiver |
|