aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/claude/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/claude/index.ts')
-rw-r--r--src/main/claude/index.ts142
1 files changed, 142 insertions, 0 deletions
diff --git a/src/main/claude/index.ts b/src/main/claude/index.ts
new file mode 100644
index 0000000..34a914e
--- /dev/null
+++ b/src/main/claude/index.ts
@@ -0,0 +1,142 @@
1import { query, type SDKMessage, type Query } from "@anthropic-ai/claude-agent-sdk";
2import type { Session } from "../db/sessions";
3import { getPhaseConfig, getNextPhase, getArtifactFilename } from "./phases";
4import type { Phase, UserPermissionMode } from "./phases";
5import { getProject } from "../db/projects";
6import { updateSession } from "../db/sessions";
7import fs from "node:fs";
8import path from "node:path";
9
10// Track active queries by session ID
11const activeQueries = new Map<string, Query>();
12
13function ensureArtifactDir(projectPath: string): void {
14 const dir = path.join(projectPath, ".claude-flow");
15 if (!fs.existsSync(dir)) {
16 fs.mkdirSync(dir, { recursive: true });
17 }
18}
19
20export interface SendMessageOptions {
21 session: Session;
22 message: string;
23 onMessage: (msg: SDKMessage) => void;
24}
25
26export async function sendMessage({
27 session,
28 message,
29 onMessage,
30}: SendMessageOptions): Promise<void> {
31 const project = getProject(session.project_id);
32 if (!project) throw new Error("Project not found");
33
34 ensureArtifactDir(project.path);
35
36 const phaseConfig = getPhaseConfig(
37 session.phase as Phase,
38 session.permission_mode as UserPermissionMode
39 );
40
41 const q = query({
42 prompt: message,
43 options: {
44 cwd: project.path,
45 resume: session.claude_session_id ?? undefined,
46 tools: phaseConfig.tools,
47 permissionMode: phaseConfig.permissionMode,
48 // Add system prompt via extraArgs since there's no direct option
49 extraArgs: {
50 "system-prompt": phaseConfig.systemPrompt,
51 },
52 },
53 });
54
55 activeQueries.set(session.id, q);
56
57 try {
58 for await (const msg of q) {
59 // Capture session ID from init message
60 if (msg.type === "system" && msg.subtype === "init") {
61 if (!session.claude_session_id) {
62 updateSession(session.id, { claude_session_id: msg.session_id });
63 }
64 }
65 onMessage(msg);
66 }
67 } finally {
68 activeQueries.delete(session.id);
69 }
70}
71
72export function interruptSession(sessionId: string): void {
73 const q = activeQueries.get(sessionId);
74 if (q) {
75 q.close();
76 activeQueries.delete(sessionId);
77 }
78}
79
80/**
81 * Trigger a review: Claude reads the document and addresses user annotations
82 */
83export async function triggerReview(
84 session: Session,
85 onMessage: (msg: SDKMessage) => void
86): Promise<void> {
87 const docName = getArtifactFilename(session.phase as Phase);
88 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.`;
89
90 await sendMessage({ session, message, onMessage });
91}
92
93/**
94 * Advance to the next phase
95 */
96export function advancePhase(session: Session): Phase | null {
97 const nextPhase = getNextPhase(session.phase as Phase);
98 if (nextPhase) {
99 updateSession(session.id, { phase: nextPhase });
100 }
101 return nextPhase;
102}
103
104/**
105 * Read an artifact file from the project's .claude-flow directory
106 */
107export function readArtifact(
108 projectPath: string,
109 filename: string
110): string | null {
111 const filePath = path.join(projectPath, ".claude-flow", filename);
112 if (fs.existsSync(filePath)) {
113 return fs.readFileSync(filePath, "utf-8");
114 }
115 return null;
116}
117
118/**
119 * Write an artifact file to the project's .claude-flow directory
120 */
121export function writeArtifact(
122 projectPath: string,
123 filename: string,
124 content: string
125): void {
126 const dir = path.join(projectPath, ".claude-flow");
127 if (!fs.existsSync(dir)) {
128 fs.mkdirSync(dir, { recursive: true });
129 }
130 fs.writeFileSync(path.join(dir, filename), content, "utf-8");
131}
132
133/**
134 * Get the initial message for a phase
135 */
136export function getPhaseInitialMessage(phase: Phase): string {
137 return getPhaseConfig(phase).initialMessage;
138}
139
140// Re-export types
141export type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
142export type { Phase, UserPermissionMode } from "./phases";