diff --git a/CHANGELOG.md b/CHANGELOG.md index decdaf6..3bc46b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,97 @@ Changes are tracked via git tags. Each release tag corresponds to an entry here. ## [Unreleased] +### Added — Item 6.8: role-template management UI (rounds 1 + 2) + +**Round 1: full-template editing** + +- **New top-level "Agents" tab** in the web UI (between Memory and + Settings). One sub-tab per managed role template; each tab shows + the template content in a scrollable monospace editor with version + selector, edit / save / promote / revert / delete actions, and an + inline help block. +- **Versioning + promote-to-production**: every role's bundled cf² + default is always selectable (read-only, never deletable). Saving + edits creates a new labelled version under + `~/.cfcf/templates-managed//`. Promoting a version writes its + content to the existing `~/.cfcf/templates/` user-global + override path that `getTemplate()` already reads — no runtime + changes to agent spawning. Reverting to default deletes the override + file so cf² falls back to the embedded default. Editing a + promoted version refreshes the override file in-place. +- **HTTP API**: `GET /api/role-templates`, `GET .../:name`, + `GET .../:name/versions/:versionId`, `POST .../:name/versions`, + `PUT .../:name/versions/:versionId`, + `DELETE .../:name/versions/:versionId`, + `POST .../:name/promote`. Uniform `{ error: string }` envelope. +- **New core module** `packages/core/src/role-templates.ts` with full + CRUD, manifest self-heal on corruption, orphan detection. + `getEmbeddedTemplate(name)` newly exported from `templates.ts` for + read-only access to the bundled default. + +**Round 2: augmented-type versions (auto-upgrade-friendly)** + +- **`type: "full" | "augmented"`** added to every saved version. + Round-1 manifests (no `type`) are read as `"full"` for back-compat. + - **`type: "full"`** — body REPLACES the bundled default at + promote time. Maximum flexibility (delete sections, restructure); + no auto-upgrade. UI shows a `ℹ Forked from cf² vX.Y.Z` badge so + the user knows their version may have drifted from the latest + bundled default. + - **`type: "augmented"`** — body is APPENDED to the live bundled + default at promote time. The harness composes + ` + separator + ` and writes that to + the override file. The bundled default is read live (never + duplicated on disk), so when cf² ships a new default, the user's + extension automatically rides along on the new version. **This is + upgrade-friendly by default.** +- **Boot-time auto-recompose**: every server boot re-composes any + promoted augmented version (`refreshAugmentedOverrides()` in core, + called from `start.ts` after the orphan reaper). Cheap idempotent + pass — only writes when the on-disk override actually differs from + the live composition. Single log line on change: + `[server] Re-composed N augmented role-template override(s)…`. + Full versions are NOT touched (frozen by design). +- **New "Augment" button** in the action row, next to "Edit". Augment + always creates a new augmented version on top of the bundled + default (regardless of which version is currently selected — keeps + the upgrade-friendly contract). +- **Split editor view** for augmented versions: bundled default + rendered read-only at the top (~25vh, smaller so the extension + editor has visual prominence), extension textarea below (~30vh) + with a placeholder showing example custom directions. Editing + toggles only the extension to editable. +- **Version dropdown** suffixes each entry with its type: + `My stricter judge — full (2026-05-09) — promoted`. +- **API extension**: `POST /api/role-templates/:name/versions` body + now accepts an optional `type: "full" | "augmented"` (defaults to + `"full"`). Invalid values return 400. +- **UI polish (also round 2)**: status messages moved above the + version-selector row (so "click Promote" reads naturally below the + buttons), directional words removed from messages, "creating new + X version" indicator added to the heading when appropriate. + +**Managed templates (rounds 1 + 2)**: `cfcf-architect-instructions.md`, +`cfcf-judge-instructions.md`, `cfcf-documenter-instructions.md`, +`cfcf-reflection-instructions.md`, `process.md`. Per-project overrides +at `/cfcf-templates/` continue to take precedence over the +user-global override (the power-user escape hatch, unmanaged from the UI). + +**Out of scope (deferred)**: dev-role custom-directions block (dev's +instructions are programmatically generated by `context-assembler`, +not file-loaded — needs a different mechanism), Product Architect / +Help Assistant system-prompt management, per-project override +management UI, diff viewer between versions. + +**Tests**: 41 unit tests in `role-templates.test.ts` (every flow + +manifest corruption recovery + cross-template isolation + +back-compat with round-1 manifests + augmented composition + boot +refresh), 17 endpoint tests in `routes/role-templates.test.ts` +(including round-2 type validation), 3 route-parser tests for +`/agents` + `?template=`. + +**Design doc**: [`docs/design/role-template-management.md`](docs/design/role-template-management.md). + ### Added — Item 6.33: ollama-models refresh - **Auto-refresh on server boot.** `cfcf server start` now calls diff --git a/CLAUDE.md b/CLAUDE.md index 1079a70..f6747a3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,10 @@ cfcf (Cerefox Code Factory, also written cf², pronounced "cf square") is a dete - Agents run as **local processes** (not containers) in the user's dev environment - **Git branches** provide isolation between iterations (feature branch per iteration, merge to main) - **Seven agent roles**: five run inside the iteration loop — dev (writes code), judge (per-iteration assessment), architect (reviews / extends Problem Pack; verdicts: READY / NEEDS_REFINEMENT / BLOCKED / SCOPE_COMPLETE), reflection (cross-iteration strategic review), documenter (produces final docs); two are interactive — Product Architect (live spec iteration before the loop, item 5.14) and Help Assistant (in-shell guidance). Each role independently configurable (adapter + model). -- **Per-adapter model registry** (item 6.26): pickers in web Settings, web workspace Config, and `cfcf init` / `cfcf config edit` source their model dropdown from `packages/core/src/adapters/seed-models.ts` (the bundled seed) merged with the user's optional override on `CfcfGlobalConfig.agentModels[]`. Resolution lives in `resolveModelsForAdapter()`. The seed is intentionally minimal — generic aliases (`opus`, `sonnet`, `haiku` for claude-code; `gpt-5-codex`, `gpt-5`, `o3` for codex) so it ages slowly. **Single edit surface**: web Settings → Model registry is the one place to add/remove/reset per-adapter models; pickers themselves are read-only dropdowns (no inline "custom name" affordance — kept the UI predictable: one place to manage models). Hand-edited config values that aren't in the registry are preserved as `(custom)` entries on first render so back-compat doesn't break. **Maintenance**: when an upstream agent CLI ships a new headline model, edit the relevant array in `seed-models.ts` and ship in the next release; user overrides survive the upgrade. +- **Per-adapter model registry** (item 6.26): pickers in web Settings, web workspace Config, and `cfcf init` / `cfcf config edit` source their model dropdown from `packages/core/src/adapters/seed-models.ts` (the bundled seed) merged with the user's optional override on `CfcfGlobalConfig.agentModels[]`. Resolution lives in `resolveModelsForAdapter()`. The seed is intentionally minimal — generic aliases (`opus`, `sonnet`, `haiku` for claude-code; `gpt-5-codex`, `gpt-5`, `o3` for codex) so it ages slowly. **Single edit surface**: web Settings → Model registry is the one place to add/remove/reset per-adapter models; pickers themselves are read-only dropdowns (no inline "custom name" affordance — kept the UI predictable: one place to manage models). Hand-edited config values that aren't in the registry are preserved as `(custom)` entries on first render so back-compat doesn't break. **Maintenance**: when an upstream agent CLI ships a new headline model, edit the relevant array in `seed-models.ts` and ship in the next release; user overrides survive the upgrade. **Ollama-models refresh** (item 6.33, v0.21.x): for `*-ollama` adapters the model list comes from `availableOllamaModels` in the global config (populated at `cfcf init`). To pick up newly-pulled ollama models, the list is auto-refreshed on every `cfcf server start` (`refreshOllamaModelsInConfig()` in `@cfcf/core`, called from `start.ts`) AND on demand via the "Refresh ollama models" button in the web UI Agent-roles section (Settings + workspace Config). `POST /api/agents/refresh-ollama-models` is the underlying endpoint. +- **Role-template management** (item 6.8): top-level **Agents** tab in the web UI lets users edit the instruction templates each role reads (`cfcf-architect-instructions.md`, `process.md` for the dev role, `cfcf-judge-instructions.md`, `cfcf-reflection-instructions.md`, `cfcf-documenter-instructions.md`). Each role has a bundled cf² default (always selectable, read-only) and any number of saved versions stored under `~/.cfcf/templates-managed//`. Two version types: **`full`** REPLACES the default (max flexibility, no auto-upgrade — UI shows a "Forked from cf² vX.Y.Z" badge); **`augmented`** APPENDS to the live default (extension-only on disk; composed at promote time + every server boot via `refreshAugmentedOverrides()` so cf² upgrades automatically propagate). Promoting a version writes the (composed-for-augmented) content to the existing user-global override path `~/.cfcf/templates/` that `getTemplate()` already reads — no runtime changes to the agent-spawn pipeline. Reverting to default deletes the override. Project-local overrides at `/cfcf-templates/` continue to take precedence (power-user escape hatch, unmanaged from the UI). Core: `packages/core/src/role-templates.ts`. API: `/api/role-templates/*`. UI: `packages/web/src/pages/AgentTemplates.tsx`. Design doc: `docs/design/role-template-management.md`. +- **Orphan agent-process reaper** (item 6.31, v0.21.0): cf² spawns agents with `detached: true` so SIGTERM at server shutdown can kill the whole process group. **Graceful shutdown** (SIGINT/SIGTERM) sends group SIGTERM, waits 1.5s, then group SIGKILL — handles the common case. **Boot-time orphan scan** (`refreshAugmentedOverrides`'s sibling, in `packages/core/src/orphan-reaper.ts`) closes the hard-crash hole: when the previous server died via SIGKILL or OS panic, its agent children get reparented to PID 1; on next `cfcf server start`, the boot scan finds them via three conjoined filters (PPID==1 + same effective user + cfcf-spawned command shape) and reaps them. Orphans on `ollama launch ` are particularly important — they hold ollama's model serializer for up to 10 min after their inference times out. **`cfcf server reap`** is the manual interactive variant (list + y/N + kill); doesn't require the cfcf server to be running. See `findOrphanAgentProcesses()` + `reapOrphans()` in `@cfcf/core`. +- **Architect role is unattended for ALL invocation paths** (item 6.30, v0.21.0): the previous "manually-invoked SA via `cfcf review` is interactive" framing was wrong — verified in code that ALL architect spawns (pre-loop autoReviewSpecs, mid-loop refine_plan, manual `cfcf review`) use `spawnProcess` with `stdout: "pipe"` + log file. There's no `stdio: "inherit"` anywhere in the architect path; `cfcf review` is just a polling client to a server-side background spawn. So architect is in `UNATTENDED_ROLE_NAMES` always (no `autoReviewSpecs=true` gate); `cfcf doctor`'s harness check + the web UI's policy callout fire on architect+claude-code regardless of `autoReviewSpecs`; fresh `cfcf init` defaults architect to `codex` instead of `claude-code` (behaviour change for new installs only — existing user configs unaffected). Only **PA (`cfcf spec`)** and **HA (`cfcf help assistant`)** use `Bun.spawn(... { stdio: "inherit" })` and are within Anthropic's allowed-interactive scope. - **Structured pause actions** (item 6.25, shipped 2026-05-02): when a paused loop is resumed via `cfcf resume --action <…>`, the user picks one of `continue` / `finish_loop` / `stop_loop_now` / `refine_plan` / `consult_reflection`. `loop-stopped` is a workspace-history event type for user-initiated `stop_loop_now`. - **Three commits per iteration** when reflection runs: `cfcf iteration N dev (...)`, `cfcf iteration N judge (...)`, `cfcf iteration N reflect (): `. - **Async execution**: iterate endpoint returns 202, CLI polls for status. @@ -74,8 +77,25 @@ packages/ iteration-loop.ts # Main iteration loop controller + decision engine # (preparing -> dev -> judging -> reflecting? -> deciding) workspace-history.ts # history.json: review / iteration / reflection / document events - adapters/ # Agent adapter implementations (claude-code, codex) + adapters/ # Agent adapter implementations (claude-code, codex, + # opencode, claude-code-ollama, opencode-ollama — + # five total since item 6.28) + ollama-detection.ts # detectOllama, listOllamaModels (item 6.28), + # refreshOllamaModelsInConfig (item 6.33 boot-time + # refresh + button-triggered re-detection) + orphan-reaper.ts # findOrphanAgentProcesses + reapOrphans for boot-time + # reap of stale agents from a hard-crashed prior + # server PID (item 6.31, v0.21.0) + role-templates.ts # Role-instruction-template versioning + promote-to- + # production layer (item 6.8). Two types: full + # (replace default) + augmented (append to live + # default; auto-recomposed on cf² upgrade via + # refreshAugmentedOverrides at server boot). templates/ # cfcf-docs/ file templates (17 entries incl. reflection + iteration-log + clio-guide) + templates.ts # Template resolver: project-local override → user-global + # override (~/.cfcf/templates/) → embedded default. + # getEmbeddedTemplate(name) exposes the bundled + # default verbatim for role-templates.ts. clio/ # Clio memory layer (item 5.7) backend/ types.ts # MemoryBackend interface (swap point for future CerefoxRemote) @@ -110,7 +130,7 @@ packages/ commands/ # CLI command implementations init.ts # First-run interactive setup (numbered agent picker, embedder # pick + inline HF download with progress bar, error classifier) - server.ts # Server start/stop/status + server.ts # Server start/stop/status/reap (item 6.31 added `reap`) workspace.ts # Workspace init/list/show/delete (--project for Clio assignment) config.ts # Global config show/edit run.ts # Start iteration loop (agent) or single iteration (manual) @@ -131,7 +151,9 @@ packages/ # + cfcf memory alias web/src/ App.tsx # Root router (dashboard / workspace / server) - pages/ # Dashboard, WorkspaceDetail, ServerInfo + pages/ # Dashboard, WorkspaceDetail, ServerInfo, MemoryPage, + # HelpPage, AgentTemplatesPage (item 6.8 — /agents + # route, role-template management UI) components/ # Header, PhaseIndicator, WorkspaceHistory, # ArchitectReview, JudgeDetail, ReflectionDetail, … api.ts # Client for all /api/* endpoints incl. /activity + /reflect diff --git a/README.md b/README.md index 9c1f286..c9221c2 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ cfcf can be driven from the CLI or from the web GUI served by the same Hono serv - **[Opencode](https://opencode.ai)** (sst.dev) — alternative to Codex; runs against your provider's API or via local ollama models - **[Ollama](https://ollama.com)** — optional, enables `claude-code-ollama` and `opencode-ollama` adapters that drive the agent CLI against locally-served models -> ⚠️ **Anthropic policy + log-visibility heads-up.** `claude-code` (direct, talking to Anthropic's API/subscription) is **only recommended for interactive roles**: Product Architect (`cfcf spec`), Help Assistant (`cfcf help assistant`), and manually-invoked Solution Architect (`cfcf review`). Anthropic's third-party-harness policy restricts subscription OAuth to interactive use; cf²'s unattended dev / judge / reflection / documenter loop is the violation pattern the rule targets. **`claude-code-ollama` is policy-clean** (no Anthropic credential involved — local ollama serves the model) but shares Claude Code's `-p` stdout-buffering behaviour: log files stay silent during the entire run and dump the final response only when the agent exits. If you want live progress monitoring, use `codex` or the opencode adapters for unattended roles. See [`docs/guides/anthropic-policy.md`](docs/guides/anthropic-policy.md) for the full breakdown + the recommended adapter-per-role table. cf² surfaces warnings in `cfcf init` and the web UI Settings / workspace Config when these recommendations are violated; neither blocks the choice. +> ⚠️ **Anthropic policy + log-visibility heads-up.** `claude-code` (direct, talking to Anthropic's API/subscription) is **only recommended for the two truly-interactive roles** that take over your shell via `stdio: "inherit"`: Product Architect (`cfcf spec`) and Help Assistant (`cfcf help assistant`). All other roles — dev, judge, reflection, documenter, **and Solution Architect** — are spawned headlessly with `claude -p` regardless of how they're invoked (including `cfcf review`, which polls a status endpoint while the server runs the architect in the background). Anthropic's third-party-harness policy restricts subscription OAuth to interactive use, so unattended `claude -p` under a personal subscription is the violation pattern. **`claude-code-ollama` is policy-clean** (no Anthropic credential involved — local ollama serves the model) but shares Claude Code's `-p` stdout-buffering behaviour: log files stay silent during the entire run and dump the final response only when the agent exits. If you want live progress monitoring, use `codex` or the opencode adapters for unattended roles. See [`docs/guides/anthropic-policy.md`](docs/guides/anthropic-policy.md) for the full breakdown + the recommended adapter-per-role table. cf² surfaces warnings in `cfcf init` and the web UI Settings / workspace Config when these recommendations are violated; neither blocks the choice. cfcf is distributed as a standard npm package (`@cerefox/codefactory`); `bun install -g` resolves the heavy native deps (transformers, ORT, sharp) the same way every JS-ecosystem CLI does. A per-platform `@cerefox/codefactory-native-` package provides the pinned libsqlite3 + sqlite-vec libs. See [`docs/guides/installing.md`](docs/guides/installing.md) for the install one-liner + local / file-URL install paths. diff --git a/docs/README.md b/docs/README.md index 82fb178..fa70dc1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,24 +8,34 @@ docs/ decisions-log.md Working docs: failed experiments, non-obvious choices design/ Specs and architecture (stabilize over time) - cfcf-requirements-vision.md What cfcf is and why - cfcf-stack.md Technology choices - technical-design.md How components fit together - agent-process-and-context.md Iteration process, file formats, signal specs + cfcf-requirements-vision.md What cfcf is and why + cfcf-stack.md Technology choices + technical-design.md How components fit together + agent-process-and-context.md Iteration process, file formats, signal specs + clio-memory-layer.md Clio architecture (item 5.7+) + clio-memory-web-ui.md Clio web UI design (item 6.18) + scheduler-and-update-notification.md JobScheduler primitive + update banner (item 6.20) + role-template-management.md Role-template versioning + promote-to-production (item 6.8) + skills-repository.md Skills system design (item 6.27) api/ API reference (grows with every endpoint) server-api.md Server REST API endpoints - research/ Ideas, brainstorms, explorations + research/ Ideas, brainstorms, explorations (promoted to design/ when committed) reflection-role-and-iterative-planning.md Item 5.6 design (shipped in v0.6.0 + v0.7.0) + product-architect-design.md Item 5.14 design (shipped) + structured-pause-actions-design.md Item 6.25 design (shipped) + … (other historical research files) guides/ User-facing how-tos - manual.md User-manual hub -- 3-min getting started + concepts + pointers - workflow.md Full workflow walkthrough (canonical for "run a loop end-to-end") - cli-usage.md CLI command reference (verb-by-verb) - clio-quickstart.md Clio (cross-workspace memory) onboarding - installing.md Install + upgrade + uninstall - troubleshooting.md Common issues + fixes + manual.md User-manual hub — 3-min getting started + concepts + pointers + workflow.md Full workflow walkthrough (canonical for "run a loop end-to-end") + cli-usage.md CLI command reference (verb-by-verb) + clio-quickstart.md Clio (cross-workspace memory) onboarding + installing.md Install + upgrade + uninstall + troubleshooting.md Common issues + fixes + anthropic-policy.md Anthropic third-party-harness policy + adapter strategy (item 6.28+6.30) + product-architect.md Product Architect interactive role (item 5.14) ``` ## What Goes Where diff --git a/docs/api/server-api.md b/docs/api/server-api.md index 4152015..e30eee5 100644 --- a/docs/api/server-api.md +++ b/docs/api/server-api.md @@ -1367,6 +1367,141 @@ Rewire a workspace's Clio Project assignment. Backs the `cfcf workspace set --pr --- +## Agent Models (item 6.26 + 6.33) + +### GET /api/agents/models + +Returns the resolved per-adapter model registry. Each entry is a list of model names the picker should offer for that adapter. For `*-ollama` adapters, the list comes from `availableOllamaModels` in the global config (last refreshed by `cfcf init` or by the boot-time / button-triggered refresh below). For seed-sourced adapters (`claude-code`, `codex`), the list is the user override on `CfcfGlobalConfig.agentModels[]` if set + non-empty, otherwise the bundled seed in `packages/core/src/adapters/seed-models.ts`. + +**Response:** `200 OK` +```json +{ + "adapters": { + "claude-code": ["sonnet", "opus", "haiku"], + "codex": ["gpt-5-codex", "gpt-5", "o3"], + "claude-code-ollama": ["gemma4:31b", "qwen3-coder:latest"], + "...": [] + }, + "seed": { "claude-code": ["sonnet", "opus", "haiku"], "...": [] } +} +``` + +The `seed` field is surfaced alongside so the Settings → Model registry editor can show users what they'd revert to if they cleared their override. + +### POST /api/agents/refresh-ollama-models (item 6.33) + +On-demand re-detection of locally-pulled ollama models. Runs `ollama list` live, persists the result to `availableOllamaModels` in the global config if the live list differs from what's saved (order-insensitive comparison, since `ollama list` reorders by mtime). After the call, re-fetch `/api/agents/models` to pick up the new list in the per-adapter resolved registry. + +Always returns `200 OK` — "ollama not installed" is surfaced as a hint in the body, not an HTTP failure. + +**Response:** `200 OK` +```json +{ "models": ["gemma4:31b", "qwen3-coder:latest"], "updated": false } +``` + +Or, when ollama isn't installed: +```json +{ "models": [], "updated": false, "error": "ollama not detected" } +``` + +`updated: true` means the saved list differed from the live list and was rewritten. + +The boot-time auto-refresh in `start.ts` calls the same underlying helper (`refreshOllamaModelsInConfig` from `@cfcf/core`) so this endpoint and `cfcf server start` share their behaviour. + +--- + +## Role-Template Management (item 6.8) + +Versioning + promote-to-production layer for the role-instruction templates each agent reads (`cfcf-architect-instructions.md`, `process.md` for the dev role, `cfcf-judge-instructions.md`, `cfcf-reflection-instructions.md`, `cfcf-documenter-instructions.md`). Two version types: + +- **`type: "full"`** — body REPLACES the bundled default at promote time. Maximum flexibility; doesn't auto-pick-up cf² upgrades. Carries a `cfcfVersion` field stamped at save time so the UI can show a "Forked from cf² vX.Y.Z" badge. +- **`type: "augmented"`** — body is APPENDED to the live bundled default at promote time AND every server boot (`refreshAugmentedOverrides()` in core, called from `start.ts`). The bundled default is read live and never duplicated on disk, so the user's extension automatically rides along when cf² ships a new template. + +The user-global override path `~/.cfcf/templates/` (read by `getTemplate()`) is what the runtime sees. Promote writes the (composed-for-augmented) content there; revert-to-default deletes it. + +### GET /api/role-templates + +List managed templates with summary info. Tab order matches the natural agent execution sequence (architect → dev → judge → reflection → documenter). + +**Response:** `200 OK` +```json +{ + "templates": [ + { "name": "cfcf-architect-instructions.md", "displayName": "Solution Architect", "currentVersionId": "default", "versionCount": 0 }, + { "name": "process.md", "displayName": "Developer", "currentVersionId": "v_abc123", "versionCount": 2 } + ] +} +``` + +### GET /api/role-templates/:name + +Full state for one template. + +**Response:** `200 OK` +```json +{ + "name": "cfcf-judge-instructions.md", + "displayName": "Judge", + "defaultContent": "...", + "currentVersionId": "v_abc123", + "currentContent": "...", + "versions": [ + { + "id": "v_abc123", + "label": "stricter judge", + "savedAt": "2026-05-09T12:00:00Z", + "contentHash": "abc123def456", + "type": "full", + "cfcfVersion": "0.21.0" + } + ] +} +``` + +For an augmented promoted version, `currentContent` is the **composed** text (matches what's in the override file). + +### GET /api/role-templates/:name/versions/:versionId + +Returns the body of a specific version. For `versionId === "default"` returns the bundled default. For an augmented version returns the **extension only** (not the composed text). 404 on unknown name or version. + +**Response:** `200 OK` -- `{ "content": "..." }` + +### POST /api/role-templates/:name/versions + +Save a new version. + +**Request body:** `{ "label": "string", "content": "string", "type"?: "full" | "augmented" }` + +`type` defaults to `"full"` when omitted. Invalid types return 400. + +**Response:** `201 Created` -- the new `TemplateVersion` object. + +### PUT /api/role-templates/:name/versions/:versionId + +Update an existing version's label and/or content. If the version is the currently-promoted one, the override file is refreshed automatically (with re-composition for augmented). + +**Request body:** `{ "label"?: "string", "content"?: "string" }` (at least one required). + +**Response:** `200 OK` -- the updated `TemplateVersion` object. 400 if attempting to update `versionId === "default"` (read-only). + +### DELETE /api/role-templates/:name/versions/:versionId + +Delete a saved version. Cannot delete `"default"`. If the deleted version was the promoted one, automatically reverts to default (override file deleted). + +**Response:** `200 OK` -- `{ "deleted": true, "template": }`. + +### POST /api/role-templates/:name/promote + +Promote a version (or `"default"`) to production. + +**Request body:** `{ "versionId": "string" }` + +For `"default"` the override file is deleted (revert). For a saved version, the body is composed (per its `type`) and written to the override file. + +**Response:** `200 OK` -- the refreshed `RoleTemplateFull` object so the UI can update local state without an extra GET. + +--- + ## Server Lifecycle ### POST /api/shutdown diff --git a/docs/design/role-template-management.md b/docs/design/role-template-management.md new file mode 100644 index 0000000..880150e --- /dev/null +++ b/docs/design/role-template-management.md @@ -0,0 +1,230 @@ +# Role-template management (item 6.8) — design + +> **Round 1 shipped 2026-05-08** (full-template editing). +> **Round 2 shipped 2026-05-09** (augmented-type versions for upgrade-friendly customisation). + +## Problem + +cf² ships a fixed set of agent-role instruction templates (`cfcf-architect- +instructions.md`, `cfcf-judge-instructions.md`, `cfcf-documenter-instructions.md`, +`cfcf-reflection-instructions.md`, plus the workspace process template +`process.md`). Today users can override any of them by dropping a file into +`~/.cfcf/templates/` (user-global) or `/cfcf-templates/` +(project-local). The override is binary — one file wins; there's no way to +keep multiple drafts, revert easily, or compare against the bundled default. + +This design adds a **versioning + promote-to-production layer** on top of +the existing override mechanism, plus a web UI for editing. + +## Vision (from user, 2026-05-08) + +- New top-level **"Agents"** tab in the web UI. +- One sub-tab per role with the template content visible (scrollable, + searchable). +- Edit-and-save flow that creates **versions** rather than overwriting. +- Selector to view / load any saved version. +- **Promote-to-production** button — the promoted version is what cf² uses + at runtime. +- The bundled default is always available + promoteable (never deletable). + +## Constraints (existing system this needs to fit) + +- `getTemplate(name)` resolves: project-local → user-global → embedded + ([packages/core/src/templates.ts](../../packages/core/src/templates.ts)). +- The embedded default is compiled into the cfcf binary via `import ... + with { type: "text" }`. +- Sentinel-merge of ` ... ` (in + `CLAUDE.md` / `AGENTS.md`) is dev-agent-specific and lives in + `context-assembler.ts` — separate concern from this item. + +## Design + +### Two layers + +1. **Existing**: `~/.cfcf/templates/` is the single override file + `getTemplate()` reads. **Unchanged.** No runtime-code touched. +2. **New**: `~/.cfcf/templates-managed//` is a managed directory of + saved versions + a manifest pointer to which one is "promoted". The + manager writes the promoted version's content to + `~/.cfcf/templates/` (the existing override path) so the agent + runners pick it up automatically without any wiring change. + +### Storage layout + +``` +~/.cfcf/templates-managed/ + cfcf-architect-instructions.md/ + manifest.json { + currentVersionId: "v_" | "default", + versions: [ + { id, label, savedAt, contentHash, type, cfcfVersion } + ] + } + v_.md body — full template body for type="full", + extension only for type="augmented" + cfcf-judge-instructions.md/ + ... +``` + +`"default"` is a sentinel value meaning "no override active". When a user +"promotes default", `~/.cfcf/templates/` is **deleted** so +`getTemplate` falls through to the embedded default. + +### Two version types (round 2) + +Each version has a `type` field: + +| `type` | What's stored on disk | What gets written to the override file at promote time | Auto-upgrade with cf²? | +|---|---|---|---| +| `"full"` | The complete template body | The body verbatim | ❌ Frozen — UI shows "Forked from cf² vX.Y.Z" | +| `"augmented"` | Just the user's extension | ` + separator + ` | ✅ Boot-time recompose picks up the new default | + +**Why both**: full edits give maximum flexibility (delete sections, restructure +the prompt, etc.) but lose the auto-upgrade benefit. Augmented edits are +upgrade-friendly but constrained to additions on top of cf²'s default. Each +serves a real use case; the user picks per version. + +**Round-1 manifests** (saved before the type field existed) read back as +`type: "full"` automatically — no migration needed. + +### Composition + boot refresh (augmented type) + +`composeForOverride(name, type, body)`: +- `type === "full"`: pass-through. +- `type === "augmented"`: `getEmbeddedTemplate(name) + AUGMENTATION_SEPARATOR + body`. + +`AUGMENTATION_SEPARATOR` is `\n\n---\n\n## Custom additions\n\n*(Managed via the +cf² Agents tab — edit this section there, not in this file.)*\n\n` so the +rendered template has a clean visual + textual demarcation. + +`refreshAugmentedOverrides()` runs at every server boot. For every managed +template whose currently-promoted version is augmented, it re-composes against +the live bundled default and writes to the override file **only if the on-disk +content differs**. Cheap, idempotent, transparent to the user. This is the +mechanism that makes cf² upgrades hands-off for augmented versions. + +Full versions are explicitly NOT touched by the boot refresh — the user +fully owns those, and any auto-merge against a new default would be wrong. + +### Templates managed + +MVP scope: +- `cfcf-architect-instructions.md` +- `cfcf-judge-instructions.md` +- `cfcf-documenter-instructions.md` +- `cfcf-reflection-instructions.md` +- `process.md` (workspace process template) + +**Out of scope for MVP**: +- Dev role: no single template file (instructions are programmatically + generated by `context-assembler.generateInstructionContent()`). A + separate "custom directions" insertion-point design will follow. +- Product Architect / Help Assistant: their system prompts are + programmatically assembled, not single template files. +- Per-project (workspace-local) override management. The user-global + layer is the simpler scope for MVP; project-local stays the manual + power-user path (drop a file into `/cfcf-templates/`). + +### API + +`packages/core/src/role-templates.ts` exports: + +```ts +type TemplateManaged = { + name: string; // e.g. "cfcf-judge-instructions.md" + displayName: string; // human label e.g. "Judge" + defaultContent: string; // bundled default (read-only) + currentVersionId: string; // "default" | "v_" + currentContent: string; // resolved content currently in effect + versions: TemplateVersion[]; // saved user versions +}; + +type TemplateVersion = { + id: string; // "v_" + label: string; // user-supplied + savedAt: string; // ISO timestamp + contentHash: string; // sha256 short prefix +}; + +listManagedTemplates(): Promise +getManagedTemplate(name): Promise +saveVersion(name, { label, content }): Promise +updateVersion(name, versionId, { label?, content? }): Promise +deleteVersion(name, versionId): Promise +promoteVersion(name, versionId | "default"): Promise +getVersionContent(name, versionId | "default"): Promise +``` + +### HTTP endpoints + +- `GET /api/role-templates` → list of TemplateManaged (sans full content) +- `GET /api/role-templates/:name` → full TemplateManaged +- `GET /api/role-templates/:name/versions/:versionId` → { content } (versionId = "default" or "v_") +- `POST /api/role-templates/:name/versions` → { label, content } +- `PUT /api/role-templates/:name/versions/:versionId` → { label?, content? } +- `DELETE /api/role-templates/:name/versions/:versionId` → 204 +- `POST /api/role-templates/:name/promote` → { versionId } (or "default") + +### Web UI + +Top-level "Agents" route. Page renders left sidebar (role list) + main +panel (textarea + version selector + actions). + +Main panel sections: +1. **Heading**: role display name + "Currently in production: