Skip to content

Implement real CorveilTaskBackend (replace stub, follow JiraTaskBackend template) — depends on corveil#1362-1365 #495

@dgershman

Description

@dgershman

Goal: flip Corveil from a stub TaskBackend to a real one, using the same template JiraTaskBackend already established. Crow's Provider enum, Session.codeProvider pairing, and capability-flag gating are already in place — this ticket just fills the concrete backend slot.

Depends on: four upstream CLI gaps being closed in radiusmethod/corveil so the Swift implementation can be straightforward (no caching, no URL synthesis, no fanout, no client-side filtering):

Corveil ticket What it unlocks for this implementation
corveil#1362@me assignee shortcut listAssigned/assign/createTask pass @me directly; no whoami cache
corveil#1363url field in task JSON fetchTask reads task.url straight from the CLI output; no URL synthesis
corveil#1364 — bulk fetch via --ids Declare the .batchedQuery capability; one HTTP request per poll
corveil#1365 — filter by --external-ref PR-to-task reverse lookup for auto-close on PR merge

This ticket assumes those four have landed. If they slip, the implementation gets uglier but not blocked — just adds the workarounds documented in those tickets.

Deliverables

1. CorveilTaskBackend.swift — real backend, mirroring JiraTaskBackend

New file: Packages/CrowProvider/Sources/CrowProvider/Backends/CorveilTaskBackend.swift

Replaces StubCorveilTaskBackend.swift (which can be removed once this lands — keep until the factory swap to avoid breaking the build). Same structure as JiraTaskBackend.swift (178 LOC); expect ~200 LOC.

Method-by-method CLI mapping:

Protocol method corveil CLI call
fetchTask(url:) parse <id> from URL → corveil task get <id>
listAssigned(includeClosed:) corveil task list --assignee @me --status open (and --status closed if includeClosed)
setLabels(url:add:remove:) corveil task update <id> --add-label X --remove-label Y (per label, or batched via repeat flag)
setTaskStatus(url:status:) map TicketStatusopen/in_progress/closedcorveil task update <id> --status X
assign(url:to:) corveil task update <id> --assignee user:<uuid> (or @me for self)
createTask(repo:title:body:labels:) corveil task create --title X --description Y --label … --assignee @me

provider: .corveil. capabilities: [.batchedQuery, .projectBoardStatus].batchedQuery because corveil#1364 lets us; .projectBoardStatus because corveil exposes the in_progress intermediate state.

Constructor takes a shellRunner: any ShellRunner (matches every other backend) so FakeShellRunner can mock CLI invocations in tests.

Discover the corveil binary via the same PATH-walk + defaults.binaries.corveil mechanism shipped in #485/#487/#489 — no new discovery logic.

2. WorkspaceInfo schema extension

File: Packages/CrowCore/Sources/CrowCore/Models/AppConfig.swift:271-345 (alongside jiraSite / jiraProjectKey / jiraJQL)

Add:

public var corveilHost: String?  // e.g. "corveil.acme.io" — only needed for URL routing; CLI authenticates against its own configured host independently

Single field. The corveil CLI already manages its own auth state and target host via corveil login / CORVEIL_URL — Crow doesn't need to pipe those through. corveilHost exists only so URL detection in ProviderManager can route self-hosted Corveil URLs (e.g. corveil.mycompany.com/dashboard/tasks/42) to the right backend.

If unset, the existing corveil.io default in ProviderManager.detect() still routes the public corveil domain. Most users won't need to set this.

3. ProviderManager factory + URL detection

File: Packages/CrowProvider/Sources/CrowProvider/ProviderManager.swift

Two small changes:

  1. Line 25 (taskBackend(for:)): replace the .corveil case from StubCorveilTaskBackend() to CorveilTaskBackend(shellRunner: ...).
  2. Lines 60-76 (detect(url:)): extend the existing corveil.io check to also match any host configured via WorkspaceInfo.corveilHost (pass the additional-host list in the same way additionalGitLabHosts is handled today).

4. Status mapping (the only deliberate design decision)

Corveil's vocabulary: open / in_progress / closed.
Crow's TicketStatus: backlog / ready / inProgress / inReview / done.

Recommended mapping:

Corveil → Crow Crow → Corveil
open.ready .backlogopen
in_progress.inProgress .readyopen
closed.done .inProgressin_progress
.inReviewin_progress (no Corveil intermediate distinguishes "in review")
.doneclosed

Document this in a comment + ADR update so the asymmetry is captured. .inReview clamping to in_progress is the only lossy direction; Crow's setTaskStatus for .inReview on a Corveil task moves it to in_progress and the UI surfacing (project-board status capability) handles the visual distinction.

5. Workspace Settings UI

File: Packages/CrowUI/Sources/CrowUI/SettingsView.swift

Add a single field row for corveilHost in the workspace editor, mirroring the existing Jira workspace UI rows (jiraSite, etc.). Label: "Corveil host". Tooltip: "Only needed for self-hosted Corveil instances. Public corveil.io is auto-detected."

No Verify button — the existing Corveil CLI picker (defaults.binaries.corveil from #482/#483) handles binary verification, and corveil whoami is the auth probe (user runs it from a terminal).

6. ADR 0005 update

File: docs/adr/0005-task-and-code-backend-protocols.md

In the Decision table around line 56-60 (the "Concrete implementations" table), flip the Corveil row:

Backend Provider Capabilities
CorveilTaskBackend .corveil [batchedQuery, projectBoardStatus]

Remove StubCorveilTaskBackend from the table. Note in the Consequences section that Corveil is now a real provider alongside Jira (both isTaskOnly: true, both paired with a CodeBackend via Session.codeProvider).

Bump the ADR's status if it's still Proposed (it shouldn't be — #411 merged), but verify and clean up if needed.

7. Tests

New file: Packages/CrowProvider/Tests/CrowProviderTests/CorveilTaskBackendTests.swift

Mirror JiraTaskBackendTests.swift structure. Each protocol method gets a test using FakeShellRunner that records the command vector and returns canned JSON.

Coverage:

  • Each method's happy path (asserts the right corveil task X command was built).
  • Status mapping (each TicketStatus ↔ corveil status round-trip).
  • URL detection routing — corveil.io/dashboard/tasks/42 and a configured self-hosted host both route to .corveil.
  • Capability flags declared correctly.

Out of scope

  • Corveil-side CLI improvements — those are the four radiusmethod/corveil tickets (#1362-1365). Land them there.
  • A new crow-create-corveil-task skill. The existing /crow-create-ticket skill should grow a corveil branch (parallel to its existing github/gitlab/jira branches in setup.sh and SKILL.md). That's a sibling ticket against the crow/skills/crow-create-ticket/ source, not this one. Worth filing as a follow-up once this backend ships.
  • Real-time webhook subscription for task state changes. Polling via Crow's existing IssueTracker is sufficient for v1; webhooks are a separate ticket if/when needed.
  • PR-merge → task-close auto-link. The mechanism (corveil#1365 + Crow's existing PR-watch path in IssueTracker.swift) will be in place; wiring them is a small follow-up ticket once both sides land.

Acceptance

  1. A workspace configured with a Corveil task and any code provider (github/gitlab) opens a Crow coding session that tracks the Corveil task end-to-end:
    • Session card displays the corveil task title/number/URL.
    • listAssigned on app launch fetches the user's open Corveil tasks in one batched --ids call (post-corveil#1364), populating the assigned-issue panel.
    • Marking the session in-review via the project-board affordance calls setTaskStatus(.inReview) which translates to corveil task update <id> --status in_progress.
    • Completing the session calls setTaskStatus(.done)corveil task update <id> --status closed (or corveil task close <id> if that's preferred by the implementation).
  2. URL routing: pasting https://corveil.io/dashboard/tasks/42 (or a self-hosted Corveil URL whose host matches workspace config) into the ticket-attach flow routes to CorveilTaskBackend.fetchTask and pulls the task metadata.
  3. StubCorveilTaskBackend.swift is removed; references to it in ProviderManager.taskBackend(for: .corveil) flip to CorveilTaskBackend.
  4. ADR 0005's Corveil row reads "real backend with [batchedQuery, projectBoardStatus] capabilities" rather than "stub that throws".
  5. FakeShellRunner-based tests covering every method pass.
  6. No regressions to GitHub / GitLab / Jira backends — the changes are purely additive at the factory and Provider enum boundary.

Critical files

Purpose Path
New real backend Packages/CrowProvider/Sources/CrowProvider/Backends/CorveilTaskBackend.swift (new)
Stub to remove after flip Packages/CrowProvider/Sources/CrowProvider/Backends/StubCorveilTaskBackend.swift (delete after factory swap)
Factory + URL detection Packages/CrowProvider/Sources/CrowProvider/ProviderManager.swift:25,60-76
Workspace schema Packages/CrowCore/Sources/CrowCore/Models/AppConfig.swift:271-345
Template to mirror Packages/CrowProvider/Sources/CrowProvider/Backends/JiraTaskBackend.swift
Settings UI Packages/CrowUI/Sources/CrowUI/SettingsView.swift (Jira workspace row, around the jiraSite field)
ADR docs/adr/0005-task-and-code-backend-protocols.md
Tests Packages/CrowProvider/Tests/CrowProviderTests/CorveilTaskBackendTests.swift (new)

Related

🐦‍⬛ Created with Crow via Claude Code

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions