@indigoai-us/hq-cloud 6.11.13 → 6.11.15

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 (43) hide show
  1. package/.github/workflows/ci.yml +18 -26
  2. package/dist/bin/sync-runner-telemetry.d.ts +1 -0
  3. package/dist/bin/sync-runner-telemetry.d.ts.map +1 -1
  4. package/dist/bin/sync-runner-telemetry.js +8 -1
  5. package/dist/bin/sync-runner-telemetry.js.map +1 -1
  6. package/dist/bin/sync-runner.d.ts +9 -1
  7. package/dist/bin/sync-runner.d.ts.map +1 -1
  8. package/dist/bin/sync-runner.js +115 -9
  9. package/dist/bin/sync-runner.js.map +1 -1
  10. package/dist/cli/share.d.ts.map +1 -1
  11. package/dist/cli/share.js +15 -1
  12. package/dist/cli/share.js.map +1 -1
  13. package/dist/cli/sync.d.ts.map +1 -1
  14. package/dist/cli/sync.js +26 -2
  15. package/dist/cli/sync.js.map +1 -1
  16. package/dist/cli/sync.test.js +16 -0
  17. package/dist/cli/sync.test.js.map +1 -1
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/qmd-reindex.d.ts +7 -0
  23. package/dist/qmd-reindex.d.ts.map +1 -1
  24. package/dist/qmd-reindex.js +12 -1
  25. package/dist/qmd-reindex.js.map +1 -1
  26. package/dist/sync-progress.d.ts +37 -0
  27. package/dist/sync-progress.d.ts.map +1 -0
  28. package/dist/sync-progress.js +104 -0
  29. package/dist/sync-progress.js.map +1 -0
  30. package/dist/sync-progress.test.d.ts +2 -0
  31. package/dist/sync-progress.test.d.ts.map +1 -0
  32. package/dist/sync-progress.test.js +70 -0
  33. package/dist/sync-progress.test.js.map +1 -0
  34. package/package.json +3 -1
  35. package/src/bin/sync-runner-telemetry.ts +8 -1
  36. package/src/bin/sync-runner.ts +131 -7
  37. package/src/cli/share.ts +17 -1
  38. package/src/cli/sync.test.ts +17 -0
  39. package/src/cli/sync.ts +35 -2
  40. package/src/index.ts +9 -0
  41. package/src/qmd-reindex.ts +18 -1
  42. package/src/sync-progress.test.ts +81 -0
  43. package/src/sync-progress.ts +133 -0
