@indigoai-us/hq-cli 5.345.24 → 5.345.25

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,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.25] — 2026-10-06
6
+ - Route qmd post-sync indexing and ontology README drift through isolated fast handlers; cover all rebuild-index targets in fast-route parity and benchmarks.
7
+
8
+ - Keep native register-project and ontology source-worklist commands aligned with staging behavior for Board identifiers, DM participant audiences, and transient ACL retries.
9
+
5
10
  ## [5.345.24] — 2026-10-06
6
11
  - Keep native core ontology, delegate-secret, share-suggestion-state, and trusted-bind behavior aligned with their scaffold scripts.
7
12
 
@@ -203,8 +203,18 @@ for f in "$D"/*.md; do
203
203
  # test/sources/inbound/slack-personal-dm.test.ts.
204
204
  [ -n "$aud" ] || aud="$(participant_ids "$f" resolved_participants | grep -E '@|^(prs|agt)_' | paste -sd, -)"
205
205
  if [ -z "$aud" ]; then
206
- aud="$("$HQ" files --company "$co" acl "$rel" --json 2>/dev/null \
207
- | jq -r '[.direct[]? | select(.granteeType=="person" or .granteeType=="email" or .granteeType=="agent") | select(.permission=="read" or .permission=="write" or .permission=="admin") | .granteeId] | unique | join(",")' 2>/dev/null)"
206
+ # A transient ACL or jq failure must not turn a source into a durable
207
+ # no-audience skip. Retry once, while treating valid empty ACL output as
208
+ # a completed lookup that needs no retry.
209
+ for attempt in 1 2; do
210
+ acl_output=""
211
+ if acl_output="$("$HQ" files --company "$co" acl "$rel" --json 2>/dev/null \
212
+ | jq -r '[.direct[]? | select(.granteeType=="person" or .granteeType=="email" or .granteeType=="agent") | select(.permission=="read" or .permission=="write" or .permission=="admin") | .granteeId] | unique | join(",")' 2>/dev/null)"; then
213
+ aud="$acl_output"
214
+ break
215
+ fi
216
+ aud=""
217
+ done
208
218
  fi ;;
209
219
  esac
210
220
  if [ -z "$aud" ]; then
@@ -831,6 +831,77 @@ function itemList(file, field) {
831
831
  }
832
832
  return output;
833
833
  }
834
+ function participantIds(file, field) {
835
+ if (!fs.existsSync(file))
836
+ return [];
837
+ const output = [];
838
+ let frontmatterLines = 0;
839
+ let inList = false;
840
+ let personUid = "";
841
+ let email = "";
842
+ const flush = () => {
843
+ if (personUid)
844
+ output.push(personUid);
845
+ else if (email)
846
+ output.push(email);
847
+ personUid = "";
848
+ email = "";
849
+ };
850
+ const setField = (line) => {
851
+ const person = line.match(/^person_uid:[\t ]*(.*)$/);
852
+ const mail = line.match(/^email:[\t ]*(.*)$/);
853
+ const value = (person?.[1] ?? mail?.[1] ?? "").replace(/["']/g, "").replace(/^[\t ]+|[\t ]+$/g, "");
854
+ if (person && value)
855
+ personUid = value;
856
+ if (mail && value)
857
+ email = value;
858
+ };
859
+ for (const rawLine of fs.readFileSync(file, "utf8").split("\n")) {
860
+ const line = rawLine.replace(/\r$/, "");
861
+ if (line === "---") {
862
+ frontmatterLines += 1;
863
+ if (frontmatterLines === 2) {
864
+ if (inList)
865
+ flush();
866
+ break;
867
+ }
868
+ continue;
869
+ }
870
+ if (frontmatterLines !== 1)
871
+ continue;
872
+ if (line.startsWith(`${field}:`)) {
873
+ inList = true;
874
+ continue;
875
+ }
876
+ if (!inList)
877
+ continue;
878
+ if (/^[^\t -]/.test(line)) {
879
+ flush();
880
+ inList = false;
881
+ continue;
882
+ }
883
+ const dash = line.match(/^[\t ]*-[\t ]+(.*)$/);
884
+ if (dash) {
885
+ flush();
886
+ let value = dash[1];
887
+ if (value.startsWith("{")) {
888
+ value = value.replace(/[{}"']/g, "");
889
+ for (const fieldValue of value.split(",")) {
890
+ const separator = fieldValue.indexOf(":");
891
+ if (separator < 0)
892
+ continue;
893
+ setField(`${fieldValue.slice(0, separator).trim()}:${fieldValue.slice(separator + 1)}`);
894
+ }
895
+ flush();
896
+ continue;
897
+ }
898
+ setField(value);
899
+ continue;
900
+ }
901
+ setField(line.replace(/^[\t ]+/, ""));
902
+ }
903
+ return output;
904
+ }
834
905
  export function worklistCommand(args, root) {
835
906
  let company = "";
836
907
  let channel = "";
@@ -1012,22 +1083,33 @@ export function worklistCommand(args, root) {
1012
1083
  audience = itemList(file, "audience").join(",");
1013
1084
  if (!audience)
1014
1085
  audience = itemList(file, "attendees").filter((value) => /@|^(prs|agt)_/.test(value)).join(",");
1086
+ if (!audience)
1087
+ audience = participantIds(file, "resolved_participants").filter((value) => /@|^(prs|agt)_/.test(value)).join(",");
1015
1088
  if (!audience) {
1016
- const acl = runHq(["files", "--company", company, "acl", relative, "--json"]);
1017
- try {
1018
- const payload = JSON.parse(acl.stdout);
1019
- const principals = (payload.direct ?? []).filter((value) => {
1020
- if (!value || typeof value !== "object")
1021
- return false;
1022
- const row = value;
1023
- return ["person", "email", "agent"].includes(String(row.granteeType))
1024
- && ["read", "write", "admin"].includes(String(row.permission));
1025
- }).map((value) => String(value.granteeId ?? "")).sort(byteSort);
1026
- audience = [...new Set(principals)].join(",");
1027
- }
1028
- catch (parseError) {
1029
- // jq emits no stdout or stderr for malformed ACL JSON in this pipeline.
1030
- void parseError;
1089
+ for (let attempt = 0; attempt < 2; attempt += 1) {
1090
+ const acl = runHq(["files", "--company", company, "acl", relative, "--json"]);
1091
+ if (acl.status !== 0)
1092
+ continue;
1093
+ try {
1094
+ const payload = JSON.parse(acl.stdout);
1095
+ const direct = payload && typeof payload === "object" && "direct" in payload
1096
+ ? payload.direct
1097
+ : undefined;
1098
+ if (!Array.isArray(direct))
1099
+ throw new Error("ACL direct grants are not an array");
1100
+ const principals = direct.filter((value) => {
1101
+ if (!value || typeof value !== "object")
1102
+ return false;
1103
+ const row = value;
1104
+ return ["person", "email", "agent"].includes(String(row.granteeType))
1105
+ && ["read", "write", "admin"].includes(String(row.permission));
1106
+ }).map((value) => String(value.granteeId ?? "")).sort(byteSort);
1107
+ audience = [...new Set(principals)].join(",");
1108
+ break;
1109
+ }
1110
+ catch {
1111
+ // The shell suppresses jq diagnostics and retries a failed ACL parse.
1112
+ }
1031
1113
  }
1032
1114
  }
1033
1115
  }
@@ -1040,7 +1122,9 @@ export function worklistCommand(args, root) {
1040
1122
  const key = shellSubstitution(audienceKey(audience).stdout);
1041
1123
  output.push(`${JSON.stringify({ file: relative, audience_key: key, audience: audience.split(",") })}\n`);
1042
1124
  }
1043
- return ok(output.join(""));
1125
+ const stdout = output.join("");
1126
+ // Git Bash captures jq.exe's record terminators as CRLF; preserve those bytes on Windows.
1127
+ return ok(process.platform === "win32" ? stdout.replace(/\n/gu, "\r\n") : stdout);
1044
1128
  }
1045
1129
  export function registerCoreOntologyNativeCommands(ontology, rootFor) {
1046
1130
  const entries = [
@@ -0,0 +1,2 @@
1
+ export declare function runOntologyReadmeDriftFast(args: readonly string[], _stdin: string, _cwd: string, hqRoot: string | undefined): number;
2
+ //# sourceMappingURL=core-ontology-readme-drift-fast.d.ts.map
@@ -0,0 +1,5 @@
1
+ import { ontologyReadmeDrift } from "../lib/core-utils/ontology-readme-drift.js";
2
+ export function runOntologyReadmeDriftFast(args, _stdin, _cwd, hqRoot) {
3
+ return ontologyReadmeDrift([...args], { root: hqRoot });
4
+ }
5
+ //# sourceMappingURL=core-ontology-readme-drift-fast.js.map
@@ -0,0 +1,3 @@
1
+ /** Fast adapter for the post-sync QMD entry point. Uses the same persisted flag snapshot as other native fast routes. */
2
+ export declare function runQmdReindexAfterSyncFast(args: readonly string[], _stdin: string, cwd: string, _hqRoot?: string, _commandName?: string, hqRootArg?: string): Promise<number>;
3
+ //# sourceMappingURL=core-qmd-reindex-after-sync-fast.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { QMD_REINDEX_STARTUP_WARNING_FLAG, qmdReindexAfterSync, } from "../lib/core-utils/qmd-reindex-after-sync.js";
2
+ import { reconcileCollections } from "../lib/search-index/index.js";
3
+ import { QMD_COLLECTION_PATH_REPAIR_FLAG } from "../lib/search-index/collection-path-repair-flag-constants.js";
4
+ import { QMD_REINDEX_SINGLE_FLIGHT_FLAG } from "../lib/search-index/reindex-single-flight-flag-constants.js";
5
+ import { fastFlagEnabled } from "../native-fast-route.js";
6
+ /** Fast adapter for the post-sync QMD entry point. Uses the same persisted flag snapshot as other native fast routes. */
7
+ export async function runQmdReindexAfterSyncFast(args, _stdin, cwd, _hqRoot, _commandName, hqRootArg) {
8
+ const [singleFlight, startupWarnings, repairCollectionPaths] = await Promise.all([
9
+ fastFlagEnabled(QMD_REINDEX_SINGLE_FLIGHT_FLAG, process.env, { numericEnvironmentOverride: false }),
10
+ fastFlagEnabled(QMD_REINDEX_STARTUP_WARNING_FLAG, process.env, { numericEnvironmentOverride: false }),
11
+ fastFlagEnabled(QMD_COLLECTION_PATH_REPAIR_FLAG, process.env, { numericEnvironmentOverride: false }),
12
+ ]);
13
+ // The full command adapter injects the explicit HQ root as the first positional
14
+ // argument when the caller supplies --hq-root. The fast route parses that
15
+ // global option before dispatch, so restore the same operand when the command
16
+ // did not receive its own path (help/unknown options and --embed-only calls).
17
+ const hasRootOperand = args.some((arg) => arg !== "--embed" && !arg.startsWith("--"));
18
+ const forwardedArgs = hasRootOperand ? [...args] : [hqRootArg ?? cwd, ...args];
19
+ return qmdReindexAfterSync(forwardedArgs, {
20
+ cwd,
21
+ singleFlightEnabled: () => singleFlight,
22
+ startupFailureWarningsEnabled: () => startupWarnings,
23
+ reconcile: (root, options) => reconcileCollections(root, { ...options, repairPathDrift: repairCollectionPaths }),
24
+ });
25
+ }
26
+ //# sourceMappingURL=core-qmd-reindex-after-sync-fast.js.map
@@ -7,7 +7,7 @@ import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
7
7
  import { resolveBundledAsset } from "../utils/hq-roots-core.js";
