dowsing.json 结构
dowsing analyze 把结果写入 .dowsing/dowsing.json。CLI 显示、HTML 报告与 CI 集成全都消费这一个文件——因此无需重新分析就能得到多种输出。
结构用 zod 定义,并在每次写出时自我校验:输出即结构。
schemaVersion 为 0 期间没有兼容性保证
字段可能在没有预告的情况下增加或变更。在它稳定之前,把它接入自动化时请谨慎。
整体形状
ts
{
schemaVersion: 0,
meta: { ... }, // 生成时间、git 窗口、排除计数
workspace: { ... }, // 包列表与检出的工具
files: [ ... ], // 按 hotspot 分数降序
packages: [ ... ], // 按 hotspotSum 降序
packageGraph: { ... }, // 声明的依赖 + 实际的 import
coupling: { ... }, // 变更耦合(无 git 历史时为 null)
findings: [ ... ], // 合成洞察,按 severity 降序
authors: [ ... ], // 贡献者,已做 mailmap 归一与 bot 剔除
config: { ... } // 所用的公式、阈值与权重
}meta
ts
{
generatedAt: string, // ISO 8601
repoRoot: string,
toolVersion: string,
git: {
since: string,
until: string | null,
commitCount: number, // 排除之后
shallow: boolean, // 为 true 时不要相信结果
exclusions: {
merges: number,
bots: number,
formatOnly: number,
ignoreRevs: number,
largeChangesets: number // 只从耦合中排除,churn 中保留
}
} | null // 没有 git 历史时为 null
}排除计数总是包含在内,因为一个你看不出范围的数字,是无法据以行动的。
files[]
ts
{
path: string, // 相对仓库根目录,posix 风格
package: string | null,
lines: number,
loc: number, // 有效行数,剔除注释与空行
complexity: {
cyclomatic: number, // 文件合计
cyclomaticMax: number, // 最复杂的函数
cognitive: number,
cognitiveMax: number
},
churn: {
commits: number,
authors: number,
addedLines: number,
deletedLines: number,
firstSeen: string,
lastChanged: string
} | null,
hotspot: {
score: number, // rank(freq) × rank(complexity)
components: {
changeFrequency: number, // 原始值
complexity: number, // 原始值
frequencyRank: number, // 百分位
complexityRank: number,
frequencyMinmax: number, // 同时保留 min-max 归一化
complexityMinmax: number
}
},
churnVerification: { // 仅在 --verify-churn 时存在
rawCommits: number,
effectiveCommits: number,
effectiveRatio: number,
breakdown: {
changed: number,
"rename-only": number,
"format-only": number,
identical: number,
unknown: number
}
} | null,
health: {
score: number, // 1.0–10.0
smells: [{
kind: "complex-method" | "high-cognitive-complexity"
| "large-file" | "many-arguments",
severity: number, // 0–1
penalty: number, // 这个坏味道扣掉的分数
value: number, // 实测值
threshold: number,
locations: string[], // 函数名与行号
explanation: string // 为什么是问题
}],
locBaselineScore: number, // 只用 LoC 会得到的分数
baselineDelta: number // score − locBaselineScore
},
ownership: {
mainDeveloper: string | null, // 按新增行数
mainDeveloperShare: number,
minorContributors: number, // ownership < 5%
contributors: number
} | null
}hotspot.components 保留原始值与两种归一化,是为了让你可以手工复算这个分数。
packages[]
ts
{
package: string,
files: number,
loc: number,
churnCommits: number,
hotspotSum: number,
hotspotLocWeighted: number,
health: number | null, // LoC 加权平均
busFactor: {
busFactor: number,
files: number,
orphanedRatio: number,
keyDevelopers: [{ email: string, ownedFiles: number }]
} | null
}packageGraph
ts
{
nodes: string[],
edges: [{
from: string,
to: string,
declared: boolean, // 在 package.json 中声明
imported: boolean, // 实际被 import
importingFiles: number,
typeOnly: boolean // 全部是 type-only import
}]
}declared && !imported 是无用依赖;!declared && imported 是幽灵依赖。
coupling
ts
{
filters: { // 应用的过滤条件——公式透明性
minRevisions: number,
minShared: number,
minDegree: number,
maxChangesetSize: number,
limit: number
},
excludedLargeChangesets: number,
files: [{ a, b, shared, revisionsA, revisionsB,
degree, confidenceAtoB, confidenceBtoA }],
packages: [{ ...同上,
kind: "hidden" | "type-only" | "declared-only" | "obvious",
dependencyDirection: "a->b" | "b->a" | "both" | null
}]
} | nullfindings[]
ts
{
kind: "hotspot-file" | "hidden-coupling" | "hotspot-package"
| "bus-factor" | "unhealthy-package" | "unused-dependency",
target: string,
severity: number,
summary: string,
evidence: Record<string, string | number> // 背后的数字
}evidence 总是携带分数的依据。没有依据的 finding 不会被输出。
config
ts
{
complexity: "loc" | "cyclomatic" | "cognitive",
healthThresholds: { cyclomatic, cognitive, fileLoc, arguments },
healthWeights: Record<string, number>
}阈值与权重是输出的一部分,这样复现这个分数所需的一切都在文件里。
示例
bash
dowsing analyze --json | jq '.files[0:5] | .[] | {path, score: .hotspot.score}'bash
# health 最低的十个
dowsing analyze --json | jq '[.files[] | {path, health: .health.score}] | sort_by(.health) | .[0:10]'