@locusai/cli 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.
package/README.md CHANGED
@@ -0,0 +1,155 @@
1
+ # @locusai/cli
2
+
3
+ Command-line interface for [Locus](https://locusai.dev) - an AI-native project management platform for engineering teams.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @locusai/cli
9
+ ```
10
+
11
+ Or with other package managers:
12
+
13
+ ```bash
14
+ # pnpm
15
+ pnpm add -g @locusai/cli
16
+
17
+ # yarn
18
+ yarn global add @locusai/cli
19
+
20
+ # bun
21
+ bun add -g @locusai/cli
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```bash
27
+ # Initialize Locus in your project
28
+ locus init
29
+
30
+ # Index your codebase for AI context
31
+ locus index
32
+
33
+ # Run an agent to work on tasks
34
+ locus run --api-key YOUR_API_KEY
35
+
36
+ # Execute a prompt with repository context
37
+ locus exec "Explain the authentication flow"
38
+ ```
39
+
40
+ ## Commands
41
+
42
+ ### `locus init`
43
+
44
+ Initialize Locus in the current directory. Creates the necessary configuration files and directory structure:
45
+
46
+ - `.locus/` - Configuration directory
47
+ - `.locus/config.json` - Project settings
48
+ - `CLAUDE.md` - AI instructions and context
49
+ - `.agent/skills/` - Domain-specific agent skills
50
+
51
+ Running `init` on an already initialized project will update the configuration to the latest version.
52
+
53
+ ### `locus index`
54
+
55
+ Index the codebase for AI context. This analyzes your project structure and creates a searchable index that helps AI agents understand your codebase.
56
+
57
+ ```bash
58
+ locus index [options]
59
+
60
+ Options:
61
+ --dir <path> Project directory (default: current directory)
62
+ --model <name> AI model to use
63
+ --provider <name> AI provider: claude or codex (default: claude)
64
+ ```
65
+
66
+ ### `locus run`
67
+
68
+ Start an agent to work on tasks from your Locus workspace.
69
+
70
+ ```bash
71
+ locus run [options]
72
+
73
+ Options:
74
+ --api-key <key> Your Locus API key (required)
75
+ --workspace <id> Workspace ID to connect to
76
+ --sprint <id> Sprint ID to work on
77
+ --model <name> AI model to use
78
+ --provider <name> AI provider: claude or codex (default: claude)
79
+ --api-url <url> Custom API URL
80
+ --dir <path> Project directory (default: current directory)
81
+ --skip-planning Skip the planning phase
82
+ ```
83
+
84
+ ### `locus exec`
85
+
86
+ Run a prompt with repository context. Supports both single execution and interactive REPL mode.
87
+
88
+ ```bash
89
+ locus exec "your prompt" [options]
90
+ locus exec --interactive [options]
91
+
92
+ Options:
93
+ --interactive, -i Start interactive REPL mode
94
+ --session, -s <id> Resume a previous session
95
+ --model <name> AI model to use
96
+ --provider <name> AI provider: claude or codex (default: claude)
97
+ --dir <path> Project directory (default: current directory)
98
+ --no-stream Disable streaming output
99
+ --no-status Disable status display
100
+ ```
101
+
102
+ #### Session Management
103
+
104
+ Manage your exec sessions with these subcommands:
105
+
106
+ ```bash
107
+ # List recent sessions
108
+ locus exec sessions list
109
+
110
+ # Show messages from a session
111
+ locus exec sessions show <session-id>
112
+
113
+ # Delete a session
114
+ locus exec sessions delete <session-id>
115
+
116
+ # Clear all sessions
117
+ locus exec sessions clear
118
+ ```
119
+
120
+ ## Configuration
121
+
122
+ Locus stores its configuration in the `.locus/` directory within your project:
123
+
124
+ - `config.json` - Project settings including workspace ID and version
125
+ - `codebase-index.json` - Indexed codebase structure
126
+
127
+ The `CLAUDE.md` file in your project root provides AI instructions and context that agents use when working on your codebase.
128
+
129
+ ## AI Providers
130
+
131
+ Locus supports multiple AI providers:
132
+
133
+ - **Claude** (default) - Anthropic's Claude models
134
+ - **Codex** - OpenAI Codex models
135
+
136
+ Specify the provider with the `--provider` flag:
137
+
138
+ ```bash
139
+ locus exec "your prompt" --provider codex
140
+ locus run --api-key YOUR_KEY --provider claude
141
+ ```
142
+
143
+ ## Requirements
144
+
145
+ - Node.js 18 or later
146
+ - A Locus API key (for `run` command)
147
+
148
+ ## Links
149
+
150
+ - [Documentation](https://locusai.dev/docs)
151
+ - [Website](https://locusai.dev)
152
+
153
+ ## License
154
+
155
+ MIT
@@ -17011,6 +17011,10 @@ var require_ignore = __commonJS((exports, module) => {
17011
17011
  define(module.exports, Symbol.for("setupWindows"), setupWindows);
17012
17012
  });
17013
17013
 
17014
+ // ../sdk/src/agent/worker.ts
17015
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
17016
+ import { join as join6 } from "node:path";
17017
+
17014
17018
  // ../sdk/src/core/config.ts
17015
17019
  import { join } from "node:path";
17016
17020
  var PROVIDER = {
@@ -17029,7 +17033,9 @@ var LOCUS_CONFIG = {
17029
17033
  artifactsDir: "artifacts",
17030
17034
  documentsDir: "documents",
17031
17035
  agentSkillsDir: ".agent/skills",
17032
- sessionsDir: "sessions"
17036
+ sessionsDir: "sessions",
17037
+ reviewsDir: "reviews",
17038
+ plansDir: "plans"
17033
17039
  };
17034
17040
  function getLocusPath(projectPath, fileName) {
17035
17041
  if (fileName === "contextFile") {
@@ -17120,7 +17126,7 @@ class ClaudeRunner {
17120
17126
  setEventEmitter(emitter) {
17121
17127
  this.eventEmitter = emitter;
17122
17128
  }
17123
- async run(prompt, _isPlanning = false) {
17129
+ async run(prompt) {
17124
17130
  const maxRetries = 3;
17125
17131
  let lastError = null;
17126
17132
  for (let attempt = 1;attempt <= maxRetries; attempt++) {
@@ -34981,6 +34987,10 @@ var EventType;
34981
34987
  EventType2["SPRINT_STATUS_CHANGED"] = "SPRINT_STATUS_CHANGED";
34982
34988
  EventType2["SPRINT_DELETED"] = "SPRINT_DELETED";
34983
34989
  EventType2["CHECKLIST_INITIALIZED"] = "CHECKLIST_INITIALIZED";
34990
+ EventType2["INTERVIEW_STARTED"] = "INTERVIEW_STARTED";
34991
+ EventType2["INTERVIEW_FIELD_COMPLETED"] = "INTERVIEW_FIELD_COMPLETED";
34992
+ EventType2["INTERVIEW_COMPLETED"] = "INTERVIEW_COMPLETED";
34993
+ EventType2["INTERVIEW_ABANDONED"] = "INTERVIEW_ABANDONED";
34984
34994
  })(EventType ||= {});
34985
34995
  // ../shared/src/models/activity.ts
34986
34996
  var CommentSchema = BaseEntitySchema.extend({
@@ -35046,6 +35056,25 @@ var CiRanPayloadSchema = exports_external.object({
35046
35056
  processed: exports_external.boolean(),
35047
35057
  commands: exports_external.array(exports_external.object({ cmd: exports_external.string(), exitCode: exports_external.number() }))
35048
35058
  });
35059
+ var InterviewStartedPayloadSchema = exports_external.object({
35060
+ workspaceName: exports_external.string(),
35061
+ firstFieldName: exports_external.string()
35062
+ });
35063
+ var InterviewFieldCompletedPayloadSchema = exports_external.object({
35064
+ fieldName: exports_external.string(),
35065
+ completionPercentage: exports_external.number()
35066
+ });
35067
+ var InterviewCompletedPayloadSchema = exports_external.object({
35068
+ workspaceName: exports_external.string(),
35069
+ timeToCompleteMs: exports_external.number(),
35070
+ completedVia: exports_external.enum(["interview", "manual_bypass"])
35071
+ });
35072
+ var InterviewAbandonedPayloadSchema = exports_external.object({
35073
+ workspaceName: exports_external.string(),
35074
+ lastActiveAt: exports_external.union([exports_external.date(), exports_external.string()]),
35075
+ completionPercentage: exports_external.number(),
35076
+ daysInactive: exports_external.number()
35077
+ });
35049
35078
  var EventPayloadSchema = exports_external.discriminatedUnion("type", [
35050
35079
  exports_external.object({
35051
35080
  type: exports_external.literal("TASK_CREATED" /* TASK_CREATED */),
@@ -35090,6 +35119,22 @@ var EventPayloadSchema = exports_external.discriminatedUnion("type", [
35090
35119
  exports_external.object({
35091
35120
  type: exports_external.literal("CI_RAN" /* CI_RAN */),
35092
35121
  payload: CiRanPayloadSchema
35122
+ }),
35123
+ exports_external.object({
35124
+ type: exports_external.literal("INTERVIEW_STARTED" /* INTERVIEW_STARTED */),
35125
+ payload: InterviewStartedPayloadSchema
35126
+ }),
35127
+ exports_external.object({
35128
+ type: exports_external.literal("INTERVIEW_FIELD_COMPLETED" /* INTERVIEW_FIELD_COMPLETED */),
35129
+ payload: InterviewFieldCompletedPayloadSchema
35130
+ }),
35131
+ exports_external.object({
35132
+ type: exports_external.literal("INTERVIEW_COMPLETED" /* INTERVIEW_COMPLETED */),
35133
+ payload: InterviewCompletedPayloadSchema
35134
+ }),
35135
+ exports_external.object({
35136
+ type: exports_external.literal("INTERVIEW_ABANDONED" /* INTERVIEW_ABANDONED */),
35137
+ payload: InterviewAbandonedPayloadSchema
35093
35138
  })
35094
35139
  ]);
35095
35140
  var EventSchema = exports_external.object({
@@ -35159,7 +35204,7 @@ var UnlockSchema = exports_external.object({
35159
35204
  var AIRoleSchema = exports_external.enum(["user", "assistant", "system"]);
35160
35205
  var AIArtifactSchema = exports_external.object({
35161
35206
  id: exports_external.string(),
35162
- type: exports_external.enum(["code", "document", "sprint", "task_list", "task"]),
35207
+ type: exports_external.enum(["code", "document"]),
35163
35208
  title: exports_external.string(),
35164
35209
  content: exports_external.string(),
35165
35210
  language: exports_external.string().optional(),
@@ -35167,13 +35212,7 @@ var AIArtifactSchema = exports_external.object({
35167
35212
  });
35168
35213
  var SuggestedActionSchema = exports_external.object({
35169
35214
  label: exports_external.string(),
35170
- type: exports_external.enum([
35171
- "create_task",
35172
- "create_doc",
35173
- "chat_suggestion",
35174
- "start_sprint",
35175
- "plan_sprint"
35176
- ]),
35215
+ type: exports_external.enum(["chat_suggestion", "create_doc"]),
35177
35216
  payload: exports_external.any()
35178
35217
  });
35179
35218
  var AIMessageSchema = exports_external.object({
@@ -35436,6 +35475,53 @@ var AcceptInvitationResponseSchema = exports_external.object({
35436
35475
  createdAt: exports_external.number()
35437
35476
  })
35438
35477
  });
35478
+ // ../shared/src/models/manifest.ts
35479
+ var ProjectPhaseSchema = exports_external.enum([
35480
+ "PLANNING",
35481
+ "MVP_BUILD",
35482
+ "SCALING",
35483
+ "MAINTENANCE"
35484
+ ]);
35485
+ var SprintStatusSchema = exports_external.enum(["PLANNED", "ACTIVE", "COMPLETED"]);
35486
+ var ProjectSprintSchema = exports_external.object({
35487
+ id: exports_external.string(),
35488
+ goal: exports_external.string(),
35489
+ tasks: exports_external.array(exports_external.string()),
35490
+ status: SprintStatusSchema
35491
+ });
35492
+ var MilestoneStatusSchema = exports_external.enum(["PENDING", "COMPLETED"]);
35493
+ var ProjectMilestoneSchema = exports_external.object({
35494
+ title: exports_external.string(),
35495
+ date: exports_external.string().optional(),
35496
+ status: MilestoneStatusSchema
35497
+ });
35498
+ var ProjectTimelineSchema = exports_external.object({
35499
+ sprints: exports_external.array(ProjectSprintSchema),
35500
+ milestones: exports_external.array(ProjectMilestoneSchema)
35501
+ });
35502
+ var RepositoryContextSchema = exports_external.object({
35503
+ summary: exports_external.string(),
35504
+ fileStructure: exports_external.string(),
35505
+ dependencies: exports_external.record(exports_external.string(), exports_external.string()),
35506
+ frameworks: exports_external.array(exports_external.string()),
35507
+ configFiles: exports_external.array(exports_external.string()),
35508
+ lastAnalysis: exports_external.string()
35509
+ });
35510
+ var ProjectManifestSchema = exports_external.object({
35511
+ name: exports_external.string(),
35512
+ mission: exports_external.string(),
35513
+ targetUsers: exports_external.array(exports_external.string()),
35514
+ techStack: exports_external.array(exports_external.string()),
35515
+ phase: ProjectPhaseSchema,
35516
+ features: exports_external.array(exports_external.string()),
35517
+ competitors: exports_external.array(exports_external.string()),
35518
+ brandVoice: exports_external.string().optional(),
35519
+ successMetrics: exports_external.array(exports_external.string()).optional(),
35520
+ completenessScore: exports_external.number().min(0).max(100),
35521
+ timeline: ProjectTimelineSchema.optional(),
35522
+ repositoryState: RepositoryContextSchema.optional()
35523
+ });
35524
+ var PartialProjectManifestSchema = ProjectManifestSchema.partial();
35439
35525
  // ../shared/src/models/organization.ts
35440
35526
  var OrganizationSchema = BaseEntitySchema.extend({
35441
35527
  name: exports_external.string().min(1, "Name is required").max(100),
@@ -35625,11 +35711,15 @@ var WorkspaceAndUserParamSchema = exports_external.object({
35625
35711
  workspaceId: exports_external.string().uuid("Invalid Workspace ID"),
35626
35712
  userId: exports_external.string().uuid("Invalid User ID")
35627
35713
  });
35714
+ var WorkspaceWithManifestInfoSchema = WorkspaceSchema.extend({
35715
+ isManifestComplete: exports_external.boolean(),
35716
+ manifestCompletionPercentage: exports_external.number().min(0).max(100)
35717
+ });
35628
35718
  var WorkspaceResponseSchema = exports_external.object({
35629
- workspace: WorkspaceSchema
35719
+ workspace: WorkspaceWithManifestInfoSchema
35630
35720
  });
35631
35721
  var WorkspacesResponseSchema = exports_external.object({
35632
- workspaces: exports_external.array(WorkspaceSchema)
35722
+ workspaces: exports_external.array(WorkspaceWithManifestInfoSchema)
35633
35723
  });
35634
35724
  var WorkspaceStatsSchema = exports_external.object({
35635
35725
  workspaceName: exports_external.string(),
@@ -35639,6 +35729,13 @@ var WorkspaceStatsSchema = exports_external.object({
35639
35729
  var WorkspaceStatsResponseSchema = exports_external.object({
35640
35730
  stats: WorkspaceStatsSchema
35641
35731
  });
35732
+ var ManifestStatusResponseSchema = exports_external.object({
35733
+ isComplete: exports_external.boolean(),
35734
+ percentage: exports_external.number().min(0).max(100),
35735
+ missingFields: exports_external.array(exports_external.string()),
35736
+ filledFields: exports_external.array(exports_external.string()),
35737
+ completenessScore: exports_external.number().min(0).max(100).nullable()
35738
+ });
35642
35739
  // ../sdk/src/modules/tasks.ts
35643
35740
  class TasksModule extends BaseModule {
35644
35741
  async list(workspaceId, options) {
@@ -35728,6 +35825,10 @@ class WorkspacesModule extends BaseModule {
35728
35825
  const { data } = await this.api.get(`/workspaces/${id}/stats`);
35729
35826
  return data;
35730
35827
  }
35828
+ async getManifestStatus(workspaceId) {
35829
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/manifest-status`);
35830
+ return data;
35831
+ }
35731
35832
  async getActivity(id, limit) {
35732
35833
  const { data } = await this.api.get(`/workspaces/${id}/activity`, {
35733
35834
  params: { limit }
@@ -36591,7 +36692,7 @@ File tree:
36591
36692
  ${tree}
36592
36693
 
36593
36694
  Return ONLY valid JSON, no markdown formatting.`;
36594
- const response = await this.deps.aiRunner.run(prompt, true);
36695
+ const response = await this.deps.aiRunner.run(prompt);
36595
36696
  const jsonMatch = response.match(/\{[\s\S]*\}/);
36596
36697
  if (jsonMatch) {
36597
36698
  return JSON.parse(jsonMatch[0]);
@@ -36654,6 +36755,66 @@ class DocumentFetcher {
36654
36755
  }
36655
36756
  }
36656
36757
 
36758
+ // ../sdk/src/agent/review-service.ts
36759
+ import { execSync } from "node:child_process";
36760
+
36761
+ class ReviewService {
36762
+ deps;
36763
+ constructor(deps) {
36764
+ this.deps = deps;
36765
+ }
36766
+ async reviewStagedChanges(sprint2) {
36767
+ const { projectPath, log } = this.deps;
36768
+ try {
36769
+ execSync("git add -A", { cwd: projectPath, stdio: "pipe" });
36770
+ log("Staged all changes for review.", "info");
36771
+ } catch (err) {
36772
+ log(`Failed to stage changes: ${err instanceof Error ? err.message : String(err)}`, "error");
36773
+ return null;
36774
+ }
36775
+ let diff;
36776
+ try {
36777
+ diff = execSync("git diff --cached --stat && echo '---' && git diff --cached", {
36778
+ cwd: projectPath,
36779
+ maxBuffer: 10 * 1024 * 1024
36780
+ }).toString();
36781
+ } catch (err) {
36782
+ log(`Failed to get staged diff: ${err instanceof Error ? err.message : String(err)}`, "error");
36783
+ return null;
36784
+ }
36785
+ if (!diff.trim()) {
36786
+ return null;
36787
+ }
36788
+ const sprintInfo = sprint2 ? `Sprint: ${sprint2.name} (${sprint2.id})` : "No active sprint";
36789
+ const reviewPrompt = `# Code Review Request
36790
+
36791
+ ## Context
36792
+ ${sprintInfo}
36793
+ Date: ${new Date().toISOString()}
36794
+
36795
+ ## Staged Changes (git diff)
36796
+ \`\`\`diff
36797
+ ${diff}
36798
+ \`\`\`
36799
+
36800
+ ## Instructions
36801
+ You are reviewing the staged changes at the end of a sprint. Produce a thorough markdown review report with the following sections:
36802
+
36803
+ 1. **Summary** — Brief overview of what changed and why.
36804
+ 2. **Files Changed** — List each file with a short description of changes.
36805
+ 3. **Code Quality** — Note any code quality concerns (naming, structure, complexity).
36806
+ 4. **Potential Issues** — Identify bugs, security issues, edge cases, or regressions.
36807
+ 5. **Recommendations** — Actionable suggestions for improvement.
36808
+ 6. **Overall Assessment** — A short verdict (e.g., "Looks good", "Needs attention", "Critical issues found").
36809
+
36810
+ Keep the review concise but thorough. Focus on substance over style.
36811
+ Do NOT output <promise>COMPLETE</promise> — just output the review report as markdown.`;
36812
+ log("Running AI review on staged changes...", "info");
36813
+ const report = await this.deps.aiRunner.run(reviewPrompt);
36814
+ return report;
36815
+ }
36816
+ }
36817
+
36657
36818
  // ../sdk/src/core/prompt-builder.ts
36658
36819
  import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
36659
36820
  import { homedir } from "node:os";
@@ -37018,29 +37179,8 @@ class TaskExecutor {
37018
37179
  taskContext: context
37019
37180
  });
37020
37181
  try {
37021
- let plan = null;
37022
- this.deps.log("Phase 1: Planning (CLI)...", "info");
37023
- const planningPrompt = `${basePrompt}
37024
-
37025
- ## Phase 1: Planning
37026
- Analyze and create a detailed plan for THIS SPECIFIC TASK. Do NOT execute changes yet.`;
37027
- plan = await this.deps.aiRunner.run(planningPrompt, true);
37028
37182
  this.deps.log("Starting Execution...", "info");
37029
- let executionPrompt = basePrompt;
37030
- if (plan != null) {
37031
- executionPrompt += `
37032
-
37033
- ## Phase 2: Execution
37034
- Based on the plan, execute the task:
37035
-
37036
- ${plan}`;
37037
- } else {
37038
- executionPrompt += `
37039
-
37040
- ## Execution
37041
- Execute the task directly.`;
37042
- }
37043
- executionPrompt += `
37183
+ const executionPrompt = `${basePrompt}
37044
37184
 
37045
37185
  When finished, output: <promise>COMPLETE</promise>`;
37046
37186
  const output = await this.deps.aiRunner.run(executionPrompt);
@@ -37074,11 +37214,9 @@ class AgentWorker {
37074
37214
  indexerService;
37075
37215
  documentFetcher;
37076
37216
  taskExecutor;
37077
- consecutiveEmpty = 0;
37078
- maxEmpty = 60;
37217
+ reviewService;
37079
37218
  maxTasks = 50;
37080
37219
  tasksCompleted = 0;
37081
- pollInterval = 1e4;
37082
37220
  constructor(config2) {
37083
37221
  this.config = config2;
37084
37222
  const projectPath = config2.projectPath || process.cwd();
@@ -37115,6 +37253,11 @@ class AgentWorker {
37115
37253
  projectPath,
37116
37254
  log
37117
37255
  });
37256
+ this.reviewService = new ReviewService({
37257
+ aiRunner: this.aiRunner,
37258
+ projectPath,
37259
+ log
37260
+ });
37118
37261
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
37119
37262
  this.log(`Using ${providerLabel} CLI for all phases`, "info");
37120
37263
  }
@@ -37160,33 +37303,42 @@ class AgentWorker {
37160
37303
  await this.indexerService.reindex();
37161
37304
  return result;
37162
37305
  }
37306
+ async runStagedChangesReview(sprint2) {
37307
+ try {
37308
+ const report = await this.reviewService.reviewStagedChanges(sprint2);
37309
+ if (report) {
37310
+ const reviewsDir = join6(this.config.projectPath, LOCUS_CONFIG.dir, "reviews");
37311
+ if (!existsSync5(reviewsDir)) {
37312
+ mkdirSync3(reviewsDir, { recursive: true });
37313
+ }
37314
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
37315
+ const sprintSlug = sprint2?.name ? sprint2.name.toLowerCase().replace(/\s+/g, "-").slice(0, 40) : "no-sprint";
37316
+ const fileName = `review-${sprintSlug}-${timestamp}.md`;
37317
+ const filePath = join6(reviewsDir, fileName);
37318
+ writeFileSync3(filePath, report);
37319
+ this.log(`Review report saved to .locus/reviews/${fileName}`, "success");
37320
+ } else {
37321
+ this.log("No staged changes to review.", "info");
37322
+ }
37323
+ } catch (err) {
37324
+ this.log(`Review failed: ${err instanceof Error ? err.message : String(err)}`, "error");
37325
+ }
37326
+ }
37163
37327
  async run() {
37164
37328
  this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
37165
37329
  const sprint2 = await this.getActiveSprint();
37166
37330
  if (sprint2) {
37167
- this.log(`Active sprint found: ${sprint2.name}. Ensuring plan is up to date...`, "info");
37168
- try {
37169
- await this.client.sprints.triggerAIPlanning(sprint2.id, this.config.workspaceId);
37170
- this.log(`Sprint plan sync checked on server.`, "success");
37171
- } catch (err) {
37172
- this.log(`Sprint planning sync failed (non-critical): ${err instanceof Error ? err.message : String(err)}`, "warn");
37173
- }
37331
+ this.log(`Active sprint found: ${sprint2.name}`, "info");
37174
37332
  } else {
37175
- this.log("No active sprint found for planning.", "warn");
37333
+ this.log("No active sprint found.", "warn");
37176
37334
  }
37177
- while (this.tasksCompleted < this.maxTasks && this.consecutiveEmpty < this.maxEmpty) {
37335
+ while (this.tasksCompleted < this.maxTasks) {
37178
37336
  const task2 = await this.getNextTask();
37179
37337
  if (!task2) {
37180
- if (this.consecutiveEmpty === 0) {
37181
- this.log("Queue empty, waiting for tasks...", "info");
37182
- }
37183
- this.consecutiveEmpty++;
37184
- if (this.consecutiveEmpty >= this.maxEmpty)
37185
- break;
37186
- await new Promise((r) => setTimeout(r, this.pollInterval));
37187
- continue;
37338
+ this.log("No tasks remaining. Running review on staged changes...", "info");
37339
+ await this.runStagedChangesReview(sprint2);
37340
+ break;
37188
37341
  }
37189
- this.consecutiveEmpty = 0;
37190
37342
  this.log(`Claimed: ${task2.title}`, "success");
37191
37343
  const result = await this.executeTask(task2);
37192
37344
  try {