@locusai/sdk 0.9.17 → 0.10.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.
Files changed (43) hide show
  1. package/dist/agent/codebase-indexer-service.d.ts.map +1 -1
  2. package/dist/agent/git-workflow.d.ts +44 -0
  3. package/dist/agent/git-workflow.d.ts.map +1 -0
  4. package/dist/agent/index.d.ts +2 -0
  5. package/dist/agent/index.d.ts.map +1 -1
  6. package/dist/agent/reviewer-worker.d.ts.map +1 -1
  7. package/dist/agent/worker-cli.d.ts +6 -0
  8. package/dist/agent/worker-cli.d.ts.map +1 -0
  9. package/dist/agent/worker-types.d.ts +44 -0
  10. package/dist/agent/worker-types.d.ts.map +1 -0
  11. package/dist/agent/worker.d.ts +12 -48
  12. package/dist/agent/worker.d.ts.map +1 -1
  13. package/dist/agent/worker.js +1026 -847
  14. package/dist/ai/claude-runner.d.ts.map +1 -1
  15. package/dist/index-node.d.ts +2 -1
  16. package/dist/index-node.d.ts.map +1 -1
  17. package/dist/index-node.js +1713 -1143
  18. package/dist/index.js +363 -316
  19. package/dist/orchestrator/agent-pool.d.ts +59 -0
  20. package/dist/orchestrator/agent-pool.d.ts.map +1 -0
  21. package/dist/orchestrator/execution.d.ts +55 -0
  22. package/dist/orchestrator/execution.d.ts.map +1 -0
  23. package/dist/orchestrator/index.d.ts +91 -0
  24. package/dist/orchestrator/index.d.ts.map +1 -0
  25. package/dist/orchestrator/tier-merge.d.ts +50 -0
  26. package/dist/orchestrator/tier-merge.d.ts.map +1 -0
  27. package/dist/orchestrator/types.d.ts +45 -0
  28. package/dist/orchestrator/types.d.ts.map +1 -0
  29. package/dist/planning/agents/cross-task-reviewer.d.ts.map +1 -1
  30. package/dist/planning/agents/sprint-organizer.d.ts.map +1 -1
  31. package/dist/planning/plan-manager.d.ts.map +1 -1
  32. package/dist/planning/sprint-plan.d.ts +2 -0
  33. package/dist/planning/sprint-plan.d.ts.map +1 -1
  34. package/dist/project/knowledge-base.d.ts +4 -5
  35. package/dist/project/knowledge-base.d.ts.map +1 -1
  36. package/dist/utils/json-extractor.d.ts +6 -0
  37. package/dist/utils/json-extractor.d.ts.map +1 -0
  38. package/dist/utils/resolve-bin.d.ts +3 -0
  39. package/dist/utils/resolve-bin.d.ts.map +1 -1
  40. package/dist/worktree/worktree-manager.d.ts.map +1 -1
  41. package/package.json +2 -2
  42. package/dist/orchestrator.d.ts +0 -124
  43. package/dist/orchestrator.d.ts.map +0 -1
@@ -38,42 +38,26 @@ var __export = (target, all) => {
38
38
  set: (newValue) => all[name] = () => newValue
39
39
  });
40
40
  };
41
-
42
- // src/index.ts
43
- var exports_src = {};
44
- __export(exports_src, {
45
- WorkspacesModule: () => WorkspacesModule,
46
- TasksModule: () => TasksModule,
47
- SprintsModule: () => SprintsModule,
48
- OrganizationsModule: () => OrganizationsModule,
49
- LocusEvent: () => LocusEvent,
50
- LocusEmitter: () => LocusEmitter,
51
- LocusClient: () => LocusClient,
52
- InvitationsModule: () => InvitationsModule,
53
- DocsModule: () => DocsModule,
54
- CiModule: () => CiModule,
55
- AuthModule: () => AuthModule
56
- });
57
- module.exports = __toCommonJS(exports_src);
58
- var import_axios = __toESM(require("axios"));
41
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
59
42
 
60
43
  // src/events.ts
