repometa

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 13 Imported by: 0

README

repometa

A Go library that scans a source repository and reports the components (buildable units) it contains, plus any monorepo workspace layouts it recognizes.

CI Release Badge Coverage Go Reference Conventional Commits

Installation · Usage · Configuration · Development · Releases · Contributing · License

Overview

repometa scans a directory tree and returns a manifest describing each buildable component it recognizes (Go module, Rust crate, Python package, Node package, CMake project, …) along with any monorepo workspace layout that groups them. It exists to unblock downstream tools that need to reason about arbitrary repositories without re-implementing discovery. Behavioral detection ("does the team use uv") is deliberately out of scope — only artifacts present are reported.

Status

v0 — API is unstable and will break without notice. This library exists to unblock a small set of first-party consumer tools; when a second consumer appears, the API will be reviewed for stability.

Non-goals
  • CLI. Import the library.
  • JSON schema stability. Encode to whatever format you want in the caller.
  • Behavioral detection. Only artifacts present are reported.
  • Full ecosystem coverage. Detectors are added when a consumer needs them.

Features

  • Bounded, symlink-safe directory traversal with a hardcoded skip list (.git, node_modules, .venv, vendor, target, dist, build, .next, .angular, …).
  • Per-ecosystem detectors returning typed component and workspace records.
  • Framework attributes on node-package (js.framework=nextjs, js.framework=angular).
  • Package-manager attributes on python-package (python.pm={uv,poetry,pipenv,pip}).
  • Language attribute on dotnet-project (dotnet.language={csharp,fsharp,vb}).
  • Build-system attribute on java-project (java.build={maven,gradle,ant}); Gradle DSL variant via java.gradle.dsl={groovy,kotlin}.
  • Configurable bounds (max depth, max directories visited, max file size read) via Option functions.
Supported detectors (v0)
Ecosystem Component kinds Workspace kinds
Go go-module go-workspace (from go.work)
Rust rust-crate, rust-workspace cargo-workspace
Python python-package uv-workspace
Node / JS / TS node-package npm-yarn-workspace, pnpm-workspace, nx, turborepo
.NET dotnet-project (.csproj / .fsproj / .vbproj), dotnet-solution (.sln with at least one .NET member) dotnet-solution
Visual C++ cpp-project (.vcxproj)
Java / JVM java-project (Maven pom.xml, Gradle build.gradle{,.kts} / settings.gradle{,.kts}, Ant build.xml) maven-multi-module, gradle-multi-project
CMake cmake-project
Make make-project
C c-source-tree (only when no structured build system)
Assembly asm-source-tree (only when no structured build system)

Requirements

  • Go 1.26 or newer (see go.mod).
  • No runtime dependencies beyond the Go standard library and the two transitive indirect deps listed in go.sum (github.com/BurntSushi/toml, gopkg.in/yaml.v3).

Installation

go get github.com/jedi-knights/repometa@latest

Pin to a specific version:

go get github.com/jedi-knights/repometa@v0.1.0

Usage

package main

import (
    "fmt"

    "github.com/jedi-knights/repometa"
)

func main() {
    manifest, err := repometa.Scan("/path/to/repo")
    if err != nil {
        panic(err)
    }
    for _, c := range manifest.Components {
        fmt.Println(c.Kind, c.Root, c.Attributes)
        for _, ws := range c.Workspaces {
            fmt.Println("  workspace:", ws.Kind, ws.Members)
        }
    }
}

API Documentation

Full API reference is published on pkg.go.dev. Every exported type, function, and option carries a godoc comment; treat pkg.go.dev as the authoritative reference and this README as an introduction.

Polyglot classification

Manifest.Languages() returns the sorted, de-duplicated set of Language values across every component. Manifest.Polyglot() reports whether more than one language is present. Component.Language() exposes the same mapping per-component.

manifest, _ := repometa.Scan("/path/to/repo")
if manifest.Polyglot() {
    fmt.Println("polyglot repo, languages:", manifest.Languages())
} else if langs := manifest.Languages(); len(langs) == 1 {
    fmt.Println("single-language repo:", langs[0])
}

Multiple Kind values fold into a single Language when the underlying runtime is shared. Component.Kind remains the source of truth for finer-grained inspection.

Language Value Sourced from Kind values
LanguageGo "go" go-module
LanguageRust "rust" rust-crate, rust-workspace
LanguagePython "python" python-package
LanguageJavaScript "javascript" node-package
LanguageDotNet "dotnet" dotnet-project, dotnet-solution
LanguageJava "java" java-project (Maven / Gradle / Ant)
LanguageC "c" cmake-project, make-project, cpp-project, c-source-tree
LanguageAssembly "assembly" asm-source-tree
LanguageUnknown "unknown" any Kind not in the table above (safety net for new detectors)

Configuration

Scan accepts Option functions to override traversal bounds. Defaults live in options.go.

Option Default Purpose
WithMaxDepth(n) defaultMaxDepth Cap directory descent depth.
WithMaxDirs(n) defaultMaxDirs Cap total directories visited.
WithMaxFileSize(n) defaultMaxFileSize Cap bytes read per file when inspecting content.

The walker refuses to descend into a hardcoded skip list and skips all symlinks. These bounds are not tunable — they are safety invariants, not preferences.

Development

git clone https://github.com/jedi-knights/repometa.git
cd repometa
go mod download
go build ./...

Lint locally (matches CI):

golangci-lint run
Project layout
  • scan.go — entry point (Scan, ScanWith).
  • walker.go — bounded, symlink-safe directory traversal.
  • detect_*.go — one file per ecosystem detector.
  • detectors.go — detector registry and dispatch.
  • manifest.goManifest, Component, Workspace types.
  • options.go — traversal-bound Option functions and defaults.

Testing

go test -race ./...

Coverage report:

go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

CI enforces a 90% coverage floor. New behavior added below the line will not merge without accompanying tests.

Roadmap

Milestones roughly correspond to detector coverage and API stability.

  • v0.x — additional detectors added on demand as consumers appear. API may break.
  • v1.0 — API frozen once a second first-party consumer exists and stresses the shape of Manifest, Component, and Workspace.
  • Post-v1 — detector plugins loaded at runtime (only if a consumer needs custom ecosystem detection).

Open issues track individual detector requests. Detector requests without a concrete consumer are declined by default — see Non-goals.

Releases

Releases are automated by go-semantic-release. On every push to main:

  1. CI runs lint, tests, and the 90% coverage gate.
  2. On success, the Release workflow analyzes conventional commits since the last tag.
  3. If a release is warranted, semantic-release writes CHANGELOG.md and VERSION, pushes a vX.Y.Z tag, and creates the matching GitHub Release.

Consumers pull the tagged version via go get github.com/jedi-knights/repometa@vX.Y.Z.

Commit messages must follow Conventional Commits: feat: and fix: drive minor/patch bumps, feat!: or a BREAKING CHANGE: footer drives a major bump.

Versioning

repometa follows Semantic Versioning 2.0.0. While the library is in v0, minor version bumps may include breaking API changes — pin to an exact version in production. Once v1 ships, breaking changes will only appear in major version bumps.

Changelog

See CHANGELOG.md. The changelog is generated automatically from conventional commits by the release workflow — do not edit it by hand.

Contributing

Contributions are welcome. Please open an issue before starting substantial work so the direction can be agreed on.

  • Fork the repository and create a topic branch from main.
  • Write tests for new behavior; keep coverage above the CI floor.
  • Ensure golangci-lint run and go test -race ./... pass locally.
  • Use Conventional Commits so the release pipeline can compute the next version.
  • Open a pull request against main describing the change and its motivation.

One PR should carry one type(scope): pair — split unrelated changes into separate PRs.

Code of Conduct

This project follows the Contributor Covenant v2.1. By participating, you agree to abide by its terms. Report unacceptable behavior to the maintainer at omar.crosby@gmail.com.

Security

Do not open public issues for security vulnerabilities. Instead, email omar.crosby@gmail.com with a description of the issue and, if possible, a reproduction. You will receive an acknowledgment within 72 hours and a coordinated disclosure timeline for the fix.

Support

This is a spare-time project; response times are best-effort.

Acknowledgments

  • go-semantic-release — automates the release pipeline.
  • golangci-lint — meta-linter used in CI.
  • The maintainers of each ecosystem's build-system conventions (Cargo, uv, pnpm, Turborepo, CMake, …) whose file formats this library reads.

Maintainers

License

MIT — see LICENSE.

Documentation

Overview

Package repometa scans a source repository and reports the components (buildable units) it contains, plus any monorepo workspace layouts it recognizes. It is intended to be imported by downstream tools that need to reason about arbitrary repositories without re-implementing discovery logic.

