Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gittensory.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Gittensory repo focus manifest — machine-readable contributor policy for this project.

Check notice on line 1 in .gittensory.yml

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 7 meaningful terms.

Check notice on line 1 in .gittensory.yml

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in .gittensory.yml

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
# Private maintainerNotes stay in authenticated API surfaces only.

source: repo_file
Expand Down Expand Up @@ -32,6 +32,17 @@

issueDiscoveryPolicy: discouraged

# Authoritative gate config, config-as-code (layered OVER dashboard repository settings:
# .gittensory.yml > DB settings > safe defaults). ONLY confirmed Gittensor contributors are ever
# hard-blocked (see PR #644); these fields only choose what the gate does, not who it applies to.
gate:
# enabled: false # set false to disable the gate from config (turning it on is a dashboard setting)
linkedIssue: advisory # block | advisory | off — issues aren't always available; advise, don't block
duplicates: block # block | advisory | off — block obvious duplicate PRs
readiness:
mode: advisory # block | advisory | off — readiness-score floor
minScore: 60

publicNotes:
- Prefer backend Workers, MCP, GitHub App, registry, and scoring work when scope allows.
- Focused control-panel UI changes are welcome when they use live API data or honest empty/error states and tie to safety, release readiness, or operator-facing analytics.
Expand Down
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Contributing

Check notice on line 1 in CONTRIBUTING.md

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 7 meaningful terms.

Check notice on line 1 in CONTRIBUTING.md

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in CONTRIBUTING.md

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.

Gittensory is a Cloudflare Workers, TanStack Start, GitHub App, and MCP project for
Gittensor OSS contribution intelligence. Contributions need to protect that scope: private
Expand Down Expand Up @@ -189,6 +189,29 @@
- Keep public comments advisory, sanitized, and low-noise.
- Keep labels limited to configured labels for officially confirmed Gittensor miner PRs.
- Never publish private reviewability, scoring, wallet, hotkey, or reward/risk context.
- The Gittensory Gate blocks **only confirmed Gittensor contributors**; every other author (and any
app/infra state) resolves to a neutral, non-blocking gate. Adding a blocker must keep it
confirmed-contributor-gated through `evaluateGateCheck`.

Config as code (`.gittensory.yml`) — every repository setting is controllable from the config file:

- **`settings:`** is a partial of the repository settings: any behaviour a maintainer can toggle in the
dashboard can be set here as code — `commentMode`, `publicAudienceMode`, `publicSurface`, `checkRunMode`,
`gateCheckMode`, the gate-blocker modes, `autoLabelEnabled`, `gittensorLabel`, `requireLinkedIssue`,
`backfillEnabled`, etc.
- **`gate:`** is a friendly typed alias for the gate subset — `enabled` (on/off), `linkedIssue`,
`duplicates`, `readiness: { mode, minScore }` (each `off | advisory | block`).
- **`review:`** customizes the public review-panel CONTENT: `footer: { text }` (custom lead copy — the
Gittensor register link + attribution are always appended), `note` (a custom intro line), and
`fields: { <row>: false }` to show/hide individual panel rows (`linkedIssue`, `relatedWork`, `reviewLoad`,
`validationEvidence`, `openPrQueue`, `contributorContext`, `gateResult`). Maintainer text that fails the
public-safe filter (reward/score/wallet/hotkey/etc.) is dropped, never published.
- Precedence: `.gittensory.yml` `gate:` > `.gittensory.yml` `settings:` > dashboard repository settings >
safe defaults; unset fields fall back to the next layer. The committed root `.gittensory.yml` is the
worked example. Resolved once in `resolveRepositorySettings`, so the whole app honours the file.
- The config chooses **what** gittensory does (gate on/off, blockers, comments, labels, surface, panel
content); it never changes **who** can be blocked — only confirmed Gittensor contributors are ever
hard-blocked, and the footer's Gittensor attribution/register link always remains.

## Commit And PR Titles

Expand Down
11 changes: 11 additions & 0 deletions src/config/gittensory-repo-focus-manifest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**

Check notice on line 1 in src/config/gittensory-repo-focus-manifest.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 7 meaningful terms.

