@locusai/sdk 0.16.2 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/PACKAGE_GUIDE.md +269 -0
  2. package/package.json +28 -28
  3. package/dist/agent/codebase-indexer-service.d.ts +0 -17
  4. package/dist/agent/codebase-indexer-service.d.ts.map +0 -1
  5. package/dist/agent/document-fetcher.d.ts +0 -23
  6. package/dist/agent/document-fetcher.d.ts.map +0 -1
  7. package/dist/agent/git-workflow.d.ts +0 -59
  8. package/dist/agent/git-workflow.d.ts.map +0 -1
  9. package/dist/agent/index.d.ts +0 -9
  10. package/dist/agent/index.d.ts.map +0 -1
  11. package/dist/agent/review-service.d.ts +0 -21
  12. package/dist/agent/review-service.d.ts.map +0 -1
  13. package/dist/agent/reviewer-worker.d.ts +0 -42
  14. package/dist/agent/reviewer-worker.d.ts.map +0 -1
  15. package/dist/agent/task-executor.d.ts +0 -26
  16. package/dist/agent/task-executor.d.ts.map +0 -1
  17. package/dist/agent/worker-cli.d.ts +0 -6
  18. package/dist/agent/worker-cli.d.ts.map +0 -1
  19. package/dist/agent/worker-types.d.ts +0 -36
  20. package/dist/agent/worker-types.d.ts.map +0 -1
  21. package/dist/agent/worker.d.ts +0 -40
  22. package/dist/agent/worker.d.ts.map +0 -1
  23. package/dist/agent/worker.js +0 -2792
  24. package/dist/ai/__tests__/factory.test.d.ts +0 -2
  25. package/dist/ai/__tests__/factory.test.d.ts.map +0 -1
  26. package/dist/ai/claude-runner.d.ts +0 -35
  27. package/dist/ai/claude-runner.d.ts.map +0 -1
  28. package/dist/ai/claude-stream-parser.d.ts +0 -41
  29. package/dist/ai/claude-stream-parser.d.ts.map +0 -1
  30. package/dist/ai/codex-runner.d.ts +0 -38
  31. package/dist/ai/codex-runner.d.ts.map +0 -1
  32. package/dist/ai/factory.d.ts +0 -26
  33. package/dist/ai/factory.d.ts.map +0 -1
  34. package/dist/ai/index.d.ts +0 -8
  35. package/dist/ai/index.d.ts.map +0 -1
  36. package/dist/ai/runner.d.ts +0 -23
  37. package/dist/ai/runner.d.ts.map +0 -1
  38. package/dist/core/config.d.ts +0 -58
  39. package/dist/core/config.d.ts.map +0 -1
  40. package/dist/core/index.d.ts +0 -4
  41. package/dist/core/index.d.ts.map +0 -1
  42. package/dist/core/indexer.d.ts +0 -31
  43. package/dist/core/indexer.d.ts.map +0 -1
  44. package/dist/core/prompt-builder.d.ts +0 -14
  45. package/dist/core/prompt-builder.d.ts.map +0 -1
  46. package/dist/discussion/agents/facilitator-prompt.d.ts +0 -13
  47. package/dist/discussion/agents/facilitator-prompt.d.ts.map +0 -1
  48. package/dist/discussion/discussion-facilitator.d.ts +0 -67
  49. package/dist/discussion/discussion-facilitator.d.ts.map +0 -1
  50. package/dist/discussion/discussion-manager.d.ts +0 -59
  51. package/dist/discussion/discussion-manager.d.ts.map +0 -1
  52. package/dist/discussion/discussion-types.d.ts +0 -89
  53. package/dist/discussion/discussion-types.d.ts.map +0 -1
  54. package/dist/discussion/index.d.ts +0 -5
  55. package/dist/discussion/index.d.ts.map +0 -1
  56. package/dist/events.d.ts +0 -22
  57. package/dist/events.d.ts.map +0 -1
  58. package/dist/exec/context-tracker.d.ts +0 -183
  59. package/dist/exec/context-tracker.d.ts.map +0 -1
  60. package/dist/exec/event-emitter.d.ts +0 -117
  61. package/dist/exec/event-emitter.d.ts.map +0 -1
  62. package/dist/exec/events.d.ts +0 -171
  63. package/dist/exec/events.d.ts.map +0 -1
  64. package/dist/exec/exec-session.d.ts +0 -164
  65. package/dist/exec/exec-session.d.ts.map +0 -1
  66. package/dist/exec/history-manager.d.ts +0 -153
  67. package/dist/exec/history-manager.d.ts.map +0 -1
  68. package/dist/exec/index.d.ts +0 -7
  69. package/dist/exec/index.d.ts.map +0 -1
  70. package/dist/exec/types.d.ts +0 -130
  71. package/dist/exec/types.d.ts.map +0 -1
  72. package/dist/git/git-utils.d.ts +0 -31
  73. package/dist/git/git-utils.d.ts.map +0 -1
  74. package/dist/git/index.d.ts +0 -3
  75. package/dist/git/index.d.ts.map +0 -1
  76. package/dist/git/pr-service.d.ts +0 -66
  77. package/dist/git/pr-service.d.ts.map +0 -1
  78. package/dist/index-node.d.ts +0 -22
  79. package/dist/index-node.d.ts.map +0 -1
  80. package/dist/index-node.js +0 -5607
  81. package/dist/index.d.ts +0 -37
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/index.js +0 -559
  84. package/dist/modules/auth.d.ts +0 -23
  85. package/dist/modules/auth.d.ts.map +0 -1
  86. package/dist/modules/base.d.ts +0 -8
  87. package/dist/modules/base.d.ts.map +0 -1
  88. package/dist/modules/ci.d.ts +0 -8
  89. package/dist/modules/ci.d.ts.map +0 -1
  90. package/dist/modules/docs.d.ts +0 -14
  91. package/dist/modules/docs.d.ts.map +0 -1
  92. package/dist/modules/invitations.d.ts +0 -10
  93. package/dist/modules/invitations.d.ts.map +0 -1
  94. package/dist/modules/organizations.d.ts +0 -24
  95. package/dist/modules/organizations.d.ts.map +0 -1
  96. package/dist/modules/sprints.d.ts +0 -14
  97. package/dist/modules/sprints.d.ts.map +0 -1
  98. package/dist/modules/tasks.d.ts +0 -25
  99. package/dist/modules/tasks.d.ts.map +0 -1
  100. package/dist/modules/workspaces.d.ts +0 -44
  101. package/dist/modules/workspaces.d.ts.map +0 -1
  102. package/dist/orchestrator/index.d.ts +0 -85
  103. package/dist/orchestrator/index.d.ts.map +0 -1
  104. package/dist/orchestrator/types.d.ts +0 -36
  105. package/dist/orchestrator/types.d.ts.map +0 -1
  106. package/dist/planning/agents/architect.d.ts +0 -1
  107. package/dist/planning/agents/architect.d.ts.map +0 -1
  108. package/dist/planning/agents/cross-task-reviewer.d.ts +0 -1
  109. package/dist/planning/agents/cross-task-reviewer.d.ts.map +0 -1
  110. package/dist/planning/agents/planner.d.ts +0 -20
  111. package/dist/planning/agents/planner.d.ts.map +0 -1
  112. package/dist/planning/agents/sprint-organizer.d.ts +0 -1
  113. package/dist/planning/agents/sprint-organizer.d.ts.map +0 -1
  114. package/dist/planning/agents/tech-lead.d.ts +0 -1
  115. package/dist/planning/agents/tech-lead.d.ts.map +0 -1
  116. package/dist/planning/index.d.ts +0 -4
  117. package/dist/planning/index.d.ts.map +0 -1
  118. package/dist/planning/plan-manager.d.ts +0 -51
  119. package/dist/planning/plan-manager.d.ts.map +0 -1
  120. package/dist/planning/planning-meeting.d.ts +0 -34
  121. package/dist/planning/planning-meeting.d.ts.map +0 -1
  122. package/dist/planning/sprint-plan.d.ts +0 -51
  123. package/dist/planning/sprint-plan.d.ts.map +0 -1
  124. package/dist/utils/colors.d.ts +0 -62
  125. package/dist/utils/colors.d.ts.map +0 -1
  126. package/dist/utils/json-extractor.d.ts +0 -10
  127. package/dist/utils/json-extractor.d.ts.map +0 -1
  128. package/dist/utils/resolve-bin.d.ts +0 -15
  129. package/dist/utils/resolve-bin.d.ts.map +0 -1
  130. package/dist/utils/retry.d.ts +0 -8
  131. package/dist/utils/retry.d.ts.map +0 -1
  132. package/dist/utils/structured-output.d.ts +0 -14
  133. package/dist/utils/structured-output.d.ts.map +0 -1
