stringer

module
v1.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2026 License: MIT

README

Stringer

Stringer

CI Coverage Release OpenSSF Scorecard OpenSSF Best Practices

v1.11.0 is an accuracy and performance release driven by a 13-repository benchmark: signal totals fall 29% to 70% per repository and the dead-code collector goes from 52 minutes to about a minute on Kafka (kubernetes 2 hours to 11 minutes). Version floors are reported as floors, test code no longer counts as complexity or dead code, duplication windows are merged, shallow clones are flagged in lottery risk, missing-tests finds mirrored test trees, and monorepo workspaces no longer repeat each other's signals. New: C# in complexity, dead code and coupling; NuGet Central Package Management and Gradle version catalogs; configurable, parallel registry lookups; per-collector progress logging. Full details in the release notes and docs/research/benchmark-2026-09.md.

Codebase archaeology for developers and AI agents. Stringer scans a repo for the tech debt already recorded in it — TODOs, vulnerable dependencies, single-owner code, complexity hotspots, stale branches — and turns it into structured output you can act on.

# Install via Homebrew
brew install davetashner/tap/stringer

# Or install via Go
go install github.com/davetashner/stringer/cmd/stringer@latest

# Get a health report
stringer report .

# Scan for actionable issues
stringer scan . -f markdown

# Or output as JSON for your own tooling
stringer scan . -f json -o signals.json

Why

The evidence of tech debt is already sitting in your repo: code comments, git history, dependency manifests, GitHub issues. What's missing is a single tool that reads all of it. Developers end up juggling grep TODO, a dependency audit tool, and issue searches, while AI agents burn tokens rediscovering the same context every session.

Stringer runs fifteen collectors in one command, scores each finding by confidence, and writes results in whatever format your workflow needs: markdown for a human, JSON or SARIF for CI, tasks for a Claude Code agent, or Beads JSONL for seeding a backlog. Nearly all of it is deterministic static analysis that runs locally, with no API keys or per-request costs. An optional LLM pass adds signal clustering, priority inference, and dependency detection on top; --no-llm skips it.

Collectors

Collector What it finds
todos TODO, FIXME, HACK, XXX, BUG, OPTIMIZE comments, enriched with git blame author and age
vuln Known CVEs via OSV.dev across 11 ecosystems (Go, npm, Maven, Gradle, Cargo, .NET incl. Central Package Management, Python, Composer, Swift, sbt, Mix) — no language toolchains required
dephealth Archived, deprecated, and stale dependencies across 11 ecosystems
lotteryrisk Directories where one author owns most of the code, weighted by recency
complexity Complex functions via Go AST analysis (or heuristics for other languages), cross-referenced with churn
deadcode Unused functions and types
duplication Copy-paste and near-clone duplication via token-based hashing
coupling Tightly coupled modules and circular dependency chains
gitlog Reverts, high-churn files, and stale branches
githygiene Large binaries, merge conflict markers, committed secrets, mixed line endings
patterns Large files and low test coverage ratios, with test detection for 12 languages
docstale Stale docs, doc/source co-change drift, broken internal links
configdrift Env var drift, dead config keys, inconsistent defaults across environment files
apidrift Drift between OpenAPI/Swagger specs and route handlers
github Open issues, PRs, and actionable review comments (requires GITHUB_TOKEN)

Run stringer collectors info <name> for signal types and tunable thresholds.

Output formats

Format Use case
beads (default) JSONL for Beads, with deterministic content-based IDs
json Raw signals with metadata envelope
markdown Human-readable summary grouped by collector
sarif SARIF v2.1.0 for IDE and CI integration
tasks Claude Code task format for direct agent consumption
html / html-dir Self-contained dashboard, or split into index.html plus assets

Pipeline

Collectors run concurrently, then signals are deduplicated (content-based SHA-256 hashing) and validated before formatting. Per-collector error modes (skip, warn, fail), delta scanning with move detection, baseline suppression, beads-aware dedup, and monorepo workspace auto-detection all happen in this pipeline.

                            ┌─────────────────────┐
                            │  Target Repository  │
                            └──────────┬──────────┘
                                       │
                                       ▼
┌────────────────────────────────────────────────────────────────────────────┐
│              15 collectors — all run concurrently (errgroup)               │
│                                                                            │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │   TODOs    │ │  Patterns  │ │ Dead Code  │ │ Complexity │ │Duplication │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
│                                                                            │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │   Gitlog   │ │Git Hygiene │ │Lottery Risk│ │   GitHub   │ │  Coupling  │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
│                                                                            │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │    Vuln    │ │ Dep Health │ │ Doc Stale  │ │Config Drift│ │ API Drift  │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
│                                                                            │
└──────────────────────────────────────┬─────────────────────────────────────┘
                                       │
                                       ▼
                           ┌──────────────────────┐
                           │  Dedup + Validation  │
                           └───────────┬──────────┘
                                       │
      ┌──────────┬──────────┬──────────┼──────────┬──────────┬──────────┐
      ▼          ▼          ▼          ▼          ▼          ▼          ▼
 ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
 │ beads  │ │  json  │ │markdown│ │ sarif  │ │ tasks  │ │  html  │ │html-dir│
 └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘

Real-world results

Runs against 13 open-source repositories, from a 130-file library to a 31k-file monorepo, on the v1.11.0 build (main 0e10cf5). "High" is the share of signals at confidence 0.8 or above, the ones worth acting on first.

Repository Language Files Signals High Scan Highlights
gin Go 130 165 8% 3s 39 complex functions, 4 lottery risks, 1 CVE
express JS 214 97 0% 3s 2 CVE floors, 4 lottery risks, 1 revert
flask Python 236 100 0% 2s 9 vulnerable version floors, 14 complex functions, 3 lottery risks
rustlings Rust 293 356 1% 5s 141 TODOs, 62 coverage gaps, 30 lottery risks
tokio Rust 874 1,089 1% 20s 15 vulnerable deps, 2 yanked crates, 4 reverts
jellyfin C# 2,619 2,969 11% 58s 915 complex functions, 227 dead code hits, 42 coupling issues
fastapi Python 3,139 408 12% 13s 67 complex functions, 35 lottery risks, 7 vulnerable deps
framework PHP 3,414 1,589 2% 28s 34 vulnerable deps, 364 dead code hits, 261 complex functions
react JS/TS 7,245 5,568 9% 1m 11s 1,020 TODOs, 86 vulnerable deps, 29 coupling issues
django Python 7,091 1,293 13% 25s 520 complex functions, 15 coupling issues, 76 git hygiene issues
kafka Java/Scala 7,558 4,514 8% 1m 11s 1,367 complex functions, 2,077 coverage gaps, 5 stale dependencies
next.js JS/TS 32,691 4,922 10% 1m 39s 1,093 complex functions, 92 vulnerable deps, 79 coupling issues
kubernetes Go 31,379 17,694 10% 10m 59s 5,939 complex functions, 2,520 TODOs, 97 vulnerable deps

