@indigoai-us/hq-cloud 6.14.22 → 6.14.24

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.
Files changed (120) hide show
  1. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  2. package/dist/bin/sync-runner-company.js +20 -0
  3. package/dist/bin/sync-runner-company.js.map +1 -1
  4. package/dist/bin/sync-runner-events.test.js +22 -0
  5. package/dist/bin/sync-runner-events.test.js.map +1 -1
  6. package/dist/bin/sync-runner-planning.d.ts +11 -0
  7. package/dist/bin/sync-runner-planning.d.ts.map +1 -1
  8. package/dist/bin/sync-runner-planning.js +37 -2
  9. package/dist/bin/sync-runner-planning.js.map +1 -1
  10. package/dist/bin/sync-runner-planning.test.js +1 -0
  11. package/dist/bin/sync-runner-planning.test.js.map +1 -1
  12. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  13. package/dist/bin/sync-runner-watch-loop.js +17 -1
  14. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  15. package/dist/bin/sync-runner.d.ts +33 -1
  16. package/dist/bin/sync-runner.d.ts.map +1 -1
  17. package/dist/bin/sync-runner.js +28 -3
  18. package/dist/bin/sync-runner.js.map +1 -1
  19. package/dist/bin/sync-runner.test.js +206 -2
  20. package/dist/bin/sync-runner.test.js.map +1 -1
  21. package/dist/cli/doctor.d.ts +119 -0
  22. package/dist/cli/doctor.d.ts.map +1 -0
  23. package/dist/cli/doctor.js +485 -0
  24. package/dist/cli/doctor.js.map +1 -0
  25. package/dist/cli/doctor.test.d.ts +13 -0
  26. package/dist/cli/doctor.test.d.ts.map +1 -0
  27. package/dist/cli/doctor.test.js +485 -0
  28. package/dist/cli/doctor.test.js.map +1 -0
  29. package/dist/cli/index.d.ts +2 -0
  30. package/dist/cli/index.d.ts.map +1 -1
  31. package/dist/cli/index.js +4 -0
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/share.js +6 -1
  34. package/dist/cli/share.js.map +1 -1
  35. package/dist/cli/share.test.js +1 -0
  36. package/dist/cli/share.test.js.map +1 -1
  37. package/dist/cli/sync.d.ts +54 -0
  38. package/dist/cli/sync.d.ts.map +1 -1
  39. package/dist/cli/sync.js +223 -3
  40. package/dist/cli/sync.js.map +1 -1
  41. package/dist/cli/sync.test.js +394 -1
  42. package/dist/cli/sync.test.js.map +1 -1
  43. package/dist/cognito-auth.test.js +7 -0
  44. package/dist/cognito-auth.test.js.map +1 -1
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +3 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/lib/conflict-file.d.ts.map +1 -1
  50. package/dist/lib/conflict-file.js +5 -1
  51. package/dist/lib/conflict-file.js.map +1 -1
  52. package/dist/lib/conflict-index.d.ts +7 -0
  53. package/dist/lib/conflict-index.d.ts.map +1 -1
  54. package/dist/lib/conflict-index.js +10 -2
  55. package/dist/lib/conflict-index.js.map +1 -1
  56. package/dist/lib/conflict.test.js +31 -0
  57. package/dist/lib/conflict.test.js.map +1 -1
  58. package/dist/lib/machine-id.test.js +14 -0
  59. package/dist/lib/machine-id.test.js.map +1 -1
  60. package/dist/local-path-codec.d.ts +18 -0
  61. package/dist/local-path-codec.d.ts.map +1 -1
  62. package/dist/local-path-codec.js +63 -0
  63. package/dist/local-path-codec.js.map +1 -1
  64. package/dist/local-path-codec.test.d.ts +14 -0
  65. package/dist/local-path-codec.test.d.ts.map +1 -0
  66. package/dist/local-path-codec.test.js +102 -0
  67. package/dist/local-path-codec.test.js.map +1 -0
  68. package/dist/machine-auth.test.js +7 -0
  69. package/dist/machine-auth.test.js.map +1 -1
  70. package/dist/s3.d.ts +5 -0
  71. package/dist/s3.d.ts.map +1 -1
  72. package/dist/s3.js +129 -3
  73. package/dist/s3.js.map +1 -1
  74. package/dist/s3.symlink-materialize.test.d.ts +18 -0
  75. package/dist/s3.symlink-materialize.test.d.ts.map +1 -0
  76. package/dist/s3.symlink-materialize.test.js +394 -0
  77. package/dist/s3.symlink-materialize.test.js.map +1 -0
  78. package/dist/s3.test.js +19 -0
  79. package/dist/s3.test.js.map +1 -1
  80. package/dist/skill-telemetry.d.ts.map +1 -1
  81. package/dist/skill-telemetry.js +23 -2
  82. package/dist/skill-telemetry.js.map +1 -1
  83. package/dist/skill-telemetry.test.js +17 -0
  84. package/dist/skill-telemetry.test.js.map +1 -1
  85. package/dist/watcher.d.ts.map +1 -1
  86. package/dist/watcher.js +10 -1
  87. package/dist/watcher.js.map +1 -1
  88. package/dist/watcher.test.js +28 -0
  89. package/dist/watcher.test.js.map +1 -1
  90. package/package.json +1 -1
  91. package/src/bin/sync-runner-company.ts +18 -0
  92. package/src/bin/sync-runner-events.test.ts +31 -0
  93. package/src/bin/sync-runner-planning.test.ts +1 -0
  94. package/src/bin/sync-runner-planning.ts +46 -3
  95. package/src/bin/sync-runner-watch-loop.ts +19 -1
  96. package/src/bin/sync-runner.test.ts +236 -1
  97. package/src/bin/sync-runner.ts +57 -2
  98. package/src/cli/doctor.test.ts +581 -0
  99. package/src/cli/doctor.ts +640 -0
  100. package/src/cli/index.ts +14 -0
  101. package/src/cli/share.test.ts +1 -0
  102. package/src/cli/share.ts +6 -1
  103. package/src/cli/sync.test.ts +473 -1
  104. package/src/cli/sync.ts +310 -3
  105. package/src/cognito-auth.test.ts +5 -0
  106. package/src/index.ts +10 -0
  107. package/src/lib/conflict-file.ts +6 -1
  108. package/src/lib/conflict-index.ts +18 -2
  109. package/src/lib/conflict.test.ts +33 -0
  110. package/src/lib/machine-id.test.ts +10 -0
  111. package/src/local-path-codec.test.ts +138 -0
  112. package/src/local-path-codec.ts +66 -0
  113. package/src/machine-auth.test.ts +5 -0
  114. package/src/s3.symlink-materialize.test.ts +492 -0
  115. package/src/s3.test.ts +24 -0
  116. package/src/s3.ts +148 -4
  117. package/src/skill-telemetry.test.ts +19 -0
  118. package/src/skill-telemetry.ts +31 -2
  119. package/src/watcher.test.ts +33 -0
  120. package/src/watcher.ts +10 -1
@@ -217,6 +217,31 @@ export type SyncProgressEvent = {
217
217
  type: "scope-excluded";
218
218
  count: number;
219
219
  samplePaths: string[];
220
+ } | {
221
+ /**
222
+ * Emitted at most ONCE per PULL leg when the leg ran under a
223
+ * membership-scoped `syncMode` (`"shared"` or `"custom"` — never `"all"`)
224
+ * AND one or more remote keys were skipped as out-of-scope
225
+ * (`filesOutOfScope > 0`). This turns the silent shared-vs-all gap into a
226
+ * VISIBLE, actionable surface. The reporter of feedback_d2082110 lost
227
+ * files across devices precisely because four memberships defaulted to
228
+ * `shared` rather than `all`, so not all vault content materialized on the
229
+ * second device — and nothing told them; they had to notice the gap and
230
+ * flip each membership to `all` by hand. This event names the gap and the
231
+ * lever (raise the membership's access level to `all`) so FULL
232
+ * materialization is a deliberate, visible choice rather than a silent
233
+ * omission.
234
+ *
235
+ * `count` is the number of remote keys skipped as out-of-scope on this
236
+ * leg; `samplePaths` carries up to 10 company-relative keys for display;
237
+ * `syncMode` is the active scoped mode. Distinct from the push-side
238
+ * `scope-excluded` (which reports what a grantee could not PUSH). Not
239
+ * emitted in `all` mode or when `count === 0` — no gap, no noise.
240
+ */
241
+ type: "scope-materialization-gap";
242
+ count: number;
243
+ samplePaths: string[];
244
+ syncMode: SyncMode;
220
245
  } | {
221
246
  /**
222
247
  * Emitted at most ONCE per `share()` push leg when the base ignore
@@ -282,6 +307,26 @@ export type SyncProgressEvent = {
282
307
  path: string;
283
308
  /** Server-compatible invalid-key code, such as INVALID_KEY_CONTROL_CHARS. */
284
309
  errorCode?: string;
310
+ } | {
311
+ /**
312
+ * Emitted by the PULL leg once per remote key skipped because its
313
+ * spelling percent-decodes to the same logical path as an
314
+ * already-journaled key with a DIFFERENT spelling — the mixed-version
315
+ * amplifier (a pre-codec peer minting `%3A`/`%253A` twins of a
316
+ * canonical `:` key). Downloading the twin would materialize a junk
317
+ * local dir and fork the journal into a second key family for one
318
+ * logical path, so the established spelling wins and the twin is
319
+ * skipped. Like `skip-size-limit`, this is deliberately NOT
320
+ * `type: "error"` (policy hq-sync-deliberate-skip-not-fatal-error-exit2:
321
+ * a recurring benign per-file skip emitted as an error lands in the
322
+ * runner's `errors[]`, forces exit 2 on every pass, and the menubar
323
+ * Sentry-alerts each time). The US-003 vault doctor is the cleanup
324
+ * path that collapses the junk family itself.
325
+ */
326
+ type: "skip-junk-key-spelling";
327
+ path: string;
328
+ /** The already-journaled spelling that owns this logical path. */
329
+ journaledKey: string;
285
330
  };
286
331
  export interface SyncOptions {
287
332
  /** Company slug or UID (defaults to active company from config) */
@@ -526,4 +571,13 @@ export declare function reportNewFilesToNotify(vaultConfig: VaultServiceConfig,
526
571
  * Sync (pull) all allowed files from the entity vault.
527
572
  */
528
573
  export declare function sync(options: SyncOptions): Promise<SyncResult>;
574
+ /**
575
+ * win32 only: true when an existing local symlink's TARGET exists but the
576
+ * link cannot be traversed as that target's type — the file-flavor-link-at-
577
+ * a-directory shape minted while the target was absent (or by an older
578
+ * client). A dangling link is NOT broken-flavored (there is nothing to
579
+ * repair until the target appears). POSIX links are flavorless: always
580
+ * false off win32.
581
+ */
582
+ export declare function win32SymlinkFlavorBroken(localPath: string, win32?: boolean): boolean;
529
583
  //# sourceMappingURL=sync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/cli/sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/cli/sync.ts"],"names":[],"mappings":"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"}
package/dist/cli/sync.js CHANGED
@@ -10,7 +10,8 @@ import { VaultAuthError, VaultClient } from "../vault-client.js";
10
10
  import { emitCloudTelemetry, } from "../telemetry-events.js";
11
11
  import { resolveEntityContext, isExpiringSoon, refreshEntityContext } from "../context.js";
12
12
  import { createSyncProgressRecorder } from "../sync-progress.js";
13
- import { localPathForVaultKey, vaultKeyForLocalPath } from "../local-path-codec.js";
13
+ import { canonicalVaultKeySpelling, localPathForVaultKey, vaultKeyForLocalPath, } from "../local-path-codec.js";
14
+ import { SKILLS_KEY_PREFIX } from "./doctor.js";
14
15
  import { downloadFile, listRemoteFiles, headRemoteFile, primeObjectTransport, toPosixKey, classifyVaultKey, DanglingSymlinkParentError, } from "../s3.js";
15
16
  import { readJournal, writeJournal, hashFile, hashSymlinkTarget, updateEntry, removeEntry, getEntry, normalizeEtag, migrateToV2, gcTombstones, isTombstone, tombstoneEntry, lastPullRecord, appendPullRecord, generatePullId, PERSONAL_VAULT_JOURNAL_SLUG, migratePersonalVaultJournal, } from "../journal.js";
16
17
  import { isGeneratedCoreMirrorKey, PERSONAL_VAULT_MANIFEST_KEY, } from "../personal-vault.js";
@@ -195,6 +196,7 @@ async function syncWithOperationLockHeld(options) {
195
196
  await emitAndReportNewFiles(run, plan);
196
197
  await verifyPlannedJournalTombstones(run, plan);
197
198
  executeJournalTombstoneDeletes(run, plan, counters);
199
+ emitScopeMaterializationGap(run, plan, counters);
198
200
  return finalizePullRun(run, plan, scopeRun, counters);
199
201
  }
200
202
  async function buildPullContext(options) {
@@ -434,7 +436,8 @@ async function executeConflictExecutor(run, plan, scopeRun, counters) {
434
436
  item.action === "skip-personal-mode" ||
435
437
  item.action === "skip-unchanged" ||
436
438
  item.action === "skip-local-only" ||
437
- item.action === "skip-stale-overlay-marker") {
439
+ item.action === "skip-stale-overlay-marker" ||
440
+ item.action === "skip-overlay-marker-with-children") {
438
441
  counters.filesSkipped++;
439
442
  continue;
440
443
  }
@@ -445,6 +448,18 @@ async function executeConflictExecutor(run, plan, scopeRun, counters) {
445
448
  counters.filesOutOfScope++;
446
449
  continue;
447
450
  }
451
+ if (item.action === "skip-junk-key-spelling") {
452
+ // Deliberate, recoverable skip — counted as skipped and surfaced via a
453
+ // dedicated warning event, never `type: "error"` (see the event doc /
454
+ // policy hq-sync-deliberate-skip-not-fatal-error-exit2).
455
+ counters.filesSkipped++;
456
+ run.emit({
457
+ type: "skip-junk-key-spelling",
458
+ path: item.remoteFile.key,
459
+ journaledKey: item.journaledKey,
460
+ });
461
+ continue;
462
+ }
448
463
  if (item.action === "tombstone-delete") {
449
464
  executeFileTombstoneDelete(run, item, counters);
450
465
  continue;
@@ -971,6 +986,34 @@ function finalizePullRun(run, plan, scopeRun, counters) {
971
986
  changedPaths: [...counters.changedPaths, ...scopeRun.changedPaths],
972
987
  };
973
988
  }
989
+ /**
990
+ * Surface the shared-vs-all materialization gap (feedback_d2082110). When a
991
+ * pull leg ran under a membership-scoped `syncMode` and skipped one or more
992
+ * remote keys as out-of-scope, emit a single summary event so the operator
993
+ * SEES that not all vault content materialized on this device and knows the
994
+ * lever — raise the membership's access level to `all`. Silent in `all` mode
995
+ * (nothing is scoped away) and when nothing fell out of scope: no gap, no
996
+ * noise. Deliberately AFTER the transfer executors, so `filesOutOfScope` is
997
+ * final; sample keys are read off the plan's `skip-out-of-scope` items (pure,
998
+ * no I/O). This is the visible complement to the previously-silent
999
+ * `SyncResult.filesOutOfScope` count.
1000
+ */
1001
+ function emitScopeMaterializationGap(run, plan, counters) {
1002
+ if (run.syncMode === "all")
1003
+ return;
1004
+ if (counters.filesOutOfScope <= 0)
1005
+ return;
1006
+ const samplePaths = plan.items
1007
+ .filter((item) => item.action === "skip-out-of-scope")
1008
+ .slice(0, 10)
1009
+ .map((item) => item.remoteFile.key);
1010
+ run.emit({
1011
+ type: "scope-materialization-gap",
1012
+ count: counters.filesOutOfScope,
1013
+ samplePaths,
1014
+ syncMode: run.syncMode,
1015
+ });
1016
+ }
974
1017
  /**
975
1018
  * Decide whether a remote object present in the LIST is a GENUINE RE-CREATE
976
1019
  * written AFTER a FILE_TOMBSTONE — in which case the tombstone is stale and the
@@ -1126,6 +1169,39 @@ class LocalSnapshotChangedError extends Error {
1126
1169
  // an intentional delete.
1127
1170
  const PULL_INTENTIONAL_DELETE_MIN_ABS = 10;
1128
1171
  const PULL_INTENTIONAL_DELETE_RATIO = 0.1;
1172
+ /**
1173
+ * win32 only: true when an existing local symlink's TARGET exists but the
1174
+ * link cannot be traversed as that target's type — the file-flavor-link-at-
1175
+ * a-directory shape minted while the target was absent (or by an older
1176
+ * client). A dangling link is NOT broken-flavored (there is nothing to
1177
+ * repair until the target appears). POSIX links are flavorless: always
1178
+ * false off win32.
1179
+ */
1180
+ export function win32SymlinkFlavorBroken(localPath, win32 = process.platform === "win32") {
1181
+ if (!win32)
1182
+ return false;
1183
+ let target;
1184
+ try {
1185
+ target = fs.readlinkSync(localPath);
1186
+ }
1187
+ catch {
1188
+ return false;
1189
+ }
1190
+ const resolved = path.resolve(path.dirname(localPath), target);
1191
+ let targetIsDir;
1192
+ try {
1193
+ targetIsDir = fs.statSync(resolved).isDirectory();
1194
+ }
1195
+ catch {
1196
+ return false; // dangling target — nothing to repair yet
1197
+ }
1198
+ try {
1199
+ return fs.statSync(localPath).isDirectory() !== targetIsDir;
1200
+ }
1201
+ catch {
1202
+ return true; // target exists but the link cannot traverse: wrong flavor
1203
+ }
1204
+ }
1129
1205
  function computePullPlan(remoteFiles, journal, companyRoot, shouldSync, personalMode, teamSyncedSlugs,
1130
1206
  // Coalesced, company-relative prefixes the pull is scoped to (US-005).
1131
1207
  // `[""]` (the `all`-mode value) covers everything via `isCoveredByAny`, so
@@ -1152,6 +1228,45 @@ excludePrefixes = []) {
1152
1228
  // Clean/current/non-divergent files missing locally — collected here, then
1153
1229
  // resolved post-loop by the bulk-asymmetry decision (tombstone vs. restore).
1154
1230
  const intentionalDeleteCandidates = [];
1231
+ // Junk-spelling guard (mixed-version amplifier): index every journaled key
1232
+ // by its canonical (percent-decode fixpoint) form so a remote twin minted
1233
+ // under a different spelling is recognized before it materializes. When
1234
+ // several journaled spellings already share one canonical form (a
1235
+ // pre-guard fork), the exactly-canonical spelling is preferred as the
1236
+ // representative; entries journaled under their own exact spelling are
1237
+ // never gated by this map (see the guard below), so pre-existing families
1238
+ // keep syncing untouched until the vault doctor (US-003) collapses them.
1239
+ const journaledKeyByCanonical = new Map();
1240
+ for (const journaledKey of Object.keys(journal.files)) {
1241
+ if (!journaledKey.startsWith(SKILLS_KEY_PREFIX))
1242
+ continue;
1243
+ const canonical = canonicalVaultKeySpelling(journaledKey);
1244
+ const existing = journaledKeyByCanonical.get(canonical);
1245
+ if (existing === undefined || journaledKey === canonical) {
1246
+ journaledKeyByCanonical.set(canonical, journaledKey);
1247
+ }
1248
+ }
1249
+ // Remote keys that ALSO appear as an ancestor of another remote key — i.e.
1250
+ // keys that carry child objects under `${key}/…` in this same LIST. The
1251
+ // vault stores a directory OVERLAY (a symlink into another tree, e.g. a
1252
+ // company skill mirrored from a shared repo) as a symlink RECORD at the
1253
+ // directory's own key AND stores the directory's contents as separate child
1254
+ // objects beneath it. A REAL directory, by contrast, only ever syncs as
1255
+ // those child objects — never as a single object AT its own key. So a single
1256
+ // remote object sitting at a key that also has children is unambiguously a
1257
+ // directory-overlay marker, not a regular file. Precomputed once (pure path
1258
+ // derivation, no I/O — the planner is synchronous) so the dir-vs-object
1259
+ // collision branch can tell an inert overlay marker apart from a genuine
1260
+ // file-vs-directory structural collision. feedback_d2082110.
1261
+ const remoteKeysWithChildren = new Set();
1262
+ for (const rf of remoteFiles) {
1263
+ const key = toPosixKey(rf.key);
1264
+ let slash = key.indexOf("/");
1265
+ while (slash !== -1) {
1266
+ remoteKeysWithChildren.add(key.slice(0, slash));
1267
+ slash = key.indexOf("/", slash + 1);
1268
+ }
1269
+ }
1155
1270
  for (const remoteFile of remoteFiles) {
1156
1271
  const invalidKey = classifyVaultKey(remoteFile.key, personalMode ? "personal" : "company");
1157
1272
  if (invalidKey !== null) {
@@ -1273,6 +1388,38 @@ excludePrefixes = []) {
1273
1388
  items.push({ action: "skip-excluded-policy", remoteFile, localPath });
1274
1389
  continue;
1275
1390
  }
1391
+ // Junk-spelling guard — SKILL-FARM KEYS ONLY. The amplifier is specific
1392
+ // to `.claude/skills/<ns>:<skill>` wrapper spellings; outside that prefix,
1393
+ // keys whose percent-decode fixpoints collide (`notes/a b.md` vs a genuine
1394
+ // `notes/a%20b.md`) are DISTINCT files that must both pull.
1395
+ // Within the farm: a remote key that is NOT journaled under its own
1396
+ // spelling but whose percent-decode fixpoint matches an already-journaled
1397
+ // key with a different spelling is a mixed-version twin (`%3A`/`%253A`
1398
+ // generations of a canonical `:` key). Materializing it would mint junk
1399
+ // local dirs and fork the journal into a second key family for the same
1400
+ // logical path — skip it (loudly, never fatally; see the
1401
+ // `skip-junk-key-spelling` event doc) and leave the journaled spelling
1402
+ // authoritative for this machine.
1403
+ if (journalEntry === undefined &&
1404
+ remoteFile.key.startsWith(SKILLS_KEY_PREFIX) &&
1405
+ // Gate NON-canonical remote spellings only. A remote key that already
1406
+ // IS the canonical spelling must always be downloadable — after the
1407
+ // doctor collapses the vault, a machine whose journal still holds only
1408
+ // the junk spelling would otherwise skip the canonical object forever
1409
+ // (and the delete-resync twin protection keeps that stale junk entry),
1410
+ // deadlocking it out of every future update.
1411
+ canonicalVaultKeySpelling(remoteFile.key) !== remoteFile.key) {
1412
+ const journaledTwin = journaledKeyByCanonical.get(canonicalVaultKeySpelling(remoteFile.key));
1413
+ if (journaledTwin !== undefined && journaledTwin !== remoteFile.key) {
1414
+ items.push({
1415
+ action: "skip-junk-key-spelling",
1416
+ remoteFile,
1417
+ localPath,
1418
+ journaledKey: journaledTwin,
1419
+ });
1420
+ continue;
1421
+ }
1422
+ }
1276
1423
  // ── FILE_TOMBSTONE consult (delete-resync) ───────────────────────────────
1277
1424
  // A remote object present in the LIST may be an intentionally-deleted key
1278
1425
  // (a peer re-pushed it, or its delete-marker hasn't propagated to this
@@ -1386,6 +1533,28 @@ excludePrefixes = []) {
1386
1533
  });
1387
1534
  continue;
1388
1535
  }
1536
+ // Directory-overlay marker vs. a locally-materialized directory:
1537
+ // the remote object at this key carries CHILD objects under it in the
1538
+ // same LIST (`remoteKeysWithChildren`), so it is a symlink/overlay
1539
+ // record for a directory — e.g. the DPP vault's `skills/cody-copywriter`
1540
+ // overlay whose 13 child objects are already materialized locally. The
1541
+ // marker can never be renamed over the real directory, and its children
1542
+ // carry the actual content and materialize via their own plan items, so
1543
+ // the marker is a benign, no-action skip. The generic "rm -rf the local
1544
+ // directory to pull" advice below would tell the operator to DELETE the
1545
+ // materialized (and possibly locally-authored) contents to install an
1546
+ // inert link, and it recurred on EVERY sync cycle. feedback_d2082110.
1547
+ if (remoteKeysWithChildren.has(toPosixKey(remoteFile.key))) {
1548
+ console.error(` Note: ${remoteFile.key} is a directory-overlay marker whose ` +
1549
+ `contents already exist locally as a directory; keeping the ` +
1550
+ `local directory and ignoring the marker (no action needed).`);
1551
+ items.push({
1552
+ action: "skip-overlay-marker-with-children",
1553
+ remoteFile,
1554
+ localPath,
1555
+ });
1556
+ continue;
1557
+ }
1389
1558
  console.error(` Warning: ${remoteFile.key} exists locally as a directory; ` +
1390
1559
  `cloud has a single object at this key. Skipping; manual ` +
1391
1560
  `reconciliation required (rm -rf the local directory to pull).`);
@@ -1481,6 +1650,23 @@ excludePrefixes = []) {
1481
1650
  continue;
1482
1651
  }
1483
1652
  if (journalEntry && !localChanged && !remoteChanged) {
1653
+ // Flavor-health repair (win32 skill farm): a link journaled while its
1654
+ // target was absent was minted file-flavor; once the target directory
1655
+ // exists the hashes still match (hash = sha256(target)) so this
1656
+ // branch would skip it forever, leaving the wrapper broken. Re-download
1657
+ // instead — the materializer's flavor-aware path recreates it 'dir'.
1658
+ if (isLocalSymlink &&
1659
+ remoteFile.key.startsWith(SKILLS_KEY_PREFIX) &&
1660
+ win32SymlinkFlavorBroken(localPath)) {
1661
+ items.push({
1662
+ action: "download",
1663
+ remoteFile,
1664
+ localPath,
1665
+ isNew: false,
1666
+ localSnapshot: plannedLocalSnapshot,
1667
+ });
1668
+ continue;
1669
+ }
1484
1670
  items.push({ action: "skip-unchanged", remoteFile, localPath });
1485
1671
  continue;
1486
1672
  }
@@ -1669,8 +1855,20 @@ excludePrefixes = []) {
1669
1855
  // as a local delete + journal removal. Symmetric to the push side's
1670
1856
  // `propagateDeletes` plan in share.ts.
1671
1857
  const remoteKeySet = new Set();
1672
- for (const rf of remoteFiles)
1858
+ // Skill-farm twin protection (companion to the junk-spelling pull guard):
1859
+ // when the LIST holds only a junk spelling of a journaled canonical key,
1860
+ // the logical object is still remotely present — a raw has() would class
1861
+ // the canonical journal entry as remote-deleted, unlink the good local
1862
+ // wrapper, and drop the journal entry (after which the next pull would
1863
+ // materialize the junk spelling the guard just refused). Track canonical
1864
+ // forms of listed skill keys and consult them below.
1865
+ const remoteCanonicalSkillKeys = new Set();
1866
+ for (const rf of remoteFiles) {
1673
1867
  remoteKeySet.add(rf.key);
1868
+ if (rf.key.startsWith(SKILLS_KEY_PREFIX)) {
1869
+ remoteCanonicalSkillKeys.add(canonicalVaultKeySpelling(rf.key));
1870
+ }
1871
+ }
1674
1872
  const tombstones = [];
1675
1873
  for (const key of Object.keys(journal.files)) {
1676
1874
  // Compare membership in POSIX space. A pre-5.47.2 Windows journal key can
@@ -1681,6 +1879,16 @@ excludePrefixes = []) {
1681
1879
  const posixKey = toPosixKey(key);
1682
1880
  if (remoteKeySet.has(posixKey))
1683
1881
  continue;
1882
+ if (posixKey.startsWith(SKILLS_KEY_PREFIX) &&
1883
+ remoteCanonicalSkillKeys.has(canonicalVaultKeySpelling(posixKey)) &&
1884
+ // An EXACT FILE_TOMBSTONE for the journaled key is an authoritative
1885
+ // delete and wins over the twin protection — a lingering junk
1886
+ // spelling in the LIST must not keep a deliberately-deleted wrapper
1887
+ // alive forever. (The key is absent from the LIST here, so there is
1888
+ // no newer re-create to defer to.)
1889
+ fileTombstones.get(posixKey) === undefined) {
1890
+ continue;
1891
+ }
1684
1892
  const localPath = resolveContainedVaultPath(companyRoot, key);
1685
1893
  if (localPath === null)
1686
1894
  continue;
@@ -1844,6 +2052,15 @@ function defaultConsoleLogger(event) {
1844
2052
  console.log(` ... and ${event.count - event.samplePaths.length} more`);
1845
2053
  }
1846
2054
  }
2055
+ else if (event.type === "scope-materialization-gap") {
2056
+ console.warn(` ! ${event.count} item${event.count === 1 ? "" : "s"} in this vault did NOT sync to this device — your access level is "${event.syncMode}", so only shared paths materialize. To pull everything you have access to, set this membership's access level to "all":`);
2057
+ for (const p of event.samplePaths) {
2058
+ console.warn(` · ${p}`);
2059
+ }
2060
+ if (event.count > event.samplePaths.length) {
2061
+ console.warn(` ... and ${event.count - event.samplePaths.length} more`);
2062
+ }
2063
+ }
1847
2064
  else if (event.type === "ignore-excluded") {
1848
2065
  console.warn(` ! ${event.count} path${event.count === 1 ? "" : "s"} were EXCLUDED from sync by an ignore rule and did NOT reach the vault (review in case this is unintended):`);
1849
2066
  for (const p of event.samplePaths) {
@@ -1860,5 +2077,8 @@ function defaultConsoleLogger(event) {
1860
2077
  else if (event.type === "skip-invalid-scoped-key") {
1861
2078
  console.warn(` ! ${event.path} skipped — invalid vault key (${event.errorCode ?? "INVALID_KEY_COMPANIES_SCOPED"})`);
1862
2079
  }
2080
+ else if (event.type === "skip-junk-key-spelling") {
2081
+ console.warn(` ! ${event.path} skipped — junk key spelling; this logical path is already synced as ${event.journaledKey} (mixed-version peer artifact; the vault doctor collapses the family)`);
2082
+ }
1863
2083
  }
1864
2084
  //# sourceMappingURL=sync.js.map