(motir-ai) Mirror the PROSPECTIVE LIMB into `VERIFY_EVERY_PRECONDITION` — a counted set, a named test home, and an unchecked NEGATIVE are precondition claims
Repo: motir-ai. One PR. The SHIPPED-planner half of the two-home pair; the manual half is MOTIR-2907 (motir-meta PR #212) plus MOTIR-2928 (motir-meta PR #218), which this card is blocked_by because the mirror LIFTS their wording rather than re-deriving it.
AMENDED 2026-08-17 by
motir run MOTIR-2928, on the record. The limb this card mirrors gained a THIRD claim after this card was authored — THE NEGATIVE — landing in the SAME rule, the SAME pack and the SAME target constant. Amended rather than duplicated: two cards editing one 25 kB template literal is a merge conflict for no gain (plan-rules/core.md's amend-don't-recreate rule). Re-sized 40 → 55 min, 2 → 3 pts for the third clause and its test.
The rule to carry
plan-rules/phase-deepen.md's precondition-verification rule gained a PROSPECTIVE LIMB: steps 1–5 enumerate only what a card CONSUMES, so nothing disciplines what it PREDICTS. A remedy section (a fix direction, an approach, a rollout) makes two forward-looking claims that are nevertheless readable in the repo today, which makes each one a step-2 discharge rather than a prediction — and a third claim that the retrospective/prospective split does not reach at all:
- A COUNTED SET — "the two files that…". A count is a claim about a WHOLE SET, made by a search whose scope is not written beside it. Re-run the enumerating search across every directory the pattern can live in, and RECORD it next to the number.
- A NAMED TEST HOME — "extend
<spec>". Naming a file also claims that lane can put the system into the state the assertion needs. Read the lane's own config — server env, fixtures, feature flags — and confirm the subject can MOUNT there. A subject gated by a process-wide, server-side env read has no per-test override, so the guard passes on the UNFIXED code and stays green forever: a check that cannot go red is not evidence, it is a tautology. - A NEGATIVE (added by MOTIR-2928) — "no spec covers that" · "there is NO existing design asset for this area" · "nothing reserves clearance". A negative sits in the DIAGNOSIS half and inherits its warrant (a diagnosis is produced by reading what is there) — which is exactly the warrant it lacks: a negative is the only claim you can write without looking at anything, and it arrives not as a claim but as the EXPLANATION for the finding, so the reader auditing the card audits the finding instead. Discharge: carry the SEARCH, not the verdict — the command and its output, the way a root cause carries its
file:line— and stamp it with the ref it was run against, because a negative is the only claim class a SIBLING's merge falsifies without touching the card. The tell: a negative is the clause that makes the REST of the card more believable, so it is the sentence to check first, not the context to skim.
Where it goes — the target is a NAMED CONSTANT, not an append
SHARED_PLANNING_RULES is no longer defined in src/llm/treeGeneration.ts (that file only re-exports it). The definition lives in src/llm/planningRulePacks.ts as ~34 named rule constants composed over CORPUS_ORDER.
The target is VERIFY_EVERY_PRECONDITION — tagged phase-deepen in CORPUS_ORDER, i.e. the same pack as both motir-meta edits, so the pair is clean and no re-packing is needed.
The gap is measured, not asserted. Re-probed on origin/main 2026-08-17 by MOTIR-2928's run, over the constant extracted to its first UNESCAPED closing backtick (25 302 chars; a naive scan to a backtick followed by ; terminates inside an escaped code span after 1 615 chars and understates every count — this card's own grep gotcha, hit and corrected in the act):
| probe | count in the constant |
|---|---|
prospect · counted · test home | 0 · 0 · 0 |
negative · existence · search · decay | 0 · 0 · 0 · 0 |
reproduc | 0 |
baseline — grep · nothing · absen · ABSENT · stale | 10 · 13 · 2 · 2 · 2 |
The one NEGATIVE token in the constant is a different rule (a design asset's out-of-scope line is a NEGATIVE RESULT, not coverage) — it is evidence a designer produced, not an unchecked assertion, so it neither closes this gap nor conflicts with it. Say so in the PR body; the next reader will grep the same word.
Hunk 2 of motir-meta PR #212 has no mirror and that is not an omission. It extends type-test.md's scaffold-reachability rule with tell (d); type-test.md has no counterpart constant, and the mirror is a selection as well as a strip. Same for MOTIR-2928's run.md half (reproduce at the reporter's own base commit) — run.md is the manual runbook and has no shipped counterpart at all; only its authoring-side twin belongs here. Say both in the PR body so the next reader does not go hunting.
Register
Condensed all-caps imperative to match the constant's neighbours; GENERIC — no motir-core / motir-ai / MOTIR-<n> / notes.html / plan-rules / Yue / date citations (tests/treeGeneration.test.ts asserts their absence, and the constant carries zero warrant by design). State the lane check abstractly: "a lane whose configuration cannot put the system into the asserted state", not playwright.cloud.config.ts.
Acceptance criteria
src/llm/planningRulePacks.ts'sVERIFY_EVERY_PRECONDITIONcarries the prospective limb — all THREE claims (a counted set, a named test home, an asserted negative), each with its discharge, plus the statement that a check which cannot go red is not evidence and that a negative is the only claim writable without looking at anything. No new constant is exported;CORPUS_ORDERandPACKS_BY_TYPEare unchanged.- The added text contains none of the forbidden tokens —
motir-core,motir-ai,open-core,MOTIR-\d,notes.html,plan-rules, a dotted plan id, a date,Yue,fixture— and the existing guard test intests/treeGeneration.test.tsstill passes on this branch. tests/planningRulePacks.test.tsgains anit(...)pinning the new clause by a distinctive phrase from EACH of the three halves (the counted-set discharge, the lane-reachability discharge, the negative's carry-the-search discharge), asserting on the composedSHARED_PLANNING_RULESso a future re-pack cannot silently drop it.pnpm vitest run tests/planningRulePacks.test.ts tests/treeGeneration.test.tsis green locally and quoted in the PR body — only these two files; the full suite is the PR's CI.- The PR body states which hunks are intentionally unmirrored and why (
type-test.mdtell (d);run.md's run-time half), and quotes the probe table above re-run on the branch so the before/after is legible.
Context refs
src/llm/planningRulePacks.ts—VERIFY_EVERY_PRECONDITION(its doc comment records its provenance, which is the fastest way to confirm you have the right constant) andCORPUS_ORDER.src/llm/treeGeneration.ts— re-export only; not the edit site. A card citing it is stale.tests/planningRulePacks.test.ts,tests/treeGeneration.test.ts— the two suites this touches.- motir-meta
prompts/plan-rules/phase-deepen.md§ THE PROSPECTIVE LIMB — the wording to LIFT (PR #212 for the first two claims, PR #218 for the negative).
Grep gotcha: planningRulePacks.ts is ~501 lines at ~272 chars/line, so grep returns hits, but a naive backtick scan mis-terminates on markdown code spans inside the rule text — walk to the first backtick NOT preceded by a backslash, not to the backtick followed by ;/,/). treeGeneration.ts has lines long enough to trip GNU grep's binary heuristic entirely; search it with python/node, never a plain grep.