Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f35f741
docs(research): inventory Polyfill capabilities
Blind-Striker Aug 12, 2026
7f317f0
docs(plan): define M1 callable client arc
Blind-Striker Aug 13, 2026
d0865e9
feat(generator): land Arc B model policy and seal the v2 retarget
Blind-Striker Aug 13, 2026
5f71294
docs(plan): seal retarget-task conventions
Blind-Striker Aug 13, 2026
911b7f3
feat(generator): retarget the pin to the opencode v2 protocol surface
Blind-Striker Aug 13, 2026
784763b
docs(agents): hand over the post-retarget Arc B state
Blind-Striker Aug 13, 2026
40c6f04
feat(generator): admit handleParameter group curation
Blind-Striker Aug 13, 2026
b4cd3a7
refactor(generator): resolve type names once per bind
Blind-Striker Aug 13, 2026
ec2bb29
feat(generator): bind the selected operations into client plans
Blind-Striker Aug 13, 2026
a2dfb91
feat(sdk): land the response, options, and exception spine
Blind-Striker Aug 13, 2026
3e0c21c
feat(sdk): implement the Pipeline behavior core
Blind-Striker Aug 13, 2026
aa7e351
feat(generator): plan route container and member names
Blind-Striker Aug 13, 2026
48b572a
docs(agents): refresh the Arc B handover to the post-Task-2 state
Blind-Striker Aug 13, 2026
367f8ff
feat(generator): emit the callable client surface
Blind-Striker Aug 13, 2026
bedb6be
feat(generator): extend writer ownership to the callable surface
Blind-Striker Aug 13, 2026
f358249
feat(generator): wire the callable surface into generation
Blind-Striker Aug 13, 2026
52a7810
test(sdk): cover the callable surface with a contract matrix
Blind-Striker Aug 13, 2026
ca128a0
test(sdk): lock the reviewed public API baseline
Blind-Striker Aug 13, 2026
257384a
docs(roadmap): close M1 after the live walking-skeleton demonstration
Blind-Striker Aug 13, 2026
3b15a5f
feat(sandbox): add a debuggable local playground project
Blind-Striker Aug 13, 2026
dcd4f80
chore(sandbox): prefill the launch profile and document id discovery
Blind-Striker Aug 13, 2026
0c4fbd9
docs(research): record the Arc B execution decisions and the post-M1 …
Blind-Striker Aug 13, 2026
52a5e3b
test(sdk): reference System.Net.Http on the net472 leg
Blind-Striker Aug 13, 2026
b1d2e75
docs(agents): seal the performance posture
Blind-Striker Aug 13, 2026
4d5084c
docs(roadmap): anchor the review queue to the M series
Blind-Striker Aug 13, 2026
959c4f0
test(perf): stand up the BenchmarkDotNet suite with P-finding baselines
Blind-Striker Aug 13, 2026
082b58c
fix(sdk): harden pipeline failure channels against BCL escapes
Blind-Striker Aug 13, 2026
1de4d78
fix(generator): harden the emitted surface against the review blocker…
Blind-Striker Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,9 @@ dotnet_diagnostic.S6602.severity = none
# =============================================================================
# SECTION 13: COMPILER DIAGNOSTICS (CSxxxx)
# =============================================================================
dotnet_diagnostic.CS1591.severity = none
# Every public member on the shipped surface is documented: generated docs come from the
# spec, hand-written docs by hand (ADR-0004). Tests are exempted in Section 15.
dotnet_diagnostic.CS1591.severity = error

# =============================================================================
# SECTION 14: GENERATED CODE
Expand All @@ -717,6 +719,7 @@ generated_code = true
# =============================================================================
[{*Tests/**/*.cs,tests/**/*.cs}]
dotnet_diagnostic.CA1707.severity = none
dotnet_diagnostic.CS1591.severity = none

# TUnit Assert.That reports false positives for MA0042 because the analyzer cannot model the assertion API shape.
dotnet_diagnostic.MA0042.severity = none
Expand Down Expand Up @@ -781,3 +784,17 @@ dotnet_diagnostic.S2325.severity = none
# enabled for every other file.
[tools/OpenCode.Sdk.Tools/Generator/Ingestion/Models/LiteralKind.cs]
dotnet_diagnostic.CA1720.severity = none

