@sema-agent/core 7.29.0 → 7.30.1

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