Provider seam v2: generalize the agent-CLI contract (dialog kinds + multi-question, slash-command catalog, clear semantics, auth descriptor, provider-declared seeding) #51

Closed
opened 2026-07-08 16:49:46 +02:00 by dominik.polakovics · 1 comment

Goal

Evolve the AgentProvider seam so lab's chat surface works with any agent CLI (Codex CLI, Gemini CLI, …), not only Claude Code — without implementing any new provider in this issue. Deliverables: the generalized seam contract, the claude-code adapter brought up to it (including two new answerable dialog shapes), the new slash-command surface end to end, the auth/seeder/frontend generalizations, and the ADR + compat documentation.

All decisions below were settled with the operator on 2026-07-08 — do not re-litigate them. Ambiguities beyond them are yours to resolve in the spirit of the pinned choices.

Grounding (read first)

  • internal/provider/provider.go — the seam: AgentProvider, the universal message schema (text|tool|dialog|lifecycle, states idle|working|needs_input|question|ended), optional capabilities (DeepLinker, ConnectingReporter, DialogHooker).
  • internal/provider/claudecode/ — the only implementation: transcript mapper (chat.go, chat_types.go, chat_dialog.go), send-keys recipes (interact.go), dialog hook spool (dialogspool.go).
  • internal/chat/ — the provider-generic tailer and chat brain (per-session locks, spool overlay, rotation follow).
  • docs/adr/ 0008, 0016, 0017, 0020, 0021 — the seam's history. This issue amends 0016 and 0020.
  • internal/compat/ — the pinned-coupling registry (Claude Code 2.1.198). Every new fragile coupling added here gets its own section, fixture-driven and live-verified.

Pinned decisions

1. Read-through stands (reaffirms ADR-0016). Every provider runs its real TUI under tmux; the adapter maps the provider-native transcript into the universal schema on read. No lab-owned message store. Replies land in native history via the provider's own recording.

2. Clear history: no seam method. Three parts:

  • Discovery: the command catalog (decision 5) carries an optional semantic role per entry; role=clear marks the provider's native clear command (claude /clear; codex would be /new). The frontend may bind a "New conversation" affordance to it; execution rides the normal Reply/paste path.
  • Effect: what is today an accident of Claude's file rotation becomes a documented seam obligation: an adapter must surface a new conversation identity (today: transcript_pathtranscript_id) whenever the provider clears context. A provider that clears in place must synthesize a new epoch. Document this on LocateTranscript/ReadTranscript contract comments.
  • Visibility: sent slash commands render in the chat as plain user text (no schema change, no Command flag/kind). For claude-code this reverses half of the issue-#45 handling: isLocalCommandEcho blocks (<command-name>/<command-message>) map to a visible user message showing the command (e.g. /clear) instead of being dropped; non-empty <local-command-stdout> maps to a follow-up lifecycle message. Command echoes stay excluded from state derivation — the echo driving deriveState to "working" was the stuck-composer root cause; add a regression test for exactly this.

3. Dialog schema: kinds + multi-question.

  • Dialog gains Kind (question | plan | approval) and Questions []Question (each: header, question text, options with label+description, multiSelect, free-text-allowed). DialogAnswer grows per-question answers. Answerable stays a per-dialog property. API (GET /runs/{id}/messages pending_dialog, POST /runs/{id}/answer) and SPA follow.
  • The adapter owns both translation directions. Outbound: for TUI-owned pickers whose options are not in structured tool input, the adapter synthesizes the option rows as pinned constants (compat-verified per provider version — a write-side coupling like the keystroke recipes; the never-scrape rule is untouched, nothing reads the pane). Inbound: the adapter alone decides what an answer becomes (keystroke sequences for TUIs; could be RPC for a future provider). The generic layer stays pure data in/out; the existing guards (per-session lock, tool_id match, re-read under lock) stay generic.
  • Concretely for claude-code, two shapes become answerable in this issue:
    • Multi-question AskUserQuestion (today: render-degraded hint). Render the full form; the UI collects all answers in one submit; the adapter plays the sequenced picker recipe (per-question normalize-to-top climb incl. trailing synth rows, Down×idx, Space toggles, Enter, paced by keyDelay). The inter-picker transition must be captured live before trusting it.
    • Plan approval (ExitPlanMode) (today: plan text + deep-link hint). Kind=plan, plan body rendered as markdown, approve/reject options synthesized as pinned constants, answered via picker recipe.
  • Post-resolve verification backstop (required, both shapes): after the retro-flushed tool_use/tool_result lands in the transcript, the adapter compares recorded answers against what was intended and emits a lifecycle warning message on mismatch, so a blind-sequence desync is visible in the chat.
  • Live verification: prefer driving a real pinned-version Claude Code picker (the ADR-0020 process — it found the 250ms pacing race). If your environment cannot drive a live TUI, ship the recipes fixture-tested with the backstop active and state explicitly in the PR that live verification is outstanding.

