@mjasnikovs/pi-task 0.14.8 → 0.14.9

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.
@@ -6,7 +6,7 @@ import { docsFocused } from '../workers/docs-core.js';
6
6
  import { fetchFocused } from '../workers/fetch-core.js';
7
7
  import { formatNpmVersionSection } from '../workers/npm-version.js';
8
8
  import { runWorker } from '../workers/pi-worker-core.js';
9
- import { findPhantomImports, formatApiCorrections } from '../workers/phantom-imports.js';
9
+ import { findPhantomImports, formatApiCorrections, rewritePhantomSpecifiers } from '../workers/phantom-imports.js';
10
10
  import { search as defaultSearch } from '../workers/search-core.js';
11
11
  import { extractEnrichTargets } from './enrichment.js';
12
12
  import { getFileInventory } from './file-inventory.js';
@@ -651,7 +651,21 @@ export const PHASES = [
651
651
  name: 'refine',
652
652
  section: 'refined prompt',
653
653
  field: 'refined',
654
- run: (d, p) => phaseRefine(d, p.rawPrompt, p.planContext)
654
+ run: async (d, p) => {
655
+ const refined = await phaseRefine(d, p.rawPrompt, p.planContext);
656
+ // Subtractively strike any phantom runtime specifier (`bun:sql`) the
657
+ // refine carried up verbatim from the spec doc, BEFORE it flows to
658
+ // research/grill/compose. An appended correction alone loses: the
659
+ // affirmative survives into the composed GOAL and on to the implementer
660
+ // (proven: compose re-leaks it 4/4). Rewriting the source so compose has
661
+ // nothing to contradict is the fix. Silent + no-op when nothing is wrong
662
+ // or the runtime's types aren't installed.
663
+ const phantoms = findPhantomImports(refined, d.cwd);
664
+ if (phantoms.length === 0)
665
+ return refined;
666
+ d.logDebug?.(`phantom specifiers rewritten in refined: ${phantoms.map(x => x.spec).join(', ')}`);
667
+ return rewritePhantomSpecifiers(refined, phantoms);
668
+ }
655
669
  },
