@indigoai-us/hq-cli 5.326.0 → 5.328.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 +10 -0
- package/assets/scaffold/core/scripts/hq-dm-bind.sh +1 -1
- package/assets/scaffold/core/scripts/hq-job-run.sh +8 -8
- package/assets/scaffold/core/scripts/ontology-audience-grant.sh +1 -1
- package/assets/scaffold/core/scripts/ontology-candidate.sh +1 -1
- package/assets/scaffold/core/scripts/resolve-company.sh +1 -1
- package/dist/command-catalog.generated.d.ts +9 -0
- package/dist/command-catalog.generated.js +10 -0
- package/dist/commands/auth.js +3 -1
- package/dist/commands/core-detect-stale-review-base-fast.d.ts +2 -0
- package/dist/commands/core-detect-stale-review-base-fast.js +23 -0
- package/dist/commands/core-register-project-native.js +1 -1
- package/dist/commands/core-repo-run-registry-fast.d.ts +2 -0
- package/dist/commands/core-repo-run-registry-fast.js +35 -0
- package/dist/commands/core-rrr-worktree-scope-fix-fast-flag.d.ts +7 -0
- package/dist/commands/core-rrr-worktree-scope-fix-fast-flag.js +25 -0
- package/dist/commands/core-session-journal-native.js +1 -1
- package/dist/commands/core-share-suggestion-state-fast.d.ts +2 -0
- package/dist/commands/core-share-suggestion-state-fast.js +25 -0
- package/dist/commands/logout.js +2 -0
- package/dist/commands/mcp-serve.js +1 -1
- package/dist/commands/mcp-status.js +7 -0
- package/dist/fast-core.js +3 -6
- package/dist/fast-scaffold.js +5 -1
- package/dist/lib/daemon/mcp-endpoint.d.ts +8 -0
- package/dist/lib/daemon/mcp-endpoint.js +30 -0
- package/dist/lib/daemon/mcp-oauth.d.ts +24 -0
- package/dist/lib/daemon/mcp-oauth.js +298 -0
- package/dist/lib/daemon/ops/mcp.d.ts +0 -1
- package/dist/lib/daemon/ops/mcp.js +31 -31
- package/dist/lib/process-flag-company-context-fast.d.ts +4 -0
- package/dist/lib/process-flag-company-context-fast.js +20 -0
- package/dist/lib/process-flag-company-context-light.d.ts +9 -0
- package/dist/lib/process-flag-company-context-light.js +81 -0
- package/dist/lib/process-flag-company-context.d.ts +2 -4
- package/dist/lib/process-flag-company-context.js +2 -46
- package/dist/node-network-compat-fast.js +4 -0
- package/dist/utils/hq-roots-core.d.ts +130 -0
- package/dist/utils/hq-roots-core.js +354 -0
- package/dist/utils/hq-roots.d.ts +2 -129
- package/dist/utils/hq-roots.js +2 -353
- package/dist/utils/package-root-diagnostics.d.ts +5 -36
- package/dist/utils/package-root-diagnostics.js +4 -83
- package/dist/utils/package-root-resolution-error.d.ts +32 -0
- package/dist/utils/package-root-resolution-error.js +79 -0
- package/dist/utils/vault-api.js +16 -1
- package/package.json +1 -1
package/dist/utils/hq-roots.js
CHANGED
|
@@ -1,354 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* When shell that used to live inside the HQ scaffold moves into this npm
|
|
5
|
-
* package, it loses the one thing it relied on: `BASH_SOURCE`/`$0` pointed at
|
|
6
|
-
* the user's live HQ tree, so "where am I" and "where is HQ" were the same
|
|
7
|
-
* answer. Inside a globally installed package they are three different answers,
|
|
8
|
-
* and conflating any two of them silently corrupts the wrong tree.
|
|
9
|
-
*
|
|
10
|
-
* liveRoot The user's HQ installation — companies/, workspace/, personal/,
|
|
11
|
-
* the installed .claude/ and core/. This is runtime state. Hidden
|
|
12
|
-
* runtime commands operate here.
|
|
13
|
-
*
|
|
14
|
-
* packageRoot This npm package's own directory, where bundled scaffold assets
|
|
15
|
-
* ship. Read-only in practice: a global install may sit in a
|
|
16
|
-
* root-owned prefix. NEVER usable as liveRoot.
|
|
17
|
-
*
|
|
18
|
-
* There is deliberately no third "source root". Release-source tooling — the
|
|
19
|
-
* linters, generators, and validators that rewrite checked-in artifacts — stays
|
|
20
|
-
* scaffold-owned, so nothing here ever needs to resolve an hq-core checkout. See
|
|
21
|
-
* the scaffold-vs-cli-code-ownership policy for why.
|
|
22
|
-
*
|
|
23
|
-
* The invariant that matters most: `resolveLiveRoot` never consults
|
|
24
|
-
* `import.meta.url`. A relocated script asking "where is HQ" must get the live
|
|
25
|
-
* tree, not the package it happens to be bundled in.
|
|
26
|
-
*/
|
|
27
|
-
import * as fs from "fs";
|
|
28
|
-
import * as path from "path";
|
|
29
|
-
import { fileURLToPath } from "url";
|
|
30
|
-
import { PackageRootResolutionError, } from "./package-root-diagnostics.js";
|
|
31
|
-
/** This package's name, used to identify its root when walking upward. */
|
|
32
|
-
const CLI_PACKAGE_NAME = "@indigoai-us/hq-cli";
|
|
33
|
-
/**
|
|
34
|
-
* A root that cannot be resolved is the caller's state — a wrong `--hq-root`, or a
|
|
35
|
-
* command run from outside any HQ tree. Marking
|
|
36
|
-
* these `expected` makes the top-level handler print the message and skip Sentry;
|
|
37
|
-
* an unmarked error there prints NOTHING and only ships a crash report, which is
|
|
38
|
-
* the worst possible outcome for a misconfiguration the user can fix.
|
|
39
|
-
*
|
|
40
|
-
* Deliberately not used for packaging faults — a missing bundled asset IS a
|
|
41
|
-
* defect and should reach Sentry.
|
|
42
|
-
*/
|
|
43
|
-
function rootError(message) {
|
|
44
|
-
const err = new Error(message);
|
|
45
|
-
err.expected = true;
|
|
46
|
-
return err;
|
|
47
|
-
}
|
|
48
|
-
/** Where bundled scaffold assets live, relative to the package root. */
|
|
49
|
-
export const BUNDLED_ASSET_DIR = path.join("assets", "scaffold");
|
|
50
|
-
/**
|
|
51
|
-
* Does this directory look like an HQ tree root?
|
|
52
|
-
*
|
|
53
|
-
* `companies/` is the load-bearing marker — it is the source of truth for
|
|
54
|
-
* tenants and exists in every real install. The second clause tolerates the
|
|
55
|
-
* three shapes a tree can legitimately be in: a full install, a scaffold
|
|
56
|
-
* checkout without a personal overlay, and a personal-only overlay.
|
|
57
|
-
*/
|
|
58
|
-
export function isHqRoot(dir) {
|
|
59
|
-
return (fs.existsSync(path.join(dir, "companies")) &&
|
|
60
|
-
(fs.existsSync(path.join(dir, ".claude")) ||
|
|
61
|
-
fs.existsSync(path.join(dir, "core")) ||
|
|
62
|
-
fs.existsSync(path.join(dir, "personal"))));
|
|
63
|
-
}
|
|
64
|
-
function realpathOrResolve(p, fileSystem = fs) {
|
|
65
|
-
try {
|
|
66
|
-
return fileSystem.realpathSync(p);
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
return path.resolve(p);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function errorCode(err) {
|
|
73
|
-
if (err && typeof err === "object" && "code" in err) {
|
|
74
|
-
const code = err.code;
|
|
75
|
-
return typeof code === "string" ? code : undefined;
|
|
76
|
-
}
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
function failureOutcome(code) {
|
|
80
|
-
if (code === "ENOENT")
|
|
81
|
-
return "no-manifest";
|
|
82
|
-
if (code === "EACCES")
|
|
83
|
-
return "eacces";
|
|
84
|
-
if (code === "EISDIR")
|
|
85
|
-
return "eisdir";
|
|
86
|
-
return "read-error";
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Make a resolver for this package's root. It caches only a successful lookup:
|
|
90
|
-
* a concurrently replaced manifest must be retried by the next command, not
|
|
91
|
-
* converted into a permanent process failure.
|
|
92
|
-
*/
|
|
93
|
-
export function createPackageRootResolver(options) {
|
|
94
|
-
const fileSystem = options.fs ?? fs;
|
|
95
|
-
const modulePath = options.modulePath ?? options.moduleDir;
|
|
96
|
-
let cachedPackageRoot;
|
|
97
|
-
return () => {
|
|
98
|
-
if (cachedPackageRoot !== undefined)
|
|
99
|
-
return cachedPackageRoot;
|
|
100
|
-
const visited = [];
|
|
101
|
-
let dir = options.moduleDir;
|
|
102
|
-
for (;;) {
|
|
103
|
-
const manifest = path.join(dir, "package.json");
|
|
104
|
-
try {
|
|
105
|
-
const parsed = JSON.parse(fileSystem.readFileSync(manifest, "utf8"));
|
|
106
|
-
if (parsed.name === CLI_PACKAGE_NAME) {
|
|
107
|
-
cachedPackageRoot = realpathOrResolve(dir, fileSystem);
|
|
108
|
-
return cachedPackageRoot;
|
|
109
|
-
}
|
|
110
|
-
visited.push({
|
|
111
|
-
directory: dir,
|
|
112
|
-
outcome: "name-mismatch",
|
|
113
|
-
manifestName: String(parsed.name),
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
catch (err) {
|
|
117
|
-
const code = errorCode(err);
|
|
118
|
-
if (code) {
|
|
119
|
-
visited.push({ directory: dir, outcome: failureOutcome(code), code });
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
visited.push({ directory: dir, outcome: "parse-error" });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const parent = path.dirname(dir);
|
|
126
|
-
if (parent === dir)
|
|
127
|
-
break;
|
|
128
|
-
dir = parent;
|
|
129
|
-
}
|
|
130
|
-
let fallbackRejectedBecause = "no dist-owner ancestor";
|
|
131
|
-
try {
|
|
132
|
-
const resolvedModuleDir = fileSystem.realpathSync(options.moduleDir);
|
|
133
|
-
let candidate = resolvedModuleDir;
|
|
134
|
-
for (;;) {
|
|
135
|
-
const relativeModulePath = path.relative(candidate, resolvedModuleDir);
|
|
136
|
-
if (relativeModulePath.split(path.sep)[0] === "dist") {
|
|
137
|
-
const assetDir = path.join(candidate, BUNDLED_ASSET_DIR);
|
|
138
|
-
try {
|
|
139
|
-
if (fileSystem.existsSync(assetDir) && fileSystem.statSync(assetDir).isDirectory()) {
|
|
140
|
-
cachedPackageRoot = candidate;
|
|
141
|
-
return cachedPackageRoot;
|
|
142
|
-
}
|
|
143
|
-
fallbackRejectedBecause =
|
|
144
|
-
"deepest dist-owner lacks assets/scaffold";
|
|
145
|
-
}
|
|
146
|
-
catch (err) {
|
|
147
|
-
fallbackRejectedBecause =
|
|
148
|
-
`could not inspect deepest dist-owner assets/scaffold (${errorCode(err) ?? "unknown"})`;
|
|
149
|
-
}
|
|
150
|
-
// Never climb to a shallower dist owner after the nearest owner fails.
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
const parent = path.dirname(candidate);
|
|
154
|
-
if (parent === candidate)
|
|
155
|
-
break;
|
|
156
|
-
candidate = parent;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
catch (err) {
|
|
160
|
-
fallbackRejectedBecause =
|
|
161
|
-
`could not realpath module directory (${errorCode(err) ?? "unknown"})`;
|
|
162
|
-
}
|
|
163
|
-
throw new PackageRootResolutionError({
|
|
164
|
-
modulePath,
|
|
165
|
-
startDir: options.moduleDir,
|
|
166
|
-
strategiesTried: ["manifest-walk", "dist-owner-fallback"],
|
|
167
|
-
visited,
|
|
168
|
-
fallbackRejectedBecause,
|
|
169
|
-
});
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
const currentModulePath = fileURLToPath(import.meta.url);
|
|
173
|
-
const defaultPackageRootResolver = createPackageRootResolver({
|
|
174
|
-
moduleDir: path.dirname(currentModulePath),
|
|
175
|
-
modulePath: currentModulePath,
|
|
176
|
-
});
|
|
177
|
-
/**
|
|
178
|
-
* This package's installed root — the directory holding its `package.json`.
|
|
179
|
-
*
|
|
180
|
-
* Walks up from the compiled module (`dist/utils/hq-roots.js`) until it finds a
|
|
181
|
-
* `package.json` whose name matches, rather than hard-coding `..` hops, so the
|
|
182
|
-
* answer survives a change in output layout.
|
|
183
|
-
*/
|
|
184
|
-
export function packageRoot() {
|
|
185
|
-
return defaultPackageRootResolver();
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* The trailing path segments that mark an installed copy of this package:
|
|
189
|
-
* `node_modules` followed by the package name's own segments (for
|
|
190
|
-
* `@indigoai-us/hq-cli`, that is `node_modules/@indigoai-us/hq-cli`).
|
|
191
|
-
*/
|
|
192
|
-
const INSTALLED_ROOT_SEGMENTS = ["node_modules", ...CLI_PACKAGE_NAME.split("/")];
|
|
193
|
-
/** A Windows-shaped absolute path (drive letter or UNC), regardless of host OS. */
|
|
194
|
-
function looksWin32Path(p) {
|
|
195
|
-
return /^[a-zA-Z]:[\\/]/.test(p) || /^\\\\/.test(p);
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Derive this package's installed root from the running module's OWN path using
|
|
199
|
-
* STRING operations only — no `readFileSync`, `realpathSync`, or `existsSync`.
|
|
200
|
-
*
|
|
201
|
-
* {@link packageRoot} must read `<dir>/package.json` and `realpathSync` the
|
|
202
|
-
* module directory to answer, so it THROWS in exactly the situation that most
|
|
203
|
-
* needs an answer: a concurrent global install has renamed the installed
|
|
204
|
-
* package directory aside (`.hq-cli-<rand>`) and is re-extracting it file by
|
|
205
|
-
* file, so both the manifest walk and the dist-owner fallback fail on a
|
|
206
|
-
* directory that is momentarily gone. This walks the compiled module's
|
|
207
|
-
* ancestors as strings and returns the DEEPEST one whose trailing segments are
|
|
208
|
-
* `node_modules/@indigoai-us/hq-cli`, which stays correct while that directory
|
|
209
|
-
* does not exist on disk.
|
|
210
|
-
*
|
|
211
|
-
* It is a FALLBACK only — {@link packageRoot} validates the manifest name and
|
|
212
|
-
* is authoritative whenever it succeeds. Returns null for a dev checkout or any
|
|
213
|
-
* layout without that segment triple, so a caller that falls back to it can
|
|
214
|
-
* only ever degrade to today's behaviour (no resolution), never widen it.
|
|
215
|
-
*
|
|
216
|
-
* The deepest match is deliberate: a nested `node_modules/@indigoai-us/hq-cli`
|
|
217
|
-
* inside another package resolves to ITSELF (the copy the module belongs to),
|
|
218
|
-
* never an outer decoy. Case is folded only for a Windows-shaped path, matching
|
|
219
|
-
* the POSIX case-sensitivity the rest of this module assumes.
|
|
220
|
-
*/
|
|
221
|
-
export function stringDerivedPackageRoot(moduleFilePath = currentModulePath) {
|
|
222
|
-
if (typeof moduleFilePath !== "string" || moduleFilePath.length === 0) {
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
const wanted = INSTALLED_ROOT_SEGMENTS;
|
|
226
|
-
const caseInsensitive = looksWin32Path(moduleFilePath);
|
|
227
|
-
const sameSegment = (a, b) => caseInsensitive ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
228
|
-
// Tokenise into segments with each one's end offset in the ORIGINAL string,
|
|
229
|
-
// so the returned root keeps the source path's leading root and separators
|
|
230
|
-
// verbatim (a leading `/`, or a `C:\` drive) rather than a rejoined guess.
|
|
231
|
-
const segments = [];
|
|
232
|
-
const segmentPattern = /[^\\/]+/g;
|
|
233
|
-
let match;
|
|
234
|
-
while ((match = segmentPattern.exec(moduleFilePath)) !== null) {
|
|
235
|
-
segments.push({ text: match[0], end: match.index + match[0].length });
|
|
236
|
-
}
|
|
237
|
-
if (segments.length < wanted.length)
|
|
238
|
-
return null;
|
|
239
|
-
// Scan from the deepest segment upward; the first (deepest) leaf whose
|
|
240
|
-
// preceding segments complete the triple wins.
|
|
241
|
-
for (let leaf = segments.length - 1; leaf >= wanted.length - 1; leaf--) {
|
|
242
|
-
let matched = true;
|
|
243
|
-
for (let k = 0; k < wanted.length; k++) {
|
|
244
|
-
if (!sameSegment(segments[leaf - (wanted.length - 1) + k].text, wanted[k])) {
|
|
245
|
-
matched = false;
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
if (matched)
|
|
250
|
-
return moduleFilePath.slice(0, segments[leaf].end);
|
|
251
|
-
}
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Resolve the user's live HQ installation.
|
|
256
|
-
*
|
|
257
|
-
* Precedence: `--hq-root` → `HQ_ROOT` → `CLAUDE_PROJECT_DIR` → walk up from cwd.
|
|
258
|
-
*
|
|
259
|
-
* ## An explicit root is trusted; a discovered one must be recognized
|
|
260
|
-
*
|
|
261
|
-
* The FIRST explicit candidate present wins outright — there is no fall-through
|
|
262
|
-
* past it, because silently operating on a different tree than the caller named
|
|
263
|
-
* is the worst available outcome.
|
|
264
|
-
*
|
|
265
|
-
* That explicit candidate is checked only for the two things that are always
|
|
266
|
-
* wrong: it must be a directory, and it must not be inside {@link packageRoot}
|
|
267
|
-
* (that is this package, not anyone's HQ). It is deliberately NOT required to
|
|
268
|
-
* satisfy {@link isHqRoot}.
|
|
269
|
-
*
|
|
270
|
-
* Requiring markers here was tried and was wrong: the scripts these roots feed
|
|
271
|
-
* accepted any `HQ_ROOT` before they moved, and several callers legitimately pass
|
|
272
|
-
* a minimal tree — e.g. `rebuild-threads-index` against a fixture holding nothing
|
|
273
|
-
* but `workspace/threads/`. Enforcing markers on an explicit value turned a
|
|
274
|
-
* relocation into a silent contract change and broke those callers.
|
|
275
|
-
*
|
|
276
|
-
* The cwd walk is the opposite case: there the marker check is the only thing
|
|
277
|
-
* that can identify a root at all, so it stays mandatory.
|
|
278
|
-
*/
|
|
279
|
-
export function resolveLiveRoot(opts = {}) {
|
|
280
|
-
const env = opts.env ?? process.env;
|
|
281
|
-
const candidates = [];
|
|
282
|
-
if (opts.hqRoot) {
|
|
283
|
-
candidates.push({ value: opts.hqRoot, source: "--hq-root" });
|
|
284
|
-
}
|
|
285
|
-
if (env.HQ_ROOT) {
|
|
286
|
-
candidates.push({ value: env.HQ_ROOT, source: "$HQ_ROOT" });
|
|
287
|
-
}
|
|
288
|
-
if (env.CLAUDE_PROJECT_DIR) {
|
|
289
|
-
candidates.push({
|
|
290
|
-
value: env.CLAUDE_PROJECT_DIR,
|
|
291
|
-
source: "$CLAUDE_PROJECT_DIR",
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
const explicit = candidates[0];
|
|
295
|
-
if (explicit) {
|
|
296
|
-
const resolved = realpathOrResolve(explicit.value);
|
|
297
|
-
if (isWithinPackage(resolved)) {
|
|
298
|
-
throw rootError(`${explicit.source} points inside the ${CLI_PACKAGE_NAME} package ` +
|
|
299
|
-
`(${resolved}). That is the CLI's own install directory, not an HQ tree.`);
|
|
300
|
-
}
|
|
301
|
-
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
|
|
302
|
-
throw rootError(`${explicit.source} is not a directory: ${resolved}`);
|
|
303
|
-
}
|
|
304
|
-
return resolved;
|
|
305
|
-
}
|
|
306
|
-
// Walk up from cwd. Unlike the explicit tiers this is a guess, so failing to
|
|
307
|
-
// find a root here is reported as "run this from inside HQ" rather than as a
|
|
308
|
-
// bad value.
|
|
309
|
-
let dir = realpathOrResolve(opts.cwd ?? process.cwd());
|
|
310
|
-
for (;;) {
|
|
311
|
-
if (isHqRoot(dir) && !isWithinPackage(dir))
|
|
312
|
-
return dir;
|
|
313
|
-
const parent = path.dirname(dir);
|
|
314
|
-
if (parent === dir)
|
|
315
|
-
break;
|
|
316
|
-
dir = parent;
|
|
317
|
-
}
|
|
318
|
-
throw rootError("Could not find your HQ installation. Run this from inside your HQ " +
|
|
319
|
-
"directory, or pass --hq-root <path> / set $HQ_ROOT.");
|
|
320
|
-
}
|
|
321
|
-
function isWithinPackage(candidate) {
|
|
322
|
-
let root;
|
|
323
|
-
try {
|
|
324
|
-
root = packageRoot();
|
|
325
|
-
}
|
|
326
|
-
catch {
|
|
327
|
-
// If we cannot locate our own package we cannot be inside it either.
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
const rel = path.relative(root, candidate);
|
|
331
|
-
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Absolute path to a scaffold asset bundled in this package.
|
|
335
|
-
*
|
|
336
|
-
* Refuses to resolve outside the asset directory so a manifest entry can never
|
|
337
|
-
* be coaxed into executing an arbitrary path, and fails loudly when the asset
|
|
338
|
-
* is missing — an absent asset means the packlist dropped it, which is a
|
|
339
|
-
* packaging bug worth surfacing rather than a runtime fallback.
|
|
340
|
-
*/
|
|
341
|
-
export function resolveBundledAsset(relativePath) {
|
|
342
|
-
const assetDir = path.join(packageRoot(), BUNDLED_ASSET_DIR);
|
|
343
|
-
const resolved = path.resolve(assetDir, relativePath);
|
|
344
|
-
const rel = path.relative(assetDir, resolved);
|
|
345
|
-
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
346
|
-
throw new Error(`Bundled asset path escapes ${BUNDLED_ASSET_DIR}: ${relativePath}`);
|
|
347
|
-
}
|
|
348
|
-
if (!fs.existsSync(resolved)) {
|
|
349
|
-
throw new Error(`Bundled asset is missing from the installed package: ${BUNDLED_ASSET_DIR}/${relativePath}\n` +
|
|
350
|
-
"This is a packaging fault — the file exists in source but was not published.");
|
|
351
|
-
}
|
|
352
|
-
return resolved;
|
|
353
|
-
}
|
|
1
|
+
/** Compatibility entrypoint for the full command graph. */
|
|
2
|
+
export * from "./hq-roots-core.js";
|
|
354
3
|
//# sourceMappingURL=hq-roots.js.map
|
|
@@ -1,40 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bounded
|
|
3
|
-
*
|
|
4
|
-
* These values are sent to Sentry, whose general scrubber deliberately keeps
|
|
5
|
-
* filesystem paths intact. Keep this module the single construction boundary
|
|
6
|
-
* so a malformed install cannot turn an error report into an unbounded or
|
|
7
|
-
* multi-line payload.
|
|
2
|
+
* Bounded Sentry context for failures to locate the CLI package root.
|
|
3
|
+
* The error value itself is kept in a dependency-light module for fast paths.
|
|
8
4
|
*/
|
|
9
|
-
|
|
10
|
-
export type PackageRootVisit
|
|
11
|
-
|
|
12
|
-
outcome: PackageRootVisitOutcome;
|
|
13
|
-
code?: string;
|
|
14
|
-
manifestName?: string;
|
|
15
|
-
};
|
|
16
|
-
export type PackageRootResolutionDiagnostics = {
|
|
17
|
-
modulePath: string;
|
|
18
|
-
startDir: string;
|
|
19
|
-
strategiesTried: string[];
|
|
20
|
-
visited: PackageRootVisit[];
|
|
21
|
-
fallbackRejectedBecause: string;
|
|
22
|
-
};
|
|
23
|
-
type RawDiagnostics = {
|
|
24
|
-
modulePath: string;
|
|
25
|
-
startDir: string;
|
|
26
|
-
strategiesTried: string[];
|
|
27
|
-
visited: PackageRootVisit[];
|
|
28
|
-
fallbackRejectedBecause: string;
|
|
29
|
-
};
|
|
30
|
-
/** Replace every Unicode control character before bounding a diagnostic value. */
|
|
31
|
-
export declare function boundedDiagnosticValue(value: unknown, maxBytes: number): string;
|
|
32
|
-
/** An unmarked packaging fault that should remain visible in Sentry. */
|
|
33
|
-
export declare class PackageRootResolutionError extends Error {
|
|
34
|
-
readonly diagnostics: PackageRootResolutionDiagnostics;
|
|
35
|
-
constructor(raw: RawDiagnostics);
|
|
36
|
-
}
|
|
37
|
-
export declare function isPackageRootResolutionError(err: unknown): err is PackageRootResolutionError;
|
|
5
|
+
import { PackageRootResolutionError, type PackageRootResolutionDiagnostics } from "./package-root-resolution-error.js";
|
|
6
|
+
export { PackageRootResolutionError, isPackageRootResolutionError, type PackageRootVisit, type PackageRootResolutionDiagnostics, } from "./package-root-resolution-error.js";
|
|
7
|
+
export { boundedDiagnosticValue } from "./package-root-resolution-error.js";
|
|
38
8
|
/**
|
|
39
9
|
* Produce the exact Sentry contexts shape while enforcing a whole-payload cap.
|
|
40
10
|
* The tail is dropped first because nearest directories contain the useful
|
|
@@ -43,5 +13,4 @@ export declare function isPackageRootResolutionError(err: unknown): err is Packa
|
|
|
43
13
|
export declare function packageRootCaptureContext(err: PackageRootResolutionError): {
|
|
44
14
|
package_root_resolution: PackageRootResolutionDiagnostics;
|
|
45
15
|
};
|
|
46
|
-
export {};
|
|
47
16
|
//# sourceMappingURL=package-root-diagnostics.d.ts.map
|
|
@@ -1,90 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bounded
|
|
3
|
-
*
|
|
4
|
-
* These values are sent to Sentry, whose general scrubber deliberately keeps
|
|
5
|
-
* filesystem paths intact. Keep this module the single construction boundary
|
|
6
|
-
* so a malformed install cannot turn an error report into an unbounded or
|
|
7
|
-
* multi-line payload.
|
|
2
|
+
* Bounded Sentry context for failures to locate the CLI package root.
|
|
3
|
+
* The error value itself is kept in a dependency-light module for fast paths.
|
|
8
4
|
*/
|
|
9
5
|
import { beforeSend } from "../sentry-before-send.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const ELLIPSIS = "…";
|
|
13
|
-
const MODULE_PATH_BYTES = 256;
|
|
14
|
-
const START_DIR_BYTES = 256;
|
|
15
|
-
const DIRECTORY_BYTES = 256;
|
|
16
|
-
const MANIFEST_NAME_BYTES = 64;
|
|
17
|
-
const CODE_BYTES = 32;
|
|
18
|
-
const MESSAGE_BYTES = 2048;
|
|
6
|
+
export { PackageRootResolutionError, isPackageRootResolutionError, } from "./package-root-resolution-error.js";
|
|
7
|
+
export { boundedDiagnosticValue } from "./package-root-resolution-error.js";
|
|
19
8
|
const CONTEXT_BYTES = 4096;
|
|
20
|
-
const MAX_VISITED = 12;
|
|
21
|
-
/** Replace every Unicode control character before bounding a diagnostic value. */
|
|
22
|
-
export function boundedDiagnosticValue(value, maxBytes) {
|
|
23
|
-
const normalized = String(value).replace(CONTROL_CHARACTER, REPLACEMENT);
|
|
24
|
-
if (Buffer.byteLength(normalized, "utf8") <= maxBytes)
|
|
25
|
-
return normalized;
|
|
26
|
-
const markerBytes = Buffer.byteLength(ELLIPSIS, "utf8");
|
|
27
|
-
let result = "";
|
|
28
|
-
let used = 0;
|
|
29
|
-
for (const character of normalized) {
|
|
30
|
-
const bytes = Buffer.byteLength(character, "utf8");
|
|
31
|
-
if (used + bytes + markerBytes > maxBytes)
|
|
32
|
-
break;
|
|
33
|
-
result += character;
|
|
34
|
-
used += bytes;
|
|
35
|
-
}
|
|
36
|
-
return result + ELLIPSIS;
|
|
37
|
-
}
|
|
38
|
-
function boundedVisit(visit) {
|
|
39
|
-
return {
|
|
40
|
-
directory: boundedDiagnosticValue(visit.directory, DIRECTORY_BYTES),
|
|
41
|
-
outcome: visit.outcome,
|
|
42
|
-
...(visit.code === undefined
|
|
43
|
-
? {}
|
|
44
|
-
: { code: boundedDiagnosticValue(visit.code, CODE_BYTES) }),
|
|
45
|
-
...(visit.manifestName === undefined
|
|
46
|
-
? {}
|
|
47
|
-
: { manifestName: boundedDiagnosticValue(visit.manifestName, MANIFEST_NAME_BYTES) }),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function boundedVisits(visits) {
|
|
51
|
-
if (visits.length <= MAX_VISITED)
|
|
52
|
-
return visits.map(boundedVisit);
|
|
53
|
-
const shown = visits.slice(0, MAX_VISITED - 1).map(boundedVisit);
|
|
54
|
-
shown.push({
|
|
55
|
-
directory: `${visits.length - shown.length} more omitted`,
|
|
56
|
-
outcome: "omitted",
|
|
57
|
-
});
|
|
58
|
-
return shown;
|
|
59
|
-
}
|
|
60
|
-
function normalizeDiagnostics(raw) {
|
|
61
|
-
return {
|
|
62
|
-
modulePath: boundedDiagnosticValue(raw.modulePath, MODULE_PATH_BYTES),
|
|
63
|
-
startDir: boundedDiagnosticValue(raw.startDir, START_DIR_BYTES),
|
|
64
|
-
strategiesTried: raw.strategiesTried.map((strategy) => boundedDiagnosticValue(strategy, CODE_BYTES)),
|
|
65
|
-
visited: boundedVisits(raw.visited),
|
|
66
|
-
fallbackRejectedBecause: boundedDiagnosticValue(raw.fallbackRejectedBecause, DIRECTORY_BYTES),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
function messageFor(diagnostics) {
|
|
70
|
-
return boundedDiagnosticValue("Could not locate the @indigoai-us/hq-cli package root from " +
|
|
71
|
-
`${diagnostics.modulePath}; manifest walk exhausted from ${diagnostics.startDir}; ` +
|
|
72
|
-
`dist-owner fallback ${diagnostics.fallbackRejectedBecause}.`, MESSAGE_BYTES);
|
|
73
|
-
}
|
|
74
|
-
/** An unmarked packaging fault that should remain visible in Sentry. */
|
|
75
|
-
export class PackageRootResolutionError extends Error {
|
|
76
|
-
diagnostics;
|
|
77
|
-
constructor(raw) {
|
|
78
|
-
const diagnostics = normalizeDiagnostics(raw);
|
|
79
|
-
super(messageFor(diagnostics));
|
|
80
|
-
this.name = "PackageRootResolutionError";
|
|
81
|
-
this.diagnostics = diagnostics;
|
|
82
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
export function isPackageRootResolutionError(err) {
|
|
86
|
-
return err instanceof PackageRootResolutionError;
|
|
87
|
-
}
|
|
88
9
|
/**
|
|
89
10
|
* Produce the exact Sentry contexts shape while enforcing a whole-payload cap.
|
|
90
11
|
* The tail is dropped first because nearest directories contain the useful
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Lightweight packaging error payload; Sentry formatting lives separately. */
|
|
2
|
+
type PackageRootVisitOutcome = "no-manifest" | "eacces" | "eisdir" | "parse-error" | "name-mismatch" | "read-error" | "omitted";
|
|
3
|
+
export type PackageRootVisit = {
|
|
4
|
+
directory: string;
|
|
5
|
+
outcome: PackageRootVisitOutcome;
|
|
6
|
+
code?: string;
|
|
7
|
+
manifestName?: string;
|
|
8
|
+
};
|
|
9
|
+
export type PackageRootResolutionDiagnostics = {
|
|
10
|
+
modulePath: string;
|
|
11
|
+
startDir: string;
|
|
12
|
+
strategiesTried: string[];
|
|
13
|
+
visited: PackageRootVisit[];
|
|
14
|
+
fallbackRejectedBecause: string;
|
|
15
|
+
};
|
|
16
|
+
type RawDiagnostics = {
|
|
17
|
+
modulePath: string;
|
|
18
|
+
startDir: string;
|
|
19
|
+
strategiesTried: string[];
|
|
20
|
+
visited: PackageRootVisit[];
|
|
21
|
+
fallbackRejectedBecause: string;
|
|
22
|
+
};
|
|
23
|
+
/** Replace every Unicode control character before bounding a diagnostic value. */
|
|
24
|
+
export declare function boundedDiagnosticValue(value: unknown, maxBytes: number): string;
|
|
25
|
+
/** An unmarked packaging fault that should remain visible in Sentry. */
|
|
26
|
+
export declare class PackageRootResolutionError extends Error {
|
|
27
|
+
readonly diagnostics: PackageRootResolutionDiagnostics;
|
|
28
|
+
constructor(raw: RawDiagnostics);
|
|
29
|
+
}
|
|
30
|
+
export declare function isPackageRootResolutionError(err: unknown): err is PackageRootResolutionError;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=package-root-resolution-error.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Lightweight packaging error payload; Sentry formatting lives separately. */
|
|
2
|
+
const CONTROL_CHARACTER = /[\p{C}\p{Zl}\p{Zp}]/gu;
|
|
3
|
+
const REPLACEMENT = "?";
|
|
4
|
+
const ELLIPSIS = "…";
|
|
5
|
+
const MODULE_PATH_BYTES = 256;
|
|
6
|
+
const START_DIR_BYTES = 256;
|
|
7
|
+
const DIRECTORY_BYTES = 256;
|
|
8
|
+
const MANIFEST_NAME_BYTES = 64;
|
|
9
|
+
const CODE_BYTES = 32;
|
|
10
|
+
const MESSAGE_BYTES = 2048;
|
|
11
|
+
const MAX_VISITED = 12;
|
|
12
|
+
/** Replace every Unicode control character before bounding a diagnostic value. */
|
|
13
|
+
export function boundedDiagnosticValue(value, maxBytes) {
|
|
14
|
+
const normalized = String(value).replace(CONTROL_CHARACTER, REPLACEMENT);
|
|
15
|
+
if (Buffer.byteLength(normalized, "utf8") <= maxBytes)
|
|
16
|
+
return normalized;
|
|
17
|
+
const markerBytes = Buffer.byteLength(ELLIPSIS, "utf8");
|
|
18
|
+
let result = "";
|
|
19
|
+
let used = 0;
|
|
20
|
+
for (const character of normalized) {
|
|
21
|
+
const bytes = Buffer.byteLength(character, "utf8");
|
|
22
|
+
if (used + bytes + markerBytes > maxBytes)
|
|
23
|
+
break;
|
|
24
|
+
result += character;
|
|
25
|
+
used += bytes;
|
|
26
|
+
}
|
|
27
|
+
return result + ELLIPSIS;
|
|
28
|
+
}
|
|
29
|
+
function boundedVisit(visit) {
|
|
30
|
+
return {
|
|
31
|
+
directory: boundedDiagnosticValue(visit.directory, DIRECTORY_BYTES),
|
|
32
|
+
outcome: visit.outcome,
|
|
33
|
+
...(visit.code === undefined
|
|
34
|
+
? {}
|
|
35
|
+
: { code: boundedDiagnosticValue(visit.code, CODE_BYTES) }),
|
|
36
|
+
...(visit.manifestName === undefined
|
|
37
|
+
? {}
|
|
38
|
+
: { manifestName: boundedDiagnosticValue(visit.manifestName, MANIFEST_NAME_BYTES) }),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function boundedVisits(visits) {
|
|
42
|
+
if (visits.length <= MAX_VISITED)
|
|
43
|
+
return visits.map(boundedVisit);
|
|
44
|
+
const shown = visits.slice(0, MAX_VISITED - 1).map(boundedVisit);
|
|
45
|
+
shown.push({
|
|
46
|
+
directory: String(visits.length - shown.length) + " more omitted",
|
|
47
|
+
outcome: "omitted",
|
|
48
|
+
});
|
|
49
|
+
return shown;
|
|
50
|
+
}
|
|
51
|
+
function normalizeDiagnostics(raw) {
|
|
52
|
+
return {
|
|
53
|
+
modulePath: boundedDiagnosticValue(raw.modulePath, MODULE_PATH_BYTES),
|
|
54
|
+
startDir: boundedDiagnosticValue(raw.startDir, START_DIR_BYTES),
|
|
55
|
+
strategiesTried: raw.strategiesTried.map((strategy) => boundedDiagnosticValue(strategy, CODE_BYTES)),
|
|
56
|
+
visited: boundedVisits(raw.visited),
|
|
57
|
+
fallbackRejectedBecause: boundedDiagnosticValue(raw.fallbackRejectedBecause, DIRECTORY_BYTES),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function messageFor(diagnostics) {
|
|
61
|
+
return boundedDiagnosticValue("Could not locate the @indigoai-us/hq-cli package root from " +
|
|
62
|
+
diagnostics.modulePath + "; manifest walk exhausted from " + diagnostics.startDir +
|
|
63
|
+
"; dist-owner fallback " + diagnostics.fallbackRejectedBecause + ".", MESSAGE_BYTES);
|
|
64
|
+
}
|
|
65
|
+
/** An unmarked packaging fault that should remain visible in Sentry. */
|
|
66
|
+
export class PackageRootResolutionError extends Error {
|
|
67
|
+
diagnostics;
|
|
68
|
+
constructor(raw) {
|
|
69
|
+
const diagnostics = normalizeDiagnostics(raw);
|
|
70
|
+
super(messageFor(diagnostics));
|
|
71
|
+
this.name = "PackageRootResolutionError";
|
|
72
|
+
this.diagnostics = diagnostics;
|
|
73
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function isPackageRootResolutionError(err) {
|
|
77
|
+
return err instanceof PackageRootResolutionError;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=package-root-resolution-error.js.map
|
package/dist/utils/vault-api.js
CHANGED
|
@@ -111,14 +111,20 @@ function identityLookupTimeoutMs() {
|
|
|
111
111
|
* exits on Linux CI. Reading once + re-wrapping fully drains the stream.
|
|
112
112
|
*/
|
|
113
113
|
async function peekPlanLimitStatus(response, options = {}) {
|
|
114
|
+
let bufferedBody;
|
|
114
115
|
try {
|
|
115
116
|
if (!response.ok)
|
|
116
117
|
return response;
|
|
118
|
+
// Fetch forbids bodies on these statuses. Leave the original response
|
|
119
|
+
// untouched instead of consuming it and trying to reconstruct it later.
|
|
120
|
+
if ([101, 103, 204, 205, 304].includes(response.status))
|
|
121
|
+
return response;
|
|
117
122
|
const ct = response.headers.get('content-type') ?? '';
|
|
118
123
|
if (!ct.includes('application/json') && !ct.includes('+json')) {
|
|
119
124
|
return response;
|
|
120
125
|
}
|
|
121
126
|
const buf = await response.arrayBuffer();
|
|
127
|
+
bufferedBody = buf;
|
|
122
128
|
try {
|
|
123
129
|
const text = new TextDecoder().decode(buf);
|
|
124
130
|
try {
|
|
@@ -141,7 +147,16 @@ async function peekPlanLimitStatus(response, options = {}) {
|
|
|
141
147
|
}
|
|
142
148
|
catch (err) {
|
|
143
149
|
// Only secret-load callers need the body-read error for offline fallback.
|
|
144
|
-
// Other endpoints retain the
|
|
150
|
+
// Other endpoints retain the best-effort peek behavior only while the
|
|
151
|
+
// original body is still readable.
|
|
152
|
+
if (bufferedBody !== undefined) {
|
|
153
|
+
// Retry with safe metadata: statusText can itself make Response
|
|
154
|
+
// construction throw after the original stream has already been drained.
|
|
155
|
+
return new Response(bufferedBody, {
|
|
156
|
+
status: response.status,
|
|
157
|
+
headers: response.headers,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
145
160
|
if (options.throwOnBodyReadError)
|
|
146
161
|
throw err;
|
|
147
162
|
return response;
|