@feng0531/dsh-code-review 0.65.0 → 0.66.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.66.0 — SVN 历史显式 mergeinfo 来源导航
6
+
7
+ 对已批准历史变更的 svn:mergeinfo 属性增加有界来源路径/区间导航,保留非继承标记,不展开 revision、不读取分支源码。预览和报告 history 保留旧/新导航;无效或超限元数据明确标记且不输出部分导航,不阻断原属性审查。不是有效继承 mergeinfo、源码归属或已审查提交证明;跨 URL 比较仍待实现。
8
+
5
9
  ## 0.65.0 — SVN 历史文件/目录类型替换
6
10
 
7
- 固定历史增删对分别记录 oldKind/newKind,读取各自节点正文和属性,支持已提交文件→目录和目录→文件。预览披露类型与说明,目录一侧后代独立覆盖且由深至浅传播未完成;目录空正文不作为空文件或旧上下文。未提交工作副本替换仍阻断,多记录与无效状态仍拒绝。
11
+ 固定历史增删对分别记录 oldKind/newKind,读取各自节点正文和属性,支持已提交文件→目录和目录→文件。预览披露类型与说明,目录一侧后代独立覆盖且由深至浅传播未完成;目录空正文不作为空文件或旧上下文。未提交工作副本替换仍阻断,多记录与无效状态仍拒绝。482 项串行回归、宿主、隔离安装和四矩阵 CI 通过;原产物/npm/Release 128,792 字节逐字节一致,双哈希、清单与冻结发布说明通过。
8
12
 
9
13
  ## 0.64.1 — SVN 已提交历史替换双侧采集修复
10
14
 
@@ -32,7 +32,8 @@
32
32
 
33
33
  ## 当前验证结论
34
34
 
35
- - 最近完整回归:481 项通过。
35
+ - 最近完整回归:482 项通过。
36
+ - v0.65.0 已交付固定 SVN 历史文件/目录类型替换,各侧节点类型/正文/属性与来源独立绑定,预览披露且后代覆盖保护保留。482 项串行回归、宿主、隔离安装和四矩阵 CI 通过;原产物/npm/Release 128,792 字节逐字节一致,双哈希、清单与冻结发布说明通过。未提交替换、自动历史链/调用方和其他全面 SVN 缺口仍待实现。
36
37
  - v0.64.1 已修复已提交 SVN 历史替换的同路径增删记录,真实目标旧/新正文和属性逐侧读取。481 项串行回归、宿主、隔离安装和四矩阵 CI 通过;原产物/npm/Release 128,279 字节逐字节一致,双哈希、清单与冻结发布说明通过。未提交工作副本替换和类型变化等仍未支持,全面目标继续。
37
38
  - v0.64.0 已交付 SVN 历史 oldContextPaths,仅旧侧正文及来源身份,不合成目标,选中旧变更复用原身份,排除项拒绝回流。479 项串行回归、宿主、隔离安装和四矩阵 CI 通过;原产物/npm/Release 127,879 字节逐字节一致,双哈希、清单与冻结发布说明通过。自动历史链、调用方及全面 SVN 矩阵仍未闭环。
38
39
  - v0.63.0 已交付 SVN 历史显式双侧上下文与目标规则,以及模型 JSON/read 请求的安全诊断。477 项串行回归、宿主、隔离安装和四矩阵 CI 通过;原产物/npm/Release 127,355 字节逐字节一致,双哈希、清单与冻结发布说明通过。用户 loader 的实际协议失败尚未核验,不宣称已修复该样本;历史自动链/调用方、旧侧独有上下文及其他 SVN 缺口继续。
package/RELEASE-NOTES.md CHANGED
@@ -1,15 +1,14 @@
1
- # v0.65.0 发布说明
1
+ # v0.66.0 发布说明
2
2
 
3
- ## SVN 历史文件/目录类型替换
3
+ ## SVN 历史显式 mergeinfo 来源导航
4
4
 
5
- 支持已提交固定 revision 中文件与目录之间的替换,不使用未提交工作副本的 BASE 或复制来源推断旧侧。
5
+ 从已批准固定历史变更中的 svn:mergeinfo 属性生成来源路径和 revision 区间导航,预览及报告 history 保存两侧结果。
6
6
 
7
- - 增删对分别绑定 oldKind/newKind,各侧读取对应固定节点正文与属性。
8
- - 预览明确披露旧/新节点类型,目录空正文不冒充空文件。
9
- - 目录一侧的后代仍逐项覆盖,未全选或子项阻断由深至浅传播至父节点。
10
- - 旧侧目录不能作为旧文件上下文,避免给模型伪造定义正文。
11
- - 保留对重复新增/删除、多条记录、不安全来源及超限的拒绝。
7
+ - 保留非继承 `*` 标记,不枚举区间内每个 revision,不增加来源正文读取或网络请求。
8
+ - 导航限 16KiB、100 来源路径、500 区间;无效/超限时明确标记,不静默裁剪或展示部分结果。
9
+ - 已排除/阻断变更不进入导航,原属性正文仍按既有证据流程审查。
10
+ - 来源路径不代表有效继承 mergeinfo、提交归属、审查覆盖,也不授权读取分支源码。
12
11
 
13
- 本版只针对已提交的历史类型替换;未提交工作副本替换、自动历史链、调用方和其他全面 SVN 缺口仍在 [支持矩阵](SVN-SUPPORT-MATRIX.md) 中,目标尚未完成。
12
+ 跨 URL 分支比較、自动历史链、调用方、工作副本替换等仍未完成,详见 [SVN 支持矩阵](SVN-SUPPORT-MATRIX.md)。本版本不把导航作为缺陷证据或完整合并审计。
14
13
 
15
14
  用法见 [README](README.md)。
@@ -19,14 +19,14 @@
19
19
  | 根目录属性/mergeinfo | v0.58.1 已发布;独立复现后修复,指定三文件和默认预览均通过、0 模型发送、状态不变;456 项串行回归、宿主、隔离安装和四矩阵 CI 通过,原产物/npm/Release 116,254 字节逐字节一致及双哈希/清单/发布说明通过 | 第一批先发布此修复 |
20
20
  | 复制/移动、copyfrom 及目录结构变更 | v0.59.0 已发布同仓库复制新增文件及文件移动的新增/删除独立覆盖:新增目标旧侧为空,copyfrom 只作来源信息,预览披露并冻结身份;离线与凭据来源拒绝测试通过。459 项串行回归、宿主、隔离安装、四矩阵 CI 通过;原产物/npm/Release 117,098 字节逐字节一致,双哈希/清单/冻结发布说明通过。v0.61.0 已发布同仓库目录复制后代展开与继承来源身份,目标旧侧为空且预览披露。465 项串行回归、宿主、隔离安装、四矩阵 CI 与原包/npm/Release 120,159 字节、双哈希/清单/冻结发布说明通过。复制替换仍未支持 | 第二批:明确旧侧来源和后代覆盖 |
21
21
  | 普通新增/删除目录及后代 | v0.60.0 已发布:补齐默认 status 隐藏的删除后代,离线递归枚举与末尾清单复核;普通结构目录只在后代全选且可审查时开放,凭据/阻断子项导致目录覆盖不足。461 项串行回归、宿主、隔离安装与四矩阵 CI 通过;原产物/npm/Release 118,284 字节逐字节一致,双哈希/清单/冻结发布说明通过 | 第二批普通目录子批 |
