README
ΒΆ
beats
Measure the structural fingerprint of a Go/Java codebase.
beats clusters Go and Java functions by the skeleton of how they are written β independent of names, comments, domain vocabulary or semantic meaning. The goal is to find meaningful patterns in code by looking at what it does structurally, not what it means semantically.
π Read the full story on Medium β
For some quick easy context here are a couple of PRs that beats raised and merged:
- https://github.com/kubernetes/kubernetes/pull/139918
- https://github.com/grafana/k6/pull/6147
- https://github.com/mattermost/mattermost/pull/37045
- https://github.com/rancher/rancher/pull/55552
Why beats
Every codebase has a rhythm β recurring structural patterns that developers fall into without naming them. Beats finds that pulse. When the rhythm is steady, you have convention. When it's irregular, you may want to pay attention.
Beats finds where the variance is and where it isn't.
Reports from sample OSS repositories
| Project | Repository | Report |
|---|---|---|
| Argo CD | argoproj/argo-cd | View β |
| Mattermost | mattermost/mattermost-server | View β |
| Gitea | go-gitea/gitea | View β |
| cAdvisor | google/cadvisor | View β |
| CockroachDB | cockroachdb/cockroach | View β |
π What is beats?
beats identifies recurring structural patterns across an entire Go codebase to answer one question: does the golang code across the repository coalesce to form a structural pattern and if so, how can we identify and evaluate the same?
Beats define Structural fingerprint as follows.
For each function, beats computes three features:
-
Token sequence β an ordered list of AST mnemonics representing the structural skeleton of the function body. Each token is a normalised AST node, for example :
CALLfor a function call,ASSIGNfor a variable assignment,RETURNfor a return statement (with arity),IF,FOR,RANGE, and so on. No names, no literals β only structure. -
Direct imports β the set of packages actually used within the function (not just imported by the file). This captures the dependency shape at the function level, not the file level.
-
Call targets (fan-out) β the set of external functions invoked within the function body.
No attempt is made to understand what a call target does or what an import statement provides. That would reintroduce vocabulary dependence and defeat the purpose.
These three features β along with some additional metadata β form a FunctionMetadata record.
Beats collects FunctionMetadata across the entire codebase and clusters structurally similar functions. Similarity between two functions is computed as the geometric mean of three signals, each normalized to [0, 1]: token-sequence similarity (1 β normalized Levenshtein distance), Jaccard overlap of imports, and Jaccard overlap of called functions. Using the geometric mean (β(AΒ·BΒ·C)) rather than a weighted sum means a pair must score well across all three dimensions to be considered similar β a function with high token overlap but no shared imports or calls won't be treated as a match
Potential outliers
Functions that came structurally close to a cluster β but didn't meet the similarity threshold to join β are potential outliers. They look like they should follow a convention but deviate in a specific, measurable way.
For each outlier, beats surfaces the exact gap between the function and its closest cluster:
- Token delta β token types present in the outlier but absent from the cluster's common subsequence, or vice versa. A
+IFmeans the outlier has an extra branch peers don't; aβDEFERmeans it's missing a cleanup step they all share. - Import delta β packages the outlier uses that peers don't, or packages peers consistently use that this function omits.
- Call delta β specific call targets (e.g.
errors.Is,sync.Mutex.Lock) that peers share but the outlier skips, or that the outlier adds beyond the cluster norm. - Cyclomatic delta β how much more or less complex the outlier is relative to the cluster mean.
The HTML report groups outliers by their closest cluster and visualises the signal across three charts:
- Package coverage β the top 20 packages by function count, split into clustered (settled convention) vs outliers (structural ad-hoc). Surfaces which areas of the codebase have converged on patterns and which haven't.
- Delta direction β breaks all outliers into three buckets: missing something peers have (strongest signal), extending beyond peers, or both. A repo dominated by the "missing" bucket has more structural drift worth reviewing.
- Token frequency β the top 10 token types most commonly absent from outliers compared to their peer clusters. A dominant type appearing across many outliers (e.g.
DEFER,IF) signals a systemic gap rather than a one-off.
beats also writes <repo>/.beats/outlier.md after indexing β a pre-computed document with every outlier, its deltas, and the full bodies of its closest cluster's peer functions. This is what the Claude skill reads to triage outliers without issuing any further queries.
Remember, beats is a lens - not a prescription.
π€ Analyze with Claude
beats ships a Claude plugin that wires the outlier triage into a conversational skill.
Install the plugin
Inside Claude Code or Cowork, add the marketplace and install:
/plugin marketplace add somak2kai/beats
/plugin install beats@beats
Recommended workflow β index outside Claude, analyse inside
Index in your terminal, triage in Claude. This is the lowest-cost way to run beats.
beats init does pure computation β parsing, clustering, writing the database. It produces no output that needs LLM reasoning. Running it inside Claude wastes tokens on a step that doesn't benefit from them.
Step 1 β index in your terminal:
beats init --repo /path/to/your/go/repo
This writes <repo>/.beats/outlier.md and <repo>/.beats/report.html. No Claude involved.
Step 2 β triage in Claude:
mini fingerprint
Tell Claude the repo path when prompted. Claude reads outlier.md directly and outputs the full triage β no indexing, no repeated database queries, just analysis.
This keeps Claude's token budget focused entirely on the one step that actually needs it.
Full mode (all-in-one)
If you prefer to run everything through Claude in one command:
run beats on /path/to/your/go/repo
Claude will handle install verification, beats init, triage, and the HTML report. Convenient, but costs more tokens since indexing runs inside the Claude session.
What the LLM analysis does
For each outlier, Claude matches its closest cluster hash to the peer cluster in outlier.md and reads the actual function bodies of every cluster member. It then asks one question per structural signal:
Does the function body explain this deviation from its peers?
If yes β Expected Variation. If no β Needs Attention. The verdict is always tied to a specific delta β a token type, import, call target, or cyclo difference β not a general code quality opinion.
π¦ Installation
Prerequisites
- Go 1.21 or later
- Git
Install via Homebrew (macOS / Linux)
brew tap somak2kai/tap
brew install beats
Upgrade to the latest release at any time:
brew upgrade beats
Install from source
Clone the repository and build the CLI:
git clone https://github.com/somak2kai/beats.git
cd beats
go build -o beats ./cmd/
Move the binary somewhere on your $PATH:
mv beats /usr/local/bin/
Or run directly without installing:
go run ./cmd/ <command> [flags]
Verify
beats --version
π Usage
beats has one main commands: init to index a repository , to create clusters and report on it.
beats init β index a repository
Walks a Go codebase and writes FunctionMetadata records into a local Badger store.
beats init --repo=<path-to-go-repository>
Example:
beats init --repo=/home/user/projects/myservice
What gets indexed:
- All Go source files under the repository root (excluding
vendor/and test files by default, auto generated files such as pb.go) - For each exported and unexported function: token sequence, call targets, direct imports, file path, line number, package name
- runs the clustering algorithm, and produces an HTML report at
<repo>/.beats/report.html.
Open the report:
open /home/user/projects/myservice/.beats/report.html
The report shows all clusters sorted by combined coherence, with per-cluster member lists, top imports, Cyclo P95, package distribution, and a coherence quadrant breakdown.
β€οΈ