@locusai/cli 0.13.0 → 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.
Files changed (3) hide show
  1. package/bin/agent/worker.js +302 -12
  2. package/bin/locus.js +1336 -326
  3. package/package.json +2 -2
@@ -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/core/prompt-builder.ts
32011
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
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 (existsSync3(contextPath)) {
32395
+ if (existsSync4(contextPath)) {
32154
32396
  try {
32155
- const context2 = readFileSync3(contextPath, "utf-8");
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 = join4(this.projectPath, "README.md");
32167
- if (existsSync3(readmePath)) {
32408
+ const readmePath = join5(this.projectPath, "README.md");
32409
+ if (existsSync4(readmePath)) {
32168
32410
  try {
32169
- const content = readFileSync3(readmePath, "utf-8");
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 (!existsSync3(learningsPath)) {
32430
+ if (!existsSync4(learningsPath)) {
32188
32431
  return null;
32189
32432
  }
32190
32433
  try {
32191
- const content = readFileSync3(learningsPath, "utf-8");
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, {