@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
|
-
|
|
399
|
-
|
|
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.
|
|
403
|
-
//
|
|
404
|
-
// repair a row it cannot refresh
|
|
405
|
-
// journal
|
|
406
|
-
if (entry.slug === PERSONAL_SCOPE_SLUG
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
|
134
|
-
// check layer
|
|
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
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
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 (
|
|
50
|
+
if (isOrdinaryCoreUpdateWarn(result)) {
|
|
49
51
|
continue;
|
|
50
52
|
}
|
|
51
53
|
if (input.clientHealthReport === true &&
|