@feng0531/dsh-code-review 0.45.1 → 0.46.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 CHANGED
@@ -2,9 +2,13 @@
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.46.0 — 历史调用方的同版本导航
6
+
7
+ Git 历史调用方可分别对应 baseline 与 target 的唯一声明。批准正文、侧与版本必须对应,缺侧不回退;支持删除目标的旧侧导航,新增文件不产生旧侧声明线索。仍为保守语法导航,不新增读取或证明完整调用覆盖。
8
+
5
9
  ## 0.45.1 — 调用方绑定的歧义与历史隔离
6
10
 
7
- 逐条导入检查唯一解析,防止另一条明确导入掩盖歧义;历史捕获和报告不附带当前侧声明绑定。仅为批准正文中的 JS/TS 命名导入和独立调用位置线索,不是完整语义绑定。替代存在这两项边界错误的 v0.45.0。
11
+ 逐条导入检查唯一解析,防止另一条明确导入掩盖歧义;历史捕获和报告不附带当前侧声明绑定。仅为批准正文中的 JS/TS 命名导入和独立调用位置线索,不是完整语义绑定。替代存在这两项边界错误的 v0.45.0。413 项完整回归、实际宿主、10 项独立安装专项与四组 CI 通过;npm、Release 和原包 96,700 字节一致,双哈希与清单通过。
8
12
 
9
13
  ## 0.45.0 — 必要调用者绑定(由 0.45.1 替代)
10
14
 
@@ -23,18 +23,19 @@
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 工作区、Git 历史两侧与 SVN 当前侧支持安全的三层字面依赖链;手动批准上下文可以作为入口。命名与默认导入支持批准正文中的唯一声明位置。这些能力不是完整语义绑定。调用方语法绑定的 v0.45.0 存在逐条导入歧义与历史发现元数据混侧错误,不计作安全对齐;v0.45.1 已修正,待完整复验与正式交付。
33
+ Git 工作区、Git 历史两侧与 SVN 当前侧支持安全的三层字面依赖链;手动批准上下文可以作为入口。命名与默认导入支持批准正文中的唯一声明位置。这些能力不是完整语义绑定。调用方语法绑定的 v0.45.0 存在逐条导入歧义与历史发现元数据混侧错误,不计作安全对齐;v0.45.1 已修正并正式交付;历史调用方的同版本语法导航已实现,待完整复验与正式交付;缺侧不用另一侧替代。
34
34
 
35
35
  ## 当前验证结论
36
36
 
37
- - 最近完整回归:407 项通过。
37
+ - 最近完整回归:413 项通过。
38
+ - v0.45.1 已正式交付保守的调用方语法线索及两项边界修正。413 项回归、实际宿主、10 项独立安装专项和四组 CI 通过;npm、Release 与原包的 96,700 字节一致,SHA-256、SHA-512 与清单核验通过。v0.45.0 已发布但存在逐条导入歧义与历史元数据混侧错误,由 0.45.1 替代,不计作安全对齐。
38
39
  - 实际 DSH 工具运行时检查通过;模型响应受控,不是质量成绩。
39
40
  - v0.39.0 独立安装包的真实 Git、服务与预算测试:5 项通过。
40
41
  - v0.39.1 已正式交付,包含 Git 工作区三层依赖能力。四组 CI 通过;npm、Release 与保存原包的 91,502 字节完全一致,SHA-256、SHA-512 和校验清单通过。v0.39.0 不单独计作已核验交付。
package/README.md CHANGED
@@ -67,7 +67,7 @@ DSH 的只读代码审查插件。它比较 Git 或 SVN 中的代码变更,使
67
67
 
68
68
  commit 与 baseRevision/targetRevision 不能同时使用。oldContextPaths 只能用于 Git 历史审查。
69
69
 
