@locusai/sdk 0.7.7 → 0.8.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.
@@ -1,5 +1,6 @@
1
1
  export { CodebaseIndexerService } from "./codebase-indexer-service.js";
2
2
  export { DocumentFetcher } from "./document-fetcher.js";
3
+ export { ReviewService, type ReviewServiceDeps } from "./review-service.js";
3
4
  export { TaskExecutor } from "./task-executor.js";
4
5
  export { AgentWorker, type WorkerConfig } from "./worker.js";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { Sprint } from "@locusai/shared";
2
+ import { LogFn } from "../ai/factory.js";
3
+ import type { AiRunner } from "../ai/runner.js";
4
+ export interface ReviewServiceDeps {
5
+ aiRunner: AiRunner;
6
+ projectPath: string;
7
+ log: LogFn;
8
+ }
9
+ /**
10
+ * Reviews staged git changes and produces a markdown report.
11
+ */
12
+ export declare class ReviewService {
13
+ private deps;
14
+ constructor(deps: ReviewServiceDeps);
15
+ /**
16
+ * Stages all changes and runs an AI review on the diff.
17
+ * Returns a markdown review report, or null if there are no changes.
18
+ */
19
+ reviewStagedChanges(sprint: Sprint | null): Promise<string | null>;
20
+ }
21
+ //# sourceMappingURL=review-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-service.d.ts","sourceRoot":"","sources":["../../src/agent/review-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,KAAK,CAAC;CACZ;AAED;;GAEG;AACH,qBAAa,aAAa;IACZ,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,iBAAiB;IAE3C;;;OAGG;IACG,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAkEzE"}
@@ -7,7 +7,7 @@ export interface TaskExecutorDeps {
7
7
  log: LogFn;
8
8
  }
9
9
  /**
10
- * Handles task execution with two-phase approach (planning + execution)
10
+ * Handles direct task execution (single-pass, no separate planning phase)
11
11
  */
