Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-4180Done

(motir-marketing) motir.co/docs/mcp/tools hand-copies motir-core's tool registry with NO guard — 24 of 55 tools in five invented groups, the one outcome MOTIR-4046's own criterion named unacceptable

Type · implementation defect · motir-marketing · ONE PR (see Fix direction — the producer half, if chosen, is a separate motir-core card)

Parent · MOTIR-3932 — the finding story, and the edge test says YES on its equivalently limb: this story's own acceptance criterion is the one being falsified ("whatever is chosen carries a guard that fails when this repository's copy drifts from motir-core's registries. A copied spec with no guard is not an acceptable outcome."). The story is todo, not done-category, so it qualifies under log-bug.md step 3. Containment therefore states something true: MOTIR-3932 genuinely is not finished while this stands.

Discovered in · motir run MOTIR-4046, 2026-09-02. That card was already done and merged (f3fd965, PR #25); the run stopped rather than rebuild it, and verifying its acceptance criteria against origin/main is what surfaced this.


Root cause / fix

Verified against motir-marketing origin/main @ ba0263b and motir-core origin/main.

1. MOTIR-4046 named TWO registries and guarded ONE

The card's scope item 3 and its acceptance criterion both name the two sources /docs is generated from:

"lib/apiDocs/reference reads the OpenAPI spec file, lib/apiDocs/mcp reads the tool catalogue … Whatever mechanism is chosen carries a guard that fails when this repo's copy drifts from motir-core's registries — a copied spec with no guard is not an acceptable outcome."

The OpenAPI half was done exactly right. lib/docs.ts:fetchOpenApiSpec fetches ${APP_ORIGIN}/api/openapi/v1.json fresh at request time (app/docs/api/page.tsx, export const dynamic = 'force-dynamic'), so there is no copy to drift; tests/docs/docs.test.ts asserts the fetch URL is the published endpoint and that no spec artifact is committed under content/docs/.

The MCP-catalogue half is the shape the criterion forbade. app/docs/mcp/tools/page.tsx:11-51 is a hard-coded const GROUPS array. Its own header says so:

* The MCP tool catalogue (MOTIR-4046) — committed, reproduced from motir-core's published docs.

2. Nothing in this repository checks it — measured, not assumed

git grep -l -E "mark_integrated|dispatch_prompt|tools/list" origin/main -- tests lib app returns exactly one path: app/docs/mcp/tools/page.tsx itself. tests/docs/docs.test.ts is the only docs guard and both of its drift limbs read lib/docs.ts — neither ever loads the tools page. So the enumeration has no reader and no checker.

3. It has ALREADY diverged structurally — the grouping is invented

This is not a latent risk; the copy is wrong today in a way a guard would have caught at authoring time.

motir-core lib/apiDocs/mcp.tsmotir-marketing app/docs/mcp/tools/page.tsx
tools55 (TOOL_SUMMARIES keys = TOOL_PERMISSIONS keys = 55, held equal by a typecheck totality chain)24
groupingDERIVED — a tool's group IS its TOOL_PERMISSIONS permission; labels derived from permissions.* copy since MOTIR-2581; only the ORDER is authoredfive authored stringsWork items · Planning · Dispatch & runs · Sprints · Lessons & designs — corresponding to no permission
truth gatea fingerprint per summary, recomputed from a live tools/list (now itself broken — MOTIR-4165)none

Where it is NOT yet wrong, stated so the card is not read as claiming more than it proves: all 24 names still exist. Diffing the page's list against TOOL_PERMISSIONS' 55 keys (comm -23) returns empty, and the 55 keys match this session's live MCP palette exactly. The defect is the absent guard, not an observed stale name — 31 tools are simply unnamed, with no stated selection rule beyond "the tools you will reach for most", and nothing would report the first rename or retirement.

4. Why the guard's absence is load-bearing rather than tidy

motir-core's catalogue cannot go stale by construction: a tool added to MCP_TOOL_NAMES without a permission fails typecheck in toolPermissions.ts, which widens keyof typeof TOOL_PERMISSIONS, which makes TOOL_SUMMARIES incomplete, which fails typecheck in mcp.ts"a tool cannot reach the server undocumented." Copying a subset of that registry into a second repository is precisely the act that breaks the chain, and it is why MOTIR-4046 called the mechanism a decision rather than a detail. Since MOTIR-3951 deleted app/(public) from motir-core, motir.co/docs/mcp/tools is the only rendered tool catalogue Motir publishes — so the unguarded copy is not a mirror of a guarded original, it is the original.

Reproduce: git -C <marketing> show origin/main:app/docs/mcp/tools/page.tsx (the literal array), then git -C <marketing> grep -l 'tools/list' origin/main -- tests (empty), then rename any tool in motir-core's TOOL_PERMISSIONS and run pnpm test in motir-marketing — green.

Fix direction

Recommended — GENERATE it, symmetric with the half that was done right. motir-core publishes the catalogue at a public, anonymous, versioned endpoint the way it already publishes /api/openapi/v1.json; this page renders it through a lib/docs.ts reader exactly as /docs/api does, and the copy ceases to exist. That is TWO cards in TWO repositories (ONE SUBTASK = ONE REPO = ONE PR): a motir-core producer, and this card as the motir-marketing consumer blocked_by it. ⚠️ The producer card does not exist yet — it is not filed here because a run creates no work item except a bug, and because MOTIR-4165's fix-direction item 4 already names the prior question ("if the catalogue has no reader left in motir-core it may belong in motir-marketing, and then the gate goes with it … a decision for the epic that moved the page") as MOTIR-3875's. Answer that question before scheduling this card, and note this bug supplies the fact it was missing: the reader DOES still exist, it is here, and it is unguarded.

Fallback, single-repo and fully sufficient for the criterion — DELETE the enumeration. The page already tells the reader the authoritative list is the live tools/list; drop the 24 names and the invented groups, keep the prose and the link. A copy that does not exist cannot drift. Cheaper and honest, but it removes the only rendered catalogue Motir has, so it is second choice.

Rejected — pin the copy against a committed fixture in this repository. That guards the copy against a second copy and proves the parse, not the agreement — the exact failure MOTIR-4139 was filed and fixed for on the subprocessor seam one repository over. Do not repeat it here.

Acceptance criteria

(Ordering: every criterion is true at PR-open in motir-marketing; none reads main, a merge or a deploy. Repo: motir-marketing alone — if the recommended shape is taken, the motir-core producer is its own card and this one is blocked_by it.)

  1. /docs/mcp/tools no longer carries an unguarded copy of motir-core's tool registry — the enumeration is either GENERATED from a published artifact, or removed in favour of prose plus the live-surface link. The disposition is recorded in the PR body, naming which of the two shapes was taken and why.
  2. If any enumerated form survives, a guard under tests/docs/ fails when a name the page renders is absent from the catalogue motir-core actually serves, naming the tool — proved to FIRE by perturbing one name inside the test itself, never proved to pass.
  3. That guard's source of truth is motir-core's published surface, never a second committed copy in this repository; when the surface is unreachable it FAILS rather than skips.
  4. The invented five-group structure is gone: either the derived permission grouping arrives with the generated data, or no grouping is asserted at all.
  5. tests/docs/docs.test.ts's existing OpenAPI limb is untouched and still passes — this widens MOTIR-4046's drift guard, it does not replace it.
  6. lib/docs.ts's header no longer implies the drift guard covers both registries unless it does; the comment in app/docs/mcp/tools/page.tsx describing the page as "committed, reproduced from motir-core's published docs" is true of what ships or is deleted with the copy.
  7. pnpm lint · format:check · typecheck · build · test pass; AA contrast holds in both themes for anything the page's structure changes.

Advisory dispositions

validate_work_item at filing time: valid: true, blockers: [], ONE advisory. Disposed of here, at CREATE time, per run.md guard #4's create-time limb.

  • referenceMOTIR-4165 (plain advisory, referenced status todo): NOT consumed — no blocked_by is owed, and wiring one would break gate 7. Two independent reasons, and both have to hold for the answer to be no edge:

    1. It is not a precondition. MOTIR-4165 restores motir-core's OWN fingerprint gate over lib/apiDocs/mcp.ts and re-pins add_plan_items. This card reads neither that gate nor those pins; every one of its criteria is dischargeable with MOTIR-4165 untouched. The reference is provenance and context — how the sibling guard came off in the same week, and which card already flagged the catalogue's HOME as an open question — which is exactly what relates_to carries and what this card has.
    2. It is in another container. MOTIR-4165 hangs under epic MOTIR-2200; this card hangs under story MOTIR-3932, under epic MOTIR-3875. A leaf-to-leaf blocked_by across parents is what gate 7 forbids, so following the wire the edge instruction literally here would satisfy one rule by breaking another.

    What IS owed and is not an edge: the answer to MOTIR-4165's fix-direction item 4 — does the catalogue still belong in motir-core? — is a prior question for MOTIR-3875, and this card must not be scheduled ahead of it (said in Fix direction above, and repeated here because this is the section a dispatch guard reads).

Resolution

(open — filled by the subtask that closes this)