# Generated response adapters gain shared, non-virtual mapping helpers on this
# base at emission build-out, and default interface members cannot carry shared
# behavior on the locked net472/netstandard2.0 targets (CS8701). Winner: the
# abstract adapter base; S1694 remains enabled for every other file.
[src/OpenCode.Sdk/Internal/ResponseAdapter.cs]
dotnet_diagnostic.S1694.severity = none

# The generated route surface deliberately nests one static container per operation group
# (maintainer-approved shape, 2026-08-13); flattening would erase the grouping the public
# API promises. Winner: the generated route shape; CA1034 remains enabled for every other
# file.
[src/OpenCode.Sdk/OpenCodeRoutes.cs]
dotnet_diagnostic.CA1034.severity = none
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ jobs:
if: runner.os == 'Linux'
run: ./tools/opencode-tool.cs --help

# Regenerating the pinned profile must be a byte-identical no-op on a clean checkout.
- name: Verify generated output
if: runner.os == 'Linux'
run: dotnet run --file tools/opencode-tool.cs -- generate --verify

# 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
Expand Down
24 changes: 16 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ lazily); this list is the complete inventory of what is settled. Do not reopen w
evidence. The design specs under `docs/superpowers/` are vision/reference material —
direction and rationale, not law; only this list and the ADRs bind.

- **Target artifact:** upstream's `packages/sdk/openapi.json` (OpenAPI 3.1), pinned under
`spec/` — the same spec the official JS SDK generates from.
- **API surface:** both generations of the pinned 1.x spec — the modern block takes the
unmarked public names ("V2" never appears); legacy lives behind a legacy-marked
sub-surface, deleted at our upstream-absorbing major; legacy deep-testing is
consumer-driven (ADR-0005).
- **Target artifact:** upstream's `packages/protocol/openapi.json` from the active `v2`
branch (OpenAPI 3.1), pinned as a snapshot under `spec/` (ADR-0005; the 1.x pin retires
at the M1 retarget task).
- **API surface:** the v2 protocol surface only — public names strip the `v2.` operationId
prefix ("V2" never appears) (ADR-0005).
- **Hybrid construction:** hand-written behavior core; models *and* operation methods from
our own Roslyn-emission generator; spec ingestion rides the pinned `Microsoft.OpenApi`
reader — the generator owns a fail-closed semantic projection, never an OpenAPI parser;
Expand All @@ -45,8 +44,9 @@ direction and rationale, not law; only this list and the ADRs bind.
- **Monorepo, independent versioning:** per-merge GitHub Packages CD, manual NuGet.org
releases (ADR-0006).
- **Licensing:** MIT (`PackageLicenseFile`).
- **SSE:** `IAsyncEnumerable<T>`, no auto-reconnect; the durable stream resumes via the
`after` cursor (research doc 02).
- **SSE:** `IAsyncEnumerable<T>`, no auto-reconnect (research doc 02); the durable-stream
resume design is re-derived against the v2 surface at M3 (`v2.session.log` carries
`after`/`follow`).
- **`ConfigureAwait(false)`:** triple-enforced in product code, off in tests (research
doc 07).
- **Analyzer policy:** fail-closed maximalist (research doc 07; relitigation ban: Hard
Expand Down Expand Up @@ -116,6 +116,14 @@ direction and rationale, not law; only this list and the ADRs bind.
- **Defensive programming is the default, everywhere:** guard public inputs, assert internal
invariants, fail loudly rather than guess — silent fallbacks exist only as explicitly
recorded tolerances (ADR-0009 pattern).
- **Performance is a standing concern, weighted by artifact:** the shipped SDK targets both
speed and minimal allocation on its hot paths (zero *avoidable* allocations — ADR-0004's
immutable models and ADR-0007's raw-body retention are contracts, not waste); repo tooling
targets speed. No speculative optimization, but obvious waste (redundant parses, needless
copies, avoidable buffering) is always evaluated, starting with the low-hanging fruit.
Performance claims are settled by benchmarks (`tests/OpenCode.Sdk.Performance.Tests`,
`MemoryDiagnoser` on), never by argument; performance-touching PRs carry before/after
numbers.
- **Code style is canonical in `docs/engineering/coding-style.md`:** named collaborator
classes over private-method accumulation; interfaces at seams with full-battery DI-first
executable composition, sealed everywhere else; no tuple returns or concrete-collection
Expand Down
13 changes: 5 additions & 8 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,17 @@ One project-directory context inside a Server process, selected per request via
targeting; a Server process hosts many.

**Directory targeting**:
Per-request project targeting via the `x-opencode-directory` header.
Per-request project targeting via `location[...]` query parameters (the v2 convention).

**PTY**:
A pseudo-terminal session managed through the API.

### This project's language

**Modern surface**:
The `v2.*`-prefixed operation block of the pinned spec; public names carry no prefix.
_Avoid_: v2, V2 (in public naming)

**Legacy surface**:
The un-prefixed operation block of the pinned spec; lives behind a legacy-marked sub-surface
and is deleted wholesale at the 2.0-absorbing major.
**Protocol surface** (historically "modern surface" in dated research docs):
The `v2.*`-prefixed protocol operation block — the surface this SDK generates (ADR-0005);
public names carry no prefix.
_Avoid_: v2, V2 (in public naming); legacy (the retired 1.x dual-surface vocabulary)

**Launcher**:
The in-core component that starts, monitors, and stops a local `opencode serve` process.
Expand Down
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.17.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.17.0" />
<!-- third-party packages -->
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="CliWrap" Version="3.10.4" />
<PackageVersion Include="Microsoft.OpenApi" Version="3.9.0" />
<PackageVersion Include="Spectre.Console" Version="0.55.0" />
Expand All @@ -41,6 +42,7 @@
<PackageVersion Include="Spectre.Console.Cli.Testing" Version="0.55.0" />
<PackageVersion Include="Testably.Abstractions.Testing" Version="7.0.2" />
<PackageVersion Include="TUnit" Version="1.63.25" />
<PackageVersion Include="PublicApiGenerator" Version="11.5.4" />
<PackageVersion Include="Verify.TUnit" Version="31.28.0" />
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions OpenCode.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<Project Path="src/OpenCode.Sdk/OpenCode.Sdk.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/OpenCode.Sdk.Performance.Tests/OpenCode.Sdk.Performance.Tests.csproj" />
<Project Path="tests/OpenCode.Sdk.Sandbox/OpenCode.Sdk.Sandbox.csproj" />
<Project Path="tests/OpenCode.Sdk.Tests/OpenCode.Sdk.Tests.csproj" />
<Project Path="tests/OpenCode.Sdk.Tools.Tests/OpenCode.Sdk.Tools.Tests.csproj" />
</Folder>
Expand Down
83 changes: 55 additions & 28 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
# Roadmap

Date: 2026-08-12
Date: 2026-08-13

Operational state: what is done, what is next, what is open. This file shrinks as work lands.
Evergreen rules and locked decisions live in `../AGENTS.md`; decision records in `adr/`.

## Status

Structural skeleton, three-OS CI, the pinned spec snapshot, Slice 0 tooling, and complete-pin
SpecIR ingestion are in place. M1's compiler arc selects the two walking-skeleton operations,
binds their reachable closure, emits committed Roslyn-built models plus source-generated JSON
metadata, and owns deterministic `generate` / `generate --verify` writes through a guarded
manifest. The partial-operation marker blocks every package while breadth remains incomplete.
The next M1 arc is the minimal transport and callable client with typed errors and `NoThrow`.
**M1 is complete.** The SDK targets the v2 protocol surface only (ADR-0005; `spec/` pins
v2-branch commit `a6a712a`), and the walking skeleton runs end to end: SpecIR plus curation
bind into client/operation/envelope/route/error plans, generic emitters render the callable
surface as committed source under `src/OpenCode.Sdk` (`OpenCodeClient.GetHealthAsync`,
`Sessions.GetSessionClient(...)`, `SessionClient.GetMessageAsync`, guarded envelopes,
`OpenCodeRoutes`, response adapters), and one hand-written `Pipeline` owns endpoint authority,
Basic-auth/User-Agent decoration, buffering, throw-versus-`NoThrow`, and transport-failure
mapping. The public API is locked in a reviewed `PublicApiGenerator` baseline; the writer
refuses unmanifested overwrites and headerless manifest entries; packing still fails on the
partial-operation marker while breadth is pending. Demonstrated live 2026-08-13 against
`opencode2 serve` v0.0.0-next-17403 (pin `a6a712a` — deliberate version skew accepted): both
generated methods returned typed 200 payloads (`ServiceHealth`; `SessionMessageAssistant`
with its wire `id`).

A verified multi-agent review of PR #16 (2026-08-13) produced a milestone-anchored queue:
the blocker set (typed-spine leaks, silent route rewriting) lands on the PR branch itself
(#17) together with the performance-test infrastructure (#18); every other finding lives in
issues #19–#25 pinned to the milestone that resolves it — nothing on that list outlives the
M series.

The next work is M2 — the first breadth batch, planned when it starts.

## Milestones

Expand All @@ -23,40 +38,52 @@ is revisited at each milestone boundary.
1. **M1 — Walking skeleton.** `v2.health.get` + `v2.session.message` end to end
(SpecDocument → Binder → EmitPlan → Roslyn emitters → committed source under
`src/OpenCode.Sdk` → minimal transport core → callable client), demonstrated once by
hand against a real `opencode serve` with the output pasted into the PR. Two
independently mergeable arcs: selected compiler + committed models, then the callable
client with typed errors and `NoThrow`. Design reference:
hand against a real `opencode2 serve` with the output pasted into the PR. Arc B opens
with the v2 retarget task (pin snapshot, ingestion-wall admit rule, regenerated
closure). Two independently mergeable arcs: selected compiler + committed models
(landed), then the callable client with typed errors and `NoThrow`. Design reference:
`superpowers/specs/2026-08-11-production-walking-skeleton-design.md`.
2. **M2 — Breadth batches.** The generation profile grows in vertical operation batches;
each batch lands its curation rows, reachable models, operation methods, and contract
tests together.
3. **M3 — Streams.** SSE engine, live/durable subscribe with `after` resume; demo: watching
a real session's event stream. The net472 `ServicePointManager` item lands here.
4. **M4 — Launcher.** `OpenCodeServer.StartAsync` with three-OS acceptance (ADR-0001);
demo: the SDK starts the server itself and calls health. The net472 stdout/tree-kill
items land here.
5. **M5 — Full surface.** Legacy hub, complete generation profile, exclusion fingerprints
(ADR-0008), packaging unblocked.
tests together. Opens with the review hardening batch (#19 `Unknown*` serialization,
#20 password-semantics decision, #25 IVT decision); the generator fail-closed walls
batch (#21, includes the P2 envelope fold) and the naming/curation wall batch (#22)
land at this boundary.
3. **M3 — Streams.** SSE engine over the v2 stream surface (`v2.event.subscribe`,
`v2.session.log` with `after`/`follow`, cursor-paged `v2.message.list`); the v1
durable-stream design does not carry over and is re-derived here. Demo: watching a
real session's event stream. The net472 `ServicePointManager` item lands here. The
union single-pass deserialization and streaming adapter-boundary redesign (#23) land
on the M3 runway, gated on the performance baselines (#18).
4. **M4 — Launcher.** `OpenCodeServer.StartAsync` with three-OS acceptance (ADR-0001)
over `opencode2 serve`; demo: the SDK starts the server itself and calls health. The
net472 stdout/tree-kill items land here. (`serve --stdio`'s stdin leash and the
background service's discovery file are candidate mechanisms — decided in the M4 plan;
platform detail: research doc 15.)
5. **M5 — Full surface.** Complete generation profile over the protocol surface,
exclusion fingerprints (ADR-0008), packaging unblocked.
6. **M6 — Operational closure.** `refresh-spec`, Extensions DI breadth,
retry/telemetry/hooks, quarantine lane, nightly canary; durable decisions distill into
ADRs and the `superpowers/` documents retire.
retry/telemetry/hooks, quarantine lane, nightly canary (the performance suite joins
it); durable decisions distill into ADRs and the `superpowers/` documents retire. Any
hygiene-sweep leftovers (#24) are resolved here — nothing from the review queue
survives the M series.

## Open Questions

- **v2 GA watch** — the v2 line ships as `opencode2` (npm `@opencode-ai/cli@next`, desktop
beta via `update.opencode.ai`) with no GA date; the spec pin stays a deliberate snapshot,
refreshed at milestone boundaries. Platform detail: research doc 15.
- **`v2.session.log` semantics** — it replaces the v1 durable stream (`after` + `follow` on
an experimental path); resume guarantees are unestablished. Owned by M3.
- **Spec refresh cadence** — the `refresh-spec` tool lands in M6; the cadence policy stays
open.
- **Structural-union emission shape** — the five structural-union pin sites
(`Config.formatter` et al.) need an emission decision when a breadth batch first reaches
one (a public API review).
- **Structural-union emission shape** — the v1 pin had five structural-union sites
(`Config.formatter` et al.); the population is re-censused at the retarget, and the
emission decision lands when a breadth batch first reaches one (a public API review).
- **Release mechanics** — decided parts live in ADR-0006 (independent semver, per-merge
GitHub Packages CD, manual NuGet.org releases). Pre-1.0 numbering, `VersionPrefix`,
RELEASE_NOTES flow, and the concrete workflows are scheduled when the first publishable
increment approaches.
- **Upstream v2 watch:** the active upstream `v2` branch publishes its spec at
`packages/protocol/openapi.json`; its dialect adds single-element `allOf` wrappers
(validation keywords, occasionally with annotations), keeps `v2.`-prefixed operationIds,
and returns literals to single-value `enum` (research log sessions 12–13). No plan impact
today; re-check at each spec refresh and before the upstream-absorbing major.

## Known Gaps

Expand Down
18 changes: 9 additions & 9 deletions docs/adr/0004-generated-model-principles.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# Generated models: immutable, required, empty-not-null

Date: 2026-08-08
Date: 2026-08-13

Generated models are immutable by default (records, `init`-only properties, read-only
collections) and mirror the spec's `required` with the C# `required` modifier. Nullability is
a last resort: absent collections deserialize to empty instead of null, and nullable
annotations appear only where absence carries meaning in the contract. This trades wire-shape
fidelity (null vs missing vs empty collapses where the distinction carries no meaning) for
consumer ergonomics, thread-safety, and AOT-friendly serialization — a deliberate deviation a
reader might otherwise "fix" back toward spec-literal emission. These principles are generator
a last resort: absent optional collections deserialize to empty instead of null, while an
explicit wire `null` is rejected unless the schema admits null; nullable annotations appear
only where null carries meaning in the contract. This trades missing-vs-empty fidelity where
the distinction carries no meaning for consumer ergonomics, thread-safety, and AOT-friendly
serialization without weakening the schema's null contract. These principles are generator
policy: they apply mechanically to every emitted type.

## Generator policy additions (2026-08-09, API design + grill sessions)
## Generator policy

- **`Uri` for URL-semantic properties** (spec `format: uri` or curation-marked);
filesystem paths stay `string`. CA1056/CA1054 firing on a generated `*Url` string
property is the fail-closed detector; a per-property fallback to `string` is the
recorded escape for version-skew malformed URLs.
- **Acronym casing follows the Framework Design Guidelines** (`ApiError`, `Pty`, `Mcp`,
`Tui`; two-letter acronyms upper; brand spellings via curated exceptions — `OAuth`).
- **Acronyms use ordinary PascalCase regardless of length** (`Id`, `Io`, `Ip`, `Url`,
`Api`, `Pty`, `Mcp`, `Tui`); brand spellings use curated exceptions (`OAuth`).
- **Identifier mapping is mechanical** — every wire name maps to PascalCase with
`[JsonPropertyName]` carrying wire fidelity (`_tag`, snake_case, dotted schema
names).
Expand Down
29 changes: 0 additions & 29 deletions docs/adr/0005-both-api-surfaces.md

This file was deleted.

Loading