4. Conversational state: unchanged, best-effort. The 5-state vocabulary is the contract. Baseline duty for every adapter: distinguish working/idle/ended from its transcript; needs_input/question arrive only where a live signal channel exists. No per-provider state metadata.

5. Slash-command catalog: new mandatory seam method.

Commands(ctx context.Context, worktree string) ([]CommandSpec, error)

CommandSpec: Name, Description, ArgHint, Source (builtin|project|user), optional Role (only clear defined for now; extensible), and a chat-safe flag so the adapter curates out commands that would strand the TUI in a picker lab cannot see. claude-code implementation: a pinned builtin table (compat section, version-verified) merged with a scan of its native custom-command locations — .claude/commands/, seeded skills under .claude/skills/ (user-invocable ones), user-level dirs. Served as GET /api/v1/runs/{id}/commands (worktree-dependent, hence per-run), briefly cached server-side. Frontend: autocomplete when the composer input starts with / (filter as you type: name, description, argHint), plus the optional role=clear affordance.

6. Live-signals capability generalization. Rename/reshape DialogHooker into a provider-neutral capability (e.g. LiveSignals) with the same contract (spawn-time setup returning settings payload + argv injection, pending-dialog read, blocked-state read, cheap change signature, spool sweep). claude-code keeps its PreToolUse/PostToolUse/Notification spool implementation. Providers without a verified structured channel simply omit the capability — honest degradation: never-scrape is universal, auto-approval spawn defaults keep dialogs rare, a residual blocked state degrades to needs_input + the copyable tmux attach affordance.

7. Auth surface generalization (design the descriptor, keep claude the only implementation).

  • Routes become /api/v1/providers/{id}/auth/{status|login|code|logout}; internal/httpapi stops importing claudecode for typed errors (move sentinel errors to the generic provider package or map behind the seam).
  • The provider declares an auth flow descriptor surfaced on GET /api/v1/providers: kind ∈ oauth-code (start returns a URL, user pastes a code back — claude today), oauth-redirect (localhost browser redirect; descriptor carries operator instructions, e.g. SSH port-forward), api-key (vault credential reference, injected at spawn via the existing materialization mechanism — schema only in this issue, no implementation needed), external (managed outside lab; status-only).
  • SSE event becomes provider-generic (e.g. provider.auth.changed with a provider id) — rename cleanly, SPA ships with the backend.
  • SPA: ClaudeAuthCard becomes a descriptor-driven ProviderAuthCard.

8. Seeding behind the seam. internal/seeder currently hardcodes claude shapes (.claude/skills, CLAUDE.local.md, exclude entries, attribution-scrub patterns for claude/anthropic.com). Make these provider-declared metadata (context-file name, skills/prompts layout, exclude entries, scrub patterns) consumed by one generic seeder; claude-code declares today's values. Keep the split between provider SeedWorkspace and the generic seeder coherent — one obvious place for each concern.

9. Provider-neutral frontend copy. RunChat.tsx hardcodes "Claude is waiting for your reply.", "open it in claude.ai", claude-styled chrome; drive user-facing strings from provider metadata (display name; the open affordance already comes from fallback_open). reposvc.DefaultProvider = "claude-code" stays (it is a default, not a leak).

