Contents

brunogama/swiftdebt

A Swift CLI, reusable analysis library, SwiftPM command plugin, and incremental build-tool plugin based on SCMA: A Lightweight Tool to Analyze Swift Projects, by Fazle Rabbi, Syeda Sumbul Hossain, and Mir Mohammad Samsul Arefin.

Contents

Detailed references: paper mapping, measurement definitions, plugin integration, release process, validation record.

An optional SQVector integration links SQVector's static product on its supported Apple platforms. It is separate from the base package.

Installation

Add the released package and the products you need to your package manifest:

// swift-tools-version: 6.2
import PackageDescription

let package = Package(
    name: "MyTools",
    dependencies: [
        // swiftdebt-release-version:start
        .package(url: "https://github.com/brunogama/SwiftDebt.git", from: "0.10.0")
        // swiftdebt-release-version:end
    ],
    targets: [
        .executableTarget(
            name: "DebtTool",
            dependencies: [
                .product(name: "SwiftDebtKit", package: "SwiftDebt")
            ]
        ),
        .target(
            name: "AppCore",
            plugins: [
                .plugin(name: "SwiftDebtBuildPlugin", package: "SwiftDebt")
            ]
        ),
    ]
)

Depending on the package also exposes the swift-debt command plugin. The build plugin requires a .swift-debt.json file in the consuming package root; an empty {} is valid.

To install the standalone CLI from a tagged release:

# swiftdebt-release-version:start
git clone --branch v0.10.0 --depth 1 https://github.com/brunogama/SwiftDebt.git
# swiftdebt-release-version:end
cd SwiftDebt
swift build -c release
.build/release/swift-debt --version
.build/release/swift-debt analyze /path/to/project

Tutorials and documentation

The published Swift-DocC site includes these practical tutorials:

Quick start

From the extracted package directory, using a Swift 6.2.x toolchain:

swift build
swift test
swift run swift-debt analyze Examples/Sources
swift run swift-debt analyze Examples/Sources --format json
swift run swift-debt analyze Examples/Sources --format html --output .swift-debt/report.html

The initial normal build resolves and compiles the pinned SwiftSyntax dependency. Once built, the analyzer itself does not need network access, the target application's dependencies, a Python server, or a successful target build. The included Python scripts are optional verification tools, not runtime dependencies.

Analyze your project, including non-class Swift types, with a bounded number of parsing jobs:

swift run swift-debt analyze /path/to/project \
  --type-scope nominals \
  --jobs 4 \
  --format json \
  --output .swift-debt/project.json

An explicit input directory becomes the configuration/discovery root. For example, analyze /project/Sources looks for /project/Sources/.swift-debt.json, not /project/.swift-debt.json; use --config /project/.swift-debt.json when needed. Output paths are relative to the current working directory, not the scanned root.

Supported SCMA-paper formats are text, json, csv, html, and diagnostics. HTML is standalone and script-free. JSON is the full machine-readable SCMA report; CSV is a flat observation export, not an equivalent serialization of the report. Ranked debt analysis can additionally render debt-json, debt-markdown, debt-dot, debt-text, debt-compact, debtmap-json, and debt-dashboard when debtAnalysis is configured. debtmap-json is a deterministic compatibility projection for Debtmap-oriented automation, not the native SwiftDebt debt model. debt-dashboard is a self-contained browser dashboard over the native debt-report schema. The closed conformance record supports Debtmap 0.23.0 workflow/capability parity for Swift for the frozen Swift workflow matrix. Canonical outputs are checked in as golden fixtures, and release evidence is in the Debtmap acceptance record.

Git-history recency in ranked debt analysis requires an explicit ISO-8601 reference time so repeated runs remain deterministic:

swift run swift-debt debt analyze /path/to/project \
  --format json \
  --debt-reference-time 2026-09-12T00:00:00Z

Without the CLI option or the debtReferenceTime configuration key, Git-history evidence is reported as unavailable. SwiftDebt does not substitute the Unix epoch or the ambient clock. Direct library callers can continue to inject AnalysisRequest(debtReferenceTime:) as a Date.

Enforce an explicit CI threshold policy:

swift run swift-debt analyze /path/to/project \
  --type-scope nominals \
  --threshold CCF=15 \
  --threshold NOPF=6 \
  --fail-on-violation \
  --format diagnostics

The values 15 and 6 above are example policy choices, not thresholds from the paper. Findings use strict value > threshold. By default, findings do not fail the process; parse/configuration/input errors do.

CLI exit statuses: 0 complete analysis with no opted-in gate failure; 1 complete analysis that violates an enabled gate; 2 invalid input/configuration, incomplete analysis, or a resource-limit failure. By default a file with parse errors is skipped with a warning and the analysis stays complete; --strict (or "strictSyntax": true) turns any parse error into an incomplete analysis and exit status 2. A type declared in mutually exclusive #if branches is omitted with a warning, never an error. SwiftPM wraps plugin failures and may report its own nonzero status rather than preserving these exact numbers.

Scoring modes:

