callidescope-trace
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRunning callidescope and reading what it says
运行 callidescope 并解读其输出
Callidescope builds one call graph for a TypeScript workspace and measures the
shape of it. It follows calls through injected dependencies — the hop from
into — which is the edge no
file-at-a-time tool can see, and where most of a framework codebase's control
flow actually lives.
this.someService.load()SomeService.loadThree commands, answering three different questions:
bash
npx callidescope # the whole workspace
npx callidescope depth --addresses src/foo.service.ts#FooService.bar # one callable, vertically
npx callidescope breadth --addresses src/foo.service.ts#FooService.bar # one callable, horizontallyA repository with no workspace configuration file is traced with defaults
rather than told to write one, so a bare run always produces something.
A traced project's own is a different matter: it
must exist and must be complete, or the run is refused before anything is
traced — see the skill. What a run gates on and what
it writes are both opt-in and both live in that skill; this one is about
reading the result.
callidescope.config.tscallidescope-configureCallidescope 为一个 TypeScript 工作区构建统一的调用图并衡量其结构。它会通过注入依赖(injected dependencies)跟踪调用——从 到 的跳转——这是逐文件工具无法看到的边,也是框架代码库中大多数控制流真正所在之处。
this.someService.load()SomeService.load三个命令分别回答三个不同的问题:
bash
npx callidescope # 整个工作区
npx callidescope depth --addresses src/foo.service.ts#FooService.bar # 单个 callable,垂直方向
npx callidescope breadth --addresses src/foo.service.ts#FooService.bar # 单个 callable,水平方向没有工作区配置文件的仓库会使用默认配置进行 trace,而不是提示用户编写配置,因此直接运行总能产生结果。
被 trace 的项目自身的 则不同:该文件必须存在且内容完整,否则运行会在开始 trace 之前直接拒绝——参见 skill。运行所执行的 gate 以及所写入的内容均为可选项,并且都在该 skill 中介绍;本 skill 只讨论如何解读结果。
callidescope.config.tscallidescope-configurecallidescope
: the whole workspace
callidescopecallidescope
:整个工作区
callidescopeReports two findings, both per project.
Deep call stacks. The single deepest path below each entry point, when it
exceeds . Only one path per entry point is ever built, so
a wide graph costs no more than a narrow one.
limits.maximumDepthBreadth. How many callables one callable calls directly. Reported always;
gated only when a project declares , which is the one
limit with no default anywhere.
limits.maximumBreadthEach finding carries the limit it was weighed against, because that limit is
a fact about the project owning the stack's root rather than about the run. One
report routinely holds several different numbers, and two identical stacks in
two projects can be a finding in one and silent in the other with nothing about
the code differing.
报告两类发现,且两类发现都按项目分别列出。
深层调用栈。 当每个 entry point 下方的最深路径超过 时报告。每个 entry point 始终只构建一条路径,因此宽图与窄图的计算成本相同。
limits.maximumDepth宽度。 一个 callable 直接调用了多少个 callable。始终报告;只有当项目声明了 时才会触发 gate,而这是唯一没有任何默认值的限制。
limits.maximumBreadth每项发现都会附带用于衡量它的限制,因为该限制是拥有 stack 根节点的项目的一项事实,而不是本次运行的一项事实。一份报告通常会包含多个不同的数值;两个项目中的两条相同 stack,在一个项目中可能构成发现,在另一个项目中则可能不产生任何输出,而代码本身并无差异。
Reading a stack
解读调用栈
text
Stack #1 | 🚨 [DEPTH ≥ 10 > 6] (decorated-method)
🚀 SomeCommand.run(options: SomeOptions): Promise<void> [.../some.command.ts:220]
↳ Measure the repository and write every configured output.
└─> SomeService.measure(args: MeasureArguments): Result [.../some.service.ts:115]
↳ Measure aggregated statistics for the provided directory.- is a floor, not a measurement. Something on that path could not be followed — a callback invoked through a parameter, a computed member name — and the run says so rather than quietly under-reporting. The real depth is at least ten. Not a defect, and not a number to distrust:
≥ 10against a limit of 6 is a genuine failure.≥ 10in the summary counts them.Unfollowable calls - The parenthesized kind is the entry-point rule that claimed the root.
means nothing in the repository calls it: either dead code, or an entry-point rule the configuration is missing.
orphan-root - Every frame carries , so the next step is opening one.
file:line - Each frame is annotated from the type checker — the signature, and the
JSDoc prose collapsed to one line — which is what makes a stack readable
rather than a list of places to go look. A frame printed had a signature over 80 characters, almost always a constructor taking a dozen injected services. A summary over 120 characters prints only its opening sentence, unmarked; only a single sentence with no boundary to cut on is trimmed and marked
(…): ReturnType.…
Shortening applies to the printed tree only. The JSON report carries every
comment in full, so a script wanting complete text should read JSON rather than
parse the tree.
The mermaid rendering draws all the stacks as one flowchart, not one
apiece. A single stack is a straight line, and a straight line is a list with
extra steps; drawn together the shared tails converge — every command reaching
the same repository, every resolver ending in the same service — and that
convergence is what a picture shows and an indented tree cannot. Entry points
are stadiums and everything else boxes, shape rather than color because the
diagram is read in whichever theme the reader has. A diagram stops at 300
callables, drops whole stacks rather than trimming so it never contains an edge
into something it did not draw, and says how many it left out.
text
Stack #1 | 🚨 [DEPTH ≥ 10 > 6] (decorated-method)
🚀 SomeCommand.run(options: SomeOptions): Promise<void> [.../some.command.ts:220]
↳ Measure the repository and write every configured output.
└─> SomeService.measure(args: MeasureArguments): Result [.../some.service.ts:115]
↳ Measure aggregated statistics for the provided directory.- 是下限,而不是实测值。 该路径上的某些内容无法继续跟踪——例如通过参数调用的 callback、计算得到的成员名称——运行会明确说明,而不会悄悄少报内容。实际深度至少为十。这不是缺陷,也不是一个不应信任的数值:
≥ 10对比限制 6,确实属于失败。摘要中的≥ 10会统计这些调用。Unfollowable calls - 括号中的 kind 是认领该根节点的 entry-point 规则。 表示仓库中没有任何内容调用它:它可能是死代码,也可能是配置中缺少了相应的 entry-point 规则。
orphan-root - 每个 frame 都带有 ,因此下一步可以直接打开对应位置。
file:line - 每个 frame 都由 type checker 提供的注释信息进行标注——包括函数签名,以及压缩为单行的 JSDoc 文本——这正是 stack 易于阅读的原因,而不只是提供一串需要自行查找的位置。某个 frame 显示为 ,说明其签名超过 80 个字符;这几乎总是指一个注入了十多个 service 的 constructor。超过 120 个字符的摘要只会打印首句,且不做标记;只有当摘要仅有一句话、没有可切分的边界时,才会进行裁剪并标记为
(…): ReturnType。…
缩短仅作用于打印出的树。 JSON 报告会完整保留每条注释,因此需要完整文本的脚本应读取 JSON,而不是解析树状输出。
mermaid 渲染会将所有 stack 绘制成一张流程图,而不是每条 stack 各画一张。单独的 stack 是一条直线,而带额外步骤的直线本质上只是列表;将它们放在一起后,共享的尾部会汇聚——每个访问同一 repository 的 command、每个终止于同一 service 的 resolver——这种汇聚关系正是图片能够呈现、而缩进树无法呈现的。Entry point 使用 stadium 形状,其他节点使用 box;使用形状而非颜色,是因为图表需要在读者所采用的主题中阅读。图表最多绘制 300 个 callable;如果超出限制,它会丢弃整条 stack,而不是裁剪路径,因此不会包含指向未绘制节点的边,并会说明省略了多少内容。
Reading a per-project verdict
解读单项目结论
In a workspace using , the thing a branch actually runs is one
project's , and what it prints is not a smaller report. It prints the
two findings it weighed and nothing else:
@callidescope/nxgatetext
undefined在使用 的工作区中,branch 实际运行的是单个项目的 ,其输出并不是一份缩小版报告。它只打印参与判定的两类发现,不包含其他内容:
@callidescope/nxgatetext
undefinedCall stacks over the depth limit (1)
Call stacks over the depth limit (1)
...the stack, as a tree...
...the stack, as a tree...
Callables over the breadth limit (1)
Callables over the breadth limit (1)
- — 3 direct callees, limit 2 (…/gated-leaf.ts)
GatedLeafService.read
Four things to hold on to when reading one:
- **The findings are the judged project's own.** A gate traces its project
together with that project's Nx dependencies, and then judges only what the
project it is named after owns. A dependency's breach is that dependency's own
gate's business — so a gate that failed is telling you about one project, and
the fix is that project's code or that project's own `callidescope.config.ts`.
- **A gate's exit code is the verdict and nothing else.** It reads no
destination, so a failing gate has changed no committed file, and a passing
one has published nothing.
- **The summary's `Deepest stack` is not the number the verdict used.** A
scoped run's counts describe the whole trace, dependency closure included, so
the deepest stack it reports is routinely deeper than anything the judged
project owns. Take the number a verdict gives you, never the one a summary
line prints. The same caution applies to a `🔭 Finished an analysis` log
line — see the `callidescope-triage` skill.
- **`## Read nothing of its own (0 files)` is a failure about the run, not the
code.** The gate judged a project whose own sources it never opened, so a
green verdict would mean only that it never looked. The `trace` target prints
the same block and passes; the gate is the one that fails on it.
The sibling `trace` target prints the whole report instead — the summary, a
`Projects` index with one row per project against **its own** limit, and a
`Depth headroom` scoreboard. Reach for `trace` to understand a project, and
read a `gate` to understand why a pipeline is red.- — 3 direct callees, limit 2 (…/gated-leaf.ts)
GatedLeafService.read
解读时需要把握以下四点:
- **这些发现属于被判定项目自身。** Gate 会连同该项目的 Nx 依赖一起 trace 项目,但只判定由项目自身拥有的内容。依赖项违反限制,应由该依赖项自己的 gate 负责。因此,失败的 gate 告诉你的是某一个项目的问题,修复对象也应是该项目的代码或该项目自己的 `callidescope.config.ts`。
- **Gate 的 exit code 就是结论,且仅有这一层含义。** 它不会读取任何 destination,因此失败的 gate 不会修改任何已提交文件,通过的 gate 也不会发布任何内容。
- **摘要中的 `Deepest stack` 不是结论所采用的数值。** 限定范围的运行所统计的是整个 trace,包括依赖闭包,因此它报告的最深 stack 经常比被判定项目自身拥有的内容更深。应采用结论给出的数值,绝不要使用摘要行打印的数值。对 `🔭 Finished an analysis` 日志行也应保持同样的谨慎——参见 `callidescope-triage` skill。
- **`## Read nothing of its own (0 files)` 表示运行失败,而不是代码失败。** Gate 判定了一个项目,却没有打开该项目的任何源文件,因此绿色结论只意味着它根本没有检查代码。`trace` target 会打印相同的区块并通过;只有 `gate` 会因此失败。
同级的 `trace` target 会改为打印完整报告,包括摘要、每行对应一个项目并采用**各自**限制的 `Projects` 索引,以及 `Depth headroom` 记分板。需要了解一个项目时使用 `trace`;需要理解 pipeline 为何变红时阅读 `gate`。Reading a committed project block
解读已提交的项目区块
Every traced project's own readme carries a section, and
its table is what says which numbers that project is held to:
## 🔭 Callidescope### Limitstext
| Limit | Value |
| --- | --- |
| `maximumDepth` | 4 |
| `maximumBreadth` | none |There is no origin column, and no second origin left to record. Every
traced project's own is complete — it spreads the
workspace's and overrides what it means to — so both numbers
are written in the file beside this readme, or the run refused to start before
anything was traced. means that project's own file names no breadth
limit — see the skill for why that is a deliberate
statement rather than an omission.
callidescope.config.tsprojectDefaultsnonecallidescope-configureRead the row of the summary above it against the depth limit
here: the two together are the whole of what that project's gate decides.
Deepest stack每个被 trace 的项目自己的 README 都包含一个 章节,其中的 表用于说明该项目受哪些数值约束:
## 🔭 Callidescope### Limitstext
| Limit | Value |
| --- | --- |
| `maximumDepth` | 4 |
| `maximumBreadth` | none |表中没有 origin 列,也不存在需要记录的第二个来源。 每个被 trace 的项目自己的 都是完整的——它会展开工作区的 ,再覆盖项目自身的定义——因此两个数值都会写入该 README 旁的配置文件中;否则运行会在开始 trace 之前直接拒绝。 表示该项目自己的文件未声明 breadth 限制——参见 skill,了解为什么这是有意作出的声明,而不是遗漏。
callidescope.config.tsprojectDefaultsnonecallidescope-configure将上方摘要中的 行与这里的 depth 限制结合阅读:二者共同构成该项目的 gate 所能判定的全部内容。
Deepest stackAddressing one callable
寻址单个 callable
depthbreadth<file>#<qualified-name>#A file holding more than one declaration under the same qualified name — two
overloads, two callbacks bound to the same property — is disambiguated with a
trailing :
:<line>bash
npx callidescope depth --addresses src/foo.service.ts#FooService.bar:118When it cannot tell which one was meant, the run says so and prints every
candidate's line, so the disambiguated address is a copy away.
Neither command writes anything, compares a destination, or takes ,
, , , , , or
. A lookup only ever prints. Both do take every flag that
shapes the graph — , the entry-point overrides, and the two
exclusion lists — plus and , because resolving an address
still means tracing the workspace first.
--check--write--json--markdown--mermaid--maximum-depth--maximum-breadth--directories--config--formatdepthbreadth<file>#<qualified-name>#如果一个文件在同一 qualified name 下包含多个声明——例如两个 overload、两个绑定到相同属性的 callback——可在末尾添加 进行区分:
:<line>bash
npx callidescope depth --addresses src/foo.service.ts#FooService.bar:118如果无法确定指的是哪一个声明,运行会明确说明,并打印每个候选声明所在的行;只需复制带行号的地址即可。
这两个命令都不会写入内容、比较 destination,也不接受 、、、、、 或 。Lookup 只会打印结果。两者都接受所有用于塑造调用图的 flag——、entry-point override,以及两个 exclusion list——还接受 和 ,因为解析地址仍然需要先 trace 整个工作区。
--check--write--json--markdown--mermaid--maximum-depth--maximum-breadth--directories--config--formatbreadth
: what it calls, and what calls it
breadthbreadth
:它调用了什么,以及谁调用了它
breadthPrints the callable's direct callees and direct callers side by side — the
two questions a refactor or a rename needs answered together, before either one
is safe.
- A rename. The callers are the exhaustive list of what has to be updated.
This is the one question a text search answers badly in a
dependency-injected codebase: a call through is found by the type checker and missed by a grep for the class name.
this.someService.load() - An extraction. Extracting a responsibility out of a wide callable means moving some subset of the callees it names. The callee list is the raw material for choosing the seam — look for the subset that shares a concern, and take those.
- An inline. A callable with one caller and few callees is a candidate for folding into that caller. Breadth confirms the "one caller" part rather than assuming it.
It reports on a callable nobody has flagged. It does not need a finding to be
worth running.
并排打印 callable 的直接 callee 和直接 caller——这是重构或重命名前需要一起回答的两个问题。
- 重命名。 Caller 列表就是所有必须更新的内容的完整清单。在依赖注入的代码库中,这是文本搜索很难正确回答的问题:类型检查器能够找到通过 发起的调用,而搜索 class 名称的 grep 会漏掉它。
this.someService.load() - 提取。 要从宽 callable 中提取某项职责,就需要移动它所列出的_callees 中的某个子集_。Callee 列表是选择切分位置的原始材料——寻找共享同一关注点的子集,并将其提取出来。
- 内联。 只有一个 caller 且只有少量 callee 的 callable,是折叠进该 caller 的候选。Breadth 可以确认“只有一个 caller”这一部分,而不必依赖假设。
即使 callable 没有被标记为问题,也值得对它运行此命令。它不需要以发现项作为运行前提。
depth
: every chain above and below
depthdepth
:上方与下方的每一条链
depthPrints every path above the callable and every path below it — every caller
chain up to a root, every callee chain down to a leaf — rather than folding
each direction into the single deepest one that 's own report
keeps.
callidescopeThat difference is the point. The workspace report answers "how deep does this
get", so one path per entry point is enough. answers "what is this
callable actually part of", and a callable reached from a dozen places, or
reaching a dozen leaves, is exactly the shape it is asked to show in full.
depthEach direction is capped at 200 paths, and a capped run says so. The cap exists
because a widely-called utility whose callees fan out just as wide multiplies
those branches together — enumerating every path is not bounded by construction
the way one deepest path is.
- Turning a depth finding into a plan. The workspace report names the
stack; against a frame in the middle of it shows every other chain that frame participates in, which is what tells you whether collapsing a forwarding layer is safe or whether three other callers depend on it.
depth - Testing a "this looks misplaced" hunch. The caller trees show where the callable is really used from, in full rather than folded into one deepest path.
打印 callable 上方和下方的每一条路径——一直到 root 的每条 caller 链,以及一直到 leaf 的每条 callee 链——而不是像 自身报告那样,将每个方向折叠成一条最深路径。
callidescope这正是两者之间的区别。工作区报告回答的是“它最多能深入到什么程度”,因此每个 entry point 只需要一条路径。 回答的是“这个 callable 实际上属于什么的一部分”;一个从十多个位置被调用,或能够到达十多个 leaf 的 callable,正是该命令需要完整展示的结构。
depth每个方向最多包含 200 条路径,达到上限时运行会明确说明。设置上限是因为,一个被广泛调用、且其 callee 同样广泛分叉的 utility,会将这些分支相乘;与只计算一条最深路径不同,枚举所有路径本身并没有边界保证。
- 将 depth 发现转化为计划。 工作区报告会指出 stack;对 stack 中间某个 frame 运行 ,可以展示该 frame 参与的_其他所有_链路,从而判断折叠转发层是否安全,或是否有另外三个 caller 依赖它。
depth - 验证“这里看起来放错了位置”的直觉。 Caller 树会完整展示 callable 实际从哪里被使用,而不是将其折叠为一条最深路径。
Which one to reach for
应该选择哪个命令
| The question | The command |
|---|---|
| Is anything in this workspace too deep or too wide? | |
| What breaks if I rename this? | |
| Where do I cut this callable in two? | |
| Can I inline this? | |
| Can I collapse this layer? | |
| What is this callable actually part of? | |
| Does this belong in this file? | |
| 问题 | 命令 |
|---|---|
| 这个工作区中是否有任何内容过深或过宽? | |
| 如果我重命名这个,会有什么受到影响? | |
| 我应该在哪里把这个 callable 拆成两个? | |
| 我可以把这个内联吗? | |
| 我可以折叠这一层吗? | |
| 这个 callable 实际上属于什么的一部分? | |
| 它应该放在这个文件中吗? | |
What a scoped run measures
限定范围的运行会测量什么
--directoriesHow to read a depth that moved. A project's own never lists
the packages it imports, so a scoped run used to leave every call out of the
named directory in code no traced project owned — and a call into unowned code
is treated as external, a leaf, exactly like a call into an installed package.
The stack ended at the package boundary and printed a plain number, with nothing
in the report saying it had stopped early. A number that went up without an edit
to explain it is that under-measurement being corrected.
tsconfig.jsonThree things follow, and they are what a moved number should be checked against:
- A scoped depth is a measurement, not a floor. still means only an unfollowable call. Scope never printed one, which is exactly why a scoped under-measurement was invisible.
≥ - The number does not depend on which run took it. A file is owned by the deepest project root containing it, whichever program pulled it in, so two scoped runs and the whole-workspace run agree about the same callable — provided each of them builds the project declaring it, which a refused destination's is not.
- A whole-workspace run's closure changes nothing. It names no directory, so every project is already a starting project and its closure is the workspace. A number that moved there moved for some other reason.
What a closure does not reach, and why each one is left out:
- Dependents, deliberately. The walk runs downward only. A project that imports the scoped one is not built and its stacks do not appear — which is what stops an edit in a dependent from moving the scoped project's numbers.
- A project root holding no . A manifest is what makes a directory something another project can depend on; a root holding only a
package.jsonis shared settings, read by every project rather than depended on by any. Without the rule, one such directory drags the whole workspace in.tsconfig.json - The workspace root. A project whose root contains every other project cannot be a meaningful dependency of any of them.
Both refusals apply to a destination only, so either kind is still traced in
full when named directly or by a run that names no directory. What they cost is
that a call into a refused directory resolves to no frame.
Depth and breadth both survive a downward-only scope, which is worth saying
because it is not obvious: both fold over a callable's transitive callees,
which run downward — precisely what a closure holds in full. Neither needs a
dependent.
Publishing does not widen with measurement. Each project's own
(and , when it declares one) is written only for
the projects a run was scoped to, so a scoped run never rewrites a section in a
dependency it merely measured.
write.markdownwrite.mermaidA worked example — the projects one real scoped run reaches, and why each — is
in the package.
examples/dependency-closure@callidescope/examples--directories如何解读发生变化的 depth。 项目自己的 从不列出它所 import 的 package,因此限定范围的运行过去会遗漏指定目录之外、且不属于任何被 trace 项目的代码调用。对不属于任何项目的代码所发起的调用会被视为外部 leaf,与调用到已安装 package 完全相同。Stack 会在 package 边界处终止,并打印一个普通数值,报告中没有任何信息表明它提前停止了。如果没有相应编辑来解释 depth 的增长,这就意味着之前漏报的深度得到了修正。
tsconfig.json由此可得以下三点,也正是在 depth 发生变化时需要检查的内容:
- 限定范围得到的 depth 是测量值,而不是下限。 仍然只表示存在无法跟踪的调用。限定范围的运行过去从不打印该标记,而这正是范围限定导致的漏报长期未被发现的原因。
≥ - 数值不取决于取自哪一次运行。 一个文件归其包含该文件的最深层 project root 所有,无论哪个 program 将其拉入分析;因此两次限定范围的运行与整个工作区的运行对同一 callable 会得出相同结果——前提是它们都构建了声明该 callable 的项目,而被拒绝的 destination 不会满足这一条件。
- 整个工作区运行的闭包不会改变任何内容。 它不指定任何目录,因此每个项目都已经是起始项目,其闭包就是整个工作区。在这种情况下数值仍发生变化,原因必然在其他地方。
闭包无法到达什么,以及为什么每种内容都会被排除:
- 依赖方,这是有意为之。 遍历只向下进行。Import 限定范围项目的项目不会被构建,其 stack 也不会出现——这正是依赖方中的编辑不会改变被限定项目数值的原因。
- 不包含 的 project root。 Manifest 才使一个目录成为其他项目可以依赖的对象;只包含
package.json的 root 是共享配置,会被每个项目读取,却不会被任何项目依赖。如果没有这条规则,这样的一个目录会将整个工作区都拖入闭包。tsconfig.json - 工作区 root。 Root 包含所有其他项目的项目,不可能成为其中任何项目的有效依赖。
这两种拒绝规则都只适用于 destination,因此当某种目录被直接指定,或运行未指定任何目录时,它仍会被完整 trace。其代价是,调用到被拒绝目录时无法解析出 frame。
Depth 和 breadth 在仅向下的范围限定下仍然有效,这一点值得强调,因为它并不明显:两者都会对一个 callable 的传递性 callees 进行折叠,而遍历方向恰好是向下的,闭包会完整保留这些内容。两者都不需要 dependency。
发布范围不会随测量范围扩大。 每个项目自己的 (以及声明了 时的对应输出)只会为运行所限定的项目写入,因此限定范围的运行绝不会重写仅仅被测量的 dependency 中的章节。
write.markdownwrite.mermaid一个实际示例——某次限定范围的运行会到达哪些项目,以及到达每个项目的原因——位于 package 的 中。
@callidescope/examplesexamples/dependency-closureWhat the graph does and does not contain
调用图包含和不包含什么
The same resolution rules govern all three commands, and they decide what any
of them can tell you.
| Written as | Resolved to |
|---|---|
| The symbol at the callee, unwrapped through import aliases |
| The symbol at the member name — the injected-dependency case |
| Every class structurally satisfying the interface, capped |
| The base declaration the checker resolves to |
| The constructor, when it has a body |
| The callback, as its own frame — |
| Nothing. Recorded as unfollowable rather than guessed |
Four consequences worth holding on to:
- Structural matching is not optional, because classes routinely satisfy an
interface without writing . It also means a caller list can contain a class that never actually calls the callable at runtime — that cap is a fixed constant in
implementsrather than a configured limit, so a result that looks implausibly wide is a fan-out to inspect rather than a number to raise.@callidescope/graph - A computed member call resolves to nothing. A caller reaching the
callable that way will not appear, so does not fully cover a rename in a codebase that dispatches through computed names.
breadth - Calls into installed dependencies are leaves. Whether
is deeply implemented says nothing about whether your layering is too deep, and counting it would move every number on an unrelated upgrade. A call into another project of the same workspace is not one of these — it resolves to a real frame, however the run was scoped.
Array.prototype.map - globs are dropped from the graph entirely, so a callable the configuration excludes — typically a logger — appears in no list and counts toward nobody's depth or breadth.
excludeCallees
Cycles are collapsed before depth is measured, so a mutually recursive
cluster of three contributes three frames once — an honest floor on a stack
that has no ceiling. That is why the numbers do not move between runs:
detecting a repeat visit mid-walk would make the answer depend on which path
arrived first, and a linter whose numbers move on its own is not usable as a
gate.
Depth is only meaningful relative to a root, and most code in a framework
codebase is called by the framework rather than by the repository. Roots are
therefore configurable — decorated methods, lifecycle hooks, bootstraps, index
exports — and anything left with no caller is promoted to an orphan root rather
than dropped.
三个命令都遵循相同的解析规则,而这些规则决定了它们能够提供哪些信息。
| 写法 | 解析结果 |
|---|---|
| 被调用方对应的 symbol,并解析 import alias |
| 成员名称对应的 symbol——即注入依赖场景 |
| 所有在结构上满足该 interface 的 class,数量有上限 |
| type checker 解析到的 base declaration |
| 具有函数体的 constructor |
| callback 本身作为独立 frame—— |
| 无法解析;记录为 unfollowable,而不是进行猜测 |
有四点需要牢记:
- 结构匹配不是可选项,因为 class 经常在未显式编写 的情况下满足 interface。这也意味着 caller 列表中可能包含一个在运行时从未真正调用目标 callable 的 class——该上限是
implements中的固定常量,而不是可配置限制,因此,如果结果宽得不太合理,应检查其扇出,而不是调高数值。@callidescope/graph - 计算成员调用无法解析。 通过这种方式到达目标 callable 的 caller 不会出现,因此,在通过计算名称进行分发的代码库中,无法完整覆盖一次重命名。
breadth - 调用已安装依赖会到达 leaf。 内部实现得有多深,与 你的 分层是否过深毫无关系;将其计入深度,会导致无关的依赖升级改变所有数值。调用同一 workspace 中的其他项目不属于这种情况——无论运行范围如何限定,它都会解析为真实 frame。
Array.prototype.map - glob 会从调用图中彻底移除,因此被配置排除的 callable——通常是 logger——不会出现在任何列表中,也不会计入任何项目的 depth 或 breadth。
excludeCallees
Cycle 会在计算 depth 前被折叠,因此一个包含三个节点的相互递归 cluster 只贡献三个 frame——对于没有上界的 stack 而言,这是诚实的下限。这也是数值不会在多次运行之间自行变化的原因:如果在遍历中途检测到重复访问,结果就会取决于哪条路径最先到达,而一个数值会自行变化的 linter 无法作为 gate 使用。
Depth 只有相对于 root 才有意义,而在框架代码库中,大多数代码是由框架而非仓库自身调用。因此 root 是可配置的——例如 decorated method、lifecycle hook、bootstrap、index export——任何没有 caller 的内容都会被提升为 orphan root,而不是直接丢弃。
After reading
解读完成之后
To narrow, gate, or publish a run, and to change any of the thresholds above,
reach for the skill. When a run fails or a report
reads stale, reach for .
callidescope-configurecallidescope-triageA refactor moves the call graph, which makes every committed report stale.
Re-run the write configuration once the change lands, and lint before
regenerating: every frame carries a , so a formatter that sorts
class members moves the line numbers of everything after it, and a report
written before that sort is stale the moment it lands.
file:line若要限定运行范围、执行 gate、发布运行结果,或修改上述任何阈值,请使用 skill。运行失败或报告内容过期时,请使用 skill。
callidescope-configurecallidescope-triage重构会改变调用图,从而使所有已提交报告过期。变更落地后,应重新运行 write 配置;在重新生成报告之前先执行 lint:每个 frame 都带有 ,因此,如果 formatter 对 class member 进行了排序,其后所有内容的行号都会变化,而在排序前生成的报告会在变更落地后立即过期。
file:line