Tested 2026-09-19 on the v1.11.0 code (main 0e10cf5, after the accuracy and performance fixes in PRs #431 to #459) on a 10-core Apple Silicon laptop. Repos cloned with --depth 100, GitHub collector excluded. Scan is the collector phase of stringer scan; stringer report takes about the same. Lottery-risk signals on shallow clones are capped at 0.5 and annotated. The v1.10.0 numbers, the method, per-collector counts, commit SHAs and the false-positive review are in docs/research/benchmark-2026-09.md.

Signal counts are dominated by three collectors (complexity, missing tests, duplication) and the "High" column is small by design: most signals are low-confidence hints, and a repository with nothing above 0.8 reads as 0%. Start with --min-confidence 0.8, or let the report rank them. Compared with v1.10.0 these fixes cut signal totals by 29% to 70% on every repository except jellyfin (which gained C# support) and took Kafka's scan from 52 minutes to 71 seconds and kubernetes from two hours to 11 minutes.

On a large repo, preview first and cap the output:

# Preview how many signals exist
stringer scan . --dry-run

# Start with a manageable batch
stringer scan . --max-issues 50 -f markdown

Getting started

# Repo health report: lottery risk, churn, coverage gaps, recommendations
stringer report .

# Preview signal count, then scan
stringer scan . --dry-run
stringer scan . -f markdown

# Save as JSON for programmatic use
stringer scan . -f json -o signals.json

# Focus on security
stringer scan . -c vuln,dephealth -f markdown

# Machine-readable dry run
stringer scan . --dry-run --json
Seed a Beads backlog

If you use Beads for agent task tracking, stringer's default output is beads-compatible JSONL. Until a native bd import lands (requested upstream), import via bd create:

stringer scan . --max-issues 20 -q | while IFS= read -r line; do
  title=$(echo "$line" | jq -r .title)
  desc=$(echo "$line" | jq -r .description)
  bd create "$title" -d "$desc"
done

bd ready --json

Example prompts

Stringer is built to be driven by agents. Paste any of these into Claude Code, Cursor, or Windsurf:

Install stringer (brew install davetashner/tap/stringer), then set it up in this repo — run stringer init ., scan the codebase, and give me a summary of what it found.

Use stringer to scan this project and tell me what needs attention. I want to know about TODOs, stale branches, security vulnerabilities, and any files where only one person understands the code.

Use stringer to scan this repo for known vulnerabilities and unhealthy dependencies. Prioritize anything with a CVE.

Set up stringer's MCP server so you can use it as a tool. Run stringer init . if there's no config yet, then register the MCP server with claude mcp add stringer -- stringer mcp serve.

Usage

stringer scan [path] [flags]

The flags you'll reach for most:

Flag Description
-c, --collectors Comma-separated list of collectors to run
-f, --format Output format (beads, json, markdown, sarif, tasks, html, html-dir)
-o, --output Output file path (default stdout)
--dry-run Show signal counts without producing output
--delta Only output new signals since the last scan
--max-issues Cap output count
-e, --exclude Glob patterns to exclude from scanning
--paths Restrict scanning to specific files or directories
--no-llm Skip all LLM passes

The full flag tables for scan and every other command live in docs/cli-reference.md, or run stringer <command> --help.

Other commands
Command What it does
stringer report . Repo health report: lottery risk, churn, hotspots, trends, recommendations. --format html-dir exports a dashboard
stringer docs . Generate an AGENTS.md scaffold from repo structure (--update preserves manual sections)
stringer context . Compact repo summary for AI prompts: structure, recent activity, open work
stringer init . Bootstrap .stringer.yaml, an AGENTS.md section, and MCP registration
stringer config Get/set config values with dot-notation key paths, repo-level or global
stringer baseline Suppress known findings so they're filtered from future scans
stringer collectors List collectors and inspect their signal types and thresholds

Configuration

Place a .stringer.yaml in your repository root for persistent scan options. Precedence: CLI flags > .stringer.yaml > global config (~/.config/stringer/config.yaml) > built-in defaults.

# .stringer.yaml
output_format: json
max_issues: 50
no_llm: true

collectors:
  todos:
    min_confidence: 0.5
    exclude_patterns:
      - vendor/**
      - node_modules/**
  gitlog:
    git_depth: 500
    git_since: 6m
  complexity:
    min_complexity_score: 6
  coupling:
    coupling_fan_out_threshold: 15
  duplication:
    duplication_min_test_lines: 12  # drop test-only clones shorter than this
  deadcode:
    include_public_api: false  # library repos: report exported symbols at 0.3 (default: suppressed)
  dephealth:
    registry_timeout: 10s      # per-lookup limit for npm/Maven/PyPI/... registries (was 30s)
    registry_concurrency: 8    # parallel registry lookups per ecosystem

Each collector accepts its own options (enabled, error_mode, thresholds, patterns); run stringer collectors info <name> to see them, or stringer config list to see every setting with its source.

By default, stringer suppresses noise-prone signals (missing-tests, low-test-ratio, low-lottery-risk) in demo, example, and tutorial directories. Use --include-demo-paths or set include_demo_paths: true per collector to scan those paths too.

SARIF integration

SARIF output is auto-detected from a .sarif file extension, or set explicitly with --format sarif. It includes automationDetails for run correlation, code snippets with 3-line context (disable with --no-snippets), and --sarif-baseline previous.sarif for differential analysis.

stringer scan . -o results.sarif

For VS Code, install the SARIF Viewer extension; signals appear as inline annotations with severity mapped from stringer priority (P1=error, P2=warning, P3=note, P4=none). For GitHub code scanning, upload the file in a workflow:

- name: Run stringer
  run: stringer scan . -o results.sarif

- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: results.sarif

Agent integration

Stringer ships an MCP server exposing scan, report, context, and docs as tools.

# Auto-detect and configure
stringer init .

# Or register manually with Claude Code
claude mcp add stringer -- stringer mcp serve

See docs/agent-integration.md for parameters and example workflows.

How output works

Each signal gets a confidence score (0.0–1.0). For TODO signals, the base score comes from the keyword (BUG 0.8, FIXME 0.65, HACK 0.55, TODO 0.5, XXX 0.45, OPTIMIZE 0.35), with a +0.1 boost if git blame shows it's under 30 days old. See DR-004 for the design rationale. Confidence maps to priority: ≥0.8 is P1, ≥0.6 is P2, ≥0.4 is P3, below that P4.

Signal IDs are deterministic: SHA-256(source + kind + filepath + line + title), truncated to 8 hex characters with a str- prefix. Re-scanning the same repo produces the same IDs, so output is idempotent and reimports don't duplicate. Every signal is labeled with its kind (todo, fixme, ...), its collector name, and stringer-generated to distinguish it from manually filed issues.

Given this source file:

// TODO: Add proper CLI argument parsing
// FIXME: This will panic on nil input
// HACK: Temporary workaround until upstream fixes the API

Stringer produces:

{"id":"str-0e4098f9","title":"TODO: Add proper CLI argument parsing","description":"Location: main.go:6","type":"task","priority":3,"status":"open","created_at":"","created_by":"stringer","labels":["todo","stringer-generated","stringer-generated","todos"]}
{"id":"str-11e6af70","title":"FIXME: This will panic on nil input","description":"Location: main.go:9","type":"bug","priority":2,"status":"open","created_at":"","created_by":"stringer","labels":["fixme","stringer-generated","stringer-generated","todos"]}
{"id":"str-3afa7732","title":"HACK: Temporary workaround until upstream fixes the API","description":"Location: main.go:15","type":"chore","priority":3,"status":"open","created_at":"","created_by":"stringer","labels":["hack","stringer-generated","stringer-generated","todos"]}

The type field derives from the keyword: bug/fixme -> bug, todo -> task, hack/xxx/optimize -> chore.

Limitations and roadmap

Signal IDs are line-sensitive: moving a TODO to a different line changes its ID. Delta scanning (--delta) detects moves, but other consumers may see a moved signal as new. A content-based ID scheme that survives line moves is planned.

Design principles

  • Read-only. Stringer never modifies the target repository.
  • Composable. Each collector is independent and implements one small Go interface (Name() and Collect()).
  • LLM-optional. Core scanning needs no API keys; the LLM pass is additive.
  • Idempotent. Same repo in, same output out, with deterministic IDs.
  • Format-agnostic. One scan pipeline feeds every output format.

Requirements

  • Go 1.26+ (for building from source)
  • Git (for blame enrichment and git log analysis)
  • GITHUB_TOKEN env var (optional, for the GitHub collector)
  • bd CLI (optional, for Beads JSONL import)

Contributing

See CONTRIBUTING.md for development setup and AGENTS.md for architecture details and the collector interface. This project uses Beads for task tracking; run bd ready --json to find open work.

License

MIT

Directories

Path Synopsis
cmd
stringer command
internal
analysis
Package analysis provides LLM-powered signal clustering and bead generation.
Package analysis provides LLM-powered signal clustering and bead generation.
baseline
Package baseline manages signal suppression state for repeat scans.
Package baseline manages signal suppression state for repeat scans.
bootstrap
Package bootstrap implements the `stringer init` command, which detects repository characteristics and generates a starter configuration.
Package bootstrap implements the `stringer init` command, which detects repository characteristics and generates a starter configuration.
collector
Package collector defines the Collector interface and a registry for managing available collectors.
Package collector defines the Collector interface and a registry for managing available collectors.
collectors
Package collectors provides signal extraction modules for stringer.
Package collectors provides signal extraction modules for stringer.
config
Package config handles .stringer.yaml configuration files.
Package config handles .stringer.yaml configuration files.
context
Package context provides CONTEXT.md generation for agent onboarding.
Package context provides CONTEXT.md generation for agent onboarding.
gitcli
Package gitcli provides native git CLI execution for blame operations.
Package gitcli provides native git CLI execution for blame operations.
llm
Package llm provides a provider-agnostic LLM client interface and implementations for use by stringer's analysis features.
Package llm provides a provider-agnostic LLM client interface and implementations for use by stringer's analysis features.
log
Package log configures structured logging for stringer using log/slog.
Package log configures structured logging for stringer using log/slog.
mcpserver
Package mcpserver implements an MCP (Model Context Protocol) server that exposes stringer's core operations as tools over stdio transport.
Package mcpserver implements an MCP (Model Context Protocol) server that exposes stringer's core operations as tools over stdio transport.
output
Package output defines the OutputFormatter interface for writing scan results in various formats.
Package output defines the OutputFormatter interface for writing scan results in various formats.
pipeline
Package pipeline provides the scan orchestration engine for stringer.
Package pipeline provides the scan orchestration engine for stringer.
redact
Package redact provides utilities to strip sensitive values from strings before they appear in output, logs, or error messages.
Package redact provides utilities to strip sensitive values from strings before they appear in output, logs, or error messages.
report
Package report provides a pluggable section registry for stringer report.
Package report provides a pluggable section registry for stringer report.
signal
Package signal defines the core domain types for stringer.
Package signal defines the core domain types for stringer.
state
Package state manages persisted scan state for delta scanning.
Package state manages persisted scan state for delta scanning.
testable
Package testable provides interfaces for mocking external dependencies such as exec.Command and exec.LookPath in tests.
Package testable provides interfaces for mocking external dependencies such as exec.Command and exec.LookPath in tests.
validate
Package validate provides JSONL validation against the bd import schema.
Package validate provides JSONL validation against the bd import schema.
workspace
Package workspace detects monorepo structures and enumerates their workspaces.
Package workspace detects monorepo structures and enumerates their workspaces.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL