aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/claude
diff options
context:
space:
mode:
authorClawd <ai@clawd.bot>2026-02-28 07:26:43 -0800
committerClawd <ai@clawd.bot>2026-02-28 07:26:43 -0800
commit332e5cec2992fefb302251962a3ceca38437a110 (patch)
tree7802469abb929e741ccbecd989828f1baf37145f /src/main/claude
parentd2d6fd48d97b876c9a9a424c9d2cc1de5352b144 (diff)
Phase 2: Claude integration layer
- Add @anthropic-ai/claude-agent-sdk dependency - Implement src/main/claude/phases.ts with phase configs (research/plan/implement) - Implement src/main/claude/index.ts with SDK wrapper - query() integration with session management - Session resume support - Artifact read/write utilities - Phase advancement logic
Diffstat (limited to 'src/main/claude')
-rw-r--r--src/main/claude/index.ts142
-rw-r--r--src/main/claude/phases.ts104
2 files changed, 246 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";
diff --git a/src/main/claude/phases.ts b/src/main/claude/phases.ts
new file mode 100644
index 0000000..d503f3a
--- /dev/null
+++ b/src/main/claude/phases.ts
@@ -0,0 +1,104 @@
1import type { PermissionMode } from "@anthropic-ai/claude-agent-sdk";
2
3export type Phase = "research" | "plan" | "implement";
4export type UserPermissionMode = "acceptEdits" | "bypassPermissions";
5
6export interface PhaseConfig {
7 systemPrompt: string;
8 tools: string[];
9 permissionMode: PermissionMode;
10 initialMessage: string;
11}
12
13export const phaseConfigs: Record<Phase, PhaseConfig> = {
14 research: {
15 permissionMode: "plan",
16 tools: ["Read", "Glob", "Grep", "Bash", "Write"],
17 initialMessage:
18 "What areas of the codebase should I research? What are you trying to build?",
19 systemPrompt: `You are in RESEARCH mode.
20
21Your job is to deeply understand the codebase before any changes are made.
22
23When the user tells you what to research:
241. Read files thoroughly — understand all intricacies
252. Write your findings to .claude-flow/research.md
263. Format it as clear, readable markdown
27
28Rules:
29- DO NOT make any code changes
30- DO NOT modify any files except .claude-flow/research.md
31- Be thorough — surface-level reading is not acceptable
32
33When the user clicks "Review", read .claude-flow/research.md for their annotations and update accordingly.
34When the user clicks "Submit", they're ready to move to planning.`,
35 },
36
37 plan: {
38 permissionMode: "plan",
39 tools: ["Read", "Glob", "Grep", "Write"],
40 initialMessage:
41 "I'll create a detailed implementation plan based on my research. Give me a moment...",
42 systemPrompt: `You are in PLANNING mode.
43
44Based on the research in .claude-flow/research.md, write a detailed implementation plan.
45
46Write the plan to .claude-flow/plan.md with:
47- Detailed explanation of the approach
48- Specific code snippets showing proposed changes
49- File paths that will be modified
50- Trade-offs and considerations
51- A granular TODO list with checkboxes
52
53Rules:
54- DO NOT implement anything
55- DO NOT modify any source files
56- Only write to .claude-flow/plan.md
57
58The plan should be detailed enough that implementation becomes mechanical.
59
60When the user clicks "Review", read .claude-flow/plan.md for their annotations and update accordingly.
61When the user clicks "Submit", begin implementation.`,
62 },
63
64 implement: {
65 permissionMode: "acceptEdits",
66 tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
67 initialMessage:
68 "Starting implementation. I'll follow the plan exactly and mark tasks complete as I go.",
69 systemPrompt: `You are in IMPLEMENTATION mode. The plan has been approved.
70
71Read .claude-flow/plan.md and execute it:
72- Follow the plan exactly
73- Mark tasks complete (- [x]) as you finish them
74- Run typecheck/lint continuously if available
75- Do not add unnecessary comments
76- Do not stop until all tasks are complete
77
78If you encounter issues not covered by the plan, stop and ask.`,
79 },
80};
81
82export function getPhaseConfig(
83 phase: Phase,
84 userPermissionMode?: UserPermissionMode
85): PhaseConfig {
86 const config = { ...phaseConfigs[phase] };
87 if (phase === "implement" && userPermissionMode) {
88 config.permissionMode = userPermissionMode;
89 }
90 return config;
91}
92
93export function getNextPhase(phase: Phase): Phase | null {
94 const transitions: Record<Phase, Phase | null> = {
95 research: "plan",
96 plan: "implement",
97 implement: null,
98 };
99 return transitions[phase];
100}
101
102export function getArtifactFilename(phase: Phase): string {
103 return phase === "research" ? "research.md" : "plan.md";
104}