@indigoai-us/hq-cli 5.315.0 → 5.317.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/assets/scaffold/core/scripts/detect-stale-review-base.sh +199 -0
  3. package/assets/scaffold/core/scripts/repo-run-registry.sh +408 -0
  4. package/assets/scaffold/core/scripts/session-journal.sh +301 -0
  5. package/assets/scaffold/core/scripts/session-project.sh +800 -0
  6. package/assets/scaffold/core/scripts/session-title-config.sh +76 -0
  7. package/assets/scaffold/core/scripts/session-title.sh +305 -0
  8. package/assets/scaffold/core/scripts/share-suggestion-state.sh +541 -0
  9. package/dist/command-catalog.generated.d.ts +97 -0
  10. package/dist/command-catalog.generated.js +120 -0
  11. package/dist/commands/bot-jobs.d.ts +5 -1
  12. package/dist/commands/bot-jobs.js +24 -4
  13. package/dist/commands/core-detect-stale-review-base-native.d.ts +23 -0
  14. package/dist/commands/core-detect-stale-review-base-native.js +178 -0
  15. package/dist/commands/core-hook-state-helpers-native.d.ts +11 -0
  16. package/dist/commands/core-hook-state-helpers-native.js +74 -0
  17. package/dist/commands/core-repo-run-registry-native.d.ts +19 -0
  18. package/dist/commands/core-repo-run-registry-native.js +357 -0
  19. package/dist/commands/core-session-helper-runtime.d.ts +10 -0
  20. package/dist/commands/core-session-helper-runtime.js +7 -0
  21. package/dist/commands/core-session-journal-native.d.ts +4 -0
  22. package/dist/commands/core-session-journal-native.js +327 -0
  23. package/dist/commands/core-session-project-native.d.ts +3 -0
  24. package/dist/commands/core-session-project-native.js +855 -0
  25. package/dist/commands/core-session-title-config-native.d.ts +4 -0
  26. package/dist/commands/core-session-title-config-native.js +77 -0
  27. package/dist/commands/core-session-title-native.d.ts +3 -0
  28. package/dist/commands/core-session-title-native.js +306 -0
  29. package/dist/commands/core-share-suggestion-state-native.d.ts +18 -0
  30. package/dist/commands/core-share-suggestion-state-native.js +445 -0
  31. package/dist/commands/core.js +66 -0
  32. package/dist/commands/lanes.js +35 -5
  33. package/dist/fast-core.d.ts +1 -0
  34. package/dist/fast-core.js +10 -6
  35. package/dist/fast-scaffold.js +33 -0
  36. package/dist/lib/bot/job-quiet-flag.d.ts +4 -0
  37. package/dist/lib/bot/job-quiet-flag.js +14 -0
  38. package/dist/lib/bot/job-run.d.ts +5 -0
  39. package/dist/lib/bot/job-run.js +23 -3
  40. package/dist/lib/bot/run.d.ts +2 -0
  41. package/dist/lib/bot/run.js +15 -3
  42. package/dist/lib/core-utils/common.d.ts +0 -1
  43. package/dist/lib/core-utils/common.js +0 -9
  44. package/dist/lib/lanes/autopilot.d.ts +5 -1
  45. package/dist/lib/lanes/autopilot.js +72 -3
  46. package/dist/lib/lanes/disk-guard.d.ts +3 -4
  47. package/dist/lib/lanes/disk-guard.js +2 -2
  48. package/dist/lib/lanes/flag-keys.d.ts +3 -0
  49. package/dist/lib/lanes/flag-keys.js +3 -0
  50. package/dist/lib/lanes/hooks-install.js +78 -2
  51. package/dist/lib/lanes/keep-alive-audit.d.ts +6 -5
  52. package/dist/lib/lanes/outpost-dispatch.d.ts +9 -0
  53. package/dist/lib/lanes/outpost-dispatch.js +22 -0
  54. package/dist/utils/version-gate.js +8 -7
  55. package/package.json +1 -1
@@ -96,6 +96,82 @@ function groupsContainCommand(groups, command) {
96
96
  return group.hooks.some((hook) => isPlainObject(hook) && hook.type === "command" && hook.command === command);
97
97
  });
98
98
  }
