@indigoai-us/hq-cli 5.293.0 → 5.295.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 +39 -0
- package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +5 -1
- package/dist/command-catalog.generated.d.ts +54 -33
- package/dist/command-catalog.generated.js +68 -42
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +76 -1
- package/dist/commands/core-command-catalog.js +3 -2
- package/dist/commands/core-delegate-w1b.d.ts +2 -0
- package/dist/commands/core-delegate-w1b.js +28 -2
- package/dist/commands/core-delegate.d.ts +3 -1
- package/dist/commands/core-delegate.js +35 -5
- package/dist/commands/core-handoff-fast.d.ts +18 -0
- package/dist/commands/core-handoff-fast.js +103 -0
- package/dist/commands/core-handoff-open-steps.d.ts +21 -0
- package/dist/commands/core-handoff-open-steps.js +264 -0
- package/dist/commands/core-handoff.d.ts +4 -2
- package/dist/commands/core-handoff.js +22 -249
- package/dist/commands/core-jobs-native.d.ts +2 -0
- package/dist/commands/core-jobs-native.js +22 -11
- package/dist/commands/core-knowledge-prefs.d.ts +2 -0
- package/dist/commands/core-knowledge-prefs.js +13 -1
- package/dist/commands/core-ontology-native.d.ts +2 -0
- package/dist/commands/core-ontology-native.js +22 -2
- package/dist/commands/core-policy.d.ts +2 -0
- package/dist/commands/core-policy.js +18 -1
- package/dist/commands/core.js +2 -1
- package/dist/commands/daemon.d.ts +2 -1
- package/dist/commands/daemon.js +22 -7
- package/dist/commands/doctor.js +0 -74
- package/dist/commands/lanes-autopilot.d.ts +11 -0
- package/dist/commands/lanes-autopilot.js +336 -0
- package/dist/commands/lanes-message.js +23 -5
- package/dist/commands/lanes-questions.js +8 -5
- package/dist/commands/lanes-resume.js +0 -3
- package/dist/commands/lanes-shared.d.ts +18 -2
- package/dist/commands/lanes-stories.d.ts +14 -0
- package/dist/commands/lanes-stories.js +308 -0
- package/dist/commands/lanes.d.ts +1 -9
- package/dist/commands/lanes.js +216 -481
- package/dist/fast-core.js +53 -25
- package/dist/fast-scaffold.d.ts +15 -7
- package/dist/fast-scaffold.js +167 -17
- package/dist/index.js +1 -10
- package/dist/lib/core-utils/sentry-report.d.ts +0 -19
- package/dist/lib/core-utils/sentry-report.js +8 -61
- package/dist/lib/core-utils/worktree-resume.d.ts +2 -0
- package/dist/lib/core-utils/worktree-resume.js +16 -0
- package/dist/lib/daemon/migrate.d.ts +8 -1
- package/dist/lib/daemon/migrate.js +19 -7
- package/dist/lib/doctor/checks/claude-wiring.js +9 -0
- package/dist/lib/lanes/autopilot-metrics.d.ts +99 -0
- package/dist/lib/lanes/autopilot-metrics.js +301 -0
- package/dist/lib/lanes/flag-keys.d.ts +2 -0
- package/dist/lib/lanes/flag-keys.js +2 -0
- package/dist/lib/lanes/manager-status-gate.d.ts +0 -17
- package/dist/lib/lanes/manager-status-gate.js +2 -43
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -0
- package/dist/lib/lanes/outpost-dispatch.js +7 -1
- package/dist/lib/lanes/outpost-hire-cache.d.ts +0 -1
- package/dist/lib/lanes/outpost-hire-cache.js +0 -1
- package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
- package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
- package/dist/lib/lanes/reconcile.d.ts +2 -3
- package/dist/lib/lanes/reconcile.js +44 -38
- package/dist/lib/lanes/spawn.d.ts +0 -2
- package/dist/lib/lanes/spawn.js +18 -5
- package/dist/lib/lanes/store.d.ts +2 -0
- package/dist/lib/lanes/store.js +54 -3
- package/dist/lib/lanes/types.d.ts +18 -1
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/lanes/watcher.d.ts +4 -3
- package/dist/lib/lanes/watcher.js +102 -34
- package/dist/lib/mesh/live/daemon/run.js +18 -1
- package/dist/native-fast-route.d.ts +3 -1
- package/dist/native-fast-route.js +10 -8
- package/dist/utils/cognito-session.d.ts +6 -0
- package/dist/utils/cognito-session.js +9 -0
- package/dist/utils/contribution-table.d.ts +1 -1
- package/dist/utils/fast-stream-errors.d.ts +12 -0
- package/dist/utils/fast-stream-errors.js +18 -0
- package/dist/utils/scaffold-fast-import-guard.d.ts +2 -2
- package/dist/utils/scaffold-fast-import-guard.js +6 -3
- package/dist/utils/self-update.js +31 -0
- package/dist/utils/version-gate.js +22 -3
- package/package.json +1 -1
- package/dist/fast-lanes-list-route.d.ts +0 -8
- package/dist/fast-lanes-list-route.js +0 -32
- package/dist/fast-lanes-list.d.ts +0 -3
- package/dist/fast-lanes-list.js +0 -224
- package/dist/lib/doctor/client-health-sentry-flag.d.ts +0 -23
- package/dist/lib/doctor/client-health-sentry-flag.js +0 -99
- package/dist/lib/doctor/client-health-sentry.d.ts +0 -71
- package/dist/lib/doctor/client-health-sentry.js +0 -286
- package/dist/lib/lanes/outpost-snapshot-cache.d.ts +0 -26
- package/dist/lib/lanes/outpost-snapshot-cache.js +0 -215
- package/dist/lib/lanes/outpost-snapshot-refresh.d.ts +0 -21
- package/dist/lib/lanes/outpost-snapshot-refresh.js +0 -329
package/dist/fast-core.js
CHANGED
|
@@ -18,43 +18,71 @@ export async function runFastCore(argv) {
|
|
|
18
18
|
const parsed = parseFastCore(argv);
|
|
19
19
|
if (!parsed)
|
|
20
20
|
return;
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const entry = parsed.entry;
|
|
22
|
+
// The full graph's process gate accepts textual true/false overrides. It ignores
|
|
23
|
+
// numeric env values, so preserve that behavior for family flags here too.
|
|
24
|
+
const familyEnabled = entry.kind !== "native" || !entry.familyFlagKey ||
|
|
25
|
+
await (await import("./native-fast-route.js")).fastFlagEnabled(entry.familyFlagKey, process.env, { numericEnvironmentOverride: false });
|
|
26
|
+
const useShell = entry.kind === "native" && Boolean(entry.familyFlagKey) && !familyEnabled;
|
|
27
|
+
const shouldInjectRoot = useShell
|
|
28
|
+
? entry.shellHqRoot === "live" || (entry.shellHqRoot === "reconcile-default" &&
|
|
29
|
+
!parsed.args.includes("--hq-root") && !parsed.args.includes("--help") && !parsed.args.includes("-h"))
|
|
30
|
+
: entry.kind === "shell"
|
|
31
|
+
? true
|
|
32
|
+
: entry.root === "live";
|
|
33
|
+
// `root: "live"` — resolve the live HQ root using the same resolver as the
|
|
34
|
+
// full command graph. Existing shell fast entries keep their historical
|
|
35
|
+
// fail-soft behavior; newly routed commands fall back to the full graph so
|
|
36
|
+
// its expected-user-error formatting remains authoritative.
|
|
26
37
|
let hqRoot;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
if (shouldInjectRoot) {
|
|
39
|
+
try {
|
|
40
|
+
hqRoot = resolveLiveRoot(parsed.hqRootArg ? { hqRoot: parsed.hqRootArg } : {});
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
if (entry.kind === "native") {
|
|
44
|
+
const { runCli } = await import("./main.js");
|
|
45
|
+
await runCli();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
hqRoot = undefined;
|
|
49
|
+
}
|
|
32
50
|
}
|
|
33
|
-
if (
|
|
34
|
-
const
|
|
35
|
-
|
|
51
|
+
if (entry.kind === "native" && !useShell) {
|
|
52
|
+
const { installFastPathStreamErrorHandlers } = await import("./utils/fast-stream-errors.js");
|
|
53
|
+
installFastPathStreamErrorHandlers();
|
|
54
|
+
const handlerModule = await import(entry.modulePath);
|
|
55
|
+
const handler = handlerModule[entry.exportName];
|
|
36
56
|
if (typeof handler !== "function") {
|
|
37
|
-
throw new Error(`Native fast handler ${
|
|
57
|
+
throw new Error(`Native fast handler ${entry.exportName} was not exported`);
|
|
38
58
|
}
|
|
39
59
|
const chunks = [];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
if (entry.readsStdin) {
|
|
61
|
+
for await (const chunk of process.stdin)
|
|
62
|
+
chunks.push(chunk);
|
|
63
|
+
}
|
|
64
|
+
const code = await handler(parsed.args, entry.readsStdin ? Buffer.concat(chunks).toString("utf8") : "", process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg);
|
|
43
65
|
// Let Node drain handler output before terminating. The timeout guard's
|
|
44
66
|
// stderr message must survive the native route's process boundary.
|
|
45
67
|
process.exitCode = code;
|
|
46
68
|
return;
|
|
47
69
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
//
|
|
70
|
+
const asset = entry.asset;
|
|
71
|
+
if (!asset)
|
|
72
|
+
throw new Error(`Fast route ${entry.name} has no shell companion`);
|
|
73
|
+
// Some historical registrations run from the live root, others preserve the
|
|
74
|
+
// caller cwd and only inject HQ_ROOT. Keep those choices explicit per entry.
|
|
75
|
+
const cwd = entry.kind === "native" && entry.shellCwd === "live"
|
|
76
|
+
? hqRoot ?? process.cwd()
|
|
77
|
+
: process.cwd();
|
|
78
|
+
const injectRoot = entry.kind === "shell" || entry.kind === "native" &&
|
|
79
|
+
(entry.shellHqRoot === "live" || entry.shellHqRoot === "reconcile-default" && shouldInjectRoot);
|
|
52
80
|
const { code } = runBundledScript({
|
|
53
|
-
asset
|
|
81
|
+
asset,
|
|
54
82
|
args: parsed.args,
|
|
55
|
-
cwd
|
|
56
|
-
hqRoot,
|
|
57
|
-
env:
|
|
83
|
+
cwd,
|
|
84
|
+
...(injectRoot ? { hqRoot } : {}),
|
|
85
|
+
env: entry.name === "hq-session" ? { HQ_HQ_SESSION_NO_CLI: "1" } : undefined,
|
|
58
86
|
});
|
|
59
87
|
// Propagate verbatim: these scripts use their exit code as their interface.
|
|
60
88
|
process.exit(code);
|
package/dist/fast-scaffold.d.ts
CHANGED
|
@@ -15,16 +15,29 @@ export type FastScaffoldScript = {
|
|
|
15
15
|
kind: "shell";
|
|
16
16
|
/** Path under `assets/scaffold/`, e.g. `core/scripts/foo.sh`. */
|
|
17
17
|
asset: string;
|
|
18
|
+
root?: "live";
|
|
18
19
|
} | {
|
|
19
|
-
/**
|
|
20
|
+
/** Command path, joined with spaces, e.g. `policy frontmatter`. */
|
|
20
21
|
name: string;
|
|
21
22
|
kind: "native";
|
|
22
23
|
/** Relative ESM module specifier, resolved from fast-core.js. */
|
|
23
24
|
modulePath: string;
|
|
24
25
|
/** Named export which accepts the native fast-handler contract. */
|
|
25
26
|
exportName: string;
|
|
26
|
-
/**
|
|
27
|
+
/** Existing default-off gate for the whole fast route. */
|
|
27
28
|
flagKey: string;
|
|
29
|
+
/** Existing family gate that chooses this native handler or its shell companion. */
|
|
30
|
+
familyFlagKey?: string;
|
|
31
|
+
/** Shell rollback companion used while the family gate is off. */
|
|
32
|
+
asset?: string;
|
|
33
|
+
/** Root context used by the native handler. */
|
|
34
|
+
root?: "live" | "cwd" | "passthrough";
|
|
35
|
+
/** Cwd used for a shell rollback companion. Defaults to the caller cwd. */
|
|
36
|
+
shellCwd?: "caller" | "live";
|
|
37
|
+
/** Root injection behavior for a shell rollback companion. */
|
|
38
|
+
shellHqRoot?: "none" | "live" | "reconcile-default";
|
|
39
|
+
/** Whether this handler consumes stdin. Shell companions inherit stdin directly. */
|
|
40
|
+
readsStdin?: boolean;
|
|
28
41
|
};
|
|
29
42
|
/** The relocated scripts that qualify for the entrypoint fast path. */
|
|
30
43
|
export declare const FAST_SCAFFOLD_SCRIPTS: readonly FastScaffoldScript[];
|
|
@@ -53,11 +66,6 @@ export type ParsedSecretsEnvFastRequest = {
|
|
|
53
66
|
* the full CLI, which remains the source of truth for help and errors.
|
|
54
67
|
*/
|
|
55
68
|
export declare function parseSecretsEnvFastRequest(argv: readonly string[]): ParsedSecretsEnvFastRequest | null;
|
|
56
|
-
/**
|
|
57
|
-
* The cache-first list path only handles the bare JSON request. `--fresh`,
|
|
58
|
-
* filters, help, and unknown options stay with Commander and the full CLI.
|
|
59
|
-
*/
|
|
60
|
-
export declare function isFastCachedLanesListRequest(argv: readonly string[]): boolean;
|
|
61
69
|
/**
|
|
62
70
|
* Parse `hq core [--hq-root <path>] <name> [args...]` when `<name>` is a
|
|
63
71
|
* fast-path script. Returns null for anything else so the caller defers to the
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -11,15 +11,175 @@
|
|
|
11
11
|
*/
|
|
12
12
|
/** The relocated scripts that qualify for the entrypoint fast path. */
|
|
13
13
|
export const FAST_SCAFFOLD_SCRIPTS = [
|
|
14
|
-
{ name: "checkpoint-stop-gate", kind: "shell", asset: "core/scripts/checkpoint-stop-gate.sh" },
|
|
15
|
-
{ name: "hq-session", kind: "shell", asset: "core/scripts/hq-session.sh" },
|
|
14
|
+
{ name: "checkpoint-stop-gate", kind: "shell", asset: "core/scripts/checkpoint-stop-gate.sh", root: "live" },
|
|
15
|
+
{ name: "hq-session", kind: "shell", asset: "core/scripts/hq-session.sh", root: "live" },
|
|
16
16
|
{
|
|
17
17
|
name: "timeout-guard",
|
|
18
18
|
kind: "native",
|
|
19
19
|
modulePath: "./lib/core-utils/timeout-guard-native.js",
|
|
20
20
|
exportName: "runTimeoutGuardFast",
|
|
21
21
|
flagKey: "hq-core-native-fast-route",
|
|
22
|
+
root: "cwd",
|
|
23
|
+
readsStdin: true,
|
|
22
24
|
},
|
|
25
|
+
{
|
|
26
|
+
name: "policy frontmatter",
|
|
27
|
+
kind: "native",
|
|
28
|
+
modulePath: "./commands/core-policy.js",
|
|
29
|
+
exportName: "runPolicyFast",
|
|
30
|
+
flagKey: "hq-core-native-fast-route",
|
|
31
|
+
familyFlagKey: "core-native-policy",
|
|
32
|
+
asset: "core/scripts/read-policy-frontmatter.sh",
|
|
33
|
+
root: "cwd",
|
|
34
|
+
shellCwd: "caller",
|
|
35
|
+
shellHqRoot: "none",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "policy age-report",
|
|
39
|
+
kind: "native",
|
|
40
|
+
modulePath: "./commands/core-policy.js",
|
|
41
|
+
exportName: "runPolicyFast",
|
|
42
|
+
flagKey: "hq-core-native-fast-route",
|
|
43
|
+
familyFlagKey: "core-native-policy",
|
|
44
|
+
asset: "core/scripts/policy-age-report.sh",
|
|
45
|
+
root: "live",
|
|
46
|
+
shellCwd: "caller",
|
|
47
|
+
shellHqRoot: "live",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "policy retire",
|
|
51
|
+
kind: "native",
|
|
52
|
+
modulePath: "./commands/core-policy.js",
|
|
53
|
+
exportName: "runPolicyFast",
|
|
54
|
+
flagKey: "hq-core-native-fast-route",
|
|
55
|
+
familyFlagKey: "core-native-policy",
|
|
56
|
+
asset: "core/scripts/policy-retire.sh",
|
|
57
|
+
root: "live",
|
|
58
|
+
shellCwd: "caller",
|
|
59
|
+
shellHqRoot: "live",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "knowledge-prefs",
|
|
63
|
+
kind: "native",
|
|
64
|
+
modulePath: "./commands/core-knowledge-prefs.js",
|
|
65
|
+
exportName: "runKnowledgePrefsFast",
|
|
66
|
+
flagKey: "hq-core-native-fast-route",
|
|
67
|
+
familyFlagKey: "core-native-policy",
|
|
68
|
+
asset: "core/scripts/knowledge-prefs.sh",
|
|
69
|
+
root: "live",
|
|
70
|
+
shellCwd: "live",
|
|
71
|
+
shellHqRoot: "live",
|
|
72
|
+
},
|
|
73
|
+
...["finalize", "post", "open-steps"].map((name) => ({
|
|
74
|
+
name: `handoff ${name}`,
|
|
75
|
+
kind: "native",
|
|
76
|
+
modulePath: name === "open-steps"
|
|
77
|
+
? "./commands/core-handoff-open-steps.js"
|
|
78
|
+
: "./commands/core-handoff-fast.js",
|
|
79
|
+
exportName: name === "open-steps" ? "runHandoffOpenStepsFast" : "runHandoffFastEntry",
|
|
80
|
+
flagKey: "hq-core-native-fast-route",
|
|
81
|
+
familyFlagKey: "core-native-handoff",
|
|
82
|
+
asset: `core/scripts/handoff-${name === "open-steps" ? "open-steps" : name}.sh`,
|
|
83
|
+
root: "live",
|
|
84
|
+
shellCwd: "live",
|
|
85
|
+
shellHqRoot: "live",
|
|
86
|
+
})),
|
|
87
|
+
{
|
|
88
|
+
name: "worktree-gc",
|
|
89
|
+
kind: "native",
|
|
90
|
+
modulePath: "./lib/core-utils/worktree-resume.js",
|
|
91
|
+
exportName: "runWorktreeResumeFast",
|
|
92
|
+
flagKey: "hq-core-native-fast-route",
|
|
93
|
+
familyFlagKey: "core-native-handoff",
|
|
94
|
+
asset: "core/scripts/worktree-gc.sh",
|
|
95
|
+
root: "live",
|
|
96
|
+
shellCwd: "live",
|
|
97
|
+
shellHqRoot: "live",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "clean-worktree-reconcile-handoff",
|
|
101
|
+
kind: "native",
|
|
102
|
+
modulePath: "./lib/core-utils/worktree-resume.js",
|
|
103
|
+
exportName: "runWorktreeResumeFast",
|
|
104
|
+
flagKey: "hq-core-native-fast-route",
|
|
105
|
+
familyFlagKey: "core-native-handoff",
|
|
106
|
+
asset: "core/scripts/clean-worktree-reconcile-handoff.sh",
|
|
107
|
+
root: "cwd",
|
|
108
|
+
shellCwd: "caller",
|
|
109
|
+
shellHqRoot: "none",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "resume-thread-lock",
|
|
113
|
+
kind: "native",
|
|
114
|
+
modulePath: "./lib/core-utils/worktree-resume.js",
|
|
115
|
+
exportName: "runWorktreeResumeFast",
|
|
116
|
+
flagKey: "hq-core-native-fast-route",
|
|
117
|
+
familyFlagKey: "core-native-handoff",
|
|
118
|
+
asset: "core/scripts/resume-thread-lock.sh",
|
|
119
|
+
root: "live",
|
|
120
|
+
shellCwd: "live",
|
|
121
|
+
shellHqRoot: "live",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "verify-story-deliverables",
|
|
125
|
+
kind: "native",
|
|
126
|
+
modulePath: "./lib/core-utils/worktree-resume.js",
|
|
127
|
+
exportName: "runWorktreeResumeFast",
|
|
128
|
+
flagKey: "hq-core-native-fast-route",
|
|
129
|
+
familyFlagKey: "core-native-handoff",
|
|
130
|
+
asset: "core/scripts/verify-story-deliverables.sh",
|
|
131
|
+
root: "live",
|
|
132
|
+
shellCwd: "live",
|
|
133
|
+
shellHqRoot: "live",
|
|
134
|
+
},
|
|
135
|
+
...["schedule-parse", "validate", "reconcile"].map((name) => ({
|
|
136
|
+
name: `jobs ${name}`,
|
|
137
|
+
kind: "native",
|
|
138
|
+
modulePath: "./commands/core-jobs-native.js",
|
|
139
|
+
exportName: "runJobsFast",
|
|
140
|
+
flagKey: "hq-core-native-fast-route",
|
|
141
|
+
familyFlagKey: "core-native-jobs",
|
|
142
|
+
asset: `core/scripts/${name === "reconcile" ? "outpost-jobs-reconcile" : `jobs-${name}`}.sh`,
|
|
143
|
+
root: name === "reconcile" ? "passthrough" : "cwd",
|
|
144
|
+
shellCwd: "caller",
|
|
145
|
+
shellHqRoot: name === "reconcile" ? "reconcile-default" : "none",
|
|
146
|
+
})),
|
|
147
|
+
...["resolve", "pickup", "repo", "secrets", "transfer"].map((name) => ({
|
|
148
|
+
name: `delegate ${name}`,
|
|
149
|
+
kind: "native",
|
|
150
|
+
modulePath: "./commands/core-delegate.js",
|
|
151
|
+
exportName: "runDelegateFast",
|
|
152
|
+
flagKey: "hq-core-native-fast-route",
|
|
153
|
+
familyFlagKey: "core-native-delegate",
|
|
154
|
+
asset: `core/scripts/hq-delegate-${name}.sh`,
|
|
155
|
+
root: name === "pickup" ? "cwd" : "live",
|
|
156
|
+
shellCwd: name === "pickup" ? "caller" : "live",
|
|
157
|
+
shellHqRoot: name === "pickup" ? "none" : "live",
|
|
158
|
+
})),
|
|
159
|
+
...["bundle", "grant", "publish", "send", "verify"].map((name) => ({
|
|
160
|
+
name: `delegate ${name}`,
|
|
161
|
+
kind: "native",
|
|
162
|
+
modulePath: "./commands/core-delegate-w1b.js",
|
|
163
|
+
exportName: "runDelegateW1bFast",
|
|
164
|
+
flagKey: "hq-core-native-fast-route",
|
|
165
|
+
familyFlagKey: "core-native-delegate",
|
|
166
|
+
asset: `core/scripts/hq-delegate-${name}.sh`,
|
|
167
|
+
root: "live",
|
|
168
|
+
shellCwd: "live",
|
|
169
|
+
shellHqRoot: "live",
|
|
170
|
+
})),
|
|
171
|
+
...["audience-grant", "brief", "candidate", "garden", "source-worklist", "source-yaml-validate"].map((name) => ({
|
|
172
|
+
name: `ontology ${name}`,
|
|
173
|
+
kind: "native",
|
|
174
|
+
modulePath: "./commands/core-ontology-native.js",
|
|
175
|
+
exportName: "runOntologyNativeFast",
|
|
176
|
+
flagKey: "hq-core-native-fast-route",
|
|
177
|
+
familyFlagKey: "core-native-ontology",
|
|
178
|
+
asset: `core/scripts/${name === "audience-grant" ? "ontology-audience-grant" : name === "source-worklist" ? "ontology-source-worklist" : name === "source-yaml-validate" ? "source-yaml-validate" : `ontology-${name}`}.${name === "brief" || name === "garden" ? "mjs" : "sh"}`,
|
|
179
|
+
root: name === "source-yaml-validate" ? "cwd" : "live",
|
|
180
|
+
shellCwd: "caller",
|
|
181
|
+
shellHqRoot: name === "source-yaml-validate" ? "none" : "live",
|
|
182
|
+
})),
|
|
23
183
|
];
|
|
24
184
|
/**
|
|
25
185
|
* Parse the small, non-ambiguous `hq secrets env` surface for the gated entry
|
|
@@ -112,16 +272,6 @@ export function parseSecretsEnvFastRequest(argv) {
|
|
|
112
272
|
}
|
|
113
273
|
return hasOnly ? { options, scope } : null;
|
|
114
274
|
}
|
|
115
|
-
/**
|
|
116
|
-
* The cache-first list path only handles the bare JSON request. `--fresh`,
|
|
117
|
-
* filters, help, and unknown options stay with Commander and the full CLI.
|
|
118
|
-
*/
|
|
119
|
-
export function isFastCachedLanesListRequest(argv) {
|
|
120
|
-
return argv.length === 5 &&
|
|
121
|
-
argv[2] === "lanes" &&
|
|
122
|
-
argv[3] === "list" &&
|
|
123
|
-
argv[4] === "--json";
|
|
124
|
-
}
|
|
125
275
|
/**
|
|
126
276
|
* Parse `hq core [--hq-root <path>] <name> [args...]` when `<name>` is a
|
|
127
277
|
* fast-path script. Returns null for anything else so the caller defers to the
|
|
@@ -148,13 +298,13 @@ export function parseFastCore(argv) {
|
|
|
148
298
|
hqRootArg = head.slice("--hq-root=".length);
|
|
149
299
|
i += 1;
|
|
150
300
|
}
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
301
|
+
const commandArgs = argv.slice(i);
|
|
302
|
+
const entry = [...FAST_SCAFFOLD_SCRIPTS]
|
|
303
|
+
.sort((left, right) => right.name.split(" ").length - left.name.split(" ").length)
|
|
304
|
+
.find((candidate) => candidate.name.split(" ").every((part, index) => commandArgs[index] === part));
|
|
155
305
|
if (!entry)
|
|
156
306
|
return null;
|
|
157
|
-
return { entry, hqRootArg, args: argv.slice(i +
|
|
307
|
+
return { entry, hqRootArg, args: argv.slice(i + entry.name.split(" ").length) };
|
|
158
308
|
}
|
|
159
309
|
/** Whether this invocation is a fast-path scaffold dispatch. */
|
|
160
310
|
export function isFastCoreRequest(argv) {
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import "./node-network-compat.js";
|
|
|
6
6
|
import { isVersionRequest, wantsLabelledVersionReport } from "./version-request.js";
|
|
7
7
|
// Dependency-light: a pure parser + table, no command graph. Safe to load on
|
|
8
8
|
// every path (including --version) without reintroducing the heavy startup.
|
|
9
|
-
import {
|
|
9
|
+
import { isFastCoreRequest, parseFastCore, parseSecretsEnvFastRequest, } from "./fast-scaffold.js";
|
|
10
10
|
// Also dependency-light: registers a process-level rejection boundary; its only
|
|
11
11
|
// heavy import (main.js) is lazy and reached only when a rejection fires.
|
|
12
12
|
import { installProcessRejectionBoundary } from "./unhandled-rejection-boundary.js";
|
|
@@ -61,15 +61,6 @@ else if (parseSecretsEnvFastRequest(process.argv)) {
|
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
-
else if (isFastCachedLanesListRequest(process.argv)) {
|
|
65
|
-
void import("./fast-lanes-list-route.js").then(async ({ runFastLanesListRequest }) => {
|
|
66
|
-
const handled = await runFastLanesListRequest(process.argv);
|
|
67
|
-
if (!handled) {
|
|
68
|
-
const { runCli } = await import("./main.js");
|
|
69
|
-
await runCli();
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
64
|
else if (isFastCoreRequest(process.argv)) {
|
|
74
65
|
// Hot relocated plumbing (`hq core hq-session`, `hq core checkpoint-stop-gate`)
|
|
75
66
|
// dispatches straight to the bundled-script runner, so it never pays for the
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import * as Sentry from "@sentry/node";
|
|
19
19
|
import type { CaptureContext, SeverityLevel } from "@sentry/node";
|
|
20
|
-
import type { ClientHealthSentryEvent } from "../doctor/client-health-sentry.js";
|
|
21
20
|
export declare const DEFAULT_SENTRY_REPORT_TIMEOUT_MS = 750;
|
|
22
21
|
export declare const MAX_SENTRY_REPORT_TIMEOUT_MS = 5000;
|
|
23
22
|
export declare const MAX_SENTRY_REPORT_PAYLOAD_BYTES = 2048;
|
|
@@ -96,17 +95,6 @@ export type SentryReportResult = {
|
|
|
96
95
|
event?: SentryReportEvent;
|
|
97
96
|
droppedMetadataKeys?: number;
|
|
98
97
|
};
|
|
99
|
-
export interface ClientHealthSentryScope {
|
|
100
|
-
clear(): void;
|
|
101
|
-
setLevel(level: SeverityLevel): void;
|
|
102
|
-
setFingerprint(fingerprint: string[]): void;
|
|
103
|
-
setTags(tags: Record<string, string>): void;
|
|
104
|
-
setUser(user: {
|
|
105
|
-
id: string;
|
|
106
|
-
ip_address: null;
|
|
107
|
-
}): void;
|
|
108
|
-
}
|
|
109
|
-
export type ClientHealthSentryScopeRunner = (callback: (scope: ClientHealthSentryScope) => void) => void;
|
|
110
98
|
/**
|
|
111
99
|
* Read no more than the accepted event size. Returning early keeps a malformed
|
|
112
100
|
* pipe from making a watchdog retain an unbounded hook payload in memory.
|
|
@@ -129,11 +117,4 @@ export declare function parseSentryReportEvent(stdin: string): SentryReportResul
|
|
|
129
117
|
* transport errors cannot echo event data back to a hook.
|
|
130
118
|
*/
|
|
131
119
|
export declare function reportSentryEvent(stdin: string, options?: SentryReportOptions, dependencies?: SentryReportDependencies): Promise<SentryReportResult>;
|
|
132
|
-
/** Send the client-health projection with a clean Sentry scope and no metadata payload. */
|
|
133
|
-
export declare function reportClientHealthEvents(events: readonly ClientHealthSentryEvent[], options?: {
|
|
134
|
-
timeoutMs?: number;
|
|
135
|
-
scopeRunner?: ClientHealthSentryScopeRunner;
|
|
136
|
-
}, dependencies?: SentryReportDependencies): Promise<{
|
|
137
|
-
code: 0 | 1;
|
|
138
|
-
}>;
|
|
139
120
|
//# sourceMappingURL=sentry-report.d.ts.map
|
|
@@ -577,6 +577,14 @@ function hookTimeoutDebugContextTags(context) {
|
|
|
577
577
|
}
|
|
578
578
|
function safeSlowChild(metadata) {
|
|
579
579
|
const value = metadata.slow_child;
|
|
580
|
+
const slowChildMs = metadata.slow_child_ms;
|
|
581
|
+
const hasMeasuredChild = typeof slowChildMs === "number" && Number.isFinite(slowChildMs) && slowChildMs >= 0;
|
|
582
|
+
if (metadata.hook_event === "SessionStart" &&
|
|
583
|
+
(metadata.bash_env_set === true || metadata.bash_env_set === "set") &&
|
|
584
|
+
!hasMeasuredChild &&
|
|
585
|
+
(typeof value !== "string" || value.length === 0 || value === "other")) {
|
|
586
|
+
return "entry-bash-startup";
|
|
587
|
+
}
|
|
580
588
|
if (typeof value !== "string" || !SAFE_HOOK_SCRIPT.test(value))
|
|
581
589
|
return "other";
|
|
582
590
|
return value;
|
|
@@ -1006,65 +1014,4 @@ export async function reportSentryEvent(stdin, options = {}, dependencies = defa
|
|
|
1006
1014
|
dependencies.stderr.write("Sentry report failed.\n");
|
|
1007
1015
|
return invalidInput();
|
|
1008
1016
|
}
|
|
1009
|
-
function isSafeClientHealthEvent(event) {
|
|
1010
|
-
const tags = event.tags;
|
|
1011
|
-
const checkClass = tags.check_class;
|
|
1012
|
-
const reasonCode = tags.reason_code;
|
|
1013
|
-
const classPattern = /^sync\.(?:update\.core|journal\.(?:personal|personal_vault|company)|manifest\.(?:personal|personal_vault|company))$|^other$/;
|
|
1014
|
-
const reasonPattern = /^(?:none|core-version-unavailable|core-update-available|no-local-tree|never-synced|invalid-last-sync|stale-threshold|manifest-unavailable|manifest-state-unavailable|no-manifest-scopes|never-uploaded|invalid-upload-time|unresolved-company-uid)$/;
|
|
1015
|
-
const versionPattern = /^(?:unknown|\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?(?:\+[A-Za-z0-9.-]+)?)$/;
|
|
1016
|
-
const safeTagKeys = [
|
|
1017
|
-
"check_class", "reason_code", "fix_outcome", "status", "failing_check_count",
|
|
1018
|
-
"cli_version", "core_version", "sync_version", "platform", "arch",
|
|
1019
|
-
];
|
|
1020
|
-
return (event.level === "warning" &&
|
|
1021
|
-
classPattern.test(checkClass) &&
|
|
1022
|
-
reasonPattern.test(reasonCode) &&
|
|
1023
|
-
event.message === `Client health: ${checkClass} unresolved after hq doctor --fix` &&
|
|
1024
|
-
event.fingerprint.length === 3 &&
|
|
1025
|
-
event.fingerprint[0] === "client-health" &&
|
|
1026
|
-
event.fingerprint[1] === checkClass &&
|
|
1027
|
-
event.fingerprint[2] === reasonCode &&
|
|
1028
|
-
(tags.fix_outcome === "not_auto_fixable" || tags.fix_outcome === "fix_failed" || tags.fix_outcome === "fix_timed_out") &&
|
|
1029
|
-
(tags.status === "FAIL" || tags.status === "WARN") &&
|
|
1030
|
-
/^\d{1,3}$/.test(tags.failing_check_count) &&
|
|
1031
|
-
safeTagKeys.every((key) => Object.hasOwn(tags, key)) &&
|
|
1032
|
-
Object.keys(tags).length === safeTagKeys.length &&
|
|
1033
|
-
[tags.cli_version, tags.core_version, tags.sync_version].every((value) => versionPattern.test(value)) &&
|
|
1034
|
-
/^[a-z0-9]+$/.test(tags.platform) &&
|
|
1035
|
-
/^[a-z0-9]+$/.test(tags.arch) &&
|
|
1036
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(event.user.id) &&
|
|
1037
|
-
event.user.ip_address === null);
|
|
1038
|
-
}
|
|
1039
|
-
/** Send the client-health projection with a clean Sentry scope and no metadata payload. */
|
|
1040
|
-
export async function reportClientHealthEvents(events, options = {}, dependencies = defaultDependencies) {
|
|
1041
|
-
const timeoutMs = resolveTimeout(options.timeoutMs);
|
|
1042
|
-
if (timeoutMs === null ||
|
|
1043
|
-
events.length === 0 ||
|
|
1044
|
-
events.length > 250 ||
|
|
1045
|
-
events.some((event) => !isSafeClientHealthEvent(event))) {
|
|
1046
|
-
dependencies.stderr.write("Invalid client health Sentry event.\n");
|
|
1047
|
-
return { code: 1 };
|
|
1048
|
-
}
|
|
1049
|
-
const withScope = options.scopeRunner ?? ((callback) => Sentry.withScope((scope) => callback(scope)));
|
|
1050
|
-
try {
|
|
1051
|
-
for (const event of events) {
|
|
1052
|
-
withScope((scope) => {
|
|
1053
|
-
scope.clear();
|
|
1054
|
-
scope.setLevel(event.level);
|
|
1055
|
-
scope.setFingerprint([...event.fingerprint]);
|
|
1056
|
-
scope.setTags({ sentry_event_type: "client_health", ...event.tags });
|
|
1057
|
-
scope.setUser({ id: event.user.id, ip_address: null });
|
|
1058
|
-
dependencies.sentry.captureMessage(event.message);
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
1061
|
-
if (await flushWithin(dependencies.sentry, timeoutMs))
|
|
1062
|
-
return { code: 0 };
|
|
1063
|
-
}
|
|
1064
|
-
catch {
|
|
1065
|
-
// Keep the event and transport details out of hook stderr.
|
|
1066
|
-
}
|
|
1067
|
-
dependencies.stderr.write("Client health Sentry report failed.\n");
|
|
1068
|
-
return { code: 1 };
|
|
1069
|
-
}
|
|
1070
1017
|
//# sourceMappingURL=sentry-report.js.map
|
|
@@ -15,4 +15,6 @@ export declare function runWorktreeGc(args: readonly string[], hqRoot: string, i
|
|
|
15
15
|
export declare function runCleanWorktreeReconcileHandoff(args: readonly string[], io?: CoreIo): number;
|
|
16
16
|
export declare function runResumeThreadLock(args: readonly string[], hqRoot: string, io?: CoreIo): number;
|
|
17
17
|
export declare function runVerifyStoryDeliverables(args: readonly string[], hqRoot: string, io?: CoreIo): Promise<number>;
|
|
18
|
+
/** Fast-entry adapter; family gating and shell fallback happen before import. */
|
|
19
|
+
export declare function runWorktreeResumeFast(args: readonly string[], _stdin: string, cwd: string, hqRoot: string | undefined, commandName: string): Promise<number>;
|
|
18
20
|
//# sourceMappingURL=worktree-resume.d.ts.map
|
|
@@ -1179,4 +1179,20 @@ export async function runVerifyStoryDeliverables(args, hqRoot, io = {}) {
|
|
|
1179
1179
|
stdout(io, `verify-story-deliverables: ${storyId} — all ${refs.length} reference(s) present (${declaredCount} declared, ${refs.length - declaredCount} from worker evidence)${recordedSuffix}\n`);
|
|
1180
1180
|
return 0;
|
|
1181
1181
|
}
|
|
1182
|
+
/** Fast-entry adapter; family gating and shell fallback happen before import. */
|
|
1183
|
+
export async function runWorktreeResumeFast(args, _stdin, cwd, hqRoot, commandName) {
|
|
1184
|
+
const root = hqRoot ?? cwd;
|
|
1185
|
+
let code;
|
|
1186
|
+
if (commandName === "worktree-gc")
|
|
1187
|
+
code = runWorktreeGc(args, root);
|
|
1188
|
+
else if (commandName === "clean-worktree-reconcile-handoff")
|
|
1189
|
+
code = runCleanWorktreeReconcileHandoff(args);
|
|
1190
|
+
else if (commandName === "resume-thread-lock")
|
|
1191
|
+
code = runResumeThreadLock(args, root);
|
|
1192
|
+
else
|
|
1193
|
+
code = await runVerifyStoryDeliverables(args, root);
|
|
1194
|
+
if (code !== 0)
|
|
1195
|
+
process.exitCode = code;
|
|
1196
|
+
return code;
|
|
1197
|
+
}
|
|
1182
1198
|
//# sourceMappingURL=worktree-resume.js.map
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* back. A unit whose stop fails is left where it was and not recorded.
|
|
10
10
|
*
|
|
11
11
|
* Units owned by another product (the desktop app's sync agent) are never
|
|
12
|
-
* touched, and box system units need root.
|
|
12
|
+
* touched, and box system units need root. Root on a box stops and restarts
|
|
13
|
+
* the login user's own units through that user's systemd manager, since
|
|
14
|
+
* root's `systemctl --user` reaches only root's. System units are recorded and
|
|
13
15
|
* saved in root-owned storage (not the login user's daemon folder, which the
|
|
14
16
|
* user can edit), and a restore checks each one lands in the system unit
|
|
15
17
|
* folder, so nobody but root decides what gets installed there.
|
|
@@ -33,6 +35,11 @@ export interface MigrateDeps {
|
|
|
33
35
|
systemRecordPath: string;
|
|
34
36
|
/** Where box system units live (/etc/systemd/system); restores must land here. */
|
|
35
37
|
systemUnitDir: string;
|
|
38
|
+
/** Root on a box: the login user whose systemd user units these are. */
|
|
39
|
+
userUnitsOwner?: {
|
|
40
|
+
user: string;
|
|
41
|
+
uid: number;
|
|
42
|
+
};
|
|
36
43
|
now?: () => number;
|
|
37
44
|
/** Tests inject a failing writer. */
|
|
38
45
|
writeRecord?: (recordPath: string, units: AdoptedUnit[]) => void;
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* back. A unit whose stop fails is left where it was and not recorded.
|
|
10
10
|
*
|
|
11
11
|
* Units owned by another product (the desktop app's sync agent) are never
|
|
12
|
-
* touched, and box system units need root.
|
|
12
|
+
* touched, and box system units need root. Root on a box stops and restarts
|
|
13
|
+
* the login user's own units through that user's systemd manager, since
|
|
14
|
+
* root's `systemctl --user` reaches only root's. System units are recorded and
|
|
13
15
|
* saved in root-owned storage (not the login user's daemon folder, which the
|
|
14
16
|
* user can edit), and a restore checks each one lands in the system unit
|
|
15
17
|
* folder, so nobody but root decides what gets installed there.
|
|
@@ -56,6 +58,18 @@ function must(run, cmd, args) {
|
|
|
56
58
|
}
|
|
57
59
|
return r.stdout;
|
|
58
60
|
}
|
|
61
|
+
/** `systemctl` for a unit: `--user` units go to their owner's manager, as that user when root acts for them. */
|
|
62
|
+
function systemctl(unit, deps, args) {
|
|
63
|
+
if (unit.kind !== "systemd-user")
|
|
64
|
+
return ["systemctl", args];
|
|
65
|
+
const owner = deps.isRoot ? deps.userUnitsOwner : undefined;
|
|
66
|
+
if (!owner)
|
|
67
|
+
return ["systemctl", ["--user", ...args]];
|
|
68
|
+
return [
|
|
69
|
+
"runuser",
|
|
70
|
+
["-u", owner.user, "--", "env", `XDG_RUNTIME_DIR=/run/user/${owner.uid}`, "systemctl", "--user", ...args],
|
|
71
|
+
];
|
|
72
|
+
}
|
|
59
73
|
function moveFile(from, to) {
|
|
60
74
|
ensurePrivateDir(path.dirname(to));
|
|
61
75
|
try {
|
|
@@ -92,10 +106,9 @@ function adoptOne(unit, deps) {
|
|
|
92
106
|
case "systemd-system": {
|
|
93
107
|
if (!unit.path)
|
|
94
108
|
throw new Error(`no unit file path for ${unit.name}`);
|
|
95
|
-
|
|
96
|
-
must(deps.run, "systemctl", [...scope, "disable", "--now", unit.name]);
|
|
109
|
+
must(deps.run, ...systemctl(unit, deps, ["disable", "--now", unit.name]));
|
|
97
110
|
moveFile(unit.path, to);
|
|
98
|
-
deps.run(
|
|
111
|
+
deps.run(...systemctl(unit, deps, ["daemon-reload"]));
|
|
99
112
|
return to;
|
|
100
113
|
}
|
|
101
114
|
case "schtasks": {
|
|
@@ -190,10 +203,9 @@ function restoreOne(unit, deps) {
|
|
|
190
203
|
if (unit.kind === "systemd-system" && !deps.isRoot) {
|
|
191
204
|
throw new Error("a system unit; run `sudo hq daemon uninstall --restore-legacy`");
|
|
192
205
|
}
|
|
193
|
-
const scope = unit.kind === "systemd-user" ? ["--user"] : [];
|
|
194
206
|
moveBack(unit, unit.path);
|
|
195
|
-
deps.run(
|
|
196
|
-
must(deps.run,
|
|
207
|
+
deps.run(...systemctl(unit, deps, ["daemon-reload"]));
|
|
208
|
+
must(deps.run, ...systemctl(unit, deps, ["enable", "--now", unit.name]));
|
|
197
209
|
return;
|
|
198
210
|
}
|
|
199
211
|
case "schtasks":
|
|
@@ -460,6 +460,15 @@ function requiredFromFirstCommand(first) {
|
|
|
460
460
|
k += 1;
|
|
461
461
|
if (k >= first.length)
|
|
462
462
|
return { required, gatedHookId };
|
|
463
|
+
// SessionStart can use env to set BASH_ENV before Bash itself starts.
|
|
464
|
+
// Walk through that launcher and its assignments just like the shell scanner.
|
|
465
|
+
if (baseName(first[k]) === "env") {
|
|
466
|
+
k += 1;
|
|
467
|
+
while (k < first.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(first[k]))
|
|
468
|
+
k += 1;
|
|
469
|
+
if (k >= first.length || first[k].startsWith("-"))
|
|
470
|
+
return { required, gatedHookId };
|
|
471
|
+
}
|
|
463
472
|
if (INTERPRETERS.has(baseName(first[k]))) {
|
|
464
473
|
k += 1;
|
|
465
474
|
while (k < first.length && first[k].startsWith("-")) {
|