Source of truth for active work. Translates SPEC.md §16 (bash — shipped)
and SPEC.POWERSHELL.md §16 (PowerShell — v0.2.0) into NOW / NEXT / LATER
buckets. Park items aggressively — autonomous loops will otherwise bulldoze
priorities.
Hard rule: every PR ends with this file updated (item moved / completed / parked) so the plan reflects reality.
Spec:
SPEC.POWERSHELL.md(v0.2.0). The PowerShell parser is implemented — phases 1–14 ofSPEC.POWERSHELL.md§16 are complete (see below). What remains is the downstream Netclaw integration, which needs actions outside this repository.
- 1. Public-API surface —
ShellParserOptionsbase;BashParserOptionsreparented;PwshParserOptions+PwshParser; additiveVerbChain.CanonicalVerb/IsDynamic;Clause.IsBashCWrapped→IsCommandStringWrapped.PublicApiSnapshotTests, corpus DTOs, and the bash corpus JSON key all updated. - 2. Verb & binding tables —
PwshApprovedVerbs,PwshAliases(complete default set),PwshVerbs,PwshBindingTables,PwshPerVerbRules. - 3.
PwshLexer— quoting, backtick escape,$var/$env:/${name}, parameters, stream redirects, statement separators, comments, opaque regions,--%;OpaqueRegionScannerbacktick mode. - 4–10.
PwshCommandParser— pipeline / statement splitting, verb-chain extraction, the §6.5 binding model,PwshResolver(§8), per-verb path rules (§7),Set-Locationpropagation (§9),pwsh -Command/-EncodedCommandrecursion (§10), anomaly safe-fail and the 64 KiB cap (§11). - 11. Multi-shell corpus runner + PII audit — directory-routed by
Corpus/<shell>/. - 12. PowerShell corpus — 273 entries under
Corpus/powershell/, every §13 category minimum exceeded. - 13.
pwshvalidation gate +tools/PwshCorpusTool—PwshOracleTestsenforces the §13 oracle matrix + thePwshAliasescompleteness[Fact]; the tool is registered inTOOLING.md. - 14.
SPEC.mdedits + CI + version bump —SPEC.md§1 / §2 / §3 / §6.4 / §15 updated;VersionPrefix→0.2.0,VersionSuffix→alpha; CI verifiespwshand runs both corpora on Linux + Windows;RELEASE_NOTES.mdv0.2.0 section; CLI + Web samples gain a shell selector;README.mdupdated.
- Issue #63 — static Invoke-Expression command-string recursion.
Recurse into provably static
Invoke-Expression/iexpayloads, safe-fail computed and pipeline-fed code, share the existing recursion limits, and preserve current-scope PowerShell location attribution. - Issue #62 — source-ordered clause elements. Added the additive
Clause.Elementsprovenance view for Bash and PowerShell with exact raw spelling, decoded values, source spans when available, verb-relative argument placement, path facts, redirects, and conservative wrapper-span handling. Authored order is authoritative; element roles andPrecedingVerbElementCountexplicitly mirror the greedy parser projection rather than executable semantics. Paired Bash/PowerShell corpus cases cover Git-c/-C, multiple occurrences, and a valueless option that stops the greedy walk. Existing projection shapes and synthetic cwd attribution remain compatible; native options that differ only by case receive corrected metadata. The post-implementation option audit explicitly covers Wget-o/-O, curl-d/-D/-o/-O, Git-c/-C, and tar-c/-C/-f/-F; paired corpus cases pin Wget log/document output, curl data/header-output and@filesemantics, and tar helper-command safe-fail behavior in both shells. Adversarial review added deterministic coverage for quoted inline native fragment runs (including unquoted prefixes and mixed-quote safe-fail), PowerShell backtick-decoded colon bindings, native file-verb boundaries, and outer redirects on PowerShell command wrappers, including empty payloads. The corpus runner now verifies direct authored-token coverage even for legacy entries without explicit element expectations. Docker-vremains explicitly context-sensitive: the generic table supportsdocker run, while consumers use authored elements for global-option interpretation. Command-string provenance is integrated with the laterInvoke-Expressionrecursion work: static expansion clears unmappable outer spans, while dynamic payloads retain conservative source-aligned elements. Nested and dynamicbash -ccases pin the equivalent Bash boundary. Consumer guidance separates strict authored-stream matching from general executable-aware normalization; Netclaw can use the latter for reusable approvals without treating parser verb roles as semantic command boundaries. - Issue #64 — path-shaped operands after native verb chains. Stop the Bash and PowerShell native greedy passes before a token that matches the shared path-shape rules. Preserve that token as a resolved argument without a command dictionary or a public API change.
- Consumer guide. Added
docs/CONSUMER_GUIDE.mdwith the shell-neutral security-consumer algorithm, Bash and PowerShell guidance, worked public use cases, and immutable permalinks to Netclaw's production integration. Linked it from the README and aligned stale PowerShell prerelease/status wording in the public project docs. - Issue #52 — hyphenated PowerShell parameters/native options.
Preserve internal hyphens, apply bash-compatible native
--flag=valuesplitting and path classification, keep colon binding cmdlet-only, and pin the behavior in unit tests plus the PowerShell corpus. Review follow-ups shipped with it: the equals-form split moved to a sharedNativeFlagSyntaxso the two parsers can't drift, a colon value under an=-bearing parameter name safe-fails toDynamicSkip,-?lexes as one parameter token, and native option tables now use ordinal spelling while PowerShell cmdlet parameters remain case-insensitive.
- Tag
0.2.0-alpha;publish_nuget.ymlproducedShellSyntaxTree.0.2.0-alpha.nupkgand it is live on nuget.org (released 2026-05-20). -
0.2.0-beta.1so Netclaw validates the parser + the breaking rename - Publish the next
0.2.0prerelease with the additive issue #62Clause.Elementsprovenance surface and migration guidance - Promote to stable
0.2.0after Netclaw validation
- Netclaw consumes the v0.2.0 package; absorbs the
Clauserename (separate repository — cannot be done here) - ≥1 Netclaw integration test exercises a real PowerShell corpus entry through the live matcher and gets the expected gate decision
- Create the release-level OpenSpec proposal, design, capability deltas,
and ordered task list under
openspec/changes/v0-3-structured-shell-analysis/. - Create issue #72 as the v0.3 roadmap and cross-link issue #71 control flow and issue #69 shared native argument-fragment classification without merging their scopes.
- Add a versioned pre-implementation design corpus with paired Bash and
PowerShell representative, boundary, and adversarial cases. The validator
rejects schema drift, checks references and command ordering, confirms
every
currentexpectation against the v0.2 parsers, and includes the files in the PII audit. Corpus review established that command role must be immediate while ancestry remains compositional, occurrence completeness is independent of value precision, and PowerShell authored parameter classification must remain distinct from effective values. The paired 32/33-candidate boundary cases lock finite-versus-unknown behavior. - Lock OpenSpec task-group decisions 1.1–1.5 and 1.8: exact public type
candidates and safe defaults, in-memory
Clauseidentity, fixed 32/16/5 analysis limits, separate Bash and PowerShell grammar matrices, static pattern-cover rules, divergent-cwd fallback, deferred forms, project context, and the preimplementation consumer-guide migration contract. - Synchronize the accepted shared and PowerShell v0.3 contracts into
SPEC.md/SPEC.POWERSHELL.mdin the public-API implementation change. The canonical specs now lock the additive types and defaults, closed record hierarchy, separate Bash and PowerShell grammars, occurrence and compatibility projections, bounded values/state, explicit redirects, resolver contexts, fail-closed consumer contract, and persistence caveat. - Correct the lexer-to-resolver provenance boundary before issue #69.
Paired Bash and PowerShell shell-oracle cases must distinguish escaped
literal resolver syntax from expandable syntax even when both decode to
the same string, including standalone, adjacent-token, all-static
mixed-quote, within-token escape, and literal-plus-expandable cases.
Preserve ordered literal / typed-expansion / opaque fragments plus
operation-specific transform eligibility, expansion identity,
cardinality, and opaque cause internally; require explicit
Bash-argument, Bash-redirect, PowerShell-native, cmdlet-Path,
cmdlet-LiteralPath, and PowerShell-redirect resolver contexts, and
aggregate adjacent fragments for complete
argument and redirect targets. Runtime special, positional, numeric, and
Unicode variables retain typed expansion identity and cardinality while
remaining unknown without a proved value; incomplete braced
interpolation is unparseable. Exact composition is required when
every fragment, binding fact, and resolver fact is exact. Do not change
the v0.2 public API or infer expansion from decoded text. The first
implementation was halted before commit after adversarial review proved
that one universal expandable bit misclassified quoted PowerShell native
and cmdlet paths, missed valid variable forms, accepted incomplete
interpolation, and split adjacent redirect targets.
Bash provider-looking text remains literal; only PowerShell cmdlet path
and redirect contexts apply provider or PSDrive semantics, overriding
the obsolete shared resolver rule when the canonical specifications are
synchronized.
PowerShell redirects remain a separate Path-like context that applies
tilde, wildcard, provider, and PSDrive semantics after quote removal;
unknown wildcard cardinality or drive mappings fail closed without
enumeration. Bash redirects instead require exactly one proved target.
The completed correction uses an internal ordered
ShellValuefragment model in both front ends and passes an explicit consumer context into each resolver without changing the public API. Direct lexer tests pin typed Bash special and multidigit positional parameters, quote-sensitive$*/$@cardinality, and PowerShell special, numeric, scoped, braced, and Unicode variable identity. Paired live-shell oracles cover standalone and adjacent escapes, static mixed quoting, literal-plus-expandable composition, runtime parameter forms, incomplete versus escaped braced interpolation, Bash provider-looking literals, native versus cmdlet provider and wildcard behavior,PathversusLiteralPath, adjacent and wildcard redirects, and PowerShell tilde/provider/PSDrive redirects. Executable corpus cases preserve the corrected v0.2 compatibility projection; unknown facts remainDynamicSkipor unparseable, while completely proved mixed fragments resolve exactly. - Implement issue #69
against the corrected fragment contract. One shell-neutral classifier
now aggregates the complete raw span, decoded value, next-token index,
and ordered
ShellValueprovenance supplied by explicit Bash and PowerShell adapters. It distinguishes literal-only, typed expansion, and opaque/computed runs without rescanning decoded text; missing lexer provenance fails closed asOpaquein both adapters. Direct adapter tests pin spans, maximal consumption, expansion identity, opaque cause, and fallback behavior. The full Bash and PowerShell corpora prove the extraction leaves raw, decoded, span, path, andDynamicSkipresults unchanged. It introduces no new compatibility correction; the shell-oracle-proved corrections remain the ones documented in the preceding provenance item. - Audit duplicated Bash and PowerShell path-normalization helpers. Share only the identical string-level join and separator-normalization rules; keep root detection, drive-relative handling, full segment normalization, provider/PSDrive behavior, and resolver failure policy in their shell-specific implementations. Direct boundary tests pin the extracted helpers, while the complete resolver and corpus suites prove the refactor leaves both compatibility projections unchanged.
- Add the inert v0.3 public API skeleton: the closed syntax-node family,
command occurrences and ancestry, value domains and fixed limits,
explicit redirect records, plus additive
ParsedCommand.SyntaxandCommands. Public snapshot tests pin every member, enum order, default, and assembly-only closure mechanism. Until the projection passes land,Syntaxis an empty block andCommandsis empty, so early use remains fail-closed while v0.2Clausesbehavior is unchanged. - Add the parser-owned structural projector and conservative compatibility
flattener. It walks every syntax shape in deterministic authored order,
assigns immediate roles and compositional ancestry, preserves the exact
Clauseinstance and its authored operator, joins parser-owned analysis facts without mutating compatibility leaves, and discards every partial projection on malformed, aliased, cyclic, or over-depth structure or invalid joined facts. Direct tests pin ordering, branch and pipeline precedence, ancestry coordinates, reference identity, span and enum validity, value/redirect invariants, safe defaults, copied collections, and the 16-container bound. - Adapt the existing Bash grammar to emit structural and command-occurrence
projections before enabling control flow. The recursive coordinator
preserves pipeline/list precedence, isolated nested groups, decoded
wrapper ownership and nullable spans, compatibility operators, and exact
Clauseidentity; unsupported wrapper tails and depth overflow fail closed. Redirect-bearing leaves remain incomplete until the explicit redirect-analysis slice lands. - Adapt the existing PowerShell grammar to emit the structural and
command-occurrence projections before enabling any control-flow
construct. The PowerShell-specific recursive coordinator preserves
statement/pipeline precedence, current-scope parenthesized groups,
isolated child-host wrappers, current-scope
Invoke-Expression, exact direct spans, nullable decoded spans, compatibility operators, and shared leaf identity. It rejects hostile structural depth before descent and leaves redirects, dynamic identities, unproved host command strings, and undiscovered executable expressions incomplete. - Promote representative existing constructs into the executable corpus
with exact v0.3 syntax and command-occurrence expectations. Bash and
PowerShell cases pin simple commands, list/pipeline precedence, group
scope, static and dynamic wrappers, redirects, hidden substitutions,
roles, ancestry, completeness, nullable decoded spans, compatibility
operators, and exact shared
Clauseidentity. The strict DTO rejects unknown fields and always requires unparseable projections to be empty. The PowerShell manifest now owns all 361 entries and round-trips exactly; explicit false/null assertions remain opt-in and generator-preserved. - Deliver the first Bash
$()substitution slice for supported simple-command arguments and redirect targets. Direct tests and corpus entries pin multiple and nested ordering, exact ancestry/spans, isolated cwd, decoded wrappers, literal boundaries, dynamic compatibility values, depth limits, comment-safe delimiter scanning, and fail-closed command identities, background lists, assignment prefixes, backticks, heredocs, and malformed interiors. - Extend Bash substitution discovery to expanding heredoc bodies. The bounded slice recognizes quoted, escaped, mixed, and tab-stripping delimiters; preserves exact body/terminator provenance; surfaces nested substitutions in authored order with isolated state; and rejects header tails, queued heredocs, backticks, arithmetic, continuations, incomplete interiors, and depth overflow atomically. Direct and executable-corpus cases pin exact syntax, command ancestry, spans, completeness, and literal-versus-expanding behavior; real-Bash output and parse-only oracles independently pin the bounded semantic boundary.
- Deliver the static-value Bash
for ... inslice: locked structural nodes and spans, iterator$()discovery, condition-free body occurrences, exact/finite/pattern/unknown value domains, quote-proved effective arguments, nested distinct-name correlation, fixed candidate/depth limits, strict executable-corpus facts, and real-Bash oracles. Compatibility leaves preserve authored dynamic operands. Loop binding and cwd mutation fail closed, loops reached after recognized prior shell-state mutation fail closed, and occurrence cwd remains Unknown. - Design and implement structure-aware Bash abstract-state analysis for the
complete bounded
for ... instate slice. The analyzer owns success/failure partitions, failure-aware cwd transfer, conservative pipeline state, ordered duplicate-preserving loop plans, persistent loop bindings, empty iteration, occurrence-fact joins, substitution isolation, and explicit decoded-wrapper remapping. Unknown-cardinality loops use bounded fixed-point widening, a 4096-transition global budget fails nested cross-products atomically, and complete effective argv is re-evaluated for every visit. Loop-derivedcdoptions, terminators, invalid/multiple operands, recursive exactcommand/builtindispatch, physical-path compatibility sanitation, and post-loop binding mutation are pinned by unit tests, native Bash oracles, the design corpus, and executable corpus. The coverage matrix now also pins empty and multiline loops, mixed separators, pipelines, nested loops, wrapper scope, static and binding-derived redirects, substitutions, option-shaped and unquoted values, indirect and parameter-operator rejection, and every candidate and transition cap. All unmodeled mutations, dynamic dispatch, control transfers, and occurrence-specific redirect values remain fail closed. Next add the Netclaw approval matrix before calling the Bash consumer integration complete. - Complete PowerShell
foreachvalue and state analysis and add the Netclaw approval-matrix cases. The structural slice now preserves literal scalar/array and executable iterator forms, recursively parses bodies, projects iterator and loop-body ancestry, survives decoded wrappers, and fails closed on dynamic iterables, iterator/body state or command-resolution mutation, malformed boundaries, and depth overflow. Loop-body and current-scope post-loop occurrences intentionally remain incomplete; isolated child-host loops do not taint their outer continuation. Before publishing exact or finite values, add an explicit PowerShell initial-runspace contract and wrapper-state metadata: ambient typed, read-only, scoped, alias, function, and module state can change binding assignment and command resolution, while child hosts inherit no fresh state guarantee unless their own invocation proves it. Expand the design corpus for cardinality, mutation, separators, wrappers, redirects, and transition caps before tasks 7.3-7.7. 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 361-entry executable corpus. - Deliver Bash
for ... inand PowerShellforeachas the first two language-specific vertical slices, then extract only the shared analysis proven by both implementations. - Build on the delivered bounded Bash heredoc grammar and quoted-delimiter
adjacency by exposing public body/delimiter/expansion/completeness facts,
then add a separately tested Bash
<<<here-string redirect slice. - Near the end of v0.3 delivery, expand the Web sample with curated complex Bash and PowerShell inputs and deterministic Mermaid views of syntax, occurrences, compatibility clauses, ancestry, redirects, and fail-closed outcomes. Keep visualization downstream of the canonical projection so it cannot become a second command-discovery implementation; snapshot the rendering, escape arbitrary shell labels, and emit no raw HTML.
- Seed corpus entries from sanitized real-world dogfood logs (SPEC §14 workflow) — both shells.
- Expand verb / cmdlet / alias tables as the corpus surfaces real commands.
- Performance sanity check (~1 ms typical) with a tiny BenchmarkDotNet harness — only if anything in the daemon hot path complains.
- The remaining v0.2.x candidate from
SPEC.POWERSHELL.md§18 is per-element comma-array path extraction (-Path a,b,c). Lossless cross-shell redirect identity is now part of issue #72's explicit v0.3 redirect model. - PowerShell definitions,
param()/begin/process/endblocks, and.ps1file parsing remain outside issue #72 (SPEC.POWERSHELL.md§18). - Any broader shared parser/analysis extraction beyond issue #69 follows the two language-specific tracer bullets in issue #72; lexers and structural parsers remain shell-specific unless proven duplication justifies a narrower composed helper.
- Windows
cmdparser. - Source-mapping (line/column on AST nodes) — only if an IDE consumer asks.
- Process substitution remains a separately gated issue #72 task; Bash function definitions remain deferred until a consumer need surfaces.
(empty; move items here when scope changes rather than deleting them)