@codyswann/lisa 4.45.12 → 4.45.14
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/gate-failure-diagnosis.mjs +126 -2
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +79 -5
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +6 -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 +6 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +79 -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/parity-safety-net.sh +79 -5
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +79 -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/parity-safety-net.sh +79 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +79 -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/parity-safety-net.sh +79 -5
|
@@ -182,13 +182,73 @@ EOF
|
|
|
182
182
|
# captures everything at risk. `HEAD` includes staged and unstaged changes, and
|
|
183
183
|
# `--binary` keeps binary edits recoverable rather than emitting a text-only
|
|
184
184
|
# placeholder that cannot be applied.
|
|
185
|
+
#
|
|
186
|
+
# Three corrections from issue #3696, each found by EXECUTING the text below
|
|
187
|
+
# rather than reading it:
|
|
188
|
+
#
|
|
189
|
+
# 1. It preserved but never RECOVERED. The text taught how to save the work and
|
|
190
|
+
# how to restore it later, and stopped there — so an agent that followed it
|
|
191
|
+
# exactly and successfully was returned to the identical refusal, because the
|
|
192
|
+
# tree was still dirty. A remedy that does not clear the wall it is printed
|
|
193
|
+
# at is a detour, and #3722 shipped only half the path. The discard branch
|
|
194
|
+
# below is the other half, and every command in it is one THIS GUARD ALREADY
|
|
195
|
+
# PERMITS: mixed `git reset`, `git diff`, and `git apply` are matched by no
|
|
196
|
+
# guard in this file. Nothing was loosened to make room for it — a blind
|
|
197
|
+
# `git reset --hard` on a dirty tree is still refused, unconditionally.
|
|
198
|
+
#
|
|
199
|
+
# That the operator must first WRITE DOWN what they are discarding is the
|
|
200
|
+
# feature, not a cost. It is a better acknowledgement than a marker the guard
|
|
201
|
+
# could look for, because a marker agents can type is a marker agents learn
|
|
202
|
+
# to type reflexively; this one cannot be satisfied without producing the
|
|
203
|
+
# artifact that makes the discard reversible.
|
|
204
|
+
#
|
|
205
|
+
# 2. It did not run as printed. `[-- <path>]` is conventional notation for an
|
|
206
|
+
# optional argument, but bash expands `[...]` as a glob, so pasting the line
|
|
207
|
+
# verbatim failed with `bash: path: No such file or directory` and wrote no
|
|
208
|
+
# patch at all. Guard remedies are unreviewed prose that agents execute
|
|
209
|
+
# literally, so the optional part is now prose and every line is runnable.
|
|
210
|
+
#
|
|
211
|
+
# 3. `mktemp` was silently a no-op. BSD/macOS mktemp substitutes only TRAILING
|
|
212
|
+
# `X`s, so the `.patch` suffix meant the template came back as the literal
|
|
213
|
+
# string `lisa-preserve-XXXXXX.patch` — one FIXED filename, where successive
|
|
214
|
+
# preserves overwrite each other, in the exact place the comment above claims
|
|
215
|
+
# uniqueness. The suffix is gone rather than spelled portably: GNU mktemp
|
|
216
|
+
# offers `--suffix` and BSD does not, and a patch file does not need an
|
|
217
|
+
# extension. The regression test asserts the produced name DIFFERS from the
|
|
218
|
+
# template, because asserting that the word `mktemp` appears is what let this
|
|
219
|
+
# ship.
|
|
220
|
+
#
|
|
221
|
+
# NO APOSTROPHES IN THE BODY BELOW. It is a quoted heredoc nested inside a `$( )`
|
|
222
|
+
# command substitution, and bash tracks quote state through the body when it
|
|
223
|
+
# scans for the closing paren — so a single `'` (in "the agent's TMPDIR", say)
|
|
224
|
+
# poisons the parse from there to the next quote and the whole file stops being
|
|
225
|
+
# valid shell, with the reported error landing hundreds of lines away in
|
|
226
|
+
# untouched code. Every guidance body in this file avoids them for this reason.
|
|
227
|
+
# Write "of this session" rather than a possessive.
|
|
185
228
|
PRESERVE_GUIDANCE="$(
|
|
186
229
|
cat <<'EOF'
|
|
187
|
-
Preserve the work first, per-worktree
|
|
230
|
+
Preserve the work first, per-worktree. `mktemp` names the file, so nothing has
|
|
231
|
+
to invent one, and it lands in the TMPDIR of this session, where no sibling
|
|
232
|
+
agent can reach it:
|
|
233
|
+
|
|
234
|
+
patch="$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX")"
|
|
235
|
+
git diff --binary HEAD > "$patch"
|
|
236
|
+
|
|
237
|
+
Add `-- <path>` before the `>` to scope the capture to one path. Restore it
|
|
238
|
+
later with `git apply "$patch"`.
|
|
239
|
+
|
|
240
|
+
If the work is NOT yours -- a concurrent agent dirtied this worktree and your
|
|
241
|
+
judgement is "none of this is mine" -- that same capture is how you return the
|
|
242
|
+
tree to HEAD:
|
|
188
243
|
|
|
189
|
-
git
|
|
244
|
+
git reset
|
|
245
|
+
git diff --binary HEAD > "$patch"
|
|
246
|
+
git apply -R "$patch"
|
|
190
247
|
|
|
191
|
-
and
|
|
248
|
+
`git reset` clears staged and unmerged entries first, so the capture sees the
|
|
249
|
+
whole tree as one diff; reversing it leaves the tree at HEAD with "$patch" as
|
|
250
|
+
the record of what you discarded. Untracked files survive, exactly as they
|
|
251
|
+
survive `git reset --hard`.
|
|
192
252
|
|
|
193
253
|
Do NOT reach for `git stash`. One stash stack is shared by every worktree of a
|
|
194
254
|
clone, so a concurrently running agent can consume the entry you just pushed.
|
|
@@ -1811,18 +1871,32 @@ $DESTRUCTIVE_GUIDANCE"
|
|
|
1811
1871
|
fi
|
|
1812
1872
|
|
|
1813
1873
|
# 5. `git switch` discards: `--discard-changes` and its `-f/--force` aliases.
|
|
1874
|
+
# Carries the same guidance as guards 3 and 4 (issue #3696). It discards the
|
|
1875
|
+
# same uncommitted work by the same mechanism, so a refusal that named no
|
|
1876
|
+
# remedy left the reader to invent one — and the one they invent is the
|
|
1877
|
+
# stash, which is the single thing $PRESERVE_GUIDANCE exists to steer off.
|
|
1814
1878
|
if matches "${GIT_CMD}switch${GIT_TOKENS}"'(--discard-changes|--force|-f)([[:space:]]|=|$)'; then
|
|
1815
|
-
block "git switch discarding local changes (--discard-changes/-f/--force)"
|
|
1879
|
+
block "git switch discarding local changes (--discard-changes/-f/--force)" "$PRESERVE_GUIDANCE
|
|
1880
|
+
|
|
1881
|
+
$DESTRUCTIVE_GUIDANCE"
|
|
1816
1882
|
fi
|
|
1817
1883
|
|
|
1818
1884
|
# 6. `git restore` defaults to overwriting the WORKTREE — a silent discard. Only
|
|
1819
1885
|
# the pure staging-area form is safe: `--staged` present and `--worktree`
|
|
1820
1886
|
# absent. Two conditions, because an ERE has no lookahead: `--staged
|
|
1821
1887
|
# --worktree` still discards, so `--worktree` blocks regardless of `--staged`.
|
|
1888
|
+
# Carries the same guidance as guards 3 and 4 (issue #3696). The permitted
|
|
1889
|
+
# alternative this reason names — `git restore --staged <path>` — is real,
|
|
1890
|
+
# but it only unstages; it leaves the worktree exactly as dirty as it found
|
|
1891
|
+
# it. So the operator who reached for `git restore` to CLEAN a tree is told
|
|
1892
|
+
# of an operation that does not do what they came for, and was told nothing
|
|
1893
|
+
# about what does. This is the refusal #3696 was filed from.
|
|
1822
1894
|
if matches "${GIT_CMD}"'restore([[:space:]]|$)'; then
|
|
1823
1895
|
if matches "${GIT_CMD}"'restore[^;&|]*--worktree' \
|
|
1824
1896
|
|| ! matches "${GIT_CMD}"'restore[^;&|]*--staged'; then
|
|
1825
|
-
block "git restore overwriting worktree files (only 'git restore --staged <path>' without --worktree is allowed)"
|
|
1897
|
+
block "git restore overwriting worktree files (only 'git restore --staged <path>' without --worktree is allowed)" "$PRESERVE_GUIDANCE
|
|
1898
|
+
|
|
1899
|
+
$DESTRUCTIVE_GUIDANCE"
|
|
1826
1900
|
fi
|
|
1827
1901
|
fi
|
|
1828
1902
|
|
package/package.json
CHANGED
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
"zod-validation-error": "^4.0.0"
|
|
177
177
|
},
|
|
178
178
|
"name": "@codyswann/lisa",
|
|
179
|
-
"version": "4.45.
|
|
179
|
+
"version": "4.45.14",
|
|
180
180
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
181
181
|
"main": "dist/index.js",
|
|
182
182
|
"exports": {
|
|
@@ -324,7 +324,7 @@
|
|
|
324
324
|
"test": "tests"
|
|
325
325
|
},
|
|
326
326
|
"types": "./dist/index.d.ts",
|
|
327
|
-
"lisaReleaseCommit": "
|
|
328
|
-
"gitHead": "
|
|
329
|
-
"lisaReleaseTag": "v4.45.
|
|
327
|
+
"lisaReleaseCommit": "1f2ecb6fe848f21bfbfdf11805c3a9a1eed3178e",
|
|
328
|
+
"gitHead": "1f2ecb6fe848f21bfbfdf11805c3a9a1eed3178e",
|
|
329
|
+
"lisaReleaseTag": "v4.45.14"
|
|
330
330
|
}
|
|
@@ -182,13 +182,73 @@ EOF
|
|
|
182
182
|
# captures everything at risk. `HEAD` includes staged and unstaged changes, and
|
|
183
183
|
# `--binary` keeps binary edits recoverable rather than emitting a text-only
|
|
184
184
|
# placeholder that cannot be applied.
|
|
185
|
+
#
|
|
186
|
+
# Three corrections from issue #3696, each found by EXECUTING the text below
|
|
187
|
+
# rather than reading it:
|
|
188
|
+
#
|
|
189
|
+
# 1. It preserved but never RECOVERED. The text taught how to save the work and
|
|
190
|
+
# how to restore it later, and stopped there — so an agent that followed it
|
|
191
|
+
# exactly and successfully was returned to the identical refusal, because the
|
|
192
|
+
# tree was still dirty. A remedy that does not clear the wall it is printed
|
|
193
|
+
# at is a detour, and #3722 shipped only half the path. The discard branch
|
|
194
|
+
# below is the other half, and every command in it is one THIS GUARD ALREADY
|
|
195
|
+
# PERMITS: mixed `git reset`, `git diff`, and `git apply` are matched by no
|
|
196
|
+
# guard in this file. Nothing was loosened to make room for it — a blind
|
|
197
|
+
# `git reset --hard` on a dirty tree is still refused, unconditionally.
|
|
198
|
+
#
|
|
199
|
+
# That the operator must first WRITE DOWN what they are discarding is the
|
|
200
|
+
# feature, not a cost. It is a better acknowledgement than a marker the guard
|
|
201
|
+
# could look for, because a marker agents can type is a marker agents learn
|
|
202
|
+
# to type reflexively; this one cannot be satisfied without producing the
|
|
203
|
+
# artifact that makes the discard reversible.
|
|
204
|
+
#
|
|
205
|
+
# 2. It did not run as printed. `[-- <path>]` is conventional notation for an
|
|
206
|
+
# optional argument, but bash expands `[...]` as a glob, so pasting the line
|
|
207
|
+
# verbatim failed with `bash: path: No such file or directory` and wrote no
|
|
208
|
+
# patch at all. Guard remedies are unreviewed prose that agents execute
|
|
209
|
+
# literally, so the optional part is now prose and every line is runnable.
|
|
210
|
+
#
|
|
211
|
+
# 3. `mktemp` was silently a no-op. BSD/macOS mktemp substitutes only TRAILING
|
|
212
|
+
# `X`s, so the `.patch` suffix meant the template came back as the literal
|
|
213
|
+
# string `lisa-preserve-XXXXXX.patch` — one FIXED filename, where successive
|
|
214
|
+
# preserves overwrite each other, in the exact place the comment above claims
|
|
215
|
+
# uniqueness. The suffix is gone rather than spelled portably: GNU mktemp
|
|
216
|
+
# offers `--suffix` and BSD does not, and a patch file does not need an
|
|
217
|
+
# extension. The regression test asserts the produced name DIFFERS from the
|
|
218
|
+
# template, because asserting that the word `mktemp` appears is what let this
|
|
219
|
+
# ship.
|
|
220
|
+
#
|
|
221
|
+
# NO APOSTROPHES IN THE BODY BELOW. It is a quoted heredoc nested inside a `$( )`
|
|
222
|
+
# command substitution, and bash tracks quote state through the body when it
|
|
223
|
+
# scans for the closing paren — so a single `'` (in "the agent's TMPDIR", say)
|
|
224
|
+
# poisons the parse from there to the next quote and the whole file stops being
|
|
225
|
+
# valid shell, with the reported error landing hundreds of lines away in
|
|
226
|
+
# untouched code. Every guidance body in this file avoids them for this reason.
|
|
227
|
+
# Write "of this session" rather than a possessive.
|
|
185
228
|
PRESERVE_GUIDANCE="$(
|
|
186
229
|
cat <<'EOF'
|
|
187
|
-
Preserve the work first, per-worktree
|
|
230
|
+
Preserve the work first, per-worktree. `mktemp` names the file, so nothing has
|
|
231
|
+
to invent one, and it lands in the TMPDIR of this session, where no sibling
|
|
232
|
+
agent can reach it:
|
|
233
|
+
|
|
234
|
+
patch="$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX")"
|
|
235
|
+
git diff --binary HEAD > "$patch"
|
|
236
|
+
|
|
237
|
+
Add `-- <path>` before the `>` to scope the capture to one path. Restore it
|
|
238
|
+
later with `git apply "$patch"`.
|
|
239
|
+
|
|
240
|
+
If the work is NOT yours -- a concurrent agent dirtied this worktree and your
|
|
241
|
+
judgement is "none of this is mine" -- that same capture is how you return the
|
|
242
|
+
tree to HEAD:
|
|
188
243
|
|
|
189
|
-
git
|
|
244
|
+
git reset
|
|
245
|
+
git diff --binary HEAD > "$patch"
|
|
246
|
+
git apply -R "$patch"
|
|
190
247
|
|
|
191
|
-
and
|
|
248
|
+
`git reset` clears staged and unmerged entries first, so the capture sees the
|
|
249
|
+
whole tree as one diff; reversing it leaves the tree at HEAD with "$patch" as
|
|
250
|
+
the record of what you discarded. Untracked files survive, exactly as they
|
|
251
|
+
survive `git reset --hard`.
|
|
192
252
|
|
|
193
253
|
Do NOT reach for `git stash`. One stash stack is shared by every worktree of a
|
|
194
254
|
clone, so a concurrently running agent can consume the entry you just pushed.
|
|
@@ -1811,18 +1871,32 @@ $DESTRUCTIVE_GUIDANCE"
|
|
|
1811
1871
|
fi
|
|
1812
1872
|
|
|
1813
1873
|
# 5. `git switch` discards: `--discard-changes` and its `-f/--force` aliases.
|
|
1874
|
+
# Carries the same guidance as guards 3 and 4 (issue #3696). It discards the
|
|
1875
|
+
# same uncommitted work by the same mechanism, so a refusal that named no
|
|
1876
|
+
# remedy left the reader to invent one — and the one they invent is the
|
|
1877
|
+
# stash, which is the single thing $PRESERVE_GUIDANCE exists to steer off.
|
|
1814
1878
|
if matches "${GIT_CMD}switch${GIT_TOKENS}"'(--discard-changes|--force|-f)([[:space:]]|=|$)'; then
|
|
1815
|
-
block "git switch discarding local changes (--discard-changes/-f/--force)"
|
|
1879
|
+
block "git switch discarding local changes (--discard-changes/-f/--force)" "$PRESERVE_GUIDANCE
|
|
1880
|
+
|
|
1881
|
+
$DESTRUCTIVE_GUIDANCE"
|
|
1816
1882
|
fi
|
|
1817
1883
|
|
|
1818
1884
|
# 6. `git restore` defaults to overwriting the WORKTREE — a silent discard. Only
|
|
1819
1885
|
# the pure staging-area form is safe: `--staged` present and `--worktree`
|
|
1820
1886
|
# absent. Two conditions, because an ERE has no lookahead: `--staged
|
|
1821
1887
|
# --worktree` still discards, so `--worktree` blocks regardless of `--staged`.
|
|
1888
|
+
# Carries the same guidance as guards 3 and 4 (issue #3696). The permitted
|
|
1889
|
+
# alternative this reason names — `git restore --staged <path>` — is real,
|
|
1890
|
+
# but it only unstages; it leaves the worktree exactly as dirty as it found
|
|
1891
|
+
# it. So the operator who reached for `git restore` to CLEAN a tree is told
|
|
1892
|
+
# of an operation that does not do what they came for, and was told nothing
|
|
1893
|
+
# about what does. This is the refusal #3696 was filed from.
|
|
1822
1894
|
if matches "${GIT_CMD}"'restore([[:space:]]|$)'; then
|
|
1823
1895
|
if matches "${GIT_CMD}"'restore[^;&|]*--worktree' \
|
|
1824
1896
|
|| ! matches "${GIT_CMD}"'restore[^;&|]*--staged'; then
|
|
1825
|
-
block "git restore overwriting worktree files (only 'git restore --staged <path>' without --worktree is allowed)"
|
|
1897
|
+
block "git restore overwriting worktree files (only 'git restore --staged <path>' without --worktree is allowed)" "$PRESERVE_GUIDANCE
|
|
1898
|
+
|
|
1899
|
+
$DESTRUCTIVE_GUIDANCE"
|
|
1826
1900
|
fi
|
|
1827
1901
|
fi
|
|
1828
1902
|
|
|
@@ -182,13 +182,73 @@ EOF
|
|
|
182
182
|
# captures everything at risk. `HEAD` includes staged and unstaged changes, and
|
|
183
183
|
# `--binary` keeps binary edits recoverable rather than emitting a text-only
|
|
184
184
|
# placeholder that cannot be applied.
|
|
185
|
+
#
|
|
186
|
+
# Three corrections from issue #3696, each found by EXECUTING the text below
|
|
187
|
+
# rather than reading it:
|
|
188
|
+
#
|
|
189
|
+
# 1. It preserved but never RECOVERED. The text taught how to save the work and
|
|
190
|
+
# how to restore it later, and stopped there — so an agent that followed it
|
|
191
|
+
# exactly and successfully was returned to the identical refusal, because the
|
|
192
|
+
# tree was still dirty. A remedy that does not clear the wall it is printed
|
|
193
|
+
# at is a detour, and #3722 shipped only half the path. The discard branch
|
|
194
|
+
# below is the other half, and every command in it is one THIS GUARD ALREADY
|
|
195
|
+
# PERMITS: mixed `git reset`, `git diff`, and `git apply` are matched by no
|
|
196
|
+
# guard in this file. Nothing was loosened to make room for it — a blind
|
|
197
|
+
# `git reset --hard` on a dirty tree is still refused, unconditionally.
|
|
198
|
+
#
|
|
199
|
+
# That the operator must first WRITE DOWN what they are discarding is the
|
|
200
|
+
# feature, not a cost. It is a better acknowledgement than a marker the guard
|
|
201
|
+
# could look for, because a marker agents can type is a marker agents learn
|
|
202
|
+
# to type reflexively; this one cannot be satisfied without producing the
|
|
203
|
+
# artifact that makes the discard reversible.
|
|
204
|
+
#
|
|
205
|
+
# 2. It did not run as printed. `[-- <path>]` is conventional notation for an
|
|
206
|
+
# optional argument, but bash expands `[...]` as a glob, so pasting the line
|
|
207
|
+
# verbatim failed with `bash: path: No such file or directory` and wrote no
|
|
208
|
+
# patch at all. Guard remedies are unreviewed prose that agents execute
|
|
209
|
+
# literally, so the optional part is now prose and every line is runnable.
|
|
210
|
+
#
|
|
211
|
+
# 3. `mktemp` was silently a no-op. BSD/macOS mktemp substitutes only TRAILING
|
|
212
|
+
# `X`s, so the `.patch` suffix meant the template came back as the literal
|
|
213
|
+
# string `lisa-preserve-XXXXXX.patch` — one FIXED filename, where successive
|
|
214
|
+
# preserves overwrite each other, in the exact place the comment above claims
|
|
215
|
+
# uniqueness. The suffix is gone rather than spelled portably: GNU mktemp
|
|
216
|
+
# offers `--suffix` and BSD does not, and a patch file does not need an
|
|
217
|
+
# extension. The regression test asserts the produced name DIFFERS from the
|
|
218
|
+
# template, because asserting that the word `mktemp` appears is what let this
|
|
219
|
+
# ship.
|
|
220
|
+
#
|
|
221
|
+
# NO APOSTROPHES IN THE BODY BELOW. It is a quoted heredoc nested inside a `$( )`
|
|
222
|
+
# command substitution, and bash tracks quote state through the body when it
|
|
223
|
+
# scans for the closing paren — so a single `'` (in "the agent's TMPDIR", say)
|
|
224
|
+
# poisons the parse from there to the next quote and the whole file stops being
|
|
225
|
+
# valid shell, with the reported error landing hundreds of lines away in
|
|
226
|
+
# untouched code. Every guidance body in this file avoids them for this reason.
|
|
227
|
+
# Write "of this session" rather than a possessive.
|
|
185
228
|
PRESERVE_GUIDANCE="$(
|
|
186
229
|
cat <<'EOF'
|
|
187
|
-
Preserve the work first, per-worktree
|
|
230
|
+
Preserve the work first, per-worktree. `mktemp` names the file, so nothing has
|
|
231
|
+
to invent one, and it lands in the TMPDIR of this session, where no sibling
|
|
232
|
+
agent can reach it:
|
|
233
|
+
|
|
234
|
+
patch="$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX")"
|
|
235
|
+
git diff --binary HEAD > "$patch"
|
|
236
|
+
|
|
237
|
+
Add `-- <path>` before the `>` to scope the capture to one path. Restore it
|
|
238
|
+
later with `git apply "$patch"`.
|
|
239
|
+
|
|
240
|
+
If the work is NOT yours -- a concurrent agent dirtied this worktree and your
|
|
241
|
+
judgement is "none of this is mine" -- that same capture is how you return the
|
|
242
|
+
tree to HEAD:
|
|
188
243
|
|
|
189
|
-
git
|
|
244
|
+
git reset
|
|
245
|
+
git diff --binary HEAD > "$patch"
|
|
246
|
+
git apply -R "$patch"
|
|
190
247
|
|
|
191
|
-
and
|
|
248
|
+
`git reset` clears staged and unmerged entries first, so the capture sees the
|
|
249
|
+
whole tree as one diff; reversing it leaves the tree at HEAD with "$patch" as
|
|
250
|
+
the record of what you discarded. Untracked files survive, exactly as they
|
|
251
|
+
survive `git reset --hard`.
|
|
192
252
|
|
|
193
253
|
Do NOT reach for `git stash`. One stash stack is shared by every worktree of a
|
|
194
254
|
clone, so a concurrently running agent can consume the entry you just pushed.
|
|
@@ -1811,18 +1871,32 @@ $DESTRUCTIVE_GUIDANCE"
|
|
|
1811
1871
|
fi
|
|
1812
1872
|
|
|
1813
1873
|
# 5. `git switch` discards: `--discard-changes` and its `-f/--force` aliases.
|
|
1874
|
+
# Carries the same guidance as guards 3 and 4 (issue #3696). It discards the
|
|
1875
|
+
# same uncommitted work by the same mechanism, so a refusal that named no
|
|
1876
|
+
# remedy left the reader to invent one — and the one they invent is the
|
|
1877
|
+
# stash, which is the single thing $PRESERVE_GUIDANCE exists to steer off.
|
|
1814
1878
|
if matches "${GIT_CMD}switch${GIT_TOKENS}"'(--discard-changes|--force|-f)([[:space:]]|=|$)'; then
|
|
1815
|
-
block "git switch discarding local changes (--discard-changes/-f/--force)"
|
|
1879
|
+
block "git switch discarding local changes (--discard-changes/-f/--force)" "$PRESERVE_GUIDANCE
|
|
1880
|
+
|
|
1881
|
+
$DESTRUCTIVE_GUIDANCE"
|
|
1816
1882
|
fi
|
|
1817
1883
|
|
|
1818
1884
|
# 6. `git restore` defaults to overwriting the WORKTREE — a silent discard. Only
|
|
1819
1885
|
# the pure staging-area form is safe: `--staged` present and `--worktree`
|
|
1820
1886
|
# absent. Two conditions, because an ERE has no lookahead: `--staged
|
|
1821
1887
|
# --worktree` still discards, so `--worktree` blocks regardless of `--staged`.
|
|
1888
|
+
# Carries the same guidance as guards 3 and 4 (issue #3696). The permitted
|
|
1889
|
+
# alternative this reason names — `git restore --staged <path>` — is real,
|
|
1890
|
+
# but it only unstages; it leaves the worktree exactly as dirty as it found
|
|
1891
|
+
# it. So the operator who reached for `git restore` to CLEAN a tree is told
|
|
1892
|
+
# of an operation that does not do what they came for, and was told nothing
|
|
1893
|
+
# about what does. This is the refusal #3696 was filed from.
|
|
1822
1894
|
if matches "${GIT_CMD}"'restore([[:space:]]|$)'; then
|
|
1823
1895
|
if matches "${GIT_CMD}"'restore[^;&|]*--worktree' \
|
|
1824
1896
|
|| ! matches "${GIT_CMD}"'restore[^;&|]*--staged'; then
|
|
1825
|
-
block "git restore overwriting worktree files (only 'git restore --staged <path>' without --worktree is allowed)"
|
|
1897
|
+
block "git restore overwriting worktree files (only 'git restore --staged <path>' without --worktree is allowed)" "$PRESERVE_GUIDANCE
|
|
1898
|
+
|
|
1899
|
+
$DESTRUCTIVE_GUIDANCE"
|
|
1826
1900
|
fi
|
|
1827
1901
|
fi
|
|
1828
1902
|
|
|
@@ -182,13 +182,73 @@ EOF
|
|
|
182
182
|
# captures everything at risk. `HEAD` includes staged and unstaged changes, and
|
|
183
183
|
# `--binary` keeps binary edits recoverable rather than emitting a text-only
|
|
184
184
|
# placeholder that cannot be applied.
|
|
185
|
+
#
|
|
186
|
+
# Three corrections from issue #3696, each found by EXECUTING the text below
|
|
187
|
+
# rather than reading it:
|
|
188
|
+
#
|
|
189
|
+
# 1. It preserved but never RECOVERED. The text taught how to save the work and
|
|
190
|
+
# how to restore it later, and stopped there — so an agent that followed it
|
|
191
|
+
# exactly and successfully was returned to the identical refusal, because the
|
|
192
|
+
# tree was still dirty. A remedy that does not clear the wall it is printed
|
|
193
|
+
# at is a detour, and #3722 shipped only half the path. The discard branch
|
|
194
|
+
# below is the other half, and every command in it is one THIS GUARD ALREADY
|
|
195
|
+
# PERMITS: mixed `git reset`, `git diff`, and `git apply` are matched by no
|
|
196
|
+
# guard in this file. Nothing was loosened to make room for it — a blind
|
|
197
|
+
# `git reset --hard` on a dirty tree is still refused, unconditionally.
|
|
198
|
+
#
|
|
199
|
+
# That the operator must first WRITE DOWN what they are discarding is the
|
|
200
|
+
# feature, not a cost. It is a better acknowledgement than a marker the guard
|
|
201
|
+
# could look for, because a marker agents can type is a marker agents learn
|
|
202
|
+
# to type reflexively; this one cannot be satisfied without producing the
|
|
203
|
+
# artifact that makes the discard reversible.
|
|
204
|
+
#
|
|
205
|
+
# 2. It did not run as printed. `[-- <path>]` is conventional notation for an
|
|
206
|
+
# optional argument, but bash expands `[...]` as a glob, so pasting the line
|
|
207
|
+
# verbatim failed with `bash: path: No such file or directory` and wrote no
|
|
208
|
+
# patch at all. Guard remedies are unreviewed prose that agents execute
|
|
209
|
+
# literally, so the optional part is now prose and every line is runnable.
|
|
210
|
+
#
|
|
211
|
+
# 3. `mktemp` was silently a no-op. BSD/macOS mktemp substitutes only TRAILING
|
|
212
|
+
# `X`s, so the `.patch` suffix meant the template came back as the literal
|
|
213
|
+
# string `lisa-preserve-XXXXXX.patch` — one FIXED filename, where successive
|
|
214
|
+
# preserves overwrite each other, in the exact place the comment above claims
|
|
215
|
+
# uniqueness. The suffix is gone rather than spelled portably: GNU mktemp
|
|
216
|
+
# offers `--suffix` and BSD does not, and a patch file does not need an
|
|
217
|
+
# extension. The regression test asserts the produced name DIFFERS from the
|
|
218
|
+
# template, because asserting that the word `mktemp` appears is what let this
|
|
219
|
+
# ship.
|
|
220
|
+
#
|
|
221
|
+
# NO APOSTROPHES IN THE BODY BELOW. It is a quoted heredoc nested inside a `$( )`
|
|
222
|
+
# command substitution, and bash tracks quote state through the body when it
|
|
223
|
+
# scans for the closing paren — so a single `'` (in "the agent's TMPDIR", say)
|
|
224
|
+
# poisons the parse from there to the next quote and the whole file stops being
|
|
225
|
+
# valid shell, with the reported error landing hundreds of lines away in
|
|
226
|
+
# untouched code. Every guidance body in this file avoids them for this reason.
|
|
227
|
+
# Write "of this session" rather than a possessive.
|
|
185
228
|
PRESERVE_GUIDANCE="$(
|
|
186
229
|
cat <<'EOF'
|
|
187
|
-
Preserve the work first, per-worktree
|
|
230
|
+
Preserve the work first, per-worktree. `mktemp` names the file, so nothing has
|
|
231
|
+
to invent one, and it lands in the TMPDIR of this session, where no sibling
|
|
232
|
+
agent can reach it:
|
|
233
|
+
|
|
234
|
+
patch="$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX")"
|
|
235
|
+
git diff --binary HEAD > "$patch"
|
|
236
|
+
|
|
237
|
+
Add `-- <path>` before the `>` to scope the capture to one path. Restore it
|
|
238
|
+
later with `git apply "$patch"`.
|
|
239
|
+
|
|
240
|
+
If the work is NOT yours -- a concurrent agent dirtied this worktree and your
|
|
241
|
+
judgement is "none of this is mine" -- that same capture is how you return the
|
|
242
|
+
tree to HEAD:
|
|
188
243
|
|
|
189
|
-
git
|
|
244
|
+
git reset
|
|
245
|
+
git diff --binary HEAD > "$patch"
|
|
246
|
+
git apply -R "$patch"
|
|
190
247
|
|
|
191
|
-
and
|
|
248
|
+
`git reset` clears staged and unmerged entries first, so the capture sees the
|
|
249
|
+
whole tree as one diff; reversing it leaves the tree at HEAD with "$patch" as
|
|
250
|
+
the record of what you discarded. Untracked files survive, exactly as they
|
|
251
|
+
survive `git reset --hard`.
|
|
192
252
|
|
|
193
253
|
Do NOT reach for `git stash`. One stash stack is shared by every worktree of a
|
|
194
254
|
clone, so a concurrently running agent can consume the entry you just pushed.
|
|
@@ -1811,18 +1871,32 @@ $DESTRUCTIVE_GUIDANCE"
|
|
|
1811
1871
|
fi
|
|
1812
1872
|
|
|
1813
1873
|
# 5. `git switch` discards: `--discard-changes` and its `-f/--force` aliases.
|
|
1874
|
+
# Carries the same guidance as guards 3 and 4 (issue #3696). It discards the
|
|
1875
|
+
# same uncommitted work by the same mechanism, so a refusal that named no
|
|
1876
|
+
# remedy left the reader to invent one — and the one they invent is the
|
|
1877
|
+
# stash, which is the single thing $PRESERVE_GUIDANCE exists to steer off.
|
|
1814
1878
|
if matches "${GIT_CMD}switch${GIT_TOKENS}"'(--discard-changes|--force|-f)([[:space:]]|=|$)'; then
|
|
1815
|
-
block "git switch discarding local changes (--discard-changes/-f/--force)"
|
|
1879
|
+
block "git switch discarding local changes (--discard-changes/-f/--force)" "$PRESERVE_GUIDANCE
|
|
1880
|
+
|
|
1881
|
+
$DESTRUCTIVE_GUIDANCE"
|
|
1816
1882
|
fi
|
|
1817
1883
|
|
|
1818
1884
|
# 6. `git restore` defaults to overwriting the WORKTREE — a silent discard. Only
|
|
1819
1885
|
# the pure staging-area form is safe: `--staged` present and `--worktree`
|
|
1820
1886
|
# absent. Two conditions, because an ERE has no lookahead: `--staged
|
|
1821
1887
|
# --worktree` still discards, so `--worktree` blocks regardless of `--staged`.
|
|
1888
|
+
# Carries the same guidance as guards 3 and 4 (issue #3696). The permitted
|
|
1889
|
+
# alternative this reason names — `git restore --staged <path>` — is real,
|
|
1890
|
+
# but it only unstages; it leaves the worktree exactly as dirty as it found
|
|
1891
|
+
# it. So the operator who reached for `git restore` to CLEAN a tree is told
|
|
1892
|
+
# of an operation that does not do what they came for, and was told nothing
|
|
1893
|
+
# about what does. This is the refusal #3696 was filed from.
|
|
1822
1894
|
if matches "${GIT_CMD}"'restore([[:space:]]|$)'; then
|
|
1823
1895
|
if matches "${GIT_CMD}"'restore[^;&|]*--worktree' \
|
|
1824
1896
|
|| ! matches "${GIT_CMD}"'restore[^;&|]*--staged'; then
|
|
1825
|
-
block "git restore overwriting worktree files (only 'git restore --staged <path>' without --worktree is allowed)"
|
|
1897
|
+
block "git restore overwriting worktree files (only 'git restore --staged <path>' without --worktree is allowed)" "$PRESERVE_GUIDANCE
|
|
1898
|
+
|
|
1899
|
+
$DESTRUCTIVE_GUIDANCE"
|
|
1826
1900
|
fi
|
|
1827
1901
|
fi
|
|
1828
1902
|
|