The API is unstable. See the repo README for scope and non-goals.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Component

type Component struct {
	// Kind identifies the ecosystem and shape of the component.
	Kind Kind

	// Root is the path relative to [Manifest.Root]; "." for the repo root.
	Root string

	// Evidence lists the files that led to this component being reported.
	Evidence []Evidence

	// Confidence is in [0.0, 1.0]; heuristic hits report < 1.0, and
	// manifest-driven detections report 1.0.
	Confidence float64

	// Workspaces lists any monorepo layouts anchored at this component.
	Workspaces []Workspace

	// Attributes carries ecosystem-specific metadata. Keys are namespaced
	// (e.g. "js.framework", "python.pm"). See the README for the current
	// set; unknown keys are safe to ignore.
	Attributes map[string]string
}

Component describes a single buildable unit inside the repo, anchored at a specific directory. A directory may produce multiple Components when more than one ecosystem's manifest is present (e.g. a Rust crate with a co-located Node package).

func (Component) Language added in v0.3.0

func (c Component) Language() Language

Language returns the coarse ecosystem label for this Component. See Language for the mapping and rationale; unrecognized kinds return LanguageUnknown.

type Evidence

type Evidence struct {
	// Path is relative to [Manifest.Root]; forward slashes on every
	// platform.
	Path string

	// Reason is a short human-readable description of why this file
	// counts as evidence (e.g. "go.mod present", "workspace = [...]").
	Reason string
}

Evidence records a single fact that led to a Component being reported. Path is relative to Manifest.Root so evidence remains meaningful when the manifest is transported.

type Kind

type Kind string

Kind identifies the type of a Component. Values are open-ended strings so new detectors can be added without breaking consumers on enum drift; unknown values are safe to log or ignore.

const (
	KindGoModule       Kind = "go-module"
	KindRustCrate      Kind = "rust-crate"
	KindRustWorkspace  Kind = "rust-workspace"
	KindPythonPackage  Kind = "python-package"
	KindNodePackage    Kind = "node-package"
	KindDotNetProject  Kind = "dotnet-project"
	KindDotNetSolution Kind = "dotnet-solution"
	KindCppProject     Kind = "cpp-project"
	KindJavaProject    Kind = "java-project"
	KindCMakeProject   Kind = "cmake-project"
	KindMakeProject    Kind = "make-project"
	KindCSource        Kind = "c-source-tree"
	KindAsmSource      Kind = "asm-source-tree"
)

Kind values for every ecosystem detector shipped in this package. See the README's "Supported detectors" table for the mapping to ecosystem.

type Language added in v0.3.0

type Language string

Language is a coarse ecosystem label derived from a Component.Kind. Multiple Kinds may map to the same Language: KindRustCrate and KindRustWorkspace both report LanguageRust; KindCMakeProject, KindMakeProject, and KindCSource all report LanguageC; KindDotNetProject and KindDotNetSolution both report LanguageDotNet. This grouping is used by the helpers on Manifest to distinguish single-language repositories from polyglot ones — treat Component.Kind as the source of truth for finer-grained work.

const (
	LanguageGo         Language = "go"
	LanguageRust       Language = "rust"
	LanguagePython     Language = "python"
	LanguageJavaScript Language = "javascript"
	LanguageDotNet     Language = "dotnet"
	LanguageJava       Language = "java"
	LanguageC          Language = "c"
	LanguageAssembly   Language = "assembly"
	LanguageUnknown    Language = "unknown"
)

Language values. LanguageUnknown is used when a Component.Kind is not in the mapping table (added by a future detector; safely reported rather than dropped).

type Manifest

type Manifest struct {
	// Root is the absolute path that was scanned. All [Component.Root]
	// and [Evidence.Path] values in this manifest are relative to Root.
	Root string

	// Components lists every buildable unit detected under Root. May be
	// empty if the scan encountered no known ecosystem manifests.
	Components []Component

	// Stats reports counters from the walk. Non-zero cap-hit fields
	// indicate the scan was truncated and may be incomplete.
	Stats ScanStats
}

Manifest is the result of scanning a repository. Consumers iterate Manifest.Components; the ordering is deterministic (by root path, then by kind) so serialized output is diff-friendly.

func Scan

func Scan(root string, opts ...Option) (*Manifest, error)

Scan walks root and returns a Manifest describing every detected component. The returned Manifest is non-nil on success.

The walk is bounded by the caps documented on WithMaxDepth, WithMaxDirs, and WithMaxFileSize; a caller who overrides none of them accepts the package defaults. Symlinks and a hardcoded skip list (.git, node_modules, vendor, target, dist, build, .next, .angular, .venv) are never traversed.

Scan returns an error if root is empty, does not exist, or is not a directory. Errors surfaced by the underlying filesystem walk are wrapped and returned as-is; there are no exported sentinel errors.

Scan is safe for concurrent use: it holds no package-level state and mutates only the Manifest it returns.

func (*Manifest) Languages added in v0.3.0

func (m *Manifest) Languages() []Language

Languages returns the sorted, de-duplicated list of Language values present across every Component in the manifest. A manifest with no components returns an empty slice.

func (*Manifest) Polyglot added in v0.3.0

func (m *Manifest) Polyglot() bool

Polyglot reports whether the manifest contains components spanning more than one Language. A manifest with zero components, or with components in a single Language, returns false. LanguageUnknown counts as its own language, so a repo mixing a known ecosystem with one repometa doesn't yet recognize is reported as polyglot.

type Option

type Option func(*options)

Option configures a Scan call. Options are applied in order; when two options set the same field, the later one wins.

func WithMaxDepth

func WithMaxDepth(n int) Option

WithMaxDepth caps directory recursion depth. The scan root is depth 0. A value of 0 or less is treated as "no descent below root".

func WithMaxDirs

func WithMaxDirs(n int) Option

WithMaxDirs caps the total number of directories visited during the scan. The walk aborts silently when this cap is hit; ScanStats.DirCapHits on the returned Manifest reports how many times the cap fired so callers can decide whether to widen the scan.

func WithMaxFileSize

func WithMaxFileSize(n int64) Option

WithMaxFileSize caps how many bytes any single manifest file may be read into memory. Files above this cap are skipped for content parsing but their presence is still recorded as Evidence. The cap protects against pathologically large manifest files (generated lockfiles, vendored artifacts) exhausting memory during a scan.

type ScanStats

type ScanStats struct {
	// DirsVisited is the total number of directories entered by the walk.
	DirsVisited int

	// FilesSeen is the total number of file entries observed (whether or
	// not their contents were read).
	FilesSeen int

	// DepthCapHits counts the number of directories the walk refused to
	// descend into because the [WithMaxDepth] cap was reached.
	DepthCapHits int

	// DirCapHits is 1 if the [WithMaxDirs] cap fired and aborted the walk;
	// 0 otherwise.
	DirCapHits int

	// SymlinksSkipped counts the number of symlink entries skipped —
	// symlinks are never traversed regardless of target.
	SymlinksSkipped int
}

ScanStats reports counters from the walk. Non-zero DepthCapHits or DirCapHits indicate the scan was truncated by WithMaxDepth or WithMaxDirs respectively — callers should widen the cap and rescan if a complete manifest is required.

type Workspace

type Workspace struct {
	// Kind identifies the monorepo tooling.
	Kind WorkspaceKind

	// Members lists the workspace's constituent package paths, relative
	// to [Manifest.Root], forward-slash separated.
	Members []string
}

Workspace describes a monorepo layout anchored at a Component. Members may be empty when the workspace kind was detected by file presence alone rather than by parsing an explicit member list.

type WorkspaceKind

type WorkspaceKind string

WorkspaceKind identifies the workspace / monorepo tooling in use. Like Kind, values are open-ended strings — unknown values are safe to log or ignore.

const (
	WorkspaceGo                 WorkspaceKind = "go-workspace"
	WorkspaceCargo              WorkspaceKind = "cargo-workspace"
	WorkspaceNpmYarn            WorkspaceKind = "npm-yarn-workspace"
	WorkspacePnpm               WorkspaceKind = "pnpm-workspace"
	WorkspaceNx                 WorkspaceKind = "nx"
	WorkspaceTurborepo          WorkspaceKind = "turborepo"
	WorkspaceUv                 WorkspaceKind = "uv-workspace"
	WorkspaceDotNetSolution     WorkspaceKind = "dotnet-solution"
	WorkspaceMavenMultiModule   WorkspaceKind = "maven-multi-module"
	WorkspaceGradleMultiProject WorkspaceKind = "gradle-multi-project"
)

WorkspaceKind values for every workspace layout recognized by the detectors in this package.

Jump to

Keyboard shortcuts

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