@indigoai-us/hq-cli 5.345.54 → 5.345.56

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 (72) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/assets/scaffold/core/scripts/derive-trigger-facts.sh +10 -1
  3. package/assets/scaffold/core/scripts/jobs-validate.sh +2 -1
  4. package/assets/scaffold/core/scripts/session-title.sh +18 -15
  5. package/dist/command-catalog.generated.d.ts +143 -2
  6. package/dist/command-catalog.generated.js +175 -2
  7. package/dist/command-registration-plan.d.ts +6 -1
  8. package/dist/command-registration-plan.js +2 -1
  9. package/dist/commands/cloud.d.ts +7 -0
  10. package/dist/commands/cloud.js +92 -16
  11. package/dist/commands/core-delegate-w1b.js +18 -5
  12. package/dist/commands/core-derive-trigger-facts-command.js +14 -0
  13. package/dist/commands/core-derive-trigger-facts-native.d.ts +1 -0
  14. package/dist/commands/core-derive-trigger-facts-native.js +20 -1
  15. package/dist/commands/daemon.js +16 -0
  16. package/dist/commands/doctor.d.ts +1 -0
  17. package/dist/commands/doctor.js +15 -2
  18. package/dist/commands/import-scan.d.ts +24 -0
  19. package/dist/commands/import-scan.js +75 -0
  20. package/dist/commands/lanes-checks.d.ts +3 -0
  21. package/dist/commands/lanes-checks.js +34 -0
  22. package/dist/commands/lanes-session-start.d.ts +17 -0
  23. package/dist/commands/lanes-session-start.js +195 -0
  24. package/dist/commands/lanes-shared.d.ts +2 -1
  25. package/dist/commands/lanes-watch.d.ts +2 -0
  26. package/dist/commands/lanes-watch.js +2 -1
  27. package/dist/commands/lanes.d.ts +3 -1
  28. package/dist/commands/lanes.js +121 -18
  29. package/dist/commands/members.d.ts +4 -0
  30. package/dist/commands/members.js +23 -2
  31. package/dist/lanes-drain-hook.js +6 -2
  32. package/dist/lib/daemon/conflict-notices.d.ts +4 -0
  33. package/dist/lib/daemon/conflict-notices.js +80 -0
  34. package/dist/lib/doctor/checks/sync-health.d.ts +2 -0
  35. package/dist/lib/doctor/checks/sync-health.js +47 -23
  36. package/dist/lib/import-scan/events.d.ts +135 -0
  37. package/dist/lib/import-scan/events.js +71 -0
  38. package/dist/lib/import-scan/run.d.ts +84 -0
  39. package/dist/lib/import-scan/run.js +308 -0
  40. package/dist/lib/import-scan/scan-event.schema.json +127 -0
  41. package/dist/lib/import-scan/validate.d.ts +31 -0
  42. package/dist/lib/import-scan/validate.js +111 -0
  43. package/dist/lib/lanes/dropbox.d.ts +4 -2
  44. package/dist/lib/lanes/dropbox.js +54 -9
  45. package/dist/lib/lanes/flag-keys.d.ts +10 -0
  46. package/dist/lib/lanes/flag-keys.js +10 -0
  47. package/dist/lib/lanes/hooks-install.d.ts +14 -11
  48. package/dist/lib/lanes/hooks-install.js +70 -50
  49. package/dist/lib/lanes/monitor.js +1 -1
  50. package/dist/lib/lanes/reaper.d.ts +58 -0
  51. package/dist/lib/lanes/reaper.js +403 -0
  52. package/dist/lib/lanes/registry-flag.d.ts +2 -0
  53. package/dist/lib/lanes/registry-flag.js +1 -1
  54. package/dist/lib/lanes/spawn.d.ts +2 -0
  55. package/dist/lib/lanes/spawn.js +32 -2
  56. package/dist/lib/lanes/status-fields.d.ts +12 -0
  57. package/dist/lib/lanes/status-fields.js +189 -0
  58. package/dist/lib/lanes/types.js +1 -1
  59. package/dist/lib/lanes/worker-continuity.d.ts +18 -0
  60. package/dist/lib/lanes/worker-continuity.js +81 -0
  61. package/dist/lib/lanes/worker-process.d.ts +4 -0
  62. package/dist/lib/lanes/worker-process.js +77 -0
  63. package/dist/main.js +9 -3
  64. package/dist/utils/cli-telemetry.d.ts +4 -1
  65. package/dist/utils/cli-telemetry.js +8 -0
  66. package/dist/utils/jq-windows-compat.d.ts +13 -0
  67. package/dist/utils/jq-windows-compat.js +48 -0
  68. package/dist/utils/run-bundled-script.d.ts +3 -0
  69. package/dist/utils/run-bundled-script.js +9 -80
  70. package/dist/utils/windows-bash.d.ts +5 -0
  71. package/dist/utils/windows-bash.js +75 -0
  72. package/package.json +1 -1
@@ -17,7 +17,7 @@ function parseHookInput(text) {
17
17
  throw new Error("invalid_hook_input_shape: expected a JSON object");
18
18
  }
19
19
  const value = parsed;
20
- for (const key of ["transcript_path", "session_id", "hook_event_name"]) {
20
+ for (const key of ["transcript_path", "session_id", "hook_event_name", "hookEventName"]) {
21
21
  if (value[key] !== undefined && value[key] !== null && typeof value[key] !== "string") {
22
22
  throw new Error(`invalid_hook_input_field: ${key} must be a string`);
23
23
  }
@@ -25,7 +25,11 @@ function parseHookInput(text) {
25
25
  return {
26
26
  ...(typeof value.transcript_path === "string" ? { transcript_path: value.transcript_path } : {}),
27
27
  ...(typeof value.session_id === "string" ? { session_id: value.session_id } : {}),
28
- ...(typeof value.hook_event_name === "string" ? { hook_event_name: value.hook_event_name } : {}),
28
+ ...(typeof value.hook_event_name === "string"
29
+ ? { hook_event_name: value.hook_event_name }
30
+ : typeof value.hookEventName === "string"
31
+ ? { hookEventName: value.hookEventName }
32
+ : {}),
29
33
  };
30
34
  }
31
35
  function laneIdIsSafe(laneId) {
@@ -0,0 +1,4 @@
1
+ export declare function conflictNoticeStateDir(env?: NodeJS.ProcessEnv, home?: string): string;
2
+ /** Acknowledge by appending a tombstone; runner-owned notice data is read-only here. */
3
+ export declare function acknowledgeConflictNotice(id: string, stateDir?: string, acknowledgedAt?: string): void;
4
+ //# sourceMappingURL=conflict-notices.d.ts.map
@@ -0,0 +1,80 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ function writeAllSync(fd, text) {
5
+ const bytes = Buffer.from(text);
6
+ let offset = 0;
7
+ while (offset < bytes.length) {
8
+ const written = fs.writeSync(fd, bytes, offset, bytes.length - offset);
9
+ if (written <= 0)
10
+ throw new Error("Conflict acknowledgement append made no progress.");
11
+ offset += written;
12
+ }
13
+ }
14
+ export function conflictNoticeStateDir(env = process.env, home = os.homedir()) {
15
+ return env.HQ_STATE_DIR || path.join(home, ".hq");
16
+ }
17
+ /** Acknowledge by appending a tombstone; runner-owned notice data is read-only here. */
18
+ export function acknowledgeConflictNotice(id, stateDir = conflictNoticeStateDir(), acknowledgedAt = new Date().toISOString()) {
19
+ if (!/^[a-f0-9]{64}$/.test(id))
20
+ throw new Error("Invalid conflict notice id.");
21
+ const noticesPath = path.join(stateDir, "conflict-notices.json");
22
+ let notices = [];
23
+ try {
24
+ const file = JSON.parse(fs.readFileSync(noticesPath, "utf8"));
25
+ if (file?.schema === 1 && Array.isArray(file.notices))
26
+ notices = file.notices;
27
+ else
28
+ process.stderr.write("Conflict notice schema is unsupported; treating notices as empty.\n");
29
+ }
30
+ catch (error) {
31
+ if (error.code !== "ENOENT") {
32
+ process.stderr.write(`Conflict notice file could not be read; treating notices as empty (${error instanceof Error ? error.name : "UnknownError"}).\n`);
33
+ }
34
+ }
35
+ if (!notices.some((notice) => notice.id === id)) {
36
+ throw new Error("Unknown conflict notice.");
37
+ }
38
+ const ackPath = path.join(stateDir, "conflict-notice-acks.jsonl");
39
+ let existing = "";
40
+ try {
41
+ existing = fs.readFileSync(ackPath, "utf8");
42
+ let malformedLineCount = 0;
43
+ let alreadyAcknowledged = false;
44
+ for (const line of existing.split("\n")) {
45
+ if (!line)
46
+ continue;
47
+ try {
48
+ const record = JSON.parse(line);
49
+ if (!/^[a-f0-9]{64}$/.test(record.id) || typeof record.acknowledgedAt !== "string") {
50
+ throw new Error("Invalid acknowledgement record");
51
+ }
52
+ if (record.id === id)
53
+ alreadyAcknowledged = true;
54
+ }
55
+ catch {
56
+ malformedLineCount += 1;
57
+ }
58
+ }
59
+ if (malformedLineCount > 0) {
60
+ process.stderr.write(`Skipped ${malformedLineCount} malformed conflict acknowledgement line(s).\n`);
61
+ }
62
+ if (alreadyAcknowledged)
63
+ return;
64
+ }
65
+ catch (error) {
66
+ if (error.code !== "ENOENT")
67
+ throw error;
68
+ }
69
+ fs.mkdirSync(stateDir, { recursive: true });
70
+ const fd = fs.openSync(ackPath, "a", 0o600);
71
+ try {
72
+ const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
73
+ writeAllSync(fd, `${separator}${JSON.stringify({ id, acknowledgedAt })}\n`);
74
+ fs.fsyncSync(fd);
75
+ }
76
+ finally {
77
+ fs.closeSync(fd);
78
+ }
79
+ }
80
+ //# sourceMappingURL=conflict-notices.js.map
@@ -59,6 +59,8 @@ export interface SyncManifestUploadStatus {
59
59
  snapshotId: string | null;
60
60
  sequence: number;
61
61
  baseUsable: boolean;
62
+ /** Local company slug for company-scoped remediation; absent for personal. */
63
+ companySlug?: string;
62
64
  }
63
65
  /** Injectable dependencies — defaults are the real collectors. */
64
66
  export interface SyncHealthDeps {
@@ -32,6 +32,7 @@ import * as path from "node:path";
32
32
  // ../../hq-cloud-manifest.js.
33
33
  import * as hqCloud from "@indigoai-us/hq-cloud";
34
34
  import { loadManifestExports, MANIFEST_UNAVAILABLE_REASON, } from "../../hq-cloud-manifest.js";
35
+ import { readCompaniesManifestMap } from "../../work-context/repo-remote.js";
35
36
  import { CLI_VERSION } from "../../../cli-version.js";
36
37
  import { readSyncVersion } from "../../../utils/feedback-versions.js";
37
38
  import { readHqVersion } from "../../../utils/pack-contributions.js";
@@ -52,9 +53,9 @@ function withDefaults(deps, hqRoot) {
52
53
  return {
53
54
  ...deps,
54
55
  runtime: deps.runtime ?? (() => []),
55
- manifestStatuses: deps.manifestStatuses ?? defaultManifestStatuses,
56
+ manifestStatuses: deps.manifestStatuses ?? ((journals) => defaultManifestStatuses(hqRoot, journals)),
56
57
  manifestAvailable: deps.manifestAvailable ?? (() => loadManifestExports() !== null),
57
- unresolvedScopes: deps.unresolvedScopes ?? defaultUnresolvedScopes,
58
+ unresolvedScopes: deps.unresolvedScopes ?? ((journals) => defaultUnresolvedScopes(hqRoot, journals)),
58
59
  localCompanyExists: deps.localCompanyExists ??
59
60
  ((slug) => defaultLocalCompanyExists(hqRoot, slug)),
60
61
  };
@@ -78,15 +79,35 @@ function defaultLocalCompanyExists(hqRoot, slug) {
78
79
  return false;
79
80
  }
80
81
  }
81
- /**
82
- * Company journal shards are unresolvable by the default collector: the
83
- * snapshot store keys company scopes by `companyUid`, which `listJournals()`
84
- * does not carry. Naming them here keeps them visible.
85
- */
86
- function defaultUnresolvedScopes(journals) {
87
- return journals
88
- .filter((entry) => !NON_COMPANY_JOURNAL_SLUGS.has(entry.slug))
89
- .map((entry) => entry.slug);
82
+ /** Resolve locally-known company journal slugs to the UID used by snapshots. */
83
+ function resolvedCompanyScopes(hqRoot, journals) {
84
+ const companies = readCompaniesManifestMap(hqRoot);
85
+ const seen = new Set();
86
+ const scopes = [];
87
+ for (const { slug } of journals) {
88
+ if (NON_COMPANY_JOURNAL_SLUGS.has(slug) ||
89
+ !isSafeCompanySlug(slug) ||
90
+ !defaultLocalCompanyExists(hqRoot, slug) ||
91
+ seen.has(slug)) {
92
+ continue;
93
+ }
94
+ seen.add(slug);
95
+ const uid = companies?.[slug]?.cloud_uid;
96
+ if (typeof uid === "string" && /^cmp_[A-Za-z0-9]{3,128}$/.test(uid.trim())) {
97
+ scopes.push({ kind: "company", companyUid: uid.trim(), slug });
98
+ }
99
+ }
100
+ return scopes;
101
+ }
102
+ function defaultUnresolvedScopes(hqRoot, journals) {
103
+ const resolved = new Set(resolvedCompanyScopes(hqRoot, journals).map((scope) => scope.slug));
104
+ return [
105
+ ...new Set(journals
106
+ .filter((entry) => !NON_COMPANY_JOURNAL_SLUGS.has(entry.slug) &&
107
+ defaultLocalCompanyExists(hqRoot, entry.slug) &&
108
+ !resolved.has(entry.slug))
109
+ .map((entry) => entry.slug)),
110
+ ];
90
111
  }
91
112
  function defaultVersions(hqRoot) {
92
113
  return {
@@ -119,21 +140,15 @@ const DEFAULT_DEPS = {
119
140
  // than no check.
120
141
  journals: () => hqCloud.listJournals(),
121
142
  now: () => new Date(),
122
- manifestStatuses: defaultManifestStatuses,
123
143
  runtime: defaultSyncRuntimeResults,
124
144
  };
125
145
  /**
126
146
  * Map the machine's journal shards onto the manifest scopes hq-cloud keys its
127
- * snapshot store by, then read each one's last upload.
128
- *
129
- * The journal slug `personal` is the personal tree; every other slug is a
130
- * company. Note the snapshot store keys company scopes by `companyUid`, which
131
- * the journal listing does not carry — so a company whose uid we cannot name
132
- * is simply not reported here rather than reported wrongly. That is the honest
133
- * outcome: this check exists to tell an operator when a manifest STOPPED being
134
- * uploaded, and inventing a scope key would make it lie in both directions.
147
+ * snapshot store by, then read each one's last upload. Company journal rows
148
+ * carry only slugs, so resolve their UIDs from the local companies manifest;
149
+ * a missing or malformed UID stays explicitly unresolved below.
135
150
  */
136
- function defaultManifestStatuses(journals) {
151
+ function defaultManifestStatuses(hqRoot, journals) {
137
152
  // Both the legacy `personal` shard and the current personal-vault
138
153
  // pseudo-slug name the SAME personal tree; a machine mid-migration has both
139
154
  // on disk, so collapse them to a single personal scope rather than asking
@@ -141,6 +156,7 @@ function defaultManifestStatuses(journals) {
141
156
  const scopes = journals.some((entry) => NON_COMPANY_JOURNAL_SLUGS.has(entry.slug))
142
157
  ? [{ kind: "personal" }]
143
158
  : [];
159
+ scopes.push(...resolvedCompanyScopes(hqRoot, journals));
144
160
  if (scopes.length === 0)
145
161
  return [];
146
162
  // Absent on an older hq-cloud; the NA row is emitted by `manifestResults`
@@ -148,7 +164,15 @@ function defaultManifestStatuses(journals) {
148
164
  const exports = loadManifestExports();
149
165
  if (!exports)
150
166
  return [];
151
- return exports.readManifestUploadStatus(exports.getStateDir(), scopes);
167
+ const companySlugsByUid = new Map(scopes
168
+ .filter((scope) => scope.kind === "company")
169
+ .map((scope) => [scope.companyUid, scope.slug]));
170
+ const statuses = exports.readManifestUploadStatus(exports.getStateDir(), scopes);
171
+ return statuses.map((status) => {
172
+ const companyUid = /^company:(cmp_[A-Za-z0-9]{3,128})$/.exec(status.scopeKey)?.[1];
173
+ const companySlug = companyUid ? companySlugsByUid.get(companyUid) : undefined;
174
+ return companySlug ? { ...status, companySlug } : status;
175
+ });
152
176
  }
153
177
  /** The legacy journal slug that named the personal (non-company) tree. */
154
178
  const PERSONAL_SCOPE_SLUG = "personal";
@@ -526,7 +550,7 @@ function manifestResults(deps, journals) {
526
550
  checkId,
527
551
  reasonCode: "stale-threshold",
528
552
  message: `Scope '${entry.scopeKey}' last uploaded a sync manifest ${days} day${days === 1 ? "" : "s"} ago (${entry.lastUploadAt}) — the daily audit pass is not running.`,
529
- remediation: "Run `hq sync manifest` and check HQ_SYNC_MANIFEST_DISABLED and the sync runner.",
553
+ remediation: `Run \`hq sync manifest${entry.companySlug ? ` --scope ${entry.companySlug}` : ""}\` and check HQ_SYNC_MANIFEST_DISABLED and the sync runner.`,
530
554
  };
531
555
  }
532
556
  return {
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Streaming context-scan events (contract v1).
3
+ *
4
+ * `hq import scan --json --stream` relays these as JSON Lines, one object per
5
+ * line. They are produced by the HQ import-context scanner
6
+ * (`.claude/skills/import-context/scan.sh --progress-json` in an HQ tree) and
7
+ * validated here before they reach stdout. The HQ desktop first-run setup
8
+ * renders them as a knowledge tree that grows while the scan runs.
9
+ *
10
+ * The canonical contract lives next to the scanner
11
+ * (`.claude/skills/import-context/progress-json.md` in hq-core) and in
12
+ * `docs/import-scan-stream.md` in this repo. The JSON Schema is
13
+ * `scan-event.schema.json` in this directory (shipped next to the compiled
14
+ * module as `dist/lib/import-scan/scan-event.schema.json`).
15
+ *
16
+ * Desktop consumers can import these types from
17
+ * `@indigoai-us/hq-cli/dist/lib/import-scan/events.js` or mirror them.
18
+ *
19
+ * Versioning: within v1, fields, source ids, count keys and messages may be
20
+ * added. Consumers ignore unknown fields and must not parse message text.
21
+ * Removing or retyping a field, or adding a new `type`, `status` or `basis`
22
+ * value, bumps `v`.
23
+ */
24
+ export declare const SCAN_EVENT_VERSION: 1;
25
+ /** Event types, in the order a stream introduces them. */
26
+ export declare const SCAN_EVENT_TYPES: readonly ["start", "source", "count", "company", "project", "error", "done"];
27
+ export type ScanEventType = (typeof SCAN_EVENT_TYPES)[number];
28
+ export declare const SCAN_SOURCE_STATUSES: readonly ["scanning", "done", "skipped", "error"];
29
+ export type ScanSourceStatus = (typeof SCAN_SOURCE_STATUSES)[number];
30
+ export declare const SCAN_COMPANY_BASES: readonly ["hq-company", "repo-org", "folder"];
31
+ export type ScanCompanyBasis = (typeof SCAN_COMPANY_BASES)[number];
32
+ export declare const SCAN_PROJECT_BASES: readonly ["repo", "claude-code-cwd", "codex-cwd"];
33
+ export type ScanProjectBasis = (typeof SCAN_PROJECT_BASES)[number];
34
+ /**
35
+ * Source ids the v1 scanner emits, in scan order. Other ids may appear in
36
+ * later v1 scanners; show them by their `label`.
37
+ */
38
+ export declare const KNOWN_SCAN_SOURCE_IDS: readonly ["hq", "repos", "claude-code", "codex", "grok", "claude-ai", "artifacts"];
39
+ export type KnownScanSourceId = (typeof KNOWN_SCAN_SOURCE_IDS)[number];
40
+ /**
41
+ * Source id used on `error` events that hq-cli itself emits (HQ not found,
42
+ * scanner missing or too old, scanner crashed). Never listed in `start`.
43
+ */
44
+ export declare const SCAN_CLI_ERROR_SOURCE: "scanner";
45
+ /**
46
+ * Stable machine-readable codes on `error` events. hq-cli sets one on every
47
+ * error it emits itself (source "scanner"); scanner-emitted errors may omit it.
48
+ *
49
+ * scanner_outdated the HQ tree's scanner is missing or predates
50
+ * --progress-json; updating HQ fixes it
51
+ * scan_failed any other reason the scan could not run or finish
52
+ * (HQ not found, scanner crashed, cancelled, non-zero exit)
53
+ *
54
+ * Consumers branch on `code`, never on `message`. Treat an error without a
55
+ * code as non-fatal unless the stream ends with `report: null`.
56
+ */
57
+ export declare const SCAN_ERROR_CODES: readonly ["scanner_outdated", "scan_failed"];
58
+ export type ScanErrorCode = (typeof SCAN_ERROR_CODES)[number];
59
+ /** Project ids: "p_" + 12 lowercase hex (pid() in scan-progress.sh). */
60
+ export declare const SCAN_PROJECT_ID_PATTERN: RegExp;
61
+ export interface ScanSourceInfo {
62
+ id: string;
63
+ label: string;
64
+ }
65
+ interface ScanEventBase {
66
+ v: typeof SCAN_EVENT_VERSION;
67
+ }
68
+ /** First line: the sources present on this machine, in scan order. */
69
+ export interface ScanStartEvent extends ScanEventBase {
70
+ type: "start";
71
+ sources: ScanSourceInfo[];
72
+ }
73
+ /** Source lifecycle: `scanning` once, then one of done/skipped/error. */
74
+ export interface ScanSourceEvent extends ScanEventBase {
75
+ type: "source";
76
+ id: string;
77
+ status: ScanSourceStatus;
78
+ /** Final counts, on `done`. Each value repeats the last `count` event for that key. */
79
+ counts?: Record<string, number>;
80
+ /** Plain-words reason, on `skipped` and `error`. */
81
+ message?: string;
82
+ }
83
+ /** Running count; fires at 1-2-5 milestones and once with the final value. */
84
+ export interface ScanCountEvent extends ScanEventBase {
85
+ type: "count";
86
+ source: string;
87
+ key: string;
88
+ value: number;
89
+ }
90
+ /** A company from the deterministic rule pass. Emitted once per id. */
91
+ export interface ScanCompanyEvent extends ScanEventBase {
92
+ type: "company";
93
+ id: string;
94
+ name: string;
95
+ basis: ScanCompanyBasis;
96
+ }
97
+ /**
98
+ * A project. Upsert by `id`: an id repeats only when a later rule pass
99
+ * attaches a company to a project that had `company: null`.
100
+ */
101
+ export interface ScanProjectEvent extends ScanEventBase {
102
+ type: "project";
103
+ /** "p_" + 12 lowercase hex, the same for the same project on any machine. */
104
+ id: string;
105
+ /** Folder or repository basename; never contains "/". */
106
+ name: string;
107
+ company: string | null;
108
+ basis: ScanProjectBasis;
109
+ }
110
+ /** Non-fatal problem, in plain words. */
111
+ export interface ScanErrorEvent extends ScanEventBase {
112
+ type: "error";
113
+ source: string;
114
+ message: string;
115
+ /** Stable code (SCAN_ERROR_CODES). Always set on hq-cli's own errors. */
116
+ code?: ScanErrorCode;
117
+ }
118
+ export interface ScanSummary {
119
+ companies: number;
120
+ projects: number;
121
+ sessions: number;
122
+ }
123
+ /**
124
+ * Last line. `report` is the absolute path of report.json, or `null` when
125
+ * hq-cli could not run the scan (then `summary` is all zeros and the command
126
+ * exits non-zero).
127
+ */
128
+ export interface ScanDoneEvent extends ScanEventBase {
129
+ type: "done";
130
+ report: string | null;
131
+ summary: ScanSummary;
132
+ }
133
+ export type ScanEvent = ScanStartEvent | ScanSourceEvent | ScanCountEvent | ScanCompanyEvent | ScanProjectEvent | ScanErrorEvent | ScanDoneEvent;
134
+ export {};
135
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Streaming context-scan events (contract v1).
3
+ *
4
+ * `hq import scan --json --stream` relays these as JSON Lines, one object per
5
+ * line. They are produced by the HQ import-context scanner
6
+ * (`.claude/skills/import-context/scan.sh --progress-json` in an HQ tree) and
7
+ * validated here before they reach stdout. The HQ desktop first-run setup
8
+ * renders them as a knowledge tree that grows while the scan runs.
9
+ *
10
+ * The canonical contract lives next to the scanner
11
+ * (`.claude/skills/import-context/progress-json.md` in hq-core) and in
12
+ * `docs/import-scan-stream.md` in this repo. The JSON Schema is
13
+ * `scan-event.schema.json` in this directory (shipped next to the compiled
14
+ * module as `dist/lib/import-scan/scan-event.schema.json`).
15
+ *
16
+ * Desktop consumers can import these types from
17
+ * `@indigoai-us/hq-cli/dist/lib/import-scan/events.js` or mirror them.
18
+ *
19
+ * Versioning: within v1, fields, source ids, count keys and messages may be
20
+ * added. Consumers ignore unknown fields and must not parse message text.
21
+ * Removing or retyping a field, or adding a new `type`, `status` or `basis`
22
+ * value, bumps `v`.
23
+ */
24
+ export const SCAN_EVENT_VERSION = 1;
25
+ /** Event types, in the order a stream introduces them. */
26
+ export const SCAN_EVENT_TYPES = [
27
+ "start",
28
+ "source",
29
+ "count",
30
+ "company",
31
+ "project",
32
+ "error",
33
+ "done",
34
+ ];
35
+ export const SCAN_SOURCE_STATUSES = ["scanning", "done", "skipped", "error"];
36
+ export const SCAN_COMPANY_BASES = ["hq-company", "repo-org", "folder"];
37
+ export const SCAN_PROJECT_BASES = ["repo", "claude-code-cwd", "codex-cwd"];
38
+ /**
39
+ * Source ids the v1 scanner emits, in scan order. Other ids may appear in
40
+ * later v1 scanners; show them by their `label`.
41
+ */
42
+ export const KNOWN_SCAN_SOURCE_IDS = [
43
+ "hq",
44
+ "repos",
45
+ "claude-code",
46
+ "codex",
47
+ "grok",
48
+ "claude-ai",
49
+ "artifacts",
50
+ ];
51
+ /**
52
+ * Source id used on `error` events that hq-cli itself emits (HQ not found,
53
+ * scanner missing or too old, scanner crashed). Never listed in `start`.
54
+ */
55
+ export const SCAN_CLI_ERROR_SOURCE = "scanner";
56
+ /**
57
+ * Stable machine-readable codes on `error` events. hq-cli sets one on every
58
+ * error it emits itself (source "scanner"); scanner-emitted errors may omit it.
59
+ *
60
+ * scanner_outdated the HQ tree's scanner is missing or predates
61
+ * --progress-json; updating HQ fixes it
62
+ * scan_failed any other reason the scan could not run or finish
63
+ * (HQ not found, scanner crashed, cancelled, non-zero exit)
64
+ *
65
+ * Consumers branch on `code`, never on `message`. Treat an error without a
66
+ * code as non-fatal unless the stream ends with `report: null`.
67
+ */
68
+ export const SCAN_ERROR_CODES = ["scanner_outdated", "scan_failed"];
69
+ /** Project ids: "p_" + 12 lowercase hex (pid() in scan-progress.sh). */
70
+ export const SCAN_PROJECT_ID_PATTERN = /^p_[0-9a-f]{12}$/;
71
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Runs the HQ import-context scanner with `--progress-json` and relays its
3
+ * JSON Lines event stream (contract: ./events.ts).
4
+ *
5
+ * The scanner is the HQ tree's own copy at
6
+ * `<hq root>/.claude/skills/import-context/scan.sh`, the same place the
7
+ * /import-context skill runs it from. The scan is local only: the scanner
8
+ * reads the filesystem and writes one report.json; nothing here touches the
9
+ * network.
10
+ *
11
+ * HQ root: `--hq-root` when given, else the root the HQ installer recorded in
12
+ * `~/.hq/menubar.json` (`hqPath`). Nothing else is trusted: no walk up from
13
+ * the current folder and no `$HQ_ROOT`, because this command runs a script
14
+ * from that tree.
15
+ *
16
+ * Output modes:
17
+ * stream every valid event, rebuilt from its v1 fields (invalid lines are
18
+ * dropped with a warning on stderr that never echoes the line)
19
+ * final only the final `done` object
20
+ * human short progress lines and a summary
21
+ *
22
+ * The `done` event is held until the scanner exits. When the scan cannot run
23
+ * or does not finish cleanly (HQ not found, scanner missing or too old,
24
+ * scanner exited non-zero or without `done`, cancelled), an `error` event
25
+ * (source "scanner") comes first, then `done` (zeroed with `report: null`
26
+ * unless the scanner produced one), and the command exits non-zero.
27
+ *
28
+ * Process handling: the scanner runs in its own process group. SIGINT/SIGTERM
29
+ * to hq-cli are forwarded to the whole group and escalated to SIGKILL after
30
+ * `killTimeoutMs`. When the scanner exits, anything still left in its group
31
+ * is terminated so an orphan holding stdout open cannot stall the command.
32
+ *
33
+ * Scanner stderr is not forwarded verbatim: each line is passed through with
34
+ * paths and token-like strings replaced, at most MAX_STDERR_LINES lines, then
35
+ * a count of the rest.
36
+ */
37
+ export declare const SCANNER_RELATIVE_PATH: string;
38
+ export type ImportScanMode = "stream" | "final" | "human";
39
+ export interface ImportScanOptions {
40
+ mode: ImportScanMode;
41
+ /** Explicit HQ root (--hq-root). */
42
+ hqRoot?: string;
43
+ /** Home directory used to find ~/.hq/menubar.json (tests). */
44
+ home?: string;
45
+ /** Report path. Defaults to <hq>/workspace/imports/<scan id>/report.json. */
46
+ output?: string;
47
+ scopes?: string[];
48
+ claudeExport?: string;
49
+ noDefaultScopes?: boolean;
50
+ /** Shell used to run scan.sh (default "bash"). */
51
+ bash?: string;
52
+ env?: NodeJS.ProcessEnv;
53
+ now?: () => Date;
54
+ /** Grace period between forwarding a signal and SIGKILL (default 5000). */
55
+ killTimeoutMs?: number;
56
+ /** Cancels the scan like SIGTERM (for embedding callers and tests). */
57
+ abortSignal?: AbortSignal;
58
+ }
59
+ export interface ImportScanIo {
60
+ stdout: NodeJS.WritableStream;
61
+ stderr: NodeJS.WritableStream;
62
+ }
63
+ /** UTC scan id in the /import-context skill's format: YYYYMMDDTHHMMSSZ. */
64
+ export declare function scanId(date: Date): string;
65
+ export declare class HqRootError extends Error {
66
+ }
67
+ /**
68
+ * --hq-root, else the installer-recorded root in ~/.hq/menubar.json. Throws
69
+ * HqRootError with a plain-words message otherwise.
70
+ */
71
+ export declare function resolveScanHqRoot(opts: {
72
+ hqRoot?: string;
73
+ home?: string;
74
+ }): string;
75
+ /**
76
+ * Replace paths and token-like strings in one scanner stderr line. A path
77
+ * runs from its first "/" (or a leading ~ / $HOME) to the next quote or the
78
+ * end of the line, because folder names may contain spaces: stopping at
79
+ * whitespace would leak the rest of "/Users/x/My Project/secret".
80
+ */
81
+ export declare function redactScannerLine(line: string): string;
82
+ /** Run the scan. Resolves to the process exit code. */
83
+ export declare function runImportScan(opts: ImportScanOptions, io: ImportScanIo): Promise<number>;
84
+ //# sourceMappingURL=run.d.ts.map