Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ priorities.
roles, ancestry, completeness, nullable decoded spans, compatibility
operators, and exact shared `Clause` identity. The strict DTO rejects
unknown fields and always requires unparseable projections to be empty.
The PowerShell manifest now owns all 310 entries and round-trips exactly;
The PowerShell manifest now owns all 341 entries and round-trips exactly;
explicit false/null assertions remain opt-in and generator-preserved.
- [x] Deliver the first Bash `$()` substitution slice for supported
simple-command arguments and redirect targets. Direct tests and corpus
Expand All @@ -267,10 +267,12 @@ priorities.
and malformed interiors.
- [ ] Extend Bash substitution discovery to iterables and expanding heredoc
bodies, then add the corresponding Netclaw approval-matrix cases.
- [ ] Deliver the PowerShell `$()` substitution slice for every locked value
and expression position, including current-scope state propagation,
literal boundaries, incomplete dynamic identities, executable corpus,
and Netclaw cases.
- [ ] Complete PowerShell `$()` discovery in `foreach` expressions and add the
Netclaw approval-matrix cases. The simple-command slice is delivered for
ordinary, adjacent, quoted, here-string, redirect, standalone,
call-operator, dynamic-identity, and host-wrapper positions, with
current-scope state propagation and bounded expression rejection pinned
by the 341-entry executable corpus.
- [ ] Deliver Bash `for ... in` and PowerShell `foreach` as the first two
language-specific vertical slices, then extract only the shared analysis
proven by both implementations.
Expand Down
25 changes: 17 additions & 8 deletions SPEC.POWERSHELL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1047,9 +1047,16 @@ pipeline elements within a statement, not separate statements.

### Opaque regions

Script blocks `{ ... }`, subexpressions `$( ... )`, array subexpressions
`@( ... )`, and hash literals `@{ ... }` are bounded by the shared
`OpaqueRegionScanner` and emitted as single tokens. The compatibility parser
Script blocks `{ ... }`, array subexpressions `@( ... )`, and hash literals
`@{ ... }` are bounded by the shared `OpaqueRegionScanner`. PowerShell command
subexpressions `$( ... )` use a specialized scanner that understands nested
subexpressions, quoted regions, backtick escapes, line and block comments, and
the shared structural-depth cap. Direct `$()` bodies recognize line comments
only at PowerShell word boundaries. A `$()` discovered while decoding an expandable
string or here-string rejects comment-bearing interiors conservatively because
the parent quoting context changes whether PowerShell can close the region.
Here-strings nested inside `$()` remain an unsupported grammar boundary. Each
bounded region is emitted as one token. The compatibility parser
retains each as one `Arg { Kind=DynamicSkip, IsPath=false, Resolved=null }`,
`Raw` being the verbatim region slice. Stable v0.3 additionally parses every
supported executable `$()` interior into `SimpleCommandSyntax.Substitutions`.
Expand All @@ -1067,9 +1074,10 @@ evaluation, and following outer commands. Unknown location mutations propagate
as unknown. This differs from Bash command substitution, whose state is
isolated from the containing shell.

`OpaqueRegionScanner` is grammar-agnostic but escapes on backslash; for
PowerShell it is given a backtick-escape mode so `` { `} } `` scans
correctly.
`OpaqueRegionScanner` is grammar-agnostic but escapes on backslash; the
PowerShell script-block, array, and hash paths give it a backtick-escape mode
so `` { `} } `` scans correctly. The specialized `$()` scanner applies the
same backtick behavior directly.

### `pwsh -Command` recursion

Expand Down Expand Up @@ -1496,8 +1504,9 @@ testable step; most are a single PR.
binding tables, §6.5), `PwshPerVerbRules` (§7).
3. **`PwshLexer`** (`Internal/Pwsh/Lexing/`) — quoting, backtick escape,
`$var` / `$env:` / `${name}`, parameters, stream redirects, statement
separators, comments; opaque regions via the shared `OpaqueRegionScanner`
(with the backtick-escape mode). Heavy unit tests.
separators, comments; script-block/array/hash regions via the shared
`OpaqueRegionScanner` (with the backtick-escape mode), and `$()` via its
specialized scanner. Heavy unit tests.
4. **`PwshCommandParser` core** — pipeline / statement splitting, verb-chain
extraction, the §6.5 parameter-binding decision, args & parameters,
redirects, `VerbChain.IsDynamic` for dynamic command names.
Expand Down
32 changes: 29 additions & 3 deletions docs/CONSUMER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,35 @@ Redirects authored on the outer PowerShell wrapper remain attached to the last
surfaced clause, so redirect policy still sees paths such as
`pwsh -Command "git status" > audit.log`.

