@locusai/sdk 0.13.0 → 0.13.3

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.
@@ -391,6 +391,39 @@ var init_workspaces = __esm(() => {
391
391
  };
392
392
  });
393
393
 
394
+ // src/discussion/discussion-types.ts
395
+ var import_zod, DiscussionMessageSchema, DiscussionInsightSchema, DiscussionSchema;
396
+ var init_discussion_types = __esm(() => {
397
+ import_zod = require("zod");
398
+ DiscussionMessageSchema = import_zod.z.object({
399
+ role: import_zod.z.enum(["user", "assistant"]),
400
+ content: import_zod.z.string(),
401
+ timestamp: import_zod.z.number()
402
+ });
403
+ DiscussionInsightSchema = import_zod.z.object({
404
+ id: import_zod.z.string(),
405
+ type: import_zod.z.enum(["decision", "requirement", "idea", "concern", "learning"]),
406
+ title: import_zod.z.string(),
407
+ content: import_zod.z.string(),
408
+ tags: import_zod.z.array(import_zod.z.string()).default([]),
409
+ createdAt: import_zod.z.string()
410
+ });
411
+ DiscussionSchema = import_zod.z.object({
412
+ id: import_zod.z.string(),
413
+ title: import_zod.z.string(),
414
+ topic: import_zod.z.string(),
415
+ status: import_zod.z.enum(["active", "completed", "archived"]).default("active"),
416
+ messages: import_zod.z.array(DiscussionMessageSchema).default([]),
417
+ insights: import_zod.z.array(DiscussionInsightSchema).default([]),
418
+ createdAt: import_zod.z.string(),
419
+ updatedAt: import_zod.z.string(),
420
+ metadata: import_zod.z.object({
421
+ model: import_zod.z.string(),
422
+ provider: import_zod.z.string()
423
+ })
424
+ });
425
+ });
426
+
394
427
  // src/index.ts
395
428
  var exports_src = {};
396
429
  __export(exports_src, {
@@ -403,6 +436,9 @@ __export(exports_src, {
403
436
  LocusClient: () => LocusClient,
404
437
  InvitationsModule: () => InvitationsModule,
405
438
  DocsModule: () => DocsModule,
439
+ DiscussionSchema: () => DiscussionSchema,
440
+ DiscussionMessageSchema: () => DiscussionMessageSchema,
441
+ DiscussionInsightSchema: () => DiscussionInsightSchema,
406
442
  CiModule: () => CiModule,
407
443
  AuthModule: () => AuthModule
408
444
  });
@@ -508,6 +544,7 @@ var init_src = __esm(() => {
508
544
  init_sprints();
509
545
  init_tasks();
510
546
  init_workspaces();
547
+ init_discussion_types();
511
548
  import_axios = __toESM(require("axios"));
512
549
  init_events();
513
550
  init_auth();
@@ -521,6 +558,12 @@ var init_src = __esm(() => {
521
558
  });
522
559
 
523
560
  // src/core/config.ts
561
+ function isValidModelForProvider(provider, model) {
562
+ return PROVIDER_MODELS[provider].includes(model);
563
+ }
564
+ function getModelsForProvider(provider) {
565
+ return PROVIDER_MODELS[provider];
566
+ }
524
567
  function getLocusPath(projectPath, fileName) {
525
568
  return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
526
569
  }
@@ -528,16 +571,36 @@ function getAgentArtifactsPath(projectPath, agentId) {
528
571
  const shortId = agentId.slice(-8);
529
572
  return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.artifactsDir, shortId);
530
573
  }
531
- var import_node_path, PROVIDER, DEFAULT_MODEL, LOCUS_SCHEMA_BASE_URL = "https://locusai.dev/schemas", LOCUS_SCHEMAS, LOCUS_CONFIG, LOCUS_GITIGNORE_PATTERNS;
574
+ var import_node_path, PROVIDER, CLAUDE_MODELS, CODEX_MODELS, PROVIDER_MODELS, DEFAULT_MODEL, LOCUS_SCHEMA_BASE_URL = "https://locusai.dev/schemas", LOCUS_SCHEMAS, LOCUS_CONFIG, LOCUS_GITIGNORE_PATTERNS;
532
575
  var init_config = __esm(() => {
533
576
  import_node_path = require("node:path");
534
577
  PROVIDER = {
535
578
  CLAUDE: "claude",
536
579
  CODEX: "codex"
537
580
  };
581
+ CLAUDE_MODELS = {
582
+ OPUS: "opus",
583
+ SONNET: "sonnet",
584
+ HAIKU: "haiku",
585
+ OPUS_PLAN: "opusplan",
586
+ CLAUDE_OPUS_4_6: "claude-opus-4-6",
587
+ CLAUDE_SONNET_4_5: "claude-sonnet-4-5-20250929",
588
+ CLAUDE_SONNET_4_6: "claude-sonnet-4-6",
589
+ CLAUDE_HAIKU_4_5: "claude-haiku-4-5-20251001"
590
+ };
591
+ CODEX_MODELS = {
592
+ GPT_5_3_CODEX: "gpt-5.3-codex",
593
+ GPT_5_3_CODEX_SPARK: "gpt-5.3-codex-spark",
594
+ GPT_5_CODEX_MINI: "gpt-5-codex-mini",
595
+ GPT_5_2_CODEX: "gpt-5.2-codex"
596
+ };
597
+ PROVIDER_MODELS = {
598
+ [PROVIDER.CLAUDE]: Object.values(CLAUDE_MODELS),
599
+ [PROVIDER.CODEX]: Object.values(CODEX_MODELS)
600
+ };
538
601
  DEFAULT_MODEL = {
539
- [PROVIDER.CLAUDE]: "opus",
540
- [PROVIDER.CODEX]: "gpt-5.3-codex"
602
+ [PROVIDER.CLAUDE]: CLAUDE_MODELS.OPUS,
603
+ [PROVIDER.CODEX]: CODEX_MODELS.GPT_5_3_CODEX
541
604
  };
542
605
  LOCUS_SCHEMAS = {
543
606
  config: `${LOCUS_SCHEMA_BASE_URL}/config.schema.json`,
@@ -554,7 +617,8 @@ var init_config = __esm(() => {
554
617
  documentsDir: "documents",
555
618
  sessionsDir: "sessions",
556
619
  reviewsDir: "reviews",
557
- plansDir: "plans"
620
+ plansDir: "plans",
621
+ discussionsDir: "discussions"
558
622
  };
559
623
  LOCUS_GITIGNORE_PATTERNS = [
560
624
  "# Locus AI - Session data (user-specific, can grow large)",
@@ -569,6 +633,9 @@ var init_config = __esm(() => {
569
633
  "# Locus AI - Plans (generated per task)",
570
634
  ".locus/plans/",
571
635
  "",
636
+ "# Locus AI - Discussions (AI discussion sessions)",
637
+ ".locus/discussions/",
638
+ "",
572
639
  "# Locus AI - Settings (contains API key, telegram config, etc.)",
573
640
  ".locus/settings.json",
574
641
  "",
@@ -1517,6 +1584,10 @@ var init_codex_runner = __esm(() => {
1517
1584
  function createAiRunner(provider, config) {
1518
1585
  const resolvedProvider = provider ?? PROVIDER.CLAUDE;
1519
1586
  const model = config.model ?? DEFAULT_MODEL[resolvedProvider];
1587
+ if (!isValidModelForProvider(resolvedProvider, model)) {
1588
+ const validModels = getModelsForProvider(resolvedProvider);
1589
+ throw new Error(`Model "${model}" is not valid for provider "${resolvedProvider}". ` + `Valid models: ${validModels.join(", ")}`);
1590
+ }
1520
1591
  switch (resolvedProvider) {
1521
1592
  case PROVIDER.CODEX:
1522
1593
  return new CodexRunner(config.projectPath, model, config.log, config.reasoningEffort ?? "high", config.timeoutMs);
@@ -1871,6 +1942,187 @@ var init_git_workflow = __esm(() => {
1871
1942
  import_node_child_process4 = require("node:child_process");
1872
1943
  });
1873
1944
 
1945
+ // src/discussion/discussion-manager.ts
1946
+ class DiscussionManager {
1947
+ discussionsDir;
1948
+ constructor(projectPath) {
1949
+ this.discussionsDir = getLocusPath(projectPath, "discussionsDir");
1950
+ }
1951
+ create(topic, model, provider) {
1952
+ this.ensureDir();
1953
+ const now = new Date().toISOString();
1954
+ const id = `disc-${Date.now()}`;
1955
+ const discussion = {
1956
+ id,
1957
+ title: topic,
1958
+ topic,
1959
+ status: "active",
1960
+ messages: [],
1961
+ insights: [],
1962
+ createdAt: now,
1963
+ updatedAt: now,
1964
+ metadata: { model, provider }
1965
+ };
1966
+ this.save(discussion);
1967
+ return discussion;
1968
+ }
1969
+ save(discussion) {
1970
+ this.ensureDir();
1971
+ const jsonPath = import_node_path5.join(this.discussionsDir, `${discussion.id}.json`);
1972
+ const mdPath = import_node_path5.join(this.discussionsDir, `summary-${discussion.id}.md`);
1973
+ import_node_fs3.writeFileSync(jsonPath, JSON.stringify(discussion, null, 2), "utf-8");
1974
+ import_node_fs3.writeFileSync(mdPath, this.toMarkdown(discussion), "utf-8");
1975
+ }
1976
+ load(id) {
1977
+ this.ensureDir();
1978
+ const filePath = import_node_path5.join(this.discussionsDir, `${id}.json`);
1979
+ if (!import_node_fs3.existsSync(filePath)) {
1980
+ return null;
1981
+ }
1982
+ try {
1983
+ return JSON.parse(import_node_fs3.readFileSync(filePath, "utf-8"));
1984
+ } catch {
1985
+ return null;
1986
+ }
1987
+ }
1988
+ list(status) {
1989
+ this.ensureDir();
1990
+ const files = import_node_fs3.readdirSync(this.discussionsDir).filter((f) => f.endsWith(".json"));
1991
+ const discussions = [];
1992
+ for (const file of files) {
1993
+ try {
1994
+ const discussion = JSON.parse(import_node_fs3.readFileSync(import_node_path5.join(this.discussionsDir, file), "utf-8"));
1995
+ if (!status || discussion.status === status) {
1996
+ discussions.push(discussion);
1997
+ }
1998
+ } catch {}
1999
+ }
2000
+ discussions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
2001
+ return discussions;
2002
+ }
2003
+ complete(id) {
2004
+ const discussion = this.load(id);
2005
+ if (!discussion) {
2006
+ throw new Error(`Discussion not found: ${id}`);
2007
+ }
2008
+ discussion.status = "completed";
2009
+ discussion.updatedAt = new Date().toISOString();
2010
+ this.save(discussion);
2011
+ return discussion;
2012
+ }
2013
+ archive(id) {
2014
+ const discussion = this.load(id);
2015
+ if (!discussion) {
2016
+ throw new Error(`Discussion not found: ${id}`);
2017
+ }
2018
+ discussion.status = "archived";
2019
+ discussion.updatedAt = new Date().toISOString();
2020
+ this.save(discussion);
2021
+ }
2022
+ delete(id) {
2023
+ this.ensureDir();
2024
+ const jsonPath = import_node_path5.join(this.discussionsDir, `${id}.json`);
2025
+ const mdPath = import_node_path5.join(this.discussionsDir, `summary-${id}.md`);
2026
+ if (import_node_fs3.existsSync(jsonPath)) {
2027
+ import_node_fs3.unlinkSync(jsonPath);
2028
+ }
2029
+ if (import_node_fs3.existsSync(mdPath)) {
2030
+ import_node_fs3.unlinkSync(mdPath);
2031
+ }
2032
+ }
2033
+ addMessage(id, role, content) {
2034
+ const discussion = this.load(id);
2035
+ if (!discussion) {
2036
+ throw new Error(`Discussion not found: ${id}`);
2037
+ }
2038
+ discussion.messages.push({
2039
+ role,
2040
+ content,
2041
+ timestamp: Date.now()
2042
+ });
2043
+ discussion.updatedAt = new Date().toISOString();
2044
+ this.save(discussion);
2045
+ return discussion;
2046
+ }
2047
+ addInsight(id, insight) {
2048
+ const discussion = this.load(id);
2049
+ if (!discussion) {
2050
+ throw new Error(`Discussion not found: ${id}`);
2051
+ }
2052
+ discussion.insights.push(insight);
2053
+ discussion.updatedAt = new Date().toISOString();
2054
+ this.save(discussion);
2055
+ return discussion;
2056
+ }
2057
+ getAllInsights() {
2058
+ const discussions = this.list("completed");
2059
+ const insights = [];
2060
+ for (const discussion of discussions) {
2061
+ insights.push(...discussion.insights);
2062
+ }
2063
+ insights.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
2064
+ return insights;
2065
+ }
2066
+ getMarkdown(id) {
2067
+ const discussion = this.load(id);
2068
+ if (!discussion)
2069
+ return null;
2070
+ return this.toMarkdown(discussion);
2071
+ }
2072
+ toMarkdown(discussion) {
2073
+ const lines = [];
2074
+ lines.push(`# Discussion: ${discussion.title}`);
2075
+ lines.push("");
2076
+ lines.push(`**Status:** ${discussion.status.toUpperCase()}`);
2077
+ lines.push(`**Topic:** ${discussion.topic}`);
2078
+ lines.push(`**Created:** ${discussion.createdAt}`);
2079
+ lines.push(`**Updated:** ${discussion.updatedAt}`);
2080
+ lines.push(`**Model:** ${discussion.metadata.model} (${discussion.metadata.provider})`);
2081
+ lines.push("");
2082
+ if (discussion.messages.length > 0) {
2083
+ lines.push(`## Messages (${discussion.messages.length})`);
2084
+ lines.push("");
2085
+ for (const msg of discussion.messages) {
2086
+ const time = new Date(msg.timestamp).toISOString();
2087
+ const roleLabel = msg.role === "user" ? "User" : "Assistant";
2088
+ lines.push(`### ${roleLabel} — ${time}`);
2089
+ lines.push("");
2090
+ lines.push(msg.content);
2091
+ lines.push("");
2092
+ }
2093
+ }
2094
+ if (discussion.insights.length > 0) {
2095
+ lines.push(`## Insights (${discussion.insights.length})`);
2096
+ lines.push("");
2097
+ for (const insight of discussion.insights) {
2098
+ lines.push(`### [${insight.type.toUpperCase()}] ${insight.title}`);
2099
+ lines.push("");
2100
+ lines.push(insight.content);
2101
+ if (insight.tags.length > 0) {
2102
+ lines.push("");
2103
+ lines.push(`**Tags:** ${insight.tags.join(", ")}`);
2104
+ }
2105
+ lines.push("");
2106
+ }
2107
+ }
2108
+ lines.push("---");
2109
+ lines.push(`*Discussion ID: ${discussion.id}*`);
2110
+ return lines.join(`
2111
+ `);
2112
+ }
2113
+ ensureDir() {
2114
+ if (!import_node_fs3.existsSync(this.discussionsDir)) {
2115
+ import_node_fs3.mkdirSync(this.discussionsDir, { recursive: true });
2116
+ }
2117
+ }
2118
+ }
2119
+ var import_node_fs3, import_node_path5;
2120
+ var init_discussion_manager = __esm(() => {
2121
+ init_config();
2122
+ import_node_fs3 = require("node:fs");
2123
+ import_node_path5 = require("node:path");
2124
+ });
2125
+
1874
2126
  // src/core/prompt-builder.ts
1875
2127
  class PromptBuilder {
1876
2128
  projectPath;
@@ -1909,6 +2161,15 @@ ${knowledgeBase}
1909
2161
  These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
1910
2162
  ${learnings}
1911
2163
  </learnings>
2164
+ `;
2165
+ }
2166
+ const discussionInsights = this.getDiscussionInsightsContent();
2167
+ if (discussionInsights) {
2168
+ sections += `
2169
+ <discussion_insights>
2170
+ These are key decisions and insights from product discussions. Follow them to maintain product coherence:
2171
+ ${discussionInsights}
2172
+ </discussion_insights>
1912
2173
  `;
1913
2174
  }
1914
2175
  if (task.docs && task.docs.length > 0) {
@@ -1997,6 +2258,15 @@ ${knowledgeBase}
1997
2258
  These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
1998
2259
  ${learnings}
1999
2260
  </learnings>
2261
+ `;
2262
+ }
2263
+ const discussionInsights = this.getDiscussionInsightsContent();
2264
+ if (discussionInsights) {
2265
+ sections += `
2266
+ <discussion_insights>
2267
+ These are key decisions and insights from product discussions. Follow them to maintain product coherence:
2268
+ ${discussionInsights}
2269
+ </discussion_insights>
2000
2270
  `;
2001
2271
  }
2002
2272
  return `<direct_execution>
@@ -2011,9 +2281,9 @@ ${sections}
2011
2281
  }
2012
2282
  getProjectContext() {
2013
2283
  const contextPath = getLocusPath(this.projectPath, "contextFile");
2014
- if (import_node_fs3.existsSync(contextPath)) {
2284
+ if (import_node_fs4.existsSync(contextPath)) {
2015
2285
  try {
2016
- const context = import_node_fs3.readFileSync(contextPath, "utf-8");
2286
+ const context = import_node_fs4.readFileSync(contextPath, "utf-8");
2017
2287
  if (context.trim().length > 20) {
2018
2288
  return context;
2019
2289
  }
@@ -2024,10 +2294,10 @@ ${sections}
2024
2294
  return this.getFallbackContext() || null;
2025
2295
  }
2026
2296
  getFallbackContext() {
2027
- const readmePath = import_node_path5.join(this.projectPath, "README.md");
2028
- if (import_node_fs3.existsSync(readmePath)) {
2297
+ const readmePath = import_node_path6.join(this.projectPath, "README.md");
2298
+ if (import_node_fs4.existsSync(readmePath)) {
2029
2299
  try {
2030
- const content = import_node_fs3.readFileSync(readmePath, "utf-8");
2300
+ const content = import_node_fs4.readFileSync(readmePath, "utf-8");
2031
2301
  const limit = 1000;
2032
2302
  return content.slice(0, limit) + (content.length > limit ? `
2033
2303
  ...(truncated)...` : "");
@@ -2041,15 +2311,16 @@ ${sections}
2041
2311
  return `You have access to the following documentation directories for context:
2042
2312
  - Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
2043
2313
  - Documents: \`.locus/documents\` (synced from cloud)
2314
+ - Discussions: \`.locus/discussions\` (product discussion insights and decisions)
2044
2315
  If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
2045
2316
  }
2046
2317
  getLearningsContent() {
2047
2318
  const learningsPath = getLocusPath(this.projectPath, "learningsFile");
2048
- if (!import_node_fs3.existsSync(learningsPath)) {
2319
+ if (!import_node_fs4.existsSync(learningsPath)) {
2049
2320
  return null;
2050
2321
  }
2051
2322
  try {
2052
- const content = import_node_fs3.readFileSync(learningsPath, "utf-8");
2323
+ const content = import_node_fs4.readFileSync(learningsPath, "utf-8");
2053
2324
  const lines = content.split(`
2054
2325
  `).filter((l) => l.startsWith("- "));
2055
2326
  if (lines.length === 0) {
@@ -2061,6 +2332,53 @@ If you need more information about the project strategies, plans, or architectur
2061
2332
  return null;
2062
2333
  }
2063
2334
  }
2335
+ getDiscussionInsightsContent() {
2336
+ try {
2337
+ const manager = new DiscussionManager(this.projectPath);
2338
+ const insights = manager.getAllInsights();
2339
+ if (insights.length === 0) {
2340
+ return null;
2341
+ }
2342
+ const groups = {};
2343
+ for (const insight of insights) {
2344
+ const key = insight.type;
2345
+ if (!groups[key]) {
2346
+ groups[key] = [];
2347
+ }
2348
+ groups[key].push(insight);
2349
+ }
2350
+ const typeLabels = {
2351
+ decision: "Decisions",
2352
+ requirement: "Requirements",
2353
+ idea: "Ideas",
2354
+ concern: "Concerns",
2355
+ learning: "Learnings"
2356
+ };
2357
+ let output = "";
2358
+ for (const [type, label] of Object.entries(typeLabels)) {
2359
+ const items = groups[type];
2360
+ if (!items || items.length === 0)
2361
+ continue;
2362
+ output += `## ${label}
2363
+ `;
2364
+ for (const item of items) {
2365
+ output += `- [${item.title}]: ${item.content}
2366
+ `;
2367
+ }
2368
+ output += `
2369
+ `;
2370
+ }
2371
+ if (output.length === 0) {
2372
+ return null;
2373
+ }
2374
+ if (output.length > 2000) {
2375
+ output = `${output.slice(0, 1997)}...`;
2376
+ }
2377
+ return output.trimEnd();
2378
+ } catch {
2379
+ return null;
2380
+ }
2381
+ }
2064
2382
  roleToText(role) {
2065
2383
  if (!role) {
2066
2384
  return null;
@@ -2081,11 +2399,12 @@ If you need more information about the project strategies, plans, or architectur
2081
2399
  }
2082
2400
  }
2083
2401
  }
2084
- var import_node_fs3, import_node_path5, import_shared2;
2402
+ var import_node_fs4, import_node_path6, import_shared2;
2085
2403
  var init_prompt_builder = __esm(() => {
2404
+ init_discussion_manager();
2086
2405
  init_config();
2087
- import_node_fs3 = require("node:fs");
2088
- import_node_path5 = require("node:path");
2406
+ import_node_fs4 = require("node:fs");
2407
+ import_node_path6 = require("node:path");
2089
2408
  import_shared2 = require("@locusai/shared");
2090
2409
  });
2091
2410
 
@@ -2408,7 +2727,6 @@ Branch: \`${result.branch}\`` : "";
2408
2727
  this.log("All tasks done. Creating pull request...", "info");
2409
2728
  const prResult = this.gitWorkflow.createPullRequest(this.completedTaskList, this.taskSummaries);
2410
2729
  if (prResult.url) {
2411
- this.log(`PR created: ${prResult.url}`, "success");
2412
2730
  for (const task of this.completedTaskList) {
2413
2731
  try {
2414
2732
  await this.client.tasks.update(task.id, this.config.workspaceId, {
@@ -2458,7 +2776,9 @@ __export(exports_index_node, {
2458
2776
  plannedTasksToCreatePayloads: () => plannedTasksToCreatePayloads,
2459
2777
  parseSprintPlanFromAI: () => parseSprintPlanFromAI,
2460
2778
  parseJsonWithSchema: () => parseJsonWithSchema,
2779
+ isValidModelForProvider: () => isValidModelForProvider,
2461
2780
  getRemoteUrl: () => getRemoteUrl,
2781
+ getModelsForProvider: () => getModelsForProvider,
2462
2782
  getLocusPath: () => getLocusPath,
2463
2783
  getDefaultBranch: () => getDefaultBranch,
2464
2784
  getCurrentBranch: () => getCurrentBranch,
@@ -2467,6 +2787,8 @@ __export(exports_index_node, {
2467
2787
  detectRemoteProvider: () => detectRemoteProvider,
2468
2788
  createAiRunner: () => createAiRunner,
2469
2789
  c: () => c,
2790
+ buildSummaryPrompt: () => buildSummaryPrompt,
2791
+ buildFacilitatorPrompt: () => buildFacilitatorPrompt,
2470
2792
  WorkspacesModule: () => WorkspacesModule,
2471
2793
  TasksModule: () => TasksModule,
2472
2794
  TaskExecutor: () => TaskExecutor,
@@ -2477,6 +2799,7 @@ __export(exports_index_node, {
2477
2799
  PrService: () => PrService,
2478
2800
  PlanningMeeting: () => PlanningMeeting,
2479
2801
  PlanManager: () => PlanManager,
2802
+ PROVIDER_MODELS: () => PROVIDER_MODELS,
2480
2803
  PROVIDER: () => PROVIDER,
2481
2804
  OrganizationsModule: () => OrganizationsModule,
2482
2805
  LocusEvent: () => LocusEvent,
@@ -2494,6 +2817,11 @@ __export(exports_index_node, {
2494
2817
  ExecEventEmitter: () => ExecEventEmitter,
2495
2818
  DocumentFetcher: () => DocumentFetcher,
2496
2819
  DocsModule: () => DocsModule,
2820
+ DiscussionSchema: () => DiscussionSchema,
2821
+ DiscussionMessageSchema: () => DiscussionMessageSchema,
2822
+ DiscussionManager: () => DiscussionManager,
2823
+ DiscussionInsightSchema: () => DiscussionInsightSchema,
2824
+ DiscussionFacilitator: () => DiscussionFacilitator,
2497
2825
  DEFAULT_MODEL: () => DEFAULT_MODEL,
2498
2826
  ContextTracker: () => ContextTracker,
2499
2827
  CodexRunner: () => CodexRunner,
@@ -2501,6 +2829,8 @@ __export(exports_index_node, {
2501
2829
  CodebaseIndexer: () => CodebaseIndexer,
2502
2830
  ClaudeRunner: () => ClaudeRunner,
2503
2831
  CiModule: () => CiModule,
2832
+ CODEX_MODELS: () => CODEX_MODELS,
2833
+ CLAUDE_MODELS: () => CLAUDE_MODELS,
2504
2834
  AuthModule: () => AuthModule,
2505
2835
  AgentWorker: () => AgentWorker,
2506
2836
  AgentOrchestrator: () => AgentOrchestrator
@@ -2509,8 +2839,8 @@ module.exports = __toCommonJS(exports_index_node);
2509
2839
 
2510
2840
  // src/core/indexer.ts
2511
2841
  var import_node_crypto2 = require("node:crypto");
2512
- var import_node_fs4 = require("node:fs");
2513
- var import_node_path6 = require("node:path");
2842
+ var import_node_fs5 = require("node:fs");
2843
+ var import_node_path7 = require("node:path");
2514
2844
  var import_globby = require("globby");
2515
2845
 
2516
2846
  class CodebaseIndexer {
@@ -2519,7 +2849,7 @@ class CodebaseIndexer {
2519
2849
  fullReindexRatioThreshold = 0.2;
2520
2850
  constructor(projectPath) {
2521
2851
  this.projectPath = projectPath;
2522
- this.indexPath = import_node_path6.join(projectPath, ".locus", "codebase-index.json");
2852
+ this.indexPath = import_node_path7.join(projectPath, ".locus", "codebase-index.json");
2523
2853
  }
2524
2854
  async index(onProgress, treeSummarizer, force = false) {
2525
2855
  if (!treeSummarizer) {
@@ -2575,11 +2905,11 @@ class CodebaseIndexer {
2575
2905
  }
2576
2906
  }
2577
2907
  async getFileTree() {
2578
- const gitmodulesPath = import_node_path6.join(this.projectPath, ".gitmodules");
2908
+ const gitmodulesPath = import_node_path7.join(this.projectPath, ".gitmodules");
2579
2909
  const submoduleIgnores = [];
2580
- if (import_node_fs4.existsSync(gitmodulesPath)) {
2910
+ if (import_node_fs5.existsSync(gitmodulesPath)) {
2581
2911
  try {
2582
- const content = import_node_fs4.readFileSync(gitmodulesPath, "utf-8");
2912
+ const content = import_node_fs5.readFileSync(gitmodulesPath, "utf-8");
2583
2913
  const lines = content.split(`
2584
2914
  `);
2585
2915
  for (const line of lines) {
@@ -2635,9 +2965,9 @@ class CodebaseIndexer {
2635
2965
  });
2636
2966
  }
2637
2967
  loadIndex() {
2638
- if (import_node_fs4.existsSync(this.indexPath)) {
2968
+ if (import_node_fs5.existsSync(this.indexPath)) {
2639
2969
  try {
2640
- return JSON.parse(import_node_fs4.readFileSync(this.indexPath, "utf-8"));
2970
+ return JSON.parse(import_node_fs5.readFileSync(this.indexPath, "utf-8"));
2641
2971
  } catch {
2642
2972
  return null;
2643
2973
  }
@@ -2645,11 +2975,11 @@ class CodebaseIndexer {
2645
2975
  return null;
2646
2976
  }
2647
2977
  saveIndex(index) {
2648
- const dir = import_node_path6.dirname(this.indexPath);
2649
- if (!import_node_fs4.existsSync(dir)) {
2650
- import_node_fs4.mkdirSync(dir, { recursive: true });
2978
+ const dir = import_node_path7.dirname(this.indexPath);
2979
+ if (!import_node_fs5.existsSync(dir)) {
2980
+ import_node_fs5.mkdirSync(dir, { recursive: true });
2651
2981
  }
2652
- import_node_fs4.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
2982
+ import_node_fs5.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
2653
2983
  }
2654
2984
  cloneIndex(index) {
2655
2985
  return JSON.parse(JSON.stringify(index));
@@ -2665,7 +2995,7 @@ class CodebaseIndexer {
2665
2995
  }
2666
2996
  hashFile(filePath) {
2667
2997
  try {
2668
- const content = import_node_fs4.readFileSync(import_node_path6.join(this.projectPath, filePath), "utf-8");
2998
+ const content = import_node_fs5.readFileSync(import_node_path7.join(this.projectPath, filePath), "utf-8");
2669
2999
  return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
2670
3000
  } catch {
2671
3001
  return null;
@@ -2820,8 +3150,8 @@ Return ONLY valid JSON (no code fences, no markdown):
2820
3150
  }
2821
3151
  // src/agent/document-fetcher.ts
2822
3152
  init_config();
2823
- var import_node_fs5 = require("node:fs");
2824
- var import_node_path7 = require("node:path");
3153
+ var import_node_fs6 = require("node:fs");
3154
+ var import_node_path8 = require("node:path");
2825
3155
 
2826
3156
  class DocumentFetcher {
2827
3157
  deps;
@@ -2830,8 +3160,8 @@ class DocumentFetcher {
2830
3160
  }
2831
3161
  async fetch() {
2832
3162
  const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
2833
- if (!import_node_fs5.existsSync(documentsDir)) {
2834
- import_node_fs5.mkdirSync(documentsDir, { recursive: true });
3163
+ if (!import_node_fs6.existsSync(documentsDir)) {
3164
+ import_node_fs6.mkdirSync(documentsDir, { recursive: true });
2835
3165
  }
2836
3166
  try {
2837
3167
  const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
@@ -2844,14 +3174,14 @@ class DocumentFetcher {
2844
3174
  continue;
2845
3175
  }
2846
3176
  const groupName = groupMap.get(doc.groupId || "") || "General";
2847
- const groupDir = import_node_path7.join(documentsDir, groupName);
2848
- if (!import_node_fs5.existsSync(groupDir)) {
2849
- import_node_fs5.mkdirSync(groupDir, { recursive: true });
3177
+ const groupDir = import_node_path8.join(documentsDir, groupName);
3178
+ if (!import_node_fs6.existsSync(groupDir)) {
3179
+ import_node_fs6.mkdirSync(groupDir, { recursive: true });
2850
3180
  }
2851
3181
  const fileName = `${doc.title}.md`;
2852
- const filePath = import_node_path7.join(groupDir, fileName);
2853
- if (!import_node_fs5.existsSync(filePath) || import_node_fs5.readFileSync(filePath, "utf-8") !== doc.content) {
2854
- import_node_fs5.writeFileSync(filePath, doc.content || "");
3182
+ const filePath = import_node_path8.join(groupDir, fileName);
3183
+ if (!import_node_fs6.existsSync(filePath) || import_node_fs6.readFileSync(filePath, "utf-8") !== doc.content) {
3184
+ import_node_fs6.writeFileSync(filePath, doc.content || "");
2855
3185
  fetchedCount++;
2856
3186
  }
2857
3187
  }
@@ -3385,6 +3715,341 @@ init_prompt_builder();
3385
3715
  // src/index-node.ts
3386
3716
  init_prompt_builder();
3387
3717
 
3718
+ // src/discussion/agents/facilitator-prompt.ts
3719
+ function buildFacilitatorPrompt(input) {
3720
+ const {
3721
+ topic,
3722
+ projectContext,
3723
+ learnings,
3724
+ knowledgeBase,
3725
+ previousMessages,
3726
+ insights,
3727
+ isFirstMessage
3728
+ } = input;
3729
+ let sections = "";
3730
+ if (projectContext) {
3731
+ sections += `
3732
+ <project_context>
3733
+ ${projectContext}
3734
+ </project_context>
3735
+ `;
3736
+ }
3737
+ sections += `
3738
+ <knowledge_base>
3739
+ ${knowledgeBase}
3740
+ </knowledge_base>
3741
+ `;
3742
+ if (learnings) {
3743
+ sections += `
3744
+ <learnings>
3745
+ These are accumulated lessons from past work on this project. Use them to ask more informed questions:
3746
+ ${learnings}
3747
+ </learnings>
3748
+ `;
3749
+ }
3750
+ if (previousMessages.length > 0) {
3751
+ let history = "";
3752
+ for (const msg of previousMessages) {
3753
+ const role = msg.role === "user" ? "User" : "Facilitator";
3754
+ history += `[${role}]: ${msg.content}
3755
+
3756
+ `;
3757
+ }
3758
+ sections += `
3759
+ <conversation_history>
3760
+ ${history.trimEnd()}
3761
+ </conversation_history>
3762
+ `;
3763
+ }
3764
+ if (insights.length > 0) {
3765
+ let insightsText = "";
3766
+ for (const insight of insights) {
3767
+ insightsText += `- [${insight.type.toUpperCase()}] ${insight.title}: ${insight.content}
3768
+ `;
3769
+ }
3770
+ sections += `
3771
+ <extracted_insights>
3772
+ Insights identified so far in this discussion:
3773
+ ${insightsText.trimEnd()}
3774
+ </extracted_insights>
3775
+ `;
3776
+ }
3777
+ 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.`;
3778
+ return `<discussion_facilitator>
3779
+ You are a product strategy facilitator leading a structured discussion.
3780
+
3781
+ <topic>
3782
+ ${topic}
3783
+ </topic>
3784
+ ${sections}
3785
+ <role>
3786
+ You are an expert product strategy facilitator. Your job is to:
3787
+ 1. Ask probing, specific questions about the topic — never generic or surface-level
3788
+ 2. Build on previous answers to progressively deepen the conversation
3789
+ 3. Identify and extract key decisions, requirements, ideas, concerns, and learnings
3790
+ 4. Reference existing project context and learnings to ask informed questions
3791
+ 5. When the topic feels fully explored, suggest wrapping up with a summary
3792
+ </role>
3793
+
3794
+ <rules>
3795
+ - ${firstMessageInstruction}
3796
+ - Ask ONE focused question at a time. Do not overwhelm with multiple questions.
3797
+ - Be conversational but purposeful — every question should drive toward actionable insights.
3798
+ - When you identify an insight from the user's response, include it as a structured XML block in your response.
3799
+ - Insight blocks use this format within your response text:
3800
+
3801
+ <insight>
3802
+ {"type": "decision|requirement|idea|concern|learning", "title": "short title", "content": "detailed description", "tags": ["relevant", "tags"]}
3803
+ </insight>
3804
+
3805
+ - You may include multiple <insight> blocks if the user's response contains several distinct insights.
3806
+ - The insight blocks will be parsed and removed from the displayed response, so write your conversational text as if they are not there.
3807
+ - Types explained:
3808
+ - **decision**: A choice or direction that has been made or agreed upon
3809
+ - **requirement**: A specific need, constraint, or must-have
3810
+ - **idea**: A suggestion, proposal, or possibility to explore
3811
+ - **concern**: A risk, worry, or potential problem identified
3812
+ - **learning**: A realization, lesson, or important context discovered
3813
+ - Keep responses concise. Aim for 2-4 sentences of conversation plus any insight blocks.
3814
+ - If the user's responses indicate the topic is well-explored, suggest summarizing and wrapping up.
3815
+ </rules>
3816
+ </discussion_facilitator>`;
3817
+ }
3818
+ function buildSummaryPrompt(topic, messages, insights) {
3819
+ let history = "";
3820
+ for (const msg of messages) {
3821
+ const role = msg.role === "user" ? "User" : "Facilitator";
3822
+ history += `[${role}]: ${msg.content}
3823
+
3824
+ `;
3825
+ }
3826
+ let insightsText = "";
3827
+ if (insights.length > 0) {
3828
+ for (const insight of insights) {
3829
+ insightsText += `- [${insight.type.toUpperCase()}] **${insight.title}**: ${insight.content}`;
3830
+ if (insight.tags.length > 0) {
3831
+ insightsText += ` (tags: ${insight.tags.join(", ")})`;
3832
+ }
3833
+ insightsText += `
3834
+ `;
3835
+ }
3836
+ }
3837
+ return `<discussion_summary>
3838
+ Create a final summary of this product discussion.
3839
+
3840
+ <topic>
3841
+ ${topic}
3842
+ </topic>
3843
+
3844
+ <conversation>
3845
+ ${history.trimEnd()}
3846
+ </conversation>
3847
+
3848
+ ${insightsText ? `<insights>
3849
+ ${insightsText.trimEnd()}
3850
+ </insights>
3851
+ ` : ""}
3852
+ <rules>
3853
+ - Write a clear, structured summary of the entire discussion.
3854
+ - Organize by: Key Decisions, Requirements, Ideas to Explore, Concerns & Risks, and Learnings.
3855
+ - Only include sections that have relevant content — skip empty categories.
3856
+ - For each item, provide a brief but actionable description.
3857
+ - End with a "Next Steps" section listing concrete action items that emerged.
3858
+ - Be concise — this summary should be scannable and useful as a reference.
3859
+ - Do NOT include any <insight> XML blocks in the summary.
3860
+ </rules>
3861
+ </discussion_summary>`;
3862
+ }
3863
+ // src/discussion/discussion-facilitator.ts
3864
+ init_config();
3865
+ var import_node_fs7 = require("node:fs");
3866
+ class DiscussionFacilitator {
3867
+ projectPath;
3868
+ aiRunner;
3869
+ discussionManager;
3870
+ log;
3871
+ provider;
3872
+ model;
3873
+ constructor(config) {
3874
+ this.projectPath = config.projectPath;
3875
+ this.aiRunner = config.aiRunner;
3876
+ this.discussionManager = config.discussionManager;
3877
+ this.log = config.log ?? ((_msg) => {
3878
+ return;
3879
+ });
3880
+ this.provider = config.provider;
3881
+ this.model = config.model;
3882
+ }
3883
+ async startDiscussion(topic) {
3884
+ this.log("Starting new discussion...", "info");
3885
+ const discussion = this.discussionManager.create(topic, this.model, this.provider);
3886
+ const { projectContext, learnings, knowledgeBase } = this.buildContext();
3887
+ const prompt = buildFacilitatorPrompt({
3888
+ topic,
3889
+ projectContext,
3890
+ learnings,
3891
+ knowledgeBase,
3892
+ previousMessages: [],
3893
+ insights: [],
3894
+ isFirstMessage: true
3895
+ });
3896
+ const response = await this.aiRunner.run(prompt);
3897
+ const { cleanResponse } = this.parseInsights(response);
3898
+ this.discussionManager.addMessage(discussion.id, "assistant", cleanResponse);
3899
+ this.log("Discussion started", "success");
3900
+ const saved = this.discussionManager.load(discussion.id);
3901
+ if (!saved) {
3902
+ throw new Error(`Failed to load discussion after creation: ${discussion.id}`);
3903
+ }
3904
+ return {
3905
+ discussion: saved,
3906
+ message: cleanResponse
3907
+ };
3908
+ }
3909
+ async continueDiscussion(discussionId, userMessage) {
3910
+ const discussion = this.discussionManager.load(discussionId);
3911
+ if (!discussion) {
3912
+ throw new Error(`Discussion not found: ${discussionId}`);
3913
+ }
3914
+ const updated = this.discussionManager.addMessage(discussionId, "user", userMessage);
3915
+ const { projectContext, learnings, knowledgeBase } = this.buildContext();
3916
+ const prompt = buildFacilitatorPrompt({
3917
+ topic: updated.topic,
3918
+ projectContext,
3919
+ learnings,
3920
+ knowledgeBase,
3921
+ previousMessages: updated.messages,
3922
+ insights: updated.insights,
3923
+ isFirstMessage: false
3924
+ });
3925
+ const response = await this.aiRunner.run(prompt);
3926
+ const { cleanResponse, insights } = this.parseInsights(response);
3927
+ for (const insight of insights) {
3928
+ this.discussionManager.addInsight(discussionId, insight);
3929
+ }
3930
+ this.discussionManager.addMessage(discussionId, "assistant", cleanResponse);
3931
+ return { response: cleanResponse, insights };
3932
+ }
3933
+ async* continueDiscussionStream(discussionId, userMessage) {
3934
+ const discussion = this.discussionManager.load(discussionId);
3935
+ if (!discussion) {
3936
+ throw new Error(`Discussion not found: ${discussionId}`);
3937
+ }
3938
+ const updated = this.discussionManager.addMessage(discussionId, "user", userMessage);
3939
+ const { projectContext, learnings, knowledgeBase } = this.buildContext();
3940
+ const prompt = buildFacilitatorPrompt({
3941
+ topic: updated.topic,
3942
+ projectContext,
3943
+ learnings,
3944
+ knowledgeBase,
3945
+ previousMessages: updated.messages,
3946
+ insights: updated.insights,
3947
+ isFirstMessage: false
3948
+ });
3949
+ let fullResponse = "";
3950
+ const stream = this.aiRunner.runStream(prompt);
3951
+ for await (const chunk of stream) {
3952
+ yield chunk;
3953
+ if (chunk.type === "text_delta") {
3954
+ fullResponse += chunk.content;
3955
+ } else if (chunk.type === "result") {
3956
+ fullResponse = chunk.content;
3957
+ }
3958
+ }
3959
+ const { cleanResponse, insights } = this.parseInsights(fullResponse);
3960
+ for (const insight of insights) {
3961
+ this.discussionManager.addInsight(discussionId, insight);
3962
+ }
3963
+ this.discussionManager.addMessage(discussionId, "assistant", cleanResponse);
3964
+ return { response: cleanResponse, insights };
3965
+ }
3966
+ async summarizeDiscussion(discussionId) {
3967
+ const discussion = this.discussionManager.load(discussionId);
3968
+ if (!discussion) {
3969
+ throw new Error(`Discussion not found: ${discussionId}`);
3970
+ }
3971
+ this.log("Generating discussion summary...", "info");
3972
+ const prompt = buildSummaryPrompt(discussion.topic, discussion.messages, discussion.insights);
3973
+ const summary = await this.aiRunner.run(prompt);
3974
+ this.discussionManager.addMessage(discussionId, "assistant", summary);
3975
+ this.discussionManager.complete(discussionId);
3976
+ this.log("Discussion summarized and completed", "success");
3977
+ return summary;
3978
+ }
3979
+ parseInsights(response) {
3980
+ const insights = [];
3981
+ const insightRegex = /<insight>\s*([\s\S]*?)\s*<\/insight>/g;
3982
+ let match = insightRegex.exec(response);
3983
+ while (match !== null) {
3984
+ try {
3985
+ const parsed = JSON.parse(match[1]);
3986
+ const id = `ins-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
3987
+ insights.push({
3988
+ id,
3989
+ type: parsed.type,
3990
+ title: parsed.title,
3991
+ content: parsed.content,
3992
+ tags: parsed.tags ?? [],
3993
+ createdAt: new Date().toISOString()
3994
+ });
3995
+ } catch {}
3996
+ match = insightRegex.exec(response);
3997
+ }
3998
+ const cleanResponse = response.replace(/<insight>\s*[\s\S]*?\s*<\/insight>/g, "").replace(/\n{3,}/g, `
3999
+
4000
+ `).trim();
4001
+ return { cleanResponse, insights };
4002
+ }
4003
+ buildContext() {
4004
+ return {
4005
+ projectContext: this.getProjectContext(),
4006
+ learnings: this.getLearningsContent(),
4007
+ knowledgeBase: this.getKnowledgeBaseSection()
4008
+ };
4009
+ }
4010
+ getProjectContext() {
4011
+ const contextPath = getLocusPath(this.projectPath, "contextFile");
4012
+ if (import_node_fs7.existsSync(contextPath)) {
4013
+ try {
4014
+ const context = import_node_fs7.readFileSync(contextPath, "utf-8");
4015
+ if (context.trim().length > 20) {
4016
+ return context;
4017
+ }
4018
+ } catch {
4019
+ return null;
4020
+ }
4021
+ }
4022
+ return null;
4023
+ }
4024
+ getLearningsContent() {
4025
+ const learningsPath = getLocusPath(this.projectPath, "learningsFile");
4026
+ if (!import_node_fs7.existsSync(learningsPath)) {
4027
+ return null;
4028
+ }
4029
+ try {
4030
+ const content = import_node_fs7.readFileSync(learningsPath, "utf-8");
4031
+ const lines = content.split(`
4032
+ `).filter((l) => l.startsWith("- "));
4033
+ if (lines.length === 0) {
4034
+ return null;
4035
+ }
4036
+ return lines.join(`
4037
+ `);
4038
+ } catch {
4039
+ return null;
4040
+ }
4041
+ }
4042
+ getKnowledgeBaseSection() {
4043
+ return `You have access to the following documentation directories for context:
4044
+ - Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
4045
+ - Documents: \`.locus/documents\` (synced from cloud)
4046
+ If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
4047
+ }
4048
+ }
4049
+
4050
+ // src/discussion/index.ts
4051
+ init_discussion_manager();
4052
+ init_discussion_types();
3388
4053
  // src/exec/context-tracker.ts
3389
4054
  var REFERENCE_ALIASES = {
3390
4055
  plan: ["the plan", "sprint plan", "project plan", "implementation plan"],
@@ -3799,8 +4464,8 @@ class ExecEventEmitter {
3799
4464
  }
3800
4465
  // src/exec/history-manager.ts
3801
4466
  init_config();
3802
- var import_node_fs6 = require("node:fs");
3803
- var import_node_path8 = require("node:path");
4467
+ var import_node_fs8 = require("node:fs");
4468
+ var import_node_path9 = require("node:path");
3804
4469
  var DEFAULT_MAX_SESSIONS = 30;
3805
4470
  function generateSessionId2() {
3806
4471
  const timestamp = Date.now().toString(36);
@@ -3812,30 +4477,30 @@ class HistoryManager {
3812
4477
  historyDir;
3813
4478
  maxSessions;
3814
4479
  constructor(projectPath, options) {
3815
- this.historyDir = options?.historyDir ?? import_node_path8.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
4480
+ this.historyDir = options?.historyDir ?? import_node_path9.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
3816
4481
  this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
3817
4482
  this.ensureHistoryDir();
3818
4483
  }
3819
4484
  ensureHistoryDir() {
3820
- if (!import_node_fs6.existsSync(this.historyDir)) {
3821
- import_node_fs6.mkdirSync(this.historyDir, { recursive: true });
4485
+ if (!import_node_fs8.existsSync(this.historyDir)) {
4486
+ import_node_fs8.mkdirSync(this.historyDir, { recursive: true });
3822
4487
  }
3823
4488
  }
3824
4489
  getSessionPath(sessionId) {
3825
- return import_node_path8.join(this.historyDir, `${sessionId}.json`);
4490
+ return import_node_path9.join(this.historyDir, `${sessionId}.json`);
3826
4491
  }
3827
4492
  saveSession(session) {
3828
4493
  const filePath = this.getSessionPath(session.id);
3829
4494
  session.updatedAt = Date.now();
3830
- import_node_fs6.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
4495
+ import_node_fs8.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
3831
4496
  }
3832
4497
  loadSession(sessionId) {
3833
4498
  const filePath = this.getSessionPath(sessionId);
3834
- if (!import_node_fs6.existsSync(filePath)) {
4499
+ if (!import_node_fs8.existsSync(filePath)) {
3835
4500
  return null;
3836
4501
  }
3837
4502
  try {
3838
- const content = import_node_fs6.readFileSync(filePath, "utf-8");
4503
+ const content = import_node_fs8.readFileSync(filePath, "utf-8");
3839
4504
  return JSON.parse(content);
3840
4505
  } catch {
3841
4506
  return null;
@@ -3843,18 +4508,18 @@ class HistoryManager {
3843
4508
  }
3844
4509
  deleteSession(sessionId) {
3845
4510
  const filePath = this.getSessionPath(sessionId);
3846
- if (!import_node_fs6.existsSync(filePath)) {
4511
+ if (!import_node_fs8.existsSync(filePath)) {
3847
4512
  return false;
3848
4513
  }
3849
4514
  try {
3850
- import_node_fs6.rmSync(filePath);
4515
+ import_node_fs8.rmSync(filePath);
3851
4516
  return true;
3852
4517
  } catch {
3853
4518
  return false;
3854
4519
  }
3855
4520
  }
3856
4521
  listSessions(options) {
3857
- const files = import_node_fs6.readdirSync(this.historyDir);
4522
+ const files = import_node_fs8.readdirSync(this.historyDir);
3858
4523
  let sessions = [];
3859
4524
  for (const file of files) {
3860
4525
  if (file.endsWith(".json")) {
@@ -3927,11 +4592,11 @@ class HistoryManager {
3927
4592
  return deleted;
3928
4593
  }
3929
4594
  getSessionCount() {
3930
- const files = import_node_fs6.readdirSync(this.historyDir);
4595
+ const files = import_node_fs8.readdirSync(this.historyDir);
3931
4596
  return files.filter((f) => f.endsWith(".json")).length;
3932
4597
  }
3933
4598
  sessionExists(sessionId) {
3934
- return import_node_fs6.existsSync(this.getSessionPath(sessionId));
4599
+ return import_node_fs8.existsSync(this.getSessionPath(sessionId));
3935
4600
  }
3936
4601
  findSessionByPartialId(partialId) {
3937
4602
  const sessions = this.listSessions();
@@ -3945,12 +4610,12 @@ class HistoryManager {
3945
4610
  return this.historyDir;
3946
4611
  }
3947
4612
  clearAllSessions() {
3948
- const files = import_node_fs6.readdirSync(this.historyDir);
4613
+ const files = import_node_fs8.readdirSync(this.historyDir);
3949
4614
  let deleted = 0;
3950
4615
  for (const file of files) {
3951
4616
  if (file.endsWith(".json")) {
3952
4617
  try {
3953
- import_node_fs6.rmSync(import_node_path8.join(this.historyDir, file));
4618
+ import_node_fs8.rmSync(import_node_path9.join(this.historyDir, file));
3954
4619
  deleted++;
3955
4620
  } catch {}
3956
4621
  }
@@ -4226,8 +4891,8 @@ init_src();
4226
4891
  init_colors();
4227
4892
  init_resolve_bin();
4228
4893
  var import_node_child_process7 = require("node:child_process");
4229
- var import_node_fs7 = require("node:fs");
4230
- var import_node_path9 = require("node:path");
4894
+ var import_node_fs9 = require("node:fs");
4895
+ var import_node_path10 = require("node:path");
4231
4896
  var import_node_url = require("node:url");
4232
4897
  var import_shared4 = require("@locusai/shared");
4233
4898
  var import_events4 = require("events");
@@ -4495,14 +5160,14 @@ ${agentId} finished (exit code: ${code})`);
4495
5160
  }
4496
5161
  resolveWorkerPath() {
4497
5162
  const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/index.ts");
4498
- const currentModuleDir = import_node_path9.dirname(currentModulePath);
5163
+ const currentModuleDir = import_node_path10.dirname(currentModulePath);
4499
5164
  const potentialPaths = [
4500
- import_node_path9.join(currentModuleDir, "..", "agent", "worker.js"),
4501
- import_node_path9.join(currentModuleDir, "agent", "worker.js"),
4502
- import_node_path9.join(currentModuleDir, "worker.js"),
4503
- import_node_path9.join(currentModuleDir, "..", "agent", "worker.ts")
5165
+ import_node_path10.join(currentModuleDir, "..", "agent", "worker.js"),
5166
+ import_node_path10.join(currentModuleDir, "agent", "worker.js"),
5167
+ import_node_path10.join(currentModuleDir, "worker.js"),
5168
+ import_node_path10.join(currentModuleDir, "..", "agent", "worker.ts")
4504
5169
  ];
4505
- return potentialPaths.find((p) => import_node_fs7.existsSync(p));
5170
+ return potentialPaths.find((p) => import_node_fs9.existsSync(p));
4506
5171
  }
4507
5172
  }
4508
5173
  function killProcessTree(proc) {
@@ -4521,12 +5186,12 @@ function sleep(ms) {
4521
5186
  }
4522
5187
  // src/planning/plan-manager.ts
4523
5188
  init_config();
4524
- var import_node_fs8 = require("node:fs");
4525
- var import_node_path10 = require("node:path");
5189
+ var import_node_fs10 = require("node:fs");
5190
+ var import_node_path11 = require("node:path");
4526
5191
 
4527
5192
  // src/planning/sprint-plan.ts
4528
5193
  var import_shared5 = require("@locusai/shared");
4529
- var import_zod = require("zod");
5194
+ var import_zod2 = require("zod");
4530
5195
 
4531
5196
  // src/utils/structured-output.ts
4532
5197
  function parseJsonWithSchema(raw, schema) {
@@ -4551,26 +5216,26 @@ Parsed JSON preview: ${JSON.stringify(parsed).slice(0, 300)}`);
4551
5216
  }
4552
5217
 
4553
5218
  // src/planning/sprint-plan.ts
4554
- var PlannedTaskSchema = import_zod.z.object({
4555
- title: import_zod.z.string().default("Untitled Task"),
4556
- description: import_zod.z.string().default(""),
4557
- assigneeRole: import_zod.z.enum(["BACKEND", "FRONTEND", "QA", "PM", "DESIGN"]).default("BACKEND"),
4558
- priority: import_zod.z.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]).default("MEDIUM"),
4559
- complexity: import_zod.z.number().min(1).max(5).default(3),
4560
- acceptanceCriteria: import_zod.z.array(import_zod.z.string()).default([]),
4561
- labels: import_zod.z.array(import_zod.z.string()).default([])
5219
+ var PlannedTaskSchema = import_zod2.z.object({
5220
+ title: import_zod2.z.string().default("Untitled Task"),
5221
+ description: import_zod2.z.string().default(""),
5222
+ assigneeRole: import_zod2.z.enum(["BACKEND", "FRONTEND", "QA", "PM", "DESIGN"]).default("BACKEND"),
5223
+ priority: import_zod2.z.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]).default("MEDIUM"),
5224
+ complexity: import_zod2.z.number().min(1).max(5).default(3),
5225
+ acceptanceCriteria: import_zod2.z.array(import_zod2.z.string()).default([]),
5226
+ labels: import_zod2.z.array(import_zod2.z.string()).default([])
4562
5227
  });
4563
- var SprintPlanRiskSchema = import_zod.z.object({
4564
- description: import_zod.z.string().default(""),
4565
- mitigation: import_zod.z.string().default(""),
4566
- severity: import_zod.z.enum(["low", "medium", "high"]).default("medium")
5228
+ var SprintPlanRiskSchema = import_zod2.z.object({
5229
+ description: import_zod2.z.string().default(""),
5230
+ mitigation: import_zod2.z.string().default(""),
5231
+ severity: import_zod2.z.enum(["low", "medium", "high"]).default("medium")
4567
5232
  });
4568
- var PlannerOutputSchema = import_zod.z.object({
4569
- name: import_zod.z.string().default("Unnamed Sprint"),
4570
- goal: import_zod.z.string().default(""),
4571
- estimatedDays: import_zod.z.number().default(1),
4572
- tasks: import_zod.z.array(PlannedTaskSchema).default([]),
4573
- risks: import_zod.z.array(SprintPlanRiskSchema).default([])
5233
+ var PlannerOutputSchema = import_zod2.z.object({
5234
+ name: import_zod2.z.string().default("Unnamed Sprint"),
5235
+ goal: import_zod2.z.string().default(""),
5236
+ estimatedDays: import_zod2.z.number().default(1),
5237
+ tasks: import_zod2.z.array(PlannedTaskSchema).default([]),
5238
+ risks: import_zod2.z.array(SprintPlanRiskSchema).default([])
4574
5239
  });
4575
5240
  var SprintPlanAIOutputSchema = PlannerOutputSchema;
4576
5241
  function sprintPlanToMarkdown(plan) {
@@ -4687,19 +5352,19 @@ class PlanManager {
4687
5352
  save(plan) {
4688
5353
  this.ensurePlansDir();
4689
5354
  const slug = this.slugify(plan.name);
4690
- const jsonPath = import_node_path10.join(this.plansDir, `${slug}.json`);
4691
- const mdPath = import_node_path10.join(this.plansDir, `sprint-${slug}.md`);
4692
- import_node_fs8.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
4693
- import_node_fs8.writeFileSync(mdPath, sprintPlanToMarkdown(plan), "utf-8");
5355
+ const jsonPath = import_node_path11.join(this.plansDir, `${slug}.json`);
5356
+ const mdPath = import_node_path11.join(this.plansDir, `sprint-${slug}.md`);
5357
+ import_node_fs10.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
5358
+ import_node_fs10.writeFileSync(mdPath, sprintPlanToMarkdown(plan), "utf-8");
4694
5359
  return plan.id;
4695
5360
  }
4696
5361
  load(idOrSlug) {
4697
5362
  this.ensurePlansDir();
4698
- const files = import_node_fs8.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
5363
+ const files = import_node_fs10.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
4699
5364
  for (const file of files) {
4700
- const filePath = import_node_path10.join(this.plansDir, file);
5365
+ const filePath = import_node_path11.join(this.plansDir, file);
4701
5366
  try {
4702
- const plan = JSON.parse(import_node_fs8.readFileSync(filePath, "utf-8"));
5367
+ const plan = JSON.parse(import_node_fs10.readFileSync(filePath, "utf-8"));
4703
5368
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
4704
5369
  return plan;
4705
5370
  }
@@ -4709,11 +5374,11 @@ class PlanManager {
4709
5374
  }
4710
5375
  list(status) {
4711
5376
  this.ensurePlansDir();
4712
- const files = import_node_fs8.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
5377
+ const files = import_node_fs10.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
4713
5378
  const plans = [];
4714
5379
  for (const file of files) {
4715
5380
  try {
4716
- const plan = JSON.parse(import_node_fs8.readFileSync(import_node_path10.join(this.plansDir, file), "utf-8"));
5381
+ const plan = JSON.parse(import_node_fs10.readFileSync(import_node_path11.join(this.plansDir, file), "utf-8"));
4717
5382
  if (!status || plan.status === status) {
4718
5383
  plans.push(plan);
4719
5384
  }
@@ -4770,18 +5435,18 @@ class PlanManager {
4770
5435
  }
4771
5436
  delete(idOrSlug) {
4772
5437
  this.ensurePlansDir();
4773
- const files = import_node_fs8.readdirSync(this.plansDir);
5438
+ const files = import_node_fs10.readdirSync(this.plansDir);
4774
5439
  for (const file of files) {
4775
- const filePath = import_node_path10.join(this.plansDir, file);
5440
+ const filePath = import_node_path11.join(this.plansDir, file);
4776
5441
  if (!file.endsWith(".json"))
4777
5442
  continue;
4778
5443
  try {
4779
- const plan = JSON.parse(import_node_fs8.readFileSync(filePath, "utf-8"));
5444
+ const plan = JSON.parse(import_node_fs10.readFileSync(filePath, "utf-8"));
4780
5445
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
4781
- import_node_fs8.unlinkSync(filePath);
4782
- const mdPath = import_node_path10.join(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
4783
- if (import_node_fs8.existsSync(mdPath)) {
4784
- import_node_fs8.unlinkSync(mdPath);
5446
+ import_node_fs10.unlinkSync(filePath);
5447
+ const mdPath = import_node_path11.join(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
5448
+ if (import_node_fs10.existsSync(mdPath)) {
5449
+ import_node_fs10.unlinkSync(mdPath);
4785
5450
  }
4786
5451
  return;
4787
5452
  }
@@ -4795,8 +5460,8 @@ class PlanManager {
4795
5460
  return sprintPlanToMarkdown(plan);
4796
5461
  }
4797
5462
  ensurePlansDir() {
4798
- if (!import_node_fs8.existsSync(this.plansDir)) {
4799
- import_node_fs8.mkdirSync(this.plansDir, { recursive: true });
5463
+ if (!import_node_fs10.existsSync(this.plansDir)) {
5464
+ import_node_fs10.mkdirSync(this.plansDir, { recursive: true });
4800
5465
  }
4801
5466
  }
4802
5467
  slugify(name) {
@@ -4805,8 +5470,8 @@ class PlanManager {
4805
5470
  }
4806
5471
  // src/planning/planning-meeting.ts
4807
5472
  init_config();
4808
- var import_node_fs9 = require("node:fs");
4809
- var import_node_path11 = require("node:path");
5473
+ var import_node_fs11 = require("node:fs");
5474
+ var import_node_path12 = require("node:path");
4810
5475
 
4811
5476
  // src/planning/agents/planner.ts
4812
5477
  function buildPlannerPrompt(input) {
@@ -4892,8 +5557,8 @@ class PlanningMeeting {
4892
5557
  async run(directive, feedback) {
4893
5558
  this.log("Planning sprint...", "info");
4894
5559
  const plansDir = getLocusPath(this.projectPath, "plansDir");
4895
- if (!import_node_fs9.existsSync(plansDir)) {
4896
- import_node_fs9.mkdirSync(plansDir, { recursive: true });
5560
+ if (!import_node_fs11.existsSync(plansDir)) {
5561
+ import_node_fs11.mkdirSync(plansDir, { recursive: true });
4897
5562
  }
4898
5563
  const ts = Date.now();
4899
5564
  const planId = `plan-${ts}`;
@@ -4907,11 +5572,11 @@ class PlanningMeeting {
4907
5572
  });
4908
5573
  const response = await this.aiRunner.run(prompt);
4909
5574
  this.log("Planning meeting complete.", "success");
4910
- const expectedPath = import_node_path11.join(plansDir, `${fileName}.json`);
5575
+ const expectedPath = import_node_path12.join(plansDir, `${fileName}.json`);
4911
5576
  let plan = null;
4912
- if (import_node_fs9.existsSync(expectedPath)) {
5577
+ if (import_node_fs11.existsSync(expectedPath)) {
4913
5578
  try {
4914
- plan = JSON.parse(import_node_fs9.readFileSync(expectedPath, "utf-8"));
5579
+ plan = JSON.parse(import_node_fs11.readFileSync(expectedPath, "utf-8"));
4915
5580
  } catch {}
4916
5581
  }
4917
5582
  if (!plan) {