Skip to content

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
  }]
} | null

findings[]

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]'

MIT ライセンスで公開されています