Skip to content

Epic/ralph loops#2

Closed
mabry1985 wants to merge 24 commits intoshaal:mainfrom
proto-labs-ai:epic/ralph-loops
Closed

Epic/ralph loops#2
mabry1985 wants to merge 24 commits intoshaal:mainfrom
proto-labs-ai:epic/ralph-loops

Conversation

@mabry1985
Copy link

@mabry1985 mabry1985 commented Feb 5, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Claude Code plugin integration with MCP server for enhanced agent capabilities
    • Project orchestration system: create, manage, and convert projects to board features with milestones and phases
    • Ralph mode: persistent retry loops with built-in verification and completion criteria
    • Epic features: organize related features hierarchically with progress tracking
    • Production deployment setup with Docker, Nginx proxy, and GitHub authentication support
  • Improvements

    • Auto-mode worktree management with automatic creation per branch
    • Enhanced board view with epic badges and progress indicators

mabry1985 and others added 24 commits February 4, 2026 02:06
- Add missing zod dependency to apps/ui
- Add missing fast-xml-parser dependency to libs/spec-parser
- Add libs/spec-parser/package.json to Dockerfile COPY commands
- Change VITE_SERVER_URL default to empty string for nginx proxy
- Add nginx proxy config for /api routes to server container
- Add GH_TOKEN environment variable to docker-compose.yml
- Add HANDOFF.md with production deployment documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add recommended specs table (minimum/baseline/heavy use)
- Add Proxmox-specific settings (storage, memory, CPU)
- Add swap configuration for agent spikes
- Add Docker log rotation config
- Add maintenance commands

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add batch-create-features.sh for programmatic feature creation
- Add example-features.json showing the expected format
- Supports AUTOMAKER_API_KEY and AUTOMAKER_API_URL env vars

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
feat(scripts): add batch feature creation script and example
Adds a standalone MCP server that exposes Automaker's functionality
to Claude Code, Cline, and other MCP-compatible tools.

Tools included:
- Feature management (list, create, update, delete, move)
- Agent control (start, stop, list, send messages)
- Queue management (add, list, clear)
- Context files (list, get, create, delete)
- Project spec (get, update)
- Utilities (health check, board summary)

Usage: Configure in ~/.claude/claude_desktop_config.json with
AUTOMAKER_API_URL and AUTOMAKER_API_KEY environment variables.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Document how to set AUTOMAKER_API_KEY for consistent
authentication during development (useful for MCP and scripts).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
fix(docker): production deployment fixes, MCP server, and documentation
Project Orchestration:
- Add Project, Milestone, Phase types with SPARC PRD support
- Add project path utilities in @automaker/platform
- Add project parser and markdown generators in @automaker/utils
- Add project API routes and ProjectService
- Add isEpic, epicId, epicColor fields to Feature type

Feature Improvements:
- Auto-generate branchName on feature creation (feature/{slug})
- Fix branchName fallback to preserve feature/ namespace

UI Worktree Fix:
- Add worktreesLoadingByProject state to app store
- Fix feature filtering to show ALL features while worktrees load
- Apply proper filtering only after loading completes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(mcp): add orchestration tools for workflow management

New tools:
- set_feature_dependencies: Set dependencies between features
- get_dependency_graph: Visualize all feature dependencies
- start_auto_mode: Start autonomous processing with concurrency control
- stop_auto_mode: Stop auto-mode
- get_auto_mode_status: Check auto-mode status
- get_execution_order: Get topologically sorted execution order

Total tools: 28

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add project orchestration system for planning and execution pipeline

Add a complete project planning and execution pipeline:
Deep Research → SPARC PRD → Review → Approval → Project Structure → Feature Creation

Types:
- Add Project, Milestone, Phase interfaces for hierarchical planning
- Add SPARCPrd for structured requirements documents
- Add isEpic, epicId, epicColor fields to Feature type

Platform utilities:
- Add project directory path helpers and slug generators
- Add listProjectPlans, listMilestones, listPhases for discovery
- Add ensureProjectStructure, ensureMilestoneDir for creation

Parsers:
- Add parseProjectFile/generateProjectFile for project.md
- Add parseMilestoneFile/generateMilestoneFile with enhanced format
- Add parsePhaseFile/generatePhaseFile with detailed structure
- Add parsePrdFile/generatePrdFile for SPARC PRDs

Server:
- Add auto branchName generation from feature title
- Add /api/projects routes for CRUD and create-features

MCP:
- Add branchName, dependencies, isEpic, epicId params to create_feature

Plugin commands:
- /deep-research - Research codebase before planning
- /sparc-prd - Create SPARC-style PRD
- /create-project - Full orchestration pipeline
- /scaffold-project - Create directory structure
- /create-project-features - Convert project to board features

Plugin agents:
- deep-research - Codebase exploration
- sparc-prd - PRD creation
- prd-reviewer - PRD validation
- project-scaffold - Directory structure creation
- feature-factory - Phase to feature conversion

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(mcp): add project orchestration tools

Add 6 new MCP tools for project lifecycle management:
- list_projects: List all project plans
- get_project: Get project details with milestones and PRD
- create_project: Create new project with SPARC PRD structure
- update_project: Update project title, goal, or status
- delete_project: Remove a project plan
- create_project_features: Convert project phases to board features

