@nomad-e/bluma-cli 0.1.75 → 0.1.76

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 (2) hide show
  1. package/dist/main.js +199 -175
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -299,21 +299,22 @@ function assessCommandSafety(command, policy = getSandboxPolicy()) {
299
299
  return { allowed: false, risk: "blocked", reason: entry.reason };
300
300
  }
301
301
  }
302
+ const skipConfirmation = ruleDecision === "allow";
302
303
  if (HIGH_RISK_COMMAND_PATTERNS.some((pattern) => pattern.test(trimmed))) {
303
304
  return {
304
305
  allowed: true,
305
306
  risk: policy.isSandbox ? "high" : "high",
306
- reason: policy.isSandbox ? "High-risk command allowed inside the workspace sandbox." : "High-risk command requires explicit approval outside sandbox mode."
307
+ reason: skipConfirmation ? "Command allowed by permission rules engine." : policy.isSandbox ? "High-risk command allowed inside the workspace sandbox." : "High-risk command requires explicit approval outside sandbox mode."
307
308
  };
308
309
  }
309
310
  if (MODERATE_RISK_COMMAND_PATTERNS.some((pattern) => pattern.test(trimmed))) {
310
311
  return {
311
312
  allowed: true,
312
313
  risk: "moderate",
313
- reason: policy.isSandbox ? "Workspace mutation command allowed inside the sandbox." : "Workspace mutation command requires confirmation outside sandbox mode."
314
+ reason: skipConfirmation ? "Command allowed by permission rules engine." : policy.isSandbox ? "Workspace mutation command allowed inside the sandbox." : "Workspace mutation command requires confirmation outside sandbox mode."
314
315
  };
315
316
  }
316
- if (ruleDecision === "allow") {
317
+ if (skipConfirmation) {
317
318
  return { allowed: true, risk: "safe", reason: "Command allowed by permission rules engine." };
318
319
  }
319
320
  return { allowed: true, risk: "safe" };
@@ -327,7 +328,7 @@ var init_sandbox_policy = __esm({
327
328
  BLOCKED_COMMAND_PATTERNS = [
328
329
  { pattern: /\bsudo\b/, reason: "Privilege escalation is not allowed." },
329
330
  { pattern: /\bsu\b\s/, reason: "User switching is not allowed." },
330
- { pattern: /rm\s+-rf\s+\/\s*$/, reason: "Deleting root filesystem is blocked." },
331
+ { pattern: /\brm\s+-rf\s+\/(?:\s*(?:$|[;&|]))/, reason: "Deleting root filesystem is blocked." },
331
332
  { pattern: /\bcurl\b.*\|\s*(bash|sh|zsh)/i, reason: "Pipe-to-shell execution is blocked." },
332
333
  { pattern: /\bwget\b.*\|\s*(bash|sh|zsh)/i, reason: "Pipe-to-shell execution is blocked." },
333
334
  { pattern: /\beval\b\s*\(/, reason: "Eval execution is blocked." },
@@ -2367,7 +2368,7 @@ var getSlashCommands = () => [
2367
2368
  },
2368
2369
  {
2369
2370
  name: "/review",
2370
- description: "review coordinator \u2014 spawn specialized QA reviewers in parallel (security, logic, perf, quality, tests, architecture)",
2371
+ description: "review changes directly or use /review mason for parallel specialist reviewers (slower, deeper)",
2371
2372
  category: "agent"
2372
2373
  },
2373
2374
  {
@@ -4770,8 +4771,12 @@ var renderAskUserQuestion = ({ args }) => {
4770
4771
  const parsed = parseArgs(args);
4771
4772
  const qs = Array.isArray(parsed.questions) ? parsed.questions : [];
4772
4773
  const q0 = qs[0];
4773
- const qtext = typeof q0?.question === "string" ? truncate2(q0.question, 100) : "(question)";
4774
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, wrap: "wrap", children: qtext }) });
4774
+ const options = Array.isArray(q0?.options) ? q0.options.length : 0;
4775
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, wrap: "wrap", children: [
4776
+ "Awaiting user answer",
4777
+ qs.length > 0 ? ` \xB7 ${qs.length} question${qs.length === 1 ? "" : "s"}` : "",
4778
+ options > 0 ? ` \xB7 ${options} option${options === 1 ? "" : "s"}` : ""
4779
+ ] }) });
4775
4780
  };
