@amalgm/shell 0.1.71 → 0.1.72

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.
@@ -16,7 +16,7 @@ import { AcceptedInboxResultIndex } from "./detection/accepted-result.js";
16
16
  import { NamedDetectRuntime } from "./detection/runtime.js";
17
17
  import { WORKSPACE_UUID as UUID, createFilesRegisterPorts, ensureWorkspaceBinding, ensureWorkspaceReference, pathExists, pathWithin, referenceWorkspaceId, selectKnownRegistrationId, workspaceBindingDir, } from "./files-register-host.js";
18
18
  import { applyRepositoryFiles, captureRepository, inspectRepositoryTransportFile, hasGitMarker, readCachedRepositoryIdentity, } from "./git-repository-host.js";
19
- import { canonicalizeGitIdentity, inspectGitRegistration, sameGitIdentity, } from "./git-registration-host.js";
19
+ import { inspectGitRegistration, sameGitIdentity, } from "./git-registration-host.js";
20
20
  import { projectMaterializedGraph } from "./materialized-graph.js";
21
21
  import { NodeWatchHost, } from "./watching/index.js";
22
22
  import { WireClient, WireRequestError } from "./wire-client.js";
@@ -2407,13 +2407,6 @@ async function reconcileGround(input) {
2407
2407
  }
2408
2408
  async function reconcileRoot(input) {
2409
2409
  const { identity, resourceId, rootUUID, rootName, rootPath, rootType, database, cacheDir, policy, bindingDir, suspects = null, evidence, } = input;
2410
- const scanSuspects = suspects !== null && rootType === "repo.git"
2411
- ? [...new Set(suspects.map((path) => {
2412
- const segments = path.split("/");
2413
- const marker = segments.indexOf(".git");
2414
- return marker < 0 ? path : segments.slice(0, marker).join("/");
2415
- }))].sort()
2416
- : suspects;
2417
2410
  const existingRows = input.existingRows
2418
2411
  ?? readRows(database).filter((row) => row.resourceId === resourceId && row.rootUUID === rootUUID);
2419
2412
  const existingByPath = new Map(existingRows.flatMap((row) => {
@@ -2453,6 +2446,43 @@ async function reconcileRoot(input) {
2453
2446
  }) : null,
2454
2447
  };
2455
2448
  }
2449
+ let rootRepositoryEvidence = null;
2450
+ let rootRepositoryInspectionFailed = false;
2451
+ if (rootType === "repo.git") {
2452
+ evidence && (evidence.gitInspections += 1);
2453
+ try {
2454
+ // A Git metadata ring can arrive before the corresponding worktree
2455
+ // ring. Git already knows every dirty tracked path, so those paths join
2456
+ // this observation and Card + Checkpoint cannot carry stale identity.
2457
+ rootRepositoryEvidence = inspectGitRegistration(rootPath);
2458
+ }
2459
+ catch {
2460
+ rootRepositoryInspectionFailed = true;
2461
+ }
2462
+ }
2463
+ const normalizedSuspects = suspects === null ? null : suspects.map((path) => {
2464
+ const segments = path.split("/");
2465
+ const marker = segments.indexOf(".git");
2466
+ return marker < 0 ? path : segments.slice(0, marker).join("/");
2467
+ });
2468
+ const changedTrackedPaths = rootRepositoryEvidence === null ? [] : [
2469
+ ...[...rootRepositoryEvidence.trackedPaths]
2470
+ .filter((path) => !existingByPath.has(path)),
2471
+ ...existingRows
2472
+ .filter((row) => row.relativePath !== ""
2473
+ && row.payloadVersion?.startsWith("git:")
2474
+ && !rootRepositoryEvidence.trackedPaths.has(row.relativePath))
2475
+ .map((row) => row.relativePath),
2476
+ ];
2477
+ const scanSuspects = suspects !== null && rootType === "repo.git"
2478
+ ? rootRepositoryInspectionFailed
2479
+ ? null
2480
+ : [...new Set([
2481
+ ...normalizedSuspects,
2482
+ ...rootRepositoryEvidence.dirtyPaths,
2483
+ ...changedTrackedPaths,
2484
+ ])].sort()
2485
+ : suspects;
2456
2486
  const entries = [];
2457
2487
  const referenceWorkspaceIds = new Set();
2458
2488
  const rootStats = lstatSync(rootPath);
@@ -2525,7 +2555,11 @@ async function reconcileRoot(input) {
2525
2555
  return [...new Set(paths)].sort();
2526
2556
  };
2527
2557
  const visit = async (directory, parentUUID, parentType, base = "", activeRepository = parentType === "repo.git"
2528
- ? { root: directory, evidencePaths: repositoryEvidencePaths(directory), evidence: null }
2558
+ ? {
2559
+ root: directory,
2560
+ evidencePaths: repositoryEvidencePaths(directory),
2561
+ evidence: directory === rootPath ? rootRepositoryEvidence : null,
2562
+ }
2529
2563
  : null, forceObserve = rootRailChanged) => {
2530
2564
  const children = readdirSync(directory, { withFileTypes: true })
2531
2565
  .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
@@ -2557,13 +2591,15 @@ async function reconcileRoot(input) {
2557
2591
  const stableDuringCompleteCatchUp = scanSuspects === null
2558
2592
  && existing !== undefined
2559
2593
  && reusableDuringCompleteCatchUp(existing, stats);
2594
+ const repositoryPath = activeRepository
2595
+ ? slashPath(relative(activeRepository.root, absolutePath))
2596
+ : null;
2560
2597
  const observe = forceObserve || existing === undefined
2598
+ || (repositoryPath !== null
2599
+ && activeRepository?.evidence?.dirtyPaths.has(repositoryPath) === true)
2561
2600
  || (scanSuspects === null ? !stableDuringCompleteCatchUp : touchesSuspicion(relativePath));
2562
2601
  if (!observe && scanSuspects === null)
2563
2602
  evidence && (evidence.metadataReused += 1);
2564
- const repositoryPath = activeRepository
2565
- ? slashPath(relative(activeRepository.root, absolutePath))
2566
- : null;
2567
2603
  if (observe && repositoryPath && activeRepository && !activeRepository.evidence) {
2568
2604
  evidence && (evidence.gitInspections += 1);
2569
2605
  try {
@@ -2573,7 +2609,9 @@ async function reconcileRoot(input) {
2573
2609
  // Classification already settled from the marker. If Git cannot
2574
2610
  // provide clean-index evidence, read current worktree bytes; Card +
2575
2611
  // Checkpoint capture below remains the transfer retry boundary.
2576
- activeRepository.evidence = { cleanLeaves: new Map() };
2612
+ activeRepository.evidence = {
2613
+ trackedPaths: new Set(), cleanLeaves: new Map(), dirtyPaths: new Set(),
2614
+ };
2577
2615
  }
2578
2616
  }
2579
2617
  const indexed = observe && repositoryPath
@@ -2751,14 +2789,73 @@ async function reconcileRoot(input) {
2751
2789
  ? entry.relativePath.slice(repository.relativePath.length + 1)
2752
2790
  : entry.relativePath;
2753
2791
  const ownedByPath = new Map(ownedEntries.map((entry) => [repositoryPath(entry), entry]));
2754
- let repoIdentity = ownedEntries
2755
- .map((entry) => ({
2756
- path: repositoryPath(entry),
2757
- uuid: entry.uuid,
2758
- type: entry.type,
2759
- payloadVersion: entry.payloadVersion,
2760
- }))
2761
- .sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
2792
+ const refreshRepositoryIdentity = async () => {
2793
+ const registration = inspectGitRegistration(repository.absolutePath);
2794
+ const removed = new Set();
2795
+ for (const [path, clean] of registration.cleanLeaves) {
2796
+ const entry = ownedByPath.get(path);
2797
+ if (!entry) {
2798
+ throw new Error(`repository indexed path ${path} has no registered identity`);
2799
+ }
2800
+ entry.type = clean.type;
2801
+ entry.payloadVersion = clean.payloadVersion;
2802
+ entry.linkTarget = undefined;
2803
+ }
2804
+ for (const path of registration.dirtyPaths) {
2805
+ const entry = ownedByPath.get(path);
2806
+ const absolutePath = join(repository.absolutePath, ...path.split("/"));
2807
+ if (!existsSync(absolutePath)) {
2808
+ if (entry)
2809
+ removed.add(path);
2810
+ continue;
2811
+ }
2812
+ if (!entry) {
2813
+ throw new Error(`repository dirty path ${path} has no registered identity`);
2814
+ }
2815
+ const stats = lstatSync(absolutePath);
2816
+ if (stats.isSymbolicLink()) {
2817
+ const target = readlinkSync(absolutePath);
2818
+ entry.type = "link";
2819
+ entry.payloadVersion = sha256Hex(Buffer.from(target, "utf8"));
2820
+ entry.linkTarget = target;
2821
+ }
2822
+ else if (stats.isFile()) {
2823
+ const current = await captureContentFile(absolutePath, stats, null);
2824
+ entry.type = classifyFile({ binary: current.binary });
2825
+ entry.payloadVersion = current.contentHash;
2826
+ entry.linkTarget = undefined;
2827
+ }
2828
+ else {
2829
+ throw new Error(`repository dirty path ${path} is not a file or link`);
2830
+ }
2831
+ entry.contentVerifiedAtMs = Date.now();
2832
+ Object.assign(entry, statFingerprint(stats));
2833
+ evidence && (evidence.contentReads += 1);
2834
+ }
2835
+ if (removed.size > 0) {
2836
+ for (const path of removed) {
2837
+ const entry = ownedByPath.get(path);
2838
+ if (!entry)
2839
+ continue;
2840
+ const index = entries.indexOf(entry);
2841
+ if (index >= 0)
2842
+ entries.splice(index, 1);
2843
+ ownedByPath.delete(path);
2844
+ }
2845
+ }
2846
+ return [...ownedByPath.entries()]
2847
+ .map(([path, entry]) => ({
2848
+ path,
2849
+ uuid: entry.uuid,
2850
+ type: entry.type,
2851
+ payloadVersion: entry.payloadVersion,
2852
+ }))
2853
+ .sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
2854
+ };
2855
+ let repoIdentity = await refreshRepositoryIdentity();
2856
+ /* The rows and the transport identity are derived from the same fresh
2857
+ * worktree evidence. A later Watch ring for one of these dirty paths is
2858
+ * therefore ordinary equality, not a transport-only echo. */
2762
2859
  const priorRow = existingByPath.get(repository.relativePath);
2763
2860
  if (priorRow?.type === "repo.git") {
2764
2861
  repository.payloadVersion = priorRow.payloadVersion;
@@ -2799,7 +2896,7 @@ async function reconcileRoot(input) {
2799
2896
  let coherent = false;
2800
2897
  for (let attempt = 0; attempt < 3; attempt += 1) {
2801
2898
  captured = captureRepository(repository.absolutePath, repoIdentity, prior, previousIdentity);
2802
- const after = canonicalizeGitIdentity(repoIdentity, inspectGitRegistration(repository.absolutePath));
2899
+ const after = await refreshRepositoryIdentity();
2803
2900
  if (sameGitIdentity(after, repoIdentity)) {
2804
2901
  coherent = true;
2805
2902
  break;