@codyswann/lisa 3.26.2 → 3.26.4
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-hooks/block-managed-file-edits.sh +96 -62
- package/all/create-only/.lisaignore +15 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +2 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +3 -3
- package/dist/utils/ignore-patterns.d.ts +9 -0
- package/dist/utils/ignore-patterns.d.ts.map +1 -1
- package/dist/utils/ignore-patterns.js +57 -25
- package/dist/utils/ignore-patterns.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/block-managed-file-edits.sh +96 -62
- 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-managed-file-edits.sh +96 -62
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +96 -62
- 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-managed-file-edits.sh +96 -62
|
@@ -11,20 +11,29 @@
|
|
|
11
11
|
# would stop agents editing files they are supposed to own, which is worse than
|
|
12
12
|
# the problem being solved.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
14
|
+
# The name `copy-overwrite` is misleading, and two successive versions of this
|
|
15
|
+
# guard got the consequence wrong by trusting it. MEASURED, by mutating four
|
|
16
|
+
# files in a scratch project and running a real `lisa apply` against them:
|
|
16
17
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# everything else (`.json` configs and friends)
|
|
22
|
-
# — replaced wholesale on the next apply, which runs on every install.
|
|
23
|
-
# The edit vanishes.
|
|
18
|
+
# scripts/lisa-gates.mjs (ledger-tracked) → SURVIVED
|
|
19
|
+
# .lintstagedrc.json (untracked, JSON) → SURVIVED
|
|
20
|
+
# .prettierignore (untracked, text) → SURVIVED
|
|
21
|
+
# .yamllint (untracked, text) → SURVIVED
|
|
24
22
|
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
23
|
+
# Summary line: `Overwritten: 0 files` / `Out of date: 3 files (managed
|
|
24
|
+
# templates changed; NOT updated)`.
|
|
25
|
+
#
|
|
26
|
+
# copy-overwrite overwrites an UNMODIFIED copy — it refreshes. It does not
|
|
27
|
+
# overwrite a host-edited one, in any population tested. So the harm is the same
|
|
28
|
+
# for both, and it is not deletion:
|
|
29
|
+
#
|
|
30
|
+
# THE FILE SILENTLY FORKS. It keeps looking current while every upstream fix
|
|
31
|
+
# stops reaching it. Nothing is lost, which is exactly what makes it invisible.
|
|
32
|
+
#
|
|
33
|
+
# Ledger membership changes the MESSAGE apply prints, not the outcome — tracked
|
|
34
|
+
# files get a provenance verdict naming the fork and offering
|
|
35
|
+
# `lisa-guard-capabilities:`; untracked ones get a bare "Out of date" warning.
|
|
36
|
+
# The refusal branches on that so the reader sees the words apply will use.
|
|
28
37
|
#
|
|
29
38
|
# Measured, not hypothetical. Nothing enforced this, so downstream copies were
|
|
30
39
|
# edited and then silently diverged: `classify-maestro-failures.mjs` reached
|
|
@@ -100,43 +109,61 @@ lisaignored() {
|
|
|
100
109
|
local rel="$1"
|
|
101
110
|
local list="$project_root/.lisaignore"
|
|
102
111
|
[ -f "$list" ] || return 1
|
|
112
|
+
# Gitignore precedence: patterns are read in order and the LAST one to select
|
|
113
|
+
# the path decides, so `!x` re-includes something an earlier line ignored.
|
|
114
|
+
# This mirrors `matchesAnyPattern` in `src/utils/ignore-patterns.ts`; the two
|
|
115
|
+
# must agree, or an agent gets blocked on a file apply considers the
|
|
116
|
+
# project's, or waved through on one it does not.
|
|
117
|
+
#
|
|
118
|
+
# `ignored` carries shell truth: 0 = ignored, 1 = not.
|
|
119
|
+
local ignored=1
|
|
103
120
|
local pattern
|
|
104
121
|
while IFS= read -r pattern || [ -n "$pattern" ]; do
|
|
105
122
|
pattern="${pattern#"${pattern%%[![:space:]]*}"}"
|
|
106
123
|
pattern="${pattern%"${pattern##*[![:space:]]}"}"
|
|
107
124
|
[ -n "$pattern" ] || continue
|
|
108
125
|
case "$pattern" in \#*) continue ;; esac
|
|
109
|
-
|
|
110
|
-
# patterns to minimatch, which negates by default, and it combines them with
|
|
111
|
-
# `.some()` — so a single `!scripts/a.mjs` line reports EVERY OTHER PATH as
|
|
112
|
-
# ignored. Measured:
|
|
113
|
-
#
|
|
114
|
-
# patterns=["!scripts/a.mjs"] path=scripts/a.mjs -> ignored=false
|
|
115
|
-
# patterns=["!scripts/a.mjs"] path=scripts/b.mjs -> ignored=TRUE
|
|
116
|
-
#
|
|
117
|
-
# Reproducing that faithfully would mean disabling this guard on a typo.
|
|
118
|
-
# Reproducing the intuitive gitignore reading would mean blocking files the
|
|
119
|
-
# matcher considers ignored. Both are wrong, so the file is treated as
|
|
120
|
-
# claimed and the write is allowed — the same direction this function errs
|
|
121
|
-
# everywhere else. Filed upstream; when the matcher stops negating, delete
|
|
122
|
-
# this branch rather than teaching it a second wrong answer.
|
|
123
|
-
case "$pattern" in !*) return 0 ;; esac
|
|
126
|
+
local negated=1
|
|
124
127
|
case "$pattern" in
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
# A bare `!` selects nothing rather than everything.
|
|
129
|
+
!) continue ;;
|
|
130
|
+
!*)
|
|
131
|
+
negated=0
|
|
132
|
+
pattern="${pattern#!}"
|
|
127
133
|
;;
|
|
134
|
+
# `\!x` is a literal leading `!`, not a negation.
|
|
135
|
+
\\!*) pattern="${pattern#\\}" ;;
|
|
128
136
|
esac
|
|
129
|
-
|
|
130
|
-
case "$rel" in $pattern) return 0 ;; esac
|
|
137
|
+
local selected=1
|
|
131
138
|
case "$pattern" in
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
# shellcheck disable=SC2254 -- ditto, matched against the basename.
|
|
135
|
-
case "${rel##*/}" in $pattern) return 0 ;; esac
|
|
139
|
+
*/)
|
|
140
|
+
case "$rel" in "${pattern%/}"/* | "${pattern%/}") selected=0 ;; esac
|
|
136
141
|
;;
|
|
137
142
|
esac
|
|
143
|
+
if [ "$selected" -ne 0 ]; then
|
|
144
|
+
# shellcheck disable=SC2254 -- the pattern is a glob on purpose.
|
|
145
|
+
case "$rel" in $pattern) selected=0 ;; esac
|
|
146
|
+
fi
|
|
147
|
+
if [ "$selected" -ne 0 ]; then
|
|
148
|
+
case "$pattern" in
|
|
149
|
+
*/*) ;;
|
|
150
|
+
*)
|
|
151
|
+
# shellcheck disable=SC2254 -- ditto, matched against the basename.
|
|
152
|
+
case "${rel##*/}" in $pattern) selected=0 ;; esac
|
|
153
|
+
;;
|
|
154
|
+
esac
|
|
155
|
+
fi
|
|
156
|
+
[ "$selected" -eq 0 ] || continue
|
|
157
|
+
# A positive match ignores the path; a negated one un-ignores it. Both
|
|
158
|
+
# OVERWRITE any earlier verdict rather than short-circuiting — that is what
|
|
159
|
+
# makes the last matching pattern win.
|
|
160
|
+
if [ "$negated" -eq 0 ]; then
|
|
161
|
+
ignored=1
|
|
162
|
+
else
|
|
163
|
+
ignored=0
|
|
164
|
+
fi
|
|
138
165
|
done <"$list"
|
|
139
|
-
return
|
|
166
|
+
return "$ignored"
|
|
140
167
|
}
|
|
141
168
|
|
|
142
169
|
# A candidate rewritten relative to the project, so an absolute target from a
|
|
@@ -174,12 +201,12 @@ managed_source() {
|
|
|
174
201
|
|
|
175
202
|
# Whether a destination is a ledger-tracked Lisa-owned guard.
|
|
176
203
|
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
204
|
+
# Both populations are PRESERVED once edited (measured — see the header). What
|
|
205
|
+
# membership changes is what apply prints and what the escape hatch is: a tracked
|
|
206
|
+
# guard gets a provenance verdict and can declare `lisa-guard-capabilities:`,
|
|
207
|
+
# while an untracked template gets a bare "Out of date" line and `.lisaignore`.
|
|
208
|
+
# The refusal quotes the words the reader will actually see, so it has to know
|
|
209
|
+
# which side it is on.
|
|
183
210
|
ledger_tracked() {
|
|
184
211
|
local rel="$1"
|
|
185
212
|
local ledger="$package_root/dist/core/lisa-owned-hash-ledger.js"
|
|
@@ -193,15 +220,21 @@ refuse() {
|
|
|
193
220
|
local rel="$3"
|
|
194
221
|
local consequence
|
|
195
222
|
if ledger_tracked "$rel"; then
|
|
196
|
-
consequence="
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
223
|
+
consequence="\`lisa apply\` will KEEP your edit — this is a Lisa-owned guard,
|
|
224
|
+
and apply says so: \"its contents match no Lisa release, so Lisa cannot tell
|
|
225
|
+
whether it is out of date or deliberately stronger. Kept yours.\"
|
|
226
|
+
|
|
227
|
+
That is the trap. Nothing is deleted; the file silently FORKS. It keeps looking
|
|
228
|
+
current while every upstream fix stops reaching it. One repository in this fleet
|
|
229
|
+
carries 243 lines of divergence nobody knew about, in a guard that had quietly
|
|
230
|
+
stopped receiving fixes."
|
|
201
231
|
else
|
|
202
|
-
consequence="
|
|
203
|
-
|
|
204
|
-
|
|
232
|
+
consequence="\`lisa apply\` will KEEP your edit and report the file as
|
|
233
|
+
\"Out of date, not updated\" on every run from now on.
|
|
234
|
+
|
|
235
|
+
That is the trap. Nothing is deleted; the file silently FORKS, stops receiving
|
|
236
|
+
upstream changes, and adds a permanent warning line that the next person learns
|
|
237
|
+
to scroll past."
|
|
205
238
|
fi
|
|
206
239
|
cat >&2 <<EOF
|
|
207
240
|
BLOCKED: refusing to write \`$target\`.
|
|
@@ -228,20 +261,21 @@ WHERE IT GOES INSTEAD — take the first one that fits:
|
|
|
228
261
|
shipped file.
|
|
229
262
|
|
|
230
263
|
4. This project has deliberately FORKED this file and means to keep its own
|
|
231
|
-
version.
|
|
232
|
-
the
|
|
264
|
+
version. Apply already preserves the edit either way, so what is left to
|
|
265
|
+
choose is whether the fork stays VISIBLE. Keeping it visible is the point:
|
|
233
266
|
|
|
234
|
-
- **A Lisa-owned guard (hash-tracked).** Do NOT add it to \`.lisaignore\`.
|
|
235
|
-
|
|
236
|
-
silences the standoff \`lisa doctor\` reports on every run, replacing a
|
|
237
|
-
warning with the line "Enforcement guards match the installed Lisa
|
|
267
|
+
- **A Lisa-owned guard (hash-tracked).** Do NOT add it to \`.lisaignore\`.
|
|
268
|
+
Apply preserves your version regardless, so ignoring it buys nothing — and
|
|
269
|
+
it silences the standoff \`lisa doctor\` reports on every run, replacing a
|
|
270
|
+
true warning with the line "Enforcement guards match the installed Lisa
|
|
238
271
|
version", which is then false. A visible, resolvable fork becomes a silent
|
|
239
|
-
permanent one.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
- **Any other template.** \`.lisaignore\`
|
|
243
|
-
|
|
244
|
-
|
|
272
|
+
permanent one. Instead declare what your version defends with a
|
|
273
|
+
\`lisa-guard-capabilities:\` line; apply then classifies it \`host-ahead\`
|
|
274
|
+
and says so by name, rather than reporting that it cannot tell.
|
|
275
|
+
- **Any other template.** \`.lisaignore\` records the divergence where the
|
|
276
|
+
next person can see it and stops the recurring "Out of date" line. It does
|
|
277
|
+
not preserve the file — apply already does — so use it to DECLARE a fork
|
|
278
|
+
you have decided on, never to quiet one you have not.
|
|
245
279
|
|
|
246
280
|
5. You believe this file should not be Lisa-managed at all. That is a real
|
|
247
281
|
argument and it belongs upstream, not in a local edit that will be erased.
|
|
@@ -11,20 +11,29 @@
|
|
|
11
11
|
# would stop agents editing files they are supposed to own, which is worse than
|
|
12
12
|
# the problem being solved.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
14
|
+
# The name `copy-overwrite` is misleading, and two successive versions of this
|
|
15
|
+
# guard got the consequence wrong by trusting it. MEASURED, by mutating four
|
|
16
|
+
# files in a scratch project and running a real `lisa apply` against them:
|
|
16
17
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# everything else (`.json` configs and friends)
|
|
22
|
-
# — replaced wholesale on the next apply, which runs on every install.
|
|
23
|
-
# The edit vanishes.
|
|
18
|
+
# scripts/lisa-gates.mjs (ledger-tracked) → SURVIVED
|
|
19
|
+
# .lintstagedrc.json (untracked, JSON) → SURVIVED
|
|
20
|
+
# .prettierignore (untracked, text) → SURVIVED
|
|
21
|
+
# .yamllint (untracked, text) → SURVIVED
|
|
24
22
|
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
23
|
+
# Summary line: `Overwritten: 0 files` / `Out of date: 3 files (managed
|
|
24
|
+
# templates changed; NOT updated)`.
|
|
25
|
+
#
|
|
26
|
+
# copy-overwrite overwrites an UNMODIFIED copy — it refreshes. It does not
|
|
27
|
+
# overwrite a host-edited one, in any population tested. So the harm is the same
|
|
28
|
+
# for both, and it is not deletion:
|
|
29
|
+
#
|
|
30
|
+
# THE FILE SILENTLY FORKS. It keeps looking current while every upstream fix
|
|
31
|
+
# stops reaching it. Nothing is lost, which is exactly what makes it invisible.
|
|
32
|
+
#
|
|
33
|
+
# Ledger membership changes the MESSAGE apply prints, not the outcome — tracked
|
|
34
|
+
# files get a provenance verdict naming the fork and offering
|
|
35
|
+
# `lisa-guard-capabilities:`; untracked ones get a bare "Out of date" warning.
|
|
36
|
+
# The refusal branches on that so the reader sees the words apply will use.
|
|
28
37
|
#
|
|
29
38
|
# Measured, not hypothetical. Nothing enforced this, so downstream copies were
|
|
30
39
|
# edited and then silently diverged: `classify-maestro-failures.mjs` reached
|
|
@@ -100,43 +109,61 @@ lisaignored() {
|
|
|
100
109
|
local rel="$1"
|
|
101
110
|
local list="$project_root/.lisaignore"
|
|
102
111
|
[ -f "$list" ] || return 1
|
|
112
|
+
# Gitignore precedence: patterns are read in order and the LAST one to select
|
|
113
|
+
# the path decides, so `!x` re-includes something an earlier line ignored.
|
|
114
|
+
# This mirrors `matchesAnyPattern` in `src/utils/ignore-patterns.ts`; the two
|
|
115
|
+
# must agree, or an agent gets blocked on a file apply considers the
|
|
116
|
+
# project's, or waved through on one it does not.
|
|
117
|
+
#
|
|
118
|
+
# `ignored` carries shell truth: 0 = ignored, 1 = not.
|
|
119
|
+
local ignored=1
|
|
103
120
|
local pattern
|
|
104
121
|
while IFS= read -r pattern || [ -n "$pattern" ]; do
|
|
105
122
|
pattern="${pattern#"${pattern%%[![:space:]]*}"}"
|
|
106
123
|
pattern="${pattern%"${pattern##*[![:space:]]}"}"
|
|
107
124
|
[ -n "$pattern" ] || continue
|
|
108
125
|
case "$pattern" in \#*) continue ;; esac
|
|
109
|
-
|
|
110
|
-
# patterns to minimatch, which negates by default, and it combines them with
|
|
111
|
-
# `.some()` — so a single `!scripts/a.mjs` line reports EVERY OTHER PATH as
|
|
112
|
-
# ignored. Measured:
|
|
113
|
-
#
|
|
114
|
-
# patterns=["!scripts/a.mjs"] path=scripts/a.mjs -> ignored=false
|
|
115
|
-
# patterns=["!scripts/a.mjs"] path=scripts/b.mjs -> ignored=TRUE
|
|
116
|
-
#
|
|
117
|
-
# Reproducing that faithfully would mean disabling this guard on a typo.
|
|
118
|
-
# Reproducing the intuitive gitignore reading would mean blocking files the
|
|
119
|
-
# matcher considers ignored. Both are wrong, so the file is treated as
|
|
120
|
-
# claimed and the write is allowed — the same direction this function errs
|
|
121
|
-
# everywhere else. Filed upstream; when the matcher stops negating, delete
|
|
122
|
-
# this branch rather than teaching it a second wrong answer.
|
|
123
|
-
case "$pattern" in !*) return 0 ;; esac
|
|
126
|
+
local negated=1
|
|
124
127
|
case "$pattern" in
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
# A bare `!` selects nothing rather than everything.
|
|
129
|
+
!) continue ;;
|
|
130
|
+
!*)
|
|
131
|
+
negated=0
|
|
132
|
+
pattern="${pattern#!}"
|
|
127
133
|
;;
|
|
134
|
+
# `\!x` is a literal leading `!`, not a negation.
|
|
135
|
+
\\!*) pattern="${pattern#\\}" ;;
|
|
128
136
|
esac
|
|
129
|
-
|
|
130
|
-
case "$rel" in $pattern) return 0 ;; esac
|
|
137
|
+
local selected=1
|
|
131
138
|
case "$pattern" in
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
# shellcheck disable=SC2254 -- ditto, matched against the basename.
|
|
135
|
-
case "${rel##*/}" in $pattern) return 0 ;; esac
|
|
139
|
+
*/)
|
|
140
|
+
case "$rel" in "${pattern%/}"/* | "${pattern%/}") selected=0 ;; esac
|
|
136
141
|
;;
|
|
137
142
|
esac
|
|
143
|
+
if [ "$selected" -ne 0 ]; then
|
|
144
|
+
# shellcheck disable=SC2254 -- the pattern is a glob on purpose.
|
|
145
|
+
case "$rel" in $pattern) selected=0 ;; esac
|
|
146
|
+
fi
|
|
147
|
+
if [ "$selected" -ne 0 ]; then
|
|
148
|
+
case "$pattern" in
|
|
149
|
+
*/*) ;;
|
|
150
|
+
*)
|
|
151
|
+
# shellcheck disable=SC2254 -- ditto, matched against the basename.
|
|
152
|
+
case "${rel##*/}" in $pattern) selected=0 ;; esac
|
|
153
|
+
;;
|
|
154
|
+
esac
|
|
155
|
+
fi
|
|
156
|
+
[ "$selected" -eq 0 ] || continue
|
|
157
|
+
# A positive match ignores the path; a negated one un-ignores it. Both
|
|
158
|
+
# OVERWRITE any earlier verdict rather than short-circuiting — that is what
|
|
159
|
+
# makes the last matching pattern win.
|
|
160
|
+
if [ "$negated" -eq 0 ]; then
|
|
161
|
+
ignored=1
|
|
162
|
+
else
|
|
163
|
+
ignored=0
|
|
164
|
+
fi
|
|
138
165
|
done <"$list"
|
|
139
|
-
return
|
|
166
|
+
return "$ignored"
|
|
140
167
|
}
|
|
141
168
|
|
|
142
169
|
# A candidate rewritten relative to the project, so an absolute target from a
|
|
@@ -174,12 +201,12 @@ managed_source() {
|
|
|
174
201
|
|
|
175
202
|
# Whether a destination is a ledger-tracked Lisa-owned guard.
|
|
176
203
|
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
204
|
+
# Both populations are PRESERVED once edited (measured — see the header). What
|
|
205
|
+
# membership changes is what apply prints and what the escape hatch is: a tracked
|
|
206
|
+
# guard gets a provenance verdict and can declare `lisa-guard-capabilities:`,
|
|
207
|
+
# while an untracked template gets a bare "Out of date" line and `.lisaignore`.
|
|
208
|
+
# The refusal quotes the words the reader will actually see, so it has to know
|
|
209
|
+
# which side it is on.
|
|
183
210
|
ledger_tracked() {
|
|
184
211
|
local rel="$1"
|
|
185
212
|
local ledger="$package_root/dist/core/lisa-owned-hash-ledger.js"
|
|
@@ -193,15 +220,21 @@ refuse() {
|
|
|
193
220
|
local rel="$3"
|
|
194
221
|
local consequence
|
|
195
222
|
if ledger_tracked "$rel"; then
|
|
196
|
-
consequence="
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
223
|
+
consequence="\`lisa apply\` will KEEP your edit — this is a Lisa-owned guard,
|
|
224
|
+
and apply says so: \"its contents match no Lisa release, so Lisa cannot tell
|
|
225
|
+
whether it is out of date or deliberately stronger. Kept yours.\"
|
|
226
|
+
|
|
227
|
+
That is the trap. Nothing is deleted; the file silently FORKS. It keeps looking
|
|
228
|
+
current while every upstream fix stops reaching it. One repository in this fleet
|
|
229
|
+
carries 243 lines of divergence nobody knew about, in a guard that had quietly
|
|
230
|
+
stopped receiving fixes."
|
|
201
231
|
else
|
|
202
|
-
consequence="
|
|
203
|
-
|
|
204
|
-
|
|
232
|
+
consequence="\`lisa apply\` will KEEP your edit and report the file as
|
|
233
|
+
\"Out of date, not updated\" on every run from now on.
|
|
234
|
+
|
|
235
|
+
That is the trap. Nothing is deleted; the file silently FORKS, stops receiving
|
|
236
|
+
upstream changes, and adds a permanent warning line that the next person learns
|
|
237
|
+
to scroll past."
|
|
205
238
|
fi
|
|
206
239
|
cat >&2 <<EOF
|
|
207
240
|
BLOCKED: refusing to write \`$target\`.
|
|
@@ -228,20 +261,21 @@ WHERE IT GOES INSTEAD — take the first one that fits:
|
|
|
228
261
|
shipped file.
|
|
229
262
|
|
|
230
263
|
4. This project has deliberately FORKED this file and means to keep its own
|
|
231
|
-
version.
|
|
232
|
-
the
|
|
264
|
+
version. Apply already preserves the edit either way, so what is left to
|
|
265
|
+
choose is whether the fork stays VISIBLE. Keeping it visible is the point:
|
|
233
266
|
|
|
234
|
-
- **A Lisa-owned guard (hash-tracked).** Do NOT add it to \`.lisaignore\`.
|
|
235
|
-
|
|
236
|
-
silences the standoff \`lisa doctor\` reports on every run, replacing a
|
|
237
|
-
warning with the line "Enforcement guards match the installed Lisa
|
|
267
|
+
- **A Lisa-owned guard (hash-tracked).** Do NOT add it to \`.lisaignore\`.
|
|
268
|
+
Apply preserves your version regardless, so ignoring it buys nothing — and
|
|
269
|
+
it silences the standoff \`lisa doctor\` reports on every run, replacing a
|
|
270
|
+
true warning with the line "Enforcement guards match the installed Lisa
|
|
238
271
|
version", which is then false. A visible, resolvable fork becomes a silent
|
|
239
|
-
permanent one.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
- **Any other template.** \`.lisaignore\`
|
|
243
|
-
|
|
244
|
-
|
|
272
|
+
permanent one. Instead declare what your version defends with a
|
|
273
|
+
\`lisa-guard-capabilities:\` line; apply then classifies it \`host-ahead\`
|
|
274
|
+
and says so by name, rather than reporting that it cannot tell.
|
|
275
|
+
- **Any other template.** \`.lisaignore\` records the divergence where the
|
|
276
|
+
next person can see it and stops the recurring "Out of date" line. It does
|
|
277
|
+
not preserve the file — apply already does — so use it to DECLARE a fork
|
|
278
|
+
you have decided on, never to quiet one you have not.
|
|
245
279
|
|
|
246
280
|
5. You believe this file should not be Lisa-managed at all. That is a real
|
|
247
281
|
argument and it belongs upstream, not in a local edit that will be erased.
|