@feng0531/dsh-code-review 0.33.7 → 0.34.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/CHANGELOG.md +7 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/coverage-plan.mjs +6 -4
- package/src/review-rounds.mjs +5 -0
- package/src/review.mjs +19 -5
- package/src/service.mjs +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.34.0
|
|
4
|
+
|
|
5
|
+
- 服务显式reviewRounds=1..3(默认1避免静默增加发送)支持独立pass,后续轮不带首轮计划,各轮主审/候选反证与综合共享原调用/检索/超时预算,不继承模型对话或已读证据。
|
|
6
|
+
- 各阶段reviewPasses与候选reviewRound保留,跨文件completedRounds完整才标完成;调用耗尽后未发送pass保留pending,取消/超时不伪造完成。预览minimumCalls及Markdown披露工作量。
|
|
7
|
+
- 353离线测试及真实宿主smoke通过,待固定标签四矩阵与原工件发布核验。
|
|
8
|
+
|
|
3
9
|
## 0.33.7
|
|
4
10
|
|
|
5
11
|
- C++窗口导航重复逐项confidence/notice移为统一免责声明,保留全部已生成line/expression/side,不裁剪hunk或批准源码。复现12KiB阻塞场景现为7217字节ready,预览/执行一致;导航不证明绑定/可达性或全部调用覆盖。
|
|
6
|
-
- 340
|
|
12
|
+
- 340冻结离线测试及固定标签四矩阵通过,唯一发布37914089607原工件91349字节与npm/Release逐字节及双hash一致。
|
|
7
13
|
|
|
8
14
|
## 0.33.6
|
|
9
15
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 当前状态
|
|
6
6
|
|
|
7
|
-
当前正式版本为 0.33.
|
|
7
|
+
当前正式版本为 0.33.7(固定标签四矩阵及npm/Release原工件字节双hash门禁通过)。JSON围栏兼容与有界窗口上下文已交付;实际项目审查效果仍需原报告和新预览执行验证。
|
|
8
8
|
|
|
9
9
|
Git历史预览新增oldContextPaths:最多20个显式仓库相对文件,与contextPaths/自动上下文共享20槽、256KiB每blob及4MiB快照。仅从baseline普通Git blob捕获,不checkout/fetch/读取工作树;根提交无baseline拒绝,working/SVN明确拒绝。旧侧条目携带oldRevision/oldBlobOid/oldHash,仅进入context-old;oldOnly表示仅批准旧侧,不证明target路径不存在。所选变更复用原changed-old,不重复上下文;excluded/blocked变更、链接/submodule、凭据路径不能重新进入。预览显示身份元数据、不显示正文,确认执行才按宿主正常权限发送。主审/综合/复核按侧提示,计入原输入预算,旧证据不能冒充新侧回归证明。
|
|
10
10
|
|
package/package.json
CHANGED
package/src/coverage-plan.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {reviewRounds} from './review-rounds.mjs';
|
|
1
2
|
import {riskPlanningDecision} from './risk-plan.mjs';
|
|
2
3
|
import {localBusinessGroups} from './business-groups.mjs';
|
|
3
4
|
import { prepareInteractionInput } from './interaction-input.mjs';
|
|
@@ -6,6 +7,7 @@ import { preparePrimaryInput } from './primary-input.mjs';
|
|
|
6
7
|
|
|
7
8
|
// Metadata only. No retained payload, file reads, model calls, or approval grants.
|
|
8
9
|
export function buildCoveragePlan(input, options) {
|
|
10
|
+
const rounds=reviewRounds(options?.reviewRounds);
|
|
9
11
|
if (!input || typeof input.id !== 'string' || !input.id || !Array.isArray(input.files) || input.files.length > 200) throw new Error('Invalid planning snapshot');
|
|
10
12
|
if (!options || typeof options.instructions !== 'string' || !Number.isSafeInteger(options.maxInputBytes) || options.maxInputBytes < 1) throw new Error('Invalid planning budget');
|
|
11
13
|
const ids = new Set(), paths = new Set();
|
|
@@ -19,12 +21,12 @@ export function buildCoveragePlan(input, options) {
|
|
|
19
21
|
if (file.eligibility !== 'reviewable') return item;
|
|
20
22
|
const prepared = preparePrimaryInput(input, file, options);
|
|
21
23
|
const riskPlanning = options.enableRiskPlanning === true ? options.skipSmallRiskPlans === true ? riskPlanningDecision([file]) : {required:true,reason:'unconditional'} : {required:false,reason:'disabled'};
|
|
22
|
-
return { ...item, ...prepared.metadata, riskPlanning, status: prepared.budget.fits ? 'ready' : 'input-blocked', minimumCalls: prepared.budget.fits ? ((prepared.batches?.length ?? 1) * (riskPlanning.required ?
|
|
24
|
+
return { ...item, ...prepared.metadata, riskPlanning, status: prepared.budget.fits ? 'ready' : 'input-blocked', minimumCalls: prepared.budget.fits ? ((prepared.batches?.length ?? 1) * (rounds + (riskPlanning.required ? 1 : 0)) + (prepared.synthesis?.budget.fits ? rounds : 0)) : 0 };
|
|
23
25
|
});
|
|
24
26
|
const local = options.enableBusinessGrouping === true ? localBusinessGroups(input.files) : null;
|
|
25
27
|
const groups = [...(options.grouping?.groups ?? [])];
|
|
26
28
|
for(const group of local?.groups ?? [])if(!groups.some(g=>[...g.fileIds].sort().join(',')===[...group.fileIds].sort().join(',')))groups.push(group);
|
|
27
|
-
const interactions = options.scope && groups.length ? groups.filter(g => g.fileIds.length > 1).map(group => { const prepared = prepareInteractionInput(input, group, payload => initialInputBudget({ instructions: options.instructions, input: payload }, options.scope, options.maxInputBytes)); const riskPlanning = options.enableRiskPlanning === true ? options.skipSmallRiskPlans === true ? riskPlanningDecision(input.files.filter(f=>group.fileIds.includes(f.id))) : {required:true,reason:'unconditional'} : {required:false,reason:'disabled'}; return { groupId: group.id, fileIds: prepared.fileIds, riskPlanning, status: prepared.status, reason: prepared.reason, minimumCalls: prepared.status === 'ready' ? (riskPlanning.required ?
|
|
28
|
-
const businessGrouping = options.enableBusinessGrouping === true && input.files.filter(f => f.eligibility === 'reviewable').length > 1 ? local ? {minimumCalls:0,strategy:local.strategy,churn:local.churn,notice:'本地调度无语义依赖证明。'} : { minimumCalls: 1, dynamicInteractionCalls: 'unknown-until-execution', maximumAdditionalInitialCalls: Math.floor(input.files.filter(f => f.eligibility === 'reviewable').length / 2) * (options.enableRiskPlanning === true ?
|
|
29
|
-
return { ...(businessGrouping ? { businessGrouping } : {}), interactions, snapshotId: input.id, maxInputBytes: options.maxInputBytes, items, minimumCalls: (businessGrouping?.minimumCalls ?? 0) + items.reduce((sum,item) => sum + item.minimumCalls, 0) + interactions.reduce((sum,item) => sum + item.minimumCalls, 0), initialRequestsOnly: true };
|
|
29
|
+
const interactions = options.scope && groups.length ? groups.filter(g => g.fileIds.length > 1).map(group => { const prepared = prepareInteractionInput(input, group, payload => initialInputBudget({ instructions: options.instructions, input: payload }, options.scope, options.maxInputBytes)); const riskPlanning = options.enableRiskPlanning === true ? options.skipSmallRiskPlans === true ? riskPlanningDecision(input.files.filter(f=>group.fileIds.includes(f.id))) : {required:true,reason:'unconditional'} : {required:false,reason:'disabled'}; return { groupId: group.id, fileIds: prepared.fileIds, riskPlanning, status: prepared.status, reason: prepared.reason, minimumCalls: prepared.status === 'ready' ? (rounds + (riskPlanning.required ? 1 : 0)) : 0 }; }) : [];
|
|
30
|
+
const businessGrouping = options.enableBusinessGrouping === true && input.files.filter(f => f.eligibility === 'reviewable').length > 1 ? local ? {minimumCalls:0,strategy:local.strategy,churn:local.churn,notice:'本地调度无语义依赖证明。'} : { minimumCalls: 1, dynamicInteractionCalls: 'unknown-until-execution', maximumAdditionalInitialCalls: Math.floor(input.files.filter(f => f.eligibility === 'reviewable').length / 2) * (rounds + (options.enableRiskPlanning === true ? 1 : 0)), notice: '模型分组假设仅调度,额外综合动态且不保证预算或覆盖。' } : null;
|
|
31
|
+
return { ...(businessGrouping ? { businessGrouping } : {}), interactions, snapshotId: input.id, reviewRounds:rounds, maxInputBytes: options.maxInputBytes, items, minimumCalls: (businessGrouping?.minimumCalls ?? 0) + items.reduce((sum,item) => sum + item.minimumCalls, 0) + interactions.reduce((sum,item) => sum + item.minimumCalls, 0), initialRequestsOnly: true };
|
|
30
32
|
}
|
package/src/review.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reviewRounds } from './review-rounds.mjs';
|
|
1
2
|
import { correctAnchor } from './anchor-correction.mjs';
|
|
2
3
|
import { validateBusinessRequirement } from './business-requirement.mjs';
|
|
3
4
|
import { riskReadCoverage } from './risk-read-coverage.mjs';
|
|
@@ -75,6 +76,7 @@ async function invoke(model, request, timeoutMs, parentSignal) {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
export async function reviewSnapshot(snapshot, model, options = {}) {
|
|
79
|
+
const rounds = reviewRounds(options.reviewRounds);
|
|
78
80
|
const { signal, maxInputBytes = 96 * 1024, maxCalls = 100, timeoutMs = 120_000 } = options;
|
|
79
81
|
if (typeof model !== 'function') throw new Error('A model executor is required');
|
|
80
82
|
for (const n of [maxInputBytes, maxCalls, timeoutMs]) if (!Number.isSafeInteger(n) || n < 1) throw new Error('Invalid review limit');
|
|
@@ -168,10 +170,12 @@ export async function reviewSnapshot(snapshot, model, options = {}) {
|
|
|
168
170
|
if (prepared.synthesis?.budget.fits) batches.push({ payload: prepared.synthesis.payload, windowIds: [], synthesis: true });
|
|
169
171
|
const matchingInteractionPlans = interactionPlans.filter(p => input.files.filter(f => p.group.fileIds.includes(f.id)).at(-1)?.id === file.id);
|
|
170
172
|
for (const interactionPlan of matchingInteractionPlans.filter(p => p.prepared.status === 'ready')) batches.push({ payload: interactionPlan.prepared.payload, windowIds: [], interaction: interactionPlan.group });
|
|
171
|
-
|
|
173
|
+
const scheduled = Array.from({length:rounds},(_,i)=>batches.map(b=>({...b,round:i+1}))).flat();
|
|
174
|
+
if(rounds>1) state.reviewPasses=scheduled.map(b=>({round:b.round,windowIds:b.windowIds,...(b.interaction?{groupId:b.interaction.id}:{}),...(b.synthesis?{synthesis:true}:{}),status:'pending'}));
|
|
175
|
+
for (const [batchIndex,batch] of scheduled.entries()) {
|
|
172
176
|
enclosingRequest.signal.throwIfAborted();
|
|
173
177
|
let batchPayload = batch.payload;
|
|
174
|
-
if (options.enableRiskPlanning === true && !batch.synthesis) {
|
|
178
|
+
if (options.enableRiskPlanning === true && !batch.synthesis && batch.round === 1) {
|
|
175
179
|
const decision = options.skipSmallRiskPlans === true ? riskPlanningDecision(batch.interaction ? input.files.filter(f=>batch.interaction.fileIds.includes(f.id)) : [file]) : {required:true,reason:'unconditional'};
|
|
176
180
|
state.riskPlans ??= [];
|
|
177
181
|
const entry = { status: 'pending', windowIds: batch.windowIds, ...(batch.interaction ? {groupId:batch.interaction.id,sourceMode:'file-interaction'} : {}), risks: [] };
|
|
@@ -194,6 +198,8 @@ export async function reviewSnapshot(snapshot, model, options = {}) {
|
|
|
194
198
|
}
|
|
195
199
|
}
|
|
196
200
|
}
|
|
201
|
+
if(report.modelCalls>=maxCalls)throw new Error('Model call budget exceeded before next review pass');
|
|
202
|
+
if(rounds>1) state.reviewPasses[batchIndex].status='running';
|
|
197
203
|
const request = { ...enclosingRequest, input: batchPayload };
|
|
198
204
|
const auditStart = state.retrievalAudit?.length ?? 0;
|
|
199
205
|
let response = await executor(request);
|
|
@@ -211,7 +217,7 @@ export async function reviewSnapshot(snapshot, model, options = {}) {
|
|
|
211
217
|
response = decoded;
|
|
212
218
|
}
|
|
213
219
|
}
|
|
214
|
-
if (options.enableRiskPlanning === true && !batch.synthesis) {
|
|
220
|
+
if (options.enableRiskPlanning === true && !batch.synthesis && batch.round === 1) {
|
|
215
221
|
const entry=state.riskPlans.at(-1);
|
|
216
222
|
entry.sourceReadCoverage=riskReadCoverage(entry.risks,(state.retrievalAudit??[]).slice(auditStart),scope?.catalog()??[],input.id);
|
|
217
223
|
if(entry.sourceReadCoverage.some(c=>c.unreadSourceIds.length)) report.limitations.push({fileId:file.id,text:'风险计划请求的部分来源未在本次生成阶段读取;不能视为已核查风险条件或排除问题'});
|
|
@@ -220,6 +226,7 @@ export async function reviewSnapshot(snapshot, model, options = {}) {
|
|
|
220
226
|
if (batch.synthesis) validateSynthesisReads(generated.findings, changes, (state.retrievalAudit ?? []).slice(auditStart), scope.catalog(), file.id, input.id);
|
|
221
227
|
if (!batch.interaction && state.sourceMode === 'change-windows') validateWindowFindings(generated.findings, batch.synthesis ? prepared.synthesis.windows : JSON.parse(batch.payload).windows, batch.synthesis === true);
|
|
222
228
|
request.signal.throwIfAborted();
|
|
229
|
+
if(rounds>1)generated.findings.forEach(f=>{f.reviewRound=batch.round;});
|
|
223
230
|
report.findings.push(...generated.findings);
|
|
224
231
|
if (generated.findings.some(f => f.attribution.status === 'missing')) report.limitations.push({ fileId: file.id, text: '部分发现缺少变更归因,仅校验了定位,尚不能确认属于本次回归' });
|
|
225
232
|
report.limitations.push(...generated.limitations.map(text => ({ fileId: file.id, text })));
|
|
@@ -242,11 +249,16 @@ export async function reviewSnapshot(snapshot, model, options = {}) {
|
|
|
242
249
|
}
|
|
243
250
|
}
|
|
244
251
|
if (batch.synthesis) state.synthesisStatus = 'completed';
|
|
245
|
-
if (batch.interaction)
|
|
246
|
-
|
|
252
|
+
if (batch.interaction) {
|
|
253
|
+
const interaction=report.interactions.find(p => p.groupId === batch.interaction.id);
|
|
254
|
+
if(rounds>1){interaction.completedRounds??=[];interaction.completedRounds.push(batch.round);interaction.reviewRounds=rounds;}
|
|
255
|
+
if(batch.round===rounds)interaction.status='completed';
|
|
256
|
+
}
|
|
257
|
+
if (state.windowCoverage && batch.round === 1) {
|
|
247
258
|
state.windowCoverage.completed.push(...batch.windowIds);
|
|
248
259
|
state.windowCoverage.pending = state.windowCoverage.pending.filter(id => !batch.windowIds.includes(id));
|
|
249
260
|
}
|
|
261
|
+
if(rounds>1)state.reviewPasses[batchIndex].status='completed';
|
|
250
262
|
}
|
|
251
263
|
}, { instructions: REVIEW_INSTRUCTIONS, input: payload }, timeoutMs, signal);
|
|
252
264
|
if (signal?.aborted) throw signal.reason;
|
|
@@ -256,6 +268,7 @@ export async function reviewSnapshot(snapshot, model, options = {}) {
|
|
|
256
268
|
} catch (error) {
|
|
257
269
|
state.status = signal?.aborted ? 'cancelled' : 'failed';
|
|
258
270
|
state.reason = error?.message ?? String(error);
|
|
271
|
+
for(const pass of state.reviewPasses??[])if(pass.status==='running')pass.status=signal?.aborted?'cancelled':'failed';
|
|
259
272
|
state.failureCode = signal?.aborted ? 'CANCELLED' : ['MODEL_INVALID_JSON','MODEL_TIMEOUT'].includes(error?.code) ? error.code : 'REVIEW_EXECUTION_FAILED';
|
|
260
273
|
}
|
|
261
274
|
}
|
|
@@ -293,6 +306,7 @@ export function markdownReport(report) {
|
|
|
293
306
|
if (report.history) output.push('- Git历史审查:' + safe(report.history.mode) + ' · ' + safe(report.history.base ?? 'empty') + ' → ' + safe(report.history.target) + ';精确端点树差异,重命名按增删,不含工作树');
|
|
294
307
|
if (report.businessGrouping) output.push('- 业务分组:' + safe(report.businessGrouping.status) + (report.businessGrouping.reason ? ' — ' + safe(report.businessGrouping.reason) : '') + ';仅调度假设,不证明依赖或覆盖');
|
|
295
308
|
for (const file of report.files) for (const plan of file.riskPlans ?? []) output.push('- 风险计划:' + safe(file.path) + ' · ' + safe(plan.status) + ' · ' + plan.risks.length + '项待证假设;不表示缺陷成立或覆盖完成');
|
|
309
|
+
for (const file of report.files) if(file.reviewPasses)output.push('- 独立审查轮次 '+safe(file.path)+':'+file.reviewPasses.filter(p=>p.status==='completed').length+'/'+file.reviewPasses.length+'阶段完成;同一生命周期预算,后续轮无首轮计划;不证明代码正确。');
|
|
296
310
|
for (const file of report.files) output.push('- ' + safe(file.path) + ':' + file.status + (file.reason ? ' — ' + safe(file.reason) : ''));
|
|
297
311
|
if (report.grouping) {
|
|
298
312
|
const names = new Map(report.files.map(f => [f.fileId, f.path]));
|
package/src/service.mjs
CHANGED
|
@@ -17,7 +17,7 @@ function owner(exec) {
|
|
|
17
17
|
if (!agent || typeof cwd !== 'string' || !cwd || !agent.options?.provider || !agent.options?.model) throw new Error('Active Agent with cwd and model is required');
|
|
18
18
|
return { agent, cwd, route: { provider: agent.options.provider, model: agent.options.model, ...(agent.options.reasoningEffort ? { reasoningEffort: agent.options.reasoningEffort } : {}) } };
|
|
19
19
|
}
|
|
20
|
-
export function createReviewService(llm, { capture = captureSnapshot, now = Date.now, ttlMs = 300000, maxPreviews = 8, allowModelSending = false, enableRiskPlanning = false, skipSmallRiskPlans = true, enableBusinessGrouping = false, enableAnchorCorrection = false, authorize = async () => false } = {}) {
|
|
20
|
+
export function createReviewService(llm, { capture = captureSnapshot, now = Date.now, ttlMs = 300000, maxPreviews = 8, allowModelSending = false, enableRiskPlanning = false, skipSmallRiskPlans = true, reviewRounds = 1, enableBusinessGrouping = false, enableAnchorCorrection = false, authorize = async () => false } = {}) {
|
|
21
21
|
for (const value of [ttlMs, maxPreviews]) if (!Number.isSafeInteger(value) || value < 1) throw new Error('Invalid preview limit');
|
|
22
22
|
const lifetime = new AbortController();
|
|
23
23
|
const bind = exec => ({ ...exec, signal: exec.signal ? AbortSignal.any([exec.signal, lifetime.signal]) : lifetime.signal });
|
|
@@ -42,12 +42,12 @@ export function createReviewService(llm, { capture = captureSnapshot, now = Date
|
|
|
42
42
|
const snapshot = await capture(target, { ...options, signal: exec.signal });
|
|
43
43
|
exec.signal?.throwIfAborted();
|
|
44
44
|
if (owner(exec).cwd !== current.cwd) throw new Error('Agent directory changed; preview again');
|
|
45
|
-
const plan = buildCoveragePlan(snapshot, { enableRiskPlanning, skipSmallRiskPlans, enableBusinessGrouping, instructions: REVIEW_INSTRUCTIONS, scope: createRetrievalScope(snapshot, { signal: exec.signal }), grouping: buildReviewGroups(snapshot.files, inferFileRelations(snapshot.files)), maxInputBytes: 96 * 1024, signal: exec.signal });
|
|
45
|
+
const plan = buildCoveragePlan(snapshot, { enableRiskPlanning, skipSmallRiskPlans, reviewRounds, enableBusinessGrouping, instructions: REVIEW_INSTRUCTIONS, scope: createRetrievalScope(snapshot, { signal: exec.signal }), grouping: buildReviewGroups(snapshot.files, inferFileRelations(snapshot.files)), maxInputBytes: 96 * 1024, signal: exec.signal });
|
|
46
46
|
prune();
|
|
47
47
|
while (previews.size >= maxPreviews) previews.delete(previews.keys().next().value);
|
|
48
48
|
const previewId = randomUUID();
|
|
49
49
|
previews.set(previewId, { ...current, target, options, snapshotId: snapshot.id, expires: now() + ttlMs });
|
|
50
|
-
return { ...(options.businessRequirement!==undefined?{businessRequirement:{text:options.businessRequirement,hash:hash(options.businessRequirement),bytes:Buffer.byteLength(options.businessRequirement)}}:{}), previewId, plan, snapshotId: snapshot.id, repositoryRoot: snapshot.root, vcs: snapshot.vcs, ...(snapshot.history ? { history: snapshot.history } : {}), model: current.route, modelSendingEnabled: allowModelSending, files: snapshot.files.map(f => ({ path: f.path, eligibility: f.eligibility, reason: f.reason })), contextPaths: snapshot.context.map(c => c.path), ...(options.oldContextPaths !== undefined ? {oldContextPaths:options.oldContextPaths,oldContextSources:snapshot.context.filter(c=>c.oldOnly).map(c=>({path:c.path,side:'context-old',revision:c.oldRevision,blobOid:c.oldBlobOid,hash:c.oldHash}))} : {}), ...(snapshot.callerDiscovery ? {callerDiscovery:snapshot.callerDiscovery} : {}), ...(snapshot.autoContext ? { autoContext: snapshot.autoContext } : {}), rules: (snapshot.rules ?? []).map(({ path, hash }) => ({ path, hash })), notice: (options.oldContextPaths !== undefined ? 'oldContextPaths仅为baseline旧侧正文,无目标侧替代;选中变更复用原old身份,旧侧不能证明新侧行为。' : '') + (snapshot.callerDiscovery ? 'callerScopePaths已授权预览读取限定目录内tracked候选正文,包括不匹配文件;非匹配正文不发送模型,匹配捕获上下文会发送。仅字面引用导航,不是函数调用或完整调用覆盖。' : '') + (enableRiskPlanning ? '执行按plan阈值决定风险假设规划,小变更可跳过独立规划但仍主审并复核;计划不是证据,所有阶段共享原预算。' : '') + '执行会向上述模型提供方发送可审查文件两侧内容、预览列出的显式/自动上下文、所选项目规则全文及预览业务需求文本。模型可在该快照范围内多轮只读检索,不读取范围外文件;候选生成后会进行证据与反证复核,两阶段各最多3轮检索,共享每文件120秒和总模型调用100次预算;复核不能证明缺陷成立。plan仅预检初始输入,ready不代表已审查;minimumCalls不含后续检索/复核/动态业务综合;多文件可先进行业务分组,分组失败保留原组并记录限制,动态综合数执行前未知,实际预算可能不足。请先向用户展示范围,获得确认后执行。' };
|
|
50
|
+
return { ...(options.businessRequirement!==undefined?{businessRequirement:{text:options.businessRequirement,hash:hash(options.businessRequirement),bytes:Buffer.byteLength(options.businessRequirement)}}:{}), previewId, plan, snapshotId: snapshot.id, repositoryRoot: snapshot.root, vcs: snapshot.vcs, ...(snapshot.history ? { history: snapshot.history } : {}), model: current.route, modelSendingEnabled: allowModelSending, files: snapshot.files.map(f => ({ path: f.path, eligibility: f.eligibility, reason: f.reason })), contextPaths: snapshot.context.map(c => c.path), ...(options.oldContextPaths !== undefined ? {oldContextPaths:options.oldContextPaths,oldContextSources:snapshot.context.filter(c=>c.oldOnly).map(c=>({path:c.path,side:'context-old',revision:c.oldRevision,blobOid:c.oldBlobOid,hash:c.oldHash}))} : {}), ...(snapshot.callerDiscovery ? {callerDiscovery:snapshot.callerDiscovery} : {}), ...(snapshot.autoContext ? { autoContext: snapshot.autoContext } : {}), rules: (snapshot.rules ?? []).map(({ path, hash }) => ({ path, hash })), notice: (options.oldContextPaths !== undefined ? 'oldContextPaths仅为baseline旧侧正文,无目标侧替代;选中变更复用原old身份,旧侧不能证明新侧行为。' : '') + (snapshot.callerDiscovery ? 'callerScopePaths已授权预览读取限定目录内tracked候选正文,包括不匹配文件;非匹配正文不发送模型,匹配捕获上下文会发送。仅字面引用导航,不是函数调用或完整调用覆盖。' : '') + (enableRiskPlanning ? '执行按plan阈值决定风险假设规划,小变更可跳过独立规划但仍主审并复核;计划不是证据,所有阶段共享原预算。' : '') + '执行会向上述模型提供方发送可审查文件两侧内容、预览列出的显式/自动上下文、所选项目规则全文及预览业务需求文本。模型可在该快照范围内多轮只读检索,不读取范围外文件;候选生成后会进行证据与反证复核,两阶段各最多3轮检索,共享每文件120秒和总模型调用100次预算;复核不能证明缺陷成立。plan.reviewRounds为独立审查工作量,非模型reasoning effort;后续轮不带首轮风险计划,所有轮共享原超时与预算。plan仅预检初始输入,ready不代表已审查;minimumCalls不含后续检索/复核/动态业务综合;多文件可先进行业务分组,分组失败保留原组并记录限制,动态综合数执行前未知,实际预算可能不足。请先向用户展示范围,获得确认后执行。' };
|
|
51
51
|
} finally { busy = false; }
|
|
52
52
|
},
|
|
53
53
|
async execute(args, exec) {
|
|
@@ -70,7 +70,7 @@ export function createReviewService(llm, { capture = captureSnapshot, now = Date
|
|
|
70
70
|
if (await authorize({ snapshot, route: preview.route, exec }) !== true) throw new Error('Model sending approval denied or unavailable');
|
|
71
71
|
exec.signal?.throwIfAborted();
|
|
72
72
|
if (owner(exec).cwd !== current.cwd || JSON.stringify(owner(exec).route) !== JSON.stringify(preview.route)) throw new Error('Agent changed during approval');
|
|
73
|
-
const report = await reviewSnapshot(snapshot, createDshModel(llm, preview.route), { signal: exec.signal, enableRetrieval: true, enableGrouping: true, enableVerification: true, enableRiskPlanning, skipSmallRiskPlans, enableBusinessGrouping, enableAnchorCorrection });
|
|
73
|
+
const report = await reviewSnapshot(snapshot, createDshModel(llm, preview.route), { signal: exec.signal, enableRetrieval: true, enableGrouping: true, enableVerification: true, enableRiskPlanning, skipSmallRiskPlans, reviewRounds, enableBusinessGrouping, enableAnchorCorrection });
|
|
74
74
|
report.model = preview.route;
|
|
75
75
|
const latest = await resolveWorkspaceRepository(current.cwd, preview.options.repositoryPath).then(target => target === preview.target ? capture(target, { ...preview.options, signal: exec.signal }) : null).catch(() => null);
|
|
76
76
|
report.outdated = !latest || latest.id !== snapshot.id || owner(exec).cwd !== current.cwd;
|