aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/claude/index.ts91
-rw-r--r--src/main/claude/phases.ts62
-rw-r--r--src/main/git/worktree.ts179
-rw-r--r--src/main/ipc/handlers.ts86
-rw-r--r--src/main/preload.ts48
5 files changed, 425 insertions, 41 deletions
diff --git a/src/main/claude/index.ts b/src/main/claude/index.ts
index 8bdcccd..4d8909b 100644
--- a/src/main/claude/index.ts
+++ b/src/main/claude/index.ts
@@ -1,6 +1,6 @@
1import { query, type SDKMessage, type Query } from "@anthropic-ai/claude-agent-sdk"; 1import { query, type SDKMessage, type Query } from "@anthropic-ai/claude-agent-sdk";
2import type { Session } from "../db/sessions"; 2import type { Session } from "../db/sessions";
3import { getPhaseConfig, getNextPhase, getArtifactFilename } from "./phases"; 3import { getPhaseConfig, getNextPhase, getArtifactFilename, getSessionArtifactDir } from "./phases";
4import type { Phase, UserPermissionMode } from "./phases"; 4import type { Phase, UserPermissionMode } from "./phases";
5import { getProject } from "../db/projects"; 5import { getProject } from "../db/projects";
6import { updateSession } from "../db/sessions"; 6import { updateSession } from "../db/sessions";
@@ -10,10 +10,9 @@ import path from "node:path";
10// Track active queries by session ID 10// Track active queries by session ID
11const activeQueries = new Map<string, Query>(); 11const activeQueries = new Map<string, Query>();
12 12
13function ensureArtifactDir(projectPath: string): void { 13function ensureDir(dirPath: string): void {
14 const dir = path.join(projectPath, ".claude-flow"); 14 if (!fs.existsSync(dirPath)) {
15 if (!fs.existsSync(dir)) { 15 fs.mkdirSync(dirPath, { recursive: true });
16 fs.mkdirSync(dir, { recursive: true });
17 } 16 }
18} 17}
19 18
@@ -31,7 +30,9 @@ export async function sendMessage({
31 const project = getProject(session.project_id); 30 const project = getProject(session.project_id);
32 if (!project) throw new Error("Project not found"); 31 if (!project) throw new Error("Project not found");
33 32
34 ensureArtifactDir(project.path); 33 // Ensure session artifact directory exists
34 const sessionDir = path.join(project.path, getSessionArtifactDir(session.id));
35 ensureDir(sessionDir);
35 36
36 const phaseConfig = getPhaseConfig( 37 const phaseConfig = getPhaseConfig(
37 session.phase as Phase, 38 session.phase as Phase,
@@ -81,8 +82,8 @@ export async function triggerReview(
81 session: Session, 82 session: Session,
82 onMessage: (msg: SDKMessage) => void 83 onMessage: (msg: SDKMessage) => void
83): Promise<void> { 84): Promise<void> {
84 const docName = getArtifactFilename(session.phase as Phase); 85 const artifactPath = getArtifactPath(session);
85 const message = `I've updated .claude-flow/${docName} with annotations. Read the file, find all my inline notes (marked with // REVIEW:, // NOTE:, TODO:, or similar), address each one, and update the document accordingly. Do not implement anything yet.`; 86 const message = `I've updated ${artifactPath} with annotations. Read the file, find all my inline notes (marked with // REVIEW:, // NOTE:, TODO:, or similar), address each one, and update the document accordingly. Do not implement anything yet.`;
86 87
87 await sendMessage({ session, message, onMessage }); 88 await sendMessage({ session, message, onMessage });
88} 89}
@@ -99,7 +100,63 @@ export function advancePhase(session: Session): Phase | null {
99} 100}
100 101
101/** 102/**
102 * Read an artifact file from the project's .claude-flow directory 103 * Get the artifact path for a session and phase
104 */
105export function getArtifactPath(session: Session): string {
106 const filename = getArtifactFilename(session.phase as Phase);
107 return path.join(getSessionArtifactDir(session.id), filename);
108}
109
110/**
111 * Read an artifact file for a session
112 */
113export function readSessionArtifact(
114 projectPath: string,
115 sessionId: string,
116 filename: string
117): string | null {
118 const filePath = path.join(projectPath, getSessionArtifactDir(sessionId), filename);
119 if (fs.existsSync(filePath)) {
120 return fs.readFileSync(filePath, "utf-8");
121 }
122 return null;
123}
124
125/**
126 * Write an artifact file for a session
127 */
128export function writeSessionArtifact(
129 projectPath: string,
130 sessionId: string,
131 filename: string,
132 content: string
133): void {
134 const dir = path.join(projectPath, getSessionArtifactDir(sessionId));
135 ensureDir(dir);
136 fs.writeFileSync(path.join(dir, filename), content, "utf-8");
137}
138
139/**
140 * Read CLAUDE.md from project root
141 */
142export function readClaudeMd(projectPath: string): string | null {
143 const filePath = path.join(projectPath, "CLAUDE.md");
144 if (fs.existsSync(filePath)) {
145 return fs.readFileSync(filePath, "utf-8");
146 }
147 return null;
148}
149
150/**
151 * Write CLAUDE.md to project root
152 */
153export function writeClaudeMd(projectPath: string, content: string): void {
154 const filePath = path.join(projectPath, "CLAUDE.md");
155 fs.writeFileSync(filePath, content, "utf-8");
156}
157
158/**
159 * Read an artifact file from the project's .claude-flow directory (legacy path)
103 */ 160 */
104export function readArtifact( 161export function readArtifact(
105 projectPath: string, 162 projectPath: string,
@@ -113,7 +170,7 @@ export function readArtifact(
113} 170}
114 171
115/** 172/**
116 * Write an artifact file to the project's .claude-flow directory 173 * Write an artifact file to the project's .claude-flow directory (legacy path)
117 */ 174 */
118export function writeArtifact( 175export function writeArtifact(
119 projectPath: string, 176 projectPath: string,
@@ -121,13 +178,21 @@ export function writeArtifact(
121 content: string 178 content: string
122): void { 179): void {
123 const dir = path.join(projectPath, ".claude-flow"); 180 const dir = path.join(projectPath, ".claude-flow");
124 if (!fs.existsSync(dir)) { 181 ensureDir(dir);
125 fs.mkdirSync(dir, { recursive: true });
126 }
127 fs.writeFileSync(path.join(dir, filename), content, "utf-8"); 182 fs.writeFileSync(path.join(dir, filename), content, "utf-8");
128} 183}
129 184
130/** 185/**
186 * Clear session artifacts
187 */
188export function clearSessionArtifacts(projectPath: string, sessionId: string): void {
189 const dir = path.join(projectPath, getSessionArtifactDir(sessionId));
190 if (fs.existsSync(dir)) {
191 fs.rmSync(dir, { recursive: true, force: true });
192 }
193}
194
195/**
131 * Get the initial message for a phase 196 * Get the initial message for a phase
132 */ 197 */
133export function getPhaseInitialMessage(phase: Phase): string { 198export function getPhaseInitialMessage(phase: Phase): string {
diff --git a/src/main/claude/phases.ts b/src/main/claude/phases.ts
index 2ea1ea8..6992047 100644
--- a/src/main/claude/phases.ts
+++ b/src/main/claude/phases.ts
@@ -10,6 +10,17 @@ export interface PhaseConfig {
10 initialMessage: string; 10 initialMessage: string;
11} 11}
12 12
13// Get session-specific artifact path
14export function getSessionArtifactDir(sessionId: string): string {
15 return `.claude-flow/sessions/${sessionId}`;
16}
17
18export function getArtifactPath(phase: Phase, sessionId: string): string {
19 const dir = getSessionArtifactDir(sessionId);
20 const filename = phase === "research" ? "research.md" : "plan.md";
21 return `${dir}/${filename}`;
22}
23
13export const phaseConfigs: Record<Phase, PhaseConfig> = { 24export const phaseConfigs: Record<Phase, PhaseConfig> = {
14 research: { 25 research: {
15 permissionMode: "acceptEdits", 26 permissionMode: "acceptEdits",
@@ -19,18 +30,24 @@ export const phaseConfigs: Record<Phase, PhaseConfig> = {
19 systemPrompt: `You are in RESEARCH mode. Your ONLY job is to understand the codebase. 30 systemPrompt: `You are in RESEARCH mode. Your ONLY job is to understand the codebase.
20 31
21CRITICAL RULES: 32CRITICAL RULES:
221. You MUST write ALL findings to .claude-flow/research.md — this is your PRIMARY output 331. You MUST write ALL findings to the session research.md — this is your PRIMARY output
232. DO NOT just respond in chat. The document viewer shows research.md, so write there. 342. DO NOT just respond in chat. The document viewer shows research.md, so write there.
243. DO NOT suggest moving to planning or implementation 353. DO NOT suggest moving to planning or implementation
254. DO NOT ask "are you ready to implement?" or similar 364. DO NOT ask "are you ready to implement?" or similar
265. DO NOT modify any source code files 375. DO NOT modify any source code files
276. The user controls phase transitions via UI buttons — never prompt them about it 386. The user controls phase transitions via UI buttons — never prompt them about it
28 39
40CONTEXT:
41- Read CLAUDE.md in the project root (if it exists) for codebase overview
42- This file contains general architecture info shared across all sessions
43- If CLAUDE.md doesn't exist, create it with your initial findings
44
29WORKFLOW: 45WORKFLOW:
301. Ask what to research (if unclear) 461. Read CLAUDE.md (create if missing)
312. Read files thoroughly using Read, Glob, Grep 472. Ask what to research (if unclear)
323. Write structured findings to .claude-flow/research.md 483. Read files thoroughly using Read, Glob, Grep
334. Keep updating research.md as you learn more 494. Write findings to session research.md
505. Update CLAUDE.md with any new general insights
34 51
35FORMAT for research.md: 52FORMAT for research.md:
36\`\`\`markdown 53\`\`\`markdown
@@ -52,8 +69,6 @@ FORMAT for research.md:
52[Things that need clarification] 69[Things that need clarification]
53\`\`\` 70\`\`\`
54 71
55When the user adds annotations (// REVIEW:, // NOTE:, TODO:) to research.md and clicks Review, address each annotation and update the document.
56
57Remember: Your output goes in research.md, not chat. Chat is for clarifying questions only.`, 72Remember: Your output goes in research.md, not chat. Chat is for clarifying questions only.`,
58 }, 73 },
59 74
@@ -65,18 +80,19 @@ Remember: Your output goes in research.md, not chat. Chat is for clarifying ques
65 systemPrompt: `You are in PLANNING mode. Your ONLY job is to create an implementation plan. 80 systemPrompt: `You are in PLANNING mode. Your ONLY job is to create an implementation plan.
66 81
67CRITICAL RULES: 82CRITICAL RULES:
681. You MUST write the plan to .claude-flow/plan.md — this is your PRIMARY output 831. You MUST write the plan to session plan.md — this is your PRIMARY output
692. DO NOT just respond in chat. The document viewer shows plan.md, so write there. 842. DO NOT just respond in chat. The document viewer shows plan.md, so write there.
703. DO NOT implement anything — no code changes to source files 853. DO NOT implement anything — no code changes to source files
714. DO NOT ask "should I start implementing?" or similar 864. DO NOT ask "should I start implementing?" or similar
725. The user controls phase transitions via UI buttons — never prompt them about it 875. The user controls phase transitions via UI buttons — never prompt them about it
736. Base your plan on .claude-flow/research.md 886. Base your plan on the session research.md and CLAUDE.md
74 89
75WORKFLOW: 90WORKFLOW:
761. Read .claude-flow/research.md to understand the codebase 911. Read CLAUDE.md for codebase overview
772. Write a detailed plan to .claude-flow/plan.md 922. Read the session research.md to understand the specific task
783. Include specific code snippets showing proposed changes 933. Write a detailed plan to session plan.md
794. Make the plan detailed enough that implementation is mechanical 944. Include specific code snippets showing proposed changes
955. Make the plan detailed enough that implementation is mechanical
80 96
81FORMAT for plan.md: 97FORMAT for plan.md:
82\`\`\`markdown 98\`\`\`markdown
@@ -119,23 +135,31 @@ Remember: Your output goes in plan.md, not chat. Chat is for clarifying question
119 permissionMode: "acceptEdits", 135 permissionMode: "acceptEdits",
120 tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"], 136 tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
121 initialMessage: 137 initialMessage:
122 "Starting implementation. I'll follow the plan exactly and mark tasks complete as I go.", 138 "Starting implementation. I'll follow the plan exactly, commit as I go, and mark tasks complete.",
123 systemPrompt: `You are in IMPLEMENTATION mode. Execute the approved plan. 139 systemPrompt: `You are in IMPLEMENTATION mode. Execute the approved plan.
124 140
125CRITICAL RULES: 141CRITICAL RULES:
1261. Read .claude-flow/plan.md and follow it exactly 1421. Read session plan.md and follow it exactly
1272. Mark tasks complete in plan.md as you finish them: - [ ] → - [x] 1432. Mark tasks complete in plan.md as you finish them: - [ ] → - [x]
1283. DO NOT deviate from the plan without asking 1443. DO NOT deviate from the plan without asking
1294. Run tests/typecheck if available 1454. Run tests/typecheck if available
1305. Stop and ask if you encounter issues not covered by the plan 1465. Make git commits as you complete logical chunks of work
1476. Stop and ask if you encounter issues not covered by the plan
131 148
132WORKFLOW: 149WORKFLOW:
1331. Read .claude-flow/plan.md 1501. Read session plan.md
1342. Execute each task in order 1512. Execute each task in order
1353. Update plan.md to mark tasks complete 1523. Update plan.md to mark tasks complete
1364. Continue until all tasks are done 1534. Make git commits with clear messages as you finish chunks
1545. Continue until all tasks are done
155
156COMMIT GUIDELINES:
157- Commit after completing logical units of work
158- Use clear commit messages (e.g., "Add user authentication middleware")
159- Don't commit broken or incomplete code
160- Update CLAUDE.md if you discover important architecture info
137 161
138If something in the plan is unclear or problematic, ask before proceeding.`, 162When complete, summarize what was done and any follow-up tasks.`,
139 }, 163 },
140}; 164};
141 165
diff --git a/src/main/git/worktree.ts b/src/main/git/worktree.ts
new file mode 100644
index 0000000..3264e5e
--- /dev/null
+++ b/src/main/git/worktree.ts
@@ -0,0 +1,179 @@
1import { execSync } from "node:child_process";
2import fs from "node:fs";
3import path from "node:path";
4
5export interface GitWorktreeInfo {
6 path: string;
7 branch: string;
8 commit?: string;
9}
10
11/**
12 * Check if a directory is a git repository
13 */
14export function isGitRepo(dir: string): boolean {
15 try {
16 execSync("git rev-parse --git-dir", { cwd: dir, stdio: "pipe" });
17 return true;
18 } catch {
19 return false;
20 }
21}
22
23/**
24 * Get the default branch name (main or master)
25 */
26export function getDefaultBranch(dir: string): string {
27 try {
28 const branches = execSync("git branch -rl '*/HEAD'", { cwd: dir, stdio: "pipe", encoding: "utf-8" });
29 const match = branches.match(/origin\/(main|master)/);
30 return match?.[1] || "main";
31 } catch {
32 return "main";
33 }
34}
35
36/**
37 * Create a new worktree for a session
38 * Returns the worktree path
39 */
40export function createWorktree(projectPath: string, sessionId: string, baseBranch?: string): string {
41 if (!isGitRepo(projectPath)) {
42 throw new Error("Not a git repository");
43 }
44
45 const branchName = `claude-flow/${sessionId}`;
46 const worktreePath = path.join(projectPath, ".claude-flow", "worktrees", sessionId);
47
48 // Ensure parent directory exists
49 fs.mkdirSync(path.dirname(worktreePath), { recursive: true });
50
51 // Create branch from base (or current HEAD)
52 const base = baseBranch || getDefaultBranch(projectPath);
53
54 try {
55 // Create worktree with new branch
56 execSync(
57 `git worktree add -b "${branchName}" "${worktreePath}" ${base}`,
58 { cwd: projectPath, stdio: "pipe" }
59 );
60 } catch (error) {
61 // If branch already exists, just add worktree pointing to it
62 try {
63 execSync(
64 `git worktree add "${worktreePath}" "${branchName}"`,
65 { cwd: projectPath, stdio: "pipe" }
66 );
67 } catch {
68 throw new Error(`Failed to create worktree: ${error}`);
69 }
70 }
71
72 return worktreePath;
73}
74
75/**
76 * Remove a worktree
77 */
78export function removeWorktree(projectPath: string, sessionId: string): void {
79 const worktreePath = path.join(projectPath, ".claude-flow", "worktrees", sessionId);
80 const branchName = `claude-flow/${sessionId}`;
81
82 try {
83 // Remove worktree
84 execSync(`git worktree remove "${worktreePath}" --force`, { cwd: projectPath, stdio: "pipe" });
85 } catch {
86 // Worktree might not exist, that's ok
87 }
88
89 try {
90 // Delete branch
91 execSync(`git branch -D "${branchName}"`, { cwd: projectPath, stdio: "pipe" });
92 } catch {
93 // Branch might not exist, that's ok
94 }
95}
96
97/**
98 * Get worktree info for a session
99 */
100export function getWorktreeInfo(projectPath: string, sessionId: string): GitWorktreeInfo | null {
101 const worktreePath = path.join(projectPath, ".claude-flow", "worktrees", sessionId);
102
103 if (!fs.existsSync(worktreePath)) {
104 return null;
105 }
106
107 try {
108 const branch = execSync("git rev-parse --abbrev-ref HEAD", {
109 cwd: worktreePath,
110 encoding: "utf-8"
111 }).trim();
112
113 const commit = execSync("git rev-parse --short HEAD", {
114 cwd: worktreePath,
115 encoding: "utf-8"
116 }).trim();
117
118 return { path: worktreePath, branch, commit };
119 } catch {
120 return null;
121 }
122}
123
124/**
125 * Commit changes in a worktree
126 */
127export function commitChanges(
128 worktreePath: string,
129 message: string,
130 files: string[] = ["."]
131): void {
132 try {
133 // Stage files
134 execSync(`git add ${files.join(" ")}`, { cwd: worktreePath, stdio: "pipe" });
135
136 // Commit
137 execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { cwd: worktreePath, stdio: "pipe" });
138 } catch (error) {
139 throw new Error(`Failed to commit: ${error}`);
140 }
141}
142
143/**
144 * Check if there are uncommitted changes
145 */
146export function hasUncommittedChanges(worktreePath: string): boolean {
147 try {
148 const status = execSync("git status --porcelain", { cwd: worktreePath, encoding: "utf-8" });
149 return status.trim().length > 0;
150 } catch {
151 return false;
152 }
153}
154
155/**
156 * Get diff summary of uncommitted changes
157 */
158export function getDiffSummary(worktreePath: string): string {
159 try {
160 return execSync("git diff --stat", { cwd: worktreePath, encoding: "utf-8" });
161 } catch {
162 return "";
163 }
164}
165
166/**
167 * Get the main project path from a worktree path
168 */
169export function getMainRepoPath(worktreePath: string): string {
170 try {
171 const gitDir = execSync("git rev-parse --git-common-dir", {
172 cwd: worktreePath,
173 encoding: "utf-8"
174 }).trim();
175 return path.dirname(gitDir);
176 } catch {
177 return worktreePath;
178 }
179}
diff --git a/src/main/ipc/handlers.ts b/src/main/ipc/handlers.ts
index ce95f4c..bc0d024 100644
--- a/src/main/ipc/handlers.ts
+++ b/src/main/ipc/handlers.ts
@@ -3,6 +3,7 @@ import * as projects from "../db/projects";
3import * as sessions from "../db/sessions"; 3import * as sessions from "../db/sessions";
4import * as claude from "../claude"; 4import * as claude from "../claude";
5import type { UserPermissionMode } from "../claude/phases"; 5import type { UserPermissionMode } from "../claude/phases";
6import * as git from "../git/worktree";
6 7
7export function registerIpcHandlers(mainWindow: BrowserWindow): void { 8export function registerIpcHandlers(mainWindow: BrowserWindow): void {
8 // Projects 9 // Projects
@@ -18,12 +19,42 @@ export function registerIpcHandlers(mainWindow: BrowserWindow): void {
18 ipcMain.handle("sessions:list", (_, projectId: string) => 19 ipcMain.handle("sessions:list", (_, projectId: string) =>
19 sessions.listSessions(projectId) 20 sessions.listSessions(projectId)
20 ); 21 );
21 ipcMain.handle("sessions:create", (_, projectId: string, name: string) => 22
22 sessions.createSession(projectId, name) 23 ipcMain.handle("sessions:create", async (_, projectId: string, name: string) => {
23 ); 24 const session = sessions.createSession(projectId, name);
24 ipcMain.handle("sessions:delete", (_, id: string) => 25 const project = projects.getProject(projectId);
25 sessions.deleteSession(id) 26
26 ); 27 if (project && git.isGitRepo(project.path)) {
28 try {
29 // Create git worktree for this session
30 git.createWorktree(project.path, session.id);
31 } catch (error) {
32 console.error("Failed to create worktree:", error);
33 // Continue without worktree - not fatal
34 }
35 }
36
37 return session;
38 });
39
40 ipcMain.handle("sessions:delete", (_, id: string) => {
41 const session = sessions.getSession(id);
42 if (session) {
43 const project = projects.getProject(session.project_id);
44 if (project) {
45 // Clean up worktree if exists
46 try {
47 git.removeWorktree(project.path, id);
48 } catch {
49 // Worktree might not exist, that's ok
50 }
51 // Clean up session artifacts
52 claude.clearSessionArtifacts(project.path, id);
53 }
54 }
55 sessions.deleteSession(id);
56 });
57
27 ipcMain.handle("sessions:get", (_, id: string) => sessions.getSession(id)); 58 ipcMain.handle("sessions:get", (_, id: string) => sessions.getSession(id));
28 59
29 // Messages 60 // Messages
@@ -92,7 +123,31 @@ export function registerIpcHandlers(mainWindow: BrowserWindow): void {
92 } 123 }
93 ); 124 );
94 125
95 // Artifacts 126 // Session Artifacts (new session-specific API)
127 ipcMain.handle(
128 "artifact:readSession",
129 (_, projectPath: string, sessionId: string, filename: string) => {
130 return claude.readSessionArtifact(projectPath, sessionId, filename);
131 }
132 );
133
134 ipcMain.handle(
135 "artifact:writeSession",
136 (_, projectPath: string, sessionId: string, filename: string, content: string) => {
137 claude.writeSessionArtifact(projectPath, sessionId, filename, content);
138 }
139 );
140
141 // CLAUDE.md
142 ipcMain.handle("claudemd:read", (_, projectPath: string) => {
143 return claude.readClaudeMd(projectPath);
144 });
145
146 ipcMain.handle("claudemd:write", (_, projectPath: string, content: string) => {
147 claude.writeClaudeMd(projectPath, content);
148 });
149
150 // Legacy artifact API (for backward compatibility)
96 ipcMain.handle( 151 ipcMain.handle(
97 "artifact:read", 152 "artifact:read",
98 (_, projectPath: string, filename: string) => { 153 (_, projectPath: string, filename: string) => {
@@ -107,6 +162,23 @@ export function registerIpcHandlers(mainWindow: BrowserWindow): void {
107 } 162 }
108 ); 163 );
109 164
165 // Git
166 ipcMain.handle("git:isRepo", (_, projectPath: string) => {
167 return git.isGitRepo(projectPath);
168 });
169
170 ipcMain.handle("git:worktreeInfo", (_, projectPath: string, sessionId: string) => {
171 return git.getWorktreeInfo(projectPath, sessionId);
172 });
173
174 ipcMain.handle("git:commit", (_, worktreePath: string, message: string, files?: string[]) => {
175 git.commitChanges(worktreePath, message, files);
176 });
177
178 ipcMain.handle("git:hasChanges", (_, worktreePath: string) => {
179 return git.hasUncommittedChanges(worktreePath);
180 });
181
110 // Dialogs 182 // Dialogs
111 ipcMain.handle("dialog:selectDirectory", async () => { 183 ipcMain.handle("dialog:selectDirectory", async () => {
112 const result = await dialog.showOpenDialog(mainWindow, { 184 const result = await dialog.showOpenDialog(mainWindow, {
diff --git a/src/main/preload.ts b/src/main/preload.ts
index b3e3f8b..1747763 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -3,6 +3,7 @@ import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
3import type { Project } from "./db/projects"; 3import type { Project } from "./db/projects";
4import type { Session, Message } from "./db/sessions"; 4import type { Session, Message } from "./db/sessions";
5import type { Phase, UserPermissionMode } from "./claude/phases"; 5import type { Phase, UserPermissionMode } from "./claude/phases";
6import type { GitWorktreeInfo } from "./git/worktree";
6 7
7export interface ClaudeFlowAPI { 8export interface ClaudeFlowAPI {
8 // Projects 9 // Projects
@@ -31,7 +32,24 @@ export interface ClaudeFlowAPI {
31 mode: UserPermissionMode 32 mode: UserPermissionMode
32 ) => Promise<void>; 33 ) => Promise<void>;
33 34
34 // Artifacts 35 // Session Artifacts (new session-specific)
36 readSessionArtifact: (
37 projectPath: string,
38 sessionId: string,
39 filename: string
40 ) => Promise<string | null>;
41 writeSessionArtifact: (
42 projectPath: string,
43 sessionId: string,
44 filename: string,
45 content: string
46 ) => Promise<void>;
47
48 // CLAUDE.md
49 readClaudeMd: (projectPath: string) => Promise<string | null>;
50 writeClaudeMd: (projectPath: string, content: string) => Promise<void>;
51
52 // Legacy Artifacts (backward compat)
35 readArtifact: ( 53 readArtifact: (
36 projectPath: string, 54 projectPath: string,
37 filename: string 55 filename: string
@@ -42,6 +60,12 @@ export interface ClaudeFlowAPI {
42 content: string 60 content: string
43 ) => Promise<void>; 61 ) => Promise<void>;
44 62
63 // Git
64 isGitRepo: (projectPath: string) => Promise<boolean>;
65 getWorktreeInfo: (projectPath: string, sessionId: string) => Promise<GitWorktreeInfo | null>;
66 commitChanges: (worktreePath: string, message: string, files?: string[]) => Promise<void>;
67 hasUncommittedChanges: (worktreePath: string) => Promise<boolean>;
68
45 // Events 69 // Events
46 onClaudeMessage: ( 70 onClaudeMessage: (
47 callback: (sessionId: string, message: SDKMessage) => void 71 callback: (sessionId: string, message: SDKMessage) => void
@@ -80,12 +104,32 @@ const api: ClaudeFlowAPI = {
80 setPermissionMode: (sessionId, mode) => 104 setPermissionMode: (sessionId, mode) =>
81 ipcRenderer.invoke("workflow:setPermissionMode", sessionId, mode), 105 ipcRenderer.invoke("workflow:setPermissionMode", sessionId, mode),
82 106
83 // Artifacts 107 // Session Artifacts
108 readSessionArtifact: (projectPath, sessionId, filename) =>
109 ipcRenderer.invoke("artifact:readSession", projectPath, sessionId, filename),
110 writeSessionArtifact: (projectPath, sessionId, filename, content) =>
111 ipcRenderer.invoke("artifact:writeSession", projectPath, sessionId, filename, content),
112
113 // CLAUDE.md
114 readClaudeMd: (projectPath) => ipcRenderer.invoke("claudemd:read", projectPath),
115 writeClaudeMd: (projectPath, content) =>
116 ipcRenderer.invoke("claudemd:write", projectPath, content),
117
118 // Legacy Artifacts
84 readArtifact: (projectPath, filename) => 119 readArtifact: (projectPath, filename) =>
85 ipcRenderer.invoke("artifact:read", projectPath, filename), 120 ipcRenderer.invoke("artifact:read", projectPath, filename),
86 writeArtifact: (projectPath, filename, content) => 121 writeArtifact: (projectPath, filename, content) =>
87 ipcRenderer.invoke("artifact:write", projectPath, filename, content), 122 ipcRenderer.invoke("artifact:write", projectPath, filename, content),
88 123
124 // Git
125 isGitRepo: (projectPath) => ipcRenderer.invoke("git:isRepo", projectPath),
126 getWorktreeInfo: (projectPath, sessionId) =>
127 ipcRenderer.invoke("git:worktreeInfo", projectPath, sessionId),
128 commitChanges: (worktreePath, message, files) =>
129 ipcRenderer.invoke("git:commit", worktreePath, message, files),
130 hasUncommittedChanges: (worktreePath) =>
131 ipcRenderer.invoke("git:hasChanges", worktreePath),
132
89 // Events 133 // Events
90 onClaudeMessage: (callback) => { 134 onClaudeMessage: (callback) => {
91 const handler = ( 135 const handler = (