@indigoai-us/hq-cli 5.315.0 → 5.316.0
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 +9 -0
- package/assets/scaffold/core/scripts/detect-stale-review-base.sh +199 -0
- package/assets/scaffold/core/scripts/repo-run-registry.sh +408 -0
- package/assets/scaffold/core/scripts/share-suggestion-state.sh +541 -0
- package/dist/command-catalog.generated.d.ts +45 -0
- package/dist/command-catalog.generated.js +56 -0
- package/dist/commands/bot-jobs.d.ts +5 -1
- package/dist/commands/bot-jobs.js +24 -4
- package/dist/commands/core-detect-stale-review-base-native.d.ts +23 -0
- package/dist/commands/core-detect-stale-review-base-native.js +178 -0
- package/dist/commands/core-hook-state-helpers-native.d.ts +11 -0
- package/dist/commands/core-hook-state-helpers-native.js +74 -0
- package/dist/commands/core-repo-run-registry-native.d.ts +19 -0
- package/dist/commands/core-repo-run-registry-native.js +357 -0
- package/dist/commands/core-share-suggestion-state-native.d.ts +18 -0
- package/dist/commands/core-share-suggestion-state-native.js +445 -0
- package/dist/commands/core.js +5 -0
- package/dist/commands/lanes.js +35 -5
- package/dist/fast-core.d.ts +1 -0
- package/dist/fast-core.js +10 -6
- package/dist/fast-scaffold.js +17 -0
- package/dist/lib/bot/job-quiet-flag.d.ts +4 -0
- package/dist/lib/bot/job-quiet-flag.js +14 -0
- package/dist/lib/bot/job-run.d.ts +5 -0
- package/dist/lib/bot/job-run.js +23 -3
- package/dist/lib/bot/run.d.ts +2 -0
- package/dist/lib/bot/run.js +15 -3
- package/dist/lib/core-utils/common.d.ts +0 -1
- package/dist/lib/core-utils/common.js +0 -9
- package/dist/lib/lanes/autopilot.d.ts +5 -1
- package/dist/lib/lanes/autopilot.js +72 -3
- package/dist/lib/lanes/disk-guard.d.ts +3 -4
- package/dist/lib/lanes/disk-guard.js +2 -2
- package/dist/lib/lanes/flag-keys.d.ts +3 -0
- package/dist/lib/lanes/flag-keys.js +3 -0
- package/dist/lib/lanes/hooks-install.js +78 -2
- package/dist/lib/lanes/keep-alive-audit.d.ts +6 -5
- package/dist/lib/lanes/outpost-dispatch.d.ts +9 -0
- package/dist/lib/lanes/outpost-dispatch.js +22 -0
- package/package.json +1 -1
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/** Native port of share-suggestion-state.sh; kept dependency-light for hooks. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
export const SHARE_SUGGESTION_STATE_ASSET = "core/scripts/share-suggestion-state.sh";
|
|
5
|
+
const isObj = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
const sortKeys = (value) => Array.isArray(value) ? value.map(sortKeys)
|
|
7
|
+
: (value && typeof value === "object")
|
|
8
|
+
? Object.keys(value).sort().reduce((out, key) => { out[key] = sortKeys(value[key]); return out; }, {})
|
|
9
|
+
: value;
|
|
10
|
+
const sanitizeValue = (value) => typeof value === "string" ? value.trim() : value;
|
|
11
|
+
function stripComments(line) {
|
|
12
|
+
let single = false, double = false;
|
|
13
|
+
const chars = [];
|
|
14
|
+
for (const char of line) {
|
|
15
|
+
if (char === "'" && !double)
|
|
16
|
+
single = !single;
|
|
17
|
+
else if (char === '"' && !single)
|
|
18
|
+
double = !double;
|
|
19
|
+
else if (char === "#" && !single && !double)
|
|
20
|
+
break;
|
|
21
|
+
chars.push(char);
|
|
22
|
+
}
|
|
23
|
+
return chars.join("").replace(/\s+$/, "");
|
|
24
|
+
}
|
|
25
|
+
function parseScalar(raw) {
|
|
26
|
+
const value = raw.trim();
|
|
27
|
+
if (!value)
|
|
28
|
+
return "";
|
|
29
|
+
const lower = value.toLowerCase();
|
|
30
|
+
if (lower === "true")
|
|
31
|
+
return true;
|
|
32
|
+
if (lower === "false")
|
|
33
|
+
return false;
|
|
34
|
+
if (lower === "null" || lower === "~")
|
|
35
|
+
return null;
|
|
36
|
+
if (value === "{}")
|
|
37
|
+
return {};
|
|
38
|
+
if (value === "[]")
|
|
39
|
+
return [];
|
|
40
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
41
|
+
const inner = value.slice(1, -1).trim();
|
|
42
|
+
return inner ? inner.split(",").map((part) => parseScalar(part.trim())) : [];
|
|
43
|
+
}
|
|
44
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
45
|
+
return value.slice(1, -1);
|
|
46
|
+
if (/^-?\d+$/.test(value))
|
|
47
|
+
return Number.parseInt(value, 10);
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
function parseYamlTokens(tokens, index = 0, indent = null) {
|
|
51
|
+
if (index >= tokens.length)
|
|
52
|
+
return [{}, index];
|
|
53
|
+
indent ??= tokens[index][0];
|
|
54
|
+
if (tokens[index][1].startsWith("- ")) {
|
|
55
|
+
const items = [];
|
|
56
|
+
while (index < tokens.length) {
|
|
57
|
+
const [current, content] = tokens[index];
|
|
58
|
+
if (current !== indent || !content.startsWith("- "))
|
|
59
|
+
break;
|
|
60
|
+
const value = content.slice(2).trim();
|
|
61
|
+
if (!value) {
|
|
62
|
+
const [child, next] = parseYamlTokens(tokens, index + 1);
|
|
63
|
+
items.push(child);
|
|
64
|
+
index = next;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
items.push(parseScalar(value));
|
|
68
|
+
index += 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return [items, index];
|
|
72
|
+
}
|
|
73
|
+
const result = {};
|
|
74
|
+
while (index < tokens.length) {
|
|
75
|
+
const [current, content] = tokens[index];
|
|
76
|
+
if (current !== indent || content.startsWith("- "))
|
|
77
|
+
break;
|
|
78
|
+
const sep = content.indexOf(":");
|
|
79
|
+
const key = (sep < 0 ? content : content.slice(0, sep)).trim();
|
|
80
|
+
const remainder = (sep < 0 ? "" : content.slice(sep + 1)).trim();
|
|
81
|
+
if (!remainder) {
|
|
82
|
+
if (index + 1 < tokens.length && tokens[index + 1][0] > current) {
|
|
83
|
+
const [child, next] = parseYamlTokens(tokens, index + 1, tokens[index + 1][0]);
|
|
84
|
+
result[key] = child;
|
|
85
|
+
index = next;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
result[key] = {};
|
|
89
|
+
index += 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
result[key] = parseScalar(remainder);
|
|
94
|
+
index += 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return [result, index];
|
|
98
|
+
}
|
|
99
|
+
function loadYaml(file, fileSystem) {
|
|
100
|
+
if (!file || !fileSystem.existsSync(file))
|
|
101
|
+
return {};
|
|
102
|
+
try {
|
|
103
|
+
const tokens = [];
|
|
104
|
+
for (const raw of fileSystem.readFileSync(file, "utf8").split(/\r?\n/)) {
|
|
105
|
+
const line = stripComments(raw);
|
|
106
|
+
if (!line.trim())
|
|
107
|
+
continue;
|
|
108
|
+
const content = line.replace(/^ +/, "");
|
|
109
|
+
tokens.push([line.length - content.length, content]);
|
|
110
|
+
}
|
|
111
|
+
if (!tokens.length)
|
|
112
|
+
return {};
|
|
113
|
+
const [value] = parseYamlTokens(tokens);
|
|
114
|
+
return isObj(value) ? value : {};
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function lookupNested(data, dotted) {
|
|
121
|
+
let current = data;
|
|
122
|
+
for (const part of dotted.split(".")) {
|
|
123
|
+
if (!isObj(current) || !(part in current))
|
|
124
|
+
return null;
|
|
125
|
+
current = current[part];
|
|
126
|
+
}
|
|
127
|
+
return current;
|
|
128
|
+
}
|
|
129
|
+
function makeContext(deps) {
|
|
130
|
+
const fileSystem = deps.fs ?? fs;
|
|
131
|
+
const env = deps.env ?? process.env;
|
|
132
|
+
const now = deps.now ?? (() => new Date());
|
|
133
|
+
const isoNow = () => {
|
|
134
|
+
const d = now();
|
|
135
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
136
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}Z`;
|
|
137
|
+
};
|
|
138
|
+
return { fileSystem, env, isoNow };
|
|
139
|
+
}
|
|
140
|
+
export function runShareSuggestionState(args, stdin, deps) {
|
|
141
|
+
const cmd = args[0] ?? "";
|
|
142
|
+
const rawSession = args[1] ?? "";
|
|
143
|
+
const argDecision = args[2] ?? "";
|
|
144
|
+
const failFromShellMain = (message) => ({ code: 0, stdout: "", stderr: `${message}share-suggestion-state: internal error\n` });
|
|
145
|
+
if (!cmd)
|
|
146
|
+
return failFromShellMain("share-suggestion-state: missing subcommand\n");
|
|
147
|
+
const root = deps.root;
|
|
148
|
+
if (!root)
|
|
149
|
+
return failFromShellMain("share-suggestion-state: unable to resolve HQ root\n");
|
|
150
|
+
const { fileSystem: f, env, isoNow } = makeContext(deps);
|
|
151
|
+
const stateHome = path.join(root, "workspace/orchestrator/share-suggestions");
|
|
152
|
+
const historyPath = path.join(stateHome, "history.jsonl");
|
|
153
|
+
const suppressionsPath = path.join(stateHome, "suppressions.jsonl");
|
|
154
|
+
try {
|
|
155
|
+
f.mkdirSync(stateHome, { recursive: true });
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const code = error.code;
|
|
159
|
+
let blockedPath = stateHome;
|
|
160
|
+
let current = root;
|
|
161
|
+
for (const piece of path.relative(root, stateHome).split(path.sep)) {
|
|
162
|
+
current = path.join(current, piece);
|
|
163
|
+
try {
|
|
164
|
+
if (!f.statSync(current).isDirectory()) {
|
|
165
|
+
blockedPath = current;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch { /* locate the first existing non-directory parent when present */ }
|
|
170
|
+
}
|
|
171
|
+
const reason = code === "ENOTDIR" ? "Not a directory" : code === "EACCES" || code === "EPERM" ? "Permission denied" : code === "EEXIST" ? "File exists" : (error instanceof Error ? error.message : String(error));
|
|
172
|
+
return failFromShellMain(`mkdir: cannot create directory ‘${blockedPath}’: ${reason}\nshare-suggestion-state: unable to create state directory\n`);
|
|
173
|
+
}
|
|
174
|
+
const sessionBytes = Buffer.from(rawSession, "utf8");
|
|
175
|
+
const sessionId = Array.from(sessionBytes, (byte) => byte === 45 || byte === 95 || (byte >= 48 && byte <= 57) || (byte >= 65 && byte <= 90) || (byte >= 97 && byte <= 122) ? String.fromCharCode(byte) : "_").join("") || "unknown";
|
|
176
|
+
const pendingPath = path.join(stateHome, `${sessionId}.json`);
|
|
177
|
+
// Bash command substitution removes every trailing LF before exporting the payload.
|
|
178
|
+
const payloadRaw = deps.stdinIsTty ? "" : stdin.replace(/\n+$/, "");
|
|
179
|
+
let payload = {};
|
|
180
|
+
if (payloadRaw) {
|
|
181
|
+
try {
|
|
182
|
+
const parsed = JSON.parse(payloadRaw);
|
|
183
|
+
if (isObj(parsed))
|
|
184
|
+
payload = parsed;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
payload = {};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const readJson = (file, fallback) => {
|
|
191
|
+
if (!f.existsSync(file))
|
|
192
|
+
return fallback;
|
|
193
|
+
try {
|
|
194
|
+
return JSON.parse(f.readFileSync(file, "utf8"));
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return fallback;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const writeJson = (file, value) => {
|
|
201
|
+
f.writeFileSync(`${file}.tmp`, `${JSON.stringify(sortKeys(value), null, 2)}\n`);
|
|
202
|
+
f.renameSync(`${file}.tmp`, file);
|
|
203
|
+
};
|
|
204
|
+
const appendJsonl = (file, value) => f.appendFileSync(file, `${JSON.stringify(sortKeys(value))}\n`);
|
|
205
|
+
const uniquePeople = (items) => {
|
|
206
|
+
const seen = new Set(), result = [];
|
|
207
|
+
for (const item of items || []) {
|
|
208
|
+
if (!isObj(item))
|
|
209
|
+
continue;
|
|
210
|
+
const personId = sanitizeValue(item.id || item.slug || ""), name = sanitizeValue(item.name || ""), role = sanitizeValue(item.role || "");
|
|
211
|
+
const cleaned = {};
|
|
212
|
+
if (personId)
|
|
213
|
+
cleaned.id = personId;
|
|
214
|
+
if (name)
|
|
215
|
+
cleaned.name = name;
|
|
216
|
+
if (role)
|
|
217
|
+
cleaned.role = role;
|
|
218
|
+
if (!Object.keys(cleaned).length)
|
|
219
|
+
continue;
|
|
220
|
+
const key = `${cleaned.id || ""}|${cleaned.name || ""}`;
|
|
221
|
+
if (!seen.has(key)) {
|
|
222
|
+
seen.add(key);
|
|
223
|
+
result.push(cleaned);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
};
|
|
228
|
+
const sanitizeSources = (items) => {
|
|
229
|
+
const seen = new Set(), result = [];
|
|
230
|
+
for (const item of items || []) {
|
|
231
|
+
if (typeof item !== "string")
|
|
232
|
+
continue;
|
|
233
|
+
const value = item.trim();
|
|
234
|
+
if (value && !seen.has(value)) {
|
|
235
|
+
seen.add(value);
|
|
236
|
+
result.push(value);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
};
|
|
241
|
+
const sanitizeArtifact = (body) => {
|
|
242
|
+
if (!isObj(body))
|
|
243
|
+
body = {};
|
|
244
|
+
let artifact = body.artifact;
|
|
245
|
+
if (!isObj(artifact))
|
|
246
|
+
artifact = body;
|
|
247
|
+
const cleaned = {};
|
|
248
|
+
for (const key of ["path", "fingerprint", "class", "surface", "permission", "app_id", "label"]) {
|
|
249
|
+
const value = sanitizeValue(artifact[key]);
|
|
250
|
+
if (typeof value === "string" && value)
|
|
251
|
+
cleaned[key] = value;
|
|
252
|
+
}
|
|
253
|
+
if (!("permission" in cleaned))
|
|
254
|
+
cleaned.permission = "read";
|
|
255
|
+
return cleaned;
|
|
256
|
+
};
|
|
257
|
+
const sanitizeCandidateHints = (body) => {
|
|
258
|
+
let hints = body.candidate_hints;
|
|
259
|
+
if (!isObj(hints))
|
|
260
|
+
hints = {};
|
|
261
|
+
const cleaned = {
|
|
262
|
+
sources: sanitizeSources(hints.sources || body.candidate_sources || []),
|
|
263
|
+
local_people: uniquePeople(hints.local_people || body.local_people || []),
|
|
264
|
+
needs_assistant_resolution: Boolean("needs_assistant_resolution" in hints ? hints.needs_assistant_resolution : body.needs_assistant_resolution),
|
|
265
|
+
};
|
|
266
|
+
const projects = sanitizeSources(hints.project_recipient_hints || body.project_recipient_hints || []);
|
|
267
|
+
if (projects.length)
|
|
268
|
+
cleaned.project_recipient_hints = projects;
|
|
269
|
+
return cleaned;
|
|
270
|
+
};
|
|
271
|
+
const sanitizeSuggestion = (body) => {
|
|
272
|
+
const artifact = sanitizeArtifact(body), fingerprint = artifact.fingerprint || "";
|
|
273
|
+
const company = sanitizeValue(body.company || ""), project = sanitizeValue(body.project || "");
|
|
274
|
+
const suggestion = {
|
|
275
|
+
session_id: sessionId, company, project, artifact,
|
|
276
|
+
trigger: sanitizeValue(body.trigger || ""), action_kind: sanitizeValue(body.action_kind || "share-suggestion"),
|
|
277
|
+
suggested_permission: sanitizeValue(body.suggested_permission || artifact.permission || "read"),
|
|
278
|
+
recommended_surface: sanitizeValue(body.recommended_surface || artifact.surface || ""),
|
|
279
|
+
recipients: uniquePeople(body.recipients || []), candidate_hints: sanitizeCandidateHints(body),
|
|
280
|
+
created_at: body.created_at || isoNow(), updated_at: isoNow(), shown_at: body.shown_at === undefined ? null : body.shown_at,
|
|
281
|
+
};
|
|
282
|
+
return !company || !fingerprint ? null : suggestion;
|
|
283
|
+
};
|
|
284
|
+
const sanitizeHistory = (body) => {
|
|
285
|
+
const artifact = sanitizeArtifact(body);
|
|
286
|
+
const entry = {
|
|
287
|
+
session_id: sessionId, company: sanitizeValue(body.company || ""), project: sanitizeValue(body.project || ""),
|
|
288
|
+
trigger: sanitizeValue(body.trigger || ""), action_kind: sanitizeValue(body.action_kind || "share-suggestion"),
|
|
289
|
+
decision: sanitizeValue(body.decision || ""), event: sanitizeValue(body.event || ""), artifact,
|
|
290
|
+
recipients: uniquePeople(body.recipients || []), recorded_at: isoNow(),
|
|
291
|
+
};
|
|
292
|
+
return artifact.fingerprint ? entry : null;
|
|
293
|
+
};
|
|
294
|
+
const sanitizeSuppression = (body) => {
|
|
295
|
+
const artifact = sanitizeArtifact(body), scope = sanitizeValue(body.scope || "");
|
|
296
|
+
const record = {
|
|
297
|
+
kind: scope ? "scope" : "artifact", scope,
|
|
298
|
+
company: sanitizeValue(body.company || ""), project: sanitizeValue(body.project || ""),
|
|
299
|
+
artifact_class: sanitizeValue(body.artifact_class || artifact.class || ""), artifact: {},
|
|
300
|
+
reason: sanitizeValue(body.reason || "suppressed"), created_at: isoNow(),
|
|
301
|
+
};
|
|
302
|
+
if (artifact.fingerprint)
|
|
303
|
+
record.artifact.fingerprint = artifact.fingerprint;
|
|
304
|
+
if (artifact.path)
|
|
305
|
+
record.artifact.path = artifact.path;
|
|
306
|
+
if (scope) {
|
|
307
|
+
if (!["global", "company", "project"].includes(scope))
|
|
308
|
+
return null;
|
|
309
|
+
return record;
|
|
310
|
+
}
|
|
311
|
+
return record.artifact.fingerprint ? record : null;
|
|
312
|
+
};
|
|
313
|
+
const configPaths = (company, project) => ({
|
|
314
|
+
global: path.join(root, "personal/settings/auto-share-preferences.yaml"),
|
|
315
|
+
company: company ? path.join(root, "companies", company, "settings/auto-share.yaml") : null,
|
|
316
|
+
project: company && project ? path.join(root, "companies", company, "projects", project, "share-policy.yaml") : null,
|
|
317
|
+
});
|
|
318
|
+
const isSuppressedByConfig = (company, project, artifactClass) => {
|
|
319
|
+
const paths = configPaths(company, project), projectCfg = loadYaml(paths.project, f), companyCfg = loadYaml(paths.company, f), globalCfg = loadYaml(paths.global, f);
|
|
320
|
+
const has = (v) => Object.keys(v).length > 0;
|
|
321
|
+
let enabled = has(projectCfg) ? (typeof lookupNested(projectCfg, "enabled") === "boolean" ? lookupNested(projectCfg, "enabled") : null) : null;
|
|
322
|
+
if (enabled === null)
|
|
323
|
+
enabled = has(companyCfg) && typeof lookupNested(companyCfg, "defaults.enabled") === "boolean" ? lookupNested(companyCfg, "defaults.enabled") : null;
|
|
324
|
+
if (enabled === null)
|
|
325
|
+
enabled = typeof lookupNested(globalCfg, "defaults.enabled") === "boolean" ? lookupNested(globalCfg, "defaults.enabled") : null;
|
|
326
|
+
if (enabled === false)
|
|
327
|
+
return true;
|
|
328
|
+
const key = `artifact_classes.${artifactClass}`;
|
|
329
|
+
let cls = has(projectCfg) && typeof lookupNested(projectCfg, key) === "boolean" ? lookupNested(projectCfg, key) : null;
|
|
330
|
+
if (cls === null)
|
|
331
|
+
cls = has(companyCfg) && typeof lookupNested(companyCfg, key) === "boolean" ? lookupNested(companyCfg, key) : null;
|
|
332
|
+
if (cls === null)
|
|
333
|
+
cls = typeof lookupNested(globalCfg, key) === "boolean" ? lookupNested(globalCfg, key) : null;
|
|
334
|
+
return cls === false;
|
|
335
|
+
};
|
|
336
|
+
const suppressionMatch = (record, company, project, cls, fingerprint) => {
|
|
337
|
+
if (!isObj(record))
|
|
338
|
+
return false;
|
|
339
|
+
const artifact = isObj(record.artifact) ? record.artifact : {};
|
|
340
|
+
if (fingerprint && artifact.fingerprint === fingerprint)
|
|
341
|
+
return true;
|
|
342
|
+
if (record.kind !== "scope")
|
|
343
|
+
return false;
|
|
344
|
+
if (record.artifact_class && cls && record.artifact_class !== cls)
|
|
345
|
+
return false;
|
|
346
|
+
if (record.scope === "global")
|
|
347
|
+
return true;
|
|
348
|
+
if (record.scope === "company")
|
|
349
|
+
return Boolean(company) && record.company === company;
|
|
350
|
+
if (record.scope === "project")
|
|
351
|
+
return Boolean(company && project) && record.company === company && record.project === project;
|
|
352
|
+
return false;
|
|
353
|
+
};
|
|
354
|
+
try {
|
|
355
|
+
if (cmd === "enqueue") {
|
|
356
|
+
const suggestion = sanitizeSuggestion(payload);
|
|
357
|
+
if (!suggestion)
|
|
358
|
+
return { code: 0, stdout: "", stderr: "" };
|
|
359
|
+
const existing = readJson(pendingPath, {});
|
|
360
|
+
if (isObj(existing) && Object.keys(existing).length && !existing.resolved_at)
|
|
361
|
+
return { code: 0, stdout: "", stderr: "" };
|
|
362
|
+
writeJson(pendingPath, suggestion);
|
|
363
|
+
const entry = sanitizeHistory(Object.assign({}, suggestion, { event: "enqueued" }));
|
|
364
|
+
if (entry)
|
|
365
|
+
appendJsonl(historyPath, entry);
|
|
366
|
+
}
|
|
367
|
+
else if (cmd === "peek") {
|
|
368
|
+
const pending = readJson(pendingPath, {});
|
|
369
|
+
if (isObj(pending) && Object.keys(pending).length && !pending.resolved_at)
|
|
370
|
+
return { code: 0, stdout: JSON.stringify(sortKeys(pending)), stderr: "" };
|
|
371
|
+
}
|
|
372
|
+
else if (cmd === "mark-shown") {
|
|
373
|
+
const pending = readJson(pendingPath, {});
|
|
374
|
+
if (isObj(pending) && Object.keys(pending).length && !pending.resolved_at && !pending.shown_at) {
|
|
375
|
+
pending.shown_at = isoNow();
|
|
376
|
+
pending.updated_at = isoNow();
|
|
377
|
+
writeJson(pendingPath, pending);
|
|
378
|
+
const entry = sanitizeHistory(Object.assign({}, pending, { event: "shown" }));
|
|
379
|
+
if (entry)
|
|
380
|
+
appendJsonl(historyPath, entry);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
else if (cmd === "record-decision") {
|
|
384
|
+
const pending = readJson(pendingPath, {}), decision = argDecision || env.SHARE_SUGGESTION_DECISION || sanitizeValue(payload.decision || "");
|
|
385
|
+
if (!isObj(pending) || !Object.keys(pending).length || pending.resolved_at || !decision)
|
|
386
|
+
return { code: 0, stdout: "", stderr: "" };
|
|
387
|
+
pending.decision = decision;
|
|
388
|
+
pending.decision_at = isoNow();
|
|
389
|
+
pending.resolved_at = isoNow();
|
|
390
|
+
pending.updated_at = isoNow();
|
|
391
|
+
if (payload.recipients)
|
|
392
|
+
pending.recipients = uniquePeople(payload.recipients);
|
|
393
|
+
const entry = sanitizeHistory(Object.assign({}, pending, { event: "decision", decision }));
|
|
394
|
+
if (entry)
|
|
395
|
+
appendJsonl(historyPath, entry);
|
|
396
|
+
if (["never", "never-again", "never_again"].includes(decision)) {
|
|
397
|
+
const suppression = sanitizeSuppression({ company: pending.company, project: pending.project, artifact_class: (pending.artifact || {}).class, artifact: pending.artifact, reason: "never-again" });
|
|
398
|
+
if (suppression)
|
|
399
|
+
appendJsonl(suppressionsPath, suppression);
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
f.unlinkSync(pendingPath);
|
|
403
|
+
}
|
|
404
|
+
catch { /* shell ignores a missing pending file */ }
|
|
405
|
+
}
|
|
406
|
+
else if (cmd === "append-history") {
|
|
407
|
+
const entry = sanitizeHistory(payload);
|
|
408
|
+
if (entry)
|
|
409
|
+
appendJsonl(historyPath, entry);
|
|
410
|
+
}
|
|
411
|
+
else if (cmd === "suppress") {
|
|
412
|
+
const record = sanitizeSuppression(payload);
|
|
413
|
+
if (record)
|
|
414
|
+
appendJsonl(suppressionsPath, record);
|
|
415
|
+
}
|
|
416
|
+
else if (cmd === "is-suppressed") {
|
|
417
|
+
const artifact = sanitizeArtifact(payload), company = sanitizeValue(payload.company || ""), project = sanitizeValue(payload.project || "");
|
|
418
|
+
const cls = sanitizeValue(payload.artifact_class || artifact.class || ""), fingerprint = sanitizeValue(artifact.fingerprint || "");
|
|
419
|
+
if (isSuppressedByConfig(company, project, cls))
|
|
420
|
+
return { code: 0, stdout: "true", stderr: "" };
|
|
421
|
+
if (f.existsSync(suppressionsPath)) {
|
|
422
|
+
for (let raw of f.readFileSync(suppressionsPath, "utf8").split(/\r?\n/)) {
|
|
423
|
+
raw = raw.trim();
|
|
424
|
+
if (!raw)
|
|
425
|
+
continue;
|
|
426
|
+
let record;
|
|
427
|
+
try {
|
|
428
|
+
record = JSON.parse(raw);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (suppressionMatch(record, company, project, cls, fingerprint))
|
|
434
|
+
return { code: 0, stdout: "true", stderr: "" };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return { code: 0, stdout: "", stderr: "" };
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
442
|
+
return { code: 0, stdout: "", stderr: `share-suggestion-state: internal error\n${detail}\n` };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
//# sourceMappingURL=core-share-suggestion-state-native.js.map
|
package/dist/commands/core.js
CHANGED
|
@@ -46,6 +46,7 @@ import { registerCorePinNative } from "./core-pin-native.js";
|
|
|
46
46
|
import { registerCoreRefreshVaultAccessNative } from "./core-refresh-vault-access-native.js";
|
|
47
47
|
import { registerCoreWorkMeshBindTrustedNative } from "./core-work-mesh-bind-trusted-native.js";
|
|
48
48
|
import { registerCoreHookHelperNativeCommands } from "./core-native-hook-helper-commands.js";
|
|
49
|
+
import { registerCoreHookStateHelpers } from "./core-hook-state-helpers-native.js";
|
|
49
50
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
50
51
|
import { interpreterFor, runBundledScript } from "../utils/run-bundled-script.js";
|
|
51
52
|
import { CompanyIndexUserError, renderIndexTarget } from "../lib/index-render/index.js";
|
|
@@ -233,6 +234,9 @@ export const SCAFFOLD_ONLY_ASSETS = [
|
|
|
233
234
|
{ asset: "core/scripts/hq-job-remediate.sh", root: "live", summary: "Scheduled jobs bounded remediation helper" },
|
|
234
235
|
{ asset: "core/scripts/hq-dm-bind.sh", root: "live", summary: "Rollback shell for native dm-bind" },
|
|
235
236
|
{ asset: "core/scripts/work-mesh-live-bind-trusted.sh", root: "live", summary: "Rollback shell for native work-mesh-bind-trusted" },
|
|
237
|
+
{ asset: "core/scripts/share-suggestion-state.sh", root: "live", summary: "Rollback shell for native share-suggestion-state" },
|
|
238
|
+
{ asset: "core/scripts/repo-run-registry.sh", root: "live", summary: "Rollback shell for native repo-run-registry" },
|
|
239
|
+
{ asset: "core/scripts/detect-stale-review-base.sh", root: "live", summary: "Rollback shell for native detect-stale-review-base" },
|
|
236
240
|
// Shared shell libs sourced by the two relocated scripts (and hooks). Never
|
|
237
241
|
// dispatched on their own (so no `hq core` command), but they MUST ship in the
|
|
238
242
|
// tarball, or the bundled hq-session / checkpoint-stop-gate die at `source`
|
|
@@ -387,6 +391,7 @@ export function registerCoreCommands(program) {
|
|
|
387
391
|
registerCoreDmBindNative(core);
|
|
388
392
|
registerCoreWorkMeshBindTrustedNative(core);
|
|
389
393
|
registerCoreHookHelperNativeCommands(core);
|
|
394
|
+
registerCoreHookStateHelpers(core);
|
|
390
395
|
// Wave 2 handoff/worktree commands retain their byte-identical shell assets
|
|
391
396
|
// as a default-off rollback path for one release. Keep the gate helper small
|
|
392
397
|
// so sibling Wave 2 work can reuse it without editing this command table.
|
package/dist/commands/lanes.js
CHANGED
|
@@ -45,7 +45,7 @@ import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } fr
|
|
|
45
45
|
import { createOutpostRegistryWorkerApprovalContext, parseOutpostRegistryWorkerApprovalContext, } from "../lib/lanes/outpost-registry-worker-approval.js";
|
|
46
46
|
import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
|
|
47
47
|
export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
48
|
-
import { buildOutpostCapacityCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostQuestionListCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, REMOTE_LANE_READ_ENV, outpostRejectedEffortOption, parseOutpostPickupPayload, PICKUP_WAIT_MS, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
|
|
48
|
+
import { buildOutpostCapacityCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostQuestionListCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, buildOutpostLegacyLaneUlidListCommand, REMOTE_LANE_READ_ENV, outpostRejectedEffortOption, parseOutpostPickupPayload, PICKUP_WAIT_MS, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
|
|
49
49
|
import { outpostHireSummaryCacheKey, OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS, readOutpostHireSummaryCache, readOutpostHireSummaryCacheIncludingStale, writeOutpostHireSummaryCache, } from "../lib/lanes/outpost-hire-cache.js";
|
|
50
50
|
import { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "../lib/lanes/host-capacity.js";
|
|
51
51
|
import { BALANCE_MOVABLE_STATES, selectClaudeAccount, } from "../lib/lanes/claude-balance.js";
|
|
@@ -781,6 +781,7 @@ function validateOwnerLaneHireSummary(input) {
|
|
|
781
781
|
function laneListQueryFlags(query) {
|
|
782
782
|
return [
|
|
783
783
|
...(query.laneIds?.length ? ["--lane"] : []),
|
|
784
|
+
...(query.laneUlids?.length ? ["--lane-ulid"] : []),
|
|
784
785
|
...(query.active ? ["--active"] : []),
|
|
785
786
|
...(query.hierarchy?.length ? ["--hierarchy"] : []),
|
|
786
787
|
...(query.snapshot ? ["--snapshot"] : []),
|
|
@@ -804,6 +805,14 @@ async function dispatchOutpostLaneList(ctx, outpostId, query, timeoutSeconds, wa
|
|
|
804
805
|
});
|
|
805
806
|
if (query.strictSnapshot || !rejectedLaneListFlag(filtered, query))
|
|
806
807
|
return filtered;
|
|
808
|
+
if (rejectedLaneListFlag(filtered, query) === "--lane-ulid" && query.laneUlids?.length === 1) {
|
|
809
|
+
return ctx.dispatchOutpostExec({
|
|
810
|
+
outpostId,
|
|
811
|
+
command: buildOutpostLegacyLaneUlidListCommand({ hqBinary, ulid: query.laneUlids[0] }),
|
|
812
|
+
timeoutSeconds,
|
|
813
|
+
waitMs,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
807
816
|
return ctx.dispatchOutpostExec({
|
|
808
817
|
outpostId,
|
|
809
818
|
command: buildOutpostLaneListCommand({ hqBinary }),
|
|
@@ -1206,15 +1215,20 @@ async function loadHostCapacitySnapshots(ctx, hqRoot, config = loadLanesConfig(h
|
|
|
1206
1215
|
};
|
|
1207
1216
|
}
|
|
1208
1217
|
const outpostLaneListMemo = new WeakMap();
|
|
1218
|
+
const MAX_OUTPOST_LANE_LIST_BYTES = 480 * 1024;
|
|
1209
1219
|
function normalizedLaneListQuery(query) {
|
|
1210
1220
|
const laneIds = query.laneIds === undefined
|
|
1211
1221
|
? undefined
|
|
1212
1222
|
: [...new Set(query.laneIds)].sort((a, b) => a.localeCompare(b));
|
|
1223
|
+
const laneUlids = query.laneUlids === undefined
|
|
1224
|
+
? undefined
|
|
1225
|
+
: [...new Set(query.laneUlids.map((ulid) => ulid.toUpperCase()))].sort((a, b) => a.localeCompare(b));
|
|
1213
1226
|
const hierarchy = query.hierarchy === undefined
|
|
1214
1227
|
? undefined
|
|
1215
1228
|
: [...new Set(query.hierarchy)].sort((a, b) => a.localeCompare(b));
|
|
1216
1229
|
return {
|
|
1217
1230
|
...(laneIds === undefined ? {} : { laneIds }),
|
|
1231
|
+
...(laneUlids === undefined ? {} : { laneUlids }),
|
|
1218
1232
|
...(query.active ? { active: true } : {}),
|
|
1219
1233
|
...(hierarchy === undefined ? {} : { hierarchy }),
|
|
1220
1234
|
...(query.snapshot ? { snapshot: true } : {}),
|
|
@@ -1223,7 +1237,7 @@ function normalizedLaneListQuery(query) {
|
|
|
1223
1237
|
};
|
|
1224
1238
|
}
|
|
1225
1239
|
async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOptions = {}) {
|
|
1226
|
-
const targetedLookup = query.hierarchy !== undefined || query.laneIds !== undefined;
|
|
1240
|
+
const targetedLookup = query.hierarchy !== undefined || query.laneIds !== undefined || query.laneUlids !== undefined;
|
|
1227
1241
|
const waitMs = targetedLookup ? outpostSingleLaneWaitMs(ctx.env) : CAPACITY_PROBE_TIMEOUT_MS;
|
|
1228
1242
|
const dispatchWaitMs = dispatchOptions.waitMs ?? waitMs;
|
|
1229
1243
|
const dispatchTimeoutSeconds = dispatchOptions.timeoutSeconds ?? Math.ceil(dispatchWaitMs / 1000);
|
|
@@ -1249,6 +1263,10 @@ async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOpt
|
|
|
1249
1263
|
exit_code: result.exitCode,
|
|
1250
1264
|
});
|
|
1251
1265
|
}
|
|
1266
|
+
const stdoutBytes = Buffer.byteLength(result.stdout, "utf8");
|
|
1267
|
+
if (stdoutBytes >= MAX_OUTPOST_LANE_LIST_BYTES) {
|
|
1268
|
+
throw new LanesError("outpost_lane_list_too_large", `Outpost ${outpostId} lane-list response reached the ${Math.floor(MAX_OUTPOST_LANE_LIST_BYTES / 1024)} KiB Outpost exec output limit`, { outpost_id: outpostId, command_id: result.commandId, stdout_bytes: stdoutBytes });
|
|
1269
|
+
}
|
|
1252
1270
|
let body;
|
|
1253
1271
|
try {
|
|
1254
1272
|
body = JSON.parse(result.stdout);
|
|
@@ -1625,7 +1643,7 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1625
1643
|
let ownerLanes;
|
|
1626
1644
|
try {
|
|
1627
1645
|
ownerLanes = await readOutpostLaneRecords(ctx, outpostId, hostlessUlidLookup
|
|
1628
|
-
? { snapshot: true }
|
|
1646
|
+
? { laneUlids: [requestedUlid], snapshot: true }
|
|
1629
1647
|
: { hierarchy: [laneId], snapshot: true });
|
|
1630
1648
|
probedOutpostIds.push(outpostId);
|
|
1631
1649
|
}
|
|
@@ -4849,6 +4867,12 @@ async function runList(ctx, opts) {
|
|
|
4849
4867
|
new Set(requestedLaneIds).size !== requestedLaneIds.length) {
|
|
4850
4868
|
throw new LanesError("lane_list_filter_invalid", "--lane must be repeated with distinct, non-empty lane ids", { lane_ids: requestedLaneIds });
|
|
4851
4869
|
}
|
|
4870
|
+
const requestedLaneUlids = opts.laneUlid ?? [];
|
|
4871
|
+
if (!Array.isArray(requestedLaneUlids) ||
|
|
4872
|
+
requestedLaneUlids.some((ulid) => typeof ulid !== "string" || laneUlidPart(ulid) !== ulid.toUpperCase()) ||
|
|
4873
|
+
new Set(requestedLaneUlids.map((ulid) => ulid.toUpperCase())).size !== requestedLaneUlids.length) {
|
|
4874
|
+
throw new LanesError("lane_list_ulid_filter_invalid", "--lane-ulid must be repeated with distinct 26-character ULIDs", { lane_ulids: requestedLaneUlids });
|
|
4875
|
+
}
|
|
4852
4876
|
if (opts.hierarchy !== undefined &&
|
|
4853
4877
|
(!Array.isArray(opts.hierarchy) || opts.hierarchy.length === 0 ||
|
|
4854
4878
|
opts.hierarchy.some((laneId) => typeof laneId !== "string" || !laneId.trim() || laneId.trim() !== laneId) ||
|
|
@@ -4869,11 +4893,13 @@ async function runList(ctx, opts) {
|
|
|
4869
4893
|
company = await ctx.resolveCompany(opts.company);
|
|
4870
4894
|
}
|
|
4871
4895
|
const requestedIds = new Set(requestedLaneIds);
|
|
4872
|
-
const
|
|
4896
|
+
const requestedUlidSet = new Set(requestedLaneUlids.map((ulid) => ulid.toUpperCase()));
|
|
4897
|
+
const canReadOnlyRequestedIds = opts.snapshot && (requestedLaneIds.length > 0 || requestedLaneUlids.length > 0) &&
|
|
4873
4898
|
!opts.active && opts.hierarchy === undefined;
|
|
4874
4899
|
const laneSource = canReadOnlyRequestedIds
|
|
4875
4900
|
? listLaneIds(hqRoot)
|
|
4876
|
-
.filter((laneId) => requestedIds.has(laneId)
|
|
4901
|
+
.filter((laneId) => requestedIds.has(laneId) ||
|
|
4902
|
+
(laneUlidPart(laneId) !== undefined && requestedUlidSet.has(laneUlidPart(laneId))))
|
|
4877
4903
|
.map((laneId) => readLane(hqRoot, laneId))
|
|
4878
4904
|
: loadAllLanes(hqRoot);
|
|
4879
4905
|
const hierarchyIds = opts.hierarchy === undefined
|
|
@@ -4882,6 +4908,8 @@ async function runList(ctx, opts) {
|
|
|
4882
4908
|
const matchesLaneFilters = (lane, includeState) => {
|
|
4883
4909
|
if (requestedLaneIds.length > 0 && !requestedIds.has(lane.lane_id))
|
|
4884
4910
|
return false;
|
|
4911
|
+
if (requestedUlidSet.size > 0 && !requestedUlidSet.has(laneUlidPart(lane.lane_id) ?? ""))
|
|
4912
|
+
return false;
|
|
4885
4913
|
if (includeState && opts.active && isTerminalLaneState(lane.state))
|
|
4886
4914
|
return false;
|
|
4887
4915
|
if (hierarchyIds && !hierarchyIds.has(lane.lane_id))
|
|
@@ -8424,6 +8452,8 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8424
8452
|
.option("--state <state>", `Filter by state (${LANE_STATES.join("|")})`)
|
|
8425
8453
|
.option("--senior <ref>", "Filter by senior ref")
|
|
8426
8454
|
.option("--lane <id>", "Filter by lane id (repeatable)", (value, previous = []) => [...previous, value], [])
|
|
8455
|
+
.addOption(new Option("--lane-ulid <ulid>", "Filter by lane ULID for internal owner lookup").hideHelp()
|
|
8456
|
+
.argParser((value, previous = []) => [...previous, value]))
|
|
8427
8457
|
.option("--active", "Include only lanes that are not terminal")
|
|
8428
8458
|
.addOption(new Option("--hierarchy <lane-id>").hideHelp()
|
|
8429
8459
|
.argParser((value, previous = []) => [...previous, value]))
|
package/dist/fast-core.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* entries use the bundled-script runner; native entries import their one handler
|
|
7
7
|
* module. The registration in `core.ts` remains the complete fallback path.
|
|
8
8
|
*/
|
|
9
|
+
export declare function readStdinIfPiped(stream: NodeJS.ReadStream): Promise<string>;
|
|
9
10
|
/**
|
|
10
11
|
* Run the fast-path core command named in `argv` and exit with its code.
|
|
11
12
|
* Only call when {@link isFastCoreRequest} is true; a non-match returns without
|
package/dist/fast-core.js
CHANGED
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
import { resolveLiveRoot } from "./utils/hq-roots.js";
|
|
10
10
|
import { runBundledScript } from "./utils/run-bundled-script.js";
|
|
11
11
|
import { parseFastCore } from "./fast-scaffold.js";
|
|
12
|
+
export async function readStdinIfPiped(stream) {
|
|
13
|
+
if (stream.isTTY)
|
|
14
|
+
return "";
|
|
15
|
+
const chunks = [];
|
|
16
|
+
for await (const chunk of stream)
|
|
17
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
18
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
19
|
+
}
|
|
12
20
|
/**
|
|
13
21
|
* Run the fast-path core command named in `argv` and exit with its code.
|
|
14
22
|
* Only call when {@link isFastCoreRequest} is true; a non-match returns without
|
|
@@ -60,12 +68,8 @@ export async function runFastCore(argv) {
|
|
|
60
68
|
throw new Error(`Native fast handler ${entry.exportName} was not exported`);
|
|
61
69
|
}
|
|
62
70
|
const readsStdin = entry.readsStdinFor?.(parsed.args) ?? entry.readsStdin ?? false;
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
for await (const chunk of process.stdin)
|
|
66
|
-
chunks.push(chunk);
|
|
67
|
-
}
|
|
68
|
-
const code = await handler(parsed.args, readsStdin ? Buffer.concat(chunks).toString("utf8") : "", process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg);
|
|
71
|
+
const stdin = readsStdin ? await readStdinIfPiped(process.stdin) : "";
|
|
72
|
+
const code = await handler(parsed.args, stdin, process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg);
|
|
69
73
|
// Let Node drain handler output before terminating. The timeout guard's
|
|
70
74
|
// stderr message must survive the native route's process boundary.
|
|
71
75
|
process.exitCode = code;
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -172,6 +172,23 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
172
172
|
shellCwd: "caller",
|
|
173
173
|
shellHqRoot: name === "reconcile" ? "reconcile-default" : "none",
|
|
174
174
|
})),
|
|
175
|
+
...["share-suggestion-state", "repo-run-registry", "detect-stale-review-base"].map((name) => ({
|
|
176
|
+
name,
|
|
177
|
+
kind: "native",
|
|
178
|
+
modulePath: "./commands/core-hook-state-helpers-native.js",
|
|
179
|
+
exportName: name === "share-suggestion-state"
|
|
180
|
+
? "runShareSuggestionStateFast"
|
|
181
|
+
: name === "repo-run-registry"
|
|
182
|
+
? "runRepoRunRegistryFast"
|
|
183
|
+
: "runDetectStaleReviewBaseFast",
|
|
184
|
+
flagKey: "hq-core-native-fast-route",
|
|
185
|
+
familyFlagKey: "core-native-hook-helpers",
|
|
186
|
+
asset: `core/scripts/${name}.sh`,
|
|
187
|
+
root: "live",
|
|
188
|
+
shellCwd: "live",
|
|
189
|
+
shellHqRoot: "live",
|
|
190
|
+
...(name === "share-suggestion-state" ? { readsStdin: true } : {}),
|
|
191
|
+
})),
|
|
175
192
|
{
|
|
176
193
|
name: "resolve-company",
|
|
177
194
|
kind: "native",
|