70
- 自动上下文和引用方发现是保守的文本导航,不是编译器分析。动态导入、宏、重载、继承和实例方法可能无法解析;找到引用不等于证明函数实际被调用。Git 历史可沿修改前后两侧分别捕获最多三层依赖;SVN 可沿安全的当前工作副本节点取得最多三层依赖,不读取 SVN 历史。已批准正文中的唯一命名声明和唯一 default 导出会标出位置;同名、重导出或跨侧内容不猜测。
70
+ 自动上下文和引用方发现是保守的文本导航,不是编译器分析。动态导入、宏、重载、继承和实例方法可能无法解析;找到引用不等于证明函数实际被调用。历史调用方的命名导入线索逐侧对应相同版本声明,标明 side/revision;缺少该版本的批准正文不回退另一侧。Git 历史可沿修改前后两侧分别捕获最多三层依赖;SVN 可沿安全的当前工作副本节点取得最多三层依赖,不读取 SVN 历史。已批准正文中的唯一命名声明和唯一 default 导出会标出位置;同名、重导出或跨侧内容不猜测。
71
71
 
72
72
  ## 如何读报告
73
73
 
package/RELEASE-NOTES.md CHANGED
@@ -1,21 +1,20 @@
1
- # v0.45.1 发布说明
1
+ # v0.46.0 发布说明
2
2
 
3
- ## 新增:必要调用者绑定
3
+ ## Git 历史调用方的同版本导航
4
4
 
5
- 启用 callerScopePaths 后,已捕获的调用方文件会附带语法绑定:导入行、本地别名、调用行,以及变更文件中唯一声明的所在行。这回答“谁调用了你改的函数”,而不只是“谁提到了这个文件”。
5
+ callerScopePaths 捕获的 JS/TS 命名导入与独立调用位置,可以分别对应 baseline 与 target 中的唯一声明。审查输入标明 side、revision、targetRevision 与 contextSide,调用方正文和声明必须来自同一版本。
6
6
 
7
- 审查输入中的绑定只在调用方正文与变更文件正文都已批准时提供,不新增文件读取。当前仅支持保守的 JS/TS 命名导入与独立调用语法。
7
+ 旧侧只使用已批准 oldText 与 oldRevision;新侧只使用已批准 text 与 revision。缺失或版本不符时不提供线索,不回退另一侧。删除目标的旧声明仍可用于旧侧导航;新增文件不产生旧侧声明线索。
8
8
 
9
- 每一条导入必须独立唯一解析;同文件另一条明确导入不能替歧义导入提供授权。历史捕获、预览、报告与审查输入均不产生这类绑定,避免新旧声明混用。v0.45.1 替代存在这两项边界错误的 v0.45.0。
9
+ ## 保持的限制
10
10
 
11
- ## 限制
11
+ - 不新增扫描或读取权限,两侧继续共享原有路径、正文、数量与时间预算。
12
+ - 每条导入须独立唯一解析;排除、歧义、动态调用和未批准正文不提供绑定。
13
+ - 仅为语法导航,不证明执行可达、动态分派、因果或完整调用覆盖。窗口初始输入仍须按相应版本 catalog 读取原文,线索不是证据。
14
+ - 可选调用线索超出输入预算时会省略并记录回退原因和数量,不挤掉批准正文、不提高预算,也不阻断原本可审查的窗口。
12
15
 
13
- - 调用方正文未获批(例如超出上下文名额)或变更声明有歧义时不给出绑定。
14
- - 历史审查不产生这类调用方绑定,避免把当前侧调用行当作旧版本证据。
15
- - 绑定不证明执行可达、动态分派、重载选择或完整调用覆盖,也不代表全部调用方。
16
+ ## 验证范围
16
17
 
17
- ## 验证
18
+ 覆盖真实 Git 两侧声明位置、删除目标、缺失旧正文、错误版本、添加文件和脏工作区隔离;完整回归、实际宿主、独立安装与四矩阵 CI 作为正式发布条件。模型响应受控,不是效果成绩。
18
19
 
