@indigoai-us/hq-cloud 6.18.14-alpha.2 → 6.18.14

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 (51) hide show
  1. package/dist/bin/sync-runner-receiver-unresolved-company.test.d.ts +2 -0
  2. package/dist/bin/sync-runner-receiver-unresolved-company.test.d.ts.map +1 -0
  3. package/dist/bin/sync-runner-receiver-unresolved-company.test.js +379 -0
  4. package/dist/bin/sync-runner-receiver-unresolved-company.test.js.map +1 -0
  5. package/dist/bin/sync-runner-watch-loop.d.ts +2 -1
  6. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  7. package/dist/bin/sync-runner-watch-loop.js +76 -50
  8. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  9. package/dist/bin/sync-runner-watch-loop.test.js +2 -199
  10. package/dist/bin/sync-runner-watch-loop.test.js.map +1 -1
  11. package/dist/bin/sync-runner.d.ts +1 -12
  12. package/dist/bin/sync-runner.d.ts.map +1 -1
  13. package/dist/bin/sync-runner.js +14 -32
  14. package/dist/bin/sync-runner.js.map +1 -1
  15. package/dist/bin/sync-runner.test.js +4 -4
  16. package/dist/bin/sync-runner.test.js.map +1 -1
  17. package/dist/cli/sync.d.ts.map +1 -1
  18. package/dist/cli/sync.js +22 -18
  19. package/dist/cli/sync.js.map +1 -1
  20. package/dist/cli/sync.test.js +32 -44
  21. package/dist/cli/sync.test.js.map +1 -1
  22. package/dist/object-io.d.ts.map +1 -1
  23. package/dist/object-io.js +10 -0
  24. package/dist/object-io.js.map +1 -1
  25. package/dist/object-io.test.js +28 -1
  26. package/dist/object-io.test.js.map +1 -1
  27. package/dist/qmd-reindex.d.ts +10 -30
  28. package/dist/qmd-reindex.d.ts.map +1 -1
  29. package/dist/qmd-reindex.js +41 -148
  30. package/dist/qmd-reindex.js.map +1 -1
  31. package/dist/qmd-reindex.test.js +77 -110
  32. package/dist/qmd-reindex.test.js.map +1 -1
  33. package/dist/sync/push-receiver.d.ts +10 -14
  34. package/dist/sync/push-receiver.d.ts.map +1 -1
  35. package/dist/sync/push-receiver.js +40 -71
  36. package/dist/sync/push-receiver.js.map +1 -1
  37. package/dist/sync/push-receiver.test.js +1 -84
  38. package/dist/sync/push-receiver.test.js.map +1 -1
  39. package/dist/sync-core.d.ts +7 -0
  40. package/dist/sync-core.d.ts.map +1 -1
  41. package/dist/sync-core.js +12 -0
  42. package/dist/sync-core.js.map +1 -1
  43. package/package.json +1 -1
  44. package/dist/sync/targeted-pull-prefixes.d.ts +0 -9
  45. package/dist/sync/targeted-pull-prefixes.d.ts.map +0 -1
  46. package/dist/sync/targeted-pull-prefixes.js +0 -43
  47. package/dist/sync/targeted-pull-prefixes.js.map +0 -1
  48. package/dist/sync/targeted-pull-prefixes.test.d.ts +0 -2
  49. package/dist/sync/targeted-pull-prefixes.test.d.ts.map +0 -1
  50. package/dist/sync/targeted-pull-prefixes.test.js +0 -33
  51. package/dist/sync/targeted-pull-prefixes.test.js.map +0 -1
@@ -14,7 +14,7 @@ import { describe, it, expect } from "vitest";
14
14
  import * as fs from "fs";
15
15
  import * as os from "os";
16
16
  import * as path from "path";
17
- import { reindexAfterSync, createQmdExec, looksCorrupt, resolveExecTimeoutMs, DEFAULT_QMD_EXEC_TIMEOUT_MS, parseIndexDirFromStatus, parseCollectionPathFromShow, pathsEquivalent, repairPathDriftEnabled, acquireDualReindexLocks, } from "./qmd-reindex.js";
17
+ import { reindexAfterSync, looksCorrupt, resolveExecTimeoutMs, DEFAULT_QMD_EXEC_TIMEOUT_MS, parseIndexDirFromStatus, parseCollectionPathFromShow, pathsEquivalent, repairPathDriftEnabled, acquireDualReindexLocks, } from "./qmd-reindex.js";
18
18
  /** A lock acquirer that always succeeds — keeps command-sequence tests pure. */
19
19
  const noLock = () => ({ release() { } });
20
20
  /** Identity realpath so path-drift comparisons stay deterministic in fakes. */