@@ -1 +1 @@
1
- {"version":3,"file":"qmd-reindex.js","sourceRoot":"","sources":["../src/qmd-reindex.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"qmd-reindex.js","sourceRoot":"","sources":["../src/qmd-reindex.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { SyncProgressEvent } from "./cli/sync.js";
2
+ /** Absolute path of the shared progress snapshot. Exported for the watcher + tests. */
3
+ export declare function syncProgressPath(): string;
4
+ export interface SyncProgressSnapshot {
5
+ schema: 1;
6
+ /** PID of the writing sync — lets a watcher detect a dead/stale holder. */
7
+ pid: number;
8
+ /** Company slug being synced, or null for the personal vault. */
9
+ company: string | null;
10
+ /** Which leg is running. */
11
+ phase: "pull" | "push";
12
+ filesTotal: number;
13
+ filesDone: number;
14
+ conflicts: number;
15
+ /** Path of the file currently transferring, or null before the first one. */
16
+ currentFile: string | null;
17
+ startedAt: string;
18
+ updatedAt: string;
19
+ status: "syncing";
20
+ }
21
+ export interface SyncProgressRecorderOptions {
22
+ company?: string | null;
23
+ phase: "pull" | "push";
24
+ }
25
+ /**
26
+ * Build an event sink that accumulates a sync's progress and atomic-writes the
27
+ * shared snapshot on each meaningful event. Wrap the engine's `onEvent` with
28
+ * it: `const emit = (e) => { baseEmit(e); record(e); }`.
29
+ */
30
+ export declare function createSyncProgressRecorder(opts: SyncProgressRecorderOptions): (event: SyncProgressEvent) => void;
31
+ /**
32
+ * Read the current snapshot, or null if absent/unreadable. Consumers should
33
+ * additionally treat a snapshot whose `updatedAt` is older than a few seconds
34
+ * (or whose `pid` is dead) as idle.
35
+ */
36
+ export declare function readSyncProgress(): SyncProgressSnapshot | null;
37
+ //# sourceMappingURL=sync-progress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-progress.d.ts","sourceRoot":"","sources":["../src/sync-progress.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAOvD,uFAAuF;AACvF,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,CAAC,CAAC;IACV,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,iEAAiE;IACjE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,2BAA2B,GAChC,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAwDpC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,oBAAoB,GAAG,IAAI,CAQ9D"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Cross-process sync progress.
3
+ *
4
+ * Every sync path — the menubar watch daemon (auto-sync), "Sync Now", and a
5
+ * CLI `hq sync` — runs through the same engine (`sync()` pull + `share()`
6
+ * push). Each writes one shared status snapshot here so a watcher (the menubar)
7
+ * can show LIVE progress for ANY sync, not just the one it spawned over stdout.
8
+ *
9
+ * Because syncs are serialized (the op-lock; the watch daemon coordinates with
10
+ * one-shots), there is at most one active sync, so this single file is
11
+ * unambiguous. Writes are atomic (temp + rename) — a torn read at worst shows
12
+ * slightly stale progress. There is intentionally no explicit "done": the
13
+ * engine simply stops writing when a sync ends, so a consumer treats the file
14
+ * as idle once `updatedAt` is older than a few seconds. Progress reporting must
15
+ * never break a sync, so every fs error here is swallowed.
16
+ */
17
+ import * as fs from "fs";
18
+ import * as os from "os";
19
+ import * as path from "path";
20
+ /** Mirror operation-lock's stateDir so the file lands next to the journal/lock. */
21
+ function stateDir() {
22
+ return process.env.HQ_STATE_DIR || path.join(os.homedir(), ".hq");
23
+ }
24
+ /** Absolute path of the shared progress snapshot. Exported for the watcher + tests. */
25
+ export function syncProgressPath() {
26
+ return path.join(stateDir(), "sync-progress.json");
27
+ }
28
+ /**
29
+ * Build an event sink that accumulates a sync's progress and atomic-writes the
30
+ * shared snapshot on each meaningful event. Wrap the engine's `onEvent` with
31
+ * it: `const emit = (e) => { baseEmit(e); record(e); }`.
32
+ */
33
+ export function createSyncProgressRecorder(opts) {
34
+ let filesTotal = 0;
35
+ let filesDone = 0;
36
+ let conflicts = 0;
37
+ let currentFile = null;
38
+ const startedAt = new Date().toISOString();
39
+ const file = syncProgressPath();
40
+ const flush = () => {
41
+ const snap = {
42
+ schema: 1,
43
+ pid: process.pid,
44
+ company: opts.company ?? null,
45
+ phase: opts.phase,
46
+ filesTotal,
47
+ filesDone,
48
+ conflicts,
49
+ currentFile,
50
+ startedAt,
51
+ updatedAt: new Date().toISOString(),
52
+ status: "syncing",
53
+ };
54
+ try {
55
+ fs.mkdirSync(path.dirname(file), { recursive: true });
56
+ const tmp = `${file}.${process.pid}.tmp`;
57
+ fs.writeFileSync(tmp, JSON.stringify(snap));
58
+ fs.renameSync(tmp, file);
59
+ }
60
+ catch {
61
+ // Progress reporting is best-effort and must never break a sync.
62
+ }
63
+ };
64
+ return (event) => {
65
+ switch (event.type) {
66
+ case "plan":
67
+ // Sum the work this leg will do. `plan` arrives once before any
68
+ // progress events for the leg.
69
+ filesTotal +=
70
+ event.filesToDownload + event.filesToUpload + event.filesToDelete;
71
+ flush();
72
+ break;
73
+ case "progress":
74
+ filesDone += 1;
75
+ currentFile = event.path;
76
+ flush();
77
+ break;
78
+ case "conflict":
79
+ conflicts += 1;
80
+ flush();
81
+ break;
82
+ default:
83
+ // error / new-files / reconciled / delete-refused etc. don't move the
84
+ // bar — skip the write to keep the file churn-free.
85
+ break;
86
+ }
87
+ };
88
+ }
89
+ /**
90
+ * Read the current snapshot, or null if absent/unreadable. Consumers should
91
+ * additionally treat a snapshot whose `updatedAt` is older than a few seconds
92
+ * (or whose `pid` is dead) as idle.
93
+ */
94
+ export function readSyncProgress() {
95
+ try {
96
+ const raw = fs.readFileSync(syncProgressPath(), "utf8");
97
+ const parsed = JSON.parse(raw);
98
+ return parsed && parsed.schema === 1 ? parsed : null;
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }
104
+ //# sourceMappingURL=sync-progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-progress.js","sourceRoot":"","sources":["../src/sync-progress.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sync-progress.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-progress.test.d.ts","sourceRoot":"","sources":["../src/sync-progress.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import * as fs from "fs";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+ import { createSyncProgressRecorder, readSyncProgress, } from "./sync-progress.js";
6
+ const planEvent = (download, upload, del) => ({
7
+ type: "plan",
8
+ filesToDownload: download,
9
+ bytesToDownload: 0,
10
+ filesToUpload: upload,
11
+ bytesToUpload: 0,
12
+ filesToSkip: 0,
13
+ filesToConflict: 0,
14
+ filesToDelete: del,
15
+ });
16
+ describe("sync-progress", () => {
17
+ let stateDir;
18
+ beforeEach(() => {
19
+ stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-progress-test-"));
20
+ process.env.HQ_STATE_DIR = stateDir;
21
+ });
22
+ afterEach(() => {
23
+ delete process.env.HQ_STATE_DIR;
24
+ fs.rmSync(stateDir, { recursive: true, force: true });
25
+ });
26
+ it("returns null when no sync has written a snapshot", () => {
27
+ expect(readSyncProgress()).toBeNull();
28
+ });
29
+ it("accumulates plan totals, progress, and conflicts into the shared snapshot", () => {
30
+ const record = createSyncProgressRecorder({ company: "acme", phase: "pull" });
31
+ record(planEvent(3, 0, 1)); // 3 to download + 1 to delete = 4 total
32
+ record({ type: "progress", path: "a.md", bytes: 10 });
33
+ record({ type: "progress", path: "b.md", bytes: 10 });
34
+ record({
35
+ type: "conflict",
36
+ path: "c.md",
37
+ direction: "pull",
38
+ resolution: "keep",
39
+ });
40
+ const snap = readSyncProgress();
41
+ expect(snap).not.toBeNull();
42
+ expect(snap.company).toBe("acme");
43
+ expect(snap.phase).toBe("pull");
44
+ expect(snap.filesTotal).toBe(4);
45
+ expect(snap.filesDone).toBe(2);
46
+ expect(snap.conflicts).toBe(1);
47
+ expect(snap.currentFile).toBe("b.md");
48
+ expect(snap.status).toBe("syncing");
49
+ expect(snap.pid).toBe(process.pid);
50
+ expect(typeof snap.updatedAt).toBe("string");
51
+ });
52
+ it("records the push leg + personal scope (company null)", () => {
53
+ const record = createSyncProgressRecorder({ company: null, phase: "push" });
54
+ record(planEvent(0, 5, 0));
55
+ const snap = readSyncProgress();
56
+ expect(snap.phase).toBe("push");
57
+ expect(snap.company).toBeNull();
58
+ expect(snap.filesTotal).toBe(5);
59
+ });
60
+ it("ignores non-progress events (no churn from new-files/reconciled)", () => {
61
+ const record = createSyncProgressRecorder({ company: "acme", phase: "pull" });
62
+ record(planEvent(1, 0, 0));
63
+ record({ type: "new-files", files: [] });
64
+ record({ type: "reconciled", path: "x.md", direction: "pull" });
65
+ const snap = readSyncProgress();
66
+ expect(snap.filesDone).toBe(0);
67
+ expect(snap.filesTotal).toBe(1);
68
+ });
69
+ });
70
+ //# sourceMappingURL=sync-progress.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-progress.test.js","sourceRoot":"","sources":["../src/sync-progress.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cloud",
3
- "version": "6.11.13",
3
+ "version": "6.11.15",
4
4
  "description": "HQ by Indigo cloud sync engine — bidirectional S3 sync for mobile access",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,6 +12,7 @@
12
12
  "build": "tsc",
13
13
  "typecheck": "tsc --noEmit",
14
14
  "test": "vitest run src/",
15
+ "coverage": "vitest run src/ --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary",
15
16
  "test:e2e": "vitest run test/",
16
17
  "test:watch": "vitest",
17
18
  "clean": "rm -rf dist"
@@ -33,6 +34,7 @@
33
34
  "@types/js-yaml": "^4.0.9",
34
35
  "@types/node": "^22.0.0",
35
36
  "aws-sdk-client-mock": "^4.1.0",
37
+ "@vitest/coverage-v8": "3.2.4",
36
38
  "typescript": "^5.7.0",
37
39
  "vitest": "^3.0.0"
38
40
  },
@@ -1,8 +1,15 @@
1
1
  export async function emitTelemetry(options: {
2
2
  collectTelemetry?: () => Promise<void>;
3
3
  defaultCollectTelemetry: () => Promise<void>;
4
+ onError?: (err: unknown) => void;
4
5
  }): Promise<void> {
5
6
  const telemetryFn =
6
7
  options.collectTelemetry ?? options.defaultCollectTelemetry;
7
- await telemetryFn().catch(() => undefined);
8
+ await telemetryFn().catch((err) => {
9
+ try {
10
+ options.onError?.(err);
11
+ } catch {
12
+ /* diagnostics must never affect sync */
13
+ }
14
+ });
8
15
  }
@@ -107,6 +107,7 @@ import { collectAndSendSkillTelemetry } from "../skill-telemetry.js";
107
107
  import { reindexAfterSync } from "../qmd-reindex.js";
108
108
  import { pruneConflictIndex } from "../lib/conflict-index.js";
109
109
  import { getOrCreateMachineId } from "../lib/machine-id.js";
110
+ import { describeError } from "../lib/describe-error.js";
110
111
  import type { Clock, TreeChangeBatch } from "../watcher.js";
111
112
  import type { PushReceiver, SyncEngineFn } from "../sync/push-receiver.js";
112
113
  import {
@@ -404,6 +405,53 @@ export interface VaultClientSurface {
404
405
  listMyExplicitGrants?: (companyUid: string) => Promise<ExplicitGrant[]>;
405
406
  }
406
407
 
408
+ interface RunnerDiagnostic {
409
+ component: string;
410
+ event: string;
411
+ message: string;
412
+ err?: unknown;
413
+ context?: Record<string, unknown>;
414
+ }
415
+
416
+ type RunnerDiagnosticReporter = (diagnostic: RunnerDiagnostic) => void;
417
+
418
+ function errorDetails(err: unknown): Record<string, unknown> {
419
+ const details: Record<string, unknown> = { message: describeError(err) };
420
+ if (err instanceof Error) {
421
+ if (err.name) details.name = err.name;
422
+ const code = (err as NodeJS.ErrnoException).code;
423
+ if (code) details.code = code;
424
+ }
425
+ return details;
426
+ }
427
+
428
+ function createRunnerDiagnosticReporter(
429
+ stderr: { write: (chunk: string) => boolean | void },
430
+ ): RunnerDiagnosticReporter {
431
+ return (diagnostic) => {
432
+ try {
433
+ const payload: Record<string, unknown> = {
434
+ type: "error",
435
+ diagnostic: true,
436
+ component: diagnostic.component,
437
+ event: diagnostic.event,
438
+ path: `(${diagnostic.component})`,
439
+ message:
440
+ diagnostic.err === undefined
441
+ ? diagnostic.message
442
+ : `${diagnostic.message}: ${describeError(diagnostic.err)}`,
443
+ ...(diagnostic.context ?? {}),
444
+ };
445
+ if (diagnostic.err !== undefined) {
446
+ payload.err = errorDetails(diagnostic.err);
447
+ }
448
+ stderr.write(`${JSON.stringify(payload)}\n`);
449
+ } catch {
450
+ /* diagnostics must never affect runner control flow */
451
+ }
452
+ };
453
+ }
454
+
407
455
  // `resolvePullScope`, `readPinnedPrefixes`, and the `PullScope` type now live
408
456
  // in `../sync/pull-scope.ts` so the menubar runner and `hq sync pull|now`
409
457
  // (hq-cli) share ONE scope resolver — the drift between them was the root
@@ -769,6 +817,7 @@ export async function defaultCollectTelemetry(
769
817
  client: VaultClientSurface,
770
818
  clientIsStub: boolean,
771
819
  hqRoot: string,
820
+ reportDiagnostic: RunnerDiagnosticReporter = () => undefined,
772
821
  ): Promise<void> {
773
822
  if (clientIsStub) return;
774
823
 
@@ -797,7 +846,14 @@ export async function defaultCollectTelemetry(
797
846
  try {
798
847
  const persons = await client.entity.listByType("person");
799
848
  if (pickCanonicalPersonEntity(persons) === null) return;
800
- } catch {
849
+ } catch (err) {
850
+ reportDiagnostic({
851
+ component: "telemetry",
852
+ event: "runner.telemetry.person_probe_failed",
853
+ message: "telemetry person probe failed; continuing with telemetry",
854
+ err,
855
+ context: { hqRoot },
856
+ });
801
857
  // Probe failed — preserve prior behavior and let telemetry run.
802
858
  }
803
859
 
@@ -820,7 +876,14 @@ export async function defaultCollectTelemetry(
820
876
  try {
821
877
  machineId = getOrCreateMachineId(hqRoot);
822
878
  installerVersion = process.env.HQ_INSTALLER_VERSION ?? "hq-cloud";
823
- } catch {
879
+ } catch (err) {
880
+ reportDiagnostic({
881
+ component: "telemetry",
882
+ event: "runner.telemetry.identity_failed",
883
+ message: "telemetry identity resolution failed; skipping telemetry",
884
+ err,
885
+ context: { hqRoot },
886
+ });
824
887
  return;
825
888
  }
826
889
 
@@ -836,8 +899,22 @@ export async function defaultCollectTelemetry(
836
899
  // manifest.yaml and stamp companyUid (US-002). Unmatched cwds stay
837
900
  // unattributed (field omitted).
838
901
  hqRoot,
902
+ log: (message) =>
903
+ reportDiagnostic({
904
+ component: "telemetry",
905
+ event: "runner.telemetry.usage_nonfatal",
906
+ message,
907
+ context: { hqRoot },
908
+ }),
909
+ });
910
+ } catch (err) {
911
+ reportDiagnostic({
912
+ component: "telemetry",
913
+ event: "runner.telemetry.usage_failed",
914
+ message: "usage telemetry collector threw",
915
+ err,
916
+ context: { hqRoot },
839
917
  });
840
- } catch {
841
918
  // Fire-and-forget; nothing escapes the boundary.
842
919
  }
843
920
 
@@ -851,8 +928,22 @@ export async function defaultCollectTelemetry(
851
928
  // Scope skill capture to the HQ project — only invocations whose cwd is
852
929
  // the HQ root are emitted; skill usage in unrelated repos is excluded.
853
930
  hqRoot,
931
+ log: (message) =>
932
+ reportDiagnostic({
933
+ component: "telemetry",
934
+ event: "runner.telemetry.skill_nonfatal",
935
+ message,
936
+ context: { hqRoot },
937
+ }),
938
+ });
939
+ } catch (err) {
940
+ reportDiagnostic({
941
+ component: "telemetry",
942
+ event: "runner.telemetry.skill_failed",
943
+ message: "skill telemetry collector threw",
944
+ err,
945
+ context: { hqRoot },
854
946
  });
855
- } catch {
856
947
  // Fire-and-forget; nothing escapes the boundary.
857
948
  }
858
949
  }
@@ -867,6 +958,7 @@ export async function runRunner(
867
958
  ): Promise<number> {
868
959
  const stdout = deps.stdout ?? process.stdout;
869
960
  const stderr = deps.stderr ?? process.stderr;
961
+ const reportDiagnostic = createRunnerDiagnosticReporter(stderr);
870
962
 
871
963
  // ---- emit ---------------------------------------------------------------
872
964
  // Error-class events go to stderr; everything else to stdout.
@@ -1068,7 +1160,16 @@ export async function runRunner(
1068
1160
  client,
1069
1161
  deps.createVaultClient !== undefined,
1070
1162
  parsed.hqRoot,
1163
+ reportDiagnostic,
1071
1164
  ),
1165
+ onError: (err) =>
1166
+ reportDiagnostic({
1167
+ component: "telemetry",
1168
+ event: "runner.telemetry.collector_failed",
1169
+ message: "telemetry collector failed",
1170
+ err,
1171
+ context: { hqRoot: parsed.hqRoot },
1172
+ }),
1072
1173
  });
