@locusai/cli 0.7.7 → 0.8.1
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/README.md +155 -0
- package/bin/agent/worker.js +208 -56
- package/bin/locus.js +452 -148
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# @locusai/cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for [Locus](https://locusai.dev) - an AI-native project management platform for engineering teams.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @locusai/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with other package managers:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# pnpm
|
|
15
|
+
pnpm add -g @locusai/cli
|
|
16
|
+
|
|
17
|
+
# yarn
|
|
18
|
+
yarn global add @locusai/cli
|
|
19
|
+
|
|
20
|
+
# bun
|
|
21
|
+
bun add -g @locusai/cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Initialize Locus in your project
|
|
28
|
+
locus init
|
|
29
|
+
|
|
30
|
+
# Index your codebase for AI context
|
|
31
|
+
locus index
|
|
32
|
+
|
|
33
|
+
# Run an agent to work on tasks
|
|
34
|
+
locus run --api-key YOUR_API_KEY
|
|
35
|
+
|
|
36
|
+
# Execute a prompt with repository context
|
|
37
|
+
locus exec "Explain the authentication flow"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
### `locus init`
|
|
43
|
+
|
|
44
|
+
Initialize Locus in the current directory. Creates the necessary configuration files and directory structure:
|
|
45
|
+
|
|
46
|
+
- `.locus/` - Configuration directory
|
|
47
|
+
- `.locus/config.json` - Project settings
|
|
48
|
+
- `CLAUDE.md` - AI instructions and context
|
|
49
|
+
- `.agent/skills/` - Domain-specific agent skills
|
|
50
|
+
|
|
51
|
+
Running `init` on an already initialized project will update the configuration to the latest version.
|
|
52
|
+
|
|
53
|
+
### `locus index`
|
|
54
|
+
|
|
55
|
+
Index the codebase for AI context. This analyzes your project structure and creates a searchable index that helps AI agents understand your codebase.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
locus index [options]
|
|
59
|
+
|
|
60
|
+
Options:
|
|
61
|
+
--dir <path> Project directory (default: current directory)
|
|
62
|
+
--model <name> AI model to use
|
|
63
|
+
--provider <name> AI provider: claude or codex (default: claude)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `locus run`
|
|
67
|
+
|
|
68
|
+
Start an agent to work on tasks from your Locus workspace.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
locus run [options]
|
|
72
|
+
|
|
73
|
+
Options:
|
|
74
|
+
--api-key <key> Your Locus API key (required)
|
|
75
|
+
--workspace <id> Workspace ID to connect to
|
|
76
|
+
--sprint <id> Sprint ID to work on
|
|
77
|
+
--model <name> AI model to use
|
|
78
|
+
--provider <name> AI provider: claude or codex (default: claude)
|
|
79
|
+
--api-url <url> Custom API URL
|
|
80
|
+
--dir <path> Project directory (default: current directory)
|
|
81
|
+
--skip-planning Skip the planning phase
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `locus exec`
|
|
85
|
+
|
|
86
|
+
Run a prompt with repository context. Supports both single execution and interactive REPL mode.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
locus exec "your prompt" [options]
|
|
90
|
+
locus exec --interactive [options]
|
|
91
|
+
|
|
92
|
+
Options:
|
|
93
|
+
--interactive, -i Start interactive REPL mode
|
|
94
|
+
--session, -s <id> Resume a previous session
|
|
95
|
+
--model <name> AI model to use
|
|
96
|
+
--provider <name> AI provider: claude or codex (default: claude)
|
|
97
|
+
--dir <path> Project directory (default: current directory)
|
|
98
|
+
--no-stream Disable streaming output
|
|
99
|
+
--no-status Disable status display
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Session Management
|
|
103
|
+
|
|
104
|
+
Manage your exec sessions with these subcommands:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# List recent sessions
|
|
108
|
+
locus exec sessions list
|
|
109
|
+
|
|
110
|
+
# Show messages from a session
|
|
111
|
+
locus exec sessions show <session-id>
|
|
112
|
+
|
|
113
|
+
# Delete a session
|
|
114
|
+
locus exec sessions delete <session-id>
|
|
115
|
+
|
|
116
|
+
# Clear all sessions
|
|
117
|
+
locus exec sessions clear
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Configuration
|
|
121
|
+
|
|
122
|
+
Locus stores its configuration in the `.locus/` directory within your project:
|
|
123
|
+
|
|
124
|
+
- `config.json` - Project settings including workspace ID and version
|
|
125
|
+
- `codebase-index.json` - Indexed codebase structure
|
|
126
|
+
|
|
127
|
+
The `CLAUDE.md` file in your project root provides AI instructions and context that agents use when working on your codebase.
|
|
128
|
+
|
|
129
|
+
## AI Providers
|
|
130
|
+
|
|
131
|
+
Locus supports multiple AI providers:
|
|
132
|
+
|
|
133
|
+
- **Claude** (default) - Anthropic's Claude models
|
|
134
|
+
- **Codex** - OpenAI Codex models
|
|
135
|
+
|
|
136
|
+
Specify the provider with the `--provider` flag:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
locus exec "your prompt" --provider codex
|
|
140
|
+
locus run --api-key YOUR_KEY --provider claude
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Requirements
|
|
144
|
+
|
|
145
|
+
- Node.js 18 or later
|
|
146
|
+
- A Locus API key (for `run` command)
|
|
147
|
+
|
|
148
|
+
## Links
|
|
149
|
+
|
|
150
|
+
- [Documentation](https://locusai.dev/docs)
|
|
151
|
+
- [Website](https://locusai.dev)
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
package/bin/agent/worker.js
CHANGED
|
@@ -17011,6 +17011,10 @@ var require_ignore = __commonJS((exports, module) => {
|
|
|
17011
17011
|
define(module.exports, Symbol.for("setupWindows"), setupWindows);
|
|
17012
17012
|
});
|
|
17013
17013
|
|
|
17014
|
+
// ../sdk/src/agent/worker.ts
|
|
17015
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
17016
|
+
import { join as join6 } from "node:path";
|
|
17017
|
+
|
|
17014
17018
|
// ../sdk/src/core/config.ts
|
|
17015
17019
|
import { join } from "node:path";
|
|
17016
17020
|
var PROVIDER = {
|
|
@@ -17029,7 +17033,9 @@ var LOCUS_CONFIG = {
|
|
|
17029
17033
|
artifactsDir: "artifacts",
|
|
17030
17034
|
documentsDir: "documents",
|
|
17031
17035
|
agentSkillsDir: ".agent/skills",
|
|
17032
|
-
sessionsDir: "sessions"
|
|
17036
|
+
sessionsDir: "sessions",
|
|
17037
|
+
reviewsDir: "reviews",
|
|
17038
|
+
plansDir: "plans"
|
|
17033
17039
|
};
|
|
17034
17040
|
function getLocusPath(projectPath, fileName) {
|
|
17035
17041
|
if (fileName === "contextFile") {
|
|
@@ -17120,7 +17126,7 @@ class ClaudeRunner {
|
|
|
17120
17126
|
setEventEmitter(emitter) {
|
|
17121
17127
|
this.eventEmitter = emitter;
|
|
17122
17128
|
}
|
|
17123
|
-
async run(prompt
|
|
17129
|
+
async run(prompt) {
|
|
17124
17130
|
const maxRetries = 3;
|
|
17125
17131
|
let lastError = null;
|
|
17126
17132
|
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
@@ -34981,6 +34987,10 @@ var EventType;
|
|
|
34981
34987
|
EventType2["SPRINT_STATUS_CHANGED"] = "SPRINT_STATUS_CHANGED";
|
|
34982
34988
|
EventType2["SPRINT_DELETED"] = "SPRINT_DELETED";
|
|
34983
34989
|
EventType2["CHECKLIST_INITIALIZED"] = "CHECKLIST_INITIALIZED";
|
|
34990
|
+
EventType2["INTERVIEW_STARTED"] = "INTERVIEW_STARTED";
|
|
34991
|
+
EventType2["INTERVIEW_FIELD_COMPLETED"] = "INTERVIEW_FIELD_COMPLETED";
|
|
34992
|
+
EventType2["INTERVIEW_COMPLETED"] = "INTERVIEW_COMPLETED";
|
|
34993
|
+
EventType2["INTERVIEW_ABANDONED"] = "INTERVIEW_ABANDONED";
|
|
34984
34994
|
})(EventType ||= {});
|
|
34985
34995
|
// ../shared/src/models/activity.ts
|
|
34986
34996
|
var CommentSchema = BaseEntitySchema.extend({
|
|
@@ -35046,6 +35056,25 @@ var CiRanPayloadSchema = exports_external.object({
|
|
|
35046
35056
|
processed: exports_external.boolean(),
|
|
35047
35057
|
commands: exports_external.array(exports_external.object({ cmd: exports_external.string(), exitCode: exports_external.number() }))
|
|
35048
35058
|
});
|
|
35059
|
+
var InterviewStartedPayloadSchema = exports_external.object({
|
|
35060
|
+
workspaceName: exports_external.string(),
|
|
35061
|
+
firstFieldName: exports_external.string()
|
|
35062
|
+
});
|
|
35063
|
+
var InterviewFieldCompletedPayloadSchema = exports_external.object({
|
|
35064
|
+
fieldName: exports_external.string(),
|
|
35065
|
+
completionPercentage: exports_external.number()
|
|
35066
|
+
});
|
|
35067
|
+
var InterviewCompletedPayloadSchema = exports_external.object({
|
|
35068
|
+
workspaceName: exports_external.string(),
|
|
35069
|
+
timeToCompleteMs: exports_external.number(),
|
|
35070
|
+
completedVia: exports_external.enum(["interview", "manual_bypass"])
|
|
35071
|
+
});
|
|
35072
|
+
var InterviewAbandonedPayloadSchema = exports_external.object({
|
|
35073
|
+
workspaceName: exports_external.string(),
|
|
35074
|
+
lastActiveAt: exports_external.union([exports_external.date(), exports_external.string()]),
|
|
35075
|
+
completionPercentage: exports_external.number(),
|
|
35076
|
+
daysInactive: exports_external.number()
|
|
35077
|
+
});
|
|
35049
35078
|
var EventPayloadSchema = exports_external.discriminatedUnion("type", [
|
|
35050
35079
|
exports_external.object({
|
|
35051
35080
|
type: exports_external.literal("TASK_CREATED" /* TASK_CREATED */),
|
|
@@ -35090,6 +35119,22 @@ var EventPayloadSchema = exports_external.discriminatedUnion("type", [
|
|
|
35090
35119
|
exports_external.object({
|
|
35091
35120
|
type: exports_external.literal("CI_RAN" /* CI_RAN */),
|
|
35092
35121
|
payload: CiRanPayloadSchema
|
|
35122
|
+
}),
|
|
35123
|
+
exports_external.object({
|
|
35124
|
+
type: exports_external.literal("INTERVIEW_STARTED" /* INTERVIEW_STARTED */),
|
|
35125
|
+
payload: InterviewStartedPayloadSchema
|
|
35126
|
+
}),
|
|
35127
|
+
exports_external.object({
|
|
35128
|
+
type: exports_external.literal("INTERVIEW_FIELD_COMPLETED" /* INTERVIEW_FIELD_COMPLETED */),
|
|
35129
|
+
payload: InterviewFieldCompletedPayloadSchema
|
|
35130
|
+
}),
|
|
35131
|
+
exports_external.object({
|
|
35132
|
+
type: exports_external.literal("INTERVIEW_COMPLETED" /* INTERVIEW_COMPLETED */),
|
|
35133
|
+
payload: InterviewCompletedPayloadSchema
|
|
35134
|
+
}),
|
|
35135
|
+
exports_external.object({
|
|
35136
|
+
type: exports_external.literal("INTERVIEW_ABANDONED" /* INTERVIEW_ABANDONED */),
|
|
35137
|
+
payload: InterviewAbandonedPayloadSchema
|
|
35093
35138
|
})
|
|
35094
35139
|
]);
|
|
35095
35140
|
var EventSchema = exports_external.object({
|
|
@@ -35159,7 +35204,7 @@ var UnlockSchema = exports_external.object({
|
|
|
35159
35204
|
var AIRoleSchema = exports_external.enum(["user", "assistant", "system"]);
|
|
35160
35205
|
var AIArtifactSchema = exports_external.object({
|
|
35161
35206
|
id: exports_external.string(),
|
|
35162
|
-
type: exports_external.enum(["code", "document"
|
|
35207
|
+
type: exports_external.enum(["code", "document"]),
|
|
35163
35208
|
title: exports_external.string(),
|
|
35164
35209
|
content: exports_external.string(),
|
|
35165
35210
|
language: exports_external.string().optional(),
|
|
@@ -35167,13 +35212,7 @@ var AIArtifactSchema = exports_external.object({
|
|
|
35167
35212
|
});
|
|
35168
35213
|
var SuggestedActionSchema = exports_external.object({
|
|
35169
35214
|
label: exports_external.string(),
|
|
35170
|
-
type: exports_external.enum([
|
|
35171
|
-
"create_task",
|
|
35172
|
-
"create_doc",
|
|
35173
|
-
"chat_suggestion",
|
|
35174
|
-
"start_sprint",
|
|
35175
|
-
"plan_sprint"
|
|
35176
|
-
]),
|
|
35215
|
+
type: exports_external.enum(["chat_suggestion", "create_doc"]),
|
|
35177
35216
|
payload: exports_external.any()
|
|
35178
35217
|
});
|
|
35179
35218
|
var AIMessageSchema = exports_external.object({
|
|
@@ -35436,6 +35475,53 @@ var AcceptInvitationResponseSchema = exports_external.object({
|
|
|
35436
35475
|
createdAt: exports_external.number()
|
|
35437
35476
|
})
|
|
35438
35477
|
});
|
|
35478
|
+
// ../shared/src/models/manifest.ts
|
|
35479
|
+
var ProjectPhaseSchema = exports_external.enum([
|
|
35480
|
+
"PLANNING",
|
|
35481
|
+
"MVP_BUILD",
|
|
35482
|
+
"SCALING",
|
|
35483
|
+
"MAINTENANCE"
|
|
35484
|
+
]);
|
|
35485
|
+
var SprintStatusSchema = exports_external.enum(["PLANNED", "ACTIVE", "COMPLETED"]);
|
|
35486
|
+
var ProjectSprintSchema = exports_external.object({
|
|
35487
|
+
id: exports_external.string(),
|
|
35488
|
+
goal: exports_external.string(),
|
|
35489
|
+
tasks: exports_external.array(exports_external.string()),
|
|
35490
|
+
status: SprintStatusSchema
|
|
35491
|
+
});
|
|
35492
|
+
var MilestoneStatusSchema = exports_external.enum(["PENDING", "COMPLETED"]);
|
|
35493
|
+
var ProjectMilestoneSchema = exports_external.object({
|
|
35494
|
+
title: exports_external.string(),
|
|
35495
|
+
date: exports_external.string().optional(),
|
|
35496
|
+
status: MilestoneStatusSchema
|
|
35497
|
+
});
|
|
35498
|
+
var ProjectTimelineSchema = exports_external.object({
|
|
35499
|
+
sprints: exports_external.array(ProjectSprintSchema),
|
|
35500
|
+
milestones: exports_external.array(ProjectMilestoneSchema)
|
|
35501
|
+
});
|
|
35502
|
+
var RepositoryContextSchema = exports_external.object({
|
|
35503
|
+
summary: exports_external.string(),
|
|
35504
|
+
fileStructure: exports_external.string(),
|
|
35505
|
+
dependencies: exports_external.record(exports_external.string(), exports_external.string()),
|
|
35506
|
+
frameworks: exports_external.array(exports_external.string()),
|
|
35507
|
+
configFiles: exports_external.array(exports_external.string()),
|
|
35508
|
+
lastAnalysis: exports_external.string()
|
|
35509
|
+
});
|
|
35510
|
+
var ProjectManifestSchema = exports_external.object({
|
|
35511
|
+
name: exports_external.string(),
|
|
35512
|
+
mission: exports_external.string(),
|
|
35513
|
+
targetUsers: exports_external.array(exports_external.string()),
|
|
35514
|
+
techStack: exports_external.array(exports_external.string()),
|
|
35515
|
+
phase: ProjectPhaseSchema,
|
|
35516
|
+
features: exports_external.array(exports_external.string()),
|
|
35517
|
+
competitors: exports_external.array(exports_external.string()),
|
|
35518
|
+
brandVoice: exports_external.string().optional(),
|
|
35519
|
+
successMetrics: exports_external.array(exports_external.string()).optional(),
|
|
35520
|
+
completenessScore: exports_external.number().min(0).max(100),
|
|
35521
|
+
timeline: ProjectTimelineSchema.optional(),
|
|
35522
|
+
repositoryState: RepositoryContextSchema.optional()
|
|
35523
|
+
});
|
|
35524
|
+
var PartialProjectManifestSchema = ProjectManifestSchema.partial();
|
|
35439
35525
|
// ../shared/src/models/organization.ts
|
|
35440
35526
|
var OrganizationSchema = BaseEntitySchema.extend({
|
|
35441
35527
|
name: exports_external.string().min(1, "Name is required").max(100),
|
|
@@ -35625,11 +35711,15 @@ var WorkspaceAndUserParamSchema = exports_external.object({
|
|
|
35625
35711
|
workspaceId: exports_external.string().uuid("Invalid Workspace ID"),
|
|
35626
35712
|
userId: exports_external.string().uuid("Invalid User ID")
|
|
35627
35713
|
});
|
|
35714
|
+
var WorkspaceWithManifestInfoSchema = WorkspaceSchema.extend({
|
|
35715
|
+
isManifestComplete: exports_external.boolean(),
|
|
35716
|
+
manifestCompletionPercentage: exports_external.number().min(0).max(100)
|
|
35717
|
+
});
|
|
35628
35718
|
var WorkspaceResponseSchema = exports_external.object({
|
|
35629
|
-
workspace:
|
|
35719
|
+
workspace: WorkspaceWithManifestInfoSchema
|
|
35630
35720
|
});
|
|
35631
35721
|
var WorkspacesResponseSchema = exports_external.object({
|
|
35632
|
-
workspaces: exports_external.array(
|
|
35722
|
+
workspaces: exports_external.array(WorkspaceWithManifestInfoSchema)
|
|
35633
35723
|
});
|
|
35634
35724
|
var WorkspaceStatsSchema = exports_external.object({
|
|
35635
35725
|
workspaceName: exports_external.string(),
|
|
@@ -35639,6 +35729,13 @@ var WorkspaceStatsSchema = exports_external.object({
|
|
|
35639
35729
|
var WorkspaceStatsResponseSchema = exports_external.object({
|
|
35640
35730
|
stats: WorkspaceStatsSchema
|
|
35641
35731
|
});
|
|
35732
|
+
var ManifestStatusResponseSchema = exports_external.object({
|
|
35733
|
+
isComplete: exports_external.boolean(),
|
|
35734
|
+
percentage: exports_external.number().min(0).max(100),
|
|
35735
|
+
missingFields: exports_external.array(exports_external.string()),
|
|
35736
|
+
filledFields: exports_external.array(exports_external.string()),
|
|
35737
|
+
completenessScore: exports_external.number().min(0).max(100).nullable()
|
|
35738
|
+
});
|
|
35642
35739
|
// ../sdk/src/modules/tasks.ts
|
|
35643
35740
|
class TasksModule extends BaseModule {
|
|
35644
35741
|
async list(workspaceId, options) {
|
|
@@ -35728,6 +35825,10 @@ class WorkspacesModule extends BaseModule {
|
|
|
35728
35825
|
const { data } = await this.api.get(`/workspaces/${id}/stats`);
|
|
35729
35826
|
return data;
|
|
35730
35827
|
}
|
|
35828
|
+
async getManifestStatus(workspaceId) {
|
|
35829
|
+
const { data } = await this.api.get(`/workspaces/${workspaceId}/manifest-status`);
|
|
35830
|
+
return data;
|
|
35831
|
+
}
|
|
35731
35832
|
async getActivity(id, limit) {
|
|
35732
35833
|
const { data } = await this.api.get(`/workspaces/${id}/activity`, {
|
|
35733
35834
|
params: { limit }
|
|
@@ -36591,7 +36692,7 @@ File tree:
|
|
|
36591
36692
|
${tree}
|
|
36592
36693
|
|
|
36593
36694
|
Return ONLY valid JSON, no markdown formatting.`;
|
|
36594
|
-
const response = await this.deps.aiRunner.run(prompt
|
|
36695
|
+
const response = await this.deps.aiRunner.run(prompt);
|
|
36595
36696
|
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
36596
36697
|
if (jsonMatch) {
|
|
36597
36698
|
return JSON.parse(jsonMatch[0]);
|
|
@@ -36654,6 +36755,66 @@ class DocumentFetcher {
|
|
|
36654
36755
|
}
|
|
36655
36756
|
}
|
|
36656
36757
|
|
|
36758
|
+
// ../sdk/src/agent/review-service.ts
|
|
36759
|
+
import { execSync } from "node:child_process";
|
|
36760
|
+
|
|
36761
|
+
class ReviewService {
|
|
36762
|
+
deps;
|
|
36763
|
+
constructor(deps) {
|
|
36764
|
+
this.deps = deps;
|
|
36765
|
+
}
|
|
36766
|
+
async reviewStagedChanges(sprint2) {
|
|
36767
|
+
const { projectPath, log } = this.deps;
|
|
36768
|
+
try {
|
|
36769
|
+
execSync("git add -A", { cwd: projectPath, stdio: "pipe" });
|
|
36770
|
+
log("Staged all changes for review.", "info");
|
|
36771
|
+
} catch (err) {
|
|
36772
|
+
log(`Failed to stage changes: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
36773
|
+
return null;
|
|
36774
|
+
}
|
|
36775
|
+
let diff;
|
|
36776
|
+
try {
|
|
36777
|
+
diff = execSync("git diff --cached --stat && echo '---' && git diff --cached", {
|
|
36778
|
+
cwd: projectPath,
|
|
36779
|
+
maxBuffer: 10 * 1024 * 1024
|
|
36780
|
+
}).toString();
|
|
36781
|
+
} catch (err) {
|
|
36782
|
+
log(`Failed to get staged diff: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
36783
|
+
return null;
|
|
36784
|
+
}
|
|
36785
|
+
if (!diff.trim()) {
|
|
36786
|
+
return null;
|
|
36787
|
+
}
|
|
36788
|
+
const sprintInfo = sprint2 ? `Sprint: ${sprint2.name} (${sprint2.id})` : "No active sprint";
|
|
36789
|
+
const reviewPrompt = `# Code Review Request
|
|
36790
|
+
|
|
36791
|
+
## Context
|
|
36792
|
+
${sprintInfo}
|
|
36793
|
+
Date: ${new Date().toISOString()}
|
|
36794
|
+
|
|
36795
|
+
## Staged Changes (git diff)
|
|
36796
|
+
\`\`\`diff
|
|
36797
|
+
${diff}
|
|
36798
|
+
\`\`\`
|
|
36799
|
+
|
|
36800
|
+
## Instructions
|
|
36801
|
+
You are reviewing the staged changes at the end of a sprint. Produce a thorough markdown review report with the following sections:
|
|
36802
|
+
|
|
36803
|
+
1. **Summary** — Brief overview of what changed and why.
|
|
36804
|
+
2. **Files Changed** — List each file with a short description of changes.
|
|
36805
|
+
3. **Code Quality** — Note any code quality concerns (naming, structure, complexity).
|
|
36806
|
+
4. **Potential Issues** — Identify bugs, security issues, edge cases, or regressions.
|
|
36807
|
+
5. **Recommendations** — Actionable suggestions for improvement.
|
|
36808
|
+
6. **Overall Assessment** — A short verdict (e.g., "Looks good", "Needs attention", "Critical issues found").
|
|
36809
|
+
|
|
36810
|
+
Keep the review concise but thorough. Focus on substance over style.
|
|
36811
|
+
Do NOT output <promise>COMPLETE</promise> — just output the review report as markdown.`;
|
|
36812
|
+
log("Running AI review on staged changes...", "info");
|
|
36813
|
+
const report = await this.deps.aiRunner.run(reviewPrompt);
|
|
36814
|
+
return report;
|
|
36815
|
+
}
|
|
36816
|
+
}
|
|
36817
|
+
|
|
36657
36818
|
// ../sdk/src/core/prompt-builder.ts
|
|
36658
36819
|
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
36659
36820
|
import { homedir } from "node:os";
|
|
@@ -37018,29 +37179,8 @@ class TaskExecutor {
|
|
|
37018
37179
|
taskContext: context
|
|
37019
37180
|
});
|
|
37020
37181
|
try {
|
|
37021
|
-
let plan = null;
|
|
37022
|
-
this.deps.log("Phase 1: Planning (CLI)...", "info");
|
|
37023
|
-
const planningPrompt = `${basePrompt}
|
|
37024
|
-
|
|
37025
|
-
## Phase 1: Planning
|
|
37026
|
-
Analyze and create a detailed plan for THIS SPECIFIC TASK. Do NOT execute changes yet.`;
|
|
37027
|
-
plan = await this.deps.aiRunner.run(planningPrompt, true);
|
|
37028
37182
|
this.deps.log("Starting Execution...", "info");
|
|
37029
|
-
|
|
37030
|
-
if (plan != null) {
|
|
37031
|
-
executionPrompt += `
|
|
37032
|
-
|
|
37033
|
-
## Phase 2: Execution
|
|
37034
|
-
Based on the plan, execute the task:
|
|
37035
|
-
|
|
37036
|
-
${plan}`;
|
|
37037
|
-
} else {
|
|
37038
|
-
executionPrompt += `
|
|
37039
|
-
|
|
37040
|
-
## Execution
|
|
37041
|
-
Execute the task directly.`;
|
|
37042
|
-
}
|
|
37043
|
-
executionPrompt += `
|
|
37183
|
+
const executionPrompt = `${basePrompt}
|
|
37044
37184
|
|
|
37045
37185
|
When finished, output: <promise>COMPLETE</promise>`;
|
|
37046
37186
|
const output = await this.deps.aiRunner.run(executionPrompt);
|
|
@@ -37074,11 +37214,9 @@ class AgentWorker {
|
|
|
37074
37214
|
indexerService;
|
|
37075
37215
|
documentFetcher;
|
|
37076
37216
|
taskExecutor;
|
|
37077
|
-
|
|
37078
|
-
maxEmpty = 60;
|
|
37217
|
+
reviewService;
|
|
37079
37218
|
maxTasks = 50;
|
|
37080
37219
|
tasksCompleted = 0;
|
|
37081
|
-
pollInterval = 1e4;
|
|
37082
37220
|
constructor(config2) {
|
|
37083
37221
|
this.config = config2;
|
|
37084
37222
|
const projectPath = config2.projectPath || process.cwd();
|
|
@@ -37115,6 +37253,11 @@ class AgentWorker {
|
|
|
37115
37253
|
projectPath,
|
|
37116
37254
|
log
|
|
37117
37255
|
});
|
|
37256
|
+
this.reviewService = new ReviewService({
|
|
37257
|
+
aiRunner: this.aiRunner,
|
|
37258
|
+
projectPath,
|
|
37259
|
+
log
|
|
37260
|
+
});
|
|
37118
37261
|
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
37119
37262
|
this.log(`Using ${providerLabel} CLI for all phases`, "info");
|
|
37120
37263
|
}
|
|
@@ -37160,33 +37303,42 @@ class AgentWorker {
|
|
|
37160
37303
|
await this.indexerService.reindex();
|
|
37161
37304
|
return result;
|
|
37162
37305
|
}
|
|
37306
|
+
async runStagedChangesReview(sprint2) {
|
|
37307
|
+
try {
|
|
37308
|
+
const report = await this.reviewService.reviewStagedChanges(sprint2);
|
|
37309
|
+
if (report) {
|
|
37310
|
+
const reviewsDir = join6(this.config.projectPath, LOCUS_CONFIG.dir, "reviews");
|
|
37311
|
+
if (!existsSync5(reviewsDir)) {
|
|
37312
|
+
mkdirSync3(reviewsDir, { recursive: true });
|
|
37313
|
+
}
|
|
37314
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
37315
|
+
const sprintSlug = sprint2?.name ? sprint2.name.toLowerCase().replace(/\s+/g, "-").slice(0, 40) : "no-sprint";
|
|
37316
|
+
const fileName = `review-${sprintSlug}-${timestamp}.md`;
|
|
37317
|
+
const filePath = join6(reviewsDir, fileName);
|
|
37318
|
+
writeFileSync3(filePath, report);
|
|
37319
|
+
this.log(`Review report saved to .locus/reviews/${fileName}`, "success");
|
|
37320
|
+
} else {
|
|
37321
|
+
this.log("No staged changes to review.", "info");
|
|
37322
|
+
}
|
|
37323
|
+
} catch (err) {
|
|
37324
|
+
this.log(`Review failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
37325
|
+
}
|
|
37326
|
+
}
|
|
37163
37327
|
async run() {
|
|
37164
37328
|
this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
|
|
37165
37329
|
const sprint2 = await this.getActiveSprint();
|
|
37166
37330
|
if (sprint2) {
|
|
37167
|
-
this.log(`Active sprint found: ${sprint2.name}
|
|
37168
|
-
try {
|
|
37169
|
-
await this.client.sprints.triggerAIPlanning(sprint2.id, this.config.workspaceId);
|
|
37170
|
-
this.log(`Sprint plan sync checked on server.`, "success");
|
|
37171
|
-
} catch (err) {
|
|
37172
|
-
this.log(`Sprint planning sync failed (non-critical): ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
37173
|
-
}
|
|
37331
|
+
this.log(`Active sprint found: ${sprint2.name}`, "info");
|
|
37174
37332
|
} else {
|
|
37175
|
-
this.log("No active sprint found
|
|
37333
|
+
this.log("No active sprint found.", "warn");
|
|
37176
37334
|
}
|
|
37177
|
-
while (this.tasksCompleted < this.maxTasks
|
|
37335
|
+
while (this.tasksCompleted < this.maxTasks) {
|
|
37178
37336
|
const task2 = await this.getNextTask();
|
|
37179
37337
|
if (!task2) {
|
|
37180
|
-
|
|
37181
|
-
|
|
37182
|
-
|
|
37183
|
-
this.consecutiveEmpty++;
|
|
37184
|
-
if (this.consecutiveEmpty >= this.maxEmpty)
|
|
37185
|
-
break;
|
|
37186
|
-
await new Promise((r) => setTimeout(r, this.pollInterval));
|
|
37187
|
-
continue;
|
|
37338
|
+
this.log("No tasks remaining. Running review on staged changes...", "info");
|
|
37339
|
+
await this.runStagedChangesReview(sprint2);
|
|
37340
|
+
break;
|
|
37188
37341
|
}
|
|
37189
|
-
this.consecutiveEmpty = 0;
|
|
37190
37342
|
this.log(`Claimed: ${task2.title}`, "success");
|
|
37191
37343
|
const result = await this.executeTask(task2);
|
|
37192
37344
|
try {
|