@locusai/cli 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 (3) hide show
  1. package/bin/agent/worker.js +130 -323
  2. package/bin/locus.js +433 -827
  3. package/package.json +2 -2
package/bin/locus.js CHANGED
@@ -6320,43 +6320,42 @@ var init_indexer = __esm(() => {
6320
6320
  // ../sdk/src/utils/json-extractor.ts
6321
6321
  function extractJsonFromLLMOutput(raw) {
6322
6322
  const trimmed = raw.trim();
6323
- const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
6324
- if (codeBlockMatch) {
6325
- return codeBlockMatch[1]?.trim() || "";
6326
- }
6327
- const startIdx = trimmed.indexOf("{");
6328
- if (startIdx === -1) {
6329
- return trimmed;
6330
- }
6331
- let depth = 0;
6332
- let inString = false;
6333
- let escaped = false;
6334
- for (let i = startIdx;i < trimmed.length; i++) {
6335
- const ch = trimmed[i];
6336
- if (escaped) {
6337
- escaped = false;
6338
- continue;
6339
- }
6340
- if (inString) {
6323
+ const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
6324
+ if (fenceMatch) {
6325
+ return fenceMatch[1].trim();
6326
+ }
6327
+ const start = trimmed.indexOf("{");
6328
+ if (start !== -1) {
6329
+ let depth = 0;
6330
+ let inString = false;
6331
+ let isEscape = false;
6332
+ for (let i = start;i < trimmed.length; i++) {
6333
+ const ch = trimmed[i];
6334
+ if (isEscape) {
6335
+ isEscape = false;
6336
+ continue;
6337
+ }
6341
6338
  if (ch === "\\") {
6342
- escaped = true;
6343
- } else if (ch === '"') {
6344
- inString = false;
6339
+ isEscape = true;
6340
+ continue;
6345
6341
  }
6346
- continue;
6347
- }
6348
- if (ch === '"') {
6349
- inString = true;
6350
- } else if (ch === "{") {
6351
- depth++;
6352
- } else if (ch === "}") {
6353
- depth--;
6354
- if (depth === 0) {
6355
- return trimmed.slice(startIdx, i + 1);
6342
+ if (ch === '"') {
6343
+ inString = !inString;
6344
+ continue;
6345
+ }
6346
+ if (inString)
6347
+ continue;
6348
+ if (ch === "{")
6349
+ depth++;
6350
+ else if (ch === "}") {
6351
+ depth--;
6352
+ if (depth === 0) {
6353
+ return trimmed.slice(start, i + 1);
6354
+ }
6356
6355
  }
6357
6356
  }
6358
6357
  }
6359
- return trimmed.slice(startIdx);
6358
+ return trimmed;
6360
6359
  }
6361
6360
 
6362
6361
  // ../sdk/src/agent/codebase-indexer-service.ts
@@ -6367,9 +6366,6 @@ var init_codebase_indexer_service = __esm(() => {
6367
6366
  // ../sdk/src/core/config.ts
6368
6367
  import { join as join2 } from "node:path";
6369
6368
  function getLocusPath(projectPath, fileName) {
6370
- if (fileName === "projectContextFile" || fileName === "projectProgressFile") {
6371
- return join2(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.projectDir, LOCUS_CONFIG[fileName]);
6372
- }
6373
6369
  return join2(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
6374
6370
  }
6375
6371
  var PROVIDER, DEFAULT_MODEL, LOCUS_SCHEMA_BASE_URL = "https://locusai.dev/schemas", LOCUS_SCHEMAS, LOCUS_CONFIG, LOCUS_GITIGNORE_PATTERNS;
@@ -6392,14 +6388,12 @@ var init_config = __esm(() => {
6392
6388
  settingsFile: "settings.json",
6393
6389
  indexFile: "codebase-index.json",
6394
6390
  contextFile: "LOCUS.md",
6391
+ learningsFile: "LEARNINGS.md",
6395
6392
  artifactsDir: "artifacts",
6396
6393
  documentsDir: "documents",
6397
6394
  sessionsDir: "sessions",
6398
6395
  reviewsDir: "reviews",
6399
- plansDir: "plans",
6400
- projectDir: "project",
6401
- projectContextFile: "context.md",
6402
- projectProgressFile: "progress.md"
6396
+ plansDir: "plans"
6403
6397
  };
6404
6398
  LOCUS_GITIGNORE_PATTERNS = [
6405
6399
  "# Locus AI - Session data (user-specific, can grow large)",
@@ -6417,11 +6411,8 @@ var init_config = __esm(() => {
6417
6411
  "# Locus AI - Settings (contains API key, telegram config, etc.)",
6418
6412
  ".locus/settings.json",
6419
6413
  "",
6420
- "# Locus AI - Configuration (contains project context, progress, etc.)",
6421
- ".locus/config.json",
6422
- "",
6423
- "# Locus AI - Project progress (contains project progress, etc.)",
6424
- ".locus/project/progress.md"
6414
+ "# Locus AI - Configuration (contains project context, etc.)",
6415
+ ".locus/config.json"
6425
6416
  ];
6426
6417
  });
6427
6418
 
@@ -7111,17 +7102,22 @@ class ClaudeRunner {
7111
7102
  });
7112
7103
  });
7113
7104
  }
7114
- async* runStream(prompt) {
7105
+ buildCliArgs() {
7115
7106
  const args = [
7116
- "--dangerously-skip-permissions",
7117
7107
  "--print",
7118
- "--verbose",
7119
7108
  "--output-format",
7120
7109
  "stream-json",
7110
+ "--verbose",
7111
+ "--dangerously-skip-permissions",
7112
+ "--no-session-persistence",
7121
7113
  "--include-partial-messages",
7122
7114
  "--model",
7123
7115
  this.model
7124
7116
  ];
7117
+ return args;
7118
+ }
7119
+ async* runStream(prompt) {
7120
+ const args = this.buildCliArgs();
7125
7121
  const env = getAugmentedEnv({
7126
7122
  FORCE_COLOR: "1",
7127
7123
  TERM: "xterm-256color"
@@ -7361,16 +7357,7 @@ class ClaudeRunner {
7361
7357
  }
7362
7358
  executeRun(prompt) {
7363
7359
  return new Promise((resolve2, reject) => {
7364
- const args = [
7365
- "--dangerously-skip-permissions",
7366
- "--print",
7367
- "--verbose",
7368
- "--output-format",
7369
- "stream-json",
7370
- "--include-partial-messages",
7371
- "--model",
7372
- this.model
7373
- ];
7360
+ const args = this.buildCliArgs();
7374
7361
  const env = getAugmentedEnv({
7375
7362
  FORCE_COLOR: "1",
7376
7363
  TERM: "xterm-256color"
@@ -7495,7 +7482,7 @@ class CodexRunner {
7495
7482
  eventEmitter;
7496
7483
  currentToolName;
7497
7484
  timeoutMs;
7498
- constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log, timeoutMs, reasoningEffort) {
7485
+ constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log, reasoningEffort, timeoutMs) {
7499
7486
  this.projectPath = projectPath;
7500
7487
  this.model = model;
7501
7488
  this.log = log;
@@ -7821,7 +7808,7 @@ function createAiRunner(provider, config) {
7821
7808
  const model = config.model ?? DEFAULT_MODEL[resolvedProvider];
7822
7809
  switch (resolvedProvider) {
7823
7810
  case PROVIDER.CODEX:
7824
- return new CodexRunner(config.projectPath, model, config.log, config.timeoutMs, config.reasoningEffort ?? "high");
7811
+ return new CodexRunner(config.projectPath, model, config.log, config.reasoningEffort ?? "high", config.timeoutMs);
7825
7812
  default:
7826
7813
  return new ClaudeRunner(config.projectPath, model, config.log, config.timeoutMs);
7827
7814
  }
@@ -38736,102 +38723,6 @@ var init_src2 = __esm(() => {
38736
38723
  init_workspaces();
38737
38724
  });
38738
38725
 
38739
- // ../sdk/src/project/knowledge-base.ts
38740
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
38741
- import { dirname as dirname2 } from "node:path";
38742
-
38743
- class KnowledgeBase {
38744
- contextPath;
38745
- progressPath;
38746
- constructor(projectPath) {
38747
- this.contextPath = getLocusPath(projectPath, "projectContextFile");
38748
- this.progressPath = getLocusPath(projectPath, "projectProgressFile");
38749
- }
38750
- readContext() {
38751
- if (!existsSync5(this.contextPath)) {
38752
- return "";
38753
- }
38754
- return readFileSync5(this.contextPath, "utf-8");
38755
- }
38756
- readProgress() {
38757
- if (!existsSync5(this.progressPath)) {
38758
- return "";
38759
- }
38760
- return readFileSync5(this.progressPath, "utf-8");
38761
- }
38762
- updateContext(content) {
38763
- this.ensureDir(this.contextPath);
38764
- writeFileSync3(this.contextPath, content);
38765
- }
38766
- updateProgress(entry) {
38767
- this.ensureDir(this.progressPath);
38768
- const existing = this.readProgress();
38769
- const timestamp = (entry.timestamp ?? new Date).toISOString();
38770
- const label = entry.role === "user" ? "User" : "Assistant";
38771
- const line = `**${label}** (${timestamp}):
38772
- ${entry.content}`;
38773
- const updated = existing ? `${existing}
38774
-
38775
- ---
38776
-
38777
- ${line}` : `# Conversation History
38778
-
38779
- ${line}`;
38780
- writeFileSync3(this.progressPath, updated);
38781
- }
38782
- getFullContext() {
38783
- const context2 = this.readContext();
38784
- const parts = [];
38785
- if (context2.trim()) {
38786
- parts.push(context2.trim());
38787
- }
38788
- return parts.join(`
38789
-
38790
- ---
38791
-
38792
- `);
38793
- }
38794
- initialize(info) {
38795
- this.ensureDir(this.contextPath);
38796
- this.ensureDir(this.progressPath);
38797
- const techStackList = info.techStack.map((t) => `- ${t}`).join(`
38798
- `);
38799
- const contextContent = `# Project: ${info.name}
38800
-
38801
- ## Mission
38802
- ${info.mission}
38803
-
38804
- ## Tech Stack
38805
- ${techStackList}
38806
-
38807
- ## Architecture
38808
- <!-- Describe your high-level architecture here -->
38809
-
38810
- ## Key Decisions
38811
- <!-- Document important technical decisions and their rationale -->
38812
-
38813
- ## Feature Areas
38814
- <!-- List your main feature areas and their status -->
38815
- `;
38816
- const progressContent = `# Conversation History
38817
- `;
38818
- writeFileSync3(this.contextPath, contextContent);
38819
- writeFileSync3(this.progressPath, progressContent);
38820
- }
38821
- get exists() {
38822
- return existsSync5(this.contextPath) || existsSync5(this.progressPath);
38823
- }
38824
- ensureDir(filePath) {
38825
- const dir = dirname2(filePath);
38826
- if (!existsSync5(dir)) {
38827
- mkdirSync3(dir, { recursive: true });
38828
- }
38829
- }
38830
- }
38831
- var init_knowledge_base = __esm(() => {
38832
- init_config();
38833
- });
38834
-
38835
38726
  // ../sdk/src/agent/reviewer-worker.ts
38836
38727
  function resolveProvider(value) {
38837
38728
  if (!value || value.startsWith("--"))
@@ -38846,7 +38737,6 @@ class ReviewerWorker {
38846
38737
  client;
38847
38738
  aiRunner;
38848
38739
  prService;
38849
- knowledgeBase;
38850
38740
  heartbeatInterval = null;
38851
38741
  currentTaskId = null;
38852
38742
  maxReviews = 50;
@@ -38872,7 +38762,6 @@ class ReviewerWorker {
38872
38762
  log
38873
38763
  });
38874
38764
  this.prService = new PrService(projectPath, log);
38875
- this.knowledgeBase = new KnowledgeBase(projectPath);
38876
38765
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
38877
38766
  this.log(`Reviewer agent using ${providerLabel} CLI`, "info");
38878
38767
  }
@@ -38988,17 +38877,6 @@ ${summary}`;
38988
38877
  this.sendHeartbeat();
38989
38878
  const result = await this.reviewPr(pr);
38990
38879
  if (result.reviewed) {
38991
- const status = result.approved ? "APPROVED" : "CHANGES REQUESTED";
38992
- try {
38993
- this.knowledgeBase.updateProgress({
38994
- role: "user",
38995
- content: `Review PR #${pr.number}: ${pr.title}`
38996
- });
38997
- this.knowledgeBase.updateProgress({
38998
- role: "assistant",
38999
- content: `${status}: ${result.summary}`
39000
- });
39001
- } catch {}
39002
38880
  this.reviewsCompleted++;
39003
38881
  } else {
39004
38882
  this.log(`Review skipped: ${result.summary}`, "warn");
@@ -39017,7 +38895,6 @@ var init_reviewer_worker = __esm(() => {
39017
38895
  init_git_utils();
39018
38896
  init_pr_service();
39019
38897
  init_src2();
39020
- init_knowledge_base();
39021
38898
  init_colors();
39022
38899
  reviewerEntrypoint = process.argv[1]?.split(/[\\/]/).pop();
39023
38900
  if (reviewerEntrypoint === "reviewer-worker.js" || reviewerEntrypoint === "reviewer-worker.ts") {
@@ -39060,7 +38937,7 @@ var init_reviewer_worker = __esm(() => {
39060
38937
  });
39061
38938
 
39062
38939
  // ../sdk/src/core/prompt-builder.ts
39063
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync } from "node:fs";
38940
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
39064
38941
  import { join as join6 } from "node:path";
39065
38942
 
39066
38943
  class PromptBuilder {
@@ -39068,223 +38945,157 @@ class PromptBuilder {
39068
38945
  constructor(projectPath) {
39069
38946
  this.projectPath = projectPath;
39070
38947
  }
39071
- async build(task2, options = {}) {
39072
- let prompt = `# Task: ${task2.title}
39073
-
39074
- `;
38948
+ async build(task2) {
39075
38949
  const roleText = this.roleToText(task2.assigneeRole);
38950
+ const description = task2.description || "No description provided.";
38951
+ const context2 = this.getProjectContext();
38952
+ const learnings = this.getLearningsContent();
38953
+ const knowledgeBase = this.getKnowledgeBaseSection();
38954
+ let sections = "";
39076
38955
  if (roleText) {
39077
- prompt += `## Role
38956
+ sections += `
38957
+ <role>
39078
38958
  You are acting as a ${roleText}.
39079
-
39080
- `;
39081
- }
39082
- prompt += `## Description
39083
- ${task2.description || "No description provided."}
39084
-
39085
- `;
39086
- const projectConfig = this.getProjectConfig();
39087
- if (projectConfig) {
39088
- prompt += `## Project Metadata
39089
- `;
39090
- prompt += `- Version: ${projectConfig.version || "Unknown"}
38959
+ </role>
39091
38960
  `;
39092
- prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
39093
-
39094
- `;
39095
- }
39096
- let serverContext = null;
39097
- if (options.taskContext) {
39098
- try {
39099
- serverContext = JSON.parse(options.taskContext);
39100
- } catch {
39101
- serverContext = { context: options.taskContext };
39102
- }
39103
38961
  }
39104
- const contextPath = getLocusPath(this.projectPath, "contextFile");
39105
- let hasLocalContext = false;
39106
- if (existsSync6(contextPath)) {
39107
- try {
39108
- const context2 = readFileSync6(contextPath, "utf-8");
39109
- if (context2.trim().length > 20) {
39110
- prompt += `## Project Context (Local)
38962
+ if (context2) {
38963
+ sections += `
38964
+ <project_context>
39111
38965
  ${context2}
39112
-
38966
+ </project_context>
39113
38967
  `;
39114
- hasLocalContext = true;
39115
- }
39116
- } catch (err) {
39117
- console.warn(`Warning: Could not read context file: ${err}`);
39118
- }
39119
38968
  }
39120
- if (!hasLocalContext) {
39121
- const fallback = this.getFallbackContext();
39122
- if (fallback) {
39123
- prompt += `## Project Context (README Fallback)
39124
- ${fallback}
39125
-
39126
- `;
39127
- }
39128
- }
39129
- if (serverContext) {
39130
- prompt += `## Project Context (Server)
39131
- `;
39132
- const project = serverContext.project;
39133
- if (project) {
39134
- prompt += `- Project: ${project.name || "Unknown"}
38969
+ sections += `
38970
+ <knowledge_base>
38971
+ ${knowledgeBase}
38972
+ </knowledge_base>
39135
38973
  `;
39136
- if (!hasLocalContext && project.techStack?.length) {
39137
- prompt += `- Tech Stack: ${project.techStack.join(", ")}
39138
- `;
39139
- }
39140
- }
39141
- if (serverContext.context) {
39142
- prompt += `
39143
- ${serverContext.context}
39144
- `;
39145
- }
39146
- prompt += `
38974
+ if (learnings) {
38975
+ sections += `
38976
+ <learnings>
38977
+ These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
38978
+ ${learnings}
38979
+ </learnings>
39147
38980
  `;
39148
38981
  }
39149
- prompt += this.getProjectStructure();
39150
- prompt += `## Project Knowledge Base
39151
- `;
39152
- prompt += `You have access to the following documentation directories for context:
39153
- `;
39154
- prompt += `- Artifacts: \`.locus/artifacts\`
39155
- `;
39156
- prompt += `- Documents: \`.locus/documents\`
39157
- `;
39158
- prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
39159
-
39160
- `;
39161
38982
  if (task2.docs && task2.docs.length > 0) {
39162
- prompt += `## Attached Documents (Summarized)
39163
- `;
39164
- prompt += `> Full content available on server. Rely on Task Description for specific requirements.
39165
-
39166
- `;
38983
+ let docsContent = "";
39167
38984
  for (const doc3 of task2.docs) {
39168
38985
  const content = doc3.content || "";
39169
38986
  const limit = 800;
39170
38987
  const preview = content.slice(0, limit);
39171
38988
  const isTruncated = content.length > limit;
39172
- prompt += `### Doc: ${doc3.title}
38989
+ docsContent += `### ${doc3.title}
39173
38990
  ${preview}${isTruncated ? `
39174
38991
  ...(truncated)...` : ""}
39175
38992
 
39176
38993
  `;
39177
38994
  }
38995
+ sections += `
38996
+ <documents>
38997
+ ${docsContent.trimEnd()}
38998
+ </documents>
38999
+ `;
39178
39000
  }
39179
39001
  if (task2.acceptanceChecklist && task2.acceptanceChecklist.length > 0) {
39180
- prompt += `## Acceptance Criteria
39181
- `;
39002
+ let criteria = "";
39182
39003
  for (const item of task2.acceptanceChecklist) {
39183
- prompt += `- ${item.done ? "[x]" : "[ ]"} ${item.text}
39004
+ criteria += `- ${item.done ? "[x]" : "[ ]"} ${item.text}
39184
39005
  `;
39185
39006
  }
39186
- prompt += `
39007
+ sections += `
39008
+ <acceptance_criteria>
39009
+ ${criteria.trimEnd()}
39010
+ </acceptance_criteria>
39187
39011
  `;
39188
39012
  }
39189
39013
  if (task2.comments && task2.comments.length > 0) {
39190
39014
  const filteredComments = task2.comments.filter((comment) => comment.author !== "system");
39191
39015
  const comments = filteredComments.slice(0, 3);
39192
- prompt += `## Task Comments & Feedback
39016
+ if (comments.length > 0) {
39017
+ let commentsContent = "";
39018
+ for (const comment of comments) {
39019
+ const date5 = new Date(comment.createdAt).toLocaleString();
39020
+ commentsContent += `- ${comment.author} (${date5}): ${comment.text}
39193
39021
  `;
39194
- for (const comment of comments) {
39195
- const date5 = new Date(comment.createdAt).toLocaleString();
39196
- prompt += `- ${comment.author} (${date5}): ${comment.text}
39022
+ }
39023
+ sections += `
39024
+ <feedback>
39025
+ ${commentsContent.trimEnd()}
39026
+ </feedback>
39197
39027
  `;
39198
39028
  }
39199
- prompt += `
39200
- `;
39201
39029
  }
39202
- prompt += `## Instructions
39203
- 1. Complete this task.
39204
- 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.
39205
- 3. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
39206
- 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.
39207
- 5. **Progress**: Do NOT modify \`.locus/project/progress.md\`. The system updates it automatically.`;
39208
- return prompt;
39030
+ return `<task_execution>
39031
+ Complete this task: ${task2.title}
39032
+
39033
+ <description>
39034
+ ${description}
39035
+ </description>
39036
+ ${sections}
39037
+ <rules>
39038
+ - Complete the task as described
39039
+ - Save any high-level documentation (PRDs, technical drafts, architecture docs) in \`.locus/artifacts/\`
39040
+ - Use relative paths from the project root at all times — no absolute local paths
39041
+ - Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches — Locus handles git automatically
39042
+ </rules>
39043
+ </task_execution>`;
39209
39044
  }
39210
39045
  async buildGenericPrompt(query) {
39211
- let prompt = `# Direct Execution
39212
-
39213
- `;
39214
- prompt += `## Prompt
39215
- ${query}
39216
-
39217
- `;
39218
- const projectConfig = this.getProjectConfig();
39219
- if (projectConfig) {
39220
- prompt += `## Project Metadata
39046
+ const context2 = this.getProjectContext();
39047
+ const learnings = this.getLearningsContent();
39048
+ const knowledgeBase = this.getKnowledgeBaseSection();
39049
+ let sections = "";
39050
+ if (context2) {
39051
+ sections += `
39052
+ <project_context>
39053
+ ${context2}
39054
+ </project_context>
39221
39055
  `;
39222
- prompt += `- Version: ${projectConfig.version || "Unknown"}
39056
+ }
39057
+ sections += `
39058
+ <knowledge_base>
39059
+ ${knowledgeBase}
39060
+ </knowledge_base>
39223
39061
  `;
39224
- prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
39225
-
39062
+ if (learnings) {
39063
+ sections += `
39064
+ <learnings>
39065
+ These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
39066
+ ${learnings}
39067
+ </learnings>
39226
39068
  `;
39227
39069
  }
39070
+ return `<direct_execution>
39071
+ Execute this prompt: ${query}
39072
+ ${sections}
39073
+ <rules>
39074
+ - Execute the prompt based on the provided project context
39075
+ - Use relative paths from the project root at all times — no absolute local paths
39076
+ - Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches — Locus handles git automatically
39077
+ </rules>
39078
+ </direct_execution>`;
39079
+ }
39080
+ getProjectContext() {
39228
39081
  const contextPath = getLocusPath(this.projectPath, "contextFile");
39229
- let hasLocalContext = false;
39230
- if (existsSync6(contextPath)) {
39082
+ if (existsSync5(contextPath)) {
39231
39083
  try {
39232
- const context2 = readFileSync6(contextPath, "utf-8");
39084
+ const context2 = readFileSync5(contextPath, "utf-8");
39233
39085
  if (context2.trim().length > 20) {
39234
- prompt += `## Project Context (Local)
39235
- ${context2}
39236
-
39237
- `;
39238
- hasLocalContext = true;
39086
+ return context2;
39239
39087
  }
39240
39088
  } catch (err) {
39241
39089
  console.warn(`Warning: Could not read context file: ${err}`);
39242
39090
  }
39243
39091
  }
39244
- if (!hasLocalContext) {
39245
- const fallback = this.getFallbackContext();
39246
- if (fallback) {
39247
- prompt += `## Project Context (README Fallback)
39248
- ${fallback}
39249
-
39250
- `;
39251
- }
39252
- }
39253
- prompt += this.getProjectStructure();
39254
- prompt += `## Project Knowledge Base
39255
- `;
39256
- prompt += `You have access to the following documentation directories for context:
39257
- `;
39258
- prompt += `- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
39259
- `;
39260
- prompt += `- Documents: \`.locus/documents\` (synced from cloud)
39261
- `;
39262
- prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
39263
-
39264
- `;
39265
- prompt += `## Instructions
39266
- 1. Execute the prompt based on the provided project context.
39267
- 2. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
39268
- 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.
39269
- 4. **Progress**: Do NOT modify \`.locus/project/progress.md\`. The system updates it automatically.`;
39270
- return prompt;
39271
- }
39272
- getProjectConfig() {
39273
- const configPath = getLocusPath(this.projectPath, "configFile");
39274
- if (existsSync6(configPath)) {
39275
- try {
39276
- return JSON.parse(readFileSync6(configPath, "utf-8"));
39277
- } catch {
39278
- return null;
39279
- }
39280
- }
39281
- return null;
39092
+ return this.getFallbackContext() || null;
39282
39093
  }
39283
39094
  getFallbackContext() {
39284
39095
  const readmePath = join6(this.projectPath, "README.md");
39285
- if (existsSync6(readmePath)) {
39096
+ if (existsSync5(readmePath)) {
39286
39097
  try {
39287
- const content = readFileSync6(readmePath, "utf-8");
39098
+ const content = readFileSync5(readmePath, "utf-8");
39288
39099
  const limit = 1000;
39289
39100
  return content.slice(0, limit) + (content.length > limit ? `
39290
39101
  ...(truncated)...` : "");
@@ -39294,32 +39105,28 @@ ${fallback}
39294
39105
  }
39295
39106
  return "";
39296
39107
  }
39297
- getProjectStructure() {
39108
+ getKnowledgeBaseSection() {
39109
+ return `You have access to the following documentation directories for context:
39110
+ - Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
39111
+ - Documents: \`.locus/documents\` (synced from cloud)
39112
+ If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
39113
+ }
39114
+ getLearningsContent() {
39115
+ const learningsPath = getLocusPath(this.projectPath, "learningsFile");
39116
+ if (!existsSync5(learningsPath)) {
39117
+ return null;
39118
+ }
39298
39119
  try {
39299
- const entries = readdirSync2(this.projectPath);
39300
- const folders = entries.filter((e) => {
39301
- if (e.startsWith(".") || e === "node_modules")
39302
- return false;
39303
- try {
39304
- return statSync(join6(this.projectPath, e)).isDirectory();
39305
- } catch {
39306
- return false;
39307
- }
39308
- });
39309
- if (folders.length === 0)
39310
- return "";
39311
- let structure = `## Project Structure
39312
- `;
39313
- structure += `Key directories in this project:
39314
- `;
39315
- for (const folder of folders) {
39316
- structure += `- \`${folder}/\`
39317
- `;
39120
+ const content = readFileSync5(learningsPath, "utf-8");
39121
+ const lines = content.split(`
39122
+ `).filter((l) => l.startsWith("- "));
39123
+ if (lines.length === 0) {
39124
+ return null;
39318
39125
  }
39319
- return `${structure}
39320
- `;
39126
+ return lines.join(`
39127
+ `);
39321
39128
  } catch {
39322
- return "";
39129
+ return null;
39323
39130
  }
39324
39131
  }
39325
39132
  roleToText(role) {
@@ -39455,7 +39262,6 @@ class AgentWorker {
39455
39262
  client;
39456
39263
  aiRunner;
39457
39264
  taskExecutor;
39458
- knowledgeBase;
39459
39265
  gitWorkflow;
39460
39266
  maxTasks = 50;
39461
39267
  tasksCompleted = 0;
@@ -39495,7 +39301,6 @@ class AgentWorker {
39495
39301
  projectPath,
39496
39302
  log
39497
39303
  });
39498
- this.knowledgeBase = new KnowledgeBase(projectPath);
39499
39304
  this.gitWorkflow = new GitWorkflow(config2, log);
39500
39305
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
39501
39306
  this.log(`Using ${providerLabel} CLI for all phases`, "info");
@@ -39569,20 +39374,6 @@ class AgentWorker {
39569
39374
  };
39570
39375
  }
39571
39376
  }
39572
- updateProgress(task2, summary) {
39573
- try {
39574
- this.knowledgeBase.updateProgress({
39575
- role: "user",
39576
- content: task2.title
39577
- });
39578
- this.knowledgeBase.updateProgress({
39579
- role: "assistant",
39580
- content: summary
39581
- });
39582
- } catch (err) {
39583
- this.log(`Failed to update progress: ${err instanceof Error ? err.message : String(err)}`, "warn");
39584
- }
39585
- }
39586
39377
  startHeartbeat() {
39587
39378
  this.sendHeartbeat();
39588
39379
  this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), 60000);
@@ -39657,7 +39448,6 @@ Branch: \`${result.branch}\`` : "";
39657
39448
  this.tasksCompleted++;
39658
39449
  this.completedTaskList.push({ title: task2.title, id: task2.id });
39659
39450
  this.taskSummaries.push(result.summary);
39660
- this.updateProgress(task2, result.summary);
39661
39451
  }
39662
39452
  } else {
39663
39453
  this.log(`Failed: ${task2.title} - ${result.summary}`, "error");
@@ -39703,7 +39493,6 @@ var init_worker = __esm(() => {
39703
39493
  init_config();
39704
39494
  init_git_utils();
39705
39495
  init_src2();
39706
- init_knowledge_base();
39707
39496
  init_colors();
39708
39497
  init_git_workflow();
39709
39498
  init_task_executor();
@@ -40153,12 +39942,12 @@ var init_event_emitter = __esm(() => {
40153
39942
 
40154
39943
  // ../sdk/src/exec/history-manager.ts
40155
39944
  import {
40156
- existsSync as existsSync7,
40157
- mkdirSync as mkdirSync4,
40158
- readdirSync as readdirSync3,
40159
- readFileSync as readFileSync7,
39945
+ existsSync as existsSync6,
39946
+ mkdirSync as mkdirSync3,
39947
+ readdirSync as readdirSync2,
39948
+ readFileSync as readFileSync6,
40160
39949
  rmSync,
40161
- writeFileSync as writeFileSync4
39950
+ writeFileSync as writeFileSync3
40162
39951
  } from "node:fs";
40163
39952
  import { join as join7 } from "node:path";
40164
39953
  function generateSessionId2() {
@@ -40176,8 +39965,8 @@ class HistoryManager {
40176
39965
  this.ensureHistoryDir();
40177
39966
  }
40178
39967
  ensureHistoryDir() {
40179
- if (!existsSync7(this.historyDir)) {
40180
- mkdirSync4(this.historyDir, { recursive: true });
39968
+ if (!existsSync6(this.historyDir)) {
39969
+ mkdirSync3(this.historyDir, { recursive: true });
40181
39970
  }
40182
39971
  }
40183
39972
  getSessionPath(sessionId) {
@@ -40186,15 +39975,15 @@ class HistoryManager {
40186
39975
  saveSession(session2) {
40187
39976
  const filePath = this.getSessionPath(session2.id);
40188
39977
  session2.updatedAt = Date.now();
40189
- writeFileSync4(filePath, JSON.stringify(session2, null, 2), "utf-8");
39978
+ writeFileSync3(filePath, JSON.stringify(session2, null, 2), "utf-8");
40190
39979
  }
40191
39980
  loadSession(sessionId) {
40192
39981
  const filePath = this.getSessionPath(sessionId);
40193
- if (!existsSync7(filePath)) {
39982
+ if (!existsSync6(filePath)) {
40194
39983
  return null;
40195
39984
  }
40196
39985
  try {
40197
- const content = readFileSync7(filePath, "utf-8");
39986
+ const content = readFileSync6(filePath, "utf-8");
40198
39987
  return JSON.parse(content);
40199
39988
  } catch {
40200
39989
  return null;
@@ -40202,7 +39991,7 @@ class HistoryManager {
40202
39991
  }
40203
39992
  deleteSession(sessionId) {
40204
39993
  const filePath = this.getSessionPath(sessionId);
40205
- if (!existsSync7(filePath)) {
39994
+ if (!existsSync6(filePath)) {
40206
39995
  return false;
40207
39996
  }
40208
39997
  try {
@@ -40213,7 +40002,7 @@ class HistoryManager {
40213
40002
  }
40214
40003
  }
40215
40004
  listSessions(options) {
40216
- const files = readdirSync3(this.historyDir);
40005
+ const files = readdirSync2(this.historyDir);
40217
40006
  let sessions = [];
40218
40007
  for (const file2 of files) {
40219
40008
  if (file2.endsWith(".json")) {
@@ -40286,11 +40075,11 @@ class HistoryManager {
40286
40075
  return deleted;
40287
40076
  }
40288
40077
  getSessionCount() {
40289
- const files = readdirSync3(this.historyDir);
40078
+ const files = readdirSync2(this.historyDir);
40290
40079
  return files.filter((f) => f.endsWith(".json")).length;
40291
40080
  }
40292
40081
  sessionExists(sessionId) {
40293
- return existsSync7(this.getSessionPath(sessionId));
40082
+ return existsSync6(this.getSessionPath(sessionId));
40294
40083
  }
40295
40084
  findSessionByPartialId(partialId) {
40296
40085
  const sessions = this.listSessions();
@@ -40304,7 +40093,7 @@ class HistoryManager {
40304
40093
  return this.historyDir;
40305
40094
  }
40306
40095
  clearAllSessions() {
40307
- const files = readdirSync3(this.historyDir);
40096
+ const files = readdirSync2(this.historyDir);
40308
40097
  let deleted = 0;
40309
40098
  for (const file2 of files) {
40310
40099
  if (file2.endsWith(".json")) {
@@ -40598,8 +40387,8 @@ var init_git = __esm(() => {
40598
40387
 
40599
40388
  // ../sdk/src/orchestrator/index.ts
40600
40389
  import { spawn as spawn3 } from "node:child_process";
40601
- import { existsSync as existsSync8 } from "node:fs";
40602
- import { dirname as dirname3, join as join8 } from "node:path";
40390
+ import { existsSync as existsSync7 } from "node:fs";
40391
+ import { dirname as dirname2, join as join8 } from "node:path";
40603
40392
  import { fileURLToPath as fileURLToPath2 } from "node:url";
40604
40393
  import { EventEmitter as EventEmitter4 } from "events";
40605
40394
  function killProcessTree(proc) {
@@ -40886,18 +40675,21 @@ ${agentId} finished (exit code: ${code})`);
40886
40675
  }
40887
40676
  resolveWorkerPath() {
40888
40677
  const currentModulePath = fileURLToPath2(import.meta.url);
40889
- const currentModuleDir = dirname3(currentModulePath);
40678
+ const currentModuleDir = dirname2(currentModulePath);
40890
40679
  const potentialPaths = [
40891
40680
  join8(currentModuleDir, "..", "agent", "worker.js"),
40892
40681
  join8(currentModuleDir, "agent", "worker.js"),
40893
40682
  join8(currentModuleDir, "worker.js"),
40894
40683
  join8(currentModuleDir, "..", "agent", "worker.ts")
40895
40684
  ];
40896
- return potentialPaths.find((p) => existsSync8(p));
40685
+ return potentialPaths.find((p) => existsSync7(p));
40897
40686
  }
40898
40687
  };
40899
40688
  });
40900
40689
 
40690
+ // ../sdk/src/utils/structured-output.ts
40691
+ var init_structured_output = () => {};
40692
+
40901
40693
  // ../sdk/src/planning/sprint-plan.ts
40902
40694
  function sprintPlanToMarkdown(plan) {
40903
40695
  const lines = [];
@@ -40972,68 +40764,48 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
40972
40764
  }))
40973
40765
  }));
40974
40766
  }
40975
- function parseSprintPlanFromAI(raw, directive) {
40976
- const jsonStr = extractJsonFromLLMOutput(raw);
40977
- let parsed;
40978
- try {
40979
- parsed = JSON.parse(jsonStr);
40980
- } catch (err) {
40981
- const preview = jsonStr.slice(0, 200);
40982
- throw new Error(`Failed to parse sprint plan JSON: ${err instanceof Error ? err.message : String(err)}
40983
- Extracted JSON preview: ${preview}`);
40984
- }
40985
- if (parsed.revisedPlan) {
40986
- parsed = parsed.revisedPlan;
40987
- }
40988
- const now = new Date().toISOString();
40989
- const id = `plan-${Date.now()}`;
40990
- const tasks2 = (parsed.tasks || []).map((t, i) => ({
40991
- index: i + 1,
40992
- title: t.title || `Task ${i + 1}`,
40993
- description: t.description || "",
40994
- assigneeRole: t.assigneeRole || "BACKEND",
40995
- priority: t.priority || "MEDIUM",
40996
- complexity: t.complexity || 3,
40997
- acceptanceCriteria: t.acceptanceCriteria || [],
40998
- labels: t.labels || []
40999
- }));
41000
- return {
41001
- id,
41002
- name: parsed.name || "Unnamed Sprint",
41003
- goal: parsed.goal || directive,
41004
- directive,
41005
- tasks: tasks2,
41006
- risks: (parsed.risks || []).map((r) => ({
41007
- description: r.description || "",
41008
- mitigation: r.mitigation || "",
41009
- severity: r.severity || "medium"
41010
- })),
41011
- estimatedDays: parsed.estimatedDays || 1,
41012
- status: "pending",
41013
- createdAt: now,
41014
- updatedAt: now
41015
- };
41016
- }
40767
+ var PlannedTaskSchema, SprintPlanRiskSchema, PlannerOutputSchema;
41017
40768
  var init_sprint_plan = __esm(() => {
41018
40769
  init_src();
40770
+ init_zod();
40771
+ init_structured_output();
40772
+ PlannedTaskSchema = exports_external.object({
40773
+ title: exports_external.string().default("Untitled Task"),
40774
+ description: exports_external.string().default(""),
40775
+ assigneeRole: exports_external.enum(["BACKEND", "FRONTEND", "QA", "PM", "DESIGN"]).default("BACKEND"),
40776
+ priority: exports_external.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]).default("MEDIUM"),
40777
+ complexity: exports_external.number().min(1).max(5).default(3),
40778
+ acceptanceCriteria: exports_external.array(exports_external.string()).default([]),
40779
+ labels: exports_external.array(exports_external.string()).default([])
40780
+ });
40781
+ SprintPlanRiskSchema = exports_external.object({
40782
+ description: exports_external.string().default(""),
40783
+ mitigation: exports_external.string().default(""),
40784
+ severity: exports_external.enum(["low", "medium", "high"]).default("medium")
40785
+ });
40786
+ PlannerOutputSchema = exports_external.object({
40787
+ name: exports_external.string().default("Unnamed Sprint"),
40788
+ goal: exports_external.string().default(""),
40789
+ estimatedDays: exports_external.number().default(1),
40790
+ tasks: exports_external.array(PlannedTaskSchema).default([]),
40791
+ risks: exports_external.array(SprintPlanRiskSchema).default([])
40792
+ });
41019
40793
  });
41020
40794
 
41021
40795
  // ../sdk/src/planning/plan-manager.ts
41022
40796
  import {
41023
- existsSync as existsSync9,
41024
- mkdirSync as mkdirSync5,
41025
- readdirSync as readdirSync4,
41026
- readFileSync as readFileSync8,
40797
+ existsSync as existsSync8,
40798
+ mkdirSync as mkdirSync4,
40799
+ readdirSync as readdirSync3,
40800
+ readFileSync as readFileSync7,
41027
40801
  unlinkSync as unlinkSync2,
41028
- writeFileSync as writeFileSync5
40802
+ writeFileSync as writeFileSync4
41029
40803
  } from "node:fs";
41030
40804
  import { join as join9 } from "node:path";
41031
40805
 
41032
40806
  class PlanManager {
41033
- projectPath;
41034
40807
  plansDir;
41035
40808
  constructor(projectPath) {
41036
- this.projectPath = projectPath;
41037
40809
  this.plansDir = getLocusPath(projectPath, "plansDir");
41038
40810
  }
41039
40811
  save(plan) {
@@ -41041,17 +40813,17 @@ class PlanManager {
41041
40813
  const slug = this.slugify(plan.name);
41042
40814
  const jsonPath = join9(this.plansDir, `${slug}.json`);
41043
40815
  const mdPath = join9(this.plansDir, `sprint-${slug}.md`);
41044
- writeFileSync5(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
41045
- writeFileSync5(mdPath, sprintPlanToMarkdown(plan), "utf-8");
40816
+ writeFileSync4(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
40817
+ writeFileSync4(mdPath, sprintPlanToMarkdown(plan), "utf-8");
41046
40818
  return plan.id;
41047
40819
  }
41048
40820
  load(idOrSlug) {
41049
40821
  this.ensurePlansDir();
41050
- const files = readdirSync4(this.plansDir).filter((f) => f.endsWith(".json"));
40822
+ const files = readdirSync3(this.plansDir).filter((f) => f.endsWith(".json"));
41051
40823
  for (const file2 of files) {
41052
40824
  const filePath = join9(this.plansDir, file2);
41053
40825
  try {
41054
- const plan = JSON.parse(readFileSync8(filePath, "utf-8"));
40826
+ const plan = JSON.parse(readFileSync7(filePath, "utf-8"));
41055
40827
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
41056
40828
  return plan;
41057
40829
  }
@@ -41061,11 +40833,11 @@ class PlanManager {
41061
40833
  }
41062
40834
  list(status) {
41063
40835
  this.ensurePlansDir();
41064
- const files = readdirSync4(this.plansDir).filter((f) => f.endsWith(".json"));
40836
+ const files = readdirSync3(this.plansDir).filter((f) => f.endsWith(".json"));
41065
40837
  const plans = [];
41066
40838
  for (const file2 of files) {
41067
40839
  try {
41068
- const plan = JSON.parse(readFileSync8(join9(this.plansDir, file2), "utf-8"));
40840
+ const plan = JSON.parse(readFileSync7(join9(this.plansDir, file2), "utf-8"));
41069
40841
  if (!status || plan.status === status) {
41070
40842
  plans.push(plan);
41071
40843
  }
@@ -41095,15 +40867,6 @@ class PlanManager {
41095
40867
  plan.status = "approved";
41096
40868
  plan.updatedAt = new Date().toISOString();
41097
40869
  this.save(plan);
41098
- const kb = new KnowledgeBase(this.projectPath);
41099
- kb.updateProgress({
41100
- role: "user",
41101
- content: `Start sprint: ${plan.name}`
41102
- });
41103
- kb.updateProgress({
41104
- role: "assistant",
41105
- content: `Sprint started with ${tasks2.length} tasks. Goal: ${plan.goal}`
41106
- });
41107
40870
  return { sprint: sprint2, tasks: tasks2 };
41108
40871
  }
41109
40872
  reject(idOrSlug, feedback) {
@@ -41131,17 +40894,17 @@ class PlanManager {
41131
40894
  }
41132
40895
  delete(idOrSlug) {
41133
40896
  this.ensurePlansDir();
41134
- const files = readdirSync4(this.plansDir);
40897
+ const files = readdirSync3(this.plansDir);
41135
40898
  for (const file2 of files) {
41136
40899
  const filePath = join9(this.plansDir, file2);
41137
40900
  if (!file2.endsWith(".json"))
41138
40901
  continue;
41139
40902
  try {
41140
- const plan = JSON.parse(readFileSync8(filePath, "utf-8"));
40903
+ const plan = JSON.parse(readFileSync7(filePath, "utf-8"));
41141
40904
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
41142
40905
  unlinkSync2(filePath);
41143
40906
  const mdPath = join9(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
41144
- if (existsSync9(mdPath)) {
40907
+ if (existsSync8(mdPath)) {
41145
40908
  unlinkSync2(mdPath);
41146
40909
  }
41147
40910
  return;
@@ -41156,8 +40919,8 @@ class PlanManager {
41156
40919
  return sprintPlanToMarkdown(plan);
41157
40920
  }
41158
40921
  ensurePlansDir() {
41159
- if (!existsSync9(this.plansDir)) {
41160
- mkdirSync5(this.plansDir, { recursive: true });
40922
+ if (!existsSync8(this.plansDir)) {
40923
+ mkdirSync4(this.plansDir, { recursive: true });
41161
40924
  }
41162
40925
  }
41163
40926
  slugify(name) {
@@ -41166,227 +40929,82 @@ class PlanManager {
41166
40929
  }
41167
40930
  var init_plan_manager = __esm(() => {
41168
40931
  init_config();
41169
- init_knowledge_base();
41170
40932
  init_sprint_plan();
41171
40933
  });
41172
40934
 
41173
- // ../sdk/src/planning/agents/cross-task-reviewer.ts
41174
- function buildCrossTaskReviewerPrompt(input) {
41175
- let prompt = `# Role: Cross-Task Reviewer (Architect + Engineer + Planner)
41176
-
41177
- 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.
41178
-
41179
- ## Context
41180
-
41181
- In this system, tasks are executed SEQUENTIALLY by a single agent on ONE branch:
41182
- - Tasks run one at a time, in the order they appear in the array
41183
- - Each task's changes are committed before the next task starts
41184
- - Later tasks can see and build on earlier tasks' work
41185
- - The final result is a single branch with all changes, which becomes a pull request
41186
-
41187
- This means:
41188
- - Task ordering is critical — a task must NOT depend on a later task's output
41189
- - Foundation work (config, schemas, shared code) must come first
41190
- - Each task should be a focused, logical unit of work
41191
-
41192
- ## CEO Directive
41193
- > ${input.directive}
41194
- `;
41195
- if (input.feedback) {
41196
- prompt += `
41197
- ## CEO Feedback on Previous Plan
41198
- > ${input.feedback}
41199
-
41200
- IMPORTANT: Ensure the reviewed plan still addresses this feedback.
41201
- `;
41202
- }
41203
- prompt += `
41204
- ## Project Context
41205
- ${input.projectContext || "No project context available."}
41206
-
41207
- ## Sprint Plan to Review
41208
- ${input.plannerOutput}
41209
-
41210
- ## Your Review Checklist
41211
-
41212
- Go through EACH task and check for:
41213
-
41214
- ### 1. Ordering & Dependency Analysis
41215
- For each task, verify:
41216
- - Does it depend on any task that appears LATER in the list? If so, reorder.
41217
- - Are foundational tasks (config, schemas, shared code) at the beginning?
41218
- - Is the overall execution order logical?
41219
-
41220
- ### 2. Scope & Completeness
41221
- For each task, verify:
41222
- - Is the task well-scoped? Not too large, not too trivial?
41223
- - Does it include ALL changes needed for its goal (given earlier tasks are done)?
41224
- - Are there any missing tasks that should be added?
41225
-
41226
- ### 3. Description Quality Validation
41227
- For each task, verify the description is a clear, actionable implementation guide. Each description must specify:
41228
- - **What to do** — the specific goal and expected behavior/outcome
41229
- - **Where to do it** — specific files, modules, or directories to modify or create
41230
- - **How to do it** — implementation approach, patterns to follow, existing utilities to use
41231
- - **Boundaries** — what is NOT in scope for this task
41232
-
41233
- 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.
41234
-
41235
- ### 4. Risk Assessment
41236
- - Are there tasks that might fail or have unknowns?
41237
- - Is the sprint scope realistic for sequential execution?
41238
-
41239
- ## Output Format
41240
-
41241
- 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:
41242
-
41243
- {
41244
- "hasIssues": true | false,
41245
- "issues": [
41246
- {
41247
- "type": "wrong_order" | "missing_task" | "scope_issue" | "vague_description",
41248
- "description": "string describing the specific issue",
41249
- "affectedTasks": ["Task Title 1", "Task Title 2"],
41250
- "resolution": "string describing how to fix it"
41251
- }
41252
- ],
41253
- "revisedPlan": {
41254
- "name": "string (2-4 words)",
41255
- "goal": "string (1 paragraph)",
41256
- "estimatedDays": 3,
41257
- "tasks": [
41258
- {
41259
- "title": "string",
41260
- "description": "string (detailed implementation guide: what to do, where to do it, how to do it, and boundaries)",
41261
- "assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
41262
- "priority": "CRITICAL | HIGH | MEDIUM | LOW",
41263
- "labels": ["string"],
41264
- "acceptanceCriteria": ["string"],
41265
- "complexity": 3
41266
- }
41267
- ],
41268
- "risks": [
41269
- {
41270
- "description": "string",
41271
- "mitigation": "string",
41272
- "severity": "low | medium | high"
41273
- }
41274
- ]
41275
- }
41276
- }
41277
-
41278
- IMPORTANT:
41279
- - If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (reordered, descriptions rewritten, missing tasks added, etc.)
41280
- - If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
41281
- - The revisedPlan is ALWAYS required — it becomes the final plan
41282
- - Ensure every task description is a detailed implementation guide (what, where, how, boundaries) — rewrite vague descriptions
41283
- - Tasks execute sequentially — the array order IS the execution order`;
41284
- return prompt;
41285
- }
41286
-
41287
40935
  // ../sdk/src/planning/agents/planner.ts
41288
40936
  function buildPlannerPrompt(input) {
41289
- let prompt = `# Role: Sprint Planner
41290
-
41291
- 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.
41292
-
41293
- ## CEO Directive
41294
- > ${input.directive}
41295
- `;
40937
+ let feedbackSection = "";
41296
40938
  if (input.feedback) {
41297
- prompt += `
41298
- ## CEO Feedback on Previous Plan
41299
- > ${input.feedback}
41300
-
41301
- IMPORTANT: Incorporate this feedback into your plan. The CEO has reviewed a previous plan and wants changes.
40939
+ feedbackSection = `
40940
+ <ceo_feedback>
40941
+ The CEO has reviewed a previous plan and wants changes. Incorporate this feedback:
40942
+ ${input.feedback}
40943
+ </ceo_feedback>
41302
40944
  `;
41303
40945
  }
41304
- prompt += `
41305
- ## Project Context
41306
- ${input.projectContext || "No project context available."}
41307
-
41308
- ## Your Task
41309
-
41310
- Analyze the directive and produce a **complete sprint plan** with the following:
41311
-
41312
- 1. **Sprint Name** A concise, memorable name (2-4 words)
41313
- 2. **Sprint Goal** One paragraph describing what this sprint delivers
41314
- 3. **Duration Estimate** How many days this sprint will take with a single agent working sequentially
41315
- 4. **Task Breakdown** An ordered list of tasks that fully implement the directive
41316
- 5. **Risk Assessment** Potential risks with mitigations
41317
-
41318
- ### Task Requirements
41319
-
41320
- For each task, provide:
41321
- - **Title** — Clear, action-oriented (e.g., "Implement user registration API endpoint")
41322
- - **Description** A detailed, actionable implementation guide (see below)
41323
- - **Assignee Role** — BACKEND, FRONTEND, QA, PM, or DESIGN
41324
- - **Priority** — CRITICAL, HIGH, MEDIUM, or LOW
41325
- - **Complexity** — 1 (trivial) to 5 (very complex)
41326
- - **Labels** — Relevant tags (e.g., "api", "database", "ui", "auth")
41327
- - **Acceptance Criteria** — Specific, testable conditions for completion
41328
-
41329
- ### CRITICAL: Task Description Requirements
41330
-
41331
- 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:
41332
-
41333
- 1. **What to do** — Clearly state the goal and expected behavior/outcome
41334
- 2. **Where to do it** — List specific files, modules, or directories to modify or create. Reference existing code paths when extending functionality
41335
- 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
41336
- 4. **Boundaries** — What is NOT in scope for this task to prevent overlap with other tasks
41337
-
41338
- Bad example: "Add authentication to the API."
41339
- 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."
41340
-
41341
- ### CRITICAL: Task Ordering Rules
41342
-
41343
- Tasks are executed SEQUENTIALLY by a single agent on ONE branch. The agent works through tasks in array order. Therefore:
41344
-
41345
- 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.
41346
- 2. **No forward dependencies.** A task must NOT depend on a task that appears later in the list.
41347
- 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.
41348
- 4. **Keep tasks focused.** Each task should do one logical unit of work. Avoid trivially small or overly large tasks.
41349
- 5. **Merge related trivial work.** If two pieces of work are trivially small and tightly related, combine them into one task.
41350
-
41351
- ### Sprint Scope Guidelines
41352
-
41353
- - If the sprint would exceed 12 tasks, reduce scope or merge related tasks
41354
- - Ensure acceptance criteria are specific and testable
41355
- - Keep the sprint focused on the directive — avoid scope creep
41356
-
41357
- ## Output Format
41358
-
41359
- 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:
41360
-
40946
+ const now = new Date().toISOString();
40947
+ return `<sprint_planning>
40948
+ Create a sprint plan for this directive: ${input.directive}
40949
+ ${feedbackSection}
40950
+ <rules>
40951
+ - Tasks execute sequentially by one agent on one branch
40952
+ - Each task must be self-contained with clear What/Where/How
40953
+ - No forward dependencies (task N can't need task N+1)
40954
+ - Foundation first (shared code, types, schemas before features)
40955
+ - Be specific: exact file paths, function names, implementation details
40956
+ - Each task description is the ONLY instruction an independent agent receives include all context it needs
40957
+ - Merge trivially small related work into one task
40958
+ - Assign appropriate roles: BACKEND, FRONTEND, QA, PM, or DESIGN
40959
+ </rules>
40960
+
40961
+ <output>
40962
+ Write the sprint plan as a JSON file to: ${input.plansDir}/${input.fileName}.json
40963
+
40964
+ The JSON file must contain this exact structure:
41361
40965
  {
41362
- "name": "string (2-4 words)",
41363
- "goal": "string (1 paragraph)",
41364
- "estimatedDays": 3,
40966
+ "id": "${input.planId}",
40967
+ "name": "2-4 words",
40968
+ "goal": "One paragraph of what this delivers",
40969
+ "directive": ${JSON.stringify(input.directive)},
40970
+ "estimatedDays": number,
40971
+ "status": "pending",
40972
+ "createdAt": "${now}",
40973
+ "updatedAt": "${now}",
41365
40974
  "tasks": [
41366
40975
  {
41367
- "title": "string",
41368
- "description": "string (detailed implementation guide: what, where, how, boundaries)",
41369
- "assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
41370
- "priority": "CRITICAL | HIGH | MEDIUM | LOW",
41371
- "labels": ["string"],
41372
- "acceptanceCriteria": ["string"],
41373
- "complexity": 3
40976
+ "index": 1,
40977
+ "title": "Action-oriented title",
40978
+ "description": "What: goal\\nWhere: files to modify\\nHow: implementation details\\nBoundaries: what's excluded",
40979
+ "assigneeRole": "BACKEND|FRONTEND|QA|PM|DESIGN",
40980
+ "priority": "CRITICAL|HIGH|MEDIUM|LOW",
40981
+ "labels": ["tags"],
40982
+ "acceptanceCriteria": ["testable conditions"],
40983
+ "complexity": 1-5
41374
40984
  }
41375
40985
  ],
41376
40986
  "risks": [
41377
40987
  {
41378
- "description": "string",
41379
- "mitigation": "string",
41380
- "severity": "low | medium | high"
40988
+ "description": "What could go wrong",
40989
+ "mitigation": "How to handle it",
40990
+ "severity": "low|medium|high"
41381
40991
  }
41382
40992
  ]
41383
40993
  }
41384
40994
 
41385
- 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.`;
41386
- return prompt;
40995
+ IMPORTANT:
40996
+ - Write the file directly using your file writing tool. Do NOT output the JSON as text.
40997
+ - Tasks must have sequential "index" values starting at 1.
40998
+ - The file must be valid JSON with no comments or trailing commas.
40999
+ - Do not create any other files. Only create the single JSON file specified above.
41000
+ </output>
41001
+ </sprint_planning>`;
41387
41002
  }
41388
41003
 
41389
41004
  // ../sdk/src/planning/planning-meeting.ts
41005
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync8 } from "node:fs";
41006
+ import { join as join10 } from "node:path";
41007
+
41390
41008
  class PlanningMeeting {
41391
41009
  projectPath;
41392
41010
  aiRunner;
@@ -41399,45 +41017,44 @@ class PlanningMeeting {
41399
41017
  });
41400
41018
  }
41401
41019
  async run(directive, feedback) {
41402
- const projectContext = this.getProjectContext();
41403
- this.log("Phase 1/2: Planner building sprint plan...", "info");
41404
- const plannerPrompt = buildPlannerPrompt({
41405
- directive,
41406
- projectContext,
41407
- feedback
41408
- });
41409
- console.log(plannerPrompt);
41410
- const plannerOutput = await this.aiRunner.run(plannerPrompt);
41411
- this.log("Planner phase complete.", "success");
41412
- this.log("Phase 2/2: Reviewer checking for conflicts and quality...", "info");
41413
- const crossTaskReviewerPrompt = buildCrossTaskReviewerPrompt({
41020
+ this.log("Planning sprint...", "info");
41021
+ const plansDir = getLocusPath(this.projectPath, "plansDir");
41022
+ if (!existsSync9(plansDir)) {
41023
+ mkdirSync5(plansDir, { recursive: true });
41024
+ }
41025
+ const ts = Date.now();
41026
+ const planId = `plan-${ts}`;
41027
+ const fileName = `plan-${ts}`;
41028
+ const prompt = buildPlannerPrompt({
41414
41029
  directive,
41415
- projectContext,
41416
- plannerOutput,
41417
- feedback
41030
+ feedback,
41031
+ plansDir,
41032
+ planId,
41033
+ fileName
41418
41034
  });
41419
- const reviewOutput = await this.aiRunner.run(crossTaskReviewerPrompt);
41420
- this.log("Review phase complete.", "success");
41421
- const plan = parseSprintPlanFromAI(reviewOutput, directive);
41035
+ const response = await this.aiRunner.run(prompt);
41036
+ this.log("Planning meeting complete.", "success");
41037
+ const expectedPath = join10(plansDir, `${fileName}.json`);
41038
+ let plan = null;
41039
+ if (existsSync9(expectedPath)) {
41040
+ try {
41041
+ plan = JSON.parse(readFileSync8(expectedPath, "utf-8"));
41042
+ } catch {}
41043
+ }
41044
+ if (!plan) {
41045
+ throw new Error("Planning agent did not create the expected plan JSON file. " + "Check the agent output for errors.");
41046
+ }
41422
41047
  if (feedback) {
41423
41048
  plan.feedback = feedback;
41424
41049
  }
41425
41050
  return {
41426
41051
  plan,
41427
- phaseOutputs: {
41428
- planner: plannerOutput,
41429
- review: reviewOutput
41430
- }
41052
+ rawOutput: response
41431
41053
  };
41432
41054
  }
41433
- getProjectContext() {
41434
- const kb = new KnowledgeBase(this.projectPath);
41435
- return kb.getFullContext();
41436
- }
41437
41055
  }
41438
41056
  var init_planning_meeting = __esm(() => {
41439
- init_knowledge_base();
41440
- init_sprint_plan();
41057
+ init_config();
41441
41058
  });
41442
41059
 
41443
41060
  // ../sdk/src/planning/index.ts
@@ -41451,8 +41068,8 @@ var init_planning = __esm(() => {
41451
41068
  var init_index_node = __esm(() => {
41452
41069
  init_prompt_builder();
41453
41070
  init_orchestrator();
41454
- init_knowledge_base();
41455
41071
  init_colors();
41072
+ init_structured_output();
41456
41073
  init_agent2();
41457
41074
  init_ai();
41458
41075
  init_core3();
@@ -42271,7 +41888,6 @@ class InteractiveSession {
42271
41888
  isProcessing = false;
42272
41889
  conversationHistory = [];
42273
41890
  historyManager;
42274
- knowledgeBase;
42275
41891
  currentSession;
42276
41892
  projectPath;
42277
41893
  model;
@@ -42287,7 +41903,6 @@ class InteractiveSession {
42287
41903
  this.promptBuilder = new PromptBuilder(options.projectPath);
42288
41904
  this.renderer = new ProgressRenderer({ animated: true });
42289
41905
  this.historyManager = new HistoryManager(options.projectPath);
42290
- this.knowledgeBase = new KnowledgeBase(options.projectPath);
42291
41906
  this.projectPath = options.projectPath;
42292
41907
  this.model = options.model;
42293
41908
  this.provider = options.provider;
@@ -42431,18 +42046,6 @@ class InteractiveSession {
42431
42046
  }
42432
42047
  }
42433
42048
  this.saveSession();
42434
- try {
42435
- this.knowledgeBase.updateProgress({
42436
- role: "user",
42437
- content: prompt
42438
- });
42439
- if (responseContent.trim()) {
42440
- this.knowledgeBase.updateProgress({
42441
- role: "assistant",
42442
- content: responseContent.trim()
42443
- });
42444
- }
42445
- } catch {}
42446
42049
  } catch (error48) {
42447
42050
  console.error(c.error(`Error: ${error48 instanceof Error ? error48.message : String(error48)}`));
42448
42051
  } finally {
@@ -42518,10 +42121,10 @@ import { createInterface } from "node:readline";
42518
42121
 
42519
42122
  // src/settings-manager.ts
42520
42123
  init_index_node();
42521
- import { existsSync as existsSync10, readFileSync as readFileSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
42522
- import { join as join10 } from "node:path";
42124
+ import { existsSync as existsSync10, readFileSync as readFileSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
42125
+ import { join as join11 } from "node:path";
42523
42126
  function getSettingsPath(projectPath) {
42524
- return join10(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42127
+ return join11(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42525
42128
  }
42526
42129
 
42527
42130
  class SettingsManager {
@@ -42540,7 +42143,7 @@ class SettingsManager {
42540
42143
  const { $schema: _2, ...rest } = settings;
42541
42144
  const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
42542
42145
  const settingsPath = getSettingsPath(this.projectPath);
42543
- writeFileSync6(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42146
+ writeFileSync5(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42544
42147
  }
42545
42148
  get(key) {
42546
42149
  return this.load()[key];
@@ -42823,74 +42426,100 @@ import { parseArgs } from "node:util";
42823
42426
  // src/config-manager.ts
42824
42427
  init_index_node();
42825
42428
  import { execSync as execSync2 } from "node:child_process";
42826
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
42827
- import { join as join11 } from "node:path";
42429
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
42430
+ import { join as join12 } from "node:path";
42828
42431
  var LOCUS_GITIGNORE_MARKER = "# Locus AI";
42829
- var DEFAULT_CONTEXT_MD = `# Project
42432
+ var LOCUS_MD_TEMPLATE = `## Planning First
42830
42433
 
42831
- ## Mission
42832
- <!-- Describe your project's core purpose and value proposition -->
42434
+ Complex tasks must be planned before writing code. Create ".locus/plans/<task-name>.md" with:
42435
+ - **Goal**: What we're trying to achieve and why
42436
+ - **Approach**: Step-by-step strategy with technical decisions
42437
+ - **Affected files**: List of files to create/modify/delete
42438
+ - **Acceptance criteria**: Specific, testable conditions for completion
42439
+ - **Dependencies**: Required packages, APIs, or external services
42833
42440
 
42834
- ## Tech Stack
42835
- <!-- List your technologies -->
42441
+ Delete the planning .md files after successful execution.
42836
42442
 
42837
- ## Architecture
42838
- <!-- Describe your high-level architecture -->
42443
+ ## Code Quality
42839
42444
 
42840
- ## Key Decisions
42841
- <!-- Document important technical decisions and their rationale -->
42445
+ - **Follow existing patterns**: Run formatters and linters before finishing (check "package.json" scripts or project config)
42446
+ - **Minimize changes**: Keep modifications atomic. Separate refactors from behavioral changes into different tasks
42447
+ - **Never commit secrets**: No API keys, passwords, or credentials in code. Use environment variables or secret management
42448
+ - **Test as you go**: If tests exist, run relevant ones. If breaking changes occur, update tests accordingly
42449
+ - **Comment complex logic**: Explain *why*, not *what*. Focus on business logic and non-obvious decisions
42842
42450
 
42843
- ## Feature Areas
42844
- <!-- List your main feature areas and their status -->
42845
- `;
42846
- var DEFAULT_PROGRESS_MD = `# Project Progress
42451
+ ## Artifacts
42847
42452
 
42848
- No sprints started yet.
42849
- `;
42850
- var LOCUS_MD_TEMPLATE = `## Artifacts
42851
-
42852
- When a task produces knowledge, analysis, or research output rather than (or in addition to) code changes, you **must** save the results as a Markdown file in \`.locus/artifacts/\`. Examples of artifact-worthy tasks:
42453
+ When a task produces knowledge, analysis, or research output rather than (or in addition to) code changes, you **must** save results as Markdown in ".locus/artifacts/<descriptive-name>.md":
42853
42454
 
42455
+ **Always create artifacts for:**
42854
42456
  - Code quality audits, security reviews, vulnerability assessments
42855
- - Architecture analyses or recommendations
42856
- - Dependency reports, performance profiling summaries
42857
- - Any research, comparison, or investigation the user requests
42457
+ - Architecture analyses, system design proposals, or recommendations
42458
+ - Dependency reports, performance profiling, benchmarking results
42459
+ - Research summaries, technology comparisons, or feasibility studies
42460
+ - Migration plans, deployment strategies, or rollback procedures
42461
+ - Post-mortems, incident analysis, or debugging investigations
42462
+
42463
+ **Artifact structure:**
42464
+ - Clear title and date
42465
+ - Executive summary (2-3 sentences)
42466
+ - Detailed findings/analysis
42467
+ - Actionable recommendations (if applicable)
42468
+
42469
+ ## Git Operations
42470
+
42471
+ - **Do NOT run**: git add, git commit, git push, git checkout, git branch, or any git commands
42472
+ - **Why**: The Locus orchestrator handles all version control automatically after execution
42473
+ - **Your role**: Focus solely on making file changes. The system commits, pushes, and creates PRs
42474
+
42475
+ ## Continuous Learning
42858
42476
 
42859
- **Rules:**
42860
- - File path: \`.locus/artifacts/<descriptive-name>.md\` (e.g., \`code-quality-audit.md\`, \`dependency-report.md\`).
42861
- - The artifact should be a well-structured Markdown document with clear headings, findings, and actionable recommendations where applicable.
42862
- - Always create the artifact file — do not only return the results as conversation text.
42863
- - If the task also involves code changes, still produce the artifact alongside the code changes.
42477
+ Read ".locus/LEARNINGS.md" **before starting any task** to avoid repeating mistakes.
42864
42478
 
42865
- ## Planning First
42479
+ **When to update:**
42480
+ - User corrects your approach or provides guidance
42481
+ - You discover a better pattern while working
42482
+ - A decision prevents future confusion (e.g., "use X not Y because Z")
42483
+ - You encounter and solve a tricky problem
42866
42484
 
42867
- Complex tasks must be planned before writing code. Create \`.locus/plans/<task-name>.md\` with: goal, approach, affected files, and acceptance criteria. Delete the planning .md files after the execution.
42485
+ **What to record:**
42486
+ - Architectural decisions and their rationale
42487
+ - Preferred libraries, tools, or patterns for this project
42488
+ - Common pitfalls and how to avoid them
42489
+ - Project-specific conventions or user preferences
42490
+ - Solutions to non-obvious problems
42868
42491
 
42869
- ## Code
42492
+ **Format (append-only, never delete):**
42870
42493
 
42871
- - Follow the existing formatter, linter, and code style. Run them before finishing.
42872
- - Keep changes minimal and atomic. Separate refactors from behavioral changes.
42873
- - No new dependencies without explicit approval.
42874
- - Never put raw secrets or credentials in the codebase.
42494
+ """
42495
+ - **[Category]**: Concise description (1-2 lines max). *Context if needed.*
42496
+ """
42875
42497
 
42876
- ## Git
42498
+ **Categories:** Architecture, Dependencies, Patterns, Debugging, Performance, Security, DevOps, User Preferences
42877
42499
 
42878
- - Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches.
42879
- - The Locus system handles all git operations (commit, push, PR creation) automatically after your execution completes.
42880
- - Focus only on making file changes the orchestrator takes care of version control.
42881
- - Do NOT modify \`.locus/project/progress.md\`. The system updates it automatically. Changes to this file are excluded from commits to prevent merge conflicts across concurrent agents.
42500
+ ## Error Handling
42501
+
42502
+ - **Read error messages carefully**: Don't guess. Parse the actual error before proposing fixes
42503
+ - **Check dependencies first**: Missing packages, wrong versions, and environment issues are common
42504
+ - **Verify assumptions**: If something "should work," confirm it actually does in this environment
42505
+ - **Ask for context**: If you need environment details, configuration, or logs, request them explicitly
42506
+
42507
+ ## Communication
42508
+
42509
+ - **Be precise**: When uncertain, state what you know and what you're assuming
42510
+ - **Show your work**: For complex changes, briefly explain the approach before executing
42511
+ - **Highlight trade-offs**: If multiple approaches exist, note why you chose one over others
42512
+ - **Request feedback**: For ambiguous requirements, propose an approach and ask for confirmation
42513
+ `;
42514
+ var DEFAULT_LEARNINGS_MD = `# Learnings
42882
42515
 
42883
- ## Avoiding Hallucinated / Slop Code
42516
+ This file captures important lessons, decisions, and corrections made during development.
42517
+ It is read by AI agents before every task to avoid repeating mistakes and to follow established patterns.
42884
42518
 
42885
- - Ask before assuming. If requirements are ambiguous, incomplete, or could be interpreted multiple ways, stop and ask clarifying questions rather than guessing.
42886
- - Never invent APIs, libraries, functions, or config options.** Only use APIs and methods you can verify exist in the project's dependencies or documentation. If unsure whether something exists, ask or look it up first.
42887
- - No placeholder or stub logic unless explicitly requested. Every piece of code you write should be functional and intentional. Do not leave TODO blocks, fake return values, or mock implementations without flagging them clearly.
42888
- - Do not generate boilerplate "just in case." Only write code that is directly required by the task. No speculative utilities, unused helpers, or premature abstractions.
42889
- - If you're uncertain, say so. State your confidence level. "I believe this is correct but haven't verified X" is always better than silent guessing.
42890
- - Read before writing Before modifying a file, read the relevant existing code to match conventions, understand context, and avoid duplicating logic that already exists.
42519
+ <!-- Add learnings below this line. Format: - **[Category]**: Description -->
42891
42520
  `;
42892
42521
  function updateGitignore(projectPath) {
42893
- const gitignorePath = join11(projectPath, ".gitignore");
42522
+ const gitignorePath = join12(projectPath, ".gitignore");
42894
42523
  let content = "";
42895
42524
  const locusBlock = LOCUS_GITIGNORE_PATTERNS.join(`
42896
42525
  `);
@@ -42912,7 +42541,7 @@ function updateGitignore(projectPath) {
42912
42541
  const after = lines.slice(endIdx + 1);
42913
42542
  content = [...before, locusBlock, ...after].join(`
42914
42543
  `);
42915
- writeFileSync7(gitignorePath, content);
42544
+ writeFileSync6(gitignorePath, content);
42916
42545
  return;
42917
42546
  }
42918
42547
  if (content.length > 0 && !content.endsWith(`
@@ -42927,7 +42556,7 @@ function updateGitignore(projectPath) {
42927
42556
  }
42928
42557
  content += `${locusBlock}
42929
42558
  `;
42930
- writeFileSync7(gitignorePath, content);
42559
+ writeFileSync6(gitignorePath, content);
42931
42560
  }
42932
42561
  function ensureGitIdentity(projectPath) {
42933
42562
  const hasName = (() => {
@@ -42974,7 +42603,7 @@ class ConfigManager {
42974
42603
  this.projectPath = projectPath;
42975
42604
  }
42976
42605
  async init(version2) {
42977
- const locusConfigDir = join11(this.projectPath, LOCUS_CONFIG.dir);
42606
+ const locusConfigDir = join12(this.projectPath, LOCUS_CONFIG.dir);
42978
42607
  const locusConfigPath = getLocusPath(this.projectPath, "configFile");
42979
42608
  if (!existsSync11(locusConfigDir)) {
42980
42609
  mkdirSync6(locusConfigDir, { recursive: true });
@@ -42984,26 +42613,21 @@ class ConfigManager {
42984
42613
  LOCUS_CONFIG.documentsDir,
42985
42614
  LOCUS_CONFIG.sessionsDir,
42986
42615
  LOCUS_CONFIG.reviewsDir,
42987
- LOCUS_CONFIG.plansDir,
42988
- LOCUS_CONFIG.projectDir
42616
+ LOCUS_CONFIG.plansDir
42989
42617
  ];
42990
42618
  for (const subdir of locusSubdirs) {
42991
- const subdirPath = join11(locusConfigDir, subdir);
42619
+ const subdirPath = join12(locusConfigDir, subdir);
42992
42620
  if (!existsSync11(subdirPath)) {
42993
42621
  mkdirSync6(subdirPath, { recursive: true });
42994
42622
  }
42995
42623
  }
42996
- const contextFilePath = getLocusPath(this.projectPath, "projectContextFile");
42997
- if (!existsSync11(contextFilePath)) {
42998
- writeFileSync7(contextFilePath, DEFAULT_CONTEXT_MD);
42999
- }
43000
- const progressFilePath = getLocusPath(this.projectPath, "projectProgressFile");
43001
- if (!existsSync11(progressFilePath)) {
43002
- writeFileSync7(progressFilePath, DEFAULT_PROGRESS_MD);
43003
- }
43004
42624
  const locusMdPath = getLocusPath(this.projectPath, "contextFile");
43005
42625
  if (!existsSync11(locusMdPath)) {
43006
- writeFileSync7(locusMdPath, LOCUS_MD_TEMPLATE);
42626
+ writeFileSync6(locusMdPath, LOCUS_MD_TEMPLATE);
42627
+ }
42628
+ const learningsMdPath = getLocusPath(this.projectPath, "learningsFile");
42629
+ if (!existsSync11(learningsMdPath)) {
42630
+ writeFileSync6(learningsMdPath, DEFAULT_LEARNINGS_MD);
43007
42631
  }
43008
42632
  if (!existsSync11(locusConfigPath)) {
43009
42633
  const config2 = {
@@ -43012,7 +42636,7 @@ class ConfigManager {
43012
42636
  createdAt: new Date().toISOString(),
43013
42637
  projectPath: "."
43014
42638
  };
43015
- writeFileSync7(locusConfigPath, JSON.stringify(config2, null, 2));
42639
+ writeFileSync6(locusConfigPath, JSON.stringify(config2, null, 2));
43016
42640
  }
43017
42641
  updateGitignore(this.projectPath);
43018
42642
  ensureGitIdentity(this.projectPath);
@@ -43038,8 +42662,6 @@ class ConfigManager {
43038
42662
  directoriesCreated: [],
43039
42663
  gitignoreUpdated: false
43040
42664
  };
43041
- const locusConfigDir = join11(this.projectPath, LOCUS_CONFIG.dir);
43042
- const locusMdPath = getLocusPath(this.projectPath, "contextFile");
43043
42665
  const config2 = this.loadConfig();
43044
42666
  if (config2) {
43045
42667
  result.previousVersion = config2.version;
@@ -43052,18 +42674,19 @@ class ConfigManager {
43052
42674
  this.saveConfig(config2);
43053
42675
  }
43054
42676
  }
43055
- const settingsPath = join11(this.projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42677
+ const settingsPath = join12(this.projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
43056
42678
  if (existsSync11(settingsPath)) {
43057
42679
  const raw = readFileSync10(settingsPath, "utf-8");
43058
42680
  const settings = JSON.parse(raw);
43059
42681
  if (settings.$schema !== LOCUS_SCHEMAS.settings) {
43060
42682
  const { $schema: _2, ...rest } = settings;
43061
42683
  const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
43062
- writeFileSync7(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42684
+ writeFileSync6(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
43063
42685
  }
43064
42686
  }
42687
+ const locusMdPath = getLocusPath(this.projectPath, "contextFile");
43065
42688
  const locusMdExisted = existsSync11(locusMdPath);
43066
- writeFileSync7(locusMdPath, LOCUS_MD_TEMPLATE);
42689
+ writeFileSync6(locusMdPath, LOCUS_MD_TEMPLATE);
43067
42690
  if (!locusMdExisted) {
43068
42691
  result.directoriesCreated.push(".locus/LOCUS.md");
43069
42692
  }
@@ -43072,27 +42695,22 @@ class ConfigManager {
43072
42695
  LOCUS_CONFIG.documentsDir,
43073
42696
  LOCUS_CONFIG.sessionsDir,
43074
42697
  LOCUS_CONFIG.reviewsDir,
43075
- LOCUS_CONFIG.plansDir,
43076
- LOCUS_CONFIG.projectDir
42698
+ LOCUS_CONFIG.plansDir
43077
42699
  ];
42700
+ const locusConfigDir = join12(this.projectPath, LOCUS_CONFIG.dir);
43078
42701
  for (const subdir of locusSubdirs) {
43079
- const subdirPath = join11(locusConfigDir, subdir);
42702
+ const subdirPath = join12(locusConfigDir, subdir);
43080
42703
  if (!existsSync11(subdirPath)) {
43081
42704
  mkdirSync6(subdirPath, { recursive: true });
43082
42705
  result.directoriesCreated.push(`.locus/${subdir}`);
43083
42706
  }
43084
42707
  }
43085
- const contextFilePath = getLocusPath(this.projectPath, "projectContextFile");
43086
- if (!existsSync11(contextFilePath)) {
43087
- writeFileSync7(contextFilePath, DEFAULT_CONTEXT_MD);
43088
- result.directoriesCreated.push(".locus/project/context.md");
42708
+ const learningsMdPath = getLocusPath(this.projectPath, "learningsFile");
42709
+ if (!existsSync11(learningsMdPath)) {
42710
+ writeFileSync6(learningsMdPath, DEFAULT_LEARNINGS_MD);
42711
+ result.directoriesCreated.push(".locus/LEARNINGS.md");
43089
42712
  }
43090
- const progressFilePath = getLocusPath(this.projectPath, "projectProgressFile");
43091
- if (!existsSync11(progressFilePath)) {
43092
- writeFileSync7(progressFilePath, DEFAULT_PROGRESS_MD);
43093
- result.directoriesCreated.push(".locus/project/progress.md");
43094
- }
43095
- const gitignorePath = join11(this.projectPath, ".gitignore");
42713
+ const gitignorePath = join12(this.projectPath, ".gitignore");
43096
42714
  const gitignoreBefore = existsSync11(gitignorePath) ? readFileSync10(gitignorePath, "utf-8") : "";
43097
42715
  updateGitignore(this.projectPath);
43098
42716
  const gitignoreAfter = readFileSync10(gitignorePath, "utf-8");
@@ -43106,7 +42724,7 @@ class ConfigManager {
43106
42724
  const { $schema: _2, ...rest } = config2;
43107
42725
  const ordered = { $schema: LOCUS_SCHEMAS.config, ...rest };
43108
42726
  const path2 = getLocusPath(this.projectPath, "configFile");
43109
- writeFileSync7(path2, JSON.stringify(ordered, null, 2));
42727
+ writeFileSync6(path2, JSON.stringify(ordered, null, 2));
43110
42728
  }
43111
42729
  }
43112
42730
 
@@ -43115,14 +42733,14 @@ init_index_node();
43115
42733
 
43116
42734
  // src/utils/version.ts
43117
42735
  import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
43118
- import { dirname as dirname4, join as join12 } from "node:path";
42736
+ import { dirname as dirname3, join as join13 } from "node:path";
43119
42737
  import { fileURLToPath as fileURLToPath3 } from "node:url";
43120
42738
  function getVersion() {
43121
42739
  try {
43122
42740
  const __filename2 = fileURLToPath3(import.meta.url);
43123
- const __dirname2 = dirname4(__filename2);
43124
- const bundledPath = join12(__dirname2, "..", "package.json");
43125
- const sourcePath = join12(__dirname2, "..", "..", "package.json");
42741
+ const __dirname2 = dirname3(__filename2);
42742
+ const bundledPath = join13(__dirname2, "..", "package.json");
42743
+ const sourcePath = join13(__dirname2, "..", "..", "package.json");
43126
42744
  if (existsSync12(bundledPath)) {
43127
42745
  const pkg = JSON.parse(readFileSync11(bundledPath, "utf-8"));
43128
42746
  if (pkg.name === "@locusai/cli") {
@@ -43153,10 +42771,10 @@ function printBanner() {
43153
42771
  // src/utils/helpers.ts
43154
42772
  init_index_node();
43155
42773
  import { existsSync as existsSync13 } from "node:fs";
43156
- import { join as join13 } from "node:path";
42774
+ import { join as join14 } from "node:path";
43157
42775
  function isProjectInitialized(projectPath) {
43158
- const locusDir = join13(projectPath, LOCUS_CONFIG.dir);
43159
- const configPath = join13(locusDir, LOCUS_CONFIG.configFile);
42776
+ const locusDir = join14(projectPath, LOCUS_CONFIG.dir);
42777
+ const configPath = join14(locusDir, LOCUS_CONFIG.configFile);
43160
42778
  return existsSync13(locusDir) && existsSync13(configPath);
43161
42779
  }
43162
42780
  function requireInitialization(projectPath, command) {
@@ -43710,7 +43328,6 @@ async function execCommand(args) {
43710
43328
  });
43711
43329
  const builder = new PromptBuilder(projectPath);
43712
43330
  const fullPrompt = await builder.buildGenericPrompt(promptInput);
43713
- const knowledgeBase = new KnowledgeBase(projectPath);
43714
43331
  console.log("");
43715
43332
  console.log(`${c.primary("\uD83D\uDE80")} ${c.bold("Executing prompt with repository context...")}`);
43716
43333
  console.log("");
@@ -43765,15 +43382,6 @@ async function execCommand(args) {
43765
43382
  responseContent = await aiRunner.run(fullPrompt);
43766
43383
  console.log(responseContent);
43767
43384
  }
43768
- try {
43769
- knowledgeBase.updateProgress({ role: "user", content: promptInput });
43770
- if (responseContent.trim()) {
43771
- knowledgeBase.updateProgress({
43772
- role: "assistant",
43773
- content: responseContent.trim()
43774
- });
43775
- }
43776
- } catch {}
43777
43385
  console.log(`
43778
43386
  ${c.success("✔")} ${c.success("Execution finished!")}
43779
43387
  `);
@@ -43829,13 +43437,6 @@ async function execJsonStream(values, positionals, projectPath) {
43829
43437
  for await (const chunk of stream4) {
43830
43438
  renderer.handleChunk(chunk);
43831
43439
  }
43832
- try {
43833
- const knowledgeBase = new KnowledgeBase(projectPath);
43834
- knowledgeBase.updateProgress({
43835
- role: "user",
43836
- content: promptInput
43837
- });
43838
- } catch {}
43839
43440
  renderer.emitDone(0);
43840
43441
  } catch (error48) {
43841
43442
  const message = error48 instanceof Error ? error48.message : String(error48);
@@ -44018,9 +43619,8 @@ async function initCommand() {
44018
43619
  ${c.bold("Created:")}
44019
43620
  ${c.primary("\uD83D\uDCC1")} ${c.bold(".locus/")} ${c.dim("Configuration directory")}
44020
43621
  ${c.primary("\uD83D\uDCC4")} ${c.bold(".locus/config.json")} ${c.dim("Project settings")}
44021
- ${c.primary("\uD83D\uDCC4")} ${c.bold(".locus/project/context.md")} ${c.dim("Project context & knowledge")}
44022
- ${c.primary("\uD83D\uDCC4")} ${c.bold(".locus/project/progress.md")} ${c.dim("Sprint progress tracking")}
44023
43622
  ${c.primary("\uD83D\uDCDD")} ${c.bold(".locus/LOCUS.md")} ${c.dim("AI agent instructions")}
43623
+ ${c.primary("\uD83D\uDCDD")} ${c.bold(".locus/LEARNINGS.md")} ${c.dim("Continuous learning log")}
44024
43624
 
44025
43625
  ${c.bold("Next steps:")}
44026
43626
  1. Run '${c.primary("locus config setup")}' to configure your API key
@@ -44032,6 +43632,8 @@ async function initCommand() {
44032
43632
  }
44033
43633
  // src/commands/plan.ts
44034
43634
  init_index_node();
43635
+ import { existsSync as existsSync14, unlinkSync as unlinkSync4 } from "node:fs";
43636
+ import { join as join15 } from "node:path";
44035
43637
  import { parseArgs as parseArgs4 } from "node:util";
44036
43638
  function normalizePlanIdArgs(args) {
44037
43639
  const planIdFlags = new Set(["--approve", "--reject", "--cancel", "--show"]);
@@ -44138,6 +43740,10 @@ async function planCommand(args) {
44138
43740
  try {
44139
43741
  const result = await meeting.run(directive, feedback);
44140
43742
  planManager.save(result.plan);
43743
+ const tempFile = join15(getLocusPath(projectPath, "plansDir"), `${result.plan.id}.json`);
43744
+ if (existsSync14(tempFile)) {
43745
+ unlinkSync4(tempFile);
43746
+ }
44141
43747
  console.log(`
44142
43748
  ${c.success("✔")} ${c.success("Planning meeting complete!")}
44143
43749
  `);
@@ -44332,8 +43938,8 @@ function showPlanHelp() {
44332
43938
  }
44333
43939
  // src/commands/review.ts
44334
43940
  init_index_node();
44335
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "node:fs";
44336
- import { join as join14 } from "node:path";
43941
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
43942
+ import { join as join16 } from "node:path";
44337
43943
  import { parseArgs as parseArgs5 } from "node:util";
44338
43944
  async function reviewCommand(args) {
44339
43945
  const subcommand = args[0];
@@ -44472,13 +44078,13 @@ async function reviewLocalCommand(args) {
44472
44078
  `);
44473
44079
  return;
44474
44080
  }
44475
- const reviewsDir = join14(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
44476
- if (!existsSync14(reviewsDir)) {
44081
+ const reviewsDir = join16(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
44082
+ if (!existsSync15(reviewsDir)) {
44477
44083
  mkdirSync7(reviewsDir, { recursive: true });
44478
44084
  }
44479
44085
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
44480
- const reportPath = join14(reviewsDir, `review-${timestamp}.md`);
44481
- writeFileSync8(reportPath, report, "utf-8");
44086
+ const reportPath = join16(reviewsDir, `review-${timestamp}.md`);
44087
+ writeFileSync7(reportPath, report, "utf-8");
44482
44088
  console.log(`
44483
44089
  ${c.success("✔")} ${c.success("Review complete!")}`);
44484
44090
  console.log(` ${c.dim("Report saved to:")} ${c.primary(reportPath)}
@@ -44570,8 +44176,8 @@ ${c.info(`Received ${signal}. Stopping agent and cleaning up...`)}`);
44570
44176
  // src/commands/telegram.ts
44571
44177
  init_index_node();
44572
44178
  import { spawn as spawn4 } from "node:child_process";
44573
- import { existsSync as existsSync15 } from "node:fs";
44574
- import { join as join15 } from "node:path";
44179
+ import { existsSync as existsSync16 } from "node:fs";
44180
+ import { join as join17 } from "node:path";
44575
44181
  import { createInterface as createInterface3 } from "node:readline";
44576
44182
  function ask2(question) {
44577
44183
  const rl = createInterface3({
@@ -44805,8 +44411,8 @@ function runBotCommand(projectPath) {
44805
44411
  `);
44806
44412
  process.exit(1);
44807
44413
  }
44808
- const monorepoTelegramEntry = join15(projectPath, "packages/telegram/src/index.ts");
44809
- const isMonorepo = existsSync15(monorepoTelegramEntry);
44414
+ const monorepoTelegramEntry = join17(projectPath, "packages/telegram/src/index.ts");
44415
+ const isMonorepo = existsSync16(monorepoTelegramEntry);
44810
44416
  let cmd;
44811
44417
  let args;
44812
44418
  if (isMonorepo) {