@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.
- package/dist/main.js +199 -175
- 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 (
|
|
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:
|
|
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
|
|
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
|
|
4774
|
-
return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */
|
|
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 <
|
|
14780
|
-
systemMessages.push(
|
|
14826
|
+
while (historyStartIndex < safeHistory.length && safeHistory[historyStartIndex].role === "system") {
|
|
14827
|
+
systemMessages.push(safeHistory[historyStartIndex]);
|
|
14781
14828
|
historyStartIndex++;
|
|
14782
14829
|
}
|
|
14783
|
-
const conversationHistory =
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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__ */
|
|
18790
|
-
/* @__PURE__ */
|
|
18791
|
-
|
|
18792
|
-
|
|
18793
|
-
|
|
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
|
|
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
|
-
|
|
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**
|
|
21047
|
+
You are now the **Review Coordinator** for a slower, deeper pass with Mason senior specialists.
|
|
20975
21048
|
|
|
20976
|
-
|
|
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
|
|
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.
|
|
21065
|
+
2. Identify the risk surface and decide which specialist areas are worth parallelizing
|
|
20990
21066
|
|
|
20991
|
-
#### Step 2:
|
|
20992
|
-
|
|
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
|
-
|
|
21073
|
+
If the scope is small, do not force parallelism. Use judgment.
|
|
20995
21074
|
|
|
20996
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
21083
|
+
\u{1F534} CRITICAL / BLOCKER (must fix before merge):
|
|
21084
|
+
- [List critical findings]
|
|
21070
21085
|
|
|
21071
|
-
|
|
21072
|
-
-
|
|
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
|
-
|
|
21083
|
-
-
|
|
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
|
-
|
|
21092
|
+
\u2139\uFE0F OBSERVATIONS (no action needed):
|
|
21093
|
+
- [List observations]
|
|
21088
21094
|
|
|
21089
|
-
|
|
21095
|
+
\u2705 POSITIVE FINDINGS:
|
|
21096
|
+
- [List strong points]
|
|
21090
21097
|
|
|
21091
|
-
|
|
21092
|
-
|
|
21093
|
-
|
|
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
|
-
|
|
21098
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21110
|
-
|
|
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
|
|
21144
|
+
- [List critical findings]
|
|
21116
21145
|
|
|
21117
21146
|
\u{1F7E1} HIGH / MAJOR (should fix):
|
|
21118
|
-
- [List
|
|
21147
|
+
- [List high findings]
|
|
21119
21148
|
|
|
21120
21149
|
\u{1F7E2} MEDIUM / MINOR (nice to fix):
|
|
21121
|
-
- [List
|
|
21150
|
+
- [List medium findings]
|
|
21122
21151
|
|
|
21123
21152
|
\u2139\uFE0F OBSERVATIONS (no action needed):
|
|
21124
|
-
- [List observations
|
|
21153
|
+
- [List observations]
|
|
21125
21154
|
|
|
21126
|
-
\u2705 POSITIVE FINDINGS
|
|
21127
|
-
- [List
|
|
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:
|
|
21160
|
+
- Reviewers used: direct review
|
|
21132
21161
|
- Recommendation: APPROVE / APPROVE WITH COMMENTS / REQUEST CHANGES
|
|
21133
21162
|
- Confidence level: HIGH / MEDIUM / LOW
|
|
21134
21163
|
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
|
|
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({
|