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.
This commit is contained in:
247
CONTRIBUTING.md
Normal file
247
CONTRIBUTING.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# 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](#1-before-you-start)
|
||||
2. [Legal: CLA and Copyright](#2-legal-cla-and-copyright)
|
||||
3. [Development Setup](#3-development-setup)
|
||||
4. [Code Standards](#4-code-standards)
|
||||
5. [Commit Messages](#5-commit-messages)
|
||||
6. [Submitting a Pull Request](#6-submitting-a-pull-request)
|
||||
7. [Reporting Bugs](#7-reporting-bugs)
|
||||
8. [Requesting Features](#8-requesting-features)
|
||||
9. [What Gets Accepted](#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.
|
||||
|
||||
---
|
||||
|
||||
## 2. Legal: CLA and Copyright
|
||||
|
||||
### 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](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.
|
||||
|
||||
### Copyright
|
||||
|
||||
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](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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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](https://www.conventionalcommits.org/) 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`:
|
||||
```bash
|
||||
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](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**
|
||||
Reference in New Issue
Block a user