Skip to content

[WIP] feat: add mini-muse - AI web app generator example#1

Open
jerryliang64 wants to merge 9 commits intomasterfrom
feat/mini-muse
Open

[WIP] feat: add mini-muse - AI web app generator example#1
jerryliang64 wants to merge 9 commits intomasterfrom
feat/mini-muse

Conversation

@jerryliang64
Copy link
Collaborator

@jerryliang64 jerryliang64 commented Mar 1, 2026

image

Summary

  • Add mini-muse: an AI-powered web application generator using Claude as an AI agent
  • Fullstack example with Egg.js 3 + TypeScript backend and React 18 + Vite + Tailwind frontend
  • Demonstrates AI agent loop with structured tool use, real-time SSE streaming, and live preview

Project Structure

mini-muse/
├── app/              # Egg.js backend (controllers, services, tools)
├── config/           # Egg.js configuration
├── docs/             # Design documentation
├── frontend/         # React + Vite frontend
├── .env.example      # Environment variable template
└── package.json

Key Features

  • Natural language → complete web app generation
  • AI agent loop with tool calls (analyze, plan, generate, validate)
  • Real-time progress streaming via SSE
  • Live preview and project file download

Checklist

  • No sensitive information (API keys, internal URLs) included
  • .env.example provided as template
  • README with quick start guide
  • Detailed design documentation in docs/design.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • AI-powered React + Tailwind project generator with real-time progress streaming
    • Live project preview directly in the browser
    • Interactive chat interface for modifying generated projects
    • File browser and code viewer for viewing project contents
    • One-click project download as ZIP archive
  • Documentation

    • Added comprehensive README with setup and quick start instructions
    • Added project architecture and design documentation
  • Chores

    • Added environment configuration templates
    • Added build and development configurations

Mini-Muse is a fullstack AI agent example that generates complete
React + Tailwind web applications from natural language descriptions.

Tech stack:
- Backend: Egg.js 3 + TypeScript
- Frontend: React 18 + Vite + Tailwind CSS
- AI: Anthropic Claude API with tool use

Features:
- AI agent loop with structured tool calls
- Real-time progress streaming via SSE
- Live preview and project download
@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Warning

Rate limit exceeded

@jerryliang64 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8406da41-585c-48af-bb40-3ce4e6cb2e9e

📥 Commits

Reviewing files that changed from the base of the PR and between d806686 and e582d47.

📒 Files selected for processing (13)
  • mini-muse/app/lib/tools/analyzer/analyzeRequirements.ts
  • mini-muse/app/lib/tools/analyzer/planArchitecture.ts
  • mini-muse/app/lib/tools/generator/createComponent.ts
  • mini-muse/app/lib/tools/generator/createConfig.ts
  • mini-muse/app/lib/tools/generator/createFile.ts
  • mini-muse/app/lib/tools/generator/createHook.ts
  • mini-muse/app/lib/tools/generator/createPage.ts
  • mini-muse/app/lib/tools/generator/createStyle.ts
  • mini-muse/app/lib/tools/generator/deleteFile.ts
  • mini-muse/app/lib/tools/reader/readFile.ts
  • mini-muse/app/lib/tools/validator/validateProject.ts
  • mini-muse/app/service/orchestrator.ts
  • mini-muse/app/service/tools.ts
📝 Walkthrough

Walkthrough

This pull request establishes a complete full-stack AI-powered web application generator using Claude. It introduces backend infrastructure with Egg.js and TypeScript, a React-based frontend with Tailwind CSS, AI orchestration via the Claude Agent SDK, a comprehensive tool system for code generation and project management, and supporting services for project preview and session tracking.

Changes

Cohort / File(s) Summary
Project Configuration
package.json, tsconfig.json, .gitignore, .env.example
Core project metadata, TypeScript compilation settings, environment variables, and git exclusions for a full-stack Node.js + React application.
Documentation
README.md, CLAUDE.md, docs/design.md
Project overview, user guide for setup and features, AI-assisted documentation sync workflow, and comprehensive system architecture documentation.
Backend Framework Setup
config/config.default.ts, config/config.local.ts, config/plugin.ts, app/middleware/errorHandler.ts, app/service/package.json
Egg.js configuration with environment variables, local overrides, Tegg plugin registration, API error handling middleware, and service package metadata.
AI Orchestration
app/lib/prompts.ts, app/service/orchestrator.ts, app/controller/MuseAgentController.ts
System and modification prompts for Claude, orchestrator service managing iterative AI agent loops and streaming, and agent handler controller integrating with OSS storage.
Tool System Infrastructure
app/lib/tools/registry.ts, app/lib/utils.ts
Centralized tool executor dispatch registry, shared file I/O and code formatting utilities, and string casing helpers for code generation.
Analysis & Planning Tools
app/lib/tools/analyzer/analyzeRequirements.ts, app/lib/tools/analyzer/planArchitecture.ts
Requirements analysis tool defining app structure and features, and architecture planning tool generating file creation order and directory layout.
Code Generation Tools
app/lib/tools/generator/createComponent.ts, app/lib/tools/generator/createPage.ts, app/lib/tools/generator/createHook.ts, app/lib/tools/generator/createFile.ts, app/lib/tools/generator/createConfig.ts, app/lib/tools/generator/createStyle.ts
Generators for React components, pages, custom hooks, generic files, project configuration files, and CSS styles with automatic formatting and barrel exports.
Project Management Tools
app/lib/tools/reader/readFile.ts, app/lib/tools/generator/deleteFile.ts, app/lib/tools/validator/validateProject.ts
File reading with path traversal protection, file deletion with safety guards, and multi-check project validation (files, structure, imports, type annotations).
Backend Services
app/service/tools.ts, app/service/preview.ts
Tool service wiring all generators/validators into MCP servers for Claude integration, and preview service managing live development server startup and port management.
Backend Controllers
app/controller/HomeController.ts, app/controller/ProjectController.ts
SPA fallback serving frontend assets and project file listing, content retrieval, ZIP download, and preview management endpoints.
Frontend Entry & Routing
frontend/src/main.tsx, frontend/src/App.tsx
React 18 application bootstrap with React Router setup and routing configuration for home, progress, and result pages.
Frontend Pages
frontend/src/pages/HomePage.tsx, frontend/src/pages/ProgressPage.tsx, frontend/src/pages/ResultPage.tsx
Home page for project creation and session history, progress tracking page with event logging, and result page with file explorer, code preview, live preview, and chat-based modifications.
Frontend Components
frontend/src/components/FileTree.tsx, frontend/src/components/CodePreview.tsx, frontend/src/components/ProgressLog.tsx, frontend/src/components/ChatPanel.tsx
Hierarchical file tree navigator, syntax-highlighted code viewer with line numbers, progress event log with styling, and interactive chat interface for project modifications.
Frontend Services & Types
frontend/src/services/api.ts, frontend/src/services/sessionHistory.ts, frontend/src/types/index.ts
Comprehensive API client with SSE streaming, session persistence via localStorage, and TypeScript interfaces for agents, messages, progress events, and API responses.
Frontend Build Configuration
frontend/package.json, frontend/tsconfig.json, frontend/vite.config.ts, frontend/tailwind.config.js, frontend/postcss.config.js, frontend/src/index.css, frontend/index.html
Frontend dependencies, TypeScript configuration, Vite bundler setup with API proxy, Tailwind CSS framework configuration, and global styling.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (Browser)
    participant API as API Server<br/>(Egg.js)
    participant Orchestrator as Orchestrator<br/>Service
    participant Claude as Claude Agent<br/>SDK
    participant Tools as Tool System<br/>(MCP)
    participant FileSystem as File System<br/>& OSS

    Client->>API: POST /api/v1/runs/stream<br/>(description, appName)
    API->>Orchestrator: agentLoop(params)
    
    Orchestrator->>Claude: Create streaming query<br/>(systemPrompt, userPrompt)
    Orchestrator-->>Client: Stream: thread.run.created<br/>(threadId)
    
    loop Agent Iteration
        Claude->>Tools: Request tool execution<br/>(analyze_requirements,<br/>plan_architecture, etc.)
        Tools->>FileSystem: Create files, read,<br/>validate project
        Tools-->>Claude: Tool result (success/fail)
        Orchestrator-->>Client: Stream: tool_use,<br/>tool_result events
    end
    
    Claude->>Claude: Generate code files
    Claude->>Tools: create_component,<br/>create_page, create_config
    Tools->>FileSystem: Write generated code
    Orchestrator-->>Client: Stream: file_created<br/>events
    
    Claude-->>Orchestrator: Completion signal
    Orchestrator-->>Client: Stream: completed event
    Client->>API: GET /api/v1/projects/{threadId}/files
    API->>FileSystem: List generated files
    API-->>Client: File list
    Client->>Client: Render FileTree,<br/>CodePreview, Preview
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A rabbit hops through code so bright,
With Claude and React, what a sight!
Components bloom and schemas dance,
While AI agents build their stance.
From tools to streams, from files to views,
Mini-Muse crafts projects anew!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title '[WIP] feat: add mini-muse - AI web app generator example' directly and clearly describes the main change: adding a new mini-muse project that is an AI-powered web application generator.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/mini-muse

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

socket-security bot commented Mar 1, 2026

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm js-beautify is 100.0% likely obfuscated

Confidence: 1.00

Location: Package overview

From: ?npm/egg@4.1.2-beta.3npm/@eggjs/tegg@4.0.2-beta.3npm/js-beautify@1.15.4

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/js-beautify@1.15.4. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: mini-muse/frontend/package.jsonnpm/vite@5.4.21

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@5.4.21. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a comprehensive example application, 'Mini-Muse', demonstrating an AI-powered web application generator. It provides a full-stack solution where users can describe a desired web application in natural language, and an AI agent, powered by Claude, will generate the complete project. The system supports a structured generation workflow, real-time progress updates, and interactive features for reviewing, previewing, and iteratively modifying the generated code, showcasing advanced AI integration in development tools.

Highlights

  • New AI Web App Generator Example: Introduced 'Mini-Muse', a full-stack example application that leverages Claude as an AI agent to generate complete React + TypeScript web applications from natural language descriptions.
  • AI Agent Orchestration: Implemented a sophisticated AI agent loop with structured tool use, enabling phases like requirements analysis, architecture planning, code generation (components, pages, hooks, configs, styles), and project validation.
  • Real-time User Experience: Integrated Server-Sent Events (SSE) for real-time progress streaming during the generation process, providing immediate feedback to the user.
  • Interactive Project Management: Developed features for live preview of generated applications, file browsing, code content viewing, project ZIP download, and a 'Human-in-the-Loop' chat interface for iterative modifications to the generated code.
  • Full-stack Implementation: The backend is built with Egg.js 3 and TypeScript, handling AI orchestration, task management, and preview server control. The frontend is a React 18 SPA with Vite and Tailwind CSS, providing the user interface for interaction.
Changelog
  • mini-muse/.env.example
    • Added example environment variables for Anthropic API configuration.
  • mini-muse/CLAUDE.md
    • Added project specifications and documentation synchronization requirements for Claude Code.
  • mini-muse/README.md
    • Added a comprehensive README detailing Mini-Muse's features, tech stack, and quick start instructions.
  • mini-muse/app/controller/generate.ts
    • Added controller for handling all AI generation-related API endpoints, including task creation, status streaming, file operations, download, preview management, and modification requests.
  • mini-muse/app/controller/home.ts
    • Added controller to serve the frontend's index.html or a fallback message.
  • mini-muse/app/lib/prompts.ts
    • Added system and user prompt definitions for the AI agent, including specific prompts for initial generation and modification modes.
  • mini-muse/app/lib/tools/analyzer/analyzeRequirements.ts
    • Added an AI tool for analyzing user requirements and extracting structured application details.
  • mini-muse/app/lib/tools/analyzer/planArchitecture.ts
    • Added an AI tool for planning the application's architecture and file creation order based on analyzed requirements.
  • mini-muse/app/lib/tools/generator/createComponent.ts
    • Added an AI tool for generating React components with associated styles and barrel exports.
  • mini-muse/app/lib/tools/generator/createConfig.ts
    • Added an AI tool for generating various project configuration files like package.json, tsconfig.json, vite.config.ts, and index.html.
  • mini-muse/app/lib/tools/generator/createFile.ts
    • Added a generic AI tool for creating files with specified content and optional formatting.
  • mini-muse/app/lib/tools/generator/createHook.ts
    • Added an AI tool for generating custom React hooks.
  • mini-muse/app/lib/tools/generator/createPage.ts
    • Added an AI tool for generating React page components with routing paths and associated styles.
  • mini-muse/app/lib/tools/generator/createStyle.ts
    • Added an AI tool for generating CSS style files.
  • mini-muse/app/lib/tools/generator/deleteFile.ts
    • Added an AI tool for deleting files from the generated project.
  • mini-muse/app/lib/tools/reader/readFile.ts
    • Added an AI tool for reading the content of existing project files.
  • mini-muse/app/lib/tools/registry.ts
    • Added a central registry for all AI tools, including their definitions and execution logic.
  • mini-muse/app/lib/tools/validator/validateProject.ts
    • Added an AI tool for validating the completeness and correctness of the generated project, including file presence, imports, and type annotations.
  • mini-muse/app/lib/utils.ts
    • Added utility functions for file system operations, code formatting with Prettier, and string case conversions.
  • mini-muse/app/middleware/errorHandler.ts
    • Added an error handling middleware for API routes to provide consistent error responses.
  • mini-muse/app/router.ts
    • Added all API routes for the Mini-Muse application and configured a SPA fallback for frontend routing.
  • mini-muse/app/service/aiClient.ts
    • Added a service to encapsulate interactions with the Anthropic AI SDK, supporting custom base URLs and authentication.
  • mini-muse/app/service/orchestrator.ts
    • Added the core AI agent orchestrator service, managing the agent's iterative loop, tool execution, and progress updates for both initial generation and modifications.
  • mini-muse/app/service/preview.ts
    • Added a service to manage the lifecycle of the generated project's preview development server, including dependency installation and port management.
  • mini-muse/app/service/taskManager.ts
    • Added a service to manage generation tasks, storing their status, progress events, and chat history in memory.
  • mini-muse/app/service/tools.ts
    • Added a service to provide an interface for accessing and executing the registered AI tools.
  • mini-muse/config/config.default.ts
    • Added default Egg.js configuration for Mini-Muse, including output directory, AI model, and iteration limits.
  • mini-muse/config/config.local.ts
    • Added local Egg.js configuration.
  • mini-muse/config/plugin.ts
    • Added Egg.js plugin configuration.
  • mini-muse/docs/design.md
    • Added a detailed design document outlining the project's overview, system architecture, technology stack, backend/frontend modules, and key design decisions.
  • mini-muse/frontend/index.html
    • Added the main HTML file for the React frontend.
  • mini-muse/frontend/package.json
    • Added frontend package dependencies, including React, Vite, and Tailwind CSS.
  • mini-muse/frontend/postcss.config.js
    • Added PostCSS configuration for Tailwind CSS.
  • mini-muse/frontend/src/App.tsx
    • Added the main React application component, setting up routing for different pages.
  • mini-muse/frontend/src/components/ChatPanel.tsx
    • Added a React component for the interactive chat panel, enabling users to send modification instructions and view real-time progress.
  • mini-muse/frontend/src/components/CodePreview.tsx
    • Added a React component to display code content with line numbers and language labels.
  • mini-muse/frontend/src/components/FileTree.tsx
    • Added a React component to render an interactive file tree for the generated project.
  • mini-muse/frontend/src/components/ProgressLog.tsx
    • Added a React component to display a scrollable log of generation progress events.
  • mini-muse/frontend/src/index.css
    • Added the main CSS file, including Tailwind CSS directives.
  • mini-muse/frontend/src/main.tsx
    • Added the entry point for the React application.
  • mini-muse/frontend/src/pages/HomePage.tsx
    • Added a React page for users to input the desired application description and name.
  • mini-muse/frontend/src/pages/ProgressPage.tsx
    • Added a React page to display the real-time generation progress using SSE.
  • mini-muse/frontend/src/pages/ResultPage.tsx
    • Added a React page to display the generated project, featuring a file tree, code preview, live preview, and chat modification panel.
  • mini-muse/frontend/src/services/api.ts
    • Added a frontend service for making API calls to the backend, including task creation, progress subscription, file retrieval, and preview management.
  • mini-muse/frontend/src/types/index.ts
    • Added shared TypeScript interfaces and types used across the frontend and for API responses.
  • mini-muse/frontend/tailwind.config.js
    • Added Tailwind CSS configuration for the frontend project.
  • mini-muse/frontend/tsconfig.json
    • Added TypeScript configuration for the frontend project.
  • mini-muse/frontend/vite.config.ts
    • Added Vite configuration for the frontend, including React plugin, build output directory, and API proxy settings.
  • mini-muse/package.json
    • Added root package.json for the Mini-Muse backend, including dependencies for Egg.js, Anthropic SDK, and development tools.
  • mini-muse/tsconfig.json
    • Added root TypeScript configuration for the backend project.
Activity
  • This pull request introduces a new feature, the 'Mini-Muse' AI web app generator example. All changes are part of the initial implementation of this feature.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an impressive full-stack example application, mini-muse, which functions as an AI-powered web app generator. The backend is built with Egg.js and the frontend with React. The overall architecture is well-documented and follows a clear separation of concerns. The use of an AI agent with a tool-based workflow is a great demonstration.

My review focuses on improving security, type safety, and error handling. I've identified a few high-severity security issues in the default configuration, such as a hardcoded secret key and disabled CSRF protection. I've also pointed out several places where type safety could be improved (e.g., using specific types instead of unknown[] and avoiding unsafe casts) and where error handling could be more robust by logging errors instead of swallowing them in empty catch blocks. These changes will make the example more secure and resilient.

outputDir: string;
filesCreated: string[];
progress: ProgressEvent[];
messages: unknown[];

Choose a reason for hiding this comment

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

high

Using unknown[] for the messages property is not type-safe and leads to unsafe casting in other parts of the application (e.g., orchestrator.ts). You should use a specific type for the messages to leverage TypeScript's type safety. I'd recommend importing the Message type from ./aiClient and using it here.

Suggested change
messages: unknown[];
messages: Message[];

export default () => {
const config = {} as PowerPartial<EggAppConfig>;

config.keys = 'mini-muse-secret-key';

Choose a reason for hiding this comment

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

high

Hardcoding secret keys in a default configuration file is a significant security risk, as it could be accidentally committed to version control. This key should be loaded from environment variables or a per-environment configuration file that is not checked into git. The .env file should be added to .gitignore.

Suggested change
config.keys = 'mini-muse-secret-key';
config.keys = process.env.EGG_KEYS || 'mini-muse-secret-key';

Comment on lines +19 to +21
csrf: {
enable: false,
},

Choose a reason for hiding this comment

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

high

Disabling CSRF protection is a security risk for any web application that handles state-changing requests from a browser. Since this application serves a UI, it's recommended to keep CSRF protection enabled. If you are using token-based authentication for your API, ensure that it's correctly implemented for all state-changing endpoints.

      enable: true,

Comment on lines +185 to +193
} catch {
ctx.body = {
success: true,
data: {
taskId: task.id,
files: [],
},
};
}

Choose a reason for hiding this comment

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

medium

This catch block swallows any error from listFiles and returns a successful response with an empty array. This can be misleading for the client, as it hides potential issues like file system permissions errors. The error should be logged, and an appropriate error response should be sent to the client.

    } catch (err) {
      ctx.logger.error('Failed to list files for task %s: %s', taskId, err);
      ctx.status = 500;
      ctx.body = {
        success: false,
        error: 'Failed to retrieve file list.',
      };
    }

- Has clean, readable formatting
- Includes meaningful comments for complex logic
- Uses semantic HTML and accessible patterns
- Implements responsive design with CSS modules or styled-components

Choose a reason for hiding this comment

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

medium

