@r3b1s/pi-repair-layer 0.3.0 → 0.4.1

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
@@ -8,6 +8,20 @@ same repair pipeline to tools in your own extension.
8
8
 
9
9
  No LLM calls. No network requests. No uploaded telemetry.
10
10
 
11
+ ## Contents
12
+
13
+ - [Why use it?](#why-use-it)
14
+ - [Install](#install)
15
+ - [Terms in plain English](#terms-in-plain-english)
16
+ - [What it repairs](#what-it-repairs)
17
+ - [Safe by design](#safe-by-design)
18
+ - [Use it in your own extension](#use-it-in-your-own-extension)
19
+ - [Settings and local telemetry](#settings-and-local-telemetry)
20
+ - [Documentation](#documentation)
21
+ - [Prior art](#prior-art)
22
+ - [Limitations](#limitations)
23
+ - [Development](#development)
24
+
11
25
  ## Why use it?
12
26
 
13
27
  Models often understand the task but miss a detail of the tool contract:
@@ -106,6 +120,10 @@ invariants in detail.
106
120
  Tools registered by other extensions are not intercepted automatically: their
107
121
  owner must opt in at the `prepareArguments` boundary.
108
122
 
123
+ Working with a coding agent? The integration is packaged as the
124
+ [`pi-tool-repair-integration`](https://github.com/r3b1s/pi-dev-skills) agent
125
+ skill: `npx skills add r3b1s/pi-dev-skills --skill pi-tool-repair-integration`.
126
+
109
127
  ```ts
110
128
  import { adaptToolDefinition } from "@r3b1s/pi-repair-layer/pi";
111
129
 
package/docs/research.md CHANGED
@@ -23,6 +23,16 @@ is protecting and where to re-verify it.
23
23
  `test/upstream-drift.test.ts` that execute the claims against the installed
24
24
  packages.
25
25
 
26
+ Claims 10–14 (optional integration) were added later with their own provenance:
27
+
28
+ - **Verification date:** 2026-07-18
29
+ - **Source read:** the same pi clone (`v0.80.6-24-g0e6909f0`); line citations
30
+ below refer to it.
31
+ - **Empirical runs:** a live pi 0.80.10 install (npm-dist `cli.js` under
32
+ Node 24) and the official `pi-linux-x64` release binary v0.80.10 (Bun
33
+ compiled executable), each driven with a throwaway probe extension
34
+ npm-installed into an isolated `$HOME/.pi/agent/npm` scope.
35
+
26
36
  ## Claims
27
37
 
28
38
  ### Claim 1 — Loop ordering: `prepareArguments` runs before validation, which runs before the `tool_call` event
@@ -202,6 +212,124 @@ queues value-free feedback, binds it at post-validation `tool_call`, then uses
202
212
  custom calls already have IDs and can be associated directly, without wrapping
203
213
  another extension's executor.
204
214
 
215
+ ### Claim 10 — pi maintains one shared npm install project per scope, with flat sibling resolution
216
+
217
+ All `npm:`-installed extensions in a scope are dependencies of a single private
218
+ npm project named `pi-extensions`, so their packages sit side by side in one
219
+ flat `node_modules`. An end user who runs
220
+ `pi install npm:@r3b1s/pi-repair-layer` therefore makes the package resolvable
221
+ from every other npm-installed extension in that scope.
222
+
223
+ - `packages/coding-agent/src/core/package-manager.ts:1933-1944` —
224
+ `ensureNpmProject` writes `{ name: "pi-extensions", private: true }` into the
225
+ install root's `package.json`.
226
+ - `packages/coding-agent/src/core/package-manager.ts:1956-1964` —
227
+ `getNpmInstallRoot` returns `join(this.agentDir, "npm")` for the user scope
228
+ (project scope roots under the project's config dir instead).
229
+ - Live install (pi 0.80.10): `~/.pi/agent/npm/package.json` has
230
+ `name: "pi-extensions"` with every npm-installed extension as a dependency,
231
+ and `node_modules` holds them as flat siblings (bun-backed, `bun.lock`
232
+ present).
233
+
234
+ **Why it matters:** this is the mechanism behind the shared-`node_modules`
235
+ adoption path in the optional-integration recipe
236
+ (`docs/tool-owner-integration.md`). It is observed managed-install layout, not
237
+ a documented pi API guarantee — the recipe never *depends* on it (resolution
238
+ either succeeds or falls back safely), but the adoption story does.
239
+
240
+ ### Claim 11 — Missing-module error shapes: `MODULE_NOT_FOUND` (jiti/require) vs `ERR_MODULE_NOT_FOUND` (ESM import)
241
+
242
+ A dynamic import of an uninstalled package surfaces differently depending on
243
+ the loader, but always with one of two `code` values and a message that names
244
+ the requested module:
245
+
246
+ - jiti 2.7.0 require path (Node): plain `Error`, `code: "MODULE_NOT_FOUND"`,
247
+ message `Cannot find module '<full specifier>'`.
248
+ - Native Node ESM `import()` (Node 24): `Error`,
249
+ `code: "ERR_MODULE_NOT_FOUND"`, message
250
+ `Cannot find package '<package name>'` — **it names the bare package, not
251
+ the full subpath specifier**, so absence checks must match the package name
252
+ (`@r3b1s/pi-repair-layer`), never the `/pi` subpath string.
253
+ - Compiled Bun binary (pi-linux-x64 v0.80.10): Bun `ResolveMessage` (an
254
+ `Error` subclass), `code: "ERR_MODULE_NOT_FOUND"` for `import()` and
255
+ `"MODULE_NOT_FOUND"` for a scoped `require`, message
256
+ `Cannot find module '<full specifier>' from '<importer path>'`.
257
+
258
+ **Why it matters:** the optional-integration recipe treats an import failure
259
+ as "package absent" only when the code is one of these two values **and** the
260
+ message names `@r3b1s/pi-repair-layer`; anything else rethrows so a broken
261
+ install (a *transitive* module missing) is not silently misread as absent.
262
+ All three observed shapes pass that discrimination.
263
+
264
+ ### Claim 12 — Git installs and other scopes do not resolve the shared npm siblings
265
+
266
+ Git-installed extensions are cloned into `<agentDir>/git/<host>/<path>` and get
267
+ their **own** dependency install inside the clone; they are not siblings of the
268
+ shared npm root. User scope (`~/.pi/agent`) and project scope (`<cwd>/.pi`)
269
+ likewise use separate install roots.
270
+
271
+ - `packages/coding-agent/src/core/package-manager.ts:1820-1846` — `installGit`
272
+ clones into `getGitInstallPath(...)` and runs a dependency install inside the
273
+ clone when it has a `package.json`.
274
+ - `packages/coding-agent/src/core/package-manager.ts:2036-2046` —
275
+ `getGitInstallRoot` returns `join(this.agentDir, "git")` (user scope) or
276
+ `join(this.cwd, CONFIG_DIR_NAME, "git")` (project scope).
277
+
278
+ **Why it matters:** a git-installed or cross-scope consumer cannot resolve an
279
+ npm-installed `@r3b1s/pi-repair-layer` sibling, so the optional recipe falls
280
+ back there even though the user "installed" the package. Documented as a hard
281
+ caveat in the integration guide; `optionalDependencies` is the alternative for
282
+ those consumers.
283
+
284
+ ### Claim 13 — Bun-binary probe: the optional dynamic import falls back under the compiled binary, resolves under npm-dist pi
285
+
286
+ pi loads extensions through jiti (`moduleCache: false`; in the compiled binary
287
+ additionally `virtualModules` + `tryNative: false`; under Node, `alias`):
288
+
289
+ - `packages/coding-agent/src/core/extensions/loader.ts:398-404` — the
290
+ `createJiti` call and both option branches.
291
+
292
+ End-to-end probe (2026-07-18): a throwaway extension implementing the
293
+ documented recipe, npm-installed into an isolated scope, run once with
294
+ `@r3b1s/pi-repair-layer` npm-installed as a sibling and once without, under
295
+ both pi distributions of v0.80.10:
296
+
297
+ | runtime | package | observed error (`name`/`code`) | branch taken |
298
+ |---|---|---|---|
299
+ | npm-dist (Node) | absent | `Error`/`ERR_MODULE_NOT_FOUND` | fallback, note emitted |
300
+ | npm-dist (Node) | present | — | **adapted** |
301
+ | compiled binary | absent | `ResolveMessage`/`ERR_MODULE_NOT_FOUND` | fallback, note emitted |
302
+ | compiled binary | present | `ResolveMessage`/`ERR_MODULE_NOT_FOUND` | fallback, note emitted |
303
+
304
+ Under the compiled Bun binary, *static* imports of the sibling package do
305
+ resolve (jiti's own resolver handles them — verified with a static-import
306
+ probe extension in the same scope), but native dynamic `import()` and the
307
+ scoped `require` both bypass jiti and hit Bun's embedded resolver, which does
308
+ no filesystem `node_modules` resolution — even an absolute-path `import()` of
309
+ the package's entry file loads but then dies on its transitive bare specifier
310
+ (`typebox/value`). There is no consumer-accessible dynamic path through jiti's
311
+ resolver as of this pi version.
312
+
313
+ **Why it matters:** the optional-integration pattern *activates* only under
314
+ Node-based pi installs (npm/bun global install). Under the official compiled
315
+ binary it degrades safely — the import failure has exactly the absent-package
316
+ shape, so consumers fall back to their raw definition with the one-line note,
317
+ even when the package is installed. A hard static dependency, by contrast,
318
+ works under both distributions. Both facts are documented in the integration
319
+ guide's caveats.
320
+
321
+ ### Claim 14 — Unrecognized preprocessor kinds fall through `preprocessInput` untouched (local guarantee)
322
+
323
+ This claim is about this repo, not pi: `preprocessInput`
324
+ (`src/preprocess.ts`) matches each configured entry against the known `kind`
325
+ branches and simply skips entries it does not recognize — no mutation, no
326
+ error, no claimed change — and the pipeline still schema-validates the final
327
+ result. A consumer configured against a newer options shape therefore degrades
328
+ to the recognized subset when running against an older installed version.
329
+ Promoted to a spec-level compatibility guarantee by the
330
+ `optional-integration-fallback` change and pinned by a unit test in
331
+ `test/pipeline.test.ts`.
332
+
205
333
  ### Package/runtime assumptions
206
334
 
207
335
  The published package targets Node 22+ and compiled ESM. `typebox` is a runtime
@@ -245,3 +373,18 @@ work through this list and update the citations/date above:
245
373
  messages, and handlers still compose in registration order.
246
374
  10. **Package/runtime:** run `pnpm run test:package`; re-check Node engines,
247
375
  peer/runtime dependencies, every `exports` target, and the pi extension path.
376
+ 11. **Shared npm root (Claim 10):** confirm `ensureNpmProject` still writes one
377
+ `pi-extensions` project per scope and installs remain flat siblings —
378
+ re-read `package-manager.ts` and inspect a live `~/.pi/agent/npm`.
379
+ 12. **Error shapes (Claim 11):** re-run a missing-module import under jiti, under
380
+ native Node `import()`, and under the compiled binary; confirm the codes are
381
+ still `MODULE_NOT_FOUND` / `ERR_MODULE_NOT_FOUND` and the message still names
382
+ the requested package.
383
+ 13. **Git/scope boundaries (Claim 12):** confirm git installs still get their own
384
+ clone-local dependency install and scopes still use separate install roots.
385
+ 14. **Bun-binary probe (Claim 13):** re-run the four-cell probe (npm-dist and
386
+ compiled binary, package present and absent) with a throwaway recipe
387
+ extension npm-installed into an isolated `$HOME`; update the outcome table —
388
+ especially whether dynamic `import()` in the compiled binary has gained
389
+ filesystem `node_modules` resolution, which would let optional consumers
390
+ activate there.
@@ -5,6 +5,30 @@ pipeline to tools that extension owns. The usual integration is one wrapper
5
5
  around the tool definition. A lower-level core API is available when the tool
6
6
  owner needs to control the surrounding lifecycle.
7
7
 
8
+ If a coding agent is doing the integration for you, this guide is also
9
+ packaged as the
10
+ [`pi-tool-repair-integration`](https://github.com/r3b1s/pi-dev-skills/blob/main/skills/pi-tool-repair-integration/SKILL.md)
11
+ agent skill — the workflow, the optional-integration recipe as a copyable
12
+ template, the preprocessor catalog, and the both-branch testing checklist.
13
+ Install it standalone with the [skills CLI](https://github.com/vercel-labs/skills):
14
+
15
+ ```bash
16
+ npx skills add r3b1s/pi-dev-skills --skill pi-tool-repair-integration
17
+ ```
18
+
19
+ ## Contents
20
+
21
+ - [The ownership boundary](#the-ownership-boundary)
22
+ - [Install the package](#install-the-package)
23
+ - [Recommended: wrap the tool definition](#recommended-wrap-the-tool-definition)
24
+ - [Optional integration](#optional-integration)
25
+ - [Configure only known-safe transforms](#configure-only-known-safe-transforms)
26
+ - [Existing `prepareArguments` hooks](#existing-preparearguments-hooks)
27
+ - [Optional: receive structured outcomes](#optional-receive-structured-outcomes)
28
+ - [Optional: attach `<repair_note>` feedback](#optional-attach-repair_note-feedback)
29
+ - [Lower level: call the pure core](#lower-level-call-the-pure-core)
30
+ - [Test the integration](#test-the-integration)
31
+
8
32
  ## The ownership boundary
9
33
 
10
34
  pi validates tool arguments after calling that tool's `prepareArguments`.
@@ -24,6 +48,12 @@ Add pi-repair-layer as a dependency of the extension that owns the tool:
24
48
  pnpm add @r3b1s/pi-repair-layer
25
49
  ```
26
50
 
51
+ The dependency does not have to be hard. A standalone extension can keep
52
+ pi-repair-layer out of its runtime dependencies entirely and degrade to its
53
+ raw tool definition when the package is absent — see
54
+ [Optional integration](#optional-integration) before committing your users to
55
+ an extra install.
56
+
27
57
  The public APIs are compiled ESM with TypeScript declarations:
28
58
 
29
59
  - `@r3b1s/pi-repair-layer/pi` provides the pi tool-definition adapter.
@@ -112,6 +142,208 @@ model-readable retry message. `unrepairable: "passthrough"` is available for a
112
142
  deliberate compatibility migration, but it can expose the input to pi's native
113
143
  conversion and should not be the default.
114
144
 
145
+ ## Optional integration
146
+
147
+ `adaptToolDefinition` is a pure decorator: it takes a `ToolDefinition` and
148
+ returns a `ToolDefinition`. That makes the identity function a complete
149
+ fallback — an extension can attempt to load the adapter at activation time and
150
+ register its unmodified definition when pi-repair-layer is not installed.
151
+ Repair support then becomes an end-user opt-in instead of an author-imposed
152
+ dependency.
153
+
154
+ ### The recipe
155
+
156
+ Author repair options as pure data, import only types at compile time, and
157
+ attempt one dynamic import inside the (awaited) extension factory:
158
+
159
+ ```ts
160
+ import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
161
+ import type {
162
+ adaptToolDefinition,
163
+ PiToolOwnerAdapterOptions,
164
+ } from "@r3b1s/pi-repair-layer/pi";
165
+ import { Type } from "typebox";
166
+
167
+ const parameters = Type.Object({ path: Type.String() });
168
+
169
+ const repairOptions = {
170
+ policy: "adaptive",
171
+ preprocessors: [
172
+ {
173
+ kind: "alias",
174
+ selector: "/path",
175
+ aliases: ["file_path"],
176
+ accepts: "string",
177
+ },
178
+ ],
179
+ } satisfies PiToolOwnerAdapterOptions;
180
+
181
+ const definition: ToolDefinition<typeof parameters> = {
182
+ name: "inspect_asset",
183
+ label: "Inspect asset",
184
+ description: "Inspect an asset",
185
+ parameters,
186
+ async execute(_toolCallId, params) {
187
+ return {
188
+ content: [{ type: "text", text: `Inspecting ${params.path}` }],
189
+ details: undefined,
190
+ };
191
+ },
192
+ };
193
+
194
+ async function loadRepairAdapter(): Promise<
195
+ typeof adaptToolDefinition | undefined
196
+ > {
197
+ try {
198
+ const repair = await import("@r3b1s/pi-repair-layer/pi");
199
+ return repair.adaptToolDefinition;
200
+ } catch (error) {
201
+ const code = (error as { code?: unknown } | null)?.code;
202
+ const message = error instanceof Error ? error.message : String(error);
203
+ const packageAbsent =
204
+ (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") &&
205
+ message.includes("@r3b1s/pi-repair-layer");
206
+ if (!packageAbsent) throw error;
207
+ return undefined;
208
+ }
209
+ }
210
+
211
+ export default async function myExtension(pi: ExtensionAPI) {
212
+ const adapt = await loadRepairAdapter();
213
+ if (!adapt) {
214
+ console.error(
215
+ "[my-extension] @r3b1s/pi-repair-layer not found; inspect_asset running unwrapped",
216
+ );
217
+ }
218
+ pi.registerTool(adapt ? adapt(definition, repairOptions) : definition);
219
+ }
220
+ ```
221
+
222
+ The details are load-bearing:
223
+
224
+ - **Discriminate before falling back.** A missing package surfaces as
225
+ `code: "MODULE_NOT_FOUND"` (jiti's require path) or
226
+ `code: "ERR_MODULE_NOT_FOUND"` (native ESM and the compiled pi binary). But
227
+ a *present-but-broken* install — pi-repair-layer resolvable while one of its
228
+ own transitive modules is not — throws the same codes naming the
229
+ *transitive* module. Requiring the message to name
230
+ `@r3b1s/pi-repair-layer` keeps a broken install loud instead of silently
231
+ running unwrapped while the user believes repairs are active. Match the
232
+ package name, not the `/pi` subpath: native ESM reports only
233
+ `Cannot find package '@r3b1s/pi-repair-layer'`.
234
+ - **Rethrow everything else.** Any other error is a real failure, not
235
+ absence.
236
+ - **Emit one stderr line when falling back.** The two branches differ in
237
+ behavior (see the caveats below); a silent divergence cannot be diagnosed
238
+ from a session transcript.
239
+ - **Keep repair options as pure data.** `satisfies PiToolOwnerAdapterOptions`
240
+ with a type-only import gives full typechecking with no runtime import, so
241
+ the compiled extension activates in installs without the package.
242
+
243
+ The runnable, tested copy of this recipe is
244
+ [`test/fixtures/optional-consumer.ts`](../test/fixtures/optional-consumer.ts);
245
+ the package smoke test exercises both branches in clean projects. Keep the
246
+ snippet above and that fixture in sync when editing either.
247
+
248
+ ### Consumer `package.json` shape
249
+
250
+ ```jsonc
251
+ {
252
+ "devDependencies": {
253
+ // for typechecking and local tests only; erased from the runtime story
254
+ "@r3b1s/pi-repair-layer": "^0.3.0"
255
+ },
256
+ "peerDependencies": {
257
+ "@r3b1s/pi-repair-layer": ">=0.3.0"
258
+ },
259
+ "peerDependenciesMeta": {
260
+ "@r3b1s/pi-repair-layer": { "optional": true }
261
+ }
262
+ }
263
+ ```
264
+
265
+ `import type` and `satisfies` are erased at compile time, so the
266
+ `devDependencies` entry is all that is needed to build. The optional peer
267
+ declaration documents which versions the extension was written against
268
+ without forcing an install.
269
+
270
+ ### Adoption paths
271
+
272
+ **End-user opt-in via pi's shared install root (the default story).** pi
273
+ installs every `npm:`-installed extension into one shared project per scope,
274
+ so all such extensions sit side by side in a flat `node_modules`. When an end
275
+ user runs:
276
+
277
+ ```bash
278
+ pi install npm:@r3b1s/pi-repair-layer
279
+ ```
280
+
281
+ to get repairs for pi's built-in tools, the package also becomes resolvable to
282
+ every npm-installed extension in that scope — and consenting extensions using
283
+ this recipe light up automatically. Nothing is required from the extension
284
+ author beyond the recipe.
285
+
286
+ **Author-controlled `optionalDependencies`.** An author who wants repairs
287
+ whenever the environment can provide them can instead declare:
288
+
289
+ ```jsonc
290
+ {
291
+ "optionalDependencies": {
292
+ "@r3b1s/pi-repair-layer": "^0.3.0"
293
+ }
294
+ }
295
+ ```
296
+
297
+ The package installs alongside the extension when possible, and a failed
298
+ optional install does not fail the extension's install. This is also the path
299
+ for consumers the shared-root story cannot reach (see the caveats).
300
+
301
+ ### Caveats
302
+
303
+ - **Install source and scope matter.** The shared `node_modules` is per pi
304
+ scope, and only for `npm:` installs. Git-installed extensions get their own
305
+ clone-local dependency install, and project-scope installs do not resolve
306
+ user-scope siblings (or vice versa). Those consumers fall back even though
307
+ the user installed the package; `optionalDependencies` is the alternative
308
+ there. The layout is observed pi behavior (verified 0.80.6–0.80.10), not a
309
+ documented pi guarantee — see
310
+ [research Claim 10](research.md#claim-10--pi-maintains-one-shared-npm-install-project-per-scope-with-flat-sibling-resolution).
311
+ - **The compiled pi binary never takes the adapter branch today.** Under the
312
+ standalone (Bun-compiled) pi executable, dynamic `import()` cannot resolve
313
+ npm-installed siblings at all, so this recipe falls back — safely, with the
314
+ note — even when the package is installed. The optional pattern currently
315
+ activates only under Node-based pi installs (`npm install -g` /
316
+ `bun install -g`). A hard static dependency resolves under both
317
+ distributions. See
318
+ [research Claim 13](research.md#claim-13--bun-binary-probe-the-optional-dynamic-import-falls-back-under-the-compiled-binary-resolves-under-npm-dist-pi).
319
+ - **Fallback mode is baseline pi, not "repairs minus notes."** Without the
320
+ adapter, pi's native validation runs `Value.Convert` first, which silently
321
+ coerces some invalid input into valid-looking values (`null` → `"null"`)
322
+ instead of repairing or rejecting it. Decide explicitly whether that is
323
+ acceptable for your tool, and test both branches.
324
+ - **No double-wrap.** The installable pi-repair-layer extension only overrides
325
+ pi's built-in tools; it never discovers or wraps tools registered by other
326
+ extensions. A tool adapted by this recipe is wrapped exactly once in either
327
+ branch.
328
+
329
+ ### Stability contract for optional consumers
330
+
331
+ Copies of this recipe keep working across versions because the surface it
332
+ touches is deliberately small and covered by the compatibility contract in
333
+ [Install the package](#install-the-package):
334
+
335
+ - The subpath names (`/pi`, `/core`, `/grammar`) and the
336
+ `adaptToolDefinition(definition, options?)` signature are stable for the
337
+ current major; documented exports follow semantic versioning.
338
+ - Absence detection semantics are part of the contract: a missing package
339
+ surfaces with `code` `MODULE_NOT_FOUND` or `ERR_MODULE_NOT_FOUND` and a
340
+ message naming the requested module.
341
+ - Unrecognized preprocessor `kind`s are ignored — never fatal, no mutation, no
342
+ claimed change — and results are still schema-validated. Repair options
343
+ written against a newer minor version therefore degrade to the recognized
344
+ subset when an older version is installed, instead of throwing at
345
+ configuration time.
346
+
115
347
  ## Configure only known-safe transforms
116
348
 
117
349
  Preprocessors use JSON-Pointer-like selectors. `"/path"` selects a field,
@@ -321,6 +553,15 @@ At minimum, an owning extension should test:
321
553
  each and stale notes do not leak to later calls.
322
554
  7. Telemetry and persisted entries contain no argument values or note text.
323
555
 
556
+ Optional consumers (the [Optional integration](#optional-integration) recipe)
557
+ should additionally test both branches:
558
+
559
+ 8. With the package absent, activation still succeeds: the raw definition is
560
+ registered and the one-line fallback note is emitted.
561
+ 9. Strictly valid input behaves identically in both branches; for invalid
562
+ input, the fallback branch exposes pi's native coercion — confirm that is
563
+ acceptable for your tool.
564
+
324
565
  For a working compile-time example, see
325
566
  [`test/fixtures/public-consumer.ts`](../test/fixtures/public-consumer.ts). The
326
567
  package smoke test installs the packed tarball into a clean project and imports
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r3b1s/pi-repair-layer",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Tool-input repair layer for the pi coding agent: validate-then-repair for built-in tool calls, ported from the behavior of commandcode's repair layer",
5
5
  "repository": {
6
6
  "type": "git",