@locusai/sdk 0.9.18 → 0.10.2

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 (40) hide show
  1. package/dist/agent/git-workflow.d.ts +44 -0
  2. package/dist/agent/git-workflow.d.ts.map +1 -0
  3. package/dist/agent/index.d.ts +2 -0
  4. package/dist/agent/index.d.ts.map +1 -1
  5. package/dist/agent/reviewer-worker.d.ts.map +1 -1
  6. package/dist/agent/worker-cli.d.ts +6 -0
  7. package/dist/agent/worker-cli.d.ts.map +1 -0
  8. package/dist/agent/worker-types.d.ts +44 -0
  9. package/dist/agent/worker-types.d.ts.map +1 -0
  10. package/dist/agent/worker.d.ts +12 -48
  11. package/dist/agent/worker.d.ts.map +1 -1
  12. package/dist/agent/worker.js +1026 -847
  13. package/dist/ai/claude-runner.d.ts.map +1 -1
  14. package/dist/index-node.d.ts +1 -1
  15. package/dist/index-node.d.ts.map +1 -1
  16. package/dist/index-node.js +1660 -1133
  17. package/dist/index.js +363 -316
  18. package/dist/orchestrator/agent-pool.d.ts +59 -0
  19. package/dist/orchestrator/agent-pool.d.ts.map +1 -0
  20. package/dist/orchestrator/execution.d.ts +55 -0
  21. package/dist/orchestrator/execution.d.ts.map +1 -0
  22. package/dist/orchestrator/index.d.ts +91 -0
  23. package/dist/orchestrator/index.d.ts.map +1 -0
  24. package/dist/orchestrator/tier-merge.d.ts +50 -0
  25. package/dist/orchestrator/tier-merge.d.ts.map +1 -0
  26. package/dist/orchestrator/types.d.ts +45 -0
  27. package/dist/orchestrator/types.d.ts.map +1 -0
  28. package/dist/planning/agents/cross-task-reviewer.d.ts.map +1 -1
  29. package/dist/planning/agents/sprint-organizer.d.ts.map +1 -1
  30. package/dist/planning/plan-manager.d.ts.map +1 -1
  31. package/dist/planning/sprint-plan.d.ts +2 -0
  32. package/dist/planning/sprint-plan.d.ts.map +1 -1
  33. package/dist/project/knowledge-base.d.ts +4 -5
  34. package/dist/project/knowledge-base.d.ts.map +1 -1
  35. package/dist/utils/resolve-bin.d.ts +3 -0
  36. package/dist/utils/resolve-bin.d.ts.map +1 -1
  37. package/dist/worktree/worktree-manager.d.ts.map +1 -1
  38. package/package.json +2 -2
  39. package/dist/orchestrator.d.ts +0 -124
  40. 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");
@@ -2820,56 +3018,36 @@ PR automation error: ${result.prError}` : "";
2820
3018
  this.sendHeartbeat();
2821
3019
  await this.delayAfterCleanup();
2822
3020
  }
2823
- this.currentTaskId = null;
2824
- this.stopHeartbeat();
2825
- 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);
3021
+ this.currentTaskId = null;
3022
+ this.stopHeartbeat();
3023
+ this.client.workspaces.heartbeat(this.config.workspaceId, this.config.agentId, null, "COMPLETED").catch(() => {});
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 = {};
@@ -2911,6 +3089,7 @@ __export(exports_index_node, {
2911
3089
  KnowledgeBase: () => KnowledgeBase,
2912
3090
  InvitationsModule: () => InvitationsModule,
2913
3091
  HistoryManager: () => HistoryManager,
3092
+ GitWorkflow: () => GitWorkflow,
2914
3093
  ExecSession: () => ExecSession,
2915
3094
  ExecEventType: () => ExecEventType,
2916
3095
  ExecEventEmitter: () => ExecEventEmitter,
@@ -3238,8 +3417,10 @@ Return ONLY valid JSON, no markdown formatting.`;
3238
3417
  }
3239
3418
  }
3240
3419
  // src/agent/document-fetcher.ts
3420
+ init_config();
3241
3421
  var import_node_fs7 = require("node:fs");
3242
3422
  var import_node_path9 = require("node:path");
3423
+
3243
3424
  class DocumentFetcher {
3244
3425
  deps;
3245
3426
  constructor(deps) {
@@ -3281,6 +3462,10 @@ class DocumentFetcher {
3281
3462
  }
3282
3463
  }
3283
3464
  }
3465
+
3466
+ // src/agent/index.ts
3467
+ init_git_workflow();
3468
+
3284
3469
  // src/agent/review-service.ts
3285
3470
  var import_node_child_process6 = require("node:child_process");
3286
3471
 
@@ -3340,6 +3525,13 @@ Keep the review concise but thorough. Focus on substance over style.`;
3340
3525
  }
3341
3526
  }
3342
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();
3343
3535
  function resolveProvider2(value) {
3344
3536
  if (!value || value.startsWith("--"))
3345
3537
  return PROVIDER.CLAUDE;
@@ -3498,9 +3690,12 @@ ${summary}`;
3498
3690
  const status = result.approved ? "APPROVED" : "CHANGES REQUESTED";
3499
3691
  try {
3500
3692
  this.knowledgeBase.updateProgress({
3501
- type: "pr_reviewed",
3502
- title: pr.title,
3503
- 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}`
3504
3699
  });
3505
3700
  } catch {}
3506
3701
  this.reviewsCompleted++;
@@ -3552,6 +3747,21 @@ if (reviewerEntrypoint === "reviewer-worker.js" || reviewerEntrypoint === "revie
3552
3747
  process.exit(1);
3553
3748
  });
3554
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
+
3555
3765
  // src/exec/context-tracker.ts
3556
3766
  var REFERENCE_ALIASES = {
3557
3767
  plan: ["the plan", "sprint plan", "project plan", "implementation plan"],
@@ -3965,6 +4175,7 @@ class ExecEventEmitter {
3965
4175
  }
3966
4176
  }
3967
4177
  // src/exec/history-manager.ts
4178
+ init_config();
3968
4179
  var import_node_fs8 = require("node:fs");
3969
4180
  var import_node_path10 = require("node:path");
3970
4181
  var DEFAULT_MAX_SESSIONS = 30;
@@ -4342,62 +4553,474 @@ ${currentPrompt}`);
4342
4553
 
4343
4554
  `);
4344
4555
  }
4345
- save() {
4346
- if (!this.currentSession) {
4347
- 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;
4348
4949
  }
4349
- if (this.contextTracker.hasContent()) {
4350
- 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;
4351
4986
  }
4352
- this.history.saveSession(this.currentSession);
4353
- this.history.pruneSessions();
4354
4987
  }
4355
- reset() {
4356
- if (!this.currentSession) {
4357
- 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 [];
4358
5004
  }
4359
- this.currentSession.messages = [];
4360
- this.currentSession.updatedAt = Date.now();
4361
- this.contextTracker.clear();
4362
5005
  }
4363
- startNewSession() {
4364
- this.currentSession = this.history.createNewSession(this.model, this.provider);
4365
- this.contextTracker.clear();
4366
- this.eventEmitter.emitSessionStarted({
4367
- model: this.model,
4368
- 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"]
4369
5011
  });
4370
5012
  }
4371
- end(success = true) {
4372
- this.eventEmitter.emitSessionEnded(success);
4373
- }
4374
- on(eventType, listener) {
4375
- this.eventEmitter.on(eventType, listener);
4376
- return this;
4377
- }
4378
- off(eventType, listener) {
4379
- this.eventEmitter.off(eventType, listener);
4380
- return this;
4381
- }
4382
5013
  }
4383
- // src/orchestrator.ts
4384
- var import_node_child_process7 = require("node:child_process");
4385
- var import_node_fs9 = require("node:fs");
4386
- var import_node_path11 = require("node:path");
4387
- var import_node_url = require("node:url");
4388
- var import_shared4 = require("@locusai/shared");
4389
- var import_events4 = require("events");
4390
- var MAX_AGENTS = 5;
4391
5014
 
4392
- class AgentOrchestrator extends import_events4.EventEmitter {
5015
+ // src/orchestrator/index.ts
5016
+ class AgentOrchestrator extends import_events5.EventEmitter {
4393
5017
  client;
4394
5018
  config;
4395
- agents = new Map;
5019
+ pool;
4396
5020
  isRunning = false;
4397
5021
  processedTasks = new Set;
4398
5022
  resolvedSprintId = null;
4399
5023
  worktreeManager = null;
4400
- heartbeatInterval = null;
4401
5024
  constructor(config) {
4402
5025
  super();
4403
5026
  this.config = config;
@@ -4405,9 +5028,10 @@ class AgentOrchestrator extends import_events4.EventEmitter {
4405
5028
  baseUrl: config.apiBase,
4406
5029
  token: config.apiKey
4407
5030
  });
4408
- }
4409
- get agentCount() {
4410
- 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));
4411
5035
  }
4412
5036
  get useWorktrees() {
4413
5037
  return this.config.useWorktrees ?? true;
@@ -4451,6 +5075,28 @@ class AgentOrchestrator extends import_events4.EventEmitter {
4451
5075
  config: this.config,
4452
5076
  sprintId: this.resolvedSprintId
4453
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() {
4454
5100
  console.log(`
4455
5101
  ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
4456
5102
  console.log(c.dim("----------------------------------------------"));
@@ -4458,7 +5104,7 @@ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
4458
5104
  if (this.resolvedSprintId) {
4459
5105
  console.log(`${c.bold("Sprint:")} ${this.resolvedSprintId}`);
4460
5106
  }
4461
- console.log(`${c.bold("Agents:")} ${this.agentCount}`);
5107
+ console.log(`${c.bold("Agents:")} ${this.pool.effectiveAgentCount}`);
4462
5108
  console.log(`${c.bold("Worktrees:")} ${this.useWorktrees ? "enabled" : "disabled"}`);
4463
5109
  if (this.useWorktrees) {
4464
5110
  console.log(`${c.bold("Cleanup policy:")} ${this.worktreeCleanupPolicy}`);
@@ -4467,154 +5113,16 @@ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
4467
5113
  console.log(`${c.bold("API Base:")} ${this.config.apiBase}`);
4468
5114
  console.log(c.dim(`----------------------------------------------
4469
5115
  `));
4470
- const tasks2 = await this.getAvailableTasks();
4471
- if (tasks2.length === 0) {
4472
- console.log(c.dim("ℹ No available tasks found in the backlog."));
4473
- return;
4474
- }
4475
- if (tasks2.length > 0 && this.useWorktrees && !isGitAvailable()) {
5116
+ }
5117
+ preflightChecks(_tasks) {
5118
+ if (this.useWorktrees && !isGitAvailable()) {
4476
5119
  console.log(c.error("git is not installed. Worktree isolation requires git. Install from https://git-scm.com/"));
4477
- return;
5120
+ return false;
4478
5121
  }
4479
- if (tasks2.length > 0 && this.config.autoPush && !isGhAvailable(this.config.projectPath)) {
5122
+ if (this.config.autoPush && !isGhAvailable(this.config.projectPath)) {
4480
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/"));
4481
5124
  }
4482
- if (tasks2.length > 0 && this.useWorktrees) {
4483
- this.worktreeManager = new WorktreeManager(this.config.projectPath, {
4484
- cleanupPolicy: this.worktreeCleanupPolicy
4485
- });
4486
- }
4487
- this.startHeartbeatMonitor();
4488
- const agentsToSpawn = Math.min(this.agentCount, tasks2.length);
4489
- const SPAWN_DELAY_MS = 5000;
4490
- const spawnPromises = [];
4491
- for (let i = 0;i < agentsToSpawn; i++) {
4492
- if (i > 0) {
4493
- await this.sleep(SPAWN_DELAY_MS);
4494
- }
4495
- spawnPromises.push(this.spawnAgent(i));
4496
- }
4497
- await Promise.all(spawnPromises);
4498
- while (this.agents.size > 0 && this.isRunning) {
4499
- if (this.agents.size === 0) {
4500
- break;
4501
- }
4502
- await this.sleep(2000);
4503
- }
4504
- console.log(`
4505
- ${c.success("✅ Orchestrator finished")}`);
4506
- }
4507
- async spawnAgent(index) {
4508
- const agentId = `agent-${index}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
4509
- const agentState = {
4510
- id: agentId,
4511
- status: "IDLE",
4512
- currentTaskId: null,
4513
- tasksCompleted: 0,
4514
- tasksFailed: 0,
4515
- lastHeartbeat: new Date
4516
- };
4517
- this.agents.set(agentId, agentState);
4518
- console.log(`${c.primary("\uD83D\uDE80 Agent started:")} ${c.bold(agentId)}
4519
- `);
4520
- const workerPath = this.resolveWorkerPath();
4521
- if (!workerPath) {
4522
- throw new Error("Worker file not found. Make sure the SDK is properly built and installed.");
4523
- }
4524
- const workerArgs = [
4525
- "--agent-id",
4526
- agentId,
4527
- "--workspace-id",
4528
- this.config.workspaceId,
4529
- "--api-url",
4530
- this.config.apiBase,
4531
- "--api-key",
4532
- this.config.apiKey,
4533
- "--project-path",
4534
- this.config.projectPath
4535
- ];
4536
- if (this.config.model) {
4537
- workerArgs.push("--model", this.config.model);
4538
- }
4539
- if (this.config.provider) {
4540
- workerArgs.push("--provider", this.config.provider);
4541
- }
4542
- if (this.resolvedSprintId) {
4543
- workerArgs.push("--sprint-id", this.resolvedSprintId);
4544
- }
4545
- if (this.useWorktrees) {
4546
- workerArgs.push("--use-worktrees");
4547
- }
4548
- if (this.config.autoPush) {
4549
- workerArgs.push("--auto-push");
4550
- }
4551
- const agentProcess = import_node_child_process7.spawn(process.execPath, [workerPath, ...workerArgs], {
4552
- stdio: ["pipe", "pipe", "pipe"],
4553
- detached: true,
4554
- env: {
4555
- ...process.env,
4556
- FORCE_COLOR: "1",
4557
- TERM: "xterm-256color",
4558
- LOCUS_WORKER: agentId,
4559
- LOCUS_WORKSPACE: this.config.workspaceId
4560
- }
4561
- });
4562
- agentState.process = agentProcess;
4563
- agentProcess.on("message", (msg) => {
4564
- if (msg.type === "stats") {
4565
- agentState.tasksCompleted = msg.tasksCompleted || 0;
4566
- agentState.tasksFailed = msg.tasksFailed || 0;
4567
- }
4568
- if (msg.type === "heartbeat") {
4569
- agentState.lastHeartbeat = new Date;
4570
- }
4571
- });
4572
- agentProcess.stdout?.on("data", (data) => {
4573
- process.stdout.write(data.toString());
4574
- });
4575
- agentProcess.stderr?.on("data", (data) => {
4576
- process.stderr.write(data.toString());
4577
- });
4578
- agentProcess.on("exit", (code) => {
4579
- console.log(`
4580
- ${agentId} finished (exit code: ${code})`);
4581
- const agent = this.agents.get(agentId);
4582
- if (agent) {
4583
- agent.status = code === 0 ? "COMPLETED" : "FAILED";
4584
- this.emit("agent:completed", {
4585
- agentId,
4586
- status: agent.status,
4587
- tasksCompleted: agent.tasksCompleted,
4588
- tasksFailed: agent.tasksFailed
4589
- });
4590
- this.agents.delete(agentId);
4591
- }
4592
- });
4593
- this.emit("agent:spawned", { agentId });
4594
- }
4595
- resolveWorkerPath() {
4596
- const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator.ts");
4597
- const currentModuleDir = import_node_path11.dirname(currentModulePath);
4598
- const potentialPaths = [
4599
- import_node_path11.join(currentModuleDir, "agent", "worker.js"),
4600
- import_node_path11.join(currentModuleDir, "worker.js"),
4601
- import_node_path11.join(currentModuleDir, "agent", "worker.ts")
4602
- ];
4603
- return potentialPaths.find((p) => import_node_fs9.existsSync(p));
4604
- }
4605
- startHeartbeatMonitor() {
4606
- this.heartbeatInterval = setInterval(() => {
4607
- const now = Date.now();
4608
- for (const [agentId, agent] of this.agents.entries()) {
4609
- if (agent.status === "WORKING" && now - agent.lastHeartbeat.getTime() > import_shared4.STALE_AGENT_TIMEOUT_MS) {
4610
- console.log(c.error(`Agent ${agentId} is stale (no heartbeat for 10 minutes). Killing.`));
4611
- if (agent.process && !agent.process.killed) {
4612
- this.killProcessTree(agent.process);
4613
- }
4614
- this.emit("agent:stale", { agentId });
4615
- }
4616
- }
4617
- }, 60000);
5125
+ return true;
4618
5126
  }
4619
5127
  async getAvailableTasks() {
4620
5128
  try {
@@ -4626,16 +5134,16 @@ ${agentId} finished (exit code: ${code})`);
4626
5134
  }
4627
5135
  }
4628
5136
  async assignTaskToAgent(agentId) {
4629
- const agent = this.agents.get(agentId);
5137
+ const agent = this.pool.get(agentId);
4630
5138
  if (!agent)
4631
5139
  return null;
4632
5140
  try {
4633
5141
  const tasks2 = await this.getAvailableTasks();
4634
5142
  const priorityOrder = [
4635
- import_shared4.TaskPriority.CRITICAL,
4636
- import_shared4.TaskPriority.HIGH,
4637
- import_shared4.TaskPriority.MEDIUM,
4638
- import_shared4.TaskPriority.LOW
5143
+ import_shared6.TaskPriority.CRITICAL,
5144
+ import_shared6.TaskPriority.HIGH,
5145
+ import_shared6.TaskPriority.MEDIUM,
5146
+ import_shared6.TaskPriority.LOW
4639
5147
  ];
4640
5148
  let task = tasks2.sort((a, b) => priorityOrder.indexOf(a.priority) - priorityOrder.indexOf(b.priority))[0];
4641
5149
  if (!task && tasks2.length > 0) {
@@ -4659,7 +5167,7 @@ ${agentId} finished (exit code: ${code})`);
4659
5167
  async completeTask(taskId, agentId, summary) {
4660
5168
  try {
4661
5169
  await this.client.tasks.update(taskId, this.config.workspaceId, {
4662
- status: import_shared4.TaskStatus.IN_REVIEW
5170
+ status: import_shared6.TaskStatus.IN_REVIEW
4663
5171
  });
4664
5172
  if (summary) {
4665
5173
  await this.client.tasks.addComment(taskId, this.config.workspaceId, {
@@ -4670,7 +5178,7 @@ ${summary}`
4670
5178
  });
4671
5179
  }
4672
5180
  this.processedTasks.add(taskId);
4673
- const agent = this.agents.get(agentId);
5181
+ const agent = this.pool.get(agentId);
4674
5182
  if (agent) {
4675
5183
  agent.tasksCompleted += 1;
4676
5184
  agent.currentTaskId = null;
@@ -4684,14 +5192,14 @@ ${summary}`
4684
5192
  async failTask(taskId, agentId, error) {
4685
5193
  try {
4686
5194
  await this.client.tasks.update(taskId, this.config.workspaceId, {
4687
- status: import_shared4.TaskStatus.BACKLOG,
5195
+ status: import_shared6.TaskStatus.BACKLOG,
4688
5196
  assignedTo: null
4689
5197
  });
4690
5198
  await this.client.tasks.addComment(taskId, this.config.workspaceId, {
4691
5199
  author: agentId,
4692
5200
  text: `❌ Agent failed: ${error}`
4693
5201
  });
4694
- const agent = this.agents.get(agentId);
5202
+ const agent = this.pool.get(agentId);
4695
5203
  if (agent) {
4696
5204
  agent.tasksFailed += 1;
4697
5205
  agent.currentTaskId = null;
@@ -4708,36 +5216,10 @@ ${summary}`
4708
5216
  this.emit("stopped", { timestamp: new Date });
4709
5217
  }
4710
5218
  stopAgent(agentId) {
4711
- const agent = this.agents.get(agentId);
4712
- if (!agent)
4713
- return false;
4714
- if (agent.process && !agent.process.killed) {
4715
- this.killProcessTree(agent.process);
4716
- }
4717
- return true;
4718
- }
4719
- killProcessTree(proc) {
4720
- if (!proc.pid || proc.killed)
4721
- return;
4722
- try {
4723
- process.kill(-proc.pid, "SIGTERM");
4724
- } catch {
4725
- try {
4726
- proc.kill("SIGTERM");
4727
- } catch {}
4728
- }
5219
+ return this.pool.stopAgent(agentId);
4729
5220
  }
4730
5221
  async cleanup() {
4731
- if (this.heartbeatInterval) {
4732
- clearInterval(this.heartbeatInterval);
4733
- this.heartbeatInterval = null;
4734
- }
4735
- for (const [agentId, agent] of this.agents.entries()) {
4736
- if (agent.process && !agent.process.killed) {
4737
- console.log(`Killing agent: ${agentId}`);
4738
- this.killProcessTree(agent.process);
4739
- }
4740
- }
5222
+ this.pool.shutdown();
4741
5223
  if (this.worktreeManager) {
4742
5224
  try {
4743
5225
  if (this.worktreeCleanupPolicy === "auto") {
@@ -4755,31 +5237,27 @@ ${summary}`
4755
5237
  console.log(c.dim("Could not clean up some worktrees"));
4756
5238
  }
4757
5239
  }
4758
- this.agents.clear();
4759
5240
  }
4760
5241
  getStats() {
5242
+ const poolStats = this.pool.getStats();
4761
5243
  return {
4762
- activeAgents: this.agents.size,
4763
- agentCount: this.agentCount,
5244
+ ...poolStats,
4764
5245
  useWorktrees: this.useWorktrees,
4765
- processedTasks: this.processedTasks.size,
4766
- totalTasksCompleted: Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.tasksCompleted, 0),
4767
- totalTasksFailed: Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.tasksFailed, 0)
5246
+ processedTasks: this.processedTasks.size
4768
5247
  };
4769
5248
  }
4770
5249
  getAgentStates() {
4771
- return Array.from(this.agents.values());
4772
- }
4773
- sleep(ms) {
4774
- return new Promise((resolve3) => setTimeout(resolve3, ms));
5250
+ return this.pool.getAll();
4775
5251
  }
4776
5252
  }
4777
5253
  // src/planning/plan-manager.ts
5254
+ init_config();
5255
+ init_knowledge_base();
4778
5256
  var import_node_fs10 = require("node:fs");
4779
5257
  var import_node_path12 = require("node:path");
4780
5258
 
4781
5259
  // src/planning/sprint-plan.ts
4782
- var import_shared5 = require("@locusai/shared");
5260
+ var import_shared7 = require("@locusai/shared");
4783
5261
  function sprintPlanToMarkdown(plan) {
4784
5262
  const lines = [];
4785
5263
  lines.push(`# Sprint Plan: ${plan.name}`);
@@ -4801,23 +5279,32 @@ function sprintPlanToMarkdown(plan) {
4801
5279
  }
4802
5280
  lines.push(`## Tasks (${plan.tasks.length})`);
4803
5281
  lines.push("");
4804
- for (const task of plan.tasks) {
4805
- lines.push(`### ${task.index}. ${task.title}`);
4806
- lines.push(`- **Role:** ${task.assigneeRole}`);
4807
- lines.push(`- **Priority:** ${task.priority}`);
4808
- lines.push(`- **Complexity:** ${"█".repeat(task.complexity)}${"░".repeat(5 - task.complexity)} (${task.complexity}/5)`);
4809
- if (task.labels.length > 0) {
4810
- lines.push(`- **Labels:** ${task.labels.join(", ")}`);
4811
- }
4812
- lines.push("");
4813
- lines.push(task.description);
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._`);
4814
5289
  lines.push("");
4815
- if (task.acceptanceCriteria.length > 0) {
4816
- lines.push(`**Acceptance Criteria:**`);
4817
- for (const ac of task.acceptanceCriteria) {
4818
- 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(", ")}`);
4819
5297
  }
4820
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
+ }
4821
5308
  }
4822
5309
  }
4823
5310
  if (plan.risks.length > 0) {
@@ -4838,12 +5325,13 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
4838
5325
  return plan.tasks.map((task) => ({
4839
5326
  title: task.title,
4840
5327
  description: task.description,
4841
- status: import_shared5.TaskStatus.BACKLOG,
5328
+ status: import_shared7.TaskStatus.BACKLOG,
4842
5329
  assigneeRole: task.assigneeRole,
4843
5330
  priority: task.priority,
4844
5331
  labels: task.labels,
4845
5332
  sprintId,
4846
5333
  order: task.index * 10,
5334
+ tier: task.tier,
4847
5335
  acceptanceChecklist: task.acceptanceCriteria.map((text, i) => ({
4848
5336
  id: `ac-${i + 1}`,
4849
5337
  text,
@@ -4867,7 +5355,8 @@ function parseSprintPlanFromAI(raw, directive) {
4867
5355
  priority: t.priority || "MEDIUM",
4868
5356
  complexity: t.complexity || 3,
4869
5357
  acceptanceCriteria: t.acceptanceCriteria || [],
4870
- labels: t.labels || []
5358
+ labels: t.labels || [],
5359
+ tier: typeof t.tier === "number" ? t.tier : 0
4871
5360
  }));
4872
5361
  return {
4873
5362
  id,
@@ -4956,9 +5445,12 @@ class PlanManager {
4956
5445
  this.save(plan);
4957
5446
  const kb = new KnowledgeBase(this.projectPath);
4958
5447
  kb.updateProgress({
4959
- type: "sprint_started",
4960
- title: plan.name,
4961
- 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}`
4962
5454
  });
4963
5455
  return { sprint, tasks: tasks2 };
4964
5456
  }
@@ -5021,6 +5513,8 @@ class PlanManager {
5021
5513
  }
5022
5514
  }
5023
5515
  // src/planning/planning-meeting.ts
5516
+ init_config();
5517
+ init_knowledge_base();
5024
5518
  var import_node_fs11 = require("node:fs");
5025
5519
 
5026
5520
  // src/planning/agents/architect.ts
@@ -5109,13 +5603,16 @@ You are a combined Architect, Senior Engineer, and Sprint Planner performing a F
5109
5603
 
5110
5604
  ## Context
5111
5605
 
5112
- In this system, each task is executed by an independent AI agent that:
5113
- - Works on its own git branch (worktree)
5114
- - Has NO knowledge of what other agents are working on
5115
- - Cannot see changes made by other concurrent agents
5116
- - 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
5117
5611
 
5118
- 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
5119
5616
 
5120
5617
  ## CEO Directive
5121
5618
  > ${input.directive}
@@ -5139,29 +5636,37 @@ ${input.sprintOrganizerOutput}
5139
5636
 
5140
5637
  Go through EACH pair of tasks and check for:
5141
5638
 
5142
- ### 1. File Overlap Analysis
5639
+ ### 1. File Overlap Analysis (WITHIN the same tier)
5143
5640
  For each task, list the files it will likely modify. Then check:
5144
- - Do any two tasks modify the same file? (e.g., app.module.ts, configuration.ts, package.json, shared DTOs)
5145
- - 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
5146
5644
 
5147
- ### 2. Duplicated Work Detection
5148
- Check if multiple tasks:
5645
+ ### 2. Duplicated Work Detection (WITHIN the same tier)
5646
+ Check if multiple tasks **in the same tier**:
5149
5647
  - Add the same environment variable or config field
5150
5648
  - Install or configure the same dependency
5151
5649
  - Register the same module or provider
5152
5650
  - Create the same helper function, guard, interceptor, or middleware
5153
5651
  - Add the same import to a shared file
5154
- If yes: consolidate into ONE task
5652
+ If yes: consolidate into ONE task or move the shared work to a lower tier
5155
5653
 
5156
5654
  ### 3. Self-Containment Validation
5157
5655
  For each task, verify:
5158
5656
  - Does it include ALL config/env changes it needs?
5159
5657
  - Does it include ALL module registrations it needs?
5160
5658
  - Does it include ALL dependency installations it needs?
5161
- - 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
5162
5667
 
5163
- ### 4. Merge Conflict Risk Zones
5164
- 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.
5165
5670
 
5166
5671
  ## Output Format
5167
5672
 
@@ -5171,10 +5676,10 @@ Your entire response must be a single JSON object — no text before it, no text
5171
5676
  "hasIssues": true | false,
5172
5677
  "issues": [
5173
5678
  {
5174
- "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",
5175
5680
  "description": "string describing the specific issue",
5176
5681
  "affectedTasks": ["Task Title 1", "Task Title 2"],
5177
- "resolution": "string describing how to fix it (merge, move, consolidate)"
5682
+ "resolution": "string describing how to fix it (merge, move to different tier, consolidate)"
5178
5683
  }
5179
5684
  ],
5180
5685
  "revisedPlan": {
@@ -5189,7 +5694,8 @@ Your entire response must be a single JSON object — no text before it, no text
5189
5694
  "priority": "CRITICAL | HIGH | MEDIUM | LOW",
5190
5695
  "labels": ["string"],
5191
5696
  "acceptanceCriteria": ["string"],
5192
- "complexity": 3
5697
+ "complexity": 3,
5698
+ "tier": 0
5193
5699
  }
5194
5700
  ],
5195
5701
  "risks": [
@@ -5203,11 +5709,14 @@ Your entire response must be a single JSON object — no text before it, no text
5203
5709
  }
5204
5710
 
5205
5711
  IMPORTANT:
5206
- - 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.)
5207
5713
  - If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
5208
5714
  - The revisedPlan is ALWAYS required — it becomes the final plan
5209
5715
  - When merging tasks, combine their acceptance criteria and update descriptions to cover all consolidated work
5210
- - 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`;
5211
5720
  return prompt;
5212
5721
  }
5213
5722
 
@@ -5239,13 +5748,16 @@ Produce the final sprint plan:
5239
5748
  1. **Sprint Name** — A concise, memorable name for this sprint (e.g., "User Authentication", "Payment Integration")
5240
5749
  2. **Sprint Goal** — One paragraph describing what this sprint delivers
5241
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.
5242
- 4. **Duration Estimate** — How many days this sprint will take with 2-3 agents working in parallel
5243
- 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
5244
5754
 
5245
5755
  Guidelines:
5246
5756
  - The order of tasks in the array determines execution order. Tasks are dispatched sequentially from first to last.
5247
- - Foundation tasks (schemas, config, shared code) must appear before tasks that build on them
5248
- - 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
5249
5761
  - Ensure acceptance criteria are specific and testable
5250
5762
  - Keep the sprint focused — if it's too large (>12 tasks), consider reducing scope
5251
5763
 
@@ -5255,7 +5767,7 @@ Before finalizing, validate that EVERY task is fully self-contained and conflict
5255
5767
 
5256
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.
5257
5769
  2. **No duplicated work.** Each env var, config field, dependency, module import, or helper function must be introduced by exactly ONE task.
5258
- 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.
5259
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.
5260
5772
 
5261
5773
  ## Output Format
@@ -5274,7 +5786,8 @@ Your entire response must be a single JSON object — no text before it, no text
5274
5786
  "priority": "CRITICAL | HIGH | MEDIUM | LOW",
5275
5787
  "labels": ["string"],
5276
5788
  "acceptanceCriteria": ["string"],
5277
- "complexity": 3
5789
+ "complexity": 3,
5790
+ "tier": 0
5278
5791
  }
5279
5792
  ],
5280
5793
  "risks": [
@@ -5284,7 +5797,14 @@ Your entire response must be a single JSON object — no text before it, no text
5284
5797
  "severity": "low | medium | high"
5285
5798
  }
5286
5799
  ]
5287
- }`;
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)`;
5288
5808
  return prompt;
5289
5809
  }
5290
5810
 
@@ -5453,3 +5973,10 @@ class PlanningMeeting {
5453
5973
  }
5454
5974
  }
5455
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();