PowerShell script blocks, subexpressions, splats, and `--%` regions are opaque
and surface as `DynamicSkip`. A dynamically invoked command such as `& $exe`
sets `VerbChain.IsDynamic = true`; no verb-pattern grant should match it.
Supported PowerShell `$()` subexpressions are structural rather than hidden
opaque values. The containing `SimpleCommandSyntax.Substitutions` records each
authored child, and `ParsedCommand.Commands` projects its executable commands
before the containing command, with `ImmediateRole = Substitution`. Consumers
should authorize that occurrence list directly; walking `Syntax` again would
double-count the same shared `Clause` instances. A standalone
`$(Write-Output Get-Date)` exposes `Write-Output` without inventing an outer
invocation. By contrast, `& $(Write-Output Get-Date)` also retains an
incomplete dynamic outer occurrence because PowerShell invokes the produced
name.

Quoting also determines the scope of host-wrapper substitutions. In
`pwsh -Command "Write-Output $(Get-Date)"`, the parent evaluates `Get-Date`, so
the result contains that parent-scope occurrence plus an incomplete outer
`pwsh` occurrence; the parser does not pretend the expanded payload is a
literal child script. A literal payload such as
`pwsh -Command 'Write-Output $(Get-Date)'` can be decoded into child-host
syntax. Decoded child nodes have null source spans because their offsets do not
map exactly onto the outer source.

An ordinary script-block argument, splat, or `--%` remainder stays opaque and
surfaces as `DynamicSkip`. Proved-literal `@()` / `@{}` data stays opaque and
incomplete; execution-bearing forms and unsupported arbitrary expressions make
the whole result unparseable with empty `Commands` and `Clauses`. A containing
command may be structurally complete after every supported `$()` command is
visible while its produced argument value remains policy-sensitive. Treat
completeness and value safety as separate decisions. A dynamically invoked
command such as `& $exe` sets `VerbChain.IsDynamic = true`; no verb-pattern
grant should match it.

## Safe-fail rules

Expand Down
4 changes: 4 additions & 0 deletions openspec/changes/v0-3-structured-shell-analysis/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@
- [ ] 3.10 Implement Bash `$()` discovery in supported argument words, redirect values, iterables, and expanding heredoc bodies; retain literal/escaped spellings and fail closed on command-name substitutions, legacy backticks, or incomplete interiors.
- [x] 3.10a Implement the simple-command argument and redirect-target slice, including comment-safe boundaries and fail-closed unsupported interiors.
- [ ] 3.11 Implement PowerShell `$()` discovery in supported words, redirect values, foreach expressions, call-operator dynamic identities, standalone expression statements, double-quoted strings, and expandable here-strings; never invent invocation from standalone output, retain literal/escaped spellings, and fail closed on trailing command-style arguments, call-operator script blocks, or unsupported execution-bearing `@()` / `@{}` forms.
- [x] 3.11a Implement words, redirect values, call-operator dynamic identities, standalone statements, expandable strings/here-strings, and parent-versus-child host payload provenance; fail closed on arbitrary expression values and unsupported execution-bearing `@()` / `@{}` forms.
- [ ] 3.12 Pin substitution parentage, authored sibling indices, innermost-first ordering, Bash-isolated versus PowerShell-current-scope state, unknown-state propagation, nesting/depth limits, and incomplete dynamic identities in direct tests.
- [x] 3.12a Pin the Bash argument/redirect slice, isolated cwd behavior, wrapper provenance, and the shared structural-depth budget.
- [x] 3.12b Pin the PowerShell simple-command slice, current-scope exact and unknown cwd propagation, parent/child wrapper provenance, expression boundaries, and the shared structural-depth budget.
- [ ] 3.13 Promote ordinary, multiple, nested, iterator, redirect, quoted, escaped, stateful, malformed, and hidden-execution substitution cases into both executable corpora and the Netclaw approval matrix.
- [x] 3.13a Promote the Bash ordinary, multiple, nested, redirect, quoted, escaped, stateful, malformed, and hidden-execution cases into its executable corpus.
- [x] 3.13b Promote the PowerShell ordinary, multiple, nested, redirect, quoted, escaped, stateful, malformed, expression-boundary, and hidden-execution cases into its executable corpus.

## 4. Explicit Redirect Semantics

Expand All @@ -52,6 +55,7 @@
## 5. Consumer Migration Baseline

- [ ] 5.1 Rewrite the production-shaped consumer loop in `docs/CONSUMER_GUIDE.md` to enumerate every command occurrence.
- [x] 5.1a Document the PowerShell `$()` occurrence ordering, standalone/call-operator distinction, parent-versus-child host payload provenance, and completeness-versus-value-safety contract.
- [ ] 5.2 Document syntax-tree display traversal separately from authorization traversal.
- [ ] 5.3 Document exact, finite, pattern, unknown, joined-state, redirect, and incomplete-result handling.
- [ ] 5.4 Document record equality, hashing, `ToString()`, serialization, and `Clauses` compatibility effects.
Expand Down
Loading