chore: update .gitignore and remove obsolete automaker files

- Added .automaker/ to .gitignore to prevent tracking of the entire directory.
- Deleted outdated files including app_spec.txt, categories.json, memory.md, clean-code.md, and gemini.md from the .automaker context.
- Enhanced the mcp-server-factory.js and spec-regeneration-service.js to enforce status management for new features, ensuring they default to "backlog" and clarifying status handling in comments.
- Introduced a new file browsing endpoint in fs.ts to improve directory navigation while maintaining security constraints.
This commit is contained in:
Alec Koifman
2025-12-12 17:34:16 -05:00
parent a4c5567768
commit 28bbc3e0e1
10 changed files with 161 additions and 947 deletions

View File

@@ -5,6 +5,7 @@
import { Router, type Request, type Response } from "express";
import fs from "fs/promises";
import os from "os";
import path from "path";
import { validatePath, addAllowedPath, isPathAllowed } from "../lib/security.js";
import type { EventEmitter } from "../lib/events.js";
@@ -422,5 +423,123 @@ export function createFsRoutes(_events: EventEmitter): Router {
}
});
// Browse directories for file picker
// SECURITY: Restricted to home directory, allowed paths, and drive roots on Windows
router.post("/browse", async (req: Request, res: Response) => {
try {
const { dirPath } = req.body as { dirPath?: string };
const homeDir = os.homedir();
// Detect available drives on Windows
const detectDrives = async (): Promise<string[]> => {
if (os.platform() !== "win32") {
return [];
}
const drives: string[] = [];
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (const letter of letters) {
const drivePath = `${letter}:\\`;
try {
await fs.access(drivePath);
drives.push(drivePath);
} catch {
// Drive doesn't exist, skip it
}
}
return drives;
};
// Check if a path is safe to browse
const isSafePath = (targetPath: string): boolean => {
const resolved = path.resolve(targetPath);
const normalizedHome = path.resolve(homeDir);
// Allow browsing within home directory
if (resolved === normalizedHome || resolved.startsWith(normalizedHome + path.sep)) {
return true;
}
// Allow browsing already-allowed paths
if (isPathAllowed(resolved)) {
return true;
}
// On Windows, allow drive roots for initial navigation
if (os.platform() === "win32") {
const driveRootMatch = /^[A-Z]:\\$/i.test(resolved);
if (driveRootMatch) {
return true;
}
}
// On Unix, allow root for initial navigation (but only list, not read files)
if (os.platform() !== "win32" && resolved === "/") {
return true;
}
return false;
};
// Default to home directory if no path provided
const targetPath = dirPath ? path.resolve(dirPath) : homeDir;
// Security check: validate the path is safe to browse
if (!isSafePath(targetPath)) {
res.status(403).json({
success: false,
error: "Access denied: browsing is restricted to your home directory and allowed project paths",
});
return;
}
try {
const stats = await fs.stat(targetPath);
if (!stats.isDirectory()) {
res.status(400).json({ success: false, error: "Path is not a directory" });
return;
}
// Read directory contents
const entries = await fs.readdir(targetPath, { withFileTypes: true });
// Filter for directories only and exclude hidden directories
const directories = entries
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
.map((entry) => ({
name: entry.name,
path: path.join(targetPath, entry.name),
}))
.sort((a, b) => a.name.localeCompare(b.name));
// Get parent directory (only if parent is also safe to browse)
const parentPath = path.dirname(targetPath);
const hasParent = parentPath !== targetPath && isSafePath(parentPath);
// Get available drives on Windows
const drives = await detectDrives();
res.json({
success: true,
currentPath: targetPath,
parentPath: hasParent ? parentPath : null,
directories,
drives,
});
} catch (error) {
res.status(400).json({
success: false,
error: error instanceof Error ? error.message : "Failed to read directory",
});
}
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
res.status(500).json({ success: false, error: message });
}
});
return router;
}

View File

@@ -255,7 +255,7 @@ Format your response as markdown. Be specific and actionable.`;
// Save spec
const specDir = path.join(projectPath, ".automaker");
const specPath = path.join(specDir, "project-spec.md");
const specPath = path.join(specDir, "app_spec.txt");
await fs.mkdir(specDir, { recursive: true });
await fs.writeFile(specPath, responseText);
@@ -278,7 +278,7 @@ async function generateFeaturesFromSpec(
abortController: AbortController
) {
// Read existing spec
const specPath = path.join(projectPath, ".automaker", "project-spec.md");
const specPath = path.join(projectPath, ".automaker", "app_spec.txt");
let spec: string;
try {
@@ -382,7 +382,7 @@ async function parseAndCreateFeatures(
id: feature.id,
title: feature.title,
description: feature.description,
status: "pending",
status: "backlog", // Features go to backlog - user must manually start them
priority: feature.priority || 2,
complexity: feature.complexity || "moderate",
dependencies: feature.dependencies || [],