justinstimatze/calque

GitHub: justinstimatze/calque

calque 是一款跨载体的偏差检测工具,专门发现在多处重复实现却悄然分叉的同一契约,帮助开发者将其合并回单一来源。

Stars: 1 | Forks: 0

# calque **calque 旨在发现多路径偏差——即在 N 处重复实现且已悄然发生偏离的单一契约——以便你能将这些副本合并回单一来源。** 首先进行召回;然后由你(或 LLM)进行裁定;最后由注册表记录记忆。 *calque*(仿译词)是指在不同语言表层之间进行的结构复制(例如英语的 "skyscraper" → 法语的 "gratte-ciel":结构相同,用词不同)。本工具专门追踪这种现象在软件领域的版本:同一个契约、概念或值在多处被表达,这些副本本应保持一致,却未能做到。 这些副本极少是刻意为之的——它们是**隐蔽的冗余代码(cruft)**:LLM(或匆忙的开发者)在不查阅已有实现路径的情况下重新实现了某个行为,导致同一契约的两个实现版本在不知不觉中发生了偏离。calque 的核心价值在于**浮现这些多路径,以便你尽可能将它们合并为单一来源**——提取共享逻辑,让一方调用另一方;单一的定义本身不会产生偏离。偶尔第二条路径确实是不可避免的,这时你需要让两者保持严格同步(通过差异化测试来固定它们)——但那是退而求其次的方案,而非目标。注册表会记录每一项裁定结果,这样拦截门就能阻止*新的*副本悄然混入。 ## 为什么现有工具会错失代码维度 双重路径属于 **Type-4(行为)克隆**——这是四种克隆类型中最难检测的一种。Type 1–3 属于逐渐宽松的*文本*副本(完全相同 → 重命名 → 带有空白的修改),而 Type-4 克隆仅在*其执行的操作*上相似,在代码阅读层面则不然。它在语法上的不相似是*天然属性*,这也正是常规工具频频失手的原因: - **grep / ctags / LSP** 索引的是词法一致性和调用图——但这些孪生路径*互不调用*,因此无法找到可供追踪的边。 - **embeddings / “语义”搜索** 提取的是*代码的编写方式*——孪生路径的写法截然不同,因此它们在嵌入空间中相距甚远。 - **克隆检测器**(`dupl`、jscpd 等)寻找的是*相似*代码——这与我们的问题背道而驰。 它们全都在索引**表示形式(representation)**。双路径偏差属于**行为空间中的角色冲突**,而“表示形式”恰恰是导致孪生路径产生偏离的维度。你必须索引契约,而不是自然语言描述。calque 会提取那些在函数体被重写时仍保持不变的信号,并根据重叠度对跨边界配对进行排名——此外还包含一个 N 元聚类扫描过程,专门捕获在多个命名不同的函数中内联的共享私有接缝(这种情况会被两两配对的评分机制在结构上稀释)。 ## 不变量 它捕获的每一个偏差都具有相同的形态: 测试套件重新实现了引擎的某个方法,却漏掉了一个步骤。重构后,文档与其描述的代码产生了冲突。两段文本描述同一个概念时,使用的复合词发生了偏离(`single-source` vs `single-sourced` vs `single sourced`)。原本单一的标准实体被多次表达,且没有任何机制来约束它们保持一致。calque 就是那个让它们保持一致的机制。 ## 核心 每个*维度*都将一个召回提取器接入到一个共享的流水线中: ``` recall (cheap, high-recall scan) → registry (adjudicated memory) → check (gate) → calibrate ``` - **recall** 提取信号并对嫌疑目标进行排名——侧重于召回率,而非精确率。 - **registry** (`.calque/registry.md`) 是持久的裁决记录,这样无论你还是 agent 都不会在上下文重置后对已排除嫌疑的目标进行重复裁定。 - **check** 将最新的扫描结果与注册表进行差异对比,仅浮现*新增*项——即可被挂钩的拦截门。 - **calibrate** (`doctor`) 汇总报告排序器是否真的具备区分真实偏差与误报的能力。 只有召回提取器是特定于底层载体实现的。目前已提供两个维度: | 维度 | 标准单元 | 索引内容 | |------|----------------|-----------------| | **code** | 被两条路径实现的契约 | 行为不变信号(输出的字符串、状态写入、返回的键、被调用者、名称词干)+ N 元私有接缝集群 | | **prose** | 术语/复合词 | 带有连字符的复合词频率对比白名单;嵌入的近义词 | ## 安装 ``` go install github.com/justinstimatze/calque/cmd/calque@latest # 或者,从 clone 创建(从 git tag 烘焙版本): make install ``` 版本字符串来源于 git 标签(`git describe`),而不是手动编辑的常量——`calque version` 会自动描述自身版本。 ## 快速开始 **Code 维度** —— 扫描一个边界,然后基于注册表进行校验拦截: ``` calque scan --left "engine*.py" --right "testing.py" # rank suspects calque check --left "engine*.py" --right "testing.py" # only what's new vs the registry calque check --strict ... # exit 1 on new suspects (for hooks) ``` **Prose 维度** —— 标记偏离白名单的复合词: ``` calque vocab-report # frequency surface (recall) calque vocab-check # gate: compounds not in .calque/vocab-allowlist.txt calque vocab-check --bootstrap # seed the allow-list from the current tail calque vocab-check --seed-cmd '' # merge a project's own slug list ``` `scan`/`check` 可用于 Go(原生 `go/ast`)和 Python(内嵌的 `python3` 提取器——当目标是 `.py` 文件时,需要在 PATH 中可用 `python3`)。 ## 循环流程 1. **选择边界** —— 即理应保持一致的两方(`--left`/`--right` 匹配模式)。选对边界永远比调参更有效。 2. **扫描 / 校验** —— 获取按相关性排序的嫌疑列表,并附带其触发的信号。 3. **裁定** 每一个嫌疑目标,并将裁决结果记录在 `.calque/registry.md` 中: - **`drift`** —— 契约相同,但行为已发生偏离。这就是 Bug。修复方法是将它们合并为单一执行路径(提取共享逻辑)——这远胜于保留两者再强行用测试去固定。 - **`contracted-twin-ok`** —— 刻意保持平行实现且当前已同步;将其记录下来,这样就不会被重复标记(理想情况下还应通过差异测试将其固定)。 - **`false-alarm`** —— 偶然触发的信号;记录下来以抑制后续报告。 4. **保持诚实** —— `calque hook install` 会将 `check` 接入 git 的 pre-commit 钩子;`calque mcp` 通过 MCP 提供双重校验拦截门(stdio JSON-RPC,包含工具 `calque_check` + `calque_vocab_check`),这样 agent 就可以直接询问“我的编辑是否引入了新的偏差?”。 calque 的设计初衷就是作为编码 agent 的等效预言机——完整的面向 agent 的流程请参阅 `SKILL.md`。 ## 它的优势所在 —— 以及如何解读输出 calque 是**一款优先追求召回率的敏锐嗅觉工具,而不是证明器。** 它会浮现那些带有相同契约“气味”的*候选目标*——通过低成本的效用足迹启发式算法(如共享的输出字符串、变更的字段路径、返回的键、被调用者、名称角色,以及共享的罕见私有接缝)——然后将它们交由你来判断。它**不负责**证明两个函数等效;因为那在理论上是无法判定的。解读输出时请牢记这一点: - **预期能够容忍误报——这正是其设计初衷。** 它的任务是*召回率*:生成一个简短的排序列表,由你(或 LLM)将其裁定为 `drift` / `contracted-twin-ok` / `false-alarm` 并写入注册表。一个真实命中率约 30% 但几乎零遗漏的运行结果,远胜于一个“看似严谨”却找不到任何可操作问题的分析器。 - **它在处理涉及副作用/有状态/文本输出的代码时最为得力** —— 比如游戏引擎、CLI、服务、agent 工具:即那些会变更状态、输出字符串并返回记录的函数。而在纯函数式、只返回值的库中,基于两两配对的效应信号会变得稀疏,此时 N 元**集群扫描**(即那些共享了罕见私有符号的函数——无需依赖编码规范且与语言无关)将承担更多的检测任务。 - **已在真实的 Python 和 Go 代码库(以及 calque 自身的源码)中得到验证**,在这些场景中它成功浮出了具体的、属于已发布 Bug 类别的偏差。TypeScript 支持已在路线图中。 - **优化边界,而不是调整阈值。** 选对 `--left/--right`(即两个确实理应保持一致的实体——测试套件 vs 生产环境、客户端 vs 服务端、v2 vs v1)永远比扫描整个代码库自身更有效。 `docs/DESIGN_NOTES.md` 第 13 节提供了客观的详细剖析,说明了哪些部分具备普适性(无需依赖规范的引擎),哪些部分仍需针对代码风格进行调整(基于两两配对的效应权重)。 ## 迁移旧版注册表 由原版 Python calque 编写的注册表(包含 `- left:`/`- right:` 块)在 Go 解析器下会被解析为零条目,因此 `check` 会将整个代码库标记为全新状态(当检测到这种情况时会发出警告)。只需运行一次转换即可: ``` calque migrate-registry --in .calque/registry.md --write # .bak backup first ``` ## 状态 核心流程已经完成并经过内部测试(calque 会扫描自身源码并保持整洁)—— 涵盖 code + prose 维度、注册表拦截门、git/MCP 钩子及校准功能。最新的维度是 **role-cardinality**(`calque cardinality`):声明“此角色理应只有一个实现;一旦出现两个或更多就触发警告”(针对多重路径的情况,N 无上限)并对其进行拦截——这种方式能够捕获基于两两相似度检测所无法发现的双重路径,因为单纯计数不需要比对相似度(详见 `docs/DESIGN_NOTES.md` 第 18 节)。目前的 MVP 提供了显式声明表单(在注册表中声明谓词 + 预期数量,使用 `--strict` 进行拦截);集群推举的候选项及无视边界的扫描将是接下来的开发切片。未来的进一步维度(config/env、catalog、narrative)及 TypeScript 提取器已在 `docs/DESIGN_NOTES.md` 第 16–18 节的路线图中。采用 Apache-2.0 协议;其中 prose 维度、校准和钩子功能整合自姊妹项目 `cupel`(MIT 协议,已保留署名)——现在该项目反过来调用了 calque:cupel 淘汰了原有的词汇检测工具,改用 `calque vocab-check` 作为其 pre-commit 的 prose 拦截门。
标签:EVTX分析, SOC Prime, 云安全监控, 代码克隆检测, 代码重构, 开发工具, 日志审计, 自动化payload嵌入, 逆向工具, 静态分析