@codyswann/lisa 4.45.13 → 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.
Files changed (66) hide show
  1. package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +126 -2
  2. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +79 -5
  3. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  4. package/dist/core/lisa-owned-hash-ledger.js +6 -0
  5. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  6. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  7. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  8. package/dist/core/upstream-evidence-manifest.js +6 -3
  9. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  10. package/dist/opencode/plugin-templates/parity-safety-net.sh +79 -5
  11. package/package.json +4 -4
  12. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  14. package/plugins/lisa/hooks/parity-safety-net.sh +79 -5
  15. package/plugins/lisa-agy/hooks/parity-safety-net.sh +79 -5
  16. package/plugins/lisa-agy/plugin.json +1 -1
  17. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  20. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +79 -5
  24. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +79 -5
  26. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo-agy/plugin.json +1 -1
  29. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  39. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  44. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  49. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails-agy/plugin.json +1 -1
  54. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  59. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  64. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  66. 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 diff --binary HEAD [-- <path>] > "$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX.patch")"
244
+ git reset
245
+ git diff --binary HEAD > "$patch"
246
+ git apply -R "$patch"
190
247
 
191
- and restore it later with `git apply <that file>`.
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.13",
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": "0b672ffb838d95cf9622a3aa66d0ff35dfee9a5b",
328
- "gitHead": "0b672ffb838d95cf9622a3aa66d0ff35dfee9a5b",
329
- "lisaReleaseTag": "v4.45.13"
327
+ "lisaReleaseCommit": "1f2ecb6fe848f21bfbfdf11805c3a9a1eed3178e",
328
+ "gitHead": "1f2ecb6fe848f21bfbfdf11805c3a9a1eed3178e",
329
+ "lisaReleaseTag": "v4.45.14"
330
330
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.45.13",
3
+ "version": "4.45.14",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.45.13",
3
+ "version": "4.45.14",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -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 diff --binary HEAD [-- <path>] > "$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX.patch")"
244
+ git reset
245
+ git diff --binary HEAD > "$patch"
246
+ git apply -R "$patch"
190
247
 
191
- and restore it later with `git apply <that file>`.
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 diff --binary HEAD [-- <path>] > "$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX.patch")"
244
+ git reset
245
+ git diff --binary HEAD > "$patch"
246
+ git apply -R "$patch"
190
247
 
191
- and restore it later with `git apply <that file>`.
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.45.13",
3
+ "version": "4.45.14",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.45.13",
3
+ "version": "4.45.14",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.45.13",
3
+ "version": "4.45.14",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.45.13",
3
+ "version": "4.45.14",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.45.13",
3
+ "version": "4.45.14",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.45.13",
3
+ "version": "4.45.14",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.45.13",
3
+ "version": "4.45.14",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -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 diff --binary HEAD [-- <path>] > "$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX.patch")"
244
+ git reset
245
+ git diff --binary HEAD > "$patch"
246
+ git apply -R "$patch"
190
247
 
191
- and restore it later with `git apply <that file>`.
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.45.13",
3
+ "version": "4.45.14",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"