agentsclimarketplace

Pi tool repair integration

Skill r3b1s/pi-dev-skills/skills/pi-tool-repair-integration

A library of agent skills for developing with Pi in mind.From the repository description

Install
npx -y skills add r3b1s/pi-dev-skills --skill pi-tool-repair-integration

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

3 things to look at

  • 23 days oldThe repository was created 23 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

8.0 KB, ~1.8k tokens by cl100k_base, as published. Nobody here has run it

pi Tool Repair Integration

Integrate @r3b1s/pi-repair-layer into a pi extension's own tools so malformed model arguments ({file_path: "/x"} instead of {path: "/x"}, stringified JSON, stray nulls) are repaired before pi validation — without forcing every downstream user to install the repair layer.

Verified baselines: package ^0.3.0, Node 22+, pi 0.80.6 (probed through 0.80.10).

The ownership boundary (read first)

pi runs prepareArguments → validation → tool_call → execute. Arguments that fail validation never reach tool_call, so repair must run in prepareArguments — and only the extension that owns a tool definition can install that hook. Installing pi-repair-layer repairs pi's built-in tools only; it never discovers or wraps tools registered by other extensions. Your extension must opt in per tool. Never attempt to wrap another extension's tools.

Step 1 — Choose the dependency posture

PostureWhenHow
Optional (default for standalone extensions)The tool works fine unwrapped; repairs are an enhancementFallback recipe below; package stays out of runtime deps
Hard dependencyThe tool relies on repair behavior (e.g. legacy-shape migration via preprocessors)pnpm add @r3b1s/pi-repair-layer; import statically

The optional posture turns "also install pi-repair-layer" into an end-user opt-in: pi installs all npm: extensions into one shared node_modules per scope, so a user who runs pi install npm:@r3b1s/pi-repair-layer makes it resolvable to every consenting npm-installed extension automatically.

Step 2 — Write the tool and repair options as pure data

Repair options must be plain data validated with a type-only import, so compilation never requires the package at runtime:

import type { PiToolOwnerAdapterOptions } from "@r3b1s/pi-repair-layer/pi";

const repairOptions = {
  policy: "adaptive",
  preprocessors: [
    {
      kind: "alias",
      selector: "/path",
      aliases: ["file_path"],
      accepts: "string",
    },
  ],
} satisfies PiToolOwnerAdapterOptions;

Configure only transforms you know are safe for your tool — see resources/preprocessor-catalog.md for every kind, selector semantics, and the policy profiles. The pipeline never guesses aliases, fuzzily renames keys, or deletes unknown fields; wrapped tools get bounded envelope recovery and schema-located repairs for free.

Step 3 — The optional-integration recipe

Copy resources/optional-extension-template.ts (a complete extension) and adapt names. The load-bearing core:

import type { adaptToolDefinition } from "@r3b1s/pi-repair-layer/pi";

async function loadRepairAdapter(): Promise<
  typeof adaptToolDefinition | undefined
> {
  try {
    const repair = await import("@r3b1s/pi-repair-layer/pi");
    return repair.adaptToolDefinition;
  } catch (error) {
    const code = (error as { code?: unknown } | null)?.code;
    const message = error instanceof Error ? error.message : String(error);
    const packageAbsent =
      (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") &&
      message.includes("@r3b1s/pi-repair-layer");
    if (!packageAbsent) throw error;
    return undefined;
  }
}

export default async function myExtension(pi: ExtensionAPI) {
  const adapt = await loadRepairAdapter();
  if (!adapt) {
    console.error(
      "[my-extension] @r3b1s/pi-repair-layer not found; my_tool running unwrapped",
    );
  }
  pi.registerTool(adapt ? adapt(definition, repairOptions) : definition);
}

Every detail matters — do not simplify these away:

  • Both error codes. MODULE_NOT_FOUND is jiti's require path; ERR_MODULE_NOT_FOUND is native ESM and the compiled pi binary.
  • The message must name @r3b1s/pi-repair-layer. A present-but-broken install throws the same codes naming a transitive module; swallowing it would silently disable repairs the user believes are active. Match the package name, not the /pi subpath — native ESM reports only Cannot find package '@r3b1s/pi-repair-layer'.
  • Rethrow anything else. Any other error is a real failure, not absence.
  • One stderr note on fallback. The branches differ in coercion behavior; a silent divergence cannot be diagnosed from a session transcript.
  • Identity fallback. Register the unmodified definition — never a partial wrapper.

Step 4 — Consumer package.json

{
  "devDependencies": {
    "@r3b1s/pi-repair-layer": "^0.3.0" // typecheck + local tests only
  },
  "peerDependencies": {
    "@r3b1s/pi-repair-layer": ">=0.3.0"
  },
  "peerDependenciesMeta": {
    "@r3b1s/pi-repair-layer": { "optional": true }
  }
}

Alternative: authors who want repairs whenever the environment allows can add "optionalDependencies": { "@r3b1s/pi-repair-layer": "^0.3.0" } — a failed optional install does not fail the extension install. This is also the path for consumers the shared-root story cannot reach (see caveats).

Step 5 — Test both branches

Work through resources/testing-checklist.md. Minimum bar: with the package absent, activation succeeds, the raw definition is registered, and the note is emitted; with it present, the adapter branch is taken silently and each configured repair produces valid arguments.

Hard caveats (state these to the user when relevant)

  1. Compiled pi binary never takes the adapter branch. Under the standalone (Bun-compiled) pi executable, dynamic import() cannot resolve npm-installed siblings, so the recipe falls back — safely, with the note — even when the package is installed. The optional pattern activates only under Node-based pi installs. A hard static dependency works under both.
  2. Scope and install source matter. Git-installed extensions get their own clone-local node_modules; project-scope and user-scope installs do not see each other's siblings. Those consumers fall back; offer optionalDependencies instead.
  3. Fallback mode is baseline pi, not "repairs minus notes." pi's native validation runs TypeBox Value.Convert first, which silently coerces some invalid input (null"null") instead of repairing or rejecting it. The tool owner must decide explicitly whether that is acceptable.
  4. No double-wrap. The installable pi-repair-layer extension only overrides pi's built-ins; a tool adapted by this recipe is wrapped exactly once in either branch.

Stability contract you may rely on

  • Subpaths (/pi, /core, /grammar) and the adaptToolDefinition(definition, options?) signature are stable for the current major (semver).
  • Absence detection semantics (the two codes + module-naming message) are part of the contract.
  • Unrecognized preprocessor kinds are ignored — never fatal, no mutation, results still schema-validated — so options written against a newer minor degrade gracefully on older installs.

Going further

  • Failing closed: the adapter throws UnrepairableToolInputError with a model-readable retry message by default; unrepairable: "passthrough" exists only for deliberate migrations.
  • Structured outcomes: pass onOutcome(result) for value-free metrics (rule IDs, stages, policy, fingerprint — never argument values).
  • <repair_note> feedback and the RepairLifecycle, plus the pure-core runRepairPipeline, are documented in the package's docs/tool-owner-integration.md.

What ships with it: 4 files

9.5 KB alongside SKILL.md, 1 of them executable

Keep looking

Skills are one crate of 326,758. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.