@feng0531/dsh-code-review 0.23.3 → 0.24.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
@@ -1,5 +1,15 @@
1
1
  # 变更记录
2
2
 
3
+ ## 0.24.1
4
+
5
+ - 历史上下文关系分别对基线/目标文本推断,标记contextSide/contextVersion;缺少oldText不回退目标正文。
6
+ - 避免旧import绑定新definition;263测试通过,不增读取调用。
7
+
8
+ ## 0.24.0
9
+
10
+ - 历史旧侧import/include导航未修改上下文,保留oldText/oldRevision/目标revision,context-old检索身份分离并校验来源。
11
+ - 同时保留目标侧正文,原总文件/大小预算与排除边界不变;旧侧独有/删除上下文和调用者搜索尚不支持。
12
+
3
13
  ## 0.23.3
4
14
 
5
15
  - 发布验收实际有界下载npm tarball并逐字节/内容SHA512验证,不再仅相信metadata integrity;tarball404重试耗尽失败。
package/README.md CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  ## 当前状态
6
6
 
7
- 当前代码版本为 0.23.3
7
+ 当前代码版本为 0.24.1
8
+
9
+ 历史自动上下文也导航旧侧import/include,仅未修改且两侧普通blob;上下文oldText/oldRevision和目标revision明确,检索context-old单独身份,仍共用20文件总量及4MiB快照。旧侧独有/删除上下文暂不捕获,调用者覆盖仍不保证。
8
10
 
9
11
  单提交审查直接读取原始commit头的父节点,不受浅克隆遍历边界影响;缺失父树明确失败且不自动fetch,不能把非根提交当空旧侧。
10
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feng0531/dsh-code-review",
3
- "version": "0.23.3",
3
+ "version": "0.24.1",
4
4
  "private": false,
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
@@ -4,6 +4,19 @@ import { inferImportRelations } from './import-relations.mjs';
4
4
  // Context entries are already sent with every primary. These annotations add
5
5
  // explanations only, never reads, group members, or reviewed coverage.
6
6
  export function inferContextRelations(input, file) {
7
+ if (input.history) {
8
+ const relations=[];
9
+ for (const side of ['old','new']) {
10
+ const contexts=(input.context ?? []).filter(c=>side==='new'||typeof c.oldText==='string').map(c=>({...c,text:side==='old'?c.oldText:c.text}));
11
+ const text=side==='old'?file.left.text:file.right.text;
12
+ // Infer each version independently: never bind an old import to target definitions.
13
+ const one={...file,left:{...file.left,text:''},right:{...file.right,text}};
14
+ const version=side==='old'?input.history.base:input.history.target;
15
+ const scoped={...input,history:undefined,context:contexts};
16
+ relations.push(...inferContextRelations(scoped,one).filter(e=>e.side!=='old').map(e=>({...e,side:e.fromPath===file.path?side:side==='old'?'context-old':'context',contextVersion:version,contextSide:side})));
17
+ }
18
+ return relations.slice(0,2000);
19
+ }
7
20
  const paths = new Set(input.files.map(f => f.path));
8
21
  const contexts = (input.context ?? []).filter(c => !paths.has(c.path));
9
22
  const primary = { ...file, id: 'primary' };
@@ -25,10 +25,10 @@ export async function captureGitHistory(cwd, options={}) {
25
25
  if(options.autoContext!==undefined&&typeof options.autoContext!=='boolean')throw new Error('Invalid autoContext option');
26
26
  if(!Array.isArray(rulePaths)||rulePaths.some(p=>contextPaths.includes(p)))throw new Error('Rule paths must be separate from context paths');
27
27
  const context=[], regular=e=>e?.type==='blob'&&['100644','100755'].includes(e.mode);
28
- const captureContext=async name=>{relativePath(name);const changed=files.find(f=>f.path===name);if(changed){if(changed.eligibility!=='reviewable'||changed.rightExists===false)throw new Error('Historical context is excluded blocked or deleted');return {path:name,...changed.right};}const entry=right.get(name);if(!regular(entry))throw new Error('Historical context not a target regular blob');return {path:name,...decode(await git(root,['cat-file','blob',entry.oid],maxFileBytes))};};
28
+ const captureContext=async name=>{relativePath(name);const changed=files.find(f=>f.path===name);if(changed){if(changed.eligibility!=='reviewable'||changed.rightExists===false)throw new Error('Historical context is excluded blocked or deleted');return {path:name,...changed.right};}const entry=right.get(name);if(!regular(entry))throw new Error('Historical context not a target regular blob');const content=decode(await git(root,['cat-file','blob',entry.oid],maxFileBytes));const previous=left.get(name);return {path:name,...content,revision:target,...(regular(previous)?{oldText:decode(await git(root,['cat-file','blob',previous.oid],maxFileBytes)).text,oldRevision:base}:{})};};
29
29
  for(const name of [...new Set(contextPaths)].sort())context.push(await captureContext(name));
30
30
  const rules=await captureProjectRules(root,rulePaths,{signal,files,read:async (_root,name,{maxFileBytes:limit})=>{const entry=right.get(name);if(!regular(entry))throw new Error('Historical rule not a target regular blob');return decode(await git(root,['cat-file','blob',entry.oid],limit));}});
31
31
  let autoContext;
32
- if(options.autoContext===true){const plan=contextCandidates(files,[...right].filter(([,e])=>regular(e)).map(([p])=>p));autoContext={...plan,notice:'Direct target-tree import/include navigation only; no caller coverage, binding or working-tree reads.',target,candidates:plan.candidates.filter(c=>!contextPaths.includes(c.path)&&!rulePaths.includes(c.path)),capturedPaths:[]};for(const c of autoContext.candidates){signal?.throwIfAborted();if(context.length>=maxContextFiles){c.status='blocked';c.reason='Context file limit exceeded';continue;}try{context.push(await captureContext(c.path));c.status='captured';autoContext.capturedPaths.push(c.path);}catch(error){signal?.throwIfAborted();c.status='blocked';c.reason=error.message;}}}
32
+ if(options.autoContext===true){const plan=contextCandidates(files,[...right].filter(([,e])=>regular(e)).map(([p])=>p));const oldPlan=contextCandidates(files.map(f=>f.eligibility==='reviewable'?{...f,right:f.left,rightExists:true}:f),[...left].filter(([p,e])=>regular(e)&&regular(right.get(p))&&!names.includes(p)).map(([p])=>p));const combined=new Map(plan.candidates.map(c=>[c.path,{...c,sourceSides:['new']}]));for(const c of oldPlan.candidates){if(combined.has(c.path))combined.get(c.path).sourceSides.push('old');else combined.set(c.path,{...c,sourceSides:['old']});}plan.candidates=[...combined.values()].sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);plan.truncated ||= oldPlan.truncated;autoContext={...plan,notice:'Direct baseline/target-tree import/include navigation only; no caller coverage, binding or working-tree reads.',target,base,candidates:plan.candidates.filter(c=>!contextPaths.includes(c.path)&&!rulePaths.includes(c.path)),capturedPaths:[]};for(const c of autoContext.candidates){signal?.throwIfAborted();if(context.length>=maxContextFiles){c.status='blocked';c.reason='Context file limit exceeded';continue;}try{context.push(await captureContext(c.path));c.status='captured';autoContext.capturedPaths.push(c.path);}catch(error){signal?.throwIfAborted();c.status='blocked';c.reason=error.message;}}}
33
33
  const snapshot={context,...(rules.length?{rules}:{}),...(autoContext?{autoContext}:{}),schemaVersion:1,vcs:'git',root,baseline:base,history:{mode:commit!==undefined?'commit':'range',base,target,semantics:'exact-tree-endpoints; renames represented as add/delete'},files};const serialized=JSON.stringify(snapshot);if(Buffer.byteLength(serialized)>maxSnapshotBytes)throw new Error('Historical snapshot limit exceeded');return {...snapshot,id:hash(serialized)};
34
34
  }
@@ -16,7 +16,7 @@ export function preparePrimaryInput(input, file, { instructions, scope, grouping
16
16
  const contextRelations = inferContextRelations(input, file);
17
17
  if (contextRelations.length) {
18
18
  base.contextRelations = contextRelations;
19
- base.contextRelationNotice = '导入/include/命名与调用位置仅为语法线索;可能被宏、构建搜索路径或同名局部变量影响,不证明绑定、可达性或因果。context为批准当前文本,不是old历史内容。';
19
+ base.contextRelationNotice = '导入/include/命名与调用位置仅为语法线索;可能被宏、构建搜索路径或同名局部变量影响,不证明绑定、可达性或因果。context.text为批准当前/目标侧文本;仅明确oldText/oldRevision字段是历史旧侧,禁止互换。';
20
20
  }
21
21
  if (/\.(?:c|cc|cpp|cxx|h|hh|hpp|hxx)$/.test(file.path)) {
22
22
  const calls = ['old','new'].flatMap(side => cppCallSites((side === 'old' ? file.left : file.right).text).map(site => ({ ...site, side })));
@@ -27,6 +27,10 @@ export function createRetrievalScope(snapshot, options) {
27
27
  continue;
28
28
  }
29
29
  add(item.path, 'context', item.text);
30
+ if (item.oldText !== undefined) {
31
+ if (!snapshot.history || item.oldRevision !== snapshot.history.base || item.revision !== snapshot.history.target || typeof item.oldText !== 'string') throw new Error('Invalid historical context provenance');
32
+ add(item.path, 'context-old', item.oldText);
33
+ }
30
34
  }
31
35
  const reader = createSnapshotReader(snapshot.id, entries, options);
32
36
  return Object.freeze({ ...reader, catalog: () => structuredClone(catalog) });