@locusai/sdk 0.4.5 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/index-node.js +1590 -20
  2. package/dist/index.js +429 -121
  3. package/dist/orchestrator.d.ts.map +1 -1
  4. package/package.json +12 -23
  5. package/dist/agent/artifact-syncer.js +0 -77
  6. package/dist/agent/codebase-indexer-service.js +0 -55
  7. package/dist/agent/index.js +0 -5
  8. package/dist/agent/sprint-planner.js +0 -68
  9. package/dist/agent/task-executor.js +0 -60
  10. package/dist/agent/worker.js +0 -252
  11. package/dist/ai/anthropic-client.js +0 -70
  12. package/dist/ai/claude-runner.js +0 -71
  13. package/dist/ai/index.js +0 -2
  14. package/dist/core/config.js +0 -15
  15. package/dist/core/index.js +0 -3
  16. package/dist/core/indexer.js +0 -113
  17. package/dist/core/prompt-builder.js +0 -83
  18. package/dist/events.js +0 -15
  19. package/dist/modules/auth.js +0 -23
  20. package/dist/modules/base.js +0 -8
  21. package/dist/modules/ci.js +0 -7
  22. package/dist/modules/docs.js +0 -38
  23. package/dist/modules/invitations.js +0 -22
  24. package/dist/modules/organizations.js +0 -39
  25. package/dist/modules/sprints.js +0 -34
  26. package/dist/modules/tasks.js +0 -56
  27. package/dist/modules/workspaces.js +0 -49
  28. package/dist/orchestrator.js +0 -356
  29. package/dist/utils/colors.js +0 -54
  30. package/dist/utils/retry.js +0 -37
  31. package/src/agent/artifact-syncer.ts +0 -111
  32. package/src/agent/codebase-indexer-service.ts +0 -71
  33. package/src/agent/index.ts +0 -5
  34. package/src/agent/sprint-planner.ts +0 -86
  35. package/src/agent/task-executor.ts +0 -85
  36. package/src/agent/worker.ts +0 -322
  37. package/src/ai/anthropic-client.ts +0 -93
  38. package/src/ai/claude-runner.ts +0 -86
  39. package/src/ai/index.ts +0 -2
  40. package/src/core/config.ts +0 -21
  41. package/src/core/index.ts +0 -3
  42. package/src/core/indexer.ts +0 -131
  43. package/src/core/prompt-builder.ts +0 -91
  44. package/src/events.ts +0 -35
  45. package/src/index-node.ts +0 -23
  46. package/src/index.ts +0 -159
  47. package/src/modules/auth.ts +0 -48
  48. package/src/modules/base.ts +0 -9
  49. package/src/modules/ci.ts +0 -12
  50. package/src/modules/docs.ts +0 -84
  51. package/src/modules/invitations.ts +0 -45
  52. package/src/modules/organizations.ts +0 -90
  53. package/src/modules/sprints.ts +0 -69
  54. package/src/modules/tasks.ts +0 -110
  55. package/src/modules/workspaces.ts +0 -94
  56. package/src/orchestrator.ts +0 -473
  57. package/src/utils/colors.ts +0 -63
  58. package/src/utils/retry.ts +0 -56
