Skip to content

feat(spine): reshape Workspace → board (Projects v2) spanning many repos + multi-repo sessions#30

Open
hoangsnowy wants to merge 4 commits into
mainfrom
feat/spine-boards-reshape
Open

feat(spine): reshape Workspace → board (Projects v2) spanning many repos + multi-repo sessions#30
hoangsnowy wants to merge 4 commits into
mainfrom
feat/spine-boards-reshape

Conversation

@hoangsnowy
Copy link
Copy Markdown
Owner

@hoangsnowy hoangsnowy commented Jun 2, 2026

Summary

A Workspace was one repo; it is now a planning board (GitHub Projects v2) that spans many repos, surfaced as a unified, filterable ticket feed — and a session can work a ticket across many repos at once, opening one linked PR per repo. "1 workspace = 1 repo" was the M2 shortcut and the root of the GitHub/ADO UX pain; this matches the roadmap's H2 "Boards" intent and how multi-service work actually looks.

GitHub-first; Azure DevOps stays a graceful stub behind ISourceProvider (drops in later with no reshape). All work stays inside the existing modules — no new products.

Stages (all in this PR)

  1. Board model + unified ticket feed — Workspace → board (Projects v2, many repos); GitHubProjectsClient reads boards + items via raw GraphQL (Octokit REST has no Projects v2); SpineApp "Boards" tab with repos sub-list + tickets filtered by repo/label/status.
  2. Multi-repo session modelSessionRepoEntity child (N target repos per session, each its own branch/PR/status); sessions show a per-repo breakdown.
  3. Multi-repo executionIssueWorkRequest carries N repos; IssueWorkAgent runs per repo; "Work on this" → repo multi-select dialog; RunSession opens one PR per repo (incremental write-back, per-repo try/catch); RecomputeSessionStatus rolls children up.
  4. Cleanup — removed dead IGitHubIssueService; relabeled Settings GitHub tab as Pipeline-only (Spine uses per-board tokens); app caption → "Boards → tickets → AI sessions".

Key design

  • WorkspaceDescriptor.ForRepo(board,…) insulation factory → per-repo services (PR creation, repo validate) keep their signatures. Low blast radius.
  • Two EF migrations with data folds: ReshapeWorkspaceToBoard (each existing workspace → board + one repo) and AddSessionRepos (each existing session → one child repo). No data loss.
  • "Server thinks, runner does" preserved — runner_shell already takes working_dir, so N repos = N working dirs, no protocol change.

Test plan

  • dotnet test AgentOs.slnx -c Release349 passed / 5 skipped (live-LLM) / 0 failed, 0 warnings (TreatWarningsAsErrors).
  • New unit tests: board connect/add-repo, GraphQL→BoardTicket mapping (Issue/PR/DraftIssue, null-repo drafts, Status), multi-repo agent loop (per-repo outcomes, one-fails aggregate, empty repos), BoardTicketService.
  • Standalone Web (no-op repos): Spine window opens, Boards UI renders, no console errors, no phantom CSS, no regression across the multi-repo changes.
  • Full Aspire stack + a real PAT (repo + read:project) + a real Projects v2 board — needed to verify the live GraphQL ticket fetch, persistence, the migration folds, and an end-to-end multi-repo run (needs a paired runner). (Maintainer creds required.)

Maintainer verification (real-service path)

  1. dotnet run --project infra/AgentOs.AppHost → Web https://localhost:5180, login operator/operator.
  2. Spine → Boards → Connect a board: owner + scope + Projects v2 number + a PAT with repo + read:project.
  3. Open board → Add repositories (the services it spans) → confirm the unified tickets list loads + filters work.
  4. Work on this on a ticket → pick repos in the dialog → Sessions tab → Run → confirm one branch + PR per repo (a paired runner must be online).

🤖 Generated with Claude Code

hoangsnowy and others added 3 commits June 2, 2026 21:50
…ny repos

A Workspace was one repo; it is now a planning board (GitHub Projects v2)
that spans many repos, surfaced as a unified, filterable ticket feed.

- Domain: BoardDescriptor + board members on ISourceProvider + BoardTicket/
  BoardSummary/BoardValidation. WorkspaceDescriptor.ForRepo insulation factory
  keeps per-repo services (PR creation, repo validate) signature-unchanged.
- Integration: GitHubProjectsClient reads boards + items via raw GraphQL
  (Octokit REST has no Projects v2) projecting Issue/PR/DraftIssue + Status;
  BoardTicketService; ADO stays a graceful stub behind the same seam.
- Workspaces: WorkspaceEntity repurposed as the board binding + a
  WorkspaceRepoEntity child (cascade FK). ReshapeWorkspaceToBoard migration
  folds each existing single-repo workspace into board + one repo row.
  Repo-child CRUD + a validated AddRepoAsync on the connector.
- Sessions: a session captures its target repo (RepoOwner/RepoName/branch) so
  a ticket -> session run stays coherent; AddSessionRepoCoords migration.
- Web: SpineApp "Boards" tab — connect-a-board, repos sub-list, unified tickets
  with repo/label/status filters; sessions show their target repo.
- Tests: board connect + add-repo + GraphQL->BoardTicket mapping (346 passed).

Stage 1 of the board reshape. Multi-repo execution (one session -> N repos ->
N linked PRs) follows in a later PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A lightweight "web" config (dotnet run src/AgentOs.Web, no-op repos) alongside
the full "aspire" stack, for fast standalone UI verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generalize a session from one repo to many: a board ticket can be worked
across several services at once, each getting its own branch + PR.

- Domain: IssueWorkRequest now carries a list of WorkRepo; IssueWorkResult
  returns a per-repo RepoWorkOutcome list.
- Pipeline: IssueWorkAgent runs the agentic loop sequentially per repo
  (reusing the single-repo prompt + parser); IssueWorkPrompt is per-repo with
  a cross-service note so the PRs cross-link.
- Sessions: new SessionRepoEntity child (N target repos per session, each with
  its own branch/PR/status); RemoteSessionEntity keeps a primary repo for
  display + gains BoardItemNodeId/TicketKind. AddSessionRepos migration folds
  existing single-repo sessions into one child row. Child CRUD +
  RecomputeSessionStatus (children roll up into the parent).
- Web: "Work on this" opens a repo multi-select dialog (pre-selects the
  ticket's repo); RunSession loops repos → one PR each, writing results back
  incrementally; the Sessions tab shows a per-repo breakdown with a PR link.
- Cleanup: removed the now-dead IGitHubIssueService/GitHubIssueService;
  relabeled the Settings GitHub tab as Pipeline-only (Spine uses per-board
  tokens); Spine app caption → "Boards → tickets → AI sessions".
- Tests: multi-repo agent loop (per-repo outcomes, one-fails aggregate, empty
  repos). 349 passed / 0 failed.

Stages 2-4 of the board reshape; completes one session -> N repos -> N PRs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hoangsnowy hoangsnowy changed the title feat(spine): reshape Workspace into a board (Projects v2) spanning many repos — Stage 1 feat(spine): reshape Workspace → board (Projects v2) spanning many repos + multi-repo sessions Jun 2, 2026
The dev-auth UI E2E (SpineAppTests) asserted the pre-reshape "Projects" tab +
the connect-a-repository PAT placeholder; the board reshape renamed them
(Projects→Boards, new board PAT hint). Point the tests at the "Boards" tab +
the connect-a-board placeholder. These are gated behind RUN_AGENTOS_E2E so they
were skipped locally but run in CI's dev-auth step. Verified 3/3 against the
standalone Web.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant