@indigoai-us/hq-cli 5.345.97 → 5.345.98

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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.98] — 2026-10-12
6
+ - Session-start health no longer files a leftover personal-files journal plus
7
+ an ordinary HQ Core upgrade as failed doctor repairs. `hq doctor --fix`
8
+ still refreshes personal files; the upgrade warning stays in `hq doctor`.
9
+
5
10
  ## [5.345.97] — 2026-10-12
6
11
  - Session-start health no longer files a leftover company journal plus an
7
12
  ordinary HQ Core upgrade as failed doctor repairs. `hq doctor --fix` still
@@ -392,26 +392,75 @@ function journalAndManifestResults(deps) {
392
392
  }
393
393
  return [...journalResults(deps, journals), ...manifestResults(deps, journals)];
394
394
  }
395
+ function leftoverPersonalJournalResult(entry) {
396
+ return {
397
+ status: "NA",
398
+ checkId: `sync.journal.${journalSlugForCheckId(PERSONAL_SCOPE_SLUG)}`,
399
+ reasonCode: "legacy-personal-journal",
400
+ target: entry.path,
401
+ message: "Sync journal 'personal' is unused on this machine — personal files now sync through the personal vault journal, so this leftover shard is not a live sync target.",
402
+ };
403
+ }
404
+ function journalFreshnessResult(entry, slug, nowMs) {
405
+ const lastSync = entry.journal?.lastSync;
406
+ const checkId = `sync.journal.${journalSlugForCheckId(slug)}`;
407
+ if (typeof lastSync !== "string" || lastSync.length === 0) {
408
+ return {
409
+ status: "WARN",
410
+ checkId,
411
+ reasonCode: "never-synced",
412
+ target: entry.path,
413
+ message: `Sync journal '${slug}' exists but has never recorded a successful sync.`,
414
+ remediation: "Run /hq-sync (or `hq sync`) to complete a first sync.",
415
+ };
416
+ }
417
+ const parsed = Date.parse(lastSync);
418
+ if (!Number.isFinite(parsed)) {
419
+ return {
420
+ status: "UNKNOWN",
421
+ checkId,
422
+ reasonCode: "invalid-last-sync",
423
+ target: entry.path,
424
+ message: `Sync journal '${slug}' has an unparseable lastSync (${lastSync}).`,
425
+ };
426
+ }
427
+ const ageMs = nowMs - parsed;
428
+ if (ageMs > STALE_JOURNAL_THRESHOLD_MS) {
429
+ const days = Math.floor(ageMs / 86_400_000);
430
+ return {
431
+ status: "WARN",
432
+ checkId,
433
+ reasonCode: "stale-threshold",
434
+ target: entry.path,
435
+ message: `Sync journal '${slug}' is stale: last successful sync ${days} day${days === 1 ? "" : "s"} ago (${lastSync}).`,
436
+ remediation: "Run /hq-sync (or `hq sync`) and check the sync runner.",
437
+ };
438
+ }
439
+ return {
440
+ status: "PASS",
441
+ checkId,
442
+ target: entry.path,
443
+ message: `Sync journal '${slug}' is fresh (last sync ${lastSync}).`,
444
+ };
445
+ }
395
446
  function journalResults(deps, journals) {
396
447
  const nowMs = deps.now().getTime();
397
448
  const hasPersonalVaultJournal = journals.some((entry) => entry.slug === PERSONAL_VAULT_SCOPE_SLUG);
398
- return journals.map((entry) => {
399
- const lastSync = entry.journal?.lastSync;
400
- const checkId = `sync.journal.${journalSlugForCheckId(entry.slug)}`;
449
+ const results = [];
450
+ for (const entry of journals) {
401
451
  // The legacy `personal` shard and `__hq_personal_vault__` name the same
402
- // tree. Once the vault journal exists, the old file is leftover
403
- // bookkeeping: WARN would make `hq doctor --fix` look like it failed to
404
- // repair a row it cannot refresh (a personal pull writes the vault
405
- // journal, not this one).
406
- if (entry.slug === PERSONAL_SCOPE_SLUG && hasPersonalVaultJournal) {
407
- return {
408
- status: "NA",
409
- checkId,
410
- reasonCode: "legacy-personal-journal",
411
- target: entry.path,
412
- message: "Sync journal 'personal' is unused on this machine — personal files now sync through the personal vault journal, so this leftover shard is not a live sync target.",
413
- };
452
+ // tree. A personal pull writes the vault journal, not this leftover file,
453
+ // so WARN on `sync.journal.personal` makes `hq doctor --fix` look like it
454
+ // failed to repair a row it cannot refresh — including when the vault
455
+ // journal has not been created yet.
456
+ if (entry.slug === PERSONAL_SCOPE_SLUG) {
457
+ results.push(leftoverPersonalJournalResult(entry));
458
+ if (!hasPersonalVaultJournal) {
459
+ results.push(journalFreshnessResult(entry, PERSONAL_VAULT_SCOPE_SLUG, nowMs));
460
+ }
461
+ continue;
414
462
  }
463
+ const checkId = `sync.journal.${journalSlugForCheckId(entry.slug)}`;
415
464
  // A company journal without a local tree is leftover membership state,
416
465
  // not a live sync target. WARN would make `hq doctor --fix` look like it
417
466
  // failed to repair something it was never going to touch (US-015 health
@@ -427,54 +476,19 @@ function journalResults(deps, journals) {
427
476
  local = true;
428
477
  }
429
478
  if (!local) {
430
- return {
479
+ results.push({
431
480
  status: "NA",
432
481
  checkId,
433
482
  reasonCode: "no-local-tree",
434
483
  target: entry.path,
435
484
  message: `Sync journal '${entry.slug}' is unused on this machine — there is no companies/${entry.slug} directory, so this journal is not a live sync target.`,
436
- };
485
+ });
486
+ continue;
437
487
  }
438
488
  }
439
- if (typeof lastSync !== "string" || lastSync.length === 0) {
440
- return {
441
- status: "WARN",
442
- checkId,
443
- reasonCode: "never-synced",
444
- target: entry.path,
445
- message: `Sync journal '${entry.slug}' exists but has never recorded a successful sync.`,
446
- remediation: "Run /hq-sync (or `hq sync`) to complete a first sync.",
447
- };
448
- }
449
- const parsed = Date.parse(lastSync);
450
- if (!Number.isFinite(parsed)) {
451
- return {
452
- status: "UNKNOWN",
453
- checkId,
454
- reasonCode: "invalid-last-sync",
455
- target: entry.path,
456
- message: `Sync journal '${entry.slug}' has an unparseable lastSync (${lastSync}).`,
457
- };
458
- }
459
- const ageMs = nowMs - parsed;
460
- if (ageMs > STALE_JOURNAL_THRESHOLD_MS) {
461
- const days = Math.floor(ageMs / 86_400_000);
462
- return {
463
- status: "WARN",
464
- checkId,
465
- reasonCode: "stale-threshold",
466
- target: entry.path,
467
- message: `Sync journal '${entry.slug}' is stale: last successful sync ${days} day${days === 1 ? "" : "s"} ago (${lastSync}).`,
468
- remediation: "Run /hq-sync (or `hq sync`) and check the sync runner.",
469
- };
470
- }
471
- return {
472
- status: "PASS",
473
- checkId,
474
- target: entry.path,
475
- message: `Sync journal '${entry.slug}' is fresh (last sync ${lastSync}).`,
476
- };
477
- });
489
+ results.push(journalFreshnessResult(entry, entry.slug, nowMs));
490
+ }
491
+ return results;
478
492
  }
