@mjasnikovs/pi-task 0.14.9 → 0.14.11
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 = [];
|
|
@@ -57,6 +57,16 @@ export declare class TaskRunner {
|
|
|
57
57
|
* finally block's call. */
|
|
58
58
|
private _disposeWidget;
|
|
59
59
|
private _deliverSpec;
|
|
60
|
+
/**
|
|
61
|
+
* The spec as the implementer should receive it (Layer B). Layer A strips phantom
|
|
62
|
+
* specifiers from the upstream pipeline text, but a residual affirmative can survive
|
|
63
|
+
* into the composed spec, or arrive when pi expands an `@design.md` the spec
|
|
64
|
+
* references — and the implementer is told that doc is authoritative. Prepend a
|
|
65
|
+
* VERIFIED API OVERRIDES banner that outranks the spec for any specifier the
|
|
66
|
+
* deterministic check proves does not exist. No-op (returns the spec unchanged) when
|
|
67
|
+
* nothing is flagged or the runtime's types aren't installed.
|
|
68
|
+
*/
|
|
69
|
+
private _specForDelivery;
|
|
60
70
|
}
|
|
61
71
|
export interface RunSingleTaskOptions {
|
|
62
72
|
/** Await the session going idle after the spec is delivered, so the caller
|
|
@@ -26,6 +26,7 @@ import { startWidget } from './widget.js';
|
|
|
26
26
|
import { publishViewer, publishNotify, registerBridgeCommand, getBridge } from '../remote/bridge.js';
|
|
27
27
|
import { pushNotify } from '../remote/push.js';
|
|
28
28
|
import { parseVerifyBlock } from './spec-validation.js';
|
|
29
|
+
import { findDeliveryPhantoms, formatApiOverrideBanner } from '../workers/phantom-imports.js';
|
|
29
30
|
import { titleForDisplay } from './parsers.js';
|
|
30
31
|
import { formatTimings } from './timings.js';
|
|
31
32
|
import { getParentContextWindow, resolveContextUsage } from './context-usage.js';
|
|
@@ -261,20 +262,38 @@ export class TaskRunner {
|
|
|
261
262
|
this._stopWidget = null;
|
|
262
263
|
}
|
|
263
264
|
async _deliverSpec(ctx) {
|
|
265
|
+
const spec = this._specForDelivery();
|
|
264
266
|
if (this._sendSpec) {
|
|
265
|
-
await this._sendSpec(
|
|
267
|
+
await this._sendSpec(spec);
|
|
266
268
|
return;
|
|
267
269
|
}
|
|
268
270
|
if (!piApi) {
|
|
269
271
|
throw new Error('extension not initialised (no ExtensionAPI captured)');
|
|
270
272
|
}
|
|
271
273
|
if (ctx.isIdle()) {
|
|
272
|
-
piApi.sendUserMessage(
|
|
274
|
+
piApi.sendUserMessage(spec);
|
|
273
275
|
}
|
|
274
276
|
else {
|
|
275
|
-
piApi.sendUserMessage(
|
|
277
|
+
piApi.sendUserMessage(spec, { deliverAs: 'followUp' });
|
|
276
278
|
}
|
|
277
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* The spec as the implementer should receive it (Layer B). Layer A strips phantom
|
|
282
|
+
* specifiers from the upstream pipeline text, but a residual affirmative can survive
|
|
283
|
+
* into the composed spec, or arrive when pi expands an `@design.md` the spec
|
|
284
|
+
* references — and the implementer is told that doc is authoritative. Prepend a
|
|
285
|
+
* VERIFIED API OVERRIDES banner that outranks the spec for any specifier the
|
|
286
|
+
* deterministic check proves does not exist. No-op (returns the spec unchanged) when
|
|
287
|
+
* nothing is flagged or the runtime's types aren't installed.
|
|
288
|
+
*/
|
|
289
|
+
_specForDelivery() {
|
|
290
|
+
const phantoms = findDeliveryPhantoms(this._pc.spec, this._cwd);
|
|
291
|
+
const banner = formatApiOverrideBanner(phantoms);
|
|
292
|
+
if (!banner)
|
|
293
|
+
return this._pc.spec;
|
|
294
|
+
this._deps.logDebug?.(`impl-handoff API override banner prepended for: ${phantoms.map(p => p.spec).join(', ')}`);
|
|
295
|
+
return `${banner}\n\n${this._pc.spec}`;
|
|
296
|
+
}
|
|
278
297
|
}
|
|
279
298
|
/** Dialog copy for the post-interrupt steering prompt. */
|
|
280
299
|
const STEER_TITLE = 'Paused — steer the model';
|
|
@@ -36,6 +36,25 @@ export declare function loadRuntimeTypeText(runtime: string, cwd: string): strin
|
|
|
36
36
|
export declare function findPhantomImports(text: string, cwd: string, loadText?: (runtime: string, cwd: string) => string | null): PhantomImport[];
|
|
37
37
|
/** Render flagged phantoms as an authoritative research section, or '' if none. */
|
|
38
38
|
export declare function formatApiCorrections(phantoms: PhantomImport[]): string;
|
|
39
|
+
/**
|
|
40
|
+
* A top-of-handoff override banner (Layer B). The deterministic check has proven —
|
|
41
|
+
* against the installed type definitions — that the spec, or a design doc it
|
|
42
|
+
* references, names an API that does NOT exist. The implementer is told the spec/doc
|
|
43
|
+
* is authoritative, so a residual affirmative that survived into the composed spec, or
|
|
44
|
+
* one re-injected when pi expands an `@design.md` the spec references, can still push it
|
|
45
|
+
* to write `bun:sql` / a `declare module` shim. This banner sits ABOVE the spec and is
|
|
46
|
+
* declared to outrank it for these specifiers only. Empty when nothing is flagged.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatApiOverrideBanner(phantoms: PhantomImport[]): string;
|
|
49
|
+
/**
|
|
50
|
+
* The phantoms the IMPLEMENTER would actually encounter: those named in the delivered
|
|
51
|
+
* spec text itself, PLUS those in any @-file the spec references (pi expands that
|
|
52
|
+
* mention at send time, re-injecting the doc's affirmative). Concatenates the spec with
|
|
53
|
+
* each readable referenced doc and runs the standard deterministic check over the whole.
|
|
54
|
+
* Unreadable mentions are skipped; silent when types are absent or nothing is flagged.
|
|
55
|
+
* Drives the impl-handoff override banner (Layer B).
|
|
56
|
+
*/
|
|
57
|
+
export declare function findDeliveryPhantoms(spec: string, cwd: string): PhantomImport[];
|
|
39
58
|
/**
|
|
40
59
|
* Subtractively rewrite every flagged phantom specifier in `text` to the canonical
|
|
41
60
|
* import the installed types prove, so NO affirmative occurrence of the non-existent
|
|
@@ -186,6 +186,56 @@ export function formatApiCorrections(phantoms) {
|
|
|
186
186
|
const lines = phantoms.map(p => ` - ${p.suggestion}`);
|
|
187
187
|
return `API CORRECTIONS\n${lines.join('\n')}`;
|
|
188
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* A top-of-handoff override banner (Layer B). The deterministic check has proven —
|
|
191
|
+
* against the installed type definitions — that the spec, or a design doc it
|
|
192
|
+
* references, names an API that does NOT exist. The implementer is told the spec/doc
|
|
193
|
+
* is authoritative, so a residual affirmative that survived into the composed spec, or
|
|
194
|
+
* one re-injected when pi expands an `@design.md` the spec references, can still push it
|
|
195
|
+
* to write `bun:sql` / a `declare module` shim. This banner sits ABOVE the spec and is
|
|
196
|
+
* declared to outrank it for these specifiers only. Empty when nothing is flagged.
|
|
197
|
+
*/
|
|
198
|
+
export function formatApiOverrideBanner(phantoms) {
|
|
199
|
+
if (phantoms.length === 0)
|
|
200
|
+
return '';
|
|
201
|
+
const lines = phantoms.map(p => ` - ${p.suggestion}`);
|
|
202
|
+
return ('VERIFIED API OVERRIDES — checked against the installed type definitions. These '
|
|
203
|
+
+ 'SUPERSEDE the spec below and any design/spec document it references: the doc is '
|
|
204
|
+
+ 'WRONG about these specifiers, so do NOT follow it here.\n'
|
|
205
|
+
+ lines.join('\n')
|
|
206
|
+
+ '\nUse the corrected import shown for each. Never write the phantom specifier, '
|
|
207
|
+
+ 'and never add a `declare module` shim to make it resolve.');
|
|
208
|
+
}
|
|
209
|
+
// An @-file mention in a spec ("@DESIGN/foo.md", path until whitespace) minus the
|
|
210
|
+
// trailing prose punctuation a sentence adds. Mirrors auto-orchestrator's mention
|
|
211
|
+
// rules so we read the same docs pi re-expands when delivering the spec.
|
|
212
|
+
const MENTION_RE = /(?:^|\s)@([^\s]+)/g;
|
|
213
|
+
const MENTION_TRAILING_PUNCT = /[.,;:!?)\]}>"']+$/;
|
|
214
|
+
/**
|
|
215
|
+
* The phantoms the IMPLEMENTER would actually encounter: those named in the delivered
|
|
216
|
+
* spec text itself, PLUS those in any @-file the spec references (pi expands that
|
|
217
|
+
* mention at send time, re-injecting the doc's affirmative). Concatenates the spec with
|
|
218
|
+
* each readable referenced doc and runs the standard deterministic check over the whole.
|
|
219
|
+
* Unreadable mentions are skipped; silent when types are absent or nothing is flagged.
|
|
220
|
+
* Drives the impl-handoff override banner (Layer B).
|
|
221
|
+
*/
|
|
222
|
+
export function findDeliveryPhantoms(spec, cwd) {
|
|
223
|
+
let text = spec;
|
|
224
|
+
const seen = new Set();
|
|
225
|
+
for (const m of spec.matchAll(MENTION_RE)) {
|
|
226
|
+
const rel = m[1].replace(MENTION_TRAILING_PUNCT, '');
|
|
227
|
+
if (rel === '' || seen.has(rel))
|
|
228
|
+
continue;
|
|
229
|
+
seen.add(rel);
|
|
230
|
+
try {
|
|
231
|
+
text += '\n' + fs.readFileSync(path.resolve(cwd, rel), 'utf8');
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// not a readable file — leave the @token, skip it
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return findPhantomImports(text, cwd);
|
|
238
|
+
}
|
|
189
239
|
/**
|
|
190
240
|
* Subtractively rewrite every flagged phantom specifier in `text` to the canonical
|
|
191
241
|
* import the installed types prove, so NO affirmative occurrence of the non-existent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.11",
|
|
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",
|