@sema-agent/core 7.24.2 → 7.24.3

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.
@@ -1,4 +1,4 @@
1
- import { type ShellSegmentConnector } from "./bash-lexer.js";
1
+ import { type SegmentTokens, type ShellSegmentConnector } from "./bash-lexer.js";
2
2
  import { type ShellScanDecline } from "./bash-program-position.js";
3
3
  /**
4
4
  * Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
@@ -480,6 +480,30 @@ export declare function formatOutOfRootReadApprovalOption(directory: string): st
480
480
  * the compound grammar refuses first, so a command-level pin cannot tell a live guard from an absent one.
481
481
  */
482
482
  export declare function deepestCommonDir(a: string, b: string): string | undefined;
483
+ /** Programs whose operands are NEVER filesystem paths, so no operand of theirs is checked:
484
+ * `echo`'s words are literal text, `which`'s are PATH lookups, `tr`'s are character sets, and
485
+ * `basename`/`dirname` do pure string surgery on a path without ever opening it. Everything else —
486
+ * including a deployment-widened name this file does not know — goes through the path-shaped
487
+ * operand scan below, which is safe for an unknown command because a bare word can only ever name
488
+ * something under the working directory. */
489
+ export declare const NO_PATH_OPERAND_COMMANDS: ReadonlySet<string>;
490
+ /** True for a token that can denote a filesystem location OUTSIDE the working directory. A bare word
491
+ * (`foo`, `a.txt`, a grep pattern, an awk program) cannot: with no separator and no `..` it always
492
+ * resolves under the cwd, which the caller's cwd governance already covers.
493
+ *
494
+ * The one spelling that would break that argument is a win-form absolute written with
495
+ * backslashes only (`\\host\share\secret`, `C:\other\secret`): no `/`, so not path-shaped, yet it names
496
+ * something the cwd does not contain. Two separate facts, once written as one:
497
+ * · on the COMPOUND faces it is indeed unreachable — {@link SHELL_SEGMENT_QUOTE_BLIND_REJECT} refuses the
498
+ * whole command as soon as it contains a backslash, wherever it stands, so no such token reaches this scan;
499
+ * · on {@link classifySimpleCommandReadBoundary} — one command, no compound grammar, and therefore no
500
+ * such gate — it DOES reach it. Measured on a win32-rooted boundary, before the lane split below: the operand
501
+ * resolved to nothing, was dropped, and the verdict was empty, i.e. a read outside every root
502
+ * AUTO-ALLOWED on the one face whose doctrine is hard refusal in exactly that direction.
503
+ * The second fact is why the lane split in
504
+ * {@link collectSegmentBoundaryFindings} tests {@link isAbsolutePathForm} BESIDE this predicate,
505
+ * which is what the option-payload arm always did; the two now read one rule rather than two. */
506
+ export declare function isPathShapedToken(tok: string): boolean;
483
507
  /** Resolve one operand against the boundary's base, WITHOUT touching the filesystem. Returns undefined
484
508
  * when the token cannot be resolved statically — the fail-closed direction, reported as its own
485
509
  * demotion rather than silently treated as in-root.
@@ -519,6 +543,13 @@ export interface OperandSpellings {
519
543
  /** Resolve one operand into {@link OperandSpellings}, or `undefined` on the same unresolvable operands
520
544
  * {@link resolveOperandLexically} reports (it is this function's folded half). */
521
545
  export declare function resolveOperandSpellings(base: string | undefined, operand: string, homeDir: string | undefined): OperandSpellings | undefined;
