@indigoai-us/hq-cli 5.268.0 → 5.270.0
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 +36 -0
- package/dist/command-catalog.generated.d.ts +77 -4
- package/dist/command-catalog.generated.js +98 -4
- package/dist/commands/files.js +6 -3
- package/dist/commands/lanes-hold.d.ts +2 -0
- package/dist/commands/lanes-hold.js +122 -310
- package/dist/commands/lanes-message.js +6 -4
- package/dist/commands/lanes-review.d.ts +10 -0
- package/dist/commands/lanes-review.js +56 -0
- package/dist/commands/lanes-shared.d.ts +1 -0
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +29 -4
- package/dist/lib/billing/plan-limit-upgrade-url.d.ts +1 -4
- package/dist/lib/billing/plan-limit-upgrade-url.js +7 -18
- package/dist/lib/doctor/checks/sync-runtime.d.ts +1 -1
- package/dist/lib/doctor/checks/sync-runtime.js +69 -19
- package/dist/lib/lanes/blocker-freshness.js +2 -1
- package/dist/lib/lanes/keep-alive.js +49 -20
- package/dist/lib/lanes/kr-freshness.js +3 -2
- package/dist/lib/lanes/lane-company.d.ts +3 -2
- package/dist/lib/lanes/lane-company.js +21 -4
- package/dist/lib/lanes/manager-status-gate.js +5 -3
- package/dist/lib/lanes/merge-command.d.ts +14 -0
- package/dist/lib/lanes/merge-command.js +363 -0
- package/dist/lib/lanes/reviews.d.ts +38 -0
- package/dist/lib/lanes/reviews.js +395 -0
- package/dist/lib/lanes/watcher.js +4 -2
- package/dist/lib/plan-limit-nag.js +2 -4
- package/dist/utils/feedback-logs.d.ts +14 -9
- package/dist/utils/feedback-logs.js +43 -14
- package/dist/utils/local-files-overview.d.ts +4 -2
- package/dist/utils/local-files-overview.js +47 -6
- package/dist/utils/sync-log-sources.d.ts +15 -0
- package/dist/utils/sync-log-sources.js +36 -0
- package/dist/utils/version-gate.d.ts +1 -0
- package/dist/utils/version-gate.js +6 -1
- package/package.json +1 -1
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* cross the wire; there is no string field on it for anything sensitive to
|
|
12
12
|
* hide inside.
|
|
13
13
|
*
|
|
14
|
-
* Sources (metadata only, via `fs.
|
|
14
|
+
* Sources (metadata only, via `fs.lstat` — full content is read only for the
|
|
15
15
|
* bounded error-line count below, and even then only a capped tail window,
|
|
16
16
|
* and even then only an integer count is ever returned):
|
|
17
|
-
* - the
|
|
17
|
+
* - the newest available desktop or daemon sync-log family and its rotations
|
|
18
|
+
* under `~/.hq/logs/` or `~/.hq/daemon/logs/`
|
|
18
19
|
* - the CLI's own per-company sync journals, enumerated via `listJournals()`
|
|
19
20
|
* (never a hand-reconstructed path — see client-health.ts's note on the
|
|
20
21
|
* single-path-reconstruction regression this mirrors).
|
|
@@ -28,6 +29,7 @@ import * as fs from "node:fs";
|
|
|
28
29
|
import * as os from "node:os";
|
|
29
30
|
import * as path from "node:path";
|
|
30
31
|
import { listJournals } from "@indigoai-us/hq-cloud";
|
|
32
|
+
import { currentSyncLogFamily, SYNC_LOG_GENERATIONS, syncLogBasePaths, syncLogGenerationPath, } from "./sync-log-sources.js";
|
|
31
33
|
import { CLIENT_HEALTH_MAX_ERROR_LINE_COUNT, CLIENT_HEALTH_MAX_FILE_AGE_SECONDS, CLIENT_HEALTH_MAX_FILE_SIZE_BYTES, } from "./client-health-contract.js";
|
|
32
34
|
/**
|
|
33
35
|
* Only the last N bytes of the sync log are read for error-line counting —
|
|
@@ -37,7 +39,8 @@ import { CLIENT_HEALTH_MAX_ERROR_LINE_COUNT, CLIENT_HEALTH_MAX_FILE_AGE_SECONDS,
|
|
|
37
39
|
export const LOCAL_FILES_OVERVIEW_TAIL_READ_BYTES = 2 * 1024 * 1024; // 2 MiB
|
|
38
40
|
/** Case-insensitive marker matched against each line (or JSON-lines `level`). */
|
|
39
41
|
const ERROR_MARKER = /error/i;
|
|
40
|
-
const JSON_LEVEL_ERROR = /"level"\s*:\s*"error"/i;
|
|
42
|
+
const JSON_LEVEL_ERROR = /"level"\s*:\s*"error"\s*(?=[,}])/i;
|
|
43
|
+
const JSON_NUMERIC_LEVEL = /"level"\s*:\s*(-?\d+(?:\.\d+)?)\s*(?=[,}])/;
|
|
41
44
|
const ABSENT = { exists: false, sizeBytes: 0, ageSeconds: 0 };
|
|
42
45
|
function clampInt(value, max) {
|
|
43
46
|
if (!Number.isFinite(value) || value < 0)
|
|
@@ -102,6 +105,12 @@ export function countRecentErrorLines(filePath, maxBytes = LOCAL_FILES_OVERVIEW_
|
|
|
102
105
|
for (const line of text.split("\n")) {
|
|
103
106
|
if (count >= cap)
|
|
104
107
|
break;
|
|
108
|
+
const numericLevel = JSON_NUMERIC_LEVEL.exec(line)?.[1];
|
|
109
|
+
if (numericLevel !== undefined) {
|
|
110
|
+
if (Number(numericLevel) >= 50)
|
|
111
|
+
count += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
105
114
|
if (ERROR_MARKER.test(line) || JSON_LEVEL_ERROR.test(line))
|
|
106
115
|
count += 1;
|
|
107
116
|
}
|
|
@@ -133,9 +142,41 @@ export function collectLocalFilesOverview(deps = {}) {
|
|
|
133
142
|
const nowMs = now.getTime();
|
|
134
143
|
const homeDir = (deps.homeDir ?? os.homedir)();
|
|
135
144
|
const countErrorLines = deps.countErrorLines ?? countRecentErrorLines;
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const
|
|
145
|
+
const hqDir = path.join(homeDir, ".hq");
|
|
146
|
+
const syncLogPaths = syncLogBasePaths(hqDir, deps.env);
|
|
147
|
+
const generations = {
|
|
148
|
+
desktop: [],
|
|
149
|
+
daemon: [],
|
|
150
|
+
};
|
|
151
|
+
const familyModifiedMs = (family) => {
|
|
152
|
+
let newest = null;
|
|
153
|
+
for (let generation = 0; generation < SYNC_LOG_GENERATIONS; generation += 1) {
|
|
154
|
+
const file = syncLogGenerationPath(syncLogPaths[family], generation);
|
|
155
|
+
try {
|
|
156
|
+
const stat = fs.lstatSync(file);
|
|
157
|
+
if (!stat.isFile())
|
|
158
|
+
continue;
|
|
159
|
+
generations[family].push({ file, modifiedMs: stat.mtimeMs });
|
|
160
|
+
newest = newest === null ? stat.mtimeMs : Math.max(newest, stat.mtimeMs);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Missing, denied, or raced generations are absent from this overview.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return newest;
|
|
167
|
+
};
|
|
168
|
+
const family = currentSyncLogFamily(familyModifiedMs("desktop"), familyModifiedMs("daemon"));
|
|
169
|
+
const selectedFamily = family ?? "desktop";
|
|
170
|
+
const selectedFiles = generations[selectedFamily];
|
|
171
|
+
const activePath = syncLogPaths[selectedFamily];
|
|
172
|
+
const activeFacts = statFacts(activePath, nowMs);
|
|
173
|
+
const freshestGeneration = [...selectedFiles].sort((a, b) => b.modifiedMs - a.modifiedMs)[0];
|
|
174
|
+
const syncLogPath = activeFacts.exists ? activePath : freshestGeneration?.file;
|
|
175
|
+
const syncLogFacts = syncLogPath ? statFacts(syncLogPath, nowMs) : ABSENT;
|
|
176
|
+
let recentErrorLineCount = 0;
|
|
177
|
+
for (const generation of selectedFiles) {
|
|
178
|
+
recentErrorLineCount = Math.min(CLIENT_HEALTH_MAX_ERROR_LINE_COUNT, recentErrorLineCount + countErrorLines(generation.file));
|
|
179
|
+
}
|
|
139
180
|
let journalEntries;
|
|
140
181
|
try {
|
|
141
182
|
journalEntries = (deps.journals ?? listJournals)();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Stable relative names used for feedback attachments. */
|
|
2
|
+
export declare const SYNC_LOG_RELATIVE_PATHS: {
|
|
3
|
+
readonly desktop: "logs/hq-sync.log";
|
|
4
|
+
readonly daemon: "daemon/logs/sync.log";
|
|
5
|
+
};
|
|
6
|
+
export type SyncLogFamily = keyof typeof SYNC_LOG_RELATIVE_PATHS;
|
|
7
|
+
/** Read the active file plus the three retained rotations. */
|
|
8
|
+
export declare const SYNC_LOG_GENERATIONS = 4;
|
|
9
|
+
/** Resolve both sync-log families, honoring the daemon's configured root. */
|
|
10
|
+
export declare function syncLogBasePaths(hqDir: string, env?: NodeJS.ProcessEnv): Record<SyncLogFamily, string>;
|
|
11
|
+
export declare function syncLogGenerationPath(basePath: string, generation: number): string;
|
|
12
|
+
export declare function syncLogFamilyForRelativePath(name: string): SyncLogFamily | null;
|
|
13
|
+
/** Choose the most recently written family; desktop wins a timestamp tie. */
|
|
14
|
+
export declare function currentSyncLogFamily(desktopModifiedMs: number | null, daemonModifiedMs: number | null): SyncLogFamily | null;
|
|
15
|
+
//# sourceMappingURL=sync-log-sources.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { daemonPaths } from "../lib/daemon/paths.js";
|
|
3
|
+
/** Stable relative names used for feedback attachments. */
|
|
4
|
+
export const SYNC_LOG_RELATIVE_PATHS = {
|
|
5
|
+
desktop: "logs/hq-sync.log",
|
|
6
|
+
daemon: "daemon/logs/sync.log",
|
|
7
|
+
};
|
|
8
|
+
/** Read the active file plus the three retained rotations. */
|
|
9
|
+
export const SYNC_LOG_GENERATIONS = 4;
|
|
10
|
+
/** Resolve both sync-log families, honoring the daemon's configured root. */
|
|
11
|
+
export function syncLogBasePaths(hqDir, env = process.env) {
|
|
12
|
+
const daemonLogDir = daemonPaths(path.dirname(hqDir), env).logsDir;
|
|
13
|
+
return {
|
|
14
|
+
desktop: path.join(hqDir, SYNC_LOG_RELATIVE_PATHS.desktop),
|
|
15
|
+
daemon: path.join(daemonLogDir, "sync.log"),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function syncLogGenerationPath(basePath, generation) {
|
|
19
|
+
return generation === 0 ? basePath : `${basePath}.${generation}`;
|
|
20
|
+
}
|
|
21
|
+
export function syncLogFamilyForRelativePath(name) {
|
|
22
|
+
if (/^logs\/hq-sync\.log(?:\.\d+)?$/.test(name))
|
|
23
|
+
return "desktop";
|
|
24
|
+
if (/^daemon\/logs\/sync\.log(?:\.\d+)?$/.test(name))
|
|
25
|
+
return "daemon";
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/** Choose the most recently written family; desktop wins a timestamp tie. */
|
|
29
|
+
export function currentSyncLogFamily(desktopModifiedMs, daemonModifiedMs) {
|
|
30
|
+
if (desktopModifiedMs === null)
|
|
31
|
+
return daemonModifiedMs === null ? null : "daemon";
|
|
32
|
+
if (daemonModifiedMs === null)
|
|
33
|
+
return "desktop";
|
|
34
|
+
return daemonModifiedMs > desktopModifiedMs ? "daemon" : "desktop";
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=sync-log-sources.js.map
|
|
@@ -390,6 +390,7 @@ interface EnforceUpdateDeps {
|
|
|
390
390
|
performUpdateString?: (command: string) => UpdateResult;
|
|
391
391
|
resolveInstall?: () => RunningInstall;
|
|
392
392
|
runner?: UpdateRunner;
|
|
393
|
+
platform?: NodeJS.Platform;
|
|
393
394
|
cleanStale?: (prefix: string) => string[];
|
|
394
395
|
acquireLock?: () => UpdateLockHandle | null;
|
|
395
396
|
/**
|
|
@@ -1224,7 +1224,12 @@ function attemptRequiredUpdate(decision, deps, install) {
|
|
|
1224
1224
|
// Never for pnpm or Bun: elevation changes the package manager's global home,
|
|
1225
1225
|
// leaving the user's shim untouched while reporting success. A failed
|
|
1226
1226
|
// non-npm update must surface instead.
|
|
1227
|
-
|
|
1227
|
+
const platform = deps.platform ?? process.platform;
|
|
1228
|
+
if (platform !== "win32" &&
|
|
1229
|
+
!result.ok &&
|
|
1230
|
+
result.code !== "ETIMEDOUT" &&
|
|
1231
|
+
primaryCmd &&
|
|
1232
|
+
!isManagedOutsideNpm) {
|
|
1228
1233
|
console.error(chalk.dim(` Update failed unprivileged; retrying with: sudo -n ${primaryCmd} ${primaryArgs.join(" ")}`));
|
|
1229
1234
|
const sudoResult = performUpdateCommand("sudo", ["-n", primaryCmd, ...primaryArgs], runner);
|
|
1230
1235
|
if (sudoResult.ok)
|