@codyswann/lisa 4.70.4 → 4.71.1
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/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +174 -0
- package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +279 -0
- package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +17 -7
- package/all/copy-overwrite/scripts/lib/github-attestation-provider.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-recovery.mjs +283 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-verifier.mjs +244 -0
- package/all/copy-overwrite/scripts/lib/npm-update-allocate.mjs +289 -0
- package/all/copy-overwrite/scripts/lib/npm-update-authorization.mjs +235 -0
- package/all/copy-overwrite/scripts/lib/npm-update-broker-client.mjs +92 -0
- package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-broker.mjs +234 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-factory.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-recipe.mjs +135 -0
- package/all/copy-overwrite/scripts/lib/npm-update-execution-adapter.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-hooks.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-install.mjs +267 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-proof.mjs +99 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +300 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-dispatch.mjs +182 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-grants.mjs +156 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-requests.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-graph.mjs +238 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +126 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-installation.mjs +116 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-preload.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-provider.mjs +207 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-read-client.mjs +164 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +300 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +157 -0
- package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +28 -0
- package/all/copy-overwrite/scripts/lib/npm-update-isolation.mjs +11 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf-contract.mjs +171 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf.mjs +194 -0
- package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +155 -0
- package/all/copy-overwrite/scripts/lib/npm-update-npm.mjs +190 -0
- package/all/copy-overwrite/scripts/lib/npm-update-object.mjs +112 -0
- package/all/copy-overwrite/scripts/lib/npm-update-orchestrator.mjs +273 -0
- package/all/copy-overwrite/scripts/lib/npm-update-owner.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-prepare.mjs +263 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process-core.mjs +179 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process.mjs +16 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publication.mjs +258 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +254 -0
- package/all/copy-overwrite/scripts/lib/npm-update-quality.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +264 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-archive.mjs +204 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-graph.mjs +185 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-transport.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime.mjs +284 -0
- package/all/copy-overwrite/scripts/lib/npm-update-tool-launcher.mjs +287 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-environment.mjs +161 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-inspection.mjs +142 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-lifecycle.mjs +256 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-policy.mjs +266 -0
- package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +230 -36
- package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +161 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +89 -5
- package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +204 -0
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +48 -123
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +107 -41
- package/all/copy-overwrite/scripts/npm-updater-gate-runtime.json +1 -0
- package/all/copy-overwrite/scripts/npm-updater-gate-supervisor.c +121 -0
- package/all/copy-overwrite/scripts/npm-updater-gate.Dockerfile +49 -0
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +1085 -0
- package/dist/codex/scripts/block-no-verify.sh +89 -5
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +604 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +159 -11
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +89 -5
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/lisa/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/block-no-verify.agy.sh +89 -5
- package/plugins/src/base/hooks/block-no-verify.sh +89 -5
- package/scripts/check-shipped-surface-removals.mjs +96 -24
- package/scripts/generate-npm-updater-helper-graph.mjs +184 -0
- package/scripts/two-channel-couplings.json +31 -7
- package/typescript/copy-contents/.husky/commit-msg +18 -0
- package/typescript/copy-overwrite/.prettierignore +2 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
3
|
+
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file lisa-npm-updater.mjs
|
|
7
|
+
* @description Four phase entry points consume bounded data from a committed trusted host policy.
|
|
8
|
+
* @module npm-updater
|
|
9
|
+
*/
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import { invokedAsScript } from "./lib/invoked-as-script.mjs";
|
|
12
|
+
import {
|
|
13
|
+
required,
|
|
14
|
+
UpdaterError,
|
|
15
|
+
keys,
|
|
16
|
+
validatePolicy,
|
|
17
|
+
validateProposal,
|
|
18
|
+
} from "./lib/npm-update-contract.mjs";
|
|
19
|
+
import {
|
|
20
|
+
readJson,
|
|
21
|
+
writeJson,
|
|
22
|
+
runProcess,
|
|
23
|
+
phaseDirectory,
|
|
24
|
+
} from "./lib/npm-update-process.mjs";
|
|
25
|
+
import { prepareUpdate } from "./lib/npm-update-prepare.mjs";
|
|
26
|
+
import { GitHub } from "./lib/npm-update-github.mjs";
|
|
27
|
+
import { allocateLeaf } from "./lib/npm-update-allocate.mjs";
|
|
28
|
+
import { publishProposal } from "./lib/npm-update-publish.mjs";
|
|
29
|
+
import {
|
|
30
|
+
prepareAuthorization,
|
|
31
|
+
finalizeAuthorization,
|
|
32
|
+
} from "./lib/npm-update-authorization.mjs";
|
|
33
|
+
import { gateProposal } from "./lib/npm-update-gate.mjs";
|
|
34
|
+
import { readGateProof } from "./lib/npm-update-gate-proof.mjs";
|
|
35
|
+
import { sha256 } from "./lib/github-attestation-verifier.mjs";
|
|
36
|
+
|
|
37
|
+
/** Only committed policy grants authority; ignored/local config cannot activate Actions. */
|
|
38
|
+
async function configuration(cwd) {
|
|
39
|
+
const result = await runProcess("git", ["show", "HEAD:.lisa.config.json"], {
|
|
40
|
+
cwd,
|
|
41
|
+
env: { PATH: process.env.PATH, HOME: "/nonexistent" },
|
|
42
|
+
maximum: 1_048_576,
|
|
43
|
+
});
|
|
44
|
+
const config = JSON.parse(result.stdout.toString());
|
|
45
|
+
required(
|
|
46
|
+
config.automationProvenance?.enabled === true,
|
|
47
|
+
"automation provenance must be explicitly configured on committed main"
|
|
48
|
+
);
|
|
49
|
+
const policy = validatePolicy(config.npmUpdater, config);
|
|
50
|
+
required(
|
|
51
|
+
config.automationProvenance.repository === policy.repository,
|
|
52
|
+
"committed authority repository differs"
|
|
53
|
+
);
|
|
54
|
+
return { config, policy };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Allocation creates its canonical leaf before previewing any attributed commit. */
|
|
58
|
+
async function allocatePhase(cwd, output, proposal, policy, config) {
|
|
59
|
+
const api = new GitHub(config.automationProvenance, process.env.GH_TOKEN);
|
|
60
|
+
const allocation = await allocateLeaf({
|
|
61
|
+
api,
|
|
62
|
+
proposal,
|
|
63
|
+
policy,
|
|
64
|
+
config,
|
|
65
|
+
cwd,
|
|
66
|
+
});
|
|
67
|
+
const result = await prepareAuthorization({
|
|
68
|
+
api,
|
|
69
|
+
cwd,
|
|
70
|
+
output,
|
|
71
|
+
proposal,
|
|
72
|
+
allocation,
|
|
73
|
+
policy,
|
|
74
|
+
config,
|
|
75
|
+
});
|
|
76
|
+
if (result.mode === "stale-outstanding")
|
|
77
|
+
writeJson(join(output, "authorization.json"), {
|
|
78
|
+
mode: result.mode,
|
|
79
|
+
workItem: allocation.workItem,
|
|
80
|
+
});
|
|
81
|
+
process.stdout.write(`${result.mode}\n`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Publication consumes the fixed gated object and proof slots without changing authority order. */
|
|
85
|
+
async function publishPhase(
|
|
86
|
+
cwd,
|
|
87
|
+
output,
|
|
88
|
+
proposal,
|
|
89
|
+
policy,
|
|
90
|
+
config,
|
|
91
|
+
allocation,
|
|
92
|
+
preview
|
|
93
|
+
) {
|
|
94
|
+
const gated = readJson(join(output, "gated.json"));
|
|
95
|
+
keys(gated, ["raw", "receipt"]);
|
|
96
|
+
required(
|
|
97
|
+
typeof gated.raw === "string" && gated.raw.length <= 90_000,
|
|
98
|
+
"bounded raw commit envelope required"
|
|
99
|
+
);
|
|
100
|
+
const api = new GitHub(config.automationProvenance, process.env.GH_TOKEN);
|
|
101
|
+
const result = await publishProposal({
|
|
102
|
+
api,
|
|
103
|
+
cwd,
|
|
104
|
+
proposal,
|
|
105
|
+
policy,
|
|
106
|
+
config,
|
|
107
|
+
allocation,
|
|
108
|
+
descriptor: preview.descriptor,
|
|
109
|
+
proof: {
|
|
110
|
+
descriptor: join(output, "descriptor.json"),
|
|
111
|
+
bundle: join(output, "bundle.json"),
|
|
112
|
+
recovery: join(output, "recovery.json"),
|
|
113
|
+
recoveryBundle: join(output, "recovery-bundle.json"),
|
|
114
|
+
},
|
|
115
|
+
receipt: gated.receipt,
|
|
116
|
+
raw: Buffer.from(gated.raw, "base64"),
|
|
117
|
+
});
|
|
118
|
+
writeJson(join(output, "published.json"), result);
|
|
119
|
+
process.stdout.write(`${result.status} ${result.url}\n`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Strict phase and fixed directory arguments are data, never a caller recipe. */
|
|
123
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
124
|
+
required(
|
|
125
|
+
argv.length === 3 &&
|
|
126
|
+
["prepare", "allocate", "checkpoint", "gate", "publish"].includes(
|
|
127
|
+
argv[0]
|
|
128
|
+
),
|
|
129
|
+
"usage: lisa-npm-updater.mjs <prepare|allocate|checkpoint|gate|publish> <owned-checkout> <private-phase-directory>"
|
|
130
|
+
);
|
|
131
|
+
const [phase, checkout, directory] = argv;
|
|
132
|
+
const cwd = resolve(checkout);
|
|
133
|
+
const output = resolve(directory);
|
|
134
|
+
const { config, policy } = await configuration(cwd);
|
|
135
|
+
phaseDirectory(output);
|
|
136
|
+
if (phase === "prepare") {
|
|
137
|
+
const result = await prepareUpdate({ cwd, policy, config });
|
|
138
|
+
writeJson(join(output, "prepare.json"), result);
|
|
139
|
+
if (result.status === "prepared")
|
|
140
|
+
writeJson(join(output, "proposal.json"), result.proposal);
|
|
141
|
+
process.stdout.write(`${result.status}\n`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const proposal = validateProposal(
|
|
145
|
+
readJson(join(output, "proposal.json")),
|
|
146
|
+
policy
|
|
147
|
+
);
|
|
148
|
+
if (phase === "allocate")
|
|
149
|
+
return allocatePhase(cwd, output, proposal, policy, config);
|
|
150
|
+
const allocation = readJson(join(output, "allocation.json"));
|
|
151
|
+
const preview = readJson(join(output, "preview.json"));
|
|
152
|
+
keys(preview, ["descriptor", "message", "epoch"]);
|
|
153
|
+
required(
|
|
154
|
+
sha256(preview.message) === preview.descriptor.messageSha256,
|
|
155
|
+
"preview final message differs"
|
|
156
|
+
);
|
|
157
|
+
if (phase === "checkpoint") {
|
|
158
|
+
const api = new GitHub(config.automationProvenance, process.env.GH_TOKEN);
|
|
159
|
+
const result = await finalizeAuthorization({
|
|
160
|
+
api,
|
|
161
|
+
output,
|
|
162
|
+
proposal,
|
|
163
|
+
allocation,
|
|
164
|
+
preview,
|
|
165
|
+
policy,
|
|
166
|
+
config,
|
|
167
|
+
});
|
|
168
|
+
process.stdout.write(`${result.mode} checkpoint complete\n`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (phase === "gate") {
|
|
172
|
+
const result = await gateProposal({
|
|
173
|
+
cwd,
|
|
174
|
+
proposal,
|
|
175
|
+
policy,
|
|
176
|
+
allocation,
|
|
177
|
+
preview,
|
|
178
|
+
...readGateProof(output),
|
|
179
|
+
token: process.env.GH_TOKEN,
|
|
180
|
+
config,
|
|
181
|
+
});
|
|
182
|
+
writeJson(join(output, "gated.json"), result);
|
|
183
|
+
process.stdout.write(`gated ${result.receipt.commit}\n`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
return publishPhase(
|
|
187
|
+
cwd,
|
|
188
|
+
output,
|
|
189
|
+
proposal,
|
|
190
|
+
policy,
|
|
191
|
+
config,
|
|
192
|
+
allocation,
|
|
193
|
+
preview
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (invokedAsScript(import.meta.url))
|
|
198
|
+
main().catch(error => {
|
|
199
|
+
// Never print candidate/provider payloads or child output that may contain credentials.
|
|
200
|
+
process.stderr.write(
|
|
201
|
+
`${error instanceof UpdaterError ? error.message : "npm updater failed: required policy, authorization, installation, gate or exact publication evidence was unavailable"}\n`
|
|
202
|
+
);
|
|
203
|
+
process.exitCode = 1;
|
|
204
|
+
});
|
|
@@ -41,15 +41,7 @@
|
|
|
41
41
|
* @module lisa-run-gates
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
|
-
import {
|
|
45
|
-
existsSync,
|
|
46
|
-
mkdirSync,
|
|
47
|
-
mkdtempSync,
|
|
48
|
-
readFileSync,
|
|
49
|
-
rmSync,
|
|
50
|
-
writeFileSync,
|
|
51
|
-
} from "node:fs";
|
|
52
|
-
import { tmpdir } from "node:os";
|
|
44
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
53
45
|
import { dirname, join, relative } from "node:path";
|
|
54
46
|
import { fileURLToPath } from "node:url";
|
|
55
47
|
|
|
@@ -323,20 +315,21 @@ function formatLine(state, gate, detail) {
|
|
|
323
315
|
/**
|
|
324
316
|
* Read an executor's answer, whichever of the two shapes it returned.
|
|
325
317
|
*
|
|
326
|
-
* An executor may answer with a bare exit code or with `{code, output}
|
|
327
|
-
*
|
|
328
|
-
*
|
|
318
|
+
* An executor may answer with a bare exit code or with `{code, output}` and an
|
|
319
|
+
* optional OS-observed signal. Both retain the actual exit status; output and
|
|
320
|
+
* signal identity only EXPLAIN a verdict already reached. An executor that
|
|
329
321
|
* cannot capture output — a stub, a Windows shell, a run with capture switched
|
|
330
322
|
* off — therefore loses the diagnosis and nothing else.
|
|
331
|
-
* @param {number|null|undefined|{code: number|null, output: string|null}} raw
|
|
323
|
+
* @param {number|null|undefined|{code: number|null, output: string|null, signal?: string}} raw
|
|
332
324
|
* Whatever the executor returned.
|
|
333
|
-
* @returns {{code: number|null, output: string|null}} The normalised answer.
|
|
325
|
+
* @returns {{code: number|null, output: string|null, signal?: string}} The normalised answer.
|
|
334
326
|
*/
|
|
335
327
|
function normaliseExec(raw) {
|
|
336
328
|
if (raw !== null && typeof raw === "object") {
|
|
337
329
|
return {
|
|
338
330
|
code: typeof raw.code === "number" ? raw.code : null,
|
|
339
331
|
output: typeof raw.output === "string" ? raw.output : null,
|
|
332
|
+
...(typeof raw.signal === "string" ? { signal: raw.signal } : {}),
|
|
340
333
|
};
|
|
341
334
|
}
|
|
342
335
|
return { code: typeof raw === "number" ? raw : null, output: null };
|
|
@@ -440,7 +433,7 @@ function stateFor(kind) {
|
|
|
440
433
|
}
|
|
441
434
|
|
|
442
435
|
function execute(gate, exec, declarations) {
|
|
443
|
-
const { code, output } = normaliseExec(exec(gate.command, gate));
|
|
436
|
+
const { code, output, signal } = normaliseExec(exec(gate.command, gate));
|
|
444
437
|
if (code === 0) {
|
|
445
438
|
// Exit 0 is ADJACENT to "the property holds"; it is not the observation.
|
|
446
439
|
// A command that exited 0 while its own output says it collected zero
|
|
@@ -472,14 +465,14 @@ function execute(gate, exec, declarations) {
|
|
|
472
465
|
};
|
|
473
466
|
}
|
|
474
467
|
const shown = typeof code === "number" ? code : "terminated";
|
|
475
|
-
// The
|
|
476
|
-
//
|
|
477
|
-
//
|
|
468
|
+
// The actual status and signal go in as well as the output: a native kill
|
|
469
|
+
// retains null status, while a shell's `exit 143` is `128 + 15`. Either can
|
|
470
|
+
// arrive carrying a truncated transcript that
|
|
478
471
|
// reads exactly like a real gate failure — which is how one `exit 1` came to
|
|
479
472
|
// have three distinct causes with re-running a rational response to all of
|
|
480
473
|
// them (CodySwannGT/lisa#2897).
|
|
481
474
|
const diagnosis = withDeclarations(
|
|
482
|
-
diagnoseFailure(output, code),
|
|
475
|
+
diagnoseFailure(output, code, undefined, undefined, undefined, signal),
|
|
483
476
|
output,
|
|
484
477
|
declarations
|
|
485
478
|
);
|
|
@@ -1141,12 +1134,6 @@ function readPackageScripts(cwd) {
|
|
|
1141
1134
|
}
|
|
1142
1135
|
}
|
|
1143
1136
|
|
|
1144
|
-
/**
|
|
1145
|
-
* How much of a gate's output is kept for diagnosis. A suite of 14,000 tests
|
|
1146
|
-
* prints megabytes; the signatures that matter are all near the end.
|
|
1147
|
-
*/
|
|
1148
|
-
const CAPTURE_TAIL_BYTES = 512 * 1024;
|
|
1149
|
-
|
|
1150
1137
|
/**
|
|
1151
1138
|
* Hang-detector deadline for one gate command, in milliseconds.
|
|
1152
1139
|
*
|
|
@@ -1218,7 +1205,7 @@ function runInterruption() {
|
|
|
1218
1205
|
*/
|
|
1219
1206
|
function runProcessTree(command, options = {}) {
|
|
1220
1207
|
const timeout = options.timeout ?? GATE_COMMAND_BUDGET_MS;
|
|
1221
|
-
const { shell: _shell, ...childOptions } = options;
|
|
1208
|
+
const { shell: _shell, capture = false, ...childOptions } = options;
|
|
1222
1209
|
// The supervisor already ends itself when THIS process dies; naming the
|
|
1223
1210
|
// process above it is what covers the measured incident, where the hook was
|
|
1224
1211
|
// killed and the runner — not the supervisor — was the orphan that kept the
|
|
@@ -1228,7 +1215,14 @@ function runProcessTree(command, options = {}) {
|
|
|
1228
1215
|
: [];
|
|
1229
1216
|
return boundedSpawnSync(
|
|
1230
1217
|
process.execPath,
|
|
1231
|
-
[
|
|
1218
|
+
[
|
|
1219
|
+
PROCESS_TREE_RUNNER,
|
|
1220
|
+
`--timeout-ms=${timeout}`,
|
|
1221
|
+
...watch,
|
|
1222
|
+
...(capture ? ["--capture-fd=3"] : []),
|
|
1223
|
+
"--",
|
|
1224
|
+
command,
|
|
1225
|
+
],
|
|
1232
1226
|
{
|
|
1233
1227
|
...childOptions,
|
|
1234
1228
|
timeout: timeout + PROCESS_TREE_REAP_BUDGET_MS,
|
|
@@ -1260,7 +1254,7 @@ function killedOrRethrow(error) {
|
|
|
1260
1254
|
* everything about capture is additive: if capture is unavailable or refused,
|
|
1261
1255
|
* the exit code is produced by exactly the code path that produced it before.
|
|
1262
1256
|
* @param {string} command The command line to run.
|
|
1263
|
-
* @returns {{code: number|null, output: null}} Exit code; null when killed.
|
|
1257
|
+
* @returns {{code: number|null, output: null, signal?: string}} Exit code; null when killed.
|
|
1264
1258
|
*/
|
|
1265
1259
|
function plainExec(command) {
|
|
1266
1260
|
try {
|
|
@@ -1271,117 +1265,48 @@ function plainExec(command) {
|
|
|
1271
1265
|
// Still reachable: a non-timeout `spawnSync` failure arrives on the result,
|
|
1272
1266
|
// and it means nothing ran, which is the same "no verdict" answer.
|
|
1273
1267
|
if (child.error) return { code: null, output: null };
|
|
1274
|
-
return {
|
|
1268
|
+
return {
|
|
1269
|
+
code: child.status,
|
|
1270
|
+
output: null,
|
|
1271
|
+
...(child.signal ? { signal: child.signal } : {}),
|
|
1272
|
+
};
|
|
1275
1273
|
} catch (error) {
|
|
1276
1274
|
return killedOrRethrow(error);
|
|
1277
1275
|
}
|
|
1278
1276
|
}
|
|
1279
1277
|
|
|
1280
1278
|
/**
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1284
|
-
*
|
|
1285
|
-
*
|
|
1286
|
-
*
|
|
1287
|
-
*
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
// The one child in this file that is NOT a project gate command, so it takes
|
|
1292
|
-
// the shared default rather than the two-hour ceiling: `command -v tee`
|
|
1293
|
-
// answers immediately or the shell is broken.
|
|
1294
|
-
try {
|
|
1295
|
-
const probe = boundedSpawnSync("sh", ["-c", "command -v tee"], {
|
|
1296
|
-
stdio: "ignore",
|
|
1297
|
-
});
|
|
1298
|
-
return !probe.error && probe.status === 0;
|
|
1299
|
-
} catch (error) {
|
|
1300
|
-
// A probe killed at its deadline has not confirmed anything, and this
|
|
1301
|
-
// function's whole contract is that an unconfirmed capability goes unused.
|
|
1302
|
-
// Letting the throw escape would convert "cannot tell" into a crash, and
|
|
1303
|
-
// the fallback path it guards answers the question perfectly well.
|
|
1304
|
-
if (isChildTimeout(error)) return false;
|
|
1305
|
-
throw error;
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
/**
|
|
1310
|
-
* Read back what the wrapper recorded, keeping only the tail of the log.
|
|
1311
|
-
* @param {string} statusPath File the wrapper wrote the exit code into.
|
|
1312
|
-
* @param {string} logPath File the wrapper tee'd the output into.
|
|
1313
|
-
* @returns {{code: number|null, output: string|null}} The recorded answer.
|
|
1314
|
-
*/
|
|
1315
|
-
function readCaptured(statusPath, logPath) {
|
|
1316
|
-
let output = null;
|
|
1317
|
-
try {
|
|
1318
|
-
output = readFileSync(logPath, "utf8").slice(-CAPTURE_TAIL_BYTES);
|
|
1319
|
-
} catch {
|
|
1320
|
-
output = null;
|
|
1321
|
-
}
|
|
1322
|
-
try {
|
|
1323
|
-
const code = Number.parseInt(readFileSync(statusPath, "utf8").trim(), 10);
|
|
1324
|
-
// Fail closed. An unreadable status is not a zero: the one thing this
|
|
1325
|
-
// runner may never do is turn "I do not know" into "it passed".
|
|
1326
|
-
return { code: Number.isInteger(code) ? code : null, output };
|
|
1327
|
-
} catch {
|
|
1328
|
-
return { code: null, output };
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
/**
|
|
1333
|
-
* The default executor: run the command in a shell, streaming AND recording.
|
|
1334
|
-
*
|
|
1335
|
-
* stdio is inherited so a failing gate's own output reaches the operator
|
|
1336
|
-
* unaltered and as it happens — a push gate runs for minutes and an operator
|
|
1337
|
-
* needs to watch it, not receive it in a lump at the end. `tee` is what lets
|
|
1338
|
-
* both be true: the operator sees the stream, and the runner keeps a copy to
|
|
1339
|
-
* say WHICH failure it was rather than only that there was one.
|
|
1340
|
-
*
|
|
1341
|
-
* The price is that the command's stdout is a pipe rather than a terminal, so
|
|
1342
|
-
* tools that colourise or animate for a TTY print their plain form. That is a
|
|
1343
|
-
* deliberate trade: a plain, diagnosable failure beats a coloured, mute one.
|
|
1344
|
-
* `LISA_GATES_CAPTURE=0` buys the colour back at the cost of the diagnosis.
|
|
1345
|
-
*
|
|
1346
|
-
* The exit code comes from a status file written INSIDE the pipeline, never
|
|
1347
|
-
* from the pipeline itself — a pipeline reports `tee`'s status, which is
|
|
1348
|
-
* almost always zero, and reading it would report every failing gate as
|
|
1349
|
-
* passing. That is the single most dangerous mistake available here.
|
|
1350
|
-
* @param {string} command The command line to run.
|
|
1351
|
-
* @returns {{code: number|null, output: string|null}} Exit code and output.
|
|
1279
|
+
* Run a gate with live output and a supervisor-owned diagnostic tail.
|
|
1280
|
+
*
|
|
1281
|
+
* The extra pipe belongs only to the supervisor and its caller: the command
|
|
1282
|
+
* inherits stdin and two output pipes, never the diagnostic descriptor. No
|
|
1283
|
+
* child-writable status record participates in the verdict. Capture failures
|
|
1284
|
+
* retain the supervised OS result and never execute the command a second time.
|
|
1285
|
+
* Windows retains its existing native-job/plain route. Capture remains opt-out
|
|
1286
|
+
* for commands that require inherited terminal handles.
|
|
1287
|
+
* @param {string} command Shell source to run once.
|
|
1288
|
+
* @returns {{code: number|null, output: string|null, signal?: string}} OS status and diagnostic tail.
|
|
1352
1289
|
*/
|
|
1353
1290
|
export function spawnExec(command) {
|
|
1354
|
-
if (
|
|
1355
|
-
let dir;
|
|
1356
|
-
try {
|
|
1357
|
-
dir = mkdtempSync(join(tmpdir(), "lisa-gate-run-"));
|
|
1358
|
-
} catch {
|
|
1291
|
+
if (process.env.LISA_GATES_CAPTURE === "0" || process.platform === "win32") {
|
|
1359
1292
|
return plainExec(command);
|
|
1360
1293
|
}
|
|
1361
|
-
const logPath = join(dir, "output.log");
|
|
1362
|
-
const statusPath = join(dir, "status");
|
|
1363
|
-
// Newline-separated inside a group, so a command ending in a trailing
|
|
1364
|
-
// comment or redirection still has `echo` run as its own statement.
|
|
1365
|
-
// Paths travel as data, never as shell source. A TMPDIR containing a quote,
|
|
1366
|
-
// newline, dollar sign, or command substitution must not be able to break
|
|
1367
|
-
// this wrapper or execute content merely because mkdtemp inherited it.
|
|
1368
|
-
const script = `{\n${command}\necho $? > "$LISA_GATE_STATUS_PATH"\n} 2>&1 | tee "$LISA_GATE_LOG_PATH"\n`;
|
|
1369
1294
|
try {
|
|
1370
|
-
const child = runProcessTree(
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
LISA_GATE_LOG_PATH: logPath,
|
|
1374
|
-
LISA_GATE_STATUS_PATH: statusPath,
|
|
1375
|
-
},
|
|
1376
|
-
stdio: "inherit",
|
|
1295
|
+
const child = runProcessTree(command, {
|
|
1296
|
+
capture: true,
|
|
1297
|
+
stdio: ["inherit", "inherit", "inherit", "pipe"],
|
|
1377
1298
|
timeout: GATE_COMMAND_BUDGET_MS,
|
|
1378
1299
|
});
|
|
1379
1300
|
if (child.error) return { code: null, output: null };
|
|
1380
|
-
|
|
1301
|
+
const captured = child.output?.[3];
|
|
1302
|
+
return {
|
|
1303
|
+
code: child.status,
|
|
1304
|
+
...(child.signal ? { signal: child.signal } : {}),
|
|
1305
|
+
output:
|
|
1306
|
+
captured === null || captured === undefined ? null : String(captured),
|
|
1307
|
+
};
|
|
1381
1308
|
} catch (error) {
|
|
1382
1309
|
return killedOrRethrow(error);
|
|
1383
|
-
} finally {
|
|
1384
|
-
rmSync(dir, { force: true, recursive: true });
|
|
1385
1310
|
}
|
|
1386
1311
|
}
|
|
1387
1312
|
|
|
@@ -1016,7 +1016,7 @@ export function deployBranchEnvironments(config) {
|
|
|
1016
1016
|
return new Map([[branch, PRODUCTION]]);
|
|
1017
1017
|
}
|
|
1018
1018
|
|
|
1019
|
-
function trackerContract(config = readConfig()) {
|
|
1019
|
+
export function trackerContract(config = readConfig()) {
|
|
1020
1020
|
const provider = requireString(config.tracker, "tracker").toLowerCase();
|
|
1021
1021
|
const identityRepo = currentRepoIdentity(config);
|
|
1022
1022
|
if (provider === "github") {
|
|
@@ -1483,6 +1483,38 @@ export function soleWorkItem(text, contract, subject) {
|
|
|
1483
1483
|
return refs[0];
|
|
1484
1484
|
}
|
|
1485
1485
|
|
|
1486
|
+
/**
|
|
1487
|
+
* Resolve a required binding through the authoritative tracker contract.
|
|
1488
|
+
* Consumers supplying trusted committed configuration avoid reparsing trailers
|
|
1489
|
+
* or treating raw `current` output as a checked branch/queue identity.
|
|
1490
|
+
* This does not change ordinary offline/live validation semantics.
|
|
1491
|
+
* @param {string} message Complete final commit message.
|
|
1492
|
+
* @param {object} [options] Trusted configuration and strict live requirement.
|
|
1493
|
+
* @returns {object} Canonical identity and optional actual provider evidence.
|
|
1494
|
+
*/
|
|
1495
|
+
export function resolveWorkItemContext(message, options = {}) {
|
|
1496
|
+
const contract = trackerContract(options.config ?? readConfig());
|
|
1497
|
+
const state = readState(false);
|
|
1498
|
+
const ref = soleWorkItem(message, contract, COMMIT_SUBJECT);
|
|
1499
|
+
assertStateBranch(state);
|
|
1500
|
+
assertStateMatches(ref, contract);
|
|
1501
|
+
if (options.requireLive && contract.provider !== "github") {
|
|
1502
|
+
throw new TrackingError("Automation provenance requires GitHub tracking");
|
|
1503
|
+
}
|
|
1504
|
+
const issue = options.requireLive
|
|
1505
|
+
? githubIssue(ref, contract, options.execute ?? run)
|
|
1506
|
+
: undefined;
|
|
1507
|
+
return {
|
|
1508
|
+
ref,
|
|
1509
|
+
provider: contract.provider,
|
|
1510
|
+
repository: contract.repository,
|
|
1511
|
+
identityRepo: contract.identityRepo,
|
|
1512
|
+
branch: state.branch,
|
|
1513
|
+
lifecycle: contract.lifecycle,
|
|
1514
|
+
issue,
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1486
1518
|
/**
|
|
1487
1519
|
* The one work item a COMMIT message names.
|
|
1488
1520
|
* @param {string} message Commit message.
|
|
@@ -1843,28 +1875,34 @@ function typeFromLabels(labels) {
|
|
|
1843
1875
|
* @param {string|number} number GitHub issue number.
|
|
1844
1876
|
* @returns {string[]} State of every sub-issue.
|
|
1845
1877
|
*/
|
|
1846
|
-
|
|
1878
|
+
/** Literal typed GraphQL fields are shared by the canonical reader and closed native transport. */
|
|
1879
|
+
export function githubHierarchyArgs(contract, number, after) {
|
|
1847
1880
|
const [owner, repo] = contract.repository.split("/");
|
|
1848
1881
|
const query =
|
|
1849
1882
|
"query($owner:String!,$repo:String!,$number:Int!,$after:String){repository(owner:$owner,name:$repo){issue(number:$number){subIssues(first:100,after:$after){nodes{state}pageInfo{hasNextPage endCursor}}}}}";
|
|
1883
|
+
const args = [
|
|
1884
|
+
"api",
|
|
1885
|
+
"graphql",
|
|
1886
|
+
"-f",
|
|
1887
|
+
`query=${query}`,
|
|
1888
|
+
"-F",
|
|
1889
|
+
`owner=${owner}`,
|
|
1890
|
+
"-F",
|
|
1891
|
+
`repo=${repo}`,
|
|
1892
|
+
"-F",
|
|
1893
|
+
`number=${number}`,
|
|
1894
|
+
];
|
|
1895
|
+
if (after) args.push("-F", `after=${after}`);
|
|
1896
|
+
return args;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
function githubHierarchy(ref, contract, number, execute = run) {
|
|
1850
1900
|
const states = [];
|
|
1851
1901
|
const cursor = { after: null };
|
|
1852
1902
|
|
|
1853
1903
|
do {
|
|
1854
|
-
const args =
|
|
1855
|
-
|
|
1856
|
-
"graphql",
|
|
1857
|
-
"-f",
|
|
1858
|
-
`query=${query}`,
|
|
1859
|
-
"-F",
|
|
1860
|
-
`owner=${owner}`,
|
|
1861
|
-
"-F",
|
|
1862
|
-
`repo=${repo}`,
|
|
1863
|
-
"-F",
|
|
1864
|
-
`number=${number}`,
|
|
1865
|
-
];
|
|
1866
|
-
if (cursor.after) args.push("-F", `after=${cursor.after}`);
|
|
1867
|
-
const result = run("gh", args, { allowFailure: true });
|
|
1904
|
+
const args = githubHierarchyArgs(contract, number, cursor.after);
|
|
1905
|
+
const result = execute("gh", args, { allowFailure: true });
|
|
1868
1906
|
if (result.status !== 0) throw githubFailure(result, ref);
|
|
1869
1907
|
|
|
1870
1908
|
const response = safeJson(result.stdout, `GitHub issue ${ref} hierarchy`);
|
|
@@ -1952,20 +1990,25 @@ export function resetGhVersionCheck() {
|
|
|
1952
1990
|
ghVersionChecked = false;
|
|
1953
1991
|
}
|
|
1954
1992
|
|
|
1955
|
-
|
|
1956
|
-
|
|
1993
|
+
/** The body is required by recovery; all ordinary canonical validation fields remain present. */
|
|
1994
|
+
export function githubIssueViewArgs(repository, number) {
|
|
1995
|
+
return [
|
|
1996
|
+
"issue",
|
|
1997
|
+
"view",
|
|
1998
|
+
number,
|
|
1999
|
+
"--repo",
|
|
2000
|
+
repository,
|
|
2001
|
+
"--json",
|
|
2002
|
+
"number,url,state,body,labels,comments,closedByPullRequestsReferences",
|
|
2003
|
+
];
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
function githubIssue(ref, contract, execute = run) {
|
|
2007
|
+
assertGhVersion(execute);
|
|
1957
2008
|
const number = ref.slice(ref.lastIndexOf("#") + 1);
|
|
1958
|
-
const result =
|
|
2009
|
+
const result = execute(
|
|
1959
2010
|
"gh",
|
|
1960
|
-
|
|
1961
|
-
"issue",
|
|
1962
|
-
"view",
|
|
1963
|
-
number,
|
|
1964
|
-
"--repo",
|
|
1965
|
-
contract.repository,
|
|
1966
|
-
"--json",
|
|
1967
|
-
"number,url,state,labels,comments,closedByPullRequestsReferences",
|
|
1968
|
-
],
|
|
2011
|
+
githubIssueViewArgs(contract.repository, number),
|
|
1969
2012
|
{
|
|
1970
2013
|
allowFailure: true,
|
|
1971
2014
|
}
|
|
@@ -1985,7 +2028,7 @@ function githubIssue(ref, contract) {
|
|
|
1985
2028
|
assertLeaf(
|
|
1986
2029
|
ref,
|
|
1987
2030
|
typeFromLabels(issue.labels),
|
|
1988
|
-
githubHierarchy(ref, contract, number)
|
|
2031
|
+
githubHierarchy(ref, contract, number, execute)
|
|
1989
2032
|
);
|
|
1990
2033
|
return issue;
|
|
1991
2034
|
}
|
|
@@ -2410,7 +2453,7 @@ export function textContainsBacklink(value, prUrl) {
|
|
|
2410
2453
|
* @param {string} prUrl Pull request URL.
|
|
2411
2454
|
* @returns {string} The comment body.
|
|
2412
2455
|
*/
|
|
2413
|
-
function backlinkBody(prUrl) {
|
|
2456
|
+
export function backlinkBody(prUrl) {
|
|
2414
2457
|
return `${MARKER} ${prUrl}`;
|
|
2415
2458
|
}
|
|
2416
2459
|
|
|
@@ -2502,7 +2545,7 @@ function managedBacklinkTarget(body) {
|
|
|
2502
2545
|
* @returns {{mine: unknown, others: number}} This PR's comment, and how many
|
|
2503
2546
|
* managed comments belong to OTHER pull requests.
|
|
2504
2547
|
*/
|
|
2505
|
-
function partitionBacklinks(comments, prUrl, bodyOf) {
|
|
2548
|
+
export function partitionBacklinks(comments, prUrl, bodyOf) {
|
|
2506
2549
|
const mine = [];
|
|
2507
2550
|
let others = 0;
|
|
2508
2551
|
for (const comment of comments) {
|
|
@@ -2538,6 +2581,33 @@ export function backlinkReport(outcome, ref, prUrl) {
|
|
|
2538
2581
|
);
|
|
2539
2582
|
}
|
|
2540
2583
|
|
|
2584
|
+
/** Complete canonical pagination is shared by the writer and its scoped controller. */
|
|
2585
|
+
export function githubBacklinkListArgs(repository, number) {
|
|
2586
|
+
if (
|
|
2587
|
+
!/^[\w.-]+\/[\w.-]+$/.test(repository) ||
|
|
2588
|
+
!/^[1-9]\d*$/.test(String(number))
|
|
2589
|
+
)
|
|
2590
|
+
throw new Error("invalid canonical GitHub backlink repository/issue");
|
|
2591
|
+
return [
|
|
2592
|
+
"api",
|
|
2593
|
+
"--paginate",
|
|
2594
|
+
"--slurp",
|
|
2595
|
+
`repos/${repository}/issues/${number}/comments?per_page=100`,
|
|
2596
|
+
];
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
/** Complete slurped pages remain bounded and are shared by the canonical writer and controller grant. */
|
|
2600
|
+
export function githubBacklinkComments(pages) {
|
|
2601
|
+
if (
|
|
2602
|
+
!Array.isArray(pages) ||
|
|
2603
|
+
pages.length < 1 ||
|
|
2604
|
+
pages.length > 100 ||
|
|
2605
|
+
pages.some(page => !Array.isArray(page) || page.length > 100)
|
|
2606
|
+
)
|
|
2607
|
+
throw new Error("invalid or over-bound GitHub backlink pages");
|
|
2608
|
+
return pages.flat();
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2541
2611
|
/**
|
|
2542
2612
|
* Establish the backlink on a GitHub issue.
|
|
2543
2613
|
* @param {string} ref Canonical `owner/repo#number` reference.
|
|
@@ -2547,17 +2617,13 @@ export function backlinkReport(outcome, ref, prUrl) {
|
|
|
2547
2617
|
*/
|
|
2548
2618
|
function githubBacklink(ref, prUrl) {
|
|
2549
2619
|
const [repository, number] = ref.split("#");
|
|
2550
|
-
const listing = run(
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
"api",
|
|
2554
|
-
"--paginate",
|
|
2555
|
-
`repos/${repository}/issues/${number}/comments?per_page=100`,
|
|
2556
|
-
],
|
|
2557
|
-
{ allowFailure: true }
|
|
2558
|
-
);
|
|
2620
|
+
const listing = run("gh", githubBacklinkListArgs(repository, number), {
|
|
2621
|
+
allowFailure: true,
|
|
2622
|
+
});
|
|
2559
2623
|
if (listing.status !== 0) throw githubFailure(listing, ref);
|
|
2560
|
-
const comments =
|
|
2624
|
+
const comments = githubBacklinkComments(
|
|
2625
|
+
safeJson(listing.stdout, `GitHub comments on ${ref}`)
|
|
2626
|
+
);
|
|
2561
2627
|
// This listing is the read-before-write, and it is fetched here rather than
|
|
2562
2628
|
// passed in for exactly that reason: the version you last wrote is not the
|
|
2563
2629
|
// version that is live on a surface other agents also comment on.
|