* Step 22 B: RepoContext over the current repository A RepoContext bundles a repository's primary checkout with the state that lives inside or is keyed by it (results, artifact store) and carries its name. Today there is exactly one, opened over the current working directory; the result and artifact-store beans now come from it, so nothing else changes yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Step 22 B: the watcher polls a RepoContext start/poll/recoverOnStartup take the context instead of a working directory and read results and artifacts from it; the per-repository poll memory (logged fetch error, deprecation warning, cached branch definitions) moves into a RepoWatch keyed by context, so the next session can iterate contexts without one repository's outage silencing another's. The shared WatcherState is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Step 22 B: the executor runs builds of a RepoContext startBuild takes the context first; builds serialize per (context, branch) and share the global maxConcurrent cap across repositories, results and artifacts go to the build's own context. ConsoleBuildRunner, the build/retry commands and the builds API restart pass the current repository's context along. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Step 22 B: the UI and the branch listing read their RepoContext UiController and BuildsApiController take the current repository's context instead of a settable working directory; BranchListing lists the branches of a context and reads the results from it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Step 22 B: document the RepoContext, PR-doc for PR #11 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
6.7 KiB
WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.
The Problem
ADR 0009 (PR #10) decided that one Werkator instance serves a set of repositories, but the code assumes a single one everywhere: the executor, the watcher, the commands, and the controllers resolve results, artifacts, worktrees, and git access through an implicit working directory, and the result repository and artifact store are context-wide beans.
A registry of repositories cannot be threaded through that — every code path would have to learn a workingDir parameter it does not have and a results file it cannot pick.
Step 22 session B is the behavior-preserving refactor that gives those paths one explicit handle to a repository, so that sessions C and D only have to open more of them and put a name on the routes.
Non-Goals
- The registry, the home
~/.werkator.yml, and N repositories (session C). - Repository-scoped routes, API paths, or UI grouping (session D) — every route, template, and JSON shape is unchanged.
- Any configuration change;
docs/configuration.mdis untouched.
The Scenarios
Feature: one explicit handle per repository
Background
- A
RepoContextbundles what is repository-scoped: the primary checkout, the repository's results file, its artifact store, and a short name (the directory basename by default) meant for display and, later, routes. - The context object is the identity: executor pools and the watcher's memory are keyed by it, so exactly one is opened per repository.
Scenario#11.01: Builds are serialized per repository and branch under one global cap
So that two repositories in one instance never build the same branch name in each other's worktree, while the instance-level executor.maxConcurrent stays the only concurrency limit.
- Given the executor and a
RepoContext - When
startBuild(repo, branch, commit, build)is called - Then the PENDING result is written to that context's results and the artifacts persist to that context's store
- and a second build of the same branch in the same context waits for the first, while other branches run concurrently up to the global cap
- and a duplicate is only detected within the same context.
Verified by
- BuildExecutorTest (the existing serialization, concurrency, and duplicate tests, now over a context)
- BuildExecutorArtifactIntegrationTest
Scenario#11.02: The watcher polls a repository context and keeps its memory per repository
So that the next session can iterate contexts in one cycle without one repository's fetch outage silencing another's log or cache.
- Given the watcher and a
RepoContext - When
start(repo),poll(repo), orrecoverOnStartup(repo)runs - Then results, artifacts, auto-build slots, and worktrees are those of the context
- and the logged fetch error, the
autoBuilddeprecation warning, and the cached branch definitions are remembered per context.
- and the logged fetch error, the
Verified by
- WatcherTest (every existing poll, recovery, and prune test, now over a context)
- ServerModeApplicationTest (the server profile starts the watcher over the served repository)
Scenario#11.03: A single-repository installation behaves exactly as before
So that no route, file location, or display changes for existing installations.
- Given no registry (there is none yet)
- When the CLI or the server starts in a repository
- Then the current working directory is the one context, named after its directory
- and the result and artifact-store beans are that context's members, so
status, the JSON API, and the UI read the same files as before.
- and the result and artifact-store beans are that context's members, so
Verified by
- RepoContextsTest
- the unchanged controller, command, and integration tests of the full suite
The Solution
RepoContext (repo package) is a plain class with name, workingDir, results, and artifactStore; RepoContexts.open(dir) builds one over .git/werkator/build-results.json and a FileArtifactStore keyed by the path, and RepoConfiguration provides the current directory as the single bean.
BuildExecutor.startBuild takes the context first, keeps its per-branch serial workers in a map keyed by (context, branch), and writes results and artifacts through the build's own context; the semaphore stays one per executor, since the cap is instance-level per ADR 0009.
Watcher.start/poll/recoverOnStartup take the context, and the three mutable per-repository fields moved into a RepoWatch keyed by context; the observable WatcherState is untouched.
ConsoleBuildRunner, BuildCommand, RetryCommand, BuildsApiController, UiController, and BranchListing lost their settable workingDir in favor of the injected context.
Git access and config loading stay path-based services taking repo.workingDir: the home defaults: layer of session C is the point where config loading needs the context, and it was not built ahead of that need.
The open artifactKey question is decided against a repository prefix: the results file and the artifact store are per repository, so the key only has to be unique within one, and the repo dimension will enter through the route segment.
Open Questions
RunningBuildcarries no repository, socurrentBuilds()and the watcher's worktree pruning cannot tell repositories apart yet — harmless with one context, listed for session C in the plan.StateDirMigration, the metrics collector's repository size, andServerCommand's config still read the current directory — instance-level or per-registry-entry concerns, deferred to session C.
Additional Changes
- Architecture skill: new "Repository Context" section; the executor and watcher paragraphs describe the context-based signatures.
- AGENTS.md:
repoin the package list and a hard invariant that repository-scoped state goes through aRepoContext. docs/plan/22-multi-repo.md: session B ticked with the carry-overs to session C, theartifactKeyquestion decided.
Prerequisite PRs
- PR #10 (ADR 0009 and the step 22 roadmap).
Follow-up PRs
- Session C: the registry and N repositories, watcher multiplexing.
- Session D: server/API/UI repo scoping.
- Session E: rollout on mih34 with Werkbaum.