22
- | 替换、删除再新增、文件/目录类型变化 | 普通与复制替换分别用 SVN_REPLACEMENT/SVN_COPIED_REPLACEMENT 诊断;离线探测确认前者 BASE 无 pristine,后者可能返回复制来源,禁止伪造旧侧。已新增未接入采集的 candidate-only 完整 diff 重建组件及真实离线测试:匹配完整工作侧,拒绝片段、错侧、截断与超限。diff 仍不能独立证明旧节点/编码/旧属性,因此未提交工作副本真实双侧替换仍未支持,不能把辅助组件算作支持完成。v0.64.1 已发布已提交固定历史版本的同路径 deleted/added 合并,真实目标旧/新正文与属性通过测试;481 项串行回归、宿主、隔离安装、四矩阵 CI 与原包/npm/Release 128,279 字节、双哈希/清单/发布说明通过。本地新增已提交固定历史文件/目录类型替换:分侧 kind/正文/属性、预览披露和目录后代覆盖,待 v0.65.0 发布。未提交替换仍拒绝 | 第二批:区分 BASE 旧节点与目标节点 |
22
+ | 替换、删除再新增、文件/目录类型变化 | 普通与复制替换分别用 SVN_REPLACEMENT/SVN_COPIED_REPLACEMENT 诊断;离线探测确认前者 BASE 无 pristine,后者可能返回复制来源,禁止伪造旧侧。已新增未接入采集的 candidate-only 完整 diff 重建组件及真实离线测试:匹配完整工作侧,拒绝片段、错侧、截断与超限。diff 仍不能独立证明旧节点/编码/旧属性,因此未提交工作副本真实双侧替换仍未支持,不能把辅助组件算作支持完成。v0.64.1 已发布已提交固定历史版本的同路径 deleted/added 合并,真实目标旧/新正文与属性通过测试;481 项串行回归、宿主、隔离安装、四矩阵 CI 与原包/npm/Release 128,279 字节、双哈希/清单/发布说明通过。v0.65.0 已发布已提交固定历史文件/目录类型替换:分侧 kind/正文/属性、预览披露和目录后代覆盖;482 项串行回归、宿主、隔离安装、四矩阵 CI 与原包/npm/Release 128,792 字节、双哈希/清单/发布说明通过。未提交替换仍拒绝 | 第二批:区分 BASE 旧节点与目标节点 |
23
23
  | 缺失、文本/属性/树冲突、阻塞 | v0.60.1 已发布稳定状态诊断:SVN_MISSING_NODE/OBSTRUCTED_NODE/INCOMPLETE_NODE/TEXT_CONFLICT/PROPERTY_CONFLICT/TREE_CONFLICT 等,预览与报告透传 blockCode。463 项串行回归、宿主、隔离安装、四矩阵 CI 与原包/npm/Release 119,462 字节、双哈希/清单/发布说明通过。诊断不算真实双侧支持,仍需后续实现 | 第三批:明确状态诊断;不能合成不存在的工作侧或声称审核通过 |
24
24
  | svn:keywords、svn:eol-style | keywords 当前阻断;换行保留原文但无规范化映射 | 第三批:双侧可验证映射,保留原字节和定位 |
25
25
  | svn:special/链接 | 当前阻断,不跟随链接取正文 | 第三批:如支持只审链接目标数据;不沿链接读取文件 |
26
26
  | switched、externals、稀疏/混合 revision | 多数拒绝;调用方/依赖链要求祖先 normal | 第四批:明确仓库边界与独立来源,禁止隐式跨仓读取 |
27
27
  | 固定 revision 单提交 | v0.62.0 已发布 svnRevision:r-1/r 固定对象、UUID/URL/peg/operative revision 逐侧绑定,正文不读工作树;远端显式 svnAllowNetwork。472 项串行回归、宿主、隔离安装、四矩阵 CI 及原包/npm/Release 125,486 字节、双哈希/清单/发布说明通过。历史上下文/规则/调用方尚未支持 | 第五批:新增明确 SVN 参数与冻结身份 |
28
28
  | 固定 revision 区间 | v0.62.0 已发布 svnBaseRevision/svnTargetRevision,固定树端点 summary+逐侧 cat/proplist,目录后代与属性纳入预算,不建临时工作树;同上述完整发布验证。分支间不同 URL 和历史上下文尚未支持 | 第五批:固定树端点与逐路径差异 |
