@indigoai-us/hq-cli 5.345.23 → 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 +8 -0
- package/assets/scaffold/core/scripts/ontology-garden.mjs +6 -0
- package/assets/scaffold/core/scripts/ontology-source-worklist.sh +12 -2
- package/dist/commands/core-delegate.d.ts +1 -0
- package/dist/commands/core-delegate.js +2 -2
- package/dist/commands/core-ontology-native.js +173 -21
- package/dist/commands/core-ontology-readme-drift-fast.d.ts +2 -0
- package/dist/commands/core-ontology-readme-drift-fast.js +5 -0
- package/dist/commands/core-qmd-reindex-after-sync-fast.d.ts +3 -0
- package/dist/commands/core-qmd-reindex-after-sync-fast.js +26 -0
- package/dist/commands/core-register-project-native.js +80 -8
- package/dist/commands/core-share-suggestion-state-native.js +19 -2
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +21 -1
- package/dist/commands/core-work-mesh-bind-trusted-native.js +79 -7
- package/dist/commands/core.js +17 -2
- package/dist/commands/index-cmd.js +2 -1
- package/dist/fast-scaffold.js +19 -0
- package/dist/lib/core-utils/qmd-reindex-after-sync.d.ts +1 -0
- package/dist/lib/core-utils/qmd-reindex-after-sync.js +4 -10
- package/dist/lib/search-index/background.js +2 -1
- package/dist/lib/search-index/collection-path-repair-flag-constants.d.ts +2 -0
- package/dist/lib/search-index/collection-path-repair-flag-constants.js +2 -0
- package/dist/lib/search-index/collection-path-repair-flag.d.ts +1 -1
- package/dist/lib/search-index/collection-path-repair-flag.js +2 -1
- package/dist/lib/search-index/index.js +1 -2
- package/dist/lib/search-index/reindex-single-flight-flag-constants.d.ts +3 -0
- package/dist/lib/search-index/reindex-single-flight-flag-constants.js +3 -0
- package/dist/lib/search-index/reindex-single-flight-flag.d.ts +1 -2
- package/dist/lib/search-index/reindex-single-flight-flag.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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
|
+
|
|
10
|
+
## [5.345.24] — 2026-10-06
|
|
11
|
+
- Keep native core ontology, delegate-secret, share-suggestion-state, and trusted-bind behavior aligned with their scaffold scripts.
|
|
12
|
+
|
|
5
13
|
## [5.345.23] — 2026-10-06
|
|
6
14
|
- Route eight additional isolated native core commands through the fast CLI entry.
|
|
7
15
|
- 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");
|
|
@@ -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
|
-
|
|
207
|
-
|
|
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
|
|
@@ -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-
|
|
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
|
-
|
|
567
|
-
|
|
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
|
|
602
|
-
|
|
603
|
-
|
|
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 = "";
|
|
@@ -763,6 +831,77 @@ function itemList(file, field) {
|
|
|
763
831
|
}
|
|
764
832
|
return output;
|
|
765
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
|
+
}
|
|
766
905
|
export function worklistCommand(args, root) {
|
|
767
906
|
let company = "";
|
|
768
907
|
let channel = "";
|
|
@@ -944,22 +1083,33 @@ export function worklistCommand(args, root) {
|
|
|
944
1083
|
audience = itemList(file, "audience").join(",");
|
|
945
1084
|
if (!audience)
|
|
946
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(",");
|
|
947
1088
|
if (!audience) {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
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
|
+
}
|
|
963
1113
|
}
|
|
964
1114
|
}
|
|
965
1115
|
}
|
|
@@ -972,7 +1122,9 @@ export function worklistCommand(args, root) {
|
|
|
972
1122
|
const key = shellSubstitution(audienceKey(audience).stdout);
|
|
973
1123
|
output.push(`${JSON.stringify({ file: relative, audience_key: key, audience: audience.split(",") })}\n`);
|
|
974
1124
|
}
|
|
975
|
-
|
|
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);
|
|
976
1128
|
}
|
|
977
1129
|
export function registerCoreOntologyNativeCommands(ontology, rootFor) {
|
|
978
1130
|
const entries = [
|
|
@@ -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",
|
|
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:
|
|
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
|
-
|
|
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")
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
29
|
-
|
|
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) =>
|
|
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/dist/commands/core.js
CHANGED
|
@@ -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
|
-
{
|
|
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,
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -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; //
|
|
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 ??
|
|
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 ??
|
|
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,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
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
|
-
|
|
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 ??
|
|
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) => {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
export
|
|
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
|
-
|
|
3
|
-
export
|
|
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);
|