@planningo/duul 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +92 -6
- package/README.md +94 -7
- package/build/prompts/code-review-system.js +11 -1
- package/build/prompts/plan-review-system.js +11 -1
- package/build/schemas/code-review.d.ts +48 -11
- package/build/schemas/code-review.js +22 -3
- package/build/schemas/common.d.ts +26 -3
- package/build/schemas/common.js +16 -2
- package/build/schemas/execution-partition.d.ts +97 -63
- package/build/schemas/execution-partition.js +13 -3
- package/build/schemas/plan-review.d.ts +42 -8
- package/build/schemas/plan-review.js +15 -1
- package/build/services/filesystem-tools.d.ts +19 -1
- package/build/services/filesystem-tools.js +50 -13
- package/build/services/filesystem.d.ts +20 -0
- package/build/services/filesystem.js +51 -17
- package/build/services/providers/anthropic.js +5 -3
- package/build/services/providers/codex-auth.d.ts +51 -0
- package/build/services/providers/codex-auth.js +178 -0
- package/build/services/providers/google.js +4 -2
- package/build/services/providers/openai.d.ts +33 -0
- package/build/services/providers/openai.js +173 -30
- package/build/services/providers/types.d.ts +7 -1
- package/build/services/review-limits.d.ts +8 -0
- package/build/services/review-limits.js +21 -0
- package/build/services/reviewer.d.ts +34 -2
- package/build/services/reviewer.js +95 -21
- package/build/tools/code-review.js +50 -7
- package/build/tools/execution-partition.js +55 -10
- package/build/tools/plan-review.js +38 -6
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CodeReviewInputSchema, CodeReviewOutputSchema, CodeReviewMcpOutputSchema, } from '../schemas/code-review.js';
|
|
2
2
|
import { getCodeReviewSystemPrompt, formatCodeReviewUserMessage } from '../prompts/code-review-system.js';
|
|
3
3
|
import { callReview } from '../services/reviewer.js';
|
|
4
|
-
import { resolveWorkspaceScope, getGitDiff } from '../services/filesystem.js';
|
|
5
|
-
import { computeIterationMeta, isIterationLimitExceeded } from '../services/review-limits.js';
|
|
4
|
+
import { resolveWorkspaceScope, getGitDiff, resolveInlineOrFile } from '../services/filesystem.js';
|
|
5
|
+
import { computeIterationMeta, isIterationLimitExceeded, computeCostWarning } from '../services/review-limits.js';
|
|
6
6
|
import { logUsage } from '../services/usage-logger.js';
|
|
7
7
|
import { applyGates } from '../services/review-gates.js';
|
|
8
8
|
const MAX_INLINE_DIFF_CHARS = 50_000;
|
|
@@ -10,14 +10,55 @@ const ZERO_USAGE = { input_tokens: 0, output_tokens: 0, total_tokens: 0, api_cal
|
|
|
10
10
|
export function registerCodeReviewTool(server) {
|
|
11
11
|
server.registerTool('request_code_review', {
|
|
12
12
|
title: 'DUUL Code Review (Strict QA)',
|
|
13
|
-
description: 'DUUL Phase 2: Submit code for
|
|
14
|
-
'
|
|
15
|
-
'
|
|
13
|
+
description: 'DUUL Phase 2: Submit code for strict QA review. ' +
|
|
14
|
+
'Provide the code EITHER inline via `code` OR (preferred for large content) via `code_file`, and the ' +
|
|
15
|
+
'Phase 1 plan EITHER inline via `approved_plan` OR via `approved_plan_file` (relative paths, e.g. ".duul/code.md" / ".duul/plan.md") plus `workspace_root`. ' +
|
|
16
|
+
'Exactly one of code/code_file and one of approved_plan/approved_plan_file are required. ' +
|
|
17
|
+
'Optional: workspace_root, file_path, changed_files, artifact_refs, previous_review_id, iteration_count. ' +
|
|
18
|
+
'Returns blocking issues, vulnerabilities, optimized snippet, or APPROVE verdict.',
|
|
16
19
|
inputSchema: CodeReviewInputSchema,
|
|
17
20
|
outputSchema: CodeReviewMcpOutputSchema,
|
|
18
21
|
}, async (input) => {
|
|
19
22
|
try {
|
|
20
23
|
const args = input;
|
|
24
|
+
const scope = resolveWorkspaceScope(args);
|
|
25
|
+
// Resolve code and approved_plan from inline values or *_file escape hatches.
|
|
26
|
+
let codeText;
|
|
27
|
+
let approvedPlanText;
|
|
28
|
+
try {
|
|
29
|
+
codeText = await resolveInlineOrFile({ inline: args.code, file: args.code_file, scope, label: 'code' });
|
|
30
|
+
approvedPlanText = await resolveInlineOrFile({
|
|
31
|
+
inline: args.approved_plan,
|
|
32
|
+
file: args.approved_plan_file,
|
|
33
|
+
scope,
|
|
34
|
+
label: 'approved_plan',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (readErr) {
|
|
38
|
+
const reason = readErr instanceof Error ? readErr.message : String(readErr);
|
|
39
|
+
console.error(`[duul] code-review file read failed: ${reason}`);
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: 'text', text: `ERROR: could not read a *_file argument. ${reason}` }],
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (typeof codeText !== 'string' ||
|
|
46
|
+
codeText.trim().length < 5 ||
|
|
47
|
+
typeof approvedPlanText !== 'string' ||
|
|
48
|
+
approvedPlanText.trim().length < 20) {
|
|
49
|
+
const message = 'ERROR: `code` and `approved_plan` are both required. ' +
|
|
50
|
+
'`code` must contain the actual code being reviewed (min 5 chars). ' +
|
|
51
|
+
'`approved_plan` must contain the full plan text approved in Phase 1 (min 20 chars). ' +
|
|
52
|
+
'You called request_code_review without usable content. ' +
|
|
53
|
+
'Inline them — { "code": "<your code>", "approved_plan": "<plan text>", ... } — or, for large content, ' +
|
|
54
|
+
'write each to a file and pass { "code_file": ".duul/code.md", "approved_plan_file": ".duul/plan.md", "workspace_root": "<absolute path>" }. ' +
|
|
55
|
+
'Do NOT call this tool again with an empty input.';
|
|
56
|
+
console.error(`[duul] code-review rejected: missing/empty code or approved_plan content`);
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: 'text', text: message }],
|
|
59
|
+
isError: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
21
62
|
const iterMeta = computeIterationMeta('code', args.iteration_count, args.max_review_iterations);
|
|
22
63
|
// Short-circuit if iteration limit exceeded
|
|
23
64
|
if (isIterationLimitExceeded('code', args.iteration_count, args.max_review_iterations)) {
|
|
@@ -44,6 +85,7 @@ export function registerCodeReviewTool(server) {
|
|
|
44
85
|
gates_tripped: null,
|
|
45
86
|
review_id: '',
|
|
46
87
|
...iterMeta,
|
|
88
|
+
cost_warning: null,
|
|
47
89
|
token_usage: ZERO_USAGE,
|
|
48
90
|
};
|
|
49
91
|
return {
|
|
@@ -51,7 +93,6 @@ export function registerCodeReviewTool(server) {
|
|
|
51
93
|
structuredContent: limitResult,
|
|
52
94
|
};
|
|
53
95
|
}
|
|
54
|
-
const scope = resolveWorkspaceScope(args);
|
|
55
96
|
// Auto-generate git diff if not provided
|
|
56
97
|
let gitDiff = args.git_diff;
|
|
57
98
|
if (!gitDiff && scope?.root && args.changed_files?.length) {
|
|
@@ -68,7 +109,7 @@ export function registerCodeReviewTool(server) {
|
|
|
68
109
|
}
|
|
69
110
|
}
|
|
70
111
|
const systemPrompt = getCodeReviewSystemPrompt();
|
|
71
|
-
const userMessage = formatCodeReviewUserMessage(
|
|
112
|
+
const userMessage = formatCodeReviewUserMessage(codeText, approvedPlanText, args.file_path, args.dependencies, args.relevant_code, args.notes_to_reviewer, {
|
|
72
113
|
workingDirectories: args.working_directories,
|
|
73
114
|
linkedRoots: args.linked_roots,
|
|
74
115
|
changedFiles: args.changed_files,
|
|
@@ -89,6 +130,7 @@ export function registerCodeReviewTool(server) {
|
|
|
89
130
|
outputSchema: CodeReviewOutputSchema,
|
|
90
131
|
workspaceScope: scope,
|
|
91
132
|
previousReviewId: args.previous_review_id,
|
|
133
|
+
toolName: 'code',
|
|
92
134
|
reviewerConfig: args.reviewer_config,
|
|
93
135
|
createFallback: (reason, usedTools) => ({
|
|
94
136
|
verdict: 'REVISE',
|
|
@@ -147,6 +189,7 @@ export function registerCodeReviewTool(server) {
|
|
|
147
189
|
gates_tripped: gates.tripped.length > 0 ? gates.tripped : null,
|
|
148
190
|
review_id: reviewId,
|
|
149
191
|
...iterMeta,
|
|
192
|
+
cost_warning: computeCostWarning(iterMeta, usage.estimated_cost_usd),
|
|
150
193
|
token_usage: usage,
|
|
151
194
|
};
|
|
152
195
|
logUsage('code_review', result.token_usage, {
|
|
@@ -1,21 +1,59 @@
|
|
|
1
1
|
import { ExecutionPartitionInputSchema, ExecutionPartitionOutputSchema, ExecutionPartitionMcpOutputSchema, } from '../schemas/execution-partition.js';
|
|
2
2
|
import { getExecutionPartitionSystemPrompt, formatExecutionPartitionUserMessage } from '../prompts/execution-partition-system.js';
|
|
3
3
|
import { callReview } from '../services/reviewer.js';
|
|
4
|
-
import { resolveWorkspaceScope } from '../services/filesystem.js';
|
|
5
|
-
import { computeIterationMeta, isIterationLimitExceeded } from '../services/review-limits.js';
|
|
4
|
+
import { resolveWorkspaceScope, resolveInlineOrFile } from '../services/filesystem.js';
|
|
5
|
+
import { computeIterationMeta, isIterationLimitExceeded, computeCostWarning } from '../services/review-limits.js';
|
|
6
6
|
import { logUsage } from '../services/usage-logger.js';
|
|
7
7
|
const ZERO_USAGE = { input_tokens: 0, output_tokens: 0, total_tokens: 0, api_calls: 0, provider: 'none', model: 'none', estimated_cost_usd: null };
|
|
8
8
|
export function registerExecutionPartitionTool(server) {
|
|
9
9
|
server.registerTool('request_execution_partition', {
|
|
10
10
|
title: 'DUUL Execution Partition (Project Manager)',
|
|
11
|
-
description: 'DUUL optional: Partition an approved plan into executable subtasks
|
|
12
|
-
'
|
|
13
|
-
'
|
|
11
|
+
description: 'DUUL optional: Partition an approved plan into executable subtasks. ' +
|
|
12
|
+
'Provide the plan EITHER inline via `approved_plan` OR (preferred for large plans) via `approved_plan_file` ' +
|
|
13
|
+
'(relative path, e.g. ".duul/plan.md"). `workspace_root` (absolute path) is required. ' +
|
|
14
|
+
'Exactly one of approved_plan/approved_plan_file is required. ' +
|
|
15
|
+
'Optional: working_directories, changed_files, entrypoints, artifact_refs, max_parallelism, iteration_count. ' +
|
|
16
|
+
'Returns dependency graph, spawn strategy, and handoff contracts.',
|
|
14
17
|
inputSchema: ExecutionPartitionInputSchema,
|
|
15
18
|
outputSchema: ExecutionPartitionMcpOutputSchema,
|
|
16
19
|
}, async (input) => {
|
|
17
20
|
try {
|
|
18
21
|
const args = input;
|
|
22
|
+
const scope = resolveWorkspaceScope(args);
|
|
23
|
+
// Resolve approved_plan from inline value or approved_plan_file escape hatch.
|
|
24
|
+
let approvedPlanText;
|
|
25
|
+
try {
|
|
26
|
+
approvedPlanText = await resolveInlineOrFile({
|
|
27
|
+
inline: args.approved_plan,
|
|
28
|
+
file: args.approved_plan_file,
|
|
29
|
+
scope,
|
|
30
|
+
label: 'approved_plan',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (readErr) {
|
|
34
|
+
const reason = readErr instanceof Error ? readErr.message : String(readErr);
|
|
35
|
+
console.error(`[duul] execution-partition approved_plan_file read failed: ${reason}`);
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: 'text', text: `ERROR: could not read approved_plan_file. ${reason}` }],
|
|
38
|
+
isError: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (typeof approvedPlanText !== 'string' ||
|
|
42
|
+
approvedPlanText.trim().length < 20 ||
|
|
43
|
+
typeof args.workspace_root !== 'string' ||
|
|
44
|
+
args.workspace_root.trim().length === 0) {
|
|
45
|
+
const message = 'ERROR: `approved_plan` and `workspace_root` are both required. ' +
|
|
46
|
+
'`approved_plan` must contain the full plan text (min 20 chars) — inline it, or pass approved_plan_file (e.g. ".duul/plan.md"). ' +
|
|
47
|
+
'`workspace_root` must be an absolute path. ' +
|
|
48
|
+
'You called request_execution_partition with missing or empty content. ' +
|
|
49
|
+
'Retry with: { "approved_plan": "<plan text>", "workspace_root": "<absolute path>" }. ' +
|
|
50
|
+
'Do NOT call this tool again with an empty input.';
|
|
51
|
+
console.error(`[duul] execution-partition rejected: missing/empty approved_plan or workspace_root`);
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: 'text', text: message }],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
19
57
|
const iterMeta = computeIterationMeta('partition', args.iteration_count, args.max_review_iterations);
|
|
20
58
|
// Short-circuit if iteration limit exceeded
|
|
21
59
|
if (isIterationLimitExceeded('partition', args.iteration_count, args.max_review_iterations)) {
|
|
@@ -31,7 +69,7 @@ export function registerExecutionPartitionTool(server) {
|
|
|
31
69
|
subtasks: [{
|
|
32
70
|
id: 'full-plan',
|
|
33
71
|
title: 'Execute full plan serially (limit reached)',
|
|
34
|
-
goal:
|
|
72
|
+
goal: approvedPlanText.slice(0, 200) + '...',
|
|
35
73
|
can_run_in_parallel: false,
|
|
36
74
|
depends_on: [],
|
|
37
75
|
workspace_name_hint: 'main',
|
|
@@ -58,6 +96,7 @@ export function registerExecutionPartitionTool(server) {
|
|
|
58
96
|
},
|
|
59
97
|
review_id: '',
|
|
60
98
|
...iterMeta,
|
|
99
|
+
cost_warning: null,
|
|
61
100
|
token_usage: ZERO_USAGE,
|
|
62
101
|
};
|
|
63
102
|
return {
|
|
@@ -65,9 +104,8 @@ export function registerExecutionPartitionTool(server) {
|
|
|
65
104
|
structuredContent: limitResult,
|
|
66
105
|
};
|
|
67
106
|
}
|
|
68
|
-
const scope = resolveWorkspaceScope(args);
|
|
69
107
|
const systemPrompt = getExecutionPartitionSystemPrompt();
|
|
70
|
-
const userMessage = formatExecutionPartitionUserMessage(
|
|
108
|
+
const userMessage = formatExecutionPartitionUserMessage(approvedPlanText, args.constraints, {
|
|
71
109
|
changedFiles: args.changed_files,
|
|
72
110
|
entrypoints: args.entrypoints,
|
|
73
111
|
artifactRefs: args.artifact_refs,
|
|
@@ -80,6 +118,7 @@ export function registerExecutionPartitionTool(server) {
|
|
|
80
118
|
outputSchema: ExecutionPartitionOutputSchema,
|
|
81
119
|
workspaceScope: scope,
|
|
82
120
|
previousReviewId: args.previous_review_id,
|
|
121
|
+
toolName: 'partition',
|
|
83
122
|
reviewerConfig: args.reviewer_config,
|
|
84
123
|
createFallback: (_reason, _usedTools) => ({
|
|
85
124
|
execution_mode: 'serial',
|
|
@@ -92,7 +131,7 @@ export function registerExecutionPartitionTool(server) {
|
|
|
92
131
|
subtasks: [{
|
|
93
132
|
id: 'full-plan',
|
|
94
133
|
title: 'Execute full plan serially',
|
|
95
|
-
goal:
|
|
134
|
+
goal: approvedPlanText.slice(0, 200) + '...',
|
|
96
135
|
can_run_in_parallel: false,
|
|
97
136
|
depends_on: [],
|
|
98
137
|
workspace_name_hint: 'main',
|
|
@@ -119,7 +158,13 @@ export function registerExecutionPartitionTool(server) {
|
|
|
119
158
|
},
|
|
120
159
|
}),
|
|
121
160
|
});
|
|
122
|
-
const result = {
|
|
161
|
+
const result = {
|
|
162
|
+
...parsed,
|
|
163
|
+
review_id: reviewId,
|
|
164
|
+
...iterMeta,
|
|
165
|
+
cost_warning: computeCostWarning(iterMeta, usage.estimated_cost_usd),
|
|
166
|
+
token_usage: usage,
|
|
167
|
+
};
|
|
123
168
|
logUsage('execution_partition', result.token_usage, {
|
|
124
169
|
review_id: reviewId,
|
|
125
170
|
iteration_count: iterMeta.iteration_count,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PlanReviewInputSchema, PlanReviewOutputSchema, PlanReviewMcpOutputSchema, } from '../schemas/plan-review.js';
|
|
2
2
|
import { getPlanReviewSystemPrompt, formatPlanReviewUserMessage } from '../prompts/plan-review-system.js';
|
|
3
3
|
import { callReview } from '../services/reviewer.js';
|
|
4
|
-
import { resolveWorkspaceScope, getGitDiff } from '../services/filesystem.js';
|
|
5
|
-
import { computeIterationMeta, isIterationLimitExceeded } from '../services/review-limits.js';
|
|
4
|
+
import { resolveWorkspaceScope, getGitDiff, resolveInlineOrFile } from '../services/filesystem.js';
|
|
5
|
+
import { computeIterationMeta, isIterationLimitExceeded, computeCostWarning } from '../services/review-limits.js';
|
|
6
6
|
import { logUsage } from '../services/usage-logger.js';
|
|
7
7
|
import { applyGates } from '../services/review-gates.js';
|
|
8
8
|
const MAX_INLINE_DIFF_CHARS = 50_000;
|
|
@@ -10,13 +10,43 @@ const ZERO_USAGE = { input_tokens: 0, output_tokens: 0, total_tokens: 0, api_cal
|
|
|
10
10
|
export function registerPlanReviewTool(server) {
|
|
11
11
|
server.registerTool('request_plan_review', {
|
|
12
12
|
title: 'DUUL Plan Review (Senior Architect)',
|
|
13
|
-
description: 'DUUL Phase 1: Submit
|
|
14
|
-
'
|
|
13
|
+
description: 'DUUL Phase 1: Submit an implementation plan for senior-architect review. ' +
|
|
14
|
+
'Provide the plan EITHER inline via `plan` OR (preferred for large plans) by writing it to a file ' +
|
|
15
|
+
'and passing `plan_file` (relative path, e.g. ".duul/plan.md") plus `workspace_root`. ' +
|
|
16
|
+
'Exactly one of plan/plan_file is required. ' +
|
|
17
|
+
'Optional: project_context, changed_files, artifact_refs, user_original_request, previous_review_id, iteration_count. ' +
|
|
18
|
+
'Returns blocking issues, edge cases, implementation checklist, or APPROVE verdict.',
|
|
15
19
|
inputSchema: PlanReviewInputSchema,
|
|
16
20
|
outputSchema: PlanReviewMcpOutputSchema,
|
|
17
21
|
}, async (input) => {
|
|
18
22
|
try {
|
|
19
23
|
const args = input;
|
|
24
|
+
const scope = resolveWorkspaceScope(args);
|
|
25
|
+
// Resolve the plan from inline `plan` or from `plan_file` (large-plan escape hatch).
|
|
26
|
+
let planText;
|
|
27
|
+
try {
|
|
28
|
+
planText = await resolveInlineOrFile({ inline: args.plan, file: args.plan_file, scope, label: 'plan' });
|
|
29
|
+
}
|
|
30
|
+
catch (readErr) {
|
|
31
|
+
const reason = readErr instanceof Error ? readErr.message : String(readErr);
|
|
32
|
+
console.error(`[duul] plan-review plan_file read failed: ${reason}`);
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: 'text', text: `ERROR: could not read plan_file. ${reason}` }],
|
|
35
|
+
isError: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (typeof planText !== 'string' || planText.trim().length < 20) {
|
|
39
|
+
const message = 'ERROR: a plan is required and must contain the full plan markdown (at least 20 chars). ' +
|
|
40
|
+
'You called request_plan_review without usable plan content. ' +
|
|
41
|
+
'Either inline it — { "plan": "<your complete plan text>", ... } — or, for a large plan, ' +
|
|
42
|
+
'write it to a file first and pass { "plan_file": ".duul/plan.md", "workspace_root": "<absolute path>" }. ' +
|
|
43
|
+
'Always include workspace_root and user_original_request. Do NOT call this tool again with an empty input.';
|
|
44
|
+
console.error(`[duul] plan-review rejected: missing/empty plan content`);
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: 'text', text: message }],
|
|
47
|
+
isError: true,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
20
50
|
const iterMeta = computeIterationMeta('plan', args.iteration_count, args.max_review_iterations);
|
|
21
51
|
// Short-circuit if iteration limit exceeded
|
|
22
52
|
if (isIterationLimitExceeded('plan', args.iteration_count, args.max_review_iterations)) {
|
|
@@ -46,6 +76,7 @@ export function registerPlanReviewTool(server) {
|
|
|
46
76
|
gates_tripped: null,
|
|
47
77
|
review_id: '',
|
|
48
78
|
...iterMeta,
|
|
79
|
+
cost_warning: null,
|
|
49
80
|
token_usage: ZERO_USAGE,
|
|
50
81
|
};
|
|
51
82
|
return {
|
|
@@ -53,7 +84,6 @@ export function registerPlanReviewTool(server) {
|
|
|
53
84
|
structuredContent: limitResult,
|
|
54
85
|
};
|
|
55
86
|
}
|
|
56
|
-
const scope = resolveWorkspaceScope(args);
|
|
57
87
|
// Auto-generate git diff if not provided
|
|
58
88
|
let gitDiff = args.git_diff;
|
|
59
89
|
if (!gitDiff && scope?.root && args.changed_files?.length) {
|
|
@@ -70,7 +100,7 @@ export function registerPlanReviewTool(server) {
|
|
|
70
100
|
}
|
|
71
101
|
}
|
|
72
102
|
const systemPrompt = getPlanReviewSystemPrompt();
|
|
73
|
-
const userMessage = formatPlanReviewUserMessage(
|
|
103
|
+
const userMessage = formatPlanReviewUserMessage(planText, args.project_context, args.constraints, args.notes_to_reviewer, {
|
|
74
104
|
workingDirectories: args.working_directories,
|
|
75
105
|
linkedRoots: args.linked_roots,
|
|
76
106
|
changedFiles: args.changed_files,
|
|
@@ -91,6 +121,7 @@ export function registerPlanReviewTool(server) {
|
|
|
91
121
|
outputSchema: PlanReviewOutputSchema,
|
|
92
122
|
workspaceScope: scope,
|
|
93
123
|
previousReviewId: args.previous_review_id,
|
|
124
|
+
toolName: 'plan',
|
|
94
125
|
reviewerConfig: args.reviewer_config,
|
|
95
126
|
createFallback: (reason, usedTools) => ({
|
|
96
127
|
verdict: 'REVISE',
|
|
@@ -152,6 +183,7 @@ export function registerPlanReviewTool(server) {
|
|
|
152
183
|
gates_tripped: gates.tripped.length > 0 ? gates.tripped : null,
|
|
153
184
|
review_id: reviewId,
|
|
154
185
|
...iterMeta,
|
|
186
|
+
cost_warning: computeCostWarning(iterMeta, usage.estimated_cost_usd),
|
|
155
187
|
token_usage: usage,
|
|
156
188
|
};
|
|
157
189
|
logUsage('plan_review', result.token_usage, {
|
package/package.json
CHANGED