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
120 changes: 120 additions & 0 deletions apps/gittensory-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -5746,6 +5746,125 @@
"risks"
]
},
"manifestGuidance": {
"type": "object",
"properties": {
"present": {
"type": "boolean"
},
"source": {
"type": "string",
"enum": [
"repo_file",
"api_record",
"none"
]
},
"linkedIssuePolicy": {
"type": "string",
"enum": [
"required",
"preferred",
"optional"
]
},
"issueDiscoveryPolicy": {
"type": "string",
"enum": [
"encouraged",
"neutral",
"discouraged"
]
},
"matchedWantedPaths": {
"type": "array",
"items": {
"type": "string"
}
},
"matchedBlockedPaths": {
"type": "array",
"items": {
"type": "string"
}
},
"preferredLabelHits": {
"type": "array",
"items": {
"type": "string"
}
},
"findings": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"severity": {
"type": "string",
"enum": [
"info",
"warning",
"critical"
]
},
"title": {
"type": "string"
},
"detail": {
"type": "string"
},
"action": {
"type": "string"
}
},
"required": [
"code",
"severity",
"title",
"detail"
]
}
},
"publicNextSteps": {
"type": "array",
"items": {
"type": "string"
}
},
"maintainerNotes": {
"type": "array",
"items": {
"type": "string"
}
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
},
"summary": {
"type": "string"
}
},
"required": [
"present",
"source",
"linkedIssuePolicy",
"issueDiscoveryPolicy",
"matchedWantedPaths",
"matchedBlockedPaths",
"preferredLabelHits",
"findings",
"publicNextSteps",
"maintainerNotes",
"warnings",
"summary"
]
},
"prPacket": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -5885,6 +6004,7 @@
"recommendedRerunCondition",
"localFindings",
"maintainerFit",
"manifestGuidance",
"prPacket",
"nextActions",
"workspaceIntelligence",
Expand Down
11 changes: 9 additions & 2 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ import { attachDataQuality, buildCoreSignalFidelity, buildFreshnessSloReport, bu
import { buildContributorOpenPrMonitor } from "../signals/contributor-open-pr-monitor";
import { buildPullRequestReviewability } from "../signals/reward-risk";
import { buildLocalBranchAnalysis, findCurrentBranchPullRequest } from "../signals/local-branch";
import { loadRepoFocusManifest } from "../signals/focus-manifest-loader";
import { buildRepoSettingsPreview } from "../signals/settings-preview";
import { buildGittensorConfigRecommendation, buildRegistrationReadiness, type InstallationHealthSummary } from "../signals/registration-readiness";
import { fileUpstreamDriftIssues, loadUpstreamStatus, refreshUpstreamDrift } from "../upstream/ruleset";
Expand Down Expand Up @@ -297,6 +298,7 @@ const localBranchAnalysisSchema = z
scenarioNotes: z.array(z.string().max(MAX_LOCAL_BRANCH_TEXT_CHARS)).max(20).optional(),
pendingCommitCount: z.number().int().min(0).optional(),
ciStatusHints: z.array(z.string().max(MAX_LOCAL_BRANCH_TEXT_CHARS)).max(20).optional(),
focusManifest: z.record(z.unknown()).optional(),
})
.strict();