These tools wrap the /api/projects/* endpoints added in the previous
commit, enabling full project orchestration via MCP protocol.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: add project orchestration documentation

- CLAUDE.md: Add MCP server, plugin, and project orchestration sections
  - Quick setup instructions
  - Full list of 32 MCP tools by category
  - Plugin commands reference
  - Project types and API routes documentation
  - Epic support explanation
  - Example MCP usage for creating projects

- packages/mcp-server/README.md: Add project orchestration tools table
  - list_projects, get_project, create_project
  - update_project, delete_project, create_project_features

- docs/claude-plugin.md: Add comprehensive project orchestration guide
  - Full orchestration flow diagram
  - Detailed example of creating a project via MCP
  - Project file structure explanation
  - Epic features documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(server): load dotenv before auth module to fix API key

Move dotenv.config() to the top of index.ts so AUTOMAKER_API_KEY is
available when auth.ts module loads. Previously, the auth module was
imported before dotenv loaded, causing the server to generate a random
API key instead of using the configured one.

Also updates documentation with MCP plugin improvements.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: add MCP test and plugin config

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: address PR feedback batch 1 - security and bug fixes

Security:
- Add validateSlugs middleware to prevent path traversal in projectSlug
- Add slug validation in libs/platform (validateSlugInput, InvalidSlugError)
- Validate slugs in getProjectDir, getMilestoneDir, getPhaseFilePath

Bug fixes:
- Fix project parser to strip "M1:" and "Phase 1:" prefixes from titles
- Fix update.ts to use 'in' operator for detecting explicit field clearing
- Sort listProjectPlans deterministically with localeCompare

Config/Docs:
- Remove hardcoded API key from plugin.json (use env var reference)
- Fix README.md hardcoded path with placeholder

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: extract create-features to ProjectOrchestrationService

- Create project-orchestration-service.ts with orchestrateProjectFeatures()
- Add normalized dependency lookup with fallback patterns
- Always apply previous-phase fallback for sequential execution
- Add WebSocket event emission for progress tracking
- Add project:features:* event types to EventType
- Keep route thin - validation only, delegates to service

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: add debug logging and restore plugin API key

- Add debug-log.ts utility for tracking issues to data/debug.log
- Add debug logging to FeatureLoader.getAll() and /features/list endpoint
- Restore hardcoded API key in plugin.json (env var interpolation doesn't work in JSON)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ui): show all features when worktrees haven't loaded

When worktrees haven't loaded yet (worktreesByProject is empty),
show ALL features instead of only those with main/master/develop
branches. This prevents features with custom branch names like
epic/* or feature/* from being hidden during initial page load.

Once worktrees load, proper branch-based filtering will apply.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The PR merge introduced duplicate export statements for project
orchestration types and paths. Removed the duplicate exports:

- libs/types/src/index.ts: Removed second export block for project
  types (already exported at lines 64-77)
- libs/platform/src/index.ts: Removed duplicate project path exports
  from paths.js block (keeping them only from projects.js)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Epic UI Components:
- Add EpicBadge component to show parent epic for child features
- Add EpicProgress component showing completion progress for epics
- Integrate both components into KanbanCard

MCP Server Fix:
- Fix list_running_agents to use GET /running-agents instead of
  POST /running-agents/list which doesn't exist
- Add method parameter to apiCall helper to support GET requests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Claude Code doesn't pass env variables from plugin.json to spawned
MCP server processes. This caused 401 authentication errors because
the API key was empty.

Added a hardcoded fallback API key that matches the server's default.
This allows the MCP tools to authenticate properly when invoked from
Claude Code plugins.

Note: This is a workaround. A proper fix would require Claude Code
to pass env vars from plugin.json to MCP processes, or the server
to use a predictable default key.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use the key from .env file as the hardcoded fallback. The server
should be started with the same key:

  AUTOMAKER_API_KEY=automaker-dev-key-2026 npm run dev:server

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add vitest and @vitest/coverage-v8 as dev dependencies
- Create vitest.config.ts with coverage thresholds (60% lines/functions)
- Add test scripts: test, test:watch, test:coverage
- Update tsconfig.json with vitest/globals types
- Create initial test file with basic configuration tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add useWorktrees parameter to start_agent tool schema
- Default useWorktrees to true so agents work in isolated git worktrees
- Update send_message_to_agent to also use worktrees
- Ensures agents work on feature branches in separate worktrees
  instead of directly on main branch

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Quick Start section to docs/claude-plugin.md with GitHub URL install
- Update README Claude Code Integration with streamlined setup
- Fix plugin.json to use relative path instead of hardcoded absolute
- Mark worktrees fix as resolved in Known Issues
- Update MCP tool count to 32

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When an agent starts on a feature with useWorktrees enabled, the system
now automatically creates an isolated git worktree if one doesn't already
exist. This eliminates the manual step of creating worktrees before
running agents.

Changes:
- Add createWorktreeForBranch() method to auto-mode service
- Update executeFeature, resumeFromPipelineStep, and followUpFeature
  to auto-create worktrees when needed
- Update documentation to reflect new auto-create behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add getAllWorktreeBranches() helper to get branches with existing worktrees
- Update loadPendingFeatures() to include orphaned features (branchName exists
  but no worktree) when running on main worktree
- Add retry logic with exponential backoff for MCP server API calls
- Fix plugin.json repository field (object -> string) for Claude Code
- Add "node" to tsconfig types for MCP server
- Add dev server management guideline to CLAUDE.md

Previously, features with custom branchNames were filtered out on main worktree
even when no worktree existed for that branch. Now these "orphaned" features
are included and can have worktrees auto-created when agents start.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Research-driven design document for autonomous development enhancements:
- Self-extending skills system (inspired by OpenClaw SKILL.md pattern)
- Self-healing recovery mechanisms with failure classification
- Persistent retry loops (Ralph Wiggum pattern) with verification
- Proactive automation with health monitoring and scheduling
- New MCP tools for skill management, recovery, and Ralph mode

Includes implementation roadmap, configuration examples, and success metrics.

https://claude.ai/code/session_01TvaZif5HXfKHpRXVJxZUVc

Co-authored-by: Claude <noreply@anthropic.com>
Prevents dev server crashes when agents modify feature files or work
in isolated worktrees.

- Add --ignore patterns to tsx watch in server
- Add watch.ignored to Vite config in UI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The relative glob patterns weren't working because Vite runs from
apps/ui/, not the project root. Use path.resolve() to create absolute
paths that properly match .automaker/ and .worktrees/ directories.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implemented by Automaker auto-mode
Feature ID: feature-1770248805382-vdfh4z3bo
@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive project orchestration system with persistent retry loop support (Ralph mode), expanding the Automaker with project planning, feature scaffolding, and MCP server capabilities. It adds new API routes, services, type definitions, UI enhancements, and extensive documentation alongside Docker/deployment configuration updates.

Changes

Cohort / File(s) Summary
Configuration & Environment
.claude/settings.json, docker-compose.yml, apps/server/package.json, Dockerfile, apps/ui/package.json
Added MCP server configuration, GitHub token support, dependency additions (zod, fast-xml-parser), environment variable defaults, and watch/ignore patterns for development.
Documentation Updates
CLAUDE.md, README.md, HANDOFF.md, docs/claude-plugin.md, docs/self-learning-self-healing-proposal.md, packages/mcp-server/README.md
Expanded guides covering MCP server setup, Claude Code integration, production deployment, self-learning mechanisms, Ralph loops, project orchestration workflows, and plugin configuration.
Project Orchestration API Routes
apps/server/src/routes/projects/*
Implemented POST endpoints for project CRUD (create, read, update, delete, list) and create-features, including request validation, project existence checks, and feature generation integration.
Ralph Loop Management Routes
apps/server/src/routes/ralph/*
Implemented six POST endpoints (start, stop, pause, resume, status, list-running) for managing persistent retry loops with featureId and projectPath validation.
Project Orchestration Services
apps/server/src/services/project-service.ts, apps/server/src/services/project-orchestration-service.ts
Added ProjectService for CRUD operations and ProjectOrchestrationService for converting projects to features with epic and dependency management.
Ralph Loop & Completion Services
apps/server/src/services/ralph-loop-service.ts
Implemented RalphLoopService for executing persistent retry loops with iteration management, verification criteria checking, failure analysis, and event emission.
Core Service Enhancements
apps/server/src/services/auto-mode-service.ts, apps/server/src/services/feature-loader.ts
Added automatic worktree creation/management per branch, branch name generation from feature titles, and debug logging for feature loading operations.
Server Infrastructure
apps/server/src/index.ts, apps/server/src/lib/debug-log.ts, apps/server/src/middleware/validate-paths.ts
Wired new routes (projects, ralph), added debug logging utility with file persistence, introduced slug-specific validation middleware for path safety.
Type System Expansion
libs/types/src/project.ts, libs/types/src/ralph.ts, libs/types/src/feature.ts, libs/types/src/event.ts, libs/types/src/index.ts
Defined comprehensive types for project planning (phases, milestones, PRD), Ralph mode (completion criteria, failure analysis, loop state), and epic features.
Platform Utilities
libs/platform/src/paths.ts, libs/platform/src/projects.ts, libs/platform/src/index.ts
Implemented path builders, slug validation/generation, directory management, listing/deletion operations, and exports for project and Ralph directory structures.
Project Parsing Utilities
libs/utils/src/project-parser.ts, libs/utils/src/index.ts
Added markdown parsing and generation for projects, milestones, phases, and SPARC PRDs with dependency resolution helpers.
UI Components & Store
apps/ui/src/components/views/board-view/components/kanban-card/*, apps/ui/src/store/app-store.ts, apps/ui/src/components/views/board-view/hooks/use-board-column-features.ts
Introduced EpicBadge and EpicProgress components for epic visualization, added per-project worktree loading state management, refined worktree filtering logic.
MCP Server Implementation
packages/mcp-server/src/index.ts, packages/mcp-server/package.json, packages/mcp-server/tsconfig.json, packages/mcp-server/vitest.config.ts, packages/mcp-server/tests/index.test.ts
Implemented complete MCP server with 30+ tools covering feature management, agent control, project orchestration, Ralph loops, and utilities; includes retry logic, dependency graph computation, and board summary generation.
MCP Server Plugin Configuration
packages/mcp-server/plugins/automaker/.claude-plugin/plugin.json, packages/mcp-server/plugins/.claude-plugin/marketplace.json
Added Claude plugin manifests defining MCP server launch configuration and marketplace metadata.
MCP Agent Specifications
packages/mcp-server/plugins/automaker/agents/*
Defined nine agent workflows (feature-planner, codebase-analyzer, deep-research, feature-factory, prd-reviewer, project-scaffold, sparc-prd, agent-reviewer) with step-by-step procedures.
MCP Command Specifications
packages/mcp-server/plugins/automaker/commands/*
Documented nine command workflows (board, auto-mode, context, create-project, create-project-features, deep-research, orchestrate, scaffold-project, sparc-prd) with UI/interaction patterns.
Testing & Scripts
apps/ui/tests/mcp-server-endpoints-verification.spec.ts, apps/ui/vite.config.mts, scripts/batch-create-features.sh, scripts/example-features.json
Added endpoint routing verification tests, Vite watch config, batch feature creation script, and example feature data.
UI Build & Proxy
apps/ui/nginx.conf
Configured nginx proxy to route /api requests to backend server:3008 with WebSocket support headers and extended timeout.

Sequence Diagram(s)

sequenceDiagram
    participant User as Claude/User
    participant MCPServer as MCP Server
    participant API as Automaker API
    participant FS as File System
    participant Ralph as Ralph Loop
    
    User->>MCPServer: create_project (projectPath, title, goal)
    MCPServer->>API: POST /api/projects/create
    API->>FS: Create project structure (.automaker/projects)
    API->>FS: Write project.json, project.md, milestones
    API-->>MCPServer: { success: true, project }
    MCPServer-->>User: Project created
    
    User->>MCPServer: create_project_features (projectPath, projectSlug)
    MCPServer->>API: POST /api/projects/create-features
    API->>FS: Load project.json
    API->>API: orchestrateProjectFeatures()
    API->>FS: Create epic features from milestones
    API->>FS: Create phase features per milestone
    API->>FS: Wire dependencies
    API->>FS: Update project.json with featureIds
    API-->>MCPServer: { success: true, result }
    MCPServer-->>User: Features created with epics
    
    User->>MCPServer: start_ralph_loop (projectPath, featureId)
    MCPServer->>API: POST /api/ralph/start
    API->>Ralph: new RalphLoopService()
    Ralph->>FS: Load feature & completion criteria
    Ralph->>Ralph: Loop: iteration 1..N
    Ralph->>API: Call auto-mode (build, test, etc)
    Ralph->>Ralph: Verify completion criteria
    Note over Ralph: If failed: analyze, retry
    Note over Ralph: If passed: emit ralph:verified
    Ralph-->>API: { success: true, state }
    API-->>MCPServer: Loop status
    MCPServer-->>User: Ralph mode progress
Loading
sequenceDiagram
    participant Client as Board UI
    participant Store as Zustand Store
    participant Hook as useWorktrees Hook
    participant API as Backend API
    participant Filter as useColumnFeatures
    
    Client->>Hook: useWorktrees(projectPath)
    Hook->>API: Fetch worktrees
    Note over Hook: isLoading = true
    Hook->>Store: setWorktreesLoading(projectPath, true)
    
    API-->>Hook: Worktrees loaded
    Note over Hook: isLoading = false
    Hook->>Store: setWorktreesLoading(projectPath, false)
    
    Client->>Filter: Filter features by worktree
    Filter->>Store: getWorktreesLoading(projectPath)
    alt Worktrees still loading
        Filter->>Filter: showAllFeatures (matchesWorktree = true)
    else Worktrees loaded but empty
        Filter->>Filter: Apply strict filtering (primaryBranch only)
    else Worktrees loaded and populated
        Filter->>Filter: Normal filtering (currentBranch match)
    end
    Filter-->>Client: Filtered feature list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A Ralph loop bounces through the code,
With epic badges shining bright,
Projects now scaffold down the road,
While worktrees dance left and right. 🌿✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Epic/ralph loops' is vague and uses unclear terminology; it does not clearly convey what the PR accomplishes or which changes are most important. Use a more descriptive title that clearly explains the main feature being added (e.g., 'Add Ralph loop service for persistent retry loops with verification' or 'Implement epic features and Ralph mode persistent loops').
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@socket-security
Copy link

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​vitest/​coverage-v8@​4.0.18991006999100
Added@​modelcontextprotocol/​sdk@​1.25.3998510099100
Addedvitest@​4.0.18961007999100
Added@​types/​node@​22.19.81001008196100

View full report

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/index.ts (1)

330-354: ⚠️ Potential issue | 🔴 Critical

Bug: /api/projects route is mounted twice.

The projects route is mounted at both line 330 and line 354. This will cause the second mount to shadow the first, potentially causing unexpected routing behavior.

🐛 Remove duplicate route mount
 app.use('/api/projects', createProjectsRoutes(featureLoader));
 app.use('/api/auto-mode', createAutoModeRoutes(autoModeService));
 ...
 app.use('/api/ralph', createRalphRoutes(ralphLoopService));
 app.use('/api/event-history', createEventHistoryRoutes(eventHistoryService, settingsService));
-app.use('/api/projects', createProjectsRoutes(featureLoader));
🤖 Fix all issues with AI agents
In @.claude/settings.json:
- Around line 9-12: The settings.json currently contains a hard-coded secret
under env -> AUTOMAKER_API_KEY which must be removed; delete the embedded
AUTOMAKER_API_KEY value from .claude/settings.json and change any code or docs
that rely on it to read the key from the runtime environment
(process.env.AUTOMAKER_API_KEY) or a local, git-ignored file (e.g., .env)
instead, and add a note to configuration docs or README showing how to set
AUTOMAKER_API_URL and AUTOMAKER_API_KEY in the shell or .env so developers can
run locally without committing secrets.

In `@apps/server/src/lib/debug-log.ts`:
- Around line 30-33: The debugLog function currently calls JSON.stringify(data)
directly which can throw on circular structures or BigInt; wrap the
serialization in a try/catch inside debugLog (or extract to a helper) so that if
JSON.stringify throws you fall back to a safe placeholder like
"[unserializable]" or try a safe replacer/inspection, then build the line using
the safe string; update references to debugLog so the function returns the
timestamped line even when serialization fails.
- Around line 35-37: The current debugLog implementation uses fs.appendFileSync
(writing to DEBUG_LOG_PATH) which blocks the event loop; update the debugLog
function to perform non-blocking writes instead—either replace fs.appendFileSync
with fs.appendFile and handle the callback error (logging it) or, preferably,
create a single persistent fs.createWriteStream for DEBUG_LOG_PATH and call
stream.write(line) from debugLog (handling backpressure/errors). Ensure
DEBUG_LOG_PATH and debugLog remain the focal symbols to modify and remove
synchronous I/O from the request path so repeated calls (e.g., from
featureLoader.getAll()) do not block.

In `@apps/server/src/services/auto-mode-service.ts`:
- Around line 2840-2922: The branch name is interpolated into shell commands in
createWorktreeForBranch (and the switch-branch route handlers), creating a
command-injection risk; fix by avoiding shell interpolation: either validate
branchName with git check-ref-format --branch (or a strict regex) before use,
and replace execAsync(...) string calls with safe execFile-style calls that pass
arguments as an array (e.g. execFile('git', ['rev-parse', '--verify',
branchName], {cwd,...}) and execFile('git', ['worktree','add', worktreePath,
branchName], ...) or execFile('git', ['worktree','add','-b', branchName,
worktreePath, 'HEAD'], ...)); ensure worktreePath is passed as an argument (not
interpolated) and preserve the gitEnv usage and existing logging/error handling
in createWorktreeForBranch and the corresponding switch-branch handlers.

In `@apps/server/src/services/ralph-loop-service.ts`:
- Around line 237-251: The file_contains branch creates a RegExp from untrusted
searchPattern (criterion.config?.searchPattern) which can cause ReDoS; before
calling new RegExp() in the case 'file_contains' block (where fullPath, content,
regex, passed, details are handled), validate or sanitize searchPattern (e.g.,
use a safe-regex validator like safe-regex or implement a conservative
whitelist/escape strategy) and wrap regex compilation/execution in a try/catch
with a safe fallback (such as using a simple string.includes search on content
or rejecting overly-complex patterns); ensure any rejected pattern sets
passed/details appropriately and avoid unbounded regex execution (consider a
timeout wrapper or running the test in a worker).
- Around line 253-266: The code executes user-provided shell commands in the
command_succeeds branch using execAsync on criterion.config?.command (with cwd
derived from workDir) which creates a command injection risk; fix by disallowing
raw user-supplied commands from HTTP POST /start (or require explicit opt-in via
server-side config), replace execution of arbitrary strings with a safe
allowlist of permitted command names and arguments validated against that list,
or remove/support only pre-approved command handlers mapped to safe functions;
update ralph-loop-service.ts to validate criterion.type === 'command_succeeds'
by checking a server-managed allowlist (or feature flag) before calling
execAsync, and reject or error when an unapproved command is supplied.

In
`@apps/ui/src/components/views/board-view/components/kanban-card/epic-progress.tsx`:
- Around line 29-31: The loop over childFeatures is checking for a non-existent
status 'done' and incrementing stats.done, so change the condition to check for
'completed' (i.e., replace child.status === 'done' with child.status ===
'completed') and ensure you increment the matching stats property
(stats.completed) instead of stats.done; also verify the stats
object/initialization includes a completed key to avoid undefined increments.

In `@libs/platform/src/paths.ts`:
- Around line 462-580: The duplicated, unvalidated path helpers in paths.ts
(getProjectDir, getProjectFilePath, getProjectJsonPath, getMilestoneDir,
getMilestoneFilePath, ensureProjectsDir and any other slug-accepting helpers)
must be consolidated to the validated implementations in projects.ts: remove the
duplicate implementations from paths.ts and either re-export or delegate to the
projects.ts functions that call validateSlugInput so all public APIs use the
slug-validated logic; if you prefer to keep implementations here instead, add
validateSlugInput calls to each slug-accepting function (e.g., getProjectDir,
getMilestoneDir) and mirror projects.ts behavior to prevent path traversal.

In `@packages/mcp-server/plugins/automaker/.claude-plugin/plugin.json`:
- Around line 13-16: The plugin manifest embeds a production API key under the
"env" object (AUTOMAKER_API_KEY), which must be removed; delete the hard-coded
AUTOMAKER_API_KEY value from the "env" section in plugin.json and instead make
the plugin read AUTOMAKER_API_KEY from the runtime environment or a git-ignored
local override (e.g. .env) at startup, leaving only a placeholder or removing
the key entry entirely while keeping AUTOMAKER_API_URL if needed; update any
README or configuration docs referenced by the plugin to instruct users to set
AUTOMAKER_API_KEY in their environment and ensure no real keys are checked into
source control.

In `@packages/mcp-server/src/index.ts`:
- Around line 24-28: Replace the hardcoded fallback for AUTOMAKER_API_KEY:
remove the literal 'automaker-dev-key-2026' and instead require
process.env.AUTOMAKER_API_KEY be provided (fail fast by throwing an Error) when
API_KEY is missing in non-development environments; if you need a dev-only
fallback, only set a clearly invalid placeholder (e.g. 'INVALID_AUTOMAKER_KEY')
when NODE_ENV === 'development' and log a warning—update the API_KEY constant
and add the guard/throw near where API_KEY and API_URL are defined to ensure the
code uses API_KEY (symbol: API_KEY, env name: AUTOMAKER_API_KEY).
🟡 Minor comments (18)
apps/ui/src/components/views/board-view/hooks/use-board-column-features.ts-81-100 (1)

81-100: ⚠️ Potential issue | 🟡 Minor

Calling useAppStore.getState() inside useMemo bypasses React's reactivity.

Changes to worktreesLoadingByProject won't trigger a re-render since it's not in the dependency array. The current code relies on currentWorktreeBranch changing when worktrees finish loading, which is fragile.

Consider using a reactive selector at the hook level:

+  const isWorktreesLoading = useAppStore((s) => 
+    projectPath ? s.getWorktreesLoading(projectPath) : false
+  );
+  const worktreesForProject = useAppStore((s) => 
+    projectPath ? s.worktreesByProject[projectPath] ?? [] : []
+  );

Then add these to the useMemo dependency array to ensure proper re-computation when loading state changes.

packages/mcp-server/README.md-160-162 (1)

160-162: ⚠️ Potential issue | 🟡 Minor

Update README license to reflect actual project license.

The README states "MIT" license, but the project uses the AUTOMAKER LICENSE AGREEMENT (defined in the root LICENSE file). Update the README to either reference the root LICENSE file or explicitly state the correct custom license to maintain consistency across the project's licensing documentation.

docs/claude-plugin.md-95-99 (1)

95-99: ⚠️ Potential issue | 🟡 Minor

Align the startup command (dev vs dev:web).
Quick Start uses npm run dev:web, but prerequisites say npm run dev. Pick one and use it consistently to avoid setup confusion.

packages/mcp-server/plugins/automaker/README.md-37-42 (1)

37-42: ⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks.
This removes markdownlint MD040 warnings and improves rendering.

✍️ Example fix
-```
+```text
 /board                    # Show board overview
 /board create             # Create a new feature
 /board [feature-id]       # Show feature details
 /board agent-output       # Review what an agent did
</details>

</blockquote></details>
<details>
<summary>docs/claude-plugin.md-154-156 (1)</summary><blockquote>

`154-156`: _⚠️ Potential issue_ | _🟡 Minor_

**Add language identifiers to fenced code blocks.**  
Markdownlint flags missing languages; please add `bash`, `json`, `yaml`, or `text` across the file for consistency and readability.  


<details>
<summary>✍️ Example fix (apply similarly to other blocks)</summary>

```diff
-```
+```text
 /board
</details>

</blockquote></details>
<details>
<summary>scripts/batch-create-features.sh-36-42 (1)</summary><blockquote>

`36-42`: _⚠️ Potential issue_ | _🟡 Minor_

**Potential JSON injection when embedding feature data.**

The `$feature` variable is interpolated directly into the JSON payload. If the feature JSON contains unescaped characters or malformed data, this could corrupt the request body.

Consider using `jq` to safely construct the payload:


<details>
<summary>Proposed fix using jq for safe JSON construction</summary>

```diff
-  response=$(curl -s -X POST "$API_URL/api/features/create" \
-    -H "Content-Type: application/json" \
-    -H "X-API-Key: $API_KEY" \
-    -d "{
-      \"projectPath\": \"$PROJECT_PATH\",
-      \"feature\": $feature
-    }")
+  payload=$(jq -n --arg path "$PROJECT_PATH" --argjson feat "$feature" \
+    '{ projectPath: $path, feature: $feat }')
+  
+  response=$(curl -s -X POST "$API_URL/api/features/create" \
+    -H "Content-Type: application/json" \
+    -H "X-API-Key: $API_KEY" \
+    -d "$payload")
packages/mcp-server/plugins/automaker/agents/sparc-prd.md-43-46 (1)

43-46: ⚠️ Potential issue | 🟡 Minor

Add a language identifier to the fenced tool block.
markdownlint MD040 requires a language for fenced code blocks; adding one keeps lint clean and improves readability.

✏️ Suggested update
-```
+```text
 mcp__automaker__get_project_spec({ projectPath })
 mcp__automaker__list_features({ projectPath })
```
packages/mcp-server/plugins/automaker/commands/create-project.md-38-40 (1)

38-40: ⚠️ Potential issue | 🟡 Minor

Add language tags to fenced blocks for markdownlint.
Several fenced blocks are missing language identifiers; MD040 will keep warning until they’re labeled. Add text/json/markdown as appropriate and repeat for the other unlabeled blocks in this doc.

✏️ Suggested update (apply similarly to the other blocks)
-```
+```text
 mcp__automaker__health_check()
```
packages/mcp-server/plugins/automaker/agents/feature-planner.md-35-37 (1)

35-37: ⚠️ Potential issue | 🟡 Minor

Add language tags to fenced blocks for markdownlint.
Several fenced blocks are missing language identifiers; adding them keeps MD040 clean and improves readability.

✏️ Suggested update (apply similarly to the other blocks)
-```
+```text
 mcp__automaker__get_project_spec({ projectPath })
```
packages/mcp-server/plugins/automaker/commands/sparc-prd.md-41-48 (1)

41-48: ⚠️ Potential issue | 🟡 Minor

Add a language identifier to the fenced tool blocks.
markdownlint MD040 expects a language for fenced code blocks; labeling the tool call blocks keeps lint clean.

✏️ Suggested update (apply to both tool blocks)
-```
+```text
 mcp__automaker__get_project_spec({ projectPath })
```
packages/mcp-server/tests/index.test.ts-1-23 (1)

1-23: ⚠️ Potential issue | 🟡 Minor

Tests are tautological; wire them to real config/tool sources.

These assertions compare constants to themselves, so they won't detect regressions in default config or tool registration. Import the actual config and tool registry and assert on their values/count instead.

apps/server/package.json-14-14 (1)

14-14: ⚠️ Potential issue | 🟡 Minor

Use double quotes for cross-platform glob compatibility.

Single quotes are not interpreted as quoting delimiters by cmd.exe (npm's default shell on Windows), so the glob patterns will be passed literally to tsx and won't match correctly. Double quotes are the standard approach for npm scripts that must work across Windows and Unix-like systems.

Cross-platform quoting fix
-    "dev": "tsx watch --ignore '**/.automaker/**' --ignore '**/.worktrees/**' src/index.ts",
+    "dev": "tsx watch --ignore \"**/.automaker/**\" --ignore \"**/.worktrees/**\" src/index.ts",
apps/server/src/services/ralph-loop-service.ts-648-660 (1)

648-660: ⚠️ Potential issue | 🟡 Minor

Dead code: enhancedDescription is computed but never used.

The variable enhancedDescription is assigned but not passed to executeFeature(). The comment indicates the intent was to include Ralph context, but the implementation doesn't actually use it.

💡 Either use the enhanced description or remove dead code

If the intent is to pass context, you'll need to modify executeFeature to accept custom description/context, or find another way to inject the Ralph context into the agent's prompt.

         // Update feature description to include Ralph context
         // This is a lightweight way to pass context without modifying auto-mode-service
         const enhancedDescription = `${feature.description}\n\n---\n\n${ralphContext}`;
+        // TODO: Pass enhancedDescription to the agent somehow
+        // Currently executeFeature doesn't support custom descriptions

         // Run the feature using auto-mode service
-        // Note: This is a simplified approach - we're leveraging existing infrastructure
         await this.autoModeService.executeFeature(
           projectPath,
           featureId,
           true, // useWorktrees
           false // isAutoMode - we're managing the loop ourselves
         );
packages/mcp-server/src/index.ts-1226-1243 (1)

1226-1243: ⚠️ Potential issue | 🟡 Minor

Potential infinite loop: Topological sort doesn't detect cycles.

The visit() function for computing execution order will infinite loop if there's a circular dependency in the feature graph. Consider tracking in-progress visits to detect cycles.

🔄 Add cycle detection
       const visited = new Set<string>();
+      const inProgress = new Set<string>();
       const order: Array<{ id: string; title: string; dependencies: string[] }> = [];
       const featureMap = new Map(filtered.map((f) => [f.id, f]));

       function visit(id: string) {
         if (visited.has(id)) return;
+        if (inProgress.has(id)) {
+          console.error(`[MCP] Circular dependency detected involving feature: ${id}`);
+          return; // Skip to avoid infinite loop
+        }
+        inProgress.add(id);
         visited.add(id);
         const feature = featureMap.get(id);
         if (!feature) return;
         for (const depId of feature.dependencies || []) {
           visit(depId);
         }
+        inProgress.delete(id);
         order.push({
           id: feature.id,
           title: feature.title,
           dependencies: feature.dependencies || [],
         });
       }
docs/self-learning-self-healing-proposal.md-789-797 (1)

789-797: ⚠️ Potential issue | 🟡 Minor

Missing language specifier on fenced code block.

The code block starting at Line 791 lacks a language identifier, which affects syntax highlighting and linting.

🔧 Proposed fix
 ### 9.4 Overall Autonomy Score
-```
+```text
 Autonomy Score = (
   (Features completed without intervention / Total features) * 0.4 +
libs/platform/src/projects.ts-242-260 (1)

242-260: ⚠️ Potential issue | 🟡 Minor

Slug generators could produce empty strings.

If title contains only special characters (e.g., "!!!" or " "), the generateProjectSlug, generateMilestoneSlug, and generatePhaseSlug functions will return an empty string (or just the number prefix for milestones). An empty slug would fail the VALID_SLUG_PATTERN validation when used.

🔧 Suggested fix
 export function generateProjectSlug(title: string): string {
-  return title
+  const slug = title
     .toLowerCase()
     .replace(/[^a-z0-9]+/g, '-')
     .replace(/^-|-$/g, '');
+  return slug || 'untitled';
 }
libs/utils/src/project-parser.ts-195-260 (1)

195-260: ⚠️ Potential issue | 🟡 Minor

Potential undefined access when milestone has no phases.

Line 257 accesses milestone.phases[0]?.name with optional chaining, which is good. However, if phases array is empty, the generated "Next" link will point to phase-01-start.md which may not exist or be misleading.

🔧 Suggested improvement
-  content += `**Next**: [Phase 1](./phase-01-${milestone.phases[0]?.name || 'start'}.md)\n`;
+  if (milestone.phases.length > 0) {
+    content += `**Next**: [Phase 1](./phase-01-${milestone.phases[0].name}.md)\n`;
+  }
apps/server/src/services/project-service.ts-248-255 (1)

248-255: ⚠️ Potential issue | 🟡 Minor

Project object is mutated in-place but update only persists status.

Lines 217, 243-244 mutate milestone.epicId and phase.featureId on the project object. However, updateProject at Line 249 only updates the status field, while Line 254-255 re-saves the entire mutated project object separately. This creates two write operations that could leave the project in an inconsistent state if the second write fails.

Consider consolidating into a single write or ensuring atomicity.

🔧 Proposed consolidation
-    // Update project with feature/epic IDs
-    await this.updateProject(projectPath, projectSlug, {
-      status: 'active',
-    });
-
-    // Re-save project.json with updated featureIds
+    // Update project status and save with all mutations (epicIds, featureIds)
+    project.status = 'active';
+    project.updatedAt = new Date().toISOString();
     const jsonPath = getProjectJsonPath(projectPath, projectSlug);
     await secureFs.writeFile(jsonPath, JSON.stringify(project, null, 2));
+
+    // Also update the markdown
+    const mdPath = getProjectFilePath(projectPath, projectSlug);
+    await secureFs.writeFile(mdPath, generateProjectMarkdown(project));
🧹 Nitpick comments (25)
apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx (1)

6-6: Unused import: Layers is imported but not used in this file.

The Layers icon is used in epic-badge.tsx, not here.

🧹 Suggested fix
-import { AlertCircle, Lock, Hand, Sparkles, Layers } from 'lucide-react';
+import { AlertCircle, Lock, Hand, Sparkles } from 'lucide-react';
packages/mcp-server/package.json (1)

1-9: Add engines field and license for consistency.

Other packages in the workspace specify "engines": { "node": ">=22.0.0 <23.0.0" } and "license": "SEE LICENSE IN LICENSE". Adding these fields ensures consistency across the monorepo.

Also, ensure the CLI entry point (dist/index.js) includes a shebang (#!/usr/bin/env node) at the top for proper execution when invoked directly.

♻️ Proposed fix
 {
   "name": "@automaker/mcp-server",
   "version": "1.0.0",
   "description": "MCP server for interacting with Automaker board and features",
   "type": "module",
   "main": "dist/index.js",
   "bin": {
     "automaker-mcp": "./dist/index.js"
   },
+  "license": "SEE LICENSE IN LICENSE",
+  "engines": {
+    "node": ">=22.0.0 <23.0.0"
+  },
   "scripts": {
packages/mcp-server/tsconfig.json (1)

13-16: Consider moving vitest/globals to a test‑only tsconfig.
Including test globals in the production build config can mask global name collisions in src. A separate tsconfig.test.json is typically cleaner.

♻️ Minimal change (then add a test tsconfig)
-    "types": ["node", "vitest/globals"]
+    "types": ["node"]
packages/mcp-server/plugins/automaker/commands/orchestrate.md (1)

38-45: Consider adding language specifiers to fenced code blocks.

Static analysis flags multiple code blocks without language specifiers. For documentation clarity and linting compliance, consider adding appropriate identifiers:

  • Use ```text for ASCII diagrams (lines 38, 68)
  • Use ```javascript or ```js for API call examples (lines 62, 105, 115, etc.)
  • Use ```yaml for configuration-like prompts (line 92)

This is a minor documentation hygiene improvement.

Also applies to: 62-64, 68-81

scripts/batch-create-features.sh (1)

31-52: Consider adding error handling for curl failures.

The script uses set -e but curl won't fail on HTTP errors (4xx/5xx) without the -f flag. The current approach of parsing the JSON response is reasonable, but network failures or malformed responses could cause unexpected behavior.

Consider adding a check for curl exit status or using --fail-with-body (curl 7.76+) to handle HTTP errors more explicitly.

packages/mcp-server/plugins/automaker/agents/codebase-analyzer.md (1)

31-34: Add language specifiers to fenced code blocks.

Multiple code blocks lack language specifiers (flagged by markdownlint MD040). Since these represent pseudo-code or command syntax, consider using text or plaintext as the language identifier to satisfy linting without misrepresenting the content.

Example fix for one block
-```
+```text
 Glob({ pattern: "src/**/*", path: projectPath })
