@codyswann/lisa 2.331.3 → 2.332.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/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +5 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
- package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -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/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
- 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/skills/lisa-secrets-access/scripts/validate-config.mjs +47 -14
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +40 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs +31 -1
package/package.json
CHANGED
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"brace-expansion": ">=5.0.9"
|
|
116
116
|
},
|
|
117
117
|
"name": "@codyswann/lisa",
|
|
118
|
-
"version": "2.
|
|
118
|
+
"version": "2.332.1",
|
|
119
119
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
120
120
|
"main": "dist/index.js",
|
|
121
121
|
"exports": {
|
|
@@ -76,13 +76,37 @@ function bindingsFor(surface) {
|
|
|
76
76
|
/**
|
|
77
77
|
* Install methods the toolchain runner supports.
|
|
78
78
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
79
|
+
* This is a deliberate copy of `INSTALL_METHODS` in `toolchain.mjs`, which is
|
|
80
|
+
* the list that actually decides. Skills are distributed as self-contained
|
|
81
|
+
* directories and nothing imports across a skill boundary, so importing the
|
|
82
|
+
* authority here would couple two skills' distribution for one array.
|
|
83
|
+
*
|
|
84
|
+
* The copy has drifted twice. First `release-tar` was missing while the runner
|
|
85
|
+
* had supported it for as long as gh has been pinned. Then `release-tree` and
|
|
86
|
+
* `release-binary` were both missing, which rejected a valid `jq` pin. Each
|
|
87
|
+
* time the only safeguard was a comment asking the next author to remember.
|
|
88
|
+
* A conformance test now compares the two sets directly, so adding a kind on
|
|
89
|
+
* either side fails until both know it.
|
|
84
90
|
*/
|
|
85
|
-
const INSTALL_METHODS = new Set([
|
|
91
|
+
const INSTALL_METHODS = new Set([
|
|
92
|
+
"release-zip",
|
|
93
|
+
"release-tar",
|
|
94
|
+
"release-tree",
|
|
95
|
+
"release-binary",
|
|
96
|
+
"npm-global",
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Download kinds — every method that fetches a URL, so every one that needs a
|
|
101
|
+
* checksum. Spelled as "not npm-global" rather than as a list, because the next
|
|
102
|
+
* download kind should inherit the obligation by default; the drift above came
|
|
103
|
+
* from lists that had to be remembered.
|
|
104
|
+
* @param {string} method An install method.
|
|
105
|
+
* @returns {boolean} Whether the method fetches a pinned artifact.
|
|
106
|
+
*/
|
|
107
|
+
function isDownloadKind(method) {
|
|
108
|
+
return INSTALL_METHODS.has(method) && method !== "npm-global";
|
|
109
|
+
}
|
|
86
110
|
|
|
87
111
|
/**
|
|
88
112
|
* Validate the `secrets` block.
|
|
@@ -171,19 +195,28 @@ function validateInstallArtifact(label, entry, problems) {
|
|
|
171
195
|
);
|
|
172
196
|
return;
|
|
173
197
|
}
|
|
174
|
-
//
|
|
175
|
-
// trusts whatever the URL serves today exactly as much as a zip
|
|
176
|
-
// checking only one of them meant the tool Lisa's guardrails shell
|
|
177
|
-
// gh, which ships a tarball on Linux — was the one going unverified.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
) {
|
|
198
|
+
// Every download kind, not an enumerated few. A tarball pinned without a
|
|
199
|
+
// checksum trusts whatever the URL serves today exactly as much as a zip
|
|
200
|
+
// does, and checking only one of them meant the tool Lisa's guardrails shell
|
|
201
|
+
// out to — gh, which ships a tarball on Linux — was the one going unverified.
|
|
202
|
+
// A `release-binary` matters most of all: the artifact is directly
|
|
203
|
+
// executable, so a wrong one needs no unpacking step to run.
|
|
204
|
+
if (isDownloadKind(entry.install) && !(entry.url && entry.sha256)) {
|
|
182
205
|
problems.push(
|
|
183
206
|
`remoteEnv install ${label} needs both url and sha256. A pinned ` +
|
|
184
207
|
`version with no checksum still trusts whatever the URL serves today.`
|
|
185
208
|
);
|
|
186
209
|
}
|
|
210
|
+
// A tree is the one kind with no sane default entry point: the archive root
|
|
211
|
+
// is a directory, so an omitted `binary` would install a directory as a
|
|
212
|
+
// command. The runner refuses it, and a validator that accepted it would just
|
|
213
|
+
// move the failure from review to provisioning.
|
|
214
|
+
if (entry.install === "release-tree" && !entry.binary) {
|
|
215
|
+
problems.push(
|
|
216
|
+
`remoteEnv install ${label} needs "binary" — the path to the entry ` +
|
|
217
|
+
`point INSIDE the archive, such as "maestro/bin/maestro".`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
187
220
|
if (entry.install === "npm-global" && !entry.package) {
|
|
188
221
|
problems.push(`remoteEnv install ${label} needs a package`);
|
|
189
222
|
}
|
package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs
CHANGED
|
@@ -300,6 +300,44 @@ function installReleaseTree(tool, binDir) {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Install a pinned artifact that IS the binary, with no archive around it.
|
|
305
|
+
*
|
|
306
|
+
* Several tools worth pinning publish a raw executable rather than an archive —
|
|
307
|
+
* jq ships `jq-linux-amd64` and a `sha256sum.txt`, which is ideal material for a
|
|
308
|
+
* checksummed pin and fits none of the archive kinds.
|
|
309
|
+
*
|
|
310
|
+
* Without this the only entry that passes `assertPinned` is `release-zip`, which
|
|
311
|
+
* then fails at install when `unzip` is handed a binary. A manifest entry that
|
|
312
|
+
* validates and cannot install is worse than one that is rejected outright: the
|
|
313
|
+
* error arrives during provisioning rather than during review.
|
|
314
|
+
*
|
|
315
|
+
* There is no `binary` field here, deliberately — the download is the binary,
|
|
316
|
+
* so there is nothing inside it to name.
|
|
317
|
+
* @param {object} tool Manifest entry.
|
|
318
|
+
* @param {string} binDir Directory to install into.
|
|
319
|
+
*/
|
|
320
|
+
function installReleaseBinary(tool, binDir) {
|
|
321
|
+
const temporary = join(binDir, `.${tool.name}-download`);
|
|
322
|
+
mkdirSync(temporary, { recursive: true });
|
|
323
|
+
try {
|
|
324
|
+
const artifact = join(temporary, tool.name);
|
|
325
|
+
execFileSync("curl", ["-fsSL", tool.url, "-o", artifact], {
|
|
326
|
+
stdio: "inherit",
|
|
327
|
+
});
|
|
328
|
+
// Verify before it reaches a directory on PATH. For this kind that ordering
|
|
329
|
+
// matters more than for the archives: the downloaded file is directly
|
|
330
|
+
// executable, so a wrong artifact placed first is a wrong artifact that can
|
|
331
|
+
// be run.
|
|
332
|
+
verifyChecksum(artifact, tool.sha256, tool.name);
|
|
333
|
+
execFileSync("install", ["-m", "0755", artifact, join(binDir, tool.name)], {
|
|
334
|
+
stdio: "inherit",
|
|
335
|
+
});
|
|
336
|
+
} finally {
|
|
337
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
303
341
|
/**
|
|
304
342
|
* Install a pinned global npm package.
|
|
305
343
|
* @param {object} tool Manifest entry.
|
|
@@ -328,6 +366,8 @@ export function installTool(tool, binDir) {
|
|
|
328
366
|
if (tool.install === "release-zip") installReleaseZip(tool, binDir);
|
|
329
367
|
else if (tool.install === "release-tar") installReleaseTar(tool, binDir);
|
|
330
368
|
else if (tool.install === "release-tree") installReleaseTree(tool, binDir);
|
|
369
|
+
else if (tool.install === "release-binary")
|
|
370
|
+
installReleaseBinary(tool, binDir);
|
|
331
371
|
else installNpmGlobal(tool);
|
|
332
372
|
}
|
|
333
373
|
|
|
@@ -301,6 +301,24 @@ export function planToolchain(
|
|
|
301
301
|
return plan;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Every install method the runner can execute — the authoritative list.
|
|
306
|
+
*
|
|
307
|
+
* `validate-config.mjs` maintains its own copy because skills are distributed
|
|
308
|
+
* as self-contained directories and nothing else imports across a skill
|
|
309
|
+
* boundary. A comment saying "keep these in step" has now failed twice — first
|
|
310
|
+
* for `release-tar`, then for `release-tree` and `release-binary` together — so
|
|
311
|
+
* the pairing is enforced by a conformance test against this export instead.
|
|
312
|
+
* Adding a kind here fails that test until the validator learns it too.
|
|
313
|
+
*/
|
|
314
|
+
export const INSTALL_METHODS = Object.freeze([
|
|
315
|
+
"release-zip",
|
|
316
|
+
"release-tar",
|
|
317
|
+
"release-tree",
|
|
318
|
+
"release-binary",
|
|
319
|
+
"npm-global",
|
|
320
|
+
]);
|
|
321
|
+
|
|
304
322
|
/**
|
|
305
323
|
* Reject a manifest entry that could install something unverifiable.
|
|
306
324
|
*
|
|
@@ -354,6 +372,18 @@ export function assertPinned(tool) {
|
|
|
354
372
|
}
|
|
355
373
|
return;
|
|
356
374
|
}
|
|
375
|
+
// The download IS the binary, so there is no `binary` path to require — but
|
|
376
|
+
// the checksum matters more here than anywhere else, because the artifact is
|
|
377
|
+
// directly executable rather than something that has to be unpacked first.
|
|
378
|
+
if (tool.install === "release-binary") {
|
|
379
|
+
if (!tool.url || !tool.sha256) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`${tool.name}: a release-binary install needs both url and sha256.\n` +
|
|
382
|
+
`A version bump must move the checksum in the same reviewed commit.`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
357
387
|
if (tool.install === "npm-global") {
|
|
358
388
|
if (!tool.package)
|
|
359
389
|
throw new Error(`${tool.name}: npm-global install needs a package`);
|
|
@@ -361,6 +391,6 @@ export function assertPinned(tool) {
|
|
|
361
391
|
}
|
|
362
392
|
throw new Error(
|
|
363
393
|
`${tool.name}: unknown install method "${tool.install}".\n` +
|
|
364
|
-
`Supported:
|
|
394
|
+
`Supported: ${INSTALL_METHODS.join(", ")}.`
|
|
365
395
|
);
|
|
366
396
|
}
|
|
@@ -76,13 +76,37 @@ function bindingsFor(surface) {
|
|
|
76
76
|
/**
|
|
77
77
|
* Install methods the toolchain runner supports.
|
|
78
78
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
79
|
+
* This is a deliberate copy of `INSTALL_METHODS` in `toolchain.mjs`, which is
|
|
80
|
+
* the list that actually decides. Skills are distributed as self-contained
|
|
81
|
+
* directories and nothing imports across a skill boundary, so importing the
|
|
82
|
+
* authority here would couple two skills' distribution for one array.
|
|
83
|
+
*
|
|
84
|
+
* The copy has drifted twice. First `release-tar` was missing while the runner
|
|
85
|
+
* had supported it for as long as gh has been pinned. Then `release-tree` and
|
|
86
|
+
* `release-binary` were both missing, which rejected a valid `jq` pin. Each
|
|
87
|
+
* time the only safeguard was a comment asking the next author to remember.
|
|
88
|
+
* A conformance test now compares the two sets directly, so adding a kind on
|
|
89
|
+
* either side fails until both know it.
|
|
84
90
|
*/
|
|
85
|
-
const INSTALL_METHODS = new Set([
|
|
91
|
+
const INSTALL_METHODS = new Set([
|
|
92
|
+
"release-zip",
|
|
93
|
+
"release-tar",
|
|
94
|
+
"release-tree",
|
|
95
|
+
"release-binary",
|
|
96
|
+
"npm-global",
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Download kinds — every method that fetches a URL, so every one that needs a
|
|
101
|
+
* checksum. Spelled as "not npm-global" rather than as a list, because the next
|
|
102
|
+
* download kind should inherit the obligation by default; the drift above came
|
|
103
|
+
* from lists that had to be remembered.
|
|
104
|
+
* @param {string} method An install method.
|
|
105
|
+
* @returns {boolean} Whether the method fetches a pinned artifact.
|
|
106
|
+
*/
|
|
107
|
+
function isDownloadKind(method) {
|
|
108
|
+
return INSTALL_METHODS.has(method) && method !== "npm-global";
|
|
109
|
+
}
|
|
86
110
|
|
|
87
111
|
/**
|
|
88
112
|
* Validate the `secrets` block.
|
|
@@ -171,19 +195,28 @@ function validateInstallArtifact(label, entry, problems) {
|
|
|
171
195
|
);
|
|
172
196
|
return;
|
|
173
197
|
}
|
|
174
|
-
//
|
|
175
|
-
// trusts whatever the URL serves today exactly as much as a zip
|
|
176
|
-
// checking only one of them meant the tool Lisa's guardrails shell
|
|
177
|
-
// gh, which ships a tarball on Linux — was the one going unverified.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
) {
|
|
198
|
+
// Every download kind, not an enumerated few. A tarball pinned without a
|
|
199
|
+
// checksum trusts whatever the URL serves today exactly as much as a zip
|
|
200
|
+
// does, and checking only one of them meant the tool Lisa's guardrails shell
|
|
201
|
+
// out to — gh, which ships a tarball on Linux — was the one going unverified.
|
|
202
|
+
// A `release-binary` matters most of all: the artifact is directly
|
|
203
|
+
// executable, so a wrong one needs no unpacking step to run.
|
|
204
|
+
if (isDownloadKind(entry.install) && !(entry.url && entry.sha256)) {
|
|
182
205
|
problems.push(
|
|
183
206
|
`remoteEnv install ${label} needs both url and sha256. A pinned ` +
|
|
184
207
|
`version with no checksum still trusts whatever the URL serves today.`
|
|
185
208
|
);
|
|
186
209
|
}
|
|
210
|
+
// A tree is the one kind with no sane default entry point: the archive root
|
|
211
|
+
// is a directory, so an omitted `binary` would install a directory as a
|
|
212
|
+
// command. The runner refuses it, and a validator that accepted it would just
|
|
213
|
+
// move the failure from review to provisioning.
|
|
214
|
+
if (entry.install === "release-tree" && !entry.binary) {
|
|
215
|
+
problems.push(
|
|
216
|
+
`remoteEnv install ${label} needs "binary" — the path to the entry ` +
|
|
217
|
+
`point INSIDE the archive, such as "maestro/bin/maestro".`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
187
220
|
if (entry.install === "npm-global" && !entry.package) {
|
|
188
221
|
problems.push(`remoteEnv install ${label} needs a package`);
|
|
189
222
|
}
|
|
@@ -300,6 +300,44 @@ function installReleaseTree(tool, binDir) {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Install a pinned artifact that IS the binary, with no archive around it.
|
|
305
|
+
*
|
|
306
|
+
* Several tools worth pinning publish a raw executable rather than an archive —
|
|
307
|
+
* jq ships `jq-linux-amd64` and a `sha256sum.txt`, which is ideal material for a
|
|
308
|
+
* checksummed pin and fits none of the archive kinds.
|
|
309
|
+
*
|
|
310
|
+
* Without this the only entry that passes `assertPinned` is `release-zip`, which
|
|
311
|
+
* then fails at install when `unzip` is handed a binary. A manifest entry that
|
|
312
|
+
* validates and cannot install is worse than one that is rejected outright: the
|
|
313
|
+
* error arrives during provisioning rather than during review.
|
|
314
|
+
*
|
|
315
|
+
* There is no `binary` field here, deliberately — the download is the binary,
|
|
316
|
+
* so there is nothing inside it to name.
|
|
317
|
+
* @param {object} tool Manifest entry.
|
|
318
|
+
* @param {string} binDir Directory to install into.
|
|
319
|
+
*/
|
|
320
|
+
function installReleaseBinary(tool, binDir) {
|
|
321
|
+
const temporary = join(binDir, `.${tool.name}-download`);
|
|
322
|
+
mkdirSync(temporary, { recursive: true });
|
|
323
|
+
try {
|
|
324
|
+
const artifact = join(temporary, tool.name);
|
|
325
|
+
execFileSync("curl", ["-fsSL", tool.url, "-o", artifact], {
|
|
326
|
+
stdio: "inherit",
|
|
327
|
+
});
|
|
328
|
+
// Verify before it reaches a directory on PATH. For this kind that ordering
|
|
329
|
+
// matters more than for the archives: the downloaded file is directly
|
|
330
|
+
// executable, so a wrong artifact placed first is a wrong artifact that can
|
|
331
|
+
// be run.
|
|
332
|
+
verifyChecksum(artifact, tool.sha256, tool.name);
|
|
333
|
+
execFileSync("install", ["-m", "0755", artifact, join(binDir, tool.name)], {
|
|
334
|
+
stdio: "inherit",
|
|
335
|
+
});
|
|
336
|
+
} finally {
|
|
337
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
303
341
|
/**
|
|
304
342
|
* Install a pinned global npm package.
|
|
305
343
|
* @param {object} tool Manifest entry.
|
|
@@ -328,6 +366,8 @@ export function installTool(tool, binDir) {
|
|
|
328
366
|
if (tool.install === "release-zip") installReleaseZip(tool, binDir);
|
|
329
367
|
else if (tool.install === "release-tar") installReleaseTar(tool, binDir);
|
|
330
368
|
else if (tool.install === "release-tree") installReleaseTree(tool, binDir);
|
|
369
|
+
else if (tool.install === "release-binary")
|
|
370
|
+
installReleaseBinary(tool, binDir);
|
|
331
371
|
else installNpmGlobal(tool);
|
|
332
372
|
}
|
|
333
373
|
|
|
@@ -301,6 +301,24 @@ export function planToolchain(
|
|
|
301
301
|
return plan;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Every install method the runner can execute — the authoritative list.
|
|
306
|
+
*
|
|
307
|
+
* `validate-config.mjs` maintains its own copy because skills are distributed
|
|
308
|
+
* as self-contained directories and nothing else imports across a skill
|
|
309
|
+
* boundary. A comment saying "keep these in step" has now failed twice — first
|
|
310
|
+
* for `release-tar`, then for `release-tree` and `release-binary` together — so
|
|
311
|
+
* the pairing is enforced by a conformance test against this export instead.
|
|
312
|
+
* Adding a kind here fails that test until the validator learns it too.
|
|
313
|
+
*/
|
|
314
|
+
export const INSTALL_METHODS = Object.freeze([
|
|
315
|
+
"release-zip",
|
|
316
|
+
"release-tar",
|
|
317
|
+
"release-tree",
|
|
318
|
+
"release-binary",
|
|
319
|
+
"npm-global",
|
|
320
|
+
]);
|
|
321
|
+
|
|
304
322
|
/**
|
|
305
323
|
* Reject a manifest entry that could install something unverifiable.
|
|
306
324
|
*
|
|
@@ -354,6 +372,18 @@ export function assertPinned(tool) {
|
|
|
354
372
|
}
|
|
355
373
|
return;
|
|
356
374
|
}
|
|
375
|
+
// The download IS the binary, so there is no `binary` path to require — but
|
|
376
|
+
// the checksum matters more here than anywhere else, because the artifact is
|
|
377
|
+
// directly executable rather than something that has to be unpacked first.
|
|
378
|
+
if (tool.install === "release-binary") {
|
|
379
|
+
if (!tool.url || !tool.sha256) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`${tool.name}: a release-binary install needs both url and sha256.\n` +
|
|
382
|
+
`A version bump must move the checksum in the same reviewed commit.`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
357
387
|
if (tool.install === "npm-global") {
|
|
358
388
|
if (!tool.package)
|
|
359
389
|
throw new Error(`${tool.name}: npm-global install needs a package`);
|
|
@@ -361,6 +391,6 @@ export function assertPinned(tool) {
|
|
|
361
391
|
}
|
|
362
392
|
throw new Error(
|
|
363
393
|
`${tool.name}: unknown install method "${tool.install}".\n` +
|
|
364
|
-
`Supported:
|
|
394
|
+
`Supported: ${INSTALL_METHODS.join(", ")}.`
|
|
365
395
|
);
|
|
366
396
|
}
|
|
@@ -76,13 +76,37 @@ function bindingsFor(surface) {
|
|
|
76
76
|
/**
|
|
77
77
|
* Install methods the toolchain runner supports.
|
|
78
78
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
79
|
+
* This is a deliberate copy of `INSTALL_METHODS` in `toolchain.mjs`, which is
|
|
80
|
+
* the list that actually decides. Skills are distributed as self-contained
|
|
81
|
+
* directories and nothing imports across a skill boundary, so importing the
|
|
82
|
+
* authority here would couple two skills' distribution for one array.
|
|
83
|
+
*
|
|
84
|
+
* The copy has drifted twice. First `release-tar` was missing while the runner
|
|
85
|
+
* had supported it for as long as gh has been pinned. Then `release-tree` and
|
|
86
|
+
* `release-binary` were both missing, which rejected a valid `jq` pin. Each
|
|
87
|
+
* time the only safeguard was a comment asking the next author to remember.
|
|
88
|
+
* A conformance test now compares the two sets directly, so adding a kind on
|
|
89
|
+
* either side fails until both know it.
|
|
84
90
|
*/
|
|
85
|
-
const INSTALL_METHODS = new Set([
|
|
91
|
+
const INSTALL_METHODS = new Set([
|
|
92
|
+
"release-zip",
|
|
93
|
+
"release-tar",
|
|
94
|
+
"release-tree",
|
|
95
|
+
"release-binary",
|
|
96
|
+
"npm-global",
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Download kinds — every method that fetches a URL, so every one that needs a
|
|
101
|
+
* checksum. Spelled as "not npm-global" rather than as a list, because the next
|
|
102
|
+
* download kind should inherit the obligation by default; the drift above came
|
|
103
|
+
* from lists that had to be remembered.
|
|
104
|
+
* @param {string} method An install method.
|
|
105
|
+
* @returns {boolean} Whether the method fetches a pinned artifact.
|
|
106
|
+
*/
|
|
107
|
+
function isDownloadKind(method) {
|
|
108
|
+
return INSTALL_METHODS.has(method) && method !== "npm-global";
|
|
109
|
+
}
|
|
86
110
|
|
|
87
111
|
/**
|
|
88
112
|
* Validate the `secrets` block.
|
|
@@ -171,19 +195,28 @@ function validateInstallArtifact(label, entry, problems) {
|
|
|
171
195
|
);
|
|
172
196
|
return;
|
|
173
197
|
}
|
|
174
|
-
//
|
|
175
|
-
// trusts whatever the URL serves today exactly as much as a zip
|
|
176
|
-
// checking only one of them meant the tool Lisa's guardrails shell
|
|
177
|
-
// gh, which ships a tarball on Linux — was the one going unverified.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
) {
|
|
198
|
+
// Every download kind, not an enumerated few. A tarball pinned without a
|
|
199
|
+
// checksum trusts whatever the URL serves today exactly as much as a zip
|
|
200
|
+
// does, and checking only one of them meant the tool Lisa's guardrails shell
|
|
201
|
+
// out to — gh, which ships a tarball on Linux — was the one going unverified.
|
|
202
|
+
// A `release-binary` matters most of all: the artifact is directly
|
|
203
|
+
// executable, so a wrong one needs no unpacking step to run.
|
|
204
|
+
if (isDownloadKind(entry.install) && !(entry.url && entry.sha256)) {
|
|
182
205
|
problems.push(
|
|
183
206
|
`remoteEnv install ${label} needs both url and sha256. A pinned ` +
|
|
184
207
|
`version with no checksum still trusts whatever the URL serves today.`
|
|
185
208
|
);
|
|
186
209
|
}
|
|
210
|
+
// A tree is the one kind with no sane default entry point: the archive root
|
|
211
|
+
// is a directory, so an omitted `binary` would install a directory as a
|
|
212
|
+
// command. The runner refuses it, and a validator that accepted it would just
|
|
213
|
+
// move the failure from review to provisioning.
|
|
214
|
+
if (entry.install === "release-tree" && !entry.binary) {
|
|
215
|
+
problems.push(
|
|
216
|
+
`remoteEnv install ${label} needs "binary" — the path to the entry ` +
|
|
217
|
+
`point INSIDE the archive, such as "maestro/bin/maestro".`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
187
220
|
if (entry.install === "npm-global" && !entry.package) {
|
|
188
221
|
problems.push(`remoteEnv install ${label} needs a package`);
|
|
189
222
|
}
|
|
@@ -300,6 +300,44 @@ function installReleaseTree(tool, binDir) {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Install a pinned artifact that IS the binary, with no archive around it.
|
|
305
|
+
*
|
|
306
|
+
* Several tools worth pinning publish a raw executable rather than an archive —
|
|
307
|
+
* jq ships `jq-linux-amd64` and a `sha256sum.txt`, which is ideal material for a
|
|
308
|
+
* checksummed pin and fits none of the archive kinds.
|
|
309
|
+
*
|
|
310
|
+
* Without this the only entry that passes `assertPinned` is `release-zip`, which
|
|
311
|
+
* then fails at install when `unzip` is handed a binary. A manifest entry that
|
|
312
|
+
* validates and cannot install is worse than one that is rejected outright: the
|
|
313
|
+
* error arrives during provisioning rather than during review.
|
|
314
|
+
*
|
|
315
|
+
* There is no `binary` field here, deliberately — the download is the binary,
|
|
316
|
+
* so there is nothing inside it to name.
|
|
317
|
+
* @param {object} tool Manifest entry.
|
|
318
|
+
* @param {string} binDir Directory to install into.
|
|
319
|
+
*/
|
|
320
|
+
function installReleaseBinary(tool, binDir) {
|
|
321
|
+
const temporary = join(binDir, `.${tool.name}-download`);
|
|
322
|
+
mkdirSync(temporary, { recursive: true });
|
|
323
|
+
try {
|
|
324
|
+
const artifact = join(temporary, tool.name);
|
|
325
|
+
execFileSync("curl", ["-fsSL", tool.url, "-o", artifact], {
|
|
326
|
+
stdio: "inherit",
|
|
327
|
+
});
|
|
328
|
+
// Verify before it reaches a directory on PATH. For this kind that ordering
|
|
329
|
+
// matters more than for the archives: the downloaded file is directly
|
|
330
|
+
// executable, so a wrong artifact placed first is a wrong artifact that can
|
|
331
|
+
// be run.
|
|
332
|
+
verifyChecksum(artifact, tool.sha256, tool.name);
|
|
333
|
+
execFileSync("install", ["-m", "0755", artifact, join(binDir, tool.name)], {
|
|
334
|
+
stdio: "inherit",
|
|
335
|
+
});
|
|
336
|
+
} finally {
|
|
337
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
303
341
|
/**
|
|
304
342
|
* Install a pinned global npm package.
|
|
305
343
|
* @param {object} tool Manifest entry.
|
|
@@ -328,6 +366,8 @@ export function installTool(tool, binDir) {
|
|
|
328
366
|
if (tool.install === "release-zip") installReleaseZip(tool, binDir);
|
|
329
367
|
else if (tool.install === "release-tar") installReleaseTar(tool, binDir);
|
|
330
368
|
else if (tool.install === "release-tree") installReleaseTree(tool, binDir);
|
|
369
|
+
else if (tool.install === "release-binary")
|
|
370
|
+
installReleaseBinary(tool, binDir);
|
|
331
371
|
else installNpmGlobal(tool);
|
|
332
372
|
}
|
|
333
373
|
|
|
@@ -301,6 +301,24 @@ export function planToolchain(
|
|
|
301
301
|
return plan;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Every install method the runner can execute — the authoritative list.
|
|
306
|
+
*
|
|
307
|
+
* `validate-config.mjs` maintains its own copy because skills are distributed
|
|
308
|
+
* as self-contained directories and nothing else imports across a skill
|
|
309
|
+
* boundary. A comment saying "keep these in step" has now failed twice — first
|
|
310
|
+
* for `release-tar`, then for `release-tree` and `release-binary` together — so
|
|
311
|
+
* the pairing is enforced by a conformance test against this export instead.
|
|
312
|
+
* Adding a kind here fails that test until the validator learns it too.
|
|
313
|
+
*/
|
|
314
|
+
export const INSTALL_METHODS = Object.freeze([
|
|
315
|
+
"release-zip",
|
|
316
|
+
"release-tar",
|
|
317
|
+
"release-tree",
|
|
318
|
+
"release-binary",
|
|
319
|
+
"npm-global",
|
|
320
|
+
]);
|
|
321
|
+
|
|
304
322
|
/**
|
|
305
323
|
* Reject a manifest entry that could install something unverifiable.
|
|
306
324
|
*
|
|
@@ -354,6 +372,18 @@ export function assertPinned(tool) {
|
|
|
354
372
|
}
|
|
355
373
|
return;
|
|
356
374
|
}
|
|
375
|
+
// The download IS the binary, so there is no `binary` path to require — but
|
|
376
|
+
// the checksum matters more here than anywhere else, because the artifact is
|
|
377
|
+
// directly executable rather than something that has to be unpacked first.
|
|
378
|
+
if (tool.install === "release-binary") {
|
|
379
|
+
if (!tool.url || !tool.sha256) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`${tool.name}: a release-binary install needs both url and sha256.\n` +
|
|
382
|
+
`A version bump must move the checksum in the same reviewed commit.`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
357
387
|
if (tool.install === "npm-global") {
|
|
358
388
|
if (!tool.package)
|
|
359
389
|
throw new Error(`${tool.name}: npm-global install needs a package`);
|
|
@@ -361,6 +391,6 @@ export function assertPinned(tool) {
|
|
|
361
391
|
}
|
|
362
392
|
throw new Error(
|
|
363
393
|
`${tool.name}: unknown install method "${tool.install}".\n` +
|
|
364
|
-
`Supported:
|
|
394
|
+
`Supported: ${INSTALL_METHODS.join(", ")}.`
|
|
365
395
|
);
|
|
366
396
|
}
|