@feng0531/dsh-code-review 0.51.0 → 0.53.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 +13 -1
- package/CORE-ALIGNMENT-TASKS.md +7 -5
- package/RELEASE-NOTES.md +5 -5
- package/package.json +1 -1
- package/src/caller-candidates.mjs +2 -1
- package/src/class-bindings.mjs +106 -0
- package/src/import-relations.mjs +2 -1
- package/src/interaction-input.mjs +3 -3
- package/src/primary-input.mjs +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,21 @@
|
|
|
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.53.0 — 分组×验证×检索的组合验收
|
|
6
|
+
|
|
7
|
+
同一生命周期验证自动分组、候选反证验证与批准快照检索协同一致,覆盖统计、输入预算上界与验证终态一致。只读批准快照,不改变审查行为或参数。
|
|
8
|
+
|
|
9
|
+
## 0.52.1 — 保守类实例方法导航
|
|
10
|
+
|
|
11
|
+
已批准的唯一普通 class 上,单一 new 实例的独立成员调用对应到唯一成员声明。接收者必须是该实例;字段初始化器、静态(含多修饰符)、继承、泛型、访问器、计算成员、重载、遮蔽、逃逸、自调用和重复实例化拒绝提示;线索与命名空间线索同样在超预算时省略。替代存在接收者逃逸、静态修饰符与字段初始化器幻影方法误判且未参与预算省略的 0.52.0。442 项完整回归、实际宿主、24 项独立安装专项与四组 CI 通过;npm、Release 与原产物 104,817 字节一致,双哈希和清单通过。
|
|
12
|
+
|
|
13
|
+
## 0.52.0 — 保守类实例方法导航(由 0.52.1 替代,未发布)
|
|
14
|
+
|
|
15
|
+
已批准的唯一普通 class 上,单一 new 实例的独立成员调用可对应到唯一成员声明。继承、泛型、访问器、静态、计算成员、重载、遮蔽、逃逸、自调用和重复实例化拒绝提示;不是类型或原型链分析。线索进入上下文关系与调用方发现,历史逐侧隔离。
|
|
16
|
+
|
|
5
17
|
## 0.51.0 — 组合验收
|
|
6
18
|
|
|
7
|
-
新增多轮×窗口×检索×预算的组合端到端验收,校验覆盖统计、每文件轮次、输入预算上界与窗口覆盖一致。只读批准快照,不改变审查行为或参数。
|
|
19
|
+
新增多轮×窗口×检索×预算的组合端到端验收,校验覆盖统计、每文件轮次、输入预算上界与窗口覆盖一致。只读批准快照,不改变审查行为或参数。432 项完整回归、实际宿主、12 项独立安装专项与四组 CI 通过;npm、Release 与原产物 102,433 字节一致,双哈希和清单通过。
|
|
8
20
|
|
|
9
21
|
## 0.50.0 — 生命周期与并发提示细化
|
|
10
22
|
|
package/CORE-ALIGNMENT-TASKS.md
CHANGED
|
@@ -23,19 +23,21 @@
|
|
|
23
23
|
|
|
24
24
|
| 优先级 | 未完成项 | 验收重点 |
|
|
25
25
|
|---|---|---|
|
|
26
|
-
| 1 |
|
|
26
|
+
| 1 | 继承与动态分派上下文 | 命名/默认定义、调用方、历史同版本、命名空间成员与唯一普通类实例方法导航已交付;继承/原型链/动态分派仍待验收 |
|
|
27
27
|
| 2 | 剩余历史来源与旧侧组合 | Git 历史与 SVN 三层链已交付;其他旧侧来源仍待验收 |
|
|
28
28
|
| 3 | 其他旧侧独有来源 | 来源、版本、权限与删除场景保持一致 |
|
|
29
29
|
| 4 | 路径专用指导与引擎版本约束 | 函数式/HDL/智能合约/格式语言、Go/Python/Rust 与 ObjC/Swift/Kotlin/Java/C# 提示已交付;模板与依赖清单引擎版本仍保持通用 |
|
|
30
|
-
| 5 |
|
|
30
|
+
| 5 | 端到端矩阵收尾 | 多轮×窗口×检索与分组×验证均已组合验收;合成与分组的端到端由专项测试覆盖 |
|
|
31
31
|
| 后置 | 真实模型质量评测 | 保存真实输出,人工裁决,分开统计失败和未完成 |
|
|
32
32
|
|
|
33
|
-
Git 工作区、Git 历史两侧与 SVN 当前侧支持安全的三层字面依赖链;手动批准上下文可以作为入口。命名与默认导入支持批准正文中的唯一声明位置。这些能力不是完整语义绑定。调用方语法绑定的 v0.45.0 存在逐条导入歧义与历史发现元数据混侧错误,不计作安全对齐;v0.45.1 已修正并正式交付;历史调用方的同版本语法导航已随 v0.46.0 正式交付;命名空间成员调用导航已随 v0.47.0 正式交付;剩余函数式/HDL/智能合约/格式语言的保守缺陷提示已随 v0.48.0 正式交付;Go/Python/Rust 运行前提与 ArkTS 提示已随 v0.49.0
|
|
33
|
+
Git 工作区、Git 历史两侧与 SVN 当前侧支持安全的三层字面依赖链;手动批准上下文可以作为入口。命名与默认导入支持批准正文中的唯一声明位置。这些能力不是完整语义绑定。调用方语法绑定的 v0.45.0 存在逐条导入歧义与历史发现元数据混侧错误,不计作安全对齐;v0.45.1 已修正并正式交付;历史调用方的同版本语法导航已随 v0.46.0 正式交付;命名空间成员调用导航已随 v0.47.0 正式交付;剩余函数式/HDL/智能合约/格式语言的保守缺陷提示已随 v0.48.0 正式交付;Go/Python/Rust 运行前提与 ArkTS 提示已随 v0.49.0 正式交付;保守唯一普通类实例方法导航已随 v0.52.1 正式交付;继承、原型链与动态分派不猜测。
|
|
34
34
|
|
|
35
35
|
## 当前验证结论
|
|
36
36
|
|
|
37
|
-
- 最近完整回归:
|
|
38
|
-
- v0.
|
|
37
|
+
- 最近完整回归:443 项通过。
|
|
38
|
+
- v0.53.0 已正式交付分组×验证×检索的组合验收。443 项回归、实际宿主与四组 CI 覆盖该组合;不改变审查行为或参数。
|
|
39
|
+
- v0.52.1 已正式交付保守类实例方法导航。442 项回归、实际宿主、24 项独立安装专项和四组 CI 通过;npm、Release 与原包 104,817 字节一致,SHA-256、SHA-512 与清单核验通过。v0.52.0 复核发现接收者逃逸、静态修饰符与字段初始化器幻影方法误判且未参与预算省略,由 0.52.1 替代,未发布不计作交付。
|
|
40
|
+
- v0.51.0 已正式交付多轮×窗口×检索×预算的组合验收。432 项回归、实际宿主、12 项独立安装专项与四组 CI 通过;npm、Release 与原包 102,433 字节一致,双哈希与清单核验通过;不改变审查行为或参数。
|
|
39
41
|
- v0.50.0 已正式交付 ObjC/Swift/Kotlin/Java/C# 生命周期与并发细化提示。431 项回归、实际宿主、9 项独立安装专项和四组 CI 通过;npm、Release 与原包 102,412 字节一致,SHA-256、SHA-512 与清单核验通过。
|
|
40
42
|
- v0.49.0 已正式交付 Go/Python/Rust 运行时提示与 ArkTS 提示。430 项回归、实际宿主、11 项独立安装专项和四组 CI 通过;npm、Release 与原包 101,770 字节一致,SHA-256、SHA-512 与清单核验通过。发布阶段 npm 成功、Release 创建因 registry 瞬时 404 由保存原产物手工补建并复核一致。
|
|
41
43
|
- v0.48.0 已正式交付剩余函数式/HDL/智能合约/格式语言的保守缺陷提示。429 项回归、实际宿主、10 项独立安装专项和四组 CI 通过;npm、Release 与原包 101,539 字节一致,SHA-256、SHA-512 与清单核验通过。
|
package/RELEASE-NOTES.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# v0.
|
|
1
|
+
# v0.53.0 发布说明
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 分组×验证×检索的组合验收
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
在同一审查生命周期内验证自动分组、候选反证验证与批准快照检索协同一致:覆盖统计总和等于审查文件数、每个入口输入不超过预算、产生候选后均完成验证、终态可判定。只读批准快照,不改变审查行为或参数,不新增读取也不放宽预算。
|
|
6
6
|
|
|
7
7
|
## 范围
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- 与多轮×窗口×检索×预算的组合验收共同覆盖主要组合;合成与分组的端到端仍由既有专项与两两组合测试覆盖。
|
|
10
|
+
- 仍不是真实模型质量成绩;真实质量评测为后置项。
|
|
11
11
|
|
|
12
12
|
使用方法见 [README](README.md)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { contextCandidates } from './context-candidates.mjs';
|
|
2
2
|
import { directImportBindings } from './import-bindings.mjs';
|
|
3
3
|
import { namespaceImportBindings } from './namespace-bindings.mjs';
|
|
4
|
+
import { instanceMethodBindings } from './class-bindings.mjs';
|
|
4
5
|
import { importedCallSites, indexStandaloneCalls } from './import-call-sites.mjs';
|
|
5
6
|
import { relativePath } from './content.mjs';
|
|
6
7
|
import { isSecretPath } from './secret-path.mjs';
|
|
@@ -18,7 +19,7 @@ function callerBindings(fromPath, targetPath, scannedText, targetText, resolutio
|
|
|
18
19
|
const candidates = path.posix.extname(target) ? [target] : [target, ...importExtensions.map(ext => target + ext), ...importExtensions.map(ext => target + '/index' + ext)];
|
|
19
20
|
const matches = candidates.filter(candidate => resolutionPaths.has(candidate));
|
|
20
21
|
if (matches.length !== 1 || matches[0] !== targetPath) continue;
|
|
21
|
-
result.push(...directImportBindings(lines, i + 1, importedCallSites(lines, i + 1, callIndex), targetText), ...namespaceImportBindings(lines, i + 1, targetText));
|
|
22
|
+
result.push(...directImportBindings(lines, i + 1, importedCallSites(lines, i + 1, callIndex), targetText), ...namespaceImportBindings(lines, i + 1, targetText), ...instanceMethodBindings(lines, i + 1, targetText));
|
|
22
23
|
}
|
|
23
24
|
return result.slice(0, 20);
|
|
24
25
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Conservative instance-method navigation over approved bodies only. No type or
|
|
2
|
+
// inheritance inference. Shadowing, mutation, accessors, static, nested, computed
|
|
3
|
+
// and duplicate definitions are all refused.
|
|
4
|
+
const BS = String.fromCharCode(96);
|
|
5
|
+
const NL = String.fromCharCode(10);
|
|
6
|
+
function classBody(lines, classLine) {
|
|
7
|
+
const col = lines[classLine - 1].indexOf('{');
|
|
8
|
+
if (col < 0) return null;
|
|
9
|
+
const rest = lines.slice(classLine - 1).join(NL).slice(col + 1);
|
|
10
|
+
let depth = 1, i = 0, out = '';
|
|
11
|
+
while (i < rest.length && depth > 0) {
|
|
12
|
+
const ch = rest[i];
|
|
13
|
+
if (ch === '"' || ch === "'" || ch === BS) {
|
|
14
|
+
const q = ch; out += ch; i++;
|
|
15
|
+
while (i < rest.length && rest[i] !== q) { out += rest[i]; if (rest[i] === '\\' && i + 1 < rest.length) { out += rest[i + 1]; i += 2; continue; } i++; }
|
|
16
|
+
if (i < rest.length) { out += rest[i]; i++; }
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (ch === '/' && rest[i + 1] === '/') { const e = rest.indexOf(NL, i); i = e < 0 ? rest.length : e; continue; }
|
|
20
|
+
if (ch === '{') depth++;
|
|
21
|
+
else if (ch === '}') { depth--; if (depth === 0) return out; }
|
|
22
|
+
out += ch; i++;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function directMethods(body) {
|
|
27
|
+
const out = [];
|
|
28
|
+
let depth = 0;
|
|
29
|
+
for (let i = 0; i < body.length;) {
|
|
30
|
+
const ch = body[i];
|
|
31
|
+
if (ch === '"' || ch === "'" || ch === BS) { const q = ch; i++; while (i < body.length && body[i] !== q) { if (body[i] === '\\') i++; i++; } i++; continue; }
|
|
32
|
+
if (ch === '/' && body[i + 1] === '/') { const e = body.indexOf(NL, i); i = e < 0 ? body.length : e; continue; }
|
|
33
|
+
if (ch === '{') { depth++; i++; continue; }
|
|
34
|
+
if (ch === '}') { depth--; i++; continue; }
|
|
35
|
+
if (depth === 0) {
|
|
36
|
+
const m = /^([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/.exec(body.slice(i));
|
|
37
|
+
if (m) { out.push({ name: m[1], i }); i += m[0].length; continue; }
|
|
38
|
+
}
|
|
39
|
+
i++;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
export function instanceMethodBindings(lines, importLine, targetText) {
|
|
44
|
+
if (!Array.isArray(lines) || lines.length > 20000 || typeof targetText !== 'string') return [];
|
|
45
|
+
const caller = lines.join(NL);
|
|
46
|
+
if (Buffer.byteLength(caller) > 256 * 1024 || caller.includes('/*') || caller.includes(BS) || /\\\r?\n/.test(caller)) return [];
|
|
47
|
+
const imports = /^\s*import\s+\{([^}]+)\}\s+from\s+(['"])([^'"\\]+)\2\s*;?\s*(?:\/\/.*)?$/.exec(lines[importLine - 1]);
|
|
48
|
+
if (!imports) return [];
|
|
49
|
+
const parts = imports[1].split(',').map(p => /^\s*([A-Za-z_$][A-Za-z0-9_$]*)(?:\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*))?\s*$/.exec(p));
|
|
50
|
+
if (parts.some(p => !p)) return [];
|
|
51
|
+
const klass = parts.length === 1 ? parts[0][1] : null;
|
|
52
|
+
if (!klass) return [];
|
|
53
|
+
const local = parts[0][2] ?? klass;
|
|
54
|
+
if (Buffer.byteLength(targetText) > 256 * 1024 || targetText.includes('/*') || targetText.includes(BS) || /\\\r?\n/.test(targetText)) return [];
|
|
55
|
+
const targets = targetText.split(NL);
|
|
56
|
+
let classLine = null;
|
|
57
|
+
for (let i = 0; i < targets.length; i++) {
|
|
58
|
+
const m = /^\s*export\s+class\s+([A-Za-z_$][A-Za-z0-9_$]*)/.exec(targets[i]);
|
|
59
|
+
if (m && m[1] === klass) { if (classLine !== null) return []; classLine = i + 1; }
|
|
60
|
+
}
|
|
61
|
+
if (classLine === null) return [];
|
|
62
|
+
if (/\bextends\b|\bimplements\b/.test(targets[classLine - 1])) return [];
|
|
63
|
+
const braceCol = targets[classLine - 1].indexOf('{');
|
|
64
|
+
if (braceCol >= 0 && /[<>]/.test(targets[classLine - 1].slice(0, braceCol))) return [];
|
|
65
|
+
const body = classBody(targets, classLine);
|
|
66
|
+
if (!body) return [];
|
|
67
|
+
const decls = directMethods(body);
|
|
68
|
+
const names = {};
|
|
69
|
+
for (const d of decls) names[d.name] = (names[d.name] ?? 0) + 1;
|
|
70
|
+
const result = [];
|
|
71
|
+
for (const d of decls) {
|
|
72
|
+
if (names[d.name] !== 1) continue;
|
|
73
|
+
if (d.name === 'constructor') continue;
|
|
74
|
+
const member = d.name;
|
|
75
|
+
const token = new RegExp('(^|[^A-Za-z0-9_$])' + member.replaceAll('$', '\\$') + '(?=[^A-Za-z0-9_$]|$)', 'g');
|
|
76
|
+
const occ = [];
|
|
77
|
+
let m; while ((m = token.exec(body)) !== null) occ.push(m.index);
|
|
78
|
+
if (occ.length !== 1) continue;
|
|
79
|
+
const before = body.slice(0, d.i);
|
|
80
|
+
const spanStart = Math.max(before.lastIndexOf('}'), before.lastIndexOf(';'), before.lastIndexOf('{'));
|
|
81
|
+
const span = before.slice(spanStart + 1);
|
|
82
|
+
if (/\b(?:static|get|set|abstract)\b/.test(span)) continue;
|
|
83
|
+
if (/=\s*$/.test(span)) continue;
|
|
84
|
+
const seg = span.slice(span.lastIndexOf(NL) + 1);
|
|
85
|
+
if (/[=,:.*#\[\]()]|\b(?:new|function|return|yield|case|throw|typeof|void|delete|instanceof)\b/.test(seg)) continue;
|
|
86
|
+
let assign = null, inst = null;
|
|
87
|
+
for (let i = 0; i < lines.length; i++) {
|
|
88
|
+
const a = /^\s*(?:const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*new\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/.exec(lines[i]);
|
|
89
|
+
if (a && a[2] === local) { if (assign !== null) return []; assign = i + 1; inst = a[1]; }
|
|
90
|
+
}
|
|
91
|
+
if (assign === null || inst === member || inst === klass || inst === local) continue;
|
|
92
|
+
const newCount = [...caller.matchAll(new RegExp('(^|[^A-Za-z0-9_$])new\\s+' + local.replaceAll('$', '\\$') + '\\s*\\(', 'g'))].length;
|
|
93
|
+
if (newCount !== 1) continue;
|
|
94
|
+
const instToken = new RegExp('(^|[^A-Za-z0-9_$])' + inst.replaceAll('$', '\\$') + '(?=[^A-Za-z0-9_$]|$)', 'g');
|
|
95
|
+
const instLines = [];
|
|
96
|
+
for (let i = 0; i < lines.length; i++) { const o = [...lines[i].matchAll(instToken)]; if (o.length) { if (o.length !== 1) return []; instLines.push(i + 1); } }
|
|
97
|
+
if (instLines.length !== 2 || !instLines.includes(assign)) continue;
|
|
98
|
+
const callLine = instLines.find(l => l !== assign);
|
|
99
|
+
const callRe = new RegExp('^\\s*(?:await\\s+)?' + inst.replaceAll('$', '\\$') + '\\.([A-Za-z_$][A-Za-z0-9_$]*)\\s*\\([^();"\x27/]*\\)\\s*;?\\s*$');
|
|
100
|
+
const call = callRe.exec(lines[callLine - 1]);
|
|
101
|
+
if (!call || call[1] !== member) continue;
|
|
102
|
+
const methodLine = classLine + (body.slice(0, d.i).split(NL).length - 1);
|
|
103
|
+
result.push({ imported: klass, local, instance: inst, member, newLine: assign, callLine, methodLine, declarationKind: 'instance-method', confidence: 'instance-method-syntax-only' });
|
|
104
|
+
}
|
|
105
|
+
return result.slice(0, 20);
|
|
106
|
+
}
|
package/src/import-relations.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { namespaceImportBindings } from './namespace-bindings.mjs';
|
|
3
|
+
import { instanceMethodBindings } from './class-bindings.mjs';
|
|
3
4
|
import { directImportBindings, namedImportDefinitions, defaultImportDefinition } from './import-bindings.mjs';
|
|
4
5
|
import { importedCallSites, indexStandaloneCalls } from './import-call-sites.mjs';
|
|
5
6
|
|
|
@@ -39,7 +40,7 @@ export function inferImportRelations(files) {
|
|
|
39
40
|
if (matches.length !== 1 || matches[0].eligibility !== 'reviewable' || matches[0].id === file.id) continue;
|
|
40
41
|
const callSites = importedCallSites(lines, i + 1, callIndex);
|
|
41
42
|
const targetText = (side === 'old' ? matches[0].left : matches[0].right)?.text;
|
|
42
|
-
const bindingHints = [...directImportBindings(lines, i + 1, callSites, targetText), ...namespaceImportBindings(lines, i + 1, targetText)].slice(0,20);
|
|
43
|
+
const bindingHints = [...directImportBindings(lines, i + 1, callSites, targetText), ...namespaceImportBindings(lines, i + 1, targetText), ...instanceMethodBindings(lines, i + 1, targetText)].slice(0,20);
|
|
43
44
|
const definitionHints = [...namedImportDefinitions(lines[i], targetText), ...defaultImportDefinition(lines[i], targetText)];
|
|
44
45
|
edges.push({ from: file.id, to: matches[0].id, reason: 'relative-import:' + side + ':L' + (i + 1), side, line: i + 1, specifier: match[2], callSites, bindingHints, definitionHints });
|
|
45
46
|
if (callSites.length && edges.length < 2000) edges.push({ from: file.id, to: matches[0].id, reason: 'imported-call-syntax:' + side + ':L' + callSites.map(c => c.line).join(','), side, callSites });
|
|
@@ -20,12 +20,12 @@ export function prepareInteractionInput(snapshot, group, measure) {
|
|
|
20
20
|
let payload = JSON.stringify({ ...(snapshot.history ? {contextSideNotice:CONTEXT_SIDE_NOTICE} : {}), ...(snapshot.businessRequirement!==undefined?{businessRequirement:snapshot.businessRequirement,requirementNotice:REQUIREMENT_NOTICE}:{}), contextRelations, contextRelationNotice:'Approved-context import/include and call binding hints only; not a call graph, complete caller coverage, semantic binding or causal evidence. Historical contextSide/contextVersion must not be interchanged.', snapshotId: snapshot.id, sourceMode: 'file-interaction', groupId: group.id, files, context: snapshot.context ?? [], rules: snapshot.rules ?? [], notice: 'Only combined defects across at least two listed files; directories are navigation not evidence. Read referenced edit source sides from approved catalog in this generation. Return ordinary findings with fileId of one listed primary and exact original line anchor; attribution.editIds references only that primary. Add interaction:{editRefs:[{fileId,editId}]} including all involved edits across at least two files. This sourceMode overrides single-primary input instructions. Retain original primary anchor and file-qualified edit references. Unsupported conclusions become limitations; no repeated single-file issues. Relations and read coverage are not causal proof.' });
|
|
21
21
|
let budget = measure(payload);
|
|
22
22
|
const metadata={};
|
|
23
|
-
const omitted=contextRelations.reduce((n,e)=>n+(e.bindingHints??[]).filter(h=>h.confidence==='namespace-member-syntax-only').length,0);
|
|
23
|
+
const omitted=contextRelations.reduce((n,e)=>n+(e.bindingHints??[]).filter(h=>h.confidence==='namespace-member-syntax-only'||h.confidence==='instance-method-syntax-only').length,0);
|
|
24
24
|
if(!budget.fits&&omitted){
|
|
25
25
|
const envelope=JSON.parse(payload);
|
|
26
|
-
envelope.contextRelations=envelope.contextRelations.map(e=>({...e,bindingHints:(e.bindingHints??[]).filter(h=>h.confidence!=='namespace-member-syntax-only')}));
|
|
26
|
+
envelope.contextRelations=envelope.contextRelations.map(e=>({...e,bindingHints:(e.bindingHints??[]).filter(h=>h.confidence!=='namespace-member-syntax-only'&&h.confidence!=='instance-method-syntax-only')}));
|
|
27
27
|
payload=JSON.stringify(envelope);budget=measure(payload);
|
|
28
|
-
metadata.
|
|
28
|
+
metadata.optionalBindingFallback='input-budget';metadata.omittedOptionalBindingCount=omitted;
|
|
29
29
|
}
|
|
30
30
|
return { status: budget.fits ? 'ready' : 'blocked', reason: budget.fits ? undefined : 'input-budget', fileIds: [...group.fileIds], payload, budget, ...metadata };
|
|
31
31
|
}
|
package/src/primary-input.mjs
CHANGED
|
@@ -34,11 +34,12 @@ export function preparePrimaryInput(input, file, { instructions, scope, grouping
|
|
|
34
34
|
let payload = JSON.stringify(base);
|
|
35
35
|
const measure = value => initialInputBudget({ instructions, input: value }, scope, maxInputBytes);
|
|
36
36
|
const metadata = {};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
metadata.
|
|
41
|
-
|
|
37
|
+
const optional = h => h.confidence === 'namespace-member-syntax-only' || h.confidence === 'instance-method-syntax-only';
|
|
38
|
+
// Optional member navigation must not block source review or force extra windows.
|
|
39
|
+
if (base.contextRelations?.some(e=>e.bindingHints?.some(optional)) && !measure(payload).fits) {
|
|
40
|
+
metadata.optionalBindingFallback = 'input-budget';
|
|
41
|
+
metadata.omittedOptionalBindingCount = base.contextRelations.reduce((n,e)=>n+(e.bindingHints??[]).filter(optional).length,0);
|
|
42
|
+
base.contextRelations = base.contextRelations.map(e=>({...e,bindingHints:(e.bindingHints??[]).filter(h=>!optional(h))}));
|
|
42
43
|
payload = JSON.stringify(base);
|
|
43
44
|
}
|
|
44
45
|
// Optional navigation must not block source review or force extra windows.
|
|
@@ -60,10 +61,10 @@ export function preparePrimaryInput(input, file, { instructions, scope, grouping
|
|
|
60
61
|
if (measure(grouped).fits) { payload = grouped; metadata.relatedFileIds = relatedFiles.map(f => f.id); }
|
|
61
62
|
else {
|
|
62
63
|
// Optional member hints must not cost the grouped related sources either.
|
|
63
|
-
const groupedOmitted = bindingRelations.reduce((n, e) => n + (e.bindingHints ?? []).filter(
|
|
64
|
-
const trimmedRelations = bindingRelations.map(e => ({ ...e, bindingHints: (e.bindingHints ?? []).filter(h => h
|
|
64
|
+
const groupedOmitted = bindingRelations.reduce((n, e) => n + (e.bindingHints ?? []).filter(optional).length, 0);
|
|
65
|
+
const trimmedRelations = bindingRelations.map(e => ({ ...e, bindingHints: (e.bindingHints ?? []).filter(h => !optional(h)) })).filter(e => e.bindingHints.length || e.definitionHints?.length);
|
|
65
66
|
const trimmed = JSON.stringify({ ...base, relatedFiles, relations, bindingRelations: trimmedRelations, relationNotice: '命名和相对导入关系仅为源码线索,可能被同名局部变量遮蔽,不证明绑定、调用可达或因果;只报告主file的问题,不为relatedFiles重复生成发现。' });
|
|
66
|
-
if (groupedOmitted && measure(trimmed).fits) { payload = trimmed; metadata.relatedFileIds = relatedFiles.map(f => f.id); metadata.
|
|
67
|
+
if (groupedOmitted && measure(trimmed).fits) { payload = trimmed; metadata.relatedFileIds = relatedFiles.map(f => f.id); metadata.optionalBindingFallback = 'input-budget'; metadata.omittedOptionalBindingCount = (metadata.omittedOptionalBindingCount ?? 0) + groupedOmitted; }
|
|
67
68
|
else metadata.groupFallback = 'input-budget';
|
|
68
69
|
}
|
|
69
70
|
}
|