</details>


Also applies to: 63-66, 79-81, 92-110, 116-118, 131-134, 189-206

</blockquote></details>
<details>
<summary>packages/mcp-server/plugins/automaker/commands/board.md (1)</summary><blockquote>

`47-49`: **Add language specifiers to fenced code blocks.**

Several code blocks are missing language specifiers (markdownlint MD040). Use `text` or `yaml` where appropriate based on the content format.



<details>
<summary>Example fixes</summary>

```diff
-```
+```text
 mcp__automaker__health_check()

For YAML-like option blocks:

```diff
-```
+```yaml
 header: "Feature Details"
 question: "What should this feature do?..."

Also applies to: 63-81, 87-91, 96-104, 110-124, 130-134

packages/mcp-server/plugins/automaker/commands/create-project-features.md (1)

64-66: Add language specifiers to fenced code blocks.

Multiple code blocks lack language specifiers (markdownlint MD040). Consider text for function calls and yaml for option structures.

Also applies to: 74-82, 84-92, 163-166, 170-173, 177-180, 193-198

packages/mcp-server/plugins/automaker/commands/scaffold-project.md (1)

28-44: Add language specifier to directory tree code block.

The directory structure block at line 28 should have a language specifier. Use text or plaintext for tree diagrams.

Proposed fix
-```
+```text
 .automaker/
 └── projects/
     └── {project-slug}/
packages/mcp-server/plugins/automaker/agents/deep-research.md (1)

34-36: Add language specifiers to fenced code blocks.

Code blocks at lines 34, 40, 47, and 55 lack language specifiers (markdownlint MD040). Use text for function call examples.

Also applies to: 40-42, 47-49, 55-61

apps/ui/tests/mcp-server-endpoints-verification.spec.ts (1)

35-38: Prefer top-level import over inline require.

Using require inside a function body is non-idiomatic for ES modules. Consider importing execSync at the top of the file with the other imports.

Proposed fix

At the top of the file:

import { execSync } from 'child_process';

Then remove line 35:

-    const { execSync } = require('child_process');
     execSync('git init', { cwd: projectPath });
apps/server/src/routes/ralph/routes/status.ts (1)

19-26: Consider adding info logging for consistency.

Other Ralph handlers (stop, pause) log the action attempt before calling the service. This handler only logs on error. Consider adding an info log before calling getStatus for consistency and debugging purposes.

♻️ Proposed fix
       if (!projectPath || !featureId) {
         res.status(400).json({
           error: 'Missing required parameters: projectPath and featureId are required',
         });
         return;
       }

+      logger.info('Getting Ralph loop status', { projectPath, featureId });
+
       const state = await ralphLoopService.getStatus(projectPath, featureId);
