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);