Skip to content

[Spec 618] Systematic configuration overhaul - #624

Merged
waleedkadous merged 30 commits into
mainfrom
builder/spir-618-systematic-configuration-overh
Mar 25, 2026
Merged

waleedkadous merged 30 commits into
mainfrom
builder/spir-618-systematic-configuration-overh

Conversation

@waleedkadous

Copy link
Copy Markdown
Contributor

Summary

Specification for a major configuration overhaul covering four concerns:

  • Unified .codev/config.json — layered config (global ~/.codev/ + project .codev/) replacing af-config.json, with deep merge semantics
  • Pluggable consultation models — configurable via porch.consultation.models (single-model, none, parent modes)
  • Runtime file resolution — framework files (protocols, roles, templates, consult-types) resolve from the npm package at runtime; eliminates codev update
  • Remote framework sources — codev sync fetches shared team frameworks via forge or arbitrary CLI command

Two rounds of 3-way consultation (Gemini, Codex, Claude) completed. Architect review comments addressed.

Status

Spec only — no implementation code. Pending spec-approval gate.

Test plan

  • Review spec for completeness and feasibility
  • Validate config schema design
  • Confirm migration path from af-config.json

🤖 Generated with Claude Code

Addresses feedback from Gemini, Codex, and Claude:
- Claude-specific files (.claude/skills, CLAUDE.md) explicitly out of scope for runtime resolution
- Deep merge semantics defined (arrays replace, objects merge, null removes)
- Two independent resolution chains documented and unification planned
- Error handling for config files specified
- models field typed as string | string[]
- Default config when no files exist
- All config loading sites enumerated
- Remote source ref pinning added
Addresses iteration 2 feedback from all three reviewers:
- Expanded config loading sites from 3 to 10 (forge, tower, spawn-worktree, etc.)
- Added third resolution chain (prompts.ts has its own PROTOCOL_PATHS)
- Added forge section to config schema
- Changed codev update to one-time skeleton cleanup (prevents update shadowing)
- Defined consultation mode behavior in verify steps
- Added codev adopt --update flag for Claude-specific file updates
- Clarified null removal, default config, remote source pre-Phase 4 behavior
…esign

