关于 callidescope-configure
callidescope-configure 是配置 callidescope 运行的技能,介绍工作区设置与命令行参数。内容涵盖必需配置、覆盖规则、分析门禁、报告写入、目录范围和输出格式。
主要功能
- 01说明工作区配置字段
- 02介绍参数及其覆盖规则
- 03配置深度、广度和报告检查
- 04控制报告写入及目标路径
- 05将分析限定到项目目录
- 06选择 Markdown、Mermaid 或 JSON 输出
适用场景
- 01在拉取请求工作流中设置 callidescope 检查
- 02在默认分支运行中写入报告
- 03将分析范围缩小到指定项目目录
- 04排查无效参数或不完整配置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ organizzolini/ codebase --skill callidescope-configure安装后在 Claude Code 中输入/callidescope-configure确认已加载。
详细介绍
告诉 callidescope 要做什么
每次运行都会同时读取两个方面:命令行标志,以及其上叠加的 callidescope.config.ts。二者单独都不完整:--check 指定的限制必须在配置中设置,而只有标志要求写入文件时,才会写入文件目标。
每次运行要做出的两个决定
每次调用都会回答两个彼此独立的问题,二者互不影响:
| 问题 | 由什么决定 |
|---|---|
| 这次运行会因什么失败? | --check,一个以逗号分隔的集合 |
| 这次运行会重写什么? | --write,以及配置中的目标位置 |
如果运行时既没有传入 --write,也没有传入 --check reports,就不会读取或重写任何目标位置:它会进行跟踪、打印,然后退出。这让你可以放心地在别人的代码检出目录中直接运行命令,也确保拉取请求的门禁不会更改任何已提交报告。
标志
| 标志 | 含义 |
|---|---|
-a, --addresses | 以逗号分隔的可调用地址,每个地址格式为 <file>#<qualified-name>。仅适用于 depth 和 breadth。省略时会提示输入 |
--config | 必填。工作区 callidescope.config.ts 的路径。不会搜索该文件;省略时会拒绝执行,并指出该标志 |
-d, --directories | 以逗号分隔的待追踪项目目录,每个目录都包含自己的 tsconfig.json |
--entry-point-addresses、--entry-point-decorators、--include-exported-functions、--include-orphans、--include-tests | 覆盖对应的 entryPoints 字段。两个列表以逗号分隔;三个开关接受 true 或 false,也可只写标志表示 true |
--exclude、--exclude-callees | 以逗号分隔的 glob 和被调用方模式,覆盖用户编写的字段。工具自身的默认排除项会重新合并,处理方式与配置了 exclude 时完全一致 |
-f, --format | 指定输出格式:markdown、mermaid 或 json。默认为 Markdown。其他值会被拒绝,而不会重写。仅限命令行使用——没有对应的配置字段 |
--json、-m, --markdown、--mermaid | 指定各报告的输出位置。仅覆盖已声明的 write 目标路径,不会影响其他设置。需要 --write 或 --check reports |
--maximum-depth、--maximum-breadth | 数值,覆盖项目声明的对应限制。若配置未声明广度,则拒绝使用 --maximum-breadth,因为没有默认值可供覆盖 |
--check、--write | 上述两项决策:在以逗号分隔的 breadth、depth 和 reports 集合中检查失败条件;写入每个已配置的目标 |
每个已配置字段都有对应的覆盖标志,只有一个例外。 excludeFrom
列出运行时_读取_的忽略文件——它没有对应的标志。覆盖只能更改已声明的值,不能补充缺失的值,因此没有任何标志能让配置不完整的运行变得合法;空列表会按缺省处理。depth
和 breadth 可使用上述所有覆盖标志,但目标位置和限制除外;必填的 --config 和 --format 也不例外;--addresses 则仅适用于它们。
一条优先级规则决定所有覆盖行为
命令行值与配置值的每种组合都由 FlagResolutionService 在同一处、按照同一规则解析:
会改变一次运行评判或写入内容的标志,只能覆盖配置中已声明的值。选择模式或呈现方式的标志仅作用于命令行,因为这两者都无法让配置不足的运行变得合法。
--check、--write 和 --format 属于模式或呈现方式,绝不会写入配置。--config 则两者皆非:它是必填项,不会设默认值,也不会搜索,并且用于选择所有其他设置所依据的文件,因此在其余解析开始之前,它的全部作用就已完成。其他所有标志都是覆盖项,而覆盖项回答的是配置中已经提出的问题:路径标志只替换已声明目标的 path,保留其标题、描述、锚点和钩子;--maximum-breadth 不能凭空生成项目自身文件中未设置的限制,就像 --json 不能凭空生成工作区文件中未声明的 write.json。限制覆盖会作用于每个确实声明了该限制的项目的实际门槛,而不会作用于未声明该限制的项目,因为覆盖一项决策和撤销一项决策并非一回事。
解析过程中发现的所有问题都会汇总,而不是遇到第一个问题就抛出。因此,命令行上有两个错误,就有两个错误需要修正,而不是分两次运行。
--check 接受一组值,而这组值至关重要
| 值 | 什么情况会导致运行失败 |
|---|---|
depth | 调用栈深度超过 limits.maximumDepth |
breadth | 某个可调用项直接调用的其他可调用项数量超过 limits.maximumBreadth |
reports | 已配置的目标不再保存全新运行本应写入的内容 |
以下两种情况会被拒绝,都是有意为之:
- 不带值的
--check会被拒绝。 空集合与未启用该标志无法区分,因此如果变量未设置,--check "$GATES"就会永远通过,即便调用栈深度是允许上限的两倍也一样——这比没有门槛更糟,因为它看起来像是提供了保护。 - 无法识别的值会被拒绝,并会在消息中列出可接受的值。
为什么 depth 和 reports 应该位于拉取请求的两侧
Depth 是门禁。 此次更改让调用栈变长,而这次更改正是修复它的内容。每个拉取请求都运行它,如果愿意,也可以在每次提交时运行——depth 会读取源代码,无需构建,因此成本足够低,可以用于提交钩子。
npx callidescope --config configuration/callidescope.config.ts --check depth
在 Nx 工作区中,优先使用每个项目的 gate 目标,而不是这个标志。 对整个工作区运行 --check depth,虽然能得出正确结论,但代价高昂:每次提交都要在整个代码库上进行一次不可缓存的运行。@callidescope/nx 会为每个项目推断出一个 gate,按照该项目的 Nx 依赖关系进行追踪,并且只针对该项目负责的发现项失败,因此 nx affected --target=gate 只评估更改触及的内容,不涉及其他内容。每个项目都会对 depth 执行门禁,因为它有默认值;只有项目自己的 callidescope.config.ts 声明了 limits.maximumBreadth,才会对该项目的 breadth 执行门禁。没有 Nx 时,仍应使用这个标志。
过时状态不是门禁。 调用图只要在任何地方发生变化,报告就会过时,而这几乎是每次更改都会发生的事,因此以此作为门禁会让拉取请求因它们没有造成的偏差而失败。应在默认分支上发布报告,这样就没有其他操作会争着改写同一个代码块:
npx callidescope --config configuration/callidescope.config.ts --write
--write --check reports 会被直接拒绝:刚刚写入报告的这次运行不可能让报告过时,因此无论它原本要检查什么,都会通过。应将门禁直接列出,与其他拉取请求门禁并列,而不要依赖类似 lint 的聚合命令,以免它将来从某个分支转发 --configuration=write。
--directories,以及为什么不加它会运行很慢
它决定了运行是进行整个工作区分析,还是能在几秒内完成检查。每个指定的目录都需要有自己的 tsconfig.json,而构建的程序包括这些目录及其传递导入的每个项目,因此对依赖项的调用仍能解析到真实的调用帧:
npx callidescope --config configuration/callidescope.config.ts -d packages/foo,packages/bar --check depth
省略它后,callidescope 会在工作目录中遍历并查找所有 tsconfig.json——得到的是整个工作区的视图,而不是关于某个包的答案。
它接收的是路径,而不是项目名称:callidescope 不知道任何工作区工具,包含 tsconfig.json 的目录就是全部约定。也可以在工作区配置文件中将同一列表设置为 directories。
缩小 breadth 查找范围确实有所取舍:指定目录及其下方闭包之外的调用方,对本次运行来说并不存在,因为闭包只向下展开,不会构建调用进来的依赖项目。对于影响范围正是重点的重命名,应扩大追踪范围,确保包含每个使用方。
Nx 工作区也可以改由 Nx 负责筛选,使用独立的 @callidescope/nx 插件即可。该插件会为每个项目推断出 trace、depth、breadth 和 gate 目标,并_连同该项目的 Nx 依赖项一起_追踪,因此这些依赖项目是运行范围内的项目,而不只是通过闭包触及的项目。前三个目标只会打印结果;gate 会决定退出代码,也是分支上运行的目标。被排除的项目仍保留三个会打印结果的目标,但不能使用 gate,因为它自己的代码不会被追踪。这个插件有意作为独立包发布:此 CLI 不依赖任何 Nx 相关内容。
--format 决定打印什么,而不是写入什么
打印和写入彼此独立,也可以同时启用。--format 为标准输出指定 markdown(默认值)、mermaid 或 json;项目自身或工作区自身的 write 下声明的目标位置,则决定写入文件的内容。优先使用 markdown,因为它适合在终端中阅读、粘贴到问题中,而且文件中已经使用这种格式。没有 write.format 字段——控制台渲染仅属于命令行的范畴。
提示,以及为什么它不会卡住脚本
callidescope、depth 和 breadth 都会提示输入命令行中缺失的值——depth 和 breadth 会提示缺失的 --addresses,三者都会提示缺失的 --format。没有用于关闭提示的标志,因为只有连接着终端才会触发提示:脚本、钩子或 CI 任务都没有连接终端,因此绝不会收到提示。此时,缺失的 --addresses 会被拒绝,并以非零状态码退出,因为没有其他方式可以提供它;缺失的 --format 则会使用其默认值 markdown,并继续运行。
拒绝输入是其中至关重要的一环。prompts 在 stdin 不是终端时不会报错——它会绘制菜单,却始终无法完成解析,让进程什么也没做就以状态码 0 退出——因此,必填值会在开始提示前先检查是否连接着终端,并将其报告为被拒绝的命令行。没有这项检查时,CI 运行若忘记传入参数,也会显示为成功。
工作区配置文件
支持 callidescope.config.{ts,mts,cts,js,mjs,cjs,json,jsonc} 中的任意一种;优先使用 TypeScript,因为它能进行类型检查。这是工作区配置,运行时必须通过 --config 指定的文件(Nx 目标的 configuration、插件的 configurationPath),不会自动搜索:此处为 configuration/callidescope.config.ts。
import { type CallidescopeConfiguration } from "@callidescope/configuration";
const callidescopeConfiguration: CallidescopeConfiguration = {
excludeFrom: ["configuration/.callidescopeignore"],
limits: { maximumDepth: 6 },
};
export default callidescopeConfiguration;
顶层配置共七个键:directories、entryPoints、exclude、excludeCallees、excludeFrom、limits 和 write。工作区文件还会在默认导出之外导出 projectDefaults——它是单独的绑定,不是此类型的成员,供项目自己的 callidescope.config.ts 展开使用。
limits
共有两个限制,均针对每个项目,且都可设门槛。
| 限制 | 默认值 | 含义 |
|---|---|---|
maximumDepth | 6 | 调用栈可包含的帧数,包含入口点 |
maximumBreadth | 无 | 一个可调用对象可直接调用的可调用对象数量 |
maximumBreadth 故意不设默认值。 在指定数值之前,广度只会报告,不会受门槛限制;如果执行 --check breadth,则会拒绝,而不是退回到一个无上限、看起来与通过检查毫无区别的限制。它还必须使用项目自身的数值,不能只依赖工作区的数值——请参阅项目可以设置什么。
确定首个 maximumDepth:先在不设门槛的情况下运行一次,查看最深的调用栈,再根据你希望达到的结构设置限制,而不是照搬代码当前的状态——如果限制刚好设为今天的最差值,就起不到门槛作用。
每个项目的每项限制都应通过边界测试来确定,而不是从某次运行的结果中读取一个数值。 写下一个候选值,运行该项目的门槛检查,然后将数值降低一档再运行:值得保留的限制应当在设定值处通过,在低一档时失败。限定范围的运行所打印的摘要涵盖整个跟踪,包括依赖闭包,因此报告的深度通常会超过受检项目自身的最深深度——只有门槛检查的结果才会指出哪些发现属于该项目。
限制现在只是一个模式字段,不再有其他含义。 模式严格,limits 只接受 maximumDepth 和 maximumBreadth。已弃用的字段——spreadThreshold、directSpreadThreshold、minimumCallers、callerMajorityRatio——会被拒绝,而不是被静默删除。这四项限制、它们调节的两项发现,以及背后的模块身份概念,都已从工具中完全移除——请参阅代码库的 ADR 0006《收窄 callidescope,仅保留深度和广度》。
实现候选项上限是常量,不是配置字段。 它决定结构化接口解析在何处停止猜测,这是解析过程自身的属性,而不是某次运行所评估的数值,因此它定义在 @callidescope/graph 中,不会写入任何 callidescope.config.ts。
entryPoints
| 选项 | 默认值 | 含义 |
|---|---|---|
addresses | 无 | 直接指定为根的可调用项,格式均为 <file>#<qualified-name> |
decorators | 13 个框架装饰器 | 框架会调用其方法的装饰器 |
includeExportedFunctions | true | 将每个 src/index.ts 导出项视为根 |
includeOrphans | true | 将仓库中没有任何调用方的可调用项提升为根 |
includeTests | false | 也追踪测试文件 |
addresses 用于声明要测量的包的表面范围,其格式与 depth 和
breadth 接受的格式相同,也与每个调用帧打印的格式相同,因此可以直接将报告中的
地址复制到配置中;如果一个文件中有多个同名声明,地址末尾还要加上 :<line>:
entryPoints: {
addresses: ["packages/foo/src/modules/read/read.service.ts#ReadService.read"],
},
声明的地址只会添加根,不会移除根:下方规则仍会先运行,孤立项仍会被提升, 而已经是根的可调用项仍只算一个根。
当某个包在调用图中的位置较低时,可以使用它:调用栈会归属于其根所在的项目,
而此类包发布的大部分内容都由上层调用,因此它不会产生根,对它设置的限制也就不会
起作用。
如果地址无法解析、解析到多个声明,或解析到无法解析的内容,整个运行都会失败
——callidescope-triage 会提供每条错误消息及其修复方法。
decorators 会替换内置列表,而不是在其上追加,因此配置若指定了自身框架的装饰器,
也应重新列出仍要保留的装饰器——如果报告中几乎所有内容都是孤立根,通常说明遗漏了
正在使用的装饰器。
includeOrphans 是安全网,而不是一项功能。 如果关闭它,缺少入口点规则会悄然将
整个子树从所有测量中移除;启用它后,这些内容会作为孤立根显现——它们可能是死代码,
也可能意味着需要添加规则。
将代码排除在运行之外
两个工具,分别用于回答不同的问题:
exclude接受 glob,并且会在内置默认项(node_modules、dist、coverage、output、.nx、.conformetry)的基础上追加排除项,因此配置指定自己的噪声项时 无需重新列出默认项。excludeFrom指定采用 gitignore 语法的文件,仅适用于工作区。这样可以将较长的 排除列表放在配置文件之外;专门设计为不合格的夹具包也应在此处排除。excludeCallees接受 glob,并与可调用项的显示名称(Type.member)匹配;匹配的 调用会从调用图中完全移除,不计入深度或广度。它适用于横切式检测:调用日志记录器 反映的是检测,而非其周围代码的形状;将其计入会导致一次与其他可调用项无关的更改 影响所有其他可调用项的数值。
exclude 和 excludeFrom 会从收集中移除_文件_;excludeCallees 会移除_边_。如果
本想用第二种却用了第一种,就会删掉真实的发现项。两者都不会将文件移出 ts.Program:
排除的文件仍会编译,对它的调用会变得无法继续追踪,而不是消失;并且任何 exclude
都无法取消对包含 tsconfig.json 的目录进行项目化。项目自身的 exclude 也有相同行为。
directories
与 --directories 接受的列表相同,一次设置,仅适用于工作区。
write
每个目标都是可选的,通常不会配置任何目标:未指定目标的运行会将结果报告到控制台,并在发现违规时以非零状态退出,因此它写入的内容不会过时。
| Key | Purpose |
|---|---|
write.json | 在 path 生成机器可读报告,并按 indentation 缩进。仅限工作区 |
write.markdown | 将一个由标记界定的区块插入 path |
write.mermaid | 同一个区块,但将调用栈绘制为一个流程图 |
write.mermaid 接受与 write.markdown 相同的键——它们的区别在于标记之间的内容,而不在于区块的放置方式——并且是独立配置项,因此一次运行可以同时发布两者:树状图说明每个调用帧接收什么、返回什么以及记录了什么,流程图则展示它们组合在一起的形状。
write: {
json: { indentation: 2, path: "output/callidescope.json" },
markdown: { path: "docs/call-stacks.md" },
mermaid: { path: "docs/call-stacks-diagram.md" },
},
两者都接受 description,显示在标题下方;heading(默认为 # 🔭 Callidescope;如果区块要插入已有标题的文件中,应设置此项,因为大多数 Markdown 检查工具会拒绝第二个一级标题);以及 previewCount——在其余内容折叠前显示多少个调用栈,默认为三个。previewCount 属于目标,而不是运行,因为它取决于区块将插入的文档:项目自己的 README 需要三个,而整个工作区的报告文件则需要全部显示。整个运行的区块会以汇总计数开头,每个项目占一行,并按各自的深度限制进行比较;随后是一个记分板,显示有多少项目超出、达到或低于各自的限制;最后是发现结果表。
不会将一个区块分发写入每个项目的 README。
每个项目都在自己的 callidescope.config.ts 中声明自己的 write.markdown(以及想要流程图时的 write.mermaid)——参见
项目可以设置什么。工作区自己的 write.markdown 才是真正关于整个工作区的区块:汇总计数、每个项目按自身深度限制列出的一行,以及记分板。
Markdown 目标还可以提供 render,用于替换内置表格;或提供 writeBlock,用于放置区块本身——该函数会获得 syncAnchoredBlock 和 wrapInAnchors,因此可以复用相同的插入逻辑。返回 false 会将目标报告为过时;返回其他任何值(包括 undefined)都视为最新。
每个被追踪项目都必须有自己的完整配置
每个被追踪项目的根目录下都要有一个 callidescope.config.ts,即存放使其成为项目的 tsconfig.json 的目录。配置文件只会在该目录中按名称查找,不会向上遍历;可使用相同的八种扩展名。
**每个被追踪项目都必须有配置文件,且每份配置都必须完整。**如果被追踪项目完全没有配置文件,系统会按名称拒绝该项目;如果配置文件漏掉某个字段,也会被拒绝。要求完整配置是有意为之:diff 中无法区分字段缺失和字段被设为原本会采用的默认值,但两者中只有一种是经过决定的——以前,未设置 maximumBreadth 和设置 maximumBreadth: undefined 分别表示“有人忘了设置”和“此项目限制深度而不限制广度”,而逐字段回退无法区分它们。参见仓库的 ADR 0007,“要求每个被追踪项目提供完整配置”。
运行指定的文件绝不会同时被当作某个项目的配置读取——每次运行中,一个文件只能承担一种角色。否则,如果某个包的任务指定了自身配置并追踪自身,该配置就会因为包含它合理设置的仅限工作区字段而被拒绝;因此,需要同时承担两种用途的包必须使用两个不同名称的文件。
项目可以设置的内容
| 字段 | 作用 |
|---|---|
entryPoints | 指定该项目中哪些可调用对象作为调用栈的根,包括 addresses |
limits.maximumDepth | 评估该项目中每个根调用栈时采用的深度上限 |
limits.maximumBreadth | 评估该项目声明的每个可调用对象时采用的广度上限 |
exclude | 指定该项目自身不进行跟踪的文件的 Glob |
write.markdown | 指定该项目自身已发布章节的写入位置 |
write.mermaid | 指定该项目自身图表的写入位置(如果需要) |
项目自身的目标位置和 exclude Glob 均以该项目的根目录为基准,而非工作区:在 packages/thing 自身的文件中,exclude: ["src/generated/**"] 指的是 packages/thing/src/generated/**;其中的 write: { markdown: { path: "docs/CALLS.md" } } 会写入 packages/thing/docs/CALLS.md。无论如何填写,都不会指向同级项目。目标位置设为 undefined 时,该项目不会发布任何内容。
本次运行自身的 exclude 仍以工作区为基准,并作为额外限制叠加,因此项目可以排除更多内容,但无法恢复本次运行已排除的内容。涉及多个项目的杂项仍应放在工作区文件中。
write.json 仅适用于工作区:它是本次运行的单份报告,不是可供项目重定向的内容。
其他所有字段都会按名称拒绝,且发生在任何跟踪开始之前;提示消息会列出上述六个字段,因此无需打开此技能即可采取相应操作。
先展开默认值,再覆盖
工作区配置会在默认导出旁导出 projectDefaults。项目展开它,并覆盖需要修改的值,因此完整文件只需一行:
import { projectDefaults } from "../../configuration/callidescope.config.js";
export default {
...projectDefaults,
limits: { maximumBreadth: undefined, maximumDepth: 10 },
};
两个限制都已命名,尽管只有一个是实际数字。maximumBreadth: undefined 表示该项目明确声明它限制的是深度而非广度——缺少字段无法区分这种情况与项目忘记填写的情况。write.mermaid: undefined 表示不发布图表,情况也是如此。
展开 projectDefaults,绝不要展开工作区的默认导出。 这两个对象有意设计成不同的:默认导出包含 directories、excludeFrom、write.json 等仅工作区可以设置的内容,因此展开它会导致拒绝,并指出第一个此类字段。projectDefaults 只包含项目有权设置的内容,因此展开它不会意外采用输出目标或忽略文件。
不会跨两个文件解析配置,也不再记录数字的来源。 判定项目所依据的每个数字,都写在该项目自己的文件中——只要项目文件存在,就不会再逐字段回退到工作区;也不再需要区分 declared 和 inherited,因为展开操作已经把数字放在这里,供读者查看。仍在描述限制“继承”而来,或项目 README 中仍有“Origin”列的文档,说的是此要求实施之前的工具——关于它取代的拒绝规则,请参阅 callidescope-triage 技能。
projectDefaults 中的工作区数字是默认值,而非上限:项目展开它之后再覆盖为更高的限制,仍会采用自己的值,因为由任何地方最复杂的调用栈确定的工作区数字,无法限制那些与之相差甚远的项目。
entryPoints 会整体替换,而非逐个成员合并:项目自行编写 entryPoints 对象时,会彻底替换展开得到的对象,用于该项目的可调用项。因此,项目若想同时使用工作区的装饰器列表和自己的 addresses,就必须通过 ...projectDefaults.entryPoints 显式保留装饰器列表。
为什么其他字段不能因项目而异。 maximumDepth 和 maximumBreadth 用于判定一次性构建的调用图,因此,两个项目针对同一组边提出不同的问题是合理的。excludeCallees、directories、excludeFrom 和 write.json 则不同:它们指定一次运行要读取什么、报告要写到哪里,或如何划分工作区,而项目不能采用与跟踪它的运行不同的设置。
重新查看完整配置集
每个项目的限制各写在一个文件中,已无法像过去那样在单个文件中审阅。现在可以通过 limits 命令将其作为一个集合来审阅:
npx callidescope limits --config configuration/callidescope.config.ts
这是一个 Markdown 表格,每个项目的每项限制占一行,包含 Project、Limit、Value、Declared in 四列,并指出每个数字写在哪个文件中。none 表示没有任何位置声明该限制;广度通常就是这种情况。workspace 行排在最前,也是唯一可以不带文件名的一行:它是每个项目自己的文件都会展开的默认值,并非某个文件为自身写入的数字。它只解析配置,不会测量任何内容,因此耗时是毫秒级,而非一次跟踪所需的时间。
修改上述任何内容后
配置变更通常会改变数字,导致所有已提交的报告过时。请在同一项变更中重新运行写入配置,并且务必在重新生成前运行 lint:每个栈帧都带有 file:line,因此,格式化工具若对类成员排序,会改变其后的行号;在排序前写入的报告一旦落地,就会立即过时。
运行被拒绝或报告显示已过时,请使用 callidescope-triage。要了解运行输出的内容,请使用 callidescope-trace。