546
+ /** Programs that run their argument AS SHELL TEXT (the sh family, `eval`, `source`, `.`): what they read is decided
547
+ * by text nobody here parsed. Spelled as a pattern, not a name list — these are program names, never tool names.
548
+ * The prefix alternation is the SPELLINGS of the shell, not a curated subset of them: the Almquist shell and
549
+ * its descendants (`ash`, `mksh`, `yash`, `pdksh`) and the C shell family (`csh`, `tcsh`) all run `-c` text
550
+ * exactly as `sh` does, and a deployment that meets one of them meets the whole member. GNU `parallel` is a member
551
+ * for the reason it is not a launcher: it hands a BUILT command line to a shell. */
552
+ export declare const SHELL_REENTRY_PROGRAM_RE: RegExp;
522
553
  /** Whether a command the grammar gate REFUSED WHOLE may READ a file the boundary never judged. The answer is the
523
554
  * complement of a WHITELIST: the command is exempt only when every part of it is a shape the scan fully understands
524
555
  * and none of those shapes reads a file outside the walk's reach — everything else is read evidence, so a token the
@@ -540,6 +571,446 @@ export interface RefusedCommandScan {
540
571
  readonly mayRead: boolean;
541
572
  readonly declined: boolean;
542
573
  }
574
+ /**
575
+ * Does this RAW token carry a metacharacter the shell EXPANDS, unquoted?
576
+ *
577
+ * The boundary scan below judges the token text as if it were the argv word the program receives. Quote
578
+ * removal (folded above) is the only rewriting step it models — but brace expansion runs BEFORE tilde
579
+ * expansion and before the word ever reaches the program, so `cat {<abs>,}` hands the program an absolute
580
+ * path this scan never saw, and `ls {~,}` hands it a home directory the `~` arm of
581
+ * {@link resolveOperandLexically} never got the chance to refuse. Both were real out-of-root reads.
582
+ *
583
+ * Rather than emulate expansion (the classifier resolves paths lexically and has no filesystem, so it
584
+ * cannot know what a brace list or a glob expands TO), an operand carrying one of these unquoted is
585
+ * declared unresolvable — the same verdict `~user` already gets, and the same direction the whole file
586
+ * errs in. Quoting is honoured exactly as bash does it: nothing expands inside single quotes, while `$`
587
+ * and a backtick still expand inside double quotes (braces do not).
588
+ *
589
+ * Deliberately NOT in the set: the GLOB characters `* ? [ ]`. A glob expands only to names that already
590
+ * exist in the directory the pattern itself names, and a path component can never match a `/` — so a glob
591
+ * cannot manufacture a traversal that is not already textually present (`cat ../*` still carries `../`
592
+ * and is judged on it), while listing them would demote the everyday `ls *.txt`. `$`, backtick and the
593
+ * parentheses are redundant against a compliant caller (both faces reject them upstream, whole-string;
594
+ * a home-directory variable word never arrives as `$` — {@link foldHomeVariableSpelling} substituted the
595
+ * declared value before segmentation) and are kept here only so this scan is self-standing.
596
+ */
597
+ export declare function hasUnquotedExpansionMetachar(rawToken: string): boolean;
598
+ /**
599
+ * The same quote walk for PATHNAME expansion (`*`, `?`, `[`). Split from
600
+ * {@link hasUnquotedExpansionMetachar} because the two have different consequences, not different
601
+ * detection: a brace/variable/command expansion can produce ANY word (including a fresh absolute path or
602
+ * an extra operand), so it is unresolvable; a glob only ever names entries of a directory that is already
603
+ * on the command line, so a caller holding a filesystem can decide it — see
604
+ * {@link CompoundReadonlyVerdict.undecidedPaths}. Both quoting forms suppress globbing in bash, so unlike
605
+ * the expansion walk there is no `"`-open arm here.
606
+ */
607
+ export declare function hasUnquotedGlobMetachar(rawToken: string): boolean;
608
+ /**
609
+ * Does this token supply the verb's FIRST-OPERAND slot, so that the first operand is a FILE rather than the
610
+ * slot ({@link VerbOptionSlots.firstOperand})? One reader, every verb with such a row.
611
+ *
612
+ * Read over the whole leading letter RUN, not a prefix: GNU clusters short options (`-if` is `-i -f`) and a
613
+ * prefix reading left the slot to swallow — and therefore skip — a real file operand. Digits belong to the run
614
+ * (grep's obsolete numeric context spec: `-2ebeta` is `-C2 -e beta`), and a long option is matched as an
615
+ * ABBREVIATION of the declared name (`grep --reg=X <outside>` is `--regexp`). Over-triggering is harmless in
616
+ * the direction that matters: a token wrongly counted here makes the first operand a CHECKED path candidate.
617
+ */
618
+ export declare function suppliesFirstOperandSlot(slot: NonNullable<VerbOptionSlots["firstOperand"]>, tok: string): boolean;
619
+ /**
620
+ * WHAT ONE OPTION'S VALUE IS, per verb — the one declaration every reader of an option token shares.
621
+ *
622
+ * The question asked of a value is always "can this name a file?", and the answer is a property of the
623
+ * OPTION and its own grammar, never of the value's shape: `sed -e 's/a/b/'` and `sed -f s.sed` differ in
624
+ * nothing a shape test can see. The kinds spell that answer out per slot so a row is auditable letter by
625
+ * letter, and {@link slotValueCandidates} is where every consumer gets it — a kind that cannot name a
626
+ * file yields nothing there, which is the same answer as "this value named none".
627
+ */
628
+ export type OptionValueKind = "path" | "indirect" | "pattern" | "script" | "count" | "delimiter" | "name-pattern" | "form" | "header" | "url" | "label";
629
+ /**
630
+ * One verb's option grammar: which short letters and long names take a VALUE, and what that value is.
631
+ *
632
+ * FAIL-SAFE BY CONSTRUCTION, which is what makes a PARTIAL row sound: the reader must account for every
633
+ * option character it walks, so the first character a row does not declare ends the reading as
634
+ * `unexplained` and hands the token back to the verb-blind payload scan ({@link attachedOptionPayloads})
635
+ * — today's over-approximation, unchanged. A row therefore only ever makes an answer MORE precise where
636
+ * it recognizes the option; omitting one costs precision, never a judgement.
637
+ *
638
+ * THREE ARITIES, because `getopt_long` has three. An option that takes no value, one that requires a
639
+ * value (attached or the next token), and one whose value is OPTIONAL — and the third is not the second
640
+ * with a caveat: `getopt_long` gives an optional value NO separated spelling, so `diff --unified a b`
641
+ * hands diff two FILES while `diff --unified=3 a b` hands it a width. A row that files an optional-value
642
+ * option under {@link VerbOptionSlots.longValue} therefore eats `a`, and the file operand it swallowed
643
+ * is judged by nobody — the one direction this table may never move. They go in
644
+ * {@link VerbOptionSlots.longOptionalValue}, which reads the attached spelling and consumes nothing in
645
+ * the separated one. A short letter whose value is optional (`sed -i[SUFFIX]`) is left out of BOTH short
646
+ * maps for the same reason: an optional value can only ever be attached, and the blind scan is the safe
647
+ * reading of a token this row will not explain.
648
+ *
649
+ * WHAT A ROW MAY NOT GET WRONG, stated as the rule for adding one: declaring a FILE-valued option
650
+ * as something else hides a read, and so does declaring an OPTIONAL-value option as a required one —
651
+ * the value it eats in the separated spelling is a file operand. Declaring a value-taking option as
652
+ * plain is safe (the reading ends unexplained at its value), and declaring the non-file slots is what
653
+ * removes the phantom candidates a blind split invents — admitting one more option character moves the
654
+ * split right, and a split that lands on a `/` manufactures an absolute path the command never names.
655
+ *
656
+ * AND A ROW IS ABOUT A NAME, WHICH TWO IMPLEMENTATIONS MAY NOT SPELL ALIKE. `sed -l` takes a required
657
+ * line length in one widely deployed sed and NO value in another; `grep --context` is optional in one
658
+ * and required in another; and the engine runs whichever the host ships. A disputed arity is resolved
659
+ * towards the reading that CONSUMES NOTHING, because only consuming can swallow a file operand: a
660
+ * disputed LONG name is filed as optional-value (right under one reading, merely imprecise under the
661
+ * other), and a disputed SHORT letter is left out of both short maps, since the short maps have no way
662
+ * to say "attached only". Where implementations disagree about a value's KIND rather than its arity,
663
+ * the file-naming reading is the one to declare — over-approximating a value costs an ask and
664
+ * under-approximating one costs the read. Every row here is checked against the binaries this
665
+ * repository's own tools run on; a row added from a manual page alone is a guess.
666
+ *
667
+ * AND A ROW APPLIES WHILE THE COMMAND IS STILL IN ITS OPTION SECTION. POSIX ends option parsing at the
668
+ * first operand, GNU permutes, and a transfer program parses its own options anywhere — so once the
669
+ * operands have begun, whether a dash-leading token is an option at all is implementation-dependent and
670
+ * BOTH readings are taken: the token is not consumed (it is judged as the operand it may be) and the
671
+ * slot's candidates are judged too (it may name a file). Adding a candidate is always safe; consuming
672
+ * is what hides one.
673
+ *
674
+ * @contract shell.read_boundary.option_value_slots — where this table declares a verb's option, the
675
+ * read boundary reads that option's value from the DECLARED slot and nowhere else: a slot that can name
676
+ * a file yields exactly its value as a path candidate (an `indirect` slot also yields what the value
677
+ * names past its `@`, a `header` slot only for a whole-value `@<file>`, and a `url` slot the local path
678
+ * a `file:` URL names), and a slot that cannot yields none — the attached and separated spellings of one
679
+ * option read the same row and can never disagree, except where the row declares the value OPTIONAL,
680
+ * which has no separated spelling at all. Past the command's first operand the separated spelling
681
+ * consumes nothing and its value is judged BOTH as that value and as an operand, because which of the
682
+ * two it is, is implementation-dependent. Where it declares nothing — no row for the verb, an
683
+ * option character the row does not account for, a long-option prefix whose reachable names leave this
684
+ * spelling's value unsettled —
685
+ * the verb-blind payload scan judges the token exactly as it did before, so an absent or partial row
686
+ * costs precision and never a judgement. A row is DATA about a PROGRAM and not about the read-only
687
+ * allowlist: it says what that program does with its own argument, never that the program may be read
688
+ * from, and it grants no per-verb arm to a program a deployment has not listed.
689
+ */
690
+ export interface VerbOptionSlots {
691
+ /** Short letters whose value is the rest of the token, or the NEXT token when the letter ends it. */
692
+ readonly shortValue: Readonly<Record<string, OptionValueKind>>;
693
+ /** Short letters taking no value. */
694
+ readonly shortPlain: string;
695
+ /** Long option FULL names whose value is `=<value>` or the next token. */
696
+ readonly longValue: Readonly<Record<string, OptionValueKind>>;
697
+ /** Long option FULL names whose value exists ONLY as `=<value>`: `getopt_long`'s `optional_argument`,
698
+ * which never reaches for the next token. Reading one of these as {@link longValue} would consume the
699
+ * operand that follows the bare spelling, so the operand goes unjudged — see the THREE ARITIES note. */
700
+ readonly longOptionalValue: Readonly<Record<string, OptionValueKind>>;
701
+ /** Long option FULL names taking no value — declared so that an abbreviation reaching one is seen to be
702
+ * ambiguous rather than read as its value-taking neighbour. */
703
+ readonly longPlain: readonly string[];
704
+ /** SINGLE-dash WORD options — a grammar getopt does not have and a cluster walk cannot read: `-name` is not
705
+ * `-n -a -m -e`, and splitting it that way manufactures a path candidate per suffix. Asked before the cluster
706
+ * walk, only for a dash plus MORE THAN ONE character; a name in neither map falls to the verb-blind scan. */
707
+ readonly dashWordValue?: Readonly<Record<string, OptionValueKind>>;
708
+ /** The value-less half of {@link dashWordValue}. */
709
+ readonly dashWordPlain?: readonly string[];
710
+ /** The FIRST positional operand, where this verb's grammar gives it a meaning that is NOT a file: grep's
711
+ * PATTERN, sed's SCRIPT. Read with the same {@link OptionValueKind} vocabulary the option slots use, so
712
+ * {@link slotValueCandidates} answers "can this word name a file" once for both spellings — `sed -e '/x/p' f`
713
+ * and `sed '/x/p' f` are one invocation and were two readings. `suppliedBy*` name the options that deliver
714
+ * the slot instead, leaving the first operand a file after all. No row ⇒ an ordinary first operand. */
715
+ readonly firstOperand?: {
716
+ readonly kind: OptionValueKind;
717
+ /** Short letters, matched inside a cluster and with digits allowed in front (grep's obsolete numeric
718
+ * context spec: `-2ebeta` is `-C2 -e beta`). */
719
+ readonly suppliedByLetters: string;
720
+ /** Long option FULL names; an abbreviation reaching one counts ({@link isLongOptionAbbrevOf}). */
721
+ readonly suppliedByLongNames: readonly string[];
722
+ };
723
+ /** This program parses its options WHEREVER they appear, so the permute ambiguity the both-readings rule
724
+ * below answers (POSIX/GNU/BSD disagree whether a dash-token past the first operand is still an option) does
725
+ * not exist for it. Declared only where the program's manual states the grammar, and never to rescue a
726
+ * spelling into auto-allow: a token the row does not explain still goes to the verb-blind scan. */
727
+ readonly optionsArePositionIndependent?: true;
728
+ }
729
+ /** What one row makes of ONE option token. `unexplained` = the row cannot account for the token, and the
730
+ * caller reads it the verb-blind way it always did. */
731
+ export type VerbOptionSlotReading = {
732
+ readonly form: "unexplained";
733
+ } | {
734
+ readonly form: "no-value";
735
+ } | {
736
+ readonly form: "attached";
737
+ readonly value: string;
738
+ readonly kind: OptionValueKind;
739
+ } | {
740
+ readonly form: "separated";
741
+ readonly kind: OptionValueKind;
742
+ };
743
+ /**
744
+ * Read one option token of `name` through its row. `undefined` ⇒ this verb has no row.
745
+ *
746
+ * Short form is getopt: scanning the cluster left to right, the FIRST value-taking letter owns the
747
+ * remainder of the token, or the next token when it ends there. Long form is `getopt_long`'s
748
+ * unambiguous-prefix rule with an EXACT name resolved first; a prefix reaching several names is settled
749
+ * only where they agree about what THIS spelling does with its value, which is one question for an
750
+ * attached `=value` (can it name a file?) and two for a separated one (does the option take the next
751
+ * token at all?) — see the branch below.
752
+ *
753
+ * An unsettled prefix falls back rather than guessing, because skipping a value that is really a FILE
754
+ * hides a read: the verb-blind scan that judges every suffix is the safe answer to "this row does not
755
+ * know". That direction is why this reading is NOT the stdin floor's ({@link SEPARATED_VALUE_OPTIONS}),
756
+ * which asks a different question — does this value fill one of the SLOTS the floor counts — and answers
757
+ * it in the other direction. Measured rather than argued: reading the floor off this table moved twelve
758
+ * of its verdicts, eight of them wrongly (`grep -e pat file` blocks on nothing, and its pattern value is
759
+ * exactly what makes grep's floor of two `<pattern> <file>`).
760
+ */
761
+ export declare function readVerbOptionSlot(name: string, tok: string): VerbOptionSlotReading | undefined;
762
+ /** Does this verb's row declare that its options are parsed WHEREVER they appear — i.e. that the permute
763
+ * ambiguity the both-readings rule answers does not exist for it? See
764
+ * {@link VerbOptionSlots.optionsArePositionIndependent}. */
765
+ export declare function verbOptionsArePositionIndependent(name: string): boolean;
766
+ /** The verb's first-operand slot row, for a reader outside this module that walks operands with the same tables. */
767
+ export declare function verbFirstOperandSlot(name: string): VerbOptionSlots["firstOperand"];
768
+ /**
769
+ * The path candidates one DECLARED value offers, which is also the answer to "did this value name a
770
+ * file?" — an empty list IS the no.
771
+ *
772
+ * Three readings, one per grammar the declared slots have: a value that always names a file offers
773
+ * itself; an `indirect` one may name a file either way, so it offers itself AND what it names past an
774
+ * `@`; and a `header` names a file ONLY as a whole-value `@<file>` — `curl -H 'X: /etc/x'` sends that
775
+ * text and opens nothing, while `curl -H @<file>` adds a header per line of the file (measured against
776
+ * the binary, both directions). Reading `header` the `indirect` way would refuse a header whose text
777
+ * merely looks like a path; reading it as a non-file kind hides the `@` spelling, which is the direction
778
+ * that may not be taken.
779
+ */
780
+ export declare function slotValueCandidates(value: string, kind: OptionValueKind): string[];
781
+ /**
782
+ * The per-command model of RECURSIVE/EXPANDING read forms: invocations that read the
783
+ * SUBTREE of a path operand, not the operand itself. Consumed by {@link collectSegmentBoundaryFindings}
784
+ * only when a {@link BashReadonlyRootBoundary.denyMatch} seat is wired and has entries to apply (see
785
+ * {@link CompoundReadonlyVerdict.recursiveReadPaths} for the contract and why the deny seat is the
786
+ * trigger), and then only for the rows whose {@link RecursiveReadFormModel.discloses} column reads
787
+ * "contents". The reference implementation draws the same line as a verb set: its recursive-reach breaker
788
+ * fires for `grep`/`egrep`/`fgrep`/`rg`/`diff` (plus two copy verbs a WRITE face answers for here) and for no
789
+ * name that merely lists a tree; `tar` is a contents row here and absent there, because that implementation
790
+ * ships no built-in deny table and this one does.
791
+ *
792
+ * Membership covers every {@link BASH_READONLY_DEFAULT_ALLOW} verb with a recursive form, PLUS the
793
+ * common tree-reading verbs a deployment plausibly widens its allowlist with (`find`, `rg`, …): the
794
+ * allow set is a caller parameter, and a table pinned to the default list would silently go blind the
795
+ * day a deployment adds `rg` — whose recursion needs no flag at all. A widened verb this table does
796
+ * not know stays name-only classified (recorded residual: the deployment owns that widening, same
797
+ * posture as the allowlist's own header note).
798
+ *
799
+ * DIRECTION. Detection over-approximates deliberately: a flag mis-read as recursive costs one ask on
800
+ * a command that is usually a real-getopt error anyway (`grep -dr …` — ambiguous enum value), while an
801
+ * under-read auto-allows a traversal the deny judge never saw — the leaking direction. The cluster
802
+ * walk therefore honours getopt value-owner semantics ONLY where skipping prevents a false fire on a
803
+ * value AND the ownership holds on every supported implementation (`grep -eRfoo` is a pattern under
804
+ * GNU and BSD alike), never to rescue a spelling into auto-allow — a letter whose ownership DIVERGES
805
+ * across implementations is not modeled at all (see the `ls` row), because a scan stopped on the
806
+ * wrong platform's table is an auto-allowed real traversal.
807
+ */
808
+ export interface RecursiveReadFormModel {
809
+ /** What the traversal puts in front of the CALLER: the file CONTENTS it walked over, or only their
810
+ * NAMES and sizes. The demotion arm reads "contents" rows only — the deny judge cannot pre-check a
811
+ * subtree, and handing back the bytes it guards is the hazard; handing back names is a different
812
+ * disclosure with a different answer. WHERE THE BYTES GO is part of the question and not always a
813
+ * property of the verb (`tar -cf -` writes the archive to stdout), and reading that per invocation is a
814
+ * per-program option model this zero-IO pass does not carry — so an ambiguous row resolves demote-ward,
815
+ * as every ambiguity here does. A "names" row's other fields have no behavioural consumer today; the
816
+ * row stays because it is a DECISION, and a verb deleted from this table reads as one nobody made. */
817
+ readonly discloses: "contents" | "names";
818
+ /** The verb's ordinary operation IS a tree walk — every invocation with a path operand recurses,
819
+ * no flag required (`du`, `find`, `rg`, `tree`, `ag`, `ack`). */
820
+ readonly always?: true;
821
+ /** Short option letters selecting recursion, judged over the getopt option-letter run. */
822
+ readonly shortLetters?: string;
823
+ /** Value-taking short letters: the FIRST one hit owns the token remainder (or, when it ends the
824
+ * token, the next token) — letters inside that value are not options and must not fire. List a
825
+ * letter ONLY when it takes a value on EVERY supported implementation (GNU and BSD): stopping the
826
+ * scan on one platform's table is an under-read on the other, the leaking direction. */
827
+ readonly valueOwners?: string;
828
+ /** Long option names selecting recursion, abbreviation-aware ({@link isLongOptionAbbrevOf} — an
829
+ * abbreviation real getopt_long would call ambiguous over-fires into an ask, the safe side). */
830
+ readonly longNames?: readonly string[];
831
+ /** Enum-valued options where one VALUE selects recursion (grep's `-d recurse` /
832
+ * `--directories=recurse`). The value is matched as a CASE-FOLDED unambiguous-prefix
833
+ * over-approximation: GNU argmatch accepts prefixes, and BSD grep accepts the value
834
+ * case-insensitively (`-d RECURSE` recurses — verified on the real binary), so
835
+ * both laxities fold in. A spelling only one implementation accepts over-fires on the other
836
+ * into an ask on an erroring command — the required side. */
837
+ readonly enumOptions?: readonly {
838
+ shortLetter?: string;
839
+ longName?: string;
840
+ recursiveValue: string;
841
+ }[];
842
+ /** tar's traditional dash-less FIRST-operand key word (`tar cf out.tar dir`, the "old option
843
+ * style" in tar's own manual): mode letters found there select recursion exactly as their dashed
844
+ * spellings do. */
845
+ readonly bundledModeLetters?: string;
846
+ /** `true` when a bare `-` operand denotes STDIN for this command (content readers: grep's file
847
+ * slot, diff, tar's `-f -`). Absent ⇒ `-` is an ordinary filename (ls/du/find/tree have no stdin
848
+ * sense) and stays a positional operand for the recursion demotion. */
849
+ readonly dashIsStdin?: boolean;
850
+ }
851
+ export declare const RECURSIVE_READ_FORMS: Readonly<Record<string, RecursiveReadFormModel>>;
852
+ /**
853
+ * Read ONE sed script and answer whether it can name a file or run a command.
854
+ *
855
+ * sed's command letters are a CLOSED set, which is what makes this a small recognizer over a known
856
+ * grammar rather than a second hand-written parser over an open one: address → optional negation →
857
+ * command letter → the argument that letter takes. Anything the walk cannot finish reading — an
858
+ * unbalanced `s` delimiter, an unclosed `{`, a letter outside the set — answers `unparsed`, which
859
+ * its caller treats exactly as it treats `names`: sed itself rejects those scripts, so the cost of the
860
+ * conservative answer is one approval on a script that was not going to run.
861
+ */
862
+ /** What one sed script NAMES: the files its `r`/`R` commands read, the files its `w`/`W` commands (and `s///w`)
863
+ * write, the command lines its `e` commands run, and whether an `s///e` flag executes the pattern space. */
864
+ export interface SedScriptNames {
865
+ readonly reads: readonly string[];
866
+ readonly writes: readonly string[];
867
+ readonly commands: readonly string[];
868
+ readonly executesPatternSpace: boolean;
869
+ }
870
+ /**
871
+ * Does this verb's SCRIPT SLOT put a file name or a command line somewhere the walk's operand reading
872
+ * cannot see? Table-driven by verb so a second such verb is a row rather than a second branch.
873
+ *
874
+ * EVERY implementation reading is asked, and the answers combined: an affirmative `names` under any one
875
+ * refuses; a script no reading could SEE refuses, because file-supplied program text may carry anything;
876
+ * and a script no reading could finish refuses. A reading that lands on a clean script is what clears the
877
+ * invocation — which is what keeps the everyday spellings, whose other reading lands on a plain file name,
878
+ * at their old verdict.
879
+ */
880
+ /** Every file and command line a sed invocation's script slot names, under BOTH option dialects (the union,
881
+ * deduplicated): what a candidate extraction hands the read judge (`reads`), the write face (`writes`) and a
882
+ * nested plan (`commands`). A reading that saw no text names nothing here — that fact rides the verdict face. */
883
+ export declare function sedScriptSlotFiles(args: readonly string[]): SedScriptNames;
884
+ /**
885
+ * How an INTERPRETER receives the program it runs, declared per interpreter rather than guessed.
886
+ *
887
+ * The member that completes the ARGUMENT-AS-PROGRAM family. An interpreter's program can arrive three
888
+ * ways: as an OPTION's value (`perl -e '...'`, `python3 -c '...'`, `node --eval '...'`), as the first
889
+ * OPERAND (the awk family, whose program is mandatory and is not a file unless `-f` named one), or on
890
+ * STANDARD INPUT when the command line names none at all. Only the third and the first are this arm's
891
+ * business together with the second; a program named as a FILE operand (`perl x.pl`, `python3 app.py`)
892
+ * is a path the ordinary operand walk already judges, and stays exactly where it was.
893
+ *
894
+ * WHAT IS JUDGED IS THE PRESENCE OF PROGRAM TEXT, NEVER ITS CONTENT. A quoted program is ONE word to
895
+ * this file's lexer and its contents are never parsed — so the answer cannot be "does this program read
896
+ * a file", only "is there a program this walk did not read". That is why the everyday harmless
897
+ * spellings (`node -e 'console.log(1)'`) refuse along with the rest: telling them apart needs a parser
898
+ * per language, which is exactly the thing a lexical floor must not grow.
899
+ *
900
+ * A row is DATA, so a further interpreter is a row rather than a branch, and a versioned spelling
901
+ * (`python3.11`, `perl5.36`, `tclsh8.6`) is the same row via {@link interpreterKey}.
902
+ */
903
+ export interface InlineProgramModel {
904
+ /** Options whose value is program TEXT, separated (`--eval prog`) or fused (`--eval=prog`). Matched on
905
+ * the whole token, so a single-dash long option (`-cmd`) is one of these rather than a letter bundle. */
906
+ readonly longText: ReadonlySet<string>;
907
+ /** Options whose value NAMES the program (a script file, or a module the interpreter resolves). */
908
+ readonly longFile: ReadonlySet<string>;
909
+ /** Options that NAME the program while taking NO value of their own — a mode that selects what runs
910
+ * (`node --test` runs the test files it discovers, and reads no program from its input). */
911
+ readonly longNamesProgram: ReadonlySet<string>;
912
+ /** Options whose value is a file the interpreter reads BESIDE the program it is given — an
913
+ * initialization script, a library to preload. Their value is consumed so it is not counted as an
914
+ * operand, and they do NOT name the program: what follows still has to. */
915
+ readonly longSupplemental: ReadonlySet<string>;
916
+ /** Options whose value is the NEXT token and means none of the above. */
917
+ readonly longValue: ReadonlySet<string>;
918
+ /** Long options this row declares to take NO value. Declaring them is what keeps an invocation
919
+ * EXPLAINED past them, so a later script operand still ends the interpreter's own option scanning. */
920
+ readonly longPlain: ReadonlySet<string>;
921
+ /** Whether an option that NAMES the program also ends this interpreter's option processing, because
922
+ * what follows is the named program's own argument list (`python3 -m <module> -c` passes `-c` to the
923
+ * module). False where a named source is PRELIMINARY and further sources still follow (`dc -f <file>
924
+ * -e '<program>'` runs both). */
925
+ readonly fileOptionEndsOptions: boolean;
926
+ /** Short-option letters that introduce program text — alone (`-e prog`), fused (`-eprog`) or inside a
927
+ * bundle (`-pe`, `-lne`, `-uc`, `-0777e`). */
928
+ readonly textLetters: string;
929
+ /** Short-option letters whose value names the program (`-f prog.awk`, `-fprog.awk`, `-m <module>`). */
930
+ readonly fileLetters: string;
931
+ /** Short-option letters whose value is the rest of the token, or the NEXT token when the token ends on
932
+ * them (`perl -I lib`). The bundle scan stops there — what follows in the token is that value. */
933
+ readonly separatedValueLetters: string;
934
+ /** Short-option letters whose value can only be ATTACHED (`perl -i.bak`, `-C<flags>`): the bundle scan
935
+ * stops there too, but a bare spelling takes NOTHING from the next token — reading `perl -C -e '…'`
936
+ * as "-C whose value is -e" swallowed the option that carries the program. */
937
+ readonly attachedValueLetters: string;
938
+ /** Short-option letters that take no value at all. Declaring them is what makes an invocation EXPLAINED
939
+ * (see `explained` in the recognizer); they change nothing else. */
940
+ readonly plainLetters: string;
941
+ /** Short-option letters that make this interpreter PRINT AND EXIT. Per interpreter, because the same
942
+ * letter is a banner in one and something else entirely in another (`-v` is the version in ruby and
943
+ * VERBOSE EXECUTION in python; `-h` is the version banner in lua and the HOST in psql). */
944
+ readonly bannerLetters: string;
945
+ /** Short-option letters that make the interpreter read commands INTERACTIVELY after whatever else it
946
+ * was given — a banner or a named script does not end the session, so the program still arrives on
947
+ * standard input (`lua -v -i`, `lua -i script.lua`). */
948
+ readonly interactiveLetters: string;
949
+ /** Which OPERAND names the program, when any does, and whether it is program TEXT (the awk family, a
950
+ * jq filter, the SQL after a database) or a program FILE (every script interpreter). Absent when no
951
+ * operand ever names the program — psql's are connection arguments, and an interactive compiler's are
952
+ * modules it loads before reading commands from its input. */
953
+ readonly programOperand?: {
954
+ readonly at: number;
955
+ readonly isText: boolean;
956
+ };
957
+ /** Whether an invocation that names no program anywhere reads one from its standard input. */
958
+ readonly stdinWhenUnnamed: boolean;
959
+ }
960
+ /** The interpreters this file has a row for, keyed by the basename bash resolves, with the trailing
961
+ * version dropped ({@link interpreterKey} — which is also why the key for `sqlite3` is `sqlite`). */
962
+ export declare const INTERPRETER_INLINE_PROGRAMS: ReadonlyMap<string, InlineProgramModel>;
963
+ /** The row a versioned spelling belongs to: `python3`, `python3.11` and `python` are one program with
964
+ * one program-delivery grammar, so the trailing version is dropped before the lookup. */
965
+ export declare function interpreterKey(name: string): string;
966
+ /**
967
+ * How this invocation receives its program, or `undefined` when it NAMES one (a script file, a module,
968
+ * a program file option) or names none at all on an interpreter that then reads none.
969
+ *
970
+ * Read over the folded arguments with the program word excluded. Three sources in one pass — an option
971
+ * whose value is program text, the operand a row declares as the program, and standard input — and one
972
+ * rule for every doubt: an option this file has no row for never ENDS the search for program text. The
973
+ * word that names the program stops the scan (past a script, `-p` belongs to the SCRIPT), but only while
974
+ * every option so far was one the row EXPLAINS: after an option of unknown arity the following word may
975
+ * be that option's value rather than the script, so the search has to run to the end.
976
+ */
977
+ export declare function inlineProgramClause(model: InlineProgramModel, args: readonly string[]): string | undefined;
978
+ /** Does this already-vetted segment (FOLDED tokens — quoting does not change what getopt sees) invoke
979
+ * a {@link RECURSIVE_READ_FORMS} verb in a recursive form that DISCLOSES CONTENTS? `args` excludes the
980
+ * command name. Names-only traversals (`find`, `ls -R`, `du`, `tree`, an archive write) are in the table
981
+ * and answer `false` here — see {@link RecursiveReadFormModel.discloses}. */
982
+ export declare function segmentSelectsRecursiveRead(name: string, args: readonly string[]): boolean;
983
+ export declare function assignmentWordPayload(word: string): {
984
+ kind: "value";
985
+ value: string;
986
+ } | {
987
+ kind: "unresolved";
988
+ reason: string;
989
+ } | undefined;
990
+ /** WHICH programs are DECLARED not to open the value of a `NAME=value` ARGUMENT — an allow-list, because
991
+ * the other polarity was measured false. `make PREFIX=/usr/local`, `cmake -D…`, `docker run -e HOME=/root`
992
+ * name a build prefix and an environment value their program never opens, and judging those was the one
993
+ * shape of the verb-blind rule that MANDATED the everyday build line. The exemption is theirs alone.
994
+ *
995
+ * The polarity is the whole point. Listing the programs that DO open such a value means anything the walk
996
+ * fails to recognise is exempt — and what it fails to recognise is WHICH PROGRAM RUNS: an undeclared
997
+ * wrapper is read as the program (`caffeinate dd if=<path>`), and a launcher can carry the program inside
998
+ * an option payload with no word equal to it (`env -Sdd if=<path>`). Both were verified to execute the
999
+ * read. Inverted, none of those is on this list, so none is exempt — nor is a fifth spelling of the same
1000
+ * trick. A further program is a member, not a branch, and adding one is a claim about it alone.
1001
+ *
1002
+ * NOT its business: the LEADING environment assignments and an OPTION word's payload, each with its own arm. */
1003
+ export declare const ASSIGNMENT_VALUE_NOT_PATHS: ReadonlySet<string>;
1004
+ /** The program the shell will RUN here and where its OWN argv begins — the two seats
1005
+ * {@link HeadedSegment.argvProgram} / {@link HeadedSegment.argvFrom} carry, from one reader so they cannot
1006
+ * disagree. Both assignment spellings are stripped BEFORE the program-position reader runs: it does not
1007
+ * read `NAME+=value` as an assignment (quoting makes `'A+=x'` a command NAME on folded text), so a leading
1008
+ * append word arrives AS the program and hides any launcher — measured on `A+=x env B=<outside> <prog>`. */
1009
+ export declare function argvProgramOf(tokens: SegmentTokens): {
1010
+ program: string;
1011
+ from: number;
1012
+ } | undefined;
1013
+ export declare function attachedOptionPayloads(tok: string): string[];
543
1014
  /**
544
1015
  * The home directory's VARIABLE spellings, SUBSTITUTED with the declared value before a read face segments the
545
1016
  * command: `$HOME` / `${HOME}` at the START of a word (an empty quote pair before it included), unquoted or
@@ -643,6 +1114,10 @@ export interface OutOfRootReadGateVerdict {
643
1114
  readonly checked: readonly string[];
644
1115
  readonly undecided: readonly string[];
645
1116
  }
1117
+ /** Working-directory moves {@link evaluateReadBoundary} does NOT model. `cd` is deliberately absent —
1118
+ * it IS modelled (the walk threads the base through it), which is the whole difference: a move the
1119
+ * walk follows keeps the verdict decidable, and one it cannot follow does not. */
1120
+ export declare const UNMODELLED_BASE_MOVERS: ReadonlySet<string>;
646
1121
  /** Test seam for the lockstep pin that the launcher table (the ONE launcher truth, in the program-position
647
1122
  * reader's module) stays a SUBSET of the rule lane's curated `BARE_INTERPRETER_NAMES`. Exported for that one
648
1123
  * assertion — production code reads the table directly. The containment face ({@link classifyOutOfRootReadGate})