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
184 changes: 184 additions & 0 deletions apps/gittensory-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2583,6 +2583,51 @@
"direct_pr_no_issue_required"
]
},
"directPrReadiness": {
"type": "object",
"properties": {
"ready": {
"type": "boolean"
},
"reasons": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"ready",
"reasons"
]
},
"issueDiscoveryReadiness": {
"type": "object",
"properties": {
"ready": {
"type": "boolean"
},
"recommendation": {
"type": "string",
"enum": [
"enabled",
"recommended",
"not_recommended"
]
},
"reasons": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"ready",
"recommendation",
"reasons"
]
},
"labelPolicy": {
"type": "object",
"additionalProperties": {
Expand All @@ -2595,6 +2640,80 @@
"nullable": true
}
},
"testCoverageHealth": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"gate_ready",
"gate_unknown"
]
},
"trustedLabelPipelineReady": {
"type": "boolean"
},
"checkRunMode": {
"type": "string",
"enum": [
"off",
"enabled"
]
},
"requiredGate": {
"type": "array",
"items": {
"type": "string"
}
},
"note": {
"type": "string"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"status",
"trustedLabelPipelineReady",
"checkRunMode",
"requiredGate",
"note",
"warnings"
]
},
"queueHealth": {
"type": "object",
"properties": {
"level": {
"type": "string",
"enum": [
"low",
"medium",
"high",
"critical"
]
},
"burdenScore": {
"type": "number"
},
"reviewablePullRequests": {
"type": "number"
},
"summary": {
"type": "string"
}
},
"required": [
"level",
"burdenScore",
"reviewablePullRequests",
"summary"
]
},
"contributorIntakeHealth": {
"type": "object",
"additionalProperties": {
Expand All @@ -2607,6 +2726,59 @@
"nullable": true
}
},
"githubApp": {
"type": "object",
"properties": {
"installed": {
"type": "boolean"
},
"publicSurface": {
"type": "string",
"enum": [
"off",
"comment_and_label",
"comment_only",
"label_only"
]
},
"commentMode": {
"type": "string",
"enum": [
"off",
"detected_contributors_only",
"all_prs"
]
},
"checkRunMode": {
"type": "string",
"enum": [
"off",
"enabled"
]
},
"quietByDefault": {
"type": "boolean"
},
"behavior": {
"type": "string"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"installed",
"publicSurface",
"commentMode",
"checkRunMode",
"quietByDefault",
"behavior",
"warnings"
]
},
"blockers": {
"type": "array",
"items": {
Expand All @@ -2632,10 +2804,15 @@
"ready",
"recommendedRegistrationMode",
"issuePolicy",
"directPrReadiness",
"issueDiscoveryReadiness",
"labelPolicy",
"maintainerCutReadiness",
"testCoverageHealth",
"queueHealth",
"contributorIntakeHealth",
"docsCompleteness",
"githubApp",
"blockers",
"warnings",
"dataQuality"
Expand Down Expand Up @@ -2666,6 +2843,12 @@
"nullable": true
}
},
"tradeoffs": {
"type": "array",
"items": {
"type": "string"
}
},
"reasons": {
"type": "array",
"items": {
Expand All @@ -2691,6 +2874,7 @@
"privateOnly",
"current",
"recommended",
"tradeoffs",
"reasons",
"warnings",
"dataQuality"
Expand Down
119 changes: 36 additions & 83 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ import { buildContributorOpenPrMonitor } from "../signals/contributor-open-pr-mo
import { buildPullRequestReviewability } from "../signals/reward-risk";
import { buildLocalBranchAnalysis, findCurrentBranchPullRequest } from "../signals/local-branch";
import { buildRepoSettingsPreview } from "../signals/settings-preview";
import { buildGittensorConfigRecommendation, buildRegistrationReadiness, type InstallationHealthSummary } from "../signals/registration-readiness";
import { fileUpstreamDriftIssues, loadUpstreamStatus, refreshUpstreamDrift } from "../upstream/ruleset";
import type { BountyLifecycleEventRecord, ContributorEvidenceRecord, DataQuality, InstallationHealthRecord, JobMessage, JsonValue, RegistrySnapshot, RepoSyncSegmentRecord, RepositoryRecord, ScoringModelSnapshotRecord } from "../types";
import { errorMessage, nowIso } from "../utils/json";
Expand Down Expand Up @@ -1908,102 +1909,54 @@ async function buildIssueQualityResponse(env: Env, fullName: string) {
return loadOrComputeIssueQualityResponse(env, fullName);
}

async function loadInstallationHealthSummary(env: Env, repo: RepositoryRecord | null): Promise<InstallationHealthSummary | null> {
/* v8 ignore start -- Installation health loading is route-level glue over covered signal helpers. */
const installationId = repo?.installationId ?? null;
if (installationId === null) return null;
const healthRecord = await getInstallationHealth(env, installationId);
if (!healthRecord) return null;
const enriched = enrichInstallationHealth(healthRecord);
return { status: enriched.status, missingPermissions: enriched.missingPermissions, missingEvents: enriched.missingEvents };
/* v8 ignore stop */
}

async function buildRegistrationReadinessResponse(env: Env, fullName: string) {
/* v8 ignore start -- Registration readiness branches are route-level response shaping over covered signal helpers. */
/* v8 ignore start -- Registration readiness route-level shaping over covered signal helpers. */
const intelligence = await buildRepoIntelligenceResponse(env, fullName);
const settings = await getRepositorySettings(env, fullName);
const repo = intelligence.repo;
const configQuality = intelligence.configQuality as ReturnType<typeof buildConfigQuality>;
const maintainerCutReadiness = intelligence.maintainerCutReadiness as ReturnType<typeof buildMaintainerCutReadiness>;
const contributorIntakeHealth = intelligence.contributorIntakeHealth as ReturnType<typeof buildContributorIntakeHealth>;
const lane = buildLaneAdvice(repo, fullName);
const blockers = [
...(!repo?.isRegistered ? ["Repository is not registered in the latest Gittensory registry snapshot."] : []),
...(configQuality.level === "fragile" ? ["Repository config quality is fragile."] : []),
...(contributorIntakeHealth.level === "blocked" ? ["Contributor intake health is blocked."] : []),
];
const warnings = [
...(configQuality.level === "needs_attention" ? ["Repository config quality needs attention before registration promotion."] : []),
...(contributorIntakeHealth.level === "strained" ? ["Contributor intake is strained; expect more maintainer triage."] : []),
...(settings.publicSurface === "off" ? ["GitHub App public surface is disabled; maintainers will not get comment/label assistance."] : []),
];
const issuePolicy =
lane.lane === "issue_discovery"
? "issue_discovery_enabled"
: lane.lane === "split"
? "split_pr_and_issue_discovery_enabled"
: settings.requireLinkedIssue
? "direct_pr_requires_linked_issue"
: "direct_pr_no_issue_required";
const ready = blockers.length === 0 && !["fragile", "needs_attention"].includes(configQuality.level);
return {
const installation = await loadInstallationHealthSummary(env, repo);
const report = buildRegistrationReadiness({
repoFullName: fullName,
generatedAt: nowIso(),
ready,
recommendedRegistrationMode: lane.lane === "issue_discovery" ? "issue_discovery" : lane.lane === "split" ? "split" : "direct_pr",
issuePolicy,
labelPolicy: {
autoLabelEnabled: settings.autoLabelEnabled,
label: settings.gittensorLabel,
createMissingLabel: settings.createMissingLabel,
configuredRegistryLabels: configQuality.configuredLabels,
missingOrUnusedRegistryLabels: configQuality.notObservedConfiguredLabels,
},
maintainerCutReadiness,
contributorIntakeHealth,
docsCompleteness: {
status: "repo_docs_not_crawled",
requiredDocs: ["README", "CONTRIBUTING", "SECURITY", "SUPPORT"],
note: "Gittensory validates public repo docs from the local project during CI; remote repo-doc crawling is not enabled in this signal yet.",
},
blockers,
warnings,
dataQuality: intelligence.dataQuality,
};
repo,
settings,
lane: buildLaneAdvice(repo, fullName),
configQuality: intelligence.configQuality as ReturnType<typeof buildConfigQuality>,
labelAudit: intelligence.labelAudit as ReturnType<typeof buildLabelAudit>,
queueHealth: intelligence.queueHealth as ReturnType<typeof buildQueueHealth>,
maintainerCutReadiness: intelligence.maintainerCutReadiness as ReturnType<typeof buildMaintainerCutReadiness>,
contributorIntakeHealth: intelligence.contributorIntakeHealth as ReturnType<typeof buildContributorIntakeHealth>,
installation,
});
return { ...report, dataQuality: intelligence.dataQuality };
/* v8 ignore stop */
}

async function buildGittensorConfigRecommendationResponse(env: Env, fullName: string) {
/* v8 ignore start -- Config recommendation branches shape advisory output over covered signal helpers. */
/* v8 ignore start -- Config recommendation route-level shaping over covered signal helpers. */
const intelligence = await buildRepoIntelligenceResponse(env, fullName);
const settings = await getRepositorySettings(env, fullName);
const repo = intelligence.repo;
const lane = buildLaneAdvice(repo, fullName);
const configQuality = intelligence.configQuality as ReturnType<typeof buildConfigQuality>;
const contributorIntakeHealth = intelligence.contributorIntakeHealth as ReturnType<typeof buildContributorIntakeHealth>;
const maintainerCutReadiness = intelligence.maintainerCutReadiness as ReturnType<typeof buildMaintainerCutReadiness>;
const current = repo?.registryConfig ?? null;
const shouldEnableIssueDiscovery = contributorIntakeHealth.level === "healthy" && configQuality.level === "excellent";
const recommendedIssueDiscoveryShare = shouldEnableIssueDiscovery ? 0.1 : 0;
const currentAllocation = current?.emissionShare ?? 0;
const directPrShare = Math.max(0, currentAllocation - recommendedIssueDiscoveryShare);
const recommendedMaintainerCut = maintainerCutReadiness.ready ? Math.max(current?.maintainerCut ?? 0, 0.02) : current?.maintainerCut ?? 0;
return {
const recommendation = buildGittensorConfigRecommendation({
repoFullName: fullName,
generatedAt: nowIso(),
privateOnly: true,
current,
recommended: {
participationMode: recommendedIssueDiscoveryShare > 0 ? "split" : "direct_pr",
issueDiscoveryShare: recommendedIssueDiscoveryShare,
directPrShare,
maintainerCut: recommendedMaintainerCut,
requireLinkedIssue: settings.requireLinkedIssue,
labelMultipliers: configQuality.configuredLabels.length > 0 ? "keep_current_and_prune_unused" : "start_without_trusted_label_multipliers",
publicSurface: settings.publicSurface,
confirmedMinerLabel: settings.gittensorLabel,
},
reasons: [
lane.lane === "issue_discovery" ? "The current registry lane already routes meaningful work through issue discovery." : "Direct-PR mode is the safest default until issue-discovery intake is intentionally staffed.",
shouldEnableIssueDiscovery ? "Config and intake signals are strong enough to consider a small issue-discovery slice." : "Issue discovery should stay disabled until config quality and intake health are excellent.",
maintainerCutReadiness.ready ? "Maintainer cut can be considered because config and queue signals are clean." : "Maintainer cut should stay unchanged until readiness blockers are cleared.",
],
warnings: [
...(configQuality.notObservedConfiguredLabels.length > 0 ? [`${configQuality.notObservedConfiguredLabels.length} configured label(s) have not been observed in cached repo activity.`] : []),
...(contributorIntakeHealth.level === "strained" || contributorIntakeHealth.level === "blocked" ? [`Contributor intake is ${contributorIntakeHealth.level}; avoid increasing noisy lanes yet.`] : []),
],
dataQuality: intelligence.dataQuality,
};
repo,
settings,
lane: buildLaneAdvice(repo, fullName),
configQuality: intelligence.configQuality as ReturnType<typeof buildConfigQuality>,
contributorIntakeHealth: intelligence.contributorIntakeHealth as ReturnType<typeof buildContributorIntakeHealth>,
maintainerCutReadiness: intelligence.maintainerCutReadiness as ReturnType<typeof buildMaintainerCutReadiness>,
});
return { ...recommendation, dataQuality: intelligence.dataQuality };
/* v8 ignore stop */
}

Expand Down
Loading