From d77cdcdfffb71daedbf3cd5c5eb22b4135b58f47 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Sun, 10 May 2026 15:51:47 +0000 Subject: [PATCH] chore: bootstrap ShellSyntaxTree from Akka template Strip inherited Akka.NET build-system-template artifacts and stand up the agent operating system for the ShellSyntaxTree library: - Remove `.azure/`, `src/Akka.Console/`, `akkalogo.png`, `build.ps1`, legacy release-notes PowerShell, DocFX local tool, Akka.Hosting and Microsoft.Extensions.Hosting package references, Akka.Event global Using. - Rewrite `Directory.Build.props` and `Directory.Packages.props` for Aaron Stannard attribution, ShellSyntaxTree NuGet metadata, xUnit test stack, and SourceLink. `VersionPrefix=0.1.0`, `VersionSuffix=alpha`. - Rename `SampleSln.slnx` to `ShellSyntaxTree.slnx`; rewrite README to describe the library rather than the build template. - Update CI: `pr_validation.yml` runs build/test/pack and verifies copyright headers; `publish_nuget.yml` triggers on `v*.*.*` tags and computes the package version from the tag. - Add agent OS files: `PROJECT_CONTEXT.md`, `TOOLING.md`, `AGENTS.md`, `IMPLEMENTATION_PLAN.md`. `CLAUDE.md` is a symlink to `AGENTS.md`. - Add `SPEC.md` (the locked v0.1 contract). - Add `scripts/Add-FileHeaders.ps1` (Aaron Stannard copyright header enforcement, modeled on the Netclaw equivalent) covering `src/` and `tests/`. CI runs `-Verify`. - Generate and commit a NuGet package icon (`assets/icon.png`, 512x512) wired into `Directory.Build.props` via `PackageIcon`. --- .azure/azure-pipeline.template.yaml | 94 -- .azure/pr-validation.yaml | 29 - .azure/windows-release.yaml | 47 - .config/dotnet-tools.json | 9 +- .github/workflows/pr_validation.yml | 16 +- .github/workflows/publish_nuget.yml | 105 ++- AGENTS.md | 207 +++++ CLAUDE.md | 1 + Directory.Build.props | 46 +- Directory.Packages.props | 20 +- IMPLEMENTATION_PLAN.md | 164 ++++ PROJECT_CONTEXT.md | 160 ++++ README.md | 118 +-- SPEC.md | 1208 ++++++++++++++++++++++++++ SampleSln.slnx | 10 - ShellSyntaxTree.slnx | 18 + TOOLING.md | 136 +++ akkalogo.png | Bin 17058 -> 0 bytes assets/icon.png | Bin 0 -> 99997 bytes build.ps1 | 12 - scripts/Add-FileHeaders.ps1 | 139 +++ scripts/bumpVersion.ps1 | 28 - scripts/getReleaseNotes.ps1 | 44 - src/Akka.Console/Akka.Console.csproj | 15 - src/Akka.Console/HelloActor.cs | 15 - src/Akka.Console/Program.cs | 29 - src/Akka.Console/README.md | 5 - src/Akka.Console/TimerActor.cs | 24 - src/Akka.Console/Usings.cs | 2 - 29 files changed, 2191 insertions(+), 510 deletions(-) delete mode 100644 .azure/azure-pipeline.template.yaml delete mode 100644 .azure/pr-validation.yaml delete mode 100644 .azure/windows-release.yaml create mode 100644 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 IMPLEMENTATION_PLAN.md create mode 100644 PROJECT_CONTEXT.md create mode 100644 SPEC.md delete mode 100644 SampleSln.slnx create mode 100644 ShellSyntaxTree.slnx create mode 100644 TOOLING.md delete mode 100644 akkalogo.png create mode 100644 assets/icon.png delete mode 100644 build.ps1 create mode 100644 scripts/Add-FileHeaders.ps1 delete mode 100644 scripts/bumpVersion.ps1 delete mode 100644 scripts/getReleaseNotes.ps1 delete mode 100644 src/Akka.Console/Akka.Console.csproj delete mode 100644 src/Akka.Console/HelloActor.cs delete mode 100644 src/Akka.Console/Program.cs delete mode 100644 src/Akka.Console/README.md delete mode 100644 src/Akka.Console/TimerActor.cs delete mode 100644 src/Akka.Console/Usings.cs diff --git a/.azure/azure-pipeline.template.yaml b/.azure/azure-pipeline.template.yaml deleted file mode 100644 index eeadb38..0000000 --- a/.azure/azure-pipeline.template.yaml +++ /dev/null @@ -1,94 +0,0 @@ -parameters: - name: '' - displayName: '' - vmImage: '' - outputDirectory: '' - artifactName: '' - timeoutInMinutes: 120 - -jobs: - - job: ${{ parameters.name }} - displayName: ${{ parameters.displayName }} - timeoutInMinutes: ${{ parameters.timeoutInMinutes }} - - pool: - vmImage: ${{ parameters.vmImage }} - - steps: - - checkout: self # self represents the repo where the initial Pipelines YAML file was found - clean: false # whether to fetch clean each time - submodules: recursive # set to 'true' for a single level of submodules or 'recursive' to get submodules of submodules - persistCredentials: true - - - task: UseDotNet@2 - displayName: 'Use .NET' - inputs: - packageType: 'sdk' - useGlobalJson: true - - - script: dotnet tool restore - displayName: 'Restore dotnet tools' - - - pwsh: | - .\build.ps1 - displayName: 'Update Release Notes' - continueOnError: false - - - script: dotnet build -c Release - displayName: 'dotnet build' - continueOnError: false - - - script: dotnet test -c Release --no-build --logger:trx --collect:"XPlat Code Coverage" --results-directory TestResults --settings coverlet.runsettings - displayName: 'Run tests' - continueOnError: true # Allow continuation even if tests fail - - - task: PublishTestResults@2 - inputs: - testResultsFormat: VSTest - testResultsFiles: '**/*.trx' #TestResults folder usually - testRunTitle: ${{ parameters.name }} - mergeTestResults: true - failTaskOnFailedTests: false - publishRunAttachments: true - - - pwsh: | - $coverageFiles = Get-ChildItem -Path "$(Build.SourcesDirectory)/TestResults" -Filter "*.cobertura.xml" -Recurse - $hasCoverageFiles = $coverageFiles.Count -gt 0 - Write-Host "##vso[task.setvariable variable=HasCoverageFiles]$hasCoverageFiles" - displayName: 'Check for Coverage Files' - condition: always() - continueOnError: true - - - task: reportgenerator@5 - displayName: ReportGenerator - # Only run if coverage files exist - condition: and(always(), eq(variables['HasCoverageFiles'], 'True')) - continueOnError: true - inputs: - reports: '$(Build.SourcesDirectory)/TestResults/**/*.cobertura.xml' - targetdir: '$(Build.SourcesDirectory)/coveragereport' - reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges' - assemblyfilters: '-xunit*' - publishCodeCoverageResults: true - - - publish: $(Build.SourcesDirectory)/coveragereport - displayName: 'Publish Coverage Report' - # Only run if coverage files exist - condition: and(always(), eq(variables['HasCoverageFiles'], 'True')) - continueOnError: true - artifact: 'CoverageReports-$(Agent.OS)-$(Build.BuildId)' - - - script: dotnet pack -c Release --no-build -o $(Build.ArtifactStagingDirectory)/nuget - displayName: 'Create packages' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish artifacts' - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)/nuget' - ArtifactName: 'nuget' - publishLocation: 'Container' - - - script: 'echo 1>&2' - failOnStderr: true - displayName: 'If above is partially succeeded, then fail' - condition: eq(variables['Agent.JobStatus'], 'SucceededWithIssues') diff --git a/.azure/pr-validation.yaml b/.azure/pr-validation.yaml deleted file mode 100644 index 704d238..0000000 --- a/.azure/pr-validation.yaml +++ /dev/null @@ -1,29 +0,0 @@ -trigger: - branches: - include: - - dev - - master - -pr: - autoCancel: true # indicates whether additional pushes to a PR should cancel in-progress runs for the same PR. Defaults to true - branches: - include: [ dev, master ] # branch names which will trigger a build - -name: $(SourceBranchName)_$(Year:yyyy).$(Month).$(DayOfMonth)$(Rev:.r) - -jobs: - - template: azure-pipeline.template.yaml - parameters: - name: 'windows_pr' - displayName: 'Windows PR Validation' - vmImage: 'windows-latest' - outputDirectory: 'bin/nuget' - artifactName: 'nuget_pack-$(Build.BuildId)' - - - template: azure-pipeline.template.yaml - parameters: - name: 'linux_pr' - displayName: 'Linux PR Validation' - vmImage: 'ubuntu-latest' - outputDirectory: 'bin/nuget' - artifactName: 'nuget_pack-$(Build.BuildId)' diff --git a/.azure/windows-release.yaml b/.azure/windows-release.yaml deleted file mode 100644 index 7081f45..0000000 --- a/.azure/windows-release.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# Release task for Akka.Streams.Kafka -# See https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema for reference - -trigger: - branches: - include: - - refs/tags/* -pr: none - -variables: - - group: nugetKeys - - name: githubConnectionName - value: AkkaDotNet_Releases - - name: projectName - value: Akka.Streams.Kafka - - name: githubRepositoryName - value: akkadotnet/Akka.Streams.Kafka - -jobs: - - job: Release - pool: - vmImage: windows-latest - demands: Cmd - steps: - - task: UseDotNet@2 - displayName: 'Use .NET SDK from global.json' - inputs: - useGlobalJson: true - - - powershell: ./build.ps1 - displayName: 'Update Release Notes' - - # Pack without version suffix for release - - script: dotnet pack -c Release -o $(Build.ArtifactStagingDirectory)/nuget - displayName: 'Create packages' - - - script: dotnet nuget push "$(Build.ArtifactStagingDirectory)\nuget\*.nupkg" --api-key $(nugetKey) --source https://api.nuget.org/v3/index.json --skip-duplicate - displayName: 'Publish to NuGet.org' - - - task: GitHubRelease@0 - displayName: 'GitHub release (create)' - inputs: - gitHubConnection: $(githubConnectionName) - repositoryName: $(githubRepositoryName) - title: '$(projectName) v$(Build.SourceBranchName)' - releaseNotesFile: 'RELEASE_NOTES.md' - assets: '$(Build.ArtifactStagingDirectory)/nuget/*.nupkg' diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 71aff30..b82ebe4 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -8,13 +8,6 @@ "incrementalist" ], "rollForward": false - }, - "docfx": { - "version": "2.78.3", - "commands": [ - "docfx" - ], - "rollForward": false } } -} \ No newline at end of file +} diff --git a/.github/workflows/pr_validation.yml b/.github/workflows/pr_validation.yml index 5c4a6e4..9732270 100644 --- a/.github/workflows/pr_validation.yml +++ b/.github/workflows/pr_validation.yml @@ -36,19 +36,19 @@ jobs: - name: "Restore .NET tools" run: dotnet tool restore - - name: "Update release notes" + - name: "Verify copyright headers" shell: pwsh - run: | - ./build.ps1 + run: ./scripts/Add-FileHeaders.ps1 -Verify - - name: "dotnet build" - run: dotnet build -c Release + - name: "dotnet restore" + run: dotnet restore - # .NET Framework tests can't run reliably on Linux, so we only do .NET 8 + - name: "dotnet build" + run: dotnet build -c Release --no-restore - name: "dotnet test" shell: bash - run: dotnet test -c Release + run: dotnet test -c Release --no-build - name: "dotnet pack" - run: dotnet pack -c Release -o ./bin/nuget \ No newline at end of file + run: dotnet pack -c Release --no-build -o ./bin/nuget diff --git a/.github/workflows/publish_nuget.yml b/.github/workflows/publish_nuget.yml index 5fa311f..8d5d129 100644 --- a/.github/workflows/publish_nuget.yml +++ b/.github/workflows/publish_nuget.yml @@ -3,72 +3,69 @@ name: Publish NuGet on: push: tags: - - '*' + - 'v*.*.*' + - 'v*.*.*-*' permissions: - contents: write + contents: write jobs: publish-nuget: - name: publish-nuget - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] + runs-on: ubuntu-latest steps: - - name: "Checkout" - uses: actions/checkout@v6.0.2 - with: - lfs: true - fetch-depth: 0 - - - name: "Install .NET SDK" - uses: actions/setup-dotnet@v5.1.0 - with: - global-json-file: "./global.json" + - name: "Checkout" + uses: actions/checkout@v6.0.2 + with: + lfs: true + fetch-depth: 0 - - name: "Restore .NET tools" - run: dotnet tool restore + - name: "Install .NET SDK" + uses: actions/setup-dotnet@v5.1.0 + with: + global-json-file: "./global.json" - - name: "Update release notes" - shell: pwsh - run: | - ./build.ps1 + - name: "Restore .NET tools" + run: dotnet tool restore - - name: Create Packages - run: dotnet pack /p:PackageVersion=${{ github.ref_name }} -c Release -o ./output + - name: "Compute package version from tag" + shell: bash + run: | + # tag form is vX.Y.Z or vX.Y.Z-suffix + TAG="${GITHUB_REF_NAME}" + VERSION="${TAG#v}" + echo "PACKAGE_VERSION=${VERSION}" >> "$GITHUB_ENV" + echo "Publishing package version: ${VERSION}" - - name: Push Packages - run: dotnet nuget push "output/*.nupkg" -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json + - name: "dotnet pack" + run: dotnet pack /p:PackageVersion=${{ env.PACKAGE_VERSION }} -c Release -o ./output - - name: "Extract latest release notes" - shell: pwsh - run: | - $content = Get-Content RELEASE_NOTES.md -Raw - # Match from the first #### heading to just before the second one - if ($content -match '(?s)(####.+?)(?=\n####|\z)') { - $Matches[1].Trim() | Set-Content RELEASE_NOTES_LATEST.md - } else { - $content | Set-Content RELEASE_NOTES_LATEST.md - } + - name: "dotnet nuget push" + run: dotnet nuget push "output/*.nupkg" -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate - - name: release - uses: actions/create-release@v1 - id: create_release - with: - draft: false - prerelease: false - release_name: 'Akka.Hosting ${{ github.ref_name }}' - tag_name: ${{ github.ref }} - body_path: RELEASE_NOTES_LATEST.md - env: - GITHUB_TOKEN: ${{ github.token }} + - name: "Extract latest release notes" + shell: pwsh + run: | + if (-not (Test-Path RELEASE_NOTES.md)) { + "No release notes available." | Set-Content RELEASE_NOTES_LATEST.md + exit 0 + } + $content = Get-Content RELEASE_NOTES.md -Raw + if ($content -match '(?s)(####.+?)(?=\n####|\z)') { + $Matches[1].Trim() | Set-Content RELEASE_NOTES_LATEST.md + } else { + $content | Set-Content RELEASE_NOTES_LATEST.md + } - - name: Upload Release Asset - uses: AButler/upload-release-assets@v3.0 - with: - repo-token: ${{ github.token }} - release-tag: ${{ github.ref_name }} - files: 'output/*.nupkg' + - name: "Create GitHub release" + uses: softprops/action-gh-release@v2 + with: + name: "ShellSyntaxTree ${{ github.ref_name }}" + tag_name: ${{ github.ref_name }} + body_path: RELEASE_NOTES_LATEST.md + files: output/*.nupkg + draft: false + prerelease: ${{ contains(github.ref_name, '-') }} + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f8bfe18 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,207 @@ +# ShellSyntaxTree Agent Constitution + +This file is the repository's stable agent constitution. Keep it small. Keep +it durable. Keep it routing-focused. Volatile detail belongs in the +referenced repo-owned artifacts, not here. + +## Authority and Scope + +- You are authorized to plan, design, implement, test, document, and + release ShellSyntaxTree v0.x. +- Default to the smallest safe change that advances the v0.1 acceptance + criteria in `SPEC.md` §17. +- Prefer explicit trade-offs over hidden complexity. Bias toward marking + inputs `DynamicSkip` / `IsUnparseable` over guessing — this library + feeds security gates. + +## Read First (in this order) + +1. `SPEC.md` — the locked v0.1 contract (public API, AST, grammar, verb + tables, resolver, corpus rules). **The contract.** +2. `PROJECT_CONTEXT.md` — what this is, who consumes it, scope discipline. +3. `TOOLING.md` — what's available and how to access it. +4. `IMPLEMENTATION_PLAN.md` — current NOW / NEXT / LATER work. +5. The relevant `dotnet-skills:*` skill for the .NET concern at hand + (see `TOOLING.md` routing table). + +When `SPEC.md` and any other artifact disagree, **`SPEC.md` wins** — fix +the other artifact in the same change. + +## Required Task Routing (MODE) + +Pick a MODE before acting. Each MODE has different signals, expected +outputs, and definition-of-done. + +### MODE=build + +**Use when:** implementing parser logic, lexer, resolver, verb tables, +AST records, options, or any production code under `src/ShellSyntaxTree/`, +plus the unit tests that exercise it. + +**Signals:** changes under `src/`, non-corpus changes under `tests/`, +public-API surface changes (rare — requires bumping toward v0.2+). + +**Expected outputs:** + +- code under `src/ShellSyntaxTree/` +- unit tests under `tests/ShellSyntaxTree.Tests/` (NOT corpus JSON) +- `dotnet build` and `dotnet test` both pass locally +- Public API surface matches `SPEC.md` §2 exactly (any drift is a bug) + +**Definition of done:** see Universal DoD below, plus: every behavior +change is covered either by a unit test or a corpus entry. + +### MODE=corpus + +**Use when:** authoring, sanitizing, or curating JSON entries under +`tests/ShellSyntaxTree.Tests/Corpus/bash/*.json`. The corpus is the +acceptance contract — corpus work is materially different from code work +(JSON shape, sanitization rules, PII audit). + +**Signals:** changes under `tests/.../Corpus/`, references to dogfood +logs, requests like "add a case for `chmod`", "seed from logs". + +**Expected outputs:** + +- new or updated `*.json` corpus entries matching the SPEC §13 schema +- entries cover the SPEC §13 category coverage targets +- if seeded from logs: every sanitization rule in SPEC §14 is applied, + manually reviewed, and the entry is committed only after PII audit + would pass on it + +**Definition of done:** see Universal DoD, plus: + +- the PII audit scan (regex rules from SPEC §14) finds zero hits +- every new entry parses to the declared `expected` AST when run through + `BashParser` (i.e., implementation must already cover it, OR this entry + is intentionally a `[Theory]` failure that pins a NOW item in the + implementation plan — note the link in the entry's `notes` field) + +### MODE=release + +**Use when:** version bumps, RELEASE_NOTES.md updates, tagging, +NuGet publish flow changes. + +**Signals:** changes to `RELEASE_NOTES.md`, `Directory.Build.props` +`VersionPrefix`/`VersionSuffix`, `.github/workflows/publish_nuget.yml`, +or a request to "tag" / "ship" / "cut a release". + +**Expected outputs:** + +- semantic version that matches SPEC §15 progression rules + (`v0.1.x` additive, `v0.2.0` for first PowerShell parser, etc.) +- `RELEASE_NOTES.md` updated with the new section in the existing format +- on tag push, `publish_nuget.yml` produces a `.nupkg` matching + `VersionPrefix`/`VersionSuffix` and pushes to nuget.org + +**Definition of done:** see Universal DoD, plus: + +- the tag triggers the publish workflow and the package appears on + nuget.org +- for v0.1.0-alpha specifically: SPEC §17 acceptance #1-#8 all hold + +## Discovery Rules + +Before adding code or a test, discover in this order: + +1. Is there a SPEC clause that already specifies the behavior? Implement + to that. Do not invent shape that disagrees with the SPEC. +2. Is there an existing corpus entry that pins the expected AST for this + case? Make the implementation match the corpus. +3. Is there a `dotnet-skills:*` skill for the .NET concern? Open it; apply + the parts that fit; note conflicts. +4. Is the construct in SPEC §1 / §18 non-goals? If yes, mark + `IsUnparseable` and stop. + +If any of (1)-(4) conflict, **fix the conflict before implementing**. + +## Universal Quality Bar + +- **API surface is locked.** Public types in `SPEC.md` §2 do not change + without a deliberate version bump. Internal types are free. +- **No silent fallbacks.** If a token can't be safely classified, mark + `DynamicSkip`. If a construct can't be parsed, mark `IsUnparseable`. + Do not guess. Consumers can always relax — they can't un-execute a + command we falsely classified safe. +- **No native dependencies.** Multi-target `netstandard2.0` and `net8.0`; + AOT-trim friendly. +- **`TreatWarningsAsErrors=true`** is enforced repo-wide via + `Directory.Build.props`. Don't suppress warnings to make a build pass — + fix the cause. +- **No `Thread.Sleep`/`Task.Delay` in tests** to wait for conditions. + Tests should be deterministic; the parser is synchronous. +- **Comments: skip noise, keep signal.** Don't narrate code that + identifiers already explain. Do explain *why* a verb is in (or absent + from) `BashArity` / `FileVerbs`, *why* a token marks `DynamicSkip`, + *why* a fallback is or isn't safe in this context. +- **No stray sample apps, helper scripts, or "interesting" files.** This + repo ships one library and its tests. If a helper is genuinely needed, + put it under `tests/` or a clearly-scoped `tools/` folder and document + it in `TOOLING.md`. + +## Definition of Done (universal) + +A change is done when **all** of these hold: + +- behavior aligns with `SPEC.md` (or `SPEC.md` was updated in the same + change with explicit justification) +- `dotnet build -c Release` is clean +- `dotnet test -c Release` passes (unit + corpus) +- copyright headers present on every new/modified `.cs` file — + `pwsh ./scripts/Add-FileHeaders.ps1 -Verify` exits 0 (CI enforces this) +- public API surface still matches SPEC §2 exactly +- corpus entries that touch the changed code path still pass +- if MODE=corpus: PII audit regex finds zero hits +- if MODE=release: tag-driven publish workflow succeeds end-to-end +- `IMPLEMENTATION_PLAN.md` is updated (item moved, completed, or + parked) so the plan reflects reality + +## Validation Expectations + +Run before declaring done: + +```bash +dotnet tool restore +dotnet build -c Release +dotnet test -c Release +dotnet pack -c Release -o ./bin/nuget # only on release-shaped changes +``` + +For any code change that touches public API: + +- diff `src/ShellSyntaxTree/` headers against `SPEC.md` §2 — they must + match field-for-field. + +For corpus changes: + +- the PII audit test (a `[Fact]` that scans `tests/.../Corpus/bash/*.json` + for the SPEC §14 forbidden patterns) must pass. + +## Continuous Improvement Rule + +- If a workflow or correction repeats twice → extract or refine a skill or + add a row to `TOOLING.md`. +- If volatile project knowledge surfaces → put it in `PROJECT_CONTEXT.md` + or `IMPLEMENTATION_PLAN.md`, not here. +- This file should rarely change. Constitution edits are a deliberate act + — they ripple to every future agent run. +- Compressing a referenced authority (SPEC, dotnet-skills, etc.) into a + rule here is a *retrieval* operation, not a *memory* operation. If you + can't write the rule without losing a distinction the source draws, + drop the rule and link to the source instead. + +## Handy Commands + +```bash +# Restore and build everything +dotnet tool restore && dotnet restore && dotnet build -c Release + +# Run the full test+corpus suite +dotnet test -c Release + +# Pack locally to validate package metadata +dotnet pack -c Release -o ./bin/nuget + +# Cut a release (manual, then push the tag) +git tag v0.1.0-alpha && git push origin v0.1.0-alpha +``` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index bc17372..fdea59e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,48 +1,44 @@ - Copyright © 2015-$([System.DateTime]::Now.Year) Akka.NET Team - Akka.NET Team - akka, akka.streams, kafka, akkadotnet, akka streams, akka.streams.kafka + Copyright © 2026-$([System.DateTime]::Now.Year) Aaron Stannard + Aaron Stannard + Aaron Stannard $(NoWarn);CS1591 latest enable true - 1.0.0 - Example release notes + 0.1.0 + alpha - + netstandard2.0 - net6.0 - net9.0 + net8.0 + net10.0 - - - - - - - + + + + + + - + - 1.5.25 June 17 2024 - -* [Updated Akka.NET to 1.5.25](https://github.com/akkadotnet/akka.net/releases/tag/1.5.25) - akka;actors;actor model;Akka;concurrency;test - https://github.com/akkadotnet/Akka.MultiNodeTestRunner + bash;shell;parser;ast;security;agent;syntax-tree + https://github.com/Aaronontheweb/ShellSyntaxTree + https://github.com/Aaronontheweb/ShellSyntaxTree + git Apache-2.0 - akkalogo.png README.md + icon.png true - true - true snupkg - \ No newline at end of file + diff --git a/Directory.Packages.props b/Directory.Packages.props index ab3f264..7aedbe6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,17 +1,19 @@ true - - 1.5.63 + + 2.9.2 + 2.8.2 - + - - + + + + - - + - + - \ No newline at end of file + diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..f2d4c80 --- /dev/null +++ b/IMPLEMENTATION_PLAN.md @@ -0,0 +1,164 @@ +# Implementation Plan — ShellSyntaxTree + +Source of truth for active work. Translates `SPEC.md` §16 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. + +--- + +## NOW (v0.1.0-alpha shipping path) + +### 1. Bootstrap projects + +- [ ] Create `src/ShellSyntaxTree/ShellSyntaxTree.csproj` (library, + multi-target `netstandard2.0;net8.0`) +- [ ] Create `tests/ShellSyntaxTree.Tests/ShellSyntaxTree.Tests.csproj` + (xunit, target `net10.0`) +- [ ] Add both to `ShellSyntaxTree.slnx` +- [ ] `dotnet build` clean, `dotnet test` clean (zero tests OK) + +### 2. Public API skeleton (lock surface first) + +- [ ] Implement public types from `SPEC.md` §2 verbatim: + `IShellParser`, `BashParser`, `BashParserOptions`, `ParsedCommand`, + `Clause`, `VerbChain`, `Arg`, `Redirect`, and the three enums +- [ ] `BashParser.Parse` throws `NotImplementedException` for now +- [ ] `Arg` includes `IsCwdAttribution` per SPEC §9 +- [ ] Public API snapshot test (a single test that asserts each public + member exists with the expected shape — fast feedback when the + surface drifts) + +### 3. BashLexer (SPEC §5) + +- [ ] Token kinds: WORD, QUOTED_STRING, OPERATOR, WHITESPACE, + CONTINUATION +- [ ] Quote handling (single literal, double with `\"`, `\\`, `\$`) +- [ ] Escape handling outside quotes +- [ ] Operator boundaries (no whitespace required) +- [ ] Heavy unit tests on tokenization + +### 4. Verb tables (SPEC §6, data only) + +- [ ] `BashArity` (multi-token verbs) +- [ ] `CwdVerbs` (`cd`, `chdir`, `popd`, `pushd`, ...) +- [ ] `FileVerbs` (cat, grep, find, ls, ...) +- [ ] `FlagsWithValue` (per-verb flags that consume the next token) +- [ ] Probe order: longest-match-first when joining 1, 2, 3 tokens + +### 5. BashParser core (SPEC §4) + +- [ ] Compound splitting on `&&`, `||`, `;`, `|` +- [ ] Verb chain extraction using `BashArity` +- [ ] Args + redirects per clause +- [ ] Subshell `( ... )` handling +- [ ] `bash -c "..."` recursion (capped at depth 5) +- [ ] Anomaly safe-fail (SPEC §11): never throw on well-formed input + +### 6. Resolver (SPEC §8) + +- [ ] Tilde + `$HOME` expansion against `BashParserOptions.HomeDirectory` +- [ ] All other `$VAR` / `${VAR}` → `DynamicSkip` +- [ ] `filesystem::/path` prefix stripping +- [ ] Glob detection (don't expand) +- [ ] Relative path joining against `BashParserOptions.WorkingDirectory` +- [ ] `LooksLikePath` heuristic per §8 + +### 7. Per-verb path-arg rules (SPEC §7) + +- [ ] Default: every non-flag positional after the verb chain is a path +- [ ] Per-verb overrides: `chmod`, `chown`, `chgrp`, `ln`, `find`, + `grep`, `rg`, `sed`, `awk`, `tar`, `curl`/`wget`, `scp`/`rsync`, + `cd`-family +- [ ] Flag-with-value handling (`-o file`, `git -C /repo`, `--output=file`) + +### 8. cd-in-compound propagation (SPEC §9) + +- [ ] First-clause `cd` sets attributed cwd for the compound +- [ ] Subsequent clauses receive synthetic `Arg` with + `IsCwdAttribution=true` +- [ ] Subsequent `cd` in the same compound replaces attribution +- [ ] Subshell boundaries reset attribution + +### 9. Subshell + bash -c surfacing (SPEC §10) + +- [ ] Flatten subshell clauses into parent's `Clauses` with + `IsSubshell=true` +- [ ] Surface `bash -c` inner clauses inline with `IsBashCWrapped=true` +- [ ] Recursion-depth cap + +### 10. Hand-authored corpus (SPEC §13 — minimum 105 entries) + +- [ ] 10 simple-verb cases +- [ ] 10 multi-token-verb cases +- [ ] 15 compound cases +- [ ] 10 cd-in-compound cases +- [ ] 10 quote-handling cases +- [ ] 10 redirect cases +- [ ] 10 subshell cases +- [ ] 10 `bash -c` cases +- [ ] 10 dynamic-skip cases +- [ ] 10 per-verb path-rule cases +- [ ] 10 unparseable cases + +### 11. Corpus runner test + +- [ ] Single `[Theory] [MemberData]` enumerating + `tests/.../Corpus/bash/*.json` +- [ ] Per-entry test name so failures point at the specific case +- [ ] Structural equality helper `AstAssert.Equal` + +### 12. PII audit (SPEC §14) + +- [ ] Single `[Fact]` that scans `tests/.../Corpus/bash/*.json` for + SPEC §14 forbidden patterns +- [ ] Wired into `pr_validation.yml` via `dotnet test` (no separate job) + +### 13. Release v0.1.0-alpha + +- [ ] `RELEASE_NOTES.md` updated with v0.1.0-alpha section +- [ ] Tag `v0.1.0-alpha`; verify `publish_nuget.yml` produces and pushes + the package + +### 14. Netclaw integration smoke (SPEC §17 #7-#8) + +- [ ] Add `` to a Netclaw + project; verify `IShellParser` resolves in DI +- [ ] One Netclaw integration test exercises a real corpus entry through + the live matcher and gets the expected gate decision + +### 15. NuGet package icon + +- [ ] Generate a fitting icon via `/generate-image` (HCTI) +- [ ] Place at `assets/icon.png`; wire into `Directory.Build.props` + (`PackageIcon` + a packed `` item) +- [ ] Re-pack to validate icon embeds + +--- + +## NEXT (v0.1.x — additive, post-alpha) + +- Seed 50–100 corpus entries from sanitized real-world dogfood logs + (SPEC §14 workflow) +- Expand verb tables as corpus surfaces real commands +- Document the "consumer's algorithm" — given a `ParsedCommand`, here is + how a security gate walks it (likely a section in `SPEC.md` Appendix + or a separate `docs/CONSUMER_GUIDE.md`) +- Performance sanity check (~1 ms typical) with a tiny BenchmarkDotNet + harness — only if anything in the daemon hot path complains + +## LATER (v0.2+ — out of v0.1 scope) + +- PowerShell parser (`PwshParser : IShellParser`) — first time we exercise + the multi-shell seam +- Windows `cmd` parser +- Source-mapping (line/column on AST nodes) — only if an IDE consumer + asks +- Heredoc body extraction, process substitution, function definitions — + only if a real consumer need surfaces + +## Parked + +*(empty; move items here when scope changes rather than deleting them)* diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md new file mode 100644 index 0000000..0c05623 --- /dev/null +++ b/PROJECT_CONTEXT.md @@ -0,0 +1,160 @@ +# Project Context — ShellSyntaxTree + +**Repository:** `Aaronontheweb/ShellSyntaxTree` +**License:** Apache-2.0 +**Owner:** Aaron Stannard ([@Aaronontheweb](https://github.com/Aaronontheweb)) + +## What ShellSyntaxTree Is + +ShellSyntaxTree is a focused .NET library that **parses shell command strings +into a structured AST** for downstream policy / security gate evaluation. It +is a *parser*, not an interpreter — it never executes, expands, or evaluates +commands. + +The output is a `ParsedCommand` containing: + +- one or more `Clause` records (split on `&&`, `||`, `;`, `|`) +- per-clause verb chain (multi-token verbs like `git push`, `docker compose up`) +- per-clause args with `IsPath` classification, `Resolved` absolute path + when known, and explicit `DynamicSkip` marking for unresolved env vars + / unexpanded globs +- redirect operators (`>`, `>>`, `<`, `2>`, `2>>`) +- `cd && cmd` propagation — the cd target is attributed to subsequent + clauses in the same compound +- recursion into `bash -c ""` so wrapped commands surface as clauses +- subshell `( ... )` isolation for `cd` attribution +- safe-fail flag `IsUnparseable` for unsupported constructs (control flow, + function definitions, process substitution, deep `bash -c` nesting, + unbalanced quotes/parens) + +The complete contract lives in [`SPEC.md`](./SPEC.md). + +## Who It's For + +**Primary consumer: [Netclaw](https://github.com/netclaw-dev/netclaw)** — an +open-source autonomous operations agent. Netclaw's approval gate currently +hand-rolls equivalent functionality in +`src/Netclaw.Security/ShellApprovalSemantics.cs` and `ShellTokenizer.cs`. +ShellSyntaxTree v0.1 is intended to **replace that hand-rolled approximation** +with a richer, structured AST that Netclaw's gate can walk directly. + +**Acceptance is tied to Netclaw integration** (see SPEC §17 #7-#8): the +package must be consumable via `` and exercise at least +one corpus entry through Netclaw's live matcher. + +**Secondary consumers:** any tool that needs to reason about the shape of +agent-emitted shell commands without executing them — pre-commit hooks, +audit pipelines, sandbox policy engines, IDE security extensions. + +## Why It Exists + +LLM-driven agents emit shell commands. Naive substring matching is unsafe +(`rm /tmp/foo` vs `rm -rf $HOME/foo`); shelling out to `bash -n` is unsafe +and doesn't yield AST nodes; tree-sitter-bash is overkill and ships native +binaries. ShellSyntaxTree fills the middle: a hand-rolled, AOT-friendly, +zero-native-deps .NET parser sized to what security gates actually need. + +## Scope Discipline + +### v0.1 (current) + +- Bash only. PowerShell and Windows `cmd` are deferred — but the + `IShellParser` seam is in place so consumers don't have to refactor. +- Public API surface in SPEC §2 is **locked**. Internal changes are free. +- Acceptance is the corpus contract (SPEC §13): every JSON entry parses to + its expected AST. + +### Explicit non-goals (v0.1) + +- Command execution. +- Variable expansion of any kind (we **mark** dynamic tokens, never resolve + them; `$HOME` is the only exception). +- Heredoc body extraction. +- Process substitution `<(cmd)`, `>(cmd)`. +- Function definitions, `for`/`while`/`case` control flow, arithmetic + expansion `$((...))`. +- Performance optimization beyond "fast enough to invoke per shell call + without noticeable latency" (~1 ms typical). +- Source-mapping (line/column for AST nodes — useful for IDEs, irrelevant + for security gates). + +### Versioning + +- `v0.1.0-alpha` — first publishable cut, bash-only. +- `v0.1.x` — additive (more verb table entries, more corpus, bug fixes). +- `v0.2.0` — first PowerShell parser implementation. +- `v1.0.0` — at least one external consumer beyond Netclaw ships against it + without finding API gaps. + +## Architectural Constraints + +- **Public API in `SPEC.md` §2 is the contract.** Everything else is + `internal`. Renaming/removing public fields requires a major version bump. +- **`IShellParser` is the multi-shell seam.** PowerShell and `cmd` parsers + must be addable without touching consumer code. +- **No native dependencies.** AOT-trim friendly; ship a single managed + package. +- **Multi-targeting**: `netstandard2.0` for broad consumer reach, `net8.0` + for modern runtimes, tests on `net10.0`. +- **Security defaults bias**: when in doubt, mark `DynamicSkip` / + `IsUnparseable`. Consumers can always relax — they cannot retroactively + un-execute a command we falsely classified as safe. + +## Acceptance for v0.1.0-alpha + +Per SPEC §17, all of the following must be true: + +1. Public API matches SPEC §2 exactly. `dotnet pack` produces a + `ShellSyntaxTree.0.1.0-alpha.nupkg`. +2. Every corpus entry in `tests/Corpus/bash/*.json` parses to its expected + AST. `dotnet test` runs them all and passes. +3. Corpus has ≥ 105 entries spanning the SPEC §13 categories. +4. PII audit scan over `tests/Corpus/bash/*.json` finds zero hits. +5. PR validation runs on GitHub Actions and passes. +6. Tagging `v0.1.0-alpha` triggers `publish_nuget.yml` and the package + appears on nuget.org. +7. Netclaw consumes the package via `` and `IShellParser` + resolves at runtime in Netclaw's DI container. +8. At least one Netclaw integration test exercises a real corpus entry + through Netclaw's matcher and produces the expected gate decision. + +## Key Pain Points and Risks + +- **Corpus PII**. Real-world bash corpus entries seed from agent dogfood + logs (`~/.netclaw/logs/daemon-*.log`) that contain usernames, repo paths, + channel/thread IDs. Sanitization is mandatory and gated by a CI scan + (SPEC §14). Shipping unsanitized PII is a release-blocker. +- **Verb table drift**. `BashArity`, `FileVerbs`, `CwdVerbs`, and + `FlagsWithValue` are static data. Adding entries is cheap; *missing* + entries means the parser silently mis-classifies. The corpus is the + early-warning system. +- **`bash -c` recursion depth**. Capped at 5 (SPEC §10). Going deeper + marks the deepest clause `IsUnparseable` so we don't blow the stack on + hostile inputs. +- **Resolver assumptions**. `WorkingDirectory` defaults to the daemon's + cwd. If consumers pass the wrong cwd, relative-path attribution will + silently disagree with what bash would do at runtime. Document loudly. +- **API surface lock**. v0.1 commits to the AST shape. Mistakes here cost + a major-version bump to fix. + +## Where Things Live + +| Concern | Location | +|---|---| +| Library source | `src/ShellSyntaxTree/` *(to be created)* | +| Tests + corpus | `tests/ShellSyntaxTree.Tests/` *(to be created)* | +| Corpus entries | `tests/ShellSyntaxTree.Tests/Corpus/bash/*.json` | +| The contract | `SPEC.md` | +| Active work plan | `IMPLEMENTATION_PLAN.md` | +| Tooling inventory | `TOOLING.md` | +| Agent constitution | `AGENTS.md` (and `CLAUDE.md`) | +| CI | `.github/workflows/{pr_validation,publish_nuget}.yml` | +| Build settings | `Directory.Build.props`, `Directory.Packages.props`, `global.json` | +| Solution | `ShellSyntaxTree.slnx` | + +## Update Discipline + +This file is **mutable** but should change only when the project's purpose, +audience, scope, or constraints actually shift. Day-to-day work tracking +belongs in `IMPLEMENTATION_PLAN.md`. Tooling changes belong in `TOOLING.md`. +The agent constitution (`AGENTS.md`) should rarely change. diff --git a/README.md b/README.md index 73b4795..b093a0c 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,84 @@ -# build-system-template -Akka.NET project build system template that provides standardized build and CI/CD configuration for all Akka.NET projects. +# ShellSyntaxTree -## Build System Overview -This repository contains our standardized build system setup that can be used across all Akka.NET projects. Here are the key components and practices we follow: +A focused .NET library that parses shell command strings into a structured AST. +Purpose-built for **security gate evaluators** — tools that inspect agent-emitted +shell commands and decide whether to allow, prompt for, or deny execution. -### CI/CD Configuration -We primarily use GitHub Actions for our CI/CD pipelines, but also maintain Azure DevOps pipeline examples. You can find the configuration examples in: -- `.github/workflows/` - GitHub Actions pipeline examples -- `.azuredevops/` - Azure DevOps pipeline examples +ShellSyntaxTree is **not** a shell interpreter. It does not execute, expand, +or evaluate commands. It returns an AST (verb chain, args with path +classification, redirects, compound operators, `cd`-in-compound propagation, +`bash -c` recursion) that consumers walk to make policy decisions. -### SDK Version Management -We use `global.json` to pin the .NET SDK version for both CI/CD environments and local development. This ensures consistent builds across all environments and developers. +The original consumer is [Netclaw](https://github.com/netclaw-dev/netclaw)'s +approval policy. Any tool that needs to reason about the shape of an +agent-emitted shell command — without running it — is welcome to consume it. -### .NET Tools -We use local .NET tools to enhance our build and documentation process. The tools are configured in `.config/dotnet-tools.json` and include: +## Status -- [Incrementalist](https://github.com/petabridge/Incrementalist) (v1.0.0-beta4) - Used for determining which projects need to be rebuilt based on Git changes -- [DocFx](https://dotnet.github.io/docfx/) (v2.78.3) - Used for generating documentation +**v0.1 — pre-alpha.** Public API surface and behavior are specified in +[`SPEC.md`](./SPEC.md). Implementation is in progress. -To restore these tools in your local environment, run: -```powershell -dotnet tool restore -``` +## Why not `tree-sitter-bash`? + +Native dependencies, AOT trim concerns, and IDE-grade fidelity we don't need. +We want unsupported constructs to mark `IsUnparseable = true` so consumers +route to safe-fail. See [`SPEC.md` Appendix B](./SPEC.md) for the full +trade-off analysis. -This command is automatically executed in our CI/CD pipelines (both GitHub Actions and Azure DevOps) to ensure tools are available during builds. +## Public API surface (locked for v0.1) -### Centralized Package and Build Management -We utilize two key MSBuild files for centralized configuration: +```csharp +namespace ShellSyntaxTree; -1. `Directory.Packages.props` - Implements [Central Package Version Management](https://learn.microsoft.com/nuget/consume-packages/Central-Package-Management) for consistent NuGet package versions across all projects in the solution. +public interface IShellParser { ParsedCommand Parse(string command); } +public sealed class BashParser : IShellParser { /* ... */ } +public sealed record BashParserOptions { /* HomeDirectory, WorkingDirectory */ } -2. `Directory.Build.props` - Defines common build properties, including: - - Copyright and author information - - Source linking configuration - - NuGet package metadata - - Common compiler settings - - Target framework definitions +public sealed record ParsedCommand { /* Source, Clauses, IsUnparseable, ... */ } +public sealed record Clause { /* Operator, Verb, Args, Redirects, ... */ } +public sealed record VerbChain { /* Tokens */ } +public sealed record Arg { /* Raw, Resolved, Kind, IsPath, ... */ } +public sealed record Redirect { /* Direction, Target */ } -### Code Coverage Configuration -The `coverlet.runsettings` file configures code coverage collection using Coverlet, with settings for: -- Multiple coverage report formats (JSON, Cobertura, LCOV, TeamCity, OpenCover) -- Test assembly exclusions -- Source linking integration -- Performance optimizations +public enum ArgKind { Literal, EnvVar, Glob, Tilde, DynamicSkip } +public enum RedirectDirection { In, Out, Append, ErrOut, ErrAppend } +public enum CompoundOperator { None, AndIf, OrIf, Sequence, Pipe } +``` + +PowerShell and Windows `cmd` parsers are deferred to later versions; the +`IShellParser` seam is already in place so consumers don't have to refactor +when they ship. -### Release Management -Our release process is streamlined through: -- `RELEASE_NOTES.md` - Contains version history and release notes -- `build.ps1` - PowerShell script that processes release notes and updates version information -- Supporting scripts in `/scripts`: - - `bumpVersion.ps1` - Updates version numbers - - `getReleaseNotes.ps1` - Parses release notes +## Multi-targeting -The build system primarily relies on standard `dotnet` CLI commands, with the PowerShell scripts mainly handling release note processing and version management. +`netstandard2.0` for broad consumer compatibility, `net8.0` for modern +runtimes. Tests target `net10.0`. -### Solution Format -We prefer the new `.slnx` XML-based solution format over the traditional `.sln` format. This requires .NET 9 SDK or later. The new format is more concise and easier to work with. You can migrate existing solutions using: +## Repository layout -```powershell -dotnet sln migrate ``` +src/ShellSyntaxTree/ # library (TBD) +tests/ShellSyntaxTree.Tests/ # xunit tests + corpus runner (TBD) +tests/ShellSyntaxTree.Tests/Corpus/ # JSON test corpus (acceptance contract) +SPEC.md # the implementation specification +PROJECT_CONTEXT.md # what this is, who it serves +TOOLING.md # available tooling and how to access it +AGENTS.md / CLAUDE.md # agent operating constitution +IMPLEMENTATION_PLAN.md # NOW / NEXT / LATER work tracker +``` + +## Building + +```bash +dotnet tool restore +dotnet build -c Release +dotnet test -c Release +dotnet pack -c Release -o ./bin/nuget +``` + +`global.json` pins the SDK version. Targeting requires .NET 10 SDK or later +(`.slnx` solution format). -For more information about the new `.slnx` format, see the [official announcement](https://devblogs.microsoft.com/dotnet/introducing-slnx-support-dotnet-cli/). +## License -## Getting Started -1. Ensure you have the correct .NET SDK version installed (check `global.json`) -2. Clone this repository -3. Run `dotnet build` to verify the build system -4. Customize the configuration files for your specific project needs +[Apache-2.0](./LICENSE). Copyright © 2026 Aaron Stannard. diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..0fd2e36 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,1208 @@ +# ShellSyntaxTree — v0.1 Specification + +**Status:** Draft for v0.1. Approved decisions; implementation pending. +**Audience:** Whoever (human or agent) implements ShellSyntaxTree v0.1. +**Read this end-to-end before writing any code.** + +This document specifies the public API, AST, grammar, verb tables, resolver +semantics, and corpus contract for ShellSyntaxTree v0.1. The library is a +focused bash command parser designed for **security gate evaluators** — +tools that inspect agent-emitted shell commands to decide whether to allow, +prompt for, or deny execution. + +It is **not** a general-purpose shell interpreter. It does not execute, +expand, or evaluate commands. It returns a structured AST that consumers +walk to make decisions. + +The original consumer is [Netclaw](https://github.com/Aaronontheweb/netclaw)'s +approval policy. The library is designed to be reusable beyond Netclaw — +any tool that needs to reason about the shape of an agent-emitted bash +command can consume it. + +--- + +## 1. Goals & Non-Goals + +### Goals (v0.1) + +1. **Parse bash commands into a structured AST** with per-clause verbs, + args, redirects, and compound operators. +2. **Extract paths a command operates on** with per-verb knowledge of which + positional args are paths vs flags vs literal values (`chmod 755 file` + knows `755` is a mode). +3. **Honor `cd && cmd` propagation** within a compound — `` + counts as a path each subsequent command operates on. +4. **Recurse into `bash -c ""`** so the inner command is parsed and + its clauses surface to the consumer. +5. **Mark dynamic-content tokens** (unresolved `$VAR`, unexpanded globs) + so consumers don't misextract literal `$VAR/foo` as a path. +6. **Multi-shell-ready** via `IShellParser` interface — bash is the only + v0.1 implementation; PowerShell and cmd are deferred to later versions + without breaking the seam. + +### Non-Goals (v0.1) + +- PowerShell parsing (deferred; interface seam is present). +- Windows cmd parsing (deferred). +- Command execution. The library never runs anything. +- Variable expansion. We mark dynamic tokens, never resolve them. +- Function definitions, here-docs body extraction, complex parameter + expansion (`${var//pattern/replacement}`), arithmetic expansion. +- Performance tuning beyond "fast enough to invoke per shell call without + noticeable latency" (~1ms per typical input). + +--- + +## 2. Public API Surface + +The package exposes a small surface from a single namespace +`ShellSyntaxTree`. **Public types only**: + +```csharp +namespace ShellSyntaxTree; + +/// +/// Parses shell command strings into structured ASTs. +/// +public interface IShellParser +{ + /// + /// Parse the command. Always returns a ParsedCommand; sets + /// when the input cannot + /// be tokenized (unbalanced quotes, etc.). Never throws on + /// well-formed strings; throws ArgumentNullException on null input. + /// + ParsedCommand Parse(string command); +} + +/// Bash implementation of IShellParser. +public sealed class BashParser : IShellParser +{ + public BashParser(); + public BashParser(BashParserOptions options); + public ParsedCommand Parse(string command); +} + +/// Configuration knobs for BashParser. +public sealed record BashParserOptions +{ + /// + /// User home directory used to expand `~` and `$HOME` tokens during + /// resolution. Defaults to . + /// + public string? HomeDirectory { get; init; } + + /// + /// Working directory used to resolve relative path tokens during + /// resolution. Defaults to the daemon-process cwd. + /// + public string? WorkingDirectory { get; init; } +} + +// AST records — see §3. +public sealed record ParsedCommand { ... } +public sealed record Clause { ... } +public sealed record VerbChain { ... } +public sealed record Arg { ... } +public sealed record Redirect { ... } +public enum ArgKind { Literal, EnvVar, Glob, Tilde, DynamicSkip } +public enum RedirectDirection { In, Out, Append, ErrOut, ErrAppend } +public enum CompoundOperator { None, AndIf, OrIf, Sequence, Pipe } +``` + +That's the entire public API. **Everything else is internal.** The lexer, +parser internals, verb tables, resolver — all implementation detail. + +--- + +## 3. AST Reference + +### `ParsedCommand` + +The top-level result of parsing. Always returned (never null). + +```csharp +public sealed record ParsedCommand +{ + /// The original input string, verbatim. + public string Source { get; init; } = ""; + + /// + /// Top-level clauses, split on compound operators (&&, ||, ;, |). + /// For a simple command, exactly one clause with Operator=None. + /// + public IReadOnlyList Clauses { get; init; } = []; + + /// + /// True when the parser could not produce a clean AST (unbalanced + /// quotes, unparseable construct). When true, Clauses MAY be partial + /// or empty. Consumers should route to safe-fail. + /// + public bool IsUnparseable { get; init; } + + /// + /// Human-readable diagnostic when IsUnparseable=true; null otherwise. + /// + public string? UnparseableReason { get; init; } +} +``` + +### `Clause` + +One logical command within a compound. Each clause has its own verb chain, +args, redirects, and the operator that joined it to the previous clause. + +```csharp +public sealed record Clause +{ + /// + /// The operator joining this clause to the previous one. The first + /// clause in a ParsedCommand has Operator=None. Subsequent clauses + /// carry the operator that preceded them in the source + /// (e.g. `a && b` produces clauses [{None,a}, {AndIf,b}]). + /// + public CompoundOperator Operator { get; init; } + + /// The verb chain (see §3.3 and §6). + public VerbChain Verb { get; init; } = new(); + + /// + /// All argument tokens after the verb chain, in source order. Includes + /// flags and positional args. See for token kind. + /// + public IReadOnlyList Args { get; init; } = []; + + /// + /// Redirect operators on this clause (>, >>, <, 2>, 2>>). Each entry + /// includes direction and target path. + /// + public IReadOnlyList Redirects { get; init; } = []; + + /// + /// True when this clause is wrapped in a subshell (parens). Subshells + /// isolate cd state — see §9. + /// + public bool IsSubshell { get; init; } + + /// + /// True when this clause is the result of recursing into a `bash -c` + /// or `sh -c` wrapper. Useful for consumers that want to surface + /// "this came from a wrapped invocation" in UI. + /// + public bool IsBashCWrapped { get; init; } +} +``` + +### `VerbChain` + +The verb of a clause. Multi-token to handle commands like `git push`, +`docker compose up`, `bun run`, `dotnet test`. Length determined by the +`BashArity` table (see §6). + +```csharp +public sealed record VerbChain +{ + /// + /// Verb tokens in source order. Empty when the clause has no verb + /// (e.g. clause is just a redirect or an empty fragment). + /// + public IReadOnlyList Tokens { get; init; } = []; + + /// Convenience: tokens joined with spaces. + public string Joined => string.Join(' ', Tokens); +} +``` + +### `Arg` + +One argument token after the verb chain. Includes resolution state. + +```csharp +public sealed record Arg +{ + /// Verbatim token from the source. + public string Raw { get; init; } = ""; + + /// + /// Resolved value for path tokens — tilde expanded, env vars + /// substituted, normalized to absolute path against + /// BashParserOptions.WorkingDirectory. Null when Kind is not a path + /// (Literal non-path / Glob / DynamicSkip). + /// + public string? Resolved { get; init; } + + /// Token kind. See . + public ArgKind Kind { get; init; } + + /// + /// True when this token starts with '-' or '--' (a flag, not a + /// positional arg). + /// + public bool IsFlag => Raw.StartsWith('-'); + + /// + /// True when this token is a path the clause operates on (per the + /// per-verb pathArgs table; see §7). Set during parsing so consumers + /// don't reapply per-verb rules. + /// + public bool IsPath { get; init; } +} + +public enum ArgKind +{ + /// Literal value (string, number, flag). + Literal, + /// Token containing an unresolved env var reference. + EnvVar, + /// Token containing glob metachars (* ? [). + Glob, + /// Token starting with ~ (tilde). + Tilde, + /// + /// Token whose value cannot be safely resolved (unresolved env var, + /// unexpandable glob). Consumers SHALL treat as "no value extracted" + /// rather than using Raw as a literal path. + /// + DynamicSkip +} +``` + +### `Redirect` + +```csharp +public sealed record Redirect +{ + public RedirectDirection Direction { get; init; } + /// Target path (resolved per Arg conventions). + public string Target { get; init; } = ""; + /// True when target is a dynamic token (skip). + public bool IsDynamicSkip { get; init; } +} + +public enum RedirectDirection +{ + In, // < + Out, // > + Append, // >> + ErrOut, // 2> + ErrAppend // 2>> +} +``` + +### `CompoundOperator` + +```csharp +public enum CompoundOperator +{ + None, // first clause; no prior operator + AndIf, // && + OrIf, // || + Sequence, // ; + Pipe // | +} +``` + +--- + +## 4. Grammar + +Approximate BNF for what the parser accepts. Anything outside this grammar +is unparseable (`ParsedCommand.IsUnparseable = true`). + +``` +command := clause (compound_op clause)* +compound_op := "&&" | "||" | ";" | "|" +clause := subshell | bash_c_wrapper | simple_clause +subshell := "(" command ")" +bash_c_wrapper := ("bash" | "sh") "-c" QUOTED_STRING +simple_clause := verb_chain arg* redirect* +verb_chain := word{1..N} // N = BashArity[word_0] +arg := word | flag | quoted_string +flag := "-" letter+ | "--" word +redirect := redirect_op target +redirect_op := ">" | ">>" | "<" | "2>" | "2>>" +target := word | quoted_string +word := non-whitespace, non-operator characters +quoted_string := single-quoted | double-quoted +``` + +**Notes:** + +- Whitespace between tokens is one or more spaces or tabs. +- `\` followed by a newline is a line continuation (treat as whitespace). +- `\` before a metachar inside a double-quoted string escapes the metachar. +- Single-quoted strings preserve all bytes literally — no escape processing. +- Heredocs (`<`, `>>`, `<`, `2>`, `2>>`, + `(`, `)`, `<<`. +- **WHITESPACE** — one or more spaces or tabs. Discarded after splitting. +- **CONTINUATION** — `\` + `\n`. Treated as whitespace. + +### Quote handling + +- **Single quotes** `'...'` preserve bytes literally. No escape processing, + no variable expansion. Anything inside is one token. +- **Double quotes** `"..."` preserve whitespace but allow: + - `\"` escapes the closing quote. + - `\\` escapes a backslash. + - `\$` escapes a dollar sign. + - `$VAR` and `${VAR}` are recognized as env var references but **not + expanded** — the token is marked `ArgKind.EnvVar` (or `DynamicSkip` + if resolution would be required for path classification). +- Unbalanced quotes → `IsUnparseable = true` with reason + `"unbalanced quote at position N"`. + +### Escape handling + +- `\X` outside quotes: removes the backslash, takes X literally. Example: + `echo \$HOME` produces token `$HOME` with `ArgKind.Literal`. +- `\X` inside double quotes: only `\"`, `\\`, `\$`, `\\`, and `\\`+newline + are recognized escape sequences. Other backslashes preserved literally. + +### Operator boundaries + +Operators terminate the current token. `cd /tmp&&ls` lexes as +`[cd, /tmp, &&, ls]` — no whitespace required around operators. The lexer +must handle this. + +--- + +## 6. Verb Tables + +These are **data**, not logic. Implement as `static readonly` collections. + +### 6.1 BashArity + +How many tokens form the verb chain for known commands. Defaults to 1 +when not in the table. + +```csharp +internal static readonly IReadOnlyDictionary BashArity = + new Dictionary(StringComparer.OrdinalIgnoreCase) +{ + // Two-token verbs + ["git"] = 2, // git push, git log, git checkout + ["dotnet"] = 2, // dotnet test, dotnet build + ["npm"] = 2, // npm install, npm run + ["yarn"] = 2, // yarn add, yarn install + ["pnpm"] = 2, // pnpm add, pnpm install + ["cargo"] = 2, // cargo build, cargo test + ["go"] = 2, // go build, go test, go run + ["kubectl"] = 2, // kubectl apply, kubectl get + ["helm"] = 2, // helm install, helm upgrade + ["systemctl"] = 2, // systemctl start, systemctl status + ["service"] = 2, // service nginx + ["pip"] = 2, // pip install, pip uninstall + ["pip3"] = 2, // pip3 install + ["brew"] = 2, // brew install, brew upgrade + ["apt"] = 2, // apt install, apt update + ["apt-get"] = 2, // apt-get install + ["yum"] = 2, // yum install + ["dnf"] = 2, // dnf install + ["pacman"] = 2, // pacman -S, pacman -Syu (the -S is the verb) + ["aws"] = 2, // aws s3, aws ec2 (top-level) + ["gcloud"] = 2, // gcloud compute, gcloud auth + ["az"] = 2, // az vm, az storage + + // Three-token verbs + ["docker"] = 2, // docker run, docker ps; "docker compose up" handled below + ["docker compose"] = 3, // docker compose up, docker compose down + ["docker-compose"] = 2, // legacy single-token form + ["bun"] = 2, // bun run, bun install + ["bun run"] = 3, // bun run my-script (treat as 3-tuple verb) + ["nuget"] = 2, // nuget push, nuget pack +}; +``` + +The table is not exhaustive. Missing verbs default to 1 token. Add entries +as the corpus surfaces real commands. + +**Implementation note:** The parser must look up multi-token verbs by +joining the first 1, 2, then 3 tokens and probing the table from longest +to shortest. So `docker compose up nginx` first probes `docker compose up` +(not in table; arity defaults wouldn't fire), then probes `docker compose` +(in table → arity 3) → verb chain is the first 3 tokens. + +### 6.2 CWD verbs + +Verbs whose first non-flag positional arg becomes the cwd for subsequent +clauses in the same compound (see §9). + +```csharp +internal static readonly HashSet CwdVerbs = + new(StringComparer.OrdinalIgnoreCase) +{ + "cd", "chdir", "popd", "pushd", + "push-location", "set-location" // PowerShell idioms (forward-compat) +}; +``` + +### 6.3 FILE verbs + +Verbs whose positional args are paths. The default extraction rule is "all +non-flag positional args after the verb chain are paths." Per-verb overrides +in §7. + +```csharp +internal static readonly HashSet FileVerbs = + new(StringComparer.OrdinalIgnoreCase) +{ + // CWD verbs are also FILE verbs (their target is a path) + "cd", "chdir", "popd", "pushd", "push-location", "set-location", + // File mutation + "rm", "cp", "mv", "mkdir", "rmdir", "touch", "ln", + "chmod", "chown", "chgrp", "stat", "test", + // Read + "cat", "less", "more", "head", "tail", "grep", "rg", + "find", "fd", "locate", "wc", "file", + // Editors / text tools + "sed", "awk", "vi", "vim", "nano", "emacs", "ed", + // Compression + "tar", "zip", "unzip", "gzip", "gunzip", "bzip2", "xz", + // Network with file targets + "curl", "wget", "scp", "rsync", "sftp", + // Shell / interpreter loaders + "bash", "sh", "zsh", "fish", + "python", "python3", "node", "ruby", "perl", "php", + // Diff / patch + "diff", "patch", "cmp", + // Listing + "ls", "dir", "tree", +}; +``` + +### 6.4 CMD_FILE verbs (Windows cmd; deferred for v0.1 implementation) + +Reserved for future PowerShell/cmd parser support. Document the table +shape now so the seam is clear. + +```csharp +internal static readonly HashSet CmdFileVerbs = + new(StringComparer.OrdinalIgnoreCase) +{ + "type", "copy", "move", "del", "erase", "ren", "ren", + "xcopy", "robocopy", "findstr", + // PowerShell cmdlets + "get-content", "set-content", "remove-item", "copy-item", + "move-item", "new-item", +}; +``` + +--- + +## 7. Per-Verb Path-Arg Extraction Rules + +The default rule for FILE verbs: every non-flag positional arg after the +verb chain is a path. Per-verb overrides: + +| Verb | Rule | +|---|---| +| `chmod` | First non-flag positional is **mode** (e.g. `755`, `+x`); rest are paths. | +| `chown` | First non-flag positional is **user[:group]**; rest are paths. | +| `chgrp` | First non-flag positional is **group**; rest are paths. | +| `ln` | All positionals are paths (source then target). | +| `find` | First positional is a path; rest are predicate args (skip). | +| `grep` | First positional is **pattern**; rest are paths. | +| `rg` | First positional is **pattern**; rest are paths. | +| `sed` | First positional is **script**; rest are paths. | +| `awk` | First positional is **program**; rest are paths. | +| `tar` | Action flag determines path roles; default to extracting all non-flag positionals as paths. | +| `curl`, `wget` | First positional is **URL**, not a path. `-o file` flag arg is a path. | +| `scp`, `rsync`, `sftp` | All positionals are paths (some remote). | +| `cd`, `chdir`, `pushd`, `popd` | First non-flag positional is the cwd target (a path). | +| Others (in FileVerbs, no override) | All non-flag positionals are paths. | + +### Flag-with-value handling + +Some flags take values (`-o file`, `-C /repo`, `--output=file`). The parser +must know which flags consume the next token as a value. Curated table: + +```csharp +internal static readonly IReadOnlyDictionary> + FlagsWithValue = new Dictionary>( + StringComparer.OrdinalIgnoreCase) +{ + ["git"] = new HashSet(StringComparer.OrdinalIgnoreCase) { "-C", "--git-dir", "--work-tree" }, + ["curl"] = new HashSet(StringComparer.OrdinalIgnoreCase) { "-o", "--output", "-d", "--data" }, + ["wget"] = new HashSet(StringComparer.OrdinalIgnoreCase) { "-O", "--output-document" }, + ["docker"]= new HashSet(StringComparer.OrdinalIgnoreCase) { "-v", "--volume", "-f", "--file" }, + ["tar"] = new HashSet(StringComparer.OrdinalIgnoreCase) { "-f", "--file", "-C", "--directory" }, + // Add as corpus surfaces real cases. +}; +``` + +When a flag-with-value consumes the next token, the consumed token's +`IsPath` flag is set if the value is path-shaped (per the resolver in §8). +For `git -C /repo log`: the `-C` flag consumes `/repo`, marks it as a +path, then the verb chain continues with `log`. + +`--output=file` (equals form) is parsed as one token; the path value after +`=` is extracted into a synthetic Arg with `IsPath=true`. + +--- + +## 8. Resolver + +For each Arg with potential path content, the resolver attempts to produce +a normalized absolute path. Resolution order: + +1. **Tilde expansion.** `~` → `BashParserOptions.HomeDirectory`. + `~/foo` → `/foo`. `~user` not supported → `DynamicSkip`. + +2. **Env-var substitution.** `$VAR` and `${VAR}` are **not expanded** + even if the value is in `Environment`. We treat any env var reference + as `DynamicSkip` because the env var available at parse time may + differ from what's available when the agent's command actually runs. + `$HOME` is the **only** exception — we treat it as equivalent to `~` + and expand it from `BashParserOptions.HomeDirectory`. + +3. **`filesystem::/path` prefix stripping.** Some tools emit + `filesystem::/path/to/file`; strip the prefix. Become `/path/to/file`. + +4. **Glob detection.** Tokens containing `*`, `?`, or `[` are marked + `ArgKind.Glob`. The resolver does **not** expand globs. The token + stays as-is in `Raw`; `Resolved` is null. Consumers that want the + glob's "covering directory" can use `Path.GetDirectoryName(Raw)` to + approximate. + +5. **Relative path resolution.** Tokens not starting with `/` (or `\\` on + Windows) are joined to `BashParserOptions.WorkingDirectory`. If + `WorkingDirectory` is null, the token stays relative and `Resolved` + is null with `Kind = DynamicSkip`. + +6. **Dynamic-skip predicates.** A token is `DynamicSkip` when: + - It contains an unresolved env var reference (other than `$HOME`). + - It contains glob metachars AND the resolver was asked for an + absolute resolution. + - Resolution throws an `IOException` or path-format exception. + + `DynamicSkip` tokens have `Resolved = null`. Consumers must not use + `Raw` as a literal path. + +### Path-shape heuristic + +When deciding whether a token "looks like a path" (used to decide whether +to apply the resolver): + +``` +LooksLikePath(token) = + token starts with '/' (Unix absolute) +|| token starts with '\\' or ':' (Windows absolute) +|| token starts with './' or '../' (Unix relative) +|| token starts with '~' (Tilde) +|| token contains '/' or '\\' anywhere +|| token ends with a known file extension (.json, .md, .txt, .conf, ...) +|| token is in the args of a FileVerb at a position the per-verb rule + marks as a path +``` + +The per-verb rule wins when present; the heuristic is the fallback. + +--- + +## 9. cd-in-Compound Propagation + +The agent's natural idiom is `cd /target && cmd1 && cmd2`. Bash semantics: +`cmd1` and `cmd2` execute with cwd `/target`. The parser honors this for +**path attribution within the same compound**. + +### Rules + +1. **First clause is a `cd`-family verb** (per §6.2): the cd target becomes + the **attributed cwd** for subsequent clauses in the same compound. +2. **Subsequent clauses inherit the attributed cwd** as if it were + prepended with `-C` semantics. Specifically: a synthetic `Arg` with + `IsPath=true`, `Resolved=`, and `Kind=Literal` is added to + each subsequent clause's `Args` list at the **end**, marked with a flag + `IsCwdAttribution=true` so consumers can distinguish it from + user-emitted args. + + *(Add `IsCwdAttribution: bool` to the `Arg` record. Default false.)* + +3. **A subsequent `cd`** in the same compound **replaces** the attributed + cwd for clauses after it. (`cd /a && cmd1 && cd /b && cmd2` → cmd1 + inherits `/a`, cmd2 inherits `/b`.) + +4. **Subshell boundaries reset attribution.** `cd /a && (cd /b && cmd1) && cmd2`: + cmd1 (inside subshell) inherits `/b`; cmd2 (outside subshell) inherits + `/a` (the subshell's `cd /b` does not leak out). + +5. **Attribution does not change the clause's verb or original args.** + The attribution is purely additive — the `cd` clause itself is still + parsed normally, and subsequent clauses retain everything the user + typed, plus the synthetic Arg. + +### Example + +Input: `cd /target && git -C /other log && cat file.txt` + +Parsed clauses: + +``` +Clause 0: Operator=None, Verb=[cd], Args=[/target] +Clause 1: Operator=AndIf, Verb=[git, log], + Args=[ + Arg{Raw="-C",IsFlag=true}, + Arg{Raw="/other",IsPath=true,Resolved="/other"}, + Arg{Raw="/target",IsPath=true,Resolved="/target",IsCwdAttribution=true} + ] +Clause 2: Operator=AndIf, Verb=[cat], + Args=[ + Arg{Raw="file.txt",IsPath=true,Resolved="/target/file.txt"}, + Arg{Raw="/target",IsPath=true,Resolved="/target",IsCwdAttribution=true} + ] +``` + +Note: `file.txt` in clause 2 resolves against the attributed cwd +`/target` to produce `/target/file.txt`. The attributed-cwd Arg is also +appended for completeness, even though the resolver already used it. + +Consumers can choose to ignore `IsCwdAttribution=true` args if they +already see the resolved path in another arg. + +--- + +## 10. Subshell & `bash -c` Recursion + +### Subshells + +Subshells are clauses wrapped in parens: `(cd /a && cmd)`. The parser +recognizes the parens, parses the inner command, and emits a single +clause with: + +- `Verb` = empty (a subshell has no verb of its own) +- `Args` = empty +- `IsSubshell = true` +- A nested `ParsedCommand` field — **but** since the AST should be flat + for consumer convenience, instead we **flatten** the subshell into the + parent's `Clauses` list with each inner clause's `IsSubshell=true`. + +Specifically: `(cd /b && cmd) && cmd2` produces three clauses: + +``` +Clause 0: Op=None, Verb=cd, Args=[/b], IsSubshell=true +Clause 1: Op=AndIf, Verb=cmd, Args=[/b attribution], IsSubshell=true +Clause 2: Op=AndIf, Verb=cmd2, Args=[] // no /b attribution — subshell isolated +``` + +### `bash -c` Recursion + +`bash -c "inner command"` and `sh -c "inner command"` are common wrappers +the agent emits. The parser: + +1. Recognizes the `bash -c` or `sh -c` prefix. +2. Parses the quoted argument as a fresh `ParsedCommand`. +3. Surfaces the inner command's clauses inline in the outer's `Clauses` + list, each with `IsBashCWrapped=true`. + +Example: `bash -c "cd /a && cmd"` produces: + +``` +Clause 0: Op=None, Verb=cd, Args=[/a], IsBashCWrapped=true +Clause 1: Op=AndIf, Verb=cmd, Args=[/a attribution], IsBashCWrapped=true +``` + +The outer `bash -c` itself does not appear as a clause — it's "consumed" +by the recursion. Consumers that care that this came from a wrapper can +inspect `IsBashCWrapped` on the surfaced clauses. + +**Recursion limit:** parse `bash -c "bash -c ..."` chains up to depth 5. +Deeper nesting → mark the deepest clause as `IsUnparseable = true`. + +--- + +## 11. Parser Anomaly Behavior + +When the parser cannot produce a clean AST: + +1. **Set `ParsedCommand.IsUnparseable = true`.** +2. **Set `UnparseableReason`** to a human-readable diagnostic. +3. **Return whatever clauses were successfully parsed** in `Clauses`. May + be empty. +4. **Never throw** on well-formed input strings (only throw on null). + +Conditions that produce `IsUnparseable = true`: + +- Unbalanced quotes (`"foo` with no closing `"`). +- Unbalanced parens (`(cmd && cmd2`). +- Unrecognized control-flow keywords (`for`, `while`, `do`, `done`, + `then`, `fi`, `case`, `esac`). +- Function definitions (`name() { ... }`). +- Process substitution (`<(cmd)`, `>(cmd)`). +- Recursion depth exceeded on `bash -c` chains. + +Consumers (e.g. Netclaw's gate evaluator) route unparseable commands to a +safe-fail path (prompt the user; offer only Once and Deny — no persistent +grants on shapes the parser can't model). + +--- + +## 12. Public Examples + +A handful of input/expected-AST pairs to anchor understanding. These belong +in the corpus (§13) verbatim. + +### Simple verb + +Input: `ls -la /tmp` + +``` +ParsedCommand { + Source = "ls -la /tmp", + IsUnparseable = false, + Clauses = [ + Clause { + Operator = None, + Verb = VerbChain { Tokens = ["ls"] }, + Args = [ + Arg { Raw = "-la", IsFlag = true, Kind = Literal }, + Arg { Raw = "/tmp", IsPath = true, Resolved = "/tmp", Kind = Literal } + ], + Redirects = [], + IsSubshell = false, + IsBashCWrapped = false + } + ] +} +``` + +### Multi-token verb + +Input: `git push origin main` + +``` +Clauses = [ + Clause { + Verb = VerbChain { Tokens = ["git", "push"] }, + Args = [ + Arg { Raw = "origin", Kind = Literal, IsPath = false }, + Arg { Raw = "main", Kind = Literal, IsPath = false } + ] + } +] +``` + +### Compound with cd attribution + +Input: `cd /target && cmd1 && cmd2 file.txt` + +``` +Clauses = [ + Clause { Verb = [cd], Args = [/target attributed-as-path], Op = None }, + Clause { + Verb = [cmd1], Op = AndIf, + Args = [Arg { Raw = "/target", Resolved = "/target", + IsPath = true, IsCwdAttribution = true }] + }, + Clause { + Verb = [cmd2], Op = AndIf, + Args = [ + Arg { Raw = "file.txt", Resolved = "/target/file.txt", IsPath = true }, + Arg { Raw = "/target", Resolved = "/target", + IsPath = true, IsCwdAttribution = true } + ] + } +] +``` + +### `git -C` flag-with-value + +Input: `git -C /repo log` + +``` +Clauses = [ + Clause { + Verb = VerbChain { Tokens = ["git", "log"] }, + Args = [ + Arg { Raw = "-C", IsFlag = true }, + Arg { Raw = "/repo", IsPath = true, Resolved = "/repo" } + ] + } +] +``` + +### Redirect + +Input: `cmd > /tmp/out.txt` + +``` +Clauses = [ + Clause { + Verb = [cmd], + Args = [], + Redirects = [Redirect { Direction = Out, Target = "/tmp/out.txt" }] + } +] +``` + +### Subshell isolation + +Input: `cd /a && (cd /b && cmd1) && cmd2` + +``` +Clauses = [ + Clause { Verb = [cd], Args = [/a], Op = None }, + Clause { Verb = [cd], Args = [/b], Op = AndIf, IsSubshell = true, + Args = [/a attribution from outer compound] }, + Clause { Verb = [cmd1], Op = AndIf, IsSubshell = true, + Args = [/b attribution — local to subshell] }, + Clause { Verb = [cmd2], Op = AndIf, + Args = [/a attribution — inherited from outer cd, NOT /b] } +] +``` + +### Dynamic skip + +Input: `rm $UNRESOLVED/foo` + +``` +Clauses = [ + Clause { + Verb = [rm], + Args = [ + Arg { Raw = "$UNRESOLVED/foo", Kind = DynamicSkip, IsPath = false, + Resolved = null } + ] + } +] +``` + +Consumer impact: zone-gate sees zero paths to evaluate; routes to the +fallback "treat as one untrusted path = the raw token" prompt. + +### Unparseable + +Input: `for i in 1 2; do echo $i; done` + +``` +ParsedCommand { + Source = "for i in 1 2; do echo $i; done", + IsUnparseable = true, + UnparseableReason = "control-flow keyword 'for' is not supported in v0.1", + Clauses = [] // or partial; consumer should not rely on contents +} +``` + +--- + +## 13. Test Corpus Contract + +The corpus is the **acceptance contract** for the parser. Implementation is +"done" when every corpus entry parses to its expected AST. + +### Location + +`tests/Corpus/bash/*.json` — one file per corpus entry. File name pattern: +`NN_descriptive_slug.json` where NN is a zero-padded sequence number. + +### Format + +Each file: + +```json +{ + "name": "Multi-token verb: git push", + "input": "git push origin main", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": ["git", "push"], + "args": [ + { "raw": "origin", "kind": "Literal", "isPath": false }, + { "raw": "main", "kind": "Literal", "isPath": false } + ], + "redirects": [], + "isSubshell": false, + "isBashCWrapped": false + } + ] + }, + "notes": "Optional explanation of edge case being captured." +} +``` + +### Coverage targets for v0.1 + +Author at least: + +- 10 simple-verb cases (ls, pwd, echo, cat, grep, etc.) +- 10 multi-token-verb cases (git push, dotnet test, docker compose up, etc.) +- 15 compound cases (`&&`, `||`, `;`, `|` combinations) +- 10 `cd`-in-compound propagation cases (single, sequential, with subshell) +- 10 quote-handling cases (single, double, escaped, mixed) +- 10 redirect cases (`>`, `>>`, `<`, `2>`, `2>>`, multiple redirects) +- 10 subshell cases (with and without isolation effects) +- 10 `bash -c` recursion cases (depth 1, 2, with inner compounds) +- 10 dynamic-skip cases (`$VAR`, `${VAR}`, `~user`, glob args) +- 10 per-verb path-rule cases (chmod, chown, find, grep, curl, git -C, etc.) +- 10 unparseable cases (unbalanced quotes, control-flow keywords, function + definitions) + +**Total minimum: 105 entries.** Strive for 150+ once seeded from sanitized +real-world commands (see §14). + +### Test runner + +A single xunit test method enumerates `tests/Corpus/bash/*.json`, parses +each `input`, and asserts the result matches `expected` field-by-field. +The runner emits a per-corpus-entry test name so failures point at the +specific case. + +```csharp +[Theory] +[MemberData(nameof(CorpusEntries))] +public void Corpus_entry_parses_to_expected_ast(CorpusEntry entry) +{ + var parser = new BashParser(); + var actual = parser.Parse(entry.Input); + AstAssert.Equal(entry.Expected, actual); // structural equality +} + +public static IEnumerable CorpusEntries() +{ + var dir = Path.Combine(AppContext.BaseDirectory, "Corpus", "bash"); + foreach (var file in Directory.GetFiles(dir, "*.json")) + { + var entry = JsonSerializer.Deserialize(File.ReadAllText(file)); + yield return [entry]; + } +} +``` + +`AstAssert.Equal` is a helper that does structural equality with helpful +diff messages on mismatch. Implement to taste. + +--- + +## 14. Sanitization Process + +A portion of the corpus seeds from real shell commands captured from +agent dogfood logs. The seed source is a daemon log file at +`~/.netclaw/logs/daemon-2026-05-09.log` (and similar). These logs contain +PII (usernames, repo paths, channel/thread IDs) that **must not** appear +in the public corpus. + +### Sanitization rules + +Apply these transformations to every seeded entry **before** committing: + +| Pattern | Replacement | +|---|---| +| `/home//` (any specific username) | `/home/user/` | +| `/Users//` (macOS) | `/Users/user/` | +| `~//` | `~/` | +| Specific repo paths like `/home/user/repositories/stannardlabs/` | `/home/user/repos/sample-repo` | +| Specific repo names (not in the org's public list) | `sample-repo` or `project` | +| Slack channel IDs (`D[A-Z0-9]{10}`) | `` (only if appears in command) | +| Slack thread IDs (`\d{10}\.\d{6}`) | `` | +| Internal hostnames | `internal-host.example` | +| Email addresses | `user@example.com` | +| API keys, tokens, secrets (any `[A-Za-z0-9]{20,}` that looks key-shaped) | `` (but prefer to drop the entry entirely) | + +### Workflow + +1. Pull candidate commands from logs: + ```bash + grep -oP "command \K\{[^}]+\}" ~/.netclaw/logs/daemon-*.log \ + | jq -r .Command | sort -u > /tmp/raw-corpus.txt + ``` +2. Apply sanitization (script TBD) — for each line, walk the table above. +3. **Manual review** of each sanitized entry before committing. The script + can miss patterns; a human (or careful agent) reviews for residual PII. +4. Drop any entry that can't be cleanly sanitized (too many specific + identifiers; rewrite as a fully-synthetic entry instead). +5. Commit with a clear message: `chore(corpus): seed from sanitized agent + logs (NN entries)`. + +### Audit gate + +Before any corpus PR merges, CI runs a regex check against the corpus +files for residual PII patterns. The check fails the build if any +sanitization-rule pattern appears in any committed corpus file. Implement +as a small `dotnet test` that scans `tests/Corpus/bash/*.json` for the +forbidden patterns. + +--- + +## 15. CI & Release Flow + +The repo template already has: + +- `.github/workflows/pr_validation.yml` — runs `dotnet test` on PR. +- `.github/workflows/publish_nuget.yml` — publishes to NuGet on release tag. + +Adapt for ShellSyntaxTree: + +- **Trigger NuGet publish on tag pattern `v*.*.*`** (e.g. `v0.1.0-alpha`). +- **Test job** runs the corpus runner plus all unit tests. +- **PII audit job** runs the sanitization-pattern scan over `tests/Corpus/`. + +### Versioning + +- **v0.1.0-alpha** — first publishable cut. Bash-only. Public API surface + per §2 is locked; internal changes are free. +- **v0.1.x** — additive changes (more verb table entries, more corpus, + bug fixes). +- **v0.2.0** — first PowerShell parser implementation. +- **v1.0.0** — ready when at least one external consumer beyond Netclaw + ships against it without finding API gaps. + +### Release notes + +Update `RELEASE_NOTES.md` for each tagged release. Format: + +``` +0.1.0-alpha YYYY-MM-DD + +* First publishable cut. +* Bash parser per SPEC.md v0.1. +* Corpus: N entries. +* Public API: IShellParser, BashParser, ParsedCommand, Clause, VerbChain, + Arg, Redirect, ArgKind, RedirectDirection, CompoundOperator. +``` + +--- + +## 16. Implementation Sequencing + +A natural order for the implementer: + +1. **Bootstrap projects.** Create `src/ShellSyntaxTree/ShellSyntaxTree.csproj` + (library) and `tests/ShellSyntaxTree.Tests/ShellSyntaxTree.Tests.csproj` + (xunit). Update `SampleSln.slnx` (rename to `ShellSyntaxTree.slnx`) and + delete the `Akka.Console` sample. +2. **Update template defaults.** `Directory.Build.props`: replace Akka + metadata with ShellSyntaxTree. `README.md`: real intro. `LICENSE`: keep + Apache-2.0 (already correct). `Directory.Packages.props`: add xunit, drop + Akka.Hosting. `Tags`: bash, shell, parser, ast. +3. **Write public API skeleton** (§2): interface + record stubs that compile + but throw `NotImplementedException` on `Parse()`. Lock the surface + first. +4. **Implement BashLexer** (§5). Heavy unit tests on tokenization. +5. **Implement BashArity / FILE / CWD verb tables** (§6) as static data. +6. **Implement BashParser** (§4). One production at a time; unit-test + each. +7. **Implement Resolver** (§8). Unit-test each resolution rule. +8. **Implement per-verb path-arg rules** (§7). Unit-test per verb. +9. **Implement cd-in-compound propagation** (§9). Unit-test. +10. **Implement subshell + bash -c recursion** (§10). Unit-test. +11. **Implement parser anomaly safe-fail** (§11). Unit-test. +12. **Author corpus** (§13) — start with 105 hand-authored entries + covering each section. Iterate parser to make all pass. +13. **Sanitize and seed from real logs** (§14) — script + manual review. + Add 50-100 more corpus entries. +14. **Wire CI** (§15). Tag v0.1.0-alpha when corpus is green and PII audit + passes. + +Estimated implementation effort: 600-800 LOC of source + 400-600 LOC of +test infrastructure + 100-150 corpus entries (~50 KB JSON). + +--- + +## 17. Acceptance Criteria + +v0.1.0-alpha ships when **all** of the following hold: + +1. ✅ Public API matches §2 exactly. `dotnet pack` produces a + ShellSyntaxTree.0.1.0-alpha.nupkg. +2. ✅ Every corpus entry in `tests/Corpus/bash/*.json` parses to its + expected AST. `dotnet test` runs them all and passes. +3. ✅ Corpus has at least 105 entries spanning the categories in §13. +4. ✅ PII audit scan over `tests/Corpus/bash/*.json` finds zero hits. +5. ✅ `dotnet test` runs on PR via GitHub Actions and passes. +6. ✅ Tagging `v0.1.0-alpha` triggers `publish_nuget.yml` and the package + appears on nuget.org. +7. ✅ Netclaw can consume the package via `` and the + `IShellParser` resolves at runtime in Netclaw's DI container. +8. ✅ At least one Netclaw integration test exercises a real corpus entry + through the live Netclaw matcher and gets the expected gate decision. + +--- + +## 18. Out of Scope (deferred from v0.1) + +- PowerShell and cmd parsers. +- Variable expansion (any kind). +- Heredoc body extraction. +- Process substitution `<(cmd)`, `>(cmd)`. +- Function definitions. +- Arithmetic expansion `$((...))`. +- `for`/`while`/`case` control flow. +- Performance optimization beyond "fast enough" (~1ms typical). +- Source-mapping (line/column for AST nodes — useful for IDEs, irrelevant + for security gates). +- Extensible verb table loading from config (v0.1 ships static tables; + consumers can layer their own knowledge on top via `BashParserOptions` + in a future version). + +--- + +## Appendix A: Consumer Contract (Netclaw) + +What Netclaw expects from this library: + +1. `IShellParser` is registered in DI and `Parse(string)` returns a + `ParsedCommand` that Netclaw walks. +2. For each `Clause`, Netclaw extracts: + - `Verb.Tokens` for the verb-pattern gate evaluation. + - All `Args` where `IsPath = true` (excluding `IsCwdAttribution = true` + when the resolved path already appears in another arg) for the zone + gate evaluation. + - All `Redirects` where the target is path-shaped — the target is a + path the clause "operates on" for zone-gate purposes. +3. When `IsUnparseable = true`, Netclaw routes to safe-fail (prompt user; + offer Once / Deny only). +4. When any `Arg.Kind = DynamicSkip`, Netclaw treats that token as + "path unknown" — falls back to prompting on the raw command for the + zone gate. +5. Hard-deny rules in Netclaw evaluate against parsed `Clause` records, + not raw text (except the `rawText` escape-hatch rules — those operate + on the **rendered clause string**, recoverable via + `Clause.ToCommandString()` if we add it, or `string.Join(" ", verb + + args + redirects)` if we don't). + +The contract is stable — additive changes to AST records (new fields with +default values) are compatible; renaming or removing fields is breaking +and requires a major version bump. + +--- + +## Appendix B: Why not tree-sitter-bash? + +OpenCode (Node) uses tree-sitter-bash. We considered porting that approach +to .NET. The packaging cost is real: + +- No first-class .NET tree-sitter binding. Community bindings exist but + vary in maintenance. +- Native dependency: ship `libtree-sitter` + `libtree-sitter-bash` per + platform (Linux x64, Linux arm64, macOS x64, macOS arm64, Windows x64). + Five binaries to ship and maintain, plus PowerShell would need a + separate native lib. +- AOT-trimming compatibility is uncertain. +- We don't need IDE-grade fidelity. Fork bombs and function definitions + legitimately confuse our parser; we want them to mark `IsUnparseable` + so the consumer routes to safe-fail. tree-sitter would parse them and + we'd have to teach the consumer to ignore the result anyway. + +The hand-rolled approach trades a higher ceiling for control over scope, +zero native deps, and a clean upgrade path to PowerShell via the same +`IShellParser` seam. For our use case, that trade is correct. diff --git a/SampleSln.slnx b/SampleSln.slnx deleted file mode 100644 index d9693ae..0000000 --- a/SampleSln.slnx +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/ShellSyntaxTree.slnx b/ShellSyntaxTree.slnx new file mode 100644 index 0000000..5ba88fc --- /dev/null +++ b/ShellSyntaxTree.slnx @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/TOOLING.md b/TOOLING.md new file mode 100644 index 0000000..bc470e6 --- /dev/null +++ b/TOOLING.md @@ -0,0 +1,136 @@ +# Tooling Inventory — ShellSyntaxTree + +What's available to agents working in this repo, how to access it, and what +it enables. Update this file when tooling actually changes; keep it concise. + +## Runtime and Build + +| Tool | Access | Purpose | +|---|---|---| +| .NET SDK | pinned by `global.json` (10.x line, `rollForward: major`) | build, test, pack, restore | +| `dotnet` CLI | shell | every build action | +| Local .NET tools | `.config/dotnet-tools.json` (run `dotnet tool restore`) | currently `incrementalist` (selective rebuilds) | + +DocFX and `build.ps1` (Akka template release-notes injector) were intentionally +removed — this repo does not need them. + +## Test Stack + +| Tool | Access | Purpose | +|---|---|---| +| xUnit 2.9.x | NuGet via Central Package Management | unit + corpus tests | +| `Microsoft.NET.Test.Sdk` 17.11+ | NuGet | test discovery | +| `coverlet.collector` | NuGet | code coverage | + +The corpus runner (SPEC §13) is a single `[Theory] [MemberData]` test that +enumerates `tests/ShellSyntaxTree.Tests/Corpus/bash/*.json` and asserts each +parses to its declared `expected` AST. + +## Source Control and CI + +| Tool | Access | Purpose | +|---|---|---| +| `git` | shell | everything | +| `gh` CLI | shell | issues, PRs, releases, tag pushes | +| GitHub Actions | `.github/workflows/` | `pr_validation.yml` (build + test + pack on PR/push), `publish_nuget.yml` (pack + push + release on `v*.*.*` tags) | +| GitHub Dependabot | `.github/dependabot.yml` | NuGet bumps | + +## NuGet + +| Tool | Access | Purpose | +|---|---|---| +| `NuGet.Config` | repo root | feed configuration (currently `nuget.org`) | +| `dotnet pack` | shell | produces `.nupkg` and `.snupkg` (symbol package) | +| `dotnet nuget push` | CI only (`NUGET_KEY` secret) | publishes to `nuget.org` on tag | +| Central Package Management | `Directory.Packages.props` | single source of truth for package versions | + +## Source-Level Conventions + +- `Directory.Build.props` enforces: `Nullable=enable`, `LangVersion=latest`, + `TreatWarningsAsErrors=true`. Any new project inherits these. +- Solution format is `.slnx` (`ShellSyntaxTree.slnx`). +- Every `.cs` file under `src/` and `tests/` carries a copyright header. + Use `scripts/Add-FileHeaders.ps1` to add them; CI runs the same script + with `-Verify` and fails the build on missing headers. + +### Copyright header script + +| Command | Purpose | +|---|---| +| `pwsh ./scripts/Add-FileHeaders.ps1` | Add Aaron Stannard copyright headers to all `.cs` files missing them under `src/` and `tests/` | +| `pwsh ./scripts/Add-FileHeaders.ps1 -WhatIf` | Preview which files would be modified | +| `pwsh ./scripts/Add-FileHeaders.ps1 -Verify` | CI mode: exit 1 if any file is missing a header | + +The script auto-detects file creation year via `git log --diff-filter=A` +and falls back to the current year for new files. + +## External Resources + +| Resource | Access | Purpose | +|---|---|---| +| Netclaw repo | local at `/home/petabridge/repositories/stannardlabs/netclaw` (also `github.com/netclaw-dev/netclaw`) | reference for the consumer contract; read `src/Netclaw.Security/ShellApprovalSemantics.cs` and `ShellTokenizer.cs` to see the surface ShellSyntaxTree replaces | +| Daemon dogfood logs (Netclaw) | `~/.netclaw/logs/daemon-*.log` (operator's machine; not in CI) | corpus seed source — strict sanitization required (SPEC §14) before any entry lands in the public corpus | + +## dotnet-skills Marketplace + +Install-once skill bundle that surfaces curated .NET guidance. Prefer +retrieval-led reasoning (open the skill) over pretraining for any non-trivial +.NET work. Routing for this repo: + +| Topic | Skill | +|---|---| +| Project layout / `.slnx` / `Directory.Build.props` / SourceLink | `dotnet-skills:project-structure` | +| Central Package Management, `dotnet add/remove` | `dotnet-skills:package-management` | +| Modern C# (records, pattern matching, value objects, Span/Memory) | `dotnet-skills:csharp-coding-standards` | +| Public API stability and extend-only design | `dotnet-skills:csharp-api-design` | +| Type/perf design (sealed classes, readonly structs, etc.) | `dotnet-skills:csharp-type-design-performance` | +| xUnit + corpus discipline | (no dedicated skill; corpus contract is in SPEC §13) | +| Snapshot/golden testing | `dotnet-skills:snapshot-testing` | +| Reward-hacking detector after code changes | `dotnet-skills:slopwatch` | +| Coverage / risk hot-spots | `dotnet-skills:crap-analysis` | +| Decompile a NuGet to confirm behavior | `dotnet-skills:ilspy-decompile` | +| Marketplace publishing workflow (skills, not this lib) | `dotnet-skills:marketplace-publishing` | + +Skills are advisory. Open the skill, apply the parts that fit ShellSyntaxTree, +note conflicts back here. + +## Specialist Agents + +Available via the `Agent` tool: + +- `dotnet-skills:dotnet-concurrency-specialist` — only relevant if the + parser ever grows mutable state (currently it shouldn't). +- `dotnet-skills:dotnet-performance-analyst` — for the "fast enough" + budget (~1 ms typical) if it's ever in doubt. +- `dotnet-skills:dotnet-benchmark-designer` — if benchmarks become + needed (SPEC explicitly defers performance tuning). +- `pr-review-specialist` — coordinated PR review, especially for the + initial public-API-locking PR. + +## Helper Skills (Claude Code) + +| Skill | When to use | +|---|---| +| `/init` | (already done — generated CLAUDE.md companion) | +| `/commit` | Branch-and-commit hygiene (creates feature branch off `dev` if needed) | +| `/pr` | Open PR against `dev` | +| `/review-pr` | Pre-flight review before requesting human review | +| `/security-review` | Targeted security review — relevant for this library | +| `/generate-image` | HCTI-backed image generation; used to mint the NuGet package icon | + +## Image Generation (HCTI) + +Used to mint the package icon. Output goes to `assets/icon.png` (or similar) +and is wired into `Directory.Build.props` via `PackageIcon`. The icon is the +only graphical artifact this repo needs. + +## Working Assumptions + +- Single maintainer (Aaron Stannard). No org-wide review SLA. +- CI is the only place that publishes packages; local `dotnet nuget push` is + not used. +- Dogfood log sanitization happens on the operator's machine before any + corpus entry is committed. CI enforces a regex audit; it does not + sanitize. +- No external API/secret credentials are required to build or test. + Publishing to nuget.org requires the `NUGET_KEY` repo secret. diff --git a/akkalogo.png b/akkalogo.png deleted file mode 100644 index 0f9cf4eda6aa34d41e5278a7e7be0998b7f9855f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17058 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 K7oN4e^S=P`Y*#x}^fzc%JutG}N7Ee}oG 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 literal 0 HcmV?d00001 diff --git a/build.ps1 b/build.ps1 deleted file mode 100644 index 221898a..0000000 --- a/build.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -. "$PSScriptRoot\scripts\getReleaseNotes.ps1" -. "$PSScriptRoot\scripts\bumpVersion.ps1" - -###################################################################### -# Step 1: Grab release notes and update solution metadata -###################################################################### -$releaseNotes = Get-ReleaseNotes -MarkdownFile (Join-Path -Path $PSScriptRoot -ChildPath "RELEASE_NOTES.md") - -# inject release notes into Directory.Buil -UpdateVersionAndReleaseNotes -ReleaseNotesResult $releaseNotes -XmlFilePath (Join-Path -Path $PSScriptRoot -ChildPath "Directory.Build.props") - -Write-Output "Added release notes $releaseNotes" \ No newline at end of file diff --git a/scripts/Add-FileHeaders.ps1 b/scripts/Add-FileHeaders.ps1 new file mode 100644 index 0000000..261a16d --- /dev/null +++ b/scripts/Add-FileHeaders.ps1 @@ -0,0 +1,139 @@ +<# +.SYNOPSIS + Adds Aaron Stannard copyright headers to C# files that don't have them. + +.DESCRIPTION + Scans all .cs files in src/ and tests/ directories and adds the standard + copyright header to files that are missing it. + +.PARAMETER WhatIf + Shows what files would be modified without making changes. + +.PARAMETER Verify + Returns exit code 1 if any files are missing headers (for CI validation). + +.EXAMPLE + ./scripts/Add-FileHeaders.ps1 + Adds headers to all files missing them. + +.EXAMPLE + ./scripts/Add-FileHeaders.ps1 -WhatIf + Shows which files would be modified. + +.EXAMPLE + ./scripts/Add-FileHeaders.ps1 -Verify + Checks if all files have headers (for CI). +#> + +param( + [switch]$WhatIf, + [switch]$Verify +) + +$ErrorActionPreference = "Stop" + +$rootDir = Split-Path -Parent $PSScriptRoot +$currentYear = (Get-Date).Year + +function Get-FileHeader { + param([string]$FileName, [int]$CreatedYear) + + return @" +// ----------------------------------------------------------------------- +// +// Copyright (C) $CreatedYear - $currentYear Aaron Stannard +// +// ----------------------------------------------------------------------- + +"@ +} + +function Test-HasHeader { + param([string]$Content) + return $Content -match "^\s*(//|/\*)\s*-{5,}" -or $Content -match "$null | Select-Object -First 1 + if ($gitYear) { + return [int]($gitYear.Substring(0, 4)) + } + + # Fall back to current year for new files + return $currentYear +} + +# Find all C# files in src/ and tests/ +$searchPaths = @( + (Join-Path $rootDir "src"), + (Join-Path $rootDir "tests") +) + +$csFiles = @() +foreach ($path in $searchPaths) { + if (Test-Path $path) { + $csFiles += Get-ChildItem -Path $path -Filter "*.cs" -Recurse | + Where-Object { $_.FullName -notmatch "[\\/](obj|bin)[\\/]" } + } +} + +$missingHeaders = @() +$processedCount = 0 + +foreach ($file in $csFiles) { + $content = Get-Content -Path $file.FullName -Raw -ErrorAction SilentlyContinue + + if (-not $content) { + continue + } + + if (-not (Test-HasHeader -Content $content)) { + $missingHeaders += $file + + if (-not $WhatIf -and -not $Verify) { + $createdYear = Get-FileCreatedYear -FilePath $file.FullName + $header = Get-FileHeader -FileName $file.Name -CreatedYear $createdYear + + # Handle BOM if present + $encoding = [System.Text.UTF8Encoding]::new($true) + $newContent = $header + $content.TrimStart() + + [System.IO.File]::WriteAllText($file.FullName, $newContent, $encoding) + $processedCount++ + Write-Host "Added header to: $($file.FullName -replace [regex]::Escape($rootDir), '')" + } + } +} + +if ($WhatIf) { + if ($missingHeaders.Count -eq 0) { + Write-Host "All files have headers." -ForegroundColor Green + } else { + Write-Host "Files missing headers:" -ForegroundColor Yellow + foreach ($file in $missingHeaders) { + Write-Host " $($file.FullName -replace [regex]::Escape($rootDir), '')" + } + Write-Host "`n$($missingHeaders.Count) file(s) would be modified." -ForegroundColor Yellow + } +} elseif ($Verify) { + if ($missingHeaders.Count -eq 0) { + Write-Host "All files have headers." -ForegroundColor Green + exit 0 + } else { + Write-Host "Files missing headers:" -ForegroundColor Red + foreach ($file in $missingHeaders) { + Write-Host " $($file.FullName -replace [regex]::Escape($rootDir), '')" + } + Write-Host "`n$($missingHeaders.Count) file(s) missing headers. Run './scripts/Add-FileHeaders.ps1' to fix." -ForegroundColor Red + exit 1 + } +} else { + if ($processedCount -eq 0) { + Write-Host "All files already have headers." -ForegroundColor Green + } else { + Write-Host "`nAdded headers to $processedCount file(s)." -ForegroundColor Green + } +} diff --git a/scripts/bumpVersion.ps1 b/scripts/bumpVersion.ps1 deleted file mode 100644 index cdc46ed..0000000 --- a/scripts/bumpVersion.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -function UpdateVersionAndReleaseNotes { - param ( - [Parameter(Mandatory=$true)] - [PSCustomObject]$ReleaseNotesResult, - - [Parameter(Mandatory=$true)] - [string]$XmlFilePath - ) - - # Load XML - $xmlContent = New-Object XML - $xmlContent.Load($XmlFilePath) - - # Update VersionPrefix and PackageReleaseNotes - $versionPrefixElement = $xmlContent.SelectSingleNode("//VersionPrefix") - $versionPrefixElement.InnerText = $ReleaseNotesResult.Version - - $packageReleaseNotesElement = $xmlContent.SelectSingleNode("//PackageReleaseNotes") - $packageReleaseNotesElement.InnerText = $ReleaseNotesResult.ReleaseNotes - - # Save the updated XML - $xmlContent.Save($XmlFilePath) -} - -# Usage example: -# $notes = Get-ReleaseNotes -MarkdownFile "$PSScriptRoot\RELEASE_NOTES.md" -# $propsPath = Join-Path -Path (Get-Item $PSScriptRoot).Parent.FullName -ChildPath "Directory.Build.props" -# UpdateVersionAndReleaseNotes -ReleaseNotesResult $notes -XmlFilePath $propsPath diff --git a/scripts/getReleaseNotes.ps1 b/scripts/getReleaseNotes.ps1 deleted file mode 100644 index 419d85f..0000000 --- a/scripts/getReleaseNotes.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -function Get-ReleaseNotes { - param ( - [Parameter(Mandatory=$true)] - [string]$MarkdownFile - ) - - # Read markdown file content - $content = Get-Content -Path $MarkdownFile -Raw - - # Split content based on headers - $sections = $content -split "####" - - # Output object to store result - $outputObject = [PSCustomObject]@{ - Version = $null - Date = $null - ReleaseNotes = $null - } - - # Check if we have at least 3 sections (1. Before the header, 2. Header, 3. Release notes) - if ($sections.Count -ge 3) { - $header = $sections[1].Trim() - $releaseNotes = $sections[2].Trim() - - # Extract version and date from the header - $headerParts = $header -split " ", 2 - if ($headerParts.Count -eq 2) { - $outputObject.Version = $headerParts[0] - $outputObject.Date = $headerParts[1] - } - - $outputObject.ReleaseNotes = $releaseNotes - } - - # Return the output object - return $outputObject -} - -# Call function example: -#$result = Get-ReleaseNotes -MarkdownFile "$PSScriptRoot\RELEASE_NOTES.md" -#Write-Output "Version: $($result.Version)" -#Write-Output "Date: $($result.Date)" -#Write-Output "Release Notes:" -#Write-Output $result.ReleaseNotes diff --git a/src/Akka.Console/Akka.Console.csproj b/src/Akka.Console/Akka.Console.csproj deleted file mode 100644 index dd9b804..0000000 --- a/src/Akka.Console/Akka.Console.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - net9.0 - enable - enable - - - - - - - - diff --git a/src/Akka.Console/HelloActor.cs b/src/Akka.Console/HelloActor.cs deleted file mode 100644 index 880c08b..0000000 --- a/src/Akka.Console/HelloActor.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Akka.Console; - -public class HelloActor : ReceiveActor -{ - private readonly ILoggingAdapter _log = Context.GetLogger(); - private int _helloCounter = 0; - - public HelloActor() - { - Receive(message => - { - _log.Info("{0} {1}", message, _helloCounter++); - }); - } -} \ No newline at end of file diff --git a/src/Akka.Console/Program.cs b/src/Akka.Console/Program.cs deleted file mode 100644 index 4d8962c..0000000 --- a/src/Akka.Console/Program.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Akka.Hosting; -using Akka.Console; -using Microsoft.Extensions.Hosting; - -var hostBuilder = new HostBuilder(); - -hostBuilder.ConfigureServices((context, services) => -{ - services.AddAkka("MyActorSystem", (builder, sp) => - { - builder - .WithActors((system, registry, resolver) => - { - var helloActor = system.ActorOf(Props.Create(() => new HelloActor()), "hello-actor"); - registry.Register(helloActor); - }) - .WithActors((system, registry, resolver) => - { - var timerActorProps = - resolver.Props(); // uses Msft.Ext.DI to inject reference to helloActor - var timerActor = system.ActorOf(timerActorProps, "timer-actor"); - registry.Register(timerActor); - }); - }); -}); - -var host = hostBuilder.Build(); - -await host.RunAsync(); \ No newline at end of file diff --git a/src/Akka.Console/README.md b/src/Akka.Console/README.md deleted file mode 100644 index 0c15d99..0000000 --- a/src/Akka.Console/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Akka.NET Console Template - -This is a simple template designed to incorporate local [Akka.NET](https://getakka.net/) into a console application. - -See https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/ConsoleTemplate.md for complete and current documentation on this template. \ No newline at end of file diff --git a/src/Akka.Console/TimerActor.cs b/src/Akka.Console/TimerActor.cs deleted file mode 100644 index 823960d..0000000 --- a/src/Akka.Console/TimerActor.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Akka.Hosting; - -namespace Akka.Console; - -public class TimerActor : ReceiveActor, IWithTimers -{ - private readonly IActorRef _helloActor; - - public TimerActor(IRequiredActor helloActor) - { - _helloActor = helloActor.ActorRef; - Receive(message => - { - _helloActor.Tell(message); - }); - } - - protected override void PreStart() - { - Timers.StartPeriodicTimer("hello-key", "hello", TimeSpan.FromSeconds(1)); - } - - public ITimerScheduler Timers { get; set; } = null!; // gets set by Akka.NET -} \ No newline at end of file diff --git a/src/Akka.Console/Usings.cs b/src/Akka.Console/Usings.cs deleted file mode 100644 index 15066e8..0000000 --- a/src/Akka.Console/Usings.cs +++ /dev/null @@ -1,2 +0,0 @@ -global using Akka.Actor; -global using Akka.Event; \ No newline at end of file