Repo: motir-ai. One PR. The SHIPPED half of a rule that currently lives only in the manual planner.
A standing planning RULE has two homes, and landing only one teaches the manual planner while the product keeps planning the old way:
motir-meta/prompts/plan-rules.md — the MANUAL planner.SHARED_PLANNING_RULES in src/llm/treeGeneration.ts — the SHIPPED planner. Its own comment calls it "the encoding of plan-rules.md", and it is composed by both fresh tree generation and re-planning (src/jobs/handlers/replan.ts imports it).Precedent, twice: MOTIR-1773 → MOTIR-1774, and MOTIR-2083 → MOTIR-2089 (merged today, PR #186 — the template for this card).
The corollary this mirrors ships in motir-meta PR #100 (docs/design-allocation-emits-consumer-edges), which is not yet merged. There is no work item to wire a blocked_by to — the motir-meta half was authored as guard #4's mandated corpus record during motir run MOTIR-2080, not as its own card. Before running this card, confirm PR #100 is merged and lift the wording from prompts/plan-rules.md on origin/main — if it is still open, the source text may still change. Do not transcribe from this description; it is a summary, not the source.
It is a corollary to the existing UI-composition rule already in SHARED_PLANNING_RULES (the host surface a UI card renders inside is a blocked_by, not a relates_to) — mirror it as such, adjacent to that rule, not as a free-standing block:
A design that allocates ONE surface across N consuming cards emits N−1 edges BETWEEN those consumers, and the design's own blocks fan-out is not them. Wiring design blocks A + design blocks B asserts only that both need the drawing; the graph then reads A and B as parallel and releases both the moment the design merges, while the design's own allocation table says one renders INSIDE the other's output. So read a design's allocation as a source of EDGES, not just of scope: for each row, name the element and the card that builds it, and wherever one row's element renders inside, beneath, or gated by another row's, wire a blocked_by between those two consumers — in the SAME pass that creates them.
Two operative tells that must survive condensation:
MOTIR-<n> — so an unlinked prose reference yields no blocker and no advisory, and validation returns a clean valid: true.SHARED_PLANNING_RULES carries the corollary in the constant's existing voice and placement convention — adjacent to the UI-composition / host-surface rule it extends, not appended as a foreign block.MOTIR-<n> key in the rule text (the same constraint MOTIR-2089 tested for).origin/main so the two homes do not drift; the PR body notes which lines correspond.replan.ts imports the same constant, and a rule that reaches only one path is half-landed.MOTIR-<id> (motir-ai is a code repo — the status-sync rule).treeGeneration.tsgrep silently finds nothing in this file — it has very long lines. Search with python or node instead, or you will conclude the constant does not exist.
This card mirrors the RULE only. It does not touch src/seed/lessons.base.ts: that corpus is the LESSON tier and is batch-seeded, currently citing up to notes.html #158 against a corpus at #213 — bringing it current is its own card, not this one.
src/llm/treeGeneration.ts — SHARED_PLANNING_RULES, the constant edited.src/jobs/handlers/replan.ts — the second consumer; the reason the test must cover both paths.tests/treeGeneration.test.ts — the existing rule-presence tests to extend (MOTIR-2089 added 11 there).prompts/plan-rules.md (the corollary) and notes.html #213 (the incident it was drawn from).motir-meta/prompts/_shared.md — the plan-rules.md ⟷ SHARED_PLANNING_RULES pairing this card honours.