@feng0531/dsh-code-review 0.22.0 → 0.22.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,10 @@
1
1
  # 变更记录
2
2
 
3
+ ## 0.22.1
4
+
5
+ - 历史显式/自动直接导入上下文仅捕获目标树普通blob,20总量/原文件与快照预算、显式去重、阻塞可见;不读工作树。
6
+ - 未选/阻塞/删除变更不可重新作为上下文,历史规则继续拒绝。真实Git隔离/去重/哈希/限额及全量252、宿主离线通过。
7
+
3
8
  ## 0.22.0
4
9
 
5
10
  - 工具注册Git指定提交/区间,预览及JSON/Markdown报告展示解析端点和精确树语义,原快照哈希/Agent再验证保留。
package/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  ## 当前状态
6
6
 
7
- 当前代码版本为 0.22.0
7
+ 当前代码版本为 0.22.1
8
8
 
9
- Git历史审查预览传 `commit`(相对唯一父提交,根提交空旧侧;merge需区间),或 `baseRevision` 与 `targetRevision`(精确端点树差异,非merge-base);两类互斥。预览/报告显示解析对象ID,执行复捕获hash验证引用变化。历史上下文/规则暂明确拒绝,不混工作树,SVN不冒充此语义。
9
+ Git历史审查预览传 `commit`(相对唯一父提交,根提交空旧侧;merge需区间),或 `baseRevision` 与 `targetRevision`(精确端点树差异,非merge-base);两类互斥。预览/报告显示解析对象ID,执行复捕获hash验证引用变化。历史显式/自动直接导入上下文只捕获目标提交树普通blob,默认20总量且排除变更不能再进入;历史规则暂明确拒绝,不混工作树,SVN不冒充此语义。
10
10
 
11
11
  工具对错误行锚可进行一次同文件同侧唯一完整原文纠正,须重叠原引用精确编辑;原候选/状态保留JSON,Markdown提示。纠正后仍校验窗口、跨文件编辑及生成读取证据,不增调用、不猜歧义或跨文件迁移,不证明缺陷或因果。
12
12
 
package/index.mjs CHANGED
@@ -12,7 +12,7 @@ export function apply(ctx, config = {}) {
12
12
  };
