@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.
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
package/bin/locus.js CHANGED
@@ -6393,7 +6393,8 @@ var init_config = __esm(() => {
6393
6393
  documentsDir: "documents",
6394
6394
  sessionsDir: "sessions",
6395
6395
  reviewsDir: "reviews",
6396
- plansDir: "plans"
6396
+ plansDir: "plans",
6397
+ discussionsDir: "discussions"
6397
6398
  };
6398
6399
  LOCUS_GITIGNORE_PATTERNS = [
6399
6400
  "# Locus AI - Session data (user-specific, can grow large)",
@@ -6408,6 +6409,9 @@ var init_config = __esm(() => {
6408
6409
  "# Locus AI - Plans (generated per task)",
6409
6410
  ".locus/plans/",
6410
6411
  "",
6412
+ "# Locus AI - Discussions (AI discussion sessions)",
6413
+ ".locus/discussions/",
6414
+ "",
6411
6415
  "# Locus AI - Settings (contains API key, telegram config, etc.)",
6412
6416
  ".locus/settings.json",
6413
6417
  "",
@@ -38244,7 +38248,8 @@ var init_session = __esm(() => {
38244
38248
  model: exports_external.string().optional(),
38245
38249
  createdAt: exports_external.number(),
38246
38250
  updatedAt: exports_external.number(),
38247
- title: exports_external.string().optional()
38251
+ title: exports_external.string().optional(),
38252
+ prompt: exports_external.string().optional()
38248
38253
  });
38249
38254
  SessionSummarySchema = exports_external.object({
38250
38255
  sessionId: exports_external.string(),
@@ -38611,6 +38616,39 @@ var init_workspaces = __esm(() => {
38611
38616
  };
38612
38617
  });
38613
38618
 
38619
+ // ../sdk/src/discussion/discussion-types.ts
38620
+ var DiscussionMessageSchema, DiscussionInsightSchema, DiscussionSchema;
38621
+ var init_discussion_types = __esm(() => {
38622
+ init_zod();
38623
+ DiscussionMessageSchema = exports_external.object({
38624
+ role: exports_external.enum(["user", "assistant"]),
38625
+ content: exports_external.string(),
38626
+ timestamp: exports_external.number()
38627
+ });
38628
+ DiscussionInsightSchema = exports_external.object({
38629
+ id: exports_external.string(),
38630
+ type: exports_external.enum(["decision", "requirement", "idea", "concern", "learning"]),
38631
+ title: exports_external.string(),
38632
+ content: exports_external.string(),
38633
+ tags: exports_external.array(exports_external.string()).default([]),
38634
+ createdAt: exports_external.string()
38635
+ });
38636
+ DiscussionSchema = exports_external.object({
38637
+ id: exports_external.string(),
38638
+ title: exports_external.string(),
38639
+ topic: exports_external.string(),
38640
+ status: exports_external.enum(["active", "completed", "archived"]).default("active"),
38641
+ messages: exports_external.array(DiscussionMessageSchema).default([]),
38642
+ insights: exports_external.array(DiscussionInsightSchema).default([]),
38643
+ createdAt: exports_external.string(),
38644
+ updatedAt: exports_external.string(),
38645
+ metadata: exports_external.object({
38646
+ model: exports_external.string(),
38647
+ provider: exports_external.string()
38648
+ })
38649
+ });
38650
+ });
38651
+
38614
38652
  // ../sdk/src/index.ts
38615
38653
  class LocusClient {
38616
38654
  api;
@@ -38712,6 +38750,7 @@ var init_src2 = __esm(() => {
38712
38750
  init_sprints();
38713
38751
  init_tasks();
38714
38752
  init_workspaces();
38753
+ init_discussion_types();
38715
38754
  init_events();
38716
38755
  init_auth();
38717
38756
  init_ci();
@@ -38936,10 +38975,198 @@ var init_reviewer_worker = __esm(() => {
38936
38975
  }
38937
38976
  });
38938
38977
 
38939
- // ../sdk/src/core/prompt-builder.ts
38940
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
38978
+ // ../sdk/src/discussion/discussion-manager.ts
38979
+ import {
38980
+ existsSync as existsSync5,
38981
+ mkdirSync as mkdirSync3,
38982
+ readdirSync as readdirSync2,
38983
+ readFileSync as readFileSync5,
38984
+ unlinkSync as unlinkSync2,
38985
+ writeFileSync as writeFileSync3
38986
+ } from "node:fs";
38941
38987
  import { join as join6 } from "node:path";
38942
38988
 
38989
+ class DiscussionManager {
38990
+ discussionsDir;
38991
+ constructor(projectPath) {
38992
+ this.discussionsDir = getLocusPath(projectPath, "discussionsDir");
38993
+ }
38994
+ create(topic, model, provider) {
38995
+ this.ensureDir();
38996
+ const now = new Date().toISOString();
38997
+ const id = `disc-${Date.now()}`;
38998
+ const discussion = {
38999
+ id,
39000
+ title: topic,
39001
+ topic,
39002
+ status: "active",
39003
+ messages: [],
39004
+ insights: [],
39005
+ createdAt: now,
39006
+ updatedAt: now,
39007
+ metadata: { model, provider }
39008
+ };
39009
+ this.save(discussion);
39010
+ return discussion;
39011
+ }
39012
+ save(discussion) {
39013
+ this.ensureDir();
39014
+ const jsonPath = join6(this.discussionsDir, `${discussion.id}.json`);
39015
+ const mdPath = join6(this.discussionsDir, `summary-${discussion.id}.md`);
39016
+ writeFileSync3(jsonPath, JSON.stringify(discussion, null, 2), "utf-8");
39017
+ writeFileSync3(mdPath, this.toMarkdown(discussion), "utf-8");
39018
+ }
39019
+ load(id) {
39020
+ this.ensureDir();
39021
+ const filePath = join6(this.discussionsDir, `${id}.json`);
39022
+ if (!existsSync5(filePath)) {
39023
+ return null;
39024
+ }
39025
+ try {
39026
+ return JSON.parse(readFileSync5(filePath, "utf-8"));
39027
+ } catch {
39028
+ return null;
39029
+ }
39030
+ }
39031
+ list(status) {
39032
+ this.ensureDir();
39033
+ const files = readdirSync2(this.discussionsDir).filter((f) => f.endsWith(".json"));
39034
+ const discussions = [];
39035
+ for (const file2 of files) {
39036
+ try {
39037
+ const discussion = JSON.parse(readFileSync5(join6(this.discussionsDir, file2), "utf-8"));
39038
+ if (!status || discussion.status === status) {
39039
+ discussions.push(discussion);
39040
+ }
39041
+ } catch {}
39042
+ }
39043
+ discussions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
39044
+ return discussions;
39045
+ }
39046
+ complete(id) {
39047
+ const discussion = this.load(id);
39048
+ if (!discussion) {
39049
+ throw new Error(`Discussion not found: ${id}`);
39050
+ }
39051
+ discussion.status = "completed";
39052
+ discussion.updatedAt = new Date().toISOString();
39053
+ this.save(discussion);
39054
+ return discussion;
39055
+ }
39056
+ archive(id) {
39057
+ const discussion = this.load(id);
39058
+ if (!discussion) {
39059
+ throw new Error(`Discussion not found: ${id}`);
39060
+ }
39061
+ discussion.status = "archived";
39062
+ discussion.updatedAt = new Date().toISOString();
39063
+ this.save(discussion);
39064
+ }
39065
+ delete(id) {
39066
+ this.ensureDir();
39067
+ const jsonPath = join6(this.discussionsDir, `${id}.json`);
39068
+ const mdPath = join6(this.discussionsDir, `summary-${id}.md`);
39069
+ if (existsSync5(jsonPath)) {
39070
+ unlinkSync2(jsonPath);
39071
+ }
39072
+ if (existsSync5(mdPath)) {
39073
+ unlinkSync2(mdPath);
39074
+ }
39075
+ }
39076
+ addMessage(id, role, content) {
39077
+ const discussion = this.load(id);
39078
+ if (!discussion) {
39079
+ throw new Error(`Discussion not found: ${id}`);
39080
+ }
39081
+ discussion.messages.push({
39082
+ role,
39083
+ content,
39084
+ timestamp: Date.now()
39085
+ });
39086
+ discussion.updatedAt = new Date().toISOString();
39087
+ this.save(discussion);
39088
+ return discussion;
39089
+ }
39090
+ addInsight(id, insight) {
39091
+ const discussion = this.load(id);
39092
+ if (!discussion) {
39093
+ throw new Error(`Discussion not found: ${id}`);
39094
+ }
39095
+ discussion.insights.push(insight);
39096
+ discussion.updatedAt = new Date().toISOString();
39097
+ this.save(discussion);
39098
+ return discussion;
39099
+ }
39100
+ getAllInsights() {
39101
+ const discussions = this.list("completed");
39102
+ const insights = [];
39103
+ for (const discussion of discussions) {
39104
+ insights.push(...discussion.insights);
39105
+ }
39106
+ insights.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
39107
+ return insights;
39108
+ }
39109
+ getMarkdown(id) {
39110
+ const discussion = this.load(id);
39111
+ if (!discussion)
39112
+ return null;
39113
+ return this.toMarkdown(discussion);
39114
+ }
39115
+ toMarkdown(discussion) {
39116
+ const lines = [];
39117
+ lines.push(`# Discussion: ${discussion.title}`);
39118
+ lines.push("");
39119
+ lines.push(`**Status:** ${discussion.status.toUpperCase()}`);
39120
+ lines.push(`**Topic:** ${discussion.topic}`);
39121
+ lines.push(`**Created:** ${discussion.createdAt}`);
39122
+ lines.push(`**Updated:** ${discussion.updatedAt}`);
39123
+ lines.push(`**Model:** ${discussion.metadata.model} (${discussion.metadata.provider})`);
39124
+ lines.push("");
39125
+ if (discussion.messages.length > 0) {
39126
+ lines.push(`## Messages (${discussion.messages.length})`);
39127
+ lines.push("");
39128
+ for (const msg of discussion.messages) {
39129
+ const time3 = new Date(msg.timestamp).toISOString();
39130
+ const roleLabel = msg.role === "user" ? "User" : "Assistant";
39131
+ lines.push(`### ${roleLabel} — ${time3}`);
39132
+ lines.push("");
39133
+ lines.push(msg.content);
39134
+ lines.push("");
39135
+ }
39136
+ }
39137
+ if (discussion.insights.length > 0) {
39138
+ lines.push(`## Insights (${discussion.insights.length})`);
39139
+ lines.push("");
39140
+ for (const insight of discussion.insights) {
39141
+ lines.push(`### [${insight.type.toUpperCase()}] ${insight.title}`);
39142
+ lines.push("");
39143
+ lines.push(insight.content);
39144
+ if (insight.tags.length > 0) {
39145
+ lines.push("");
39146
+ lines.push(`**Tags:** ${insight.tags.join(", ")}`);
39147
+ }
39148
+ lines.push("");
39149
+ }
39150
+ }
39151
+ lines.push("---");
39152
+ lines.push(`*Discussion ID: ${discussion.id}*`);
39153
+ return lines.join(`
39154
+ `);
39155
+ }
39156
+ ensureDir() {
39157
+ if (!existsSync5(this.discussionsDir)) {
39158
+ mkdirSync3(this.discussionsDir, { recursive: true });
39159
+ }
39160
+ }
39161
+ }
39162
+ var init_discussion_manager = __esm(() => {
39163
+ init_config();
39164
+ });
39165
+
39166
+ // ../sdk/src/core/prompt-builder.ts
39167
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
39168
+ import { join as join7 } from "node:path";
39169
+
38943
39170
  class PromptBuilder {
38944
39171
  projectPath;
38945
39172
  constructor(projectPath) {
@@ -38977,6 +39204,15 @@ ${knowledgeBase}
38977
39204
  These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
38978
39205
  ${learnings}
38979
39206
  </learnings>
39207
+ `;
39208
+ }
39209
+ const discussionInsights = this.getDiscussionInsightsContent();
39210
+ if (discussionInsights) {
39211
+ sections += `
39212
+ <discussion_insights>
39213
+ These are key decisions and insights from product discussions. Follow them to maintain product coherence:
39214
+ ${discussionInsights}
39215
+ </discussion_insights>
38980
39216
  `;
38981
39217
  }
38982
39218
  if (task2.docs && task2.docs.length > 0) {
@@ -39065,6 +39301,15 @@ ${knowledgeBase}
39065
39301
  These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
39066
39302
  ${learnings}
39067
39303
  </learnings>
39304
+ `;
39305
+ }
39306
+ const discussionInsights = this.getDiscussionInsightsContent();
39307
+ if (discussionInsights) {
39308
+ sections += `
39309
+ <discussion_insights>
39310
+ These are key decisions and insights from product discussions. Follow them to maintain product coherence:
39311
+ ${discussionInsights}
39312
+ </discussion_insights>
39068
39313
  `;
39069
39314
  }
39070
39315
  return `<direct_execution>
@@ -39079,9 +39324,9 @@ ${sections}
39079
39324
  }
39080
39325
  getProjectContext() {
39081
39326
  const contextPath = getLocusPath(this.projectPath, "contextFile");
39082
- if (existsSync5(contextPath)) {
39327
+ if (existsSync6(contextPath)) {
39083
39328
  try {
39084
- const context2 = readFileSync5(contextPath, "utf-8");
39329
+ const context2 = readFileSync6(contextPath, "utf-8");
39085
39330
  if (context2.trim().length > 20) {
39086
39331
  return context2;
39087
39332
  }
@@ -39092,10 +39337,10 @@ ${sections}
39092
39337
  return this.getFallbackContext() || null;
39093
39338
  }
39094
39339
  getFallbackContext() {
39095
- const readmePath = join6(this.projectPath, "README.md");
39096
- if (existsSync5(readmePath)) {
39340
+ const readmePath = join7(this.projectPath, "README.md");
39341
+ if (existsSync6(readmePath)) {
39097
39342
  try {
39098
- const content = readFileSync5(readmePath, "utf-8");
39343
+ const content = readFileSync6(readmePath, "utf-8");
39099
39344
  const limit = 1000;
39100
39345
  return content.slice(0, limit) + (content.length > limit ? `
39101
39346
  ...(truncated)...` : "");
@@ -39109,15 +39354,16 @@ ${sections}
39109
39354
  return `You have access to the following documentation directories for context:
39110
39355
  - Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
39111
39356
  - Documents: \`.locus/documents\` (synced from cloud)
39357
+ - Discussions: \`.locus/discussions\` (product discussion insights and decisions)
39112
39358
  If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
39113
39359
  }
39114
39360
  getLearningsContent() {
39115
39361
  const learningsPath = getLocusPath(this.projectPath, "learningsFile");
39116
- if (!existsSync5(learningsPath)) {
39362
+ if (!existsSync6(learningsPath)) {
39117
39363
  return null;
39118
39364
  }
39119
39365
  try {
39120
- const content = readFileSync5(learningsPath, "utf-8");
39366
+ const content = readFileSync6(learningsPath, "utf-8");
39121
39367
  const lines = content.split(`
39122
39368
  `).filter((l) => l.startsWith("- "));
39123
39369
  if (lines.length === 0) {
@@ -39129,6 +39375,53 @@ If you need more information about the project strategies, plans, or architectur
39129
39375
  return null;
39130
39376
  }
39131
39377
  }
39378
+ getDiscussionInsightsContent() {
39379
+ try {
39380
+ const manager = new DiscussionManager(this.projectPath);
39381
+ const insights = manager.getAllInsights();
39382
+ if (insights.length === 0) {
39383
+ return null;
39384
+ }
39385
+ const groups = {};
39386
+ for (const insight of insights) {
39387
+ const key = insight.type;
39388
+ if (!groups[key]) {
39389
+ groups[key] = [];
39390
+ }
39391
+ groups[key].push(insight);
39392
+ }
39393
+ const typeLabels = {
39394
+ decision: "Decisions",
39395
+ requirement: "Requirements",
39396
+ idea: "Ideas",
39397
+ concern: "Concerns",
39398
+ learning: "Learnings"
39399
+ };
39400
+ let output = "";
39401
+ for (const [type, label] of Object.entries(typeLabels)) {
39402
+ const items = groups[type];
39403
+ if (!items || items.length === 0)
39404
+ continue;
39405
+ output += `## ${label}
39406
+ `;
39407
+ for (const item of items) {
39408
+ output += `- [${item.title}]: ${item.content}
39409
+ `;
39410
+ }
39411
+ output += `
39412
+ `;
39413
+ }
39414
+ if (output.length === 0) {
39415
+ return null;
39416
+ }
39417
+ if (output.length > 2000) {
39418
+ output = `${output.slice(0, 1997)}...`;
39419
+ }
39420
+ return output.trimEnd();
39421
+ } catch {
39422
+ return null;
39423
+ }
39424
+ }
39132
39425
  roleToText(role) {
39133
39426
  if (!role) {
39134
39427
  return null;
@@ -39151,6 +39444,7 @@ If you need more information about the project strategies, plans, or architectur
39151
39444
  }
39152
39445
  var init_prompt_builder = __esm(() => {
39153
39446
  init_src();
39447
+ init_discussion_manager();
39154
39448
  init_config();
39155
39449
  });
39156
39450
 
@@ -39467,7 +39761,6 @@ Branch: \`${result.branch}\`` : "";
39467
39761
  this.log("All tasks done. Creating pull request...", "info");
39468
39762
  const prResult = this.gitWorkflow.createPullRequest(this.completedTaskList, this.taskSummaries);
39469
39763
  if (prResult.url) {
39470
- this.log(`PR created: ${prResult.url}`, "success");
39471
39764
  for (const task2 of this.completedTaskList) {
39472
39765
  try {
39473
39766
  await this.client.tasks.update(task2.id, this.config.workspaceId, {
@@ -39535,6 +39828,349 @@ var init_core3 = __esm(() => {
39535
39828
  init_prompt_builder();
39536
39829
  });
39537
39830
 
39831
+ // ../sdk/src/discussion/agents/facilitator-prompt.ts
39832
+ function buildFacilitatorPrompt(input) {
39833
+ const {
39834
+ topic,
39835
+ projectContext,
39836
+ learnings,
39837
+ knowledgeBase,
39838
+ previousMessages,
39839
+ insights,
39840
+ isFirstMessage
39841
+ } = input;
39842
+ let sections = "";
39843
+ if (projectContext) {
39844
+ sections += `
39845
+ <project_context>
39846
+ ${projectContext}
39847
+ </project_context>
39848
+ `;
39849
+ }
39850
+ sections += `
39851
+ <knowledge_base>
39852
+ ${knowledgeBase}
39853
+ </knowledge_base>
39854
+ `;
39855
+ if (learnings) {
39856
+ sections += `
39857
+ <learnings>
39858
+ These are accumulated lessons from past work on this project. Use them to ask more informed questions:
39859
+ ${learnings}
39860
+ </learnings>
39861
+ `;
39862
+ }
39863
+ if (previousMessages.length > 0) {
39864
+ let history = "";
39865
+ for (const msg of previousMessages) {
39866
+ const role = msg.role === "user" ? "User" : "Facilitator";
39867
+ history += `[${role}]: ${msg.content}
39868
+
39869
+ `;
39870
+ }
39871
+ sections += `
39872
+ <conversation_history>
39873
+ ${history.trimEnd()}
39874
+ </conversation_history>
39875
+ `;
39876
+ }
39877
+ if (insights.length > 0) {
39878
+ let insightsText = "";
39879
+ for (const insight of insights) {
39880
+ insightsText += `- [${insight.type.toUpperCase()}] ${insight.title}: ${insight.content}
39881
+ `;
39882
+ }
39883
+ sections += `
39884
+ <extracted_insights>
39885
+ Insights identified so far in this discussion:
39886
+ ${insightsText.trimEnd()}
39887
+ </extracted_insights>
39888
+ `;
39889
+ }
39890
+ const firstMessageInstruction = isFirstMessage ? `This is the START of the discussion. Introduce yourself briefly, then ask your first probing question about the topic. Do NOT extract any insights yet — there is no user input to extract from.` : `Continue the discussion by responding to the user's latest message. Build on their answers to go deeper. After responding, extract any insights from their message.`;
39891
+ return `<discussion_facilitator>
39892
+ You are a product strategy facilitator leading a structured discussion.
39893
+
39894
+ <topic>
39895
+ ${topic}
39896
+ </topic>
39897
+ ${sections}
39898
+ <role>
39899
+ You are an expert product strategy facilitator. Your job is to:
39900
+ 1. Ask probing, specific questions about the topic — never generic or surface-level
39901
+ 2. Build on previous answers to progressively deepen the conversation
39902
+ 3. Identify and extract key decisions, requirements, ideas, concerns, and learnings
39903
+ 4. Reference existing project context and learnings to ask informed questions
39904
+ 5. When the topic feels fully explored, suggest wrapping up with a summary
39905
+ </role>
39906
+
39907
+ <rules>
39908
+ - ${firstMessageInstruction}
39909
+ - Ask ONE focused question at a time. Do not overwhelm with multiple questions.
39910
+ - Be conversational but purposeful — every question should drive toward actionable insights.
39911
+ - When you identify an insight from the user's response, include it as a structured XML block in your response.
39912
+ - Insight blocks use this format within your response text:
39913
+
39914
+ <insight>
39915
+ {"type": "decision|requirement|idea|concern|learning", "title": "short title", "content": "detailed description", "tags": ["relevant", "tags"]}
39916
+ </insight>
39917
+
39918
+ - You may include multiple <insight> blocks if the user's response contains several distinct insights.
39919
+ - The insight blocks will be parsed and removed from the displayed response, so write your conversational text as if they are not there.
39920
+ - Types explained:
39921
+ - **decision**: A choice or direction that has been made or agreed upon
39922
+ - **requirement**: A specific need, constraint, or must-have
39923
+ - **idea**: A suggestion, proposal, or possibility to explore
39924
+ - **concern**: A risk, worry, or potential problem identified
39925
+ - **learning**: A realization, lesson, or important context discovered
39926
+ - Keep responses concise. Aim for 2-4 sentences of conversation plus any insight blocks.
39927
+ - If the user's responses indicate the topic is well-explored, suggest summarizing and wrapping up.
39928
+ </rules>
39929
+ </discussion_facilitator>`;
39930
+ }
39931
+ function buildSummaryPrompt(topic, messages, insights) {
39932
+ let history = "";
39933
+ for (const msg of messages) {
39934
+ const role = msg.role === "user" ? "User" : "Facilitator";
39935
+ history += `[${role}]: ${msg.content}
39936
+
39937
+ `;
39938
+ }
39939
+ let insightsText = "";
39940
+ if (insights.length > 0) {
39941
+ for (const insight of insights) {
39942
+ insightsText += `- [${insight.type.toUpperCase()}] **${insight.title}**: ${insight.content}`;
39943
+ if (insight.tags.length > 0) {
39944
+ insightsText += ` (tags: ${insight.tags.join(", ")})`;
39945
+ }
39946
+ insightsText += `
39947
+ `;
39948
+ }
39949
+ }
39950
+ return `<discussion_summary>
39951
+ Create a final summary of this product discussion.
39952
+
39953
+ <topic>
39954
+ ${topic}
39955
+ </topic>
39956
+
39957
+ <conversation>
39958
+ ${history.trimEnd()}
39959
+ </conversation>
39960
+
39961
+ ${insightsText ? `<insights>
39962
+ ${insightsText.trimEnd()}
39963
+ </insights>
39964
+ ` : ""}
39965
+ <rules>
39966
+ - Write a clear, structured summary of the entire discussion.
39967
+ - Organize by: Key Decisions, Requirements, Ideas to Explore, Concerns & Risks, and Learnings.
39968
+ - Only include sections that have relevant content — skip empty categories.
39969
+ - For each item, provide a brief but actionable description.
39970
+ - End with a "Next Steps" section listing concrete action items that emerged.
39971
+ - Be concise — this summary should be scannable and useful as a reference.
39972
+ - Do NOT include any <insight> XML blocks in the summary.
39973
+ </rules>
39974
+ </discussion_summary>`;
39975
+ }
39976
+
39977
+ // ../sdk/src/discussion/discussion-facilitator.ts
39978
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
39979
+
39980
+ class DiscussionFacilitator {
39981
+ projectPath;
39982
+ aiRunner;
39983
+ discussionManager;
39984
+ log;
39985
+ provider;
39986
+ model;
39987
+ constructor(config2) {
39988
+ this.projectPath = config2.projectPath;
39989
+ this.aiRunner = config2.aiRunner;
39990
+ this.discussionManager = config2.discussionManager;
39991
+ this.log = config2.log ?? ((_msg) => {
39992
+ return;
39993
+ });
39994
+ this.provider = config2.provider;
39995
+ this.model = config2.model;
39996
+ }
39997
+ async startDiscussion(topic) {
39998
+ this.log("Starting new discussion...", "info");
39999
+ const discussion = this.discussionManager.create(topic, this.model, this.provider);
40000
+ const { projectContext, learnings, knowledgeBase } = this.buildContext();
40001
+ const prompt = buildFacilitatorPrompt({
40002
+ topic,
40003
+ projectContext,
40004
+ learnings,
40005
+ knowledgeBase,
40006
+ previousMessages: [],
40007
+ insights: [],
40008
+ isFirstMessage: true
40009
+ });
40010
+ const response = await this.aiRunner.run(prompt);
40011
+ const { cleanResponse } = this.parseInsights(response);
40012
+ this.discussionManager.addMessage(discussion.id, "assistant", cleanResponse);
40013
+ this.log("Discussion started", "success");
40014
+ const saved = this.discussionManager.load(discussion.id);
40015
+ if (!saved) {
40016
+ throw new Error(`Failed to load discussion after creation: ${discussion.id}`);
40017
+ }
40018
+ return {
40019
+ discussion: saved,
40020
+ message: cleanResponse
40021
+ };
40022
+ }
40023
+ async continueDiscussion(discussionId, userMessage) {
40024
+ const discussion = this.discussionManager.load(discussionId);
40025
+ if (!discussion) {
40026
+ throw new Error(`Discussion not found: ${discussionId}`);
40027
+ }
40028
+ const updated = this.discussionManager.addMessage(discussionId, "user", userMessage);
40029
+ const { projectContext, learnings, knowledgeBase } = this.buildContext();
40030
+ const prompt = buildFacilitatorPrompt({
40031
+ topic: updated.topic,
40032
+ projectContext,
40033
+ learnings,
40034
+ knowledgeBase,
40035
+ previousMessages: updated.messages,
40036
+ insights: updated.insights,
40037
+ isFirstMessage: false
40038
+ });
40039
+ const response = await this.aiRunner.run(prompt);
40040
+ const { cleanResponse, insights } = this.parseInsights(response);
40041
+ for (const insight of insights) {
40042
+ this.discussionManager.addInsight(discussionId, insight);
40043
+ }
40044
+ this.discussionManager.addMessage(discussionId, "assistant", cleanResponse);
40045
+ return { response: cleanResponse, insights };
40046
+ }
40047
+ async* continueDiscussionStream(discussionId, userMessage) {
40048
+ const discussion = this.discussionManager.load(discussionId);
40049
+ if (!discussion) {
40050
+ throw new Error(`Discussion not found: ${discussionId}`);
40051
+ }
40052
+ const updated = this.discussionManager.addMessage(discussionId, "user", userMessage);
40053
+ const { projectContext, learnings, knowledgeBase } = this.buildContext();
40054
+ const prompt = buildFacilitatorPrompt({
40055
+ topic: updated.topic,
40056
+ projectContext,
40057
+ learnings,
40058
+ knowledgeBase,
40059
+ previousMessages: updated.messages,
40060
+ insights: updated.insights,
40061
+ isFirstMessage: false
40062
+ });
40063
+ let fullResponse = "";
40064
+ const stream4 = this.aiRunner.runStream(prompt);
40065
+ for await (const chunk of stream4) {
40066
+ yield chunk;
40067
+ if (chunk.type === "text_delta") {
40068
+ fullResponse += chunk.content;
40069
+ } else if (chunk.type === "result") {
40070
+ fullResponse = chunk.content;
40071
+ }
40072
+ }
40073
+ const { cleanResponse, insights } = this.parseInsights(fullResponse);
40074
+ for (const insight of insights) {
40075
+ this.discussionManager.addInsight(discussionId, insight);
40076
+ }
40077
+ this.discussionManager.addMessage(discussionId, "assistant", cleanResponse);
40078
+ return { response: cleanResponse, insights };
40079
+ }
40080
+ async summarizeDiscussion(discussionId) {
40081
+ const discussion = this.discussionManager.load(discussionId);
40082
+ if (!discussion) {
40083
+ throw new Error(`Discussion not found: ${discussionId}`);
40084
+ }
40085
+ this.log("Generating discussion summary...", "info");
40086
+ const prompt = buildSummaryPrompt(discussion.topic, discussion.messages, discussion.insights);
40087
+ const summary = await this.aiRunner.run(prompt);
40088
+ this.discussionManager.addMessage(discussionId, "assistant", summary);
40089
+ this.discussionManager.complete(discussionId);
40090
+ this.log("Discussion summarized and completed", "success");
40091
+ return summary;
40092
+ }
40093
+ parseInsights(response) {
40094
+ const insights = [];
40095
+ const insightRegex = /<insight>\s*([\s\S]*?)\s*<\/insight>/g;
40096
+ let match = insightRegex.exec(response);
40097
+ while (match !== null) {
40098
+ try {
40099
+ const parsed = JSON.parse(match[1]);
40100
+ const id = `ins-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
40101
+ insights.push({
40102
+ id,
40103
+ type: parsed.type,
40104
+ title: parsed.title,
40105
+ content: parsed.content,
40106
+ tags: parsed.tags ?? [],
40107
+ createdAt: new Date().toISOString()
40108
+ });
40109
+ } catch {}
40110
+ match = insightRegex.exec(response);
40111
+ }
40112
+ const cleanResponse = response.replace(/<insight>\s*[\s\S]*?\s*<\/insight>/g, "").replace(/\n{3,}/g, `
40113
+
40114
+ `).trim();
40115
+ return { cleanResponse, insights };
40116
+ }
40117
+ buildContext() {
40118
+ return {
40119
+ projectContext: this.getProjectContext(),
40120
+ learnings: this.getLearningsContent(),
40121
+ knowledgeBase: this.getKnowledgeBaseSection()
40122
+ };
40123
+ }
40124
+ getProjectContext() {
40125
+ const contextPath = getLocusPath(this.projectPath, "contextFile");
40126
+ if (existsSync7(contextPath)) {
40127
+ try {
40128
+ const context2 = readFileSync7(contextPath, "utf-8");
40129
+ if (context2.trim().length > 20) {
40130
+ return context2;
40131
+ }
40132
+ } catch {
40133
+ return null;
40134
+ }
40135
+ }
40136
+ return null;
40137
+ }
40138
+ getLearningsContent() {
40139
+ const learningsPath = getLocusPath(this.projectPath, "learningsFile");
40140
+ if (!existsSync7(learningsPath)) {
40141
+ return null;
40142
+ }
40143
+ try {
40144
+ const content = readFileSync7(learningsPath, "utf-8");
40145
+ const lines = content.split(`
40146
+ `).filter((l) => l.startsWith("- "));
40147
+ if (lines.length === 0) {
40148
+ return null;
40149
+ }
40150
+ return lines.join(`
40151
+ `);
40152
+ } catch {
40153
+ return null;
40154
+ }
40155
+ }
40156
+ getKnowledgeBaseSection() {
40157
+ return `You have access to the following documentation directories for context:
40158
+ - Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
40159
+ - Documents: \`.locus/documents\` (synced from cloud)
40160
+ If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
40161
+ }
40162
+ }
40163
+ var init_discussion_facilitator = __esm(() => {
40164
+ init_config();
40165
+ });
40166
+
40167
+ // ../sdk/src/discussion/index.ts
40168
+ var init_discussion = __esm(() => {
40169
+ init_discussion_facilitator();
40170
+ init_discussion_manager();
40171
+ init_discussion_types();
40172
+ });
40173
+
39538
40174
  // ../sdk/src/exec/context-tracker.ts
39539
40175
  function generateArtifactId() {
39540
40176
  const timestamp = Date.now().toString(36);
@@ -39942,14 +40578,14 @@ var init_event_emitter = __esm(() => {
39942
40578
 
39943
40579
  // ../sdk/src/exec/history-manager.ts
39944
40580
  import {
39945
- existsSync as existsSync6,
39946
- mkdirSync as mkdirSync3,
39947
- readdirSync as readdirSync2,
39948
- readFileSync as readFileSync6,
40581
+ existsSync as existsSync8,
40582
+ mkdirSync as mkdirSync4,
40583
+ readdirSync as readdirSync3,
40584
+ readFileSync as readFileSync8,
39949
40585
  rmSync,
39950
- writeFileSync as writeFileSync3
40586
+ writeFileSync as writeFileSync4
39951
40587
  } from "node:fs";
39952
- import { join as join7 } from "node:path";
40588
+ import { join as join8 } from "node:path";
39953
40589
  function generateSessionId2() {
39954
40590
  const timestamp = Date.now().toString(36);
39955
40591
  const random = Math.random().toString(36).substring(2, 9);
@@ -39960,30 +40596,30 @@ class HistoryManager {
39960
40596
  historyDir;
39961
40597
  maxSessions;
39962
40598
  constructor(projectPath, options) {
39963
- this.historyDir = options?.historyDir ?? join7(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
40599
+ this.historyDir = options?.historyDir ?? join8(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
39964
40600
  this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
39965
40601
  this.ensureHistoryDir();
39966
40602
  }
39967
40603
  ensureHistoryDir() {
39968
- if (!existsSync6(this.historyDir)) {
39969
- mkdirSync3(this.historyDir, { recursive: true });
40604
+ if (!existsSync8(this.historyDir)) {
40605
+ mkdirSync4(this.historyDir, { recursive: true });
39970
40606
  }
39971
40607
  }
39972
40608
  getSessionPath(sessionId) {
39973
- return join7(this.historyDir, `${sessionId}.json`);
40609
+ return join8(this.historyDir, `${sessionId}.json`);
39974
40610
  }
39975
40611
  saveSession(session2) {
39976
40612
  const filePath = this.getSessionPath(session2.id);
39977
40613
  session2.updatedAt = Date.now();
39978
- writeFileSync3(filePath, JSON.stringify(session2, null, 2), "utf-8");
40614
+ writeFileSync4(filePath, JSON.stringify(session2, null, 2), "utf-8");
39979
40615
  }
39980
40616
  loadSession(sessionId) {
39981
40617
  const filePath = this.getSessionPath(sessionId);
39982
- if (!existsSync6(filePath)) {
40618
+ if (!existsSync8(filePath)) {
39983
40619
  return null;
39984
40620
  }
39985
40621
  try {
39986
- const content = readFileSync6(filePath, "utf-8");
40622
+ const content = readFileSync8(filePath, "utf-8");
39987
40623
  return JSON.parse(content);
39988
40624
  } catch {
39989
40625
  return null;
@@ -39991,7 +40627,7 @@ class HistoryManager {
39991
40627
  }
39992
40628
  deleteSession(sessionId) {
39993
40629
  const filePath = this.getSessionPath(sessionId);
39994
- if (!existsSync6(filePath)) {
40630
+ if (!existsSync8(filePath)) {
39995
40631
  return false;
39996
40632
  }
39997
40633
  try {
@@ -40002,7 +40638,7 @@ class HistoryManager {
40002
40638
  }
40003
40639
  }
40004
40640
  listSessions(options) {
40005
- const files = readdirSync2(this.historyDir);
40641
+ const files = readdirSync3(this.historyDir);
40006
40642
  let sessions = [];
40007
40643
  for (const file2 of files) {
40008
40644
  if (file2.endsWith(".json")) {
@@ -40075,11 +40711,11 @@ class HistoryManager {
40075
40711
  return deleted;
40076
40712
  }
40077
40713
  getSessionCount() {
40078
- const files = readdirSync2(this.historyDir);
40714
+ const files = readdirSync3(this.historyDir);
40079
40715
  return files.filter((f) => f.endsWith(".json")).length;
40080
40716
  }
40081
40717
  sessionExists(sessionId) {
40082
- return existsSync6(this.getSessionPath(sessionId));
40718
+ return existsSync8(this.getSessionPath(sessionId));
40083
40719
  }
40084
40720
  findSessionByPartialId(partialId) {
40085
40721
  const sessions = this.listSessions();
@@ -40093,12 +40729,12 @@ class HistoryManager {
40093
40729
  return this.historyDir;
40094
40730
  }
40095
40731
  clearAllSessions() {
40096
- const files = readdirSync2(this.historyDir);
40732
+ const files = readdirSync3(this.historyDir);
40097
40733
  let deleted = 0;
40098
40734
  for (const file2 of files) {
40099
40735
  if (file2.endsWith(".json")) {
40100
40736
  try {
40101
- rmSync(join7(this.historyDir, file2));
40737
+ rmSync(join8(this.historyDir, file2));
40102
40738
  deleted++;
40103
40739
  } catch {}
40104
40740
  }
@@ -40387,8 +41023,8 @@ var init_git = __esm(() => {
40387
41023
 
40388
41024
  // ../sdk/src/orchestrator/index.ts
40389
41025
  import { spawn as spawn3 } from "node:child_process";
40390
- import { existsSync as existsSync7 } from "node:fs";
40391
- import { dirname as dirname2, join as join8 } from "node:path";
41026
+ import { existsSync as existsSync9 } from "node:fs";
41027
+ import { dirname as dirname2, join as join9 } from "node:path";
40392
41028
  import { fileURLToPath as fileURLToPath2 } from "node:url";
40393
41029
  import { EventEmitter as EventEmitter4 } from "events";
40394
41030
  function killProcessTree(proc) {
@@ -40677,12 +41313,12 @@ ${agentId} finished (exit code: ${code})`);
40677
41313
  const currentModulePath = fileURLToPath2(import.meta.url);
40678
41314
  const currentModuleDir = dirname2(currentModulePath);
40679
41315
  const potentialPaths = [
40680
- join8(currentModuleDir, "..", "agent", "worker.js"),
40681
- join8(currentModuleDir, "agent", "worker.js"),
40682
- join8(currentModuleDir, "worker.js"),
40683
- join8(currentModuleDir, "..", "agent", "worker.ts")
41316
+ join9(currentModuleDir, "..", "agent", "worker.js"),
41317
+ join9(currentModuleDir, "agent", "worker.js"),
41318
+ join9(currentModuleDir, "worker.js"),
41319
+ join9(currentModuleDir, "..", "agent", "worker.ts")
40684
41320
  ];
40685
- return potentialPaths.find((p) => existsSync7(p));
41321
+ return potentialPaths.find((p) => existsSync9(p));
40686
41322
  }
40687
41323
  };
40688
41324
  });
@@ -40794,14 +41430,14 @@ var init_sprint_plan = __esm(() => {
40794
41430
 
40795
41431
  // ../sdk/src/planning/plan-manager.ts
40796
41432
  import {
40797
- existsSync as existsSync8,
40798
- mkdirSync as mkdirSync4,
40799
- readdirSync as readdirSync3,
40800
- readFileSync as readFileSync7,
40801
- unlinkSync as unlinkSync2,
40802
- writeFileSync as writeFileSync4
41433
+ existsSync as existsSync10,
41434
+ mkdirSync as mkdirSync5,
41435
+ readdirSync as readdirSync4,
41436
+ readFileSync as readFileSync9,
41437
+ unlinkSync as unlinkSync3,
41438
+ writeFileSync as writeFileSync5
40803
41439
  } from "node:fs";
40804
- import { join as join9 } from "node:path";
41440
+ import { join as join10 } from "node:path";
40805
41441
 
40806
41442
  class PlanManager {
40807
41443
  plansDir;
@@ -40811,19 +41447,19 @@ class PlanManager {
40811
41447
  save(plan) {
40812
41448
  this.ensurePlansDir();
40813
41449
  const slug = this.slugify(plan.name);
40814
- const jsonPath = join9(this.plansDir, `${slug}.json`);
40815
- const mdPath = join9(this.plansDir, `sprint-${slug}.md`);
40816
- writeFileSync4(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
40817
- writeFileSync4(mdPath, sprintPlanToMarkdown(plan), "utf-8");
41450
+ const jsonPath = join10(this.plansDir, `${slug}.json`);
41451
+ const mdPath = join10(this.plansDir, `sprint-${slug}.md`);
41452
+ writeFileSync5(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
41453
+ writeFileSync5(mdPath, sprintPlanToMarkdown(plan), "utf-8");
40818
41454
  return plan.id;
40819
41455
  }
40820
41456
  load(idOrSlug) {
40821
41457
  this.ensurePlansDir();
40822
- const files = readdirSync3(this.plansDir).filter((f) => f.endsWith(".json"));
41458
+ const files = readdirSync4(this.plansDir).filter((f) => f.endsWith(".json"));
40823
41459
  for (const file2 of files) {
40824
- const filePath = join9(this.plansDir, file2);
41460
+ const filePath = join10(this.plansDir, file2);
40825
41461
  try {
40826
- const plan = JSON.parse(readFileSync7(filePath, "utf-8"));
41462
+ const plan = JSON.parse(readFileSync9(filePath, "utf-8"));
40827
41463
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
40828
41464
  return plan;
40829
41465
  }
@@ -40833,11 +41469,11 @@ class PlanManager {
40833
41469
  }
40834
41470
  list(status) {
40835
41471
  this.ensurePlansDir();
40836
- const files = readdirSync3(this.plansDir).filter((f) => f.endsWith(".json"));
41472
+ const files = readdirSync4(this.plansDir).filter((f) => f.endsWith(".json"));
40837
41473
  const plans = [];
40838
41474
  for (const file2 of files) {
40839
41475
  try {
40840
- const plan = JSON.parse(readFileSync7(join9(this.plansDir, file2), "utf-8"));
41476
+ const plan = JSON.parse(readFileSync9(join10(this.plansDir, file2), "utf-8"));
40841
41477
  if (!status || plan.status === status) {
40842
41478
  plans.push(plan);
40843
41479
  }
@@ -40894,18 +41530,18 @@ class PlanManager {
40894
41530
  }
40895
41531
  delete(idOrSlug) {
40896
41532
  this.ensurePlansDir();
40897
- const files = readdirSync3(this.plansDir);
41533
+ const files = readdirSync4(this.plansDir);
40898
41534
  for (const file2 of files) {
40899
- const filePath = join9(this.plansDir, file2);
41535
+ const filePath = join10(this.plansDir, file2);
40900
41536
  if (!file2.endsWith(".json"))
40901
41537
  continue;
40902
41538
  try {
40903
- const plan = JSON.parse(readFileSync7(filePath, "utf-8"));
41539
+ const plan = JSON.parse(readFileSync9(filePath, "utf-8"));
40904
41540
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
40905
- unlinkSync2(filePath);
40906
- const mdPath = join9(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
40907
- if (existsSync8(mdPath)) {
40908
- unlinkSync2(mdPath);
41541
+ unlinkSync3(filePath);
41542
+ const mdPath = join10(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
41543
+ if (existsSync10(mdPath)) {
41544
+ unlinkSync3(mdPath);
40909
41545
  }
40910
41546
  return;
40911
41547
  }
@@ -40919,8 +41555,8 @@ class PlanManager {
40919
41555
  return sprintPlanToMarkdown(plan);
40920
41556
  }
40921
41557
  ensurePlansDir() {
40922
- if (!existsSync8(this.plansDir)) {
40923
- mkdirSync4(this.plansDir, { recursive: true });
41558
+ if (!existsSync10(this.plansDir)) {
41559
+ mkdirSync5(this.plansDir, { recursive: true });
40924
41560
  }
40925
41561
  }
40926
41562
  slugify(name) {
@@ -41002,8 +41638,8 @@ IMPORTANT:
41002
41638
  }
41003
41639
 
41004
41640
  // ../sdk/src/planning/planning-meeting.ts
41005
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync8 } from "node:fs";
41006
- import { join as join10 } from "node:path";
41641
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync10 } from "node:fs";
41642
+ import { join as join11 } from "node:path";
41007
41643
 
41008
41644
  class PlanningMeeting {
41009
41645
  projectPath;
@@ -41019,8 +41655,8 @@ class PlanningMeeting {
41019
41655
  async run(directive, feedback) {
41020
41656
  this.log("Planning sprint...", "info");
41021
41657
  const plansDir = getLocusPath(this.projectPath, "plansDir");
41022
- if (!existsSync9(plansDir)) {
41023
- mkdirSync5(plansDir, { recursive: true });
41658
+ if (!existsSync11(plansDir)) {
41659
+ mkdirSync6(plansDir, { recursive: true });
41024
41660
  }
41025
41661
  const ts = Date.now();
41026
41662
  const planId = `plan-${ts}`;
@@ -41034,11 +41670,11 @@ class PlanningMeeting {
41034
41670
  });
41035
41671
  const response = await this.aiRunner.run(prompt);
41036
41672
  this.log("Planning meeting complete.", "success");
41037
- const expectedPath = join10(plansDir, `${fileName}.json`);
41673
+ const expectedPath = join11(plansDir, `${fileName}.json`);
41038
41674
  let plan = null;
41039
- if (existsSync9(expectedPath)) {
41675
+ if (existsSync11(expectedPath)) {
41040
41676
  try {
41041
- plan = JSON.parse(readFileSync8(expectedPath, "utf-8"));
41677
+ plan = JSON.parse(readFileSync10(expectedPath, "utf-8"));
41042
41678
  } catch {}
41043
41679
  }
41044
41680
  if (!plan) {
@@ -41073,112 +41709,13 @@ var init_index_node = __esm(() => {
41073
41709
  init_agent2();
41074
41710
  init_ai();
41075
41711
  init_core3();
41712
+ init_discussion();
41076
41713
  init_exec();
41077
41714
  init_git();
41078
41715
  init_src2();
41079
41716
  init_planning();
41080
41717
  });
41081
41718
 
41082
- // src/display/execution-stats.ts
41083
- class ExecutionStatsTracker {
41084
- startTime;
41085
- endTime = null;
41086
- toolTimings = new Map;
41087
- toolOrder = [];
41088
- tokensUsed = null;
41089
- error = null;
41090
- constructor() {
41091
- this.startTime = Date.now();
41092
- }
41093
- toolStarted(toolName, toolId) {
41094
- const key = toolId ?? `${toolName}-${Date.now()}`;
41095
- this.toolTimings.set(key, {
41096
- name: toolName,
41097
- id: toolId,
41098
- startTime: Date.now()
41099
- });
41100
- this.toolOrder.push(key);
41101
- }
41102
- toolCompleted(toolName, toolId) {
41103
- const key = this.findToolKey(toolName, toolId);
41104
- if (key) {
41105
- const timing = this.toolTimings.get(key);
41106
- if (timing) {
41107
- timing.endTime = Date.now();
41108
- timing.duration = timing.endTime - timing.startTime;
41109
- timing.success = true;
41110
- }
41111
- }
41112
- }
41113
- toolFailed(toolName, error48, toolId) {
41114
- const key = this.findToolKey(toolName, toolId);
41115
- if (key) {
41116
- const timing = this.toolTimings.get(key);
41117
- if (timing) {
41118
- timing.endTime = Date.now();
41119
- timing.duration = timing.endTime - timing.startTime;
41120
- timing.success = false;
41121
- timing.error = error48;
41122
- }
41123
- }
41124
- }
41125
- setTokensUsed(tokens) {
41126
- this.tokensUsed = tokens;
41127
- }
41128
- setError(error48) {
41129
- this.error = error48;
41130
- }
41131
- finalize() {
41132
- this.endTime = Date.now();
41133
- const toolsUsed = [];
41134
- const seenTools = new Set;
41135
- for (const key of this.toolOrder) {
41136
- const timing = this.toolTimings.get(key);
41137
- if (timing && !seenTools.has(timing.name)) {
41138
- seenTools.add(timing.name);
41139
- toolsUsed.push(timing.name);
41140
- }
41141
- }
41142
- const toolTimings = this.toolOrder.map((key) => this.toolTimings.get(key)).filter((t) => t !== undefined);
41143
- const stats = {
41144
- duration: this.endTime - this.startTime,
41145
- toolsUsed,
41146
- toolTimings,
41147
- success: this.error === null
41148
- };
41149
- if (this.tokensUsed !== null) {
41150
- stats.tokensUsed = this.tokensUsed;
41151
- }
41152
- if (this.error !== null) {
41153
- stats.error = this.error;
41154
- }
41155
- return stats;
41156
- }
41157
- getCurrentDuration() {
41158
- return Date.now() - this.startTime;
41159
- }
41160
- findToolKey(toolName, toolId) {
41161
- if (toolId && this.toolTimings.has(toolId)) {
41162
- return toolId;
41163
- }
41164
- for (let i = this.toolOrder.length - 1;i >= 0; i--) {
41165
- const key = this.toolOrder[i];
41166
- const timing = this.toolTimings.get(key);
41167
- if (timing && timing.name === toolName && timing.endTime === undefined) {
41168
- return key;
41169
- }
41170
- }
41171
- for (let i = this.toolOrder.length - 1;i >= 0; i--) {
41172
- const key = this.toolOrder[i];
41173
- const timing = this.toolTimings.get(key);
41174
- if (timing && timing.name === toolName) {
41175
- return key;
41176
- }
41177
- }
41178
- return null;
41179
- }
41180
- }
41181
-
41182
41719
  // src/display/tool-display.ts
41183
41720
  import * as path2 from "node:path";
41184
41721
 
@@ -41769,6 +42306,106 @@ var init_progress_renderer = __esm(() => {
41769
42306
  SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
41770
42307
  });
41771
42308
 
42309
+ // src/display/execution-stats.ts
42310
+ class ExecutionStatsTracker {
42311
+ startTime;
42312
+ endTime = null;
42313
+ toolTimings = new Map;
42314
+ toolOrder = [];
42315
+ tokensUsed = null;
42316
+ error = null;
42317
+ constructor() {
42318
+ this.startTime = Date.now();
42319
+ }
42320
+ toolStarted(toolName, toolId) {
42321
+ const key = toolId ?? `${toolName}-${Date.now()}`;
42322
+ this.toolTimings.set(key, {
42323
+ name: toolName,
42324
+ id: toolId,
42325
+ startTime: Date.now()
42326
+ });
42327
+ this.toolOrder.push(key);
42328
+ }
42329
+ toolCompleted(toolName, toolId) {
42330
+ const key = this.findToolKey(toolName, toolId);
42331
+ if (key) {
42332
+ const timing = this.toolTimings.get(key);
42333
+ if (timing) {
42334
+ timing.endTime = Date.now();
42335
+ timing.duration = timing.endTime - timing.startTime;
42336
+ timing.success = true;
42337
+ }
42338
+ }
42339
+ }
42340
+ toolFailed(toolName, error48, toolId) {
42341
+ const key = this.findToolKey(toolName, toolId);
42342
+ if (key) {
42343
+ const timing = this.toolTimings.get(key);
42344
+ if (timing) {
42345
+ timing.endTime = Date.now();
42346
+ timing.duration = timing.endTime - timing.startTime;
42347
+ timing.success = false;
42348
+ timing.error = error48;
42349
+ }
42350
+ }
42351
+ }
42352
+ setTokensUsed(tokens) {
42353
+ this.tokensUsed = tokens;
42354
+ }
42355
+ setError(error48) {
42356
+ this.error = error48;
42357
+ }
42358
+ finalize() {
42359
+ this.endTime = Date.now();
42360
+ const toolsUsed = [];
42361
+ const seenTools = new Set;
42362
+ for (const key of this.toolOrder) {
42363
+ const timing = this.toolTimings.get(key);
42364
+ if (timing && !seenTools.has(timing.name)) {
42365
+ seenTools.add(timing.name);
42366
+ toolsUsed.push(timing.name);
42367
+ }
42368
+ }
42369
+ const toolTimings = this.toolOrder.map((key) => this.toolTimings.get(key)).filter((t) => t !== undefined);
42370
+ const stats = {
42371
+ duration: this.endTime - this.startTime,
42372
+ toolsUsed,
42373
+ toolTimings,
42374
+ success: this.error === null
42375
+ };
42376
+ if (this.tokensUsed !== null) {
42377
+ stats.tokensUsed = this.tokensUsed;
42378
+ }
42379
+ if (this.error !== null) {
42380
+ stats.error = this.error;
42381
+ }
42382
+ return stats;
42383
+ }
42384
+ getCurrentDuration() {
42385
+ return Date.now() - this.startTime;
42386
+ }
42387
+ findToolKey(toolName, toolId) {
42388
+ if (toolId && this.toolTimings.has(toolId)) {
42389
+ return toolId;
42390
+ }
42391
+ for (let i = this.toolOrder.length - 1;i >= 0; i--) {
42392
+ const key = this.toolOrder[i];
42393
+ const timing = this.toolTimings.get(key);
42394
+ if (timing && timing.name === toolName && timing.endTime === undefined) {
42395
+ return key;
42396
+ }
42397
+ }
42398
+ for (let i = this.toolOrder.length - 1;i >= 0; i--) {
42399
+ const key = this.toolOrder[i];
42400
+ const timing = this.toolTimings.get(key);
42401
+ if (timing && timing.name === toolName) {
42402
+ return key;
42403
+ }
42404
+ }
42405
+ return null;
42406
+ }
42407
+ }
42408
+
41772
42409
  // src/repl/commands.ts
41773
42410
  function parseCommand(input) {
41774
42411
  const lowerInput = input.toLowerCase();
@@ -41878,7 +42515,7 @@ var exports_interactive_session = {};
41878
42515
  __export(exports_interactive_session, {
41879
42516
  InteractiveSession: () => InteractiveSession
41880
42517
  });
41881
- import * as readline from "node:readline";
42518
+ import * as readline2 from "node:readline";
41882
42519
 
41883
42520
  class InteractiveSession {
41884
42521
  readline = null;
@@ -41924,7 +42561,7 @@ class InteractiveSession {
41924
42561
  }
41925
42562
  async start() {
41926
42563
  this.printWelcome();
41927
- this.readline = readline.createInterface({
42564
+ this.readline = readline2.createInterface({
41928
42565
  input: process.stdin,
41929
42566
  output: process.stdout,
41930
42567
  terminal: true
@@ -42121,10 +42758,10 @@ import { createInterface } from "node:readline";
42121
42758
 
42122
42759
  // src/settings-manager.ts
42123
42760
  init_index_node();
42124
- import { existsSync as existsSync10, readFileSync as readFileSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
42125
- import { join as join11 } from "node:path";
42761
+ import { existsSync as existsSync12, readFileSync as readFileSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync6 } from "node:fs";
42762
+ import { join as join12 } from "node:path";
42126
42763
  function getSettingsPath(projectPath) {
42127
- return join11(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42764
+ return join12(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42128
42765
  }
42129
42766
 
42130
42767
  class SettingsManager {
@@ -42134,16 +42771,16 @@ class SettingsManager {
42134
42771
  }
42135
42772
  load() {
42136
42773
  const settingsPath = getSettingsPath(this.projectPath);
42137
- if (!existsSync10(settingsPath)) {
42774
+ if (!existsSync12(settingsPath)) {
42138
42775
  return {};
42139
42776
  }
42140
- return JSON.parse(readFileSync9(settingsPath, "utf-8"));
42777
+ return JSON.parse(readFileSync11(settingsPath, "utf-8"));
42141
42778
  }
42142
42779
  save(settings) {
42143
42780
  const { $schema: _2, ...rest } = settings;
42144
42781
  const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
42145
42782
  const settingsPath = getSettingsPath(this.projectPath);
42146
- writeFileSync5(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42783
+ writeFileSync6(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42147
42784
  }
42148
42785
  get(key) {
42149
42786
  return this.load()[key];
@@ -42155,12 +42792,12 @@ class SettingsManager {
42155
42792
  }
42156
42793
  remove() {
42157
42794
  const settingsPath = getSettingsPath(this.projectPath);
42158
- if (existsSync10(settingsPath)) {
42159
- unlinkSync3(settingsPath);
42795
+ if (existsSync12(settingsPath)) {
42796
+ unlinkSync4(settingsPath);
42160
42797
  }
42161
42798
  }
42162
42799
  exists() {
42163
- return existsSync10(getSettingsPath(this.projectPath));
42800
+ return existsSync12(getSettingsPath(this.projectPath));
42164
42801
  }
42165
42802
  }
42166
42803
 
@@ -42419,15 +43056,444 @@ async function configCommand(args) {
42419
43056
  showConfigHelp();
42420
43057
  }
42421
43058
  }
42422
- // src/commands/docs.ts
43059
+ // src/commands/discuss.ts
42423
43060
  init_index_node();
43061
+ init_progress_renderer();
43062
+ import * as readline from "node:readline";
42424
43063
  import { parseArgs } from "node:util";
42425
43064
 
43065
+ // src/utils/banner.ts
43066
+ init_index_node();
43067
+
43068
+ // src/utils/version.ts
43069
+ import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
43070
+ import { dirname as dirname3, join as join13 } from "node:path";
43071
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
43072
+ function getVersion() {
43073
+ try {
43074
+ const __filename2 = fileURLToPath3(import.meta.url);
43075
+ const __dirname2 = dirname3(__filename2);
43076
+ const bundledPath = join13(__dirname2, "..", "package.json");
43077
+ const sourcePath = join13(__dirname2, "..", "..", "package.json");
43078
+ if (existsSync13(bundledPath)) {
43079
+ const pkg = JSON.parse(readFileSync12(bundledPath, "utf-8"));
43080
+ if (pkg.name === "@locusai/cli") {
43081
+ return pkg.version || "0.0.0";
43082
+ }
43083
+ }
43084
+ if (existsSync13(sourcePath)) {
43085
+ const pkg = JSON.parse(readFileSync12(sourcePath, "utf-8"));
43086
+ if (pkg.name === "@locusai/cli") {
43087
+ return pkg.version || "0.0.0";
43088
+ }
43089
+ }
43090
+ } catch {}
43091
+ return "0.0.0";
43092
+ }
43093
+ var VERSION2 = getVersion();
43094
+
43095
+ // src/utils/banner.ts
43096
+ function printBanner() {
43097
+ console.log(c.primary(`
43098
+ _ ____ ____ _ _ ____
43099
+ | | / __ \\ / ___| | | |/ ___|
43100
+ | | | | | | | | | | |\\___ \\
43101
+ | |___| |__| | |___| |_| |___) |
43102
+ |_____|\\____/ \\____|\\___/|____/ ${c.dim(`v${VERSION2}`)}
43103
+ `));
43104
+ }
43105
+ // src/utils/helpers.ts
43106
+ init_index_node();
43107
+ import { existsSync as existsSync14 } from "node:fs";
43108
+ import { join as join14 } from "node:path";
43109
+ function isProjectInitialized(projectPath) {
43110
+ const locusDir = join14(projectPath, LOCUS_CONFIG.dir);
43111
+ const configPath = join14(locusDir, LOCUS_CONFIG.configFile);
43112
+ return existsSync14(locusDir) && existsSync14(configPath);
43113
+ }
43114
+ function requireInitialization(projectPath, command) {
43115
+ if (!isProjectInitialized(projectPath)) {
43116
+ console.error(`
43117
+ ${c.error("✖ Error")} ${c.red(`Locus is not initialized in this directory.`)}
43118
+
43119
+ The '${c.bold(command)}' command requires a Locus project to be initialized.
43120
+
43121
+ To initialize Locus in this directory, run:
43122
+ ${c.primary("locus init")}
43123
+
43124
+ This will create a ${c.dim(".locus")} directory with the necessary configuration.
43125
+ `);
43126
+ process.exit(1);
43127
+ }
43128
+ }
43129
+ function resolveProvider3(input) {
43130
+ if (!input)
43131
+ return PROVIDER.CLAUDE;
43132
+ if (input === PROVIDER.CLAUDE || input === PROVIDER.CODEX)
43133
+ return input;
43134
+ console.error(c.error(`Error: invalid provider '${input}'. Use 'claude' or 'codex'.`));
43135
+ process.exit(1);
43136
+ }
43137
+ // src/commands/discuss.ts
43138
+ async function discussCommand(args) {
43139
+ const { values, positionals } = parseArgs({
43140
+ args,
43141
+ options: {
43142
+ list: { type: "boolean" },
43143
+ show: { type: "string" },
43144
+ archive: { type: "string" },
43145
+ delete: { type: "string" },
43146
+ model: { type: "string" },
43147
+ provider: { type: "string" },
43148
+ "reasoning-effort": { type: "string" },
43149
+ dir: { type: "string" }
43150
+ },
43151
+ strict: false,
43152
+ allowPositionals: true
43153
+ });
43154
+ const projectPath = values.dir || process.cwd();
43155
+ requireInitialization(projectPath, "discuss");
43156
+ const discussionManager = new DiscussionManager(projectPath);
43157
+ if (values.list) {
43158
+ return listDiscussions(discussionManager);
43159
+ }
43160
+ if (values.show) {
43161
+ return showDiscussion(discussionManager, values.show);
43162
+ }
43163
+ if (values.archive) {
43164
+ return archiveDiscussion(discussionManager, values.archive);
43165
+ }
43166
+ if (values.delete) {
43167
+ return deleteDiscussion(discussionManager, values.delete);
43168
+ }
43169
+ const topic = positionals.join(" ").trim();
43170
+ if (!topic) {
43171
+ showDiscussHelp();
43172
+ return;
43173
+ }
43174
+ const settings = new SettingsManager(projectPath).load();
43175
+ const provider = resolveProvider3(values.provider || settings.provider);
43176
+ const model = values.model || settings.model || DEFAULT_MODEL[provider];
43177
+ const reasoningEffort = values["reasoning-effort"];
43178
+ const aiRunner = createAiRunner(provider, {
43179
+ projectPath,
43180
+ model,
43181
+ reasoningEffort
43182
+ });
43183
+ const log = (message, level) => {
43184
+ const icon = level === "success" ? c.success("✔") : level === "error" ? c.error("✖") : level === "warn" ? c.warning("!") : c.info("●");
43185
+ console.log(` ${icon} ${message}`);
43186
+ };
43187
+ const facilitator = new DiscussionFacilitator({
43188
+ projectPath,
43189
+ aiRunner,
43190
+ discussionManager,
43191
+ log,
43192
+ provider,
43193
+ model
43194
+ });
43195
+ console.log(`
43196
+ ${c.header(" DISCUSSION ")} ${c.bold("Starting interactive discussion...")}
43197
+ `);
43198
+ console.log(` ${c.dim("Topic:")} ${c.bold(topic)}`);
43199
+ console.log(` ${c.dim("Model:")} ${c.dim(`${model} (${provider})`)}
43200
+ `);
43201
+ const renderer = new ProgressRenderer({ animated: true });
43202
+ let discussionId;
43203
+ try {
43204
+ renderer.showThinkingStarted();
43205
+ const result = await facilitator.startDiscussion(topic);
43206
+ renderer.showThinkingStopped();
43207
+ discussionId = result.discussion.id;
43208
+ process.stdout.write(`
43209
+ `);
43210
+ process.stdout.write(result.message);
43211
+ process.stdout.write(`
43212
+
43213
+ `);
43214
+ renderer.finalize();
43215
+ } catch (error48) {
43216
+ renderer.finalize();
43217
+ console.error(`
43218
+ ${c.error("✖")} ${c.red("Failed to start discussion:")} ${error48 instanceof Error ? error48.message : String(error48)}
43219
+ `);
43220
+ process.exit(1);
43221
+ }
43222
+ console.log(` ${c.dim("Type your response, or 'help' for commands. Use 'exit' or Ctrl+C to quit.")}
43223
+ `);
43224
+ const rl = readline.createInterface({
43225
+ input: process.stdin,
43226
+ output: process.stdout,
43227
+ terminal: true
43228
+ });
43229
+ rl.setPrompt(c.cyan("> "));
43230
+ rl.prompt();
43231
+ let isProcessing = false;
43232
+ const shutdown = () => {
43233
+ if (isProcessing) {
43234
+ aiRunner.abort();
43235
+ }
43236
+ console.log(`
43237
+ ${c.dim("Discussion saved.")} ${c.dim("ID:")} ${c.cyan(discussionId)}`);
43238
+ console.log(c.dim(`
43239
+ Goodbye!
43240
+ `));
43241
+ rl.close();
43242
+ process.exit(0);
43243
+ };
43244
+ process.on("SIGINT", () => {
43245
+ if (isProcessing) {
43246
+ aiRunner.abort();
43247
+ isProcessing = false;
43248
+ console.log(c.dim(`
43249
+ [Interrupted]`));
43250
+ rl.prompt();
43251
+ } else {
43252
+ shutdown();
43253
+ }
43254
+ });
43255
+ rl.on("close", () => {
43256
+ shutdown();
43257
+ });
43258
+ rl.on("line", async (input) => {
43259
+ const trimmed = input.trim();
43260
+ if (trimmed === "" || isProcessing) {
43261
+ rl.prompt();
43262
+ return;
43263
+ }
43264
+ const lowerInput = trimmed.toLowerCase();
43265
+ if (lowerInput === "help") {
43266
+ showReplHelp();
43267
+ rl.prompt();
43268
+ return;
43269
+ }
43270
+ if (lowerInput === "exit" || lowerInput === "quit") {
43271
+ shutdown();
43272
+ return;
43273
+ }
43274
+ if (lowerInput === "insights") {
43275
+ showCurrentInsights(discussionManager, discussionId);
43276
+ rl.prompt();
43277
+ return;
43278
+ }
43279
+ if (lowerInput === "summary") {
43280
+ isProcessing = true;
43281
+ const summaryRenderer = new ProgressRenderer({ animated: true });
43282
+ try {
43283
+ summaryRenderer.showThinkingStarted();
43284
+ const summary = await facilitator.summarizeDiscussion(discussionId);
43285
+ summaryRenderer.showThinkingStopped();
43286
+ process.stdout.write(`
43287
+ `);
43288
+ process.stdout.write(summary);
43289
+ process.stdout.write(`
43290
+ `);
43291
+ summaryRenderer.finalize();
43292
+ const discussion2 = discussionManager.load(discussionId);
43293
+ if (discussion2) {
43294
+ console.log(`
43295
+ ${c.success("✔")} ${c.success("Discussion completed!")}
43296
+ `);
43297
+ console.log(` ${c.dim("Messages:")} ${discussion2.messages.length} ${c.dim("Insights:")} ${discussion2.insights.length}
43298
+ `);
43299
+ }
43300
+ console.log(` ${c.dim("To review:")} ${c.cyan(`locus discuss --show ${discussionId}`)}`);
43301
+ console.log(` ${c.dim("To list all:")} ${c.cyan("locus discuss --list")}
43302
+ `);
43303
+ } catch (error48) {
43304
+ summaryRenderer.finalize();
43305
+ console.error(`
43306
+ ${c.error("✖")} ${c.red("Failed to summarize:")} ${error48 instanceof Error ? error48.message : String(error48)}
43307
+ `);
43308
+ }
43309
+ rl.close();
43310
+ process.exit(0);
43311
+ return;
43312
+ }
43313
+ isProcessing = true;
43314
+ const chunkRenderer = new ProgressRenderer({ animated: true });
43315
+ try {
43316
+ chunkRenderer.showThinkingStarted();
43317
+ const stream4 = facilitator.continueDiscussionStream(discussionId, trimmed);
43318
+ let result = {
43319
+ response: "",
43320
+ insights: []
43321
+ };
43322
+ let iterResult = await stream4.next();
43323
+ while (!iterResult.done) {
43324
+ chunkRenderer.renderChunk(iterResult.value);
43325
+ iterResult = await stream4.next();
43326
+ }
43327
+ result = iterResult.value;
43328
+ chunkRenderer.finalize();
43329
+ if (result.insights.length > 0) {
43330
+ console.log("");
43331
+ for (const insight of result.insights) {
43332
+ const tag = formatInsightTag(insight.type);
43333
+ console.log(` ${tag} ${c.bold(insight.title)}`);
43334
+ console.log(` ${c.dim(insight.content)}
43335
+ `);
43336
+ }
43337
+ }
43338
+ } catch (error48) {
43339
+ chunkRenderer.finalize();
43340
+ console.error(`
43341
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
43342
+ `);
43343
+ }
43344
+ isProcessing = false;
43345
+ rl.prompt();
43346
+ });
43347
+ }
43348
+ function listDiscussions(discussionManager) {
43349
+ const discussions = discussionManager.list();
43350
+ if (discussions.length === 0) {
43351
+ console.log(`
43352
+ ${c.dim("No discussions found.")}
43353
+ `);
43354
+ console.log(` ${c.dim("Start one with:")} ${c.cyan('locus discuss "your topic"')}
43355
+ `);
43356
+ return;
43357
+ }
43358
+ console.log(`
43359
+ ${c.header(" DISCUSSIONS ")} ${c.dim(`(${discussions.length})`)}
43360
+ `);
43361
+ for (const disc of discussions) {
43362
+ const statusIcon = disc.status === "active" ? c.warning("◯") : disc.status === "completed" ? c.success("✔") : c.dim("⊘");
43363
+ console.log(` ${statusIcon} ${c.bold(disc.title)} ${c.dim(`[${disc.status}]`)} ${c.dim(`— ${disc.messages.length} messages, ${disc.insights.length} insights`)}`);
43364
+ console.log(` ${c.dim("ID:")} ${disc.id}`);
43365
+ console.log(` ${c.dim("Created:")} ${disc.createdAt}`);
43366
+ console.log("");
43367
+ }
43368
+ }
43369
+ function showDiscussion(discussionManager, id) {
43370
+ const md = discussionManager.getMarkdown(id);
43371
+ if (!md) {
43372
+ console.error(`
43373
+ ${c.error("✖")} ${c.red(`Discussion not found: ${id}`)}
43374
+ `);
43375
+ process.exit(1);
43376
+ }
43377
+ console.log(`
43378
+ ${md}
43379
+ `);
43380
+ }
43381
+ function archiveDiscussion(discussionManager, id) {
43382
+ try {
43383
+ discussionManager.archive(id);
43384
+ console.log(`
43385
+ ${c.success("✔")} ${c.dim("Discussion archived.")}
43386
+ `);
43387
+ } catch (error48) {
43388
+ console.error(`
43389
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
43390
+ `);
43391
+ process.exit(1);
43392
+ }
43393
+ }
43394
+ function deleteDiscussion(discussionManager, id) {
43395
+ try {
43396
+ discussionManager.delete(id);
43397
+ console.log(`
43398
+ ${c.success("✔")} ${c.dim("Discussion deleted.")}
43399
+ `);
43400
+ } catch (error48) {
43401
+ console.error(`
43402
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
43403
+ `);
43404
+ process.exit(1);
43405
+ }
43406
+ }
43407
+ function showCurrentInsights(discussionManager, discussionId) {
43408
+ const discussion2 = discussionManager.load(discussionId);
43409
+ if (!discussion2 || discussion2.insights.length === 0) {
43410
+ console.log(`
43411
+ ${c.dim("No insights extracted yet.")}
43412
+ `);
43413
+ return;
43414
+ }
43415
+ console.log(`
43416
+ ${c.header(" INSIGHTS ")} ${c.dim(`(${discussion2.insights.length})`)}
43417
+ `);
43418
+ for (const insight of discussion2.insights) {
43419
+ const tag = formatInsightTag(insight.type);
43420
+ console.log(` ${tag} ${c.bold(insight.title)}`);
43421
+ console.log(` ${c.dim(insight.content)}`);
43422
+ if (insight.tags.length > 0) {
43423
+ console.log(` ${c.dim(`Tags: ${insight.tags.join(", ")}`)}`);
43424
+ }
43425
+ console.log("");
43426
+ }
43427
+ }
43428
+ function formatInsightTag(type) {
43429
+ switch (type) {
43430
+ case "decision":
43431
+ return c.green("[DECISION]");
43432
+ case "requirement":
43433
+ return c.blue("[REQUIREMENT]");
43434
+ case "idea":
43435
+ return c.yellow("[IDEA]");
43436
+ case "concern":
43437
+ return c.red("[CONCERN]");
43438
+ case "learning":
43439
+ return c.cyan("[LEARNING]");
43440
+ }
43441
+ }
43442
+ function showReplHelp() {
43443
+ console.log(`
43444
+ ${c.header(" DISCUSSION COMMANDS ")}
43445
+
43446
+ ${c.cyan("summary")} Generate a final summary and end the discussion
43447
+ ${c.cyan("insights")} Show all insights extracted so far
43448
+ ${c.cyan("exit")} Save and exit without generating a summary
43449
+ ${c.cyan("help")} Show this help message
43450
+
43451
+ ${c.dim("Type anything else to continue the discussion.")}
43452
+ `);
43453
+ }
43454
+ function showDiscussHelp() {
43455
+ console.log(`
43456
+ ${c.header(" LOCUS DISCUSS ")} ${c.dim("— Interactive AI Discussion")}
43457
+
43458
+ ${c.bold("Usage:")}
43459
+ ${c.cyan('locus discuss "topic"')} Start a discussion on a topic
43460
+ ${c.cyan("locus discuss --list")} List all discussions
43461
+ ${c.cyan("locus discuss --show <id>")} Show discussion details
43462
+ ${c.cyan("locus discuss --archive <id>")} Archive a discussion
43463
+ ${c.cyan("locus discuss --delete <id>")} Delete a discussion
43464
+
43465
+ ${c.bold("Options:")}
43466
+ ${c.dim("--model <model>")} AI model to use
43467
+ ${c.dim("--provider <p>")} AI provider (claude, codex)
43468
+ ${c.dim("--reasoning-effort <level>")} Reasoning effort (low, medium, high)
43469
+ ${c.dim("--dir <path>")} Project directory
43470
+
43471
+ ${c.bold("REPL Commands:")}
43472
+ ${c.dim("summary")} Generate final summary and end the discussion
43473
+ ${c.dim("insights")} Show all insights extracted so far
43474
+ ${c.dim("exit")} Save and exit without generating a summary
43475
+ ${c.dim("help")} Show available commands
43476
+
43477
+ ${c.bold("Examples:")}
43478
+ ${c.dim("# Start a discussion about architecture")}
43479
+ ${c.cyan('locus discuss "how should we structure the auth system?"')}
43480
+
43481
+ ${c.dim("# Review a past discussion")}
43482
+ ${c.cyan("locus discuss --show disc-1234567890")}
43483
+
43484
+ ${c.dim("# List all discussions")}
43485
+ ${c.cyan("locus discuss --list")}
43486
+ `);
43487
+ }
43488
+ // src/commands/docs.ts
43489
+ init_index_node();
43490
+ import { parseArgs as parseArgs2 } from "node:util";
43491
+
42426
43492
  // src/config-manager.ts
42427
43493
  init_index_node();
42428
43494
  import { execSync as execSync2 } from "node:child_process";
42429
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
42430
- import { join as join12 } from "node:path";
43495
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
43496
+ import { join as join15 } from "node:path";
42431
43497
  var LOCUS_GITIGNORE_MARKER = "# Locus AI";
42432
43498
  var LOCUS_MD_TEMPLATE = `## Planning First
42433
43499
 
@@ -42519,12 +43585,12 @@ It is read by AI agents before every task to avoid repeating mistakes and to fol
42519
43585
  <!-- Add learnings below this line. Format: - **[Category]**: Description -->
42520
43586
  `;
42521
43587
  function updateGitignore(projectPath) {
42522
- const gitignorePath = join12(projectPath, ".gitignore");
43588
+ const gitignorePath = join15(projectPath, ".gitignore");
42523
43589
  let content = "";
42524
43590
  const locusBlock = LOCUS_GITIGNORE_PATTERNS.join(`
42525
43591
  `);
42526
- if (existsSync11(gitignorePath)) {
42527
- content = readFileSync10(gitignorePath, "utf-8");
43592
+ if (existsSync15(gitignorePath)) {
43593
+ content = readFileSync13(gitignorePath, "utf-8");
42528
43594
  if (content.includes(LOCUS_GITIGNORE_MARKER)) {
42529
43595
  const lines = content.split(`
42530
43596
  `);
@@ -42541,7 +43607,7 @@ function updateGitignore(projectPath) {
42541
43607
  const after = lines.slice(endIdx + 1);
42542
43608
  content = [...before, locusBlock, ...after].join(`
42543
43609
  `);
42544
- writeFileSync6(gitignorePath, content);
43610
+ writeFileSync7(gitignorePath, content);
42545
43611
  return;
42546
43612
  }
42547
43613
  if (content.length > 0 && !content.endsWith(`
@@ -42556,7 +43622,7 @@ function updateGitignore(projectPath) {
42556
43622
  }
42557
43623
  content += `${locusBlock}
42558
43624
  `;
42559
- writeFileSync6(gitignorePath, content);
43625
+ writeFileSync7(gitignorePath, content);
42560
43626
  }
42561
43627
  function ensureGitIdentity(projectPath) {
42562
43628
  const hasName = (() => {
@@ -42603,48 +43669,49 @@ class ConfigManager {
42603
43669
  this.projectPath = projectPath;
42604
43670
  }
42605
43671
  async init(version2) {
42606
- const locusConfigDir = join12(this.projectPath, LOCUS_CONFIG.dir);
43672
+ const locusConfigDir = join15(this.projectPath, LOCUS_CONFIG.dir);
42607
43673
  const locusConfigPath = getLocusPath(this.projectPath, "configFile");
42608
- if (!existsSync11(locusConfigDir)) {
42609
- mkdirSync6(locusConfigDir, { recursive: true });
43674
+ if (!existsSync15(locusConfigDir)) {
43675
+ mkdirSync7(locusConfigDir, { recursive: true });
42610
43676
  }
42611
43677
  const locusSubdirs = [
42612
43678
  LOCUS_CONFIG.artifactsDir,
42613
43679
  LOCUS_CONFIG.documentsDir,
42614
43680
  LOCUS_CONFIG.sessionsDir,
42615
43681
  LOCUS_CONFIG.reviewsDir,
42616
- LOCUS_CONFIG.plansDir
43682
+ LOCUS_CONFIG.plansDir,
43683
+ LOCUS_CONFIG.discussionsDir
42617
43684
  ];
42618
43685
  for (const subdir of locusSubdirs) {
42619
- const subdirPath = join12(locusConfigDir, subdir);
42620
- if (!existsSync11(subdirPath)) {
42621
- mkdirSync6(subdirPath, { recursive: true });
43686
+ const subdirPath = join15(locusConfigDir, subdir);
43687
+ if (!existsSync15(subdirPath)) {
43688
+ mkdirSync7(subdirPath, { recursive: true });
42622
43689
  }
42623
43690
  }
42624
43691
  const locusMdPath = getLocusPath(this.projectPath, "contextFile");
42625
- if (!existsSync11(locusMdPath)) {
42626
- writeFileSync6(locusMdPath, LOCUS_MD_TEMPLATE);
43692
+ if (!existsSync15(locusMdPath)) {
43693
+ writeFileSync7(locusMdPath, LOCUS_MD_TEMPLATE);
42627
43694
  }
42628
43695
  const learningsMdPath = getLocusPath(this.projectPath, "learningsFile");
42629
- if (!existsSync11(learningsMdPath)) {
42630
- writeFileSync6(learningsMdPath, DEFAULT_LEARNINGS_MD);
43696
+ if (!existsSync15(learningsMdPath)) {
43697
+ writeFileSync7(learningsMdPath, DEFAULT_LEARNINGS_MD);
42631
43698
  }
42632
- if (!existsSync11(locusConfigPath)) {
43699
+ if (!existsSync15(locusConfigPath)) {
42633
43700
  const config2 = {
42634
43701
  $schema: LOCUS_SCHEMAS.config,
42635
43702
  version: version2,
42636
43703
  createdAt: new Date().toISOString(),
42637
43704
  projectPath: "."
42638
43705
  };
42639
- writeFileSync6(locusConfigPath, JSON.stringify(config2, null, 2));
43706
+ writeFileSync7(locusConfigPath, JSON.stringify(config2, null, 2));
42640
43707
  }
42641
43708
  updateGitignore(this.projectPath);
42642
43709
  ensureGitIdentity(this.projectPath);
42643
43710
  }
42644
43711
  loadConfig() {
42645
- const path2 = getLocusPath(this.projectPath, "configFile");
42646
- if (existsSync11(path2)) {
42647
- return JSON.parse(readFileSync10(path2, "utf-8"));
43712
+ const path3 = getLocusPath(this.projectPath, "configFile");
43713
+ if (existsSync15(path3)) {
43714
+ return JSON.parse(readFileSync13(path3, "utf-8"));
42648
43715
  }
42649
43716
  return null;
42650
43717
  }
@@ -42674,19 +43741,19 @@ class ConfigManager {
42674
43741
  this.saveConfig(config2);
42675
43742
  }
42676
43743
  }
42677
- const settingsPath = join12(this.projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42678
- if (existsSync11(settingsPath)) {
42679
- const raw = readFileSync10(settingsPath, "utf-8");
43744
+ const settingsPath = join15(this.projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
43745
+ if (existsSync15(settingsPath)) {
43746
+ const raw = readFileSync13(settingsPath, "utf-8");
42680
43747
  const settings = JSON.parse(raw);
42681
43748
  if (settings.$schema !== LOCUS_SCHEMAS.settings) {
42682
43749
  const { $schema: _2, ...rest } = settings;
42683
43750
  const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
42684
- writeFileSync6(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
43751
+ writeFileSync7(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42685
43752
  }
42686
43753
  }
42687
43754
  const locusMdPath = getLocusPath(this.projectPath, "contextFile");
42688
- const locusMdExisted = existsSync11(locusMdPath);
42689
- writeFileSync6(locusMdPath, LOCUS_MD_TEMPLATE);
43755
+ const locusMdExisted = existsSync15(locusMdPath);
43756
+ writeFileSync7(locusMdPath, LOCUS_MD_TEMPLATE);
42690
43757
  if (!locusMdExisted) {
42691
43758
  result.directoriesCreated.push(".locus/LOCUS.md");
42692
43759
  }
@@ -42695,25 +43762,26 @@ class ConfigManager {
42695
43762
  LOCUS_CONFIG.documentsDir,
42696
43763
  LOCUS_CONFIG.sessionsDir,
42697
43764
  LOCUS_CONFIG.reviewsDir,
42698
- LOCUS_CONFIG.plansDir
43765
+ LOCUS_CONFIG.plansDir,
43766
+ LOCUS_CONFIG.discussionsDir
42699
43767
  ];
42700
- const locusConfigDir = join12(this.projectPath, LOCUS_CONFIG.dir);
43768
+ const locusConfigDir = join15(this.projectPath, LOCUS_CONFIG.dir);
42701
43769
  for (const subdir of locusSubdirs) {
42702
- const subdirPath = join12(locusConfigDir, subdir);
42703
- if (!existsSync11(subdirPath)) {
42704
- mkdirSync6(subdirPath, { recursive: true });
43770
+ const subdirPath = join15(locusConfigDir, subdir);
43771
+ if (!existsSync15(subdirPath)) {
43772
+ mkdirSync7(subdirPath, { recursive: true });
42705
43773
  result.directoriesCreated.push(`.locus/${subdir}`);
42706
43774
  }
42707
43775
  }
42708
43776
  const learningsMdPath = getLocusPath(this.projectPath, "learningsFile");
42709
- if (!existsSync11(learningsMdPath)) {
42710
- writeFileSync6(learningsMdPath, DEFAULT_LEARNINGS_MD);
43777
+ if (!existsSync15(learningsMdPath)) {
43778
+ writeFileSync7(learningsMdPath, DEFAULT_LEARNINGS_MD);
42711
43779
  result.directoriesCreated.push(".locus/LEARNINGS.md");
42712
43780
  }
42713
- const gitignorePath = join12(this.projectPath, ".gitignore");
42714
- const gitignoreBefore = existsSync11(gitignorePath) ? readFileSync10(gitignorePath, "utf-8") : "";
43781
+ const gitignorePath = join15(this.projectPath, ".gitignore");
43782
+ const gitignoreBefore = existsSync15(gitignorePath) ? readFileSync13(gitignorePath, "utf-8") : "";
42715
43783
  updateGitignore(this.projectPath);
42716
- const gitignoreAfter = readFileSync10(gitignorePath, "utf-8");
43784
+ const gitignoreAfter = readFileSync13(gitignorePath, "utf-8");
42717
43785
  if (gitignoreBefore !== gitignoreAfter) {
42718
43786
  result.gitignoreUpdated = true;
42719
43787
  }
@@ -42723,83 +43791,11 @@ class ConfigManager {
42723
43791
  saveConfig(config2) {
42724
43792
  const { $schema: _2, ...rest } = config2;
42725
43793
  const ordered = { $schema: LOCUS_SCHEMAS.config, ...rest };
42726
- const path2 = getLocusPath(this.projectPath, "configFile");
42727
- writeFileSync6(path2, JSON.stringify(ordered, null, 2));
43794
+ const path3 = getLocusPath(this.projectPath, "configFile");
43795
+ writeFileSync7(path3, JSON.stringify(ordered, null, 2));
42728
43796
  }
42729
43797
  }
42730
43798
 
42731
- // src/utils/banner.ts
42732
- init_index_node();
42733
-
42734
- // src/utils/version.ts
42735
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
42736
- import { dirname as dirname3, join as join13 } from "node:path";
42737
- import { fileURLToPath as fileURLToPath3 } from "node:url";
42738
- function getVersion() {
42739
- try {
42740
- const __filename2 = fileURLToPath3(import.meta.url);
42741
- const __dirname2 = dirname3(__filename2);
42742
- const bundledPath = join13(__dirname2, "..", "package.json");
42743
- const sourcePath = join13(__dirname2, "..", "..", "package.json");
42744
- if (existsSync12(bundledPath)) {
42745
- const pkg = JSON.parse(readFileSync11(bundledPath, "utf-8"));
42746
- if (pkg.name === "@locusai/cli") {
42747
- return pkg.version || "0.0.0";
42748
- }
42749
- }
42750
- if (existsSync12(sourcePath)) {
42751
- const pkg = JSON.parse(readFileSync11(sourcePath, "utf-8"));
42752
- if (pkg.name === "@locusai/cli") {
42753
- return pkg.version || "0.0.0";
42754
- }
42755
- }
42756
- } catch {}
42757
- return "0.0.0";
42758
- }
42759
- var VERSION2 = getVersion();
42760
-
42761
- // src/utils/banner.ts
42762
- function printBanner() {
42763
- console.log(c.primary(`
42764
- _ ____ ____ _ _ ____
42765
- | | / __ \\ / ___| | | |/ ___|
42766
- | | | | | | | | | | |\\___ \\
42767
- | |___| |__| | |___| |_| |___) |
42768
- |_____|\\____/ \\____|\\___/|____/ ${c.dim(`v${VERSION2}`)}
42769
- `));
42770
- }
42771
- // src/utils/helpers.ts
42772
- init_index_node();
42773
- import { existsSync as existsSync13 } from "node:fs";
42774
- import { join as join14 } from "node:path";
42775
- function isProjectInitialized(projectPath) {
42776
- const locusDir = join14(projectPath, LOCUS_CONFIG.dir);
42777
- const configPath = join14(locusDir, LOCUS_CONFIG.configFile);
42778
- return existsSync13(locusDir) && existsSync13(configPath);
42779
- }
42780
- function requireInitialization(projectPath, command) {
42781
- if (!isProjectInitialized(projectPath)) {
42782
- console.error(`
42783
- ${c.error("✖ Error")} ${c.red(`Locus is not initialized in this directory.`)}
42784
-
42785
- The '${c.bold(command)}' command requires a Locus project to be initialized.
42786
-
42787
- To initialize Locus in this directory, run:
42788
- ${c.primary("locus init")}
42789
-
42790
- This will create a ${c.dim(".locus")} directory with the necessary configuration.
42791
- `);
42792
- process.exit(1);
42793
- }
42794
- }
42795
- function resolveProvider3(input) {
42796
- if (!input)
42797
- return PROVIDER.CLAUDE;
42798
- if (input === PROVIDER.CLAUDE || input === PROVIDER.CODEX)
42799
- return input;
42800
- console.error(c.error(`Error: invalid provider '${input}'. Use 'claude' or 'codex'.`));
42801
- process.exit(1);
42802
- }
42803
43799
  // src/workspace-resolver.ts
42804
43800
  init_index_node();
42805
43801
 
@@ -42844,7 +43840,7 @@ async function docsCommand(args) {
42844
43840
  }
42845
43841
  }
42846
43842
  async function docsSyncCommand(args) {
42847
- const { values } = parseArgs({
43843
+ const { values } = parseArgs2({
42848
43844
  args,
42849
43845
  options: {
42850
43846
  "api-key": { type: "string" },
@@ -42961,7 +43957,7 @@ function showDocsSyncHelp() {
42961
43957
  // src/commands/exec.ts
42962
43958
  init_index_node();
42963
43959
  import { randomUUID as randomUUID2 } from "node:crypto";
42964
- import { parseArgs as parseArgs2 } from "node:util";
43960
+ import { parseArgs as parseArgs3 } from "node:util";
42965
43961
 
42966
43962
  // src/display/json-stream-renderer.ts
42967
43963
  init_src();
@@ -43254,7 +44250,7 @@ function showSessionsHelp() {
43254
44250
 
43255
44251
  // src/commands/exec.ts
43256
44252
  async function execCommand(args) {
43257
- const { values, positionals } = parseArgs2({
44253
+ const { values, positionals } = parseArgs3({
43258
44254
  args,
43259
44255
  options: {
43260
44256
  model: { type: "string" },
@@ -43265,6 +44261,7 @@ async function execCommand(args) {
43265
44261
  "no-status": { type: "boolean" },
43266
44262
  interactive: { type: "boolean", short: "i" },
43267
44263
  session: { type: "string", short: "s" },
44264
+ "session-id": { type: "string" },
43268
44265
  "json-stream": { type: "boolean" }
43269
44266
  },
43270
44267
  strict: false
@@ -43393,7 +44390,7 @@ async function execCommand(args) {
43393
44390
  }
43394
44391
  }
43395
44392
  async function execJsonStream(values, positionals, projectPath) {
43396
- const sessionId = values.session ?? randomUUID2();
44393
+ const sessionId = values["session-id"] ?? values.session ?? randomUUID2();
43397
44394
  const execSettings = new SettingsManager(projectPath).load();
43398
44395
  const provider = resolveProvider3(values.provider || execSettings.provider);
43399
44396
  const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
@@ -43462,6 +44459,11 @@ function showHelp2() {
43462
44459
  ${c.dim("remove Remove all settings")}
43463
44460
  ${c.success("index")} Index the codebase for AI context
43464
44461
  ${c.success("run")} Start agent to work on tasks sequentially
44462
+ ${c.success("discuss")} Start an interactive AI discussion on a topic
44463
+ ${c.dim("--list List all discussions")}
44464
+ ${c.dim("--show <id> Show discussion details")}
44465
+ ${c.dim("--archive <id> Archive a discussion")}
44466
+ ${c.dim("--delete <id> Delete a discussion")}
43465
44467
  ${c.success("plan")} Run async planning meeting to create sprint plans
43466
44468
  ${c.success("docs")} Manage workspace docs
43467
44469
  ${c.dim("sync Sync docs from API to .locus/documents")}
@@ -43499,6 +44501,7 @@ function showHelp2() {
43499
44501
  ${c.dim("$")} ${c.primary("locus review")}
43500
44502
  ${c.dim("$")} ${c.primary("locus review local")}
43501
44503
  ${c.dim("$")} ${c.primary("locus telegram setup")}
44504
+ ${c.dim("$")} ${c.primary('locus discuss "how should we design the auth system?"')}
43502
44505
  ${c.dim("$")} ${c.primary("locus exec sessions list")}
43503
44506
 
43504
44507
  For more information, visit: ${c.underline("https://docs.locusai.dev")}
@@ -43506,7 +44509,7 @@ function showHelp2() {
43506
44509
  }
43507
44510
  // src/commands/index-codebase.ts
43508
44511
  init_index_node();
43509
- import { parseArgs as parseArgs3 } from "node:util";
44512
+ import { parseArgs as parseArgs4 } from "node:util";
43510
44513
 
43511
44514
  // src/tree-summarizer.ts
43512
44515
  init_index_node();
@@ -43534,7 +44537,7 @@ ${tree}`;
43534
44537
 
43535
44538
  // src/commands/index-codebase.ts
43536
44539
  async function indexCommand(args) {
43537
- const { values } = parseArgs3({
44540
+ const { values } = parseArgs4({
43538
44541
  args,
43539
44542
  options: {
43540
44543
  dir: { type: "string" },
@@ -43632,9 +44635,9 @@ async function initCommand() {
43632
44635
  }
43633
44636
  // src/commands/plan.ts
43634
44637
  init_index_node();
43635
- import { existsSync as existsSync14, unlinkSync as unlinkSync4 } from "node:fs";
43636
- import { join as join15 } from "node:path";
43637
- import { parseArgs as parseArgs4 } from "node:util";
44638
+ import { existsSync as existsSync16, unlinkSync as unlinkSync5 } from "node:fs";
44639
+ import { join as join16 } from "node:path";
44640
+ import { parseArgs as parseArgs5 } from "node:util";
43638
44641
  function normalizePlanIdArgs(args) {
43639
44642
  const planIdFlags = new Set(["--approve", "--reject", "--cancel", "--show"]);
43640
44643
  const result = [];
@@ -43653,7 +44656,7 @@ function normalizePlanIdArgs(args) {
43653
44656
  }
43654
44657
  async function planCommand(args) {
43655
44658
  const normalizedArgs = normalizePlanIdArgs(args);
43656
- const { values, positionals } = parseArgs4({
44659
+ const { values, positionals } = parseArgs5({
43657
44660
  args: normalizedArgs,
43658
44661
  options: {
43659
44662
  approve: { type: "string" },
@@ -43740,9 +44743,9 @@ async function planCommand(args) {
43740
44743
  try {
43741
44744
  const result = await meeting.run(directive, feedback);
43742
44745
  planManager.save(result.plan);
43743
- const tempFile = join15(getLocusPath(projectPath, "plansDir"), `${result.plan.id}.json`);
43744
- if (existsSync14(tempFile)) {
43745
- unlinkSync4(tempFile);
44746
+ const tempFile = join16(getLocusPath(projectPath, "plansDir"), `${result.plan.id}.json`);
44747
+ if (existsSync16(tempFile)) {
44748
+ unlinkSync5(tempFile);
43746
44749
  }
43747
44750
  console.log(`
43748
44751
  ${c.success("✔")} ${c.success("Planning meeting complete!")}
@@ -43938,9 +44941,9 @@ function showPlanHelp() {
43938
44941
  }
43939
44942
  // src/commands/review.ts
43940
44943
  init_index_node();
43941
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
43942
- import { join as join16 } from "node:path";
43943
- import { parseArgs as parseArgs5 } from "node:util";
44944
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
44945
+ import { join as join17 } from "node:path";
44946
+ import { parseArgs as parseArgs6 } from "node:util";
43944
44947
  async function reviewCommand(args) {
43945
44948
  const subcommand = args[0];
43946
44949
  if (subcommand === "local") {
@@ -43949,7 +44952,7 @@ async function reviewCommand(args) {
43949
44952
  return reviewPrsCommand(args);
43950
44953
  }
43951
44954
  async function reviewPrsCommand(args) {
43952
- const { values } = parseArgs5({
44955
+ const { values } = parseArgs6({
43953
44956
  args,
43954
44957
  options: {
43955
44958
  "api-key": { type: "string" },
@@ -44035,7 +45038,7 @@ ${c.info("Received shutdown signal. Stopping reviewer...")}`);
44035
45038
  await reviewer.run();
44036
45039
  }
44037
45040
  async function reviewLocalCommand(args) {
44038
- const { values } = parseArgs5({
45041
+ const { values } = parseArgs6({
44039
45042
  args,
44040
45043
  options: {
44041
45044
  model: { type: "string" },
@@ -44078,13 +45081,13 @@ async function reviewLocalCommand(args) {
44078
45081
  `);
44079
45082
  return;
44080
45083
  }
44081
- const reviewsDir = join16(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
44082
- if (!existsSync15(reviewsDir)) {
44083
- mkdirSync7(reviewsDir, { recursive: true });
45084
+ const reviewsDir = join17(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
45085
+ if (!existsSync17(reviewsDir)) {
45086
+ mkdirSync8(reviewsDir, { recursive: true });
44084
45087
  }
44085
45088
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
44086
- const reportPath = join16(reviewsDir, `review-${timestamp}.md`);
44087
- writeFileSync7(reportPath, report, "utf-8");
45089
+ const reportPath = join17(reviewsDir, `review-${timestamp}.md`);
45090
+ writeFileSync8(reportPath, report, "utf-8");
44088
45091
  console.log(`
44089
45092
  ${c.success("✔")} ${c.success("Review complete!")}`);
44090
45093
  console.log(` ${c.dim("Report saved to:")} ${c.primary(reportPath)}
@@ -44092,9 +45095,9 @@ async function reviewLocalCommand(args) {
44092
45095
  }
44093
45096
  // src/commands/run.ts
44094
45097
  init_index_node();
44095
- import { parseArgs as parseArgs6 } from "node:util";
45098
+ import { parseArgs as parseArgs7 } from "node:util";
44096
45099
  async function runCommand(args) {
44097
- const { values } = parseArgs6({
45100
+ const { values } = parseArgs7({
44098
45101
  args,
44099
45102
  options: {
44100
45103
  "api-key": { type: "string" },
@@ -44176,11 +45179,11 @@ ${c.info(`Received ${signal}. Stopping agent and cleaning up...`)}`);
44176
45179
  // src/commands/telegram.ts
44177
45180
  init_index_node();
44178
45181
  import { spawn as spawn4 } from "node:child_process";
44179
- import { existsSync as existsSync16 } from "node:fs";
44180
- import { join as join17 } from "node:path";
44181
- import { createInterface as createInterface3 } from "node:readline";
45182
+ import { existsSync as existsSync18 } from "node:fs";
45183
+ import { join as join18 } from "node:path";
45184
+ import { createInterface as createInterface4 } from "node:readline";
44182
45185
  function ask2(question) {
44183
- const rl = createInterface3({
45186
+ const rl = createInterface4({
44184
45187
  input: process.stdin,
44185
45188
  output: process.stdout
44186
45189
  });
@@ -44411,8 +45414,8 @@ function runBotCommand(projectPath) {
44411
45414
  `);
44412
45415
  process.exit(1);
44413
45416
  }
44414
- const monorepoTelegramEntry = join17(projectPath, "packages/telegram/src/index.ts");
44415
- const isMonorepo = existsSync16(monorepoTelegramEntry);
45417
+ const monorepoTelegramEntry = join18(projectPath, "packages/telegram/src/index.ts");
45418
+ const isMonorepo = existsSync18(monorepoTelegramEntry);
44416
45419
  let cmd;
44417
45420
  let args;
44418
45421
  if (isMonorepo) {
@@ -44571,6 +45574,10 @@ var isJsonStream = process.argv.includes("--json-stream");
44571
45574
  async function main() {
44572
45575
  const command = process.argv[2];
44573
45576
  const args = process.argv.slice(3);
45577
+ if (command === "--json-stream") {
45578
+ await execCommand([command, ...args]);
45579
+ return;
45580
+ }
44574
45581
  if (command !== "exec" && !isJsonStream) {
44575
45582
  printBanner();
44576
45583
  }
@@ -44587,6 +45594,9 @@ async function main() {
44587
45594
  case "exec":
44588
45595
  await execCommand(args);
44589
45596
  break;
45597
+ case "discuss":
45598
+ await discussCommand(args);
45599
+ break;
44590
45600
  case "plan":
44591
45601
  await planCommand(args);
44592
45602
  break;