@indigoai-us/hq-cli 5.345.146 → 5.345.148

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 (138) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +14 -0
  3. package/assets/bot-workers/setup/worker.yaml +11 -0
  4. package/dist/command-catalog.generated.d.ts +326 -4
  5. package/dist/command-catalog.generated.js +403 -4
  6. package/dist/commands/agent-inbox.js +2 -1
  7. package/dist/commands/auth.js +4 -2
  8. package/dist/commands/bot.js +1 -1
  9. package/dist/commands/cloud.js +31 -1
  10. package/dist/commands/core-refresh-vault-access-native.d.ts +17 -0
  11. package/dist/commands/core-refresh-vault-access-native.js +214 -19
  12. package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +9 -0
  13. package/dist/commands/core-work-mesh-bind-trusted-native.js +26 -19
  14. package/dist/commands/core.js +6 -0
  15. package/dist/commands/daemon.d.ts +7 -0
  16. package/dist/commands/daemon.js +37 -3
  17. package/dist/commands/dm.js +9 -2
  18. package/dist/commands/doctor.d.ts +11 -1
  19. package/dist/commands/doctor.js +56 -1
  20. package/dist/commands/files.js +10 -6
  21. package/dist/commands/install-global.d.ts +7 -0
  22. package/dist/commands/install-global.js +129 -35
  23. package/dist/commands/lanes-shared.d.ts +3 -1
  24. package/dist/commands/lanes.d.ts +4 -4
  25. package/dist/commands/lanes.js +380 -25
  26. package/dist/commands/login.js +4 -3
  27. package/dist/commands/pack-install.d.ts +2 -2
  28. package/dist/commands/pack-install.js +133 -35
  29. package/dist/commands/sync-manifest.d.ts +2 -9
  30. package/dist/commands/sync-manifest.js +4 -15
  31. package/dist/hook-engine-path.d.ts +4 -0
  32. package/dist/hook-engine-path.js +36 -0
  33. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2784 -2360
  34. package/dist/hq-cloud-sync-runner/package.json +1 -1
  35. package/dist/index.js +6 -0
  36. package/dist/lib/agent-kit/inbox-redact.d.ts +44 -0
  37. package/dist/lib/agent-kit/inbox-redact.js +361 -0
  38. package/dist/lib/agent-kit/inbox-state.js +11 -2
  39. package/dist/lib/agent-kit/mcp/tools.js +2 -1
  40. package/dist/lib/agent-kit/run/inbox.js +36 -7
  41. package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
  42. package/dist/lib/anywhere-runtime-flag-key.js +9 -0
  43. package/dist/lib/bot/daemon.js +3 -0
  44. package/dist/lib/bot/runtime/index.js +3 -1
  45. package/dist/lib/company-skill-wrapper.js +6 -4
  46. package/dist/lib/daemon/install.d.ts +47 -1
  47. package/dist/lib/daemon/install.js +181 -15
  48. package/dist/lib/daemon/lock.d.ts +32 -0
  49. package/dist/lib/daemon/lock.js +55 -2
  50. package/dist/lib/doctor/__testing__/run-doctor.js +4 -0
  51. package/dist/lib/doctor/checks/anywhere.d.ts +3 -0
  52. package/dist/lib/doctor/checks/anywhere.js +23 -8
  53. package/dist/lib/doctor/checks/cli-path.d.ts +11 -0
  54. package/dist/lib/doctor/checks/cli-path.js +45 -0
  55. package/dist/lib/doctor/checks/hook-engine.d.ts +4 -0
  56. package/dist/lib/doctor/checks/hook-engine.js +16 -0
  57. package/dist/lib/doctor/checks/release-links.d.ts +23 -0
  58. package/dist/lib/doctor/checks/release-links.js +152 -0
  59. package/dist/lib/doctor/checks/sync-health.d.ts +7 -0
  60. package/dist/lib/doctor/checks/sync-health.js +38 -2
  61. package/dist/lib/doctor/checks/temp-homes.d.ts +19 -0
  62. package/dist/lib/doctor/checks/temp-homes.js +54 -0
  63. package/dist/lib/doctor/fix/apply.d.ts +90 -1
  64. package/dist/lib/doctor/fix/apply.js +176 -10
  65. package/dist/lib/doctor/fix/json-output.d.ts +4 -1
  66. package/dist/lib/doctor/fix/json-output.js +6 -0
  67. package/dist/lib/doctor/fix/remediation.d.ts +7 -1
  68. package/dist/lib/doctor/fix/remediation.js +23 -1
  69. package/dist/lib/doctor/registry.js +13 -0
  70. package/dist/lib/doctor/temp-home-sweeper.d.ts +149 -0
  71. package/dist/lib/doctor/temp-home-sweeper.js +418 -0
  72. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  73. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  74. package/dist/lib/flag-registry.js +9 -1
  75. package/dist/lib/flag-snapshot-file.js +8 -3
  76. package/dist/lib/hook-engine/resolve.d.ts +31 -0
  77. package/dist/lib/hook-engine/resolve.js +134 -0
  78. package/dist/lib/lanes/admission-host.d.ts +1 -0
  79. package/dist/lib/lanes/admission-host.js +8 -1
  80. package/dist/lib/lanes/admission.d.ts +4 -0
  81. package/dist/lib/lanes/admission.js +6 -2
  82. package/dist/lib/lanes/envelope-repair-dispatch.d.ts +5 -0
  83. package/dist/lib/lanes/envelope-repair-dispatch.js +12 -1
  84. package/dist/lib/lanes/index.d.ts +2 -2
  85. package/dist/lib/lanes/index.js +1 -1
  86. package/dist/lib/lanes/keep-alive.js +4 -1
  87. package/dist/lib/lanes/local-host-identity.d.ts +40 -0
  88. package/dist/lib/lanes/local-host-identity.js +314 -0
  89. package/dist/lib/lanes/reaper.d.ts +1 -1
  90. package/dist/lib/lanes/reaper.js +7 -0
  91. package/dist/lib/lanes/reconcile.d.ts +4 -0
  92. package/dist/lib/lanes/reconcile.js +16 -8
  93. package/dist/lib/lanes/spawn.d.ts +20 -0
  94. package/dist/lib/lanes/spawn.js +48 -2
  95. package/dist/lib/lanes/status-fields.js +6 -1
  96. package/dist/lib/lanes/store.js +8 -1
  97. package/dist/lib/lanes/throttle.d.ts +49 -6
  98. package/dist/lib/lanes/throttle.js +154 -17
  99. package/dist/lib/lanes/types.d.ts +22 -1
  100. package/dist/lib/lanes/types.js +27 -3
  101. package/dist/lib/lanes/watcher.js +27 -2
  102. package/dist/lib/lanes/worker-continuity.d.ts +2 -0
  103. package/dist/lib/lanes/worker-continuity.js +18 -11
  104. package/dist/lib/manifest-disabled.d.ts +13 -0
  105. package/dist/lib/manifest-disabled.js +19 -0
  106. package/dist/lib/service-manager/launchd.d.ts +1 -0
  107. package/dist/lib/service-manager/launchd.js +1 -1
  108. package/dist/lib/windows/user-path.d.ts +48 -0
  109. package/dist/lib/windows/user-path.js +114 -0
  110. package/dist/lib/work-context/auto-bind.d.ts +1 -1
  111. package/dist/lib/work-context/auto-bind.js +22 -3
  112. package/dist/lib/work-context/lane-bind.d.ts +31 -0
  113. package/dist/lib/work-context/lane-bind.js +101 -0
  114. package/dist/lib/work-context/organize.js +1 -0
  115. package/dist/lib/work-context/prd-sync.js +7 -0
  116. package/dist/lib/work-context/project.d.ts +36 -10
  117. package/dist/lib/work-context/project.js +130 -41
  118. package/dist/lib/work-context/reconcile.d.ts +6 -0
  119. package/dist/lib/work-context/reconcile.js +25 -5
  120. package/dist/lib/work-context/state.d.ts +6 -0
  121. package/dist/lib/work-context/state.js +29 -1
  122. package/dist/lib/workers-registry/index.d.ts +6 -0
  123. package/dist/lib/workers-registry/index.js +8 -0
  124. package/dist/utils/copy-tree.d.ts +61 -0
  125. package/dist/utils/copy-tree.js +249 -0
  126. package/dist/utils/environmental-error.js +7 -3
  127. package/dist/utils/headless.d.ts +2 -0
  128. package/dist/utils/headless.js +8 -0
  129. package/dist/utils/login-browser.d.ts +4 -0
  130. package/dist/utils/login-browser.js +122 -0
  131. package/dist/utils/npm-invocation.d.ts +27 -0
  132. package/dist/utils/npm-invocation.js +54 -0
  133. package/dist/utils/safe-link.d.ts +50 -0
  134. package/dist/utils/safe-link.js +300 -0
  135. package/dist/utils/team-upgrade.js +1 -7
  136. package/package.json +17 -5
  137. package/scripts/hook-engine-manifest.mjs +22 -0
  138. package/scripts/install-hook-engine.mjs +10 -0
@@ -0,0 +1,16 @@
1
+ import { findHookEngine, hookEngineRequired, pinnedHookEngineMatches, HOOK_ENGINE_REPAIR, HOOK_ENGINE_REPAIR_PIN } from '../../hook-engine/resolve.js';
2
+ export function checkHookEngine() {
3
+ const required = hookEngineRequired();
4
+ const fallback = required ? 'Native engine is required; repair before running hooks.' : 'Existing bash hooks remain active.';
5
+ try {
6
+ const engine = findHookEngine();
7
+ if (engine && pinnedHookEngineMatches(engine))
8
+ return { checkId: 'hooks.native-engine', status: 'PASS', target: engine.pinned, message: `Native hook engine ${engine.version} installed at its version-pinned path.` };
9
+ return { checkId: 'hooks.native-engine', status: required ? 'FAIL' : 'WARN', message: `Native hook engine is missing or damaged. ${fallback}`, remediation: engine ? `Close HQ sessions, then run ${HOOK_ENGINE_REPAIR_PIN}` : HOOK_ENGINE_REPAIR };
10
+ }
11
+ catch (error) {
12
+ return { checkId: 'hooks.native-engine', status: required ? 'FAIL' : 'WARN', message: `Native hook engine unavailable: ${error.message}. ${fallback}`, remediation: HOOK_ENGINE_REPAIR };
13
+ }
14
+ }
15
+ export const hookEngineFamily = { id: 'hook-engine', title: 'Native hook engine', run: async () => [checkHookEngine()] };
16
+ //# sourceMappingURL=hook-engine.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Release-shipped links (hq-core mode 120000). An update that junctions a
3
+ * directory at the staging clone, then deletes that clone, leaves a link that
4
+ * does not resolve (feedback_3e1a7445). `hq doctor` reports those and the
5
+ * repair step. A link that resolves somewhere else inside the HQ root is a
6
+ * user customization and is left alone.
7
+ */
8
+ import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
9
+ export declare const RELEASE_LINKS_FAMILY_ID = "release-links";
10
+ export declare const RELEASE_LINKS_FAMILY_TITLE = "Release links";
11
+ export declare const RELEASE_LINKS_CHECK_ID = "release-links.resolve";
12
+ export declare const RELEASE_LINK_REMEDIATION = "Run `hq rescue -y` again. On Windows without Developer Mode, a directory link is written as a junction and a file link as a copy.";
13
+ interface ReleaseLink {
14
+ rel: string;
15
+ linkText: string;
16
+ kind: "dir" | "file";
17
+ }
18
+ /** Paths git records as symlinks in the hq-core release. */
19
+ export declare const RELEASE_LINKS: readonly ReleaseLink[];
20
+ export declare const releaseLinksFamily: CheckFamily;
21
+ export declare function checkReleaseLinks(context: CheckContext): CheckResult[];
22
+ export {};
23
+ //# sourceMappingURL=release-links.d.ts.map
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Release-shipped links (hq-core mode 120000). An update that junctions a
3
+ * directory at the staging clone, then deletes that clone, leaves a link that
4
+ * does not resolve (feedback_3e1a7445). `hq doctor` reports those and the
5
+ * repair step. A link that resolves somewhere else inside the HQ root is a
6
+ * user customization and is left alone.
7
+ */
8
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
+ export const RELEASE_LINKS_FAMILY_ID = "release-links";
11
+ export const RELEASE_LINKS_FAMILY_TITLE = "Release links";
12
+ export const RELEASE_LINKS_CHECK_ID = "release-links.resolve";
13
+ export const RELEASE_LINK_REMEDIATION = "Run `hq rescue -y` again. On Windows without Developer Mode, a directory link is written as a junction and a file link as a copy.";
14
+ const CYGWIN_SYMLINK_COOKIE = Buffer.from("!<symlink>", "latin1");
15
+ /** Paths git records as symlinks in the hq-core release. */
16
+ export const RELEASE_LINKS = [
17
+ { rel: ".agents/skills", linkText: "../.claude/skills", kind: "dir" },
18
+ { rel: ".codex/claude", linkText: "../.claude", kind: "dir" },
19
+ {
20
+ rel: ".codex/output-style.md",
21
+ linkText: "../.claude/output-styles/hq.md",
22
+ kind: "file",
23
+ },
24
+ { rel: "AGENTS.md", linkText: ".claude/CLAUDE.md", kind: "file" },
25
+ {
26
+ rel: "companies/_template/.obsidian",
27
+ linkText: "../../.obsidian",
28
+ kind: "dir",
29
+ },
30
+ ];
31
+ export const releaseLinksFamily = {
32
+ id: RELEASE_LINKS_FAMILY_ID,
33
+ title: RELEASE_LINKS_FAMILY_TITLE,
34
+ run: (context) => Promise.resolve(checkReleaseLinks(context)),
35
+ };
36
+ export function checkReleaseLinks(context) {
37
+ const present = RELEASE_LINKS.filter((entry) => lstatOrNull(path.join(context.hqRoot, entry.rel)) !== null);
38
+ if (present.length === 0) {
39
+ return [
40
+ {
41
+ status: "NA",
42
+ checkId: RELEASE_LINKS_CHECK_ID,
43
+ target: context.hqRoot,
44
+ message: "Skipped: this HQ root has none of the release-shipped links.",
45
+ },
46
+ ];
47
+ }
48
+ const failures = [];
49
+ for (const entry of present) {
50
+ const problem = releaseLinkProblem(context.hqRoot, entry);
51
+ if (!problem)
52
+ continue;
53
+ failures.push({
54
+ status: "FAIL",
55
+ checkId: RELEASE_LINKS_CHECK_ID,
56
+ target: entry.rel,
57
+ message: `${entry.rel} ${problem}`,
58
+ remediation: RELEASE_LINK_REMEDIATION,
59
+ });
60
+ }
61
+ if (failures.length > 0)
62
+ return failures;
63
+ return [
64
+ {
65
+ status: "PASS",
66
+ checkId: RELEASE_LINKS_CHECK_ID,
67
+ target: context.hqRoot,
68
+ message: "Release links resolve inside this HQ root.",
69
+ },
70
+ ];
71
+ }
72
+ function releaseLinkProblem(hqRoot, entry) {
73
+ const destination = path.join(hqRoot, entry.rel);
74
+ const lst = lstatOrNull(destination);
75
+ if (!lst)
76
+ return null;
77
+ if (lst.isSymbolicLink()) {
78
+ let resolved;
79
+ try {
80
+ resolved = fs.realpathSync.native(destination);
81
+ }
82
+ catch {
83
+ return "does not resolve. Its target is missing, often because an update linked it at a staging folder that was then deleted.";
84
+ }
85
+ if (!pathIsInside(hqRoot, resolved)) {
86
+ return "points outside this HQ root, so it does not resolve to the shipped target.";
87
+ }
88
+ return null;
89
+ }
90
+ if (lst.isFile()) {
91
+ const bytes = readFileOrNull(destination);
92
+ if (bytes && isSymlinkStub(bytes, entry.linkText)) {
93
+ return "is still a Git symlink stub, not a working link.";
94
+ }
95
+ if (entry.kind === "dir") {
96
+ return "is a file, not a directory link.";
97
+ }
98
+ const expected = containedTarget(hqRoot, entry);
99
+ if (expected) {
100
+ const target = readFileOrNull(expected);
101
+ if (target && bytes && bytes.equals(target))
102
+ return null;
103
+ }
104
+ // A regular file that is not the stub is a user edit or a Windows copy
105
+ // of the target. Either way the path is usable.
106
+ return null;
107
+ }
108
+ // A real directory in place of a directory link is a user replacement.
109
+ return null;
110
+ }
111
+ function containedTarget(hqRoot, entry) {
112
+ const destination = path.join(hqRoot, entry.rel);
113
+ const resolved = path.resolve(path.dirname(destination), entry.linkText);
114
+ return pathIsInside(hqRoot, resolved) ? resolved : null;
115
+ }
116
+ function isSymlinkStub(bytes, linkText) {
117
+ if (bytes.equals(Buffer.from(linkText, "utf8")))
118
+ return true;
119
+ return (bytes.length >= CYGWIN_SYMLINK_COOKIE.length &&
120
+ bytes.subarray(0, CYGWIN_SYMLINK_COOKIE.length).equals(CYGWIN_SYMLINK_COOKIE));
121
+ }
122
+ function pathIsInside(parent, child) {
123
+ const parentAbs = path.resolve(parent);
124
+ const childAbs = path.resolve(child);
125
+ if (childAbs === parentAbs || childAbs.startsWith(parentAbs + path.sep))
126
+ return true;
127
+ try {
128
+ const parentReal = fs.realpathSync.native(parentAbs);
129
+ const childReal = fs.realpathSync.native(childAbs);
130
+ return childReal === parentReal || childReal.startsWith(parentReal + path.sep);
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ }
136
+ function lstatOrNull(target) {
137
+ try {
138
+ return fs.lstatSync(target);
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ function readFileOrNull(target) {
145
+ try {
146
+ return fs.readFileSync(target);
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }
152
+ //# sourceMappingURL=release-links.js.map
@@ -100,6 +100,13 @@ export interface SyncHealthDeps {
100
100
  * unhealthy-sync signal, and `hq doctor --fix` has nothing local to refresh.
101
101
  */
102
102
  localCompanyExists?: (slug: string) => boolean;
103
+ /**
104
+ * Whether HQ_SYNC_MANIFEST_DISABLED turns manifest uploads off on this
105
+ * machine. Defaults to reading the environment the way `hq sync manifest`
106
+ * does. When true a stale row names the switch and is not auto-fixable,
107
+ * since a re-upload would exit 0 without uploading.
108
+ */
109
+ manifestUploadsDisabled?: () => boolean;
103
110
  }
104
111
  /**
105
112
  * True when `slug` is a filesystem-safe company folder name. Rejects empty
@@ -33,6 +33,7 @@ import * as path from "node:path";
33
33
  import * as hqCloud from "@indigoai-us/hq-cloud";
34
34
  import { loadManifestExports, MANIFEST_UNAVAILABLE_REASON, } from "../../hq-cloud-manifest.js";
35
35
  import { readCompaniesManifestMap } from "../../work-context/repo-remote.js";
36
+ import { MANIFEST_DISABLED_ENV, manifestUploadsDisabled, } from "../../manifest-disabled.js";
36
37
  import { CLI_VERSION } from "../../../cli-version.js";
37
38
  import { readSyncVersion } from "../../../utils/feedback-versions.js";
38
39
  import { readHqVersion } from "../../../utils/pack-contributions.js";
@@ -58,6 +59,7 @@ function withDefaults(deps, hqRoot) {
58
59
  unresolvedScopes: deps.unresolvedScopes ?? ((journals) => defaultUnresolvedScopes(hqRoot, journals)),
59
60
  localCompanyExists: deps.localCompanyExists ??
60
61
  ((slug) => defaultLocalCompanyExists(hqRoot, slug)),
62
+ manifestUploadsDisabled: deps.manifestUploadsDisabled ?? (() => manifestUploadsDisabled()),
61
63
  };
62
64
  }
63
65
  /**
@@ -216,6 +218,22 @@ function boundInvalidScopeToken(token) {
216
218
  return token;
217
219
  return token.slice(0, maxSuffix);
218
220
  }
221
+ /**
222
+ * The `hq sync manifest --scope` value that re-uploads this status's scope:
223
+ * `personal`, the company slug, or undefined when the scope cannot be named
224
+ * safely (a company row whose slug was not resolved locally).
225
+ */
226
+ function manifestUploadScope(entry) {
227
+ if (entry.scopeKey === "personal")
228
+ return "personal";
229
+ if (entry.scopeKey.startsWith("company:") &&
230
+ entry.companySlug &&
231
+ isSafeCompanySlug(entry.companySlug) &&
232
+ !NON_COMPANY_JOURNAL_SLUGS.has(entry.companySlug)) {
233
+ return entry.companySlug;
234
+ }
235
+ return undefined;
236
+ }
219
237
  /**
220
238
  * Doctor JSON `checkId` for a manifest scope. hq-cloud keys company rows
221
239
  * `company:cmp_…`; the colon is replaced so SessionStart health can report
@@ -629,12 +647,30 @@ function manifestResults(deps, journals) {
629
647
  : "next pass will send a full manifest";
630
648
  if (ageMs > STALE_MANIFEST_THRESHOLD_MS) {
631
649
  const days = Math.floor(ageMs / 86_400_000);
650
+ // `target` is the `hq sync manifest --scope` value `--fix` re-uploads.
651
+ // Without it the row stays manual-only. Personal must be explicit: a
652
+ // bare `hq sync manifest` uploads the active company instead.
653
+ const scope = manifestUploadScope(entry);
654
+ const command = `hq sync manifest${scope ? ` --scope ${scope}` : ""}`;
655
+ const message = `Scope '${entry.scopeKey}' last uploaded a sync manifest ${days} day${days === 1 ? "" : "s"} ago (${entry.lastUploadAt}) — the daily audit pass is not running.`;
656
+ // With the kill switch set the upload exits 0 and sends nothing, so the
657
+ // row carries no `--fix` target and names the switch instead.
658
+ if (deps.manifestUploadsDisabled()) {
659
+ return {
660
+ status: "WARN",
661
+ checkId,
662
+ reasonCode: "stale-threshold",
663
+ message: `${message} Manifest uploads are turned off on this machine by ${MANIFEST_DISABLED_ENV}.`,
664
+ remediation: `Unset ${MANIFEST_DISABLED_ENV}, then run \`${command}\`.`,
665
+ };
666
+ }
632
667
  return {
633
668
  status: "WARN",
634
669
  checkId,
635
670
  reasonCode: "stale-threshold",
636
- message: `Scope '${entry.scopeKey}' last uploaded a sync manifest ${days} day${days === 1 ? "" : "s"} ago (${entry.lastUploadAt}) — the daily audit pass is not running.`,
637
- remediation: `Run \`hq sync manifest${entry.companySlug ? ` --scope ${entry.companySlug}` : ""}\` and check HQ_SYNC_MANIFEST_DISABLED and the sync runner.`,
671
+ ...(scope ? { target: scope } : {}),
672
+ message,
673
+ remediation: `Run \`${command}\` and check ${MANIFEST_DISABLED_ENV} and the sync runner.`,
638
674
  };
639
675
  }
640
676
  return {
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Doctor family: abandoned throwaway `$HOME` folders.
3
+ *
4
+ * Read-only and cheap. It runs the sweeper in shallow mode: no size walk and
5
+ * no process probe, so plain `hq doctor` stays fast. It reports how many idle
6
+ * temp homes it found; `hq doctor --sweep-temp-homes --dry-run` measures them,
7
+ * and `hq doctor --sweep-temp-homes` removes them.
8
+ */
9
+ import { type TempHomeSweepOptions } from "../temp-home-sweeper.js";
10
+ import type { CheckFamily, CheckResult } from "../types.js";
11
+ export declare const TEMP_HOMES_FAMILY_ID = "temp-homes";
12
+ export declare const TEMP_HOMES_FAMILY_TITLE = "Temp homes";
13
+ type TempHomesCheckOptions = Omit<TempHomeSweepOptions, "dryRun" | "shallow">;
14
+ /** Build the family; tests pass fake roots and clock through `options`. */
15
+ export declare function createTempHomesFamily(options?: TempHomesCheckOptions): CheckFamily;
16
+ export declare const tempHomesFamily: CheckFamily;
17
+ export declare function checkTempHomes(options?: TempHomesCheckOptions): CheckResult[];
18
+ export {};
19
+ //# sourceMappingURL=temp-homes.d.ts.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Doctor family: abandoned throwaway `$HOME` folders.
3
+ *
4
+ * Read-only and cheap. It runs the sweeper in shallow mode: no size walk and
5
+ * no process probe, so plain `hq doctor` stays fast. It reports how many idle
6
+ * temp homes it found; `hq doctor --sweep-temp-homes --dry-run` measures them,
7
+ * and `hq doctor --sweep-temp-homes` removes them.
8
+ */
9
+ import { sweepTempHomes, } from "../temp-home-sweeper.js";
10
+ export const TEMP_HOMES_FAMILY_ID = "temp-homes";
11
+ export const TEMP_HOMES_FAMILY_TITLE = "Temp homes";
12
+ /** Build the family; tests pass fake roots and clock through `options`. */
13
+ export function createTempHomesFamily(options = {}) {
14
+ return {
15
+ id: TEMP_HOMES_FAMILY_ID,
16
+ title: TEMP_HOMES_FAMILY_TITLE,
17
+ run: () => Promise.resolve(checkTempHomes(options)),
18
+ };
19
+ }
20
+ export const tempHomesFamily = createTempHomesFamily();
21
+ export function checkTempHomes(options = {}) {
22
+ let result;
23
+ try {
24
+ result = sweepTempHomes({ ...options, shallow: true });
25
+ }
26
+ catch (error) {
27
+ return [{
28
+ status: "UNKNOWN",
29
+ checkId: "temp-homes.scan-error",
30
+ message: `Could not scan temp folders: ${error instanceof Error ? error.message : String(error)}`,
31
+ }];
32
+ }
33
+ const stale = result.entries.filter((entry) => entry.action === "would-remove");
34
+ if (stale.length === 0) {
35
+ return [{
36
+ status: "PASS",
37
+ checkId: "temp-homes.none",
38
+ message: "No idle temp homes to sweep.",
39
+ details: { roots: result.roots.length },
40
+ }];
41
+ }
42
+ return [{
43
+ status: "WARN",
44
+ checkId: "temp-homes.stale",
45
+ message: `${stale.length} idle temp home${stale.length === 1 ? "" : "s"} left in the temp folder. ` +
46
+ "Run hq doctor --sweep-temp-homes --dry-run to see the disk space they use.",
47
+ details: {
48
+ count: stale.length,
49
+ paths: stale.map((entry) => entry.path),
50
+ },
51
+ remediation: "hq doctor --sweep-temp-homes --dry-run, then hq doctor --sweep-temp-homes",
52
+ }];
53
+ }
54
+ //# sourceMappingURL=temp-homes.js.map
@@ -34,6 +34,7 @@
34
34
  * (the command resolves it once), keeping this module decoupled from the CLI and
35
35
  * trivially testable against a fake tree.
36
36
  */
37
+ import { type SpawnSyncReturns } from "node:child_process";
37
38
  import type { CheckResult, DoctorStatus } from "../types.js";
38
39
  import { type GateProfile } from "../hook-gate-profiles.js";
39
40
  import { type FixClass } from "./remediation.js";
@@ -78,6 +79,12 @@ export interface ApplyFixesOptions {
78
79
  * Tests inject a stub so `--fix` never hits the network.
79
80
  */
80
81
  refreshSync?: (target: RefreshSyncTarget) => Promise<RefreshSyncResult>;
82
+ /**
83
+ * Manifest upload used by the `refresh-manifest` class. Default shells out
84
+ * to `hq sync manifest --scope <slug|personal>`. Tests inject a stub so
85
+ * `--fix` never hits the network.
86
+ */
87
+ refreshManifest?: (target: RefreshManifestTarget) => Promise<RefreshManifestResult>;
81
88
  /**
82
89
  * File-only rewrite used by the `bot-node-path` class. Default rewrites
83
90
  * ~/Library/LaunchAgents/ai.getindigo.hq-bot.<name>.plist and never calls
@@ -117,6 +124,42 @@ export interface RefreshSyncResult {
117
124
  }
118
125
  /** Per-company bound for the default `hq sync pull` repair. */
119
126
  export declare const REFRESH_SYNC_TIMEOUT_MS = 90000;
127
+ /** One manifest scope the `refresh-manifest` class will upload. */
128
+ export interface RefreshManifestTarget {
129
+ /** `personal` or a company slug, as `hq sync manifest --scope` takes it. */
130
+ scope: string;
131
+ /** HQ root the manifest is built from. */
132
+ hqRoot: string;
133
+ }
134
+ /** Outcome of one {@link ApplyFixesOptions.refreshManifest} call. */
135
+ export interface RefreshManifestResult {
136
+ /** Whether the upload command exited 0. The re-check is the real verdict. */
137
+ ok: boolean;
138
+ /** One-line summary. */
139
+ message: string;
140
+ /**
141
+ * Why the upload failed, in plain words with no paths or slugs, for the
142
+ * unresolved-check next step. Absent when the upload exited 0.
143
+ */
144
+ reason?: string;
145
+ }
146
+ /**
147
+ * Why a sync check is still FAIL or WARN after `--fix`:
148
+ *
149
+ * - `manual-only` — no safe automatic repair exists.
150
+ * - `repair-did-not-clear` — `--fix` ran its repair and the re-check still fails.
151
+ * - `not-attempted` — a repair exists but this run did not get to it
152
+ * (per-run cap, declined, dirty tree, pass limit).
153
+ */
154
+ export type UnresolvedSyncReason = "manual-only" | "repair-did-not-clear" | "not-attempted";
155
+ /** A sync check `--fix` left FAIL or WARN, with the next step in plain words. */
156
+ export interface UnresolvedSyncCheck {
157
+ checkId: string;
158
+ status: DoctorStatus;
159
+ reason: UnresolvedSyncReason;
160
+ /** What the user should do next. Never the finding message (it can hold paths). */
161
+ nextStep: string;
162
+ }
120
163
  /** One applied (or attempted) repair, with its post-fix re-check status. */
121
164
  export interface AppliedFix {
122
165
  /** One-based repair pass in this `hq doctor --fix` invocation. */
@@ -145,7 +188,11 @@ export interface AppliedFixBackup {
145
188
  }
146
189
  /** A newly unlocked safe repair can be picked up for at most this many passes. */
147
190
  export declare const MAX_FIX_PASSES = 3;
148
- /** Two 90s pulls leave 120s for startup and check overhead under the hook's 300s ceiling. */
191
+ /**
192
+ * Two 90s network repairs leave 120s for startup and check overhead under the
193
+ * hook's 300s ceiling. Journal pulls and manifest uploads share this budget;
194
+ * pulls go first and uploads take any slot left.
195
+ */
149
196
  export declare const MAX_REFRESH_SYNC_PLANS_PER_RUN = 2;
150
197
  /** The outcome of an {@link applyFixes} run. */
151
198
  export interface ApplyFixesResult {
@@ -165,6 +212,27 @@ export interface ApplyFixesResult {
165
212
  backups: AppliedFixBackup[];
166
213
  /** The applied repairs with their post-fix status. */
167
214
  applied: AppliedFix[];
215
+ /** Sync checks still FAIL or WARN when the run ended, with the next step. */
216
+ unresolved: UnresolvedSyncCheck[];
217
+ }
218
+ /** A concrete, planned change: its preview, the file it touches, and its apply. */
219
+ interface FixPlan {
220
+ checkId: string;
221
+ fixClass: FixClass;
222
+ target: string;
223
+ /** HQ-root-relative path of the single file this plan writes. Empty when none. */
224
+ relpath: string;
225
+ /** Diff-style preview lines. */
226
+ preview: string;
227
+ /** One-line summary for the post-fix report. */
228
+ summary: string;
229
+ /** Apply the change. Reads the current on-disk state each time. */
230
+ apply: () => void | Promise<void>;
231
+ /** The status of `results` this target maps to after the fix. */
232
+ postStatus: (results: CheckResult[]) => DoctorStatus | null;
233
+ refreshSyncOutcome?: () => RefreshSyncOutcome | undefined;
234
+ /** Why the last apply's command failed (path-free), when it reported one. */
235
+ failureReason?: () => string | undefined;
168
236
  }
169
237
  /**
170
238
  * Apply every auto-fixable finding, honouring the dirty-tree refusal, the
@@ -173,6 +241,13 @@ export interface ApplyFixesResult {
173
241
  * assert on it.
174
242
  */
175
243
  export declare function applyFixes(options: ApplyFixesOptions): Promise<ApplyFixesResult>;
244
+ /**
245
+ * The sync checks still FAIL or WARN in `findings`, each with why `--fix` left
246
+ * it and what to do next. Reported on every run, independent of the
247
+ * manual-only flag: the SessionStart health hook files a bug for any sync row
248
+ * left after `--fix`, so the user must see the same rows and a next step.
249
+ */
250
+ export declare function unresolvedSyncChecks(findings: readonly CheckResult[], attempted: readonly Pick<FixPlan, "checkId" | "fixClass" | "summary" | "failureReason">[]): UnresolvedSyncCheck[];
176
251
  /**
177
252
  * Default `refresh-sync` repair: a bounded, pull-only, keep-conflicts
178
253
  * `hq sync pull --company <slug>` (or `--personal`) against the tree
@@ -180,6 +255,19 @@ export declare function applyFixes(options: ApplyFixesOptions): Promise<ApplyFix
180
255
  * non-zero exit is reported rather than thrown so later plans still run.
181
256
  */
182
257
  export declare function defaultRefreshSync(target: RefreshSyncTarget): Promise<RefreshSyncResult>;
258
+ /**
259
+ * Default `refresh-manifest` repair: a bounded `hq sync manifest --scope <s>`
260
+ * against the tree `--fix` is repairing. Exit 0 also covers throttled,
261
+ * disabled, and skipped passes, so the post-fix re-check decides whether the
262
+ * row cleared. A timeout or non-zero exit is reported, not thrown.
263
+ */
264
+ export declare function defaultRefreshManifest(target: RefreshManifestTarget): Promise<RefreshManifestResult>;
265
+ /**
266
+ * Map one `hq sync manifest` spawn result to a {@link RefreshManifestResult}.
267
+ * A spawn killed by the timeout has a null status, so it is reported as a
268
+ * timeout rather than as an exit code.
269
+ */
270
+ export declare function manifestUploadResult(scope: string, result: Pick<SpawnSyncReturns<string>, "error" | "status" | "signal">): RefreshManifestResult;
183
271
  /**
184
272
  * The uncommitted changes under {@link HOOK_CONFIG_DIRS}, one porcelain line
185
273
  * each. Returns [] when the tree is clean OR when `hqRoot` is not a git repo
@@ -218,4 +306,5 @@ export declare function addHookIdToGateProfiles(source: string, hookId: string,
218
306
  * non-object `hooks` value — it is replaced rather than trusted.
219
307
  */
220
308
  export declare function registerHookInSettings(settings: unknown, event: string, command: string): Record<string, unknown>;
309
+ export {};
221
310
  //# sourceMappingURL=apply.d.ts.map