Skip to content

feat: CodeFRAME v2 - Headless CLI-first architecture - #299

Merged
frankbria merged 94 commits into
mainfrom
v2-refactor
Jan 22, 2026
Merged

feat: CodeFRAME v2 - Headless CLI-first architecture#299
frankbria merged 94 commits into
mainfrom
v2-refactor

Conversation

@frankbria

@frankbria frankbria commented Jan 22, 2026

Copy link
Copy Markdown
Owner

Summary

This PR introduces the CodeFRAME v2 refactor - a complete architectural shift to a headless, CLI-first design following the "strangler fig" pattern. The v2 architecture enables fully autonomous AI-driven development without requiring a running server.

Key Changes

  • Headless core: All domain logic moved to codeframe/core/ with no FastAPI/HTTP dependencies
  • CLI-first Golden Path: Complete workflow works from CLI without a server (codeframe init, prd add, tasks generate, work start --execute)
  • Agent execution system: Full AI agent orchestration with planning, execution, verification, and self-correction
  • Batch orchestration: Parallel task execution with dependency graph analysis and LLM-based dependency inference
  • Comprehensive credential management: Encrypted storage, audit logging, and validation
  • PRD versioning: Full version control for product requirements documents

Architecture Highlights

Component Purpose
core/agent.py Agent orchestrator with blocker detection
core/planner.py LLM-powered implementation planning
core/executor.py Code execution engine with rollback
core/conductor.py Batch orchestration with worker pool
core/dependency_graph.py DAG operations for parallel execution
adapters/llm/ Anthropic Claude integration

CLI Commands (v2)

# Workspace
cf init <repo> --detect           # Initialize + auto-detect tech stack

# PRD
cf prd add <file.md>              # Add requirements document
cf prd show                       # Display current PRD

# Tasks
cf tasks generate                 # LLM generates tasks from PRD
cf tasks list --status READY      # List tasks by status

# Work execution
cf work start <id> --execute      # Run AI agent on task
cf work start <id> --verbose      # With detailed progress
cf work batch run --strategy auto # Parallel with LLM dependency inference

# Blockers
cf blocker list                   # Human-in-the-loop blockers
cf blocker answer <id> "response" # Resolve blockers

Testing

  • 400+ v2-specific tests (pytest -m v2)
  • All tests pass with >85% coverage target
  • Integration tests use real services (no mocks)

Test plan

  • Run uv run pytest -m v2 - all tests pass
  • Run uv run ruff check . - no linting errors
  • Test Golden Path workflow end-to-end:
    • cf init . --detect
    • cf prd add docs/sample.md
    • cf tasks generate
    • cf work start <task-id> --execute --verbose
  • Verify python -m codeframe --help shows correct program name
  • Verify batch execution with cf work batch run --all-ready

Summary by CodeRabbit

  • New Features

    • CLI entry points; full credential management and audit logging; LLM adapters (live + mock); autonomous agent flow (plan→execute→verify→self-correct); batch execution (serial/parallel/auto) with progress/ETA; checkpoints, blockers, verification gates, dependency analysis, quick fixes, artifact export, workspace/environment config, PRD & task management, and run tracking.
  • Documentation

    • New v2 Golden Path guides, CLI wireframes, batch execution plan, roadmap, test reports, and workflow/gap analyses.
  • Chores

    • Added test-results/ to .gitignore.

✏️ Tip: You can customize this high-level summary in your review settings.

Test User added 30 commits January 14, 2026 12:01
Phase 1 - Workspace & Events:
- New Typer CLI at codeframe/cli/app.py with domain-grouped commands
- Workspace management with SQLite state storage in .codeframe/
- Append-only event log for all workspace activity
- Updated pyproject.toml entry point

Phase 2 - PRD & Task Management:
- PRD storage with title extraction and metadata
- Task state machine (BACKLOG→READY→IN_PROGRESS→BLOCKED→DONE→MERGED)
- LLM-powered task generation from PRD (with simple fallback)
- Status transitions with validation

Test coverage:
- 28 state machine unit tests
- 17 workspace unit tests
- 11 integration tests covering full Phase 1-2 flow
Shows workspace summary including:
- PRD info (title and date)
- Task counts by status with color-coding
- Recent activity from event log
- Configurable event count with --events/-e flag

Emits STATUS_VIEWED event for activity tracking.
New runtime module (codeframe/core/runtime.py):
- Run lifecycle management (start, stop, complete, fail, block, resume)
- RunStatus enum (RUNNING, COMPLETED, FAILED, BLOCKED)
- Stub agent execution loop that emits events

Work CLI commands:
- work start: Creates run, transitions task to IN_PROGRESS
- work stop: Gracefully stops run, returns task to READY
- work resume: Resumes a blocked run
- work status: Shows active runs

The --execute flag on work start runs the stub agent, emitting
AGENT_STEP_STARTED and AGENT_STEP_COMPLETED events for testing.
New blockers module (codeframe/core/blockers.py):
- BlockerStatus enum (OPEN, ANSWERED, RESOLVED)
- Blocker CRUD operations
- Partial ID matching for convenience

Blocker CLI commands:
- blocker list: Show open blockers (--all for all)
- blocker show: View blocker details with question/answer
- blocker create: Manually create blockers for testing
- blocker answer: Provide answer to unblock work
- blocker resolve: Mark blocker as resolved

Emits BLOCKER_CREATED, BLOCKER_ANSWERED, BLOCKER_RESOLVED events.
New gates module (codeframe/core/gates.py):
- Auto-detect available gates (pytest, ruff, mypy, npm-test, npm-lint)
- Run gates with configurable verbosity
- Capture output, exit codes, and timing
- GateStatus enum (PASSED, FAILED, SKIPPED, ERROR)

Review CLI command:
- codeframe review: Run all detected gates
- --gate/-g: Run specific gates only
- --verbose/-v: Show full gate output

Emits GATES_STARTED and GATES_COMPLETED events.

Also: Added .codeframe/ to .gitignore
New artifacts module (codeframe/core/artifacts.py):
- export_patch: Export git diff as a .patch file
- create_commit: Create git commits with proper validation
- get_status: Get git status summary
- list_patches: List previously exported patches

Patch CLI commands:
- patch export: Export changes to .codeframe/patches/
- patch list: List exported patches
- patch status: Show git status summary

Commit CLI commands:
- commit create: Create commits with -m message
- commit create --all: Stage all changes before committing

Emits PATCH_EXPORTED and COMMIT_CREATED events.
Adds checkpoint module for state snapshots and updates summary command
to display workspace overview. Completes Golden Path CLI implementation.
Tracks the work needed to replace execute_stub() with a fully
functional agent that can read context, plan, and execute code changes.
Adds codeframe/adapters/llm/ with:
- base.py: Protocol, ModelSelector, LLMResponse, Tool/ToolCall types
- anthropic.py: Claude provider with tool use and streaming support
- mock.py: Test provider with call tracking and queued responses

