diff --git a/.editorconfig b/.editorconfig index 4c6cba24..5f8004f9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -709,3 +709,15 @@ dotnet_diagnostic.MA0042.severity = none dotnet_diagnostic.CA2007.severity = none dotnet_diagnostic.MA0004.severity = none dotnet_diagnostic.VSTHRD111.severity = none + +# ============================================================================= +# FILE-BASED ENTRY ARBITRATION (MA0048) +# ============================================================================= +# Placed after every global C# diagnostic section so the file-scoped glob never +# narrows a later section. MA0048 (file name = type name) cannot apply to the +# file-based tool entry: the SDK synthesizes the Program type, and the sealed +# entry contract pins the file name (it is the tool name: +# ./tools/opencode-tool.cs). Winner: the entry contract; the rule stays error +# for every other file, generated output included (emission keeps file = type). +[tools/opencode-tool.cs] +dotnet_diagnostic.MA0048.severity = none diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b312d372..3665c1b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,12 @@ jobs: if: runner.os == 'Linux' run: dotnet format --verify-no-changes --no-restore + # Smoke-runs the repo tooling entry; exercises the committed executable bit and the + # shebang, which only take effect on a Linux checkout. + - name: Tooling entry smoke + if: runner.os == 'Linux' + run: ./tools/opencode-tool.cs --help + # TUnit's always-on HTML report self-uploads as a workflow artifact, but only if these # runtime vars (normally JS/Docker-action-only) are re-exported to run steps. - name: Enable TUnit report artifact upload diff --git a/Directory.Packages.props b/Directory.Packages.props index 32050e37..54710f0b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,6 +13,7 @@ + @@ -24,10 +25,13 @@ + + + diff --git a/OpenCode.slnx b/OpenCode.slnx index dc541485..0770c524 100644 --- a/OpenCode.slnx +++ b/OpenCode.slnx @@ -17,5 +17,10 @@ + + + + + diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d9aa472e..36904218 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -15,15 +15,18 @@ as of 2026-08-08. Design runway complete and grill-hardened: three sealed specs `superpowers/specs/` (public API, generator architecture, testing architecture), ADRs 0001–0009, research log sessions 1–9. Implementation planning complete (2026-08-10): slice map at `superpowers/plans/2026-08-10-implementation-slice-map.md` (12 vertical slices), slice issues -**#1–#12** with native `blocked_by` ordering, slice 0 detail plan written (issue #1 is -`ready-for-agent`); deviation protocol added (`docs/agents/deviation-protocol.md`), falsifiability -working agreement added to `AGENTS.md`. No SDK code yet — next up: execute slice 0 (issue #1). +**#1–#12** with native `blocked_by` ordering; deviation protocol added +(`docs/agents/deviation-protocol.md`), falsifiability working agreement added to `AGENTS.md`. +The Slice 0 tooling skeleton has landed; next is the Slice 1 planning cycle for issue #2. ## Queue In order — do not improvise beyond it without asking the maintainer. -1. **SDK build-out — slice issues #1–#12.** Scope, sequencing, and the per-slice done +1. **Post-Slice-0 Slopwatch adoption evaluation.** Decide local-tool pinning, baseline/config, + and CI policy together as one deliberate adoption decision. Do not add a pin, baseline, + configuration, or CI gate until that decision is made. +2. **SDK build-out — remaining slice issues #2–#12.** Scope, sequencing, and the per-slice done definition live in the slice map (`superpowers/plans/2026-08-10-implementation-slice-map.md`); task-level progress lives in per-slice plan checkboxes (plans written just-in-time; a slice whose plan exists gets @@ -34,7 +37,7 @@ In order — do not improvise beyond it without asking the maintainer. lock the public surface as implementation lands (testing spec §12; slices 4–5). **Spec retirement rides slice 11:** durable sealed decisions without an ADR home distill into ADRs before the transient `docs/superpowers/` documents are deleted. -2. **Later** — **MCP server**: in this repo as a thin SDK adapter (ADR-0006). Tech targeting +3. **Later** — **MCP server**: in this repo as a thin SDK adapter (ADR-0006). Tech targeting decided 2026-08-08 (research doc 05), **re-sealed at phase start against the then-current MCP landscape:** the 2026-07-28 protocol revision via MCP C# SDK v2.0, stdio + streamable HTTP, no investment in deprecated features. Evaluate NuGet's `McpServer` package type for diff --git a/docs/research/00-research-log.md b/docs/research/00-research-log.md index 5592fb3b..183aa545 100644 --- a/docs/research/00-research-log.md +++ b/docs/research/00-research-log.md @@ -970,3 +970,78 @@ TUnit reads arbitrary nested keys via `TestContext.Configuration.Get` — the `testingPlatform.environmentVariables` section did **not** apply and is not relied upon. **Decision:** the TUnit block leaves §14; results recorded at their §5.1/§5.4 use sites. + +# Session 10 — 2026-08-10: slice 0 build-out — file-based entry verification + +Closing session for slice 0's tooling skeleton: ran the generator spec §3.3 three-item +verification list against the committed file-based entry +(`tools/opencode-tool.cs` → `OpenCode.Sdk.Tools` via the `#:project` directive) and +recorded the Task 3 MA0048 arbitration trace in its research home. + +## Q52: Does the file-based entry survive the §3.3 verification list? + +**How researched:** the three §3.3 checks run live against the entry from the repo root. +(1) Strict-props build: `dotnet run --file tools/opencode-tool.cs -- generate` — the +entry inherits `Directory.Build.props` strict props on the cached `dotnet run` path, so a +green build under the full analyzer wall + TWAE is the proof. (2) Cache staleness: with +the entry warm, the `GenerateCommand` stub message was mutated to append +` (cache-probe)` via a file-only edit (no `dotnet build` between edit and re-run), then +the entry was re-run. (3) Invocation forms: `dotnet run --file … -- generate` (any OS) +and `--help` were exercised; the Unix direct-invocation form `./tools/opencode-tool.cs` +was pinned (mode 100755, shebang) but not run on this Windows host — Linux CI runs it in +Task 5. Task 3's strict-props build at session 9 had hit MA0048 on the synthesized +`Program` behind the sealed entry filename; that arbitration is included below as the +required strict-props finding trace. + +**Found (per item):** + +- *Strict-props build verdict:* clean. Post-arbitration, the entry builds under the + inherited strict props with `0 Warning(s) 0 Error(s)` and the fail-loud stub prints + `generate is not implemented yet — the generator pipeline has not landed.` (exit 1, + captured not misclassified). MA0048 (mezintou/Meziantou.Analyzer — "File name must + match type name") fired on the first run because the SDK synthesizes a `Program` type + behind a sealed entry filename no MA0048 mode (`Exact`/`Prefix`/`LongestCommonPrefix`) + can match. Classified as a **Level 1 recorded fallback** (deviation protocol Level 1; + AGENTS.md Hard Rule — "when a rule misfires on real code, the move is a per-rule + arbitration comment naming the winner — never a policy rollback"). The pre-authorized + remedy is a file-scoped arbitration (`.editorconfig`'s last section: + `[tools/opencode-tool.cs]` → `dotnet_diagnostic.MA0048.severity = none`, comment + naming the sealed entry contract as winner); it silences MA0048 for that file only, + and MA0048 stays `error` for every other file (emission file = type commitment + intact). This is a recorded fallback, not a policy rollback — the rule remains on for + the rest of the codebase, generated output included. The §3.3 cache-staleness + fallbacks (routed-build Level 1; console-app promotion + ADR-0003 correction Level 2) + are independent of MA0048 and were not triggered by it: their subject is cache + staleness, evaluated under the staleness check below. Initial placement of the + file-scoped section silently narrowed the global `[*.cs]` scope (`.editorconfig` + section headers are cumulative until the next header); a fix-up commit moved the + arbitration to the file's last section so it no longer narrows any subsequent section. + +- *Staleness verdict:* **no mitigation needed.** After the ` (cache-probe)` edit and + re-running `dotnet run --file tools/opencode-tool.cs -- generate` without an + intervening `dotnet build`, the output read + `generate is not implemented yet — the generator pipeline has not landed. + (cache-probe)` (exit 1) — the freshly edited `GenerateCommand` ran. The marker + appeared, so `#:project` library changes trigger rebuilds on the file-based entry path; + the stale-tool hazard does not exist for this entry shape. The generator spec §3.3 + routed-build Level 1 fallback was therefore **not triggered**, and the two-condition + fallback (console-app promotion + ADR-0003 correction) stayed dormant. Probe edit + reverted by apply_patch; the `GenerateCommand.cs` blob was bit-identical pre- and + post-task (SHA256 `de69cb4e…`, git blob `df921b4…`). + +- *Invocation forms:* `dotnet run --file tools/opencode-tool.cs -- generate` exits 1 with + the stub message; `dotnet run --file tools/opencode-tool.cs -- --help` exits 0 with + usage listing `generate — Regenerate the SDK model layer from spec/openapi.json`. The + Unix direct-invocation form `./tools/opencode-tool.cs ` is pinned by mode 100755 + + the `#!/usr/bin/env -S dotnet --` shebang and awaits Linux CI verification in Task 5. + +**Decision:** §3.3 verification list passes; the file-based entry survives as sealed. +Cache mitigation is **off** — Task 5's CI smoke step does **not** prepend a routed +`dotnet build` line; the §3.3 cache-staleness fallbacks were **not triggered** +(marker-present path: routed-build Level 1 unneeded; console-app promotion + ADR-0003 +correction Level 2 dormant). MA0048 is the **one Level 1 recorded fallback executed in +this slice** — pre-authorized by the analyzer-policy Hard Rule's per-rule arbitration +procedure (a recorded fallback, not a policy rollback); the file-scoped arbitration is +the status quo for this one entry file. If the entry contract ever moves (e.g. +console-app promotion at a future cache-Level-2 trigger), the arbitration moves with it +or is deleted. diff --git a/docs/superpowers/plans/2026-08-10-slice-00-tooling-skeleton.md b/docs/superpowers/plans/2026-08-10-slice-00-tooling-skeleton.md index 7f66a78e..6378cb43 100644 --- a/docs/superpowers/plans/2026-08-10-slice-00-tooling-skeleton.md +++ b/docs/superpowers/plans/2026-08-10-slice-00-tooling-skeleton.md @@ -56,7 +56,7 @@ Microsoft.Extensions.DependencyInjection, TUnit (already pinned). - Produces: an empty net10.0 library project that later tasks fill; the `/tools/` solution folder. -- [ ] **Step 1: Add package pins to `Directory.Packages.props`** +- [x] **Step 1: Add package pins to `Directory.Packages.props`** Under `` add: @@ -78,7 +78,7 @@ Under `` add: ``` -- [ ] **Step 2: Create `tools/OpenCode.Sdk.Tools/OpenCode.Sdk.Tools.csproj`** +- [x] **Step 2: Create `tools/OpenCode.Sdk.Tools/OpenCode.Sdk.Tools.csproj`** ```xml @@ -100,7 +100,7 @@ Under `` add: (`$(DefaultTargetFramework)` is `net10.0` from `Directory.Build.props`; the tool is single-TFM by design — testing spec §4.) -- [ ] **Step 3: Add the project to `OpenCode.slnx`** +- [x] **Step 3: Add the project to `OpenCode.slnx`** Insert after the `/tests/` folder (folders are kept alphabetical): @@ -110,14 +110,14 @@ Insert after the `/tests/` folder (folders are kept alphabetical): ``` -- [ ] **Step 4: Build** +- [x] **Step 4: Build** Run: `dotnet build --configuration Release` Expected: success, zero warnings (the empty library must pass the wall — if a repo-wide analyzer fires on the bare project, that is a finding, not something to suppress; classify per the deviation protocol). -- [ ] **Step 5: Commit** +- [x] **Step 5: Commit** ```bash git add Directory.Packages.props tools/OpenCode.Sdk.Tools/OpenCode.Sdk.Tools.csproj OpenCode.slnx @@ -149,7 +149,7 @@ API note: `CommandAppTester` (ctor taking `ITypeRegistrar`, `Configure`, `RunAsy pinned version's signatures differ in detail (e.g. an added `CancellationToken` on `AsyncCommand.ExecuteAsync`), adapt in place — that is a level-0 deviation. -- [ ] **Step 1: Create the test project** +- [x] **Step 1: Create the test project** `tests/OpenCode.Sdk.Tools.Tests/OpenCode.Sdk.Tools.Tests.csproj`: @@ -179,7 +179,7 @@ Add to `OpenCode.slnx` under the existing `/tests/` folder: ``` -- [ ] **Step 2: Write the failing tests** +- [x] **Step 2: Write the failing tests** `tests/OpenCode.Sdk.Tools.Tests/ToolAppTests.cs`: @@ -218,12 +218,12 @@ public sealed class ToolAppTests (`OpenCode.Sdk.Tools.Tests` sits inside the `OpenCode.Sdk.Tools` namespace hierarchy, so `ToolApp` resolves without a using directive — adding one would trip IDE0005.) -- [ ] **Step 3: Run tests to verify they fail** +- [x] **Step 3: Run tests to verify they fail** Run: `dotnet test tests/OpenCode.Sdk.Tools.Tests` Expected: build FAILS with CS0246 (`ToolApp` not found) — the red state for scaffolding. -- [ ] **Step 4: Implement `ToolApp`** +- [x] **Step 4: Implement `ToolApp`** `tools/OpenCode.Sdk.Tools/ToolApp.cs`: @@ -300,17 +300,17 @@ public sealed class GenerateCommand : AsyncCommand } ``` -- [ ] **Step 5: Run tests to verify they pass** +- [x] **Step 5: Run tests to verify they pass** Run: `dotnet test tests/OpenCode.Sdk.Tools.Tests` Expected: PASS (both tests, net10.0 leg). -- [ ] **Step 6: Full gate check** +- [x] **Step 6: Full gate check** Run: `dotnet build --configuration Release` then `dotnet format --verify-no-changes --no-restore` Expected: both clean. -- [ ] **Step 7: Commit** +- [x] **Step 7: Commit** ```bash git add tools/OpenCode.Sdk.Tools tests/OpenCode.Sdk.Tools.Tests OpenCode.slnx @@ -331,7 +331,7 @@ git commit -m "feat(tools): ToolApp composition root with fail-loud generate stu pinned invocation forms (`dotnet run --file tools/opencode-tool.cs -- ` on any OS; `./tools/opencode-tool.cs ` on Unix). -- [ ] **Step 1: Write the entry** +- [x] **Step 1: Write the entry** `tools/opencode-tool.cs` — exactly this content, LF line endings, UTF-8 **without BOM** (a BOM breaks the shebang): @@ -349,7 +349,7 @@ Add to the `/tools/` folder in `OpenCode.slnx`: ``` -- [ ] **Step 2: Run through the entry — §3.3 item 1 (strict-props build) + item 3 (invocation form)** +- [x] **Step 2: Run through the entry — §3.3 item 1 (strict-props build) + item 3 (invocation form)** Run: `dotnet run --file tools/opencode-tool.cs -- generate` Expected: exit code 1 and the stub message — proving the entry builds **clean under the @@ -359,7 +359,7 @@ inherited strict props** (any analyzer/TWAE diagnostic fails this run) and that Run: `dotnet run --file tools/opencode-tool.cs -- --help` Expected: exit 0, help output listing `generate`. -- [ ] **Step 3: Commit the executable bit into the index** +- [x] **Step 3: Commit the executable bit into the index** ```bash git add tools/opencode-tool.cs OpenCode.slnx @@ -370,7 +370,7 @@ git ls-files -s tools/opencode-tool.cs Expected: `git ls-files -s` prints mode `100755`. (The bit is meaningless on NTFS and effective on the Linux CI checkout — generator spec §3.1.) -- [ ] **Step 4: Commit** +- [x] **Step 4: Commit** ```bash git commit -m "feat(tools): file-based entry dogfooding ToolApp" @@ -389,16 +389,16 @@ git commit -m "feat(tools): file-based entry dogfooding ToolApp" - Produces: the recorded §3.3 verdict (research log, question→finding→decision) and, if stale, the mitigation wired into CI/docs in Task 5. -- [ ] **Step 1: Baseline run** +- [x] **Step 1: Baseline run** Run: `dotnet run --file tools/opencode-tool.cs -- generate` — note the message text. -- [ ] **Step 2: Mutate the referenced library without an explicit build** +- [x] **Step 2: Mutate the referenced library without an explicit build** Edit the stub message in `GenerateCommand.cs` to append ` (cache-probe)`. Do **not** run `dotnet build`. Re-run: `dotnet run --file tools/opencode-tool.cs -- generate` -- [ ] **Step 3: Read the verdict** +- [x] **Step 3: Read the verdict** - Marker appears → `#:project` changes trigger rebuilds: **no mitigation needed**. - Marker absent → the stale-tool hazard is real: **mitigation** = every documented/CI @@ -409,12 +409,12 @@ Edit the stub message in `GenerateCommand.cs` to append ` (cache-probe)`. Do **n promotion + one-line ADR-0003 correction) triggers — **stop; maintainer decision** (level 2). -- [ ] **Step 4: Revert the probe edit** +- [x] **Step 4: Revert the probe edit** Revert `GenerateCommand.cs` to Task 2's exact content; re-run the entry once to confirm the original message. -- [ ] **Step 5: Record the findings** +- [x] **Step 5: Record the findings** Append a session entry to `docs/research/00-research-log.md` (next session number, `# Session N — : slice 0 build-out — file-based entry verification`) with one @@ -423,7 +423,7 @@ researched (the three checks above), found (per item: strict-props build verdict staleness verdict, invocation forms), decision (mitigation on/off; fallback not triggered / triggered). -- [ ] **Step 6: Commit** +- [x] **Step 6: Commit** ```bash git add docs/research/00-research-log.md @@ -441,7 +441,7 @@ git commit -m "docs(research): file-based entry verification results (slice 0)" - Consumes: the entry + executable bit (Task 3), the staleness verdict (Task 4). - Produces: the Linux-leg smoke step slice 3 will upgrade to `generate --verify`. -- [ ] **Step 1: Add the smoke step** +- [x] **Step 1: Add the smoke step** After the `Verify formatting` step in `.github/workflows/ci.yml`: @@ -461,13 +461,13 @@ If Task 4 recorded the staleness mitigation, prepend the routed build: ./tools/opencode-tool.cs --help ``` -- [ ] **Step 2: Local full-gate sweep** +- [x] **Step 2: Local full-gate sweep** Run, in order: `dotnet build --configuration Release`, `dotnet test --configuration Release --no-build`, `dotnet format --verify-no-changes --no-restore` Expected: all clean/green. -- [ ] **Step 3: Commit and push the branch** +- [x] **Step 3: Commit and push the branch** ```bash git add .github/workflows/ci.yml @@ -475,7 +475,7 @@ git commit -m "ci: smoke the tooling entry on the Linux leg" git push -u origin feature/slice-00-tooling-skeleton ``` -- [ ] **Step 4: Verify CI** +- [x] **Step 4: Verify CI** Open the PR (`gh pr create`), watch the three legs; the Linux leg must run the smoke step green. Slice exit: PR review + merge closes the slice issue. diff --git a/tests/OpenCode.Sdk.Tools.Tests/OpenCode.Sdk.Tools.Tests.csproj b/tests/OpenCode.Sdk.Tools.Tests/OpenCode.Sdk.Tools.Tests.csproj new file mode 100644 index 00000000..f1cd837b --- /dev/null +++ b/tests/OpenCode.Sdk.Tools.Tests/OpenCode.Sdk.Tools.Tests.csproj @@ -0,0 +1,17 @@ + + + + Exe + $(DefaultTargetFramework) + + + + + + + + + + + + diff --git a/tests/OpenCode.Sdk.Tools.Tests/ToolAppTests.cs b/tests/OpenCode.Sdk.Tools.Tests/ToolAppTests.cs new file mode 100644 index 00000000..4354c13c --- /dev/null +++ b/tests/OpenCode.Sdk.Tools.Tests/ToolAppTests.cs @@ -0,0 +1,31 @@ +using Spectre.Console.Cli.Testing; + +namespace OpenCode.Sdk.Tools.Tests; + +public sealed class ToolAppTests +{ + [Test] + public async Task RunAsync_Should_Return_Nonzero_When_Command_Is_Unknown() + { + using var registrar = ToolApp.CreateRegistrar(); + var tester = new CommandAppTester(registrar); + tester.Configure(ToolApp.Configure); + + var result = await tester.RunAsync(["does-not-exist"]); + + await Assert.That(result.ExitCode).IsNotEqualTo(0); + } + + [Test] + public async Task RunAsync_Should_Fail_Loud_When_Generate_Is_Invoked() + { + using var registrar = ToolApp.CreateRegistrar(); + var tester = new CommandAppTester(registrar); + tester.Configure(ToolApp.Configure); + + var result = await tester.RunAsync(["generate"]); + + await Assert.That(result.ExitCode).IsEqualTo(1); + await Assert.That(result.Output).Contains("not implemented"); + } +} diff --git a/tools/OpenCode.Sdk.Tools/Commands/GenerateCommand.cs b/tools/OpenCode.Sdk.Tools/Commands/GenerateCommand.cs new file mode 100644 index 00000000..df921b42 --- /dev/null +++ b/tools/OpenCode.Sdk.Tools/Commands/GenerateCommand.cs @@ -0,0 +1,25 @@ +using Spectre.Console; +using Spectre.Console.Cli; + +namespace OpenCode.Sdk.Tools.Commands; + +/// Fail-loud stub; the generator pipeline replaces the body in a later slice. +public sealed class GenerateCommand : AsyncCommand +{ + private readonly IAnsiConsole _console; + + /// Creates the command; the console is injected so tests capture output. + public GenerateCommand(IAnsiConsole console) + { + ArgumentNullException.ThrowIfNull(console); + _console = console; + } + + /// + protected override Task ExecuteAsync(CommandContext context, CancellationToken cancellationToken) + { + _console.MarkupLine( + "[red]generate is not implemented yet[/] — the generator pipeline has not landed."); + return Task.FromResult(1); + } +} diff --git a/tools/OpenCode.Sdk.Tools/OpenCode.Sdk.Tools.csproj b/tools/OpenCode.Sdk.Tools/OpenCode.Sdk.Tools.csproj new file mode 100644 index 00000000..04fea304 --- /dev/null +++ b/tools/OpenCode.Sdk.Tools/OpenCode.Sdk.Tools.csproj @@ -0,0 +1,14 @@ + + + + $(DefaultTargetFramework) + false + + + + + + + + + diff --git a/tools/OpenCode.Sdk.Tools/ToolApp.cs b/tools/OpenCode.Sdk.Tools/ToolApp.cs new file mode 100644 index 00000000..6ee7dba3 --- /dev/null +++ b/tools/OpenCode.Sdk.Tools/ToolApp.cs @@ -0,0 +1,40 @@ +using Microsoft.Extensions.DependencyInjection; +using OpenCode.Sdk.Tools.Commands; +using Spectre.Console; +using Spectre.Console.Cli; +using Spectre.Console.Cli.Extensions.DependencyInjection; + +namespace OpenCode.Sdk.Tools; + +/// Composition root for the repo tool: DI registrar plus the command surface. +public static class ToolApp +{ + /// Builds the DI registrar; tests inject service overrides. + public static DependencyInjectionRegistrar CreateRegistrar( + Action? overrideServices = null) + { + var services = new ServiceCollection(); + services.AddSingleton(AnsiConsole.Console); + overrideServices?.Invoke(services); + return new DependencyInjectionRegistrar(services); + } + + /// Registers the command surface; shared by the app and CommandAppTester. + public static void Configure(IConfigurator configurator) + { + ArgumentNullException.ThrowIfNull(configurator); + configurator.SetApplicationName("opencode-tool"); + configurator.AddCommand("generate") + .WithDescription("Regenerate the SDK model layer from spec/openapi.json."); + } + + /// Entry point used by tools/opencode-tool.cs. + public static async Task RunAsync(string[] args) + { + ArgumentNullException.ThrowIfNull(args); + using var registrar = CreateRegistrar(); + var app = new CommandApp(registrar); + app.Configure(Configure); + return await app.RunAsync(args).ConfigureAwait(false); + } +} diff --git a/tools/opencode-tool.cs b/tools/opencode-tool.cs new file mode 100755 index 00000000..6f85abde --- /dev/null +++ b/tools/opencode-tool.cs @@ -0,0 +1,4 @@ +#!/usr/bin/env -S dotnet -- +#:project ./OpenCode.Sdk.Tools/OpenCode.Sdk.Tools.csproj + +return await OpenCode.Sdk.Tools.ToolApp.RunAsync(args).ConfigureAwait(false);