Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ htmlcov/
artifacts/
tests/e2e/run-e2e-full.sh
test-results/
tests/test_new_feature.py

# CodeFRAME specific
.codeframe/state.db
Expand Down
25 changes: 19 additions & 6 deletions codeframe/core/react_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,20 @@
}

# ---------------------------------------------------------------------------
# Layer 1: Base rules (verbatim from AGENT_V3_UNIFIED_PLAN.md)
# Layer 1: Base rules (adapted from AGENT_V3_UNIFIED_PLAN.md)
# ---------------------------------------------------------------------------

_LAYER_1_RULES = """\
You are CodeFRAME, an autonomous software engineering agent.

## Rules

- ALWAYS read a file before editing it. Never assume file contents.
- Read files before editing them unless their contents are already provided in \
the "Relevant Source Files" section below. Never assume file contents that aren't shown.
- Make small, targeted edits. Do not rewrite entire files.
- For NEW files: use create_file. For EXISTING files: use edit_file with search/replace.
- Never edit_file on a file you haven't read in this session.
- Never edit_file on a file you haven't seen in this session (either via \
read_file tool or in the Relevant Source Files section).
- Run tests after implementing each major feature, not after every line change.
- Keep solutions simple. Do not add features beyond what was asked.
- Do not change configuration files (pyproject.toml, package.json, etc.) unless
Expand Down Expand Up @@ -234,8 +236,8 @@ def _react_loop(self, system_prompt: str) -> AgentStatus:
"role": "user",
"content": (
"Implement the task described in the system prompt. "
"Start by reading relevant files to understand the current "
"codebase, then make the necessary changes. "
"Relevant source files are provided above when available. "
"Use tools to explore further only if needed. "
"When you are done, respond with a brief summary."
),
}
Expand Down Expand Up @@ -499,14 +501,15 @@ def _build_system_prompt(self, context: TaskContext) -> str:

Layer 1: Base rules (verbatim)
Layer 2: Project preferences + tech stack + file tree summary
+ loaded file contents (reduces redundant read_file calls)
Layer 3: Task title/description + PRD + answered blockers
"""
sections: list[str] = []

# Layer 1: Base rules
sections.append(_LAYER_1_RULES)

# Layer 2: Preferences, tech stack, file tree
# Layer 2: Preferences, tech stack, file tree, loaded file contents
if context.preferences and context.preferences.has_preferences():
pref_section = context.preferences.to_prompt_section()
if pref_section:
Expand All @@ -523,6 +526,16 @@ def _build_system_prompt(self, context: TaskContext) -> str:
tree_lines.append(f" ... and {len(context.file_tree) - 50} more")
sections.append("\n".join(tree_lines))

if context.loaded_files:
file_lines = ["## Relevant Source Files"]
for f in context.loaded_files:
file_lines.append(f"### {f.path}")
file_lines.append("```")
file_lines.append(f.content)
file_lines.append("```")
file_lines.append("")
sections.append("\n".join(file_lines))

