@gonrocca/zero-pi 0.1.69 → 0.1.71

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 CHANGED
@@ -123,6 +123,8 @@ into `/forge` for you.
123
123
  | **SDD routing skill** | Natural-language requests that say "hacelo con sdd" route into `/forge` without remembering the slash command. |
124
124
  | **`zero-sdd` theme** | A dark, high-contrast pi theme tuned for SDD work. |
125
125
  | **`zero-sunset` theme** | A warm sunset variant — gold/coral/magenta accents over warm-dark panels, with one cool tone kept for syntax legibility. Activate with `/theme zero-sunset`. |
126
+ | **`zero-omp-neon` theme** | A high-contrast OMP-inspired neon theme with separated colour roles: cyan links/paths, gold titles, mint code/success, violet borders, and navy tool cards. Activate with `/theme zero-omp-neon`. |
127
+ | **ZERO theme pack** | Extra visual variants: `zero-sith`, `zero-saiyan`, `zero-matrix`, and `zero-cyberpunk`. Switch quickly with `/zero-theme sith|saiyan|matrix|cyberpunk`. |
126
128
 
127
129
  ## ⌨️ Commands
128
130
 
@@ -136,6 +138,7 @@ into `/forge` for you.
136
138
  | `/zero-validate <slug>` | Validate proposal/spec/design/tasks artifacts, including task schema and per-domain specs. |
137
139
  | `/zero-status` | Show each `.sdd/` run's artifact, sync, latest-verdict, and GitHub-link status. |
138
140
  | `/zero-hud [compact\|minimal\|full\|ascii\|off\|on\|preview]` | Preview or switch the segmented ZERO footer for this pi session. Default preset: `compact`. |
141
+ | `/zero-theme [neon\|sunset\|sdd\|sith\|saiyan\|matrix\|cyberpunk]` | Switch between packaged ZERO theme variants without remembering full `zero-*` names. |
139
142
  | `/zero-cost [<slug>]` | Report tokens (in/out/cache), USD cost, duration, and tool-count per SDD phase for a run — reads native pi session metas and project-local `.pi-subagents/artifacts`; `<slug>` for a specific run, no argument for the most recent. |
140
143
  | `/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>/`. |
141
144
  | `/zero-branch <slug>` | Create/reuse the configured SDD Git branch and persist `branch`/`baseBranch`. |
@@ -153,6 +156,7 @@ zero-pi ships several visual extensions. They are related, but each one owns a d
153
156
  | ------- | ---- | ---------------- | ------- |
154
157
  | **ZERO HUD** | `extensions/zero-hud.ts` | Footer/status line at the bottom | Persistent model/tokens/cost/diff/context/branch telemetry. |
155
158
  | **Activity panel** | `extensions/zero-activity-panel.ts` | Widget above the prompt while work is running | Live `/forge` phase progress plus recent tool cards. |
159
+ | **Tool cards** | `extensions/zero-pretty-tool-cards.ts` | Each tool execution/result in chat | ZERO-styled bordered cards with status glyphs, title metadata, and padded content. |
156
160
  | **Prompt box** | `extensions/zero-pretty-input-box.ts` | The input/editor box | OMP-like rounded box with ZERO chips and key hints. |
157
161
  | **Code fences** | `extensions/zero-pretty-code-fences.ts` | Markdown code blocks in chat | Bordered panels with language badges instead of raw ```txt / ```json lines. |
158
162
  | **Working phrases** | `extensions/working-phrases.ts` | Tiny loading row (`Procesando… (esc)`) | Rotating ZERO/SDD/Star Wars/DBZ phrases and themed spinner. |
