Files
mcp/CONTRIBUTING.md
Rene Nochebuena cc62906c6f feat(mcp): initial implementation — MCP server, framework indexer, 10 tools, 8 validation rules (v0.1.0)
Introduces code.nochebuena.dev/einherjar/mcp — the Einherjar Model Context Protocol
server. A remote, streamable-HTTP service that teaches AI assistants about every
other module of the framework: which package exposes which type, what each module
guarantees through its compliance tests, the canonical wiring shape for a service,
and whether a Go snippet follows the conventions. Indexes the framework on disk at
build time and ships a self-contained binary via go:embed; imports nothing from
other einherjar/* modules at compile time.

server (cmd/server):
- Streamable-HTTP MCP server built on github.com/modelcontextprotocol/go-sdk v1.0.0
- mcp.NewServer + mcp.NewStreamableHTTPHandler, served via net/http on EINHERJAR_MCP_ADDR
  (default :8080) and EINHERJAR_MCP_PATH (default /mcp)
- /healthz liveness endpoint; structured JSON logging via log/slog
- Loads the embedded data/index.json once at startup; in-memory for the process lifetime

indexer (cmd/indexer):
- Walks an Einherjar repository checkout (default ../), parses every sibling
  module's go.mod, README.md, CHANGELOG.md, docs/adr/ADR-*.md, doc.go package
  comments, every exported type/interface/func/method/const/var (via go/doc on
  go/parser ASTs), and compliance_test.go
- Captures module dependency edges by regex over each go.mod's require lines
  (einherjar/* paths only; self-reference filtered)
- Appends a synthetic "wire" module documenting canonical application wiring
  conventions, authored at internal/index/builtins/README.md and embedded via
  go:embed; participates in list_modules / get_module / get_example like a real module

internal/index:
- Schema einherjar.mcp/index/v1; types: Index, Module, SubPackage, Symbol, ADR,
  Example, Compliance, InterfaceAssert, ComplianceTest
- Build(repoRoot) → *Index walks the repo; BuildBuiltins() returns the synthetic
  wire module from the embedded markdown
- Load([]byte) → *Index validates the schema version on read
- FindModule, SearchSymbols helpers used by tools

internal/tools (10 tools):
- list_modules — enumerate every module with purpose + sub-packages
- get_module — package doc, dependencies, sub-packages, key symbols, ADRs,
  compliance counts; optional embedded README
- search_symbols — full-text across name, doc, sub-package, module; filterable by
  module and kind
- get_symbol — full signature, doc comment, source file:line for one symbol
- list_adrs — list ADRs across the framework or within one module
- get_adr — fetch one ADR's markdown body
- get_example — canonical usage snippets extracted from module READMEs and from
  the synthetic wire conventions
- get_compliance — interface assertions (var _ Iface = impl) and structural test
  names from a module's compliance_test.go
- get_changelog — full CHANGELOG.md markdown for one module
- validate_snippet — pattern-match a Go snippet against framework conventions

internal/rules (8 rules, registered via init() against a single registered slice):
- launcher.missing-run — launcher constructed but Run() never called
- launcher.no-components — launcher.New() called without any .Append(...)
- launcher.run-error-discarded — lc.Run() invoked as an ExprStmt (return ignored)
- logz.direct-env-read — os.Getenv("EINHERJAR_LOG_*") bypassing logz config
- web.server-not-appended — web/server constructed but not added to the launcher
- wire.hook-bad-signature — with<Feature>(...) first param is not launcher.Launcher
- wire.hook-outside-beforestart — repo/service/handler construction or route
  registration at the top level of a hook (outside lc.BeforeStart)
- wire.route-specific-after-param — /users/{id} registered before a sibling
  /users/me of the same length and method (chi would shadow the literal route)

Synthetic wire module (internal/index/builtins/README.md):
- Project layout (cmd/<app>/main.go + internal/wire/*.go + per-feature domain dirs)
- Canonical Run() shape: config → logger → infra (db, cache, pool, mc, srv) → cross-
  cutting (validator, permission provider) → launcher.New → lc.Append(infra...) →
  withMigrations / withSuperAdminSeed / withHealth / withFeature hooks → return lc.Run()
- Canonical with<Feature> hook shape: signature (launcher.Launcher first, server.Server
  second, deps last), single lc.BeforeStart closure containing all construction +
  route registration
- chi route ordering, srv.With(authz(...)) authorization, middleware helpers
  (authz / skipPublicPaths / skipMethodPath), tokenSignerAdapter pattern showing
  that the framework exposes Signer.Sign as a primitive and the application owns
  the access/refresh response shape

Packaging:
- Multi-stage Dockerfile that builds from the einherjar repository root
  (docker build -f mcp/Dockerfile .) so cmd/indexer can walk every sibling module
  at image-build time; runtime layer is gcr.io/distroless/static-debian12:nonroot
- 86-byte placeholder data/index.json committed once with `git add -f`; subsequent
  indexer runs overwrite it locally but the file is .gitignored
- .gitea/CODEOWNERS and pull_request_template.md mirror the sibling layout

Design notes:
- mcp depends on nothing in einherjar/* — it reads the framework via the filesystem
  at index time. This keeps mcp outside the framework dependency graph and lets it
  index any version of einherjar without versioning itself in lock-step.
- All structured-output tool responses initialise empty slices ([]Type{}) rather
  than relying on Go's nil-marshals-to-null default, so the SDK's JSON-schema
  output validator never rejects a tools/call result.
2026-05-29 18:12:45 +00:00

9.9 KiB

Contributing to Einherjar

Thank you for your interest in contributing to Einherjar. This document explains everything you need to know before sending your first Pull Request.

Einherjar is developed and maintained by NOCHEBUENADEV, the trade name of its founder operating as a Persona Física con Actividad Empresarial (PFAE) under Mexican law. Contributions are welcome and valued — but they are accepted under the terms described here, so please read this document fully before you begin.


Table of Contents

  1. Before You Start
  2. Legal: CLA and Copyright
  3. Development Setup
  4. Code Standards
  5. Commit Messages
  6. Submitting a Pull Request
  7. Reporting Bugs
  8. Requesting Features
  9. What Gets Accepted

1. Before You Start

Open an issue first for anything non-trivial.

Before you write a line of code, open an issue describing what you want to change and why. This protects your time: a change that seems straightforward may conflict with a planned refactor, an architectural decision, or the project's direction. Getting alignment before coding means your PR will not be rejected for reasons unrelated to its quality.

Exceptions where you can skip the issue:

  • Typo or documentation-only fix
  • Test coverage improvement for existing behavior
  • Trivially obvious bug with a clear, contained fix

Do not submit a PR that changes the public API of any module without prior discussion. Every Einherjar module has a stability contract. Breaking changes require a major version bump and coordinated updates across dependent modules.


Contributor License Agreement

Before your first PR can be merged, you must sign the Contributor License Agreement by posting a specific comment on your Pull Request. The required comment text and full instructions are in CLA.md.

Checkboxes in PR descriptions are not used for CLA consent — they can be silently toggled by anyone. A comment is a timestamped, author-attributed record.

All original code in Einherjar is copyright NOCHEBUENADEV. NOCHEBUENADEV is the registered trade name of its founder, a natural person operating under the Mexican Persona Física con Actividad Empresarial (PFAE) regime.

When you contribute, you retain ownership of what you wrote. By signing the CLA you grant NOCHEBUENADEV a perpetual, irrevocable, worldwide license to use, modify, sublicense, and redistribute your Contribution — including the right to relicense it. See CLA.md for the full terms.

License

Einherjar is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). Your Contributions will be distributed under the same license unless the Maintainers exercise their relicensing rights under the CLA.


3. Development Setup

Einherjar uses a Go workspace (go.work) that spans all modules. You do not need to go get anything — local replacements are wired automatically.

Prerequisites

  • Go 1.26+
  • Git

Clone and initialize

git clone https://code.nochebuena.dev/einherjar/<module-name>
cd <module-name>

# If working across multiple modules, clone the workspace root instead
# and all modules will resolve from disk via go.work.

Verify your setup

go build ./...   # must compile clean
go vet ./...     # no warnings
go test ./...    # all tests pass
gofmt -l .       # no output (no unformatted files)

All four commands must produce clean output before a PR will be reviewed.


4. Code Standards

These are non-negotiable. Every PR is checked against them.

One exported type per file (CT-6)

Each non-test .go file may contain at most one exported TypeSpec (type, struct, or interface declaration). Unexported helpers, constants, and functions may coexist in the same file. _test.go files are exempt.

This rule exists to keep the codebase navigable: a developer who knows the type name can immediately predict the file name.

provider.go         ← type Provider interface { ... }    ✓ one exported type
component.go        ← type Component interface { ... }   ✓ one exported type
new.go              ← func New(...) + unexported impl     ✓ zero exported types

Formatting

All code must be formatted with gofmt. No exceptions. If gofmt -l . produces output, the PR will not be merged.

Do not configure your editor to use goimports as a replacement — it may add import groups that diverge from the project style. Use gofmt + manual import management.

Naming conventions

  • Follow standard Go naming: CamelCase for exported, camelCase for unexported.
  • Interfaces that represent a capability are named with an agent noun: Provider, Sender, Checkable.
  • Interfaces that represent a full component are named Component.
  • Config structs are named Config. One config struct per module root.
  • Constructors are named New (main) or NewXxx (adapters and variants).

Error handling

  • Use core/xerrors for all errors returned from public API. Never return raw errors.New or fmt.Errorf from exported functions.
  • Error codes must map to the gRPC canonical set defined in xerrors. If you need a new code, open an issue first.
  • Do not swallow errors silently. Log at the appropriate level or return them.

No comments unless necessary

Do not add comments that restate what the code already says. Only add a comment when the why is non-obvious: a hidden constraint, a subtle invariant, a workaround for a known upstream bug. If removing the comment would not confuse a future reader, do not write it.

Dependencies

Do not add new external dependencies without opening an issue and getting explicit approval first. Einherjar modules are deliberately lean. Every new dependency increases the blast radius for downstream consumers.


5. Commit Messages

Follow the Conventional Commits specification:

<type>(<scope>): <short description>

[optional body]
Type When to use
feat New exported function, type, or behavior
fix Bug fix in existing behavior
docs Documentation only
test Tests only, no production code change
refactor Code restructure with no behavior change
chore Build system, CI, dependency updates

Scope is the module name without the einherjar/ prefix: core, web, db-postgres, cache-valkey, etc.

Examples:

feat(cache-valkey): add IncrWithTTL for atomic fixed-window counters
fix(db-postgres): handle pgconn deadline exceeded as ErrDeadlineExceeded
docs(web): document rate limiter fail-open behavior

Keep the subject line under 72 characters. Write in the imperative mood ("add", "fix", "remove" — not "added", "fixes", "removed").


6. Submitting a Pull Request

  1. Open an issue first (see §1) unless the change is trivial.
  2. Fork the repository and create a branch from main:
    git checkout -b feat/your-feature-name
    
  3. Make your changes following the standards in §4.
  4. Ensure all verification commands pass (§3).
  5. Open the PR against main using the provided PR template.
  6. Post the CLA comment on the PR before requesting review (see §2 and CLA.md).
  7. Respond to review feedback. Keep the review cycle short by addressing all comments before re-requesting review.

Branch naming

Prefix Use for
feat/ New features
fix/ Bug fixes
docs/ Documentation changes
test/ Test additions or improvements
refactor/ Refactors without behavior change

PR size

Keep PRs focused. A PR that does one thing is easier to review, faster to merge, and safer to revert if needed. If your change naturally spans multiple concerns, split it into multiple PRs.


7. Reporting Bugs

Open an issue with the following information:

  • Module affected (einherjar/db-postgres, einherjar/web, etc.)
  • Go version (go version)
  • Minimal reproduction — the smallest code snippet that demonstrates the problem
  • Expected behavior vs actual behavior
  • Error output if applicable (sanitize any credentials or sensitive data)

Do not open a PR to fix a bug without first opening an issue. The bug may be intentional behavior, already fixed on main, or caused by something outside the module.


8. Requesting Features

Open an issue with:

  • What you want to add and why it belongs in the framework (not in application code)
  • Which module it affects, or whether it requires a new module
  • API sketch — what the interface, function signature, or config field would look like
  • Alternative approaches you considered

Feature requests that add a new external dependency, change a public interface, or cross module boundaries require longer discussion before approval.


9. What Gets Accepted

Einherjar is a focused framework. It covers a specific, well-defined set of infrastructure concerns. Contributions that fall outside that scope — however well-written — will not be merged.

What fits:

  • Bug fixes in existing behavior
  • Performance improvements with benchmarks
  • Missing error mappings for existing drivers
  • Documentation improvements and example corrections
  • Test coverage for untested edge cases

What does not fit without prior architectural agreement:

  • New modules (open an issue first)
  • New external dependencies
  • Changes to public interfaces in any module
  • Features that belong in application code rather than the framework

If you are unsure, open an issue and ask. It costs nothing and saves everyone time.


Einherjar was built for those who come after. Contributions that hold to that standard — clear, documented, tested, designed for the developer who was never in the room — are always welcome.

NOCHEBUENADEV