61
- var import_events = require("events");
62
- var LocusEvent;
63
- ((LocusEvent2) => {
64
- LocusEvent2["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
65
- LocusEvent2["AUTH_ERROR"] = "AUTH_ERROR";
66
- LocusEvent2["REQUEST_ERROR"] = "REQUEST_ERROR";
67
- })(LocusEvent ||= {});
68
-
69
- class LocusEmitter extends import_events.EventEmitter {
70
- on(event, listener) {
71
- return super.on(event, listener);
72
- }
73
- emit(event, ...args) {
74
- return super.emit(event, ...args);
75
- }
76
- }
44
+ var import_events, LocusEvent, LocusEmitter;
45
+ var init_events = __esm(() => {
46
+ import_events = require("events");
47
+ ((LocusEvent2) => {
48
+ LocusEvent2["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
49
+ LocusEvent2["AUTH_ERROR"] = "AUTH_ERROR";
50
+ LocusEvent2["REQUEST_ERROR"] = "REQUEST_ERROR";
51
+ })(LocusEvent ||= {});
52
+ LocusEmitter = class LocusEmitter extends import_events.EventEmitter {
53
+ on(event, listener) {
54
+ return super.on(event, listener);
55
+ }
56
+ emit(event, ...args) {
57
+ return super.emit(event, ...args);
58
+ }
59
+ };
60
+ });
77
61
 
78
62
  // src/modules/base.ts
79
63
  class BaseModule {
@@ -86,304 +70,344 @@ class BaseModule {
86
70
  }
87
71
 
88
72
  // src/modules/auth.ts
89
- class AuthModule extends BaseModule {
90
- async getProfile() {
91
- const { data } = await this.api.get("/auth/me");
92
- return data;
93
- }
94
- async getApiKeyInfo() {
95
- const { data } = await this.api.get("/auth/api-key");
96
- return data;
97
- }
98
- async requestRegisterOtp(email) {
99
- const { data } = await this.api.post("/auth/register-otp", { email });
100
- return data;
101
- }
102
- async requestLoginOtp(email) {
103
- const { data } = await this.api.post("/auth/login-otp", { email });
104
- return data;
105
- }
106
- async verifyLogin(body) {
107
- const { data } = await this.api.post("/auth/verify-login", body);
108
- return data;
109
- }
110
- async completeRegistration(body) {
111
- const { data } = await this.api.post("/auth/complete-registration", body);
112
- return data;
113
- }
114
- async deleteAccount() {
115
- const { data } = await this.api.delete("/auth/account");
116
- return data;
117
- }
118
- }
73
+ var AuthModule;
74
+ var init_auth = __esm(() => {
75
+ AuthModule = class AuthModule extends BaseModule {
76
+ async getProfile() {
77
+ const { data } = await this.api.get("/auth/me");
78
+ return data;
79
+ }
80
+ async getApiKeyInfo() {
81
+ const { data } = await this.api.get("/auth/api-key");
82
+ return data;
83
+ }
84
+ async requestRegisterOtp(email) {
85
+ const { data } = await this.api.post("/auth/register-otp", { email });
86
+ return data;
87
+ }
88
+ async requestLoginOtp(email) {
89
+ const { data } = await this.api.post("/auth/login-otp", { email });
90
+ return data;
91
+ }
92
+ async verifyLogin(body) {
93
+ const { data } = await this.api.post("/auth/verify-login", body);
94
+ return data;
95
+ }
96
+ async completeRegistration(body) {
97
+ const { data } = await this.api.post("/auth/complete-registration", body);
98
+ return data;
99
+ }
100
+ async deleteAccount() {
101
+ const { data } = await this.api.delete("/auth/account");
102
+ return data;
103
+ }
104
+ };
105
+ });
119
106
 
120
107
  // src/modules/ci.ts
121
- class CiModule extends BaseModule {
122
- async report(body) {
123
- const { data } = await this.api.post("/ci/report", body);
124
- return data;
125
- }
126
- }
108
+ var CiModule;
109
+ var init_ci = __esm(() => {
110
+ CiModule = class CiModule extends BaseModule {
111
+ async report(body) {
112
+ const { data } = await this.api.post("/ci/report", body);
113
+ return data;
114
+ }
115
+ };
116
+ });
127
117
 
128
118
  // src/modules/docs.ts
129
- class DocsModule extends BaseModule {
130
- async create(workspaceId, body) {
131
- const { data } = await this.api.post(`/workspaces/${workspaceId}/docs`, body);
132
- return data.doc;
133
- }
134
- async list(workspaceId) {
135
- const { data } = await this.api.get(`/workspaces/${workspaceId}/docs`);
136
- return data.docs;
137
- }
138
- async getById(id, workspaceId) {
139
- const { data } = await this.api.get(`/workspaces/${workspaceId}/docs/${id}`);
140
- return data.doc;
141
- }
142
- async update(id, workspaceId, body) {
143
- const { data } = await this.api.put(`/workspaces/${workspaceId}/docs/${id}`, body);
144
- return data.doc;
145
- }
146
- async delete(id, workspaceId) {
147
- await this.api.delete(`/workspaces/${workspaceId}/docs/${id}`);
148
- }
149
- async listGroups(workspaceId) {
150
- const { data } = await this.api.get(`/workspaces/${workspaceId}/doc-groups`);
151
- return data.groups;
152
- }
153
- async createGroup(workspaceId, body) {
154
- const { data } = await this.api.post(`/workspaces/${workspaceId}/doc-groups`, body);
155
- return data.group;
156
- }
157
- async updateGroup(id, workspaceId, body) {
158
- const { data } = await this.api.patch(`/workspaces/${workspaceId}/doc-groups/${id}`, body);
159
- return data.group;
160
- }
161
- async deleteGroup(id, workspaceId) {
162
- await this.api.delete(`/workspaces/${workspaceId}/doc-groups/${id}`);
163
- }
164
- }
119
+ var DocsModule;
120
+ var init_docs = __esm(() => {
121
+ DocsModule = class DocsModule extends BaseModule {
122
+ async create(workspaceId, body) {
123
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/docs`, body);
124
+ return data.doc;
125
+ }
126
+ async list(workspaceId) {
127
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/docs`);
128
+ return data.docs;
129
+ }
130
+ async getById(id, workspaceId) {
131
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/docs/${id}`);
132
+ return data.doc;
133
+ }
134
+ async update(id, workspaceId, body) {
135
+ const { data } = await this.api.put(`/workspaces/${workspaceId}/docs/${id}`, body);
136
+ return data.doc;
137
+ }
138
+ async delete(id, workspaceId) {
139
+ await this.api.delete(`/workspaces/${workspaceId}/docs/${id}`);
140
+ }
141
+ async listGroups(workspaceId) {
142
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/doc-groups`);
143
+ return data.groups;
144
+ }
145
+ async createGroup(workspaceId, body) {
146
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/doc-groups`, body);
147
+ return data.group;
148
+ }
149
+ async updateGroup(id, workspaceId, body) {
150
+ const { data } = await this.api.patch(`/workspaces/${workspaceId}/doc-groups/${id}`, body);
151
+ return data.group;
152
+ }
153
+ async deleteGroup(id, workspaceId) {
154
+ await this.api.delete(`/workspaces/${workspaceId}/doc-groups/${id}`);
155
+ }
156
+ };
157
+ });
165
158
 
166
159
  // src/modules/invitations.ts
167
- class InvitationsModule extends BaseModule {
168
- async create(orgId, body) {
169
- const { data } = await this.api.post(`/org/${orgId}/invitations`, body);
170
- return data.invitation;
171
- }
172
- async list(orgId) {
173
- const { data } = await this.api.get(`/org/${orgId}/invitations`);
174
- return data.invitations;
175
- }
176
- async verify(token) {
177
- const { data } = await this.api.get(`/invitations/verify/${token}`);
178
- return data;
179
- }
180
- async accept(body) {
181
- const { data } = await this.api.post("/invitations/accept", body);
182
- return data;
183
- }
184
- async revoke(orgId, id) {
185
- await this.api.delete(`/org/${orgId}/invitations/${id}`);
186
- }
187
- }
160
+ var InvitationsModule;
161
+ var init_invitations = __esm(() => {
162
+ InvitationsModule = class InvitationsModule extends BaseModule {
163
+ async create(orgId, body) {
164
+ const { data } = await this.api.post(`/org/${orgId}/invitations`, body);
165
+ return data.invitation;
166
+ }
167
+ async list(orgId) {
168
+ const { data } = await this.api.get(`/org/${orgId}/invitations`);
169
+ return data.invitations;
170
+ }
171
+ async verify(token) {
172
+ const { data } = await this.api.get(`/invitations/verify/${token}`);
173
+ return data;
174
+ }
175
+ async accept(body) {
176
+ const { data } = await this.api.post("/invitations/accept", body);
177
+ return data;
178
+ }
179
+ async revoke(orgId, id) {
180
+ await this.api.delete(`/org/${orgId}/invitations/${id}`);
181
+ }
182
+ };
183
+ });
188
184
 
189
185
  // src/modules/organizations.ts
190
- class OrganizationsModule extends BaseModule {
191
- async list() {
192
- const { data } = await this.api.get("/organizations");
193
- return data.organizations;
194
- }
195
- async getById(id) {
196
- const { data } = await this.api.get(`/organizations/${id}`);
197
- return data.organization;
198
- }
199
- async listMembers(id) {
200
- const { data } = await this.api.get(`/organizations/${id}/members`);
201
- return data.members;
202
- }
203
- async addMember(id, body) {
204
- const { data } = await this.api.post(`/organizations/${id}/members`, body);
205
- return data.membership;
206
- }
207
- async removeMember(orgId, userId) {
208
- await this.api.delete(`/organizations/${orgId}/members/${userId}`);
209
- }
210
- async delete(orgId) {
211
- await this.api.delete(`/organizations/${orgId}`);
212
- }
213
- async listApiKeys(orgId) {
214
- const { data } = await this.api.get(`/organizations/${orgId}/api-keys`);
215
- return data.apiKeys;
216
- }
217
- async createApiKey(orgId, name) {
218
- const { data } = await this.api.post(`/organizations/${orgId}/api-keys`, { name });
219
- return data.apiKey;
220
- }
221
- async deleteApiKey(orgId, keyId) {
222
- await this.api.delete(`/organizations/${orgId}/api-keys/${keyId}`);
223
- }
224
- }
186
+ var OrganizationsModule;
187
+ var init_organizations = __esm(() => {
188
+ OrganizationsModule = class OrganizationsModule extends BaseModule {
189
+ async list() {
190
+ const { data } = await this.api.get("/organizations");
191
+ return data.organizations;
192
+ }
193
+ async getById(id) {
194
+ const { data } = await this.api.get(`/organizations/${id}`);
195
+ return data.organization;
196
+ }
197
+ async listMembers(id) {
198
+ const { data } = await this.api.get(`/organizations/${id}/members`);
199
+ return data.members;
200
+ }
201
+ async addMember(id, body) {
202
+ const { data } = await this.api.post(`/organizations/${id}/members`, body);
203
+ return data.membership;
204
+ }
205
+ async removeMember(orgId, userId) {
206
+ await this.api.delete(`/organizations/${orgId}/members/${userId}`);
207
+ }
208
+ async delete(orgId) {
209
+ await this.api.delete(`/organizations/${orgId}`);
210
+ }
211
+ async listApiKeys(orgId) {
212
+ const { data } = await this.api.get(`/organizations/${orgId}/api-keys`);
213
+ return data.apiKeys;
214
+ }
215
+ async createApiKey(orgId, name) {
216
+ const { data } = await this.api.post(`/organizations/${orgId}/api-keys`, { name });
217
+ return data.apiKey;
218
+ }
219
+ async deleteApiKey(orgId, keyId) {
220
+ await this.api.delete(`/organizations/${orgId}/api-keys/${keyId}`);
221
+ }
222
+ };
223
+ });
225
224
 
226
225
  // src/modules/sprints.ts
227
- class SprintsModule extends BaseModule {
228
- async list(workspaceId) {
229
- const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints`);
230
- return data.sprints;
231
- }
232
- async getActive(workspaceId) {
233
- const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints/active`);
234
- return data.sprint;
235
- }
236
- async getById(id, workspaceId) {
237
- const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints/${id}`);
238
- return data.sprint;
239
- }
240
- async create(workspaceId, body) {
241
- const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints`, body);
242
- return data.sprint;
243
- }
244
- async update(id, workspaceId, body) {
245
- const { data } = await this.api.patch(`/workspaces/${workspaceId}/sprints/${id}`, body);
246
- return data.sprint;
247
- }
248
- async delete(id, workspaceId) {
249
- await this.api.delete(`/workspaces/${workspaceId}/sprints/${id}`);
250
- }
251
- async start(id, workspaceId) {
252
- const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/start`);
253
- return data.sprint;
254
- }
255
- async complete(id, workspaceId) {
256
- const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/complete`);
257
- return data.sprint;
258
- }
259
- async triggerAIPlanning(id, workspaceId) {
260
- const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/trigger-ai-planning`);
261
- return data.sprint;
262
- }
263
- }
226
+ var SprintsModule;
227
+ var init_sprints = __esm(() => {
228
+ SprintsModule = class SprintsModule extends BaseModule {
229
+ async list(workspaceId) {
230
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints`);
231
+ return data.sprints;
232
+ }
233
+ async getActive(workspaceId) {
234
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints/active`);
235
+ return data.sprint;
236
+ }
237
+ async getById(id, workspaceId) {
238
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints/${id}`);
239
+ return data.sprint;
240
+ }
241
+ async create(workspaceId, body) {
242
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints`, body);
243
+ return data.sprint;
244
+ }
245
+ async update(id, workspaceId, body) {
246
+ const { data } = await this.api.patch(`/workspaces/${workspaceId}/sprints/${id}`, body);
247
+ return data.sprint;
248
+ }
249
+ async delete(id, workspaceId) {
250
+ await this.api.delete(`/workspaces/${workspaceId}/sprints/${id}`);
251
+ }
252
+ async start(id, workspaceId) {
253
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/start`);
254
+ return data.sprint;
255
+ }
256
+ async complete(id, workspaceId) {
257
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/complete`);
258
+ return data.sprint;
259
+ }
260
+ async triggerAIPlanning(id, workspaceId) {
261
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/trigger-ai-planning`);
262
+ return data.sprint;
263
+ }
264
+ };
265
+ });
264
266
 
265
267
  // src/modules/tasks.ts
266
- var import_shared = require("@locusai/shared");
267
- class TasksModule extends BaseModule {
268
- async list(workspaceId, options) {
269
- const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks`);
270
- let tasks = data.tasks;
271
- if (options?.sprintId) {
272
- tasks = tasks.filter((t) => t.sprintId === options.sprintId);
273
- }
274
- if (options?.status) {
275
- const statuses = Array.isArray(options.status) ? options.status : [options.status];
276
- tasks = tasks.filter((t) => statuses.includes(t.status));
277
- }
278
- return tasks;
279
- }
280
- async getAvailable(workspaceId, sprintId) {
281
- const tasks = await this.list(workspaceId, {
282
- sprintId
283
- });
284
- return tasks.filter((t) => t.status === import_shared.TaskStatus.BACKLOG || t.status === import_shared.TaskStatus.IN_PROGRESS && !t.assignedTo);
285
- }
286
- async getById(id, workspaceId) {
287
- const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks/${id}`);
288
- return data.task;
289
- }
290
- async create(workspaceId, body) {
291
- const { data } = await this.api.post(`/workspaces/${workspaceId}/tasks`, body);
292
- return data.task;
293
- }
294
- async update(id, workspaceId, body) {
295
- const { data } = await this.api.patch(`/workspaces/${workspaceId}/tasks/${id}`, body);
296
- return data.task;
297
- }
298
- async delete(id, workspaceId) {
299
- await this.api.delete(`/workspaces/${workspaceId}/tasks/${id}`);
300
- }
301
- async getBacklog(workspaceId) {
302
- const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks/backlog`);
303
- return data.tasks;
304
- }
305
- async addComment(id, workspaceId, body) {
306
- const { data } = await this.api.post(`/workspaces/${workspaceId}/tasks/${id}/comment`, body);
307
- return data.comment;
308
- }
309
- async batchUpdate(ids, workspaceId, updates) {
310
- await this.api.patch(`/workspaces/${workspaceId}/tasks/batch`, {
311
- ids,
312
- updates
313
- });
314
- }
315
- }
268
+ var import_shared, TasksModule;
269
+ var init_tasks = __esm(() => {
270
+ import_shared = require("@locusai/shared");
271
+ TasksModule = class TasksModule extends BaseModule {
272
+ async list(workspaceId, options) {
273
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks`);
274
+ let tasks = data.tasks;
275
+ if (options?.sprintId) {
276
+ tasks = tasks.filter((t) => t.sprintId === options.sprintId);
277
+ }
278
+ if (options?.status) {
279
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
280
+ tasks = tasks.filter((t) => statuses.includes(t.status));
281
+ }
282
+ return tasks;
283
+ }
284
+ async getAvailable(workspaceId, sprintId) {
285
+ const tasks = await this.list(workspaceId, {
286
+ sprintId
287
+ });
288
+ return tasks.filter((t) => t.status === import_shared.TaskStatus.BACKLOG || t.status === import_shared.TaskStatus.IN_PROGRESS && !t.assignedTo);
289
+ }
290
+ async getById(id, workspaceId) {
291
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks/${id}`);
292
+ return data.task;
293
+ }
294
+ async create(workspaceId, body) {
295
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/tasks`, body);
296
+ return data.task;
297
+ }
298
+ async update(id, workspaceId, body) {
299
+ const { data } = await this.api.patch(`/workspaces/${workspaceId}/tasks/${id}`, body);
300
+ return data.task;
301
+ }
302
+ async delete(id, workspaceId) {
303
+ await this.api.delete(`/workspaces/${workspaceId}/tasks/${id}`);
304
+ }
305
+ async getBacklog(workspaceId) {
306
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks/backlog`);
307
+ return data.tasks;
308
+ }
309
+ async addComment(id, workspaceId, body) {
310
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/tasks/${id}/comment`, body);
311
+ return data.comment;
312
+ }
313
+ async batchUpdate(ids, workspaceId, updates) {
314
+ await this.api.patch(`/workspaces/${workspaceId}/tasks/batch`, {
315
+ ids,
316
+ updates
317
+ });
318
+ }
319
+ };
320
+ });
316
321
 
317
322
  // src/modules/workspaces.ts
318
- class WorkspacesModule extends BaseModule {
319
- async listAll() {
320
- const { data } = await this.api.get("/workspaces");
321
- return data.workspaces;
322
- }
323
- async listByOrg(orgId) {
324
- const { data } = await this.api.get(`/workspaces/org/${orgId}`);
325
- return data.workspaces;
326
- }
327
- async create(body) {
328
- const { orgId, ...bodyWithoutOrgId } = body;
329
- const { data } = await this.api.post(`/workspaces/org/${orgId}`, bodyWithoutOrgId);
330
- return data.workspace;
331
- }
332
- async createWithAutoOrg(body) {
333
- const { data } = await this.api.post("/workspaces", body);
334
- return data.workspace;
335
- }
336
- async getById(id) {
337
- const { data } = await this.api.get(`/workspaces/${id}`);
338
- return data.workspace;
339
- }
340
- async update(id, body) {
341
- const { data } = await this.api.put(`/workspaces/${id}`, body);
342
- return data.workspace;
343
- }
344
- async delete(id) {
345
- await this.api.delete(`/workspaces/${id}`);
346
- }
347
- async getStats(id) {
348
- const { data } = await this.api.get(`/workspaces/${id}/stats`);
349
- return data;
350
- }
351
- async getActivity(id, limit) {
352
- const { data } = await this.api.get(`/workspaces/${id}/activity`, {
353
- params: { limit }
354
- });
355
- return data.activity;
356
- }
357
- async dispatch(id, workerId, sprintId) {
358
- const { data } = await this.api.post(`/workspaces/${id}/dispatch`, { workerId, sprintId });
359
- return data.task;
360
- }
361
- async heartbeat(workspaceId, agentId, currentTaskId, status) {
362
- const { data } = await this.api.post(`/workspaces/${workspaceId}/agents/heartbeat`, {
363
- agentId,
364
- currentTaskId: currentTaskId ?? null,
365
- status: status ?? "WORKING"
366
- });
367
- return data.agent;
368
- }
369
- async getAgents(workspaceId) {
370
- const { data } = await this.api.get(`/workspaces/${workspaceId}/agents`);
371
- return data.agents;
372
- }
373
- async listApiKeys(workspaceId) {
374
- const { data } = await this.api.get(`/workspaces/${workspaceId}/api-keys`);
375
- return data.apiKeys;
376
- }
377
- async createApiKey(workspaceId, name) {
378
- const { data } = await this.api.post(`/workspaces/${workspaceId}/api-keys`, { name });
379
- return data.apiKey;
380
- }
381
- async deleteApiKey(workspaceId, keyId) {
382
- await this.api.delete(`/workspaces/${workspaceId}/api-keys/${keyId}`);
383
- }
384
- }
323
+ var WorkspacesModule;
324
+ var init_workspaces = __esm(() => {
325
+ WorkspacesModule = class WorkspacesModule extends BaseModule {
326
+ async listAll() {
327
+ const { data } = await this.api.get("/workspaces");
328
+ return data.workspaces;
329
+ }
330
+ async listByOrg(orgId) {
331
+ const { data } = await this.api.get(`/workspaces/org/${orgId}`);
332
+ return data.workspaces;
333
+ }
334
+ async create(body) {
335
+ const { orgId, ...bodyWithoutOrgId } = body;
336
+ const { data } = await this.api.post(`/workspaces/org/${orgId}`, bodyWithoutOrgId);
337
+ return data.workspace;
338
+ }
339
+ async createWithAutoOrg(body) {
340
+ const { data } = await this.api.post("/workspaces", body);
341
+ return data.workspace;
342
+ }
343
+ async getById(id) {
344
+ const { data } = await this.api.get(`/workspaces/${id}`);
345
+ return data.workspace;
346
+ }
347
+ async update(id, body) {
348
+ const { data } = await this.api.put(`/workspaces/${id}`, body);
349
+ return data.workspace;
350
+ }
351
+ async delete(id) {
352
+ await this.api.delete(`/workspaces/${id}`);
353
+ }
354
+ async getStats(id) {
355
+ const { data } = await this.api.get(`/workspaces/${id}/stats`);
356
+ return data;
357
+ }
358
+ async getActivity(id, limit) {
359
+ const { data } = await this.api.get(`/workspaces/${id}/activity`, {
360
+ params: { limit }
361
+ });
362
+ return data.activity;
363
+ }
364
+ async dispatch(id, workerId, sprintId) {
365
+ const { data } = await this.api.post(`/workspaces/${id}/dispatch`, { workerId, sprintId });
366
+ return data.task;
367
+ }
368
+ async heartbeat(workspaceId, agentId, currentTaskId, status) {
369
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/agents/heartbeat`, {
370
+ agentId,
371
+ currentTaskId: currentTaskId ?? null,
372
+ status: status ?? "WORKING"
373
+ });
374
+ return data.agent;
375
+ }
376
+ async getAgents(workspaceId) {
377
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/agents`);
378
+ return data.agents;
379
+ }
380
+ async listApiKeys(workspaceId) {
381
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/api-keys`);
382
+ return data.apiKeys;
383
+ }
384
+ async createApiKey(workspaceId, name) {
385
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/api-keys`, { name });
386
+ return data.apiKey;
387
+ }
388
+ async deleteApiKey(workspaceId, keyId) {
389
+ await this.api.delete(`/workspaces/${workspaceId}/api-keys/${keyId}`);
390
+ }
391
+ };
392
+ });
385
393
 
386
394
  // src/index.ts
395
+ var exports_src = {};
396
+ __export(exports_src, {
397
+ WorkspacesModule: () => WorkspacesModule,
398
+ TasksModule: () => TasksModule,
399
+ SprintsModule: () => SprintsModule,
400
+ OrganizationsModule: () => OrganizationsModule,
401
+ LocusEvent: () => LocusEvent,
402
+ LocusEmitter: () => LocusEmitter,
403
+ LocusClient: () => LocusClient,
404
+ InvitationsModule: () => InvitationsModule,
405
+ DocsModule: () => DocsModule,
406
+ CiModule: () => CiModule,
407
+ AuthModule: () => AuthModule
408
+ });
409
+ module.exports = __toCommonJS(exports_src);
410
+
387
411
  class LocusClient {
388
412
  api;
389
413
  emitter;
@@ -473,70 +497,30 @@ class LocusClient {
473
497
  }
474
498
  }
475
499
  }
476
-
477
- // src/agent/worker.ts
478
- var exports_worker = {};
479
- __export(exports_worker, {
480
- AgentWorker: () => AgentWorker
500
+ var import_axios;
501
+ var init_src = __esm(() => {
502
+ init_events();
503
+ init_auth();
504
+ init_ci();
505
+ init_docs();
506
+ init_invitations();
507
+ init_organizations();
508
+ init_sprints();
509
+ init_tasks();
510
+ init_workspaces();
511
+ import_axios = __toESM(require("axios"));
512
+ init_events();
513
+ init_auth();
514
+ init_ci();
515
+ init_docs();
516
+ init_invitations();
517
+ init_organizations();
518
+ init_sprints();
519
+ init_tasks();
520
+ init_workspaces();
481
521
  });
482
- module.exports = __toCommonJS(exports_worker);
483
- var import_shared3 = require("@locusai/shared");
484
522
 
485
523
  // src/core/config.ts
486
- var import_node_path = require("node:path");
487
- var PROVIDER = {
488
- CLAUDE: "claude",
489
- CODEX: "codex"
490
- };
491
- var DEFAULT_MODEL = {
492
- [PROVIDER.CLAUDE]: "opus",
493
- [PROVIDER.CODEX]: "gpt-5.3-codex"
494
- };
495
- var LOCUS_SCHEMA_BASE_URL = "https://locusai.dev/schemas";
496
- var LOCUS_SCHEMAS = {
497
- config: `${LOCUS_SCHEMA_BASE_URL}/config.schema.json`,
498
- settings: `${LOCUS_SCHEMA_BASE_URL}/settings.schema.json`
499
- };
500
- var LOCUS_CONFIG = {
501
- dir: ".locus",
502
- configFile: "config.json",
503
- settingsFile: "settings.json",
504
- indexFile: "codebase-index.json",
505
- contextFile: "LOCUS.md",
506
- artifactsDir: "artifacts",
507
- documentsDir: "documents",
508
- sessionsDir: "sessions",
509
- reviewsDir: "reviews",
510
- plansDir: "plans",
511
- projectDir: "project",
512
- projectContextFile: "context.md",
513
- projectProgressFile: "progress.md"
514
- };
515
- var LOCUS_GITIGNORE_PATTERNS = [
516
- "# Locus AI - Session data (user-specific, can grow large)",
517
- ".locus/sessions/",
518
- "",
519
- "# Locus AI - Artifacts (local-only, user-specific)",
520
- ".locus/artifacts/",
521
- "",
522
- "# Locus AI - Review reports (generated per sprint)",
523
- ".locus/reviews/",
524
- "",
525
- "# Locus AI - Plans (generated per task)",
526
- ".locus/plans/",
527
- "",
528
- "# Locus AI - Agent worktrees (parallel execution)",
529
- ".locus-worktrees/",
530
- "",
531
- "# Locus AI - Settings (contains API key, telegram config, etc.)",
532
- ".locus/settings.json",
533
- "",
534
- "# Locus AI - Configuration (contains project context, progress, etc.)",
535
- ".locus/config.json",
536
- "",
537
- "# Locus AI - Project progress (contains project progress, etc.)",
538
- ".locus/project/progress.md"
539
- ];
540
524
  function getLocusPath(projectPath, fileName) {
541
525
  if (fileName === "projectContextFile" || fileName === "projectProgressFile") {
542
526
  return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.projectDir, LOCUS_CONFIG[fileName]);
@@ -547,123 +531,235 @@ function getAgentArtifactsPath(projectPath, agentId) {
547
531
  const shortId = agentId.slice(-8);
548
532
  return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.artifactsDir, shortId);
549
533
  }
550
-
551
- // src/ai/claude-runner.ts
552
- var import_node_child_process = require("node:child_process");
553
- var import_node_path3 = require("node:path");
534
+ var import_node_path, PROVIDER, DEFAULT_MODEL, LOCUS_SCHEMA_BASE_URL = "https://locusai.dev/schemas", LOCUS_SCHEMAS, LOCUS_CONFIG, LOCUS_GITIGNORE_PATTERNS;
535
+ var init_config = __esm(() => {
536
+ import_node_path = require("node:path");
537
+ PROVIDER = {
538
+ CLAUDE: "claude",
539
+ CODEX: "codex"
540
+ };
541
+ DEFAULT_MODEL = {
542
+ [PROVIDER.CLAUDE]: "opus",
543
+ [PROVIDER.CODEX]: "gpt-5.3-codex"
544
+ };
545
+ LOCUS_SCHEMAS = {
546
+ config: `${LOCUS_SCHEMA_BASE_URL}/config.schema.json`,
547
+ settings: `${LOCUS_SCHEMA_BASE_URL}/settings.schema.json`
548
+ };
549
+ LOCUS_CONFIG = {
550
+ dir: ".locus",
551
+ configFile: "config.json",
552
+ settingsFile: "settings.json",
553
+ indexFile: "codebase-index.json",
554
+ contextFile: "LOCUS.md",
555
+ artifactsDir: "artifacts",
556
+ documentsDir: "documents",
557
+ sessionsDir: "sessions",
558
+ reviewsDir: "reviews",
559
+ plansDir: "plans",
560
+ projectDir: "project",
561
+ projectContextFile: "context.md",
562
+ projectProgressFile: "progress.md"
563
+ };
564
+ LOCUS_GITIGNORE_PATTERNS = [
565
+ "# Locus AI - Session data (user-specific, can grow large)",
566
+ ".locus/sessions/",
567
+ "",
568
+ "# Locus AI - Artifacts (local-only, user-specific)",
569
+ ".locus/artifacts/",
570
+ "",
571
+ "# Locus AI - Review reports (generated per sprint)",
572
+ ".locus/reviews/",
573
+ "",
574
+ "# Locus AI - Plans (generated per task)",
575
+ ".locus/plans/",
576
+ "",
577
+ "# Locus AI - Agent worktrees (parallel execution)",
578
+ ".locus-worktrees/",
579
+ "",
580
+ "# Locus AI - Settings (contains API key, telegram config, etc.)",
581
+ ".locus/settings.json",
582
+ "",
583
+ "# Locus AI - Configuration (contains project context, progress, etc.)",
584
+ ".locus/config.json",
585
+ "",
586
+ "# Locus AI - Project progress (contains project progress, etc.)",
587
+ ".locus/project/progress.md"
588
+ ];
589
+ });
554
590
 
555
591
  // src/utils/colors.ts
556
- var ESC = "\x1B[";
557
- var RESET = `${ESC}0m`;
558
- var colors = {
559
- reset: RESET,
560
- bold: `${ESC}1m`,
561
- dim: `${ESC}2m`,
562
- italic: `${ESC}3m`,
563
- underline: `${ESC}4m`,
564
- black: `${ESC}30m`,
565
- red: `${ESC}31m`,
566
- green: `${ESC}32m`,
567
- yellow: `${ESC}33m`,
568
- blue: `${ESC}34m`,
569
- magenta: `${ESC}35m`,
570
- cyan: `${ESC}36m`,
571
- white: `${ESC}37m`,
572
- gray: `${ESC}90m`,
573
- brightRed: `${ESC}91m`,
574
- brightGreen: `${ESC}92m`,
575
- brightYellow: `${ESC}93m`,
576
- brightBlue: `${ESC}94m`,
577
- brightMagenta: `${ESC}95m`,
578
- brightCyan: `${ESC}96m`,
579
- brightWhite: `${ESC}97m`,
580
- bgBlack: `${ESC}40m`,
581
- bgRed: `${ESC}41m`,
582
- bgGreen: `${ESC}42m`,
583
- bgYellow: `${ESC}43m`,
584
- bgBlue: `${ESC}44m`,
585
- bgMagenta: `${ESC}45m`,
586
- bgCyan: `${ESC}46m`,
587
- bgWhite: `${ESC}47m`
588
- };
589
- var c = {
590
- text: (text, ...colorNames) => {
591
- const codes = colorNames.map((name) => colors[name]).join("");
592
- return `${codes}${text}${RESET}`;
593
- },
594
- bold: (t) => c.text(t, "bold"),
595
- dim: (t) => c.text(t, "dim"),
596
- red: (t) => c.text(t, "red"),
597
- green: (t) => c.text(t, "green"),
598
- yellow: (t) => c.text(t, "yellow"),
599
- blue: (t) => c.text(t, "blue"),
600
- magenta: (t) => c.text(t, "magenta"),
601
- cyan: (t) => c.text(t, "cyan"),
602
- gray: (t) => c.text(t, "gray"),
603
- white: (t) => c.text(t, "white"),
604
- brightBlue: (t) => c.text(t, "brightBlue"),
605
- bgBlue: (t) => c.text(t, "bgBlue", "white", "bold"),
606
- success: (t) => c.text(t, "green", "bold"),
607
- error: (t) => c.text(t, "red", "bold"),
608
- warning: (t) => c.text(t, "yellow", "bold"),
609
- info: (t) => c.text(t, "cyan", "bold"),
610
- primary: (t) => c.text(t, "blue", "bold"),
611
- secondary: (t) => c.text(t, "magenta", "bold"),
612
- header: (t) => c.text(` ${t} `, "bgBlue", "white", "bold"),
613
- step: (t) => c.text(` ${t} `, "bgCyan", "black", "bold"),
614
- underline: (t) => c.text(t, "underline")
615
- };
592
+ var ESC = "\x1B[", RESET, colors, c;
593
+ var init_colors = __esm(() => {
594
+ RESET = `${ESC}0m`;
595
+ colors = {
596
+ reset: RESET,
597
+ bold: `${ESC}1m`,
598
+ dim: `${ESC}2m`,
599
+ italic: `${ESC}3m`,
600
+ underline: `${ESC}4m`,
601
+ black: `${ESC}30m`,
602
+ red: `${ESC}31m`,
603
+ green: `${ESC}32m`,
604
+ yellow: `${ESC}33m`,
605
+ blue: `${ESC}34m`,
606
+ magenta: `${ESC}35m`,
607
+ cyan: `${ESC}36m`,
608
+ white: `${ESC}37m`,
609
+ gray: `${ESC}90m`,
610
+ brightRed: `${ESC}91m`,
611
+ brightGreen: `${ESC}92m`,
612
+ brightYellow: `${ESC}93m`,
613
+ brightBlue: `${ESC}94m`,
614
+ brightMagenta: `${ESC}95m`,
615
+ brightCyan: `${ESC}96m`,
616
+ brightWhite: `${ESC}97m`,
617
+ bgBlack: `${ESC}40m`,
618
+ bgRed: `${ESC}41m`,
619
+ bgGreen: `${ESC}42m`,
620
+ bgYellow: `${ESC}43m`,
621
+ bgBlue: `${ESC}44m`,
622
+ bgMagenta: `${ESC}45m`,
623
+ bgCyan: `${ESC}46m`,
624
+ bgWhite: `${ESC}47m`
625
+ };
626
+ c = {
627
+ text: (text, ...colorNames) => {
628
+ const codes = colorNames.map((name) => colors[name]).join("");
629
+ return `${codes}${text}${RESET}`;
630
+ },
631
+ bold: (t) => c.text(t, "bold"),
632
+ dim: (t) => c.text(t, "dim"),
633
+ red: (t) => c.text(t, "red"),
634
+ green: (t) => c.text(t, "green"),
635
+ yellow: (t) => c.text(t, "yellow"),
636
+ blue: (t) => c.text(t, "blue"),
637
+ magenta: (t) => c.text(t, "magenta"),
638
+ cyan: (t) => c.text(t, "cyan"),
639
+ gray: (t) => c.text(t, "gray"),
640
+ white: (t) => c.text(t, "white"),
641
+ brightBlue: (t) => c.text(t, "brightBlue"),
642
+ bgBlue: (t) => c.text(t, "bgBlue", "white", "bold"),
643
+ success: (t) => c.text(t, "green", "bold"),
644
+ error: (t) => c.text(t, "red", "bold"),
645
+ warning: (t) => c.text(t, "yellow", "bold"),
646
+ info: (t) => c.text(t, "cyan", "bold"),
647
+ primary: (t) => c.text(t, "blue", "bold"),
648
+ secondary: (t) => c.text(t, "magenta", "bold"),
649
+ header: (t) => c.text(` ${t} `, "bgBlue", "white", "bold"),
650
+ step: (t) => c.text(` ${t} `, "bgCyan", "black", "bold"),
651
+ underline: (t) => c.text(t, "underline")
652
+ };
653
+ });
616
654
 
617
655
  // src/utils/resolve-bin.ts
618
- var import_node_fs = require("node:fs");
619
- var import_node_os = require("node:os");
620
- var import_node_path2 = require("node:path");
621
- var EXTRA_BIN_DIRS = [
622
- import_node_path2.join(import_node_os.homedir(), ".local", "bin"),
623
- import_node_path2.join(import_node_os.homedir(), ".npm", "bin"),
624
- import_node_path2.join(import_node_os.homedir(), ".npm-global", "bin"),
625
- import_node_path2.join(import_node_os.homedir(), ".yarn", "bin"),
626
- "/usr/local/bin"
627
- ];
628
- function getNodeManagerDirs() {
629
- const dirs = [];
656
+ function getNvmNodeBinDir() {
630
657
  const nvmDir = process.env.NVM_DIR || import_node_path2.join(import_node_os.homedir(), ".nvm");
631
- const nvmCurrent = import_node_path2.join(nvmDir, "current", "bin");
632
- if (import_node_fs.existsSync(nvmCurrent)) {
633
- dirs.push(nvmCurrent);
658
+ const versionsDir = import_node_path2.join(nvmDir, "versions", "node");
659
+ if (!import_node_fs.existsSync(versionsDir))
660
+ return null;
661
+ let versions;
662
+ try {
663
+ versions = import_node_fs.readdirSync(versionsDir).filter((d) => d.startsWith("v"));
664
+ } catch {
665
+ return null;
634
666
  }
635
- const fnmDir = process.env.FNM_DIR || import_node_path2.join(import_node_os.homedir(), ".fnm");
636
- const fnmCurrent = import_node_path2.join(fnmDir, "current", "bin");
637
- if (import_node_fs.existsSync(fnmCurrent)) {
638
- dirs.push(fnmCurrent);
667
+ if (versions.length === 0)
668
+ return null;
669
+ const currentNodeVersion = `v${process.versions.node}`;
670
+ const currentBin = import_node_path2.join(versionsDir, currentNodeVersion, "bin");
671
+ if (versions.includes(currentNodeVersion) && import_node_fs.existsSync(currentBin)) {
672
+ return currentBin;
673
+ }
674
+ const aliasPath = import_node_path2.join(nvmDir, "alias", "default");
675
+ if (import_node_fs.existsSync(aliasPath)) {
676
+ try {
677
+ const alias = import_node_fs.readFileSync(aliasPath, "utf-8").trim();
678
+ const match = versions.find((v) => v === `v${alias}` || v.startsWith(`v${alias}.`));
679
+ if (match) {
680
+ const bin2 = import_node_path2.join(versionsDir, match, "bin");
681
+ if (import_node_fs.existsSync(bin2))
682
+ return bin2;
683
+ }
684
+ } catch {}
639
685
  }
640
- return dirs;
686
+ const sorted = versions.sort((a, b) => {
687
+ const pa = a.slice(1).split(".").map(Number);
688
+ const pb = b.slice(1).split(".").map(Number);
689
+ for (let i = 0;i < 3; i++) {
690
+ if ((pa[i] || 0) !== (pb[i] || 0))
691
+ return (pb[i] || 0) - (pa[i] || 0);
692
+ }
693
+ return 0;
694
+ });
695
+ const bin = import_node_path2.join(versionsDir, sorted[0], "bin");
696
+ return import_node_fs.existsSync(bin) ? bin : null;
697
+ }
698
+ function getFnmNodeBinDir() {
699
+ const fnmDir = process.env.FNM_DIR || import_node_path2.join(import_node_os.homedir(), ".fnm");
700
+ const currentBin = import_node_path2.join(fnmDir, "current", "bin");
701
+ if (import_node_fs.existsSync(currentBin))
702
+ return currentBin;
703
+ const aliasDir = import_node_path2.join(fnmDir, "aliases", "default");
704
+ if (import_node_fs.existsSync(aliasDir)) {
705
+ const bin = import_node_path2.join(aliasDir, "bin");
706
+ if (import_node_fs.existsSync(bin))
707
+ return bin;
708
+ }
709
+ return null;
641
710
  }
642
711
  function getAugmentedPath() {
643
712
  const currentPath = process.env.PATH || "";
644
713
  const currentDirs = new Set(currentPath.split(import_node_path2.delimiter));
645
- const extra = [...EXTRA_BIN_DIRS, ...getNodeManagerDirs()].filter((dir) => !currentDirs.has(dir) && import_node_fs.existsSync(dir));
714
+ const extra = [];
715
+ for (const dir of EXTRA_BIN_DIRS) {
716
+ if (!currentDirs.has(dir) && import_node_fs.existsSync(dir)) {
717
+ extra.push(dir);
718
+ }
719
+ }
720
+ const nvmBin = getNvmNodeBinDir();
721
+ if (nvmBin && !currentDirs.has(nvmBin)) {
722
+ extra.push(nvmBin);
723
+ }
724
+ const fnmBin = getFnmNodeBinDir();
725
+ if (fnmBin && !currentDirs.has(fnmBin)) {
726
+ extra.push(fnmBin);
727
+ }
646
728
  if (extra.length === 0)
647
729
  return currentPath;
648
730
  return currentPath + import_node_path2.delimiter + extra.join(import_node_path2.delimiter);
649
731
  }
650
732
  function getAugmentedEnv(overrides = {}) {
651
- return {
733
+ const env = {
652
734
  ...process.env,
653
735
  ...overrides,
654
736
  PATH: getAugmentedPath()
655
737
  };
656
- }
657
-
658
- // src/ai/claude-runner.ts
659
- var SANDBOX_SETTINGS = JSON.stringify({
660
- sandbox: {
661
- enabled: true,
662
- autoAllow: true,
663
- allowUnsandboxedCommands: false
738
+ for (const key of ENV_VARS_TO_STRIP) {
739
+ delete env[key];
664
740
  }
741
+ return env;
742
+ }
743
+ var import_node_fs, import_node_os, import_node_path2, EXTRA_BIN_DIRS, ENV_VARS_TO_STRIP;
744
+ var init_resolve_bin = __esm(() => {
745
+ import_node_fs = require("node:fs");
746
+ import_node_os = require("node:os");
747
+ import_node_path2 = require("node:path");
748
+ EXTRA_BIN_DIRS = [
749
+ import_node_path2.join(import_node_os.homedir(), ".local", "bin"),
750
+ import_node_path2.join(import_node_os.homedir(), ".npm", "bin"),
751
+ import_node_path2.join(import_node_os.homedir(), ".npm-global", "bin"),
752
+ import_node_path2.join(import_node_os.homedir(), ".yarn", "bin"),
753
+ import_node_path2.join(import_node_os.homedir(), ".bun", "bin"),
754
+ import_node_path2.join(import_node_os.homedir(), "Library", "pnpm"),
755
+ "/usr/local/bin",
756
+ "/opt/homebrew/bin",
757
+ "/opt/homebrew/sbin"
758
+ ];
759
+ ENV_VARS_TO_STRIP = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
665
760
  });
666
761
 
762
+ // src/ai/claude-runner.ts
667
763
  class ClaudeRunner {
668
764
  model;
669
765
  log;
@@ -735,11 +831,13 @@ class ClaudeRunner {
735
831
  this.activeProcess = claude;
736
832
  let buffer = "";
737
833
  let stderrBuffer = "";
834
+ let stderrFull = "";
738
835
  let resolveChunk = null;
739
836
  const chunkQueue = [];
740
837
  let processEnded = false;
741
838
  let errorMessage = "";
742
839
  let finalContent = "";
840
+ let lastResultContent = "";
743
841
  let isThinking = false;
744
842
  const enqueueChunk = (chunk) => {
745
843
  this.emitEventForChunk(chunk, isThinking);
@@ -777,6 +875,9 @@ class ClaudeRunner {
777
875
  for (const line of lines) {
778
876
  const chunk = this.parseStreamLineToChunk(line);
779
877
  if (chunk) {
878
+ if (chunk.type === "result") {
879
+ lastResultContent = chunk.content;
880
+ }
780
881
  enqueueChunk(chunk);
781
882
  }
782
883
  }
@@ -784,6 +885,7 @@ class ClaudeRunner {
784
885
  claude.stderr.on("data", (data) => {
785
886
  const chunk = data.toString();
786
887
  stderrBuffer += chunk;
888
+ stderrFull += chunk;
787
889
  const lines = stderrBuffer.split(`
788
890
  `);
789
891
  stderrBuffer = lines.pop() || "";
@@ -806,7 +908,8 @@ class ClaudeRunner {
806
908
  `);