@@ -429,24 +429,55 @@ export function aggregate(records: RunRecord[]): Map<string, PhaseModelStat> {
429
429
  // Model tier ladder
430
430
  // ---------------------------------------------------------------------------
431
431
 
432
- /** Three Claude tiers, ordered `haiku < sonnet < opus`. */
432
+ /** Three tiers per model family, ordered cheap < balanced < flagship. */
433
433
  const TIER = { haiku: 0, sonnet: 1, opus: 2 } as const;
434
434
 
435
435
  /** A model's tier index, or `null` for an unrecognized (untierable) model. */
436
436
  export type Tier = (typeof TIER)[keyof typeof TIER];
437
437
 
438
- /** A single hardcoded representative model id per tier, used as the fallback
439
- * step-up target when the user has no known model at the next tier. */
440
- const TIER_REPRESENTATIVE: Record<Tier, string> = {
441
- [TIER.haiku]: "claude-haiku-4-5",
442
- [TIER.sonnet]: "claude-sonnet-4-6",
443
- [TIER.opus]: "claude-opus-4-8",
438
+ /** Model families the tier ladder understands. Step-ups never cross families:
439
+ * a Codex/GPT phase steps up to a bigger GPT model, never to a Claude one. */
440
+ export type ModelFamily = "claude" | "gpt";
441
+
442
+ /** A single hardcoded representative model id per (family, tier), used as the
443
+ * fallback step-up target when the user has no known model at the next tier.
444
+ * GPT tiers map OpenAI's GPT‑5.6 family (2026‑07‑09): Luna (cost‑efficient) <
445
+ * Terra (balanced) < Sol (flagship). */
446
+ const TIER_REPRESENTATIVE: Record<ModelFamily, Record<Tier, string>> = {
447
+ claude: {
448
+ [TIER.haiku]: "claude-haiku-4-5",
449
+ [TIER.sonnet]: "claude-sonnet-4-6",
450
+ [TIER.opus]: "claude-opus-4-8",
451
+ },
452
+ gpt: {
453
+ [TIER.haiku]: "gpt-5.6-luna",
454
+ [TIER.sonnet]: "gpt-5.6-terra",
455
+ [TIER.opus]: "gpt-5.6-sol",
456
+ },
444
457
  };
445
458
 
459
+ /** Match a GPT‑5.6 variant name as its own dash/dot separated segment, so
460
+ * `gpt-5.6-sol` matches but an unrelated id like `solar-pro` does not. */
461
+ function gptVariant(id: string, variant: string): boolean {
462
+ return new RegExp(`(^|[-._])${variant}($|[-._])`).test(id);
463
+ }
464
+
465
+ /**
466
+ * Classify a model id into its family, or `null` when unrecognized.
467
+ * GPT/Codex ids are recognized by the `gpt` or `codex` marker.
468
+ */
469
+ export function familyOf(modelId: string): ModelFamily | null {
470
+ if (typeof modelId !== "string") return null;
471
+ const id = modelId.toLowerCase();
472
+ if (id.includes("claude") || id.includes("haiku") || id.includes("sonnet") || id.includes("opus")) return "claude";
473
+ if (id.includes("gpt") || id.includes("codex")) return "gpt";
474
+ return null;
475
+ }
476
+
446
477
  /**
447
478
  * Classify a model id into a tier by substring match — deliberate so future
448
- * point releases (`claude-sonnet-4-7`, etc.) classify with no code change.
449
- * Returns `null` for any id that is not recognizably haiku/sonnet/opus.
479
+ * point releases (`claude-sonnet-4-7`, `gpt-5.7-luna`, etc.) classify with no
480
+ * code change. Returns `null` for any id without a recognizable tier marker.
450
481
  */
451
482
  export function tierOf(modelId: string): Tier | null {
452
483
  if (typeof modelId !== "string") return null;
@@ -454,6 +485,9 @@ export function tierOf(modelId: string): Tier | null {
454
485
  if (id.includes("haiku")) return TIER.haiku;
455
486
  if (id.includes("sonnet")) return TIER.sonnet;
456
487
  if (id.includes("opus")) return TIER.opus;
488
+ if (gptVariant(id, "luna")) return TIER.haiku;
489
+ if (gptVariant(id, "terra")) return TIER.sonnet;
490
+ if (gptVariant(id, "sol")) return TIER.opus;
457
491
  return null;
458
492
  }
459
493
 
@@ -467,22 +501,24 @@ export function tierOf(modelId: string): Tier | null {
467
501
  * hardcoded representative for that tier. Never returns an arbitrary id, and
468
502
  * never steps more than one tier.
469
503
  *
470
- * Returns `null` when `model` is already at `opus` (no higher tier) or is
471
- * untierable (an unrecognized model id).
504
+ * Returns `null` when `model` is already at the top tier (no higher tier) or
505
+ * is untierable (an unrecognized model id). Candidates are restricted to the
506
+ * same model family, so a Codex phase never steps up onto a Claude model.
472
507
  */
473
508
  export function stepUp(model: string, knownModels: readonly string[]): string | null {
474
509
  const tier = tierOf(model);
475
- if (tier === null) return null;
510
+ const family = familyOf(model);
511
+ if (tier === null || family === null) return null;
476
512
  if (tier === TIER.opus) return null;
477
513
 
478
514
  const nextTier = (tier + 1) as Tier;
479
515
 
480
516
  const candidates = knownModels
481
- .filter((m) => typeof m === "string" && tierOf(m) === nextTier)
517
+ .filter((m) => typeof m === "string" && tierOf(m) === nextTier && familyOf(m) === family)
482
518
  .sort();
483
519
  if (candidates.length > 0) return candidates[0];
484
520
 
485
- return TIER_REPRESENTATIVE[nextTier];
521
+ return TIER_REPRESENTATIVE[family][nextTier];
486
522
  }
487
523
 
488
524
  // ---------------------------------------------------------------------------
@@ -165,7 +165,7 @@ function toolGlyph(state: ToolState): string {
165
165
  }
166
166
 
167
167
  export function renderActivityPanel(state: ActivityState): string[] {
168
- if (!state.sddActive && state.tools.length === 0) return [];
168
+ if (!state.sddActive) return [];
169
169
  const rule = (n: number) => color.coral("─".repeat(n));
170
170
  const border = rule(72);
171
171
  const header = `${color.coral("╭─")} ${color.gold("ZERO activity")} ${rule(54)}${color.coral("╮")}`;
@@ -232,13 +232,15 @@ export default function register(pi?: PiAPI): void {
232
232
  const e = event as { toolCallId?: string; toolName?: string; args?: unknown };
233
233
  const phase = e.toolName === "subagent" ? phaseFromSubagentArgs(e.args) : undefined;
234
234
  if (phase) markPhaseActive(state, phase);
235
- upsertTool(state, {
236
- id: e.toolCallId ?? `${Date.now()}`,
237
- name: e.toolName ?? "tool",
238
- label: toolLabel(e.toolName ?? "tool", e.args),
239
- state: "running",
240
- });
241
- draw();
235
+ if (state.sddActive || phase) {
236
+ upsertTool(state, {
237
+ id: e.toolCallId ?? `${Date.now()}`,
238
+ name: e.toolName ?? "tool",
239
+ label: toolLabel(e.toolName ?? "tool", e.args),
240
+ state: "running",
241
+ });
242
+ draw();
243
+ }
242
244
  });
243
245
 
244
246
  pi.on("tool_execution_end", (event, ctx) => {
@@ -248,7 +250,7 @@ export default function register(pi?: PiAPI): void {
248
250
  const idx = state.tools.findIndex((tool) => tool.id === id);
249
251
  if (idx >= 0) state.tools[idx].state = e.isError ? "error" : "ok";
250
252
  if (e.toolName === "subagent") finishActivePhase(state, Boolean(e.isError));
251
- draw();
253
+ if (state.sddActive) draw();
252
254
  });
253
255
 
254
256
  pi.on("agent_end", (_event, ctx) => {
@@ -104,13 +104,17 @@ export const DEFAULT_THINKING: Record<Phase, ThinkingLevel> = {
104
104
  veredicto: "xhigh",
105
105
  };
106
106
 
107
- /** Model list used only when pi's model registry is unavailable. */
107
+ /** Model list used only when pi's model registry is unavailable. Includes
108
+ * OpenAI's GPT‑5.6 family (Sol/Terra/Luna, GA 2026‑07‑09) available via Codex. */
108
109
  const FALLBACK_MODELS = [
109
110
  "claude-opus-4-8",
110
111
  "claude-opus-4-7",
111
112
  "claude-opus-4-6",
112
113
  "claude-sonnet-4-6",
113
114
  "claude-haiku-4-5",
115
+ "gpt-5.6-sol",
116
+ "gpt-5.6-terra",
117
+ "gpt-5.6-luna",
114
118
  ];
115
119
 
116
120
  /** Absolute path of pi's `zero.json` marker. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonrocca/zero-pi",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
4
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": [
@@ -0,0 +1,85 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "zero-omp-neon",
4
+ "vars": {
5
+ "bg": "#090b12",
6
+ "panel": "#111421",
7
+ "panel2": "#171329",
8
+ "panelTool": "#101a28",
9
+ "panelOk": "#0d1f1e",
10
+ "panelErr": "#2a111a",
11
+ "selected": "#27213f",
12
+ "textMain": "#ece8f6",
13
+ "prose": "#c9bed6",
14
+ "mutedCool": "#a99eb8",
15
+ "dimCool": "#6e657e",
16
+ "cyan": "#35e8ff",
17
+ "blue": "#67a8ff",
18
+ "mint": "#2dfcb3",
19
+ "green": "#5bff8a",
20
+ "gold": "#ffd166",
21
+ "amber": "#ff9f43",
22
+ "coral": "#ff7a66",
23
+ "rose": "#ff4f7b",
24
+ "magenta": "#d86bff",
25
+ "violet": "#a78bfa"
26
+ },
27
+ "colors": {
28
+ "accent": "cyan",
29
+ "border": "violet",
30
+ "borderAccent": "cyan",
31
+ "borderMuted": "dimCool",
32
+ "success": "mint",
33
+ "error": "rose",
34
+ "warning": "amber",
35
+ "muted": "mutedCool",
36
+ "dim": "dimCool",
37
+ "text": "textMain",
38
+ "thinkingText": "prose",
39
+ "selectedBg": "selected",
40
+ "userMessageBg": "panel2",
41
+ "userMessageText": "gold",
42
+ "customMessageBg": "panel",
43
+ "customMessageText": "prose",
44
+ "customMessageLabel": "cyan",
45
+ "toolPendingBg": "panel2",
46
+ "toolSuccessBg": "panelTool",
47
+ "toolErrorBg": "panelErr",
48
+ "toolTitle": "gold",
49
+ "toolOutput": "prose",
50
+ "mdHeading": "cyan",
51
+ "mdLink": "cyan",
52
+ "mdLinkUrl": "violet",
53
+ "mdCode": "mint",
54
+ "mdCodeBlock": "mint",
55
+ "mdCodeBlockBorder": "violet",
56
+ "mdQuote": "mutedCool",
57
+ "mdQuoteBorder": "dimCool",
58
+ "mdHr": "dimCool",
59
+ "mdListBullet": "magenta",
60
+ "toolDiffAdded": "mint",
61
+ "toolDiffRemoved": "rose",
62
+ "toolDiffContext": "mutedCool",
63
+ "syntaxComment": "dimCool",
64
+ "syntaxKeyword": "magenta",
65
+ "syntaxFunction": "cyan",
66
+ "syntaxVariable": "gold",
67
+ "syntaxString": "mint",
68
+ "syntaxNumber": "coral",
69
+ "syntaxType": "blue",
70
+ "syntaxOperator": "rose",
71
+ "syntaxPunctuation": "mutedCool",
72
+ "thinkingOff": "dimCool",
73
+ "thinkingMinimal": "mutedCool",
74
+ "thinkingLow": "blue",
75
+ "thinkingMedium": "gold",
76
+ "thinkingHigh": "coral",
77
+ "thinkingXhigh": "magenta",
78
+ "bashMode": "cyan"
79
+ },
80
+ "export": {
81
+ "pageBg": "#090b12",
82
+ "cardBg": "#111421",
83
+ "infoBg": "#171329"
84
+ }
85
+ }