@mjasnikovs/pi-task 0.14.8 → 0.14.10
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.
|
@@ -17,6 +17,7 @@ import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath, tasks
|
|
|
17
17
|
import { gitCommitAll } from './auto-commit.js';
|
|
18
18
|
import { runGuidelineEnforcement, classifyEnforceChildFailure } from './enforce-guidelines.js';
|
|
19
19
|
import { runWorker } from '../workers/pi-worker-core.js';
|
|
20
|
+
import { findPhantomImports, rewritePhantomSpecifiers } from '../workers/phantom-imports.js';
|
|
20
21
|
import { runPhaseChild, prependHint, formatLoopHint, USER_CANCELLED } from './child-runner.js';
|
|
21
22
|
import { SessionUI, registerBridgeCommand } from '../remote/bridge.js';
|
|
22
23
|
import { pushNotify } from '../remote/push.js';
|
|
@@ -42,6 +43,19 @@ const MENTION_TRAILING_PUNCT = /[.,;:!?)\]}>"']+$/;
|
|
|
42
43
|
function mentionPath(token) {
|
|
43
44
|
return token.replace(MENTION_TRAILING_PUNCT, '');
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Fire-and-forget debug line for the PLAN phase (clarify/decompose), which runs
|
|
48
|
+
* before any task file — hence any per-task `TASK_XXXX-debug.log` — exists. Writes
|
|
49
|
+
* to `.pi-tasks/plan-debug.log`; the `*-debug.log` suffix keeps it grep-compatible
|
|
50
|
+
* with the per-task logs. Never throws (mkdir + append are best-effort).
|
|
51
|
+
*/
|
|
52
|
+
function logPlanDebug(cwd, msg) {
|
|
53
|
+
const line = `${new Date().toISOString()} ${msg}\n`;
|
|
54
|
+
const dir = tasksDir(cwd);
|
|
55
|
+
fsp.mkdir(dir, { recursive: true })
|
|
56
|
+
.then(() => fsp.appendFile(path.join(dir, 'plan-debug.log'), line))
|
|
57
|
+
.catch(() => { });
|
|
58
|
+
}
|
|
45
59
|
/**
|
|
46
60
|
* Expand any @file references in the feature text by appending each referenced
|
|
47
61
|
* file's contents, so the planning children (clarify, decompose) always see the
|
|
@@ -180,7 +194,22 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
180
194
|
const ui = new SessionUI(ctx);
|
|
181
195
|
// Inline any @file spec the user referenced so clarify/decompose reason over
|
|
182
196
|
// the real content, not a one-line "Implement @file" that reads as trivial.
|
|
183
|
-
const
|
|
197
|
+
const rawFeatureForModel = await expandFeatureMentions(cwd, feature);
|
|
198
|
+
// Strike phantom runtime specifiers (`bun:sql`) out of the inlined spec BEFORE
|
|
199
|
+
// clarify/decompose ever see it. Layer A only rewrites the per-task `refined`
|
|
200
|
+
// text — which is DOWNSTREAM of here: clarify is the first phase and runs on
|
|
201
|
+
// this raw inline, so the doc's affirmative `bun:sql` is parroted straight into
|
|
202
|
+
// the very first clarifying question ("instantly bun:sql is back"). Apply the
|
|
203
|
+
// same deterministic, no-LLM strike at the single point that feeds both planning
|
|
204
|
+
// children. Silent + no-op when nothing is flagged or the runtime's types aren't
|
|
205
|
+
// installed.
|
|
206
|
+
const planPhantoms = findPhantomImports(rawFeatureForModel, cwd);
|
|
207
|
+
const featureForModel = planPhantoms.length === 0 ?
|
|
208
|
+
rawFeatureForModel
|
|
209
|
+
: rewritePhantomSpecifiers(rawFeatureForModel, planPhantoms);
|
|
210
|
+
if (planPhantoms.length > 0) {
|
|
211
|
+
logPlanDebug(cwd, `phantom specifiers rewritten in plan spec: ${planPhantoms.map(x => x.spec).join(', ')}`);
|
|
212
|
+
}
|
|
184
213
|
const answers = [];
|
|
185
214
|
// Plain text of every question already shown, for the duplicate backstop.
|
|
186
215
|
const askedQuestions = [];
|
package/dist/task/phases.js
CHANGED
|
@@ -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) =>
|
|
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.
|
|
3
|
+
"version": "0.14.10",
|
|
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",
|