807
909
  }
808
910
  if (code !== 0 && !errorMessage) {
809
- errorMessage = this.createExecutionError(code, stderrBuffer).message;
911
+ const detail = stderrFull.trim() || lastResultContent.trim();
912
+ errorMessage = this.createExecutionError(code, detail).message;
810
913
  this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
811
914
  }
812
915
  signalEnd();
@@ -1015,7 +1118,8 @@ class ClaudeRunner {
1015
1118
  if (code === 0) {
1016
1119
  resolve2(finalResult);
1017
1120
  } else {
1018
- reject(this.createExecutionError(code, errorOutput));
1121
+ const detail = errorOutput.trim() || finalResult.trim();
1122
+ reject(this.createExecutionError(code, detail));
1019
1123
  }
1020
1124
  });
1021
1125
  claude.stdin.write(prompt);
@@ -1061,13 +1165,23 @@ ${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
1061
1165
  return new Error(message);
1062
1166
  }
1063
1167
  }
1168
+ var import_node_child_process, import_node_path3, SANDBOX_SETTINGS;
1169
+ var init_claude_runner = __esm(() => {
1170
+ init_config();
1171
+ init_colors();
1172
+ init_resolve_bin();
1173
+ import_node_child_process = require("node:child_process");
1174
+ import_node_path3 = require("node:path");
1175
+ SANDBOX_SETTINGS = JSON.stringify({
1176
+ sandbox: {
1177
+ enabled: true,
1178
+ autoAllow: true,
1179
+ allowUnsandboxedCommands: false
1180
+ }
1181
+ });
1182
+ });
1064
1183
 
1065
1184
  // src/ai/codex-runner.ts
1066
- var import_node_child_process2 = require("node:child_process");
1067
- var import_node_crypto = require("node:crypto");
1068
- var import_node_fs2 = require("node:fs");
1069
- var import_node_os2 = require("node:os");
1070
- var import_node_path4 = require("node:path");
1071
1185
  class CodexRunner {
1072
1186
  projectPath;
1073
1187
  model;
@@ -1294,6 +1408,16 @@ class CodexRunner {
1294
1408
  return new Promise((resolve2) => setTimeout(resolve2, ms));
1295
1409
  }
1296
1410
  }
1411
+ var import_node_child_process2, import_node_crypto, import_node_fs2, import_node_os2, import_node_path4;
1412
+ var init_codex_runner = __esm(() => {
1413
+ init_config();
1414
+ init_resolve_bin();
1415
+ import_node_child_process2 = require("node:child_process");
1416
+ import_node_crypto = require("node:crypto");
1417
+ import_node_fs2 = require("node:fs");
1418
+ import_node_os2 = require("node:os");
1419
+ import_node_path4 = require("node:path");
1420
+ });
1297
1421
 
1298
1422
  // src/ai/factory.ts
1299
1423
  function createAiRunner(provider, config) {
@@ -1306,9 +1430,13 @@ function createAiRunner(provider, config) {
1306
1430
  return new ClaudeRunner(config.projectPath, model, config.log);
1307
1431
  }
1308
1432
  }
1433
+ var init_factory = __esm(() => {
1434
+ init_config();
1435
+ init_claude_runner();
1436
+ init_codex_runner();
1437
+ });
1309
1438
 
1310
1439
  // src/git/git-utils.ts
