@mjasnikovs/pi-task 0.17.6 → 0.17.7

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.
@@ -3,7 +3,14 @@
3
3
  * orchestrator can fan out docs/URL lookups before the research phase.
4
4
  */
5
5
  export declare function extractEnrichTargets(text: string): {
6
+ /** Packages that get a (heavy) docs fetch — capped at ENRICH_CAP. */
6
7
  packages: string[];
8
+ /**
9
+ * Every named package, up to ENRICH_VERSION_CAP, for a cheap live npm version
10
+ * lookup. A superset of `packages`; the extras get a version block only (no
11
+ * docs body). Order-preserving and deduped, same as `packages`.
12
+ */
13
+ versionPackages: string[];
7
14
  urls: string[];
8
15
  services: Array<{
9
16
  name: string;
@@ -21,6 +21,13 @@ const ENRICH_DENYLIST = new Set([
21
21
  'rm'
22
22
  ]);
23
23
  const ENRICH_CAP = 3;
24
+ // Heavy docs/url fetches stay capped at ENRICH_CAP (each is a worker spawn +
25
+ // page fetch). A live npm VERSION lookup is just one cheap registry GET, so it
26
+ // can cover far more packages — every dependency a task explicitly names should
27
+ // get a grounded latest-version block, not just the first ENRICH_CAP of them. A
28
+ // task that named 6 runtime deps used to leave the 4th–6th (e.g. tailwindcss)
29
+ // with NO live version, so a version question fell back to stale training data.
30
+ const ENRICH_VERSION_CAP = 12;
24
31
  const ENRICH_SERVICE_HEADER = 'EXTERNAL-DEPENDENCIES';
25
32
  const ENRICH_HEADER_LINE_RE = /^[A-Z][A-Z0-9 -]+$/;
26
33
  const ENRICH_SERVICE_BULLET_RE = /^\s*-\s+(.+?)\s{2,}(.+?)\s*$/;
@@ -61,13 +68,15 @@ function parseServices(text) {
61
68
  return out;
62
69
  }
63
70
  export function extractEnrichTargets(text) {
64
- const pkgs = new Set();
71
+ const pkgs = [];
72
+ const seen = new Set();
65
73
  for (const m of text.matchAll(ENRICH_PKG_RE)) {
66
74
  const t = m[1];
67
- if (ENRICH_DENYLIST.has(t))
75
+ if (ENRICH_DENYLIST.has(t) || seen.has(t))
68
76
  continue;
69
- pkgs.add(t);
70
- if (pkgs.size >= ENRICH_CAP)
77
+ seen.add(t);
78
+ pkgs.push(t);
79
+ if (pkgs.length >= ENRICH_VERSION_CAP)
71
80
  break;
72
81
  }
73
82
  const urls = new Set();
@@ -78,5 +87,5 @@ export function extractEnrichTargets(text) {
78
87
  break;
79
88
  }
80
89
  const services = parseServices(text);
81
- return { packages: [...pkgs], urls: [...urls], services };
90
+ return { packages: pkgs.slice(0, ENRICH_CAP), versionPackages: pkgs, urls: [...urls], services };
82
91
  }
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { docsRaw } from '../workers/docs-core.js';
12
12
  import { fetchRaw } from '../workers/fetch-core.js';
13
+ import { npmVersionLookup } from '../workers/npm-version.js';
13
14
  import type { SearchCoreInput, SearchCoreResult } from '../workers/search-core.js';
14
15
  import type { PhaseDeps } from './child-runner.js';
15
16
  /** Injectable workers so enrichment is testable without spawning real lookups. */
@@ -17,6 +18,7 @@ export interface ExternalContextDeps {
17
18
  docsRaw?: typeof docsRaw;
18
19
  fetchRaw?: typeof fetchRaw;
19
20
  searchFn?: (input: SearchCoreInput) => Promise<SearchCoreResult>;
21
+ npmVersionLookup?: typeof npmVersionLookup;
20
22
  }
21
23
  type GatherDeps = Pick<PhaseDeps, 'cwd' | 'signal' | 'recordSubStep'>;
22
24
  /**
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { docsRaw } from '../workers/docs-core.js';
12
12
  import { fetchRaw } from '../workers/fetch-core.js';
13
- import { formatNpmVersionSection } from '../workers/npm-version.js';
13
+ import { formatNpmVersionSection, npmVersionLookup } from '../workers/npm-version.js';
14
14
  import { search as defaultSearch } from '../workers/search-core.js';
15
15
  import { extractEnrichTargets } from './enrichment.js';
16
16
  import { formatServiceBlock, formatFreshnessSkippedBlock } from './service-blocks.js';
@@ -22,7 +22,15 @@ export async function gatherExternalContext(refined, deps, researchDeps = {}) {
22
22
  const docsRawFn = researchDeps.docsRaw ?? docsRaw;
23
23
  const fetchRawFn = researchDeps.fetchRaw ?? fetchRaw;
24
24
  const searchFn = researchDeps.searchFn ?? defaultSearch;
25
+ const npmVersionFn = researchDeps.npmVersionLookup ?? npmVersionLookup;
25
26
  const enrichTargets = extractEnrichTargets(refined);
27
+ // Every named dep past the heavy-docs cap still gets a cheap live version
28
+ // lookup, so a version block exists for ALL of them (the docs-fetched ones
29
+ // emit their version below from the bundled lookup). Without this, deps 4..N
30
+ // had no live version and a "which version?" question fell back to the model's
31
+ // stale training data — how tailwindcss got pinned to an old major.
32
+ const docsPkgs = new Set(enrichTargets.packages);
33
+ const extraVersionPkgs = enrichTargets.versionPackages.filter(p => !docsPkgs.has(p));
26
34
  if (enrichTargets.packages.length === 0
27
35
  && enrichTargets.urls.length === 0
28
36
  && enrichTargets.services.length === 0) {
@@ -30,7 +38,7 @@ export async function gatherExternalContext(refined, deps, researchDeps = {}) {
30
38
  }
31
39
  const enrichSections = [];
32
40
  const tEnrichStart = Date.now();
33
- const [docsResults, fetchResults, serviceResults] = await Promise.all([
41
+ const [docsResults, fetchResults, serviceResults, extraVersionResults] = await Promise.all([
34
42
  Promise.all(enrichTargets.packages.map(pkg => docsRawFn({
35
43
  pkg,
36
44
  query: refined.split('\n').find(l => l.trim()) ?? refined,
@@ -42,16 +50,22 @@ export async function gatherExternalContext(refined, deps, researchDeps = {}) {
42
50
  query: `${s.name} ${s.query}`,
43
51
  count: 3,
44
52
  signal: deps.signal
45
- }).catch(() => null)))
53
+ }).catch(() => null))),
54
+ Promise.all(extraVersionPkgs.map(pkg => npmVersionFn(pkg, { signal: deps.signal }).catch(() => null)))
46
55
  ]);
47
- // npm version blocks come from docsRaw's bundled lookup and lead the
48
- // section so the model anchors on live version data before reading
49
- // the docs body.
56
+ // npm version blocks lead the section so the model anchors on live version
57
+ // data before reading any docs body. The docs-fetched packages carry their
58
+ // version in docsRaw's bundled lookup; the remaining named deps get theirs
59
+ // from the cheap standalone lookup above. Together they cover EVERY named dep.
50
60
  for (let i = 0; i < enrichTargets.packages.length; i++) {
51
61
  const v = docsResults[i]?.npmVersion;
52
62
  if (v)
53
63
  enrichSections.push(formatNpmVersionSection(v));
54
64
  }
65
+ for (const v of extraVersionResults) {
66
+ if (v)
67
+ enrichSections.push(formatNpmVersionSection(v));
68
+ }
55
69
  for (let i = 0; i < enrichTargets.packages.length; i++) {
56
70
  const r = docsResults[i];
57
71
  if (r?.kind === 'ok' && r.chunks.length > 0) {
@@ -221,6 +221,7 @@ LIVE-DATA RULE:
221
221
  - "### service: <name>" blocks are LIVE web data; authoritative over training data for that service.
222
222
  - "### freshness-check skipped" → tag UNKNOWN and say current state needs verification.
223
223
  - No npm block + question is about latest/current version → tag UNKNOWN (training data goes stale).
224
+ - VERSION-PIN questions ("pin to X.y vs latest", "which major version") are costly-to-reverse build-shaping choices: unless the spec or an "### npm:" block already settles it (then ANSWER that value), tag UNKNOWN and surface it. NEVER auto-answer a downgrade to an OLDER major "to avoid breaking changes" from memory — that reasoning is exactly the stale-training-data trap. If an "### npm:" block shows a newer major than your instinct, that block is the live latest; do not silently pin an older major the live data and spec never asked for.
224
225
 
225
226
  TRIAGE — run these checks IN ORDER first. The REVERSIBILITY TEST below applies ONLY to a question that survives all checks as a genuine preference.
226
227
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.17.6",
3
+ "version": "0.17.7",
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",