mcpField
C
compare-codebases

compare-codebases

github.com/kucherenko开发6.5k103 次安装今天更新
代码仓库

关于 compare-codebases

此 Skill 指导开发者使用 jscpd 比较两个代码目录中的函数,也支持不同语言的实现。它通过匹配结果、测试和未匹配函数,辅助分析代码库之间的对应关系。

主要功能

  1. 01跨语言函数匹配
  2. 02相似度等级报告
  3. 03分别比较代码与测试
  4. 04按名称匹配函数
  5. 05验证未匹配函数的指导

适用场景

  1. 01比较 Python 实现与 TypeScript 移植版
  2. 02审查两个代码库中的函数匹配
  3. 03查找没有对应项的函数
  4. 04评估测试之间的对应关系

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kucherenko/jscpd --skill compare-codebases

安装后在 Claude Code 中输入/compare-codebases确认已加载。

详细介绍

compare-codebases

jscpd --compare A B 会将文件夹 A 中的每个函数与文件夹 B 中执行相同任务的函数配对,并列出每个文件夹中没有对应函数的函数。两个文件夹可以使用同一种语言,也可以使用两种语言(Python 和 TypeScript、Kotlin 和 Swift、Java 和 Rust)。本技能说明比较的工作原理,以及如何做好比较。若要以比较结果衡量移植代码的进度,请使用 code-migration 技能;若要了解 jscpd 的其他内容,请使用 jscpd 技能。

比较的工作原理

jscpd 会在两个文件夹中查找 JavaScript、TypeScript、JSX、TSX、Vue、Svelte、Astro、Python、Rust、Go、Java、Kotlin、C#、C、C++、PHP、Ruby、Scala 和 Swift 文件中的函数。它会使用在 jscpd 内运行的代码嵌入模型(默认是 CodeRankEmbed),将每个函数的代码转换为向量。因此,即使语言、名称和结构不同,执行相同任务的两个函数也会指向相同方向。同一文件夹中的函数不会相互比较。

函数分两步配对:

  1. 按代码配对。如果两个函数互为对方文件夹中最接近的匹配项、它们的余弦相似度达到模型阈值(使用 CodeRankEmbed 时,跨语言为 0.4125,同一语言内为 0.6375),且相似度明显高于该函数的其他匹配项,就会配对。与最佳匹配接近的函数达到更高标准时也会配对,因此同一侧重复实现的功能可以产生两组配对。只有当一对函数中的至少一个是另一方的最佳匹配时,这对配对才会保留,因此其他类中相似的函数不会与某个函数及其对应函数配对;若出现并列,则选择文件名相同的文件。短于 --min-tokens(使用 --compare 时为 30)或 --min-lines(5)的函数不会参与这一步,因为短函数容易与太多其他函数相似。
  2. 按名称配对。剩下的函数会与另一个文件夹中名称相同的函数配对,忽略大小写、下划线、空格和标点(encodeBinary、encode_binary、_encode_binary;测试标题 rounds cents 和 rounds_cents),前提是它们的相似度达到 medium 级别(使用 CodeRankEmbed 时,跨语言为 0.5625)。按名称配对会跳过第一步中的最接近匹配和显著性检查,因此需要达到高于第一步阈值的相似度;否则两个代码库中的所有 load 和 init 都会配对。此处不考虑大小,因此可以找到短小的移植版本。按名称配对必须位于第一步已建立关联的模块中。模块是某一侧所有文件共有的最深文件夹下一级的文件夹(例如 android/notification/… 中的 notification);若某个文件位于其他文件之上(例如构建脚本),则不会因此将该文件夹上移。当一个模块包含另一个模块的大部分配对项时(代码和测试分别计数),这两个模块就会建立关联。因此,某个插件中的 checkPermissions 不会与另一个插件中同名的函数配对。

每对函数都会根据模型的尺度获得一个级别,因为对一个模型而言较高的余弦值,对另一个模型而言可能较低:

级别使用 CodeRankEmbed,跨语言含义
high0.7125 及以上几乎总是同一个函数
medium0.5625 至 0.7125通常是同一个函数,但经过了重构
low0.4125 至 0.5625请对照阅读两者:相关代码也会在此级别配对

