(motir-core) A `modify` proposal's body cannot carry an intra-plan CHIP — materialize rewrites `motir-ref:planItem:` on `add`s only, so an amendment naming a card the same plan proposes ships an inert link
Opened by Zhu Yue ·
Repo · motir-core. Found on 2026-08-28 while authoring the to-do-list plan (cmtdaoakn01fehvphc5q6s0dh), which modifys MOTIR-1344 to name a story the SAME plan proposes.
The defect
plansService.materialize's Pass 3 rewrites intra-plan item-link tokens — [label](motir-ref:planItem:<id>) → [label](motir:<workItemId>) — inside
for (const { created, prefix } of createdAdds) { … rewriteIntraPlanRefs(created.descriptionMd …) … }
createdAdds holds add proposals only. A modify proposal's patch.descriptionMd / patch.explanationMd are applied to their target and never passed through rewriteIntraPlanRefs — verified at origin/main 268bb2a5b: git grep -n 'rewriteIntraPlanRefs' -- lib/services/plansService.ts returns exactly two call sites (lines 1325 and 1333), both inside that loop.
So a token in a modify body materializes verbatim, as a literal motir-ref:planItem:<id> href pointing at nothing. It does not chip, it does not resolve, and — unlike an add's dangling ref — it does not even reach the console.warn that reports one, because the rewrite never runs on it.
Why this is a PLANNING defect and not a niche one
It is the shape a re-plan takes by default. plan-procedure.md step 4 tells an author to "write every reference to another work item as an ITEM LINK, never a pure-text key", and to use motir-ref:planItem:<tempRef> for "an intra-plan sibling the generator is still proposing" — while THE REPLAN ACTION's own three ops are add the new, remove the superseded, and modify the survivor. Amending a survivor to name the card that took over part of its scope is the single most common thing a re-plan writes, and it is exactly the case the rewrite cannot reach.
The failure is silent in the worst direction. An author following the rule produces a broken link; an author who notices produces a prose reference and quietly violates the rule. This pass took the second road: MOTIR-1344's 2026-08-28 amendment names the new to-do-list story descriptively ("the sibling story this card is blocked_by") rather than as a chip, and says so in the body. That works and it is worse than a chip.
Fix direction
Run the same rewrite over a modify's patched bodies. The information is all present at Pass 3 — planItemToWorkItem is already built and already covers every add in the plan — so this is a second, small loop over the modifies whose patch carried a body, applying rewriteIntraPlanRefs to descriptionMd and explanationMd and surfacing an unresolved ref through the same console.warn. Consider whether autoRelateWorkItemMentions should run on a modified body too, or whether that would re-wire edges on a card the plan only meant to amend — decide it, do not leave it implicit.
Acceptance criteria
- A
modifyproposal whosepatch.descriptionMdcarries[label](motir-ref:planItem:<id>), where<id>is anaddon the same plan, materializes with that token rewritten to[label](motir:<the created work item's id>)— asserted end to end throughapprovePlan, not on the helper. - The same holds for
patch.explanationMd. - An unresolvable
planItem:ref in amodifybody is left inert and reported through the sameconsole.warnpath anadd's dangling ref uses — asserted, so the two ops fail the same way. - A
modifybody containing no such token is byte-identical after materialize (no incidental rewriting). - Whether
autoRelateWorkItemMentionsruns over a rewrittenmodifybody is decided and asserted — either it does and therelates_toedges appear, or it does not and a test says so. Not left to inference. git grep -n 'rewriteIntraPlanRefs' -- lib/services/plansService.tsshows the call sites covering both ops, andplansService's coverage stays at or above the per-file floor.
Context refs
lib/services/plansService.ts~1315–1360 — Pass 3, thecreatedAddsloop, and theconsole.warnfor a dangling ref.lib/mentions/workItemRefs.ts—INTRA_PLAN_REF_TOKEN_REandrewriteIntraPlanRefs; the token form is[label](motir-ref:planItem:<planItemId>).lib/dto/plans.ts:130— the contract line promisingmotir:<id>/motir-ref:<tempRef>are "resolved at materialize", which is true of one op and not the other.- MOTIR-1344 — the card whose 2026-08-28 amendment had to fall back to a prose reference because of this.
- MOTIR-3111 — the sibling defect in the same family:
PlanItemPatchomittedexplanationMdentirely, which made the FIELD unsettable on amodify. This one makes that field's CONTENT unresolvable. Same op, same blind spot, one layer apart.
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.