@locusai/sdk 0.12.0 → 0.13.0

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 (44) hide show
  1. package/dist/agent/codebase-indexer-service.d.ts.map +1 -1
  2. package/dist/agent/reviewer-worker.d.ts +0 -1
  3. package/dist/agent/reviewer-worker.d.ts.map +1 -1
  4. package/dist/agent/worker.d.ts +0 -2
  5. package/dist/agent/worker.d.ts.map +1 -1
  6. package/dist/agent/worker.js +135 -331
  7. package/dist/ai/claude-runner.d.ts +1 -0
  8. package/dist/ai/claude-runner.d.ts.map +1 -1
  9. package/dist/ai/codex-runner.d.ts +1 -1
  10. package/dist/ai/codex-runner.d.ts.map +1 -1
  11. package/dist/ai/factory.d.ts +2 -0
  12. package/dist/ai/factory.d.ts.map +1 -1
  13. package/dist/core/config.d.ts +2 -4
  14. package/dist/core/config.d.ts.map +1 -1
  15. package/dist/core/prompt-builder.d.ts +4 -5
  16. package/dist/core/prompt-builder.d.ts.map +1 -1
  17. package/dist/index-node.d.ts +1 -1
  18. package/dist/index-node.d.ts.map +1 -1
  19. package/dist/index-node.js +396 -721
  20. package/dist/planning/agents/cross-task-reviewer.d.ts +0 -14
  21. package/dist/planning/agents/cross-task-reviewer.d.ts.map +1 -1
  22. package/dist/planning/agents/planner.d.ts +11 -5
  23. package/dist/planning/agents/planner.d.ts.map +1 -1
  24. package/dist/planning/index.d.ts +1 -1
  25. package/dist/planning/index.d.ts.map +1 -1
  26. package/dist/planning/plan-manager.d.ts +0 -1
  27. package/dist/planning/plan-manager.d.ts.map +1 -1
  28. package/dist/planning/planning-meeting.d.ts +11 -12
  29. package/dist/planning/planning-meeting.d.ts.map +1 -1
  30. package/dist/planning/sprint-plan.d.ts +7 -3
  31. package/dist/planning/sprint-plan.d.ts.map +1 -1
  32. package/dist/utils/json-extractor.d.ts +6 -2
  33. package/dist/utils/json-extractor.d.ts.map +1 -1
  34. package/dist/utils/structured-output.d.ts +14 -0
  35. package/dist/utils/structured-output.d.ts.map +1 -0
  36. package/package.json +5 -4
  37. package/dist/project/knowledge-base.d.ts +0 -24
  38. package/dist/project/knowledge-base.d.ts.map +0 -1
  39. package/dist/worktree/index.d.ts +0 -2
  40. package/dist/worktree/index.d.ts.map +0 -1
  41. package/dist/worktree/worktree-config.d.ts +0 -2
  42. package/dist/worktree/worktree-config.d.ts.map +0 -1
  43. package/dist/worktree/worktree-manager.d.ts +0 -2
  44. package/dist/worktree/worktree-manager.d.ts.map +0 -1
@@ -522,9 +522,6 @@ var init_src = __esm(() => {
522
522
 
523
523
  // src/core/config.ts
524
524
  function getLocusPath(projectPath, fileName) {
525
- if (fileName === "projectContextFile" || fileName === "projectProgressFile") {
526
- return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.projectDir, LOCUS_CONFIG[fileName]);
527
- }
528
525
  return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
529
526
  }
530
527
  function getAgentArtifactsPath(projectPath, agentId) {
@@ -552,14 +549,12 @@ var init_config = __esm(() => {
552
549
  settingsFile: "settings.json",
553
550
  indexFile: "codebase-index.json",
554
551
  contextFile: "LOCUS.md",
552
+ learningsFile: "LEARNINGS.md",
555
553
  artifactsDir: "artifacts",
556
554
  documentsDir: "documents",
557
555
  sessionsDir: "sessions",
558
556
  reviewsDir: "reviews",
559
- plansDir: "plans",
560
- projectDir: "project",
561
- projectContextFile: "context.md",
562
- projectProgressFile: "progress.md"
557
+ plansDir: "plans"
563
558
  };
564
559
  LOCUS_GITIGNORE_PATTERNS = [
565
560
  "# Locus AI - Session data (user-specific, can grow large)",
@@ -577,11 +572,8 @@ var init_config = __esm(() => {
577
572
  "# Locus AI - Settings (contains API key, telegram config, etc.)",
578
573
  ".locus/settings.json",
579
574
  "",
580
- "# Locus AI - Configuration (contains project context, progress, etc.)",
581
- ".locus/config.json",
582
- "",
583
- "# Locus AI - Project progress (contains project progress, etc.)",
584
- ".locus/project/progress.md"
575
+ "# Locus AI - Configuration (contains project context, etc.)",
576
+ ".locus/config.json"
585
577
  ];
586
578
  });
587
579
 
@@ -820,17 +812,22 @@ class ClaudeRunner {
820
812
  });
821
813
  });
822
814
  }
823
- async* runStream(prompt) {
815
+ buildCliArgs() {
824
816
  const args = [
825
- "--dangerously-skip-permissions",
826
817
  "--print",
827
- "--verbose",
828
818
  "--output-format",
829
819
  "stream-json",
820
+ "--verbose",
821
+ "--dangerously-skip-permissions",
822
+ "--no-session-persistence",
830
823
  "--include-partial-messages",
831
824
  "--model",
832
825
  this.model
833
826
  ];
827
+ return args;
828
+ }
829
+ async* runStream(prompt) {
830
+ const args = this.buildCliArgs();
834
831
  const env = getAugmentedEnv({
835
832
  FORCE_COLOR: "1",
836
833
  TERM: "xterm-256color"
@@ -1070,16 +1067,7 @@ class ClaudeRunner {
1070
1067
  }
1071
1068
  executeRun(prompt) {
1072
1069
  return new Promise((resolve2, reject) => {
1073
- const args = [
1074
- "--dangerously-skip-permissions",
1075
- "--print",
1076
- "--verbose",
1077
- "--output-format",
1078
- "stream-json",
1079
- "--include-partial-messages",
1080
- "--model",
1081
- this.model
1082
- ];
1070
+ const args = this.buildCliArgs();
1083
1071
  const env = getAugmentedEnv({
1084
1072
  FORCE_COLOR: "1",
1085
1073
  TERM: "xterm-256color"
@@ -1200,7 +1188,7 @@ class CodexRunner {
1200
1188
  eventEmitter;
1201
1189
  currentToolName;
1202
1190
  timeoutMs;
1203
- constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log, timeoutMs, reasoningEffort) {
1191
+ constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log, reasoningEffort, timeoutMs) {
1204
1192
  this.projectPath = projectPath;
1205
1193
  this.model = model;
1206
1194
  this.log = log;
@@ -1531,7 +1519,7 @@ function createAiRunner(provider, config) {
1531
1519
  const model = config.model ?? DEFAULT_MODEL[resolvedProvider];
1532
1520
  switch (resolvedProvider) {
1533
1521
  case PROVIDER.CODEX:
1534
- return new CodexRunner(config.projectPath, model, config.log, config.timeoutMs, config.reasoningEffort ?? "high");
1522
+ return new CodexRunner(config.projectPath, model, config.log, config.reasoningEffort ?? "high", config.timeoutMs);
1535
1523
  default:
1536
1524
  return new ClaudeRunner(config.projectPath, model, config.log, config.timeoutMs);
1537
1525
  }
@@ -1638,102 +1626,6 @@ var init_git_utils = __esm(() => {
1638
1626
  import_node_child_process3 = require("node:child_process");
1639
1627
  });
1640
1628
 
1641
- // src/project/knowledge-base.ts
1642
- class KnowledgeBase {
1643
- contextPath;
1644
- progressPath;
1645
- constructor(projectPath) {
1646
- this.contextPath = getLocusPath(projectPath, "projectContextFile");
1647
- this.progressPath = getLocusPath(projectPath, "projectProgressFile");
1648
- }
1649
- readContext() {
1650
- if (!import_node_fs3.existsSync(this.contextPath)) {
1651
- return "";
1652
- }
1653
- return import_node_fs3.readFileSync(this.contextPath, "utf-8");
1654
- }
1655
- readProgress() {
1656
- if (!import_node_fs3.existsSync(this.progressPath)) {
1657
- return "";
1658
- }
1659
- return import_node_fs3.readFileSync(this.progressPath, "utf-8");
1660
- }
1661
- updateContext(content) {
1662
- this.ensureDir(this.contextPath);
1663
- import_node_fs3.writeFileSync(this.contextPath, content);
1664
- }
1665
- updateProgress(entry) {
1666
- this.ensureDir(this.progressPath);
1667
- const existing = this.readProgress();
1668
- const timestamp = (entry.timestamp ?? new Date).toISOString();
1669
- const label = entry.role === "user" ? "User" : "Assistant";
1670
- const line = `**${label}** (${timestamp}):
1671
- ${entry.content}`;
1672
- const updated = existing ? `${existing}
1673
-
1674
- ---
1675
-
1676
- ${line}` : `# Conversation History
1677
-
1678
- ${line}`;
1679
- import_node_fs3.writeFileSync(this.progressPath, updated);
1680
- }
1681
- getFullContext() {
1682
- const context = this.readContext();
1683
- const parts = [];
1684
- if (context.trim()) {
1685
- parts.push(context.trim());
1686
- }
1687
- return parts.join(`
1688
-
1689
- ---
1690
-
1691
- `);
1692
- }
1693
- initialize(info) {
1694
- this.ensureDir(this.contextPath);
1695
- this.ensureDir(this.progressPath);
1696
- const techStackList = info.techStack.map((t) => `- ${t}`).join(`
1697
- `);
1698
- const contextContent = `# Project: ${info.name}
1699
-
1700
- ## Mission
1701
- ${info.mission}
1702
-
1703
- ## Tech Stack
1704
- ${techStackList}
1705
-
1706
- ## Architecture
1707
- <!-- Describe your high-level architecture here -->
1708
-
1709
- ## Key Decisions
1710
- <!-- Document important technical decisions and their rationale -->
1711
-
1712
- ## Feature Areas
1713
- <!-- List your main feature areas and their status -->
1714
- `;
1715
- const progressContent = `# Conversation History
1716
- `;
1717
- import_node_fs3.writeFileSync(this.contextPath, contextContent);
1718
- import_node_fs3.writeFileSync(this.progressPath, progressContent);
1719
- }
1720
- get exists() {
1721
- return import_node_fs3.existsSync(this.contextPath) || import_node_fs3.existsSync(this.progressPath);
1722
- }
1723
- ensureDir(filePath) {
1724
- const dir = import_node_path5.dirname(filePath);
1725
- if (!import_node_fs3.existsSync(dir)) {
1726
- import_node_fs3.mkdirSync(dir, { recursive: true });
1727
- }
1728
- }
1729
- }
1730
- var import_node_fs3, import_node_path5;
1731
- var init_knowledge_base = __esm(() => {
1732
- init_config();
1733
- import_node_fs3 = require("node:fs");
1734
- import_node_path5 = require("node:path");
1735
- });
1736
-
1737
1629
  // src/agent/git-workflow.ts
1738
1630
  class GitWorkflow {
1739
1631
  config;
@@ -1985,223 +1877,157 @@ class PromptBuilder {
1985
1877
  constructor(projectPath) {
1986
1878
  this.projectPath = projectPath;
1987
1879
  }
1988
- async build(task, options = {}) {
1989
- let prompt = `# Task: ${task.title}
1990
-
1991
- `;
1880
+ async build(task) {
1992
1881
  const roleText = this.roleToText(task.assigneeRole);
1882
+ const description = task.description || "No description provided.";
1883
+ const context = this.getProjectContext();
1884
+ const learnings = this.getLearningsContent();
1885
+ const knowledgeBase = this.getKnowledgeBaseSection();
1886
+ let sections = "";
1993
1887
  if (roleText) {
1994
- prompt += `## Role
1888
+ sections += `
1889
+ <role>
1995
1890
  You are acting as a ${roleText}.
1996
-
1891
+ </role>
1997
1892
  `;
1998
1893
  }
1999
- prompt += `## Description
2000
- ${task.description || "No description provided."}
2001
-
2002
- `;
2003
- const projectConfig = this.getProjectConfig();
2004
- if (projectConfig) {
2005
- prompt += `## Project Metadata
2006
- `;
2007
- prompt += `- Version: ${projectConfig.version || "Unknown"}
2008
- `;
2009
- prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
2010
-
2011
- `;
2012
- }
2013
- let serverContext = null;
2014
- if (options.taskContext) {
2015
- try {
2016
- serverContext = JSON.parse(options.taskContext);
2017
- } catch {
2018
- serverContext = { context: options.taskContext };
2019
- }
2020
- }
2021
- const contextPath = getLocusPath(this.projectPath, "contextFile");
2022
- let hasLocalContext = false;
2023
- if (import_node_fs4.existsSync(contextPath)) {
2024
- try {
2025
- const context = import_node_fs4.readFileSync(contextPath, "utf-8");
2026
- if (context.trim().length > 20) {
2027
- prompt += `## Project Context (Local)
1894
+ if (context) {
1895
+ sections += `
1896
+ <project_context>
2028
1897
  ${context}
2029
-
1898
+ </project_context>
2030
1899
  `;
2031
- hasLocalContext = true;
2032
- }
2033
- } catch (err) {
2034
- console.warn(`Warning: Could not read context file: ${err}`);
2035
- }
2036
1900
  }
2037
- if (!hasLocalContext) {
2038
- const fallback = this.getFallbackContext();
2039
- if (fallback) {
2040
- prompt += `## Project Context (README Fallback)
2041
- ${fallback}
2042
-
1901
+ sections += `
1902
+ <knowledge_base>
1903
+ ${knowledgeBase}
1904
+ </knowledge_base>
2043
1905
  `;
2044
- }
2045
- }
2046
- if (serverContext) {
2047
- prompt += `## Project Context (Server)
2048
- `;
2049
- const project = serverContext.project;
2050
- if (project) {
2051
- prompt += `- Project: ${project.name || "Unknown"}
2052
- `;
2053
- if (!hasLocalContext && project.techStack?.length) {
2054
- prompt += `- Tech Stack: ${project.techStack.join(", ")}
2055
- `;
2056
- }
2057
- }
2058
- if (serverContext.context) {
2059
- prompt += `
2060
- ${serverContext.context}
2061
- `;
2062
- }
2063
- prompt += `
1906
+ if (learnings) {
1907
+ sections += `
1908
+ <learnings>
1909
+ These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
1910
+ ${learnings}
1911
+ </learnings>
2064
1912
  `;
2065
1913
  }
2066
- prompt += this.getProjectStructure();
2067
- prompt += `## Project Knowledge Base
2068
- `;
2069
- prompt += `You have access to the following documentation directories for context:
2070
- `;
2071
- prompt += `- Artifacts: \`.locus/artifacts\`
2072
- `;
2073
- prompt += `- Documents: \`.locus/documents\`
2074
- `;
2075
- prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
2076
-
2077
- `;
2078
1914
  if (task.docs && task.docs.length > 0) {
2079
- prompt += `## Attached Documents (Summarized)
2080
- `;
2081
- prompt += `> Full content available on server. Rely on Task Description for specific requirements.
2082
-
2083
- `;
1915
+ let docsContent = "";
2084
1916
  for (const doc of task.docs) {
2085
1917
  const content = doc.content || "";
2086
1918
  const limit = 800;
2087
1919
  const preview = content.slice(0, limit);
2088
1920
  const isTruncated = content.length > limit;
2089
- prompt += `### Doc: ${doc.title}
1921
+ docsContent += `### ${doc.title}
2090
1922
  ${preview}${isTruncated ? `
2091
1923
  ...(truncated)...` : ""}
2092
1924
 
2093
1925
  `;
2094
1926
  }
1927
+ sections += `
1928
+ <documents>
1929
+ ${docsContent.trimEnd()}
1930
+ </documents>
1931
+ `;
2095
1932
  }
2096
1933
  if (task.acceptanceChecklist && task.acceptanceChecklist.length > 0) {
2097
- prompt += `## Acceptance Criteria
2098
- `;
1934
+ let criteria = "";
2099
1935
  for (const item of task.acceptanceChecklist) {
2100
- prompt += `- ${item.done ? "[x]" : "[ ]"} ${item.text}
1936
+ criteria += `- ${item.done ? "[x]" : "[ ]"} ${item.text}
2101
1937
  `;
2102
1938
  }
2103
- prompt += `
1939
+ sections += `
1940
+ <acceptance_criteria>
1941
+ ${criteria.trimEnd()}
1942
+ </acceptance_criteria>
2104
1943
  `;
2105
1944
  }
2106
1945
  if (task.comments && task.comments.length > 0) {
2107
1946
  const filteredComments = task.comments.filter((comment) => comment.author !== "system");
2108
1947
  const comments = filteredComments.slice(0, 3);
2109
- prompt += `## Task Comments & Feedback
1948
+ if (comments.length > 0) {
1949
+ let commentsContent = "";
1950
+ for (const comment of comments) {
1951
+ const date = new Date(comment.createdAt).toLocaleString();
1952
+ commentsContent += `- ${comment.author} (${date}): ${comment.text}
2110
1953
  `;
2111
- for (const comment of comments) {
2112
- const date = new Date(comment.createdAt).toLocaleString();
2113
- prompt += `- ${comment.author} (${date}): ${comment.text}
1954
+ }
1955
+ sections += `
1956
+ <feedback>
1957
+ ${commentsContent.trimEnd()}
1958
+ </feedback>
2114
1959
  `;
2115
1960
  }
2116
- prompt += `
2117
- `;
2118
1961
  }
2119
- prompt += `## Instructions
2120
- 1. Complete this task.
2121
- 2. **Artifact Management**: If you create any high-level documentation (PRDs, technical drafts, architecture docs), you MUST save them in \`.locus/artifacts/\`. Do NOT create them in the root directory.
2122
- 3. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
2123
- 4. **Git**: Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches. The Locus system handles all git operations automatically after your execution completes.
2124
- 5. **Progress**: Do NOT modify \`.locus/project/progress.md\`. The system updates it automatically.`;
2125
- return prompt;
1962
+ return `<task_execution>
1963
+ Complete this task: ${task.title}
1964
+
1965
+ <description>
1966
+ ${description}
1967
+ </description>
1968
+ ${sections}
1969
+ <rules>
1970
+ - Complete the task as described
1971
+ - Save any high-level documentation (PRDs, technical drafts, architecture docs) in \`.locus/artifacts/\`
1972
+ - Use relative paths from the project root at all times — no absolute local paths
1973
+ - Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches — Locus handles git automatically
1974
+ </rules>
1975
+ </task_execution>`;
2126
1976
  }
2127
1977
  async buildGenericPrompt(query) {
2128
- let prompt = `# Direct Execution
2129
-
2130
- `;
2131
- prompt += `## Prompt
2132
- ${query}
2133
-
2134
- `;
2135
- const projectConfig = this.getProjectConfig();
2136
- if (projectConfig) {
2137
- prompt += `## Project Metadata
1978
+ const context = this.getProjectContext();
1979
+ const learnings = this.getLearningsContent();
1980
+ const knowledgeBase = this.getKnowledgeBaseSection();
1981
+ let sections = "";
1982
+ if (context) {
1983
+ sections += `
1984
+ <project_context>
1985
+ ${context}
1986
+ </project_context>
2138
1987
  `;
2139
- prompt += `- Version: ${projectConfig.version || "Unknown"}
1988
+ }
1989
+ sections += `
1990
+ <knowledge_base>
1991
+ ${knowledgeBase}
1992
+ </knowledge_base>
2140
1993
  `;
2141
- prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
2142
-
1994
+ if (learnings) {
1995
+ sections += `
1996
+ <learnings>
1997
+ These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
1998
+ ${learnings}
1999
+ </learnings>
2143
2000
  `;
2144
2001
  }
2002
+ return `<direct_execution>
2003
+ Execute this prompt: ${query}
2004
+ ${sections}
2005
+ <rules>
2006
+ - Execute the prompt based on the provided project context
2007
+ - Use relative paths from the project root at all times — no absolute local paths
2008
+ - Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches — Locus handles git automatically
2009
+ </rules>
2010
+ </direct_execution>`;
2011
+ }
2012
+ getProjectContext() {
2145
2013
  const contextPath = getLocusPath(this.projectPath, "contextFile");
2146
- let hasLocalContext = false;
2147
- if (import_node_fs4.existsSync(contextPath)) {
2014
+ if (import_node_fs3.existsSync(contextPath)) {
2148
2015
  try {
2149
- const context = import_node_fs4.readFileSync(contextPath, "utf-8");
2016
+ const context = import_node_fs3.readFileSync(contextPath, "utf-8");
2150
2017
  if (context.trim().length > 20) {
2151
- prompt += `## Project Context (Local)
2152
- ${context}
2153
-
2154
- `;
2155
- hasLocalContext = true;
2018
+ return context;
2156
2019
  }
2157
2020
  } catch (err) {
2158
2021
  console.warn(`Warning: Could not read context file: ${err}`);
2159
2022
  }
2160
2023
  }
2161
- if (!hasLocalContext) {
2162
- const fallback = this.getFallbackContext();
2163
- if (fallback) {
2164
- prompt += `## Project Context (README Fallback)
2165
- ${fallback}
2166
-
2167
- `;
2168
- }
2169
- }
2170
- prompt += this.getProjectStructure();
2171
- prompt += `## Project Knowledge Base
2172
- `;
2173
- prompt += `You have access to the following documentation directories for context:
2174
- `;
2175
- prompt += `- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
2176
- `;
2177
- prompt += `- Documents: \`.locus/documents\` (synced from cloud)
2178
- `;
2179
- prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
2180
-
2181
- `;
2182
- prompt += `## Instructions
2183
- 1. Execute the prompt based on the provided project context.
2184
- 2. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
2185
- 3. **Git**: Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches. The Locus system handles all git operations automatically after your execution completes.
2186
- 4. **Progress**: Do NOT modify \`.locus/project/progress.md\`. The system updates it automatically.`;
2187
- return prompt;
2188
- }
2189
- getProjectConfig() {
2190
- const configPath = getLocusPath(this.projectPath, "configFile");
2191
- if (import_node_fs4.existsSync(configPath)) {
2192
- try {
2193
- return JSON.parse(import_node_fs4.readFileSync(configPath, "utf-8"));
2194
- } catch {
2195
- return null;
2196
- }
2197
- }
2198
- return null;
2024
+ return this.getFallbackContext() || null;
2199
2025
  }
2200
2026
  getFallbackContext() {
2201
- const readmePath = import_node_path6.join(this.projectPath, "README.md");
2202
- if (import_node_fs4.existsSync(readmePath)) {
2027
+ const readmePath = import_node_path5.join(this.projectPath, "README.md");
2028
+ if (import_node_fs3.existsSync(readmePath)) {
2203
2029
  try {
2204
- const content = import_node_fs4.readFileSync(readmePath, "utf-8");
2030
+ const content = import_node_fs3.readFileSync(readmePath, "utf-8");
2205
2031
  const limit = 1000;
2206
2032
  return content.slice(0, limit) + (content.length > limit ? `
2207
2033
  ...(truncated)...` : "");
@@ -2211,32 +2037,28 @@ ${fallback}
2211
2037
  }
2212
2038
  return "";
2213
2039
  }
2214
- getProjectStructure() {
2040
+ getKnowledgeBaseSection() {
2041
+ return `You have access to the following documentation directories for context:
2042
+ - Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
2043
+ - Documents: \`.locus/documents\` (synced from cloud)
2044
+ If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
2045
+ }
2046
+ getLearningsContent() {
2047
+ const learningsPath = getLocusPath(this.projectPath, "learningsFile");
2048
+ if (!import_node_fs3.existsSync(learningsPath)) {
2049
+ return null;
2050
+ }
2215
2051
  try {
2216
- const entries = import_node_fs4.readdirSync(this.projectPath);
2217
- const folders = entries.filter((e) => {
2218
- if (e.startsWith(".") || e === "node_modules")
2219
- return false;
2220
- try {
2221
- return import_node_fs4.statSync(import_node_path6.join(this.projectPath, e)).isDirectory();
2222
- } catch {
2223
- return false;
2224
- }
2225
- });
2226
- if (folders.length === 0)
2227
- return "";
2228
- let structure = `## Project Structure
2229
- `;
2230
- structure += `Key directories in this project:
2231
- `;
2232
- for (const folder of folders) {
2233
- structure += `- \`${folder}/\`
2234
- `;
2052
+ const content = import_node_fs3.readFileSync(learningsPath, "utf-8");
2053
+ const lines = content.split(`
2054
+ `).filter((l) => l.startsWith("- "));
2055
+ if (lines.length === 0) {
2056
+ return null;
2235
2057
  }
2236
- return `${structure}
2237
- `;
2058
+ return lines.join(`
2059
+ `);
2238
2060
  } catch {
2239
- return "";
2061
+ return null;
2240
2062
  }
2241
2063
  }
2242
2064
  roleToText(role) {
@@ -2259,11 +2081,11 @@ ${fallback}
2259
2081
  }
2260
2082
  }
2261
2083
  }
2262
- var import_node_fs4, import_node_path6, import_shared2;
2084
+ var import_node_fs3, import_node_path5, import_shared2;
2263
2085
  var init_prompt_builder = __esm(() => {
2264
2086
  init_config();
2265
- import_node_fs4 = require("node:fs");
2266
- import_node_path6 = require("node:path");
2087
+ import_node_fs3 = require("node:fs");
2088
+ import_node_path5 = require("node:path");
2267
2089
  import_shared2 = require("@locusai/shared");
2268
2090
  });
2269
2091
 
@@ -2381,7 +2203,6 @@ class AgentWorker {
2381
2203
  client;
2382
2204
  aiRunner;
2383
2205
  taskExecutor;
2384
- knowledgeBase;
2385
2206
  gitWorkflow;
2386
2207
  maxTasks = 50;
2387
2208
  tasksCompleted = 0;
@@ -2421,7 +2242,6 @@ class AgentWorker {
2421
2242
  projectPath,
2422
2243
  log
2423
2244
  });
2424
- this.knowledgeBase = new KnowledgeBase(projectPath);
2425
2245
  this.gitWorkflow = new GitWorkflow(config, log);
2426
2246
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
2427
2247
  this.log(`Using ${providerLabel} CLI for all phases`, "info");
@@ -2495,20 +2315,6 @@ class AgentWorker {
2495
2315
  };
2496
2316
  }
2497
2317
  }
2498
- updateProgress(task, summary) {
2499
- try {
2500
- this.knowledgeBase.updateProgress({
2501
- role: "user",
2502
- content: task.title
2503
- });
2504
- this.knowledgeBase.updateProgress({
2505
- role: "assistant",
2506
- content: summary
2507
- });
2508
- } catch (err) {
2509
- this.log(`Failed to update progress: ${err instanceof Error ? err.message : String(err)}`, "warn");
2510
- }
2511
- }
2512
2318
  startHeartbeat() {
2513
2319
  this.sendHeartbeat();
2514
2320
  this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), 60000);
@@ -2583,7 +2389,6 @@ Branch: \`${result.branch}\`` : "";
2583
2389
  this.tasksCompleted++;
2584
2390
  this.completedTaskList.push({ title: task.title, id: task.id });
2585
2391
  this.taskSummaries.push(result.summary);
2586
- this.updateProgress(task, result.summary);
2587
2392
  }
2588
2393
  } else {
2589
2394
  this.log(`Failed: ${task.title} - ${result.summary}`, "error");
@@ -2628,7 +2433,6 @@ var init_worker = __esm(() => {
2628
2433
  init_config();
2629
2434
  init_git_utils();
2630
2435
  init_src();
2631
- init_knowledge_base();
2632
2436
  init_colors();
2633
2437
  init_git_workflow();
2634
2438
  init_task_executor();
@@ -2653,6 +2457,7 @@ __export(exports_index_node, {
2653
2457
  sprintPlanToMarkdown: () => sprintPlanToMarkdown,
2654
2458
  plannedTasksToCreatePayloads: () => plannedTasksToCreatePayloads,
2655
2459
  parseSprintPlanFromAI: () => parseSprintPlanFromAI,
2460
+ parseJsonWithSchema: () => parseJsonWithSchema,
2656
2461
  getRemoteUrl: () => getRemoteUrl,
2657
2462
  getLocusPath: () => getLocusPath,
2658
2463
  getDefaultBranch: () => getDefaultBranch,
@@ -2681,7 +2486,6 @@ __export(exports_index_node, {
2681
2486
  LOCUS_SCHEMAS: () => LOCUS_SCHEMAS,
2682
2487
  LOCUS_GITIGNORE_PATTERNS: () => LOCUS_GITIGNORE_PATTERNS,
2683
2488
  LOCUS_CONFIG: () => LOCUS_CONFIG,
2684
- KnowledgeBase: () => KnowledgeBase,
2685
2489
  InvitationsModule: () => InvitationsModule,
2686
2490
  HistoryManager: () => HistoryManager,
2687
2491
  GitWorkflow: () => GitWorkflow,
@@ -2705,8 +2509,8 @@ module.exports = __toCommonJS(exports_index_node);
2705
2509
 
2706
2510
  // src/core/indexer.ts
2707
2511
  var import_node_crypto2 = require("node:crypto");
2708
- var import_node_fs5 = require("node:fs");
2709
- var import_node_path7 = require("node:path");
2512
+ var import_node_fs4 = require("node:fs");
2513
+ var import_node_path6 = require("node:path");
2710
2514
  var import_globby = require("globby");
2711
2515
 
2712
2516
  class CodebaseIndexer {
@@ -2715,7 +2519,7 @@ class CodebaseIndexer {
2715
2519
  fullReindexRatioThreshold = 0.2;
2716
2520
  constructor(projectPath) {
2717
2521
  this.projectPath = projectPath;
2718
- this.indexPath = import_node_path7.join(projectPath, ".locus", "codebase-index.json");
2522
+ this.indexPath = import_node_path6.join(projectPath, ".locus", "codebase-index.json");
2719
2523
  }
2720
2524
  async index(onProgress, treeSummarizer, force = false) {
2721
2525
  if (!treeSummarizer) {
@@ -2771,11 +2575,11 @@ class CodebaseIndexer {
2771
2575
  }
2772
2576
  }
2773
2577
  async getFileTree() {
2774
- const gitmodulesPath = import_node_path7.join(this.projectPath, ".gitmodules");
2578
+ const gitmodulesPath = import_node_path6.join(this.projectPath, ".gitmodules");
2775
2579
  const submoduleIgnores = [];
2776
- if (import_node_fs5.existsSync(gitmodulesPath)) {
2580
+ if (import_node_fs4.existsSync(gitmodulesPath)) {
2777
2581
  try {
2778
- const content = import_node_fs5.readFileSync(gitmodulesPath, "utf-8");
2582
+ const content = import_node_fs4.readFileSync(gitmodulesPath, "utf-8");
2779
2583
  const lines = content.split(`
2780
2584
  `);
2781
2585
  for (const line of lines) {
@@ -2831,9 +2635,9 @@ class CodebaseIndexer {
2831
2635
  });
2832
2636
  }
2833
2637
  loadIndex() {
2834
- if (import_node_fs5.existsSync(this.indexPath)) {
2638
+ if (import_node_fs4.existsSync(this.indexPath)) {
2835
2639
  try {
2836
- return JSON.parse(import_node_fs5.readFileSync(this.indexPath, "utf-8"));
2640
+ return JSON.parse(import_node_fs4.readFileSync(this.indexPath, "utf-8"));
2837
2641
  } catch {
2838
2642
  return null;
2839
2643
  }
@@ -2841,11 +2645,11 @@ class CodebaseIndexer {
2841
2645
  return null;
2842
2646
  }
2843
2647
  saveIndex(index) {
2844
- const dir = import_node_path7.dirname(this.indexPath);
2845
- if (!import_node_fs5.existsSync(dir)) {
2846
- import_node_fs5.mkdirSync(dir, { recursive: true });
2648
+ const dir = import_node_path6.dirname(this.indexPath);
2649
+ if (!import_node_fs4.existsSync(dir)) {
2650
+ import_node_fs4.mkdirSync(dir, { recursive: true });
2847
2651
  }
2848
- import_node_fs5.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
2652
+ import_node_fs4.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
2849
2653
  }
2850
2654
  cloneIndex(index) {
2851
2655
  return JSON.parse(JSON.stringify(index));
@@ -2861,7 +2665,7 @@ class CodebaseIndexer {
2861
2665
  }
2862
2666
  hashFile(filePath) {
2863
2667
  try {
2864
- const content = import_node_fs5.readFileSync(import_node_path7.join(this.projectPath, filePath), "utf-8");
2668
+ const content = import_node_fs4.readFileSync(import_node_path6.join(this.projectPath, filePath), "utf-8");
2865
2669
  return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
2866
2670
  } catch {
2867
2671
  return null;
@@ -2925,43 +2729,42 @@ class CodebaseIndexer {
2925
2729
  // src/utils/json-extractor.ts
2926
2730
  function extractJsonFromLLMOutput(raw) {
2927
2731
  const trimmed = raw.trim();
2928
- const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
2929
- if (codeBlockMatch) {
2930
- return codeBlockMatch[1]?.trim() || "";
2931
- }
2932
- const startIdx = trimmed.indexOf("{");
2933
- if (startIdx === -1) {
2934
- return trimmed;
2935
- }
2936
- let depth = 0;
2937
- let inString = false;
2938
- let escaped = false;
2939
- for (let i = startIdx;i < trimmed.length; i++) {
2940
- const ch = trimmed[i];
2941
- if (escaped) {
2942
- escaped = false;
2943
- continue;
2944
- }
2945
- if (inString) {
2732
+ const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
2733
+ if (fenceMatch) {
2734
+ return fenceMatch[1].trim();
2735
+ }
2736
+ const start = trimmed.indexOf("{");
2737
+ if (start !== -1) {
2738
+ let depth = 0;
2739
+ let inString = false;
2740
+ let isEscape = false;
2741
+ for (let i = start;i < trimmed.length; i++) {
2742
+ const ch = trimmed[i];
2743
+ if (isEscape) {
2744
+ isEscape = false;
2745
+ continue;
2746
+ }
2946
2747
  if (ch === "\\") {
2947
- escaped = true;
2948
- } else if (ch === '"') {
2949
- inString = false;
2748
+ isEscape = true;
2749
+ continue;
2750
+ }
2751
+ if (ch === '"') {
2752
+ inString = !inString;
2753
+ continue;
2950
2754
  }
2951
- continue;
2952
- }
2953
- if (ch === '"') {
2954
- inString = true;
2955
- } else if (ch === "{") {
2956
- depth++;
2957
- } else if (ch === "}") {
2958
- depth--;
2959
- if (depth === 0) {
2960
- return trimmed.slice(startIdx, i + 1);
2755
+ if (inString)
2756
+ continue;
2757
+ if (ch === "{")
2758
+ depth++;
2759
+ else if (ch === "}") {
2760
+ depth--;
2761
+ if (depth === 0) {
2762
+ return trimmed.slice(start, i + 1);
2763
+ }
2961
2764
  }
2962
2765
  }
2963
2766
  }
2964
- return trimmed.slice(startIdx);
2767
+ return trimmed;
2965
2768
  }
2966
2769
 
2967
2770
  // src/agent/codebase-indexer-service.ts
@@ -2975,22 +2778,30 @@ class CodebaseIndexerService {
2975
2778
  async reindex(force = false) {
2976
2779
  try {
2977
2780
  const index = await this.indexer.index((msg) => this.deps.log(msg, "info"), async (tree) => {
2978
- const prompt = `You are a codebase analysis expert. Analyze the file tree and extract:
2979
- 1. Key symbols (classes, functions, types) and their locations
2980
- 2. Responsibilities of each directory/file
2981
- 3. Overall project structure
2781
+ const prompt = `<codebase_analysis>
2782
+ Analyze this codebase file tree and extract key information.
2982
2783
 
2983
- Analyze this file tree and provide a JSON response with:
2984
- - "symbols": object mapping symbol names to file paths (array)
2985
- - "responsibilities": object mapping paths to brief descriptions
2986
-
2987
- File tree:
2784
+ <file_tree>
2988
2785
  ${tree}
2786
+ </file_tree>
2787
+
2788
+ <rules>
2789
+ - Extract key symbols (classes, functions, types) and their file locations
2790
+ - Identify responsibilities of each directory/file
2791
+ - Map overall project structure
2792
+ </rules>
2989
2793
 
2990
- Return ONLY valid JSON, no markdown formatting.`;
2794
+ <output>
2795
+ Return ONLY valid JSON (no code fences, no markdown):
2796
+ {
2797
+ "symbols": { "symbolName": ["file/path.ts"] },
2798
+ "responsibilities": { "path": "brief description" }
2799
+ }
2800
+ </output>
2801
+ </codebase_analysis>`;
2991
2802
  const response = await this.deps.aiRunner.run(prompt);
2992
- const jsonStr = extractJsonFromLLMOutput(response);
2993
2803
  try {
2804
+ const jsonStr = extractJsonFromLLMOutput(response);
2994
2805
  return JSON.parse(jsonStr);
2995
2806
  } catch {
2996
2807
  return { symbols: {}, responsibilities: {}, lastIndexed: "" };
@@ -3009,8 +2820,8 @@ Return ONLY valid JSON, no markdown formatting.`;
3009
2820
  }
3010
2821
  // src/agent/document-fetcher.ts
3011
2822
  init_config();
3012
- var import_node_fs6 = require("node:fs");
3013
- var import_node_path8 = require("node:path");
2823
+ var import_node_fs5 = require("node:fs");
2824
+ var import_node_path7 = require("node:path");
3014
2825
 
3015
2826
  class DocumentFetcher {
3016
2827
  deps;
@@ -3019,8 +2830,8 @@ class DocumentFetcher {
3019
2830
  }
3020
2831
  async fetch() {
3021
2832
  const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
3022
- if (!import_node_fs6.existsSync(documentsDir)) {
3023
- import_node_fs6.mkdirSync(documentsDir, { recursive: true });
2833
+ if (!import_node_fs5.existsSync(documentsDir)) {
2834
+ import_node_fs5.mkdirSync(documentsDir, { recursive: true });
3024
2835
  }
3025
2836
  try {
3026
2837
  const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
@@ -3033,14 +2844,14 @@ class DocumentFetcher {
3033
2844
  continue;
3034
2845
  }
3035
2846
  const groupName = groupMap.get(doc.groupId || "") || "General";
3036
- const groupDir = import_node_path8.join(documentsDir, groupName);
3037
- if (!import_node_fs6.existsSync(groupDir)) {
3038
- import_node_fs6.mkdirSync(groupDir, { recursive: true });
2847
+ const groupDir = import_node_path7.join(documentsDir, groupName);
2848
+ if (!import_node_fs5.existsSync(groupDir)) {
2849
+ import_node_fs5.mkdirSync(groupDir, { recursive: true });
3039
2850
  }
3040
2851
  const fileName = `${doc.title}.md`;
3041
- const filePath = import_node_path8.join(groupDir, fileName);
3042
- if (!import_node_fs6.existsSync(filePath) || import_node_fs6.readFileSync(filePath, "utf-8") !== doc.content) {
3043
- import_node_fs6.writeFileSync(filePath, doc.content || "");
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 || "");
3044
2855
  fetchedCount++;
3045
2856
  }
3046
2857
  }
@@ -3356,7 +3167,6 @@ class PrService {
3356
3167
 
3357
3168
  // src/agent/reviewer-worker.ts
3358
3169
  init_src();
3359
- init_knowledge_base();
3360
3170
  init_colors();
3361
3171
  function resolveProvider2(value) {
3362
3172
  if (!value || value.startsWith("--"))
@@ -3371,7 +3181,6 @@ class ReviewerWorker {
3371
3181
  client;
3372
3182
  aiRunner;
3373
3183
  prService;
3374
- knowledgeBase;
3375
3184
  heartbeatInterval = null;
3376
3185
  currentTaskId = null;
3377
3186
  maxReviews = 50;
@@ -3397,7 +3206,6 @@ class ReviewerWorker {
3397
3206
  log
3398
3207
  });
3399
3208
  this.prService = new PrService(projectPath, log);
3400
- this.knowledgeBase = new KnowledgeBase(projectPath);
3401
3209
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
3402
3210
  this.log(`Reviewer agent using ${providerLabel} CLI`, "info");
3403
3211
  }
@@ -3513,17 +3321,6 @@ ${summary}`;
3513
3321
  this.sendHeartbeat();
3514
3322
  const result = await this.reviewPr(pr);
3515
3323
  if (result.reviewed) {
3516
- const status = result.approved ? "APPROVED" : "CHANGES REQUESTED";
3517
- try {
3518
- this.knowledgeBase.updateProgress({
3519
- role: "user",
3520
- content: `Review PR #${pr.number}: ${pr.title}`
3521
- });
3522
- this.knowledgeBase.updateProgress({
3523
- role: "assistant",
3524
- content: `${status}: ${result.summary}`
3525
- });
3526
- } catch {}
3527
3324
  this.reviewsCompleted++;
3528
3325
  } else {
3529
3326
  this.log(`Review skipped: ${result.summary}`, "warn");
@@ -4002,8 +3799,8 @@ class ExecEventEmitter {
4002
3799
  }
4003
3800
  // src/exec/history-manager.ts
4004
3801
  init_config();
4005
- var import_node_fs7 = require("node:fs");
4006
- var import_node_path9 = require("node:path");
3802
+ var import_node_fs6 = require("node:fs");
3803
+ var import_node_path8 = require("node:path");
4007
3804
  var DEFAULT_MAX_SESSIONS = 30;
4008
3805
  function generateSessionId2() {
4009
3806
  const timestamp = Date.now().toString(36);
@@ -4015,30 +3812,30 @@ class HistoryManager {
4015
3812
  historyDir;
4016
3813
  maxSessions;
4017
3814
  constructor(projectPath, options) {
4018
- this.historyDir = options?.historyDir ?? import_node_path9.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
3815
+ this.historyDir = options?.historyDir ?? import_node_path8.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
4019
3816
  this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
4020
3817
  this.ensureHistoryDir();
4021
3818
  }
4022
3819
  ensureHistoryDir() {
4023
- if (!import_node_fs7.existsSync(this.historyDir)) {
4024
- import_node_fs7.mkdirSync(this.historyDir, { recursive: true });
3820
+ if (!import_node_fs6.existsSync(this.historyDir)) {
3821
+ import_node_fs6.mkdirSync(this.historyDir, { recursive: true });
4025
3822
  }
4026
3823
  }
4027
3824
  getSessionPath(sessionId) {
4028
- return import_node_path9.join(this.historyDir, `${sessionId}.json`);
3825
+ return import_node_path8.join(this.historyDir, `${sessionId}.json`);
4029
3826
  }
4030
3827
  saveSession(session) {
4031
3828
  const filePath = this.getSessionPath(session.id);
4032
3829
  session.updatedAt = Date.now();
4033
- import_node_fs7.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
3830
+ import_node_fs6.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
4034
3831
  }
4035
3832
  loadSession(sessionId) {
4036
3833
  const filePath = this.getSessionPath(sessionId);
4037
- if (!import_node_fs7.existsSync(filePath)) {
3834
+ if (!import_node_fs6.existsSync(filePath)) {
4038
3835
  return null;
4039
3836
  }
4040
3837
  try {
4041
- const content = import_node_fs7.readFileSync(filePath, "utf-8");
3838
+ const content = import_node_fs6.readFileSync(filePath, "utf-8");
4042
3839
  return JSON.parse(content);
4043
3840
  } catch {
4044
3841
  return null;
@@ -4046,18 +3843,18 @@ class HistoryManager {
4046
3843
  }
4047
3844
  deleteSession(sessionId) {
4048
3845
  const filePath = this.getSessionPath(sessionId);
4049
- if (!import_node_fs7.existsSync(filePath)) {
3846
+ if (!import_node_fs6.existsSync(filePath)) {
4050
3847
  return false;
4051
3848
  }
4052
3849
  try {
4053
- import_node_fs7.rmSync(filePath);
3850
+ import_node_fs6.rmSync(filePath);
4054
3851
  return true;
4055
3852
  } catch {
4056
3853
  return false;
4057
3854
  }
4058
3855
  }
4059
3856
  listSessions(options) {
4060
- const files = import_node_fs7.readdirSync(this.historyDir);
3857
+ const files = import_node_fs6.readdirSync(this.historyDir);
4061
3858
  let sessions = [];
4062
3859
  for (const file of files) {
4063
3860
  if (file.endsWith(".json")) {
@@ -4130,11 +3927,11 @@ class HistoryManager {
4130
3927
  return deleted;
4131
3928
  }
4132
3929
  getSessionCount() {
4133
- const files = import_node_fs7.readdirSync(this.historyDir);
3930
+ const files = import_node_fs6.readdirSync(this.historyDir);
4134
3931
  return files.filter((f) => f.endsWith(".json")).length;
4135
3932
  }
4136
3933
  sessionExists(sessionId) {
4137
- return import_node_fs7.existsSync(this.getSessionPath(sessionId));
3934
+ return import_node_fs6.existsSync(this.getSessionPath(sessionId));
4138
3935
  }
4139
3936
  findSessionByPartialId(partialId) {
4140
3937
  const sessions = this.listSessions();
@@ -4148,12 +3945,12 @@ class HistoryManager {
4148
3945
  return this.historyDir;
4149
3946
  }
4150
3947
  clearAllSessions() {
4151
- const files = import_node_fs7.readdirSync(this.historyDir);
3948
+ const files = import_node_fs6.readdirSync(this.historyDir);
4152
3949
  let deleted = 0;
4153
3950
  for (const file of files) {
4154
3951
  if (file.endsWith(".json")) {
4155
3952
  try {
4156
- import_node_fs7.rmSync(import_node_path9.join(this.historyDir, file));
3953
+ import_node_fs6.rmSync(import_node_path8.join(this.historyDir, file));
4157
3954
  deleted++;
4158
3955
  } catch {}
4159
3956
  }
@@ -4429,8 +4226,8 @@ init_src();
4429
4226
  init_colors();
4430
4227
  init_resolve_bin();
4431
4228
  var import_node_child_process7 = require("node:child_process");
4432
- var import_node_fs8 = require("node:fs");
4433
- var import_node_path10 = require("node:path");
4229
+ var import_node_fs7 = require("node:fs");
4230
+ var import_node_path9 = require("node:path");
4434
4231
  var import_node_url = require("node:url");
4435
4232
  var import_shared4 = require("@locusai/shared");
4436
4233
  var import_events4 = require("events");
@@ -4698,14 +4495,14 @@ ${agentId} finished (exit code: ${code})`);
4698
4495
  }
4699
4496
  resolveWorkerPath() {
4700
4497
  const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/index.ts");
4701
- const currentModuleDir = import_node_path10.dirname(currentModulePath);
4498
+ const currentModuleDir = import_node_path9.dirname(currentModulePath);
4702
4499
  const potentialPaths = [
4703
- import_node_path10.join(currentModuleDir, "..", "agent", "worker.js"),
4704
- import_node_path10.join(currentModuleDir, "agent", "worker.js"),
4705
- import_node_path10.join(currentModuleDir, "worker.js"),
4706
- import_node_path10.join(currentModuleDir, "..", "agent", "worker.ts")
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")
4707
4504
  ];
4708
- return potentialPaths.find((p) => import_node_fs8.existsSync(p));
4505
+ return potentialPaths.find((p) => import_node_fs7.existsSync(p));
4709
4506
  }
4710
4507
  }
4711
4508
  function killProcessTree(proc) {
@@ -4724,12 +4521,58 @@ function sleep(ms) {
4724
4521
  }
4725
4522
  // src/planning/plan-manager.ts
4726
4523
  init_config();
4727
- init_knowledge_base();
4728
- var import_node_fs9 = require("node:fs");
4729
- var import_node_path11 = require("node:path");
4524
+ var import_node_fs8 = require("node:fs");
4525
+ var import_node_path10 = require("node:path");
4730
4526
 
4731
4527
  // src/planning/sprint-plan.ts
4732
4528
  var import_shared5 = require("@locusai/shared");
4529
+ var import_zod = require("zod");
4530
+
4531
+ // src/utils/structured-output.ts
4532
+ function parseJsonWithSchema(raw, schema) {
4533
+ const jsonStr = extractJsonFromLLMOutput(raw);
4534
+ let parsed;
4535
+ try {
4536
+ parsed = JSON.parse(jsonStr);
4537
+ } catch (err) {
4538
+ const preview = jsonStr.slice(0, 200);
4539
+ throw new Error(`Failed to parse JSON from LLM output: ${err instanceof Error ? err.message : String(err)}
4540
+ Extracted text preview: ${preview}`);
4541
+ }
4542
+ const result = schema.safeParse(parsed);
4543
+ if (!result.success) {
4544
+ const issues = result.error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join(`
4545
+ `);
4546
+ throw new Error(`LLM output failed schema validation:
4547
+ ${issues}
4548
+ Parsed JSON preview: ${JSON.stringify(parsed).slice(0, 300)}`);
4549
+ }
4550
+ return result.data;
4551
+ }
4552
+
4553
+ // 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([])
4562
+ });
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")
4567
+ });
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([])
4574
+ });
4575
+ var SprintPlanAIOutputSchema = PlannerOutputSchema;
4733
4576
  function sprintPlanToMarkdown(plan) {
4734
4577
  const lines = [];
4735
4578
  lines.push(`# Sprint Plan: ${plan.name}`);
@@ -4804,42 +4647,31 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
4804
4647
  }));
4805
4648
  }
4806
4649
  function parseSprintPlanFromAI(raw, directive) {
4807
- const jsonStr = extractJsonFromLLMOutput(raw);
4808
- let parsed;
4809
- try {
4810
- parsed = JSON.parse(jsonStr);
4811
- } catch (err) {
4812
- const preview = jsonStr.slice(0, 200);
4813
- throw new Error(`Failed to parse sprint plan JSON: ${err instanceof Error ? err.message : String(err)}
4814
- Extracted JSON preview: ${preview}`);
4815
- }
4816
- if (parsed.revisedPlan) {
4817
- parsed = parsed.revisedPlan;
4818
- }
4650
+ const planData = parseJsonWithSchema(raw, SprintPlanAIOutputSchema);
4819
4651
  const now = new Date().toISOString();
4820
4652
  const id = `plan-${Date.now()}`;
4821
- const tasks2 = (parsed.tasks || []).map((t, i) => ({
4653
+ const tasks2 = planData.tasks.map((t, i) => ({
4822
4654
  index: i + 1,
4823
- title: t.title || `Task ${i + 1}`,
4824
- description: t.description || "",
4825
- assigneeRole: t.assigneeRole || "BACKEND",
4826
- priority: t.priority || "MEDIUM",
4827
- complexity: t.complexity || 3,
4828
- acceptanceCriteria: t.acceptanceCriteria || [],
4829
- labels: t.labels || []
4655
+ title: t.title,
4656
+ description: t.description,
4657
+ assigneeRole: t.assigneeRole,
4658
+ priority: t.priority,
4659
+ complexity: t.complexity,
4660
+ acceptanceCriteria: t.acceptanceCriteria,
4661
+ labels: t.labels
4830
4662
  }));
4831
4663
  return {
4832
4664
  id,
4833
- name: parsed.name || "Unnamed Sprint",
4834
- goal: parsed.goal || directive,
4665
+ name: planData.name,
4666
+ goal: planData.goal || directive,
4835
4667
  directive,
4836
4668
  tasks: tasks2,
4837
- risks: (parsed.risks || []).map((r) => ({
4838
- description: r.description || "",
4839
- mitigation: r.mitigation || "",
4840
- severity: r.severity || "medium"
4669
+ risks: planData.risks.map((r) => ({
4670
+ description: r.description,
4671
+ mitigation: r.mitigation,
4672
+ severity: r.severity
4841
4673
  })),
4842
- estimatedDays: parsed.estimatedDays || 1,
4674
+ estimatedDays: planData.estimatedDays,
4843
4675
  status: "pending",
4844
4676
  createdAt: now,
4845
4677
  updatedAt: now
@@ -4848,28 +4680,26 @@ Extracted JSON preview: ${preview}`);
4848
4680
 
4849
4681
  // src/planning/plan-manager.ts
4850
4682
  class PlanManager {
4851
- projectPath;
4852
4683
  plansDir;
4853
4684
  constructor(projectPath) {
4854
- this.projectPath = projectPath;
4855
4685
  this.plansDir = getLocusPath(projectPath, "plansDir");
4856
4686
  }
4857
4687
  save(plan) {
4858
4688
  this.ensurePlansDir();
4859
4689
  const slug = this.slugify(plan.name);
4860
- const jsonPath = import_node_path11.join(this.plansDir, `${slug}.json`);
4861
- const mdPath = import_node_path11.join(this.plansDir, `sprint-${slug}.md`);
4862
- import_node_fs9.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
4863
- import_node_fs9.writeFileSync(mdPath, sprintPlanToMarkdown(plan), "utf-8");
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");
4864
4694
  return plan.id;
4865
4695
  }
4866
4696
  load(idOrSlug) {
4867
4697
  this.ensurePlansDir();
4868
- const files = import_node_fs9.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
4698
+ const files = import_node_fs8.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
4869
4699
  for (const file of files) {
4870
- const filePath = import_node_path11.join(this.plansDir, file);
4700
+ const filePath = import_node_path10.join(this.plansDir, file);
4871
4701
  try {
4872
- const plan = JSON.parse(import_node_fs9.readFileSync(filePath, "utf-8"));
4702
+ const plan = JSON.parse(import_node_fs8.readFileSync(filePath, "utf-8"));
4873
4703
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
4874
4704
  return plan;
4875
4705
  }
@@ -4879,11 +4709,11 @@ class PlanManager {
4879
4709
  }
4880
4710
  list(status) {
4881
4711
  this.ensurePlansDir();
4882
- const files = import_node_fs9.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
4712
+ const files = import_node_fs8.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
4883
4713
  const plans = [];
4884
4714
  for (const file of files) {
4885
4715
  try {
4886
- const plan = JSON.parse(import_node_fs9.readFileSync(import_node_path11.join(this.plansDir, file), "utf-8"));
4716
+ const plan = JSON.parse(import_node_fs8.readFileSync(import_node_path10.join(this.plansDir, file), "utf-8"));
4887
4717
  if (!status || plan.status === status) {
4888
4718
  plans.push(plan);
4889
4719
  }
@@ -4913,15 +4743,6 @@ class PlanManager {
4913
4743
  plan.status = "approved";
4914
4744
  plan.updatedAt = new Date().toISOString();
4915
4745
  this.save(plan);
4916
- const kb = new KnowledgeBase(this.projectPath);
4917
- kb.updateProgress({
4918
- role: "user",
4919
- content: `Start sprint: ${plan.name}`
4920
- });
4921
- kb.updateProgress({
4922
- role: "assistant",
4923
- content: `Sprint started with ${tasks2.length} tasks. Goal: ${plan.goal}`
4924
- });
4925
4746
  return { sprint, tasks: tasks2 };
4926
4747
  }
4927
4748
  reject(idOrSlug, feedback) {
@@ -4949,18 +4770,18 @@ class PlanManager {
4949
4770
  }
4950
4771
  delete(idOrSlug) {
4951
4772
  this.ensurePlansDir();
4952
- const files = import_node_fs9.readdirSync(this.plansDir);
4773
+ const files = import_node_fs8.readdirSync(this.plansDir);
4953
4774
  for (const file of files) {
4954
- const filePath = import_node_path11.join(this.plansDir, file);
4775
+ const filePath = import_node_path10.join(this.plansDir, file);
4955
4776
  if (!file.endsWith(".json"))
4956
4777
  continue;
4957
4778
  try {
4958
- const plan = JSON.parse(import_node_fs9.readFileSync(filePath, "utf-8"));
4779
+ const plan = JSON.parse(import_node_fs8.readFileSync(filePath, "utf-8"));
4959
4780
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
4960
- import_node_fs9.unlinkSync(filePath);
4961
- const mdPath = import_node_path11.join(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
4962
- if (import_node_fs9.existsSync(mdPath)) {
4963
- import_node_fs9.unlinkSync(mdPath);
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);
4964
4785
  }
4965
4786
  return;
4966
4787
  }
@@ -4974,8 +4795,8 @@ class PlanManager {
4974
4795
  return sprintPlanToMarkdown(plan);
4975
4796
  }
4976
4797
  ensurePlansDir() {
4977
- if (!import_node_fs9.existsSync(this.plansDir)) {
4978
- import_node_fs9.mkdirSync(this.plansDir, { recursive: true });
4798
+ if (!import_node_fs8.existsSync(this.plansDir)) {
4799
+ import_node_fs8.mkdirSync(this.plansDir, { recursive: true });
4979
4800
  }
4980
4801
  }
4981
4802
  slugify(name) {
@@ -4983,222 +4804,77 @@ class PlanManager {
4983
4804
  }
4984
4805
  }
4985
4806
  // src/planning/planning-meeting.ts
4986
- init_knowledge_base();
4987
-
4988
- // src/planning/agents/cross-task-reviewer.ts
4989
- function buildCrossTaskReviewerPrompt(input) {
4990
- let prompt = `# Role: Cross-Task Reviewer (Architect + Engineer + Planner)
4991
-
4992
- You are a combined Architect, Senior Engineer, and Sprint Planner performing a FINAL review of a sprint plan. Your focus is ensuring that tasks are correctly ordered, well-scoped, and will execute successfully in sequence.
4993
-
4994
- ## Context
4995
-
4996
- In this system, tasks are executed SEQUENTIALLY by a single agent on ONE branch:
4997
- - Tasks run one at a time, in the order they appear in the array
4998
- - Each task's changes are committed before the next task starts
4999
- - Later tasks can see and build on earlier tasks' work
5000
- - The final result is a single branch with all changes, which becomes a pull request
5001
-
5002
- This means:
5003
- - Task ordering is critical — a task must NOT depend on a later task's output
5004
- - Foundation work (config, schemas, shared code) must come first
5005
- - Each task should be a focused, logical unit of work
5006
-
5007
- ## CEO Directive
5008
- > ${input.directive}
5009
- `;
5010
- if (input.feedback) {
5011
- prompt += `
5012
- ## CEO Feedback on Previous Plan
5013
- > ${input.feedback}
5014
-
5015
- IMPORTANT: Ensure the reviewed plan still addresses this feedback.
5016
- `;
5017
- }
5018
- prompt += `
5019
- ## Project Context
5020
- ${input.projectContext || "No project context available."}
5021
-
5022
- ## Sprint Plan to Review
5023
- ${input.plannerOutput}
5024
-
5025
- ## Your Review Checklist
5026
-
5027
- Go through EACH task and check for:
5028
-
5029
- ### 1. Ordering & Dependency Analysis
5030
- For each task, verify:
5031
- - Does it depend on any task that appears LATER in the list? If so, reorder.
5032
- - Are foundational tasks (config, schemas, shared code) at the beginning?
5033
- - Is the overall execution order logical?
5034
-
5035
- ### 2. Scope & Completeness
5036
- For each task, verify:
5037
- - Is the task well-scoped? Not too large, not too trivial?
5038
- - Does it include ALL changes needed for its goal (given earlier tasks are done)?
5039
- - Are there any missing tasks that should be added?
5040
-
5041
- ### 3. Description Quality Validation
5042
- For each task, verify the description is a clear, actionable implementation guide. Each description must specify:
5043
- - **What to do** — the specific goal and expected behavior/outcome
5044
- - **Where to do it** — specific files, modules, or directories to modify or create
5045
- - **How to do it** — implementation approach, patterns to follow, existing utilities to use
5046
- - **Boundaries** — what is NOT in scope for this task
5047
-
5048
- If any description is vague (e.g., "Add authentication", "Update the API", "Fix the frontend"), rewrite it with concrete implementation details. The executing agent receives ONLY the task title, description, and acceptance criteria as its instructions.
5049
-
5050
- ### 4. Risk Assessment
5051
- - Are there tasks that might fail or have unknowns?
5052
- - Is the sprint scope realistic for sequential execution?
5053
-
5054
- ## Output Format
5055
-
5056
- Your entire response must be a single JSON object — no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
5057
-
5058
- {
5059
- "hasIssues": true | false,
5060
- "issues": [
5061
- {
5062
- "type": "wrong_order" | "missing_task" | "scope_issue" | "vague_description",
5063
- "description": "string describing the specific issue",
5064
- "affectedTasks": ["Task Title 1", "Task Title 2"],
5065
- "resolution": "string describing how to fix it"
5066
- }
5067
- ],
5068
- "revisedPlan": {
5069
- "name": "string (2-4 words)",
5070
- "goal": "string (1 paragraph)",
5071
- "estimatedDays": 3,
5072
- "tasks": [
5073
- {
5074
- "title": "string",
5075
- "description": "string (detailed implementation guide: what to do, where to do it, how to do it, and boundaries)",
5076
- "assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
5077
- "priority": "CRITICAL | HIGH | MEDIUM | LOW",
5078
- "labels": ["string"],
5079
- "acceptanceCriteria": ["string"],
5080
- "complexity": 3
5081
- }
5082
- ],
5083
- "risks": [
5084
- {
5085
- "description": "string",
5086
- "mitigation": "string",
5087
- "severity": "low | medium | high"
5088
- }
5089
- ]
5090
- }
5091
- }
5092
-
5093
- IMPORTANT:
5094
- - If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (reordered, descriptions rewritten, missing tasks added, etc.)
5095
- - If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
5096
- - The revisedPlan is ALWAYS required — it becomes the final plan
5097
- - Ensure every task description is a detailed implementation guide (what, where, how, boundaries) — rewrite vague descriptions
5098
- - Tasks execute sequentially — the array order IS the execution order`;
5099
- return prompt;
5100
- }
4807
+ init_config();
4808
+ var import_node_fs9 = require("node:fs");
4809
+ var import_node_path11 = require("node:path");
5101
4810
 
5102
4811
  // src/planning/agents/planner.ts
5103
4812
  function buildPlannerPrompt(input) {
5104
- let prompt = `# Role: Sprint Planner
5105
-
5106
- You are a Sprint Planner — an expert engineer, architect, and project organizer rolled into one. Your job is to take a CEO directive and produce a complete, ready-to-execute sprint plan in a single pass.
5107
-
5108
- ## CEO Directive
5109
- > ${input.directive}
5110
- `;
4813
+ let feedbackSection = "";
5111
4814
  if (input.feedback) {
5112
- prompt += `
5113
- ## CEO Feedback on Previous Plan
5114
- > ${input.feedback}
5115
-
5116
- IMPORTANT: Incorporate this feedback into your plan. The CEO has reviewed a previous plan and wants changes.
4815
+ feedbackSection = `
4816
+ <ceo_feedback>
4817
+ The CEO has reviewed a previous plan and wants changes. Incorporate this feedback:
4818
+ ${input.feedback}
4819
+ </ceo_feedback>
5117
4820
  `;
5118
4821
  }
5119
- prompt += `
5120
- ## Project Context
5121
- ${input.projectContext || "No project context available."}
5122
-
5123
- ## Your Task
5124
-
5125
- Analyze the directive and produce a **complete sprint plan** with the following:
5126
-
5127
- 1. **Sprint Name** A concise, memorable name (2-4 words)
5128
- 2. **Sprint Goal** One paragraph describing what this sprint delivers
5129
- 3. **Duration Estimate** How many days this sprint will take with a single agent working sequentially
5130
- 4. **Task Breakdown** An ordered list of tasks that fully implement the directive
5131
- 5. **Risk Assessment** Potential risks with mitigations
5132
-
5133
- ### Task Requirements
5134
-
5135
- For each task, provide:
5136
- - **Title** — Clear, action-oriented (e.g., "Implement user registration API endpoint")
5137
- - **Description** A detailed, actionable implementation guide (see below)
5138
- - **Assignee Role** — BACKEND, FRONTEND, QA, PM, or DESIGN
5139
- - **Priority** — CRITICAL, HIGH, MEDIUM, or LOW
5140
- - **Complexity** — 1 (trivial) to 5 (very complex)
5141
- - **Labels** — Relevant tags (e.g., "api", "database", "ui", "auth")
5142
- - **Acceptance Criteria** — Specific, testable conditions for completion
5143
-
5144
- ### CRITICAL: Task Description Requirements
5145
-
5146
- Each task description will be handed to an INDEPENDENT agent as its primary instruction. The agent will have access to the codebase but NO context about the planning meeting. Each description MUST include:
5147
-
5148
- 1. **What to do** — Clearly state the goal and expected behavior/outcome
5149
- 2. **Where to do it** — List specific files, modules, or directories to modify or create. Reference existing code paths when extending functionality
5150
- 3. **How to do it** — Key implementation details: which patterns to follow, which existing utilities or services to use, what the data flow looks like
5151
- 4. **Boundaries** — What is NOT in scope for this task to prevent overlap with other tasks
5152
-
5153
- Bad example: "Add authentication to the API."
5154
- Good example: "Implement JWT-based authentication middleware in src/middleware/auth.ts. Create a verifyToken middleware that extracts the Bearer token from the Authorization header, validates it using the existing JWT_SECRET from env config, and attaches the decoded user payload to req.user. Apply this middleware to all routes in src/routes/protected/. This task does NOT include user registration or password reset — those are handled separately."
5155
-
5156
- ### CRITICAL: Task Ordering Rules
5157
-
5158
- Tasks are executed SEQUENTIALLY by a single agent on ONE branch. The agent works through tasks in array order. Therefore:
5159
-
5160
- 1. **Foundation first.** Place foundational tasks (schemas, config, shared code) at the beginning. Later tasks can build on earlier ones since they run in sequence.
5161
- 2. **No forward dependencies.** A task must NOT depend on a task that appears later in the list.
5162
- 3. **Each task is self-contained for its scope.** A task can depend on earlier tasks but must include all changes needed for its own goal.
5163
- 4. **Keep tasks focused.** Each task should do one logical unit of work. Avoid trivially small or overly large tasks.
5164
- 5. **Merge related trivial work.** If two pieces of work are trivially small and tightly related, combine them into one task.
5165
-
5166
- ### Sprint Scope Guidelines
5167
-
5168
- - If the sprint would exceed 12 tasks, reduce scope or merge related tasks
5169
- - Ensure acceptance criteria are specific and testable
5170
- - Keep the sprint focused on the directive — avoid scope creep
5171
-
5172
- ## Output Format
5173
-
5174
- Your entire response must be a single JSON object — no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
5175
-
4822
+ const now = new Date().toISOString();
4823
+ return `<sprint_planning>
4824
+ Create a sprint plan for this directive: ${input.directive}
4825
+ ${feedbackSection}
4826
+ <rules>
4827
+ - Tasks execute sequentially by one agent on one branch
4828
+ - Each task must be self-contained with clear What/Where/How
4829
+ - No forward dependencies (task N can't need task N+1)
4830
+ - Foundation first (shared code, types, schemas before features)
4831
+ - Be specific: exact file paths, function names, implementation details
4832
+ - Each task description is the ONLY instruction an independent agent receives include all context it needs
4833
+ - Merge trivially small related work into one task
4834
+ - Assign appropriate roles: BACKEND, FRONTEND, QA, PM, or DESIGN
4835
+ </rules>
4836
+
4837
+ <output>
4838
+ Write the sprint plan as a JSON file to: ${input.plansDir}/${input.fileName}.json
4839
+
4840
+ The JSON file must contain this exact structure:
5176
4841
  {
5177
- "name": "string (2-4 words)",
5178
- "goal": "string (1 paragraph)",
5179
- "estimatedDays": 3,
4842
+ "id": "${input.planId}",
4843
+ "name": "2-4 words",
4844
+ "goal": "One paragraph of what this delivers",
4845
+ "directive": ${JSON.stringify(input.directive)},
4846
+ "estimatedDays": number,
4847
+ "status": "pending",
4848
+ "createdAt": "${now}",
4849
+ "updatedAt": "${now}",
5180
4850
  "tasks": [
5181
4851
  {
5182
- "title": "string",
5183
- "description": "string (detailed implementation guide: what, where, how, boundaries)",
5184
- "assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
5185
- "priority": "CRITICAL | HIGH | MEDIUM | LOW",
5186
- "labels": ["string"],
5187
- "acceptanceCriteria": ["string"],
5188
- "complexity": 3
4852
+ "index": 1,
4853
+ "title": "Action-oriented title",
4854
+ "description": "What: goal\\nWhere: files to modify\\nHow: implementation details\\nBoundaries: what's excluded",
4855
+ "assigneeRole": "BACKEND|FRONTEND|QA|PM|DESIGN",
4856
+ "priority": "CRITICAL|HIGH|MEDIUM|LOW",
4857
+ "labels": ["tags"],
4858
+ "acceptanceCriteria": ["testable conditions"],
4859
+ "complexity": 1-5
5189
4860
  }
5190
4861
  ],
5191
4862
  "risks": [
5192
4863
  {
5193
- "description": "string",
5194
- "mitigation": "string",
5195
- "severity": "low | medium | high"
4864
+ "description": "What could go wrong",
4865
+ "mitigation": "How to handle it",
4866
+ "severity": "low|medium|high"
5196
4867
  }
5197
4868
  ]
5198
4869
  }
5199
4870
 
5200
- IMPORTANT: Tasks are executed sequentially by a single agent. The array order IS the execution order — ensure foundational work comes first and dependent tasks come after their prerequisites.`;
5201
- return prompt;
4871
+ IMPORTANT:
4872
+ - Write the file directly using your file writing tool. Do NOT output the JSON as text.
4873
+ - Tasks must have sequential "index" values starting at 1.
4874
+ - The file must be valid JSON with no comments or trailing commas.
4875
+ - Do not create any other files. Only create the single JSON file specified above.
4876
+ </output>
4877
+ </sprint_planning>`;
5202
4878
  }
5203
4879
 
5204
4880
  // src/planning/planning-meeting.ts
@@ -5214,42 +4890,41 @@ class PlanningMeeting {
5214
4890
  });
5215
4891
  }
5216
4892
  async run(directive, feedback) {
5217
- const projectContext = this.getProjectContext();
5218
- this.log("Phase 1/2: Planner building sprint plan...", "info");
5219
- const plannerPrompt = buildPlannerPrompt({
5220
- directive,
5221
- projectContext,
5222
- feedback
5223
- });
5224
- console.log(plannerPrompt);
5225
- const plannerOutput = await this.aiRunner.run(plannerPrompt);
5226
- this.log("Planner phase complete.", "success");
5227
- this.log("Phase 2/2: Reviewer checking for conflicts and quality...", "info");
5228
- const crossTaskReviewerPrompt = buildCrossTaskReviewerPrompt({
4893
+ this.log("Planning sprint...", "info");
4894
+ const plansDir = getLocusPath(this.projectPath, "plansDir");
4895
+ if (!import_node_fs9.existsSync(plansDir)) {
4896
+ import_node_fs9.mkdirSync(plansDir, { recursive: true });
4897
+ }
4898
+ const ts = Date.now();
4899
+ const planId = `plan-${ts}`;
4900
+ const fileName = `plan-${ts}`;
4901
+ const prompt = buildPlannerPrompt({
5229
4902
  directive,
5230
- projectContext,
5231
- plannerOutput,
5232
- feedback
4903
+ feedback,
4904
+ plansDir,
4905
+ planId,
4906
+ fileName
5233
4907
  });
5234
- const reviewOutput = await this.aiRunner.run(crossTaskReviewerPrompt);
5235
- this.log("Review phase complete.", "success");
5236
- const plan = parseSprintPlanFromAI(reviewOutput, directive);
4908
+ const response = await this.aiRunner.run(prompt);
4909
+ this.log("Planning meeting complete.", "success");
4910
+ const expectedPath = import_node_path11.join(plansDir, `${fileName}.json`);
4911
+ let plan = null;
4912
+ if (import_node_fs9.existsSync(expectedPath)) {
4913
+ try {
4914
+ plan = JSON.parse(import_node_fs9.readFileSync(expectedPath, "utf-8"));
4915
+ } catch {}
4916
+ }
4917
+ if (!plan) {
4918
+ throw new Error("Planning agent did not create the expected plan JSON file. " + "Check the agent output for errors.");
4919
+ }
5237
4920
  if (feedback) {
5238
4921
  plan.feedback = feedback;
5239
4922
  }
5240
4923
  return {
5241
4924
  plan,
5242
- phaseOutputs: {
5243
- planner: plannerOutput,
5244
- review: reviewOutput
5245
- }
4925
+ rawOutput: response
5246
4926
  };
5247
4927
  }
5248
- getProjectContext() {
5249
- const kb = new KnowledgeBase(this.projectPath);
5250
- return kb.getFullContext();
5251
- }
5252
4928
  }
5253
4929
  // src/index-node.ts
5254
- init_knowledge_base();
5255
4930
  init_colors();