@indigoai-us/hq-cli 5.345.24 → 5.345.26
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 +12 -0
- package/assets/scaffold/core/scripts/ontology-source-worklist.sh +12 -2
- package/dist/command-catalog.generated.d.ts +50 -0
- package/dist/command-catalog.generated.js +62 -0
- package/dist/commands/core-ontology-native.js +100 -16
- 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.js +17 -2
- package/dist/commands/index-cmd.js +2 -1
- package/dist/commands/install-global.js +19 -4
- package/dist/commands/integrations-core.d.ts +4 -0
- package/dist/commands/integrations-core.js +15 -3
- package/dist/commands/lanes.js +180 -28
- 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/lanes/grants.d.ts +0 -1
- package/dist/lib/lanes/grants.js +3 -6
- package/dist/lib/lanes/index.d.ts +2 -2
- package/dist/lib/lanes/index.js +2 -2
- package/dist/lib/lanes/keep-alive-audit.d.ts +5 -0
- package/dist/lib/lanes/keep-alive-audit.js +15 -1
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
- package/dist/lib/lanes/outpost-dispatch.js +1 -0
- package/dist/lib/lanes/store.js +33 -0
- package/dist/lib/lanes/story-ledger.d.ts +2 -0
- package/dist/lib/lanes/story-ledger.js +33 -0
- package/dist/lib/lanes/types.d.ts +10 -0
- package/dist/lib/lanes/types.js +1 -1
- 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/dist/main.js +4 -1
- package/dist/utils/install-tree-torn.d.ts +7 -7
- package/dist/utils/install-tree-torn.js +10 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.26] — 2026-10-06
|
|
6
|
+
- Give users recovery guidance for malformed HTTP 200 integration gateway errors while keeping the gateway defect reportable.
|
|
7
|
+
- Fix Claude plugin setup on CLI versions that reject `--json` for marketplace add and remove. Failed Claude plugin commands show a bounded stderr excerpt with quoted and unquoted credential fields redacted.
|
|
8
|
+
- Add `hq lanes repair-company` for legacy lanes with missing company attribution, report company-less lead lanes in `hq lanes keep-alive-audit`, and inherit company attribution from company-bound seniors when creating child lanes.
|
|
9
|
+
- Resolve resume hire-carve policy from the senior record and remove the misleading alive-child helper alias.
|
|
10
|
+
- Keep install-tree recovery waiting through the bounded settle deadline when a retired npm sibling cannot be fully inspected.
|
|
11
|
+
|
|
12
|
+
## [5.345.25] — 2026-10-06
|
|
13
|
+
- Route qmd post-sync indexing and ontology README drift through isolated fast handlers; cover all rebuild-index targets in fast-route parity and benchmarks.
|
|
14
|
+
|
|
15
|
+
- Keep native register-project and ontology source-worklist commands aligned with staging behavior for Board identifiers, DM participant audiences, and transient ACL retries.
|
|
16
|
+
|
|
5
17
|
## [5.345.24] — 2026-10-06
|
|
6
18
|
- Keep native core ontology, delegate-secret, share-suggestion-state, and trusted-bind behavior aligned with their scaffold scripts.
|
|
7
19
|
|
|
@@ -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
|
|
@@ -11395,6 +11395,31 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
11395
11395
|
readonly hidden: false;
|
|
11396
11396
|
}];
|
|
11397
11397
|
readonly subcommands: readonly [];
|
|
11398
|
+
}, {
|
|
11399
|
+
readonly name: "repair-company-target";
|
|
11400
|
+
readonly description: "";
|
|
11401
|
+
readonly aliases: readonly [];
|
|
11402
|
+
readonly hidden: true;
|
|
11403
|
+
readonly helpOption: true;
|
|
11404
|
+
readonly helpCommand: false;
|
|
11405
|
+
readonly usage: "[options] <lane-id>";
|
|
11406
|
+
readonly helpText: readonly [];
|
|
11407
|
+
readonly arguments: readonly [{
|
|
11408
|
+
readonly name: "lane-id";
|
|
11409
|
+
readonly required: true;
|
|
11410
|
+
readonly variadic: false;
|
|
11411
|
+
readonly description: "Lane id";
|
|
11412
|
+
}];
|
|
11413
|
+
readonly options: readonly [{
|
|
11414
|
+
readonly flags: "--company <slug>";
|
|
11415
|
+
readonly description: "Company slug to assign";
|
|
11416
|
+
readonly hidden: false;
|
|
11417
|
+
}, {
|
|
11418
|
+
readonly flags: "--json";
|
|
11419
|
+
readonly description: "JSON on stdout (always)";
|
|
11420
|
+
readonly hidden: false;
|
|
11421
|
+
}];
|
|
11422
|
+
readonly subcommands: readonly [];
|
|
11398
11423
|
}, {
|
|
11399
11424
|
readonly name: "grant-target";
|
|
11400
11425
|
readonly description: "";
|
|
@@ -12328,6 +12353,31 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
12328
12353
|
readonly hidden: false;
|
|
12329
12354
|
}];
|
|
12330
12355
|
readonly subcommands: readonly [];
|
|
12356
|
+
}, {
|
|
12357
|
+
readonly name: "repair-company";
|
|
12358
|
+
readonly description: "Set missing company attribution on a legacy lane";
|
|
12359
|
+
readonly aliases: readonly [];
|
|
12360
|
+
readonly hidden: false;
|
|
12361
|
+
readonly helpOption: true;
|
|
12362
|
+
readonly helpCommand: false;
|
|
12363
|
+
readonly usage: "[options] <lane-id>";
|
|
12364
|
+
readonly helpText: readonly [];
|
|
12365
|
+
readonly arguments: readonly [{
|
|
12366
|
+
readonly name: "lane-id";
|
|
12367
|
+
readonly required: true;
|
|
12368
|
+
readonly variadic: false;
|
|
12369
|
+
readonly description: "Lane id";
|
|
12370
|
+
}];
|
|
12371
|
+
readonly options: readonly [{
|
|
12372
|
+
readonly flags: "--company <slug>";
|
|
12373
|
+
readonly description: "Company slug to assign";
|
|
12374
|
+
readonly hidden: false;
|
|
12375
|
+
}, {
|
|
12376
|
+
readonly flags: "--json";
|
|
12377
|
+
readonly description: "JSON on stdout (always)";
|
|
12378
|
+
readonly hidden: false;
|
|
12379
|
+
}];
|
|
12380
|
+
readonly subcommands: readonly [];
|
|
12331
12381
|
}, {
|
|
12332
12382
|
readonly name: "logs";
|
|
12333
12383
|
readonly description: "Print a lane's local logs";
|
|
@@ -13843,6 +13843,37 @@ export const COMMAND_CATALOG = [
|
|
|
13843
13843
|
],
|
|
13844
13844
|
"subcommands": []
|
|
13845
13845
|
},
|
|
13846
|
+
{
|
|
13847
|
+
"name": "repair-company-target",
|
|
13848
|
+
"description": "",
|
|
13849
|
+
"aliases": [],
|
|
13850
|
+
"hidden": true,
|
|
13851
|
+
"helpOption": true,
|
|
13852
|
+
"helpCommand": false,
|
|
13853
|
+
"usage": "[options] <lane-id>",
|
|
13854
|
+
"helpText": [],
|
|
13855
|
+
"arguments": [
|
|
13856
|
+
{
|
|
13857
|
+
"name": "lane-id",
|
|
13858
|
+
"required": true,
|
|
13859
|
+
"variadic": false,
|
|
13860
|
+
"description": "Lane id"
|
|
13861
|
+
}
|
|
13862
|
+
],
|
|
13863
|
+
"options": [
|
|
13864
|
+
{
|
|
13865
|
+
"flags": "--company <slug>",
|
|
13866
|
+
"description": "Company slug to assign",
|
|
13867
|
+
"hidden": false
|
|
13868
|
+
},
|
|
13869
|
+
{
|
|
13870
|
+
"flags": "--json",
|
|
13871
|
+
"description": "JSON on stdout (always)",
|
|
13872
|
+
"hidden": false
|
|
13873
|
+
}
|
|
13874
|
+
],
|
|
13875
|
+
"subcommands": []
|
|
13876
|
+
},
|
|
13846
13877
|
{
|
|
13847
13878
|
"name": "grant-target",
|
|
13848
13879
|
"description": "",
|
|
@@ -14991,6 +15022,37 @@ export const COMMAND_CATALOG = [
|
|
|
14991
15022
|
],
|
|
14992
15023
|
"subcommands": []
|
|
14993
15024
|
},
|
|
15025
|
+
{
|
|
15026
|
+
"name": "repair-company",
|
|
15027
|
+
"description": "Set missing company attribution on a legacy lane",
|
|
15028
|
+
"aliases": [],
|
|
15029
|
+
"hidden": false,
|
|
15030
|
+
"helpOption": true,
|
|
15031
|
+
"helpCommand": false,
|
|
15032
|
+
"usage": "[options] <lane-id>",
|
|
15033
|
+
"helpText": [],
|
|
15034
|
+
"arguments": [
|
|
15035
|
+
{
|
|
15036
|
+
"name": "lane-id",
|
|
15037
|
+
"required": true,
|
|
15038
|
+
"variadic": false,
|
|
15039
|
+
"description": "Lane id"
|
|
15040
|
+
}
|
|
15041
|
+
],
|
|
15042
|
+
"options": [
|
|
15043
|
+
{
|
|
15044
|
+
"flags": "--company <slug>",
|
|
15045
|
+
"description": "Company slug to assign",
|
|
15046
|
+
"hidden": false
|
|
15047
|
+
},
|
|
15048
|
+
{
|
|
15049
|
+
"flags": "--json",
|
|
15050
|
+
"description": "JSON on stdout (always)",
|
|
15051
|
+
"hidden": false
|
|
15052
|
+
}
|
|
15053
|
+
],
|
|
15054
|
+
"subcommands": []
|
|
15055
|
+
},
|
|
14994
15056
|
{
|
|
14995
15057
|
"name": "logs",
|
|
14996
15058
|
"description": "Print a lane's local logs",
|
|
@@ -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
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
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
|
-
|
|
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,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")
|
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,
|
|
@@ -76,8 +76,23 @@ function runClaudeCli(args, home, env) {
|
|
|
76
76
|
const childEnv = { ...process.env, ...env, HOME: home, CLAUDE_CONFIG_DIR: path.join(home, '.claude') };
|
|
77
77
|
const result = spawnSync('claude', ['plugin', ...args], { encoding: 'utf8', env: childEnv, timeout: 120_000, windowsHide: true });
|
|
78
78
|
if (result.error || result.status !== 0) {
|
|
79
|
+
if (result.error?.code === 'ENOENT') {
|
|
80
|
+
throw Object.assign(new Error('Claude Code plugin command failed because the Claude Code CLI was not found. Ensure the Claude Code CLI is installed and try again.'), { expected: true });
|
|
81
|
+
}
|
|
82
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr : '';
|
|
83
|
+
let excerpt = stderr.replace(/\s+/g, ' ').trim();
|
|
84
|
+
excerpt = excerpt
|
|
85
|
+
.replace(/\bBearer\s+\S+/gi, 'Bearer [redacted]')
|
|
86
|
+
.replace(/(?:"((?:(?:access|refresh|id)[_-]?)?token|secret|password|authorization|api[_-]?key)"|'((?:(?:access|refresh|id)[_-]?)?token|secret|password|authorization|api[_-]?key)'|\b((?:(?:access|refresh|id)[_-]?)?token|secret|password|authorization|api[_-]?key)\b)\s*[:=]\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,}\]]+)/gi, (_match, doubleQuotedKey, singleQuotedKey, unquotedKey) => `${doubleQuotedKey ?? singleQuotedKey ?? unquotedKey}=[redacted]`)
|
|
87
|
+
.replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, '[redacted]')
|
|
88
|
+
.replace(/\b(?:gh[pousr]_|github_pat_|sk-|xox[baprs]-)[A-Za-z0-9_-]{16,}\b/g, '[redacted]');
|
|
89
|
+
for (const value of Object.values(childEnv).filter((value) => typeof value === 'string' && value.length >= 4).sort((a, b) => b.length - a.length)) {
|
|
90
|
+
excerpt = excerpt.split(value).join('[redacted]');
|
|
91
|
+
}
|
|
92
|
+
excerpt = excerpt.slice(0, 240);
|
|
79
93
|
const cause = result.error?.message ?? `exit status ${String(result.status)}`;
|
|
80
|
-
|
|
94
|
+
const detail = excerpt ? `; stderr: ${excerpt}` : '';
|
|
95
|
+
throw Object.assign(new Error(`Claude Code plugin command failed (${cause}${detail}).`), { expected: true });
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
function removePluginSettings(doc, plugin, removePlugin, removeMarketplace, pluginSettingBefore) {
|
|
@@ -374,7 +389,7 @@ export function installGlobalClaude(opts) {
|
|
|
374
389
|
try {
|
|
375
390
|
if (marketplaceNeedsAdd) {
|
|
376
391
|
marketplaceMayNeedRollback = true;
|
|
377
|
-
runClaude(['marketplace', 'add', '--scope', 'user',
|
|
392
|
+
runClaude(['marketplace', 'add', '--scope', 'user', pluginSource.sourceDir], home, env);
|
|
378
393
|
}
|
|
379
394
|
if (pluginNeedsInstall)
|
|
380
395
|
runClaude(['install', '--scope', 'user', '--json', '-y', pluginSource.pluginId], home, env);
|
|
@@ -391,7 +406,7 @@ export function installGlobalClaude(opts) {
|
|
|
391
406
|
}
|
|
392
407
|
if (marketplaceMayNeedRollback) {
|
|
393
408
|
try {
|
|
394
|
-
runClaude(['marketplace', 'remove', '--scope', 'user',
|
|
409
|
+
runClaude(['marketplace', 'remove', '--scope', 'user', pluginSource.marketplaceName], home, env);
|
|
395
410
|
}
|
|
396
411
|
catch (rollbackError) {
|
|
397
412
|
rollbackErrors.push(`marketplace removal: ${rollbackError.message}`);
|
|
@@ -574,7 +589,7 @@ export function uninstallGlobalClaude(opts = {}) {
|
|
|
574
589
|
runClaude(['uninstall', '--scope', 'user', '--json', pluginSource.pluginId], home, env);
|
|
575
590
|
}
|
|
576
591
|
if (pluginSource && state?.plugin?.marketplaceAdded) {
|
|
577
|
-
runClaude(['marketplace', 'remove', '--scope', 'user',
|
|
592
|
+
runClaude(['marketplace', 'remove', '--scope', 'user', pluginSource.marketplaceName], home, env);
|
|
578
593
|
}
|
|
579
594
|
unregisterClaudeServer({ name: HQ_MCP_SERVER_NAME, pack: HQ_ANYWHERE_PACK, env: { home } });
|
|
580
595
|
if (fs.existsSync(t.settingsJson)) {
|
|
@@ -113,6 +113,7 @@ interface GatewayMessage {
|
|
|
113
113
|
error?: unknown;
|
|
114
114
|
}
|
|
115
115
|
export type GatewayErrorShape = "string_error" | "object_no_code" | "object_code" | "string_code" | "nested_code" | "empty" | "redacted_empty";
|
|
116
|
+
export type GatewayFailureCode = "invalid_envelope";
|
|
116
117
|
export declare class IntegrationsCliError extends Error {
|
|
117
118
|
/**
|
|
118
119
|
* True when the error is the caller's request/state/permission (a client 4xx
|
|
@@ -152,6 +153,8 @@ export declare class IntegrationsCliError extends Error {
|
|
|
152
153
|
readonly gatewaySubcommand?: "tools_list" | "tools_call" | "other";
|
|
153
154
|
/** Fixed vocabulary describing the shape of an upstream JSON-RPC error. */
|
|
154
155
|
readonly gatewayErrorShape?: GatewayErrorShape;
|
|
156
|
+
/** Fixed classification for malformed gateway envelopes. */
|
|
157
|
+
readonly gatewayFailureCode?: GatewayFailureCode;
|
|
155
158
|
/**
|
|
156
159
|
* HQ's own OAuth callback URL, carried on the errors that say a provider
|
|
157
160
|
* refused to register HQ automatically. The provider must register the exact
|
|
@@ -168,6 +171,7 @@ export declare class IntegrationsCliError extends Error {
|
|
|
168
171
|
rpcCode?: number;
|
|
169
172
|
gatewaySubcommand?: "tools_list" | "tools_call" | "other";
|
|
170
173
|
gatewayErrorShape?: GatewayErrorShape;
|
|
174
|
+
gatewayFailureCode?: GatewayFailureCode;
|
|
171
175
|
redirectUri?: string;
|
|
172
176
|
});
|
|
173
177
|
}
|