4776
4781
  var renderPlanMode = ({ args }) => {
4777
4782
  const parsed = parseArgs(args);
@@ -14739,6 +14744,46 @@ Next steps: ${anchor.nextSteps}`;
14739
14744
  }
14740
14745
 
14741
14746
  // src/app/agent/core/context-api/context_manager.ts
14747
+ function isValidJsonArguments(value) {
14748
+ if (typeof value !== "string") return false;
14749
+ try {
14750
+ JSON.parse(value);
14751
+ return true;
14752
+ } catch {
14753
+ return false;
14754
+ }
14755
+ }
14756
+ function sanitizeConversationForProvider(conversationHistory) {
14757
+ const cleaned = [];
14758
+ const issues = [];
14759
+ let droppingCorruptTurn = false;
14760
+ for (let index = 0; index < conversationHistory.length; index += 1) {
14761
+ const msg = conversationHistory[index];
14762
+ if (droppingCorruptTurn) {
14763
+ if (msg?.role === "assistant") {
14764
+ continue;
14765
+ }
14766
+ droppingCorruptTurn = false;
14767
+ }
14768
+ const toolCalls = Array.isArray(msg?.tool_calls) ? msg.tool_calls : null;
14769
+ if (msg?.role === "assistant" && toolCalls && toolCalls.length > 0) {
14770
+ const invalidCalls = toolCalls.filter(
14771
+ (call) => !isValidJsonArguments(call?.function?.arguments)
14772
+ );
14773
+ if (invalidCalls.length > 0) {
14774
+ issues.push({
14775
+ index,
14776
+ reason: "assistant tool_calls had invalid JSON arguments",
14777
+ toolNames: invalidCalls.map((call) => String(call?.function?.name ?? "unknown"))
14778
+ });
14779
+ droppingCorruptTurn = true;
14780
+ continue;
14781
+ }
14782
+ }
14783
+ cleaned.push(conversationHistory[index]);
14784
+ }
14785
+ return { messages: cleaned, issues };
14786
+ }
14742
14787
  function partitionConversationIntoTurnSlices(conversationHistory) {
14743
14788
  const turns = [];
14744
14789
  let current = [];
@@ -14774,13 +14819,15 @@ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurn
14774
14819
  const tokenBudget = options?.tokenBudget ?? CONTEXT_TOKEN_BUDGET;
14775
14820
  const compressThreshold = options?.compressThreshold ?? COMPRESS_THRESHOLD;
14776
14821
  const keepRecentTurns = options?.keepRecentTurns ?? KEEP_RECENT_TURNS;
14822
+ const sanitized = sanitizeConversationForProvider(fullHistory);
14823
+ const safeHistory = sanitized.messages;
14777
14824
  const systemMessages = [];
14778
14825
  let historyStartIndex = 0;
14779
- while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
14780
- systemMessages.push(fullHistory[historyStartIndex]);
14826
+ while (historyStartIndex < safeHistory.length && safeHistory[historyStartIndex].role === "system") {
14827
+ systemMessages.push(safeHistory[historyStartIndex]);
14781
14828
  historyStartIndex++;
14782
14829
  }
14783
- const conversationHistory = fullHistory.slice(historyStartIndex);
14830
+ const conversationHistory = safeHistory.slice(historyStartIndex);
14784
14831
  const turnSlices = partitionConversationIntoTurnSlices(conversationHistory);
14785
14832
  const n = turnSlices.length;
14786
14833
  const recentStart = Math.max(0, n - keepRecentTurns);
@@ -15094,8 +15141,9 @@ function formatLlmUiError(error) {
15094
15141
  }
15095
15142
  return {
15096
15143
  message: message2,
15097
- details: rawMessage,
15098
- hint
15144
+ details: "See server logs for technical details.",
15145
+ hint,
15146
+ rawMessage
15099
15147
  };
15100
15148
  }
15101
15149
 
@@ -15254,7 +15302,15 @@ var ToolCallNormalizer = class {
15254
15302
  * Valida se um tool call normalizado é válido
15255
15303
  */
15256
15304
  static isValidToolCall(call) {
15257
- return !!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string");
15305
+ if (!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string")) {
15306
+ return false;
15307
+ }
15308
+ try {
15309
+ JSON.parse(call.function.arguments);
15310
+ return true;
15311
+ } catch {
15312
+ return false;
15313
+ }
15258
15314
  }
15259
15315
  };
15260
15316
 
@@ -15538,7 +15594,8 @@ function buildTurnStartBackendMessage(params) {
15538
15594
  }
15539
15595
 
15540
15596
  // src/app/agent/bluma/core/bluma.ts
15541
- var BluMaAgent = class {
15597
+ var BluMaAgent = class _BluMaAgent {
15598
+ static MAX_INVALID_TOOL_CALL_RETRIES = 3;
15542
15599
  llm;
15543
15600
  sessionId;
15544
15601
  sessionFile = "";
@@ -15557,6 +15614,8 @@ var BluMaAgent = class {
15557
15614
  factorRouterTurnClosed = false;
15558
15615
  /** Passos seguidos sem tool_calls nem texto visível (só raciocínio) — evita loop lento no mesmo turno. */
15559
15616
  emptyAssistantReplySteps = 0;
15617
+ /** Reintentos consecutivos por tool call inválido. */
15618
+ invalidToolCallRetrySteps = 0;
15560
15619
  /** Deduplicação de reasoning chunks no streaming — evita repetição. */
15561
15620
  lastReasoningChunkRef = "";
15562
15621
  constructor(sessionId, eventBus, llm, mcpClient, feedbackSystem) {
@@ -15601,6 +15660,33 @@ var BluMaAgent = class {
15601
15660
  if (!this.sessionFile) return;
15602
15661
  void saveSessionHistory(this.sessionFile, this.history, this.getMemorySnapshot());
15603
15662
  }
15663
+ async handleInvalidToolCallRetry(message2) {
15664
+ this.invalidToolCallRetrySteps += 1;
15665
+ if (this.history[this.history.length - 1] === message2) {
15666
+ this.history.pop();
15667
+ }
15668
+ if (this.invalidToolCallRetrySteps >= _BluMaAgent.MAX_INVALID_TOOL_CALL_RETRIES) {
15669
+ this.eventBus.emit("backend_message", {
15670
+ type: "error",
15671
+ message: "The model kept returning invalid tool calls. Closing the turn to avoid a retry loop."
15672
+ });
15673
+ this.eventBus.emit("backend_message", {
15674
+ type: "log",
15675
+ message: "Invalid tool call retry limit reached",
15676
+ payload: String(this.invalidToolCallRetrySteps)
15677
+ });
15678
+ await this.notifyFactorTurnEndIfNeeded("invalid_tool_calls_exhausted");
15679
+ this.eventBus.emit("backend_message", { type: "done", status: "failed" });
15680
+ this.invalidToolCallRetrySteps = 0;
15681
+ return;
15682
+ }
15683
+ this.history.push({
15684
+ role: "system",
15685
+ content: "Previous assistant tool_calls were invalid. Retry with valid JSON arguments only, or answer without tools."
15686
+ });
15687
+ this.persistSession();
15688
+ await this._continueConversation();
15689
+ }
15604
15690
  async initialize() {
15605
15691
  await this.mcpClient.nativeToolInvoker.initialize();
15606
15692
  await this.mcpClient.initialize();
@@ -15709,6 +15795,7 @@ var BluMaAgent = class {
15709
15795
  const userContent = buildUserMessageContent(inputText, process.cwd());
15710
15796
  this.history.push({ role: "user", content: userContent });
15711
15797
  this.emptyAssistantReplySteps = 0;
15798
+ this.invalidToolCallRetrySteps = 0;
15712
15799
  this.eventBus.emit(
15713
15800
  "backend_message",
15714
15801
  buildTurnStartBackendMessage({
@@ -16145,6 +16232,11 @@ ${editData.error.display}`;
16145
16232
  message: `Received follow-up from coordinator (priority: ${mailboxUpdate.followUp.priority})`
16146
16233
  });
