@gonrocca/zero-pi 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/extensions/zero-models.ts +189 -0
- package/package.json +4 -2
- package/prompts/forge.md +6 -26
package/README.md
CHANGED
|
@@ -46,9 +46,19 @@ Run it with the `/forge <feature>` prompt. The orchestrator drives phase order
|
|
|
46
46
|
and enforces a hard build/veredicto iteration cap. Each phase has its own prompt
|
|
47
47
|
under `prompts/phases/` so it can be delegated to a dedicated sub-agent.
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
### Per-phase models (`/zero-models`)
|
|
50
|
+
|
|
51
|
+
`/zero-models` is a real pi command — a code handler, not an LLM prompt — for
|
|
52
|
+
the SDD models. Run it with no argument to pick a phase and a model
|
|
53
|
+
interactively, or set one directly:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
/zero-models # interactive picker
|
|
57
|
+
/zero-models build=claude-opus-4-7 # set one phase
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
It reads and writes `~/.pi/zero.json`; the orchestrator picks the change up on
|
|
61
|
+
the next `/forge` run.
|
|
52
62
|
|
|
53
63
|
Per-phase model assignments are read from `~/.pi/zero.json`, which the `zero`
|
|
54
64
|
CLI writes when it installs this layer.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// zero-pi — the /zero-models command.
|
|
2
|
+
//
|
|
3
|
+
// A real pi command — a code handler, not an LLM prompt — for reading and
|
|
4
|
+
// changing the per-phase SDD models in `~/.pi/zero.json`. It is deterministic:
|
|
5
|
+
// no model is involved, so it does exactly what you pick, every time.
|
|
6
|
+
//
|
|
7
|
+
// /zero-models interactive — pick a phase, pick a model
|
|
8
|
+
// /zero-models build=claude-opus-4-7 set one phase directly
|
|
9
|
+
//
|
|
10
|
+
// The SDD orchestrator reads `~/.pi/zero.json` at the start of every `/forge`
|
|
11
|
+
// run, so a change takes effect on the next run.
|
|
12
|
+
|
|
13
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
|
|
17
|
+
/** The SDD phases, in pipeline order. */
|
|
18
|
+
export const PHASES = ["explore", "plan", "build", "veredicto"] as const;
|
|
19
|
+
export type Phase = (typeof PHASES)[number];
|
|
20
|
+
|
|
21
|
+
/** The per-phase model map. */
|
|
22
|
+
export type PhaseModels = Record<Phase, string>;
|
|
23
|
+
|
|
24
|
+
/** Fallback models when `~/.pi/zero.json` has none — cheap to explore, strong
|
|
25
|
+
* to plan and review. */
|
|
26
|
+
const DEFAULT_MODELS: PhaseModels = {
|
|
27
|
+
explore: "claude-haiku-4-5",
|
|
28
|
+
plan: "claude-opus-4-7",
|
|
29
|
+
build: "claude-sonnet-4-6",
|
|
30
|
+
veredicto: "claude-opus-4-7",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Models offered in the interactive picker — the Claude lineup pi-claude-cli
|
|
34
|
+
* exposes. Any other model can still be typed via the custom option. */
|
|
35
|
+
const MODEL_CHOICES = [
|
|
36
|
+
"claude-opus-4-7",
|
|
37
|
+
"claude-opus-4-6",
|
|
38
|
+
"claude-sonnet-4-6",
|
|
39
|
+
"claude-haiku-4-5",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/** Absolute path of pi's `zero.json` marker. */
|
|
43
|
+
function zeroJsonPath(): string {
|
|
44
|
+
return join(homedir(), ".pi", "zero.json");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Whether a string names an SDD phase. */
|
|
48
|
+
export function isPhase(value: string): value is Phase {
|
|
49
|
+
return (PHASES as readonly string[]).includes(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Read `~/.pi/zero.json`, returning an empty object when absent or invalid. */
|
|
53
|
+
function readZeroJson(): Record<string, unknown> {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(readFileSync(zeroJsonPath(), "utf8")) as Record<string, unknown>;
|
|
56
|
+
} catch {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Extract the per-phase models from a zero.json object, filling any gap with
|
|
63
|
+
* the default so the picker always has a value to show.
|
|
64
|
+
*/
|
|
65
|
+
export function readModels(data: Record<string, unknown>): PhaseModels {
|
|
66
|
+
const raw = (data.models ?? {}) as Record<string, unknown>;
|
|
67
|
+
const models: PhaseModels = { ...DEFAULT_MODELS };
|
|
68
|
+
for (const phase of PHASES) {
|
|
69
|
+
if (typeof raw[phase] === "string") models[phase] = raw[phase] as string;
|
|
70
|
+
}
|
|
71
|
+
return models;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Parse a `<phase>=<model>` (or `<phase> <model>`) assignment. */
|
|
75
|
+
export function parseAssignment(arg: string): { phase: Phase; model: string } | null {
|
|
76
|
+
const match = arg.trim().match(/^(\w+)\s*[=\s]\s*(.+)$/);
|
|
77
|
+
if (!match) return null;
|
|
78
|
+
const phase = match[1].toLowerCase();
|
|
79
|
+
const model = match[2].trim();
|
|
80
|
+
if (!isPhase(phase) || model === "") return null;
|
|
81
|
+
return { phase, model };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Render the per-phase model map as an aligned block. */
|
|
85
|
+
export function formatModels(models: PhaseModels): string {
|
|
86
|
+
return PHASES.map((phase) => ` ${phase.padEnd(10)} ${models[phase]}`).join("\n");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Write the models back into `~/.pi/zero.json`, preserving every other key. */
|
|
90
|
+
function writeModels(data: Record<string, unknown>, models: PhaseModels): void {
|
|
91
|
+
writeFileSync(zeroJsonPath(), `${JSON.stringify({ ...data, models }, null, 2)}\n`, "utf8");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The slice of pi's extension API this command uses. */
|
|
95
|
+
interface PiUI {
|
|
96
|
+
select(prompt: string, options: string[]): Promise<string | undefined>;
|
|
97
|
+
input(prompt: string, placeholder?: string): Promise<string | undefined>;
|
|
98
|
+
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
99
|
+
}
|
|
100
|
+
interface PiCommandContext {
|
|
101
|
+
ui: PiUI;
|
|
102
|
+
}
|
|
103
|
+
interface PiExtensionAPI {
|
|
104
|
+
registerCommand(
|
|
105
|
+
name: string,
|
|
106
|
+
options: {
|
|
107
|
+
description?: string;
|
|
108
|
+
handler: (args: string, ctx: PiCommandContext) => Promise<void> | void;
|
|
109
|
+
},
|
|
110
|
+
): void;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const SAVE_AND_EXIT = "— guardar y salir —";
|
|
114
|
+
const CUSTOM_MODEL = "— otro modelo (escribir) —";
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The pi extension entry point — registers the `/zero-models` command.
|
|
118
|
+
*/
|
|
119
|
+
export default function register(pi?: PiExtensionAPI): void {
|
|
120
|
+
if (!pi || typeof pi.registerCommand !== "function") return;
|
|
121
|
+
|
|
122
|
+
pi.registerCommand("zero-models", {
|
|
123
|
+
description: "Show or set the per-phase SDD models — /zero-models [<phase>=<model>]",
|
|
124
|
+
handler: async (args: string, ctx: PiCommandContext): Promise<void> => {
|
|
125
|
+
try {
|
|
126
|
+
const data = readZeroJson();
|
|
127
|
+
const models = readModels(data);
|
|
128
|
+
|
|
129
|
+
// Direct form: /zero-models build=claude-opus-4-7
|
|
130
|
+
const arg = args.trim();
|
|
131
|
+
if (arg) {
|
|
132
|
+
const assignment = parseAssignment(arg);
|
|
133
|
+
if (!assignment) {
|
|
134
|
+
ctx.ui.notify(
|
|
135
|
+
"uso: /zero-models —o— /zero-models <fase>=<modelo> " +
|
|
136
|
+
"(fase: explore | plan | build | veredicto)",
|
|
137
|
+
"warning",
|
|
138
|
+
);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
models[assignment.phase] = assignment.model;
|
|
142
|
+
writeModels(data, models);
|
|
143
|
+
ctx.ui.notify(`zero models: ${assignment.phase} → ${assignment.model}`, "info");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Interactive form: pick a phase, pick a model, repeat until saved.
|
|
148
|
+
let changed = false;
|
|
149
|
+
for (;;) {
|
|
150
|
+
const phasePick = await ctx.ui.select(
|
|
151
|
+
"zero · modelos SDD — elegí una fase",
|
|
152
|
+
[...PHASES.map((p) => `${p} → ${models[p]}`), SAVE_AND_EXIT],
|
|
153
|
+
);
|
|
154
|
+
if (!phasePick || phasePick === SAVE_AND_EXIT) break;
|
|
155
|
+
|
|
156
|
+
const phase = phasePick.split(/\s/)[0];
|
|
157
|
+
if (!isPhase(phase)) break;
|
|
158
|
+
|
|
159
|
+
const modelPick = await ctx.ui.select(`Modelo para «${phase}»`, [
|
|
160
|
+
...MODEL_CHOICES,
|
|
161
|
+
CUSTOM_MODEL,
|
|
162
|
+
]);
|
|
163
|
+
if (!modelPick) continue;
|
|
164
|
+
|
|
165
|
+
let model = modelPick;
|
|
166
|
+
if (modelPick === CUSTOM_MODEL) {
|
|
167
|
+
const typed = await ctx.ui.input(`Modelo para «${phase}»`, models[phase]);
|
|
168
|
+
if (!typed || typed.trim() === "") continue;
|
|
169
|
+
model = typed.trim();
|
|
170
|
+
}
|
|
171
|
+
models[phase] = model;
|
|
172
|
+
changed = true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (changed) {
|
|
176
|
+
writeModels(data, models);
|
|
177
|
+
ctx.ui.notify(`zero · modelos SDD guardados:\n${formatModels(models)}`, "info");
|
|
178
|
+
} else {
|
|
179
|
+
ctx.ui.notify(`zero · modelos SDD (sin cambios):\n${formatModels(models)}`, "info");
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
ctx.ui.notify(
|
|
183
|
+
`zero-models: ${err instanceof Error ? err.message : String(err)}`,
|
|
184
|
+
"error",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gonrocca/zero-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "zero-pi — an installable layer for pi (pi.dev): the zero spec-driven development workflow, skill auto-learning, and an animated ZERO startup banner. Adds capability to pi without modifying pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -20,13 +20,15 @@
|
|
|
20
20
|
"./skills"
|
|
21
21
|
],
|
|
22
22
|
"extensions": [
|
|
23
|
-
"./extensions/startup-banner.ts"
|
|
23
|
+
"./extensions/startup-banner.ts",
|
|
24
|
+
"./extensions/zero-models.ts"
|
|
24
25
|
]
|
|
25
26
|
},
|
|
26
27
|
"files": [
|
|
27
28
|
"prompts",
|
|
28
29
|
"skills",
|
|
29
30
|
"extensions/startup-banner.ts",
|
|
31
|
+
"extensions/zero-models.ts",
|
|
30
32
|
"README.md",
|
|
31
33
|
"LICENSE"
|
|
32
34
|
],
|
package/prompts/forge.md
CHANGED
|
@@ -1,31 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Run
|
|
2
|
+
description: Run an automatic spec-driven development pipeline for a feature request
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## `/forge models` — configure the SDD models
|
|
8
|
-
|
|
9
|
-
When the argument is exactly `models`, do **not** run the pipeline. Configure
|
|
10
|
-
the per-phase models instead:
|
|
11
|
-
|
|
12
|
-
1. Read `~/.pi/zero.json`. Show the user the current `models` mapping — the
|
|
13
|
-
model assigned to each phase: `explore`, `plan`, `build`, `veredicto`.
|
|
14
|
-
2. Ask which phases they want to change and to what. Offer the models the
|
|
15
|
-
active provider exposes — run `pi --list-models` if you need the list.
|
|
16
|
-
Guidance: a cheap, fast model suits `explore`; a strong model suits `plan`
|
|
17
|
-
and the adversarial `veredicto`; `build` sits in between.
|
|
18
|
-
3. Write the chosen models back into `~/.pi/zero.json`, replacing only the
|
|
19
|
-
`models` block and preserving every other key (`version`, `installedAt`).
|
|
20
|
-
Validate each model name against `pi --list-models` before writing.
|
|
21
|
-
4. Confirm the new mapping back to the user.
|
|
22
|
-
|
|
23
|
-
This changes the models for every later `/forge` run — the orchestrator reads
|
|
24
|
-
`~/.pi/zero.json` at the start of each run.
|
|
25
|
-
|
|
26
|
-
## `/forge <feature request>` — run the SDD pipeline
|
|
27
|
-
|
|
28
|
-
For any other argument, run the zero SDD pipeline for that feature request.
|
|
5
|
+
Run the zero SDD pipeline for the feature request in the arguments.
|
|
29
6
|
|
|
30
7
|
Follow the zero SDD orchestrator instructions: drive the run through the
|
|
31
8
|
explore → plan → build → veredicto phases, honour the build/veredicto iteration
|
|
@@ -40,4 +17,7 @@ In interactive mode, pause after each phase with a summary and ask before
|
|
|
40
17
|
continuing. Never report success unless the veredicto phase returned a `pasa`
|
|
41
18
|
verdict; if the cap is reached first, report that the result is not verified.
|
|
42
19
|
|
|
43
|
-
|
|
20
|
+
To change the per-phase models, the user runs the `/zero-models` command — do
|
|
21
|
+
not handle model configuration here.
|
|
22
|
+
|
|
23
|
+
Feature request: $ARGUMENTS
|