Skip to content
Closed
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: 10 additions & 1 deletion apps/gittensory-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -8113,9 +8113,13 @@
},
"agentPaused": {
"type": "boolean"
},

Check notice on line 8116 in apps/gittensory-ui/public/openapi.json

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.
"agentDryRun": {
"type": "boolean"
},
"newcomerGuideMode": {
"type": "string",
"enum": ["off", "enabled"]
}
},
"required": [
Expand Down Expand Up @@ -8143,7 +8147,8 @@
"requireLinkedIssue",
"backfillEnabled",
"privateTrustEnabled",
"commandAuthorization"
"commandAuthorization",
"newcomerGuideMode"
]
},
"InstallationRepair": {
Expand Down Expand Up @@ -8619,6 +8624,10 @@
"requireLinkedIssue": {
"type": "boolean"
},
"newcomerGuideMode": {
"type": "string",
"enum": ["off", "enabled"]
},
"commandAuthorization": {
"type": "object",
"properties": {
Expand Down
4 changes: 4 additions & 0 deletions migrations/0047_newcomer_guide_mode.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Advisory newcomer-PR auto-guide (#803, Phase-1-lite). When enabled, the webhook posts a one-time

Check notice on line 1 in migrations/0047_newcomer_guide_mode.sql

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.
-- welcoming advisory comment on first-time-contributor PRs (0 merged PRs in the repo). Advisory
-- only — never blocks, never auto-merges. Reuses the #552 newcomer detection. Default 'off'.
ALTER TABLE repository_settings ADD COLUMN newcomer_guide_mode TEXT NOT NULL DEFAULT 'off';
9 changes: 9 additions & 0 deletions src/db/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,11 @@
includeMaintainerAuthors: false,
requireLinkedIssue: false,
backfillEnabled: true,
privateTrustEnabled: true,

Check notice on line 432 in src/db/repositories.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.
badgeEnabled: false,
agentPaused: false,
agentDryRun: false,
newcomerGuideMode: "off",
commandAuthorization: normalizeCommandAuthorizationPolicy(DEFAULT_COMMAND_AUTHORIZATION_POLICY).policy,
autonomy: {},
autoMaintain: { ...DEFAULT_AUTO_MAINTAIN_POLICY },
Expand Down Expand Up @@ -472,6 +473,7 @@
badgeEnabled: row.badgeEnabled,
agentPaused: row.agentPaused,
agentDryRun: row.agentDryRun,
newcomerGuideMode: parseNewcomerGuideMode(row.newcomerGuideMode),
commandAuthorization: parseCommandAuthorizationPolicy(row.commandAuthorizationJson),
autonomy: parseAutonomyPolicy(row.autonomyJson),
autoMaintain: parseAutoMaintainPolicy(row.autoMaintainJson),
Expand Down Expand Up @@ -515,6 +517,7 @@
badgeEnabled: settings.badgeEnabled ?? false,
agentPaused: settings.agentPaused ?? false,
agentDryRun: settings.agentDryRun ?? false,
newcomerGuideMode: settings.newcomerGuideMode ?? "off",
commandAuthorization: normalizeCommandAuthorizationPolicy(settings.commandAuthorization).policy,
autonomy: normalizeAutonomyPolicy(settings.autonomy),
autoMaintain: normalizeAutoMaintainPolicy(settings.autoMaintain),
Expand Down Expand Up @@ -556,6 +559,7 @@
badgeEnabled: resolved.badgeEnabled,
agentPaused: resolved.agentPaused,
agentDryRun: resolved.agentDryRun,
newcomerGuideMode: resolved.newcomerGuideMode,
commandAuthorizationJson: jsonString(resolved.commandAuthorization),
autonomyJson: jsonString(resolved.autonomy),
autoMaintainJson: jsonString(resolved.autoMaintain),
Expand Down Expand Up @@ -598,6 +602,7 @@
badgeEnabled: resolved.badgeEnabled,
agentPaused: resolved.agentPaused,
agentDryRun: resolved.agentDryRun,
newcomerGuideMode: resolved.newcomerGuideMode,
commandAuthorizationJson: jsonString(resolved.commandAuthorization),
autonomyJson: jsonString(resolved.autonomy),
autoMaintainJson: jsonString(resolved.autoMaintain),
Expand Down Expand Up @@ -5125,6 +5130,10 @@
return value === "oss-anti-slop" ? "oss-anti-slop" : "gittensor";
}

function parseNewcomerGuideMode(value: string): RepositorySettings["newcomerGuideMode"] {
return value === "enabled" ? "enabled" : "off";
}

function parseGateRuleMode(value: string): RepositorySettings["linkedIssueGateMode"] {
if (value === "off" || value === "block") return value;
return "advisory";
Expand Down
1 change: 1 addition & 0 deletions src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,10 @@
badgeEnabled: integer("badge_enabled", { mode: "boolean" }).notNull().default(false),
commandAuthorizationJson: text("command_authorization_json").notNull().default("{}"),
autonomyJson: text("autonomy_json").notNull().default("{}"),
autoMaintainJson: text("auto_maintain_json").notNull().default("{}"),

Check notice on line 75 in src/db/schema.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.
agentPaused: integer("agent_paused", { mode: "boolean" }).notNull().default(false),
agentDryRun: integer("agent_dry_run", { mode: "boolean" }).notNull().default(false),
newcomerGuideMode: text("newcomer_guide_mode").notNull().default("off"),
createdAt: text("created_at").notNull().$defaultFn(() => nowIso()),
updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()),
});
Expand Down
12 changes: 12 additions & 0 deletions src/github/comments.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Octokit } from "@octokit/core";
import { createInstallationToken } from "./app";

