@sema-agent/core 7.29.0 → 7.30.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 (149) hide show
  1. package/CHANGELOG.md +361 -0
  2. package/dist/agents/child-model-seat.d.ts +59 -7
  3. package/dist/agents/child-model-seat.js +33 -7
  4. package/dist/agents/cross-session-judge.d.ts +27 -5
  5. package/dist/agents/cross-session-judge.js +23 -12
  6. package/dist/agents/list-agents-tool.d.ts +31 -6
  7. package/dist/agents/list-agents-tool.js +66 -14
  8. package/dist/agents/peer-admission.d.ts +20 -8
  9. package/dist/agents/peer-admission.js +21 -12
  10. package/dist/agents/peer-directory.d.ts +29 -0
  11. package/dist/agents/peer-directory.js +20 -0
  12. package/dist/agents/peer-held-queue.d.ts +31 -4
  13. package/dist/agents/peer-held-queue.js +32 -1
  14. package/dist/agents/peer-session-drain.d.ts +91 -13
  15. package/dist/agents/peer-session-drain.js +409 -102
  16. package/dist/agents/read-inbox-tool.d.ts +22 -0
  17. package/dist/agents/read-inbox-tool.js +39 -0
  18. package/dist/agents/roster-store.d.ts +2 -4
  19. package/dist/agents/roster-store.js +1 -1
  20. package/dist/agents/send-message-tool.d.ts +4 -4
  21. package/dist/agents/send-message-tool.js +22 -2
  22. package/dist/agents/subagent.js +62 -44
  23. package/dist/agents/team-role.d.ts +34 -0
  24. package/dist/agents/team-role.js +17 -0
  25. package/dist/agents/verify.js +2 -2
  26. package/dist/core/auto-compaction.d.ts +42 -0
  27. package/dist/core/auto-compaction.js +35 -2
  28. package/dist/core/child-clamp-seats.d.ts +11 -0
  29. package/dist/core/child-clamp-seats.js +3 -0
  30. package/dist/core/context-edit.d.ts +3 -2
  31. package/dist/core/context-edit.js +6 -5
  32. package/dist/core/context-guard.d.ts +2 -1
  33. package/dist/core/context-guard.js +3 -2
  34. package/dist/core/context-settings.d.ts +103 -0
  35. package/dist/core/context-settings.js +60 -0
  36. package/dist/core/engine-notice.d.ts +11 -1
  37. package/dist/core/frame-settlement.d.ts +97 -0
  38. package/dist/core/frame-settlement.js +36 -0
  39. package/dist/core/governance-codes.d.ts +1 -1
  40. package/dist/core/governance-codes.js +2 -0
  41. package/dist/core/hooks.d.ts +10 -10
  42. package/dist/core/mailbox-store.d.ts +41 -7
  43. package/dist/core/mailbox-store.js +9 -3
  44. package/dist/core/parent-spec-seat.d.ts +14 -1
  45. package/dist/core/parent-spec-seat.js +6 -0
  46. package/dist/core/permission-rule-model.js +1 -1
  47. package/dist/core/roles.d.ts +20 -0
  48. package/dist/core/roles.js +35 -16
  49. package/dist/core/runner/assemble-result.d.ts +2 -0
  50. package/dist/core/runner/assemble-result.js +1 -1
  51. package/dist/core/runner/compaction-call-options.d.ts +26 -2
  52. package/dist/core/runner/compaction-call-options.js +9 -0
  53. package/dist/core/runner/compaction-seams.d.ts +4 -3
  54. package/dist/core/runner/compaction-seams.js +13 -6
  55. package/dist/core/runner/contracts.d.ts +56 -7
  56. package/dist/core/runner/decide-continuation.d.ts +2 -1
  57. package/dist/core/runner/decide-continuation.js +11 -10
  58. package/dist/core/runner/initial-run-state.js +1 -1
  59. package/dist/core/runner/prepare-caps-and-workflow.js +9 -1
  60. package/dist/core/runner/prepare-config-doors.js +4 -1
  61. package/dist/core/runner/prepare-context-lane.d.ts +9 -2
  62. package/dist/core/runner/prepare-context-lane.js +15 -10
  63. package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
  64. package/dist/core/runner/prepare-delegation-surface.js +9 -3
  65. package/dist/core/runner/prepare-file-history.js +10 -8
  66. package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
  67. package/dist/core/runner/prepare-gate-stations.js +3 -1
  68. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  69. package/dist/core/runner/prepare-prompt-inputs.d.ts +6 -0
  70. package/dist/core/runner/prepare-prompt-inputs.js +2 -1
  71. package/dist/core/runner/prepare-task.js +22 -9
  72. package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -1
  73. package/dist/core/runner/prepare-wiring-manifest.js +6 -1
  74. package/dist/core/runner/retired-runner-deps.d.ts +3 -1
  75. package/dist/core/runner/retired-runner-deps.js +1 -0
  76. package/dist/core/runner/run-compaction-machinery.d.ts +4 -2
  77. package/dist/core/runner/run-compaction-machinery.js +5 -3
  78. package/dist/core/runner/run-harness-handlers.js +1 -1
  79. package/dist/core/runner/run-identity-wiring.js +19 -1
  80. package/dist/core/runner/run-leg.js +1 -1
  81. package/dist/core/runner/run-notification-lane.js +8 -2
  82. package/dist/core/runner/run-recovery-lanes.js +9 -5
  83. package/dist/core/runner/run-settle-and-teardown.d.ts +4 -22
  84. package/dist/core/runner/run-settle-and-teardown.js +6 -10
  85. package/dist/core/runner/run-stop-and-final-verify.js +1 -0
  86. package/dist/core/runner/run-terminal-adoption.js +5 -5
  87. package/dist/core/runner/run-turn-boundary.js +32 -5
  88. package/dist/core/runner/runtask.d.ts +8 -1
  89. package/dist/core/runner/runtask.js +32 -1
  90. package/dist/core/runner-deps.d.ts +44 -13
  91. package/dist/core/swappable-deps.d.ts +20 -4
  92. package/dist/core/swappable-deps.js +6 -1
  93. package/dist/core/task-event.d.ts +5 -0
  94. package/dist/core/task-notification.d.ts +144 -2
  95. package/dist/core/task-notification.js +154 -5
  96. package/dist/core/task-result.d.ts +14 -0
  97. package/dist/core/task-spec.d.ts +7 -0
  98. package/dist/core/tool-catalog-entries.js +2 -1
  99. package/dist/core/tool-conformance.js +1 -0
  100. package/dist/core/tool-policy.d.ts +9 -4
  101. package/dist/core/tool-spec.d.ts +14 -0
  102. package/dist/core/wiring-manifest.d.ts +12 -0
  103. package/dist/core/wiring-manifest.js +5 -1
  104. package/dist/engine/harness/agent-harness.d.ts +14 -1
  105. package/dist/engine/harness/agent-harness.js +28 -3
  106. package/dist/engine/harness/types.d.ts +3 -1
  107. package/dist/engine/llm/types.d.ts +8 -7
  108. package/dist/engine/llm/types.js +1 -1
  109. package/dist/engine/session/import-validate.js +4 -0
  110. package/dist/index.d.ts +10 -7
  111. package/dist/index.js +7 -5
  112. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  113. package/dist/orchestration/workflow.d.ts +1 -1
  114. package/dist/orchestration/workflow.js +8 -5
  115. package/dist/shell/lex.d.ts +30 -51
  116. package/dist/shell/lex.js +237 -284
  117. package/dist/stores/cc/mailbox-store.js +3 -2
  118. package/dist/stores/file/corrupt-read-sink.d.ts +28 -2
  119. package/dist/stores/file/file-history-store.d.ts +2 -4
  120. package/dist/stores/file/file-history-store.js +1 -1
  121. package/dist/stores/file/fs-atomic.d.ts +16 -11
  122. package/dist/stores/file/fs-atomic.js +17 -6
  123. package/dist/stores/file/index.d.ts +5 -6
  124. package/dist/stores/file/index.js +1 -1
  125. package/dist/stores/file/mailbox-store.d.ts +4 -9
  126. package/dist/stores/file/mailbox-store.js +29 -30
  127. package/dist/stores/file/session-policy-store.d.ts +2 -0
  128. package/dist/stores/file/session-policy-store.js +1 -1
  129. package/dist/stores/file/session-store.d.ts +11 -7
  130. package/dist/stores/file/session-store.js +23 -16
  131. package/dist/stores/file/shared-ledger.js +3 -14
  132. package/dist/stores/file/tool-result-store.d.ts +2 -0
  133. package/dist/stores/file/tool-result-store.js +5 -2
  134. package/dist/stores/file/workflow-journal-store.js +14 -9
  135. package/dist/tools/fs/bash-program-position.d.ts +15 -24
  136. package/dist/tools/fs/bash-program-position.js +11 -29
  137. package/dist/tools/fs/bash-readonly-classifier.d.ts +125 -117
  138. package/dist/tools/fs/bash-readonly-classifier.js +400 -272
  139. package/dist/tools/fs/shell-read-check.js +3 -3
  140. package/dist/tools/fs/shell-read-establish.js +1 -1
  141. package/dist/tools/fs/shell-read-plan.d.ts +11 -6
  142. package/dist/tools/fs/shell-read-plan.js +167 -183
  143. package/dist/tools/fs/shell-read-word.d.ts +50 -0
  144. package/dist/tools/fs/shell-read-word.js +59 -0
  145. package/dist/tools/fs/shell-reentry-text.d.ts +10 -0
  146. package/dist/tools/fs/shell-reentry-text.js +73 -0
  147. package/package.json +1 -1
  148. package/test/export-surface.snapshot.json +58 -8
  149. package/wire-changes/core@7.30.0.json +1821 -0