99
+ /** Parse the single-quoted argv form emitted by shellQuote without invoking a shell. */
100
+ function parseShellQuotedArguments(command) {
101
+ const args = [];
102
+ let index = 0;
103
+ while (index < command.length) {
104
+ while (/\s/.test(command[index] ?? ""))
105
+ index += 1;
106
+ if (index >= command.length)
107
+ break;
108
+ if (command[index] !== "'")
109
+ return null;
110
+ index += 1;
111
+ let value = "";
112
+ let closed = false;
113
+ while (index < command.length) {
114
+ if (command.startsWith("'\\''", index)) {
115
+ value += "'";
116
+ index += 4;
117
+ }
118
+ else if (command[index] === "'") {
119
+ index += 1;
120
+ closed = true;
121
+ break;
122
+ }
123
+ else {
124
+ value += command[index];
125
+ index += 1;
126
+ }
127
+ }
128
+ if (!closed)
129
+ return null;
130
+ args.push(value);
131
+ if (index < command.length && !/\s/.test(command[index]))
132
+ return null;
133
+ }
134
+ return args;
135
+ }
136
+ function isInstalledDrainEntry(entryPath) {
137
+ if (!path.isAbsolute(entryPath))
138
+ return false;
139
+ const segments = path.resolve(entryPath).split(path.sep);
140
+ return segments.slice(-5).join(path.sep) ===
141
+ ["node_modules", "@indigoai-us", "hq-cli", "dist", "lanes-drain-hook.js"].join(path.sep);
142
+ }
143
+ /**
144
+ * The provider settings can outlive a CLI worktree or global package update.
145
+ * Both paths target the same hq-cli drain entry, so recognize an installed
146
+ * package entry alongside the exact entry belonging to this CLI invocation.
147
+ */
148
+ function drainCommandsMatch(candidate, expected) {
149
+ if (candidate === expected)
150
+ return true;
151
+ const argv = parseShellQuotedArguments(candidate);
152
+ if (argv === null || argv.length !== 2)
153
+ return false;
154
+ if (path.parse(argv[0]).name.toLowerCase() !== "node")
155
+ return false;
156
+ if (!isInstalledDrainEntry(argv[1]))
157
+ return false;
158
+ return fs.existsSync(argv[0]) && fs.existsSync(argv[1]);
159
+ }
160
+ function groupsContainDrainCommand(groups, expected) {
161
+ if (!Array.isArray(groups))
162
+ return false;
163
+ return groups.some((group) => {
164
+ if (!isPlainObject(group) || !Array.isArray(group.hooks))
165
+ return false;
166
+ return group.hooks.some((hook) => isPlainObject(hook) &&
167
+ hook.type === "command" &&
168
+ typeof hook.command === "string" &&
169
+ drainCommandsMatch(hook.command, expected));
170
+ });
171
+ }
172
+ function tomlHasDrainCommand(parsed, expected) {
173
+ return isPlainObject(parsed.hooks) && groupsContainDrainCommand(parsed.hooks.PreToolUse, expected);
174
+ }
99
175
  function removeCommandFromGroups(groups, command) {
100
176
  let changed = false;
101
177
  const next = groups.flatMap((group) => {
@@ -413,7 +489,7 @@ export function probeDrainHookRegistration(provider, providerHome) {
413
489
  const toml = parseToml(text);
414
490
  return {
415
491
  ...base,
416
- registered: isPlainObject(toml) && tomlHasCommand(toml, command) && tomlHooksFeatureEnabled(toml),
492
+ registered: isPlainObject(toml) && tomlHasDrainCommand(toml, command) && tomlHooksFeatureEnabled(toml),
417
493
  };
418
494
  }
419
495
  const json = JSON.parse(text);
@@ -421,7 +497,7 @@ export function probeDrainHookRegistration(provider, providerHome) {
421
497
  ...base,
422
498
  registered: isPlainObject(json) &&
423
499
  isPlainObject(json.hooks) &&
424
- groupsContainCommand(json.hooks.PreToolUse, command),
500
+ groupsContainDrainCommand(json.hooks.PreToolUse, command),
425
501
  };
426
502
  }
427
503
  catch (err) {
@@ -1,10 +1,10 @@
1
- export type KeepAliveAuditVerdict = "on_time" | "late" | "early_wake" | "message_wake" | "self_wake" | "early_by_keep_alive" | "not_restarted" | "waiting";
2
- export interface KeepAliveAuditRestart {
1
+ type KeepAliveAuditVerdict = "on_time" | "late" | "early_wake" | "message_wake" | "self_wake" | "early_by_keep_alive" | "not_restarted" | "waiting";
2
+ interface KeepAliveAuditRestart {
3
3
  at: string;
4
4
  trigger: string;
5
5
  caller: string | null;
6
6
  }
7
- export interface KeepAliveAuditRow {
7
+ interface KeepAliveAuditRow {
8
8
  lane: string;
9
9
  project: string;
10
10
  profile: string | null;
@@ -15,7 +15,7 @@ export interface KeepAliveAuditRow {
15
15
  restart: KeepAliveAuditRestart | null;
16
16
  verdict: KeepAliveAuditVerdict;
17
17
  }
18
- export interface KeepAliveAuditSummary {
18
+ interface KeepAliveAuditSummary {
19
19
  by_verdict: Record<KeepAliveAuditVerdict, number>;
20
20
  not_restarted: number;
21
21
  late_over_10m: number;
@@ -23,7 +23,7 @@ export interface KeepAliveAuditSummary {
23
23
  self_wake: number;
24
24
  stopped_and_not_restarted_on_their_own: number;
25
25
  }
26
- export interface KeepAliveAuditError {
26
+ interface KeepAliveAuditError {
27
27
  lane: string;
28
28
  error: string;
29
29
  path: string;
@@ -43,4 +43,5 @@ export declare function buildKeepAliveAudit(input: {
43
43
  laneId?: string;
44
44
  }): KeepAliveAuditResult;
45
45
  export declare function formatKeepAliveAuditText(result: KeepAliveAuditResult): string;
46
+ export {};
46
47
  //# sourceMappingURL=keep-alive-audit.d.ts.map
@@ -95,10 +95,19 @@ export declare const REMOTE_LANE_READ_ENV = "HQ_LANES_REMOTE_READ";
95
95
  export declare function buildOutpostLaneListCommand(input?: {
96
96
  hqBinary?: string;
97
97
  laneIds?: readonly string[];
98
+ laneUlids?: readonly string[];
98
99
  active?: boolean;
99
100
  hierarchy?: string | readonly string[];
100
101
  snapshot?: boolean;
101
102
  }): string;
103
+ /**
104
+ * Older Outpost CLIs do not know --lane-ulid. Filter their lane-list output on
105
+ * the owner host so the exec transport carries only the requested record.
106
+ */
107
+ export declare function buildOutpostLegacyLaneUlidListCommand(input: {
108
+ hqBinary?: string;
109
+ ulid: string;
110
+ }): string;
102
111
  /** Read questions on one Outpost, scoped to a company when supplied. */
103
112
  export declare function buildOutpostQuestionListCommand(input?: {
104
113
  company?: string;
@@ -85,6 +85,8 @@ export function buildOutpostLaneListCommand(input = {}) {
85
85
  ];
86
86
  for (const laneId of input.laneIds ?? [])
87
87
  args.push("--lane", laneId);
88
+ for (const ulid of input.laneUlids ?? [])
89
+ args.push("--lane-ulid", ulid);
88
90
  if (input.active)
89
91
  args.push("--active");
90
92
  for (const laneId of typeof input.hierarchy === "string"
@@ -95,6 +97,26 @@ export function buildOutpostLaneListCommand(input = {}) {
95
97
  args.push("--snapshot");
96
98
  return withRemoteHqDir(joinCommandParts(args));
97
99
  }
100
+ /**
101
+ * Older Outpost CLIs do not know --lane-ulid. Filter their lane-list output on
102
+ * the owner host so the exec transport carries only the requested record.
103
+ */
104
+ export function buildOutpostLegacyLaneUlidListCommand(input) {
105
+ const hqBinary = input.hqBinary?.trim() || "hq";
106
+ const script = [
107
+ "const { spawnSync } = require('node:child_process');",
108
+ `const result = spawnSync(${JSON.stringify(hqBinary)}, ['lanes', 'list', '--json'], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, env: { ...process.env, ${REMOTE_LANE_READ_ENV}: '1' } });`,
109
+ "if (result.error || result.status !== 0) { console.error(JSON.stringify({ error: result.error?.message || 'legacy Outpost lane list failed', status: result.status, signal: result.signal, stderr_tail: String(result.stderr || '').slice(-4096), stdout_tail: String(result.stdout || '').slice(-4096) })); process.exit(result.status || 1); }",
110
+ "try {",
111
+ "const payload = JSON.parse(result.stdout);",
112
+ "if (!payload || payload.ok !== true || payload.action !== 'list' || !Array.isArray(payload.lanes)) throw new Error('malformed lane list');",
113
+ `const ulid = ${JSON.stringify(input.ulid.toUpperCase())};`,
114
+ "payload.lanes = payload.lanes.filter(row => row && typeof row.lane_id === 'string' && row.lane_id.slice(0, 26).toUpperCase() === ulid);",
115
+ "process.stdout.write(JSON.stringify(payload));",
116
+ "} catch (error) { console.error('Legacy Outpost returned invalid lane-list JSON: ' + (error instanceof Error ? error.message : String(error))); process.exit(1); }",
117
+ ].join("\n");
118
+ return withRemoteHqDir(joinCommandParts(["node", "-e", script]));
119
+ }
98
120
  /** Read questions on one Outpost, scoped to a company when supplied. */
99
121
  export function buildOutpostQuestionListCommand(input = {}) {
100
122
  const args = ["hq", "lanes", "questions", "list"];
@@ -1382,7 +1382,9 @@ function attemptRequiredUpdate(decision, deps, install) {
1382
1382
  const command = decision.updateCommand;
1383
1383
  const isManagedOutsideNpm = install.manager !== "npm";
1384
1384
  const prefix = install.prefix;
1385
- const stagedNpmPrefix = Boolean(!isManagedOutsideNpm && prefix && /\s/.test(prefix));
1385
+ // Any npm global install with a known prefix must build away from the live package.
1386
+ // An interrupted in-place npm install can leave hq-cli itself un-runnable.
1387
+ const stagedNpmPrefix = Boolean(!isManagedOutsideNpm && prefix);
1386
1388
  const runner = deps.runner ?? runUpdateCommand;
1387
1389
  // Resolve the concrete install argv once so the sudo fallback below can re-run
1388
1390
  // the EXACT same command under elevation.
@@ -1422,7 +1424,7 @@ function attemptRequiredUpdate(decision, deps, install) {
1422
1424
  else if (prefix) {
1423
1425
  console.error(chalk.dim(` Installing into npm prefix: ${prefix}`));
1424
1426
  if (stagedNpmPrefix) {
1425
- console.error(chalk.dim(" Building in a space-free temporary prefix before activation."));
1427
+ console.error(chalk.dim(" Building outside the active npm prefix before activation."));
1426
1428
  const installStaged = deps.installNpmPackageStaged;
1427
1429
  if (!installStaged) {
1428
1430
  result = { ok: false, detail: "staged npm installer was not loaded" };
@@ -1556,11 +1558,10 @@ export async function enforceVersionGate(onUpdateRecommended, options = {}) {
1556
1558
  // neither downstream path repeats it.
1557
1559
  const install = resolveRunningInstall();
1558
1560
  if (decision.updateRequired) {
1559
- // Load the staged installer only for the uncommon spaced npm-prefix path.
1560
- // The version gate is imported during every CLI startup, including native
1561
- // fast routes, so eagerly importing this filesystem-heavy module adds cost
1562
- // to commands that never update.
1563
- const installNpmPackageStaged = install.manager === "npm" && install.prefix && /\s/.test(install.prefix)
1561
+ // Load the staged installer only when an npm prefix is resolved. The version
1562
+ // gate is imported during every CLI startup, so keep the filesystem-heavy
1563
+ // module off commands that never need a required update.
1564
+ const installNpmPackageStaged = install.manager === "npm" && install.prefix
1564
1565
  ? (await import("./staged-npm-install.js")).installNpmPackageStaged
1565
1566
  : undefined;
1566
1567
  // Exits the process, EXCEPT when another updater holds the shared update
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.315.0",
3
+ "version": "5.317.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {