From 9a636af9090b122db2e55737fca3e78550aab9df Mon Sep 17 00:00:00 2001 From: bndw Date: Sat, 28 Feb 2026 19:14:01 -0800 Subject: fix: scope artifacts to sessions --- src/main/claude/index.ts | 51 ++++++++++++++++++--------------------- src/main/claude/phases.ts | 61 +++++++++++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 54 deletions(-) (limited to 'src/main') diff --git a/src/main/claude/index.ts b/src/main/claude/index.ts index b8c9c07..4dd49f2 100644 --- a/src/main/claude/index.ts +++ b/src/main/claude/index.ts @@ -1,31 +1,24 @@ import { query, type SDKMessage, type Query } from "@anthropic-ai/claude-agent-sdk"; import type { Session } from "../db/sessions"; -import { getPhaseConfig, getNextPhase, getArtifactFilename, getSessionArtifactDir } from "./phases"; +import { getPhaseConfig, getNextPhase, getArtifactFilename } from "./phases"; import type { Phase, UserPermissionMode } from "./phases"; import { getProject } from "../db/projects"; import { updateSession } from "../db/sessions"; import fs from "node:fs"; import path from "node:path"; -import os from "node:os"; // Track active queries by session ID const activeQueries = new Map(); -// Global storage in home directory -const GLOBAL_CLAUDE_FLOW_DIR = path.join(os.homedir(), ".claude-flow"); - function ensureDir(dirPath: string): void { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } -function getProjectDir(projectId: string): string { - return path.join(GLOBAL_CLAUDE_FLOW_DIR, "projects", projectId); -} - -function getSessionDir(projectId: string, sessionId: string): string { - return path.join(getProjectDir(projectId), "sessions", sessionId); +// Artifacts live inside the project directory so the SDK's Write tool can reach them +function getSessionDir(projectPath: string, sessionId: string): string { + return path.join(projectPath, ".claude-flow", "sessions", sessionId); } export interface SendMessageOptions { @@ -42,12 +35,13 @@ export async function sendMessage({ const project = getProject(session.project_id); if (!project) throw new Error("Project not found"); - // Ensure session artifact directory exists in global storage - const sessionDir = getSessionDir(session.project_id, session.id); + // Ensure session artifact directory exists inside the project + const sessionDir = getSessionDir(project.path, session.id); ensureDir(sessionDir); const phaseConfig = getPhaseConfig( session.phase as Phase, + sessionDir, session.permission_mode as UserPermissionMode ); @@ -112,22 +106,26 @@ export function advancePhase(session: Session): Phase | null { } /** - * Get the artifact path for a session and phase (in global storage) + * Get the artifact path for a session and phase (inside the project directory) */ export function getArtifactPath(session: Session): string { + const project = getProject(session.project_id); + if (!project) throw new Error("Project not found"); const filename = getArtifactFilename(session.phase as Phase); - return path.join(getSessionDir(session.project_id, session.id), filename); + return path.join(getSessionDir(project.path, session.id), filename); } /** - * Read an artifact file for a session (from global storage) + * Read an artifact file for a session */ export function readSessionArtifact( projectId: string, sessionId: string, filename: string ): string | null { - const filePath = path.join(getSessionDir(projectId, sessionId), filename); + const project = getProject(projectId); + if (!project) return null; + const filePath = path.join(getSessionDir(project.path, sessionId), filename); if (fs.existsSync(filePath)) { return fs.readFileSync(filePath, "utf-8"); } @@ -135,7 +133,7 @@ export function readSessionArtifact( } /** - * Write an artifact file for a session (to global storage) + * Write an artifact file for a session */ export function writeSessionArtifact( projectId: string, @@ -143,7 +141,9 @@ export function writeSessionArtifact( filename: string, content: string ): void { - const dir = getSessionDir(projectId, sessionId); + const project = getProject(projectId); + if (!project) throw new Error("Project not found"); + const dir = getSessionDir(project.path, sessionId); ensureDir(dir); fs.writeFileSync(path.join(dir, filename), content, "utf-8"); } @@ -168,22 +168,17 @@ export function writeClaudeMd(projectPath: string, content: string): void { } /** - * Clear session artifacts from global storage + * Clear session artifacts */ export function clearSessionArtifacts(projectId: string, sessionId: string): void { - const dir = getSessionDir(projectId, sessionId); + const project = getProject(projectId); + if (!project) return; + const dir = getSessionDir(project.path, sessionId); if (fs.existsSync(dir)) { fs.rmSync(dir, { recursive: true, force: true }); } } -/** - * Get the initial message for a phase - */ -export function getPhaseInitialMessage(phase: Phase): string { - return getPhaseConfig(phase).initialMessage; -} - // Re-export types export type { SDKMessage } from "@anthropic-ai/claude-agent-sdk"; export type { Phase, UserPermissionMode } from "./phases"; diff --git a/src/main/claude/phases.ts b/src/main/claude/phases.ts index f1df719..89e7c22 100644 --- a/src/main/claude/phases.ts +++ b/src/main/claude/phases.ts @@ -3,6 +3,7 @@ import type { PermissionMode } from "@anthropic-ai/claude-agent-sdk"; export type Phase = "research" | "plan" | "implement"; export type UserPermissionMode = "acceptEdits" | "bypassPermissions"; +// External interface — consumers see a resolved string export interface PhaseConfig { systemPrompt: string; tools: string[]; @@ -10,27 +11,24 @@ export interface PhaseConfig { initialMessage: string; } -// Get session-specific artifact path (relative to ~/.claude-flow/) -export function getSessionArtifactDir(sessionId: string): string { - return `sessions/${sessionId}`; -} - -export function getArtifactPath(phase: Phase, sessionId: string): string { - const dir = getSessionArtifactDir(sessionId); - const filename = phase === "research" ? "research.md" : "plan.md"; - return `${dir}/${filename}`; +// Internal template — systemPrompt is a function that receives the artifact dir +interface PhaseConfigTemplate { + systemPrompt: (artifactDir: string) => string; + tools: string[]; + permissionMode: PermissionMode; + initialMessage: string; } -export const phaseConfigs: Record = { +const phaseConfigTemplates: Record = { research: { permissionMode: "acceptEdits", tools: ["Read", "Glob", "Grep", "Bash", "Write"], initialMessage: "What areas of the codebase should I research? What are you trying to build?", - systemPrompt: `You are in RESEARCH mode. Your ONLY job is to understand the codebase. + systemPrompt: (artifactDir) => `You are in RESEARCH mode. Your ONLY job is to understand the codebase. CRITICAL RULES: -1. You MUST write ALL findings to the session research.md — this is your PRIMARY output +1. You MUST write ALL findings to ${artifactDir}/research.md — this is your PRIMARY output 2. DO NOT just respond in chat. The document viewer shows research.md, so write there. 3. DO NOT suggest moving to planning or implementation 4. DO NOT ask "are you ready to implement?" or similar @@ -46,7 +44,7 @@ WORKFLOW: 1. Read CLAUDE.md (create at project root if missing) 2. Ask what to research (if unclear) 3. Read files thoroughly using Read, Glob, Grep -4. Write findings to session research.md +4. Write findings to ${artifactDir}/research.md 5. Update CLAUDE.md with any new general insights worth sharing FORMAT for research.md: @@ -69,7 +67,7 @@ FORMAT for research.md: [Things that need clarification] \`\`\` -Remember: Your output goes in research.md, not chat. Chat is for clarifying questions only.`, +Remember: Your output goes in ${artifactDir}/research.md, not chat. Chat is for clarifying questions only.`, }, plan: { @@ -77,10 +75,10 @@ Remember: Your output goes in research.md, not chat. Chat is for clarifying ques tools: ["Read", "Glob", "Grep", "Write"], initialMessage: "I'll create a detailed implementation plan based on my research. Writing to plan.md...", - systemPrompt: `You are in PLANNING mode. Your ONLY job is to create an implementation plan. + systemPrompt: (artifactDir) => `You are in PLANNING mode. Your ONLY job is to create an implementation plan. CRITICAL RULES: -1. You MUST write the plan to session plan.md — this is your PRIMARY output +1. You MUST write the plan to ${artifactDir}/plan.md — this is your PRIMARY output 2. DO NOT just respond in chat. The document viewer shows plan.md, so write there. 3. DO NOT implement anything — no code changes to source files 4. DO NOT ask "should I start implementing?" or similar @@ -89,12 +87,12 @@ CRITICAL RULES: CONTEXT: - Read CLAUDE.md at project root for codebase overview -- Read the session research.md to understand the specific task +- Read ${artifactDir}/research.md to understand the specific task WORKFLOW: 1. Read CLAUDE.md for codebase overview -2. Read the session research.md to understand the specific task -3. Write a detailed plan to session plan.md +2. Read ${artifactDir}/research.md to understand the specific task +3. Write a detailed plan to ${artifactDir}/plan.md 4. Include specific code snippets showing proposed changes 5. Make the plan detailed enough that implementation is mechanical @@ -132,7 +130,7 @@ FORMAT for plan.md: When the user adds annotations to plan.md and clicks Review, address each annotation and update the document. -Remember: Your output goes in plan.md, not chat. Chat is for clarifying questions only.`, +Remember: Your output goes in ${artifactDir}/plan.md, not chat. Chat is for clarifying questions only.`, }, implement: { @@ -140,19 +138,19 @@ Remember: Your output goes in plan.md, not chat. Chat is for clarifying question tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"], initialMessage: "Starting implementation. I'll follow the plan exactly and mark tasks complete as I go.", - systemPrompt: `You are in IMPLEMENTATION mode. Execute the approved plan. + systemPrompt: (artifactDir) => `You are in IMPLEMENTATION mode. Execute the approved plan. CRITICAL RULES: -1. Read session plan.md and follow it exactly -2. Mark tasks complete in plan.md as you finish them: - [ ] → - [x] +1. Read ${artifactDir}/plan.md and follow it exactly +2. Mark tasks complete in ${artifactDir}/plan.md as you finish them: - [ ] → - [x] 3. DO NOT deviate from the plan without asking 4. Run tests/typecheck if available 5. Stop and ask if you encounter issues not covered by the plan WORKFLOW: -1. Read session plan.md +1. Read ${artifactDir}/plan.md 2. Execute each task in order -3. Update plan.md to mark tasks complete +3. Update ${artifactDir}/plan.md to mark tasks complete 4. Continue until all tasks are done When complete, summarize what was done and any follow-up tasks.`, @@ -161,15 +159,26 @@ When complete, summarize what was done and any follow-up tasks.`, export function getPhaseConfig( phase: Phase, + artifactDir: string, userPermissionMode?: UserPermissionMode ): PhaseConfig { - const config = { ...phaseConfigs[phase] }; + const template = phaseConfigTemplates[phase]; + const config: PhaseConfig = { + systemPrompt: template.systemPrompt(artifactDir), + tools: template.tools, + permissionMode: template.permissionMode, + initialMessage: template.initialMessage, + }; if (phase === "implement" && userPermissionMode) { config.permissionMode = userPermissionMode; } return config; } +export function getPhaseInitialMessage(phase: Phase): string { + return phaseConfigTemplates[phase].initialMessage; +} + export function getNextPhase(phase: Phase): Phase | null { const transitions: Record = { research: "plan", -- cgit v1.2.3