8
8
  import { buildSpawnPlan } from "../utils/windows-spawn.js";
9
9
  export const REGISTER_PROJECT_ASSET = "core/scripts/register-project.sh";
10
- const USAGE = "usage: register-project.sh <company> <project> | --audit <company> | --backfill <company> --only-active [--yes] | --retry-pending <company>";
10
+ const USAGE = "usage: register-project.sh <company> <project> | --brainstorm <company> <board-project-id> | --audit <company> | --backfill <company> --only-active [--yes] | --retry-pending <company>";
11
11
  const MIN_HQ_CLI = "5.139.0";
12
12
  export function createRegisterProjectHqRunner(spawn = spawnSync) {
13
13
  return (args, options) => {
@@ -117,8 +117,20 @@ function projectRows(board) {
117
117
  return record;
118
118
  });
119
119
  }
120
- function matchesProject(row, id, rel) {
121
- return row.id === id || row.prd_path === rel || row.mesh_project_id === id;
120
+ function matchesProject(row, id, rel, meshProjectId = id) {
121
+ return row.id === id || row.prd_path === rel || row.mesh_project_id === id || row.id === meshProjectId || row.mesh_project_id === meshProjectId;
122
+ }
123
+ function meshProjectIdForPrd(company, project, deps) {
124
+ const rel = `companies/${company}/projects/${project}/prd.json`;
125
+ try {
126
+ const row = projectRows(boardData(company, boardPath(company, deps), deps)).find((item) => item.prd_path === rel || item.id === project);
127
+ const value = jqString(jqOr(asRecord(row)?.mesh_project_id, jqOr(asRecord(row)?.id, "")));
128
+ return value || project;
129
+ }
130
+ catch {
131
+ // The shell suppresses jq's board lookup error and falls back to the local id.
132
+ return project;
133
+ }
122
134
  }
123
135
  function atomicJqWrite(target, value, deps, initialize = false) {
124
136
  deps.fs.mkdirSync(path.dirname(target), { recursive: true });
@@ -167,12 +179,13 @@ function ensureOne(company, project, deps) {
167
179
  catch { /* legacy jq/write errors remain a failed registration */ }
168
180
  return errorResult(`hq-cli ${MIN_HQ_CLI} or newer is required; ${company}/${project} stays local until then`);
169
181
  }
182
+ const meshProject = meshProjectIdForPrd(company, project, deps);
170
183
  // The shell builds this argument from its raw HQ_ROOT plus POSIX separators.
171
184
  // On Git Bash that preserves a Win32 drive prefix and appends `/companies/...`.
172
185
  const hqPrd = process.platform === "win32"
173
186
  ? `${deps.root.replace(/[\\/]+$/u, "")}/companies/${company}/projects/${project}/prd.json`
174
187
  : prd;
175
- const ensure = deps.runHq(["mesh", "project", "ensure", project, "--company", company, "--stories-file", hqPrd, "--json"], {
188
+ const ensure = deps.runHq(["mesh", "project", "ensure", meshProject, "--company", company, "--stories-file", hqPrd, "--json"], {
176
189
  cwd: deps.cwd, env: deps.env, capture: true,
177
190
  });
178
191
  if (ensure.code !== 0)
@@ -204,9 +217,9 @@ function ensureOne(company, project, deps) {
204
217
  const rows = projectRows(document);
205
218
  if (rows.some((row) => matchesProject(row, project, rel))) {
206
219
  document.projects = rows.map((row) => {
207
- if (!matchesProject(row, project, rel))
220
+ if (!matchesProject(row, project, rel, meshProject))
208
221
  return row;
209
- const updated = { ...row, threadId: thread, channelId: channel, updated_at: now, prd_path: jqOr(row.prd_path, rel), mesh_project_id: project };
222
+ const updated = { ...row, threadId: thread, channelId: channel, updated_at: now, prd_path: jqOr(row.prd_path, rel), mesh_project_id: meshProject };
210
223
  delete updated.pending_registration;
211
224
  return updated;
212
225
  });
@@ -224,11 +237,62 @@ function ensureOne(company, project, deps) {
224
237
  return errorResult("registration incomplete: board.json write failed");
225
238
  }
226
239
  const written = boardData(company, board, deps);
227
- const verified = projectRows(written).some((row) => matchesProject(row, project, rel) && row.threadId === thread && row.channelId === channel);
240
+ const verified = projectRows(written).some((row) => matchesProject(row, project, rel, meshProject) && row.mesh_project_id === meshProject && row.threadId === thread && row.channelId === channel);
228
241
  if (!verified)
229
242
  return errorResult(`registration incomplete: board.json entry for ${project} did not verify`);
230
243
  return { code: 0, stdout: `registered ${company}/${project} thread=${thread} channel=${channel}\n`, stderr: "" };
231
244
  }
245
+ function registerBrainstorm(company, project, deps) {
246
+ if (!companyUid(company, deps))
247
+ return errorResult(`no company uid for ${company} (set HQ_COMPANY_UID, companies/${company}/.company-uid, or manifest cloud_uid)`);
248
+ const board = boardPath(company, deps);
249
+ if (!deps.fs.existsSync(board))
250
+ return errorResult(`registration incomplete: missing ${board}`);
251
+ const document = boardData(company, board, deps);
252
+ const row = projectRows(document).find((item) => item.id === project || item.mesh_project_id === project);
253
+ const title = jqString(jqOr(asRecord(row)?.title, ""));
254
+ const description = jqString(jqOr(asRecord(row)?.description, ""));
255
+ if (!title)
256
+ return errorResult(`registration incomplete: missing local Board project ${company}/${project}`);
257
+ const set = deps.runHq(["mesh", "project", "set", project, "--company", company, "--name", title, "--description", description, "--create", "--json"], {
258
+ cwd: deps.cwd, env: deps.env, capture: true,
259
+ });
260
+ if (set.code !== 0)
261
+ return errorResult(`registration incomplete: hq mesh project set failed for ${company}/${project}`);
262
+ let result;
263
+ try {
264
+ result = asRecord(JSON.parse(set.stdout));
265
+ }
266
+ catch {
267
+ return errorResult("registration incomplete: project set output missing threadId");
268
+ }
269
+ const registration = asRecord(result?.registration);
270
+ const thread = jqString(jqOr(registration?.threadId, jqOr(result?.threadId, "")));
271
+ if (!thread)
272
+ return errorResult("registration incomplete: project set output missing threadId");
273
+ const channel = jqString(jqOr(registration?.channelId, jqOr(result?.channelId, "")));
274
+ if (!channel)
275
+ return errorResult("registration incomplete: project set output missing channelId");
276
+ const now = timestamp(deps);
277
+ const rows = projectRows(document);
278
+ document.projects = rows.map((item) => item.id === project || item.mesh_project_id === project
279
+ ? { ...item, threadId: thread, channelId: channel, mesh_project_id: project, mesh_registration_prompt_state: "accepted", updated_at: now }
280
+ : item);
281
+ for (const updated of document.projects) {
282
+ if (updated.id === project || updated.mesh_project_id === project)
283
+ delete updated.pending_registration;
284
+ }
285
+ try {
286
+ atomicJqWrite(board, document, deps);
287
+ }
288
+ catch {
289
+ return errorResult("registration incomplete: local Board registration did not verify");
290
+ }
291
+ const verified = projectRows(boardData(company, board, deps)).some((item) => (item.id === project || item.mesh_project_id === project) && item.threadId === thread && item.channelId === channel);
292
+ if (!verified)
293
+ return errorResult("registration incomplete: local Board registration did not verify");
294
+ return { code: 0, stdout: `registered ${company}/${project} thread=${thread} channel=${channel}\n`, stderr: "" };
295
+ }
232
296
  function audit(company, deps) {
233
297
  if (!company)
234
298
  return errorResult("usage: register-project.sh --audit <company>");
@@ -253,7 +317,10 @@ function audit(company, deps) {
253
317
  stderr += `no company uid for ${company} (set HQ_COMPANY_UID, companies/${company}/.company-uid, or manifest cloud_uid)\n`;
254
318
  return true;
255
319
  }
256
- return !deps.fs.existsSync(path.join(deps.env.HOME || os.homedir(), ".hq/work-mesh/cache/projects", uid, `${project}.json`));
320
+ const cache = path.join(deps.env.HOME || os.homedir(), ".hq/work-mesh/cache/projects", uid);
321
+ const meshProject = meshProjectIdForPrd(company, project, deps);
322
+ return !deps.fs.existsSync(path.join(cache, `${meshProject}.json`))
323
+ && !deps.fs.existsSync(path.join(cache, `${project}.json`));
257
324
  }
258
325
  catch {
259
326
  return false;
@@ -360,6 +427,11 @@ function retryPending(company, deps) {
360
427
  export function runRegisterProject(args, deps) {
361
428
  try {
362
429
  const [mode, arg1, ...rest] = args;
430
+ if (mode === "--brainstorm") {
431
+ if (args.length !== 3)
432
+ return errorResult("usage: register-project.sh --brainstorm <company> <board-project-id>");
433
+ return registerBrainstorm(arg1 ?? "", rest[0] ?? "", deps);
434
+ }
363
435
  if (mode === "--audit")
364
436
  return audit(arg1 ?? "", deps);
365
437
  if (mode === "--backfill")
@@ -63,7 +63,11 @@ import { tokenUsageReport } from "../lib/core-utils/token-usage-report.js";
63
63
  import { createWorktree } from "../lib/core-utils/worktree.js";
64
64
  import { runCodexSkillBridgeCommand } from "../lib/core-utils/codex-skill-bridge-entry.js";
65
65
  import { archiveOldThreads } from "../lib/core-utils/archive-old-threads.js";
66
- import { qmdReindexAfterSync } from "../lib/core-utils/qmd-reindex-after-sync.js";
66
+ import { QMD_REINDEX_STARTUP_WARNING_FLAG, qmdReindexAfterSync } from "../lib/core-utils/qmd-reindex-after-sync.js";
67
+ import { qmdReindexSingleFlightEnabled } from "../lib/search-index/reindex-single-flight-flag.js";
68
+ import { qmdCollectionPathRepairEnabled } from "../lib/search-index/collection-path-repair-flag.js";
69
+ import { reconcileCollections } from "../lib/search-index/index.js";
70
+ import { resolveProcessFlagGate } from "../lib/flag-registry.js";
67
71
  import { softTimeoutCommand } from "../lib/core-utils/soft-timeout.js";
68
72
  import { timeoutGuardCommand } from "../lib/core-utils/timeout-guard.js";
69
73
  import { readSentryReportStdin, reportSentryEvent } from "../lib/core-utils/sentry-report.js";
@@ -87,7 +91,18 @@ export const NATIVE_UTILITY_COMMANDS = [
87
91
  { name: "worktree", asset: "core/scripts/worktree.sh", root: "live", summary: "Create a git worktree under workspace/worktrees/", run: (args, context) => createWorktree(args, { cwd: context.cwd, hqRoot: context.hqRoot }) },
88
92
  { name: "codex-skill-bridge", asset: "core/scripts/codex-skill-bridge.sh", root: "live", summary: "Install/inspect the Codex skill bridge", run: (args, context) => runCodexSkillBridgeCommand(args, { root: context.hqRoot ?? context.cwd }) },
89
93
  { name: "archive-old-threads", asset: "core/scripts/archive-old-threads.sh", root: "live", summary: "Archive aged session-thread files", run: (args, context) => archiveOldThreads(args, { hqRoot: context.hqRoot ?? context.cwd }) },
90
- { name: "qmd-reindex-after-sync", asset: "core/scripts/qmd-reindex-after-sync.sh", root: "cwd", summary: "Refresh qmd collections after a sync", run: (args, context) => qmdReindexAfterSync(args, { cwd: context.cwd }) },
94
+ {
95
+ name: "qmd-reindex-after-sync",
96
+ asset: "core/scripts/qmd-reindex-after-sync.sh",
97
+ root: "cwd",
98
+ summary: "Refresh qmd collections after a sync",
99
+ run: (args, context) => qmdReindexAfterSync(args, {
100
+ cwd: context.cwd,
101
+ singleFlightEnabled: qmdReindexSingleFlightEnabled,
102
+ reconcile: (root, options) => reconcileCollections(root, { ...options, repairPathDrift: qmdCollectionPathRepairEnabled() }),
103
+ startupFailureWarningsEnabled: () => resolveProcessFlagGate(QMD_REINDEX_STARTUP_WARNING_FLAG, { fallback: false }, () => false),
104
+ }),
105
+ },
91
106
  ];
92
107
  export const NATIVE_SCAFFOLD_COMMANDS = [
93
108
  {
@@ -5,13 +5,14 @@ import { defaultEmbedLockDependencies, runWithEmbedLock, } from '../lib/search-i
5
5
  import { checkEmbedLoad, defaultLoadGateDependencies, recordSuccessfulEmbed, } from '../lib/search-index/load-gate.js';
6
6
  import { applyIndexSizeLimit } from '../lib/search-index/max-doc-bytes.js';
7
7
  import { qmdReindexSingleFlightEnabled } from '../lib/search-index/reindex-single-flight-flag.js';
8
+ import { qmdCollectionPathRepairEnabled } from '../lib/search-index/collection-path-repair-flag.js';
8
9
  import { findHqRoot } from '../utils/manifest.js';
9
10
  import { QMD_NATIVE_BINDING_REMEDY, isQmdNativeBindingError, } from '../utils/qmd-native-binding-error.js';
10
11
  import { isQmdStoreMissingError, qmdStoreMissingMessage, } from '../utils/qmd-store-missing-error.js';
11
12
  import { isQmdStoreUnopenableError, qmdStoreUnopenableMessage, } from '../utils/qmd-store-unopenable-error.js';
12
13
  import { isQmdWorkdirMissingError, qmdWorkdirMissingMessage, } from '../utils/qmd-workdir-missing-error.js';
13
14
  const defaults = {
14
- reconcileCollections,
15
+ reconcileCollections: (root) => reconcileCollections(root, { repairPathDrift: qmdCollectionPathRepairEnabled() }),
15
16
  applyIndexSizeLimit,
16
17
  deriveCollections,
17
18
  listRegisteredCollections,
@@ -143,6 +143,25 @@ export const FAST_SCAFFOLD_SCRIPTS = [
143
143
  shellCwd: "caller",
144
144
  shellHqRoot: "none",
145
145
  },
146
+ {
147
+ name: "ontology-readme-drift",
148
+ kind: "native",
149
+ modulePath: "./commands/core-ontology-readme-drift-fast.js",
150
+ exportName: "runOntologyReadmeDriftFast",
151
+ flagKey: "hq-core-native-fast-route",
152
+ asset: "core/scripts/ontology-readme-drift.sh",
153
+ root: "live",
154
+ },
155
+ {
156
+ name: "qmd-reindex-after-sync",
157
+ kind: "native",
158
+ modulePath: "./commands/core-qmd-reindex-after-sync-fast.js",
159
+ exportName: "runQmdReindexAfterSyncFast",
160
+ flagKey: "hq-core-native-fast-route",
161
+ asset: "core/scripts/qmd-reindex-after-sync.sh",
162
+ root: "cwd",
163
+ requiresNodeNetworkCompatibility: true,
164
+ },
146
165
  {
147
166
  name: "rebuild-index",
148
167
  kind: "native",
@@ -1,6 +1,7 @@
1
1
  import { type EmbedLockDependencies } from "../search-index/embed-lock.js";
2
2
  import { type LoadGateDependencies } from "../search-index/load-gate.js";
3
3
  import { type UtilityIo } from "./common.js";
4
+ export declare const QMD_REINDEX_STARTUP_WARNING_FLAG = "qmd.reindex-startup-warning";
4
5
  export type QmdReindexOptions = UtilityIo & {
5
6
  cwd?: string;
6
7
  /** Test seams; default to the real search-index implementations. */
@@ -16,15 +16,9 @@ import { reconcileCollections, resolveQmdBin, resolveQmdHome, runQmd } from "../
16
16
  import { applyIndexSizeLimit } from "../search-index/max-doc-bytes.js";
17
17
  import { defaultEmbedLockDependencies, runWithEmbedLock, } from "../search-index/embed-lock.js";
18
18
  import { LoadGateConfigurationError, checkEmbedLoad, defaultLoadGateDependencies, recordSuccessfulEmbed, } from "../search-index/load-gate.js";
19
- import { resolveProcessFlagGate } from "../flag-registry.js";
20
- import { qmdReindexSingleFlightEnabled } from "../search-index/reindex-single-flight-flag.js";
21
19
  import { isQmdNativeBindingError, QMD_NATIVE_BINDING_REMEDY, } from "../../utils/qmd-native-binding-error.js";
22
20
  import { ioFor, line } from "./common.js";
23
- const QMD_REINDEX_STARTUP_WARNING_FLAG = "qmd.reindex-startup-warning";
24
- const QMD_REINDEX_STARTUP_WARNING_FLAG_FALLBACK = false;
25
- function qmdStartupFailureWarningsEnabled() {
26
- return resolveProcessFlagGate(QMD_REINDEX_STARTUP_WARNING_FLAG, { fallback: QMD_REINDEX_STARTUP_WARNING_FLAG_FALLBACK }, () => QMD_REINDEX_STARTUP_WARNING_FLAG_FALLBACK);
27
- }
21
+ export const QMD_REINDEX_STARTUP_WARNING_FLAG = "qmd.reindex-startup-warning";
28
22
  export function qmdReindexAfterSync(args = [], options = {}) {
29
23
  let hqRoot = "";
30
24
  let embed = false;
@@ -32,7 +26,7 @@ export function qmdReindexAfterSync(args = [], options = {}) {
32
26
  if (arg === "--embed")
33
27
  embed = true;
34
28
  else if (arg.startsWith("--"))
35
- continue; // unknown flags are ignored, as in the shell
29
+ continue; // long options are ignored, matching the shell oracle
36
30
  else if (!hqRoot)
37
31
  hqRoot = arg;
38
32
  }
@@ -55,7 +49,7 @@ export function qmdReindexAfterSync(args = [], options = {}) {
55
49
  const warnOnNativeStartupFailure = (error) => {
56
50
  if (startupFailureWarningShown ||
57
51
  !isQmdNativeBindingError(error) ||
58
- !(options.startupFailureWarningsEnabled ?? qmdStartupFailureWarningsEnabled)())
52
+ !(options.startupFailureWarningsEnabled ?? (() => false))())
59
53
  return;
60
54
  startupFailureWarningShown = true;
61
55
  line(stderr, `hq: search index was not updated after sync; ${QMD_NATIVE_BINDING_REMEDY}`);
@@ -99,7 +93,7 @@ export function qmdReindexAfterSync(args = [], options = {}) {
99
93
  recordSuccessfulEmbed(home, loadGateDependencies);
100
94
  return undefined;
101
95
  };
102
- if ((options.singleFlightEnabled ?? qmdReindexSingleFlightEnabled)()) {
96
+ if ((options.singleFlightEnabled ?? (() => false))()) {
103
97
  const lockDependencies = options.embedLockDependencies ?? defaultEmbedLockDependencies();
104
98
  const home = resolveQmdHome(lockDependencies.env);
105
99
  try {
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import { Sentry } from '../../sentry.js';
5
5
  import { reconcileCollections as defaultReconcileCollections, resolveQmdBin as defaultResolveQmdBin, runQmd as defaultRunQmd, } from './index.js';
6
+ import { qmdCollectionPathRepairEnabled } from './collection-path-repair-flag.js';
6
7
  import { applyIndexSizeLimit as defaultApplyIndexSizeLimit } from './max-doc-bytes.js';
7
8
  import { acquireEmbedLock as acquireLock, installEmbedLockCleanup, lockPath, lockRoot, lockState, releaseEmbedLock as releaseLock, } from './embed-lock.js';
8
9
  import { LoadGateConfigurationError, checkEmbedLoad, defaultLoadGateDependencies, recordSuccessfulEmbed, } from './load-gate.js';
@@ -121,7 +122,7 @@ export function defaultBackgroundDependencies(hqRoot) {
121
122
  random: () => Math.random().toString(36).slice(2),
122
123
  isProcessAlive: alive,
123
124
  resolveQmdBin: defaultResolveQmdBin,
124
- reconcileCollections: defaultReconcileCollections,
125
+ reconcileCollections: (root) => defaultReconcileCollections(root, { repairPathDrift: qmdCollectionPathRepairEnabled() }),
125
126
  applyIndexSizeLimit: defaultApplyIndexSizeLimit,
126
127
  runQmd: defaultRunQmd,
127
128
  spawnWorker: defaultSpawnWorker,
@@ -0,0 +1,2 @@
1
+ export declare const QMD_COLLECTION_PATH_REPAIR_FLAG = "qmd.collection-path-repair";
2
+ //# sourceMappingURL=collection-path-repair-flag-constants.d.ts.map
@@ -0,0 +1,2 @@
1
+ export const QMD_COLLECTION_PATH_REPAIR_FLAG = 'qmd.collection-path-repair';
2
+ //# sourceMappingURL=collection-path-repair-flag-constants.js.map
@@ -1,4 +1,4 @@
1
- export declare const QMD_COLLECTION_PATH_REPAIR_FLAG = "qmd.collection-path-repair";
1
+ export { QMD_COLLECTION_PATH_REPAIR_FLAG } from './collection-path-repair-flag-constants.js';
2
2
  /** Keep collection-path repair off unless the hq-flags registry enables a canary. */
3
3
  export declare function qmdCollectionPathRepairEnabled(): boolean;
4
4
  //# sourceMappingURL=collection-path-repair-flag.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import { resolveProcessFlagGate } from '../flag-registry.js';
2
- export const QMD_COLLECTION_PATH_REPAIR_FLAG = 'qmd.collection-path-repair';
2
+ import { QMD_COLLECTION_PATH_REPAIR_FLAG } from './collection-path-repair-flag-constants.js';
3
+ export { QMD_COLLECTION_PATH_REPAIR_FLAG } from './collection-path-repair-flag-constants.js';
3
4
  /** Keep collection-path repair off unless the hq-flags registry enables a canary. */
4
5
  export function qmdCollectionPathRepairEnabled() {
5
6
  return resolveProcessFlagGate(QMD_COLLECTION_PATH_REPAIR_FLAG, { fallback: false }, () => false);
@@ -14,7 +14,6 @@ import { markExpectedUserError } from '../../utils/expected-cli-error.js';
14
14
  import { redactErrorText } from '../../utils/redact-error-text.js';
15
15
  import { planCommandSpawn } from '../../utils/windows-spawn.js';
16
16
  import { resolveQmdConfigPath } from './max-doc-bytes.js';
17
- import { qmdCollectionPathRepairEnabled } from './collection-path-repair-flag.js';
18
17
  /** The `path` implementation for a (possibly injected) platform. */
19
18
  function pathFor(platform) {
20
19
  return platform === 'win32' ? path.win32 : path.posix;
@@ -1472,7 +1471,7 @@ export function reconcileCollections(hqRoot, options = {}) {
1472
1471
  const runOptions = { bin: options.bin, runner: options.runner, cwd: hqRoot };
1473
1472
  const registered = listRegisteredCollections(hqRoot, runOptions);
1474
1473
  const expected = deriveCollections(hqRoot);
1475
- const repairPathDrift = options.repairPathDrift ?? qmdCollectionPathRepairEnabled();
1474
+ const repairPathDrift = options.repairPathDrift ?? false;
1476
1475
  const configured = repairPathDrift ? configuredCollections(hqRoot) : new Map();
1477
1476
  const stale = repairPathDrift
1478
1477
  ? expected.filter((collection) => {
@@ -0,0 +1,3 @@
1
+ export declare const QMD_REINDEX_SINGLE_FLIGHT_FLAG = "qmd.reindex-single-flight";
2
+ export declare const QMD_REINDEX_SINGLE_FLIGHT_FLAG_FALLBACK = false;
3
+ //# sourceMappingURL=reindex-single-flight-flag-constants.d.ts.map
@@ -0,0 +1,3 @@
1
+ export const QMD_REINDEX_SINGLE_FLIGHT_FLAG = "qmd.reindex-single-flight";
2
+ export const QMD_REINDEX_SINGLE_FLIGHT_FLAG_FALLBACK = false;
3
+ //# sourceMappingURL=reindex-single-flight-flag-constants.js.map
@@ -1,5 +1,4 @@
1
- export declare const QMD_REINDEX_SINGLE_FLIGHT_FLAG = "qmd.reindex-single-flight";
2
- export declare const QMD_REINDEX_SINGLE_FLIGHT_FLAG_FALLBACK = false;
1
+ export { QMD_REINDEX_SINGLE_FLIGHT_FLAG, QMD_REINDEX_SINGLE_FLIGHT_FLAG_FALLBACK } from "./reindex-single-flight-flag-constants.js";
3
2
  /** Keep whole update-to-embed passes single-flight only for explicitly enabled canaries. */
4
3
  export declare function qmdReindexSingleFlightEnabled(): boolean;
5
4
  //# sourceMappingURL=reindex-single-flight-flag.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { resolveProcessFlagGate } from "../flag-registry.js";
2
- export const QMD_REINDEX_SINGLE_FLIGHT_FLAG = "qmd.reindex-single-flight";
3
- export const QMD_REINDEX_SINGLE_FLIGHT_FLAG_FALLBACK = false;
2
+ import { QMD_REINDEX_SINGLE_FLIGHT_FLAG, QMD_REINDEX_SINGLE_FLIGHT_FLAG_FALLBACK } from "./reindex-single-flight-flag-constants.js";
3
+ export { QMD_REINDEX_SINGLE_FLIGHT_FLAG, QMD_REINDEX_SINGLE_FLIGHT_FLAG_FALLBACK } from "./reindex-single-flight-flag-constants.js";
4
4
  /** Keep whole update-to-embed passes single-flight only for explicitly enabled canaries. */
5
5
  export function qmdReindexSingleFlightEnabled() {
6
6
  return resolveProcessFlagGate(QMD_REINDEX_SINGLE_FLIGHT_FLAG, { fallback: QMD_REINDEX_SINGLE_FLIGHT_FLAG_FALLBACK }, () => QMD_REINDEX_SINGLE_FLIGHT_FLAG_FALLBACK);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.24",
3
+ "version": "5.345.25",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {