@locusai/sdk 0.11.8 → 0.12.1

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 +140 -356
  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 +400 -776
  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 -6
  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 -13
  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,106 +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 progress = this.readProgress();
1684
- const parts = [];
1685
- if (context.trim()) {
1686
- parts.push(context.trim());
1687
- }
1688
- if (progress.trim()) {
1689
- parts.push(progress.trim());
1690
- }
1691
- return parts.join(`
1692
-
1693
- ---
1694
-
1695
- `);
1696
- }
1697
- initialize(info) {
1698
- this.ensureDir(this.contextPath);
1699
- this.ensureDir(this.progressPath);
1700
- const techStackList = info.techStack.map((t) => `- ${t}`).join(`
1701
- `);
1702
- const contextContent = `# Project: ${info.name}
1703
-
1704
- ## Mission
1705
- ${info.mission}
1706
-
1707
- ## Tech Stack
1708
- ${techStackList}
1709
-
1710
- ## Architecture
1711
- <!-- Describe your high-level architecture here -->
1712
-
1713
- ## Key Decisions
1714
- <!-- Document important technical decisions and their rationale -->
1715
-
1716
- ## Feature Areas
1717
- <!-- List your main feature areas and their status -->
1718
- `;
1719
- const progressContent = `# Conversation History
1720
- `;
1721
- import_node_fs3.writeFileSync(this.contextPath, contextContent);
1722
- import_node_fs3.writeFileSync(this.progressPath, progressContent);
1723
- }
1724
- get exists() {
1725
- return import_node_fs3.existsSync(this.contextPath) || import_node_fs3.existsSync(this.progressPath);
1726
- }
1727
- ensureDir(filePath) {
1728
- const dir = import_node_path5.dirname(filePath);
1729
- if (!import_node_fs3.existsSync(dir)) {
1730
- import_node_fs3.mkdirSync(dir, { recursive: true });
1731
- }
1732
- }
1733
- }
1734
- var import_node_fs3, import_node_path5;
1735
- var init_knowledge_base = __esm(() => {
1736
- init_config();
1737
- import_node_fs3 = require("node:fs");
1738
- import_node_path5 = require("node:path");
1739
- });
1740
-
1741
1629
  // src/agent/git-workflow.ts
1742
1630
  class GitWorkflow {
1743
1631
  config;
@@ -1989,239 +1877,157 @@ class PromptBuilder {
1989
1877
  constructor(projectPath) {
1990
1878
  this.projectPath = projectPath;
1991
1879
  }
1992
- async build(task, options = {}) {
1993
- let prompt = `# Task: ${task.title}
1994
-
1995
- `;
1880
+ async build(task) {
1996
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 = "";
1997
1887
  if (roleText) {
1998
- prompt += `## Role
1888
+ sections += `
1889
+ <role>
1999
1890
  You are acting as a ${roleText}.
2000
-
1891
+ </role>
2001
1892
  `;
2002
1893
  }
2003
- prompt += `## Description
2004
- ${task.description || "No description provided."}
2005
-
2006
- `;
2007
- const projectConfig = this.getProjectConfig();
2008
- if (projectConfig) {
2009
- prompt += `## Project Metadata
2010
- `;
2011
- prompt += `- Version: ${projectConfig.version || "Unknown"}
2012
- `;
2013
- prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
2014
-
2015
- `;
2016
- }
2017
- let serverContext = null;
2018
- if (options.taskContext) {
2019
- try {
2020
- serverContext = JSON.parse(options.taskContext);
2021
- } catch {
2022
- serverContext = { context: options.taskContext };
2023
- }
2024
- }
2025
- const contextPath = getLocusPath(this.projectPath, "contextFile");
2026
- let hasLocalContext = false;
2027
- if (import_node_fs4.existsSync(contextPath)) {
2028
- try {
2029
- const context = import_node_fs4.readFileSync(contextPath, "utf-8");
2030
- if (context.trim().length > 20) {
2031
- prompt += `## Project Context (Local)
1894
+ if (context) {
1895
+ sections += `
1896
+ <project_context>
2032
1897
  ${context}
2033
-
1898
+ </project_context>
2034
1899
  `;
2035
- hasLocalContext = true;
2036
- }
2037
- } catch (err) {
2038
- console.warn(`Warning: Could not read context file: ${err}`);
2039
- }
2040
1900
  }
2041
- if (!hasLocalContext) {
2042
- const fallback = this.getFallbackContext();
2043
- if (fallback) {
2044
- prompt += `## Project Context (README Fallback)
2045
- ${fallback}
2046
-
2047
- `;
2048
- }
2049
- }
2050
- if (serverContext) {
2051
- prompt += `## Project Context (Server)
2052
- `;
2053
- const project = serverContext.project;
2054
- if (project) {
2055
- prompt += `- Project: ${project.name || "Unknown"}
1901
+ sections += `
1902
+ <knowledge_base>
1903
+ ${knowledgeBase}
1904
+ </knowledge_base>
2056
1905
  `;
2057
- if (!hasLocalContext && project.techStack?.length) {
2058
- prompt += `- Tech Stack: ${project.techStack.join(", ")}
2059
- `;
2060
- }
2061
- }
2062
- if (serverContext.context) {
2063
- prompt += `
2064
- ${serverContext.context}
2065
- `;
2066
- }
2067
- prompt += `
2068
- `;
2069
- }
2070
- prompt += this.getProjectStructure();
2071
- prompt += `## Project Knowledge Base
2072
- `;
2073
- prompt += `You have access to the following documentation directories for context:
2074
- `;
2075
- prompt += `- Artifacts: \`.locus/artifacts\`
2076
- `;
2077
- prompt += `- Documents: \`.locus/documents\`
2078
- `;
2079
- prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
2080
-
2081
- `;
2082
- const indexPath = getLocusPath(this.projectPath, "indexFile");
2083
- if (import_node_fs4.existsSync(indexPath)) {
2084
- prompt += `## Codebase Overview
2085
- There is an index file in the .locus/codebase-index.json and if you need you can check it.
2086
-
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>
2087
1912
  `;
2088
1913
  }
2089
1914
  if (task.docs && task.docs.length > 0) {
2090
- prompt += `## Attached Documents (Summarized)
2091
- `;
2092
- prompt += `> Full content available on server. Rely on Task Description for specific requirements.
2093
-
2094
- `;
1915
+ let docsContent = "";
2095
1916
  for (const doc of task.docs) {
2096
1917
  const content = doc.content || "";
2097
1918
  const limit = 800;
2098
1919
  const preview = content.slice(0, limit);
2099
1920
  const isTruncated = content.length > limit;
2100
- prompt += `### Doc: ${doc.title}
1921
+ docsContent += `### ${doc.title}
2101
1922
  ${preview}${isTruncated ? `
2102
1923
  ...(truncated)...` : ""}
2103
1924
 
2104
1925
  `;
2105
1926
  }
1927
+ sections += `
1928
+ <documents>
1929
+ ${docsContent.trimEnd()}
1930
+ </documents>
1931
+ `;
2106
1932
  }
2107
1933
  if (task.acceptanceChecklist && task.acceptanceChecklist.length > 0) {
2108
- prompt += `## Acceptance Criteria
2109
- `;
1934
+ let criteria = "";
2110
1935
  for (const item of task.acceptanceChecklist) {
2111
- prompt += `- ${item.done ? "[x]" : "[ ]"} ${item.text}
1936
+ criteria += `- ${item.done ? "[x]" : "[ ]"} ${item.text}
2112
1937
  `;
2113
1938
  }
2114
- prompt += `
1939
+ sections += `
1940
+ <acceptance_criteria>
1941
+ ${criteria.trimEnd()}
1942
+ </acceptance_criteria>
2115
1943
  `;
2116
1944
  }
2117
1945
  if (task.comments && task.comments.length > 0) {
2118
- const comments = task.comments.slice(0, 3);
2119
- prompt += `## Task History & Feedback
1946
+ const filteredComments = task.comments.filter((comment) => comment.author !== "system");
1947
+ const comments = filteredComments.slice(0, 3);
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}
2120
1953
  `;
2121
- prompt += `Review the following comments for context or rejection feedback:
2122
-
2123
- `;
2124
- for (const comment of comments) {
2125
- const date = new Date(comment.createdAt).toLocaleString();
2126
- prompt += `### ${comment.author} (${date})
2127
- ${comment.text}
2128
-
1954
+ }
1955
+ sections += `
1956
+ <feedback>
1957
+ ${commentsContent.trimEnd()}
1958
+ </feedback>
2129
1959
  `;
2130
1960
  }
2131
1961
  }
2132
- prompt += `## Instructions
2133
- 1. Complete this task.
2134
- 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.
2135
- 3. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
2136
- 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.
2137
- 5. **Progress**: Do NOT modify \`.locus/project/progress.md\`. The system updates it automatically.`;
2138
- 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>`;
2139
1976
  }
2140
1977
  async buildGenericPrompt(query) {
2141
- let prompt = `# Direct Execution
2142
-
2143
- `;
2144
- prompt += `## Prompt
2145
- ${query}
2146
-
2147
- `;
2148
- const projectConfig = this.getProjectConfig();
2149
- if (projectConfig) {
2150
- 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>
2151
1987
  `;
2152
- prompt += `- Version: ${projectConfig.version || "Unknown"}
1988
+ }
1989
+ sections += `
1990
+ <knowledge_base>
1991
+ ${knowledgeBase}
1992
+ </knowledge_base>
2153
1993
  `;
2154
- prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
2155
-
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>
2156
2000
  `;
2157
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() {
2158
2013
  const contextPath = getLocusPath(this.projectPath, "contextFile");
2159
- let hasLocalContext = false;
2160
- if (import_node_fs4.existsSync(contextPath)) {
2014
+ if (import_node_fs3.existsSync(contextPath)) {
2161
2015
  try {
2162
- const context = import_node_fs4.readFileSync(contextPath, "utf-8");
2016
+ const context = import_node_fs3.readFileSync(contextPath, "utf-8");
2163
2017
  if (context.trim().length > 20) {
2164
- prompt += `## Project Context (Local)
2165
- ${context}
2166
-
2167
- `;
2168
- hasLocalContext = true;
2018
+ return context;
2169
2019
  }
2170
2020
  } catch (err) {
2171
2021
  console.warn(`Warning: Could not read context file: ${err}`);
2172
2022
  }
2173
2023
  }
2174
- if (!hasLocalContext) {
2175
- const fallback = this.getFallbackContext();
2176
- if (fallback) {
2177
- prompt += `## Project Context (README Fallback)
2178
- ${fallback}
2179
-
2180
- `;
2181
- }
2182
- }
2183
- prompt += this.getProjectStructure();
2184
- prompt += `## Project Knowledge Base
2185
- `;
2186
- prompt += `You have access to the following documentation directories for context:
2187
- `;
2188
- prompt += `- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
2189
- `;
2190
- prompt += `- Documents: \`.locus/documents\` (synced from cloud)
2191
- `;
2192
- prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
2193
-
2194
- `;
2195
- const indexPath = getLocusPath(this.projectPath, "indexFile");
2196
- if (import_node_fs4.existsSync(indexPath)) {
2197
- prompt += `## Codebase Overview
2198
- There is an index file in the .locus/codebase-index.json and if you need you can check it.
2199
-
2200
- `;
2201
- }
2202
- prompt += `## Instructions
2203
- 1. Execute the prompt based on the provided project context.
2204
- 2. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
2205
- 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.
2206
- 4. **Progress**: Do NOT modify \`.locus/project/progress.md\`. The system updates it automatically.`;
2207
- return prompt;
2208
- }
2209
- getProjectConfig() {
2210
- const configPath = getLocusPath(this.projectPath, "configFile");
2211
- if (import_node_fs4.existsSync(configPath)) {
2212
- try {
2213
- return JSON.parse(import_node_fs4.readFileSync(configPath, "utf-8"));
2214
- } catch {
2215
- return null;
2216
- }
2217
- }
2218
- return null;
2024
+ return this.getFallbackContext() || null;
2219
2025
  }
2220
2026
  getFallbackContext() {
2221
- const readmePath = import_node_path6.join(this.projectPath, "README.md");
2222
- if (import_node_fs4.existsSync(readmePath)) {
2027
+ const readmePath = import_node_path5.join(this.projectPath, "README.md");
2028
+ if (import_node_fs3.existsSync(readmePath)) {
2223
2029
  try {
2224
- const content = import_node_fs4.readFileSync(readmePath, "utf-8");
2030
+ const content = import_node_fs3.readFileSync(readmePath, "utf-8");
2225
2031
  const limit = 1000;
2226
2032
  return content.slice(0, limit) + (content.length > limit ? `
2227
2033
  ...(truncated)...` : "");
@@ -2231,32 +2037,28 @@ There is an index file in the .locus/codebase-index.json and if you need you can
2231
2037
  }
2232
2038
  return "";
2233
2039
  }
2234
- 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
+ }
2235
2051
  try {
2236
- const entries = import_node_fs4.readdirSync(this.projectPath);
2237
- const folders = entries.filter((e) => {
2238
- if (e.startsWith(".") || e === "node_modules")
2239
- return false;
2240
- try {
2241
- return import_node_fs4.statSync(import_node_path6.join(this.projectPath, e)).isDirectory();
2242
- } catch {
2243
- return false;
2244
- }
2245
- });
2246
- if (folders.length === 0)
2247
- return "";
2248
- let structure = `## Project Structure
2249
- `;
2250
- structure += `Key directories in this project:
2251
- `;
2252
- for (const folder of folders) {
2253
- structure += `- \`${folder}/\`
2254
- `;
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;
2255
2057
  }
2256
- return `${structure}
2257
- `;
2058
+ return lines.join(`
2059
+ `);
2258
2060
  } catch {
2259
- return "";
2061
+ return null;
2260
2062
  }
2261
2063
  }
2262
2064
  roleToText(role) {
@@ -2279,11 +2081,11 @@ There is an index file in the .locus/codebase-index.json and if you need you can
2279
2081
  }
2280
2082
  }
2281
2083
  }
2282
- var import_node_fs4, import_node_path6, import_shared2;
2084
+ var import_node_fs3, import_node_path5, import_shared2;
2283
2085
  var init_prompt_builder = __esm(() => {
2284
2086
  init_config();
2285
- import_node_fs4 = require("node:fs");
2286
- import_node_path6 = require("node:path");
2087
+ import_node_fs3 = require("node:fs");
2088
+ import_node_path5 = require("node:path");
2287
2089
  import_shared2 = require("@locusai/shared");
2288
2090
  });
2289
2091
 
@@ -2401,7 +2203,6 @@ class AgentWorker {
2401
2203
  client;
2402
2204
  aiRunner;
2403
2205
  taskExecutor;
2404
- knowledgeBase;
2405
2206
  gitWorkflow;
2406
2207
  maxTasks = 50;
2407
2208
  tasksCompleted = 0;
@@ -2441,7 +2242,6 @@ class AgentWorker {
2441
2242
  projectPath,
2442
2243
  log
2443
2244
  });
2444
- this.knowledgeBase = new KnowledgeBase(projectPath);
2445
2245
  this.gitWorkflow = new GitWorkflow(config, log);
2446
2246
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
2447
2247
  this.log(`Using ${providerLabel} CLI for all phases`, "info");
@@ -2515,20 +2315,6 @@ class AgentWorker {
2515
2315
  };
2516
2316
  }
2517
2317
  }
2518
- updateProgress(task, summary) {
2519
- try {
2520
- this.knowledgeBase.updateProgress({
2521
- role: "user",
2522
- content: task.title
2523
- });
2524
- this.knowledgeBase.updateProgress({
2525
- role: "assistant",
2526
- content: summary
2527
- });
2528
- } catch (err) {
2529
- this.log(`Failed to update progress: ${err instanceof Error ? err.message : String(err)}`, "warn");
2530
- }
2531
- }
2532
2318
  startHeartbeat() {
2533
2319
  this.sendHeartbeat();
2534
2320
  this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), 60000);
@@ -2582,7 +2368,7 @@ class AgentWorker {
2582
2368
  assignedTo: null
2583
2369
  });
2584
2370
  await this.client.tasks.addComment(task.id, this.config.workspaceId, {
2585
- author: this.config.agentId,
2371
+ author: "system",
2586
2372
  text: `⚠️ Agent execution finished with no file changes, so no commit was created.
2587
2373
 
2588
2374
  ${result.summary}`
@@ -2597,13 +2383,12 @@ ${result.summary}`
2597
2383
 
2598
2384
  Branch: \`${result.branch}\`` : "";
2599
2385
  await this.client.tasks.addComment(task.id, this.config.workspaceId, {
2600
- author: this.config.agentId,
2386
+ author: "system",
2601
2387
  text: `✅ ${result.summary}${branchInfo}`
2602
2388
  });
2603
2389
  this.tasksCompleted++;
2604
2390
  this.completedTaskList.push({ title: task.title, id: task.id });
2605
2391
  this.taskSummaries.push(result.summary);
2606
- this.updateProgress(task, result.summary);
2607
2392
  }
2608
2393
  } else {
2609
2394
  this.log(`Failed: ${task.title} - ${result.summary}`, "error");
@@ -2612,7 +2397,7 @@ Branch: \`${result.branch}\`` : "";
2612
2397
  assignedTo: null
2613
2398
  });