swift run swift-debt analyze Examples/Sources --scoring none     # default: raw metrics
swift run swift-debt analyze Examples/Sources --scoring paper    # literal Table I formulas
swift run swift-debt analyze Examples/Sources --scoring bounded  # formulas, then explicit clamp
swift run swift-debt analyze Examples/Sources --scoring corrected  # bounded, plus DC ratio = duplicated / total lines

paper can produce results greater than 5. Undefined scores are null in JSON, never replaced with a passing grade. The overall average is absent/null unless analysis is complete and all ten individual scores are defined. bounded is a convenience transform, not a scientifically corrected scoring model. corrected additionally replaces the paper's printed DC ratio (duplicatedLines * totalLines / totalParams, which zeroes the score on any clone) with duplicatedLines / totalLines; every other equation is unchanged.

Command plugin

After adding the released dependency shown in Installation, run the command from the consumer package:

swift package swift-debt --format json
swift package swift-debt --target AppCore --target Domain --format json
swift package swift-debt --include-tests --type-scope nominals

The command plugin uses SwiftPM's exact target source lists and module names. By default it analyzes regular Swift targets in the current package, excluding test targets and dependency packages. An explicit --target may select a test target. There is no need to attach the command plugin to every target.

The plugin requests no package-write or network permissions. For a report written outside the plugin work directory, grant only the intended output directory:

mkdir -p reports
swift package --allow-writing-to-directory ./reports swift-debt \
  --format html --output ./reports/swift-debt.html

Alternatively, redirect stdout from the shell. Keep machine output and SwiftPM's build messages separate; the analyzer's own JSON renderer is deterministic, while launcher logging is controlled by SwiftPM.

Build plugin

Attach SwiftDebtBuildPlugin to each target you want checked:

.target(
    name: "AppCore",
    plugins: [
        .plugin(name: "SwiftDebtBuildPlugin", package: "SwiftDebt")
    ]
)

Create .swift-debt.json in the consuming package root before the first build. An empty {} is valid. Requiring an existing file makes configuration changes an explicit build input instead of relying on discovery of a previously nonexistent file.

{
  "typeScope": "nominals",
  "scoring": "none",
  "thresholds": { "CCF": 15, "NOPF": 6 },
  "failOnViolation": true
}
swift build

The plugin emits source-located diagnostics. It uses a regular buildCommand with declared source/configuration inputs and a comment-only generated SwiftDebt.analysis.swift completion file. No report or source text is bundled into your application as a generated resource. The completion file adds no runtime declarations. A no-op build can skip analysis once inputs/tool outputs are stable.

Build checks are per target; they cannot calculate project-wide coupling or cross-target duplicates. Use the command plugin for an aggregate package report. Do not attach the build plugin to a target whose Swift files are all excluded by configuration; empty selected input is an error, not a clean result.

A complete consumer example is provided:

cd Examples/PluginConsumer
swift build
swift package swift-debt --target Demo --type-scope nominals --format json

The build plugin supports Swift package targets, including packages opened in Xcode, and standalone Xcode project targets through its XcodeBuildToolPlugin adapter. The checked-in Xcode consumer project attaches the plugin to a macOS framework target. Run python3 scripts/xcode-plugin-smoke.py on the pinned Xcode environment to verify a clean build, warmed no-op, source and configuration invalidation, an enforced failure, and recovery.


Genesis code reviewer workflow

This repository includes .github/workflows/genesis-code-reviewer.yml, a manual GitHub Actions workflow for reviewing production Genesis code with the workflow's current SwiftDebt commit.

The workflow:

  • checks out the current repository and commit as SwiftDebt;
  • configures a strict SSH identity for the genesis-production host alias;
  • clones the Genesis repository over SSH, defaulting to git@genesis-production:brunogama/Genesis.git and the production ref;
  • runs swift-debt analyze against the selected Genesis path, defaulting to Sources;
  • uploads genesis-swift-debt-diagnostics as an artifact.

Configure these repository secrets before running it:

| Secret | Purpose | | --- | --- | | GENESIS_SSH_PRIVATE_KEY | Read-only deploy key for the Genesis repository. | | GENESIS_SSH_KNOWN_HOSTS | Trusted known_hosts entries for the SSH host. Do not replace this with disabled host checking. |

Optional dispatch inputs let you override the SSH repository URL, reviewed ref, analyzed path, and whether configured SwiftDebt threshold violations fail the workflow.


Configuration

The CLI and command plugin automatically load .swift-debt.json at the analysis root when it exists. The build plugin requires that file. Unknown keys, unknown metrics, invalid enums, and out-of-range numeric options are errors rather than silently ignored configuration.

{
  "typeScope": "classes",
  "scoring": "none",
  "format": "text",
  "debtReferenceTime": "2026-09-12T00:00:00Z",
  "thresholds": {
    "LOCC": 500,
    "WMCC": 200,
    "CCF": 20,
    "NOPF": 10,
    "DC": 10
  },
  "exclude": ["Generated", "Vendor"],
  "jobs": 4,
  "failOnViolation": false,
  "strictSyntax": false,
  "minimumDuplicateLines": 11,
  "maximumDuplicateComparisons": 250000,
  "maximumFileBytes": 16777216
}

These five thresholds reproduce the thresholds actually specified by Table I. The other five metrics remain informational unless explicitly configured. NOAV has an unresolved desirability direction in the paper; any custom NOAV upper-bound gate is your own policy, not its recommendation.

CLI scalar options override file settings. CLI thresholds merge by metric, and exclusions append to file exclusions. --fail-on-violation can enable a gate; it does not disable a gate already enabled in the configuration. --strict likewise enables strictSyntax but cannot disable it. JSON metric keys are case-sensitive uppercase IDs; CLI threshold IDs are normalized to uppercase.

debtReferenceTime must be an ISO-8601 internet timestamp with a time zone, such as 2026-09-12T00:00:00Z. --debt-reference-time overrides the configured value.

Exclusions are literal root-relative path prefixes such as Generated or Sources/Legacy, not globs. Directory scanning always omits .git, .build, .swiftpm, .swift-debt, Pods, Carthage, node_modules, symbolic links, and discovered Package.swift files. An explicitly selected Package.swift can still be analyzed. A filesystem scan treats all input as one virtual Workspace module; prefer the command plugin for a multi-module Swift package.

Parsing jobs must be 1...64; the default is the active processor count, capped at 8. The per-file size ceiling defaults to 16 MiB and can be overridden with --max-file-bytes. The clone comparison budget is a deterministic work limit, not a time or total-memory limit. Exhausting it fails the analysis rather than falsely reporting no duplication. There is no persistent content cache; files are parsed again on each CLI invocation. SwiftPM handles build-level invalidation.

The default clone floor is 11 nonblank code lines, corresponding to the paper's >10 condition. A custom lower DC threshold also requires a matching minimumDuplicateLines in the JSON configuration. The floor must not exceed the configured DC threshold plus one. Custom floors require scoring: "none"; paper/bounded/corrected modes retain floor 11 for consistency with the selected compatibility policy.

Library API

Add the SwiftDebtKit product to your tool target. The product exposes SwiftDebtKit and SwiftDebtCore modules:

import SwiftDebtCore
import SwiftDebtKit

func inspect() async throws -> AnalysisReport {
    let source = SourceUnit(
        path: "Sources/Feature/Example.swift",
        module: "Feature",
        content: """
        final class Example {
            func answer(_ value: Int) -> Int {
                if value > 0 { return value }
                return 0
            }
        }
        """
    )
    return try await Analyzer().analyze(
        [source],
        options: AnalysisOptions(typeScope: .nominals),
        jobs: 2
    )
}

Analyzer is the in-memory boundary. AnalysisService().run(AnalysisRequest(...)) is the filesystem/configuration/reporting boundary shared by the CLI and plugins. Inputs and report values are Sendable; parsing uses bounded task-group concurrency. No DI container or global service locator is involved.

Architecture

SwiftPM plugins / CLI -> swift-debt executable -> SwiftDebtKit composition root
                                                |       |       |
                                                v       v       v
                                           SwiftDebtSyntax   |  SwiftDebtReporting
                                                |       |       |
                                                +-------v-------+
                                                    SwiftDebtCore

| Target | Responsibility | | --- | --- | | SwiftDebtCore | Domain values, ten metrics, paper equations, coupling aggregation, clone engine. No imports. | | SwiftDebtSyntax | SwiftParser/SwiftSyntax extraction and parser diagnostics; no filesystem access. | | SwiftDebtReporting | Text, JSON, CSV, HTML, diagnostic serialization. | | SwiftDebtKit | Discovery, input validation, configuration, bounded orchestration, safe report writes. | | swift-debt | Strict argument parsing, stdout/stderr, process exit statuses. | | SwiftDebtCommandPlugin | Source/module manifest and on-demand package analysis. | | SwiftDebtBuildPlugin | Per-target build commands and explicit input/output tracking. |

Implementation-only types use internal/package access. Public types form the reusable domain/application boundary. Dependency versions are pinned in the manifest; the shipping package contains no host-library search paths or unsafe compiler flags.

Validation and limitations

The macOS release validation resolved the pinned SwiftSyntax 602.0.0 dependency with Swift 6.2, passed 186 Swift Testing tests, and passed 28 CLI and SwiftPM plugin subprocess checks. The plugin checks exercise a real downstream package. The earlier Linux validation used a disposable offline dependency copy; its scope and qualifications remain in the validation record.

An independent Xcode 27.0 run resolved the shipping manifest normally and passed the standalone project plugin smoke suite on macOS 27.2. The environments, commands, and qualifications are in docs/VALIDATION.md.

Run full validation on your own toolchain:

./scripts/verify.sh

This tool does not type-check or expand macros, perform compiler-backed name binding, evaluate #if, model dispatch, prove architecture compliance, or validate the paper's quality score against outcomes. Debtmap parsers for non-Swift languages are outside the current SwiftDebt parity target. The current performance gate uses frozen examples, so it does not establish large-repository behavior. Original SCMA/Lizard parity has not been established. The limitations plan states the evidence required to close each gap. Read METRICS.md before comparing results with another analyzer.

Package Metadata

Repository: brunogama/swiftdebt

Default branch: main

README: README.md