479
493
  // ─── Manifest upload freshness (sync-reconciliation-audit US-004) ────────────
480
494
  /**
@@ -130,8 +130,9 @@ export function deriveRemediation(result) {
130
130
  };
131
131
  }
132
132
  // refresh-sync — a company journal that is stale or never-synced, or a
133
- // live personal / personal-vault journal. Leftover shards are NA at the
134
- // check layer, so they never reach this classifier. A SessionStart
133
+ // live personal / personal-vault journal. Leftover `personal` shards are
134
+ // NA at the check layer (including when the vault journal is not on disk
135
+ // yet), so they never reach this classifier. A SessionStart
135
136
  // `--fix --yes` pull is keep-conflicts and pull-only, matching the
136
137
  // company repair: leaving personal journals WARN made the health hook
137
138
  // report an unrepaired failure for a row doctor refused to touch.
@@ -106,11 +106,13 @@ export interface BuildDoctorJsonInput {
106
106
  clientHealthCheckStatusEnabledCheckIds?: ReadonlySet<string>;
107
107
  /**
108
108
  * JSON consumed by the SessionStart health hook (`--client-health-report`).
109
- * Ordinary hq-core upgrade WARNs stay in human `hq doctor` output and in
110
- * default `--json`, but they are omitted here: `--fix` cannot apply a
111
- * release, and older hooks treated the leftover WARN as an unrepaired
112
- * failure. Company journals this `--fix` pass did not try to refresh are
113
- * omitted the same way: the repair cap leaves them for a later session.
109
+ * Ordinary hq-core upgrade WARNs are omitted from every JSON document
110
+ * (`--json` and `--client-health-report`): `--fix` cannot apply a release,
111
+ * and both current hooks and older ones that only call `hq doctor --json`
112
+ * treated the leftover WARN as an unrepaired failure. Human `hq doctor`
113
+ * still prints the upgrade. Company journals this `--fix` pass did not try
114
+ * to refresh are omitted only from `--client-health-report`: the repair cap
115
+ * leaves them for a later session.
114
116
  */
115
117
  clientHealthReport?: boolean;
116
118
  /** Clock for the leftover-journal freshness window. Default: `Date.now`. */
@@ -119,6 +121,8 @@ export interface BuildDoctorJsonInput {
119
121
  /**
120
122
  * True when a sync finding is an ordinary, non-repairable hq-core upgrade.
121
123
  * FAIL stays visible: that is a broken local core, not "a newer release exists".
124
+ * JSON omits the WARN so SessionStart health (including core 15.x hooks that
125
+ * only call `hq doctor --json`) does not file it as a failed repair.
122
126
  */
123
127
  export declare function isOrdinaryCoreUpdateWarn(result: {
124
128
  checkId: string;
@@ -33,6 +33,8 @@ export const DOCTOR_JSON_SCHEMA_VERSION = 2;
33
33
  /**
34
34
  * True when a sync finding is an ordinary, non-repairable hq-core upgrade.
35
35
  * FAIL stays visible: that is a broken local core, not "a newer release exists".
36
+ * JSON omits the WARN so SessionStart health (including core 15.x hooks that
37
+ * only call `hq doctor --json`) does not file it as a failed repair.
36
38
  */
37
39
  export function isOrdinaryCoreUpdateWarn(result) {
38
40
  return result.checkId === "sync.update.core" && result.status === "WARN";
@@ -45,7 +47,7 @@ export function buildDoctorJson(input) {
45
47
  const results = [];
46
48
  for (const run of input.families) {
47
49
  for (const result of run.results) {
48
- if (input.clientHealthReport === true && isOrdinaryCoreUpdateWarn(result)) {
50
+ if (isOrdinaryCoreUpdateWarn(result)) {
49
51
  continue;
50
52
  }
51
53
  if (input.clientHealthReport === true &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.97",
3
+ "version": "5.345.98",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {