Conversation
- 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>
Fix/docker production setup
- 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
📝 WalkthroughWalkthroughThis 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
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 | 🔴 CriticalBug:
/api/projectsroute 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 | 🟡 MinorCalling
useAppStore.getState()insideuseMemobypasses React's reactivity.Changes to
worktreesLoadingByProjectwon't trigger a re-render since it's not in the dependency array. The current code relies oncurrentWorktreeBranchchanging 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
useMemodependency array to ensure proper re-computation when loading state changes.packages/mcp-server/README.md-160-162 (1)
160-162:⚠️ Potential issue | 🟡 MinorUpdate 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 | 🟡 MinorAlign the startup command (
devvsdev:web).
Quick Start usesnpm run dev:web, but prerequisites saynpm 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 | 🟡 MinorAdd 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 | 🟡 MinorAdd 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 | 🟡 MinorAdd language tags to fenced blocks for markdownlint.
Several fenced blocks are missing language identifiers; MD040 will keep warning until they’re labeled. Addtext/json/markdownas 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 | 🟡 MinorAdd 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 | 🟡 MinorAdd 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 | 🟡 MinorTests 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 | 🟡 MinorUse 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 | 🟡 MinorDead code:
enhancedDescriptionis computed but never used.The variable
enhancedDescriptionis assigned but not passed toexecuteFeature(). 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
executeFeatureto 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 | 🟡 MinorPotential 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 | 🟡 MinorMissing 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 | 🟡 MinorSlug generators could produce empty strings.
If
titlecontains only special characters (e.g.,"!!!"or" "), thegenerateProjectSlug,generateMilestoneSlug, andgeneratePhaseSlugfunctions will return an empty string (or just the number prefix for milestones). An empty slug would fail theVALID_SLUG_PATTERNvalidation 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 | 🟡 MinorPotential undefined access when milestone has no phases.
Line 257 accesses
milestone.phases[0]?.namewith optional chaining, which is good. However, ifphasesarray is empty, the generated "Next" link will point tophase-01-start.mdwhich 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 | 🟡 MinorProject object is mutated in-place but update only persists
status.Lines 217, 243-244 mutate
milestone.epicIdandphase.featureIdon theprojectobject. However,updateProjectat Line 249 only updates thestatusfield, while Line 254-255 re-saves the entire mutatedprojectobject 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:Layersis imported but not used in this file.The
Layersicon is used inepic-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: Addenginesfield andlicensefor 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 movingvitest/globalsto a test‑only tsconfig.
Including test globals in the production build config can mask global name collisions insrc. A separatetsconfig.test.jsonis 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
```textfor ASCII diagrams (lines 38, 68)- Use
```javascriptor```jsfor API call examples (lines 62, 105, 115, etc.)- Use
```yamlfor 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 -ebut curl won't fail on HTTP errors (4xx/5xx) without the-fflag. 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
textorplaintextas 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
textfor function calls andyamlfor 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
textorplaintextfor 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
textfor 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 inlinerequire.Using
requireinside a function body is non-idiomatic for ES modules. Consider importingexecSyncat 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
getStatusfor 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.prdis explicitly set tonullorundefinedto clear the PRD, theprd.mdfile is not deleted or cleared. This may leave stale PRD content on disk. TheresearchSummaryhandling 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
textorplaintextas 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.joinPathif 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
textorplaintextas 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
textorplaintext.📝 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
EventEmitteris created for progress tracking but:
- No subscriber is attached to forward events to the client
- 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_checktool uses directfetchwithout theX-API-Keyheader, while all other tools useapiCall. 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
updatesinto theProjectobject could allow callers to overwrite internal fields likeslug,createdAt, ormilestonesunexpectedly. Consider explicitly picking allowed fields fromUpdateProjectInput.♻️ 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
CompletionCriterioninterface has a singleconfigobject where all properties are optional. This allows invalid combinations (e.g.,type: 'tests_pass'withurlproperty). 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 } // ... etclibs/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' : '')checkstrimmedbut appendsline, 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 assertionas any[]on readdir results.Lines 331, 354, and 382 cast
readdirresults toany[]. IfsecureFs.readdirreturns properly typedDirent[]whenwithFileTypes: trueis 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
secureFstyping is incomplete, consider augmenting its type definitions instead.Also applies to: 354-358, 382-388
| "env": { | ||
| "AUTOMAKER_API_URL": "http://localhost:3008", | ||
| "AUTOMAKER_API_KEY": "automaker-dev-key-2026" | ||
| } |
There was a problem hiding this comment.
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.
| "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.
| 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`; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat apps/server/src/lib/debug-log.tsRepository: shaal/automaker
Length of output: 1563
🏁 Script executed:
rg -n "debugLog\(" --type ts --type js -B2 -A2Repository: 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.
| 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.
| try { | ||
| fs.appendFileSync(DEBUG_LOG_PATH, line); | ||
| } catch (err) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/server/src/lib/debug-log.tsRepository: shaal/automaker
Length of output: 1976
🏁 Script executed:
rg -r '$1' 'import\s+.*debugLog' --no-headingRepository: shaal/automaker
Length of output: 216
🏁 Script executed:
rg 'debugLog\(' --type ts --type js -B 2 -A 1 | head -100Repository: shaal/automaker
Length of output: 2565
🏁 Script executed:
cat -n apps/server/src/routes/features/routes/list.tsRepository: 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.
| 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.
| /** | ||
| * 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/server/src/services/auto-mode-service.ts | head -100Repository: shaal/automaker
Length of output: 3773
🏁 Script executed:
wc -l apps/server/src/services/auto-mode-service.tsRepository: 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.tsRepository: 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 3Repository: 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 -100Repository: 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.tsRepository: 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 -50Repository: 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 -30Repository: 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 -60Repository: shaal/automaker
Length of output: 164
🏁 Script executed:
# Find Feature interface definition
rg 'type Feature|interface Feature' apps/server/src -A 20 | head -80Repository: 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 -80Repository: 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 1Repository: 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.tsRepository: 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.
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| 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).
| 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; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find RalphLoopConfig definition and usage
rg -n "RalphLoopConfig|completionCriteria" --type=ts -B2 -A2 | head -150Repository: 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.tsRepository: shaal/automaker
Length of output: 1555
🏁 Script executed:
# Find where RalphLoopConfig is instantiated or loaded
rg -n "new RalphLoopConfig|RalphLoopConfig\s*=" --type=ts -B3 -A3Repository: shaal/automaker
Length of output: 1039
🏁 Script executed:
# Check if completionCriteria comes from file, API, or external sources
rg -n "completionCriteria" --type=ts -B3 -A3Repository: shaal/automaker
Length of output: 3639
🏁 Script executed:
cat -n apps/server/src/routes/ralph/routes/start.tsRepository: shaal/automaker
Length of output: 1581
🏁 Script executed:
# Check CompletionCriterion type definition
rg -n "type CompletionCriterion|interface CompletionCriterion" --type=ts -A20 | head -80Repository: 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 -A2Repository: 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_succeedsentirely 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.
| for (const child of childFeatures) { | ||
| if (child.status === 'done' || child.status === 'verified') { | ||
| stats.done++; |
There was a problem hiding this comment.
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.
|
|
||
| // ============================================================================ | ||
| // 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; | ||
| } |
There was a problem hiding this comment.
🧩 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.tsRepository: 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 -40Repository: 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.tsRepository: 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.tsRepository: 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 -30Repository: 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.tsRepository: 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:
- Security inconsistency—unsecured path construction available in public API
- Maintenance burden—duplicate code in two files
- 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.
| "env": { | ||
| "AUTOMAKER_API_URL": "http://localhost:3008", | ||
| "AUTOMAKER_API_KEY": "automaker-dev-key-2026" | ||
| } |
There was a problem hiding this comment.
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.
| "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.
| // 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'; | ||
|
|
There was a problem hiding this comment.
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:
- Requiring
AUTOMAKER_API_KEYto be set (fail fast if missing) - Using a clearly invalid placeholder that won't work
- 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.
| // 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).
Summary by CodeRabbit
Release Notes
New Features
Improvements