@feng0531/dsh-code-review 0.45.0 → 0.45.1
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
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
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.45.
|
|
5
|
+
## 0.45.1 — 调用方绑定的歧义与历史隔离
|
|
6
|
+
|
|
7
|
+
逐条导入检查唯一解析,防止另一条明确导入掩盖歧义;历史捕获和报告不附带当前侧声明绑定。仅为批准正文中的 JS/TS 命名导入和独立调用位置线索,不是完整语义绑定。替代存在这两项边界错误的 v0.45.0。
|
|
8
|
+
|
|
9
|
+
## 0.45.0 — 必要调用者绑定(由 0.45.1 替代)
|
|
6
10
|
|
|
7
11
|
已捕获的调用方文件会附带导入行、本地别名、调用行与变更文件中唯一声明行的语法绑定,仅在两侧正文都已批准时生成,不新增读取。未批准调用方、歧义声明与历史审查不产生绑定;不证明可达、分派或完整调用覆盖。
|
|
8
12
|
|
package/CORE-ALIGNMENT-TASKS.md
CHANGED
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
|
|
24
24
|
| 优先级 | 未完成项 | 验收重点 |
|
|
25
25
|
|---|---|---|
|
|
26
|
-
| 1 |
|
|
26
|
+
| 1 | 必要调用方、实例与成员级上下文 | 命名与默认定义位置已交付;调用方绑定修正版待完整复验,实例与成员级仍待验收 |
|
|
27
27
|
| 2 | 剩余历史来源与旧侧组合 | Git 历史与 SVN 三层链已交付;其他旧侧来源仍待验收 |
|
|
28
28
|
| 3 | 其他旧侧独有来源 | 来源、版本、权限与删除场景保持一致 |
|
|
29
29
|
| 4 | 剩余语言与路径专用指导 | 只提示具体缺陷和反证;不照搬风格禁令 |
|
|
30
30
|
| 5 | 剩余预算组合与端到端矩阵 | 分组、窗口、检索、多次审查的终态与覆盖一致 |
|
|
31
31
|
| 后置 | 真实模型质量评测 | 保存真实输出,人工裁决,分开统计失败和未完成 |
|
|
32
32
|
|
|
33
|
-
Git
|
|
33
|
+
Git 工作区、Git 历史两侧与 SVN 当前侧支持安全的三层字面依赖链;手动批准上下文可以作为入口。命名与默认导入支持批准正文中的唯一声明位置。这些能力不是完整语义绑定。调用方语法绑定的 v0.45.0 存在逐条导入歧义与历史发现元数据混侧错误,不计作安全对齐;v0.45.1 已修正,待完整复验与正式交付。
|
|
34
34
|
|
|
35
35
|
## 当前验证结论
|
|
36
36
|
|
package/RELEASE-NOTES.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# v0.45.
|
|
1
|
+
# v0.45.1 发布说明
|
|
2
2
|
|
|
3
3
|
## 新增:必要调用者绑定
|
|
4
4
|
|
|
5
5
|
启用 callerScopePaths 后,已捕获的调用方文件会附带语法绑定:导入行、本地别名、调用行,以及变更文件中唯一声明的所在行。这回答“谁调用了你改的函数”,而不只是“谁提到了这个文件”。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
审查输入中的绑定只在调用方正文与变更文件正文都已批准时提供,不新增文件读取。当前仅支持保守的 JS/TS 命名导入与独立调用语法。
|
|
8
|
+
|
|
9
|
+
每一条导入必须独立唯一解析;同文件另一条明确导入不能替歧义导入提供授权。历史捕获、预览、报告与审查输入均不产生这类绑定,避免新旧声明混用。v0.45.1 替代存在这两项边界错误的 v0.45.0。
|
|
8
10
|
|
|
9
11
|
## 限制
|
|
10
12
|
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ import { isSecretPath } from './secret-path.mjs';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
const importExtensions=['.mjs','.cjs','.js','.jsx','.ts','.tsx'];
|
|
8
8
|
// Approved caller body plus approved target text only; no additional reads.
|
|
9
|
-
function callerBindings(fromPath, targetPath, scannedText, targetText) {
|
|
9
|
+
function callerBindings(fromPath, targetPath, scannedText, targetText, resolutionPaths) {
|
|
10
10
|
if (typeof scannedText !== 'string' || typeof targetText !== 'string') return [];
|
|
11
11
|
const lines = scannedText.split(String.fromCharCode(10)), callIndex = indexStandaloneCalls(lines), result = [];
|
|
12
12
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -15,16 +15,18 @@ function callerBindings(fromPath, targetPath, scannedText, targetText) {
|
|
|
15
15
|
const target = path.posix.normalize(path.posix.join(path.posix.dirname(fromPath), match[2]));
|
|
16
16
|
try { relativePath(target); } catch { continue; }
|
|
17
17
|
const candidates = path.posix.extname(target) ? [target] : [target, ...importExtensions.map(ext => target + ext), ...importExtensions.map(ext => target + '/index' + ext)];
|
|
18
|
-
|
|
18
|
+
const matches = candidates.filter(candidate => resolutionPaths.has(candidate));
|
|
19
|
+
if (matches.length !== 1 || matches[0] !== targetPath) continue;
|
|
19
20
|
result.push(...directImportBindings(lines, i + 1, importedCallSites(lines, i + 1, callIndex), targetText));
|
|
20
21
|
}
|
|
21
22
|
return result.slice(0, 20);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
// Pure reverse navigation; resolutionPaths must contain every competitor.
|
|
25
|
-
export function callerCandidates(files, scanned, resolutionPaths) {
|
|
26
|
+
export function callerCandidates(files, scanned, resolutionPaths, {includeBindings=true}={}) {
|
|
26
27
|
if (!Array.isArray(files) || files.length > 200 || !Array.isArray(scanned) || scanned.length > 128 || !Array.isArray(resolutionPaths) || resolutionPaths.length > 512) throw new Error('Caller navigation limit exceeded');
|
|
27
28
|
for (const name of resolutionPaths) relativePath(name);
|
|
29
|
+
const resolved = new Set(resolutionPaths);
|
|
28
30
|
const changed = new Set(), targets = new Set();
|
|
29
31
|
for (const file of files) {
|
|
30
32
|
relativePath(file.path); changed.add(file.path);
|
|
@@ -44,7 +46,7 @@ export function callerCandidates(files, scanned, resolutionPaths) {
|
|
|
44
46
|
if (probes.size > 512) throw new Error('Caller resolution probe limit exceeded');
|
|
45
47
|
}});
|
|
46
48
|
if (plan.truncated) throw new Error('Caller navigation result limit exceeded');
|
|
47
|
-
for (const target of plan.candidates) if (targets.has(target.path)) { const bindings=callerBindings(source.path,target.path,source.text,files.find(f=>f.path===target.path)?.right?.text); candidates.push({fromPath:source.path,targetPath:target.path,reason:target.reasons[0],confidence:'navigation-only',...(bindings.length?{bindings}:{})}); }
|
|
49
|
+
for (const target of plan.candidates) if (targets.has(target.path)) { const bindings=includeBindings?callerBindings(source.path,target.path,source.text,files.find(f=>f.path===target.path)?.right?.text,resolved):[]; candidates.push({fromPath:source.path,targetPath:target.path,reason:target.reasons[0],confidence:'navigation-only',...(bindings.length?{bindings}:{})}); }
|
|
48
50
|
}
|
|
49
51
|
const compare=(a,b)=>a<b?-1:a>b?1:0;
|
|
50
52
|
return {candidates:candidates.sort((a,b)=>compare(a.fromPath,b.fromPath)||compare(a.targetPath,b.targetPath)),probePaths:[...probes].sort(),notice:'Literal module/include references only; not function calls, reachability, semantic binding or complete caller coverage.'};
|
|
@@ -9,7 +9,7 @@ export async function captureHistoryCallers(root,files,history,scopes,options={}
|
|
|
9
9
|
const changed=new Set(files.map(f=>f.path)),rules=new Set(options.rulePaths??[]),scanned=[],candidates=[],indexes=[],contexts=new Map();let scanBytes=0,indexBytes=0,indexRecords=0,probeBytes=0;const allProbes=new Set();
|
|
10
10
|
for(const [side,commit] of [['old',history.base],['new',history.target]]){if(!commit)continue;const index=await historyCallerIndex(root,commit,scopes,bounded);indexBytes+=index.bytes;indexRecords+=index.records;if(indexBytes>64*1024||indexRecords>256)throw new Error('Historical caller shared index limit exceeded');const sources=[];
|
|
11
11
|
for(const entry of index.entries){signal.throwIfAborted();if(changed.has(entry.path)||rules.has(entry.path)||isSecretPath(entry.path)||!/\.(mjs|cjs|js|jsx|ts|tsx|c|cc|cpp|cxx|h|hh|hpp|hxx|shader|hlsl|glsl|cginc|hlsli|compute)$/i.test(entry.path))continue;if(scanned.length>=128)throw new Error('Historical caller shared scan file limit exceeded');const content=decode(await checked('git',['--no-optional-locks','cat-file','blob',entry.oid],{...bounded,cwd:root,maxBytes:Math.min(options.maxFileBytes??256*1024,256*1024)}));scanBytes+=content.bytes;if(scanBytes>1024*1024)throw new Error('Historical caller shared scan byte limit exceeded');const source={path:entry.path,...content,blobOid:entry.oid,revision:commit,side};scanned.push(source);sources.push(source);}
|
|
12
|
-
const targets=side==='old'?files.map(f=>({...f,rightExists:!!f.left})):files;const requested=callerCandidates(targets,sources,[]).probePaths;for(const p of requested)allProbes.add(p);if(allProbes.size>512)throw new Error('Historical caller shared probe limit exceeded');const probes=await historyCallerProbes(root,commit,requested,bounded);probeBytes+=probes.bytes;if(probeBytes>1024*1024)throw new Error('Historical caller shared probe byte limit exceeded');const plan=callerCandidates(targets,sources,probes.paths);indexes.push({side,commit,indexFingerprint:index.fingerprint,probeFingerprint:probes.fingerprint});for(const c of plan.candidates)candidates.push({...c,side,revision:commit});}
|
|
12
|
+
const targets=side==='old'?files.map(f=>({...f,rightExists:!!f.left})):files;const requested=callerCandidates(targets,sources,[]).probePaths;for(const p of requested)allProbes.add(p);if(allProbes.size>512)throw new Error('Historical caller shared probe limit exceeded');const probes=await historyCallerProbes(root,commit,requested,bounded);probeBytes+=probes.bytes;if(probeBytes>1024*1024)throw new Error('Historical caller shared probe byte limit exceeded');const plan=callerCandidates(targets,sources,probes.paths,{includeBindings:false});indexes.push({side,commit,indexFingerprint:index.fingerprint,probeFingerprint:probes.fingerprint});for(const c of plan.candidates)candidates.push({...c,side,revision:commit});}
|
|
13
13
|
const wanted=new Set(candidates.map(c=>c.fromPath));for(const s of scanned){if(!wanted.has(s.path))continue;const item=contexts.get(s.path)??{path:s.path};if(s.side==='new')Object.assign(item,{text:s.text,hash:s.hash,bytes:s.bytes,encoding:s.encoding,revision:s.revision});else Object.assign(item,{oldText:s.text,oldHash:hash(s.text),oldBlobOid:s.blobOid,oldRevision:s.revision});contexts.set(s.path,item);}for(const item of contexts.values())if(item.text===undefined)item.oldOnly=true;signal.throwIfAborted();
|
|
14
14
|
return {context:[...contexts.values()].sort((a,b)=>a.path<b.path?-1:1),discovery:{scopes,history,indexes,scanned:scanned.map(({path,hash,bytes,blobOid,revision,side})=>({path,hash,bytes,blobOid,revision,side})),scannedBytes:scanBytes,candidates,capturedPaths:[],incomplete:true,notice:'Fixed baseline/target literal references in explicit scopes only; no worktree reads, semantic binding or complete caller coverage.',limitations:['Only supported literal references within approved scopes were searched.']}};
|
|
15
15
|
}
|