1073
1174
 
1074
1175
  emit({
@@ -1095,8 +1196,24 @@ export async function runRunner(
1095
1196
  process.env.HQ_QMD_REINDEX_ON_SYNC !== "0"
1096
1197
  ) {
1097
1198
  try {
1098
- reindexAfterSync(parsed.hqRoot);
1099
- } catch {
1199
+ reindexAfterSync(parsed.hqRoot, {
1200
+ log: ({ event, message, err, context }) =>
1201
+ reportDiagnostic({
1202
+ component: "qmd-reindex",
1203
+ event,
1204
+ message,
1205
+ err,
1206
+ context: { hqRoot: parsed.hqRoot, ...(context ?? {}) },
1207
+ }),
1208
+ });
1209
+ } catch (err) {
1210
+ reportDiagnostic({
1211
+ component: "qmd-reindex",
1212
+ event: "runner.qmd_reindex.failed",
1213
+ message: "post-sync qmd reindex threw",
1214
+ err,
1215
+ context: { hqRoot: parsed.hqRoot },
1216
+ });
1100
1217
  // Defensive: reindexAfterSync already swallows internally.
1101
1218
  }
1102
1219
  }
@@ -1113,7 +1230,14 @@ export async function runRunner(
1113
1230
  if (process.env.HQ_CONFLICT_PRUNE_ON_SYNC !== "0") {
1114
1231
  try {
1115
1232
  pruneConflictIndex(parsed.hqRoot);
1116
- } catch {
1233
+ } catch (err) {
1234
+ reportDiagnostic({
1235
+ component: "conflict-index",
1236
+ event: "runner.conflict_index.prune_failed",
1237
+ message: "post-sync conflict index prune failed",
1238
+ err,
1239
+ context: { hqRoot: parsed.hqRoot },
1240
+ });
1117
1241
  // Defensive: a prune failure must never affect the sync outcome.
1118
1242
  }
1119
1243
  }
package/src/cli/share.ts CHANGED
@@ -10,6 +10,7 @@ import * as os from "os";
10
10
  import * as path from "path";
11
11
  import type { EntityContext, VaultServiceConfig, SyncJournal } from "../types.js";
12
12
  import { resolveEntityContext, isExpiringSoon, refreshEntityContext } from "../context.js";
13
+ import { createSyncProgressRecorder } from "../sync-progress.js";
13
14
  import {
14
15
  uploadFile,
15
16
  uploadSymlink,
@@ -886,7 +887,7 @@ async function createPushRunContext(options: ShareOptions): Promise<PushRunConte
886
887
  const { paths, company, message, onConflict, vaultConfig, entityContext, hqRoot, skipUnchanged, propagateDeletes } = options;
887
888
  let propagateDeletePolicy: DeletePolicy =
888
889
  options.propagateDeletePolicy ?? "owned-only";
889
- const emit = options.onEvent ?? defaultConsoleLogger;
890
+ const baseEmit = options.onEvent ?? defaultConsoleLogger;
890
891
 
891
892
  if (vaultConfig && entityContext) {
892
893
  throw new Error(
@@ -918,6 +919,21 @@ async function createPushRunContext(options: ShareOptions): Promise<PushRunConte
918
919
  propagateDeletePolicy = "currency-gated";
919
920
  }
920
921
 
922
+ // Mirror push progress into the shared cross-process snapshot (see sync.ts).
923
+ // Record the friendly company slug, or null for the personal vault so the
924
+ // menubar shows "Personal" — never the raw prs_/cmp_ UID.
925
+ const recordProgress = createSyncProgressRecorder({
926
+ company:
927
+ ctx.uid.startsWith("prs_") || options.personalMode === true
928
+ ? null
929
+ : ctx.slug,
930
+ phase: "push",
931
+ });
932
+ const emit: ShareEmit = (event) => {
933
+ baseEmit(event);
934
+ recordProgress(event);
935
+ };
936
+
921
937
  const syncRoot = options.personalMode === true
922
938
  ? hqRoot
923
939
  : path.join(hqRoot, "companies", ctx.slug);
@@ -46,6 +46,7 @@ vi.mock("./reindex.js", () => ({
46
46
  import { sync, reportNewFilesToNotify } from "./sync.js";
47
47
  import * as s3Module from "../s3.js";
48
48
  import { reindex } from "./reindex.js";
49
+ import { readSyncProgress } from "../sync-progress.js";
49
50
 
50
51
  const mockConfig: VaultServiceConfig = {
51
52
  apiUrl: "https://vault-api.test",
@@ -144,6 +145,22 @@ describe("sync", () => {
144
145
  expect(reindex).toHaveBeenCalledWith({ repoRoot: tmpDir, skipLock: true });
145
146
  });
146
147
 
148
+ it("records the company slug, not the input UID, in the shared progress snapshot", async () => {
149
+ // sync() is called with the company UID, but the menubar popover must show
150
+ // the friendly slug — so the producer records ctx.slug, never the raw UID.
151
+ vi.mocked(s3Module.listRemoteFiles).mockResolvedValueOnce([
152
+ { key: "docs/x.md", size: 10, lastModified: new Date(), etag: '"x"' },
153
+ ]);
154
+ await sync({
155
+ company: "cmp_01ABCDEF",
156
+ vaultConfig: mockConfig,
157
+ hqRoot: tmpDir,
158
+ });
159
+ const snap = readSyncProgress();
160
+ expect(snap).not.toBeNull();
161
+ expect(snap!.company).toBe("acme"); // ctx.slug, not "cmp_01ABCDEF"
162
+ });
163
+
147
164
  it("F15: public sync entrypoint refuses an already-held operation lock", async () => {
148
165
  process.env.HQ_OP_LOCK_TIMEOUT = "0";
149
166
  const lockPath = lockPathFor(tmpDir);
package/src/cli/sync.ts CHANGED
@@ -14,6 +14,7 @@ import type {
14
14
  } from "../types.js";
15
15
  import type { SyncMode } from "../vault-client.js";
16
16
  import { resolveEntityContext, isExpiringSoon, refreshEntityContext } from "../context.js";
17
+ import { createSyncProgressRecorder } from "../sync-progress.js";
17
18
  import {
18
19
  downloadFile,
19
20
  listRemoteFiles,
@@ -657,6 +658,23 @@ function logNotifyFailure(err: unknown): void {
657
658
  }
658
659
  }
659
660
 
661
+ /** Log a non-fatal post-sync maintenance failure without changing sync state. */
662
+ function logPostSyncMaintenanceFailure(
663
+ task: string,
664
+ hqRoot: string,
665
+ err: unknown,
666
+ ): void {
667
+ try {
668
+ console.error(
669
+ `[hq-sync] ${task} failed (non-fatal; hqRoot=${hqRoot}): ${
670
+ err instanceof Error ? err.message : String(err)
671
+ }`,
672
+ );
673
+ } catch {
674
+ // swallow — logging must never break sync
675
+ }
676
+ }
677
+
660
678
  /**
661
679
  * Sync (pull) all allowed files from the entity vault.
662
680
  */
@@ -708,7 +726,7 @@ async function syncWithOperationLockHeld(
708
726
 
709
727
  async function buildPullContext(options: SyncOptions): Promise<PullRunContext> {
710
728
  const { company, vaultConfig, hqRoot } = options;
711
- const emit = options.onEvent ?? defaultConsoleLogger;
729
+ const baseEmit = options.onEvent ?? defaultConsoleLogger;
712
730
 
713
731
  const companyRef = company ?? resolveActiveCompany(hqRoot);
714
732
  if (!companyRef) {
@@ -726,6 +744,20 @@ async function buildPullContext(options: SyncOptions): Promise<PullRunContext> {
726
744
  const journalSlug = options.journalSlug ?? ctx.slug;
727
745
  const startedAt = new Date().toISOString();
728
746
 
747
+ // Mirror every progress event into the shared cross-process snapshot so the
748
+ // menubar shows live progress for THIS sync regardless of who launched it
749
+ // (auto-sync / Sync Now / CLI). Record the friendly company slug (e.g.
750
+ // "indigo") — or null for the personal vault so the menubar shows "Personal"
751
+ // — never the raw prs_/cmp_ UID. Best-effort; never affects the sync.
752
+ const recordProgress = createSyncProgressRecorder({
753
+ company: options.personalMode === true ? null : ctx.slug,
754
+ phase: "pull",
755
+ });
756
+ const emit: SyncEventEmitter = (event) => {
757
+ baseEmit(event);
758
+ recordProgress(event);
759
+ };
760
+
729
761
  if (journalSlug === PERSONAL_VAULT_JOURNAL_SLUG) migratePersonalVaultJournal();
730
762
  const journal = migrateToV2(readJournal(journalSlug));
731
763
  gcTombstones(journal, Date.now());
@@ -1454,7 +1486,8 @@ function finalizePullRun(
1454
1486
  if (!run.options.skipReindex && changedOnDisk) {
1455
1487
  try {
1456
1488
  reindex({ repoRoot: run.hqRoot, skipLock: true });
1457
- } catch {
1489
+ } catch (err) {
1490
+ logPostSyncMaintenanceFailure("post-sync reindex", run.hqRoot, err);
1458
1491
  // best-effort: a post-sync refresh failure never fails the sync
1459
1492
  }
1460
1493
  }