@feng0531/dsh-code-review 0.19.0 → 0.19.2
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 +10 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/context-candidates.mjs +24 -0
- package/src/snapshot.mjs +18 -1
- package/src/tracked-context.mjs +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.19.2
|
|
4
|
+
|
|
5
|
+
- 低层autoContext接Git有界跟踪regular索引及必要上下文安全捕获,双读/索引一致性、hash及总限额复用,阻塞元数据可见。
|
|
6
|
+
- untracked/excluded不再进入;真实Git与全量245通过。入口/SVN/调用者搜索未完成,保持明确限制。
|
|
7
|
+
|
|
8
|
+
## 0.19.1
|
|
9
|
+
|
|
10
|
+
- 新增只读纯上下文候选导航:唯一tracked未修改直接相对import/include,排除歧义/条件宏/不支持文本/已选变更,20候选截断可见。
|
|
11
|
+
- 尚未接捕获/入口,不声称上下文检索完成;全量244项通过。
|
|
12
|
+
|
|
3
13
|
## 0.19.0
|
|
4
14
|
|
|
5
15
|
- 注册入口启用有界业务分组,预检计一次调用并披露动态综合执行前未知/初始上界,报告展示假设状态。
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 当前状态
|
|
6
6
|
|
|
7
|
-
当前代码版本为 0.19.
|
|
7
|
+
当前代码版本为 0.19.2,包含有界覆盖预检、显式项目规则及候选证据与反证复核;发布状态以 npm 和 GitHub Release 为准。已通过真实临时 Git/SVN 仓库测试,以及隔离进程中的真实 DSH ToolRuntime 注册验证。当前开发会话的实际预览已返回0.6.0的plan/rules字段,空变更预览与默认关闭外发已验证;已进行经宿主审批的六次小规模真实试跑,累计12次模型调用;四组合成正负例与历史修复对照符合预期,历史引入方向检出候选但引用复核未完成(partial)。不是独立盲测或整体质量证明。详情见 [试跑结果](evaluation/PILOT-RESULTS.md)。
|
|
8
8
|
|
|
9
9
|
## 工作区下多个子仓库(0.11.0)
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {relativePath} from './content.mjs';
|
|
3
|
+
// Navigation only over caller-supplied tracked regular paths; no reads or authority.
|
|
4
|
+
export function contextCandidates(files, trackedPaths, {maxCandidates=20}={}) {
|
|
5
|
+
if(!Array.isArray(files)||files.length>200||!Array.isArray(trackedPaths)||trackedPaths.length>10000||!Number.isSafeInteger(maxCandidates)||maxCandidates<1||maxCandidates>20)throw new Error('Invalid context candidate limit');
|
|
6
|
+
const tracked=new Set();for(const p of trackedPaths){relativePath(p);if(tracked.has(p))throw new Error('Duplicate tracked path');tracked.add(p);}
|
|
7
|
+
const changed=new Set(files.flatMap(f=>[f.path,...(f.oldPath?[f.oldPath]:[])]));
|
|
8
|
+
const found=new Map();const add=(p,from,reason)=>{if(!tracked.has(p)||changed.has(p))return;if(!found.has(p))found.set(p,{path:p,fromPaths:[],reasons:[],confidence:'navigation-only'});const v=found.get(p);if(!v.fromPaths.includes(from))v.fromPaths.push(from);if(!v.reasons.includes(reason))v.reasons.push(reason);};
|
|
9
|
+
for(const f of files){
|
|
10
|
+
relativePath(f.path);if(f.eligibility!=='reviewable'||f.rightExists===false)continue;
|
|
11
|
+
const text=f.right?.text;if(typeof text!=='string'||Buffer.byteLength(text)>256*1024)continue;
|
|
12
|
+
if(text.includes('/*')||text.includes(String.fromCharCode(96))||text.includes('R"')||/\\\r?\n/.test(text))continue;
|
|
13
|
+
const cpp=/\.(c|cc|cpp|cxx|h|hh|hpp|hxx)$/i.test(f.path),js=/\.(mjs|cjs|js|jsx|ts|tsx)$/i.test(f.path);
|
|
14
|
+
if(cpp&&/^\s*#\s*(if|ifdef|ifndef|elif|else|define|undef)\b/m.test(text))continue;
|
|
15
|
+
for(const line of text.split('\n')){
|
|
16
|
+
const match=cpp?/^\s*#\s*include\s*"([^"\\]+)"\s*(?:\/\/.*)?$/.exec(line):js?/^\s*(?:import\s+(?:(?:[^'";]+)\s+from\s+)?|export\s+(?:[^'";]+)\s+from\s+)(['"])([^'"\\]+)\1\s*;?\s*(?:\/\/.*)?$/.exec(line):null;
|
|
17
|
+
if(!match)continue;const spec=cpp?match[1]:match[2];if(!cpp&&!/^\.\.?\//.test(spec))continue;
|
|
18
|
+
const target=path.posix.normalize(path.posix.join(path.posix.dirname(f.path),spec));try{relativePath(target);}catch{continue;}
|
|
19
|
+
const candidates=js&&!path.posix.extname(target)?[target,...['.mjs','.cjs','.js','.jsx','.ts','.tsx'].flatMap(e=>[target+e,target+'/index'+e])]:[target];
|
|
20
|
+
const matches=candidates.filter(p=>tracked.has(p));if(matches.length===1)add(matches[0],f.path,cpp?'literal-relative-include':'literal-relative-import');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const candidates=[...found.values()].sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);return {candidates:candidates.slice(0,maxCandidates),truncated:candidates.length>maxCandidates,notice:'Only direct current-side import/include navigation. Not caller coverage, semantic binding or historical context. No untracked, recursive or filesystem search.'};
|
|
24
|
+
}
|
package/src/snapshot.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { gitContextIndex } from './tracked-context.mjs';
|
|
2
|
+
import { contextCandidates } from './context-candidates.mjs';
|
|
1
3
|
import { lstat, realpath } from 'node:fs/promises';
|
|
2
4
|
import path from 'node:path';
|
|
3
5
|
import { captureGit } from './git.mjs';
|
|
@@ -31,6 +33,20 @@ export async function captureSnapshot(cwd, options = {}) {
|
|
|
31
33
|
if (!Array.isArray(rulePaths) || rulePaths.some(name => contextPaths.includes(name))) throw new Error('Rule paths must be separate from context paths');
|
|
32
34
|
const rules = await captureProjectRules(snapshot.root, rulePaths, { signal: options.signal, files: snapshot.files });
|
|
33
35
|
const context = [];
|
|
36
|
+
let autoContext, index;
|
|
37
|
+
if (options.autoContext === true) {
|
|
38
|
+
if (type !== 'git') throw new Error('Automatic SVN context is not yet supported');
|
|
39
|
+
index = await gitContextIndex(snapshot.root, options);
|
|
40
|
+
const plan = contextCandidates(snapshot.files, index.paths);
|
|
41
|
+
autoContext = { ...plan, candidates: plan.candidates.filter(c => !contextPaths.includes(c.path) && !rulePaths.includes(c.path)), capturedPaths: [] };
|
|
42
|
+
for (const candidate of autoContext.candidates) {
|
|
43
|
+
if (context.length + new Set(contextPaths).size >= maxContextFiles) { candidate.status = 'blocked'; candidate.reason = 'Context file limit exceeded'; continue; }
|
|
44
|
+
try {
|
|
45
|
+
context.push({ path: candidate.path, ...await readLocal(snapshot.root, candidate.path, options) });
|
|
46
|
+
candidate.status = 'captured'; autoContext.capturedPaths.push(candidate.path);
|
|
47
|
+
} catch (error) { options.signal?.throwIfAborted(); candidate.status = 'blocked'; candidate.reason = error.message; }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
34
50
|
for (const name of [...new Set(contextPaths)].sort()) {
|
|
35
51
|
const changed = snapshot.files.find(f => f.path === name);
|
|
36
52
|
if (changed) {
|
|
@@ -42,9 +58,10 @@ export async function captureSnapshot(cwd, options = {}) {
|
|
|
42
58
|
// Re-capture verifies both selected sides while collecting explicit context.
|
|
43
59
|
if ((await capture(cwd, options)).id !== snapshot.id) throw new Error('Snapshot changed while collecting context');
|
|
44
60
|
for (const item of context) if ((await readLocal(snapshot.root, item.path, options)).hash !== item.hash) throw new Error('Context changed during capture');
|
|
61
|
+
if (index && (await gitContextIndex(snapshot.root, options)).fingerprint !== index.fingerprint) throw new Error('Tracked context index changed during capture');
|
|
45
62
|
const verifiedRules = await captureProjectRules(snapshot.root, rulePaths, { signal: options.signal, files: snapshot.files });
|
|
46
63
|
if (JSON.stringify(verifiedRules) !== JSON.stringify(rules)) throw new Error('Rules changed during capture');
|
|
47
|
-
const result = { ...snapshot, context, ...(rules.length ? { rules } : {}) };
|
|
64
|
+
const result = { ...snapshot, context, ...(autoContext ? { autoContext } : {}), ...(rules.length ? { rules } : {}) };
|
|
48
65
|
delete result.id;
|
|
49
66
|
const serialized = JSON.stringify(result);
|
|
50
67
|
if (Buffer.byteLength(serialized) > maxSnapshotBytes) throw new Error('Snapshot size limit exceeded');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { checked } from './process.mjs';
|
|
2
|
+
import { relativePath } from './content.mjs';
|
|
3
|
+
// Index metadata only; never invokes network, diff drivers or source reads.
|
|
4
|
+
export async function gitContextIndex(root, options = {}) {
|
|
5
|
+
const bytes = await checked('git', ['--no-optional-locks','-c','core.fsmonitor=false','-c','core.untrackedCache=false','ls-files','--stage','-z'], {...options,cwd:root,maxBytes:1024*1024});
|
|
6
|
+
const records = new TextDecoder('utf-8',{fatal:true}).decode(bytes).split('\0').filter(Boolean);
|
|
7
|
+
if(records.length>10000)throw new Error('Tracked context index limit exceeded');
|
|
8
|
+
const entries=new Map();
|
|
9
|
+
for(const record of records){
|
|
10
|
+
const match=/^(\d{6}) ([a-f0-9]+) ([0-3])\t(.+)$/.exec(record);if(!match)throw new Error('Invalid Git context index');
|
|
11
|
+
const name=relativePath(match[4]);const valid=match[3]==='0'&&['100644','100755'].includes(match[1]);
|
|
12
|
+
if(entries.has(name))entries.set(name,false);else entries.set(name,valid);
|
|
13
|
+
}
|
|
14
|
+
return {paths:[...entries].filter(([,valid])=>valid).map(([name])=>name).sort(),fingerprint:bytes.toString('base64')};
|
|
15
|
+
}
|