Skip to content

feat(settings): add contributorOpenPrCap/contributorOpenIssueCap config (#2270)#2467

Merged
JSONbored merged 2 commits into
mainfrom
feat/contributor-cap-settings
Jul 2, 2026
Merged

feat(settings): add contributorOpenPrCap/contributorOpenIssueCap config (#2270)#2467
JSONbored merged 2 commits into
mainfrom
feat/contributor-cap-settings

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Scope

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally — 100% line/branch on every changed line (verified via the v8 report's uncovered-line list against the diff).
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries — DB round-trip (insert/update/clear/defaults) in test/unit/data-spine.test.ts, manifest parse + precedence + invalid-value rejection in test/unit/focus-manifest.test.ts.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. — N/A, no auth/session/CORS surface touched.
  • API/OpenAPI/MCP behavior is updated and tested where needed — RepositorySettings OpenAPI schema regenerated (npm run ui:openapi).
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks. — N/A, no UI change.
  • Visible UI changes include a UI Evidence section. — N/A, no visible UI change (config/type/schema only).
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs — .gittensory.yml.example updated; CHANGELOG.md untouched.

Notes

…ig (#2270)

Adds the config-as-code contract for per-contributor open PR/issue caps: DB
migration, RepositorySettings type, .gittensory.yml settings: parsing with
precedence over the DB, OpenAPI response fields, and docs. No enforcement
behavior yet (a repo with no cap configured sees zero change) — auto-close
over the cap lands in a follow-up PR.
@gittensory-orb

gittensory-orb Bot commented Jul 2, 2026

Copy link
Copy Markdown

Warning

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-02 05:35:21 UTC

10 files · 1 AI reviewer · no blockers · readiness 68/100 · CI pending · blocked

⏸️ Suggested Action - Manual Review

Review summary
The diff adds the contributor open PR/issue cap contract end to end: typed settings, manifest parsing with explicit-null clearing semantics, DB schema/migration wiring, repository normalization, OpenAPI output, docs, and focused tests. The implementation preserves the stated inert/default behavior by normalizing absent or invalid caps to null and only applying YAML overrides when the key is present. The most important correctness detail, distinguishing omitted YAML keys from explicit null, is handled and covered by regression tests.

Nits — 6 non-blocking
  • nit: migrations/0089_contributor_open_caps.sql:4 uses bare `ALTER TABLE ... ADD COLUMN`, which matches SQLite practice but should be double-checked against the repo's migration numbering discipline because reusing or renumbering this file would make the migration non-idempotent on replay.
  • nit: src/db/repositories.ts:5596 documents invalid caps as "dropped to null", but callers that use partial updates should confirm that omitted cap fields intentionally clear rather than preserve existing DB values, matching the surrounding repository settings semantics.
  • nit: apps/gittensory-ui/public/openapi.json:8589 exposes the generated schema correctly, but this PR relies on generated artifact freshness; keep `ui:openapi:check` as the source of truth for this file.
  • src/signals/focus-manifest.ts:794 could reduce the duplicated PR/issue parsing block with a tiny helper that preserves the explicit-null-vs-omitted distinction, since that distinction is the core contract here.
  • test/unit/data-spine.test.ts:302 should keep the insert/update/clear cases together as the canonical regression coverage for DB round-trip behavior when enforcement lands.
  • Readiness score is below the configured threshold — Use the readiness panel as advisory maintainer context; the score does not block this PR.
Signal Result Evidence
Code review ✅ No blockers 1 reviewer
Linked issue ⚠️ Missing No linked issue or no-issue rationale found.
Related work ⚠️ 3 scoped overlaps Top overlaps are listed below; lower-confidence bulk is hidden.
Change scope ❌ 8/20 High review scope from cached public metadata (size label size:M; no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 65 registered-repo PR(s), 55 merged, 554 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 65 PR(s), 554 issue(s).
Gate result ✅ Passing No configured blocker found.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository registration is not available in the local Gittensory cache.
  • Public profile languages: not available
  • Official Gittensor activity: 65 PR(s), 554 issue(s).
  • Related work: Titles/paths share 9 meaningful terms. (issue #2270, PR #2479)
  • Related work: Titles/paths share 6 meaningful terms. (issue #2040, issue #2052)
  • Related work: Titles/paths share 6 meaningful terms. (issue #2039, issue #2052)
  • Additional title-only matches omitted; title-only overlap does not block.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Explain no-issue PR.
  • Review top overlaps.
  • Add a concise scope and risk note.
  • Triage stale or unlinked PRs.
  • No action.
  • Link the issue being solved, or explicitly explain why this is a no-issue PR.
  • Check active issues and PRs before submitting.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.

  • Re-run Gittensory review

@gittensory-orb gittensory-orb Bot added gittensor Gittensor contributor context gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 1.25x multiplier. labels Jul 2, 2026
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gittensory-ui ad44ba3 Commit Preview URL

Branch Preview URL
Jul 02 2026, 05:15 AM

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.93%. Comparing base (118d537) to head (a6f40cb).
⚠️ Report is 26 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2467   +/-   ##
=======================================
  Coverage   95.93%   95.93%           
=======================================
  Files         225      225           
  Lines       25338    25348   +10     
  Branches     9218     9223    +5     
=======================================
+ Hits        24308    24318   +10     
  Misses        417      417           
  Partials      613      613           
Files with missing lines Coverage Δ
src/db/repositories.ts 96.52% <100.00%> (+<0.01%) ⬆️
src/db/schema.ts 69.46% <ø> (ø)
src/openapi/schemas.ts 100.00% <ø> (ø)
src/signals/focus-manifest.ts 99.25% <100.00%> (+<0.01%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…ap (#2467)

parseSettingsOverride collapsed an explicit `settings.contributorOpenPrCap:
null` in .gittensory.yml to the same outcome as omitting the key, so a
maintainer had no way to force a DB-configured cap back to disabled via
config-as-code, contradicting the documented yml > DB > null precedence.
Distinguish the three states (omitted / explicit null / invalid) before
normalizing. Also corrects .gittensory.yml.example, which described these
fields as already closing over-cap PRs; enforcement lands in a follow-up PR.
@JSONbored JSONbored merged commit aad5235 into main Jul 2, 2026
14 checks passed
@JSONbored JSONbored deleted the feat/contributor-cap-settings branch July 2, 2026 06:15
@github-project-automation github-project-automation Bot moved this from Todo to Done in gittensory - v1 roadmap Jul 2, 2026
JSONbored added a commit that referenced this pull request Jul 2, 2026
…ap (#2467)

parseSettingsOverride collapsed an explicit `settings.contributorOpenPrCap:
null` in .gittensory.yml to the same outcome as omitting the key, so a
maintainer had no way to force a DB-configured cap back to disabled via
config-as-code, contradicting the documented yml > DB > null precedence.
Distinguish the three states (omitted / explicit null / invalid) before
normalizing. Also corrects .gittensory.yml.example, which described these
fields as already closing over-cap PRs; enforcement lands in a follow-up PR.
JSONbored added a commit that referenced this pull request Jul 2, 2026
…ssue cap (#2270) (#2493)

* feat(settings): add contributorOpenPrCap/contributorOpenIssueCap config (#2270)

Adds the config-as-code contract for per-contributor open PR/issue caps: DB
migration, RepositorySettings type, .gittensory.yml settings: parsing with
precedence over the DB, OpenAPI response fields, and docs. No enforcement
behavior yet (a repo with no cap configured sees zero change) — auto-close
over the cap lands in a follow-up PR.

* fix(settings): preserve an explicit yml null clearing a contributor cap (#2467)

parseSettingsOverride collapsed an explicit `settings.contributorOpenPrCap:
null` in .gittensory.yml to the same outcome as omitting the key, so a
maintainer had no way to force a DB-configured cap back to disabled via
config-as-code, contradicting the documented yml > DB > null precedence.
Distinguish the three states (omitted / explicit null / invalid) before
normalizing. Also corrects .gittensory.yml.example, which described these
fields as already closing over-cap PRs; enforcement lands in a follow-up PR.

* feat(agent-actions): auto-close a contributor's issue over the open-issue cap (#2270)

Adds the first eventName === "issues" actuation branch in
processGitHubWebhook — issues previously had zero auto-close path.
maybeCloseIssueOverContributorCap mirrors the PR-path cap exactly (same
oldest-stays/newest-closes ranking by item number, same owner/admin/bot
exemption) and reuses planAgentMaintenanceActions's contributor_cap
short-circuit to build the label+close plan, so the label/message/closeKind
construction is identical between the PR and issue paths.

Fixes a bug introduced by that reuse: the close-comment message was
hardcoded to say "pull requests" even when closing an issue. contributorCapMatch
now carries an itemKind ("pull requests" | "issues") so each caller states
its own noun explicitly.

New executeIssueMaintenanceActions is deliberately narrower than the PR
executor: no freshness/live-CI/pull_requests:write gates (none of those PR
concepts apply to a plain issue), and auto_with_approval is denied rather
than staged — the pending-action queue is PR-shaped (pullNumber-typed
staging + a /pull/{n} deeplink); extending it to issues is a documented
follow-up, not silently bypassed here. closeIssue is a new GitHub REST
primitive (PATCH /issues/{n}), since closePullRequest hits the Pulls API,
which a plain issue number isn't valid against.

* fix(agent-actions): close every over-cap sibling issue, not just the incoming one (#2493)

Same delivery-order gap as #2479, mirrored for issues: closing only "the
incoming issue, if it's over cap" let an older sibling's stale verdict
(computed before a newer sibling existed in the DB) stand forever, since
nothing else ever re-evaluates an issue. Now closes every number in the
over-cap set discovered by the current delivery. Unlike the PR path,
issues have no live-head/CI staleness risk to guard against and no
issue-side "regate" job type to reuse, so acting directly on the
already-fetched snapshot is safe.

Also fixes the codecov/patch gap the gate flagged on this PR: adds
coverage for the label ?? "" fallback, the no-slash repoFullName guard,
and an author-less (ghost) sibling issue.

* test(queue): cover the sibling-wake enqueue-failure path (#2270)

env.JOBS.send can reject (queue backpressure/outage) when waking a missed
over-cap sibling; pin the fail-safe behavior — swallow the error and skip
claiming the coalescing key so a later discovery can retry the enqueue.
JSONbored added a commit that referenced this pull request Jul 2, 2026
…ssue cap (#2270) (#2493)

* feat(settings): add contributorOpenPrCap/contributorOpenIssueCap config (#2270)

Adds the config-as-code contract for per-contributor open PR/issue caps: DB
migration, RepositorySettings type, .gittensory.yml settings: parsing with
precedence over the DB, OpenAPI response fields, and docs. No enforcement
behavior yet (a repo with no cap configured sees zero change) — auto-close
over the cap lands in a follow-up PR.

* fix(settings): preserve an explicit yml null clearing a contributor cap (#2467)

parseSettingsOverride collapsed an explicit `settings.contributorOpenPrCap:
null` in .gittensory.yml to the same outcome as omitting the key, so a
maintainer had no way to force a DB-configured cap back to disabled via
config-as-code, contradicting the documented yml > DB > null precedence.
Distinguish the three states (omitted / explicit null / invalid) before
normalizing. Also corrects .gittensory.yml.example, which described these
fields as already closing over-cap PRs; enforcement lands in a follow-up PR.

* feat(agent-actions): auto-close a contributor's issue over the open-issue cap (#2270)

Adds the first eventName === "issues" actuation branch in
processGitHubWebhook — issues previously had zero auto-close path.
maybeCloseIssueOverContributorCap mirrors the PR-path cap exactly (same
oldest-stays/newest-closes ranking by item number, same owner/admin/bot
exemption) and reuses planAgentMaintenanceActions's contributor_cap
short-circuit to build the label+close plan, so the label/message/closeKind
construction is identical between the PR and issue paths.

Fixes a bug introduced by that reuse: the close-comment message was
hardcoded to say "pull requests" even when closing an issue. contributorCapMatch
now carries an itemKind ("pull requests" | "issues") so each caller states
its own noun explicitly.

New executeIssueMaintenanceActions is deliberately narrower than the PR
executor: no freshness/live-CI/pull_requests:write gates (none of those PR
concepts apply to a plain issue), and auto_with_approval is denied rather
than staged — the pending-action queue is PR-shaped (pullNumber-typed
staging + a /pull/{n} deeplink); extending it to issues is a documented
follow-up, not silently bypassed here. closeIssue is a new GitHub REST
primitive (PATCH /issues/{n}), since closePullRequest hits the Pulls API,
which a plain issue number isn't valid against.

* fix(agent-actions): close every over-cap sibling issue, not just the incoming one (#2493)

Same delivery-order gap as #2479, mirrored for issues: closing only "the
incoming issue, if it's over cap" let an older sibling's stale verdict
(computed before a newer sibling existed in the DB) stand forever, since
nothing else ever re-evaluates an issue. Now closes every number in the
over-cap set discovered by the current delivery. Unlike the PR path,
issues have no live-head/CI staleness risk to guard against and no
issue-side "regate" job type to reuse, so acting directly on the
already-fetched snapshot is safe.

Also fixes the codecov/patch gap the gate flagged on this PR: adds
coverage for the label ?? "" fallback, the no-slash repoFullName guard,
and an author-less (ghost) sibling issue.

* test(queue): cover the sibling-wake enqueue-failure path (#2270)

env.JOBS.send can reject (queue backpressure/outage) when waking a missed
over-cap sibling; pin the fail-safe behavior — swallow the error and skip
claiming the coalescing key so a later discovery can retry the enqueue.
JSONbored added a commit that referenced this pull request Jul 2, 2026
…ap (#2270) (#2479)

* feat(settings): add contributorOpenPrCap/contributorOpenIssueCap config (#2270)

Adds the config-as-code contract for per-contributor open PR/issue caps: DB
migration, RepositorySettings type, .gittensory.yml settings: parsing with
precedence over the DB, OpenAPI response fields, and docs. No enforcement
behavior yet (a repo with no cap configured sees zero change) — auto-close
over the cap lands in a follow-up PR.

* feat(agent-actions): auto-close a contributor's PR over the open-PR cap (#2270)

Adds the enforcement half of #2270: a new closeKind: "contributor_cap"
short-circuit in planAgentMaintenanceActions, mirroring the existing
contributor-blacklist block exactly (fires ahead of merit/CI/AI analysis,
exempt from the close-precision breaker, owner/admin/bot never touched).

The webhook-time check counts the author's other open PRs on the repo
(already-fetched, live-DB-backed data — no new query) plus the incoming
one, ranked by PR number so a burst of near-simultaneous opens still
ranks deterministically; only the PR(s) pushing the count over the cap
close, oldest ones stay open. Adds the paired contributorCapLabel setting
(default "over-contributor-limit") through the full config-as-code chain.

Known scope limit: this covers live webhook-time enforcement only. A
maintainer lowering the cap after a contributor already has more PRs open
than the new cap allows is not retroactively enforced by a sweep in this
PR — tracked as follow-up work on #2270, not required for the core ask
(closing a NEW PR that pushes someone over the limit).

* fix(docs): correct .gittensory.yml.example — the PR cap is now enforced (#2479)

Gate finding on this PR: the example file still said contributorOpenPrCap
was inert with enforcement "landing in a follow-up PR" — but this very PR
IS that follow-up, so the doc was stale the moment it merged. Splits the
two fields' status: contributorOpenPrCap is enforced; contributorOpenIssueCap
remains inert until its own follow-up. Also adds a webhook-level (not just
planner-level) owner-exemption test, per the gate's non-blocking suggestion.

* fix(queue): wake missed siblings on out-of-order contributor-cap delivery (#2270)

Webhook delivery order is not guaranteed to match PR creation order, so a
sibling PR's webhook can process before a later-created PR exists in the DB
and wrongly conclude the author is within the open-PR cap. Nothing else
would ever re-evaluate that sibling, permanently bypassing the cap. Now that
a later delivery has the complete picture, wake any other still-open
over-cap sibling so its own next pass self-corrects.

* feat(agent-actions): auto-close a contributor's issue over the open-issue cap (#2270) (#2493)

* feat(settings): add contributorOpenPrCap/contributorOpenIssueCap config (#2270)

Adds the config-as-code contract for per-contributor open PR/issue caps: DB
migration, RepositorySettings type, .gittensory.yml settings: parsing with
precedence over the DB, OpenAPI response fields, and docs. No enforcement
behavior yet (a repo with no cap configured sees zero change) — auto-close
over the cap lands in a follow-up PR.

* fix(settings): preserve an explicit yml null clearing a contributor cap (#2467)

parseSettingsOverride collapsed an explicit `settings.contributorOpenPrCap:
null` in .gittensory.yml to the same outcome as omitting the key, so a
maintainer had no way to force a DB-configured cap back to disabled via
config-as-code, contradicting the documented yml > DB > null precedence.
Distinguish the three states (omitted / explicit null / invalid) before
normalizing. Also corrects .gittensory.yml.example, which described these
fields as already closing over-cap PRs; enforcement lands in a follow-up PR.

* feat(agent-actions): auto-close a contributor's issue over the open-issue cap (#2270)

Adds the first eventName === "issues" actuation branch in
processGitHubWebhook — issues previously had zero auto-close path.
maybeCloseIssueOverContributorCap mirrors the PR-path cap exactly (same
oldest-stays/newest-closes ranking by item number, same owner/admin/bot
exemption) and reuses planAgentMaintenanceActions's contributor_cap
short-circuit to build the label+close plan, so the label/message/closeKind
construction is identical between the PR and issue paths.

Fixes a bug introduced by that reuse: the close-comment message was
hardcoded to say "pull requests" even when closing an issue. contributorCapMatch
now carries an itemKind ("pull requests" | "issues") so each caller states
its own noun explicitly.

New executeIssueMaintenanceActions is deliberately narrower than the PR
executor: no freshness/live-CI/pull_requests:write gates (none of those PR
concepts apply to a plain issue), and auto_with_approval is denied rather
than staged — the pending-action queue is PR-shaped (pullNumber-typed
staging + a /pull/{n} deeplink); extending it to issues is a documented
follow-up, not silently bypassed here. closeIssue is a new GitHub REST
primitive (PATCH /issues/{n}), since closePullRequest hits the Pulls API,
which a plain issue number isn't valid against.

* fix(agent-actions): close every over-cap sibling issue, not just the incoming one (#2493)

Same delivery-order gap as #2479, mirrored for issues: closing only "the
incoming issue, if it's over cap" let an older sibling's stale verdict
(computed before a newer sibling existed in the DB) stand forever, since
nothing else ever re-evaluates an issue. Now closes every number in the
over-cap set discovered by the current delivery. Unlike the PR path,
issues have no live-head/CI staleness risk to guard against and no
issue-side "regate" job type to reuse, so acting directly on the
already-fetched snapshot is safe.

Also fixes the codecov/patch gap the gate flagged on this PR: adds
coverage for the label ?? "" fallback, the no-slash repoFullName guard,
and an author-less (ghost) sibling issue.

* test(queue): cover the sibling-wake enqueue-failure path (#2270)

env.JOBS.send can reject (queue backpressure/outage) when waking a missed
over-cap sibling; pin the fail-safe behavior — swallow the error and skip
claiming the coalescing key so a later discovery can retry the enqueue.

* fix(queue): resolve base conflict and 2 gate findings on contributor-cap enforcement

Gate findings on this PR:
- .gittensory.yml.example still documented contributorOpenIssueCap as
  inert, but the issue-cap enforcement from #2493 already landed on
  this branch — the doc was stale. Both caps are now documented as
  enforced.
- maybeCloseIssueOverContributorCap counted siblings from
  listOpenIssues (a DB read), with no live verification. A stale
  "open" row for a sibling already closed on GitHub could inflate the
  count and wrongly close a newly opened issue within the real cap.
  Live-verify each counted sibling before trusting it, mirroring
  reconcileLiveDuplicateSiblings' fail-open contract (only drop a
  sibling on a positive "not open" confirmation).

Also resolves a base conflict from a rebase onto main (closeKind's
"contributor_cap" variant combined with the closeRequiresCiState
tri-state added by #2478, both touching the same close-action types).

* fix(queue): fail SAFE, not fail-open, on an unverifiable issue-cap sibling

The live-verify guard added for the first gate finding on this PR still
counted an unreadable sibling toward the cap by default (mirroring
reconcileLiveDuplicateSiblings' fail-open-to-stored contract). The gate
flagged that this compounds with a stale "open" DB row: a transient
fetch failure could still let a stale-closed sibling inflate the count
and wrongly close a newly opened issue within the real cap.

Unlike reconcileLiveDuplicateSiblings (which only re-ranks a non-final
duplicate-cluster winner), this count gates an irreversible close, so
the safe default flips: only a POSITIVE "open" confirmation counts a
sibling toward the cap. An unreadable check now excludes it instead of
including it.
@github-actions github-actions Bot mentioned this pull request Jul 2, 2026
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 1.25x multiplier. gittensor Gittensor contributor context

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

feat(agent-actions): configurable per-contributor open PR/issue cap with auto-close over the limit

1 participant