Task-based model selection heuristic:
- Planning/reasoning → Sonnet
- Execution → Sonnet
- Generation → Haiku
Adds codeframe/core/context.py with:
- TaskContext: dataclass holding task, PRD, blockers, and file contents
- ContextLoader: loads and scores relevant files within token budget
- Keyword extraction and relevance scoring for file selection
- Token budgeting to maximize useful context

Also adds list_for_task() helper to blockers module.
Adds codeframe/core/planner.py with:
- Planner: transforms TaskContext into ImplementationPlan via LLM
- ImplementationPlan: structured plan with steps, files, complexity
- PlanStep: individual step with type, target, dependencies
- StepType enum: file_create, file_edit, shell_command, verification

Uses Purpose.PLANNING to select stronger model for reasoning tasks.
Adds codeframe/core/executor.py with:
- Executor: executes plan steps via LLM-driven code generation
- File operations: create, edit, delete with rollback tracking
- Shell commands: sandboxed execution with dangerous pattern blocking
- Dry-run mode for previewing changes without applying them
- Full rollback capability for all file changes

Uses Purpose.EXECUTION for balanced model selection during code generation.
Adds codeframe/core/agent.py with:
- Agent: main orchestrator coordinating context, planning, execution
- AgentState: serializable state for pause/resume
- Blocker detection: creates blockers for failures needing human input
- Gate integration: runs verification after file changes
- Event emission: callback-based event system for monitoring

Patterns detected for blocker creation:
- Consecutive failures exceeding threshold
- 'not found', 'missing', 'credentials' errors
- Verification failures after max attempts
Adds execute_agent() to runtime.py:
- Integrates full agent orchestration (context, plan, execute, verify)
- Requires ANTHROPIC_API_KEY for real execution
- Emits workspace events for monitoring

Updates CLI work start command:
- --execute: runs the real AI agent
- --dry-run: preview changes without applying
- --stub: legacy stub execution for testing

The Golden Path is now fully functional from PRD to committed code.
- GateResult has `passed` (bool), not `status`
- GateCheck has `name`, not `gate`

Fixes AttributeError during agent execution verification.
Task status is now only updated by runtime.complete_run(),
avoiding DONE -> DONE transition error.
- Python files: check existence and syntax
- Commands: execute as shell
- Other paths: check existence

Fixes issue where 'task_tracker.py' was run as a command instead of verified.
- Update status badge to reflect v2 completion
- Add "What's New" section for v2 agent implementation
- Document CLI-first workflow as recommended approach
- Update architecture diagram to show CLI/Agent orchestrator
- Add complete CLI command reference
- Move previous updates to collapsible sections
- Update roadmap with completed items
- Add links to v2 documentation (Golden Path, Agent Tasks)
- Update status to v2 Agent Implementation Complete
- Add agent system architecture section with component table
- Add execution flow diagram for agent orchestration
- Document critical state separation pattern (Agent→AgentState, Runtime→TaskStatus)
- Add recent updates section with bug fixes
…al errors

Previously, the agent would create blockers for any error matching patterns
like "not found" or "missing". This caused technical errors (syntax errors,
file not found, import errors) to block execution when the agent should
solve them automatically.

Changes:
- Add HUMAN_INPUT_PATTERNS for genuine human-needed situations (credentials,
  unclear requirements, design decisions)
- Add TECHNICAL_ERROR_PATTERNS for errors agent can self-correct (file not
  found, syntax errors, import errors)
- Add _classify_error() to categorize errors
- Add _attempt_self_correction() to use LLM to fix technical errors
- Update _execute_plan() to try self-correction before creating blockers
- Update tests to reflect new behavior

The agent now:
1. Classifies errors as "technical" or "human"
2. For technical errors: tries self-correction (up to 2 attempts)
3. Only creates blockers for human-input-needed situations or after
   exhausting self-correction attempts
When a blocker is answered, the associated task is now automatically
reset to READY status. This eliminates the need for separate "work stop"
and "work resume" commands.

Flow is now:
1. Task runs → hits blocker → status becomes BLOCKED
2. User answers blocker: `cf blocker answer <id> "answer"`
3. Task automatically resets to READY
4. User can restart: `cf work start <id> --execute`

The blocker answer includes the user's input, so the agent will have
access to it when the task is restarted.
The previous code used Python's while...else construct, but when
_attempt_self_correction returned None, we'd break out of the loop
and skip the else block, which meant current_step was never incremented
and the same step would be retried forever.

Fixed by using a flag to track self-correction success and handling
the failure case unconditionally after the loop ends.
…e edit

