@indigoai-us/hq-cloud 6.11.11 → 6.11.13
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/bin/sync-runner-company.d.ts +35 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -0
- package/dist/bin/sync-runner-company.js +290 -0
- package/dist/bin/sync-runner-company.js.map +1 -0
- package/dist/bin/sync-runner-events.d.ts +12 -0
- package/dist/bin/sync-runner-events.d.ts.map +1 -0
- package/dist/bin/sync-runner-events.js +12 -0
- package/dist/bin/sync-runner-events.js.map +1 -0
- package/dist/bin/sync-runner-planning.d.ts +53 -0
- package/dist/bin/sync-runner-planning.d.ts.map +1 -0
- package/dist/bin/sync-runner-planning.js +59 -0
- package/dist/bin/sync-runner-planning.js.map +1 -0
- package/dist/bin/sync-runner-rollup.d.ts +24 -0
- package/dist/bin/sync-runner-rollup.d.ts.map +1 -0
- package/dist/bin/sync-runner-rollup.js +46 -0
- package/dist/bin/sync-runner-rollup.js.map +1 -0
- package/dist/bin/sync-runner-telemetry.d.ts +5 -0
- package/dist/bin/sync-runner-telemetry.d.ts.map +1 -0
- package/dist/bin/sync-runner-telemetry.js +5 -0
- package/dist/bin/sync-runner-telemetry.js.map +1 -0
- package/dist/bin/sync-runner-watch-loop.d.ts +17 -0
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -0
- package/dist/bin/sync-runner-watch-loop.js +372 -0
- package/dist/bin/sync-runner-watch-loop.js.map +1 -0
- package/dist/bin/sync-runner-watch-routes.d.ts +25 -0
- package/dist/bin/sync-runner-watch-routes.d.ts.map +1 -0
- package/dist/bin/sync-runner-watch-routes.js +74 -0
- package/dist/bin/sync-runner-watch-routes.js.map +1 -0
- package/dist/bin/sync-runner.d.ts +5 -54
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +76 -978
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +265 -11
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/reindex.d.ts.map +1 -1
- package/dist/cli/reindex.js +34 -17
- package/dist/cli/reindex.js.map +1 -1
- package/dist/cli/reindex.test.js +39 -5
- package/dist/cli/reindex.test.js.map +1 -1
- package/dist/cli/rescue-classify-ordering.test.js +75 -0
- package/dist/cli/rescue-classify-ordering.test.js.map +1 -1
- package/dist/cli/rescue-core.d.ts +45 -0
- package/dist/cli/rescue-core.d.ts.map +1 -1
- package/dist/cli/rescue-core.js +320 -170
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/share.d.ts +2 -1
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +276 -660
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +30 -0
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +28 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +541 -748
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +382 -1
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/cognito-auth.d.ts.map +1 -1
- package/dist/cognito-auth.js +55 -10
- package/dist/cognito-auth.js.map +1 -1
- package/dist/cognito-auth.test.js +61 -0
- package/dist/cognito-auth.test.js.map +1 -1
- package/dist/daemon-worker.d.ts +2 -2
- package/dist/daemon-worker.js +3 -3
- package/dist/daemon-worker.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +93 -6
- package/dist/journal.js.map +1 -1
- package/dist/journal.test.js +59 -0
- package/dist/journal.test.js.map +1 -1
- package/dist/machine-auth.test.js +60 -2
- package/dist/machine-auth.test.js.map +1 -1
- package/dist/object-io.d.ts +37 -1
- package/dist/object-io.d.ts.map +1 -1
- package/dist/object-io.js +149 -30
- package/dist/object-io.js.map +1 -1
- package/dist/object-io.test.js +121 -0
- package/dist/object-io.test.js.map +1 -1
- package/dist/operation-lock.d.ts +8 -8
- package/dist/operation-lock.d.ts.map +1 -1
- package/dist/operation-lock.js +99 -32
- package/dist/operation-lock.js.map +1 -1
- package/dist/operation-lock.test.js +51 -4
- package/dist/operation-lock.test.js.map +1 -1
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +8 -2
- package/dist/personal-vault.js.map +1 -1
- package/dist/personal-vault.test.js +34 -0
- package/dist/personal-vault.test.js.map +1 -1
- package/dist/prefix-coalesce.d.ts +20 -9
- package/dist/prefix-coalesce.d.ts.map +1 -1
- package/dist/prefix-coalesce.js +124 -28
- package/dist/prefix-coalesce.js.map +1 -1
- package/dist/prefix-coalesce.test.js +57 -2
- package/dist/prefix-coalesce.test.js.map +1 -1
- package/dist/remote-pull.d.ts +8 -3
- package/dist/remote-pull.d.ts.map +1 -1
- package/dist/remote-pull.js +85 -16
- package/dist/remote-pull.js.map +1 -1
- package/dist/remote-pull.test.js +213 -2
- package/dist/remote-pull.test.js.map +1 -1
- package/dist/s3.d.ts +2 -0
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +197 -116
- package/dist/s3.js.map +1 -1
- package/dist/s3.test.js +109 -0
- package/dist/s3.test.js.map +1 -1
- package/dist/scope-shrink.d.ts +3 -2
- package/dist/scope-shrink.d.ts.map +1 -1
- package/dist/scope-shrink.js +1 -1
- package/dist/scope-shrink.js.map +1 -1
- package/dist/skill-telemetry.d.ts +1 -1
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +69 -9
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/skill-telemetry.test.js +86 -0
- package/dist/skill-telemetry.test.js.map +1 -1
- package/dist/sync/event-sync.d.ts +6 -0
- package/dist/sync/event-sync.d.ts.map +1 -1
- package/dist/sync/event-sync.js +34 -1
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/event-sync.test.js +73 -0
- package/dist/sync/event-sync.test.js.map +1 -1
- package/dist/sync/metrics.d.ts +17 -1
- package/dist/sync/metrics.d.ts.map +1 -1
- package/dist/sync/metrics.js +32 -1
- package/dist/sync/metrics.js.map +1 -1
- package/dist/sync/metrics.test.js +74 -1
- package/dist/sync/metrics.test.js.map +1 -1
- package/dist/sync/pull-scope.d.ts.map +1 -1
- package/dist/sync/pull-scope.js +15 -7
- package/dist/sync/pull-scope.js.map +1 -1
- package/dist/sync/push-receiver.d.ts +12 -5
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +45 -17
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +67 -1
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/sync-core.d.ts +27 -0
- package/dist/sync-core.d.ts.map +1 -0
- package/dist/sync-core.js +54 -0
- package/dist/sync-core.js.map +1 -0
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +59 -6
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.js +74 -0
- package/dist/telemetry.test.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vault-client.d.ts.map +1 -1
- package/dist/vault-client.js +284 -36
- package/dist/vault-client.js.map +1 -1
- package/dist/vault-client.test.js +59 -0
- package/dist/vault-client.test.js.map +1 -1
- package/dist/watcher.d.ts +38 -20
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +155 -143
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +103 -0
- package/dist/watcher.test.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner-company.ts +350 -0
- package/src/bin/sync-runner-events.ts +25 -0
- package/src/bin/sync-runner-planning.ts +121 -0
- package/src/bin/sync-runner-rollup.ts +72 -0
- package/src/bin/sync-runner-telemetry.ts +8 -0
- package/src/bin/sync-runner-watch-loop.ts +443 -0
- package/src/bin/sync-runner-watch-routes.ts +86 -0
- package/src/bin/sync-runner.test.ts +298 -11
- package/src/bin/sync-runner.ts +99 -1054
- package/src/cli/reindex.test.ts +41 -3
- package/src/cli/reindex.ts +35 -19
- package/src/cli/rescue-classify-ordering.test.ts +81 -0
- package/src/cli/rescue-core.ts +400 -165
- package/src/cli/share.test.ts +38 -0
- package/src/cli/share.ts +420 -693
- package/src/cli/sync.test.ts +460 -1
- package/src/cli/sync.ts +788 -825
- package/src/cognito-auth.test.ts +77 -0
- package/src/cognito-auth.ts +73 -11
- package/src/daemon-worker.ts +3 -3
- package/src/index.ts +8 -0
- package/src/journal.test.ts +72 -0
- package/src/journal.ts +95 -8
- package/src/machine-auth.test.ts +64 -2
- package/src/object-io.test.ts +142 -0
- package/src/object-io.ts +183 -31
- package/src/operation-lock.test.ts +63 -4
- package/src/operation-lock.ts +99 -31
- package/src/personal-vault.test.ts +42 -0
- package/src/personal-vault.ts +8 -2
- package/src/prefix-coalesce.test.ts +71 -1
- package/src/prefix-coalesce.ts +155 -30
- package/src/remote-pull.test.ts +235 -1
- package/src/remote-pull.ts +106 -18
- package/src/s3.test.ts +126 -0
- package/src/s3.ts +237 -122
- package/src/scope-shrink.ts +6 -3
- package/src/skill-telemetry.test.ts +109 -0
- package/src/skill-telemetry.ts +82 -14
- package/src/sync/event-sync.test.ts +75 -0
- package/src/sync/event-sync.ts +54 -1
- package/src/sync/metrics.test.ts +81 -0
- package/src/sync/metrics.ts +59 -4
- package/src/sync/pull-scope.ts +23 -7
- package/src/sync/push-receiver.test.ts +73 -1
- package/src/sync/push-receiver.ts +56 -20
- package/src/sync-core.ts +58 -0
- package/src/telemetry.test.ts +85 -0
- package/src/telemetry.ts +69 -6
- package/src/types.ts +8 -0
- package/src/vault-client.test.ts +74 -0
- package/src/vault-client.ts +395 -43
- package/src/watcher.test.ts +117 -0
- package/src/watcher.ts +215 -174
package/dist/cli/rescue-core.js
CHANGED
|
@@ -616,6 +616,7 @@ function doRescue(cfg, env, out, err, setTmp) {
|
|
|
616
616
|
unchangedList,
|
|
617
617
|
appendLog,
|
|
618
618
|
out,
|
|
619
|
+
decisions: [],
|
|
619
620
|
actions: [],
|
|
620
621
|
};
|
|
621
622
|
// --- Pre-operation safety snapshot (BEFORE any destructive op) ---
|
|
@@ -645,16 +646,19 @@ function doRescue(cfg, env, out, err, setTmp) {
|
|
|
645
646
|
out(` snapshot complete (restore any file: cp "${backupDir}/<relpath>" "${hqRoot}/<relpath>")\n`);
|
|
646
647
|
}
|
|
647
648
|
// --- Walk + classify (PHASE 1: read-only) ---
|
|
648
|
-
// Classification mutates nothing on disk: every
|
|
649
|
-
//
|
|
650
|
-
//
|
|
649
|
+
// Classification mutates nothing on disk: every outcome is recorded as a
|
|
650
|
+
// typed RescueDecision. Destructive ops (delete, rescue-move,
|
|
651
|
+
// conflict-quarantine, symlink-drop) are built from those decisions and
|
|
652
|
+
// executed later (PHASE 2). This is the
|
|
651
653
|
// ordering-safety invariant: if classification throws partway through the
|
|
652
654
|
// wipe set (e.g. an unreadable entry, a future unhandled file shape), it
|
|
653
|
-
// aborts HERE — before a single file has been touched
|
|
654
|
-
// half-applied wipe like the
|
|
655
|
+
// aborts HERE — before a single file has been touched or even queued as a
|
|
656
|
+
// filesystem closure — instead of leaving a half-applied wipe like the
|
|
657
|
+
// pre-port bash classifier did (DEV-1767:
|
|
655
658
|
// `Error: Path is a directory` on a nested dir-symlink, thrown after ~10 core
|
|
656
|
-
// files were already deleted). Dry-run
|
|
657
|
-
//
|
|
659
|
+
// files were already deleted). Dry-run renders each typed decision inline as
|
|
660
|
+
// the walk records it, preserving the legacy progress/fault timing while
|
|
661
|
+
// still sharing the same classify path as the live pass.
|
|
658
662
|
out("\n");
|
|
659
663
|
if (wipeToplevel.length === 0) {
|
|
660
664
|
out("==> Wipe set is empty; nothing to process or overlay.\n");
|
|
@@ -734,8 +738,8 @@ function doRescue(cfg, env, out, err, setTmp) {
|
|
|
734
738
|
// Actions run in classification (walk) order — the same order the old
|
|
735
739
|
// interleaved walk mutated in — so per-file output and on-disk results are
|
|
736
740
|
// unchanged versus before the two-phase split.
|
|
737
|
-
|
|
738
|
-
|
|
741
|
+
ctx.actions = buildRescueActions(ctx);
|
|
742
|
+
applyClassifiedActions(ctx, backupDir);
|
|
739
743
|
// --- Back up preserve-subpaths to a mktemp shuttle ---
|
|
740
744
|
const shuttle = path.join(tmpdir, "preserve");
|
|
741
745
|
fs.mkdirSync(shuttle, { recursive: true });
|
|
@@ -966,6 +970,152 @@ function doRescue(cfg, env, out, err, setTmp) {
|
|
|
966
970
|
}
|
|
967
971
|
return { status: 0 };
|
|
968
972
|
}
|
|
973
|
+
function recordDecision(ctx, decision) {
|
|
974
|
+
ctx.decisions.push(decision);
|
|
975
|
+
if (ctx.cfg.dryRun)
|
|
976
|
+
renderDryRunDecision(ctx, decision);
|
|
977
|
+
switch (decision.kind) {
|
|
978
|
+
case "drop-reindex-symlink":
|
|
979
|
+
ctx.counts.symlinkDropped += 1;
|
|
980
|
+
return;
|
|
981
|
+
case "user-only":
|
|
982
|
+
ctx.counts.userOnly += 1;
|
|
983
|
+
return;
|
|
984
|
+
case "cloud-symlink-reconciled":
|
|
985
|
+
ctx.counts.cloudSymlinkReconciled += 1;
|
|
986
|
+
return;
|
|
987
|
+
case "drift-reconciled":
|
|
988
|
+
ctx.counts.driftReconciled += 1;
|
|
989
|
+
return;
|
|
990
|
+
case "unchanged-preserve":
|
|
991
|
+
case "unchanged-delete":
|
|
992
|
+
ctx.counts.unchanged += 1;
|
|
993
|
+
return;
|
|
994
|
+
default:
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
function applyClassifiedActions(ctx, backupDir) {
|
|
999
|
+
const completed = [];
|
|
1000
|
+
for (const action of ctx.actions) {
|
|
1001
|
+
try {
|
|
1002
|
+
action.run();
|
|
1003
|
+
completed.push(action);
|
|
1004
|
+
}
|
|
1005
|
+
catch (err) {
|
|
1006
|
+
const restored = rollbackCompletedActions(ctx, completed, backupDir);
|
|
1007
|
+
const manifest = writeApplyFailureManifest(ctx, backupDir, action, completed, restored, err);
|
|
1008
|
+
ctx.out("\n");
|
|
1009
|
+
ctx.out(`error: rescue apply failed during '${action.label}'.\n`);
|
|
1010
|
+
if (restored.length > 0) {
|
|
1011
|
+
ctx.out(`==> Rolled back ${restored.length} prior action path(s) from the safety snapshot.\n`);
|
|
1012
|
+
}
|
|
1013
|
+
if (manifest) {
|
|
1014
|
+
ctx.out(`==> Recovery manifest: ${manifest}\n`);
|
|
1015
|
+
}
|
|
1016
|
+
throw err;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
function rollbackCompletedActions(ctx, completed, backupDir) {
|
|
1021
|
+
if (!backupDir || !isDir(backupDir))
|
|
1022
|
+
return [];
|
|
1023
|
+
const rels = [];
|
|
1024
|
+
const seen = new Set();
|
|
1025
|
+
for (let i = completed.length - 1; i >= 0; i--) {
|
|
1026
|
+
const action = completed[i];
|
|
1027
|
+
for (let j = action.affectedRels.length - 1; j >= 0; j--) {
|
|
1028
|
+
const rel = action.affectedRels[j];
|
|
1029
|
+
if (rel && !seen.has(rel)) {
|
|
1030
|
+
seen.add(rel);
|
|
1031
|
+
rels.push(rel);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const restored = [];
|
|
1036
|
+
for (const rel of rels) {
|
|
1037
|
+
const snapshotPath = path.join(backupDir, rel);
|
|
1038
|
+
if (!lexists(snapshotPath))
|
|
1039
|
+
continue;
|
|
1040
|
+
const dest = path.join(ctx.hqRoot, rel);
|
|
1041
|
+
try {
|
|
1042
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
1043
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
1044
|
+
cpATo(snapshotPath, dest);
|
|
1045
|
+
restored.push(rel);
|
|
1046
|
+
}
|
|
1047
|
+
catch {
|
|
1048
|
+
// Recovery manifest below records any path that could not be restored.
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return restored;
|
|
1052
|
+
}
|
|
1053
|
+
function writeApplyFailureManifest(ctx, backupDir, failedAction, completed, restored, err) {
|
|
1054
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1055
|
+
const completedLines = completed.length === 0
|
|
1056
|
+
? [" (none)"]
|
|
1057
|
+
: completed.map((action) => {
|
|
1058
|
+
const paths = action.affectedRels.length > 0 ? action.affectedRels.join(", ") : "(no filesystem path)";
|
|
1059
|
+
return ` - ${action.label}: ${paths}`;
|
|
1060
|
+
});
|
|
1061
|
+
const restoredSet = new Set(restored);
|
|
1062
|
+
const unrestored = completed
|
|
1063
|
+
.flatMap((action) => action.affectedRels)
|
|
1064
|
+
.filter((rel, idx, arr) => rel && arr.indexOf(rel) === idx && !restoredSet.has(rel));
|
|
1065
|
+
const lines = [];
|
|
1066
|
+
lines.push("# HQ rescue apply recovery", "");
|
|
1067
|
+
lines.push(`Created: ${ctx.runTs}`);
|
|
1068
|
+
lines.push(`HQ root: ${ctx.hqRoot}`);
|
|
1069
|
+
lines.push(`Source: ${ctx.cfg.sourceRepo}@${ctx.cfg.ref} (${ctx.srcSha})`);
|
|
1070
|
+
lines.push(`Failed action: ${failedAction.label}`);
|
|
1071
|
+
lines.push(`Error: ${message}`);
|
|
1072
|
+
lines.push("");
|
|
1073
|
+
lines.push("## Completed Before Failure");
|
|
1074
|
+
lines.push(...completedLines);
|
|
1075
|
+
lines.push("");
|
|
1076
|
+
lines.push("## Failed Action Paths");
|
|
1077
|
+
if (failedAction.affectedRels.length > 0) {
|
|
1078
|
+
for (const rel of failedAction.affectedRels)
|
|
1079
|
+
lines.push(` - ${rel}`);
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
lines.push(" (no filesystem path)");
|
|
1083
|
+
}
|
|
1084
|
+
lines.push("");
|
|
1085
|
+
lines.push("## Rollback");
|
|
1086
|
+
if (restored.length > 0) {
|
|
1087
|
+
lines.push("Restored from the pre-op safety snapshot:");
|
|
1088
|
+
for (const rel of restored)
|
|
1089
|
+
lines.push(` - ${rel}`);
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
lines.push("No completed action paths were restored automatically.");
|
|
1093
|
+
}
|
|
1094
|
+
if (unrestored.length > 0) {
|
|
1095
|
+
lines.push("", "Completed action paths still requiring review:");
|
|
1096
|
+
for (const rel of unrestored)
|
|
1097
|
+
lines.push(` - ${rel}`);
|
|
1098
|
+
}
|
|
1099
|
+
if (backupDir && isDir(backupDir)) {
|
|
1100
|
+
lines.push("");
|
|
1101
|
+
lines.push("Manual restore examples:");
|
|
1102
|
+
lines.push(` cp "${backupDir}/<relpath>" "${ctx.hqRoot}/<relpath>"`);
|
|
1103
|
+
lines.push(` rsync -a "${backupDir}/" "${ctx.hqRoot}/"`);
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
lines.push("", "No pre-op safety snapshot was available for automatic restore.");
|
|
1107
|
+
}
|
|
1108
|
+
const manifestDir = path.join(ctx.hqRoot, ".hq", "rescue-recovery");
|
|
1109
|
+
const manifestPath = path.join(manifestDir, `rescue-apply-failure-${ctx.runTs}.md`);
|
|
1110
|
+
try {
|
|
1111
|
+
fs.mkdirSync(manifestDir, { recursive: true });
|
|
1112
|
+
fs.writeFileSync(manifestPath, lines.join("\n") + "\n");
|
|
1113
|
+
return manifestPath;
|
|
1114
|
+
}
|
|
1115
|
+
catch {
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
969
1119
|
// --- Rescue-target mapping ---
|
|
970
1120
|
function mapRescueTarget(rel) {
|
|
971
1121
|
if (rel === ".claude/CLAUDE.md")
|
|
@@ -994,18 +1144,18 @@ function isOverwriteSafe(rel) {
|
|
|
994
1144
|
rel === "core/docs/hq/USER-GUIDE.md" ||
|
|
995
1145
|
rel === "core/policies/_digest.md");
|
|
996
1146
|
}
|
|
997
|
-
function
|
|
1147
|
+
function masterSyncSymlinkTarget(localPath) {
|
|
998
1148
|
const st = lstatOrNull(localPath);
|
|
999
1149
|
if (!st || !st.isSymbolicLink())
|
|
1000
|
-
return
|
|
1150
|
+
return null;
|
|
1001
1151
|
let tgt = "";
|
|
1002
1152
|
try {
|
|
1003
1153
|
tgt = fs.readlinkSync(localPath);
|
|
1004
1154
|
}
|
|
1005
1155
|
catch {
|
|
1006
|
-
return
|
|
1156
|
+
return null;
|
|
1007
1157
|
}
|
|
1008
|
-
return tgt.includes("/personal/") || tgt.startsWith("personal/");
|
|
1158
|
+
return tgt.includes("/personal/") || tgt.startsWith("personal/") ? tgt : null;
|
|
1009
1159
|
}
|
|
1010
1160
|
function isUnderPreserve(cfg, rel) {
|
|
1011
1161
|
for (const sp of cfg.preserveSubpaths) {
|
|
@@ -1257,14 +1407,13 @@ function conflictOne(ctx, rel) {
|
|
|
1257
1407
|
ctx.out(` conflicted: ${rel} -> ${destRel}\n`);
|
|
1258
1408
|
ctx.appendLog(`conflicted\t${rel}\t-> ${destRel}\n`);
|
|
1259
1409
|
}
|
|
1260
|
-
// --- classify
|
|
1410
|
+
// --- classify one file (the per-file workhorse) ---
|
|
1261
1411
|
//
|
|
1262
|
-
// Read-only by contract: this
|
|
1263
|
-
//
|
|
1264
|
-
//
|
|
1265
|
-
// set has classified without throwing. Keep it that way
|
|
1266
|
-
|
|
1267
|
-
function processOne(ctx, rel) {
|
|
1412
|
+
// Read-only by contract: this returns a RescueDecision only. It does not render
|
|
1413
|
+
// dry-run text, update action queues, or mutate the filesystem. Dry-run text is
|
|
1414
|
+
// rendered by recordDecision, and PHASE 2 action building happens only once the
|
|
1415
|
+
// whole wipe set has classified without throwing. Keep it that way.
|
|
1416
|
+
function classifyOne(ctx, rel) {
|
|
1268
1417
|
const { cfg } = ctx;
|
|
1269
1418
|
const localPath = path.join(ctx.hqRoot, rel);
|
|
1270
1419
|
const srcPath = path.join(ctx.srcDir, rel);
|
|
@@ -1274,51 +1423,24 @@ function processOne(ctx, rel) {
|
|
|
1274
1423
|
throw new Error(`rescue classifier fault injected at ${rel} (HQ_RESCUE_FAULT_AT_REL)`);
|
|
1275
1424
|
}
|
|
1276
1425
|
if (isUnderPreserve(cfg, rel))
|
|
1277
|
-
return;
|
|
1426
|
+
return { kind: "skip", rel };
|
|
1278
1427
|
// Conflict-resolution artifacts (`<name>.conflict-<ts>-<peer>.<ext>`).
|
|
1279
1428
|
const base = rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
|
|
1280
1429
|
if (/\.conflict-/.test(base)) {
|
|
1281
|
-
|
|
1282
|
-
ctx.out(` drop conflict artifact: ${rel}\n`);
|
|
1283
|
-
}
|
|
1284
|
-
else {
|
|
1285
|
-
ctx.actions.push(() => fs.rmSync(localPath, { force: true }));
|
|
1286
|
-
}
|
|
1287
|
-
return;
|
|
1430
|
+
return { kind: "drop-conflict-artifact", rel };
|
|
1288
1431
|
}
|
|
1289
1432
|
// Script-managed files: core/core.yaml + legacy core.yaml.
|
|
1290
1433
|
if (rel === "core/core.yaml" || rel === "core.yaml") {
|
|
1291
|
-
|
|
1292
|
-
ctx.out(` skip script-managed (rewrites at stamp step): ${rel}\n`);
|
|
1293
|
-
}
|
|
1294
|
-
else {
|
|
1295
|
-
ctx.actions.push(() => fs.rmSync(localPath, { force: true }));
|
|
1296
|
-
}
|
|
1297
|
-
return;
|
|
1434
|
+
return { kind: "drop-script-managed", rel };
|
|
1298
1435
|
}
|
|
1299
1436
|
// Symlinks (mid-tree).
|
|
1300
1437
|
const lst = lstatOrNull(localPath);
|
|
1301
1438
|
if (lst && lst.isSymbolicLink()) {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
let tgt = "";
|
|
1306
|
-
try {
|
|
1307
|
-
tgt = fs.readlinkSync(localPath);
|
|
1308
|
-
}
|
|
1309
|
-
catch {
|
|
1310
|
-
/* ignore */
|
|
1311
|
-
}
|
|
1312
|
-
ctx.out(` drop reindex symlink: ${rel} -> ${tgt}\n`);
|
|
1313
|
-
}
|
|
1314
|
-
else {
|
|
1315
|
-
ctx.actions.push(() => {
|
|
1316
|
-
fs.rmSync(localPath, { force: true });
|
|
1317
|
-
ctx.appendLog(`symlink-dropped\t${rel}\t(reindex regenerable)\n`);
|
|
1318
|
-
});
|
|
1319
|
-
}
|
|
1439
|
+
const target = masterSyncSymlinkTarget(localPath);
|
|
1440
|
+
if (target !== null) {
|
|
1441
|
+
return { kind: "drop-reindex-symlink", rel, target };
|
|
1320
1442
|
}
|
|
1321
|
-
return;
|
|
1443
|
+
return { kind: "skip", rel };
|
|
1322
1444
|
}
|
|
1323
1445
|
// Is path in upstream HEAD?
|
|
1324
1446
|
const inHead = existsFollow(srcPath) ? 1 : 0;
|
|
@@ -1345,10 +1467,7 @@ function processOne(ctx, rel) {
|
|
|
1345
1467
|
}
|
|
1346
1468
|
// USER-ONLY: unknown to upstream (HEAD AND floor both lack it).
|
|
1347
1469
|
if (inHead === 0 && inFloor === 0) {
|
|
1348
|
-
|
|
1349
|
-
if (cfg.dryRun)
|
|
1350
|
-
ctx.out(` user-only (leave in place): ${rel}\n`);
|
|
1351
|
-
return;
|
|
1470
|
+
return { kind: "user-only", rel };
|
|
1352
1471
|
}
|
|
1353
1472
|
// Path is/was in upstream. Determine if user edited it.
|
|
1354
1473
|
let userEdited = 0;
|
|
@@ -1373,99 +1492,154 @@ function processOne(ctx, rel) {
|
|
|
1373
1492
|
}
|
|
1374
1493
|
// Cloud-update reclassification.
|
|
1375
1494
|
if (userEdited === 1 && isCloudFlattenedSymlinkEquiv(ctx, rel, localPath)) {
|
|
1376
|
-
|
|
1377
|
-
if (cfg.dryRun) {
|
|
1378
|
-
ctx.out(` cloud-symlink reconciled (unchanged): ${rel} (hq-symlink: marker matches upstream target)\n`);
|
|
1379
|
-
}
|
|
1380
|
-
else {
|
|
1381
|
-
ctx.actions.push(() => {
|
|
1382
|
-
fs.rmSync(localPath, { force: true });
|
|
1383
|
-
ctx.appendLog(`cloud-symlink-reconciled\t${rel}\t(hq-symlink: marker matches upstream target; overlay re-lays symlink)\n`);
|
|
1384
|
-
});
|
|
1385
|
-
}
|
|
1386
|
-
return;
|
|
1495
|
+
return { kind: "cloud-symlink-reconciled", rel };
|
|
1387
1496
|
}
|
|
1388
1497
|
// Convergence guard: drifted from floor but identical to upstream HEAD.
|
|
1389
1498
|
if (userEdited === 1 && inHead === 1 && bytesEqual(localPath, srcPath)) {
|
|
1390
|
-
|
|
1391
|
-
if (cfg.dryRun) {
|
|
1392
|
-
ctx.out(` drift reconciled (identical to upstream HEAD; no rescue): ${rel}\n`);
|
|
1393
|
-
}
|
|
1394
|
-
else {
|
|
1395
|
-
ctx.actions.push(() => {
|
|
1396
|
-
fs.rmSync(localPath, { force: true });
|
|
1397
|
-
ctx.appendLog(`drift-reconciled\t${rel}\t(identical to upstream HEAD; drifted from floor only — no personal/ copy)\n`);
|
|
1398
|
-
});
|
|
1399
|
-
}
|
|
1400
|
-
return;
|
|
1499
|
+
return { kind: "drift-reconciled", rel };
|
|
1401
1500
|
}
|
|
1402
1501
|
if (userEdited === 1) {
|
|
1403
|
-
if (
|
|
1404
|
-
|
|
1405
|
-
ctx.out(` user-edit (diff-append): ${rel} -> personal/CLAUDE.md\n`);
|
|
1406
|
-
ctx.counts.userEdit += 1;
|
|
1407
|
-
}
|
|
1408
|
-
else if (isOverwriteSafe(rel)) {
|
|
1409
|
-
ctx.out(` user-edit (overwrite-safe): ${rel} -> upstream wins (no copy preserved)\n`);
|
|
1410
|
-
ctx.counts.userOverwrite += 1;
|
|
1411
|
-
}
|
|
1412
|
-
else if (isConflictClass(rel)) {
|
|
1413
|
-
ctx.out(` user-edit (conflict): ${rel} -> .hq-conflicts/rescue-${ctx.runTs}/${rel}\n`);
|
|
1414
|
-
ctx.counts.userConflict += 1;
|
|
1415
|
-
}
|
|
1416
|
-
else {
|
|
1417
|
-
ctx.out(` user-edit (rescue): ${rel} -> ${mapRescueTarget(rel)}\n`);
|
|
1418
|
-
ctx.counts.userEdit += 1;
|
|
1419
|
-
}
|
|
1502
|
+
if (rel === ".claude/CLAUDE.md") {
|
|
1503
|
+
return { kind: "user-edit-diff-append", rel };
|
|
1420
1504
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1505
|
+
if (isOverwriteSafe(rel)) {
|
|
1506
|
+
return { kind: "user-edit-overwrite-safe", rel };
|
|
1507
|
+
}
|
|
1508
|
+
if (isConflictClass(rel)) {
|
|
1509
|
+
return { kind: "user-edit-conflict", rel };
|
|
1510
|
+
}
|
|
1511
|
+
return { kind: "user-edit-rescue", rel, target: mapRescueTarget(rel) };
|
|
1512
|
+
}
|
|
1513
|
+
// user_edited=0: matches floor baseline. Split on byte-equality to HEAD.
|
|
1514
|
+
if (bytesEqual(localPath, path.join(ctx.srcDir, rel))) {
|
|
1515
|
+
return { kind: "unchanged-preserve", rel };
|
|
1516
|
+
}
|
|
1517
|
+
return { kind: "unchanged-delete", rel };
|
|
1518
|
+
}
|
|
1519
|
+
function renderDryRunDecision(ctx, decision) {
|
|
1520
|
+
switch (decision.kind) {
|
|
1521
|
+
case "skip":
|
|
1522
|
+
break;
|
|
1523
|
+
case "drop-conflict-artifact":
|
|
1524
|
+
ctx.out(` drop conflict artifact: ${decision.rel}\n`);
|
|
1525
|
+
break;
|
|
1526
|
+
case "drop-script-managed":
|
|
1527
|
+
ctx.out(` skip script-managed (rewrites at stamp step): ${decision.rel}\n`);
|
|
1528
|
+
break;
|
|
1529
|
+
case "drop-reindex-symlink":
|
|
1530
|
+
ctx.out(` drop reindex symlink: ${decision.rel} -> ${decision.target}\n`);
|
|
1531
|
+
break;
|
|
1532
|
+
case "user-only":
|
|
1533
|
+
ctx.out(` user-only (leave in place): ${decision.rel}\n`);
|
|
1534
|
+
break;
|
|
1535
|
+
case "cloud-symlink-reconciled":
|
|
1536
|
+
ctx.out(` cloud-symlink reconciled (unchanged): ${decision.rel} (hq-symlink: marker matches upstream target)\n`);
|
|
1537
|
+
break;
|
|
1538
|
+
case "drift-reconciled":
|
|
1539
|
+
ctx.out(` drift reconciled (identical to upstream HEAD; no rescue): ${decision.rel}\n`);
|
|
1540
|
+
break;
|
|
1541
|
+
case "user-edit-diff-append":
|
|
1542
|
+
ctx.out(` user-edit (diff-append): ${decision.rel} -> personal/CLAUDE.md\n`);
|
|
1543
|
+
ctx.counts.userEdit += 1;
|
|
1544
|
+
break;
|
|
1545
|
+
case "user-edit-overwrite-safe":
|
|
1546
|
+
ctx.out(` user-edit (overwrite-safe): ${decision.rel} -> upstream wins (no copy preserved)\n`);
|
|
1547
|
+
ctx.counts.userOverwrite += 1;
|
|
1548
|
+
break;
|
|
1549
|
+
case "user-edit-conflict":
|
|
1550
|
+
ctx.out(` user-edit (conflict): ${decision.rel} -> .hq-conflicts/rescue-${ctx.runTs}/${decision.rel}\n`);
|
|
1551
|
+
ctx.counts.userConflict += 1;
|
|
1552
|
+
break;
|
|
1553
|
+
case "user-edit-rescue":
|
|
1554
|
+
ctx.out(` user-edit (rescue): ${decision.rel} -> ${decision.target}\n`);
|
|
1555
|
+
ctx.counts.userEdit += 1;
|
|
1556
|
+
break;
|
|
1557
|
+
case "unchanged-preserve":
|
|
1558
|
+
ctx.out(` unchanged (preserved in place): ${decision.rel}\n`);
|
|
1559
|
+
break;
|
|
1560
|
+
case "unchanged-delete":
|
|
1561
|
+
ctx.out(` unchanged (delete + replace): ${decision.rel}\n`);
|
|
1562
|
+
break;
|
|
1563
|
+
case "wholesale-replace-template":
|
|
1564
|
+
ctx.out(" wholesale-replace: companies/_template (template carve-out)\n");
|
|
1565
|
+
break;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
function buildRescueActions(ctx) {
|
|
1569
|
+
const actions = [];
|
|
1570
|
+
const add = (label, affectedRels, runAction) => {
|
|
1571
|
+
actions.push({ label, affectedRels, run: runAction });
|
|
1572
|
+
};
|
|
1573
|
+
for (const decision of ctx.decisions) {
|
|
1574
|
+
const localPath = path.join(ctx.hqRoot, decision.rel);
|
|
1575
|
+
switch (decision.kind) {
|
|
1576
|
+
case "skip":
|
|
1577
|
+
case "user-only":
|
|
1578
|
+
break;
|
|
1579
|
+
case "drop-conflict-artifact":
|
|
1580
|
+
add(`drop conflict artifact: ${decision.rel}`, [decision.rel], () => fs.rmSync(localPath, { force: true }));
|
|
1581
|
+
break;
|
|
1582
|
+
case "drop-script-managed":
|
|
1583
|
+
add(`drop script-managed: ${decision.rel}`, [decision.rel], () => fs.rmSync(localPath, { force: true }));
|
|
1584
|
+
break;
|
|
1585
|
+
case "drop-reindex-symlink":
|
|
1586
|
+
add(`drop reindex symlink: ${decision.rel}`, [decision.rel], () => {
|
|
1587
|
+
fs.rmSync(localPath, { force: true });
|
|
1588
|
+
ctx.appendLog(`symlink-dropped\t${decision.rel}\t(reindex regenerable)\n`);
|
|
1589
|
+
});
|
|
1590
|
+
break;
|
|
1591
|
+
case "cloud-symlink-reconciled":
|
|
1592
|
+
add(`cloud-symlink reconciled: ${decision.rel}`, [decision.rel], () => {
|
|
1593
|
+
fs.rmSync(localPath, { force: true });
|
|
1594
|
+
ctx.appendLog(`cloud-symlink-reconciled\t${decision.rel}\t(hq-symlink: marker matches upstream target; overlay re-lays symlink)\n`);
|
|
1595
|
+
});
|
|
1596
|
+
break;
|
|
1597
|
+
case "drift-reconciled":
|
|
1598
|
+
add(`drift reconciled: ${decision.rel}`, [decision.rel], () => {
|
|
1599
|
+
fs.rmSync(localPath, { force: true });
|
|
1600
|
+
ctx.appendLog(`drift-reconciled\t${decision.rel}\t(identical to upstream HEAD; drifted from floor only — no personal/ copy)\n`);
|
|
1601
|
+
});
|
|
1602
|
+
break;
|
|
1603
|
+
case "user-edit-diff-append":
|
|
1604
|
+
add(`rescue diff-append: ${decision.rel}`, [decision.rel], () => rescueOne(ctx, decision.rel));
|
|
1605
|
+
break;
|
|
1606
|
+
case "user-edit-overwrite-safe":
|
|
1607
|
+
add(`overwrite-safe: ${decision.rel}`, [decision.rel], () => {
|
|
1427
1608
|
fs.rmSync(localPath, { force: true });
|
|
1428
1609
|
ctx.counts.userOverwrite += 1;
|
|
1429
|
-
ctx.appendLog(`overwritten\t${rel}\t(overwrite-safe; upstream wins, no copy preserved)\n`);
|
|
1610
|
+
ctx.appendLog(`overwritten\t${decision.rel}\t(overwrite-safe; upstream wins, no copy preserved)\n`);
|
|
1430
1611
|
});
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
ctx.counts.unchanged += 1;
|
|
1443
|
-
if (bytesEqual(localPath, path.join(ctx.srcDir, rel))) {
|
|
1444
|
-
if (cfg.dryRun) {
|
|
1445
|
-
ctx.out(` unchanged (preserved in place): ${rel}\n`);
|
|
1446
|
-
}
|
|
1447
|
-
else {
|
|
1448
|
-
ctx.actions.push(() => {
|
|
1449
|
-
fs.appendFileSync(ctx.unchangedList, `/${rel}\n`);
|
|
1450
|
-
ctx.appendLog(`unchanged\t${rel}\t(identical to upstream; left in place, mtime preserved)\n`);
|
|
1612
|
+
break;
|
|
1613
|
+
case "user-edit-conflict":
|
|
1614
|
+
add(`conflict quarantine: ${decision.rel}`, [decision.rel], () => conflictOne(ctx, decision.rel));
|
|
1615
|
+
break;
|
|
1616
|
+
case "user-edit-rescue":
|
|
1617
|
+
add(`rescue: ${decision.rel}`, [decision.rel], () => rescueOne(ctx, decision.rel));
|
|
1618
|
+
break;
|
|
1619
|
+
case "unchanged-preserve":
|
|
1620
|
+
add(`preserve unchanged: ${decision.rel}`, [], () => {
|
|
1621
|
+
fs.appendFileSync(ctx.unchangedList, `/${decision.rel}\n`);
|
|
1622
|
+
ctx.appendLog(`unchanged\t${decision.rel}\t(identical to upstream; left in place, mtime preserved)\n`);
|
|
1451
1623
|
});
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
if (cfg.dryRun) {
|
|
1456
|
-
ctx.out(` unchanged (delete + replace): ${rel}\n`);
|
|
1457
|
-
}
|
|
1458
|
-
else {
|
|
1459
|
-
ctx.actions.push(() => {
|
|
1624
|
+
break;
|
|
1625
|
+
case "unchanged-delete":
|
|
1626
|
+
add(`delete unchanged: ${decision.rel}`, [decision.rel], () => {
|
|
1460
1627
|
fs.rmSync(localPath, { force: true });
|
|
1461
|
-
ctx.appendLog(`deleted\t${rel}\t(unchanged vs baseline; re-laid by overlay if still upstream)\n`);
|
|
1628
|
+
ctx.appendLog(`deleted\t${decision.rel}\t(unchanged vs baseline; re-laid by overlay if still upstream)\n`);
|
|
1462
1629
|
});
|
|
1463
|
-
|
|
1630
|
+
break;
|
|
1631
|
+
case "wholesale-replace-template":
|
|
1632
|
+
add("wholesale-replace: companies/_template", [decision.rel], () => fs.rmSync(path.join(ctx.hqRoot, "companies", "_template"), {
|
|
1633
|
+
recursive: true,
|
|
1634
|
+
force: true,
|
|
1635
|
+
}));
|
|
1636
|
+
break;
|
|
1464
1637
|
}
|
|
1465
1638
|
}
|
|
1639
|
+
return actions;
|
|
1466
1640
|
}
|
|
1467
1641
|
// --- walk a wipe-set root ---
|
|
1468
|
-
// Precompute the per-file git facts
|
|
1642
|
+
// Precompute the per-file git facts classifyOne needs, in two batched passes
|
|
1469
1643
|
// instead of ~3 `git` spawns per file. Process-startup overhead (~15-20ms per
|
|
1470
1644
|
// spawn) dominates the per-file path, so on a large wipe set this turns a
|
|
1471
1645
|
// minutes-long classify into a seconds-long one. Behaviour is identical: the
|
|
@@ -1473,10 +1647,10 @@ function processOne(ctx, rel) {
|
|
|
1473
1647
|
// rel not represented in a map falls back to its original per-file spawn.
|
|
1474
1648
|
//
|
|
1475
1649
|
// Mirrors walkAndProcess's enumeration so the batched set matches what actually
|
|
1476
|
-
// reaches
|
|
1650
|
+
// reaches classifyOne: it skips `companies/_template` (wholesale-replaced, never
|
|
1477
1651
|
// classified per-file) and top-level symlinks (dropped, never classified).
|
|
1478
1652
|
function precomputeGitMaps(ctx, wipeToplevel) {
|
|
1479
|
-
const allRels = []; // every rel that reaches
|
|
1653
|
+
const allRels = []; // every rel that reaches classifyOne (files + symlinks)
|
|
1480
1654
|
const fileRels = []; // subset that are regular files (hashable)
|
|
1481
1655
|
const fileAbs = []; // parallel to fileRels
|
|
1482
1656
|
for (const rootRel of wipeToplevel) {
|
|
@@ -1493,7 +1667,7 @@ function precomputeGitMaps(ctx, wipeToplevel) {
|
|
|
1493
1667
|
: [];
|
|
1494
1668
|
for (const rel of rels) {
|
|
1495
1669
|
// A path containing a newline can't survive line-delimited batch stdin;
|
|
1496
|
-
// leave it out of the maps and let
|
|
1670
|
+
// leave it out of the maps and let classifyOne spawn per-file for it.
|
|
1497
1671
|
if (rel.includes("\n"))
|
|
1498
1672
|
continue;
|
|
1499
1673
|
allRels.push(rel);
|
|
@@ -1504,7 +1678,7 @@ function precomputeGitMaps(ctx, wipeToplevel) {
|
|
|
1504
1678
|
}
|
|
1505
1679
|
}
|
|
1506
1680
|
}
|
|
1507
|
-
// In head_compare mode
|
|
1681
|
+
// In head_compare mode classifyOne never consults these maps (it compares
|
|
1508
1682
|
// bytes against the upstream working tree directly), so skip both git passes.
|
|
1509
1683
|
if (ctx.baselineMode !== "history_floor") {
|
|
1510
1684
|
ctx.floorShas = new Map();
|
|
@@ -1540,7 +1714,7 @@ function precomputeGitMaps(ctx, wipeToplevel) {
|
|
|
1540
1714
|
}
|
|
1541
1715
|
ctx.floorShas = floorShas;
|
|
1542
1716
|
// Pass 2: local blob SHA, but ONLY for regular files that are in the floor.
|
|
1543
|
-
//
|
|
1717
|
+
// classifyOne reads localShas exclusively in the `inFloor === 1` branch, so a
|
|
1544
1718
|
// file absent from the floor (e.g. the transient `.claude/worktrees/` trees,
|
|
1545
1719
|
// which classify as user-only) never needs a hash — and hashing it would mean
|
|
1546
1720
|
// reading its bytes for nothing. Restricting the hash set to floor members is
|
|
@@ -1571,55 +1745,31 @@ function precomputeGitMaps(ctx, wipeToplevel) {
|
|
|
1571
1745
|
ctx.localShas = localShas;
|
|
1572
1746
|
}
|
|
1573
1747
|
function walkAndProcess(ctx, rootRel) {
|
|
1574
|
-
const { cfg } = ctx;
|
|
1575
1748
|
const rootAbs = path.join(ctx.hqRoot, rootRel);
|
|
1576
1749
|
if (!lexists(rootAbs))
|
|
1577
1750
|
return;
|
|
1578
1751
|
// companies/_template — wholesale-replace.
|
|
1579
1752
|
if (rootRel === "companies/_template") {
|
|
1580
|
-
|
|
1581
|
-
ctx.out(" wholesale-replace: companies/_template (template carve-out)\n");
|
|
1582
|
-
}
|
|
1583
|
-
else {
|
|
1584
|
-
ctx.actions.push(() => fs.rmSync(path.join(ctx.hqRoot, "companies", "_template"), {
|
|
1585
|
-
recursive: true,
|
|
1586
|
-
force: true,
|
|
1587
|
-
}));
|
|
1588
|
-
}
|
|
1753
|
+
recordDecision(ctx, { kind: "wholesale-replace-template", rel: "companies/_template" });
|
|
1589
1754
|
return;
|
|
1590
1755
|
}
|
|
1591
1756
|
const lst = lstatOrNull(rootAbs);
|
|
1592
1757
|
// Top-level symlink.
|
|
1593
1758
|
if (lst && lst.isSymbolicLink()) {
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
let tgt = "";
|
|
1598
|
-
try {
|
|
1599
|
-
tgt = fs.readlinkSync(rootAbs);
|
|
1600
|
-
}
|
|
1601
|
-
catch {
|
|
1602
|
-
/* ignore */
|
|
1603
|
-
}
|
|
1604
|
-
ctx.out(` drop reindex symlink: ${rootRel} -> ${tgt}\n`);
|
|
1605
|
-
}
|
|
1606
|
-
else {
|
|
1607
|
-
ctx.actions.push(() => {
|
|
1608
|
-
fs.rmSync(rootAbs, { force: true });
|
|
1609
|
-
ctx.appendLog(`symlink-dropped\t${rootRel}\t(reindex regenerable)\n`);
|
|
1610
|
-
});
|
|
1611
|
-
}
|
|
1759
|
+
const target = masterSyncSymlinkTarget(rootAbs);
|
|
1760
|
+
if (target !== null) {
|
|
1761
|
+
recordDecision(ctx, { kind: "drop-reindex-symlink", rel: rootRel, target });
|
|
1612
1762
|
}
|
|
1613
1763
|
return;
|
|
1614
1764
|
}
|
|
1615
1765
|
// Top-level regular file.
|
|
1616
1766
|
if (lst && lst.isFile()) {
|
|
1617
|
-
|
|
1767
|
+
recordDecision(ctx, classifyOne(ctx, rootRel));
|
|
1618
1768
|
return;
|
|
1619
1769
|
}
|
|
1620
1770
|
// Top-level directory: recursive walk, pruning node_modules + nested .git.
|
|
1621
1771
|
for (const rel of findFilesAndSymlinks(rootAbs, ctx.hqRoot)) {
|
|
1622
|
-
|
|
1772
|
+
recordDecision(ctx, classifyOne(ctx, rel));
|
|
1623
1773
|
}
|
|
1624
1774
|
}
|
|
1625
1775
|
/**
|