@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.
Files changed (73) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/assets/scaffold/core/scripts/checkpoint-stop-gate.sh +347 -0
  3. package/assets/scaffold/core/scripts/hook-lib.sh +557 -0
  4. package/assets/scaffold/core/scripts/hq-session.sh +251 -0
  5. package/assets/scaffold/core/scripts/lib/session-id.sh +96 -0
  6. package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +52 -0
  7. package/dist/commands/core-checkpoint.js +11 -3
  8. package/dist/commands/core.js +60 -5
  9. package/dist/commands/doctor.d.ts +97 -0
  10. package/dist/commands/doctor.js +228 -0
  11. package/dist/commands/scaffold-fast.d.ts +41 -0
  12. package/dist/commands/scaffold-fast.js +57 -0
  13. package/dist/fast-core.d.ts +16 -0
  14. package/dist/fast-core.js +47 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +10 -1
  17. package/dist/lib/core-utils/soft-timeout.d.ts +55 -0
  18. package/dist/lib/core-utils/soft-timeout.js +205 -0
  19. package/dist/lib/core-utils/timeout-guard.d.ts +62 -0
  20. package/dist/lib/core-utils/timeout-guard.js +207 -0
  21. package/dist/lib/doctor/__testing__/fake-hq-tree.d.ts +194 -0
  22. package/dist/lib/doctor/__testing__/fake-hq-tree.js +357 -0
  23. package/dist/lib/doctor/allowed-divergence.d.ts +72 -0
  24. package/dist/lib/doctor/allowed-divergence.js +134 -0
  25. package/dist/lib/doctor/checks/claude-wiring.d.ts +55 -0
  26. package/dist/lib/doctor/checks/claude-wiring.js +524 -0
  27. package/dist/lib/doctor/checks/codex-wiring.d.ts +45 -0
  28. package/dist/lib/doctor/checks/codex-wiring.js +376 -0
  29. package/dist/lib/doctor/checks/grok-wiring.d.ts +35 -0
  30. package/dist/lib/doctor/checks/grok-wiring.js +186 -0
  31. package/dist/lib/doctor/checks/runtime-probe.d.ts +101 -0
  32. package/dist/lib/doctor/checks/runtime-probe.js +335 -0
  33. package/dist/lib/doctor/compat.d.ts +85 -0
  34. package/dist/lib/doctor/compat.js +102 -0
  35. package/dist/lib/doctor/deep/classify.d.ts +61 -0
  36. package/dist/lib/doctor/deep/classify.js +75 -0
  37. package/dist/lib/doctor/deep/effects.d.ts +107 -0
  38. package/dist/lib/doctor/deep/effects.js +229 -0
  39. package/dist/lib/doctor/deep/executor.d.ts +112 -0
  40. package/dist/lib/doctor/deep/executor.js +369 -0
  41. package/dist/lib/doctor/deep/parity.d.ts +129 -0
  42. package/dist/lib/doctor/deep/parity.js +355 -0
  43. package/dist/lib/doctor/deep/sandbox.d.ts +190 -0
  44. package/dist/lib/doctor/deep/sandbox.js +572 -0
  45. package/dist/lib/doctor/fix/apply.d.ts +119 -0
  46. package/dist/lib/doctor/fix/apply.js +352 -0
  47. package/dist/lib/doctor/fix/backup.d.ts +40 -0
  48. package/dist/lib/doctor/fix/backup.js +64 -0
  49. package/dist/lib/doctor/fix/remediation.d.ts +71 -0
  50. package/dist/lib/doctor/fix/remediation.js +103 -0
  51. package/dist/lib/doctor/fixtures/discover.d.ts +96 -0
  52. package/dist/lib/doctor/fixtures/discover.js +287 -0
  53. package/dist/lib/doctor/fixtures/schema.d.ts +171 -0
  54. package/dist/lib/doctor/fixtures/schema.js +248 -0
  55. package/dist/lib/doctor/hook-gate-profiles.d.ts +55 -0
  56. package/dist/lib/doctor/hook-gate-profiles.js +107 -0
  57. package/dist/lib/doctor/json-output.d.ts +90 -0
  58. package/dist/lib/doctor/json-output.js +76 -0
  59. package/dist/lib/doctor/payload-shapes.d.ts +170 -0
  60. package/dist/lib/doctor/payload-shapes.js +275 -0
  61. package/dist/lib/doctor/platform.d.ts +244 -0
  62. package/dist/lib/doctor/platform.js +490 -0
  63. package/dist/lib/doctor/registry.d.ts +49 -0
  64. package/dist/lib/doctor/registry.js +176 -0
  65. package/dist/lib/doctor/report.d.ts +87 -0
  66. package/dist/lib/doctor/report.js +164 -0
  67. package/dist/lib/doctor/types.d.ts +87 -0
  68. package/dist/lib/doctor/types.js +29 -0
  69. package/dist/main.js +6 -0
  70. package/dist/utils/version-check.js +2 -2
  71. package/dist/utils/version-gate.d.ts +1 -1
  72. package/dist/utils/version-gate.js +1 -1
  73. 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