@indigoai-us/hq-cli 5.345.118 → 5.345.120

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 (42) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/assets/scaffold/core/scripts/pipeline-conductor.sh +2671 -0
  3. package/assets/scaffold/core/scripts/pipeline-driver.sh +820 -0
  4. package/assets/scaffold/core/scripts/pipeline-envelope.sh +211 -0
  5. package/assets/scaffold/core/scripts/session-journal.sh +67 -5
  6. package/dist/command-catalog.generated.d.ts +38 -0
  7. package/dist/command-catalog.generated.js +45 -0
  8. package/dist/commands/core-checkpoint-native.d.ts +17 -3
  9. package/dist/commands/core-checkpoint-native.js +48 -8
  10. package/dist/commands/core-derive-trigger-facts-native.js +15 -7
  11. package/dist/commands/core-migrate-policy-triggers-native.js +3 -1
  12. package/dist/commands/core-pipeline-conductor-command.d.ts +5 -0
  13. package/dist/commands/core-pipeline-conductor-command.js +138 -0
  14. package/dist/commands/core-pipeline-conductor-native.d.ts +7 -0
  15. package/dist/commands/core-pipeline-conductor-native.js +2950 -0
  16. package/dist/commands/core-pipeline-driver-native.d.ts +24 -0
  17. package/dist/commands/core-pipeline-driver-native.js +843 -0
  18. package/dist/commands/core-pipeline-driver-process.d.ts +4 -0
  19. package/dist/commands/core-pipeline-driver-process.js +68 -0
  20. package/dist/commands/core-pipeline-envelope-native.d.ts +4 -0
  21. package/dist/commands/core-pipeline-envelope-native.js +87 -0
  22. package/dist/commands/core-policy.js +3 -0
  23. package/dist/commands/core-session-journal-native.js +142 -12
  24. package/dist/commands/core-worker-lint-native.d.ts +0 -1
  25. package/dist/commands/core-worker-lint-native.js +0 -1
  26. package/dist/commands/core-worker-share-native.js +2 -2
  27. package/dist/commands/core-worker-skills-command.d.ts +1 -1
  28. package/dist/commands/core-worker-skills-command.js +3 -20
  29. package/dist/commands/core.d.ts +5 -1
  30. package/dist/commands/core.js +8 -9
  31. package/dist/commands/dm.js +23 -3
  32. package/dist/commands/storage-gate.d.ts +4 -3
  33. package/dist/commands/storage-gate.js +4 -3
  34. package/dist/fast-scaffold.js +0 -2
  35. package/dist/lib/storage-feature-flag.d.ts +7 -7
  36. package/dist/utils/vault-api.d.ts +10 -0
  37. package/dist/utils/vault-api.js +27 -0
  38. package/package.json +1 -1
  39. package/assets/scaffold/core/scripts/lint-shared-worker-skills.sh +0 -197
  40. package/assets/scaffold/core/scripts/share-worker-skill.sh +0 -178
  41. package/dist/commands/core-native-worker-skills-flag.d.ts +0 -4
  42. package/dist/commands/core-native-worker-skills-flag.js +0 -7
@@ -0,0 +1,4 @@
1
+ export declare function extractWindowsDriverIdentityFunction(source: string): string;
2
+ /** Preserve the pinned driver's liveness-then-identity lock check. */
3
+ export declare function isPipelineDriverProcess(pid: number, platform?: NodeJS.Platform): boolean;
4
+ //# sourceMappingURL=core-pipeline-driver-process.d.ts.map
@@ -0,0 +1,68 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { BUNDLED_ASSET_DIR, packageRoot } from "../utils/hq-roots.js";
5
+ import { resolveWindowsBash } from "../utils/run-bundled-script.js";
6
+ const DRIVER_MARKER = "pipeline-driver";
7
+ const WINDOWS_DRIVER_IDENTITY_SIGNATURE = "is_driver() { # pid -> 0 when that pid is alive and is a pipeline-driver";
8
+ function isAlive(pid) {
9
+ try {
10
+ process.kill(pid, 0);
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ export function extractWindowsDriverIdentityFunction(source) {
18
+ const escapedSignature = WINDOWS_DRIVER_IDENTITY_SIGNATURE.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
19
+ const functionSource = source.match(new RegExp(`^${escapedSignature}\\n[\\s\\S]*?^\\}`, "mu"))?.[0];
20
+ if (!functionSource)
21
+ throw new Error(`Bundled pipeline-driver.sh must define '${WINDOWS_DRIVER_IDENTITY_SIGNATURE}'`);
22
+ return functionSource;
23
+ }
24
+ function isWindowsPipelineDriverProcess(pid) {
25
+ const bash = resolveWindowsBash(process.env).executable;
26
+ if (!bash)
27
+ return false;
28
+ const scriptPath = path.join(packageRoot(), BUNDLED_ASSET_DIR, "core", "scripts", "pipeline-driver.sh");
29
+ const source = fs.readFileSync(scriptPath, "utf8");
30
+ const functionSource = extractWindowsDriverIdentityFunction(source);
31
+ // Windows intentionally runs the pinned Git Bash implementation because its
32
+ // lock format uses both MSYS and Win32 PIDs. This keeps native checks aligned
33
+ // with the shell's liveness-then-identity order and process-table lookup.
34
+ const result = spawnSync(bash, ["-c", `${functionSource}\nis_driver "$1"`, "pipeline-driver-lock-check", String(pid)], {
35
+ encoding: "utf8",
36
+ stdio: ["ignore", "pipe", "ignore"],
37
+ windowsHide: true,
38
+ });
39
+ if (result.error)
40
+ throw result.error;
41
+ return result.status === 0;
42
+ }
43
+ /** Preserve the pinned driver's liveness-then-identity lock check. */
44
+ export function isPipelineDriverProcess(pid, platform = process.platform) {
45
+ if (!Number.isSafeInteger(pid) || pid <= 0)
46
+ return false;
47
+ if (platform === "win32")
48
+ return isWindowsPipelineDriverProcess(pid);
49
+ if (!isAlive(pid))
50
+ return false;
51
+ if (platform === "linux") {
52
+ try {
53
+ return fs.readFileSync(`/proc/${pid}/cmdline`).toString("utf8").includes(DRIVER_MARKER);
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ const args = platform === "darwin"
60
+ ? ["-ww", "-o", "args=", "-p", String(pid)]
61
+ : ["-p", String(pid), "-o", "args="];
62
+ const result = spawnSync("ps", args, {
63
+ encoding: "utf8",
64
+ stdio: ["ignore", "pipe", "ignore"],
65
+ });
66
+ return result.status === 0 && String(result.stdout ?? "").includes(DRIVER_MARKER);
67
+ }
68
+ //# sourceMappingURL=core-pipeline-driver-process.js.map
@@ -0,0 +1,4 @@
1
+ type EnvelopeKind = "envelope" | "handoff";
2
+ export declare function validatePipelinePhaseFile(filePath: string, kind: EnvelopeKind): [boolean, string];
3
+ export {};
4
+ //# sourceMappingURL=core-pipeline-envelope-native.d.ts.map
@@ -0,0 +1,87 @@
1
+ import * as fs from "node:fs";
2
+ export function validatePipelinePhaseFile(filePath, kind) {
3
+ let text;
4
+ try {
5
+ text = new TextDecoder("utf-8", { fatal: true }).decode(fs.readFileSync(filePath));
6
+ if (text.charCodeAt(0) === 0xfeff)
7
+ text = text.slice(1);
8
+ text = text.replace(/\r\n?/g, "\n");
9
+ }
10
+ catch (error) {
11
+ const message = error && typeof error === "object" && "message" in error ? String(error.message) : String(error);
12
+ return [false, "pipeline-envelope: cannot read input: " + message];
13
+ }
14
+ if (!text.trim())
15
+ return [false, "invalid: empty input"];
16
+ let doc;
17
+ try {
18
+ doc = JSON.parse(text);
19
+ }
20
+ catch (error) {
21
+ const message = error instanceof Error ? error.message : String(error);
22
+ return [false, "invalid: unparseable JSON (" + message + ")"];
23
+ }
24
+ if (!isObject(doc)) {
25
+ const typeName = doc === null ? "NoneType" : Array.isArray(doc) ? "list" : typeof doc === "string" ? "str"
26
+ : typeof doc === "boolean" ? "bool" : Number.isInteger(doc) ? "int" : "float";
27
+ return [false, "invalid: top level must be a JSON object, got " + typeName];
28
+ }
29
+ const problems = [];
30
+ const schemas = { envelope: "hq-phase-envelope/v1", handoff: "hq-phase-handoff/v1" };
31
+ if (Object.hasOwn(doc, "schema") && doc.schema !== schemas[kind])
32
+ problems.push("wrong type: schema (want " + schemas[kind] + ")");
33
+ else if (!Object.hasOwn(doc, "schema"))
34
+ problems.push("missing field: schema");
35
+ const isString = (value) => typeof value === "string";
36
+ const isNonEmpty = (value) => isString(value) && value !== "";
37
+ const stringList = (value) => Array.isArray(value) && value.every(isString);
38
+ const check = (name, predicate, want, required = true) => {
39
+ if (!Object.hasOwn(doc, name)) {
40
+ if (required)
41
+ problems.push("missing field: " + name);
42
+ return;
43
+ }
44
+ if (!predicate(doc[name]))
45
+ problems.push("wrong type: " + name + " (want " + want + ")");
46
+ };
47
+ if (kind === "envelope") {
48
+ for (const field of ["story_id", "phase", "worker_id"])
49
+ check(field, isNonEmpty, "non-empty string");
50
+ check("worktree", isNonEmpty, "string path");
51
+ check("incoming_handoff", (value) => value === null || isNonEmpty(value), "string path or null");
52
+ check("acceptance_criteria", stringList, "array of strings");
53
+ check("deadline", (value) => isString(value) && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]00:?00)\n?$/.test(value), "ISO8601 UTC string");
54
+ check("fresh_call", (value) => typeof value === "boolean", "boolean");
55
+ for (const field of ["project", "engine", "result_path", "story_title", "story_description", "reopen_note", "repo", "branch", "prior_handoff"])
56
+ check(field, isString, "string", false);
57
+ check("resumed_after_interrupt", (value) => typeof value === "boolean", "boolean", false);
58
+ check("constraints", stringList, "array of strings", false);
59
+ }
60
+ else {
61
+ for (const field of ["story_id", "phase", "worker_id"])
62
+ check(field, isNonEmpty, "non-empty string");
63
+ check("status", (value) => value === "passed" || value === "failed" || value === "blocked", "one of passed|failed|blocked");
64
+ check("summary", isString, "string");
65
+ check("files_changed", stringList, "array of strings");
66
+ check("commits", stringList, "array of strings");
67
+ if (!Object.hasOwn(doc, "back_pressure"))
68
+ problems.push("missing field: back_pressure");
69
+ else if (!isObject(doc.back_pressure))
70
+ problems.push("wrong type: back_pressure (want object)");
71
+ else
72
+ for (const group of ["tests", "lint", "typecheck", "build"]) {
73
+ if (!Object.hasOwn(doc.back_pressure, group))
74
+ problems.push("missing field: back_pressure." + group);
75
+ else if (!["pass", "fail", "skip"].includes(String(doc.back_pressure[group])))
76
+ problems.push("wrong type: back_pressure." + group + " (want one of pass|fail|skip)");
77
+ }
78
+ check("context_for_next", isString, "string");
79
+ for (const field of ["engine", "notes"])
80
+ check(field, isString, "string", false);
81
+ }
82
+ return problems.length ? [false, problems.join("\n")] : [true, "valid " + kind];
83
+ }
84
+ function isObject(value) {
85
+ return value !== null && typeof value === "object" && !Array.isArray(value);
86
+ }
87
+ //# sourceMappingURL=core-pipeline-envelope-native.js.map
@@ -772,6 +772,8 @@ function autoRetire(options, root, cwd, env, now) {
772
772
  reportText += `# Automatic policy retirement — ${today}\n\nMode: human on the loop. Each policy below was retired by \`policy-retire.sh --auto\` with the reason shown.\nReverse any wrong call with \`bash core/scripts/policy-retire.sh <slug> --restore\` and fix the rule that produced it.\n\n`;
773
773
  }
774
774
  reportText += `## Run ${now.toISOString().slice(11, 19)}Z — dirs: ${dirs.join(' ')}\n\n`;
775
+ // Match the shell's preflight: fail to create the report temp before any
776
+ // policy can be retired, then replace it with the complete run section.
775
777
  fs.writeFileSync(tempPath, reportText);
776
778
  let retired = 0;
777
779
  let failed = 0;
@@ -847,6 +849,7 @@ function autoRetire(options, root, cwd, env, now) {
847
849
  stdout += `policy-retire --auto: ${wouldRetire} of ${candidates.length} candidates would be retired (dry run)\n`;
848
850
  }
849
851
  else if (retired > 0 || failed > 0 || needfix > 0) {
852
+ fs.writeFileSync(tempPath, reportText);
850
853
  fs.appendFileSync(outputPath, fs.readFileSync(tempPath));
851
854
  fs.rmSync(tempPath, { force: true });
852
855
  stdout += `policy-retire --auto: ${retired} retired, ${failed} failed, ${needfix} need a fix; report ${displayPolicyPath(outputPath, root)}\n`;
@@ -89,6 +89,18 @@ function processIsAlive(pid) {
89
89
  return error.code !== "ESRCH";
90
90
  }
91
91
  }
92
+ function counterLockOwnerIsAlive(lock, pid) {
93
+ if (processIsAlive(pid))
94
+ return true;
95
+ if (process.platform !== "win32")
96
+ return false;
97
+ let windowsPid = 0;
98
+ try {
99
+ windowsPid = Number.parseInt(fs.readFileSync(path.join(lock, "winpid"), "utf8"), 10) || 0;
100
+ }
101
+ catch { /* older shell locks did not record the Win32 PID */ }
102
+ return windowsPid > 1 && processIsAlive(windowsPid);
103
+ }
92
104
  function acquireJournalWriteLock(dir) {
93
105
  const lock = path.join(dir, ".session-journal-write.lock");
94
106
  const ownerFile = path.join(lock, "pid");
@@ -142,26 +154,144 @@ function releaseJournalWriteLock(lock) {
142
154
  }
143
155
  catch { /* shell lock cleanup is best-effort */ }
144
156
  }
145
- function withCounterLock(lock, action) {
157
+ const COUNTER_LOCK_PIDLESS_STALE_MS = 60_000;
158
+ function isAgedPidlessCounterLock(lock, now) {
159
+ try {
160
+ return now().getTime() - fs.statSync(lock).mtimeMs >= COUNTER_LOCK_PIDLESS_STALE_MS;
161
+ }
162
+ catch {
163
+ return false;
164
+ }
165
+ }
166
+ function reclaimStaleCounterLock(lock, now) {
167
+ let rawOwner;
168
+ try {
169
+ rawOwner = fs.readFileSync(path.join(lock, "pid"), "utf8").trim();
170
+ }
171
+ catch (error) {
172
+ if (error.code !== "ENOENT" || !isAgedPidlessCounterLock(lock, now))
173
+ return false;
174
+ rawOwner = "";
175
+ }
176
+ if (rawOwner !== "" && !/^\d+$/u.test(rawOwner))
177
+ return false;
178
+ let rawWindowsPid = "";
179
+ try {
180
+ rawWindowsPid = fs.readFileSync(path.join(lock, "winpid"), "utf8").trim();
181
+ }
182
+ catch { /* older shell locks did not record the Win32 PID */ }
183
+ if (rawWindowsPid !== "" && !/^\d+$/u.test(rawWindowsPid))
184
+ return false;
185
+ const owner = rawOwner ? Number.parseInt(rawOwner, 10) : 0;
186
+ const windowsOwner = rawWindowsPid ? Number.parseInt(rawWindowsPid, 10) : 0;
187
+ if (rawOwner && (owner <= 1 || counterLockOwnerIsAlive(lock, owner)))
188
+ return false;
189
+ if (!rawOwner && windowsOwner > 1 && processIsAlive(windowsOwner))
190
+ return false;
191
+ const claim = `${lock}.reclaim`;
192
+ try {
193
+ fs.mkdirSync(claim);
194
+ }
195
+ catch {
196
+ return false;
197
+ }
198
+ try {
199
+ fs.writeFileSync(path.join(claim, "pid"), `${process.pid}\n`, { flag: "wx" });
200
+ }
201
+ catch {
202
+ try {
203
+ fs.rmSync(claim, { recursive: true, force: true });
204
+ }
205
+ catch { /* best effort */ }
206
+ return false;
207
+ }
208
+ let currentRawOwner;
209
+ try {
210
+ currentRawOwner = fs.readFileSync(path.join(lock, "pid"), "utf8").trim();
211
+ }
212
+ catch {
213
+ currentRawOwner = "";
214
+ }
215
+ let currentRawWindowsPid;
216
+ try {
217
+ currentRawWindowsPid = fs.readFileSync(path.join(lock, "winpid"), "utf8").trim();
218
+ }
219
+ catch {
220
+ currentRawWindowsPid = "";
221
+ }
222
+ const sameOwner = currentRawOwner === rawOwner && currentRawWindowsPid === rawWindowsPid;
223
+ const stillStale = rawOwner
224
+ ? sameOwner && !counterLockOwnerIsAlive(lock, owner)
225
+ : sameOwner && !(windowsOwner > 1 && processIsAlive(windowsOwner)) && isAgedPidlessCounterLock(lock, now);
226
+ if (stillStale) {
227
+ const stale = `${lock}.stale-${process.pid}-${Math.random().toString(16).slice(2)}`;
228
+ try {
229
+ fs.renameSync(lock, stale);
230
+ fs.rmSync(stale, { recursive: true, force: true });
231
+ fs.rmSync(claim, { recursive: true, force: true });
232
+ return true;
233
+ }
234
+ catch { /* another waiter reclaimed it */ }
235
+ }
236
+ try {
237
+ fs.rmSync(claim, { recursive: true, force: true });
238
+ }
239
+ catch { /* best effort */ }
240
+ return false;
241
+ }
242
+ function withCounterLock(lock, env, writers, now, action) {
146
243
  let i = 0;
147
244
  let acquired = false;
148
- while (!acquired) {
245
+ let tries = env.HQ_JOURNAL_COUNTER_LOCK_TRIES ?? "250";
246
+ if (!/^\d+$/u.test(tries))
247
+ tries = "250";
248
+ const retryLimit = Number.parseInt(tries, 10);
249
+ while (i < retryLimit) {
149
250
  try {
150
251
  fs.mkdirSync(lock);
151
252
  acquired = true;
253
+ try {
254
+ if (process.platform === "win32")
255
+ fs.writeFileSync(path.join(lock, "winpid"), `${process.pid}\n`, { flag: "wx" });
256
+ fs.writeFileSync(path.join(lock, "pid"), `${process.pid}\n`, { flag: "wx" });
257
+ }
258
+ catch {
259
+ try {
260
+ fs.rmSync(lock, { recursive: true, force: true });
261
+ }
262
+ catch { /* best effort */ }
263
+ return;
264
+ }
265
+ break;
152
266
  }
153
267
  catch {
268
+ if (reclaimStaleCounterLock(lock, now))
269
+ continue;
154
270
  i++;
155
- if (i >= 50)
156
- break;
157
- sleepSync(20);
271
+ if (i < retryLimit)
272
+ sleepSync(20);
158
273
  }
159
274
  }
160
- action();
275
+ if (!acquired) {
276
+ writers.stderr("session-journal: tool-counter: lock remained busy; skipped update\n");
277
+ return;
278
+ }
161
279
  try {
162
- fs.rmdirSync(lock);
280
+ action();
281
+ }
282
+ finally {
283
+ let owner = 0;
284
+ try {
285
+ owner = Number.parseInt(fs.readFileSync(path.join(lock, "pid"), "utf8"), 10) || 0;
286
+ }
287
+ catch { /* lock disappeared */ }
288
+ if (owner === process.pid) {
289
+ try {
290
+ fs.rmSync(lock, { recursive: true, force: true });
291
+ }
292
+ catch { /* best effort */ }
293
+ }
163
294
  }
164
- catch { /* shell rmdir is best-effort */ }
165
295
  }
166
296
  function nextSequence(dir) {
167
297
  let names = [];
@@ -205,12 +335,12 @@ function runToolCounter(root, action, sessionArg, env, now, writers) {
205
335
  writers.stderr(`${sessionJournalAsset}: line ${line}: ${printableCounter}: Is a directory\n`);
206
336
  };
207
337
  if (action === "increment") {
208
- withCounterLock(lock, () => {
338
+ withCounterLock(lock, env, writers, now, () => {
209
339
  try {
210
340
  fs.writeFileSync(counter, `${readCounter(counter) + 1}\n`);
211
341
  }
212
342
  catch (error) {
213
- reportDirectoryWriteFailure(error, 395);
343
+ reportDirectoryWriteFailure(error, 457);
214
344
  }
215
345
  });
216
346
  }
@@ -218,12 +348,12 @@ function runToolCounter(root, action, sessionArg, env, now, writers) {
218
348
  writers.stdout(`${readCounter(counter)}\n`);
219
349
  }
220
350
  else if (action === "reset") {
221
- withCounterLock(lock, () => {
351
+ withCounterLock(lock, env, writers, now, () => {
222
352
  try {
223
353
  fs.writeFileSync(counter, "0\n");
224
354
  }
225
355
  catch (error) {
226
- reportDirectoryWriteFailure(error, 403);
356
+ reportDirectoryWriteFailure(error, 465);
227
357
  }
228
358
  });
229
359
  }
@@ -1,4 +1,3 @@
1
1
  import { type WorkerSkillDependencies, type WorkerSkillResult } from "./core-worker-skills-runtime.js";
2
- export declare const WORKER_SKILL_LINT_ASSET = "core/scripts/lint-shared-worker-skills.sh";
3
2
  export declare function runWorkerSkillLint(args: string[], dependencies: WorkerSkillDependencies): WorkerSkillResult;
4
3
  //# sourceMappingURL=core-worker-lint-native.d.ts.map
@@ -2,7 +2,6 @@
2
2
  import * as crypto from "node:crypto";
3
3
  import * as path from "node:path";
4
4
  import { errorCode, isDirectory, missingOrUnreadable, resolveWorkerCallerRoot, } from "./core-worker-skills-runtime.js";
5
- export const WORKER_SKILL_LINT_ASSET = "core/scripts/lint-shared-worker-skills.sh";
6
5
  const DEFAULT_ROOTS = ["core/workers", "core/packages"];
7
6
  const USAGE = "Usage: lint-shared-worker-skills.sh [root ...]\nDefault roots: core/workers core/packages\n";
8
7
  function joinFindPath(parent, child) {
@@ -1,6 +1,6 @@
1
1
  /** Native port of share-worker-skill.sh. */
2
2
  import * as path from "node:path";
3
- import { resolveBundledAsset } from "../utils/hq-roots.js";
3
+ import { BUNDLED_ASSET_DIR, packageRoot } from "../utils/hq-roots.js";
4
4
  import { existsFollowingLinks, isDirectory, isSymlink, resolveWorkerCallerRoot, runGit, errorCode, } from "./core-worker-skills-runtime.js";
5
5
  export const WORKER_SKILL_SHARE_ASSET = "core/scripts/share-worker-skill.sh";
6
6
  const WORKER_SKILL_SHARE_USAGE = "usage: $0 [--apply] [--force] <scope-dir> <skill-name> <worker-skill-path>...\n";
@@ -88,7 +88,7 @@ function forceSymlink(target, filePath, fileSystem) {
88
88
  fileSystem.symlinkSync(target, filePath);
89
89
  }
90
90
  function runtimeScriptPath(dependencies) {
91
- return dependencies.scriptPath ?? resolveBundledAsset(WORKER_SKILL_SHARE_ASSET);
91
+ return dependencies.scriptPath ?? path.join(packageRoot(), BUNDLED_ASSET_DIR, WORKER_SKILL_SHARE_ASSET);
92
92
  }
93
93
  export function runWorkerSkillShare(argv, dependencies) {
94
94
  let apply = false;
@@ -3,6 +3,6 @@ export type WorkerSkillCommandContext = {
3
3
  cwd: string;
4
4
  hqRoot?: string;
5
5
  };
6
- /** Keep the shell rollback route byte-transparent while the family flag is off. */
6
+ /** Run the native implementation for the worker-skills family. */
7
7
  export declare function runWorkerSkillCommand(name: WorkerSkillCommandName, args: string[], context: WorkerSkillCommandContext): Promise<number>;
8
8
  //# sourceMappingURL=core-worker-skills-command.d.ts.map
@@ -1,24 +1,7 @@
1
- import { runBundledScript } from "../utils/run-bundled-script.js";
2
- import { coreNativeWorkerSkillsEnabled } from "./core-native-worker-skills-flag.js";
3
- import { runWorkerSkillLint, WORKER_SKILL_LINT_ASSET } from "./core-worker-lint-native.js";
4
- import { runWorkerSkillShare, WORKER_SKILL_SHARE_ASSET } from "./core-worker-share-native.js";
5
- const assets = {
6
- lint: WORKER_SKILL_LINT_ASSET,
7
- share: WORKER_SKILL_SHARE_ASSET,
8
- };
9
- /** Keep the shell rollback route byte-transparent while the family flag is off. */
1
+ import { runWorkerSkillLint } from "./core-worker-lint-native.js";
2
+ import { runWorkerSkillShare } from "./core-worker-share-native.js";
3
+ /** Run the native implementation for the worker-skills family. */
10
4
  export async function runWorkerSkillCommand(name, args, context) {
11
- if (!(await coreNativeWorkerSkillsEnabled())) {
12
- const result = runBundledScript({
13
- asset: assets[name],
14
- args,
15
- cwd: context.cwd,
16
- hqRoot: context.hqRoot,
17
- });
18
- if (result.code !== 0)
19
- process.exit(result.code);
20
- return result.code;
21
- }
22
5
  const result = name === "lint"
23
6
  ? runWorkerSkillLint(args, { cwd: context.cwd })
24
7
  : runWorkerSkillShare(args, { cwd: context.cwd });
@@ -75,9 +75,13 @@ export type RebuildIndexTarget = {
75
75
  /** One-line summary, shown in `hq core --help`. */
76
76
  summary: string;
77
77
  };
78
- export type WorkerSubcommand = ScaffoldAsset & {
78
+ export type WorkerSubcommand = {
79
79
  /** Subcommand name under the `worker` group — `hq core worker <name>`. */
80
80
  name: string;
81
+ /** Caller-relative root used by the native command. */
82
+ root: "cwd";
83
+ /** One-line summary, shown in `hq core --help`. */
84
+ summary: string;
81
85
  };
82
86
  /**
83
87
  * Native commands that still claim a scaffold forwarder asset. The forwarder
@@ -51,6 +51,7 @@ import { DERIVE_TRIGGER_FACTS_ASSET } from "./core-derive-trigger-facts-native.j
51
51
  import { registerCoreHookStateHelpers } from "./core-hook-state-helpers-native.js";
52
52
  import { registerCoreHookBatchCommand } from "./core-hook-batch-command.js";
53
53
  import { registerCorePipelineWorkerTableCommand } from "./core-pipeline-workers-command.js";
54
+ import { registerCorePipelineRuntimeCommands } from "./core-pipeline-conductor-command.js";
54
55
  import { resolveLiveRoot } from "../utils/hq-roots.js";
55
56
  import { interpreterFor, runBundledScript } from "../utils/run-bundled-script.js";
56
57
  import { CompanyIndexUserError, renderIndexTarget } from "../lib/index-render/index.js";
@@ -132,13 +133,11 @@ export const NATIVE_SCAFFOLD_COMMANDS = [
132
133
  export const WORKER_SUBCOMMANDS = [
133
134
  {
134
135
  name: "lint",
135
- asset: "core/scripts/lint-shared-worker-skills.sh",
136
136
  root: "cwd",
137
137
  summary: "Fail on worker-scoped skills duplicated by copy instead of single-sourced",
138
138
  },
139
139
  {
140
140
  name: "share",
141
- asset: "core/scripts/share-worker-skill.sh",
142
141
  root: "cwd",
143
142
  summary: "Migrate duplicated worker skills onto one canonical file + relative symlinks",
144
143
  },
@@ -307,6 +306,10 @@ export const SCAFFOLD_ONLY_ASSETS = [
307
306
  { asset: "core/scripts/handoff-post.sh", root: "live", summary: "Rollback shell for native handoff post" },
308
307
  { asset: "core/scripts/handoff-open-steps.sh", root: "live", summary: "Rollback shell for native handoff open-steps" },
309
308
  { asset: "core/scripts/qmd-reindex-bg.sh", root: "live", summary: "Rollback handoff reindex forwarder" },
309
+ // Pipeline rollback companions remain bundled until the family removal PR.
310
+ { asset: "core/scripts/pipeline-conductor.sh", root: "live", summary: "Rollback shell for native pipeline conductor" },
311
+ { asset: "core/scripts/pipeline-driver.sh", root: "live", summary: "Rollback shell for native pipeline driver" },
312
+ { asset: "core/scripts/pipeline-envelope.sh", root: "live", summary: "Validation helper for the pipeline shell rollback companions" },
310
313
  { asset: "core/scripts/lib/portable.sh", root: "live", summary: "Shared portable helpers sourced by bundled scripts" },
311
314
  ...NATIVE_UTILITY_COMMANDS.map(({ asset, root, summary }) => ({
312
315
  asset,
@@ -324,7 +327,6 @@ export const SCAFFOLD_ASSETS = [
324
327
  ...SCAFFOLD_COMMANDS,
325
328
  ...NATIVE_SCAFFOLD_COMMANDS,
326
329
  ...SCAFFOLD_ONLY_ASSETS,
327
- ...WORKER_SUBCOMMANDS,
328
330
  ];
329
331
  /**
330
332
  * Resolve the tree an entry runs against, and the cwd to run it in.
@@ -404,6 +406,7 @@ export function registerCoreCommands(program) {
404
406
  registerCoreDeriveTriggerFactsCommand(core);
405
407
  registerCoreHookStateHelpers(core);
406
408
  registerCoreHookBatchCommand(core);
409
+ registerCorePipelineRuntimeCommands(core);
407
410
  const sessionHelperCommands = [
408
411
  {
409
412
  name: "session-journal", asset: "core/scripts/session-journal.sh", root: "live",
@@ -555,7 +558,7 @@ export function registerCoreCommands(program) {
555
558
  const command = worker
556
559
  .command(entry.name)
557
560
  .description(entry.summary)
558
- // Pure passthrough: the wrapped script owns its own argument grammar.
561
+ // Pure passthrough: the native handler owns the worker command grammar.
559
562
  .allowUnknownOption()
560
563
  .allowExcessArguments()
561
564
  .helpOption(false)
@@ -565,11 +568,7 @@ export function registerCoreCommands(program) {
565
568
  const operands = cmd.args.length > 0 ? cmd.args : args;
566
569
  await runWorkerSkillCommand(entry.name, operands, resolveEntryRoot(entry, scope));
567
570
  });
568
- describeCoreCommand(command, {
569
- execution: interpreterFor(entry.asset) === "node" ? "bundled-node" : "bundled-shell",
570
- asset: entry.asset,
571
- root: entry.root,
572
- });
571
+ describeCoreCommand(command, { execution: "native", root: entry.root });
573
572
  }
574
573
  const runCommand = (entry, args = [], cmd, target) => {
575
574
  const scope = core.opts();
@@ -1,7 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { ensureCognitoToken } from "../utils/cognito-session.js";
4
- import { vaultApiFetch } from "../utils/vault-api.js";
4
+ import { resolveDestinationCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
5
5
  import { isMachineIdentity } from "@indigoai-us/hq-cloud";
6
6
  import { peekIdToken } from "../utils/id-token.js";
7
7
  import { EMAIL_PATTERN, RECIPIENT_UID_PATTERN } from "../lib/notify-recipient.js";
@@ -830,12 +830,20 @@ async function runDmForward(eventId, opts) {
830
830
  console.error(chalk.red("--to accepts one recipient or channel, not a group recipient list."));
831
831
  process.exit(1);
832
832
  }
833
+ const destinationChannelName = parseChannelName(opts.to);
834
+ if (opts.company !== undefined && destinationChannelName !== null) {
835
+ console.error(chalk.red("--company applies to person or agent destinations; a channel uses its own company."));
836
+ process.exit(1);
837
+ }
838
+ if (opts.company !== undefined && !opts.company.trim()) {
839
+ console.error(chalk.red("--company requires a company slug or cmp_ ID."));
840
+ process.exit(1);
841
+ }
833
842
  const note = opts.noteFile !== undefined
834
843
  ? readFileSync(opts.noteFile, "utf8")
835
844
  : opts.note;
836
845
  const token = await ensureCognitoToken();
837
846
  const source = await resolveForwardSourceConversation(token, opts.from);
838
- const destinationChannelName = parseChannelName(opts.to);
839
847
  const fileAccess = opts.grantFiles ? "grant" : opts.omitFiles ? "omit" : undefined;
840
848
  const shared = {
841
849
  ...(note !== undefined ? { body: note } : {}),
@@ -869,8 +877,19 @@ async function runDmForward(eventId, opts) {
869
877
  console.error(chalk.red("--to must be a person email, person/agent UID, or channel name/ID."));
870
878
  process.exit(1);
871
879
  }
880
+ // The server runs the destination-side cross-company check only when the
881
+ // request names the destination company; without it the destination is
882
+ // treated as personal. Not defaulted from the source, which would make a
883
+ // forward into another company look same-company.
884
+ const destinationCompanyUid = opts.company !== undefined
885
+ ? await resolveDestinationCompanyUid(token, opts.company.trim())
886
+ : undefined;
872
887
  path = "/v1/notify/dm";
873
- payload = { ...recipient, ...shared };
888
+ payload = {
889
+ ...recipient,
890
+ ...shared,
891
+ ...(destinationCompanyUid ? { companyUid: destinationCompanyUid } : {}),
892
+ };
874
893
  destinationLabel = opts.to;
875
894
  }
876
895
  const res = await vaultApiFetch({ token, path, method: "POST", body: payload });
@@ -1529,6 +1548,7 @@ export function registerDmCommand(program) {
1529
1548
  .option("--grant-files", "Grant the recipient access to files they cannot currently open")
1530
1549
  .option("--omit-files", "Omit files the recipient cannot currently open")
1531
1550
  .option("--cross-company", "Confirm forwarding across company boundaries")
1551
+ .option("--company <slug|cmp_id>", "Company the person or agent destination belongs to, so cross-company rules apply")
1532
1552
  .action(async (eventId, opts) => {
1533
1553
  await runDmForward(eventId, opts);
1534
1554
  });
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Live HQ Storage gate and refusal surface for `hq storage`.
3
3
  *
4
- * Allowlist gate, fails closed (owner decision 2026-10-11, Indigo-only until
5
- * the owner widens it; see the storage-feature-flag module in src/lib). This
6
- * is the deliberate exception to the default-on kill-switch rule: any error,
4
+ * Registry gate, fails closed. The `hq-storage` registry row decides who has
5
+ * Storage; it defaults on for every signed-in account (widened 2026-10-13
6
+ * from the 2026-10-11 Indigo-only start; see the storage-feature-flag module
7
+ * in src/lib). The fail-closed client fallback is the deliberate exception to the default-on kill-switch rule: any error,
7
8
  * a signed-out caller, an API-key caller, a membership read failure, or an
8
9
  * unreadable registry all mean Storage is OFF.
9
10
  *
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Live HQ Storage gate and refusal surface for `hq storage`.
3
3
  *
4
- * Allowlist gate, fails closed (owner decision 2026-10-11, Indigo-only until
5
- * the owner widens it; see the storage-feature-flag module in src/lib). This
6
- * is the deliberate exception to the default-on kill-switch rule: any error,
4
+ * Registry gate, fails closed. The `hq-storage` registry row decides who has
5
+ * Storage; it defaults on for every signed-in account (widened 2026-10-13
6
+ * from the 2026-10-11 Indigo-only start; see the storage-feature-flag module
7
+ * in src/lib). The fail-closed client fallback is the deliberate exception to the default-on kill-switch rule: any error,
7
8
  * a signed-out caller, an API-key caller, a membership read failure, or an
8
9
  * unreadable registry all mean Storage is OFF.
9
10
  *
@@ -278,8 +278,6 @@ export const FAST_SCAFFOLD_SCRIPTS = [
278
278
  modulePath: "./commands/core-worker-lint-fast.js",
279
279
  exportName: "runWorkerSkillLintFast",
280
280
  flagKey: "hq-core-native-fast-route",
281
- familyFlagKey: "core-native-worker-skills",
282
- asset: "core/scripts/lint-shared-worker-skills.sh",
283
281
  root: "cwd",
284
282
  shellCwd: "caller",
285
283
  shellHqRoot: "none",