@@ -1,7 +1,9 @@
1
- import { lexShell, PARSE_FAILURE_SENTENCE, UNRESOLVED_SENTENCE, UNRESOLVED_VIEW, substitutionsInHeredocBody, wordsOf, literalTildeSpelling, SUBSTITUTION_PLACEHOLDER, } from "../../shell/lex.js";
2
- import { LAUNCHER_TABLE, SHELL_BLOCK_WORDS, programBasename, programPositionRunsShell, readProgramPosition, shellScanDeclineOf, writtenAssignmentPrefix, } from "./bash-program-position.js";
3
- import { ASSIGNMENT_VALUE_NOT_PATHS, INTERPRETER_INLINE_PROGRAMS, NO_PATH_OPERAND_COMMANDS, POLL_LOOP_MAX_BEATS, RECURSIVE_READ_FORMS, SHELL_REENTRY_PROGRAM_RE, UNMODELLED_BASE_MOVERS, argvProgramOf, assignmentWordPayload, attachedOptionPayloads, foldHomeVariableSpelling, resolveOperandLexically, hasUnquotedExpansionMetachar, hasUnquotedGlobMetachar, inlineProgramClause, interpreterKey, isPathShapedToken, readVerbOptionSlot, sedScriptSlotFiles, segmentSelectsRecursiveRead, slotValueCandidates, suppliesFirstOperandSlot, verbFirstOperandSlot, verbOptionsArePositionIndependent, } from "./bash-readonly-classifier.js";
1
+ import { lexShell, PARSE_FAILURE_SENTENCE, UNRESOLVED_SENTENCE, substitutionsInHeredocBody, literalTildeSpelling, } from "../../shell/lex.js";
2
+ import { compoundArrayElements, dropDefinitionHeads, expandsOf, readOfText, readsOf, rewritten, spelledWithHome } from "./shell-read-word.js";
3
+ import { LAUNCHER_TABLE, SHELL_BLOCK_WORDS, programBasename, programPositionRunsShell, readProgramPosition, shellScanDeclineOf, } from "./bash-program-position.js";
4
+ import { ASSIGNMENT_VALUE_NOT_PATHS, INTERPRETER_INLINE_PROGRAMS, NO_PATH_OPERAND_COMMANDS, POLL_LOOP_MAX_BEATS, RECURSIVE_READ_FORMS, SHELL_REENTRY_PROGRAM_RE, UNMODELLED_BASE_MOVERS, argvProgramOf, homeMayBeRebound, attachedOptionPayloads, resolveOperandLexically, inlineProgramClause, interpreterKey, isPathShapedToken, readVerbOptionSlot, sedScriptSlotFiles, segmentSelectsRecursiveRead, slotValueCandidates, suppliesFirstOperandSlot, verbFirstOperandSlot, verbOptionsArePositionIndependent, } from "./bash-readonly-classifier.js";
4
5
  import { isAbsolutePathForm } from "./safety.js";