1311
- var import_node_child_process3 = require("node:child_process");
1312
1440
  function isGitAvailable() {
1313
1441
  try {
1314
1442
  import_node_child_process3.execFileSync("git", ["--version"], {
@@ -1399,18 +1527,121 @@ function getDefaultBranch(projectPath, remote = "origin") {
1399
1527
  }
1400
1528
  }
1401
1529
  }
1530
+ var import_node_child_process3;
1531
+ var init_git_utils = __esm(() => {
1532
+ import_node_child_process3 = require("node:child_process");
1533
+ });
1402
1534
 
1403
- // src/git/pr-service.ts
1404
- var import_node_child_process4 = require("node:child_process");
1405
- class PrService {
1406
- projectPath;
1407
- log;
1408
- constructor(projectPath, log) {
1409
- this.projectPath = projectPath;
1410
- this.log = log;
1535
+ // src/project/knowledge-base.ts
1536
+ class KnowledgeBase {
1537
+ contextPath;
1538
+ progressPath;
1539
+ constructor(projectPath) {
1540
+ this.contextPath = getLocusPath(projectPath, "projectContextFile");
1541
+ this.progressPath = getLocusPath(projectPath, "projectProgressFile");
1411
1542
  }
1412
- createPr(options) {
1413
- const {
1543
+ readContext() {
1544
+ if (!import_node_fs3.existsSync(this.contextPath)) {
1545
+ return "";
1546
+ }
1547
+ return import_node_fs3.readFileSync(this.contextPath, "utf-8");
1548
+ }
1549
+ readProgress() {
1550
+ if (!import_node_fs3.existsSync(this.progressPath)) {
1551
+ return "";
1552
+ }
1553
+ return import_node_fs3.readFileSync(this.progressPath, "utf-8");
1554
+ }
1555
+ updateContext(content) {
1556
+ this.ensureDir(this.contextPath);
1557
+ import_node_fs3.writeFileSync(this.contextPath, content);
1558
+ }
1559
+ updateProgress(entry) {
1560
+ this.ensureDir(this.progressPath);
1561
+ const existing = this.readProgress();
1562
+ const timestamp = (entry.timestamp ?? new Date).toISOString();
1563
+ const label = entry.role === "user" ? "User" : "Assistant";
1564
+ const line = `**${label}** (${timestamp}):
1565
+ ${entry.content}`;
1566
+ const updated = existing ? `${existing}
1567
+
1568
+ ---
1569
+
1570
+ ${line}` : `# Conversation History
1571
+
1572
+ ${line}`;
1573
+ import_node_fs3.writeFileSync(this.progressPath, updated);
1574
+ }
1575
+ getFullContext() {
1576
+ const context = this.readContext();
1577
+ const progress = this.readProgress();
1578
+ const parts = [];
1579
+ if (context.trim()) {
1580
+ parts.push(context.trim());
1581
+ }
1582
+ if (progress.trim()) {
1583
+ parts.push(progress.trim());
1584
+ }
1585
+ return parts.join(`
1586
+
1587
+ ---
1588
+
1589
+ `);
1590
+ }
1591
+ initialize(info) {
1592
+ this.ensureDir(this.contextPath);
1593
+ this.ensureDir(this.progressPath);
1594
+ const techStackList = info.techStack.map((t) => `- ${t}`).join(`
1595
+ `);
1596
+ const contextContent = `# Project: ${info.name}
1597
+
1598
+ ## Mission
1599
+ ${info.mission}
1600
+
1601
+ ## Tech Stack
1602
+ ${techStackList}
1603
+
1604
+ ## Architecture
1605
+ <!-- Describe your high-level architecture here -->
1606
+
1607
+ ## Key Decisions
1608
+ <!-- Document important technical decisions and their rationale -->
1609
+
1610
+ ## Feature Areas
1611
+ <!-- List your main feature areas and their status -->
1612
+ `;
1613
+ const progressContent = `# Conversation History
1614
+ `;
1615
+ import_node_fs3.writeFileSync(this.contextPath, contextContent);
1616
+ import_node_fs3.writeFileSync(this.progressPath, progressContent);
1617
+ }
1618
+ get exists() {
1619
+ return import_node_fs3.existsSync(this.contextPath) || import_node_fs3.existsSync(this.progressPath);
1620
+ }
1621
+ ensureDir(filePath) {
1622
+ const dir = import_node_path5.dirname(filePath);
1623
+ if (!import_node_fs3.existsSync(dir)) {
1624
+ import_node_fs3.mkdirSync(dir, { recursive: true });
1625
+ }
1626
+ }
1627
+ }
1628
+ var import_node_fs3, import_node_path5;
1629
+ var init_knowledge_base = __esm(() => {
1630
+ init_config();
1631
+ import_node_fs3 = require("node:fs");
1632
+ import_node_path5 = require("node:path");
1633
+ });
1634
+
1635
+ // src/git/pr-service.ts
1636
+ class PrService {
1637
+ projectPath;
1638
+ log;
1639
+ constructor(projectPath, log) {
1640
+ this.projectPath = projectPath;
1641
+ this.log = log;
1642
+ }
1643
+ createPr(options) {
1644
+ const {
1414
1645
  task,
1415
1646
  branch,
1416
1647
  baseBranch: requestedBaseBranch,
@@ -1631,148 +1862,21 @@ class PrService {
1631
1862
  return match ? Number.parseInt(match[1], 10) : 0;
1632
1863
  }
1633
1864
  }
1634
-
1635
- // src/project/knowledge-base.ts
1636
- var import_node_fs3 = require("node:fs");
1637
- var import_node_path5 = require("node:path");
1638
- class KnowledgeBase {
1639
- contextPath;
1640
- progressPath;
1641
- constructor(projectPath) {
1642
- this.contextPath = getLocusPath(projectPath, "projectContextFile");
1643
- this.progressPath = getLocusPath(projectPath, "projectProgressFile");
1644
- }
1645
- readContext() {
1646
- if (!import_node_fs3.existsSync(this.contextPath)) {
1647
- return "";
1648
- }
1649
- return import_node_fs3.readFileSync(this.contextPath, "utf-8");
1650
- }
1651
- readProgress() {
1652
- if (!import_node_fs3.existsSync(this.progressPath)) {
1653
- return "";
1654
- }
1655
- return import_node_fs3.readFileSync(this.progressPath, "utf-8");
1656
- }
1657
- updateContext(content) {
1658
- this.ensureDir(this.contextPath);
1659
- import_node_fs3.writeFileSync(this.contextPath, content);
1660
- }
1661
- updateProgress(event) {
1662
- this.ensureDir(this.progressPath);
1663
- const existing = this.readProgress();
1664
- const timestamp = (event.timestamp ?? new Date).toISOString();
1665
- let entry = "";
1666
- switch (event.type) {
1667
- case "task_completed":
1668
- entry = `- [x] ${event.title} — completed ${timestamp}`;
1669
- break;
1670
- case "sprint_started":
1671
- entry = `
1672
- ## Current Sprint: ${event.title}
1673
- **Status:** ACTIVE | Started: ${timestamp}
1674
- `;
1675
- break;
1676
- case "sprint_completed":
1677
- entry = `
1678
- ### Sprint Completed: ${event.title} — ${timestamp}
1679
- `;
1680
- break;
1681
- case "blocker":
1682
- entry = `- BLOCKER: ${event.title}`;
1683
- break;
1684
- case "pr_opened":
1685
- entry = `- [ ] ${event.title} — PR opened ${timestamp}`;
1686
- break;
1687
- case "pr_reviewed":
1688
- entry = `- ${event.title} — reviewed ${timestamp}`;
1689
- break;
1690
- case "pr_merged":
1691
- entry = `- [x] ${event.title} — PR merged ${timestamp}`;
1692
- break;
1693
- case "exec_completed":
1694
- entry = `- [x] ${event.title} — exec ${timestamp}`;
1695
- break;
1696
- }
1697
- if (event.details) {
1698
- entry += `
1699
- ${event.details}`;
1700
- }
1701
- const updated = existing ? `${existing}
1702
- ${entry}` : `# Project Progress
1703
-
1704
- ${entry}`;
1705
- import_node_fs3.writeFileSync(this.progressPath, updated);
1706
- }
1707
- getFullContext() {
1708
- const context = this.readContext();
1709
- const progress = this.readProgress();
1710
- const parts = [];
1711
- if (context.trim()) {
1712
- parts.push(context.trim());
1713
- }
1714
- if (progress.trim()) {
1715
- parts.push(progress.trim());
1716
- }
1717
- return parts.join(`
1718
-
1719
- ---
1720
-
1721
- `);
1722
- }
1723
- initialize(info) {
1724
- this.ensureDir(this.contextPath);
1725
- this.ensureDir(this.progressPath);
1726
- const techStackList = info.techStack.map((t) => `- ${t}`).join(`
1727
- `);
1728
- const contextContent = `# Project: ${info.name}
1729
-
1730
- ## Mission
1731
- ${info.mission}
1732
-
1733
- ## Tech Stack
1734
- ${techStackList}
1735
-
1736
- ## Architecture
1737
- <!-- Describe your high-level architecture here -->
1738
-
1739
- ## Key Decisions
1740
- <!-- Document important technical decisions and their rationale -->
1741
-
1742
- ## Feature Areas
1743
- <!-- List your main feature areas and their status -->
1744
- `;
1745
- const progressContent = `# Project Progress
1746
-
1747
- No sprints started yet.
1748
- `;
1749
- import_node_fs3.writeFileSync(this.contextPath, contextContent);
1750
- import_node_fs3.writeFileSync(this.progressPath, progressContent);
1751
- }
1752
- get exists() {
1753
- return import_node_fs3.existsSync(this.contextPath) || import_node_fs3.existsSync(this.progressPath);
1754
- }
1755
- ensureDir(filePath) {
1756
- const dir = import_node_path5.dirname(filePath);
1757
- if (!import_node_fs3.existsSync(dir)) {
1758
- import_node_fs3.mkdirSync(dir, { recursive: true });
1759
- }
1760
- }
1761
- }
1762
-
1763
- // src/worktree/worktree-manager.ts
1764
- var import_node_child_process5 = require("node:child_process");
1765
- var import_node_fs4 = require("node:fs");
1766
- var import_node_path6 = require("node:path");
1865
+ var import_node_child_process4;
1866
+ var init_pr_service = __esm(() => {
1867
+ init_git_utils();
1868
+ import_node_child_process4 = require("node:child_process");
1869
+ });
1767
1870
 
1768
1871
  // src/worktree/worktree-config.ts
1769
- var WORKTREE_ROOT_DIR = ".locus-worktrees";
1770
- var WORKTREE_BRANCH_PREFIX = "agent";
1771
- var DEFAULT_WORKTREE_CONFIG = {
1772
- rootDir: WORKTREE_ROOT_DIR,
1773
- branchPrefix: WORKTREE_BRANCH_PREFIX,
1774
- cleanupPolicy: "retain-on-failure"
1775
- };
1872
+ var WORKTREE_ROOT_DIR = ".locus-worktrees", WORKTREE_BRANCH_PREFIX = "agent", DEFAULT_WORKTREE_CONFIG;
1873
+ var init_worktree_config = __esm(() => {
1874
+ DEFAULT_WORKTREE_CONFIG = {
1875
+ rootDir: WORKTREE_ROOT_DIR,
1876
+ branchPrefix: WORKTREE_BRANCH_PREFIX,
1877
+ cleanupPolicy: "retain-on-failure"
1878
+ };
1879
+ });
1776
1880
 
1777
1881
  // src/worktree/worktree-manager.ts
1778
1882
  class WorktreeManager {
@@ -1799,6 +1903,15 @@ class WorktreeManager {
1799
1903
  const worktreePath = import_node_path6.join(this.rootPath, worktreeDir);
1800
1904
  this.ensureDirectory(this.rootPath, "Worktree root");
1801
1905
  const baseBranch = options.baseBranch ?? this.config.baseBranch ?? this.getCurrentBranch();
1906
+ if (!this.branchExists(baseBranch)) {
1907
+ this.log(`Base branch "${baseBranch}" not found locally, fetching from origin`, "info");
1908
+ try {
1909
+ this.gitExec(["fetch", "origin", baseBranch], this.projectPath);
1910
+ this.gitExec(["branch", baseBranch, `origin/${baseBranch}`], this.projectPath);
1911
+ } catch {
1912
+ this.log(`Could not fetch/create local branch for "${baseBranch}", falling back to current branch`, "warn");
1913
+ }
1914
+ }
1802
1915
  this.log(`Creating worktree: ${worktreeDir} (branch: ${branch}, base: ${baseBranch})`, "info");
1803
1916
  if (import_node_fs4.existsSync(worktreePath)) {
1804
1917
  this.log(`Removing stale worktree directory: ${worktreePath}`, "warn");
@@ -2062,11 +2175,15 @@ class WorktreeManager {
2062
2175
  });
2063
2176
  }
2064
2177
  }
2178
+ var import_node_child_process5, import_node_fs4, import_node_path6;
2179
+ var init_worktree_manager = __esm(() => {
2180
+ init_worktree_config();
2181
+ import_node_child_process5 = require("node:child_process");
2182
+ import_node_fs4 = require("node:fs");
2183
+ import_node_path6 = require("node:path");
2184
+ });
2065
2185
 
2066
2186
  // src/core/prompt-builder.ts
2067
- var import_node_fs5 = require("node:fs");
2068
- var import_node_path7 = require("node:path");
2069
- var import_shared2 = require("@locusai/shared");
2070
2187
  class PromptBuilder {
2071
2188
  projectPath;
2072
2189
  constructor(projectPath) {
@@ -2362,6 +2479,13 @@ There is an index file in the .locus/codebase-index.json and if you need you can
2362
2479
  }
2363
2480
  }
2364
2481
  }
2482
+ var import_node_fs5, import_node_path7, import_shared2;
2483
+ var init_prompt_builder = __esm(() => {
2484
+ init_config();
2485
+ import_node_fs5 = require("node:fs");
2486
+ import_node_path7 = require("node:path");
2487
+ import_shared2 = require("@locusai/shared");
2488
+ });
2365
2489
 
2366
2490
  // src/agent/task-executor.ts
2367
2491
  class TaskExecutor {
@@ -2386,162 +2510,51 @@ class TaskExecutor {
2386
2510
  }
2387
2511
  }
2388
2512
  }
2513
+ var init_task_executor = __esm(() => {
2514
+ init_prompt_builder();
2515
+ });
2389
2516
 
2390
- // src/agent/worker.ts
2391
- function resolveProvider(value) {
2392
- if (!value || value.startsWith("--")) {
2393
- console.warn("Warning: --provider requires a value. Falling back to 'claude'.");
2394
- return PROVIDER.CLAUDE;
2395
- }
2396
- if (value === PROVIDER.CLAUDE || value === PROVIDER.CODEX)
2397
- return value;
2398
- console.warn(`Warning: invalid --provider value '${value}'. Falling back to 'claude'.`);
2399
- return PROVIDER.CLAUDE;
2400
- }
2401
-
2402
- class AgentWorker {
2517
+ // src/agent/git-workflow.ts
2518
+ class GitWorkflow {
2403
2519
  config;
2404
- client;
2405
- aiRunner;
2406
- taskExecutor;
2407
- knowledgeBase;
2408
- worktreeManager = null;
2409
- prService = null;
2410
- maxTasks = 50;
2411
- tasksCompleted = 0;
2412
- heartbeatInterval = null;
2413
- currentTaskId = null;
2414
- currentWorktreePath = null;
2415
- postCleanupDelayMs = 5000;
2416
- ghUsername = null;
2417
- constructor(config) {
2520
+ log;
2521
+ ghUsername;
2522
+ worktreeManager;
2523
+ prService;
2524
+ constructor(config, log, ghUsername) {
2418
2525
  this.config = config;
2526
+ this.log = log;
2527
+ this.ghUsername = ghUsername;
2419
2528
  const projectPath = config.projectPath || process.cwd();
2420
- this.client = new LocusClient({
2421
- baseUrl: config.apiBase,
2422
- token: config.apiKey,
2423
- retryOptions: {
2424
- maxRetries: 3,
2425
- initialDelay: 1000,
2426
- maxDelay: 5000,
2427
- factor: 2
2428
- }
2429
- });
2430
- const log = this.log.bind(this);
2431
- if (config.useWorktrees && !isGitAvailable()) {
2432
- this.log("git is not installed — worktree isolation will not work", "error");
2433
- config.useWorktrees = false;
2434
- }
2435
- if (config.autoPush && !isGhAvailable(projectPath)) {
2436
- this.log("GitHub CLI (gh) not available or not authenticated. Branch push can continue, but automatic PR creation may fail until gh is configured. Install from https://cli.github.com/", "warn");
2437
- }
2438
- if (config.autoPush) {
2439
- this.ghUsername = getGhUsername();
2440
- if (this.ghUsername) {
2441
- this.log(`GitHub user: ${this.ghUsername}`, "info");
2442
- }
2443
- }
2444
- const provider = config.provider ?? PROVIDER.CLAUDE;
2445
- this.aiRunner = createAiRunner(provider, {
2446
- projectPath,
2447
- model: config.model,
2448
- log
2449
- });
2450
- this.taskExecutor = new TaskExecutor({
2451
- aiRunner: this.aiRunner,
2452
- projectPath,
2453
- log
2454
- });
2455
- this.knowledgeBase = new KnowledgeBase(projectPath);
2456
- if (config.useWorktrees) {
2457
- this.worktreeManager = new WorktreeManager(projectPath, {
2458
- cleanupPolicy: "auto"
2459
- });
2460
- }
2461
- if (config.autoPush) {
2462
- this.prService = new PrService(projectPath, log);
2463
- }
2464
- const providerLabel = provider === "codex" ? "Codex" : "Claude";
2465
- this.log(`Using ${providerLabel} CLI for all phases`, "info");
2466
- if (config.useWorktrees) {
2467
- this.log("Per-task worktree isolation enabled", "info");
2468
- if (config.autoPush) {
2469
- this.log("Auto-push enabled: branches will be pushed to remote", "info");
2470
- }
2471
- }
2472
- }
2473
- log(message, level = "info") {
2474
- const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
2475
- const colorFn = {
2476
- info: c.cyan,
2477
- success: c.green,
2478
- warn: c.yellow,
2479
- error: c.red
2480
- }[level];
2481
- const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
2482
- console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`);
2483
- }
2484
- async getActiveSprint() {
2485
- try {
2486
- if (this.config.sprintId) {
2487
- return await this.client.sprints.getById(this.config.sprintId, this.config.workspaceId);
2488
- }
2489
- return await this.client.sprints.getActive(this.config.workspaceId);
2490
- } catch (_error) {
2491
- return null;
2492
- }
2493
- }
2494
- async getNextTask() {
2495
- const maxRetries = 10;
2496
- for (let attempt = 1;attempt <= maxRetries; attempt++) {
2497
- try {
2498
- const task = await this.client.workspaces.dispatch(this.config.workspaceId, this.config.agentId, this.config.sprintId);
2499
- return task;
2500
- } catch (error) {
2501
- const isAxiosError = error != null && typeof error === "object" && "response" in error && typeof error.response?.status === "number";
2502
- const status = isAxiosError ? error.response.status : 0;
2503
- if (status === 404) {
2504
- this.log("No tasks available in the backlog.", "info");
2505
- return null;
2506
- }
2507
- const msg = error instanceof Error ? error.message : String(error);
2508
- if (attempt < maxRetries) {
2509
- this.log(`Nothing dispatched (attempt ${attempt}/${maxRetries}): ${msg}. Retrying in 30s...`, "warn");
2510
- await new Promise((r) => setTimeout(r, 30000));
2511
- } else {
2512
- this.log(`Nothing dispatched after ${maxRetries} attempts: ${msg}`, "warn");
2513
- return null;
2514
- }
2515
- }
2516
- }
2517
- return null;
2518
- }
2519
- createTaskWorktree(task) {
2520
- if (!this.worktreeManager) {
2521
- return {
2522
- worktreePath: null,
2523
- baseBranch: null,
2524
- executor: this.taskExecutor
2525
- };
2529
+ this.worktreeManager = config.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }) : null;
2530
+ this.prService = config.autoPush ? new PrService(projectPath, log) : null;
2531
+ }
2532
+ createTaskWorktree(task, defaultExecutor) {
2533
+ if (!this.worktreeManager) {
2534
+ return {
2535
+ worktreePath: null,
2536
+ baseBranch: null,
2537
+ executor: defaultExecutor
2538
+ };
2526
2539
  }
2527
2540
  const slug = task.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
2528
2541
  const result = this.worktreeManager.create({
2529
2542
  taskId: task.id,
2530
2543
  taskSlug: slug,
2531
- agentId: this.config.agentId
2544
+ agentId: this.config.agentId,
2545
+ baseBranch: this.config.baseBranch
2532
2546
  });
2533
2547
  this.log(`Worktree created: ${result.worktreePath} (${result.branch})`, "info");
2534
- const log = this.log.bind(this);
2535
2548
  const provider = this.config.provider ?? PROVIDER.CLAUDE;
2536
2549
  const taskAiRunner = createAiRunner(provider, {
2537
2550
  projectPath: result.worktreePath,
2538
2551
  model: this.config.model,
2539
- log
2552
+ log: this.log
2540
2553
  });
2541
2554
  const taskExecutor = new TaskExecutor({
2542
2555
  aiRunner: taskAiRunner,
2543
2556
  projectPath: result.worktreePath,
2544
- log
2557
+ log: this.log
2545
2558
  });
2546
2559
  return {
2547
2560
  worktreePath: result.worktreePath,
@@ -2549,7 +2562,7 @@ class AgentWorker {
2549
2562
  executor: taskExecutor
2550
2563
  };
2551
2564
  }
2552
- commitAndPushWorktree(worktreePath, task, baseBranch) {
2565
+ commitAndPush(worktreePath, task, baseBranch) {
2553
2566
  if (!this.worktreeManager) {
2554
2567
  return { branch: null, pushed: false, pushFailed: false };
2555
2568
  }
@@ -2631,7 +2644,7 @@ ${trailers.join(`
2631
2644
  return { url: null, error: errorMessage };
2632
2645
  }
2633
2646
  }
2634
- cleanupTaskWorktree(worktreePath, keepBranch) {
2647
+ cleanupWorktree(worktreePath, keepBranch) {
2635
2648
  if (!this.worktreeManager || !worktreePath)
2636
2649
  return;
2637
2650
  try {
@@ -2640,11 +2653,205 @@ ${trailers.join(`
2640
2653
  } catch {
2641
2654
  this.log(`Could not clean up worktree: ${worktreePath}`, "warn");
2642
2655
  }
2643
- this.currentWorktreePath = null;
2656
+ }
2657
+ }
2658
+ var init_git_workflow = __esm(() => {
2659
+ init_factory();
2660
+ init_config();
2661
+ init_pr_service();
2662
+ init_worktree_manager();
2663
+ init_task_executor();
2664
+ });
2665
+
2666
+ // src/agent/worker-cli.ts
2667
+ var exports_worker_cli = {};
2668
+ __export(exports_worker_cli, {
2669
+ parseWorkerArgs: () => parseWorkerArgs
2670
+ });
2671
+ function resolveProvider(value) {
2672
+ if (!value || value.startsWith("--")) {
2673
+ console.warn("Warning: --provider requires a value. Falling back to 'claude'.");
2674
+ return PROVIDER.CLAUDE;
2675
+ }
2676
+ if (value === PROVIDER.CLAUDE || value === PROVIDER.CODEX)
2677
+ return value;
2678
+ console.warn(`Warning: invalid --provider value '${value}'. Falling back to 'claude'.`);
2679
+ return PROVIDER.CLAUDE;
2680
+ }
2681
+ function parseWorkerArgs(argv) {
2682
+ const args = argv.slice(2);
2683
+ const config = {};
2684
+ for (let i = 0;i < args.length; i++) {
2685
+ const arg = args[i];
2686
+ if (arg === "--agent-id")
2687
+ config.agentId = args[++i];
2688
+ else if (arg === "--workspace-id")
2689
+ config.workspaceId = args[++i];
2690
+ else if (arg === "--sprint-id")
2691
+ config.sprintId = args[++i];
2692
+ else if (arg === "--api-url")
2693
+ config.apiBase = args[++i];
2694
+ else if (arg === "--api-key")
2695
+ config.apiKey = args[++i];
2696
+ else if (arg === "--project-path")
2697
+ config.projectPath = args[++i];
2698
+ else if (arg === "--main-project-path")
2699
+ config.mainProjectPath = args[++i];
2700
+ else if (arg === "--model")
2701
+ config.model = args[++i];
2702
+ else if (arg === "--use-worktrees")
2703
+ config.useWorktrees = true;
2704
+ else if (arg === "--auto-push")
2705
+ config.autoPush = true;
2706
+ else if (arg === "--base-branch")
2707
+ config.baseBranch = args[++i];
2708
+ else if (arg === "--provider") {
2709
+ const value = args[i + 1];
2710
+ if (value && !value.startsWith("--"))
2711
+ i++;
2712
+ config.provider = resolveProvider(value);
2713
+ }
2714
+ }
2715
+ if (!config.agentId || !config.workspaceId || !config.apiBase || !config.apiKey || !config.projectPath) {
2716
+ console.error("Missing required arguments");
2717
+ process.exit(1);
2718
+ }
2719
+ return config;
2720
+ }
2721
+ var entrypoint;
2722
+ var init_worker_cli = __esm(() => {
2723
+ init_config();
2724
+ init_worker();
2725
+ entrypoint = process.argv[1]?.split(/[\\/]/).pop();
2726
+ if (entrypoint === "worker-cli.js" || entrypoint === "worker-cli.ts") {
2727
+ process.title = "locus-worker";
2728
+ const config = parseWorkerArgs(process.argv);
2729
+ const worker = new AgentWorker(config);
2730
+ worker.run().catch((err) => {
2731
+ console.error("Fatal worker error:", err);
2732
+ process.exit(1);
2733
+ });
2734
+ }
2735
+ });
2736
+
2737
+ // src/agent/worker.ts
2738
+ var exports_worker = {};
2739
+ __export(exports_worker, {
2740
+ AgentWorker: () => AgentWorker
2741
+ });
2742
+ module.exports = __toCommonJS(exports_worker);
2743
+
2744
+ class AgentWorker {
2745
+ config;
2746
+ client;
2747
+ aiRunner;
2748
+ taskExecutor;
2749
+ knowledgeBase;
2750
+ gitWorkflow;
2751
+ maxTasks = 50;
2752
+ tasksCompleted = 0;
2753
+ heartbeatInterval = null;
2754
+ currentTaskId = null;
2755
+ currentWorktreePath = null;
2756
+ postCleanupDelayMs = 5000;
2757
+ constructor(config) {
2758
+ this.config = config;
2759
+ const projectPath = config.projectPath || process.cwd();
2760
+ this.client = new LocusClient({
2761
+ baseUrl: config.apiBase,
2762
+ token: config.apiKey,
2763
+ retryOptions: {
2764
+ maxRetries: 3,
2765
+ initialDelay: 1000,
2766
+ maxDelay: 5000,
2767
+ factor: 2
2768
+ }
2769
+ });
2770
+ const log = this.log.bind(this);
2771
+ if (config.useWorktrees && !isGitAvailable()) {
2772
+ this.log("git is not installed — worktree isolation will not work", "error");
2773
+ config.useWorktrees = false;
2774
+ }
2775
+ if (config.autoPush && !isGhAvailable(projectPath)) {
2776
+ this.log("GitHub CLI (gh) not available or not authenticated. Branch push can continue, but automatic PR creation may fail until gh is configured. Install from https://cli.github.com/", "warn");
2777
+ }
2778
+ const ghUsername = config.autoPush ? getGhUsername() : null;
2779
+ if (ghUsername) {
2780
+ this.log(`GitHub user: ${ghUsername}`, "info");
2781
+ }
2782
+ const provider = config.provider ?? PROVIDER.CLAUDE;
2783
+ this.aiRunner = createAiRunner(provider, {
2784
+ projectPath,
2785
+ model: config.model,
2786
+ log
2787
+ });
2788
+ this.taskExecutor = new TaskExecutor({
2789
+ aiRunner: this.aiRunner,
2790
+ projectPath,
2791
+ log
2792
+ });
2793
+ this.knowledgeBase = new KnowledgeBase(projectPath);
2794
+ this.gitWorkflow = new GitWorkflow(config, log, ghUsername);
2795
+ const providerLabel = provider === "codex" ? "Codex" : "Claude";
2796
+ this.log(`Using ${providerLabel} CLI for all phases`, "info");
2797
+ if (config.useWorktrees) {
2798
+ this.log("Per-task worktree isolation enabled", "info");
2799
+ if (config.baseBranch) {
2800
+ this.log(`Base branch for worktrees: ${config.baseBranch}`, "info");
2801
+ }
2802
+ if (config.autoPush) {
2803
+ this.log("Auto-push enabled: branches will be pushed to remote", "info");
2804
+ }
2805
+ }
2806
+ }
2807
+ log(message, level = "info") {
2808
+ const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
2809
+ const colorFn = {
2810
+ info: c.cyan,
2811
+ success: c.green,
2812
+ warn: c.yellow,
2813
+ error: c.red
2814
+ }[level];
2815
+ const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
2816
+ console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`);
2817
+ }
2818
+ async getActiveSprint() {
2819
+ try {
2820
+ if (this.config.sprintId) {
2821
+ return await this.client.sprints.getById(this.config.sprintId, this.config.workspaceId);
2822
+ }
2823
+ return await this.client.sprints.getActive(this.config.workspaceId);
2824
+ } catch (_error) {
2825
+ return null;
2826
+ }
2827
+ }
2828
+ async getNextTask() {
2829
+ const maxRetries = 10;
2830
+ for (let attempt = 1;attempt <= maxRetries; attempt++) {
2831
+ try {
2832
+ return await this.client.workspaces.dispatch(this.config.workspaceId, this.config.agentId, this.config.sprintId);
2833
+ } catch (error) {
2834
+ const isAxiosError = error != null && typeof error === "object" && "response" in error && typeof error.response?.status === "number";
2835
+ const status = isAxiosError ? error.response.status : 0;
2836
+ if (status === 404) {
2837
+ this.log("No tasks available in the backlog.", "info");
2838
+ return null;
2839
+ }
2840
+ const msg = error instanceof Error ? error.message : String(error);
2841
+ if (attempt < maxRetries) {
2842
+ this.log(`Nothing dispatched (attempt ${attempt}/${maxRetries}): ${msg}. Retrying in 30s...`, "warn");
2843
+ await new Promise((r) => setTimeout(r, 30000));
2844
+ } else {
2845
+ this.log(`Nothing dispatched after ${maxRetries} attempts: ${msg}`, "warn");
2846
+ return null;
2847
+ }
2848
+ }
2849
+ }
2850
+ return null;
2644
2851
  }
2645
2852
  async executeTask(task) {
2646
2853
  const fullTask = await this.client.tasks.getById(task.id, this.config.workspaceId);
2647
- const { worktreePath, baseBranch, executor } = this.createTaskWorktree(fullTask);
2854
+ const { worktreePath, baseBranch, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
2648
2855
  this.currentWorktreePath = worktreePath;
2649
2856
  let branchPushed = false;
2650
2857
  let keepBranch = false;
@@ -2656,7 +2863,7 @@ ${trailers.join(`
2656
2863
  let prError = null;
2657
2864
  let noChanges = false;
2658
2865
  if (result.success && worktreePath) {
2659
- const commitResult = this.commitAndPushWorktree(worktreePath, fullTask, baseBranch ?? undefined);
2866
+ const commitResult = this.gitWorkflow.commitAndPush(worktreePath, fullTask, baseBranch ?? undefined);
2660
2867
  taskBranch = commitResult.branch;
2661
2868
  branchPushed = commitResult.pushed;
2662
2869
  keepBranch = taskBranch !== null;
@@ -2667,7 +2874,7 @@ ${trailers.join(`
2667
2874
  this.log(`Preserving worktree after push failure: ${worktreePath}`, "warn");
2668
2875
  }
2669
2876
  if (branchPushed && taskBranch) {
2670
- const prResult = this.createPullRequest(fullTask, taskBranch, result.summary, baseBranch ?? undefined);
2877
+ const prResult = this.gitWorkflow.createPullRequest(fullTask, taskBranch, result.summary, baseBranch ?? undefined);
2671
2878
  prUrl = prResult.url;
2672
2879
  prError = prResult.error ?? null;
2673
2880
  if (!prUrl) {
@@ -2691,29 +2898,29 @@ ${trailers.join(`
2691
2898
  if (preserveWorktree || keepBranch) {
2692
2899
  this.currentWorktreePath = null;
2693
2900
  } else {
2694
- this.cleanupTaskWorktree(worktreePath, keepBranch);
2901
+ this.gitWorkflow.cleanupWorktree(worktreePath, keepBranch);
2902
+ this.currentWorktreePath = null;
2695
2903
  }
2696
2904
  }
2697
2905
  }
2698
- updateProgress(task, success) {
2906
+ updateProgress(task, summary) {
2699
2907
  try {
2700
- if (success) {
2701
- this.knowledgeBase.updateProgress({
2702
- type: "task_completed",
2703
- title: task.title,
2704
- details: `Agent: ${this.config.agentId.slice(-8)}`
2705
- });
2706
- this.log(`Updated progress.md: ${task.title}`, "info");
2707
- }
2908
+ this.knowledgeBase.updateProgress({
2909
+ role: "user",
2910
+ content: task.title
2911
+ });
2912
+ this.knowledgeBase.updateProgress({
2913
+ role: "assistant",
2914
+ content: summary
2915
+ });
2916
+ this.log(`Updated progress.md: ${task.title}`, "info");
2708
2917
  } catch (err) {
2709
2918
  this.log(`Failed to update progress: ${err instanceof Error ? err.message : String(err)}`, "warn");
2710
2919
  }
2711
2920
  }
2712
2921
  startHeartbeat() {
2713
2922
  this.sendHeartbeat();
2714
- this.heartbeatInterval = setInterval(() => {
2715
- this.sendHeartbeat();
2716
- }, 60000);
2923
+ this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), 60000);
2717
2924
  }
2718
2925
  stopHeartbeat() {
2719
2926
  if (this.heartbeatInterval) {
@@ -2738,7 +2945,7 @@ ${trailers.join(`
2738
2945
  this.log("Received shutdown signal. Aborting...", "warn");
2739
2946
  this.aiRunner.abort();
2740
2947
  this.stopHeartbeat();
2741
- this.cleanupTaskWorktree(this.currentWorktreePath, false);
2948
+ this.gitWorkflow.cleanupWorktree(this.currentWorktreePath, false);
2742
2949
  process.exit(1);
2743
2950
  };
2744
2951
  process.on("SIGTERM", handleShutdown);
@@ -2794,16 +3001,7 @@ PR automation error: ${result.prError}` : "";
2794
3001
  text: `✅ ${result.summary}${branchInfo}${prInfo}${prErrorInfo}`
2795
3002
  });
2796
3003
  this.tasksCompleted++;
2797
- this.updateProgress(task, true);
2798
- if (result.prUrl) {
2799
- try {
2800
- this.knowledgeBase.updateProgress({
2801
- type: "pr_opened",
2802
- title: task.title,
2803
- details: `PR: ${result.prUrl}`
2804
- });
2805
- } catch {}
2806
- }
3004
+ this.updateProgress(task, result.summary);
2807
3005
  }
2808
3006
  } else {
2809
3007
  this.log(`Failed: ${task.title} - ${result.summary}`, "error");
@@ -2823,53 +3021,33 @@ PR automation error: ${result.prError}` : "";
2823
3021
  this.currentTaskId = null;
2824
3022
  this.stopHeartbeat();
2825
3023
  this.client.workspaces.heartbeat(this.config.workspaceId, this.config.agentId, null, "COMPLETED").catch(() => {});
2826
- process.exit(0);
2827
- }
2828
- }
2829
- var workerEntrypoint = process.argv[1]?.split(/[\\/]/).pop();
2830
- if (workerEntrypoint === "worker.js" || workerEntrypoint === "worker.ts") {
2831
- process.title = "locus-worker";
2832
- const args = process.argv.slice(2);
2833
- const config = {};
2834
- for (let i = 0;i < args.length; i++) {
2835
- const arg = args[i];
2836
- if (arg === "--agent-id")
2837
- config.agentId = args[++i];
2838
- else if (arg === "--workspace-id")
2839
- config.workspaceId = args[++i];
2840
- else if (arg === "--sprint-id")
2841
- config.sprintId = args[++i];
2842
- else if (arg === "--api-url")
2843
- config.apiBase = args[++i];
2844
- else if (arg === "--api-key")
2845
- config.apiKey = args[++i];
2846
- else if (arg === "--project-path")
2847
- config.projectPath = args[++i];
2848
- else if (arg === "--main-project-path")
2849
- config.mainProjectPath = args[++i];
2850
- else if (arg === "--model")
2851
- config.model = args[++i];
2852
- else if (arg === "--use-worktrees")
2853
- config.useWorktrees = true;
2854
- else if (arg === "--auto-push")
2855
- config.autoPush = true;
2856
- else if (arg === "--provider") {
2857
- const value = args[i + 1];
2858
- if (value && !value.startsWith("--"))
2859
- i++;
2860
- config.provider = resolveProvider(value);
2861
- }
2862
- }
2863
- if (!config.agentId || !config.workspaceId || !config.apiBase || !config.apiKey || !config.projectPath) {
2864
- console.error("Missing required arguments");
2865
- process.exit(1);
3024
+ process.exit(0);
2866
3025
  }
2867
- const worker = new AgentWorker(config);
2868
- worker.run().catch((err) => {
2869
- console.error("Fatal worker error:", err);
2870
- process.exit(1);
2871
- });
2872
3026
  }
3027
+ var import_shared3, workerEntrypoint;
3028
+ var init_worker = __esm(() => {
3029
+ init_factory();
3030
+ init_config();
3031
+ init_git_utils();
3032
+ init_src();
3033
+ init_knowledge_base();
3034
+ init_colors();
3035
+ init_git_workflow();
3036
+ init_task_executor();
3037
+ import_shared3 = require("@locusai/shared");
3038
+ workerEntrypoint = process.argv[1]?.split(/[\\/]/).pop();
3039
+ if (workerEntrypoint === "worker.js" || workerEntrypoint === "worker.ts") {
3040
+ process.title = "locus-worker";
3041
+ Promise.resolve().then(() => (init_worker_cli(), exports_worker_cli)).then(({ parseWorkerArgs: parseWorkerArgs2 }) => {
3042
+ const config = parseWorkerArgs2(process.argv);
3043
+ const worker = new AgentWorker(config);
3044
+ worker.run().catch((err) => {
3045
+ console.error("Fatal worker error:", err);
3046
+ process.exit(1);
3047
+ });
3048
+ });
3049
+ }
3050
+ });
2873
3051
 
2874
3052
  // src/index-node.ts
2875
3053
  var exports_index_node = {};
@@ -2882,6 +3060,7 @@ __export(exports_index_node, {
2882
3060
  getDefaultBranch: () => getDefaultBranch,
2883
3061
  getCurrentBranch: () => getCurrentBranch,
2884
3062
  getAgentArtifactsPath: () => getAgentArtifactsPath,
3063
+ extractJsonFromLLMOutput: () => extractJsonFromLLMOutput,
2885
3064
  detectRemoteProvider: () => detectRemoteProvider,
2886
3065
  createAiRunner: () => createAiRunner,
2887
3066
  c: () => c,
@@ -2910,6 +3089,7 @@ __export(exports_index_node, {
2910
3089
  KnowledgeBase: () => KnowledgeBase,
2911
3090
  InvitationsModule: () => InvitationsModule,
2912
3091
  HistoryManager: () => HistoryManager,
3092
+ GitWorkflow: () => GitWorkflow,
2913
3093
  ExecSession: () => ExecSession,
2914
3094
  ExecEventType: () => ExecEventType,
2915
3095
  ExecEventEmitter: () => ExecEventEmitter,
@@ -3148,6 +3328,51 @@ class CodebaseIndexer {
3148
3328
  }
3149
3329
  }
3150
3330
 
3331
+ // src/utils/json-extractor.ts
3332
+ function extractJsonFromLLMOutput(raw) {
3333
+ const trimmed = raw.trim();
3334
+ const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
3335
+ if (codeBlockMatch) {
3336
+ return codeBlockMatch[1]?.trim() || "";
3337
+ }
3338
+ if (trimmed.startsWith("{")) {
3339
+ return trimmed;
3340
+ }
3341
+ const startIdx = trimmed.indexOf("{");
3342
+ if (startIdx === -1) {
3343
+ return trimmed;
3344
+ }
3345
+ let depth = 0;
3346
+ let inString = false;
3347
+ let escaped = false;
3348
+ for (let i = startIdx;i < trimmed.length; i++) {
3349
+ const ch = trimmed[i];
3350
+ if (escaped) {
3351
+ escaped = false;
3352
+ continue;
3353
+ }
3354
+ if (ch === "\\") {
3355
+ escaped = true;
3356
+ continue;
3357
+ }
3358
+ if (ch === '"') {
3359
+ inString = !inString;
3360
+ continue;
3361
+ }
3362
+ if (inString)
3363
+ continue;
3364
+ if (ch === "{")
3365
+ depth++;
3366
+ else if (ch === "}") {
3367
+ depth--;
3368
+ if (depth === 0) {
3369
+ return trimmed.slice(startIdx, i + 1);
3370
+ }
3371
+ }
3372
+ }
3373
+ return trimmed.slice(startIdx);
3374
+ }
3375
+
3151
3376
  // src/agent/codebase-indexer-service.ts
3152
3377
  class CodebaseIndexerService {
3153
3378
  deps;
@@ -3173,11 +3398,12 @@ ${tree}
3173
3398
 
3174
3399
  Return ONLY valid JSON, no markdown formatting.`;
3175
3400
  const response = await this.deps.aiRunner.run(prompt);
3176
- const jsonMatch = response.match(/\{[\s\S]*\}/);
3177
- if (jsonMatch) {
3178
- return JSON.parse(jsonMatch[0]);
3401
+ const jsonStr = extractJsonFromLLMOutput(response);
3402
+ try {
3403
+ return JSON.parse(jsonStr);
3404
+ } catch {
3405
+ return { symbols: {}, responsibilities: {}, lastIndexed: "" };
3179
3406
  }
3180
- return { symbols: {}, responsibilities: {}, lastIndexed: "" };
3181
3407
  }, force);
3182
3408
  if (index === null) {
3183
3409
  this.deps.log("No changes detected, skipping reindex", "info");
@@ -3191,8 +3417,10 @@ Return ONLY valid JSON, no markdown formatting.`;
3191
3417
  }
3192
3418
  }
3193
3419
  // src/agent/document-fetcher.ts
3420
+ init_config();
3194
3421
  var import_node_fs7 = require("node:fs");
3195
3422
  var import_node_path9 = require("node:path");
3423
+
3196
3424
  class DocumentFetcher {
3197
3425
  deps;
3198
3426
  constructor(deps) {
@@ -3234,6 +3462,10 @@ class DocumentFetcher {
3234
3462
  }
3235
3463
  }
3236
3464
  }
3465
+
3466
+ // src/agent/index.ts
3467
+ init_git_workflow();
3468
+
3237
3469
  // src/agent/review-service.ts
3238
3470
  var import_node_child_process6 = require("node:child_process");
3239
3471
 
@@ -3293,6 +3525,13 @@ Keep the review concise but thorough. Focus on substance over style.`;
3293
3525
  }
3294
3526
  }
3295
3527
  // src/agent/reviewer-worker.ts
3528
+ init_factory();
3529
+ init_config();
3530
+ init_git_utils();
3531
+ init_pr_service();
3532
+ init_src();
3533
+ init_knowledge_base();
3534
+ init_colors();
3296
3535
  function resolveProvider2(value) {
3297
3536
  if (!value || value.startsWith("--"))
3298
3537
  return PROVIDER.CLAUDE;
@@ -3451,9 +3690,12 @@ ${summary}`;
3451
3690
  const status = result.approved ? "APPROVED" : "CHANGES REQUESTED";
3452
3691
  try {
3453
3692
  this.knowledgeBase.updateProgress({
3454
- type: "pr_reviewed",
3455
- title: pr.title,
3456
- details: `Review: ${status}`
3693
+ role: "user",
3694
+ content: `Review PR #${pr.number}: ${pr.title}`
3695
+ });
3696
+ this.knowledgeBase.updateProgress({
3697
+ role: "assistant",
3698
+ content: `${status}: ${result.summary}`
3457
3699
  });
3458
3700
  } catch {}
3459
3701
  this.reviewsCompleted++;
@@ -3505,6 +3747,21 @@ if (reviewerEntrypoint === "reviewer-worker.js" || reviewerEntrypoint === "revie
3505
3747
  process.exit(1);
3506
3748
  });
3507
3749
  }
3750
+
3751
+ // src/agent/index.ts
3752
+ init_task_executor();
3753
+ init_worker();
3754
+ // src/ai/index.ts
3755
+ init_claude_runner();
3756
+ init_codex_runner();
3757
+ init_factory();
3758
+ // src/core/index.ts
3759
+ init_config();
3760
+ init_prompt_builder();
3761
+
3762
+ // src/index-node.ts
3763
+ init_prompt_builder();
3764
+
3508
3765
  // src/exec/context-tracker.ts
3509
3766
  var REFERENCE_ALIASES = {
3510
3767
  plan: ["the plan", "sprint plan", "project plan", "implementation plan"],
@@ -3918,6 +4175,7 @@ class ExecEventEmitter {
3918
4175
  }
3919
4176
  }
3920
4177
  // src/exec/history-manager.ts
4178
+ init_config();
3921
4179
  var import_node_fs8 = require("node:fs");
3922
4180
  var import_node_path10 = require("node:path");
3923
4181
  var DEFAULT_MAX_SESSIONS = 30;
@@ -4295,62 +4553,474 @@ ${currentPrompt}`);
4295
4553
 
4296
4554
  `);
4297
4555
  }
4298
- save() {
4299
- if (!this.currentSession) {
4300
- throw new Error("Session not initialized. Call initialize() first.");
4556
+ save() {
4557
+ if (!this.currentSession) {
4558
+ throw new Error("Session not initialized. Call initialize() first.");
4559
+ }
4560
+ if (this.contextTracker.hasContent()) {
4561
+ this.currentSession.metadata.contextTracker = this.contextTracker.toJSON();
4562
+ }
4563
+ this.history.saveSession(this.currentSession);
4564
+ this.history.pruneSessions();
4565
+ }
4566
+ reset() {
4567
+ if (!this.currentSession) {
4568
+ throw new Error("Session not initialized. Call initialize() first.");
4569
+ }
4570
+ this.currentSession.messages = [];
4571
+ this.currentSession.updatedAt = Date.now();
4572
+ this.contextTracker.clear();
4573
+ }
4574
+ startNewSession() {
4575
+ this.currentSession = this.history.createNewSession(this.model, this.provider);
4576
+ this.contextTracker.clear();
4577
+ this.eventEmitter.emitSessionStarted({
4578
+ model: this.model,
4579
+ provider: this.provider
4580
+ });
4581
+ }
4582
+ end(success = true) {
4583
+ this.eventEmitter.emitSessionEnded(success);
4584
+ }
4585
+ on(eventType, listener) {
4586
+ this.eventEmitter.on(eventType, listener);
4587
+ return this;
4588
+ }
4589
+ off(eventType, listener) {
4590
+ this.eventEmitter.off(eventType, listener);
4591
+ return this;
4592
+ }
4593
+ }
4594
+ // src/git/index.ts
4595
+ init_git_utils();
4596
+ init_pr_service();
4597
+
4598
+ // src/index-node.ts
4599
+ init_src();
4600
+
4601
+ // src/orchestrator/index.ts
4602
+ init_git_utils();
4603
+ init_src();
4604
+ init_colors();
4605
+ init_worktree_manager();
4606
+ var import_shared6 = require("@locusai/shared");
4607
+ var import_events5 = require("events");
4608
+
4609
+ // src/orchestrator/agent-pool.ts
4610
+ init_colors();
4611
+ init_resolve_bin();
4612
+ var import_node_child_process7 = require("node:child_process");
4613
+ var import_node_fs9 = require("node:fs");
4614
+ var import_node_path11 = require("node:path");
4615
+ var import_node_url = require("node:url");
4616
+ var import_shared4 = require("@locusai/shared");
4617
+ var import_events4 = require("events");
4618
+ var MAX_AGENTS = 5;
4619
+
4620
+ class AgentPool extends import_events4.EventEmitter {
4621
+ config;
4622
+ agents = new Map;
4623
+ heartbeatInterval = null;
4624
+ constructor(config) {
4625
+ super();
4626
+ this.config = config;
4627
+ }
4628
+ get size() {
4629
+ return this.agents.size;
4630
+ }
4631
+ get effectiveAgentCount() {
4632
+ return Math.min(Math.max(this.config.agentCount ?? 1, 1), MAX_AGENTS);
4633
+ }
4634
+ getAll() {
4635
+ return Array.from(this.agents.values());
4636
+ }
4637
+ get(agentId) {
4638
+ return this.agents.get(agentId);
4639
+ }
4640
+ async spawn(index, resolvedSprintId, baseBranch) {
4641
+ const agentId = `agent-${index}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
4642
+ const agentState = {
4643
+ id: agentId,
4644
+ status: "IDLE",
4645
+ currentTaskId: null,
4646
+ tasksCompleted: 0,
4647
+ tasksFailed: 0,
4648
+ lastHeartbeat: new Date
4649
+ };
4650
+ this.agents.set(agentId, agentState);
4651
+ console.log(`${c.primary("\uD83D\uDE80 Agent started:")} ${c.bold(agentId)}
4652
+ `);
4653
+ const workerPath = this.resolveWorkerPath();
4654
+ if (!workerPath) {
4655
+ throw new Error("Worker file not found. Make sure the SDK is properly built and installed.");
4656
+ }
4657
+ const workerArgs = this.buildWorkerArgs(agentId, resolvedSprintId, baseBranch);
4658
+ const agentProcess = import_node_child_process7.spawn(process.execPath, [workerPath, ...workerArgs], {
4659
+ stdio: ["pipe", "pipe", "pipe"],
4660
+ detached: true,
4661
+ env: getAugmentedEnv({
4662
+ FORCE_COLOR: "1",
4663
+ TERM: "xterm-256color",
4664
+ LOCUS_WORKER: agentId,
4665
+ LOCUS_WORKSPACE: this.config.workspaceId
4666
+ })
4667
+ });
4668
+ agentState.process = agentProcess;
4669
+ this.attachProcessHandlers(agentId, agentState, agentProcess);
4670
+ this.emit("agent:spawned", { agentId });
4671
+ }
4672
+ async waitForAll(isRunning) {
4673
+ while (this.agents.size > 0 && isRunning()) {
4674
+ await sleep(2000);
4675
+ }
4676
+ }
4677
+ startHeartbeatMonitor() {
4678
+ this.heartbeatInterval = setInterval(() => {
4679
+ const now = Date.now();
4680
+ for (const [agentId, agent] of this.agents.entries()) {
4681
+ if (agent.status === "WORKING" && now - agent.lastHeartbeat.getTime() > import_shared4.STALE_AGENT_TIMEOUT_MS) {
4682
+ console.log(c.error(`Agent ${agentId} is stale (no heartbeat for 10 minutes). Killing.`));
4683
+ if (agent.process && !agent.process.killed) {
4684
+ killProcessTree(agent.process);
4685
+ }
4686
+ this.emit("agent:stale", { agentId });
4687
+ }
4688
+ }
4689
+ }, 60000);
4690
+ }
4691
+ stopAgent(agentId) {
4692
+ const agent = this.agents.get(agentId);
4693
+ if (!agent)
4694
+ return false;
4695
+ if (agent.process && !agent.process.killed) {
4696
+ killProcessTree(agent.process);
4697
+ }
4698
+ return true;
4699
+ }
4700
+ shutdown() {
4701
+ if (this.heartbeatInterval) {
4702
+ clearInterval(this.heartbeatInterval);
4703
+ this.heartbeatInterval = null;
4704
+ }
4705
+ for (const [agentId, agent] of this.agents.entries()) {
4706
+ if (agent.process && !agent.process.killed) {
4707
+ console.log(`Killing agent: ${agentId}`);
4708
+ killProcessTree(agent.process);
4709
+ }
4710
+ }
4711
+ this.agents.clear();
4712
+ }
4713
+ getStats() {
4714
+ return {
4715
+ activeAgents: this.agents.size,
4716
+ agentCount: this.effectiveAgentCount,
4717
+ totalTasksCompleted: this.getAll().reduce((sum, a) => sum + a.tasksCompleted, 0),
4718
+ totalTasksFailed: this.getAll().reduce((sum, a) => sum + a.tasksFailed, 0)
4719
+ };
4720
+ }
4721
+ buildWorkerArgs(agentId, resolvedSprintId, baseBranch) {
4722
+ const args = [
4723
+ "--agent-id",
4724
+ agentId,
4725
+ "--workspace-id",
4726
+ this.config.workspaceId,
4727
+ "--api-url",
4728
+ this.config.apiBase,
4729
+ "--api-key",
4730
+ this.config.apiKey,
4731
+ "--project-path",
4732
+ this.config.projectPath
4733
+ ];
4734
+ if (this.config.model) {
4735
+ args.push("--model", this.config.model);
4736
+ }
4737
+ if (this.config.provider) {
4738
+ args.push("--provider", this.config.provider);
4739
+ }
4740
+ if (resolvedSprintId) {
4741
+ args.push("--sprint-id", resolvedSprintId);
4742
+ }
4743
+ if (this.config.useWorktrees ?? true) {
4744
+ args.push("--use-worktrees");
4745
+ }
4746
+ if (this.config.autoPush) {
4747
+ args.push("--auto-push");
4748
+ }
4749
+ if (baseBranch) {
4750
+ args.push("--base-branch", baseBranch);
4751
+ }
4752
+ return args;
4753
+ }
4754
+ attachProcessHandlers(agentId, agentState, proc) {
4755
+ proc.on("message", (msg) => {
4756
+ if (msg.type === "stats") {
4757
+ agentState.tasksCompleted = msg.tasksCompleted || 0;
4758
+ agentState.tasksFailed = msg.tasksFailed || 0;
4759
+ }
4760
+ if (msg.type === "heartbeat") {
4761
+ agentState.lastHeartbeat = new Date;
4762
+ }
4763
+ });
4764
+ proc.stdout?.on("data", (data) => {
4765
+ process.stdout.write(data.toString());
4766
+ });
4767
+ proc.stderr?.on("data", (data) => {
4768
+ process.stderr.write(data.toString());
4769
+ });
4770
+ proc.on("exit", (code) => {
4771
+ console.log(`
4772
+ ${agentId} finished (exit code: ${code})`);
4773
+ const agent = this.agents.get(agentId);
4774
+ if (agent) {
4775
+ agent.status = code === 0 ? "COMPLETED" : "FAILED";
4776
+ this.emit("agent:completed", {
4777
+ agentId,
4778
+ status: agent.status,
4779
+ tasksCompleted: agent.tasksCompleted,
4780
+ tasksFailed: agent.tasksFailed
4781
+ });
4782
+ this.agents.delete(agentId);
4783
+ }
4784
+ });
4785
+ }
4786
+ resolveWorkerPath() {
4787
+ const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/agent-pool.ts");
4788
+ const currentModuleDir = import_node_path11.dirname(currentModulePath);
4789
+ const potentialPaths = [
4790
+ import_node_path11.join(currentModuleDir, "..", "agent", "worker.js"),
4791
+ import_node_path11.join(currentModuleDir, "agent", "worker.js"),
4792
+ import_node_path11.join(currentModuleDir, "worker.js"),
4793
+ import_node_path11.join(currentModuleDir, "..", "agent", "worker.ts")
4794
+ ];
4795
+ return potentialPaths.find((p) => import_node_fs9.existsSync(p));
4796
+ }
4797
+ }
4798
+ function killProcessTree(proc) {
4799
+ if (!proc.pid || proc.killed)
4800
+ return;
4801
+ try {
4802
+ process.kill(-proc.pid, "SIGTERM");
4803
+ } catch {
4804
+ try {
4805
+ proc.kill("SIGTERM");
4806
+ } catch {}
4807
+ }
4808
+ }
4809
+ function sleep(ms) {
4810
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
4811
+ }
4812
+
4813
+ // src/orchestrator/execution.ts
4814
+ init_git_utils();
4815
+ init_colors();
4816
+ var import_shared5 = require("@locusai/shared");
4817
+ var SPAWN_DELAY_MS = 5000;
4818
+
4819
+ class ExecutionStrategy {
4820
+ config;
4821
+ pool;
4822
+ tierMerge;
4823
+ resolvedSprintId;
4824
+ isRunning;
4825
+ constructor(config, pool, tierMerge, resolvedSprintId, isRunning) {
4826
+ this.config = config;
4827
+ this.pool = pool;
4828
+ this.tierMerge = tierMerge;
4829
+ this.resolvedSprintId = resolvedSprintId;
4830
+ this.isRunning = isRunning;
4831
+ }
4832
+ async execute(tasks2) {
4833
+ const hasTiers = tasks2.some((t) => t.tier !== null && t.tier !== undefined);
4834
+ const useWorktrees = this.config.useWorktrees ?? true;
4835
+ if (hasTiers && useWorktrees) {
4836
+ await this.tierBasedExecution(tasks2);
4837
+ } else {
4838
+ await this.legacyExecution(tasks2);
4839
+ }
4840
+ }
4841
+ async tierBasedExecution(allTasks) {
4842
+ const tierMap = groupByTier(allTasks);
4843
+ const tiers = Array.from(tierMap.keys()).sort((a, b) => a - b);
4844
+ const defaultBranch = getDefaultBranch(this.config.projectPath);
4845
+ console.log(c.primary(`\uD83D\uDCCA Tier-based execution: ${tiers.length} tier(s) detected [${tiers.join(", ")}]`));
4846
+ let currentBaseBranch = defaultBranch;
4847
+ for (const tier of tiers) {
4848
+ if (!this.isRunning())
4849
+ break;
4850
+ const tierTasks = tierMap.get(tier) ?? [];
4851
+ const dispatchable = tierTasks.filter(isDispatchable);
4852
+ if (dispatchable.length === 0) {
4853
+ console.log(c.dim(`ℹ Tier ${tier}: all ${tierTasks.length} task(s) already completed, skipping`));
4854
+ const tierBranch = this.tierMerge.tierBranchName(tier);
4855
+ if (this.tierMerge.remoteBranchExists(tierBranch)) {
4856
+ currentBaseBranch = tierBranch;
4857
+ }
4858
+ continue;
4859
+ }
4860
+ console.log(`
4861
+ ${c.primary(`\uD83C\uDFD7️ Tier ${tier}:`)} ${dispatchable.length} task(s) | base: ${c.bold(currentBaseBranch)}`);
4862
+ await this.spawnAgentsForTasks(dispatchable.length, currentBaseBranch);
4863
+ await this.pool.waitForAll(this.isRunning);
4864
+ console.log(c.success(`✓ Tier ${tier} complete`));
4865
+ if (this.config.autoPush && tiers.indexOf(tier) < tiers.length - 1) {
4866
+ const mergeBranch = this.tierMerge.createMergeBranch(tier, currentBaseBranch);
4867
+ if (mergeBranch) {
4868
+ currentBaseBranch = mergeBranch;
4869
+ console.log(c.success(`\uD83D\uDCCC Created merge branch: ${mergeBranch} (base for tier ${tier + 1})`));
4870
+ }
4871
+ }
4872
+ }
4873
+ }
4874
+ async legacyExecution(tasks2) {
4875
+ const defaultBranch = getDefaultBranch(this.config.projectPath);
4876
+ await this.spawnAgentsForTasks(tasks2.length, defaultBranch);
4877
+ await this.pool.waitForAll(this.isRunning);
4878
+ }
4879
+ async spawnAgentsForTasks(taskCount, baseBranch) {
4880
+ const agentsToSpawn = Math.min(this.pool.effectiveAgentCount, taskCount);
4881
+ const spawnPromises = [];
4882
+ for (let i = 0;i < agentsToSpawn; i++) {
4883
+ if (i > 0) {
4884
+ await sleep2(SPAWN_DELAY_MS);
4885
+ }
4886
+ spawnPromises.push(this.pool.spawn(i, this.resolvedSprintId, baseBranch));
4887
+ }
4888
+ await Promise.all(spawnPromises);
4889
+ }
4890
+ }
4891
+ function groupByTier(tasks2) {
4892
+ const tierMap = new Map;
4893
+ for (const task of tasks2) {
4894
+ const tier = task.tier ?? 0;
4895
+ const existing = tierMap.get(tier);
4896
+ if (existing) {
4897
+ existing.push(task);
4898
+ } else {
4899
+ tierMap.set(tier, [task]);
4900
+ }
4901
+ }
4902
+ return tierMap;
4903
+ }
4904
+ function isDispatchable(task) {
4905
+ return task.status === import_shared5.TaskStatus.BACKLOG || task.status === import_shared5.TaskStatus.IN_PROGRESS && !task.assignedTo;
4906
+ }
4907
+ function sleep2(ms) {
4908
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
4909
+ }
4910
+
4911
+ // src/orchestrator/tier-merge.ts
4912
+ init_colors();
4913
+ var import_node_child_process8 = require("node:child_process");
4914
+ var TIER_BRANCH_PREFIX = "locus/tier";
4915
+
4916
+ class TierMergeService {
4917
+ projectPath;
4918
+ sprintId;
4919
+ tierTaskIds = new Map;
4920
+ constructor(projectPath, sprintId) {
4921
+ this.projectPath = projectPath;
4922
+ this.sprintId = sprintId;
4923
+ }
4924
+ registerTierTasks(tasks2) {
4925
+ for (const task of tasks2) {
4926
+ const tier = task.tier ?? 0;
4927
+ const existing = this.tierTaskIds.get(tier);
4928
+ if (existing) {
4929
+ existing.push(task.id);
4930
+ } else {
4931
+ this.tierTaskIds.set(tier, [task.id]);
4932
+ }
4933
+ }
4934
+ }
4935
+ tierBranchName(tier) {
4936
+ const suffix = this.sprintId ? `-${this.sprintId.slice(0, 8)}` : "";
4937
+ return `${TIER_BRANCH_PREFIX}-${tier}${suffix}`;
4938
+ }
4939
+ remoteBranchExists(branch) {
4940
+ try {
4941
+ import_node_child_process8.execFileSync("git", ["ls-remote", "--exit-code", "--heads", "origin", branch], {
4942
+ cwd: this.projectPath,
4943
+ encoding: "utf-8",
4944
+ stdio: ["pipe", "pipe", "pipe"]
4945
+ });
4946
+ return true;
4947
+ } catch {
4948
+ return false;
4301
4949
  }
4302
- if (this.contextTracker.hasContent()) {
4303
- this.currentSession.metadata.contextTracker = this.contextTracker.toJSON();
4950
+ }
4951
+ createMergeBranch(tier, baseBranch) {
4952
+ const mergeBranchName = this.tierBranchName(tier);
4953
+ try {
4954
+ this.gitExec(["fetch", "origin"]);
4955
+ const tierTaskBranches = this.findTierTaskBranches(tier);
4956
+ if (tierTaskBranches.length === 0) {
4957
+ console.log(c.dim(` Tier ${tier}: no pushed task branches found, skipping merge branch creation`));
4958
+ return null;
4959
+ }
4960
+ console.log(c.dim(` Merging ${tierTaskBranches.length} branch(es) into ${mergeBranchName}: ${tierTaskBranches.join(", ")}`));
4961
+ try {
4962
+ this.gitExec(["branch", "-D", mergeBranchName]);
4963
+ } catch {}
4964
+ this.gitExec(["checkout", "-b", mergeBranchName, `origin/${baseBranch}`]);
4965
+ for (const branch of tierTaskBranches) {
4966
+ try {
4967
+ this.gitExec(["merge", `origin/${branch}`, "--no-edit"]);
4968
+ } catch (err) {
4969
+ const msg = err instanceof Error ? err.message : String(err);
4970
+ console.log(c.error(` Merge conflict merging ${branch} into ${mergeBranchName}: ${msg}`));
4971
+ try {
4972
+ this.gitExec(["merge", "--abort"]);
4973
+ } catch {}
4974
+ }
4975
+ }
4976
+ this.gitExec(["push", "-u", "origin", mergeBranchName, "--force"]);
4977
+ this.gitExec(["checkout", baseBranch]);
4978
+ return mergeBranchName;
4979
+ } catch (err) {
4980
+ const msg = err instanceof Error ? err.message : String(err);
4981
+ console.log(c.error(`Failed to create tier merge branch: ${msg}`));
4982
+ try {
4983
+ this.gitExec(["checkout", baseBranch]);
4984
+ } catch {}
4985
+ return null;
4304
4986
  }
4305
- this.history.saveSession(this.currentSession);
4306
- this.history.pruneSessions();
4307
4987
  }
4308
- reset() {
4309
- if (!this.currentSession) {
4310
- throw new Error("Session not initialized. Call initialize() first.");
4988
+ findTierTaskBranches(tier) {
4989
+ try {
4990
+ const output = import_node_child_process8.execSync('git branch -r --list "origin/agent/*" --format="%(refname:short)"', { cwd: this.projectPath, encoding: "utf-8" }).trim();
4991
+ if (!output)
4992
+ return [];
4993
+ const remoteBranches = output.split(`
4994
+ `).map((b) => b.replace("origin/", ""));
4995
+ return remoteBranches.filter((branch) => {
4996
+ const match = branch.match(/^agent\/([^-]+)/);
4997
+ if (!match)
4998
+ return false;
4999
+ const taskIdPrefix = match[1];
5000
+ return this.tierTaskIds.get(tier)?.some((id) => id.startsWith(taskIdPrefix) || taskIdPrefix.startsWith(id.slice(0, 8))) ?? false;
5001
+ });
5002
+ } catch {
5003
+ return [];
4311
5004
  }
4312
- this.currentSession.messages = [];
4313
- this.currentSession.updatedAt = Date.now();
4314
- this.contextTracker.clear();
4315
5005
  }
4316
- startNewSession() {
4317
- this.currentSession = this.history.createNewSession(this.model, this.provider);
4318
- this.contextTracker.clear();
4319
- this.eventEmitter.emitSessionStarted({
4320
- model: this.model,
4321
- provider: this.provider
5006
+ gitExec(args) {
5007
+ return import_node_child_process8.execFileSync("git", args, {
5008
+ cwd: this.projectPath,
5009
+ encoding: "utf-8",
5010
+ stdio: ["pipe", "pipe", "pipe"]
4322
5011
  });
4323
5012
  }
4324
- end(success = true) {
4325
- this.eventEmitter.emitSessionEnded(success);
4326
- }
4327
- on(eventType, listener) {
4328
- this.eventEmitter.on(eventType, listener);
4329
- return this;
4330
- }
4331
- off(eventType, listener) {
4332
- this.eventEmitter.off(eventType, listener);
4333
- return this;
4334
- }
4335
5013
  }
4336
- // src/orchestrator.ts
4337
- var import_node_child_process7 = require("node:child_process");
4338
- var import_node_fs9 = require("node:fs");
4339
- var import_node_path11 = require("node:path");
4340
- var import_node_url = require("node:url");
4341
- var import_shared4 = require("@locusai/shared");
4342
- var import_events4 = require("events");
4343
- var MAX_AGENTS = 5;
4344
5014
 
4345
- class AgentOrchestrator extends import_events4.EventEmitter {
5015
+ // src/orchestrator/index.ts
5016
+ class AgentOrchestrator extends import_events5.EventEmitter {
4346
5017
  client;
4347
5018
  config;
4348
- agents = new Map;
5019
+ pool;
4349
5020
  isRunning = false;
4350
5021
  processedTasks = new Set;
4351
5022
  resolvedSprintId = null;
4352
5023
  worktreeManager = null;
4353
- heartbeatInterval = null;
4354
5024
  constructor(config) {
4355
5025
  super();
4356
5026
  this.config = config;
@@ -4358,9 +5028,10 @@ class AgentOrchestrator extends import_events4.EventEmitter {
4358
5028
  baseUrl: config.apiBase,
4359
5029
  token: config.apiKey
4360
5030
  });
4361
- }
4362
- get agentCount() {
4363
- return Math.min(Math.max(this.config.agentCount ?? 1, 1), MAX_AGENTS);
5031
+ this.pool = new AgentPool(config);
5032
+ this.pool.on("agent:spawned", (data) => this.emit("agent:spawned", data));
5033
+ this.pool.on("agent:completed", (data) => this.emit("agent:completed", data));
5034
+ this.pool.on("agent:stale", (data) => this.emit("agent:stale", data));
4364
5035
  }
4365
5036
  get useWorktrees() {
4366
5037
  return this.config.useWorktrees ?? true;
@@ -4404,6 +5075,28 @@ class AgentOrchestrator extends import_events4.EventEmitter {
4404
5075
  config: this.config,
4405
5076
  sprintId: this.resolvedSprintId
4406
5077
  });
5078
+ this.printBanner();
5079
+ const tasks2 = await this.getAvailableTasks();
5080
+ if (tasks2.length === 0) {
5081
+ console.log(c.dim("ℹ No available tasks found in the backlog."));
5082
+ return;
5083
+ }
5084
+ if (!this.preflightChecks(tasks2))
5085
+ return;
5086
+ if (this.useWorktrees) {
5087
+ this.worktreeManager = new WorktreeManager(this.config.projectPath, {
5088
+ cleanupPolicy: this.worktreeCleanupPolicy
5089
+ });
5090
+ }
5091
+ this.pool.startHeartbeatMonitor();
5092
+ const tierMerge = new TierMergeService(this.config.projectPath, this.resolvedSprintId);
5093
+ tierMerge.registerTierTasks(tasks2);
5094
+ const execution = new ExecutionStrategy(this.config, this.pool, tierMerge, this.resolvedSprintId, () => this.isRunning);
5095
+ await execution.execute(tasks2);
5096
+ console.log(`
5097
+ ${c.success("✅ Orchestrator finished")}`);
5098
+ }
5099
+ printBanner() {
4407
5100
  console.log(`
4408
5101
  ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
4409
5102
  console.log(c.dim("----------------------------------------------"));
@@ -4411,7 +5104,7 @@ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
4411
5104
  if (this.resolvedSprintId) {
4412
5105
  console.log(`${c.bold("Sprint:")} ${this.resolvedSprintId}`);
4413
5106
  }
4414
- console.log(`${c.bold("Agents:")} ${this.agentCount}`);
5107
+ console.log(`${c.bold("Agents:")} ${this.pool.effectiveAgentCount}`);
4415
5108
  console.log(`${c.bold("Worktrees:")} ${this.useWorktrees ? "enabled" : "disabled"}`);
4416
5109
  if (this.useWorktrees) {
4417
5110
  console.log(`${c.bold("Cleanup policy:")} ${this.worktreeCleanupPolicy}`);
@@ -4420,154 +5113,16 @@ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
4420
5113
  console.log(`${c.bold("API Base:")} ${this.config.apiBase}`);
4421
5114
  console.log(c.dim(`----------------------------------------------
4422
5115
  `));
4423
- const tasks2 = await this.getAvailableTasks();
4424
- if (tasks2.length === 0) {
4425
- console.log(c.dim("ℹ No available tasks found in the backlog."));
4426
- return;
4427
- }
4428
- if (tasks2.length > 0 && this.useWorktrees && !isGitAvailable()) {
5116
+ }
5117
+ preflightChecks(_tasks) {
5118
+ if (this.useWorktrees && !isGitAvailable()) {
4429
5119
  console.log(c.error("git is not installed. Worktree isolation requires git. Install from https://git-scm.com/"));
4430
- return;
5120
+ return false;
4431
5121
  }
4432
- if (tasks2.length > 0 && this.config.autoPush && !isGhAvailable(this.config.projectPath)) {
5122
+ if (this.config.autoPush && !isGhAvailable(this.config.projectPath)) {
4433
5123
  console.log(c.warning("GitHub CLI (gh) not available or not authenticated. Branch push can continue, but automatic PR creation may fail until gh is configured. Install from https://cli.github.com/"));
4434
5124
  }
4435
- if (tasks2.length > 0 && this.useWorktrees) {
4436
- this.worktreeManager = new WorktreeManager(this.config.projectPath, {
4437
- cleanupPolicy: this.worktreeCleanupPolicy
4438
- });
4439
- }
4440
- this.startHeartbeatMonitor();
4441
- const agentsToSpawn = Math.min(this.agentCount, tasks2.length);
4442
- const SPAWN_DELAY_MS = 5000;
4443
- const spawnPromises = [];
4444
- for (let i = 0;i < agentsToSpawn; i++) {
4445
- if (i > 0) {
4446
- await this.sleep(SPAWN_DELAY_MS);
4447
- }
4448
- spawnPromises.push(this.spawnAgent(i));
4449
- }
4450
- await Promise.all(spawnPromises);
4451
- while (this.agents.size > 0 && this.isRunning) {
4452
- if (this.agents.size === 0) {
4453
- break;
4454
- }
4455
- await this.sleep(2000);
4456
- }
4457
- console.log(`
4458
- ${c.success("✅ Orchestrator finished")}`);
4459
- }
4460
- async spawnAgent(index) {
4461
- const agentId = `agent-${index}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
4462
- const agentState = {
4463
- id: agentId,
4464
- status: "IDLE",
4465
- currentTaskId: null,
4466
- tasksCompleted: 0,
4467
- tasksFailed: 0,
4468
- lastHeartbeat: new Date
4469
- };
4470
- this.agents.set(agentId, agentState);
4471
- console.log(`${c.primary("\uD83D\uDE80 Agent started:")} ${c.bold(agentId)}
4472
- `);
4473
- const workerPath = this.resolveWorkerPath();
4474
- if (!workerPath) {
4475
- throw new Error("Worker file not found. Make sure the SDK is properly built and installed.");
4476
- }
4477
- const workerArgs = [
4478
- "--agent-id",
4479
- agentId,
4480
- "--workspace-id",
4481
- this.config.workspaceId,
4482
- "--api-url",
4483
- this.config.apiBase,
4484
- "--api-key",
4485
- this.config.apiKey,
4486
- "--project-path",
4487
- this.config.projectPath
4488
- ];
4489
- if (this.config.model) {
4490
- workerArgs.push("--model", this.config.model);
4491
- }
4492
- if (this.config.provider) {
4493
- workerArgs.push("--provider", this.config.provider);
4494
- }
4495
- if (this.resolvedSprintId) {
4496
- workerArgs.push("--sprint-id", this.resolvedSprintId);
4497
- }
4498
- if (this.useWorktrees) {
4499
- workerArgs.push("--use-worktrees");
4500
- }
4501
- if (this.config.autoPush) {
4502
- workerArgs.push("--auto-push");
4503
- }
4504
- const agentProcess = import_node_child_process7.spawn(process.execPath, [workerPath, ...workerArgs], {
4505
- stdio: ["pipe", "pipe", "pipe"],
4506
- detached: true,
4507
- env: {
4508
- ...process.env,
4509
- FORCE_COLOR: "1",
4510
- TERM: "xterm-256color",
4511
- LOCUS_WORKER: agentId,
4512
- LOCUS_WORKSPACE: this.config.workspaceId
4513
- }
4514
- });
4515
- agentState.process = agentProcess;
4516
- agentProcess.on("message", (msg) => {
4517
- if (msg.type === "stats") {
4518
- agentState.tasksCompleted = msg.tasksCompleted || 0;
4519
- agentState.tasksFailed = msg.tasksFailed || 0;
4520
- }
4521
- if (msg.type === "heartbeat") {
4522
- agentState.lastHeartbeat = new Date;
4523
- }
4524
- });
4525
- agentProcess.stdout?.on("data", (data) => {
4526
- process.stdout.write(data.toString());
4527
- });
4528
- agentProcess.stderr?.on("data", (data) => {
4529
- process.stderr.write(data.toString());
4530
- });
4531
- agentProcess.on("exit", (code) => {
4532
- console.log(`
4533
- ${agentId} finished (exit code: ${code})`);
4534
- const agent = this.agents.get(agentId);
4535
- if (agent) {
4536
- agent.status = code === 0 ? "COMPLETED" : "FAILED";
4537
- this.emit("agent:completed", {
4538
- agentId,
4539
- status: agent.status,
4540
- tasksCompleted: agent.tasksCompleted,
4541
- tasksFailed: agent.tasksFailed
4542
- });
4543
- this.agents.delete(agentId);
4544
- }
4545
- });
4546
- this.emit("agent:spawned", { agentId });
4547
- }
4548
- resolveWorkerPath() {
4549
- const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator.ts");
4550
- const currentModuleDir = import_node_path11.dirname(currentModulePath);
4551
- const potentialPaths = [
4552
- import_node_path11.join(currentModuleDir, "agent", "worker.js"),
4553
- import_node_path11.join(currentModuleDir, "worker.js"),
4554
- import_node_path11.join(currentModuleDir, "agent", "worker.ts")
4555
- ];
4556
- return potentialPaths.find((p) => import_node_fs9.existsSync(p));
4557
- }
4558
- startHeartbeatMonitor() {
4559
- this.heartbeatInterval = setInterval(() => {
4560
- const now = Date.now();
4561
- for (const [agentId, agent] of this.agents.entries()) {
4562
- if (agent.status === "WORKING" && now - agent.lastHeartbeat.getTime() > import_shared4.STALE_AGENT_TIMEOUT_MS) {
4563
- console.log(c.error(`Agent ${agentId} is stale (no heartbeat for 10 minutes). Killing.`));
4564
- if (agent.process && !agent.process.killed) {
4565
- this.killProcessTree(agent.process);
4566
- }
4567
- this.emit("agent:stale", { agentId });
4568
- }
4569
- }
4570
- }, 60000);
5125
+ return true;
4571
5126
  }
4572
5127
  async getAvailableTasks() {
4573
5128
  try {
@@ -4579,16 +5134,16 @@ ${agentId} finished (exit code: ${code})`);
4579
5134
  }
4580
5135
  }
4581
5136
  async assignTaskToAgent(agentId) {
4582
- const agent = this.agents.get(agentId);
5137
+ const agent = this.pool.get(agentId);
4583
5138
  if (!agent)
4584
5139
  return null;
4585
5140
  try {
4586
5141
  const tasks2 = await this.getAvailableTasks();
4587
5142
  const priorityOrder = [
4588
- import_shared4.TaskPriority.CRITICAL,
4589
- import_shared4.TaskPriority.HIGH,
4590
- import_shared4.TaskPriority.MEDIUM,
4591
- import_shared4.TaskPriority.LOW
5143
+ import_shared6.TaskPriority.CRITICAL,
5144
+ import_shared6.TaskPriority.HIGH,
5145
+ import_shared6.TaskPriority.MEDIUM,
5146
+ import_shared6.TaskPriority.LOW
4592
5147
  ];
4593
5148
  let task = tasks2.sort((a, b) => priorityOrder.indexOf(a.priority) - priorityOrder.indexOf(b.priority))[0];
4594
5149
  if (!task && tasks2.length > 0) {
@@ -4612,7 +5167,7 @@ ${agentId} finished (exit code: ${code})`);
4612
5167
  async completeTask(taskId, agentId, summary) {
4613
5168
  try {
4614
5169
  await this.client.tasks.update(taskId, this.config.workspaceId, {
4615
- status: import_shared4.TaskStatus.IN_REVIEW
5170
+ status: import_shared6.TaskStatus.IN_REVIEW
4616
5171
  });
4617
5172
  if (summary) {
4618
5173
  await this.client.tasks.addComment(taskId, this.config.workspaceId, {
@@ -4623,7 +5178,7 @@ ${summary}`
4623
5178
  });
4624
5179
  }
4625
5180
  this.processedTasks.add(taskId);
4626
- const agent = this.agents.get(agentId);
5181
+ const agent = this.pool.get(agentId);
4627
5182
  if (agent) {
4628
5183
  agent.tasksCompleted += 1;
4629
5184
  agent.currentTaskId = null;
@@ -4637,14 +5192,14 @@ ${summary}`
4637
5192
  async failTask(taskId, agentId, error) {
4638
5193
  try {
4639
5194
  await this.client.tasks.update(taskId, this.config.workspaceId, {
4640
- status: import_shared4.TaskStatus.BACKLOG,
5195
+ status: import_shared6.TaskStatus.BACKLOG,
4641
5196
  assignedTo: null
4642
5197
  });
4643
5198
  await this.client.tasks.addComment(taskId, this.config.workspaceId, {
4644
5199
  author: agentId,
4645
5200
  text: `❌ Agent failed: ${error}`
4646
5201
  });
4647
- const agent = this.agents.get(agentId);
5202
+ const agent = this.pool.get(agentId);
4648
5203
  if (agent) {
4649
5204
  agent.tasksFailed += 1;
4650
5205
  agent.currentTaskId = null;
@@ -4661,36 +5216,10 @@ ${summary}`
4661
5216
  this.emit("stopped", { timestamp: new Date });
4662
5217
  }
4663
5218
  stopAgent(agentId) {
4664
- const agent = this.agents.get(agentId);
4665
- if (!agent)
4666
- return false;
4667
- if (agent.process && !agent.process.killed) {
4668
- this.killProcessTree(agent.process);
4669
- }
4670
- return true;
4671
- }
4672
- killProcessTree(proc) {
4673
- if (!proc.pid || proc.killed)
4674
- return;
4675
- try {
4676
- process.kill(-proc.pid, "SIGTERM");
4677
- } catch {
4678
- try {
4679
- proc.kill("SIGTERM");
4680
- } catch {}
4681
- }
5219
+ return this.pool.stopAgent(agentId);
4682
5220
  }
4683
5221
  async cleanup() {
4684
- if (this.heartbeatInterval) {
4685
- clearInterval(this.heartbeatInterval);
4686
- this.heartbeatInterval = null;
4687
- }
4688
- for (const [agentId, agent] of this.agents.entries()) {
4689
- if (agent.process && !agent.process.killed) {
4690
- console.log(`Killing agent: ${agentId}`);
4691
- this.killProcessTree(agent.process);
4692
- }
4693
- }
5222
+ this.pool.shutdown();
4694
5223
  if (this.worktreeManager) {
4695
5224
  try {
4696
5225
  if (this.worktreeCleanupPolicy === "auto") {
@@ -4708,31 +5237,27 @@ ${summary}`
4708
5237
  console.log(c.dim("Could not clean up some worktrees"));
4709
5238
  }
4710
5239
  }
4711
- this.agents.clear();
4712
5240
  }
4713
5241
  getStats() {
5242
+ const poolStats = this.pool.getStats();
4714
5243
  return {
4715
- activeAgents: this.agents.size,
4716
- agentCount: this.agentCount,
5244
+ ...poolStats,
4717
5245
  useWorktrees: this.useWorktrees,
4718
- processedTasks: this.processedTasks.size,
4719
- totalTasksCompleted: Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.tasksCompleted, 0),
4720
- totalTasksFailed: Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.tasksFailed, 0)
5246
+ processedTasks: this.processedTasks.size
4721
5247
  };
4722
5248
  }
4723
5249
  getAgentStates() {
4724
- return Array.from(this.agents.values());
4725
- }
4726
- sleep(ms) {
4727
- return new Promise((resolve3) => setTimeout(resolve3, ms));
5250
+ return this.pool.getAll();
4728
5251
  }
4729
5252
  }
4730
5253
  // src/planning/plan-manager.ts
5254
+ init_config();
5255
+ init_knowledge_base();
4731
5256
  var import_node_fs10 = require("node:fs");
4732
5257
  var import_node_path12 = require("node:path");
4733
5258
 
4734
5259
  // src/planning/sprint-plan.ts
4735
- var import_shared5 = require("@locusai/shared");
5260
+ var import_shared7 = require("@locusai/shared");
4736
5261
  function sprintPlanToMarkdown(plan) {
4737
5262
  const lines = [];
4738
5263
  lines.push(`# Sprint Plan: ${plan.name}`);
@@ -4754,23 +5279,32 @@ function sprintPlanToMarkdown(plan) {
4754
5279
  }
4755
5280
  lines.push(`## Tasks (${plan.tasks.length})`);
4756
5281
  lines.push("");
4757
- for (const task of plan.tasks) {
4758
- lines.push(`### ${task.index}. ${task.title}`);
4759
- lines.push(`- **Role:** ${task.assigneeRole}`);
4760
- lines.push(`- **Priority:** ${task.priority}`);
4761
- lines.push(`- **Complexity:** ${"█".repeat(task.complexity)}${"░".repeat(5 - task.complexity)} (${task.complexity}/5)`);
4762
- if (task.labels.length > 0) {
4763
- lines.push(`- **Labels:** ${task.labels.join(", ")}`);
4764
- }
5282
+ const maxTier = Math.max(0, ...plan.tasks.map((t) => t.tier ?? 0));
5283
+ for (let tier = 0;tier <= maxTier; tier++) {
5284
+ const tierTasks = plan.tasks.filter((t) => (t.tier ?? 0) === tier);
5285
+ if (tierTasks.length === 0)
5286
+ continue;
5287
+ lines.push(`### Tier ${tier}${tier === 0 ? " (Foundation)" : ""}`);
5288
+ lines.push(tier === 0 ? "_These tasks run first and must complete before higher tiers start._" : `_These tasks run in parallel after Tier ${tier - 1} completes._`);
4765
5289
  lines.push("");
4766
- lines.push(task.description);
4767
- lines.push("");
4768
- if (task.acceptanceCriteria.length > 0) {
4769
- lines.push(`**Acceptance Criteria:**`);
4770
- for (const ac of task.acceptanceCriteria) {
4771
- lines.push(`- [ ] ${ac}`);
5290
+ for (const task of tierTasks) {
5291
+ lines.push(`#### ${task.index}. ${task.title}`);
5292
+ lines.push(`- **Role:** ${task.assigneeRole}`);
5293
+ lines.push(`- **Priority:** ${task.priority}`);
5294
+ lines.push(`- **Complexity:** ${"█".repeat(task.complexity)}${"░".repeat(5 - task.complexity)} (${task.complexity}/5)`);
5295
+ if (task.labels.length > 0) {
5296
+ lines.push(`- **Labels:** ${task.labels.join(", ")}`);
4772
5297
  }
4773
5298
  lines.push("");
5299
+ lines.push(task.description);
5300
+ lines.push("");
5301
+ if (task.acceptanceCriteria.length > 0) {
5302
+ lines.push(`**Acceptance Criteria:**`);
5303
+ for (const ac of task.acceptanceCriteria) {
5304
+ lines.push(`- [ ] ${ac}`);
5305
+ }
5306
+ lines.push("");
5307
+ }
4774
5308
  }
4775
5309
  }
4776
5310
  if (plan.risks.length > 0) {
@@ -4791,12 +5325,13 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
4791
5325
  return plan.tasks.map((task) => ({
4792
5326
  title: task.title,
4793
5327
  description: task.description,
4794
- status: import_shared5.TaskStatus.BACKLOG,
5328
+ status: import_shared7.TaskStatus.BACKLOG,
4795
5329
  assigneeRole: task.assigneeRole,
4796
5330
  priority: task.priority,
4797
5331
  labels: task.labels,
4798
5332
  sprintId,
4799
5333
  order: task.index * 10,
5334
+ tier: task.tier,
4800
5335
  acceptanceChecklist: task.acceptanceCriteria.map((text, i) => ({
4801
5336
  id: `ac-${i + 1}`,
4802
5337
  text,
@@ -4805,11 +5340,7 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
4805
5340
  }));
4806
5341
  }
4807
5342
  function parseSprintPlanFromAI(raw, directive) {
4808
- let jsonStr = raw.trim();
4809
- const jsonMatch = jsonStr.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
4810
- if (jsonMatch) {
4811
- jsonStr = jsonMatch[1]?.trim() || "";
4812
- }
5343
+ const jsonStr = extractJsonFromLLMOutput(raw);
4813
5344
  let parsed = JSON.parse(jsonStr);
4814
5345
  if (parsed.revisedPlan) {
4815
5346
  parsed = parsed.revisedPlan;
@@ -4824,7 +5355,8 @@ function parseSprintPlanFromAI(raw, directive) {
4824
5355
  priority: t.priority || "MEDIUM",
4825
5356
  complexity: t.complexity || 3,
4826
5357
  acceptanceCriteria: t.acceptanceCriteria || [],
4827
- labels: t.labels || []
5358
+ labels: t.labels || [],
5359
+ tier: typeof t.tier === "number" ? t.tier : 0
4828
5360
  }));
4829
5361
  return {
4830
5362
  id,
@@ -4913,9 +5445,12 @@ class PlanManager {
4913
5445
  this.save(plan);
4914
5446
  const kb = new KnowledgeBase(this.projectPath);
4915
5447
  kb.updateProgress({
4916
- type: "sprint_started",
4917
- title: plan.name,
4918
- details: `${tasks2.length} tasks created from planning meeting. Sprint goal: ${plan.goal}`
5448
+ role: "user",
5449
+ content: `Start sprint: ${plan.name}`
5450
+ });
5451
+ kb.updateProgress({
5452
+ role: "assistant",
5453
+ content: `Sprint started with ${tasks2.length} tasks. Goal: ${plan.goal}`
4919
5454
  });
4920
5455
  return { sprint, tasks: tasks2 };
4921
5456
  }
@@ -4978,6 +5513,8 @@ class PlanManager {
4978
5513
  }
4979
5514
  }
4980
5515
  // src/planning/planning-meeting.ts
5516
+ init_config();
5517
+ init_knowledge_base();
4981
5518
  var import_node_fs11 = require("node:fs");
4982
5519
 
4983
5520
  // src/planning/agents/architect.ts
@@ -5032,7 +5569,7 @@ Tasks are executed by INDEPENDENT agents on SEPARATE git branches that get merge
5032
5569
 
5033
5570
  ## Output Format
5034
5571
 
5035
- Respond with ONLY a JSON object (no markdown code blocks, no explanation):
5572
+ Your entire response must be a single JSON object no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
5036
5573
 
5037
5574
  {
5038
5575
  "tasks": [
@@ -5066,13 +5603,16 @@ You are a combined Architect, Senior Engineer, and Sprint Planner performing a F
5066
5603
 
5067
5604
  ## Context
5068
5605
 
5069
- In this system, each task is executed by an independent AI agent that:
5070
- - Works on its own git branch (worktree)
5071
- - Has NO knowledge of what other agents are working on
5072
- - Cannot see changes made by other concurrent agents
5073
- - Its branch will be merged into main after completion
5606
+ In this system, tasks are organized into execution **tiers** (0, 1, 2, ...):
5607
+ - All tasks within the same tier run IN PARALLEL on separate git branches (worktrees)
5608
+ - Agents within a tier have NO knowledge of what other agents in that tier are doing
5609
+ - After ALL tasks in tier N complete and merge, tier N+1 starts
5610
+ - Tier N+1 branches are created FROM the merged tier N result, so they can see tier N's work
5074
5611
 
5075
- This means that if two tasks modify the same file, add the same dependency, or introduce the same config variable — they WILL cause merge conflicts and duplicated code.
5612
+ This means:
5613
+ - Two tasks in the SAME tier that modify the same file WILL cause merge conflicts
5614
+ - Two tasks in DIFFERENT tiers are safe — the later tier sees the earlier tier's merged output
5615
+ - Tier assignment is critical: foundational work must be in tier 0, dependent work in higher tiers
5076
5616
 
5077
5617
  ## CEO Directive
5078
5618
  > ${input.directive}
@@ -5096,42 +5636,50 @@ ${input.sprintOrganizerOutput}
5096
5636
 
5097
5637
  Go through EACH pair of tasks and check for:
5098
5638
 
5099
- ### 1. File Overlap Analysis
5639
+ ### 1. File Overlap Analysis (WITHIN the same tier)
5100
5640
  For each task, list the files it will likely modify. Then check:
5101
- - Do any two tasks modify the same file? (e.g., app.module.ts, configuration.ts, package.json, shared DTOs)
5102
- - If yes: MERGE those tasks or move shared changes to a foundational task
5641
+ - Do any two tasks **in the same tier** modify the same file? (e.g., app.module.ts, configuration.ts, package.json, shared DTOs)
5642
+ - If yes: MERGE those tasks, move them to different tiers, or move shared changes to a foundational task in a lower tier
5643
+ - Note: tasks in different tiers are safe because higher tiers branch from merged lower-tier results
5103
5644
 
5104
- ### 2. Duplicated Work Detection
5105
- Check if multiple tasks:
5645
+ ### 2. Duplicated Work Detection (WITHIN the same tier)
5646
+ Check if multiple tasks **in the same tier**:
5106
5647
  - Add the same environment variable or config field
5107
5648
  - Install or configure the same dependency
5108
5649
  - Register the same module or provider
5109
5650
  - Create the same helper function, guard, interceptor, or middleware
5110
5651
  - Add the same import to a shared file
5111
- If yes: consolidate into ONE task
5652
+ If yes: consolidate into ONE task or move the shared work to a lower tier
5112
5653
 
5113
5654
  ### 3. Self-Containment Validation
5114
5655
  For each task, verify:
5115
5656
  - Does it include ALL config/env changes it needs?
5116
5657
  - Does it include ALL module registrations it needs?
5117
5658
  - Does it include ALL dependency installations it needs?
5118
- - Can it be completed without ANY output from concurrent tasks?
5659
+ - Can it be completed without ANY output from tasks in the SAME tier? (It CAN depend on lower-tier tasks that are already merged)
5660
+
5661
+ ### 4. Tier Assignment Validation
5662
+ Verify tier assignments are correct:
5663
+ - Foundational tasks (schemas, config, shared code) MUST be in tier 0
5664
+ - Tasks that depend on another task's output must be in a HIGHER tier
5665
+ - Tasks in the same tier must be truly independent of each other
5666
+ - No circular dependencies between tiers
5119
5667
 
5120
- ### 4. Merge Conflict Risk Zones
5121
- Identify the highest-risk files (files that multiple tasks might touch) and ensure only ONE task modifies each.
5668
+ ### 5. Merge Conflict Risk Zones
5669
+ Identify the highest-risk files (files that multiple same-tier tasks might touch) and ensure only ONE task per tier modifies each.
5122
5670
 
5123
5671
  ## Output Format
5124
5672
 
5125
- Respond with ONLY a JSON object (no markdown code blocks, no explanation):
5673
+ Your entire response must be a single JSON object no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
5126
5674
 
5127
5675
  {
5128
5676
  "hasIssues": true | false,
5129
5677
  "issues": [
5130
5678
  {
5131
- "type": "file_overlap" | "duplicated_work" | "not_self_contained" | "merge_conflict_risk",
5679
+ "type": "file_overlap" | "duplicated_work" | "not_self_contained" | "merge_conflict_risk" | "wrong_tier",
5132
5680
  "description": "string describing the specific issue",
5133
5681
  "affectedTasks": ["Task Title 1", "Task Title 2"],
5134
- "resolution": "string describing how to fix it (merge, move, consolidate)"
5682
+ "resolution": "string describing how to fix it (merge, move to different tier, consolidate)"
5135
5683
  }
5136
5684
  ],
5137
5685
  "revisedPlan": {
@@ -5146,7 +5694,8 @@ Respond with ONLY a JSON object (no markdown code blocks, no explanation):
5146
5694
  "priority": "CRITICAL | HIGH | MEDIUM | LOW",
5147
5695
  "labels": ["string"],
5148
5696
  "acceptanceCriteria": ["string"],
5149
- "complexity": 3
5697
+ "complexity": 3,
5698
+ "tier": 0
5150
5699
  }
5151
5700
  ],
5152
5701
  "risks": [
@@ -5160,11 +5709,14 @@ Respond with ONLY a JSON object (no markdown code blocks, no explanation):
5160
5709
  }
5161
5710
 
5162
5711
  IMPORTANT:
5163
- - If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (tasks merged, duplicated work consolidated, etc.)
5712
+ - If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (tasks merged, duplicated work consolidated, tier assignments fixed, etc.)
5164
5713
  - If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
5165
5714
  - The revisedPlan is ALWAYS required — it becomes the final plan
5166
5715
  - When merging tasks, combine their acceptance criteria and update descriptions to cover all consolidated work
5167
- - Prefer fewer, larger, self-contained tasks over many small conflicting ones`;
5716
+ - Prefer fewer, larger, self-contained tasks over many small conflicting ones
5717
+ - Every task MUST have a "tier" field (integer >= 0)
5718
+ - tier 0 = foundational (runs first), tier 1 = depends on tier 0, tier 2 = depends on tier 1, etc.
5719
+ - Tasks within the same tier run in parallel — they MUST NOT conflict with each other`;
5168
5720
  return prompt;
5169
5721
  }
5170
5722
 
@@ -5196,13 +5748,16 @@ Produce the final sprint plan:
5196
5748
  1. **Sprint Name** — A concise, memorable name for this sprint (e.g., "User Authentication", "Payment Integration")
5197
5749
  2. **Sprint Goal** — One paragraph describing what this sprint delivers
5198
5750
  3. **Task Ordering** — Final ordering so that foundational work comes first. The position in the array IS the execution order — task at index 0 runs first, index 1 runs second, etc.
5199
- 4. **Duration Estimate** — How many days this sprint will take with 2-3 agents working in parallel
5200
- 5. **Final Task List** — Each task with all fields filled in, ordered by execution priority
5751
+ 4. **Tier Assignment** — Assign each task an execution tier (integer, starting at 0). Tasks within the same tier run IN PARALLEL on separate git branches. Tasks in tier N+1 only start AFTER all tier N tasks are complete and merged. Tier 0 = foundational tasks (config, schemas, shared code). Higher tiers build on lower tier outputs.
5752
+ 5. **Duration Estimate** — How many days this sprint will take with 2-3 agents working in parallel
5753
+ 6. **Final Task List** — Each task with all fields filled in, ordered by execution priority
5201
5754
 
5202
5755
  Guidelines:
5203
5756
  - The order of tasks in the array determines execution order. Tasks are dispatched sequentially from first to last.
5204
- - Foundation tasks (schemas, config, shared code) must appear before tasks that build on them
5205
- - Group related tasks together when possible
5757
+ - Foundation tasks (schemas, config, shared code) must appear before tasks that build on them AND must be in tier 0
5758
+ - Tasks within the same tier MUST be truly independent — no shared file modifications, no dependencies between them
5759
+ - Tasks that depend on outputs from other tasks must be in a higher tier than those dependencies
5760
+ - Group related independent tasks in the same tier for maximum parallelism
5206
5761
  - Ensure acceptance criteria are specific and testable
5207
5762
  - Keep the sprint focused — if it's too large (>12 tasks), consider reducing scope
5208
5763
 
@@ -5212,12 +5767,12 @@ Before finalizing, validate that EVERY task is fully self-contained and conflict
5212
5767
 
5213
5768
  1. **No two tasks should modify the same file.** If they do, merge them or restructure so shared changes live in one foundational task.
5214
5769
  2. **No duplicated work.** Each env var, config field, dependency, module import, or helper function must be introduced by exactly ONE task.
5215
- 3. **Each task is independently executable.** An agent working on task N must be able to complete it without knowing what tasks N-1 or N+1 are doing. The only exception is foundational tasks that are merged BEFORE dependent tasks start.
5770
+ 3. **Each task is independently executable within its tier.** An agent working on a task must be able to complete it without knowing what other tasks in the same tier are doing. Tasks CAN depend on lower-tier tasks since those are merged before the current tier starts.
5216
5771
  4. **Prefer fewer, larger self-contained tasks over many small overlapping ones.** Do not split a task if the parts would conflict with each other.
5217
5772
 
5218
5773
  ## Output Format
5219
5774
 
5220
- Respond with ONLY a JSON object (no markdown code blocks, no explanation):
5775
+ Your entire response must be a single JSON object no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
5221
5776
 
5222
5777
  {
5223
5778
  "name": "string (2-4 words)",
@@ -5231,7 +5786,8 @@ Respond with ONLY a JSON object (no markdown code blocks, no explanation):
5231
5786
  "priority": "CRITICAL | HIGH | MEDIUM | LOW",
5232
5787
  "labels": ["string"],
5233
5788
  "acceptanceCriteria": ["string"],
5234
- "complexity": 3
5789
+ "complexity": 3,
5790
+ "tier": 0
5235
5791
  }
5236
5792
  ],
5237
5793
  "risks": [
@@ -5241,7 +5797,14 @@ Respond with ONLY a JSON object (no markdown code blocks, no explanation):
5241
5797
  "severity": "low | medium | high"
5242
5798
  }
5243
5799
  ]
5244
- }`;
5800
+ }
5801
+
5802
+ IMPORTANT about tiers:
5803
+ - tier 0 = foundational tasks (run first, merged before anything else)
5804
+ - tier 1 = tasks that depend on tier 0 outputs (run in parallel after tier 0 merges)
5805
+ - tier 2 = tasks that depend on tier 1 outputs (run in parallel after tier 1 merges)
5806
+ - Tasks within the same tier run in parallel on separate branches — they MUST NOT conflict
5807
+ - Every task MUST have a "tier" field (integer >= 0)`;
5245
5808
  return prompt;
5246
5809
  }
5247
5810
 
@@ -5298,7 +5861,7 @@ Tasks will be executed by INDEPENDENT agents on SEPARATE git branches that get m
5298
5861
 
5299
5862
  ## Output Format
5300
5863
 
5301
- Respond with ONLY a JSON object (no markdown code blocks, no explanation):
5864
+ Your entire response must be a single JSON object no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
5302
5865
 
5303
5866
  {
5304
5867
  "tasks": [
@@ -5410,3 +5973,10 @@ class PlanningMeeting {
5410
5973
  }
5411
5974
  }
5412
5975
  }
5976
+ // src/index-node.ts
5977
+ init_knowledge_base();
5978
+ init_colors();
5979
+
5980
+ // src/worktree/index.ts
5981
+ init_worktree_config();
5982
+ init_worktree_manager();