@r3b1s/pi-repair-layer 0.1.0 → 0.2.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
@@ -41,12 +41,17 @@ Plain-English first, precise meaning after — for readers new to LLM tool-calli
41
41
  ## Install
42
42
 
43
43
  ```bash
44
- ln -s /home/dev/Local/personal/pi-commandcode-conundrum ~/.pi/agent/extensions/pi-repair-layer
44
+ pi install npm:@r3b1s/pi-repair-layer
45
45
  ```
46
46
 
47
- pi auto-discovers `~/.pi/agent/extensions/*/index.ts`. Remove the symlink to
48
- uninstall. pi's TUI will show a one-time warning that built-in tools were
49
- overridden — that is this extension working as intended.
47
+ Or from git:
48
+
49
+ ```bash
50
+ pi install git:github.com/r3b1s/pi-repair-layer
51
+ ```
52
+
53
+ pi's TUI will show a one-time warning that built-in tools were overridden —
54
+ that is this extension working as intended.
50
55
 
51
56
  At runtime pi's extension loader aliases `@earendil-works/pi-coding-agent` and
52
57
  `typebox` to the running instance's own modules, so the extension always binds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r3b1s/pi-repair-layer",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",
@@ -35,8 +35,8 @@
35
35
  },
36
36
  "devDependencies": {
37
37
  "@biomejs/biome": "2.4.8",
38
- "@earendil-works/pi-ai": "^0.80.3",
39
- "@earendil-works/pi-coding-agent": "0.80.3",
38
+ "@earendil-works/pi-ai": "^0.80.6",
39
+ "@earendil-works/pi-coding-agent": "0.80.6",
40
40
  "@types/node": "^24.0.0",
41
41
  "eslint": "^9.25.1",
42
42
  "typescript": "^5.9.3",
@@ -292,6 +292,14 @@ const wrapBareStringAsArray: IssueRule = (site, toolName) => {
292
292
  return `Wrapped your bare string in a single-element array for \`${String(key)}\` in tool "${toolName}". Send an array (e.g. \`["foo"]\`) next time, not a single string.`;
293
293
  };
294
294
 
295
+ /**
296
+ * Stage 5 re-collects issue sites after each pass that fired a rule, so
297
+ * repairs that reveal nested problems (parse a stringified array, then rename
298
+ * aliased fields inside it) converge instead of failing. Three passes cover
299
+ * every known two-step combination with one pass of headroom.
300
+ */
301
+ const MAX_ISSUE_PASSES = 3;
302
+
295
303
  // Order matters: parse before wrap, rename before drop.
296
304
  const ISSUE_RULES: readonly [string, IssueRule][] = [
297
305
  ["renameAliasedField", renameAliasedField],
@@ -456,16 +464,28 @@ export function repairToolInput(options: {
456
464
  }
457
465
 
458
466
  // Stage 5: per-issue repairs at the strict validator's failure sites.
459
- if (!schemaAccepts(schema, current)) {
467
+ // Iterates because one repair can expose sites the first collection couldn't
468
+ // see — e.g. parsing a stringified `edits` array surfaces aliased fields
469
+ // inside the parsed elements. Stops when the value validates or a full pass
470
+ // fires nothing (each firing rule mutates `current`, so a firing pass always
471
+ // makes progress); the cap only bounds pathological rule interactions.
472
+ for (
473
+ let pass = 0;
474
+ pass < MAX_ISSUE_PASSES && !schemaAccepts(schema, current);
475
+ pass++
476
+ ) {
477
+ let repairedThisPass = false;
460
478
  for (const site of collectIssueSites(schema, current)) {
461
479
  for (const [name, rule] of ISSUE_RULES) {
462
480
  const note = rule(site, toolName, config);
463
481
  if (note !== false) {
464
482
  fire(name, note);
483
+ repairedThisPass = true;
465
484
  break;
466
485
  }
467
486
  }
468
487
  }
488
+ if (!repairedThisPass) break;
469
489
  }
470
490
 
471
491
  // Stage 6: final verdict, now through pi's own pipeline (Convert, then