@locusai/cli 0.12.1 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agent/worker.js +302 -12
- package/bin/locus.js +1336 -326
- package/package.json +2 -2
package/bin/agent/worker.js
CHANGED
|
@@ -15014,7 +15014,8 @@ var init_session = __esm(() => {
|
|
|
15014
15014
|
model: exports_external.string().optional(),
|
|
15015
15015
|
createdAt: exports_external.number(),
|
|
15016
15016
|
updatedAt: exports_external.number(),
|
|
15017
|
-
title: exports_external.string().optional()
|
|
15017
|
+
title: exports_external.string().optional(),
|
|
15018
|
+
prompt: exports_external.string().optional()
|
|
15018
15019
|
});
|
|
15019
15020
|
SessionSummarySchema = exports_external.object({
|
|
15020
15021
|
sessionId: exports_external.string(),
|
|
@@ -15276,7 +15277,8 @@ var init_config = __esm(() => {
|
|
|
15276
15277
|
documentsDir: "documents",
|
|
15277
15278
|
sessionsDir: "sessions",
|
|
15278
15279
|
reviewsDir: "reviews",
|
|
15279
|
-
plansDir: "plans"
|
|
15280
|
+
plansDir: "plans",
|
|
15281
|
+
discussionsDir: "discussions"
|
|
15280
15282
|
};
|
|
15281
15283
|
});
|
|
15282
15284
|
|
|
@@ -31650,6 +31652,39 @@ var init_workspaces = __esm(() => {
|
|
|
31650
31652
|
};
|
|
31651
31653
|
});
|
|
31652
31654
|
|
|
31655
|
+
// ../sdk/src/discussion/discussion-types.ts
|
|
31656
|
+
var DiscussionMessageSchema, DiscussionInsightSchema, DiscussionSchema;
|
|
31657
|
+
var init_discussion_types = __esm(() => {
|
|
31658
|
+
init_zod();
|
|
31659
|
+
DiscussionMessageSchema = exports_external.object({
|
|
31660
|
+
role: exports_external.enum(["user", "assistant"]),
|
|
31661
|
+
content: exports_external.string(),
|
|
31662
|
+
timestamp: exports_external.number()
|
|
31663
|
+
});
|
|
31664
|
+
DiscussionInsightSchema = exports_external.object({
|
|
31665
|
+
id: exports_external.string(),
|
|
31666
|
+
type: exports_external.enum(["decision", "requirement", "idea", "concern", "learning"]),
|
|
31667
|
+
title: exports_external.string(),
|
|
31668
|
+
content: exports_external.string(),
|
|
31669
|
+
tags: exports_external.array(exports_external.string()).default([]),
|
|
31670
|
+
createdAt: exports_external.string()
|
|
31671
|
+
});
|
|
31672
|
+
DiscussionSchema = exports_external.object({
|
|
31673
|
+
id: exports_external.string(),
|
|
31674
|
+
title: exports_external.string(),
|
|
31675
|
+
topic: exports_external.string(),
|
|
31676
|
+
status: exports_external.enum(["active", "completed", "archived"]).default("active"),
|
|
31677
|
+
messages: exports_external.array(DiscussionMessageSchema).default([]),
|
|
31678
|
+
insights: exports_external.array(DiscussionInsightSchema).default([]),
|
|
31679
|
+
createdAt: exports_external.string(),
|
|
31680
|
+
updatedAt: exports_external.string(),
|
|
31681
|
+
metadata: exports_external.object({
|
|
31682
|
+
model: exports_external.string(),
|
|
31683
|
+
provider: exports_external.string()
|
|
31684
|
+
})
|
|
31685
|
+
});
|
|
31686
|
+
});
|
|
31687
|
+
|
|
31653
31688
|
// ../sdk/src/index.ts
|
|
31654
31689
|
class LocusClient {
|
|
31655
31690
|
api;
|
|
@@ -31751,6 +31786,7 @@ var init_src2 = __esm(() => {
|
|
|
31751
31786
|
init_sprints();
|
|
31752
31787
|
init_tasks();
|
|
31753
31788
|
init_workspaces();
|
|
31789
|
+
init_discussion_types();
|
|
31754
31790
|
init_events();
|
|
31755
31791
|
init_auth2();
|
|
31756
31792
|
init_ci2();
|
|
@@ -32007,10 +32043,198 @@ var init_git_workflow = __esm(() => {
|
|
|
32007
32043
|
init_git_utils();
|
|
32008
32044
|
});
|
|
32009
32045
|
|
|
32010
|
-
// ../sdk/src/
|
|
32011
|
-
import {
|
|
32046
|
+
// ../sdk/src/discussion/discussion-manager.ts
|
|
32047
|
+
import {
|
|
32048
|
+
existsSync as existsSync3,
|
|
32049
|
+
mkdirSync,
|
|
32050
|
+
readdirSync as readdirSync2,
|
|
32051
|
+
readFileSync as readFileSync3,
|
|
32052
|
+
unlinkSync as unlinkSync2,
|
|
32053
|
+
writeFileSync
|
|
32054
|
+
} from "node:fs";
|
|
32012
32055
|
import { join as join4 } from "node:path";
|
|
32013
32056
|
|
|
32057
|
+
class DiscussionManager {
|
|
32058
|
+
discussionsDir;
|
|
32059
|
+
constructor(projectPath) {
|
|
32060
|
+
this.discussionsDir = getLocusPath(projectPath, "discussionsDir");
|
|
32061
|
+
}
|
|
32062
|
+
create(topic, model, provider) {
|
|
32063
|
+
this.ensureDir();
|
|
32064
|
+
const now = new Date().toISOString();
|
|
32065
|
+
const id = `disc-${Date.now()}`;
|
|
32066
|
+
const discussion = {
|
|
32067
|
+
id,
|
|
32068
|
+
title: topic,
|
|
32069
|
+
topic,
|
|
32070
|
+
status: "active",
|
|
32071
|
+
messages: [],
|
|
32072
|
+
insights: [],
|
|
32073
|
+
createdAt: now,
|
|
32074
|
+
updatedAt: now,
|
|
32075
|
+
metadata: { model, provider }
|
|
32076
|
+
};
|
|
32077
|
+
this.save(discussion);
|
|
32078
|
+
return discussion;
|
|
32079
|
+
}
|
|
32080
|
+
save(discussion) {
|
|
32081
|
+
this.ensureDir();
|
|
32082
|
+
const jsonPath = join4(this.discussionsDir, `${discussion.id}.json`);
|
|
32083
|
+
const mdPath = join4(this.discussionsDir, `summary-${discussion.id}.md`);
|
|
32084
|
+
writeFileSync(jsonPath, JSON.stringify(discussion, null, 2), "utf-8");
|
|
32085
|
+
writeFileSync(mdPath, this.toMarkdown(discussion), "utf-8");
|
|
32086
|
+
}
|
|
32087
|
+
load(id) {
|
|
32088
|
+
this.ensureDir();
|
|
32089
|
+
const filePath = join4(this.discussionsDir, `${id}.json`);
|
|
32090
|
+
if (!existsSync3(filePath)) {
|
|
32091
|
+
return null;
|
|
32092
|
+
}
|
|
32093
|
+
try {
|
|
32094
|
+
return JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
32095
|
+
} catch {
|
|
32096
|
+
return null;
|
|
32097
|
+
}
|
|
32098
|
+
}
|
|
32099
|
+
list(status) {
|
|
32100
|
+
this.ensureDir();
|
|
32101
|
+
const files = readdirSync2(this.discussionsDir).filter((f) => f.endsWith(".json"));
|
|
32102
|
+
const discussions = [];
|
|
32103
|
+
for (const file2 of files) {
|
|
32104
|
+
try {
|
|
32105
|
+
const discussion = JSON.parse(readFileSync3(join4(this.discussionsDir, file2), "utf-8"));
|
|
32106
|
+
if (!status || discussion.status === status) {
|
|
32107
|
+
discussions.push(discussion);
|
|
32108
|
+
}
|
|
32109
|
+
} catch {}
|
|
32110
|
+
}
|
|
32111
|
+
discussions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
32112
|
+
return discussions;
|
|
32113
|
+
}
|
|
32114
|
+
complete(id) {
|
|
32115
|
+
const discussion = this.load(id);
|
|
32116
|
+
if (!discussion) {
|
|
32117
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
32118
|
+
}
|
|
32119
|
+
discussion.status = "completed";
|
|
32120
|
+
discussion.updatedAt = new Date().toISOString();
|
|
32121
|
+
this.save(discussion);
|
|
32122
|
+
return discussion;
|
|
32123
|
+
}
|
|
32124
|
+
archive(id) {
|
|
32125
|
+
const discussion = this.load(id);
|
|
32126
|
+
if (!discussion) {
|
|
32127
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
32128
|
+
}
|
|
32129
|
+
discussion.status = "archived";
|
|
32130
|
+
discussion.updatedAt = new Date().toISOString();
|
|
32131
|
+
this.save(discussion);
|
|
32132
|
+
}
|
|
32133
|
+
delete(id) {
|
|
32134
|
+
this.ensureDir();
|
|
32135
|
+
const jsonPath = join4(this.discussionsDir, `${id}.json`);
|
|
32136
|
+
const mdPath = join4(this.discussionsDir, `summary-${id}.md`);
|
|
32137
|
+
if (existsSync3(jsonPath)) {
|
|
32138
|
+
unlinkSync2(jsonPath);
|
|
32139
|
+
}
|
|
32140
|
+
if (existsSync3(mdPath)) {
|
|
32141
|
+
unlinkSync2(mdPath);
|
|
32142
|
+
}
|
|
32143
|
+
}
|
|
32144
|
+
addMessage(id, role, content) {
|
|
32145
|
+
const discussion = this.load(id);
|
|
32146
|
+
if (!discussion) {
|
|
32147
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
32148
|
+
}
|
|
32149
|
+
discussion.messages.push({
|
|
32150
|
+
role,
|
|
32151
|
+
content,
|
|
32152
|
+
timestamp: Date.now()
|
|
32153
|
+
});
|
|
32154
|
+
discussion.updatedAt = new Date().toISOString();
|
|
32155
|
+
this.save(discussion);
|
|
32156
|
+
return discussion;
|
|
32157
|
+
}
|
|
32158
|
+
addInsight(id, insight) {
|
|
32159
|
+
const discussion = this.load(id);
|
|
32160
|
+
if (!discussion) {
|
|
32161
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
32162
|
+
}
|
|
32163
|
+
discussion.insights.push(insight);
|
|
32164
|
+
discussion.updatedAt = new Date().toISOString();
|
|
32165
|
+
this.save(discussion);
|
|
32166
|
+
return discussion;
|
|
32167
|
+
}
|
|
32168
|
+
getAllInsights() {
|
|
32169
|
+
const discussions = this.list("completed");
|
|
32170
|
+
const insights = [];
|
|
32171
|
+
for (const discussion of discussions) {
|
|
32172
|
+
insights.push(...discussion.insights);
|
|
32173
|
+
}
|
|
32174
|
+
insights.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
32175
|
+
return insights;
|
|
32176
|
+
}
|
|
32177
|
+
getMarkdown(id) {
|
|
32178
|
+
const discussion = this.load(id);
|
|
32179
|
+
if (!discussion)
|
|
32180
|
+
return null;
|
|
32181
|
+
return this.toMarkdown(discussion);
|
|
32182
|
+
}
|
|
32183
|
+
toMarkdown(discussion) {
|
|
32184
|
+
const lines = [];
|
|
32185
|
+
lines.push(`# Discussion: ${discussion.title}`);
|
|
32186
|
+
lines.push("");
|
|
32187
|
+
lines.push(`**Status:** ${discussion.status.toUpperCase()}`);
|
|
32188
|
+
lines.push(`**Topic:** ${discussion.topic}`);
|
|
32189
|
+
lines.push(`**Created:** ${discussion.createdAt}`);
|
|
32190
|
+
lines.push(`**Updated:** ${discussion.updatedAt}`);
|
|
32191
|
+
lines.push(`**Model:** ${discussion.metadata.model} (${discussion.metadata.provider})`);
|
|
32192
|
+
lines.push("");
|
|
32193
|
+
if (discussion.messages.length > 0) {
|
|
32194
|
+
lines.push(`## Messages (${discussion.messages.length})`);
|
|
32195
|
+
lines.push("");
|
|
32196
|
+
for (const msg of discussion.messages) {
|
|
32197
|
+
const time3 = new Date(msg.timestamp).toISOString();
|
|
32198
|
+
const roleLabel = msg.role === "user" ? "User" : "Assistant";
|
|
32199
|
+
lines.push(`### ${roleLabel} — ${time3}`);
|
|
32200
|
+
lines.push("");
|
|
32201
|
+
lines.push(msg.content);
|
|
32202
|
+
lines.push("");
|
|
32203
|
+
}
|
|
32204
|
+
}
|
|
32205
|
+
if (discussion.insights.length > 0) {
|
|
32206
|
+
lines.push(`## Insights (${discussion.insights.length})`);
|
|
32207
|
+
lines.push("");
|
|
32208
|
+
for (const insight of discussion.insights) {
|
|
32209
|
+
lines.push(`### [${insight.type.toUpperCase()}] ${insight.title}`);
|
|
32210
|
+
lines.push("");
|
|
32211
|
+
lines.push(insight.content);
|
|
32212
|
+
if (insight.tags.length > 0) {
|
|
32213
|
+
lines.push("");
|
|
32214
|
+
lines.push(`**Tags:** ${insight.tags.join(", ")}`);
|
|
32215
|
+
}
|
|
32216
|
+
lines.push("");
|
|
32217
|
+
}
|
|
32218
|
+
}
|
|
32219
|
+
lines.push("---");
|
|
32220
|
+
lines.push(`*Discussion ID: ${discussion.id}*`);
|
|
32221
|
+
return lines.join(`
|
|
32222
|
+
`);
|
|
32223
|
+
}
|
|
32224
|
+
ensureDir() {
|
|
32225
|
+
if (!existsSync3(this.discussionsDir)) {
|
|
32226
|
+
mkdirSync(this.discussionsDir, { recursive: true });
|
|
32227
|
+
}
|
|
32228
|
+
}
|
|
32229
|
+
}
|
|
32230
|
+
var init_discussion_manager = __esm(() => {
|
|
32231
|
+
init_config();
|
|
32232
|
+
});
|
|
32233
|
+
|
|
32234
|
+
// ../sdk/src/core/prompt-builder.ts
|
|
32235
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
32236
|
+
import { join as join5 } from "node:path";
|
|
32237
|
+
|
|
32014
32238
|
class PromptBuilder {
|
|
32015
32239
|
projectPath;
|
|
32016
32240
|
constructor(projectPath) {
|
|
@@ -32048,6 +32272,15 @@ ${knowledgeBase}
|
|
|
32048
32272
|
These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
|
|
32049
32273
|
${learnings}
|
|
32050
32274
|
</learnings>
|
|
32275
|
+
`;
|
|
32276
|
+
}
|
|
32277
|
+
const discussionInsights = this.getDiscussionInsightsContent();
|
|
32278
|
+
if (discussionInsights) {
|
|
32279
|
+
sections += `
|
|
32280
|
+
<discussion_insights>
|
|
32281
|
+
These are key decisions and insights from product discussions. Follow them to maintain product coherence:
|
|
32282
|
+
${discussionInsights}
|
|
32283
|
+
</discussion_insights>
|
|
32051
32284
|
`;
|
|
32052
32285
|
}
|
|
32053
32286
|
if (task2.docs && task2.docs.length > 0) {
|
|
@@ -32136,6 +32369,15 @@ ${knowledgeBase}
|
|
|
32136
32369
|
These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
|
|
32137
32370
|
${learnings}
|
|
32138
32371
|
</learnings>
|
|
32372
|
+
`;
|
|
32373
|
+
}
|
|
32374
|
+
const discussionInsights = this.getDiscussionInsightsContent();
|
|
32375
|
+
if (discussionInsights) {
|
|
32376
|
+
sections += `
|
|
32377
|
+
<discussion_insights>
|
|
32378
|
+
These are key decisions and insights from product discussions. Follow them to maintain product coherence:
|
|
32379
|
+
${discussionInsights}
|
|
32380
|
+
</discussion_insights>
|
|
32139
32381
|
`;
|
|
32140
32382
|
}
|
|
32141
32383
|
return `<direct_execution>
|
|
@@ -32150,9 +32392,9 @@ ${sections}
|
|
|
32150
32392
|
}
|
|
32151
32393
|
getProjectContext() {
|
|
32152
32394
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
32153
|
-
if (
|
|
32395
|
+
if (existsSync4(contextPath)) {
|
|
32154
32396
|
try {
|
|
32155
|
-
const context2 =
|
|
32397
|
+
const context2 = readFileSync4(contextPath, "utf-8");
|
|
32156
32398
|
if (context2.trim().length > 20) {
|
|
32157
32399
|
return context2;
|
|
32158
32400
|
}
|
|
@@ -32163,10 +32405,10 @@ ${sections}
|
|
|
32163
32405
|
return this.getFallbackContext() || null;
|
|
32164
32406
|
}
|
|
32165
32407
|
getFallbackContext() {
|
|
32166
|
-
const readmePath =
|
|
32167
|
-
if (
|
|
32408
|
+
const readmePath = join5(this.projectPath, "README.md");
|
|
32409
|
+
if (existsSync4(readmePath)) {
|
|
32168
32410
|
try {
|
|
32169
|
-
const content =
|
|
32411
|
+
const content = readFileSync4(readmePath, "utf-8");
|
|
32170
32412
|
const limit = 1000;
|
|
32171
32413
|
return content.slice(0, limit) + (content.length > limit ? `
|
|
32172
32414
|
...(truncated)...` : "");
|
|
@@ -32180,15 +32422,16 @@ ${sections}
|
|
|
32180
32422
|
return `You have access to the following documentation directories for context:
|
|
32181
32423
|
- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
32182
32424
|
- Documents: \`.locus/documents\` (synced from cloud)
|
|
32425
|
+
- Discussions: \`.locus/discussions\` (product discussion insights and decisions)
|
|
32183
32426
|
If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
|
|
32184
32427
|
}
|
|
32185
32428
|
getLearningsContent() {
|
|
32186
32429
|
const learningsPath = getLocusPath(this.projectPath, "learningsFile");
|
|
32187
|
-
if (!
|
|
32430
|
+
if (!existsSync4(learningsPath)) {
|
|
32188
32431
|
return null;
|
|
32189
32432
|
}
|
|
32190
32433
|
try {
|
|
32191
|
-
const content =
|
|
32434
|
+
const content = readFileSync4(learningsPath, "utf-8");
|
|
32192
32435
|
const lines = content.split(`
|
|
32193
32436
|
`).filter((l) => l.startsWith("- "));
|
|
32194
32437
|
if (lines.length === 0) {
|
|
@@ -32200,6 +32443,53 @@ If you need more information about the project strategies, plans, or architectur
|
|
|
32200
32443
|
return null;
|
|
32201
32444
|
}
|
|
32202
32445
|
}
|
|
32446
|
+
getDiscussionInsightsContent() {
|
|
32447
|
+
try {
|
|
32448
|
+
const manager = new DiscussionManager(this.projectPath);
|
|
32449
|
+
const insights = manager.getAllInsights();
|
|
32450
|
+
if (insights.length === 0) {
|
|
32451
|
+
return null;
|
|
32452
|
+
}
|
|
32453
|
+
const groups = {};
|
|
32454
|
+
for (const insight of insights) {
|
|
32455
|
+
const key = insight.type;
|
|
32456
|
+
if (!groups[key]) {
|
|
32457
|
+
groups[key] = [];
|
|
32458
|
+
}
|
|
32459
|
+
groups[key].push(insight);
|
|
32460
|
+
}
|
|
32461
|
+
const typeLabels = {
|
|
32462
|
+
decision: "Decisions",
|
|
32463
|
+
requirement: "Requirements",
|
|
32464
|
+
idea: "Ideas",
|
|
32465
|
+
concern: "Concerns",
|
|
32466
|
+
learning: "Learnings"
|
|
32467
|
+
};
|
|
32468
|
+
let output = "";
|
|
32469
|
+
for (const [type, label] of Object.entries(typeLabels)) {
|
|
32470
|
+
const items = groups[type];
|
|
32471
|
+
if (!items || items.length === 0)
|
|
32472
|
+
continue;
|
|
32473
|
+
output += `## ${label}
|
|
32474
|
+
`;
|
|
32475
|
+
for (const item of items) {
|
|
32476
|
+
output += `- [${item.title}]: ${item.content}
|
|
32477
|
+
`;
|
|
32478
|
+
}
|
|
32479
|
+
output += `
|
|
32480
|
+
`;
|
|
32481
|
+
}
|
|
32482
|
+
if (output.length === 0) {
|
|
32483
|
+
return null;
|
|
32484
|
+
}
|
|
32485
|
+
if (output.length > 2000) {
|
|
32486
|
+
output = `${output.slice(0, 1997)}...`;
|
|
32487
|
+
}
|
|
32488
|
+
return output.trimEnd();
|
|
32489
|
+
} catch {
|
|
32490
|
+
return null;
|
|
32491
|
+
}
|
|
32492
|
+
}
|
|
32203
32493
|
roleToText(role) {
|
|
32204
32494
|
if (!role) {
|
|
32205
32495
|
return null;
|
|
@@ -32222,6 +32512,7 @@ If you need more information about the project strategies, plans, or architectur
|
|
|
32222
32512
|
}
|
|
32223
32513
|
var init_prompt_builder = __esm(() => {
|
|
32224
32514
|
init_src();
|
|
32515
|
+
init_discussion_manager();
|
|
32225
32516
|
init_config();
|
|
32226
32517
|
});
|
|
32227
32518
|
|
|
@@ -32538,7 +32829,6 @@ Branch: \`${result.branch}\`` : "";
|
|
|
32538
32829
|
this.log("All tasks done. Creating pull request...", "info");
|
|
32539
32830
|
const prResult = this.gitWorkflow.createPullRequest(this.completedTaskList, this.taskSummaries);
|
|
32540
32831
|
if (prResult.url) {
|
|
32541
|
-
this.log(`PR created: ${prResult.url}`, "success");
|
|
32542
32832
|
for (const task2 of this.completedTaskList) {
|
|
32543
32833
|
try {
|
|
32544
32834
|
await this.client.tasks.update(task2.id, this.config.workspaceId, {
|