jscpd 会将测试和代码分开衡量,并在报告的两个区块中分别呈现;测试只会与测试配对。它会根据各语言的惯例识别测试:

  • 测试文件,例如 *_test.go、test_*.py、*.test.ts、*.spec.js、*Tests.swift 或 *_spec.rb;
  • 测试文件夹,例如 tests/、__tests__/、spec/、src/test/(Java、Kotlin 和 Scala 存放测试的文件夹)、MyAppTests/ 或 MyApp.Tests.Integration/;比较文件夹自身的名称也包括在内;
  • Rust 函数,位于 #[cfg(test)] 模块中或标记了 #[test];
  • JavaScript 或 TypeScript 测试用例,例如 it('rounds cents', () => …)。 当双方都没有符合条件的测试(见下文)时,报告只有一个区块,且没有标题。

总数只统计至少达到 --min-tokens 个令牌和 --min-lines 行的函数;更短的函数只会作为配对项显示。不带函数体的声明(TypeScript 重载、.d.ts 文件中的函数、接口方法和抽象方法)不参与统计。匿名函数(回调、闭包)也不参与统计,JavaScript 和 TypeScript 测试用例除外。像 it('rounds cents', () => …) 这样的测试用例按其标题统计,使用 test、specify、fit、xit、xtest 或 bench 编写的测试用例也一样,后面跟有 .only、.skip 或 .each(table) 时亦如此。测试套件和钩子保持匿名。jscpd 不比较类型、常量、SQL 或 UI 标记。

比较两个文件夹的方法

1. 选择文件夹
  • 指向代码所在位置,而不是代码仓库:使用 app/src/main/java 和 ios/Sources,而非两个仓库的根目录。构建输出、供应商代码、已安装的软件包和生成文件会稀释结果;供应商依赖树还可能让一次运行从几秒延长到几十分钟,因为其中的每个函数都会被嵌入。在 git 仓库内,jscpd 会跳过 .gitignore 排除的内容;在仓库外,或对于 .gitignore 未涵盖的文件夹,请通过 --ignore 传入(--ignore "**/vendor/**,**/target/**,**/node_modules/**")。如果某一侧的 .gitignore 没有排除这类文件夹,请建议用户将其排除。
  • 必须提供两个文件夹,而且它们不能互相嵌套:app/ 和 app/android/ 会被拒绝。
  • 尽可能保持目录结构平行(ios/<module> 和 android/<module>)。模块会影响名称匹配,因此文件夹名称相同会有所帮助。
  • 对于移植,将源目录放在前面、目标目录放在后面,这样报告的第一行就能体现移植进度。对于两个都会继续维护的实现,顺序无关紧要。
  • 一次运行会同时检查测试和代码,因为报告会在独立区块中统计测试。如果用户只询问代码,请用 --ignore "**/__tests__/**,**/*.test.*,**/test/**" 排除测试。如果用户只询问测试,请将两个测试文件夹作为路径传入,或用 --pattern 选择测试文件。
2. 获取模型

首次运行需要下载模型(548 MB)。下载前请征求用户同意:

npx jscpd --semantic-download

jscpd 会按文件夹对缓存向量,因此后续运行只会嵌入代码发生变化的函数。

3. 阅读概览
npx jscpd --compare billing-py/ billing-ts/
 71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/
 80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/

billing-py/
  file         paired  similarity  counterpart
  billing.py   4 / 5   0.89        billing.ts
  shipping.py  1 / 2   0.91        shipping.ts

billing-ts/
  file         paired  similarity  counterpart
  billing.ts   3 / 4   0.89        billing.py
  shipping.ts  1 / 1   0.91        shipping.py

Paired under other names (1):
  billing-py/                   billing-ts/             similarity
  billing.py:28 tax_for_region  billing.ts:27 salesTax  0.87 high

Only in billing-py/ (2):
  billing.py (1)
    46  due_date                6 lines
  shipping.py (1)
    18  estimate_delivery_days  8 lines

Only in billing-ts/ (1):
  billing.ts (1)
    39  toCurrency  8 lines
  • 当两个文件夹都包含测试时,报告会有 Code 和 Tests 两个区块,每个区块都包含以下所有内容。每个区块顶部的两行给出两个文件夹中在另一侧有对应项的函数(或测试)占比。
  • 文件表会列出每个文件的配对函数、配对项的平均相似度(以及 low 配对项的数量,例如 0.62, 1 low),以及另一侧包含其最多对应项的文件。
  • “名称不同的配对项”列出名称存在差异的配对项,即使差异仅在大小写或下划线。按名称搜索永远找不到这些项。
  • “仅在此处”按文件列出没有对应项的函数,并显示每个文件的函数数量,随后列出每个函数的首行、名称和长度。
  • 颜色等级中,绿色表示 high,黄色表示 medium,红色表示 low;占比完整时为绿色,部分匹配时为黄色,完全没有匹配时为红色。解析控制台输出时请传入 --no-colors,或者改为读取 JSON 报告。
  • 如果某个文件夹没有函数,报告会显示一行总数和 <folder> has no functions yet。在得出其他结论前,请先检查路径和语言。
