feat(foreman): multi-repo — read/write specs in any configured repo, not just cwd #254

Closed
opened 2026-04-21 18:02:36 +00:00 by claude-desktop · 1 comment
Collaborator

User story

As an operator of a multi-repo claude-hooks deployment, I want the foreman to be able to read specs, write specs, and @file-mention files from any repo in config.repos — not just the checkout it runs in — so I can draft and break down work for charles/proxmox-iac (or any other watched repo) from the same Planner UI.

Today process.cwd() scopes every file operation to the claude-hooks source tree. The service already watches multiple repos via the webhook layer; the foreman is the only surface still hardcoded to one.

Dependencies

  • Blocked by the Penpot mockup ticket (sibling). Ship against the approved visual for the repo picker + session chip.

Acceptance criteria

Session state

  • The foreman_sessions table (or wherever the SQLite schema lives — likely apps/server/src/foreman.ts) gains a repo TEXT NOT NULL column. Migration fills existing rows with the first entry from cfg.repos (or charles/claude-hooks as a sentinel).
  • New sessions default to cfg.repos[0] unless the POST /foreman/chat body overrides it. Overrides must be validated against cfg.repos — 400 on unknown repo.

Endpoints

  • GET /foreman/files?repo=owner/name — when repo equals the cwd repo, scan the local filesystem as today. Otherwise use the Forgejo content API (GET /repos/{owner}/{repo}/contents/specs) with the foreman's token. 403 when !cfg.repos.includes(repo).
  • GET /foreman/file-content?repo=owner/name&path=specs/foo.md — same split (local fs vs. Forgejo content API). Returns { path, content, sha }.
  • POST /foreman/save gains a repo field — when it's the cwd repo, write to disk + commit via git (current behavior). Otherwise go through Forgejo create_file / update_file with the foreman identity. Commit message convention: spec(foreman): update specs/<name>.md via Planner.
  • POST /foreman/chat — accepts repo in the body; stores on session. Each turn's prompt is augmented with a line like Active repo: <owner>/<name> so the foreman knows the context.

Tool allowlist / canUseTool rails

  • Extend foreman.ts::buildForemanSdkOptions canUseTool hook: block any mcp__forgejo__* call whose owner+repo args don't match the active session's repo. Keeps a compromised prompt from leaking into other repos the foreman's token can reach.

UI

  • Repo picker implemented per the mockup (session header + new-session empty state).
  • Sidebar session rows render the small repo chip.
  • @file autocomplete scopes to the active repo.
  • Switching the active repo mid-session posts a system line in the transcript.

Verification

  • Unit test in apps/server/src/foreman.test.ts — POST /foreman/chat with repo: "charles/proxmox-iac", assert the stored session row carries that repo, assert the Forgejo content API is called (mocked) rather than local fs.
  • Unit test for 403 on cfg.repos-mismatched repo.
  • Manual: start a session, pick charles/proxmox-iac, @ an existing spec from that repo, confirm it inlines. Save a new spec via the editor; verify the file landed in Forgejo (not on disk).

Out of scope

  • Checkout / git-level access to non-cwd repos — foreman doesn't clone, it uses the Forgejo content API. If future skills need real worktrees there, file a follow-up; the boss/dev/reviewer fleet already clones per-dispatch.
  • Multi-repo /breakdown dispatch — already works; /breakdown takes repo as a parameter and validates against cfg.repos.
  • Cross-repo dependency-graph view — covered by the dep-DAG tickets.

References

  • apps/server/src/foreman.ts — file endpoints + session handlers.
  • apps/server/src/main.ts::handleForemanConfig — already returns repos on /foreman/config; UI just needs to wire it.
  • apps/server/src/forgejo-api.ts — contains getFileContent, may need a createFile / updateFile wrapper if not already present.
  • apps/web/src/lib/foreman.ts — client-side fetch helpers to extend with the repo param.
  • Companion mockup ticket — blocker.
