@indigoai-us/hq-cli 5.345.100 → 5.345.102
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,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.102] — 2026-10-12
|
|
6
|
+
- Session-start health no longer files a leftover company journal as an
|
|
7
|
+
unrepaired doctor finding, and company manifest rows keep a real check id
|
|
8
|
+
instead of showing up as unlabeled `unknown`. `hq doctor --fix` still
|
|
9
|
+
refreshes that company journal when hook files have uncommitted changes.
|
|
10
|
+
|
|
11
|
+
## [5.345.101] — 2026-10-12
|
|
12
|
+
- Doctor JSON no longer counts an ordinary HQ Core upgrade warning in its
|
|
13
|
+
summary after omitting it from results. Session-start health that only saw
|
|
14
|
+
that leftover warning after `hq doctor --fix` stops treating it as an
|
|
15
|
+
unrepaired failure. `hq doctor` still prints the upgrade.
|
|
16
|
+
|
|
5
17
|
## [5.345.100] — 2026-10-12
|
|
6
18
|
- `hq storage status` no longer runs the big-file scan, so it returns the
|
|
7
19
|
local backup and cloud history figures without waiting minutes on a big HQ.
|
package/dist/commands/doctor.js
CHANGED
|
@@ -33,7 +33,7 @@ import { PARITY_FAMILY_ID, PARITY_FAMILY_TITLE, runParityReplay, } from "../lib/
|
|
|
33
33
|
import { applyFixes } from "../lib/doctor/fix/apply.js";
|
|
34
34
|
import { renderDoctorFixJson } from "../lib/doctor/fix/json-output.js";
|
|
35
35
|
import { clientHealthCheckStatusEnabled as isActiveCompanyClientHealthCheckStatusEnabled, clientHealthCheckStatusEnabledForCompany, clientHealthReasonCodesEnabled as isActiveCompanyClientHealthReasonCodesEnabled, clientHealthReasonCodesEnabledForCompany, } from "../lib/doctor/client-health-reason-codes-flag.js";
|
|
36
|
-
import {
|
|
36
|
+
import { clientHealthCompanyScopeFromCheckId } from "../lib/doctor/checks/sync-health.js";
|
|
37
37
|
import { readCompaniesManifestMap } from "../lib/work-context/repo-remote.js";
|
|
38
38
|
import * as readline from "node:readline";
|
|
39
39
|
import { runSideEffectTests } from "../lib/doctor/deep/sandbox.js";
|
|
@@ -207,15 +207,7 @@ async function resolveClientHealthReasonCodesCheckIds(families, resolveForCompan
|
|
|
207
207
|
(result.status !== "FAIL" && result.status !== "WARN")) {
|
|
208
208
|
continue;
|
|
209
209
|
}
|
|
210
|
-
const
|
|
211
|
-
const companyUidScope = scoped
|
|
212
|
-
? /^company:(cmp_[A-Za-z0-9]{3,128})$/.exec(scoped[1])
|
|
213
|
-
: null;
|
|
214
|
-
const companySlug = companyUidScope
|
|
215
|
-
? `company:${companyUidScope[1]}`
|
|
216
|
-
: scoped && isCompanyJournalSlug(scoped[1])
|
|
217
|
-
? scoped[1]
|
|
218
|
-
: null;
|
|
210
|
+
const companySlug = clientHealthCompanyScopeFromCheckId(result.checkId);
|
|
219
211
|
const checkIds = checksByCompany.get(companySlug) ?? [];
|
|
220
212
|
checkIds.push(result.checkId);
|
|
221
213
|
checksByCompany.set(companySlug, checkIds);
|
|
@@ -242,8 +234,7 @@ async function resolveClientHealthCheckStatusCheckIds(families, resolveForCompan
|
|
|
242
234
|
for (const result of run.results) {
|
|
243
235
|
if (result.status !== "FAIL" && result.status !== "WARN")
|
|
244
236
|
continue;
|
|
245
|
-
const
|
|
246
|
-
const companySlug = scoped && isCompanyJournalSlug(scoped[1]) ? scoped[1] : null;
|
|
237
|
+
const companySlug = clientHealthCompanyScopeFromCheckId(result.checkId);
|
|
247
238
|
const checkIds = checksByCompany.get(companySlug) ?? [];
|
|
248
239
|
checkIds.push(result.checkId);
|
|
249
240
|
checksByCompany.set(companySlug, checkIds);
|
|
@@ -107,6 +107,23 @@ export interface SyncHealthDeps {
|
|
|
107
107
|
* outside `companies/`.
|
|
108
108
|
*/
|
|
109
109
|
export declare function isSafeCompanySlug(slug: string): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* SessionStart health only ships `checkId` values matching
|
|
112
|
+
* `^sync.[A-Za-z0-9._-]{1,160}$`. A colon (hq-cloud's `company:cmp_…` scope
|
|
113
|
+
* key) becomes the unlabeled `unknown` row in those reports.
|
|
114
|
+
*/
|
|
115
|
+
export declare const CLIENT_HEALTH_SYNC_CHECK_ID: RegExp;
|
|
116
|
+
/**
|
|
117
|
+
* Doctor JSON `checkId` for a manifest scope. hq-cloud keys company rows
|
|
118
|
+
* `company:cmp_…`; the colon is replaced so SessionStart health can report
|
|
119
|
+
* the real id instead of `unknown`.
|
|
120
|
+
*/
|
|
121
|
+
export declare function manifestCheckIdFromScopeKey(scopeKey: string): string;
|
|
122
|
+
/**
|
|
123
|
+
* Company slug or `company:cmp_…` uid used to resolve per-company health
|
|
124
|
+
* flags from a sync finding. Manifest ids may use a colon or a dot.
|
|
125
|
+
*/
|
|
126
|
+
export declare function clientHealthCompanyScopeFromCheckId(checkId: string): string | null;
|
|
110
127
|
/** True when this journal slug names a company rather than the personal tree. */
|
|
111
128
|
export declare function isCompanyJournalSlug(slug: string): boolean;
|
|
112
129
|
/** True when this journal slug names the personal tree or personal vault. */
|
|
@@ -194,13 +194,64 @@ const NON_COMPANY_JOURNAL_SLUGS = new Set([
|
|
|
194
194
|
PERSONAL_SCOPE_SLUG,
|
|
195
195
|
PERSONAL_VAULT_SCOPE_SLUG,
|
|
196
196
|
]);
|
|
197
|
+
/**
|
|
198
|
+
* SessionStart health only ships `checkId` values matching
|
|
199
|
+
* `^sync.[A-Za-z0-9._-]{1,160}$`. A colon (hq-cloud's `company:cmp_…` scope
|
|
200
|
+
* key) becomes the unlabeled `unknown` row in those reports.
|
|
201
|
+
*/
|
|
202
|
+
export const CLIENT_HEALTH_SYNC_CHECK_ID = /^sync\.[A-Za-z0-9._-]{1,160}$/;
|
|
203
|
+
const COMPANY_SCOPE_UID = /^company[.:](cmp_[A-Za-z0-9]{3,128})$/;
|
|
197
204
|
/** Keep malformed scopes distinct without letting them imitate valid slugs. */
|
|
198
205
|
function journalSlugForCheckId(slug) {
|
|
199
206
|
if (NON_COMPANY_JOURNAL_SLUGS.has(slug) || isSafeCompanySlug(slug)) {
|
|
200
207
|
return slug;
|
|
201
208
|
}
|
|
202
209
|
const encoded = Array.from(slug, (character) => character.codePointAt(0).toString(16)).join("-");
|
|
203
|
-
return `__hq_invalid_journal_scope__${encoded}
|
|
210
|
+
return boundInvalidScopeToken(`__hq_invalid_journal_scope__${encoded}`);
|
|
211
|
+
}
|
|
212
|
+
function boundInvalidScopeToken(token) {
|
|
213
|
+
// `sync.` + token must fit the health-hook 160-char suffix budget.
|
|
214
|
+
const maxSuffix = 160;
|
|
215
|
+
if (token.length <= maxSuffix)
|
|
216
|
+
return token;
|
|
217
|
+
return token.slice(0, maxSuffix);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Doctor JSON `checkId` for a manifest scope. hq-cloud keys company rows
|
|
221
|
+
* `company:cmp_…`; the colon is replaced so SessionStart health can report
|
|
222
|
+
* the real id instead of `unknown`.
|
|
223
|
+
*/
|
|
224
|
+
export function manifestCheckIdFromScopeKey(scopeKey) {
|
|
225
|
+
if (scopeKey.startsWith("company:")) {
|
|
226
|
+
return `sync.manifest.${scopeKey.replace(/:/g, ".")}`;
|
|
227
|
+
}
|
|
228
|
+
if (scopeKey === "personal" || isSafeCompanySlug(scopeKey)) {
|
|
229
|
+
return `sync.manifest.${scopeKey}`;
|
|
230
|
+
}
|
|
231
|
+
const encoded = Array.from(scopeKey, (character) => character.codePointAt(0).toString(16)).join("-");
|
|
232
|
+
return `sync.manifest.${boundInvalidScopeToken(`__hq_invalid_scope__${encoded}`)}`;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Company slug or `company:cmp_…` uid used to resolve per-company health
|
|
236
|
+
* flags from a sync finding. Manifest ids may use a colon or a dot.
|
|
237
|
+
*/
|
|
238
|
+
export function clientHealthCompanyScopeFromCheckId(checkId) {
|
|
239
|
+
const journalSlug = journalSlugFromCheckId(checkId);
|
|
240
|
+
if (journalSlug && isCompanyJournalSlug(journalSlug) && isSafeCompanySlug(journalSlug)) {
|
|
241
|
+
return journalSlug;
|
|
242
|
+
}
|
|
243
|
+
const manifest = /^sync\.manifest\.(.+)$/.exec(checkId);
|
|
244
|
+
if (!manifest)
|
|
245
|
+
return null;
|
|
246
|
+
const rest = manifest[1];
|
|
247
|
+
const companyUid = COMPANY_SCOPE_UID.exec(rest)?.[1];
|
|
248
|
+
if (companyUid)
|
|
249
|
+
return `company:${companyUid}`;
|
|
250
|
+
if (rest === "personal" || NON_COMPANY_JOURNAL_SLUGS.has(rest))
|
|
251
|
+
return null;
|
|
252
|
+
if (isSafeCompanySlug(rest))
|
|
253
|
+
return rest;
|
|
254
|
+
return null;
|
|
204
255
|
}
|
|
205
256
|
/** True when this journal slug names a company rather than the personal tree. */
|
|
206
257
|
export function isCompanyJournalSlug(slug) {
|
|
@@ -553,7 +604,7 @@ function manifestResults(deps, journals) {
|
|
|
553
604
|
}
|
|
554
605
|
const nowMs = deps.now().getTime();
|
|
555
606
|
const reported = statuses.map((entry) => {
|
|
556
|
-
const checkId =
|
|
607
|
+
const checkId = manifestCheckIdFromScopeKey(entry.scopeKey);
|
|
557
608
|
if (!entry.lastUploadAt) {
|
|
558
609
|
return {
|
|
559
610
|
status: "UNTESTED",
|
|
@@ -107,12 +107,12 @@ export interface BuildDoctorJsonInput {
|
|
|
107
107
|
/**
|
|
108
108
|
* JSON consumed by the SessionStart health hook (`--client-health-report`).
|
|
109
109
|
* Ordinary hq-core upgrade WARNs are omitted from every JSON document
|
|
110
|
-
* (`--json` and `--client-health-report`)
|
|
111
|
-
* and both current hooks and older ones
|
|
112
|
-
* treated the leftover WARN as an
|
|
113
|
-
* still prints the upgrade. Company
|
|
114
|
-
* to refresh are omitted only from
|
|
115
|
-
* leaves them for a later session.
|
|
110
|
+
* (`--json` and `--client-health-report`), including `summary` tallies:
|
|
111
|
+
* `--fix` cannot apply a release, and both current hooks and older ones
|
|
112
|
+
* that only call `hq doctor --json` treated the leftover WARN as an
|
|
113
|
+
* unrepaired failure. Human `hq doctor` still prints the upgrade. Company
|
|
114
|
+
* journals this `--fix` pass did not try to refresh are omitted only from
|
|
115
|
+
* `--client-health-report`: the repair cap leaves them for a later session.
|
|
116
116
|
*/
|
|
117
117
|
clientHealthReport?: boolean;
|
|
118
118
|
/** Clock for the leftover-journal freshness window. Default: `Date.now`. */
|
|
@@ -121,8 +121,9 @@ export interface BuildDoctorJsonInput {
|
|
|
121
121
|
/**
|
|
122
122
|
* True when a sync finding is an ordinary, non-repairable hq-core upgrade.
|
|
123
123
|
* FAIL stays visible: that is a broken local core, not "a newer release exists".
|
|
124
|
-
* JSON omits the WARN
|
|
125
|
-
* only call `hq doctor --json`) does not file
|
|
124
|
+
* JSON omits the WARN (from `results` and `summary`) so SessionStart health
|
|
125
|
+
* (including core 15.x hooks that only call `hq doctor --json`) does not file
|
|
126
|
+
* it as a failed repair.
|
|
126
127
|
*/
|
|
127
128
|
export declare function isOrdinaryCoreUpdateWarn(result: {
|
|
128
129
|
checkId: string;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* `target` and `remediation` are emitted only when present, matching the
|
|
18
18
|
* per-item result shape where both are optional.
|
|
19
19
|
*/
|
|
20
|
-
import { computeExitCode,
|
|
20
|
+
import { computeExitCode, emptyStatusCounts, UNKNOWN_PLATFORM, } from "./report.js";
|
|
21
21
|
import { deriveRemediation } from "./fix/remediation.js";
|
|
22
22
|
import { freshAttemptedCompanyJournalCheckIds, isUnreachedCompanyJournalForClientHealth, } from "./fix/refresh-sync-progress.js";
|
|
23
23
|
/**
|
|
@@ -33,12 +33,20 @@ 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
|
|
37
|
-
* only call `hq doctor --json`) does not file
|
|
36
|
+
* JSON omits the WARN (from `results` and `summary`) so SessionStart health
|
|
37
|
+
* (including core 15.x hooks that only call `hq doctor --json`) does not file
|
|
38
|
+
* it as a failed repair.
|
|
38
39
|
*/
|
|
39
40
|
export function isOrdinaryCoreUpdateWarn(result) {
|
|
40
41
|
return result.checkId === "sync.update.core" && result.status === "WARN";
|
|
41
42
|
}
|
|
43
|
+
/** Tally the results that actually ship in the JSON document. */
|
|
44
|
+
function summarizeJsonResults(results) {
|
|
45
|
+
const counts = emptyStatusCounts();
|
|
46
|
+
for (const result of results)
|
|
47
|
+
counts[result.status] += 1;
|
|
48
|
+
return counts;
|
|
49
|
+
}
|
|
42
50
|
/** Assemble the machine-readable document from a completed run. */
|
|
43
51
|
export function buildDoctorJson(input) {
|
|
44
52
|
const attemptedRefreshSyncCheckIds = input.clientHealthReport === true
|
|
@@ -61,7 +69,7 @@ export function buildDoctorJson(input) {
|
|
|
61
69
|
schemaVersion: DOCTOR_JSON_SCHEMA_VERSION,
|
|
62
70
|
platform: input.platform ?? UNKNOWN_PLATFORM,
|
|
63
71
|
hqRoot: input.hqRoot,
|
|
64
|
-
summary:
|
|
72
|
+
summary: summarizeJsonResults(results),
|
|
65
73
|
exitCode: computeExitCode(input.families),
|
|
66
74
|
...(input.scope && input.scope.length > 0 ? { scope: [...input.scope] } : {}),
|
|
67
75
|
results,
|