4. 检查配对
npx jscpd --compare billing-py/ billing-ts/ -r console-full

console-full 会添加每个配对及其相似度和级别,并标记按名称匹配的配对。在报告比较结果可靠之前,请先抽样检查:

  • 检查两三个 high 配对,确认这种配对方式适用于这段代码;
  • 检查所有 low 配对,因为相关代码也会在这些配对中出现(例如客户端调用及其调用的端点、用于统计 UTF-8 字节数的函数及其将字符串转换为字节的函数);
  • 检查标记为 by name 的配对,因为同名函数可能执行不同的操作。

如果抽样检查发现配对有误,请说明是哪一组配对,以及原因。

5. 检查没有对应项的函数

“Only in”列表中的函数要么在另一侧缺失,要么未能识别出对应项。认定函数缺失之前,请在另一个文件夹中查找:检查对应文件、其他名称下、更大函数中的相关代码,或是否已由库调用或平台调用取代。以下是已知的配对缺陷:

  • 跨语言的构造函数(例如 Java 构造函数与 Rust 的 new、Kotlin 的 constructor、Swift 的 init)只有在代码足够相似时才会配对;
  • 一侧重命名的短函数(例如另一侧的 _finder_penalty_add_history 对应 add_history);
  • 一个函数拆分为多个函数,或多个函数合并为一个函数:可能只有最接近的部分能配对。

按设计只存在于一侧的函数是正常的:平台衔接代码(例如 iOS 代理回调、Android 通知渠道)、某种语言需要而另一种语言不需要的辅助函数,以及某一侧已舍弃的功能。

6. 报告

向用户用几行总结:两个百分比、值得注意的重命名配对、你检查过的 low 配对及检查结果,以及两侧真正缺失的功能(按文件或模块分组)。如果有人想深入查看结果,请生成迁移图 -r html:一个离线页面,在两侧绘制依赖图并用配对连接两侧,同时在第二个标签页中以表格列出相同的配对。对于文档或问题报告,请生成 Markdown 报告并附上或粘贴:

npx jscpd --compare billing-py/ billing-ts/ -r markdown -o .jscpd-compare

若要自行处理结果,请读取 JSON 报告(-r json,写入 jscpd-compare.json)。其中包含结构相同的 code 和 tests 两个部分。每个部分都包含 sides[0] 和 sides[1],每一侧都有 path、functions、matched、percentage、files(包含 file、functions、matched、counterpart、similarity、lowPairs)、unmatched(包含 file、name、start、end)和 readyToPort(其中列出被调用函数均有对应项的未匹配函数,并包含每个函数的 callers 数量),以及 pairs,每个配对包含 a、b、similarity、level、renamed 和 matchedBy。路径均相对于各自的文件夹。请将报告写到仓库之外,或将报告所在文件夹添加到 .gitignore。

会改变结果的选项

  • --min-tokens 和 --min-lines 用于设置哪些函数会被统计。增大这两个值可聚焦于较重要的函数。
  • --ignore 和 --format 用于缩小文件范围,例如排除测试或构建脚本。
  • --semantic-model 用于选择其他模型,--semantic-url 用于指定兼容 OpenAI 的嵌入 API;npx jscpd --semantic-models 会列出经过阈值校准的模型。--semantic-threshold 和 --semantic-same-threshold 用于调整阈值;只有在检查过新阈值两侧的配对后,才应更改它们。
  • --semantic-rebuild-cache 会重新嵌入所有内容,仅在缓存可能有问题时才需要使用。

长期比较时,请保持文件夹、文件夹顺序、选项和模型一致,否则各次运行的数字就无法比较。无论结果如何,退出代码都是 0。

限制

  • 相似度无法识别行为上的细微差异:两个逐渐偏离的版本仍会配对,且通常置信度为 high。只有通过测试和阅读代码才能发现偏差。
  • 一侧重构得越多,其中能通过代码配对的函数就越少。
  • 仅比较函数。
  • 模型在 CPU 上运行。首次运行时,在 Apple M1 上每秒嵌入约 25 个函数(261 个函数耗时 11 秒);在配置较低的 CI 机器上,速度可能慢几倍;后续运行会复用缓存。

评论

支持 Markdown