apps/server/src/routes/projects/routes/create.ts (1)

88-151: Consider rollback on partial failure.

The sequential directory and file creation for milestones and phases could leave the project in an inconsistent state if a write fails mid-way. Consider wrapping the entire creation in a try-catch that cleans up the project directory on failure.

💡 Optional: Add cleanup on failure
+      let createdSlug: string | null = null;
       try {
         // Create directory structure
         await ensureProjectStructure(projectPath, projectSlug);
+        createdSlug = projectSlug;

         // ... rest of creation logic ...

         res.json({ success: true, project });
       } catch (error) {
+        // Cleanup on failure
+        if (createdSlug) {
+          try {
+            await deleteProjectPlan(projectPath, createdSlug);
+          } catch {
+            // Ignore cleanup errors
+          }
+        }
         logError(error, 'Create project failed');
         res.status(500).json({ success: false, error: getErrorMessage(error) });
       }
apps/server/src/routes/projects/routes/update.ts (1)

97-109: Consider handling PRD deletion when cleared.

When updates.prd is explicitly set to null or undefined to clear the PRD, the prd.md file is not deleted or cleared. This may leave stale PRD content on disk. The researchSummary handling correctly writes an empty string when cleared—consider similar treatment for PRD.

💡 Optional: Handle PRD clearing
       // Update prd.md if PRD field is present and has a value
-      // Note: We check for presence and truthy value since PRD is a complex object
-      if ('prd' in updates && updates.prd) {
+      if ('prd' in updates) {
         const prdFilePath = getPrdFilePath(projectPath, projectSlug);
-        const prdContent = generatePrdFile(project.title, updates.prd);
-        await secureFs.writeFile(prdFilePath, prdContent, 'utf-8');
+        if (updates.prd) {
+          const prdContent = generatePrdFile(project.title, updates.prd);
+          await secureFs.writeFile(prdFilePath, prdContent, 'utf-8');
+        } else {
+          // Clear or delete prd.md when PRD is removed
+          try {
+            await secureFs.unlink(prdFilePath);
+          } catch {
+            // Ignore if file doesn't exist
+          }
+        }
       }
packages/mcp-server/plugins/automaker/agents/agent-reviewer.md (1)

34-51: Add language specifiers to fenced code blocks.

Static analysis flags multiple code blocks without language specifiers (lines 34, 40, 49, 95, 142). For MCP tool call examples, consider using text or plaintext as a generic identifier to satisfy linting while indicating these aren't executable code.

📝 Example fix for one block
-```
+```text
 mcp__automaker__get_feature({ projectPath, featureId })
 mcp__automaker__get_agent_output({ projectPath, featureId })
</details>

</blockquote></details>
<details>
<summary>apps/server/src/routes/projects/routes/get.ts (1)</summary><blockquote>

`102-113`: **Use platform path utilities instead of string concatenation.**

Line 104 uses string template for path construction (`${milestoneDir}/${phaseFile}`), which could cause issues on Windows or with edge cases. Consider using `path.join` or a dedicated platform helper for consistency with the rest of the codebase.



<details>
<summary>♻️ Proposed fix</summary>

```diff
+import path from 'path';
 // ... in the loop:
-          const phaseFilePath = `${milestoneDir}/${phaseFile}`;
+          const phaseFilePath = path.join(milestoneDir, phaseFile);

Or use secureFs.joinPath if available from the platform utilities.

packages/mcp-server/plugins/automaker/agents/prd-reviewer.md (1)

85-89: Add language specifiers to code blocks.

Static analysis flags code blocks at lines 85 and 181 without language specifiers. These show glob/read patterns - consider using text or plaintext as identifiers.

CLAUDE.md (1)

237-246: Add language specifier to the project structure code block.

Static analysis flags the fenced code block at line 237 without a language specifier. Since this shows a directory structure, use text or plaintext.

📝 Proposed fix
-```
+```text
 .automaker/projects/{project-slug}/
 ├── project.md           # Project overview
 ...
apps/server/src/services/ralph-loop-service.ts (1)

847-869: Clarify pause semantics: current iteration completes before pause takes effect.

The pause implementation sets status = 'paused', which is checked in the loop condition. This means pause takes effect after the current iteration (agent execution + verification) completes, not immediately.

This is likely the intended behavior, but consider documenting this clearly or emitting an event when the pause actually takes effect (vs when requested).

apps/server/src/routes/projects/routes/create-features.ts (1)

30-31: EventEmitter created but events are not utilized.

A new EventEmitter is created for progress tracking but:

  1. No subscriber is attached to forward events to the client
  2. Events are emitted but lost since the response is synchronous

If progress tracking is desired, consider streaming events via WebSocket or Server-Sent Events, or remove the unused emitter to reduce confusion.

💡 Options to address unused EventEmitter

Option 1: Remove if not needed

-    // Create event emitter for progress tracking
-    const events = createEventEmitter();
...
     const result = await orchestrateProjectFeatures(
       project,
       {
         projectPath,
         projectSlug,
         createEpics,
         setupDependencies,
         initialStatus,
       },
       featureLoader,
-      events
     );

Option 2: Store events for debugging/logging

+    const progressEvents: unknown[] = [];
     const events = createEventEmitter();
+    events.subscribe((type, payload) => {
+      progressEvents.push({ type, payload, timestamp: Date.now() });
+    });
packages/mcp-server/src/index.ts (1)

1330-1333: Inconsistent: health_check bypasses apiCall and omits auth header.

The health_check tool uses direct fetch without the X-API-Key header, while all other tools use apiCall. This may cause issues if the health endpoint is behind authentication in some deployments.

💡 Use apiCall for consistency
     case 'health_check': {
-      const response = await fetch(`${API_URL}/api/health`);
-      return response.json();
+      return apiCall('/health', {}, 'GET');
     }
apps/server/src/services/project-service.ts (1)

143-147: Spread operator allows arbitrary field injection.

The spread of updates into the Project object could allow callers to overwrite internal fields like slug, createdAt, or milestones unexpectedly. Consider explicitly picking allowed fields from UpdateProjectInput.

♻️ Suggested approach
     const updated: Project = {
       ...existing,
-      ...updates,
+      ...(updates.title !== undefined && { title: updates.title }),
+      ...(updates.goal !== undefined && { goal: updates.goal }),
+      ...(updates.status !== undefined && { status: updates.status }),
+      // Add other explicitly allowed fields from UpdateProjectInput
       updatedAt: new Date().toISOString(),
     };
libs/types/src/ralph.ts (1)

26-51: Consider using discriminated unions for type-safe criterion configs.

The current CompletionCriterion interface has a single config object where all properties are optional. This allows invalid combinations (e.g., type: 'tests_pass' with url property). A discriminated union would provide better type safety.

♻️ Example discriminated union approach
export type CompletionCriterion =
  | { type: 'tests_pass'; name: string; required?: boolean }
  | { type: 'build_succeeds'; name: string; required?: boolean }
  | { type: 'file_exists'; name: string; required?: boolean; filePath: string }
  | { type: 'command_succeeds'; name: string; required?: boolean; command: string; cwd?: string; timeout?: number }
  | { type: 'http_endpoint'; name: string; required?: boolean; url: string; expectedStatus?: number; expectedBodyContains?: string }
  // ... etc
libs/utils/src/project-parser.ts (1)

488-500: Section content accumulates raw lines including leading whitespace.

The PRD parser accumulates the entire line (not trimmed) when building section content (Lines 490, 492, 494, 496, 498). This preserves formatting but may include unintended leading whitespace from the markdown. The condition (situation && trimmed ? '\n' : '') checks trimmed but appends line, which could lead to inconsistent results.

♻️ Consider using trimmed consistently or preserving all whitespace intentionally
-    if (currentSection === 'situation' || currentSection === 's - situation') {
-      situation += (situation && trimmed ? '\n' : '') + line;
+    if (currentSection === 'situation' || currentSection === 's - situation') {
+      if (trimmed) situation += (situation ? '\n' : '') + trimmed;
libs/platform/src/projects.ts (1)

331-336: Type assertion as any[] on readdir results.

Lines 331, 354, and 382 cast readdir results to any[]. If secureFs.readdir returns properly typed Dirent[] when withFileTypes: true is passed, these casts are unnecessary and hide type information.

♻️ Consider proper typing
-  const entries = (await secureFs.readdir(projectsDir, { withFileTypes: true })) as any[];
+  const entries = await secureFs.readdir(projectsDir, { withFileTypes: true });

If secureFs typing is incomplete, consider augmenting its type definitions instead.

Also applies to: 354-358, 382-388

Comment on lines +9 to +12
"env": {
"AUTOMAKER_API_URL": "http://localhost:3008",
"AUTOMAKER_API_KEY": "automaker-dev-key-2026"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove the hard‑coded API key from repo config.
Shipping a fixed key in version control is a security/compliance risk and encourages reuse across environments. Prefer sourcing it from the user’s shell env or a local, git‑ignored file.

🔐 Suggested change (remove embedded key)
       "env": {
         "AUTOMAKER_API_URL": "http://localhost:3008",
-        "AUTOMAKER_API_KEY": "automaker-dev-key-2026"
       }
📝 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
"env": {
"AUTOMAKER_API_URL": "http://localhost:3008",
"AUTOMAKER_API_KEY": "automaker-dev-key-2026"
}
"env": {
"AUTOMAKER_API_URL": "http://localhost:3008"
}
🤖 Prompt for AI Agents
In @.claude/settings.json around lines 9 - 12, The settings.json currently
contains a hard-coded secret under env -> AUTOMAKER_API_KEY which must be
removed; delete the embedded AUTOMAKER_API_KEY value from .claude/settings.json
and change any code or docs that rely on it to read the key from the runtime
environment (process.env.AUTOMAKER_API_KEY) or a local, git-ignored file (e.g.,
.env) instead, and add a note to configuration docs or README showing how to set
AUTOMAKER_API_URL and AUTOMAKER_API_KEY in the shell or .env so developers can
run locally without committing secrets.

Comment on lines +30 to +33
export function debugLog(category: string, message: string, data?: unknown): void {
const timestamp = new Date().toISOString();
const dataStr = data ? ` | ${JSON.stringify(data)}` : '';
const line = `[${timestamp}] [${category}] ${message}${dataStr}\n`;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat apps/server/src/lib/debug-log.ts

Repository: shaal/automaker

Length of output: 1563


🏁 Script executed:

rg -n "debugLog\(" --type ts --type js -B2 -A2

Repository: shaal/automaker

Length of output: 3253


Guard JSON serialization to avoid throwing in request paths.

JSON.stringify runs before the try/catch; circular references or BigInt data will throw and can bubble into request handlers. Wrap serialization and fall back to a safe placeholder.

🐛 Suggested fix
 export function debugLog(category: string, message: string, data?: unknown): void {
   const timestamp = new Date().toISOString();
-  const dataStr = data ? ` | ${JSON.stringify(data)}` : '';
+  let dataStr = '';
+  if (data !== undefined) {
+    try {
+      dataStr = ` | ${JSON.stringify(data)}`;
+    } catch {
+      dataStr = ' | [unserializable]';
+    }
+  }
   const line = `[${timestamp}] [${category}] ${message}${dataStr}\n`;
📝 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
export function debugLog(category: string, message: string, data?: unknown): void {
const timestamp = new Date().toISOString();
const dataStr = data ? ` | ${JSON.stringify(data)}` : '';
const line = `[${timestamp}] [${category}] ${message}${dataStr}\n`;
export function debugLog(category: string, message: string, data?: unknown): void {
const timestamp = new Date().toISOString();
let dataStr = '';
if (data !== undefined) {
try {
dataStr = ` | ${JSON.stringify(data)}`;
} catch {
dataStr = ' | [unserializable]';
}
}
const line = `[${timestamp}] [${category}] ${message}${dataStr}\n`;
🤖 Prompt for AI Agents
In `@apps/server/src/lib/debug-log.ts` around lines 30 - 33, The debugLog function
currently calls JSON.stringify(data) directly which can throw on circular
structures or BigInt; wrap the serialization in a try/catch inside debugLog (or
extract to a helper) so that if JSON.stringify throws you fall back to a safe
placeholder like "[unserializable]" or try a safe replacer/inspection, then
build the line using the safe string; update references to debugLog so the
function returns the timestamped line even when serialization fails.

Comment on lines +35 to +37
try {
fs.appendFileSync(DEBUG_LOG_PATH, line);
} catch (err) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n apps/server/src/lib/debug-log.ts

Repository: shaal/automaker

Length of output: 1976


🏁 Script executed:

rg -r '$1' 'import\s+.*debugLog' --no-heading

Repository: shaal/automaker

Length of output: 216


🏁 Script executed:

rg 'debugLog\(' --type ts --type js -B 2 -A 1 | head -100

Repository: shaal/automaker

Length of output: 2565


🏁 Script executed:

cat -n apps/server/src/routes/features/routes/list.ts

Repository: shaal/automaker

Length of output: 1359


Avoid synchronous file writes on the request path.

appendFileSync blocks the event loop; the logger is called directly from API route handlers (e.g., apps/server/src/routes/features/routes/list.ts), where it adds unnecessary latency under load. Additionally, featureLoader.getAll() calls debugLog multiple times, compounding the blocking I/O cost per request. Use fs.appendFile (with a callback) or a write stream instead.

♻️ Suggested update
-  try {
-    fs.appendFileSync(DEBUG_LOG_PATH, line);
-  } catch (err) {
-    console.error('[DebugLog] Failed to write:', err);
-  }
+  fs.appendFile(DEBUG_LOG_PATH, line, (err) => {
+    if (err) {
+      console.error('[DebugLog] Failed to write:', err);
+    }
+  });
📝 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
try {
fs.appendFileSync(DEBUG_LOG_PATH, line);
} catch (err) {
fs.appendFile(DEBUG_LOG_PATH, line, (err) => {
if (err) {
console.error('[DebugLog] Failed to write:', err);
}
});
🤖 Prompt for AI Agents
In `@apps/server/src/lib/debug-log.ts` around lines 35 - 37, The current debugLog
implementation uses fs.appendFileSync (writing to DEBUG_LOG_PATH) which blocks
the event loop; update the debugLog function to perform non-blocking writes
instead—either replace fs.appendFileSync with fs.appendFile and handle the
callback error (logging it) or, preferably, create a single persistent
fs.createWriteStream for DEBUG_LOG_PATH and call stream.write(line) from
debugLog (handling backpressure/errors). Ensure DEBUG_LOG_PATH and debugLog
remain the focal symbols to modify and remove synchronous I/O from the request
path so repeated calls (e.g., from featureLoader.getAll()) do not block.

Comment on lines +2840 to +2922
/**
* Get all branch names that have existing worktrees
* Used to identify orphaned features (features with branchNames but no worktrees)
*/
private async getAllWorktreeBranches(projectPath: string): Promise<Set<string>> {
const branches = new Set<string>();
try {
const { stdout } = await execAsync('git worktree list --porcelain', {
cwd: projectPath,
});

const lines = stdout.split('\n');
for (const line of lines) {
if (line.startsWith('branch ')) {
const branch = line.slice(7).replace('refs/heads/', '');
branches.add(branch);
}
}
} catch {
// If we can't get worktrees, return empty set
// This will cause all features to be treated as orphaned on main worktree
}
return branches;
}

/**
* Create a new worktree for a given branch
* Returns the worktree path on success, null on failure
*/
private async createWorktreeForBranch(
projectPath: string,
branchName: string
): Promise<string | null> {
try {
// Sanitize branch name for directory usage
const sanitizedName = branchName.replace(/[^a-zA-Z0-9_-]/g, '-');
const worktreesDir = path.join(projectPath, '.worktrees');
const worktreePath = path.join(worktreesDir, sanitizedName);

// Create worktrees directory if it doesn't exist
await secureFs.mkdir(worktreesDir, { recursive: true });

// Check if branch exists
let branchExists = false;
try {
await execAsync(`git rev-parse --verify "${branchName}"`, {
cwd: projectPath,
});
branchExists = true;
} catch {
// Branch doesn't exist
}

// Create worktree with git identity env vars
const gitEnv = {
...process.env,
GIT_AUTHOR_NAME: 'Automaker',
GIT_AUTHOR_EMAIL: 'automaker@localhost',
GIT_COMMITTER_NAME: 'Automaker',
GIT_COMMITTER_EMAIL: 'automaker@localhost',
};

if (branchExists) {
// Use existing branch
await execAsync(`git worktree add "${worktreePath}" "${branchName}"`, {
cwd: projectPath,
env: gitEnv,
});
} else {
// Create new branch from HEAD
await execAsync(`git worktree add -b "${branchName}" "${worktreePath}" HEAD`, {
cwd: projectPath,
env: gitEnv,
});
}

logger.info(`Created worktree for branch "${branchName}" at: ${worktreePath}`);
return path.resolve(worktreePath);
} catch (error) {
logger.error(`Failed to create worktree for branch "${branchName}":`, error);
return null;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/server/src/services/auto-mode-service.ts | head -100

Repository: shaal/automaker

Length of output: 3773


🏁 Script executed:

wc -l apps/server/src/services/auto-mode-service.ts

Repository: shaal/automaker

Length of output: 109


🏁 Script executed:

# Get the imports and context around the vulnerable code
sed -n '1,50p' apps/server/src/services/auto-mode-service.ts

Repository: shaal/automaker

Length of output: 1385


🏁 Script executed:

# Look for where createWorktreeForBranch is called
rg 'createWorktreeForBranch' apps/server/src/services/auto-mode-service.ts -B 3 -A 3

Repository: shaal/automaker

Length of output: 1450


🏁 Script executed:

# Check for branchName parameter sources and validations
rg 'branchName' apps/server/src/services/auto-mode-service.ts -B 2 -A 2 | head -100

Repository: shaal/automaker

Length of output: 3275


🏁 Script executed:

# Extract the exact vulnerable methods (lines 2840-2922)
sed -n '2840,2922p' apps/server/src/services/auto-mode-service.ts

Repository: shaal/automaker

Length of output: 2726


🏁 Script executed:

# Search for where branchName comes from in Feature interface or related types
rg 'interface Feature|branchName.*:' apps/server/src -A 5 | head -50

Repository: shaal/automaker

Length of output: 3866


🏁 Script executed:

# Check if there's any validation of branchName before use
rg 'branchName|check.*branch|validate.*branch' apps/server/src/services/auto-mode-service.ts | head -30

Repository: shaal/automaker

Length of output: 1982


🏁 Script executed:

# Look at how features are loaded/created - where branchName gets its value
rg 'new Feature|Feature {' apps/server/src/services/auto-mode-service.ts -B 2 -A 5 | head -60

Repository: shaal/automaker

Length of output: 164


🏁 Script executed:

# Find Feature interface definition
rg 'type Feature|interface Feature' apps/server/src -A 20 | head -80

Repository: shaal/automaker

Length of output: 5086


🏁 Script executed:

# Check how branchName gets assigned in features
rg 'branchName:' apps/server/src/services -B 2 -A 2 | head -80

Repository: shaal/automaker

Length of output: 5500


🏁 Script executed:

# Check if there are other git commands using branchName with execAsync
rg 'execAsync.*branch|execAsync.*git' apps/server/src/services/auto-mode-service.ts -B 1 -A 1

Repository: shaal/automaker

Length of output: 1269


🏁 Script executed:

# Look at switch-branch.ts which also appears to have similar vulnerability
cat -n apps/server/src/routes/worktree/routes/switch-branch.ts

Repository: shaal/automaker

Length of output: 5319


Harden git worktree and branch operations against command injection.

branchName is interpolated into shell command strings without proper escaping. Even though current code paths use slugify() for branch creation, this does not guarantee safety against shell metacharacters. Prefer execFile with array arguments (or validate via git check-ref-format --branch) before invoking git commands.

This vulnerability exists in three places within createWorktreeForBranch():

  • git rev-parse --verify "${branchName}"
  • git worktree add "${worktreePath}" "${branchName}"
  • git worktree add -b "${branchName}" "${worktreePath}" HEAD

Additionally, the same pattern appears in apps/server/src/routes/worktree/routes/switch-branch.ts at lines 109 and 132.

🔒 Safer execution using execFile
-import { exec } from 'child_process';
+import { exec, execFile } from 'child_process';
@@
-const execAsync = promisify(exec);
+const execAsync = promisify(exec);
+const execFileAsync = promisify(execFile);
@@
-        await execAsync(`git rev-parse --verify "${branchName}"`, {
+        await execFileAsync('git', ['rev-parse', '--verify', branchName], {
           cwd: projectPath,
         });
@@
-        await execAsync(`git worktree add "${worktreePath}" "${branchName}"`, {
+        await execFileAsync('git', ['worktree', 'add', worktreePath, branchName], {
           cwd: projectPath,
           env: gitEnv,
         });
@@
-        await execAsync(`git worktree add -b "${branchName}" "${worktreePath}" HEAD`, {
+        await execFileAsync('git', ['worktree', 'add', '-b', branchName, worktreePath, 'HEAD'], {
           cwd: projectPath,
           env: gitEnv,
         });
🤖 Prompt for AI Agents
In `@apps/server/src/services/auto-mode-service.ts` around lines 2840 - 2922, The
branch name is interpolated into shell commands in createWorktreeForBranch (and
the switch-branch route handlers), creating a command-injection risk; fix by
avoiding shell interpolation: either validate branchName with git
check-ref-format --branch (or a strict regex) before use, and replace
execAsync(...) string calls with safe execFile-style calls that pass arguments
as an array (e.g. execFile('git', ['rev-parse', '--verify', branchName],
{cwd,...}) and execFile('git', ['worktree','add', worktreePath, branchName],
...) or execFile('git', ['worktree','add','-b', branchName, worktreePath,
'HEAD'], ...)); ensure worktreePath is passed as an argument (not interpolated)
and preserve the gitEnv usage and existing logging/error handling in
createWorktreeForBranch and the corresponding switch-branch handlers.

Comment on lines +237 to +251
case 'file_contains': {
const filePath = criterion.config?.filePath;
const searchPattern = criterion.config?.searchPattern;
if (!filePath || !searchPattern) {
throw new Error('file_contains criterion requires filePath and searchPattern config');
}
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(workDir, filePath);
const content = await secureFs.readFile(fullPath, 'utf-8');
const regex = new RegExp(searchPattern);
passed = regex.test(content);
details = passed
? `File contains pattern: ${searchPattern}`
: `File does not contain pattern: ${searchPattern}`;
break;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

ReDoS vulnerability: Sanitize or validate regex patterns before constructing RegExp.

The searchPattern comes from user-provided criterion.config and is passed directly to new RegExp(). Malicious patterns like (a+)+$ can cause catastrophic backtracking.

🛡️ Proposed fix to validate regex safety
 case 'file_contains': {
   const filePath = criterion.config?.filePath;
   const searchPattern = criterion.config?.searchPattern;
   if (!filePath || !searchPattern) {
     throw new Error('file_contains criterion requires filePath and searchPattern config');
   }
   const fullPath = path.isAbsolute(filePath) ? filePath : path.join(workDir, filePath);
   const content = await secureFs.readFile(fullPath, 'utf-8');
-  const regex = new RegExp(searchPattern);
-  passed = regex.test(content);
+  // Use simple string includes for safety, or validate regex complexity
+  // Option 1: Simple string match (safest)
+  passed = content.includes(searchPattern);
+  // Option 2: If regex is required, add timeout or use safe-regex library
   details = passed
     ? `File contains pattern: ${searchPattern}`
     : `File does not contain pattern: ${searchPattern}`;
   break;
 }

If regex functionality is required, consider using a library like safe-regex to validate patterns before compilation, or implement a timeout wrapper around the regex execution.

📝 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
case 'file_contains': {
const filePath = criterion.config?.filePath;
const searchPattern = criterion.config?.searchPattern;
if (!filePath || !searchPattern) {
throw new Error('file_contains criterion requires filePath and searchPattern config');
}
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(workDir, filePath);
const content = await secureFs.readFile(fullPath, 'utf-8');
const regex = new RegExp(searchPattern);
passed = regex.test(content);
details = passed
? `File contains pattern: ${searchPattern}`
: `File does not contain pattern: ${searchPattern}`;
break;
}
case 'file_contains': {
const filePath = criterion.config?.filePath;
const searchPattern = criterion.config?.searchPattern;
if (!filePath || !searchPattern) {
throw new Error('file_contains criterion requires filePath and searchPattern config');
}
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(workDir, filePath);
const content = await secureFs.readFile(fullPath, 'utf-8');
// Use simple string includes for safety, or validate regex complexity
// Option 1: Simple string match (safest)
passed = content.includes(searchPattern);
// Option 2: If regex is required, add timeout or use safe-regex library
details = passed
? `File contains pattern: ${searchPattern}`
: `File does not contain pattern: ${searchPattern}`;
break;
}
🧰 Tools
🪛 ast-grep (0.40.5)

[warning] 244-244: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(searchPattern)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
In `@apps/server/src/services/ralph-loop-service.ts` around lines 237 - 251, The
file_contains branch creates a RegExp from untrusted searchPattern
(criterion.config?.searchPattern) which can cause ReDoS; before calling new
RegExp() in the case 'file_contains' block (where fullPath, content, regex,
passed, details are handled), validate or sanitize searchPattern (e.g., use a
safe-regex validator like safe-regex or implement a conservative
whitelist/escape strategy) and wrap regex compilation/execution in a try/catch
with a safe fallback (such as using a simple string.includes search on content
or rejecting overly-complex patterns); ensure any rejected pattern sets
passed/details appropriately and avoid unbounded regex execution (consider a
timeout wrapper or running the test in a worker).

Comment on lines +253 to +266
case 'command_succeeds': {
const command = criterion.config?.command;
if (!command) {
throw new Error('command_succeeds criterion requires command config');
}
const cwd = criterion.config?.cwd || workDir;
const { stdout, stderr } = await execAsync(command, {
cwd,
timeout: criterion.config?.timeout || 120000,
});
passed = true;
details = stdout || stderr || 'Command succeeded with no output';
break;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find RalphLoopConfig definition and usage
rg -n "RalphLoopConfig|completionCriteria" --type=ts -B2 -A2 | head -150

Repository: shaal/automaker

Length of output: 6137


🏁 Script executed:

# Check the ralph-loop-service file to understand the context
cd apps/server/src/services && head -50 ralph-loop-service.ts

Repository: shaal/automaker

Length of output: 1555


🏁 Script executed:

# Find where RalphLoopConfig is instantiated or loaded
rg -n "new RalphLoopConfig|RalphLoopConfig\s*=" --type=ts -B3 -A3

Repository: shaal/automaker

Length of output: 1039


🏁 Script executed:

# Check if completionCriteria comes from file, API, or external sources
rg -n "completionCriteria" --type=ts -B3 -A3

Repository: shaal/automaker

Length of output: 3639


🏁 Script executed:

cat -n apps/server/src/routes/ralph/routes/start.ts

Repository: shaal/automaker

Length of output: 1581


🏁 Script executed:

# Check CompletionCriterion type definition
rg -n "type CompletionCriterion|interface CompletionCriterion" --type=ts -A20 | head -80

Repository: shaal/automaker

Length of output: 1470


🏁 Script executed:

# Check if there's any validation before the config is used
rg -n "validator|validate|schema|joi|zod" apps/server/src/routes/ralph/ --type=ts -B2 -A2

Repository: shaal/automaker

Length of output: 1465


Fix command injection vulnerability in command_succeeds criterion.

The command parameter is executed directly via execAsync without validation. Since completionCriteria can be configured via the HTTP POST /start endpoint (lines 15-19 of apps/server/src/routes/ralph/routes/start.ts), an untrusted client can inject arbitrary shell commands.

Mitigations:

  • Add strict validation/sanitization for commands (allowlist approach preferred)
  • Consider disallowing command_succeeds entirely and using safer alternatives
  • If commands must be user-provided, require explicit allowlisting in configuration or environment
🤖 Prompt for AI Agents
In `@apps/server/src/services/ralph-loop-service.ts` around lines 253 - 266, The
code executes user-provided shell commands in the command_succeeds branch using
execAsync on criterion.config?.command (with cwd derived from workDir) which
creates a command injection risk; fix by disallowing raw user-supplied commands
from HTTP POST /start (or require explicit opt-in via server-side config),
replace execution of arbitrary strings with a safe allowlist of permitted
command names and arguments validated against that list, or remove/support only
pre-approved command handlers mapped to safe functions; update
ralph-loop-service.ts to validate criterion.type === 'command_succeeds' by
checking a server-managed allowlist (or feature flag) before calling execAsync,
and reject or error when an unapproved command is supplied.

Comment on lines +29 to +31
for (const child of childFeatures) {
if (child.status === 'done' || child.status === 'verified') {
stats.done++;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bug: Status 'done' doesn't exist - should be 'completed'.

Looking at the status types used elsewhere in this PR (e.g., use-board-column-features.ts), valid statuses include backlog, in_progress, waiting_approval, verified, and completed. There is no 'done' status, so completed features won't be counted correctly.

🐛 Proposed fix
   for (const child of childFeatures) {
-    if (child.status === 'done' || child.status === 'verified') {
+    if (child.status === 'completed' || child.status === 'verified') {
       stats.done++;
🤖 Prompt for AI Agents
In
`@apps/ui/src/components/views/board-view/components/kanban-card/epic-progress.tsx`
around lines 29 - 31, The loop over childFeatures is checking for a non-existent
status 'done' and incrementing stats.done, so change the condition to check for
'completed' (i.e., replace child.status === 'done' with child.status ===
'completed') and ensure you increment the matching stats property
(stats.completed) instead of stats.done; also verify the stats
object/initialization includes a completed key to avoid undefined increments.

Comment on lines +462 to +580

// ============================================================================
// Project Orchestration Paths
// ============================================================================

/**
* Get the projects directory for a project
*
* Contains project plans organized by project slug.
*
* @param projectPath - Absolute path to project directory
* @returns Absolute path to {projectPath}/.automaker/projects
*/
export function getProjectsDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), 'projects');
}

/**
* Get the directory for a specific project plan
*
* Contains project.md, milestones/ directory, and related files.
*
* @param projectPath - Absolute path to project directory
* @param projectSlug - URL-safe project identifier
* @returns Absolute path to {projectPath}/.automaker/projects/{projectSlug}
*/
export function getProjectDir(projectPath: string, projectSlug: string): string {
return path.join(getProjectsDir(projectPath), projectSlug);
}

/**
* Get the project.md file path for a project plan
*
* Stores the project overview, goals, and metadata.
*
* @param projectPath - Absolute path to project directory
* @param projectSlug - URL-safe project identifier
* @returns Absolute path to {projectPath}/.automaker/projects/{projectSlug}/project.md
*/
export function getProjectFilePath(projectPath: string, projectSlug: string): string {
return path.join(getProjectDir(projectPath, projectSlug), 'project.md');
}

/**
* Get the project.json file path for a project plan
*
* Stores the full project data including milestones and phases.
*
* @param projectPath - Absolute path to project directory
* @param projectSlug - URL-safe project identifier
* @returns Absolute path to {projectPath}/.automaker/projects/{projectSlug}/project.json
*/
export function getProjectJsonPath(projectPath: string, projectSlug: string): string {
return path.join(getProjectDir(projectPath, projectSlug), 'project.json');
}

/**
* Get the milestones directory for a project plan
*
* Contains subdirectories for each milestone.
*
* @param projectPath - Absolute path to project directory
* @param projectSlug - URL-safe project identifier
* @returns Absolute path to {projectPath}/.automaker/projects/{projectSlug}/milestones
*/
export function getMilestonesDir(projectPath: string, projectSlug: string): string {
return path.join(getProjectDir(projectPath, projectSlug), 'milestones');
}

/**
* Get the directory for a specific milestone
*
* Contains milestone.md and phase files.
*
* @param projectPath - Absolute path to project directory
* @param projectSlug - URL-safe project identifier
* @param milestoneSlug - URL-safe milestone identifier
* @returns Absolute path to {projectPath}/.automaker/projects/{projectSlug}/milestones/{milestoneSlug}
*/
export function getMilestoneDir(
projectPath: string,
projectSlug: string,
milestoneSlug: string
): string {
return path.join(getMilestonesDir(projectPath, projectSlug), milestoneSlug);
}

/**
* Get the milestone.md file path for a milestone
*
* Stores the milestone overview and metadata.
*
* @param projectPath - Absolute path to project directory
* @param projectSlug - URL-safe project identifier
* @param milestoneSlug - URL-safe milestone identifier
* @returns Absolute path to milestone.md
*/
export function getMilestoneFilePath(
projectPath: string,
projectSlug: string,
milestoneSlug: string
): string {
return path.join(getMilestoneDir(projectPath, projectSlug, milestoneSlug), 'milestone.md');
}

/**
* Create the projects directory structure for a project if it doesn't exist
*
* Creates {projectPath}/.automaker/projects with all subdirectories.
* Safe to call multiple times - uses recursive: true.
*
* @param projectPath - Absolute path to project directory
* @returns Promise resolving to the created projects directory path
*/
export async function ensureProjectsDir(projectPath: string): Promise<string> {
const projectsDir = getProjectsDir(projectPath);
await secureFs.mkdir(projectsDir, { recursive: true });
return projectsDir;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for duplicate function definitions between paths.ts and projects.ts
echo "=== Functions in paths.ts ==="
rg -n "^export (async )?function (get|ensure)" libs/platform/src/paths.ts

echo ""
echo "=== Functions in projects.ts ==="
rg -n "^export (async )?function (get|ensure)" libs/platform/src/projects.ts

Repository: shaal/automaker

Length of output: 4665


🏁 Script executed:

#!/bin/bash
# Extract the implementations of the duplicate functions to compare
echo "=== getProjectDir in paths.ts ==="
sed -n '488,500p' libs/platform/src/paths.ts

echo ""
echo "=== getProjectDir in projects.ts ==="
sed -n '93,104p' libs/platform/src/projects.ts

echo ""
echo "=== Check for validateSlugInput usage in projects.ts ==="
rg -B 2 -A 10 "validateSlugInput" libs/platform/src/projects.ts | head -40

echo ""
echo "=== Check for validateSlugInput usage in paths.ts ==="
rg -B 2 -A 10 "validateSlugInput" libs/platform/src/paths.ts | head -40

Repository: shaal/automaker

Length of output: 2373


🏁 Script executed:

#!/bin/bash
# Check all the duplicate functions mentioned in the review
echo "=== Checking getProjectsDir ==="
echo "paths.ts:"
sed -n '475,487p' libs/platform/src/paths.ts

echo ""
echo "projects.ts:"
sed -n '81,92p' libs/platform/src/projects.ts

echo ""
echo "=== Checking ensureProjectDir ==="
echo "paths.ts:"
sed -n '592,606p' libs/platform/src/paths.ts

echo ""
echo "projects.ts:"
sed -n '285,305p' libs/platform/src/projects.ts

echo ""
echo "=== List all exported functions in both files ==="
echo "paths.ts project section (line 475-680):"
rg -n "^export" libs/platform/src/paths.ts | awk '$1 >= 475 && $1 <= 680'

echo ""
echo "projects.ts full file:"
rg -n "^export" libs/platform/src/projects.ts

Repository: shaal/automaker

Length of output: 5298


🏁 Script executed:

#!/bin/bash
# Verify more duplicate functions and their validation status
echo "=== getMilestoneDir comparison ==="
echo "paths.ts (541-558):"
sed -n '541,558p' libs/platform/src/paths.ts

echo ""
echo "projects.ts (162-178):"
sed -n '162,178p' libs/platform/src/projects.ts

echo ""
echo "=== getMilestoneFilePath comparison ==="
echo "paths.ts (559-575):"
sed -n '559,575p' libs/platform/src/paths.ts

echo ""
echo "projects.ts (179-198):"
sed -n '179,198p' libs/platform/src/projects.ts

echo ""
echo "=== ensureMilestoneDir comparison ==="
echo "paths.ts (607-628):"
sed -n '607,628p' libs/platform/src/paths.ts

echo ""
echo "projects.ts (306-321):"
sed -n '306,321p' libs/platform/src/projects.ts

Repository: shaal/automaker

Length of output: 3896


🏁 Script executed:

#!/bin/bash
# Check remaining functions to complete the comparison
echo "=== getProjectFilePath comparison ==="
echo "paths.ts (501-513):"
sed -n '501,513p' libs/platform/src/paths.ts

echo ""
echo "projects.ts (105-115):"
sed -n '105,115p' libs/platform/src/projects.ts

echo ""
echo "=== getMilestonesDir comparison ==="
echo "paths.ts (527-540):"
sed -n '527,540p' libs/platform/src/paths.ts

echo ""
echo "projects.ts (149-161):"
sed -n '149,161p' libs/platform/src/projects.ts

echo ""
echo "=== Check which functions accept slug parameters in paths.ts ==="
rg -B 3 "projectSlug|milestoneSlug" libs/platform/src/paths.ts | grep -E "export.*function|projectSlug|milestoneSlug" | head -30

Repository: shaal/automaker

Length of output: 4212


🏁 Script executed:

#!/bin/bash
# Check imports and usage to understand impact
echo "=== Who imports from paths.ts ==="
rg "from.*paths" --type ts | grep -v node_modules | head -20

echo ""
echo "=== Who imports from projects.ts ==="
rg "from.*projects" --type ts | grep -v node_modules | head -20

echo ""
echo "=== Check if paths.ts functions are re-exported ==="
grep -n "export.*from" libs/platform/src/paths.ts

Repository: shaal/automaker

Length of output: 3459


Remove duplicate path functions from paths.ts—consolidate to validated versions in projects.ts.

Functions getProjectDir, getProjectFilePath, getProjectJsonPath, getMilestoneDir, getMilestoneFilePath, ensureMilestoneDir, and related path helpers are duplicated between paths.ts and projects.ts. The projects.ts versions validate slug inputs via validateSlugInput to prevent path traversal attacks, while paths.ts versions lack this protection. This creates:

  1. Security inconsistency—unsecured path construction available in public API
  2. Maintenance burden—duplicate code in two files
  3. Developer confusion—which import to use?

Consolidate to the validated implementations in projects.ts, or add validateSlugInput calls to all slug-accepting functions in paths.ts.

🤖 Prompt for AI Agents
In `@libs/platform/src/paths.ts` around lines 462 - 580, The duplicated,
unvalidated path helpers in paths.ts (getProjectDir, getProjectFilePath,
getProjectJsonPath, getMilestoneDir, getMilestoneFilePath, ensureProjectsDir and
any other slug-accepting helpers) must be consolidated to the validated
implementations in projects.ts: remove the duplicate implementations from
paths.ts and either re-export or delegate to the projects.ts functions that call
validateSlugInput so all public APIs use the slug-validated logic; if you prefer
to keep implementations here instead, add validateSlugInput calls to each
slug-accepting function (e.g., getProjectDir, getMilestoneDir) and mirror
projects.ts behavior to prevent path traversal.

Comment on lines +13 to +16
"env": {
"AUTOMAKER_API_URL": "http://localhost:3008",
"AUTOMAKER_API_KEY": "automaker-dev-key-2026"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid embedding the API key in the plugin manifest.
This file is shipped with the plugin, so the fixed key risks reuse and accidental exposure. Prefer reading from the user’s environment or a local, git‑ignored override.

🔐 Suggested change (remove embedded key)
       "env": {
         "AUTOMAKER_API_URL": "http://localhost:3008",
-        "AUTOMAKER_API_KEY": "automaker-dev-key-2026"
       }
📝 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
"env": {
"AUTOMAKER_API_URL": "http://localhost:3008",
"AUTOMAKER_API_KEY": "automaker-dev-key-2026"
}
"env": {
"AUTOMAKER_API_URL": "http://localhost:3008"
}
🤖 Prompt for AI Agents
In `@packages/mcp-server/plugins/automaker/.claude-plugin/plugin.json` around
lines 13 - 16, The plugin manifest embeds a production API key under the "env"
object (AUTOMAKER_API_KEY), which must be removed; delete the hard-coded
AUTOMAKER_API_KEY value from the "env" section in plugin.json and instead make
the plugin read AUTOMAKER_API_KEY from the runtime environment or a git-ignored
local override (e.g. .env) at startup, leaving only a placeholder or removing
the key entry entirely while keeping AUTOMAKER_API_URL if needed; update any
README or configuration docs referenced by the plugin to instruct users to set
AUTOMAKER_API_KEY in their environment and ensure no real keys are checked into
source control.

Comment on lines +24 to +28
// Configuration
// Note: Hardcoded fallback because Claude Code doesn't pass env vars to MCP servers
const API_URL = process.env.AUTOMAKER_API_URL || 'http://localhost:3008';
const API_KEY = process.env.AUTOMAKER_API_KEY || 'automaker-dev-key-2026';

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Security concern: Hardcoded API key fallback.

The fallback API key automaker-dev-key-2026 is embedded in the source code. If this package is published to npm, the key will be exposed. Consider:

  1. Requiring AUTOMAKER_API_KEY to be set (fail fast if missing)
  2. Using a clearly invalid placeholder that won't work
  3. Documenting that the fallback is development-only
🔒 Suggested fix
 const API_URL = process.env.AUTOMAKER_API_URL || 'http://localhost:3008';
-const API_KEY = process.env.AUTOMAKER_API_KEY || 'automaker-dev-key-2026';
+const API_KEY = process.env.AUTOMAKER_API_KEY;
+
+if (!API_KEY) {
+  console.error('[MCP] Warning: AUTOMAKER_API_KEY not set. API calls will fail authentication.');
+}
📝 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
// Configuration
// Note: Hardcoded fallback because Claude Code doesn't pass env vars to MCP servers
const API_URL = process.env.AUTOMAKER_API_URL || 'http://localhost:3008';
const API_KEY = process.env.AUTOMAKER_API_KEY || 'automaker-dev-key-2026';
// Configuration
// Note: Hardcoded fallback because Claude Code doesn't pass env vars to MCP servers
const API_URL = process.env.AUTOMAKER_API_URL || 'http://localhost:3008';
const API_KEY = process.env.AUTOMAKER_API_KEY;
if (!API_KEY) {
console.error('[MCP] Warning: AUTOMAKER_API_KEY not set. API calls will fail authentication.');
}
🤖 Prompt for AI Agents
In `@packages/mcp-server/src/index.ts` around lines 24 - 28, Replace the hardcoded
fallback for AUTOMAKER_API_KEY: remove the literal 'automaker-dev-key-2026' and
instead require process.env.AUTOMAKER_API_KEY be provided (fail fast by throwing
an Error) when API_KEY is missing in non-development environments; if you need a
dev-only fallback, only set a clearly invalid placeholder (e.g.
'INVALID_AUTOMAKER_KEY') when NODE_ENV === 'development' and log a
warning—update the API_KEY constant and add the guard/throw near where API_KEY
and API_URL are defined to ensure the code uses API_KEY (symbol: API_KEY, env
name: AUTOMAKER_API_KEY).

@mabry1985 mabry1985 closed this Feb 5, 2026
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.

1 participant