@sema-agent/core 7.46.0 → 7.48.0
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/CHANGELOG.md +106 -0
- package/dist/core/allow-entry.d.ts +1 -1
- package/dist/core/allow-entry.js +4 -5
- package/dist/core/ask-origin.d.ts +35 -12
- package/dist/core/ask-origin.js +23 -6
- package/dist/core/checkpoint-store.d.ts +1 -1
- package/dist/core/engine-notice.d.ts +7 -4
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/gate-outcome.d.ts +4 -2
- package/dist/core/gate-outcome.js +2 -2
- package/dist/core/mcp-server-spec.d.ts +1 -1
- package/dist/core/permission-rule-model.d.ts +12 -6
- package/dist/core/permission-rule-model.js +10 -2
- package/dist/core/permission-rules.d.ts +4 -5
- package/dist/core/permission-rules.js +28 -15
- package/dist/core/runner/gate-exit.d.ts +4 -1
- package/dist/core/runner/gate-exit.js +5 -1
- package/dist/core/runner/park-commit.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.js +5 -5
- package/dist/core/runner/prepare-policy-chain.js +2 -1
- package/dist/core/safety-axis-vocab.d.ts +1 -1
- package/dist/core/safety-merge-corpus.d.ts +1 -1
- package/dist/core/session-policy-store.d.ts +6 -3
- package/dist/core/session-policy-store.js +28 -10
- package/dist/core/shell-wrapper-projection.js +1 -1
- package/dist/core/shell-wrapper-table.d.ts +8 -8
- package/dist/core/shell-wrapper-table.js +3 -3
- package/dist/core/tool-catalog-entries.js +3 -2
- package/dist/core/tool-face.d.ts +10 -1
- package/dist/core/tool-name-aliases.d.ts +9 -8
- package/dist/core/tool-name-aliases.js +2 -0
- package/dist/core/tool-name-readings.d.ts +8 -0
- package/dist/core/tool-name-readings.js +2 -0
- package/dist/core/tool-policy.d.ts +10 -9
- package/dist/core/tool-policy.js +6 -7
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +10 -0
- package/dist/core/write-protect.d.ts +10 -3
- package/dist/core/write-protect.js +11 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompt-assembly/types.d.ts +1 -1
- package/dist/shell/lex.d.ts +15 -0
- package/dist/shell/lex.js +86 -15
- package/dist/tools/fs/bash-program-position.d.ts +16 -6
- package/dist/tools/fs/bash-program-position.js +18 -12
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/program-read-model.d.ts +84 -12
- package/dist/tools/fs/program-read-model.js +105 -34
- package/dist/tools/fs/program-read-model.rows.js +26 -7
- package/dist/tools/fs/read-deny-home.d.ts +20 -1
- package/dist/tools/fs/read-deny-home.js +28 -7
- package/dist/tools/fs/read-deny.d.ts +13 -6
- package/dist/tools/fs/read-deny.js +12 -3
- package/dist/tools/fs/removal-targets.js +24 -5
- package/dist/tools/fs/shell-read-argv.d.ts +6 -8
- package/dist/tools/fs/shell-read-argv.js +5 -5
- package/dist/tools/fs/shell-read-bindings.d.ts +71 -19
- package/dist/tools/fs/shell-read-bindings.js +190 -75
- package/dist/tools/fs/shell-read-establish.js +6 -7
- package/dist/tools/fs/shell-read-plan.d.ts +3 -15
- package/dist/tools/fs/shell-read-plan.js +126 -129
- package/dist/tools/fs/shell-read-start.d.ts +57 -0
- package/dist/tools/fs/shell-read-start.js +92 -0
- package/dist/tools/fs/shell-read-word.d.ts +68 -8
- package/dist/tools/fs/shell-read-word.js +106 -12
- package/dist/tools/fs/shell-read-write-targets.js +4 -3
- package/dist/tools/fs/shell-reentry-text.d.ts +103 -3
- package/dist/tools/fs/shell-reentry-text.js +122 -4
- package/dist/tools/monitor.d.ts +3 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +9 -3
- package/wire-changes/core@7.47.0.json +412 -0
- package/wire-changes/core@7.48.0.json +252 -0
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { argvOf, lexShell, literalTildeSpelling, substitutionsInHeredocBody } from "../../shell/lex.js";
|
|
2
|
-
import { readProgramPosition } from "./bash-program-position.js";
|
|
2
|
+
import { isShellSyntaxWord, LOOP_LIST_HEADS, readProgramPosition } from "./bash-program-position.js";
|
|
3
3
|
import { declaredAttributes, programRow, readArgRoles } from "./program-read-model.js";
|
|
4
4
|
import { codeBodies } from "./shell-reentry-text.js";
|
|
5
|
+
export function judgedSpellingOf(v) {
|
|
6
|
+
if (v.glob !== true)
|
|
7
|
+
return undefined;
|
|
8
|
+
const judged = literalTildeSpelling(v.parts.map((p) => (p.pattern ? p.judged : p.text)).join(""));
|
|
9
|
+
return judged === literalTildeSpelling(v.spelled) ? undefined : judged;
|
|
10
|
+
}
|
|
11
|
+
export function floorOf(v) {
|
|
12
|
+
const pattern = judgedSpellingOf(v);
|
|
13
|
+
return v.glob === true ? { spelled: v.spelled, glob: true, ...(pattern !== undefined ? { pattern } : {}) } : { spelled: v.spelled };
|
|
14
|
+
}
|
|
15
|
+
function sameValue(a, b) {
|
|
16
|
+
if (a.spelled !== b.spelled || a.fields?.length !== b.fields?.length)
|
|
17
|
+
return false;
|
|
18
|
+
if (a.glob !== true || b.glob !== true)
|
|
19
|
+
return a.glob === b.glob;
|
|
20
|
+
return a.parts.length === b.parts.length && a.parts.every((p, i) => p.text === b.parts[i].text && p.pattern === b.parts[i].pattern);
|
|
21
|
+
}
|
|
5
22
|
export const SHELL_INTEGER_VARIABLES = new Set([
|
|
6
23
|
"RANDOM", "SECONDS", "LINENO", "OPTIND", "MAILCHECK", "HISTCMD", "SRANDOM", "EPOCHSECONDS", "EPOCHREALTIME", "BASHPID", "BASH_MONOSECONDS",
|
|
7
24
|
"BASH_TRAPSIG", "COLUMNS", "LINES", "SHLVL", "ERRNO", "TMOUT", "HISTSIZE", "SAVEHIST", "TRY_BLOCK_ERROR", "TRY_BLOCK_INTERRUPT", "KEYTIMEOUT",
|
|
@@ -196,62 +213,157 @@ function blockStructure(segments) {
|
|
|
196
213
|
return { words: words ?? depth, after: depth, lowest, switches, ...(body !== undefined ? { body } : {}) };
|
|
197
214
|
});
|
|
198
215
|
}
|
|
199
|
-
const
|
|
216
|
+
const READABLE_VALUE = /^[^\\~()]*$/;
|
|
200
217
|
const WORD_VALUES_MAX = 64;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
218
|
+
const quotedRunsOf = (v) => v.glob === true ? v.parts.map((r) => (r.pattern ? { text: r.text, mode: "set", judged: r.judged } : { text: r.text, mode: "inactive" })) : [{ text: v.spelled, mode: "inactive" }];
|
|
219
|
+
function formedField(f) {
|
|
220
|
+
const runs = f.runs;
|
|
221
|
+
if (!runs.some((r) => r.mode === "active" && /[[\]*?]/.test(r.text))) {
|
|
222
|
+
if (!runs.some((r) => r.mode === "raw" || r.mode === "set"))
|
|
223
|
+
return { spelled: f.text };
|
|
224
|
+
return { spelled: f.text, glob: true, parts: joinRuns([], runs.map((r) => (r.mode === "raw" || r.mode === "set" ? { text: r.text, pattern: true, judged: r.judged } : { text: r.text, pattern: false }))) };
|
|
225
|
+
}
|
|
226
|
+
if (runs.some((r) => r.mode === "active" && /[ \t\n\\]/.test(r.text)) || (runs.some((r) => r.mode === "set") && runs.some((r) => r.mode === "active" && /[[\]]/.test(r.text))))
|
|
204
227
|
return undefined;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
for (const p of pieces) {
|
|
228
|
+
const escaped = runs.map((r) => (r.mode === "raw" || r.mode === "set" ? r.text : r.text.replace(r.mode === "active" ? /[^A-Za-z0-9_./*?[\]!^:=,+@%-]/g : /[^A-Za-z0-9_./]/g, (c) => `\\${c}`))).join("");
|
|
229
|
+
const lexed = lexShell(`: ${escaped}`);
|
|
230
|
+
const words = "parseFailure" in lexed || lexed.segments.length !== 1 ? undefined : argvOf(lexed.segments[0]);
|
|
231
|
+
if (words?.length !== 2)
|
|
232
|
+
return undefined;
|
|
233
|
+
let parts = [];
|
|
234
|
+
for (const p of words[1].pieces) {
|
|
235
|
+
if (p.expansion?.pattern !== undefined)
|
|
236
|
+
parts = joinRuns(parts, [{ text: p.expansion.pattern.written, pattern: true, judged: p.expansion.pattern.judged }]);
|
|
237
|
+
else if (p.expansion === undefined && p.literal !== undefined)
|
|
238
|
+
parts = joinRuns(parts, [{ text: p.literal, pattern: false }]);
|
|
239
|
+
else
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
return parts.some((r) => r.pattern) ? { spelled: f.text, glob: true, parts } : { spelled: f.text };
|
|
243
|
+
}
|
|
244
|
+
export function wordValues(word, table, quoted, overlay = new Map(), split = false) {
|
|
245
|
+
if (!word.pieces.some((p) => p.expansion?.kind === "parameter" || p.expansion?.kind === "tilde") && namesIn(word).length === 0)
|
|
246
|
+
return undefined;
|
|
247
|
+
return valueOf(word, table, quoted, overlay, split);
|
|
248
|
+
}
|
|
249
|
+
export function namesIn(word) {
|
|
250
|
+
return word.pieces.flatMap((p) => {
|
|
212
251
|
const e = p.expansion;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
252
|
+
if (e?.kind === "parameter" && e.name !== undefined)
|
|
253
|
+
return e.body === undefined || e.body === e.name ? [e.name] : [];
|
|
254
|
+
return e?.kind === "glob" && e.pattern === undefined ? (bracketBodyOf(e.body) ?? []).flatMap((w) => namesIn(w)) : [];
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function bracketBodyOf(body) {
|
|
258
|
+
const lexed = body === undefined ? undefined : lexShell(`""${body}`);
|
|
259
|
+
return lexed === undefined || "parseFailure" in lexed ? undefined : lexed.segments.flatMap((s) => argvOf(s));
|
|
260
|
+
}
|
|
261
|
+
function joinRuns(runs, more) {
|
|
262
|
+
const out = [...runs];
|
|
263
|
+
for (const r of more) {
|
|
264
|
+
const last = out[out.length - 1];
|
|
265
|
+
if (last !== undefined && last.pattern && r.pattern)
|
|
266
|
+
out[out.length - 1] = { text: last.text + r.text, pattern: true, judged: last.judged + r.judged };
|
|
267
|
+
else if (last !== undefined && !last.pattern && !r.pattern)
|
|
268
|
+
out[out.length - 1] = { text: last.text + r.text, pattern: false };
|
|
269
|
+
else if (r.text !== "")
|
|
270
|
+
out.push(r);
|
|
271
|
+
}
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
export function valueOf(word, table, quoted, overlay = new Map(), split = false) {
|
|
275
|
+
const stateOf = (name) => (table === undefined ? "unknown" : overlay.get(name) ?? table.stateOf(name));
|
|
276
|
+
const EMPTY = { text: "", runs: [] };
|
|
277
|
+
let combos = [{ field: EMPTY, closed: [], started: false, split: false }];
|
|
278
|
+
const activeRun = (text) => ({ text, mode: "active" });
|
|
279
|
+
const splitInto = (c, value) => {
|
|
280
|
+
const words = value.split(/[ \t\n]+/).filter((w) => w !== "");
|
|
281
|
+
if (words.length === 0)
|
|
282
|
+
return value === "" ? { ...c, split: true } : { ...c, field: EMPTY, started: false, closed: c.started ? [...c.closed, c.field] : c.closed, split: true };
|
|
283
|
+
const closed = [...c.closed];
|
|
284
|
+
if (/^[ \t\n]/.test(value) && c.started)
|
|
285
|
+
closed.push(c.field);
|
|
286
|
+
let field = closed.length > c.closed.length ? { text: words[0], runs: [activeRun(words[0])] } : { text: c.field.text + words[0], runs: [...c.field.runs, activeRun(words[0])] };
|
|
287
|
+
for (let i = 1; i < words.length; i++) {
|
|
288
|
+
closed.push(field);
|
|
289
|
+
field = { text: words[i], runs: [activeRun(words[i])] };
|
|
240
290
|
}
|
|
241
|
-
|
|
242
|
-
return
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
291
|
+
if (!/[ \t\n]$/.test(value))
|
|
292
|
+
return { ...c, field, started: true, closed, split: true };
|
|
293
|
+
closed.push(field);
|
|
294
|
+
return { ...c, field: EMPTY, started: false, closed, split: true };
|
|
295
|
+
};
|
|
296
|
+
const extend = (options) => {
|
|
297
|
+
const next = combos.flatMap((c) => options.map((o) => ({ ...c, field: { text: c.field.text + o.text, runs: [...c.field.runs, ...o.runs] }, started: true })));
|
|
247
298
|
if (next.length > WORD_VALUES_MAX)
|
|
248
|
-
return
|
|
299
|
+
return false;
|
|
249
300
|
combos = next;
|
|
250
|
-
|
|
301
|
+
return true;
|
|
302
|
+
};
|
|
303
|
+
const unquotedKind = (v) => v.glob === true || !READABLE_VALUE.test(v.spelled) ? undefined : !/[ \t\n]/.test(v.spelled) && v.spelled !== "" ? "run" : split ? "split" : undefined;
|
|
304
|
+
const walk = (pieces, unquoted) => {
|
|
305
|
+
for (const p of pieces) {
|
|
306
|
+
const e = p.expansion;
|
|
307
|
+
const bare = unquoted && p.quotedBy === undefined;
|
|
308
|
+
if (e === undefined) {
|
|
309
|
+
if (p.literal === undefined || !extend([{ text: p.literal, runs: [{ text: p.literal, mode: bare ? "active" : "inactive" }] }]))
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
else if (e.kind === "glob") {
|
|
313
|
+
const formed = e.pattern;
|
|
314
|
+
if (formed !== undefined) {
|
|
315
|
+
if (!extend([{ text: formed.folded, runs: [{ text: formed.written, mode: "raw", judged: formed.judged }] }]))
|
|
316
|
+
return false;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const words = bracketBodyOf(e.body);
|
|
320
|
+
if (words?.length !== 1 || !extend([{ text: "[", runs: [activeRun("[")] }]) || !walk(words[0].pieces, true) || !extend([{ text: "]", runs: [activeRun("]")] }]))
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
else if (e.kind === "tilde") {
|
|
324
|
+
const home = e.body === "~" ? stateOf("HOME") : "unknown";
|
|
325
|
+
if (home === "unknown" || !extend(home.map((v) => ({ text: v.spelled, runs: quotedRunsOf(v) }))))
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
else if (e.kind === "parameter" && e.name !== undefined && (e.body === undefined || e.body === e.name)) {
|
|
329
|
+
const state = stateOf(e.name);
|
|
330
|
+
if (state === "unknown")
|
|
331
|
+
return false;
|
|
332
|
+
if (!bare) {
|
|
333
|
+
if (!extend(state.map((v) => ({ text: v.spelled, runs: quotedRunsOf(v) }))))
|
|
334
|
+
return false;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const kinds = state.map(unquotedKind);
|
|
338
|
+
if (table?.ifsTouched === true || kinds.includes(undefined))
|
|
339
|
+
return false;
|
|
340
|
+
if (!kinds.includes("split")) {
|
|
341
|
+
if (!extend(state.map((v) => ({ text: v.spelled, runs: [activeRun(v.spelled)] }))))
|
|
342
|
+
return false;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const next = combos.flatMap((c) => state.map((v) => splitInto(c, v.spelled)));
|
|
346
|
+
if (next.length > WORD_VALUES_MAX)
|
|
347
|
+
return false;
|
|
348
|
+
combos = next;
|
|
349
|
+
}
|
|
350
|
+
else
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
return true;
|
|
354
|
+
};
|
|
355
|
+
if (!walk(word.pieces, !quoted))
|
|
356
|
+
return "unknown";
|
|
251
357
|
const out = [];
|
|
252
|
-
for (const c of combos)
|
|
253
|
-
|
|
254
|
-
|
|
358
|
+
for (const c of combos) {
|
|
359
|
+
const fields = (c.split ? (c.started ? [...c.closed, c.field] : c.closed) : [c.field]).map(formedField);
|
|
360
|
+
const formed = fields.filter((f) => f !== undefined && !(c.split && f.glob === true));
|
|
361
|
+
if (formed.length !== fields.length)
|
|
362
|
+
return "unknown";
|
|
363
|
+
const v = !c.split ? formed[0] : formed.length !== 1 ? { spelled: formed.map((f) => f.spelled).join(" "), fields: formed.map((f) => f.spelled) } : { spelled: formed[0].spelled };
|
|
364
|
+
if (!out.some((x) => sameValue(x, v)))
|
|
365
|
+
out.push(v);
|
|
366
|
+
}
|
|
255
367
|
return out;
|
|
256
368
|
}
|
|
257
369
|
export function wordFloorValues(word, table) {
|
|
@@ -268,32 +380,28 @@ export function wordFloorValues(word, table) {
|
|
|
268
380
|
}
|
|
269
381
|
else if (e.kind === "parameter" && e.name !== undefined) {
|
|
270
382
|
const state = table.stateOf(e.name);
|
|
271
|
-
options = state !== "unknown" && (e.body === undefined || e.body === e.name) ? state : table.floorOf(e.name);
|
|
383
|
+
options = state !== "unknown" && (e.body === undefined || e.body === e.name) ? state.map(floorOf) : table.floorOf(e.name);
|
|
272
384
|
}
|
|
273
385
|
else
|
|
274
386
|
return [];
|
|
275
387
|
if (options.length === 0)
|
|
276
388
|
return [];
|
|
277
389
|
const next = [];
|
|
278
|
-
for (const c of combos)
|
|
279
|
-
for (const o of options)
|
|
280
|
-
|
|
390
|
+
for (const c of combos) {
|
|
391
|
+
for (const o of options) {
|
|
392
|
+
const pattern = c.pattern !== undefined || o.pattern !== undefined ? (c.pattern ?? c.spelled) + (o.pattern ?? o.spelled) : undefined;
|
|
393
|
+
next.push(c.glob === true || o.glob === true ? { spelled: c.spelled + o.spelled, glob: true, ...(pattern !== undefined ? { pattern } : {}) } : { spelled: c.spelled + o.spelled });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
281
396
|
if (next.length > WORD_VALUES_MAX)
|
|
282
397
|
return [];
|
|
283
398
|
combos = next;
|
|
284
399
|
}
|
|
285
400
|
return combos;
|
|
286
401
|
}
|
|
287
|
-
export function
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
return values;
|
|
291
|
-
if (word.pieces.every((p) => p.expansion === undefined && p.literal !== undefined))
|
|
292
|
-
return [{ spelled: word.pieces.map((p) => p.literal).join("") }];
|
|
293
|
-
const folded = word.folded;
|
|
294
|
-
if (folded !== undefined && word.pieces.every((p) => p.expansion === undefined ? p.literal !== undefined : p.expansion.kind === "glob"))
|
|
295
|
-
return [{ spelled: folded, glob: true }];
|
|
296
|
-
return "unknown";
|
|
402
|
+
export function loopListHeadAt(argv) {
|
|
403
|
+
const at = isShellSyntaxWord(argv[0], LOOP_LIST_HEADS) ? 0 : readProgramPosition(argv).written;
|
|
404
|
+
return at !== undefined && isShellSyntaxWord(argv[at], LOOP_LIST_HEADS) ? at : undefined;
|
|
297
405
|
}
|
|
298
406
|
export function bindingEffectOf(segment, argv, shape, readValue) {
|
|
299
407
|
const bindings = [];
|
|
@@ -344,21 +452,21 @@ export function bindingEffectOf(segment, argv, shape, readValue) {
|
|
|
344
452
|
kill(name);
|
|
345
453
|
return { bindings, all };
|
|
346
454
|
}
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
455
|
+
const loopAt = loopListHeadAt(argv);
|
|
456
|
+
if (loopAt !== undefined) {
|
|
457
|
+
const headWord = argv[loopAt].folded;
|
|
350
458
|
if (tokens.some((t) => t.kind === "group"))
|
|
351
459
|
unknownAll();
|
|
352
|
-
const name = argv[1] !== undefined ? nameOf(argv[1]) : undefined;
|
|
460
|
+
const name = argv[loopAt + 1] !== undefined ? nameOf(argv[loopAt + 1]) : undefined;
|
|
353
461
|
if (name === undefined || name === "computed") {
|
|
354
462
|
unknownAll();
|
|
355
463
|
return { bindings, all };
|
|
356
464
|
}
|
|
357
|
-
const inAt = argv.findIndex((w, i) => i >= 2 && w.raw === "in");
|
|
465
|
+
const inAt = argv.findIndex((w, i) => i >= loopAt + 2 && w.raw === "in");
|
|
358
466
|
let state = "unknown";
|
|
359
|
-
if (headWord === "for" && inAt === 2 && argv.length > 3) {
|
|
467
|
+
if (headWord === "for" && inAt === loopAt + 2 && argv.length > loopAt + 3) {
|
|
360
468
|
const values = [];
|
|
361
|
-
for (const w of argv.slice(3)) {
|
|
469
|
+
for (const w of argv.slice(loopAt + 3)) {
|
|
362
470
|
const v = readValue(w, false, overlay);
|
|
363
471
|
if (v === "unknown") {
|
|
364
472
|
values.length = 0;
|
|
@@ -372,7 +480,7 @@ export function bindingEffectOf(segment, argv, shape, readValue) {
|
|
|
372
480
|
bind({ name, state, loopVar: true });
|
|
373
481
|
return { bindings, all };
|
|
374
482
|
}
|
|
375
|
-
if (
|
|
483
|
+
if (argv[0]?.raw === "coproc" && argv[1] !== undefined)
|
|
376
484
|
killNamed(nameOf(argv[1]));
|
|
377
485
|
const pos = readProgramPosition(argv);
|
|
378
486
|
const program = pos.program;
|
|
@@ -481,7 +589,7 @@ function compoundFloor(raw, readValue, overlay) {
|
|
|
481
589
|
return compoundElementWords(raw).flatMap((w) => {
|
|
482
590
|
const v = w.tail === true ? "unknown" : readValue(w, true, overlay);
|
|
483
591
|
if (v !== "unknown")
|
|
484
|
-
return
|
|
592
|
+
return v.map(floorOf);
|
|
485
593
|
const text = w.pieces.some((q) => q.expansion?.kind === "tilde") ? w.folded : literalTildeSpelling(w.folded);
|
|
486
594
|
return [w.pieces.some((q) => q.expansion?.kind === "glob") ? { spelled: text, glob: true } : { spelled: text }];
|
|
487
595
|
});
|
|
@@ -541,6 +649,7 @@ export class BindingThread {
|
|
|
541
649
|
unreadable;
|
|
542
650
|
blocks;
|
|
543
651
|
detached;
|
|
652
|
+
startFloors;
|
|
544
653
|
constructor(segments, named, initial, boundAnywhere, loops, homeStartsTouched = false, inherited = NOTHING_INHERITED) {
|
|
545
654
|
this.segments = segments;
|
|
546
655
|
this.named = named;
|
|
@@ -548,6 +657,7 @@ export class BindingThread {
|
|
|
548
657
|
this.boundAnywhere = boundAnywhere;
|
|
549
658
|
this.loops = loops;
|
|
550
659
|
this.homeStartsTouched = homeStartsTouched;
|
|
660
|
+
this.startFloors = inherited.floors ?? new Map();
|
|
551
661
|
this.blocks = blockStructure(segments);
|
|
552
662
|
this.detached = segments.map((s, i) => s.connector === "|" || segments[i + 1]?.connector === "|" || segments[i + 1]?.connector === "&");
|
|
553
663
|
this.unreadable = [...(inherited.references ? [{ at: -1 }] : []), ...named.flatMap((effect, at) => this.unreadableBy(effect, at))];
|
|
@@ -555,9 +665,14 @@ export class BindingThread {
|
|
|
555
665
|
}
|
|
556
666
|
inheritedAt(j) {
|
|
557
667
|
const holds = (at) => j === undefined || this.mayRunBefore(at, j);
|
|
668
|
+
const floors = new Map(this.startFloors);
|
|
669
|
+
for (const e of this.entries)
|
|
670
|
+
if (e.name !== undefined && e.floor !== undefined && (j === undefined || e.at < j))
|
|
671
|
+
floors.set(e.name, [...(floors.get(e.name) ?? []), ...e.floor]);
|
|
558
672
|
return {
|
|
559
673
|
attributes: this.attributesAt.filter((x) => holds(x.at)).map(({ name, kind }) => (name !== undefined ? { name, kind } : { kind })),
|
|
560
674
|
references: this.unreadable.some((u) => u.name === undefined && u.body === undefined && holds(u.at)),
|
|
675
|
+
...(floors.size > 0 ? { floors } : {}),
|
|
561
676
|
};
|
|
562
677
|
}
|
|
563
678
|
unreadableBy(effect, at) {
|
|
@@ -652,7 +767,7 @@ export class BindingThread {
|
|
|
652
767
|
return "unknown";
|
|
653
768
|
const values = [];
|
|
654
769
|
const add = (vs) => { for (const v of vs)
|
|
655
|
-
if (!values.some((x) => x
|
|
770
|
+
if (!values.some((x) => sameValue(x, v)))
|
|
656
771
|
values.push(v); };
|
|
657
772
|
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
658
773
|
const e = this.entries[i];
|
|
@@ -679,7 +794,7 @@ export class BindingThread {
|
|
|
679
794
|
isInteger: (name) => SHELL_INTEGER_VARIABLES.has(name) || given(name, j, true),
|
|
680
795
|
ifsTouched: touched("IFS"),
|
|
681
796
|
homeTouched: this.homeStartsTouched || touched("HOME"),
|
|
682
|
-
floorOf: (name) => this.entries.filter((e) => e.at < j && e.name === name).flatMap((e) => e.floor ?? []),
|
|
797
|
+
floorOf: (name) => [...(this.startFloors.get(name) ?? []), ...this.entries.filter((e) => e.at < j && e.name === name).flatMap((e) => e.floor ?? [])],
|
|
683
798
|
inherited: this.inheritedAt(j),
|
|
684
799
|
};
|
|
685
800
|
}
|
|
@@ -57,11 +57,10 @@ function parseBracketExpression(pattern, start) {
|
|
|
57
57
|
return { source: anyChar ? "[^/]" : `[${negate ? "^" : ""}${body}]`, end: i };
|
|
58
58
|
}
|
|
59
59
|
if (ch === "[" && (pattern[i + 1] === ":" || pattern[i + 1] === "." || pattern[i + 1] === "=")) {
|
|
60
|
-
const close = pattern.indexOf(`${pattern[i + 1]}]`, i + 2);
|
|
61
|
-
if (close < 0)
|
|
62
|
-
return undefined;
|
|
63
60
|
anyChar = true;
|
|
64
|
-
|
|
61
|
+
const close = pattern.indexOf(`${pattern[i + 1]}]`, i + 2);
|
|
62
|
+
if (close >= 0 && (pattern[i + 1] === ":" ? /^[A-Za-z]+$/.test(pattern.slice(i + 2, close)) : close === i + 3))
|
|
63
|
+
i = close + 1;
|
|
65
64
|
continue;
|
|
66
65
|
}
|
|
67
66
|
body += ch === "-" ? "-" : ch.replace(/[\]\\^]/g, "\\$&");
|
|
@@ -557,7 +556,7 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
557
556
|
for (const m of walkedTree.members)
|
|
558
557
|
out.push({ ...(await canonicalReadOf(candidate, m.path, [m.path], "expansion", folded, m.canonical, signal)), node: m.node, walked: true });
|
|
559
558
|
};
|
|
560
|
-
for (const lexical of lexicalReadsOf(candidate, boundary)) {
|
|
559
|
+
for (const [i, lexical] of lexicalReadsOf(candidate, boundary).entries()) {
|
|
561
560
|
if (lexical.established !== "lexical") {
|
|
562
561
|
out.push(lexical);
|
|
563
562
|
continue;
|
|
@@ -567,7 +566,7 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
567
566
|
const walked = spellings.find((s) => s !== folded) ?? folded;
|
|
568
567
|
if (candidate.expands === "glob") {
|
|
569
568
|
let matched = false;
|
|
570
|
-
for (const p of spellings) {
|
|
569
|
+
for (const p of candidate.pattern === undefined ? spellings : lexicalReadsOf({ ...candidate, spelled: candidate.pattern }, boundary)[i]?.spellings ?? spellings) {
|
|
571
570
|
const expanded = await expandGlobCandidate(p, roots, signal);
|
|
572
571
|
if (expanded !== undefined && "unverifiable" in expanded) {
|
|
573
572
|
out.push({ candidate, established: "unestablishable", spellings, origin: folded, cause: expanded.unverifiable });
|
|
@@ -581,7 +580,7 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
581
580
|
await land(read, spellings, folded);
|
|
582
581
|
}
|
|
583
582
|
}
|
|
584
|
-
if (!matched)
|
|
583
|
+
if (!matched || candidate.pattern !== undefined)
|
|
585
584
|
for (const read of await atSeats(walked, spellings, "canonical", undefined, undefined))
|
|
586
585
|
await land(read, spellings, folded);
|
|
587
586
|
continue;
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
* Every ambiguity resolves toward MORE candidates, never fewer.
|
|
31
31
|
*/
|
|
32
32
|
import { type LenientConnector, type LexedRedirect, type LexedWord } from "../../shell/lex.js";
|
|
33
|
-
import { type BindingTable, type NameReader } from "./shell-read-bindings.js";
|
|
34
33
|
import { type ShellScanDecline } from "./bash-program-position.js";
|
|
35
34
|
import { type StdinText } from "./shell-reentry-text.js";
|
|
35
|
+
import { type PlanStart } from "./shell-read-start.js";
|
|
36
36
|
/** WHY a word is a read candidate — a closed set, registered in `docs/CLOSED-SETS.md`. */
|
|
37
37
|
export declare const READ_CANDIDATE_PROVENANCES: readonly ["operand", "option_value", "stdin_redirect", "write_redirect_target", "env_assignment", "unlisted_word", "traversal_root", "cd_target", "program_path", "script_slot_file", "mount_source", "object_path"];
|
|
38
38
|
export type ReadCandidateProvenance = (typeof READ_CANDIDATE_PROVENANCES)[number];
|
|
@@ -49,8 +49,9 @@ export interface ShellReadDeclarations {
|
|
|
49
49
|
readonly face?: "open" | "roots";
|
|
50
50
|
}
|
|
51
51
|
export interface ReadCandidate {
|
|
52
|
-
/** The folded word (quotes removed, a literal leading tilde spelled `./~`). */
|
|
52
|
+
/** The folded word (quotes removed, a literal leading tilde spelled `./~`) — a pattern's name where nothing matches. */
|
|
53
53
|
readonly spelled: string;
|
|
54
|
+
/** The spelling a pattern's names are matched by where the matcher widens a class (`["!"a]`, `[[:alpha:]]`: any one character). */ readonly pattern?: string;
|
|
54
55
|
/** The raw spelling, quotes retained — where "is this metacharacter quoted" is still answerable. */
|
|
55
56
|
readonly raw: string;
|
|
56
57
|
readonly provenance: ReadCandidateProvenance;
|
|
@@ -141,18 +142,5 @@ export interface ShellReadPlan {
|
|
|
141
142
|
readonly strayRedirection: boolean;
|
|
142
143
|
};
|
|
143
144
|
}
|
|
144
|
-
/** Where a nested text starts its names: a substitution body inherits the table of the segment it sits in (a subshell
|
|
145
|
-
* holds every variable); a text the shell runs later or elsewhere (a code body, `eval`'s text, a child shell's `-c`)
|
|
146
|
-
* starts from the text's own start — every name unknown, `HOME` the declared home unless the enclosing text binds it
|
|
147
|
-
* anywhere. `boundAnywhere` answers for the enclosing texts. */
|
|
148
|
-
interface PlanStart {
|
|
149
|
-
readonly initial: NameReader;
|
|
150
|
-
readonly boundAnywhere: (name: string) => boolean;
|
|
151
|
-
/** `HOME` may already hold something other than the declared home where the nested text starts. */
|
|
152
|
-
readonly homeTouched: boolean;
|
|
153
|
-
/** The attributes and name references in force where the nested text starts. */
|
|
154
|
-
readonly inherited: BindingTable["inherited"];
|
|
155
|
-
}
|
|
156
145
|
/** Plan every read candidate of one command (the contract is this module's header). */
|
|
157
146
|
export declare function planShellReads(command: string, decl: ShellReadDeclarations, depth?: number, start?: PlanStart): ShellReadPlan;
|
|
158
|
-
export {};
|