@indigoai-us/hq-cli 5.326.0 → 5.327.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 +5 -0
- 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-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/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/package.json +1 -1
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
|
-
import { ensureCognitoToken, isMachineIdentity } from "../../../utils/cognito-session.js";
|
|
8
|
-
import { DEFAULT_VAULT_API_URL } from "../../../utils/cognito-session.js";
|
|
5
|
+
import { McpEndpointError, resolveHqMcpEndpoint as resolveEndpoint } from "../mcp-endpoint.js";
|
|
9
6
|
import { ensurePrivateDir, openPrivateAppend } from "../paths.js";
|
|
10
7
|
export class McpOpError extends Error {
|
|
11
8
|
code;
|
|
@@ -20,28 +17,28 @@ export function jsonResult(value) {
|
|
|
20
17
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }], structuredContent: structured };
|
|
21
18
|
}
|
|
22
19
|
export const HQ_MCP_HTTP_TIMEOUT_MS = 30_000;
|
|
23
|
-
/** Derive the hosted MCP sibling from the same hq-pro URL used by hq-cli. */
|
|
24
20
|
export function resolveHqMcpEndpoint(env = process.env) {
|
|
25
|
-
const apiUrl = env.HQ_VAULT_API_URL?.trim() || DEFAULT_VAULT_API_URL;
|
|
26
|
-
let api;
|
|
27
21
|
try {
|
|
28
|
-
|
|
22
|
+
return resolveEndpoint(env);
|
|
29
23
|
}
|
|
30
|
-
catch {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
throw new McpOpError("hq_mcp_config_error", "Hosted hq-mcp requires an HTTPS hqapi host in HQ_VAULT_API_URL.");
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof McpEndpointError)
|
|
26
|
+
throw new McpOpError(error.code, error.message);
|
|
27
|
+
throw error;
|
|
35
28
|
}
|
|
36
|
-
api.hostname = `hq-mcp.${api.hostname.slice("hqapi.".length)}`;
|
|
37
|
-
api.pathname = "/mcp";
|
|
38
|
-
api.search = "";
|
|
39
|
-
api.hash = "";
|
|
40
|
-
return api;
|
|
41
29
|
}
|
|
42
30
|
async function defaultMcpToken() {
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
const { ensureCognitoToken, resolveCognitoTokenSource } = await import("../../../utils/cognito-session.js");
|
|
32
|
+
if (resolveCognitoTokenSource() !== "machine") {
|
|
33
|
+
const { getMcpAudienceToken, McpOAuthError } = await import("../mcp-oauth.js");
|
|
34
|
+
try {
|
|
35
|
+
return await getMcpAudienceToken();
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error instanceof McpOAuthError)
|
|
39
|
+
throw new McpOpError(error.code, error.message);
|
|
40
|
+
throw new McpOpError("hq_mcp_auth_required", "Hosted MCP sign-in is required. Run `hq mcp login` and retry.");
|
|
41
|
+
}
|
|
45
42
|
}
|
|
46
43
|
try {
|
|
47
44
|
// Send this box's existing machine token. The hosted hq-mcp audience check
|
|
@@ -82,12 +79,7 @@ export class HqMcpBridge {
|
|
|
82
79
|
if (!tools.some((tool) => tool.name === name)) {
|
|
83
80
|
throw new McpOpError("hq_mcp_tool_unavailable", `Hosted hq-mcp does not expose ${name}.`);
|
|
84
81
|
}
|
|
85
|
-
|
|
86
|
-
return await client.callTool({ name, arguments: args });
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
throw this.toSafeError(error, token);
|
|
90
|
-
}
|
|
82
|
+
return await client.callTool({ name, arguments: args });
|
|
91
83
|
}, timeoutMs);
|
|
92
84
|
}
|
|
93
85
|
close() {
|
|
@@ -104,11 +96,17 @@ export class HqMcpBridge {
|
|
|
104
96
|
let timeout;
|
|
105
97
|
let timedOut = false;
|
|
106
98
|
const abortController = new AbortController();
|
|
99
|
+
let httpErrorClass;
|
|
107
100
|
try {
|
|
101
|
+
const [clientModule, httpModule] = await Promise.all([
|
|
102
|
+
import("@modelcontextprotocol/sdk/client/index.js"),
|
|
103
|
+
import("@modelcontextprotocol/sdk/client/streamableHttp.js"),
|
|
104
|
+
]);
|
|
105
|
+
httpErrorClass = httpModule.StreamableHTTPError;
|
|
108
106
|
token = await this.getToken();
|
|
109
107
|
if (!token.trim())
|
|
110
108
|
throw new McpOpError("hq_mcp_auth_required", "An hq-mcp audience token is required.");
|
|
111
|
-
transport = new StreamableHTTPClientTransport(this.endpoint, {
|
|
109
|
+
transport = new httpModule.StreamableHTTPClientTransport(this.endpoint, {
|
|
112
110
|
requestInit: {
|
|
113
111
|
headers: { Authorization: `Bearer ${token}` },
|
|
114
112
|
},
|
|
@@ -117,7 +115,7 @@ export class HqMcpBridge {
|
|
|
117
115
|
signal: init?.signal ? AbortSignal.any([abortController.signal, init.signal]) : abortController.signal,
|
|
118
116
|
}),
|
|
119
117
|
});
|
|
120
|
-
client = new Client({ name: "hqd", version: "1" }, { capabilities: {} });
|
|
118
|
+
client = new clientModule.Client({ name: "hqd", version: "1" }, { capabilities: {} });
|
|
121
119
|
this.activeClients.add(client);
|
|
122
120
|
timeout = setTimeout(() => {
|
|
123
121
|
timedOut = true;
|
|
@@ -133,7 +131,7 @@ export class HqMcpBridge {
|
|
|
133
131
|
if (timedOut) {
|
|
134
132
|
throw new McpOpError("hq_mcp_timeout", `Hosted hq-mcp request did not finish within ${timeoutMs}ms.`);
|
|
135
133
|
}
|
|
136
|
-
throw this.toSafeError(error, token);
|
|
134
|
+
throw this.toSafeError(error, token, httpErrorClass);
|
|
137
135
|
}
|
|
138
136
|
finally {
|
|
139
137
|
if (timeout)
|
|
@@ -157,10 +155,12 @@ export class HqMcpBridge {
|
|
|
157
155
|
}
|
|
158
156
|
}
|
|
159
157
|
}
|
|
160
|
-
toSafeError(error, token) {
|
|
158
|
+
toSafeError(error, token, StreamableHTTPErrorClass) {
|
|
161
159
|
if (error instanceof McpOpError)
|
|
162
160
|
return error;
|
|
163
|
-
|
|
161
|
+
if (error instanceof McpEndpointError)
|
|
162
|
+
return new McpOpError(error.code, error.message);
|
|
163
|
+
const status = StreamableHTTPErrorClass && error instanceof StreamableHTTPErrorClass ? error.code : undefined;
|
|
164
164
|
if (status === 401 || status === 403) {
|
|
165
165
|
return new McpOpError("hq_mcp_auth_required", "Hosted hq-mcp rejected this token. Use the DCR plus PKCE person flow or configure the hq-mcp machine client ID on this box.");
|
|
166
166
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Fast scope resolver; it loads manifest and HQ-root code only for slug scopes. */
|
|
2
|
+
import { type ProcessFlagCompanyContext } from "./process-flag-company-context-light.js";
|
|
3
|
+
export declare function resolveFastProcessFlagCompanyContext(env?: NodeJS.ProcessEnv): Promise<ProcessFlagCompanyContext>;
|
|
4
|
+
//# sourceMappingURL=process-flag-company-context-fast.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Fast scope resolver; it loads manifest and HQ-root code only for slug scopes. */
|
|
2
|
+
import { normalizedCompanyUid, resolveLightProcessFlagCompanyContext, } from "./process-flag-company-context-light.js";
|
|
3
|
+
export async function resolveFastProcessFlagCompanyContext(env = process.env) {
|
|
4
|
+
const context = resolveLightProcessFlagCompanyContext(env);
|
|
5
|
+
let companyUid = context.companyUid;
|
|
6
|
+
if (!companyUid && context.companySlug) {
|
|
7
|
+
const [{ resolveDefaultHqRoot }, { readCompaniesManifestMap }] = await Promise.all([
|
|
8
|
+
import("../utils/hq-root-resolver.js"),
|
|
9
|
+
import("./work-context/repo-remote.js"),
|
|
10
|
+
]);
|
|
11
|
+
const hqRoot = resolveDefaultHqRoot({ env });
|
|
12
|
+
const manifestUid = readCompaniesManifestMap(hqRoot)?.[context.companySlug]?.cloud_uid;
|
|
13
|
+
companyUid = typeof manifestUid === "string" ? normalizedCompanyUid(manifestUid) : undefined;
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
...(companyUid ? { companyUid } : {}),
|
|
17
|
+
...(context.companySlug ? { companySlug: context.companySlug } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=process-flag-company-context-fast.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Lightweight process company context shared by full and fast flag readers. */
|
|
2
|
+
export interface ProcessFlagCompanyContext {
|
|
3
|
+
companyUid?: string;
|
|
4
|
+
companySlug?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function normalizedCompanyUid(value: string | undefined): string | undefined;
|
|
7
|
+
/** Resolve explicit, machine, or bound-session company scope without defaults. */
|
|
8
|
+
export declare function resolveLightProcessFlagCompanyContext(env?: NodeJS.ProcessEnv): ProcessFlagCompanyContext;
|
|
9
|
+
//# sourceMappingURL=process-flag-company-context-light.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Lightweight process company context shared by full and fast flag readers. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
|
|
6
|
+
const COMPANY_SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
7
|
+
const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
8
|
+
const DEFAULT_AGENT_IDENTITY_FILE = "/var/lib/hq-agent/identity.json";
|
|
9
|
+
export function normalizedCompanyUid(value) {
|
|
10
|
+
const candidate = value?.trim() ?? "";
|
|
11
|
+
return COMPANY_UID_RE.test(candidate) ? candidate : undefined;
|
|
12
|
+
}
|
|
13
|
+
function normalizedCompanySlug(value) {
|
|
14
|
+
const candidate = value?.trim() ?? "";
|
|
15
|
+
return COMPANY_SLUG_RE.test(candidate) ? candidate : undefined;
|
|
16
|
+
}
|
|
17
|
+
function record(value) {
|
|
18
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
19
|
+
? value
|
|
20
|
+
: undefined;
|
|
21
|
+
}
|
|
22
|
+
function readAgentIdentity(env) {
|
|
23
|
+
const configured = env.HQ_AGENT_IDENTITY_FILE;
|
|
24
|
+
const file = configured === undefined ? DEFAULT_AGENT_IDENTITY_FILE : configured.trim();
|
|
25
|
+
if (!file)
|
|
26
|
+
return undefined;
|
|
27
|
+
try {
|
|
28
|
+
const identity = record(JSON.parse(fs.readFileSync(file, "utf8")));
|
|
29
|
+
const companyUid = normalizedCompanyUid(typeof identity?.companyUid === "string" ? identity.companyUid : undefined);
|
|
30
|
+
const companySlug = normalizedCompanySlug(typeof identity?.companySlug === "string" ? identity.companySlug : undefined);
|
|
31
|
+
return companyUid || companySlug ? {
|
|
32
|
+
...(companyUid ? { companyUid } : {}),
|
|
33
|
+
...(companySlug ? { companySlug } : {}),
|
|
34
|
+
} : undefined;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function readSessionCompany(env) {
|
|
41
|
+
const sessionId = env.HQ_SESSION_ID?.trim();
|
|
42
|
+
if (!sessionId || sessionId.includes("..") || !SESSION_ID_RE.test(sessionId))
|
|
43
|
+
return undefined;
|
|
44
|
+
const contextRoot = env.HQ_WORK_CONTEXT_ROOT?.trim() || path.join(os.homedir(), ".hq", "work-context");
|
|
45
|
+
try {
|
|
46
|
+
const sessionPath = path.join(contextRoot, "sessions", sessionId + ".json");
|
|
47
|
+
const session = record(JSON.parse(fs.readFileSync(sessionPath, "utf8")));
|
|
48
|
+
if (session?.sessionId !== sessionId ||
|
|
49
|
+
session.contextStatus !== "bound" ||
|
|
50
|
+
session.companyConfidence === "device_default")
|
|
51
|
+
return undefined;
|
|
52
|
+
const companyUid = normalizedCompanyUid(typeof session.companyUid === "string" ? session.companyUid : undefined);
|
|
53
|
+
const companySlug = normalizedCompanySlug(typeof session.companySlug === "string" ? session.companySlug : undefined);
|
|
54
|
+
return companyUid || companySlug ? {
|
|
55
|
+
...(companyUid ? { companyUid } : {}),
|
|
56
|
+
...(companySlug ? { companySlug } : {}),
|
|
57
|
+
} : undefined;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Resolve explicit, machine, or bound-session company scope without defaults. */
|
|
64
|
+
export function resolveLightProcessFlagCompanyContext(env = process.env) {
|
|
65
|
+
const explicitUid = normalizedCompanyUid(env.HQ_COMPANY_UID);
|
|
66
|
+
const explicitSlug = normalizedCompanySlug(env.HQ_COMPANY_SLUG);
|
|
67
|
+
if (explicitUid || explicitSlug) {
|
|
68
|
+
return { companyUid: explicitUid, companySlug: explicitSlug };
|
|
69
|
+
}
|
|
70
|
+
const spawnValue = env.HQ_SPAWN_COMPANY?.trim();
|
|
71
|
+
if (spawnValue && COMPANY_UID_RE.test(spawnValue))
|
|
72
|
+
return { companyUid: spawnValue };
|
|
73
|
+
const spawnSlug = normalizedCompanySlug(spawnValue);
|
|
74
|
+
if (spawnSlug)
|
|
75
|
+
return { companySlug: spawnSlug };
|
|
76
|
+
const identity = readAgentIdentity(env);
|
|
77
|
+
if (identity)
|
|
78
|
+
return identity;
|
|
79
|
+
return readSessionCompany(env) ?? {};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=process-flag-company-context-light.js.map
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/** Company scope shared by the full flag registry and its fast-path reader. */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
companySlug?: string;
|
|
5
|
-
}
|
|
2
|
+
import { type ProcessFlagCompanyContext } from "./process-flag-company-context-light.js";
|
|
3
|
+
export type { ProcessFlagCompanyContext } from "./process-flag-company-context-light.js";
|
|
6
4
|
/** Resolve the same company identity the process-wide flag reader uses. */
|
|
7
5
|
export declare function resolveProcessFlagCompanyContext(env?: NodeJS.ProcessEnv): ProcessFlagCompanyContext;
|
|
8
6
|
//# sourceMappingURL=process-flag-company-context.d.ts.map
|
|
@@ -1,51 +1,7 @@
|
|
|
1
1
|
/** Company scope shared by the full flag registry and its fast-path reader. */
|
|
2
|
-
import { readMachineCompanyBinding } from "./work-context/company.js";
|
|
3
|
-
import { readSessionState } from "./work-context/state.js";
|
|
4
|
-
import { workContextRoot } from "./work-context/paths.js";
|
|
5
2
|
import { readCompaniesManifestMap } from "./work-context/repo-remote.js";
|
|
6
3
|
import { resolveDefaultHqRoot } from "../utils/hq-root-resolver.js";
|
|
7
|
-
|
|
8
|
-
const COMPANY_SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
9
|
-
function normalizedCompanyUid(value) {
|
|
10
|
-
const candidate = value?.trim() ?? "";
|
|
11
|
-
return COMPANY_UID_RE.test(candidate) ? candidate : undefined;
|
|
12
|
-
}
|
|
13
|
-
function normalizedCompanySlug(value) {
|
|
14
|
-
const candidate = value?.trim() ?? "";
|
|
15
|
-
return COMPANY_SLUG_RE.test(candidate) ? candidate : undefined;
|
|
16
|
-
}
|
|
17
|
-
/** Resolve explicit, machine, or bound-session company scope without defaults. */
|
|
18
|
-
function processCompanyContext(env = process.env) {
|
|
19
|
-
const explicitUid = normalizedCompanyUid(env.HQ_COMPANY_UID);
|
|
20
|
-
const explicitSlug = normalizedCompanySlug(env.HQ_COMPANY_SLUG);
|
|
21
|
-
if (explicitUid || explicitSlug) {
|
|
22
|
-
return { companyUid: explicitUid, companySlug: explicitSlug };
|
|
23
|
-
}
|
|
24
|
-
const machine = readMachineCompanyBinding(env);
|
|
25
|
-
const spawnValue = env.HQ_SPAWN_COMPANY?.trim();
|
|
26
|
-
const spawnSlug = normalizedCompanySlug(spawnValue);
|
|
27
|
-
const machineContext = spawnValue && COMPANY_UID_RE.test(spawnValue)
|
|
28
|
-
? { companyUid: spawnValue }
|
|
29
|
-
: spawnSlug
|
|
30
|
-
? { companySlug: spawnSlug }
|
|
31
|
-
: {
|
|
32
|
-
companyUid: normalizedCompanyUid(machine?.uid),
|
|
33
|
-
companySlug: normalizedCompanySlug(machine?.slug),
|
|
34
|
-
};
|
|
35
|
-
if (machineContext.companyUid || machineContext.companySlug)
|
|
36
|
-
return machineContext;
|
|
37
|
-
const sessionId = env.HQ_SESSION_ID?.trim();
|
|
38
|
-
if (!sessionId)
|
|
39
|
-
return {};
|
|
40
|
-
const session = readSessionState(sessionId, workContextRoot(undefined, env));
|
|
41
|
-
if (session?.contextStatus !== "bound" || session.companyConfidence === "device_default") {
|
|
42
|
-
return {};
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
companyUid: normalizedCompanyUid(session.companyUid),
|
|
46
|
-
companySlug: normalizedCompanySlug(session.companySlug),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
4
|
+
import { normalizedCompanyUid, resolveLightProcessFlagCompanyContext, } from "./process-flag-company-context-light.js";
|
|
49
5
|
function companyUidForContext(context, env = process.env) {
|
|
50
6
|
if (context.companyUid)
|
|
51
7
|
return context.companyUid;
|
|
@@ -57,7 +13,7 @@ function companyUidForContext(context, env = process.env) {
|
|
|
57
13
|
}
|
|
58
14
|
/** Resolve the same company identity the process-wide flag reader uses. */
|
|
59
15
|
export function resolveProcessFlagCompanyContext(env = process.env) {
|
|
60
|
-
const context =
|
|
16
|
+
const context = resolveLightProcessFlagCompanyContext(env);
|
|
61
17
|
const companyUid = companyUidForContext(context, env);
|
|
62
18
|
return {
|
|
63
19
|
...(companyUid ? { companyUid } : {}),
|
|
@@ -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
|