关于 callidescope-triage
用于排查 callidescope 分析与 gate 失败的技能,区分代码问题、未读取项目、配置错误和过期报告,并说明相应处理方式。
主要功能
- 01分类分析与 gate 消息
- 02说明深度和广度问题
- 03排查项目源码未读取
- 04说明如何重新生成过期报告
适用场景
- 01调查深度 gate 失败
- 02追查项目未获判定的原因
- 03处理过期生成报告
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ organizzolini/ codebase --skill callidescope-triage安装后在 Claude Code 中输入/callidescope-triage确认已加载。
详细介绍
处理 callidescope 失败
callidescope 失败的原因有几种,弄清楚发生了哪一种是最重要的工作。先区分它们:
| 运行结果 | 含义 |
|---|---|
🚨 [DEPTH n > limit] | 关于代码的一条发现。修复分层 |
| 某个宽度行超过限制 | 一条发现。拆分可调用项 |
A configured destination is stale | 偏差。重新运行 --write |
## Read nothing of its own (0 files) | 某个门禁评估了一个它从未读取过自身文件的项目。其判定毫无意义 |
🔭 Rejected a project it could not read | tsconfig.json 缺失或无法解析。追踪在此处停止 |
🔭 Traced nothing | 本次运行完全没有看到代码。后续内容都毫无意义 |
🔭 Rejected the command line | 标志中有错误。没有进行追踪 |
🔭 Rejected a project configuration | 某个项目自己的 callidescope.config.ts 缺失、不完整,或因某个只有工作区才能设置的字段而被拒绝。没有报告任何内容 |
后五种情况都表示本次运行从未对代码作出判定。它们都不会写入目标位置,因此无论发生哪一种,检出目录都不会改变——而且,对于打印出这些内容的运行结果,无论其是否有发现,都不应轻信。
limits 命令会在同一个 🔭 Rejected a project configuration 标题下打印它自身的读取失败信息,修复方式也相同——请参阅项目配置被拒绝。
深度门禁失败
某个调用栈的深度超过了 limits.maximumDepth。让它变长的那项改动,就是修复它的改动——这正是深度作为门禁条件、而陈旧度不是的全部原因。
门禁失败只涉及一个项目。限制按项目编写在各自的 callidescope.config.ts 中,而项目的门禁只评判该项目负责的发现项——调用栈归其根节点所属的项目负责,依赖项超限则由该依赖项自己的门禁负责。因此,修复只有两个去处:该项目的代码,或该项目自己的 callidescope.config.ts。在考虑下面整节内容后,再决定是否采用第二种方案。
在 Nx 工作区中,这会表现为 gate 目标失败,而不是整个工作区的 --check depth 失败;任务名称会告诉你该打开哪个项目。在其他情况下,发现项自身的 limit 会说明它是对照哪个数值进行评估的,而 callidescope limits 会告诉你该数值写在哪个文件中。
开始处理前,先阅读打印出来的调用栈。各帧包含每个可调用项的签名,以及其文档的一行摘要;这些摘要能帮你区分以下两种情况:
- 每一帧说的都不一样。 这种分层确有必要:每个阶段都有自己的工作。可以质疑是否需要所有阶段,但不要开始逐层删除——这种情况通常需要设计决策,而不是简单精简。
- 一连串帧说的内容大致相同。 这些是只负责传递参数的转发层。将它们合并。 这是最常见的情况,也正是该工具要揭示的问题:否则能告诉你这一点的工具都是一次读取一个文件,因此只能看到转发,看不到深度。
调高 maximumDepth 不是修复。 这个限制描述的是仓库期望的结构;把它调到适应当前最深调用栈的值,会让门禁失去作用,之后每个调用栈都能免检到这个新数值。如果该项目的限制确实不合理,应将其作为独立决策单独修改,并写明理由——而不是把它当作解除拉取请求阻塞的方法。调高项目的数值,只是同一种错误的缩小版,并非例外:该数值正是经过边界测试、设为比该项目测得的深度多一帧的那个数值,因此调高一帧,就会把门禁变成仅针对该发现项所属项目的额外余量。
摘要中的深度不是项目的深度
应采用的数值来自判定结果,绝不要采用摘要行打印的数值。🔭 Finished an analysis 日志行会报告 maximumDepthTraced,这个名称就是字面意思:它汇总一次运行的全部测量结果,覆盖整个扩展到依赖项的追踪范围,因此代表该次运行所到达的最深调用栈,而不只是指定范围内的最深调用栈。决定门禁判定结果的项目级范围,是完全不同的代码路径。
两者经常相差好几帧。synchronization 的限定范围运行会打印 maximumDepthTraced:13,但门禁在设为 10 时通过、设为 9 时失败——10 才是该项目负责的数值,13 属于与其一同追踪的某个依赖项。
因此,不要根据那一行来设置项目的限制。应当进行边界测试:写入一个候选值,运行门禁,让失败结果指出真实数值。一个有意义的限制,必须在设为该数值时通过、低一档时失败;而只有门禁判定结果才能告诉你边界在哪里。
≥ n 而不是 n
带 ≥ 的深度是一个下限,而非测量值。路径上有些内容无法继续跟踪——例如通过参数调用的回调、target[key]() 这类计算成员名称,或因超出实现候选项上限而被丢弃的结构展开——运行会明确报告这一点,而不是悄悄低报。
这不是需要修复的缺陷,也不是一个不可信的数字。它表示真实深度_至少_为该值,因此,超过 6 的 ≥ 10 确实意味着失败。摘要中的 Unfollowable calls 会统计运行遇到的此类调用数量。
以 orphan-root 为首的调用栈
仓库中没有任何内容调用该根节点。它可能是死代码,也可能是配置中缺少入口点规则——最常见的是 entryPoints.decorators 中缺少框架装饰器。判定深度是否属实之前,先确定是哪一种情况:框架实际调用的根节点下的调用栈属于发现的问题;确实是死代码的根节点下的调用栈,则说明应该删除这段代码。
Stacks through recursion 大于零
存在一个在测量深度前已折叠的循环。一个由三个函数组成的相互递归簇只计为三个调用帧。深度出于上述相同原因是下限;折叠递归则能避免数字在不同运行之间变化——不要仅仅因为 callidescope 提到了递归就试图“修复”它。
广度门禁失败
一个可调用项直接调用的其他可调用项数量超过了 limits.maximumBreadth。与深度不同,广度完全没有默认限制,因此只有在项目自己的 callidescope.config.ts 设置了数值时才会发生广度失败——仅设置工作区级别的 maximumBreadth 不会限制任何内容,因为每个项目都必须自行指定该值,才会按它进行判定。
修复方法是按照被调用项已有的职责分组来拆分该可调用项,而不是内联任何内容。在决定从哪里拆分之前,针对该可调用项的地址运行 breadth 命令,即可并排查看其直接被调用项和调用方——callidescope-trace 技能会介绍如何解读这些信息。
门禁未读取任何项目自身内容
## Read nothing of its own (0 files)
This run judged a project whose own code it never read:
- `packages/thing`
运行本身并非空运行——它跟踪的依赖项都已读取——因此这比 🔭 Traced nothing 更具体:只有被判定的项目自身源文件缺失了。门禁会因此失败,而 trace 会打印该信息并通过,因为未进行检查的门禁无法区分项目是否干净与是否未读取;而 trace 不会作出判定,只是生成一份供读者查看的报告。
按以下顺序检查两项内容:
exclude匹配范围过广。 项目自身的excludeglob 以项目根目录为基准,因此,按工作区相对路径编写的模式不会匹配任何内容,而范围过广的模式则会匹配所有内容。还要检查运行中的exclude和excludeFrom,确认是否有模式覆盖了整个项目。- 项目不再包含其
tsconfig.json中include所指定的源文件。 源根目录被移动或清空,读取结果也完全相同。
不要通过删除门禁来掩盖此问题。 被工作区配置排除的项目会被有意禁止使用门禁,因此不会出现此消息;仍设有门禁的项目预期应包含代码,而此检查正是为了防止空无一物却得到通过判定的情况。
过期报告
一个或多个已配置的目标中,已不再包含全新运行会写入的内容。本次运行会列出所有发生偏差的文件,而不是发现第一个就停止。
重新运行 --write。这就是全部修复方法。
npx callidescope --config configuration/callidescope.config.ts --write
过期报告并不意味着以下三件事:
- 这不是缺陷。 调用图会随着代码变化而变化,因此任何地方发生变动,报告都会过期。这正是为什么
--check reports应该放在默认分支上,而不应该放在拉取请求中。 - 这不是需要手动编辑的内容。 下一次
--write会整体替换标记之间的内容,因此编辑报告产生的差异会在下一次运行时悄然消失。 - 这不是在拉取请求检查关卡中添加
--check reports的理由。 这样做会导致拉取请求因其未造成的偏差而失败。
先运行 Lint,再重新生成;绝不要反过来。 每份报告中的每个帧都包含 file:line,而会对类成员排序的格式化工具会改变它插入位置之后所有内容的行号,因此,在排序前写入的报告一旦提交,就会立即过期。
被拒绝的命令行
没有进行任何追踪。消息会列出原因;以下是你可能遇到的情况:
- 缺少
--config。 所有会加载配置的命令(callidescope、depth、breadth、limits)都需要它,而且不会搜索文件或使用默认配置。请传入工作区文件,例如--config configuration/callidescope.config.ts。在 Nx 中,同一路径是nx.json注册项中的插件configurationPath选项,也是手写目标的configuration选项;两者都会以名称明确拒绝,并使用相同的ConfigurationPathRequiredError措辞。 --check需要一个值。 没有值的标志、空字符串或仅含逗号的值,都是同一种错误。空集合无法与未传入该标志区分;如果将其理解为“检查无内容”,就会产生一个永远不会失败的门禁——当变量未设置时,--check "$GATES"会在调用栈深度超过任何允许值两倍时仍永远通过。这比没有门禁更糟,因为它看起来像是有保护。--check不接受该值。 它接受由breadth、depth和reports组成的逗号分隔集合,消息会列出这些可选值。--write不能与--check reports同时使用。 刚写入报告的那次运行不可能检测到报告过时,因此两者组合会通过它本来要捕获的任何问题。请在拉取请求中各自对应的阶段分别运行。- 目标位置标志没有动词。
--json、--markdown和--mermaid指定报告要写到_哪里_;--write和--check reports则指定_是否_写入或比较报告。若指定了目标位置却没有指定这两种动词之一,运行会被拒绝,而不会悄悄照做。请添加消息中指出的动词。--check reports也算一种:它会比较目标位置,因此覆盖目标位置同样有意义。 - 目标位置标志覆盖了未声明的目标位置。
--json、--markdown和--mermaid会更改已声明报告的写入位置;它们无法凭空创建配置从未要求的报告,否则就能从终端绕过配置完整性的要求。请将消息中指出的write.json、write.markdown或write.mermaid添加到本次运行所读取的配置中,然后用标志将其指向其他位置。 --format不接受该值。 它接受markdown、mermaid或json,消息会列出这些可选值。无法识别的值会被拒绝,而不是改写为 markdown;过去会这样改写——当--format mermiad打印出一棵树时,运行以 0 退出,让读者误以为该标志不起作用。--maximum-breadth覆盖了未声明的限制。 它会像目标位置标志一样被拒绝:如果作用范围内没有任何配置声明limits.maximumBreadth,就没有可覆盖的限制;从命令行提供该值会让工作区受一个配置从未选择的数字约束。depth或breadth未提供地址。 这些命令需要<file>#<qualified-name>。在 CI 之外的真实终端中,它们会先进行追踪,然后将地址与找到的所有可调用项匹配,因此你可以选择名称,而不必凭记忆输入;如果同一文件中声明了同名项,名称会自动附上:<line>。在脚本或 CI 任务中,提示功能处于关闭状态,因此缺少参数会导致拒绝,而不会卡住等待输入。
被拒绝的项目配置
某个被追踪项目自己的根目录中存在 callidescope.config.ts,或者该文件不存在,都会导致拒绝。不会打印任何内容,也不会触及任何目标位置,因此检出内容与运行开始时完全一致。<project> 是相对于工作区的项目根目录;工作区文件中声明的地址则会标记为 the workspace configuration。
该项目完全没有配置文件。
每个被跟踪项目都必须有自己的 callidescope.config.ts,不能省略。
如果项目没有该文件,系统会在开始跟踪前按名称拒绝该项目。解决方法是
添加该文件,展开工作区的 projectDefaults 导出,并覆盖需要设置的内容:
import { projectDefaults } from "../../configuration/callidescope.config.js";
export default { ...projectDefaults };
这里的导入路径仅供示例,并非字面值——请根据项目自身的位置编写相对路径。 即使文件没有覆盖任何内容,也仍然是完整的声明:它明确说明该项目采用 所有默认值,而缺少文件永远无法表达这一点。
文件无法读取,或缺少必填字段。
Failed to read the callidescope configuration for <project> at <path>: <reason>
读取失败,或对象未通过架构验证。<reason> 会显示底层故障,并保留为
错误的 cause。架构错误会指出具体字段:缺少 entryPoints、exclude、
limits 或 write 成员;某项限制不是正整数;addresses 中有条目不是
字符串。现在所有字段都是必填项——entryPoints 必须包含全部五个
成员,limits 必须包含两个成员,write 必须包含两个成员,还必须有
exclude——因此,缺少任何字段的文件都会像完全没有配置文件一样被拒绝。
先展开 projectDefaults 再进行覆盖,便可避免文件必须逐一写出所有字段。
只有工作区才能设置的字段。
<project> sets <field>, which only the workspace configuration may set. A project configuration may set entryPoints, exclude, limits, write.markdown, and write.mermaid.
将该字段移到工作区文件中。对于限制,<field> 会显示为 limits.<name>;
对于目标位置,会显示为 write.<name>;其他顶层字段则显示为字段名本身,
以指出这三类设置中哪一项有误。
有两种情况会导致此错误,解决方法也不同:
- 在项目自己的文件中展开了工作区的默认导出。
删除它,改为展开
projectDefaults。默认导出包含directories、excludeFrom、write.json以及其他只能由工作区设置的内容;projectDefaults则只包含项目有权设置的字段,因此展开它不会意外引入 这些字段。 - 确实属于工作区级别的字段——
directories、excludeFrom、excludeCallees或write.json。这些字段应放在工作区文件中,且没有 对应的项目级设置:它们分别指定运行要读取的内容、运行报告的保存位置, 或工作区的划分方式;项目无法针对跟踪它的运行另行指定这些内容。
声明的地址未解析到任何内容。
<project> declares an entryPoints.addresses entry that resolves to nothing: "<address>". Check the file path and the qualified name callidescope prints for it in a stack.
该可调用项已重命名、移动,或被排除在本次运行之外。请更正地址或删除它。 不要让此错误静默通过:之所以拒绝,是因为如果重命名导致已声明的根 静默丢失,该项目测得的深度就会降低,而输出中却不会说明原因,这会在 没有人想到要检查的那次提交中放宽门槛。如果地址正确,但该可调用项不在 本次运行的跟踪范围内,那么有问题的是范围,而非地址。
声明的地址存在歧义。
<project> declares an entryPoints.addresses entry that matches more than one declaration: "<address>". Candidates: <address>:<line>, <address>:<line>. Add ":<line>" to the address to pick one.
每个候选项都会显示一个能选中它的地址,因此请将其中一个复制到配置中。
如果两条声明位于同一行,这种方式无法区分它们;此时应改用列号,
提示也会相应变为
Two declarations on one line cannot be told apart by ":<line>" — rename one, or name a different callable.
声明的地址格式错误。
<project> declares an invalid entryPoints.addresses entry. "<address>" is not a callable address. It needs a file path and a qualified name joined by "#", as in "src/foo.service.ts#FooService.bar", optionally followed by ":<line>" to disambiguate.
常见原因包括只写文件路径、只写限定名称,或在 # 应出现的位置使用了
路径分隔符。路径相对于工作区。
运行会收集所有未解析的地址,然后才拒绝,因此请一次性修复:如果有
多个地址,系统会为它们编号,并显示
<count> declared entry points did not resolve.。depth 和 breadth
命令也会对提示中输入的地址报告相同的三个问题,措辞会适配相应上下文,
并由同一段代码生成。
一个既不会被拒绝、也不会产生任何效果的项目配置
拼错键名不等于被拒绝——架构会列出它接受的每个字段,因此,limits: { maxDepth: 10 } 与拼写正确的 maximumDepth 并列时,会因键名无法识别而报错,而不是悄悄保留这个拼写错误。如果通过架构校验后,项目的限制似乎仍未生效,与其重新读取文件,不如查看它实际解析成了什么:
npx callidescope limits --config configuration/callidescope.config.ts
该项目对应的行会显示数值以及它的来源文件。
项目的 exclude 不起作用,原因则不同。 它的 glob 模式以该项目自身的根目录为基准,因此,相对于工作区的模式——在 packages/thing 自己的文件中写入 packages/thing/src/generated/**——不会匹配任何内容,这些文件仍会被追踪。去掉前面的项目根目录:src/generated/**。这里无法编写会延伸到另一个项目的 glob 模式;跨多个项目的干扰内容应放在这次运行自己的 exclude 中。
无法读取的项目
某个项目的 tsconfig.json 缺失或无法解析,因此运行在此处停止。消息中会包含路径,以及编译器自己的诊断信息;如果目录中根本没有 tsconfig.json,则会说明这一点。depth 和 breadth 也会引发此错误,且使用相同的标题:查找会先进行追踪,再进行匹配,因此它可能会遇到整个工作区追踪始终可能遇到的同一个无法读取的项目。
没有输出任何内容,也没有写入目标位置;这正是停止而不是跳过它的原因:调用方会先写入报告,再评估发现结果,因此,若生成部分图谱,就会先发布基于缺少某个项目的工作区测得的深度,然后才失败。停止运行意味着无需撤销已提交的内容。
有两种解决办法,具体选择取决于文件无法解析的原因:
- 配置意外损坏。 修复它。消息中包含编译器自己的诊断信息,通常会指出具体选项。
- 配置是故意损坏的。 有些仓库会将损坏的
tsconfig.json作为测试样例提交——这个工作区的codependix-examples中就有一个,修复它会删掉唯一一个演示此故障的地方。请改为排除该项目:将其目录添加到excludeFrom忽略文件,或添加一个excludeglob。排除操作会在打开tsconfig.json之前应用,因此确实能让运行避开它。排除项目的_文件_则不行:失败发生在打开其配置这一步,而且这一步先于其他操作。
没有追踪到任何内容的运行
所有前置条件都通过了,但没有内容可供判断,因此运行因自身没有内容而失败。这绝不算成功。按以下列表逐项检查:
- 代码是否被排除了?
excludeglob 模式会叠加到内置默认值上,而excludeFrom文件很容易被忽略。如果排除规则覆盖了所有项目,运行就会变空。 --directories是否指向了你认为的位置? 它接受的是包含各自tsconfig.json的路径,而不是项目名称。
没有找到调用栈的运行
这不是失败。追踪了真实代码并且没有报告任何发现,是正常结果:摘要会显示可调用项、文件和边的数量,证明它确实进行了检查;在限制范围内的仓库没有调用栈可供列出。
发现的问题该由谁负责
深度或广度行反映的是代码,而不是测量代码所用的配置。应该在代码中修复问题。只有测量结果本身有误时,才需要调整配置——例如缺少入口点规则,或横切各处的日志记录器抬高了所有数据的数值。callidescope-configure 技能涵盖了这些情况。