export const PR_PANEL_COMMENT_MARKER = "<!-- gittensory-pr-panel:v1 -->";

Check notice on line 4 in src/github/comments.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.
export const PR_INTELLIGENCE_COMMENT_MARKER = PR_PANEL_COMMENT_MARKER;
export const AGENT_COMMAND_COMMENT_MARKER = PR_PANEL_COMMENT_MARKER;
export const NEWCOMER_GUIDE_COMMENT_MARKER = "<!-- gittensory-newcomer-guide:v1 -->";
const LEGACY_PR_INTELLIGENCE_COMMENT_MARKER = "<!-- gittensory-pr-intelligence -->";
const LEGACY_AGENT_COMMAND_COMMENT_MARKER = "<!-- gittensory-agent-command -->";

Expand Down Expand Up @@ -38,6 +39,17 @@
return createOrUpdateIssueCommentWithMarker(env, installationId, repoFullName, issueNumber, body, AGENT_COMMAND_COMMENT_MARKER);
}

export async function createOrUpdateNewcomerGuideComment(
env: Env,
installationId: number,
repoFullName: string,
pullNumber: number,
body: string,
options: { createIfMissing?: boolean | undefined } = {},
): Promise<{ id: number; html_url?: string } | null> {
return createOrUpdateIssueCommentWithMarker(env, installationId, repoFullName, pullNumber, body, NEWCOMER_GUIDE_COMMENT_MARKER, options);
}

