@gonrocca/zero-pi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gonzalo Rocca
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @gonrocca/zero-pi
2
+
3
+ An installable layer for **[pi](https://pi.dev)** — it adds the zero
4
+ spec-driven development workflow, skill auto-learning, and an animated `ZERO`
5
+ startup banner **without modifying pi itself**.
6
+
7
+ Same idea as `gentle-pi`: pi stays untouched; zero-pi is a package pi loads.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pi install npm:@gonrocca/zero-pi
13
+ ```
14
+
15
+ That registers zero-pi in `~/.pi/agent/settings.json` and makes its prompts,
16
+ skills, and the startup-banner extension available in every pi session.
17
+
18
+ To remove it:
19
+
20
+ ```bash
21
+ pi remove npm:@gonrocca/zero-pi
22
+ ```
23
+
24
+ ## What it adds
25
+
26
+ ### SDD workflow (`prompts/`)
27
+
28
+ A spec-driven development pipeline driven through four phases, in order:
29
+
30
+ 1. **explore** — investigate the codebase read-only; produce findings.
31
+ 2. **plan** — write requirements, design, and an ordered task list.
32
+ 3. **build** — implement the plan.
33
+ 4. **veredicto** — review the build adversarially and record a verdict
34
+ (`pasa`, `corregir`, or `replantear`).
35
+
36
+ Run it with the `/forge` prompt. The orchestrator drives phase order and
37
+ enforces a hard build/veredicto iteration cap. Each phase has its own prompt
38
+ under `prompts/phases/` so it can be delegated to a dedicated sub-agent.
39
+
40
+ Per-phase model assignments are read from `~/.pi/zero.json`, which the `zero`
41
+ CLI writes when it installs this layer.
42
+
43
+ ### Skill auto-learning (`skills/`)
44
+
45
+ `skill-loop.md` gives the agent a closed learning loop so solutions are reused
46
+ instead of re-derived.
47
+
48
+ ### Startup banner (`extensions/startup-banner.ts`)
49
+
50
+ Renders the `ZERO` wordmark in ANSI Shadow figlet style with a purple → amber
51
+ shimmer sweep the moment a pi session starts, then settles into a static
52
+ gradient glow. Pure ANSI 24-bit colour, no runtime dependencies.
53
+
54
+ The render runs synchronously before pi draws its UI, so the animation never
55
+ fights pi's renderer.
56
+
57
+ Control it with the `ZERO_BANNER` environment variable:
58
+
59
+ | `ZERO_BANNER` | Effect |
60
+ | ------------- | ------ |
61
+ | _(unset)_ / `shimmer` | Animated shimmer sweep, then settle (default) |
62
+ | `static` | Settled gradient only, no animation |
63
+ | `off` | Render nothing |
64
+
65
+ Colour is skipped automatically when `NO_COLOR` is set or the output is not a
66
+ TTY.
67
+
68
+ ## Relationship to `zero`
69
+
70
+ `zero-pi` is the pi-specific layer of the **[zero](https://github.com/gonzalonicolasr/zero)**
71
+ integrator. The `zero` CLI installs this layer onto pi (bootstrapping pi.dev
72
+ itself when it is missing) and writes the per-phase model configuration. You
73
+ can also install `zero-pi` directly with `pi install npm:@gonrocca/zero-pi` if
74
+ you only want the pi layer.
75
+
76
+ ## License
77
+
78
+ MIT © Gonzalo Rocca
@@ -0,0 +1,268 @@
1
+ // zero-pi — animated startup banner.
2
+ //
3
+ // A pi extension that renders the "ZERO" wordmark in ANSI Shadow figlet style
4
+ // — heavy block glyphs with box-drawing trim for a chunky pixel-art feel — the
5
+ // instant a pi session starts. A purple -> amber shimmer sweeps left -> right
6
+ // once, then the glyphs settle into a static gradient glow.
7
+ //
8
+ // The render runs synchronously inside the extension's register() call, before
9
+ // pi draws its interactive UI, so the animation never fights pi's renderer and
10
+ // no extra dependency is needed. A banner failure is swallowed: it must never
11
+ // break a pi session.
12
+ //
13
+ // Behaviour is controlled by the ZERO_BANNER environment variable:
14
+ // ZERO_BANNER=off render nothing
15
+ // ZERO_BANNER=static render the settled gradient, no animation
16
+ // (unset) / shimmer render the animated shimmer sweep (default)
17
+ //
18
+ // Pure ANSI 24-bit colour, zero runtime dependencies.
19
+
20
+ type RGB = [number, number, number];
21
+
22
+ const COLORS: Record<string, RGB> = {
23
+ purpleDim: [82, 70, 124],
24
+ purpleMid: [157, 124, 216],
25
+ amber: [255, 180, 84],
26
+ peak: [255, 240, 210],
27
+ };
28
+
29
+ /** Number of rows in every glyph of the ANSI Shadow font. */
30
+ const ROWS = 6;
31
+
32
+ // The ANSI Shadow font, one entry per supported character. Each glyph is six
33
+ // rows tall; widths vary per letter and the layout adds one space between
34
+ // glyphs for breathing room.
35
+ const FONT: Record<string, string[]> = {
36
+ Z: [
37
+ "███████╗",
38
+ "╚══███╔╝",
39
+ " ███╔╝ ",
40
+ " ███╔╝ ",
41
+ "███████╗",
42
+ "╚══════╝",
43
+ ],
44
+ E: [
45
+ "███████╗",
46
+ "██╔════╝",
47
+ "█████╗ ",
48
+ "██╔══╝ ",
49
+ "███████╗",
50
+ "╚══════╝",
51
+ ],
52
+ R: [
53
+ "██████╗ ",
54
+ "██╔══██╗",
55
+ "██████╔╝",
56
+ "██╔══██╗",
57
+ "██║ ██║",
58
+ "╚═╝ ╚═╝",
59
+ ],
60
+ O: [
61
+ " ██████╗ ",
62
+ "██╔═══██╗",
63
+ "██║ ██║",
64
+ "██║ ██║",
65
+ "╚██████╔╝",
66
+ " ╚═════╝ ",
67
+ ],
68
+ P: [
69
+ "██████╗ ",
70
+ "██╔══██╗",
71
+ "██████╔╝",
72
+ "██╔═══╝ ",
73
+ "██║ ",
74
+ "╚═╝ ",
75
+ ],
76
+ I: [
77
+ "██╗",
78
+ "██║",
79
+ "██║",
80
+ "██║",
81
+ "██║",
82
+ "╚═╝",
83
+ ],
84
+ "-": [
85
+ " ",
86
+ " ",
87
+ " █████╗ ",
88
+ " ╚════╝ ",
89
+ " ",
90
+ " ",
91
+ ],
92
+ " ": [" ", " ", " ", " ", " ", " "],
93
+ };
94
+
95
+ /** Lay a word out as six text rows of ANSI Shadow glyphs. */
96
+ export function bannerLines(text = "ZERO"): string[] {
97
+ const lines: string[] = Array.from({ length: ROWS }, () => "");
98
+ for (const ch of text) {
99
+ const glyph = FONT[ch.toUpperCase()] ?? FONT[" "];
100
+ for (let r = 0; r < ROWS; r++) {
101
+ lines[r] += glyph[r] + " ";
102
+ }
103
+ }
104
+ return lines;
105
+ }
106
+
107
+ function lerp(a: number, b: number, t: number): number {
108
+ return a + (b - a) * t;
109
+ }
110
+
111
+ function lerpColor(c1: RGB, c2: RGB, t: number): RGB {
112
+ const k = Math.max(0, Math.min(1, t));
113
+ return [
114
+ Math.round(lerp(c1[0], c2[0], k)),
115
+ Math.round(lerp(c1[1], c2[1], k)),
116
+ Math.round(lerp(c1[2], c2[2], k)),
117
+ ];
118
+ }
119
+
120
+ /**
121
+ * The colour of one glyph cell.
122
+ *
123
+ * When `spotlight` is null the banner is in its settled state: a subtle
124
+ * horizontal purple -> amber gradient with a top-to-bottom warmth bias so the
125
+ * lower rows glow like embers. Otherwise the cell's colour depends on its
126
+ * distance from the moving spotlight column.
127
+ */
128
+ function colorAt(col: number, row: number, spotlight: number | null, width: number): RGB {
129
+ if (spotlight === null) {
130
+ const horizontal = col / Math.max(1, width - 1);
131
+ const vertical = row / (ROWS - 1);
132
+ const base = lerpColor(COLORS.purpleMid, COLORS.amber, horizontal * 0.55);
133
+ return lerpColor(base, COLORS.amber, vertical * 0.25);
134
+ }
135
+ const dx = Math.abs(col - spotlight);
136
+ if (dx < 1.5) return COLORS.peak;
137
+ if (dx < 4) return lerpColor(COLORS.peak, COLORS.amber, (dx - 1.5) / 2.5);
138
+ if (dx < 9) return lerpColor(COLORS.amber, COLORS.purpleMid, (dx - 4) / 5);
139
+ if (dx < 16) return lerpColor(COLORS.purpleMid, COLORS.purpleDim, (dx - 9) / 7);
140
+ return COLORS.purpleDim;
141
+ }
142
+
143
+ /** Paint one banner row with 24-bit ANSI colour, leaving spaces uncoloured. */
144
+ function paintLine(line: string, row: number, spotlight: number | null, width: number): string {
145
+ let out = "";
146
+ let colored = false;
147
+ for (let i = 0; i < line.length; i++) {
148
+ const ch = line[i];
149
+ if (ch === " ") {
150
+ if (colored) {
151
+ out += "\x1b[0m";
152
+ colored = false;
153
+ }
154
+ out += " ";
155
+ continue;
156
+ }
157
+ const [r, g, b] = colorAt(i, row, spotlight, width);
158
+ out += `\x1b[38;2;${r};${g};${b}m${ch}`;
159
+ colored = true;
160
+ }
161
+ if (colored) out += "\x1b[0m";
162
+ return out;
163
+ }
164
+
165
+ interface OutStream {
166
+ write(chunk: string): unknown;
167
+ isTTY?: boolean;
168
+ }
169
+
170
+ /** Whether colour output is appropriate for the given stream. */
171
+ function shouldColor(stream: OutStream): boolean {
172
+ if (process.env.NO_COLOR) return false;
173
+ if (process.env.FORCE_COLOR) return true;
174
+ return Boolean(stream && stream.isTTY);
175
+ }
176
+
177
+ /** The three banner modes, resolved from the ZERO_BANNER environment variable. */
178
+ export type BannerMode = "off" | "static" | "shimmer";
179
+
180
+ /** Resolve the banner mode from an environment-variable value. */
181
+ export function resolveBannerMode(raw: string | undefined): BannerMode {
182
+ const value = (raw ?? "").trim().toLowerCase();
183
+ if (value === "off" || value === "0" || value === "false" || value === "none") return "off";
184
+ if (value === "static" || value === "plain" || value === "no-animate") return "static";
185
+ return "shimmer";
186
+ }
187
+
188
+ // A synchronous millisecond sleep — Atomics.wait blocks the thread until the
189
+ // timeout elapses (the value never changes). Used to pace animation frames
190
+ // without making register() async, so the whole banner is drawn before pi's
191
+ // UI takes over.
192
+ const SLEEP_SLOT = new Int32Array(new SharedArrayBuffer(4));
193
+ function sleepSync(ms: number): void {
194
+ if (ms > 0) Atomics.wait(SLEEP_SLOT, 0, 0, ms);
195
+ }
196
+
197
+ /** Options for {@link renderBanner}. */
198
+ export interface RenderOptions {
199
+ mode?: BannerMode;
200
+ text?: string;
201
+ frames?: number;
202
+ frameMs?: number;
203
+ stream?: OutStream;
204
+ }
205
+
206
+ /**
207
+ * Render the ZERO banner synchronously.
208
+ *
209
+ * On a non-colour stream the banner is printed plain. Otherwise the shimmer
210
+ * sweep is animated frame by frame (mode `shimmer`) or skipped (mode `static`),
211
+ * settling on the static gradient either way.
212
+ */
213
+ export function renderBanner(options: RenderOptions = {}): void {
214
+ const mode = options.mode ?? resolveBannerMode(process.env.ZERO_BANNER);
215
+ if (mode === "off") return;
216
+
217
+ const stream: OutStream = options.stream ?? process.stdout;
218
+ const lines = bannerLines(options.text ?? "ZERO");
219
+ const width = lines[0].length;
220
+
221
+ if (!shouldColor(stream)) {
222
+ stream.write("\n" + lines.join("\n") + "\n\n");
223
+ return;
224
+ }
225
+
226
+ // Reserve the vertical space the banner will occupy, then redraw in place.
227
+ stream.write("\n" + lines.map(() => "").join("\n") + "\n");
228
+
229
+ if (mode === "shimmer") {
230
+ const frames = Math.max(2, options.frames ?? 28);
231
+ const frameMs = Math.max(1, options.frameMs ?? 24);
232
+ const startCol = -10;
233
+ const endCol = width + 10;
234
+ for (let f = 0; f < frames; f++) {
235
+ const t = f / (frames - 1);
236
+ // ease-in-out so the sweep slows at the edges
237
+ const eased = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
238
+ const spotlight = startCol + (endCol - startCol) * eased;
239
+ stream.write(`\x1b[${ROWS}A\r`);
240
+ for (let r = 0; r < ROWS; r++) {
241
+ stream.write("\x1b[2K" + paintLine(lines[r], r, spotlight, width) + "\n");
242
+ }
243
+ sleepSync(frameMs);
244
+ }
245
+ }
246
+
247
+ // Settle on the static gradient.
248
+ stream.write(`\x1b[${ROWS}A\r`);
249
+ for (let r = 0; r < ROWS; r++) {
250
+ stream.write("\x1b[2K" + paintLine(lines[r], r, null, width) + "\n");
251
+ }
252
+ stream.write("\n");
253
+ }
254
+
255
+ /**
256
+ * The pi extension entry point.
257
+ *
258
+ * pi calls this once when the extension loads, before the interactive UI
259
+ * starts — exactly when the banner should be drawn. The `pi` argument is
260
+ * accepted for API compatibility but not needed: the banner is self-contained.
261
+ */
262
+ export default function register(_pi?: unknown): void {
263
+ try {
264
+ renderBanner();
265
+ } catch {
266
+ // A banner failure must never break a pi session.
267
+ }
268
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@gonrocca/zero-pi",
3
+ "version": "0.1.0",
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
+ "type": "module",
6
+ "keywords": [
7
+ "pi",
8
+ "pi-package",
9
+ "pi-extension",
10
+ "zero",
11
+ "sdd",
12
+ "spec-driven-development",
13
+ "ai-coding-agent"
14
+ ],
15
+ "pi": {
16
+ "prompts": [
17
+ "./prompts"
18
+ ],
19
+ "skills": [
20
+ "./skills"
21
+ ],
22
+ "extensions": [
23
+ "./extensions/startup-banner.ts"
24
+ ]
25
+ },
26
+ "files": [
27
+ "prompts",
28
+ "skills",
29
+ "extensions/startup-banner.ts",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "scripts": {
34
+ "test": "node --test --experimental-strip-types",
35
+ "prepublishOnly": "node --test --experimental-strip-types"
36
+ },
37
+ "engines": {
38
+ "node": ">=20.6.0"
39
+ },
40
+ "author": "Gonzalo Rocca",
41
+ "license": "MIT",
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/gonzalonicolasr/zero.git",
48
+ "directory": "packages/zero-pi"
49
+ },
50
+ "homepage": "https://github.com/gonzalonicolasr/zero/tree/main/packages/zero-pi#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/gonzalonicolasr/zero/issues"
53
+ }
54
+ }
@@ -0,0 +1,20 @@
1
+ ---
2
+ description: Run an automatic spec-driven development pipeline for a feature request
3
+ ---
4
+
5
+ Run the zero SDD pipeline for the feature request in the arguments.
6
+
7
+ Follow the zero SDD orchestrator instructions: drive the run through the
8
+ explore → plan → build → veredicto phases, honour the build/veredicto iteration
9
+ cap, and use the execution mode (interactive or automatic) the user chose.
10
+
11
+ Delegate each phase to its dedicated sub-agent so the phase runs on the model
12
+ it is configured for: `zero-explore`, `zero-plan`, `zero-build`, and
13
+ `zero-veredicto`. You stay the orchestrator — you decide phase order and count
14
+ the rounds; the sub-agents only execute their phase.
15
+
16
+ In interactive mode, pause after each phase with a summary and ask before
17
+ continuing. Never report success unless the veredicto phase returned a `pasa`
18
+ verdict; if the cap is reached first, report that the result is not verified.
19
+
20
+ Feature request: $ARGUMENTS
@@ -0,0 +1,58 @@
1
+ # zero — SDD Orchestrator
2
+
3
+ You are the orchestrator of a spec-driven development (SDD) run. You COORDINATE
4
+ the work; you decide what runs next — never let the loop drift on the model's
5
+ whim. Drive the run through four phases, in order:
6
+
7
+ 1. **explore** — investigate the codebase read-only; produce findings.
8
+ 2. **plan** — write a plan: requirements, design, and an ordered task list.
9
+ 3. **build** — implement the plan.
10
+ 4. **veredicto** — review the build adversarially with a fresh perspective and
11
+ record a verdict: `pasa`, `corregir`, or `replantear`.
12
+
13
+ ## Phase order and the iteration cap
14
+
15
+ - A `pasa` verdict finishes the run successfully.
16
+ - A `corregir` verdict re-runs **build**.
17
+ - A `replantear` verdict re-runs **plan**, then **build**.
18
+ - Count every build/veredicto round. There is a hard **cap** on rounds. When
19
+ the cap is reached without a `pasa` verdict, STOP. Report that the result is
20
+ **not verified** — do **not** claim success.
21
+
22
+ The orchestrator code controls phase order and the round count. The cap is not
23
+ optional and the model does not get to extend it.
24
+
25
+ ## Sub-agent delegation
26
+
27
+ Each phase runs as its own sub-agent — `zero-explore`, `zero-plan`,
28
+ `zero-build`, `zero-veredicto` — so every phase executes on the model it is
29
+ configured for: a cheaper model for exploration, a stronger model for planning
30
+ and the adversarial veredicto. Delegate each phase to its sub-agent and wait
31
+ for its result. The orchestrator keeps control of phase order and the round
32
+ count — the sub-agents only carry out their own phase.
33
+
34
+ ## Model configuration
35
+
36
+ The per-phase model assignments live in `~/.pi/zero.json` under `models`
37
+ (keys: `explore`, `plan`, `build`, `veredicto`). Read that file at the start of
38
+ a run and delegate each phase's sub-agent to the model listed there. When the
39
+ file is absent or a phase is missing, fall back to the session's default model.
40
+
41
+ ## Execution mode
42
+
43
+ At the start of a run, ask the user which mode they want:
44
+
45
+ - **interactive** (default): after each phase, show a concise summary of what
46
+ the phase produced and what the next phase will do, then ask
47
+ "¿Continuamos? / Continue?" before proceeding. Accept continue, stop, or
48
+ feedback to adjust.
49
+ - **automatic**: run all phases back to back without pausing; show the final
50
+ result only.
51
+
52
+ Cache the mode for the run.
53
+
54
+ ## Visibility
55
+
56
+ While a phase runs, keep the user informed of progress — never run silently for
57
+ a long stretch. The user must always be able to tell which phase and round the
58
+ run is in.
@@ -0,0 +1,11 @@
1
+ ---
2
+ description: SDD build phase — implement the planned tasks, test-first, and make the suite pass
3
+ ---
4
+
5
+ You run the **build** phase of a zero SDD pipeline.
6
+
7
+ Implement the planned tasks in order, test-first where practical. Keep every
8
+ change within the plan's scope — do not expand it on your own initiative.
9
+
10
+ Run the test suite and make it pass before reporting the phase complete. Report
11
+ what you changed so the veredicto phase has something concrete to review.
@@ -0,0 +1,12 @@
1
+ ---
2
+ description: SDD explore phase — investigate the codebase read-only and produce findings
3
+ ---
4
+
5
+ You run the **explore** phase of a zero SDD pipeline.
6
+
7
+ Investigate the codebase and the feature request read-only. Do not modify any
8
+ files. Map the relevant modules, the existing patterns and conventions, the
9
+ integration points, and the constraints. Identify the risks and the unknowns.
10
+
11
+ Produce a concise findings report the **plan** phase can build on: what exists,
12
+ what is relevant to the request, and what to watch out for.
@@ -0,0 +1,13 @@
1
+ ---
2
+ description: SDD plan phase — turn findings into requirements, design, and an ordered task list
3
+ ---
4
+
5
+ You run the **plan** phase of a zero SDD pipeline.
6
+
7
+ Using the explore findings, write the plan: the requirements (what must be
8
+ true), the design (how it will be built), and an ordered list of small,
9
+ independently verifiable tasks.
10
+
11
+ Do not write implementation code in this phase — only the plan. Keep each task
12
+ scoped tightly enough that the build phase can implement and check it on its
13
+ own.
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: SDD veredicto phase — adversarially review the build and record a verdict
3
+ ---
4
+
5
+ You run the **veredicto** phase of a zero SDD pipeline.
6
+
7
+ Review the build adversarially, with a fresh perspective. Check it against the
8
+ plan's requirements, run the tests yourself, and look for gaps, regressions,
9
+ and unmet acceptance criteria.
10
+
11
+ Record exactly one verdict:
12
+
13
+ - `pasa` — the build meets the plan; the run finishes successfully.
14
+ - `corregir` — fixable defects remain; the build phase must re-run.
15
+ - `replantear` — the plan itself is wrong; the plan phase must re-run.
16
+
17
+ Never return `pasa` unless the evidence supports it.
@@ -0,0 +1,28 @@
1
+ # zero — Skill Auto-Learning
2
+
3
+ zero gives the agent a closed learning loop so solutions are reused, not
4
+ re-derived.
5
+
6
+ ## Distill
7
+
8
+ When a substantial task completes, **distill** a reusable skill from it: capture
9
+ the solution pattern, the ordered steps, and the non-obvious gotchas into a
10
+ single skill document. If the task did only routine or one-off work with no
11
+ reusable pattern, do not create a skill.
12
+
13
+ ## Store
14
+
15
+ Store each learned skill in the per-user skill library so it persists across
16
+ sessions and is available to every agent zero has configured.
17
+
18
+ ## Surface
19
+
20
+ When a new task begins, surface the stored skills relevant to it — match by the
21
+ skill's subject and description — so the agent consults a known solution before
22
+ re-deriving one. Surface nothing when no stored skill is relevant.
23
+
24
+ ## Refine
25
+
26
+ When a run re-applies an existing skill, **refine** that skill rather than
27
+ create a duplicate: merge new gotchas and steps without repeating what is
28
+ already there, and prefer the newer learning when it contradicts the old.