# SlopGuard: complete interface reference (agent-ingestible) slopguard-swift measures complex, undertested code in Swift sources. It computes a weighted CRAP (Change Risk Anti-Patterns) score combining cyclomatic and cognitive complexity with line coverage, and prints a structured report you can pipe into jq or fail CI on. Status: alpha (v0.1). The JSON schema is versioned and stable; flags may move before v0.2. License: MIT. Repo: https://github.com/JeevanThandi/SlopGuard-Swift ## Why this matters for coding agents Autonomous/background agents iterate by changing code and running tests. Two codebase properties decide how well that loop works: 1. Coverage: an agent editing untested code cannot tell a fix from a regression. Coverage is the agent's only feedback signal. 2. Complexity: deeply nested, many-branched methods are where agents (like humans) make mistakes. wCRAP captures both in one number per method. Use it as a CI gate (agents cannot merge new slop) or as a work queue (feed the JSON to an agent: "get every method under 30"). ## The formula wCRAP(m) = (cyc × cog) × (1 − cov/100)³ + sqrt(cyc × cog) - cyc: cyclomatic complexity (McCabe), parsed via SwiftSyntax. - cog: cognitive complexity (SonarSource 2023 spec): penalises nesting, ignores early-exit shapes (guard, ??, plain return). Recursion increment currently deferred (known undercount vs Sonar parity). - cov: line coverage percentage in [0, 100], gathered by slopguard-swift itself via `xcodebuild test -enableCodeCoverage YES` + `xcrun xccov`. Never user-supplied. It cannot be hand-fed or faked; the only way to lower a score without refactoring is to write tests that actually execute the code. - Semantics: cov = 100 → score collapses to sqrt(cyc × cog) (complexity floor). cov = 0 → full quadratic penalty (cyc × cog) + sqrt(cyc × cog). The cubed factor sharply rewards partial coverage: 50% coverage removes 87.5% of the risk term. - Inputs are clamped (complexity ≥ 0, coverage in [0,100]); score is always ≥ 0. - Default "crappy" threshold: 30 (matches the original CRAP paper). - Weighting rationale: a flat 50-case switch (cyc=50, cog=1) scores like a small method; a deeply nested 3-branch tangle (cyc=3, cog=12) scores like medium-complex code. ## Install git clone https://github.com/JeevanThandi/SlopGuard-Swift.git cd SlopGuard-Swift swift build -c release cp .build/release/slopguard-swift /usr/local/bin Requirements: Xcode 16 / Swift 6.0+ on macOS 13+. Release binaries are SBOM'd and SHA-256-checksummed; v0.1.x binaries are NOT yet code-signed or notarized. Verify the checksum or build from source. ## Subcommands | Command | Purpose | |---|---| | analyze | Walk a directory of Swift sources, drive xcodebuild test for coverage, emit a wCRAP report (text or JSON). Default subcommand. | | version | Print version metadata as JSON (name, version, mcpProtocol). | `analyze` is the default and `--path` defaults to the current directory; a bare `slopguard-swift` in a project root just works. ## analyze: all flags | Flag | Default | Meaning | |---|---|---| | -p, --path | . | Directory of Swift sources, or a single .swift file. | | -t, --threshold | 30 | wCRAP score above which a method/type is flagged isCrappy. | | --scheme | auto-discovered | xcodebuild scheme to test for coverage. | | --workspace | — | .xcworkspace passed as -workspace to xcodebuild. Use for CocoaPods-style multi-container directories where xcodebuild picks the wrong container. | | --destination | platform=macOS | xcodebuild destination, e.g. 'platform=iOS Simulator,name=iPhone 16'. | | --project-dir | cwd | Directory passed as cwd to xcodebuild. | | --no-coverage | off | Skip the xcodebuild step; complexity only. Every method reads 0% coverage (worst-case scores). | | --include | — | Glob(s) of files to include. Repeat or pass space-separated. | | --exclude | — | Extra exclude glob(s), combined with built-in defaults (.build, Pods, Carthage, Generated, *Tests, *Spec, …). | | --no-default-excludes | off | Start with an empty exclude list (e.g. to analyze test code itself). | | --json | off | Emit JSON to stdout (default is pretty text). | | --fail-over | — | Exit code 2 if any method's wCRAP exceeds this value. The CI gate. | | -v, --verbose | off | Stream xcodebuild/subprocess output to stderr. | | --quiet | off | Silence all stderr progress chatter. Wins over --verbose. Stdout unaffected. | ## Exit codes and streams | Code | Meaning | |---|---| | 0 | Analysis completed; nothing exceeded --fail-over (or flag not passed). | | 1 | Analysis error (bad path, xcodebuild failure, parse error). With --json a structured error envelope goes to stderr. | | 2 | Quality gate tripped: at least one method's wCRAP exceeds --fail-over. | Stream discipline: the report (text or JSON) goes to stdout; progress markers ("slopguard: running xcodebuild test…") go to stderr. Piped stdout is always clean. ## JSON report schema (schemaVersion "2") Top level: schemaVersion, generatedAt, coverageAvailable, summary, methods[], types[]. summary: - files, types, methods counts + crappy counts - avgCrap, maxCrap, average cyclomatic/cognitive/weighted complexity - weighted coverage across analyzed methods (when gathered) - coverageAvailable: false under --no-coverage or when no coverage data was found methods[], every analyzed function/initializer/subscript/accessor: - id: STABLE identifier "file#qualifiedName@line". Track methods across runs with this. - qualifiedName: "Type.method(signature)" - file, line, endLine: location relative to analyzed root - kind: function | initializer | getter | setter | subscript | … - typeName: enclosing type - complexity: cyclomatic (McCabe) - cognitiveComplexity: SonarSource 2023 - weightedComplexity: sqrt(cyc × cog) - coverage: line coverage percent 0–100 - crap: the wCRAP score - isCrappy: true when crap > threshold types[], per-type aggregation: - aggregatedCrap: formula applied to the type's totals (total burden, comparable across types) - maxCrap: worst single-method offender ("biggest fire") - isCrappy, weighted coverage, method counts Example (real output from SlopGuard analyzing its own sources): { "qualifiedName": "CrapAggregator.aggregate(fileReports:sourceRootURL:xcresultPath:threshold:coverage:notes:)", "file": "slopguard-core/Aggregation/CrapAggregator.swift", "line": 19, "kind": "function", "complexity": 17, "cognitiveComplexity": 15, "weightedComplexity": 15.97, "coverage": 0, "crap": 270.97, "isCrappy": true, "id": "slopguard-core/Aggregation/CrapAggregator.swift#CrapAggregator.aggregate(fileReports:sourceRootURL:xcresultPath:threshold:coverage:notes:)@19" } ## Quickstart commands # Zero-config: analyze the current directory (drives xcodebuild test for coverage) slopguard-swift # Scan a specific directory and print the top crappy methods slopguard-swift analyze --path Sources --threshold 30 # iOS app: pick a scheme and destination slopguard-swift analyze --path . --scheme MyApp --destination 'platform=iOS Simulator,name=iPhone 16' # CocoaPods-style project: point xcodebuild at the workspace explicitly slopguard-swift analyze --path . --workspace MyApp.xcworkspace --scheme MyApp # Full JSON for CI / downstream tooling slopguard-swift analyze --path Sources --json | jq '.methods | sort_by(-.crap)[:10]' # Fail CI when any method's CRAP exceeds 50 slopguard-swift analyze --path Sources --fail-over 50 # Complexity only (skip the test build) slopguard-swift analyze --path Sources --no-coverage ## Agent integration ### CLAUDE.md / AGENTS.md drop-in ## Quality gate: SlopGuard Before declaring any Swift task complete, run: slopguard-swift analyze --path Sources --json --quiet - The report is JSON on stdout; `.methods[]` entries with `isCrappy: true` exceed the wCRAP threshold (30). - If your change ADDED a crappy method, refactor it or cover it with tests until its `crap` score is below 30. Coverage is gathered by the tool itself via `xcodebuild test`. Write real tests; the number cannot be hand-fed. - CI runs `--fail-over 50` and will reject the PR at exit code 2. - Full interface reference: https://www.slopguard.dev/llms-full.txt ### Recipes # Agent work queue: worst methods first, with stable IDs slopguard-swift analyze --json --quiet \ | jq '[.methods[] | select(.isCrappy)] | sort_by(-.crap) | map({id, crap, coverage, file, line})' # Coverage gaps: complex code no test touches slopguard-swift analyze --json --quiet \ | jq '.methods[] | select(.complexity >= 5 and .coverage <= 50)' # Regression check around an agent change before=$(slopguard-swift analyze --json --quiet | jq '.summary.maxCrap') # … agent edits code … after=$(slopguard-swift analyze --json --quiet | jq '.summary.maxCrap') # Fast pre-commit sweep, no test build (use a high fail-over: coverage reads 0%) slopguard-swift analyze --no-coverage --quiet --fail-over 200 ### CI gate (GitHub Actions) name: slop-gate on: [pull_request] jobs: slopguard: runs-on: macos-15 steps: - uses: actions/checkout@v4 - name: Build slopguard-swift run: | git clone --depth 1 https://github.com/JeevanThandi/SlopGuard-Swift.git /tmp/sg cd /tmp/sg && swift build -c release - name: Gate on wCRAP run: /tmp/sg/.build/release/slopguard-swift analyze --path Sources --fail-over 50 Threshold guidance: flag at 30 (classic CRAP paper threshold); hard-fail CI at 50. Under --no-coverage all methods read 0% coverage, so use a much higher fail-over (e.g. 200) for complexity-only sweeps. ## Security / supply-chain posture - Two top-level Swift dependencies, both upstream Apple swiftlang: swift-syntax (Apache-2.0) and swift-argument-parser (Apache-2.0). No transitive deps. - Zero runtime dependencies beyond Xcode. Only subprocesses spawned: xcrun xcodebuild, xcrun xccov. - No network, no telemetry, no source mutation. - SBOM'd and SHA-256-checksummed release binaries. Signing + notarization before v0.2. - Dogfooded: every CI run analyzes SlopGuard's own sources and asserts a full-coverage baseline report on SampleApps/TodoList. ## Roadmap - v0.1: CLI, full Core + Coverage, notarized release. (current) - v0.2: Linux build (analyzer-only; no Xcode there), SARIF output for GitHub code scanning. - v0.3: Per-PR diff mode (slopguard-swift diff origin/main…HEAD).