eeco

Extending

Where each kind of extension plugs in, seam by seam.

README · Vision · Cockpit · Usage · Architecture · Public API · Extending · Contributing · Upgrading · Versioning · Changelog · Security

--- ## What this is This is the how-to companion to the **Extension seams** table in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md#extension-seams). That table is the authoritative map of where each kind of extension is registered; this guide expands the two most common seams into step-by-step examples and gives a short reference for the rest. eeco is built so that most extensions have a **single registration point** plus one new file. The verb dispatcher, the workflow registry, the config parser, and the TUI command index each read from one list, and adding an entry there is the whole job for the low-friction seams. One seam — a new hook type — is deliberately higher friction; see [Hook types](#hook-types) below. Before you start, read [CONTRIBUTING.md](CONTRIBUTING.md) for the build, the gates, and the Definition of Done every change meets. ## Add a builtin workflow A builtin workflow is a Go type that ships in the binary and runs via `eeco run `. The whole job is one new file plus one line in the registry. **1. Implement the `Workflow` interface** (`internal/workflow/workflow.go`) in a new file `internal/workflow/.go`. The interface is three methods: ```go func (myWorkflow) Name() string { return "my-workflow" } func (myWorkflow) Summary() string { return "one line shown by eeco run." } func (myWorkflow) Run(env Env) (Result, error) { /* ... */ } ``` Use an existing workflow as the model — `internal/workflow/leakguard.go` is a good template for a read-only gate. **2. Register it** by adding a zero value to the `DefaultRegistry` slice literal in `internal/workflow/registry.go`: ```go for _, w := range []Workflow{commentHygiene{}, leakGuard{}, /* ... */, myWorkflow{}} { ``` Lookup, listing, and tab-completion are all derived from this slice — there is nothing else to wire. **3. Test it** in `internal/workflow/_test.go`. **4. If it is user-facing**, the workflow name is part of the frozen surface: add it to [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md) (builtin workflow names), document it in [`docs/USAGE.md`](docs/USAGE.md) and run `make sync-guide` to mirror that into the binary, then add a [CHANGELOG.md](CHANGELOG.md) entry. **Worked reference:** commit `575557b` added the `commit-guard` workflow exactly this way — `internal/workflow/commitguard.go` plus the one registry line plus a test — in the same change that added its companion hook type. ## Add a `config.local` key `config.local` is the per-workspace settings file. A new key is a struct field plus a parse case. **1. Add the field** to the `Config` struct in `internal/config/config.go`, with its JSON tag. **2. Give it a default** if it carries one — a `Default…` const applied in `Load` so an absent key resolves to a safe value (the unknown-to-default floor). **3. Parse it** in `applyLocal`, the switch that maps each `config.local` key onto its field. Validate or normalize there when the value is constrained: an enum key falls back to its default on garbage input, never crashes. **4. Test it** in `internal/config/config_test.go`, including the garbage-in case. **5. If it is user-facing**, config keys are frozen: add it to [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md) and document it in [`docs/USAGE.md`](docs/USAGE.md) (then `make sync-guide`), with a CHANGELOG entry. **Mind the ripple.** The registration is small, but every read site is a touch. Commit `5d5b2d9` added `workspace_history` — a single struct field and parse case, yet roughly fifteen call sites across `cmd/eeco/` read it to decide whether to auto-commit. Plan for where the value is consumed, not only where it is declared. ## Other seams (reference) Each entry below is the short form; the [Extension seams table](docs/ARCHITECTURE.md#extension-seams) carries the canonical list with the full touch-set. - **CLI verb** *(low)* — register in the `run` dispatch switch in `cmd/eeco/main.go` and add it to the `usage` const; put the runner in a new `cmd/eeco/.go`, reusing the `loadInitedConfig` / `loadRepoConfig` / `newFlagSet` guards in `cmd/eeco/helpers.go`. - **Memory frontmatter field** *(medium)* — add the field to the `Fact` struct in `internal/memory/fact.go`, then teach `frontmatter.go` to read it (`setField`) and write it (`Serialize`); add a `Validate` rule if it is constrained. - **AI provider** *(medium)* — add the provider type (`Name` / `Run`) and wire it into the `Select` chooser in `internal/ai`, plus the `config.local` key that selects it (see above). - **TUI slash-command** *(low)* — add it to the `commandIndex` in `internal/tui/commands.go` and handle it in the `dispatch` switch; completion is derived automatically. - **TUI chat tool** *(low)* — add it to the `chatTools` set in `internal/tui/tools.go` and handle it in `chatExecutor`. The chat-tool registry is read-only by construction, so a tool never gains a new write capability. - **Hook type** *(HIGH)* — see below. ### Hook types A new hook type is the one irreducibly multi-file seam, and the friction is the price of the trust boundary. A hook is an opt-in, reversible escape from the workspace, so every hook type carries a ledger field that records its install for exact, byte-identical removal. Adding one touches the name const and the `Names` list, the `ledger` struct, the enable/disable/refresh trio, and the `Status` read-out in `internal/hooks/hooks.go`, plus the CLI dispatch in `cmd/eeco/hooks.go` — and they stay in lock-step. Commit `575557b` is the worked reference (it added the `commit-guard` hook). Prefer the single-registration seams above; add a hook type only when the capability genuinely needs to leave the workspace. See **Trust boundaries** in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for why this coupling is deliberate. ## Before you ship Every change meets the Definition of Done in [CONTRIBUTING.md](CONTRIBUTING.md): `make verify`, `make gates`, `make lint`, and `make cover-check` green; additive over [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md) unless you declare a semver bump (see [`VERSIONING.md`](VERSIONING.md)); no AI attribution; a CHANGELOG entry when the change is user-facing. --- [← Prev: Public API](docs/PUBLIC_API.md) · [Next: Contributing →](CONTRIBUTING.md)