Overview
Forge apps and repos already declare their toolchains — this repo alone carries .nvmrc, flake.nix, and forge-ci.toml — but ForgeGraph ignores all of them. This delivery slice makes mise (the TOML dev-environment manager) first-class for native CI: repos get a parsed, viewable Dev Environment panel; Linux agents gain a mise execution path with explicit precedence against Nix; and completed CI builds record which tools they ran with as append-only delivery evidence.
Problem & Status Quo
The pain, in one line: environment setup is tribal knowledge. The repo knows what tools it needs; Forge doesn't.
- Cloning any repo onto a node means manually installing the right tool versions — nothing records or automates it.
- The agent already has an environment-setup fork (
nixcivsmanifestci), so there's a natural home for a third path — but no story for the majority of repos that use neither flakes nor raw manifests well. - mise is what gets used day-to-day locally; today there is zero overlap between local reality and what CI/node setup assumes.
Premises Confirmed
mise.toml is the single source of truth — Forge mirrors it, never forks it. No shadow copy of tool versions in the DB. If UI editing comes later, it commits back to the file; it never maintains a parallel config.nix > mise > manifest. A flake.nix wins (flakes are strictly more total: system deps + tools). Otherwise mise.toml. Otherwise legacy forge-ci.toml. Behavior is never undefined.mise install on nodes stays inside the existing trust boundary. It executes plugin/tool code declared by whoever can push to the repo — same trust class as running forge-ci.toml builds or flakes today. Explicit, not accidental.[env] secrets through Forge, and anything on CF Workers deploys. This is the CI vertical slice.Approaches Considered
A — Parse-on-read + agent hook Minimal viable
No new tables. Server-side lib parses mise config from git for display; agent gains a mise branch in its prep path. Effort S · Risk low. Kills the manual-setup pain but leaves no record of what tools a given build actually used.
C — A + evidence snapshots Recommended
Everything in A, plus one small append-only table written atomically with a CI run's terminal report: this build ran node 22.6 + pnpm 9 with mise 2026.x at sha …. Reproducibility evidence for completed reports without B's sync machinery. Effort M (~3d) · Risk low.
B — First-class toolchain projection Ideal architecture
Push-webhook-synced repo_toolchain table, server-resolved precedence encoded once, agent consumes resolved specs from the hub, edit-in-UI commits back. The long-game home for this feature — but heavy machinery before validating that anyone looks at the panel. Effort L · Risk medium. C preserves A's shape, so upgrading to B later wastes no work.
Diagram source (mermaid)
flowchart LR
subgraph REPO["Repo (source of truth)"]
MT["mise.toml / .mise.toml"]
end
subgraph SERVER["ForgeGraph web/API"]
P["parseMiseConfig\n(TS lib)"]
UI["Dev Environment panel\nrepos + apps"]
EV[("build_toolchain\nappend-only evidence")]
end
subgraph AGENT["Node agent (Go)"]
PRE["Precedence gate\nnix > mise > manifest"]
MI["mise install + exec"]
SNAP["Toolchain snapshot\ncallback"]
end
MT -->|"git fetch @sha"| P
P --> UI
P -.->|"same rule, Go twin"| PRE
PRE -->|"mise.toml present"| MI
MI --> SNAP
SNAP -->|"POST run evidence"| EV
Goals & Non-goals
Goals
- Fresh clone → correct toolchain with zero human steps (agent runs
mise install). - Dev environment visible on repo and app pages: tools + versions, task names, env key names.
- Every CI build that delivers a terminal report records its resolved toolchain as queryable evidence.
- Deterministic precedence: nix > mise > manifest, identical rule in Go and TS.
Non-goals
- Editing mise config from the UI (future: commit-back flow).
- Running mise tasks from the UI.
- Managing
[env]values/secrets through Forge. - CF Workers deploy paths; interactive dev shells on nodes.
- Replacing the nixci path — flakes remain first in line.
Phase 1 — Shared detection & parsing (TS) Done
One library owns detection + parsing so both the API surface and the UI read from the same place.
| Task | Files | Verification | Status |
|---|---|---|---|
Detect config paths in order: mise.toml, .mise.toml, .config/mise/config.toml; ignore *.local.toml / mise.local.toml | apps/web/src/lib/mise.ts | unit tests over fixture trees incl. local-file exclusion | Done |
Parse TOML into a typed shape: tools, task metadata, env key names only, and settings | apps/web/src/lib/mise.ts | golden fixture tests; env values never cross the parser boundary | Done |
Expose precedence helper resolveEnvStrategy(files) → "nix" | "mise" | "manifest" mirroring the Go rule | apps/web/src/lib/mise.ts | shared table-driven fixture tests | Done |
Phase 2 — Dev Environment panel (UI) Done
A compact panel on repo detail pages, linked from app pages when the app's repo has a mise config. Read-only mirror of the file at the current default-branch head.
| Task | Files | Verification | Status |
|---|---|---|---|
"Dev Environment" section on /repos/[id]: strategy badge, tools, tasks, and env key names | apps/web/src/app/repos/[id]/ | component and parser tests | Done |
| Link the selected mise config into the existing code viewer with TOML highlighting | existing repository code viewer | generated URL targets the selected config and ref | Done |
| App page cross-link to the linked repository's Dev Environment panel, which reports the detected Nix, mise, or manifest strategy | app detail page | typecheck and route rendering coverage | Done |
[env] can carry secrets. The UI renders env key names only, never values — enforce this in the parser shape itself (envKeys: string[]), not by convention in components.Phase 3 — Agent mise path (Go) CI slice done
The agent's prep gate becomes three-way instead of two-way. New package keeps nixci untouched.
| Task | Files | Verification | Status |
|---|---|---|---|
Precedence gate: flake.nix → nixci; else mise config → mise-managed manifest CI; else legacy manifest CI | agent/internal/agentci/, agent/internal/miseenv/ | Go tests mirror the shared precedence fixture | Done |
| Install a pinned, architecture-specific mise binary with SHA-256 verification and cache it across runs | agent/internal/miseenv/install.go | idempotency and checksum tests | Done |
Run mise install after worktree sync and execute manifest commands through mise exec inside bubblewrap | agent/internal/miseenv/, agent/internal/manifestci/ | unit-level fake-manager integration; real container follow-up | Done |
| Emit structured per-tool installation log records | miseenv package | log assertions | Follow-up |
Phase 4 — Toolchain evidence snapshots Terminal evidence done
The part that makes this feel like ForgeGraph rather than a config viewer: builds become reproducible-on-paper.
| Task | Files | Verification | Status |
|---|---|---|---|
Append-only build_toolchains table keyed by build with repository, commit, strategy, manager version, tools, and timestamp | packages/db/src/schema/build.ts, migration 0088 | additive migration verified on beta | Done |
| Persist the frozen snapshot atomically with the agent's terminal CI report | /api/agent/ci-report | route tests cover attribution, idempotency, and terminal compatibility | Done |
| Build detail page shows the frozen toolchain snapshot as "Ran with" evidence | CI run detail component | component tests | Done |
Risks & Mitigations
| Risk | Severity | Mitigation |
|---|---|---|
mise install downloads arbitrary plugin/tool code — supply-chain surface | Medium | Same trust class as existing repo-declared builds; pinned mise binary with architecture-specific SHA-256 verification; lockfile respected when present. Structured per-tool install records remain an explicit follow-up. |
| Precedence rule drifts between Go and TS implementations | Medium | Identical table-driven test fixtures committed to both sides; a shared fixture JSON file referenced by both test suites |
| Repos with stale/broken mise configs now fail CI where they previously "worked" (via ambient node state) | Low | Prep failure produces a clear named error ("mise install failed: …"); ambient fallback explicitly rejected — silent drift is the bug we're killing |
| Panel shows nothing for repos without mise — feels half-built | Low | Strategy badge always renders (Nix / Mise / Legacy manifest) so the absence is informative, not empty |
Verification
- Exact manifest: typecheck, lint, general package tests, 1,047 API tests, 978 web tests, and 22 health tests pass from a clean JJ workspace.
- Agent: targeted Go tests cover precedence, install verification, mise execution, sandbox mounts, capability advertisement, and report flow.
- Database: migration 0088 was applied and rerun idempotently against beta before application rollout.
- Test harness: API setup generates full Drizzle DDL through a file and terminates its emulator process cleanly; consecutive runs leave no fixed ports occupied.
Open Questions & Follow-ups
The Assignment
What I noticed
You described the friction precisely: not "we need a config format" but "setup is manual every time." And when given the wedge options you picked the slice that includes evidence — consistent with how ForgeGraph has evolved (attestations, health evidence, delivery readiness). The tell that this is the right shape: your own repo is the messiest case (three competing mechanisms), and you chose to make precedence explicit rather than hope it never matters.