@feng0531/dsh-code-review 0.41.0 → 0.42.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.42.0 — SVN 安全依赖链
6
+
7
+ SVN 工作副本的自动上下文支持最多三层依赖及手动入口。精确路径探测替代全上下文索引;读取前检查节点与祖先身份,捕获后重新核验。歧义、未跟踪依赖及不安全属性不取得正文权限,各层共享数量、字节与时间上限。
8
+
5
9
  ## 0.41.0 — Git 历史两侧依赖链
6
10
 
7
- 启用 autoContext 后,提交和区间审查沿固定修改前后版本的静态依赖读取最多三层上下文。两侧共享预算;旧侧专用上下文保持旧侧权限,不读取工作区,不恢复排除或阻断内容。SVN 仍为直接导航。
11
+ 启用 autoContext 后,提交和区间审查沿固定修改前后版本的静态依赖读取最多三层上下文。两侧共享预算;旧侧专用上下文保持旧侧权限,不读取工作区,不恢复排除或阻断内容。SVN 仍为直接导航。已正式发布;393 项回归、实际宿主、独立安装及四组 CI 通过,公开安装包与原包一致。
8
12
 
9
13
  ## 0.40.0 — 手动上下文的依赖导航
10
14
 
@@ -24,21 +24,22 @@
24
24
  | 优先级 | 未完成项 | 验收重点 |
25
25
  |---|---|---|
26
26
  | 1 | 必要语义调用者、定义、别名及实例上下文 | 歧义不能猜测;不扩大到全仓无界扫描 |
27
- | 2 | Git 历史两侧及 SVN 的依赖链 | 旧新版本分别捕获;共享读取上限;排除文件不能重新进入 |
27
+ | 2 | SVN 的有界依赖链及剩余历史来源场景 | Git 历史三层链已交付;仍需更完整来源组合验收 |
28
28
  | 3 | 其他旧侧独有来源 | 来源、版本、权限与删除场景保持一致 |
29
29
  | 4 | 剩余语言与路径专用指导 | 只提示具体缺陷和反证;不照搬风格禁令 |
30
30
  | 5 | 剩余预算组合与端到端矩阵 | 分组、窗口、检索、多次审查的终态与覆盖一致 |
31
31
  | 后置 | 真实模型质量评测 | 保存真实输出,人工裁决,分开统计失败和未完成 |
32
32
 
33
- Git 工作区三层字面依赖链已实现并通过测试。这解决了“导入转发文件后,未取得后续实现正文”的部分问题,但仍不是完整定义绑定。本批还补齐了手动上下文作为链入口,真实 Git、服务与独立安装四项测试通过,388 项全量回归和实际宿主检查通过,已随 v0.40.0 正式交付。Git 历史三层链本批已实现,5 项独立安装验收及 393 项回归、实际宿主检查通过,待固定版本正式发布;SVN 尚未具备同样的链式能力。
33
+ Git 工作区三层字面依赖链已实现并通过测试。这解决了“导入转发文件后,未取得后续实现正文”的部分问题,但仍不是完整定义绑定。本批还补齐了手动上下文作为链入口,真实 Git、服务与独立安装四项测试通过,388 项全量回归和实际宿主检查通过,已随 v0.40.0 正式交付。Git 历史三层链本批已实现,5 项独立安装验收及 393 项回归、实际宿主检查通过,已随 v0.41.0 正式交付;SVN 安全三层链本批已实现,6 项独立安装验收及节点属性变化专项通过,400 项完整回归与实际宿主检查通过,待正式发布。
34
34
 
35
35
  ## 当前验证结论
36
36
 
37
- - 最近完整回归:393 项通过。
37
+ - 最近完整回归:400 项通过。
38
38
  - 实际 DSH 工具运行时检查通过;模型响应受控,不是质量成绩。
39
39
  - v0.39.0 独立安装包的真实 Git、服务与预算测试:5 项通过。
40
40
  - v0.39.1 已正式交付,包含 Git 工作区三层依赖能力。四组 CI 通过;npm、Release 与保存原包的 91,502 字节完全一致,SHA-256、SHA-512 和校验清单通过。v0.39.0 不单独计作已核验交付。
41
- - v0.40.0 已正式交付手动上下文依赖导航。四组 CI 与唯一发布通过,npm、Release 与保存原包的 91,901 字节完全一致,双哈希及校验清单通过。准确语义绑定、历史与 SVN 依赖链等差距仍保留。
41
+ - v0.40.0 已正式交付手动上下文依赖导航。四组 CI 与唯一发布通过,npm、Release 与保存原包的 91,901 字节完全一致,双哈希及校验清单通过。准确语义绑定、SVN 依赖链和剩余旧侧来源等差距仍保留。
42
+ - v0.41.0 已正式交付 Git 历史两侧三层链。393 项回归、实际宿主、独立安装与四组 CI 通过;npm、Release 与原包的 93,202 字节一致,双哈希及校验清单通过。
42
43
 
43
44
  ## 不在范围内
44
45
 
package/DEVELOPMENT.md CHANGED
@@ -19,6 +19,7 @@
19
19
  - 每个主文件的模型与检索阶段共享默认 120 秒;关联综合沿用相关主文件的生命周期,不因切换阶段重新计时。
20
20
  - 每阶段最多三轮检索;全局最多 50 次读取请求,返回内容累计 256 KiB,单次最多 64 KiB,读取最多 200 行。
21
21
  - Git 工作区依赖链最多三层、512 个精确路径查询、20 个候选、手动入口与自动依赖共享 1 MiB 正文;文件读取与最终复核共享 30 秒。
22
+ - SVN 依赖链最多三层,以精确本地路径和安全正常节点捕获;512 路径、20 候选和上下文、1 MiB 来源及 30 秒含核验,拒绝切换、外部与特殊属性节点。
22
23
  - Git 历史依赖链同样最多三层,两侧共享 512 个路径、20 个候选和上下文、1 MiB 正文及 30 秒;只读取固定对象。
23
24
  - 引用方发现最多四个不重叠的非根目录;128 个候选正文共 1 MiB,目录索引最多 256 条/64 KiB,竞争路径最多 512 个。发现与复核共享 30 秒。
24
25
 
package/README.md CHANGED
@@ -58,7 +58,7 @@ DSH 的只读代码审查插件。它比较 Git 或 SVN 中的代码变更,使
58
58
  | selectedPaths | 只审查选中的变更文件 | ["src/api.ts"] |
59
59
  | repositoryPath | 指定工作区内的子仓库 | "project-a" |
60
60
  | contextPaths | 手动加入理解代码所需的文件 | ["src/types.ts"] |
61
- | autoContext | 从变更及手动上下文寻找依赖;Git 工作区最多沿三层依赖链读取 | true |
61
+ | autoContext | 从变更及手动上下文寻找依赖;Git 和 SVN 最多沿三层依赖链读取 | true |
62
62
  | callerScopePaths | 在指定目录中寻找引用变更文件的代码 | ["src/clients"] |
63
63
  | oldContextPaths | Git 历史审查中,只加入旧版本的上下文 | ["src/removed-helper.ts"] |
64
64
  | rulePaths | 加入项目审查规则,最多四个 UTF-8 Markdown 或文本文件 | ["review-rules.md"] |
@@ -67,7 +67,7 @@ DSH 的只读代码审查插件。它比较 Git 或 SVN 中的代码变更,使
67
67
 
68
68
  commit 与 baseRevision/targetRevision 不能同时使用。oldContextPaths 只能用于 Git 历史审查。
69
69
 
70
- 自动上下文和引用方发现是保守的文本导航,不是编译器分析。动态导入、宏、重载、继承和实例方法可能无法解析;找到引用不等于证明函数实际被调用。Git 历史可沿修改前后两侧分别捕获最多三层依赖;SVN 目前仍限直接依赖。
70
+ 自动上下文和引用方发现是保守的文本导航,不是编译器分析。动态导入、宏、重载、继承和实例方法可能无法解析;找到引用不等于证明函数实际被调用。Git 历史可沿修改前后两侧分别捕获最多三层依赖;SVN 可沿安全的当前工作副本节点取得最多三层依赖,不读取 SVN 历史。
71
71
 
72
72
  ## 如何读报告
73
73
 
package/RELEASE-NOTES.md CHANGED
@@ -1,22 +1,22 @@
1
- # v0.41.0 发布说明
1
+ # v0.42.0 发布说明
2
2
 
3
- ## 新增:Git 历史依赖链
3
+ ## 新增:SVN 有界依赖链
4
4
 
5
- 审查 Git 提交或明确版本区间时,启用 autoContext 可以从修改前后源码分别沿静态导入和包含关系取得最多三层必要上下文。手动指定的历史上下文也可作为入口。
5
+ SVN 工作副本启用 autoContext 后,可以从变更文件及手动上下文沿静态导入/包含关系读取最多三层必要实现。无需新参数,不启用时不扩大上下文。
6
6
 
7
- 所有正文来自固定 Git 对象,不读取当前工作区。两侧共享 512 个路径查询、20 个候选及上下文名额、1 MiB 来源正文和 30 秒捕获上限,不因切换版本重新计时。
7
+ 依赖发现改为精确本地路径探测,不枚举全工作副本上下文。目录、链接和未跟踪路径也会参与歧义判断,但不会因此获得正文读取权限。
8
8
 
9
- ## 版本与证据边界
9
+ ## 安全与限制
10
10
 
11
- - oldContextPaths 仍只批准旧侧,不自动增加新侧正文权限。
12
- - 删除文件复用原批准变更的旧侧身份,不重复作为上下文加入。
13
- - 排除、阻断或无法确定的依赖不会被恢复为可读证据。
14
- - 自动取得的普通历史上下文沿用原有两侧正文规则;各版本的依赖分别解析,不用工作区猜测。
11
+ - 每个自动依赖读取前检查正常 SVN 节点、祖先目录、工作副本根和仓库身份。
12
+ - 切换目录、外部工作副本、复制状态和特殊/关键字属性被拒绝。
13
+ - 节点身份、竞争路径及正文在捕获结束重新核验;预览过期必须重新生成。
14
+ - 所有层共享 512 个路径、20 个候选和上下文名额、1 MiB 来源正文、256 KiB 单文件及 30 秒捕获与核验时间。
15
15
 
16
- SVN 仍仅支持直接依赖。此功能是保守文本导航,不是完整定义绑定或语义调用图。
16
+ 仅支持当前工作副本,未新增 SVN 历史审查。依赖导航不等于编译器解析、完整语义绑定或完整调用图。
17
17
 
18
18
  ## 验证
19
19
 
20
- 真实 Git 测试覆盖三层深度、脏工作区隔离、旧侧身份、上下文名额、循环、取消及共享上限。完整回归、宿主检查、独立安装与四组 CI 是正式发布条件,不代表真实模型效果已达标。
20
+ 真实 SVN 测试覆盖三层深度、循环、手动入口、数量与路径预算、未跟踪歧义、关键字属性及批准发送前过期拒绝。全量回归、实际宿主、独立安装和四组 CI 是正式发布条件,不是模型效果成绩。
21
21
 
22
- 使用方法见 [README](README.md),维护流程见 [开发指南](DEVELOPMENT.md)。
22
+ 使用方法见 [README](README.md),维护步骤见 [开发指南](DEVELOPMENT.md)。
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: { reviewRounds: { type: 'integer', minimum: 1, maximum: 3, description: 'Explicit independent review passes, default 1. Preview discloses extra sends; later passes omit the first risk plan. Shares existing timeout, call and retrieval budgets, not provider reasoning effort. Bound to this preview; execute cannot override.' }, oldContextPaths: { type: 'array', maxItems: 20, items: { type: 'string' }, description: 'Explicit Git historical baseline-only blob context paths. Shares 20 context slots. No target text is inferred; selected old changes reuse their original identity, excluded changes cannot reenter. Unsupported on working reviews and SVN.' }, callerScopePaths: { type: 'array', minItems: 1, maxItems: 4, items: { type: 'string' }, description: 'Explicit non-root repository-relative directories for bounded tracked literal reverse import/include discovery. Git/SVN working review or Git fixed commit/range review. SVN uses offline normal nodes, rejects unsafe ancestor states and special/keyword properties. Historical sides share scan budgets and use object blobs only. Preview reads candidate bodies, including nonmatches; only captured matching context is sent. Navigation only, not complete caller coverage.' }, businessRequirement: { type: 'string', maxLength: 16384, description: 'Optional business constraint text up to 16KiB UTF8; untrusted data, not evidence or permission. Bound to preview and sent in review phases.' }, 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. Explicit context uses target tree with available baseline provenance; oldContextPaths is baseline-only. Historical caller discovery scans fixed sides; explicit rules use target-tree blobs.' }, 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 tracked literal import/include context shown in preview. Working Git follows changed and explicitly selected context sources at most 3 dependency layers under shared 512 exact probes, 20 candidates/context slots, 1MiB source and 30s verification budget. Git history follows at most 3 baseline/target dependency layers with shared limits, object reads only; SVN remains direct navigation. Not semantic binding or complete definitions.' }, contextPaths: { type: 'array', items: { type: 'string' }, maxItems: 20 }, rulePaths: { type: 'array', items: { type: 'string' }, maxItems: 4 } } },
15
+ parameters: { type: 'object', additionalProperties: false, properties: { reviewRounds: { type: 'integer', minimum: 1, maximum: 3, description: 'Explicit independent review passes, default 1. Preview discloses extra sends; later passes omit the first risk plan. Shares existing timeout, call and retrieval budgets, not provider reasoning effort. Bound to this preview; execute cannot override.' }, oldContextPaths: { type: 'array', maxItems: 20, items: { type: 'string' }, description: 'Explicit Git historical baseline-only blob context paths. Shares 20 context slots. No target text is inferred; selected old changes reuse their original identity, excluded changes cannot reenter. Unsupported on working reviews and SVN.' }, callerScopePaths: { type: 'array', minItems: 1, maxItems: 4, items: { type: 'string' }, description: 'Explicit non-root repository-relative directories for bounded tracked literal reverse import/include discovery. Git/SVN working review or Git fixed commit/range review. SVN uses offline normal nodes, rejects unsafe ancestor states and special/keyword properties. Historical sides share scan budgets and use object blobs only. Preview reads candidate bodies, including nonmatches; only captured matching context is sent. Navigation only, not complete caller coverage.' }, businessRequirement: { type: 'string', maxLength: 16384, description: 'Optional business constraint text up to 16KiB UTF8; untrusted data, not evidence or permission. Bound to preview and sent in review phases.' }, 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. Explicit context uses target tree with available baseline provenance; oldContextPaths is baseline-only. Historical caller discovery scans fixed sides; explicit rules use target-tree blobs.' }, 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 tracked literal import/include context shown in preview. Working Git follows changed and explicitly selected context sources at most 3 dependency layers under shared 512 exact probes, 20 candidates/context slots, 1MiB source and 30s verification budget. Git history follows at most 3 baseline/target dependency layers with shared limits, object reads only; SVN follows at most 3 safe offline current-side layers with exact path probes and node/ancestor identity verification, sharing limits; no SVN historical review. Not semantic binding or complete definitions.' }, 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.41.0",
3
+ "version": "0.42.0",
4
4
  "private": false,
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
package/src/snapshot.mjs CHANGED
@@ -1,10 +1,10 @@
1
+ import {captureSvnDependencyChain} from './svn-dependency-chain.mjs';
1
2
  import {captureDependencyChain} from './dependency-chain.mjs';
2
3
  import {captureSvnCallers} from './svn-caller-capture.mjs';
3
4
  import { captureCallers } from './caller-capture.mjs';
4
5
  import { validateCallerScopes } from './caller-index.mjs';
5
6
  import { bindBusinessRequirement, validateBusinessRequirement } from './business-requirement.mjs';
6
7
  import { gitContextIndex, svnContextIndex } from './tracked-context.mjs';
7
- import { contextCandidates } from './context-candidates.mjs';
8
8
  import { lstat, realpath } from 'node:fs/promises';
9
9
  import path from 'node:path';
10
10
  import { captureGitHistory } from './git-history.mjs';
@@ -49,22 +49,13 @@ export async function captureSnapshot(cwd, options = {}) {
49
49
  if (!Array.isArray(rulePaths) || rulePaths.some(name => contextPaths.includes(name))) throw new Error('Rule paths must be separate from context paths');
50
50
  const rules = await captureProjectRules(snapshot.root, rulePaths, { signal: options.signal, files: snapshot.files });
51
51
  const context = [];
52
- let autoContext, index, explicitSeeds = [], indexOptions = options;
52
+ let autoContext, index, verifyDependencies, explicitSeeds = [], indexOptions = options;
53
53
  if (options.autoContext === true && type==='git') {
54
54
  const chain=await captureDependencyChain(snapshot.root,snapshot.files,options);
55
55
  explicitSeeds=chain.explicitSeeds;context.push(...chain.context);autoContext=chain.autoContext;index=chain.index;indexOptions=chain.indexOptions;
56
56
  } else if (options.autoContext === true) {
57
- if(type==='git'){const probes=new Set();contextCandidates(snapshot.files,[],{onProbe:paths=>{for(const p of paths)probes.add(p);if(probes.size>512)throw new Error('Git context lookup limit exceeded');}});indexOptions={...options,probePaths:[...probes]};}
58
- index = await (type === 'git' ? gitContextIndex : svnContextIndex)(snapshot.root, indexOptions);
59
- const plan = contextCandidates(snapshot.files, index.paths);
60
- autoContext = { ...plan, candidates: plan.candidates.filter(c => !contextPaths.includes(c.path) && !rulePaths.includes(c.path)), capturedPaths: [] };
61
- for (const candidate of autoContext.candidates) {
62
- if (context.length + new Set(contextPaths).size >= maxContextFiles) { candidate.status = 'blocked'; candidate.reason = 'Context file limit exceeded'; continue; }
63
- try {
64
- context.push({ path: candidate.path, ...await readLocal(snapshot.root, candidate.path, options) });
65
- candidate.status = 'captured'; autoContext.capturedPaths.push(candidate.path);
66
- } catch (error) { options.signal?.throwIfAborted(); candidate.status = 'blocked'; candidate.reason = error.message; }
67
- }
57
+ const chain=await captureSvnDependencyChain(snapshot.root,snapshot.files,options);
58
+ explicitSeeds=chain.explicitSeeds;context.push(...chain.context);autoContext=chain.autoContext;indexOptions=chain.indexOptions;verifyDependencies=chain.verify;
68
59
  }
69
60
  for (const name of [...new Set(contextPaths)].sort()) {
70
61
  const changed = snapshot.files.find(f => f.path === name);
@@ -103,6 +94,7 @@ export async function captureSnapshot(cwd, options = {}) {
103
94
  if (index && (await (type === 'git' ? gitContextIndex : svnContextIndex)(snapshot.root, indexOptions)).fingerprint !== index.fingerprint) throw new Error('Tracked context index changed during capture');
104
95
  const verifiedRules = await captureProjectRules(snapshot.root, rulePaths, { signal: options.signal, files: snapshot.files });
105
96
  if (JSON.stringify(verifiedRules) !== JSON.stringify(rules)) throw new Error('Rules changed during capture');
97
+ if (verifyDependencies) await verifyDependencies();
106
98
  if (verifyCallers) await verifyCallers();
107
99
  const result = { ...snapshot, context, ...(callerDiscovery ? {callerDiscovery} : {}), ...(autoContext ? { autoContext } : {}), ...(rules.length ? { rules } : {}) };
108
100
  delete result.id;
@@ -0,0 +1,26 @@
1
+ import {contextCandidates} from './context-candidates.mjs';
2
+ import {svnCallerNode,svnCallerProbes} from './svn-caller-node.mjs';
3
+ import {readLocal} from './content.mjs';
4
+ // Exact local path probes only. Every body requires safe offline SVN node identity.
5
+ export async function captureSvnDependencyChain(root,files,options={}){
6
+ const signal=AbortSignal.any([AbortSignal.timeout(30000),...(options.signal?[options.signal]:[])]),bounded={...options,signal,maxFileBytes:Math.min(options.maxFileBytes??256*1024,256*1024)};
7
+ const visited=new Set(files.map(f=>f.path)),reserved=new Set([...(options.contextPaths??[]),...(options.rulePaths??[])]),probes=new Set(),context=[],candidates=[],nodes=[];let frontier=[...files],bytes=0,truncated=false,index;
8
+ const limit=Math.min(options.maxContextFiles??20,20);if(new Set(options.contextPaths??[]).size>limit)throw new Error('Context file limit exceeded');
9
+ // Explicit context remains separately selected; safe normal nodes can seed navigation.
10
+ const explicitSeeds=[];for(const name of [...new Set(options.contextPaths??[])].sort()){
11
+ const changed=files.find(f=>f.path===name);if(changed){if(changed.eligibility!=='reviewable'||changed.rightExists===false)throw new Error('Context path is an excluded blocked or deleted change');continue;}
12
+ const node=await svnCallerNode(root,name,bounded),item={path:name,...await readLocal(root,name,bounded)};bytes+=item.bytes;if(bytes>1024*1024)throw new Error('SVN dependency source byte limit exceeded');nodes.push({path:name,fingerprint:node.fingerprint});explicitSeeds.push(item);visited.add(name);frontier.push({path:name,eligibility:'reviewable',right:item});
13
+ }
14
+ const onProbe=paths=>{for(const p of paths)probes.add(p);if(probes.size>512)throw new Error('SVN dependency lookup limit exceeded');};
15
+ for(let depth=1;depth<=3&&frontier.length;depth++){
16
+ for(let i=0;i<frontier.length;i+=200)contextCandidates(frontier.slice(i,i+200),[],{onProbe});index=await svnCallerProbes(root,[...probes],bounded);
17
+ const plans=[];for(let i=0;i<frontier.length;i+=200)plans.push(contextCandidates(frontier.slice(i,i+200),index.paths));const next=[];
18
+ for(const plan of plans){truncated ||= plan.truncated;for(const entry of plan.candidates){if(visited.has(entry.path)||reserved.has(entry.path))continue;visited.add(entry.path);const c={...entry,depth};candidates.push(c);if(candidates.length>20)throw new Error('SVN dependency candidate limit exceeded');
19
+ if(context.length+new Set(options.contextPaths??[]).size>=limit){c.status='blocked';c.reason='Context file limit exceeded';continue;}
20
+ try{const node=await svnCallerNode(root,entry.path,bounded),item={path:entry.path,...await readLocal(root,entry.path,bounded)};bytes+=item.bytes;if(bytes>1024*1024)throw new Error('SVN dependency source byte limit exceeded');context.push(item);nodes.push({path:item.path,fingerprint:node.fingerprint});c.status='captured';next.push({path:item.path,eligibility:'reviewable',right:item});}
21
+ catch(error){signal.throwIfAborted();if(error.message==='SVN dependency source byte limit exceeded')throw error;c.status='blocked';c.reason=error.message;}
22
+ }}frontier=next;
23
+ }if(frontier.length)truncated=true;
24
+ const verify=async()=>{for(const node of nodes)if((await svnCallerNode(root,node.path,bounded)).fingerprint!==node.fingerprint)throw new Error('SVN dependency node changed during capture');if(index&&(await svnCallerProbes(root,[...probes],bounded)).fingerprint!==index.fingerprint)throw new Error('SVN dependency resolution changed during capture');signal.throwIfAborted();};
25
+ return {context,explicitSeeds,indexOptions:bounded,verify,autoContext:{candidates,capturedPaths:context.map(c=>c.path),truncated,maxDepth:3,sourceBytes:bytes,probeCount:probes.size,notice:'Offline safe SVN current-side literal dependencies, at most 3 layers, 512 exact probes, 20 candidates/context slots, 1MiB source and 30s including verification. Not symbol binding or complete caller coverage.'}};
26
+ }