Repo: JSONbored/gittensory
Area: src/services/contributor-issue-draft.ts (follow-up to #462)
Type: feature / correctness enhancement · Value: medium-high (maintainer trust, signal-to-noise)
Background
#462 added generateContributorIssueDrafts, which turns repo policy-readiness
warnings, upstream registry drift, and focus-manifest wanted paths into
structured, fingerprinted GitHub issue drafts and (optionally) creates them. The
generator dedupes against issues that already exist via
findDuplicateContributorDraft (marker fingerprint, then normalized title).
By design, that duplicate check only considers open issues — it is the
documented, tested behavior:
// src/services/contributor-issue-draft.ts:156
for (const issue of openIssues) {
if (issue.state !== "open") continue; // closed issues are skipped
if (issue.body?.includes(marker)) return { number: issue.number, ..., reason: "marker" };
}
// test/unit/contributor-issue-draft.test.ts:318
it("ignores closed issues and empty title keys when checking duplicates", () => {
expect(findDuplicateContributorDraft([{ ...openIssue(1, title), state: "closed" }], { fingerprint, title })).toBeNull();
});
And the context only loads open issues (loadContributorIssueDraftContext →
listOpenIssues, which filters state = "open").
Current Behavior
Because the generator has no memory of declined drafts, a maintainer who
closes a generated draft (declines it as wontfix / out-of-scope) sees the same
draft re-proposed — and, in create mode, re-created — on the next
generation run, as long as the originating warning persists. The fingerprint is
stable for policy:* and upstream:registry_drift topics
(candidateKey returns the topic, so
contributorIssueDraftFingerprint(repo, topic, topic) is deterministic across
runs), so the same declined draft reappears indefinitely.
This produces recurring noise on the maintainer's tracker and erodes trust in
the automation — the system keeps re-filing work a human already rejected.
Desired Behavior
The generator should respect a maintainer's decision to close a generated draft:
- A closed issue carrying a contributor-draft marker for a draft's
fingerprint should suppress re-proposal of that draft (status
skipped_declined), rather than being ignored.
- Optionally, support a cooldown (e.g. re-propose only if the warning is
still present N days after closure, or never for an explicit wontfix label)
so genuine regressions can still resurface while routine churn is silenced.
- The dry-run preview and
create path must both honor the suppression so the
preview never advertises a draft that would be skipped.
This intentionally does not change findDuplicateContributorDraft's
open-only semantics (that function answers "is there an open duplicate?"); it
adds a separate, explicit "was this draft declined?" check fed by closed
marked issues.
Implementation Requirements
- Add a loader for closed issues carrying the draft marker (e.g.
listClosedContributorDraftIssues(env, repoFullName) or extend the context to
fetch a bounded set of recently-closed issues), scoped and bounded like the
existing listers (limit, recent-first), to avoid unbounded scans.
- Add a
findDeclinedContributorDraft(closedIssues, draft, { cooldownDays?, wontfixLabels? })
helper that matches by stable marker fingerprint and applies the
cooldown/wontfix policy.
- In
generateContributorIssueDrafts, after the unsafe and open-duplicate
checks, short-circuit to a new skipped_declined status (add it to
ContributorIssueDraftStatus, the result counters, and the audit metadata).
- Keep behavior backward compatible: with no closed marked issues, output is
unchanged.
Public/Private Output Boundaries
- Reuse
isContributorIssueDraftPublicSafe for any new draft body content.
- Do not surface private maintainer closure reasons in public GitHub output;
closure handling is an internal suppression signal only.
- Generated GitHub issues must stay advisory and must not imply guaranteed
participation outcomes.
Acceptance Criteria
- A draft whose stable marker appears on a closed issue is reported as
skipped_declined (with duplicateOf/declinedBy provenance) and is not
re-created in create mode.
- Existing open-duplicate and unsafe behavior is unchanged; the
state: "closed"
case in findDuplicateContributorDraft keeps returning null.
- Cooldown/
wontfix policy is covered: a stale closure past the cooldown with a
still-active warning may re-propose; a wontfix-labeled closure never does.
- Result counters and audit metadata account for the new status.
Testing Requirements
- Unit tests for
findDeclinedContributorDraft (marker match, cooldown boundary,
wontfix label, no-match passthrough).
- Generation tests: closed marked issue →
skipped_declined, no GitHub POST;
closed past cooldown with active warning → re-proposed.
- Run the repository's documented validation command before requesting review.
- Add tests for every new branch, fallback path, and counter, and keep public
GitHub output advisory.
Repo: JSONbored/gittensory
Area:
src/services/contributor-issue-draft.ts(follow-up to #462)Type: feature / correctness enhancement · Value: medium-high (maintainer trust, signal-to-noise)
Background
#462 added
generateContributorIssueDrafts, which turns repo policy-readinesswarnings, upstream registry drift, and focus-manifest wanted paths into
structured, fingerprinted GitHub issue drafts and (optionally) creates them. The
generator dedupes against issues that already exist via
findDuplicateContributorDraft(marker fingerprint, then normalized title).By design, that duplicate check only considers open issues — it is the
documented, tested behavior:
And the context only loads open issues (
loadContributorIssueDraftContext→listOpenIssues, which filtersstate = "open").Current Behavior
Because the generator has no memory of declined drafts, a maintainer who
closes a generated draft (declines it as wontfix / out-of-scope) sees the same
draft re-proposed — and, in
createmode, re-created — on the nextgeneration run, as long as the originating warning persists. The fingerprint is
stable for
policy:*andupstream:registry_drifttopics(
candidateKeyreturns the topic, socontributorIssueDraftFingerprint(repo, topic, topic)is deterministic acrossruns), so the same declined draft reappears indefinitely.
This produces recurring noise on the maintainer's tracker and erodes trust in
the automation — the system keeps re-filing work a human already rejected.
Desired Behavior
The generator should respect a maintainer's decision to close a generated draft:
fingerprint should suppress re-proposal of that draft (status
skipped_declined), rather than being ignored.still present N days after closure, or never for an explicit
wontfixlabel)so genuine regressions can still resurface while routine churn is silenced.
createpath must both honor the suppression so thepreview never advertises a draft that would be skipped.
This intentionally does not change
findDuplicateContributorDraft'sopen-only semantics (that function answers "is there an open duplicate?"); it
adds a separate, explicit "was this draft declined?" check fed by closed
marked issues.
Implementation Requirements
listClosedContributorDraftIssues(env, repoFullName)or extend the context tofetch a bounded set of recently-closed issues), scoped and bounded like the
existing listers (
limit, recent-first), to avoid unbounded scans.findDeclinedContributorDraft(closedIssues, draft, { cooldownDays?, wontfixLabels? })helper that matches by stable marker fingerprint and applies the
cooldown/
wontfixpolicy.generateContributorIssueDrafts, after the unsafe and open-duplicatechecks, short-circuit to a new
skipped_declinedstatus (add it toContributorIssueDraftStatus, the result counters, and the audit metadata).unchanged.
Public/Private Output Boundaries
isContributorIssueDraftPublicSafefor any new draft body content.closure handling is an internal suppression signal only.
participation outcomes.
Acceptance Criteria
skipped_declined(withduplicateOf/declinedByprovenance) and is notre-created in
createmode.state: "closed"case in
findDuplicateContributorDraftkeeps returningnull.wontfixpolicy is covered: a stale closure past the cooldown with astill-active warning may re-propose; a
wontfix-labeled closure never does.Testing Requirements
findDeclinedContributorDraft(marker match, cooldown boundary,wontfixlabel, no-match passthrough).skipped_declined, no GitHub POST;closed past cooldown with active warning → re-proposed.
GitHub output advisory.