@@ -22,7 +22,7 @@ const identityRealpath = (p) => path.resolve(p);
22
22
  /** Build a fake exec that returns status 0 for everything and logs calls. */
23
23
  function fakeExec(opts) {
24
24
  const calls = [];
25
- const exec = async (args) => {
25
+ const exec = (args) => {
26
26
  calls.push(args);
27
27
  const override = opts?.resultFor?.(args);
28
28
  if (override)
@@ -48,9 +48,9 @@ function fakeExec(opts) {
48
48
  return { exec, calls };
49
49
  }
50
50
  describe("reindexAfterSync", () => {
51
- it("no-ops when the path is not an HQ root", async () => {
51
+ it("no-ops when the path is not an HQ root", () => {
52
52
  const { exec, calls } = fakeExec();
53
- const r = await reindexAfterSync("/tmp/not-hq", {
53
+ const r = reindexAfterSync("/tmp/not-hq", {
54
54
  exec,
55
55
  acquireReindexLock: noLock,
56
56
  existsSync: () => false, // core/core.yaml absent
@@ -58,9 +58,9 @@ describe("reindexAfterSync", () => {
58
58
  expect(r.qmdAvailable).toBe(false);
59
59
  expect(calls).toHaveLength(0);
60
60
  });
61
- it("no-ops when qmd is unavailable (collection list errors)", async () => {
61
+ it("no-ops when qmd is unavailable (collection list errors)", () => {
62
62
  const { exec, calls } = fakeExec({ failOn: "collection" });
63
- const r = await reindexAfterSync("/hq", {
63
+ const r = reindexAfterSync("/hq", {
64
64
  exec,
65
65
  acquireReindexLock: noLock,
66
66
  existsSync: () => true, // core.yaml present
@@ -69,9 +69,9 @@ describe("reindexAfterSync", () => {
69
69
  expect(r.updated).toBe(false);
70
70
  expect(calls).toEqual([["status"], ["collection", "list"]]);
71
71
  });
72
- it("registers a missing company collection then runs an incremental update", async () => {
72
+ it("registers a missing company collection then runs an incremental update", () => {
73
73
  const { exec, calls } = fakeExec({ listStdout: "Collections (1):\n\nHQ (qmd://HQ/)\n" });
74
- const r = await reindexAfterSync("/hq", {
74
+ const r = reindexAfterSync("/hq", {
75
75
  exec,
76
76
  acquireReindexLock: noLock,
77
77
  existsSync: () => true,
@@ -90,9 +90,9 @@ describe("reindexAfterSync", () => {
90
90
  ["update"],
91
91
  ]);
92
92
  });
93
- it("skips collections that already exist at the desired path", async () => {
93
+ it("skips collections that already exist at the desired path", () => {
94
94
  const { exec, calls } = fakeExec({ listStdout: "acme (qmd://acme/)\n" });
95
- const r = await reindexAfterSync("/hq", {
95
+ const r = reindexAfterSync("/hq", {
96
96
  exec,
97
97
  acquireReindexLock: noLock,
98
98
  existsSync: () => true,
@@ -109,7 +109,7 @@ describe("reindexAfterSync", () => {
109
109
  ["update"],
110
110
  ]);
111
111
  });
112
- it("detects path drift without repairing when HQ_QMD_REPAIR_PATH_DRIFT is off", async () => {
112
+ it("detects path drift without repairing when HQ_QMD_REPAIR_PATH_DRIFT is off", () => {
113
113
  const drifted = path.join("/mirror", "knowledge-acme");
114
114
  const { exec, calls } = fakeExec({
115
115
  listStdout: "acme (qmd://acme/)\n",
@@ -120,7 +120,7 @@ describe("reindexAfterSync", () => {
120
120
  }
121
121
  : undefined,
122
122
  });
123
- const r = await reindexAfterSync("/hq", {
123
+ const r = reindexAfterSync("/hq", {
124
124
  exec,
125
125
  acquireReindexLock: noLock,
126
126
  existsSync: () => true,
@@ -136,7 +136,7 @@ describe("reindexAfterSync", () => {
136
136
  // Detect-only must still allow embed (no repair this cycle).
137
137
  expect(r.embedded).toBe(true);
138
138
  });
139
- it("repairs path drift when HQ_QMD_REPAIR_PATH_DRIFT=1 and skips embed", async () => {
139
+ it("repairs path drift when HQ_QMD_REPAIR_PATH_DRIFT=1 and skips embed", () => {
140
140
  const drifted = path.join("/mirror", "knowledge-acme");
141
141
  const desired = path.join("/hq", "companies", "acme", "knowledge");
142
142
  const { exec, calls } = fakeExec({
@@ -145,7 +145,7 @@ describe("reindexAfterSync", () => {
145
145
  ? { status: 0, stdout: `Collection: acme\n Path: ${drifted}\n` }
146
146
  : undefined,
147
147
  });
148
- const r = await reindexAfterSync("/hq", {
148
+ const r = reindexAfterSync("/hq", {
149
149
  exec,
150
150
  acquireReindexLock: noLock,
151
151
  existsSync: () => true,
@@ -168,7 +168,7 @@ describe("reindexAfterSync", () => {
168
168
  ["update"],
169
169
  ]);
170
170
  });
171
- it("does not remove a drifted collection when desired path has no indexable markdown", async () => {
171
+ it("does not remove a drifted collection when desired path has no indexable markdown", () => {
172
172
  const drifted = path.join("/mirror", "knowledge-acme");
173
173
  const { exec, calls } = fakeExec({
174
174
  listStdout: "acme (qmd://acme/)\n",
@@ -177,7 +177,7 @@ describe("reindexAfterSync", () => {
177
177
  : undefined,
178
178
  });
179
179
  // hasIndexableMarkdown false → skipped before show/repair (desired empty).
180
- const r = await reindexAfterSync("/hq", {
180
+ const r = reindexAfterSync("/hq", {
181
181
  exec,
182
182
  acquireReindexLock: noLock,
183
183
  existsSync: () => true,
@@ -189,9 +189,9 @@ describe("reindexAfterSync", () => {
189
189
  expect(r.collectionsRepaired).toEqual([]);
190
190
  expect(calls.some((c) => c[1] === "remove")).toBe(false);
191
191
  });
192
- it("skips company dirs with no indexable markdown", async () => {
192
+ it("skips company dirs with no indexable markdown", () => {
193
193
  const { exec, calls } = fakeExec({ listStdout: "" });
194
- const r = await reindexAfterSync("/hq", {
194
+ const r = reindexAfterSync("/hq", {
195
195
  exec,
196
196
  acquireReindexLock: noLock,
197
197
  existsSync: () => true,
@@ -201,9 +201,9 @@ describe("reindexAfterSync", () => {
201
201
  expect(r.collectionsAdded).toEqual([]);
202
202
  expect(calls).toEqual([["status"], ["collection", "list"], ["update"]]);
203
203
  });
204
- it("registers populated company projects with the shell name and mask", async () => {
204
+ it("registers populated company projects with the shell name and mask", () => {
205
205
  const { exec, calls } = fakeExec({ listStdout: "" });
206
- const r = await reindexAfterSync("/hq", {
206
+ const r = reindexAfterSync("/hq", {
207
207
  exec,
208
208
  acquireReindexLock: noLock,
209
209
  existsSync: () => true,
@@ -228,10 +228,10 @@ describe("reindexAfterSync", () => {
228
228
  ["update"],
229
229
  ]);
230
230
  });
231
- it("skips missing and empty company projects dirs", async () => {
231
+ it("skips missing and empty company projects dirs", () => {
232
232
  const { exec, calls } = fakeExec({ listStdout: "" });
233
233
  const emptyProjects = path.join("/hq", "companies", "empty", "projects");
234
- const r = await reindexAfterSync("/hq", {
234
+ const r = reindexAfterSync("/hq", {
235
235
  exec,
236
236
  acquireReindexLock: noLock,
237
237
  existsSync: (p) => p !== path.join("/hq", "companies", "missing", "projects"),
@@ -242,10 +242,10 @@ describe("reindexAfterSync", () => {
242
242
  expect(r.collectionsAdded).toEqual([]);
243
243
  expect(calls).toEqual([["status"], ["collection", "list"], ["update"]]);
244
244
  });
245
- it("registers populated personal knowledge with the shell name and mask", async () => {
245
+ it("registers populated personal knowledge with the shell name and mask", () => {
246
246
  const { exec, calls } = fakeExec({ listStdout: "" });
247
247
  const personalKnowledge = path.join("/hq", "personal", "knowledge");
248
- const r = await reindexAfterSync("/hq", {
248
+ const r = reindexAfterSync("/hq", {
249
249
  exec,
250
250
  acquireReindexLock: noLock,
251
251
  existsSync: () => true,
@@ -269,9 +269,9 @@ describe("reindexAfterSync", () => {
269
269
  ["update"],
270
270
  ]);
271
271
  });
272
- it("skips absent and empty personal knowledge", async () => {
272
+ it("skips absent and empty personal knowledge", () => {
273
273
  const absent = fakeExec({ listStdout: "" });
274
- const absentResult = await reindexAfterSync("/hq", {
274
+ const absentResult = reindexAfterSync("/hq", {
275
275
  exec: absent.exec,
276
276
  acquireReindexLock: noLock,
277
277
  existsSync: (p) => p !== path.join("/hq", "personal", "knowledge"),
@@ -280,7 +280,7 @@ describe("reindexAfterSync", () => {
280
280
  expect(absentResult.collectionsAdded).toEqual([]);
281
281
  expect(absent.calls).toEqual([["status"], ["collection", "list"], ["update"]]);
282
282
  const empty = fakeExec({ listStdout: "" });
283
- const emptyResult = await reindexAfterSync("/hq", {
283
+ const emptyResult = reindexAfterSync("/hq", {
284
284
  exec: empty.exec,
285
285
  acquireReindexLock: noLock,
286
286
  existsSync: () => true,
@@ -290,7 +290,7 @@ describe("reindexAfterSync", () => {
290
290
  expect(emptyResult.collectionsAdded).toEqual([]);
291
291
  expect(empty.calls).toEqual([["status"], ["collection", "list"], ["update"]]);
292
292
  });
293
- it("does not add projects or personal knowledge a second time", async () => {
293
+ it("does not add projects or personal knowledge a second time", () => {
294
294
  let listCount = 0;
295
295
  const { exec, calls } = fakeExec({
296
296
  resultFor: (args) => {
@@ -314,15 +314,15 @@ describe("reindexAfterSync", () => {
314
314
  hasIndexableProjectContent: () => true,
315
315
  realpathSync: identityRealpath,
316
316
  };
317
- expect((await reindexAfterSync("/hq", options)).collectionsAdded).toEqual([
317
+ expect(reindexAfterSync("/hq", options).collectionsAdded).toEqual([
318
318
  "acme",
319
319
  "acme-projects",
320
320
  "personal-knowledge",
321
321
  ]);
322
- expect((await reindexAfterSync("/hq", options)).collectionsAdded).toEqual([]);
322
+ expect(reindexAfterSync("/hq", options).collectionsAdded).toEqual([]);
323
323
  expect(calls.filter((c) => c[0] === "collection" && c[1] === "add")).toHaveLength(3);
324
324
  });
325
- it("emits the same collection-add commands as the shell for a fixture tree", async () => {
325
+ it("emits the same collection-add commands as the shell for a fixture tree", () => {
326
326
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-qmd-parity-"));
327
327
  try {
328
328
  const hq = path.join(dir, "hq");
@@ -340,7 +340,7 @@ describe("reindexAfterSync", () => {
340
340
  fs.writeFileSync(path.join(acmeProjects, "prd.json"), "{}\n");
341
341
  fs.writeFileSync(path.join(personalKnowledge, "note.md"), "# personal\n");
342
342
  const { exec, calls } = fakeExec({ listStdout: "" });
343
- await reindexAfterSync(hq, { exec, acquireReindexLock: noLock });
343
+ reindexAfterSync(hq, { exec, acquireReindexLock: noLock });
344
344
  expect(calls.filter((c) => c[0] === "collection" && c[1] === "add")).toEqual([
345
345
  ["collection", "add", acmeKnowledge, "--name", "acme", "--mask", "**/*.md"],
346
346
  [
@@ -367,9 +367,9 @@ describe("reindexAfterSync", () => {
367
367
  fs.rmSync(dir, { recursive: true, force: true });
368
368
  }
369
369
  });
370
- it("skips qmd entirely when provided changed paths do not touch knowledge markdown", async () => {
370
+ it("skips qmd entirely when provided changed paths do not touch knowledge markdown", () => {
371
371
  const { exec, calls } = fakeExec({ listStdout: "" });
372
- const r = await reindexAfterSync("/hq", {
372
+ const r = reindexAfterSync("/hq", {
373
373
  exec,
374
374
  acquireReindexLock: noLock,
375
375
  existsSync: () => true,
@@ -379,12 +379,12 @@ describe("reindexAfterSync", () => {
379
379
  expect(r.updated).toBe(false);
380
380
  expect(calls).toEqual([]);
381
381
  });
382
- it("debounces dirty updates with a pending marker and flushes the final update later", async () => {
382
+ it("debounces dirty updates with a pending marker and flushes the final update later", () => {
383
383
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-qmd-debounce-"));
384
384
  try {
385
385
  const statePath = path.join(dir, "qmd-state.json");
386
386
  const { exec, calls } = fakeExec({ listStdout: "acme (qmd://acme/)\n" });
387
- const first = await reindexAfterSync("/hq", {
387
+ const first = reindexAfterSync("/hq", {
388
388
  exec,
389
389
  acquireReindexLock: noLock,
390
390
  existsSync: () => true,
@@ -403,7 +403,7 @@ describe("reindexAfterSync", () => {
403
403
  ["collection", "list"],
404
404
  ["collection", "show", "acme"],
405
405
  ]);
406
- const second = await reindexAfterSync("/hq", {
406
+ const second = reindexAfterSync("/hq", {
407
407
  exec,
408
408
  acquireReindexLock: noLock,
409
409
  existsSync: () => true,
@@ -427,9 +427,9 @@ describe("reindexAfterSync", () => {
427
427
  fs.rmSync(dir, { recursive: true, force: true });
428
428
  }
429
429
  });
430
- it("runs embed only when embed:true", async () => {
430
+ it("runs embed only when embed:true", () => {
431
431
  const { exec, calls } = fakeExec({ listStdout: "" });
432
- const r = await reindexAfterSync("/hq", {
432
+ const r = reindexAfterSync("/hq", {
433
433
  exec,
434
434
  acquireReindexLock: noLock,
435
435
  existsSync: () => true,
@@ -439,13 +439,13 @@ describe("reindexAfterSync", () => {
439
439
  expect(r.embedded).toBe(true);
440
440
  expect(calls).toEqual([["status"], ["collection", "list"], ["update"], ["embed"]]);
441
441
  });
442
- it("never throws even if exec misbehaves", async () => {
443
- const exec = async () => {
442
+ it("never throws even if exec misbehaves", () => {
443
+ const exec = () => {
444
444
  throw new Error("boom");
445
445
  };
446
- await expect(reindexAfterSync("/hq", { exec, acquireReindexLock: noLock, existsSync: () => true })).resolves.toMatchObject({ qmdAvailable: false });
446
+ expect(() => reindexAfterSync("/hq", { exec, acquireReindexLock: noLock, existsSync: () => true })).not.toThrow();
447
447
  });
448
- it("hasIndexableMarkdown finds a real .md and ignores INDEX.md", async () => {
448
+ it("hasIndexableMarkdown finds a real .md and ignores INDEX.md", () => {
449
449
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-qmd-test-"));
450
450
  try {
451
451
  const hq = path.join(dir, "hq");
@@ -456,7 +456,7 @@ describe("reindexAfterSync", () => {
456
456
  fs.writeFileSync(path.join(kdir, "INDEX.md"), "# index\n");
457
457
  fs.writeFileSync(path.join(kdir, "note.md"), "# real\n");
458
458
  const { exec, calls } = fakeExec({ listStdout: "" });
459
- const r = await reindexAfterSync(hq, { exec, acquireReindexLock: noLock }); // real fs walk
459
+ const r = reindexAfterSync(hq, { exec, acquireReindexLock: noLock }); // real fs walk
460
460
  expect(r.collectionsAdded).toEqual(["acme"]);
461
461
  expect(calls.some((c) => c[0] === "collection" && c[1] === "add")).toBe(true);
462
462
  }
@@ -465,12 +465,12 @@ describe("reindexAfterSync", () => {
465
465
  }
466
466
  });
467
467
  // ── Corruption safety: lock serialization (feedback_b9a369ff) ──────────────
468
- it("skips the cycle after status when another writer holds the lock", async () => {
468
+ it("skips the cycle after status when another writer holds the lock", () => {
469
469
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-qmd-lockbusy-"));
470
470
  try {
471
471
  const statePath = path.join(dir, "qmd-state.json");
472
472
  const { exec, calls } = fakeExec({ listStdout: "" });
473
- const r = await reindexAfterSync("/hq", {
473
+ const r = reindexAfterSync("/hq", {
474
474
  exec,
475
475
  existsSync: () => true,
476
476
  statePath,
@@ -489,14 +489,14 @@ describe("reindexAfterSync", () => {
489
489
  fs.rmSync(dir, { recursive: true, force: true });
490
490
  }
491
491
  });
492
- it("always releases the lock, even when a qmd command throws after status", async () => {
492
+ it("always releases the lock, even when a qmd command throws after status", () => {
493
493
  let released = false;
494
- const exec = async (args) => {
494
+ const exec = (args) => {
495
495
  if (args[0] === "status")
496
496
  return { status: 0, stdout: "" };
497
497
  throw new Error("boom");
498
498
  };
499
- await reindexAfterSync("/hq", {
499
+ reindexAfterSync("/hq", {
500
500
  exec,
501
501
  existsSync: () => true,
502
502
  acquireReindexLock: () => ({
@@ -508,7 +508,7 @@ describe("reindexAfterSync", () => {
508
508
  expect(released).toBe(true);
509
509
  });
510
510
  // ── Corruption safety: timeout-kill handling ───────────────────────────────
511
- it("treats a timed-out update as not-done, marks dirty, and does NOT embed", async () => {
511
+ it("treats a timed-out update as not-done, marks dirty, and does NOT embed", () => {
512
512
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-qmd-timeout-"));
513
513
  try {
514
514
  const statePath = path.join(dir, "qmd-state.json");
@@ -516,7 +516,7 @@ describe("reindexAfterSync", () => {
516
516
  listStdout: "",
517
517
  resultFor: (args) => args[0] === "update" ? { status: null, timedOut: true } : undefined,
518
518
  });
519
- const r = await reindexAfterSync("/hq", {
519
+ const r = reindexAfterSync("/hq", {
520
520
  exec,
521
521
  acquireReindexLock: noLock,
522
522
  existsSync: () => true,
@@ -536,12 +536,12 @@ describe("reindexAfterSync", () => {
536
536
  fs.rmSync(dir, { recursive: true, force: true });
537
537
  }
538
538
  });
539
- it("treats a timed-out embed as not-embedded and marks dirty", async () => {
539
+ it("treats a timed-out embed as not-embedded and marks dirty", () => {
540
540
  const { exec } = fakeExec({
541
541
  listStdout: "",
542
542
  resultFor: (args) => args[0] === "embed" ? { status: null, timedOut: true } : undefined,
543
543
  });
544
- const r = await reindexAfterSync("/hq", {
544
+ const r = reindexAfterSync("/hq", {
545
545
  exec,
546
546
  acquireReindexLock: noLock,
547
547
  existsSync: () => true,
@@ -554,7 +554,7 @@ describe("reindexAfterSync", () => {
554
554
  expect(r.pendingDirty).toBe(true);
555
555
  });
556
556
  // ── Corruption safety: SQLITE_CORRUPT detection + quarantine ────────────────
557
- it("quarantines the resolved index dir when update reports SQLITE_CORRUPT", async () => {
557
+ it("quarantines the resolved index dir when update reports SQLITE_CORRUPT", () => {
558
558
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-qmd-corrupt-"));
559
559
  try {
560
560
  const statePath = path.join(dir, "qmd-state.json");
@@ -571,7 +571,7 @@ describe("reindexAfterSync", () => {
571
571
  ? { status: 1, stderr: "SqliteError: database disk image is malformed (SQLITE_CORRUPT)" }
572
572
  : undefined,
573
573
  });
574
- const r = await reindexAfterSync("/hq", {
574
+ const r = reindexAfterSync("/hq", {
575
575
  exec,
576
576
  acquireReindexLock: noLock,
577
577
  existsSync: () => true,
@@ -593,14 +593,14 @@ describe("reindexAfterSync", () => {
593
593
  fs.rmSync(dir, { recursive: true, force: true });
594
594
  }
595
595
  });
596
- it("reports quarantine failure when corruption is detected but nothing moves", async () => {
596
+ it("reports quarantine failure when corruption is detected but nothing moves", () => {
597
597
  const quarantined = [];
598
598
  const { exec, calls } = fakeExec({
599
599
  resultFor: (args) => args[0] === "collection" && args[1] === "list"
600
600
  ? { status: 0, stdout: "database disk image is malformed" }
601
601
  : undefined,
602
602
  });
603
- const r = await reindexAfterSync("/hq", {
603
+ const r = reindexAfterSync("/hq", {
604
604
  exec,
605
605
  acquireReindexLock: noLock,
606
606
  existsSync: () => true,
@@ -615,7 +615,7 @@ describe("reindexAfterSync", () => {
615
615
  expect(calls).toEqual([["status"], ["collection", "list"]]);
616
616
  expect(quarantined).toEqual([path.join("/hq", ".qmd")]);
617
617
  });
618
- it("quarantines when embed reports SQLITE_CORRUPT", async () => {
618
+ it("quarantines when embed reports SQLITE_CORRUPT", () => {
619
619
  const quarantined = [];
620
620
  const { exec, calls } = fakeExec({
621
621
  listStdout: "",
@@ -623,7 +623,7 @@ describe("reindexAfterSync", () => {
623
623
  ? { status: 1, stderr: "code SQLITE_CORRUPT at store.js:1546" }
624
624
  : undefined,
625
625
  });
626
- const r = await reindexAfterSync("/hq", {
626
+ const r = reindexAfterSync("/hq", {
627
627
  exec,
628
628
  acquireReindexLock: noLock,
629
629
  existsSync: () => true,
@@ -642,7 +642,7 @@ describe("reindexAfterSync", () => {
642
642
  });
643
643
  // ── The default reindex lock (real filesystem) ───────────────────────────────
644
644
  describe("default reindex lock", () => {
645
- it("holds the lock for the duration of the cycle and releases it after", async () => {
645
+ it("holds the lock for the duration of the cycle and releases it after", () => {
646
646
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-qmd-lock-"));
647
647
  try {
648
648
  const hq = path.join(dir, "hq");
@@ -654,7 +654,7 @@ describe("default reindex lock", () => {
654
654
  const lockPath = path.join(hq, ".qmd", ".reindex.lock");
655
655
  let reAcquireBlockedWhileHeld = false;
656
656
  const { exec } = fakeExec({ listStdout: "" });
657
- const observingExec = async (args) => {
657
+ const observingExec = (args) => {
658
658
  if (args[0] === "update") {
659
659
  try {
660
660
  fs.closeSync(fs.openSync(lockPath, "wx"));
@@ -665,9 +665,9 @@ describe("default reindex lock", () => {
665
665
  err.code === "EEXIST";
666
666
  }
667
667
  }
668
- return await exec(args);
668
+ return exec(args);
669
669
  };
670
- const r = await reindexAfterSync(hq, { exec: observingExec });
670
+ const r = reindexAfterSync(hq, { exec: observingExec });
671
671
  expect(r.updated).toBe(true);
672
672
  expect(reAcquireBlockedWhileHeld).toBe(true);
673
673
  expect(fs.existsSync(lockPath)).toBe(false);
@@ -676,7 +676,7 @@ describe("default reindex lock", () => {
676
676
  fs.rmSync(dir, { recursive: true, force: true });
677
677
  }
678
678
  });
679
- it("reclaims a lock held by a dead PID (stale takeover)", async () => {
679
+ it("reclaims a lock held by a dead PID (stale takeover)", () => {
680
680
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-qmd-lock-stale-"));
681
681
  try {
682
682
  const hq = path.join(dir, "hq");
@@ -689,7 +689,7 @@ describe("default reindex lock", () => {
689
689
  fs.mkdirSync(path.dirname(lockPath), { recursive: true });
690
690
  fs.writeFileSync(lockPath, JSON.stringify({ pid: 2147483646, startedAt: "2000-01-01T00:00:00.000Z", command: "dead" }));
691
691
  const { exec, calls } = fakeExec({ listStdout: "" });
692
- const r = await reindexAfterSync(hq, { exec });
692
+ const r = reindexAfterSync(hq, { exec });
693
693
  expect(r.updated).toBe(true);
694
694
  expect(r.lockBusy).toBe(false);
695
695
  expect(calls.some((c) => c[0] === "update")).toBe(true);
@@ -700,7 +700,7 @@ describe("default reindex lock", () => {
700
700
  });
701
701
  });
702
702
  describe("acquireDualReindexLocks", () => {
703
- it("dedupes identical paths so a process cannot lock itself out", async () => {
703
+ it("dedupes identical paths so a process cannot lock itself out", () => {
704
704
  let acquires = 0;
705
705
  const acquire = () => {
706
706
  acquires++;
@@ -712,7 +712,7 @@ describe("acquireDualReindexLocks", () => {
712
712
  expect(locks).toHaveLength(1);
713
713
  expect(acquires).toBe(1);
714
714
  });
715
- it("releases the first lock when the second is busy", async () => {
715
+ it("releases the first lock when the second is busy", () => {
716
716
  const released = [];
717
717
  let n = 0;
718
718
  const acquire = (lockPath) => {
@@ -736,79 +736,46 @@ describe("acquireDualReindexLocks", () => {
736
736
  });
737
737
  // ── Pure helpers ─────────────────────────────────────────────────────────────
738
738
  describe("resolveExecTimeoutMs", () => {
739
- it("defaults to a generous bound, not the old 120s that killed writes", async () => {
739
+ it("defaults to a generous bound, not the old 120s that killed writes", () => {
740
740
  expect(resolveExecTimeoutMs({})).toBe(DEFAULT_QMD_EXEC_TIMEOUT_MS);
741
741
  expect(DEFAULT_QMD_EXEC_TIMEOUT_MS).toBeGreaterThan(120_000);
742
742
  });
743
- it("honors a valid HQ_QMD_EXEC_TIMEOUT_MS override", async () => {
743
+ it("honors a valid HQ_QMD_EXEC_TIMEOUT_MS override", () => {
744
744
  expect(resolveExecTimeoutMs({ HQ_QMD_EXEC_TIMEOUT_MS: "300000" })).toBe(300_000);
745
745
  });
746
- it("ignores an invalid override", async () => {
746
+ it("ignores an invalid override", () => {
747
747
  expect(resolveExecTimeoutMs({ HQ_QMD_EXEC_TIMEOUT_MS: "nope" })).toBe(DEFAULT_QMD_EXEC_TIMEOUT_MS);
748
748
  expect(resolveExecTimeoutMs({ HQ_QMD_EXEC_TIMEOUT_MS: "-5" })).toBe(DEFAULT_QMD_EXEC_TIMEOUT_MS);
749
749
  expect(resolveExecTimeoutMs({ HQ_QMD_EXEC_TIMEOUT_MS: "0" })).toBe(DEFAULT_QMD_EXEC_TIMEOUT_MS);
750
750
  });
751
751
  });
752
- describe("createQmdExec", () => {
753
- it("captures output and sends SIGINT when the configured timeout expires", async () => {
754
- const exec = createQmdExec({
755
- command: process.execPath,
756
- timeoutMs: 250,
757
- sigintGraceMs: 100,
758
- });
759
- const result = await exec([
760
- "-e",
761
- "process.on('SIGINT', () => { process.stdout.write('sigint'); process.exit(0); }); setInterval(() => {}, 1000);",
762
- ]);
763
- expect(result).toMatchObject({ status: 0, timedOut: true });
764
- expect(result.stdout).toContain("sigint");
765
- });
766
- it("sends SIGINT to an in-flight child on shutdown and escalates after the grace period", async () => {
767
- const controller = new AbortController();
768
- const exec = createQmdExec({
769
- command: process.execPath,
770
- timeoutMs: 10_000,
771
- sigintGraceMs: 50,
772
- });
773
- const resultPromise = exec([
774
- "-e",
775
- "process.on('SIGINT', () => process.stdout.write('sigint')); setInterval(() => {}, 1000);",
776
- ], { signal: controller.signal });
777
- const abortTimer = setTimeout(() => controller.abort(), 250);
778
- const result = await resultPromise;
779
- clearTimeout(abortTimer);
780
- expect(result.timedOut).toBe(false);
781
- expect(result.stdout).toContain("sigint");
782
- expect(result.status).toBeNull();
783
- });
784
- });
785
752
  describe("looksCorrupt", () => {
786
- it("matches the SQLITE_CORRUPT signature on stdout or stderr", async () => {
753
+ it("matches the SQLITE_CORRUPT signature on stdout or stderr", () => {
787
754
  expect(looksCorrupt({ stderr: "SqliteError: ... code SQLITE_CORRUPT" })).toBe(true);
788
755
  expect(looksCorrupt({ stdout: "database disk image is malformed" })).toBe(true);
789
756
  expect(looksCorrupt({ stdout: "Database disk image IS MALFORMED" })).toBe(true);
790
757
  });
791
- it("does not match clean output", async () => {
758
+ it("does not match clean output", () => {
792
759
  expect(looksCorrupt({ stdout: "Updated 688 documents", stderr: "" })).toBe(false);
793
760
  expect(looksCorrupt({})).toBe(false);
794
761
  });
795
762
  });
796
763
  describe("parseIndexDirFromStatus / parseCollectionPathFromShow", () => {
797
- it("parses Index file path into its directory", async () => {
764
+ it("parses Index file path into its directory", () => {
798
765
  expect(parseIndexDirFromStatus("Index: C:/Users/me/.cache/qmd/index.sqlite\nSize: 1.3 GB\n")).toBe(path.dirname("C:/Users/me/.cache/qmd/index.sqlite"));
799
766
  });
800
- it("returns null when Index line is missing", async () => {
767
+ it("returns null when Index line is missing", () => {
801
768
  expect(parseIndexDirFromStatus("Collections: 2\n")).toBeNull();
802
769
  });
803
- it("parses Path from collection show", async () => {
770
+ it("parses Path from collection show", () => {
804
771
  expect(parseCollectionPathFromShow("Collection: indigo\n Path: /hq/companies/indigo/knowledge\n")).toBe("/hq/companies/indigo/knowledge");
805
772
  });
806
773
  });
807
774
  describe("pathsEquivalent / repairPathDriftEnabled", () => {
808
- it("treats case and trailing separators as equal", async () => {
775
+ it("treats case and trailing separators as equal", () => {
809
776
  expect(pathsEquivalent("/HQ/Companies/Acme/knowledge/", "/hq/companies/acme/knowledge", (p) => p)).toBe(true);
810
777
  });
811
- it("enables repair only for 1/true/yes", async () => {
778
+ it("enables repair only for 1/true/yes", () => {
812
779
  expect(repairPathDriftEnabled({})).toBe(false);
813
780
  expect(repairPathDriftEnabled({ HQ_QMD_REPAIR_PATH_DRIFT: "1" })).toBe(true);
814
781
  expect(repairPathDriftEnabled({ HQ_QMD_REPAIR_PATH_DRIFT: "true" })).toBe(true);