dowse.json スキーマ
dowse analyze は解析結果を .dowse/dowse.json に出力します。CLI 表示・HTML レポート・MCP・CI 連携はすべてこの単一のファイルを消費します。再解析なしで複数の出力を得るためです。
スキーマは zod で定義され、出力時に必ず自己検証されます(出力 = スキーマ)。
schemaVersion 0 のあいだは互換保証がありません
フィールドの追加・変更が予告なく入ります。安定するまでは自動化に組み込む際に注意してください。
全体構造
ts
{
schemaVersion: 0,
meta: { ... }, // 生成日時・git の解析条件と除外件数
workspace: { ... }, // パッケージ一覧とツール検出結果
files: [ ... ], // hotspot score 降順
packages: [ ... ], // hotspotSum 降順
packageGraph: { ... }, // 宣言依存 + 実 import
coupling: { ... }, // change 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 // coupling のみ除外(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, // この smell が引いた点数
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 に生値と 2 種類の正規化をすべて保持しているのは、スコアを手で再計算できるようにするためです。
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 は phantom dependency です。
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
dowse analyze --json | jq '.files[0:5] | .[] | {path, score: .hotspot.score}'bash
# health が低い順に 10 件
dowse analyze --json | jq '[.files[] | {path, health: .health.score}] | sort_by(.health) | .[0:10]'