@indigoai-us/hq-cli 5.101.0 → 5.101.1
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,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.101.1] — 2026-08-14
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `hq core rebuild-index threads` no longer crashes with an `ENOENT` stat error
|
|
10
|
+
when a `workspace/threads/T-*.json` entry that the directory scan listed can no
|
|
11
|
+
longer be resolved — removed mid-scan by a concurrent writer (HQ Sync, another
|
|
12
|
+
session, or `archive-old-threads`) or left as a dangling symlink. The renderer
|
|
13
|
+
now stats each file exactly once before sorting (a Schwartzian transform rather
|
|
14
|
+
than statting inside the sort comparator), skips entries that vanished (logging
|
|
15
|
+
a single summary line by basename), and still fails loudly on a genuine stat
|
|
16
|
+
error such as `EACCES`. Both `INDEX.md` and `recent.md` are regenerated as
|
|
17
|
+
before. Sentry 7669694322 (HQ-CLI-P).
|
|
18
|
+
|
|
5
19
|
## [5.101.0] — 2026-08-13
|
|
6
20
|
|
|
7
21
|
### Added
|
|
@@ -31,5 +31,24 @@ export declare function projectStatus(root: string, project: string, prdPath: st
|
|
|
31
31
|
export declare function basename(file: string): string;
|
|
32
32
|
export declare function isHidden(name: string): boolean;
|
|
33
33
|
export declare function mtime(file: string): number;
|
|
34
|
+
/**
|
|
35
|
+
* Sort key for newest-first ordering: the file's modification time in
|
|
36
|
+
* nanoseconds. Stat the file exactly once, before sorting — never from inside a
|
|
37
|
+
* comparator, where a throw aborts `Array.sort` and an mtime that changes
|
|
38
|
+
* mid-sort makes the comparator inconsistent.
|
|
39
|
+
*
|
|
40
|
+
* Returns `undefined` when the entry a directory scan just listed can no longer
|
|
41
|
+
* be resolved: it was removed between the `readdirSync` snapshot and this stat
|
|
42
|
+
* (a time-of-check-to-time-of-use race in a hot, multi-writer directory), or it
|
|
43
|
+
* is a symlink whose target is missing (`statSync` follows symlinks). Callers
|
|
44
|
+
* drop such entries — mirroring how `immediateEntries` and `readJson` already
|
|
45
|
+
* tolerate a vanished file — so the render never crashes on a benign race.
|
|
46
|
+
*
|
|
47
|
+
* Any other stat failure (EACCES, EIO, …) is a genuine fault and is rethrown
|
|
48
|
+
* with the offending file's BASENAME attached, never its absolute path, which
|
|
49
|
+
* must not reach error reporting such as Sentry. The original errno `code` is
|
|
50
|
+
* preserved so upstream error classification is unaffected.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sortKeyMtimeNs(file: string): bigint | undefined;
|
|
34
53
|
export declare function tempDirectory(prefix: string): string;
|
|
35
54
|
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -120,6 +120,38 @@ export function mtime(file) { try {
|
|
|
120
120
|
catch {
|
|
121
121
|
return 0;
|
|
122
122
|
} }
|
|
123
|
+
/**
|
|
124
|
+
* Sort key for newest-first ordering: the file's modification time in
|
|
125
|
+
* nanoseconds. Stat the file exactly once, before sorting — never from inside a
|
|
126
|
+
* comparator, where a throw aborts `Array.sort` and an mtime that changes
|
|
127
|
+
* mid-sort makes the comparator inconsistent.
|
|
128
|
+
*
|
|
129
|
+
* Returns `undefined` when the entry a directory scan just listed can no longer
|
|
130
|
+
* be resolved: it was removed between the `readdirSync` snapshot and this stat
|
|
131
|
+
* (a time-of-check-to-time-of-use race in a hot, multi-writer directory), or it
|
|
132
|
+
* is a symlink whose target is missing (`statSync` follows symlinks). Callers
|
|
133
|
+
* drop such entries — mirroring how `immediateEntries` and `readJson` already
|
|
134
|
+
* tolerate a vanished file — so the render never crashes on a benign race.
|
|
135
|
+
*
|
|
136
|
+
* Any other stat failure (EACCES, EIO, …) is a genuine fault and is rethrown
|
|
137
|
+
* with the offending file's BASENAME attached, never its absolute path, which
|
|
138
|
+
* must not reach error reporting such as Sentry. The original errno `code` is
|
|
139
|
+
* preserved so upstream error classification is unaffected.
|
|
140
|
+
*/
|
|
141
|
+
export function sortKeyMtimeNs(file) {
|
|
142
|
+
try {
|
|
143
|
+
return fs.statSync(file, { bigint: true }).mtimeNs;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
const code = error?.code;
|
|
147
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
148
|
+
return undefined;
|
|
149
|
+
const wrapped = new Error(`failed to stat ${basename(file)} (${code ?? "unknown error"})`);
|
|
150
|
+
if (code !== undefined)
|
|
151
|
+
wrapped.code = code;
|
|
152
|
+
throw wrapped;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
123
155
|
// Kept exported for primitive tests and consumers that need a temp base without
|
|
124
156
|
// relying on an application-specific fixture location.
|
|
125
157
|
export function tempDirectory(prefix) { return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); }
|
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
|
-
import { at, log, readJson, sanitize, timestamp, write } from "./shared.js";
|
|
2
|
+
import { at, basename, log, readJson, sanitize, sortKeyMtimeNs, timestamp, write } from "./shared.js";
|
|
3
3
|
export function renderThreads(context, args = []) {
|
|
4
4
|
const mode = args[0] ?? "--index";
|
|
5
5
|
const directory = at(context.root, "workspace/threads");
|
|
6
6
|
fs.mkdirSync(directory, { recursive: true });
|
|
7
7
|
// Match `ls -t`: newest filesystem modification time first. `updated_at` is
|
|
8
8
|
// rendered metadata only, and must not influence the order.
|
|
9
|
+
//
|
|
10
|
+
// Stat every listed file exactly once, BEFORE sorting (a Schwartzian
|
|
11
|
+
// transform), never from inside the comparator. workspace/threads is a hot,
|
|
12
|
+
// multi-writer directory (HQ Sync reconciliation, concurrent agent sessions,
|
|
13
|
+
// and `archive-old-threads` renaming T-*.json out of it), so an entry
|
|
14
|
+
// readdirSync just snapshotted can vanish before it is stat'ed. Statting
|
|
15
|
+
// inside the comparator turned that time-of-check-to-time-of-use window — and
|
|
16
|
+
// any dangling symlink — into an ENOENT that aborted the whole command before
|
|
17
|
+
// either file was written. Precompute the key, drop entries that no longer
|
|
18
|
+
// resolve (as `readJson` already drops unreadable files), then compare only
|
|
19
|
+
// precomputed keys so the comparator can neither throw nor be inconsistent.
|
|
20
|
+
const skipped = [];
|
|
9
21
|
const files = fs.readdirSync(directory)
|
|
10
22
|
.filter((name) => /^T-.*\.json$/.test(name) && !name.endsWith(".changeset.json"))
|
|
11
23
|
.map((name) => `${directory}/${name}`)
|
|
12
|
-
.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
.map((file) => ({ file, key: sortKeyMtimeNs(file) }))
|
|
25
|
+
.filter((entry) => {
|
|
26
|
+
if (entry.key === undefined) {
|
|
27
|
+
skipped.push(basename(entry.file));
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
})
|
|
32
|
+
.sort((a, b) => (b.key > a.key ? 1 : b.key < a.key ? -1 : 0))
|
|
33
|
+
.map((entry) => entry.file);
|
|
34
|
+
if (skipped.length > 0) {
|
|
35
|
+
const shown = skipped.slice(0, 10).join(", ");
|
|
36
|
+
log(context, `rebuild-threads-index: skipped ${skipped.length} thread file(s) that vanished during the scan: ${shown}${skipped.length > 10 ? ", …" : ""}`);
|
|
37
|
+
}
|
|
17
38
|
const rows = files.flatMap((file) => {
|
|
18
39
|
const data = readJson(file);
|
|
19
40
|
if (!data)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.101.
|
|
3
|
+
"version": "5.101.1",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@aws-sdk/client-iot-data-plane": "^3.1096.0",
|
|
31
31
|
"@aws-sdk/client-s3": "^3.1049.0",
|
|
32
|
-
"@indigoai-us/hq-cloud": "
|
|
32
|
+
"@indigoai-us/hq-cloud": "~6.15.0",
|
|
33
33
|
"@indigoai-us/hq-onboarding": "^0.1.0",
|
|
34
34
|
"@sentry/node": "^10.49.0",
|
|
35
35
|
"@tobilu/qmd": "2.5.3",
|