19
- 测试覆盖真实 Git 调用方绑定、未批准调用方拒绝、歧义拒绝、审查输入投递与历史排除。全量回归、实际宿主、独立安装和四组 CI 是正式发布条件,不是模型效果成绩。
20
-
21
- 使用方法见 [README](README.md),维护步骤见 [开发指南](DEVELOPMENT.md)。
20
+ 使用方法见 [README](README.md)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feng0531/dsh-code-review",
3
- "version": "0.45.1",
3
+ "version": "0.46.0",
4
4
  "private": false,
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
@@ -0,0 +1,12 @@
1
+ // Pure annotations over approved snapshot bodies. No capture, reads or coverage.
2
+ export function approvedCallerBindings(input, file) {
3
+ const contexts=input.context??[],candidates=input.callerDiscovery?.candidates??[];
4
+ return candidates.filter(candidate=>{
5
+ if(candidate.targetPath!==file.path||!candidate.bindings?.length)return false;
6
+ if(!input.history)return !candidate.side&&contexts.some(c=>c.path===candidate.fromPath&&typeof c.text==='string');
7
+ const side=candidate.side,revision=side==='old'?input.history.base:side==='new'?input.history.target:undefined;
8
+ if(!revision||candidate.revision!==revision)return false;
9
+ if(side==='old'?(file.rawStatus==='A'||typeof file.left?.text!=='string'):(file.rightExists===false||typeof file.right?.text!=='string'))return false;
10
+ return contexts.some(c=>c.path===candidate.fromPath&&(side==='old'?typeof c.oldText==='string'&&c.oldRevision===revision:typeof c.text==='string'&&c.revision===revision));
11
+ }).map(({fromPath,bindings,side,revision})=>({fromPath,bindings,...(input.history?{side,revision,targetRevision:revision,contextSide:side==='old'?'context-old':'context'}:{})}));
12
+ }
@@ -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,{includeBindings:false});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,right:f.left,rightExists:f.rawStatus!=='A'&&typeof f.left?.text==='string'})):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});}
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
  }
@@ -1,4 +1,5 @@
1
1
  import { CONTEXT_SIDE_NOTICE } from './context-identity.mjs';
2
+ import { approvedCallerBindings } from './caller-binding-input.mjs';
2
3
  import { REQUIREMENT_NOTICE } from './business-requirement.mjs';
3
4
  import { defectGuidance } from './defect-guidance.mjs';
4
5
  import { cppCallSites } from './cpp-call-sites.mjs';
@@ -26,14 +27,20 @@ export function preparePrimaryInput(input, file, { instructions, scope, grouping
26
27
  }
27
28
  const csharpHints = inferCsharpContext(input, file);
28
29
  if (csharpHints.length) { base.csharpContextHints = csharpHints; base.csharpContextNotice = '仅批准context的C#静态类型/方法语法位置;历史输入按contextSide/contextVersion区分正文,无旧正文不回退目标侧。不证明语义绑定或执行可达。歧义不推定,线索不增加审查覆盖。'; }
29
- if (!input.history && input.callerDiscovery) {
30
- const approved = new Set((input.context ?? []).filter(c => typeof c.text === 'string').map(c => c.path));
31
- const callerBindings = input.callerDiscovery.candidates.filter(c => c.targetPath === file.path && c.bindings?.length && approved.has(c.fromPath)).map(({ fromPath, bindings }) => ({ fromPath, bindings }));
32
- if (callerBindings.length) { base.callerBindings = callerBindings; base.callerBindingNotice = '调用方文件的导入行、调用行与目标声明行仅为语法位置线索,来源限于已批准上下文;不证明执行可达、动态分派、重载选择或完整调用覆盖,也不代表全部调用方。'; }
30
+ if (input.callerDiscovery) {
31
+ const callerBindings = approvedCallerBindings(input, file);
32
+ if (callerBindings.length) { base.callerBindings = callerBindings; base.callerBindingNotice = '调用方文件的导入行、调用行与目标声明行仅为语法位置线索,来源限于已批准上下文;历史side/revision要求调用方与目标声明同版本,缺侧不回退。仅导航,窗口中的原文须按相应版本catalog读取;不证明执行可达、动态分派、重载选择或完整调用覆盖,也不代表全部调用方。'; }
33
33
  }
34
34
  let payload = JSON.stringify(base);
35
35
  const measure = value => initialInputBudget({ instructions, input: value }, scope, maxInputBytes);
36
36
  const metadata = {};
37
+ // Optional navigation must not block source review or force extra windows.
38
+ if (base.callerBindings?.length && !measure(payload).fits) {
39
+ metadata.callerBindingFallback = 'input-budget';
40
+ metadata.omittedCallerBindingCount = base.callerBindings.reduce((count,c)=>count+c.bindings.length,0);
41
+ delete base.callerBindings; delete base.callerBindingNotice;
42
+ payload = JSON.stringify(base);
43
+ }
37
44
  let batches, synthesis;
38
45
  const group = grouping?.groups.find(g => g.fileIds.includes(file.id));
39
46
  if (group) {