Check notice on line 1 in src/config/gittensory-repo-focus-manifest.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in src/config/gittensory-repo-focus-manifest.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
* Bundled fallback for JSONbored/gittensory when the repo file is not yet reachable
* (local dev, pre-merge branches). Keep aligned with `.gittensory.yml` at repo root.
*/
Expand Down Expand Up @@ -36,6 +36,17 @@

issueDiscoveryPolicy: discouraged

# Authoritative gate config, config-as-code (layered OVER dashboard repository settings:
# .gittensory.yml > DB settings > safe defaults). ONLY confirmed Gittensor contributors are ever
# hard-blocked (see PR #644); these fields only choose what the gate does, not who it applies to.
gate:
# enabled: false # set false to disable the gate from config (turning it on is a dashboard setting)
linkedIssue: advisory # block | advisory | off — issues aren't always available; advise, don't block
duplicates: block # block | advisory | off — block obvious duplicate PRs
readiness:
mode: advisory # block | advisory | off — readiness-score floor
minScore: 60

publicNotes:
- Prefer backend Workers, MCP, GitHub App, registry, and scoring work when scope allows.
- Focused control-panel UI changes are welcome when they use live API data or honest empty/error states and tie to safety, release readiness, or operator-facing analytics.
Expand Down
12 changes: 11 additions & 1 deletion src/github/footer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,18 @@ export function gittensorRepoEarnUrl(repoFullName: string): string {
* invite and anyone viewing a registered contributor's PR sees it too. The registered/non-registered
* distinction lives in the review BODY (full panel vs. minimal), not here.
* Uses only "earn" wording — never reward/payout/score (forbidden in public comments). */
export function gittensoryFooter(opts: { earnUrl?: string | undefined } = {}): string {
export function gittensoryFooter(opts: { earnUrl?: string | undefined; customText?: string | undefined } = {}): string {
const earnUrl = opts.earnUrl ?? GITTENSOR_HOME_URL;
// Maintainer-customized footer (via `.gittensory.yml review.footer.text`): the maintainer's public-safe
// lead replaces the default CTA copy, but the Gittensor register link + Gittensory attribution are
// ALWAYS appended — the growth surface is preserved regardless of customization.
if (opts.customText) {
return [
opts.customText,
"",
`[Gittensor](${GITTENSOR_HOME_URL}) lets GitHub contributors earn for the work they already do — [register to start earning →](${earnUrl}). Checked by [Gittensory](${GITTENSORY_SITE_URL}).`,
].join("\n");
}
return [
`💰 **Earn for open-source contributions like this.** [Gittensor](${GITTENSOR_HOME_URL}) lets GitHub contributors earn for the work they already do — [register to start earning →](${earnUrl}).`,
"",
Expand Down
34 changes: 27 additions & 7 deletions src/queue/processors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {

Check notice on line 1 in src/queue/processors.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 7 meaningful terms.

Check notice on line 1 in src/queue/processors.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in src/queue/processors.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
countOpenIssues,
countOpenPullRequests,
getAgentCommandAnswer,
Expand Down Expand Up @@ -129,6 +129,8 @@
unionScopedOverlapClusters,
} from "../signals/engine";
import { decidePublicSurface } from "../signals/settings-preview";
import { loadRepoFocusManifest } from "../signals/focus-manifest-loader";
import { resolveEffectiveSettings } from "../signals/focus-manifest";
import type { LocalBranchAnalysisInput } from "../signals/local-branch";
import type { ContributorEvidenceRecord, GitHubWebhookPayload, JobMessage, JsonValue, PullRequestRecord, RepositorySettings } from "../types";
import { sha256Hex } from "../utils/crypto";
Expand Down Expand Up @@ -695,7 +697,7 @@
const pr = await upsertPullRequestFromGitHub(env, repoFullName, payload.pull_request);
const [repo, settings, otherOpenPullRequests] = await Promise.all([
getRepository(env, repoFullName),
getRepositorySettings(env, repoFullName),
resolveRepositorySettings(env, repoFullName),
listOtherOpenPullRequests(env, repoFullName, pr.number),
]);
const advisory = buildPullRequestAdvisory(repo, pr, {
Expand Down Expand Up @@ -780,7 +782,10 @@
return PR_PUBLIC_SURFACE_ACTIONS.has(action ?? "") || PR_GATE_CLOSED_ACTIONS.has(action ?? "");
}

function gateCheckPolicy(settings: RepositorySettings, readinessScore?: number | null, confirmedContributor?: boolean) {
export function gateCheckPolicy(settings: RepositorySettings, readinessScore?: number | null, confirmedContributor?: boolean) {
// `settings` is already the EFFECTIVE config (`.gittensory.yml` > DB > defaults), resolved upstream by
// resolveRepositorySettings, so the blocker modes here reflect the repo's config file directly.
// confirmedContributor governs WHO can be blocked, downstream in evaluateGateCheck.
return {
linkedIssueGateMode: settings.linkedIssueGateMode,
duplicatePrGateMode: settings.duplicatePrGateMode,
Expand All @@ -791,6 +796,16 @@
};
}

/**
* Effective repository settings for webhook handling: the DB-backed settings overlaid with the repo's
* `.gittensory.yml` (config-as-code). This single resolver is why EVERYTHING — gate on/off, all blocker
* modes, comments, labels, surface, audience — is controllable from the repo's config file.
*/
async function resolveRepositorySettings(env: Env, repoFullName: string): Promise<RepositorySettings> {
const [dbSettings, manifest] = await Promise.all([getRepositorySettings(env, repoFullName), loadRepoFocusManifest(env, repoFullName)]);
return resolveEffectiveSettings(dbSettings, manifest);
}

function linkedIssueDuplicatePullRequestsForGate(pr: PullRequestRecord, pullRequests: PullRequestRecord[]): number[] {
const linkedIssues = new Set(pr.linkedIssues);
if (linkedIssues.size === 0) return [];
Expand Down Expand Up @@ -850,6 +865,9 @@
webhook: { deliveryId: string; authorType?: string | undefined; action?: string | undefined },
): Promise<void> {
const author = pr.authorLogin ?? null;
// `settings` is the EFFECTIVE config (`.gittensory.yml` > DB > defaults), resolved by the caller via
// resolveRepositorySettings — so gate on/off and every blocker mode already reflect the repo's config
// file. The gate only chooses what to do; confirmedContributor governs WHO can be blocked.
const gateEnabled = settings.gateCheckMode === "enabled" && Boolean(advisory.headSha);
// Cheap, network-free skip checks (also avoids the miner lookup when it would be wasted).
const prelim = decidePublicSurface({
Expand Down Expand Up @@ -967,8 +985,7 @@
// detection) gets a neutral, non-blocking gate. Gate-only runs still verify confirmation before
// evaluating blockers so confirmed contributors cannot bypass a required Gate check.
const confirmedContributor = official?.status === "confirmed";
const gateEvaluation =
settings.gateCheckMode === "enabled" ? evaluateGateCheck(advisory, gateCheckPolicy(settings, readiness.total, confirmedContributor)) : undefined;
const gateEvaluation = gateEnabled ? evaluateGateCheck(advisory, gateCheckPolicy(settings, readiness.total, confirmedContributor)) : undefined;
if (gateEnabled) {
const gateCheckResult = await createOrUpdateGateCheckRun(
env,
Expand Down Expand Up @@ -1030,7 +1047,10 @@
}

if (decision.willComment) {
const commentArgs = { repo, pr, profile, detection, queueHealth, collisions, preflight, settings, gate: gateEvaluation };
// Maintainer review-content overrides from `.gittensory.yml` (footer text, row toggles, intro note).
// Cached, so this is a DB read after the settings resolution already loaded the manifest.
const reviewConfig = (await loadRepoFocusManifest(env, repoFullName)).review;
const commentArgs = { repo, pr, profile, detection, queueHealth, collisions, preflight, settings, gate: gateEvaluation, review: reviewConfig };
const deterministicBody = buildPublicPrIntelligenceComment(commentArgs);
try {
await createOrUpdatePrIntelligenceComment(env, installationId, repoFullName, pr.number, deterministicBody);
Expand Down Expand Up @@ -1161,7 +1181,7 @@
await recordPrPanelRetriggerSkip(env, deliveryId, repoFullName, targetKey, actor, "missing_repo_pr_or_installation");
return true;
}
const [pr, settings] = await Promise.all([getPullRequest(env, repoFullName, issue.number), getRepositorySettings(env, repoFullName)]);
const [pr, settings] = await Promise.all([getPullRequest(env, repoFullName, issue.number), resolveRepositorySettings(env, repoFullName)]);
if (!pr) {
await recordPrPanelRetriggerSkip(env, deliveryId, repoFullName, targetKey, actor, "cached_pr_missing");
return true;
Expand Down Expand Up @@ -1352,7 +1372,7 @@
return true;
}

const [repo, cachedPullRequest, settings] = await Promise.all([getRepository(env, repoFullName), getPullRequest(env, repoFullName, issue.number), getRepositorySettings(env, repoFullName)]);
const [repo, cachedPullRequest, settings] = await Promise.all([getRepository(env, repoFullName), getPullRequest(env, repoFullName, issue.number), resolveRepositorySettings(env, repoFullName)]);
const pullRequestAuthor = cachedPullRequest?.authorLogin ?? issue.user?.login ?? null;
const needsMinerDetection = commandAuthorizationNeedsMinerDetection({
policy: settings.commandAuthorization,
Expand Down
37 changes: 21 additions & 16 deletions src/signals/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
} from "../types";
import type { PublicContributorProfile } from "../github/public";
import { gittensoryFooter, gittensorRepoEarnUrl } from "../github/footer";
import type { FocusManifestReviewConfig, ReviewFieldKey } from "./focus-manifest";
import type { GittensorContributorSnapshot } from "../gittensor/api";
import { nowIso } from "../utils/json";
import { sanitizePublicComment } from "../queue-intelligence";
Expand Down Expand Up @@ -3857,6 +3858,7 @@ export function buildPublicPrIntelligenceComment(args: {
preflight: PreflightResult;
settings: RepositorySettings;
gate?: PublicPrPanelGateEvaluation | undefined;
review?: FocusManifestReviewConfig | undefined;
}): string {
const publicFindings = args.preflight.findings
.filter((finding) => finding.severity !== "critical")
Expand Down Expand Up @@ -3935,22 +3937,21 @@ export function buildPublicPrIntelligenceComment(args: {
const changeScopeComponent = readinessByKey.get("change_scope");
const queueComponent = readinessByKey.get("queue_pressure");
const contributorContext = contributorContextPanelResult(args.pr, args.profile, args.detection, confirmedMiner);
const rows: Array<[string, string, string, string]> = [
[
"Linked issue",
linkedIssueResult.result,
linkedIssueResult.evidence,
linkedIssueResult.action,
],
["Related work", relatedWorkResult.result, relatedWorkResult.evidence, relatedWorkResult.action],
// Each row carries a stable key so a maintainer can show/hide it from `.gittensory.yml review.fields`
// (default: shown). Hiding a row is cosmetic — the underlying signal/gate still functions.
const allRows: Array<{ key: ReviewFieldKey; cells: [string, string, string, string] }> = [
{ key: "linkedIssue", cells: ["Linked issue", linkedIssueResult.result, linkedIssueResult.evidence, linkedIssueResult.action] },
{ key: "relatedWork", cells: ["Related work", relatedWorkResult.result, relatedWorkResult.evidence, relatedWorkResult.action] },
/* v8 ignore start -- Readiness components are built as a fixed key set; fallbacks guard future partial score shapes. */
["Review load", scoreResultIcon(changeScopeComponent), changeScopeComponent?.evidence ?? "No public scope metadata found.", changeScopeComponent?.action ?? "No action."],
["Validation evidence", scoreResultIcon(validationComponent), validationComponent?.evidence ?? "No validation signal found.", validationComponent?.action ?? "Add validation note."],
["Open PR queue", scoreResultIcon(queueComponent), queueComponent?.evidence ?? "Open PR queue unavailable.", queueComponent?.action ?? "No action."],
{ key: "reviewLoad", cells: ["Review load", scoreResultIcon(changeScopeComponent), changeScopeComponent?.evidence ?? "No public scope metadata found.", changeScopeComponent?.action ?? "No action."] },
{ key: "validationEvidence", cells: ["Validation evidence", scoreResultIcon(validationComponent), validationComponent?.evidence ?? "No validation signal found.", validationComponent?.action ?? "Add validation note."] },
{ key: "openPrQueue", cells: ["Open PR queue", scoreResultIcon(queueComponent), queueComponent?.evidence ?? "Open PR queue unavailable.", queueComponent?.action ?? "No action."] },
/* v8 ignore stop */
["Contributor context", contributorContext.result, contributorContext.evidence, contributorContext.action],
["Gate result", gateStatus(gateEnabled, gateConclusion), gateEnabled ? gateAction(gateConclusion) : "Advisory only.", gateEnabled ? gateNextAction(gateConclusion) : "No action."],
{ key: "contributorContext", cells: ["Contributor context", contributorContext.result, contributorContext.evidence, contributorContext.action] },
{ key: "gateResult", cells: ["Gate result", gateStatus(gateEnabled, gateConclusion), gateEnabled ? gateAction(gateConclusion) : "Advisory only.", gateEnabled ? gateNextAction(gateConclusion) : "No action."] },
];
const reviewFields = args.review?.fields;
const rows: Array<[string, string, string, string]> = allRows.filter((row) => reviewFields?.[row.key] !== false).map((row) => row.cells);
const overlapDetails = relatedWorkDetails(args.pr, scopedOverlapClusters);
const maintainerNotes =
publicFindings.length > 0
Expand All @@ -3959,14 +3960,18 @@ export function buildPublicPrIntelligenceComment(args: {
// Always-on earn CTA — a permanent, free marketing surface on every reviewed PR. For a registered
// repo the CTA points at this repo's public Gittensor miner page (social proof for THIS repo + a
// path to register); for an unregistered repo it falls back to the general Gittensor home URL.
const footer = gittensoryFooter({ earnUrl: footerEarnUrl(args.repo, args.pr.repoFullName) });
// The earn CTA stays a permanent marketing surface; `.gittensory.yml review.footer.text` can replace
// the lead copy (already public-safe-validated) but the Gittensor register link + attribution remain.
const footer = gittensoryFooter({ earnUrl: footerEarnUrl(args.repo, args.pr.repoFullName), customText: args.review?.footerText ?? undefined });
return [
"<!-- gittensory-pr-panel:v1 -->",
"",
...formatAlertBlock([
`[!${alert}]`,
`## ${panelTitle}`,
panelSummary,
// Optional maintainer intro note (public-safe-validated at parse time; re-sanitized here).
...(args.review?.note ? ["", sanitizePanelText(args.review.note)] : []),
"",
`**Readiness score: ${readiness.total}/100**`,
"",
Expand Down Expand Up @@ -4023,7 +4028,7 @@ export function buildPublicPrIntelligenceComment(args: {
* analysis is for registered Gittensor contributors, so we skip the panel and post a brief welcome
* + earn invite; the always-on footer CTA does the conversion. Carries the same panel marker so it
* updates in place if the author later registers (the full panel then replaces it). */
function buildMinimalInviteComment(args: { repo: RepositoryRecord | null; pr: PullRequestRecord }): string {
function buildMinimalInviteComment(args: { repo: RepositoryRecord | null; pr: PullRequestRecord; review?: FocusManifestReviewConfig | undefined }): string {
return [
"<!-- gittensory-pr-panel:v1 -->",
"",
Expand All @@ -4034,7 +4039,7 @@ function buildMinimalInviteComment(args: { repo: RepositoryRecord | null; pr: Pu
]),
"",
"---",
gittensoryFooter({ earnUrl: footerEarnUrl(args.repo, args.pr.repoFullName) }),
gittensoryFooter({ earnUrl: footerEarnUrl(args.repo, args.pr.repoFullName), customText: args.review?.footerText ?? undefined }),
].join("\n");
}

Expand Down
Loading