@mjasnikovs/pi-task 0.40.10 → 0.40.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.
@@ -87,11 +87,15 @@ export interface EcosystemProfile {
87
87
  */
88
88
  exportGap?: (root: string) => ExportGap;
89
89
  /**
90
- * Source of `exportGap` AND everything it delegates to, for the index
91
- * fingerprint. `String(exportGap)` alone leaves a fix in a helper invisible,
92
- * which is the failure `chunkerFingerprint` exists to close one level up.
90
+ * Source of everything this row contributes to a chunk's CONTENT — `surface`,
91
+ * `exportGap`, and every function and regex they delegate to.
92
+ *
93
+ * Required, and not derived from `String(surface)`, because that has hidden a
94
+ * real fix three times: `surface` here is the wrapper
95
+ * `content => rustSurface(content)`, which shows none of `rustSurface`, and
96
+ * neither gap rule's helpers appear in its entry point either.
93
97
  */
94
- exportGapFingerprint?: () => string;
98
+ contentFingerprint: () => string;
95
99
  /** The registry's own newest version, for grounding an answer in the present. */
96
100
  latest: (name: string, io: EcosystemIo) => Promise<NpmVersionInfo | null>;
97
101
  /** True for a file that carries the package's public API surface. */
@@ -144,13 +148,6 @@ export interface NpmProfileHooks {
144
148
  resolvePackage?: typeof resolvePackage;
145
149
  npmVersionLookup?: typeof npmVersionLookup;
146
150
  }
147
- /**
148
- * The npm row, with the pieces a caller may have replaced left as parameters.
149
- *
150
- * `docsRaw` already takes `resolvePackage` and `npmVersionLookup` as injection
151
- * hooks, and those hooks must keep reaching the resolution they are injected
152
- * for. A per-call row carries them; {@link ECOSYSTEMS} holds the plain one.
153
- */
154
151
  export declare function npmProfile(hooks?: NpmProfileHooks): EcosystemProfile;
155
152
  /**
156
153
  * The package.json manifest, not the lockfile: a lockfile is rewritten by
@@ -21,8 +21,8 @@ import { runAutoInstall, findDeclaredRange, extractParentPackage, resolveTypeSou
21
21
  import { resolvePackage, isDtsFile, isValidModuleName } from './docs-resolve.js';
22
22
  import { DECL_SPLIT_RE } from './docs-chunk.js';
23
23
  import { npmVersionLookup } from './npm-version.js';
24
- import { resolveCrate, cratesLatest, crateTarballUrl, crateOf, isValidCrateName, isRustFile, lockedVersion, rustSurface, cargoProjectName, childDirs, lockedDeps, manifestCrates, cargoExportGap, cargoGapFingerprint, cargoSupplementCandidates, CARGO_DECL_SPLIT_RE } from './eco-cargo.js';
25
- import { resolveHackage, hackageLatest, hackageVersion, hackageTarballUrl, hackageExtractDir, hackageProjectName, supplementCandidates, hackageExportGap, hackageGapFingerprint, findCabalTarball, cachedVersions, resolvedVersions, manifestPackages, isValidHackageName, isHaskellFile, haskellSurface, HACKAGE_DECL_SPLIT_RE, HACKAGE_SKIP_DIRS } from './eco-hackage.js';
24
+ import { resolveCrate, cratesLatest, crateTarballUrl, crateOf, isValidCrateName, isRustFile, lockedVersion, rustSurface, cargoProjectName, childDirs, lockedDeps, manifestCrates, cargoExportGap, cargoContentFingerprint, cargoSupplementCandidates, CARGO_DECL_SPLIT_RE } from './eco-cargo.js';
25
+ import { resolveHackage, hackageLatest, hackageVersion, hackageTarballUrl, hackageExtractDir, hackageProjectName, supplementCandidates, hackageExportGap, hackageContentFingerprint, findCabalTarball, cachedVersions, resolvedVersions, manifestPackages, isValidHackageName, isHaskellFile, haskellSurface, HACKAGE_DECL_SPLIT_RE, HACKAGE_SKIP_DIRS } from './eco-hackage.js';
26
26
  import { runChild } from '../shared/child-process.js';
27
27
  /**
28
28
  * Is any of `names` present at `cwd` or above it?
@@ -101,6 +101,9 @@ function defaultCabalPackageDirs() {
101
101
  * hooks, and those hooks must keep reaching the resolution they are injected
102
102
  * for. A per-call row carries them; {@link ECOSYSTEMS} holds the plain one.
103
103
  */
104
+ /** A `.d.ts` is already declarations only, so npm's extractor is the identity.
105
+ * Named rather than inline so `contentFingerprint` can point at it. */
106
+ const npmSurface = (content) => content;
104
107
  export function npmProfile(hooks = {}) {
105
108
  const resolve = hooks.resolvePackage ?? resolvePackage;
106
109
  const versionLookup = hooks.npmVersionLookup ?? npmVersionLookup;
@@ -126,7 +129,10 @@ export function npmProfile(hooks = {}) {
126
129
  afterResolve: (pkg, requested, cwd, io) => resolveTypeSourceForDocs(pkg, requested, cwd, io.spawn, resolve, io.signal),
127
130
  latest: (name, io) => versionLookup(name, io.signal === undefined ? {} : { signal: io.signal }),
128
131
  isSurfaceFile: isDtsFile,
129
- surface: content => content,
132
+ surface: npmSurface,
133
+ // No gap rule, and a `.d.ts` IS the surface, so there is nothing below the
134
+ // identity for a fingerprint to miss.
135
+ contentFingerprint: () => String(npmSurface),
130
136
  declSplitRe: DECL_SPLIT_RE,
131
137
  typeKeywords: ['interface', 'type', 'class', 'enum'],
132
138
  commentPrefix: '//',
@@ -299,9 +305,12 @@ const cargoProfile = {
299
305
  return out;
300
306
  },
301
307
  exportGap: cargoExportGap,
302
- exportGapFingerprint: cargoGapFingerprint,
308
+ contentFingerprint: cargoContentFingerprint,
303
309
  latest: (name, io) => cratesLatest(name, io.fetch, io.signal),
304
310
  isSurfaceFile: isRustFile,
311
+ // Wrapped, not passed by reference: `rustSurface` takes two more optional
312
+ // arguments, and a bare reference would let a `.map(profile.surface)` pass an
313
+ // index as `insideTrait`. `contentFingerprint` is what covers its source.
305
314
  surface: content => rustSurface(content),
306
315
  declSplitRe: CARGO_DECL_SPLIT_RE,
307
316
  typeKeywords: ['struct', 'trait', 'enum', 'type', 'union'],
@@ -415,7 +424,7 @@ const hackageProfile = {
415
424
  return out;
416
425
  },
417
426
  exportGap: hackageExportGap,
418
- exportGapFingerprint: hackageGapFingerprint,
427
+ contentFingerprint: hackageContentFingerprint,
419
428
  latest: (name, io) => hackageLatest(name, io.fetch, io.signal),
420
429
  isSurfaceFile: isHaskellFile,
421
430
  surface: haskellSurface,
@@ -52,7 +52,8 @@ function computeContentHash(pkg, profile, supplements = []) {
52
52
  // leaves a package cached whenever a fix moves some OTHER module — the
53
53
  // wrapped `instance` head is in aeson's `Types/FromJSON.hs`, never its entry
54
54
  // — and nothing surfaced the duplicate drop in `ingestBody` at all.
55
- hash.update(Buffer.from(`${String(profile.surface)}\u0000${String(ingestBody)}`, 'utf8'));
55
+ hash.update(Buffer.from(`${String(profile.surface)}\u0000${profile.contentFingerprint()}`
56
+ + `\u0000${String(ingestBody)}`, 'utf8'));
56
57
  hash.update(ZERO_SEP);
57
58
  // Which packages were folded in, so gaining or losing one re-indexes — and the
58
59
  // rule that decides WHICH of their chunks are kept, by source. Hashing the set
@@ -60,7 +61,6 @@ function computeContentHash(pkg, profile, supplements = []) {
60
61
  // the chunks the old rule chose.
61
62
  hash.update(Buffer.from(supplements.map(s => `${s.name}@${s.version}`).join('\u0000'), 'utf8'));
62
63
  hash.update(ZERO_SEP);
63
- hash.update(Buffer.from(profile.exportGapFingerprint?.() ?? String(profile.exportGap ?? ''), 'utf8'));
64
64
  hash.update(ZERO_SEP);
65
65
  if (pkg.entry && fs.existsSync(pkg.entry)) {
66
66
  try {
@@ -99,16 +99,6 @@ interface Item {
99
99
  * finds the end of the item it is in.
100
100
  */
101
101
  export declare function splitRustItems(src: string): Item[];
102
- /**
103
- * Reduce Rust source to its public API surface.
104
- *
105
- * Function bodies go — they are the bulk of the file and answer no question the
106
- * docs tool is asked. Everything a caller can name stays: the item head, its doc
107
- * comment, its attributes, and for a struct or enum its fields and variants.
108
- *
109
- * Inside a `trait` every member is public by definition, so the `pub` test is
110
- * suspended there; anywhere else a bare or `pub(crate)` item is dropped.
111
- */
112
102
  export declare function rustSurface(src: string, insideTrait?: boolean, topLevel?: boolean): string;
113
103
  export declare function isRustFile(name: string): boolean;
114
104
  /** The `[package] name` of a cargo project, for labelling its own source. */
@@ -134,14 +124,17 @@ export declare function manifestCrates(cwd: string): Set<string> | undefined;
134
124
  */
135
125
  export declare function cargoExportGap(root: string): ExportGap;
136
126
  /**
137
- * Source of every function `cargoExportGap` delegates to.
127
+ * Source of everything this row contributes to a chunk's CONTENT — the surface
128
+ * extractor, the gap rule, and every function and regex they delegate to.
138
129
  *
139
- * `String(cargoExportGap)` covers only the top level, and the two bugs already
140
- * found in this pass a rename split that truncated `Hasher`, a lock read from
141
- * the wrong root — both lived in helpers. A fix to one of them has to move the
142
- * index fingerprint or every cached facade keeps the chunks the old rule chose.
130
+ * `String(fn)` covers only a top level, and three separate bugs have now hidden
131
+ * one level below it: the chunker (which `chunkerFingerprint` closes), the gap
132
+ * rule's helpers, and `surface` itself, declared as the wrapper
133
+ * `content => rustSurface(content)` which shows none of `rustSurface`. A fix that
134
+ * does not move this leaves every cached crate holding the chunks the old rule
135
+ * chose.
143
136
  */
144
- export declare function cargoGapFingerprint(): string;
137
+ export declare function cargoContentFingerprint(): string;
145
138
  /**
146
139
  * Which declared dependencies may be opened to fill the gap.
147
140
  *
@@ -631,6 +631,37 @@ const LIST_KINDS = new Set(['use']);
631
631
  * Inside a `trait` every member is public by definition, so the `pub` test is
632
632
  * suspended there; anywhere else a bare or `pub(crate)` item is dropped.
633
633
  */
634
+ /** A `#name` interpolation. Rust item syntax has none; a `quote!` template is
635
+ * nothing but. Comments come out first — a doctest hides lines with `# use …`. */
636
+ const INTERPOLATION = /#[A-Za-z_(]/;
637
+ function withoutComments(body) {
638
+ return body
639
+ .split('\n')
640
+ .map(l => (/^\s*(?:\/\/|\*)/.test(l) ? '' : l.replace(/\/\/.*$/, '')))
641
+ .join('\n');
642
+ }
643
+ /**
644
+ * The body of a `name! { … }` block that WRAPS items, or null.
645
+ *
646
+ * tokio declares `pub struct TcpListener` inside `cfg_net! { … }`, and every
647
+ * async crate does the same: 818 public items across twenty-two crates sit inside
648
+ * such a block, 441 of them tokio's. Treating the invocation as one opaque item
649
+ * dropped all of them, `TcpListener` — a ground-truth symbol of the live test —
650
+ * included.
651
+ *
652
+ * The guard is what the body IS, never which macro it is. A `quote!` body is a
653
+ * token template, and the same measurement found zero public items inside an
654
+ * interpolating body, so the two populations do not overlap.
655
+ */
656
+ function macroWrappedItems(item) {
657
+ if (item.body === null)
658
+ return null;
659
+ if (!/^[a-z_][a-z0-9_]*!$/.test(item.head.trim()))
660
+ return null;
661
+ if (INTERPOLATION.test(withoutComments(item.body)))
662
+ return null;
663
+ return item.body;
664
+ }
634
665
  export function rustSurface(src, insideTrait = false, topLevel = true) {
635
666
  const out = [];
636
667
  const items = splitRustItems(src);
@@ -645,8 +676,12 @@ export function rustSurface(src, insideTrait = false, topLevel = true) {
645
676
  out.push(moduleDoc);
646
677
  for (const item of items) {
647
678
  const headMatch = ITEM_HEAD_RE.exec(item.head);
648
- if (!headMatch)
679
+ if (!headMatch) {
680
+ const wrapped = macroWrappedItems(item);
681
+ if (wrapped !== null)
682
+ out.push(rustSurface(wrapped, insideTrait, false));
649
683
  continue;
684
+ }
650
685
  const kind = headMatch[1];
651
686
  // A `#[macro_export]` macro IS the crate's API — `anyhow::bail!`,
652
687
  // `serde_json::json!`. Dropping every macro answered "no such thing"
@@ -1002,19 +1037,38 @@ export function cargoExportGap(root) {
1002
1037
  };
1003
1038
  }
1004
1039
  /**
1005
- * Source of every function `cargoExportGap` delegates to.
1040
+ * Source of everything this row contributes to a chunk's CONTENT — the surface
1041
+ * extractor, the gap rule, and every function and regex they delegate to.
1006
1042
  *
1007
- * `String(cargoExportGap)` covers only the top level, and the two bugs already
1008
- * found in this pass a rename split that truncated `Hasher`, a lock read from
1009
- * the wrong root — both lived in helpers. A fix to one of them has to move the
1010
- * index fingerprint or every cached facade keeps the chunks the old rule chose.
1043
+ * `String(fn)` covers only a top level, and three separate bugs have now hidden
1044
+ * one level below it: the chunker (which `chunkerFingerprint` closes), the gap
1045
+ * rule's helpers, and `surface` itself, declared as the wrapper
1046
+ * `content => rustSurface(content)` which shows none of `rustSurface`. A fix that
1047
+ * does not move this leaves every cached crate holding the chunks the old rule
1048
+ * chose.
1011
1049
  */
1012
- export function cargoGapFingerprint() {
1013
- return [cargoExportGap, runtimeDeps, useTargets, rustSources, moduleOfPath]
1050
+ export function cargoContentFingerprint() {
1051
+ return [
1052
+ rustSurface,
1053
+ splitRustItems,
1054
+ macroWrappedItems,
1055
+ withoutComments,
1056
+ keptPreamble,
1057
+ privateTypeNames,
1058
+ implTarget,
1059
+ fieldsOf,
1060
+ cargoExportGap,
1061
+ runtimeDeps,
1062
+ useTargets,
1063
+ rustSources,
1064
+ moduleOfPath
1065
+ ]
1014
1066
  .map(String)
1015
1067
  .concat([
1016
1068
  PUB_USE_RE.source,
1017
1069
  RUST_DECL_RE.source,
1070
+ ITEM_HEAD_RE.source,
1071
+ INTERPOLATION.source,
1018
1072
  CARGO_SKIP_DIRS.join(','),
1019
1073
  [...OWN_PATH_ROOTS].join(',')
1020
1074
  ])
@@ -103,9 +103,9 @@ export declare function hackageProjectName(cwd: string): string | null;
103
103
  export declare function manifestPackages(cwd: string): Set<string> | undefined;
104
104
  /** Every name the extracted surface declares: signatures, heads, constructors, fields. */
105
105
  export declare function declaredInSurface(surface: string): Set<string>;
106
- /** Source of every function {@link hackageExportGap} delegates to — see
107
- * `cargoGapFingerprint` for why the top level alone is not enough. */
108
- export declare function hackageGapFingerprint(): string;
106
+ /** Source of everything this row contributes to a chunk's content — see
107
+ * `cargoContentFingerprint` for why a top-level `String(fn)` is not enough. */
108
+ export declare function hackageContentFingerprint(): string;
109
109
  export declare function hackageExportGap(root: string): ExportGap;
110
110
  /**
111
111
  * Which declared dependencies may be opened to fill the gap.
@@ -670,9 +670,9 @@ function haskellSources(root) {
670
670
  walk(root);
671
671
  return out;
672
672
  }
673
- /** Source of every function {@link hackageExportGap} delegates to — see
674
- * `cargoGapFingerprint` for why the top level alone is not enough. */
675
- export function hackageGapFingerprint() {
673
+ /** Source of everything this row contributes to a chunk's content — see
674
+ * `cargoContentFingerprint` for why a top-level `String(fn)` is not enough. */
675
+ export function hackageContentFingerprint() {
676
676
  return [hackageExportGap, haskellSources, exportListText, declaredInSurface, haskellSurface]
677
677
  .map(String)
678
678
  .concat([REEXPORT_RE.source, EXPORT_NAME_RE.source])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.40.10",
3
+ "version": "0.40.11",
4
4
  "description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",