eeco

Public API

The exhaustive enumeration of eeco's frozen public surface.

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

--- This document is the exhaustive enumeration of eeco's **frozen public surface**. Companion to [`USAGE.md`](USAGE.md) (the user-facing reference) and [`ARCHITECTURE.md`](ARCHITECTURE.md) (the architecture overview). ## Scope From v0.1.0 eeco follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html), under the pre-stability caveat of [`VERSIONING.md`](../VERSIONING.md) §2.1. The semver promise covers exactly the items listed here and nothing else. A breaking change to any one of them takes a MAJOR bump once eeco is post-1.0; while eeco is on the `v0.x` line a MINOR MAY make it, with a CHANGELOG migration note. Internal package APIs under `internal/` are not part of this surface and may change in any release. v0.1.0 is the first public release of the cockpit-generator product: every CLI command, flag, config key, JSON top-level key, queue and ledger format, memory frontmatter field, and builtin workflow name enumerated below is part of the tracked surface from this release. Cockpit (`eeco cockpit …`, the `cockpit-sync` builtin, `handover_glob`, and `state/cockpit.json`) is **pre-1.0 and not yet frozen** — see [`COCKPIT.md`](COCKPIT.md). It is deliberately **not** enumerated in the frozen lists below. ## Frozen surface ### CLI commands and flags Every command and flag documented in [`USAGE.md`](USAGE.md) §4. The `eeco gates check-attribution` subcommand and its flag set (`--paths`, `--commits`, `--no-commits`, `--no-files`, `--exclude`) are frozen as documented in §9a; additional `eeco gates …` subcommands are additive and may land in any minor release. Additional `eeco history …` subcommands (e.g. `compact`) are likewise additive and may land in any minor release. The `eeco config` verb group (`list`, `get`, `set`, `import`, the `--global` flag on `set`, and the `--force` flag on `import`) is covered by the [`USAGE.md`](USAGE.md) §4 command list. Its existence is frozen; its exact output wording is a human-readable, evolving readout and is not frozen. The `--global` flag on `eeco cockpit target` and the `--from ` flag on `eeco init` are likewise additive and covered by §4. What `--from`/`import` copies (config.local, cockpit.json, workflows — never knowledge, state, or bug reports) is the frozen behaviour; the merge wording is not. The `eeco go --json` output is a JSON object whose **top-level keys** are frozen: `project`, `profile`, `gate`, `top_level`, `initialized`, `workflows`, `where_to_look`, `knowledge`, `open_decisions`. Their meanings are documented in [`USAGE.md`](USAGE.md) §13. Nested object fields are best-effort and may gain keys in a minor release. The `eeco go --metrics` flag is **additive and not part of the frozen surface**: it prints a human-readable assembly readout to stderr (timing, brief size, and an estimated compression of the knowledge layer) and does **not** appear in or alter the frozen `eeco go --json` nine-key surface. Its wording and the token estimates are not frozen. The `eeco stats` verb is frozen (its existence is covered by the [`USAGE.md`](USAGE.md) §4 command list), but — like `--metrics` — its **readout wording and the displayed figures are not frozen**: they are a human-readable, evolving readout aggregated from the `state/ai-calls.json` ledger, so a future minor release may add fields or formats. The `eeco report-bug --submit` flag is **additive and not part of the frozen surface**: the flag's existence is covered by [`USAGE.md`](USAGE.md) §4, but its **submission mechanism and the lines it prints are not frozen** — `--submit` currently opens the pre-filled issue URL in a browser, and a future minor release may change or add to how the report reaches the project. The invariant that `eeco report-bug` never sends anything without a human action is part of the frozen behaviour. The `eeco ask --json` output is a JSON object whose **top-level keys** are frozen: `question`, `memory`, `code`. Both arrays are always present (an empty list, never null). Their meanings are documented in [`USAGE.md`](USAGE.md) §13. Nested object fields (the per-hit `score`, `path`, `line`, etc.) are best-effort and may gain keys in a minor release. ### Workflow contract - **Exit codes:** `0` clean, `1` finding or failure, `2` blocked (a required tool is missing), `3` AI pass deferred (no consent). - The `Env` value passed to a workflow. - The read-only `gitx` helpers a user workflow may import: `Available`, `TrackedFiles`, `HeadSHA`, `ChangesSince`, `RemoteTags`, `LatestSemverTag`, `LastCommitDate`, `SemverTags`. ### Config keys Keys recognised in `/config.local`: `profile`, `gate`, `stale_days`, `attribution_pattern`, `automation`, `ai_command`, `ai_budget`, `ai_provider`, `ai_model`, `ai_api_key_env`, `session_settings_path`, `bug_report_dir`, `context_path`, `context_budget`, `brief_include_notes`, `session_start_pinned_bodies`, `session_start_docs`, `session_start_mailbox`, `session_start_roadmap_glob`, `session_files`, `version_locations`, `version_anchor`, `pre_commit_workflows`, `post_merge_workflows`, `workspace_history`. Unknown keys are tolerated and preserved for forward compatibility. Config resolves in three layers, each overriding the previous: built-in defaults → the **user-global** file → the **workspace** `config.local`. The global file is `config.local` under the user-global config directory, resolved as `$EECO_CONFIG_HOME`, else `$XDG_CONFIG_HOME/eeco`, else `$HOME/.config/eeco`. The global layer holds the same keys as a workspace `config.local`; a project inherits it unless its own `config.local` overrides the key. `eeco config set --global …` and `eeco cockpit target --global …` are the only commands that write outside a repository, and they write only into that global directory — never the tracked tree. The three-layer resolution order is a frozen behaviour; the global directory locations above are part of the contract. The `workspace_history` key selects whether `eeco init` stands up a private, local git repository inside the gitignored workspace directory to version eeco's own knowledge layer, and how often it commits: `off` (no repo), `manual` (the default — commit only on `eeco history snapshot`), or `auto` (commits automatically after each mutating verb). An unknown value falls back to the default. The repo has no remote and is never pushed; see [`USAGE.md`](USAGE.md) §11a. The `gate` key is repeatable: each occurrence declares one step of the project's parse/build gate chain (a whitespace-split command). The first occurrence resets the profile default so the operator-declared chain fully replaces it; subsequent occurrences append. A lone empty `gate=` clears the chain. The `gate` builtin workflow runs the chain in declared order, with the repository root as the working directory, stopping at the first failing step. The four `session_start_*` keys tune what the bundled session-start hook (`eeco hooks session-start on`) surfaces: - `session_start_docs` — repeatable, repo-relative path; explicit reading routine in order. When unset the hook auto-detects from a built-in list (`docs/PUBLIC_API.md`, `docs/ARCHITECTURE.md`, `CHANGELOG.md`, `ARCHITECTURE.md`, `docs/USAGE.md`, `README.md`). Paths that point outside the repo are rejected at parse time. - `session_start_mailbox` — repo-relative filename of the mailbox the hook checks for unprocessed content. Default: `Ideas.md`. Empty disables the mailbox block. - `session_start_roadmap_glob` — glob, relative to the repo root, for the live planning surface; the most-recently-modified match is appended to the reading routine. Default: `roadmap*.md`. Empty disables roadmap discovery. - `session_start_pinned_bodies` — boolean, default `false`. When `true`, the bundled session-start hook composes a fourth block that emits the full body of every `pin: true` memory fact. The `--with-pinned-bodies` flag on `eeco hooks session-emit` enables this for one invocation without editing config; the flag and the config key compose harmlessly. The `eeco hooks session-emit` subcommand also accepts `--if-initialized`, which suppresses all output unless the working directory holds an initialized eeco workspace (the `IsInitialized` 5-subdir check). It composes with `--with-pinned-bodies`. The command installed by `eeco hooks session-start on` (and rewritten by `… refresh`) now carries this flag, so the bundled brief emits only inside an eeco workspace — repos without one stay silent regardless of which docs they contain. The three `ai_*` provider keys select and tune which provider gated AI passes use: - `ai_provider` — `cli` or `none`, or empty to auto-select. `cli` uses the CLI provider when `ai_command` is set, otherwise the not-configured stub (every pass parks). Empty, `none`, or any unknown/legacy value (e.g. `anthropic`) auto-selects the CLI provider when `ai_command` is set, otherwise the not-configured stub — never a config error. eeco runs no in-binary model client; the AI lives in the harness eeco configures. - `ai_model` — an inert legacy key. It is read and passed through but ignored by the CLI provider; there is no native API path to consume it. Kept only so an old `config.local` loads unchanged. - `ai_api_key_env` — an inert legacy key naming an environment variable. The retired native provider read its API key from it; the CLI provider does not. Kept only for backward-compatible config loading; no key value is read from or written to disk. The `version_locations` key is repeatable; each value is a `:` pair split on the first colon, and the regex must declare at least one capture group. Absolute paths and `..` traversal are rejected at parse time. The `version-sync` builtin workflow consumes the list; with no entries the workflow exits 0. The reserved value `auto` switches `version-sync` to scan a fixed set of common version files instead of an explicit list; it must stand alone and cannot be mixed with `path:regex` entries. The `version_anchor` key is single-valued and selects the source of truth `version-sync` compares declared `version_locations` against. Three modes: unset (default) keeps the consistency-only behaviour (first declared location is the anchor); `tag` uses the latest semver-shaped (`vX.Y.Z`) tag reachable from HEAD and lets declared locations be semver-`>=` the tag so a release commit can bump declared locations ahead of the not-yet-pushed tag (backward-drift still fails); a `:` value designates a file whose captured version is the source of truth, and declared locations must strict-equal it. Absolute paths and `..` traversal in the designated-file form are rejected at parse time; the regex must declare at least one capture group. The `pre_commit_workflows` key is repeatable; each value is one builtin workflow name. The first occurrence in the file resets the binary default (`leak-guard`, `version-sync`), subsequent occurrences append. An empty value clears the list and `eeco hooks pre-commit on` refuses to install. Whitespace inside a value is rejected at parse time; unknown workflow names are rejected at hook-install time. The `post_merge_workflows` key is repeatable with the same semantics as `pre_commit_workflows`; each value is one builtin workflow name run by the `post-merge` hook after a merge. The first occurrence resets the binary default (`memory-drift`, `doc-drift`, `manifest-refresh`), subsequent occurrences append. An empty value clears the list and `eeco hooks post-merge on` refuses to install. Whitespace inside a value is rejected at parse time; unknown workflow names are rejected at hook-install time. The binary default additionally wires the pre-1.0 `cockpit-sync` machinery (not part of the frozen builtin enumeration — see [`COCKPIT.md`](COCKPIT.md)). The `context_budget` key is single-valued: a non-negative integer byte cap on the file `eeco go --write` renders. When positive, `eeco go --write` trims the saved brief down a deterministic ladder (full, then the smaller `--brief` form with progressively shorter lists) until it fits the budget. `0` (the default) means no cap; an empty value resets to the default; a negative value is rejected at parse time. The `brief_include_notes` key is single-valued and boolean: when set truthy, `eeco go` adds a **Recent notes** section to the Markdown brief, listing the five newest files under `/notes/`. The JSON brief (`eeco go --json`) is unchanged — the nine frozen top-level keys remain the only surface; notes live on the Markdown channel only. Accepted values are the standard `strconv.ParseBool` set (`true`/`false`, `1`/`0`, `t`/`f`, case-insensitive); an empty value resets to the default `false`; anything else is rejected at parse time. The `session_files` key is repeatable; each value declares one text/markdown file where the `session-start` hook maintains a marker block carrying the same content `eeco hooks session-emit` prints. An entry is either repo-relative (held inside the repo by the same path-traversal guard `session_start_docs` uses) or absolute (matching the precedent set by `session_settings_path`). Whitespace inside a value is rejected at parse time. With no entries the file-delivery channel is disabled; the JSON-settings channel keyed by `session_settings_path` is independent and either channel alone is enough for `eeco hooks session-start on`. The block is fenced by `` / ``; bytes outside the marker pair are never edited. `eeco hooks session-start refresh` re-renders the block; `eeco hooks session-start off` removes the block (or the whole file when eeco created it and the block was its only content). ### Memory frontmatter Each memory fact carries flat, shell- and Go-parseable frontmatter: | Field | Meaning | | ------------- | ---------------------------------------------------------------- | | `name` | kebab-case identifier. | | `description` | one-line summary, used for relevance matching. | | `type` | one of `user`, `feedback`, `project`, `reference`, `finding`. | | `created` | creation date, `YYYY-MM-DD`. | | `last_used` | date the fact was last surfaced, `YYYY-MM-DD`. | | `ref` | optional repo-relative path; garbage collection validates it. | | `expires` | optional expiry date, `YYYY-MM-DD`. | | `status` | optional; for `finding` facts only — `open` or `resolved`. | | `pin` | `true` or `false`; a pinned fact is never garbage-collected. | | `source` | optional snippet (≤120 chars) of what triggered the fact. | | `agent` | optional assistant identity that recorded the fact. | | `disabled` | optional; `true` hides the fact from `eeco go` and `eeco ask` and exempts it from garbage collection. Omission means `false`. | `source`, `agent`, and `disabled` are additive and optional on the wire: a fact file without them still loads. `disabled` is omitted from serialisation when `false` so legacy facts round-trip without gaining a new line. The `eeco add fact` CLI requires `--provenance` (which populates `source`) for `--type=feedback` and `--type=user` facts; the store layer remains permissive so a hand-authored fact can still be loaded if its provenance is unknown. ### Queue file format `state/queue.md` is a Markdown checklist. Each item is one line — `- [ ] **** — _(<project>, <date>)_` — followed by an indented detail line. The count of unchecked items is the queue count. ### Ledger formats Three ledger files inside `<workspace>/state/` are part of the frozen surface. All follow the same additive discipline: adding a top-level key or a per-record field is non-breaking; older ledgers without it still load. Removing or renaming a ledger filename, a top-level key, or a documented per-record field is breaking and ships in a major release only. **`state/hooks.json`** records every hook eeco has installed, so each one can be cleanly reverted. The toggleable hook names are `pre-commit`, `post-merge`, `session-start`, `commit-msg`, and `commit-guard` (`eeco hooks <name> on|off`); the ledger carries one record per hook (`pre_commit`, `post_merge`, `session_start`, `commit_msg`, `commit_guard`). The `session_start` record may carry an additional `files[]` array — one entry per file the file-delivery channel manages (`session_files`), each recording its `path`, the `sha256` of the eeco-written block at install time, and a `created` boolean; a ledger without the field still loads. The `commit_msg` and `commit_guard` records are optional; a ledger without the key still loads (absent = off). Additive `eeco hooks` subcommands and managed hook names (like `commit-guard`) are non-breaking, the same way additive `eeco gates` subcommands are: they extend the surface without changing the frozen verb set. **`state/evolve-history.json`** records every workflow candidate the `evolve` builtin has surfaced, so a recurring signal is proposed exactly once in its lifetime and the operator's resolve-state on the queue row can be reconciled back into the ledger. Top-level shape: `{ "records": [ … ] }`. Per-record fields: | Field | Meaning | | ------------------- | ----------------------------------------------------------------------- | | `signal_kind` | the signal class (`commit-type` today; future kinds are additive). | | `signal_key` | the specific signal value (e.g. `fix`). | | `count_at_proposal` | the signal's count in the inspected history window at proposal time. | | `queue_kind` | the queue row's `Kind` (always `evolve`). | | `queue_title` | the queue row's `Title` (e.g. `Workflow candidate: fix-workflow`). | | `proposed_at` | RFC 3339 UTC timestamp of the proposal. | | `resolved` | optional; `true` once the queue row's checkbox is ticked. Omitted false. | | `resolved_at` | optional; RFC 3339 UTC timestamp of the resolution. Omitted when empty. | A corrupt ledger file degrades to the empty ledger so a broken file never wedges `evolve`; the next save rewrites it. The same additive-field discipline applies — later slices may add fields (e.g. accepted-vs-rejected disambiguation) without breaking older readers. **`state/ai-calls.json`** records every gated provider attempt — ran, parked, or gated-out — so the operator has an audit trail of what the AI was asked and what it produced. It stores hashes, never raw text: the prompt and response bodies live under `state/parked/` when applicable and are not duplicated here. Top-level shape: `{ "records": [ … ] }`. Per-record fields: | Field | Meaning | | ----------------- | ----------------------------------------------------------------------------- | | `label` | the call's parking/ledger key (e.g. `evolve`, `bug-sweep`). | | `provider` | the selected provider's name (`cli` or `none`; `anthropic` may appear in old ledgers — a legacy/tolerated value, not selectable). | | `model` | optional; the model the provider resolved (omitted when none was resolved). | | `prompt_sha256` | SHA-256 of the folded prompt (the same bytes the parked file stores). | | `response_sha256` | optional; SHA-256 of the response text. Omitted on a parked pass, except a pass blocked by the attribution filter, which still records the blocked response's hash. | | `ran` | `true` when the provider produced text. | | `parked` | `true` when the pass was parked (no consent, over budget, or provider error). | | `park_reason` | optional; why the pass parked. Omitted when empty. | | `tokens` | `{ input, cached_input, output }` token counts; zero on a pass parked before the provider was called. | | `tools` | optional; the tool names the model invoked in this round (a tool-using chat pass records one entry per round). Omitted when empty. | | `ts` | RFC 3339 UTC timestamp of the attempt. | A corrupt `ai-calls.json` degrades to the empty ledger and the next write rewrites it; recording is best-effort and never turns a gated pass into a hard failure. ### Builtin workflow names `comment-hygiene`, `leak-guard`, `version-sync`, `gate`, `bug-sweep`, `handover-refresh`, `evolve`, `memory-drift`, `doc-drift`, `manifest-refresh`. Removing or renaming any one of these is a breaking change. ### `eeco version` output The first line, `eeco <version>`, is stable. The indented `commit:` and `built:` lines are best-effort build metadata; they may change and are not intended to be parsed. ## Not frozen Internal package APIs under `internal/` — the packages the CLI and the builtin workflows are implemented on top of — are deliberately excluded from the public surface and may change in any release. --- [← Prev: Architecture](ARCHITECTURE.md) · [Next: Extending →](../EXTENDING.md)