feat(mcp): index struct fields and interface method sets #1

Merged
Rene Nochebuena merged 1 commits from feat/index-struct-fields-and-interface-methods into main 2026-06-10 10:38:30 -06:00

Summary

The framework indexer now captures the members of composite types — struct fields (with their tags) and interface method sets — closing a gap where get_symbol/search_symbols could name a type but not describe its shape.


Type of change

  • Bug fix — non-breaking change that resolves an issue
  • New feature — non-breaking addition of functionality
  • Breaking change — alters existing behavior or public API
  • Documentation update
  • Refactor — no functional change, no new API surface
  • Test improvement

Description

Problem. formatNode truncates every declaration at the first {, so the index stored only the one-line header of each type. For a struct that meant the field names, types, and struct tags were dropped — e.g. nothing in the index revealed that db-postgres Config.Host is set by env:"EINHERJAR_PG_HOST,required". For an interface it meant the method set was dropped — you could not learn what Provider requires. This surfaced in practice while wiring an app: the index could be asked "does Config have a DSN() method?" and "what env vars configure Postgres?" and answer neither. (The separate DSN()-not-found incident was a stale deployed index, not a builder bug — the builder already indexes methods, and a redeploy fixes it. This PR fixes the structural truncation that no redeploy would have cured.)

Approach. Extend the index schema with two optional, omitempty members on Symbol:

  • fields []Field — for struct types: name, type, raw struct tag (backticks stripped), doc, and an embedded flag.
  • methods []Method — for interface types: name, signature (without the leading func), doc; embedded interfaces included.

The builder fills these by inspecting each type's TypeSpec (*ast.StructType / *ast.InterfaceType) via small helpers, using a new non-truncating nodeString printer alongside the existing formatNode (which still produces the one-line header). search_symbols now also matches a query against field names/types/tags and interface method names/signatures, so an env-var key or a method name resolves to the declaring type. get_symbol needed no logic change — it already returns the whole Symbol.

Alternatives considered. Storing a single untruncated "full definition" string per symbol — rejected because it would drag in function bodies and is far less queryable than structured fields/methods (no search-by-tag).

Versioning / schema. Index SchemaVersion is intentionally unchanged (additive, omitempty fields — older consumers parse the new index unchanged). The release version is a minor bump, v0.2.0 (new non-breaking functionality over v0.1.1); CHANGELOG.md and RELEASE.md are set accordingly — adjust the 2026-06-10 date if the release commit lands on a different day.

Known limitations / follow-up. The committed data/index.json placeholder is untouched; the index is regenerated at image build (Dockerfile runs cmd/indexer), so the live service only gains the richer data after a rebuild/redeploy — please redeploy (this also clears the pre-existing stale-index issue). Generic type parameters and const values are out of scope here.


Testing

  • I added or updated tests that cover my changes
  • All tests pass locally — go test ./...
  • No formatting issues — gofmt -l . produces no output
  • No vet warnings — go vet ./... is clean

New internal/index/builder_test.go (the package had no tests before) builds a temp module fixture and asserts: struct fields with tags and docs, embedded fields, interface methods with signatures and docs, embedded interfaces, and search-by-struct-tag. Reindexing the live framework confirms db-postgres.Config now exposes all 12 fields with their EINHERJAR_PG_* tags (and Config.DSN), and Provider exposes GetExecutor/Begin/BeginTx/Ping/HandleError.


Checklist

  • At most one exported type per non-test .go file (CT-6) — new Field/Method types live in index.go beside the existing Symbol/Module/etc. type group; consistent with the file's established multi-type schema layout
  • No new external dependencies added without prior discussion in an issue — none added
  • Public API changes are reflected in CHANGELOG.md
  • Breaking changes include a migration note in the PR description above — n/a, non-breaking

## Summary The framework indexer now captures the **members** of composite types — struct fields (with their tags) and interface method sets — closing a gap where `get_symbol`/`search_symbols` could name a type but not describe its shape. --- ## Type of change - [ ] Bug fix — non-breaking change that resolves an issue - [x] New feature — non-breaking addition of functionality - [ ] Breaking change — alters existing behavior or public API - [ ] Documentation update - [ ] Refactor — no functional change, no new API surface - [ ] Test improvement --- ## Description **Problem.** `formatNode` truncates every declaration at the first `{`, so the index stored only the one-line header of each type. For a struct that meant the field names, types, and **struct tags were dropped** — e.g. nothing in the index revealed that `db-postgres` `Config.Host` is set by `env:"EINHERJAR_PG_HOST,required"`. For an interface it meant the **method set was dropped** — you could not learn what `Provider` requires. This surfaced in practice while wiring an app: the index could be asked "does `Config` have a `DSN()` method?" and "what env vars configure Postgres?" and answer neither. (The separate `DSN()`-not-found incident was a *stale deployed index*, not a builder bug — the builder already indexes methods, and a redeploy fixes it. This PR fixes the structural truncation that no redeploy would have cured.) **Approach.** Extend the index schema with two optional, `omitempty` members on `Symbol`: - `fields []Field` — for struct types: name, type, raw struct tag (backticks stripped), doc, and an `embedded` flag. - `methods []Method` — for interface types: name, signature (without the leading `func`), doc; embedded interfaces included. The builder fills these by inspecting each type's `TypeSpec` (`*ast.StructType` / `*ast.InterfaceType`) via small helpers, using a new non-truncating `nodeString` printer alongside the existing `formatNode` (which still produces the one-line header). `search_symbols` now also matches a query against field names/types/tags and interface method names/signatures, so an env-var key or a method name resolves to the declaring type. `get_symbol` needed no logic change — it already returns the whole `Symbol`. **Alternatives considered.** Storing a single untruncated "full definition" string per symbol — rejected because it would drag in function bodies and is far less queryable than structured fields/methods (no search-by-tag). **Versioning / schema.** Index `SchemaVersion` is intentionally **unchanged** (additive, `omitempty` fields — older consumers parse the new index unchanged). The *release* version is a **minor** bump, **v0.2.0** (new non-breaking functionality over v0.1.1); `CHANGELOG.md` and `RELEASE.md` are set accordingly — adjust the `2026-06-10` date if the release commit lands on a different day. **Known limitations / follow-up.** The committed `data/index.json` placeholder is untouched; the index is regenerated at image build (`Dockerfile` runs `cmd/indexer`), so the live service only gains the richer data **after a rebuild/redeploy** — please redeploy (this also clears the pre-existing stale-index issue). Generic type parameters and const *values* are out of scope here. --- ## Testing - [x] I added or updated tests that cover my changes - [x] All tests pass locally — `go test ./...` - [x] No formatting issues — `gofmt -l .` produces no output - [x] No vet warnings — `go vet ./...` is clean New `internal/index/builder_test.go` (the package had no tests before) builds a temp module fixture and asserts: struct fields with tags and docs, embedded fields, interface methods with signatures and docs, embedded interfaces, and search-by-struct-tag. Reindexing the live framework confirms `db-postgres.Config` now exposes all 12 fields with their `EINHERJAR_PG_*` tags (and `Config.DSN`), and `Provider` exposes `GetExecutor`/`Begin`/`BeginTx`/`Ping`/`HandleError`. --- ## Checklist - [x] At most one exported type per non-test `.go` file (CT-6) — new `Field`/`Method` types live in `index.go` beside the existing `Symbol`/`Module`/etc. type group; consistent with the file's established multi-type schema layout - [x] No new external dependencies added without prior discussion in an issue — none added - [x] Public API changes are reflected in `CHANGELOG.md` - [ ] Breaking changes include a migration note in the PR description above — n/a, non-breaking ---
Rene Nochebuena added 1 commit 2026-06-10 10:38:22 -06:00
Minor release. The indexer named composite types but could not describe
their shape: every declaration was truncated at the first brace, so a
struct symbol carried only its `type X struct` header and an interface
symbol only its `type X interface` header. Field names, field types, and
— most painfully — struct tags such as `env:"EINHERJAR_PG_HOST"` were
dropped, as were the method sets of every port interface. An assistant
could be told that `db-postgres` has a `Config` and a `Provider`, but not
what env vars configure the one or what methods the other requires. This
change captures both.

internal/index (schema):
- Symbol gains two optional fields. `fields` ([]Field) carries a struct's
  field set — name, type, raw struct tag (surrounding backticks stripped),
  doc comment, and an `embedded` marker. `methods` ([]Method) carries an
  interface's method set — name, signature without the leading `func`, and
  doc comment, including embedded interfaces. Both are omitempty and absent
  for every other kind.
- SchemaVersion is deliberately unchanged. The two additions are additive
  and omitempty, so an older consumer parses the new index unchanged; per
  the existing rule the constant only bumps on a breaking format change.

internal/index (builder):
- collectSymbols now inspects each type's TypeSpec and, for a *ast.StructType
  or *ast.InterfaceType, fills the new Symbol members. A grouped field
  declaration (`x, y int`) yields one Field per name; an embedded field or
  interface yields an entry with an empty name.
- New helpers: typeSpecType (underlying type expr of a lone type spec),
  extractFields, extractIfaceMethods, fieldDoc (doc comment or trailing
  line comment), and nodeString — a non-truncating printer used for field
  types, tags, and method signatures, distinct from formatNode which keeps
  truncating to produce the one-line header.

internal/index (search):
- matches() now also tests the query against struct field names, field
  types, and struct tags, and against interface method names and
  signatures. A query like an env-var key or a method name now resolves to
  the type that declares it.

internal/tools:
- get_symbol and search_symbols descriptions updated to advertise the new
  struct-field and interface-method coverage. No input/output schema change
  beyond the additive Symbol fields, which get_symbol already returns whole.

internal/index (tests):
- New builder_test.go — the package previously had no tests. Builds a
  temporary module fixture and asserts capture of struct fields (with tags
  and docs), embedded fields, interface methods (with signatures and docs),
  embedded interfaces, and discovery of a struct by one of its struct tags
  (the failure mode that motivated the change).

Docs:
- CHANGELOG.md gains an [Unreleased] entry; README.md tool table updated to
  state that get_symbol returns struct fields and interface methods and
  that search_symbols matches fields, tags, and methods.

No new dependencies. The committed data/index.json placeholder is untouched
— the index is regenerated at image build (Dockerfile runs cmd/indexer),
so a deployment must be rebuilt to serve the richer index; a server still
running the prior image keeps serving the older, member-less one.
Rene Nochebuena requested review from CoreDevelopers 2026-06-10 10:38:22 -06:00
Rene Nochebuena requested review from Agents 2026-06-10 10:38:22 -06:00
Rene Nochebuena merged commit 13a186c60a into main 2026-06-10 10:38:30 -06:00
Rene Nochebuena deleted branch feat/index-struct-fields-and-interface-methods 2026-06-10 10:38:30 -06:00
Sign in to join this conversation.
No Reviewers
einherjar/CoreDevelopers
einherjar/Agents
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: einherjar/mcp#1