diff --git a/apps/app/electron/services/spec-regeneration-service.js b/apps/app/electron/services/spec-regeneration-service.js
index a977a0a6..90a37817 100644
--- a/apps/app/electron/services/spec-regeneration-service.js
+++ b/apps/app/electron/services/spec-regeneration-service.js
@@ -588,7 +588,7 @@ You should:
4. Based on the user's project overview, create a comprehensive app specification
5. Be liberal and comprehensive when defining features - include everything needed for a complete, polished application
6. Use the XML template format provided
-7. Write the specification to .automaker/app_spec.txt
+7. **MANDATORY: Write the spec to EXACTLY \`.automaker/app_spec.txt\` - this exact filename, no alternatives**
When analyzing, look at:
- package.json, cargo.toml, requirements.txt or similar config files for tech stack
@@ -598,11 +598,17 @@ When analyzing, look at:
- API structures and patterns
You CAN and SHOULD modify:
-- .automaker/app_spec.txt (this is your primary target)
+- .automaker/app_spec.txt (this is your ONLY target file - use EXACTLY this filename)
-You have access to file reading, writing, and search tools. Use them to understand the codebase and write the new spec.
+You have access to file reading, writing, and search tools. Use them to understand the codebase and WRITE the new spec to .automaker/app_spec.txt.
-**IMPORTANT:** Focus ONLY on creating the app_spec.txt file. Do NOT create any feature files or use any feature management tools during this phase.`;
+**IMPORTANT:** Focus ONLY on creating the app_spec.txt file. Do NOT create any feature files or use any feature management tools during this phase.
+
+**CRITICAL FILE NAMING RULES:**
+- The spec file MUST be named exactly \`app_spec.txt\`
+- Do NOT create project-spec.md, spec.md, or any other filename
+- Do NOT use markdown (.md) extension - use .txt
+- The full path must be: \`.automaker/app_spec.txt\``;
}
/**
@@ -639,7 +645,11 @@ ${APP_SPEC_XML_TEMPLATE}
- **development_workflow**: Note any testing or development patterns
- **implementation_roadmap**: Break down the features into phases - be VERY detailed here, listing every feature that needs to be built
-4. **IMPORTANT**: Write the complete specification to the file \`.automaker/app_spec.txt\`
+4. **MANDATORY FILE WRITE**: You MUST write the spec to EXACTLY this file path: \`.automaker/app_spec.txt\`
+ - The filename MUST be exactly \`app_spec.txt\` - do NOT use any other name
+ - Do NOT create \`project-spec.md\`, \`spec.md\`, or any other filename
+ - Do NOT output the spec in your response - write it to the file
+ - Use the Write tool with path \`.automaker/app_spec.txt\`
**Guidelines:**
- Be comprehensive! Include ALL features needed for a complete application
@@ -648,8 +658,9 @@ ${APP_SPEC_XML_TEMPLATE}
- The implementation_roadmap should reflect logical phases for building out the app - list EVERY feature individually
- Consider user flows, error states, and edge cases when defining features
- Each phase should have multiple specific, actionable features
+- **CRITICAL: Write to EXACTLY \`.automaker/app_spec.txt\` - not project-spec.md or any other name!**
-Begin by exploring the project structure.`;
+Begin by exploring the project structure, then generate and WRITE the spec to \`.automaker/app_spec.txt\`.`;
}
/**
@@ -848,7 +859,7 @@ You should:
3. Understand the current architecture and patterns used
4. Based on the user's project definition, create a comprehensive app specification that includes ALL features needed to realize their vision
5. Be liberal and comprehensive when defining features - include everything needed for a complete, polished application
-6. Write the specification to .automaker/app_spec.txt
+6. **MANDATORY: Write the spec to EXACTLY \`.automaker/app_spec.txt\` - this exact filename, no alternatives**
When analyzing, look at:
- package.json, cargo.toml, or similar config files for tech stack
@@ -861,9 +872,15 @@ When analyzing, look at:
Your task is ONLY to update the app_spec.txt file - feature files will be managed separately.
You CAN and SHOULD modify:
-- .automaker/app_spec.txt (this is your primary target)
+- .automaker/app_spec.txt (this is your ONLY target file - use EXACTLY this filename)
-You have access to file reading, writing, and search tools. Use them to understand the codebase and write the new spec.`;
+You have access to file reading, writing, and search tools. Use them to understand the codebase and WRITE the new spec to .automaker/app_spec.txt.
+
+**CRITICAL FILE NAMING RULES:**
+- The spec file MUST be named exactly \`app_spec.txt\`
+- Do NOT create project-spec.md, spec.md, or any other filename
+- Do NOT use markdown (.md) extension - use .txt
+- The full path must be: \`.automaker/app_spec.txt\``;
}
/**
@@ -892,37 +909,40 @@ ${projectDefinition}
- Think about user experience, error handling, edge cases, etc.
- Architecture Notes: Any important architectural decisions or patterns
-3. **IMPORTANT**: Write the complete specification to the file \`.automaker/app_spec.txt\`
+3. **MANDATORY FILE WRITE**: You MUST write the spec to EXACTLY this file path: \`.automaker/app_spec.txt\`
+ - The filename MUST be exactly \`app_spec.txt\` - do NOT use any other name
+ - Do NOT create \`project-spec.md\`, \`spec.md\`, or any other filename
+ - Do NOT output the spec in your response - write it to the file
+ - Use the Write tool with path \`.automaker/app_spec.txt\`
-**Format Guidelines for the Spec:**
+**Format Guidelines for the Spec (use XML format in app_spec.txt):**
-Use this general structure:
+Use this XML structure inside app_spec.txt:
-\`\`\`
-# [App Name] - Application Specification
-
-## Product Overview
-[Description of what the app does and its purpose]
-
-## Tech Stack
-- Frontend: [frameworks, libraries]
-- Backend: [frameworks, APIs]
-- Database: [if applicable]
-- Other: [other relevant tech]
-
-## Features
-
-### [Category 1]
-- **[Feature Name]**: [Detailed description of the feature]
-- **[Feature Name]**: [Detailed description]
-...
-
-### [Category 2]
-- **[Feature Name]**: [Detailed description]
-...
-
-## Architecture Notes
-[Any important architectural notes, patterns, or conventions]
+\`\`\`xml
+
+ [App Name]
+
+
+ [Description of what the app does and its purpose]
+
+
+
+ [frameworks, libraries]
+ [frameworks, APIs]
+ [if applicable]
+
+
+
+ [List all the major capabilities]
+
+
+
+ [Foundation features]
+ [Core features]
+ [Polish features]
+
+
\`\`\`
**Remember:**
@@ -930,9 +950,9 @@ Use this general structure:
- Consider user flows, error states, loading states, etc.
- Include authentication, authorization if relevant
- Think about what would make this a polished, production-ready app
-- The more detailed and complete the spec, the better
+- **CRITICAL: Write to EXACTLY \`.automaker/app_spec.txt\` - not project-spec.md or any other name!**
-Begin by exploring the project structure.`;
+Begin by exploring the project structure, then generate and WRITE the spec to \`.automaker/app_spec.txt\`.`;
}
/**
diff --git a/apps/app/src/components/views/analysis-view.tsx b/apps/app/src/components/views/analysis-view.tsx
index 530ef2f9..a7a9e85f 100644
--- a/apps/app/src/components/views/analysis-view.tsx
+++ b/apps/app/src/components/views/analysis-view.tsx
@@ -399,7 +399,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
`;
// Write the spec file
- const specPath = `${currentProject.path}/app_spec.txt`;
+ const specPath = `${currentProject.path}/.automaker/app_spec.txt`;
const writeResult = await api.writeFile(specPath, specContent);
if (writeResult.success) {
diff --git a/apps/app/src/components/views/kanban-card.tsx b/apps/app/src/components/views/kanban-card.tsx
index 7888de3e..41a35729 100644
--- a/apps/app/src/components/views/kanban-card.tsx
+++ b/apps/app/src/components/views/kanban-card.tsx
@@ -207,10 +207,12 @@ export const KanbanCard = memo(function KanbanCard({
// - Backlog items can always be dragged
// - skipTests items can be dragged even when in_progress or verified (unless currently running)
// - waiting_approval items can always be dragged (to allow manual verification via drag)
- // - Non-skipTests (TDD) items in progress or verified cannot be dragged
+ // - verified items can always be dragged (to allow moving back to waiting_approval or backlog)
+ // - Non-skipTests (TDD) items in progress cannot be dragged (they are running)
const isDraggable =
feature.status === "backlog" ||
feature.status === "waiting_approval" ||
+ feature.status === "verified" ||
(feature.skipTests && !isCurrentAutoTask);
const {
attributes,
diff --git a/apps/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx b/apps/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx
index 94a49338..0c36b2ef 100644
--- a/apps/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx
+++ b/apps/app/src/components/views/settings-view/api-keys/authentication-status-display.tsx
@@ -61,13 +61,15 @@ export function AuthenticationStatusDisplay({
{claudeAuthStatus.method === "oauth_token_env"
? "Using CLAUDE_CODE_OAUTH_TOKEN"
: claudeAuthStatus.method === "oauth_token"
- ? "Using stored OAuth token (claude login)"
+ ? "Using stored OAuth token (subscription)"
: claudeAuthStatus.method === "api_key_env"
? "Using ANTHROPIC_API_KEY"
: claudeAuthStatus.method === "api_key"
? "Using stored API key"
: claudeAuthStatus.method === "credentials_file"
? "Using credentials file"
+ : claudeAuthStatus.method === "cli_authenticated"
+ ? "Using Claude CLI authentication"
: `Using ${claudeAuthStatus.method || "detected"} authentication`}
diff --git a/apps/app/src/components/views/settings-view/hooks/use-cli-status.ts b/apps/app/src/components/views/settings-view/hooks/use-cli-status.ts
index 3f4422c4..600a5f67 100644
--- a/apps/app/src/components/views/settings-view/hooks/use-cli-status.ts
+++ b/apps/app/src/components/views/settings-view/hooks/use-cli-status.ts
@@ -74,8 +74,8 @@ export function useCliStatus() {
apiKeyValid?: boolean;
};
// Map server method names to client method types
- // Server returns: oauth_token_env, oauth_token, api_key_env, api_key, credentials_file, none
- const validMethods = ["oauth_token_env", "oauth_token", "api_key", "api_key_env", "credentials_file", "none"] as const;
+ // Server returns: oauth_token_env, oauth_token, api_key_env, api_key, credentials_file, cli_authenticated, none
+ const validMethods = ["oauth_token_env", "oauth_token", "api_key", "api_key_env", "credentials_file", "cli_authenticated", "none"] as const;
type AuthMethod = typeof validMethods[number];
const method: AuthMethod = validMethods.includes(auth.method as AuthMethod)
? (auth.method as AuthMethod)
diff --git a/apps/app/src/components/views/setup-view/hooks/use-cli-status.ts b/apps/app/src/components/views/setup-view/hooks/use-cli-status.ts
index b7a31685..1aa0d094 100644
--- a/apps/app/src/components/views/setup-view/hooks/use-cli-status.ts
+++ b/apps/app/src/components/views/setup-view/hooks/use-cli-status.ts
@@ -40,6 +40,8 @@ export function useCliStatus({
"oauth_token",
"api_key",
"api_key_env",
+ "credentials_file",
+ "cli_authenticated",
"none",
] as const;
type AuthMethod = (typeof validMethods)[number];
diff --git a/apps/app/src/components/views/spec-view.tsx b/apps/app/src/components/views/spec-view.tsx
index 87173e9b..3fbf4ffe 100644
--- a/apps/app/src/components/views/spec-view.tsx
+++ b/apps/app/src/components/views/spec-view.tsx
@@ -14,7 +14,8 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
-import { Save, RefreshCw, FileText, Sparkles, Loader2, FilePlus2, AlertCircle, ListPlus } from "lucide-react";
+import { Save, RefreshCw, FileText, Sparkles, Loader2, FilePlus2, AlertCircle, ListPlus, CheckCircle2 } from "lucide-react";
+import { toast } from "sonner";
import { Checkbox } from "@/components/ui/checkbox";
import { XmlSyntaxEditor } from "@/components/ui/xml-syntax-editor";
import type { SpecRegenerationEvent } from "@/types/electron";
@@ -311,14 +312,22 @@ export function SpecView() {
// The backend sends explicit signals for completion:
// 1. "All tasks completed" in the message
// 2. [Phase: complete] marker in logs
+ // 3. "Spec regeneration complete!" for regeneration
+ // 4. "Initial spec creation complete!" for creation without features
const isFinalCompletionMessage = event.message?.includes("All tasks completed") ||
event.message === "All tasks completed!" ||
- event.message === "All tasks completed";
+ event.message === "All tasks completed" ||
+ event.message === "Spec regeneration complete!" ||
+ event.message === "Initial spec creation complete!";
const hasCompletePhase = logsRef.current.includes("[Phase: complete]");
+ // Intermediate completion means features are being generated after spec creation
+ const isIntermediateCompletion = event.message?.includes("Features are being generated") ||
+ event.message?.includes("features are being generated");
+
// Rely solely on explicit backend signals
- const shouldComplete = isFinalCompletionMessage || hasCompletePhase;
+ const shouldComplete = (isFinalCompletionMessage || hasCompletePhase) && !isIntermediateCompletion;
if (shouldComplete) {
// Fully complete - clear all states immediately
@@ -337,9 +346,29 @@ export function SpecView() {
setProjectOverview("");
setErrorMessage("");
stateRestoredRef.current = false;
- // Reload the spec to show the new content
- loadSpec();
- } else {
+
+ // Reload the spec with delay to ensure file is written to disk
+ setTimeout(() => {
+ loadSpec();
+ }, SPEC_FILE_WRITE_DELAY);
+
+ // Show success toast notification
+ const isRegeneration = event.message?.includes("regeneration");
+ const isFeatureGeneration = event.message?.includes("Feature generation");
+ toast.success(
+ isFeatureGeneration
+ ? "Feature Generation Complete"
+ : isRegeneration
+ ? "Spec Regeneration Complete"
+ : "Spec Creation Complete",
+ {
+ description: isFeatureGeneration
+ ? "Features have been created from the app specification."
+ : "Your app specification has been saved.",
+ icon: ,
+ }
+ );
+ } else if (isIntermediateCompletion) {
// Intermediate completion - keep state active for feature generation
setIsCreating(true);
setIsRegenerating(true);
diff --git a/apps/app/src/store/setup-store.ts b/apps/app/src/store/setup-store.ts
index 0265e64b..15714d28 100644
--- a/apps/app/src/store/setup-store.ts
+++ b/apps/app/src/store/setup-store.ts
@@ -17,6 +17,7 @@ export type ClaudeAuthMethod =
| "api_key_env" // ANTHROPIC_API_KEY environment variable
| "api_key" // Manually stored API key
| "credentials_file" // Generic credentials file detection
+ | "cli_authenticated" // Claude CLI is installed and has active sessions/activity
| "none";
// Claude Auth Status
diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts
index 2c4821b2..90238848 100644
--- a/apps/server/src/index.ts
+++ b/apps/server/src/index.ts
@@ -39,30 +39,30 @@ const PORT = parseInt(process.env.PORT || "3008", 10);
const DATA_DIR = process.env.DATA_DIR || "./data";
// Check for required environment variables
+// Claude Agent SDK supports EITHER OAuth token (subscription) OR API key (pay-per-use)
const hasAnthropicKey = !!process.env.ANTHROPIC_API_KEY;
const hasOAuthToken = !!process.env.CLAUDE_CODE_OAUTH_TOKEN;
-if (!hasAnthropicKey) {
+if (!hasAnthropicKey && !hasOAuthToken) {
console.warn(`
╔═══════════════════════════════════════════════════════════════════════╗
-║ ⚠️ WARNING: ANTHROPIC_API_KEY not set ║
+║ ⚠️ WARNING: No Claude authentication configured ║
║ ║
-║ The Claude Agent SDK requires ANTHROPIC_API_KEY to function. ║
-║ ${
- hasOAuthToken
- ? " You have CLAUDE_CODE_OAUTH_TOKEN set - this is for CLI auth only."
- : ""
- }
+║ The Claude Agent SDK requires authentication to function. ║
║ ║
-║ Set your API key: ║
+║ Option 1 - Subscription (OAuth Token): ║
+║ export CLAUDE_CODE_OAUTH_TOKEN="your-oauth-token" ║
+║ ║
+║ Option 2 - Pay-per-use (API Key): ║
║ export ANTHROPIC_API_KEY="sk-ant-..." ║
║ ║
-║ Or add to apps/server/.env: ║
-║ ANTHROPIC_API_KEY=sk-ant-... ║
+║ Or use the setup wizard in Settings to configure authentication. ║
╚═══════════════════════════════════════════════════════════════════════╝
`);
+} else if (hasOAuthToken) {
+ console.log("[Server] ✓ CLAUDE_CODE_OAUTH_TOKEN detected (subscription auth)");
} else {
- console.log("[Server] ✓ ANTHROPIC_API_KEY detected");
+ console.log("[Server] ✓ ANTHROPIC_API_KEY detected (API key auth)");
}
// Initialize security
diff --git a/apps/server/src/routes/setup.ts b/apps/server/src/routes/setup.ts
index 48cbec89..a1b5b38b 100644
--- a/apps/server/src/routes/setup.ts
+++ b/apps/server/src/routes/setup.ts
@@ -107,12 +107,14 @@ export function createSetupRoutes(): Router {
}
// Check authentication - detect all possible auth methods
+ // Note: apiKeys.anthropic_oauth_token stores OAuth tokens from subscription auth
+ // apiKeys.anthropic stores direct API keys for pay-per-use
let auth = {
authenticated: false,
method: "none" as string,
hasCredentialsFile: false,
hasToken: false,
- hasStoredOAuthToken: false,
+ hasStoredOAuthToken: !!apiKeys.anthropic_oauth_token,
hasStoredApiKey: !!apiKeys.anthropic,
hasEnvApiKey: !!process.env.ANTHROPIC_API_KEY,
hasEnvOAuthToken: !!process.env.CLAUDE_CODE_OAUTH_TOKEN,
@@ -199,9 +201,17 @@ export function createSetupRoutes(): Router {
auth.method = "api_key_env"; // API key from ANTHROPIC_API_KEY env var
}
- // In-memory stored API key (from settings UI)
+ // In-memory stored OAuth token (from setup wizard - subscription auth)
+ if (!auth.authenticated && apiKeys.anthropic_oauth_token) {
+ auth.authenticated = true;
+ auth.oauthTokenValid = true;
+ auth.method = "oauth_token"; // Stored OAuth token from setup wizard
+ }
+
+ // In-memory stored API key (from settings UI - pay-per-use)
if (!auth.authenticated && apiKeys.anthropic) {
auth.authenticated = true;
+ auth.apiKeyValid = true;
auth.method = "api_key"; // Manually stored API key
}
@@ -393,9 +403,19 @@ export function createSetupRoutes(): Router {
apiKeys[provider] = apiKey;
// Also set as environment variable and persist to .env
- if (provider === "anthropic" || provider === "anthropic_oauth_token") {
+ // IMPORTANT: OAuth tokens and API keys must be stored separately
+ // - OAuth tokens (subscription auth) -> CLAUDE_CODE_OAUTH_TOKEN
+ // - API keys (pay-per-use) -> ANTHROPIC_API_KEY
+ if (provider === "anthropic_oauth_token") {
+ // OAuth token from claude setup-token (subscription-based auth)
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = apiKey;
+ await persistApiKeyToEnv("CLAUDE_CODE_OAUTH_TOKEN", apiKey);
+ console.log("[Setup] Stored OAuth token as CLAUDE_CODE_OAUTH_TOKEN");
+ } else if (provider === "anthropic") {
+ // Direct API key (pay-per-use)
process.env.ANTHROPIC_API_KEY = apiKey;
await persistApiKeyToEnv("ANTHROPIC_API_KEY", apiKey);
+ console.log("[Setup] Stored API key as ANTHROPIC_API_KEY");
} else if (provider === "openai") {
process.env.OPENAI_API_KEY = apiKey;
await persistApiKeyToEnv("OPENAI_API_KEY", apiKey);
diff --git a/apps/server/src/routes/spec-regeneration.ts b/apps/server/src/routes/spec-regeneration.ts
index f2409d34..85148f42 100644
--- a/apps/server/src/routes/spec-regeneration.ts
+++ b/apps/server/src/routes/spec-regeneration.ts
@@ -11,11 +11,28 @@ import type { EventEmitter } from "../lib/events.js";
let isRunning = false;
let currentAbortController: AbortController | null = null;
+// Helper to log authentication status
+function logAuthStatus(context: string): void {
+ const hasOAuthToken = !!process.env.CLAUDE_CODE_OAUTH_TOKEN;
+ const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
+
+ console.log(`[SpecRegeneration] ${context} - Auth Status:`);
+ console.log(`[SpecRegeneration] CLAUDE_CODE_OAUTH_TOKEN: ${hasOAuthToken ? 'SET (' + process.env.CLAUDE_CODE_OAUTH_TOKEN?.substring(0, 20) + '...)' : 'NOT SET'}`);
+ console.log(`[SpecRegeneration] ANTHROPIC_API_KEY: ${hasApiKey ? 'SET (' + process.env.ANTHROPIC_API_KEY?.substring(0, 20) + '...)' : 'NOT SET'}`);
+
+ if (!hasOAuthToken && !hasApiKey) {
+ console.error(`[SpecRegeneration] ⚠️ WARNING: No authentication configured! SDK will fail.`);
+ }
+}
+
export function createSpecRegenerationRoutes(events: EventEmitter): Router {
const router = Router();
// Create project spec from overview
router.post("/create", async (req: Request, res: Response) => {
+ console.log("[SpecRegeneration] ========== /create endpoint called ==========");
+ console.log("[SpecRegeneration] Request body:", JSON.stringify(req.body, null, 2));
+
try {
const { projectPath, projectOverview, generateFeatures } = req.body as {
projectPath: string;
@@ -23,7 +40,13 @@ export function createSpecRegenerationRoutes(events: EventEmitter): Router {
generateFeatures?: boolean;
};
+ console.log(`[SpecRegeneration] Parsed params:`);
+ console.log(`[SpecRegeneration] projectPath: ${projectPath}`);
+ console.log(`[SpecRegeneration] projectOverview length: ${projectOverview?.length || 0} chars`);
+ console.log(`[SpecRegeneration] generateFeatures: ${generateFeatures}`);
+
if (!projectPath || !projectOverview) {
+ console.error("[SpecRegeneration] Missing required parameters");
res.status(400).json({
success: false,
error: "projectPath and projectOverview required",
@@ -32,12 +55,16 @@ export function createSpecRegenerationRoutes(events: EventEmitter): Router {
}
if (isRunning) {
+ console.warn("[SpecRegeneration] Generation already running, rejecting request");
res.json({ success: false, error: "Spec generation already running" });
return;
}
+ logAuthStatus("Before starting generation");
+
isRunning = true;
currentAbortController = new AbortController();
+ console.log("[SpecRegeneration] Starting background generation task...");
// Start generation in background
generateSpec(
@@ -48,19 +75,27 @@ export function createSpecRegenerationRoutes(events: EventEmitter): Router {
generateFeatures
)
.catch((error) => {
- console.error("[SpecRegeneration] Error:", error);
+ console.error("[SpecRegeneration] ❌ Generation failed with error:");
+ console.error("[SpecRegeneration] Error name:", error?.name);
+ console.error("[SpecRegeneration] Error message:", error?.message);
+ console.error("[SpecRegeneration] Error stack:", error?.stack);
+ console.error("[SpecRegeneration] Full error object:", JSON.stringify(error, Object.getOwnPropertyNames(error), 2));
events.emit("spec-regeneration:event", {
type: "spec_error",
- error: error.message,
+ error: error.message || String(error),
});
})
.finally(() => {
+ console.log("[SpecRegeneration] Generation task finished (success or error)");
isRunning = false;
currentAbortController = null;
});
+ console.log("[SpecRegeneration] Returning success response (generation running in background)");
res.json({ success: true });
} catch (error) {
+ console.error("[SpecRegeneration] ❌ Route handler exception:");
+ console.error("[SpecRegeneration] Error:", error);
const message = error instanceof Error ? error.message : "Unknown error";
res.status(500).json({ success: false, error: message });
}
@@ -68,13 +103,21 @@ export function createSpecRegenerationRoutes(events: EventEmitter): Router {
// Generate from project definition
router.post("/generate", async (req: Request, res: Response) => {
+ console.log("[SpecRegeneration] ========== /generate endpoint called ==========");
+ console.log("[SpecRegeneration] Request body:", JSON.stringify(req.body, null, 2));
+
try {
const { projectPath, projectDefinition } = req.body as {
projectPath: string;
projectDefinition: string;
};
+ console.log(`[SpecRegeneration] Parsed params:`);
+ console.log(`[SpecRegeneration] projectPath: ${projectPath}`);
+ console.log(`[SpecRegeneration] projectDefinition length: ${projectDefinition?.length || 0} chars`);
+
if (!projectPath || !projectDefinition) {
+ console.error("[SpecRegeneration] Missing required parameters");
res.status(400).json({
success: false,
error: "projectPath and projectDefinition required",
@@ -83,12 +126,16 @@ export function createSpecRegenerationRoutes(events: EventEmitter): Router {
}
if (isRunning) {
+ console.warn("[SpecRegeneration] Generation already running, rejecting request");
res.json({ success: false, error: "Spec generation already running" });
return;
}
+ logAuthStatus("Before starting generation");
+
isRunning = true;
currentAbortController = new AbortController();
+ console.log("[SpecRegeneration] Starting background generation task...");
generateSpec(
projectPath,
@@ -98,19 +145,27 @@ export function createSpecRegenerationRoutes(events: EventEmitter): Router {
false
)
.catch((error) => {
- console.error("[SpecRegeneration] Error:", error);
+ console.error("[SpecRegeneration] ❌ Generation failed with error:");
+ console.error("[SpecRegeneration] Error name:", error?.name);
+ console.error("[SpecRegeneration] Error message:", error?.message);
+ console.error("[SpecRegeneration] Error stack:", error?.stack);
+ console.error("[SpecRegeneration] Full error object:", JSON.stringify(error, Object.getOwnPropertyNames(error), 2));
events.emit("spec-regeneration:event", {
type: "spec_error",
- error: error.message,
+ error: error.message || String(error),
});
})
.finally(() => {
+ console.log("[SpecRegeneration] Generation task finished (success or error)");
isRunning = false;
currentAbortController = null;
});
+ console.log("[SpecRegeneration] Returning success response (generation running in background)");
res.json({ success: true });
} catch (error) {
+ console.error("[SpecRegeneration] ❌ Route handler exception:");
+ console.error("[SpecRegeneration] Error:", error);
const message = error instanceof Error ? error.message : "Unknown error";
res.status(500).json({ success: false, error: message });
}
@@ -118,37 +173,55 @@ export function createSpecRegenerationRoutes(events: EventEmitter): Router {
// Generate features from existing spec
router.post("/generate-features", async (req: Request, res: Response) => {
+ console.log("[SpecRegeneration] ========== /generate-features endpoint called ==========");
+ console.log("[SpecRegeneration] Request body:", JSON.stringify(req.body, null, 2));
+
try {
const { projectPath } = req.body as { projectPath: string };
+ console.log(`[SpecRegeneration] projectPath: ${projectPath}`);
+
if (!projectPath) {
+ console.error("[SpecRegeneration] Missing projectPath parameter");
res.status(400).json({ success: false, error: "projectPath required" });
return;
}
if (isRunning) {
+ console.warn("[SpecRegeneration] Generation already running, rejecting request");
res.json({ success: false, error: "Generation already running" });
return;
}
+ logAuthStatus("Before starting feature generation");
+
isRunning = true;
currentAbortController = new AbortController();
+ console.log("[SpecRegeneration] Starting background feature generation task...");
generateFeaturesFromSpec(projectPath, events, currentAbortController)
.catch((error) => {
- console.error("[SpecRegeneration] Error:", error);
+ console.error("[SpecRegeneration] ❌ Feature generation failed with error:");
+ console.error("[SpecRegeneration] Error name:", error?.name);
+ console.error("[SpecRegeneration] Error message:", error?.message);
+ console.error("[SpecRegeneration] Error stack:", error?.stack);
+ console.error("[SpecRegeneration] Full error object:", JSON.stringify(error, Object.getOwnPropertyNames(error), 2));
events.emit("spec-regeneration:event", {
type: "features_error",
- error: error.message,
+ error: error.message || String(error),
});
})
.finally(() => {
+ console.log("[SpecRegeneration] Feature generation task finished (success or error)");
isRunning = false;
currentAbortController = null;
});
+ console.log("[SpecRegeneration] Returning success response (generation running in background)");
res.json({ success: true });
} catch (error) {
+ console.error("[SpecRegeneration] ❌ Route handler exception:");
+ console.error("[SpecRegeneration] Error:", error);
const message = error instanceof Error ? error.message : "Unknown error";
res.status(500).json({ success: false, error: message });
}
@@ -188,6 +261,11 @@ async function generateSpec(
abortController: AbortController,
generateFeatures?: boolean
) {
+ console.log("[SpecRegeneration] ========== generateSpec() started ==========");
+ console.log(`[SpecRegeneration] projectPath: ${projectPath}`);
+ console.log(`[SpecRegeneration] projectOverview length: ${projectOverview.length} chars`);
+ console.log(`[SpecRegeneration] generateFeatures: ${generateFeatures}`);
+
const prompt = `You are helping to define a software project specification.
Project Overview:
@@ -214,6 +292,8 @@ Also generate a list of features to implement. For each feature provide:
Format your response as markdown. Be specific and actionable.`;
+ console.log(`[SpecRegeneration] Prompt length: ${prompt.length} chars`);
+
events.emit("spec-regeneration:event", {
type: "spec_progress",
content: "Starting spec generation...\n",
@@ -228,38 +308,78 @@ Format your response as markdown. Be specific and actionable.`;
abortController,
};
- const stream = query({ prompt, options });
- let responseText = "";
+ console.log("[SpecRegeneration] SDK Options:", JSON.stringify(options, null, 2));
+ console.log("[SpecRegeneration] Calling Claude Agent SDK query()...");
+
+ // Log auth status right before the SDK call
+ logAuthStatus("Right before SDK query()");
- for await (const msg of stream) {
- if (msg.type === "assistant" && msg.message.content) {
- for (const block of msg.message.content) {
- if (block.type === "text") {
- responseText = block.text;
- events.emit("spec-regeneration:event", {
- type: "spec_progress",
- content: block.text,
- });
- } else if (block.type === "tool_use") {
- events.emit("spec-regeneration:event", {
- type: "spec_tool",
- tool: block.name,
- input: block.input,
- });
- }
- }
- } else if (msg.type === "result" && msg.subtype === "success") {
- responseText = msg.result || responseText;
- }
+ let stream;
+ try {
+ stream = query({ prompt, options });
+ console.log("[SpecRegeneration] query() returned stream successfully");
+ } catch (queryError) {
+ console.error("[SpecRegeneration] ❌ query() threw an exception:");
+ console.error("[SpecRegeneration] Error:", queryError);
+ throw queryError;
}
+ let responseText = "";
+ let messageCount = 0;
+
+ console.log("[SpecRegeneration] Starting to iterate over stream...");
+
+ try {
+ for await (const msg of stream) {
+ messageCount++;
+ console.log(`[SpecRegeneration] Stream message #${messageCount}:`, JSON.stringify({ type: msg.type, subtype: (msg as any).subtype }, null, 2));
+
+ if (msg.type === "assistant" && msg.message.content) {
+ for (const block of msg.message.content) {
+ if (block.type === "text") {
+ responseText = block.text;
+ console.log(`[SpecRegeneration] Text block received (${block.text.length} chars)`);
+ events.emit("spec-regeneration:event", {
+ type: "spec_progress",
+ content: block.text,
+ });
+ } else if (block.type === "tool_use") {
+ console.log(`[SpecRegeneration] Tool use: ${block.name}`);
+ events.emit("spec-regeneration:event", {
+ type: "spec_tool",
+ tool: block.name,
+ input: block.input,
+ });
+ }
+ }
+ } else if (msg.type === "result" && (msg as any).subtype === "success") {
+ console.log("[SpecRegeneration] Received success result");
+ responseText = (msg as any).result || responseText;
+ } else if (msg.type === "error") {
+ console.error("[SpecRegeneration] ❌ Received error message from stream:");
+ console.error("[SpecRegeneration] Error message:", JSON.stringify(msg, null, 2));
+ }
+ }
+ } catch (streamError) {
+ console.error("[SpecRegeneration] ❌ Error while iterating stream:");
+ console.error("[SpecRegeneration] Stream error:", streamError);
+ throw streamError;
+ }
+
+ console.log(`[SpecRegeneration] Stream iteration complete. Total messages: ${messageCount}`);
+ console.log(`[SpecRegeneration] Response text length: ${responseText.length} chars`);
+
// Save spec
const specDir = path.join(projectPath, ".automaker");
- const specPath = path.join(specDir, "project-spec.md");
+ const specPath = path.join(specDir, "app_spec.txt");
+ console.log(`[SpecRegeneration] Saving spec to: ${specPath}`);
+
await fs.mkdir(specDir, { recursive: true });
await fs.writeFile(specPath, responseText);
+ console.log("[SpecRegeneration] Spec saved successfully");
+
events.emit("spec-regeneration:event", {
type: "spec_complete",
specPath,
@@ -268,8 +388,11 @@ Format your response as markdown. Be specific and actionable.`;
// If generate features was requested, parse and create them
if (generateFeatures) {
+ console.log("[SpecRegeneration] Starting feature generation...");
await parseAndCreateFeatures(projectPath, responseText, events);
}
+
+ console.log("[SpecRegeneration] ========== generateSpec() completed ==========");
}
async function generateFeaturesFromSpec(
@@ -277,13 +400,20 @@ async function generateFeaturesFromSpec(
events: EventEmitter,
abortController: AbortController
) {
+ console.log("[SpecRegeneration] ========== generateFeaturesFromSpec() started ==========");
+ console.log(`[SpecRegeneration] projectPath: ${projectPath}`);
+
// Read existing spec
- const specPath = path.join(projectPath, ".automaker", "project-spec.md");
+ const specPath = path.join(projectPath, ".automaker", "app_spec.txt");
let spec: string;
+ console.log(`[SpecRegeneration] Reading spec from: ${specPath}`);
+
try {
spec = await fs.readFile(specPath, "utf-8");
- } catch {
+ console.log(`[SpecRegeneration] Spec loaded successfully (${spec.length} chars)`);
+ } catch (readError) {
+ console.error("[SpecRegeneration] ❌ Failed to read spec file:", readError);
events.emit("spec-regeneration:event", {
type: "features_error",
error: "No project spec found. Generate spec first.",
@@ -320,6 +450,8 @@ Format as JSON:
Generate 5-15 features that build on each other logically.`;
+ console.log(`[SpecRegeneration] Prompt length: ${prompt.length} chars`);
+
events.emit("spec-regeneration:event", {
type: "features_progress",
content: "Analyzing spec and generating features...\n",
@@ -334,26 +466,62 @@ Generate 5-15 features that build on each other logically.`;
abortController,
};
- const stream = query({ prompt, options });
- let responseText = "";
+ console.log("[SpecRegeneration] SDK Options:", JSON.stringify(options, null, 2));
+ console.log("[SpecRegeneration] Calling Claude Agent SDK query() for features...");
+
+ logAuthStatus("Right before SDK query() for features");
- for await (const msg of stream) {
- if (msg.type === "assistant" && msg.message.content) {
- for (const block of msg.message.content) {
- if (block.type === "text") {
- responseText = block.text;
- events.emit("spec-regeneration:event", {
- type: "features_progress",
- content: block.text,
- });
- }
- }
- } else if (msg.type === "result" && msg.subtype === "success") {
- responseText = msg.result || responseText;
- }
+ let stream;
+ try {
+ stream = query({ prompt, options });
+ console.log("[SpecRegeneration] query() returned stream successfully");
+ } catch (queryError) {
+ console.error("[SpecRegeneration] ❌ query() threw an exception:");
+ console.error("[SpecRegeneration] Error:", queryError);
+ throw queryError;
}
+ let responseText = "";
+ let messageCount = 0;
+
+ console.log("[SpecRegeneration] Starting to iterate over feature stream...");
+
+ try {
+ for await (const msg of stream) {
+ messageCount++;
+ console.log(`[SpecRegeneration] Feature stream message #${messageCount}:`, JSON.stringify({ type: msg.type, subtype: (msg as any).subtype }, null, 2));
+
+ if (msg.type === "assistant" && msg.message.content) {
+ for (const block of msg.message.content) {
+ if (block.type === "text") {
+ responseText = block.text;
+ console.log(`[SpecRegeneration] Feature text block received (${block.text.length} chars)`);
+ events.emit("spec-regeneration:event", {
+ type: "features_progress",
+ content: block.text,
+ });
+ }
+ }
+ } else if (msg.type === "result" && (msg as any).subtype === "success") {
+ console.log("[SpecRegeneration] Received success result for features");
+ responseText = (msg as any).result || responseText;
+ } else if (msg.type === "error") {
+ console.error("[SpecRegeneration] ❌ Received error message from feature stream:");
+ console.error("[SpecRegeneration] Error message:", JSON.stringify(msg, null, 2));
+ }
+ }
+ } catch (streamError) {
+ console.error("[SpecRegeneration] ❌ Error while iterating feature stream:");
+ console.error("[SpecRegeneration] Stream error:", streamError);
+ throw streamError;
+ }
+
+ console.log(`[SpecRegeneration] Feature stream complete. Total messages: ${messageCount}`);
+ console.log(`[SpecRegeneration] Feature response length: ${responseText.length} chars`);
+
await parseAndCreateFeatures(projectPath, responseText, events);
+
+ console.log("[SpecRegeneration] ========== generateFeaturesFromSpec() completed ==========");
}
async function parseAndCreateFeatures(
@@ -361,20 +529,31 @@ async function parseAndCreateFeatures(
content: string,
events: EventEmitter
) {
+ console.log("[SpecRegeneration] ========== parseAndCreateFeatures() started ==========");
+ console.log(`[SpecRegeneration] Content length: ${content.length} chars`);
+
try {
// Extract JSON from response
+ console.log("[SpecRegeneration] Extracting JSON from response...");
const jsonMatch = content.match(/\{[\s\S]*"features"[\s\S]*\}/);
if (!jsonMatch) {
+ console.error("[SpecRegeneration] ❌ No valid JSON found in response");
+ console.error("[SpecRegeneration] Content preview:", content.substring(0, 500));
throw new Error("No valid JSON found in response");
}
+ console.log(`[SpecRegeneration] JSON match found (${jsonMatch[0].length} chars)`);
+
const parsed = JSON.parse(jsonMatch[0]);
+ console.log(`[SpecRegeneration] Parsed ${parsed.features?.length || 0} features`);
+
const featuresDir = path.join(projectPath, ".automaker", "features");
await fs.mkdir(featuresDir, { recursive: true });
const createdFeatures: Array<{ id: string; title: string }> = [];
for (const feature of parsed.features) {
+ console.log(`[SpecRegeneration] Creating feature: ${feature.id}`);
const featureDir = path.join(featuresDir, feature.id);
await fs.mkdir(featureDir, { recursive: true });
@@ -398,15 +577,21 @@ async function parseAndCreateFeatures(
createdFeatures.push({ id: feature.id, title: feature.title });
}
+ console.log(`[SpecRegeneration] ✓ Created ${createdFeatures.length} features successfully`);
+
events.emit("spec-regeneration:event", {
type: "features_complete",
features: createdFeatures,
count: createdFeatures.length,
});
} catch (error) {
+ console.error("[SpecRegeneration] ❌ parseAndCreateFeatures() failed:");
+ console.error("[SpecRegeneration] Error:", error);
events.emit("spec-regeneration:event", {
type: "features_error",
error: (error as Error).message,
});
}
+
+ console.log("[SpecRegeneration] ========== parseAndCreateFeatures() completed ==========");
}