async function createOrUpdateIssueCommentWithMarker(
env: Env,
installationId: number,
Expand Down
2 changes: 2 additions & 0 deletions src/openapi/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,9 +584,10 @@
autonomy: z
.record(z.enum(["review", "request_changes", "approve", "merge", "close", "label"]), z.enum(["observe", "suggest", "propose", "auto_with_approval", "auto"]))
.optional(),
autoMaintain: z.object({ requireApprovals: z.number().int(), mergeMethod: z.enum(["merge", "squash", "rebase"]) }).optional(),

Check notice on line 587 in src/openapi/schemas.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.
agentPaused: z.boolean().optional(),
agentDryRun: z.boolean().optional(),
newcomerGuideMode: z.enum(["off", "enabled"]),
createdAt: z.string().nullable().optional(),
updatedAt: z.string().nullable().optional(),
})
Expand Down Expand Up @@ -619,6 +620,7 @@
createMissingLabel: z.boolean(),
includeMaintainerAuthors: z.boolean(),
requireLinkedIssue: z.boolean(),
newcomerGuideMode: z.enum(["off", "enabled"]),
commandAuthorization: z.object({
defaultAllowed: z.array(z.enum(["maintainer", "collaborator", "pr_author", "confirmed_miner"])),
commandOverrides: z.array(
Expand Down
46 changes: 44 additions & 2 deletions src/queue/processors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@
backfillRepositorySegment,
enqueueRepositoryOpenDataBackfill,
refreshContributorActivity,
refreshInstallationHealth,

Check notice on line 67 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.
refreshPullRequestDetails,
} from "../github/backfill";
import { contributorRepoStatsFromGittensor, fetchGittensorContributorSnapshot, fetchOfficialGittensorMiner, type GittensorContributorSnapshot, type OfficialGittensorMinerDetection } from "../gittensor/api";
import { createOrUpdateCheckRun, createOrUpdateErroredGateCheckRun, createOrUpdateGateCheckRun, createOrUpdateOverriddenGateCheckRun, createOrUpdatePendingGateCheckRun, createOrUpdateSkippedGateCheckRun, getInstallationId, getRepositoryCollaboratorPermission } from "../github/app";
import { AGENT_COMMAND_COMMENT_MARKER, createOrUpdateAgentCommandComment, createOrUpdatePrIntelligenceComment, PR_PANEL_COMMENT_MARKER } from "../github/comments";
import { AGENT_COMMAND_COMMENT_MARKER, createOrUpdateAgentCommandComment, createOrUpdateNewcomerGuideComment, createOrUpdatePrIntelligenceComment, PR_PANEL_COMMENT_MARKER } from "../github/comments";
import { gittensoryFooter, gittensorRepoEarnUrl } from "../github/footer";
import {
buildMaintainerQueueDigest,
Expand All @@ -86,6 +86,7 @@
import { fetchPublicContributorProfile } from "../github/public";
import { refreshRegistry } from "../registry/sync";
import { buildIssueAdvisory, buildPullRequestAdvisory, evaluateGateCheck, isTestPath } from "../rules/advisory";
import { buildNewcomerGuideComment } from "../signals/newcomer-guide";
import { detectNotificationEvents } from "../notifications/events";
import { deliverNotification, detectIssueWatchEvents, evaluateNotificationEvent } from "../notifications/service";
import { getOrCreateScoringModelSnapshot, refreshScoringModelSnapshot } from "../scoring/model";
Expand Down Expand Up @@ -1404,6 +1405,7 @@
let gateEvaluation: ReturnType<typeof evaluateGateCheck> | undefined;
let aiReview: { notes: string } | undefined;
let gateFinalized = false;
let authorHistory: { mergedPrCount: number; closedUnmergedPrCount: number } | undefined;
try {
const [repoIssues, repoPullRequests, repoBounties] = await Promise.all([
listIssues(env, repoFullName),
Expand Down Expand Up @@ -1512,7 +1514,7 @@
// First-time-contributor grace (#552): compute the author's complete per-repo PR history
// (excluding this PR) with an aggregate DB query. Do not derive policy-enforcement history from
// the bounded repoPullRequests sample; missing or case-mismatched history could soften a block.
const authorHistory = await loadGateAuthorHistory(env, repoFullName, author, pr.number);
authorHistory = await loadGateAuthorHistory(env, repoFullName, author, pr.number);

const gatePolicy = gateCheckPolicy(settings, readiness.total, confirmedContributor, slopRisk, authorHistory);
gateEvaluation = gateEnabled ? evaluateGateCheck(advisory, gatePolicy) : undefined;
Expand Down Expand Up @@ -1573,6 +1575,46 @@
throw error;
}

// Advisory newcomer-PR auto-guide (#803, Phase-1-lite): when enabled and the author is a genuine
// newcomer (0 merged PRs in this repo), post a one-time welcoming advisory comment with specific
// guidance derived from the gate findings. Advisory only — never blocks, never auto-merges.
// Independent of the public-surface decision so it fires even when commentMode is off.
if (settings.newcomerGuideMode === "enabled" && author && authorHistory.mergedPrCount === 0 && pr.state === "open" && webhook.action !== "closed") {
try {
const guideBody = buildNewcomerGuideComment({
authorLogin: author,
pullNumber: pr.number,
title: pr.title ?? "your contribution",
repoFullName,
advisory,
gateBlocking: gateEvaluation?.conclusion === "failure",
});
await createOrUpdateNewcomerGuideComment(env, installationId, repoFullName, pr.number, guideBody);
await recordAuditEvent(env, {
eventType: "github_app.newcomer_guide_posted",
actor: author,
targetKey: `${repoFullName}#${pr.number}`,
outcome: "completed",
metadata: { deliveryId: webhook.deliveryId, repoFullName },
}).catch(() => {
/* v8 ignore next -- best-effort: the posted audit is advisory; a D1 write failure is swallowed so it never aborts the webhook. */
return undefined;
});
} catch (error) {
await recordAuditEvent(env, {
eventType: "github_app.newcomer_guide_failed",
actor: author,
targetKey: `${repoFullName}#${pr.number}`,
outcome: "error",
detail: errorMessage(error),
metadata: { deliveryId: webhook.deliveryId, repoFullName },
}).catch(() => {
/* v8 ignore next -- best-effort: the failed audit is advisory; a D1 write failure is swallowed so it never aborts the webhook. */
return undefined;
});
}
}

if (!prelimHasPublicOutput) return gateEvaluation;
if (publicSurfaceSkipped || !official || !author) return gateEvaluation;

Expand Down
11 changes: 10 additions & 1 deletion src/signals/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
aiReviewByok: boolean | null;
aiReviewProvider: "anthropic" | "openai" | null;
aiReviewModel: string | null;
mergeReadiness: GateRuleMode | null;

Check notice on line 33 in src/signals/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.
manifestPolicy: GateRuleMode | null;
firstTimeContributorGrace: boolean | null;
newcomerGuide: "off" | "enabled" | null;
};

/**
Expand Down Expand Up @@ -71,6 +72,7 @@
| "autoMaintain"
| "agentPaused"
| "agentDryRun"
| "newcomerGuideMode"
>
>;

Expand Down Expand Up @@ -168,6 +170,7 @@
mergeReadiness: null,
manifestPolicy: null,
firstTimeContributorGrace: null,
newcomerGuide: null,
};

const EMPTY_MANIFEST: FocusManifest = {
Expand Down Expand Up @@ -311,6 +314,7 @@
mergeReadiness: normalizeOptionalGateMode(record.mergeReadiness, "gate.mergeReadiness", warnings),
manifestPolicy: normalizeOptionalGateMode(record.manifestPolicy, "gate.manifestPolicy", warnings),
firstTimeContributorGrace: normalizeOptionalBoolean(record.firstTimeContributorGrace, "gate.firstTimeContributorGrace", warnings),
newcomerGuide: normalizeOptionalEnum(record.newcomerGuide, "gate.newcomerGuide", ["off", "enabled"] as const, warnings),
};
gate.present =
gate.enabled !== null ||
Expand All @@ -328,7 +332,8 @@
gate.aiReviewModel !== null ||
gate.mergeReadiness !== null ||
gate.manifestPolicy !== null ||
gate.firstTimeContributorGrace !== null;
gate.firstTimeContributorGrace !== null ||
gate.newcomerGuide !== null;
return gate;
}

Expand Down Expand Up @@ -367,6 +372,7 @@
if (gate.mergeReadiness !== null) out.mergeReadiness = gate.mergeReadiness;
if (gate.manifestPolicy !== null) out.manifestPolicy = gate.manifestPolicy;
if (gate.firstTimeContributorGrace !== null) out.firstTimeContributorGrace = gate.firstTimeContributorGrace;
if (gate.newcomerGuide !== null) out.newcomerGuide = gate.newcomerGuide;
return out;
}

Expand Down Expand Up @@ -408,6 +414,8 @@
if (checkRunDetailLevel !== null) out.checkRunDetailLevel = checkRunDetailLevel;
const gateCheckMode = normalizeOptionalEnum(r.gateCheckMode, "settings.gateCheckMode", ["off", "enabled"] as const, warnings);
if (gateCheckMode !== null) out.gateCheckMode = gateCheckMode;
const newcomerGuideMode = normalizeOptionalEnum(r.newcomerGuideMode, "settings.newcomerGuideMode", ["off", "enabled"] as const, warnings);
if (newcomerGuideMode !== null) out.newcomerGuideMode = newcomerGuideMode;
const linkedIssueGateMode = normalizeOptionalGateMode(r.linkedIssueGateMode, "settings.linkedIssueGateMode", warnings);
if (linkedIssueGateMode !== null) out.linkedIssueGateMode = linkedIssueGateMode;
const duplicatePrGateMode = normalizeOptionalGateMode(r.duplicatePrGateMode, "settings.duplicatePrGateMode", warnings);
Expand Down Expand Up @@ -527,6 +535,7 @@
if (gate.mergeReadiness !== null) effective.mergeReadinessGateMode = gate.mergeReadiness;
if (gate.manifestPolicy !== null) effective.manifestPolicyGateMode = gate.manifestPolicy;
if (gate.firstTimeContributorGrace !== null) effective.firstTimeContributorGrace = gate.firstTimeContributorGrace;
if (gate.newcomerGuide !== null) effective.newcomerGuideMode = gate.newcomerGuide;
return effective;
}

Expand Down
Loading
Loading