13
13
  ctx.tools.register({
14
14
  name: 'code_review_preview', description: 'Preview Git/SVN working-copy or Git commit/range review scope and model destination without sending source to a model. repositoryPath selects an explicit child repository relative to session workspace; file paths are repository-relative. Preview each repository separately. Show the preview before execution.',
15
- parameters: { type: 'object', additionalProperties: false, properties: { commit: { type: 'string', description: 'Git commit versus sole parent; root empty, merge requires range.' }, baseRevision: { type: 'string', description: 'Git old endpoint; requires targetRevision, mutually exclusive with commit.' }, targetRevision: { type: 'string', description: 'Git new endpoint, exact tree endpoints not merge-base. Historical context/rules rejected.' }, repositoryPath: { type: 'string', description: 'Workspace-relative explicit child Git/SVN repository root, forward slashes; omit for current repository.' }, selectedPaths: { type: 'array', items: { type: 'string' }, maxItems: 200 }, autoContext: { type: 'boolean', description: 'Opt in to bounded direct tracked Git/SVN import/include context captured and shown in preview.' }, contextPaths: { type: 'array', items: { type: 'string' }, maxItems: 20 }, rulePaths: { type: 'array', items: { type: 'string' }, maxItems: 4 } } },
15
+ parameters: { type: 'object', additionalProperties: false, properties: { commit: { type: 'string', description: 'Git commit versus sole parent; root empty, merge requires range.' }, baseRevision: { type: 'string', description: 'Git old endpoint; requires targetRevision, mutually exclusive with commit.' }, targetRevision: { type: 'string', description: 'Git new endpoint, exact tree endpoints not merge-base. Context is captured from target tree only; historical rules rejected.' }, repositoryPath: { type: 'string', description: 'Workspace-relative explicit child Git/SVN repository root, forward slashes; omit for current repository.' }, selectedPaths: { type: 'array', items: { type: 'string' }, maxItems: 200 }, autoContext: { type: 'boolean', description: 'Opt in to bounded direct tracked Git/SVN import/include context captured and shown in preview.' }, contextPaths: { type: 'array', items: { type: 'string' }, maxItems: 20 }, rulePaths: { type: 'array', items: { type: 'string' }, maxItems: 4 } } },
16
16
  output,
17
17
  async execute(args, exec) { const preview = await service.preview(args, exec); return { json: JSON.stringify(preview), markdown: '' }; },
18
18
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feng0531/dsh-code-review",
3
- "version": "0.22.0",
3
+ "version": "0.22.1",
4
4
  "private": false,
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
@@ -1,3 +1,4 @@
1
+ import {contextCandidates} from './context-candidates.mjs';
1
2
  import {realpath} from 'node:fs/promises';
2
3
  import {checked} from './process.mjs';
3
4
  import {hash,decode,relativePath} from './content.mjs';
@@ -17,5 +18,13 @@ export async function captureGitHistory(cwd, options={}) {
17
18
  const left=await tree(base),right=await tree(target),names=[...new Set([...left.keys(),...right.keys()])].filter(p=>JSON.stringify(left.get(p))!==JSON.stringify(right.get(p))).sort();
18
19
  if(names.length>maxFiles)throw new Error('Historical change count limit exceeded');if(selectedPaths?.some(p=>!names.includes(p)))throw new Error('Selected path is not a historical change');
19
20
  const files=[];for(const name of names){signal?.throwIfAborted();const l=left.get(name),r=right.get(name),item={id:hash(name),path:name,rawStatus:!l?'A':!r?'D':'M',eligibility:'reviewable',properties:[]};files.push(item);if(selectedPaths&&!selectedPaths.includes(name)){item.eligibility='excluded';item.reason='Not explicitly selected';continue;}try{for(const e of [l,r].filter(Boolean))if(e.type!=='blob'||!['100644','100755'].includes(e.mode))throw new Error('Historical link or submodule excluded');const read=async e=>decode(e?await git(root,['cat-file','blob',e.oid],maxFileBytes):Buffer.alloc(0));item.left=await read(l);item.right=await read(r);item.rightExists=!!r;if(l&&r&&item.left.hash===item.right.hash){item.eligibility='excluded';item.reason='Mode-only historical change not analyzed';}}catch(error){signal?.throwIfAborted();item.eligibility='blocked';item.reason=error.message;delete item.left;delete item.right;}if(Buffer.byteLength(JSON.stringify(files))>maxSnapshotBytes)throw new Error('Historical snapshot limit exceeded');}
20
- const snapshot={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)};
21
+ const contextPaths=options.contextPaths ?? [], maxContextFiles=options.maxContextFiles ?? 20;
22
+ if(!Array.isArray(contextPaths)||!Number.isSafeInteger(maxContextFiles)||maxContextFiles<1||maxContextFiles>20||contextPaths.length>maxContextFiles)throw new Error('Historical context file limit exceeded');
23
+ if(options.autoContext!==undefined&&typeof options.autoContext!=='boolean')throw new Error('Invalid autoContext option');
24
+ const context=[], regular=e=>e?.type==='blob'&&['100644','100755'].includes(e.mode);
25
+ 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))};};
26
+ for(const name of [...new Set(contextPaths)].sort())context.push(await captureContext(name));
27
+ let autoContext;
28
+ 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)),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;}}}
29
+ const snapshot={context,...(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)};
21
30
  }
package/src/snapshot.mjs CHANGED
@@ -32,9 +32,8 @@ export async function captureSnapshot(cwd, options = {}) {
32
32
  const type = await detectVcs(cwd);
33
33
  if (['commit','baseRevision','targetRevision'].some(k => options[k] !== undefined)) {
34
34
  if (type !== 'git') throw new Error('Git revision review is unsupported on SVN');
35
- if (!Array.isArray(rulePaths) || contextPaths.length || rulePaths.length || options.autoContext === true) throw new Error('Historical context/rules not yet supported; do not mix working-tree sources');
36
- const result = { ...await captureGitHistory(cwd, options), context: [] }; delete result.id;
37
- return { ...result, id: hash(JSON.stringify(result)) };
35
+ if (!Array.isArray(rulePaths) || rulePaths.length) throw new Error('Historical rules not yet supported; do not mix working-tree sources');
36
+ return captureGitHistory(cwd, options);
38
37
  }
39
38
  const capture = type === 'git' ? captureGit : captureSvn;
40
39
  const snapshot = await capture(cwd, options);