Expand Down Expand Up @@ -1424,7 +1426,7 @@ export function createApp() {
if (!parsed.success) return c.json({ error: "invalid_local_branch_analysis_request", issues: parsed.error.issues }, 400);
const unauthorized = await requireContributorAccess(c, parsed.data.login);
if (unauthorized) return unauthorized;
const [context, repo, issues, pullRequests, recentMergedPullRequests, bounties, snapshot, issueQuality] = await Promise.all([
const [context, repo, issues, pullRequests, recentMergedPullRequests, bounties, snapshot, issueQuality, repoManifest] = await Promise.all([
loadContributorFastContext(c.env, parsed.data.login),
getRepository(c.env, parsed.data.repoFullName),
listIssues(c.env, parsed.data.repoFullName),
Expand All @@ -1433,12 +1435,17 @@ export function createApp() {
listBountiesByRepo(c.env, parsed.data.repoFullName),
getOrCreateScoringModelSnapshot(c.env),
loadOrComputeIssueQualityResponse(c.env, parsed.data.repoFullName),
loadRepoFocusManifest(c.env, parsed.data.repoFullName),
]);
const fit = buildContributorFit(context.profile, context.repositories, [], [], context.syncStates, context.repoStats);
const scoringProfile = buildContributorScoringProfile({ login: parsed.data.login, fit, scoringSnapshot: snapshot });
const checkSummaries = await loadCheckSummariesForPullRequests(c.env, parsed.data.repoFullName, parsed.data, pullRequests);
// Caller-supplied focusManifest wins; otherwise fall back to the repo-owned manifest when present.
const analysisInput = parsed.data.focusManifest !== undefined || !repoManifest.present
? parsed.data
: { ...parsed.data, focusManifest: repoManifest as unknown };
const analysis = buildLocalBranchAnalysis({
input: parsed.data,
input: analysisInput,
repo,
issues,
pullRequests,
Expand Down
11 changes: 9 additions & 2 deletions src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
} from "../signals/engine";
import { buildContributorOpenPrMonitor } from "../signals/contributor-open-pr-monitor";
import { buildLocalBranchAnalysis, findCurrentBranchPullRequest } from "../signals/local-branch";
import { loadRepoFocusManifest } from "../signals/focus-manifest-loader";
import { buildRepoDataQuality } from "../signals/data-quality";
import { loadUpstreamStatus } from "../upstream/ruleset";

Expand Down Expand Up @@ -163,6 +164,7 @@ const localBranchAnalysisShape = {
expectedOpenPrCountAfterMerge: z.number().int().min(0).optional(),
projectedCredibility: z.number().min(0).max(1).optional(),
scenarioNotes: z.array(z.string()).max(20).optional(),
focusManifest: z.record(z.unknown()).optional(),
localScorer: z
.object({
mode: z.enum(["metadata_only", "external_command", "gittensor_root"]),
Expand Down Expand Up @@ -931,7 +933,7 @@ export class GittensoryMcp {

private async analyzeLocalBranch(input: z.infer<z.ZodObject<typeof localBranchAnalysisShape>>) {
this.requireContributorAccess(input.login);
const [context, repo, issues, pullRequests, recentMergedPullRequests, bounties, snapshot, issueQuality] = await Promise.all([
const [context, repo, issues, pullRequests, recentMergedPullRequests, bounties, snapshot, issueQuality, repoManifest] = await Promise.all([
this.loadContributorFastContext(input.login),
getRepository(this.env, input.repoFullName),
listIssues(this.env, input.repoFullName),
Expand All @@ -940,13 +942,18 @@ export class GittensoryMcp {
listBountiesByRepo(this.env, input.repoFullName),
getOrCreateScoringModelSnapshot(this.env),
loadOrComputeIssueQualityResponse(this.env, input.repoFullName),
loadRepoFocusManifest(this.env, input.repoFullName),
]);
const fit = buildContributorFit(context.profile, context.repositories, [], [], context.syncStates, context.repoStats);
const scoringProfile = buildContributorScoringProfile({ login: input.login, fit, scoringSnapshot: snapshot });
const checkSummaries = await this.loadCheckSummariesForPullRequests(input.repoFullName, input, pullRequests);
// Caller-supplied focusManifest wins; otherwise fall back to the repo-owned manifest when present.
const analysisInput = input.focusManifest !== undefined || !repoManifest.present
? input
: { ...input, focusManifest: repoManifest as unknown };
return {
...buildLocalBranchAnalysis({
input,
input: analysisInput,
repo,
issues,
pullRequests,
Expand Down
14 changes: 14 additions & 0 deletions src/openapi/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,20 @@ export const LocalBranchAnalysisSchema = z
reasons: z.array(z.string()),
risks: z.array(z.string()),
}),
manifestGuidance: z.object({
present: z.boolean(),
source: z.enum(["repo_file", "api_record", "none"]),
linkedIssuePolicy: z.enum(["required", "preferred", "optional"]),
issueDiscoveryPolicy: z.enum(["encouraged", "neutral", "discouraged"]),
matchedWantedPaths: z.array(z.string()),
matchedBlockedPaths: z.array(z.string()),
preferredLabelHits: z.array(z.string()),
findings: z.array(z.object({ code: z.string(), severity: z.enum(["info", "warning", "critical"]), title: z.string(), detail: z.string(), action: z.string().optional() })),
publicNextSteps: z.array(z.string()),
maintainerNotes: z.array(z.string()),
warnings: z.array(z.string()),
summary: z.string(),
}),
prPacket: z.object({
titleSuggestion: z.string(),
markdown: z.string(),
Expand Down
10 changes: 8 additions & 2 deletions src/services/agent-orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { summarizeAgentBundleWithAi } from "./ai-summaries";
import { buildContributorFit, buildContributorOutcomeHistory, buildContributorProfile, buildContributorScoringProfile } from "../signals/engine";
import { buildContributorOpenPrMonitor, type ContributorOpenPrMonitor } from "../signals/contributor-open-pr-monitor";
import { buildLocalBranchAnalysis, findCurrentBranchPullRequest, type LocalBranchAnalysis, type LocalBranchAnalysisInput } from "../signals/local-branch";
import { loadRepoFocusManifest } from "../signals/focus-manifest-loader";
import type {
AgentActionRecord,
AgentActionStatus,
Expand Down Expand Up @@ -290,7 +291,7 @@ async function executeLocalBranchRun(env: Env, run: AgentRunRecord, kind: string
}

async function analyzeLocalBranch(env: Env, input: LocalBranchAnalysisInput): Promise<LocalBranchAnalysis & { dataQuality?: { status: "complete" | "degraded" | "blocked" | "unknown"; warnings: string[] } }> {
const [github, contributorPullRequests, contributorIssues, repositories, syncStates, cachedRepoStats, gittensorSnapshot, repo, issues, pullRequests, recentMergedPullRequests, bounties, scoringSnapshot, issueQuality] =
const [github, contributorPullRequests, contributorIssues, repositories, syncStates, cachedRepoStats, gittensorSnapshot, repo, issues, pullRequests, recentMergedPullRequests, bounties, scoringSnapshot, issueQuality, repoManifest] =
await Promise.all([
fetchPublicContributorProfile(input.login),
listContributorPullRequests(env, input.login),
Expand All @@ -306,15 +307,20 @@ async function analyzeLocalBranch(env: Env, input: LocalBranchAnalysisInput): Pr
listBountiesByRepo(env, input.repoFullName),
getOrCreateScoringModelSnapshot(env),
loadOrComputeIssueQualityResponse(env, input.repoFullName),
loadRepoFocusManifest(env, input.repoFullName),
]);
const repoStats = contributorRepoStatsFromGittensor(gittensorSnapshot).length > 0 ? contributorRepoStatsFromGittensor(gittensorSnapshot) : cachedRepoStats;
const profile = buildContributorProfile(input.login, github, contributorPullRequests, contributorIssues, repoStats, gittensorSnapshot);
const outcomeHistory = buildContributorOutcomeHistory({ login: input.login, profile, repositories, pullRequests: contributorPullRequests, issues: contributorIssues, repoStats, cachedRepoStats });
const fit = buildContributorFit(profile, repositories, [], [], syncStates, repoStats);
const scoringProfile = buildContributorScoringProfile({ login: input.login, fit, scoringSnapshot });
const checkSummaries = await loadCheckSummariesForPullRequests(env, input.repoFullName, input, pullRequests);
// Caller-supplied focusManifest wins; otherwise fall back to the repo-owned manifest when present.
const analysisInput = input.focusManifest !== undefined || !repoManifest.present
? input
: { ...input, focusManifest: repoManifest as unknown };
return buildLocalBranchAnalysis({
input,
input: analysisInput,
repo,
issues,
pullRequests,
Expand Down
Loading