Previously, when a file was written successfully but verification (ruff)
detected a syntax error, the agent would:
1. Try ruff --fix (which can't fix syntax errors)
2. Just increment consecutive_failures and move on

This left broken code in the file and continued to the next step.

Now the agent:
1. Detects verification failure after successful file write
2. Triggers self-correction to fix the syntax/code error
3. Re-runs verification after each correction attempt
4. Creates a blocker if self-correction can't fix it

This ensures syntax errors caught by linting get the same self-correction
treatment as other technical errors.
…orrection

When a VERIFICATION step fails (e.g., ast.parse catches a syntax error),
we were trying to "self-correct" the verification step itself, which
doesn't make sense. Now we convert it to a FILE_EDIT step targeting
the same file, so self-correction actually fixes the broken code.

This fixes the case where:
1. File is written with syntax error
2. Ruff doesn't catch it (ruff misses some errors that ast catches)
3. Verification step catches the syntax error
4. Self-correction can now actually fix the file
- Add BATCH_EXECUTION_PLAN.md with phased approach:
  - Phase 1: Serial batch execution via conductor
  - Phase 2: Parallel execution with dependency analysis
  - Phase 3: Observability and websocket streaming

- Update CLI_WIREFRAME.md:
  - Add conductor.py and dependency_analyzer.py to module layout
  - Add cf work batch commands (batch, status, cancel)
  - Update implementation order with batch phases

Design decisions:
- Subprocess-based execution (isolation, crash-safe)
- No server required (CLI-first)
- Serial by default, parallel opt-in
@github-actions

Copy link
Copy Markdown
Contributor

Critical v1→v2 Migration Path Missing

This PR implements an excellent v2 architecture, but there's a blocking issue for existing users: no migration path from v1 to v2 data schemas.

The Problem

Users with existing v1 workspaces (containing tables like , , , ) will find their data completely inaccessible when running any v2 command. The function only adds missing columns to existing v2 tables - it does not migrate v1 schemas to v2 structure.

Evidence

codeframe/core/workspace.py:324-331

  • Line 324-325: If workspace exists → calls
  • Line 331: If workspace doesn't exist → creates fresh v2 schema via

The function handles v2→v2 upgrades (e.g., adding , columns) but has no v1→v2 migration logic.

Recommended Action

  1. Add migration detection in :

  2. Provide migration command (e.g., ):

    • Backup existing workspace
    • Migrate v1 data to v2 structure
    • Document breaking changes in docs/GOLDEN_PATH.md
  3. Or, at minimum: Add a warning for users with existing v1 workspaces before proceeding with v2 commands.

Summary

The v2 architecture is well-implemented and follows the Golden Path. However, without a documented migration strategy, existing users will experience data loss. This is the only issue that should block merge; all other concerns identified by CodeRabbit/Macroscope are valid but can be addressed post-merge.

Please clarify the intended handling for existing v1 workspaces in the PR description or add a migration path before merging.

@github-actions

Copy link
Copy Markdown
Contributor

Posted critical migration blocker comment to PR #299.

New%20session%20-%202026-01-22T01%3A23%3A34.042Z
opencode session  |  github run

@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: 6

🤖 Fix all issues with AI agents
In `@codeframe/core/artifacts.py`:
- Around line 68-128: The fallback from "git diff --cached" to "git diff" in
export_patch can produce patch_content that doesn't match the original
staged_only flag used later to compute stats; update export_patch to track which
diff was actually used (e.g., introduce a boolean like actual_staged_used
initialized from staged_only and set to False when you fall back to running
["git", "diff"]) and then use that actual_staged_used value (instead of the
original staged_only) when computing stats and any metadata returned in
PatchInfo so the reported stats match the patch_content generated by diff_cmd
and patch_content.

In `@codeframe/core/dependency_graph.py`:
- Around line 150-166: Remove the dead initialization and no-op loop that
precomputes in_degree then iterates with a for loop containing only pass;
specifically delete the initial in_degree: dict[str, int] = {node: 0 for node in
graph} and the subsequent for node in graph: for dep in graph[node]: ... pass
block, and rely solely on the final in_degree = {node: len(graph.get(node, []))
for node in graph} used by the Kahn topological sort (references: graph and
in_degree).

In `@codeframe/core/events.py`:
- Around line 217-270: The list_recent function opens a DB connection via
get_db_connection and creates a cursor but doesn't guarantee conn (and cursor)
are closed on exceptions; wrap the DB operations in a try/finally (or use a
context manager) inside list_recent so that cursor.close() and conn.close()
always run even if cursor.execute or fetchall raises, keeping the existing
SELECT logic and JSON/datetime parsing intact; reference the function name
list_recent and the get_db_connection call to locate where to add the
try/finally and ensure both cursor and conn are closed in the finally block.
- Around line 110-165: The emit function opens a DB connection via
get_db_connection(workspace) but may leak it if an exception occurs before
conn.close(); wrap the DB operations (cursor creation, execute, retrieving
lastrowid, and conn.commit()) in a try/finally so that conn.close() is always
called in the finally block, preserving the commit on success and re-raising the
exception after cleanup; update emit to use this try/finally around the
cursor/execute/commit sequence (referencing emit, get_db_connection, conn,
cursor, conn.close) to ensure the connection is closed even on errors.

In `@codeframe/core/gates.py`:
- Around line 54-85: The summary property on GateResult currently omits
ERROR-status checks so when all checks error it returns "no checks run"; update
GateResult.summary to count GateStatus.ERROR (e.g., error_count = sum(1 for c in
self.checks if c.status == GateStatus.ERROR)) and add an entry for errors in the
parts list (e.g., parts.append(f"{error_count} errors") when error_count > 0) so
the human-readable summary includes error counts alongside
passed/failed/skipped.
- Around line 88-167: The run function currently treats unknown gate names as
SKIPPED and uses `gates or ["auto"]` when emitting the GATES_STARTED event which
misreports an explicitly empty list; update run to (1) change the GATES_STARTED
payload to use the actual provided gates list when gates is not None (e.g. use
`gates if gates is not None else ["auto"]`) so an explicit empty list is
reported correctly, and (2) when iterating gates, if a gate_name is not one of
the known names (pytest, ruff, mypy, npm-test, npm-lint) create a GateCheck with
GateStatus.FAILED and a clear output message (instead of SKIPPED) so
mistyped/explicitly requested unknown gates cause the run to fail (update the
branch that currently constructs GateCheck(..., status=GateStatus.SKIPPED, ...)
to use GateStatus.FAILED when gates was explicitly provided).
🧹 Nitpick comments (8)
codeframe/core/events.py (2)

168-214: Same connection leak issue and code duplication.

This function has the same resource leak risk as emit(). Additionally, there's significant code duplication between emit() and emit_for_workspace().

Consider extracting shared logic into a private helper:

♻️ Proposed refactor to reduce duplication
def _insert_event(
    workspace: Workspace,
    workspace_id: str,
    event_type: str,
    payload: dict[str, Any],
    print_event: bool,
) -> Event:
    """Internal helper to insert and return an event."""
    now = _utc_now().isoformat()
    payload_json = json.dumps(payload)

    conn = get_db_connection(workspace)
    try:
        cursor = conn.cursor()
        cursor.execute(
            "INSERT INTO events (workspace_id, event_type, payload, created_at) VALUES (?, ?, ?, ?)",
            (workspace_id, event_type, payload_json, now),
        )
        event_id = cursor.lastrowid
        conn.commit()
    finally:
        conn.close()

    event = Event(
        id=event_id,
        workspace_id=workspace_id,
        event_type=event_type,
        payload=payload,
        created_at=datetime.fromisoformat(now),
    )

    if print_event:
        _print_event(event)

    return event

Then both public functions can delegate to this helper.


273-303: Infinite polling loop with no exit condition.

The tail() generator runs forever with while True. There's no mechanism for the caller to signal shutdown gracefully, and if an exception occurs in list_recent(), it will propagate without cleanup. Consider:

  1. Adding a timeout or stop event parameter
  2. Documenting that callers must handle GeneratorExit

Also, the import time at line 290 should be moved to the top-level imports for consistency.

♻️ Move import to top-level
 import json
+import time
 from dataclasses import dataclass

And remove line 290.

codeframe/core/artifacts.py (3)

1-21: Duplicate _utc_now helper.

This is duplicated from events.py and also appears in test files. Consider extracting to a shared utility module.

♻️ Suggestion

Create codeframe/core/utils.py:

from datetime import datetime, timezone

def utc_now() -> datetime:
    """Get current UTC time as timezone-aware datetime."""
    return datetime.now(timezone.utc)

Then import from there in both modules.


262-303: Missing git availability check.

Unlike export_patch and create_commit, get_status doesn't verify git is available before running commands. If git is missing, the error will be less informative.

♻️ Add consistency check
 def get_status(repo_path: Path) -> dict:
     """Get git status summary.
     ...
     """
+    if not shutil.which("git"):
+        raise ValueError("git not found in PATH")
+
     result = subprocess.run(

338-378: Move import re to top-level.

The import re at line 364 is inside the function. Per Python best practices, imports should be at module level for clarity and to avoid repeated import overhead (though negligible here).

♻️ Move import to top
 import subprocess
 import shutil
+import re
 from dataclasses import dataclass

Then remove line 364.

codeframe/core/dependency_graph.py (2)

99-99: Remove unused variable parent.

The parent dict is declared and assigned within the loop but never read. This is dead code.

🧹 Remove unused variable
     # States: 0 = unvisited, 1 = visiting, 2 = visited
     state: dict[str, int] = {node: 0 for node in graph}
-    parent: dict[str, Optional[str]] = {node: None for node in graph}

     def dfs(node: str, path: list[str]) -> Optional[list[str]]:
         state[node] = 1  # visiting
         path.append(node)

         for dep in graph.get(node, []):
             if dep not in state:
                 continue  # dependency not in our graph

             if state[dep] == 1:  # back edge - cycle found
                 # Find where the cycle starts
                 cycle_start = path.index(dep)
                 cycle = path[cycle_start:] + [dep]
                 return cycle

             if state[dep] == 0:  # unvisited
-                parent[dep] = node
                 result = dfs(dep, path)

172-182: Consider using deque for O(1) queue operations.

queue.pop(0) on a list is O(n). Using collections.deque with popleft() provides O(1) performance.

⚡ Use deque for better performance
+from collections import deque
+
 # ...
 
     # Start with nodes that have no dependencies
-    queue = [node for node in graph if in_degree[node] == 0]
+    queue = deque(node for node in graph if in_degree[node] == 0)
     result = []

     while queue:
         # Take a node with no remaining dependencies
-        node = queue.pop(0)
+        node = queue.popleft()
         result.append(node)
codeframe/core/gates.py (1)

170-203: Consider scanning subdirs for Python files when detecting gates.

repo_path.glob("*.py") only checks the repo root (Line 185), which can skip Python projects using src/ or nested packages.

♻️ Proposed tweak
-        any(repo_path.glob("*.py"))
+        any(repo_path.rglob("*.py"))

Comment thread codeframe/core/artifacts.py
Comment thread codeframe/core/dependency_graph.py
Comment thread codeframe/core/events.py
Comment thread codeframe/core/events.py
Comment thread codeframe/core/gates.py
Comment thread codeframe/core/gates.py
- Comment out frontend-tests, e2e-smoke-tests jobs (web-ui is legacy)
- Remove Node.js setup from code-quality job
- Add skip checks for web-ui/src in hardcoded-urls job
- Update test-summary to remove frontend-tests dependency

The web-ui package.json is missing; re-enable these jobs when
the frontend is restored.
@github-actions

Copy link
Copy Markdown
Contributor

Code Quality and Architecture Review Summary

This PR represents an impressive 33K+ line refactor introducing a well-structured v2 architecture with clear separation of concerns (headless core, CLI adapters, LLM abstraction). The extensive reviews from Macroscope, CodeRabbit, and github-actions have thoroughly documented both strengths and areas for improvement.

Critical Blocker: v1→v2 Migration Path

The most significant concern raised by github-actions is the missing v1→v2 data migration path. Users with existing v1 workspaces (containing tables like issues, sessions, memory, etc.) will lose access to their data when running any v2 command. The workspace._ensure_schema_upgrades() function only handles v2 schema changes, not v1 migration.

Recommendation: Before merge, implement:

  • A migration detection mechanism (identify v1 schema version vs v2 requirements)
  • A cf migrate command to safely convert v1 workspaces to v2 structure
  • Clear documentation in docs/GOLDEN_PATH.md about breaking changes

This should be the highest priority item to address.

Additional Observations

Addressed by previous reviews (commit 65f2ddb):

  • ✅ Import error: get_llm_providerget_provider
  • ✅ Model identifiers: Updated to valid Anthropic model names
  • ✅ ModelSelector: Constructor overrides now respected

Well-documented strengths:

  • Excellent separation of concerns with headless core layer
  • Comprehensive credential management with encryption and audit logging
  • Proper abstraction for LLM providers
  • Strong test coverage for new modules (400+ v2 tests)
  • Good event-driven architecture with append-only event log

One final high-level recommendation:
Consider adding end-to-end integration tests that cover the complete Golden Path workflow (init → prd add → tasks generate → work start --execute) to validate that all components work together as intended.

Overall, this is a substantial architectural improvement that sets a solid foundation for the v2 CLI-first vision. Once the migration path is documented and implemented, this should be ready for merge.

@github-actions

Copy link
Copy Markdown
Contributor

I've posted a comprehensive review comment to PR #299 that acknowledges the extensive existing analysis and focuses on the critical v1→v2 migration blocker as the main concern. The comment also notes that most CodeRabbit findings have been addressed (commit 65f2ddb) and provides recommendations for implementing a migration path with clear documentation before merge.

New%20session%20-%202026-01-22T01%3A43%3A04.511Z
opencode session  |  github run

Comment thread codeframe/cli/app.py
# Update tasks
updated_count = 0
skipped_count = 0
for task in matching:

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.

Bulk update with --all catches InvalidTransitionError in the generic handler, leaving users unaware which tasks succeeded before the failure. Consider wrapping individual update_status() calls in try/except, collecting failures, and reporting both successes and failures at the end.

🚀 Want me to fix this? Reply ex: "fix it for me".

artifacts.py:
- Track which diff was actually used when falling back from staged to
  unstaged, ensuring stats match the exported patch content

dependency_graph.py:
- Remove dead no-op loop in topological_sort that computed in_degree
  but only contained pass statements

events.py:
- Add try/finally to emit() to ensure DB connection closes on exception
- Add try/finally to emit_for_workspace() for same reason
- Add try/finally to list_recent() to ensure DB connection closes

gates.py:
- Add ERROR status count to GateResult.summary property
- Fix GATES_STARTED event to report actual empty list vs ["auto"]
- Make unknown gates FAILED (not SKIPPED) when explicitly requested,
  with helpful error message listing valid gate names
@github-actions

Copy link
Copy Markdown
Contributor

Critical v1→v2 Migration Blocker

This is an impressive architectural refactor with excellent separation of concerns (headless core, CLI adapters, LLM abstraction). However, there's one critical blocker that must be addressed before merge.

🚨 Critical Issue: No v1→v2 Migration Path

Users with existing v1 workspaces will lose access to their data when running any v2 command. The schema is incompatible:

v1 schema: , , , ,
v2 schema: , , , , (different structure)

The function in only handles v2→v2 upgrades (adding columns like ), not v1→v2 migration. When a v1 workspace exists, the CLI assumes it's compatible and proceeds, but v1 tables won't have the required v2 columns.

Recommendation

Add a migration check in :

Then implement CLI command in .

Other Critical Issues

Security (from CodeRabbit):

    • Shell command execution with accepts LLM-generated commands without proper validation (path traversal, injection)
    • File deletion without safeguards (can delete files outside workspace)
    • Dangerous command sanitization is bypassable with simple substring matching

Database Leaks (from CodeRabbit):

    • DB connections opened without try/finally
    • Same pattern
    • Same pattern in multiple functions
    • Direct sqlite3 calls bypass repository abstraction

Architecture (from CodeRabbit):

  • Conductor uses synchronous DB access instead of async repository pattern
  • Dependency inference doesn't validate against loaded tasks ()
  • Missing in initial schema migration
  • Workspace DB operations should use , , etc.

Positive Notes

✅ Excellent architectural separation (headless core, CLI adapters, LLM abstraction)
✅ Comprehensive credential management with encryption
✅ Strong test coverage (400+ v2 tests)
✅ Import errors and invalid models already fixed (commit 65f2ddb)
✅ Good event-driven architecture with append-only event log

Summary

The v2 architecture is well-designed and follows the documented Golden Path. The migration issue is the only blocking concern - without it, existing users will experience data loss. Please add a command before merging, or at minimum warn users with v1 workspaces before proceeding with v2 commands. The security and DB leak issues are important but could be addressed in follow-up PRs.

@github-actions

Copy link
Copy Markdown
Contributor

Critical v1→v2 Migration Blocker

This is an impressive architectural refactor with excellent separation of concerns (headless core, CLI adapters, LLM abstraction). However, there's one critical blocker that must be addressed before merge.

🚨 Critical Issue: No v1→v2 Migration Path

Users with existing v1 workspaces will lose access to their data when running any v2 command. The schema is incompatible:

v1 schema: , , , ,
v2 schema: , , , , (different structure)

The function in only handles v2→v2 upgrades (adding columns like ), not v1→v2 migration. When a v1 workspace exists, CLI assumes it's compatible and proceeds, but v1 tables won't have required v2 columns.

Recommendation

Add migration detection in :

Then implement CLI command in .

Other Critical Issues (from CodeRabbit/github-actions)

Security:

  • Command injection in (shell=True with LLM commands) - CRITICAL
  • Path traversal in file operations - HIGH PRIORITY
  • Unsafe command sanitization in

Database Leaks:

    • DB connections opened without try/finally
    • Same pattern
    • Same pattern in multiple functions
    • Direct sqlite3 calls bypass repository abstraction

Architecture:

  • Conductor uses synchronous DB access instead of async repository pattern
  • Dependency inference doesn't validate against loaded tasks
  • Missing in initial schema migration

Code Quality:

  • Dead code and unused variables
  • Inconsistent error handling
  • Missing timeouts on subprocess calls

Positive Notes

✅ Excellent architectural separation (headless core, CLI adapters, LLM abstraction)
✅ Comprehensive credential management with encryption
✅ Strong test coverage (400+ v2 tests)
✅ Import errors and invalid models already fixed (commit 65f2ddb)
✅ Good event-driven architecture with append-only event log

Summary

The v2 architecture is well-designed and follows the documented Golden Path. The migration issue is the only blocking concern - without it, existing users will experience data loss. Please add a command before merging, or at minimum warn users with v1 workspaces before proceeding with v2 commands. The security and DB leak issues are important but could be addressed in follow-up PRs.

@github-actions

Copy link
Copy Markdown
Contributor

I've posted a comprehensive review comment to PR #299 highlighting the critical v1→v2 migration blocker while acknowledging the excellent work done on this architectural refactor. The comment provides a concrete recommendation for adding migration detection and a cf migrate command to prevent data loss for existing users.

New%20session%20-%202026-01-22T01%3A52%3A39.818Z
opencode session  |  github run

@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: 2

🤖 Fix all issues with AI agents
In `@codeframe/core/artifacts.py`:
- Around line 120-130: The fallback path that sets actual_staged_used=False can
produce a patch but leaves stats computed by _get_diff_stats(repo_path,
staged_only=False) which still runs "git diff HEAD --stat" and may return zeros;
instead, when you detect you fell back to unstaged (actual_staged_used is False)
parse the stats directly from the actual patch content via
_parse_patch_stats(patch_content) (or modify _get_diff_stats to accept the raw
patch and use _parse_patch_stats when provided) so functions like the block that
builds patch_content, actual_staged_used, and the subsequent _get_diff_stats
call produce accurate stats.

In `@codeframe/core/dependency_graph.py`:
- Around line 212-217: The code in calculate_level uses max(calculate_level(dep)
for dep in deps if dep in graph) which can raise ValueError when that generator
is empty; change calculate_level to handle the case where none of deps are
present in graph (e.g., compute dep_levels = [calculate_level(dep) for dep in
deps if dep in graph], then set max_dep_level = max(dep_levels, default=0) or
treat missing deps as level 0) and then assign levels[node] = max_dep_level + 1
so nodes with no valid in-graph deps don't error; update references to
calculate_level, deps, levels, graph, and node accordingly.
♻️ Duplicate comments (1)
codeframe/core/gates.py (1)

83-84: Pluralization inconsistency for error count.

The summary uses f"{error_count} errors" unconditionally, which produces "1 errors" for a single error. Other counts (passed, failed, skipped) don't have this issue because they use bare nouns.

🐛 Proposed fix
         if error_count:
-            parts.append(f"{error_count} errors")
+            parts.append(f"{error_count} error{'s' if error_count != 1 else ''}")
🧹 Nitpick comments (6)
codeframe/core/gates.py (3)

21-23: Consider consolidating _utc_now() helper.

This function is duplicated across multiple core modules (gates.py, events.py, artifacts.py). Consider extracting to a shared utility module to maintain DRY.


209-218: Bare exception handler may hide parsing issues.

The except Exception: pass block silently swallows all errors when parsing package.json, including issues like permission errors or malformed JSON. Consider logging at debug level for troubleshooting.


348-353: Consider adding uv run fallback for mypy.

The _run_mypy function only checks for shutil.which("mypy") directly, unlike _run_pytest and _run_ruff which also support running via uv run. For consistency, consider adding the same fallback pattern.

♻️ Suggested change
-    if not shutil.which("mypy"):
+    if not shutil.which("mypy") and not shutil.which("uv"):
         return GateCheck(
             name="mypy",
             status=GateStatus.SKIPPED,
             output="mypy not found",
         )

     try:
-        result = subprocess.run(
-            ["mypy", "."],
+        if shutil.which("uv"):
+            cmd = ["uv", "run", "mypy", "."]
+        else:
+            cmd = ["mypy", "."]
+
+        result = subprocess.run(
+            cmd,
codeframe/core/dependency_graph.py (2)

99-99: Unused parent dictionary.

The parent dict is initialized and populated but never read. This appears to be leftover from an alternative cycle reconstruction approach.

🧹 Remove unused code
     # States: 0 = unvisited, 1 = visiting, 2 = visited
     state: dict[str, int] = {node: 0 for node in graph}
-    parent: dict[str, Optional[str]] = {node: None for node in graph}

     def dfs(node: str, path: list[str]) -> Optional[list[str]]:
         state[node] = 1  # visiting
@@ -113,7 +112,6 @@
                 return cycle

             if state[dep] == 0:  # unvisited
-                parent[dep] = node
                 result = dfs(dep, path)
                 if result:
                     return result

Also applies to: 116-116


160-170: Performance: O(n²) topological sort implementation.

The current implementation uses queue.pop(0) which is O(n), and iterates all nodes to find dependents (lines 166-170), making the overall complexity O(n²). For small task sets this is acceptable, but consider optimizing if large batches are expected.

♻️ More efficient implementation using deque and reverse adjacency
+from collections import deque
+
 def topological_sort(graph: dict[str, list[str]]) -> list[str]:
     ...
     in_degree = {node: len(graph.get(node, [])) for node in graph}

+    # Build reverse adjacency: who depends on each node
+    dependents: dict[str, list[str]] = {node: [] for node in graph}
+    for node, deps in graph.items():
+        for dep in deps:
+            if dep in dependents:
+                dependents[dep].append(node)
+
     # Start with nodes that have no dependencies
-    queue = [node for node in graph if in_degree[node] == 0]
+    queue = deque(node for node in graph if in_degree[node] == 0)
     result = []

     while queue:
         # Take a node with no remaining dependencies
-        node = queue.pop(0)
+        node = queue.popleft()
         result.append(node)

         # For each node that depends on this node, reduce its in_degree
-        for other_node in graph:
-            if node in graph.get(other_node, []):
-                in_degree[other_node] -= 1
-                if in_degree[other_node] == 0:
-                    queue.append(other_node)
+        for dependent in dependents[node]:
+            in_degree[dependent] -= 1
+            if in_degree[dependent] == 0:
+                queue.append(dependent)
codeframe/core/events.py (1)

138-140: Implicit fallback to cwd may cause unexpected behavior.

When repo_path is None, the function silently defaults to Path.cwd(). If the current directory isn't a workspace, get_workspace() will raise FileNotFoundError. Consider documenting this behavior or requiring repo_path explicitly.

Comment thread codeframe/core/artifacts.py
Comment thread codeframe/core/dependency_graph.py
- Register auth_app from auth_commands.py in main CLI app
- Fix test_credential_commands.py tests to mock get_credential_source
- Skip test_serve_command.py tests (serve is stub during v2 refactor)
- Skip test_cli_session.py tests (session management not in v2 Golden Path)
Comment thread codeframe/cli/app.py

console.print(f"\n[bold]Diff: v{version1} → v{version2}[/bold]\n")
# Color the diff output
for line in diff.splitlines():

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.

Rich is interpreting bracketed user/content text as markup, causing MarkupError or corrupted output. Suggest treating it as plain text: use rich.markup.escape() or pass markup=False when printing arbitrary lines/output.

🚀 Want me to fix this? Reply ex: "fix it for me".

These tests require a running FastAPI server with full WebSocket support,
but the v2 serve command is a stub. The server adapter will be implemented
post-Golden Path.
@github-actions

Copy link
Copy Markdown
Contributor

Critical Blocker: v1→v2 Migration Path

The most significant concern is the missing migration path for existing v1 workspaces. Users with existing v1 data will find their data inaccessible when running v2 commands because v1 and v2 schemas are incompatible.

Critical Issues Requiring Action:

  1. Migration path - Add command or at minimum warn users with existing v1 workspaces before proceeding with v2 commands
  2. Security - Command injection in (shell=True with LLM commands) and path traversal in file operations
  3. Database leaks - Multiple functions don't close SQLite connections properly (, , )

What's Already Fixed:
✅ Import errors and invalid model identifiers (commit 65f2ddb)

Positive Notes:
✅ Excellent headless core with clear separation of concerns
✅ Comprehensive credential management with encryption
✅ Strong test coverage (400+ v2 tests)

Recommendation: Consider running Golden Path end-to-end test before merging to validate complete workflow, and address migration path as blocking issue.

@github-actions

Copy link
Copy Markdown
Contributor

I reviewed PR #299 and posted a comprehensive review highlighting:

Critical Blocker: No v1→v2 migration path for existing workspaces (potential data loss)

Key Issues:

  • Security: Command injection (agent.py), path traversal risks
  • Database: Connection leaks in runtime.py, tasks.py, events.py
  • Architecture: Direct sqlite3 access bypassing repository pattern

Positives:

  • ✅ Excellent headless core architecture
  • ✅ Comprehensive credential management with encryption
  • ✅ Strong test coverage (400+ v2 tests)
  • ✅ Import/model errors already fixed in commit 65f2ddb

Recommendation: Add cf migrate command and migration detection before merging, or at minimum warn users with existing v1 workspaces.

New%20session%20-%202026-01-22T02%3A35%3A26.858Z
opencode session  |  github run

artifacts.py:
- When falling back to plain unstaged diff (git diff without HEAD),
  parse stats directly from patch content via _parse_patch_content_stats()
- _get_diff_stats with staged_only=False runs "git diff HEAD --stat"
  which may return zeros for pure working tree changes

dependency_graph.py:
- Fix ValueError when max() is called on empty generator in calculate_level()
- Use max(dep_levels, default=-1) to handle nodes with deps not in graph
- Nodes with no valid in-graph deps are treated as level 0 (root nodes)
@github-actions

Copy link
Copy Markdown
Contributor

Outstanding Critical Issue: v1→v2 Migration Path

This is an impressive architectural refactor introducing a headless, CLI-first v2 system. The existing reviews from CodeRabbit, Macroscope, and github-actions have thoroughly documented both the strengths and concerns.

✅ Strengths Already Addressed

Most CodeRabbit findings were already fixed in commit 65f2ddb:

  • Import error: get_llm_providerget_provider
  • Invalid model identifiers → valid Anthropic model names
  • ModelSelector constructor overrides → now respected

🚨 Critical Blocker: Missing v1→v2 Migration Path

The most significant concern is the absence of a migration path for existing v1 workspaces:

Problem: Users with existing v1 workspaces (tables: issues, sessions, memory, work, etc.) will find their data inaccessible when running any v2 command. The _ensure_schema_upgrades() function only handles v2→v2 upgrades (adding columns like tech_stack), not v1→v2 migration.

Evidence (codeframe/core/workspace.py:324-331):

  • Line 324-325: If workspace exists → calls _ensure_schema_upgrades()
  • Line 331: If workspace doesn't exist → calls _init_database() (fresh v2 schema)

Recommendation: Before merging, implement one of:

  1. Add migration detection in get_workspace():

    • Check if v1 tables exist (issues, sessions, memory, work)
    • Warn users and provide clear next steps
  2. Provide cf migrate command (codeframe/cli/app.py):

    • Backup existing workspace
    • Migrate v1 data to v2 structure
    • Document in docs/GOLDEN_PATH.md
  3. At minimum: Display a warning for users with existing v1 workspaces before proceeding with v2 commands

The security concerns (command injection, path traversal) and database leaks are valid but secondary to the migration blocker. The architectural refactor is excellent and follows the documented Golden Path approach.

@github-actions

Copy link
Copy Markdown
Contributor

I've posted a review focusing on the critical v1→v2 migration blocker that could cause data loss for existing users.

New%20session%20-%202026-01-22T02%3A53%3A32.417Z
opencode session  |  github run

@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: 1

🤖 Fix all issues with AI agents
In `@codeframe/core/artifacts.py`:
- Around line 102-135: The current diff logic fails for repos without a HEAD
(new repos) because "git diff HEAD" can error or omit staged changes; update the
logic in the artifact generation code that uses staged_only, diff_cmd,
actual_staged_used, fell_back_to_plain_unstaged and patch_content to explicitly
detect a missing HEAD (e.g., run "git rev-parse --verify HEAD" or inspect the
subprocess result for the fatal/exit code) and, when HEAD is missing or the diff
call returns empty/error, produce patch_content by concatenating the staged (git
diff --cached) and unstaged (git diff) outputs so stats are computed from the
combined content; ensure actual_staged_used and fell_back_to_plain_unstaged are
set appropriately when using the concatenated diffs.
🧹 Nitpick comments (3)
codeframe/core/dependency_graph.py (3)

99-99: Remove unused parent dict.

The parent dictionary is initialized and assigned to on line 116, but never read. This is dead code that can be safely removed.

🧹 Proposed cleanup
     # States: 0 = unvisited, 1 = visiting, 2 = visited
     state: dict[str, int] = {node: 0 for node in graph}
-    parent: dict[str, Optional[str]] = {node: None for node in graph}

     def dfs(node: str, path: list[str]) -> Optional[list[str]]:
         state[node] = 1  # visiting
         path.append(node)

         for dep in graph.get(node, []):
             if dep not in state:
                 continue  # dependency not in our graph

             if state[dep] == 1:  # back edge - cycle found
                 # Find where the cycle starts
                 cycle_start = path.index(dep)
                 cycle = path[cycle_start:] + [dep]
                 return cycle

             if state[dep] == 0:  # unvisited
-                parent[dep] = node
                 result = dfs(dep, path)

160-170: Consider using deque and a reverse adjacency list for better performance.

queue.pop(0) is O(n). Additionally, the inner loop (lines 166-170) scans all nodes to find dependents, making the algorithm O(V²) instead of optimal O(V+E). For small task graphs this is fine, but if graphs grow larger, precomputing a reverse adjacency map and using collections.deque would improve performance.

♻️ Optional optimization
+from collections import deque
+
 def topological_sort(graph: dict[str, list[str]]) -> list[str]:
     ...
     in_degree = {node: len(graph.get(node, [])) for node in graph}
+
+    # Build reverse adjacency: node -> list of nodes that depend on it
+    dependents: dict[str, list[str]] = {node: [] for node in graph}
+    for node, deps in graph.items():
+        for dep in deps:
+            if dep in dependents:
+                dependents[dep].append(node)

     # Start with nodes that have no dependencies
-    queue = [node for node in graph if in_degree[node] == 0]
+    queue = deque(node for node in graph if in_degree[node] == 0)
     result = []

     while queue:
-        node = queue.pop(0)
+        node = queue.popleft()
         result.append(node)

-        for other_node in graph:
-            if node in graph.get(other_node, []):
-                in_degree[other_node] -= 1
-                if in_degree[other_node] == 0:
-                    queue.append(other_node)
+        for dependent in dependents[node]:
+            in_degree[dependent] -= 1
+            if in_degree[dependent] == 0:
+                queue.append(dependent)

289-290: Consider narrowing the exception catch.

Catching bare Exception may hide unexpected bugs. Since build_graph and detect_cycle are the only operations, consider catching more specific exceptions or at least excluding base exceptions like KeyboardInterrupt.

♻️ Optional refinement
-    except Exception as e:
+    except (CycleDetectedError, KeyError, ValueError) as e:
         return False, str(e)

Alternatively, if you want to remain defensive but avoid swallowing critical errors:

-    except Exception as e:
+    except Exception as e:  # noqa: BLE001 - intentionally broad for validation
         return False, str(e)

Comment on lines +102 to +135
# Generate patch content
# Track which diff was actually used for stats calculation
actual_staged_used = staged_only

if staged_only:
diff_cmd = ["git", "diff", "--cached"]
else:
diff_cmd = ["git", "diff", "HEAD"]

result = subprocess.run(
diff_cmd,
cwd=repo_path,
capture_output=True,
text=True,
)

patch_content = result.stdout

# Track if we fell back to plain unstaged diff (git diff without HEAD)
fell_back_to_plain_unstaged = False

if not patch_content.strip():
# Try just unstaged changes (git diff without HEAD)
result = subprocess.run(
["git", "diff"],
cwd=repo_path,
capture_output=True,
text=True,
)
patch_content = result.stdout
# We fell back to unstaged diff, so update the tracking flag
actual_staged_used = False
fell_back_to_plain_unstaged = True

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

Handle repos without HEAD to avoid missing staged changes.

In a repo with no commits, git diff HEAD fails and the fallback to git diff can omit staged changes entirely (or raise "No changes"). Consider detecting missing HEAD and concatenating staged + unstaged diffs, then computing stats from the combined content.

🐛 Proposed fix
-    if staged_only:
-        diff_cmd = ["git", "diff", "--cached"]
-    else:
-        diff_cmd = ["git", "diff", "HEAD"]
+    head_exists = (
+        subprocess.run(
+            ["git", "rev-parse", "--verify", "HEAD"],
+            cwd=repo_path,
+            capture_output=True,
+            text=True,
+        ).returncode
+        == 0
+    )
+
+    if staged_only:
+        diff_cmds = [["git", "diff", "--cached"]]
+    elif head_exists:
+        diff_cmds = [["git", "diff", "HEAD"]]
+    else:
+        # No HEAD yet: include staged + unstaged
+        diff_cmds = [["git", "diff", "--cached"], ["git", "diff"]]
 
-    result = subprocess.run(
-        diff_cmd,
-        cwd=repo_path,
-        capture_output=True,
-        text=True,
-    )
-
-    patch_content = result.stdout
+    patch_chunks = []
+    for diff_cmd in diff_cmds:
+        result = subprocess.run(
+            diff_cmd,
+            cwd=repo_path,
+            capture_output=True,
+            text=True,
+        )
+        if result.returncode != 0:
+            raise ValueError(f"git diff failed: {result.stderr}")
+        patch_chunks.append(result.stdout)
+    patch_content = "".join(patch_chunks)
@@
-    if fell_back_to_plain_unstaged:
+    if fell_back_to_plain_unstaged or (not staged_only and not head_exists):
         stats = _parse_patch_content_stats(patch_content)
     else:
         stats = _get_diff_stats(repo_path, actual_staged_used)

Also applies to: 150-155

🤖 Prompt for AI Agents
In `@codeframe/core/artifacts.py` around lines 102 - 135, The current diff logic
fails for repos without a HEAD (new repos) because "git diff HEAD" can error or
omit staged changes; update the logic in the artifact generation code that uses
staged_only, diff_cmd, actual_staged_used, fell_back_to_plain_unstaged and
patch_content to explicitly detect a missing HEAD (e.g., run "git rev-parse
--verify HEAD" or inspect the subprocess result for the fatal/exit code) and,
when HEAD is missing or the diff call returns empty/error, produce patch_content
by concatenating the staged (git diff --cached) and unstaged (git diff) outputs
so stats are computed from the combined content; ensure actual_staged_used and
fell_back_to_plain_unstaged are set appropriately when using the concatenated
diffs.

@github-actions

Copy link
Copy Markdown
Contributor

Critical v1→v2 Migration Blocker

This is an impressive architectural refactor introducing a well-structured v2 system. The 33K+ lines demonstrate excellent separation of concerns with a headless core, CLI adapters, and comprehensive orchestration.

🚨 Critical Issue: No v1→v2 Migration Path

This is the only blocker that should prevent merge:

Users with existing v1 workspaces will lose access to their data when running any v2 command. The v1 schema (tables: , , , , etc.) is incompatible with v2 (tables: , , , , etc.).

Evidence:

    • only adds v2→v2 columns (e.g., ), not v1→v2 migration
  • When v1 workspace exists, CLI calls and proceeds, but v1 tables lack required v2 columns

Recommended Action:

Add a migration detection and command before merging. This should:

  1. Detect v1 schema presence (tables like , )
  2. Either migrate data or warn users with clear instructions
  3. Document the migration strategy in

Positive Notes

✅ Excellent headless core architecture
✅ Comprehensive credential management with encryption
✅ Strong test coverage (400+ v2 tests)
✅ Good event-driven design with append-only event log
✅ Most CodeRabbit findings already addressed in commit 65f2ddb

The architectural refactor follows the documented Golden Path approach and sets a solid foundation for v2. Once the migration path is resolved, this should be ready to merge.

@github-actions

Copy link
Copy Markdown
Contributor

Critical v1→v2 Migration Blocker

This is an impressive architectural refactor introducing a well-structured v2 system. The 33K+ lines demonstrate excellent separation of concerns with a headless core, CLI adapters, and comprehensive orchestration.

🚨 Critical Issue: No v1→v2 Migration Path

This is the only blocker that should prevent merge:

Users with existing v1 workspaces will lose access to their data when running any v2 command. The v1 schema (tables: issues, sessions, memory, work, etc.) is incompatible with v2 (tables: tasks, runs, prds, events, etc.).

Evidence:

  • workspace.py:324-331 - _ensure_schema_upgrades() only adds v2→v2 columns (e.g., tech_stack), not v1→v2 migration
  • When v1 workspace exists, CLI calls _ensure_schema_upgrades() and proceeds, but v1 tables lack required v2 columns

Recommended Action:

Add migration detection and a cf migrate command before merging. This should:

  1. Detect v1 schema presence (tables like issues, sessions)
  2. Either migrate data or warn users with clear instructions
  3. Document migration strategy in docs/GOLDEN_PATH.md

Positive Notes

✅ Excellent headless core architecture
✅ Comprehensive credential management with encryption
✅ Strong test coverage (400+ v2 tests)
✅ Good event-driven design with append-only event log
✅ Most CodeRabbit findings already addressed in commit 65f2ddb

The architectural refactor follows the documented Golden Path approach and sets a solid foundation for v2. Once a migration path is implemented, this should be ready to merge.

@github-actions

Copy link
Copy Markdown
Contributor

Critical v1→v2 Migration Blocker

This is an impressive architectural refactor introducing a well-structured v2 system. The 33K+ lines demonstrate excellent separation of concerns with a headless core, CLI adapters, and comprehensive orchestration.

🚨 Critical Issue: No v1→v2 Migration Path

This is the only blocker that should prevent merge:

Users with existing v1 workspaces will lose access to their data when running any v2 command. The v1 schema (tables: issues, sessions, memory, work, etc.) is incompatible with v2 (tables: tasks, runs, prds, events, etc.).

Evidence:

  • workspace.py:324-331 - _ensure_schema_upgrades() only adds v2→v2 columns (e.g., tech_stack), not v1→v2 migration
  • When v1 workspace exists, CLI calls _ensure_schema_upgrades() and proceeds, but v1 tables lack required v2 columns

Recommended Action:

Add a migration detection and cf migrate command before merging. This should:

  1. Detect v1 schema presence (tables like issues, sessions)
  2. Either migrate data or warn users with clear instructions
  3. Document migration strategy in docs/GOLDEN_PATH.md

Positive Notes

✅ Excellent headless core architecture
✅ Comprehensive credential management with encryption
✅ Strong test coverage (400+ v2 tests)
✅ Good event-driven design with append-only event log
✅ Most CodeRabbit findings already addressed in commit 65f2ddb

The architectural refactor follows the documented Golden Path approach and sets a solid foundation for v2. Once a migration path is implemented, this should be ready to merge.

@github-actions

Copy link
Copy Markdown
Contributor

I've posted a comprehensive review comment on PR #299 highlighting the critical v1→v2 migration blocker as the main concern, while acknowledging the excellent architectural work done.

New%20session%20-%202026-01-22T03%3A00%3A22.522Z
opencode session  |  github run

@frankbria
frankbria merged commit 53032c8 into main Jan 22, 2026
14 checks passed
@frankbria
frankbria deleted the v2-refactor branch January 22, 2026 03:04
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.

[V2] CLI help strings should reference codeframe commands and not python -m commands

1 participant