Skip to content

mcpx testing: inventory every feature against its integration tests, and guard that each has one #1978

Open

Description

@dezren39

Relocated from dezren39/darwin-nix#132 on 2026-10-28 (placement audit): mcpx's single source lives in developing-today/code (pkgs/mcpx); re-home tracked at #309. Original body below; issue references inside it are historical (dezren39/nix numbering).

Parent: #97

Summary

Map every mcpx feature to the integration tests that exercise it, list the features with none, and then make the map enforce itself: a guard, in the style of TestEveryDeclaredRouteAnswersOverTheSocket, that fails CI when a new op, command or setting ships without an integration test. The opencode plugin is part of the inventory. It was item 6 in the brief and is folded in here because its first step is small: run the test file that already exists.

This is a review (audit what exists) followed by a design (the guard). Closing the gaps it finds is follow-up implement work. File that work as one issue per feature group, not per op.

Original notes

  • testing
    • a general goal is to ensure all features have one or more integration tests
      • the mcp servers can be specialized mocks but the app entrypoint or binary is used and config is passed in or config path set to appropriate test
      • for both binary && app entrypoint (consider binary a superset)

For this issue, "integration test" means what the two child bullets say: the real binary or an app entrypoint (#133) drives mcpx, mock MCP servers are allowed, and config is passed in. A unit test of internal/<pkg> does not count, however thorough it is.

Current state (written on d0b7e01; counts re-checked at 05c78b2)

Surfaces, and whether a guard can enumerate them

Surface Declared in Enumerable by a test today?
CLI commands handlers map literal inside main(), cmd/mcpx/main.go:119-162: 42 names including the aliases list/namespaces and logs No. It is a local variable in main. A guard needs it exported, which the cli.Run refactor in #133 does as a side effect
/v1 ops api.Ops(), internal/api/ops*.go: 53 distinct paths Yes
MCP tools at /mcp the same ops table. internal/api/parity_test.go:87 TestEveryNonStreamingOpIsReachableAsAnMCPTool Yes
mcpx serve --transport stdio app.CmdServe One e2e test: internal/e2e/proto_test.go:289 TestAStdioClientIsAskedOnTheWire
Settings internal/settings registry, about 149 Path: entries at d0b7e01; 152 at 05c78b2 after #171 (registry.go 57, wire.go 45, consumer.go 15, exec.go 13, plugin.go 10, proto.go 9) Yes
Plugin plugin/opencode/mcpx-session.ts, mcpx-tui.tsx, mcpx/daemon.ts No registry. The surface is the opencode hooks (shell.env, tools, and so on)

Gaps found by a cheap grep

Method: search internal/e2e/*.go for the literal command name as the first argument of e.run/e.try/e.runStdin, and for the literal /v1 path prefix. The grep can mislead in both directions. A mention does not prove a test, and a missing literal does not prove missing coverage, because the CLI reaches /v1 routes indirectly. The inventory has to go feature by feature. This list only says where to look first.

  • CLI commands never invoked by e2e: adapter, api, completion, daemon, help, init, man, openapi, registry, scripts, plus the aliases namespaces and logs. Some are partly covered elsewhere. internal/adapter, internal/openapi and internal/registry have unit tests. package.nix postInstall runs man and completion, but nothing checks their output.
  • /v1 paths with no literal in e2e (26 of 53): /v1/ask, /v1/ask/{id}, /v1/ask/{id}/abandon, /v1/ask/{id}/answers, /v1/client.ts, /v1/elicit, /v1/elicit/{id}, /v1/elicit/{id}/{action}, /v1/globals.d.ts, /v1/intent, /v1/namespaces, /v1/prompt, /v1/prompts, /v1/recipes, /v1/recipes/{name}, /v1/recipes/{name}/run, /v1/refresh, /v1/resource, /v1/resource-templates, /v1/resources, /v1/restart, /v1/search, /v1/session/release, /v1/shutdown, /v1/status, /v1/types. Several of these are reached through the CLI (mcpx elicit, mcpx status, mcpx prompt). All non-streaming ones are hit by the route guard, but that guard only asserts "not 404 and not 5xx" (internal/e2e/v1ops_test.go:350-357). It proves the route is served, not that it is correct.

Feature by feature (starter table; the review completes it)

Feature e2e evidence today Gap to audit
Launchers with @placeholders (#7) e2e_test.go (19 mentions of launcher) depth
Recipes, mcpx prompt, POST /v1/intent consumer_test.go:92-196 through the CLI /v1/intent is never called directly
Artifacts and ExecOptions (#40) exec_test.go:22-494 (11 tests) none obvious
Elicitation broker proto_test.go, consumer_test.go:248-331, e2e_test.go seedElicitation (e2e_test.go:2367-2381 at 05c78b2) shells out to python3 for sqlite3. That is a hidden environment dependency (#134)
Sampling passthrough proto_test.go:287-353 (stdio client asked sampling/createMessage) HTTP /mcp sampling path
Tasks v1ops_test.go cancel/result edge cases
mcpx api --spec (OpenAPI to tools) none the whole feature
CLI to MCP adapters (paths.adapters) none the whole feature
Registry search/add search against the stub (#65) the registry CLI; add
Live server add/remove settings_test.go:111,139 remove
Content-addressed exec workdirs no literal workdir in e2e reuse, invalidation
Connection ladder, inline mode, named endpoint e2e_test.go:2383-2468 (05c78b2) remote success path (#134)
Every runtime runtime_test.go:13,45 these skip when a runtime is missing (runtime_test.go:17-18), so a runner without bun is silently green (#134)
Plugin plugin/opencode/mcpx/daemon.test.ts: 21 bun:test cases at 05c78b2 of the discovery ladder against fake daemons (Bun.serve({unix})) still not run by CI (no bun test step in .github/workflows/). mcpx-session.ts and mcpx-tui.tsx have no tests

The existing "every X has Y" guards (the model)

Guard What it enumerates What it asserts
TestEveryDeclaredRouteAnswersOverTheSocket (v1ops_test.go:287) api.Ops() each route is served (not 404/5xx)
TestEveryDaemonRouteIsDeclared, TestEveryDeclaredOpHasARoute, TestEveryNonStreamingOpIsReachableAsAnMCPTool (api/parity_test.go:54,73,87) ops and the mux declaration and route/tool parity
TestEverySettingIsReachableOverV1 (settings_test.go:173) settings registry each setting can be read over /v1
TestEveryExecFlagChangesSomething (e2e_test.go:2877 at 05c78b2, after #74/#75) exec flags each flag changes behaviour
TestEverySettingIsReadSomewhere, TestNoHandRolledSettingEnv, TestThePluginReadsNoUndeclaredSetting (internal/settings/consumed_test.go:73,145,209, #171) settings registry, MCPX_* reads, plugin reads each setting is read by some code; no undeclared env read
TestNoPackageRedeclaresADefault (defaults/guard_test.go:92) package sources no duplicate defaults

None of them asserts that a feature has a behavioural test. They assert reachability or parity. That is the gap this issue closes. The "declared but never read" audit and guard landed in #171 (rows above). It covers "every setting is read" and should not be duplicated here; the stronger form -- reading a setting can change an observable outcome through an ordinary caller -- is #129's (#181 collision 15).

Approaches

A -- a hand-maintained inventory table in docs (least recommended)

Put the table above in docs/testing.md and ask contributors to update it. It costs nothing and helps humans. It also starts going stale the day it is written. Nothing fails when someone adds an op and skips the table, and the history (#74: flags accepted and ignored, found by accident) shows that unenforced lists do not hold. Use it only as the output of the review, not as the mechanism.

B -- a static name guard

A Go test enumerates api.Ops(), the command table and the settings registry, then greps the e2e sources for each name. Any name that never appears fails, unless it is on an exemption list with a reason. It is cheap, runs in milliseconds, needs no daemon, and can run in every shard. The weakness: a mention is not a test. A name in a comment or an unrelated string satisfies it, so it measures presence, not behaviour, and it pushes people toward adding the literal rather than the test. Aliases and paths with parameters need special-casing.

C -- a runtime coverage ledger (recommended)

The e2e harness records what each test actually exercised. e.run/e.try log the resolved command (after alias and global-flag handling). The socket/TCP client logs the op. The daemon already records every request in its log store (/v1/log), so op coverage can also be read back from each env's state dir before cleanup. A TestMain in internal/e2e writes the ledger. A guard then compares it against the three registries and fails on any entry that was neither exercised nor exempted. The exemption list is a checked-in file with one reason per entry, like the route guard's inline skip list (v1ops_test.go:294-298).

Recommended because it measures what actually ran, so gaming it takes a real call. It extends naturally to "every setting was set by some test" and to the plugin's hooks. And it composes with #133: line coverage from a -cover binary is complementary evidence, not a replacement, because a covered line is not an asserted behaviour.

Costs:

  • It needs the full e2e run. Under sharding (Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/launcher #95) each shard writes a partial ledger, and a final job merges them and runs the guard. The CI tree owns that job; this issue defines the ledger format (JSON lines: {test, kind: cli|op|mcp|setting, name}).
  • The command table must be enumerable (see above).
  • Tests that call /mcp must go through a harness helper so the ledger sees them.

Take B's static check as a cheap per-shard pre-check if the merged job comes later. Do not treat it as the end state.

The plugin (folded item 6)

  1. Run daemon.test.ts in CI. It runs as bun test plugin/opencode/mcpx/daemon.test.ts (the file's header comment). It needs only bun. The CI tree (Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/launcher #95) owns where the step goes; it is under a second of work.
  2. A plugin harness against the real daemon (build(deps): bump actions/checkout from 3 to 4 #47/Update Rust crate anyhow to 1.0.75 #48 need this). Load the plugin module with a stub opencode PluginInput, point it at an isolated e2e-style daemon (Update dependency htmx.org to v1.9.12 #138), and assert on the hooks. shell.env should inject MCPX_DAEMON_ENDPOINT (mcpx-session.ts:696), a tool call should reach fakemcp, and the TUI should load. Write it against Update Rust crate anyhow to 1.0.75 #48's harness-free core API once that exists, so the build(deps): bump actions/checkout from 3 to 4 #47 v2 rewrite reuses the same harness and does not get its own. Before Update Rust crate anyhow to 1.0.75 #48, a thin harness over the v1 plugin is acceptable, on the understanding it will be rewritten.

Open questions

Ease · Impact · Related

Ease M (review S; ledger and guard M; closing the gaps is separate implement work) · Impact high
Depends on / related: #133 (exported command table, harness runner), #138 (isolation, needed before the plugin harness), #95 (merged-ledger job, bun step), #99 (guard policy), #93 (tests that randomise take the run seed), #47, #48, #76, #74 (closed, fixed in #75 -- the precedent), #171 (merged: the declared-but-never-read guard), #129 (the behaviour-changing guard, #181 collision 15), #137 (whether real-software-only coverage counts).

Security/auth deferred; see tracker.

Acceptance criteria

  • A feature↔integration-test inventory exists (on this issue or in the docs tree and linked here) covering CLI commands/ops, settings, and the opencode plugin, naming the features with no integration test.
  • A guard in the style of TestEveryDeclaredRouteAnswersOverTheSocket fails CI when a new op, command, or setting ships without an integration test, or the guard's design is pinned here with a follow-up implementation issue filed.
  • Gaps the inventory finds are filed as follow-up issues, one per feature group (not per op), and linked here.
  • The work described in the Summary/Scope above is delivered via a branch + PR (no direct push to main); verification runs in GitHub Actions CI, not on the sandbox.
  • Evidence (CI links or inventory output as applicable) is recorded on this issue before it is closed.

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    History carried over from dezren39/darwin-nix#132 (verbatim, oldest first):


    @dezren39 · 2026-09-30T07:21:06Z

    Integration pass. Placed in the DAG rooted at #181.


    @dezren39 · 2026-09-30T12:03:22Z

    Issues pass (2026-09-30, at 05c78b2).

  3. added
    kind/testTest coverage, test infrastructure, or test-only fixes
    priority/normalOrdinary priority (the default)
    effort/mMedium: expected under 1 day of work
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 29, 2026
  4. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    Intake review

    🤖 Agent note · state: filed

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: repo-fleet-monitor run 2026-10-28 19:19 CDT
    • Found via: monitor reconciliation (intake enrichment, protocol §5)
    • Task: intake enrichment — acceptance criteria, normalized labels, this review
    • Expectation: effort/m; done when the AC section, labels, and this note are on the issue
    • Links: n/a
  5. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked-up

  6. 51 remaining items

  7. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 4, 2026
  8. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  9. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    event: handed_off
    task: 8561 (repo-health-code-pilot; repo-worker-code; lane code-y)
    at: 2026-11-04T16:50Z
    found_via: atomic queue claim (lane code-y)
    verified: Issue #1978 OPEN, no hard-stop labels; claim labels already synced (queue/claimed, status/in-progress, work/assigned). Execution-lane assignment for task 8561 is opencode — the bound opencode claimant session owns the triage work; this lane does not work it directly. Attempts 22/24 after repeated unlaunched handoffs.
    next: Claimant launches the bound opencode session for task 8561.

  10. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  11. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    queue/queuedHas a queued task in the Supabase agent queue
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 4, 2026
  12. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: fleet monitor
    • Session / ID: task 8561 (repo-fleet-monitor run 2026-11-05T15:45:48Z)
    • Found via: monitor reconciliation (Duty 1 claim/label divergence repair)
    • Task: Claim evaporated: task 8561 is queued and unclaimed in repo-health-code-pilot, but this issue still carried claimed labels. Labels reset to the truth (queue/queued + status/needs-triage). Checkpoint stage at evaporation: handed_off.
    • Expectation: The next lane claim picks task 8561 up from the queued state and posts a fresh pickup note.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/mMedium: expected under 1 day of workenhancementNew feature or requestkind/testTest coverage, test infrastructure, or test-only fixesmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)queue/parkedQueue task is parked (waiting on CI, time, or an event)queue/queuedHas a queued task in the Supabase agent queuesource/creatorFiled by the new-issue-creator jobstatus/needs-triageNot yet triaged; needs a first lookstatus/parkedDeliberately paused with a recorded resume conditiontask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions