Skip to content

feat(config): unified CODEFRAME.md workflow config (#398)#445

Merged
frankbria merged 3 commits into
mainfrom
feature/issue-398-unified-codeframe-config
Mar 15, 2026
Merged

feat(config): unified CODEFRAME.md workflow config (#398)#445
frankbria merged 3 commits into
mainfrom
feature/issue-398-unified-codeframe-config

Conversation

@frankbria

@frankbria frankbria commented Mar 15, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #398

  • CODEFRAME.md parser: Parse files with YAML front matter (engine, tech_stack, batch, gates, hooks, agent settings) + Markdown body (agent instructions)
  • CodeframeConfig dataclass: Typed config extracted from YAML front matter
  • Precedence: CODEFRAME.md > AGENTS.md > CLAUDE.md in load_preferences()
  • Runtime fallback: load_environment_config() falls back to CODEFRAME.md when no .codeframe/config.yaml
  • CLI: cf init --generate-config scaffolds a starter CODEFRAME.md with auto-detected settings
  • Backward compatible: No changes if CODEFRAME.md doesn't exist

Files Changed

Area Files What
Config Parser core/agents_config.py CodeframeConfig dataclass, parse_codeframe_md(), get_codeframe_config()
Runtime core/config.py Fallback to CODEFRAME.md in load_environment_config()
CLI cli/app.py --generate-config flag, _generate_codeframe_md()
Tests 3 new test files 41 new tests

Test plan

  • 41 new tests across 3 test files
  • Ruff lint clean
  • V2 regression check (running)
  • Demo cf init --generate-config produces valid CODEFRAME.md
  • Demo CODEFRAME.md precedence over AGENTS.md

Summary by CodeRabbit

  • New Features

    • Added init --generate-config to create a starter CODEFRAME.md with YAML front matter and instructional body
    • Introduced support for CODEFRAME.md (YAML + markdown) as a first-class project config and agent instructions source
    • Environment loading will fall back to CODEFRAME.md front matter when dedicated config is missing
  • Tests

    • Added tests covering CODEFRAME.md generation, parsing, precedence, and environment-config fallback

Test User added 2 commits March 14, 2026 21:18
)

Steps 1 & 2 of issue #398: Add CodeframeConfig dataclass, parse_codeframe_md()
function for YAML front matter + markdown body, integrate into load_preferences()
with CODEFRAME.md as highest priority, and add get_codeframe_config() accessor.

15 new tests, all 31 existing tests pass with no regressions.
…steps 3-4)

Step 3: Add --generate-config flag to cf init that creates a starter
CODEFRAME.md with YAML front matter (engine, tech_stack, gates, batch,
agent settings) and a markdown body with project instructions.

Step 4: Update load_environment_config() to fall back to CODEFRAME.md
front matter when .codeframe/config.yaml is absent, with conversion
from CodeframeConfig to EnvironmentConfig.
@coderabbitai

coderabbitai Bot commented Mar 15, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds unified CODEFRAME.md support (YAML front matter + Markdown body), a new cf init --generate-config flag to create starter CODEFRAME.md, and prioritizes CODEFRAME.md when loading preferences/config with fallbacks to AGENTS.md/CLAUDE.md and existing .codeframe/config.yaml.

Changes

