@indigoai-us/hq-cli 5.98.3 → 5.99.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/assets/scaffold/core/scripts/checkpoint-stop-gate.sh +347 -0
- package/assets/scaffold/core/scripts/hook-lib.sh +557 -0
- package/assets/scaffold/core/scripts/hq-session.sh +251 -0
- package/assets/scaffold/core/scripts/lib/session-id.sh +96 -0
- package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +52 -0
- package/dist/commands/core-checkpoint.js +11 -3
- package/dist/commands/core.js +60 -5
- package/dist/commands/doctor.d.ts +97 -0
- package/dist/commands/doctor.js +228 -0
- package/dist/commands/scaffold-fast.d.ts +41 -0
- package/dist/commands/scaffold-fast.js +57 -0
- package/dist/fast-core.d.ts +16 -0
- package/dist/fast-core.js +47 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -1
- package/dist/lib/core-utils/soft-timeout.d.ts +55 -0
- package/dist/lib/core-utils/soft-timeout.js +205 -0
- package/dist/lib/core-utils/timeout-guard.d.ts +62 -0
- package/dist/lib/core-utils/timeout-guard.js +207 -0
- package/dist/lib/doctor/__testing__/fake-hq-tree.d.ts +194 -0
- package/dist/lib/doctor/__testing__/fake-hq-tree.js +357 -0
- package/dist/lib/doctor/allowed-divergence.d.ts +72 -0
- package/dist/lib/doctor/allowed-divergence.js +134 -0
- package/dist/lib/doctor/checks/claude-wiring.d.ts +55 -0
- package/dist/lib/doctor/checks/claude-wiring.js +524 -0
- package/dist/lib/doctor/checks/codex-wiring.d.ts +45 -0
- package/dist/lib/doctor/checks/codex-wiring.js +376 -0
- package/dist/lib/doctor/checks/grok-wiring.d.ts +35 -0
- package/dist/lib/doctor/checks/grok-wiring.js +186 -0
- package/dist/lib/doctor/checks/runtime-probe.d.ts +101 -0
- package/dist/lib/doctor/checks/runtime-probe.js +335 -0
- package/dist/lib/doctor/compat.d.ts +85 -0
- package/dist/lib/doctor/compat.js +102 -0
- package/dist/lib/doctor/deep/classify.d.ts +61 -0
- package/dist/lib/doctor/deep/classify.js +75 -0
- package/dist/lib/doctor/deep/effects.d.ts +107 -0
- package/dist/lib/doctor/deep/effects.js +229 -0
- package/dist/lib/doctor/deep/executor.d.ts +112 -0
- package/dist/lib/doctor/deep/executor.js +369 -0
- package/dist/lib/doctor/deep/parity.d.ts +129 -0
- package/dist/lib/doctor/deep/parity.js +355 -0
- package/dist/lib/doctor/deep/sandbox.d.ts +190 -0
- package/dist/lib/doctor/deep/sandbox.js +572 -0
- package/dist/lib/doctor/fix/apply.d.ts +119 -0
- package/dist/lib/doctor/fix/apply.js +352 -0
- package/dist/lib/doctor/fix/backup.d.ts +40 -0
- package/dist/lib/doctor/fix/backup.js +64 -0
- package/dist/lib/doctor/fix/remediation.d.ts +71 -0
- package/dist/lib/doctor/fix/remediation.js +103 -0
- package/dist/lib/doctor/fixtures/discover.d.ts +96 -0
- package/dist/lib/doctor/fixtures/discover.js +287 -0
- package/dist/lib/doctor/fixtures/schema.d.ts +171 -0
- package/dist/lib/doctor/fixtures/schema.js +248 -0
- package/dist/lib/doctor/hook-gate-profiles.d.ts +55 -0
- package/dist/lib/doctor/hook-gate-profiles.js +107 -0
- package/dist/lib/doctor/json-output.d.ts +90 -0
- package/dist/lib/doctor/json-output.js +76 -0
- package/dist/lib/doctor/payload-shapes.d.ts +170 -0
- package/dist/lib/doctor/payload-shapes.js +275 -0
- package/dist/lib/doctor/platform.d.ts +244 -0
- package/dist/lib/doctor/platform.js +490 -0
- package/dist/lib/doctor/registry.d.ts +49 -0
- package/dist/lib/doctor/registry.js +176 -0
- package/dist/lib/doctor/report.d.ts +87 -0
- package/dist/lib/doctor/report.js +164 -0
- package/dist/lib/doctor/types.d.ts +87 -0
- package/dist/lib/doctor/types.js +29 -0
- package/dist/main.js +6 -0
- package/dist/utils/version-check.js +2 -2
- package/dist/utils/version-gate.d.ts +1 -1
- package/dist/utils/version-gate.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host platform detection for `hq doctor`.
|
|
3
|
+
*
|
|
4
|
+
* The doctor runs live checks against the agent runtime it is embedded in, so it
|
|
5
|
+
* has to know which host launched it. This module answers that question and,
|
|
6
|
+
* crucially, records the *evidence* behind every verdict so the report can show
|
|
7
|
+
* its work rather than assert a bare label.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this is the project's highest-risk piece
|
|
10
|
+
*
|
|
11
|
+
* `personal-context.md` and `core/docs/hq/HOOKS-NOT-FIRING.md` document that the
|
|
12
|
+
* Claude Code **app** and **SDK** runtimes do not dispatch command hooks at all,
|
|
13
|
+
* even when `.claude/settings.json` is present and correct. The terminal **CLI**
|
|
14
|
+
* does dispatch them. So the single most important distinction this module can
|
|
15
|
+
* get wrong is calling an app/SDK host a CLI host: that would let a runtime with
|
|
16
|
+
* *zero* live hook enforcement report PASS on hook checks — the exact false
|
|
17
|
+
* confidence the whole tool exists to prevent.
|
|
18
|
+
*
|
|
19
|
+
* The design therefore treats `claude-code-cli` as the verdict that requires the
|
|
20
|
+
* strongest, most specific positive evidence, and treats ambiguity as `unknown`
|
|
21
|
+
* rather than guessing:
|
|
22
|
+
*
|
|
23
|
+
* - `claude-code-cli` is asserted only on a positive CLI signal
|
|
24
|
+
* (`CLAUDE_CODE_ENTRYPOINT=cli`) with no competing strong signal.
|
|
25
|
+
* - `CLAUDECODE=1` alone is *weak*: the CLI, app, and SDK all set it, so on its
|
|
26
|
+
* own it proves only "some Claude Code host", never which one. Weak-only
|
|
27
|
+
* evidence resolves to `unknown`, never to CLI.
|
|
28
|
+
* - Conflicting strong signals (e.g. `entrypoint=cli` while a Claude desktop
|
|
29
|
+
* bundle id is also present) resolve to `unknown`, because a host that looks
|
|
30
|
+
* like two things at once cannot be trusted to be the safe one.
|
|
31
|
+
* - `unknown` is a first-class, safe outcome: the doctor still runs every
|
|
32
|
+
* platform-independent check and marks host-specific checks UNKNOWN (see
|
|
33
|
+
* {@link hostSpecificChecksTrustworthy}) rather than PASS.
|
|
34
|
+
*
|
|
35
|
+
* Residual limitation, stated honestly: if a non-dispatching host set
|
|
36
|
+
* `CLAUDE_CODE_ENTRYPOINT=cli` and left behind no other distinguishing signal,
|
|
37
|
+
* this module would report `claude-code-cli`. That gap is closed downstream by
|
|
38
|
+
* the runtime probe (US-006), which reads the policy-trigger ledger to confirm
|
|
39
|
+
* hooks *actually fired*, independent of the label here. Platform detection is a
|
|
40
|
+
* strong hint; the ledger is the proof.
|
|
41
|
+
*
|
|
42
|
+
* ## Signals, not a single variable
|
|
43
|
+
*
|
|
44
|
+
* Detection combines environment signals with process ancestry, mirroring the
|
|
45
|
+
* canonical shell detector `core/scripts/lib/detect-codex.sh`. Every signal is
|
|
46
|
+
* collected by its own small, individually testable function; {@link
|
|
47
|
+
* detectPlatform} composes them and applies the resolution rules above.
|
|
48
|
+
*
|
|
49
|
+
* The whole path is cheap (env reads plus at most one `ps` snapshot) and never
|
|
50
|
+
* launches an agent binary, satisfying the startup-latency budget.
|
|
51
|
+
*/
|
|
52
|
+
import type { DoctorStatus } from "./types.js";
|
|
53
|
+
/** Every platform the doctor can report, including the `unknown` fallback. */
|
|
54
|
+
export declare const PLATFORMS: readonly ["claude-code-cli", "claude-code-app", "claude-code-sdk", "codex", "grok", "unknown"];
|
|
55
|
+
/** A detected host platform. Closed union derived from {@link PLATFORMS}. */
|
|
56
|
+
export type Platform = (typeof PLATFORMS)[number];
|
|
57
|
+
/**
|
|
58
|
+
* The platforms a signal can positively point at. `unknown` is never the target
|
|
59
|
+
* of a signal — it is the verdict when no platform is positively established.
|
|
60
|
+
*/
|
|
61
|
+
export type DetectablePlatform = Exclude<Platform, "unknown">;
|
|
62
|
+
/**
|
|
63
|
+
* How dispositive a signal is:
|
|
64
|
+
* - `strong`: on its own, identifies a specific platform (e.g.
|
|
65
|
+
* `CLAUDE_CODE_ENTRYPOINT=cli`, a Codex runtime env var, a `codex` ancestor).
|
|
66
|
+
* - `weak`: corroborating but not variant-specific (e.g. `CLAUDECODE=1`, which
|
|
67
|
+
* is shared by the CLI, app, and SDK).
|
|
68
|
+
*/
|
|
69
|
+
export type SignalStrength = "strong" | "weak";
|
|
70
|
+
/** One observed signal and the platform it points at. */
|
|
71
|
+
export interface PlatformEvidence {
|
|
72
|
+
/** Stable machine key for the signal, e.g. `env.CLAUDE_CODE_ENTRYPOINT`. */
|
|
73
|
+
key: string;
|
|
74
|
+
/** Human-readable detail, e.g. `CLAUDE_CODE_ENTRYPOINT=cli`. Never a secret. */
|
|
75
|
+
detail: string;
|
|
76
|
+
/** The platform this signal supports. */
|
|
77
|
+
points: DetectablePlatform;
|
|
78
|
+
/** How dispositive the signal is. */
|
|
79
|
+
strength: SignalStrength;
|
|
80
|
+
}
|
|
81
|
+
/** The result of a detection: the verdict plus the evidence behind it. */
|
|
82
|
+
export interface PlatformDetection {
|
|
83
|
+
/** The resolved platform, or `unknown` when none could be established. */
|
|
84
|
+
platform: Platform;
|
|
85
|
+
/** Every signal observed, in collection order — the report shows its work. */
|
|
86
|
+
evidence: PlatformEvidence[];
|
|
87
|
+
/** The evidence keys that determined the verdict; empty when `unknown`. */
|
|
88
|
+
decidedBy: string[];
|
|
89
|
+
/** One-line, plain-language explanation of why this verdict was reached. */
|
|
90
|
+
reason: string;
|
|
91
|
+
}
|
|
92
|
+
/** One ancestor process in the chain from this process up toward init. */
|
|
93
|
+
export interface AncestorProcess {
|
|
94
|
+
/** Process id. */
|
|
95
|
+
pid: number;
|
|
96
|
+
/** Executable command (path or name) as reported by `ps`. */
|
|
97
|
+
command: string;
|
|
98
|
+
}
|
|
99
|
+
/** Reads this process's ancestor chain, nearest parent first. Injectable. */
|
|
100
|
+
export type AncestryReader = () => AncestorProcess[];
|
|
101
|
+
/** Options for {@link detectPlatform}. All are injectable so runs are testable. */
|
|
102
|
+
export interface DetectPlatformOptions {
|
|
103
|
+
/** Environment to read. Default: `process.env`. */
|
|
104
|
+
env?: NodeJS.ProcessEnv;
|
|
105
|
+
/** Ancestor-process reader. Default: {@link readProcessAncestry}. */
|
|
106
|
+
ancestry?: AncestryReader;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The `CLAUDE_CODE_ENTRYPOINT` signal — the one variable that distinguishes the
|
|
110
|
+
* Claude Code CLI from the SDK. `cli` ⇒ CLI; anything starting `sdk` (e.g.
|
|
111
|
+
* `sdk-ts`, `sdk-py`, `sdk-cli`) ⇒ SDK. Any other non-empty value is a Claude
|
|
112
|
+
* Code host of a variant we do not map, recorded as a *weak* Claude-app-leaning
|
|
113
|
+
* hint that on its own resolves to `unknown` rather than a CLI false positive.
|
|
114
|
+
*/
|
|
115
|
+
export declare function entrypointEvidence(env: NodeJS.ProcessEnv): PlatformEvidence | null;
|
|
116
|
+
/**
|
|
117
|
+
* The `CLAUDECODE=1` signal. Set by the CLI, app, and SDK alike, so it proves
|
|
118
|
+
* only that some Claude Code host is present — never which one. Always weak, and
|
|
119
|
+
* pointed at the app so it can never, on its own, promote a run to CLI.
|
|
120
|
+
*/
|
|
121
|
+
export declare function claudeCodeFlagEvidence(env: NodeJS.ProcessEnv): PlatformEvidence | null;
|
|
122
|
+
/**
|
|
123
|
+
* The macOS `__CFBundleIdentifier` signal — the bundle of the app that launched
|
|
124
|
+
* this process, set when a GUI application spawns it. Mirrors the Codex check in
|
|
125
|
+
* `detect-codex.sh`. A terminal emulator sets its own bundle id (e.g.
|
|
126
|
+
* `com.googlecode.iterm2`), so this fires only under a real desktop host.
|
|
127
|
+
*/
|
|
128
|
+
export declare function bundleIdEvidence(env: NodeJS.ProcessEnv): PlatformEvidence | null;
|
|
129
|
+
/**
|
|
130
|
+
* Codex runtime environment variables, kept in parity with the canonical shell
|
|
131
|
+
* detector `core/scripts/lib/detect-codex.sh`. Presence of any is a strong Codex
|
|
132
|
+
* signal. One evidence entry is produced per variable found so a single Codex
|
|
133
|
+
* signal can be tested in isolation.
|
|
134
|
+
*/
|
|
135
|
+
export declare const CODEX_ENV_VARS: readonly ["CODEX_SANDBOX", "CODEX_SESSION_ID", "CODEX_EXECUTION_ID", "CODEX_AGENT_ID", "CODEX_THREAD_ID", "CODEX_SHELL", "CODEX_CI", "CODEX_INTERNAL_ORIGINATOR_OVERRIDE", "OPENAI_CODEX"];
|
|
136
|
+
/** Every Codex environment signal present in `env`, one entry per variable. */
|
|
137
|
+
export declare function codexEnvEvidence(env: NodeJS.ProcessEnv): PlatformEvidence[];
|
|
138
|
+
/**
|
|
139
|
+
* Grok runtime environment variables. Restricted to variables the Grok host
|
|
140
|
+
* exports into the session (a session id, its workspace root) — HQ's own Grok
|
|
141
|
+
* *launcher* variables (`GROK_BIN`, `GROK_CONFIG`, `XAI_API_KEY`, …) are
|
|
142
|
+
* excluded on purpose: those can be set inside a Claude or Codex session that is
|
|
143
|
+
* merely preparing to launch Grok, and treating them as "we are running under
|
|
144
|
+
* Grok" would be a false positive.
|
|
145
|
+
*/
|
|
146
|
+
export declare const GROK_ENV_VARS: readonly ["GROK_SESSION_ID", "GROK_WORKSPACE_ROOT"];
|
|
147
|
+
/** Every Grok environment signal present in `env`, one entry per variable. */
|
|
148
|
+
export declare function grokEnvEvidence(env: NodeJS.ProcessEnv): PlatformEvidence[];
|
|
149
|
+
/**
|
|
150
|
+
* Match an ancestor process's command against a platform's known binary names.
|
|
151
|
+
* `codex`/`codex-*`/`Codex*` ⇒ Codex, `grok`/`grok-*`/`Grok*` ⇒ Grok — the same
|
|
152
|
+
* name families `detect-codex.sh` walks for. A Codex/Grok ancestor is a strong
|
|
153
|
+
* signal because those runtimes launch a distinctly-named binary.
|
|
154
|
+
*
|
|
155
|
+
* The Claude CLI is a Node application whose process is frequently reported as
|
|
156
|
+
* `node`, so a Claude ancestor cannot be matched reliably by name; Claude relies
|
|
157
|
+
* on its environment signals instead, and this function does not guess it.
|
|
158
|
+
*/
|
|
159
|
+
export declare function ancestryEvidence(ancestors: AncestorProcess[]): PlatformEvidence[];
|
|
160
|
+
/** The one command this module ever launches to read process ancestry. */
|
|
161
|
+
export declare const PS_COMMAND = "ps";
|
|
162
|
+
/** `ps` arguments: every process as `pid ppid comm`, header-suppressed. */
|
|
163
|
+
export declare const PS_ARGS: readonly ["-Ao", "pid=,ppid=,comm="];
|
|
164
|
+
/**
|
|
165
|
+
* Low-level process-table provider: runs a command and returns its stdout, or
|
|
166
|
+
* `null` on failure. This is the single seam through which any process is
|
|
167
|
+
* spawned, so a test can both feed a synthetic table and assert that the only
|
|
168
|
+
* command ever requested is `ps` — never an agent binary.
|
|
169
|
+
*/
|
|
170
|
+
export type ProcessSpawner = (command: string, args: readonly string[]) => string | null;
|
|
171
|
+
/** Options for {@link readProcessAncestry}. */
|
|
172
|
+
export interface ReadAncestryOptions {
|
|
173
|
+
/** Process to start from. Default: `process.pid`. */
|
|
174
|
+
pid?: number;
|
|
175
|
+
/** Maximum ancestors to walk. Default: {@link DEFAULT_MAX_HOPS}. */
|
|
176
|
+
maxHops?: number;
|
|
177
|
+
/** Process-table provider. Default: {@link defaultPsSpawner} (runs `ps`). */
|
|
178
|
+
spawner?: ProcessSpawner;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Read this process's ancestor chain (nearest parent first) by taking one `ps`
|
|
182
|
+
* snapshot of the process table and walking parent pointers upward. A single
|
|
183
|
+
* spawn — never per-hop — keeps the cost to a few milliseconds, and it launches
|
|
184
|
+
* only `ps`, never an agent binary. Fails closed (returns `[]`) if `ps` is
|
|
185
|
+
* unavailable or its output cannot be parsed, so detection degrades to
|
|
186
|
+
* environment signals rather than throwing.
|
|
187
|
+
*/
|
|
188
|
+
export declare function readProcessAncestry(options?: ReadAncestryOptions): AncestorProcess[];
|
|
189
|
+
/**
|
|
190
|
+
* Collect the environment signals, in a stable order: entrypoint, the Claude
|
|
191
|
+
* flag, bundle id, Codex vars, then Grok vars. Process ancestry is collected
|
|
192
|
+
* separately (and lazily) by {@link detectPlatform}.
|
|
193
|
+
*/
|
|
194
|
+
export declare function collectEnvEvidence(env: NodeJS.ProcessEnv): PlatformEvidence[];
|
|
195
|
+
/**
|
|
196
|
+
* Collect every signal from an environment and an explicit ancestor chain, in a
|
|
197
|
+
* stable order: environment signals first, then process ancestry. Exposed for
|
|
198
|
+
* callers that already hold an ancestor chain and want the full combined view.
|
|
199
|
+
*/
|
|
200
|
+
export declare function collectPlatformEvidence(env: NodeJS.ProcessEnv, ancestors: AncestorProcess[]): PlatformEvidence[];
|
|
201
|
+
/**
|
|
202
|
+
* Detect the host platform from environment signals and process ancestry, and
|
|
203
|
+
* return the verdict together with the evidence that produced it.
|
|
204
|
+
*
|
|
205
|
+
* Resolution rules (see the module header for the safety rationale):
|
|
206
|
+
* 1. If exactly one platform has strong evidence, that platform wins.
|
|
207
|
+
* 2. If more than one platform has strong evidence, the verdict is `unknown`
|
|
208
|
+
* (a host that looks like two things cannot be trusted to be the safe one).
|
|
209
|
+
* 3. With no strong evidence, weak signals never establish a platform — the
|
|
210
|
+
* verdict is `unknown`, which keeps a bare `CLAUDECODE=1` from being read
|
|
211
|
+
* as the CLI.
|
|
212
|
+
*
|
|
213
|
+
* Process ancestry is consulted **lazily**: when the environment already yields
|
|
214
|
+
* exactly one strong platform, or a genuine strong conflict, the ancestor chain
|
|
215
|
+
* cannot safely change the answer, so it is not read at all. This makes the
|
|
216
|
+
* common case (a dispositive env signal) spawn no process whatsoever, and limits
|
|
217
|
+
* the one `ps` snapshot to the cases where env signals are silent and ancestry
|
|
218
|
+
* is the only remaining evidence.
|
|
219
|
+
*/
|
|
220
|
+
export declare function detectPlatform(options?: DetectPlatformOptions): PlatformDetection;
|
|
221
|
+
/** A human-readable label for a platform, for text and report output. */
|
|
222
|
+
export declare function platformLabel(platform: Platform): string;
|
|
223
|
+
/**
|
|
224
|
+
* Whether host-specific *live* checks can be trusted to report PASS on this
|
|
225
|
+
* platform. False for `unknown`: a check that verifies live behavior on the
|
|
226
|
+
* detected host has no trustworthy host to verify against when the host is
|
|
227
|
+
* unknown, and must report UNKNOWN instead of PASS. This is the single source of
|
|
228
|
+
* truth the runtime tier (US-006) keys its UNKNOWN-not-PASS behavior off.
|
|
229
|
+
*/
|
|
230
|
+
export declare function hostSpecificChecksTrustworthy(platform: Platform): boolean;
|
|
231
|
+
/**
|
|
232
|
+
* The status a host-specific check must use when the platform is unknown —
|
|
233
|
+
* always {@link DoctorStatus} `UNKNOWN`, never PASS. Exposed so consumers share
|
|
234
|
+
* one honest answer rather than each re-deriving it.
|
|
235
|
+
*/
|
|
236
|
+
export declare function unknownPlatformCheckStatus(): DoctorStatus;
|
|
237
|
+
/**
|
|
238
|
+
* Render a detection as plain text lines for the default report: the labelled
|
|
239
|
+
* verdict, the reason, and each piece of evidence. US-015 owns the surrounding
|
|
240
|
+
* layout, colour, and `--json`; this is a self-contained, colour-free block it
|
|
241
|
+
* can drop in.
|
|
242
|
+
*/
|
|
243
|
+
export declare function formatPlatformLines(detection: PlatformDetection): string[];
|
|
244
|
+
//# sourceMappingURL=platform.d.ts.map
|