6
+ import { codeBodies, reentryText } from "./shell-reentry-text.js";
5
7
  export const READ_CANDIDATE_PROVENANCES = [
6
8
  "operand",
7
9
  "option_value",
@@ -16,39 +18,39 @@ export const READ_CANDIDATE_PROVENANCES = [
16
18
  ];
17
19
  const NESTING_MAX = 4;
18
20
  const BASE_CANDIDATE_MAX = 256;
19
- function firstRefusedUnresolvedSentence(lexed) {
21
+ function firstUnresolvedSentence(lexed) {
20
22
  for (const segment of lexed.segments) {
21
23
  for (const token of segment.tokens) {
22
24
  const word = token.kind === "word" ? token : token.kind === "redirect" ? token.target : undefined;
23
- if (word?.unresolved !== undefined && UNRESOLVED_VIEW[word.unresolved] === "refuse")
25
+ if (word?.unresolved !== undefined)
24
26
  return UNRESOLVED_SENTENCE[word.unresolved];
25
27
  }
26
28
  }
27
29
  return undefined;
28
30
  }
29
- export function planShellReads(command, decl, depth = 0) {
31
+ export function planShellReads(command, decl, depth = 0, homeRebound = false) {
30
32
  const shape = { grouped: false, backgrounded: false, strayRedirection: false };
31
33
  if (depth > NESTING_MAX)
32
34
  return { segments: [], unreadable: `command text nested more than ${NESTING_MAX} levels deep`, shape };
33
- const homeReassigned = /(^|[\s;&|(){}`])(?:export\s+)?HOME\+?=/.test(command);
34
- const lexed = lexShell(foldHomeVariableSpelling(command, homeReassigned ? undefined : decl.homeDir), { mode: "lenient" });
35
+ const lexed = lexShell(command);
35
36
  if ("parseFailure" in lexed)
36
37
  return { segments: [], unreadable: PARSE_FAILURE_SENTENCE[lexed.parseFailure.reason], shape };
37
- const unreadable = firstRefusedUnresolvedSentence(lexed);
38
- const nestedPlan = (text) => planShellReads(text, decl, depth + 1);
38
+ const home = homeRebound || homeMayBeRebound(command) ? undefined : decl.homeDir;
39
+ const unreadable = firstUnresolvedSentence(lexed);
40
+ const nestedPlan = (text) => planShellReads(text, decl, depth + 1, home === undefined);
39
41
  const projected = lexed.segments.map((segment) => projectSegment(segment));
40
- const facts = projected.map((p, si) => segmentFacts(lexed.segments[si], p, decl, nestedPlan));
42
+ const facts = projected.map((p, si) => segmentFacts(lexed.segments[si], p, decl, home, nestedPlan));
41
43
  shape.grouped = projected.some((p) => p.grouped);
42
44
  shape.backgrounded = lexed.segments.some((s) => s.connector === "&") || facts.some((f) => f.backgrounds === true);
43
- shape.strayRedirection = projected.some((p) => p.words.folded.length === 0 && p.redirects.length > 0);
45
+ shape.strayRedirection = projected.some((p) => p.argv.length === 0 && p.redirects.length > 0);
44
46
  const basesOf = facts.map(() => new Set());
45
47
  const unmodelled = facts.map(() => false);
46
48
  let candidates = [{ base: decl.cwd, skippedCd: -1 }];
47
49
  let baseLost = false;
48
50
  let floorBases;
49
- const loseBase = (reachable) => {
51
+ const loseBase = (reachable, floorTarget) => {
50
52
  baseLost = true;
51
- floorBases ??= [...new Set(reachable.map((c) => c.base))];
53
+ floorBases ??= [...new Set([...reachable.map((c) => c.base), ...(floorTarget !== undefined ? [floorTarget] : [])])];
52
54
  };
53
55
  let andRunStart = 0;
54
56
  let crossedAllAnd = true;
@@ -84,7 +86,7 @@ export function planShellReads(command, decl, depth = 0) {
84
86
  if (move === undefined || baseLost)
85
87
  return;
86
88
  if (typeof move.target === "string" || move.target.expands !== false) {
87
- loseBase(reachable);
89
+ loseBase(reachable, typeof move.target === "string" ? undefined : move.target.floorSpelled);
88
90
  return;
89
91
  }
90
92
  const target = move.target.spelled;
@@ -176,11 +178,7 @@ export function planShellReads(command, decl, depth = 0) {
176
178
  function projectSegment(segment) {
177
179
  const tokens = [...segment.tokens];
178
180
  let grouped = tokens.some((t) => t.kind === "group");
179
- for (let k = 0; k + 2 < tokens.length; k++) {
180
- const [a, b, c] = [tokens[k], tokens[k + 1], tokens[k + 2]];
181
- if (a.kind === "word" && b.kind === "group" && b.op === "(" && c.kind === "group" && c.op === ")" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(a.folded))
182
- tokens.splice(k, 3);
183
- }
181
+ dropDefinitionHeads(tokens);
184
182
  const words = tokens.filter((t) => {
185
183
  if (t.kind !== "word")
186
184
  return false;
@@ -191,23 +189,22 @@ function projectSegment(segment) {
191
189
  return true;
192
190
  });
193
191
  return {
194
- words: wordsOf({ ...segment, tokens: words }),
195
192
  argv: words,
196
193
  redirects: tokens.filter((t) => t.kind === "redirect"),
197
194
  grouped,
198
195
  };
199
196
  }
200
- function segmentFacts(segment, p, decl, nestedPlan) {
201
- const facts = segmentFactsRead(segment, p, decl, nestedPlan);
197
+ function segmentFacts(segment, p, decl, home, nestedPlan) {
198
+ const facts = segmentFactsRead(segment, p, decl, home, nestedPlan);
202
199
  const unresolvedRaw = new Set();
203
200
  for (const t of segment.tokens) {
204
201
  const w = t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined;
205
- if (w?.unresolved !== undefined && UNRESOLVED_VIEW[w.unresolved] === "refuse")
202
+ if (w !== undefined && w.pieces.some((p) => p.literal === undefined && p.expansion === undefined))
206
203
  unresolvedRaw.add(w.raw);
207
204
  }
208
- if (unresolvedRaw.size === 0)
209
- return facts;
210
- const unsettle = (c) => (unresolvedRaw.has(c.raw) && c.expands !== "unresolvable" ? { ...c, expands: "unresolvable" } : c);
205
+ const homeWithheld = home === undefined && decl.homeDir !== undefined;
206
+ const floor = (c) => { const f = c.expands === "unresolvable" && decl.homeDir !== undefined ? spelledWithHome(c.spelled, c.raw, decl.homeDir) : undefined; return f === undefined ? c : { ...c, floorSpelled: f }; };
207
+ const unsettle = (c) => floor((unresolvedRaw.has(c.raw) || (homeWithheld && c.spelled.startsWith("~"))) && c.expands !== "unresolvable" ? { ...c, expands: "unresolvable" } : c);
211
208
  const move = facts.baseMove;
212
209
  return {
213
210
  ...facts,
@@ -215,7 +212,7 @@ function segmentFacts(segment, p, decl, nestedPlan) {
215
212
  ...(move !== undefined && typeof move.target !== "string" ? { baseMove: { target: unsettle(move.target) } } : {}),
216
213
  };
217
214
  }
218
- function segmentFactsRead(segment, p, decl, nestedPlan) {
215
+ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
219
216
  const nested = [];
220
217
  for (const body of segment.substitutions)
221
218
  nested.push(nestedPlan(body));
@@ -233,7 +230,7 @@ function segmentFactsRead(segment, p, decl, nestedPlan) {
233
230
  }
234
231
  if (unreadableHeredoc)
235
232
  nested.push({ segments: [], unreadable: "a here-document body carries a substitution this lexer cannot delimit", shape: { grouped: false, backgrounded: false, strayRedirection: false } });
236
- const redirects = p.redirects.map((r) => redirectFact(r));
233
+ const redirects = p.redirects.map((r) => redirectFact(r, home));
237
234
  const reads = [];
238
235
  for (const r of redirects)
239
236
  if (r.direction === "in" && r.target !== undefined)
@@ -245,30 +242,30 @@ function segmentFactsRead(segment, p, decl, nestedPlan) {
245
242
  redirects,
246
243
  nested,
247
244
  };
248
- const toks = p.words;
249
- const head = toks.folded[0];
250
- if (head === "coproc" && toks.raw[0] === head && toks.folded.length > 1) {
251
- const tail = (from) => ({ words: { folded: toks.folded.slice(from), raw: toks.raw.slice(from) }, argv: p.argv.slice(from), redirects: p.redirects, grouped: p.grouped });
252
- const first = segmentFacts(segment, tail(1), decl, nestedPlan);
253
- if (toks.folded.length < 3)
245
+ const toks = readsOf(p.argv, home);
246
+ const head = toks[0]?.folded;
247
+ if (head === "coproc" && toks[0].raw === head && toks.length > 1) {
248
+ const tail = (from) => ({ argv: p.argv.slice(from), redirects: p.redirects, grouped: p.grouped });
249
+ const first = segmentFacts(segment, tail(1), decl, home, nestedPlan);
250
+ if (toks.length < 3)
254
251
  return { ...first, backgrounds: true };
255
- const second = segmentFacts(segment, tail(2), decl, nestedPlan);
252
+ const second = segmentFacts(segment, tail(2), decl, home, nestedPlan);
256
253
  const seen = new Set(first.nested.map((n) => JSON.stringify(n.segments.map((sg) => sg.text))));
257
254
  const extraNested = second.nested.filter((n) => !seen.has(JSON.stringify(n.segments.map((sg) => sg.text))));
258
- return { ...first, reads: [...first.reads, ...second.reads], nested: [...first.nested, ...extraNested], backgrounds: true };
255
+ return { ...first, ...(second.reentry === "shell" ? { reentry: second.reentry } : {}), reads: [...first.reads, ...second.reads], nested: [...first.nested, ...extraNested], backgrounds: true };
259
256
  }
260
- if (head !== undefined && toks.raw[0] === head && SHELL_BLOCK_WORDS.has(head)) {
257
+ if (head !== undefined && toks[0].raw === head && SHELL_BLOCK_WORDS.has(head)) {
261
258
  if (head === "for" || head === "select")
262
259
  return { ...base, empty: false, launcherChain: [], declines: [], reads, moveProvable: false, loopHead: { beats: literalBeats(toks) }, loopEnd: false };
263
260
  if (head === "done")
264
261
  return { ...base, empty: false, launcherChain: [], declines: [], reads, moveProvable: false, loopEnd: true };
265
262
  return { ...base, empty: false, launcherChain: [], declines: [], reads, moveProvable: false, loopEnd: false };
266
263
  }
267
- const pos = readProgramPosition(toks);
264
+ const pos = readProgramPosition(p.argv);
268
265
  const written = pos.written;
269
266
  if (written === undefined)
270
- return { ...base, empty: toks.folded.length === 0 && redirects.length === 0 && nested.length === 0, launcherChain: [], declines: [], reads, moveProvable: false, loopEnd: false };
271
- const loopHead = pos.controlWords.some((i) => toks.folded[i] === "while" || toks.folded[i] === "until") ? { beats: undefined } : undefined;
267
+ return { ...base, empty: toks.length === 0 && redirects.length === 0 && nested.length === 0, launcherChain: [], declines: [], reads, moveProvable: false, loopEnd: false };
268
+ const loopHead = pos.controlWords.some((i) => toks[i].folded === "while" || toks[i].folded === "until") ? { beats: undefined } : undefined;
272
269
  const launcherChain = pos.launchers.map((l) => l.name);
273
270
  const launcherIndices = new Set(pos.launchers.map((l) => l.index));
274
271
  const declines = [];
@@ -281,24 +278,23 @@ function segmentFactsRead(segment, p, decl, nestedPlan) {
281
278
  const verb = program === undefined ? undefined : { name: program.name, basename: program.basename, listed };
282
279
  const envOpens = basename !== undefined && !NO_PATH_OPERAND_COMMANDS.has(basename);
283
280
  for (const k of pos.assignments)
284
- pushAssignment(reads, toks.folded[k], toks.raw[k], "env_assignment", envOpens);
281
+ pushAssignment(reads, toks[k].word, "env_assignment", envOpens);
285
282
  for (const l of pos.launchers) {
286
- const raw = toks.raw[l.index];
287
- const folded = toks.folded[l.index];
288
- if (isPathShapedToken(folded))
289
- reads.push(bare(raw, folded, "program_path"));
283
+ const r = toks[l.index];
284
+ if (isPathShapedToken(r.folded))
285
+ reads.push(bare(r, "program_path"));
290
286
  }
291
287
  {
292
288
  const controlIndices = new Set(pos.controlWords);
293
289
  const assignmentIndices = new Set(pos.assignments);
294
- const chainEnd = pos.program?.index ?? toks.folded.length;
290
+ const chainEnd = pos.program?.index ?? toks.length;
295
291
  const launcherValueIndices = new Set();
296
292
  for (const l of pos.launchers) {
297
293
  const row = LAUNCHER_TABLE.get(programBasename(l.name));
298
294
  if (row === undefined)
299
295
  continue;
300
296
  for (let j = l.index + 1; j < chainEnd; j++) {
301
- const w = toks.folded[j];
297
+ const w = toks[j].folded;
302
298
  if (!w.startsWith("-") || w === "-")
303
299
  break;
304
300
  const arity = row.optionArity.get(w);
@@ -311,24 +307,24 @@ function segmentFactsRead(segment, p, decl, nestedPlan) {
311
307
  for (let j = 0; j < chainEnd; j++) {
312
308
  if (launcherIndices.has(j) || controlIndices.has(j) || assignmentIndices.has(j) || launcherValueIndices.has(j) || j === written)
313
309
  continue;
314
- const word = toks.folded[j];
315
- const raw = toks.raw[j];
310
+ const r = toks[j];
311
+ const word = r.folded;
316
312
  if (word.startsWith("-") && word !== "-") {
317
313
  for (const payload of attachedOptionPayloads(word))
318
- reads.push(bare(raw, payload, "unlisted_word"));
314
+ reads.push(bare(r, "unlisted_word", payload));
319
315
  if (isPathShapedToken(word))
320
- reads.push(bare(raw, word, "unlisted_word"));
316
+ reads.push(bare(r, "unlisted_word"));
321
317
  continue;
322
318
  }
323
- if (expandsOf(raw, word) === "unresolvable") {
324
- reads.push({ spelled: word, raw, provenance: "unlisted_word", expands: "unresolvable" });
319
+ if (r.expands === "unresolvable") {
320
+ reads.push(bare(r, "unlisted_word"));
325
321
  continue;
326
322
  }
327
- if (assignmentWordPayload(word, toks.raw[j]) !== undefined) {
328
- pushAssignment(reads, word, toks.raw[j], "env_assignment", envOpens);
323
+ if (r.word.assignment !== undefined) {
324
+ pushAssignment(reads, r.word, "env_assignment", envOpens);
329
325
  continue;
330
326
  }
331
- reads.push(bare(raw, word, "unlisted_word"));
327
+ reads.push(bare(r, "unlisted_word"));
332
328
  }
333
329
  }
334
330
  let reentry;
@@ -349,7 +345,7 @@ function segmentFactsRead(segment, p, decl, nestedPlan) {
349
345
  if (programPositionRunsShell(pos, SHELL_REENTRY_PROGRAM_RE)) {
350
346
  reentry = "shell";
351
347
  const first = pos.launchers[0]?.index ?? written;
352
- const text = toks.folded.slice(first + 1).filter((w) => !w.startsWith("-") || w === "-").join(" ");
348
+ const text = toks.slice(first + 1).map((r) => r.folded).filter((w) => !w.startsWith("-") || w === "-").join(" ");
353
349
  if (text.length > 0)
354
350
  nested.push(nestedPlan(text));
355
351
  return { ...base, empty: false, launcherChain, ...(verb !== undefined ? { verb } : {}), reentry, declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}), loopEnd: false };
@@ -363,27 +359,27 @@ function segmentFactsRead(segment, p, decl, nestedPlan) {
363
359
  unlistedWords(reads, toks, pos.launchers[0].index, launcherIndices, "unlisted_word");
364
360
  return { ...base, empty: false, launcherChain, ...(reentry !== undefined ? { reentry } : {}), declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}), loopEnd: false };
365
361
  }
366
- const args = toks.folded.slice(program.index + 1);
367
- const rawArgs = toks.raw.slice(program.index + 1);
368
- const programWordUnread = program.name.includes(SUBSTITUTION_PLACEHOLDER) || hasUnquotedExpansionMetachar(toks.raw[program.index]);
369
- if (programWordUnread)
370
- reads.push(bare(toks.raw[program.index], program.name, "program_path"));
371
- else if (isPathShapedToken(program.name))
372
- reads.push(bare(toks.raw[program.index], program.name, "program_path"));
362
+ const argReads = toks.slice(program.index + 1);
363
+ const args = argReads.map((r) => r.folded);
364
+ const texts = argReads.map((r) => (r.word.pieces.every((q) => q.literal !== undefined) ? r.word.pieces.map((q) => q.literal).join("") : r.folded));
365
+ const programRead = toks[program.index];
366
+ if (rewritten(programRead) || isPathShapedToken(programRead.folded))
367
+ reads.push(bare(programRead, "program_path"));
373
368
  if (SHELL_REENTRY_PROGRAM_RE.test(basename)) {
374
369
  reentry = "shell";
375
- const text = reentryText(basename, args);
376
- if (text !== undefined)
377
- nested.push(nestedPlan(text));
370
+ const text = reentryText(basename, texts);
371
+ for (const t of new Set([text, reentryText(basename, args)]))
372
+ if (t !== undefined)
373
+ nested.push(nestedPlan(t));
378
374
  if (basename === "source" || basename === ".") {
379
- const file = args.find((a) => !a.startsWith("-") || a === "-");
375
+ const file = argReads.find((a) => !a.folded.startsWith("-") || a.folded === "-");
380
376
  if (file !== undefined)
381
- reads.push(bare(rawArgs[args.indexOf(file)], file, "operand"));
377
+ reads.push(bare(file, "operand"));
382
378
  }
383
379
  else if (text === undefined) {
384
- const file = args.find((a) => !a.startsWith("-") || a === "-");
385
- if (file !== undefined && file !== "-")
386
- reads.push(bare(rawArgs[args.indexOf(file)], file, "operand"));
380
+ const file = argReads.find((a) => !a.folded.startsWith("-") || a.folded === "-");
381
+ if (file !== undefined && file.folded !== "-")
382
+ reads.push(bare(file, "operand"));
387
383
  }
388
384
  return { ...base, empty: false, launcherChain, verb: verb, reentry, declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}), loopEnd: false };
389
385
  }
@@ -393,14 +389,14 @@ function segmentFactsRead(segment, p, decl, nestedPlan) {
393
389
  const moveProvable = pos.controlWords.length === 0 && pos.launchers.length === 0 && pos.assignments.length === 0 && program.name === basename;
394
390
  let baseMove;
395
391
  if (basename === "cd") {
396
- baseMove = cdMove(args, rawArgs, reads);
392
+ baseMove = cdMove(argReads, reads);
397
393
  }
398
394
  else if (UNMODELLED_BASE_MOVERS.has(basename)) {
399
395
  baseMove = { target: "unmodelled" };
400
396
  unlistedWords(reads, toks, program.index, launcherIndices, "unlisted_word", program);
401
397
  }
402
398
  else if (listed) {
403
- listedVerbCandidates(basename, args, rawArgs, reads);
399
+ listedVerbCandidates(basename, argReads, reads);
404
400
  }
405
401
  else {
406
402
  unlistedWords(reads, toks, program.index, launcherIndices, "unlisted_word", program);
@@ -409,15 +405,20 @@ function segmentFactsRead(segment, p, decl, nestedPlan) {
409
405
  if (basename === "sed") {
410
406
  const slot = sedScriptSlotFiles(args);
411
407
  for (const f of slot.reads)
412
- reads.push(bare(f, f, "script_slot_file"));
408
+ reads.push(bare(readOfText(f), "script_slot_file"));
413
409
  for (const f of slot.writes)
414
- reads.push(bare(f, f, "write_redirect_target"));
410
+ reads.push(bare(readOfText(f), "write_redirect_target"));
415
411
  for (const c of slot.commands)
416
412
  nested.push(nestedPlan(c));
417
413
  if (slot.commands.length > 0 || slot.executesPatternSpace)
418
414
  reentry = "shell";
419
415
  unreadProgramText = slot.unread;
420
416
  }
417
+ const bodies = new Set([...codeBodies(basename, texts), ...codeBodies(basename, args)]);
418
+ if (bodies.size > 0)
419
+ reentry = "shell";
420
+ for (const body of bodies)
421
+ nested.push(nestedPlan(body));
421
422
  return {
422
423
  ...base,
423
424
  empty: false,
@@ -477,15 +478,8 @@ function dedupe(reads) {
477
478
  }
478
479
  return out;
479
480
  }
480
- function bare(raw, folded, provenance) {
481
- return { spelled: folded, raw, provenance, expands: expandsOf(raw, folded) };
482
- }
483
- function expandsOf(raw, folded) {
484
- if (folded.includes(SUBSTITUTION_PLACEHOLDER) || hasUnquotedExpansionMetachar(raw) || /[`(]/.test(raw) || raw.includes(")"))
485
- return "unresolvable";
486
- if (hasUnquotedGlobMetachar(raw))
487
- return "glob";
488
- return false;
481
+ function bare(r, provenance, spelled = r.folded) {
482
+ return { spelled, raw: r.raw, provenance, expands: r.expands };
489
483
  }
490
484
  function relativeSpelling(spelled) {
491
485
  return !(isAbsolutePathForm(spelled) || spelled.startsWith("~"));
@@ -493,54 +487,67 @@ function relativeSpelling(spelled) {
493
487
  function joinBase(base, target, homeDir) {
494
488
  return resolveOperandLexically(base, target, homeDir);
495
489
  }
496
- function redirectFact(r) {
490
+ function redirectFact(r, home) {
497
491
  if (r.target === undefined)
498
492
  return { op: r.op, direction: "dup" };
499
493
  if (r.op === "<")
500
- return { op: r.op, direction: "in", target: bare(r.target.raw, r.target.folded, "stdin_redirect") };
494
+ return { op: r.op, direction: "in", target: bare(readsOf([r.target], home)[0], "stdin_redirect") };
501
495
  if (r.op === "<<<")
502
496
  return { op: r.op, direction: "in" };
503
- return { op: r.op, direction: "out", target: bare(r.target.raw, r.target.folded, "write_redirect_target") };
497
+ return { op: r.op, direction: "out", target: bare(readsOf([r.target], home)[0], "write_redirect_target") };
504
498
  }
505
- function pushAssignment(reads, folded, raw, provenance, opensValue = true) {
506
- const payload = assignmentWordPayload(folded, raw);
507
- const prefix = writtenAssignmentPrefix(folded)?.prefix;
508
- if (payload === undefined || prefix === undefined)
499
+ function pushAssignment(reads, w, provenance, opensValue = true) {
500
+ const a = w.assignment;
501
+ if (a === undefined)
502
+ return;
503
+ const expandsTilde = a.value.expansions.some((p) => p.expansion?.kind === "tilde");
504
+ const value = expandsTilde ? a.value.folded : literalTildeSpelling(a.value.folded);
505
+ const settled = a.op === "=" && !expandsTilde;
506
+ const named = a.value.folded === "" || (a.op === "+=" ? expandsTilde : !settled) ? undefined : value;
507
+ if (a.value.folded === "" && settled)
509
508
  return;
510
- const v = { spelled: folded.slice(prefix.length), raw: raw.startsWith(prefix) ? raw.slice(prefix.length) : raw, provenance };
511
- const unsettled = provenance !== "env_assignment" ? { ...v, expands: "unresolvable" } : v.spelled.startsWith("~") && !v.spelled.includes(":") ? bare(v.raw, v.spelled, provenance) : undefined;
512
- if (payload.kind === "unresolved" && opensValue && unsettled !== undefined)
509
+ const v = { spelled: a.value.folded, raw: a.bare ? a.value.raw : w.raw, provenance };
510
+ const unsettled = provenance !== "env_assignment" ? { ...v, expands: "unresolvable" } : v.spelled.startsWith("~") && !v.spelled.includes(":") ? { ...v, expands: expandsOf(a.value) } : undefined;
511
+ if (!settled && opensValue && unsettled !== undefined)
513
512
  reads.push(unsettled);
514
- if (payload.value !== undefined && payload.value !== SUBSTITUTION_PLACEHOLDER)
515
- reads.push(bare(raw, payload.value, provenance));
513
+ const substitutes = a.value.expansions.some((p) => p.expansion?.kind === "command" || p.expansion?.kind === "process" || p.expansion?.kind === "arithmetic");
514
+ if (named !== undefined && !substitutes)
515
+ reads.push({ spelled: named, raw: w.raw, provenance, expands: expandsOf(w) });
516
+ if (a.form === "compound")
517
+ for (const e of compoundArrayElements(a.value))
518
+ reads.push({ spelled: e.folded, raw: e.raw, provenance, expands: e.expands });
516
519
  }
517
520
  function unlistedWords(reads, toks, from, launcherIndices, provenance, program) {
518
- const argv = program !== undefined ? { program: program.basename, from: program.index } : argvProgramOf(toks);
519
- const argvStart = argv?.from ?? toks.folded.length;
521
+ const argv = program !== undefined ? { program: program.basename, from: program.index } : argvProgramOf(toks.map((r) => r.word));
522
+ const argvStart = argv?.from ?? toks.length;
520
523
  const slotVerb = argv?.program;
521
524
  const declaredNotPaths = ASSIGNMENT_VALUE_NOT_PATHS.has(slotVerb ?? "");
522
525
  const noPathOperands = slotVerb !== undefined && NO_PATH_OPERAND_COMMANDS.has(slotVerb);
523
526
  const permutes = slotVerb === undefined || !verbOptionsArePositionIndependent(slotVerb);
524
527
  const expanding = new Set();
525
- for (let j = from; j < toks.folded.length; j++) {
528
+ for (let j = from; j < toks.length; j++) {
526
529
  if (launcherIndices.has(j))
527
530
  continue;
528
531
  if (noPathOperands && j > argvStart)
529
532
  break;
530
- if (expandsOf(toks.raw[j], toks.folded[j]) !== "unresolvable")
533
+ if (toks[j].expands !== "unresolvable")
531
534
  continue;
532
535
  expanding.add(j);
533
- reads.push({ spelled: toks.folded[j], raw: toks.raw[j], provenance: j === argvStart ? "program_path" : provenance, expands: "unresolvable" });
536
+ reads.push(bare(toks[j], j === argvStart ? "program_path" : provenance));
537
+ if (j < argvStart)
538
+ pushAssignment(reads, toks[j].word, "env_assignment", slotVerb !== undefined && !noPathOperands);
539
+ else if (j > argvStart && !declaredNotPaths)
540
+ pushAssignment(reads, toks[j].word, provenance);
534
541
  }
535
542
  const positional = [];
536
543
  let stdinOperand = false;
537
544
  let endOfOptions = false;
538
545
  let sawArgvOperand = false;
539
- for (let j = from; j < toks.folded.length; j++) {
546
+ for (let j = from; j < toks.length; j++) {
540
547
  if (launcherIndices.has(j) || expanding.has(j))
541
548
  continue;
542
- const word = toks.folded[j];
543
- const raw = toks.raw[j];
549
+ const r = toks[j];
550
+ const word = r.folded;
544
551
  if (j > from && !endOfOptions && word === "--") {
545
552
  endOfOptions = true;
546
553
  continue;
@@ -553,43 +560,43 @@ function unlistedWords(reads, toks, from, launcherIndices, provenance, program)
553
560
  if (optionShaped && j > argvStart) {
554
561
  const slot = slotVerb === undefined ? undefined : readVerbOptionSlot(slotVerb, word);
555
562
  if (sawArgvOperand && permutes) {
556
- reads.push(bare(raw, word, provenance));
563
+ reads.push(bare(r, provenance));
557
564
  if (slot !== undefined && slot.form === "separated") {
558
- const ahead = toks.folded[j + 1];
559
- if (ahead !== undefined && expandsOf(toks.raw[j + 1], ahead) !== "unresolvable")
560
- for (const c of slotValueCandidates(ahead, slot.kind))
561
- reads.push(bare(toks.raw[j + 1], c, "option_value"));
565
+ const ahead = toks[j + 1];
566
+ if (ahead !== undefined && ahead.expands !== "unresolvable")
567
+ for (const c of slotValueCandidates(ahead.folded, slot.kind))
568
+ reads.push(bare(ahead, "option_value", c));
562
569
  }
563
570
  }
564
571
  if (slot !== undefined && slot.form !== "unexplained" && !(slot.form === "separated" && sawArgvOperand && permutes)) {
565
572
  if (slot.form === "attached") {
566
573
  for (const c of slotValueCandidates(literalTildeSpelling(slot.value), slot.kind))
567
- reads.push(bare(raw, c, "option_value"));
574
+ reads.push(bare(r, "option_value", c));
568
575
  }
569
576
  else if (slot.form === "separated") {
570
577
  j++;
571
- const value = toks.folded[j];
578
+ const value = toks[j];
572
579
  if (value !== undefined && !expanding.has(j)) {
573
- const named = slotValueCandidates(value, slot.kind);
580
+ const named = slotValueCandidates(value.folded, slot.kind);
574
581
  for (const c of named)
575
- reads.push(bare(toks.raw[j], c, "option_value"));
582
+ reads.push(bare(value, "option_value", c));
576
583
  if (named.length > 0)
577
- positional.push({ word: value, raw: toks.raw[j] });
584
+ positional.push(value);
578
585
  }
579
586
  }
580
587
  continue;
581
588
  }
582
589
  for (const payload of attachedOptionPayloads(word))
583
- reads.push(bare(raw, payload, provenance));
590
+ reads.push(bare(r, provenance, payload));
584
591
  if (isPathShapedToken(word))
585
- reads.push(bare(raw, word, provenance));
592
+ reads.push(bare(r, provenance));
586
593
  continue;
587
594
  }
588
595
  if (optionShaped) {
589
596
  for (const payload of attachedOptionPayloads(word))
590
- reads.push(bare(raw, payload, provenance));
597
+ reads.push(bare(r, provenance, payload));
591
598
  if (isPathShapedToken(word))
592
- reads.push(bare(raw, word, provenance));
599
+ reads.push(bare(r, provenance));
593
600
  continue;
594
601
  }
595
602
  if (j > argvStart) {
@@ -599,39 +606,38 @@ function unlistedWords(reads, toks, from, launcherIndices, provenance, program)
599
606
  if (m === undefined || m.dashIsStdin === true)
600
607
  stdinOperand = true;
601
608
  else
602
- positional.push({ word, raw });
609
+ positional.push(r);
603
610
  }
604
611
  else
605
- positional.push({ word, raw });
612
+ positional.push(r);
606
613
  }
607
- reads.push(bare(raw, word, j === argvStart ? "program_path" : provenance));
614
+ reads.push(bare(r, j === argvStart ? "program_path" : provenance));
608
615
  if (j < argvStart)
609
- pushAssignment(reads, word, raw, "env_assignment", slotVerb !== undefined && !noPathOperands);
616
+ pushAssignment(reads, r.word, "env_assignment", slotVerb !== undefined && !noPathOperands);
610
617
  else if (!declaredNotPaths)
611
- pushAssignment(reads, word, raw, provenance);
618
+ pushAssignment(reads, r.word, provenance);
612
619
  }
613
- if (slotVerb !== undefined && argv !== undefined && segmentSelectsRecursiveRead(slotVerb, toks.folded.slice(argvStart + 1))) {
614
- const roots = positional.length > 0 ? positional : stdinOperand ? [] : [{ word: ".", raw: "." }];
620
+ if (slotVerb !== undefined && argv !== undefined && segmentSelectsRecursiveRead(slotVerb, toks.slice(argvStart + 1).map((x) => x.folded))) {
621
+ const roots = positional.length > 0 ? positional : stdinOperand ? [] : [readOfText(".")];
615
622
  for (const root of roots)
616
- reads.push({ spelled: root.word, raw: root.raw, provenance: "traversal_root", expands: expandsOf(root.raw, root.word) });
623
+ reads.push(bare(root, "traversal_root"));
617
624
  }
618
- for (let j = from; j < toks.folded.length; j++) {
625
+ for (let j = from; j < toks.length; j++) {
619
626
  if (launcherIndices.has(j) || j === argvStart)
620
627
  continue;
621
628
  if (noPathOperands && j > argvStart)
622
629
  break;
623
- const raw = toks.raw[j];
624
- if (expandsOf(raw, toks.folded[j]) === "glob")
625
- reads.push(bare(raw, toks.folded[j], provenance));
630
+ if (toks[j].expands === "glob")
631
+ reads.push(bare(toks[j], provenance));
626
632
  }
627
633
  }
628
634
  function commandStringValue(options, toks, option) {
629
- const at = toks.folded.indexOf(option);
635
+ const at = toks.findIndex((r) => r.folded === option);
630
636
  if (at < 0)
631
637
  return undefined;
632
638
  for (const spelling of options) {
633
639
  if (option === spelling) {
634
- const value = toks.folded[at + 1];
640
+ const value = toks[at + 1]?.folded;
635
641
  return value === undefined ? undefined : { value, next: at + 2 };
636
642
  }
637
643
  if (spelling.startsWith("--") && option.startsWith(`${spelling}=`))
@@ -641,68 +647,46 @@ function commandStringValue(options, toks, option) {
641
647
  }
642
648
  return undefined;
643
649
  }
644
- function reentryText(basename, args) {
645
- if (basename === "eval")
646
- return args.length === 0 ? undefined : args.join(" ");
647
- if (basename === "parallel")
648
- return args.filter((a) => !a.startsWith("-")).join(" ") || undefined;
649
- if (basename === "source" || basename === ".")
650
- return undefined;
651
- for (let k = 0; k < args.length; k++) {
652
- const a = args[k];
653
- if (a === "--")
654
- return undefined;
655
- if (!a.startsWith("-") || a === "-")
656
- return undefined;
657
- const c = a.indexOf("c");
658
- if (a.startsWith("--"))
659
- continue;
660
- if (c > 0)
661
- return a.length > c + 1 ? a.slice(c + 1) : args[k + 1];
662
- }
663
- return undefined;
664
- }
665
650
  function literalBeats(toks) {
666
- const at = toks.folded.indexOf("in");
651
+ const at = toks.findIndex((r) => r.folded === "in");
667
652
  if (at < 0)
668
653
  return undefined;
669
- const words = toks.folded.slice(at + 1);
670
- const raws = toks.raw.slice(at + 1);
654
+ const words = toks.slice(at + 1);
671
655
  if (words.length === 0)
672
656
  return undefined;
673
- if (words.length === 1 && words[0].includes(SUBSTITUTION_PLACEHOLDER)) {
674
- const m = /^\$\(\s*seq\s+(\d+)\s+(\d+)\s*\)$/.exec(raws[0]);
657
+ const only = words.length === 1 && words[0].word.pieces.length === 1 ? words[0].word.pieces[0].expansion : undefined;
658
+ if (only !== undefined) {
659
+ const m = only.kind === "command" ? /^\s*seq\s+(\d+)\s+(\d+)\s*$/.exec(only.body ?? "") : null;
675
660
  if (m === null)
676
661
  return undefined;
677
662
  const beats = Number(m[2]) - Number(m[1]) + 1;
678
663
  return beats >= 1 && beats <= POLL_LOOP_MAX_BEATS ? beats : undefined;
679
664
  }
680
- for (let k = 0; k < words.length; k++) {
681
- if (expandsOf(raws[k], words[k]) !== false)
665
+ for (const w of words)
666
+ if (w.expands !== false)
682
667
  return undefined;
683
- }
684
668
  return words.length <= POLL_LOOP_MAX_BEATS ? words.length : undefined;
685
669
  }
686
- function cdMove(args, rawArgs, reads) {
687
- const idx = args.findIndex((t) => !t.startsWith("-") || t === "-");
688
- if (idx < 0)
670
+ function cdMove(args, reads) {
671
+ const target = args.find((t) => !t.folded.startsWith("-") || t.folded === "-");
672
+ if (target === undefined)
689
673
  return { target: "home" };
690
- const target = args[idx];
691
- if (target === "-")
674
+ if (target.folded === "-")
692
675
  return { target: "previous" };
693
- const c = bare(rawArgs[idx], target, "cd_target");
676
+ const c = bare(target, "cd_target");
694
677
  reads.push(c);
695
678
  return { target: c };
696
679
  }
697
- function listedVerbCandidates(name, args, rawArgs, reads) {
680
+ function listedVerbCandidates(name, argReads, reads) {
698
681
  if (NO_PATH_OPERAND_COMMANDS.has(name))
699
682
  return;
683
+ const args = argReads.map((r) => r.folded);
700
684
  const expanding = new Set();
701
- rawArgs.forEach((raw, k) => {
702
- if (!hasUnquotedExpansionMetachar(raw))
685
+ argReads.forEach((r, k) => {
686
+ if (!rewritten(r))
703
687
  return;
704
688
  expanding.add(k);
705
- reads.push({ spelled: args[k], raw, provenance: "operand", expands: "unresolvable" });
689
+ reads.push({ spelled: r.folded, raw: r.raw, provenance: "operand", expands: "unresolvable" });
706
690
  });
707
691
  const eoo = args.indexOf("--");
708
692
  const firstOperandSlot = verbFirstOperandSlot(name);
@@ -713,7 +697,7 @@ function listedVerbCandidates(name, args, rawArgs, reads) {
713
697
  let endOfOptions = false;
714
698
  const permutes = !verbOptionsArePositionIndependent(name);
715
699
  const push = (k, text, provenance) => {
716
- reads.push(bare(rawArgs[k], text, provenance));
700
+ reads.push(bare(argReads[k], provenance, text));
717
701
  };
718
702
  for (let k = 0; k < args.length; k++) {
719
703
  const t = args[k];
@@ -747,7 +731,7 @@ function listedVerbCandidates(name, args, rawArgs, reads) {
747
731
  for (const c of named)
748
732
  push(k, c, "option_value");
749
733
  if (named.length > 0)
750
- positional.push({ word: value, raw: rawArgs[k] });
734
+ positional.push(argReads[k]);
751
735
  }
752
736
  }
753
737
  continue;
@@ -767,7 +751,7 @@ function listedVerbCandidates(name, args, rawArgs, reads) {
767
751
  stdinOperand = true;
768
752
  continue;
769
753
  }
770
- positional.push({ word: t, raw: rawArgs[k] });
754
+ positional.push(argReads[k]);
771
755
  continue;
772
756
  }
773
757
  if (inFirstOperandSlot) {
@@ -775,24 +759,24 @@ function listedVerbCandidates(name, args, rawArgs, reads) {
775
759
  for (const c of named)
776
760
  push(k, c, "operand");
777
761
  if (named.length > 0)
778
- positional.push({ word: t, raw: rawArgs[k] });
762
+ positional.push(argReads[k]);
779
763
  continue;
780
764
  }
781
- positional.push({ word: t, raw: rawArgs[k] });
765
+ positional.push(argReads[k]);
782
766
  push(k, t, "operand");
783
767
  if (!ASSIGNMENT_VALUE_NOT_PATHS.has(name)) {
784
- pushAssignment(reads, t, rawArgs[k], "operand");
768
+ pushAssignment(reads, argReads[k].word, "operand");
785
769
  }
786
770
  }
787
771
  for (let k = 0; k < args.length; k++) {
788
772
  if (expanding.has(k))
789
773
  continue;
790
- if (expandsOf(rawArgs[k], args[k]) === "glob")
791
- reads.push(bare(rawArgs[k], args[k], "operand"));
774
+ if (argReads[k].expands === "glob")
775
+ reads.push(bare(argReads[k], "operand"));
792
776
  }
793
777
  if (segmentSelectsRecursiveRead(name, args)) {
794
- const roots = positional.length > 0 ? positional : stdinOperand ? [] : [{ word: ".", raw: "." }];
778
+ const roots = positional.length > 0 ? positional : stdinOperand ? [] : [readOfText(".")];
795
779
  for (const root of roots)
796
- reads.push({ spelled: root.word, raw: root.raw, provenance: "traversal_root", expands: expandsOf(root.raw, root.word) });
780
+ reads.push(bare(root, "traversal_root"));
797
781
  }
798
782
  }