@mjasnikovs/pi-task 0.18.49 → 0.18.51
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/dist/remote/register.js +4 -2
- package/dist/remote/server.d.ts +14 -0
- package/dist/remote/server.js +61 -18
- package/dist/shared/child-output.d.ts +21 -7
- package/dist/shared/child-output.js +11 -0
- package/dist/task/apis-contract.d.ts +67 -0
- package/dist/task/apis-contract.js +77 -0
- package/dist/task/context-attribution.d.ts +123 -0
- package/dist/task/context-attribution.js +285 -0
- package/dist/task/context-silence.d.ts +53 -0
- package/dist/task/context-silence.js +93 -0
- package/dist/task/phases.js +187 -5
- package/dist/task/prompts.js +13 -0
- package/dist/task/spec-urls.d.ts +117 -0
- package/dist/task/spec-urls.js +214 -0
- package/dist/task/type-only-answer.d.ts +95 -0
- package/dist/task/type-only-answer.js +249 -0
- package/dist/workers/fetch-core.d.ts +50 -0
- package/dist/workers/fetch-core.js +118 -16
- package/dist/workers/pi-worker-core.d.ts +11 -0
- package/dist/workers/pi-worker-core.js +30 -0
- package/dist/workers/pi-worker-docs.d.ts +9 -0
- package/dist/workers/pi-worker-docs.js +119 -7
- package/dist/workers/pi-worker-fetch.js +3 -1
- package/dist/workers/typeonly-log.d.ts +45 -0
- package/dist/workers/typeonly-log.js +88 -0
- package/package.json +1 -1
package/dist/task/phases.js
CHANGED
|
@@ -12,6 +12,8 @@ import { search as defaultSearch } from '../workers/search-core.js';
|
|
|
12
12
|
import { extractEnrichTargets } from './enrichment.js';
|
|
13
13
|
import { isIntegrationUnknown } from './unknown-routing.js';
|
|
14
14
|
import { extractUserDirectives, preserveDirectivesBlock, enforceDirectives } from './user-directives.js';
|
|
15
|
+
import { demoteUnsourcedAttributions } from './context-attribution.js';
|
|
16
|
+
import { classifyContextSilence, countBullets } from './context-silence.js';
|
|
15
17
|
import { getFileInventory } from './file-inventory.js';
|
|
16
18
|
import { buildOrientation, orientationTier } from './orientation.js';
|
|
17
19
|
import { getConfig } from '../config/config.js';
|
|
@@ -328,6 +330,54 @@ export function degradedSectionBody(name, reason, partial) {
|
|
|
328
330
|
const body = partial.trim();
|
|
329
331
|
return body.length > 0 ? `${marker}\n\n${body}` : marker;
|
|
330
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Dependency names declared by the project manifest, used by the CONTEXT post-check to
|
|
335
|
+
* tell "this bullet is about an external library" from "this bullet is about our source".
|
|
336
|
+
* A missing or malformed package.json yields none, which makes the post-check a no-op
|
|
337
|
+
* rather than an error — a non-node project must still be able to run research.
|
|
338
|
+
*/
|
|
339
|
+
async function manifestDependencyNames(cwd) {
|
|
340
|
+
try {
|
|
341
|
+
const raw = await readFile(resolve(cwd, 'package.json'), 'utf8');
|
|
342
|
+
const pkg = JSON.parse(raw);
|
|
343
|
+
return [...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})];
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Prepended to worker:apis's prompt on the ONE retry the zero-retrieval gate triggers. It
|
|
351
|
+
* names the exact failure (a section written with no retrieval) so the correction is concrete,
|
|
352
|
+
* and bounds the retrieval to the symbols about to be listed — a broad "read everything" here
|
|
353
|
+
* would trade the memory-written section for the 37-read near-runaway at phases.ts's read tail.
|
|
354
|
+
*/
|
|
355
|
+
const APIS_ZERO_RETRIEVAL_PREAMBLE = 'STOP. Your previous attempt at this task wrote a complete APIS section without calling a '
|
|
356
|
+
+ 'single retrieval tool — so every signature, type, and command in it was recalled from '
|
|
357
|
+
+ 'memory, unverified, and must not be trusted. This time you MUST verify before you write: '
|
|
358
|
+
+ 'call `pi-worker-docs` for each third-party package and each project symbol you are about '
|
|
359
|
+
+ 'to list (or `read`/`grep` the project source for project symbols), and write each entry '
|
|
360
|
+
+ 'from what the tool actually returned, not from memory. Look up only the symbols you will '
|
|
361
|
+
+ 'list — no more, no less; do not read the whole tree.';
|
|
362
|
+
/**
|
|
363
|
+
* Prepended to worker:context's prompt on the ONE retry the silent-retry gate triggers. The
|
|
364
|
+
* previous attempt produced ZERO bullets — STEP 0 (context-silence.ts) showed every such rep
|
|
365
|
+
* across 48 live reps was a genuine loss (a loop-degrade or a hallucinated non-bullet
|
|
366
|
+
* fragment), never a legitimate empty answer, because the same tree reliably yields 11–21
|
|
367
|
+
* bullets. So this names that failure and steers away from the two shapes that caused it:
|
|
368
|
+
* the repeated-grep thrash that trips the loop-killer, and emitting anything that is not a
|
|
369
|
+
* bullet. It does NOT loosen the sourced-bullet invariant — it explicitly repeats that
|
|
370
|
+
* external-API semantics stay open questions unless quoted.
|
|
371
|
+
*/
|
|
372
|
+
const CONTEXT_SILENT_RETRY_PREAMBLE = 'STOP. Your previous attempt at this task produced ZERO usable bullets — either it thrashed '
|
|
373
|
+
+ 'the same search until it was killed, or it emitted text that was not a bullet list. That is '
|
|
374
|
+
+ 'a dropped section, not an empty one: this repository has architecture worth surfacing. This '
|
|
375
|
+
+ 'time, read a few key files (package.json, the entry point, the directory the task names), do '
|
|
376
|
+
+ 'NOT repeat an identical grep — if a search returns nothing, move on rather than retrying it, '
|
|
377
|
+
+ 'and once you have read enough, WRITE the bullet list and stop. Output ONLY `- <bullet>` lines, '
|
|
378
|
+
+ 'nothing else. Keep the same rules as before: state an external library/API behaviour as fact '
|
|
379
|
+
+ 'ONLY when quoting an EXTERNAL CONTEXT block; otherwise write it as an "unverified:" open '
|
|
380
|
+
+ 'question. One claim per bullet. Better to emit three sharp sourced bullets than to say nothing.';
|
|
331
381
|
export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
332
382
|
const fileInventoryFn = researchDeps.getFileInventory ?? getFileInventory;
|
|
333
383
|
const externalContext = await gatherExternalContext(refined, deps, researchDeps);
|
|
@@ -370,6 +420,18 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
370
420
|
deps.logDebug?.(`orientation: pre-supplied ${orientation.supplied.size} core files`);
|
|
371
421
|
}
|
|
372
422
|
const promptHeader = externalContext + inventoryHeader;
|
|
423
|
+
// Braces for the CONTEXT worker's LIVE-DATA RULE (the belt is the prompt itself).
|
|
424
|
+
// Judged against the EXTERNAL CONTEXT this run actually gathered — the same string
|
|
425
|
+
// the worker is handed below — and the manifest's dependency names.
|
|
426
|
+
const manifestPackages = await manifestDependencyNames(deps.cwd);
|
|
427
|
+
// PROMPT 4's spec-cited-URL lever is NOT WIRED HERE. It is built and unit-tested in
|
|
428
|
+
// ./spec-urls.ts and its live A/B FAILED: baseline 2/20 vs treatment 3/20, Fisher
|
|
429
|
+
// one-tailed p = 0.50, over two fixtures, with delivery into this very prompt proven
|
|
430
|
+
// separately (scripts/spec-url-prompt-delivery-check.ts). Shipping it anyway would put
|
|
431
|
+
// ~1000 characters of prefill into every APIS prompt for no measured benefit, which is
|
|
432
|
+
// the pattern nexxtasks exists to prevent. To re-run the experiment, restore the block
|
|
433
|
+
// this comment replaces — see the git history of this file and the PROMPT 4 entry in
|
|
434
|
+
// nexxtasks.txt RESULTS.
|
|
373
435
|
let doneCount = 0;
|
|
374
436
|
const updateProgress = () => {
|
|
375
437
|
doneCount++;
|
|
@@ -433,7 +495,30 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
433
495
|
extensions: [
|
|
434
496
|
DOCS_EXTENSION_PATH,
|
|
435
497
|
...(searchConfigured() ? [SEARCH_EXTENSION_PATH] : [])
|
|
436
|
-
]
|
|
498
|
+
],
|
|
499
|
+
// ZERO-RETRIEVAL GATE (mx5 run-15 F-1, distinct from the STAGE 1-3 stopping-point
|
|
500
|
+
// thread). In a MINORITY of reps worker:apis emits a complete, plausible APIS section
|
|
501
|
+
// having made ZERO retrieval tool calls — the whole thing recalled from memory.
|
|
502
|
+
// The output contract at RESEARCH_APIS_PROMPT already INSTRUCTS tool use and the
|
|
503
|
+
// worker skips it anyway (STAGE 2 proved a prompt line does not move grounding), so
|
|
504
|
+
// this is a deterministic gate, not another instruction: groundingRetrievalCount === 0
|
|
505
|
+
// on a non-empty section is ungrounded BY CONSTRUCTION — no semantic judgement, the
|
|
506
|
+
// exact checkable handle STAGE 3's prose-clause gate lacked. The forced-retrieval
|
|
507
|
+
// retry recovers a grounded section rather than silencing the worker (entry count
|
|
508
|
+
// preserved). It bounds itself ("look up the symbols you will list — no more") away
|
|
509
|
+
// from the near-runaway 37-read tail.
|
|
510
|
+
//
|
|
511
|
+
// EFFICACY NOT DEMONSTRATED — read before trusting this to matter. The live A/B
|
|
512
|
+
// (scripts/live-apis-zero-retrieval-ab.ts) ABSTAINED, underpowered: the failure is
|
|
513
|
+
// RARE and intermittent (base rate 0/40 one session, pooled ~5%), so the primary
|
|
514
|
+
// reduction (baseline 1/40 zero-retrieval ships -> treatment 0/40) did NOT reach
|
|
515
|
+
// significance (Fisher p = 0.50 — needs ~5 baseline ships, ~85 reps at ~6%). What IS
|
|
516
|
+
// established: the gate is correct BY CONSTRUCTION; on the 3 firings observed it
|
|
517
|
+
// recovered a grounded section every time; and it did NO harm (ungrounded-symbol rate
|
|
518
|
+
// went DOWN not up, no entry collapse, no runaway, cost +4-6%). It is wired as a
|
|
519
|
+
// harmless safety net, NOT a proven-effective lever — do not cite it as a measured win
|
|
520
|
+
// (nexxtasks.txt "ZERO-RETRIEVAL GATE ... ABSTAIN"). A powered A/B is STILL OPEN.
|
|
521
|
+
zeroRetrievalRetry: APIS_ZERO_RETRIEVAL_PREAMBLE
|
|
437
522
|
},
|
|
438
523
|
{
|
|
439
524
|
section: 'CONTEXT',
|
|
@@ -443,7 +528,40 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
443
528
|
// FILES handles that. Dropping `find`/`ls` keeps the worker from
|
|
444
529
|
// spawning long enumeration loops whose output then inflates
|
|
445
530
|
// prefill on every subsequent round.
|
|
446
|
-
|
|
531
|
+
//
|
|
532
|
+
// RECORDED DECISION (mx5 run-15 F-1, PROMPT 1 item 4): this worker stays
|
|
533
|
+
// ISOLATED — it is NOT given the APIS worker's output. It keeps `read,grep`
|
|
534
|
+
// and is forbidden, by prompt and by the post-check below, from asserting
|
|
535
|
+
// external-API behaviour it cannot see. Handing it the APIS section would
|
|
536
|
+
// widen what it may assert without making any of it checkable here, and
|
|
537
|
+
// APIS' own answers are the ones F-2 shows are type-only and unverified.
|
|
538
|
+
tools: 'read,grep',
|
|
539
|
+
// SILENT-RETRY GATE. STEP 0 measured this worker silent (zero bullets) in ~10%
|
|
540
|
+
// of live reps (5/48, Wilson95 [4.5%, 22.2%]) and classified EVERY silent rep as
|
|
541
|
+
// a genuine loss — a loop-degrade banner (60%) or a hallucinated non-bullet
|
|
542
|
+
// fragment (40%) — never a legitimate empty answer, because the identical fixture
|
|
543
|
+
// reliably yields 11–21 bullets. A silent section is therefore a dropped section;
|
|
544
|
+
// retry once, keep the retry only if it emits bullets. See context-silence.ts.
|
|
545
|
+
retryIfSilent: CONTEXT_SILENT_RETRY_PREAMBLE,
|
|
546
|
+
// BRACES for the LIVE-DATA RULE. In run 15 this worker wrote, verbatim, "The
|
|
547
|
+
// `hono` dependency is pinned at `^4.12.31` in package.json, and the external
|
|
548
|
+
// context confirms `hc<AppType>` pattern with base URL `/api` ... works
|
|
549
|
+
// correctly (per Hono RPC docs LIVE data)". It has read+grep only, so the
|
|
550
|
+
// base-URL half came from memory; fused with the true version half under one
|
|
551
|
+
// attribution it read as sourced, became a hard requirement in TASK_0027's
|
|
552
|
+
// CONSTRAINTS and ACCEPTANCE, and every request went to /api/api/... ⇒ 404.
|
|
553
|
+
// A flagged bullet is demoted to an OPEN QUESTION here — before the section is
|
|
554
|
+
// persisted — so it cannot reach compose as fact. Demotion, not deletion: the
|
|
555
|
+
// bullet count is preserved, because a silenced worker is a different
|
|
556
|
+
// regression.
|
|
557
|
+
postProcess: text => {
|
|
558
|
+
const r = demoteUnsourcedAttributions(text, externalContext, manifestPackages);
|
|
559
|
+
for (const f of r.demoted) {
|
|
560
|
+
deps.logDebug?.(`worker:context: demoted unsourced attribution [${f.unsourced.join(',')}]`
|
|
561
|
+
+ ` cue="${f.cue}" — ${f.bullet.slice(0, 160)}`);
|
|
562
|
+
}
|
|
563
|
+
return r.text;
|
|
564
|
+
}
|
|
447
565
|
},
|
|
448
566
|
{
|
|
449
567
|
section: 'TOOLING',
|
|
@@ -484,8 +602,9 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
484
602
|
return { name: spec.section, text: cached.trim() };
|
|
485
603
|
}
|
|
486
604
|
deps.logDebug?.(`${spec.label}: start`);
|
|
487
|
-
const
|
|
488
|
-
|
|
605
|
+
const basePrompt = typeof spec.prompt === 'function' ? spec.prompt(prior) : spec.prompt;
|
|
606
|
+
const runOnce = (extraPreamble) => recordWorker(spec.label, runWorker({
|
|
607
|
+
prompt: extraPreamble ? `${extraPreamble}\n\n${basePrompt}` : basePrompt,
|
|
489
608
|
cwd: deps.cwd,
|
|
490
609
|
signal: deps.signal,
|
|
491
610
|
spawn: deps.spawn,
|
|
@@ -496,6 +615,65 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
496
615
|
deps.onChildOutput?.(`${spec.label}: ${line}`);
|
|
497
616
|
}
|
|
498
617
|
}));
|
|
618
|
+
let r = await runOnce();
|
|
619
|
+
// ZERO-RETRIEVAL GATE — a deterministic handle, not another instruction. A non-empty
|
|
620
|
+
// section produced with no grounding-retrieval call was written from memory; retry ONCE
|
|
621
|
+
// with a forced retrieval-first pass and keep the retry only if it actually retrieved.
|
|
622
|
+
if (spec.zeroRetrievalRetry
|
|
623
|
+
&& r.groundingRetrievalCount === 0
|
|
624
|
+
&& r.text.trim().length > 0) {
|
|
625
|
+
deps.logDebug?.(`${spec.label}: ZERO grounding-retrieval on a non-empty section`
|
|
626
|
+
+ ' — every symbol is unverified memory; re-running once with a forced'
|
|
627
|
+
+ ' retrieval-first pass');
|
|
628
|
+
deps.onChildOutput?.(`${spec.label}: zero-retrieval — retrying with forced retrieval`);
|
|
629
|
+
const retry = await runOnce(spec.zeroRetrievalRetry);
|
|
630
|
+
if (retry.groundingRetrievalCount > 0 && retry.text.trim().length > 0) {
|
|
631
|
+
deps.logDebug?.(`${spec.label}: retry grounded (${retry.groundingRetrievalCount} retrieval`
|
|
632
|
+
+ ' calls) — replacing the memory-written section');
|
|
633
|
+
r = retry;
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
deps.logDebug?.(`${spec.label}: retry STILL zero-retrieval`
|
|
637
|
+
+ ` (calls=${retry.groundingRetrievalCount}, len=${retry.text.trim().length})`
|
|
638
|
+
+ ' — keeping the original (no regression, entry count preserved)');
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
// SILENT-RETRY GATE — a deterministic handle over the section body, not another
|
|
642
|
+
// instruction. A section that parses to ZERO bullets from a loop-degrade banner or a
|
|
643
|
+
// hallucinated non-bullet fragment (classifyContextSilence → genuineLoss) dropped
|
|
644
|
+
// context that was there to surface; retry ONCE with a forced-emit preamble and keep
|
|
645
|
+
// the retry only if it produces bullets. A legitimately-empty section (an honest
|
|
646
|
+
// "nothing to surface") and a fatal failure are BOTH left alone — the former is not a
|
|
647
|
+
// loss, the latter throws below and must stay a loud failure, not a silent retry.
|
|
648
|
+
const silentBodyOf = (res) => {
|
|
649
|
+
const f = classifyResearchWorker(spec.section, res);
|
|
650
|
+
if (f?.kind === 'fatal')
|
|
651
|
+
return null;
|
|
652
|
+
return f?.kind === 'runaway' ?
|
|
653
|
+
degradedSectionBody(spec.section, f.reason, res.text)
|
|
654
|
+
: res.text.trim();
|
|
655
|
+
};
|
|
656
|
+
if (spec.retryIfSilent) {
|
|
657
|
+
const body = silentBodyOf(r);
|
|
658
|
+
const verdict = body === null ? null : classifyContextSilence(body);
|
|
659
|
+
if (verdict?.silent && verdict.genuineLoss) {
|
|
660
|
+
deps.logDebug?.(`${spec.label}: silent-retry first-silent cause=${verdict.cause}`
|
|
661
|
+
+ ` — zero bullets, re-running once with a forced-emit preamble`);
|
|
662
|
+
deps.onChildOutput?.(`${spec.label}: silent — retrying`);
|
|
663
|
+
const retry = await runOnce(spec.retryIfSilent);
|
|
664
|
+
const retryBody = silentBodyOf(retry);
|
|
665
|
+
const retryBullets = retryBody === null ? 0 : countBullets(retryBody);
|
|
666
|
+
if (retryBullets > 0) {
|
|
667
|
+
deps.logDebug?.(`${spec.label}: silent-retry recovered bullets=${retryBullets}`
|
|
668
|
+
+ ' — replacing the silent section');
|
|
669
|
+
r = retry;
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
deps.logDebug?.(`${spec.label}: silent-retry still-silent`
|
|
673
|
+
+ ` (bullets=${retryBullets}) — keeping the original`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
499
677
|
deps.logDebug?.(`${spec.label}: done exit=${r.exitCode} wait=${r.waitMs}ms work=${r.workMs}ms`
|
|
500
678
|
+ (r.stderr ? ` stderr=${r.stderr.slice(0, 300)}` : '')
|
|
501
679
|
+ (r.leakedToolCall ? ` leaked=${r.leakedToolCall.trim().slice(0, 80)}` : ''));
|
|
@@ -503,12 +681,16 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
503
681
|
const failure = classifyResearchWorker(spec.section, r);
|
|
504
682
|
if (failure?.kind === 'fatal')
|
|
505
683
|
throw failure.error;
|
|
506
|
-
const
|
|
684
|
+
const rawText = failure?.kind === 'runaway' ?
|
|
507
685
|
degradedSectionBody(spec.section, failure.reason, r.text)
|
|
508
686
|
: r.text.trim();
|
|
509
687
|
if (failure?.kind === 'runaway') {
|
|
510
688
|
deps.logDebug?.(`${spec.label}: degraded — ${failure.reason}`);
|
|
511
689
|
}
|
|
690
|
+
// Post-check the worker's own output before it is persisted, so the cache a
|
|
691
|
+
// resume reads back is already gated. A degraded partial goes through it too —
|
|
692
|
+
// a truncated section can still carry a laundered claim.
|
|
693
|
+
const sectionText = spec.postProcess ? spec.postProcess(rawText) : rawText;
|
|
512
694
|
await persistSection(cacheHeading, sectionText);
|
|
513
695
|
return { name: spec.section, text: sectionText };
|
|
514
696
|
};
|
package/dist/task/prompts.js
CHANGED
|
@@ -153,6 +153,17 @@ No section header. No other sections. No preamble.
|
|
|
153
153
|
|
|
154
154
|
Task:
|
|
155
155
|
${refined}`;
|
|
156
|
+
// STAGE 2 (2026-07-23): APIS_SEMANTICS_CONTRACT was wired in above and UNWIRED after its A/B
|
|
157
|
+
// FAILED. It is the ONE lever of three that moved worker:apis's behaviour — behaviour-class
|
|
158
|
+
// package queries 20/20 vs 2/20, Fisher p ≈ 0 — confirming Stage 1's mechanism (completion is
|
|
159
|
+
// set by the output CONTRACT, not the answer or the target). But it failed invariant 2a: the
|
|
160
|
+
// ungrounded-symbol rate rose 1.0% -> 4.7% (p(rise) = 0.0010) and the semantics clause it adds
|
|
161
|
+
// carried 15% ungrounded symbols with the mandatory `UNVERIFIED:` abstention used 0 times in 40
|
|
162
|
+
// reps. The model obeys "ask a behaviour question" and ignores "abstain when you cannot verify",
|
|
163
|
+
// so it manufactures semantics — the exact F-1 laundering the file exists to prevent. The module
|
|
164
|
+
// and its tests are KEPT as the durable asset (like spec-urls.ts after PROMPT 4); only this
|
|
165
|
+
// interpolation is reverted. Full write-up: nexxtasks.txt "STAGE 2". Re-run: scripts/
|
|
166
|
+
// live-apis-contract-ab.ts. Do NOT re-wire without a lever that closes the abstention gap.
|
|
156
167
|
const RESEARCH_CONTEXT_PROMPT = (refined) => `You are doing targeted research for an AI coding agent. Use the read, grep, find, and ls tools to gather background knowledge and architectural context the agent will need for the following task.
|
|
157
168
|
|
|
158
169
|
RELEVANCE — read carefully: keep it tight. Each bullet must be an architectural fact that changes HOW the agent implements THIS task — a constraint, a non-obvious data flow, a gotcha, a hidden coupling. No general project tour, no restating the task, no facts the agent would not act on. If a bullet would not change a single implementation decision, drop it. There is no fixed bullet count — include every fact that bears on the task and no filler; fewer sharp bullets beat many shallow ones. If the task is itself an analysis or review, these bullets capture facts that analysis will rely on — they are NOT the analysis; do not write findings or recommendations here.
|
|
@@ -166,6 +177,8 @@ LIVE-DATA RULE:
|
|
|
166
177
|
- If EXTERNAL CONTEXT contains a "### service: <name>" block, those search results are LIVE web data and are authoritative over training data for that service's current API surface, deprecation status, and replacement systems. Do not contradict them from memory. If you must cite a version, status, or API name for that service, take it from the block.
|
|
167
178
|
- If EXTERNAL CONTEXT contains a "### freshness-check skipped" block, you have no current data for the listed services. Do NOT claim their current state from memory; say "current state not verified" and recommend the user verify before implementation.
|
|
168
179
|
- Do NOT write bullets like "X is the latest stable" or "version Y is current" from memory — your training data goes stale. Either quote from EXTERNAL CONTEXT or omit the claim entirely.
|
|
180
|
+
- API USAGE SEMANTICS — how an external library's function is called, what one of its parameters MEANS, what value it defaults to, what it returns, or what behaviour results — is the same kind of claim. You have read and grep only: you CANNOT open any documentation, so you have no way to check it. State such a claim ONLY when you are quoting a block that is actually present in EXTERNAL CONTEXT, and quote the wording you are relying on. Otherwise write it as an open question — "unverified: does hc's base URL argument mean an origin or a mount prefix?" — never as a fact. A version number in an "### npm:" block tells you NOTHING about how the API behaves; it cannot support a semantics claim.
|
|
181
|
+
- ONE CLAIM PER BULLET. Never fuse a sourced fact and an unsourced claim into one sentence under one attribution — a true half does not make the other half true, and the reader cannot tell which half was checked. If you write "package X is pinned at 1.2.3 AND <how its API behaves>", split it: keep the pinned version as its own bullet, and write the behaviour as its own bullet, marked as an open question unless you are quoting EXTERNAL CONTEXT.
|
|
169
182
|
|
|
170
183
|
Output ONLY the content of a CONTEXT section — bullet list, one bullet per line, format:
|
|
171
184
|
- <bullet>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROMPT 4 / F-2(b) — the design document's own cited URLs, ranked as fetch candidates for
|
|
3
|
+
* the APIS research worker.
|
|
4
|
+
*
|
|
5
|
+
* THE FACT THIS CLOSES. mx5's DESIGN/PROJECT.md is a literal reference list: §5 line 184
|
|
6
|
+
* cites https://hono.dev/docs/guides/rpc and §13 lists four hono.dev URLs plus
|
|
7
|
+
* https://bun.com/docs/runtime/sql. Neither of the two pages that document the semantics
|
|
8
|
+
* behind run 15's two fatal defects was ever fetched — worker:apis' 6 distinct fetches went
|
|
9
|
+
* to bun.com/reference/*, tailwindcss.com/* and nothing on hono.dev at all. Measured
|
|
10
|
+
* afterwards (scripts/spec-url-reach.ts): 31 of 44 tasks asked pi-worker-docs about a package
|
|
11
|
+
* for which the design cites a page, 215 (task, URL) pairs, and 15 of the 17 reachable cited
|
|
12
|
+
* URLs were never fetched by anyone. The pages were named, in the project's own spec, and the
|
|
13
|
+
* worker went looking somewhere else.
|
|
14
|
+
*
|
|
15
|
+
* WHY THIS IS A POPULATION LEVER AND THE TYPE-ONLY GUARD WAS NOT. PROMPT 2's detector fires
|
|
16
|
+
* on 0.54% of docs answers (9/1680 live) because it has to RECOGNISE something about an
|
|
17
|
+
* answer. This one recognises nothing: the URLs are already sitting in the spec text, so its
|
|
18
|
+
* reach is "every task whose design cites a page for a package the task uses" — the 31/44
|
|
19
|
+
* above, measured before any of this was written.
|
|
20
|
+
*
|
|
21
|
+
* RANKING, NOT REPLACING. The block says the cited pages outrank a page the model would pick
|
|
22
|
+
* itself, and says in terms that they are not the only pages it may fetch. A worker that can
|
|
23
|
+
* no longer follow a question off the design's reference list has been narrowed, not
|
|
24
|
+
* improved; PROMPT 4 invariant 3 asserts that explicitly in the A/B.
|
|
25
|
+
*
|
|
26
|
+
* ── *** NOT WIRED. THE LIVE A/B FAILED. READ THIS BEFORE RE-ENABLING IT. *** ─────────────
|
|
27
|
+
*
|
|
28
|
+
* scripts/live-spec-url-fetch-ab.ts, 2026-07-22, 40 reps, both arms in one process, real
|
|
29
|
+
* phaseResearch, offline fixture web, metric = WHICH URL WAS FETCHED at the tool layer:
|
|
30
|
+
*
|
|
31
|
+
* task27-hono baseline 2/10 treatment 3/10 Fisher one-tailed p = 0.50
|
|
32
|
+
* task28-wouter baseline 0/10 treatment 0/10 p = 1.00
|
|
33
|
+
* POOLED baseline 2/20 treatment 3/20 p = 0.50
|
|
34
|
+
*
|
|
35
|
+
* Everything that could have made that a false negative was ruled out, not assumed:
|
|
36
|
+
* - the surgery held in every rep (baseline block 0 chars 20/20, treatment non-empty 20/20);
|
|
37
|
+
* - a positive control proved a real child can reach the stubbed pi-worker-fetch;
|
|
38
|
+
* - the block was proven to REACH the APIS prompt, not merely to be built — the prompt is
|
|
39
|
+
* 19,473 chars and ends with the six ranked URLs
|
|
40
|
+
* (scripts/spec-url-prompt-delivery-check.ts).
|
|
41
|
+
* So the model reads the instruction and does not act on it.
|
|
42
|
+
*
|
|
43
|
+
* AND THE PREMISE ITSELF DID NOT SURVIVE. PROMPT 4 rests on run 15 having fetched the WRONG
|
|
44
|
+
* pages. Across all 40 reps the only URL any worker ever fetched, in either arm, was
|
|
45
|
+
* https://hono.dev/docs/guides/rpc — the cited one. The worker does not choose badly between
|
|
46
|
+
* pages; it almost never fetches at all (5 of 40 reps). A lever that improves URL RANKING is
|
|
47
|
+
* aimed at a decision this worker rarely makes.
|
|
48
|
+
*
|
|
49
|
+
* The module is kept — deterministic, unit-tested against the real run-15 design text, and
|
|
50
|
+
* the A/B harness's string surgery targets it — so the experiment can be re-run cheaply if
|
|
51
|
+
* the fetch rate itself is ever moved. It is NOT called from phases.ts, deliberately.
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* How many cited pages the block may name. The design cites 21 URLs; listing all of them in
|
|
55
|
+
* every APIS prompt would be prefill spent on pages the task has no use for, and a list long
|
|
56
|
+
* enough to skim past is a list the model ignores. Eight is above the highest per-task
|
|
57
|
+
* reachable count measured on run 15 (5 hono.dev pages for a hono task) with headroom.
|
|
58
|
+
*/
|
|
59
|
+
export declare const MAX_SPEC_URLS = 8;
|
|
60
|
+
/** http(s) URLs in a text, deduped, with local and placeholder hosts dropped. */
|
|
61
|
+
export declare function extractSpecUrls(text: string): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Tokens of a package specifier that could plausibly appear in its documentation URL: the
|
|
64
|
+
* root name plus every scope/path/hyphen segment. `hono/client` ⇒ {hono, client};
|
|
65
|
+
* `@hono/zod-validator` ⇒ {hono, zod, validator, zod-validator}. Segments of TWO characters or
|
|
66
|
+
* fewer are dropped — `pg` would match half the documentation web. Three is the floor rather
|
|
67
|
+
* than four because `zod` is a real dependency with a real cited page (zod.dev), and a rule
|
|
68
|
+
* that silently drops it is a rule that silently drops reach.
|
|
69
|
+
*/
|
|
70
|
+
export declare function packageTokens(pkg: string): string[];
|
|
71
|
+
/**
|
|
72
|
+
* Does this cited URL document this package? Host OR path, deliberately: wouter's page is
|
|
73
|
+
* github.com/molefrog/wouter#readme and @hono/zod-validator's is
|
|
74
|
+
* github.com/honojs/middleware/tree/main/packages/zod-validator — both real associations that
|
|
75
|
+
* live in the path. A host-only rule scored 0 tasks for either.
|
|
76
|
+
*/
|
|
77
|
+
export declare function urlDocumentsPackage(url: string, pkg: string): boolean;
|
|
78
|
+
export interface RankedSpecUrl {
|
|
79
|
+
url: string;
|
|
80
|
+
/** The packages this URL documents, in the order they were supplied. */
|
|
81
|
+
packages: string[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Cited URLs that document a package this task touches, most relevant first.
|
|
85
|
+
*
|
|
86
|
+
* `packages` is ordered by relevance by the CALLER — the task's own named dependencies
|
|
87
|
+
* before the rest of the manifest — and a URL inherits the rank of the earliest package it
|
|
88
|
+
* documents. A URL documenting no supplied package is dropped entirely rather than ranked
|
|
89
|
+
* last: the design cites pages for the whole project, and a task about the router has no use
|
|
90
|
+
* for the image-processing reference.
|
|
91
|
+
*/
|
|
92
|
+
export declare function rankSpecUrls(urls: string[], packages: string[]): RankedSpecUrl[];
|
|
93
|
+
/**
|
|
94
|
+
* The prompt block, or '' when nothing is cited for anything this task uses.
|
|
95
|
+
*
|
|
96
|
+
* NOTE FOR ANYONE EDITING THE GUARD CLAUSE BELOW: scripts/live-spec-url-fetch-ab.ts strips
|
|
97
|
+
* this lever for its baseline arm by replacing that exact statement in the compiled output,
|
|
98
|
+
* and asserts it occurs exactly once. Reshaping it (an early `return` on the caller's side, a
|
|
99
|
+
* ternary, a different variable name) silently makes both arms identical, which reads as
|
|
100
|
+
* "the lever had no effect". Update the harness's anchor in the same commit.
|
|
101
|
+
*/
|
|
102
|
+
export declare function buildSpecUrlBlock(urls: string[], packages: string[]): string;
|
|
103
|
+
/**
|
|
104
|
+
* The manifest dependencies this task's refined text actually names — the relevance signal
|
|
105
|
+
* the ranking needs, and the reason the block does not simply list all 21 cited URLs.
|
|
106
|
+
*
|
|
107
|
+
* WHY NOT extractEnrichTargets. That parser is tuned for the EXTERNAL-DEPENDENCIES section
|
|
108
|
+
* and, run over TASK_0027's refined text, returns ["any", "api", "hc", "package.json",
|
|
109
|
+
* "tsconfig.json", "eslint.config.js"] — and NOT "hono". Ranking off that would drop the one
|
|
110
|
+
* package the task is about while promoting URL noise. The manifest is the authoritative list
|
|
111
|
+
* of what the project actually depends on; matching it against the task text is both
|
|
112
|
+
* deterministic and impossible to fool with prose.
|
|
113
|
+
*
|
|
114
|
+
* Word-boundary matched, so `react` does not match inside `react-dom` or `@types/react`, and
|
|
115
|
+
* a package genuinely named twice is still listed once.
|
|
116
|
+
*/
|
|
117
|
+
export declare function mentionedPackages(refined: string, manifest: string[]): string[];
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROMPT 4 / F-2(b) — the design document's own cited URLs, ranked as fetch candidates for
|
|
3
|
+
* the APIS research worker.
|
|
4
|
+
*
|
|
5
|
+
* THE FACT THIS CLOSES. mx5's DESIGN/PROJECT.md is a literal reference list: §5 line 184
|
|
6
|
+
* cites https://hono.dev/docs/guides/rpc and §13 lists four hono.dev URLs plus
|
|
7
|
+
* https://bun.com/docs/runtime/sql. Neither of the two pages that document the semantics
|
|
8
|
+
* behind run 15's two fatal defects was ever fetched — worker:apis' 6 distinct fetches went
|
|
9
|
+
* to bun.com/reference/*, tailwindcss.com/* and nothing on hono.dev at all. Measured
|
|
10
|
+
* afterwards (scripts/spec-url-reach.ts): 31 of 44 tasks asked pi-worker-docs about a package
|
|
11
|
+
* for which the design cites a page, 215 (task, URL) pairs, and 15 of the 17 reachable cited
|
|
12
|
+
* URLs were never fetched by anyone. The pages were named, in the project's own spec, and the
|
|
13
|
+
* worker went looking somewhere else.
|
|
14
|
+
*
|
|
15
|
+
* WHY THIS IS A POPULATION LEVER AND THE TYPE-ONLY GUARD WAS NOT. PROMPT 2's detector fires
|
|
16
|
+
* on 0.54% of docs answers (9/1680 live) because it has to RECOGNISE something about an
|
|
17
|
+
* answer. This one recognises nothing: the URLs are already sitting in the spec text, so its
|
|
18
|
+
* reach is "every task whose design cites a page for a package the task uses" — the 31/44
|
|
19
|
+
* above, measured before any of this was written.
|
|
20
|
+
*
|
|
21
|
+
* RANKING, NOT REPLACING. The block says the cited pages outrank a page the model would pick
|
|
22
|
+
* itself, and says in terms that they are not the only pages it may fetch. A worker that can
|
|
23
|
+
* no longer follow a question off the design's reference list has been narrowed, not
|
|
24
|
+
* improved; PROMPT 4 invariant 3 asserts that explicitly in the A/B.
|
|
25
|
+
*
|
|
26
|
+
* ── *** NOT WIRED. THE LIVE A/B FAILED. READ THIS BEFORE RE-ENABLING IT. *** ─────────────
|
|
27
|
+
*
|
|
28
|
+
* scripts/live-spec-url-fetch-ab.ts, 2026-07-22, 40 reps, both arms in one process, real
|
|
29
|
+
* phaseResearch, offline fixture web, metric = WHICH URL WAS FETCHED at the tool layer:
|
|
30
|
+
*
|
|
31
|
+
* task27-hono baseline 2/10 treatment 3/10 Fisher one-tailed p = 0.50
|
|
32
|
+
* task28-wouter baseline 0/10 treatment 0/10 p = 1.00
|
|
33
|
+
* POOLED baseline 2/20 treatment 3/20 p = 0.50
|
|
34
|
+
*
|
|
35
|
+
* Everything that could have made that a false negative was ruled out, not assumed:
|
|
36
|
+
* - the surgery held in every rep (baseline block 0 chars 20/20, treatment non-empty 20/20);
|
|
37
|
+
* - a positive control proved a real child can reach the stubbed pi-worker-fetch;
|
|
38
|
+
* - the block was proven to REACH the APIS prompt, not merely to be built — the prompt is
|
|
39
|
+
* 19,473 chars and ends with the six ranked URLs
|
|
40
|
+
* (scripts/spec-url-prompt-delivery-check.ts).
|
|
41
|
+
* So the model reads the instruction and does not act on it.
|
|
42
|
+
*
|
|
43
|
+
* AND THE PREMISE ITSELF DID NOT SURVIVE. PROMPT 4 rests on run 15 having fetched the WRONG
|
|
44
|
+
* pages. Across all 40 reps the only URL any worker ever fetched, in either arm, was
|
|
45
|
+
* https://hono.dev/docs/guides/rpc — the cited one. The worker does not choose badly between
|
|
46
|
+
* pages; it almost never fetches at all (5 of 40 reps). A lever that improves URL RANKING is
|
|
47
|
+
* aimed at a decision this worker rarely makes.
|
|
48
|
+
*
|
|
49
|
+
* The module is kept — deterministic, unit-tested against the real run-15 design text, and
|
|
50
|
+
* the A/B harness's string surgery targets it — so the experiment can be re-run cheaply if
|
|
51
|
+
* the fetch rate itself is ever moved. It is NOT called from phases.ts, deliberately.
|
|
52
|
+
*/
|
|
53
|
+
/** Never candidates: a local dev URL is not documentation. */
|
|
54
|
+
const LOCAL_HOST = /^(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\]|.*\.local)$/i;
|
|
55
|
+
const PLACEHOLDER_HOST = /^(example\.(com|org|net)|your-.*|<.*)$/i;
|
|
56
|
+
/**
|
|
57
|
+
* How many cited pages the block may name. The design cites 21 URLs; listing all of them in
|
|
58
|
+
* every APIS prompt would be prefill spent on pages the task has no use for, and a list long
|
|
59
|
+
* enough to skim past is a list the model ignores. Eight is above the highest per-task
|
|
60
|
+
* reachable count measured on run 15 (5 hono.dev pages for a hono task) with headroom.
|
|
61
|
+
*/
|
|
62
|
+
export const MAX_SPEC_URLS = 8;
|
|
63
|
+
/** http(s) URLs in a text, deduped, with local and placeholder hosts dropped. */
|
|
64
|
+
export function extractSpecUrls(text) {
|
|
65
|
+
const out = [];
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
for (const raw of text.match(/https?:\/\/[^\s)>`"']+/g) ?? []) {
|
|
68
|
+
// Trailing sentence punctuation is not part of the URL; a #fragment and a query are.
|
|
69
|
+
const url = raw.replace(/[.,;:]+$/, '');
|
|
70
|
+
if (seen.has(url))
|
|
71
|
+
continue;
|
|
72
|
+
let host;
|
|
73
|
+
try {
|
|
74
|
+
host = new URL(url).hostname;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (LOCAL_HOST.test(host) || PLACEHOLDER_HOST.test(host))
|
|
80
|
+
continue;
|
|
81
|
+
seen.add(url);
|
|
82
|
+
out.push(url);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Tokens of a package specifier that could plausibly appear in its documentation URL: the
|
|
88
|
+
* root name plus every scope/path/hyphen segment. `hono/client` ⇒ {hono, client};
|
|
89
|
+
* `@hono/zod-validator` ⇒ {hono, zod, validator, zod-validator}. Segments of TWO characters or
|
|
90
|
+
* fewer are dropped — `pg` would match half the documentation web. Three is the floor rather
|
|
91
|
+
* than four because `zod` is a real dependency with a real cited page (zod.dev), and a rule
|
|
92
|
+
* that silently drops it is a rule that silently drops reach.
|
|
93
|
+
*/
|
|
94
|
+
export function packageTokens(pkg) {
|
|
95
|
+
const m = pkg.toLowerCase().trim();
|
|
96
|
+
if (m === '.' || m.length === 0)
|
|
97
|
+
return [];
|
|
98
|
+
const out = new Set();
|
|
99
|
+
for (const seg of m.split('/').filter(Boolean)) {
|
|
100
|
+
const bare = seg.replace(/^@/, '');
|
|
101
|
+
if (bare.length > 2)
|
|
102
|
+
out.add(bare);
|
|
103
|
+
for (const part of bare.split('-'))
|
|
104
|
+
if (part.length > 2)
|
|
105
|
+
out.add(part);
|
|
106
|
+
}
|
|
107
|
+
return [...out];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Does this cited URL document this package? Host OR path, deliberately: wouter's page is
|
|
111
|
+
* github.com/molefrog/wouter#readme and @hono/zod-validator's is
|
|
112
|
+
* github.com/honojs/middleware/tree/main/packages/zod-validator — both real associations that
|
|
113
|
+
* live in the path. A host-only rule scored 0 tasks for either.
|
|
114
|
+
*/
|
|
115
|
+
export function urlDocumentsPackage(url, pkg) {
|
|
116
|
+
const toks = packageTokens(pkg);
|
|
117
|
+
if (toks.length === 0)
|
|
118
|
+
return false;
|
|
119
|
+
let scope;
|
|
120
|
+
try {
|
|
121
|
+
const u = new URL(url);
|
|
122
|
+
scope = `${u.hostname} ${u.pathname}`;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const inUrl = new Set(scope
|
|
128
|
+
.toLowerCase()
|
|
129
|
+
.split(/[^a-z0-9]+/)
|
|
130
|
+
.filter(Boolean));
|
|
131
|
+
return toks.some(t => inUrl.has(t));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Cited URLs that document a package this task touches, most relevant first.
|
|
135
|
+
*
|
|
136
|
+
* `packages` is ordered by relevance by the CALLER — the task's own named dependencies
|
|
137
|
+
* before the rest of the manifest — and a URL inherits the rank of the earliest package it
|
|
138
|
+
* documents. A URL documenting no supplied package is dropped entirely rather than ranked
|
|
139
|
+
* last: the design cites pages for the whole project, and a task about the router has no use
|
|
140
|
+
* for the image-processing reference.
|
|
141
|
+
*/
|
|
142
|
+
export function rankSpecUrls(urls, packages) {
|
|
143
|
+
const ranked = [];
|
|
144
|
+
for (const url of urls) {
|
|
145
|
+
const matched = packages.filter(p => urlDocumentsPackage(url, p));
|
|
146
|
+
if (matched.length === 0)
|
|
147
|
+
continue;
|
|
148
|
+
ranked.push({ r: { url, packages: matched }, rank: packages.indexOf(matched[0]) });
|
|
149
|
+
}
|
|
150
|
+
ranked.sort((a, b) => a.rank - b.rank || a.r.url.localeCompare(b.r.url));
|
|
151
|
+
return ranked.slice(0, MAX_SPEC_URLS).map(x => x.r);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The prompt block, or '' when nothing is cited for anything this task uses.
|
|
155
|
+
*
|
|
156
|
+
* NOTE FOR ANYONE EDITING THE GUARD CLAUSE BELOW: scripts/live-spec-url-fetch-ab.ts strips
|
|
157
|
+
* this lever for its baseline arm by replacing that exact statement in the compiled output,
|
|
158
|
+
* and asserts it occurs exactly once. Reshaping it (an early `return` on the caller's side, a
|
|
159
|
+
* ternary, a different variable name) silently makes both arms identical, which reads as
|
|
160
|
+
* "the lever had no effect". Update the harness's anchor in the same commit.
|
|
161
|
+
*/
|
|
162
|
+
export function buildSpecUrlBlock(urls, packages) {
|
|
163
|
+
const ranked = rankSpecUrls(urls, packages);
|
|
164
|
+
if (ranked.length === 0)
|
|
165
|
+
return '';
|
|
166
|
+
// Grouped under the package rather than one package-name-per-URL: five consecutive lines
|
|
167
|
+
// reading "hono" is the shape a reader — and a small model — skims past, and the grouping
|
|
168
|
+
// is also what makes the ranking legible as a ranking.
|
|
169
|
+
const byPackage = new Map();
|
|
170
|
+
for (const r of ranked) {
|
|
171
|
+
const key = r.packages[0];
|
|
172
|
+
byPackage.set(key, [...(byPackage.get(key) ?? []), r.url]);
|
|
173
|
+
}
|
|
174
|
+
const rows = [...byPackage]
|
|
175
|
+
.map(([pkg, us]) => ` ${pkg}\n${us.map(u => ` ${u}`).join('\n')}`)
|
|
176
|
+
.join('\n');
|
|
177
|
+
return ("SPEC-CITED DOCUMENTATION — this project's own design document names these pages, by "
|
|
178
|
+
+ "URL, for packages this task uses. They are the project's chosen references, so they "
|
|
179
|
+
+ 'OUTRANK any page you would pick from a search result.\n'
|
|
180
|
+
+ 'WHEN TO USE THEM: the moment a `pi-worker-docs` answer for one of these packages '
|
|
181
|
+
+ 'does not actually answer your question — including when it hands you a type '
|
|
182
|
+
+ 'signature instead of behaviour — call `pi-worker-fetch(<the URL below>)` and re-ask '
|
|
183
|
+
+ 'the same question. Do NOT search for a page first, and do NOT fill the gap from '
|
|
184
|
+
+ 'memory.\n'
|
|
185
|
+
+ `${rows}\n`
|
|
186
|
+
+ 'NOT AN ALLOW-LIST: these are ranked first, not exclusive. If your question is about '
|
|
187
|
+
+ 'something these pages do not cover, use pi-worker-search / pi-worker-fetch on '
|
|
188
|
+
+ 'whatever page does.\n\n');
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The manifest dependencies this task's refined text actually names — the relevance signal
|
|
192
|
+
* the ranking needs, and the reason the block does not simply list all 21 cited URLs.
|
|
193
|
+
*
|
|
194
|
+
* WHY NOT extractEnrichTargets. That parser is tuned for the EXTERNAL-DEPENDENCIES section
|
|
195
|
+
* and, run over TASK_0027's refined text, returns ["any", "api", "hc", "package.json",
|
|
196
|
+
* "tsconfig.json", "eslint.config.js"] — and NOT "hono". Ranking off that would drop the one
|
|
197
|
+
* package the task is about while promoting URL noise. The manifest is the authoritative list
|
|
198
|
+
* of what the project actually depends on; matching it against the task text is both
|
|
199
|
+
* deterministic and impossible to fool with prose.
|
|
200
|
+
*
|
|
201
|
+
* Word-boundary matched, so `react` does not match inside `react-dom` or `@types/react`, and
|
|
202
|
+
* a package genuinely named twice is still listed once.
|
|
203
|
+
*/
|
|
204
|
+
export function mentionedPackages(refined, manifest) {
|
|
205
|
+
const text = refined.toLowerCase();
|
|
206
|
+
const out = [];
|
|
207
|
+
for (const dep of manifest) {
|
|
208
|
+
const d = dep.toLowerCase();
|
|
209
|
+
const esc = d.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
210
|
+
if (new RegExp(`(^|[^a-z0-9@/._-])${esc}([^a-z0-9-]|$)`, 'i').test(text))
|
|
211
|
+
out.push(dep);
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|