@indigoai-us/hq-cli 5.345.159 → 5.345.161

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 (104) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/assets/scaffold/core/scripts/session-journal.sh +116 -24
  3. package/dist/command-catalog.generated.d.ts +222 -9
  4. package/dist/command-catalog.generated.js +273 -9
  5. package/dist/commands/access.d.ts +6 -0
  6. package/dist/commands/access.js +18 -1
  7. package/dist/commands/acl-lock.d.ts +41 -0
  8. package/dist/commands/acl-lock.js +28 -0
  9. package/dist/commands/agent-enroll.d.ts +2 -0
  10. package/dist/commands/agent-enroll.js +9 -4
  11. package/dist/commands/agents.js +5 -11
  12. package/dist/commands/auth.js +6 -5
  13. package/dist/commands/billing-sponsor.d.ts +4 -4
  14. package/dist/commands/billing-sponsor.js +11 -11
  15. package/dist/commands/billing-usage.d.ts +22 -8
  16. package/dist/commands/billing-usage.js +57 -22
  17. package/dist/commands/billing.d.ts +7 -7
  18. package/dist/commands/billing.js +13 -13
  19. package/dist/commands/cloud-provision.js +2 -2
  20. package/dist/commands/cloud.js +7 -7
  21. package/dist/commands/core-session-helper-runtime.d.ts +1 -0
  22. package/dist/commands/core-session-journal-native.d.ts +1 -0
  23. package/dist/commands/core-session-journal-native.js +158 -34
  24. package/dist/commands/db-remote-deps.js +12 -1
  25. package/dist/commands/feedback.d.ts +12 -0
  26. package/dist/commands/feedback.js +52 -0
  27. package/dist/commands/files-browse.d.ts +8 -0
  28. package/dist/commands/files-browse.js +83 -1
  29. package/dist/commands/files.js +19 -7
  30. package/dist/commands/install-global.d.ts +2 -0
  31. package/dist/commands/install-global.js +24 -1
  32. package/dist/commands/integrations-connect.js +1 -0
  33. package/dist/commands/integrations-manage.d.ts +2 -0
  34. package/dist/commands/integrations-manage.js +6 -4
  35. package/dist/commands/lanes-link.js +5 -3
  36. package/dist/commands/lanes-session-start.d.ts +12 -0
  37. package/dist/commands/lanes-session-start.js +93 -12
  38. package/dist/commands/meetings.d.ts +15 -4
  39. package/dist/commands/meetings.js +58 -19
  40. package/dist/commands/skill.js +6 -6
  41. package/dist/commands/whoami.js +1 -1
  42. package/dist/hook-engine-path.js +13 -2
  43. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +3236 -1988
  44. package/dist/hq-cloud-sync-runner/package.json +1 -1
  45. package/dist/index.js +21 -0
  46. package/dist/lanes-session-start-argv.d.ts +7 -0
  47. package/dist/lanes-session-start-argv.js +9 -0
  48. package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
  49. package/dist/lib/billing/plan-limit-refusals.js +26 -3
  50. package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
  51. package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
  52. package/dist/lib/billing/plan-lock.d.ts +22 -6
  53. package/dist/lib/billing/plan-lock.js +58 -30
  54. package/dist/lib/billing/usage-cost.d.ts +24 -2
  55. package/dist/lib/billing/usage-cost.js +103 -3
  56. package/dist/lib/billing/usage-only.d.ts +37 -1
  57. package/dist/lib/billing/usage-only.js +89 -4
  58. package/dist/lib/billing/usage-pause.d.ts +27 -0
  59. package/dist/lib/billing/usage-pause.js +50 -0
  60. package/dist/lib/bot/log.d.ts +3 -2
  61. package/dist/lib/bot/progress.d.ts +23 -1
  62. package/dist/lib/bot/progress.js +43 -5
  63. package/dist/lib/bot/run.js +2 -0
  64. package/dist/lib/bot/runtime/claude.d.ts +12 -0
  65. package/dist/lib/bot/runtime/claude.js +20 -0
  66. package/dist/lib/bot/runtime/index.d.ts +10 -1
  67. package/dist/lib/bot/runtime/index.js +14 -2
  68. package/dist/lib/bot/runtime/types.d.ts +8 -0
  69. package/dist/lib/db/control-plane.d.ts +5 -0
  70. package/dist/lib/db/control-plane.js +10 -0
  71. package/dist/lib/doctor/checks/ps1-shims.d.ts +21 -0
  72. package/dist/lib/doctor/checks/ps1-shims.js +82 -0
  73. package/dist/lib/doctor/fix/apply.d.ts +13 -1
  74. package/dist/lib/doctor/fix/apply.js +38 -3
  75. package/dist/lib/doctor/fix/remediation.d.ts +9 -1
  76. package/dist/lib/doctor/fix/remediation.js +22 -0
  77. package/dist/lib/doctor/registry.js +2 -0
  78. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  79. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  80. package/dist/lib/hook-engine/resolve.d.ts +14 -0
  81. package/dist/lib/hook-engine/resolve.js +55 -1
  82. package/dist/lib/lanes/links.d.ts +2 -1
  83. package/dist/lib/lanes/links.js +235 -17
  84. package/dist/lib/lanes/registry-flag.d.ts +24 -0
  85. package/dist/lib/lanes/registry-flag.js +72 -6
  86. package/dist/lib/lanes/store.d.ts +4 -2
  87. package/dist/lib/lanes/store.js +8 -8
  88. package/dist/lib/plan-limit-nag.d.ts +0 -16
  89. package/dist/lib/plan-limit-nag.js +17 -18
  90. package/dist/lib/windows/ps1-shims.d.ts +60 -0
  91. package/dist/lib/windows/ps1-shims.js +152 -0
  92. package/dist/main.d.ts +0 -2
  93. package/dist/main.js +19 -57
  94. package/dist/utils/plan-gate-error.d.ts +30 -14
  95. package/dist/utils/plan-gate-error.js +60 -46
  96. package/dist/utils/self-update.d.ts +2 -0
  97. package/dist/utils/self-update.js +8 -0
  98. package/dist/utils/staged-npm-install.js +13 -2
  99. package/dist/utils/team-upgrade.d.ts +19 -6
  100. package/dist/utils/team-upgrade.js +37 -39
  101. package/dist/utils/token-keychain.d.ts +11 -8
  102. package/dist/utils/token-keychain.js +36 -15
  103. package/package.json +8 -8
  104. package/scripts/install-hook-engine.mjs +12 -0