Cohort / File(s) Summary
CLI Init Command
codeframe/cli/app.py
Added generate_config: bool = typer.Option(False, "--generate-config", help="Generate starter CODEFRAME.md with project configuration") to init() to enable CODEFRAME.md generation.
Core Preferences & Parsing
codeframe/core/agents_config.py
Introduced CodeframeConfig, parse_codeframe_md(content), and get_codeframe_config(workspace_path). Updated load_preferences() to prioritize CODEFRAME.md and merge/track source file. Added YAML parsing and logging.
Environment Config Mapping
codeframe/core/config.py
Added helpers _has_meaningful_config() and _codeframe_config_to_env_config(); load_environment_config() now falls back to CODEFRAME.md front matter when .codeframe/config.yaml is absent.
CLI Integration Tests
tests/cli/test_init_generate_config.py
New tests for cf init --generate-config: creation, YAML front matter parsing, engine/tech_stack/gates/batch/agent fields, body content, detection modes, and CLI output.
Unit Tests — Codeframe Parser
tests/core/test_codeframe_config.py
Comprehensive tests for parse_codeframe_md(), edge cases (invalid/empty/body-only), propagation into AgentPreferences, load_preferences() precedence, and get_codeframe_config().
Unit Tests — Env Config Fallback
tests/core/test_config_codeframe_fallback.py
Tests for fallback precedence between .codeframe/config.yaml and CODEFRAME.md, and mappings from front matter to EnvironmentConfig (tech_stack → package_manager/test_framework, gates → lint_tools, hooks, batch, agent, engine).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as CLI (app.py)
    participant Generator as Config Generator
    participant FS as Filesystem
    participant Loader as Preference Loader

    User->>CLI: cf init --generate-config
    CLI->>Generator: init(generate_config=true)
    Generator->>Generator: detect tech_stack & gates
    Generator->>FS: write CODEFRAME.md (YAML + body)
    FS-->>User: CODEFRAME.md created
    User->>Loader: load_preferences(workspace)
    Loader->>FS: locate CODEFRAME.md / AGENTS.md / CLAUDE.md
    FS-->>Loader: return CODEFRAME.md (if found)
    Loader->>Loader: parse_codeframe_md(content) -> CodeframeConfig + AgentPreferences
    Loader-->>User: preferences/config returned
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble YAML, tidy and sweet,
I stitch a CODEFRAME where prompts and configs meet,
From init a file blooms with front matter and lore,
Agents wake ready, instructions galore—
Hooray for unified config, hop, clap, and more! 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(config): unified CODEFRAME.md workflow config (#398)' accurately summarizes the main change: introducing unified CODEFRAME.md configuration management. It is concise, clear, and directly reflects the primary objective.
Linked Issues check ✅ Passed All major objectives from issue #398 are met: CODEFRAME.md parser with YAML front matter and Markdown body support, CodeframeConfig typed extraction, precedence implementation, backward compatibility, CLI --generate-config command, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #398 scope: CODEFRAME.md parsing, precedence rules, environment config fallback, CLI generation, and tests. No extraneous modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/issue-398-unified-codeframe-config
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@codeframe/cli/app.py`:
- Around line 353-362: The current gate inference uses an if/elif chain so only
the first match is emitted; modify the logic around tech_stack/ts_lower and
gates so each detected runtime/framework adds its gates independently (replace
the if/elif with separate if checks), and add checks for plain JavaScript
indicators like "javascript" or "node" (so eslint/jest are added when TypeScript
isn't mentioned). Ensure you still append results into the gates list and assign
yaml_section["gates"] when non-empty.
- Around line 180-187: The current generation code unconditionally overwrites
CODEFRAME.md; modify the logic around generate_config/_generate_codeframe_md so
that before calling config_path.write_text you check if (repo_path /
"CODEFRAME.md").exists() and if so fail fast (raise an error or console.print
and exit) unless an explicit force flag is provided; add or wire a boolean force
parameter (e.g., --force or force_generate) into the CLI handler and use it in
this branch to allow overwriting when true, otherwise do not clobber the
existing file and return a clear message referencing CODEFRAME.md and the
--force option.

In `@codeframe/core/config.py`:
- Around line 399-403: The hook keys produced by parse_codeframe_md (e.g.,
after_task, on_failure) are not being normalized before creating HooksConfig, so
documented hooks get dropped; update the cf_config.hooks handling to first
normalize/translate documented hook names to the actual HooksConfig field names
(for example by building a mapping from documented names -> dataclass field
names or by applying a deterministic rename function), then filter using
HooksConfig.__dataclass_fields__ and instantiate config.hooks =
HooksConfig(**filtered); touch the cf_config.hooks branch and use the
HooksConfig symbol and parse_codeframe_md examples to ensure all documented keys
are mapped before constructing config.hooks.
- Around line 414-421: When building budget_kwargs for AgentBudgetConfig from
cf_config.agent, ensure min_iterations is adjusted when max_iterations is
lowered: if "max_iterations" is present and "min_iterations" is not explicitly
provided, set budget_kwargs["min_iterations"] to the same (or to no greater
than) budget_kwargs["max_iterations"] so that
base_iterations/max_iterations/min_iterations remain consistent; update the
block that currently sets budget_kwargs["max_iterations"] and
budget_kwargs["base_iterations"] (the cf_config.agent handling that creates
config.agent_budget = AgentBudgetConfig(**budget_kwargs)) to also set
budget_kwargs["min_iterations"] accordingly.

In `@tests/core/test_codeframe_config.py`:
- Around line 11-17: Remove the unused imports AgentPreferences and
CodeframeConfig from the import list in tests/core/test_codeframe_config.py so
Ruff no longer flags F401; keep only the symbols actually used (e.g.,
load_preferences, parse_codeframe_md, get_codeframe_config) in the from
codeframe.core.agents_config import (...) statement.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d53c3cc-3539-497a-a654-e0bbbd218caf

📥 Commits

Reviewing files that changed from the base of the PR and between 1a15a27 and f66d2af.

📒 Files selected for processing (6)
  • codeframe/cli/app.py
  • codeframe/core/agents_config.py
  • codeframe/core/config.py
  • tests/cli/test_init_generate_config.py
  • tests/core/test_codeframe_config.py
  • tests/core/test_config_codeframe_fallback.py

Comment thread codeframe/cli/app.py
Comment on lines +180 to +187
# Generate CODEFRAME.md if requested
if generate_config:
config_content = _generate_codeframe_md(
tech_stack=final_tech_stack or "",
)
config_path = repo_path / "CODEFRAME.md"
config_path.write_text(config_content)
console.print(" Generated: CODEFRAME.md")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid clobbering an existing CODEFRAME.md.

Re-running cf init --generate-config currently truncates any checked-in CODEFRAME.md without confirmation. Since that file now carries both runtime settings and agent instructions, this should fail fast or require an explicit force flag.

🛠️ Suggested guard
         # Generate CODEFRAME.md if requested
         if generate_config:
-            config_content = _generate_codeframe_md(
-                tech_stack=final_tech_stack or "",
-            )
             config_path = repo_path / "CODEFRAME.md"
-            config_path.write_text(config_content)
+            if config_path.exists():
+                console.print("[red]Error:[/red] CODEFRAME.md already exists. Use --force to overwrite it.")
+                raise typer.Exit(1)
+            config_content = _generate_codeframe_md(tech_stack=final_tech_stack or "")
+            config_path.write_text(config_content, encoding="utf-8")
             console.print("  Generated: CODEFRAME.md")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/cli/app.py` around lines 180 - 187, The current generation code
unconditionally overwrites CODEFRAME.md; modify the logic around
generate_config/_generate_codeframe_md so that before calling
config_path.write_text you check if (repo_path / "CODEFRAME.md").exists() and if
so fail fast (raise an error or console.print and exit) unless an explicit force
flag is provided; add or wire a boolean force parameter (e.g., --force or
force_generate) into the CLI handler and use it in this branch to allow
overwriting when true, otherwise do not clobber the existing file and return a
clear message referencing CODEFRAME.md and the --force option.

Comment thread codeframe/cli/app.py
Comment on lines +353 to +362
# Detect gates from tech stack
gates: list[str] = []
if tech_stack:
ts_lower = tech_stack.lower()
if "python" in ts_lower or "pytest" in ts_lower:
gates.extend(["ruff", "pytest"])
elif "typescript" in ts_lower or "jest" in ts_lower:
gates.extend(["eslint", "jest"])
if gates:
yaml_section["gates"] = gates

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Infer gates for every detected stack, not just the first one.

_detect_tech_stack() can return mixed or JavaScript-only descriptions, but the if/elif chain only emits the first matching gate set and ignores JS stacks that do not mention typescript. The generated CODEFRAME.md ends up incomplete for monorepos and plain JS repos.

🛠️ Suggested fix
     # Detect gates from tech stack
     gates: list[str] = []
     if tech_stack:
         ts_lower = tech_stack.lower()
         if "python" in ts_lower or "pytest" in ts_lower:
             gates.extend(["ruff", "pytest"])
-        elif "typescript" in ts_lower or "jest" in ts_lower:
-            gates.extend(["eslint", "jest"])
+        if any(token in ts_lower for token in ("typescript", "javascript", "jest", "vitest", "eslint")):
+            gates.append("eslint")
+        if "jest" in ts_lower:
+            gates.append("jest")
+        elif "vitest" in ts_lower:
+            gates.append("vitest")
+    gates = list(dict.fromkeys(gates))
     if gates:
         yaml_section["gates"] = gates
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Detect gates from tech stack
gates: list[str] = []
if tech_stack:
ts_lower = tech_stack.lower()
if "python" in ts_lower or "pytest" in ts_lower:
gates.extend(["ruff", "pytest"])
elif "typescript" in ts_lower or "jest" in ts_lower:
gates.extend(["eslint", "jest"])
if gates:
yaml_section["gates"] = gates
# Detect gates from tech stack
gates: list[str] = []
if tech_stack:
ts_lower = tech_stack.lower()
if "python" in ts_lower or "pytest" in ts_lower:
gates.extend(["ruff", "pytest"])
if any(token in ts_lower for token in ("typescript", "javascript", "jest", "vitest", "eslint")):
gates.append("eslint")
if "jest" in ts_lower:
gates.append("jest")
elif "vitest" in ts_lower:
gates.append("vitest")
gates = list(dict.fromkeys(gates))
if gates:
yaml_section["gates"] = gates
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/cli/app.py` around lines 353 - 362, The current gate inference uses
an if/elif chain so only the first match is emitted; modify the logic around
tech_stack/ts_lower and gates so each detected runtime/framework adds its gates
independently (replace the if/elif with separate if checks), and add checks for
plain JavaScript indicators like "javascript" or "node" (so eslint/jest are
added when TypeScript isn't mentioned). Ensure you still append results into the
gates list and assign yaml_section["gates"] when non-empty.

Comment thread codeframe/core/config.py
Comment on lines +399 to +403
# Map hooks
if cf_config.hooks:
valid_hook_fields = {f.name for f in HooksConfig.__dataclass_fields__.values()}
filtered = {k: v for k, v in cf_config.hooks.items() if k in valid_hook_fields}
config.hooks = HooksConfig(**filtered)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Normalize the documented hook names before filtering.

parse_codeframe_md() exposes hook keys like after_task and on_failure, but this branch only preserves exact HooksConfig field names. In the CODEFRAME.md fallback path those hooks are silently dropped, so the runtime config never executes them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/config.py` around lines 399 - 403, The hook keys produced by
parse_codeframe_md (e.g., after_task, on_failure) are not being normalized
before creating HooksConfig, so documented hooks get dropped; update the
cf_config.hooks handling to first normalize/translate documented hook names to
the actual HooksConfig field names (for example by building a mapping from
documented names -> dataclass field names or by applying a deterministic rename
function), then filter using HooksConfig.__dataclass_fields__ and instantiate
config.hooks = HooksConfig(**filtered); touch the cf_config.hooks branch and use
the HooksConfig symbol and parse_codeframe_md examples to ensure all documented
keys are mapped before constructing config.hooks.

Comment thread codeframe/core/config.py
Comment on lines +414 to +421
if cf_config.agent:
budget_kwargs: dict[str, Any] = {}
if "max_iterations" in cf_config.agent:
budget_kwargs["max_iterations"] = cf_config.agent["max_iterations"]
# Also set base_iterations to match (they're conceptually the same)
budget_kwargs["base_iterations"] = cf_config.agent["max_iterations"]
if budget_kwargs:
config.agent_budget = AgentBudgetConfig(**budget_kwargs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep min_iterations in sync with a lowered max_iterations.

A value like agent.max_iterations: 5 produces base_iterations=5, max_iterations=5, and the default min_iterations=15. That makes the fallback EnvironmentConfig internally invalid before it ever reaches runtime validation.

🛠️ Suggested fix
     if cf_config.agent:
         budget_kwargs: dict[str, Any] = {}
         if "max_iterations" in cf_config.agent:
-            budget_kwargs["max_iterations"] = cf_config.agent["max_iterations"]
-            # Also set base_iterations to match (they're conceptually the same)
-            budget_kwargs["base_iterations"] = cf_config.agent["max_iterations"]
+            max_iterations = int(cf_config.agent["max_iterations"])
+            default_min_iterations = AgentBudgetConfig().min_iterations
+            budget_kwargs["max_iterations"] = max_iterations
+            budget_kwargs["base_iterations"] = max_iterations
+            budget_kwargs["min_iterations"] = min(default_min_iterations, max_iterations)
         if budget_kwargs:
             config.agent_budget = AgentBudgetConfig(**budget_kwargs)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/config.py` around lines 414 - 421, When building budget_kwargs
for AgentBudgetConfig from cf_config.agent, ensure min_iterations is adjusted
when max_iterations is lowered: if "max_iterations" is present and
"min_iterations" is not explicitly provided, set budget_kwargs["min_iterations"]
to the same (or to no greater than) budget_kwargs["max_iterations"] so that
base_iterations/max_iterations/min_iterations remain consistent; update the
block that currently sets budget_kwargs["max_iterations"] and
budget_kwargs["base_iterations"] (the cf_config.agent handling that creates
config.agent_budget = AgentBudgetConfig(**budget_kwargs)) to also set
budget_kwargs["min_iterations"] accordingly.

Comment thread tests/core/test_codeframe_config.py
Repository owner deleted a comment from claude Bot Mar 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/core/test_codeframe_config.py (1)

274-307: Consider adding a test for parent directory walking.

The tests cover the basic cases well. However, get_codeframe_config() walks up the directory tree to find CODEFRAME.md (per lines 556-566 in agents_config.py). A test verifying that a CODEFRAME.md in a parent directory is found when called from a subdirectory would improve coverage of this behavior.

💡 Example additional test
def test_get_codeframe_config_finds_parent(self):
    """Finds CODEFRAME.md in parent directory when searching from subdirectory."""
    with TemporaryDirectory() as tmpdir:
        root = Path(tmpdir)
        subdir = root / "src" / "module"
        subdir.mkdir(parents=True)

        codeframe_md = root / "CODEFRAME.md"
        codeframe_md.write_text("""\
---
engine: plan
---
""")

        config = get_codeframe_config(subdir)
        assert config is not None
        assert config.engine == "plan"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/test_codeframe_config.py` around lines 274 - 307, Add a new unit
test in TestGetCodeframeConfig that verifies get_codeframe_config walks parent
directories: create a temporary root with a CODEFRAME.md (containing at least
engine: plan), make a nested subdirectory (e.g., root/src/module), call
get_codeframe_config(subdir) and assert it returns a config with engine ==
"plan"; name the test method test_get_codeframe_config_finds_parent so it
clearly targets the parent-walking behavior and references CODEFRAME.md and
get_codeframe_config.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/core/test_codeframe_config.py`:
- Around line 274-307: Add a new unit test in TestGetCodeframeConfig that
verifies get_codeframe_config walks parent directories: create a temporary root
with a CODEFRAME.md (containing at least engine: plan), make a nested
subdirectory (e.g., root/src/module), call get_codeframe_config(subdir) and
assert it returns a config with engine == "plan"; name the test method
test_get_codeframe_config_finds_parent so it clearly targets the parent-walking
behavior and references CODEFRAME.md and get_codeframe_config.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bcb51c82-5df4-4f35-a376-1b4d191577ab

📥 Commits

Reviewing files that changed from the base of the PR and between f66d2af and e5cddb5.

📒 Files selected for processing (1)
  • tests/core/test_codeframe_config.py

Repository owner deleted a comment from claude Bot Mar 15, 2026
Repository owner deleted a comment from claude Bot Mar 15, 2026
Repository owner deleted a comment from claude Bot Mar 15, 2026
Repository owner deleted a comment from claude Bot Mar 15, 2026
@frankbria frankbria merged commit 1d3f2d5 into main Mar 15, 2026
22 checks passed
@frankbria frankbria deleted the feature/issue-398-unified-codeframe-config branch March 24, 2026 23:28
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.

[Phase 5] Unified CODEFRAME.md Workflow Config

1 participant