@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/dist/core/allow-entry.d.ts +1 -1
  3. package/dist/core/allow-entry.js +4 -5
  4. package/dist/core/ask-origin.d.ts +35 -12
  5. package/dist/core/ask-origin.js +23 -6
  6. package/dist/core/checkpoint-store.d.ts +1 -1
  7. package/dist/core/engine-notice.d.ts +7 -4
  8. package/dist/core/gate-fold.js +2 -2
  9. package/dist/core/gate-lanes.js +2 -1
  10. package/dist/core/gate-outcome.d.ts +4 -2
  11. package/dist/core/gate-outcome.js +2 -2
  12. package/dist/core/mcp-server-spec.d.ts +1 -1
  13. package/dist/core/permission-rule-model.d.ts +12 -6
  14. package/dist/core/permission-rule-model.js +10 -2
  15. package/dist/core/permission-rules.d.ts +4 -5
  16. package/dist/core/permission-rules.js +28 -15
  17. package/dist/core/runner/gate-exit.d.ts +4 -1
  18. package/dist/core/runner/gate-exit.js +5 -1
  19. package/dist/core/runner/park-commit.d.ts +1 -1
  20. package/dist/core/runner/prepare-hands-readface.js +5 -5
  21. package/dist/core/runner/prepare-policy-chain.js +2 -1
  22. package/dist/core/safety-axis-vocab.d.ts +1 -1
  23. package/dist/core/safety-merge-corpus.d.ts +1 -1
  24. package/dist/core/session-policy-store.d.ts +6 -3
  25. package/dist/core/session-policy-store.js +28 -10
  26. package/dist/core/shell-wrapper-projection.js +1 -1
  27. package/dist/core/shell-wrapper-table.d.ts +8 -8
  28. package/dist/core/shell-wrapper-table.js +3 -3
  29. package/dist/core/tool-catalog-entries.js +3 -2
  30. package/dist/core/tool-face.d.ts +10 -1
  31. package/dist/core/tool-name-aliases.d.ts +9 -8
  32. package/dist/core/tool-name-aliases.js +2 -0
  33. package/dist/core/tool-name-readings.d.ts +8 -0
  34. package/dist/core/tool-name-readings.js +2 -0
  35. package/dist/core/tool-policy.d.ts +10 -9
  36. package/dist/core/tool-policy.js +6 -7
  37. package/dist/core/tool-registry.d.ts +9 -0
  38. package/dist/core/tool-registry.js +10 -0
  39. package/dist/core/write-protect.d.ts +10 -3
  40. package/dist/core/write-protect.js +11 -1
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.js +1 -1
  43. package/dist/orchestration/workflow.js +2 -2
  44. package/dist/prompt-assembly/types.d.ts +1 -1
  45. package/dist/shell/lex.d.ts +15 -0
  46. package/dist/shell/lex.js +86 -15
  47. package/dist/tools/fs/bash-program-position.d.ts +16 -6
  48. package/dist/tools/fs/bash-program-position.js +18 -12
  49. package/dist/tools/fs/index.d.ts +3 -2
  50. package/dist/tools/fs/index.js +2 -2
  51. package/dist/tools/fs/program-read-model.d.ts +84 -12
  52. package/dist/tools/fs/program-read-model.js +105 -34
  53. package/dist/tools/fs/program-read-model.rows.js +26 -7
  54. package/dist/tools/fs/read-deny-home.d.ts +20 -1
  55. package/dist/tools/fs/read-deny-home.js +28 -7
  56. package/dist/tools/fs/read-deny.d.ts +13 -6
  57. package/dist/tools/fs/read-deny.js +12 -3
  58. package/dist/tools/fs/removal-targets.js +24 -5
  59. package/dist/tools/fs/shell-read-argv.d.ts +6 -8
  60. package/dist/tools/fs/shell-read-argv.js +5 -5
  61. package/dist/tools/fs/shell-read-bindings.d.ts +71 -19
  62. package/dist/tools/fs/shell-read-bindings.js +190 -75
  63. package/dist/tools/fs/shell-read-establish.js +6 -7
  64. package/dist/tools/fs/shell-read-plan.d.ts +3 -15
  65. package/dist/tools/fs/shell-read-plan.js +126 -129
  66. package/dist/tools/fs/shell-read-start.d.ts +57 -0
  67. package/dist/tools/fs/shell-read-start.js +92 -0
  68. package/dist/tools/fs/shell-read-word.d.ts +68 -8
  69. package/dist/tools/fs/shell-read-word.js +106 -12
  70. package/dist/tools/fs/shell-read-write-targets.js +4 -3
  71. package/dist/tools/fs/shell-reentry-text.d.ts +103 -3
  72. package/dist/tools/fs/shell-reentry-text.js +122 -4
  73. package/dist/tools/monitor.d.ts +3 -3
  74. package/package.json +1 -1
  75. package/test/export-surface.snapshot.json +9 -3
  76. package/wire-changes/core@7.47.0.json +412 -0
  77. 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 ONE_WORD = /^[A-Za-z0-9_./+@:%=,-]+$/;
216
+ const READABLE_VALUE = /^[^\\~()]*$/;
200
217
  const WORD_VALUES_MAX = 64;
201
- export function wordValues(word, table, quoted, overlay = new Map()) {
202
- const pieces = word.pieces;
203
- if (!pieces.some((p) => p.expansion?.kind === "parameter" || p.expansion?.kind === "tilde"))
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
- if (table === undefined)
206
- return "unknown";
207
- const start = pieces[0]?.span[0] ?? 0;
208
- const raw = word.raw;
209
- let combos = [{ text: "", glob: false }];
210
- const stateOf = (name) => overlay.get(name) ?? table.stateOf(name);
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
- let options;
214
- if (e === undefined) {
215
- if (p.literal === undefined)
216
- return "unknown";
217
- options = [{ spelled: p.literal }];
218
- }
219
- else if (e.kind === "glob") {
220
- const written = raw === undefined ? undefined : raw.slice(p.span[0] - start, p.span[1] - start);
221
- if (written === undefined)
222
- return "unknown";
223
- options = [{ spelled: written, glob: true }];
224
- }
225
- else if (e.kind === "tilde") {
226
- if (e.body !== "~")
227
- return "unknown";
228
- const home = stateOf("HOME");
229
- if (home === "unknown")
230
- return "unknown";
231
- options = home;
232
- }
233
- else if (e.kind === "parameter" && e.name !== undefined && (e.body === undefined || e.body === e.name)) {
234
- const state = stateOf(e.name);
235
- if (state === "unknown")
236
- return "unknown";
237
- if (p.quotedBy === undefined && !quoted && (table.ifsTouched || state.some((v) => v.glob === true || !ONE_WORD.test(v.spelled))))
238
- return "unknown";
239
- options = state;
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
- else
242
- return "unknown";
243
- const next = [];
244
- for (const c of combos)
245
- for (const o of options)
246
- next.push({ text: c.text + o.spelled, glob: c.glob || o.glob === true });
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 "unknown";
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
- if (!out.some((v) => v.spelled === c.text && (v.glob === true) === c.glob))
254
- out.push(c.glob ? { spelled: c.text, glob: true } : { spelled: c.text });
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
- next.push(c.glob === true || o.glob === true ? { spelled: c.spelled + o.spelled, glob: true } : { spelled: c.spelled + o.spelled });
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 valueOf(word, table, quoted, overlay = new Map()) {
288
- const values = wordValues(word, table, quoted, overlay);
289
- if (values !== undefined)
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 head = argv[0];
348
- const headWord = head !== undefined && head.raw === head.folded ? head.folded : undefined;
349
- if (headWord === "for" || headWord === "select") {
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 (headWord === "coproc" && argv[1] !== undefined)
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 [...v];
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.spelled === v.spelled && x.glob === v.glob))
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
- i = close + 1;
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 {};