@@ -146,6 +146,14 @@ export interface BotRuntime {
146
146
  resolveBinary?(env: NodeJS.ProcessEnv, cwd?: string, platform?: NodeJS.Platform): string;
147
147
  /** Optional full prompt stream sent through stdin instead of argv. */
148
148
  stdinInput?(input: RuntimeTurnInput): string | undefined;
149
+ /**
150
+ * Optional: runtime-specific defaults layered onto the turn's environment
151
+ * (per-tool safety defaults the CLI honours, for example). Applied in
152
+ * `runRuntimeTurn` on top of the env that `turnEnv` in run.ts built from
153
+ * the bot's identity rules; it only adds runtime keys and never changes
154
+ * who the turn acts as. Pure; must not mutate `env`.
155
+ */
156
+ runtimeEnv?(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
149
157
  /** Optional bidirectional process protocol for this runtime. */
150
158
  spawnProtocol?(input: RuntimeTurnInput): SpawnProtocol | undefined;
151
159
  /** Build the argv for one turn. Pure. */
@@ -1,3 +1,4 @@
1
+ import { type UsagePause } from "../billing/usage-pause.js";
1
2
  /**
2
3
  * Control-plane client for remote vault DB (US-009).
3
4
  * Injectable fetch for tests — never logs response bodies that might hold secrets.
@@ -167,5 +168,9 @@ export type ControlPlaneHttpError = Error & {
167
168
  status?: number;
168
169
  code?: string;
169
170
  reason?: string;
171
+ /** The 402 usage pause (hq-pro `usagePause`), when the server sent one. */
172
+ usagePause?: UsagePause;
173
+ /** The body's customer sentence (`message`), when it sent one. */
174
+ serverMessage?: string;
170
175
  };
171
176
  //# sourceMappingURL=control-plane.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { planGateErrorFromPayload } from "../../utils/plan-gate-error.js";
2
+ import { usagePauseFromBody } from "../billing/usage-pause.js";
2
3
  // Provision may allocate remote resources, so allow two minutes and never retry.
3
4
  const DB_PROVISION_TIMEOUT_MS = 120_000;
4
5
  // Status is a read-only probe; 15 seconds is longer than its normal response time.
@@ -195,6 +196,8 @@ function httpError(label, status, text) {
195
196
  let msg = `${label} failed (${status})`;
196
197
  let code;
197
198
  let reason;
199
+ let usagePause = null;
200
+ let serverMessage;
198
201
  try {
199
202
  const j = JSON.parse(text);
200
203
  if (typeof j.error === "string")
@@ -203,6 +206,9 @@ function httpError(label, status, text) {
203
206
  code = j.code;
204
207
  if (typeof j.reason === "string")
205
208
  reason = j.reason;
209
+ if (typeof j.message === "string" && j.message.trim())
210
+ serverMessage = j.message.trim();
211
+ usagePause = usagePauseFromBody(j);
206
212
  }
207
213
  catch {
208
214
  /* keep */
@@ -213,6 +219,10 @@ function httpError(label, status, text) {
213
219
  err.code = code;
214
220
  if (reason)
215
221
  err.reason = reason;
222
+ if (usagePause)
223
+ err.usagePause = usagePause;
224
+ if (serverMessage)
225
+ err.serverMessage = serverMessage;
216
226
  return err;
217
227
  }
218
228
  function personalHttpError(label, status, text) {
@@ -0,0 +1,21 @@
1
+ import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
2
+ export declare const PS1_SHIM_FAMILY_ID = "cli-ps1-shim";
3
+ export declare const PS1_SHIM_CHECK_ID = "cli-ps1-shim.blocked-by-policy";
4
+ export interface Ps1ShimCheckDeps {
5
+ platform: NodeJS.Platform;
6
+ /** The npm prefix holding `hq.cmd`, or null when hq is not an npm global install. */
7
+ binDir: () => string | null;
8
+ /** The npm-generated hq-cli `.ps1` launchers present in the prefix. */
9
+ findShims: (dir: string) => string[];
10
+ /** The effective PowerShell execution policy, or null when it could not be read. */
11
+ readPolicy: () => string | null;
12
+ }
13
+ /**
14
+ * Windows: npm writes `hq.ps1` next to `hq.cmd`, and PowerShell prefers the
15
+ * `.ps1`. Under a policy that blocks unsigned scripts (the Windows client
16
+ * default) `hq` then fails with UnauthorizedAccess. This check only reads:
17
+ * it never changes the execution policy.
18
+ */
19
+ export declare const ps1ShimsFamily: CheckFamily;
20
+ export declare function checkPs1Shims(_context: CheckContext, deps?: Ps1ShimCheckDeps): CheckResult[];
21
+ //# sourceMappingURL=ps1-shims.d.ts.map
@@ -0,0 +1,82 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { npmShimDir } from "../../windows/user-path.js";
4
+ import { findHqCliPs1Shims, policyBlocksUnsignedScripts, readExecutionPolicy } from "../../windows/ps1-shims.js";
5
+ export const PS1_SHIM_FAMILY_ID = "cli-ps1-shim";
6
+ export const PS1_SHIM_CHECK_ID = "cli-ps1-shim.blocked-by-policy";
7
+ const defaultDeps = {
8
+ platform: process.platform,
9
+ binDir: () => npmShimDir(process.argv[1], fs.existsSync),
10
+ findShims: (dir) => findHqCliPs1Shims(dir),
11
+ readPolicy: () => readExecutionPolicy(),
12
+ };
13
+ const REMEDIATION = "Run `hq.cmd doctor --fix` (or `hq.cmd install --global`) to remove the hq .ps1 launchers. " +
14
+ "Until then, type `hq.cmd` instead of `hq` in PowerShell. Your execution policy is not changed.";
15
+ /**
16
+ * Windows: npm writes `hq.ps1` next to `hq.cmd`, and PowerShell prefers the
17
+ * `.ps1`. Under a policy that blocks unsigned scripts (the Windows client
18
+ * default) `hq` then fails with UnauthorizedAccess. This check only reads:
19
+ * it never changes the execution policy.
20
+ */
21
+ export const ps1ShimsFamily = {
22
+ id: PS1_SHIM_FAMILY_ID,
23
+ title: "hq PowerShell launchers",
24
+ run: (context) => Promise.resolve(checkPs1Shims(context)),
25
+ };
26
+ export function checkPs1Shims(_context, deps = defaultDeps) {
27
+ const dir = deps.platform === "win32" ? deps.binDir() : null;
28
+ if (!dir) {
29
+ return [
30
+ {
31
+ status: "NA",
32
+ checkId: PS1_SHIM_CHECK_ID,
33
+ message: "Not a Windows npm global install, so there are no hq PowerShell launchers to check.",
34
+ },
35
+ ];
36
+ }
37
+ const shims = deps.findShims(dir);
38
+ if (shims.length === 0) {
39
+ return [
40
+ {
41
+ status: "PASS",
42
+ checkId: PS1_SHIM_CHECK_ID,
43
+ target: dir,
44
+ message: `No hq .ps1 launchers in ${dir}; PowerShell runs hq.cmd.`,
45
+ },
46
+ ];
47
+ }
48
+ const names = shims.map((file) => path.win32.basename(file)).join(", ");
49
+ const policy = deps.readPolicy();
50
+ if (policy === null) {
51
+ return [
52
+ {
53
+ status: "UNKNOWN",
54
+ checkId: PS1_SHIM_CHECK_ID,
55
+ target: shims[0],
56
+ message: `Found ${names} in ${dir} but could not read the PowerShell execution policy. If \`hq\` fails with UnauthorizedAccess, use \`hq.cmd\`.`,
57
+ remediation: REMEDIATION,
58
+ },
59
+ ];
60
+ }
61
+ if (!policyBlocksUnsignedScripts(policy)) {
62
+ return [
63
+ {
64
+ status: "PASS",
65
+ checkId: PS1_SHIM_CHECK_ID,
66
+ target: shims[0],
67
+ message: `${names} can run under the ${policy} execution policy.`,
68
+ },
69
+ ];
70
+ }
71
+ return [
72
+ {
73
+ status: "WARN",
74
+ checkId: PS1_SHIM_CHECK_ID,
75
+ target: shims[0],
76
+ message: `PowerShell runs ${names} for \`hq\`, and the ${policy} execution policy blocks it, so \`hq\` fails with UnauthorizedAccess.`,
77
+ details: { policy, shims },
78
+ remediation: REMEDIATION,
79
+ },
80
+ ];
81
+ }
82
+ //# sourceMappingURL=ps1-shims.js.map
@@ -17,7 +17,10 @@
17
17
  * execute bit, add a hook id to the gate profiles it is missing from,
18
18
  * re-register a script present on disk, or pull a company or personal
19
19
  * vault whose local tree exists but whose journal is stale — and NEVER
20
- * rewrites a hook body or deletes a file. Classification is owned by
20
+ * rewrites a hook body or deletes a file, with one scoped exception: on
21
+ * Windows, `remove-ps1-shims` deletes npm-generated hq-cli `.ps1`
22
+ * launchers (and only files whose content is that shim) from the npm
23
+ * prefix, outside the HQ tree. Classification is owned by
21
24
  * {@link deriveRemediation};
22
25
  * a content-drift finding is manual-only and simply never appears in the
23
26
  * fixable set.
@@ -38,6 +41,7 @@ import { type SpawnSyncReturns } from "node:child_process";
38
41
  import type { CheckResult, DoctorStatus } from "../types.js";
39
42
  import { type GateProfile } from "../hook-gate-profiles.js";
40
43
  import { type FixClass } from "./remediation.js";
44
+ import { type RemovePs1ShimsResult } from "../../windows/ps1-shims.js";
41
45
  import { type RefreshSyncOutcome } from "./refresh-sync-outcome.js";
42
46
  /** The tree subtrees whose uncommitted changes block a `--fix` run. */
43
47
  export declare const HOOK_CONFIG_DIRS: readonly [".claude", ".codex", ".grok"];
@@ -109,6 +113,12 @@ export interface ApplyFixesOptions {
109
113
  from: string | null;
110
114
  to: string;
111
115
  } | null;
116
+ /**
117
+ * Removal used by the `remove-ps1-shims` class: delete npm's hq-cli `.ps1`
118
+ * launchers from the given npm prefix (Windows only; other files and the
119
+ * execution policy are left alone). Tests inject a stub.
120
+ */
121
+ removePs1Shims?: (dir: string) => RemovePs1ShimsResult;
112
122
  }
113
123
  /** One company or personal vault the `refresh-sync` class will pull. */
114
124
  export interface RefreshSyncTarget {
@@ -311,6 +321,8 @@ export declare function defaultRewriteBotNodePath(name: string): {
311
321
  from: string;
312
322
  to: string;
313
323
  } | null;
324
+ /** Default `remove-ps1-shims` repair: Windows only, a no-op elsewhere. */
325
+ export declare function defaultRemovePs1Shims(dir: string): RemovePs1ShimsResult;
314
326
  /**
315
327
  * An hq path a bot may be pointed at unattended: an installed package entry
316
328
  * or its stable shim, never a worktree or one-off build that happens to be
@@ -17,7 +17,10 @@
17
17
  * execute bit, add a hook id to the gate profiles it is missing from,
18
18
  * re-register a script present on disk, or pull a company or personal
19
19
  * vault whose local tree exists but whose journal is stale — and NEVER
20
- * rewrites a hook body or deletes a file. Classification is owned by
20
+ * rewrites a hook body or deletes a file, with one scoped exception: on
21
+ * Windows, `remove-ps1-shims` deletes npm-generated hq-cli `.ps1`
22
+ * launchers (and only files whose content is that shim) from the npm
23
+ * prefix, outside the HQ tree. Classification is owned by
21
24
  * {@link deriveRemediation};
22
25
  * a content-drift finding is manual-only and simply never appears in the
23
26
  * fixable set.
@@ -44,6 +47,7 @@ import { parseHookGateProfiles, requiredMissingProfiles, } from "../hook-gate-pr
44
47
  import { createBackup } from "./backup.js";
45
48
  import { removeStrayGateEntries } from "../stray-gate-entries.js";
46
49
  import { deriveRemediation } from "./remediation.js";
50
+ import { HQ_CLI_BINS, removeHqCliPs1Shims } from "../../windows/ps1-shims.js";
47
51
  import { botDir } from "../../bot/paths.js";
48
52
  import { readBotConfig } from "../../bot/config.js";
49
53
  import { buildBotDaemonPaths, installedDaemonHqBinary, repairBotDaemonIfStale, rewriteBotDaemonNodePath, } from "../../bot/daemon.js";
@@ -197,6 +201,7 @@ export async function applyFixes(options) {
197
201
  };
198
202
  const rewriteBotNodePath = options.rewriteBotNodePath ?? defaultRewriteBotNodePath;
199
203
  const repairBotHqBinary = options.repairBotHqBinary ?? defaultRepairBotHqBinary;
204
+ const removePs1Shims = options.removePs1Shims ?? defaultRemovePs1Shims;
200
205
  // 2. Collect findings, then plan each newly unlocked allowlisted repair.
201
206
  // Identity is deliberately the check id + target pair: an unsuccessful
202
207
  // repair must not be applied repeatedly in one invocation.
@@ -238,7 +243,7 @@ export async function applyFixes(options) {
238
243
  const rem = deriveRemediation(result);
239
244
  if (!rem?.autoFixable)
240
245
  continue;
241
- const plan = planFix(hqRoot, result, rem, refreshSync, rewriteBotNodePath, repairBotHqBinary, refreshManifest);
246
+ const plan = planFix(hqRoot, result, rem, refreshSync, rewriteBotNodePath, repairBotHqBinary, refreshManifest, removePs1Shims);
242
247
  if (!plan)
243
248
  continue;
244
249
  const key = JSON.stringify([plan.checkId, plan.target]);
@@ -683,8 +688,10 @@ export function uncommittedHookConfigChanges(hqRoot) {
683
688
  }
684
689
  // --- Fix planning --------------------------------------------------------------
685
690
  /** Build the concrete plan for one auto-fixable finding, or null if unplannable. */
686
- function planFix(hqRoot, result, rem, refreshSync, rewriteBotNodePath = defaultRewriteBotNodePath, repairBotHqBinary = defaultRepairBotHqBinary, refreshManifest = defaultRefreshManifest) {
691
+ function planFix(hqRoot, result, rem, refreshSync, rewriteBotNodePath = defaultRewriteBotNodePath, repairBotHqBinary = defaultRepairBotHqBinary, refreshManifest = defaultRefreshManifest, removePs1Shims = defaultRemovePs1Shims) {
687
692
  switch (rem.fixClass) {
693
+ case "remove-ps1-shims":
694
+ return planRemovePs1Shims(result, rem, removePs1Shims);
688
695
  case "bot-node-path":
689
696
  return planBotNodePath(result, rem, rewriteBotNodePath);
690
697
  case "bot-hq-binary":
@@ -753,6 +760,34 @@ function planBotHqBinary(result, rem, repair) {
753
760
  postStatus: (results) => results.find((entry) => entry.checkId === result.checkId)?.status ?? null,
754
761
  };
755
762
  }
763
+ /** Remove npm's hq-cli `.ps1` launchers so PowerShell falls back to `hq.cmd`. */
764
+ function planRemovePs1Shims(result, rem, remove) {
765
+ const dir = rem.fixTarget;
766
+ if (!dir)
767
+ return null;
768
+ let failure;
769
+ return {
770
+ checkId: result.checkId,
771
+ fixClass: "remove-ps1-shims",
772
+ target: dir,
773
+ relpath: "",
774
+ preview: Object.keys(HQ_CLI_BINS).map((bin) => ` - ${path.win32.join(dir, `${bin}.ps1`)}`).join("\n") +
775
+ "\n only npm's hq-cli launchers are removed; hq.cmd stays and the execution policy is not changed",
776
+ summary: "remove hq .ps1 launchers",
777
+ apply: () => {
778
+ const outcome = remove(dir);
779
+ failure = outcome.failed.length > 0 ? outcome.failed.map((entry) => entry.reason).join("; ") : undefined;
780
+ },
781
+ postStatus: (results) => results.find((entry) => entry.checkId === result.checkId)?.status ?? null,
782
+ failureReason: () => failure,
783
+ };
784
+ }
785
+ /** Default `remove-ps1-shims` repair: Windows only, a no-op elsewhere. */
786
+ export function defaultRemovePs1Shims(dir) {
787
+ if (process.platform !== "win32")
788
+ return { removed: [], kept: [], failed: [] };
789
+ return removeHqCliPs1Shims(dir);
790
+ }
756
791
  /** Real launchctl for the `bot-hq-binary` reload; a failure is a non-ok result. */
757
792
  const doctorLaunchctl = (args) => {
758
793
  const r = spawnSync("launchctl", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
@@ -35,6 +35,14 @@
35
35
  * and hashes); it never writes the HQ tree. A company
36
36
  * scope whose slug did not resolve locally stays
37
37
  * manual-only.
38
+ * - `remove-ps1-shims` — Windows only: delete the npm-generated `hq.ps1` /
39
+ * `hq-auth-refresh.ps1` launchers from the npm prefix
40
+ * when the PowerShell execution policy blocks them.
41
+ * `hq.cmd` stays and PowerShell falls back to it. The
42
+ * removal only touches files whose content is npm's
43
+ * hq-cli shim (see `lib/windows/ps1-shims.ts`); the
44
+ * execution policy is never changed. This is the one
45
+ * scoped exception to the no-delete rule below.
38
46
  *
39
47
  * EVERYTHING else — content drift between platform copies, a missing script, a
40
48
  * missing Codex counterpart, an unquoted `$CLAUDE_PROJECT_DIR`, a stale
@@ -46,7 +54,7 @@
46
54
  */
47
55
  import type { CheckResult } from "../types.js";
48
56
  /** The allowlisted safe repair classes `--fix` is permitted to apply. */
49
- export type FixClass = "executable-bit" | "gate-profile" | "remove-stray-gate-entries" | "refresh-sync" | "refresh-manifest" | "bot-node-path" | "bot-hq-binary";
57
+ export type FixClass = "executable-bit" | "gate-profile" | "remove-stray-gate-entries" | "refresh-sync" | "refresh-manifest" | "bot-node-path" | "bot-hq-binary" | "remove-ps1-shims";
50
58
  /**
51
59
  * A finding's structured remediation. `autoFixable`, `action`, and `command`
52
60
  * are the `--json` surface (AC1); `fixTarget`/`fixClass` are the internal handle
@@ -35,6 +35,14 @@
35
35
  * and hashes); it never writes the HQ tree. A company
36
36
  * scope whose slug did not resolve locally stays
37
37
  * manual-only.
38
+ * - `remove-ps1-shims` — Windows only: delete the npm-generated `hq.ps1` /
39
+ * `hq-auth-refresh.ps1` launchers from the npm prefix
40
+ * when the PowerShell execution policy blocks them.
41
+ * `hq.cmd` stays and PowerShell falls back to it. The
42
+ * removal only touches files whose content is npm's
43
+ * hq-cli shim (see `lib/windows/ps1-shims.ts`); the
44
+ * execution policy is never changed. This is the one
45
+ * scoped exception to the no-delete rule below.
38
46
  *
39
47
  * EVERYTHING else — content drift between platform copies, a missing script, a
40
48
  * missing Codex counterpart, an unquoted `$CLAUDE_PROJECT_DIR`, a stale
@@ -44,6 +52,7 @@
44
52
  * content-drift finding is manual-only, because that boundary is load-bearing:
45
53
  * deciding which of two diverged copies is correct needs human judgement.
46
54
  */
55
+ import * as path from "node:path";
47
56
  import { isCompanyJournalSlug, isSafeCompanySlug, isPersonalJournalSlug, journalSlugFromCheckId, } from "../checks/sync-health.js";
48
57
  /** Matches an exec-bit remediation command, quoted or not: `chmod +x <path>`. */
49
58
  const CHMOD_EXEC = /^chmod\s+\+x\s+(.+)$/;
@@ -143,6 +152,19 @@ export function deriveRemediation(result) {
143
152
  command: remediation ?? `hq bot daemon install ${name}`,
144
153
  };
145
154
  }
155
+ // remove-ps1-shims — PowerShell resolves `hq` to npm's hq.ps1 and the
156
+ // execution policy blocks it (feedback_29d91817). The target is the shim;
157
+ // the fix acts on its directory, the npm prefix.
158
+ if (checkId === "cli-ps1-shim.blocked-by-policy" && result.status === "WARN" && target) {
159
+ const dir = path.win32.dirname(target);
160
+ return {
161
+ autoFixable: true,
162
+ fixClass: "remove-ps1-shims",
163
+ fixTarget: dir,
164
+ action: `Remove the npm-generated hq .ps1 launchers from ${dir} so PowerShell runs hq.cmd (the execution policy is not changed).`,
165
+ command: "hq.cmd install --global",
166
+ };
167
+ }
146
168
  // Available hq-core upgrade. WARN is intentional in `hq doctor` and is
147
169
  // not a broken sync; --fix has no mechanical repair for it.
148
170
  if (checkId === "sync.update.core") {
@@ -29,6 +29,7 @@ import { scaffoldCliFloorFamily } from "./checks/scaffold-cli-floor.js";
29
29
  import { strandedLanesFamily } from "./checks/stranded-lanes.js";
30
30
  import { releaseLinksFamily } from "./checks/release-links.js";
31
31
  import { cliPathFamily } from "./checks/cli-path.js";
32
+ import { ps1ShimsFamily } from "./checks/ps1-shims.js";
32
33
  import { tempHomesFamily } from "./checks/temp-homes.js";
33
34
  import { fixtureCoverageFamily } from "./fixtures/discover.js";
34
35
  import { checkClaudeWiring } from "./checks/claude-wiring.js";
@@ -280,6 +281,7 @@ export function createDefaultRegistry() {
280
281
  // Windows: the npm hq.cmd shim folder must be on PATH for a new terminal to
281
282
  // find hq. Registry read only; prints the fix, never edits PATH.
282
283
  registry.register(cliPathFamily);
284
+ registry.register(ps1ShimsFamily);
283
285
  // Idle tmp.* throwaway homes holding a Rust toolchain download. Dry-run
284
286
  // scan only; `hq doctor --sweep-temp-homes` is the delete path.
285
287
  registry.register(tempHomesFamily);