feat(mcp): check_env struct-level granularity via composes selectors

Patch to v1.1.1. check_env could only reason at module granularity, so a var
that lives on one struct of a multi-struct module (e.g. EINHERJAR_SERVER_CORS_ORIGINS
on web.Config, not web/server.Config) couldn't be flagged dead when the app
composes the sibling struct. Adds struct-level selectors to close that gap.

internal/tools (check_env):
- New `composes` input: exact config structs as module/subpackage/Struct selectors
  (e.g. web/server/Config, db-postgres/Config, core/logz/Config), alongside or
  instead of the coarse `modules` list.
- not-composed now reasons over the union of composed vars (from modules AND
  struct selectors), so it surfaces struct-level dead vars. Coarse modules:["web"]
  keeps the prior behavior.
- parseStructSelector helper; errors on malformed or non-existent selectors.

Tests: struct-level dead-var catch (CORS flagged with web/server/Config but not
with module web), bad-selector errors; existing calls updated to the new arg.
Version bumped to v1.1.1 (badge + serverVersion). No dependency changes.
This commit is contained in:
2026-08-07 19:38:00 -06:00
parent 850b63607c
commit cff5a7b4c8
6 changed files with 135 additions and 24 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# einherjar/mcp
[![version](https://img.shields.io/badge/version-v1.1.0-5C4EE5?style=flat-square)](https://code.nochebuena.dev/einherjar/mcp)
[![version](https://img.shields.io/badge/version-v1.1.1-5C4EE5?style=flat-square)](https://code.nochebuena.dev/einherjar/mcp)
[![license](https://img.shields.io/badge/license-AGPL--3.0-22863A?style=flat-square)](LICENSE)
[![go](https://img.shields.io/badge/Go-1.26+-00ADD8?style=flat-square&logo=go&logoColor=white)](https://go.dev)
@@ -52,7 +52,7 @@ Cursor, Zed, and anything else that speaks MCP):
| `get_example` | Canonical usage snippet — pulled from module READMEs and from the synthetic `wire` conventions |
| `get_scaffold` | The canonical **minimum application scaffold** as ready-to-write files — a clean `main.go`, `internal/wire/wire.go`, a composed `internal/config`, a health hook, and a **`.env.example` derived from the real component-config tags**. Use it when starting a new Einherjar service |
| `get_config_env` | The **real** env vars a component config reads — name, declaring module/struct/field, required, and default — derived from the framework's struct tags. The source of truth for building a global config or a `.env` |
| `check_env` | Check a `.env` / `.env.example` against the framework: flags EINHERJAR_* names that don't exist (e.g. `EINHERJAR_PG_DATABASE`), missing required vars for the modules you compose, and vars set for a module you don't compose |
| `check_env` | Check a `.env` / `.env.example` against the framework: flags EINHERJAR_* names that don't exist (e.g. `EINHERJAR_PG_DATABASE`), missing required vars, and dead vars nothing composed reads. Declare what the app composes by `modules` (coarse) or `composes` struct selectors like `web/server/Config` (precise — catches struct-level dead vars) |
| `get_compliance` | Interface assertions 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; returns findings with severity, hint, and line |