@feng0531/dsh-code-review 0.87.8 → 0.87.9
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 +6 -0
- package/RELEASE-NOTES.md +8 -10
- package/package.json +1 -1
- package/src/background-review.mjs +1 -0
- package/src/project-report.mjs +1 -0
- package/src/project-review-service.mjs +1 -1
- package/src/report-readability.mjs +10 -1
- package/src/report-summary.mjs +2 -0
- package/src/review-jobs.mjs +1 -1
- package/src/review.mjs +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
本文件介绍对使用者有意义的变化。安装与参数见 [README](README.md),本次发布正文见 [发布说明](RELEASE-NOTES.md)。历史提交和安装包保留在 [GitHub Releases](https://github.com/f-e-n-g-0531/dsh-code-review/releases);本文不把开发日志当作功能介绍。
|
|
4
4
|
|
|
5
|
+
## 0.87.9 — 范围说明与未完成分开记账
|
|
6
|
+
|
|
7
|
+
- 初始按需上下文说明由宿主单独记录为范围说明,不再仅因这项导航提示将已完成的所选审核标成部分完成;仍不表示全部上下文已核查或可以放行。模型返回、未知及其他限制仍保守影响完成状态,预算、失败、未完成综合与复核门槛不变。
|
|
8
|
+
- 保留范围说明原文和文件归属;旧报告不重新分类。本版不改模型提示、检索额度或证据判定,不宣称提速。
|
|
9
|
+
- 后续真实 0.87.8 固定七例已补测:三正例最终有效支持从旧版 0/3 到 3/3,四反例无误报;18次调用,执行88.8秒,仍为partial。仅这七个合成样例的一次观察,不是0.87.9实模验收或业务性能证明。
|
|
10
|
+
|
|
5
11
|
## 0.87.8 — 契约内静态复核与明确证据协议
|
|
6
12
|
|
|
7
13
|
- 区分源码和明确适用契约内的静态支持与实机复现:触发可构造、局部实现完整且排除现有保护时,不因缺少业务运行日志自动降为不确定;关键外部条件未知时仍保留不确定,合同文本不扩大权限。
|
package/RELEASE-NOTES.md
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
# v0.87.
|
|
1
|
+
# v0.87.9 发布说明
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 修复范围说明导致的部分完成标记
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
初始输入采用批准目录按需读取时,宿主将这一范围说明单独记账,不再把它当成未完成限制。报告仍保留说明原文、归属及“不是全部上下文已核查”的限定。仅影响这项明确的宿主提示,不按文本匹配删除模型或未知限制,也不改写历史报告。预算耗尽、阶段失败、未完成综合或复核仍须保守呈现。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
本版不修改模型提示、调用额度、读取预算或严格原文及旧新归因校验,不把所选范围处理完成等同于代码无缺陷或允许放行。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## 实际验收依据与边界
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
先前未加载新版的边界现已补测:实际运行0.87.8、同冻结七例和gpt/gpt-6.1-sol,三正例均最终supported/introduced并有宿主校验的旧新原文,四反例无误报,主审7/7完成。旧0.87.6有效支持为0/3,本次为3/3;独立裁决核对正文、哈希和确定性行为。
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
本次18次模型调用、执行88.809秒,旧基线82.293秒,未观察到提速;各一次且时间环境不同,不作稳定速度结论。0.87.8仍因七条通用上下文范围提示标为partial,预注册严格整体验收未追改为通过。这些是0.87.8的观察,不是本版真实模型成绩。本版离线回归不能代替实际加载后的完整真实验收。
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
发布不自动安装或重启宿主。固定样例、原始公开结果及裁决材料留在本地实验目录,不进入安装包。安装见 [README](README.md),门禁见 [开发说明](DEVELOPMENT.md)。
|
|
15
|
+
发布不自动安装或重启运行宿主。固定样例与本地验收材料不进入包。安装见[README](README.md),门禁见[开发说明](DEVELOPMENT.md)。
|
package/package.json
CHANGED
|
@@ -28,6 +28,7 @@ function terminalSummary(value) {
|
|
|
28
28
|
lines.push('报告状态:' + report.status + ';任务结束不等于所有审查通过。');
|
|
29
29
|
lines.push('文件覆盖:' + COVERAGE_FIELDS.map(key => key + ' ' + count(report.coverage?.[key])).join(';'));
|
|
30
30
|
lines.push('总文件:' + count(report.totalFiles) + ';总发现:' + count(report.totalFindings) + ';限制项:' + count(report.totalLimitations));
|
|
31
|
+
if (Number.isSafeInteger(report.totalScopeNotices) && report.totalScopeNotices > 0) lines.push('范围说明:' + report.totalScopeNotices + ' 项(非未完成限制);完成不等于全部批准上下文已核查,原文与归属见报告附录。');
|
|
31
32
|
lines.push('模型调用:' + count(report.modelCalls ?? report.diagnostics?.modelCalls));
|
|
32
33
|
const d = report.diagnostics;
|
|
33
34
|
if (d?.readerBudget) {
|
package/src/project-report.mjs
CHANGED
|
@@ -7,6 +7,7 @@ export function renderProjectReport(report) {
|
|
|
7
7
|
findings: report.findings ?? [],
|
|
8
8
|
interactions: (report.batches ?? []).flatMap(b => b.interactions ?? []),
|
|
9
9
|
limitations: [...(report.limitations ?? []), ...(report.batches ?? []).flatMap(b => b.limitations ?? [])],
|
|
10
|
+
scopeNotices: report.scopeNotices ?? (report.batches ?? []).flatMap(b => b.scopeNotices ?? []),
|
|
10
11
|
};
|
|
11
12
|
return markdownReport(merged).replace('# Code Review 报告', '# 全项目代码审查报告');
|
|
12
13
|
}
|
|
@@ -204,7 +204,7 @@ export function createProjectReviewService(llm, { allowModelSending = false, aut
|
|
|
204
204
|
: 'partial',
|
|
205
205
|
repositoryRoot: s.root, vcs: s.vcs, model: p.route, modelCalls: calls, maxModelCalls: projectMaxCalls,
|
|
206
206
|
totalTrackedNodes: s.totalTrackedNodes, selectionComplete: s.selectionComplete, inventoryComplete: s.inventoryComplete, inventoryScope: s.inventoryScope, inventoryNodes: s.inventoryNodes,
|
|
207
|
-
coverage, files, findings, artifacts: results.flatMap(result => result.artifacts ?? []), batches: results, resume: { reusedFiles: files.filter(file => file.reused).length },
|
|
207
|
+
coverage, files, findings, scopeNotices: results.flatMap(result => result.scopeNotices ?? []), artifacts: results.flatMap(result => result.artifacts ?? []), batches: results, resume: { reusedFiles: files.filter(file => file.reused).length },
|
|
208
208
|
privateArtifacts: { enabled: typeof exec.onModelArtifact === 'function', sourceBearing: true, maxArtifactBytes: 1048576, maxJobBytes: 8388608, maxArtifacts: 4096, notice: 'Bounded private untrusted model outputs; raw access requires explicit consent and is not review evidence.' },
|
|
209
209
|
execution: { nativeOutputTools, enableGroupPrimary, batchConcurrency: projectConcurrency, maxRetrievalRounds },
|
|
210
210
|
notice: s.notice + REPORT_NOTICE_SUFFIX,
|
|
@@ -95,7 +95,7 @@ function collect(report) {
|
|
|
95
95
|
return { record, reasons: fileReasons(record, findings, associated), paths: record?.path != null ? [String(record.path)] : paths(record) };
|
|
96
96
|
}).filter(item => item.reasons.length);
|
|
97
97
|
const interactions = rows(report?.interactions).filter(record => record?.status !== 'completed').map(record => ({ record, reasons: [reason(record)], paths: paths(record) }));
|
|
98
|
-
return { unfinished, interactions, units, limitations: rows(report?.limitations), paths };
|
|
98
|
+
return { unfinished, interactions, units, limitations: rows(report?.limitations), scopeNotices: rows(report?.scopeNotices), paths };
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function grouped(items) {
|
|
@@ -183,6 +183,15 @@ export function limitationDetailsMarkdown(report = {}) {
|
|
|
183
183
|
output.push('以下仅为状态与原因元数据;源码、候选及其他完整字段见完整源报告 JSON。');
|
|
184
184
|
originalDetails(output, packed(items.map(item => Object.fromEntries(keys.filter(key => Object.hasOwn(item.record ?? {}, key)).map(key => [key, item.record[key]])))));
|
|
185
185
|
}
|
|
186
|
+
if (data.scopeNotices.length) {
|
|
187
|
+
output.push('', '### 范围说明(' + data.scopeNotices.length + ' 条记录)', '以下为上下文提供方式说明,不计为未完成限制;处理完成不等于全部批准上下文已核查。');
|
|
188
|
+
for (const record of data.scopeNotices) {
|
|
189
|
+
const paths = data.paths(record);
|
|
190
|
+
output.push('- 范围:' + (paths.length ? paths.map(escape).join('、') : '未标明文件身份'));
|
|
191
|
+
}
|
|
192
|
+
// Preserve complete original values, order, duplicates and file ownership.
|
|
193
|
+
originalDetails(output, data.scopeNotices);
|
|
194
|
+
}
|
|
186
195
|
if (data.units.length) {
|
|
187
196
|
output.push('', '### 未完成阶段续审元数据', '按 continuation.units 原始索引保留对应阶段及依赖身份;record 为原始状态元数据,resolvedReason 仅为展示解释,不覆盖原始 pendingReason。');
|
|
188
197
|
originalDetails(output, data.units.map(item => ({ position: item.position, record: unitMetadata(item.record), paths: item.paths, resolvedReason: reason(item.evidence), dependencies: item.dependencies })));
|
package/src/report-summary.mjs
CHANGED
|
@@ -83,6 +83,7 @@ export function reportSummaryData(report = {}) {
|
|
|
83
83
|
coverage: { total, loaded: files.length, detailsComplete, unit: project ? 'files' : 'change-entries', ...coverage },
|
|
84
84
|
unfinished, crossFile, stages, unverifiedCandidates,
|
|
85
85
|
limitations: rows(report.limitations).length,
|
|
86
|
+
scopeNotices: rows(report.scopeNotices).length,
|
|
86
87
|
reused: findings.some(finding => finding?.reused || finding?.priorRun),
|
|
87
88
|
currentSourceOnly: Boolean(project || findings.some(finding => finding?.attribution?.causality === 'current-source-only')),
|
|
88
89
|
};
|
|
@@ -113,6 +114,7 @@ export function reportSummaryMarkdown(report = {}) {
|
|
|
113
114
|
if (data.unverifiedCandidates) output.push('- 候选复核未完成:' + data.unverifiedCandidates + ' 项。');
|
|
114
115
|
if (data.crossFile.total) output.push('- 跨文件综合:完成 ' + data.crossFile.completed + '/' + data.crossFile.total + ' 项;未完成 ' + data.crossFile.pending + ' 项。');
|
|
115
116
|
if (data.limitations) output.push('- 已记录限制:' + data.limitations + ' 项,见限制明细。');
|
|
117
|
+
if (data.scopeNotices) output.push('- 范围说明:' + data.scopeNotices + ' 项,见附录;不计为未完成限制,完成不等于全部批准上下文已核查。');
|
|
116
118
|
const qualifications = ['完成仅指处理进度,空发现不证明无缺陷'];
|
|
117
119
|
if (counts.total) qualifications.push('按候选记录而非去重根因计数,复核支持及引用校验不是事实或因果证明');
|
|
118
120
|
if (data.currentSourceOnly) qualifications.push('当前源码模式不评估历史新增归因');
|
package/src/review-jobs.mjs
CHANGED
|
@@ -78,7 +78,7 @@ export function createReviewJobs({directory}={}){
|
|
|
78
78
|
},
|
|
79
79
|
async resume(id,exec){const r=await owned(id,exec);if(jobs.get(id)&&!jobs.get(id).settled||['queued','running'].includes(r.status)||!r.resumeState)throw new Error('No terminal completed-chain resume state available');return structuredClone(r.resumeState);},
|
|
80
80
|
async artifacts(id,exec,page){const r=await owned(id,exec);return artifacts.read(id,r.owner,page);},
|
|
81
|
-
async status(id,exec,page){const r=structuredClone(await owned(id,exec));delete r.resumeState;const active=jobs.get(id);if(active&&!active.settled&&!['queued','running'].includes(r.status)){r.status='running';r.stage='finalizing';delete r.result;delete r.finishedAt;}r.recovery=recoveryCoverage(r);r.progress=jobProgress(r);r.waitRevision=jobs.get(id)?.savedRevision??r.checkpoints.length;r.nextAction=['queued','running'].includes(r.status)?'Call code_review_wait with waitFor=terminal; reuse the exact returned waitRevision as after, never guess increments. No code_review_status call is needed after each event. Do not end the user request while task runs.':r.result?.previewId?'Inspect preview against current explicit user authorization. For a matching explicit review request, execute with confirmed=true without asking again. Do not execute preview-only or explicit wait-for-confirmation requests; ask for authorization if scope/model/destination exceeds authorization or is unclear. Preserve host permissions and snapshot/identity checks.':'Collect final report and unfinished scope, then provide one final answer.';if(!page)return r;const {offset=0,limit=20,checkpointIndex}=page;if(!Number.isSafeInteger(offset)||offset<0||!Number.isSafeInteger(limit)||limit<1||limit>100)throw new Error('Invalid result page');if(checkpointIndex!==undefined){if(!Number.isSafeInteger(checkpointIndex)||checkpointIndex<0||checkpointIndex>=r.checkpoints.length)throw new Error('Invalid checkpoint index');return {jobId:r.jobId,status:r.status,waitRevision:r.waitRevision,progress:r.progress,nextAction:r.nextAction,checkpointIndex,checkpoint:pageCheckpoint(r.checkpoints[checkpointIndex],offset,limit),page:{offset,limit}};}const result=r.result;const report=result?.report;return {...r,result:undefined,recovery:{...r.recovery,unfinishedStages:r.recovery.unfinishedStages.slice(offset,offset+limit),files:r.recovery.files.slice(offset,offset+limit),windows:r.recovery.windows.slice(offset,offset+limit),suggestedPaths:r.recovery.suggestedPaths.slice(offset,offset+limit),totalObservedFiles:r.recovery.files.length},checkpoints:r.checkpoints.slice(offset,offset+limit).map(c=>pageCheckpoint(c,0,limit)),checkpointPage:{offset,limit,total:r.checkpoints.length},...(result?{resultSummary:report?{status:report.status,coverage:report.coverage,model:report.model,snapshotId:report.snapshotId,diagnostics:report.diagnostics,execution:report.execution,modelCalls:report.modelCalls,totalFiles:report.files?.length,totalFindings:report.findings?.length,totalLimitations:report.limitations?.length}:({...result,files:result.files?.slice(offset,offset+limit),filePage:result.files?{offset,limit,total:result.files.length}:undefined}),resultPage:report?{files:report.files?.slice(offset,offset+limit),findings:report.findings?.slice(offset,offset+limit),totalFiles:report.files?.length,totalFindings:report.findings?.length}:undefined}: {})};},
|
|
81
|
+
async status(id,exec,page){const r=structuredClone(await owned(id,exec));delete r.resumeState;const active=jobs.get(id);if(active&&!active.settled&&!['queued','running'].includes(r.status)){r.status='running';r.stage='finalizing';delete r.result;delete r.finishedAt;}r.recovery=recoveryCoverage(r);r.progress=jobProgress(r);r.waitRevision=jobs.get(id)?.savedRevision??r.checkpoints.length;r.nextAction=['queued','running'].includes(r.status)?'Call code_review_wait with waitFor=terminal; reuse the exact returned waitRevision as after, never guess increments. No code_review_status call is needed after each event. Do not end the user request while task runs.':r.result?.previewId?'Inspect preview against current explicit user authorization. For a matching explicit review request, execute with confirmed=true without asking again. Do not execute preview-only or explicit wait-for-confirmation requests; ask for authorization if scope/model/destination exceeds authorization or is unclear. Preserve host permissions and snapshot/identity checks.':'Collect final report and unfinished scope, then provide one final answer.';if(!page)return r;const {offset=0,limit=20,checkpointIndex}=page;if(!Number.isSafeInteger(offset)||offset<0||!Number.isSafeInteger(limit)||limit<1||limit>100)throw new Error('Invalid result page');if(checkpointIndex!==undefined){if(!Number.isSafeInteger(checkpointIndex)||checkpointIndex<0||checkpointIndex>=r.checkpoints.length)throw new Error('Invalid checkpoint index');return {jobId:r.jobId,status:r.status,waitRevision:r.waitRevision,progress:r.progress,nextAction:r.nextAction,checkpointIndex,checkpoint:pageCheckpoint(r.checkpoints[checkpointIndex],offset,limit),page:{offset,limit}};}const result=r.result;const report=result?.report;return {...r,result:undefined,recovery:{...r.recovery,unfinishedStages:r.recovery.unfinishedStages.slice(offset,offset+limit),files:r.recovery.files.slice(offset,offset+limit),windows:r.recovery.windows.slice(offset,offset+limit),suggestedPaths:r.recovery.suggestedPaths.slice(offset,offset+limit),totalObservedFiles:r.recovery.files.length},checkpoints:r.checkpoints.slice(offset,offset+limit).map(c=>pageCheckpoint(c,0,limit)),checkpointPage:{offset,limit,total:r.checkpoints.length},...(result?{resultSummary:report?{status:report.status,coverage:report.coverage,model:report.model,snapshotId:report.snapshotId,diagnostics:report.diagnostics,execution:report.execution,modelCalls:report.modelCalls,totalFiles:report.files?.length,totalFindings:report.findings?.length,totalLimitations:report.limitations?.length,totalScopeNotices:report.scopeNotices?.length??0}:({...result,files:result.files?.slice(offset,offset+limit),filePage:result.files?{offset,limit,total:result.files.length}:undefined}),resultPage:report?{files:report.files?.slice(offset,offset+limit),findings:report.findings?.slice(offset,offset+limit),totalFiles:report.files?.length,totalFindings:report.findings?.length}:undefined}: {})};},
|
|
82
82
|
async wait(id,exec,{after,waitFor='progress',timeoutMs=60000,signal}={}){
|
|
83
83
|
const r=await owned(id,exec);
|
|
84
84
|
if(!Number.isSafeInteger(timeoutMs)||timeoutMs<1||timeoutMs>60000)throw new Error('Invalid wait duration');
|
package/src/review.mjs
CHANGED
|
@@ -286,7 +286,6 @@ export async function reviewSnapshot(snapshot, model, options = {}) {
|
|
|
286
286
|
const prepared = preparePrimaryInput(input, file, { instructions, scope, grouping: report.grouping, maxInputBytes, riskPlanningMode: options.riskPlanningMode, focusPrimaryContext: options.focusPrimaryContext === true, enableRiskPlanning: options.enableRiskPlanning, skipSmallRiskPlans: options.skipSmallRiskPlans });
|
|
287
287
|
const { changes, payload } = prepared;
|
|
288
288
|
Object.assign(state, prepared.metadata);
|
|
289
|
-
if (state.contextMode === 'approved-retrieval') result.limitations.push({ fileId: file.id, text: '初始输入仅提供聚焦上下文或身份目录,其他批准全文须按需读取;不声明所有上下文已核查。' });
|
|
290
289
|
if (prepared.synthesis) state.synthesisStatus = prepared.synthesis.status === 'ready' ? 'pending' : 'blocked';
|
|
291
290
|
if (prepared.synthesis?.status === 'blocked') result.limitations.push({ fileId: file.id, text: (prepared.synthesis.reason === 'batch-limit' ? '跨窗口综合批次数超过上限(20),未发送;全部主窗口仍独立审查' : '跨窗口综合输入超限,未发送') + ';单窗口完成不表示交互影响已检查' });
|
|
292
291
|
if (state.sourceMode === 'change-windows') state.windowCoverage = { completed: [], pending: [...state.windowIds] };
|
|
@@ -822,6 +821,9 @@ export async function reviewSnapshot(snapshot, model, options = {}) {
|
|
|
822
821
|
if (checkpointFailed) throw checkpointFailure;
|
|
823
822
|
for (const result of fileResults) if (result?.state.continuationPending === false) { const state = result.state; state.resumeEligible = state.status === 'completed' && !state.stageFailures?.length && !state.reviewPasses?.some(pass => pass.status !== 'completed') && !state.windowCoverage?.pending?.length && !state.retrievalLimited && result.findings.every(finding => finding.verification?.status === 'completed') && report.interactions.filter(item => item.fileIds?.includes(state.fileId)).every(item => item.status === 'completed'); }
|
|
824
823
|
report.files = fileResults.map(result => result.state);
|
|
824
|
+
// Derive only from host-prepared state, including reused checkpoint state. Never
|
|
825
|
+
// reclassify model or historical limitations by text or model-supplied tags.
|
|
826
|
+
report.scopeNotices = report.files.filter(file => file.contextMode === 'approved-retrieval').map(file => ({ fileId: file.fileId, text: '初始输入仅提供聚焦上下文或身份目录,其他批准全文须按需读取;不声明所有上下文已核查。' }));
|
|
825
827
|
report.findings = fileResults.flatMap(result => result.findings);
|
|
826
828
|
report.limitations.push(...fileResults.flatMap(result => result.limitations));
|
|
827
829
|
report.execution = { fileConcurrency, maxRetrievalRounds };
|