10. Contract footnotes (document as seam contract comments):

  • Reply while working is legal and best-effort per TUI (claude queues natively); an adapter whose TUI drops mid-turn input returns a typed error the UI can surface.
  • Interrupt recipes are adapter-owned and must be hazard-checked (some TUIs quit on double-Esc/Ctrl-C).
  • Each future adapter gets its own internal/compat pin + fixture-driven port; nothing in this issue may weaken the existing claude pins.

Suggested implementation order

  1. Seam + schema: Dialog kinds/questions, DialogAnswer, Commands, DialogHookerLiveSignals rename, epoch/clear + footnote contract comments; providertest fakes grow every new surface.
  2. claude-code adapter: command-echo rendering (state-neutral, regression-tested), command catalog (pinned builtins + scans), multi-question mapping.
  3. Recipes: multi-question sequence + plan-approval synthesized options, post-resolve verification backstop, compat sections updated (§5 echo mapping, §7 recipes, new § for the builtin command table).
  4. HTTP/SSE: /runs/{id}/commands, per-question answer payload, generalized auth routes + descriptor + event, typed-error decoupling.
  5. Seeder metadata + generic seeder.
  6. Frontend: autocomplete, role=clear affordance, multi-question form rendered the way the Claude app renders it (stacked questions, header chip + question text, option buttons with label+description, multi-select toggles, "Other" free text, one submit), plan approval with real approve/reject buttons, approval-kind rendering, descriptor-driven auth card, provider-neutral copy.
  7. Docs: new ADR capturing these decisions (amends 0016/0020, next free number under docs/adr/), compat.md updates, CONTEXT.md only if new operator-facing vocabulary is introduced.

Acceptance criteria

  • AgentProvider carries Commands; Dialog kinds + Questions[] + per-question answers flow seam → API → SPA; DialogHooker renamed provider-neutral; all fakes updated; no claudecode import outside its package except the wiring in cmd/lab.
  • Sent slash commands appear in chat as user text; command echoes never drive state (regression test for the post-/clear stuck-composer case).
  • Transcript-rotation/clear obligation documented on the seam; existing transcript_id reset behavior unchanged.
  • Composer autocompletes from GET /runs/{id}/commands (builtins + project commands + seeded skills for claude-code); role=clear binding works; chat-unsafe commands are curated out.
  • Single-question dialogs behave exactly as before; multi-question and plan approval render with real content and are answerable; post-resolve mismatch backstop emits a visible warning; recipes live-verified or explicitly flagged as fixture-only in the PR.
  • Auth works end to end for claude via /providers/{id}/auth/... with descriptor-driven SPA card; no claude-typed errors in httpapi; SSE event renamed.
  • Seeder consumes provider-declared metadata; seeded worktree contents for claude-code are byte-identical to today.
  • No hardcoded "Claude"/"claude.ai" strings in SPA components (grep-guard); provider display metadata drives copy.
  • ADR written; compat.md sections added/updated; make gates (fmt/vet/tests, web build) green.

Out of scope

  • Codex/Gemini adapter implementations (#2 tracks Codex) and their verification spikes (Gemini transcript durability — the one open threat to read-through for that provider; Codex notify payload richness; pasted-slash execution on their TUIs).
  • Protocol-mode/push adapters (rejected for now), a lab-owned message store (rejected, ADR-0016), TUI pane scraping (rejected, universal).
  • Web push, embedded terminal (separate roadmap items).
## Goal Evolve the `AgentProvider` seam so lab's chat surface works with any agent CLI (Codex CLI, Gemini CLI, …), not only Claude Code — **without implementing any new provider in this issue**. Deliverables: the generalized seam contract, the claude-code adapter brought up to it (including two new answerable dialog shapes), the new slash-command surface end to end, the auth/seeder/frontend generalizations, and the ADR + compat documentation. All decisions below were settled with the operator on 2026-07-08 — do **not** re-litigate them. Ambiguities beyond them are yours to resolve in the spirit of the pinned choices. ## Grounding (read first) - `internal/provider/provider.go` — the seam: `AgentProvider`, the universal message schema (`text|tool|dialog|lifecycle`, states `idle|working|needs_input|question|ended`), optional capabilities (`DeepLinker`, `ConnectingReporter`, `DialogHooker`). - `internal/provider/claudecode/` — the only implementation: transcript mapper (`chat.go`, `chat_types.go`, `chat_dialog.go`), send-keys recipes (`interact.go`), dialog hook spool (`dialogspool.go`). - `internal/chat/` — the provider-generic tailer and chat brain (per-session locks, spool overlay, rotation follow). - `docs/adr/` 0008, 0016, 0017, 0020, 0021 — the seam's history. This issue **amends 0016 and 0020**. - `internal/compat/` — the pinned-coupling registry (Claude Code 2.1.198). Every new fragile coupling added here gets its own section, fixture-driven and live-verified. ## Pinned decisions **1. Read-through stands (reaffirms ADR-0016).** Every provider runs its real TUI under tmux; the adapter maps the provider-native transcript into the universal schema on read. No lab-owned message store. Replies land in native history via the provider's own recording. **2. Clear history: no seam method.** Three parts: - *Discovery*: the command catalog (decision 5) carries an optional semantic `role` per entry; `role=clear` marks the provider's native clear command (claude `/clear`; codex would be `/new`). The frontend may bind a "New conversation" affordance to it; execution rides the normal Reply/paste path. - *Effect*: what is today an accident of Claude's file rotation becomes a documented **seam obligation**: an adapter must surface a new conversation identity (today: `transcript_path` → `transcript_id`) whenever the provider clears context. A provider that clears in place must synthesize a new epoch. Document this on `LocateTranscript`/`ReadTranscript` contract comments. - *Visibility*: sent slash commands render in the chat as **plain user text** (no schema change, no Command flag/kind). For claude-code this reverses half of the issue-#45 handling: `isLocalCommandEcho` blocks (`<command-name>`/`<command-message>`) map to a visible user message showing the command (e.g. `/clear`) instead of being dropped; non-empty `<local-command-stdout>` maps to a follow-up lifecycle message. Command echoes stay **excluded from state derivation** — the echo driving `deriveState` to "working" was the stuck-composer root cause; add a regression test for exactly this. **3. Dialog schema: kinds + multi-question.** - `Dialog` gains `Kind` (`question | plan | approval`) and `Questions []Question` (each: header, question text, options with label+description, multiSelect, free-text-allowed). `DialogAnswer` grows per-question answers. `Answerable` stays a per-dialog property. API (`GET /runs/{id}/messages` `pending_dialog`, `POST /runs/{id}/answer`) and SPA follow. - **The adapter owns both translation directions.** Outbound: for TUI-owned pickers whose options are not in structured tool input, the adapter synthesizes the option rows as **pinned constants** (compat-verified per provider version — a write-side coupling like the keystroke recipes; the never-scrape rule is untouched, nothing reads the pane). Inbound: the adapter alone decides what an answer becomes (keystroke sequences for TUIs; could be RPC for a future provider). The generic layer stays pure data in/out; the existing guards (per-session lock, `tool_id` match, re-read under lock) stay generic. - Concretely for claude-code, two shapes become answerable in this issue: - **Multi-question `AskUserQuestion`** (today: render-degraded hint). Render the full form; the UI collects all answers in one submit; the adapter plays the sequenced picker recipe (per-question normalize-to-top climb incl. trailing synth rows, Down×idx, Space toggles, Enter, paced by `keyDelay`). The inter-picker transition must be captured live before trusting it. - **Plan approval (`ExitPlanMode`)** (today: plan text + deep-link hint). `Kind=plan`, plan body rendered as markdown, approve/reject options synthesized as pinned constants, answered via picker recipe. - **Post-resolve verification backstop** (required, both shapes): after the retro-flushed `tool_use`/`tool_result` lands in the transcript, the adapter compares recorded answers against what was intended and emits a lifecycle warning message on mismatch, so a blind-sequence desync is visible in the chat. - *Live verification*: prefer driving a real pinned-version Claude Code picker (the ADR-0020 process — it found the 250ms pacing race). If your environment cannot drive a live TUI, ship the recipes fixture-tested with the backstop active and state explicitly in the PR that live verification is outstanding. **4. Conversational state: unchanged, best-effort.** The 5-state vocabulary is the contract. Baseline duty for every adapter: distinguish `working`/`idle`/`ended` from its transcript; `needs_input`/`question` arrive only where a live signal channel exists. No per-provider state metadata. **5. Slash-command catalog: new mandatory seam method.** ```go Commands(ctx context.Context, worktree string) ([]CommandSpec, error) ``` `CommandSpec`: `Name`, `Description`, `ArgHint`, `Source` (`builtin|project|user`), optional `Role` (only `clear` defined for now; extensible), and a chat-safe flag so the adapter curates out commands that would strand the TUI in a picker lab cannot see. claude-code implementation: a pinned builtin table (compat section, version-verified) merged with a scan of its native custom-command locations — `.claude/commands/`, seeded skills under `.claude/skills/` (user-invocable ones), user-level dirs. Served as `GET /api/v1/runs/{id}/commands` (worktree-dependent, hence per-run), briefly cached server-side. Frontend: autocomplete when the composer input starts with `/` (filter as you type: name, description, argHint), plus the optional role=clear affordance. **6. Live-signals capability generalization.** Rename/reshape `DialogHooker` into a provider-neutral capability (e.g. `LiveSignals`) with the same contract (spawn-time setup returning settings payload + argv injection, pending-dialog read, blocked-state read, cheap change signature, spool sweep). claude-code keeps its PreToolUse/PostToolUse/Notification spool implementation. Providers without a verified structured channel simply omit the capability — **honest degradation**: never-scrape is universal, auto-approval spawn defaults keep dialogs rare, a residual blocked state degrades to `needs_input` + the copyable tmux attach affordance. **7. Auth surface generalization (design the descriptor, keep claude the only implementation).** - Routes become `/api/v1/providers/{id}/auth/{status|login|code|logout}`; `internal/httpapi` stops importing `claudecode` for typed errors (move sentinel errors to the generic `provider` package or map behind the seam). - The provider declares an **auth flow descriptor** surfaced on `GET /api/v1/providers`: kind ∈ `oauth-code` (start returns a URL, user pastes a code back — claude today), `oauth-redirect` (localhost browser redirect; descriptor carries operator instructions, e.g. SSH port-forward), `api-key` (vault credential reference, injected at spawn via the existing materialization mechanism — schema only in this issue, no implementation needed), `external` (managed outside lab; status-only). - SSE event becomes provider-generic (e.g. `provider.auth.changed` with a provider id) — rename cleanly, SPA ships with the backend. - SPA: `ClaudeAuthCard` becomes a descriptor-driven `ProviderAuthCard`. **8. Seeding behind the seam.** `internal/seeder` currently hardcodes claude shapes (`.claude/skills`, `CLAUDE.local.md`, exclude entries, attribution-scrub patterns for `claude`/`anthropic.com`). Make these provider-declared metadata (context-file name, skills/prompts layout, exclude entries, scrub patterns) consumed by one generic seeder; claude-code declares today's values. Keep the split between provider `SeedWorkspace` and the generic seeder coherent — one obvious place for each concern. **9. Provider-neutral frontend copy.** `RunChat.tsx` hardcodes "Claude is waiting for your reply.", "open it in claude.ai", claude-styled chrome; drive user-facing strings from provider metadata (display name; the open affordance already comes from `fallback_open`). `reposvc.DefaultProvider = "claude-code"` stays (it is a default, not a leak). **10. Contract footnotes** (document as seam contract comments): - `Reply` while `working` is legal and best-effort per TUI (claude queues natively); an adapter whose TUI drops mid-turn input returns a typed error the UI can surface. - Interrupt recipes are adapter-owned and must be hazard-checked (some TUIs quit on double-Esc/Ctrl-C). - Each future adapter gets its own `internal/compat` pin + fixture-driven port; nothing in this issue may weaken the existing claude pins. ## Suggested implementation order 1. Seam + schema: Dialog kinds/questions, `DialogAnswer`, `Commands`, `DialogHooker`→`LiveSignals` rename, epoch/clear + footnote contract comments; `providertest` fakes grow every new surface. 2. claude-code adapter: command-echo rendering (state-neutral, regression-tested), command catalog (pinned builtins + scans), multi-question mapping. 3. Recipes: multi-question sequence + plan-approval synthesized options, post-resolve verification backstop, compat sections updated (§5 echo mapping, §7 recipes, new § for the builtin command table). 4. HTTP/SSE: `/runs/{id}/commands`, per-question answer payload, generalized auth routes + descriptor + event, typed-error decoupling. 5. Seeder metadata + generic seeder. 6. Frontend: autocomplete, role=clear affordance, multi-question form rendered the way the Claude app renders it (stacked questions, header chip + question text, option buttons with label+description, multi-select toggles, "Other" free text, one submit), plan approval with real approve/reject buttons, approval-kind rendering, descriptor-driven auth card, provider-neutral copy. 7. Docs: new ADR capturing these decisions (amends 0016/0020, next free number under `docs/adr/`), `compat.md` updates, CONTEXT.md only if new operator-facing vocabulary is introduced. ## Acceptance criteria - [ ] `AgentProvider` carries `Commands`; Dialog kinds + `Questions[]` + per-question answers flow seam → API → SPA; `DialogHooker` renamed provider-neutral; all fakes updated; no `claudecode` import outside its package except the wiring in `cmd/lab`. - [ ] Sent slash commands appear in chat as user text; command echoes never drive state (regression test for the post-/clear stuck-composer case). - [ ] Transcript-rotation/clear obligation documented on the seam; existing `transcript_id` reset behavior unchanged. - [ ] Composer autocompletes from `GET /runs/{id}/commands` (builtins + project commands + seeded skills for claude-code); role=clear binding works; chat-unsafe commands are curated out. - [ ] Single-question dialogs behave exactly as before; multi-question and plan approval render with real content and are answerable; post-resolve mismatch backstop emits a visible warning; recipes live-verified or explicitly flagged as fixture-only in the PR. - [ ] Auth works end to end for claude via `/providers/{id}/auth/...` with descriptor-driven SPA card; no claude-typed errors in `httpapi`; SSE event renamed. - [ ] Seeder consumes provider-declared metadata; seeded worktree contents for claude-code are byte-identical to today. - [ ] No hardcoded "Claude"/"claude.ai" strings in SPA components (grep-guard); provider display metadata drives copy. - [ ] ADR written; compat.md sections added/updated; `make` gates (fmt/vet/tests, web build) green. ## Out of scope - Codex/Gemini adapter implementations (#2 tracks Codex) and their verification spikes (Gemini transcript durability — the one open threat to read-through for that provider; Codex `notify` payload richness; pasted-slash execution on their TUIs). - Protocol-mode/push adapters (rejected for now), a lab-owned message store (rejected, ADR-0016), TUI pane scraping (rejected, universal). - Web push, embedded terminal (separate roadmap items).
Author
Owner

This was generated by AI while landing a PR.

land-pr audit — PR #53 "feat: provider seam v2 — generalize the agent-CLI contract" → PASS

What was checked:

  • State/gate: open, not a draft; head lab/20260708-1652 (60b018e) sits exactly one commit atop current origin/main (64ee80c) — base unmoved, no conflict possible. Title is Conventional Commits (feat:); body carries a working Closes #51.
  • Verification signal relied on: the required native CI job (ci.yml) runs on every PR and branch protection enforces it green at merge — not re-run here. ci-nix is path-gated to *.nix/flake.lock/go.mod/go.sum; this diff touches none, so its skip is legitimate. The PR additionally documents local gates green (go test with the known pre-existing tmuxx sandbox failure; 452 vitest, lint, format, build) and live recipe verification against pinned Claude Code 2.1.198, plus three in-session adversarial reviews whose findings were fixed with regression tests.
  • Diff review (82 files, +6925/−1224 vs origin/main): maps 1:1 onto the issue's pinned decisions 1–10 and the suggested seven-workstream order. Acceptance criteria verified mechanically:
    • AgentProvider carries Commands (ChatSafe curation, role=clear); Dialog Kind/Questions[] + per-question Answers[]; DialogHooker fully renamed to LiveSignals (only a historical comment mentions the old name); AuthFlow descriptor + SeedMeta on the interface.
    • Clear/epoch obligation documented on LocateTranscript/ReadTranscript; reply-while-working and interrupt-hazard footnotes present as contract comments.
    • internal/httpapi imports no claudecode; sentinels live in the generic provider package; SSE event is provider.auth.changed.
    • Regression tests present by name: TestParseTranscript_commandEchoNeverDrivesState (the post-/clear stuck composer), TestParseTranscript_localCommandEchoRenders, live TestCompat_Live_askUserQuestionRecipe / TestCompat_Live_exitPlanModeApproval.
    • Review-found defects verifiably fixed: empty-SeedMeta pre-push hook gets a : no-op body with sh -n parse checks in hook_golden_test.go; "New conversation" is gated on state() !== 'working' + !composerLocked() with tests for working/locked/ended.
    • providerNeutral.test.ts grep-guard: no allowlist, includes a can-never-go-blind sanity check.
  • Non-blocking notes: (1) internal/compat tests import claudecode — pre-existing and by design (compat is the claude pin registry; the live-recipes harness must drive the real adapter); all production layers are clean. (2) Cosmetic: the generic provider.ErrDialogNotAnswerable message hardcodes "open it in claude.ai" — accurate for the only current provider, but a second adapter wrapping this sentinel would inherit the copy; tweak when a second provider lands.

Verdict: PASS — awaiting the operator's explicit merge go-ahead.

> *This was generated by AI while landing a PR.* **land-pr audit — PR #53 "feat: provider seam v2 — generalize the agent-CLI contract" → PASS** What was checked: - **State/gate**: open, not a draft; head `lab/20260708-1652` (60b018e) sits exactly one commit atop current `origin/main` (64ee80c) — base unmoved, no conflict possible. Title is Conventional Commits (`feat:`); body carries a working `Closes #51`. - **Verification signal relied on**: the required `native` CI job (ci.yml) runs on every PR and branch protection enforces it green at merge — not re-run here. `ci-nix` is path-gated to `*.nix`/`flake.lock`/`go.mod`/`go.sum`; this diff touches none, so its skip is legitimate. The PR additionally documents local gates green (go test with the known pre-existing tmuxx sandbox failure; 452 vitest, lint, format, build) and live recipe verification against pinned Claude Code 2.1.198, plus three in-session adversarial reviews whose findings were fixed with regression tests. - **Diff review** (82 files, +6925/−1224 vs origin/main): maps 1:1 onto the issue's pinned decisions 1–10 and the suggested seven-workstream order. Acceptance criteria verified mechanically: - `AgentProvider` carries `Commands` (ChatSafe curation, role=clear); Dialog `Kind`/`Questions[]` + per-question `Answers[]`; `DialogHooker` fully renamed to `LiveSignals` (only a historical comment mentions the old name); `AuthFlow` descriptor + `SeedMeta` on the interface. - Clear/epoch obligation documented on `LocateTranscript`/`ReadTranscript`; reply-while-working and interrupt-hazard footnotes present as contract comments. - `internal/httpapi` imports no `claudecode`; sentinels live in the generic `provider` package; SSE event is `provider.auth.changed`. - Regression tests present by name: `TestParseTranscript_commandEchoNeverDrivesState` (the post-/clear stuck composer), `TestParseTranscript_localCommandEchoRenders`, live `TestCompat_Live_askUserQuestionRecipe` / `TestCompat_Live_exitPlanModeApproval`. - Review-found defects verifiably fixed: empty-`SeedMeta` pre-push hook gets a `:` no-op body with `sh -n` parse checks in `hook_golden_test.go`; "New conversation" is gated on `state() !== 'working'` + `!composerLocked()` with tests for working/locked/ended. - `providerNeutral.test.ts` grep-guard: no allowlist, includes a can-never-go-blind sanity check. - **Non-blocking notes**: (1) `internal/compat` tests import `claudecode` — pre-existing and by design (compat is the claude pin registry; the live-recipes harness must drive the real adapter); all production layers are clean. (2) Cosmetic: the generic `provider.ErrDialogNotAnswerable` message hardcodes "open it in claude.ai" — accurate for the only current provider, but a second adapter wrapping this sentinel would inherit the copy; tweak when a second provider lands. Verdict: **PASS** — awaiting the operator's explicit merge go-ahead.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#51
No description provided.