@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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root resolution for CLI-hosted HQ scaffold code.
|
|
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
|
+
/** Where bundled scaffold assets live, relative to the package root. */
|
|
29
|
+
export declare const BUNDLED_ASSET_DIR: string;
|
|
30
|
+
/**
|
|
31
|
+
* Does this directory look like an HQ tree root?
|
|
32
|
+
*
|
|
33
|
+
* `companies/` is the load-bearing marker — it is the source of truth for
|
|
34
|
+
* tenants and exists in every real install. The second clause tolerates the
|
|
35
|
+
* three shapes a tree can legitimately be in: a full install, a scaffold
|
|
36
|
+
* checkout without a personal overlay, and a personal-only overlay.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isHqRoot(dir: string): boolean;
|
|
39
|
+
export type PackageRootFs = Pick<typeof fs, "existsSync" | "readFileSync" | "realpathSync" | "statSync">;
|
|
40
|
+
export type PackageRootResolverOptions = {
|
|
41
|
+
/** Directory containing the currently-running compiled module. */
|
|
42
|
+
moduleDir: string;
|
|
43
|
+
/** Full module path used only in safe diagnostic output. */
|
|
44
|
+
modulePath?: string;
|
|
45
|
+
/** Injectable filesystem seam for resolver tests. */
|
|
46
|
+
fs?: PackageRootFs;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Make a resolver for this package's root. It caches only a successful lookup:
|
|
50
|
+
* a concurrently replaced manifest must be retried by the next command, not
|
|
51
|
+
* converted into a permanent process failure.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createPackageRootResolver(options: PackageRootResolverOptions): () => string;
|
|
54
|
+
/**
|
|
55
|
+
* This package's installed root — the directory holding its `package.json`.
|
|
56
|
+
*
|
|
57
|
+
* Walks up from the compiled module (`dist/utils/hq-roots.js`) until it finds a
|
|
58
|
+
* `package.json` whose name matches, rather than hard-coding `..` hops, so the
|
|
59
|
+
* answer survives a change in output layout.
|
|
60
|
+
*/
|
|
61
|
+
export declare function packageRoot(): string;
|
|
62
|
+
/**
|
|
63
|
+
* Derive this package's installed root from the running module's OWN path using
|
|
64
|
+
* STRING operations only — no `readFileSync`, `realpathSync`, or `existsSync`.
|
|
65
|
+
*
|
|
66
|
+
* {@link packageRoot} must read `<dir>/package.json` and `realpathSync` the
|
|
67
|
+
* module directory to answer, so it THROWS in exactly the situation that most
|
|
68
|
+
* needs an answer: a concurrent global install has renamed the installed
|
|
69
|
+
* package directory aside (`.hq-cli-<rand>`) and is re-extracting it file by
|
|
70
|
+
* file, so both the manifest walk and the dist-owner fallback fail on a
|
|
71
|
+
* directory that is momentarily gone. This walks the compiled module's
|
|
72
|
+
* ancestors as strings and returns the DEEPEST one whose trailing segments are
|
|
73
|
+
* `node_modules/@indigoai-us/hq-cli`, which stays correct while that directory
|
|
74
|
+
* does not exist on disk.
|
|
75
|
+
*
|
|
76
|
+
* It is a FALLBACK only — {@link packageRoot} validates the manifest name and
|
|
77
|
+
* is authoritative whenever it succeeds. Returns null for a dev checkout or any
|
|
78
|
+
* layout without that segment triple, so a caller that falls back to it can
|
|
79
|
+
* only ever degrade to today's behaviour (no resolution), never widen it.
|
|
80
|
+
*
|
|
81
|
+
* The deepest match is deliberate: a nested `node_modules/@indigoai-us/hq-cli`
|
|
82
|
+
* inside another package resolves to ITSELF (the copy the module belongs to),
|
|
83
|
+
* never an outer decoy. Case is folded only for a Windows-shaped path, matching
|
|
84
|
+
* the POSIX case-sensitivity the rest of this module assumes.
|
|
85
|
+
*/
|
|
86
|
+
export declare function stringDerivedPackageRoot(moduleFilePath?: string): string | null;
|
|
87
|
+
export type LiveRootOptions = {
|
|
88
|
+
/** Explicit `--hq-root` value. Highest precedence. */
|
|
89
|
+
hqRoot?: string;
|
|
90
|
+
/** Start the walk-up here instead of `process.cwd()`. Tests only. */
|
|
91
|
+
cwd?: string;
|
|
92
|
+
/** Environment to read. Tests only. */
|
|
93
|
+
env?: NodeJS.ProcessEnv;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Resolve the user's live HQ installation.
|
|
97
|
+
*
|
|
98
|
+
* Precedence: `--hq-root` → `HQ_ROOT` → `CLAUDE_PROJECT_DIR` → walk up from cwd.
|
|
99
|
+
*
|
|
100
|
+
* ## An explicit root is trusted; a discovered one must be recognized
|
|
101
|
+
*
|
|
102
|
+
* The FIRST explicit candidate present wins outright — there is no fall-through
|
|
103
|
+
* past it, because silently operating on a different tree than the caller named
|
|
104
|
+
* is the worst available outcome.
|
|
105
|
+
*
|
|
106
|
+
* That explicit candidate is checked only for the two things that are always
|
|
107
|
+
* wrong: it must be a directory, and it must not be inside {@link packageRoot}
|
|
108
|
+
* (that is this package, not anyone's HQ). It is deliberately NOT required to
|
|
109
|
+
* satisfy {@link isHqRoot}.
|
|
110
|
+
*
|
|
111
|
+
* Requiring markers here was tried and was wrong: the scripts these roots feed
|
|
112
|
+
* accepted any `HQ_ROOT` before they moved, and several callers legitimately pass
|
|
113
|
+
* a minimal tree — e.g. `rebuild-threads-index` against a fixture holding nothing
|
|
114
|
+
* but `workspace/threads/`. Enforcing markers on an explicit value turned a
|
|
115
|
+
* relocation into a silent contract change and broke those callers.
|
|
116
|
+
*
|
|
117
|
+
* The cwd walk is the opposite case: there the marker check is the only thing
|
|
118
|
+
* that can identify a root at all, so it stays mandatory.
|
|
119
|
+
*/
|
|
120
|
+
export declare function resolveLiveRoot(opts?: LiveRootOptions): string;
|
|
121
|
+
/**
|
|
122
|
+
* Absolute path to a scaffold asset bundled in this package.
|
|
123
|
+
*
|
|
124
|
+
* Refuses to resolve outside the asset directory so a manifest entry can never
|
|
125
|
+
* be coaxed into executing an arbitrary path, and fails loudly when the asset
|
|
126
|
+
* is missing — an absent asset means the packlist dropped it, which is a
|
|
127
|
+
* packaging bug worth surfacing rather than a runtime fallback.
|
|
128
|
+
*/
|
|
129
|
+
export declare function resolveBundledAsset(relativePath: string): string;
|
|
130
|
+
//# sourceMappingURL=hq-roots-core.d.ts.map
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root resolution for CLI-hosted HQ scaffold code.
|
|
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-resolution-error.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(new URL("./hq-roots.js", 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
|
+
}
|
|
354
|
+
//# sourceMappingURL=hq-roots-core.js.map
|
package/dist/utils/hq-roots.d.ts
CHANGED
|
@@ -1,130 +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
|
-
/** Where bundled scaffold assets live, relative to the package root. */
|
|
29
|
-
export declare const BUNDLED_ASSET_DIR: string;
|
|
30
|
-
/**
|
|
31
|
-
* Does this directory look like an HQ tree root?
|
|
32
|
-
*
|
|
33
|
-
* `companies/` is the load-bearing marker — it is the source of truth for
|
|
34
|
-
* tenants and exists in every real install. The second clause tolerates the
|
|
35
|
-
* three shapes a tree can legitimately be in: a full install, a scaffold
|
|
36
|
-
* checkout without a personal overlay, and a personal-only overlay.
|
|
37
|
-
*/
|
|
38
|
-
export declare function isHqRoot(dir: string): boolean;
|
|
39
|
-
export type PackageRootFs = Pick<typeof fs, "existsSync" | "readFileSync" | "realpathSync" | "statSync">;
|
|
40
|
-
export type PackageRootResolverOptions = {
|
|
41
|
-
/** Directory containing the currently-running compiled module. */
|
|
42
|
-
moduleDir: string;
|
|
43
|
-
/** Full module path used only in safe diagnostic output. */
|
|
44
|
-
modulePath?: string;
|
|
45
|
-
/** Injectable filesystem seam for resolver tests. */
|
|
46
|
-
fs?: PackageRootFs;
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* Make a resolver for this package's root. It caches only a successful lookup:
|
|
50
|
-
* a concurrently replaced manifest must be retried by the next command, not
|
|
51
|
-
* converted into a permanent process failure.
|
|
52
|
-
*/
|
|
53
|
-
export declare function createPackageRootResolver(options: PackageRootResolverOptions): () => string;
|
|
54
|
-
/**
|
|
55
|
-
* This package's installed root — the directory holding its `package.json`.
|
|
56
|
-
*
|
|
57
|
-
* Walks up from the compiled module (`dist/utils/hq-roots.js`) until it finds a
|
|
58
|
-
* `package.json` whose name matches, rather than hard-coding `..` hops, so the
|
|
59
|
-
* answer survives a change in output layout.
|
|
60
|
-
*/
|
|
61
|
-
export declare function packageRoot(): string;
|
|
62
|
-
/**
|
|
63
|
-
* Derive this package's installed root from the running module's OWN path using
|
|
64
|
-
* STRING operations only — no `readFileSync`, `realpathSync`, or `existsSync`.
|
|
65
|
-
*
|
|
66
|
-
* {@link packageRoot} must read `<dir>/package.json` and `realpathSync` the
|
|
67
|
-
* module directory to answer, so it THROWS in exactly the situation that most
|
|
68
|
-
* needs an answer: a concurrent global install has renamed the installed
|
|
69
|
-
* package directory aside (`.hq-cli-<rand>`) and is re-extracting it file by
|
|
70
|
-
* file, so both the manifest walk and the dist-owner fallback fail on a
|
|
71
|
-
* directory that is momentarily gone. This walks the compiled module's
|
|
72
|
-
* ancestors as strings and returns the DEEPEST one whose trailing segments are
|
|
73
|
-
* `node_modules/@indigoai-us/hq-cli`, which stays correct while that directory
|
|
74
|
-
* does not exist on disk.
|
|
75
|
-
*
|
|
76
|
-
* It is a FALLBACK only — {@link packageRoot} validates the manifest name and
|
|
77
|
-
* is authoritative whenever it succeeds. Returns null for a dev checkout or any
|
|
78
|
-
* layout without that segment triple, so a caller that falls back to it can
|
|
79
|
-
* only ever degrade to today's behaviour (no resolution), never widen it.
|
|
80
|
-
*
|
|
81
|
-
* The deepest match is deliberate: a nested `node_modules/@indigoai-us/hq-cli`
|
|
82
|
-
* inside another package resolves to ITSELF (the copy the module belongs to),
|
|
83
|
-
* never an outer decoy. Case is folded only for a Windows-shaped path, matching
|
|
84
|
-
* the POSIX case-sensitivity the rest of this module assumes.
|
|
85
|
-
*/
|
|
86
|
-
export declare function stringDerivedPackageRoot(moduleFilePath?: string): string | null;
|
|
87
|
-
export type LiveRootOptions = {
|
|
88
|
-
/** Explicit `--hq-root` value. Highest precedence. */
|
|
89
|
-
hqRoot?: string;
|
|
90
|
-
/** Start the walk-up here instead of `process.cwd()`. Tests only. */
|
|
91
|
-
cwd?: string;
|
|
92
|
-
/** Environment to read. Tests only. */
|
|
93
|
-
env?: NodeJS.ProcessEnv;
|
|
94
|
-
};
|
|
95
|
-
/**
|
|
96
|
-
* Resolve the user's live HQ installation.
|
|
97
|
-
*
|
|
98
|
-
* Precedence: `--hq-root` → `HQ_ROOT` → `CLAUDE_PROJECT_DIR` → walk up from cwd.
|
|
99
|
-
*
|
|
100
|
-
* ## An explicit root is trusted; a discovered one must be recognized
|
|
101
|
-
*
|
|
102
|
-
* The FIRST explicit candidate present wins outright — there is no fall-through
|
|
103
|
-
* past it, because silently operating on a different tree than the caller named
|
|
104
|
-
* is the worst available outcome.
|
|
105
|
-
*
|
|
106
|
-
* That explicit candidate is checked only for the two things that are always
|
|
107
|
-
* wrong: it must be a directory, and it must not be inside {@link packageRoot}
|
|
108
|
-
* (that is this package, not anyone's HQ). It is deliberately NOT required to
|
|
109
|
-
* satisfy {@link isHqRoot}.
|
|
110
|
-
*
|
|
111
|
-
* Requiring markers here was tried and was wrong: the scripts these roots feed
|
|
112
|
-
* accepted any `HQ_ROOT` before they moved, and several callers legitimately pass
|
|
113
|
-
* a minimal tree — e.g. `rebuild-threads-index` against a fixture holding nothing
|
|
114
|
-
* but `workspace/threads/`. Enforcing markers on an explicit value turned a
|
|
115
|
-
* relocation into a silent contract change and broke those callers.
|
|
116
|
-
*
|
|
117
|
-
* The cwd walk is the opposite case: there the marker check is the only thing
|
|
118
|
-
* that can identify a root at all, so it stays mandatory.
|
|
119
|
-
*/
|
|
120
|
-
export declare function resolveLiveRoot(opts?: LiveRootOptions): string;
|
|
121
|
-
/**
|
|
122
|
-
* Absolute path to a scaffold asset bundled in this package.
|
|
123
|
-
*
|
|
124
|
-
* Refuses to resolve outside the asset directory so a manifest entry can never
|
|
125
|
-
* be coaxed into executing an arbitrary path, and fails loudly when the asset
|
|
126
|
-
* is missing — an absent asset means the packlist dropped it, which is a
|
|
127
|
-
* packaging bug worth surfacing rather than a runtime fallback.
|
|
128
|
-
*/
|
|
129
|
-
export declare function resolveBundledAsset(relativePath: string): string;
|
|
1
|
+
/** Compatibility entrypoint for the full command graph. */
|
|
2
|
+
export * from "./hq-roots-core.js";
|
|
130
3
|
//# sourceMappingURL=hq-roots.d.ts.map
|