@indigoai-us/hq-cli 5.290.0 → 5.292.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 (83) hide show
  1. package/CHANGELOG.md +66 -3
  2. package/assets/scaffold/core/scripts/handoff-finalize.sh +18 -0
  3. package/assets/scaffold/core/scripts/handoff-post.sh +71 -6
  4. package/assets/scaffold/core/scripts/hook-lib.sh +345 -50
  5. package/assets/scaffold/core/scripts/hq-delegate-bundle.sh +307 -0
  6. package/assets/scaffold/core/scripts/hq-delegate-grant.sh +226 -0
  7. package/assets/scaffold/core/scripts/hq-delegate-publish.sh +91 -0
  8. package/assets/scaffold/core/scripts/hq-delegate-send.sh +259 -0
  9. package/assets/scaffold/core/scripts/hq-delegate-verify.sh +213 -0
  10. package/assets/scaffold/core/scripts/hq-job-notify.sh +389 -0
  11. package/assets/scaffold/core/scripts/hq-job-probe.sh +835 -0
  12. package/assets/scaffold/core/scripts/hq-job-remediate.sh +359 -0
  13. package/assets/scaffold/core/scripts/hq-job-run.sh +1025 -0
  14. package/assets/scaffold/core/scripts/hq-session.sh +182 -21
  15. package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +310 -0
  16. package/assets/scaffold/core/scripts/jobs-validate.sh +451 -0
  17. package/assets/scaffold/core/scripts/lib/session-id.sh +3 -2
  18. package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +2 -1
  19. package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +830 -0
  20. package/dist/command-catalog.generated.d.ts +165 -1
  21. package/dist/command-catalog.generated.js +205 -1
  22. package/dist/commands/cloud.d.ts +26 -14
  23. package/dist/commands/cloud.js +207 -56
  24. package/dist/commands/core-delegate-w1b.d.ts +8 -0
  25. package/dist/commands/core-delegate-w1b.js +1049 -0
  26. package/dist/commands/core-delegate.d.ts +28 -0
  27. package/dist/commands/core-delegate.js +15 -13
  28. package/dist/commands/core-handoff.js +152 -23
  29. package/dist/commands/core-jobs-native.d.ts +23 -0
  30. package/dist/commands/core-jobs-native.js +1331 -0
  31. package/dist/commands/core-native-jobs-flag.d.ts +4 -0
  32. package/dist/commands/core-native-jobs-flag.js +7 -0
  33. package/dist/commands/core-ontology.d.ts +22 -4
  34. package/dist/commands/core-ontology.js +90 -8
  35. package/dist/commands/core-policy.js +24 -5
  36. package/dist/commands/core.js +17 -0
  37. package/dist/commands/integrations-core.js +7 -0
  38. package/dist/commands/lanes.js +111 -35
  39. package/dist/commands/members.js +5 -0
  40. package/dist/commands/mesh.js +46 -25
  41. package/dist/commands/personal-targeted-push-flag.d.ts +4 -0
  42. package/dist/commands/personal-targeted-push-flag.js +7 -0
  43. package/dist/commands/personal-targeted-push-paths.d.ts +3 -0
  44. package/dist/commands/personal-targeted-push-paths.js +67 -0
  45. package/dist/commands/secrets.js +9 -0
  46. package/dist/commands/skill.d.ts +3 -0
  47. package/dist/commands/skill.js +28 -0
  48. package/dist/fast-core.js +1 -0
  49. package/dist/lib/billing/free-month.d.ts +18 -0
  50. package/dist/lib/billing/free-month.js +31 -0
  51. package/dist/lib/billing/plan-limit-refusals.d.ts +69 -0
  52. package/dist/lib/billing/plan-limit-refusals.js +187 -0
  53. package/dist/lib/billing/plan-lock.d.ts +2 -6
  54. package/dist/lib/billing/plan-lock.js +5 -16
  55. package/dist/lib/checkpoint-ontology/drop-aliases.d.ts +41 -0
  56. package/dist/lib/checkpoint-ontology/drop-aliases.js +76 -0
  57. package/dist/lib/checkpoint-ontology/entity-file.d.ts +6 -3
  58. package/dist/lib/checkpoint-ontology/entity-file.js +12 -6
  59. package/dist/lib/checkpoint-ontology/merge-copies.d.ts +27 -8
  60. package/dist/lib/checkpoint-ontology/merge-copies.js +64 -97
  61. package/dist/lib/core-utils/secret-patterns.d.ts +3 -1
  62. package/dist/lib/core-utils/secret-patterns.js +3 -2
  63. package/dist/lib/daemon/flag.d.ts +2 -0
  64. package/dist/lib/daemon/flag.js +2 -0
  65. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  66. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  67. package/dist/lib/lanes/autopilot.d.ts +69 -0
  68. package/dist/lib/lanes/autopilot.js +418 -0
  69. package/dist/lib/lanes/mesh-events.js +2 -0
  70. package/dist/lib/lanes/store.js +11 -6
  71. package/dist/lib/mesh/api.js +4 -0
  72. package/dist/lib/mesh/live/daemon/state.d.ts +7 -1
  73. package/dist/lib/mesh/live/daemon/state.js +34 -2
  74. package/dist/main.js +23 -12
  75. package/dist/utils/plan-gate-error.d.ts +24 -0
  76. package/dist/utils/plan-gate-error.js +219 -9
  77. package/dist/utils/scaffold-sibling-references.js +4 -4
  78. package/dist/utils/self-update.d.ts +6 -0
  79. package/dist/utils/self-update.js +28 -6
  80. package/dist/utils/staged-install.d.ts +34 -0
  81. package/dist/utils/staged-install.js +296 -0
  82. package/package.json +2 -2
  83. package/scripts/mesh-daemon-if-stale.mjs +4 -0
@@ -1,6 +1,34 @@
1
1
  /** Native implementations of the five `hq core delegate` shell companions. */
2
2
  import { Command } from "commander";
3
3
  export declare const CORE_NATIVE_DELEGATE_FLAG = "core-native-delegate";
4
+ export type DelegateContext = {
5
+ cwd: string;
6
+ root?: string;
7
+ };
8
+ type CommandResult = {
9
+ code: number;
10
+ stdout?: string;
11
+ stderr?: string;
12
+ };
13
+ export declare function subprocess(command: string, args: string[], cwd: string, options?: {
14
+ input?: Buffer | string;
15
+ stdinFd?: number;
16
+ stderr?: "inherit" | "ignore" | "pipe";
17
+ stdout?: "capture" | "inherit" | "ignore";
18
+ }): CommandResult;
19
+ type JsonRecord = Record<string, unknown>;
20
+ export declare function isRecord(value: unknown): value is JsonRecord;
21
+ export declare function readJsonText(text: string): unknown;
22
+ export declare function readJsonFile(file: string): unknown;
23
+ export declare function jqFallback(value: unknown, fallback?: unknown): unknown;
24
+ export declare function jsonAt(value: unknown, dottedPath: string): unknown;
25
+ export declare function rawJsonValue(value: unknown): string;
26
+ export declare function jqRawPath(value: unknown, dottedPath: string, fallback?: unknown): string;
27
+ export declare function jqRawPaths(value: unknown, dottedPaths: string[], fallback?: unknown): string;
28
+ export declare function requireRecord(value: unknown, property: string): JsonRecord;
29
+ export declare function moveFile(source: string, target: string): void;
30
+ export declare function writeJsonAtomically(target: string, value: unknown, siblingTemp?: boolean): void;
4
31
  /** Register the Wave 1a delegate commands and preserve the shell rollback path. */
5
32
  export declare function registerCoreDelegateCommand(core: Command): void;
33
+ export {};
6
34
  //# sourceMappingURL=core-delegate.d.ts.map
@@ -8,6 +8,7 @@ import { resolveProcessFlagGate } from "../lib/flag-registry.js";
8
8
  import { describeCoreCommand } from "./core-command-catalog.js";
9
9
  import { resolveLiveRoot } from "../utils/hq-roots.js";
10
10
  import { runBundledScript } from "../utils/run-bundled-script.js";
11
+ import { registerCoreDelegateW1b } from "./core-delegate-w1b.js";
11
12
  export const CORE_NATIVE_DELEGATE_FLAG = "core-native-delegate";
12
13
  const SCRIPT_ASSETS = {
13
14
  resolve: "core/scripts/hq-delegate-resolve.sh",
@@ -60,13 +61,13 @@ function commandPath(name, cwd = process.cwd()) {
60
61
  function commandExists(name, cwd = process.cwd()) {
61
62
  return commandPath(name, cwd) !== undefined;
62
63
  }
63
- function subprocess(command, args, cwd, options = {}) {
64
+ export function subprocess(command, args, cwd, options = {}) {
64
65
  const result = spawnSync(command, args, {
65
66
  cwd,
66
67
  env: process.env,
67
68
  input: options.input,
68
69
  encoding: "utf8",
69
- stdio: [options.stdinFd ?? "inherit", options.stdout === "inherit" ? "inherit" : options.stdout === "ignore" ? "ignore" : "pipe", options.stderr ?? "inherit"],
70
+ stdio: [options.stdinFd ?? "inherit", options.stdout === "inherit" ? "inherit" : options.stdout === "ignore" ? "ignore" : "pipe", options.stderr === "pipe" ? "pipe" : options.stderr ?? "inherit"],
70
71
  });
71
72
  if (result.error) {
72
73
  return { code: 127, stderr: result.error.message, stdout: "" };
@@ -77,10 +78,10 @@ function subprocess(command, args, cwd, options = {}) {
77
78
  stderr: typeof result.stderr === "string" ? result.stderr : "",
78
79
  };
79
80
  }
80
- function isRecord(value) {
81
+ export function isRecord(value) {
81
82
  return value !== null && typeof value === "object" && !Array.isArray(value);
82
83
  }
83
- function readJsonText(text) {
84
+ export function readJsonText(text) {
84
85
  return JSON.parse(text);
85
86
  }
86
87
  function jqParseDiagnostic(text) {
@@ -92,7 +93,7 @@ function jqParseDiagnostic(text) {
92
93
  }
93
94
  return "jq: parse error: Invalid JSON text\n";
94
95
  }
95
- function readJsonFile(file) {
96
+ export function readJsonFile(file) {
96
97
  return readJsonText(fs.readFileSync(file, "utf8"));
97
98
  }
98
99
  function jqValid(file) {
@@ -108,24 +109,24 @@ function jqValid(file) {
108
109
  function jqTruthy(value) {
109
110
  return value !== null && value !== undefined && value !== false;
110
111
  }
111
- function jqFallback(value, fallback = "") {
112
+ export function jqFallback(value, fallback = "") {
112
113
  return jqTruthy(value) ? value : fallback;
113
114
  }
114
- function jsonAt(value, dottedPath) {
115
+ export function jsonAt(value, dottedPath) {
115
116
  let current = value;
116
117
  for (const part of dottedPath.split(".")) {
117
118
  current = isRecord(current) ? current[part] : undefined;
118
119
  }
119
120
  return current;
120
121
  }
121
- function rawJsonValue(value) {
122
+ export function rawJsonValue(value) {
122
123
  return serializeJqJson(value, "raw").replace(/\n+$/, "");
123
124
  }
124
- function jqRawPath(value, dottedPath, fallback = "") {
125
+ export function jqRawPath(value, dottedPath, fallback = "") {
125
126
  const found = jsonAt(value, dottedPath);
126
127
  return found === undefined && fallback === "" ? "" : rawJsonValue(jqFallback(found, fallback));
127
128
  }
128
- function jqRawPaths(value, dottedPaths, fallback = "") {
129
+ export function jqRawPaths(value, dottedPaths, fallback = "") {
129
130
  for (const dottedPath of dottedPaths) {
130
131
  const found = jsonAt(value, dottedPath);
131
132
  if (jqTruthy(found))
@@ -133,13 +134,13 @@ function jqRawPaths(value, dottedPaths, fallback = "") {
133
134
  }
134
135
  return rawJsonValue(fallback);
135
136
  }
136
- function requireRecord(value, property) {
137
+ export function requireRecord(value, property) {
137
138
  const result = isRecord(value) ? value[property] : undefined;
138
139
  if (!isRecord(result))
139
140
  throw new TypeError(`Cannot update non-object field ${property}`);
140
141
  return result;
141
142
  }
142
- function moveFile(source, target) {
143
+ export function moveFile(source, target) {
143
144
  try {
144
145
  fs.renameSync(source, target);
145
146
  }
@@ -151,7 +152,7 @@ function moveFile(source, target) {
151
152
  fs.unlinkSync(source);
152
153
  }
153
154
  }
154
- function writeJsonAtomically(target, value, siblingTemp = false) {
155
+ export function writeJsonAtomically(target, value, siblingTemp = false) {
155
156
  const serialized = serializeJqJson(value, "pretty");
156
157
  if (siblingTemp) {
157
158
  const temporary = `${target}.tmp`;
@@ -983,5 +984,6 @@ export function registerCoreDelegateCommand(core) {
983
984
  });
984
985
  describeCoreCommand(command, { execution: "native", asset: SCRIPT_ASSETS[name], root: rootMode });
985
986
  }
987
+ registerCoreDelegateW1b(delegate, core);
986
988
  }
987
989
  //# sourceMappingURL=core-delegate.js.map
@@ -46,6 +46,66 @@ const OPEN_STEPS_UNKNOWN_USAGE = `# hq-core: public
46
46
  set -uo pipefail
47
47
  HQ_ROOT="\${HQ_ROOT:-\${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "\${BASH_SOURCE[0]}")/../.." && pwd)}}"
48
48
  `;
49
+ const SESSION_ID_ENV_KEYS = [
50
+ "HQ_SESSION_ID",
51
+ "CLAUDE_CODE_SESSION_ID",
52
+ "CLAUDE_SESSION_ID",
53
+ "CODEX_SESSION_ID",
54
+ "CODEX_THREAD_ID",
55
+ "GROK_SESSION_ID",
56
+ ];
57
+ function validSessionId(value) {
58
+ return value !== "" && value !== "." && value !== ".." && /^[A-Za-z0-9._-]+$/.test(value);
59
+ }
60
+ function resolveSessionId(root) {
61
+ for (const key of SESSION_ID_ENV_KEYS) {
62
+ const value = (process.env[key] ?? "").replace(/\s/g, "");
63
+ if (validSessionId(value))
64
+ return value;
65
+ }
66
+ const current = path.join(root, "workspace/sessions/.current");
67
+ try {
68
+ const value = fs.readFileSync(current, "utf8").replace(/\s/g, "");
69
+ return validSessionId(value) ? value : "";
70
+ }
71
+ catch {
72
+ return "";
73
+ }
74
+ }
75
+ function boundSessionCompany(root) {
76
+ const sessionId = resolveSessionId(root);
77
+ if (!sessionId)
78
+ return "";
79
+ const metadata = path.join(root, "workspace/sessions", sessionId, "meta.yaml");
80
+ let contents;
81
+ try {
82
+ contents = fs.readFileSync(metadata, "utf8");
83
+ }
84
+ catch {
85
+ return "";
86
+ }
87
+ const line = contents.split(/\r?\n/).find((entry) => /^\s*company_slug:/.test(entry));
88
+ if (!line)
89
+ return "";
90
+ const company = (line.replace(/^\s*company_slug:\s*/, "").replace(/^"|"$/g, ""));
91
+ return /^[a-z][a-z0-9_-]*$/.test(company) ? company : "";
92
+ }
93
+ class HandoffJsonOptionError extends Error {
94
+ flag;
95
+ constructor(flag, cause) {
96
+ super(cause instanceof Error ? cause.message : String(cause));
97
+ this.flag = flag;
98
+ this.name = "HandoffJsonOptionError";
99
+ }
100
+ }
101
+ function parseHandoffJson(value, flag) {
102
+ try {
103
+ return JSON.parse(value);
104
+ }
105
+ catch (error) {
106
+ throw new HandoffJsonOptionError(flag, error);
107
+ }
108
+ }
49
109
  function nowFor(context) {
50
110
  return context.now?.() ?? new Date();
51
111
  }
@@ -259,7 +319,7 @@ export function runHandoffOpenSteps(args, context) {
259
319
  title: (isRecord(thread.metadata) ? thread.metadata.title : undefined) ?? "",
260
320
  created: thread.created_at,
261
321
  id: step.id,
262
- step: step.step,
322
+ step: step.step ?? null,
263
323
  });
264
324
  }
265
325
  }
@@ -540,7 +600,7 @@ function resolveFinalizeOptions(args) {
540
600
  return { values, error: { code: 2, stdout: "", stderr: `Unknown arg: ${key}\n` } };
541
601
  if (index + 1 >= args.length) {
542
602
  const shellAsset = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../assets/scaffold", ASSETS.finalize);
543
- return { values, error: { code: 1, stdout: "", stderr: `${shellAsset}: line 91: $2: unbound variable\n` } };
603
+ return { values, error: { code: 1, stdout: "", stderr: `${shellAsset}: line 93: $2: unbound variable\n` } };
544
604
  }
545
605
  values[target] = args[index + 1] ?? "";
546
606
  index += 2;
@@ -605,10 +665,10 @@ export function runHandoffFinalize(args, context) {
605
665
  const file = path.isAbsolute(values.filesTouchedFile) ? values.filesTouchedFile : path.join(root, values.filesTouchedFile);
606
666
  if (!fs.existsSync(file))
607
667
  return { code: 3, stdout: "", stderr: `handoff-finalize: --files-touched-json-file not found: ${values.filesTouchedFile}\n` };
608
- touchedValue = JSON.parse(fs.readFileSync(file, "utf8"));
668
+ touchedValue = parseHandoffJson(fs.readFileSync(file, "utf8"), "--files-touched-json-file");
609
669
  }
610
670
  else {
611
- touchedValue = JSON.parse(values.filesTouched);
671
+ touchedValue = parseHandoffJson(values.filesTouched, "--files-touched-json");
612
672
  }
613
673
  const touchedFile = values.filesTouchedFile
614
674
  ? (path.isAbsolute(values.filesTouchedFile) ? values.filesTouchedFile : path.join(root, values.filesTouchedFile))
@@ -636,14 +696,21 @@ export function runHandoffFinalize(args, context) {
636
696
  }
637
697
  safePaths.push(relative);
638
698
  }
639
- const companies = touchedCompanyPaths(safePaths);
699
+ const sessionCompany = boundSessionCompany(root);
700
+ const companyDirectory = sessionCompany
701
+ ? fs.statSync(path.join(root, "companies", sessionCompany), { throwIfNoEntry: false })
702
+ : undefined;
703
+ const companies = [...new Set([
704
+ ...touchedCompanyPaths(safePaths),
705
+ ...(companyDirectory?.isDirectory() ? [sessionCompany] : []),
706
+ ])].sort();
640
707
  const dirty = probe.hasGit && printGitStatus(root) !== "";
641
708
  const branchResult = probe.hasGit ? git(["symbolic-ref", "--short", "HEAD"], root) : { code: 1, stdout: "", stderr: "" };
642
709
  const commitResult = probe.hasGit ? git(["rev-parse", "--short", "HEAD"], root) : { code: 1, stdout: "", stderr: "" };
643
710
  const branch = branchResult.code === 0 ? branchResult.stdout.trim() : "DETACHED";
644
711
  const startCommit = commitResult.code === 0 ? commitResult.stdout.trim() : "unknown";
645
712
  const status = statusSummary(root, touchedFile);
646
- const stepsInput = JSON.parse(values.nextSteps);
713
+ const stepsInput = parseHandoffJson(values.nextSteps, "--next-steps-json");
647
714
  const nextSteps = Array.isArray(stepsInput) ? stepsInput.map((raw, index) => {
648
715
  const step = typeof raw === "string" ? { step: raw } : isRecord(raw) ? { ...raw } : {};
649
716
  if (!jqTruthy(step.id))
@@ -666,8 +733,8 @@ export function runHandoffFinalize(args, context) {
666
733
  conversation_summary: values.summary,
667
734
  next_steps: nextSteps,
668
735
  files_touched: touchedValue,
669
- learnings: JSON.parse(values.learnings),
670
- metadata: { title: values.title, tags: JSON.parse(values.tags), company: companies },
736
+ learnings: parseHandoffJson(values.learnings, "--learnings-json"),
737
+ metadata: { title: values.title, tags: parseHandoffJson(values.tags, "--tags-json"), company: companies },
671
738
  };
672
739
  fs.mkdirSync(threadsDir, { recursive: true });
673
740
  fs.writeFileSync(path.join(root, threadPath), prettyJson(thread));
@@ -823,6 +890,9 @@ export function runHandoffFinalize(args, context) {
823
890
  return { code: 0, stdout, stderr: stderrParts.join("") };
824
891
  }
825
892
  catch (error) {
893
+ if (error instanceof HandoffJsonOptionError) {
894
+ return { code: 2, stdout: "", stderr: `handoff-finalize: invalid JSON for ${error.flag}: ${error.message}\n` };
895
+ }
826
896
  const message = error instanceof Error ? error.message : String(error);
827
897
  return { code: 1, stdout: "", stderr: `${message}\n` };
828
898
  }
@@ -891,7 +961,7 @@ function readThreadCompanyNames(file) {
891
961
  if (!jqTruthy(value))
892
962
  return [];
893
963
  const values = Array.isArray(value) ? value : [value];
894
- return values.map((item) => typeof item === "string" ? item : compactJson(item).trimEnd());
964
+ return [...new Set(values.filter((item) => typeof item === "string" && /^[a-z][a-z0-9_-]*$/.test(item)))].sort();
895
965
  }
896
966
  catch {
897
967
  return [];
@@ -927,17 +997,7 @@ function workMeshSessionEnd(root) {
927
997
  return "work-mesh-session-end: skipped (hook disabled)";
928
998
  if (/(^|,)(work-mesh|work-mesh-live|\*)(,|$)/.test(`,${process.env.HQ_DISABLED_HOOKS ?? ""},`))
929
999
  return "work-mesh-session-end: skipped (hook disabled)";
930
- let sessionId = "";
931
- const current = path.join(root, "workspace/sessions/.current");
932
- if (fs.existsSync(current)) {
933
- try {
934
- sessionId = fs.readFileSync(current, "utf8").replace(/\s/g, "");
935
- }
936
- catch {
937
- sessionId = "";
938
- }
939
- }
940
- sessionId = sessionId || process.env.CLAUDE_SESSION_ID || process.env.CODEX_SESSION_ID || process.env.CODEX_THREAD_ID || "";
1000
+ const sessionId = resolveSessionId(root);
941
1001
  if (!sessionId)
942
1002
  return undefined;
943
1003
  let harness = process.env.HQ_HARNESS || process.env.HQ_WORK_MESH_HARNESS || process.env.HQ_CHECKPOINT_RUNTIME || "claude-code";
@@ -1112,11 +1172,80 @@ export function runHandoffPostWorker(args, context) {
1112
1172
  else
1113
1173
  log("document-release: skipped (no thread path)");
1114
1174
  if (threadFile && fs.existsSync(threadFile) && commandAvailable("hq")) {
1115
- for (const company of readThreadCompanyNames(threadFile)) {
1116
- if (!/^[a-z][a-z0-9_-]*$/.test(company))
1117
- continue;
1175
+ let syncCompanies = readThreadCompanyNames(threadFile);
1176
+ const sessionCompany = boundSessionCompany(root);
1177
+ if (sessionCompany) {
1178
+ const updatedCompanies = [...new Set([...syncCompanies, sessionCompany])].sort();
1179
+ const companyListChanged = JSON.stringify(syncCompanies) !== JSON.stringify(updatedCompanies);
1180
+ syncCompanies = updatedCompanies;
1181
+ if (companyListChanged) {
1182
+ try {
1183
+ const thread = JSON.parse(fs.readFileSync(threadFile, "utf8"));
1184
+ if (!isRecord(thread))
1185
+ throw new Error("thread is not an object");
1186
+ let metadata;
1187
+ if (thread.metadata === null || thread.metadata === undefined)
1188
+ metadata = {};
1189
+ else if (isRecord(thread.metadata))
1190
+ metadata = thread.metadata;
1191
+ else
1192
+ throw new Error("thread metadata is not an object");
1193
+ metadata.company = syncCompanies;
1194
+ thread.metadata = metadata;
1195
+ const temporaryThread = path.join(path.dirname(threadFile), `.handoff-post-${process.pid}-${Math.random().toString(36).slice(2)}`);
1196
+ let temporaryFd;
1197
+ try {
1198
+ temporaryFd = fs.openSync(temporaryThread, "wx", 0o600);
1199
+ fs.writeFileSync(temporaryFd, `${compactJson(thread).trimEnd()}\n`);
1200
+ fs.closeSync(temporaryFd);
1201
+ temporaryFd = undefined;
1202
+ fs.chmodSync(temporaryThread, 0o600);
1203
+ fs.renameSync(temporaryThread, threadFile);
1204
+ }
1205
+ catch (error) {
1206
+ if (temporaryFd !== undefined) {
1207
+ try {
1208
+ fs.closeSync(temporaryFd);
1209
+ }
1210
+ catch { /* temporary output cleanup remains best effort */ }
1211
+ }
1212
+ try {
1213
+ fs.rmSync(temporaryThread, { force: true });
1214
+ }
1215
+ catch { /* jq/mv cleanup is fail-soft */ }
1216
+ throw error;
1217
+ }
1218
+ }
1219
+ catch {
1220
+ log("workspace-sync: skipped (could not add bound company to handoff thread)");
1221
+ syncCompanies = [];
1222
+ }
1223
+ }
1224
+ }
1225
+ let threadId = "";
1226
+ let threadMirrorable = false;
1227
+ try {
1228
+ const thread = JSON.parse(fs.readFileSync(threadFile, "utf8"));
1229
+ threadId = isRecord(thread) && typeof thread.thread_id === "string" ? thread.thread_id : "";
1230
+ const relative = path.relative(root, threadFile);
1231
+ threadMirrorable = /^workspace[\\/]threads[\\/]T-[A-Za-z0-9._-]+\.json$/.test(relative)
1232
+ && /^T-[A-Za-z0-9._-]+$/.test(threadId);
1233
+ }
1234
+ catch { /* shell helper treats an invalid thread as non-mirrorable */ }
1235
+ if (threadMirrorable && syncCompanies.length > 0) {
1236
+ const mirrorHook = path.join(root, ".claude/hooks/mirror-thread-to-company.sh");
1237
+ if (fs.existsSync(mirrorHook))
1238
+ mirrorThread(root, threadFile);
1239
+ else
1240
+ log("workspace-sync: mirror hook unavailable for handoff thread");
1241
+ }
1242
+ for (const company of syncCompanies) {
1118
1243
  if (!fs.existsSync(path.join(root, "companies", company, "workspace")))
1119
1244
  continue;
1245
+ if (!threadMirrorable || !fs.existsSync(path.join(root, "companies", company, "workspace/sessions", `${threadId}.json`))) {
1246
+ log(`workspace-sync: skipped (thread mirror missing for ${company})`);
1247
+ continue;
1248
+ }
1120
1249
  const pushed = spawnSync("hq", ["sync", "push", "--company", company, `companies/${company}/workspace`], {
1121
1250
  cwd: root,
1122
1251
  env: process.env,
@@ -0,0 +1,23 @@
1
+ /** Native TypeScript ports for the Wave 4a scheduled-jobs scaffold tools. */
2
+ import { Command } from "commander";
3
+ export declare function detectHostTimezone(override: string, resolveLocaltime?: (file: string) => string, readTimedatectl?: () => string): string;
4
+ export declare function runScheduleParse(argv: string[]): number;
5
+ export declare function validateJobs(args: string[]): number;
6
+ type ReconcileCommandResult = {
7
+ status: number;
8
+ stdout: string;
9
+ stderr: string;
10
+ };
11
+ export type ReconcileRunner = (command: string, args: string[]) => ReconcileCommandResult;
12
+ export type ReconcileOptions = {
13
+ schedulerRunner?: ReconcileRunner;
14
+ commandRunner?: ReconcileRunner;
15
+ identityFileReader?: (file: string) => string | undefined;
16
+ fetcher?: typeof fetch;
17
+ now?: () => Date;
18
+ };
19
+ export declare function runReconcile(argv: string[], rootArg?: string, options?: ReconcileOptions): Promise<number>;
20
+ export declare function registerCoreJobsNativeCommand(core: Command): void;
21
+ export declare function rawArgumentsForCommand(command: Command, fallback: string[], raw?: string[]): string[];
22
+ export {};
23
+ //# sourceMappingURL=core-jobs-native.d.ts.map