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-3296Done

The runbook lesson corpus reaches the store — every un-accounted notes.html entry distilled, categorized, and delivered by data migration

⚠️ MEASUREMENT CORRECTION (applied by the run of this story, 2026-08-21). Three figures below were wrong WHEN WRITTEN, and are superseded by this block rather than edited out of the prose, so the diff shows an amendment and not a silent drop.

this card saysactualhow it was checked
lessons.base.ts holds 70 rows69counted at f682d19 and at this branch's base; git log f682d19..HEAD -- src/seed/lessons.base.ts is empty, so the file is byte-identical across the window and the count was wrong when written, not drift
those rows account for 112 distinct entries114{1..111} ∪ {156,157,158}112 seeded + 2 excluded
notes.html head is 355, un-accounted 241356 and 242re-measured at motir-meta origin/main 2d3c93e; the corpus is contiguous 1..356

No range and no disposition changes. #112–#155 is 44 entries exactly as stated, and the two backfill ranges are unaffected. Evidence and the per-range disposition counts are in motir-ai#274; the same finding is added to MOTIR-3363, which already owns this measurement family.

notes.html holds 355 entries and is written to most days. src/seed/lessons.base.ts holds 70 rows, was last written 2026-07-26, and accounts for #1–#111 plus #156, #157, #158 — 112 distinct entries, two of which (#1, #19) are EXCLUDED_ENTRIES carrying a reason. The runtime captureMistake path only ingests the shipped planner's own mistakes, so the one bridge for runbook-captured lessons is a seed script somebody runs by hand.

The gap, re-measured

On origin/mainmotir-ai @ f682d19, motir-meta origin/main — on 2026-08-21:

notes.html entries355, numbered contiguously 1..355, no gaps, no duplicates
lessons.base.ts rows70
entries those rows account for#1–#111{#156, #157, #158}112
un-accounted#112–#155 (44) and #159–#355 (197)241 entries

⚠️ This story was first authored on the premise that the seed "stops at #158" — which reads as everything below it is covered. It is not. #112–#155 has been in neither the seeded set nor the excluded set since the day those entries were written. A backfill scoped from #159 upward would satisfy this story's own "every source entry has a disposition" gate while 44 entries received none — and then MOTIR-3320 would delete the hand-run seed, removing the last bridge they had. A gap that was merely unfilled would become one that is closed off, with the store's row count reading exactly as it should.

And the reason the drift was silent is a hole in the guard, so closing it is part of this story. tests/lessonsBaseSeed.test.ts:83 asserts totality exhaustively for n <= 111 only, then checks a hardcoded for (const n of [156, 157, 158]). Writing a new notes.html entry therefore creates no obligation anywhere: the corpus can grow indefinitely and CI stays green. That is what happened between 2026-07-26 and today, and backfilling without closing it only resets a clock that will run down again. The fix rides on MOTIR-3306, which already owns that file.

Delivery is a DATA MIGRATION, not a hand-run seed. fly.toml's release_command is pnpm prisma migrate deploy, so a migration reaches production on release with no operator — and, more importantly, so does every lesson added after this story. A generator emits the migration from lessons.base.ts, which stays the authored source.

The half that is easy to miss: ranking is cosine distance against embedding, and setEmbedding is called only from the create and edit paths. A row inserted by SQL is present, healthy and permanently unrankable. The story therefore owes an embedding sweep as well as the migration.

This is a distillation, not an import — the corpus-distillation gate applies in full: generalize past the incident, rewrite in the product's voice, dedup against the 70 rows already there, and give every one of the 241 source entries an explicit disposition. Dropping one silently is the failure that gate exists to prevent, and it is the failure this story's own first draft was about to commit at scale.

⚠️ SUPERSEDES MOTIR-1948, which this re-plan retires. That card — filed 2026-07-31 under MOTIR-2200, still todo and ready — owned exactly this backfill and the totality-guard fix, and had been kept current by five of Yue's comments routing individual entries into it. It predates the migration delivery path and the routing axes, and its own follow-up plan was a manual prod seed run; this story is strictly the better shape. Its comments do not die with it — the per-entry routing decisions recorded there (#180, #196, #224 among them, each with a suggested generic register and a dedup note) are inputs to the distillation cards and are cited on each of them.

⚠️ NOT BLOCKED on the retirement loop, and this is a considered exclusion rather than an oversight. Every row this story adds is global scope, and MOTIR-3323 establishes that a global lesson's lastOccurredAt can never be bumped — so these rows fall out of every query 90 days after they are seeded, with no path back. That is a real dependency in fact. It is deliberately not wired as an edge because the backfill does not need the fix to BUILD, blocking on the whole of MOTIR-3322 would also serialize this behind work it has no relationship to, and 3323 is a highest-priority three-pointer with ninety days of runway. If 3323 slips past that window, this story's output is gone and the exclusion was wrong — so the two are linked, and whoever schedules this one should check that one first.

Acceptance criteria

  • Every notes.html entry in #112–#155 and in #159–the corpus head has an explicit disposition recorded in a PR: a new row, a merge into an existing row, or a drop with a stated reason. The disposition count equals the source count, and each distillation card states the head it measured and the ref it measured on.
  • Each new row carries its kind/type/phase axes, title + howToApply written generically, and its sourceRef.
  • No row is a verbatim paste of a notes.html entry.
  • The seed's totality assertion no longer restates a hardcoded list of entry numbers: adding an entry to notes.html without accounting for it fails a test, and the failure message says what to do.
  • The rows reach a deployed database by prisma migrate deploy on a normal release — no card in this story is manual.
  • The migration is forward-only and idempotent on sourceRef; a re-run inserts nothing and removes nothing.
  • A migration-inserted row is proved rankable: not returned by listForInjection before the embedding sweep, returned after it.

Context refs

  • motir-ai src/seed/lessons.base.ts — the 70 existing rows, EXCLUDED_ENTRIES, and their shape.
  • motir-ai tests/lessonsBaseSeed.test.ts:83 — the totality assertion, exhaustive to n <= 111 and hardcoded thereafter; the hole that made the drift silent.
  • motir-ai fly.toml release_command, .github/workflows/ci.yml:107 — the automatic prisma migrate deploy.
  • motir-ai src/repositories/lessonRepository.tssetEmbedding and the cosine ranking that makes the sweep necessary.
  • motir-meta notes.html — the source corpus, at #355 on 2026-08-21.
  • MOTIR-1948 — superseded by this story; its five comments carry Yue's per-entry routing decisions and remain authoritative.
  • MOTIR-3323 — the global-expiry defect these rows are exposed to.