@codyswann/lisa 3.47.0 → 3.47.2
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-enforcement-fallback.sh +78 -2
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +3 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +7 -5
- package/dist/core/upstream-evidence-manifest.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/threshold-ratchet-compare.mjs +41 -2
- 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/threshold-ratchet-compare.mjs +41 -2
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/threshold-ratchet-compare.mjs +41 -2
- 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/threshold-ratchet-compare.mjs +41 -2
- package/rails/copy-overwrite/scripts/threshold-ratchet-compare.mjs +41 -2
- package/scripts/lisa-enforcement-fallback.sh +78 -2
- package/typescript/copy-overwrite/scripts/threshold-ratchet-compare.mjs +41 -2
|
@@ -28,7 +28,7 @@ const KIND_ALLOW_LIST = "allow-list";
|
|
|
28
28
|
* @typedef {object} Finding
|
|
29
29
|
* @property {string} file Repo-relative path of the gate file
|
|
30
30
|
* @property {string} key Dotted key path within the file
|
|
31
|
-
* @property {"weakened"|"removed"|"exemption-added"|"file-deleted"|"allow-added"|"unparseable"} type
|
|
31
|
+
* @property {"weakened"|"removed"|"exemption-added"|"file-deleted"|"allow-added"|"unparseable"|"unparseable-baseline"} type
|
|
32
32
|
* Which ratchet rule the change violated
|
|
33
33
|
* @property {number|string} [base] Baseline value
|
|
34
34
|
* @property {number|string} [current] Current value
|
|
@@ -49,6 +49,25 @@ function unparseable(relPath) {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Build the "baseline could not be parsed" finding.
|
|
54
|
+
*
|
|
55
|
+
* Separate from `unparseable` because the two send an operator to different
|
|
56
|
+
* files. Told only that `vitest.thresholds.json` is not valid JSON, they open
|
|
57
|
+
* the current file, find it well-formed, and conclude the gate is broken; the
|
|
58
|
+
* defect is at the base ref.
|
|
59
|
+
* @param {string} relPath Repo-relative path
|
|
60
|
+
* @returns {Finding} The unparseable-baseline finding
|
|
61
|
+
*/
|
|
62
|
+
function unparseableBaseline(relPath) {
|
|
63
|
+
return {
|
|
64
|
+
file: relPath,
|
|
65
|
+
key: "*",
|
|
66
|
+
type: "unparseable-baseline",
|
|
67
|
+
message: `${relPath} is not valid JSON in the baseline — with no baseline to compare against, the ratchet cannot see a loosening in this file and will not see one in any later change either, until the baseline is repaired. A UTF-8 BOM, a trailing comma or an empty file all land here.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
/**
|
|
53
72
|
* Compare two constraint maps: report removals and direction violations.
|
|
54
73
|
* @param {string} relPath Repo-relative path the constraints came from
|
|
@@ -242,7 +261,27 @@ export function compareFile(relPath, baselineText, currentText) {
|
|
|
242
261
|
|
|
243
262
|
const base = parseJson(baselineText);
|
|
244
263
|
const current = parseJson(currentText);
|
|
245
|
-
|
|
264
|
+
// Both sides are reported, and both used to not be. An unparseable baseline
|
|
265
|
+
// returned no findings at all, which did not merely miss one change: once a
|
|
266
|
+
// malformed threshold file is on the base branch, every later pull request
|
|
267
|
+
// compares against a baseline that yields no constraints, so the ratchet
|
|
268
|
+
// stops having an opinion about that file — permanently, and in silence.
|
|
269
|
+
//
|
|
270
|
+
// This is only reached for a file that EXISTS at the baseline and did not
|
|
271
|
+
// parse. A file absent from the base ref arrives as a null `baselineText`
|
|
272
|
+
// and returned above: new gate files have nothing to weaken, and the caller
|
|
273
|
+
// separates absent from present-but-unreadable with `cat-file -e` before
|
|
274
|
+
// calling.
|
|
275
|
+
//
|
|
276
|
+
// The allow-list carve-out is symmetric with the current side and holds for
|
|
277
|
+
// the same reason: an allow list nobody can read grants no exceptions, so an
|
|
278
|
+
// unreadable one on either side already fails closed. Reporting it would
|
|
279
|
+
// block every change touching the file without making anything safer.
|
|
280
|
+
if (base === undefined) {
|
|
281
|
+
return family.kind === KIND_ALLOW_LIST
|
|
282
|
+
? []
|
|
283
|
+
: [unparseableBaseline(relPath)];
|
|
284
|
+
}
|
|
246
285
|
if (current === undefined) {
|
|
247
286
|
return family.kind === KIND_ALLOW_LIST ? [] : [unparseable(relPath)];
|
|
248
287
|
}
|
|
@@ -31,7 +31,7 @@ const KIND_ALLOW_LIST = "allow-list";
|
|
|
31
31
|
* @typedef {object} Finding
|
|
32
32
|
* @property {string} file Repo-relative path of the gate file
|
|
33
33
|
* @property {string} key Dotted key path within the file
|
|
34
|
-
* @property {"weakened"|"removed"|"exemption-added"|"file-deleted"|"allow-added"|"unparseable"} type
|
|
34
|
+
* @property {"weakened"|"removed"|"exemption-added"|"file-deleted"|"allow-added"|"unparseable"|"unparseable-baseline"} type
|
|
35
35
|
* Which ratchet rule the change violated
|
|
36
36
|
* @property {number|string} [base] Baseline value
|
|
37
37
|
* @property {number|string} [current] Current value
|
|
@@ -52,6 +52,25 @@ function unparseable(relPath) {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Build the "baseline could not be parsed" finding.
|
|
57
|
+
*
|
|
58
|
+
* Separate from `unparseable` because the two send an operator to different
|
|
59
|
+
* files. Told only that `vitest.thresholds.json` is not valid JSON, they open
|
|
60
|
+
* the current file, find it well-formed, and conclude the gate is broken; the
|
|
61
|
+
* defect is at the base ref.
|
|
62
|
+
* @param {string} relPath Repo-relative path
|
|
63
|
+
* @returns {Finding} The unparseable-baseline finding
|
|
64
|
+
*/
|
|
65
|
+
function unparseableBaseline(relPath) {
|
|
66
|
+
return {
|
|
67
|
+
file: relPath,
|
|
68
|
+
key: "*",
|
|
69
|
+
type: "unparseable-baseline",
|
|
70
|
+
message: `${relPath} is not valid JSON in the baseline — with no baseline to compare against, the ratchet cannot see a loosening in this file and will not see one in any later change either, until the baseline is repaired. A UTF-8 BOM, a trailing comma or an empty file all land here.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
55
74
|
/**
|
|
56
75
|
* Compare two constraint maps: report removals and direction violations.
|
|
57
76
|
* @param {string} relPath Repo-relative path the constraints came from
|
|
@@ -245,7 +264,27 @@ export function compareFile(relPath, baselineText, currentText) {
|
|
|
245
264
|
|
|
246
265
|
const base = parseJson(baselineText);
|
|
247
266
|
const current = parseJson(currentText);
|
|
248
|
-
|
|
267
|
+
// Both sides are reported, and both used to not be. An unparseable baseline
|
|
268
|
+
// returned no findings at all, which did not merely miss one change: once a
|
|
269
|
+
// malformed threshold file is on the base branch, every later pull request
|
|
270
|
+
// compares against a baseline that yields no constraints, so the ratchet
|
|
271
|
+
// stops having an opinion about that file — permanently, and in silence.
|
|
272
|
+
//
|
|
273
|
+
// This is only reached for a file that EXISTS at the baseline and did not
|
|
274
|
+
// parse. A file absent from the base ref arrives as a null `baselineText`
|
|
275
|
+
// and returned above: new gate files have nothing to weaken, and the caller
|
|
276
|
+
// separates absent from present-but-unreadable with `cat-file -e` before
|
|
277
|
+
// calling.
|
|
278
|
+
//
|
|
279
|
+
// The allow-list carve-out is symmetric with the current side and holds for
|
|
280
|
+
// the same reason: an allow list nobody can read grants no exceptions, so an
|
|
281
|
+
// unreadable one on either side already fails closed. Reporting it would
|
|
282
|
+
// block every change touching the file without making anything safer.
|
|
283
|
+
if (base === undefined) {
|
|
284
|
+
return family.kind === KIND_ALLOW_LIST
|
|
285
|
+
? []
|
|
286
|
+
: [unparseableBaseline(relPath)];
|
|
287
|
+
}
|
|
249
288
|
if (current === undefined) {
|
|
250
289
|
return family.kind === KIND_ALLOW_LIST ? [] : [unparseable(relPath)];
|
|
251
290
|
}
|
|
@@ -22,7 +22,25 @@ set -uo pipefail
|
|
|
22
22
|
|
|
23
23
|
payload="$(cat)"
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
# `:-` substitutes on unset and on set-but-empty, and on nothing else. A value
|
|
26
|
+
# of " " is neither: `-n " "` is true, so it used to survive the emptiness test
|
|
27
|
+
# below, every candidate path became " /scripts/lisa-hooks/..." , no file
|
|
28
|
+
# matched, and every guard was skipped. That is the empty-string-fallback class
|
|
29
|
+
# verbatim — a value that passes the truthiness test, normalizes to nothing, and
|
|
30
|
+
# makes the downstream match find nothing.
|
|
31
|
+
#
|
|
32
|
+
# So the variable is trimmed FIRST and the substitution keyed off the trimmed
|
|
33
|
+
# value. Whitespace then reaches `git rev-parse` exactly as an unset variable
|
|
34
|
+
# does, instead of resolving to a root that cannot exist.
|
|
35
|
+
repo_root="${CLAUDE_PROJECT_DIR-}"
|
|
36
|
+
repo_root="${repo_root#"${repo_root%%[![:space:]]*}"}"
|
|
37
|
+
repo_root="${repo_root%"${repo_root##*[![:space:]]}"}"
|
|
38
|
+
if [ -z "$repo_root" ]; then
|
|
39
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
40
|
+
fi
|
|
41
|
+
# No root at all means no repository to protect — a tool call outside any
|
|
42
|
+
# checkout. That is a genuine absence of subject matter, not a missing guard,
|
|
43
|
+
# and is the one case that still stands down.
|
|
26
44
|
[ -n "$repo_root" ] || exit 0
|
|
27
45
|
|
|
28
46
|
# There is deliberately no skip here, and that is the whole point.
|
|
@@ -66,6 +84,8 @@ repo_root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)}"
|
|
|
66
84
|
# host project whose plugin install fails has exactly the same hole and no
|
|
67
85
|
# `plugins/` directory to fall back on.
|
|
68
86
|
status=0
|
|
87
|
+
resolved=0
|
|
88
|
+
missing=""
|
|
69
89
|
for guard in block-no-verify parity-safety-net block-shell-json-parsing \
|
|
70
90
|
block-instruction-file-edits block-direct-issue-create \
|
|
71
91
|
block-managed-file-edits; do
|
|
@@ -78,7 +98,11 @@ for guard in block-no-verify parity-safety-net block-shell-json-parsing \
|
|
|
78
98
|
break
|
|
79
99
|
fi
|
|
80
100
|
done
|
|
81
|
-
[ -
|
|
101
|
+
if [ -z "$script" ]; then
|
|
102
|
+
missing="${missing:+$missing, }$guard"
|
|
103
|
+
continue
|
|
104
|
+
fi
|
|
105
|
+
resolved=$((resolved + 1))
|
|
82
106
|
# Each guard reads the tool payload on stdin and signals a refusal with exit
|
|
83
107
|
# 2. The payload is replayed to every one of them, and the strongest refusal
|
|
84
108
|
# is returned — a guard that declines must not be able to clear one that did
|
|
@@ -108,4 +132,56 @@ for guard in block-no-verify parity-safety-net block-shell-json-parsing \
|
|
|
108
132
|
fi
|
|
109
133
|
done
|
|
110
134
|
|
|
135
|
+
# Zero guards resolved is a refusal, not a pass.
|
|
136
|
+
#
|
|
137
|
+
# Six `continue`s used to leave `status` at 0, so nothing distinguished "every
|
|
138
|
+
# guard ran and none objected" from "no guard was found". That is the same
|
|
139
|
+
# silent fail-open this file was written to close, reproduced one layer down: a
|
|
140
|
+
# host whose `scripts/lisa-hooks/` was never written by `lisa apply`, or was
|
|
141
|
+
# deleted, or drifted — precisely the state this file exists for — got the same
|
|
142
|
+
# green as a clean session.
|
|
143
|
+
#
|
|
144
|
+
# Refusing rather than warning is a deliberate choice between two imperfect
|
|
145
|
+
# options, and the reasons are these.
|
|
146
|
+
#
|
|
147
|
+
# - The hook entry and the guards ship from the SAME `lisa apply`:
|
|
148
|
+
# `all/merge/.claude/settings.json` registers this dispatcher and
|
|
149
|
+
# `all/copy-overwrite/scripts/lisa-hooks/` writes the guards. "Registered
|
|
150
|
+
# but no guards" is therefore never a configuration anyone chose; it is
|
|
151
|
+
# always drift, deletion, or a partial apply.
|
|
152
|
+
# - Warning on exit 0 is barely louder than silence. Claude Code shows a
|
|
153
|
+
# zero-status hook's output to the user in transcript mode only and never
|
|
154
|
+
# to the agent, so "fail loud, exit unchanged" would have left the failure
|
|
155
|
+
# very nearly as invisible as it already was while claiming to have fixed
|
|
156
|
+
# it.
|
|
157
|
+
# - The blocking cost is bounded and recoverable without the agent. The
|
|
158
|
+
# refusal below names the guards, both searched paths, and the one command
|
|
159
|
+
# that repairs it, which a human runs in a terminal — no tool call needed.
|
|
160
|
+
#
|
|
161
|
+
# The scope is deliberately "zero", not "fewer than six". A partial resolution
|
|
162
|
+
# means some enforcement ran, and version skew across an interrupted apply is a
|
|
163
|
+
# real enough way to reach it that refusing there would trade a silent hole for
|
|
164
|
+
# a noisy outage.
|
|
165
|
+
if [ "$resolved" -eq 0 ]; then
|
|
166
|
+
cat >&2 <<EOF
|
|
167
|
+
Blocked: Lisa's enforcement guards are missing from this repository, so this
|
|
168
|
+
tool call was checked by nothing at all.
|
|
169
|
+
|
|
170
|
+
This hook is registered in .claude/settings.json but resolved none of the
|
|
171
|
+
guards it dispatches: $missing
|
|
172
|
+
|
|
173
|
+
Searched:
|
|
174
|
+
$repo_root/scripts/lisa-hooks/<guard>.sh
|
|
175
|
+
$repo_root/plugins/lisa/hooks/<guard>.sh
|
|
176
|
+
|
|
177
|
+
Refused rather than allowed on purpose. A dispatcher that resolves nothing is
|
|
178
|
+
indistinguishable from one that was never installed, and letting the call
|
|
179
|
+
through would be the silent fail-open this hook exists to close.
|
|
180
|
+
|
|
181
|
+
To repair, run this in a terminal outside the agent session:
|
|
182
|
+
npx @codyswann/lisa apply
|
|
183
|
+
EOF
|
|
184
|
+
exit 2
|
|
185
|
+
fi
|
|
186
|
+
|
|
111
187
|
exit "$status"
|
|
@@ -31,7 +31,7 @@ const KIND_ALLOW_LIST = "allow-list";
|
|
|
31
31
|
* @typedef {object} Finding
|
|
32
32
|
* @property {string} file Repo-relative path of the gate file
|
|
33
33
|
* @property {string} key Dotted key path within the file
|
|
34
|
-
* @property {"weakened"|"removed"|"exemption-added"|"file-deleted"|"allow-added"|"unparseable"} type
|
|
34
|
+
* @property {"weakened"|"removed"|"exemption-added"|"file-deleted"|"allow-added"|"unparseable"|"unparseable-baseline"} type
|
|
35
35
|
* Which ratchet rule the change violated
|
|
36
36
|
* @property {number|string} [base] Baseline value
|
|
37
37
|
* @property {number|string} [current] Current value
|
|
@@ -52,6 +52,25 @@ function unparseable(relPath) {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Build the "baseline could not be parsed" finding.
|
|
57
|
+
*
|
|
58
|
+
* Separate from `unparseable` because the two send an operator to different
|
|
59
|
+
* files. Told only that `vitest.thresholds.json` is not valid JSON, they open
|
|
60
|
+
* the current file, find it well-formed, and conclude the gate is broken; the
|
|
61
|
+
* defect is at the base ref.
|
|
62
|
+
* @param {string} relPath Repo-relative path
|
|
63
|
+
* @returns {Finding} The unparseable-baseline finding
|
|
64
|
+
*/
|
|
65
|
+
function unparseableBaseline(relPath) {
|
|
66
|
+
return {
|
|
67
|
+
file: relPath,
|
|
68
|
+
key: "*",
|
|
69
|
+
type: "unparseable-baseline",
|
|
70
|
+
message: `${relPath} is not valid JSON in the baseline — with no baseline to compare against, the ratchet cannot see a loosening in this file and will not see one in any later change either, until the baseline is repaired. A UTF-8 BOM, a trailing comma or an empty file all land here.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
55
74
|
/**
|
|
56
75
|
* Compare two constraint maps: report removals and direction violations.
|
|
57
76
|
* @param {string} relPath Repo-relative path the constraints came from
|
|
@@ -245,7 +264,27 @@ export function compareFile(relPath, baselineText, currentText) {
|
|
|
245
264
|
|
|
246
265
|
const base = parseJson(baselineText);
|
|
247
266
|
const current = parseJson(currentText);
|
|
248
|
-
|
|
267
|
+
// Both sides are reported, and both used to not be. An unparseable baseline
|
|
268
|
+
// returned no findings at all, which did not merely miss one change: once a
|
|
269
|
+
// malformed threshold file is on the base branch, every later pull request
|
|
270
|
+
// compares against a baseline that yields no constraints, so the ratchet
|
|
271
|
+
// stops having an opinion about that file — permanently, and in silence.
|
|
272
|
+
//
|
|
273
|
+
// This is only reached for a file that EXISTS at the baseline and did not
|
|
274
|
+
// parse. A file absent from the base ref arrives as a null `baselineText`
|
|
275
|
+
// and returned above: new gate files have nothing to weaken, and the caller
|
|
276
|
+
// separates absent from present-but-unreadable with `cat-file -e` before
|
|
277
|
+
// calling.
|
|
278
|
+
//
|
|
279
|
+
// The allow-list carve-out is symmetric with the current side and holds for
|
|
280
|
+
// the same reason: an allow list nobody can read grants no exceptions, so an
|
|
281
|
+
// unreadable one on either side already fails closed. Reporting it would
|
|
282
|
+
// block every change touching the file without making anything safer.
|
|
283
|
+
if (base === undefined) {
|
|
284
|
+
return family.kind === KIND_ALLOW_LIST
|
|
285
|
+
? []
|
|
286
|
+
: [unparseableBaseline(relPath)];
|
|
287
|
+
}
|
|
249
288
|
if (current === undefined) {
|
|
250
289
|
return family.kind === KIND_ALLOW_LIST ? [] : [unparseable(relPath)];
|
|
251
290
|
}
|