feat(forge-labels): label + milestone bootstrap parity (MF-7) #311
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!311
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/298-mf7-label-bootstrap-parity"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
ForgePort.createLabel(repo, { name, color, description })— color is a 6-hex string without#; each adapter adds/strips the prefix its forge expects (Forgejo#rrggbb, GitHubrrggbb, GitLab#rrggbb). Implement on all three adapters.labels.ts::reconcileLabelsoff raw HTTP onto the adapter factory — it now callscreateForgeAdapterForRepo(repo, token)and exerciseslistRepoLabels+createLabelthrough the port.labels.tsstays forge-agnostic and speaks domain colors throughout.ReconcileOptionsswapsfetchImpl+forgejoUrlfor an injectableadapterused by tests;main.tsbootstrap call simplified (no more URL override needed — the factory reads the forge binding out ofconfig/agents.json).Design notes
reconcileLabelshit the Forgejo REST API directly. A{ forge: "github" }repo declared inconfig/agents.jsonsilently dropped every routing label, which meant every webhook delivered to that repo would no-op throughaddIssueLabels. MF-7 closes that hole so adding a GitHub/GitLab repo to the fleet gets the canonicalarea:*/type:*set installed end-to-end on first boot.createLabelkeeps the signature name-based on the port; existingForgejoAdapter.addLabels/.removeLabelalready resolve IDs internally viaresolveLabelId, no change there.listRepoLabelsalready strips#, so reconcile round-trips cleanly against an already-populated GitLab project (covered by a dedicated test).Test plan
bun test apps/server/src/infrastructure/forge/labels.test.ts— 16 pass (10 mock-adapter behaviour + 6 per-forge end-to-end reconciles × Forgejo/GitHub/GitLab × {empty repo, fully populated}).bun test apps/server— 1266 pass / 4 fail, matches pre-existing baseline (session JSONL pruning × 3, foreman session CRUD × 1 — all unrelated to this change).bun x turbo run typecheckclean across all workspaces.bun x biome checkclean on touched files.Out of scope
listMilestones+createMilestoneto the port, butconfig/agents.jsonhas nomilestones:stanza today — there is nothing to reconcile against yet. Track as a follow-up once operators grow a config surface for canonical milestones. No port methods added on this PR.Closes #298
Wire `labels.ts` reconcile through `ForgePort` so the canonical `area:*` / `type:*` set bootstraps against Forgejo, GitHub, and GitLab from a single code path. Previously `reconcileLabels` hit the Forgejo REST API directly, which meant a `{ forge: "github" }` repo in `config/agents.json` silently dropped every webhook until an operator hand-created the labels in the forge UI. Port surface - Add `ForgePort.createLabel(repo, { name, color, description })` — color is a 6-hex string without `#`; each adapter adds/strips the prefix its forge expects (Forgejo `#rrggbb`, GitHub `rrggbb`, GitLab `#rrggbb`). - Implement on all three adapters; GitLab's existing `listRepoLabels` already strips `#`, so reconcile now round-trips cleanly. Labels module - `reconcileLabels` now resolves the forge binding via `createForgeAdapterForRepo` and calls `adapter.listRepoLabels` + `adapter.createLabel`. `labels.ts` stays forge-agnostic — it speaks domain colors throughout. - Test override switched from `fetchImpl` + `forgejoUrl` to an injectable `adapter` on `ReconcileOptions`. - `main.ts` bootstrap call simplified (no more `forgejoUrl` override). Milestones are deliberately out of scope — `config/agents.json` has no `milestones:` stanza yet, so there is nothing to reconcile. Track under a follow-up. Tests - `labels.test.ts` driven by an in-memory mock adapter for the behavioural cases plus three per-forge end-to-end reconciles (Forgejo, GitHub, GitLab) against a fetch spy. Each e2e pair asserts (1) canonical set created on an empty repo, and (2) no-op on a fully-populated repo — the MF-7 parity guarantee. Closes #298 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>