@locusai/sdk 0.4.6 → 0.4.10
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.
- package/dist/agent/worker.js +1271 -239
- package/dist/index-node.js +1601 -20
- package/dist/index.js +429 -121
- package/dist/orchestrator.d.ts.map +1 -1
- package/package.json +7 -19
- package/dist/agent/artifact-syncer.js +0 -77
- package/dist/agent/codebase-indexer-service.js +0 -55
- package/dist/agent/index.js +0 -5
- package/dist/agent/sprint-planner.js +0 -68
- package/dist/agent/task-executor.js +0 -60
- package/dist/ai/anthropic-client.js +0 -70
- package/dist/ai/claude-runner.js +0 -71
- package/dist/ai/index.js +0 -2
- package/dist/core/config.js +0 -15
- package/dist/core/index.js +0 -3
- package/dist/core/indexer.js +0 -113
- package/dist/core/prompt-builder.js +0 -83
- package/dist/events.js +0 -15
- package/dist/modules/auth.js +0 -23
- package/dist/modules/base.js +0 -8
- package/dist/modules/ci.js +0 -7
- package/dist/modules/docs.js +0 -38
- package/dist/modules/invitations.js +0 -22
- package/dist/modules/organizations.js +0 -39
- package/dist/modules/sprints.js +0 -34
- package/dist/modules/tasks.js +0 -56
- package/dist/modules/workspaces.js +0 -49
- package/dist/orchestrator.js +0 -356
- package/dist/utils/colors.js +0 -54
- package/dist/utils/retry.js +0 -37
- package/src/agent/artifact-syncer.ts +0 -111
- package/src/agent/codebase-indexer-service.ts +0 -71
- package/src/agent/index.ts +0 -5
- package/src/agent/sprint-planner.ts +0 -86
- package/src/agent/task-executor.ts +0 -85
- package/src/agent/worker.ts +0 -322
- package/src/ai/anthropic-client.ts +0 -93
- package/src/ai/claude-runner.ts +0 -86
- package/src/ai/index.ts +0 -2
- package/src/core/config.ts +0 -21
- package/src/core/index.ts +0 -3
- package/src/core/indexer.ts +0 -131
- package/src/core/prompt-builder.ts +0 -91
- package/src/events.ts +0 -35
- package/src/index-node.ts +0 -23
- package/src/index.ts +0 -159
- package/src/modules/auth.ts +0 -48
- package/src/modules/base.ts +0 -9
- package/src/modules/ci.ts +0 -12
- package/src/modules/docs.ts +0 -84
- package/src/modules/invitations.ts +0 -45
- package/src/modules/organizations.ts +0 -90
- package/src/modules/sprints.ts +0 -69
- package/src/modules/tasks.ts +0 -110
- package/src/modules/workspaces.ts +0 -94
- package/src/orchestrator.ts +0 -473
- package/src/utils/colors.ts +0 -63
- package/src/utils/retry.ts +0 -56
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { getLocusPath } from "../core/config.js";
|
|
4
|
-
/**
|
|
5
|
-
* Handles syncing local artifacts to the platform
|
|
6
|
-
*/
|
|
7
|
-
export class ArtifactSyncer {
|
|
8
|
-
deps;
|
|
9
|
-
constructor(deps) {
|
|
10
|
-
this.deps = deps;
|
|
11
|
-
}
|
|
12
|
-
async getOrCreateArtifactsGroup() {
|
|
13
|
-
try {
|
|
14
|
-
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
15
|
-
const artifactsGroup = groups.find((g) => g.name === "Artifacts");
|
|
16
|
-
if (artifactsGroup) {
|
|
17
|
-
return artifactsGroup.id;
|
|
18
|
-
}
|
|
19
|
-
// Create the Artifacts group
|
|
20
|
-
const newGroup = await this.deps.client.docs.createGroup(this.deps.workspaceId, {
|
|
21
|
-
name: "Artifacts",
|
|
22
|
-
order: 999, // Place at the end
|
|
23
|
-
});
|
|
24
|
-
this.deps.log("Created 'Artifacts' group for agent-generated docs", "info");
|
|
25
|
-
return newGroup.id;
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
this.deps.log(`Failed to get/create Artifacts group: ${error}`, "error");
|
|
29
|
-
throw error;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
async sync() {
|
|
33
|
-
const artifactsDir = getLocusPath(this.deps.projectPath, "artifactsDir");
|
|
34
|
-
if (!existsSync(artifactsDir)) {
|
|
35
|
-
mkdirSync(artifactsDir, { recursive: true });
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
try {
|
|
39
|
-
const files = readdirSync(artifactsDir);
|
|
40
|
-
if (files.length === 0)
|
|
41
|
-
return;
|
|
42
|
-
this.deps.log(`Syncing ${files.length} artifacts to server...`, "info");
|
|
43
|
-
// Get or create the Artifacts group
|
|
44
|
-
const artifactsGroupId = await this.getOrCreateArtifactsGroup();
|
|
45
|
-
// Get existing docs to check for updates
|
|
46
|
-
const existingDocs = await this.deps.client.docs.list(this.deps.workspaceId);
|
|
47
|
-
for (const file of files) {
|
|
48
|
-
const filePath = join(artifactsDir, file);
|
|
49
|
-
if (statSync(filePath).isFile()) {
|
|
50
|
-
const content = readFileSync(filePath, "utf-8");
|
|
51
|
-
const title = file.replace(/\.md$/, "").trim();
|
|
52
|
-
if (!title)
|
|
53
|
-
continue;
|
|
54
|
-
const existing = existingDocs.find((d) => d.title === title);
|
|
55
|
-
if (existing) {
|
|
56
|
-
if (existing.content !== content ||
|
|
57
|
-
existing.groupId !== artifactsGroupId) {
|
|
58
|
-
await this.deps.client.docs.update(existing.id, this.deps.workspaceId, { content, groupId: artifactsGroupId });
|
|
59
|
-
this.deps.log(`Updated artifact: ${file}`, "success");
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
await this.deps.client.docs.create(this.deps.workspaceId, {
|
|
64
|
-
title,
|
|
65
|
-
content,
|
|
66
|
-
groupId: artifactsGroupId,
|
|
67
|
-
});
|
|
68
|
-
this.deps.log(`Created artifact: ${file}`, "success");
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
this.deps.log(`Failed to sync artifacts: ${error}`, "error");
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { CodebaseIndexer } from "../core/indexer.js";
|
|
2
|
-
/**
|
|
3
|
-
* Handles codebase indexing with AI analysis
|
|
4
|
-
*/
|
|
5
|
-
export class CodebaseIndexerService {
|
|
6
|
-
deps;
|
|
7
|
-
indexer;
|
|
8
|
-
constructor(deps) {
|
|
9
|
-
this.deps = deps;
|
|
10
|
-
this.indexer = new CodebaseIndexer(deps.projectPath);
|
|
11
|
-
}
|
|
12
|
-
async reindex() {
|
|
13
|
-
try {
|
|
14
|
-
this.deps.log("Reindexing codebase...", "info");
|
|
15
|
-
const index = await this.indexer.index((msg) => this.deps.log(msg, "info"), async (tree) => {
|
|
16
|
-
const prompt = `You are a codebase analysis expert. Analyze the file tree and extract:
|
|
17
|
-
1. Key symbols (classes, functions, types) and their locations
|
|
18
|
-
2. Responsibilities of each directory/file
|
|
19
|
-
3. Overall project structure
|
|
20
|
-
|
|
21
|
-
Analyze this file tree and provide a JSON response with:
|
|
22
|
-
- "symbols": object mapping symbol names to file paths (array)
|
|
23
|
-
- "responsibilities": object mapping paths to brief descriptions
|
|
24
|
-
|
|
25
|
-
File tree:
|
|
26
|
-
${tree}
|
|
27
|
-
|
|
28
|
-
Return ONLY valid JSON, no markdown formatting.`;
|
|
29
|
-
let response;
|
|
30
|
-
if (this.deps.anthropicClient) {
|
|
31
|
-
// Use Anthropic SDK with caching for faster indexing
|
|
32
|
-
response = await this.deps.anthropicClient.run({
|
|
33
|
-
systemPrompt: "You are a codebase analysis expert specialized in extracting structure and symbols from file trees.",
|
|
34
|
-
userPrompt: prompt,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
// Fallback to Claude CLI
|
|
39
|
-
response = await this.deps.claudeRunner.run(prompt, true);
|
|
40
|
-
}
|
|
41
|
-
// Extract JSON from response (handle markdown code blocks)
|
|
42
|
-
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
43
|
-
if (jsonMatch) {
|
|
44
|
-
return JSON.parse(jsonMatch[0]);
|
|
45
|
-
}
|
|
46
|
-
return { symbols: {}, responsibilities: {}, lastIndexed: "" };
|
|
47
|
-
});
|
|
48
|
-
this.indexer.saveIndex(index);
|
|
49
|
-
this.deps.log("Codebase reindexed successfully", "success");
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
this.deps.log(`Failed to reindex codebase: ${error}`, "error");
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
package/dist/agent/index.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { ArtifactSyncer } from "./artifact-syncer.js";
|
|
2
|
-
export { CodebaseIndexerService } from "./codebase-indexer-service.js";
|
|
3
|
-
export { SprintPlanner } from "./sprint-planner.js";
|
|
4
|
-
export { TaskExecutor } from "./task-executor.js";
|
|
5
|
-
export { AgentWorker } from "./worker.js";
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Handles sprint planning and mindmap generation
|
|
3
|
-
*/
|
|
4
|
-
export class SprintPlanner {
|
|
5
|
-
deps;
|
|
6
|
-
constructor(deps) {
|
|
7
|
-
this.deps = deps;
|
|
8
|
-
}
|
|
9
|
-
async planSprint(sprint, tasks) {
|
|
10
|
-
this.deps.log(`Planning sprint: ${sprint.name}`, "info");
|
|
11
|
-
try {
|
|
12
|
-
const taskList = tasks
|
|
13
|
-
.map((t) => `- [${t.id}] ${t.title}: ${t.description || "No description"}`)
|
|
14
|
-
.join("\n");
|
|
15
|
-
let plan;
|
|
16
|
-
if (this.deps.anthropicClient) {
|
|
17
|
-
// Use Anthropic SDK with caching for faster planning
|
|
18
|
-
const systemPrompt = `You are an expert project manager and lead engineer specialized in sprint planning and task prioritization.`;
|
|
19
|
-
const userPrompt = `# Sprint Planning: ${sprint.name}
|
|
20
|
-
|
|
21
|
-
## Tasks
|
|
22
|
-
${taskList}
|
|
23
|
-
|
|
24
|
-
## Instructions
|
|
25
|
-
1. Analyze dependencies between these tasks.
|
|
26
|
-
2. Prioritize them for the most efficient execution.
|
|
27
|
-
3. Create a mindmap (in Markdown or Mermaid format) that visualizes the sprint structure.
|
|
28
|
-
4. Output your final plan. The plan should clearly state the order of execution.
|
|
29
|
-
|
|
30
|
-
**IMPORTANT**:
|
|
31
|
-
- Do NOT create any files on the filesystem during this planning phase.
|
|
32
|
-
- Avoid using absolute local paths (e.g., /Users/...) in your output. Use relative paths starting from the project root if necessary.
|
|
33
|
-
- Your output will be saved as the official sprint mindmap on the server.`;
|
|
34
|
-
plan = await this.deps.anthropicClient.run({
|
|
35
|
-
systemPrompt,
|
|
36
|
-
userPrompt,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
// Fallback to Claude CLI
|
|
41
|
-
const planningPrompt = `# Sprint Planning: ${sprint.name}
|
|
42
|
-
|
|
43
|
-
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.
|
|
44
|
-
|
|
45
|
-
## Tasks
|
|
46
|
-
${taskList}
|
|
47
|
-
|
|
48
|
-
## Instructions
|
|
49
|
-
1. Analyze dependencies between these tasks.
|
|
50
|
-
2. Prioritize them for the most efficient execution.
|
|
51
|
-
3. Create a mindmap (in Markdown or Mermaid format) that visualizes the sprint structure.
|
|
52
|
-
4. Output your final plan. The plan should clearly state the order of execution.
|
|
53
|
-
|
|
54
|
-
**IMPORTANT**:
|
|
55
|
-
- Do NOT create any files on the filesystem during this planning phase.
|
|
56
|
-
- Avoid using absolute local paths (e.g., /Users/...) in your output. Use relative paths starting from the project root if necessary.
|
|
57
|
-
- Your output will be saved as the official sprint mindmap on the server.`;
|
|
58
|
-
plan = await this.deps.claudeRunner.run(planningPrompt, true);
|
|
59
|
-
}
|
|
60
|
-
this.deps.log("Sprint mindmap generated and posted to server.", "success");
|
|
61
|
-
return plan;
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
this.deps.log(`Sprint planning failed: ${error}`, "error");
|
|
65
|
-
return sprint.mindmap || "";
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { PromptBuilder } from "../core/prompt-builder.js";
|
|
2
|
-
/**
|
|
3
|
-
* Handles task execution with two-phase approach (planning + execution)
|
|
4
|
-
*/
|
|
5
|
-
export class TaskExecutor {
|
|
6
|
-
deps;
|
|
7
|
-
promptBuilder;
|
|
8
|
-
constructor(deps) {
|
|
9
|
-
this.deps = deps;
|
|
10
|
-
this.promptBuilder = new PromptBuilder(deps.projectPath);
|
|
11
|
-
}
|
|
12
|
-
updateSprintPlan(sprintPlan) {
|
|
13
|
-
this.deps.sprintPlan = sprintPlan;
|
|
14
|
-
}
|
|
15
|
-
async execute(task) {
|
|
16
|
-
this.deps.log(`Executing: ${task.title}`, "info");
|
|
17
|
-
let basePrompt = await this.promptBuilder.build(task);
|
|
18
|
-
if (this.deps.sprintPlan) {
|
|
19
|
-
basePrompt = `## Sprint Context\n${this.deps.sprintPlan}\n\n${basePrompt}`;
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
let plan = "";
|
|
23
|
-
if (this.deps.anthropicClient) {
|
|
24
|
-
// Phase 1: Planning (using Anthropic SDK with caching)
|
|
25
|
-
this.deps.log("Phase 1: Planning (Anthropic SDK)...", "info");
|
|
26
|
-
// Build cacheable context blocks
|
|
27
|
-
const cacheableContext = [basePrompt];
|
|
28
|
-
plan = await this.deps.anthropicClient.run({
|
|
29
|
-
systemPrompt: "You are an expert software engineer. Analyze the task carefully and create a detailed implementation plan.",
|
|
30
|
-
cacheableContext,
|
|
31
|
-
userPrompt: "## Phase 1: Planning\nAnalyze and create a detailed plan for THIS SPECIFIC TASK. Do NOT execute changes yet.",
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
this.deps.log("Skipping Phase 1: Planning (No Anthropic API Key)...", "info");
|
|
36
|
-
}
|
|
37
|
-
// Phase 2: Execution (always using Claude CLI for agentic tools)
|
|
38
|
-
this.deps.log("Starting Execution...", "info");
|
|
39
|
-
let executionPrompt = basePrompt;
|
|
40
|
-
if (plan) {
|
|
41
|
-
executionPrompt += `\n\n## Phase 2: Execution\nBased on the plan, execute the task:\n\n${plan}`;
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
executionPrompt += `\n\n## Execution\nExecute the task directly.`;
|
|
45
|
-
}
|
|
46
|
-
executionPrompt += `\n\nWhen finished, output: <promise>COMPLETE</promise>`;
|
|
47
|
-
const output = await this.deps.claudeRunner.run(executionPrompt);
|
|
48
|
-
const success = output.includes("<promise>COMPLETE</promise>");
|
|
49
|
-
return {
|
|
50
|
-
success,
|
|
51
|
-
summary: success
|
|
52
|
-
? "Task completed by Claude"
|
|
53
|
-
: "Claude did not signal completion",
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
return { success: false, summary: `Error: ${error}` };
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
-
import { DEFAULT_MODEL } from "../core/config.js";
|
|
3
|
-
/**
|
|
4
|
-
* Anthropic Client with Prompt Caching Support
|
|
5
|
-
*
|
|
6
|
-
* This client wraps the official Anthropic SDK and adds support for
|
|
7
|
-
* prompt caching to dramatically reduce latency and costs for repeated
|
|
8
|
-
* context (like codebase indexes, CLAUDE.md, etc.)
|
|
9
|
-
*/
|
|
10
|
-
export class AnthropicClient {
|
|
11
|
-
client;
|
|
12
|
-
model;
|
|
13
|
-
constructor(config) {
|
|
14
|
-
this.client = new Anthropic({
|
|
15
|
-
apiKey: config.apiKey,
|
|
16
|
-
});
|
|
17
|
-
this.model = config.model || DEFAULT_MODEL;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Run a prompt with optional caching for large context blocks
|
|
21
|
-
*
|
|
22
|
-
* @param options - Prompt configuration with cacheable context
|
|
23
|
-
* @returns The generated text response
|
|
24
|
-
*/
|
|
25
|
-
async run(options) {
|
|
26
|
-
const { systemPrompt, cacheableContext = [], userPrompt } = options;
|
|
27
|
-
// Build system message with cache breakpoints
|
|
28
|
-
const systemContent = [];
|
|
29
|
-
if (systemPrompt) {
|
|
30
|
-
systemContent.push({
|
|
31
|
-
type: "text",
|
|
32
|
-
text: systemPrompt,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
// Add each cacheable context block with cache_control
|
|
36
|
-
for (let i = 0; i < cacheableContext.length; i++) {
|
|
37
|
-
const isLast = i === cacheableContext.length - 1;
|
|
38
|
-
systemContent.push({
|
|
39
|
-
type: "text",
|
|
40
|
-
text: cacheableContext[i],
|
|
41
|
-
// Only the last block gets the cache breakpoint
|
|
42
|
-
...(isLast && {
|
|
43
|
-
cache_control: { type: "ephemeral" },
|
|
44
|
-
}),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
const response = await this.client.messages.create({
|
|
48
|
-
model: this.model,
|
|
49
|
-
max_tokens: 8000,
|
|
50
|
-
system: systemContent,
|
|
51
|
-
messages: [
|
|
52
|
-
{
|
|
53
|
-
role: "user",
|
|
54
|
-
content: userPrompt,
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
});
|
|
58
|
-
// Extract text from response
|
|
59
|
-
const textBlocks = response.content.filter((block) => block.type === "text");
|
|
60
|
-
return textBlocks.map((block) => block.text).join("\n");
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Simple run without caching (for short prompts)
|
|
64
|
-
*/
|
|
65
|
-
async runSimple(prompt) {
|
|
66
|
-
return this.run({
|
|
67
|
-
userPrompt: prompt,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
package/dist/ai/claude-runner.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { DEFAULT_MODEL } from "../core/config.js";
|
|
3
|
-
export class ClaudeRunner {
|
|
4
|
-
projectPath;
|
|
5
|
-
model;
|
|
6
|
-
constructor(projectPath, model = DEFAULT_MODEL) {
|
|
7
|
-
this.projectPath = projectPath;
|
|
8
|
-
this.model = model;
|
|
9
|
-
}
|
|
10
|
-
async run(prompt, _isPlanning = false) {
|
|
11
|
-
const maxRetries = 3;
|
|
12
|
-
let lastError = null;
|
|
13
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
14
|
-
try {
|
|
15
|
-
return await this.executeRun(prompt);
|
|
16
|
-
}
|
|
17
|
-
catch (error) {
|
|
18
|
-
const err = error;
|
|
19
|
-
lastError = err;
|
|
20
|
-
const isLastAttempt = attempt === maxRetries;
|
|
21
|
-
if (!isLastAttempt) {
|
|
22
|
-
const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
|
|
23
|
-
console.warn(`Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`);
|
|
24
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
throw lastError || new Error("Claude CLI failed after multiple attempts");
|
|
29
|
-
}
|
|
30
|
-
executeRun(prompt) {
|
|
31
|
-
return new Promise((resolve, reject) => {
|
|
32
|
-
const args = [
|
|
33
|
-
"--dangerously-skip-permissions",
|
|
34
|
-
"--print",
|
|
35
|
-
"--model",
|
|
36
|
-
this.model,
|
|
37
|
-
];
|
|
38
|
-
const claude = spawn("claude", args, {
|
|
39
|
-
cwd: this.projectPath,
|
|
40
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
41
|
-
env: process.env,
|
|
42
|
-
shell: true,
|
|
43
|
-
});
|
|
44
|
-
let output = "";
|
|
45
|
-
let errorOutput = "";
|
|
46
|
-
claude.stdout.on("data", (data) => {
|
|
47
|
-
output += data.toString();
|
|
48
|
-
// Only write to stdout if we're not retrying or if logic dictates
|
|
49
|
-
// process.stdout.write(data.toString());
|
|
50
|
-
});
|
|
51
|
-
claude.stderr.on("data", (data) => {
|
|
52
|
-
errorOutput += data.toString();
|
|
53
|
-
// process.stderr.write(data.toString());
|
|
54
|
-
});
|
|
55
|
-
claude.on("error", (err) => reject(new Error(`Failed to start Claude CLI (shell: true): ${err.message}. Please ensure the 'claude' command is available in your PATH.`)));
|
|
56
|
-
claude.on("close", (code) => {
|
|
57
|
-
if (code === 0)
|
|
58
|
-
resolve(output);
|
|
59
|
-
else {
|
|
60
|
-
const detail = errorOutput.trim();
|
|
61
|
-
const message = detail
|
|
62
|
-
? `Claude CLI error (exit code ${code}): ${detail}`
|
|
63
|
-
: `Claude CLI exited with code ${code}. Please ensure the Claude CLI is installed and you are logged in (run 'claude' manually to check).`;
|
|
64
|
-
reject(new Error(message));
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
claude.stdin.write(prompt);
|
|
68
|
-
claude.stdin.end();
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
package/dist/ai/index.js
DELETED
package/dist/core/config.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
export const DEFAULT_MODEL = "sonnet";
|
|
3
|
-
export const LOCUS_CONFIG = {
|
|
4
|
-
dir: ".locus",
|
|
5
|
-
configFile: "config.json",
|
|
6
|
-
indexFile: "codebase-index.json",
|
|
7
|
-
contextFile: "CLAUDE.md",
|
|
8
|
-
artifactsDir: "artifacts",
|
|
9
|
-
};
|
|
10
|
-
export function getLocusPath(projectPath, fileName) {
|
|
11
|
-
if (fileName === "contextFile") {
|
|
12
|
-
return join(projectPath, LOCUS_CONFIG.contextFile);
|
|
13
|
-
}
|
|
14
|
-
return join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
|
|
15
|
-
}
|
package/dist/core/index.js
DELETED
package/dist/core/indexer.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { globby } from "globby";
|
|
4
|
-
export class CodebaseIndexer {
|
|
5
|
-
projectPath;
|
|
6
|
-
indexPath;
|
|
7
|
-
constructor(projectPath) {
|
|
8
|
-
this.projectPath = projectPath;
|
|
9
|
-
this.indexPath = join(projectPath, ".locus", "codebase-index.json");
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Generates a codebase index by providing the entire file tree to an AI summarizer.
|
|
13
|
-
* This is much more efficient than per-file indexing for large projects.
|
|
14
|
-
*/
|
|
15
|
-
async index(onProgress, treeSummarizer) {
|
|
16
|
-
if (!treeSummarizer) {
|
|
17
|
-
throw new Error("A treeSummarizer is required for this indexing method.");
|
|
18
|
-
}
|
|
19
|
-
if (onProgress)
|
|
20
|
-
onProgress("Generating file tree...");
|
|
21
|
-
// 1. Get a comprehensive but clean file tree
|
|
22
|
-
const gitmodulesPath = join(this.projectPath, ".gitmodules");
|
|
23
|
-
const submoduleIgnores = [];
|
|
24
|
-
if (existsSync(gitmodulesPath)) {
|
|
25
|
-
try {
|
|
26
|
-
const content = readFileSync(gitmodulesPath, "utf-8");
|
|
27
|
-
const lines = content.split("\n");
|
|
28
|
-
for (const line of lines) {
|
|
29
|
-
const match = line.match(/^\s*path\s*=\s*(.*)$/);
|
|
30
|
-
const path = match?.[1]?.trim();
|
|
31
|
-
if (path) {
|
|
32
|
-
submoduleIgnores.push(`${path}/**`);
|
|
33
|
-
submoduleIgnores.push(`**/${path}/**`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// Fallback if .gitmodules exists but can't be read or parsed
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const files = await globby(["**/*"], {
|
|
42
|
-
cwd: this.projectPath,
|
|
43
|
-
gitignore: true,
|
|
44
|
-
ignore: [
|
|
45
|
-
...submoduleIgnores,
|
|
46
|
-
"**/node_modules/**",
|
|
47
|
-
"**/dist/**",
|
|
48
|
-
"**/build/**",
|
|
49
|
-
"**/target/**", // Rust build artifacts
|
|
50
|
-
"**/bin/**",
|
|
51
|
-
"**/obj/**",
|
|
52
|
-
"**/.next/**",
|
|
53
|
-
"**/.svelte-kit/**",
|
|
54
|
-
"**/.nuxt/**",
|
|
55
|
-
"**/.cache/**",
|
|
56
|
-
"**/out/**",
|
|
57
|
-
"**/__tests__/**",
|
|
58
|
-
"**/coverage/**",
|
|
59
|
-
"**/*.test.*",
|
|
60
|
-
"**/*.spec.*",
|
|
61
|
-
"**/*.d.ts",
|
|
62
|
-
"**/tsconfig.tsbuildinfo",
|
|
63
|
-
"**/.locus/*.json", // Ignore index and other system JSONs
|
|
64
|
-
"**/.locus/*.md", // Ignore system MDs
|
|
65
|
-
"**/.locus/!(artifacts)/**", // Ignore everything in .locus EXCEPT artifacts
|
|
66
|
-
"**/.git/**",
|
|
67
|
-
"**/.svn/**",
|
|
68
|
-
"**/.hg/**",
|
|
69
|
-
"**/.vscode/**",
|
|
70
|
-
"**/.idea/**",
|
|
71
|
-
"**/.DS_Store",
|
|
72
|
-
"**/bun.lock",
|
|
73
|
-
"**/package-lock.json",
|
|
74
|
-
"**/yarn.lock",
|
|
75
|
-
"**/pnpm-lock.yaml",
|
|
76
|
-
"**/Cargo.lock",
|
|
77
|
-
"**/go.sum",
|
|
78
|
-
"**/poetry.lock",
|
|
79
|
-
// Binary/Large Assets
|
|
80
|
-
"**/*.{png,jpg,jpeg,gif,svg,ico,mp4,webm,wav,mp3,woff,woff2,eot,ttf,otf,pdf,zip,tar.gz,rar}",
|
|
81
|
-
],
|
|
82
|
-
});
|
|
83
|
-
// Format the tree for the AI
|
|
84
|
-
const treeString = files.join("\n");
|
|
85
|
-
if (onProgress)
|
|
86
|
-
onProgress("AI is analyzing codebase structure...");
|
|
87
|
-
// 2. Ask AI to generate the index based on the tree
|
|
88
|
-
const index = await treeSummarizer(treeString);
|
|
89
|
-
// 3. Post-process: Ensure symbols are extracted for core files if not provided by AI
|
|
90
|
-
// (AI is good at structure, but might miss specific exports unless it reads the files)
|
|
91
|
-
// For now, we trust the AI's structural summary and can supplement symbols later if needed.
|
|
92
|
-
index.lastIndexed = new Date().toISOString();
|
|
93
|
-
return index;
|
|
94
|
-
}
|
|
95
|
-
loadIndex() {
|
|
96
|
-
if (existsSync(this.indexPath)) {
|
|
97
|
-
try {
|
|
98
|
-
return JSON.parse(readFileSync(this.indexPath, "utf-8"));
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
saveIndex(index) {
|
|
107
|
-
const dir = dirname(this.indexPath);
|
|
108
|
-
if (!existsSync(dir)) {
|
|
109
|
-
mkdirSync(dir, { recursive: true });
|
|
110
|
-
}
|
|
111
|
-
writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
112
|
-
}
|
|
113
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { AssigneeRole } from "@locusai/shared";
|
|
3
|
-
import { getLocusPath } from "./config.js";
|
|
4
|
-
export class PromptBuilder {
|
|
5
|
-
projectPath;
|
|
6
|
-
constructor(projectPath) {
|
|
7
|
-
this.projectPath = projectPath;
|
|
8
|
-
}
|
|
9
|
-
async build(task) {
|
|
10
|
-
let prompt = `# Task: ${task.title}\n\n`;
|
|
11
|
-
const roleText = this.roleToText(task.assigneeRole);
|
|
12
|
-
if (roleText) {
|
|
13
|
-
prompt += `## Role\nYou are acting as a ${roleText}.\n\n`;
|
|
14
|
-
}
|
|
15
|
-
prompt += `## Description\n${task.description || "No description provided."}\n\n`;
|
|
16
|
-
// 1. Add CLAUDE.md context
|
|
17
|
-
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
18
|
-
if (existsSync(contextPath)) {
|
|
19
|
-
try {
|
|
20
|
-
const context = readFileSync(contextPath, "utf-8");
|
|
21
|
-
prompt += `## Project Context (from CLAUDE.md)\n${context}\n\n`;
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
console.warn(`Warning: Could not read context file: ${err}`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
// 2. Add Codebase Index context
|
|
28
|
-
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
29
|
-
if (existsSync(indexPath)) {
|
|
30
|
-
prompt += `## Codebase Overview\nThere is an index file in the .locus/codebase-index.json and if you need you can check it.\n\n`;
|
|
31
|
-
}
|
|
32
|
-
// 3. Add Documents
|
|
33
|
-
if (task.docs && task.docs.length > 0) {
|
|
34
|
-
prompt += `## Attached Documents\n`;
|
|
35
|
-
for (const doc of task.docs) {
|
|
36
|
-
prompt += `### ${doc.title}\n${doc.content || "(No content)"}\n\n`;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
// 4. Add Checklist
|
|
40
|
-
if (task.acceptanceChecklist && task.acceptanceChecklist.length > 0) {
|
|
41
|
-
prompt += `## Acceptance Criteria\n`;
|
|
42
|
-
for (const item of task.acceptanceChecklist) {
|
|
43
|
-
prompt += `- ${item.done ? "[x]" : "[ ]"} ${item.text}\n`;
|
|
44
|
-
}
|
|
45
|
-
prompt += "\n";
|
|
46
|
-
}
|
|
47
|
-
// 5. Add Comments & Feedback
|
|
48
|
-
if (task.comments && task.comments.length > 0) {
|
|
49
|
-
const comments = task.comments.slice(0, 5);
|
|
50
|
-
prompt += `## Task History & Feedback\n`;
|
|
51
|
-
prompt += `Review the following comments for context or rejection feedback:\n\n`;
|
|
52
|
-
for (const comment of comments) {
|
|
53
|
-
const date = new Date(comment.createdAt).toLocaleString();
|
|
54
|
-
prompt += `### ${comment.author} (${date})\n${comment.text}\n\n`;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
prompt += `## Instructions
|
|
58
|
-
1. Complete this task.
|
|
59
|
-
2. **Artifact Management**: If you create any high-level documentation (PRDs, technical drafts, architecture docs), you MUST save them in \`.locus/artifacts/\`. Do NOT create them in the root directory.
|
|
60
|
-
3. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
|
|
61
|
-
4. When finished successfully, output: <promise>COMPLETE</promise>\n`;
|
|
62
|
-
return prompt;
|
|
63
|
-
}
|
|
64
|
-
roleToText(role) {
|
|
65
|
-
if (!role) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
switch (role) {
|
|
69
|
-
case AssigneeRole.BACKEND:
|
|
70
|
-
return "Backend Engineer";
|
|
71
|
-
case AssigneeRole.FRONTEND:
|
|
72
|
-
return "Frontend Engineer";
|
|
73
|
-
case AssigneeRole.PM:
|
|
74
|
-
return "Product Manager";
|
|
75
|
-
case AssigneeRole.QA:
|
|
76
|
-
return "QA Engineer";
|
|
77
|
-
case AssigneeRole.DESIGN:
|
|
78
|
-
return "Product Designer";
|
|
79
|
-
default:
|
|
80
|
-
return "engineer";
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
package/dist/events.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "events";
|
|
2
|
-
export var LocusEvent;
|
|
3
|
-
(function (LocusEvent) {
|
|
4
|
-
LocusEvent["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
|
|
5
|
-
LocusEvent["AUTH_ERROR"] = "AUTH_ERROR";
|
|
6
|
-
LocusEvent["REQUEST_ERROR"] = "REQUEST_ERROR";
|
|
7
|
-
})(LocusEvent || (LocusEvent = {}));
|
|
8
|
-
export class LocusEmitter extends EventEmitter {
|
|
9
|
-
on(event, listener) {
|
|
10
|
-
return super.on(event, listener);
|
|
11
|
-
}
|
|
12
|
-
emit(event, ...args) {
|
|
13
|
-
return super.emit(event, ...args);
|
|
14
|
-
}
|
|
15
|
-
}
|
package/dist/modules/auth.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { BaseModule } from "./base.js";
|
|
2
|
-
export class AuthModule extends BaseModule {
|
|
3
|
-
async getMe() {
|
|
4
|
-
const { data } = await this.api.get("/auth/me");
|
|
5
|
-
return data;
|
|
6
|
-
}
|
|
7
|
-
async requestRegisterOtp(email) {
|
|
8
|
-
const { data } = await this.api.post("/auth/register-otp", { email });
|
|
9
|
-
return data;
|
|
10
|
-
}
|
|
11
|
-
async requestLoginOtp(email) {
|
|
12
|
-
const { data } = await this.api.post("/auth/login-otp", { email });
|
|
13
|
-
return data;
|
|
14
|
-
}
|
|
15
|
-
async verifyLogin(body) {
|
|
16
|
-
const { data } = await this.api.post("/auth/verify-login", body);
|
|
17
|
-
return data;
|
|
18
|
-
}
|
|
19
|
-
async completeRegistration(body) {
|
|
20
|
-
const { data } = await this.api.post("/auth/complete-registration", body);
|
|
21
|
-
return data;
|
|
22
|
-
}
|
|
23
|
-
}
|