Added 'What Lives Where' table, 'What This Means for Users' section, and
concrete example of resolution order to make it clear that version-dependent
files (protocols, roles, templates) stay in the npm package and are read at
runtime — not copied into projects.
Full design for remote protocol fetching:
- Git clone via shallow clone, uses existing SSH/HTTPS auth
- Cache at ~/.codev/cache/ with immutable ref optimization
- ref is required (prevents silent drift)
- Fail hard on fetch failure (no silent fallback)
- codev protocols fetch/--force commands
- Resolution chain: local > project > remote > package
- Security: trust model, audit, immutable refs recommended
Remote protocol fetching now goes through the forge's concept-command system
via a new 'repo-archive' concept, not raw git clone. This:
- Uses existing forge auth (gh CLI for GitHub, glab for GitLab, etc.)
- No dependency on git CLI being installed
- Platform-agnostic via forge providers
- Consistent with how codev interacts with external platforms
- Source config is forge-relative (owner/repo), not a git URL
Remote sources now cover the full framework (protocols, roles, consult-types,
templates, porch prompts), not just protocols. Config key is 'framework',
command is 'codev sync' (with --force and --status flags).
- framework.type: 'forge' (default) or 'command' (arbitrary CLI)
- source accepts full URLs and forge-relative owner/repo
- ref is now optional (defaults to repo's default branch)
- Remote framework can include base config.json (participates in config layering)
- codev update becomes the full migration tool (moves config, cleans skeleton, updates Claude files)
- command type: escape hatch for self-hosted git, S3, NFS, etc.
{
"porch": {
"consultation": {
"models": ["claude", "gemini"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these really models? I'm not raising an issue with terminology , just wondering if this is a spot where users would add custom ones. Is the model in this context the cli command that codev executes to trigger the consult? If yes, I might try and do gemini -m "<some model of my choosing>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! You're right that "models" here refers to CLI command names (gemini, codex, claude) — each maps to a consult -m <name> invocation, not a raw model ID like gemini-3-pro-preview. Your instinct about wanting to use a custom backing model is spot on though — today consult only knows these three backends, so models in the config must be from that set. Extending to support user-registered models (e.g. a custom Ollama backend) is a natural follow-on but out of scope for this spec. We'll make sure the spec clarifies this distinction.

- `["claude"]` — single-model mode (addresses #592)
- `["gemini", "codex", "claude"]` — current default (backward compatible)
- `"parent"` — delegate to architect session (foundation for #614)
- `"none"` — skip all consultations (equivalent to "without consultation")

@damongabrielle damongabrielle Mar 19, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I see, this section makes it clearer to me that they're cli commands, and not the backing model names.

| Mode | Verify step behavior |
|------|---------------------|
| `["claude"]` | Run consult with only claude. Verify passes if claude approves. |
| `["gemini", "codex", "claude"]` | Current behavior — 3-way parallel consultation. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if models must be one of the three

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, currently they must be one of the three registered backends. The spec should state this explicitly — and note that adding new backends would be a separate feature. Good catch.


- **Fresh projects**: `codev init` no longer copies protocols/roles/templates. They resolve from the package.
- **Existing projects**: Local copies in `codev/protocols/` etc. continue to work (they take precedence). Over time, unmodified copies are cleaned up (see "Stale skeleton file cleanup" below).
- **Customization**: To customize a protocol, copy just that file into `.codev/protocols/<name>/protocol.json`. Your override takes precedence over the package version.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does customize specifically mean override or does overrie behavior on ly occur if I the name I give the protocol in the path overlaps with an existing protocol? For instance if I named it SPIR that'd be an override but if I named the new protocol FOOBAR, then I've essentially created a new protocol? I am also not forgetting your guidance that my idea for a Quality engineering role I mentioned probably belongs somewhere as a part of a protocol or extension to an existing one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've got it exactly right — it's both. Same name = override (your custom spir replaces the built-in one), new name like FOOBAR = net-new protocol. The resolution chain just looks for protocols/<name>/protocol.json at each tier, so whatever name you use in the path is what you get. And yes, your QE role idea could absolutely work as either a new protocol or an extension to SPIR (e.g. adding a QE verification phase). This framework makes both paths possible.

- Detects existing `codev/protocols/` etc. — leaves them in place (they take precedence via resolution order)
- Creates `.codev/config.json` if not present

**`codev update` becomes the migration tool**:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes a lot of sense to me.


### 4. Remote Framework Sources

Teams can point to a shared repository containing their development framework — protocols, roles, consult-types, and templates:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature is going to be awesome!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Damon! Really appreciate you taking the time to read through the whole spec and leave such thoughtful questions.

}
```

The config key is `framework`, not `protocols`, because a team's shared methodology includes more than just protocols — it includes roles, consult-types, templates, porch prompts, and base config. The entire framework bundle is fetched and cached as a unit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exactly the feature I was hoping for. Quick clarifying question about frameworks. Will storing multiple frameworks in the same repo be ok? (Basically wondering if the full path would be respected) Thinking about collecting favorites in one repo.

  "framework": {
    "source": "myorg/our-codev-frameworks/team-a-favorite-framework",
    "ref": "v1.2.0"
  }
  "framework": {
    "source": "myorg/our-codev-frameworks/<team-b-python-framework">,
    "ref": "v1.2.0"
  }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question, and the short answer is yes — the source field supports subpaths, so myorg/our-codev-frameworks/team-a-favorite-framework and myorg/our-codev-frameworks/team-b-python-framework would both work from the same repo. The forge repo-archive concept would need to handle extracting just the specified subdirectory rather than the entire repo archive. We'll make sure the spec explicitly addresses subpath extraction and cache keying so there's no ambiguity about how this works.

@nharward

Copy link
Copy Markdown
Collaborator

Suggestion: for config/temp/etc. file locations use the XDG Base Directory Specification. Benefits include avoiding clutter in $HOME, clear separation of concerns for different types of files, and well-known user-level customization with standard environment variables.

@waleedkadous

Copy link
Copy Markdown
Contributor Author

Thanks for the suggestion, Neil! We considered XDG and it's a solid standard. We went with ~/.codev/ to stay consistent with Claude Code's ~/.claude/ convention, which our users are already familiar with. On macOS (our primary platform today) XDG isn't widely adopted. That said, if Linux adoption grows, adding XDG support as an alternative (respecting $XDG_CONFIG_HOME when set, falling back to ~/.codev/) would be a natural enhancement. We'll keep it in mind.

…raction, override semantics

1. Section 2: Add note that model names must be registered backends (gemini, codex, claude);
   user-defined backends are future/out-of-scope.
2. Section 4: Add paragraph on subpath sources — forge fetches full archive, extracts
   subdirectory only; cache key includes full source string for independent caching.
3. Section 3: Add bullet clarifying override vs. create semantics — same name as built-in
   overrides it, new name creates net-new protocol, resolution chain handles both identically.
Six phases: unified config loader, unified file resolver, pluggable
consultation models, init/adopt/update changes, remote framework
sources, migration and cleanup.
Address issues from 3-way consultation (Gemini, Codex, Claude):
- Fix config layering contradiction: project > global consistently across all phases
- Fix consumer list: tower-terminals.ts and tower-instances.ts instead of architect.ts
- Clarify parent/none validation path for consultation modes
- Add command type env vars (CODEV_OUTPUT_DIR, CODEV_REF) to Phase 5
- Add ref requirement coverage in Phase 5 acceptance criteria
- Add migration edge-case test scenarios to Phase 4
- Note: forge providers are github/gitlab/gitea (not Bitbucket)
- Drop af-config.json backward compat: hard error if found, no fallback
- codev update: always refreshes CLAUDE.md/AGENTS.md (not just a no-op)
- Remove --update flag from adopt (unclear purpose, codev update handles it)
- repo-archive uses existing forge infrastructure, not standalone scripts
- Worktree symlink: just rename af-config.json to .codev/config.json
- Update risk analysis and validation checkpoints accordingly
New lib/config.ts provides centralized config loading with:
- Three-layer merge: defaults → ~/.codev/config.json → .codev/config.json
- Deep merge semantics: objects recurse, arrays replace, null deletes
- Hard error if legacy af-config.json found (migration required)

All consumers updated to use .codev/config.json:
- agent-farm config.ts delegates to unified loader
- porch config.ts delegates to unified loader
- forge.ts delegates to unified loader
- tower-terminals.ts, tower-instances.ts read .codev/config.json
- send.ts detects .codev/ directory instead of af-config.json
- spawn-worktree.ts symlinks .codev/config.json into worktrees
- All string references updated from af-config.json to .codev/config.json
…json

- agent-farm config.test.ts: mock lib/config.ts to avoid real workspace
- spawn-worktree.test.ts: update symlinkConfigFiles tests for new .codev/ pattern, add mkdirSync mock
- forge.test.ts: use .codev/config.json instead of af-config.json
- readJsonFile: handle EACCES/EPERM with warning + fallback (per spec)
- loadForgeConfig: don't swallow af-config.json hard error
- forge.test.ts: fix stale test name referencing af-config.json
…lution

resolveCodevFile() now checks .codev/ → codev/ → skeleton/ in order:
- skeleton.ts: added .codev/<path> as highest-priority tier
- protocol.ts: replaced PROTOCOL_PATHS with resolveCodevFile()
- prompts.ts: replaced PROTOCOL_PATHS with resolveCodevFile()
- config.ts getRolesDir: added .codev/roles/ check

Tests: 4 new tests for .codev/ resolution priority and fallthrough
…resolver

Tests were writing protocols to codev-skeleton/ which is no longer in
the resolution chain. Updated to use codev/ (project-level tier).
…pts and roles

Address reviewer feedback: directory-level resolution broke per-file
fallback when partial overrides exist.

- prompts.ts: loadPromptFile now resolves each prompt file individually
  via resolveCodevFile(), removed findPromptsDir entirely
- roles.ts: findRolePromptPath uses resolveCodevFile when workspaceRoot
  is available, with legacy fallback for backward compat
- tower-utils.ts: passes workspaceRoot to RoleConfig
porch verify steps now use configurable models from .codev/config.json:
- resolveConsultationModels() checks config porch.consultation.models
- Config overrides protocol's verify.models (protocol is fallback)
- "none" mode: skip verification entirely with clear note
- "parent" mode: emit phase-review gate for architect review
- Model validation: must be gemini, codex, or claude
- Default behavior (no config) unchanged

Tests: 7 new tests covering config loading for consultation modes
…fig models

Address reviewer feedback: porch done was using protocol models,
breaking config overrides.

- index.ts done(): resolves effective models from config, handles
  "none" (skip verification) and "parent" (delegate to architect)
- next.ts resolveConsultationModels(): let config errors propagate
  instead of silently swallowing them
…dev/

- init.ts: creates .codev/config.json instead of af-config.json,
  no longer copies framework files (protocols, roles, consult-types)
- adopt.ts: creates .codev/config.json, leaves existing local files
  in place (resolution chain handles them)
- update.ts: rewritten with two responsibilities:
  A. One-time migration: af-config.json → .codev/config.json,
     clean unmodified skeleton files using .update-hashes.json
  B. Claude file refresh: always updates CLAUDE.md, AGENTS.md, skills
Skills are Claude-specific files that should be updated on every
codev update run, not skipped when existing.
New codev sync command for fetching and caching remote frameworks:
- repo-archive forge concept for github/gitlab/gitea providers
- codev sync, codev sync --force, codev sync --status
- Cache at ~/.codev/cache/framework/<hash>/<ref>/
- Subpath support (myorg/repo/team-a extracts only team-a/)
- Framework type: "forge" (default) and "command" (arbitrary shell)
- Immutable refs cached without re-fetch; branches re-fetch on sync

Four-tier resolution chain:
- skeleton.ts: .codev/ → codev/ → cache → skeleton
- config.ts: defaults → cache config → global → project

Tests: 10 new tests for cache dirs, resolution chain with cache
…cepts

Updated concept count from 15 to 16 after adding repo-archive.
Address reviewer feedback:
- Initialize framework cache dir in requireWorkspace() so remote
  framework files and config actually resolve in production
- fetchFromCommand: use temp dir + atomic rename (no partial cache)
- isImmutableRef: tighten to semver-like patterns (v1.0.0, not 1-branch)
…config.json

Migrated all test files from af-config.json to .codev/config.json:
- tower-test-utils.ts: creates .codev/config.json in test workspaces
- send.test.ts: workspace detection checks for .codev/ dir
- tower-instances/terminals tests: read .codev/config.json
- All E2E tests: create .codev/ dir and write config.json there
@waleedkadous

Copy link
Copy Markdown
Contributor Author

Architect Integration Review (3-Way CMAP)

Excellent work on this massive overhaul — the unified config loader, four-tier resolution chain, and pluggable consultation models are all well-designed. The test coverage is strong. However, the 3-way review (Gemini, Codex, Claude) surfaced 5 issues I'd like to see addressed before merge.

1. Tower config reads not migrated to unified loader (all 3 reviewers)

tower-instances.ts and tower-terminals.ts (3 locations) still use raw fs.readFileSync + JSON.parse instead of the unified loadConfig(). This means they miss global ~/.codev/config.json layering and deep merge semantics. The spec explicitly lists these as sites that "must use new centralized loader."

2. af-config.json migration path (Codex + Gemini)

Two related issues:

  • The loader hard-errors when af-config.json exists — the spec says there should be a migration period with a deprecation warning where af-config.json still works.
  • Data loss scenario: if codev adopt creates a default .codev/config.json first, then codev update skips migrating af-config.json (because .codev/config.json already exists) but still deletes it. User customizations are lost.

3. Workspace detection false positive (Codex)

detectWorkspaceRoot() now checks for a .codev/ directory, which will match ~/.codev/ when run from $HOME. This causes false-positive workspace detection. Consider requiring both .codev/config.json and .git (or similar) to be present.

4. Framework cache never initialized for core commands (Gemini)

setFrameworkCacheDir() is only called from requireWorkspace(), which only runs for legacy team commands. Core commands (porch, consult, af) never call it, so remote framework resolution is effectively dead code in production. Perhaps move cache dir resolution to a shared module with lazy initialization.

5. URL sources not parsed correctly (Codex)

parseSource() in commands/sync.ts assumes owner/repo format and will mis-parse full URLs (e.g., https://gitlab.example.com/team/protocols) that the spec says are supported.

@waleedkadous

Copy link
Copy Markdown
Contributor Author

6. codev update silently overwrites user-customized skills (Codex)

The old codev update called copySkills(targetDir, templatesDir, { skipExisting: true }) — preserving any skills the user had customized. The new version calls copySkills(targetDir, templatesDir) without skipExisting, which defaults to false. This means running codev update will silently overwrite user-customized .claude/skills/ files with the package defaults.

This is especially important given that the whole point of this PR is enabling user customization. Please restore { skipExisting: true } or add conflict handling similar to copyRootFiles.

1. Tower config reads: tower-instances.ts and tower-terminals.ts
   now use loadConfig() instead of manual fs.readFileSync
2. af-config.json migration: deprecation warning with fallback
   instead of hard error; fix data loss when both configs exist
   (merge instead of skip+delete)
3. Workspace detection: check .codev/config.json (file) not .codev/
   (directory) to avoid false positive at HOME
4. Framework cache: lazy initialization in skeleton.ts computes
   cache dir on first access, no explicit startup wiring needed
5. URL source parsing: parseSource handles http/https/git@ URLs
Calling copySkills without skipExisting silently overwrites user-customized
skills. Restore skipExisting: true to preserve customizations while still
adding new skills from the package.
Per architect: we don't do fallbacks. af-config.json presence triggers
a hard error directing user to run 'codev update' to migrate. That IS
the migration path.
@waleedkadous
waleedkadous merged commit 499f35c into main Mar 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants