feat(pm): flag decision-box cards with open downstream dependents - #18015
Conversation
Standing triage instruction ④ (2026-08-11 maintainer ruling) asks every round-closing brief to flag the `needs-user-decision` cards some open card's `Blocked-by:` line points at. That was measured unrunnable on the triage seat's own tool surface: MCP `search_issues` is semantic matching, so a literal `Blocked-by:` needle returns 0 against a board where an open card's first body line is exactly that string. The half-state patrol already builds that reverse index for H14, over both channels (body, plus comments for `pm:blocked` / `pm:blocking` cards whose body is clean). So ④ is one lookup, not a new channel: this adds a rendered inventory section beside the H17 trigger-file index — never a finding, never a label — carrying the flagged cards and their waiters, a count pair, the H4 count as a declared NOT-MEASURED population, and the section's own population scope, so a brief can cite both numbers or write NOT MEASURED honestly. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Contract reviewHead: ① derived judgments — the patrol gains one report-only inventory section; nothing it reads or writes changes:
Seat measurements on the head tree ( ② semver: ③ boundary flags: Implemented-by: Verdict: PASS — route A as claimed (5652746378): instruction ④ becomes one lookup over the index the patrol already has, both of its numbers rendered, its scope declared; no channel, no charter text, no gate added. Generated by Claude Code |
|
Landing step (NOT GOVERNED, measured: Generated by Claude Code |
Fixes #17968
Standing triage instruction ④ (2026-08-11 maintainer ruling) asks every round-closing brief to flag the decision cards somebody is waiting on. Verbatim, and quoted verbatim in the code:
The card measured that unrunnable on the triage seat's own tool surface: MCP
search_issuesis semantic matching, so a literalBlocked-by:needle returns 0 against a board where an open card's first body line is exactly that string. It offers routes A / B / C and picks none.This is route A over a reader the repo already has — ⛔ no new channel, no charter text, no gate on the line, no objectui edit.
scripts/pm/check-half-states.mjsalready builds theBlocked-by:reverse index for H14 across BOTH channels (every open card's body; comments forpm:blocked/pm:blockingcards whose body is clean), so ④ is oneMap.getper decision card and costs the patrol no request.What lands
needs-user-decisioncard that an open card'sBlocked-by:line names, listing its waiters (capped by the existingBLOCKING_DEPENDENT_LIST_CAP, overflow counted).N of M, so a quiet inbox reads differently from an empty one), a declared NOT-MEASURED population equal to H4's own count of openpm:blockedcards with no machine-readable line in either channel (the loss objectui#6653 measured at 17 of 24) — counted off the H4 rows the sweep emitted, one computation with two readers — and the section's own population scope, so a brief can cite both numbers or write NOT MEASURED honestly.list_issues只能按 label / state / since 过滤,不返回正文检索」 — is true of that MCP tool and FALSE of this script's REST reader. Stated once in the docblock so the next reader does not re-file the card.Acceptance
Decision-box dependency flagsincheck-half-states.mjs:git grep -c0 → 1. Lit controlOn-hold trigger-file index (H17)still hits, 4 → 4.c45ea8c3vs HEAD, identical on both sides:h4BlockedNoBlockedBy1d13e91922b8,h14BlockingCacheIncoherent7fbcf7210da9,buildBlockingIndex9a9e68b027de,blockedByTargetseb04b7cd2dc6,needsBlockedByCommentse0aad3851d31,renderTriggerIndex6848a4809bc0.node scripts/pm/check-half-states.mjs --self-testexits 0.④ loss:cases red, exit 1 — the predicted direction. Restored fromHEAD; blob back tof48453c67d6c,git diff HEADempty, self-test green again at 3806.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 41 commands at8ff5d429; all 41 exit 0, among themcheck:nul-bytes,check:refd-timer-probe,check:whole-set-label-write,check:pm-half-states,check:self-test-wired,check:commit-card-trailers,check:pm-dispatch-gates. Reconciled:--ranwith a record carrying every exit code returns 「41 derived, 41 run, 0 NOT-MEASURED, 0 UNRUN」, exit 0. The 47 artifact-roster, 11 wide-population, 10 pending-changeset and 1 path-scheduled families dispatch-gates names as outside that total are left to CI.check:whole-set-label-writeis green.scripts/pm/**publishes nothing ⇒skip-changesetlabel, no changeset file.Acceptance notes
pnpm check:pm-dispatch-gatesneeds roughly 25 minutes of wall clock in this shared container — past a 300s and a 540s foreground cap, though it exits 0 with 1682 cases passing. The box was running parallel agents, so that is a cost reading under contention rather than a reproducible defect; every dev agent whose diff touchesscripts/**derives this family.pm:blocked∪pm:blockingpopulation. Measured on the tree, the BODY channel is wider — the unscoped open-issue listing, i.e. every open card — and only the COMMENT channel is label-gated. The section's scope line states the real shape rather than the narrower premise.Generated by Claude Code
Generated by Claude Code