diff --git a/src/api/routes.ts b/src/api/routes.ts index cf5904d7c4..b5e6c22afe 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -121,6 +121,7 @@ import { refreshInstallationHealthForInstallation, } from "../github/backfill"; import { getRepositoryCollaboratorPermission } from "../github/app"; +import type { GittensoryFooterEnv } from "../github/footer"; import { contributorRepoStatsFromGittensor, fetchGittensorContributorSnapshot } from "../gittensor/api"; import { fetchPublicContributorProfile, fetchPublicRepoStats } from "../github/public"; import { @@ -1701,7 +1702,7 @@ export function createApp() { if (repoForbidden) return repoForbidden; const installationId = repo?.installationId ?? null; const installation = installationId !== null ? await getInstallationHealth(c.env, installationId) : null; - const preview = buildCommandPreview(command, parsed.data, { repo, installation, pullRequest }); + const preview = buildCommandPreview(command, parsed.data, { repo, installation, pullRequest, env: c.env }); await recordRouteProductUsage(c, { surface: "control_panel", eventName: "command_previewed", @@ -2690,6 +2691,7 @@ export function createApp() { issues, pullRequests, sample: parsed.data.sample ?? {}, + env: c.env, }), ); }); @@ -4262,7 +4264,7 @@ type CommandPreviewDecision = { function buildCommandPreview( command: (typeof APP_COMMANDS)[number], request: z.infer, - context: { repo: RepositoryRecord | null; installation: InstallationHealthRecord | null; pullRequest: PullRequestRecord | null }, + context: { repo: RepositoryRecord | null; installation: InstallationHealthRecord | null; pullRequest: PullRequestRecord | null; env: GittensoryFooterEnv }, ) { const target = request.repoFullName ? `${request.repoFullName}${request.pullNumber ? `#${request.pullNumber}` : ""}` : "selected target"; const mentionCommandName = previewableMentionCommandName(command.id); @@ -4380,6 +4382,7 @@ function buildCommandPreview( confirmedMinerLogins: sample.minerStatus === "confirmed" ? [sample.authorLogin] : [], }) : null, + env: context.env, }); return { diff --git a/src/github/commands.ts b/src/github/commands.ts index 57d6b9d142..b4f9d662e9 100644 --- a/src/github/commands.ts +++ b/src/github/commands.ts @@ -4,7 +4,7 @@ import { suggestCommand as suggestCommandFromCatalog, type CommandSuggestCatalog, } from "./command-suggest"; -import { gittensoryFooter, GITTENSORY_SITE_URL } from "./footer"; +import { gittensoryFooter, GITTENSORY_SITE_URL, type GittensoryFooterEnv } from "./footer"; import type { AgentRunBundle } from "../services/agent-orchestrator"; import type { GittensorContributorSnapshot, OfficialGittensorMinerDetection } from "../gittensor/api"; import type { AgentActionRecord, RepositoryCommandAuthorizationPolicy } from "../types"; @@ -362,6 +362,8 @@ export function buildPublicAgentCommandComment(args: { officialMiner?: GittensorContributorSnapshot | null | undefined; bundle?: AgentRunBundle | null | undefined; maintainerDigest?: MaintainerQueueDigest | null | undefined; + /** Resolved by the caller from `env.PUBLIC_SITE_ORIGIN` -- see `gittensoryFooter` (#4613). */ + env: GittensoryFooterEnv; }): string { const repoFullName = args.repo?.fullName ?? args.pullRequest?.repoFullName ?? "this repository"; // Action commands (e.g. gate-override) never reach this Q&A renderer — they are handled and short-circuited @@ -407,7 +409,7 @@ export function buildPublicAgentCommandComment(args: { ...feedbackPromptSections(args.answerId), "", "---", - gittensoryFooter(), + gittensoryFooter(args.env), ].join("\n"); return sanitizePublicComment(body); } diff --git a/src/github/footer.ts b/src/github/footer.ts index d8e9e380f7..317ac60819 100644 --- a/src/github/footer.ts +++ b/src/github/footer.ts @@ -8,9 +8,17 @@ // scoreability out of public output). This footer uses ONLY "earn" — a factual, public invitation, // not a payout guarantee or a private-score disclosure. -/** The Gittensory product site (marketing on-ramp / attribution target). */ +/** The Gittensory product site (marketing on-ramp / attribution target) -- the DEFAULT only. A + * self-hoster with `PUBLIC_SITE_ORIGIN` set gets their own domain instead, both here and in + * `gittensoryFooter` below (#4613). */ export const GITTENSORY_SITE_URL = "https://gittensory.aethereal.dev"; +/** Minimal env slice `gittensoryFooter` needs, narrowed from the full `Env` the same way this file's + * `maintainerControlPanelUrl` already narrows its own `env` param inline -- so every file that renders + * the footer only has to thread this one field down from wherever the real `Env` is in scope, not the + * whole worker binding type. */ +export type GittensoryFooterEnv = { PUBLIC_SITE_ORIGIN?: string | undefined }; + /** The maintainer control panel for a repo on the Gittensory site (`/app?view=maintainer&repo=…`). Used as the * check-run `details_url` so the merge-box "Details" link lands on the repo's review panel instead of GitHub's * generic check page, and as the in-comment control-panel link. Returns null only if URL construction throws. */ @@ -39,8 +47,12 @@ export function gittensorRepoEarnUrl(repoFullName: string): string { * appears on EVERY reviewed PR (the link persists forever), so non-registered authors see the * 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; customText?: string | undefined } = {}): string { + * Uses only "earn" wording — never reward/payout/score (forbidden in public comments). + * `env.PUBLIC_SITE_ORIGIN` (same resolution as `maintainerControlPanelUrl` above) lets a self-hoster's + * own domain replace `GITTENSORY_SITE_URL` in the "Checked by Gittensory" attribution link (#4613) -- + * the Gittensor register link (`GITTENSOR_HOME_URL`) is a separate, shared network and is never rebranded. */ +export function gittensoryFooter(env: GittensoryFooterEnv, opts: { earnUrl?: string | undefined; customText?: string | undefined } = {}): string { + const siteUrl = env.PUBLIC_SITE_ORIGIN ?? GITTENSORY_SITE_URL; 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 @@ -49,12 +61,12 @@ export function gittensoryFooter(opts: { earnUrl?: string | undefined; customTex 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}).`, + `[Gittensor](${GITTENSOR_HOME_URL}) lets GitHub contributors earn for the work they already do — [register to start earning →](${earnUrl}). Checked by [Gittensory](${siteUrl}).`, ].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}).`, "", - `Checked by [Gittensory](${GITTENSORY_SITE_URL}), a quiet PR intelligence layer for OSS maintainers.`, + `Checked by [Gittensory](${siteUrl}), a quiet PR intelligence layer for OSS maintainers.`, ].join("\n"); } diff --git a/src/github/repo-doc-pr.ts b/src/github/repo-doc-pr.ts index 2e28789f5f..b36e2dbb9f 100644 --- a/src/github/repo-doc-pr.ts +++ b/src/github/repo-doc-pr.ts @@ -27,6 +27,7 @@ // the skill from this run rather than blocking the AGENTS.md refresh it rode in with. import { githubErrorStatus, withInstallationTokenRetry } from "./app"; import { githubRateLimitAdmissionKeyForInstallation, makeInstallationOctokit } from "./client"; +import { GITTENSORY_SITE_URL } from "./footer"; import { getRepository } from "../db/repositories"; import { loadRepoFocusManifest } from "../signals/focus-manifest-loader"; import { extractRepoProfile } from "../review/repo-profile"; @@ -148,7 +149,10 @@ export async function openRepoDocPullRequest(env: Env, repoFullName: string, mod const profile = await extractRepoProfile(env, repoFullName); if (!profile.present) return { opened: false, reason: profile.reason }; - const generatedSection = renderRepoDocContent(profile); + // #4613: a self-hoster's own domain (env.PUBLIC_SITE_ORIGIN) reaches the generated AGENTS.md's + // attribution link instead of gittensory.aethereal.dev -- same fallback `maintainerControlPanelUrl`/ + // `gittensoryFooter` already use. + const generatedSection = renderRepoDocContent(profile, env.PUBLIC_SITE_ORIGIN ?? GITTENSORY_SITE_URL); if (!generatedSection) return { opened: false, reason: "no content rendered from profile" }; if (mode !== "live") return { opened: false, reason: `repo-doc pull request not opened: action mode is "${mode}"` }; diff --git a/src/queue/processors.ts b/src/queue/processors.ts index 88a46baeea..6bc66e39cd 100644 --- a/src/queue/processors.ts +++ b/src/queue/processors.ts @@ -11058,6 +11058,7 @@ async function maybePublishPrPublicSurface( review: reviewConfig, aiReview, duplicateWinnerEnabled, + env, }; let deterministicBody: string; // Convergence (Stage D): when the unified-review-comment flag is ON, render the single converged comment @@ -11375,7 +11376,7 @@ async function maybePublishPrPublicSurface( ...(missingTestsFinding !== undefined ? { missingTestsFinding } : {}), e2eTestGenAvailable, }), - footerMarkdown: gittensoryFooter({ + footerMarkdown: gittensoryFooter(env, { earnUrl: repo?.isRegistered ? gittensorRepoEarnUrl(repoFullName) : undefined, @@ -11794,7 +11795,7 @@ async function maybeProcessGateOverrideCommand( `- Reason: ${safeReason}`, "", "---", - gittensoryFooter(), + gittensoryFooter(env), ].join("\n"), ); await createOrUpdateAgentCommandComment( @@ -11922,7 +11923,7 @@ async function maybeProcessResolveCommand(env: Env, deliveryId: string, payload: let recordedSuppressionCount = 0; if (reviewMemoryEnabled && selection.findings.length > 0) { const { fingerprint } = await import("../review/review-memory-match"); const { recordReviewSuppression } = await import("../db/repositories"); const suppressionWrites = selection.findings.map((finding) => ({ category: finding.code, pathGlob: "", patternHash: fingerprint({ category: finding.code, message: `${finding.title} ${finding.detail}` }) })); await Promise.all(suppressionWrites.map((write) => recordReviewSuppression(env, { repoFullName: req.repoFullName, category: write.category, pathGlob: write.pathGlob, patternHash: write.patternHash, createdBy: req.actor }))); recordedSuppressionCount = suppressionWrites.length; invalidateReviewSuppressionCache(req.repoFullName); /* #4508: this repo's cached suppression list is stale as of this write -- the very next render must see it, not wait out the TTL. */ await recordAuditEvent(env, { eventType: "github_app.review_memory_recorded", actor: req.actor, targetKey, outcome: "completed", detail: `Recorded ${recordedSuppressionCount} review-memory suppression signal(s).`, metadata: { deliveryId, repoFullName: req.repoFullName, recordedSuppressionCount, scope: findingRef.scope, ...(findingRef.scope === "single" ? { findingCode: findingRef.findingCode } : {}) } }); await recordGithubProductUsage(env, "review_memory_recorded", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "completed", metadata: { recordedSuppressionCount, scope: findingRef.scope, ...(findingRef.scope === "single" ? { findingCode: findingRef.findingCode } : {}) } }); } const resolvedLabel = findingRef.scope === "whole_pr" ? "all current advisory findings" : `\`${findingRef.findingCode}\``; - const confirmation = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **Review finding resolved by @${req.actor}**`, `> Marked ${resolvedLabel} as resolved for this PR. The Gate check-run is unchanged.`, ...(recordedSuppressionCount > 0 ? ["", `Recorded ${recordedSuppressionCount} review-memory suppression signal(s) for future reviews.`] : []), "", "---", gittensoryFooter()].join("\n")); + const confirmation = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **Review finding resolved by @${req.actor}**`, `> Marked ${resolvedLabel} as resolved for this PR. The Gate check-run is unchanged.`, ...(recordedSuppressionCount > 0 ? ["", `Recorded ${recordedSuppressionCount} review-memory suppression signal(s) for future reviews.`] : []), "", "---", gittensoryFooter(env)].join("\n")); await createOrUpdateAgentCommandComment(env, req.installationId, req.repoFullName, req.pr.number, confirmation, mode); await recordAuditEvent(env, { eventType: "github_app.finding_resolved", actor: req.actor, targetKey, outcome: "completed", detail: `Marked ${resolvedLabel} as resolved.`, metadata: { deliveryId, repoFullName: req.repoFullName, scope: findingRef.scope, resolvedWarningCount: selection.findings.length, recordedSuppressionCount, ...(findingRef.scope === "single" ? { findingCode: findingRef.findingCode } : {}) } }); await recordGithubProductUsage(env, "finding_resolved", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "completed", metadata: { scope: findingRef.scope, resolvedWarningCount: selection.findings.length, recordedSuppressionCount, ...(findingRef.scope === "single" ? { findingCode: findingRef.findingCode } : {}) } }); return true; } @@ -11972,7 +11973,7 @@ async function maybeProcessReviewCommand(env: Env, deliveryId: string, payload: await recordReviewCommandSkip(env, deliveryId, req.repoFullName, targetKey, req.actor, mode === "dry_run" ? "dry_run" : "agent_paused"); return true; } - const confirmation = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **Re-review triggered by @${req.actor}**`, "> Re-running auto-review for this PR. The Gate check-run and one-shot disposition are produced the same way a scheduled pass would.", "", "---", gittensoryFooter()].join("\n")); + const confirmation = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **Re-review triggered by @${req.actor}**`, "> Re-running auto-review for this PR. The Gate check-run and one-shot disposition are produced the same way a scheduled pass would.", "", "---", gittensoryFooter(env)].join("\n")); await createIssueComment(env, req.installationId, req.repoFullName, req.pr.number, confirmation); const forceFreshReview = authorization.actorKind === "maintainer"; await reReviewStoredPullRequest(env, deliveryId, req.installationId, req.repoFullName, req.pr.number, undefined, forceFreshReview ? { force: true } : undefined); @@ -12022,7 +12023,7 @@ async function maybeProcessPauseCommand(env: Env, deliveryId: string, payload: G return true; } const safeReason = sanitizePublicComment((command.reason ?? "").trim() || "No reason provided."); - const confirmation = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **Auto-review paused by @${req.actor}**`, "> Auto-review is paused for this PR only. Gate enforcement and the one-shot disposition are unchanged; use `@gittensory resume` to re-enable auto-review.", "", `- Reason: ${safeReason}`, "", "---", gittensoryFooter()].join("\n")); + const confirmation = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **Auto-review paused by @${req.actor}**`, "> Auto-review is paused for this PR only. Gate enforcement and the one-shot disposition are unchanged; use `@gittensory resume` to re-enable auto-review.", "", `- Reason: ${safeReason}`, "", "---", gittensoryFooter(env)].join("\n")); await createIssueComment(env, req.installationId, req.repoFullName, req.pr.number, confirmation); await recordAuditEvent(env, { eventType: "github_app.autoreview_paused", actor: req.actor, targetKey, outcome: "completed", detail: safeReason, metadata: { deliveryId, repoFullName: req.repoFullName } }); await recordGithubProductUsage(env, "autoreview_paused", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "completed", metadata: { actorKind: authorization.actorKind } }); @@ -12064,7 +12065,7 @@ async function maybeProcessResumeCommand(env: Env, deliveryId: string, payload: await recordGithubProductUsage(env, "autoreview_resumed_denied", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "denied", metadata: { reason: authorization.reason, actorKind: authorization.actorKind, allowedRoles: commandAuthorizationAllowedRoles(settings.commandAuthorization, "resume") } }); return true; } - const confirmation = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **Auto-review resumed by @${req.actor}**`, "> Auto-review is resumed for this PR. Gate enforcement and the one-shot disposition were never affected by pause.", "", "---", gittensoryFooter()].join("\n")); + const confirmation = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **Auto-review resumed by @${req.actor}**`, "> Auto-review is resumed for this PR. Gate enforcement and the one-shot disposition were never affected by pause.", "", "---", gittensoryFooter(env)].join("\n")); await createIssueComment(env, req.installationId, req.repoFullName, req.pr.number, confirmation); await recordAuditEvent(env, { eventType: "github_app.autoreview_resumed", actor: req.actor, targetKey, outcome: "completed", detail: "Auto-review resumed.", metadata: { deliveryId, repoFullName: req.repoFullName } }); await recordGithubProductUsage(env, "autoreview_resumed", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "completed", metadata: { actorKind: authorization.actorKind } }); @@ -12146,7 +12147,7 @@ async function maybeProcessExplainCommand(env: Env, deliveryId: string, payload: const gate = evaluateGateCheck(advisory, gateCheckPolicy(settings, null, undefined, pr.slopRisk ?? null)); const selection = selectWarningsForResolve(gate.warnings, findingRef); if (selection.reason === "finding_not_found") { - const notFound = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **No review finding \`${findingRef.findingCode}\` on this PR**`, "> That id is not among this PR's current review findings — re-run `@gittensory explain ` with an id from the review summary.", "", "---", gittensoryFooter()].join("\n")); + const notFound = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **No review finding \`${findingRef.findingCode}\` on this PR**`, "> That id is not among this PR's current review findings — re-run `@gittensory explain ` with an id from the review summary.", "", "---", gittensoryFooter(env)].join("\n")); await createIssueComment(env, req.installationId, req.repoFullName, req.pr.number, notFound); await recordFindingExplainedSkip(env, deliveryId, req.repoFullName, targetKey, req.actor, "finding_not_found"); return true; @@ -12166,7 +12167,7 @@ async function maybeProcessExplainCommand(env: Env, deliveryId: string, payload: "", ]), "---", - gittensoryFooter(), + gittensoryFooter(env), ].join("\n"), ); await createIssueComment(env, req.installationId, req.repoFullName, req.pr.number, body); @@ -12324,7 +12325,7 @@ async function runE2eTestGenerationAndDeliver( } } - const body = buildE2eTestGenCommentBody({ actor: args.actor, testSource, commit: commitOutcome }); + const body = buildE2eTestGenCommentBody({ actor: args.actor, testSource, commit: commitOutcome, env }); try { await createIssueComment(env, args.installationId, args.repoFullName, args.pr.number, sanitizePublicComment(body)); } catch (error) { @@ -12337,7 +12338,7 @@ async function runE2eTestGenerationAndDeliver( args.installationId, args.repoFullName, args.pr.number, - sanitizePublicComment(buildE2eTestGenCommentBody({ actor: args.actor, testSource: null })), + sanitizePublicComment(buildE2eTestGenCommentBody({ actor: args.actor, testSource: null, env })), ); console.log(JSON.stringify({ event: "e2e_test_gen_comment_withheld", repoFullName: args.repoFullName, pr: args.pr.number, error: errorMessage(error) })); } @@ -12365,7 +12366,7 @@ async function postGenerateTestsNotEnabledComment(env: Env, installationId: numb "> Ask a maintainer to enable `features.e2eTests` in `.gittensory.yml` (the operator's global flag must also be on).", "", "---", - gittensoryFooter(), + gittensoryFooter(env), ].join("\n"), ); await createIssueComment(env, installationId, repoFullName, prNumber, body); @@ -12436,7 +12437,7 @@ async function maybeProcessConfigurationCommand( agentDryRun: settings.agentDryRun, }); const body = sanitizePublicComment( - [AGENT_COMMAND_COMMENT_MARKER, "", summarizeEffectiveConfig(settings, mode), "", "---", gittensoryFooter()].join("\n"), + [AGENT_COMMAND_COMMENT_MARKER, "", summarizeEffectiveConfig(settings, mode), "", "---", gittensoryFooter(env)].join("\n"), ); await createOrUpdateAgentCommandComment(env, req.installationId, req.repoFullName, req.issueNumber, body, mode); await recordAuditEvent(env, { @@ -12598,6 +12599,7 @@ async function maybeProcessPlanCommand( actor: req.actor, repoFullName: req.repoFullName, issueNumber: req.issue.number, + env, }), ); await recordAuditEvent(env, { @@ -14908,6 +14910,7 @@ async function maybeProcessGittensoryMentionCommand( officialMiner: official?.status === "confirmed" ? official.snapshot : null, bundle, maintainerDigest, + env, }); const responseComment = await createOrUpdateAgentCommandComment( env, diff --git a/src/review/e2e-test-gen-render.ts b/src/review/e2e-test-gen-render.ts index f3760c0e93..9714cebcdd 100644 --- a/src/review/e2e-test-gen-render.ts +++ b/src/review/e2e-test-gen-render.ts @@ -10,7 +10,7 @@ // the test source is plausible Playwright before this ever sees it, and that #4195's caller already // resolved authorization — this file only turns already-decided content into a public-safe comment body. import { AGENT_COMMAND_COMMENT_MARKER } from "../github/comments"; -import { gittensoryFooter } from "../github/footer"; +import { gittensoryFooter, type GittensoryFooterEnv } from "../github/footer"; /** Outcome of an attempted `commit`-mode delivery (#4197), or its absence entirely (comment-only mode, or * generation itself produced nothing usable — see `buildE2eTestGenCommentBody`'s own null-testSource @@ -29,6 +29,8 @@ export type E2eTestGenCommentInput = { /** Present only when `commit` delivery mode was configured AND generation produced a usable test. Absent * for comment-only delivery — the generated test always renders as a suggestion in that case. */ commit?: E2eTestGenCommitOutcome | undefined; + /** Resolved by the caller from `env.PUBLIC_SITE_ORIGIN` -- see `gittensoryFooter` (#4613). */ + env: GittensoryFooterEnv; }; function markdownFenceFor(source: string): string { @@ -56,7 +58,7 @@ export function buildE2eTestGenCommentBody(input: E2eTestGenCommentInput): strin `> The model's output didn't parse as valid ${framework} source — try again, or add the test by hand.`, "", "---", - gittensoryFooter(), + gittensoryFooter(input.env), ].join("\n"); } if (input.commit?.status === "committed") { @@ -68,7 +70,7 @@ export function buildE2eTestGenCommentBody(input: E2eTestGenCommentInput): strin `> [View the commit](${input.commit.htmlUrl}) (\`${input.commit.commitSha.slice(0, 7)}\`). This is a suggestion, not a guarantee — review it like any other test before merging.`, "", "---", - gittensoryFooter(), + gittensoryFooter(input.env), ].join("\n"); } const declineNote = @@ -94,6 +96,6 @@ export function buildE2eTestGenCommentBody(input: E2eTestGenCommentInput): strin fence, "", "---", - gittensoryFooter(), + gittensoryFooter(input.env), ].join("\n"); } diff --git a/src/review/planner.ts b/src/review/planner.ts index 34bbba7008..d47140caa8 100644 --- a/src/review/planner.ts +++ b/src/review/planner.ts @@ -14,7 +14,7 @@ import { type AiReviewActualUsage, BEST_REVIEW_MODELS, clampNumber, coerceAiText import { recordAiUsageEvent, sumAiEstimatedNeuronsSince } from "../db/repositories"; import { sanitizePublicComment } from "../github/commands"; import { AGENT_COMMAND_COMMENT_MARKER } from "../github/comments"; -import { gittensoryFooter } from "../github/footer"; +import { gittensoryFooter, type GittensoryFooterEnv } from "../github/footer"; import type { GitHubWebhookPayload } from "../types"; /** True when the issue-planning command is enabled. Flag-OFF (default) → every export below is unreachable from @@ -153,7 +153,10 @@ export async function generateIssuePlan( /** Render the generated plan into a public-safe issue comment. Sanitized at the boundary so the posted body can * never carry private terms even if the model emitted them. */ -export function buildIssuePlanComment(plan: string, args: { actor: string; repoFullName: string; issueNumber: number }): string { +export function buildIssuePlanComment( + plan: string, + args: { actor: string; repoFullName: string; issueNumber: number; env: GittensoryFooterEnv }, +): string { return sanitizePublicComment( [ AGENT_COMMAND_COMMENT_MARKER, @@ -170,7 +173,7 @@ export function buildIssuePlanComment(plan: string, args: { actor: string; repoF plan, "", "---", - gittensoryFooter(), + gittensoryFooter(args.env), ].join("\n"), ); } diff --git a/src/review/repo-doc-render.ts b/src/review/repo-doc-render.ts index e47d368e26..e872e1bba7 100644 --- a/src/review/repo-doc-render.ts +++ b/src/review/repo-doc-render.ts @@ -68,14 +68,20 @@ function renderCiWorkflowFiles(ciWorkflowFiles: string[]): string { * (src/review/generated-doc-refresh.ts) it is exactly the span that gets recomputed -- anything a maintainer * adds before the start marker or after the end marker in the delivered file is never part of this output and * is therefore never touched. + * + * `siteUrl` is resolved by the caller from `env.PUBLIC_SITE_ORIGIN ?? GITTENSORY_SITE_URL` (#4613), so a + * self-hoster's own domain reaches the attribution link instead of `gittensory.aethereal.dev`. Threading it as a + * plain string argument (rather than an `env` slice) keeps this function's purity/determinism contract intact -- + * the SAME `(profile, siteUrl)` pair always renders the SAME output, which is all the diff-aware refresh in + * `generated-doc-refresh.ts` needs for its byte-for-byte comparison. */ -export function renderRepoDocContent(profile: RepoProfile): string | null { +export function renderRepoDocContent(profile: RepoProfile, siteUrl: string): string | null { if (!profile.present) return null; const { architecture, conventions, commands, contributionWorkflow } = profile; return `${REPO_DOC_MARKER_START} # AGENTS.md -This file is generated by [Gittensory](https://gittensory.aethereal.dev) from a profile of this repository's own +This file is generated by [Gittensory](${siteUrl}) from a profile of this repository's own code -- it is not hand-written and not a generic template. Content between the markers above and below this line is recomputed on every refresh; add anything you want kept forever outside them instead. diff --git a/src/signals/engine.ts b/src/signals/engine.ts index 23aaec638d..37f18fe23f 100644 --- a/src/signals/engine.ts +++ b/src/signals/engine.ts @@ -20,7 +20,7 @@ import type { ScoringModelSnapshotRecord, } from "../types"; import type { PublicContributorProfile } from "../github/public"; -import { gittensoryFooter, gittensorRepoEarnUrl } from "../github/footer"; +import { gittensoryFooter, gittensorRepoEarnUrl, type GittensoryFooterEnv } from "../github/footer"; import type { FocusManifestReviewConfig, ReviewFieldKey } from "./focus-manifest"; import type { GittensorContributorSnapshot } from "../gittensor/api"; import { nowIso } from "../utils/json"; @@ -4294,6 +4294,9 @@ export function buildPublicPrIntelligenceComment(args: { * claimant among `linkedDuplicatePrs`, the hard-duplicate panel block is suppressed so the winner's panel * does not show a blocking duplicate. Default/false ⇒ byte-identical to today. */ duplicateWinnerEnabled?: boolean | undefined; + /** Resolved by the caller from `env.PUBLIC_SITE_ORIGIN` so a self-hoster's own domain reaches the + * always-on footer's attribution link instead of `GITTENSORY_SITE_URL` (#4613). */ + env: GittensoryFooterEnv; }): string { const publicFindings = publicSafePreflightFindings(args.preflight, args.settings); const relatedWork = buildDuplicateWinnerRelatedWorkView({ @@ -4424,7 +4427,7 @@ export function buildPublicPrIntelligenceComment(args: { // path to register); for an unregistered repo it falls back to the general Gittensor home URL. // 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 }); + const footer = gittensoryFooter(args.env, { earnUrl: footerEarnUrl(args.repo, args.pr.repoFullName), customText: args.review?.footerText ?? undefined }); return [ "", "", @@ -4509,7 +4512,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; review?: FocusManifestReviewConfig | undefined }): string { +function buildMinimalInviteComment(args: { repo: RepositoryRecord | null; pr: PullRequestRecord; review?: FocusManifestReviewConfig | undefined; env: GittensoryFooterEnv }): string { return [ "", "", @@ -4520,7 +4523,7 @@ function buildMinimalInviteComment(args: { repo: RepositoryRecord | null; pr: Pu ]), "", "---", - gittensoryFooter({ earnUrl: footerEarnUrl(args.repo, args.pr.repoFullName), customText: args.review?.footerText ?? undefined }), + gittensoryFooter(args.env, { earnUrl: footerEarnUrl(args.repo, args.pr.repoFullName), customText: args.review?.footerText ?? undefined }), ].join("\n"); } diff --git a/src/signals/settings-preview.ts b/src/signals/settings-preview.ts index eff163cfab..cad577bf06 100644 --- a/src/signals/settings-preview.ts +++ b/src/signals/settings-preview.ts @@ -14,6 +14,7 @@ import { type ContributorDetection, } from "./engine"; import { REQUIRED_INSTALLATION_PERMISSIONS } from "../github/backfill"; +import type { GittensoryFooterEnv } from "../github/footer"; import { GITTENSORY_GATE_CHECK_NAME, shouldPublishReviewCheck } from "../review/check-names"; import { decideReviewEligibility } from "../review/review-eligibility"; import { requiredAgentActionPermissions } from "../settings/agent-execution"; @@ -259,6 +260,8 @@ export function buildRepoSettingsPreview(args: { issues: IssueRecord[]; pullRequests: PullRequestRecord[]; sample: PublicSurfaceSample; + /** Resolved by the caller from `env.PUBLIC_SITE_ORIGIN` -- see `gittensoryFooter` (#4613). */ + env: GittensoryFooterEnv; }): RepoSettingsPreview { const { settings, repo, repoFullName } = args; const sample = { @@ -280,7 +283,7 @@ export function buildRepoSettingsPreview(args: { }); const previewComment = decision.willComment - ? buildSamplePreviewComment({ repoFullName, repo, settings, issues: args.issues, pullRequests: args.pullRequests, sample, body: args.sample.body ?? null }) + ? buildSamplePreviewComment({ repoFullName, repo, settings, issues: args.issues, pullRequests: args.pullRequests, sample, body: args.sample.body ?? null, env: args.env }) : null; const warnings = buildWarnings(settings, decision, args.installation); @@ -591,6 +594,7 @@ function buildSamplePreviewComment(args: { pullRequests: PullRequestRecord[]; sample: { authorLogin: string; authorAssociation: string; minerStatus: "confirmed" | "not_found" | "unavailable"; title: string; labels: string[]; linkedIssues: number[] }; body: string | null; + env: GittensoryFooterEnv; }): string { const samplePr: PullRequestRecord = { repoFullName: args.repoFullName, @@ -621,5 +625,5 @@ function buildSamplePreviewComment(args: { args.issues, args.pullRequests, ); - return buildPublicPrIntelligenceComment({ repo: args.repo, pr: samplePr, profile, detection, queueHealth, collisions, preflight, settings: args.settings }); + return buildPublicPrIntelligenceComment({ repo: args.repo, pr: samplePr, profile, detection, queueHealth, collisions, preflight, settings: args.settings, env: args.env }); } diff --git a/test/unit/agent-orchestrator.test.ts b/test/unit/agent-orchestrator.test.ts index 9f7ab7e53a..87e2bb72af 100644 --- a/test/unit/agent-orchestrator.test.ts +++ b/test/unit/agent-orchestrator.test.ts @@ -263,6 +263,7 @@ describe("agent orchestrator", () => { pullRequest: null, actorKind: "maintainer", bundle: publicPlan, + env, }); expect(publicPlan.actions).toHaveLength(0); diff --git a/test/unit/e2e-test-gen-render.test.ts b/test/unit/e2e-test-gen-render.test.ts index e782845ade..60656b4fe4 100644 --- a/test/unit/e2e-test-gen-render.test.ts +++ b/test/unit/e2e-test-gen-render.test.ts @@ -1,10 +1,11 @@ import { describe, expect, it } from "vitest"; import { buildE2eTestGenCommentBody } from "../../src/review/e2e-test-gen-render"; import { PR_PANEL_COMMENT_MARKER } from "../../src/github/comments"; +import { GITTENSORY_SITE_URL } from "../../src/github/footer"; describe("buildE2eTestGenCommentBody", () => { it("renders the generated test source in a fenced code block, defaulting the framework to Playwright", () => { - const body = buildE2eTestGenCommentBody({ actor: "maintainer", testSource: "test('x', () => {});" }); + const body = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: "test('x', () => {});" }); expect(body).toContain(PR_PANEL_COMMENT_MARKER); expect(body).toContain("AI-generated Playwright test for @maintainer"); expect(body).toContain("```typescript\ntest('x', () => {});\n```"); @@ -24,7 +25,7 @@ describe("buildE2eTestGenCommentBody", () => { "});", ].join("\n"); - const body = buildE2eTestGenCommentBody({ actor: "maintainer", testSource: source }); + const body = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: source }); expect(body).toContain("````typescript\n"); expect(body).toContain(source + "\n````"); @@ -32,24 +33,24 @@ describe("buildE2eTestGenCommentBody", () => { }); it("uses a custom framework name when provided", () => { - const body = buildE2eTestGenCommentBody({ actor: "maintainer", testSource: "it('x', () => {});", framework: "Cypress" }); + const body = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: "it('x', () => {});", framework: "Cypress" }); expect(body).toContain("AI-generated Cypress test for @maintainer"); }); it("renders a not-usable note (no code fence) when testSource is null", () => { - const body = buildE2eTestGenCommentBody({ actor: "maintainer", testSource: null }); + const body = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: null }); expect(body).toContain(PR_PANEL_COMMENT_MARKER); expect(body).toContain("did not produce a usable result"); expect(body).not.toContain("```"); }); it("names the configured framework in the not-usable note too", () => { - const body = buildE2eTestGenCommentBody({ actor: "maintainer", testSource: null, framework: "Cypress" }); + const body = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: null, framework: "Cypress" }); expect(body).toContain("didn't parse as valid Cypress source"); }); it("links to the commit instead of repeating the code when commit delivery succeeded", () => { - const body = buildE2eTestGenCommentBody({ + const body = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: "test('x', () => {});", commit: { status: "committed", commitSha: "abcdef1234567890", htmlUrl: "https://github.com/o/r/commit/abcdef1234567890" }, @@ -61,7 +62,7 @@ describe("buildE2eTestGenCommentBody", () => { }); it("still renders the suggestion, with a reason, when commit delivery was declined", () => { - const body = buildE2eTestGenCommentBody({ + const body = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: "test('x', () => {});", commit: { status: "declined", reason: "no write access to the PR branch" }, @@ -71,7 +72,7 @@ describe("buildE2eTestGenCommentBody", () => { }); it("still renders the suggestion, with the scoring-integrity reason, when commit delivery was blocked for a confirmed miner", () => { - const body = buildE2eTestGenCommentBody({ + const body = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: "test('x', () => {});", commit: { status: "blocked" }, @@ -81,8 +82,21 @@ describe("buildE2eTestGenCommentBody", () => { }); it("renders exactly like comment-only mode when commit is omitted entirely", () => { - const withoutCommit = buildE2eTestGenCommentBody({ actor: "maintainer", testSource: "test('x', () => {});" }); - const withUndefinedCommit = buildE2eTestGenCommentBody({ actor: "maintainer", testSource: "test('x', () => {});", commit: undefined }); + const withoutCommit = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: "test('x', () => {});" }); + const withUndefinedCommit = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: "test('x', () => {});", commit: undefined }); expect(withUndefinedCommit).toBe(withoutCommit); }); + + // #4613: the footer's attribution link honors a self-hoster's PUBLIC_SITE_ORIGIN instead of always + // pointing at GITTENSORY_SITE_URL. + it("#4613: honors env.PUBLIC_SITE_ORIGIN in the footer attribution link", () => { + const selfHosted = buildE2eTestGenCommentBody({ env: { PUBLIC_SITE_ORIGIN: "https://gittensory.example.org" }, actor: "maintainer", testSource: "test('x', () => {});" }); + expect(selfHosted).toContain("Checked by [Gittensory](https://gittensory.example.org)"); + expect(selfHosted).not.toContain(GITTENSORY_SITE_URL); + }); + + it("#4613: falls back to GITTENSORY_SITE_URL when PUBLIC_SITE_ORIGIN is unset", () => { + const defaultHosted = buildE2eTestGenCommentBody({ env: {}, actor: "maintainer", testSource: "test('x', () => {});" }); + expect(defaultHosted).toContain(`Checked by [Gittensory](${GITTENSORY_SITE_URL})`); + }); }); diff --git a/test/unit/footer.test.ts b/test/unit/footer.test.ts index 76164f2c62..0fe71d1c51 100644 --- a/test/unit/footer.test.ts +++ b/test/unit/footer.test.ts @@ -18,7 +18,7 @@ describe("maintainerControlPanelUrl", () => { describe("gittensory public-comment footer", () => { it("always shows the earn CTA + attribution (permanent marketing surface on every PR)", () => { - const footer = gittensoryFooter(); + const footer = gittensoryFooter({}); expect(footer).toMatch(/earn/i); expect(footer).toContain("register to start earning"); expect(footer).toContain(GITTENSOR_HOME_URL); @@ -26,16 +26,16 @@ describe("gittensory public-comment footer", () => { }); it("points the CTA at a specific repo's public miner page when given an earnUrl", () => { - const footer = gittensoryFooter({ earnUrl: gittensorRepoEarnUrl("JSONbored/gittensory") }); + const footer = gittensoryFooter({}, { earnUrl: gittensorRepoEarnUrl("JSONbored/gittensory") }); expect(footer).toContain("https://gittensor.io/miners/repository?name=JSONbored%2Fgittensory&tab=miners"); }); it("falls back to the Gittensor home URL when no earnUrl is given", () => { - expect(gittensoryFooter()).toContain(`(${GITTENSOR_HOME_URL})`); + expect(gittensoryFooter({})).toContain(`(${GITTENSOR_HOME_URL})`); }); it("never uses reward/payout/score wording (would throw in sanitizePublicComment)", () => { - const footer = gittensoryFooter({ earnUrl: gittensorRepoEarnUrl("o/r") }).toLowerCase(); + const footer = gittensoryFooter({}, { earnUrl: gittensorRepoEarnUrl("o/r") }).toLowerCase(); for (const word of FORBIDDEN_PUBLIC_COMMENT_WORDS) { expect(footer).not.toContain(word.toLowerCase()); } @@ -43,11 +43,31 @@ describe("gittensory public-comment footer", () => { it("preserves maintainer custom lead text while appending the Gittensor CTA", () => { const earnUrl = gittensorRepoEarnUrl("JSONbored/gittensory"); - const footer = gittensoryFooter({ customText: "Thanks for contributing to Gittensory!", earnUrl }); + const footer = gittensoryFooter({}, { customText: "Thanks for contributing to Gittensory!", earnUrl }); expect(footer.startsWith("Thanks for contributing to Gittensory!")).toBe(true); expect(footer).toContain("register to start earning"); expect(footer).toContain(earnUrl); expect(footer).toContain(GITTENSORY_SITE_URL); expect(footer.toLowerCase()).not.toMatch(/reward|payout|score/); }); + + // #4613: a self-hoster's PUBLIC_SITE_ORIGIN replaces GITTENSORY_SITE_URL in the "Checked by Gittensory" + // attribution link -- both the default-copy branch and the maintainer-customText branch splice it in, + // and the Gittensor register link (a separate, shared network) is never rebranded. + it("#4613: uses PUBLIC_SITE_ORIGIN in the attribution link when configured", () => { + const footer = gittensoryFooter({ PUBLIC_SITE_ORIGIN: "https://gittensory.example.org" }); + expect(footer).toContain("Checked by [Gittensory](https://gittensory.example.org)"); + expect(footer).not.toContain(GITTENSORY_SITE_URL); + expect(footer).toContain(GITTENSOR_HOME_URL); // the network link is never rebranded + }); + + it("#4613: falls back to GITTENSORY_SITE_URL when PUBLIC_SITE_ORIGIN is unset", () => { + expect(gittensoryFooter({})).toContain(`Checked by [Gittensory](${GITTENSORY_SITE_URL})`); + }); + + it("#4613: uses PUBLIC_SITE_ORIGIN in the attribution link on the customText branch too", () => { + const footer = gittensoryFooter({ PUBLIC_SITE_ORIGIN: "https://gittensory.example.org" }, { customText: "Thanks for contributing!" }); + expect(footer).toContain("Checked by [Gittensory](https://gittensory.example.org)"); + expect(footer).not.toContain(GITTENSORY_SITE_URL); + }); }); diff --git a/test/unit/generated-doc-refresh.test.ts b/test/unit/generated-doc-refresh.test.ts index 52cc819749..5f59431b15 100644 --- a/test/unit/generated-doc-refresh.test.ts +++ b/test/unit/generated-doc-refresh.test.ts @@ -3,6 +3,7 @@ import { refreshGeneratedDoc } from "../../src/review/generated-doc-refresh"; import { REPO_DOC_MARKERS, renderRepoDocContent } from "../../src/review/repo-doc-render"; import { REPO_PROFILE_SCHEMA_VERSION } from "../../src/review/repo-profile"; import type { RepoProfile } from "../../src/review/repo-profile"; +import { GITTENSORY_SITE_URL } from "../../src/github/footer"; const MARKERS = { start: "", end: "" }; const SECTION = `${MARKERS.start}\ngenerated body v1\n${MARKERS.end}\n`; @@ -113,7 +114,7 @@ describe("refreshGeneratedDoc (#3004)", () => { }); it("REGRESSION: a real renderRepoDocContent() output round-trips as no-change against itself, with or without surrounding manual content", () => { - const rendered = renderRepoDocContent(fixtureProfile())!; + const rendered = renderRepoDocContent(fixtureProfile(), GITTENSORY_SITE_URL)!; expect(refreshGeneratedDoc(rendered, rendered, REPO_DOC_MARKERS)).toEqual({ action: "no-change" }); const withManualContent = `\n\n${rendered}\n\n`; diff --git a/test/unit/github-commands.test.ts b/test/unit/github-commands.test.ts index c2c059ecab..e2633b8dbb 100644 --- a/test/unit/github-commands.test.ts +++ b/test/unit/github-commands.test.ts @@ -94,7 +94,7 @@ describe("GitHub mention commands", () => { expect(suggestCommand("prefliht")).toBe("preflight"); expect(suggestCommand("reveiw")).toBe("review"); const typo = parseGittensoryMentionCommand("@gittensory prefliht")!; - const typoBody = buildPublicAgentCommandComment({ + const typoBody = buildPublicAgentCommandComment({env: {}, command: typo, repo: null, issue: { number: 1, title: "t", state: "open" }, @@ -103,7 +103,7 @@ describe("GitHub mention commands", () => { }); expect(typoBody).toContain("Did you mean `@gittensory preflight`?"); const far = parseGittensoryMentionCommand("@gittensory zzzz")!; - const farBody = buildPublicAgentCommandComment({ + const farBody = buildPublicAgentCommandComment({env: {}, command: far, repo: null, issue: { number: 1, title: "t", state: "open" }, @@ -112,7 +112,7 @@ describe("GitHub mention commands", () => { }); expect(farBody).not.toContain("Did you mean"); const ok = parseGittensoryMentionCommand("@gittensory preflight")!; - const okBody = buildPublicAgentCommandComment({ + const okBody = buildPublicAgentCommandComment({env: {}, command: ok, repo: null, issue: { number: 1, title: "t", state: "open" }, @@ -121,7 +121,7 @@ describe("GitHub mention commands", () => { }); expect(okBody).not.toContain("Did you mean"); const bareHelp = parseGittensoryMentionCommand("@gittensory")!; - const bareHelpBody = buildPublicAgentCommandComment({ + const bareHelpBody = buildPublicAgentCommandComment({env: {}, command: bareHelp, repo: null, issue: { number: 1, title: "t", state: "open" }, @@ -161,7 +161,7 @@ describe("GitHub mention commands", () => { } const rendered = githubCommandsInternals.actionCommandHelpSections().join("\n"); expect(rendered).toContain("@gittensory re-review"); - const helpCard = buildPublicAgentCommandComment({ + const helpCard = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory")!, repo: null, issue: { number: 1, title: "t", state: "open" }, @@ -246,7 +246,7 @@ describe("GitHub mention commands", () => { it("keeps public comments sanitized", () => { const command = parseGittensoryMentionCommand("@gittensory next-action")!; - const body = buildPublicAgentCommandComment({ + const body = buildPublicAgentCommandComment({env: {}, command, repo: null, issue: { number: 12, title: "PR", state: "open", pull_request: {} }, @@ -340,7 +340,7 @@ describe("GitHub mention commands", () => { }; for (const mention of ["@gittensory preflight", "@gittensory reviewability", "@gittensory packet"]) { - const body = buildPublicAgentCommandComment({ + const body = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand(mention)!, repo: { fullName: "owner/repo" } as any, issue: { number: 12, title: "PR", state: "open", pull_request: {} }, @@ -356,7 +356,7 @@ describe("GitHub mention commands", () => { }); it("adds parseable aggregate-only feedback context without public leak terms", () => { - const body = buildPublicAgentCommandComment({ + const body = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory preflight")!, repo: { fullName: "owner/repo" } as any, issue: { number: 12, title: "PR", state: "open", pull_request: {} }, @@ -378,7 +378,7 @@ describe("GitHub mention commands", () => { }); it("does not publish repo outcome-pattern details in duplicate-check comments", () => { - const body = buildPublicAgentCommandComment({ + const body = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-check")!, repo: null, issue: { number: 99, title: "PR", state: "open", pull_request: {} }, @@ -420,7 +420,7 @@ describe("GitHub mention commands", () => { it("renders command-specific sections for preflight, blockers, duplicate-check, and next-action", () => { const bundle = sampleBundle(); - const preflight = buildPublicAgentCommandComment({ + const preflight = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory preflight")!, repo: null, issue: { number: 10, title: "PR", state: "open", pull_request: {} }, @@ -432,7 +432,7 @@ describe("GitHub mention commands", () => { expect(preflight).toContain("**Preflight summary**"); expect(preflight).toContain("Run local branch preflight first."); - const blockers = buildPublicAgentCommandComment({ + const blockers = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory blockers")!, repo: null, issue: { number: 11, title: "PR", state: "open", pull_request: {} }, @@ -446,7 +446,7 @@ describe("GitHub mention commands", () => { expect(blockers).toContain("Private readiness context available in authenticated Gittensory views"); expect(blockers).not.toContain("5 open PR(s)"); - const duplicateCheck = buildPublicAgentCommandComment({ + const duplicateCheck = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-check")!, repo: null, issue: { number: 12, title: "PR", state: "open", pull_request: {} }, @@ -459,7 +459,7 @@ describe("GitHub mention commands", () => { expect(duplicateCheck).toContain("Possible overlap with existing work"); expect(duplicateCheck).not.toMatch(/\blikely_duplicate\b/i); - const nextAction = buildPublicAgentCommandComment({ + const nextAction = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory next-action")!, repo: null, issue: { number: 13, title: "PR", state: "open", pull_request: {} }, @@ -471,7 +471,7 @@ describe("GitHub mention commands", () => { expect(nextAction).toContain("**Recommended next step**"); expect(nextAction).toContain("After tests pass."); - const ask = buildPublicAgentCommandComment({ + const ask = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask what should I improve for contribution quality?")!, repo: null, issue: { number: 14, title: "PR", state: "open", pull_request: {} }, @@ -499,7 +499,7 @@ describe("GitHub mention commands", () => { expect(ask).toContain("cached GitHub open PR/issue queue"); expect(ask).toContain("Freshness: agent run status completed."); - const askWithTargets = buildPublicAgentCommandComment({ + const askWithTargets = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask what should I clean up before review?")!, repo: null, issue: { number: 15, title: "PR", state: "open", pull_request: {} }, @@ -546,7 +546,7 @@ describe("GitHub mention commands", () => { }); it("REGRESSION (#2457): neutralizes markdown/HTML and zero-width-spaces @mentions in the ask question, so an authorized-but-untrusted actor cannot forge a bot-endorsed approval or break out of the
wrapper", () => { - const forged = buildPublicAgentCommandComment({ + const forged = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask **APPROVED by @jsonbored** please merge now

FAKE

")!, repo: null, issue: { number: 16, title: "PR", state: "open", pull_request: {} }, @@ -567,7 +567,7 @@ describe("GitHub mention commands", () => { }); it("redacts private score floor blockers from public preflight comments", () => { - const body = buildPublicAgentCommandComment({ + const body = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory preflight")!, repo: { fullName: "owner/repo" } as any, issue: { number: 25, title: "PR", state: "open", pull_request: {} }, @@ -606,7 +606,7 @@ describe("GitHub mention commands", () => { }); it("does not publish private blocker why details", () => { - const body = buildPublicAgentCommandComment({ + const body = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory blockers")!, repo: { fullName: "owner/repo" } as any, issue: { number: 24, title: "PR", state: "open", pull_request: {} }, @@ -645,7 +645,7 @@ describe("GitHub mention commands", () => { }); it("renders help, miner-context fallback, refresh, and empty-action responses", () => { - const help = buildPublicAgentCommandComment({ + const help = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory help")!, repo: null, issue: { number: 1, title: "PR", state: "open", pull_request: {} }, @@ -659,7 +659,7 @@ describe("GitHub mention commands", () => { expect(help).toContain("Additional safe details"); expect(help).toContain("@gittensory next-action"); - const minerFallback = buildPublicAgentCommandComment({ + const minerFallback = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory miner-context")!, repo: null, issue: { number: 2, title: "PR", state: "open", pull_request: {} }, @@ -669,7 +669,7 @@ describe("GitHub mention commands", () => { }); expect(minerFallback).toContain("Official miner context is unavailable"); - const minerContext = buildPublicAgentCommandComment({ + const minerContext = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory miner-context")!, repo: { fullName: "owner/repo" } as any, issue: { number: 22, title: "PR", state: "open", pull_request: {} }, @@ -680,7 +680,7 @@ describe("GitHub mention commands", () => { expect(minerContext).toContain("confirmed by the official Gittensor API"); expect(minerContext).toContain("| Scope | owner/repo#22 |"); - const refresh = buildPublicAgentCommandComment({ + const refresh = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory blockers")!, repo: null, issue: { number: 3, title: "PR", state: "open", pull_request: {} }, @@ -706,7 +706,7 @@ describe("GitHub mention commands", () => { expect(refresh).toContain("Freshness: snapshot refresh in progress."); expect(refresh).toContain("Retry after the contributor decision snapshot refresh completes."); - const preflightRefresh = buildPublicAgentCommandComment({ + const preflightRefresh = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory preflight")!, repo: null, issue: { number: 31, title: "PR", state: "open", pull_request: {} }, @@ -730,7 +730,7 @@ describe("GitHub mention commands", () => { }); expect(preflightRefresh).toContain("**Preflight snapshot refresh**"); - const duplicateRefresh = buildPublicAgentCommandComment({ + const duplicateRefresh = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-check")!, repo: null, issue: { number: 33, title: "PR", state: "open", pull_request: {} }, @@ -754,7 +754,7 @@ describe("GitHub mention commands", () => { }); expect(duplicateRefresh).toContain("**Duplicate-check snapshot refresh**"); - const askRefresh = buildPublicAgentCommandComment({ + const askRefresh = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask should I update linked issue context?")!, repo: null, issue: { number: 35, title: "PR", state: "open", pull_request: {} }, @@ -783,7 +783,7 @@ describe("GitHub mention commands", () => { expect(askRefresh).toContain("Retry @gittensory ask after the contribution context snapshot refresh completes."); expect(askRefresh).toContain("Freshness: contribution context snapshot refresh in progress."); - const nextActionRefresh = buildPublicAgentCommandComment({ + const nextActionRefresh = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory next-action")!, repo: null, issue: { number: 36, title: "PR", state: "open", pull_request: {} }, @@ -793,7 +793,7 @@ describe("GitHub mention commands", () => { }); expect(nextActionRefresh).toContain("**Next-action snapshot refresh**"); - const empty = buildPublicAgentCommandComment({ + const empty = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory next-action")!, repo: null, issue: { number: 4, title: "PR", state: "open", pull_request: {} }, @@ -818,7 +818,7 @@ describe("GitHub mention commands", () => { expect(empty).toContain("**Recommended next step**"); expect(empty).toContain("No public-safe context is available"); - const askNoQuestion = buildPublicAgentCommandComment({ + const askNoQuestion = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask")!, repo: null, issue: { number: 36, title: "PR", state: "open", pull_request: {} }, @@ -843,7 +843,7 @@ describe("GitHub mention commands", () => { expect(askNoQuestion).toContain("No specific question was provided"); expect(askNoQuestion).toContain("No matching contribution-quality context is available"); - const askMetadata = buildPublicAgentCommandComment({ + const askMetadata = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask what blocks contribution readiness?")!, repo: null, issue: { number: 37, title: "PR", state: "open", pull_request: {} }, @@ -930,7 +930,7 @@ describe("GitHub mention commands", () => { expect(askMetadata.slice(askMetadata.indexOf("Additional safe details"))).toContain("freshness: partial"); expect(askMetadata).not.toContain("No concrete cached source reference is available for this response."); - const askNoSources = buildPublicAgentCommandComment({ + const askNoSources = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask what is the repo policy?")!, repo: null, issue: { number: 38, title: "PR", state: "open", pull_request: {} }, @@ -939,7 +939,7 @@ describe("GitHub mention commands", () => { }); expect(askNoSources).toContain("cached Gittensory agent context (no connected-source metadata in this run)"); - const askEvidenceOnly = buildPublicAgentCommandComment({ + const askEvidenceOnly = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask what should I verify locally?")!, repo: null, issue: { number: 39, title: "PR", state: "open", pull_request: {} }, @@ -976,7 +976,7 @@ describe("GitHub mention commands", () => { expect(askEvidenceOnly).toContain("Source: custom unknown source; freshness:"); expect(askEvidenceOnly).toContain("custom unknown source"); - const askFallbackCitations = buildPublicAgentCommandComment({ + const askFallbackCitations = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask what is missing?")!, repo: null, issue: { number: 40, title: "PR", state: "open", pull_request: {} }, @@ -1009,7 +1009,7 @@ describe("GitHub mention commands", () => { expect(askFallbackDetails).toContain("README/docs context is included only when connected repo sources"); expect(askFallbackDetails).not.toMatch(/origin: /); - const noBundle = buildPublicAgentCommandComment({ + const noBundle = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory preflight")!, repo: null, issue: { number: 44, title: "PR", state: "open", pull_request: {} }, @@ -1019,7 +1019,7 @@ describe("GitHub mention commands", () => { expect(noBundle).toContain("**Preflight summary**"); expect(noBundle).toContain("No public-safe context is available"); - const noBundleNextAction = buildPublicAgentCommandComment({ + const noBundleNextAction = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory next-action")!, repo: null, issue: { number: 48, title: "PR", state: "open", pull_request: {} }, @@ -1029,7 +1029,7 @@ describe("GitHub mention commands", () => { expect(noBundleNextAction).toContain("**Recommended next step**"); expect(noBundleNextAction).toContain("No public-safe context is available"); - const emptyBlockers = buildPublicAgentCommandComment({ + const emptyBlockers = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory blockers")!, repo: null, issue: { number: 45, title: "PR", state: "open", pull_request: {} }, @@ -1044,7 +1044,7 @@ describe("GitHub mention commands", () => { }); expect(emptyBlockers).toContain("No public readiness blockers are visible"); - const emptyDuplicate = buildPublicAgentCommandComment({ + const emptyDuplicate = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-check")!, repo: null, issue: { number: 46, title: "PR", state: "open", pull_request: {} }, @@ -1059,7 +1059,7 @@ describe("GitHub mention commands", () => { }); expect(emptyDuplicate).toContain("No duplicate or work-in-progress collision signal is visible"); - const missingDigest = buildPublicAgentCommandComment({ + const missingDigest = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory queue-summary")!, repo: null, issue: { number: 47, title: "PR", state: "open", pull_request: {} }, @@ -1068,7 +1068,7 @@ describe("GitHub mention commands", () => { }); expect(missingDigest).toContain("Cached queue context is unavailable"); - const withPrFallbackScope = buildPublicAgentCommandComment({ + const withPrFallbackScope = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory next-action")!, repo: null, issue: { number: 5, title: "PR", state: "open", pull_request: {} }, @@ -1113,7 +1113,7 @@ describe("GitHub mention commands", () => { it("does not duplicate ask citations across Findings and Additional safe details when there are 5+ sources", () => { // Five distinct contributing sources → five citations. The first four render under Findings and the // overflow (citations 5+) under Additional safe details; no citation should appear in both sections. - const ask = buildPublicAgentCommandComment({ + const ask = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask what is missing?")!, repo: null, issue: { number: 61, title: "PR", state: "open", pull_request: {} }, @@ -1174,7 +1174,7 @@ describe("GitHub mention commands", () => { }); it("covers blocker label fallbacks, rerun bullets, and duplicate-risk heuristics", () => { - const blockersWithFallbackLabel = buildPublicAgentCommandComment({ + const blockersWithFallbackLabel = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory blockers")!, repo: null, issue: { number: 20, title: "PR", state: "open", pull_request: {} }, @@ -1204,7 +1204,7 @@ describe("GitHub mention commands", () => { expect(blockersWithFallbackLabel).toContain("custom signal code"); expect(blockersWithFallbackLabel).toContain("Reduce concurrent review load."); - const blockersWithDuplicateCodes = buildPublicAgentCommandComment({ + const blockersWithDuplicateCodes = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory blockers")!, repo: null, issue: { number: 24, title: "PR", state: "open", pull_request: {} }, @@ -1233,7 +1233,7 @@ describe("GitHub mention commands", () => { }); expect(blockersWithDuplicateCodes.match(/Private readiness context available in authenticated Gittensory views/g)).toHaveLength(1); - const blockersFromStatus = buildPublicAgentCommandComment({ + const blockersFromStatus = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory blockers")!, repo: null, issue: { number: 25, title: "PR", state: "open", pull_request: {} }, @@ -1262,7 +1262,7 @@ describe("GitHub mention commands", () => { }); expect(blockersFromStatus.match(/Private readiness context available in authenticated Gittensory views/g)).toHaveLength(1); - const statusOnlyBlocker = buildPublicAgentCommandComment({ + const statusOnlyBlocker = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory blockers")!, repo: null, issue: { number: 26, title: "PR", state: "open", pull_request: {} }, @@ -1291,7 +1291,7 @@ describe("GitHub mention commands", () => { }); expect(statusOnlyBlocker).toContain("Wait for maintainer review capacity."); - const duplicateViaRecommendation = buildPublicAgentCommandComment({ + const duplicateViaRecommendation = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-check")!, repo: null, issue: { number: 21, title: "PR", state: "open", pull_request: {} }, @@ -1323,7 +1323,7 @@ describe("GitHub mention commands", () => { expect(duplicateViaRecommendation).toContain("Review linked issues before requesting detailed review."); expect(duplicateViaRecommendation).not.toContain("Concurrent review pressure"); - const duplicateWithInjectedWhy = buildPublicAgentCommandComment({ + const duplicateWithInjectedWhy = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-check")!, repo: null, issue: { number: 27, title: "PR", state: "open", pull_request: {} }, @@ -1354,7 +1354,7 @@ describe("GitHub mention commands", () => { expect(duplicateWithInjectedWhy).not.toContain("PRs touching duplicate"); expect(duplicateWithInjectedWhy).not.toMatch(/\n@octo-team|@octo-team|[^\\]\[click\]\(https:\/\/example\.test\)/); - const preflightWithRerun = buildPublicAgentCommandComment({ + const preflightWithRerun = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory preflight")!, repo: null, issue: { number: 22, title: "PR", state: "open", pull_request: {} }, @@ -1385,7 +1385,7 @@ describe("GitHub mention commands", () => { expect(preflightWithRerun).toContain("Rerun when:"); expect(preflightWithRerun).toContain("Private readiness context available in authenticated Gittensory views"); - const duplicateBlockerLabels = buildPublicAgentCommandComment({ + const duplicateBlockerLabels = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory blockers")!, repo: null, issue: { number: 25, title: "PR", state: "open", pull_request: {} }, @@ -1414,7 +1414,7 @@ describe("GitHub mention commands", () => { }); expect(duplicateBlockerLabels.match(/Private readiness context available in authenticated Gittensory views/g)).toHaveLength(1); - const duplicateFallbackPick = buildPublicAgentCommandComment({ + const duplicateFallbackPick = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-check")!, repo: null, issue: { number: 23, title: "PR", state: "open", pull_request: {} }, @@ -1445,7 +1445,7 @@ describe("GitHub mention commands", () => { }); it("renders v2 reviewability, repo-fit, and packet sections without private internals", () => { - const reviewability = buildPublicAgentCommandComment({ + const reviewability = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory reviewability")!, repo: { fullName: "owner/repo" } as any, issue: { number: 31, title: "PR", state: "open", pull_request: {} }, @@ -1459,7 +1459,7 @@ describe("GitHub mention commands", () => { expect(reviewability).toContain("Run local branch preflight first."); expect(reviewability).not.toMatch(/private reviewability|reviewability internals|scoreability|public score estimate|wallet|hotkey|payout|farming/i); - const repoFit = buildPublicAgentCommandComment({ + const repoFit = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory repo-fit")!, repo: { fullName: "owner/repo" } as any, issue: { number: 32, title: "PR", state: "open", pull_request: {} }, @@ -1473,7 +1473,7 @@ describe("GitHub mention commands", () => { expect(repoFit).toContain("Use local branch preflight before posting."); expect(repoFit).not.toMatch(/private reviewability|scoreability|public score estimate|wallet|hotkey|payout|farming/i); - const packet = buildPublicAgentCommandComment({ + const packet = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory packet")!, repo: { fullName: "owner/repo" } as any, issue: { number: 33, title: "PR", state: "open", pull_request: {} }, @@ -1489,7 +1489,7 @@ describe("GitHub mention commands", () => { }); it("covers v2 refresh, empty, rerun, and duplicate-line fallbacks", () => { - const preflightRefresh = buildPublicAgentCommandComment({ + const preflightRefresh = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory preflight")!, repo: null, issue: { number: 40, title: "PR", state: "open", pull_request: {} }, @@ -1503,7 +1503,7 @@ describe("GitHub mention commands", () => { ["@gittensory blockers", "Readiness blockers", "No public readiness blockers are visible"], ["@gittensory duplicate-check", "Duplicate & WIP caution", "No duplicate or work-in-progress collision signal is visible"], ] as const) { - const body = buildPublicAgentCommandComment({ + const body = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand(commandText)!, repo: null, issue: { number: 40, title: "PR", state: "open", pull_request: {} }, @@ -1520,7 +1520,7 @@ describe("GitHub mention commands", () => { ["@gittensory repo-fit", "Repository fit snapshot refresh"], ["@gittensory packet", "Public packet snapshot refresh"], ] as const) { - const body = buildPublicAgentCommandComment({ + const body = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand(commandText)!, repo: null, issue: { number: 41, title: "PR", state: "open", pull_request: {} }, @@ -1536,7 +1536,7 @@ describe("GitHub mention commands", () => { ["@gittensory repo-fit", "Repository fit"], ["@gittensory packet", "Public packet"], ] as const) { - const body = buildPublicAgentCommandComment({ + const body = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand(commandText)!, repo: null, issue: { number: 42, title: "PR", state: "open", pull_request: {} }, @@ -1548,7 +1548,7 @@ describe("GitHub mention commands", () => { expect(body).toContain("No public-safe context is available"); } - const repoFitWithRerun = buildPublicAgentCommandComment({ + const repoFitWithRerun = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory repo-fit")!, repo: null, issue: { number: 43, title: "PR", state: "open", pull_request: {} }, @@ -1579,7 +1579,7 @@ describe("GitHub mention commands", () => { expect(repoFitWithRerun).toContain("Rerun when: After queue changes."); expect(repoFitWithRerun).not.toContain("Target:"); - const repoFitFromSummary = buildPublicAgentCommandComment({ + const repoFitFromSummary = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory repo-fit")!, repo: null, issue: { number: 43, title: "PR", state: "open", pull_request: {} }, @@ -1608,7 +1608,7 @@ describe("GitHub mention commands", () => { }); expect(repoFitFromSummary).toContain("Repository fit looks clean"); - const packetFromSafetyClass = buildPublicAgentCommandComment({ + const packetFromSafetyClass = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory packet")!, repo: null, issue: { number: 43, title: "PR", state: "open", pull_request: {} }, @@ -1637,7 +1637,7 @@ describe("GitHub mention commands", () => { }); expect(packetFromSafetyClass).toContain("Post the public-safe PR packet"); - const duplicateBlockers = buildPublicAgentCommandComment({ + const duplicateBlockers = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory blockers")!, repo: null, issue: { number: 44, title: "PR", state: "open", pull_request: {} }, @@ -1676,7 +1676,7 @@ describe("GitHub mention commands", () => { const FORBIDDEN = /wallet|hotkey|coldkey|mnemonic|raw trust score|trust score|payout|reward estimate|farming|private reviewability|scoreability/i; const render = (mention: string) => - buildPublicAgentCommandComment({ + buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand(mention)!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -1713,7 +1713,7 @@ describe("GitHub mention commands", () => { it("renders populated and empty outcome/noise report variants", () => { const base = sampleMaintainerDigest(); const render = (mention: string, digest: typeof base) => - buildPublicAgentCommandComment({ + buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand(mention)!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -1796,7 +1796,7 @@ describe("GitHub mention commands", () => { expect(reversedDigest.reviewNowPullRequests.map((pr) => pr.number)).toEqual(digest.reviewNowPullRequests.map((pr) => pr.number)); expect(reversedDigest.needsAuthorPullRequests.map((pr) => pr.number)).toEqual(digest.needsAuthorPullRequests.map((pr) => pr.number)); - const queueSummary = buildPublicAgentCommandComment({ + const queueSummary = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory queue-summary")!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -1810,7 +1810,7 @@ describe("GitHub mention commands", () => { expect(queueSummary).toContain("Feedback on this response is tracked separately"); expect(queueSummary).not.toMatch(/wallet|hotkey|raw trust score|payout|reward estimate|farming|private reviewability|public score estimate/i); - const confirmed = buildPublicAgentCommandComment({ + const confirmed = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory confirmed-miners")!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -1822,7 +1822,7 @@ describe("GitHub mention commands", () => { expect(confirmed).toContain("#10: Ready linked fix"); expect(confirmed).toContain("#13: Cache overlap first"); - const reviewNow = buildPublicAgentCommandComment({ + const reviewNow = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory review-now")!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -1834,7 +1834,7 @@ describe("GitHub mention commands", () => { expect(reviewNow).toContain("#10: Ready linked fix"); expect(reviewNow).not.toContain("#12: Needs issue context"); - const needsAuthor = buildPublicAgentCommandComment({ + const needsAuthor = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory needs-author")!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -1847,7 +1847,7 @@ describe("GitHub mention commands", () => { expect(needsAuthor).toContain("1 cached check(s) need attention."); expect(needsAuthor).toContain("Possible duplicate or WIP overlap"); - const duplicateClusters = buildPublicAgentCommandComment({ + const duplicateClusters = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-clusters")!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -1878,7 +1878,7 @@ describe("GitHub mention commands", () => { }, ], }); - const defensiveClusters = buildPublicAgentCommandComment({ + const defensiveClusters = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-clusters")!, repo: null, issue: { number: 100, title: "Digest", state: "open", pull_request: {} }, @@ -1888,7 +1888,7 @@ describe("GitHub mention commands", () => { }); expect(defensiveClusters).toContain("medium risk:"); expect(defensiveClusters).toContain("..."); - const defensiveSummary = buildPublicAgentCommandComment({ + const defensiveSummary = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory queue-summary")!, repo: null, issue: { number: 101, title: "Digest", state: "open", pull_request: {} }, @@ -1899,7 +1899,7 @@ describe("GitHub mention commands", () => { expect(defensiveSummary).toContain("Use the authenticated maintainer dashboard and private API"); expect(defensiveDigest.needsAuthorPullRequests.find((pr) => pr.number === 18)?.reasons).toContain("Maintainer-authored PR; review as repo stewardship."); - const unavailableDigest = buildPublicAgentCommandComment({ + const unavailableDigest = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory review-now")!, repo: { fullName: "owner/repo" } as any, issue: { number: 102, title: "Digest", state: "open", pull_request: {} }, @@ -1909,7 +1909,7 @@ describe("GitHub mention commands", () => { }); expect(unavailableDigest).toContain("Cached queue context is unavailable for this command."); - const emptyReviewNow = buildPublicAgentCommandComment({ + const emptyReviewNow = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory review-now")!, repo: { fullName: "owner/repo" } as any, issue: { number: 103, title: "Digest", state: "open", pull_request: {} }, @@ -1919,7 +1919,7 @@ describe("GitHub mention commands", () => { }); expect(emptyReviewNow).toContain("No cached PR currently looks ready for detailed review."); - const emptyDuplicateClusters = buildPublicAgentCommandComment({ + const emptyDuplicateClusters = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-clusters")!, repo: { fullName: "owner/repo" } as any, issue: { number: 104, title: "Digest", state: "open", pull_request: {} }, @@ -1929,7 +1929,7 @@ describe("GitHub mention commands", () => { }); expect(emptyDuplicateClusters).toContain("No duplicate or WIP cluster is visible from cached metadata."); - const emptyConfirmed = buildPublicAgentCommandComment({ + const emptyConfirmed = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory confirmed-miners")!, repo: { fullName: "owner/repo" } as any, issue: { number: 105, title: "Digest", state: "open", pull_request: {} }, @@ -1939,7 +1939,7 @@ describe("GitHub mention commands", () => { }); expect(emptyConfirmed).toContain("No cached confirmed-miner PRs are visible in this queue."); - const emptyNeedsAuthor = buildPublicAgentCommandComment({ + const emptyNeedsAuthor = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory needs-author")!, repo: { fullName: "owner/repo" } as any, issue: { number: 106, title: "Digest", state: "open", pull_request: {} }, @@ -1977,7 +1977,7 @@ describe("GitHub mention commands", () => { ], } satisfies ReturnType; - const reviewNow = buildPublicAgentCommandComment({ + const reviewNow = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory review-now")!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -1985,7 +1985,7 @@ describe("GitHub mention commands", () => { actorKind: "maintainer", maintainerDigest: digest, }); - const duplicateClusters = buildPublicAgentCommandComment({ + const duplicateClusters = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-clusters")!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -2037,7 +2037,7 @@ describe("GitHub mention commands", () => { ], } satisfies ReturnType; - const reviewNow = buildPublicAgentCommandComment({ + const reviewNow = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory review-now")!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -2045,7 +2045,7 @@ describe("GitHub mention commands", () => { actorKind: "maintainer", maintainerDigest: digest, }); - const duplicateClusters = buildPublicAgentCommandComment({ + const duplicateClusters = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory duplicate-clusters")!, repo: { fullName: "owner/repo" } as any, issue: { number: 99, title: "Digest", state: "open", pull_request: {} }, @@ -2153,7 +2153,7 @@ describe("ask citation helpers", () => { "Cached open PR and issue queue metadata was available for this cached agent run.", ); - const comment = buildPublicAgentCommandComment({ + const comment = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask what should I do next?")!, repo: null, issue: { number: 44, title: "PR", state: "open", pull_request: {} }, @@ -2269,7 +2269,7 @@ describe("ask citation helpers", () => { expect(extended.some((source) => source.key === "contributor_decision_pack" && source.detail.includes("Contributor decision-pack metadata"))).toBe(true); expect(extended.find((source) => source.key === "open_pr_monitor")?.freshness).toBe("unknown"); - const askOverflow = buildPublicAgentCommandComment({ + const askOverflow = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask list every connected source?")!, repo: null, issue: { number: 41, title: "PR", state: "open", pull_request: {} }, @@ -2280,7 +2280,7 @@ describe("ask citation helpers", () => { expect(askOverflow).toContain("Additional safe details"); expect(askOverflow).toContain("Source: cached GitHub open PR/issue queue; freshness:"); - const askWithoutTimestamps = buildPublicAgentCommandComment({ + const askWithoutTimestamps = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask what is synced?")!, repo: null, issue: { number: 42, title: "PR", state: "open", pull_request: {} }, @@ -2304,7 +2304,7 @@ describe("ask citation helpers", () => { expect(askWithoutTimestamps).toContain("Connected source repo sync freshness metadata: freshness unknown."); expect(askWithoutTimestamps).not.toContain("freshness unknown as of"); - const askFourSources = buildPublicAgentCommandComment({ + const askFourSources = buildPublicAgentCommandComment({env: {}, command: parseGittensoryMentionCommand("@gittensory ask what sources apply?")!, repo: null, issue: { number: 43, title: "PR", state: "open", pull_request: {} }, diff --git a/test/unit/planner.test.ts b/test/unit/planner.test.ts index 978259bb63..04108985f5 100644 --- a/test/unit/planner.test.ts +++ b/test/unit/planner.test.ts @@ -113,7 +113,7 @@ describe("classifyPlanCommandRequest (#issue-coding-plan)", () => { describe("buildIssuePlanComment (#issue-coding-plan)", () => { it("renders the plan with the marker, actor, scope, and footer", () => { - const body = buildIssuePlanComment("## Summary\nShip it.", { actor: "maintainer1", repoFullName: "acme/widgets", issueNumber: 42 }); + const body = buildIssuePlanComment("## Summary\nShip it.", { actor: "maintainer1", repoFullName: "acme/widgets", issueNumber: 42, env: {} }); expect(body).toContain("Gittensory implementation plan"); expect(body).toContain("@maintainer1"); expect(body).toContain("acme/widgets#42"); diff --git a/test/unit/policy-sanitizer.test.ts b/test/unit/policy-sanitizer.test.ts index b8371b9aae..abb70c6e83 100644 --- a/test/unit/policy-sanitizer.test.ts +++ b/test/unit/policy-sanitizer.test.ts @@ -524,7 +524,7 @@ describe("label guidance sanitizer", () => { it("does not expose private terms in settings-preview label decisions", () => { const repo = registeredRepo("octo/label-test", configFor({ repo: "octo/label-test" })); const settings = settingsFor(repo.fullName, { gittensorLabel: "gittensor", autoLabelEnabled: true, publicSurface: "label_only" }); - const preview = buildRepoSettingsPreview({ repoFullName: repo.fullName, repo, settings, installation: previewHealthyInstall, issues: [], pullRequests: [], sample: { authorLogin: "miner", minerStatus: "confirmed" } }); + const preview = buildRepoSettingsPreview({env: {}, repoFullName: repo.fullName, repo, settings, installation: previewHealthyInstall, issues: [], pullRequests: [], sample: { authorLogin: "miner", minerStatus: "confirmed" } }); expect(preview.appliedLabel).toBe("gittensor"); expect(JSON.stringify(preview)).not.toMatch(PRIVATE_TERMS_PATTERN); @@ -541,7 +541,7 @@ describe("label guidance sanitizer", () => { it("sanitizes preview warnings that reference label permissions without leaking private context", () => { const repo = registeredRepo("octo/perms", configFor({ repo: "octo/perms" })); - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, repoFullName: repo.fullName, repo, settings: settingsFor(repo.fullName), @@ -560,7 +560,7 @@ describe("label guidance sanitizer", () => { describe("validation guidance sanitizer", () => { it("keeps check-run decisions free of private terms", () => { const repo = registeredRepo("octo/checks", configFor({ repo: "octo/checks" })); - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, repoFullName: repo.fullName, repo, settings: settingsFor(repo.fullName, { checkRunMode: "enabled", checkRunDetailLevel: "deep" }), diff --git a/test/unit/repo-doc-pr.test.ts b/test/unit/repo-doc-pr.test.ts index 435cf80372..582260e07b 100644 --- a/test/unit/repo-doc-pr.test.ts +++ b/test/unit/repo-doc-pr.test.ts @@ -9,6 +9,7 @@ import { renderRepoSkillContent, repoSkillFilePath } from "../../src/review/repo import { extractRepoProfile } from "../../src/review/repo-profile"; import { upsertRepoFocusManifest } from "../../src/signals/focus-manifest-loader"; import { createTestEnv } from "../helpers/d1"; +import { GITTENSORY_SITE_URL } from "../../src/github/footer"; function base64Utf8(text: string): string { return Buffer.from(text, "utf8").toString("base64"); @@ -211,6 +212,75 @@ describe("openRepoDocPullRequest (#3000)", () => { expect(prCall?.body.body as string).toContain("Gittensory opened this pull request"); }); + // #4613: a self-hoster's PUBLIC_SITE_ORIGIN reaches the generated AGENTS.md's attribution link instead + // of GITTENSORY_SITE_URL. NOTE: createTestEnv() defaults PUBLIC_SITE_ORIGIN to a truthy value (matching + // GITTENSORY_SITE_URL's own string), so envWithKey() alone does NOT exercise the `??` fallback's nullish + // side -- the paired test below overrides it to `undefined` explicitly to cover that branch too. + it("#4613: uses env.PUBLIC_SITE_ORIGIN in the generated AGENTS.md attribution link when configured", async () => { + const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: generateRsaPrivateKeyPem(), PUBLIC_SITE_ORIGIN: "https://gittensory.example.org" }); + await seedInstalledRepo(env, { defaultBranch: "main" }); + await seedProfileData(env); + await seedRepoDocGenerationConfig(env, REPO); + const calls: Array<{ method: string; url: string; body: Record }> = []; + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const url = input.toString(); + if (TOKEN_URL.test(url)) return Response.json({ token: "t" }); + const method = init?.method ?? "GET"; + calls.push({ method, url, body: init?.body ? JSON.parse(String(init.body)) : {} }); + if (url.includes("/pulls?") && method === "GET") return Response.json([]); + if (url.includes("/contents/AGENTS.md") && method === "GET") return new Response("not found", { status: 404 }); + if (url.includes("/contents/CLAUDE.md") && method === "GET") return new Response("not found", { status: 404 }); + if (url.endsWith("/branches/main")) return Response.json({ commit: { sha: "base-commit-sha", commit: { tree: { sha: "base-tree-sha" } } } }); + if (url.endsWith("/git/trees") && method === "POST") return Response.json({ sha: "new-tree-sha" }); + if (url.endsWith("/git/commits") && method === "POST") return Response.json({ sha: "new-commit-sha" }); + if (url.endsWith("/git/refs") && method === "POST") return Response.json({ ref: "refs/heads/gittensory/repo-docs" }); + if (url.endsWith("/repos/owner/widgets/pulls") && method === "POST") return Response.json({ number: 43, html_url: "https://github.com/owner/widgets/pull/43" }); + return new Response("unexpected", { status: 500 }); + }); + const result = await openRepoDocPullRequest(env, REPO, "live"); + expect(result.opened).toBe(true); + + const treeCall = calls.find((c) => c.url.endsWith("/git/trees")); + const tree = treeCall?.body.tree as Array<{ path: string; content: string }>; + const agentsContent = tree.find((entry) => entry.path === "AGENTS.md")?.content; + expect(agentsContent).toContain("This file is generated by [Gittensory](https://gittensory.example.org)"); + expect(agentsContent).not.toContain(GITTENSORY_SITE_URL); + }); + + // #4613: the true nullish case -- PUBLIC_SITE_ORIGIN explicitly absent (unlike envWithKey()'s default, + // which sets it) -- must still fall back to GITTENSORY_SITE_URL, matching the acceptance criterion that + // default behavior for deployments without PUBLIC_SITE_ORIGIN configured is unchanged. + it("#4613: falls back to GITTENSORY_SITE_URL when env.PUBLIC_SITE_ORIGIN is genuinely unset", async () => { + const env = envWithKey(); + delete env.PUBLIC_SITE_ORIGIN; // exactOptionalPropertyTypes forbids passing `undefined` via createTestEnv's overrides -- delete instead + await seedInstalledRepo(env, { defaultBranch: "main" }); + await seedProfileData(env); + await seedRepoDocGenerationConfig(env, REPO); + const calls: Array<{ method: string; url: string; body: Record }> = []; + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const url = input.toString(); + if (TOKEN_URL.test(url)) return Response.json({ token: "t" }); + const method = init?.method ?? "GET"; + calls.push({ method, url, body: init?.body ? JSON.parse(String(init.body)) : {} }); + if (url.includes("/pulls?") && method === "GET") return Response.json([]); + if (url.includes("/contents/AGENTS.md") && method === "GET") return new Response("not found", { status: 404 }); + if (url.includes("/contents/CLAUDE.md") && method === "GET") return new Response("not found", { status: 404 }); + if (url.endsWith("/branches/main")) return Response.json({ commit: { sha: "base-commit-sha", commit: { tree: { sha: "base-tree-sha" } } } }); + if (url.endsWith("/git/trees") && method === "POST") return Response.json({ sha: "new-tree-sha" }); + if (url.endsWith("/git/commits") && method === "POST") return Response.json({ sha: "new-commit-sha" }); + if (url.endsWith("/git/refs") && method === "POST") return Response.json({ ref: "refs/heads/gittensory/repo-docs" }); + if (url.endsWith("/repos/owner/widgets/pulls") && method === "POST") return Response.json({ number: 44, html_url: "https://github.com/owner/widgets/pull/44" }); + return new Response("unexpected", { status: 500 }); + }); + const result = await openRepoDocPullRequest(env, REPO, "live"); + expect(result.opened).toBe(true); + + const treeCall = calls.find((c) => c.url.endsWith("/git/trees")); + const tree = treeCall?.body.tree as Array<{ path: string; content: string }>; + const agentsContent = tree.find((entry) => entry.path === "AGENTS.md")?.content; + expect(agentsContent).toContain(`This file is generated by [Gittensory](${GITTENSORY_SITE_URL})`); + }); + it("falls back to a byte-identical CLAUDE.md copy when the target repo rejects a symlink tree entry", async () => { const env = envWithKey(); await seedInstalledRepo(env, { defaultBranch: "main" }); @@ -296,7 +366,7 @@ describe("openRepoDocPullRequest (#3000)", () => { await seedProfileData(env); await seedRepoDocGenerationConfig(env, REPO); const profile = await extractRepoProfile(env, REPO); - const currentContent = renderRepoDocContent(profile)!; + const currentContent = renderRepoDocContent(profile, GITTENSORY_SITE_URL)!; let wroteAnything = false; vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { const url = input.toString(); @@ -411,7 +481,7 @@ describe("openRepoDocPullRequest (#3000)", () => { await seedProfileData(env); await seedRepoDocGenerationConfig(env, REPO); const profile = await extractRepoProfile(env, REPO); - const staleGeneratedSection = renderRepoDocContent(profile)!.replace("`npm run lint`", "an older lint command"); + const staleGeneratedSection = renderRepoDocContent(profile, GITTENSORY_SITE_URL)!.replace("`npm run lint`", "an older lint command"); const currentContent = `# Preamble the maintainer added.\n\n${staleGeneratedSection}\nAn appendix the maintainer added.\n`; const calls: Array<{ method: string; url: string; body: Record }> = []; vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { @@ -530,7 +600,7 @@ describe("openRepoDocPullRequest (#3000)", () => { await seedInstalledRepo(env, { defaultBranch: "main" }); await seedSkillTriggerRepo(env, REPO); const profile = await extractRepoProfile(env, REPO); - const currentAgentsContent = renderRepoDocContent(profile)!; + const currentAgentsContent = renderRepoDocContent(profile, GITTENSORY_SITE_URL)!; const calls: Array<{ method: string; url: string; body: Record }> = []; vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { const url = input.toString(); @@ -562,7 +632,7 @@ describe("openRepoDocPullRequest (#3000)", () => { await seedInstalledRepo(env, { defaultBranch: "main" }); await seedSkillTriggerRepo(env, REPO); const profile = await extractRepoProfile(env, REPO); - const currentAgentsContent = renderRepoDocContent(profile)!; + const currentAgentsContent = renderRepoDocContent(profile, GITTENSORY_SITE_URL)!; const currentSkillContent = renderRepoSkillContent(profile)!; let wroteAnything = false; vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { diff --git a/test/unit/repo-doc-render.test.ts b/test/unit/repo-doc-render.test.ts index 42b016b723..bf79cee126 100644 --- a/test/unit/repo-doc-render.test.ts +++ b/test/unit/repo-doc-render.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { REPO_DOC_MARKER_END, REPO_DOC_MARKER_START, renderRepoDocContent } from "../../src/review/repo-doc-render"; import type { RepoProfile, RepoProfileFileNamingStyle, RepoProfileTestFileConvention } from "../../src/review/repo-profile"; import { REPO_PROFILE_SCHEMA_VERSION } from "../../src/review/repo-profile"; +import { GITTENSORY_SITE_URL } from "../../src/github/footer"; function presentProfile(overrides: Partial> = {}): RepoProfile { return { @@ -20,11 +21,11 @@ function presentProfile(overrides: Partial { it("renders null for an absent profile, never a placeholder document", () => { const profile: RepoProfile = { version: REPO_PROFILE_SCHEMA_VERSION, present: false, repoFullName: "owner/widgets", generatedAt: "now", reason: "no RAG index configured or populated for this repo yet" }; - expect(renderRepoDocContent(profile)).toBeNull(); + expect(renderRepoDocContent(profile, GITTENSORY_SITE_URL)).toBeNull(); }); it("renders the marker, architecture, conventions, commands, and workflow sections for a full profile", () => { - const content = renderRepoDocContent(presentProfile()); + const content = renderRepoDocContent(presentProfile(), GITTENSORY_SITE_URL); expect(content).not.toBeNull(); expect(content!.startsWith(REPO_DOC_MARKER_START)).toBe(true); expect(content!.trimEnd().endsWith(REPO_DOC_MARKER_END)).toBe(true); @@ -43,63 +44,81 @@ describe("renderRepoDocContent (#3000)", () => { expect(content).toContain("Requires a linked issue: no"); expect(content).toContain("- `.github/workflows/ci.yml`"); expect(content).toContain("Generated by Gittensory from this repository's own indexed code."); + expect(content).toContain(`This file is generated by [Gittensory](${GITTENSORY_SITE_URL})`); + }); + + // #4613: a self-hoster's own domain (resolved by the caller from PUBLIC_SITE_ORIGIN) replaces + // GITTENSORY_SITE_URL in the attribution link -- the SAME (profile, siteUrl) pair still renders + // identically on repeat calls, preserving the purity/determinism the diff-aware refresh depends on. + it("#4613: renders the caller-provided siteUrl in the attribution link instead of GITTENSORY_SITE_URL", () => { + const selfHostedUrl = "https://gittensory.example.org"; + const content = renderRepoDocContent(presentProfile(), selfHostedUrl); + expect(content).toContain(`This file is generated by [Gittensory](${selfHostedUrl})`); + expect(content).not.toContain(GITTENSORY_SITE_URL); + }); + + it("#4613: renders byte-identical output for the same (profile, siteUrl) pair on repeat calls", () => { + const selfHostedUrl = "https://gittensory.example.org"; + const a = renderRepoDocContent(presentProfile(), selfHostedUrl); + const b = renderRepoDocContent(presentProfile(), selfHostedUrl); + expect(a).toEqual(b); }); it("renders byte-identical output for the same profile facts regardless of generatedAt, so refresh's no-change check is meaningful", () => { - const a = renderRepoDocContent(presentProfile({ generatedAt: "2026-01-01T00:00:00.000Z" })); - const b = renderRepoDocContent(presentProfile({ generatedAt: "2026-12-31T23:59:59.000Z" })); + const a = renderRepoDocContent(presentProfile({ generatedAt: "2026-01-01T00:00:00.000Z" }), GITTENSORY_SITE_URL); + const b = renderRepoDocContent(presentProfile({ generatedAt: "2026-12-31T23:59:59.000Z" }), GITTENSORY_SITE_URL); expect(a).toEqual(b); }); it("uses singular wording for exactly one indexed file and one top-level directory", () => { - const content = renderRepoDocContent(presentProfile({ architecture: { indexedFileCount: 1, topLevelDirectories: [{ path: "src", fileCount: 1 }] } })); + const content = renderRepoDocContent(presentProfile({ architecture: { indexedFileCount: 1, topLevelDirectories: [{ path: "src", fileCount: 1 }] } }), GITTENSORY_SITE_URL); expect(content).toContain("1 indexed source file across 1 top-level directory:"); expect(content).toContain("- `src` -- 1 file"); }); it("caps the rendered top-level directory list and reports how many were omitted", () => { const topLevelDirectories = Array.from({ length: 15 }, (_, i) => ({ path: `dir${i}`, fileCount: 15 - i })); - const content = renderRepoDocContent(presentProfile({ architecture: { indexedFileCount: 200, topLevelDirectories } })); + const content = renderRepoDocContent(presentProfile({ architecture: { indexedFileCount: 200, topLevelDirectories } }), GITTENSORY_SITE_URL); expect(content).toContain("- `dir11` -- 4 files"); expect(content).not.toContain("`dir12`"); expect(content).toContain("- (3 more, not shown)"); }); it("degrades to 'none detected' for empty build/test/lint command lists", () => { - const content = renderRepoDocContent(presentProfile({ commands: { packageManager: "npm", buildCommands: [], testCommands: [], lintCommands: [] } })); + const content = renderRepoDocContent(presentProfile({ commands: { packageManager: "npm", buildCommands: [], testCommands: [], lintCommands: [] } }), GITTENSORY_SITE_URL); expect(content).toContain("- Build: none detected"); expect(content).toContain("- Test: none detected"); expect(content).toContain("- Lint: none detected"); }); it("falls back to npm as the runner prefix and 'not detected' label when no package manager is known", () => { - const content = renderRepoDocContent(presentProfile({ commands: { packageManager: null, buildCommands: ["build"], testCommands: [], lintCommands: [] } })); + const content = renderRepoDocContent(presentProfile({ commands: { packageManager: null, buildCommands: ["build"], testCommands: [], lintCommands: [] } }), GITTENSORY_SITE_URL); expect(content).toContain("Package manager: not detected"); expect(content).toContain("- Build: `npm run build`"); }); it("renders each non-npm package manager as its own run-command prefix", () => { for (const packageManager of ["yarn", "pnpm", "bun"] as const) { - const content = renderRepoDocContent(presentProfile({ commands: { packageManager, buildCommands: ["build"], testCommands: [], lintCommands: [] } })); + const content = renderRepoDocContent(presentProfile({ commands: { packageManager, buildCommands: ["build"], testCommands: [], lintCommands: [] } }), GITTENSORY_SITE_URL); expect(content).toContain(`Package manager: ${packageManager}`); expect(content).toContain(`- Build: \`${packageManager} run build\``); } }); it("lists multiple commands for the same category as a comma-separated set", () => { - const content = renderRepoDocContent(presentProfile({ commands: { packageManager: "npm", buildCommands: [], testCommands: ["test", "test:watch"], lintCommands: [] } })); + const content = renderRepoDocContent(presentProfile({ commands: { packageManager: "npm", buildCommands: [], testCommands: ["test", "test:watch"], lintCommands: [] } }), GITTENSORY_SITE_URL); expect(content).toContain("- Test: `npm run test`, `npm run test:watch`"); }); it("renders 'none indexed' when no CI workflow files were found", () => { - const content = renderRepoDocContent(presentProfile({ contributionWorkflow: { gatePublishesCheck: false, linkedIssuePolicy: "optional", requireLinkedIssue: false, ciWorkflowFiles: [] } })); + const content = renderRepoDocContent(presentProfile({ contributionWorkflow: { gatePublishesCheck: false, linkedIssuePolicy: "optional", requireLinkedIssue: false, ciWorkflowFiles: [] } }), GITTENSORY_SITE_URL); expect(content).toContain("CI publishes a required check: no"); expect(content).toContain("Requires a linked issue: no"); expect(content).toContain("- none indexed"); }); it("renders 'yes' for requireLinkedIssue when the setting is on", () => { - const content = renderRepoDocContent(presentProfile({ contributionWorkflow: { gatePublishesCheck: true, linkedIssuePolicy: "required", requireLinkedIssue: true, ciWorkflowFiles: [] } })); + const content = renderRepoDocContent(presentProfile({ contributionWorkflow: { gatePublishesCheck: true, linkedIssuePolicy: "required", requireLinkedIssue: true, ciWorkflowFiles: [] } }), GITTENSORY_SITE_URL); expect(content).toContain("Requires a linked issue: yes"); }); @@ -112,7 +131,7 @@ describe("renderRepoDocContent (#3000)", () => { ["unknown", "not detected"], ]; it.each(namingStyles)("renders the file-naming label for %s", (style, label) => { - const content = renderRepoDocContent(presentProfile({ conventions: { fileNamingStyle: style, testFileConvention: "none-detected" } })); + const content = renderRepoDocContent(presentProfile({ conventions: { fileNamingStyle: style, testFileConvention: "none-detected" } }), GITTENSORY_SITE_URL); expect(content).toContain(`File naming: ${label}`); }); @@ -123,7 +142,7 @@ describe("renderRepoDocContent (#3000)", () => { ["none-detected", "not detected"], ]; it.each(testConventions)("renders the test-file-convention label for %s", (convention, label) => { - const content = renderRepoDocContent(presentProfile({ conventions: { fileNamingStyle: "unknown", testFileConvention: convention } })); + const content = renderRepoDocContent(presentProfile({ conventions: { fileNamingStyle: "unknown", testFileConvention: convention } }), GITTENSORY_SITE_URL); expect(content).toContain(`Test files: ${label}`); }); }); diff --git a/test/unit/settings-preview.test.ts b/test/unit/settings-preview.test.ts index 729a11d418..4e14fd177d 100644 --- a/test/unit/settings-preview.test.ts +++ b/test/unit/settings-preview.test.ts @@ -140,7 +140,7 @@ describe("buildRepoSettingsPreview", () => { const base = { repoFullName: repo.fullName, repo, issues, pullRequests }; it("previews a confirmed-miner PR on a healthy install with no warnings", () => { - const preview = buildRepoSettingsPreview({ ...base, settings: settings(), installation: healthyInstall, sample: { authorLogin: "miner", minerStatus: "confirmed" } }); + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings(), installation: healthyInstall, sample: { authorLogin: "miner", minerStatus: "confirmed" } }); expect(preview.decision.willComment).toBe(true); expect(preview.appliedLabel).toBe("gittensor"); expect(preview.settings.blacklistLabel).toBe("slop"); @@ -180,7 +180,7 @@ describe("buildRepoSettingsPreview", () => { }); it("includes the configured blacklist label required by the OpenAPI contract", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ blacklistLabel: "abuse" }), installation: healthyInstall, @@ -192,13 +192,13 @@ describe("buildRepoSettingsPreview", () => { }); it("uses safe defaults for an empty sample preview", () => { - const preview = buildRepoSettingsPreview({ ...base, settings: settings(), installation: healthyInstall, sample: {} }); + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings(), installation: healthyInstall, sample: {} }); expect(preview.sample).toMatchObject({ authorLogin: "sample-contributor", authorType: "User", authorAssociation: "NONE", minerStatus: "confirmed", title: "Sample pull request" }); expect(preview.decision.skipped).toBe(false); }); it("explains a missing Issues: write permission", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings(), installation: { ...healthyInstall, status: "needs_attention", missingPermissions: ["issues"] }, @@ -217,7 +217,7 @@ describe("buildRepoSettingsPreview", () => { // Installation grants issues:write (everything comment/label output actually writes with) but is missing // pull_requests:read, which the app still requires to read PRs. This must be reported without // regressing to the previous overbroad pull_requests:write requirement. - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings(), installation: { ...healthyInstall, missingPermissions: ["pull_requests"] }, @@ -235,7 +235,7 @@ describe("buildRepoSettingsPreview", () => { }); it("explains a missing optional Checks: write permission only when check runs are enabled", () => { - const withChecks = buildRepoSettingsPreview({ + const withChecks = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ checkRunMode: "enabled" }), installation: { ...healthyInstall, status: "needs_attention", missingPermissions: ["checks"] }, @@ -244,7 +244,7 @@ describe("buildRepoSettingsPreview", () => { expect(withChecks.checkRun).toMatchObject({ willCreate: true }); expect(withChecks.warnings.some((warning) => /Checks: write/.test(warning))).toBe(true); - const withoutChecks = buildRepoSettingsPreview({ + const withoutChecks = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ checkRunMode: "off" }), installation: { ...healthyInstall, missingPermissions: ["checks"] }, @@ -258,7 +258,7 @@ describe("buildRepoSettingsPreview", () => { // detected_contributors_only + comment_only comments for confirmed miners, so the repo needs // issues:write regardless of the previewed sample's miner status. Previewing a non-confirmed // author must not drop the required (and missing) issues permission. - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ publicSurface: "comment_only", commentMode: "detected_contributors_only", autoLabelEnabled: false, publicAudienceMode: "oss_maintainer" }), installation: { ...healthyInstall, status: "needs_attention", missingPermissions: ["issues"] }, @@ -270,7 +270,7 @@ describe("buildRepoSettingsPreview", () => { }); it("explains a missing Checks: write permission when the opt-in gate is enabled", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ publicSurface: "off", commentMode: "off", autoLabelEnabled: false, gateCheckMode: "enabled", reviewCheckMode: "required" }), installation: { ...healthyInstall, status: "needs_attention", missingPermissions: ["checks"] }, @@ -284,14 +284,14 @@ describe("buildRepoSettingsPreview", () => { }); it("shows a quiet skip for a non-miner author with no rendered comment", () => { - const preview = buildRepoSettingsPreview({ ...base, settings: settings({ publicAudienceMode: "gittensor_only" }), installation: healthyInstall, sample: { authorLogin: "drive-by", minerStatus: "not_found" } }); + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ publicAudienceMode: "gittensor_only" }), installation: healthyInstall, sample: { authorLogin: "drive-by", minerStatus: "not_found" } }); expect(preview.decision).toMatchObject({ skipped: true, skipReason: "not_official_gittensor_miner" }); expect(preview.previewComment).toBeNull(); expect(preview.appliedLabel).toBeNull(); }); it("warns that label-only mode still needs Issues: write", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ publicSurface: "label_only", autoLabelEnabled: true, commentMode: "off" }), installation: { ...healthyInstall, status: "needs_attention", missingPermissions: ["issues"] }, @@ -304,13 +304,13 @@ describe("buildRepoSettingsPreview", () => { }); it("shows the default maintainer-author skip", () => { - const preview = buildRepoSettingsPreview({ ...base, settings: settings(), installation: healthyInstall, sample: { authorLogin: "owner", authorAssociation: "OWNER", minerStatus: "confirmed" } }); + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings(), installation: healthyInstall, sample: { authorLogin: "owner", authorAssociation: "OWNER", minerStatus: "confirmed" } }); expect(preview.decision.skipReason).toBe("maintainer_author"); expect(preview.previewComment).toBeNull(); }); it("warns when installation health is unknown", () => { - const preview = buildRepoSettingsPreview({ ...base, settings: settings(), installation: null, sample: { authorLogin: "miner", minerStatus: "confirmed" } }); + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings(), installation: null, sample: { authorLogin: "miner", minerStatus: "confirmed" } }); expect(preview.warnings.some((warning) => /Installation health is unknown/.test(warning))).toBe(true); expect(preview.installPreview).toMatchObject({ status: "blocked", @@ -320,7 +320,7 @@ describe("buildRepoSettingsPreview", () => { }); it("explains missing webhook event subscriptions", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings(), installation: { ...healthyInstall, status: "needs_attention", missingEvents: ["pull_request"] }, @@ -331,7 +331,7 @@ describe("buildRepoSettingsPreview", () => { }); it("falls back to the installation status warning when no specific remediation is available", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings(), installation: { ...healthyInstall, status: "broken" }, @@ -343,7 +343,7 @@ describe("buildRepoSettingsPreview", () => { }); it("marks broad all-PR output as needing maintainer attention before enablement", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ commentMode: "all_prs" }), installation: healthyInstall, @@ -361,7 +361,7 @@ describe("buildRepoSettingsPreview", () => { }); it("never leaks private scoring/trust terms into the preview comment (sanitizer regression)", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings(), installation: healthyInstall, @@ -373,7 +373,7 @@ describe("buildRepoSettingsPreview", () => { }); it("reports a generic needs-attention summary when health is degraded but no permission or event is missing", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings(), installation: { ...healthyInstall, status: "needs_attention", missingPermissions: [], missingEvents: [] }, @@ -385,7 +385,7 @@ describe("buildRepoSettingsPreview", () => { }); it("requires no issues/checks write scope and lists a no-output sample when every public action is disabled", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ publicSurface: "label_only", autoLabelEnabled: false, commentMode: "off", checkRunMode: "off" }), installation: healthyInstall, @@ -405,7 +405,7 @@ describe("buildRepoSettingsPreview", () => { const readEntries = Object.entries(REQUIRED_INSTALLATION_PERMISSIONS).filter(([, v]) => v === "read"); const expectedReadPerms = readEntries.map(([k, v]) => `${k}: ${v}`).sort(); - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ publicSurface: "label_only", autoLabelEnabled: false, commentMode: "off", checkRunMode: "off" }), installation: healthyInstall, @@ -417,7 +417,7 @@ describe("buildRepoSettingsPreview", () => { }); it("REGRESSION: merge autonomy requires contents: write in the install preview", () => { - const preview = buildRepoSettingsPreview({ + const preview = buildRepoSettingsPreview({env: {}, ...base, settings: settings({ publicSurface: "label_only", autoLabelEnabled: false, commentMode: "off", checkRunMode: "off", autonomy: { merge: "auto" } }), installation: { ...healthyInstall, missingPermissions: ["contents"] }, diff --git a/test/unit/signals-coverage.test.ts b/test/unit/signals-coverage.test.ts index 10c7717602..8623f0e32a 100644 --- a/test/unit/signals-coverage.test.ts +++ b/test/unit/signals-coverage.test.ts @@ -746,7 +746,7 @@ describe("signal coverage edge cases", () => { [], [], ); - const comment = buildPublicPrIntelligenceComment({ + const comment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: prRecord, profile, @@ -769,7 +769,7 @@ describe("signal coverage edge cases", () => { expect(comment).toMatch(/Readiness score: \d+\/100/); expect(comment).not.toMatch(/reward|wallet|hotkey|trust score|farming|critical private/i); - const maintainerComment = buildPublicPrIntelligenceComment({ + const maintainerComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: { ...prRecord, authorLogin: "owner", authorAssociation: "OWNER", linkedIssues: [1], body: "Fixes #1" }, profile: buildContributorProfile("owner", { login: "owner", topLanguages: ["TypeScript"], source: "github" }, [], []), @@ -850,6 +850,7 @@ describe("signal coverage edge cases", () => { const directRepo = repo("owner/disabled-autonomy"); const collisions = buildCollisionReport(directRepo.fullName, [], []); const baseArgs = { + env: {}, repo: directRepo, pr: pr(directRepo.fullName, 90, "Fix cache", { authorLogin: "miner", linkedIssues: [42], body: "Fixes #42" }), profile: buildContributorProfile("miner", { login: "miner", topLanguages: ["TypeScript"], source: "github" }, [], []), @@ -904,6 +905,7 @@ describe("signal coverage edge cases", () => { const preflightFor = (target: PullRequestRecord) => buildPreflightResult({ repoFullName: directRepo.fullName, title: target.title, body: target.body ?? undefined, linkedIssues: target.linkedIssues }, directRepo, [dupIssue], [winnerPr, loserPr]); const baseFor = (target: PullRequestRecord) => ({ + env: {}, repo: directRepo, pr: target, profile, @@ -981,6 +983,7 @@ describe("signal coverage edge cases", () => { ], }; const baseArgs = { + env: {}, repo: directRepo, pr: winnerPr, profile: buildContributorProfile("miner", { login: "miner", topLanguages: ["TypeScript"], source: "github" }, [], []), @@ -1045,7 +1048,7 @@ describe("signal coverage edge cases", () => { const detection = { detected: true, source: "github_cache" as const, reason: "cached contributor", priorPullRequests: 1, priorMergedPullRequests: 0, priorIssues: 0 }; const gateSettings = { ...repoSettings(directRepo.fullName), gateCheckMode: "enabled" as const, reviewCheckMode: "required" as const, duplicatePrGateMode: "block" as const }; - const collisionComment = buildPublicPrIntelligenceComment({ + const collisionComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: currentPr, profile, @@ -1067,7 +1070,7 @@ describe("signal coverage edge cases", () => { // The always-on earn CTA footer is a permanent marketing surface on every PR. expect(collisionComment).toContain("register to start earning"); - const repoBlockedComment = buildPublicPrIntelligenceComment({ + const repoBlockedComment = buildPublicPrIntelligenceComment({env: {}, repo: null, pr: { ...currentPr, linkedIssues: [99], body: "Fixes #99" }, profile, @@ -1088,7 +1091,7 @@ describe("signal coverage edge cases", () => { expect(repoBlockedComment).toContain("> | Gate result | ⚠️ Not blocking | Advisory; not blocking this PR. | No action. |"); expect(repoBlockedComment).not.toContain("App action required"); - const missingIssueComment = buildPublicPrIntelligenceComment({ + const missingIssueComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: { ...currentPr, linkedIssues: [], body: "No linked issue yet." }, profile, @@ -1108,7 +1111,7 @@ describe("signal coverage edge cases", () => { expect(missingIssueComment).toContain("> | Linked issue | ⚠️ Missing | No linked issue or no-issue rationale found. | Explain no-issue PR. |"); expect(missingIssueComment).toContain("Explain no-issue PR."); - const passingGateComment = buildPublicPrIntelligenceComment({ + const passingGateComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: { ...currentPr, linkedIssues: [99], body: "Fixes #99" }, profile, @@ -1129,7 +1132,7 @@ describe("signal coverage edge cases", () => { expect(passingGateComment).toContain("Public GitHub metadata was checked"); // .gittensory.yml review overrides: custom footer lead, an intro note, and a hidden row. - const customizedComment = buildPublicPrIntelligenceComment({ + const customizedComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: { ...currentPr, linkedIssues: [99], body: "Fixes #99" }, profile, @@ -1157,7 +1160,7 @@ describe("signal coverage edge cases", () => { expect(nitsIndex).toBeGreaterThan(summaryIndex); expect(readinessIndex).toBeGreaterThan(nitsIndex); - const aiBlockedComment = buildPublicPrIntelligenceComment({ + const aiBlockedComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: { ...currentPr, linkedIssues: [99], body: "Fixes #99" }, profile, @@ -1173,7 +1176,7 @@ describe("signal coverage edge cases", () => { expect(aiBlockedComment).toContain("`src/a.ts` has a syntax error."); expect(aiBlockedComment.indexOf("**Review summary**")).toBeLessThan(aiBlockedComment.indexOf("**Readiness score:")); - const aiExplicitNoBlockersComment = buildPublicPrIntelligenceComment({ + const aiExplicitNoBlockersComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: { ...currentPr, linkedIssues: [99], body: "Fixes #99" }, profile, @@ -1189,7 +1192,7 @@ describe("signal coverage edge cases", () => { "Gittensory review found blockers", ); - const advisoryOnlyComment = buildPublicPrIntelligenceComment({ + const advisoryOnlyComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: { ...currentPr, linkedIssues: [99], body: "Fixes #99" }, profile, @@ -1213,7 +1216,7 @@ describe("signal coverage edge cases", () => { expect(advisoryOnlyComment).toContain("Validation note missing"); expect(advisoryOnlyComment).toContain("> | Gate result | ⚠️ Advisory only | Advisory only. | No action. |"); - const actionRequiredComment = buildPublicPrIntelligenceComment({ + const actionRequiredComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: { ...currentPr, linkedIssues: [99], body: "Fixes #99" }, profile, @@ -1233,7 +1236,7 @@ describe("signal coverage edge cases", () => { expect(actionRequiredComment).toContain("Gittensory cannot evaluate this PR until installation state is repaired."); expect(actionRequiredComment).toContain("> | Gate result | ⚠️ App action required | Install/config needs attention. | Fix app config. |"); - const duplicateAdvisoryComment = buildPublicPrIntelligenceComment({ + const duplicateAdvisoryComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: currentPr, profile, @@ -1252,7 +1255,7 @@ describe("signal coverage edge cases", () => { reason: "Titles share 2 meaningful terms.", items: [{ type: "issue", number: index + 100, title: `Related issue ${index}`, authorLogin: "reporter", labels: [], linkedIssues: [] }], })); - const scopedComment = buildPublicPrIntelligenceComment({ + const scopedComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: { ...currentPr, linkedIssues: [99], body: "Fixes #99" }, profile, @@ -1304,7 +1307,7 @@ describe("signal coverage edge cases", () => { })); const profile = buildContributorProfile("dev", { login: "dev", topLanguages: [], source: "github" }, [currentPr], []); const detection = { detected: true, source: "github_cache" as const, reason: "cached contributor", priorPullRequests: 1, priorMergedPullRequests: 0, priorIssues: 0 }; - const comment = buildPublicPrIntelligenceComment({ + const comment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: currentPr, profile, @@ -1343,7 +1346,7 @@ describe("signal coverage edge cases", () => { expect(collisions.summary.clusterCount).toBeGreaterThan(0); expect(preflight.collisions).toHaveLength(0); - const comment = buildPublicPrIntelligenceComment({ + const comment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: currentPr, profile: buildContributorProfile("dev", { login: "dev", topLanguages: ["Markdown"], source: "github" }, [], []), @@ -1372,7 +1375,7 @@ describe("signal coverage edge cases", () => { [currentPr], ); - const comment = buildPublicPrIntelligenceComment({ + const comment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: currentPr, profile: buildContributorProfile("newcomer", { login: "newcomer", topLanguages: [], source: "github" }, [], []), @@ -1402,7 +1405,7 @@ describe("signal coverage edge cases", () => { [], [currentPr], ); - const officialComment = buildPublicPrIntelligenceComment({ + const officialComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: currentPr, profile, @@ -1426,7 +1429,7 @@ describe("signal coverage edge cases", () => { { id: "recent-merged", risk: "medium", reason: "Recent merged work is related.", items: [selfItem, { type: "recent_merged_pull_request" as const, number: 11, title: "Merged edge fix" }] }, ], }; - const edgeComment = buildPublicPrIntelligenceComment({ + const edgeComment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: currentPr, profile, @@ -1489,7 +1492,7 @@ describe("signal coverage edge cases", () => { buildCollisionReport(directRepo.fullName, [], []), ); const settings = { ...repoSettings(directRepo.fullName), gateCheckMode: "enabled" as const, reviewCheckMode: "required" as const, qualityGateMode: "block" as const, qualityGateMinScore: 95 }; - const comment = buildPublicPrIntelligenceComment({ + const comment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: currentPr, profile, @@ -1850,7 +1853,7 @@ describe("signal coverage edge cases", () => { { code: "private_reward", severity: "warning" as const, title: "Reward wallet", detail: "wallet reward", action: "secret" }, ], }; - const comment = buildPublicPrIntelligenceComment({ + const comment = buildPublicPrIntelligenceComment({env: {}, repo: directRepo, pr: currentPr, profile, diff --git a/test/unit/signals-v2.test.ts b/test/unit/signals-v2.test.ts index d88a90f5c7..284306c207 100644 --- a/test/unit/signals-v2.test.ts +++ b/test/unit/signals-v2.test.ts @@ -2086,7 +2086,7 @@ describe("v2 signal builders", () => { ); expect(activeBounty).toMatchObject({ lifecycle: "active", fundingStatus: "funded", consensusRisk: "medium" }); - const comment = buildPublicPrIntelligenceComment({ + const comment = buildPublicPrIntelligenceComment({env: {}, repo, pr: { ...pullRequests[0]!, authorLogin: undefined, linkedIssues: [] }, profile: noLanguageProfile, diff --git a/test/unit/signals.test.ts b/test/unit/signals.test.ts index 9602f5025e..05e874b273 100644 --- a/test/unit/signals.test.ts +++ b/test/unit/signals.test.ts @@ -540,7 +540,7 @@ describe("world-class backend signals", () => { currentPr, priorPr, ], []); - const comment = buildPublicPrIntelligenceComment({ repo, pr: currentPr, profile, detection, queueHealth, collisions, preflight, settings }); + const comment = buildPublicPrIntelligenceComment({env: {}, repo, pr: currentPr, profile, detection, queueHealth, collisions, preflight, settings }); expect(detection.detected).toBe(true); expect(shouldPublishPrIntelligenceComment(settings, detection)).toBe(true); @@ -596,11 +596,11 @@ describe("world-class backend signals", () => { const detectedProfile = buildContributorProfile("oktofeesh1", { login: "oktofeesh1", topLanguages: ["TypeScript"], source: "github" }, [currentPr, priorPr], []); expect(detected.detected).toBe(true); - const registeredComment = buildPublicPrIntelligenceComment({ repo, pr: currentPr, profile: detectedProfile, detection: detected, queueHealth, collisions, preflight, settings }); + const registeredComment = buildPublicPrIntelligenceComment({env: {}, repo, pr: currentPr, profile: detectedProfile, detection: detected, queueHealth, collisions, preflight, settings }); expect(registeredComment).toContain(repoEarnPage); const unregisteredRepo = { ...repo, isRegistered: false, registryConfig: null }; - const unregisteredComment = buildPublicPrIntelligenceComment({ repo: unregisteredRepo, pr: currentPr, profile: detectedProfile, detection: detected, queueHealth, collisions, preflight, settings }); + const unregisteredComment = buildPublicPrIntelligenceComment({env: {}, repo: unregisteredRepo, pr: currentPr, profile: detectedProfile, detection: detected, queueHealth, collisions, preflight, settings }); expect(unregisteredComment).not.toContain("/miners/repository"); expect(unregisteredComment).toContain(homeCta); @@ -609,10 +609,10 @@ describe("world-class backend signals", () => { const undetectedProfile = buildContributorProfile("brand-new-outsider", { login: "brand-new-outsider", topLanguages: [], source: "github" }, [], []); expect(undetected.detected).toBe(false); - const minimalRegistered = buildPublicPrIntelligenceComment({ repo, pr: currentPr, profile: undetectedProfile, detection: undetected, queueHealth, collisions, preflight, settings }); + const minimalRegistered = buildPublicPrIntelligenceComment({env: {}, repo, pr: currentPr, profile: undetectedProfile, detection: undetected, queueHealth, collisions, preflight, settings }); expect(minimalRegistered).toContain(repoEarnPage); - const minimalUnregistered = buildPublicPrIntelligenceComment({ repo: unregisteredRepo, pr: currentPr, profile: undetectedProfile, detection: undetected, queueHealth, collisions, preflight, settings }); + const minimalUnregistered = buildPublicPrIntelligenceComment({env: {}, repo: unregisteredRepo, pr: currentPr, profile: undetectedProfile, detection: undetected, queueHealth, collisions, preflight, settings }); expect(minimalUnregistered).not.toContain("/miners/repository"); expect(minimalUnregistered).toContain(homeCta); }); @@ -931,7 +931,7 @@ describe("world-class backend signals", () => { aiReviewAllAuthors: false, closeOwnerAuthors: false, }; - const comment = buildPublicPrIntelligenceComment({ repo, pr: currentPr, profile, detection, queueHealth, collisions, preflight, settings }); + const comment = buildPublicPrIntelligenceComment({env: {}, repo, pr: currentPr, profile, detection, queueHealth, collisions, preflight, settings }); expect(comment).toContain("| Linked issue | ⚠️ Missing | No linked issue or no-issue rationale found. | Explain no-issue PR. |"); expect(comment).toContain("Public profile languages: not available"); @@ -1004,7 +1004,7 @@ describe("world-class backend signals", () => { const currentPr: PullRequestRecord = { ...pullRequests[0]!, body: "Fixes #7", linkedIssues: [7] }; const publicPreflight = buildPreflightResult({ repoFullName: repo.fullName, title: currentPr.title, body: currentPr.body ?? undefined, linkedIssues: [7] }, repo, [openIssue], [], [completed]); - const publicComment = buildPublicPrIntelligenceComment({ + const publicComment = buildPublicPrIntelligenceComment({env: {}, repo, pr: currentPr, profile: buildContributorProfile("oktofeesh1", { login: "oktofeesh1", topLanguages: ["TypeScript"], source: "github" }, [currentPr], []), diff --git a/test/unit/unified-comment-parity.test.ts b/test/unit/unified-comment-parity.test.ts index 9e85dd9692..b3cc5850a5 100644 --- a/test/unit/unified-comment-parity.test.ts +++ b/test/unit/unified-comment-parity.test.ts @@ -150,7 +150,7 @@ describe("converged comment ↔ legacy panel parity (#unified-comment)", () => { it("the public-safe collapsible bodies are byte-identical to the legacy panel's
bodies", () => { const { currentPr, detection, collisions, queueHealth, preflight, profile } = buildFixtures(); const aiReview = { notes: "Looks reasonable. Add a regression test for reconnect.", reviewerCount: 2 }; - const legacy = buildPublicPrIntelligenceComment({ repo, pr: currentPr, profile, detection, queueHealth, collisions, preflight, settings, aiReview }); + const legacy = buildPublicPrIntelligenceComment({env: {}, repo, pr: currentPr, profile, detection, queueHealth, collisions, preflight, settings, aiReview }); const collapsibles = buildPublicSafeCollapsibles({ repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth }); // Each shared collapsible body's individual lines must appear verbatim in the legacy panel so the two @@ -168,7 +168,7 @@ describe("converged comment ↔ legacy panel parity (#unified-comment)", () => { it("the legacy panel still renders 'Maintainer notes' inline (private section is unchanged, just not shared)", () => { const { currentPr, detection, collisions, queueHealth, preflight, profile } = buildFixtures(); - const legacy = buildPublicPrIntelligenceComment({ repo, pr: currentPr, profile, detection, queueHealth, collisions, preflight, settings }); + const legacy = buildPublicPrIntelligenceComment({env: {}, repo, pr: currentPr, profile, detection, queueHealth, collisions, preflight, settings }); expect(legacy).toContain("Maintainer notes"); });