The prompt specifies using CSS modules or styled-components, but the generated project uses Tailwind CSS. The prompt should be updated to reflect the actual technology stack being used to avoid confusion and ensure the AI generates code consistent with the project's setup.

Suggested change
- Implements responsive design with CSS modules or styled-components
- Implements responsive design with Tailwind CSS

input: Record<string, unknown>,
_outputDir: string
): Promise<ToolResult> {
const data = input as unknown as AnalyzeRequirementsInput;

Choose a reason for hiding this comment

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

medium

Using an unsafe type cast as unknown as AnalyzeRequirementsInput is risky. If the input from the AI doesn't match the expected schema, it can lead to runtime errors. A more robust approach is to use a validation library like Zod to parse and validate the input. This would ensure type safety and provide clear errors if the input is invalid. This feedback applies to all other tool implementations in this PR that use similar unsafe casts.


for (const file of tsFiles) {
const content = await readFile(file);
const importMatches = content.matchAll(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g);

Choose a reason for hiding this comment

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

medium

Using regular expressions to parse code for validating imports is quite fragile. It can easily fail with multi-line imports, comments, or string literals that look like imports. For a more robust solution, consider using a proper code parser like the TypeScript Compiler API or a dedicated tool like dependency-cruiser. For an example project this might be acceptable, but it's worth noting this limitation.

Comment on lines +59 to +61
} catch {
return code;
}

Choose a reason for hiding this comment

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

medium

The catch block for prettier.format is empty, which means formatting errors will be silently ignored, and unformatted code will be returned. This can make debugging difficult. You should at least log the error to be aware of any formatting issues.

  } catch (err) {
    console.error('Prettier formatting failed:', err);
    return code;
  }

setPreviewStarting(false);
if (pollRef.current) clearInterval(pollRef.current);
}
} catch { /* ignore */ }

Choose a reason for hiding this comment

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

medium

This empty catch block ignores errors that might occur while polling for the preview status. Swallowing errors makes debugging very difficult. At a minimum, the error should be logged to the console.

Suggested change
} catch { /* ignore */ }
} catch (err) { console.error('Failed to poll preview status:', err); }

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: 18

Note

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

🟡 Minor comments (6)
mini-muse/docs/design.md-194-194 (1)

194-194: ⚠️ Potential issue | 🟡 Minor

Add fence languages for markdownlint compliance (MD040).

Line 194, Line 262, and Line 286 open fenced blocks without language identifiers.

📝 Proposed fix
-```
+```text
 frontend/src/
 ...
-```
+```

-```
+```bash
 # Option 1: Direct Anthropic API
 ANTHROPIC_API_KEY=sk-ant-your-api-key-here
 ...
-```
+```

-```
+```text
 mini-muse/
 ...
-```
+```

Also applies to: 262-262, 286-286

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

In `@mini-muse/docs/design.md` at line 194, Three fenced code blocks are missing
language identifiers; add appropriate fence languages to satisfy markdownlint
MD040 by changing the opening fences near the snippets that include
"frontend/src/" to ```text, the block that begins with "# Option 1: Direct
Anthropic API" to ```bash, and the block showing "mini-muse/" to ```text so each
fenced block has a language label.
mini-muse/frontend/src/pages/ResultPage.tsx-11-29 (1)

11-29: ⚠️ Potential issue | 🟡 Minor

Handle missing taskId explicitly to avoid stuck loading and invalid URL generation.

If taskId is absent, Line [24] returns early and loading never flips; Line [114] still force-asserts taskId!.

Suggested fix
 export default function ResultPage() {
   const { taskId } = useParams<{ taskId: string }>();
@@
+  if (!taskId) {
+    return <div className="text-center text-red-500 py-12">Invalid task id</div>;
+  }
+
   useEffect(() => {
-    if (!taskId) return;
     getFiles(taskId).then((f) => {
       setFiles(f);
       setLoading(false);
@@
-              href={getDownloadUrl(taskId!)}
+              href={getDownloadUrl(taskId)}

Also applies to: 114-114

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

In `@mini-muse/frontend/src/pages/ResultPage.tsx` around lines 11 - 29, The effect
and other code assume taskId exists and leave loading true or call taskId!
later; update the useEffect in ResultPage to explicitly handle a missing taskId
by setting setLoading(false) and either navigate to a safe route or show an
error (i.e., when !taskId do setLoading(false) and navigate('/somewhere') or set
an error state), and audit all uses of the non-null assertion taskId! (e.g.,
places building URLs or calling getFiles/getContent) to guard them with if
(!taskId) return/throw so no invalid URL is generated or background polling
starts without an id; ensure pollRef and any functions that reference taskId
check taskId before using it.
mini-muse/frontend/src/components/FileTree.tsx-19-19 (1)

19-19: ⚠️ Potential issue | 🟡 Minor

Avoid mutating files while building the tree.

Line [19] sorts files in place, which mutates the prop array and can create parent-state side effects.

Suggested fix
-  for (const file of files.sort()) {
+  const sortedFiles = [...files].sort();
+  for (const file of sortedFiles) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/components/FileTree.tsx` at line 19, The loop in the
FileTree component currently calls files.sort(), which mutates the incoming prop
array; to fix, iterate over a sorted copy instead (e.g., create a shallow copy
via slice() or spread and then sort) so the prop is not mutated—replace the
iteration over files.sort() with iteration over a copied-and-sorted array (use
the FileTree component and the files variable to locate the change).
mini-muse/app/service/orchestrator.ts-67-72 (1)

67-72: ⚠️ Potential issue | 🟡 Minor

Potential runtime error if task.messages is undefined.

In modify(), line 68 spreads task.messages which may be undefined if the task was never successfully run or if it errored before completion. This would cause a runtime error.

🐛 Suggested fix
     // Restore previous messages and append modification instruction
-    const messages: Message[] = [...task.messages as Message[]];
+    const messages: Message[] = [...(task.messages as Message[] || [])];

Alternatively, add a guard:

+    if (!task.messages || task.messages.length === 0) {
+      throw new Error('Cannot modify task: no previous messages found');
+    }
     const messages: Message[] = [...task.messages as Message[]];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/service/orchestrator.ts` around lines 67 - 72, In modify(),
spreading task.messages can throw if task.messages is undefined; update the code
that builds messages to safely handle that by using a default empty array or an
explicit guard before spreading (e.g., const messages: Message[] =
[...(task.messages ?? [])]) so that messages.push({... role:'user', content:
createModifyPrompt(instruction, relativeFiles) }) always operates on a defined
array; ensure you update any type annotations or null checks in modify() to
reflect that task.messages may be undefined.
mini-muse/app/controller/generate.ts-262-271 (1)

262-271: ⚠️ Potential issue | 🟡 Minor

Potential HTTP header injection via appName.

The appName is user-provided (defaulting to 'my-app') and used directly in the Content-Disposition header (line 264). Special characters like newlines or quotes could cause header injection or malformed headers.

🛡️ Suggested fix to sanitize filename
+    // Sanitize appName for use in Content-Disposition header
+    const safeAppName = task.appName.replace(/[^a-zA-Z0-9_-]/g, '_');
     ctx.res.writeHead(200, {
       'Content-Type': 'application/zip',
-      'Content-Disposition': `attachment; filename="${task.appName}.zip"`,
+      'Content-Disposition': `attachment; filename="${safeAppName}.zip"`,
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/controller/generate.ts` around lines 262 - 271, The
Content-Disposition header uses the user-controlled task.appName directly in
ctx.res.writeHead which can allow header injection; sanitize and validate
task.appName before using it in the header (e.g., strip CR/LF, quotes, path
separators, and any unsafe characters, collapse whitespace, and fallback to a
safe default like "my-app" if the result is empty), then use the sanitized name
when building the header and the archive.directory call; update references in
this block (task.appName, ctx.res.writeHead, and archive.directory) to use the
sanitized variable to prevent injection and malformed headers.
mini-muse/app/service/taskManager.ts-85-88 (1)

85-88: ⚠️ Potential issue | 🟡 Minor

Unsafe type assertion could cause runtime errors.

The event.data as string[] assertion doesn't validate the actual type. If event.data is not an array of strings, spreading it into filesCreated will fail or produce unexpected results.

🛡️ Proposed fix with runtime validation
     if (event.type === 'file_created' && event.data) {
-      const files = event.data as string[];
-      task.filesCreated.push(...files);
+      const files = event.data;
+      if (Array.isArray(files)) {
+        task.filesCreated.push(...files.filter((f): f is string => typeof f === 'string'));
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/service/taskManager.ts` around lines 85 - 88, The code unsafely
asserts event.data as string[] and spreads it into task.filesCreated; replace
that with a runtime check: verify Array.isArray(event.data), map/filter to keep
only items where typeof item === 'string' (or coerce to string if intended), and
then push those validated strings into task.filesCreated (e.g., const files =
(Array.isArray(event.data) ? event.data.filter(i => typeof i === 'string') :
[]); task.filesCreated.push(...files)). Update the block handling event.type ===
'file_created' in taskManager.ts to gracefully handle non-array or mixed-type
event.data rather than using the unsafe cast.
🧹 Nitpick comments (25)
mini-muse/frontend/src/types/index.ts (1)

30-33: Consider using a status union type for consistency.

CreateTaskResponse.status is typed as string, while TaskSummary.status uses a union type 'pending' | 'running' | 'completed' | 'failed'. Using the same union would provide better type safety.

💡 Optional: Use consistent status typing
 export interface CreateTaskResponse {
   taskId: string;
-  status: string;
+  status: 'pending' | 'running' | 'completed' | 'failed';
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/types/index.ts` around lines 30 - 33,
CreateTaskResponse.status is currently a plain string while TaskSummary.status
uses the union 'pending' | 'running' | 'completed' | 'failed'; change
CreateTaskResponse to use the same union for status (or extract a shared
TaskStatus type and use it for both CreateTaskResponse.status and
TaskSummary.status) so both types share the identical union for consistent type
safety.
mini-muse/frontend/vite.config.ts (1)

10-14: Consider adding changeOrigin to the proxy configuration.

Adding changeOrigin: true can help with CORS and ensures the Host header is rewritten to match the target. This is particularly useful if you add authentication or host-based routing later.

💡 Optional: Enhanced proxy configuration
   server: {
     proxy: {
-      '/api': 'http://127.0.0.1:7001',
+      '/api': {
+        target: 'http://127.0.0.1:7001',
+        changeOrigin: true,
+      },
     },
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/vite.config.ts` around lines 10 - 14, Update the Vite dev
server proxy for the '/api' entry to include changeOrigin: true so the Host
header is rewritten to the target; locate the server.proxy configuration in
vite.config.ts (the '/api' proxy entry) and change the simple string target into
an object with target: 'http://127.0.0.1:7001' and changeOrigin: true to ensure
proper CORS/host behavior (optionally add secure or other proxy options later).
mini-muse/frontend/package.json (1)

11-25: Consider pinning dependency versions or adding engines field.

The caret ranges (^) are fine for an example project, but consider:

  1. Adding an engines field to document the Node.js version requirement (README mentions >= 18)
  2. Adding npm-shrinkwrap.json or committing package-lock.json if reproducibility is important
💡 Optional: Add engines field
   "version": "1.0.0",
   "type": "module",
+  "engines": {
+    "node": ">=18"
+  },
   "scripts": {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/package.json` around lines 11 - 25, Add an "engines" field
to package.json to document the Node.js version required (e.g., matching
README's ">= 18") and either commit the generated lockfile (package-lock.json)
or add an npm-shrinkwrap.json to the repo for reproducible installs; update the
package.json near the existing "dependencies" and "devDependencies" entries to
include the "engines" key and ensure package-lock.json (or npm-shrinkwrap.json)
is present in version control.
mini-muse/README.md (1)

42-56: Consider a single root “build-all” command for production flow.

Line 55 depends on Line 45 having run first. A single script reduces deployment mistakes.

♻️ Suggested change
# mini-muse/package.json
  "scripts": {
    "dev": "egg-bin dev --ts",
    "build": "tsc",
+   "build:all": "cd frontend && npm run build && cd .. && npm run build",
    "start": "egg-scripts start --ts",
    "test": "egg-bin test --ts"
  }
# mini-muse/README.md
-# Production
-npm run build && npm start
+# Production
+npm run build:all && npm start
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/README.md` around lines 42 - 56, Add a new top-level npm script
(e.g., "build-all") that runs the frontend build then the root production build
so production deploys don't depend on manual ordering; update package.json
scripts to include "build-all" which executes the frontend build step (the
command currently run via "cd frontend && npm run build") followed by the
existing root "build" and ensure "npm start" still runs the server; update
README instructions to reference "npm run build-all && npm start" for the
Production flow and keep "npm run dev" unchanged for Development.
mini-muse/app/lib/prompts.ts (1)

86-96: Escape fenced-block delimiters in dynamic prompt content.

If existingFiles or instruction contains ``` it can break the prompt format and degrade tool behavior.

🧩 Suggested hardening
 export function createModifyPrompt(instruction: string, existingFiles: string[]): string {
+  const sanitize = (value: string) => value.replace(/```/g, '\\`\\`\\`');
+  const safeFiles = existingFiles.map(sanitize);
+  const safeInstruction = sanitize(instruction);
+
   return `The project has been generated with the following file structure:

 \`\`\`
-${existingFiles.join('\n')}
+${safeFiles.join('\n')}
 \`\`\`

 The user wants to make the following modification:

 ---
-${instruction}
+${safeInstruction}
 ---
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/prompts.ts` around lines 86 - 96, The template inserts raw
existingFiles and instruction into a fenced code block; if either contains ```
it breaks the prompt format, so add a sanitize helper (e.g., sanitize) that
escapes triple-backticks (replace /```/g with '\`\\`\\`' or equivalent), produce
safeFiles = existingFiles.map(sanitize) and safeInstruction =
sanitize(instruction), and use ${safeFiles.join('\n')} and ${safeInstruction} in
the returned template instead of ${existingFiles.join('\n')} and ${instruction};
ensure the helper is applied wherever these dynamic values are interpolated in
the prompt generation function.
mini-muse/app/middleware/errorHandler.ts (1)

17-21: Consider sanitizing error messages for 500 errors.

Exposing error.message directly in the response could leak implementation details in production. For non-client errors (500s), consider using a generic message.

💡 Suggestion
     ctx.status = error.status || 500;
     ctx.body = {
       success: false,
-      error: error.message || 'Internal Server Error',
+      error: error.status && error.status < 500 
+        ? (error.message || 'Request Error')
+        : 'Internal Server Error',
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/middleware/errorHandler.ts` around lines 17 - 21, The current
error handler sets ctx.body.error to error.message which can leak details for
server errors; update the middleware (where ctx.status is set) to only return
the real error.message for client errors (status in 400-499) and replace
messages for 500/unknown errors with a generic string like "Internal Server
Error"; keep full error details logged on the server (e.g., via logger or
console.error) but do not include them in ctx.body when ctx.status >= 500 so
production responses are sanitized.
mini-muse/app/lib/tools/reader/readFile.ts (1)

50-54: Consider preserving the original error for debugging.

The catch block assumes all errors are "file not found", but other errors like permission denied could occur. Logging or including the actual error would help debugging.

💡 Suggestion
-  } catch {
+  } catch (err) {
+    const errorCode = (err as NodeJS.ErrnoException).code;
     return {
       success: false,
-      error: `File not found: ${data.filePath}`,
+      error: errorCode === 'ENOENT' 
+        ? `File not found: ${data.filePath}`
+        : `Failed to read file: ${data.filePath}`,
     };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/tools/reader/readFile.ts` around lines 50 - 54, In the
readFile catch block (in readFile.ts) capture the thrown error (e.g., catch
(err)) instead of a bare catch and preserve its information in the returned
object: keep success: false but change error to a concise message and add a new
field (e.g., originalError or errorDetails) containing err.message or err.stack
so callers and logs can see the real cause (permission denied, EACCES, etc.);
update any callers if needed to read the new field and/or log the original error
for debugging.
mini-muse/app/service/tools.ts (2)

10-12: Unsafe cast of name to ToolName.

The cast name as ToolName bypasses type safety. While executeTool handles unknown tools gracefully at runtime, consider validating the name or documenting this behavior.

💡 Optional: Add validation
+import { getToolDefinitions, executeTool, ToolName, ToolResult, isValidToolName } from '../lib/tools/registry';
+
 async execute(name: string, input: Record<string, unknown>, outputDir: string): Promise<ToolResult> {
+  if (!isValidToolName(name)) {
+    return { success: false, error: `Unknown tool: ${name}` };
+  }
   return executeTool(name as ToolName, input, outputDir);
 }

This requires adding isValidToolName to the registry.

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

In `@mini-muse/app/service/tools.ts` around lines 10 - 12, The current
implementation in execute (async execute(name: string, ...)) unsafely casts name
to ToolName before calling executeTool; replace the cast with a proper
validation: check the provided name against the tool registry (implement or call
isValidToolName) and only call executeTool(name as ToolName, ...) after the
check, otherwise return a rejected Promise or throw a descriptive error (or log
and return a ToolResult indicating unknown tool). Update or add isValidToolName
in the registry and use it from execute, or change the execute signature to
accept ToolName if callers can guarantee the type.

1-4: Consider consolidating ToolDefinition to a single source of truth.

ToolDefinition is defined in both aiClient.ts and registry.ts (see relevant snippets). The service imports from aiClient.ts but getToolDefinitions() returns from registry.ts. If these interfaces diverge, type mismatches could occur.

Consider exporting ToolDefinition from a single location (e.g., registry.ts) and importing it elsewhere.

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

In `@mini-muse/app/service/tools.ts` around lines 1 - 4, ToolDefinition is
declared in multiple modules causing potential type divergence between the
service import and getToolDefinitions() return; consolidate to a single source
by exporting ToolDefinition from the registry module (the same module that
defines getToolDefinitions()/executeTool/ToolName/ToolResult) and update
aiClient.ts and mini-muse/app/service/tools.ts to import ToolDefinition from
that registry export; ensure you remove or re-export any duplicate declaration
(or make aiClient re-export registry's ToolDefinition) so all usages
(getToolDefinitions, executeTool, and the Service code) reference the same type
symbol ToolDefinition.
mini-muse/frontend/src/components/ProgressLog.tsx (1)

8-26: Missing styles and labels for modify_started and modify_completed event types.

The ProgressEvent type includes 'modify_started' and 'modify_completed' (see mini-muse/frontend/src/types/index.ts), but these are not present in the typeStyles and typeLabels maps. They'll fall back to defaults, but explicit handling would improve UX.

💡 Suggestion
 const typeStyles: Record<string, string> = {
   status: 'text-blue-600',
   thinking: 'text-gray-500',
   tool_call: 'text-purple-600',
   tool_result: 'text-green-600',
   file_created: 'text-emerald-600',
   completed: 'text-green-700 font-medium',
   error: 'text-red-600 font-medium',
+  modify_started: 'text-amber-600',
+  modify_completed: 'text-amber-700 font-medium',
 };

 const typeLabels: Record<string, string> = {
   status: 'STATUS',
   thinking: 'THINKING',
   tool_call: 'TOOL',
   tool_result: 'RESULT',
   file_created: 'FILE',
   completed: 'DONE',
   error: 'ERROR',
+  modify_started: 'MODIFY',
+  modify_completed: 'MODIFIED',
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/components/ProgressLog.tsx` around lines 8 - 26, Add
explicit mappings for the missing ProgressEvent types by updating the typeStyles
and typeLabels objects to include entries for "modify_started" and
"modify_completed" so they don't fall back to defaults; e.g., add semantic class
names (such as 'text-yellow-600' or 'text-amber-600' for modify_started and
'text-amber-700 font-medium' for modify_completed) to typeStyles and matching
labels like "MODIFY" or "MODIFY_DONE" to typeLabels, ensuring the keys exactly
match the ProgressEvent variants so components using typeStyles/typeLabels
render the correct text and classes.
mini-muse/frontend/src/pages/ProgressPage.tsx (1)

44-52: Edge case: No status indicator shown when stream ends unexpectedly.

If the SSE stream ends (done becomes true) without a 'completed' or 'error' event, none of the three status indicator conditions will match, leaving the status area empty. Consider handling this edge case.

💡 Suggestion
         {!done && !failed && (
           <div className="w-4 h-4 rounded-full bg-indigo-500 animate-pulse" />
         )}
+        {done && !isCompleted && !failed && (
+          <div className="w-4 h-4 rounded-full bg-yellow-500" />
+        )}
         {isCompleted && (
           <div className="w-4 h-4 rounded-full bg-green-500" />
         )}

And update the status text:

         <span className="font-medium text-gray-700">
-          {failed ? 'Generation Failed' : isCompleted ? 'Generation Complete' : 'Generating...'}
+          {failed ? 'Generation Failed' : isCompleted ? 'Generation Complete' : done ? 'Stream Ended' : 'Generating...'}
         </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/pages/ProgressPage.tsx` around lines 44 - 52, The
status area can be empty when the SSE stream ends (done) without emitting
'completed' or 'error'; update the render logic in ProgressPage.tsx to handle
the edge case by adding a condition (e.g., done && !isCompleted && !failed) that
shows a distinct indicator (amber/gray) and corresponding status text for
"stream ended unexpectedly" or "stopped" so users see a terminal state; you can
also set or map an explicit state flag instead of relying solely on done to make
the intent clear.
mini-muse/config/config.default.ts (1)

14-14: Model identifier is valid. claude-sonnet-4-20250514 is Anthropic's current Claude Sonnet 4 model and is available on the Anthropic API. Consider making this configurable via environment variable for better flexibility in different environments.

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

In `@mini-muse/config/config.default.ts` at line 14, The config currently
hardcodes model: 'claude-sonnet-4-20250514'; change it to read from an
environment variable (e.g., process.env.ANTHROPIC_MODEL or similar) with
'claude-sonnet-4-20250514' as the fallback default so different environments can
override the model; update the exported config object in config.default.ts (the
model property) to use the env var fallback pattern and update any README or
comments if present to document the new ANTHROPIC_MODEL variable name.
mini-muse/app/lib/tools/generator/createComponent.ts (2)

78-81: Consider validating the directory input to prevent path traversal.

The directory field is user-controlled and directly joined into the path without sanitization. While the output is constrained to outputDir/src/components/..., a malicious input like ../../etc could write outside the intended directory.

For consistency with deleteFile.ts and readFile.ts (which include path traversal checks), consider adding a similar guard here.

🛡️ Suggested validation
   // Determine component directory
+  if (data.directory && (data.directory.includes('..') || path.isAbsolute(data.directory))) {
+    return {
+      success: false,
+      error: 'Invalid directory: path traversal not allowed',
+    };
+  }
   const baseDir = data.directory
     ? path.join(outputDir, 'src', 'components', data.directory, componentName)
     : path.join(outputDir, 'src', 'components', componentName);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/tools/generator/createComponent.ts` around lines 78 - 81,
The code builds baseDir from user-controlled data.directory without
sanitization, allowing path traversal; update createComponent.ts to
validate/sanitize data.directory before using it in path.join (same approach as
deleteFile.ts/readFile.ts): check for presence of ".." or absolute paths and
normalize the joined path, then verify the resulting baseDir is a subpath of the
intended root (outputDir/src/components) before writing; apply this guard around
the baseDir computation and use the safe baseDir in the rest of the
createComponent flow.

62-73: Missing error handling for file system operations.

The function performs multiple file writes (lines 86, 93, 103) without try-catch, so any I/O error will propagate as an unhandled exception. Other tools in this PR (e.g., deleteFile, readProjectFile) wrap operations and return structured error results.

♻️ Suggested fix to add error handling
 export async function createComponent(
   input: Record<string, unknown>,
   outputDir: string
 ): Promise<ToolResult> {
   const data = input as unknown as CreateComponentInput;

   if (!data.name || !data.componentCode) {
     return {
       success: false,
       error: 'name and componentCode are required',
     };
   }

+  try {
     const componentName = toPascalCase(data.name);
     const filesCreated: string[] = [];
     // ... rest of implementation ...
     return {
       success: true,
       message: `Created component: ${componentName}`,
       filesCreated,
       data: {
         componentName,
         directory: baseDir,
       },
     };
+  } catch (error) {
+    return {
+      success: false,
+      error: `Failed to create component: ${error instanceof Error ? error.message : String(error)}`,
+    };
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/tools/generator/createComponent.ts` around lines 62 - 73,
createComponent currently performs multiple filesystem writes (the write
operations that create the component files and update index) without error
handling; wrap the file I/O in a try/catch around the region that performs the
writes in createComponent and on any thrown error return a ToolResult with
success: false and a descriptive error string (include the caught
error.message). Ensure the catch covers all write operations (the same block
that writes componentCode, CSS/TSX files, and the index update) so failures are
returned as structured errors rather than letting exceptions propagate.
mini-muse/app/lib/tools/registry.ts (1)

13-21: Duplicate ToolDefinition interface.

The ToolDefinition interface is also defined in mini-muse/app/service/aiClient.ts (lines 31-39) with an identical structure. Consider exporting from a single location and importing where needed to avoid drift.

♻️ Suggested consolidation

Export from registry.ts (or a shared types file) and import in aiClient.ts:

// In aiClient.ts
import { ToolDefinition } from '../lib/tools/registry';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/tools/registry.ts` around lines 13 - 21, Duplicate
ToolDefinition interface exists; remove the duplicate definition from the
aiClient.ts module and instead import the single exported ToolDefinition from
the registry module (or move ToolDefinition to a new shared types module and
export it from there), update aiClient.ts to import { ToolDefinition } from the
registry/shared-types export, and ensure all references to ToolDefinition (the
interface name) remain valid.
mini-muse/app/controller/generate.ts (2)

111-120: Direct mutation of task.status bypasses service layer.

Line 113 directly sets task.status = 'running', bypassing taskManager which may have additional logic for state transitions. This could lead to inconsistent state if the service tracks status changes.

♻️ Consider using a service method
-    // Reset status to running
-    task.status = 'running';
+    // Reset status to running via service
+    ctx.service.taskManager.setTaskStatus(taskId, 'running');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/controller/generate.ts` around lines 111 - 120, The code
directly mutates task.status and task.chatHistory; instead call the task service
methods (e.g., taskManager.updateStatus or taskManager.setTaskStatus) to set
status to 'running' and use a taskManager.appendChatEntry/ addChatHistoryEntry
method to add the user message (preserving role, content, timestamp) so any
business rules/side effects in taskManager run; replace direct assignments to
task.status and task.chatHistory.push with those service calls (use task.id or
the task object as the identifier per the taskManager API).

268-271: Missing error handling for archive streaming.

If archive.directory() or archive.finalize() throws (e.g., directory doesn't exist), the error will propagate unhandled and the response may be left in an inconsistent state.

♻️ Suggested error handling
     const archive = archiver('zip', { zlib: { level: 9 } });
+    archive.on('error', (err) => {
+      ctx.logger.error('Archive error for task %s: %s', taskId, err.message);
+      ctx.res.end();
+    });
     archive.pipe(ctx.res);
     archive.directory(task.outputDir, task.appName);
     await archive.finalize();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/controller/generate.ts` around lines 268 - 271, Wrap the
archiver streaming code in a try/catch and add error event handlers so streaming
failures from archive.directory or archive.finalize are caught and the HTTP
response is properly closed; specifically, attach an 'error' listener to the
archive to handle stream errors, guard the calls to
archive.directory(task.outputDir, task.appName) and await archive.finalize()
inside the try block, and in the catch (or on error) call ctx.status and
ctx.body (or ctx.res.end) to return a clear error and ensure ctx.res is ended so
the response doesn't hang.
mini-muse/app/lib/tools/generator/createPage.ts (2)

40-91: Significant code duplication with createComponent.ts.

Both createPage and createComponent share nearly identical logic for:

  • Formatting and writing main TSX file
  • Formatting and writing optional CSS module
  • Generating barrel index.ts with conditional default export handling

Consider extracting a shared helper function (e.g., createModuleWithStyle) to reduce duplication and ensure consistent behavior across generators.

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

In `@mini-muse/app/lib/tools/generator/createPage.ts` around lines 40 - 91,
Extract shared file-creation logic into a helper (e.g., createModuleWithStyle)
that both createPage and createComponent call: the helper should accept
parameters like moduleName, outputDir (or parent path), mainCode (tsx string),
styleCode (optional), and route/metadata info, perform toPascalCase(moduleName),
create the module directory, format mainCode with formatCode('typescript') and
styleCode with formatCode('css') as needed, write the main .tsx and optional
.module.css via writeFile, detect default export via
/export\s+default\b/.test(mainCode) and generate the index.ts barrel
accordingly, and return filesCreated plus module metadata (name, directory,
etc.); then replace the duplicated logic in createPage (and createComponent) to
call this helper and reuse its returned filesCreated/metadata.

40-51: Same missing error handling pattern as createComponent.ts.

File operations at lines 62, 69, and 79 lack try-catch wrapping, so I/O errors will propagate as unhandled exceptions rather than returning a structured ToolResult with success: false.

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

In `@mini-muse/app/lib/tools/generator/createPage.ts` around lines 40 - 51, The
createPage function is missing try/catch around its file I/O calls so filesystem
errors will throw instead of returning a ToolResult; wrap the file write and
directory operations in a try/catch (same pattern used in createComponent.ts) so
any thrown error is caught and you return { success: false, error: String(err) }
(include contextual text like "failed to create page" plus err.message). Locate
the I/O calls in createPage that use outputDir and data.pageCode (the
write/mkdir operations) and move them into the try block, and in the catch
return a structured ToolResult with success:false and the error details.
mini-muse/app/lib/tools/validator/validateProject.ts (2)

166-172: Regex for detecting any type may produce false positives.

The regex /:\s*any\b/g will match occurrences inside comments and string literals (e.g., // type: any example or "type: any"). For a validation tool, this could report spurious issues.

A more robust approach would use a parser, but that may be overkill. Consider documenting this limitation or using a slightly more restrictive pattern.

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

In `@mini-muse/app/lib/tools/validator/validateProject.ts` around lines 166 - 172,
The current detection using the regex /:\s*any\b/g inside validateProject.ts
(where anyMatches is computed) can match comments and string literals; update
the logic to avoid false positives by either (1) using a simple pre-filter that
strips or ignores comments and string literals before running the regex, or (2)
tightening the pattern to only match TypeScript-like annotations (for example
require a preceding identifier or punctuation like
/\b[a-zA-Z0-9_$]+\s*:\s*any\b/ or include lookbehind to avoid quotes/slash), or
(3) document the limitation in the validation note if you prefer not to change
detection; apply this change to the block that sets anyMatches and adjust the
issue message generation accordingly.

104-137: Import resolution doesn't handle @/ path alias.

The import validator skips external packages but also skips @/ aliases (line 112). This means aliased imports won't be validated even though they're local paths. If the generated projects use @/ aliases, unresolved aliased imports will be silently ignored.

Consider adding resolution logic for the @/ alias (typically maps to src/) if you want comprehensive import validation.

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

In `@mini-muse/app/lib/tools/validator/validateProject.ts` around lines 104 - 137,
The validator currently skips imports that start with '@/' so aliased local
imports are ignored; update the loop in validateProject.ts (inside the tsFiles
for-loop where importPath is computed) to resolve '@/...' to the project source
directory and validate it like a relative path: when importPath
startsWith('@/'), map it to a filesystem path (e.g. join outputDir and 'src'
with the remainder of importPath), compute a targetPath from that mapping, then
run the same extensions check (the extensions array and fileExists loop used for
relative imports) and push the same error message using relativePath if no file
is found; keep existing behavior for external packages and for './'/'../'
relative paths.
mini-muse/app/service/orchestrator.ts (1)

219-226: Path separator replacement may fail on Windows.

Line 222 uses f.replace(outputDir + '/', '') which assumes Unix-style path separators. On Windows, outputDir would use backslashes, causing the replacement to fail.

Consider using path.relative() for cross-platform compatibility:

♻️ Suggested fix
           if (result.filesCreated && result.filesCreated.length > 0) {
             ctx.service.taskManager.updateProgress(taskId, {
               type: 'file_created',
-              message: `Created: ${result.filesCreated.map(f => f.replace(outputDir + '/', '')).join(', ')}`,
+              message: `Created: ${result.filesCreated.map(f => path.relative(outputDir, f)).join(', ')}`,
               data: result.filesCreated,
               timestamp: Date.now(),
             });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/service/orchestrator.ts` around lines 219 - 226, The code uses
f.replace(outputDir + '/', '') which breaks on Windows due to backslashes;
replace that logic to compute a cross-platform relative path (e.g., use
path.relative(outputDir, f) on each entry in result.filesCreated) and use the
relative path in the message and data; also ensure the Node 'path' module is
imported and, if you need consistent separators in the message, normalize the
resulting relative path (e.g., replace backslashes) before joining.
mini-muse/frontend/src/services/api.ts (2)

5-14: Consider checking HTTP status before parsing JSON.

The functions don't check res.ok before attempting to parse JSON. If the server returns a non-2xx response with a non-JSON body (e.g., HTML error page), res.json() will throw an unhelpful parsing error.

♻️ Suggested improvement
 export async function createTask(description: string, appName: string): Promise<CreateTaskResponse> {
   const res = await fetch(`${BASE_URL}/generate`, {
     method: 'POST',
     headers: { 'Content-Type': 'application/json' },
     body: JSON.stringify({ description, appName }),
   });
+  if (!res.ok) {
+    throw new Error(`Request failed with status ${res.status}`);
+  }
   const json: ApiResponse<CreateTaskResponse> = await res.json();
   if (!json.success || !json.data) throw new Error(json.error || 'Failed to create task');
   return json.data;
 }

This pattern should be applied consistently across all API functions.

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

In `@mini-muse/frontend/src/services/api.ts` around lines 5 - 14, The createTask
function should check the HTTP response status before calling res.json to avoid
parse errors on non-JSON error pages: after awaiting fetch in createTask, verify
res.ok and if false attempt to read and include a safe error payload (e.g., try
res.json() in a try/catch or fallback to res.text()) and then throw a meaningful
Error containing res.status/res.statusText and the body; apply this same pattern
to other API functions that call res.json so they always handle non-2xx
responses gracefully and include status and body in the thrown error.

27-29: Silent error swallowing hides debugging issues.

The empty catch block silently ignores JSON parse errors. While ignoring malformed events may be intentional, completely swallowing errors makes debugging difficult when things go wrong.

♻️ Consider logging parse failures
     } catch {
-      // ignore parse errors
+      // Log parse errors in development for debugging
+      if (import.meta.env.DEV) {
+        console.warn('Failed to parse SSE event:', e.data);
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/services/api.ts` around lines 27 - 29, The empty catch
block that swallows JSON parse errors in the API service should be changed to
log the parse failure instead of silently ignoring it: in the catch after the
JSON.parse call (the lone catch { ... } in the api service), capture the error
and the raw payload and emit a debug/warn log entry (e.g., include error.message
and the raw event string) so malformed events are visible while keeping the
existing flow (do not rethrow unless intended).
mini-muse/app/service/taskManager.ts (1)

32-37: In-memory task storage lacks cleanup mechanism and persistence.

The tasks Map will grow indefinitely as tasks are created but never removed. This leads to unbounded memory growth over time. Additionally, all tasks are lost on server restart.

Consider adding:

  1. A TTL-based cleanup for completed/failed tasks
  2. Optional persistence if task history matters
♻️ Example cleanup mechanism
 // Singleton task store shared across all service instances
 const tasks = new Map<string, TaskInfo>();
+
+// Optional: Clean up old completed/failed tasks periodically
+const TASK_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
+
+setInterval(() => {
+  const now = Date.now();
+  for (const [id, task] of tasks) {
+    if ((task.status === 'completed' || task.status === 'failed') &&
+        now - task.createdAt > TASK_TTL_MS) {
+      tasks.delete(id);
+    }
+  }
+}, 60 * 60 * 1000); // Check hourly
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/service/taskManager.ts` around lines 32 - 37, The tasks Map
(tasks) holding TaskInfo entries has no cleanup or persistence; add a TTL
cleanup and optional persistence: extend TaskInfo with timestamps (createdAt,
finishedAt) and a status field if not present, implement a configurable cleanup
loop (e.g., a setInterval in taskManager) that scans tasks and deletes entries
whose status is completed/failed and finishedAt + TTL < now, and ensure task
lifecycle points (where tasks are created/updated—reference the
functions/methods that set TaskInfo in the tasks Map) update finishedAt/status
so the cleaner can work; additionally add an optional persistence adapter hook
(simple JSON file flush/load or pluggable interface) invoked on startup to load
existing tasks and periodically/on-change to persist, controlled by
configuration.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9ecaec and 71a7d0c.

📒 Files selected for processing (50)
  • mini-muse/.env.example
  • mini-muse/CLAUDE.md
  • mini-muse/README.md
  • mini-muse/app/controller/generate.ts
  • mini-muse/app/controller/home.ts
  • mini-muse/app/lib/prompts.ts
  • mini-muse/app/lib/tools/analyzer/analyzeRequirements.ts
  • mini-muse/app/lib/tools/analyzer/planArchitecture.ts
  • mini-muse/app/lib/tools/generator/createComponent.ts
  • mini-muse/app/lib/tools/generator/createConfig.ts
  • mini-muse/app/lib/tools/generator/createFile.ts
  • mini-muse/app/lib/tools/generator/createHook.ts
  • mini-muse/app/lib/tools/generator/createPage.ts
  • mini-muse/app/lib/tools/generator/createStyle.ts
  • mini-muse/app/lib/tools/generator/deleteFile.ts
  • mini-muse/app/lib/tools/reader/readFile.ts
  • mini-muse/app/lib/tools/registry.ts
  • mini-muse/app/lib/tools/validator/validateProject.ts
  • mini-muse/app/lib/utils.ts
  • mini-muse/app/middleware/errorHandler.ts
  • mini-muse/app/router.ts
  • mini-muse/app/service/aiClient.ts
  • mini-muse/app/service/orchestrator.ts
  • mini-muse/app/service/preview.ts
  • mini-muse/app/service/taskManager.ts
  • mini-muse/app/service/tools.ts
  • mini-muse/config/config.default.ts
  • mini-muse/config/config.local.ts
  • mini-muse/config/plugin.ts
  • mini-muse/docs/design.md
  • mini-muse/frontend/index.html
  • mini-muse/frontend/package.json
  • mini-muse/frontend/postcss.config.js
  • mini-muse/frontend/src/App.tsx
  • mini-muse/frontend/src/components/ChatPanel.tsx
  • mini-muse/frontend/src/components/CodePreview.tsx
  • mini-muse/frontend/src/components/FileTree.tsx
  • mini-muse/frontend/src/components/ProgressLog.tsx
  • mini-muse/frontend/src/index.css
  • mini-muse/frontend/src/main.tsx
  • mini-muse/frontend/src/pages/HomePage.tsx
  • mini-muse/frontend/src/pages/ProgressPage.tsx
  • mini-muse/frontend/src/pages/ResultPage.tsx
  • mini-muse/frontend/src/services/api.ts
  • mini-muse/frontend/src/types/index.ts
  • mini-muse/frontend/tailwind.config.js
  • mini-muse/frontend/tsconfig.json
  • mini-muse/frontend/vite.config.ts
  • mini-muse/package.json
  • mini-muse/tsconfig.json

Comment on lines +10 to +16
try {
const html = await fs.readFile(indexPath, 'utf-8');
ctx.type = 'text/html';
ctx.body = html;
} catch {
ctx.body = 'Mini-Muse is running. Please build the frontend first: cd frontend && npm run build';
}
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

Return an explicit error status when frontend entry cannot be served.

The failure branch sends a success-shaped response body without setting an error status, which hides deployment/runtime problems.

🔧 Proposed fix
-    } catch {
-      ctx.body = 'Mini-Muse is running. Please build the frontend first: cd frontend && npm run build';
+    } catch (error) {
+      ctx.logger.error({ err: error }, 'Failed to read app/public/index.html');
+      ctx.status = 503;
+      ctx.type = 'text/plain';
+      ctx.body = 'Mini-Muse is running. Please build the frontend first: cd frontend && npm run build';
     }
📝 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 {
const html = await fs.readFile(indexPath, 'utf-8');
ctx.type = 'text/html';
ctx.body = html;
} catch {
ctx.body = 'Mini-Muse is running. Please build the frontend first: cd frontend && npm run build';
}
try {
const html = await fs.readFile(indexPath, 'utf-8');
ctx.type = 'text/html';
ctx.body = html;
} catch (error) {
ctx.logger.error({ err: error }, 'Failed to read app/public/index.html');
ctx.status = 503;
ctx.type = 'text/plain';
ctx.body = 'Mini-Muse is running. Please build the frontend first: cd frontend && npm run build';
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/controller/home.ts` around lines 10 - 16, The catch block in
the handler that reads indexPath using fs.readFile currently returns a plaintext
success-shaped body without an HTTP error status; update the catch to set an
appropriate error status (e.g., ctx.status = 500 or 404 depending on semantics)
before setting ctx.type and ctx.body so callers see a non-2xx response when
fs.readFile(indexPath, 'utf-8') fails; modify the try/catch around fs.readFile,
referencing indexPath, ctx.type, and ctx.body to add the status line and keep
the informative message.

Comment on lines +48 to +57
- Implements responsive design with CSS modules or styled-components

## Technology Stack

Always generate projects using:
- **Build Tool**: Vite
- **Framework**: React 18+
- **Language**: TypeScript (strict mode)
- **Styling**: CSS Modules (*.module.css)
- **Routing**: React Router v6 (if multiple pages)
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

Align styling instructions with the project’s Tailwind stack.

Line 48 and Lines 56-57 instruct CSS Modules/styled-components, but this project is documented and scaffolded around Tailwind. This can produce inconsistent or non-runnable output.

🧭 Proposed fix
- - Implements responsive design with CSS modules or styled-components
+ - Implements responsive design with Tailwind CSS utility classes
...
- - **Styling**: CSS Modules (*.module.css)
+ - **Styling**: Tailwind CSS
📝 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
- Implements responsive design with CSS modules or styled-components
## Technology Stack
Always generate projects using:
- **Build Tool**: Vite
- **Framework**: React 18+
- **Language**: TypeScript (strict mode)
- **Styling**: CSS Modules (*.module.css)
- **Routing**: React Router v6 (if multiple pages)
- Implements responsive design with Tailwind CSS utility classes
## Technology Stack
Always generate projects using:
- **Build Tool**: Vite
- **Framework**: React 18+
- **Language**: TypeScript (strict mode)
- **Styling**: Tailwind CSS
- **Routing**: React Router v6 (if multiple pages)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/prompts.ts` around lines 48 - 57, The styling instructions
currently reference "Styling: CSS Modules (*.module.css)" and a note about
"responsive design with CSS modules or styled-components", which conflicts with
this project's Tailwind-based scaffold; update the prompt text to replace any
"CSS Modules", "styled-components" or related responsive guidance with
Tailwind-specific guidance (e.g., "Styling: Tailwind CSS (utility-first,
configured with postcss/tailwind.config.js)" and remove mentions of CSS
Modules/styled-components) so all generated projects and docs consistently use
Tailwind classes and configuration; locate the block containing the "Styling"
line and the responsive-design sentence and change them accordingly.

Comment on lines +139 to +157
if (!data.appName || !data.pages || !data.components || !data.dataModels) {
return {
success: false,
error: 'Missing required fields in requirements analysis',
};
}

// Return the analyzed requirements for the next phase
return {
success: true,
message: `Analyzed requirements for "${data.appTitle}": ${data.pages.length} pages, ${data.components.length} components, ${data.dataModels.length} data models`,
data: {
appName: data.appName,
appTitle: data.appTitle,
pages: data.pages,
components: data.components,
dataModels: data.dataModels,
features: data.features,
hooks: data.hooks || [],
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

Validate all required fields and array shapes before building the result.

Line 139 does not enforce description, appTitle, or features, and it does not confirm array types. Line 149 then assumes .length exists, which can break or propagate malformed state.

🛠️ Proposed fix
 export async function analyzeRequirements(
   input: Record<string, unknown>,
   _outputDir: string
 ): Promise<ToolResult> {
   const data = input as unknown as AnalyzeRequirementsInput;

   // Validate required fields
-  if (!data.appName || !data.pages || !data.components || !data.dataModels) {
+  if (
+    !data.description ||
+    !data.appName ||
+    !data.appTitle ||
+    !Array.isArray(data.pages) ||
+    !Array.isArray(data.components) ||
+    !Array.isArray(data.dataModels) ||
+    !Array.isArray(data.features)
+  ) {
     return {
       success: false,
       error: 'Missing required fields in requirements analysis',
     };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/tools/analyzer/analyzeRequirements.ts` around lines 139 -
157, The current validation in the analyzeRequirements function only checks
appName/pages/components/dataModels but misses required fields (description,
appTitle, features) and doesn't verify that pages/components/dataModels/features
are arrays before using .length; update the initial guard to assert presence of
description, appTitle, and features and verify Array.isArray(...) for pages,
components, dataModels and features (and that they have non-negative lengths),
returning the existing error shape if any check fails; also make the result
message robust by using a safe fallback (e.g., data.appTitle || data.appName)
when interpolating the title and ensure hooks defaults remain as hooks || [].

Comment on lines +57 to +77
// Format code if possible
let content = data.content;
if (fileType) {
content = await formatCode(content, fileType);
}

// Determine the full path
let fullPath: string;
if (data.filePath.startsWith('src/')) {
fullPath = path.join(outputDir, data.filePath);
} else {
fullPath = path.join(outputDir, 'src', data.filePath);
}

await writeFile(fullPath, content);

return {
success: true,
message: `Created file: ${data.filePath}`,
filesCreated: [fullPath],
};
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

Normalize write/format failures to ToolResult instead of throwing.

The tool API returns ToolResult, but errors from formatting/writing currently bubble out and can break orchestration flow.

Suggested fix
-  // Format code if possible
-  let content = data.content;
-  if (fileType) {
-    content = await formatCode(content, fileType);
-  }
+  try {
+    // Format code if possible
+    let content = data.content;
+    if (fileType) {
+      content = await formatCode(content, fileType);
+    }

-  await writeFile(fullPath, content);
+    await writeFile(fullPath, content);

-  return {
-    success: true,
-    message: `Created file: ${data.filePath}`,
-    filesCreated: [fullPath],
-  };
+    return {
+      success: true,
+      message: `Created file: ${data.filePath}`,
+      filesCreated: [fullPath],
+    };
+  } catch (err) {
+    const msg = err instanceof Error ? err.message : String(err);
+    return { success: false, error: `Failed to create file: ${msg}` };
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/tools/generator/createFile.ts` around lines 57 - 77, The
createFile flow currently lets errors from formatCode and writeFile escape; wrap
the formatCode call and the await writeFile(fullPath, content) in try/catch
blocks (or a single try around both) and return a ToolResult object on failure
instead of throwing: catch the error from formatCode (when calling
formatCode(content, fileType)) and from writeFile(fullPath, content) and return
{ success: false, message: `Failed to create ${data.filePath}: ${err.message ||
String(err)}`, filesCreated: [] } so callers always get a ToolResult; keep
success path returning the existing success result.

Comment on lines +63 to +71
// Determine the full path
let fullPath: string;
if (data.filePath.startsWith('src/')) {
fullPath = path.join(outputDir, data.filePath);
} else {
fullPath = path.join(outputDir, 'src', data.filePath);
}

await writeFile(fullPath, content);
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

Block path traversal before writing files.

Lines [63-69] allow ../ and absolute path escape; Line [71] then writes outside the project root.

Suggested fix
-  // Determine the full path
+  const rootDir = path.resolve(outputDir);
+  // Determine the full path
   let fullPath: string;
   if (data.filePath.startsWith('src/')) {
-    fullPath = path.join(outputDir, data.filePath);
+    fullPath = path.resolve(rootDir, data.filePath);
   } else {
-    fullPath = path.join(outputDir, 'src', data.filePath);
+    fullPath = path.resolve(rootDir, 'src', data.filePath);
+  }
+
+  if (fullPath !== rootDir && !fullPath.startsWith(rootDir + path.sep)) {
+    return {
+      success: false,
+      error: 'Access denied: path traversal detected',
+    };
   }

   await writeFile(fullPath, content);
📝 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
// Determine the full path
let fullPath: string;
if (data.filePath.startsWith('src/')) {
fullPath = path.join(outputDir, data.filePath);
} else {
fullPath = path.join(outputDir, 'src', data.filePath);
}
await writeFile(fullPath, content);
const rootDir = path.resolve(outputDir);
// Determine the full path
let fullPath: string;
if (data.filePath.startsWith('src/')) {
fullPath = path.resolve(rootDir, data.filePath);
} else {
fullPath = path.resolve(rootDir, 'src', data.filePath);
}
if (fullPath !== rootDir && !fullPath.startsWith(rootDir + path.sep)) {
return {
success: false,
error: 'Access denied: path traversal detected',
};
}
await writeFile(fullPath, content);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/tools/generator/createFile.ts` around lines 63 - 71, The
current logic in createFile.ts builds fullPath from data.filePath and then calls
writeFile, allowing directory traversal or absolute escapes; fix by normalizing
and validating the target path before writing: first reject/normalize any input
that is absolute or contains '..' segments (data.filePath), then construct the
final resolvedPath with path.resolve(outputDir, data.filePath) or
path.resolve(outputDir, 'src', data.filePath) depending on the src prefix, and
verify path.relative(outputDir, resolvedPath) does not start with '..' (or
ensure resolvedPath.startsWith(path.resolve(outputDir))). Only call writeFile
with the validated resolvedPath; reference symbols: data.filePath, fullPath (or
resolvedPath), outputDir, and writeFile.

export default () => {
const config = {} as PowerPartial<EggAppConfig>;

config.keys = 'mini-muse-secret-key';
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

Hardcoded secret key should use environment variable.

Using a hardcoded secret key in config is a security risk. In production, this key is used for signing cookies and sessions. Consider reading from environment variable with a fallback only for development.

🔒 Proposed fix
-  config.keys = 'mini-muse-secret-key';
+  config.keys = process.env.EGG_SERVER_SECRET || 'mini-muse-secret-key-dev';

Add EGG_SERVER_SECRET to .env.example as well.

📝 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
config.keys = 'mini-muse-secret-key';
config.keys = process.env.EGG_SERVER_SECRET || 'mini-muse-secret-key-dev';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/config/config.default.ts` at line 9, The config currently hardcodes
the session/cookie secret via config.keys = 'mini-muse-secret-key'; replace this
by reading from an environment variable (use process.env.EGG_SERVER_SECRET or
similar) with a safe development fallback only when NODE_ENV !== 'production',
and assign that value to config.keys; also add EGG_SERVER_SECRET to .env.example
so deploys know to set it. Ensure the referenced symbol is config.keys and the
env var name is EGG_SERVER_SECRET.

Comment on lines +60 to +92
unsubscribeRef.current = subscribeToProgress(
taskId,
(event) => {
const ev = event as ProgressEvent;
// Filter out events from before the modification started
if (ev.timestamp < modifyStartTimeRef.current) return;
setModifyEvents(prev => [...prev, ev]);

if (ev.type === 'completed' || ev.type === 'modify_completed') {
// Refresh chat history, files, and selected file content
getChatHistory(taskId).then(history => {
setChatHistory(history);
setModifyEvents([]);
setIsModifying(false);
});

getFiles(taskId).then(onFilesUpdated);

if (selectedFile) {
getFileContent(taskId, selectedFile).then(content => {
onFileContentUpdated(selectedFile, content);
}).catch(() => {});
}
}

if (ev.type === 'error') {
setIsModifying(false);
}
},
() => {
// SSE connection closed
}
);
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

Close previous/current SSE subscriptions to prevent leaks and duplicate streams.

Lines [60-92] overwrite unsubscribeRef.current without closing existing subscriptions, and completion/error paths never unsubscribe.

Suggested fix
-      unsubscribeRef.current = subscribeToProgress(
+      unsubscribeRef.current?.();
+      unsubscribeRef.current = subscribeToProgress(
         taskId,
         (event) => {
@@
           if (ev.type === 'completed' || ev.type === 'modify_completed') {
+            unsubscribeRef.current?.();
+            unsubscribeRef.current = null;
             // Refresh chat history, files, and selected file content
@@
           if (ev.type === 'error') {
+            unsubscribeRef.current?.();
+            unsubscribeRef.current = null;
             setIsModifying(false);
           }
📝 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
unsubscribeRef.current = subscribeToProgress(
taskId,
(event) => {
const ev = event as ProgressEvent;
// Filter out events from before the modification started
if (ev.timestamp < modifyStartTimeRef.current) return;
setModifyEvents(prev => [...prev, ev]);
if (ev.type === 'completed' || ev.type === 'modify_completed') {
// Refresh chat history, files, and selected file content
getChatHistory(taskId).then(history => {
setChatHistory(history);
setModifyEvents([]);
setIsModifying(false);
});
getFiles(taskId).then(onFilesUpdated);
if (selectedFile) {
getFileContent(taskId, selectedFile).then(content => {
onFileContentUpdated(selectedFile, content);
}).catch(() => {});
}
}
if (ev.type === 'error') {
setIsModifying(false);
}
},
() => {
// SSE connection closed
}
);
unsubscribeRef.current?.();
unsubscribeRef.current = subscribeToProgress(
taskId,
(event) => {
const ev = event as ProgressEvent;
// Filter out events from before the modification started
if (ev.timestamp < modifyStartTimeRef.current) return;
setModifyEvents(prev => [...prev, ev]);
if (ev.type === 'completed' || ev.type === 'modify_completed') {
unsubscribeRef.current?.();
unsubscribeRef.current = null;
// Refresh chat history, files, and selected file content
getChatHistory(taskId).then(history => {
setChatHistory(history);
setModifyEvents([]);
setIsModifying(false);
});
getFiles(taskId).then(onFilesUpdated);
if (selectedFile) {
getFileContent(taskId, selectedFile).then(content => {
onFileContentUpdated(selectedFile, content);
}).catch(() => {});
}
}
if (ev.type === 'error') {
unsubscribeRef.current?.();
unsubscribeRef.current = null;
setIsModifying(false);
}
},
() => {
// SSE connection closed
}
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/components/ChatPanel.tsx` around lines 60 - 92, The
SSE subscription logic overwrites unsubscribeRef.current from
subscribeToProgress without closing any previous subscription and never
unsubscribes on completion/error or when the SSE closes; before assigning
unsubscribeRef.current call and await/clear any existing unsubscribe (if
present) and set it to null, and ensure the callback paths (ev.type ===
'completed' / 'modify_completed', ev.type === 'error', and the
subscribeToProgress close callback) invoke unsubscribeRef.current() and then
clear it; update usages around subscribeToProgress, unsubscribeRef.current,
setIsModifying, getChatHistory, getFiles, and getFileContent to reliably clean
up the SSE handle.

Comment on lines +70 to +74
getChatHistory(taskId).then(history => {
setChatHistory(history);
setModifyEvents([]);
setIsModifying(false);
});
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

Don’t gate setIsModifying(false) on successful history refresh.

If getChatHistory fails, Line [73] is never reached and the panel can remain stuck in modifying mode.

Suggested fix
-            getChatHistory(taskId).then(history => {
-              setChatHistory(history);
-              setModifyEvents([]);
-              setIsModifying(false);
-            });
+            getChatHistory(taskId)
+              .then(history => {
+                setChatHistory(history);
+              })
+              .catch(() => {})
+              .finally(() => {
+                setModifyEvents([]);
+                setIsModifying(false);
+              });
📝 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
getChatHistory(taskId).then(history => {
setChatHistory(history);
setModifyEvents([]);
setIsModifying(false);
});
getChatHistory(taskId)
.then(history => {
setChatHistory(history);
})
.catch(() => {})
.finally(() => {
setModifyEvents([]);
setIsModifying(false);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/components/ChatPanel.tsx` around lines 70 - 74, Ensure
setIsModifying(false) runs regardless of getChatHistory outcome by moving it out
of the success-only then branch; update the usage around getChatHistory(taskId)
(where setChatHistory and setModifyEvents are called) to handle errors and
always call setIsModifying(false) (e.g., use .catch(...) to handle failures and
a .finally(...) to call setIsModifying(false) or ensure setIsModifying(false) is
invoked in both then and catch paths) so the panel cannot remain stuck in
modifying mode.

const root: TreeNode[] = [];

for (const file of files.sort()) {
const parts = file.split('/');
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

Normalize path separators before splitting.

Line [20] assumes /. If backend runs on Windows, path.relative may return \, and the tree will flatten incorrectly.

Suggested fix
-    const parts = file.split('/');
+    const normalized = file.replace(/\\/g, '/');
+    const parts = normalized.split('/');
📝 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
const parts = file.split('/');
const normalized = file.replace(/\\/g, '/');
const parts = normalized.split('/');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/components/FileTree.tsx` at line 20, The code splits
file paths using a hardcoded '/' which breaks when path.relative returns Windows
backslashes; normalize separators before splitting by converting backslashes to
forward slashes (or use Node's path.sep logic) so the variable parts =
file.split('/') receives consistent separators—update the logic around the
variable parts (where file is derived, e.g., in the FileTree component) to
replace backslashes with '/' (or normalize the path) then split, ensuring
consistent tree structure across platforms.

Comment on lines +1 to +6
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
@apply bg-gray-50 text-gray-900;
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

# First, check for lint configuration files
echo "=== Searching for lint config files ==="
fd -HI 'biome.json|.biome.json|stylelint.config.*|.stylelintrc*' . 2>/dev/null || echo "No config files found"

echo -e "\n=== Checking project structure ==="
ls -la mini-muse/frontend/ 2>/dev/null | head -20 || echo "Directory not found"

echo -e "\n=== Checking for CSS-related config ==="
find . -maxdepth 3 -type f \( -name "package.json" -o -name "*lint*" -o -name "*tailwind*" \) 2>/dev/null | grep -E "(package.json|lint|tailwind)" | head -20

echo -e "\n=== Checking frontend directory structure ==="
find mini-muse/frontend -maxdepth 2 -type f 2>/dev/null | head -30

echo -e "\n=== Looking for postcss config ==="
fd -HI 'postcss.config.*|tailwind.config.*' . 2>/dev/null || echo "No postcss/tailwind config found"

Repository: eggjs/agent-examples

Length of output: 1371


🏁 Script executed:

#!/bin/bash

echo "=== .stylelintrc.json content ==="
cat -n .stylelintrc.json

echo -e "\n=== biome.json content (CSS section) ==="
cat -n biome.json | grep -A 30 -i "css\|lint"

echo -e "\n=== Full biome.json ==="
cat -n biome.json

echo -e "\n=== mini-muse/frontend/postcss.config.js ==="
cat -n mini-muse/frontend/postcss.config.js

echo -e "\n=== mini-muse/frontend/tailwind.config.js ==="
head -50 mini-muse/frontend/tailwind.config.js | cat -n

Repository: eggjs/agent-examples

Length of output: 5523


Configure StyleLint and Biome to recognize Tailwind directives.

The .stylelintrc.json extends stylelint-config-standard-scss, which treats @tailwind, @apply, and @layer as unknown at-rules and will fail linting. Add Tailwind support by either:

  • Installing and using stylelint-config-tailwindcss in the StyleLint config, or
  • Explicitly disabling the at-rule-no-unknown rule in .stylelintrc.json for Tailwind at-rules

Biome's CSS linter (enabled in biome.json) also lacks Tailwind directive support.

🧰 Tools
🪛 Biome (2.4.4)

[error] 6-6: Tailwind-specific syntax is disabled.

(parse)


[error] 1-1: Unexpected unknown at-rule: tailwind

(lint/suspicious/noUnknownAtRules)


[error] 2-2: Unexpected unknown at-rule: tailwind

(lint/suspicious/noUnknownAtRules)


[error] 3-3: Unexpected unknown at-rule: tailwind

(lint/suspicious/noUnknownAtRules)

🪛 Stylelint (17.3.0)

[error] 1-1: Unexpected unknown at-rule "@tailwind" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 2-2: Unexpected unknown at-rule "@tailwind" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 3-3: Unexpected unknown at-rule "@tailwind" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

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

In `@mini-muse/frontend/src/index.css` around lines 1 - 6, Style linting fails
because Tailwind at-rules like `@tailwind` and `@apply` in index.css are
unrecognized; update your linter configs to accept Tailwind directives by either
adding the Tailwind StyleLint preset (stylelint-config-tailwindcss) to your
StyleLint extends or by disabling the at-rule-no-unknown rule for Tailwind
at-rules in .stylelintrc.json (allow `@tailwind`, `@apply`, `@layer`), and likewise
configure biome.json’s CSS linter to ignore or support those at-rules so
`@tailwind`, `@apply` and `@layer` no longer trigger errors.

@jerryliang64 jerryliang64 changed the title feat: add mini-muse - AI web app generator example [WIP] feat: add mini-muse - AI web app generator example Mar 1, 2026
jerryliang64 and others added 3 commits March 11, 2026 18:21
…t API

- Replace traditional Egg.js controllers/services with tegg decorators
- Backend: @AgentController (auto Thread/Run API), @HTTPController (projects), @SingletonProto (services)
- Frontend: fetch+ReadableStream SSE parsing, taskId→threadId migration
- Storage: OSSAgentStore replaces in-memory TaskManager
- Orchestrator: async generator yielding AgentStreamMessage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix config exports: use defineConfig/plain objects instead of removed EggPlugin/EggAppConfig types
- Add "type": "module" to enable ts-node/esm loader for dynamic import() of .ts files
- Update tsconfig to module:esnext/bundler with ts-node ESM experimental resolver
- Create app/service/package.json as tegg module to avoid controller double-registration
- Fix DI inject names to match class names (aiClientService, toolsService, etc.)
- Rename HomeController to SpaFallbackController to avoid name collision with egg built-in

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add response validation in AiClientService for empty/malformed API responses
- Filter out 'thinking' content blocks that some models (e.g. GLM-4.7) return
- Only include system/tools params when non-empty to avoid proxy issues
- Restore .gitignore (was missing from tracked files)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: 11

♻️ Duplicate comments (3)
mini-muse/app/service/aiClient.ts (1)

74-80: ⚠️ Potential issue | 🟠 Major

Fail fast when Anthropic credentials are missing.

Line 76 can construct the client with both apiKey and authToken undefined, so configuration errors only surface on the first live request. Reject that case here with a clear error instead.

Possible fix
   private getClient(): Anthropic {
     if (!this.client) {
+      const apiKey = process.env.ANTHROPIC_API_KEY;
+      const authToken = process.env.ANTHROPIC_AUTH_TOKEN;
+      if (!apiKey && !authToken) {
+        throw new Error(
+          'Missing Anthropic credentials: set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN'
+        );
+      }
+
       this.client = new Anthropic({
         baseURL: process.env.ANTHROPIC_BASE_URL || undefined,
-        authToken: process.env.ANTHROPIC_AUTH_TOKEN || undefined,
-        apiKey: process.env.ANTHROPIC_API_KEY || undefined,
+        authToken: authToken || undefined,
+        apiKey: apiKey || undefined,
       });
     }
     return this.client;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/service/aiClient.ts` around lines 74 - 80, The getClient method
currently allows constructing an Anthropic client with both apiKey and authToken
undefined; update getClient to fail fast by checking
process.env.ANTHROPIC_API_KEY and process.env.ANTHROPIC_AUTH_TOKEN (and/or
existing this.client) before instantiating Anthropic and throw a clear Error if
neither credential is provided, so the code in getClient (which sets this.client
= new Anthropic(...)) never creates a client with missing credentials and
surfaces configuration problems immediately.
mini-muse/app/service/preview.ts (2)

73-83: ⚠️ Potential issue | 🟠 Major

Make the npm registry configurable and bound the install time.

Hardcoding registry.npmmirror.com makes preview startup region-specific, and this Promise can hang forever on a stalled install. Read the registry and install timeout from config, and kill the child when the timeout expires.

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

In `@mini-muse/app/service/preview.ts` around lines 73 - 83, Replace the hardcoded
registry and unbounded install Promise in the npm install block: read registry
and installTimeout from config (e.g., preview config) and set env
npm_config_registry to that value when spawning child in the Promise inside the
function that calls spawn; start a timer (setTimeout) using installTimeout that,
when fired, kills the child process (child.kill()) and rejects the Promise with
a timeout Error; when the child emits 'close' or 'error' clear the timer and
resolve/reject appropriately; ensure the rejection includes the exit code or
timeout reason so callers of this Promise can handle failures.

44-46: ⚠️ Potential issue | 🟠 Major

Persist failed preview state on both startup rejection and later process exit.

The outer catch only logs rejected startup, so install failures can stay stuck at installing; the close handler also ignores exits once the preview became running, so dead servers can still report healthy. Persist info.status = 'failed' and info.error in both places so getStatus() can surface the failure.

Also applies to: 126-132

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

In `@mini-muse/app/service/preview.ts` around lines 44 - 46, When
installAndStart(threadId, outputDir, info) rejects or the preview process later
exits abnormally, update and persist the preview info so getStatus() can surface
failures: in the .catch handler for installAndStart set info.status = 'failed'
and info.error (e.g., err.message or err.stack) before persisting/committing the
info, and likewise in the process close/exit handler detect non-zero/abnormal
exits and set info.status = 'failed' and info.error to the exit reason/details
and persist; ensure you reference the same info object used by getStatus() so
the status change is visible.
🧹 Nitpick comments (3)
mini-muse/docs/design.md (1)

220-235: Add language specifiers to fenced code blocks.

Per markdownlint, the following code blocks are missing language specifiers:

  • Line 220: Directory structure (use text or plaintext)
  • Line 255: SSE event format (use text or http)
  • Line 366: Directory structure (use text or plaintext)
📝 Proposed fix
-```
+```text
 frontend/src/
 ├── pages/
-```
+```text
 event: thread.run.created
 data: {"id":"run_xxx","threadId":"thread_xxx","status":"queued",...}
-```
+```text
 mini-muse/
 ├── app/

Also applies to: 255-267, 366-399

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

In `@mini-muse/docs/design.md` around lines 220 - 235, Several fenced code blocks
in design.md (the directory listings and the SSE event example) lack language
specifiers; edit the three blocks that show the frontend/mini-muse directory
trees and the SSE event format (the blocks around the directory structure at the
top and bottom and the SSE event example in the middle) and add appropriate
language identifiers such as "text" or "plaintext" for the tree blocks and
"text" or "http" for the SSE event block so markdownlint warnings are resolved.
mini-muse/frontend/src/pages/ResultPage.tsx (1)

81-84: Consider HTTPS/HTTP mixed content when page is served over HTTPS.

The preview URL is constructed with http:// which will cause mixed content issues if the main app is served over HTTPS. For local development this is typically fine, but in production deployments the iframe may be blocked.

💡 Optional: Use same protocol as current page
   const previewUrl = preview.port
-    ? `http://${window.location.hostname}:${preview.port}`
+    ? `${window.location.protocol}//${window.location.hostname}:${preview.port}`
     : '';

Note: This only works if the preview server also supports HTTPS, which may not be the case for Vite dev server without additional configuration.

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

In `@mini-muse/frontend/src/pages/ResultPage.tsx` around lines 81 - 84, The
preview URL currently hardcodes "http://" which causes mixed-content failures
when the app is served over HTTPS; change the construction of previewUrl (the
code using preview.port and window.location.hostname) to use the current page
protocol by prefixing with window.location.protocol + '//' so the iframe uses
the same protocol as the host page (i.e., build the URL from
window.location.protocol, window.location.hostname and preview.port), keeping
the empty-string fallback when preview.port is falsy.
mini-muse/frontend/src/services/api.ts (1)

175-184: Weak type safety and potential timestamp assumption issue.

  1. The function uses any types extensively which bypasses TypeScript's type checking.
  2. Line 182 assumes createdAt is in seconds (multiplies by 1000), but this should be verified against the backend's actual timestamp format.
💡 Proposed improvement
+interface ThreadMessage {
+  role: string;
+  content?: Array<{ text?: { value?: string } }>;
+  createdAt: number;
+}
+
+interface ThreadResponse {
+  messages?: ThreadMessage[];
+}
+
 export async function getChatHistory(threadId: string): Promise<{ role: string; content: string; timestamp: number }[]> {
-  const thread = await getThread(threadId);
+  const thread: ThreadResponse = await getThread(threadId);
   if (!thread?.messages) return [];
-  return thread.messages.map((msg: any) => ({
+  return thread.messages.map((msg) => ({
     role: msg.role,
-    content: msg.content?.map((c: any) => c.text?.value || '').join('') || '',
+    content: msg.content?.map((c) => c.text?.value || '').join('') || '',
     timestamp: msg.createdAt * 1000,
   }));
 }

Please verify that the backend returns createdAt as Unix timestamp in seconds (not milliseconds).

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

In `@mini-muse/frontend/src/services/api.ts` around lines 175 - 184, The
getChatHistory function currently uses unsafe any types and assumes
msg.createdAt is seconds; replace the any usages by defining proper
types/interfaces for the thread and message objects (e.g., Thread { messages:
Message[] } and Message with role, content array items, and createdAt), update
getChatHistory to use those types when calling getThread, validate msg.createdAt
(check typeof and value range) before multiplying by 1000 and only convert when
the backend returns seconds (avoid double-conversion for milliseconds), and
replace the content mapping to safely handle undefined/null content items (use
explicit checks on msg.content and content[i].text.value) instead of any so
TypeScript can catch errors; also verify with the backend whether createdAt is
in seconds or milliseconds and change the conversion accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mini-muse/app/controller/HomeController.ts`:
- Around line 5-15: The SPA fallback currently matches all GET routes
(HTTPMethod path '/*') and lives in SpaFallbackController.index; add a guard at
the start of the index method to skip serving index.html for API routes: check
ctx.request.path (or ctx.path) and if it startsWith '/api' then set an
appropriate API-style response (e.g., ctx.status = 404; ctx.body = { error: 'Not
Found' }; return) so API GETs are not swallowed by the SPA fallback;
alternatively, adjust the HTTPMethod path to explicitly exclude '/api' paths if
your router supports that.

In `@mini-muse/app/controller/MuseAgentController.ts`:
- Around line 13-27: In createStore(), validate required OSS configuration env
vars (at minimum OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET and OSS_BUCKET;
optionally OSS_REGION/OSS_ENDPOINT) before constructing
OSSObject/OSSObjectStorageClient/OSSAgentStore: check each required variable is
non-empty and if any are missing, throw or return a descriptive error (e.g.,
"missing OSS_ACCESS_KEY_ID") to fail fast rather than building a client with
empty credentials; perform this validation at the top of the createStore()
method and only construct OSSObject, OSSObjectStorageClient, and OSSAgentStore
when all validations pass.
- Around line 33-37: The execRun implementation assumes input.input.messages[0]
exists and that content parts have a text field; validate input in execRun
before using userMessage: check that input?.input?.messages is an array with at
least one element, that the first message has a content property which is either
a string or an array, and if it's an array ensure every element has a text
string; if any of these checks fail, throw or return a controlled bad-request
error (e.g., HTTP 400 / a specific error) rather than letting
userMessage/description access throw. Ensure you update references to
userMessage and description to use the validated values.

In `@mini-muse/app/controller/ProjectController.ts`:
- Around line 23-24: The threadId interpolation allows path traversal; add a
shared helper resolveProjectDir(threadId) that does: base =
path.resolve('output'), projectDir = path.resolve(base, threadId), rel =
path.relative(base, projectDir) and reject when rel === '' ||
path.isAbsolute(rel) || rel.startsWith('..'); use this helper wherever project
paths are built (the files method and the other similar blocks referenced around
lines 43-46, 62-64, 83-86) and apply the same path.relative check to any derived
filePath before reading/zipping to ensure filePath remains inside the resolved
projectDir.

In `@mini-muse/app/service/orchestrator.ts`:
- Around line 52-62: The loop currently only checks signal?.aborted before
awaiting heavy operations, so cancellation arriving during createMessage or tool
execution is ignored; modify the loop to propagate the AbortSignal into
in-flight work by passing the signal into
this.aiClientService.createMessage(...) (and any tool execution calls referenced
in this file), and after each await check signal?.aborted and immediately return
or throw to stop further processing and side-effects; update usages of
createMessage and tool execution helpers so they accept an AbortSignal parameter
and respect it during their async work (e.g., cancel network requests and stop
file mutations).

In `@mini-muse/app/service/preview.ts`:
- Around line 73-95: The code currently spawns untrusted project processes
directly (spawn('npm', ['install'], ...) and spawn('npx', ['vite', ...]) in
start()), which is an RCE risk and leaves stale entries in the previews map on
failure; update start() to run all untrusted project commands inside an isolated
sandbox (e.g., Docker/nsjail/other container runtime) instead of host spawn, or
at minimum run npm install with scripts disabled (npm ci / --ignore-scripts)
while you implement containerization, make the registry configurable via a
passed-in option instead of hardcoding npm_config_registry, add a timeout (e.g.,
5 minutes) to the npm install promise and to starting the vite server, and
ensure any failure path/catch in start() cleans up the previews map and resets
info.status/info.port to avoid stale state (reference symbols: start(), spawn
calls, npm install block, npx vite child, outputDir, findFreePort, previews map,
info.status, info.port).

In `@mini-muse/frontend/src/pages/ProgressPage.tsx`:
- Around line 24-46: Race when cleanup runs before startRun's promise resolves:
create an AbortController before calling startRun, assign cancelRef.current =
controller.abort.bind(controller) immediately, and pass controller.signal into
startRun (update startRun signature to accept an optional signal and use it
inside the SSE/fetch logic); this ensures the cleanup callback can synchronously
abort the in-flight run even if startRun's returned cancel function hasn't
resolved yet.

In `@mini-muse/frontend/src/pages/ResultPage.tsx`:
- Around line 113-118: The anchor uses a non-null assertion on threadId
(threadId!) which can cause a runtime error if threadId is undefined; update the
ResultPage JSX so the "Download ZIP" anchor is only rendered when threadId is
defined (e.g., wrap the <a ...> that calls getDownloadUrl(threadId) in a
conditional check like if (threadId) or render a disabled fallback), or
alternatively compute a safe href (fallback '#' or disable) and avoid using the
non-null assertion; locate the getDownloadUrl usage and the Download ZIP anchor
in ResultPage.tsx and remove the threadId! assertion.

In `@mini-muse/frontend/src/services/api.ts`:
- Around line 55-59: The code currently may call onDone() twice (once after the
read loop and once when handleSSEEvent processes a 'done' event); to fix, create
a single-use wrapper or boolean guard (e.g., doneCalled = false or a once(fn)
wrapper) and use that wrapped callback everywhere you currently call onDone,
including in handleSSEEvent and after the stream loop (instead of calling onDone
directly); ensure the guard is checked/set before invoking so the 'done' event
and the stream-close path cannot both trigger the real onDone implementation.
- Line 25: The code currently uses a non-null assertion on response.body when
creating the reader (const reader = response.body!.getReader()), which can throw
if body is null; update the logic to guard against a missing body by checking
response.body before calling getReader(), and handle the null case (e.g., throw
a descriptive error or return an empty/closed stream) so caller code won't
encounter a runtime exception when response.body is absent.

---

Duplicate comments:
In `@mini-muse/app/service/aiClient.ts`:
- Around line 74-80: The getClient method currently allows constructing an
Anthropic client with both apiKey and authToken undefined; update getClient to
fail fast by checking process.env.ANTHROPIC_API_KEY and
process.env.ANTHROPIC_AUTH_TOKEN (and/or existing this.client) before
instantiating Anthropic and throw a clear Error if neither credential is
provided, so the code in getClient (which sets this.client = new Anthropic(...))
never creates a client with missing credentials and surfaces configuration
problems immediately.

In `@mini-muse/app/service/preview.ts`:
- Around line 73-83: Replace the hardcoded registry and unbounded install
Promise in the npm install block: read registry and installTimeout from config
(e.g., preview config) and set env npm_config_registry to that value when
spawning child in the Promise inside the function that calls spawn; start a
timer (setTimeout) using installTimeout that, when fired, kills the child
process (child.kill()) and rejects the Promise with a timeout Error; when the
child emits 'close' or 'error' clear the timer and resolve/reject appropriately;
ensure the rejection includes the exit code or timeout reason so callers of this
Promise can handle failures.
- Around line 44-46: When installAndStart(threadId, outputDir, info) rejects or
the preview process later exits abnormally, update and persist the preview info
so getStatus() can surface failures: in the .catch handler for installAndStart
set info.status = 'failed' and info.error (e.g., err.message or err.stack)
before persisting/committing the info, and likewise in the process close/exit
handler detect non-zero/abnormal exits and set info.status = 'failed' and
info.error to the exit reason/details and persist; ensure you reference the same
info object used by getStatus() so the status change is visible.

---

Nitpick comments:
In `@mini-muse/docs/design.md`:
- Around line 220-235: Several fenced code blocks in design.md (the directory
listings and the SSE event example) lack language specifiers; edit the three
blocks that show the frontend/mini-muse directory trees and the SSE event format
(the blocks around the directory structure at the top and bottom and the SSE
event example in the middle) and add appropriate language identifiers such as
"text" or "plaintext" for the tree blocks and "text" or "http" for the SSE event
block so markdownlint warnings are resolved.

In `@mini-muse/frontend/src/pages/ResultPage.tsx`:
- Around line 81-84: The preview URL currently hardcodes "http://" which causes
mixed-content failures when the app is served over HTTPS; change the
construction of previewUrl (the code using preview.port and
window.location.hostname) to use the current page protocol by prefixing with
window.location.protocol + '//' so the iframe uses the same protocol as the host
page (i.e., build the URL from window.location.protocol,
window.location.hostname and preview.port), keeping the empty-string fallback
when preview.port is falsy.

In `@mini-muse/frontend/src/services/api.ts`:
- Around line 175-184: The getChatHistory function currently uses unsafe any
types and assumes msg.createdAt is seconds; replace the any usages by defining
proper types/interfaces for the thread and message objects (e.g., Thread {
messages: Message[] } and Message with role, content array items, and
createdAt), update getChatHistory to use those types when calling getThread,
validate msg.createdAt (check typeof and value range) before multiplying by 1000
and only convert when the backend returns seconds (avoid double-conversion for
milliseconds), and replace the content mapping to safely handle undefined/null
content items (use explicit checks on msg.content and content[i].text.value)
instead of any so TypeScript can catch errors; also verify with the backend
whether createdAt is in seconds or milliseconds and change the conversion
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f2ea815-abc8-48bb-8c1e-f3ac5178365b

📥 Commits

Reviewing files that changed from the base of the PR and between 71a7d0c and 33b8e37.

📒 Files selected for processing (23)
  • mini-muse/.env.example
  • mini-muse/.gitignore
  • mini-muse/app/controller/HomeController.ts
  • mini-muse/app/controller/MuseAgentController.ts
  • mini-muse/app/controller/ProjectController.ts
  • mini-muse/app/service/aiClient.ts
  • mini-muse/app/service/orchestrator.ts
  • mini-muse/app/service/package.json
  • mini-muse/app/service/preview.ts
  • mini-muse/app/service/tools.ts
  • mini-muse/config/config.default.ts
  • mini-muse/config/config.local.ts
  • mini-muse/config/plugin.ts
  • mini-muse/docs/design.md
  • mini-muse/frontend/src/App.tsx
  • mini-muse/frontend/src/components/ChatPanel.tsx
  • mini-muse/frontend/src/pages/HomePage.tsx
  • mini-muse/frontend/src/pages/ProgressPage.tsx
  • mini-muse/frontend/src/pages/ResultPage.tsx
  • mini-muse/frontend/src/services/api.ts
  • mini-muse/frontend/src/types/index.ts
  • mini-muse/package.json
  • mini-muse/tsconfig.json
✅ Files skipped from review due to trivial changes (1)
  • mini-muse/.gitignore
🚧 Files skipped from review as they are similar to previous changes (9)
  • mini-muse/package.json
  • mini-muse/config/config.local.ts
  • mini-muse/config/config.default.ts
  • mini-muse/config/plugin.ts
  • mini-muse/frontend/src/types/index.ts
  • mini-muse/app/service/tools.ts
  • mini-muse/tsconfig.json
  • mini-muse/frontend/src/components/ChatPanel.tsx
  • mini-muse/frontend/src/App.tsx

Comment on lines +5 to +15
@HTTPController({ path: '/' })
export class SpaFallbackController {
@HTTPMethod({ method: HTTPMethodEnum.GET, path: '/*' })
async index(@HTTPContext() ctx: any) {
const indexPath = path.join(process.cwd(), 'app/public/index.html');
try {
const html = await fs.readFile(indexPath, 'utf-8');
ctx.type = 'text/html';
return html;
} catch {
return 'Mini-Muse is running. Please build the frontend first.';
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

Keep the SPA fallback off /api/*.

This catch-all GET route also matches API URLs, so unknown GET /api/... requests will return HTML instead of a JSON 404, and route registration order can also shadow real GET controllers. Guard /api before serving index.html, or narrow the fallback to non-API paths only.

Possible fix
 `@HTTPController`({ path: '/' })
 export class SpaFallbackController {
   `@HTTPMethod`({ method: HTTPMethodEnum.GET, path: '/*' })
   async index(`@HTTPContext`() ctx: any) {
+    if (ctx.path === '/api' || ctx.path.startsWith('/api/')) {
+      ctx.throw(404, 'Not Found');
+    }
+
     const indexPath = path.join(process.cwd(), 'app/public/index.html');
     try {
       const html = await fs.readFile(indexPath, 'utf-8');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/controller/HomeController.ts` around lines 5 - 15, The SPA
fallback currently matches all GET routes (HTTPMethod path '/*') and lives in
SpaFallbackController.index; add a guard at the start of the index method to
skip serving index.html for API routes: check ctx.request.path (or ctx.path) and
if it startsWith '/api' then set an appropriate API-style response (e.g.,
ctx.status = 404; ctx.body = { error: 'Not Found' }; return) so API GETs are not
swallowed by the SPA fallback; alternatively, adjust the HTTPMethod path to
explicitly exclude '/api' paths if your router supports that.

Comment on lines +13 to +27
async createStore(): Promise<AgentStore> {
const endpoint = process.env.OSS_ENDPOINT || `https://${process.env.OSS_REGION || 'oss-cn-hangzhou'}.aliyuncs.com`;

const ossClient = new OSSObject({
endpoint,
accessKeyId: process.env.OSS_ACCESS_KEY_ID || '',
accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET || '',
bucket: process.env.OSS_BUCKET || '',
});

const storageClient = new OSSObjectStorageClient(ossClient);
const store = new OSSAgentStore({
client: storageClient,
prefix: process.env.OSS_PREFIX || 'mini-muse/',
});
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

Validate OSS config before constructing the store.

This can build the OSS client with empty credentials or bucket names, so misconfiguration only shows up later on the first persistence call. Fail fast here with a clear error if the required OSS env vars are missing.

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

In `@mini-muse/app/controller/MuseAgentController.ts` around lines 13 - 27, In
createStore(), validate required OSS configuration env vars (at minimum
OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET and OSS_BUCKET; optionally
OSS_REGION/OSS_ENDPOINT) before constructing
OSSObject/OSSObjectStorageClient/OSSAgentStore: check each required variable is
non-empty and if any are missing, throw or return a descriptive error (e.g.,
"missing OSS_ACCESS_KEY_ID") to fail fast rather than building a client with
empty credentials; perform this validation at the top of the createStore()
method and only construct OSSObject, OSSObjectStorageClient, and OSSAgentStore
when all validations pass.

Comment on lines +33 to +37
async *execRun(input: CreateRunInput, signal?: AbortSignal): AsyncGenerator<AgentStreamMessage> {
const userMessage = input.input.messages[0];
const description = typeof userMessage.content === 'string'
? userMessage.content
: userMessage.content.map((p: { type: string; text: string }) => p.text).join('');
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

Reject empty or non-text run inputs up front.

Line 34 assumes at least one message exists and that every content part has a text field. Malformed requests will throw here instead of returning a controlled bad-request error.

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

In `@mini-muse/app/controller/MuseAgentController.ts` around lines 33 - 37, The
execRun implementation assumes input.input.messages[0] exists and that content
parts have a text field; validate input in execRun before using userMessage:
check that input?.input?.messages is an array with at least one element, that
the first message has a content property which is either a string or an array,
and if it's an array ensure every element has a text string; if any of these
checks fail, throw or return a controlled bad-request error (e.g., HTTP 400 / a
specific error) rather than letting userMessage/description access throw. Ensure
you update references to userMessage and description to use the validated
values.

Comment on lines +23 to +24
async files(@HTTPParam({ name: 'threadId' }) threadId: string) {
const outputDir = `./output/${threadId}`;
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

Harden threadId and filePath path resolution.

threadId is interpolated directly into ./output/${threadId}, so values like .. escape the output root, and the startsWith() check on filePath is bypassable by sibling-prefix paths such as ../app2/... when the project dir is app. That exposes arbitrary file reads/zips and lets the preview endpoint run commands outside the intended project directory.

Use a shared resolveProjectDir() helper that resolves against path.resolve('output') and rejects any path.relative() that is empty, absolute, or starts with .., then apply the same path.relative() check to filePath.

Also applies to: 43-46, 62-64, 83-86

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

In `@mini-muse/app/controller/ProjectController.ts` around lines 23 - 24, The
threadId interpolation allows path traversal; add a shared helper
resolveProjectDir(threadId) that does: base = path.resolve('output'), projectDir
= path.resolve(base, threadId), rel = path.relative(base, projectDir) and reject
when rel === '' || path.isAbsolute(rel) || rel.startsWith('..'); use this helper
wherever project paths are built (the files method and the other similar blocks
referenced around lines 43-46, 62-64, 83-86) and apply the same path.relative
check to any derived filePath before reading/zipping to ensure filePath remains
inside the resolved projectDir.

Comment on lines +73 to +95
await new Promise<void>((resolve, reject) => {
const child = spawn('npm', ['install'], {
cwd: outputDir,
stdio: 'pipe',
env: { ...process.env, npm_config_registry: 'https://registry.npmmirror.com' },
});
child.on('close', (code) => {
if (code === 0) resolve();
else reject(new Error(`npm install failed with code ${code}`));
});
child.on('error', reject);
});

// Step 2: Find a free port and start vite dev server
info.status = 'starting';
const port = await findFreePort();
info.port = port;

const child = spawn('npx', ['vite', '--port', String(port), '--host', '0.0.0.0'], {
cwd: outputDir,
stdio: 'pipe',
env: { ...process.env },
});
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:

#!/bin/bash
set -euo pipefail

echo "Preview execution path:"
sed -n '71,95p' mini-muse/app/service/preview.ts

echo
echo "Generated runtime files / config writers:"
rg -n -C2 "package\\.json|vite\\.config|create_config|create_file|writeFile\\(" mini-muse

echo
echo "Sandboxing or script suppression hooks:"
rg -n -C2 "ignore-scripts|docker|container|sandbox|nsjail|firejail|chroot|vm2" mini-muse

Repository: eggjs/agent-examples

Length of output: 15290


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Preview service process handlers and error handling ==="
sed -n '50,150p' mini-muse/app/service/preview.ts

echo
echo "=== PreviewInfo state management and module-level storage ==="
rg -n -B3 -A3 "PreviewInfo|previewMap|previewState" mini-muse/app/service/preview.ts | head -50

Repository: eggjs/agent-examples

Length of output: 3933


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Catch block in start() method and error handling ==="
sed -n '32,50p' mini-muse/app/service/preview.ts

echo
echo "=== Full start() method including catch ==="
sed -n '30,60p' mini-muse/app/service/preview.ts

Repository: eggjs/agent-examples

Length of output: 1573


Don't execute generated projects on the host—this is a critical RCE vulnerability.

npm install and npx vite are spawned directly inside AI-generated project directories. The AI can inject arbitrary code into package.json (postinstall scripts), vite.config.ts (plugins), or index.html that executes on the server with full process privileges. No sandboxing, file validation, or script suppression (e.g., --ignore-scripts) is present. This must be isolated using containers or a sandbox (Docker, nsjail, vm2, etc.) before release.

Additionally, the catch block in start() only logs errors without cleaning up the previews map, so failed installations leave stale state that blocks retries. Implement proper state cleanup on startup failure.

Finally, use a timeout for npm install (e.g., 5 minutes) and make the registry configurable instead of hardcoded.

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

In `@mini-muse/app/service/preview.ts` around lines 73 - 95, The code currently
spawns untrusted project processes directly (spawn('npm', ['install'], ...) and
spawn('npx', ['vite', ...]) in start()), which is an RCE risk and leaves stale
entries in the previews map on failure; update start() to run all untrusted
project commands inside an isolated sandbox (e.g., Docker/nsjail/other container
runtime) instead of host spawn, or at minimum run npm install with scripts
disabled (npm ci / --ignore-scripts) while you implement containerization, make
the registry configurable via a passed-in option instead of hardcoding
npm_config_registry, add a timeout (e.g., 5 minutes) to the npm install promise
and to starting the vite server, and ensure any failure path/catch in start()
cleans up the previews map and resets info.status/info.port to avoid stale state
(reference symbols: start(), spawn calls, npm install block, npx vite child,
outputDir, findFreePort, previews map, info.status, info.port).

Comment on lines +24 to +46
startRun(
description,
appName || 'my-app',
(event) => {
setEvents(prev => [...prev, event]);
if (event.type === 'error') setFailed(true);
},
(threadId) => {
threadIdRef.current = threadId;
},
() => setDone(true)
).then(cancel => {
cancelRef.current = cancel;
}).catch(err => {
setEvents(prev => [...prev, { type: 'error', message: err.message, timestamp: Date.now() }]);
setFailed(true);
setDone(true);
});

return () => {
cancelRef.current?.();
};
}, [description, appName, navigate]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential race condition: cleanup may run before cancelRef is assigned.

The startRun function is async and returns a cancel function via .then(). If the component unmounts before the promise resolves, the cleanup function at line 44 will find cancelRef.current still null, leaving the SSE stream uncancelled.

🛡️ Proposed fix using AbortController directly
+  const abortRef = useRef<AbortController | null>(null);

   useEffect(() => {
     if (!description) {
       navigate('/');
       return;
     }

+    const controller = new AbortController();
+    abortRef.current = controller;
+
     startRun(
       description,
       appName || 'my-app',
       (event) => {
         setEvents(prev => [...prev, event]);
         if (event.type === 'error') setFailed(true);
       },
       (threadId) => {
         threadIdRef.current = threadId;
       },
-      () => setDone(true)
-    ).then(cancel => {
-      cancelRef.current = cancel;
-    }).catch(err => {
+      () => setDone(true),
+      undefined,
+      controller.signal  // Pass signal to startRun
+    ).catch(err => {
+      if (controller.signal.aborted) return; // Ignore if aborted
       setEvents(prev => [...prev, { type: 'error', message: err.message, timestamp: Date.now() }]);
       setFailed(true);
       setDone(true);
     });

     return () => {
-      cancelRef.current?.();
+      controller.abort();
     };
   }, [description, appName, navigate]);

This would require modifying startRun to accept an optional signal parameter, but provides more reliable cleanup.

Alternatively, you could store the AbortController reference immediately before calling startRun, since startRun internally creates one and returns the abort function.

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

In `@mini-muse/frontend/src/pages/ProgressPage.tsx` around lines 24 - 46, Race
when cleanup runs before startRun's promise resolves: create an AbortController
before calling startRun, assign cancelRef.current =
controller.abort.bind(controller) immediately, and pass controller.signal into
startRun (update startRun signature to accept an optional signal and use it
inside the SSE/fetch logic); this ensures the cleanup callback can synchronously
abort the in-flight run even if startRun's returned cancel function hasn't
resolved yet.

Comment on lines +113 to +118
<a
href={getDownloadUrl(threadId!)}
className="px-4 py-2 text-sm bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors inline-flex items-center gap-2"
>
Download ZIP
</a>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Non-null assertion on threadId may cause runtime error.

At line 114, threadId! assumes threadId is always defined when the download link is rendered. However, threadId comes from useParams and could be undefined. While the parent conditional chatOpen && threadId on line 230 guards the ChatPanel, the download link is always rendered.

🛡️ Proposed fix to add a guard or fallback
             <a
-              href={getDownloadUrl(threadId!)}
+              href={threadId ? getDownloadUrl(threadId) : '#'}
               className="px-4 py-2 text-sm bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors inline-flex items-center gap-2"
+              onClick={(e) => { if (!threadId) e.preventDefault(); }}
             >
               Download ZIP
             </a>

Alternatively, conditionally render the download button only when threadId is defined.

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

In `@mini-muse/frontend/src/pages/ResultPage.tsx` around lines 113 - 118, The
anchor uses a non-null assertion on threadId (threadId!) which can cause a
runtime error if threadId is undefined; update the ResultPage JSX so the
"Download ZIP" anchor is only rendered when threadId is defined (e.g., wrap the
<a ...> that calls getDownloadUrl(threadId) in a conditional check like if
(threadId) or render a disabled fallback), or alternatively compute a safe href
(fallback '#' or disable) and avoid using the non-null assertion; locate the
getDownloadUrl usage and the Download ZIP anchor in ResultPage.tsx and remove
the threadId! assertion.

onThreadId: (threadId: string) => void,
onDone: () => void
): Promise<void> {
const reader = response.body!.getReader();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Non-null assertion on response.body could cause runtime error.

At line 25, response.body! assumes the body is always present. While this is typically true for successful fetch responses, it's safer to add a guard.

🛡️ Proposed fix
 async function parseSSEStream(
   response: Response,
   onEvent: (event: ProgressEvent) => void,
   onThreadId: (threadId: string) => void,
   onDone: () => void
 ): Promise<void> {
+  if (!response.body) {
+    onDone();
+    return;
+  }
-  const reader = response.body!.getReader();
+  const reader = response.body.getReader();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/services/api.ts` at line 25, The code currently uses a
non-null assertion on response.body when creating the reader (const reader =
response.body!.getReader()), which can throw if body is null; update the logic
to guard against a missing body by checking response.body before calling
getReader(), and handle the null case (e.g., throw a descriptive error or return
an empty/closed stream) so caller code won't encounter a runtime exception when
response.body is absent.

Comment on lines +55 to +59
} finally {
reader.releaseLock();
}
onDone();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential double onDone invocation.

onDone() is called both at line 58 (after stream ends) and at line 88 (when 'done' event is received). If the server sends a 'done' event before closing the stream, onDone will be called twice, potentially causing duplicate state updates in consumers.

🛡️ Proposed fix using a flag
 async function parseSSEStream(
   response: Response,
   onEvent: (event: ProgressEvent) => void,
   onThreadId: (threadId: string) => void,
   onDone: () => void
 ): Promise<void> {
+  let doneCalled = false;
+  const callDone = () => {
+    if (!doneCalled) {
+      doneCalled = true;
+      onDone();
+    }
+  };
   // ... in handleSSEEvent calls, pass callDone instead of onDone
   // ... and at line 58, call callDone() instead of onDone()

Or pass a wrapped callback to handleSSEEvent and use it at line 58.

Also applies to: 87-89

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

In `@mini-muse/frontend/src/services/api.ts` around lines 55 - 59, The code
currently may call onDone() twice (once after the read loop and once when
handleSSEEvent processes a 'done' event); to fix, create a single-use wrapper or
boolean guard (e.g., doneCalled = false or a once(fn) wrapper) and use that
wrapped callback everywhere you currently call onDone, including in
handleSSEEvent and after the stream loop (instead of calling onDone directly);
ensure the guard is checked/set before invoking so the 'done' event and the
stream-close path cannot both trigger the real onDone implementation.

jerryliang64 and others added 3 commits March 11, 2026 23:06
…t-sdk

- Replace manual agentic loop with Agent SDK's `query()` function
- Create `mcpTools.ts` to register 11 custom tools as MCP tools via
  `tool()` + `createSdkMcpServer()` from the Agent SDK
- Remove `AiClientService` (no longer needed, Agent SDK manages API calls)
- Simplify `OrchestratorService` to consume `query()` async iterator
- Update `ToolsService` to remove dependency on deleted `aiClient.ts`
- Update design docs to reflect new architecture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Users can now see their previous generation sessions on the HomePage
and click to navigate directly to the result page. Session records
(threadId, appName, description, status) are saved to localStorage
when a run starts and updated on completion/failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Map CLAUDE_MODEL to ANTHROPIC_MODEL for Agent SDK (Claude CLI)
- Set CLAUDE_CODE_OFFLINE_MODE and disable telemetry to prevent
  CLI from making external network calls
- Ensure ~/.claude.json has onboarding completed
- Create outputDir before query() to fix spawn ENOENT error
- Pass model, cwd, and stderr options to query()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: 6

🧹 Nitpick comments (7)
mini-muse/app/service/tools.ts (1)

6-7: Accept ToolName type in execute() parameter to preserve compile-time safety.

Line 6 accepts any string but line 7 immediately casts it with as ToolName. The function signature of executeTool (from registry.ts:133) already expects ToolName, so the cast only suppresses TypeScript warnings for invalid tool names. While executeTool does handle unknown names at runtime (returning an error ToolResult), the public API should validate tool names at the boundary rather than silently accepting any string and failing later.

Change name: string to name: ToolName on line 6 to align the type contract with the called function and prevent typos in callers.

♻️ Proposed fix
 export class ToolsService {
-  async execute(name: string, input: Record<string, unknown>, outputDir: string): Promise<ToolResult> {
-    return executeTool(name as ToolName, input, outputDir);
+  async execute(name: ToolName, input: Record<string, unknown>, outputDir: string): Promise<ToolResult> {
+    return executeTool(name, input, outputDir);
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/service/tools.ts` around lines 6 - 7, Change the execute()
function signature to accept a ToolName instead of string to preserve
compile-time safety: update the parameter type in execute (in
mini-muse/app/service/tools.ts) from name: string to name: ToolName so you can
call executeTool(name, input, outputDir) without casting; this aligns the public
API with executeTool (registry.ts:133) and prevents callers from passing invalid
tool names.
mini-muse/frontend/src/pages/HomePage.tsx (2)

99-105: Add aria-label for better screen reader support.

The delete button relies on title for accessibility context, but screen readers may not announce title attributes consistently. Adding an explicit aria-label improves accessibility.

♿ Suggested improvement
 <button
   onClick={(e) => { e.stopPropagation(); handleDelete(s.threadId); }}
   className="text-gray-400 hover:text-red-500 transition-colors text-sm"
   title="Delete"
+  aria-label={`Delete ${s.appName} session`}
 >
   &times;
 </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/pages/HomePage.tsx` around lines 99 - 105, The delete
button in HomePage.tsx currently uses title="Delete" but lacks an explicit
accessibility label; update the button element that calls
handleDelete(s.threadId) to include an aria-label (e.g., aria-label="Delete
thread") so screen readers announce its purpose reliably while keeping the
existing title and onClick behavior unchanged.

24-29: setLoading(true) has no visible effect before navigation.

The setLoading(true) on line 27 triggers a state update, but navigate() is called synchronously on line 28. The component will unmount before React can re-render with the loading state, so the "Creating..." button text will never display.

If the intent is purely to disable double-submission during the brief navigation, the current approach works. If you want visible feedback, consider adding a small delay or removing the loading state entirely since the navigation is immediate.

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

In `@mini-muse/frontend/src/pages/HomePage.tsx` around lines 24 - 29, In
handleSubmit, setLoading(true) is being set immediately before synchronous
navigate so the component unmounts before the render shows the "Creating..."
state; either remove the setLoading call and rely on immediate navigation to
prevent double submits, or if you want visible feedback, after calling
setLoading(true) await a short timer (e.g. await new Promise(r => setTimeout(r,
50))) before calling navigate('/progress', { state: { description:
description.trim(), appName: appName.trim() || 'my-app' } }); so the Loading
state (managed by setLoading) can render; reference handleSubmit, setLoading,
and navigate when making the change.
mini-muse/frontend/src/pages/ProgressPage.tsx (1)

75-80: Consider handling the edge case where done is true without a terminal event.

If the SSE stream ends unexpectedly (e.g., network interruption) without emitting a completed or error event, the component will show no status indicator since:

  • Line 75: !done && !failed → false
  • Line 76: isCompleted → false
  • Line 77: failed → false

This is unlikely in normal operation but could leave users without visual feedback in edge cases.

💡 Defensive fallback indicator
   {!done && !failed && <div className="w-4 h-4 rounded-full bg-indigo-500 animate-pulse" />}
   {isCompleted && <div className="w-4 h-4 rounded-full bg-green-500" />}
   {failed && <div className="w-4 h-4 rounded-full bg-red-500" />}
+  {done && !isCompleted && !failed && <div className="w-4 h-4 rounded-full bg-gray-400" />}
   <span className="font-medium text-gray-700">
-    {failed ? 'Generation Failed' : isCompleted ? 'Generation Complete' : 'Generating...'}
+    {failed ? 'Generation Failed' : isCompleted ? 'Generation Complete' : done ? 'Finished' : 'Generating...'}
   </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/pages/ProgressPage.tsx` around lines 75 - 80, The
component currently shows no indicator when done is true but neither isCompleted
nor failed is set; add a defensive fallback for that edge case by rendering a
neutral/notice indicator when (done && !isCompleted && !failed) and update the
status text logic to handle that case (e.g., show "Generation Interrupted" or
similar). Modify the JSX around the existing indicators (the lines referencing
done, isCompleted, failed in ProgressPage.tsx) to include a new conditional
branch for (done && !isCompleted && !failed) that renders a distinct colored dot
(e.g., yellow) and adjusts the span text to cover this fallback state.
mini-muse/frontend/src/services/sessionHistory.ts (1)

32-44: Consider adding a session limit to prevent unbounded localStorage growth.

While localStorage has its own size limits, sessions will accumulate indefinitely over time. Consider pruning old sessions to maintain a reasonable history size.

💡 Optional: Limit stored sessions
+const MAX_SESSIONS = 50;
+
 export function saveSession(record: SessionRecord): void {
   const sessions = getSessions();
   const idx = sessions.findIndex(s => s.threadId === record.threadId);
   if (idx >= 0) {
     sessions[idx] = record;
   } else {
     sessions.unshift(record);
   }
+  // Keep only the most recent sessions
+  const trimmed = sessions.slice(0, MAX_SESSIONS);
-  localStorage.setItem(STORAGE_KEY, JSON.stringify(sessions));
+  localStorage.setItem(STORAGE_KEY, JSON.stringify(trimmed));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/frontend/src/services/sessionHistory.ts` around lines 32 - 44, Add
a max-session cap to prevent unbounded localStorage growth by introducing a
constant (e.g. MAX_SESSIONS = 100) and a small helper to prune sessions to that
size (pruneSessions(sessions): return most-recent MAX_SESSIONS). Call the pruner
before persisting to localStorage in any place that writes STORAGE_KEY (e.g.
updateSessionStatus and other save paths that call localStorage.setItem), using
getSessions() to load and relying on a timestamp/ordering field if available (or
array order) to keep the newest entries. Ensure you update only the in-memory
sessions array and then persist the pruned array via
localStorage.setItem(STORAGE_KEY, JSON.stringify(prunedSessions)).
mini-muse/app/service/orchestrator.ts (2)

22-47: Global state mutation on each agentLoop invocation.

ensureClaudeEnv() is called on every agentLoop() call, repeatedly mutating process.env and checking/writing ~/.claude.json. This is idempotent but wasteful. Consider running this once at service initialization or module load.

Additionally, the accessSync + writeFileSync pattern is not atomic—concurrent agentLoop invocations could race here. The synchronous I/O also blocks the event loop.

♻️ Proposed refactor: run once at module load with async I/O
+import { access, writeFile } from 'fs/promises';
+
+let claudeEnvInitialized = false;
+
 /** Ensure Claude CLI env vars and config are ready for Agent SDK */
-function ensureClaudeEnv() {
+async function ensureClaudeEnv() {
+  if (claudeEnvInitialized) return;
+  claudeEnvInitialized = true;
+
   // Agent SDK (Claude CLI) reads ANTHROPIC_MODEL, not CLAUDE_MODEL
   if (process.env.CLAUDE_MODEL && !process.env.ANTHROPIC_MODEL) {
     process.env.ANTHROPIC_MODEL = process.env.CLAUDE_MODEL;
   }
   // ... env vars unchanged ...

   const claudeConfigPath = path.join(os.homedir(), '.claude.json');
   try {
-    fs.accessSync(claudeConfigPath);
-  } catch {
-    fs.writeFileSync(claudeConfigPath, JSON.stringify({
+    await access(claudeConfigPath);
+  } catch {
+    await writeFile(claudeConfigPath, JSON.stringify({
       // ...
-    }, null, 2));
+    }, null, 2), { flag: 'wx' }); // 'wx' fails if file exists (atomic)
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/service/orchestrator.ts` around lines 22 - 47,
ensureClaudeEnv() is mutating process.env and doing synchronous file I/O every
time agentLoop() runs; move this to run once at module initialization (or
service startup) and remove calls from agentLoop. Change ensureClaudeEnv to an
async initializer (e.g., ensureClaudeEnvAsync) that only sets env vars when they
are unset (check process.env.ANTHROPIC_MODEL before assigning) and uses
fs.promises to create the claudeConfigPath file atomically
(fs.promises.writeFile with flag 'wx' or open with 'wx' and handle EEXIST)
instead of accessSync/writeFileSync to avoid races and blocking the event loop;
call this initializer once from module load or the top-level service init
instead of inside agentLoop().

62-65: Silent error suppression may hide legitimate failures.

The empty catch block at line 65 assumes any error means "directory might not exist," but it could mask permission errors, disk issues, or other problems. Consider logging at debug level or narrowing the catch to specific error codes.

🛡️ Proposed fix to handle specific errors
       try {
         const fileList = await listFiles(outputDir);
         relativeFiles = fileList.map(f => path.relative(outputDir, f));
-      } catch { /* directory might not exist */ }
+      } catch (err) {
+        // ENOENT is expected for new projects; log other errors
+        if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
+          console.warn('[orchestrator] listFiles failed:', err);
+        }
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/service/orchestrator.ts` around lines 62 - 65, The empty catch
around the listFiles call hides real failures; update the try/catch where you
await listFiles(outputDir) (which sets relativeFiles by mapping path.relative)
to only suppress "directory not found" cases and otherwise log the error at
debug or error level—either check the error.code (e.g., ENOENT) before
swallowing or rethrow; include context (outputDir and the function
name/orchestrator) in the log so permission or disk errors are visible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mini-muse/app/lib/tools/mcpTools.ts`:
- Around line 107-115: The create_file tool (museTool 'create_file' in
mcpTools.ts) and other file-writing tools (e.g., createComponent, createPage,
createHook handlers that accept filePath or directory) lack path traversal
protection; update each tool to resolve the user-supplied path against the
server output base (use path.resolve(outputDir, userInput)) and reject any path
where the resolved fullPath does not start with path.resolve(outputDir); on
rejection return a failure result (e.g., { success: false, error: 'Access
denied: path traversal detected' }) and do this validation before
writing/creating files so ../ escapes cannot reach outside the intended
directory.
- Around line 1-4: The import of AnyZodRawShape from
'@anthropic-ai/claude-agent-sdk' is invalid; remove that import and either omit
the explicit type constraint (let TypeScript infer it) or import ZodRawShape
from 'zod' instead and use that where needed; update usage sites related to tool
or createSdkMcpServer so they no longer reference AnyZodRawShape (or switch to
z.ZodRawShape) while keeping the rest of imports (tool, createSdkMcpServer, z,
executeTool, ToolName) unchanged.

In `@mini-muse/app/service/orchestrator.ts`:
- Around line 87-88: The configuration currently sets permissionMode:
'bypassPermissions' and allowDangerouslySkipPermissions: true which grants
unrestricted FS access; change this by removing or disabling these flags and
enforce sandboxing and path validation in the orchestrator and tools: restrict
allowed operations to the configured outputDir, validate and canonicalize target
paths (reject paths that escape outputDir via .. or symlinks), and ensure any
tool methods (e.g., file read/write methods in the orchestrator or tool
implementations) check that resolved paths are inside outputDir before
performing IO; if bypass is required for tests, gate it behind an explicit
opt-in env flag and add logging/auditing when enabled.

In `@mini-muse/docs/design.md`:
- Around line 248-260: The example SSE code block in docs/design.md lacks a
fenced-code language specifier; update the block that shows the events (the
lines beginning with "event: thread.run.created", "event: thread.message.delta",
"event: thread.run.completed", "event: done") to start with a fenced code tag
including a language identifier (e.g., ```text or a custom tag) and close with
the matching ``` so markdown linters recognize it and readability is improved.
- Around line 211-227: The fenced code block in mini-muse/docs/design.md
containing the frontend directory tree lacks a language specifier; update that
fenced block (the triple-backtick that encloses the directory listing) to
include a language such as text or plaintext so Markdown renderers treat it as
plain text (e.g., change ``` to ```text) while keeping the existing directory
content (frontend/src/, pages/, components/, services/, types/, etc.) unchanged.
- Around line 357-388: The fenced code block showing the project directory tree
(the block beginning with "mini-muse/" and the ASCII tree under it) needs a
language specifier; update the opening triple backticks to ```text so the
directory structure block is ```text ... ``` for consistent syntax highlighting
in the docs/design.md file.

---

Nitpick comments:
In `@mini-muse/app/service/orchestrator.ts`:
- Around line 22-47: ensureClaudeEnv() is mutating process.env and doing
synchronous file I/O every time agentLoop() runs; move this to run once at
module initialization (or service startup) and remove calls from agentLoop.
Change ensureClaudeEnv to an async initializer (e.g., ensureClaudeEnvAsync) that
only sets env vars when they are unset (check process.env.ANTHROPIC_MODEL before
assigning) and uses fs.promises to create the claudeConfigPath file atomically
(fs.promises.writeFile with flag 'wx' or open with 'wx' and handle EEXIST)
instead of accessSync/writeFileSync to avoid races and blocking the event loop;
call this initializer once from module load or the top-level service init
instead of inside agentLoop().
- Around line 62-65: The empty catch around the listFiles call hides real
failures; update the try/catch where you await listFiles(outputDir) (which sets
relativeFiles by mapping path.relative) to only suppress "directory not found"
cases and otherwise log the error at debug or error level—either check the
error.code (e.g., ENOENT) before swallowing or rethrow; include context
(outputDir and the function name/orchestrator) in the log so permission or disk
errors are visible.

In `@mini-muse/app/service/tools.ts`:
- Around line 6-7: Change the execute() function signature to accept a ToolName
instead of string to preserve compile-time safety: update the parameter type in
execute (in mini-muse/app/service/tools.ts) from name: string to name: ToolName
so you can call executeTool(name, input, outputDir) without casting; this aligns
the public API with executeTool (registry.ts:133) and prevents callers from
passing invalid tool names.

In `@mini-muse/frontend/src/pages/HomePage.tsx`:
- Around line 99-105: The delete button in HomePage.tsx currently uses
title="Delete" but lacks an explicit accessibility label; update the button
element that calls handleDelete(s.threadId) to include an aria-label (e.g.,
aria-label="Delete thread") so screen readers announce its purpose reliably
while keeping the existing title and onClick behavior unchanged.
- Around line 24-29: In handleSubmit, setLoading(true) is being set immediately
before synchronous navigate so the component unmounts before the render shows
the "Creating..." state; either remove the setLoading call and rely on immediate
navigation to prevent double submits, or if you want visible feedback, after
calling setLoading(true) await a short timer (e.g. await new Promise(r =>
setTimeout(r, 50))) before calling navigate('/progress', { state: { description:
description.trim(), appName: appName.trim() || 'my-app' } }); so the Loading
state (managed by setLoading) can render; reference handleSubmit, setLoading,
and navigate when making the change.

In `@mini-muse/frontend/src/pages/ProgressPage.tsx`:
- Around line 75-80: The component currently shows no indicator when done is
true but neither isCompleted nor failed is set; add a defensive fallback for
that edge case by rendering a neutral/notice indicator when (done &&
!isCompleted && !failed) and update the status text logic to handle that case
(e.g., show "Generation Interrupted" or similar). Modify the JSX around the
existing indicators (the lines referencing done, isCompleted, failed in
ProgressPage.tsx) to include a new conditional branch for (done && !isCompleted
&& !failed) that renders a distinct colored dot (e.g., yellow) and adjusts the
span text to cover this fallback state.

In `@mini-muse/frontend/src/services/sessionHistory.ts`:
- Around line 32-44: Add a max-session cap to prevent unbounded localStorage
growth by introducing a constant (e.g. MAX_SESSIONS = 100) and a small helper to
prune sessions to that size (pruneSessions(sessions): return most-recent
MAX_SESSIONS). Call the pruner before persisting to localStorage in any place
that writes STORAGE_KEY (e.g. updateSessionStatus and other save paths that call
localStorage.setItem), using getSessions() to load and relying on a
timestamp/ordering field if available (or array order) to keep the newest
entries. Ensure you update only the in-memory sessions array and then persist
the pruned array via localStorage.setItem(STORAGE_KEY,
JSON.stringify(prunedSessions)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 04f8cdd2-a8d8-436b-abd6-f5b9d34e479a

📥 Commits

Reviewing files that changed from the base of the PR and between 33b8e37 and 2973efa.

📒 Files selected for processing (8)
  • mini-muse/app/lib/tools/mcpTools.ts
  • mini-muse/app/service/orchestrator.ts
  • mini-muse/app/service/tools.ts
  • mini-muse/docs/design.md
  • mini-muse/frontend/src/pages/HomePage.tsx
  • mini-muse/frontend/src/pages/ProgressPage.tsx
  • mini-muse/frontend/src/services/sessionHistory.ts
  • mini-muse/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • mini-muse/package.json

Comment on lines +1 to +4
import { tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk';
import type { AnyZodRawShape } from '@anthropic-ai/claude-agent-sdk';
import { z } from 'zod';
import { executeTool, ToolName } from './registry';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

@anthropic-ai/claude-agent-sdk npm package API documentation tool createSdkMcpServer

💡 Result:

createSdkMcpServer is documented in Anthropic’s Claude Agent SDK (TypeScript) reference and used in the Custom Tools guide. [1], [2]

Signature (TypeScript)

function createSdkMcpServer(options: {
  name: string;
  version?: string;
  tools?: Array<SdkMcpToolDefinition<any>>;
}): McpSdkServerConfigWithInstance;

It “creates an MCP server instance that runs in the same process as your application.” [1]

Typical usage (custom tools)

  1. Define tools with tool(...), then create an in-process MCP server:
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const customServer = createSdkMcpServer({
  name: "my-custom-tools",
  version: "1.0.0",
  tools: [
    tool("get_weather", "Get current temperature", { latitude: z.number(), longitude: z.number() }, async (args) => {
      return { content: [{ type: "text", text: `...` }] };
    }),
  ],
});

[2]

  1. Pass the server to query() via options.mcpServers (as an object/dictionary). Custom MCP tools require streaming input mode (i.e., prompt must be an AsyncIterable, not a plain string). [2]

Tool naming when exposed to Claude

MCP tool names follow: mcp__{server_name}__{tool_name} (e.g., mcp__my-custom-tools__get_weather). [2]

Sources: Anthropic Claude Agent SDK docs—TypeScript reference [1] and Custom Tools guide [2].


🏁 Script executed:

# First, let's check the full content of the mcpTools.ts file, focusing on where AnyZodRawShape is used
head -50 mini-muse/app/lib/tools/mcpTools.ts

Repository: eggjs/agent-examples

Length of output: 2328


🌐 Web query:

@anthropic-ai/claude-agent-sdk AnyZodRawShape type export TypeScript

💡 Result:

In the Claude Agent SDK TypeScript docs, tool() is typed as:

However, AnyZodRawShape is not documented in the “Types” section there, so if you’re trying to reference that constraint in your own TypeScript types, the practical options are:

  1. Use Zod’s raw-shape type (most common)
import { z, type ZodRawShape } from "zod";

type MyShape = ZodRawShape;
  1. Or define a Zod-3/4-compatible alias yourself
import { z } from "zod";

// Works as a “raw shape” constraint for both Zod 3/4-style shapes
export type AnyZodRawShape = Record<string, z.ZodTypeAny>;

In many cases you don’t need to name the type at all—tool() will infer Schema from the inputSchema you pass. (docs.claude.com)

Citations:


AnyZodRawShape is not a public export from @anthropic-ai/claude-agent-sdk.

The SDK's tool() function uses this type internally, but it does not expose AnyZodRawShape in its public API. Remove the import on line 2 or use ZodRawShape from zod instead. Most commonly, the type constraint can be omitted entirely and inferred automatically by TypeScript.

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

In `@mini-muse/app/lib/tools/mcpTools.ts` around lines 1 - 4, The import of
AnyZodRawShape from '@anthropic-ai/claude-agent-sdk' is invalid; remove that
import and either omit the explicit type constraint (let TypeScript infer it) or
import ZodRawShape from 'zod' instead and use that where needed; update usage
sites related to tool or createSdkMcpServer so they no longer reference
AnyZodRawShape (or switch to z.ZodRawShape) while keeping the rest of imports
(tool, createSdkMcpServer, z, executeTool, ToolName) unchanged.

Comment on lines +107 to +115
const createFile = museTool(
'create_file',
'Create a generic file with specified content. Use for type definitions, utilities, constants, or any file that does not fit other specialized tools.',
{
filePath: z.string().describe('Path relative to src/ directory (e.g., "types/index.ts", "utils/helpers.ts")'),
content: z.string().describe('The complete file content'),
fileType: z.enum(['typescript', 'css', 'json']).optional().describe('File type for formatting'),
},
);
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:

#!/bin/bash
# Check which tool implementations have path traversal protection

echo "=== Checking for path traversal validation in tool handlers ==="

# Search for the secure pattern (path.resolve + startsWith check)
echo -e "\n--- Tools WITH path traversal protection ---"
rg -l "startsWith.*resolve" mini-muse/app/lib/tools/

echo -e "\n--- Tools using path.join (potentially unsafe) ---"
rg -n "path\.join\(outputDir" mini-muse/app/lib/tools/generator/

echo -e "\n--- All file-writing operations ---"
rg -n "writeFile|fs\.write" mini-muse/app/lib/tools/

Repository: eggjs/agent-examples

Length of output: 3511


🏁 Script executed:

cat -n mini-muse/app/lib/tools/generator/deleteFile.ts

Repository: eggjs/agent-examples

Length of output: 1621


🏁 Script executed:

cat -n mini-muse/app/lib/tools/generator/createFile.ts

Repository: eggjs/agent-examples

Length of output: 2529


🏁 Script executed:

cat -n mini-muse/app/lib/tools/reader/readFile.ts

Repository: eggjs/agent-examples

Length of output: 1703


🏁 Script executed:

cat -n mini-muse/app/lib/tools/generator/createComponent.ts | head -90

Repository: eggjs/agent-examples

Length of output: 3367


🏁 Script executed:

cat -n mini-muse/app/lib/tools/generator/createPage.ts | head -80

Repository: eggjs/agent-examples

Length of output: 3040


🏁 Script executed:

cat -n mini-muse/app/lib/tools/generator/createHook.ts | head -80

Repository: eggjs/agent-examples

Length of output: 2628


Path traversal vulnerability in file-creation tools.

The filePath and directory parameters in file-writing tools accept user-controlled paths without consistent validation. Comparison with deleteFile.ts reveals the gap:

  • deleteFile.ts and readFile.ts correctly validate using path.resolve() + startsWith() check (preventing ../ escapes)
  • createFile.ts uses path.join() without traversal validation; checking only startsWith('src/') allows attacks like filePath='src/../../../etc/passwd' to escape outputDir
  • createComponent.ts accepts unvalidated directory parameter with the same risk
  • createPage.ts and createHook.ts have marginal protection via toPascalCase/toCamelCase normalization, but lack explicit path validation

Apply the secure validation pattern from deleteFile.ts to all file-writing tools:

const fullPath = path.resolve(outputDir, userInput);
if (!fullPath.startsWith(path.resolve(outputDir))) {
  return { success: false, error: 'Access denied: path traversal detected' };
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/tools/mcpTools.ts` around lines 107 - 115, The create_file
tool (museTool 'create_file' in mcpTools.ts) and other file-writing tools (e.g.,
createComponent, createPage, createHook handlers that accept filePath or
directory) lack path traversal protection; update each tool to resolve the
user-supplied path against the server output base (use path.resolve(outputDir,
userInput)) and reject any path where the resolved fullPath does not start with
path.resolve(outputDir); on rejection return a failure result (e.g., { success:
false, error: 'Access denied: path traversal detected' }) and do this validation
before writing/creating files so ../ escapes cannot reach outside the intended
directory.

Comment on lines +87 to +88
permissionMode: 'bypassPermissions',
allowDangerouslySkipPermissions: true,
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: bypassPermissions + allowDangerouslySkipPermissions grants the agent unrestricted file system access.

These options disable all permission checks, allowing the agent to read/write any files the process can access. Ensure this is intentional and that outputDir is properly sandboxed. Consider adding path validation in tool implementations to restrict operations to outputDir.

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

In `@mini-muse/app/service/orchestrator.ts` around lines 87 - 88, The
configuration currently sets permissionMode: 'bypassPermissions' and
allowDangerouslySkipPermissions: true which grants unrestricted FS access;
change this by removing or disabling these flags and enforce sandboxing and path
validation in the orchestrator and tools: restrict allowed operations to the
configured outputDir, validate and canonicalize target paths (reject paths that
escape outputDir via .. or symlinks), and ensure any tool methods (e.g., file
read/write methods in the orchestrator or tool implementations) check that
resolved paths are inside outputDir before performing IO; if bypass is required
for tests, gate it behind an explicit opt-in env flag and add logging/auditing
when enabled.

Comment on lines +211 to +227
```
frontend/src/
├── pages/
│ ├── HomePage.tsx # 输入表单 + 历史会话列表 (localStorage)
│ ├── ProgressPage.tsx # SSE 实时进度展示 + 会话记录持久化
│ └── ResultPage.tsx # Code/Preview 双标签页 + 可折叠聊天面板
├── components/
│ ├── FileTree.tsx # 递归文件树
│ ├── CodePreview.tsx # 带行号的代码展示
│ ├── ProgressLog.tsx # 进度日志滚动列表
│ └── ChatPanel.tsx # 迭代修改聊天面板
├── services/
│ ├── api.ts # API 封装 (fetch + ReadableStream SSE 解析)
│ └── sessionHistory.ts # 历史会话管理 (localStorage CRUD)
└── types/
└── index.ts # 共享类型 (RunObject, ThreadObject, ProgressEvent 等)
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language specifier to fenced code block.

Static analysis flagged this code block as missing a language specifier. For directory structures, use text or plaintext for consistent rendering across Markdown processors.

📝 Proposed fix
-```
+```text
 frontend/src/
 ├── pages/
 ...
📝 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
```
frontend/src/
├── pages/
│ ├── HomePage.tsx # 输入表单 + 历史会话列表 (localStorage)
│ ├── ProgressPage.tsx # SSE 实时进度展示 + 会话记录持久化
│ └── ResultPage.tsx # Code/Preview 双标签页 + 可折叠聊天面板
├── components/
│ ├── FileTree.tsx # 递归文件树
│ ├── CodePreview.tsx # 带行号的代码展示
│ ├── ProgressLog.tsx # 进度日志滚动列表
│ └── ChatPanel.tsx # 迭代修改聊天面板
├── services/
│ ├── api.ts # API 封装 (fetch + ReadableStream SSE 解析)
│ └── sessionHistory.ts # 历史会话管理 (localStorage CRUD)
└── types/
└── index.ts # 共享类型 (RunObject, ThreadObject, ProgressEvent 等)
```
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 211-211: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

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

In `@mini-muse/docs/design.md` around lines 211 - 227, The fenced code block in
mini-muse/docs/design.md containing the frontend directory tree lacks a language
specifier; update that fenced block (the triple-backtick that encloses the
directory listing) to include a language such as text or plaintext so Markdown
renderers treat it as plain text (e.g., change ``` to ```text) while keeping the
existing directory content (frontend/src/, pages/, components/, services/,
types/, etc.) unchanged.

Comment on lines +248 to +260
```
event: thread.run.created
data: {"id":"run_xxx","threadId":"thread_xxx","status":"queued",...}

event: thread.message.delta
data: {"id":"msg_xxx","delta":{"content":[{"type":"text","text":{"value":"[status] Processing...","annotations":[]}}]}}

event: thread.run.completed
data: {"id":"run_xxx","status":"completed",...}

event: done
data: "[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 | 🟡 Minor

Add language specifier to SSE example code block.

Use text or a custom identifier to satisfy markdown linting and improve readability.

📝 Proposed fix
-```
+```text
 event: thread.run.created
 data: {"id":"run_xxx","threadId":"thread_xxx","status":"queued",...}
 ...
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 248-248: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

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

In `@mini-muse/docs/design.md` around lines 248 - 260, The example SSE code block
in docs/design.md lacks a fenced-code language specifier; update the block that
shows the events (the lines beginning with "event: thread.run.created", "event:
thread.message.delta", "event: thread.run.completed", "event: done") to start
with a fenced code tag including a language identifier (e.g., ```text or a
custom tag) and close with the matching ``` so markdown linters recognize it and
readability is improved.

Comment on lines +357 to +388
```
mini-muse/
├── app/
│ ├── controller/ # tegg 装饰器控制器
│ │ ├── MuseAgentController.ts # @AgentController - 标准 Agent API
│ │ ├── ProjectController.ts # @HTTPController - 文件/预览 API
│ │ └── HomeController.ts # @HTTPController - SPA fallback
│ ├── service/ # tegg @SingletonProto 服务
│ │ ├── orchestrator.ts # Agent 主循环 (claude-agent-sdk query)
│ │ ├── tools.ts # 工具调用入口
│ │ └── preview.ts # 预览服务器管理
│ ├── middleware/
│ │ └── errorHandler.ts # API 错误统一处理
│ ├── lib/ # 纯业务逻辑 (框架无关)
│ │ ├── prompts.ts
│ │ ├── utils.ts
│ │ └── tools/ # 11 个 AI 工具
│ ├── public/ # 前端构建产物
│ └── module.json # tegg 模块声明
├── config/ # Egg.js + tegg 配置
│ ├── config.default.ts
│ ├── config.local.ts
│ └── plugin.ts
├── frontend/ # React SPA 源码
│ └── src/
├── docs/
│ └── design.md # 本文档
├── .env # 环境变量 (不提交)
├── .env.example # 环境变量模板
├── CLAUDE.md # Claude Code 项目规范
├── package.json
└── tsconfig.json
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language specifier to directory structure code block.

Same issue as above—add text language specifier for consistency.

📝 Proposed fix
-```
+```text
 mini-muse/
 ├── app/
 ...
📝 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
```
mini-muse/
├── app/
│ ├── controller/ # tegg 装饰器控制器
│ │ ├── MuseAgentController.ts # @AgentController - 标准 Agent API
│ │ ├── ProjectController.ts # @HTTPController - 文件/预览 API
│ │ └── HomeController.ts # @HTTPController - SPA fallback
│ ├── service/ # tegg @SingletonProto 服务
│ │ ├── orchestrator.ts # Agent 主循环 (claude-agent-sdk query)
│ │ ├── tools.ts # 工具调用入口
│ │ └── preview.ts # 预览服务器管理
│ ├── middleware/
│ │ └── errorHandler.ts # API 错误统一处理
│ ├── lib/ # 纯业务逻辑 (框架无关)
│ │ ├── prompts.ts
│ │ ├── utils.ts
│ │ └── tools/ # 11 个 AI 工具
│ ├── public/ # 前端构建产物
│ └── module.json # tegg 模块声明
├── config/ # Egg.js + tegg 配置
│ ├── config.default.ts
│ ├── config.local.ts
│ └── plugin.ts
├── frontend/ # React SPA 源码
│ └── src/
├── docs/
│ └── design.md # 本文档
├── .env # 环境变量 (不提交)
├── .env.example # 环境变量模板
├── CLAUDE.md # Claude Code 项目规范
├── package.json
└── tsconfig.json
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 357-357: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

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

In `@mini-muse/docs/design.md` around lines 357 - 388, The fenced code block
showing the project directory tree (the block beginning with "mini-muse/" and
the ASCII tree under it) needs a language specifier; update the opening triple
backticks to ```text so the directory structure block is ```text ... ``` for
consistent syntax highlighting in the docs/design.md file.

jerryliang64 and others added 2 commits March 12, 2026 11:32
…th DI

Move all 11 tool definitions from mcpTools.ts into ToolsService as class
properties using tool() from Agent SDK. Add getMcpServers()/getAllowedTools()
methods following wohuguiagent pattern. OrchestratorService now uses @Inject
for ToolsService and @LifecyclePostInject for env init. Delete redundant
mcpTools.ts and clean up registry.ts (remove unused schemas/definitions).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix singleton concurrency issue: outputDir was stored as mutable state
  on the singleton ToolsService, causing concurrent requests to overwrite
  each other's output directory. Now getMcpServers(outputDir) accepts the
  directory as a parameter and creates tool instances bound to it.
- Remove dead exports: xxxSchema and xxxInput from all 11 tool files are
  no longer imported after the Zod migration, saving ~430 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: 3

♻️ Duplicate comments (3)
mini-muse/docs/design.md (3)

259-271: ⚠️ Potential issue | 🟡 Minor

Add language specifier to SSE example code block.

Use text or a custom identifier to satisfy markdown linting and improve readability.

📝 Proposed fix
-```
+```text
 event: thread.run.created
 data: {"id":"run_xxx","threadId":"thread_xxx","status":"queued",...}
 ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/docs/design.md` around lines 259 - 271, The markdown SSE example
block lacks a fenced-code language specifier; update the triple-backtick fence
that wraps the events (containing lines like "event: thread.run.created",
"event: thread.message.delta", "event: thread.run.completed", and "event: done")
to include a language tag such as "text" (e.g., change ``` to ```text) so the
code block satisfies markdown linting and improves readability.

368-400: ⚠️ Potential issue | 🟡 Minor

Add language specifier to directory structure code block.

Same issue as above—add text language specifier for consistency.

📝 Proposed fix
-```
+```text
 mini-muse/
 ├── app/
 ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/docs/design.md` around lines 368 - 400, The fenced code block
showing the project directory tree in design.md is missing a language specifier;
update the triple-backtick opening fence that precedes the mini-muse/ tree to
use ```text so the block is consistently highlighted (e.g., change ``` to
```text for the directory structure block in docs/design.md).

222-238: ⚠️ Potential issue | 🟡 Minor

Add language specifier to fenced code block.

Static analysis flagged this code block as missing a language specifier. For directory structures, use text or plaintext for consistent rendering across Markdown processors.

📝 Proposed fix
-```
+```text
 frontend/src/
 ├── pages/
 ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/docs/design.md` around lines 222 - 238, Update the fenced code
block in mini-muse/docs/design.md that shows the frontend/src/ directory tree to
include a language specifier (e.g., change the opening "```" to "```text" or
"```plaintext") so Markdown renderers treat the directory structure correctly;
locate the block containing "frontend/src/" and the list of
pages/components/services/types and replace the fence accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mini-muse/app/lib/tools/registry.ts`:
- Around line 50-63: The dispatcher executeTool currently calls the selected
executor directly and lets exceptions propagate; wrap the executor invocation in
a try/catch inside executeTool (referencing executeTool and toolExecutors) so
any thrown error is caught and you return a ToolResult with success: false and
an error message (include the caught error message/string). Preserve the
existing handling for unknown tools, and ensure the catch returns a ToolResult
rather than rethrowing so callers always receive a ToolResult.

In `@mini-muse/app/service/tools.ts`:
- Around line 17-21: The tool wrapper currently calls executeTool with
this.outputDir even when outputDir is empty, causing filesystem tools to operate
in the process CWD; update the async handler returned by tool (the anonymous
async (args) => { ... } block) to validate this.outputDir (or this?.outputDir)
up front and throw a clear error (e.g., "outputDir not initialized") if falsy
before calling executeTool(name, args, this.outputDir) so execution fails fast
and prevents accidental reads/writes outside the intended workspace.
- Around line 8-18: ToolsService is a singleton but currently stores per-run
state in the private outputDir and setOutputDir, which causes concurrent runs to
clobber each other; make ToolsService stateless by removing the outputDir field
and setOutputDir, and change createTool (and wherever you construct tool(...)
handlers) so the handler captures the per-run directory via a parameter/closure
or reads it from the invocation args instead of this.outputDir — specifically
update createTool, the tool(...) factory calls, and executeTool usage so
executeTool(name, args, outputDir) receives a run-scoped outputDir supplied when
the tool is registered or invoked (reference: ToolsService, setOutputDir,
createTool, outputDir, tool, executeTool).

---

Duplicate comments:
In `@mini-muse/docs/design.md`:
- Around line 259-271: The markdown SSE example block lacks a fenced-code
language specifier; update the triple-backtick fence that wraps the events
(containing lines like "event: thread.run.created", "event:
thread.message.delta", "event: thread.run.completed", and "event: done") to
include a language tag such as "text" (e.g., change ``` to ```text) so the code
block satisfies markdown linting and improves readability.
- Around line 368-400: The fenced code block showing the project directory tree
in design.md is missing a language specifier; update the triple-backtick opening
fence that precedes the mini-muse/ tree to use ```text so the block is
consistently highlighted (e.g., change ``` to ```text for the directory
structure block in docs/design.md).
- Around line 222-238: Update the fenced code block in mini-muse/docs/design.md
that shows the frontend/src/ directory tree to include a language specifier
(e.g., change the opening "```" to "```text" or "```plaintext") so Markdown
renderers treat the directory structure correctly; locate the block containing
"frontend/src/" and the list of pages/components/services/types and replace the
fence accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3858f6c4-ad66-49b6-8046-a77a3563810a

📥 Commits

Reviewing files that changed from the base of the PR and between 2973efa and d806686.

📒 Files selected for processing (4)
  • mini-muse/app/lib/tools/registry.ts
  • mini-muse/app/service/orchestrator.ts
  • mini-muse/app/service/tools.ts
  • mini-muse/docs/design.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • mini-muse/app/service/orchestrator.ts

Comment on lines +50 to +63
export async function executeTool(
name: ToolName,
input: Record<string, unknown>,
outputDir: string
): Promise<ToolResult> {
const executor = toolExecutors[name];
if (!executor) {
return {
success: false,
error: `Unknown tool: ${name}`,
};
}

return executor(input, outputDir);
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

Catch executor exceptions in the dispatcher.

A few executors do raw formatting/filesystem work and can throw instead of returning ToolResult (for example, mini-muse/app/lib/tools/generator/createFile.ts, Lines 34-78, and mini-muse/app/lib/tools/generator/createConfig.ts, Lines 140-205). Letting that escape here will fail the whole MCP call instead of returning a recoverable tool error.

Proposed fix
 export async function executeTool(
   name: ToolName,
   input: Record<string, unknown>,
   outputDir: string
 ): Promise<ToolResult> {
   const executor = toolExecutors[name];
   if (!executor) {
     return {
       success: false,
       error: `Unknown tool: ${name}`,
     };
   }
 
-  return executor(input, outputDir);
+  try {
+    return await executor(input, outputDir);
+  } catch (error) {
+    return {
+      success: false,
+      error: error instanceof Error ? error.message : 'Tool execution failed',
+    };
+  }
 }
📝 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 async function executeTool(
name: ToolName,
input: Record<string, unknown>,
outputDir: string
): Promise<ToolResult> {
const executor = toolExecutors[name];
if (!executor) {
return {
success: false,
error: `Unknown tool: ${name}`,
};
}
return executor(input, outputDir);
export async function executeTool(
name: ToolName,
input: Record<string, unknown>,
outputDir: string
): Promise<ToolResult> {
const executor = toolExecutors[name];
if (!executor) {
return {
success: false,
error: `Unknown tool: ${name}`,
};
}
try {
return await executor(input, outputDir);
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Tool execution failed',
};
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/lib/tools/registry.ts` around lines 50 - 63, The dispatcher
executeTool currently calls the selected executor directly and lets exceptions
propagate; wrap the executor invocation in a try/catch inside executeTool
(referencing executeTool and toolExecutors) so any thrown error is caught and
you return a ToolResult with success: false and an error message (include the
caught error message/string). Preserve the existing handling for unknown tools,
and ensure the catch returns a ToolResult rather than rethrowing so callers
always receive a ToolResult.

Comment on lines +17 to +21
return tool(name, description, inputSchema, async (args) => {
const result = await executeTool(name, args as Record<string, unknown>, this.outputDir);
return {
content: [{ type: 'text' as const, text: JSON.stringify(result) }],
};
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

Fail fast when outputDir has not been initialized.

Right now an empty outputDir falls through to the executors. For the filesystem-backed tools, that means reads/writes/deletes resolve relative to the server's current working directory instead of an isolated generated workspace.

Proposed fix
   private createTool(name: ToolName, description: string, inputSchema: Parameters<typeof tool>[2]) {
     return tool(name, description, inputSchema, async (args) => {
+      if (!this.outputDir) {
+        return {
+          content: [{
+            type: 'text' as const,
+            text: JSON.stringify({
+              success: false,
+              error: 'outputDir is not initialized',
+            }),
+          }],
+        };
+      }
       const result = await executeTool(name, args as Record<string, unknown>, this.outputDir);
       return {
         content: [{ type: 'text' as const, text: JSON.stringify(result) }],
       };
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mini-muse/app/service/tools.ts` around lines 17 - 21, The tool wrapper
currently calls executeTool with this.outputDir even when outputDir is empty,
causing filesystem tools to operate in the process CWD; update the async handler
returned by tool (the anonymous async (args) => { ... } block) to validate
this.outputDir (or this?.outputDir) up front and throw a clear error (e.g.,
"outputDir not initialized") if falsy before calling executeTool(name, args,
this.outputDir) so execution fails fast and prevents accidental reads/writes
outside the intended workspace.

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