@@ -1,86 +0,0 @@
1
- import type { Sprint, Task } from "@locusai/shared";
2
- import type { AnthropicClient } from "../ai/anthropic-client.js";
3
- import type { ClaudeRunner } from "../ai/claude-runner.js";
4
-
5
- export interface SprintPlannerDeps {
6
- anthropicClient: AnthropicClient | null;
7
- claudeRunner: ClaudeRunner;
8
- log: (message: string, level?: "info" | "success" | "warn" | "error") => void;
9
- }
10
-
11
- /**
12
- * Handles sprint planning and mindmap generation
13
- */
14
- export class SprintPlanner {
15
- constructor(private deps: SprintPlannerDeps) {}
16
-
17
- async planSprint(sprint: Sprint, tasks: Task[]): Promise<string> {
18
- this.deps.log(`Planning sprint: ${sprint.name}`, "info");
19
-
20
- try {
21
- const taskList = tasks
22
- .map(
23
- (t) => `- [${t.id}] ${t.title}: ${t.description || "No description"}`
24
- )
25
- .join("\n");
26
-
27
- let plan: string;
28
-
29
- if (this.deps.anthropicClient) {
30
- // Use Anthropic SDK with caching for faster planning
31
- const systemPrompt = `You are an expert project manager and lead engineer specialized in sprint planning and task prioritization.`;
32
-
33
- const userPrompt = `# Sprint Planning: ${sprint.name}
34
-
35
- ## Tasks
36
- ${taskList}
37
-
38
- ## Instructions
39
- 1. Analyze dependencies between these tasks.
40
- 2. Prioritize them for the most efficient execution.
41
- 3. Create a mindmap (in Markdown or Mermaid format) that visualizes the sprint structure.
42
- 4. Output your final plan. The plan should clearly state the order of execution.
43
-
44
- **IMPORTANT**:
45
- - Do NOT create any files on the filesystem during this planning phase.
46
- - Avoid using absolute local paths (e.g., /Users/...) in your output. Use relative paths starting from the project root if necessary.
47
- - Your output will be saved as the official sprint mindmap on the server.`;
48
-
49
- plan = await this.deps.anthropicClient.run({
50
- systemPrompt,
51
- userPrompt,
52
- });
53
- } else {
54
- // Fallback to Claude CLI
55
- const planningPrompt = `# Sprint Planning: ${sprint.name}
56
-
57
- You are an expert project manager and lead engineer. You need to create a mindmap and execution plan for the following tasks in this sprint.
58
-
59
- ## Tasks
60
- ${taskList}
61
-
62
- ## Instructions
63
- 1. Analyze dependencies between these tasks.
64
- 2. Prioritize them for the most efficient execution.
65
- 3. Create a mindmap (in Markdown or Mermaid format) that visualizes the sprint structure.
66
- 4. Output your final plan. The plan should clearly state the order of execution.
67
-
68
- **IMPORTANT**:
69
- - Do NOT create any files on the filesystem during this planning phase.
70
- - Avoid using absolute local paths (e.g., /Users/...) in your output. Use relative paths starting from the project root if necessary.
71
- - Your output will be saved as the official sprint mindmap on the server.`;
72
-
73
- plan = await this.deps.claudeRunner.run(planningPrompt, true);
74
- }
75
-
76
- this.deps.log(
77
- "Sprint mindmap generated and posted to server.",
78
- "success"
79
- );
80
- return plan;
81
- } catch (error) {
82
- this.deps.log(`Sprint planning failed: ${error}`, "error");
83
- return sprint.mindmap || "";
84
- }
85
- }
86
- }
@@ -1,85 +0,0 @@
1
- import type { Task } from "@locusai/shared";
2
- import type { AnthropicClient } from "../ai/anthropic-client.js";
3
- import type { ClaudeRunner } from "../ai/claude-runner.js";
4
- import { PromptBuilder } from "../core/prompt-builder.js";
5
-
6
- export interface TaskExecutorDeps {
7
- anthropicClient: AnthropicClient | null;
8
- claudeRunner: ClaudeRunner;
9
- projectPath: string;
10
- sprintPlan: string | null;
11
- log: (message: string, level?: "info" | "success" | "warn" | "error") => void;
12
- }
13
-
14
- /**
15
- * Handles task execution with two-phase approach (planning + execution)
16
- */
17
- export class TaskExecutor {
18
- private promptBuilder: PromptBuilder;
19
-
20
- constructor(private deps: TaskExecutorDeps) {
21
- this.promptBuilder = new PromptBuilder(deps.projectPath);
22
- }
23
-
24
- updateSprintPlan(sprintPlan: string | null) {
25
- this.deps.sprintPlan = sprintPlan;
26
- }
27
-
28
- async execute(task: Task): Promise<{ success: boolean; summary: string }> {
29
- this.deps.log(`Executing: ${task.title}`, "info");
30
- let basePrompt = await this.promptBuilder.build(task);
31
-
32
- if (this.deps.sprintPlan) {
33
- basePrompt = `## Sprint Context\n${this.deps.sprintPlan}\n\n${basePrompt}`;
34
- }
35
-
36
- try {
37
- let plan: string = "";
38
-
39
- if (this.deps.anthropicClient) {
40
- // Phase 1: Planning (using Anthropic SDK with caching)
41
- this.deps.log("Phase 1: Planning (Anthropic SDK)...", "info");
42
-
43
- // Build cacheable context blocks
44
- const cacheableContext: string[] = [basePrompt];
45
-
46
- plan = await this.deps.anthropicClient.run({
47
- systemPrompt:
48
- "You are an expert software engineer. Analyze the task carefully and create a detailed implementation plan.",
49
- cacheableContext,
50
- userPrompt:
51
- "## Phase 1: Planning\nAnalyze and create a detailed plan for THIS SPECIFIC TASK. Do NOT execute changes yet.",
52
- });
53
- } else {
54
- this.deps.log(
55
- "Skipping Phase 1: Planning (No Anthropic API Key)...",
56
- "info"
57
- );
58
- }
59
-
60
- // Phase 2: Execution (always using Claude CLI for agentic tools)
61
- this.deps.log("Starting Execution...", "info");
62
-
63
- let executionPrompt = basePrompt;
64
- if (plan) {
65
- executionPrompt += `\n\n## Phase 2: Execution\nBased on the plan, execute the task:\n\n${plan}`;
66
- } else {
67
- executionPrompt += `\n\n## Execution\nExecute the task directly.`;
68
- }
69
-
70
- executionPrompt += `\n\nWhen finished, output: <promise>COMPLETE</promise>`;
71
- const output = await this.deps.claudeRunner.run(executionPrompt);
72
-
73
- const success = output.includes("<promise>COMPLETE</promise>");
74
-
75
- return {
76
- success,
77
- summary: success
78
- ? "Task completed by Claude"
79
- : "Claude did not signal completion",
80
- };
81
- } catch (error) {
82
- return { success: false, summary: `Error: ${error}` };
83
- }
84
- }
85
- }
@@ -1,322 +0,0 @@
1
- import type { Sprint, Task, TaskStatus } from "@locusai/shared";
2
- import { AnthropicClient } from "../ai/anthropic-client.js";
3
- import { ClaudeRunner } from "../ai/claude-runner.js";
4
- import { LocusClient } from "../index.js";
5
- import { c } from "../utils/colors.js";
6
- import { ArtifactSyncer } from "./artifact-syncer.js";
7
- import { CodebaseIndexerService } from "./codebase-indexer-service.js";
8
- import { SprintPlanner } from "./sprint-planner.js";
9
- import { TaskExecutor } from "./task-executor.js";
10
-
11
- export interface WorkerConfig {
12
- agentId: string;
13
- workspaceId: string;
14
- sprintId?: string;
15
- apiBase: string;
16
- projectPath: string;
17
- apiKey: string;
18
- anthropicApiKey?: string;
19
- model?: string;
20
- }
21
-
22
- /**
23
- * Main agent worker that orchestrates task execution
24
- * Delegates responsibilities to specialized services
25
- */
26
- export class AgentWorker {
27
- private client: LocusClient;
28
- private claudeRunner: ClaudeRunner;
29
- private anthropicClient: AnthropicClient | null;
30
-
31
- // Services
32
- private sprintPlanner: SprintPlanner;
33
- private indexerService: CodebaseIndexerService;
34
- private artifactSyncer: ArtifactSyncer;
35
- private taskExecutor: TaskExecutor;
36
-
37
- // State
38
- private consecutiveEmpty = 0;
39
- private maxEmpty = 10;
40
- private maxTasks = 50;
41
- private tasksCompleted = 0;
42
- private pollInterval = 10_000;
43
- private sprintPlan: string | null = null;
44
-
45
- constructor(private config: WorkerConfig) {
46
- const projectPath = config.projectPath || process.cwd();
47
-
48
- // Initialize API client
49
- this.client = new LocusClient({
50
- baseUrl: config.apiBase,
51
- token: config.apiKey,
52
- retryOptions: {
53
- maxRetries: 3,
54
- initialDelay: 1000,
55
- maxDelay: 5000,
56
- factor: 2,
57
- },
58
- });
59
-
60
- // Initialize AI clients
61
- this.claudeRunner = new ClaudeRunner(projectPath, config.model);
62
- this.anthropicClient = config.anthropicApiKey
63
- ? new AnthropicClient({
64
- apiKey: config.anthropicApiKey,
65
- model: config.model,
66
- })
67
- : null;
68
-
69
- // Initialize services with dependencies
70
- const logFn = this.log.bind(this);
71
-
72
- this.sprintPlanner = new SprintPlanner({
73
- anthropicClient: this.anthropicClient,
74
- claudeRunner: this.claudeRunner,
75
- log: logFn,
76
- });
77
-
78
- this.indexerService = new CodebaseIndexerService({
79
- anthropicClient: this.anthropicClient,
80
- claudeRunner: this.claudeRunner,
81
- projectPath,
82
- log: logFn,
83
- });
84
-
85
- this.artifactSyncer = new ArtifactSyncer({
86
- client: this.client,
87
- workspaceId: config.workspaceId,
88
- projectPath,
89
- log: logFn,
90
- });
91
-
92
- this.taskExecutor = new TaskExecutor({
93
- anthropicClient: this.anthropicClient,
94
- claudeRunner: this.claudeRunner,
95
- projectPath,
96
- sprintPlan: null, // Will be set after sprint planning
97
- log: logFn,
98
- });
99
-
100
- // Log initialization
101
- if (this.anthropicClient) {
102
- this.log(
103
- "Using Anthropic SDK with prompt caching for planning phases",
104
- "info"
105
- );
106
- } else {
107
- this.log(
108
- "Using Claude CLI for all phases (no Anthropic API key provided)",
109
- "info"
110
- );
111
- }
112
- }
113
-
114
- log(message: string, level: "info" | "success" | "warn" | "error" = "info") {
115
- const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
116
- const colorFn = {
117
- info: c.cyan,
118
- success: c.green,
119
- warn: c.yellow,
120
- error: c.red,
121
- }[level];
122
- const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
123
-
124
- console.log(
125
- `${c.dim(`[${timestamp}]`)} ${c.bold(`[${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`
126
- );
127
- }
128
-
129
- private async getActiveSprint(): Promise<Sprint | null> {
130
- try {
131
- if (this.config.sprintId) {
132
- return await this.client.sprints.getById(
133
- this.config.sprintId,
134
- this.config.workspaceId
135
- );
136
- }
137
- return await this.client.sprints.getActive(this.config.workspaceId);
138
- } catch (_error) {
139
- return null;
140
- }
141
- }
142
-
143
- private async getNextTask(): Promise<Task | null> {
144
- try {
145
- const task = await this.client.workspaces.dispatch(
146
- this.config.workspaceId,
147
- this.config.agentId,
148
- this.config.sprintId
149
- );
150
- return task;
151
- } catch (error) {
152
- this.log(
153
- `No task dispatched: ${error instanceof Error ? error.message : String(error)}`,
154
- "info"
155
- );
156
- return null;
157
- }
158
- }
159
-
160
- private async executeTask(
161
- task: Task
162
- ): Promise<{ success: boolean; summary: string }> {
163
- // Fetch full task details to get comments/feedback
164
- const fullTask = await this.client.tasks.getById(
165
- task.id,
166
- this.config.workspaceId
167
- );
168
-
169
- // Update task executor with current sprint plan
170
- this.taskExecutor.updateSprintPlan(this.sprintPlan);
171
-
172
- // Execute the task
173
- const result = await this.taskExecutor.execute(fullTask);
174
-
175
- // Reindex codebase after execution to ensure fresh context
176
- await this.indexerService.reindex();
177
-
178
- return result;
179
- }
180
-
181
- async run(): Promise<void> {
182
- this.log(
183
- `Agent started in ${this.config.projectPath || process.cwd()}`,
184
- "success"
185
- );
186
-
187
- // Initial Sprint Planning Phase
188
- const sprint = await this.getActiveSprint();
189
- if (sprint) {
190
- this.log(`Found active sprint: ${sprint.name} (${sprint.id})`, "info");
191
- const tasks = await this.client.tasks.list(this.config.workspaceId, {
192
- sprintId: sprint.id,
193
- });
194
-
195
- this.log(`Sprint tasks found: ${tasks.length}`, "info");
196
-
197
- const latestTaskCreation = tasks.reduce((latest, task) => {
198
- const taskDate = new Date(task.createdAt);
199
- return taskDate > latest ? taskDate : latest;
200
- }, new Date(0));
201
-
202
- const mindmapDate = sprint.mindmapUpdatedAt
203
- ? new Date(sprint.mindmapUpdatedAt)
204
- : new Date(0);
205
-
206
- // Skip mindmap generation if there's only one task
207
- if (tasks.length <= 1) {
208
- this.log(
209
- "Skipping mindmap generation (only one task in sprint).",
210
- "info"
211
- );
212
- this.sprintPlan = null;
213
- } else {
214
- const needsPlanning =
215
- !sprint.mindmap ||
216
- sprint.mindmap.trim() === "" ||
217
- latestTaskCreation > mindmapDate;
218
-
219
- if (needsPlanning) {
220
- if (sprint.mindmap && latestTaskCreation > mindmapDate) {
221
- this.log(
222
- "New tasks have been added to the sprint since last mindmap. Regenerating...",
223
- "warn"
224
- );
225
- }
226
- this.sprintPlan = await this.sprintPlanner.planSprint(sprint, tasks);
227
-
228
- // Save mindmap to server
229
- await this.client.sprints.update(sprint.id, this.config.workspaceId, {
230
- mindmap: this.sprintPlan,
231
- mindmapUpdatedAt: new Date(),
232
- });
233
- } else {
234
- this.log("Using existing sprint mindmap.", "info");
235
- this.sprintPlan = sprint.mindmap ?? null;
236
- }
237
- }
238
- } else {
239
- this.log("No active sprint found for planning.", "warn");
240
- }
241
-
242
- // Main task execution loop
243
- while (
244
- this.tasksCompleted < this.maxTasks &&
245
- this.consecutiveEmpty < this.maxEmpty
246
- ) {
247
- const task = await this.getNextTask();
248
- if (!task) {
249
- this.consecutiveEmpty++;
250
- if (this.consecutiveEmpty >= this.maxEmpty) break;
251
- await new Promise((r) => setTimeout(r, this.pollInterval));
252
- continue;
253
- }
254
-
255
- this.consecutiveEmpty = 0;
256
- this.log(`Claimed: ${task.title}`, "success");
257
-
258
- const result = await this.executeTask(task);
259
-
260
- // Sync artifacts after task execution
261
- await this.artifactSyncer.sync();
262
-
263
- if (result.success) {
264
- await this.client.tasks.update(task.id, this.config.workspaceId, {
265
- status: "VERIFICATION" as TaskStatus,
266
- });
267
- await this.client.tasks.addComment(task.id, this.config.workspaceId, {
268
- author: this.config.agentId,
269
- text: `✅ ${result.summary}`,
270
- });
271
- this.tasksCompleted++;
272
- } else {
273
- await this.client.tasks.update(task.id, this.config.workspaceId, {
274
- status: "BACKLOG" as TaskStatus,
275
- assignedTo: null,
276
- });
277
- await this.client.tasks.addComment(task.id, this.config.workspaceId, {
278
- author: this.config.agentId,
279
- text: `❌ ${result.summary}`,
280
- });
281
- }
282
- }
283
- process.exit(0);
284
- }
285
- }
286
-
287
- // CLI entry point
288
- if (
289
- process.argv[1]?.includes("agent-worker") ||
290
- process.argv[1]?.includes("worker")
291
- ) {
292
- const args = process.argv.slice(2);
293
- const config: Partial<WorkerConfig> = {};
294
- for (let i = 0; i < args.length; i++) {
295
- const arg = args[i];
296
- if (arg === "--agent-id") config.agentId = args[++i];
297
- else if (arg === "--workspace-id") config.workspaceId = args[++i];
298
- else if (arg === "--sprint-id") config.sprintId = args[++i];
299
- else if (arg === "--api-base") config.apiBase = args[++i];
300
- else if (arg === "--api-key") config.apiKey = args[++i];
301
- else if (arg === "--anthropic-api-key") config.anthropicApiKey = args[++i];
302
- else if (arg === "--project-path") config.projectPath = args[++i];
303
- else if (arg === "--model") config.model = args[++i];
304
- }
305
-
306
- if (
307
- !config.agentId ||
308
- !config.workspaceId ||
309
- !config.apiBase ||
310
- !config.apiKey ||
311
- !config.projectPath
312
- ) {
313
- console.error("Missing required arguments");
314
- process.exit(1);
315
- }
316
-
317
- const worker = new AgentWorker(config as WorkerConfig);
318
- worker.run().catch((err) => {
319
- console.error("Fatal worker error:", err);
320
- process.exit(1);
321
- });
322
- }
@@ -1,93 +0,0 @@
1
- import Anthropic from "@anthropic-ai/sdk";
2
- import { DEFAULT_MODEL } from "../core/config.js";
3
-
4
- export interface AnthropicClientConfig {
5
- apiKey: string;
6
- model?: string;
7
- }
8
-
9
- export interface CachedPromptOptions {
10
- systemPrompt?: string;
11
- cacheableContext?: string[];
12
- userPrompt: string;
13
- }
14
-
15
- /**
16
- * Anthropic Client with Prompt Caching Support
17
- *
18
- * This client wraps the official Anthropic SDK and adds support for
19
- * prompt caching to dramatically reduce latency and costs for repeated
20
- * context (like codebase indexes, CLAUDE.md, etc.)
21
- */
22
- export class AnthropicClient {
23
- private client: Anthropic;
24
- private model: string;
25
-
26
- constructor(config: AnthropicClientConfig) {
27
- this.client = new Anthropic({
28
- apiKey: config.apiKey,
29
- });
30
- this.model = config.model || DEFAULT_MODEL;
31
- }
32
-
33
- /**
34
- * Run a prompt with optional caching for large context blocks
35
- *
36
- * @param options - Prompt configuration with cacheable context
37
- * @returns The generated text response
38
- */
39
- async run(options: CachedPromptOptions): Promise<string> {
40
- const { systemPrompt, cacheableContext = [], userPrompt } = options;
41
-
42
- // Build system message with cache breakpoints
43
- const systemContent: Anthropic.Messages.TextBlockParam[] = [];
44
-
45
- if (systemPrompt) {
46
- systemContent.push({
47
- type: "text",
48
- text: systemPrompt,
49
- });
50
- }
51
-
52
- // Add each cacheable context block with cache_control
53
- for (let i = 0; i < cacheableContext.length; i++) {
54
- const isLast = i === cacheableContext.length - 1;
55
- systemContent.push({
56
- type: "text",
57
- text: cacheableContext[i],
58
- // Only the last block gets the cache breakpoint
59
- ...(isLast && {
60
- cache_control: { type: "ephemeral" as const },
61
- }),
62
- });
63
- }
64
-
65
- const response = await this.client.messages.create({
66
- model: this.model,
67
- max_tokens: 8000,
68
- system: systemContent,
69
- messages: [
70
- {
71
- role: "user",
72
- content: userPrompt,
73
- },
74
- ],
75
- });
76
-
77
- // Extract text from response
78
- const textBlocks = response.content.filter(
79
- (block): block is Anthropic.Messages.TextBlock => block.type === "text"
80
- );
81
-
82
- return textBlocks.map((block) => block.text).join("\n");
83
- }
84
-
85
- /**
86
- * Simple run without caching (for short prompts)
87
- */
88
- async runSimple(prompt: string): Promise<string> {
89
- return this.run({
90
- userPrompt: prompt,
91
- });
92
- }
93
- }
@@ -1,86 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { DEFAULT_MODEL } from "../core/config.js";
3
-
4
- export class ClaudeRunner {
5
- constructor(
6
- private projectPath: string,
7
- private model: string = DEFAULT_MODEL
8
- ) {}
9
-
10
- async run(prompt: string, _isPlanning = false): Promise<string> {
11
- const maxRetries = 3;
12
- let lastError: Error | null = null;
13
-
14
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
15
- try {
16
- return await this.executeRun(prompt);
17
- } catch (error) {
18
- const err = error as Error;
19
- lastError = err;
20
- const isLastAttempt = attempt === maxRetries;
21
-
22
- if (!isLastAttempt) {
23
- const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
24
- console.warn(
25
- `Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`
26
- );
27
- await new Promise((resolve) => setTimeout(resolve, delay));
28
- }
29
- }
30
- }
31
-
32
- throw lastError || new Error("Claude CLI failed after multiple attempts");
33
- }
34
-
35
- private executeRun(prompt: string): Promise<string> {
36
- return new Promise((resolve, reject) => {
37
- const args = [
38
- "--dangerously-skip-permissions",
39
- "--print",
40
- "--model",
41
- this.model,
42
- ];
43
-
44
- const claude = spawn("claude", args, {
45
- cwd: this.projectPath,
46
- stdio: ["pipe", "pipe", "pipe"],
47
- env: process.env,
48
- shell: true,
49
- });
50
-
51
- let output = "";
52
- let errorOutput = "";
53
-
54
- claude.stdout.on("data", (data) => {
55
- output += data.toString();
56
- // Only write to stdout if we're not retrying or if logic dictates
57
- // process.stdout.write(data.toString());
58
- });
59
- claude.stderr.on("data", (data) => {
60
- errorOutput += data.toString();
61
- // process.stderr.write(data.toString());
62
- });
63
-
64
- claude.on("error", (err) =>
65
- reject(
66
- new Error(
67
- `Failed to start Claude CLI (shell: true): ${err.message}. Please ensure the 'claude' command is available in your PATH.`
68
- )
69
- )
70
- );
71
- claude.on("close", (code) => {
72
- if (code === 0) resolve(output);
73
- else {
74
- const detail = errorOutput.trim();
75
- const message = detail
76
- ? `Claude CLI error (exit code ${code}): ${detail}`
77
- : `Claude CLI exited with code ${code}. Please ensure the Claude CLI is installed and you are logged in (run 'claude' manually to check).`;
78
- reject(new Error(message));
79
- }
80
- });
81
-
82
- claude.stdin.write(prompt);
83
- claude.stdin.end();
84
- });
85
- }
86
- }
package/src/ai/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { AnthropicClient } from "./anthropic-client.js";
2
- export { ClaudeRunner } from "./claude-runner.js";
@@ -1,21 +0,0 @@
1
- import { join } from "node:path";
2
-
3
- export const DEFAULT_MODEL = "sonnet";
4
-
5
- export const LOCUS_CONFIG = {
6
- dir: ".locus",
7
- configFile: "config.json",
8
- indexFile: "codebase-index.json",
9
- contextFile: "CLAUDE.md",
10
- artifactsDir: "artifacts",
11
- };
12
-
13
- export function getLocusPath(
14
- projectPath: string,
15
- fileName: keyof typeof LOCUS_CONFIG
16
- ): string {
17
- if (fileName === "contextFile") {
18
- return join(projectPath, LOCUS_CONFIG.contextFile);
19
- }
20
- return join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
21
- }
package/src/core/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export { DEFAULT_MODEL, getLocusPath, LOCUS_CONFIG } from "./config.js";
2
- export { type CodebaseIndex, CodebaseIndexer } from "./indexer.js";
3
- export { PromptBuilder } from "./prompt-builder.js";