# Layer 3: Task info
sections.append(f"## Current Task\n**Title:** {context.task.title}")
if context.task.description:
Expand Down
113 changes: 110 additions & 3 deletions tests/core/test_react_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
)
from codeframe.adapters.llm.mock import MockProvider
from codeframe.core.agent import AgentStatus
from codeframe.core.context import TaskContext
from codeframe.core.context import FileContent, TaskContext
from codeframe.core.gates import GateResult, GateCheck, GateStatus
from codeframe.core.tasks import Task, TaskStatus
from codeframe.core.models import ProgressEvent
Expand Down Expand Up @@ -234,8 +234,8 @@ def test_system_prompt_contains_all_3_layers(
first_call = provider.get_call(0)
system_prompt = first_call["system"]

# Layer 1: base rules
assert "ALWAYS read a file before editing" in system_prompt
# Layer 1: base rules (updated for pre-loaded context awareness)
assert "Read files before editing" in system_prompt

# Layer 2: tech stack / preferences
assert "Python with uv" in system_prompt
Expand All @@ -244,6 +244,113 @@ def test_system_prompt_contains_all_3_layers(
assert "Add hello function" in system_prompt


class TestPreloadedFileContext:
"""Tests for pre-loaded file contents in the system prompt (issue #373)."""

@patch("codeframe.core.react_agent.gates")
@patch("codeframe.core.react_agent.execute_tool")
@patch("codeframe.core.react_agent.ContextLoader")
def test_system_prompt_includes_loaded_files(
self, mock_ctx_loader, mock_exec_tool, mock_gates, workspace, provider, mock_context
):
"""When loaded_files is populated, system prompt should include
a 'Relevant Source Files' section with file contents."""
from codeframe.core.react_agent import ReactAgent

mock_context.loaded_files = [
FileContent(path="src/main.py", content="def hello():\n return 'Hello'", tokens_estimate=10),
FileContent(path="src/utils.py", content="def add(a, b):\n return a + b", tokens_estimate=10),
]

provider.add_text_response("Done.")
mock_ctx_loader.return_value.load.return_value = mock_context
mock_gates.run.return_value = _gate_passed()

agent = ReactAgent(workspace=workspace, llm_provider=provider)
agent.run("task-1")

first_call = provider.get_call(0)
system_prompt = first_call["system"]

assert "## Relevant Source Files" in system_prompt
assert "src/main.py" in system_prompt
assert "src/utils.py" in system_prompt
assert "def hello():" in system_prompt
assert "def add(a, b):" in system_prompt

@patch("codeframe.core.react_agent.gates")
@patch("codeframe.core.react_agent.execute_tool")
@patch("codeframe.core.react_agent.ContextLoader")
def test_system_prompt_no_loaded_files_section_when_empty(
self, mock_ctx_loader, mock_exec_tool, mock_gates, workspace, provider, mock_context
):
"""When loaded_files is empty, no 'Relevant Source Files' section should appear."""
from codeframe.core.react_agent import ReactAgent

# mock_context.loaded_files is already empty by default
assert mock_context.loaded_files == []

provider.add_text_response("Done.")
mock_ctx_loader.return_value.load.return_value = mock_context
mock_gates.run.return_value = _gate_passed()

agent = ReactAgent(workspace=workspace, llm_provider=provider)
agent.run("task-1")

first_call = provider.get_call(0)
system_prompt = first_call["system"]

assert "## Relevant Source Files" not in system_prompt

@patch("codeframe.core.react_agent.gates")
@patch("codeframe.core.react_agent.execute_tool")
@patch("codeframe.core.react_agent.ContextLoader")
def test_rules_acknowledge_preloaded_context(
self, mock_ctx_loader, mock_exec_tool, mock_gates, workspace, provider, mock_context
):
"""Base rules should acknowledge that pre-loaded files don't need re-reading."""
from codeframe.core.react_agent import ReactAgent

provider.add_text_response("Done.")
mock_ctx_loader.return_value.load.return_value = mock_context
mock_gates.run.return_value = _gate_passed()

agent = ReactAgent(workspace=workspace, llm_provider=provider)
agent.run("task-1")

first_call = provider.get_call(0)
system_prompt = first_call["system"]

# Should NOT contain the old "ALWAYS read" rule
assert "ALWAYS read a file before editing" not in system_prompt
# Should contain updated rule that acknowledges pre-loaded context
assert "Read files before editing them unless" in system_prompt
assert "Relevant Source Files" in system_prompt

@patch("codeframe.core.react_agent.gates")
@patch("codeframe.core.react_agent.execute_tool")
@patch("codeframe.core.react_agent.ContextLoader")
def test_initial_message_does_not_mandate_reading(
self, mock_ctx_loader, mock_exec_tool, mock_gates, workspace, provider, mock_context
):
"""The initial user message should not tell the agent to 'start by reading files'."""
from codeframe.core.react_agent import ReactAgent

provider.add_text_response("Done.")
mock_ctx_loader.return_value.load.return_value = mock_context
mock_gates.run.return_value = _gate_passed()

agent = ReactAgent(workspace=workspace, llm_provider=provider)
agent.run("task-1")

first_call = provider.get_call(0)
messages = first_call["messages"]
initial_message = messages[0]["content"]

# Should NOT mandate reading
assert "Start by reading relevant files" not in initial_message


class TestFinalVerification:
"""Tests for final verification behavior."""

Expand Down
Loading