@indigoai-us/hq-cli 5.345.23 → 5.345.24

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.24] — 2026-10-06
6
+ - Keep native core ontology, delegate-secret, share-suggestion-state, and trusted-bind behavior aligned with their scaffold scripts.
7
+
5
8
  ## [5.345.23] — 2026-10-06
6
9
  - Route eight additional isolated native core commands through the fast CLI entry.
7
10
  - hqd applies the runtime kill switch to new hook and MCP operations within the existing 60-second freshness bound. A local opt-out also disables the daemon, while a local opt-in cannot override an operator-off value.
@@ -158,6 +158,12 @@ function listCandidates(dir) {
158
158
 
159
159
  function main() {
160
160
  const a = parseArgs(process.argv.slice(2));
161
+ const backrefThreshold = Number(process.env.HQ_ONTOLOGY_BACKREF_THRESHOLD || 50);
162
+ const backrefKeepInline = Number(process.env.HQ_ONTOLOGY_BACKREF_KEEP_INLINE || 25);
163
+ if (!(backrefThreshold > backrefKeepInline)) {
164
+ console.error(`ontology-garden: BACKREF_THRESHOLD (${backrefThreshold}) must be greater than BACKREF_KEEP_INLINE (${backrefKeepInline})`);
165
+ process.exit(1);
166
+ }
161
167
  const co = path.join(a.root, "companies", a.company);
162
168
  if (!fs.existsSync(co)) { console.error(`ontology-garden: no company ${a.company}`); process.exit(2); }
163
169
  const ont = path.join(co, "ontology"), sig = path.join(co, "signals");
@@ -41,6 +41,7 @@ export declare function requireRecord(value: unknown, property: string): JsonRec
41
41
  export declare function moveFile(source: string, target: string): void;
42
42
  export declare function writeJsonAtomically(target: string, value: unknown, siblingTemp?: boolean): void;
43
43
  export declare function delegateDiagnosticPath(argument: string): string;
44
+ export declare function scanSecretFiles(file: string): string[] | undefined;
44
45
  /** Fast-entry adapter; family gating and shell fallback happen before import. */
45
46
  export declare function runDelegateFast(args: readonly string[], _stdin: string, cwd: string, hqRoot: string | undefined, commandName: string): number;
46
47
  /** Register the Wave 1a delegate commands and preserve the shell rollback path. */
@@ -703,7 +703,7 @@ function runRepo(args, context) {
703
703
  writeOut(`${prefix} recorded branch state (pushed=${pushed}, dirty=${dirtyLines.length} files, accessVerified=${accessVerified})\n`);
704
704
  return 0;
705
705
  }
706
- function scanSecretFiles(file) {
706
+ export function scanSecretFiles(file) {
707
707
  let schema;
708
708
  try {
709
709
  schema = fs.readFileSync(file, "utf8");
@@ -715,7 +715,7 @@ function scanSecretFiles(file) {
715
715
  throw error;
716
716
  }
717
717
  return [...new Set(schema.split(/\r?\n/)
718
- .filter((line) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(line))
718
+ .filter((line) => /^[A-Z][A-Z0-9_]*(\/[A-Z][A-Z0-9_]+)*=/.test(line))
719
719
  .map((line) => line.slice(0, line.indexOf("="))))].sort();
720
720
  }
721
721
  function isRegularFile(file) {
@@ -397,6 +397,52 @@ function writeGardenDoc(file, fm, body, dryRun) {
397
397
  fs.writeFileSync(`${file}.tmp`, lines.join("\n"));
398
398
  fs.renameSync(`${file}.tmp`, file);
399
399
  }
400
+ const BACKREF_SUMMARY_MARKER = "<!-- backref-archive -->";
401
+ function readGardenArchiveIndex(file) {
402
+ if (!fs.existsSync(file))
403
+ return { signalIds: [], unreadable: false, stderr: "" };
404
+ try {
405
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
406
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && Array.isArray(parsed.signalIds)) {
407
+ return {
408
+ signalIds: (parsed.signalIds).filter((item) => typeof item === "string"),
409
+ unreadable: false,
410
+ stderr: "",
411
+ };
412
+ }
413
+ }
414
+ catch (error) {
415
+ const detail = error instanceof Error ? error.message : String(error);
416
+ return { signalIds: [], unreadable: true, stderr: `ontology-garden: archive index unreadable at ${file}: ${detail}; skipping compaction\n` };
417
+ }
418
+ return { signalIds: [], unreadable: true, stderr: "" };
419
+ }
420
+ function compactGardenFactBody(body, slug, threshold, keepInline, archivedTotalBefore) {
421
+ const lines = body.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0);
422
+ const isBackref = (line) => /^- \[[a-z_]+\] .+ \(signal [0-9a-f]{12}\)\s*$/.test(line);
423
+ const backrefLines = lines.filter(isBackref);
424
+ const other = lines.filter((line) => !isBackref(line) && !line.includes(BACKREF_SUMMARY_MARKER));
425
+ if (backrefLines.length <= threshold)
426
+ return { body, archivedLines: [], archivedIds: [], stamp: "", changed: false };
427
+ const archiveCount = backrefLines.length - keepInline;
428
+ const archivedLines = backrefLines.slice(0, archiveCount);
429
+ const inlineLines = backrefLines.slice(archiveCount);
430
+ const archivedIds = archivedLines.map((line) => line.match(/\(signal ([0-9a-f]{12})\)$/)?.[1]).filter((id) => Boolean(id));
431
+ const total = archivedTotalBefore + archivedLines.length;
432
+ const stamp = new Date().toISOString().slice(0, 7);
433
+ const summary = `- ${BACKREF_SUMMARY_MARKER} _${total} older mention${total === 1 ? "" : "s"} archived in [history](./${slug}.history/${stamp}.md)_`;
434
+ return { body: [summary, ...other, ...inlineLines].join("\n"), archivedLines, archivedIds, stamp, changed: true };
435
+ }
436
+ function appendGardenArchive(file, isoDate, lines, dryRun) {
437
+ if (dryRun)
438
+ return;
439
+ const batch = `\n<!-- archived ${isoDate} -->\n${lines.join("\n")}\n`;
440
+ const previous = fs.existsSync(file) ? fs.readFileSync(file, "utf8").replace(/\s+$/, "") : "";
441
+ const next = previous ? `${previous}\n${batch}` : `# Back-reference archive\n${batch}`;
442
+ fs.mkdirSync(path.dirname(file), { recursive: true });
443
+ fs.writeFileSync(`${file}.tmp`, next);
444
+ fs.renameSync(`${file}.tmp`, file);
445
+ }
400
446
  const slugify = (value) => value.toLowerCase().normalize("NFKD").replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, 80);
401
447
  const isoNow = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
402
448
  function listCandidates(directory) {
@@ -436,6 +482,11 @@ export function gardenCommand(args, defaultRoot) {
436
482
  }
437
483
  if (!company)
438
484
  return err(2, "usage: ontology-garden.mjs --company <co> [--hq-root <p>] [--dry-run] [--json]\n");
485
+ const threshold = Number(process.env.HQ_ONTOLOGY_BACKREF_THRESHOLD || 50);
486
+ const keepInline = Number(process.env.HQ_ONTOLOGY_BACKREF_KEEP_INLINE || 25);
487
+ if (!(threshold > keepInline)) {
488
+ return err(1, `ontology-garden: BACKREF_THRESHOLD (${threshold}) must be greater than BACKREF_KEEP_INLINE (${keepInline})\n`);
489
+ }
439
490
  const hqRoot = root || process.env.HQ_ROOT || defaultRoot;
440
491
  const companyDir = path.join(hqRoot, "companies", company);
441
492
  if (!fs.existsSync(companyDir))
@@ -445,6 +496,7 @@ export function gardenCommand(args, defaultRoot) {
445
496
  const report = { company, entities_created: 0, entities_updated: 0, signals_promoted: 0, facts_written: 0, rejected: 0, candidates_processed: 0, dry_run: dryRun };
446
497
  const rejected = [];
447
498
  const moves = [];
499
+ let stderr = "";
448
500
  const entities = new Map();
449
501
  const nameIndex = new Map();
450
502
  const entityDirectory = path.join(ontology, "entities");
@@ -563,8 +615,24 @@ export function gardenCommand(args, defaultRoot) {
563
615
  const previous = fs.existsSync(factFile)
564
616
  ? readGardenDoc(factFile)
565
617
  : { fm: { entity: slug, type: entity.type, audience_key: key }, body: "" };
566
- if (!previous.body.includes(`signal ${id.slice(0, 12)}`)) {
567
- writeGardenDoc(factFile, previous.fm, [previous.body, line].filter(Boolean).join("\n"), dryRun);
618
+ const indexFile = path.join(path.dirname(factFile), `${slug}.history`, "_index.json");
619
+ const archiveIndex = readGardenArchiveIndex(indexFile);
620
+ stderr += archiveIndex.stderr;
621
+ const signalId = id.slice(0, 12);
622
+ if (!previous.body.includes(`signal ${signalId}`) && !archiveIndex.signalIds.includes(signalId)) {
623
+ let nextBody = [previous.body, line].filter(Boolean).join("\n");
624
+ const compact = compactGardenFactBody(nextBody, slug, threshold, keepInline, archiveIndex.signalIds.length);
625
+ if (compact.changed && !archiveIndex.unreadable) {
626
+ const archiveFile = path.join(path.dirname(factFile), `${slug}.history`, `${compact.stamp}.md`);
627
+ appendGardenArchive(archiveFile, new Date().toISOString().slice(0, 10), compact.archivedLines, dryRun);
628
+ if (!dryRun) {
629
+ const nextIndex = { version: 1, signalIds: [...archiveIndex.signalIds, ...compact.archivedIds] };
630
+ fs.mkdirSync(path.dirname(indexFile), { recursive: true });
631
+ fs.writeFileSync(indexFile, `${JSON.stringify(nextIndex)}\n`);
632
+ }
633
+ nextBody = compact.body;
634
+ }
635
+ writeGardenDoc(factFile, previous.fm, nextBody, dryRun);
568
636
  report.facts_written += 1;
569
637
  }
570
638
  if (key === "company") {
@@ -598,9 +666,9 @@ export function gardenCommand(args, defaultRoot) {
598
666
  fs.writeFileSync(path.join(ontology, ".last-run"), `${Date.now()}\n`);
599
667
  }
600
668
  }
601
- return ok(json
602
- ? `${JSON.stringify(report)}\n`
603
- : `garden ${company}: +${report.entities_created} entities, ~${report.entities_updated} updated, +${report.signals_promoted} signals, +${report.facts_written} facts, ${report.rejected} rejected, ${report.candidates_processed} candidates${dryRun ? " (dry run)" : ""}\n`);
669
+ return { code: 0, stdout: json
670
+ ? `${JSON.stringify(report)}\n`
671
+ : `garden ${company}: +${report.entities_created} entities, ~${report.entities_updated} updated, +${report.signals_promoted} signals, +${report.facts_written} facts, ${report.rejected} rejected, ${report.candidates_processed} candidates${dryRun ? " (dry run)" : ""}\n`, stderr };
604
672
  }
605
673
  export function audienceGrant(args, root) {
606
674
  let company = "";
@@ -209,8 +209,14 @@ export function runShareSuggestionState(args, stdin, deps) {
209
209
  try {
210
210
  return JSON.parse(f.readFileSync(file, "utf8"));
211
211
  }
212
- catch {
213
- return fallback;
212
+ catch (cause) {
213
+ const failure = new Error("share-suggestion-state JSON read failed");
214
+ failure.name = "ShareSuggestionStateReadError";
215
+ Object.assign(failure, {
216
+ shareSuggestionStateReadPath: file,
217
+ shareSuggestionStateCauseName: cause instanceof Error && cause.name ? cause.name : "Error",
218
+ });
219
+ throw failure;
214
220
  }
215
221
  };
216
222
  const writeJson = (file, value) => {
@@ -454,6 +460,17 @@ export function runShareSuggestionState(args, stdin, deps) {
454
460
  return { code: 0, stdout: "", stderr: "" };
455
461
  }
456
462
  catch (error) {
463
+ if (error instanceof Error && "shareSuggestionStateReadPath" in error) {
464
+ const failure = error;
465
+ const readPath = platform === "win32"
466
+ ? path.win32.resolve(failure.shareSuggestionStateReadPath).replaceAll("\\", "/")
467
+ : failure.shareSuggestionStateReadPath;
468
+ return {
469
+ code: 3,
470
+ stdout: "",
471
+ stderr: `share-suggestion-state: unable to read ${readPath} (${failure.shareSuggestionStateCauseName})\n`,
472
+ };
473
+ }
457
474
  const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
458
475
  return { code: 0, stdout: "", stderr: `share-suggestion-state: internal error\n${detail}\n` };
459
476
  }
@@ -15,6 +15,7 @@ export type WorkMeshBindDeps = {
15
15
  stdout: string;
16
16
  stderr: string;
17
17
  };
18
+ daemonWarningEnabled: boolean;
18
19
  runHqSession: (root: string, args: string[], env: NodeJS.ProcessEnv) => {
19
20
  code: number;
20
21
  stdout: string;
@@ -25,8 +26,27 @@ export type WorkMeshBindDeps = {
25
26
  tmpDir: string;
26
27
  ulid: (now: Date) => string;
27
28
  };
28
- export declare const createWorkMeshBindDeps: (root: string, cwd: string, env: NodeJS.ProcessEnv, now: () => Date) => WorkMeshBindDeps;
29
+ export declare const createWorkMeshBindDeps: (root: string, cwd: string, env: NodeJS.ProcessEnv, now: () => Date, daemonWarningEnabled?: boolean) => WorkMeshBindDeps;
30
+ type DetachedSpawnOptions = {
31
+ cwd: string;
32
+ root: string;
33
+ env: NodeJS.ProcessEnv;
34
+ platform?: NodeJS.Platform;
35
+ existsSync?: (candidate: string) => boolean;
36
+ spawnProcess?: (command: string, args: string[], options: {
37
+ cwd: string;
38
+ env: NodeJS.ProcessEnv;
39
+ detached: boolean;
40
+ stdio: "ignore";
41
+ shell: boolean;
42
+ windowsVerbatimArguments?: boolean;
43
+ }) => {
44
+ unref: () => void;
45
+ };
46
+ };
47
+ export declare function spawnWorkMeshHqDetached(args: string[], options: DetachedSpawnOptions): void;
29
48
  export declare function resolveWorkMeshBindSessionId(root: string, env: NodeJS.ProcessEnv, fileSystem?: typeof fs): string;
30
49
  export declare function runWorkMeshBindTrusted(args: string[], deps: WorkMeshBindDeps): UtilityOutput;
31
50
  export declare function registerCoreWorkMeshBindTrustedNative(core: Command): void;
51
+ export {};
32
52
  //# sourceMappingURL=core-work-mesh-bind-trusted-native.d.ts.map
@@ -2,10 +2,13 @@
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { spawn, spawnSync } from "node:child_process";
5
+ import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
5
6
  import { generateUlid } from "../lib/mesh/live/ulid.js";
6
7
  import { resolveLiveRoot } from "../utils/hq-roots.js";
8
+ import { planResolvedCommandSpawn } from "../utils/windows-spawn.js";
7
9
  import { registerNativeUtility } from "./core-native-utilities.js";
8
10
  export const WORK_MESH_BIND_TRUSTED_ASSET = "core/scripts/work-mesh-live-bind-trusted.sh";
11
+ const WORK_MESH_DAEMON_WARNING_FLAG = "hooks.work-mesh-daemon-not-loaded-warning";
9
12
  export const WORK_MESH_BIND_TRUSTED_HELP = `hq-core: public
10
13
  US-011 — write session meta + reconcile with observation.trustedContext.
11
14
  No --trusted CLI flag in hq-cli; trusted bind is the observation field.
@@ -14,21 +17,61 @@ set -euo pipefail
14
17
  ROOT="\${HQ_ROOT:-\${CLAUDE_PROJECT_DIR:-}}"
15
18
  if [ -z "$ROOT" ]; then
16
19
  `;
17
- export const createWorkMeshBindDeps = (root, cwd, env, now) => ({
20
+ export const createWorkMeshBindDeps = (root, cwd, env, now, daemonWarningEnabled = false) => ({
18
21
  root, cwd, env, now, fs, pid: process.pid, tmpDir: env.TMPDIR || "/tmp", ulid: (date) => generateUlid(date.getTime()),
22
+ daemonWarningEnabled: daemonWarningEnabled
23
+ && Boolean(env.HQ_FLAGS_API_URL?.trim())
24
+ && /^cmp_[A-Za-z0-9]{3,128}$/.test(env.HQ_COMPANY_UID?.trim() ?? ""),
19
25
  runHq: (args, childEnv) => {
20
- const result = spawnSync("hq", args, { cwd, env: { ...childEnv, HQ_ROOT: root }, encoding: "utf8" });
26
+ const executable = "hq";
27
+ const resolvedExecutable = process.platform === "win32"
28
+ ? resolveWindowsExecutable(executable, childEnv, (candidate) => {
29
+ try {
30
+ return fs.statSync(candidate).isFile();
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }, cwd) ?? executable
36
+ : executable;
37
+ const plan = planResolvedCommandSpawn(resolvedExecutable, args, childEnv);
38
+ const result = spawnSync(plan.cmd, plan.args, {
39
+ cwd,
40
+ env: { ...childEnv, HQ_ROOT: root },
41
+ encoding: "utf8",
42
+ shell: plan.shell,
43
+ ...(plan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
44
+ timeout: 2_000,
45
+ });
21
46
  return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
22
47
  },
23
48
  runHqSession: (scriptRoot, args, childEnv) => {
24
49
  const result = spawnSync("bash", [path.join(scriptRoot, "core/scripts/hq-session.sh"), ...args], { cwd, env: childEnv, encoding: "utf8" });
25
50
  return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
26
51
  },
27
- spawnHq: (args, childEnv) => {
28
- const child = spawn("hq", args, { cwd, env: { ...childEnv, HQ_ROOT: root }, detached: true, stdio: "ignore" });
29
- child.unref();
30
- },
52
+ spawnHq: (args, childEnv) => spawnWorkMeshHqDetached(args, {
53
+ cwd,
54
+ root,
55
+ env: childEnv,
56
+ }),
31
57
  });
58
+ export function spawnWorkMeshHqDetached(args, options) {
59
+ const platform = options.platform ?? process.platform;
60
+ const childEnv = { ...options.env, HQ_ROOT: options.root };
61
+ const executable = platform === "win32"
62
+ ? resolveWindowsExecutable("hq", childEnv, options.existsSync ?? fs.existsSync, options.cwd) ?? "hq"
63
+ : "hq";
64
+ const plan = planResolvedCommandSpawn(executable, args, childEnv, platform);
65
+ const child = (options.spawnProcess ?? spawn)(plan.cmd, plan.args, {
66
+ cwd: options.cwd,
67
+ env: childEnv,
68
+ detached: true,
69
+ stdio: "ignore",
70
+ shell: plan.shell,
71
+ ...(plan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
72
+ });
73
+ child.unref();
74
+ }
32
75
  export function resolveWorkMeshBindSessionId(root, env, fileSystem = fs) {
33
76
  for (const key of ["HQ_SESSION_ID", "CLAUDE_CODE_SESSION_ID", "CLAUDE_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID"]) {
34
77
  const value = (env[key] ?? "").replace(/\s/g, "");
@@ -44,6 +87,16 @@ export function resolveWorkMeshBindSessionId(root, env, fileSystem = fs) {
44
87
  }
45
88
  }
46
89
  function hasExecutable(deps, command) {
90
+ if (process.platform === "win32") {
91
+ return Boolean(resolveWindowsExecutable(command, deps.env, (candidate) => {
92
+ try {
93
+ return deps.fs.statSync(candidate).isFile();
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ }, deps.cwd));
99
+ }
47
100
  for (const directory of (deps.env.PATH ?? "").split(path.delimiter)) {
48
101
  if (!directory)
49
102
  continue;
@@ -175,6 +228,21 @@ export function runWorkMeshBindTrusted(args, deps) {
175
228
  }
176
229
  if (deps.env.HQ_WORK_MESH_RECONCILE_STUB === "1")
177
230
  return { code: 0, stdout: output.join(""), stderr };
231
+ if (deps.daemonWarningEnabled && hasExecutable(deps, "hq")) {
232
+ const status = deps.runHq(["mesh", "daemon", "status", "--json"], { ...deps.env, HQ_NO_UPDATE_CHECK: "1" });
233
+ if (status.code === 0) {
234
+ try {
235
+ const parsed = JSON.parse(status.stdout);
236
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
237
+ const daemon = parsed;
238
+ if (daemon.ok === true && daemon.running === false && typeof daemon.message === "string" && daemon.message.includes("installed (")) {
239
+ stderr += "Work Mesh daemon is installed but not loaded; start it with: hq mesh daemon install\n";
240
+ }
241
+ }
242
+ }
243
+ catch { /* the shell's jq test fails quiet on invalid status JSON */ }
244
+ }
245
+ }
178
246
  if (hasExecutable(deps, "hq") && deps.fs.existsSync(observationFile)) {
179
247
  deps.spawnHq(["mesh", "context", "reconcile", "--observation-file", observationFile, "--machine"], childEnv);
180
248
  }
@@ -184,7 +252,11 @@ export function registerCoreWorkMeshBindTrustedNative(core) {
184
252
  registerNativeUtility(core, {
185
253
  name: "work-mesh-bind-trusted", asset: WORK_MESH_BIND_TRUSTED_ASSET, root: "live", summary: "Bind trusted session context and reconcile it with Work Mesh",
186
254
  resolveRoot: (_cwd, hqRoot) => resolveLiveRoot({ hqRoot }),
187
- run: (args, context) => runWorkMeshBindTrusted(args, createWorkMeshBindDeps(context.root, context.cwd, context.env, context.now)),
255
+ run: async (args, context) => {
256
+ const { fastFlagEnabled } = await import("../native-fast-route.js");
257
+ const daemonWarningEnabled = await fastFlagEnabled(WORK_MESH_DAEMON_WARNING_FLAG, context.env);
258
+ return runWorkMeshBindTrusted(args, createWorkMeshBindDeps(context.root, context.cwd, context.env, context.now, daemonWarningEnabled));
259
+ },
188
260
  });
189
261
  }
190
262
  //# sourceMappingURL=core-work-mesh-bind-trusted-native.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.23",
3
+ "version": "5.345.24",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {