@codyswann/lisa 4.39.0 → 4.40.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/lisa-cross-worktree-guard.mjs +333 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +17 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh +14 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +28 -0
- package/dist/codex/scripts/block-no-verify.sh +21 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +22 -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 +19 -13
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-managed-file-edits.sh +14 -0
- package/dist/opencode/plugin-templates/block-no-verify.sh +28 -0
- package/npm-package/create-only/.github/workflows/publish-to-npm.yml +48 -19
- 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 +17 -0
- package/plugins/lisa/hooks/block-managed-file-edits.sh +14 -0
- package/plugins/lisa/hooks/block-no-verify.agy.sh +21 -0
- package/plugins/lisa/hooks/block-no-verify.sh +28 -0
- package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +17 -0
- package/plugins/lisa-agy/hooks/block-managed-file-edits.sh +14 -0
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +21 -0
- 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 +17 -0
- package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +14 -0
- package/plugins/lisa-copilot/hooks/block-no-verify.sh +28 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +17 -0
- package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +14 -0
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +28 -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 +17 -0
- package/plugins/src/base/hooks/block-managed-file-edits.sh +14 -0
- package/plugins/src/base/hooks/block-no-verify.agy.sh +21 -0
- package/plugins/src/base/hooks/block-no-verify.sh +28 -0
- package/rails/copy-contents/scripts/lisa-mutation.sh +8 -1
- package/scripts/two-channel-couplings.json +1 -1
- package/typescript/copy-contents/.husky/pre-commit +40 -0
- package/typescript/copy-overwrite/.prettierignore +1 -0
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +20 -14
|
@@ -961,6 +961,10 @@ def resolve_script(token):
|
|
|
961
961
|
|
|
962
962
|
SHELL_COMMAND_STRING = object()
|
|
963
963
|
SHELL_STDIN = object()
|
|
964
|
+
# `-n` / `--noexec`: the shell READS and parses its operand, then exits without
|
|
965
|
+
# executing a line of it. Distinct from the two above because nothing runs at
|
|
966
|
+
# all — there is no payload to scan and no stdin script to wait for.
|
|
967
|
+
SHELL_NOEXEC = object()
|
|
964
968
|
|
|
965
969
|
|
|
966
970
|
def shell_execution_operand(args):
|
|
@@ -991,12 +995,25 @@ def shell_execution_operand(args):
|
|
|
991
995
|
index += 1
|
|
992
996
|
continue
|
|
993
997
|
if token.startswith("--"):
|
|
998
|
+
if token == "--noexec":
|
|
999
|
+
return SHELL_NOEXEC
|
|
994
1000
|
if token not in SHELL_LONG_OPTIONS_NO_VALUE:
|
|
995
1001
|
opaque_option_seen = True
|
|
996
1002
|
index += 1
|
|
997
1003
|
continue
|
|
998
1004
|
if "c" in token[1:]:
|
|
999
1005
|
return SHELL_COMMAND_STRING
|
|
1006
|
+
# `-n` is noexec. `bash -n <file>` is the one shape that puts a path at
|
|
1007
|
+
# a command position while provably executing nothing, so it is the
|
|
1008
|
+
# exact seam between reading and running — and reach got it wrong in
|
|
1009
|
+
# BOTH directions (CodySwannGT/lisa#3781). Here it meant a syntax check
|
|
1010
|
+
# of any file merely DISCUSSING the bypass was refused, which is most of
|
|
1011
|
+
# this repository's own guards and every husky hook.
|
|
1012
|
+
#
|
|
1013
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
1014
|
+
# distinction, not the program: this is not a read-only exemption.
|
|
1015
|
+
if "n" in token[1:]:
|
|
1016
|
+
return SHELL_NOEXEC
|
|
1000
1017
|
index += 1
|
|
1001
1018
|
return args[index] if index < len(args) else SHELL_STDIN
|
|
1002
1019
|
|
|
@@ -1041,6 +1058,10 @@ def executed_scripts(tokens):
|
|
|
1041
1058
|
continue
|
|
1042
1059
|
if operand is SHELL_COMMAND_STRING:
|
|
1043
1060
|
continue
|
|
1061
|
+
if operand is SHELL_NOEXEC:
|
|
1062
|
+
# Nothing runs, so there is nothing to follow and nothing
|
|
1063
|
+
# to fail closed about.
|
|
1064
|
+
continue
|
|
1044
1065
|
else:
|
|
1045
1066
|
continue
|
|
1046
1067
|
if operand is None:
|
|
@@ -24,6 +24,13 @@
|
|
|
24
24
|
# `source`, `.`, `bash < nv.sh`, and an interpreter a `find -exec` or
|
|
25
25
|
# `xargs` puts at a command position.
|
|
26
26
|
#
|
|
27
|
+
# NOT vector 11: `bash -n nv.sh`. `-n` is noexec — the shell reads and parses
|
|
28
|
+
# the file, then exits without running a line — so a syntax check is the one
|
|
29
|
+
# shape that puts a path at a command position while executing nothing. It used
|
|
30
|
+
# to be refused, which made `bash -n` unusable on this file, on every husky
|
|
31
|
+
# hook, and on most of this repository (#3781). The distinction is noexec, not
|
|
32
|
+
# the program: `bash nv.sh` still executes and is still refused.
|
|
33
|
+
#
|
|
27
34
|
# The line below is what lets `lisa apply` tell a downstream copy of this guard
|
|
28
35
|
# that is BEHIND from one that is AHEAD. Byte comparison cannot: both look like
|
|
29
36
|
# "differs from mine", and guessing "behind" is how a fork's stronger guard gets
|
|
@@ -1031,6 +1038,10 @@ def resolve_script(token):
|
|
|
1031
1038
|
|
|
1032
1039
|
SHELL_COMMAND_STRING = object()
|
|
1033
1040
|
SHELL_STDIN = object()
|
|
1041
|
+
# `-n` / `--noexec`: the shell READS and parses its operand, then exits without
|
|
1042
|
+
# executing a line of it. Distinct from the two above because nothing runs at
|
|
1043
|
+
# all — there is no payload to scan and no stdin script to wait for.
|
|
1044
|
+
SHELL_NOEXEC = object()
|
|
1034
1045
|
|
|
1035
1046
|
|
|
1036
1047
|
def shell_execution_operand(args):
|
|
@@ -1061,12 +1072,25 @@ def shell_execution_operand(args):
|
|
|
1061
1072
|
index += 1
|
|
1062
1073
|
continue
|
|
1063
1074
|
if token.startswith("--"):
|
|
1075
|
+
if token == "--noexec":
|
|
1076
|
+
return SHELL_NOEXEC
|
|
1064
1077
|
if token not in SHELL_LONG_OPTIONS_NO_VALUE:
|
|
1065
1078
|
opaque_option_seen = True
|
|
1066
1079
|
index += 1
|
|
1067
1080
|
continue
|
|
1068
1081
|
if "c" in token[1:]:
|
|
1069
1082
|
return SHELL_COMMAND_STRING
|
|
1083
|
+
# `-n` is noexec. `bash -n <file>` is the one shape that puts a path at
|
|
1084
|
+
# a command position while provably executing nothing, so it is the
|
|
1085
|
+
# exact seam between reading and running — and reach got it wrong in
|
|
1086
|
+
# BOTH directions (CodySwannGT/lisa#3781). Here it meant a syntax check
|
|
1087
|
+
# of any file merely DISCUSSING the bypass was refused, which is most of
|
|
1088
|
+
# this repository's own guards and every husky hook.
|
|
1089
|
+
#
|
|
1090
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
1091
|
+
# distinction, not the program: this is not a read-only exemption.
|
|
1092
|
+
if "n" in token[1:]:
|
|
1093
|
+
return SHELL_NOEXEC
|
|
1070
1094
|
index += 1
|
|
1071
1095
|
return args[index] if index < len(args) else SHELL_STDIN
|
|
1072
1096
|
|
|
@@ -1111,6 +1135,10 @@ def executed_scripts(tokens):
|
|
|
1111
1135
|
continue
|
|
1112
1136
|
if operand is SHELL_COMMAND_STRING:
|
|
1113
1137
|
continue
|
|
1138
|
+
if operand is SHELL_NOEXEC:
|
|
1139
|
+
# Nothing runs, so there is nothing to follow and nothing
|
|
1140
|
+
# to fail closed about.
|
|
1141
|
+
continue
|
|
1114
1142
|
else:
|
|
1115
1143
|
continue
|
|
1116
1144
|
if operand is None:
|
|
@@ -70,11 +70,18 @@ if [ -z "$BASE" ]; then
|
|
|
70
70
|
exit 0
|
|
71
71
|
fi
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
# Working tree, not "${BASE}...HEAD": mutant reads the files on disk, so a
|
|
74
|
+
# subject whose only change is uncommitted is invisible to committed state and
|
|
75
|
+
# this pre-check would report "nothing to mutate" while a changed subject sat
|
|
76
|
+
# right there. Parity with the TypeScript gate's selectChangedTargets.
|
|
77
|
+
CHANGED="$(git diff --name-only --diff-filter=ACMR "${BASE}" -- 'app/**/*.rb' 'lib/**/*.rb' || true)"
|
|
74
78
|
if [ -z "$CHANGED" ]; then
|
|
75
79
|
echo "⚪ Mutation gate: no changed Ruby files under app/ or lib/ vs ${SINCE}. Nothing to mutate."
|
|
76
80
|
exit 0
|
|
77
81
|
fi
|
|
78
82
|
|
|
83
|
+
# Known gap, deliberately not papered over: mutant's own --since scoping is
|
|
84
|
+
# committed-based, so the working-tree correction above reaches the pre-check
|
|
85
|
+
# but not mutant's subject selection. There is no mutant flag for it.
|
|
79
86
|
echo "🧬 Mutation gate: running mutant (--since ${SINCE}) on changed subjects..."
|
|
80
87
|
exec bundle exec mutant run --since "$SINCE"
|
|
@@ -107,6 +107,46 @@ if [ -f ".lintstagedrc.json" ]; then
|
|
|
107
107
|
fi
|
|
108
108
|
fi
|
|
109
109
|
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# Cross-worktree staged-blob guard.
|
|
112
|
+
#
|
|
113
|
+
# Refuses a commit that stages a NEW file byte-identical to uncommitted work
|
|
114
|
+
# sitting in a different linked worktree. Where several agents share one
|
|
115
|
+
# repository through worktrees, one agent's in-progress file can be committed
|
|
116
|
+
# by another: the file is real, the branch is real, the commit succeeds and
|
|
117
|
+
# every other gate passes, so nothing reports it. Confirmed once — a 284-line
|
|
118
|
+
# test file staged in one worktree, committed an hour later from elsewhere,
|
|
119
|
+
# byte-identical, under a reworded subject.
|
|
120
|
+
#
|
|
121
|
+
# HERE RATHER THAN IN THE GATE REGISTRY, deliberately. This is a property of
|
|
122
|
+
# the machine — which other worktrees exist and what is sitting in them —
|
|
123
|
+
# not of the project, so there is no task a project could declare to prove it.
|
|
124
|
+
# It also has to run on EVERY commit: the whole failure mode is that nobody
|
|
125
|
+
# notices, so a check that waits to be invoked is a check that was not running
|
|
126
|
+
# on the commit where it mattered.
|
|
127
|
+
#
|
|
128
|
+
# Fails OPEN when the script is missing, unlike the lint-staged preflight
|
|
129
|
+
# above. That preflight guards a tool that reports success for work it did not
|
|
130
|
+
# do, so skipping it cannot be read as a pass. This one only ever adds a
|
|
131
|
+
# refusal — a repository without it is exactly as safe as it was before the
|
|
132
|
+
# guard existed, and blocking every commit in a project that has not yet
|
|
133
|
+
# re-applied templates would be a worse trade than the window it closes.
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
CROSS_WORKTREE_GUARD="node_modules/@codyswann/lisa/all/copy-overwrite/scripts/lisa-cross-worktree-guard.mjs"
|
|
136
|
+
if [ ! -f "$CROSS_WORKTREE_GUARD" ]; then
|
|
137
|
+
CROSS_WORKTREE_GUARD="scripts/lisa-cross-worktree-guard.mjs"
|
|
138
|
+
fi
|
|
139
|
+
if [ ! -f "$CROSS_WORKTREE_GUARD" ]; then
|
|
140
|
+
CROSS_WORKTREE_GUARD="all/copy-overwrite/scripts/lisa-cross-worktree-guard.mjs"
|
|
141
|
+
fi
|
|
142
|
+
if [ -f "$CROSS_WORKTREE_GUARD" ] && command -v node >/dev/null 2>&1; then
|
|
143
|
+
node "$CROSS_WORKTREE_GUARD"
|
|
144
|
+
CROSS_WORKTREE_GUARD_STATUS=$?
|
|
145
|
+
if [ $CROSS_WORKTREE_GUARD_STATUS -ne 0 ]; then
|
|
146
|
+
exit $CROSS_WORKTREE_GUARD_STATUS
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
|
|
110
150
|
# ---------------------------------------------------------------------------
|
|
111
151
|
# Gate-driven checks.
|
|
112
152
|
#
|
|
@@ -80,6 +80,7 @@ scripts/check-verification-coverage.mjs
|
|
|
80
80
|
scripts/classify-maestro-failures.mjs
|
|
81
81
|
scripts/lisa-command-envelope.mjs
|
|
82
82
|
scripts/lisa-commit-msg-gates.mjs
|
|
83
|
+
scripts/lisa-cross-worktree-guard.mjs
|
|
83
84
|
scripts/lisa-destructive-guard.mjs
|
|
84
85
|
scripts/lisa-environment-prepare.mjs
|
|
85
86
|
scripts/lisa-floor-collisions.mjs
|
|
@@ -1388,6 +1388,15 @@ export const parseChangedLineRanges = patch => {
|
|
|
1388
1388
|
|
|
1389
1389
|
/**
|
|
1390
1390
|
* Stryker mutation ranges for one changed file.
|
|
1391
|
+
*
|
|
1392
|
+
* The diff runs against the WORKING TREE, not `base...HEAD`. Stryker mutates
|
|
1393
|
+
* the files on disk, so a window derived from committed state describes lines
|
|
1394
|
+
* that may no longer be where it says they are: any uncommitted edit above the
|
|
1395
|
+
* change shifts the numbering out from under the window, and the run then
|
|
1396
|
+
* mutates whatever now occupies those lines — measured once as 37 lines of pure
|
|
1397
|
+
* comment, scored, and reported as a verdict about the change. It could as
|
|
1398
|
+
* easily have produced a pass. Diffing what Stryker actually reads is what
|
|
1399
|
+
* makes the window and the subject the same thing.
|
|
1391
1400
|
* @param {string} cwd - Project root.
|
|
1392
1401
|
* @param {string} base - Merge-base sha.
|
|
1393
1402
|
* @param {string} file - Repository-relative path.
|
|
@@ -1395,14 +1404,7 @@ export const parseChangedLineRanges = patch => {
|
|
|
1395
1404
|
*/
|
|
1396
1405
|
export const selectChangedLineRanges = (cwd, base, file) =>
|
|
1397
1406
|
parseChangedLineRanges(
|
|
1398
|
-
git(cwd, [
|
|
1399
|
-
"diff",
|
|
1400
|
-
"--unified=0",
|
|
1401
|
-
"--diff-filter=ACMR",
|
|
1402
|
-
`${base}...HEAD`,
|
|
1403
|
-
"--",
|
|
1404
|
-
file,
|
|
1405
|
-
])
|
|
1407
|
+
git(cwd, ["diff", "--unified=0", "--diff-filter=ACMR", base, "--", file])
|
|
1406
1408
|
).map(({ start, end }) => `${file}:${start}-${end}`);
|
|
1407
1409
|
|
|
1408
1410
|
/**
|
|
@@ -1416,12 +1418,11 @@ export const selectChangedLineRanges = (cwd, base, file) =>
|
|
|
1416
1418
|
* files no mutation tool here reaches.
|
|
1417
1419
|
*/
|
|
1418
1420
|
export const selectChangedTargets = (cwd, base, patterns) => {
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
])
|
|
1421
|
+
// Working tree, not `base...HEAD`, for the same reason the line ranges are:
|
|
1422
|
+
// a target whose only change is uncommitted is invisible to committed state,
|
|
1423
|
+
// so the gate printed "0 mutate targets" and exited 0 while Stryker, pointed
|
|
1424
|
+
// at the same tree, had a changed target in front of it.
|
|
1425
|
+
const changed = git(cwd, ["diff", "--name-only", "--diff-filter=ACMRD", base])
|
|
1425
1426
|
.split("\n")
|
|
1426
1427
|
.map(file => file.trim())
|
|
1427
1428
|
.filter(Boolean);
|
|
@@ -2115,6 +2116,11 @@ export const reportRun = (cwd, result) => {
|
|
|
2115
2116
|
export const runGate = (cwd = process.cwd(), argv = []) => {
|
|
2116
2117
|
const gate = readGate(cwd);
|
|
2117
2118
|
const enabled = envFlag("MUTATION_ENABLED") ?? gate.enabled === true;
|
|
2119
|
+
// Reads like the LOCAL branch and is not: `resolveDiffBase` probes
|
|
2120
|
+
// `origin/<since>` first and only falls back to the local name, so a stale
|
|
2121
|
+
// local `main` does not drag every commit it is missing into the diff. Said
|
|
2122
|
+
// here because the line alone has already been read the other way and filed
|
|
2123
|
+
// as a defect; the behaviour is pinned by a test rather than by this note.
|
|
2118
2124
|
const since = process.env.MUTATION_SINCE || gate.since || "main";
|
|
2119
2125
|
|
|
2120
2126
|
if (!enabled) {
|