## User story As an operator of a multi-repo claude-hooks deployment, I want the **foreman** to be able to read specs, write specs, and `@file`-mention files from **any** repo in `config.repos` — not just the checkout it runs in — so I can draft and break down work for `charles/proxmox-iac` (or any other watched repo) from the same Planner UI. Today `process.cwd()` scopes every file operation to the claude-hooks source tree. The service already watches multiple repos via the webhook layer; the foreman is the only surface still hardcoded to one. ## Dependencies - **Blocked by the Penpot mockup ticket** (sibling). Ship against the approved visual for the repo picker + session chip. ## Acceptance criteria ### Session state - [ ] The `foreman_sessions` table (or wherever the SQLite schema lives — likely `apps/server/src/foreman.ts`) gains a `repo TEXT NOT NULL` column. Migration fills existing rows with the first entry from `cfg.repos` (or `charles/claude-hooks` as a sentinel). - [ ] New sessions default to `cfg.repos[0]` unless the POST /foreman/chat body overrides it. Overrides must be validated against `cfg.repos` — 400 on unknown repo. ### Endpoints - [ ] `GET /foreman/files?repo=owner/name` — when `repo` equals the cwd repo, scan the local filesystem as today. Otherwise use the Forgejo content API (`GET /repos/{owner}/{repo}/contents/specs`) with the foreman's token. 403 when `!cfg.repos.includes(repo)`. - [ ] `GET /foreman/file-content?repo=owner/name&path=specs/foo.md` — same split (local fs vs. Forgejo content API). Returns `{ path, content, sha }`. - [ ] `POST /foreman/save` gains a `repo` field — when it's the cwd repo, write to disk + commit via git (current behavior). Otherwise go through Forgejo `create_file` / `update_file` with the foreman identity. Commit message convention: `spec(foreman): update specs/<name>.md via Planner`. - [ ] `POST /foreman/chat` — accepts `repo` in the body; stores on session. Each turn's prompt is augmented with a line like `Active repo: <owner>/<name>` so the foreman knows the context. ### Tool allowlist / canUseTool rails - [ ] Extend `foreman.ts::buildForemanSdkOptions` `canUseTool` hook: block any `mcp__forgejo__*` call whose `owner`+`repo` args don't match the active session's repo. Keeps a compromised prompt from leaking into other repos the foreman's token can reach. ### UI - [ ] Repo picker implemented per the mockup (session header + new-session empty state). - [ ] Sidebar session rows render the small repo chip. - [ ] `@file` autocomplete scopes to the active repo. - [ ] Switching the active repo mid-session posts a system line in the transcript. ### Verification - [ ] Unit test in `apps/server/src/foreman.test.ts` — POST /foreman/chat with `repo: "charles/proxmox-iac"`, assert the stored session row carries that repo, assert the Forgejo content API is called (mocked) rather than local fs. - [ ] Unit test for 403 on `cfg.repos`-mismatched `repo`. - [ ] Manual: start a session, pick `charles/proxmox-iac`, `@` an existing spec from that repo, confirm it inlines. Save a new spec via the editor; verify the file landed in Forgejo (not on disk). ## Out of scope - Checkout / git-level access to non-cwd repos — foreman doesn't clone, it uses the Forgejo content API. If future skills need real worktrees there, file a follow-up; the boss/dev/reviewer fleet already clones per-dispatch. - Multi-repo `/breakdown` dispatch — already works; `/breakdown` takes `repo` as a parameter and validates against `cfg.repos`. - Cross-repo dependency-graph view — covered by the dep-DAG tickets. ## References - `apps/server/src/foreman.ts` — file endpoints + session handlers. - `apps/server/src/main.ts::handleForemanConfig` — already returns `repos` on /foreman/config; UI just needs to wire it. - `apps/server/src/forgejo-api.ts` — contains `getFileContent`, may need a `createFile` / `updateFile` wrapper if not already present. - `apps/web/src/lib/foreman.ts` — client-side fetch helpers to extend with the `repo` param. - Companion mockup ticket — blocker.
Collaborator

🤖 Auto-assigned to boss (heuristic: area:agents → boss (architecture-touching)). Reply /unassign to reroute.

🤖 Auto-assigned to **boss** (heuristic: area:agents → boss (architecture-touching)). Reply `/unassign` to reroute.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
charles/claude-hooks#254
No description provided.