assistant-api + ui: manage a workspace's repositories (multi-repo workspaces)#595
Merged
Conversation
A workspace could only ever clone one repository: the workspace_repositories junction, the attach/detach commands, and the REPO_URLS orchestrator injection all existed (#590) but nothing populated the junction or exposed it, so multi-repo workspaces were unreachable and invisible. Backend: - CreateWorkspaceCommandHandler seeds workspace_repositories when a repo-backed workspace is created: the workspace's own repository as is_primary=true, and — when a projectId is present — the project's linked repositories as additional members, so the orchestrator emits REPO_URLS and the runner clones them all. - POST /api/v1/workspaces/{id}/repositories and DELETE /api/v1/workspaces/{id}/repositories/{repositoryId} dispatch the existing attach/detach commands (204). - GET /api/v1/workspaces/{id} now returns the workspace's repositories (id, name, repoUrl, isPrimary) via a detail envelope; list/create keep the existing summary response shape. Frontend: - The workspace view shows its repositories (primary marked), adds/removes repositories through the new endpoints, and a split-guidance panel assembles the `council split` command for extracting a path into a new repo and explains registering + linking it afterward. Built via the council with codex (planners, consolidator, workers, verifier). The OpenAPI contract changes (new endpoints + repositories field), so services/assistant-api/openapi.json and services/assistant-ui/src/api/generated.ts must be regenerated by the operator with `./gradlew :services:assistant-api:exportOpenApiSpec` and `pnpm --filter @personal-stack/assistant-ui contract:generate` — not hand-edited.
The wrapped `??` expression tripped style/operator-linebreak: eslint wants the operator at the line start, prettier moves it to the line end, and the pre-commit hook (eslint --fix then prettier) left prettier's form, which CI's eslint then rejected. Rewrite the computed to keep the `??` chain on one short line via an intermediate, so eslint and prettier agree.
Regenerated after adding workspace repository management endpoints and the repositories field to the workspace contract.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A workspace could only clone one repository — the
workspace_repositoriesjunction, attach/detach commands, andREPO_URLSorchestrator injection existed (#590), but nothing populated the junction or exposed it, so multi-repo workspaces were unreachable and invisible. This makes them real and manageable from the UI.Backend
CreateWorkspaceCommandHandlerseedsworkspace_repositorieson create: the workspace's own repo asis_primary=true, and — when aprojectIdis present — the project's linked repositories as additional members. So the orchestrator emitsREPO_URLSand the runner clones every repo.POST /api/v1/workspaces/{id}/repositories(attach, 204) andDELETE /api/v1/workspaces/{id}/repositories/{repositoryId}(detach, 204; primary refused).GET /api/v1/workspaces/{id}returns the workspace's repositories (id, name, repoUrl, isPrimary) via a detail envelope; list/create keep the existing summary shape.Frontend
council split --path <p> --dest <owner>/<name>command and explains registering + linking the new repo afterward.How it was built
Planned and implemented entirely through the council with codex — codex as both planners, consolidator, workers, and verifier (claude cost reported $0.00). 6-task DAG fanned out across codex workers in isolated worktrees.
Verification (this sandbox, no Docker)
:services:assistant-api:test— 284 unit tests pass (incl. the new handler/controller/query tests);compileIntegrationTestKotlinpasses (new Testcontainers flow test compiles; CI runs it);ktlintCheckclean; jOOQ regenerates.typecheckclean,eslint .clean, 53 vitest tests pass.This changes the assistant-api HTTP contract (new endpoints + the
repositoriesfield), so the pinned spec + generated client must be regenerated where the app can run (your env / docker-compose) — they are intentionally NOT hand-edited here:Until then CI's Validate OpenAPI Contract will be red; everything else is green. The frontend uses local pre-regeneration types that can switch to the generated aliases after.