@animus-ui/extract 0.1.7 → 0.1.8

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../pipeline/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../pipeline/index.ts"],"names":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { createHash } from "crypto";
3
3
  import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "fs";
4
- import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "path";
4
+ import { dirname, extname, isAbsolute, join, relative, resolve } from "path";
5
+ import { realpathSync as realpathSync$1 } from "node:fs";
6
+ import path, { basename, dirname as dirname$1, join as join$1, normalize } from "node:path";
5
7
  import { createRequire as createRequire$1 } from "module";
6
8
  import browserslist from "browserslist";
7
9
  import { browserslistToTargets, transform } from "lightningcss";
8
10
  import { isUnitlessProperty } from "@animus-ui/properties";
9
- import { realpathSync as realpathSync$1 } from "node:fs";
10
- import { basename, dirname as dirname$1, join as join$1, normalize } from "node:path";
11
11
  //#region \0rolldown/runtime.js
12
12
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
13
13
  //#endregion
@@ -317,6 +317,126 @@ function discoverFiles(dir, rootDir, excludePatterns, extensionsSet) {
317
317
  return results;
318
318
  }
319
319
  //#endregion
320
+ //#region pipeline/source-identity.ts
321
+ /**
322
+ * SourceId derivation authority + allowlist membership (openspec:
323
+ * external-source-watch-ingestion, design D1/D2/D5).
324
+ *
325
+ * One shared derivation produces the canonical source identity consumed by
326
+ * discovery, watch ingestion, ownership, deletion pruning, and diagnostics
327
+ * — raw event paths are lookup INPUTS only, so event spelling (symlink
328
+ * alias vs canonical, lexical vs realpath) can never fork identity.
329
+ *
330
+ * The containment and volume helpers are pure and path-API-injectable so
331
+ * Windows semantics are unit-testable via `path.win32` on any host; runtime
332
+ * callers use the ambient platform implementation.
333
+ */
334
+ /**
335
+ * Structural containment: `target` is the root itself or a descendant of it,
336
+ * decided by `relative()` shape — never by string prefixing, so `/ui` can
337
+ * never claim `/ui-old`, and a cross-drive win32 target (whose relative()
338
+ * result is absolute) is never contained.
339
+ */
340
+ function isPathWithinRoot(root, target, pathApi = path) {
341
+ const rel = pathApi.relative(root, target);
342
+ return rel === "" || !pathApi.isAbsolute(rel) && rel !== ".." && !rel.startsWith(".." + pathApi.sep);
343
+ }
344
+ /**
345
+ * Cross-volume gate (design D5): two paths share a platform path root
346
+ * (win32 drive letter or UNC share; always true on posix). Compared
347
+ * case-insensitively — win32 drive letters are case-insensitive.
348
+ */
349
+ function sharesVolumeRoot(a, b, pathApi = path) {
350
+ const volumeOf = (p) => pathApi.parse(pathApi.resolve(p)).root.toLowerCase();
351
+ return volumeOf(a) === volumeOf(b);
352
+ }
353
+ function canonicalize(p) {
354
+ try {
355
+ return realpathSync$1(p);
356
+ } catch {
357
+ return null;
358
+ }
359
+ }
360
+ function createSourceIdentity(rootDir) {
361
+ const normalizedRootDir = path.normalize(rootDir);
362
+ const canonicalRootDir = canonicalize(normalizedRootDir) ?? normalizedRootDir;
363
+ const roots = [];
364
+ const aliases = /* @__PURE__ */ new Map();
365
+ const owningRecord = (canonicalPath) => {
366
+ let owner = null;
367
+ for (const record of roots) {
368
+ if (!isPathWithinRoot(record.canonical, canonicalPath)) continue;
369
+ if (!owner || record.canonical.length > owner.canonical.length) owner = record;
370
+ }
371
+ return owner;
372
+ };
373
+ return {
374
+ rootDir: normalizedRootDir,
375
+ canonicalRootDir,
376
+ registerExternalRoot(root) {
377
+ const asRegistered = path.normalize(root);
378
+ const canonical = canonicalize(asRegistered) ?? asRegistered;
379
+ const existing = roots.find((record) => record.canonical === canonical);
380
+ if (existing) return existing.canonical;
381
+ roots.push({
382
+ asRegistered,
383
+ canonical
384
+ });
385
+ return canonical;
386
+ },
387
+ externalRoots() {
388
+ return roots.map((record) => record.canonical);
389
+ },
390
+ resolveSourceId(inputPath) {
391
+ const lexical = path.normalize(inputPath);
392
+ const canonical = canonicalize(lexical);
393
+ if (canonical === null) return null;
394
+ const owner = owningRecord(canonical);
395
+ const ownedBy = (record) => {
396
+ const pathInRoot = path.relative(record.canonical, canonical);
397
+ return {
398
+ sourceKey: path.relative(normalizedRootDir, path.join(record.asRegistered, pathInRoot)),
399
+ owningRoot: record.canonical,
400
+ pathInRoot
401
+ };
402
+ };
403
+ const ownerIsMoreSpecific = owner !== null && owner.canonical.length > canonicalRootDir.length;
404
+ let resolved;
405
+ if (!ownerIsMoreSpecific && isPathWithinRoot(canonicalRootDir, canonical)) {
406
+ const key = path.relative(canonicalRootDir, canonical);
407
+ resolved = {
408
+ sourceKey: key,
409
+ owningRoot: null,
410
+ pathInRoot: key
411
+ };
412
+ } else if (owner) resolved = ownedBy(owner);
413
+ else return null;
414
+ aliases.set(lexical, resolved);
415
+ aliases.set(canonical, resolved);
416
+ return resolved;
417
+ },
418
+ resolveDeletedSourceId(inputPath) {
419
+ return aliases.get(path.normalize(inputPath)) ?? null;
420
+ },
421
+ containingExternalRoot(inputPath) {
422
+ const lexical = path.normalize(inputPath);
423
+ let owner = null;
424
+ for (const record of roots) {
425
+ if (!(isPathWithinRoot(record.canonical, lexical) || isPathWithinRoot(record.asRegistered, lexical))) continue;
426
+ if (!owner || record.canonical.length > owner.canonical.length) owner = record;
427
+ }
428
+ if (owner) return owner.canonical;
429
+ const canonical = canonicalize(lexical);
430
+ if (canonical === null) return null;
431
+ for (const record of roots) {
432
+ if (!isPathWithinRoot(record.canonical, canonical)) continue;
433
+ if (!owner || record.canonical.length > owner.canonical.length) owner = record;
434
+ }
435
+ return owner?.canonical ?? null;
436
+ }
437
+ };
438
+ }
439
+ //#endregion
320
440
  //#region pipeline/discover-packages.ts
321
441
  /**
322
442
  * Walk up from a resolved package entry file to the nearest directory
@@ -341,6 +461,20 @@ const PACKAGE_SRC_EXCLUDES = [
341
461
  ".test.",
342
462
  ".spec."
343
463
  ];
464
+ /** True when a PACKAGE-RELATIVE path matches a package-source exclusion
465
+ * fragment (the "within the package" filter — see PACKAGE_SRC_EXCLUDES). */
466
+ function isExcludedPackageRelativePath(rel) {
467
+ return PACKAGE_SRC_EXCLUDES.some((pattern) => rel.includes(pattern));
468
+ }
469
+ /**
470
+ * THE package-source walk (guardrail G1 — one policy): shared discovery with
471
+ * no consumer patterns, then exclusion by package-relative path so fragments
472
+ * in the package's own location can't blank out its sources. Used by the
473
+ * collector, hosts' dirty-root rewalks, and watch-path classification alike.
474
+ */
475
+ function walkPackageSources(packageDir, extensionsSet) {
476
+ return discoverFiles(packageDir, packageDir, [], extensionsSet).filter((absPath) => !isExcludedPackageRelativePath(relative(packageDir, absPath)));
477
+ }
344
478
  /** Exclusions for the compiled-output fallback when a package does not ship src/. */
