packages/gittensory-miner/lib/rejection-templates.js (#2324) exists and is fully implemented — renderRejectionMessage (rejection-templates.js:57-71) deterministically renders one of three courtesy notes (REJECTION_REASONS: gate_close, maintainer_close_no_reason, superseded_by_duplicate, rejection-templates.js:9-19) for a closed PR. But it has zero callers anywhere outside its own test (test/unit/miner-rejection-templates.test.ts) — nothing in the codebase currently detects a rejection, classifies which of the three buckets applies, or invokes the renderer. This issue is that missing state machine, not the templates themselves.
Two concrete gaps to close along the way:
- Detecting the rejection at all.
ci-poller.js's fetchHeadSha (ci-poller.js:155-163) already fetches the full GET /pulls/{number} payload but discards everything except head.sha — the PR's state/merged/merged_at/closed_at fields (exactly what's needed to detect "closed without merge") are right there in the response and currently thrown away. manage-poll.js's outcome field (mapPollConclusionToOutcome, manage-poll.js:33-42) is derived purely from CI conclusion (ready/needs-work/open) and has no "closed"/"rejected" value at all today.
- The target state "disengaged" doesn't exist yet.
run-state.js's RUN_STATES is ["idle", "discovering", "planning", "preparing"] (run-state.js:6) — a per-repo lifecycle state, with no "disengaged" member. A rejected PR is a per-PR outcome, not a per-repo run state, so "closed/rejected → disengaged" most likely belongs as a new value in the per-PR outcome vocabulary manage-poll.js already writes (alongside ready/needs-work/open), not as a fifth RUN_STATES entry — but this is a real design decision the implementer needs to make and document, not assume.
Deliverables
References
packages/gittensory-miner/lib/rejection-templates.js:1-20,57-71 (existing templates + renderer, zero current callers)
packages/gittensory-miner/lib/ci-poller.js:155-163 (fetchHeadSha — fetches the full PR payload but discards state/merged)
packages/gittensory-miner/lib/manage-poll.js:33-42 (mapPollConclusionToOutcome — current outcome vocabulary with no closed/rejected value)
packages/gittensory-miner/lib/run-state.js:6 (RUN_STATES — confirms "disengaged" is not a current per-repo state)
test/unit/miner-rejection-templates.test.ts (confirms the templates' only current caller is their own test)
packages/gittensory-miner/lib/rejection-templates.js(#2324) exists and is fully implemented —renderRejectionMessage(rejection-templates.js:57-71) deterministically renders one of three courtesy notes (REJECTION_REASONS:gate_close,maintainer_close_no_reason,superseded_by_duplicate, rejection-templates.js:9-19) for a closed PR. But it has zero callers anywhere outside its own test (test/unit/miner-rejection-templates.test.ts) — nothing in the codebase currently detects a rejection, classifies which of the three buckets applies, or invokes the renderer. This issue is that missing state machine, not the templates themselves.Two concrete gaps to close along the way:
ci-poller.js'sfetchHeadSha(ci-poller.js:155-163) already fetches the fullGET /pulls/{number}payload but discards everything excepthead.sha— the PR'sstate/merged/merged_at/closed_atfields (exactly what's needed to detect "closed without merge") are right there in the response and currently thrown away.manage-poll.js'soutcomefield (mapPollConclusionToOutcome, manage-poll.js:33-42) is derived purely from CI conclusion (ready/needs-work/open) and has no "closed"/"rejected" value at all today.run-state.js'sRUN_STATESis["idle", "discovering", "planning", "preparing"](run-state.js:6) — a per-repo lifecycle state, with no "disengaged" member. A rejected PR is a per-PR outcome, not a per-repo run state, so "closed/rejected → disengaged" most likely belongs as a new value in the per-PRoutcomevocabularymanage-poll.jsalready writes (alongsideready/needs-work/open), not as a fifthRUN_STATESentry — but this is a real design decision the implementer needs to make and document, not assume.Deliverables
state/merged/merged_at(either widenci-poller.js'sfetchHeadShareturn shape, or add a sibling fetch) so a poller can detect "closed without merge" without a second unrelated API callREJECTION_REASONS— decide and document whether "no signal available" defaults tomaintainer_close_no_reasonor is left unclassifiedmanage-poll.jsoutcomevalue or a newrun-state.jsstate; implement whichever is chosen without silently expanding the other module's enum as a side effectrenderRejectionMessage(rejection-templates.js:57) so it finally has a real caller, and persist the rendered note + classification via the local event ledger (reusing thepr-outcome.jswriter from the sibling issue if it lands first)References
packages/gittensory-miner/lib/rejection-templates.js:1-20,57-71(existing templates + renderer, zero current callers)packages/gittensory-miner/lib/ci-poller.js:155-163(fetchHeadSha— fetches the full PR payload but discardsstate/merged)packages/gittensory-miner/lib/manage-poll.js:33-42(mapPollConclusionToOutcome— current outcome vocabulary with no closed/rejected value)packages/gittensory-miner/lib/run-state.js:6(RUN_STATES— confirms "disengaged" is not a current per-repo state)test/unit/miner-rejection-templates.test.ts(confirms the templates' only current caller is their own test)