2614
2399
  await this.client.tasks.addComment(task.id, this.config.workspaceId, {
2615
- author: this.config.agentId,
2400
+ author: "system",
2616
2401
  text: `❌ ${result.summary}`
2617
2402
  });
2618
2403
  }
@@ -2648,7 +2433,6 @@ var init_worker = __esm(() => {
2648
2433
  init_config();
2649
2434
  init_git_utils();
2650
2435
  init_src();
2651
- init_knowledge_base();
2652
2436
  init_colors();
2653
2437
  init_git_workflow();
2654
2438
  init_task_executor();
@@ -2673,6 +2457,7 @@ __export(exports_index_node, {
2673
2457
  sprintPlanToMarkdown: () => sprintPlanToMarkdown,
2674
2458
  plannedTasksToCreatePayloads: () => plannedTasksToCreatePayloads,
2675
2459
  parseSprintPlanFromAI: () => parseSprintPlanFromAI,
2460
+ parseJsonWithSchema: () => parseJsonWithSchema,
2676
2461
  getRemoteUrl: () => getRemoteUrl,
2677
2462
  getLocusPath: () => getLocusPath,
2678
2463
  getDefaultBranch: () => getDefaultBranch,
@@ -2701,7 +2486,6 @@ __export(exports_index_node, {
2701
2486
  LOCUS_SCHEMAS: () => LOCUS_SCHEMAS,
2702
2487
  LOCUS_GITIGNORE_PATTERNS: () => LOCUS_GITIGNORE_PATTERNS,
2703
2488
  LOCUS_CONFIG: () => LOCUS_CONFIG,
2704
- KnowledgeBase: () => KnowledgeBase,
2705
2489
  InvitationsModule: () => InvitationsModule,
2706
2490
  HistoryManager: () => HistoryManager,
2707
2491
  GitWorkflow: () => GitWorkflow,
@@ -2725,8 +2509,8 @@ module.exports = __toCommonJS(exports_index_node);
2725
2509
 
2726
2510
  // src/core/indexer.ts
2727
2511
  var import_node_crypto2 = require("node:crypto");
2728
- var import_node_fs5 = require("node:fs");
2729
- var import_node_path7 = require("node:path");
2512
+ var import_node_fs4 = require("node:fs");
2513
+ var import_node_path6 = require("node:path");
2730
2514
  var import_globby = require("globby");
2731
2515
 
2732
2516
  class CodebaseIndexer {
@@ -2735,7 +2519,7 @@ class CodebaseIndexer {
2735
2519
  fullReindexRatioThreshold = 0.2;
2736
2520
  constructor(projectPath) {
2737
2521
  this.projectPath = projectPath;
2738
- this.indexPath = import_node_path7.join(projectPath, ".locus", "codebase-index.json");
2522
+ this.indexPath = import_node_path6.join(projectPath, ".locus", "codebase-index.json");
2739
2523
  }
2740
2524
  async index(onProgress, treeSummarizer, force = false) {
2741
2525
  if (!treeSummarizer) {
@@ -2791,11 +2575,11 @@ class CodebaseIndexer {
2791
2575
  }
2792
2576
  }
2793
2577
  async getFileTree() {
2794
- const gitmodulesPath = import_node_path7.join(this.projectPath, ".gitmodules");
2578
+ const gitmodulesPath = import_node_path6.join(this.projectPath, ".gitmodules");
2795
2579
  const submoduleIgnores = [];
2796
- if (import_node_fs5.existsSync(gitmodulesPath)) {
2580
+ if (import_node_fs4.existsSync(gitmodulesPath)) {
2797
2581
  try {
2798
- const content = import_node_fs5.readFileSync(gitmodulesPath, "utf-8");
2582
+ const content = import_node_fs4.readFileSync(gitmodulesPath, "utf-8");
2799
2583
  const lines = content.split(`
2800
2584
  `);
2801
2585
  for (const line of lines) {
@@ -2851,9 +2635,9 @@ class CodebaseIndexer {
2851
2635
  });
2852
2636
  }
2853
2637
  loadIndex() {
2854
- if (import_node_fs5.existsSync(this.indexPath)) {
2638
+ if (import_node_fs4.existsSync(this.indexPath)) {
2855
2639
  try {
2856
- return JSON.parse(import_node_fs5.readFileSync(this.indexPath, "utf-8"));
2640
+ return JSON.parse(import_node_fs4.readFileSync(this.indexPath, "utf-8"));
2857
2641
  } catch {
2858
2642
  return null;
2859
2643
  }
@@ -2861,11 +2645,11 @@ class CodebaseIndexer {
2861
2645
  return null;
2862
2646
  }
2863
2647
  saveIndex(index) {
2864
- const dir = import_node_path7.dirname(this.indexPath);
2865
- if (!import_node_fs5.existsSync(dir)) {
2866
- 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 });
2867
2651
  }
2868
- import_node_fs5.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
2652
+ import_node_fs4.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
2869
2653
  }
2870
2654
  cloneIndex(index) {
2871
2655
  return JSON.parse(JSON.stringify(index));
@@ -2881,7 +2665,7 @@ class CodebaseIndexer {
2881
2665
  }
2882
2666
  hashFile(filePath) {
2883
2667
  try {
2884
- 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");
2885
2669
  return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
2886
2670
  } catch {
2887
2671
  return null;
@@ -2945,43 +2729,42 @@ class CodebaseIndexer {
2945
2729
  // src/utils/json-extractor.ts
2946
2730
  function extractJsonFromLLMOutput(raw) {
2947
2731
  const trimmed = raw.trim();
2948
- const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
2949
- if (codeBlockMatch) {
2950
- return codeBlockMatch[1]?.trim() || "";
2951
- }
2952
- const startIdx = trimmed.indexOf("{");
2953
- if (startIdx === -1) {
2954
- return trimmed;
2955
- }
2956
- let depth = 0;
2957
- let inString = false;
2958
- let escaped = false;
2959
- for (let i = startIdx;i < trimmed.length; i++) {
2960
- const ch = trimmed[i];
2961
- if (escaped) {
2962
- escaped = false;
2963
- continue;
2964
- }
2965
- 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
+ }
2966
2747
  if (ch === "\\") {
2967
- escaped = true;
2968
- } else if (ch === '"') {
2969
- inString = false;
2748
+ isEscape = true;
2749
+ continue;
2970
2750
  }
2971
- continue;
2972
- }
2973
- if (ch === '"') {
2974
- inString = true;
2975
- } else if (ch === "{") {
2976
- depth++;
2977
- } else if (ch === "}") {
2978
- depth--;
2979
- if (depth === 0) {
2980
- return trimmed.slice(startIdx, i + 1);
2751
+ if (ch === '"') {
2752
+ inString = !inString;
2753
+ continue;
2754
+ }
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
+ }
2981
2764
  }
2982
2765
  }
2983
2766
  }
2984
- return trimmed.slice(startIdx);
2767
+ return trimmed;
2985
2768
  }
2986
2769
 
2987
2770
  // src/agent/codebase-indexer-service.ts
@@ -2995,22 +2778,30 @@ class CodebaseIndexerService {
2995
2778
  async reindex(force = false) {
2996
2779
  try {
2997
2780
  const index = await this.indexer.index((msg) => this.deps.log(msg, "info"), async (tree) => {
2998
- const prompt = `You are a codebase analysis expert. Analyze the file tree and extract:
2999
- 1. Key symbols (classes, functions, types) and their locations
3000
- 2. Responsibilities of each directory/file
3001
- 3. Overall project structure
3002
-
3003
- Analyze this file tree and provide a JSON response with:
3004
- - "symbols": object mapping symbol names to file paths (array)
3005
- - "responsibilities": object mapping paths to brief descriptions
2781
+ const prompt = `<codebase_analysis>
2782
+ Analyze this codebase file tree and extract key information.
3006
2783
 
3007
- File tree:
2784
+ <file_tree>
3008
2785
  ${tree}
2786
+ </file_tree>
3009
2787
 
3010
- Return ONLY valid JSON, no markdown formatting.`;
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>
2793
+
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>`;
3011
2802
  const response = await this.deps.aiRunner.run(prompt);
3012
- const jsonStr = extractJsonFromLLMOutput(response);
3013
2803
  try {
2804
+ const jsonStr = extractJsonFromLLMOutput(response);
3014
2805
  return JSON.parse(jsonStr);
3015
2806
  } catch {
3016
2807
  return { symbols: {}, responsibilities: {}, lastIndexed: "" };
@@ -3029,8 +2820,8 @@ Return ONLY valid JSON, no markdown formatting.`;
3029
2820
  }
3030
2821
  // src/agent/document-fetcher.ts
3031
2822
  init_config();
3032
- var import_node_fs6 = require("node:fs");
3033
- var import_node_path8 = require("node:path");
2823
+ var import_node_fs5 = require("node:fs");
2824
+ var import_node_path7 = require("node:path");
3034
2825
 
3035
2826
  class DocumentFetcher {
3036
2827
  deps;
@@ -3039,8 +2830,8 @@ class DocumentFetcher {
3039
2830
  }
3040
2831
  async fetch() {
3041
2832
  const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
3042
- if (!import_node_fs6.existsSync(documentsDir)) {
3043
- import_node_fs6.mkdirSync(documentsDir, { recursive: true });
2833
+ if (!import_node_fs5.existsSync(documentsDir)) {
2834
+ import_node_fs5.mkdirSync(documentsDir, { recursive: true });
3044
2835
  }
3045
2836
  try {
3046
2837
  const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
@@ -3053,14 +2844,14 @@ class DocumentFetcher {
3053
2844
  continue;
3054
2845
  }
3055
2846
  const groupName = groupMap.get(doc.groupId || "") || "General";
3056
- const groupDir = import_node_path8.join(documentsDir, groupName);
3057
- if (!import_node_fs6.existsSync(groupDir)) {
3058
- 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 });
3059
2850
  }
3060
2851
  const fileName = `${doc.title}.md`;
3061
- const filePath = import_node_path8.join(groupDir, fileName);
3062
- if (!import_node_fs6.existsSync(filePath) || import_node_fs6.readFileSync(filePath, "utf-8") !== doc.content) {
3063
- 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 || "");
3064
2855
  fetchedCount++;
3065
2856
  }
3066
2857
  }
@@ -3376,7 +3167,6 @@ class PrService {
3376
3167
 
3377
3168
  // src/agent/reviewer-worker.ts
3378
3169
  init_src();
3379
- init_knowledge_base();
3380
3170
  init_colors();
3381
3171
  function resolveProvider2(value) {
3382
3172
  if (!value || value.startsWith("--"))
@@ -3391,7 +3181,6 @@ class ReviewerWorker {
3391
3181
  client;
3392
3182
  aiRunner;
3393
3183
  prService;
3394
- knowledgeBase;
3395
3184
  heartbeatInterval = null;
3396
3185
  currentTaskId = null;
3397
3186
  maxReviews = 50;
@@ -3417,7 +3206,6 @@ class ReviewerWorker {
3417
3206
  log
3418
3207
  });
3419
3208
  this.prService = new PrService(projectPath, log);
3420
- this.knowledgeBase = new KnowledgeBase(projectPath);
3421
3209
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
3422
3210
  this.log(`Reviewer agent using ${providerLabel} CLI`, "info");
3423
3211
  }
@@ -3533,17 +3321,6 @@ ${summary}`;
3533
3321
  this.sendHeartbeat();
3534
3322
  const result = await this.reviewPr(pr);
3535
3323
  if (result.reviewed) {
3536
- const status = result.approved ? "APPROVED" : "CHANGES REQUESTED";
3537
- try {
3538
- this.knowledgeBase.updateProgress({
3539
- role: "user",
3540
- content: `Review PR #${pr.number}: ${pr.title}`
3541
- });
3542
- this.knowledgeBase.updateProgress({
3543
- role: "assistant",
3544
- content: `${status}: ${result.summary}`
3545
- });
3546
- } catch {}
3547
3324
  this.reviewsCompleted++;
3548
3325
  } else {
3549
3326
  this.log(`Review skipped: ${result.summary}`, "warn");
@@ -4022,8 +3799,8 @@ class ExecEventEmitter {
4022
3799
  }
4023
3800
  // src/exec/history-manager.ts
4024
3801
  init_config();
4025
- var import_node_fs7 = require("node:fs");
4026
- var import_node_path9 = require("node:path");
3802
+ var import_node_fs6 = require("node:fs");
3803
+ var import_node_path8 = require("node:path");
4027
3804
  var DEFAULT_MAX_SESSIONS = 30;
4028
3805
  function generateSessionId2() {
4029
3806
  const timestamp = Date.now().toString(36);
@@ -4035,30 +3812,30 @@ class HistoryManager {
4035
3812
  historyDir;
4036
3813
  maxSessions;
4037
3814
  constructor(projectPath, options) {
4038
- 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);
4039
3816
  this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
4040
3817
  this.ensureHistoryDir();
4041
3818
  }
4042
3819
  ensureHistoryDir() {
4043
- if (!import_node_fs7.existsSync(this.historyDir)) {
4044
- 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 });
4045
3822
  }
4046
3823
  }
4047
3824
  getSessionPath(sessionId) {
4048
- return import_node_path9.join(this.historyDir, `${sessionId}.json`);
3825
+ return import_node_path8.join(this.historyDir, `${sessionId}.json`);
4049
3826
  }
4050
3827
  saveSession(session) {
4051
3828
  const filePath = this.getSessionPath(session.id);
4052
3829
  session.updatedAt = Date.now();
4053
- 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");
4054
3831
  }
4055
3832
  loadSession(sessionId) {
4056
3833
  const filePath = this.getSessionPath(sessionId);
4057
- if (!import_node_fs7.existsSync(filePath)) {
3834
+ if (!import_node_fs6.existsSync(filePath)) {
4058
3835
  return null;
4059
3836
  }
4060
3837
  try {
4061
- const content = import_node_fs7.readFileSync(filePath, "utf-8");
3838
+ const content = import_node_fs6.readFileSync(filePath, "utf-8");
4062
3839
  return JSON.parse(content);
4063
3840
  } catch {
4064
3841
  return null;
@@ -4066,18 +3843,18 @@ class HistoryManager {
4066
3843
  }
4067
3844
  deleteSession(sessionId) {
4068
3845
  const filePath = this.getSessionPath(sessionId);
4069
- if (!import_node_fs7.existsSync(filePath)) {
3846
+ if (!import_node_fs6.existsSync(filePath)) {
4070
3847
  return false;
4071
3848
  }
4072
3849
  try {
4073
- import_node_fs7.rmSync(filePath);
3850
+ import_node_fs6.rmSync(filePath);
4074
3851
  return true;
4075
3852
  } catch {
4076
3853
  return false;
4077
3854
  }
4078
3855
  }
4079
3856
  listSessions(options) {
4080
- const files = import_node_fs7.readdirSync(this.historyDir);
3857
+ const files = import_node_fs6.readdirSync(this.historyDir);
4081
3858
  let sessions = [];
4082
3859
  for (const file of files) {
4083
3860
  if (file.endsWith(".json")) {
@@ -4150,11 +3927,11 @@ class HistoryManager {
4150
3927
  return deleted;
4151
3928
  }
4152
3929
  getSessionCount() {
4153
- const files = import_node_fs7.readdirSync(this.historyDir);
3930
+ const files = import_node_fs6.readdirSync(this.historyDir);
4154
3931
  return files.filter((f) => f.endsWith(".json")).length;
4155
3932
  }
4156
3933
  sessionExists(sessionId) {
4157
- return import_node_fs7.existsSync(this.getSessionPath(sessionId));
3934
+ return import_node_fs6.existsSync(this.getSessionPath(sessionId));
4158
3935
  }
4159
3936
  findSessionByPartialId(partialId) {
4160
3937
  const sessions = this.listSessions();
@@ -4168,12 +3945,12 @@ class HistoryManager {
4168
3945
  return this.historyDir;
4169
3946
  }
4170
3947
  clearAllSessions() {
4171
- const files = import_node_fs7.readdirSync(this.historyDir);
3948
+ const files = import_node_fs6.readdirSync(this.historyDir);
4172
3949
  let deleted = 0;
4173
3950
  for (const file of files) {
4174
3951
  if (file.endsWith(".json")) {
4175
3952
  try {
4176
- import_node_fs7.rmSync(import_node_path9.join(this.historyDir, file));
3953
+ import_node_fs6.rmSync(import_node_path8.join(this.historyDir, file));
4177
3954
  deleted++;
4178
3955
  } catch {}
4179
3956
  }
@@ -4449,8 +4226,8 @@ init_src();
4449
4226
  init_colors();
4450
4227
  init_resolve_bin();
4451
4228
  var import_node_child_process7 = require("node:child_process");
4452
- var import_node_fs8 = require("node:fs");
4453
- var import_node_path10 = require("node:path");
4229
+ var import_node_fs7 = require("node:fs");
4230
+ var import_node_path9 = require("node:path");
4454
4231
  var import_node_url = require("node:url");
4455
4232
  var import_shared4 = require("@locusai/shared");
4456
4233
  var import_events4 = require("events");
@@ -4718,14 +4495,14 @@ ${agentId} finished (exit code: ${code})`);
4718
4495
  }
4719
4496
  resolveWorkerPath() {
4720
4497
  const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/index.ts");
4721
- const currentModuleDir = import_node_path10.dirname(currentModulePath);
4498
+ const currentModuleDir = import_node_path9.dirname(currentModulePath);
4722
4499
  const potentialPaths = [
4723
- import_node_path10.join(currentModuleDir, "..", "agent", "worker.js"),
4724
- import_node_path10.join(currentModuleDir, "agent", "worker.js"),
4725
- import_node_path10.join(currentModuleDir, "worker.js"),
4726
- 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")
4727
4504
  ];
4728
- return potentialPaths.find((p) => import_node_fs8.existsSync(p));
4505
+ return potentialPaths.find((p) => import_node_fs7.existsSync(p));
4729
4506
  }
4730
4507
  }
4731
4508
  function killProcessTree(proc) {
@@ -4744,12 +4521,58 @@ function sleep(ms) {
4744
4521
  }
4745
4522
  // src/planning/plan-manager.ts
4746
4523
  init_config();
4747
- init_knowledge_base();
4748
- var import_node_fs9 = require("node:fs");
4749
- var import_node_path11 = require("node:path");
4524
+ var import_node_fs8 = require("node:fs");
4525
+ var import_node_path10 = require("node:path");
4750
4526
 
4751
4527
  // src/planning/sprint-plan.ts
4752
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;
4753
4576
  function sprintPlanToMarkdown(plan) {
4754
4577
  const lines = [];
4755
4578
  lines.push(`# Sprint Plan: ${plan.name}`);
@@ -4824,42 +4647,31 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
4824
4647
  }));
4825
4648
  }
4826
4649
  function parseSprintPlanFromAI(raw, directive) {
4827
- const jsonStr = extractJsonFromLLMOutput(raw);
4828
- let parsed;
4829
- try {
4830
- parsed = JSON.parse(jsonStr);
4831
- } catch (err) {
4832
- const preview = jsonStr.slice(0, 200);
4833
- throw new Error(`Failed to parse sprint plan JSON: ${err instanceof Error ? err.message : String(err)}
4834
- Extracted JSON preview: ${preview}`);
4835
- }
4836
- if (parsed.revisedPlan) {
4837
- parsed = parsed.revisedPlan;
4838
- }
4650
+ const planData = parseJsonWithSchema(raw, SprintPlanAIOutputSchema);
4839
4651
  const now = new Date().toISOString();
4840
4652
  const id = `plan-${Date.now()}`;
4841
- const tasks2 = (parsed.tasks || []).map((t, i) => ({
4653
+ const tasks2 = planData.tasks.map((t, i) => ({
4842
4654
  index: i + 1,
4843
- title: t.title || `Task ${i + 1}`,
4844
- description: t.description || "",
4845
- assigneeRole: t.assigneeRole || "BACKEND",
4846
- priority: t.priority || "MEDIUM",
4847
- complexity: t.complexity || 3,
4848
- acceptanceCriteria: t.acceptanceCriteria || [],
4849
- 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
4850
4662
  }));
4851
4663
  return {
4852
4664
  id,
4853
- name: parsed.name || "Unnamed Sprint",
4854
- goal: parsed.goal || directive,
4665
+ name: planData.name,
4666
+ goal: planData.goal || directive,
4855
4667
  directive,
4856
4668
  tasks: tasks2,
4857
- risks: (parsed.risks || []).map((r) => ({
4858
- description: r.description || "",
4859
- mitigation: r.mitigation || "",
4860
- severity: r.severity || "medium"
4669
+ risks: planData.risks.map((r) => ({
4670
+ description: r.description,
4671
+ mitigation: r.mitigation,
4672
+ severity: r.severity
4861
4673
  })),
4862
- estimatedDays: parsed.estimatedDays || 1,
4674
+ estimatedDays: planData.estimatedDays,
4863
4675
  status: "pending",
4864
4676
  createdAt: now,
4865
4677
  updatedAt: now
@@ -4868,28 +4680,26 @@ Extracted JSON preview: ${preview}`);
4868
4680
 
4869
4681
  // src/planning/plan-manager.ts
4870
4682
  class PlanManager {
4871
- projectPath;
4872
4683
  plansDir;
4873
4684
  constructor(projectPath) {
4874
- this.projectPath = projectPath;
4875
4685
  this.plansDir = getLocusPath(projectPath, "plansDir");
4876
4686
  }
4877
4687
  save(plan) {
4878
4688
  this.ensurePlansDir();
4879
4689
  const slug = this.slugify(plan.name);
4880
- const jsonPath = import_node_path11.join(this.plansDir, `${slug}.json`);
4881
- const mdPath = import_node_path11.join(this.plansDir, `sprint-${slug}.md`);
4882
- import_node_fs9.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
4883
- 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");
4884
4694
  return plan.id;
4885
4695
  }
4886
4696
  load(idOrSlug) {
4887
4697
  this.ensurePlansDir();
4888
- 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"));
4889
4699
  for (const file of files) {
4890
- const filePath = import_node_path11.join(this.plansDir, file);
4700
+ const filePath = import_node_path10.join(this.plansDir, file);
4891
4701
  try {
4892
- const plan = JSON.parse(import_node_fs9.readFileSync(filePath, "utf-8"));
4702
+ const plan = JSON.parse(import_node_fs8.readFileSync(filePath, "utf-8"));
4893
4703
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
4894
4704
  return plan;
4895
4705
  }
@@ -4899,11 +4709,11 @@ class PlanManager {
4899
4709
  }
4900
4710
  list(status) {
4901
4711
  this.ensurePlansDir();
4902
- 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"));
4903
4713
  const plans = [];
4904
4714
  for (const file of files) {
4905
4715
  try {
4906
- 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"));
4907
4717
  if (!status || plan.status === status) {
4908
4718
  plans.push(plan);
4909
4719
  }
@@ -4933,15 +4743,6 @@ class PlanManager {
4933
4743
  plan.status = "approved";
4934
4744
  plan.updatedAt = new Date().toISOString();
4935
4745
  this.save(plan);
4936
- const kb = new KnowledgeBase(this.projectPath);
4937
- kb.updateProgress({
4938
- role: "user",
4939
- content: `Start sprint: ${plan.name}`
4940
- });
4941
- kb.updateProgress({
4942
- role: "assistant",
4943
- content: `Sprint started with ${tasks2.length} tasks. Goal: ${plan.goal}`
4944
- });
4945
4746
  return { sprint, tasks: tasks2 };
4946
4747
  }
4947
4748
  reject(idOrSlug, feedback) {
@@ -4969,18 +4770,18 @@ class PlanManager {
4969
4770
  }
4970
4771
  delete(idOrSlug) {
4971
4772
  this.ensurePlansDir();
4972
- const files = import_node_fs9.readdirSync(this.plansDir);
4773
+ const files = import_node_fs8.readdirSync(this.plansDir);
4973
4774
  for (const file of files) {
4974
- const filePath = import_node_path11.join(this.plansDir, file);
4775
+ const filePath = import_node_path10.join(this.plansDir, file);
4975
4776
  if (!file.endsWith(".json"))
4976
4777
  continue;
4977
4778
  try {
4978
- const plan = JSON.parse(import_node_fs9.readFileSync(filePath, "utf-8"));
4779
+ const plan = JSON.parse(import_node_fs8.readFileSync(filePath, "utf-8"));
4979
4780
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
4980
- import_node_fs9.unlinkSync(filePath);
4981
- const mdPath = import_node_path11.join(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
4982
- if (import_node_fs9.existsSync(mdPath)) {
4983
- 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);
4984
4785
  }
4985
4786
  return;
4986
4787
  }
@@ -4994,8 +4795,8 @@ class PlanManager {
4994
4795
  return sprintPlanToMarkdown(plan);
4995
4796
  }
4996
4797
  ensurePlansDir() {
4997
- if (!import_node_fs9.existsSync(this.plansDir)) {
4998
- 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 });
4999
4800
  }
5000
4801
  }
5001
4802
  slugify(name) {
@@ -5004,226 +4805,76 @@ class PlanManager {
5004
4805
  }
5005
4806
  // src/planning/planning-meeting.ts
5006
4807
  init_config();
5007
- init_knowledge_base();
5008
- var import_node_fs10 = require("node:fs");
5009
-
5010
- // src/planning/agents/cross-task-reviewer.ts
5011
- function buildCrossTaskReviewerPrompt(input) {
5012
- let prompt = `# Role: Cross-Task Reviewer (Architect + Engineer + Planner)
5013
-
5014
- 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.
5015
-
5016
- ## Context
5017
-
5018
- In this system, tasks are executed SEQUENTIALLY by a single agent on ONE branch:
5019
- - Tasks run one at a time, in the order they appear in the array
5020
- - Each task's changes are committed before the next task starts
5021
- - Later tasks can see and build on earlier tasks' work
5022
- - The final result is a single branch with all changes, which becomes a pull request
5023
-
5024
- This means:
5025
- - Task ordering is critical — a task must NOT depend on a later task's output
5026
- - Foundation work (config, schemas, shared code) must come first
5027
- - Each task should be a focused, logical unit of work
5028
-
5029
- ## CEO Directive
5030
- > ${input.directive}
5031
- `;
5032
- if (input.feedback) {
5033
- prompt += `
5034
- ## CEO Feedback on Previous Plan
5035
- > ${input.feedback}
5036
-
5037
- IMPORTANT: Ensure the reviewed plan still addresses this feedback.
5038
- `;
5039
- }
5040
- prompt += `
5041
- ## Project Context
5042
- ${input.projectContext || "No project context available."}
5043
-
5044
- ## Sprint Plan to Review
5045
- ${input.plannerOutput}
5046
-
5047
- ## Your Review Checklist
5048
-
5049
- Go through EACH task and check for:
5050
-
5051
- ### 1. Ordering & Dependency Analysis
5052
- For each task, verify:
5053
- - Does it depend on any task that appears LATER in the list? If so, reorder.
5054
- - Are foundational tasks (config, schemas, shared code) at the beginning?
5055
- - Is the overall execution order logical?
5056
-
5057
- ### 2. Scope & Completeness
5058
- For each task, verify:
5059
- - Is the task well-scoped? Not too large, not too trivial?
5060
- - Does it include ALL changes needed for its goal (given earlier tasks are done)?
5061
- - Are there any missing tasks that should be added?
5062
-
5063
- ### 3. Description Quality Validation
5064
- For each task, verify the description is a clear, actionable implementation guide. Each description must specify:
5065
- - **What to do** — the specific goal and expected behavior/outcome
5066
- - **Where to do it** — specific files, modules, or directories to modify or create
5067
- - **How to do it** — implementation approach, patterns to follow, existing utilities to use
5068
- - **Boundaries** — what is NOT in scope for this task
5069
-
5070
- 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.
5071
-
5072
- ### 4. Risk Assessment
5073
- - Are there tasks that might fail or have unknowns?
5074
- - Is the sprint scope realistic for sequential execution?
5075
-
5076
- ## Output Format
5077
-
5078
- 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:
5079
-
5080
- {
5081
- "hasIssues": true | false,
5082
- "issues": [
5083
- {
5084
- "type": "wrong_order" | "missing_task" | "scope_issue" | "vague_description",
5085
- "description": "string describing the specific issue",
5086
- "affectedTasks": ["Task Title 1", "Task Title 2"],
5087
- "resolution": "string describing how to fix it"
5088
- }
5089
- ],
5090
- "revisedPlan": {
5091
- "name": "string (2-4 words)",
5092
- "goal": "string (1 paragraph)",
5093
- "estimatedDays": 3,
5094
- "tasks": [
5095
- {
5096
- "title": "string",
5097
- "description": "string (detailed implementation guide: what to do, where to do it, how to do it, and boundaries)",
5098
- "assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
5099
- "priority": "CRITICAL | HIGH | MEDIUM | LOW",
5100
- "labels": ["string"],
5101
- "acceptanceCriteria": ["string"],
5102
- "complexity": 3
5103
- }
5104
- ],
5105
- "risks": [
5106
- {
5107
- "description": "string",
5108
- "mitigation": "string",
5109
- "severity": "low | medium | high"
5110
- }
5111
- ]
5112
- }
5113
- }
5114
-
5115
- IMPORTANT:
5116
- - If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (reordered, descriptions rewritten, missing tasks added, etc.)
5117
- - If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
5118
- - The revisedPlan is ALWAYS required — it becomes the final plan
5119
- - Ensure every task description is a detailed implementation guide (what, where, how, boundaries) — rewrite vague descriptions
5120
- - Tasks execute sequentially — the array order IS the execution order`;
5121
- return prompt;
5122
- }
4808
+ var import_node_fs9 = require("node:fs");
4809
+ var import_node_path11 = require("node:path");
5123
4810
 
5124
4811
  // src/planning/agents/planner.ts
5125
4812
  function buildPlannerPrompt(input) {
5126
- let prompt = `# Role: Sprint Planner
5127
-
5128
- 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.
5129
-
5130
- ## CEO Directive
5131
- > ${input.directive}
5132
- `;
4813
+ let feedbackSection = "";
5133
4814
  if (input.feedback) {
5134
- prompt += `
5135
- ## CEO Feedback on Previous Plan
5136
- > ${input.feedback}
5137
-
5138
- 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>
5139
4820
  `;
5140
4821
  }
5141
- prompt += `
5142
- ## Project Context
5143
- ${input.projectContext || "No project context available."}
5144
-
5145
- ## Codebase Structure
5146
- ${input.codebaseIndex || "No codebase index available."}
5147
-
5148
- ## Your Task
5149
-
5150
- Analyze the directive and produce a **complete sprint plan** with the following:
5151
-
5152
- 1. **Sprint Name** A concise, memorable name (2-4 words)
5153
- 2. **Sprint Goal** One paragraph describing what this sprint delivers
5154
- 3. **Duration Estimate** — How many days this sprint will take with a single agent working sequentially
5155
- 4. **Task Breakdown** — An ordered list of tasks that fully implement the directive
5156
- 5. **Risk Assessment** — Potential risks with mitigations
5157
-
5158
- ### Task Requirements
5159
-
5160
- For each task, provide:
5161
- - **Title** — Clear, action-oriented (e.g., "Implement user registration API endpoint")
5162
- - **Description** — A detailed, actionable implementation guide (see below)
5163
- - **Assignee Role** — BACKEND, FRONTEND, QA, PM, or DESIGN
5164
- - **Priority** — CRITICAL, HIGH, MEDIUM, or LOW
5165
- - **Complexity** — 1 (trivial) to 5 (very complex)
5166
- - **Labels** — Relevant tags (e.g., "api", "database", "ui", "auth")
5167
- - **Acceptance Criteria** — Specific, testable conditions for completion
5168
-
5169
- ### CRITICAL: Task Description Requirements
5170
-
5171
- 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:
5172
-
5173
- 1. **What to do** — Clearly state the goal and expected behavior/outcome
5174
- 2. **Where to do it** — List specific files, modules, or directories to modify or create. Reference existing code paths when extending functionality
5175
- 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
5176
- 4. **Boundaries** — What is NOT in scope for this task to prevent overlap with other tasks
5177
-
5178
- Bad example: "Add authentication to the API."
5179
- 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."
5180
-
5181
- ### CRITICAL: Task Ordering Rules
5182
-
5183
- Tasks are executed SEQUENTIALLY by a single agent on ONE branch. The agent works through tasks in array order. Therefore:
5184
-
5185
- 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.
5186
- 2. **No forward dependencies.** A task must NOT depend on a task that appears later in the list.
5187
- 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.
5188
- 4. **Keep tasks focused.** Each task should do one logical unit of work. Avoid trivially small or overly large tasks.
5189
- 5. **Merge related trivial work.** If two pieces of work are trivially small and tightly related, combine them into one task.
5190
-
5191
- ### Sprint Scope Guidelines
5192
-
5193
- - If the sprint would exceed 12 tasks, reduce scope or merge related tasks
5194
- - Ensure acceptance criteria are specific and testable
5195
- - Keep the sprint focused on the directive — avoid scope creep
5196
-
5197
- ## Output Format
5198
-
5199
- 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:
5200
-
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:
5201
4841
  {
5202
- "name": "string (2-4 words)",
5203
- "goal": "string (1 paragraph)",
5204
- "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}",
5205
4850
  "tasks": [
5206
4851
  {
5207
- "title": "string",
5208
- "description": "string (detailed implementation guide: what, where, how, boundaries)",
5209
- "assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
5210
- "priority": "CRITICAL | HIGH | MEDIUM | LOW",
5211
- "labels": ["string"],
5212
- "acceptanceCriteria": ["string"],
5213
- "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
5214
4860
  }
5215
4861
  ],
5216
4862
  "risks": [
5217
4863
  {
5218
- "description": "string",
5219
- "mitigation": "string",
5220
- "severity": "low | medium | high"
4864
+ "description": "What could go wrong",
4865
+ "mitigation": "How to handle it",
4866
+ "severity": "low|medium|high"
5221
4867
  }
5222
4868
  ]
5223
4869
  }
5224
4870
 
5225
- 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.`;
5226
- 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>`;
5227
4878
  }
5228
4879
 
5229
4880
  // src/planning/planning-meeting.ts
@@ -5239,68 +4890,41 @@ class PlanningMeeting {
5239
4890
  });
5240
4891
  }
5241
4892
  async run(directive, feedback) {
5242
- const projectContext = this.getProjectContext();
5243
- const codebaseIndex = this.getCodebaseIndex();
5244
- this.log("Phase 1/2: Planner building sprint plan...", "info");
5245
- const plannerPrompt = buildPlannerPrompt({
5246
- directive,
5247
- projectContext,
5248
- codebaseIndex,
5249
- feedback
5250
- });
5251
- const plannerOutput = await this.aiRunner.run(plannerPrompt);
5252
- this.log("Planner phase complete.", "success");
5253
- this.log("Phase 2/2: Reviewer checking for conflicts and quality...", "info");
5254
- 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({
5255
4902
  directive,
5256
- projectContext,
5257
- plannerOutput,
5258
- feedback
4903
+ feedback,
4904
+ plansDir,
4905
+ planId,
4906
+ fileName
5259
4907
  });
5260
- const reviewOutput = await this.aiRunner.run(crossTaskReviewerPrompt);
5261
- this.log("Review phase complete.", "success");
5262
- 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
+ }
5263
4920
  if (feedback) {
5264
4921
  plan.feedback = feedback;
5265
4922
  }
5266
4923
  return {
5267
4924
  plan,
5268
- phaseOutputs: {
5269
- planner: plannerOutput,
5270
- review: reviewOutput
5271
- }
4925
+ rawOutput: response
5272
4926
  };
5273
4927
  }
5274
- getProjectContext() {
5275
- const kb = new KnowledgeBase(this.projectPath);
5276
- return kb.getFullContext();
5277
- }
5278
- getCodebaseIndex() {
5279
- const indexPath = getLocusPath(this.projectPath, "indexFile");
5280
- if (!import_node_fs10.existsSync(indexPath)) {
5281
- return "";
5282
- }
5283
- try {
5284
- const raw = import_node_fs10.readFileSync(indexPath, "utf-8");
5285
- const index = JSON.parse(raw);
5286
- const parts = [];
5287
- if (index.responsibilities) {
5288
- parts.push("### File Responsibilities");
5289
- const entries = Object.entries(index.responsibilities);
5290
- for (const [file, summary] of entries.slice(0, 50)) {
5291
- parts.push(`- \`${file}\`: ${summary}`);
5292
- }
5293
- if (entries.length > 50) {
5294
- parts.push(`... and ${entries.length - 50} more files`);
5295
- }
5296
- }
5297
- return parts.join(`
5298
- `);
5299
- } catch {
5300
- return "";
5301
- }
5302
- }
5303
4928
  }
5304
4929
  // src/index-node.ts
5305
- init_knowledge_base();
5306
4930
  init_colors();