656
670
  {
657
671
  name: 'research',
@@ -5,6 +5,9 @@ export interface PhantomImport {
5
5
  realModules: string[];
6
6
  /** Corrective guidance to inject into the spec. */
7
7
  suggestion: string;
8
+ /** Base-module symbol the submodule leaf maps to (`bun:sql` → `SQL`), or null
9
+ * when the types declare no matching symbol. Drives the canonical-import rewrite. */
10
+ baseSymbol: string | null;
8
11
  }
9
12
  /** Every distinct runtime-namespace specifier mentioned in the text, in first-seen order. */
10
13
  export declare function extractRuntimeSpecifiers(text: string): string[];
@@ -33,3 +36,19 @@ export declare function loadRuntimeTypeText(runtime: string, cwd: string): strin
33
36
  export declare function findPhantomImports(text: string, cwd: string, loadText?: (runtime: string, cwd: string) => string | null): PhantomImport[];
34
37
  /** Render flagged phantoms as an authoritative research section, or '' if none. */
35
38
  export declare function formatApiCorrections(phantoms: PhantomImport[]): string;
39
+ /**
40
+ * Subtractively rewrite every flagged phantom specifier in `text` to the canonical
41
+ * import the installed types prove, so NO affirmative occurrence of the non-existent
42
+ * specifier survives downstream. This is the half an appended correction can't do:
43
+ * REFINE preserves identifiers verbatim, so `bun:sql` otherwise rides into the
44
+ * composed GOAL (proven: compose re-leaks it 4/4) and on to the implementer. Strike
45
+ * it at the source and compose has nothing to contradict.
46
+ *
47
+ * Deterministic, no LLM. Idempotent on healthy input: once a specifier is replaced
48
+ * the literal is gone, so a second pass is a no-op (the only residue is a `declare
49
+ * module` comment that quotes the spec inside a slash-star, which the bare-word rule
50
+ * skips via its lookbehind). Handles the four forms a phantom takes: an import/require
51
+ * `from "<spec>"`, a fabricated `declare module "<spec>"`, a backticked prose mention
52
+ * `` `<spec>` ``, and a bare word `<spec>`.
53
+ */
54
+ export declare function rewritePhantomSpecifiers(text: string, phantoms: PhantomImport[]): string;
@@ -173,7 +173,8 @@ export function findPhantomImports(text, cwd, loadText = loadRuntimeTypeText) {
173
173
  out.push({
174
174
  spec,
175
175
  realModules: verdict.realModules,
176
- suggestion: suggestionFor(verdict, ns.runtime)
176
+ suggestion: suggestionFor(verdict, ns.runtime),
177
+ baseSymbol: verdict.baseSymbol
177
178
  });
178
179
  }
179
180
  return out;
@@ -185,3 +186,43 @@ export function formatApiCorrections(phantoms) {
185
186
  const lines = phantoms.map(p => ` - ${p.suggestion}`);
186
187
  return `API CORRECTIONS\n${lines.join('\n')}`;
187
188
  }
189
+ /**
190
+ * Subtractively rewrite every flagged phantom specifier in `text` to the canonical
191
+ * import the installed types prove, so NO affirmative occurrence of the non-existent
192
+ * specifier survives downstream. This is the half an appended correction can't do:
193
+ * REFINE preserves identifiers verbatim, so `bun:sql` otherwise rides into the
194
+ * composed GOAL (proven: compose re-leaks it 4/4) and on to the implementer. Strike
195
+ * it at the source and compose has nothing to contradict.
196
+ *
197
+ * Deterministic, no LLM. Idempotent on healthy input: once a specifier is replaced
198
+ * the literal is gone, so a second pass is a no-op (the only residue is a `declare
199
+ * module` comment that quotes the spec inside a slash-star, which the bare-word rule
200
+ * skips via its lookbehind). Handles the four forms a phantom takes: an import/require
201
+ * `from "<spec>"`, a fabricated `declare module "<spec>"`, a backticked prose mention
202
+ * `` `<spec>` ``, and a bare word `<spec>`.
203
+ */
204
+ export function rewritePhantomSpecifiers(text, phantoms) {
205
+ let out = text;
206
+ for (const p of phantoms) {
207
+ const ns = splitRuntimeNamespace(p.spec);
208
+ if (!ns)
209
+ continue;
210
+ const runtime = ns.runtime;
211
+ const canonical = p.baseSymbol ?
212
+ `import { ${p.baseSymbol} } from "${runtime}"`
213
+ : `the "${runtime}" module`;
214
+ const spec = escapeRe(p.spec);
215
+ out = out
216
+ // `from "bun:sql"` / `require("bun:sql")` → point at the base runtime module
217
+ .replace(new RegExp(`((?:from\\s+|require\\(\\s*)["'])${spec}(["'])`, 'g'), `$1${runtime}$2`)
218
+ // a fabricated `declare module "bun:sql"` → a comment naming the real import.
219
+ // Deliberately does NOT echo the spec literal, so the bare-word pass below
220
+ // can't re-corrupt this comment (keeps the rewrite idempotent).
221
+ .replace(new RegExp(`declare\\s+module\\s+["']${spec}["']`, 'g'), `/* not a module — use ${canonical} */`)
222
+ // backticked prose mention → the canonical import
223
+ .replace(new RegExp('`' + spec + '`', 'g'), '`' + canonical + '`')
224
+ // bare word, not already inside quotes/backticks/a path or our own comment
225
+ .replace(new RegExp(`(?<![\`"'/])\\b${spec}\\b(?![\`"'])`, 'g'), canonical);
226
+ }
227
+ return out;
228
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.14.8",
3
+ "version": "0.14.9",
4
4
  "description": "Deterministic spec-orchestration for local models, with a bundled real-time remote web view and web/docs/fetch/worker subagent tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",