345
479
  const PACKAGE_OUTPUT_EXCLUDES = [
346
480
  "node_modules",
@@ -379,7 +513,7 @@ function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
379
513
  if (isAbsolute(specifier)) {
380
514
  const resolved = resolveAbsolutePathSpecifier(specifier, extensionsSet);
381
515
  if (!resolved) return null;
382
- return !relative(srcDir, resolved).startsWith("..") ? resolved : null;
516
+ return isPathWithinRoot(srcDir, resolved) ? resolved : null;
383
517
  }
384
518
  const packageName = bareSpecifierPackageName(specifier);
385
519
  return resolveAbsolutePathSpecifier(join(srcDir, specifier.slice(packageName.length + 1) || "index"), extensionsSet);
@@ -393,7 +527,7 @@ function sourceEntryForSpecifier(specifier, srcDir, extensionsSet) {
393
527
  * file's mtime is readable. Detection only — reporting is the caller's policy.
394
528
  */
395
529
  function distEntryIsStale(absEntry, srcDir, srcFiles) {
396
- if (!relative(srcDir, absEntry).startsWith("..")) return false;
530
+ if (isPathWithinRoot(srcDir, absEntry)) return false;
397
531
  let distMtime;
398
532
  try {
399
533
  distMtime = statSync(absEntry).mtimeMs;
@@ -425,15 +559,19 @@ function distEntryIsStale(absEntry, srcDir, srcFiles) {
425
559
  * them — any warning or gate is the caller's policy.
426
560
  */
427
561
  async function collectExternalPackageSources(opts) {
428
- const { specifiers, resolveSpecifier, rootDir, extensionsSet, hasEntry, preprocessFile, onUnreadable } = opts;
562
+ const { specifiers, resolveSpecifier, rootDir, extensionsSet, hasEntry, preprocessFile, onUnreadable, onPackageResolved } = opts;
429
563
  const entries = [];
430
564
  const pushed = /* @__PURE__ */ new Set();
431
565
  const packageMap = {};
432
566
  const sourceEntries = /* @__PURE__ */ new Map();
567
+ const keyframesScanEntries = /* @__PURE__ */ new Map();
433
568
  const packageDirs = [];
434
- const dirOwners = {};
569
+ const dirOwnerSets = {};
435
570
  const fileOwners = {};
436
571
  const outcomes = [];
572
+ const claimDir = (dir, specifier) => {
573
+ (dirOwnerSets[dir] ??= []).push(specifier);
574
+ };
437
575
  const alreadyIngested = (relPath) => hasEntry(relPath) || pushed.has(relPath);
438
576
  for (const specifier of specifiers) {
439
577
  let absEntry;
@@ -456,12 +594,14 @@ async function collectExternalPackageSources(opts) {
456
594
  let staleDist = false;
457
595
  if (existsSync(srcDir)) {
458
596
  packageDirs.push(srcDir);
459
- dirOwners[srcDir] ??= specifier;
597
+ claimDir(srcDir, specifier);
598
+ onPackageResolved?.(specifier, srcDir);
460
599
  const srcEntry = sourceEntryForSpecifier(specifier, srcDir, extensionsSet);
461
600
  if (srcEntry) {
462
601
  packageMap[specifier] = relative(rootDir, srcEntry);
463
602
  sourceEntries.set(specifier, srcEntry);
464
603
  } else packageMap[specifier] = relative(rootDir, absEntry);
604
+ keyframesScanEntries.set(specifier, srcEntry ?? absEntry);
465
605
  if (!isAbsolute(specifier)) {
466
606
  const packageName = bareSpecifierPackageName(specifier);
467
607
  if (packageName !== specifier && !(packageName in packageMap)) {
@@ -469,13 +609,11 @@ async function collectExternalPackageSources(opts) {
469
609
  if (rootEntry) {
470
610
  packageMap[packageName] = relative(rootDir, rootEntry);
471
611
  sourceEntries.set(packageName, rootEntry);
612
+ keyframesScanEntries.set(packageName, rootEntry);
472
613
  }
473
614
  }
474
615
  }
475
- const pkgFiles = discoverFiles(srcDir, srcDir, [], extensionsSet).filter((absPath) => {
476
- const inPkg = relative(srcDir, absPath);
477
- return !PACKAGE_SRC_EXCLUDES.some((pattern) => inPkg.includes(pattern));
478
- });
616
+ const pkgFiles = walkPackageSources(srcDir, extensionsSet);
479
617
  staleDist = distEntryIsStale(absEntry, srcDir, pkgFiles);
480
618
  for (const pkgFile of pkgFiles) {
481
619
  const relPath = relative(rootDir, pkgFile);
@@ -503,8 +641,10 @@ async function collectExternalPackageSources(opts) {
503
641
  } else {
504
642
  const outputDir = dirname(absEntry);
505
643
  packageDirs.push(outputDir);
506
- dirOwners[outputDir] ??= specifier;
644
+ claimDir(outputDir, specifier);
645
+ onPackageResolved?.(specifier, outputDir);
507
646
  packageMap[specifier] = relative(rootDir, absEntry);
647
+ keyframesScanEntries.set(specifier, absEntry);
508
648
  const outputExtensions = new Set(extensionsSet);
509
649
  outputExtensions.add(extname(absEntry));
510
650
  const outputFiles = discoverFiles(outputDir, outputDir, [], outputExtensions).filter((file) => {
@@ -546,12 +686,71 @@ async function collectExternalPackageSources(opts) {
546
686
  entries,
547
687
  packageMap,
548
688
  sourceEntries,
689
+ keyframesScanEntries,
549
690
  packageDirs,
550
- dirOwners,
691
+ dirOwnerSets,
551
692
  fileOwners,
552
693
  outcomes
553
694
  };
554
695
  }
696
+ /** First-declared specifier per package dir — the single-value ownership
697
+ * view correlation consumers key on, derived from `dirOwnerSets`. */
698
+ function firstOwners(dirOwnerSets) {
699
+ const owners = {};
700
+ for (const [dir, specifiers] of Object.entries(dirOwnerSets)) if (specifiers.length > 0) owners[dir] = specifiers[0];
701
+ return owners;
702
+ }
703
+ /**
704
+ * Excise a set of rejected specifiers from a collection result ATOMICALLY
705
+ * (openspec: external-source-watch-ingestion, design D5): the returned copy
706
+ * carries no entries, package-map targets, source entries, package dirs, or
707
+ * ownership for the rejected packages — a package dir is removed only when
708
+ * EVERY specifier that claimed it is rejected (a dir-level rejection, such
709
+ * as the cross-volume gate, always rejects them together). Outcome records
710
+ * are reporting inputs, not membership, and pass through untouched.
711
+ */
712
+ function excludeCollectedPackages(collected, rejectedSpecifiers, rootDir) {
713
+ if (rejectedSpecifiers.size === 0) return collected;
714
+ const rejectedDirs = Object.entries(collected.dirOwnerSets).filter(([, specs]) => specs.every((s) => rejectedSpecifiers.has(s))).map(([dir]) => dir);
715
+ const underRejectedDir = (absPath) => rejectedDirs.some((dir) => isPathWithinRoot(dir, absPath));
716
+ const targetRejected = (specifier, absTarget) => rejectedSpecifiers.has(specifier) || underRejectedDir(absTarget);
717
+ const packageMap = {};
718
+ for (const [specifier, relTarget] of Object.entries(collected.packageMap)) {
719
+ if (targetRejected(specifier, resolve(rootDir, relTarget))) continue;
720
+ packageMap[specifier] = relTarget;
721
+ }
722
+ const sourceEntries = /* @__PURE__ */ new Map();
723
+ for (const [specifier, absEntry] of collected.sourceEntries) {
724
+ if (targetRejected(specifier, absEntry)) continue;
725
+ sourceEntries.set(specifier, absEntry);
726
+ }
727
+ const keyframesScanEntries = /* @__PURE__ */ new Map();
728
+ for (const [specifier, absEntry] of collected.keyframesScanEntries) {
729
+ if (targetRejected(specifier, absEntry)) continue;
730
+ keyframesScanEntries.set(specifier, absEntry);
731
+ }
732
+ const dirOwnerSets = {};
733
+ for (const [dir, specs] of Object.entries(collected.dirOwnerSets)) {
734
+ const kept = specs.filter((s) => !rejectedSpecifiers.has(s));
735
+ if (kept.length === 0) continue;
736
+ dirOwnerSets[dir] = kept;
737
+ }
738
+ const fileOwners = {};
739
+ for (const [relPath, owner] of Object.entries(collected.fileOwners)) {
740
+ if (rejectedSpecifiers.has(owner)) continue;
741
+ fileOwners[relPath] = owner;
742
+ }
743
+ return {
744
+ entries: collected.entries.filter((entry) => !rejectedSpecifiers.has(collected.fileOwners[entry.path])),
745
+ packageMap,
746
+ sourceEntries,
747
+ keyframesScanEntries,
748
+ packageDirs: collected.packageDirs.filter((dir) => !rejectedDirs.includes(dir)),
749
+ dirOwnerSets,
750
+ fileOwners,
751
+ outcomes: collected.outcomes
752
+ };
753
+ }
555
754
  /**
556
755
  * Extract external DS package names from inheritance declarations in the
557
756
  * system file.
@@ -921,7 +1120,7 @@ function buildSourceTokenIndex(opts) {
921
1120
  if (existsSync(join(packageRoot, "package.json"))) addOwnerDir(packageRoot, specifier);
922
1121
  }
923
1122
  for (const [modulePath, exports] of Object.entries(manifests)) {
924
- const owner = realDirOwners.find(({ dir }) => modulePath === dir || modulePath.startsWith(dir + sep));
1123
+ const owner = realDirOwners.find(({ dir }) => isPathWithinRoot(dir, modulePath));
925
1124
  if (!owner) continue;
926
1125
  let tokens = index.get(owner.specifier);
927
1126
  if (!tokens) {
@@ -1062,16 +1261,42 @@ function postProcessCss(css, opts) {
1062
1261
  /** Stable code for selector forms with no substitutable subject.
1063
1262
  * Mirrors the Rust constant in `extract-v2/src/eval.rs`. */
1064
1263
  const SELECTOR_UNSUPPORTED_SUBJECT = "animus.selector.unsupported-subject";
1264
+ /** Unresolved-parent chain-drop message (mirrors the Rust diagnostic in
1265
+ * `extract-v2` — the ONE encoding of this message shape; consumers match
1266
+ * through the helpers below, never their own regex copies). */
1267
+ const UNRESOLVED_PARENT_RE = /chain dropped: could not resolve parent component '([^']+)'/;
1268
+ /** True when the diagnostic reports a chain dropped for an unresolved
1269
+ * parent component. */
1270
+ function isUnresolvedParentDrop(diagnostic) {
1271
+ return typeof diagnostic?.message === "string" && UNRESOLVED_PARENT_RE.test(diagnostic.message);
1272
+ }
1273
+ /** The parent binding named by an unresolved-parent drop, or null when the
1274
+ * diagnostic is not one. */
1275
+ function unresolvedParentName(diagnostic) {
1276
+ if (typeof diagnostic?.message !== "string") return null;
1277
+ return UNRESOLVED_PARENT_RE.exec(diagnostic.message)?.[1] ?? null;
1278
+ }
1065
1279
  /** True when the selector string carries at least one substitutable `&`
1066
1280
  * subject outside quoted text (mirrors the Rust `selector_subject` walk —
1067
1281
  * ancestor, leading, and repeated subjects all count; a `&` inside a
1068
1282
  * quoted attribute value does not). */
1069
1283
  function hasSelectorSubject(value) {
1070
1284
  let quote = null;
1071
- for (const c of value) if (quote !== null) {
1072
- if (c === quote) quote = null;
1073
- } else if (c === "\"" || c === "'") quote = c;
1074
- else if (c === "&") return true;
1285
+ let escaped = false;
1286
+ for (const c of value) {
1287
+ if (escaped) {
1288
+ escaped = false;
1289
+ continue;
1290
+ }
1291
+ if (c === "\\") {
1292
+ escaped = true;
1293
+ continue;
1294
+ }
1295
+ if (quote !== null) {
1296
+ if (c === quote) quote = null;
1297
+ } else if (c === "\"" || c === "'") quote = c;
1298
+ else if (c === "&") return true;
1299
+ }
1075
1300
  return false;
1076
1301
  }
1077
1302
  /**
@@ -1644,6 +1869,79 @@ async function preprocessMdx(source, filename) {
1644
1869
  }
1645
1870
  }
1646
1871
  //#endregion
1872
+ //#region pipeline/replacement-plans.ts
1873
+ const snapshotByManifest = /* @__PURE__ */ new WeakMap();
1874
+ function snapshotFilePlans(manifest) {
1875
+ const memoKey = manifest !== null && typeof manifest === "object" ? manifest : null;
1876
+ if (memoKey) {
1877
+ const cached = snapshotByManifest.get(memoKey);
1878
+ if (cached) return cached;
1879
+ }
1880
+ const entriesByFile = /* @__PURE__ */ new Map();
1881
+ for (const [id, desc] of Object.entries(manifest?.components ?? {})) {
1882
+ const file = desc.file;
1883
+ if (!file) continue;
1884
+ const list = entriesByFile.get(file) ?? [];
1885
+ list.push([id, desc.replacement ?? ""]);
1886
+ entriesByFile.set(file, list);
1887
+ }
1888
+ const snapshot = /* @__PURE__ */ new Map();
1889
+ for (const [file, entries] of entriesByFile) {
1890
+ entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1891
+ snapshot.set(file, JSON.stringify(entries));
1892
+ }
1893
+ if (memoKey) snapshotByManifest.set(memoKey, snapshot);
1894
+ return snapshot;
1895
+ }
1896
+ /**
1897
+ * Rel paths whose plan changed between two snapshots: absent→present,
1898
+ * present→absent, and any change to membership or replacement content.
1899
+ * `exclude` drops one path (the changed file itself on the edit path — its
1900
+ * own modules are already in the update set).
1901
+ */
1902
+ function diffFilePlans(prev, next, opts) {
1903
+ const changed = /* @__PURE__ */ new Set();
1904
+ for (const [file, plan] of next) if (prev.get(file) !== plan) changed.add(file);
1905
+ for (const file of prev.keys()) if (!next.has(file)) changed.add(file);
1906
+ if (opts?.exclude) changed.delete(opts.exclude);
1907
+ return [...changed].sort();
1908
+ }
1909
+ /** Domain prefix keeping the epoch hash disjoint from every other
1910
+ * contentHash use in the pipeline (design D5). */
1911
+ const REPLACEMENT_PLANS_DOMAIN = "animus-replacement-plans-v1\0";
1912
+ /** Section marker for the served-dependency witness. Leads with NUL: every
1913
+ * plan entry ends `…\0` and neither file paths nor plan JSON contain NUL,
1914
+ * so `\0\0` is unreachable from entry serialization alone — a snapshot can
1915
+ * never forge the witness section. */
1916
+ const SERVED_DEPENDENCY_DOMAIN = "\0animus-served-dependency-v1\0";
1917
+ /**
1918
+ * Canonical replacement epoch: contentHash over the domain-prefixed, sorted
1919
+ * canonical serialization of a file-plan snapshot — each entry contributes
1920
+ * `file + '\0' + plan + '\0'` (unambiguous: neither file paths nor the
1921
+ * JSON plan strings contain NUL). Byte-identical plans → identical epoch;
1922
+ * any membership/replacement/absent↔present change moves it.
1923
+ *
1924
+ * `servedDependencyWitness` extends the epoch to content the plans cannot
1925
+ * see but restored modules depend on: transformed modules import the
1926
+ * session's served system-props artifact, so a consumer passes that
1927
+ * artifact's content here and an offline change to it moves the epoch —
1928
+ * invalidating persistent-cache snapshots that reference the old epoch
1929
+ * artifact. Absence and empty-string are distinct values.
1930
+ *
1931
+ * This is THE epoch definition — integrations consume this hash (or the
1932
+ * snapshot diff) and never define their own. Epoch values compare only for
1933
+ * equality (a moved value invalidates), so the serialization is free to
1934
+ * evolve with a domain bump: sessions running different serializations
1935
+ * simply disagree once and rebuild.
1936
+ */
1937
+ function hashReplacementPlans(snapshot, servedDependencyWitness) {
1938
+ const entries = [...snapshot.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1939
+ let serialized = REPLACEMENT_PLANS_DOMAIN;
1940
+ for (const [file, plan] of entries) serialized += file + "\0" + plan + "\0";
1941
+ if (servedDependencyWitness !== void 0) serialized += SERVED_DEPENDENCY_DOMAIN + servedDependencyWitness;
1942
+ return contentHash(serialized);
1943
+ }
1944
+ //#endregion
1647
1945
  //#region pipeline/utils.ts
1648
1946
  /** Convert camelCase CSS property names to kebab-case. */
1649
1947
  function camelToKebab(str) {
@@ -1695,4 +1993,4 @@ function realpathThroughNearestExistingAncestor(path) {
1695
1993
  }
1696
1994
  }
1697
1995
  //#endregion
1698
- export { ANIMUS_LAYERS, DEFAULT_EXTENSIONS, KEYFRAMES_EXPORT_COLLISION, KEYFRAMES_EXTERNAL_ENTRY_FAILED, RETIRED_ENGINE_MESSAGE, applyPrefix, applyUnitFallback, assembleStylesheet, assertNoRetiredEngineSelection, buildAnalysisInputs, buildAnalyzeProjectArgs, buildDynamicPropConfig, buildPathAliasesJson, buildSystemPropsModule, camelToKebab, clearEngineCache, collectExternalPackageSources, contentHash, createV2EngineApi, discoverFiles, enforceExternalTokenContracts, extractSystemFilePackages, findAssetSpecifiers, findPackageRoot, formatRustTimingWaterfall, loadSystemConfig, mergeExternalKeyframes, postProcessCss, preprocessMdx, readTsconfigAliasPairs, resolveAssetFile, resolveLightningTargets, resolveThroughPathAliases, runProjectAnalysis, serializeStaticCss, staleDistIncludesMessage, stripLeadingLayerDeclaration, substituteAssetPlaceholders, surfaceManifestDiagnostics, toWatchKeys, unresolvableIncludesMessage, validateLayerOrder };
1996
+ export { ANIMUS_LAYERS, DEFAULT_EXTENSIONS, KEYFRAMES_EXPORT_COLLISION, KEYFRAMES_EXTERNAL_ENTRY_FAILED, RETIRED_ENGINE_MESSAGE, applyPrefix, applyUnitFallback, assembleStylesheet, assertNoRetiredEngineSelection, buildAnalysisInputs, buildAnalyzeProjectArgs, buildDynamicPropConfig, buildPathAliasesJson, buildSystemPropsModule, camelToKebab, clearEngineCache, collectExternalPackageSources, contentHash, createSourceIdentity, createV2EngineApi, diffFilePlans, discoverFiles, enforceExternalTokenContracts, excludeCollectedPackages, extractSystemFilePackages, findAssetSpecifiers, findPackageRoot, firstOwners, formatRustTimingWaterfall, hashReplacementPlans, isExcludedPackageRelativePath, isPathWithinRoot, isUnresolvedParentDrop, loadSystemConfig, mergeExternalKeyframes, postProcessCss, preprocessMdx, readTsconfigAliasPairs, resolveAbsolutePathSpecifier, resolveAssetFile, resolveLightningTargets, resolveThroughPathAliases, runProjectAnalysis, serializeStaticCss, sharesVolumeRoot, snapshotFilePlans, staleDistIncludesMessage, stripLeadingLayerDeclaration, substituteAssetPlaceholders, surfaceManifestDiagnostics, toWatchKeys, unresolvableIncludesMessage, unresolvedParentName, validateLayerOrder, walkPackageSources };
@@ -15,6 +15,16 @@ export type ManifestDiagnostic = {
15
15
  /** Stable code for selector forms with no substitutable subject.
16
16
  * Mirrors the Rust constant in `extract-v2/src/eval.rs`. */
17
17
  export declare const SELECTOR_UNSUPPORTED_SUBJECT = "animus.selector.unsupported-subject";
18
+ /** True when the diagnostic reports a chain dropped for an unresolved
19
+ * parent component. */
20
+ export declare function isUnresolvedParentDrop(diagnostic: {
21
+ message?: unknown;
22
+ }): boolean;
23
+ /** The parent binding named by an unresolved-parent drop, or null when the
24
+ * diagnostic is not one. */
25
+ export declare function unresolvedParentName(diagnostic: {
26
+ message?: unknown;
27
+ }): string | null;
18
28
  export interface DiagnosticPolicy {
19
29
  /** When true, error-severity diagnostics throw instead of warning. */
20
30
  strict?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-diagnostics.d.ts","sourceRoot":"","sources":["../pipeline/manifest-diagnostics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;6EACyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;sBACkB;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;6DAC6D;AAC7D,eAAO,MAAM,4BAA4B,wCACF,CAAC;AAExC,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;gEAC4D;IAC5D,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAChC;AAED;;;wCAGwC;AACxC,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAYzD;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7C,kBAAkB,EAAE,CAuBtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE;IAAE,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAA;CAAE,EAChD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAC/B,MAAM,GAAE,gBAAqB,GAC5B,IAAI,CA+BN"}
1
+ {"version":3,"file":"manifest-diagnostics.d.ts","sourceRoot":"","sources":["../pipeline/manifest-diagnostics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;6EACyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;sBACkB;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;6DAC6D;AAC7D,eAAO,MAAM,4BAA4B,wCACF,CAAC;AAQxC;wBACwB;AACxB,wBAAgB,sBAAsB,CAAC,UAAU,EAAE;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAKV;AAED;6BAC6B;AAC7B,wBAAgB,oBAAoB,CAAC,UAAU,EAAE;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,MAAM,GAAG,IAAI,CAGhB;AAED,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;gEAC4D;IAC5D,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAChC;AAED;;;wCAGwC;AACxC,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAqBzD;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7C,kBAAkB,EAAE,CAuBtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE;IAAE,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAA;CAAE,EAChD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAC/B,MAAM,GAAE,gBAAqB,GAC5B,IAAI,CA+BN"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * File-plan snapshot/diff — the invalidation candidate source shared by
3
+ * every analysis path (openspec: dev-transform-coherence,
4
+ * dev-served-transform-coherence). A file's plan is the keyed, ordered set
5
+ * of (component id, replacement) pairs the manifest holds for it. A file
6
+ * with NO entries is ABSENT from the snapshot — a distinct state from a
7
+ * present entry whose replacement is empty, so raw↔extracted transitions
8
+ * are plan changes like any other.
9
+ *
10
+ * Lives in the shared pipeline package so the Vite diff consumer and the
11
+ * Next (webpack/Turbopack) epoch consumers share ONE semantic definition
12
+ * (design D5, next-webpack-served-transform-coherence).
13
+ */
14
+ export type FilePlanSnapshot = Map<string, string>;
15
+ export declare function snapshotFilePlans(manifest: any): FilePlanSnapshot;
16
+ /**
17
+ * Rel paths whose plan changed between two snapshots: absent→present,
18
+ * present→absent, and any change to membership or replacement content.
19
+ * `exclude` drops one path (the changed file itself on the edit path — its
20
+ * own modules are already in the update set).
21
+ */
22
+ export declare function diffFilePlans(prev: FilePlanSnapshot, next: FilePlanSnapshot, opts?: {
23
+ exclude?: string;
24
+ }): string[];
25
+ /**
26
+ * Canonical replacement epoch: contentHash over the domain-prefixed, sorted
27
+ * canonical serialization of a file-plan snapshot — each entry contributes
28
+ * `file + '\0' + plan + '\0'` (unambiguous: neither file paths nor the
29
+ * JSON plan strings contain NUL). Byte-identical plans → identical epoch;
30
+ * any membership/replacement/absent↔present change moves it.
31
+ *
32
+ * `servedDependencyWitness` extends the epoch to content the plans cannot
33
+ * see but restored modules depend on: transformed modules import the
34
+ * session's served system-props artifact, so a consumer passes that
35
+ * artifact's content here and an offline change to it moves the epoch —
36
+ * invalidating persistent-cache snapshots that reference the old epoch
37
+ * artifact. Absence and empty-string are distinct values.
38
+ *
39
+ * This is THE epoch definition — integrations consume this hash (or the
40
+ * snapshot diff) and never define their own. Epoch values compare only for
41
+ * equality (a moved value invalidates), so the serialization is free to
42
+ * evolve with a domain bump: sessions running different serializations
43
+ * simply disagree once and rebuild.
44
+ */
45
+ export declare function hashReplacementPlans(snapshot: FilePlanSnapshot, servedDependencyWitness?: string): string;
46
+ //# sourceMappingURL=replacement-plans.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replacement-plans.d.ts","sourceRoot":"","sources":["../pipeline/replacement-plans.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAQnD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,CA4BjE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,gBAAgB,EACtB,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1B,MAAM,EAAE,CAUV;AAYD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,uBAAuB,CAAC,EAAE,MAAM,GAC/B,MAAM,CAYR"}
@@ -0,0 +1,93 @@
1
+ import path from 'node:path';
2
+ /** Platform path API (`path` / `path.win32` / `path.posix`). Structural
3
+ * local alias — the packed consumers' @types/node need not export
4
+ * `PlatformPath` from 'node:path' (older versions don't, which fails
5
+ * their declaration type-check against this file's emitted d.ts). */
6
+ type PlatformPath = typeof path.win32;
7
+ /**
8
+ * SourceId derivation authority + allowlist membership (openspec:
9
+ * external-source-watch-ingestion, design D1/D2/D5).
10
+ *
11
+ * One shared derivation produces the canonical source identity consumed by
12
+ * discovery, watch ingestion, ownership, deletion pruning, and diagnostics
13
+ * — raw event paths are lookup INPUTS only, so event spelling (symlink
14
+ * alias vs canonical, lexical vs realpath) can never fork identity.
15
+ *
16
+ * The containment and volume helpers are pure and path-API-injectable so
17
+ * Windows semantics are unit-testable via `path.win32` on any host; runtime
18
+ * callers use the ambient platform implementation.
19
+ */
20
+ /**
21
+ * Structural containment: `target` is the root itself or a descendant of it,
22
+ * decided by `relative()` shape — never by string prefixing, so `/ui` can
23
+ * never claim `/ui-old`, and a cross-drive win32 target (whose relative()
24
+ * result is absolute) is never contained.
25
+ */
26
+ export declare function isPathWithinRoot(root: string, target: string, pathApi?: PlatformPath): boolean;
27
+ /**
28
+ * Cross-volume gate (design D5): two paths share a platform path root
29
+ * (win32 drive letter or UNC share; always true on posix). Compared
30
+ * case-insensitively — win32 drive letters are case-insensitive.
31
+ */
32
+ export declare function sharesVolumeRoot(a: string, b: string, pathApi?: PlatformPath): boolean;
33
+ /** One resolved source identity. */
34
+ export interface ResolvedSourceId {
35
+ /**
36
+ * The rootDir-relative source key all persistent analysis state is
37
+ * indexed by — kit files keep their `..`-prefixed keys. Derived from
38
+ * CANONICAL paths (canonical suffix re-attached to the root's
39
+ * as-registered spelling) so it always matches the key discovery
40
+ * produced, whatever spelling the event carried.
41
+ */
42
+ sourceKey: string;
43
+ /** Canonical form of the owning external root; null when the project
44
+ * root itself owns the file. */
45
+ owningRoot: string | null;
46
+ /** Canonical path of the file relative to its owning root (equals
47
+ * `sourceKey` for project-root members) — the input for package-relative
48
+ * exclusion filters. */
49
+ pathInRoot: string;
50
+ }
51
+ /**
52
+ * The per-generation identity handle (design D2): canonical roots are
53
+ * realpath'd once at registration, and every alias→SourceId association
54
+ * observed while a file existed is recorded so DELETION resolves through
55
+ * the cache — never through fresh canonicalization of a gone path.
56
+ */
57
+ export interface SourceIdentity {
58
+ readonly rootDir: string;
59
+ readonly canonicalRootDir: string;
60
+ /**
61
+ * Register a discovery-resolved external source root. Both the
62
+ * as-registered (lexical) and canonical forms become membership
63
+ * witnesses; duplicate spellings of one canonical root collapse into the
64
+ * first registration. Returns the canonical form.
65
+ */
66
+ registerExternalRoot(root: string): string;
67
+ /** Canonical forms of every registered external root (registration order). */
68
+ externalRoots(): string[];
69
+ /**
70
+ * Resolve an EXISTING path to its source identity: canonicalize, then
71
+ * re-authorize containment against the canonical form of the allowed
72
+ * trees (a nested symlink escaping every allowed tree is rejected).
73
+ * Records the observed spelling (and the canonical form) as deletion
74
+ * aliases. Returns null for non-members and paths that cannot be
75
+ * canonicalized (vanished between event and resolution).
76
+ */
77
+ resolveSourceId(inputPath: string): ResolvedSourceId | null;
78
+ /**
79
+ * Resolve a DELETED path through the recorded alias associations only —
80
+ * a spelling never observed while the file existed resolves nothing.
81
+ */
82
+ resolveDeletedSourceId(inputPath: string): ResolvedSourceId | null;
83
+ /**
84
+ * The canonical external root structurally containing `inputPath`
85
+ * (matched against both root forms; the path itself need not exist and
86
+ * no file identity is resolved) — the watch pass's dirty-root witness
87
+ * for directory-granularity reports. Null when no root contains it.
88
+ */
89
+ containingExternalRoot(inputPath: string): string | null;
90
+ }
91
+ export declare function createSourceIdentity(rootDir: string): SourceIdentity;
92
+ export {};
93
+ //# sourceMappingURL=source-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-identity.d.ts","sourceRoot":"","sources":["../pipeline/source-identity.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;sEAGsE;AACtE,KAAK,YAAY,GAAG,OAAO,IAAI,CAAC,KAAK,CAAC;AAEtC;;;;;;;;;;;;GAYG;AAEH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,YAAmB,GAC3B,OAAO,CAQT;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,OAAO,GAAE,YAAmB,GAC3B,OAAO,CAIT;AAED,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;qCACiC;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;6BAEyB;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC;;;;;OAKG;IACH,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3C,8EAA8E;IAC9E,aAAa,IAAI,MAAM,EAAE,CAAC;IAC1B;;;;;;;OAOG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAC5D;;;OAGG;IACH,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAAC;IACnE;;;;;OAKG;IACH,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CAC1D;AAeD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA0GpE"}