16147
16234
  }
16235
+ const sanitized = sanitizeConversationForProvider(this.history);
16236
+ if (sanitized.issues.length > 0) {
16237
+ this.history = sanitized.messages;
16238
+ this.persistSession();
16239
+ }
16148
16240
  const { messages: contextWindow, newAnchor, newCompressedTurnSliceCount } = await createApiContextWindow(
16149
16241
  this.history,
16150
16242
  this.sessionAnchor,
@@ -16172,7 +16264,7 @@ ${editData.error.display}`;
16172
16264
  this.eventBus.emit("backend_message", {
16173
16265
  type: "log",
16174
16266
  message: "LLM request failed",
16175
- payload: uiError.details
16267
+ payload: uiError.rawMessage
16176
16268
  });
16177
16269
  await this.notifyFactorTurnEndIfNeeded("llm_error");
16178
16270
  this.eventBus.emit("backend_message", { type: "done", status: "failed" });
@@ -16285,15 +16377,12 @@ ${editData.error.display}`;
16285
16377
  this.history.push(normalizedMessage);
16286
16378
  if (normalizedMessage.tool_calls && normalizedMessage.tool_calls.length > 0) {
16287
16379
  this.emptyAssistantReplySteps = 0;
16380
+ this.invalidToolCallRetrySteps = 0;
16288
16381
  const validToolCalls = normalizedMessage.tool_calls.filter(
16289
16382
  (call) => ToolCallNormalizer.isValidToolCall(call)
16290
16383
  );
16291
16384
  if (validToolCalls.length === 0) {
16292
- this.eventBus.emit("backend_message", {
16293
- type: "error",
16294
- message: "Model returned invalid tool calls. Retrying..."
16295
- });
16296
- await this._continueConversation();
16385
+ await this.handleInvalidToolCallRetry(normalizedMessage);
16297
16386
  return;
16298
16387
  }
16299
16388
  const needsConfirmation = validToolCalls.some(
@@ -16325,9 +16414,6 @@ ${editData.error.display}`;
16325
16414
  } else if (trimmedText) {
16326
16415
  this.emptyAssistantReplySteps = 0;
16327
16416
  this.eventBus.emit("backend_message", { type: "assistant_message", content: accumulatedContent });
16328
- this.eventBus.emit("info", {
16329
- message: "Assistant returned plain text without tool_calls. Closing the turn to avoid protocol drift."
16330
- });
16331
16417
  await this.notifyFactorTurnEndIfNeeded("assistant_text_without_tool_call");
16332
16418
  this.emitTurnCompleted();
16333
16419
  return;
@@ -16360,15 +16446,12 @@ ${editData.error.display}`;
16360
16446
  this.history.push(message2);
16361
16447
  if (message2.tool_calls && message2.tool_calls.length > 0) {
16362
16448
  this.emptyAssistantReplySteps = 0;
16449
+ this.invalidToolCallRetrySteps = 0;
16363
16450
  const validToolCalls = message2.tool_calls.filter(
16364
16451
  (call) => ToolCallNormalizer.isValidToolCall(call)
16365
16452
  );
16366
16453
  if (validToolCalls.length === 0) {
16367
- this.eventBus.emit("backend_message", {
16368
- type: "error",
16369
- message: "Model returned invalid tool calls. Retrying..."
16370
- });
16371
- await this._continueConversation();
16454
+ await this.handleInvalidToolCallRetry(message2);
16372
16455
  return;
16373
16456
  }
16374
16457
  const needsConfirmation = validToolCalls.some(
@@ -16399,10 +16482,8 @@ ${editData.error.display}`;
16399
16482
  }
16400
16483
  } else if (typeof message2.content === "string" && message2.content.trim()) {
16401
16484
  this.emptyAssistantReplySteps = 0;
16485
+ this.invalidToolCallRetrySteps = 0;
16402
16486
  this.eventBus.emit("backend_message", { type: "assistant_message", content: message2.content });
16403
- this.eventBus.emit("info", {
16404
- message: "Assistant returned plain text without tool_calls. Closing the turn to avoid protocol drift."
16405
- });
16406
16487
  await this.notifyFactorTurnEndIfNeeded("assistant_text_without_tool_call");
16407
16488
  this.emitTurnCompleted();
16408
16489
  return;
@@ -18780,23 +18861,12 @@ var ToolResultDisplayComponent = ({
18780
18861
  if (toolName.includes("ask_user_question")) {
18781
18862
  const success = parsed?.success === true;
18782
18863
  const selectedLabel = typeof parsed?.selected_label === "string" ? parsed.selected_label : "";
18783
- const selectedIndex = typeof parsed?.selected_index === "number" ? parsed.selected_index : null;
18784
- const questionIndex = typeof parsed?.question_index === "number" ? parsed.question_index : 0;
18785
- const qs = Array.isArray(args?.questions) ? args.questions : [];
18786
- const q = qs[questionIndex];
18787
- const questionText = typeof q?.question === "string" ? q.question : "";
18788
18864
  if (success && selectedLabel) {
18789
- return /* @__PURE__ */ jsx13(ResultGutter, { children: /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
18790
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
18791
- /* @__PURE__ */ jsx13(Text13, { bold: true, children: "Response" }),
18792
- " \xB7 ",
18793
- selectedLabel
18794
- ] }),
18795
- questionText ? /* @__PURE__ */ jsxs13(Text13, { dimColor: true, wrap: "wrap", children: [
18796
- truncate3(questionText, 140),
18797
- selectedIndex !== null ? ` \xB7 option ${selectedIndex + 1}` : ""
18798
- ] }) : null
18799
- ] }) });
18865
+ return /* @__PURE__ */ jsx13(ResultGutter, { children: /* @__PURE__ */ jsx13(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
18866
+ /* @__PURE__ */ jsx13(Text13, { bold: true, children: "Response" }),
18867
+ " \xB7 ",
18868
+ selectedLabel
18869
+ ] }) }) });
18800
18870
  }
18801
18871
  if (parsed?.cancelled === true) {
18802
18872
  return /* @__PURE__ */ jsx13(ResultGutter, { children: /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
@@ -20963,183 +21033,137 @@ Report the release version, tag, changelog summary, and verification results whe
20963
21033
  );
20964
21034
  }
20965
21035
  if (cmd === "review") {
20966
- const target = args.join(" ") || "";
21036
+ const normalizedArgs = args.map((a) => a.toLowerCase());
21037
+ const hasMasonPrefix = normalizedArgs[0] === "mason" || normalizedArgs[0] === "with" && normalizedArgs[1] === "mason";
21038
+ const reviewMode = hasMasonPrefix ? "mason" : "direct";
21039
+ const targetArgs = hasMasonPrefix ? normalizedArgs[0] === "mason" ? args.slice(1) : args.slice(2) : args;
21040
+ const target = targetArgs.join(" ") || "";
20967
21041
  const isPR = target && /^\d+$/.test(target);
20968
21042
  (async () => {
20969
21043
  try {
20970
21044
  const reviewTarget = isPR ? `PR #${target}` : target === "local" || target === "local changes" ? "current local changes (git diff HEAD)" : target ? `the file/module: ${target}` : "current local changes (git diff HEAD)";
20971
- await agentRef.current?.processTurn({
20972
- content: `## REVIEW COORDINATOR MODE \u2014 Lead a Team of Senior QA Reviewers
21045
+ const reviewPrompt = reviewMode === "mason" ? `## REVIEW COORDINATOR MODE \u2014 Mason Specialists
20973
21046
 
20974
- You are now the **Review Coordinator** \u2014 a Principal Engineer leading a team of senior, picky QA reviewers. Your job is to orchestrate a **thorough, line-by-line code review** where NOTHING slips through.
21047
+ You are now the **Review Coordinator** for a slower, deeper pass with Mason senior specialists.
20975
21048
 
20976
- **NEVER be afraid to coordinate.** Spawning specialized reviewers is how you catch bugs that a single reviewer would miss.
21049
+ This mode is intentionally heavier:
21050
+ - You may coordinate specialized reviewers in parallel
21051
+ - Each reviewer should focus on one area of risk
21052
+ - This can take longer, but it should surface deeper issues
20977
21053
 
20978
21054
  **Review Target:** ${reviewTarget}
20979
21055
 
20980
21056
  ### COORDINATOR REVIEW WORKFLOW
20981
21057
 
20982
- #### Step 1: Triage (you do this \u2014 quick, ~30s)
21058
+ #### Step 1: Triage
20983
21059
  1. Gather the diff/changes:
20984
21060
  ${isPR ? `- Run \`gh pr view ${target}\` for PR details` : ""}
20985
21061
  ${isPR ? `- Run \`gh pr diff ${target}\` for the full diff` : ""}
20986
21062
  ${!isPR && target !== "local" && target !== "local changes" ? `- Read the file: ${target}` : ""}
20987
21063
  ${target === "local" || target === "local changes" ? `- Run \`git diff HEAD\` for unstaged changes` : ""}
20988
21064
  ${target === "local" || target === "local changes" ? `- Run \`git diff --cached HEAD\` for staged changes` : ""}
20989
- 2. Understand the SCOPE: how many files changed, what areas are affected
21065
+ 2. Identify the risk surface and decide which specialist areas are worth parallelizing
20990
21066
 
20991
- #### Step 2: Spawn 3 Parallel Review Workers
20992
- Launch exactly **3 workers in parallel** \u2014 one for each core area.
21067
+ #### Step 2: Parallel Specialists
21068
+ If the scope justifies it, spawn specialized reviewers in parallel:
21069
+ - Security
21070
+ - Logic & Correctness
21071
+ - Code Quality
20993
21072
 
20994
- **IMPORTANT:** Each worker MUST read EVERY changed file line by line. Do NOT report until you have examined all files. List each file you reviewed in your report.
21073
+ If the scope is small, do not force parallelism. Use judgment.
20995
21074
 
20996
- **Worker 1 \u2014 Security Reviewer:**
20997
- \`\`\`
20998
- spawn_agent({
20999
- task: "SECURITY REVIEW: Thoroughly review ${reviewTarget} for security vulnerabilities.
21000
-
21001
- You are a Senior Security Engineer. Read EVERY changed file line by line. Do NOT report until you have examined all files.
21002
-
21003
- Look for:
21004
- - Injection vulnerabilities (SQL, XSS, command injection, template injection)
21005
- - Authentication/authorization flaws (missing auth checks, privilege escalation)
21006
- - Sensitive data exposure (secrets in logs, PII leaks, hardcoded credentials)
21007
- - Insecure defaults (missing TLS, weak crypto, permissive CORS)
21008
- - Input validation gaps (missing sanitization, type confusion)
21009
- - Dependency vulnerabilities (outdated packages, known CVEs)
21010
- - Path traversal, SSRF, CSRF, race conditions
21011
-
21012
- For EACH issue found:
21013
- - Severity: CRITICAL / HIGH / MEDIUM / LOW
21014
- - File:line number
21015
- - Exact code snippet
21016
- - Why it's vulnerable
21017
- - How to exploit it (brief)
21018
- - Recommended fix
21019
-
21020
- Be PICKY. If something looks suspicious, flag it.
21021
-
21022
- Do NOT modify files. Report only.
21023
-
21024
- At the end of your report, list ALL files you reviewed.",
21025
- title: "Security Review",
21026
- agent_type: "reviewer"
21027
- })
21028
- \`\`\`
21075
+ #### Step 3: Synthesize
21076
+ Wait for all reviewers that you spawned, then synthesize the findings into a single review report.
21029
21077
 
21030
- **Worker 2 \u2014 Logic & Correctness:**
21031
- \`\`\`
21032
- spawn_agent({
21033
- task: "LOGIC REVIEW: Thoroughly review ${reviewTarget} for bugs and logic errors.
21034
-
21035
- You are a Senior QA Engineer who finds bugs for a living. Read EVERY changed file line by line. Do NOT report until you have examined all files.
21036
-
21037
- Look for:
21038
- - Logic errors (wrong conditions, off-by-one, inverted boolean, wrong operator)
21039
- - Null/undefined handling (missing null checks, unsafe property access)
21040
- - State management issues (stale state, missing initialization, race conditions)
21041
- - Async bugs (unawaited promises, missing error handling, promise rejections)
21042
- - Edge cases (empty arrays, zero values, negative numbers, boundary conditions)
21043
- - Wrong assumptions (code assumes X but Y can happen)
21044
- - Dead code (unreachable branches, unused variables, commented-out logic)
21045
- - Error handling gaps (swallowed errors, missing catch blocks, generic catches)
21046
-
21047
- For EACH issue found:
21048
- - Severity: BLOCKER / MAJOR / MINOR
21049
- - File:line number
21050
- - What the code does vs what it SHOULD do
21051
- - How to trigger the bug
21052
- - Recommended fix
21053
-
21054
- Be RELENTLESS. Question every assumption.
21055
-
21056
- Do NOT modify files. Report only.
21057
-
21058
- At the end of your report, list ALL files you reviewed.",
21059
- title: "Logic & Correctness Review",
21060
- agent_type: "reviewer"
21061
- })
21062
- \`\`\`
21078
+ #### Step 4: Produce the Review Report
21079
+ Compile a comprehensive review report:
21063
21080
 
21064
- **Worker 3 \u2014 Code Quality:**
21065
- \`\`\`
21066
- spawn_agent({
21067
- task: "CODE QUALITY REVIEW: Thoroughly review ${reviewTarget} for code quality and convention violations.
21081
+ **REVIEW REPORT for ${reviewTarget}**
21068
21082
 
21069
- You are a Staff Engineer obsessed with clean code. Read EVERY changed file line by line. Do NOT report until you have examined all files.
21083
+ \u{1F534} CRITICAL / BLOCKER (must fix before merge):
21084
+ - [List critical findings]
21070
21085
 
21071
- Look for:
21072
- - Naming issues (misleading names, abbreviations, inconsistent casing)
21073
- - Function length and complexity (too long, too many responsibilities, deep nesting)
21074
- - DRY violations (duplicated logic that should be extracted)
21075
- - SOLID violations (tight coupling, god classes, leaking abstractions)
21076
- - Style inconsistencies (formatting, import order, naming conventions)
21077
- - Missing or wrong comments (no docs for complex logic, outdated comments)
21078
- - Type safety issues (any usage, missing type annotations, wrong types)
21079
- - Error message quality (unhelpful messages, missing context)
21080
- - API design (inconsistent interfaces, breaking changes, missing deprecation)
21086
+ \u{1F7E1} HIGH / MAJOR (should fix):
21087
+ - [List high findings]
21081
21088
 
21082
- For EACH issue found:
21083
- - File:line number
21084
- - What's wrong
21085
- - Suggested improvement with before/after code
21089
+ \u{1F7E2} MEDIUM / MINOR (nice to fix):
21090
+ - [List medium findings]
21086
21091
 
21087
- Be PICKY about readability. Code is read 10x more than written.
21092
+ \u2139\uFE0F OBSERVATIONS (no action needed):
21093
+ - [List observations]
21088
21094
 
21089
- Do NOT modify files. Report only.
21095
+ \u2705 POSITIVE FINDINGS:
21096
+ - [List strong points]
21090
21097
 
21091
- At the end of your report, list ALL files you reviewed.",
21092
- title: "Code Quality Review",
21093
- agent_type: "reviewer"
21094
- })
21095
- \`\`\`
21098
+ **Review Summary:**
21099
+ - Total issues found: X critical, Y high, Z medium
21100
+ - Reviewers used: [list workers or "direct review"]
21101
+ - Recommendation: APPROVE / APPROVE WITH COMMENTS / REQUEST CHANGES
21102
+ - Confidence level: HIGH / MEDIUM / LOW
21096
21103
 
21097
- #### Step 3: Wait for Workers + Synthesize
21098
- Wait for ALL 3 workers to complete. Use wait_agent with a large timeout (600000ms).
21104
+ ### COORDINATOR RULES
21105
+ - Be selective: do not spawn workers unless the scope justifies it
21106
+ - If workers fail, finish the review yourself
21107
+ - Never rubber-stamp
21108
+ - Never fabricate results
21099
21109
 
21100
- **If workers fail or sessions disappear:**
21101
- - This can happen with fast-completing workers
21102
- - Simply perform the review yourself by reading the changed files
21103
- - Report: "Workers completed/unavailable \u2014 performing review directly"
21104
- - Do NOT waste time retrying \u2014 just do the review
21110
+ Start coordinating now.` : `## REVIEW MODE \u2014 Direct Senior Review
21105
21111
 
21106
- **NEVER write** "the review looks good" \u2014 that's lazy.
21107
- **ALWAYS synthesize**: Group findings by severity, cross-reference between reviewers, identify patterns.
21112
+ You are a senior engineer performing a direct code review. Do the review yourself using the available tools and your own judgment.
21108
21113
 
21109
- #### Step 4: Produce the Review Report
21110
- Compile a comprehensive review report:
21114
+ **Do not spawn parallel reviewers by default.** Only use extra agents if the scope is genuinely large and you need them.
21115
+
21116
+ **Review Target:** ${reviewTarget}
21117
+
21118
+ ### REVIEW WORKFLOW
21119
+
21120
+ #### Step 1: Triage
21121
+ 1. Gather the diff/changes:
21122
+ ${isPR ? `- Run \`gh pr view ${target}\` for PR details` : ""}
21123
+ ${isPR ? `- Run \`gh pr diff ${target}\` for the full diff` : ""}
21124
+ ${!isPR && target !== "local" && target !== "local changes" ? `- Read the file: ${target}` : ""}
21125
+ ${target === "local" || target === "local changes" ? `- Run \`git diff HEAD\` for unstaged changes` : ""}
21126
+ ${target === "local" || target === "local changes" ? `- Run \`git diff --cached HEAD\` for staged changes` : ""}
21127
+ 2. Understand the scope and the main risk areas
21128
+
21129
+ #### Step 2: Review Directly
21130
+ Read the changed files carefully yourself. Focus on:
21131
+ - Correctness and regressions
21132
+ - Security and data handling
21133
+ - Tests and edge cases
21134
+ - Clarity and maintainability
21135
+
21136
+ If the diff is large, you may use helpers, but keep the review centered on your own synthesis.
21137
+
21138
+ #### Step 3: Produce the Review Report
21139
+ Compile a concise but rigorous review report:
21111
21140
 
21112
21141
  **REVIEW REPORT for ${reviewTarget}**
21113
21142
 
21114
21143
  \u{1F534} CRITICAL / BLOCKER (must fix before merge):
21115
- - [List all critical findings]
21144
+ - [List critical findings]
21116
21145
 
21117
21146
  \u{1F7E1} HIGH / MAJOR (should fix):
21118
- - [List all high findings]
21147
+ - [List high findings]
21119
21148
 
21120
21149
  \u{1F7E2} MEDIUM / MINOR (nice to fix):
21121
- - [List all medium findings]
21150
+ - [List medium findings]
21122
21151
 
21123
21152
  \u2139\uFE0F OBSERVATIONS (no action needed):
21124
- - [List observations, style notes]
21153
+ - [List observations]
21125
21154
 
21126
- \u2705 POSITIVE FINDINGS (what's good):
21127
- - [List well-written code, good patterns]
21155
+ \u2705 POSITIVE FINDINGS:
21156
+ - [List strong points]
21128
21157
 
21129
21158
  **Review Summary:**
21130
21159
  - Total issues found: X critical, Y high, Z medium
21131
- - Reviewers used: [list workers or "direct review"]
21160
+ - Reviewers used: direct review
21132
21161
  - Recommendation: APPROVE / APPROVE WITH COMMENTS / REQUEST CHANGES
21133
21162
  - Confidence level: HIGH / MEDIUM / LOW
21134
21163
 
21135
- ### COORDINATOR RULES
21136
- - **You are the brain, reviewers are the eyes** \u2014 synthesize, don't just copy-paste
21137
- - **Spawn 3 workers in parallel** \u2014 Security, Logic, Code Quality
21138
- - **If workers fail, do the review yourself** \u2014 no drama, just deliver
21139
- - **NEVER rubber-stamp** \u2014 your job is to find issues
21140
- - **NEVER fabricate results** \u2014 report truth
21141
-
21142
- Start coordinating now. Triage the changes, then spawn your 3 reviewers.`
21164
+ Start the review now.`;
21165
+ await agentRef.current?.processTurn({
21166
+ content: reviewPrompt
21143
21167
  });
21144
21168
  } catch (e) {
21145
21169
  setHistory((prev) => prev.concat({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.1.75",
3
+ "version": "0.1.76",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",