@codyswann/lisa 4.34.21 → 4.35.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/check-npm-publish-landed.mjs +49 -26
- package/all/copy-overwrite/scripts/lib/kill-marks.mjs +1 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +49 -10
- package/all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh +42 -4
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +33 -12
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +9 -4
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +9 -6
- package/dist/codex/scripts/_extract-edit-paths.sh +4 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +20 -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/nightly-e2e-guard-behavior-certificate.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +21 -10
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/migrations/ensure-lisa-postinstall.d.ts +6 -37
- package/dist/migrations/ensure-lisa-postinstall.d.ts.map +1 -1
- package/dist/migrations/ensure-lisa-postinstall.js +20 -67
- package/dist/migrations/ensure-lisa-postinstall.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +33 -12
- 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-direct-issue-create.sh +49 -10
- package/plugins/lisa/hooks/block-managed-file-edits.sh +42 -4
- package/plugins/lisa/hooks/parity-safety-net.sh +33 -12
- package/plugins/lisa/rules/eager/measurement-integrity.md +18 -0
- package/plugins/lisa/rules/reference/measurement-integrity.md +109 -0
- package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +49 -10
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +33 -12
- 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-direct-issue-create.sh +49 -10
- package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +42 -4
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +33 -12
- package/plugins/lisa-copilot/rules/eager/measurement-integrity.md +18 -0
- package/plugins/lisa-copilot/rules/reference/measurement-integrity.md +109 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +49 -10
- package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +42 -4
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +33 -12
- package/plugins/lisa-cursor/rules/measurement-integrity-reference.mdc +114 -0
- package/plugins/lisa-cursor/rules/measurement-integrity.mdc +23 -0
- 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-direct-issue-create.sh +49 -10
- package/plugins/src/base/hooks/block-managed-file-edits.sh +42 -4
- package/plugins/src/base/hooks/parity-safety-net.sh +33 -12
- package/plugins/src/base/rules/eager/measurement-integrity.md +18 -0
- package/plugins/src/base/rules/reference/measurement-integrity.md +109 -0
|
@@ -1,48 +1,17 @@
|
|
|
1
1
|
import type { Migration, MigrationContext, MigrationResult } from "./migration.interface.js";
|
|
2
|
-
/**
|
|
3
|
-
* What the operator sees on stderr when the bootstrap apply fails.
|
|
4
|
-
*
|
|
5
|
-
* Written for someone scrolling past a wall of install output who may not know
|
|
6
|
-
* what Lisa is: it says what stopped working (templates and guardrails are no
|
|
7
|
-
* longer arriving), that the real error is directly above, and the two commands
|
|
8
|
-
* that reproduce and diagnose it. ASCII and free of `"`, `$`, and backticks so
|
|
9
|
-
* it survives being embedded in a JSON string inside a `sh -c` command.
|
|
10
|
-
*/
|
|
11
|
-
export declare const APPLY_FAILURE_NOTICE: string;
|
|
12
2
|
/**
|
|
13
3
|
* The bootstrap invocation chained into a host project's `postinstall`.
|
|
14
4
|
*
|
|
15
|
-
* Shape:
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* so a failed apply still reported success and the one warning line scrolled
|
|
21
|
-
* away in a wall of install output (CodySwannGT/lisa#3466). The failure branch
|
|
22
|
-
* now prints the notice and exits 1.
|
|
23
|
-
*
|
|
24
|
-
* Failing the install is a deliberate trade against the previous reasoning,
|
|
25
|
-
* which was that a non-zero postinstall would convert a template-sync problem
|
|
26
|
-
* into an install outage across the fleet. Two things bound that risk. The
|
|
27
|
-
* `[ -n "$CI" ] ||` guard means this branch never executes when `CI` is set, so
|
|
28
|
-
* no CI pipeline can be broken by it and the exposure is local installs only;
|
|
29
|
-
* and `CI=1` remains the escape hatch for anyone who needs to install past a
|
|
30
|
-
* broken apply. Against that, the apply receipt this failed run did NOT write —
|
|
31
|
-
* previously offered as the durable signal — cannot tell `lisa doctor` that an
|
|
32
|
-
* apply *failed*, only that none has succeeded recently, which is equally true
|
|
33
|
-
* of a project that was never applied or was deliberately skipped. The exit code
|
|
34
|
-
* is the only signal that distinguishes them.
|
|
35
|
-
*
|
|
36
|
-
* The cost, stated plainly: {@link composePostinstall} builds
|
|
37
|
-
* `<lisa> && <the host's own postinstall>`, so a failed apply now also skips
|
|
38
|
-
* whatever the host chained after it (`patch-package` and friends). That is the
|
|
39
|
-
* honest outcome — the install genuinely did not complete — but it is a wider
|
|
40
|
-
* change than the exit code alone.
|
|
5
|
+
* Shape: bounded, loud, durable, and deliberately non-fatal. The runner owns
|
|
6
|
+
* the timeout, exposes the real error, and records a marker for `lisa doctor`;
|
|
7
|
+
* `|| true` keeps a template failure from stranding the host before it can
|
|
8
|
+
* install the fix. This is the same invocation the package template writes,
|
|
9
|
+
* so a migration cannot leave older callers on the superseded direct apply.
|
|
41
10
|
*
|
|
42
11
|
* Exported so tests can execute the real script under `sh` rather than assert
|
|
43
12
|
* on a string.
|
|
44
13
|
*/
|
|
45
|
-
export declare const LISA_INVOCATION = "[ -n \"$CI\" ] || LISA_BOOTSTRAP=1
|
|
14
|
+
export declare const LISA_INVOCATION = "[ -n \"$CI\" ] || LISA_BOOTSTRAP=1 node node_modules/@codyswann/lisa/all/copy-overwrite/scripts/lisa-postinstall.mjs || true";
|
|
46
15
|
/**
|
|
47
16
|
* Migration: ensure Node-based projects chain Lisa into their postinstall script.
|
|
48
17
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ensure-lisa-postinstall.d.ts","sourceRoot":"","sources":["../../src/migrations/ensure-lisa-postinstall.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"ensure-lisa-postinstall.d.ts","sourceRoot":"","sources":["../../src/migrations/ensure-lisa-postinstall.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AASlC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,iIAAmF,CAAC;AAoGhH;;;;;;;;;GASG;AACH,qBAAa,8BAA+B,YAAW,SAAS;IAC9D,QAAQ,CAAC,IAAI,6BAA6B;IAC1C,QAAQ,CAAC,WAAW,qEACgD;IAEpE;;;;;;;;;;;;;OAaG;IACG,OAAO,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IA0BtD;;;;OAIG;IACG,KAAK,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;CAqC7D"}
|
|
@@ -4,76 +4,21 @@ import { readJsonOrNull, writeJson } from "../utils/json-utils.js";
|
|
|
4
4
|
const PACKAGE_JSON = "package.json";
|
|
5
5
|
const CI_GUARD_PREFIX = '[ -n "$CI" ] || ';
|
|
6
6
|
const BOOTSTRAP_PREFIX = "LISA_BOOTSTRAP=1 ";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
* This — not `--skip-git-check` — is what selects the reduced
|
|
11
|
-
* `postinstall-safe` apply (CodySwannGT/lisa#3066). It is spelled as an
|
|
12
|
-
* environment prefix rather than a flag on purpose: the command tail stays
|
|
13
|
-
* byte-identical, so {@link LISA_INVOCATION_RE} keeps matching every hook
|
|
14
|
-
* already installed in a consumer's `package.json` with one added alternative
|
|
15
|
-
* in a group it already had.
|
|
16
|
-
*/
|
|
17
|
-
const POSTINSTALL_PREFIX = "LISA_POSTINSTALL=1 ";
|
|
18
|
-
const LISA_MARKER = "node_modules/@codyswann/lisa/dist/index.js";
|
|
19
|
-
const LISA_APPLY_COMMAND = `node ${LISA_MARKER} --yes --skip-git-check .`;
|
|
20
|
-
/** The apply command with the declaration, as an operator would reproduce it. */
|
|
21
|
-
const LISA_APPLY_COMMAND_AS_POSTINSTALL = `${POSTINSTALL_PREFIX}${LISA_APPLY_COMMAND}`;
|
|
22
|
-
/**
|
|
23
|
-
* What the operator sees on stderr when the bootstrap apply fails.
|
|
24
|
-
*
|
|
25
|
-
* Written for someone scrolling past a wall of install output who may not know
|
|
26
|
-
* what Lisa is: it says what stopped working (templates and guardrails are no
|
|
27
|
-
* longer arriving), that the real error is directly above, and the two commands
|
|
28
|
-
* that reproduce and diagnose it. ASCII and free of `"`, `$`, and backticks so
|
|
29
|
-
* it survives being embedded in a JSON string inside a `sh -c` command.
|
|
30
|
-
*/
|
|
31
|
-
export const APPLY_FAILURE_NOTICE = "lisa: TEMPLATE APPLY FAILED - this project is NOT receiving Lisa template " +
|
|
32
|
-
"or guardrail updates. The error is above. Reproduce it with: " +
|
|
33
|
-
`${LISA_APPLY_COMMAND_AS_POSTINSTALL} - then run: node ${LISA_MARKER} doctor`;
|
|
34
|
-
/**
|
|
35
|
-
* The failure branch: say why, then exit non-zero.
|
|
36
|
-
*
|
|
37
|
-
* `exit 1` rather than a bare `false` because this invocation is routinely
|
|
38
|
-
* chained (`<lisa> && <the host's own postinstall>`); `exit` ends the script at
|
|
39
|
-
* the failure regardless of what a consumer composed around it, so the status
|
|
40
|
-
* cannot be overwritten by a later command in the chain.
|
|
41
|
-
*/
|
|
42
|
-
const APPLY_FAILURE_TAIL = ` || { echo "${APPLY_FAILURE_NOTICE}" >&2; exit 1; }`;
|
|
7
|
+
const LEGACY_LISA_MARKER = "node_modules/@codyswann/lisa/dist/index.js";
|
|
8
|
+
const POSTINSTALL_RUNNER_MARKER = "node_modules/@codyswann/lisa/all/copy-overwrite/scripts/lisa-postinstall.mjs";
|
|
43
9
|
/**
|
|
44
10
|
* The bootstrap invocation chained into a host project's `postinstall`.
|
|
45
11
|
*
|
|
46
|
-
* Shape:
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* so a failed apply still reported success and the one warning line scrolled
|
|
52
|
-
* away in a wall of install output (CodySwannGT/lisa#3466). The failure branch
|
|
53
|
-
* now prints the notice and exits 1.
|
|
54
|
-
*
|
|
55
|
-
* Failing the install is a deliberate trade against the previous reasoning,
|
|
56
|
-
* which was that a non-zero postinstall would convert a template-sync problem
|
|
57
|
-
* into an install outage across the fleet. Two things bound that risk. The
|
|
58
|
-
* `[ -n "$CI" ] ||` guard means this branch never executes when `CI` is set, so
|
|
59
|
-
* no CI pipeline can be broken by it and the exposure is local installs only;
|
|
60
|
-
* and `CI=1` remains the escape hatch for anyone who needs to install past a
|
|
61
|
-
* broken apply. Against that, the apply receipt this failed run did NOT write —
|
|
62
|
-
* previously offered as the durable signal — cannot tell `lisa doctor` that an
|
|
63
|
-
* apply *failed*, only that none has succeeded recently, which is equally true
|
|
64
|
-
* of a project that was never applied or was deliberately skipped. The exit code
|
|
65
|
-
* is the only signal that distinguishes them.
|
|
66
|
-
*
|
|
67
|
-
* The cost, stated plainly: {@link composePostinstall} builds
|
|
68
|
-
* `<lisa> && <the host's own postinstall>`, so a failed apply now also skips
|
|
69
|
-
* whatever the host chained after it (`patch-package` and friends). That is the
|
|
70
|
-
* honest outcome — the install genuinely did not complete — but it is a wider
|
|
71
|
-
* change than the exit code alone.
|
|
12
|
+
* Shape: bounded, loud, durable, and deliberately non-fatal. The runner owns
|
|
13
|
+
* the timeout, exposes the real error, and records a marker for `lisa doctor`;
|
|
14
|
+
* `|| true` keeps a template failure from stranding the host before it can
|
|
15
|
+
* install the fix. This is the same invocation the package template writes,
|
|
16
|
+
* so a migration cannot leave older callers on the superseded direct apply.
|
|
72
17
|
*
|
|
73
18
|
* Exported so tests can execute the real script under `sh` rather than assert
|
|
74
19
|
* on a string.
|
|
75
20
|
*/
|
|
76
|
-
export const LISA_INVOCATION = `${CI_GUARD_PREFIX}${BOOTSTRAP_PREFIX}${
|
|
21
|
+
export const LISA_INVOCATION = `${CI_GUARD_PREFIX}${BOOTSTRAP_PREFIX}node ${POSTINSTALL_RUNNER_MARKER} || true`;
|
|
77
22
|
/**
|
|
78
23
|
* Project types that do not use Node.js postinstall hooks (e.g. Rails).
|
|
79
24
|
* Projects detected as only these types are skipped by this migration.
|
|
@@ -108,11 +53,15 @@ async function readPackageJson(projectDir) {
|
|
|
108
53
|
* never re-applies keeps its old spelling forever, which is why an old
|
|
109
54
|
* alternative may never be removed.
|
|
110
55
|
*/
|
|
111
|
-
const
|
|
56
|
+
const LEGACY_LISA_INVOCATION_RE = new RegExp('(?:(?:\\[ -n "\\$CI" \\] \\|\\| )|(?:LISA_BOOTSTRAP=1 )|(?:LISA_POSTINSTALL=1 ))*' +
|
|
112
57
|
"node node_modules/@codyswann/lisa/dist/index\\.js --yes --skip-git-check \\." +
|
|
113
58
|
"(?: 2>/dev/null \\|\\| true" +
|
|
114
59
|
'| \\|\\| \\{ echo "[^"]*" >&2; exit 1; \\}' +
|
|
115
60
|
'| \\|\\| echo "[^"]*" >&2)?');
|
|
61
|
+
/** The bounded runner spelling currently written by Lisa. */
|
|
62
|
+
const POSTINSTALL_RUNNER_RE = new RegExp('(?:(?:\\[ -n "\\$CI" \\] \\|\\| )|(?:LISA_BOOTSTRAP=1 ))*' +
|
|
63
|
+
"node node_modules/@codyswann/lisa/all/copy-overwrite/scripts/" +
|
|
64
|
+
"lisa-postinstall\\.mjs(?: \\|\\| true)?");
|
|
116
65
|
/**
|
|
117
66
|
* Compose the new postinstall, prepending the Lisa invocation to any existing command.
|
|
118
67
|
* If the existing script already contains a legacy Lisa invocation (no CI guard),
|
|
@@ -125,8 +74,11 @@ function composePostinstall(existing) {
|
|
|
125
74
|
if (!trimmed) {
|
|
126
75
|
return LISA_INVOCATION;
|
|
127
76
|
}
|
|
128
|
-
if (trimmed.includes(
|
|
129
|
-
return trimmed.replace(
|
|
77
|
+
if (trimmed.includes(POSTINSTALL_RUNNER_MARKER)) {
|
|
78
|
+
return trimmed.replace(POSTINSTALL_RUNNER_RE, LISA_INVOCATION);
|
|
79
|
+
}
|
|
80
|
+
if (trimmed.includes(LEGACY_LISA_MARKER)) {
|
|
81
|
+
return trimmed.replace(LEGACY_LISA_INVOCATION_RE, LISA_INVOCATION);
|
|
130
82
|
}
|
|
131
83
|
return `${LISA_INVOCATION} && ${trimmed}`;
|
|
132
84
|
}
|
|
@@ -187,7 +139,8 @@ export class EnsureLisaPostinstallMigration {
|
|
|
187
139
|
}
|
|
188
140
|
const normalizedPostinstall = composePostinstall(postinstall);
|
|
189
141
|
if (!hasNodePostinstallType(ctx.detectedTypes)) {
|
|
190
|
-
return (postinstall.includes(
|
|
142
|
+
return ((postinstall.includes(LEGACY_LISA_MARKER) ||
|
|
143
|
+
postinstall.includes(POSTINSTALL_RUNNER_MARKER)) &&
|
|
191
144
|
normalizedPostinstall !== postinstall);
|
|
192
145
|
}
|
|
193
146
|
return normalizedPostinstall !== postinstall;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ensure-lisa-postinstall.js","sourceRoot":"","sources":["../../src/migrations/ensure-lisa-postinstall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAOnE,MAAM,YAAY,GAAG,cAAc,CAAC;AACpC,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAC3C,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAC7C
|
|
1
|
+
{"version":3,"file":"ensure-lisa-postinstall.js","sourceRoot":"","sources":["../../src/migrations/ensure-lisa-postinstall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAOnE,MAAM,YAAY,GAAG,cAAc,CAAC;AACpC,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAC3C,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAC7C,MAAM,kBAAkB,GAAG,4CAA4C,CAAC;AACxE,MAAM,yBAAyB,GAC7B,8EAA8E,CAAC;AAEjF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,eAAe,GAAG,gBAAgB,QAAQ,yBAAyB,UAAU,CAAC;AAEhH;;;GAGG;AACH,MAAM,cAAc,GAA2B,CAAC,OAAO,CAAC,CAAC;AAUzD;;;;GAIG;AACH,KAAK,UAAU,eAAe,CAC5B,UAAkB;IAElB,OAAO,cAAc,CAAc,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,yBAAyB,GAAG,IAAI,MAAM,CAC1C,mFAAmF;IACjF,8EAA8E;IAC9E,6BAA6B;IAC7B,4CAA4C;IAC5C,6BAA6B,CAChC,CAAC;AAEF,6DAA6D;AAC7D,MAAM,qBAAqB,GAAG,IAAI,MAAM,CACtC,2DAA2D;IACzD,+DAA+D;IAC/D,yCAAyC,CAC5C,CAAC;AAEF;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAC,QAA4B;IACtD,MAAM,OAAO,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,eAAe,CAAC;IACzB,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAC,EAAE,CAAC;QAChD,OAAO,OAAO,CAAC,OAAO,CAAC,qBAAqB,EAAE,eAAe,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACzC,OAAO,OAAO,CAAC,OAAO,CAAC,yBAAyB,EAAE,eAAe,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,GAAG,eAAe,OAAO,OAAO,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAC7B,aAAqC;IAErC,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,8BAA8B;IAChC,IAAI,GAAG,yBAAyB,CAAC;IACjC,WAAW,GAClB,iEAAiE,CAAC;IAEpE;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,OAAO,CAAC,GAAqB;QACjC,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,KAAK,CAAC;QACf,CAAC;QACD,wEAAwE;QACxE,2EAA2E;QAC3E,gEAAgE;QAChE,IAAI,GAAG,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACnC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,EAAE,WAAW,CAAC;QAC7C,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,sBAAsB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACnD,CAAC;QACD,MAAM,qBAAqB,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;QAC9D,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;YAC/C,OAAO,CACL,CAAC,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC;gBACvC,WAAW,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC;gBAClD,qBAAqB,KAAK,WAAW,CACtC,CAAC;QACJ,CAAC;QACD,OAAO,qBAAqB,KAAK,WAAW,CAAC;IAC/C,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,GAAqB;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QACxD,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAC7C,CAAC;QAED,MAAM,cAAc,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;QACzC,MAAM,cAAc,GAAG,kBAAkB,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QACtE,MAAM,OAAO,GAAgB;YAC3B,GAAG,GAAG;YACN,OAAO,EAAE,EAAE,GAAG,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE;SAC5D,CAAC;QAEF,MAAM,OAAO,GAAG,cAAc,CAAC,WAAW;YACxC,CAAC,CAAC,2CAA2C,cAAc,EAAE;YAC7D,CAAC,CAAC,uCAAuC,cAAc,EAAE,CAAC;QAE5D,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACf,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;YAChE,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,SAAS;gBACjB,YAAY,EAAE,CAAC,YAAY,CAAC;gBAC5B,OAAO;aACR,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAClC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5B,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,SAAS;YACjB,YAAY,EAAE,CAAC,YAAY,CAAC;YAC5B,OAAO;SACR,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -179,12 +179,14 @@ EOF
|
|
|
179
179
|
# The patch file is per-worktree: `mktemp` names it, so nothing has to invent a
|
|
180
180
|
# filename, and it lives in this agent's own TMPDIR where no sibling can reach
|
|
181
181
|
# it. Untracked files are unaffected by both refused operations, so `git diff`
|
|
182
|
-
# captures everything at risk.
|
|
182
|
+
# captures everything at risk. `HEAD` includes staged and unstaged changes, and
|
|
183
|
+
# `--binary` keeps binary edits recoverable rather than emitting a text-only
|
|
184
|
+
# placeholder that cannot be applied.
|
|
183
185
|
PRESERVE_GUIDANCE="$(
|
|
184
186
|
cat <<'EOF'
|
|
185
187
|
Preserve the work first, per-worktree:
|
|
186
188
|
|
|
187
|
-
git diff [-- <path>] > "$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX.patch")"
|
|
189
|
+
git diff --binary HEAD [-- <path>] > "$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX.patch")"
|
|
188
190
|
|
|
189
191
|
and restore it later with `git apply <that file>`.
|
|
190
192
|
|
|
@@ -444,6 +446,7 @@ follow_refuse() {
|
|
|
444
446
|
# so peak process count never rises. The latency was the whole problem.
|
|
445
447
|
follow_unwrapped=""
|
|
446
448
|
follow_operand_index=0
|
|
449
|
+
follow_located=""
|
|
447
450
|
|
|
448
451
|
# Strip substitution, quote and alias wrappers off one token. A sibling of
|
|
449
452
|
# strip_subst_wrappers() below rather than a call to it: that one is defined
|
|
@@ -568,6 +571,7 @@ follow_add_base() {
|
|
|
568
571
|
# this hook cannot evaluate.
|
|
569
572
|
follow_locate() {
|
|
570
573
|
local token="$1" home="${HOME:-}" tmp="${TMPDIR:-/tmp}" proj="${CLAUDE_PROJECT_DIR:-$PWD}" base
|
|
574
|
+
follow_located=""
|
|
571
575
|
tmp="${tmp%/}"
|
|
572
576
|
[ -n "$tmp" ] || tmp="/tmp"
|
|
573
577
|
# The tilde arms below are LITERAL text: this is the unexpanded spelling the
|
|
@@ -589,14 +593,14 @@ follow_locate() {
|
|
|
589
593
|
"" | *'$'* | *'*'* | *'?'* | *'`'* | *'{'* | *'['*) return 1 ;;
|
|
590
594
|
/*)
|
|
591
595
|
[ -f "$token" ] && [ -r "$token" ] || return 1
|
|
592
|
-
|
|
596
|
+
follow_located="$token"
|
|
593
597
|
return 0
|
|
594
598
|
;;
|
|
595
599
|
esac
|
|
596
600
|
while IFS= read -r base; do
|
|
597
601
|
[ -n "$base" ] || continue
|
|
598
602
|
if [ -f "$base/$token" ] && [ -r "$base/$token" ]; then
|
|
599
|
-
|
|
603
|
+
follow_located="$base/$token"
|
|
600
604
|
return 0
|
|
601
605
|
fi
|
|
602
606
|
done <<<"$PWD"$'\n'"$proj$follow_bases"
|
|
@@ -641,12 +645,13 @@ follow_take() {
|
|
|
641
645
|
follow_target() {
|
|
642
646
|
local token="$1" depth="$2" path=""
|
|
643
647
|
[ -n "$token" ] || return 0
|
|
644
|
-
if !
|
|
648
|
+
if ! follow_locate "$token"; then
|
|
645
649
|
if [ "$depth" -eq 0 ]; then
|
|
646
650
|
follow_refuse "$token" "no readable file could be resolved for it"
|
|
647
651
|
fi
|
|
648
652
|
return 0
|
|
649
653
|
fi
|
|
654
|
+
path="$follow_located"
|
|
650
655
|
follow_take "$path" "$depth"
|
|
651
656
|
}
|
|
652
657
|
|
|
@@ -667,11 +672,18 @@ follow_target() {
|
|
|
667
672
|
# Accepted residual: a shebang script whose relative path resolves only from a
|
|
668
673
|
# working directory this hook does not share is not followed.
|
|
669
674
|
follow_direct() {
|
|
670
|
-
local token="$1" depth="$2" path="" first=""
|
|
671
|
-
|
|
675
|
+
local token="$1" depth="$2" path="" first="" raw="" char="" k=0
|
|
676
|
+
local LC_ALL=C
|
|
677
|
+
if ! follow_locate "$token"; then
|
|
672
678
|
return 0
|
|
673
679
|
fi
|
|
674
|
-
|
|
680
|
+
path="$follow_located"
|
|
681
|
+
IFS= read -r -n 256 raw <"$path" 2>/dev/null || true
|
|
682
|
+
while [ "$k" -lt "${#raw}" ]; do
|
|
683
|
+
char="${raw:k:1}"
|
|
684
|
+
case "$char" in [[:print:]]) first="$first$char" ;; esac
|
|
685
|
+
k=$((k + 1))
|
|
686
|
+
done
|
|
675
687
|
case "$first" in
|
|
676
688
|
'#!'*[/[:space:]]bash* | '#!'*[/[:space:]]sh* | '#!'*[/[:space:]]zsh* \
|
|
677
689
|
| '#!'*[/[:space:]]ksh* | '#!'*[/[:space:]]dash* | '#!'*[/[:space:]]ash*) ;;
|
|
@@ -701,7 +713,7 @@ follow_next_operand() {
|
|
|
701
713
|
follow_scan() {
|
|
702
714
|
local text="$1" depth="$2"
|
|
703
715
|
local split_out line sep stmt t tj i j n cmd_pos eval_mode inline arg_exec
|
|
704
|
-
local heredoc redirected pipe_cat prev_cat pending_operand
|
|
716
|
+
local heredoc redirected pipe_cat prev_cat pending_operand stdin_mode
|
|
705
717
|
local find_mode dispatch_pending
|
|
706
718
|
local -a toks=()
|
|
707
719
|
split_out="$(follow_split "$text")"
|
|
@@ -888,6 +900,7 @@ follow_scan() {
|
|
|
888
900
|
inline=0
|
|
889
901
|
redirected=""
|
|
890
902
|
heredoc=0
|
|
903
|
+
stdin_mode=0
|
|
891
904
|
j=$((i + 1))
|
|
892
905
|
while [ "$j" -lt "$n" ]; do
|
|
893
906
|
follow_unwrap "${toks[j]}"
|
|
@@ -920,6 +933,7 @@ follow_scan() {
|
|
|
920
933
|
;;
|
|
921
934
|
--*) j=$((j + 1)) ;;
|
|
922
935
|
-*c*) inline=1; j=$((j + 1)) ;;
|
|
936
|
+
-s | -[a-zA-Z]*s[a-zA-Z]*) stdin_mode=1; j=$((j + 1)) ;;
|
|
923
937
|
-*) j=$((j + 1)) ;;
|
|
924
938
|
*) break ;;
|
|
925
939
|
esac
|
|
@@ -944,6 +958,14 @@ follow_scan() {
|
|
|
944
958
|
i="$j"
|
|
945
959
|
continue
|
|
946
960
|
fi
|
|
961
|
+
if [ "$stdin_mode" -eq 1 ]; then
|
|
962
|
+
# `sh -s -- -y` reads its program from stdin; tokens after `--` are
|
|
963
|
+
# positional arguments to that program, never a script path.
|
|
964
|
+
[ -z "$pipe_cat" ] || follow_target "$pipe_cat" "$depth"
|
|
965
|
+
cmd_pos=0
|
|
966
|
+
i="$n"
|
|
967
|
+
continue
|
|
968
|
+
fi
|
|
947
969
|
if [ "$j" -ge "$n" ]; then
|
|
948
970
|
# A dispatcher supplies the script from ANOTHER command's output, so
|
|
949
971
|
# the operand exists but is unreadable here. That is the fail-closed
|
|
@@ -1806,15 +1828,14 @@ readonly SQL_TRUNCATE_EVIDENCE='\btruncate[[:space:]]+(table[[:space:]]+[[:alnum
|
|
|
1806
1828
|
# the same command must also invoke a command-line database client. Prose in a
|
|
1807
1829
|
# tracker filing or a commit message invokes none, which is exactly the
|
|
1808
1830
|
# difference the text scan could not see on its own.
|
|
1809
|
-
readonly
|
|
1810
|
-
readonly SQL_TRUNCATE_BARE='\btruncate[[:space:]]+(only[[:space:]]+)?[[:alnum:]_."`]'
|
|
1831
|
+
readonly SQL_CLIENT_TRUNCATE='(^|[;&|()][[:space:]]*)([[:alnum:]_./-]*/)?(psql|pgcli|mysql|mysqladmin|mysqlsh|mariadb|mycli|sqlite3|sqlcmd|cqlsh|clickhouse-client|cockroach|mongosh|duckdb|usql)([[:space:]]+[^;&|()]*)?[[:space:]]+(-e|-c|--command|--eval)(=|[[:space:]]+)[^;&|()]*\btruncate[[:space:]]+(only[[:space:]]+)?[[:alnum:]_."`]'
|
|
1811
1832
|
# Residual, accepted and stated: a bare `TRUNCATE <name>` with no terminator,
|
|
1812
1833
|
# no qualification and no recognized client in the same command goes unmatched
|
|
1813
1834
|
# — the same trade the `drop` arm has always made for `drop the ball`. No DROP
|
|
1814
1835
|
# shape is weakened, and the guard still fails in the safe direction.
|
|
1815
1836
|
if matches "$SQL_DROP" \
|
|
1816
1837
|
|| matches "$SQL_TRUNCATE_EVIDENCE" \
|
|
1817
|
-
||
|
|
1838
|
+
|| matches "$SQL_CLIENT_TRUNCATE"; then
|
|
1818
1839
|
block "destructive SQL (DROP/TRUNCATE) detected"
|
|
1819
1840
|
fi
|
|
1820
1841
|
|
package/package.json
CHANGED
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
"zod-validation-error": "^4.0.0"
|
|
167
167
|
},
|
|
168
168
|
"name": "@codyswann/lisa",
|
|
169
|
-
"version": "4.
|
|
169
|
+
"version": "4.35.1",
|
|
170
170
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
171
171
|
"main": "dist/index.js",
|
|
172
172
|
"exports": {
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
"test": "tests"
|
|
315
315
|
},
|
|
316
316
|
"types": "./dist/index.d.ts",
|
|
317
|
-
"lisaReleaseCommit": "
|
|
318
|
-
"gitHead": "
|
|
319
|
-
"lisaReleaseTag": "v4.
|
|
317
|
+
"lisaReleaseCommit": "6fb4eaca043a1b6e8d96be3392dc4a42abd9ce11",
|
|
318
|
+
"gitHead": "6fb4eaca043a1b6e8d96be3392dc4a42abd9ce11",
|
|
319
|
+
"lisaReleaseTag": "v4.35.1"
|
|
320
320
|
}
|
|
@@ -1426,7 +1426,11 @@ EXEC_WRAPPERS = {
|
|
|
1426
1426
|
"nohup": (frozenset(), 0),
|
|
1427
1427
|
"setsid": (frozenset(), 0),
|
|
1428
1428
|
"stdbuf": (frozenset({"-i", "-o", "-e"}), 0),
|
|
1429
|
-
"sudo": (frozenset({
|
|
1429
|
+
"sudo": (frozenset({
|
|
1430
|
+
"-u", "--user", "-g", "--group", "-C", "--close-from",
|
|
1431
|
+
"-p", "--prompt", "-h", "--host", "-r", "--role",
|
|
1432
|
+
"-t", "--type", "-U", "--other-user",
|
|
1433
|
+
}), 0),
|
|
1430
1434
|
"time": (frozenset(), 0),
|
|
1431
1435
|
"timeout": (frozenset({"-k", "--kill-after", "-s", "--signal"}), 1),
|
|
1432
1436
|
}
|
|
@@ -1447,6 +1451,31 @@ NO_SCRIPT_OPTIONS = {
|
|
|
1447
1451
|
"ruby": {"-e"},
|
|
1448
1452
|
}
|
|
1449
1453
|
|
|
1454
|
+
# Interpreter switches whose value is a separate token, followed by the real
|
|
1455
|
+
# script operand. Treating the value as the script makes the guard inspect a
|
|
1456
|
+
# preload or option value and never reach what the interpreter executes.
|
|
1457
|
+
INTERPRETER_VALUE_OPTIONS = {
|
|
1458
|
+
"bash": {"-o", "--option"},
|
|
1459
|
+
"dash": {"-o"},
|
|
1460
|
+
"deno": {"-c", "--config"},
|
|
1461
|
+
"ksh": {"-o"},
|
|
1462
|
+
"node": {"-r", "--require"},
|
|
1463
|
+
"nodejs": {"-r", "--require"},
|
|
1464
|
+
"sh": {"-o"},
|
|
1465
|
+
"zsh": {"-o"},
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
# Runtime subcommands that introduce the script path rather than name it.
|
|
1469
|
+
INTERPRETER_SUBCOMMANDS = {
|
|
1470
|
+
"bun": {"run"},
|
|
1471
|
+
"deno": {"run"},
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
|
|
1475
|
+
def is_assignment_word(token):
|
|
1476
|
+
"""Whether token is a POSIX shell assignment in command-prefix position."""
|
|
1477
|
+
return re.match(r"^[A-Za-z_][A-Za-z0-9_]*\+?=", token) is not None
|
|
1478
|
+
|
|
1450
1479
|
# Programs that READ their operands and never execute them as programs.
|
|
1451
1480
|
#
|
|
1452
1481
|
# This is an allowlist of readers rather than the inverse, and the asymmetry is
|
|
@@ -1501,7 +1530,7 @@ def executing_command(argv):
|
|
|
1501
1530
|
index = 0
|
|
1502
1531
|
while index < len(argv):
|
|
1503
1532
|
token = argv[index]
|
|
1504
|
-
if
|
|
1533
|
+
if is_assignment_word(token):
|
|
1505
1534
|
index += 1
|
|
1506
1535
|
continue
|
|
1507
1536
|
program = basename(token)
|
|
@@ -1568,16 +1597,20 @@ def executed_operand(argv):
|
|
|
1568
1597
|
# shebang — `./create.sh` names no interpreter and runs all the same.
|
|
1569
1598
|
if program not in EXECUTING_INTERPRETERS and resolve_operand(token) is not None:
|
|
1570
1599
|
return token
|
|
1571
|
-
# `bash < create.sh` runs that file as surely as `bash create.sh` does, and
|
|
1572
|
-
# `<` is not a segment boundary, so the pair survives in one argv.
|
|
1573
|
-
for index, argument in enumerate(args):
|
|
1574
|
-
if argument == "<" and index + 1 < len(args):
|
|
1575
|
-
return args[index + 1]
|
|
1576
1600
|
# Reached by a known interpreter AND by a program this parser cannot name.
|
|
1577
1601
|
# The unknown case is followed on purpose — see READ_ONLY_PROGRAMS.
|
|
1578
|
-
|
|
1579
|
-
|
|
1602
|
+
redirected = None
|
|
1603
|
+
index = 0
|
|
1604
|
+
while index < len(args):
|
|
1605
|
+
argument = args[index]
|
|
1606
|
+
if argument == "<":
|
|
1607
|
+
if index + 1 < len(args):
|
|
1608
|
+
redirected = args[index + 1]
|
|
1609
|
+
index += 2
|
|
1580
1610
|
continue
|
|
1611
|
+
if argument == "--":
|
|
1612
|
+
index += 1
|
|
1613
|
+
return args[index] if index < len(args) else redirected
|
|
1581
1614
|
if argument.startswith("-"):
|
|
1582
1615
|
# A command string or a module name; no script file follows.
|
|
1583
1616
|
head = argument.split("=", 1)[0]
|
|
@@ -1588,9 +1621,15 @@ def executed_operand(argv):
|
|
|
1588
1621
|
return None
|
|
1589
1622
|
elif head in NO_SCRIPT_OPTIONS.get(program, set()):
|
|
1590
1623
|
return None
|
|
1624
|
+
takes_value = head in INTERPRETER_VALUE_OPTIONS.get(program, set())
|
|
1625
|
+
index += 2 if takes_value and "=" not in argument else 1
|
|
1626
|
+
continue
|
|
1627
|
+
if argument in INTERPRETER_SUBCOMMANDS.get(program, set()):
|
|
1628
|
+
index += 1
|
|
1591
1629
|
continue
|
|
1592
1630
|
return argument
|
|
1593
|
-
|
|
1631
|
+
# `bash < create.sh` runs stdin only when no explicit script was named.
|
|
1632
|
+
return redirected
|
|
1594
1633
|
|
|
1595
1634
|
|
|
1596
1635
|
def file_operands(argv):
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
# from mine", and guessing "behind" is how a fork's stronger guard gets silently
|
|
91
91
|
# replaced by a weaker upstream one. Add a name here in the same commit that
|
|
92
92
|
# closes a vector.
|
|
93
|
-
# lisa-guard-capabilities: managed-path-resolution, lisaignore-precedence, generated-path-rebuild, redirect-target, tee-target, sed-in-place-all-spellings, executed-script-reach, source-builtin-reach, stdin-redirect-reach, wrapper-positional-operand, direct-script-command-reach, analyzer-failure-visible
|
|
93
|
+
# lisa-guard-capabilities: managed-path-resolution, lisaignore-precedence, generated-path-rebuild, redirect-target, tee-target, sed-in-place-all-spellings, executed-script-reach, source-builtin-reach, stdin-redirect-reach, wrapper-positional-operand, direct-script-command-reach, analyzer-failure-visible, apply-patch-parse-failure-visible, apply-patch-move-target, shell-command-string-reach
|
|
94
94
|
set -euo pipefail
|
|
95
95
|
|
|
96
96
|
input="$(cat)"
|
|
@@ -399,6 +399,7 @@ case "$tool_name" in
|
|
|
399
399
|
# *** Add File: <path>
|
|
400
400
|
# *** Update File: <path>
|
|
401
401
|
# *** Delete File: <path>
|
|
402
|
+
# *** Move to: <path>
|
|
402
403
|
#
|
|
403
404
|
# So every header is walked and each target classified; one managed
|
|
404
405
|
# target anywhere in a multi-file patch refuses the whole call, because
|
|
@@ -415,14 +416,25 @@ case "$tool_name" in
|
|
|
415
416
|
# legitimately cannot be one file. What is NOT restated is the
|
|
416
417
|
# classification: `managed_source` and `refuse` are the single copy here,
|
|
417
418
|
# exactly as for every other arm.
|
|
418
|
-
patch_text="$(printf '%s' "$input" | jq -
|
|
419
|
+
if ! patch_text="$(printf '%s' "$input" | jq -er '
|
|
420
|
+
if ((.tool_input.command? // "") | type) == "string"
|
|
421
|
+
then (.tool_input.command? // "")
|
|
422
|
+
else error("tool_input.command must be a string")
|
|
423
|
+
end')"; then
|
|
424
|
+
printf 'block-managed-file-edits: could not parse the apply_patch command; managed-file protection refused the edit\n' >&2
|
|
425
|
+
exit 2
|
|
426
|
+
fi
|
|
419
427
|
[ -n "$patch_text" ] || exit 0
|
|
420
428
|
while IFS= read -r patch_line; do
|
|
421
429
|
case "$patch_line" in
|
|
422
|
-
"*** Add File: "* | "*** Update File: "* | "*** Delete File: "*)
|
|
430
|
+
"*** Add File: "* | "*** Update File: "* | "*** Delete File: "*)
|
|
431
|
+
candidate="${patch_line#*File: }"
|
|
432
|
+
;;
|
|
433
|
+
"*** Move to: "*)
|
|
434
|
+
candidate="${patch_line#*** Move to: }"
|
|
435
|
+
;;
|
|
423
436
|
*) continue ;;
|
|
424
437
|
esac
|
|
425
|
-
candidate="${patch_line#*File: }"
|
|
426
438
|
[ -n "$candidate" ] || continue
|
|
427
439
|
if source_path="$(managed_source "$candidate")"; then
|
|
428
440
|
refuse "$candidate" "$source_path" "$(relative_path "$candidate")"
|
|
@@ -712,6 +724,29 @@ def shell_script_operand(args):
|
|
|
712
724
|
return None
|
|
713
725
|
|
|
714
726
|
|
|
727
|
+
def shell_command_string(statement):
|
|
728
|
+
"""The inline command a shell executes with `-c`, or None.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
statement: One statement's tokens.
|
|
732
|
+
|
|
733
|
+
Returns:
|
|
734
|
+
The command string operand, or None when this is not a shell `-c` call.
|
|
735
|
+
"""
|
|
736
|
+
_token, program, args = command_word(statement)
|
|
737
|
+
if program not in SHELL_PROGRAMS:
|
|
738
|
+
return None
|
|
739
|
+
for index, token in enumerate(args):
|
|
740
|
+
if token == "--":
|
|
741
|
+
return None
|
|
742
|
+
if token.startswith("-") and not token.startswith("--") and "c" in token[1:]:
|
|
743
|
+
command_index = index + 1
|
|
744
|
+
if command_index < len(args) and args[command_index] == "--":
|
|
745
|
+
command_index += 1
|
|
746
|
+
return args[command_index] if command_index < len(args) else None
|
|
747
|
+
return None
|
|
748
|
+
|
|
749
|
+
|
|
715
750
|
def write_targets(statement):
|
|
716
751
|
"""Paths this statement WRITES.
|
|
717
752
|
|
|
@@ -834,6 +869,9 @@ def collect(text, depth, seen, out):
|
|
|
834
869
|
if not statement:
|
|
835
870
|
continue
|
|
836
871
|
out.extend(write_targets(statement))
|
|
872
|
+
nested = shell_command_string(statement)
|
|
873
|
+
if nested is not None and depth < FOLLOW_MAX_DEPTH:
|
|
874
|
+
collect(nested, depth + 1, seen, out)
|
|
837
875
|
operand = executed_script(statement)
|
|
838
876
|
if operand is None:
|
|
839
877
|
continue
|