@sjawhar/pi-legion-envoy 5.16.6 → 5.16.8
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/dist/envoy.js +6 -1
- package/dist/legion.js +167 -31
- package/dist/skills/legion-worker/SKILL.md +23 -10
- package/package.json +2 -2
package/dist/envoy.js
CHANGED
|
@@ -30600,6 +30600,7 @@ function controllerToken(project) {
|
|
|
30600
30600
|
|
|
30601
30601
|
// ../contracts/src/legion-daemon-api.ts
|
|
30602
30602
|
var nonEmptyString = exports_external.string().min(1);
|
|
30603
|
+
var appLogin = exports_external.string().regex(/^[^[\]]+\[bot\]$/);
|
|
30603
30604
|
var legionRole = exports_external.enum(LEGION_ROLES);
|
|
30604
30605
|
var requiredUnknown = exports_external.unknown().refine((value) => value !== undefined, {
|
|
30605
30606
|
message: "Required"
|
|
@@ -30853,7 +30854,11 @@ var LegionDaemonApi = {
|
|
|
30853
30854
|
},
|
|
30854
30855
|
GitHubToken: {
|
|
30855
30856
|
request: exports_external.strictObject({ grantId: nonEmptyString }),
|
|
30856
|
-
response: exports_external.object({
|
|
30857
|
+
response: exports_external.object({
|
|
30858
|
+
token: nonEmptyString,
|
|
30859
|
+
appLogin: exports_external.string().endsWith("[bot]"),
|
|
30860
|
+
legionAppLogins: exports_external.object({ implement: appLogin, review: appLogin }).optional()
|
|
30861
|
+
})
|
|
30857
30862
|
},
|
|
30858
30863
|
GitCredential: {
|
|
30859
30864
|
request: exports_external.strictObject({ grantId: nonEmptyString })
|
package/dist/legion.js
CHANGED
|
@@ -30769,6 +30769,7 @@ function controllerToken(project) {
|
|
|
30769
30769
|
|
|
30770
30770
|
// ../contracts/src/legion-daemon-api.ts
|
|
30771
30771
|
var nonEmptyString = exports_external.string().min(1);
|
|
30772
|
+
var appLogin = exports_external.string().regex(/^[^[\]]+\[bot\]$/);
|
|
30772
30773
|
var legionRole = exports_external.enum(LEGION_ROLES);
|
|
30773
30774
|
var requiredUnknown = exports_external.unknown().refine((value) => value !== undefined, {
|
|
30774
30775
|
message: "Required"
|
|
@@ -31022,7 +31023,11 @@ var LegionDaemonApi = {
|
|
|
31022
31023
|
},
|
|
31023
31024
|
GitHubToken: {
|
|
31024
31025
|
request: exports_external.strictObject({ grantId: nonEmptyString }),
|
|
31025
|
-
response: exports_external.object({
|
|
31026
|
+
response: exports_external.object({
|
|
31027
|
+
token: nonEmptyString,
|
|
31028
|
+
appLogin: exports_external.string().endsWith("[bot]"),
|
|
31029
|
+
legionAppLogins: exports_external.object({ implement: appLogin, review: appLogin }).optional()
|
|
31030
|
+
})
|
|
31026
31031
|
},
|
|
31027
31032
|
GitCredential: {
|
|
31028
31033
|
request: exports_external.strictObject({ grantId: nonEmptyString })
|
|
@@ -33415,7 +33420,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
33415
33420
|
// package.json
|
|
33416
33421
|
var package_default = {
|
|
33417
33422
|
name: "@sjawhar/pi-legion-envoy",
|
|
33418
|
-
version: "5.16.
|
|
33423
|
+
version: "5.16.8",
|
|
33419
33424
|
type: "module",
|
|
33420
33425
|
omp: {
|
|
33421
33426
|
extensions: [
|
|
@@ -33428,7 +33433,7 @@ var package_default = {
|
|
|
33428
33433
|
},
|
|
33429
33434
|
legion: {
|
|
33430
33435
|
daemonApiVersion: 9,
|
|
33431
|
-
goDaemonApiVersion:
|
|
33436
|
+
goDaemonApiVersion: 11
|
|
33432
33437
|
},
|
|
33433
33438
|
repository: {
|
|
33434
33439
|
type: "git",
|
|
@@ -33990,9 +33995,11 @@ var LegionGoGrantResponse = exports_external.strictObject({
|
|
|
33990
33995
|
grantId: nonEmptyString2,
|
|
33991
33996
|
expiresAt: timestamp
|
|
33992
33997
|
});
|
|
33998
|
+
var appLogin2 = exports_external.string().regex(/^[^[\]]+\[bot\]$/);
|
|
33993
33999
|
var LegionGoGitHubTokenResponse = exports_external.strictObject({
|
|
33994
34000
|
token: nonEmptyString2,
|
|
33995
|
-
appLogin: exports_external.string().endsWith("[bot]")
|
|
34001
|
+
appLogin: exports_external.string().endsWith("[bot]"),
|
|
34002
|
+
legionAppLogins: exports_external.strictObject({ implement: appLogin2, review: appLogin2 }).optional()
|
|
33996
34003
|
});
|
|
33997
34004
|
var LegionGoGitCredentialResponse = exports_external.strictObject({
|
|
33998
34005
|
username: exports_external.literal("x-access-token"),
|
|
@@ -41227,6 +41234,15 @@ function scanIpythonShell(source, sinks) {
|
|
|
41227
41234
|
}
|
|
41228
41235
|
|
|
41229
41236
|
// src/legion/pane-guard.ts
|
|
41237
|
+
var definitions = new WeakMap;
|
|
41238
|
+
function trapSignal(operand) {
|
|
41239
|
+
const name = operand.toUpperCase();
|
|
41240
|
+
if (name === "0")
|
|
41241
|
+
return "EXIT";
|
|
41242
|
+
return name.startsWith("SIG") && name !== "SIGEXIT" ? name.slice(3) : name;
|
|
41243
|
+
}
|
|
41244
|
+
var UNREADABLE_CONDITION = "an unreadable condition";
|
|
41245
|
+
|
|
41230
41246
|
class Refusal extends Error {
|
|
41231
41247
|
snippet;
|
|
41232
41248
|
line;
|
|
@@ -41252,7 +41268,7 @@ function wrapRefusal(error48, site, label) {
|
|
|
41252
41268
|
throw new Refusal(site.snippet, site.line, `line ${error48.line} of ${source}, \`${error48.snippet}\`: ${error48.detail}`);
|
|
41253
41269
|
}
|
|
41254
41270
|
var MAX_DEPTH = 8;
|
|
41255
|
-
var MAX_WALK_STEPS =
|
|
41271
|
+
var MAX_WALK_STEPS = 1e5;
|
|
41256
41272
|
var MAX_ALTERNATIVES = 64;
|
|
41257
41273
|
var MAX_SCRIPT_BYTES = 1024 * 1024;
|
|
41258
41274
|
var FRESH_TEMP_NAME = "tmp.XXXXXXXXXX";
|
|
@@ -41570,12 +41586,22 @@ function substitution(script, text, st, ctx) {
|
|
|
41570
41586
|
}
|
|
41571
41587
|
const only = script?.commands.length === 1 ? script.commands[0]?.command : undefined;
|
|
41572
41588
|
const invocation = substitutionInvocation(only, st, ctx);
|
|
41573
|
-
const
|
|
41574
|
-
if (
|
|
41575
|
-
const
|
|
41576
|
-
|
|
41577
|
-
|
|
41589
|
+
const callee = invocation === undefined ? undefined : st.functions.get(invocation.base);
|
|
41590
|
+
if (callee !== undefined && invocation !== undefined) {
|
|
41591
|
+
const alternatives = [];
|
|
41592
|
+
let external2 = false;
|
|
41593
|
+
for (const definition of callee) {
|
|
41594
|
+
const output = definition === undefined ? undefined : functionOutput(invocation.base, definition, invocation.rest, st, ctx);
|
|
41595
|
+
if (output !== undefined)
|
|
41596
|
+
alternatives.push(...output.map((value) => [...value]));
|
|
41597
|
+
else
|
|
41598
|
+
external2 = true;
|
|
41599
|
+
}
|
|
41600
|
+
return external2 ? [...alternatives, ...commandOutput(only, invocation, text, st, ctx)] : alternatives;
|
|
41578
41601
|
}
|
|
41602
|
+
return commandOutput(only, invocation, text, st, ctx);
|
|
41603
|
+
}
|
|
41604
|
+
function commandOutput(only, invocation, text, st, ctx) {
|
|
41579
41605
|
if (invocation !== undefined) {
|
|
41580
41606
|
if (invocation.base === "pwd" && invocation.rest.length === 0 && st.cwd !== undefined) {
|
|
41581
41607
|
return [[literal2(st.cwd)]];
|
|
@@ -41813,7 +41839,7 @@ function walkSubstitution(script, inner, text, st, ctx) {
|
|
|
41813
41839
|
throw new Refusal(text, 1, `the guard could not read the command in \`${text}\``);
|
|
41814
41840
|
}
|
|
41815
41841
|
const source = parsed.source ?? (script === undefined ? inner ?? "" : st.source);
|
|
41816
|
-
walkScript(parsed,
|
|
41842
|
+
walkScript(parsed, subshell(st, { source }), ctx);
|
|
41817
41843
|
}
|
|
41818
41844
|
function clone2(st) {
|
|
41819
41845
|
return {
|
|
@@ -41856,6 +41882,8 @@ function merge2(target, branches) {
|
|
|
41856
41882
|
target.arrays.set(name, new Map([[UNKNOWN_ARRAY_INDEX, unknownElement]]));
|
|
41857
41883
|
}
|
|
41858
41884
|
}
|
|
41885
|
+
const positionals = branches.map((branch) => JSON.stringify(branch.positional ?? null));
|
|
41886
|
+
target.positional = positionals.every((value) => value === positionals[0]) ? branches[0]?.positional : undefined;
|
|
41859
41887
|
const cwds = new Set(branches.map((branch) => branch.cwd));
|
|
41860
41888
|
if (cwds.size > 1) {
|
|
41861
41889
|
target.cwd = undefined;
|
|
@@ -41869,6 +41897,46 @@ function merge2(target, branches) {
|
|
|
41869
41897
|
target.exported.add(name);
|
|
41870
41898
|
target.backgroundStarted ||= branch.backgroundStarted;
|
|
41871
41899
|
}
|
|
41900
|
+
const functionNames = new Set;
|
|
41901
|
+
for (const branch of branches)
|
|
41902
|
+
for (const name of branch.functions.keys())
|
|
41903
|
+
functionNames.add(name);
|
|
41904
|
+
const functions = new Map;
|
|
41905
|
+
for (const name of functionNames) {
|
|
41906
|
+
const callees = branches.map((branch) => branch.functions.get(name));
|
|
41907
|
+
const first = callees[0];
|
|
41908
|
+
if (first !== undefined && callees.every((callee) => callee === first)) {
|
|
41909
|
+
functions.set(name, first);
|
|
41910
|
+
continue;
|
|
41911
|
+
}
|
|
41912
|
+
const candidates = new Set;
|
|
41913
|
+
for (const callee of callees)
|
|
41914
|
+
for (const definition of callee ?? [undefined])
|
|
41915
|
+
candidates.add(definition);
|
|
41916
|
+
functions.set(name, [...candidates]);
|
|
41917
|
+
}
|
|
41918
|
+
target.functions = functions;
|
|
41919
|
+
const lifted = [];
|
|
41920
|
+
let merged;
|
|
41921
|
+
for (const branch of branches) {
|
|
41922
|
+
for (const trap of branch.traps) {
|
|
41923
|
+
if (target.traps.includes(trap))
|
|
41924
|
+
continue;
|
|
41925
|
+
merged ??= new Map(target.vars);
|
|
41926
|
+
lifted.push({ ...trap, vars: handlerVars(trap, branch.vars), merged });
|
|
41927
|
+
}
|
|
41928
|
+
}
|
|
41929
|
+
target.traps = [...target.traps, ...lifted];
|
|
41930
|
+
}
|
|
41931
|
+
function handlerVars(trap, vars) {
|
|
41932
|
+
const { vars: branch, merged } = trap;
|
|
41933
|
+
if (branch === undefined || merged === undefined)
|
|
41934
|
+
return vars;
|
|
41935
|
+
const out = new Map(vars);
|
|
41936
|
+
for (const [name, value] of branch)
|
|
41937
|
+
if (vars.get(name) === merged.get(name))
|
|
41938
|
+
out.set(name, value);
|
|
41939
|
+
return out;
|
|
41872
41940
|
}
|
|
41873
41941
|
function lineOf(source, pos) {
|
|
41874
41942
|
let line = 1;
|
|
@@ -41894,13 +41962,19 @@ function walkScript(script, st, ctx, runTraps = true) {
|
|
|
41894
41962
|
}
|
|
41895
41963
|
for (const statement of script.commands)
|
|
41896
41964
|
walkNode(statement, st, ctx, false);
|
|
41897
|
-
if (
|
|
41898
|
-
|
|
41965
|
+
if (runTraps)
|
|
41966
|
+
runSetTraps(st, ctx);
|
|
41967
|
+
}
|
|
41968
|
+
function subshell(st, patch) {
|
|
41969
|
+
return { ...clone2(st), traps: [], ...patch };
|
|
41970
|
+
}
|
|
41971
|
+
function runSetTraps(st, ctx) {
|
|
41899
41972
|
for (const trap of st.traps) {
|
|
41973
|
+
const vars = new Map([...handlerVars(trap, st.vars), ...trap.pids ?? []]);
|
|
41900
41974
|
try {
|
|
41901
|
-
runText(trap.text, "the EXIT trap",
|
|
41902
|
-
} catch (
|
|
41903
|
-
locateRefusal(
|
|
41975
|
+
runText(trap.text, "the EXIT trap", subshell(st, { source: trap.file, vars }), ctx, trap.site);
|
|
41976
|
+
} catch (error48) {
|
|
41977
|
+
locateRefusal(error48, trap.file);
|
|
41904
41978
|
}
|
|
41905
41979
|
}
|
|
41906
41980
|
}
|
|
@@ -41919,20 +41993,34 @@ function walkNode(node, st, ctx, pipeIn) {
|
|
|
41919
41993
|
switch (node.type) {
|
|
41920
41994
|
case "Statement": {
|
|
41921
41995
|
checkRedirects(node.redirects, undefined, siteOf(node, st), st, ctx);
|
|
41922
|
-
|
|
41923
|
-
|
|
41996
|
+
if (node.background) {
|
|
41997
|
+
const before = st.output;
|
|
41998
|
+
const child = subshell(st);
|
|
41999
|
+
walkNode(node.command, child, ctx, pipeIn);
|
|
42000
|
+
runSetTraps(child, ctx);
|
|
42001
|
+
if (outputChanged(before, child.output))
|
|
42002
|
+
st.output = undefined;
|
|
41924
42003
|
st.backgroundStarted = true;
|
|
42004
|
+
return;
|
|
42005
|
+
}
|
|
42006
|
+
walkNode(node.command, st, ctx, pipeIn);
|
|
41925
42007
|
return;
|
|
41926
42008
|
}
|
|
41927
42009
|
case "Command":
|
|
41928
42010
|
handleCommand(node, st, ctx, pipeIn);
|
|
41929
42011
|
return;
|
|
41930
42012
|
case "Pipeline": {
|
|
42013
|
+
if (node.commands.length === 1) {
|
|
42014
|
+
for (const command of node.commands)
|
|
42015
|
+
walkNode(command, st, ctx, pipeIn);
|
|
42016
|
+
return;
|
|
42017
|
+
}
|
|
41931
42018
|
const before = st.output;
|
|
41932
42019
|
const parts = [];
|
|
41933
42020
|
for (const [index, command] of node.commands.entries()) {
|
|
41934
|
-
const part =
|
|
42021
|
+
const part = subshell(st);
|
|
41935
42022
|
walkNode(command, part, ctx, index > 0);
|
|
42023
|
+
runSetTraps(part, ctx);
|
|
41936
42024
|
parts.push(part);
|
|
41937
42025
|
}
|
|
41938
42026
|
if (parts.some((part) => outputChanged(before, part.output)))
|
|
@@ -41951,8 +42039,9 @@ function walkNode(node, st, ctx, pipeIn) {
|
|
|
41951
42039
|
return;
|
|
41952
42040
|
case "Subshell": {
|
|
41953
42041
|
const before = st.output;
|
|
41954
|
-
const child =
|
|
42042
|
+
const child = subshell(st);
|
|
41955
42043
|
walkList(node.body.commands, child, ctx);
|
|
42044
|
+
runSetTraps(child, ctx);
|
|
41956
42045
|
if (outputChanged(before, child.output))
|
|
41957
42046
|
st.output = undefined;
|
|
41958
42047
|
return;
|
|
@@ -42041,14 +42130,21 @@ function walkNode(node, st, ctx, pipeIn) {
|
|
|
42041
42130
|
st.output = undefined;
|
|
42042
42131
|
return;
|
|
42043
42132
|
}
|
|
42044
|
-
case "Function":
|
|
42045
|
-
|
|
42133
|
+
case "Function": {
|
|
42134
|
+
let callee = definitions.get(node);
|
|
42135
|
+
if (callee === undefined) {
|
|
42136
|
+
callee = [{ body: node.body, source: st.source, file: st.script }];
|
|
42137
|
+
definitions.set(node, callee);
|
|
42138
|
+
}
|
|
42139
|
+
st.functions.set(node.name.value, callee);
|
|
42046
42140
|
checkRedirects(node.redirects, undefined, siteOf(node, st), st, ctx);
|
|
42047
42141
|
return;
|
|
42142
|
+
}
|
|
42048
42143
|
case "Coproc": {
|
|
42049
42144
|
const before = st.output;
|
|
42050
|
-
const child =
|
|
42145
|
+
const child = subshell(st);
|
|
42051
42146
|
walkNode(node.body, child, ctx, false);
|
|
42147
|
+
runSetTraps(child, ctx);
|
|
42052
42148
|
if (outputChanged(before, child.output))
|
|
42053
42149
|
st.output = undefined;
|
|
42054
42150
|
checkRedirects(node.redirects, undefined, siteOf(node, st), st, ctx);
|
|
@@ -42359,9 +42455,24 @@ function dispatch(invocation, outer, ctx) {
|
|
|
42359
42455
|
const base = path4.basename(name);
|
|
42360
42456
|
const rest = argv.slice(1);
|
|
42361
42457
|
const site = invocation.site;
|
|
42362
|
-
const
|
|
42363
|
-
if (
|
|
42364
|
-
|
|
42458
|
+
const callee = outer.functions.get(base);
|
|
42459
|
+
if (callee !== undefined) {
|
|
42460
|
+
const [only] = callee;
|
|
42461
|
+
if (callee.length === 1 && only !== undefined) {
|
|
42462
|
+
runFunction(base, only, rest, outer, ctx);
|
|
42463
|
+
return;
|
|
42464
|
+
}
|
|
42465
|
+
const runs = callee.map((definition) => {
|
|
42466
|
+
const run = clone2(outer);
|
|
42467
|
+
if (definition !== undefined)
|
|
42468
|
+
runFunction(base, definition, rest, run, ctx);
|
|
42469
|
+
else {
|
|
42470
|
+
run.functions.delete(base);
|
|
42471
|
+
dispatch(invocation, run, ctx);
|
|
42472
|
+
}
|
|
42473
|
+
return run;
|
|
42474
|
+
});
|
|
42475
|
+
merge2(outer, runs);
|
|
42365
42476
|
return;
|
|
42366
42477
|
}
|
|
42367
42478
|
const stdoutRedirected = invocation.redirects.some((redirect) => (redirect.fileDescriptor === undefined || redirect.fileDescriptor === 1) && [">", ">>", ">|", "&>", "&>>", ">&"].includes(redirect.operator));
|
|
@@ -42453,11 +42564,34 @@ function dispatch(invocation, outer, ctx) {
|
|
|
42453
42564
|
return;
|
|
42454
42565
|
}
|
|
42455
42566
|
case "trap": {
|
|
42456
|
-
const
|
|
42567
|
+
const [handler, ...conditions] = operands(rest, "").operands;
|
|
42568
|
+
if (handler === undefined)
|
|
42569
|
+
return;
|
|
42570
|
+
const text = literalText(handler.exp);
|
|
42571
|
+
const readable = conditions.map((arg) => literalText(arg.exp));
|
|
42572
|
+
const signals = readable.map((condition) => condition === undefined ? UNREADABLE_CONDITION : trapSignal(condition));
|
|
42573
|
+
const handled = signals.length === 0 ? ["EXIT"] : signals;
|
|
42574
|
+
const file2 = outer.script ?? outer.source;
|
|
42457
42575
|
if (text === "-") {
|
|
42458
|
-
|
|
42459
|
-
|
|
42460
|
-
|
|
42576
|
+
const named = readable.flatMap((condition) => condition === undefined ? [] : [trapSignal(condition)]);
|
|
42577
|
+
outer.traps = outer.traps.flatMap((trap) => {
|
|
42578
|
+
const signals2 = trap.signals.filter((signal) => !named.includes(signal));
|
|
42579
|
+
return signals2.length === 0 ? [] : [{ ...trap, signals: signals2 }];
|
|
42580
|
+
});
|
|
42581
|
+
} else if (text === undefined && handler.exp.every((piece) => piece.kind === "literal" || piece.descendantPid)) {
|
|
42582
|
+
const pids = new Map;
|
|
42583
|
+
const handlerText = handler.exp.map((piece) => {
|
|
42584
|
+
if (piece.kind === "literal")
|
|
42585
|
+
return piece.text;
|
|
42586
|
+
const name2 = `__legion_guard_trap_pid_${pids.size}`;
|
|
42587
|
+
pids.set(name2, [piece]);
|
|
42588
|
+
return `\${${name2}}`;
|
|
42589
|
+
}).join("");
|
|
42590
|
+
outer.traps = [...outer.traps, { text: handlerText, site, signals: handled, file: file2, pids }];
|
|
42591
|
+
} else if (text === undefined) {
|
|
42592
|
+
throw new Refusal(site.snippet, site.line, `the guard cannot read the handler this \`trap\` sets, \`${handler.text}\` (${handler.exp.find((piece) => piece.kind !== "literal")?.why ?? "a value the guard cannot know"}), so it cannot check what runs when this shell exits; write the handler out, or put it in a function`);
|
|
42593
|
+
} else if (!text.startsWith("-")) {
|
|
42594
|
+
outer.traps = [...outer.traps, { text, site, signals: handled, file: file2 }];
|
|
42461
42595
|
}
|
|
42462
42596
|
return;
|
|
42463
42597
|
}
|
|
@@ -43139,9 +43273,11 @@ function runFile(file2, positional, st, ctx, site, kind, source = false) {
|
|
|
43139
43273
|
throw new Refusal(site.snippet, site.line, "the guard stops following scripts this deep; run the inner script directly");
|
|
43140
43274
|
}
|
|
43141
43275
|
const child = { ...clone2(st), source: content, script: abs, depth: st.depth + 1 };
|
|
43142
|
-
|
|
43276
|
+
const operands2 = positional.map((arg) => arg.exp);
|
|
43277
|
+
child.positional = source && operands2.length === 0 ? st.positional : operands2;
|
|
43143
43278
|
walkScript(parse5(content), child, ctx, !source);
|
|
43144
43279
|
if (source) {
|
|
43280
|
+
st.positional = operands2.length === 0 ? child.positional : child.positional === operands2 ? st.positional : undefined;
|
|
43145
43281
|
st.vars = child.vars;
|
|
43146
43282
|
st.exported = child.exported;
|
|
43147
43283
|
st.arrays.clear();
|
|
@@ -285,9 +285,11 @@ later phase keeps it current rather than replacing it:
|
|
|
285
285
|
- Thread <id>: fixed in <commit-sha> — <one line>.
|
|
286
286
|
- Thread <id>: not a defect — <reason>.
|
|
287
287
|
`legion threads resolve --pr <n> --repo <owner>/<repo>` at <head-sha>:
|
|
288
|
-
resolved <thread URL>
|
|
288
|
+
resolved <thread URL> — its opener's acceptance
|
|
289
|
+
resolved <thread URL> — the Legion reviewer's acceptance of a bot's thread
|
|
289
290
|
left open <thread URL> — newest reply by <login> is not an acceptance
|
|
290
291
|
left open <thread URL> — newest reply by <login> is an unsubmitted draft in a pending review
|
|
292
|
+
left open <thread URL> — newest reply by <login> is not its opener's or the Legion reviewer's acceptance
|
|
291
293
|
|
|
292
294
|
**Thermo:** `ce-simplify-code` once at <head-sha>: <0 applied | applied → new head <sha>>; thermonuclear pair at the final head <sha>:
|
|
293
295
|
<verdict>. (omitted entirely on a docs-only PR — there is no code for either pass, so neither runs)
|
|
@@ -329,7 +331,8 @@ this proof.
|
|
|
329
331
|
|
|
330
332
|
- **Threads are dispositioned individually, never resolved in bulk.** Every open review
|
|
331
333
|
thread gets its own line naming the fixing commit or the reason it isn't a defect. The
|
|
332
|
-
reviewer answers each thread it opened
|
|
334
|
+
reviewer answers each thread it opened, and each thread a bot opened that is none of Legion's
|
|
335
|
+
role Apps, with exactly one of `Accepted: fixed in <commit> — <one line>`,
|
|
333
336
|
`Accepted: not a defect — <reason>`, or `Still open: <what remains>`; nothing else is an
|
|
334
337
|
acceptance, and nobody replies after an `Accepted:` (any later reply that is not itself an
|
|
335
338
|
`Accepted:` — the opener's own follow-up included — leaves the thread open, because resolution
|
|
@@ -341,9 +344,16 @@ this proof.
|
|
|
341
344
|
In a Legion pane, the **implementer** runs the command before every push that answers a review
|
|
342
345
|
(the corrective push and the final `.legion/` deletion push) and pastes its output into the
|
|
343
346
|
`Threads` section. The command resolves each unresolved thread whose newest submitted comment is
|
|
344
|
-
the opener's own `Accepted:` reply
|
|
345
|
-
|
|
346
|
-
|
|
347
|
+
the opener's own `Accepted:` reply. On a thread a bot account opened that is none of Legion's
|
|
348
|
+
role Apps (the daemon names them, keyed by App role), the Legion reviewer's `Accepted:` also
|
|
349
|
+
closes it. GitHub cannot tell a CI bot, which never accepts, from a person whose `gh` is routed
|
|
350
|
+
to an App, so the reviewer adjudicates such a finding, and it may accept one an App-routed person
|
|
351
|
+
raised. The subject of a finding never closes it: the implementer's `Fixed in <commit>: …` or
|
|
352
|
+
`Declined: …` answers a thread and closes none. A thread either Legion App opened, a reviewer's
|
|
353
|
+
finding included, still needs its opener's `Accepted:`. It makes one `resolveReviewThread` per
|
|
354
|
+
thread, prints `resolved <url> — <whose acceptance>` (its opener's, or the Legion reviewer's on a
|
|
355
|
+
bot's thread, so the ledger shows which) or `left open <url> — newest reply by <login> is …`
|
|
356
|
+
naming why, and exits 1 naming the thread's URL and GitHub's message when GitHub refuses one.
|
|
347
357
|
|
|
348
358
|
Without a grant, page through `reviewThreads`, skip `isResolved: true`, and compare the opener
|
|
349
359
|
with the newest comment. Query shape, inside `repository { pullRequest { … } }`:
|
|
@@ -353,15 +363,18 @@ this proof.
|
|
|
353
363
|
pageInfo { hasNextPage endCursor }
|
|
354
364
|
nodes {
|
|
355
365
|
id isResolved
|
|
356
|
-
opener: comments(first: 1) { nodes { author { login } } }
|
|
357
|
-
newest: comments(last: 1) { nodes { author { login } body state } }
|
|
366
|
+
opener: comments(first: 1) { nodes { author { __typename login } } }
|
|
367
|
+
newest: comments(last: 1) { nodes { author { __typename login } body state } }
|
|
358
368
|
}
|
|
359
369
|
}
|
|
360
370
|
```
|
|
361
371
|
|
|
362
|
-
Resolve only when the newest comment is submitted, its `author
|
|
363
|
-
and
|
|
364
|
-
|
|
372
|
+
Resolve only when the newest comment is submitted, its `author` is the opener's account (the same
|
|
373
|
+
`__typename` and `login`: a login alone is a string anyone may register), and its `body`, after
|
|
374
|
+
removing leading spaces, tabs, CR, and LF, begins `Accepted:`. Without a
|
|
375
|
+
grant nothing names Legion's own App logins, so this route closes a bot's thread only on its
|
|
376
|
+
opener's `Accepted:`: leave one the Legion reviewer accepted for the implementer's or merger's
|
|
377
|
+
run in a pane, or report it. For each thread to resolve:
|
|
365
378
|
|
|
366
379
|
```graphql
|
|
367
380
|
mutation($threadId: ID!) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjawhar/pi-legion-envoy",
|
|
3
|
-
"version": "5.16.
|
|
3
|
+
"version": "5.16.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"omp": {
|
|
6
6
|
"extensions": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"legion": {
|
|
15
15
|
"daemonApiVersion": 9,
|
|
16
|
-
"goDaemonApiVersion":
|
|
16
|
+
"goDaemonApiVersion": 11
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|