@@ -1,2792 +0,0 @@
1
- var __create = Object.create;
2
- var __getProtoOf = Object.getPrototypeOf;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __toESM = (mod, isNodeMode, target) => {
8
- target = mod != null ? __create(__getProtoOf(mod)) : {};
9
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
- for (let key of __getOwnPropNames(mod))
11
- if (!__hasOwnProp.call(to, key))
12
- __defProp(to, key, {
13
- get: () => mod[key],
14
- enumerable: true
15
- });
16
- return to;
17
- };
18
- var __moduleCache = /* @__PURE__ */ new WeakMap;
19
- var __toCommonJS = (from) => {
20
- var entry = __moduleCache.get(from), desc;
21
- if (entry)
22
- return entry;
23
- entry = __defProp({}, "__esModule", { value: true });
24
- if (from && typeof from === "object" || typeof from === "function")
25
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
26
- get: () => from[key],
27
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
28
- }));
29
- __moduleCache.set(from, entry);
30
- return entry;
31
- };
32
- var __export = (target, all) => {
33
- for (var name in all)
34
- __defProp(target, name, {
35
- get: all[name],
36
- enumerable: true,
37
- configurable: true,
38
- set: (newValue) => all[name] = () => newValue
39
- });
40
- };
41
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
42
-
43
- // src/events.ts
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
- });
61
-
62
- // src/modules/base.ts
63
- class BaseModule {
64
- api;
65
- emitter;
66
- constructor(api, emitter) {
67
- this.api = api;
68
- this.emitter = emitter;
69
- }
70
- }
71
-
72
- // src/modules/auth.ts
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
- });
106
-
107
- // src/modules/ci.ts
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
- });
117
-
118
- // src/modules/docs.ts
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
- });
158
-
159
- // src/modules/invitations.ts
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
- });
184
-
185
- // src/modules/organizations.ts
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
- });
224
-
225
- // src/modules/sprints.ts
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
- });
266
-
267
- // src/modules/tasks.ts
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
- });
321
-
322
- // src/modules/workspaces.ts
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
- });
393
-
394
- // src/discussion/discussion-types.ts
395
- var import_zod, DiscussionMessageSchema, DiscussionInsightSchema, DiscussionSchema;
396
- var init_discussion_types = __esm(() => {
397
- import_zod = require("zod");
398
- DiscussionMessageSchema = import_zod.z.object({
399
- role: import_zod.z.enum(["user", "assistant"]),
400
- content: import_zod.z.string(),
401
- timestamp: import_zod.z.number()
402
- });
403
- DiscussionInsightSchema = import_zod.z.object({
404
- id: import_zod.z.string(),
405
- type: import_zod.z.enum(["decision", "requirement", "idea", "concern", "learning"]),
406
- title: import_zod.z.string(),
407
- content: import_zod.z.string(),
408
- tags: import_zod.z.array(import_zod.z.string()).default([]),
409
- createdAt: import_zod.z.string()
410
- });
411
- DiscussionSchema = import_zod.z.object({
412
- id: import_zod.z.string(),
413
- title: import_zod.z.string(),
414
- topic: import_zod.z.string(),
415
- status: import_zod.z.enum(["active", "completed", "archived"]).default("active"),
416
- messages: import_zod.z.array(DiscussionMessageSchema).default([]),
417
- insights: import_zod.z.array(DiscussionInsightSchema).default([]),
418
- createdAt: import_zod.z.string(),
419
- updatedAt: import_zod.z.string(),
420
- metadata: import_zod.z.object({
421
- model: import_zod.z.string(),
422
- provider: import_zod.z.string()
423
- })
424
- });
425
- });
426
-
427
- // src/index.ts
428
- var exports_src = {};
429
- __export(exports_src, {
430
- WorkspacesModule: () => WorkspacesModule,
431
- TasksModule: () => TasksModule,
432
- SprintsModule: () => SprintsModule,
433
- OrganizationsModule: () => OrganizationsModule,
434
- LocusEvent: () => LocusEvent,
435
- LocusEmitter: () => LocusEmitter,
436
- LocusClient: () => LocusClient,
437
- InvitationsModule: () => InvitationsModule,
438
- DocsModule: () => DocsModule,
439
- DiscussionSchema: () => DiscussionSchema,
440
- DiscussionMessageSchema: () => DiscussionMessageSchema,
441
- DiscussionInsightSchema: () => DiscussionInsightSchema,
442
- CiModule: () => CiModule,
443
- AuthModule: () => AuthModule
444
- });
445
- module.exports = __toCommonJS(exports_src);
446
-
447
- class LocusClient {
448
- api;
449
- emitter;
450
- auth;
451
- tasks;
452
- sprints;
453
- workspaces;
454
- organizations;
455
- invitations;
456
- docs;
457
- ci;
458
- constructor(config) {
459
- this.emitter = new LocusEmitter;
460
- this.api = import_axios.default.create({
461
- baseURL: config.baseUrl,
462
- timeout: config.timeout || 60000,
463
- headers: {
464
- "Content-Type": "application/json",
465
- ...config.token ? { Authorization: `Bearer ${config.token}` } : {}
466
- }
467
- });
468
- this.setupInterceptors();
469
- this.auth = new AuthModule(this.api, this.emitter);
470
- this.tasks = new TasksModule(this.api, this.emitter);
471
- this.sprints = new SprintsModule(this.api, this.emitter);
472
- this.workspaces = new WorkspacesModule(this.api, this.emitter);
473
- this.organizations = new OrganizationsModule(this.api, this.emitter);
474
- this.invitations = new InvitationsModule(this.api, this.emitter);
475
- this.docs = new DocsModule(this.api, this.emitter);
476
- this.ci = new CiModule(this.api, this.emitter);
477
- if (config.retryOptions) {
478
- this.setupRetryInterceptor(config.retryOptions);
479
- }
480
- }
481
- setupRetryInterceptor(retryOptions) {
482
- this.api.interceptors.response.use(undefined, async (error) => {
483
- const config = error.config;
484
- if (!config || !retryOptions) {
485
- return Promise.reject(error);
486
- }
487
- config._retryCount = config._retryCount || 0;
488
- const maxRetries = retryOptions.maxRetries ?? 3;
489
- const shouldRetry = config._retryCount < maxRetries && (retryOptions.retryCondition ? retryOptions.retryCondition(error) : !error.response || error.response.status >= 500);
490
- if (shouldRetry) {
491
- config._retryCount++;
492
- const delay = Math.min((retryOptions.initialDelay ?? 1000) * Math.pow(retryOptions.factor ?? 2, config._retryCount - 1), retryOptions.maxDelay ?? 5000);
493
- await new Promise((resolve) => setTimeout(resolve, delay));
494
- return this.api(config);
495
- }
496
- return Promise.reject(error);
497
- });
498
- }
499
- setupInterceptors() {
500
- this.api.interceptors.response.use((response) => {
501
- if (response.data && typeof response.data === "object" && "data" in response.data) {
502
- response.data = response.data.data;
503
- }
504
- return response;
505
- }, (error) => {
506
- const status = error.response?.status;
507
- let message;
508
- if (error.response?.data?.error?.message && typeof error.response.data.error.message === "string") {
509
- message = error.response.data.error.message;
510
- } else if (error.response?.data?.message && typeof error.response.data.message === "string") {
511
- message = error.response.data.message;
512
- } else if (error.message && typeof error.message === "string") {
513
- message = error.message;
514
- } else {
515
- message = "An error occurred";
516
- }
517
- const enhancedError = new Error(message);
518
- enhancedError.name = `HTTP${status || "Error"}`;
519
- if (status === 401) {
520
- this.emitter.emit("TOKEN_EXPIRED" /* TOKEN_EXPIRED */);
521
- this.emitter.emit("AUTH_ERROR" /* AUTH_ERROR */, enhancedError);
522
- } else {
523
- this.emitter.emit("REQUEST_ERROR" /* REQUEST_ERROR */, enhancedError);
524
- }
525
- return Promise.reject(enhancedError);
526
- });
527
- }
528
- setToken(token) {
529
- if (token) {
530
- this.api.defaults.headers.common.Authorization = `Bearer ${token}`;
531
- } else {
532
- delete this.api.defaults.headers.common.Authorization;
533
- }
534
- }
535
- }
536
- var import_axios;
537
- var init_src = __esm(() => {
538
- init_events();
539
- init_auth();
540
- init_ci();
541
- init_docs();
542
- init_invitations();
543
- init_organizations();
544
- init_sprints();
545
- init_tasks();
546
- init_workspaces();
547
- init_discussion_types();
548
- import_axios = __toESM(require("axios"));
549
- init_events();
550
- init_auth();
551
- init_ci();
552
- init_docs();
553
- init_invitations();
554
- init_organizations();
555
- init_sprints();
556
- init_tasks();
557
- init_workspaces();
558
- });
559
-
560
- // src/core/config.ts
561
- function isValidModelForProvider(provider, model) {
562
- return PROVIDER_MODELS[provider].includes(model);
563
- }
564
- function getModelsForProvider(provider) {
565
- return PROVIDER_MODELS[provider];
566
- }
567
- function getLocusPath(projectPath, fileName) {
568
- return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
569
- }
570
- function getAgentArtifactsPath(projectPath, agentId) {
571
- const shortId = agentId.slice(-8);
572
- return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.artifactsDir, shortId);
573
- }
574
- var import_node_path, PROVIDER, CLAUDE_MODELS, CODEX_MODELS, PROVIDER_MODELS, DEFAULT_MODEL, LOCUS_SCHEMA_BASE_URL = "https://locusai.dev/schemas", LOCUS_SCHEMAS, LOCUS_CONFIG, LOCUS_GITIGNORE_PATTERNS;
575
- var init_config = __esm(() => {
576
- import_node_path = require("node:path");
577
- PROVIDER = {
578
- CLAUDE: "claude",
579
- CODEX: "codex"
580
- };
581
- CLAUDE_MODELS = {
582
- OPUS: "opus",
583
- SONNET: "sonnet",
584
- HAIKU: "haiku",
585
- OPUS_PLAN: "opusplan",
586
- CLAUDE_OPUS_4_6: "claude-opus-4-6",
587
- CLAUDE_SONNET_4_5: "claude-sonnet-4-5-20250929",
588
- CLAUDE_SONNET_4_6: "claude-sonnet-4-6",
589
- CLAUDE_HAIKU_4_5: "claude-haiku-4-5-20251001"
590
- };
591
- CODEX_MODELS = {
592
- GPT_5_3_CODEX: "gpt-5.3-codex",
593
- GPT_5_3_CODEX_SPARK: "gpt-5.3-codex-spark",
594
- GPT_5_CODEX_MINI: "gpt-5-codex-mini",
595
- GPT_5_2_CODEX: "gpt-5.2-codex"
596
- };
597
- PROVIDER_MODELS = {
598
- [PROVIDER.CLAUDE]: Object.values(CLAUDE_MODELS),
599
- [PROVIDER.CODEX]: Object.values(CODEX_MODELS)
600
- };
601
- DEFAULT_MODEL = {
602
- [PROVIDER.CLAUDE]: CLAUDE_MODELS.OPUS,
603
- [PROVIDER.CODEX]: CODEX_MODELS.GPT_5_3_CODEX
604
- };
605
- LOCUS_SCHEMAS = {
606
- config: `${LOCUS_SCHEMA_BASE_URL}/config.schema.json`,
607
- settings: `${LOCUS_SCHEMA_BASE_URL}/settings.schema.json`
608
- };
609
- LOCUS_CONFIG = {
610
- dir: ".locus",
611
- configFile: "config.json",
612
- settingsFile: "settings.json",
613
- indexFile: "codebase-index.json",
614
- contextFile: "LOCUS.md",
615
- learningsFile: "LEARNINGS.md",
616
- artifactsDir: "artifacts",
617
- documentsDir: "documents",
618
- sessionsDir: "sessions",
619
- reviewsDir: "reviews",
620
- plansDir: "plans",
621
- discussionsDir: "discussions"
622
- };
623
- LOCUS_GITIGNORE_PATTERNS = [
624
- "# Locus AI - Session data (user-specific, can grow large)",
625
- ".locus/sessions/",
626
- "",
627
- "# Locus AI - Artifacts (local-only, user-specific)",
628
- ".locus/artifacts/",
629
- "",
630
- "# Locus AI - Review reports (generated per sprint)",
631
- ".locus/reviews/",
632
- "",
633
- "# Locus AI - Plans (generated per task)",
634
- ".locus/plans/",
635
- "",
636
- "# Locus AI - Discussions (AI discussion sessions)",
637
- ".locus/discussions/",
638
- "",
639
- "# Locus AI - Settings (contains API key, telegram config, etc.)",
640
- ".locus/settings.json",
641
- "",
642
- "# Locus AI - Configuration (contains project context, etc.)",
643
- ".locus/config.json"
644
- ];
645
- });
646
-
647
- // src/utils/colors.ts
648
- var ESC = "\x1B[", RESET, colors, c;
649
- var init_colors = __esm(() => {
650
- RESET = `${ESC}0m`;
651
- colors = {
652
- reset: RESET,
653
- bold: `${ESC}1m`,
654
- dim: `${ESC}2m`,
655
- italic: `${ESC}3m`,
656
- underline: `${ESC}4m`,
657
- black: `${ESC}30m`,
658
- red: `${ESC}31m`,
659
- green: `${ESC}32m`,
660
- yellow: `${ESC}33m`,
661
- blue: `${ESC}34m`,
662
- magenta: `${ESC}35m`,
663
- cyan: `${ESC}36m`,
664
- white: `${ESC}37m`,
665
- gray: `${ESC}90m`,
666
- brightRed: `${ESC}91m`,
667
- brightGreen: `${ESC}92m`,
668
- brightYellow: `${ESC}93m`,
669
- brightBlue: `${ESC}94m`,
670
- brightMagenta: `${ESC}95m`,
671
- brightCyan: `${ESC}96m`,
672
- brightWhite: `${ESC}97m`,
673
- bgBlack: `${ESC}40m`,
674
- bgRed: `${ESC}41m`,
675
- bgGreen: `${ESC}42m`,
676
- bgYellow: `${ESC}43m`,
677
- bgBlue: `${ESC}44m`,
678
- bgMagenta: `${ESC}45m`,
679
- bgCyan: `${ESC}46m`,
680
- bgWhite: `${ESC}47m`
681
- };
682
- c = {
683
- text: (text, ...colorNames) => {
684
- const codes = colorNames.map((name) => colors[name]).join("");
685
- return `${codes}${text}${RESET}`;
686
- },
687
- bold: (t) => c.text(t, "bold"),
688
- dim: (t) => c.text(t, "dim"),
689
- red: (t) => c.text(t, "red"),
690
- green: (t) => c.text(t, "green"),
691
- yellow: (t) => c.text(t, "yellow"),
692
- blue: (t) => c.text(t, "blue"),
693
- magenta: (t) => c.text(t, "magenta"),
694
- cyan: (t) => c.text(t, "cyan"),
695
- gray: (t) => c.text(t, "gray"),
696
- white: (t) => c.text(t, "white"),
697
- brightBlue: (t) => c.text(t, "brightBlue"),
698
- bgBlue: (t) => c.text(t, "bgBlue", "white", "bold"),
699
- success: (t) => c.text(t, "green", "bold"),
700
- error: (t) => c.text(t, "red", "bold"),
701
- warning: (t) => c.text(t, "yellow", "bold"),
702
- info: (t) => c.text(t, "cyan", "bold"),
703
- primary: (t) => c.text(t, "blue", "bold"),
704
- secondary: (t) => c.text(t, "magenta", "bold"),
705
- header: (t) => c.text(` ${t} `, "bgBlue", "white", "bold"),
706
- step: (t) => c.text(` ${t} `, "bgCyan", "black", "bold"),
707
- underline: (t) => c.text(t, "underline")
708
- };
709
- });
710
-
711
- // src/utils/resolve-bin.ts
712
- function getNvmNodeBinDir() {
713
- const nvmDir = process.env.NVM_DIR || import_node_path2.join(import_node_os.homedir(), ".nvm");
714
- const versionsDir = import_node_path2.join(nvmDir, "versions", "node");
715
- if (!import_node_fs.existsSync(versionsDir))
716
- return null;
717
- let versions;
718
- try {
719
- versions = import_node_fs.readdirSync(versionsDir).filter((d) => d.startsWith("v"));
720
- } catch {
721
- return null;
722
- }
723
- if (versions.length === 0)
724
- return null;
725
- const currentNodeVersion = `v${process.versions.node}`;
726
- const currentBin = import_node_path2.join(versionsDir, currentNodeVersion, "bin");
727
- if (versions.includes(currentNodeVersion) && import_node_fs.existsSync(currentBin)) {
728
- return currentBin;
729
- }
730
- const aliasPath = import_node_path2.join(nvmDir, "alias", "default");
731
- if (import_node_fs.existsSync(aliasPath)) {
732
- try {
733
- const alias = import_node_fs.readFileSync(aliasPath, "utf-8").trim();
734
- const match = versions.find((v) => v === `v${alias}` || v.startsWith(`v${alias}.`));
735
- if (match) {
736
- const bin2 = import_node_path2.join(versionsDir, match, "bin");
737
- if (import_node_fs.existsSync(bin2))
738
- return bin2;
739
- }
740
- } catch {}
741
- }
742
- const sorted = versions.sort((a, b) => {
743
- const pa = a.slice(1).split(".").map(Number);
744
- const pb = b.slice(1).split(".").map(Number);
745
- for (let i = 0;i < 3; i++) {
746
- if ((pa[i] || 0) !== (pb[i] || 0))
747
- return (pb[i] || 0) - (pa[i] || 0);
748
- }
749
- return 0;
750
- });
751
- const bin = import_node_path2.join(versionsDir, sorted[0], "bin");
752
- return import_node_fs.existsSync(bin) ? bin : null;
753
- }
754
- function getFnmNodeBinDir() {
755
- const fnmDir = process.env.FNM_DIR || import_node_path2.join(import_node_os.homedir(), ".fnm");
756
- const currentBin = import_node_path2.join(fnmDir, "current", "bin");
757
- if (import_node_fs.existsSync(currentBin))
758
- return currentBin;
759
- const aliasDir = import_node_path2.join(fnmDir, "aliases", "default");
760
- if (import_node_fs.existsSync(aliasDir)) {
761
- const bin = import_node_path2.join(aliasDir, "bin");
762
- if (import_node_fs.existsSync(bin))
763
- return bin;
764
- }
765
- return null;
766
- }
767
- function getAugmentedPath() {
768
- const currentPath = process.env.PATH || "";
769
- const currentDirs = new Set(currentPath.split(import_node_path2.delimiter));
770
- const extra = [];
771
- for (const dir of EXTRA_BIN_DIRS) {
772
- if (!currentDirs.has(dir) && import_node_fs.existsSync(dir)) {
773
- extra.push(dir);
774
- }
775
- }
776
- const nvmBin = getNvmNodeBinDir();
777
- if (nvmBin && !currentDirs.has(nvmBin)) {
778
- extra.push(nvmBin);
779
- }
780
- const fnmBin = getFnmNodeBinDir();
781
- if (fnmBin && !currentDirs.has(fnmBin)) {
782
- extra.push(fnmBin);
783
- }
784
- if (extra.length === 0)
785
- return currentPath;
786
- return currentPath + import_node_path2.delimiter + extra.join(import_node_path2.delimiter);
787
- }
788
- function getAugmentedEnv(overrides = {}) {
789
- const env = {
790
- ...process.env,
791
- ...overrides,
792
- PATH: getAugmentedPath()
793
- };
794
- for (const key of ENV_VARS_TO_STRIP) {
795
- delete env[key];
796
- }
797
- return env;
798
- }
799
- var import_node_fs, import_node_os, import_node_path2, EXTRA_BIN_DIRS, ENV_VARS_TO_STRIP;
800
- var init_resolve_bin = __esm(() => {
801
- import_node_fs = require("node:fs");
802
- import_node_os = require("node:os");
803
- import_node_path2 = require("node:path");
804
- EXTRA_BIN_DIRS = [
805
- import_node_path2.join(import_node_os.homedir(), ".local", "bin"),
806
- import_node_path2.join(import_node_os.homedir(), ".npm", "bin"),
807
- import_node_path2.join(import_node_os.homedir(), ".npm-global", "bin"),
808
- import_node_path2.join(import_node_os.homedir(), ".yarn", "bin"),
809
- import_node_path2.join(import_node_os.homedir(), ".bun", "bin"),
810
- import_node_path2.join(import_node_os.homedir(), "Library", "pnpm"),
811
- "/usr/local/bin",
812
- "/opt/homebrew/bin",
813
- "/opt/homebrew/sbin"
814
- ];
815
- ENV_VARS_TO_STRIP = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
816
- });
817
-
818
- // src/ai/claude-stream-parser.ts
819
- class ClaudeStreamParser {
820
- activeTools = new Map;
821
- parseLineToChunk(line) {
822
- if (!line.trim())
823
- return null;
824
- try {
825
- const item = JSON.parse(line);
826
- return this.processItemToChunk(item);
827
- } catch {
828
- return null;
829
- }
830
- }
831
- parseLine(line, log) {
832
- if (!line.trim())
833
- return null;
834
- try {
835
- const item = JSON.parse(line);
836
- return this.processItem(item, log);
837
- } catch {
838
- return null;
839
- }
840
- }
841
- processItemToChunk(item) {
842
- if (item.type === "result") {
843
- return { type: "result", content: item.result || "" };
844
- }
845
- if (item.type === "stream_event" && item.event) {
846
- return this.handleEventToChunk(item.event);
847
- }
848
- return null;
849
- }
850
- handleEventToChunk(event) {
851
- const { type, delta, content_block, index } = event;
852
- if (type === "content_block_delta" && delta?.type === "text_delta") {
853
- return { type: "text_delta", content: delta.text || "" };
854
- }
855
- if (type === "content_block_delta" && delta?.type === "input_json_delta" && delta.partial_json !== undefined && index !== undefined) {
856
- const activeTool = this.activeTools.get(index);
857
- if (activeTool) {
858
- activeTool.parameterJson += delta.partial_json;
859
- }
860
- return null;
861
- }
862
- if (type === "content_block_start" && content_block) {
863
- if (content_block.type === "tool_use" && content_block.name) {
864
- if (index !== undefined) {
865
- this.activeTools.set(index, {
866
- name: content_block.name,
867
- id: content_block.id,
868
- index,
869
- parameterJson: "",
870
- startTime: Date.now()
871
- });
872
- }
873
- return {
874
- type: "tool_use",
875
- tool: content_block.name,
876
- id: content_block.id
877
- };
878
- }
879
- if (content_block.type === "thinking") {
880
- return { type: "thinking" };
881
- }
882
- }
883
- if (type === "content_block_stop" && index !== undefined) {
884
- const activeTool = this.activeTools.get(index);
885
- if (activeTool?.parameterJson) {
886
- try {
887
- const parameters = JSON.parse(activeTool.parameterJson);
888
- return {
889
- type: "tool_parameters",
890
- tool: activeTool.name,
891
- id: activeTool.id,
892
- parameters
893
- };
894
- } catch {}
895
- }
896
- return null;
897
- }
898
- return null;
899
- }
900
- processItem(item, log) {
901
- if (item.type === "result") {
902
- return item.result || "";
903
- }
904
- if (item.type === "stream_event" && item.event) {
905
- this.handleEvent(item.event, log);
906
- }
907
- return null;
908
- }
909
- handleEvent(event, log) {
910
- const { type, content_block } = event;
911
- if (type === "content_block_start" && content_block) {
912
- if (content_block.type === "tool_use" && content_block.name) {
913
- log?.(`
914
- ${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
915
- `, "info");
916
- }
917
- }
918
- }
919
- }
920
- var init_claude_stream_parser = __esm(() => {
921
- init_colors();
922
- });
923
-
924
- // src/ai/claude-runner.ts
925
- class ClaudeRunner {
926
- model;
927
- log;
928
- projectPath;
929
- eventEmitter;
930
- currentToolName;
931
- activeProcess = null;
932
- aborted = false;
933
- timeoutMs;
934
- constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CLAUDE], log, timeoutMs) {
935
- this.model = model;
936
- this.log = log;
937
- this.projectPath = import_node_path3.resolve(projectPath);
938
- this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS;
939
- }
940
- setEventEmitter(emitter) {
941
- this.eventEmitter = emitter;
942
- }
943
- abort() {
944
- if (this.activeProcess && !this.activeProcess.killed) {
945
- this.aborted = true;
946
- this.activeProcess.kill("SIGTERM");
947
- this.activeProcess = null;
948
- }
949
- }
950
- async run(prompt) {
951
- const maxRetries = 3;
952
- let lastError = null;
953
- for (let attempt = 1;attempt <= maxRetries; attempt++) {
954
- try {
955
- return await this.withTimeout(this.executeRun(prompt));
956
- } catch (error) {
957
- const err = error;
958
- lastError = err;
959
- const isLastAttempt = attempt === maxRetries;
960
- if (err.message.includes("timed out")) {
961
- throw err;
962
- }
963
- if (!isLastAttempt) {
964
- const delay = Math.pow(2, attempt) * 1000;
965
- this.log?.(`Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`, "warn");
966
- await new Promise((resolve2) => setTimeout(resolve2, delay));
967
- }
968
- }
969
- }
970
- throw lastError || new Error("Claude CLI failed after multiple attempts");
971
- }
972
- withTimeout(promise) {
973
- if (this.timeoutMs <= 0)
974
- return promise;
975
- return new Promise((resolve2, reject) => {
976
- const timer = setTimeout(() => {
977
- this.abort();
978
- reject(new Error(`Claude CLI execution timed out after ${Math.round(this.timeoutMs / 60000)} minutes`));
979
- }, this.timeoutMs);
980
- promise.then((value) => {
981
- clearTimeout(timer);
982
- resolve2(value);
983
- }, (err) => {
984
- clearTimeout(timer);
985
- reject(err);
986
- });
987
- });
988
- }
989
- buildCliArgs() {
990
- const args = [
991
- "--print",
992
- "--output-format",
993
- "stream-json",
994
- "--verbose",
995
- "--dangerously-skip-permissions",
996
- "--no-session-persistence",
997
- "--include-partial-messages",
998
- "--model",
999
- this.model
1000
- ];
1001
- return args;
1002
- }
1003
- async* runStream(prompt) {
1004
- this.aborted = false;
1005
- const parser = new ClaudeStreamParser;
1006
- const args = this.buildCliArgs();
1007
- const env = getAugmentedEnv({
1008
- FORCE_COLOR: "1",
1009
- TERM: "xterm-256color"
1010
- });
1011
- this.eventEmitter?.emitSessionStarted({
1012
- model: this.model,
1013
- provider: "claude"
1014
- });
1015
- this.eventEmitter?.emitPromptSubmitted(prompt, prompt.length > 500);
1016
- const claude = import_node_child_process.spawn("claude", args, {
1017
- cwd: this.projectPath,
1018
- stdio: ["pipe", "pipe", "pipe"],
1019
- env
1020
- });
1021
- this.activeProcess = claude;
1022
- let buffer = "";
1023
- let stderrBuffer = "";
1024
- let stderrFull = "";
1025
- let resolveChunk = null;
1026
- const chunkQueue = [];
1027
- let processEnded = false;
1028
- let errorMessage = "";
1029
- let finalContent = "";
1030
- let lastResultContent = "";
1031
- let isThinking = false;
1032
- const enqueueChunk = (chunk) => {
1033
- this.emitEventForChunk(chunk, isThinking);
1034
- if (chunk.type === "thinking") {
1035
- isThinking = true;
1036
- } else if (chunk.type === "text_delta" || chunk.type === "tool_use") {
1037
- if (isThinking) {
1038
- this.eventEmitter?.emitThinkingStoped();
1039
- isThinking = false;
1040
- }
1041
- }
1042
- if (chunk.type === "text_delta") {
1043
- finalContent += chunk.content;
1044
- }
1045
- if (resolveChunk) {
1046
- const resolve2 = resolveChunk;
1047
- resolveChunk = null;
1048
- resolve2(chunk);
1049
- } else {
1050
- chunkQueue.push(chunk);
1051
- }
1052
- };
1053
- const signalEnd = () => {
1054
- processEnded = true;
1055
- if (resolveChunk) {
1056
- resolveChunk(null);
1057
- resolveChunk = null;
1058
- }
1059
- };
1060
- claude.stdout.on("data", (data) => {
1061
- buffer += data.toString();
1062
- const lines = buffer.split(`
1063
- `);
1064
- buffer = lines.pop() || "";
1065
- for (const line of lines) {
1066
- const chunk = parser.parseLineToChunk(line);
1067
- if (chunk) {
1068
- if (chunk.type === "result") {
1069
- lastResultContent = chunk.content;
1070
- }
1071
- enqueueChunk(chunk);
1072
- }
1073
- }
1074
- });
1075
- claude.stderr.on("data", (data) => {
1076
- const chunk = data.toString();
1077
- stderrBuffer += chunk;
1078
- stderrFull += chunk;
1079
- const lines = stderrBuffer.split(`
1080
- `);
1081
- stderrBuffer = lines.pop() || "";
1082
- for (const line of lines) {
1083
- if (!this.shouldSuppressLine(line)) {
1084
- process.stderr.write(`${line}
1085
- `);
1086
- }
1087
- }
1088
- });
1089
- claude.on("error", (err) => {
1090
- errorMessage = `Failed to start Claude CLI: ${err.message}. Please ensure the 'claude' command is available in your PATH.`;
1091
- this.eventEmitter?.emitErrorOccurred(errorMessage, "SPAWN_ERROR");
1092
- signalEnd();
1093
- });
1094
- claude.on("close", (code) => {
1095
- this.activeProcess = null;
1096
- if (stderrBuffer && !this.shouldSuppressLine(stderrBuffer)) {
1097
- process.stderr.write(`${stderrBuffer}
1098
- `);
1099
- }
1100
- if (code !== 0 && !errorMessage && !this.aborted) {
1101
- const detail = stderrFull.trim() || lastResultContent.trim();
1102
- errorMessage = this.createExecutionError(code, detail).message;
1103
- this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
1104
- }
1105
- signalEnd();
1106
- });
1107
- claude.stdin.write(prompt);
1108
- claude.stdin.end();
1109
- while (true) {
1110
- if (chunkQueue.length > 0) {
1111
- const chunk = chunkQueue.shift();
1112
- if (chunk)
1113
- yield chunk;
1114
- } else if (processEnded) {
1115
- if (errorMessage) {
1116
- yield { type: "error", error: errorMessage };
1117
- this.eventEmitter?.emitSessionEnded(false);
1118
- } else {
1119
- if (finalContent) {
1120
- this.eventEmitter?.emitResponseCompleted(finalContent);
1121
- }
1122
- this.eventEmitter?.emitSessionEnded(true);
1123
- }
1124
- break;
1125
- } else {
1126
- const chunk = await new Promise((resolve2) => {
1127
- resolveChunk = resolve2;
1128
- });
1129
- if (chunk === null) {
1130
- if (errorMessage) {
1131
- yield { type: "error", error: errorMessage };
1132
- this.eventEmitter?.emitSessionEnded(false);
1133
- } else {
1134
- if (finalContent) {
1135
- this.eventEmitter?.emitResponseCompleted(finalContent);
1136
- }
1137
- this.eventEmitter?.emitSessionEnded(true);
1138
- }
1139
- break;
1140
- }
1141
- yield chunk;
1142
- }
1143
- }
1144
- }
1145
- emitEventForChunk(chunk, isThinking) {
1146
- if (!this.eventEmitter)
1147
- return;
1148
- switch (chunk.type) {
1149
- case "text_delta":
1150
- this.eventEmitter.emitTextDelta(chunk.content);
1151
- break;
1152
- case "tool_use":
1153
- if (this.currentToolName) {
1154
- this.eventEmitter.emitToolCompleted(this.currentToolName);
1155
- }
1156
- this.currentToolName = chunk.tool;
1157
- this.eventEmitter.emitToolStarted(chunk.tool, chunk.id);
1158
- break;
1159
- case "thinking":
1160
- if (!isThinking) {
1161
- this.eventEmitter.emitThinkingStarted(chunk.content);
1162
- }
1163
- break;
1164
- case "result":
1165
- if (this.currentToolName) {
1166
- this.eventEmitter.emitToolCompleted(this.currentToolName);
1167
- this.currentToolName = undefined;
1168
- }
1169
- break;
1170
- case "error":
1171
- this.eventEmitter.emitErrorOccurred(chunk.error);
1172
- break;
1173
- }
1174
- }
1175
- executeRun(prompt) {
1176
- this.aborted = false;
1177
- const parser = new ClaudeStreamParser;
1178
- return new Promise((resolve2, reject) => {
1179
- const args = this.buildCliArgs();
1180
- const env = getAugmentedEnv({
1181
- FORCE_COLOR: "1",
1182
- TERM: "xterm-256color"
1183
- });
1184
- const claude = import_node_child_process.spawn("claude", args, {
1185
- cwd: this.projectPath,
1186
- stdio: ["pipe", "pipe", "pipe"],
1187
- env
1188
- });
1189
- this.activeProcess = claude;
1190
- let finalResult = "";
1191
- let errorOutput = "";
1192
- let buffer = "";
1193
- let stderrBuffer = "";
1194
- claude.stdout.on("data", (data) => {
1195
- buffer += data.toString();
1196
- const lines = buffer.split(`
1197
- `);
1198
- buffer = lines.pop() || "";
1199
- for (const line of lines) {
1200
- const result = parser.parseLine(line, this.log);
1201
- if (result)
1202
- finalResult = result;
1203
- }
1204
- });
1205
- claude.stderr.on("data", (data) => {
1206
- const chunk = data.toString();
1207
- errorOutput += chunk;
1208
- stderrBuffer += chunk;
1209
- const lines = stderrBuffer.split(`
1210
- `);
1211
- stderrBuffer = lines.pop() || "";
1212
- for (const line of lines) {
1213
- if (!this.shouldSuppressLine(line)) {
1214
- process.stderr.write(`${line}
1215
- `);
1216
- }
1217
- }
1218
- });
1219
- claude.on("error", (err) => {
1220
- reject(new Error(`Failed to start Claude CLI: ${err.message}. Please ensure the 'claude' command is available in your PATH.`));
1221
- });
1222
- claude.on("close", (code) => {
1223
- this.activeProcess = null;
1224
- if (stderrBuffer && !this.shouldSuppressLine(stderrBuffer)) {
1225
- process.stderr.write(`${stderrBuffer}
1226
- `);
1227
- }
1228
- process.stdout.write(`
1229
- `);
1230
- if (code === 0 || this.aborted) {
1231
- resolve2(finalResult);
1232
- } else {
1233
- const detail = errorOutput.trim() || finalResult.trim();
1234
- reject(this.createExecutionError(code, detail));
1235
- }
1236
- });
1237
- claude.stdin.write(prompt);
1238
- claude.stdin.end();
1239
- });
1240
- }
1241
- shouldSuppressLine(line) {
1242
- const infoLogRegex = /^\[\d{2}:\d{2}:\d{2}\]\s\[.*?\]\sℹ\s*$/;
1243
- return infoLogRegex.test(line.trim());
1244
- }
1245
- createExecutionError(code, detail) {
1246
- const errorMsg = detail.trim();
1247
- const message = errorMsg ? `Claude CLI error (exit code ${code}): ${errorMsg}` : `Claude CLI exited with code ${code}. Please ensure the Claude CLI is installed and you are logged in.`;
1248
- return new Error(message);
1249
- }
1250
- }
1251
- var import_node_child_process, import_node_path3, DEFAULT_TIMEOUT_MS;
1252
- var init_claude_runner = __esm(() => {
1253
- init_config();
1254
- init_resolve_bin();
1255
- init_claude_stream_parser();
1256
- import_node_child_process = require("node:child_process");
1257
- import_node_path3 = require("node:path");
1258
- DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
1259
- });
1260
-
1261
- // src/ai/codex-runner.ts
1262
- class CodexRunner {
1263
- projectPath;
1264
- model;
1265
- log;
1266
- reasoningEffort;
1267
- activeProcess = null;
1268
- aborted = false;
1269
- eventEmitter;
1270
- currentToolName;
1271
- timeoutMs;
1272
- constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log, reasoningEffort, timeoutMs) {
1273
- this.projectPath = projectPath;
1274
- this.model = model;
1275
- this.log = log;
1276
- this.reasoningEffort = reasoningEffort;
1277
- this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS2;
1278
- }
1279
- setEventEmitter(emitter) {
1280
- this.eventEmitter = emitter;
1281
- }
1282
- abort() {
1283
- if (this.activeProcess && !this.activeProcess.killed) {
1284
- this.aborted = true;
1285
- this.activeProcess.kill("SIGTERM");
1286
- this.activeProcess = null;
1287
- }
1288
- }
1289
- async run(prompt) {
1290
- this.aborted = false;
1291
- const maxRetries = 3;
1292
- let lastError = null;
1293
- for (let attempt = 1;attempt <= maxRetries; attempt++) {
1294
- try {
1295
- return await this.withTimeout(this.executeRun(prompt));
1296
- } catch (error) {
1297
- lastError = error;
1298
- if (lastError.message.includes("timed out")) {
1299
- throw lastError;
1300
- }
1301
- if (attempt < maxRetries) {
1302
- const delay = Math.pow(2, attempt) * 1000;
1303
- this.log?.(`Codex CLI attempt ${attempt} failed: ${lastError.message}. Retrying in ${delay}ms...`, "warn");
1304
- await this.sleep(delay);
1305
- }
1306
- }
1307
- }
1308
- throw lastError || new Error("Codex CLI failed after multiple attempts");
1309
- }
1310
- withTimeout(promise) {
1311
- if (this.timeoutMs <= 0)
1312
- return promise;
1313
- return new Promise((resolve2, reject) => {
1314
- const timer = setTimeout(() => {
1315
- this.abort();
1316
- reject(new Error(`Codex CLI execution timed out after ${Math.round(this.timeoutMs / 60000)} minutes`));
1317
- }, this.timeoutMs);
1318
- promise.then((value) => {
1319
- clearTimeout(timer);
1320
- resolve2(value);
1321
- }, (err) => {
1322
- clearTimeout(timer);
1323
- reject(err);
1324
- });
1325
- });
1326
- }
1327
- async* runStream(prompt) {
1328
- const outputPath = import_node_path4.join(import_node_os2.tmpdir(), `locus-codex-${import_node_crypto.randomUUID()}.txt`);
1329
- const args = this.buildArgs(outputPath);
1330
- this.eventEmitter?.emitSessionStarted({
1331
- model: this.model,
1332
- provider: "codex"
1333
- });
1334
- this.eventEmitter?.emitPromptSubmitted(prompt, prompt.length > 500);
1335
- const codex = import_node_child_process2.spawn("codex", args, {
1336
- cwd: this.projectPath,
1337
- stdio: ["pipe", "pipe", "pipe"],
1338
- env: getAugmentedEnv(),
1339
- shell: false
1340
- });
1341
- this.activeProcess = codex;
1342
- let resolveChunk = null;
1343
- const chunkQueue = [];
1344
- let processEnded = false;
1345
- let errorMessage = "";
1346
- let finalOutput = "";
1347
- let finalContent = "";
1348
- let isThinking = false;
1349
- const enqueueChunk = (chunk) => {
1350
- this.emitEventForChunk(chunk, isThinking);
1351
- if (chunk.type === "thinking") {
1352
- isThinking = true;
1353
- } else if (chunk.type === "text_delta" || chunk.type === "tool_use") {
1354
- if (isThinking) {
1355
- this.eventEmitter?.emitThinkingStoped();
1356
- isThinking = false;
1357
- }
1358
- }
1359
- if (chunk.type === "text_delta") {
1360
- finalContent += chunk.content;
1361
- }
1362
- if (resolveChunk) {
1363
- const resolve2 = resolveChunk;
1364
- resolveChunk = null;
1365
- resolve2(chunk);
1366
- } else {
1367
- chunkQueue.push(chunk);
1368
- }
1369
- };
1370
- const signalEnd = () => {
1371
- processEnded = true;
1372
- if (resolveChunk) {
1373
- resolveChunk(null);
1374
- resolveChunk = null;
1375
- }
1376
- };
1377
- const processOutput = (data) => {
1378
- const msg = data.toString();
1379
- finalOutput += msg;
1380
- for (const rawLine of msg.split(`
1381
- `)) {
1382
- const line = rawLine.trim();
1383
- if (!line)
1384
- continue;
1385
- if (/^thinking\b/i.test(line)) {
1386
- enqueueChunk({ type: "thinking", content: line });
1387
- } else if (/^[→•✓]/.test(line) || /^Plan update\b/.test(line)) {
1388
- enqueueChunk({
1389
- type: "tool_use",
1390
- tool: line.replace(/^[→•✓]\s*/, "")
1391
- });
1392
- }
1393
- }
1394
- };
1395
- codex.stdout.on("data", processOutput);
1396
- codex.stderr.on("data", (data) => {
1397
- finalOutput += data.toString();
1398
- });
1399
- codex.on("error", (err) => {
1400
- errorMessage = `Failed to start Codex CLI: ${err.message}. Ensure 'codex' is installed and available in PATH.`;
1401
- this.eventEmitter?.emitErrorOccurred(errorMessage, "SPAWN_ERROR");
1402
- signalEnd();
1403
- });
1404
- codex.on("close", (code) => {
1405
- this.activeProcess = null;
1406
- if (code === 0 || this.aborted) {
1407
- const result = this.readOutput(outputPath, finalOutput);
1408
- this.cleanupTempFile(outputPath);
1409
- if (result && finalContent.trim().length === 0) {
1410
- enqueueChunk({ type: "text_delta", content: result });
1411
- }
1412
- enqueueChunk({ type: "result", content: result });
1413
- } else {
1414
- this.cleanupTempFile(outputPath);
1415
- if (!errorMessage) {
1416
- errorMessage = this.createErrorFromOutput(code, finalOutput).message;
1417
- this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
1418
- }
1419
- }
1420
- signalEnd();
1421
- });
1422
- codex.stdin.write(prompt);
1423
- codex.stdin.end();
1424
- while (true) {
1425
- if (chunkQueue.length > 0) {
1426
- const chunk = chunkQueue.shift();
1427
- if (chunk)
1428
- yield chunk;
1429
- } else if (processEnded) {
1430
- if (errorMessage) {
1431
- yield { type: "error", error: errorMessage };
1432
- this.eventEmitter?.emitSessionEnded(false);
1433
- } else {
1434
- if (finalContent) {
1435
- this.eventEmitter?.emitResponseCompleted(finalContent);
1436
- }
1437
- this.eventEmitter?.emitSessionEnded(true);
1438
- }
1439
- break;
1440
- } else {
1441
- const chunk = await new Promise((resolve2) => {
1442
- resolveChunk = resolve2;
1443
- });
1444
- if (chunk === null) {
1445
- if (errorMessage) {
1446
- yield { type: "error", error: errorMessage };
1447
- this.eventEmitter?.emitSessionEnded(false);
1448
- } else {
1449
- if (finalContent) {
1450
- this.eventEmitter?.emitResponseCompleted(finalContent);
1451
- }
1452
- this.eventEmitter?.emitSessionEnded(true);
1453
- }
1454
- break;
1455
- }
1456
- yield chunk;
1457
- }
1458
- }
1459
- }
1460
- emitEventForChunk(chunk, isThinking) {
1461
- if (!this.eventEmitter)
1462
- return;
1463
- switch (chunk.type) {
1464
- case "text_delta":
1465
- this.eventEmitter.emitTextDelta(chunk.content);
1466
- break;
1467
- case "tool_use":
1468
- if (this.currentToolName) {
1469
- this.eventEmitter.emitToolCompleted(this.currentToolName);
1470
- }
1471
- this.currentToolName = chunk.tool;
1472
- this.eventEmitter.emitToolStarted(chunk.tool);
1473
- break;
1474
- case "thinking":
1475
- if (!isThinking) {
1476
- this.eventEmitter.emitThinkingStarted(chunk.content);
1477
- }
1478
- break;
1479
- case "result":
1480
- if (this.currentToolName) {
1481
- this.eventEmitter.emitToolCompleted(this.currentToolName);
1482
- this.currentToolName = undefined;
1483
- }
1484
- break;
1485
- case "error":
1486
- this.eventEmitter.emitErrorOccurred(chunk.error);
1487
- break;
1488
- }
1489
- }
1490
- executeRun(prompt) {
1491
- return new Promise((resolve2, reject) => {
1492
- const outputPath = import_node_path4.join(import_node_os2.tmpdir(), `locus-codex-${import_node_crypto.randomUUID()}.txt`);
1493
- const args = this.buildArgs(outputPath);
1494
- const codex = import_node_child_process2.spawn("codex", args, {
1495
- cwd: this.projectPath,
1496
- stdio: ["pipe", "pipe", "pipe"],
1497
- env: getAugmentedEnv(),
1498
- shell: false
1499
- });
1500
- this.activeProcess = codex;
1501
- let output = "";
1502
- let errorOutput = "";
1503
- const handleOutput = (data) => {
1504
- const msg = data.toString();
1505
- output += msg;
1506
- this.streamToConsole(msg);
1507
- };
1508
- codex.stdout.on("data", handleOutput);
1509
- codex.stderr.on("data", (data) => {
1510
- const msg = data.toString();
1511
- errorOutput += msg;
1512
- this.streamToConsole(msg);
1513
- });
1514
- codex.on("error", (err) => {
1515
- reject(new Error(`Failed to start Codex CLI: ${err.message}. ` + `Ensure 'codex' is installed and available in PATH.`));
1516
- });
1517
- codex.on("close", (code) => {
1518
- this.activeProcess = null;
1519
- if (code === 0 || this.aborted) {
1520
- const result = this.readOutput(outputPath, output);
1521
- this.cleanupTempFile(outputPath);
1522
- resolve2(result);
1523
- } else {
1524
- this.cleanupTempFile(outputPath);
1525
- reject(this.createErrorFromOutput(code, errorOutput));
1526
- }
1527
- });
1528
- codex.stdin.write(prompt);
1529
- codex.stdin.end();
1530
- });
1531
- }
1532
- buildArgs(outputPath) {
1533
- const args = [
1534
- "exec",
1535
- "--full-auto",
1536
- "--skip-git-repo-check",
1537
- "--output-last-message",
1538
- outputPath
1539
- ];
1540
- if (this.model) {
1541
- args.push("--model", this.model);
1542
- }
1543
- if (this.reasoningEffort) {
1544
- args.push("-c", `model_reasoning_effort=${this.reasoningEffort}`);
1545
- }
1546
- args.push("-");
1547
- return args;
1548
- }
1549
- streamToConsole(chunk) {
1550
- for (const rawLine of chunk.split(`
1551
- `)) {
1552
- const line = rawLine.trim();
1553
- if (line && this.shouldDisplay(line)) {
1554
- const formattedLine = "[Codex]: ".concat(line.replace(/\*/g, ""));
1555
- this.log?.(formattedLine, "info");
1556
- }
1557
- }
1558
- }
1559
- shouldDisplay(line) {
1560
- return /^Plan update\b/.test(line);
1561
- }
1562
- readOutput(outputPath, fallback) {
1563
- if (import_node_fs2.existsSync(outputPath)) {
1564
- try {
1565
- const text = import_node_fs2.readFileSync(outputPath, "utf-8").trim();
1566
- if (text)
1567
- return text;
1568
- } catch {}
1569
- }
1570
- return fallback.trim();
1571
- }
1572
- createErrorFromOutput(code, errorOutput) {
1573
- const detail = errorOutput.trim();
1574
- const message = detail ? `Codex CLI error (exit code ${code}): ${detail}` : `Codex CLI exited with code ${code}. ` + `Ensure Codex CLI is installed and you are logged in.`;
1575
- return new Error(message);
1576
- }
1577
- cleanupTempFile(path) {
1578
- try {
1579
- if (import_node_fs2.existsSync(path))
1580
- import_node_fs2.unlinkSync(path);
1581
- } catch {}
1582
- }
1583
- sleep(ms) {
1584
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1585
- }
1586
- }
1587
- var import_node_child_process2, import_node_crypto, import_node_fs2, import_node_os2, import_node_path4, DEFAULT_TIMEOUT_MS2;
1588
- var init_codex_runner = __esm(() => {
1589
- init_config();
1590
- init_resolve_bin();
1591
- import_node_child_process2 = require("node:child_process");
1592
- import_node_crypto = require("node:crypto");
1593
- import_node_fs2 = require("node:fs");
1594
- import_node_os2 = require("node:os");
1595
- import_node_path4 = require("node:path");
1596
- DEFAULT_TIMEOUT_MS2 = 60 * 60 * 1000;
1597
- });
1598
-
1599
- // src/ai/factory.ts
1600
- function createWorkerLogger(agentId, prefix) {
1601
- const tag = prefix ? `${prefix}:${agentId.slice(-8)}` : agentId.slice(-8);
1602
- return (message, level = "info") => {
1603
- const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
1604
- const colorFn = {
1605
- info: c.cyan,
1606
- success: c.green,
1607
- warn: c.yellow,
1608
- error: c.red
1609
- }[level];
1610
- const icon = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
1611
- console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[${tag}]`)} ${colorFn(`${icon} ${message}`)}`);
1612
- };
1613
- }
1614
- function createAiRunner(provider, config) {
1615
- const resolvedProvider = provider ?? PROVIDER.CLAUDE;
1616
- const model = config.model ?? DEFAULT_MODEL[resolvedProvider];
1617
- if (!isValidModelForProvider(resolvedProvider, model)) {
1618
- const validModels = getModelsForProvider(resolvedProvider);
1619
- throw new Error(`Model "${model}" is not valid for provider "${resolvedProvider}". ` + `Valid models: ${validModels.join(", ")}`);
1620
- }
1621
- switch (resolvedProvider) {
1622
- case PROVIDER.CODEX:
1623
- return new CodexRunner(config.projectPath, model, config.log, config.reasoningEffort ?? "high", config.timeoutMs);
1624
- default:
1625
- return new ClaudeRunner(config.projectPath, model, config.log, config.timeoutMs);
1626
- }
1627
- }
1628
- var noopLogger = () => {};
1629
- var init_factory = __esm(() => {
1630
- init_config();
1631
- init_colors();
1632
- init_claude_runner();
1633
- init_codex_runner();
1634
- });
1635
-
1636
- // src/git/git-utils.ts
1637
- function isGitAvailable() {
1638
- try {
1639
- import_node_child_process3.execFileSync("git", ["--version"], {
1640
- encoding: "utf-8",
1641
- stdio: ["pipe", "pipe", "pipe"]
1642
- });
1643
- return true;
1644
- } catch {
1645
- return false;
1646
- }
1647
- }
1648
- function isGhAvailable(projectPath) {
1649
- try {
1650
- import_node_child_process3.execFileSync("gh", ["auth", "status"], {
1651
- cwd: projectPath,
1652
- encoding: "utf-8",
1653
- stdio: ["pipe", "pipe", "pipe"]
1654
- });
1655
- return true;
1656
- } catch {
1657
- return false;
1658
- }
1659
- }
1660
- function getGhUsername() {
1661
- try {
1662
- const output = import_node_child_process3.execFileSync("gh", ["api", "user", "--jq", ".login"], {
1663
- encoding: "utf-8",
1664
- stdio: ["pipe", "pipe", "pipe"]
1665
- }).trim();
1666
- return output || null;
1667
- } catch {
1668
- return null;
1669
- }
1670
- }
1671
- function detectRemoteProvider(projectPath) {
1672
- const url = getRemoteUrl(projectPath);
1673
- if (!url)
1674
- return "unknown";
1675
- if (url.includes("github.com"))
1676
- return "github";
1677
- if (url.includes("gitlab.com") || url.includes("gitlab"))
1678
- return "gitlab";
1679
- if (url.includes("bitbucket.org"))
1680
- return "bitbucket";
1681
- return "unknown";
1682
- }
1683
- function getRemoteUrl(projectPath, remote = "origin") {
1684
- try {
1685
- return import_node_child_process3.execFileSync("git", ["remote", "get-url", remote], {
1686
- cwd: projectPath,
1687
- encoding: "utf-8",
1688
- stdio: ["pipe", "pipe", "pipe"]
1689
- }).trim();
1690
- } catch {
1691
- return null;
1692
- }
1693
- }
1694
- function getCurrentBranch(projectPath) {
1695
- return import_node_child_process3.execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
1696
- cwd: projectPath,
1697
- encoding: "utf-8",
1698
- stdio: ["pipe", "pipe", "pipe"]
1699
- }).trim();
1700
- }
1701
- function getDefaultBranch(projectPath, remote = "origin") {
1702
- try {
1703
- const ref = import_node_child_process3.execFileSync("git", ["symbolic-ref", `refs/remotes/${remote}/HEAD`], {
1704
- cwd: projectPath,
1705
- encoding: "utf-8",
1706
- stdio: ["pipe", "pipe", "pipe"]
1707
- }).trim();
1708
- return ref.replace(`refs/remotes/${remote}/`, "");
1709
- } catch {
1710
- for (const candidate of ["main", "master"]) {
1711
- try {
1712
- import_node_child_process3.execFileSync("git", ["ls-remote", "--exit-code", "--heads", remote, candidate], {
1713
- cwd: projectPath,
1714
- encoding: "utf-8",
1715
- stdio: ["pipe", "pipe", "pipe"]
1716
- });
1717
- return candidate;
1718
- } catch {}
1719
- }
1720
- try {
1721
- return getCurrentBranch(projectPath);
1722
- } catch {
1723
- return "main";
1724
- }
1725
- }
1726
- }
1727
- var import_node_child_process3;
1728
- var init_git_utils = __esm(() => {
1729
- import_node_child_process3 = require("node:child_process");
1730
- });
1731
-
1732
- // src/agent/git-workflow.ts
1733
- class GitWorkflow {
1734
- config;
1735
- log;
1736
- projectPath;
1737
- branchName = null;
1738
- baseBranch = null;
1739
- ghUsername;
1740
- constructor(config, log) {
1741
- this.config = config;
1742
- this.log = log;
1743
- this.projectPath = config.projectPath || process.cwd();
1744
- this.ghUsername = getGhUsername();
1745
- if (this.ghUsername) {
1746
- this.log(`GitHub user: ${this.ghUsername}`, "info");
1747
- }
1748
- }
1749
- createBranch(sprintId) {
1750
- const defaultBranch = getDefaultBranch(this.projectPath);
1751
- this.baseBranch = defaultBranch;
1752
- try {
1753
- this.gitExec(["checkout", defaultBranch]);
1754
- this.gitExec(["pull", "origin", defaultBranch]);
1755
- } catch {
1756
- this.log(`Could not pull latest from ${defaultBranch}, continuing with current state`, "warn");
1757
- }
1758
- const suffix = sprintId ? sprintId.slice(0, 8) : Date.now().toString(36);
1759
- this.branchName = `locus/${suffix}`;
1760
- try {
1761
- this.gitExec(["checkout", this.branchName]);
1762
- } catch {
1763
- this.log(`Branch ${this.branchName} does not exist, creating it`, "info");
1764
- this.gitExec(["checkout", "-b", this.branchName]);
1765
- this.log(`Created branch: ${this.branchName} (from ${defaultBranch})`, "success");
1766
- }
1767
- return this.branchName;
1768
- }
1769
- commitAndPush(task) {
1770
- if (!this.branchName) {
1771
- this.log("No branch created yet, skipping commit", "warn");
1772
- return { branch: null, pushed: false, pushFailed: false };
1773
- }
1774
- try {
1775
- const status = this.gitExec(["status", "--porcelain"]).trim();
1776
- if (!status) {
1777
- const baseBranchCommit = this.getBaseCommit();
1778
- const headCommit = this.gitExec(["rev-parse", "HEAD"]).trim();
1779
- if (baseBranchCommit && headCommit !== baseBranchCommit) {
1780
- return this.pushBranch();
1781
- }
1782
- this.log("No changes to commit for this task", "info");
1783
- return {
1784
- branch: this.branchName,
1785
- pushed: false,
1786
- pushFailed: false,
1787
- noChanges: true,
1788
- skipReason: "No changes were made for this task."
1789
- };
1790
- }
1791
- this.gitExec(["add", "-A"]);
1792
- const staged = this.gitExec(["diff", "--cached", "--name-only"]).trim();
1793
- if (!staged) {
1794
- this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
1795
- return {
1796
- branch: this.branchName,
1797
- pushed: false,
1798
- pushFailed: false,
1799
- noChanges: true,
1800
- skipReason: "All changes were ignored by .gitignore."
1801
- };
1802
- }
1803
- this.log(`Staging ${staged.split(`
1804
- `).length} file(s) for commit`, "info");
1805
- const trailers = [
1806
- `Task-ID: ${task.id}`,
1807
- `Agent: ${this.config.agentId}`,
1808
- "Co-authored-by: LocusAI <agent@locusai.team>"
1809
- ];
1810
- if (this.ghUsername) {
1811
- trailers.push(`Co-authored-by: ${this.ghUsername} <${this.ghUsername}@users.noreply.github.com>`);
1812
- }
1813
- const commitMessage = `feat(agent): ${task.title}
1814
-
1815
- ${trailers.join(`
1816
- `)}`;
1817
- this.gitExec(["commit", "-m", commitMessage]);
1818
- const hash = this.gitExec(["rev-parse", "HEAD"]).trim();
1819
- this.log(`Committed: ${hash.slice(0, 8)}`, "success");
1820
- return this.pushBranch();
1821
- } catch (err) {
1822
- const errorMessage = err instanceof Error ? err.message : String(err);
1823
- this.log(`Git commit failed: ${errorMessage}`, "error");
1824
- return {
1825
- branch: this.branchName,
1826
- pushed: false,
1827
- pushFailed: true,
1828
- pushError: `Git commit/push failed: ${errorMessage}`
1829
- };
1830
- }
1831
- }
1832
- pushBranch() {
1833
- if (!this.branchName) {
1834
- return { branch: null, pushed: false, pushFailed: false };
1835
- }
1836
- try {
1837
- this.gitExec(["push", "-u", "origin", this.branchName]);
1838
- this.log(`Pushed ${this.branchName} to origin`, "success");
1839
- return { branch: this.branchName, pushed: true, pushFailed: false };
1840
- } catch (error) {
1841
- const msg = error instanceof Error ? error.message : String(error);
1842
- if (msg.includes("non-fast-forward") || msg.includes("[rejected]") || msg.includes("fetch first")) {
1843
- this.log(`Push rejected (non-fast-forward). Retrying with --force-with-lease.`, "warn");
1844
- try {
1845
- this.gitExec([
1846
- "push",
1847
- "--force-with-lease",
1848
- "-u",
1849
- "origin",
1850
- this.branchName
1851
- ]);
1852
- this.log(`Pushed ${this.branchName} to origin with --force-with-lease`, "success");
1853
- return { branch: this.branchName, pushed: true, pushFailed: false };
1854
- } catch (retryErr) {
1855
- const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
1856
- this.log(`Git push retry failed: ${retryMsg}`, "error");
1857
- return {
1858
- branch: this.branchName,
1859
- pushed: false,
1860
- pushFailed: true,
1861
- pushError: retryMsg
1862
- };
1863
- }
1864
- }
1865
- this.log(`Git push failed: ${msg}`, "error");
1866
- return {
1867
- branch: this.branchName,
1868
- pushed: false,
1869
- pushFailed: true,
1870
- pushError: msg
1871
- };
1872
- }
1873
- }
1874
- createPullRequest(completedTasks, summaries) {
1875
- if (!this.branchName || !this.baseBranch) {
1876
- return { url: null, error: "No branch or base branch available." };
1877
- }
1878
- const provider = detectRemoteProvider(this.projectPath);
1879
- if (provider !== "github") {
1880
- return {
1881
- url: null,
1882
- error: `PR creation is only supported for GitHub repositories (detected: ${provider})`
1883
- };
1884
- }
1885
- if (!isGhAvailable(this.projectPath)) {
1886
- return {
1887
- url: null,
1888
- error: "GitHub CLI (gh) is not installed or not authenticated. Install from https://cli.github.com/"
1889
- };
1890
- }
1891
- const title = `[Locus] Sprint tasks (${completedTasks.length} task${completedTasks.length !== 1 ? "s" : ""})`;
1892
- const body = this.buildPrBody(completedTasks, summaries);
1893
- this.log(`Creating PR: ${title} (${this.branchName} → ${this.baseBranch})`, "info");
1894
- try {
1895
- const output = import_node_child_process4.execFileSync("gh", [
1896
- "pr",
1897
- "create",
1898
- "--title",
1899
- title,
1900
- "--body",
1901
- body,
1902
- "--base",
1903
- this.baseBranch,
1904
- "--head",
1905
- this.branchName
1906
- ], {
1907
- cwd: this.projectPath,
1908
- encoding: "utf-8",
1909
- stdio: ["pipe", "pipe", "pipe"]
1910
- }).trim();
1911
- this.log(`PR created: ${output}`, "success");
1912
- return { url: output };
1913
- } catch (err) {
1914
- const errorMessage = err instanceof Error ? err.message : String(err);
1915
- this.log(`PR creation failed: ${errorMessage}`, "error");
1916
- return { url: null, error: errorMessage };
1917
- }
1918
- }
1919
- checkoutBaseBranch() {
1920
- if (!this.baseBranch)
1921
- return;
1922
- try {
1923
- this.gitExec(["checkout", this.baseBranch]);
1924
- this.log(`Checked out base branch: ${this.baseBranch}`, "info");
1925
- } catch (err) {
1926
- this.log(`Could not checkout base branch: ${err instanceof Error ? err.message : String(err)}`, "warn");
1927
- }
1928
- }
1929
- getBranchName() {
1930
- return this.branchName;
1931
- }
1932
- getBaseBranch() {
1933
- return this.baseBranch;
1934
- }
1935
- getBaseCommit() {
1936
- if (!this.baseBranch)
1937
- return null;
1938
- try {
1939
- return this.gitExec(["rev-parse", this.baseBranch]).trim();
1940
- } catch {
1941
- return null;
1942
- }
1943
- }
1944
- buildPrBody(completedTasks, summaries) {
1945
- const sections = [];
1946
- sections.push("## Completed Tasks");
1947
- sections.push("");
1948
- for (let i = 0;i < completedTasks.length; i++) {
1949
- const task = completedTasks[i];
1950
- sections.push(`### ${i + 1}. ${task.title}`);
1951
- sections.push(`Task ID: \`${task.id}\``);
1952
- if (summaries[i]) {
1953
- sections.push("");
1954
- sections.push(summaries[i]);
1955
- }
1956
- sections.push("");
1957
- }
1958
- sections.push("---");
1959
- sections.push(`*Created by Locus Agent \`${this.config.agentId.slice(-8)}\`*`);
1960
- return sections.join(`
1961
- `);
1962
- }
1963
- gitExec(args) {
1964
- return import_node_child_process4.execFileSync("git", args, {
1965
- cwd: this.projectPath,
1966
- encoding: "utf-8",
1967
- stdio: ["pipe", "pipe", "pipe"]
1968
- });
1969
- }
1970
- }
1971
- var import_node_child_process4;
1972
- var init_git_workflow = __esm(() => {
1973
- init_git_utils();
1974
- import_node_child_process4 = require("node:child_process");
1975
- });
1976
-
1977
- // src/discussion/discussion-manager.ts
1978
- class DiscussionManager {
1979
- discussionsDir;
1980
- constructor(projectPath) {
1981
- this.discussionsDir = getLocusPath(projectPath, "discussionsDir");
1982
- }
1983
- create(topic, model, provider) {
1984
- this.ensureDir();
1985
- const now = new Date().toISOString();
1986
- const id = `disc-${Date.now()}`;
1987
- const discussion = {
1988
- id,
1989
- title: topic,
1990
- topic,
1991
- status: "active",
1992
- messages: [],
1993
- insights: [],
1994
- createdAt: now,
1995
- updatedAt: now,
1996
- metadata: { model, provider }
1997
- };
1998
- this.save(discussion);
1999
- return discussion;
2000
- }
2001
- save(discussion) {
2002
- this.ensureDir();
2003
- const jsonPath = import_node_path5.join(this.discussionsDir, `${discussion.id}.json`);
2004
- const mdPath = import_node_path5.join(this.discussionsDir, `summary-${discussion.id}.md`);
2005
- import_node_fs3.writeFileSync(jsonPath, JSON.stringify(discussion, null, 2), "utf-8");
2006
- import_node_fs3.writeFileSync(mdPath, this.toMarkdown(discussion), "utf-8");
2007
- }
2008
- load(id) {
2009
- this.ensureDir();
2010
- const filePath = import_node_path5.join(this.discussionsDir, `${id}.json`);
2011
- if (!import_node_fs3.existsSync(filePath)) {
2012
- return null;
2013
- }
2014
- try {
2015
- return JSON.parse(import_node_fs3.readFileSync(filePath, "utf-8"));
2016
- } catch {
2017
- return null;
2018
- }
2019
- }
2020
- list(status) {
2021
- this.ensureDir();
2022
- const files = import_node_fs3.readdirSync(this.discussionsDir).filter((f) => f.endsWith(".json"));
2023
- const discussions = [];
2024
- for (const file of files) {
2025
- try {
2026
- const discussion = JSON.parse(import_node_fs3.readFileSync(import_node_path5.join(this.discussionsDir, file), "utf-8"));
2027
- if (!status || discussion.status === status) {
2028
- discussions.push(discussion);
2029
- }
2030
- } catch {}
2031
- }
2032
- discussions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
2033
- return discussions;
2034
- }
2035
- complete(id) {
2036
- const discussion = this.load(id);
2037
- if (!discussion) {
2038
- throw new Error(`Discussion not found: ${id}`);
2039
- }
2040
- discussion.status = "completed";
2041
- discussion.updatedAt = new Date().toISOString();
2042
- this.save(discussion);
2043
- return discussion;
2044
- }
2045
- archive(id) {
2046
- const discussion = this.load(id);
2047
- if (!discussion) {
2048
- throw new Error(`Discussion not found: ${id}`);
2049
- }
2050
- discussion.status = "archived";
2051
- discussion.updatedAt = new Date().toISOString();
2052
- this.save(discussion);
2053
- }
2054
- delete(id) {
2055
- this.ensureDir();
2056
- const jsonPath = import_node_path5.join(this.discussionsDir, `${id}.json`);
2057
- const mdPath = import_node_path5.join(this.discussionsDir, `summary-${id}.md`);
2058
- if (import_node_fs3.existsSync(jsonPath)) {
2059
- import_node_fs3.unlinkSync(jsonPath);
2060
- }
2061
- if (import_node_fs3.existsSync(mdPath)) {
2062
- import_node_fs3.unlinkSync(mdPath);
2063
- }
2064
- }
2065
- addMessage(id, role, content) {
2066
- const discussion = this.load(id);
2067
- if (!discussion) {
2068
- throw new Error(`Discussion not found: ${id}`);
2069
- }
2070
- discussion.messages.push({
2071
- role,
2072
- content,
2073
- timestamp: Date.now()
2074
- });
2075
- discussion.updatedAt = new Date().toISOString();
2076
- this.save(discussion);
2077
- return discussion;
2078
- }
2079
- addInsight(id, insight) {
2080
- const discussion = this.load(id);
2081
- if (!discussion) {
2082
- throw new Error(`Discussion not found: ${id}`);
2083
- }
2084
- discussion.insights.push(insight);
2085
- discussion.updatedAt = new Date().toISOString();
2086
- this.save(discussion);
2087
- return discussion;
2088
- }
2089
- getAllInsights() {
2090
- const discussions = this.list("completed");
2091
- const insights = [];
2092
- for (const discussion of discussions) {
2093
- insights.push(...discussion.insights);
2094
- }
2095
- insights.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
2096
- return insights;
2097
- }
2098
- getMarkdown(id) {
2099
- const discussion = this.load(id);
2100
- if (!discussion)
2101
- return null;
2102
- return this.toMarkdown(discussion);
2103
- }
2104
- toMarkdown(discussion) {
2105
- const lines = [];
2106
- lines.push(`# Discussion: ${discussion.title}`);
2107
- lines.push("");
2108
- lines.push(`**Status:** ${discussion.status.toUpperCase()}`);
2109
- lines.push(`**Topic:** ${discussion.topic}`);
2110
- lines.push(`**Created:** ${discussion.createdAt}`);
2111
- lines.push(`**Updated:** ${discussion.updatedAt}`);
2112
- lines.push(`**Model:** ${discussion.metadata.model} (${discussion.metadata.provider})`);
2113
- lines.push("");
2114
- if (discussion.messages.length > 0) {
2115
- lines.push(`## Messages (${discussion.messages.length})`);
2116
- lines.push("");
2117
- for (const msg of discussion.messages) {
2118
- const time = new Date(msg.timestamp).toISOString();
2119
- const roleLabel = msg.role === "user" ? "User" : "Assistant";
2120
- lines.push(`### ${roleLabel} — ${time}`);
2121
- lines.push("");
2122
- lines.push(msg.content);
2123
- lines.push("");
2124
- }
2125
- }
2126
- if (discussion.insights.length > 0) {
2127
- lines.push(`## Insights (${discussion.insights.length})`);
2128
- lines.push("");
2129
- for (const insight of discussion.insights) {
2130
- lines.push(`### [${insight.type.toUpperCase()}] ${insight.title}`);
2131
- lines.push("");
2132
- lines.push(insight.content);
2133
- if (insight.tags.length > 0) {
2134
- lines.push("");
2135
- lines.push(`**Tags:** ${insight.tags.join(", ")}`);
2136
- }
2137
- lines.push("");
2138
- }
2139
- }
2140
- lines.push("---");
2141
- lines.push(`*Discussion ID: ${discussion.id}*`);
2142
- return lines.join(`
2143
- `);
2144
- }
2145
- ensureDir() {
2146
- if (!import_node_fs3.existsSync(this.discussionsDir)) {
2147
- import_node_fs3.mkdirSync(this.discussionsDir, { recursive: true });
2148
- }
2149
- }
2150
- }
2151
- var import_node_fs3, import_node_path5;
2152
- var init_discussion_manager = __esm(() => {
2153
- init_config();
2154
- import_node_fs3 = require("node:fs");
2155
- import_node_path5 = require("node:path");
2156
- });
2157
-
2158
- // src/core/prompt-builder.ts
2159
- class PromptBuilder {
2160
- projectPath;
2161
- constructor(projectPath) {
2162
- this.projectPath = projectPath;
2163
- }
2164
- async build(task) {
2165
- const roleText = this.roleToText(task.assigneeRole);
2166
- const description = task.description || "No description provided.";
2167
- const context = this.getProjectContext();
2168
- const learnings = this.getLearningsContent();
2169
- const knowledgeBase = this.getKnowledgeBaseSection();
2170
- let sections = "";
2171
- if (roleText) {
2172
- sections += `
2173
- <role>
2174
- You are acting as a ${roleText}.
2175
- </role>
2176
- `;
2177
- }
2178
- if (context) {
2179
- sections += `
2180
- <project_context>
2181
- ${context}
2182
- </project_context>
2183
- `;
2184
- }
2185
- sections += `
2186
- <knowledge_base>
2187
- ${knowledgeBase}
2188
- </knowledge_base>
2189
- `;
2190
- if (learnings) {
2191
- sections += `
2192
- <learnings>
2193
- These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
2194
- ${learnings}
2195
- </learnings>
2196
- `;
2197
- }
2198
- const discussionInsights = this.getDiscussionInsightsContent();
2199
- if (discussionInsights) {
2200
- sections += `
2201
- <discussion_insights>
2202
- These are key decisions and insights from product discussions. Follow them to maintain product coherence:
2203
- ${discussionInsights}
2204
- </discussion_insights>
2205
- `;
2206
- }
2207
- if (task.docs && task.docs.length > 0) {
2208
- let docsContent = "";
2209
- for (const doc of task.docs) {
2210
- const content = doc.content || "";
2211
- const limit = 800;
2212
- const preview = content.slice(0, limit);
2213
- const isTruncated = content.length > limit;
2214
- docsContent += `### ${doc.title}
2215
- ${preview}${isTruncated ? `
2216
- ...(truncated)...` : ""}
2217
-
2218
- `;
2219
- }
2220
- sections += `
2221
- <documents>
2222
- ${docsContent.trimEnd()}
2223
- </documents>
2224
- `;
2225
- }
2226
- if (task.acceptanceChecklist && task.acceptanceChecklist.length > 0) {
2227
- let criteria = "";
2228
- for (const item of task.acceptanceChecklist) {
2229
- criteria += `- ${item.done ? "[x]" : "[ ]"} ${item.text}
2230
- `;
2231
- }
2232
- sections += `
2233
- <acceptance_criteria>
2234
- ${criteria.trimEnd()}
2235
- </acceptance_criteria>
2236
- `;
2237
- }
2238
- if (task.comments && task.comments.length > 0) {
2239
- const filteredComments = task.comments.filter((comment) => comment.author !== "system");
2240
- const comments = filteredComments.slice(0, 3);
2241
- if (comments.length > 0) {
2242
- let commentsContent = "";
2243
- for (const comment of comments) {
2244
- const date = new Date(comment.createdAt).toLocaleString();
2245
- commentsContent += `- ${comment.author} (${date}): ${comment.text}
2246
- `;
2247
- }
2248
- sections += `
2249
- <feedback>
2250
- ${commentsContent.trimEnd()}
2251
- </feedback>
2252
- `;
2253
- }
2254
- }
2255
- return `<task_execution>
2256
- Complete this task: ${task.title}
2257
-
2258
- <description>
2259
- ${description}
2260
- </description>
2261
- ${sections}
2262
- <rules>
2263
- - Complete the task as described
2264
- - Save any high-level documentation (PRDs, technical drafts, architecture docs) in \`.locus/artifacts/\`
2265
- - Use relative paths from the project root at all times — no absolute local paths
2266
- - Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches — Locus handles git automatically
2267
- </rules>
2268
- </task_execution>`;
2269
- }
2270
- async buildGenericPrompt(query) {
2271
- const context = this.getProjectContext();
2272
- const learnings = this.getLearningsContent();
2273
- const knowledgeBase = this.getKnowledgeBaseSection();
2274
- let sections = "";
2275
- if (context) {
2276
- sections += `
2277
- <project_context>
2278
- ${context}
2279
- </project_context>
2280
- `;
2281
- }
2282
- sections += `
2283
- <knowledge_base>
2284
- ${knowledgeBase}
2285
- </knowledge_base>
2286
- `;
2287
- if (learnings) {
2288
- sections += `
2289
- <learnings>
2290
- These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
2291
- ${learnings}
2292
- </learnings>
2293
- `;
2294
- }
2295
- const discussionInsights = this.getDiscussionInsightsContent();
2296
- if (discussionInsights) {
2297
- sections += `
2298
- <discussion_insights>
2299
- These are key decisions and insights from product discussions. Follow them to maintain product coherence:
2300
- ${discussionInsights}
2301
- </discussion_insights>
2302
- `;
2303
- }
2304
- return `<direct_execution>
2305
- Execute this prompt: ${query}
2306
- ${sections}
2307
- <rules>
2308
- - Execute the prompt based on the provided project context
2309
- - Use relative paths from the project root at all times — no absolute local paths
2310
- - Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches — Locus handles git automatically
2311
- </rules>
2312
- </direct_execution>`;
2313
- }
2314
- getProjectContext() {
2315
- const contextPath = getLocusPath(this.projectPath, "contextFile");
2316
- if (import_node_fs4.existsSync(contextPath)) {
2317
- try {
2318
- const context = import_node_fs4.readFileSync(contextPath, "utf-8");
2319
- if (context.trim().length > 20) {
2320
- return context;
2321
- }
2322
- } catch (err) {
2323
- console.warn(`Warning: Could not read context file: ${err}`);
2324
- }
2325
- }
2326
- return this.getFallbackContext() || null;
2327
- }
2328
- getFallbackContext() {
2329
- const readmePath = import_node_path6.join(this.projectPath, "README.md");
2330
- if (import_node_fs4.existsSync(readmePath)) {
2331
- try {
2332
- const content = import_node_fs4.readFileSync(readmePath, "utf-8");
2333
- const limit = 1000;
2334
- return content.slice(0, limit) + (content.length > limit ? `
2335
- ...(truncated)...` : "");
2336
- } catch {
2337
- return "";
2338
- }
2339
- }
2340
- return "";
2341
- }
2342
- getKnowledgeBaseSection() {
2343
- return `You have access to the following documentation directories for context:
2344
- - Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
2345
- - Documents: \`.locus/documents\` (synced from cloud)
2346
- - Discussions: \`.locus/discussions\` (product discussion insights and decisions)
2347
- If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
2348
- }
2349
- getLearningsContent() {
2350
- const learningsPath = getLocusPath(this.projectPath, "learningsFile");
2351
- if (!import_node_fs4.existsSync(learningsPath)) {
2352
- return null;
2353
- }
2354
- try {
2355
- const content = import_node_fs4.readFileSync(learningsPath, "utf-8");
2356
- const lines = content.split(`
2357
- `).filter((l) => l.startsWith("- "));
2358
- if (lines.length === 0) {
2359
- return null;
2360
- }
2361
- return lines.join(`
2362
- `);
2363
- } catch {
2364
- return null;
2365
- }
2366
- }
2367
- getDiscussionInsightsContent() {
2368
- try {
2369
- const manager = new DiscussionManager(this.projectPath);
2370
- const insights = manager.getAllInsights();
2371
- if (insights.length === 0) {
2372
- return null;
2373
- }
2374
- const groups = {};
2375
- for (const insight of insights) {
2376
- const key = insight.type;
2377
- if (!groups[key]) {
2378
- groups[key] = [];
2379
- }
2380
- groups[key].push(insight);
2381
- }
2382
- const typeLabels = {
2383
- decision: "Decisions",
2384
- requirement: "Requirements",
2385
- idea: "Ideas",
2386
- concern: "Concerns",
2387
- learning: "Learnings"
2388
- };
2389
- let output = "";
2390
- for (const [type, label] of Object.entries(typeLabels)) {
2391
- const items = groups[type];
2392
- if (!items || items.length === 0)
2393
- continue;
2394
- output += `## ${label}
2395
- `;
2396
- for (const item of items) {
2397
- output += `- [${item.title}]: ${item.content}
2398
- `;
2399
- }
2400
- output += `
2401
- `;
2402
- }
2403
- if (output.length === 0) {
2404
- return null;
2405
- }
2406
- if (output.length > 2000) {
2407
- output = `${output.slice(0, 1997)}...`;
2408
- }
2409
- return output.trimEnd();
2410
- } catch {
2411
- return null;
2412
- }
2413
- }
2414
- roleToText(role) {
2415
- if (!role) {
2416
- return null;
2417
- }
2418
- switch (role) {
2419
- case import_shared2.AssigneeRole.BACKEND:
2420
- return "Backend Engineer";
2421
- case import_shared2.AssigneeRole.FRONTEND:
2422
- return "Frontend Engineer";
2423
- case import_shared2.AssigneeRole.PM:
2424
- return "Product Manager";
2425
- case import_shared2.AssigneeRole.QA:
2426
- return "QA Engineer";
2427
- case import_shared2.AssigneeRole.DESIGN:
2428
- return "Product Designer";
2429
- default:
2430
- return "engineer";
2431
- }
2432
- }
2433
- }
2434
- var import_node_fs4, import_node_path6, import_shared2;
2435
- var init_prompt_builder = __esm(() => {
2436
- init_discussion_manager();
2437
- init_config();
2438
- import_node_fs4 = require("node:fs");
2439
- import_node_path6 = require("node:path");
2440
- import_shared2 = require("@locusai/shared");
2441
- });
2442
-
2443
- // src/agent/task-executor.ts
2444
- class TaskExecutor {
2445
- deps;
2446
- promptBuilder;
2447
- constructor(deps) {
2448
- this.deps = deps;
2449
- this.promptBuilder = new PromptBuilder(deps.projectPath);
2450
- }
2451
- async execute(task) {
2452
- this.deps.log(`Executing: ${task.title}`, "info");
2453
- const basePrompt = await this.promptBuilder.build(task);
2454
- try {
2455
- const output = await this.deps.aiRunner.run(basePrompt);
2456
- const summary = this.extractSummary(output);
2457
- return { success: true, summary };
2458
- } catch (error) {
2459
- return { success: false, summary: `Error: ${error}` };
2460
- }
2461
- }
2462
- extractSummary(output) {
2463
- if (!output || !output.trim()) {
2464
- return "Task completed by the agent";
2465
- }
2466
- const paragraphs = output.split(/\n\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
2467
- if (paragraphs.length === 0) {
2468
- return "Task completed by the agent";
2469
- }
2470
- const last = paragraphs[paragraphs.length - 1];
2471
- if (last.length > 500) {
2472
- return `${last.slice(0, 497)}...`;
2473
- }
2474
- return last;
2475
- }
2476
- }
2477
- var init_task_executor = __esm(() => {
2478
- init_prompt_builder();
2479
- });
2480
-
2481
- // src/agent/worker-cli.ts
2482
- var exports_worker_cli = {};
2483
- __export(exports_worker_cli, {
2484
- parseWorkerArgs: () => parseWorkerArgs
2485
- });
2486
- function resolveProvider(value) {
2487
- if (!value || value.startsWith("--")) {
2488
- console.warn("Warning: --provider requires a value. Falling back to 'claude'.");
2489
- return PROVIDER.CLAUDE;
2490
- }
2491
- if (value === PROVIDER.CLAUDE || value === PROVIDER.CODEX)
2492
- return value;
2493
- console.warn(`Warning: invalid --provider value '${value}'. Falling back to 'claude'.`);
2494
- return PROVIDER.CLAUDE;
2495
- }
2496
- function parseWorkerArgs(argv) {
2497
- const args = argv.slice(2);
2498
- const config = {};
2499
- for (let i = 0;i < args.length; i++) {
2500
- const arg = args[i];
2501
- if (arg === "--agent-id")
2502
- config.agentId = args[++i];
2503
- else if (arg === "--workspace-id")
2504
- config.workspaceId = args[++i];
2505
- else if (arg === "--sprint-id")
2506
- config.sprintId = args[++i];
2507
- else if (arg === "--api-url")
2508
- config.apiBase = args[++i];
2509
- else if (arg === "--api-key")
2510
- config.apiKey = args[++i];
2511
- else if (arg === "--project-path")
2512
- config.projectPath = args[++i];
2513
- else if (arg === "--model")
2514
- config.model = args[++i];
2515
- else if (arg === "--provider") {
2516
- const value = args[i + 1];
2517
- if (value && !value.startsWith("--"))
2518
- i++;
2519
- config.provider = resolveProvider(value);
2520
- } else if (arg === "--reasoning-effort")
2521
- config.reasoningEffort = args[++i];
2522
- }
2523
- if (!config.agentId || !config.workspaceId || !config.apiBase || !config.apiKey || !config.projectPath) {
2524
- console.error("Missing required arguments");
2525
- process.exit(1);
2526
- }
2527
- return config;
2528
- }
2529
- var entrypoint;
2530
- var init_worker_cli = __esm(() => {
2531
- init_config();
2532
- init_worker();
2533
- entrypoint = process.argv[1]?.split(/[\\/]/).pop();
2534
- if (entrypoint === "worker-cli.js" || entrypoint === "worker-cli.ts") {
2535
- process.title = "locus-worker";
2536
- const config = parseWorkerArgs(process.argv);
2537
- const worker = new AgentWorker(config);
2538
- worker.run().catch((err) => {
2539
- console.error("Fatal worker error:", err);
2540
- process.exit(1);
2541
- });
2542
- }
2543
- });
2544
-
2545
- // src/agent/worker.ts
2546
- var exports_worker = {};
2547
- __export(exports_worker, {
2548
- AgentWorker: () => AgentWorker
2549
- });
2550
- module.exports = __toCommonJS(exports_worker);
2551
-
2552
- class AgentWorker {
2553
- config;
2554
- client;
2555
- aiRunner;
2556
- taskExecutor;
2557
- gitWorkflow;
2558
- maxTasks = 50;
2559
- tasksCompleted = 0;
2560
- heartbeatInterval = null;
2561
- currentTaskId = null;
2562
- completedTaskList = [];
2563
- taskSummaries = [];
2564
- log;
2565
- constructor(config) {
2566
- this.config = config;
2567
- const projectPath = config.projectPath || process.cwd();
2568
- this.log = createWorkerLogger(config.agentId);
2569
- this.client = new LocusClient({
2570
- baseUrl: config.apiBase,
2571
- token: config.apiKey,
2572
- retryOptions: {
2573
- maxRetries: 3,
2574
- initialDelay: 1000,
2575
- maxDelay: 5000,
2576
- factor: 2
2577
- }
2578
- });
2579
- if (!isGitAvailable()) {
2580
- this.log("git is not installed — branch management will not work", "error");
2581
- }
2582
- if (!isGhAvailable(projectPath)) {
2583
- 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");
2584
- }
2585
- const provider = config.provider ?? PROVIDER.CLAUDE;
2586
- this.aiRunner = createAiRunner(provider, {
2587
- projectPath,
2588
- model: config.model,
2589
- log: this.log,
2590
- reasoningEffort: config.reasoningEffort
2591
- });
2592
- this.taskExecutor = new TaskExecutor({
2593
- aiRunner: this.aiRunner,
2594
- projectPath,
2595
- log: this.log
2596
- });
2597
- this.gitWorkflow = new GitWorkflow(config, this.log);
2598
- const providerLabel = provider === "codex" ? "Codex" : "Claude";
2599
- this.log(`Using ${providerLabel} CLI for all phases`, "info");
2600
- }
2601
- async getActiveSprint() {
2602
- try {
2603
- if (this.config.sprintId) {
2604
- return await this.client.sprints.getById(this.config.sprintId, this.config.workspaceId);
2605
- }
2606
- return await this.client.sprints.getActive(this.config.workspaceId);
2607
- } catch (_error) {
2608
- return null;
2609
- }
2610
- }
2611
- async getNextTask() {
2612
- const maxRetries = 10;
2613
- for (let attempt = 1;attempt <= maxRetries; attempt++) {
2614
- try {
2615
- return await this.client.workspaces.dispatch(this.config.workspaceId, this.config.agentId, this.config.sprintId);
2616
- } catch (error) {
2617
- const isAxiosError = error != null && typeof error === "object" && "response" in error && typeof error.response?.status === "number";
2618
- const status = isAxiosError ? error.response.status : 0;
2619
- if (status === 404) {
2620
- this.log("No tasks available in the backlog.", "info");
2621
- return null;
2622
- }
2623
- const msg = error instanceof Error ? error.message : String(error);
2624
- if (attempt < maxRetries) {
2625
- this.log(`Nothing dispatched (attempt ${attempt}/${maxRetries}): ${msg}. Retrying in 30s...`, "warn");
2626
- await new Promise((r) => setTimeout(r, 30000));
2627
- } else {
2628
- this.log(`Nothing dispatched after ${maxRetries} attempts: ${msg}`, "warn");
2629
- return null;
2630
- }
2631
- }
2632
- }
2633
- return null;
2634
- }
2635
- async executeTask(task) {
2636
- const fullTask = await this.client.tasks.getById(task.id, this.config.workspaceId);
2637
- try {
2638
- const result = await this.taskExecutor.execute(fullTask);
2639
- let noChanges = false;
2640
- let taskBranch = null;
2641
- if (result.success) {
2642
- const commitResult = this.gitWorkflow.commitAndPush(fullTask);
2643
- taskBranch = commitResult.branch;
2644
- noChanges = Boolean(commitResult.noChanges);
2645
- }
2646
- return {
2647
- ...result,
2648
- branch: taskBranch ?? undefined,
2649
- noChanges: noChanges || undefined
2650
- };
2651
- } catch (err) {
2652
- const msg = err instanceof Error ? err.message : String(err);
2653
- return {
2654
- success: false,
2655
- summary: `Execution error: ${msg}`
2656
- };
2657
- }
2658
- }
2659
- startHeartbeat() {
2660
- this.sendHeartbeat();
2661
- this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), 60000);
2662
- }
2663
- stopHeartbeat() {
2664
- if (this.heartbeatInterval) {
2665
- clearInterval(this.heartbeatInterval);
2666
- this.heartbeatInterval = null;
2667
- }
2668
- }
2669
- sendHeartbeat() {
2670
- this.client.workspaces.heartbeat(this.config.workspaceId, this.config.agentId, this.currentTaskId, this.currentTaskId ? "WORKING" : "IDLE").catch((err) => {
2671
- this.log(`Heartbeat failed: ${err instanceof Error ? err.message : String(err)}`, "warn");
2672
- });
2673
- }
2674
- async run() {
2675
- this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
2676
- const handleShutdown = () => {
2677
- this.log("Received shutdown signal. Aborting...", "warn");
2678
- this.aiRunner.abort();
2679
- this.stopHeartbeat();
2680
- this.gitWorkflow.checkoutBaseBranch();
2681
- process.exit(1);
2682
- };
2683
- process.on("SIGTERM", handleShutdown);
2684
- process.on("SIGINT", handleShutdown);
2685
- this.startHeartbeat();
2686
- const sprint = await this.getActiveSprint();
2687
- if (sprint) {
2688
- this.log(`Active sprint found: ${sprint.name}`, "info");
2689
- } else {
2690
- this.log("No active sprint found.", "warn");
2691
- }
2692
- const branchName = this.gitWorkflow.createBranch(this.config.sprintId);
2693
- this.log(`Working on branch: ${branchName}`, "info");
2694
- while (this.tasksCompleted < this.maxTasks) {
2695
- const task = await this.getNextTask();
2696
- if (!task) {
2697
- this.log("No more tasks to process.", "info");
2698
- break;
2699
- }
2700
- this.log(`Claimed: ${task.title}`, "success");
2701
- this.currentTaskId = task.id;
2702
- this.sendHeartbeat();
2703
- const result = await this.executeTask(task);
2704
- if (result.success) {
2705
- if (result.noChanges) {
2706
- this.log(`Blocked: ${task.title} - execution produced no file changes`, "warn");
2707
- await this.client.tasks.update(task.id, this.config.workspaceId, {
2708
- status: import_shared3.TaskStatus.BLOCKED,
2709
- assignedTo: null
2710
- });
2711
- await this.client.tasks.addComment(task.id, this.config.workspaceId, {
2712
- author: "system",
2713
- text: `⚠️ Agent execution finished with no file changes, so no commit was created.
2714
-
2715
- ${result.summary}`
2716
- });
2717
- } else {
2718
- this.log(`Completed: ${task.title}`, "success");
2719
- const updatePayload = {
2720
- status: import_shared3.TaskStatus.IN_REVIEW
2721
- };
2722
- await this.client.tasks.update(task.id, this.config.workspaceId, updatePayload);
2723
- const branchInfo = result.branch ? `
2724
-
2725
- Branch: \`${result.branch}\`` : "";
2726
- await this.client.tasks.addComment(task.id, this.config.workspaceId, {
2727
- author: "system",
2728
- text: `✅ ${result.summary}${branchInfo}`
2729
- });
2730
- this.tasksCompleted++;
2731
- this.completedTaskList.push({ title: task.title, id: task.id });
2732
- this.taskSummaries.push(result.summary);
2733
- }
2734
- } else {
2735
- this.log(`Failed: ${task.title} - ${result.summary}`, "error");
2736
- await this.client.tasks.update(task.id, this.config.workspaceId, {
2737
- status: import_shared3.TaskStatus.BACKLOG,
2738
- assignedTo: null
2739
- });
2740
- await this.client.tasks.addComment(task.id, this.config.workspaceId, {
2741
- author: "system",
2742
- text: `❌ ${result.summary}`
2743
- });
2744
- }
2745
- this.currentTaskId = null;
2746
- this.sendHeartbeat();
2747
- }
2748
- if (this.completedTaskList.length > 0) {
2749
- this.log("All tasks done. Creating pull request...", "info");
2750
- const prResult = this.gitWorkflow.createPullRequest(this.completedTaskList, this.taskSummaries);
2751
- if (prResult.url) {
2752
- for (const task of this.completedTaskList) {
2753
- try {
2754
- await this.client.tasks.update(task.id, this.config.workspaceId, {
2755
- prUrl: prResult.url
2756
- });
2757
- } catch {}
2758
- }
2759
- } else if (prResult.error) {
2760
- this.log(`PR creation failed: ${prResult.error}`, "error");
2761
- }
2762
- }
2763
- this.gitWorkflow.checkoutBaseBranch();
2764
- this.currentTaskId = null;
2765
- this.stopHeartbeat();
2766
- this.client.workspaces.heartbeat(this.config.workspaceId, this.config.agentId, null, "COMPLETED").catch(() => {});
2767
- process.exit(0);
2768
- }
2769
- }
2770
- var import_shared3, workerEntrypoint;
2771
- var init_worker = __esm(() => {
2772
- init_factory();
2773
- init_config();
2774
- init_git_utils();
2775
- init_src();
2776
- init_git_workflow();
2777
- init_task_executor();
2778
- import_shared3 = require("@locusai/shared");
2779
- workerEntrypoint = process.argv[1]?.split(/[\\/]/).pop();
2780
- if (workerEntrypoint === "worker.js" || workerEntrypoint === "worker.ts") {
2781
- process.title = "locus-worker";
2782
- Promise.resolve().then(() => (init_worker_cli(), exports_worker_cli)).then(({ parseWorkerArgs: parseWorkerArgs2 }) => {
2783
- const config = parseWorkerArgs2(process.argv);
2784
- const worker = new AgentWorker(config);
2785
- worker.run().catch((err) => {
2786
- console.error("Fatal worker error:", err);
2787
- process.exit(1);
2788
- });
2789
- });
2790
- }
2791
- });
2792
- init_worker();