29
- | 分支复制、分支间比较、合并来源 | mergeinfo 当前仅作为属性文本;不证明源码归属或被合并提交均已审查 | 第六批:URL/peg/operative revision 与 mergeinfo 导航披露 |
29
+ | 分支复制、分支间比较、合并来源 | 本地新增批准历史 mergeinfo 属性的来源路径/区间导航,保留非继承标记、有界不展开 revision、不读取分支源码;invalid/limited 明确且不裁剪。待 v0.66.0 发布;有效继承 mergeinfo、跨 URL 比较、源码归属/合并提交覆盖仍未支持 | 第六批:URL/peg/operative revision 与 mergeinfo 导航披露 |
30
30
  | 当前三层依赖与限定目录调用方 | 已有安全 normal 节点的离线有界测试;根属性修改可能使祖先检查拒绝,需单独验证 | 第四批补祖先/混合状态组合 |
31
31
  | 历史两侧上下文与调用方 | v0.63.0 已发布显式 contextPaths 固定双侧来源及目标 rulePaths,UUID/URL/peg/operative revision/原字节与正文 hash 验证,禁止脏工作树与排除项回流。477 项串行回归、宿主、隔离安装、四矩阵 CI 与原包/npm/Release 127,355 字节、双哈希/清单/发布说明通过。v0.64.0 已发布 oldContextPaths 旧侧独有正文、SVN 来源专用校验、预览披露与选中删除身份复用;479 项串行回归、宿主、隔离安装、四矩阵 CI 与原包/npm/Release 127,879 字节、双哈希/清单/发布说明通过;自动三层历史链与历史调用方仍未实现 | 第六批:逐侧固定版本,不能回退至工作副本 |
32
32
  | 真正 r7314→r7315 样本 | 用户未提供隔离副本路径;本地类似复现不是该样本验收 | 获得路径后只读生成预览,不自动发送模型 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feng0531/dsh-code-review",
3
- "version": "0.65.0",
3
+ "version": "0.66.0",
4
4
  "private": false,
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
@@ -4,6 +4,7 @@ import {xml} from './svn.mjs';
4
4
  import {hash,decode,relativePath} from './content.mjs';
5
5
  import {isSecretPath,assertNonSecretPath} from './secret-path.mjs';
6
6
  import {captureProjectRules} from './project-rules.mjs';
7
+ import {mergeinfoNavigation} from './svn-mergeinfo.mjs';
7
8
 
8
9
  const revision = value => {
9
10
  if (typeof value !== 'string' || !/^(0|[1-9][0-9]*)$/.test(value) || !Number.isSafeInteger(Number(value))) throw new Error('SVN revision must be a fixed nonnegative decimal string');
@@ -131,6 +132,7 @@ export async function captureSvnHistory(cwd,options={}) {
131
132
  for(const item of files.filter(f=>(f.oldKind==='dir'||f.newKind==='dir')&&['added','deleted','replaced'].includes(f.rawStatus)&&f.eligibility==='reviewable').sort((a,b)=>b.path.length-a.path.length)){
132
133
  if(files.some(child=>child.path.startsWith(item.path==='.'?'':item.path+'/')&&child!==item&&child.eligibility!=='reviewable')){item.eligibility='blocked';item.reason='Historical directory descendant coverage incomplete';delete item.left;delete item.right;}
133
134
  }
135
+ const mergeSources=files.filter(file=>file.eligibility==='reviewable').flatMap(file=>file.properties.filter(prop=>prop.name==='svn:mergeinfo').map(prop=>({path:file.path,old:mergeinfoNavigation(prop.old),new:mergeinfoNavigation(prop.new)})));
134
136
  const context=[];
135
137
  const targetContent=async(name)=>{
136
138
  const changed=files.find(file=>file.path===name);
@@ -169,7 +171,7 @@ export async function captureSvnHistory(cwd,options={}) {
169
171
  const after=await run(['diff','--summarize','--xml','--notice-ancestry','-r',range.base+':'+range.target,'--',at('.',range.target)]);
170
172
  if(!isDeepSummary(summary,after))throw new Error('SVN historical summary changed during capture');
171
173
  await identity('.',range.base);await identity('.',range.target);
172
- const snapshot={schemaVersion:1,vcs:'svn',root,repositoryUuid:local.repository.uuid,context,...(rules.length?{rules}:{}),history:{...range,vcs:'svn',url,repositoryUuid:local.repository.uuid,pegRevision:range.target,networkAccess,semantics:'fixed revision tree endpoints; no merge attribution or working text'},files};
174
+ const snapshot={schemaVersion:1,vcs:'svn',root,repositoryUuid:local.repository.uuid,context,...(rules.length?{rules}:{}),history:{...range,vcs:'svn',url,repositoryUuid:local.repository.uuid,pegRevision:range.target,networkAccess,...(mergeSources.length?{mergeSources}:{}),semantics:'fixed revision tree endpoints; no merge attribution or working text'},files};
173
175
  const serialized=JSON.stringify(snapshot);if(Buffer.byteLength(serialized)>maxSnapshotBytes)throw new Error('SVN historical snapshot limit exceeded');
174
176
  return {...snapshot,id:hash(serialized)};
175
177
  }
@@ -0,0 +1,28 @@
1
+ import {hash} from './content.mjs';
2
+
3
+ // Navigation over an already approved property. Never expands revisions or reads URLs.
4
+ export function mergeinfoNavigation(text) {
5
+ if(text===null || text==='')return {status:'parsed',sources:[],hash:hash(text??'')};
6
+ if(typeof text!=='string')return {status:'invalid',reason:'Mergeinfo property is not text',sources:[]};
7
+ if(Buffer.byteLength(text)>16*1024)return {status:'limited',reason:'Mergeinfo navigation byte limit exceeded',sources:[],hash:hash(text)};
8
+ const lines=text.split(/\r?\n/);if(lines.at(-1)==='')lines.pop();
9
+ if(lines.length>100)return {status:'limited',reason:'Mergeinfo navigation source limit exceeded',sources:[],hash:hash(text)};
10
+ const sources=[],seen=new Set();let rangeCount=0;
11
+ for(const line of lines){
12
+ const delimiter=line.lastIndexOf(':');
13
+ const sourcePath=line.slice(0,delimiter),list=line.slice(delimiter+1);
14
+ if(delimiter<1||!sourcePath.startsWith('/')||/[\x00-\x1f\\]/.test(sourcePath)||sourcePath.length>1024||(sourcePath!=='/'&&sourcePath.slice(1).split('/').some(part=>!part||part==='.'||part==='..'))||seen.has(sourcePath))return {status:'invalid',reason:'Mergeinfo navigation path or duplicate source invalid',sources:[],hash:hash(text)};
15
+ seen.add(sourcePath);
16
+ const ranges=[];
17
+ for(const token of list.split(',')){
18
+ if(++rangeCount>500)return {status:'limited',reason:'Mergeinfo navigation range limit exceeded',sources:[],hash:hash(text)};
19
+ const match=/^([1-9][0-9]*)(?:-([1-9][0-9]*))?(\*)?$/.exec(token);
20
+ if(!match)return {status:'invalid',reason:'Mergeinfo navigation revision range invalid',sources:[],hash:hash(text)};
21
+ const start=Number(match[1]),end=Number(match[2]??match[1]);
22
+ if(!Number.isSafeInteger(start)||!Number.isSafeInteger(end)||start>end)return {status:'invalid',reason:'Mergeinfo navigation revision range invalid',sources:[],hash:hash(text)};
23
+ ranges.push({start:String(start),end:String(end),inheritable:!match[3]});
24
+ }
25
+ sources.push({sourcePath,ranges});
26
+ }
27
+ return {status:'parsed',sources,hash:hash(text),notice:'Explicit mergeinfo navigation only; not effective inherited mergeinfo, change attribution, permission to read a source or evidence that listed revisions were reviewed.'};
28
+ }