12
12
  export declare class TaskExecutor {
13
13
  private deps;
@@ -1 +1 @@
1
- {"version":3,"file":"task-executor.d.ts","sourceRoot":"","sources":["../../src/agent/task-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,KAAK,CAAC;CACZ;AAED;;GAEG;AACH,qBAAa,YAAY;IAGX,OAAO,CAAC,IAAI;IAFxB,OAAO,CAAC,aAAa,CAAgB;gBAEjB,IAAI,EAAE,gBAAgB;IAIpC,OAAO,CACX,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CA2ClD"}
1
+ {"version":3,"file":"task-executor.d.ts","sourceRoot":"","sources":["../../src/agent/task-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,KAAK,CAAC;CACZ;AAED;;GAEG;AACH,qBAAa,YAAY;IAGX,OAAO,CAAC,IAAI;IAFxB,OAAO,CAAC,aAAa,CAAgB;gBAEjB,IAAI,EAAE,gBAAgB;IAIpC,OAAO,CACX,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAyBlD"}
@@ -20,16 +20,15 @@ export declare class AgentWorker {
20
20
  private indexerService;
21
21
  private documentFetcher;
22
22
  private taskExecutor;
23
- private consecutiveEmpty;
24
- private maxEmpty;
23
+ private reviewService;
25
24
  private maxTasks;
26
25
  private tasksCompleted;
27
- private pollInterval;
28
26
  constructor(config: WorkerConfig);
29
27
  log(message: string, level?: "info" | "success" | "warn" | "error"): void;
30
28
  private getActiveSprint;
31
29
  private getNextTask;
32
30
  private executeTask;
31
+ private runStagedChangesReview;
33
32
  run(): Promise<void>;
34
33
  }
35
34
  //# sourceMappingURL=worker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/agent/worker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAY,MAAM,iBAAiB,CAAC;AAsB5D,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,UAAU,CAAC;CACvB;AAED;;;GAGG;AACH,qBAAa,WAAW;IAgBV,OAAO,CAAC,MAAM;IAf1B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAW;IAG3B,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAe;IAGnC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,YAAY,CAAU;gBAEV,MAAM,EAAE,YAAY;IAiDxC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAgB;YAe5D,eAAe;YAcf,WAAW;YAiBX,WAAW;IA6BnB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAqF3B"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/agent/worker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAY,MAAM,iBAAiB,CAAC;AAuB5D,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,UAAU,CAAC;CACvB;AAED;;;GAGG;AACH,qBAAa,WAAW;IAcV,OAAO,CAAC,MAAM;IAb1B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAW;IAG3B,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,aAAa,CAAgB;IAGrC,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,cAAc,CAAK;gBAEP,MAAM,EAAE,YAAY;IAuDxC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAgB;YAe5D,eAAe;YAcf,WAAW;YAiBX,WAAW;YA6BX,sBAAsB;IAoC9B,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA2D3B"}
@@ -386,6 +386,10 @@ class WorkspacesModule extends BaseModule {
386
386
  const { data } = await this.api.get(`/workspaces/${id}/stats`);
387
387
  return data;
388
388
  }
389
+ async getManifestStatus(workspaceId) {
390
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/manifest-status`);
391
+ return data;
392
+ }
389
393
  async getActivity(id, limit) {
390
394
  const { data } = await this.api.get(`/workspaces/${id}/activity`, {
391
395
  params: { limit }
@@ -508,6 +512,8 @@ __export(exports_worker, {
508
512
  AgentWorker: () => AgentWorker
509
513
  });
510
514
  module.exports = __toCommonJS(exports_worker);
515
+ var import_node_fs5 = require("node:fs");
516
+ var import_node_path7 = require("node:path");
511
517
 
512
518
  // src/core/config.ts
513
519
  var import_node_path = require("node:path");
@@ -527,14 +533,22 @@ var LOCUS_CONFIG = {
527
533
  artifactsDir: "artifacts",
528
534
  documentsDir: "documents",
529
535
  agentSkillsDir: ".agent/skills",
530
- sessionsDir: "sessions"
536
+ sessionsDir: "sessions",
537
+ reviewsDir: "reviews",
538
+ plansDir: "plans"
531
539
  };
532
540
  var LOCUS_GITIGNORE_PATTERNS = [
533
541
  "# Locus AI - Session data (user-specific, can grow large)",
534
542
  ".locus/sessions/",
535
543
  "",
536
544
  "# Locus AI - Artifacts (local-only, user-specific)",
537
- ".locus/artifacts/"
545
+ ".locus/artifacts/",
546
+ "",
547
+ "# Locus AI - Review reports (generated per sprint)",
548
+ ".locus/reviews/",
549
+ "",
550
+ "# Locus AI - Plans (generated per task)",
551
+ ".locus/plans/"
538
552
  ];
539
553
  function getLocusPath(projectPath, fileName) {
540
554
  if (fileName === "contextFile") {
@@ -629,7 +643,7 @@ class ClaudeRunner {
629
643
  setEventEmitter(emitter) {
630
644
  this.eventEmitter = emitter;
631
645
  }
632
- async run(prompt, _isPlanning = false) {
646
+ async run(prompt) {
633
647
  const maxRetries = 3;
634
648
  let lastError = null;
635
649
  for (let attempt = 1;attempt <= maxRetries; attempt++) {
@@ -1475,7 +1489,7 @@ File tree:
1475
1489
  ${tree}
1476
1490
 
1477
1491
  Return ONLY valid JSON, no markdown formatting.`;
1478
- const response = await this.deps.aiRunner.run(prompt, true);
1492
+ const response = await this.deps.aiRunner.run(prompt);
1479
1493
  const jsonMatch = response.match(/\{[\s\S]*\}/);
1480
1494
  if (jsonMatch) {
1481
1495
  return JSON.parse(jsonMatch[0]);
@@ -1538,6 +1552,66 @@ class DocumentFetcher {
1538
1552
  }
1539
1553
  }
1540
1554
 
1555
+ // src/agent/review-service.ts
1556
+ var import_node_child_process3 = require("node:child_process");
1557
+
1558
+ class ReviewService {
1559
+ deps;
1560
+ constructor(deps) {
1561
+ this.deps = deps;
1562
+ }
1563
+ async reviewStagedChanges(sprint) {
1564
+ const { projectPath, log } = this.deps;
1565
+ try {
1566
+ import_node_child_process3.execSync("git add -A", { cwd: projectPath, stdio: "pipe" });
1567
+ log("Staged all changes for review.", "info");
1568
+ } catch (err) {
1569
+ log(`Failed to stage changes: ${err instanceof Error ? err.message : String(err)}`, "error");
1570
+ return null;
1571
+ }
1572
+ let diff;
1573
+ try {
1574
+ diff = import_node_child_process3.execSync("git diff --cached --stat && echo '---' && git diff --cached", {
1575
+ cwd: projectPath,
1576
+ maxBuffer: 10 * 1024 * 1024
1577
+ }).toString();
1578
+ } catch (err) {
1579
+ log(`Failed to get staged diff: ${err instanceof Error ? err.message : String(err)}`, "error");
1580
+ return null;
1581
+ }
1582
+ if (!diff.trim()) {
1583
+ return null;
1584
+ }
1585
+ const sprintInfo = sprint ? `Sprint: ${sprint.name} (${sprint.id})` : "No active sprint";
1586
+ const reviewPrompt = `# Code Review Request
1587
+
1588
+ ## Context
1589
+ ${sprintInfo}
1590
+ Date: ${new Date().toISOString()}
1591
+
1592
+ ## Staged Changes (git diff)
1593
+ \`\`\`diff
1594
+ ${diff}
1595
+ \`\`\`
1596
+
1597
+ ## Instructions
1598
+ You are reviewing the staged changes at the end of a sprint. Produce a thorough markdown review report with the following sections:
1599
+
1600
+ 1. **Summary** — Brief overview of what changed and why.
1601
+ 2. **Files Changed** — List each file with a short description of changes.
1602
+ 3. **Code Quality** — Note any code quality concerns (naming, structure, complexity).
1603
+ 4. **Potential Issues** — Identify bugs, security issues, edge cases, or regressions.
1604
+ 5. **Recommendations** — Actionable suggestions for improvement.
1605
+ 6. **Overall Assessment** — A short verdict (e.g., "Looks good", "Needs attention", "Critical issues found").
1606
+
1607
+ Keep the review concise but thorough. Focus on substance over style.
1608
+ Do NOT output <promise>COMPLETE</promise> — just output the review report as markdown.`;
1609
+ log("Running AI review on staged changes...", "info");
1610
+ const report = await this.deps.aiRunner.run(reviewPrompt);
1611
+ return report;
1612
+ }
1613
+ }
1614
+
1541
1615
  // src/core/prompt-builder.ts
1542
1616
  var import_node_fs4 = require("node:fs");
1543
1617
  var import_node_os2 = require("node:os");
@@ -1903,29 +1977,8 @@ class TaskExecutor {
1903
1977
  taskContext: context
1904
1978
  });
1905
1979
  try {
1906
- let plan = null;
1907
- this.deps.log("Phase 1: Planning (CLI)...", "info");
1908
- const planningPrompt = `${basePrompt}
1909
-
1910
- ## Phase 1: Planning
1911
- Analyze and create a detailed plan for THIS SPECIFIC TASK. Do NOT execute changes yet.`;
1912
- plan = await this.deps.aiRunner.run(planningPrompt, true);
1913
1980
  this.deps.log("Starting Execution...", "info");
1914
- let executionPrompt = basePrompt;
1915
- if (plan != null) {
1916
- executionPrompt += `
1917
-
1918
- ## Phase 2: Execution
1919
- Based on the plan, execute the task:
1920
-
1921
- ${plan}`;
1922
- } else {
1923
- executionPrompt += `
1924
-
1925
- ## Execution
1926
- Execute the task directly.`;
1927
- }
1928
- executionPrompt += `
1981
+ const executionPrompt = `${basePrompt}
1929
1982
 
1930
1983
  When finished, output: <promise>COMPLETE</promise>`;
1931
1984
  const output = await this.deps.aiRunner.run(executionPrompt);
@@ -1959,11 +2012,9 @@ class AgentWorker {
1959
2012
  indexerService;
1960
2013
  documentFetcher;
1961
2014
  taskExecutor;
1962
- consecutiveEmpty = 0;
1963
- maxEmpty = 60;
2015
+ reviewService;
1964
2016
  maxTasks = 50;
1965
2017
  tasksCompleted = 0;
1966
- pollInterval = 1e4;
1967
2018
  constructor(config) {
1968
2019
  this.config = config;
1969
2020
  const projectPath = config.projectPath || process.cwd();
@@ -2000,6 +2051,11 @@ class AgentWorker {
2000
2051
  projectPath,
2001
2052
  log
2002
2053
  });
2054
+ this.reviewService = new ReviewService({
2055
+ aiRunner: this.aiRunner,
2056
+ projectPath,
2057
+ log
2058
+ });
2003
2059
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
2004
2060
  this.log(`Using ${providerLabel} CLI for all phases`, "info");
2005
2061
  }
@@ -2045,33 +2101,42 @@ class AgentWorker {
2045
2101
  await this.indexerService.reindex();
2046
2102
  return result;
2047
2103
  }
2104
+ async runStagedChangesReview(sprint) {
2105
+ try {
2106
+ const report = await this.reviewService.reviewStagedChanges(sprint);
2107
+ if (report) {
2108
+ const reviewsDir = import_node_path7.join(this.config.projectPath, LOCUS_CONFIG.dir, "reviews");
2109
+ if (!import_node_fs5.existsSync(reviewsDir)) {
2110
+ import_node_fs5.mkdirSync(reviewsDir, { recursive: true });
2111
+ }
2112
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
2113
+ const sprintSlug = sprint?.name ? sprint.name.toLowerCase().replace(/\s+/g, "-").slice(0, 40) : "no-sprint";
2114
+ const fileName = `review-${sprintSlug}-${timestamp}.md`;
2115
+ const filePath = import_node_path7.join(reviewsDir, fileName);
2116
+ import_node_fs5.writeFileSync(filePath, report);
2117
+ this.log(`Review report saved to .locus/reviews/${fileName}`, "success");
2118
+ } else {
2119
+ this.log("No staged changes to review.", "info");
2120
+ }
2121
+ } catch (err) {
2122
+ this.log(`Review failed: ${err instanceof Error ? err.message : String(err)}`, "error");
2123
+ }
2124
+ }
2048
2125
  async run() {
2049
2126
  this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
2050
2127
  const sprint = await this.getActiveSprint();
2051
2128
  if (sprint) {
2052
- this.log(`Active sprint found: ${sprint.name}. Ensuring plan is up to date...`, "info");
2053
- try {
2054
- await this.client.sprints.triggerAIPlanning(sprint.id, this.config.workspaceId);
2055
- this.log(`Sprint plan sync checked on server.`, "success");
2056
- } catch (err) {
2057
- this.log(`Sprint planning sync failed (non-critical): ${err instanceof Error ? err.message : String(err)}`, "warn");
2058
- }
2129
+ this.log(`Active sprint found: ${sprint.name}`, "info");
2059
2130
  } else {
2060
- this.log("No active sprint found for planning.", "warn");
2131
+ this.log("No active sprint found.", "warn");
2061
2132
  }
2062
- while (this.tasksCompleted < this.maxTasks && this.consecutiveEmpty < this.maxEmpty) {
2133
+ while (this.tasksCompleted < this.maxTasks) {
2063
2134
  const task = await this.getNextTask();
2064
2135
  if (!task) {
2065
- if (this.consecutiveEmpty === 0) {
2066
- this.log("Queue empty, waiting for tasks...", "info");
2067
- }
2068
- this.consecutiveEmpty++;
2069
- if (this.consecutiveEmpty >= this.maxEmpty)
2070
- break;
2071
- await new Promise((r) => setTimeout(r, this.pollInterval));
2072
- continue;
2136
+ this.log("No tasks remaining. Running review on staged changes...", "info");
2137
+ await this.runStagedChangesReview(sprint);
2138
+ break;
2073
2139
  }
2074
- this.consecutiveEmpty = 0;
2075
2140
  this.log(`Claimed: ${task.title}`, "success");
2076
2141
  const result = await this.executeTask(task);
2077
2142
  try {
@@ -14,7 +14,7 @@ export declare class ClaudeRunner implements AiRunner {
14
14
  * Set an event emitter to receive execution events.
15
15
  */
16
16
  setEventEmitter(emitter: ExecEventEmitter): void;
17
- run(prompt: string, _isPlanning?: boolean): Promise<string>;
17
+ run(prompt: string): Promise<string>;
18
18
  runStream(prompt: string): AsyncGenerator<StreamChunk, void, unknown>;
19
19
  /**
20
20
  * Emit an event corresponding to a stream chunk.
@@ -1 +1 @@
1
- {"version":3,"file":"claude-runner.d.ts","sourceRoot":"","sources":["../../src/ai/claude-runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,kBAAkB,CAAC;AAEhE,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAgC5C,qBAAa,YAAa,YAAW,QAAQ;IAQzC,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,GAAG,CAAC;IARd,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAmB;IACxC,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,WAAW,CAA+C;gBAGhE,WAAW,EAAE,MAAM,EACX,KAAK,GAAE,MAAuC,EAC9C,GAAG,CAAC,EAAE,KAAK,YAAA;IAKrB;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAI1C,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAyBxD,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC;IAqK5E;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAiCzB,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,kBAAkB;IAwE1B,OAAO,CAAC,UAAU;IAkFlB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,oBAAoB;CAO7B"}
1
+ {"version":3,"file":"claude-runner.d.ts","sourceRoot":"","sources":["../../src/ai/claude-runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,kBAAkB,CAAC;AAEhE,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAgC5C,qBAAa,YAAa,YAAW,QAAQ;IAQzC,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,GAAG,CAAC;IARd,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAmB;IACxC,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,WAAW,CAA+C;gBAGhE,WAAW,EAAE,MAAM,EACX,KAAK,GAAE,MAAuC,EAC9C,GAAG,CAAC,EAAE,KAAK,YAAA;IAKrB;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAI1C,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAyBnC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC;IAqK5E;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAiCzB,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,kBAAkB;IAwE1B,OAAO,CAAC,UAAU;IAkFlB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,oBAAoB;CAO7B"}
@@ -2,7 +2,7 @@ import type { Provider } from "../core/config.js";
2
2
  import type { ExecEventEmitter } from "../exec/event-emitter.js";
3
3
  import type { StreamChunk } from "../exec/types.js";
4
4
  export interface AiRunner {
5
- run(prompt: string, isPlanning?: boolean): Promise<string>;
5
+ run(prompt: string): Promise<string>;
6
6
  runStream(prompt: string): AsyncGenerator<StreamChunk, void, unknown>;
7
7
  /**
8
8
  * Set an event emitter to receive execution events.
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/ai/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACtE;;OAEG;IACH,eAAe,CAAC,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACnD;AAED,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/ai/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACtE;;OAEG;IACH,eAAe,CAAC,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACnD;AAED,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC"}
@@ -13,8 +13,10 @@ export declare const LOCUS_CONFIG: {
13
13
  documentsDir: string;
14
14
  agentSkillsDir: string;
15
15
  sessionsDir: string;
16
+ reviewsDir: string;
17
+ plansDir: string;
16
18
  };
17
- export declare const LOCUS_GITIGNORE_PATTERNS: readonly ["# Locus AI - Session data (user-specific, can grow large)", ".locus/sessions/", "", "# Locus AI - Artifacts (local-only, user-specific)", ".locus/artifacts/"];
19
+ export declare const LOCUS_GITIGNORE_PATTERNS: readonly ["# Locus AI - Session data (user-specific, can grow large)", ".locus/sessions/", "", "# Locus AI - Artifacts (local-only, user-specific)", ".locus/artifacts/", "", "# Locus AI - Review reports (generated per sprint)", ".locus/reviews/", "", "# Locus AI - Plans (generated per task)", ".locus/plans/"];
18
20
  export declare function getLocusPath(projectPath: string, fileName: keyof typeof LOCUS_CONFIG): string;
19
21
  /**
20
22
  * Gets the artifacts directory path for a specific agent.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ;;;CAGX,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC;AAEhE,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAGlD,CAAC;AAEF,eAAO,MAAM,YAAY;;;;;;;;;CASxB,CAAC;AAIF,eAAO,MAAM,wBAAwB,2KAM3B,CAAC;AAEX,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,OAAO,YAAY,GAClC,MAAM,CAKR;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,MAAM,CASR"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ;;;CAGX,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC;AAEhE,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAGlD,CAAC;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;CAWxB,CAAC;AAIF,eAAO,MAAM,wBAAwB,wTAY3B,CAAC;AAEX,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,OAAO,YAAY,GAClC,MAAM,CAKR;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,MAAM,CASR"}
@@ -386,6 +386,10 @@ class WorkspacesModule extends BaseModule {
386
386
  const { data } = await this.api.get(`/workspaces/${id}/stats`);
387
387
  return data;
388
388
  }
389
+ async getManifestStatus(workspaceId) {
390
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/manifest-status`);
391
+ return data;
392
+ }
389
393
  async getActivity(id, limit) {
390
394
  const { data } = await this.api.get(`/workspaces/${id}/activity`, {
391
395
  params: { limit }
@@ -508,6 +512,8 @@ __export(exports_worker, {
508
512
  AgentWorker: () => AgentWorker
509
513
  });
510
514
  module.exports = __toCommonJS(exports_worker);
515
+ var import_node_fs5 = require("node:fs");
516
+ var import_node_path7 = require("node:path");
511
517
 
512
518
  // src/core/config.ts
513
519
  var import_node_path = require("node:path");
@@ -527,14 +533,22 @@ var LOCUS_CONFIG = {
527
533
  artifactsDir: "artifacts",
528
534
  documentsDir: "documents",
529
535
  agentSkillsDir: ".agent/skills",
530
- sessionsDir: "sessions"
536
+ sessionsDir: "sessions",
537
+ reviewsDir: "reviews",
538
+ plansDir: "plans"
531
539
  };
532
540
  var LOCUS_GITIGNORE_PATTERNS = [
533
541
  "# Locus AI - Session data (user-specific, can grow large)",
534
542
  ".locus/sessions/",
535
543
  "",
536
544
  "# Locus AI - Artifacts (local-only, user-specific)",
537
- ".locus/artifacts/"
545
+ ".locus/artifacts/",
546
+ "",
547
+ "# Locus AI - Review reports (generated per sprint)",
548
+ ".locus/reviews/",
549
+ "",
550
+ "# Locus AI - Plans (generated per task)",
551
+ ".locus/plans/"
538
552
  ];
539
553
  function getLocusPath(projectPath, fileName) {
540
554
  if (fileName === "contextFile") {
@@ -629,7 +643,7 @@ class ClaudeRunner {
629
643
  setEventEmitter(emitter) {
630
644
  this.eventEmitter = emitter;
631
645
  }
632
- async run(prompt, _isPlanning = false) {
646
+ async run(prompt) {
633
647
  const maxRetries = 3;
634
648
  let lastError = null;
635
649
  for (let attempt = 1;attempt <= maxRetries; attempt++) {
@@ -1475,7 +1489,7 @@ File tree:
1475
1489
  ${tree}
1476
1490
 
1477
1491
  Return ONLY valid JSON, no markdown formatting.`;
1478
- const response = await this.deps.aiRunner.run(prompt, true);
1492
+ const response = await this.deps.aiRunner.run(prompt);
1479
1493
  const jsonMatch = response.match(/\{[\s\S]*\}/);
1480
1494
  if (jsonMatch) {
1481
1495
  return JSON.parse(jsonMatch[0]);
@@ -1538,6 +1552,66 @@ class DocumentFetcher {
1538
1552
  }
1539
1553
  }
1540
1554
 
1555
+ // src/agent/review-service.ts
1556
+ var import_node_child_process3 = require("node:child_process");
1557
+
1558
+ class ReviewService {
1559
+ deps;
1560
+ constructor(deps) {
1561
+ this.deps = deps;
1562
+ }
1563
+ async reviewStagedChanges(sprint) {
1564
+ const { projectPath, log } = this.deps;
1565
+ try {
1566
+ import_node_child_process3.execSync("git add -A", { cwd: projectPath, stdio: "pipe" });
1567
+ log("Staged all changes for review.", "info");
1568
+ } catch (err) {
1569
+ log(`Failed to stage changes: ${err instanceof Error ? err.message : String(err)}`, "error");
1570
+ return null;
1571
+ }
1572
+ let diff;
1573
+ try {
1574
+ diff = import_node_child_process3.execSync("git diff --cached --stat && echo '---' && git diff --cached", {
1575
+ cwd: projectPath,
1576
+ maxBuffer: 10 * 1024 * 1024
1577
+ }).toString();
1578
+ } catch (err) {
1579
+ log(`Failed to get staged diff: ${err instanceof Error ? err.message : String(err)}`, "error");
1580
+ return null;
1581
+ }
1582
+ if (!diff.trim()) {
1583
+ return null;
1584
+ }
1585
+ const sprintInfo = sprint ? `Sprint: ${sprint.name} (${sprint.id})` : "No active sprint";
1586
+ const reviewPrompt = `# Code Review Request
1587
+
1588
+ ## Context
1589
+ ${sprintInfo}
1590
+ Date: ${new Date().toISOString()}
1591
+
1592
+ ## Staged Changes (git diff)
1593
+ \`\`\`diff
1594
+ ${diff}
1595
+ \`\`\`
1596
+
1597
+ ## Instructions
1598
+ You are reviewing the staged changes at the end of a sprint. Produce a thorough markdown review report with the following sections:
1599
+
1600
+ 1. **Summary** — Brief overview of what changed and why.
1601
+ 2. **Files Changed** — List each file with a short description of changes.
1602
+ 3. **Code Quality** — Note any code quality concerns (naming, structure, complexity).
1603
+ 4. **Potential Issues** — Identify bugs, security issues, edge cases, or regressions.
1604
+ 5. **Recommendations** — Actionable suggestions for improvement.
1605
+ 6. **Overall Assessment** — A short verdict (e.g., "Looks good", "Needs attention", "Critical issues found").
1606
+
1607
+ Keep the review concise but thorough. Focus on substance over style.
1608
+ Do NOT output <promise>COMPLETE</promise> — just output the review report as markdown.`;
1609
+ log("Running AI review on staged changes...", "info");
1610
+ const report = await this.deps.aiRunner.run(reviewPrompt);
1611
+ return report;
1612
+ }
1613
+ }
1614
+
1541
1615
  // src/core/prompt-builder.ts
1542
1616
  var import_node_fs4 = require("node:fs");
1543
1617
  var import_node_os2 = require("node:os");
@@ -1903,29 +1977,8 @@ class TaskExecutor {
1903
1977
  taskContext: context
1904
1978
  });
1905
1979
  try {
1906
- let plan = null;
1907
- this.deps.log("Phase 1: Planning (CLI)...", "info");
1908
- const planningPrompt = `${basePrompt}
1909
-
1910
- ## Phase 1: Planning
1911
- Analyze and create a detailed plan for THIS SPECIFIC TASK. Do NOT execute changes yet.`;
1912
- plan = await this.deps.aiRunner.run(planningPrompt, true);
1913
1980
  this.deps.log("Starting Execution...", "info");
1914
- let executionPrompt = basePrompt;
1915
- if (plan != null) {
1916
- executionPrompt += `
1917
-
1918
- ## Phase 2: Execution
1919
- Based on the plan, execute the task:
1920
-
1921
- ${plan}`;
1922
- } else {
1923
- executionPrompt += `
1924
-
1925
- ## Execution
1926
- Execute the task directly.`;
1927
- }
1928
- executionPrompt += `
1981
+ const executionPrompt = `${basePrompt}
1929
1982
 
1930
1983
  When finished, output: <promise>COMPLETE</promise>`;
1931
1984
  const output = await this.deps.aiRunner.run(executionPrompt);
@@ -1959,11 +2012,9 @@ class AgentWorker {
1959
2012
  indexerService;
1960
2013
  documentFetcher;
1961
2014
  taskExecutor;
1962
- consecutiveEmpty = 0;
1963
- maxEmpty = 60;
2015
+ reviewService;
1964
2016
  maxTasks = 50;
1965
2017
  tasksCompleted = 0;
1966
- pollInterval = 1e4;
1967
2018
  constructor(config) {
1968
2019
  this.config = config;
1969
2020
  const projectPath = config.projectPath || process.cwd();
@@ -2000,6 +2051,11 @@ class AgentWorker {
2000
2051
  projectPath,
2001
2052
  log
2002
2053
  });
2054
+ this.reviewService = new ReviewService({
2055
+ aiRunner: this.aiRunner,
2056
+ projectPath,
2057
+ log
2058
+ });
2003
2059
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
2004
2060
  this.log(`Using ${providerLabel} CLI for all phases`, "info");
2005
2061
  }
@@ -2045,33 +2101,42 @@ class AgentWorker {
2045
2101
  await this.indexerService.reindex();
2046
2102
  return result;
2047
2103
  }
2104
+ async runStagedChangesReview(sprint) {
2105
+ try {
2106
+ const report = await this.reviewService.reviewStagedChanges(sprint);
2107
+ if (report) {
2108
+ const reviewsDir = import_node_path7.join(this.config.projectPath, LOCUS_CONFIG.dir, "reviews");
2109
+ if (!import_node_fs5.existsSync(reviewsDir)) {
2110
+ import_node_fs5.mkdirSync(reviewsDir, { recursive: true });
2111
+ }
2112
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
2113
+ const sprintSlug = sprint?.name ? sprint.name.toLowerCase().replace(/\s+/g, "-").slice(0, 40) : "no-sprint";
2114
+ const fileName = `review-${sprintSlug}-${timestamp}.md`;
2115
+ const filePath = import_node_path7.join(reviewsDir, fileName);
2116
+ import_node_fs5.writeFileSync(filePath, report);
2117
+ this.log(`Review report saved to .locus/reviews/${fileName}`, "success");
2118
+ } else {
2119
+ this.log("No staged changes to review.", "info");
2120
+ }
2121
+ } catch (err) {
2122
+ this.log(`Review failed: ${err instanceof Error ? err.message : String(err)}`, "error");
2123
+ }
2124
+ }
2048
2125
  async run() {
2049
2126
  this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
2050
2127
  const sprint = await this.getActiveSprint();
2051
2128
  if (sprint) {
2052
- this.log(`Active sprint found: ${sprint.name}. Ensuring plan is up to date...`, "info");
2053
- try {
2054
- await this.client.sprints.triggerAIPlanning(sprint.id, this.config.workspaceId);
2055
- this.log(`Sprint plan sync checked on server.`, "success");
2056
- } catch (err) {
2057
- this.log(`Sprint planning sync failed (non-critical): ${err instanceof Error ? err.message : String(err)}`, "warn");
2058
- }
2129
+ this.log(`Active sprint found: ${sprint.name}`, "info");
2059
2130
  } else {
2060
- this.log("No active sprint found for planning.", "warn");
2131
+ this.log("No active sprint found.", "warn");
2061
2132
  }
2062
- while (this.tasksCompleted < this.maxTasks && this.consecutiveEmpty < this.maxEmpty) {
2133
+ while (this.tasksCompleted < this.maxTasks) {
2063
2134
  const task = await this.getNextTask();
2064
2135
  if (!task) {
2065
- if (this.consecutiveEmpty === 0) {
2066
- this.log("Queue empty, waiting for tasks...", "info");
2067
- }
2068
- this.consecutiveEmpty++;
2069
- if (this.consecutiveEmpty >= this.maxEmpty)
2070
- break;
2071
- await new Promise((r) => setTimeout(r, this.pollInterval));
2072
- continue;
2136
+ this.log("No tasks remaining. Running review on staged changes...", "info");
2137
+ await this.runStagedChangesReview(sprint);
2138
+ break;
2073
2139
  }
2074
- this.consecutiveEmpty = 0;
2075
2140
  this.log(`Claimed: ${task.title}`, "success");
2076
2141
  const result = await this.executeTask(task);
2077
2142
  try {
@@ -2153,6 +2218,7 @@ __export(exports_index_node, {
2153
2218
  TasksModule: () => TasksModule,
2154
2219
  TaskExecutor: () => TaskExecutor,
2155
2220
  SprintsModule: () => SprintsModule,
2221
+ ReviewService: () => ReviewService,
2156
2222
  PromptBuilder: () => PromptBuilder,
2157
2223
  PROVIDER: () => PROVIDER,
2158
2224
  OrganizationsModule: () => OrganizationsModule,
@@ -2594,8 +2660,8 @@ class ExecEventEmitter {
2594
2660
  }
2595
2661
  }
2596
2662
  // src/exec/history-manager.ts
2597
- var import_node_fs5 = require("node:fs");
2598
- var import_node_path7 = require("node:path");
2663
+ var import_node_fs6 = require("node:fs");
2664
+ var import_node_path8 = require("node:path");
2599
2665
  var DEFAULT_MAX_SESSIONS = 30;
2600
2666
  function generateSessionId2() {
2601
2667
  const timestamp = Date.now().toString(36);
@@ -2607,30 +2673,30 @@ class HistoryManager {
2607
2673
  historyDir;
2608
2674
  maxSessions;
2609
2675
  constructor(projectPath, options) {
2610
- this.historyDir = options?.historyDir ?? import_node_path7.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
2676
+ this.historyDir = options?.historyDir ?? import_node_path8.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
2611
2677
  this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
2612
2678
  this.ensureHistoryDir();
2613
2679
  }
2614
2680
  ensureHistoryDir() {
2615
- if (!import_node_fs5.existsSync(this.historyDir)) {
2616
- import_node_fs5.mkdirSync(this.historyDir, { recursive: true });
2681
+ if (!import_node_fs6.existsSync(this.historyDir)) {
2682
+ import_node_fs6.mkdirSync(this.historyDir, { recursive: true });
2617
2683
  }
2618
2684
  }
2619
2685
  getSessionPath(sessionId) {
2620
- return import_node_path7.join(this.historyDir, `${sessionId}.json`);
2686
+ return import_node_path8.join(this.historyDir, `${sessionId}.json`);
2621
2687
  }
2622
2688
  saveSession(session) {
2623
2689
  const filePath = this.getSessionPath(session.id);
2624
2690
  session.updatedAt = Date.now();
2625
- import_node_fs5.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
2691
+ import_node_fs6.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
2626
2692
  }
2627
2693
  loadSession(sessionId) {
2628
2694
  const filePath = this.getSessionPath(sessionId);
2629
- if (!import_node_fs5.existsSync(filePath)) {
2695
+ if (!import_node_fs6.existsSync(filePath)) {
2630
2696
  return null;
2631
2697
  }
2632
2698
  try {
2633
- const content = import_node_fs5.readFileSync(filePath, "utf-8");
2699
+ const content = import_node_fs6.readFileSync(filePath, "utf-8");
2634
2700
  return JSON.parse(content);
2635
2701
  } catch {
2636
2702
  return null;
@@ -2638,18 +2704,18 @@ class HistoryManager {
2638
2704
  }
2639
2705
  deleteSession(sessionId) {
2640
2706
  const filePath = this.getSessionPath(sessionId);
2641
- if (!import_node_fs5.existsSync(filePath)) {
2707
+ if (!import_node_fs6.existsSync(filePath)) {
2642
2708
  return false;
2643
2709
  }
2644
2710
  try {
2645
- import_node_fs5.rmSync(filePath);
2711
+ import_node_fs6.rmSync(filePath);
2646
2712
  return true;
2647
2713
  } catch {
2648
2714
  return false;
2649
2715
  }
2650
2716
  }
2651
2717
  listSessions(options) {
2652
- const files = import_node_fs5.readdirSync(this.historyDir);
2718
+ const files = import_node_fs6.readdirSync(this.historyDir);
2653
2719
  let sessions = [];
2654
2720
  for (const file of files) {
2655
2721
  if (file.endsWith(".json")) {
@@ -2722,11 +2788,11 @@ class HistoryManager {
2722
2788
  return deleted;
2723
2789
  }
2724
2790
  getSessionCount() {
2725
- const files = import_node_fs5.readdirSync(this.historyDir);
2791
+ const files = import_node_fs6.readdirSync(this.historyDir);
2726
2792
  return files.filter((f) => f.endsWith(".json")).length;
2727
2793
  }
2728
2794
  sessionExists(sessionId) {
2729
- return import_node_fs5.existsSync(this.getSessionPath(sessionId));
2795
+ return import_node_fs6.existsSync(this.getSessionPath(sessionId));
2730
2796
  }
2731
2797
  findSessionByPartialId(partialId) {
2732
2798
  const sessions = this.listSessions();
@@ -2740,12 +2806,12 @@ class HistoryManager {
2740
2806
  return this.historyDir;
2741
2807
  }
2742
2808
  clearAllSessions() {
2743
- const files = import_node_fs5.readdirSync(this.historyDir);
2809
+ const files = import_node_fs6.readdirSync(this.historyDir);
2744
2810
  let deleted = 0;
2745
2811
  for (const file of files) {
2746
2812
  if (file.endsWith(".json")) {
2747
2813
  try {
2748
- import_node_fs5.rmSync(import_node_path7.join(this.historyDir, file));
2814
+ import_node_fs6.rmSync(import_node_path8.join(this.historyDir, file));
2749
2815
  deleted++;
2750
2816
  } catch {}
2751
2817
  }
@@ -3010,9 +3076,9 @@ ${currentPrompt}`);
3010
3076
  }
3011
3077
  }
3012
3078
  // src/orchestrator.ts
3013
- var import_node_child_process3 = require("node:child_process");
3014
- var import_node_fs6 = require("node:fs");
3015
- var import_node_path8 = require("node:path");
3079
+ var import_node_child_process4 = require("node:child_process");
3080
+ var import_node_fs7 = require("node:fs");
3081
+ var import_node_path9 = require("node:path");
3016
3082
  var import_node_url = require("node:url");
3017
3083
  var import_shared3 = require("@locusai/shared");
3018
3084
  var import_events4 = require("events");
@@ -3108,9 +3174,9 @@ ${c.success("✅ Orchestrator finished")}`);
3108
3174
  `);
3109
3175
  const potentialPaths = [];
3110
3176
  const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator.ts");
3111
- const currentModuleDir = import_node_path8.dirname(currentModulePath);
3112
- potentialPaths.push(import_node_path8.join(currentModuleDir, "agent", "worker.js"), import_node_path8.join(currentModuleDir, "worker.js"), import_node_path8.join(currentModuleDir, "agent", "worker.ts"));
3113
- const workerPath = potentialPaths.find((p) => import_node_fs6.existsSync(p));
3177
+ const currentModuleDir = import_node_path9.dirname(currentModulePath);
3178
+ potentialPaths.push(import_node_path9.join(currentModuleDir, "agent", "worker.js"), import_node_path9.join(currentModuleDir, "worker.js"), import_node_path9.join(currentModuleDir, "agent", "worker.ts"));
3179
+ const workerPath = potentialPaths.find((p) => import_node_fs7.existsSync(p));
3114
3180
  if (!workerPath) {
3115
3181
  throw new Error(`Worker file not found. Checked: ${potentialPaths.join(", ")}. ` + `Make sure the SDK is properly built and installed.`);
3116
3182
  }
@@ -3135,7 +3201,7 @@ ${c.success("✅ Orchestrator finished")}`);
3135
3201
  if (this.resolvedSprintId) {
3136
3202
  workerArgs.push("--sprint-id", this.resolvedSprintId);
3137
3203
  }
3138
- const agentProcess = import_node_child_process3.spawn(process.execPath, [workerPath, ...workerArgs], {
3204
+ const agentProcess = import_node_child_process4.spawn(process.execPath, [workerPath, ...workerArgs], {
3139
3205
  stdio: ["pipe", "pipe", "pipe"],
3140
3206
  env: {
3141
3207
  ...process.env,
package/dist/index.js CHANGED
@@ -386,6 +386,10 @@ class WorkspacesModule extends BaseModule {
386
386
  const { data } = await this.api.get(`/workspaces/${id}/stats`);
387
387
  return data;
388
388
  }
389
+ async getManifestStatus(workspaceId) {
390
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/manifest-status`);
391
+ return data;
392
+ }
389
393
  async getActivity(id, limit) {
390
394
  const { data } = await this.api.get(`/workspaces/${id}/activity`, {
391
395
  params: { limit }
@@ -1,4 +1,4 @@
1
- import { CreateWorkspace, Event, Task, UpdateWorkspace, Workspace, WorkspaceStats } from "@locusai/shared";
1
+ import { CreateWorkspace, Event, ManifestStatusResponse, Task, UpdateWorkspace, Workspace, WorkspaceStats } from "@locusai/shared";
2
2
  import { BaseModule } from "./base.js";
3
3
  export declare class WorkspacesModule extends BaseModule {
4
4
  listAll(): Promise<Workspace[]>;
@@ -11,6 +11,11 @@ export declare class WorkspacesModule extends BaseModule {
11
11
  update(id: string, body: UpdateWorkspace): Promise<Workspace>;
12
12
  delete(id: string): Promise<void>;
13
13
  getStats(id: string): Promise<WorkspaceStats>;
14
+ /**
15
+ * Get detailed manifest completion status for a workspace.
16
+ * Returns completion percentage and missing fields.
17
+ */
18
+ getManifestStatus(workspaceId: string): Promise<ManifestStatusResponse>;
14
19
  getActivity(id: string, limit?: number): Promise<Event[]>;
15
20
  /**
16
21
  * Dispatch a task from the workspace backlog to an agent.
@@ -1 +1 @@
1
- {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/modules/workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,KAAK,EACL,IAAI,EAEJ,eAAe,EACf,SAAS,EAET,cAAc,EAEf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,qBAAa,gBAAiB,SAAQ,UAAU;IACxC,OAAO,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAK/B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAO9C,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IASrE,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC;IAQ5D,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAKvC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC;IAQ7D,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAO7C,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAU/D;;;OAGG;IACH;;;OAGG;IACG,QAAQ,CACZ,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAYV,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAO5D,YAAY,CAChB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,CAAC;IAQrB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGtE;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB"}
1
+ {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/modules/workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,KAAK,EACL,sBAAsB,EACtB,IAAI,EAEJ,eAAe,EACf,SAAS,EAET,cAAc,EAEf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,qBAAa,gBAAiB,SAAQ,UAAU;IACxC,OAAO,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAK/B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAO9C,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IASrE,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC;IAQ5D,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAKvC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC;IAQ7D,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAOnD;;;OAGG;IACG,iBAAiB,CACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,sBAAsB,CAAC;IAO5B,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAU/D;;;OAGG;IACH;;;OAGG;IACG,QAAQ,CACZ,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAYV,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAO5D,YAAY,CAChB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,CAAC;IAQrB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGtE;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/sdk",
3
- "version": "0.7.7",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -30,7 +30,7 @@
30
30
  "clean": "rm -rf node_modules"
31
31
  },
32
32
  "dependencies": {
33
- "@locusai/shared": "^0.7.7",
33
+ "@locusai/shared": "^0.8.1",
34
34
  "axios": "^1.13.2",
35
35
  "events": "^3.3.0",
36
36
  "globby": "^14.0.2"