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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions src/github/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
suggestCommand as suggestCommandFromCatalog,
type CommandSuggestCatalog,
} from "./command-suggest";
import { gittensoryFooter, GITTENSORY_SITE_URL, type GittensoryFooterEnv } from "./footer";
import { commandReferenceUrl, gittensoryFooter, type GittensoryFooterEnv } from "./footer";
import type { AgentRunBundle } from "../services/agent-orchestrator";
import type { ChatQaResult } from "../services/ai-chat-qa";
import type { GittensorContributorSnapshot, OfficialGittensorMinerDetection } from "../gittensor/api";
Expand Down Expand Up @@ -930,20 +930,6 @@ function actionCommandHelpSections(): string[] {
];
}

/** The public command-reference doc link for `@gittensory help` (#4670). `new URL(path, origin)` -- same
* idiom as the sibling `maintainerControlPanelUrl` in footer.ts -- so a `PUBLIC_SITE_ORIGIN` with or
* without a trailing slash both resolve correctly instead of risking a double slash from naive
* concatenation. Falls back to the literal path string only if origin resolution itself throws (an
* operator-misconfigured PUBLIC_SITE_ORIGIN should degrade the link, not crash comment rendering). */
function commandReferenceUrl(env: GittensoryFooterEnv): string {
const origin = env.PUBLIC_SITE_ORIGIN ?? GITTENSORY_SITE_URL;
try {
return new URL("/docs/gittensory-commands", origin).toString();
} catch {
return `${GITTENSORY_SITE_URL}/docs/gittensory-commands`;
}
}

function helpSections(env: GittensoryFooterEnv, unknownVerb?: string | undefined): string[] {
return [
"**Commands**",
Expand Down
17 changes: 17 additions & 0 deletions src/github/footer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,23 @@ export function maintainerControlPanelUrl(env: { PUBLIC_SITE_ORIGIN?: string | u
return null;
}
}
/** The public command-reference doc link for `@gittensory help` (#4670) and the "[BETA] Chat with
* Gittensory" public-safe collapsible (#5078). Lives here (not commands.ts/engine.ts) so both can import
* the SAME resolved URL without a circular module edge -- commands.ts already imports from signals/engine.ts,
* so engine.ts importing back from commands.ts would cycle. `new URL(path, origin)` -- same idiom as the
* sibling `maintainerControlPanelUrl` above -- so a `PUBLIC_SITE_ORIGIN` with or without a trailing slash
* both resolve correctly instead of risking a double slash from naive concatenation. Falls back to the
* literal path string only if origin resolution itself throws (an operator-misconfigured PUBLIC_SITE_ORIGIN
* should degrade the link, not crash comment rendering). */
export function commandReferenceUrl(env: GittensoryFooterEnv): string {
const origin = env.PUBLIC_SITE_ORIGIN ?? GITTENSORY_SITE_URL;
try {
return new URL("/docs/gittensory-commands", origin).toString();
} catch {
return `${GITTENSORY_SITE_URL}/docs/gittensory-commands`;
}
}

/** The Gittensor network — where GitHub contributors register to earn for their contributions. */
export const GITTENSOR_HOME_URL = "https://gittensor.io";

Expand Down
1 change: 1 addition & 0 deletions src/queue/processors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10022,6 +10022,7 @@ async function maybePublishPrPublicSurface(
// function's own e2eTestGenAvailable const), rather than a second detection pass.
...(missingTestsFinding !== undefined ? { missingTestsFinding } : {}),
e2eTestGenAvailable,
env,
}),
footerMarkdown: gittensoryFooter(env, {
earnUrl: repo?.isRegistered
Expand Down
32 changes: 30 additions & 2 deletions src/signals/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type {
ScoringModelSnapshotRecord,
} from "../types";
import type { PublicContributorProfile } from "../github/public";
import { gittensoryFooter, gittensorRepoEarnUrl, type GittensoryFooterEnv } from "../github/footer";
import { commandReferenceUrl, 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";
Expand Down Expand Up @@ -4154,6 +4154,10 @@ type PublicSafeCollapsibleArgs = {
* resolveConvergedFeature("e2eTests") check the checkbox itself is gated on) -- controls whether the
* collapsible below points the reader at the checkbox, or just states the gap with no next step. */
e2eTestGenAvailable?: boolean | undefined;
/** #5078: resolved by the caller from `env.PUBLIC_SITE_ORIGIN`, same as `buildPublicPrIntelligenceComment`'s
* own `env` param -- lets the "[BETA] Chat with Gittensory" collapsible link to a self-hoster's own
* command-reference doc page instead of always the canonical gittensory.aethereal.dev. */
env: GittensoryFooterEnv;
};

/** "Signal definitions" body — a static legend for the readiness signals. No inputs. */
Expand Down Expand Up @@ -4184,6 +4188,28 @@ function testCoverageBody(args: PublicSafeCollapsibleArgs): string[] {
];
}

/** "[BETA] Chat with Gittensory" body (#5078) — empty (thus invisible, same convention as "Test coverage"
* above) unless `chatQa` or `intentRouting` is enabled for this repo, so a repo that hasn't opted into
* either capability never advertises a command that would just decline. Descriptions are byte-identical
* to `@gittensory help`'s own listing (helpSections in github/commands.ts) so the two surfaces never
* describe the same commands differently. */
function chatBetaBody(args: PublicSafeCollapsibleArgs): string[] {
const chatQaEnabled = args.settings.advisoryAiRouting?.chatQa === true;
const intentRoutingEnabled = args.settings.advisoryAiRouting?.intentRouting === true;
if (!chatQaEnabled && !intentRoutingEnabled) return [];
return [
"Ask Gittensory a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.",
"",
"- `@gittensory ask <question>` answers contribution-quality Q&A with source citations and freshness.",
"- `@gittensory chat <question>` answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).",
...(intentRoutingEnabled
? ["- A plain-language `@gittensory` mention with a real question is routed to the closest matching read-only command automatically -- no exact syntax required."]
: []),
"",
`Full command reference: ${commandReferenceUrl(args.env)}`,
];
}

/** "Review context" body — public author/role/lane/profile context plus any PR-specific overlap detail. */
function reviewContextBody(args: PublicSafeCollapsibleArgs): string[] {
const roleContext = buildRoleContext({
Expand Down Expand Up @@ -4227,9 +4253,11 @@ export function buildPublicSafeCollapsibles(args: PublicSafeCollapsibleArgs): Un
{ title: "Review context", body: reviewContextBody(args).join("\n") },
{ title: "Contributor next steps", body: contributorNextStepsBody(publicSafeNextSteps(args)).join("\n") },
{ title: "Signal definitions", body: signalDefinitionsBody().join("\n") },
// #4589: last, after Signal definitions -- empty (thus invisible, per the caller's empty-body skip) unless
// #4589: after Signal definitions -- empty (thus invisible, per the caller's empty-body skip) unless
// there's an actual coverage gap AND the generate-tests checkbox is available for this repo.
{ title: "Test coverage", body: testCoverageBody(args).join("\n") },
// #5078: last -- empty (thus invisible) unless chatQa or intentRouting is enabled for this repo.
{ title: "[BETA] Chat with Gittensory", body: chatBetaBody(args).join("\n") },
];
}

Expand Down
68 changes: 59 additions & 9 deletions test/unit/unified-comment-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
detectGittensorContributor,
} from "../../src/signals/engine";
import { buildUnifiedCommentBody } from "../../src/review/unified-comment-bridge";
import { GITTENSORY_SITE_URL } from "../../src/github/footer";
import type { GateCheckEvaluation } from "../../src/rules/advisory";
import type { IssueRecord, PullRequestRecord, RepositoryRecord, RepositorySettings } from "../../src/types";

Expand Down Expand Up @@ -119,7 +120,7 @@ describe("converged comment ↔ legacy panel parity (#unified-comment)", () => {
reviewerCount: aiReview.reviewerCount,
footerMarkdown: "💰 Earn for open-source contributions like this. Checked by Gittensory.",
reRunLabel: "gittensory-pr-panel:retrigger Re-run Gittensory review",
extraCollapsibles: buildPublicSafeCollapsibles({ repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth }),
extraCollapsibles: buildPublicSafeCollapsibles({ repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth, env: {} }),
});

// The three public-safe sections the legacy panel carried must survive into the converged comment.
Expand All @@ -132,15 +133,19 @@ describe("converged comment ↔ legacy panel parity (#unified-comment)", () => {
expect(body).not.toContain("Maintainer notes");
// #4589: no coverage gap was supplied here, so "Test coverage" stays an empty (thus invisible) collapsible.
expect(body).not.toContain("Test coverage");
// #5078: advisoryAiRouting isn't set in the base `settings` fixture, so the beta chat collapsible stays empty too.
expect(body).not.toContain("[BETA] Chat with Gittensory");
});

it("never includes a duplicate AI 'Review details' collapsible", () => {
const { currentPr, detection, collisions, queueHealth, preflight, profile } = buildFixtures();
const collapsibles = buildPublicSafeCollapsibles({ repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth });
// #4589: "Test coverage" is always present (title-wise) after Signal definitions, but its body is empty
// (thus invisible when rendered) whenever missingTestsFinding/e2eTestGenAvailable aren't supplied, as here.
expect(collapsibles.map((section) => section.title)).toEqual(["Review context", "Contributor next steps", "Signal definitions", "Test coverage"]);
const collapsibles = buildPublicSafeCollapsibles({ repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth, env: {} });
// #4589/#5078: "Test coverage" and "[BETA] Chat with Gittensory" are always present (title-wise) after
// Signal definitions, but their bodies are empty (thus invisible when rendered) whenever their respective
// gating inputs aren't supplied, as here.
expect(collapsibles.map((section) => section.title)).toEqual(["Review context", "Contributor next steps", "Signal definitions", "Test coverage", "[BETA] Chat with Gittensory"]);
expect(collapsibles.find((section) => section.title === "Test coverage")?.body).toBe("");
expect(collapsibles.find((section) => section.title === "[BETA] Chat with Gittensory")?.body).toBe("");
expect(collapsibles.map((section) => section.title)).not.toContain("Review details");
// No section may carry the private maintainer-notes content.
expect(collapsibles.map((section) => section.title)).not.toContain("Maintainer notes");
Expand All @@ -151,7 +156,7 @@ describe("converged comment ↔ legacy panel parity (#unified-comment)", () => {
const { currentPr, detection, collisions, queueHealth, preflight, profile } = buildFixtures();
const aiReview = { notes: "Looks reasonable. Add a regression test for reconnect.", reviewerCount: 2 };
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 });
const collapsibles = buildPublicSafeCollapsibles({ repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth, env: {} });

// Each shared collapsible body's individual lines must appear verbatim in the legacy panel so the two
// renderers can never diverge on the public-safe content.
Expand Down Expand Up @@ -179,7 +184,7 @@ describe("converged comment ↔ legacy panel parity (#unified-comment)", () => {
it("renders the gap detail + a pointer to the checkbox when a coverage gap exists AND e2eTests is available", () => {
const { currentPr, detection, collisions, queueHealth, preflight, profile } = buildFixtures();
const collapsibles = buildPublicSafeCollapsibles({
repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth,
repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth, env: {},
missingTestsFinding: { detail: "No changed test files or passing validation evidence were detected for this PR." },
e2eTestGenAvailable: true,
});
Expand All @@ -192,7 +197,7 @@ describe("converged comment ↔ legacy panel parity (#unified-comment)", () => {
it("stays empty when a coverage gap exists but e2eTests is NOT available for this repo", () => {
const { currentPr, detection, collisions, queueHealth, preflight, profile } = buildFixtures();
const collapsibles = buildPublicSafeCollapsibles({
repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth,
repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth, env: {},
missingTestsFinding: { detail: "No changed test files or passing validation evidence were detected for this PR." },
e2eTestGenAvailable: false,
});
Expand All @@ -202,10 +207,55 @@ describe("converged comment ↔ legacy panel parity (#unified-comment)", () => {
it("stays empty when e2eTests is available but there is no coverage gap to report", () => {
const { currentPr, detection, collisions, queueHealth, preflight, profile } = buildFixtures();
const collapsibles = buildPublicSafeCollapsibles({
repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth,
repo, pr: currentPr, profile, detection, settings, collisions, preflight, queueHealth, env: {},
e2eTestGenAvailable: true,
});
expect(collapsibles.find((section) => section.title === "Test coverage")?.body).toBe("");
});
});

// #5078: the "[BETA] Chat with Gittensory" collapsible points readers at the ask/chat commands -- empty
// (thus invisible) unless the repo has opted into chatQa or intentRouting, mirroring #4589's own
// "never mention a command that would bounce" principle.
describe("[BETA] Chat with Gittensory collapsible (#5078)", () => {
const advisoryAiRoutingAllOff = {
slop: false, e2eTestGen: false, planner: false, summaries: false,
chatQa: false, chatQaFrontierFallback: false, intentRouting: false,
};

it("stays empty when neither chatQa nor intentRouting is enabled", () => {
const { currentPr, detection, collisions, queueHealth, preflight, profile } = buildFixtures();
const collapsibles = buildPublicSafeCollapsibles({
repo, pr: currentPr, profile, detection, collisions, preflight, queueHealth, env: {},
settings: { ...settings, advisoryAiRouting: advisoryAiRoutingAllOff },
});
expect(collapsibles.find((section) => section.title === "[BETA] Chat with Gittensory")?.body).toBe("");
});

it("renders ask/chat usage + the docs link when chatQa is enabled", () => {
const { currentPr, detection, collisions, queueHealth, preflight, profile } = buildFixtures();
const collapsibles = buildPublicSafeCollapsibles({
repo, pr: currentPr, profile, detection, collisions, preflight, queueHealth,
env: { PUBLIC_SITE_ORIGIN: "https://example-selfhost.test" },
settings: { ...settings, advisoryAiRouting: { ...advisoryAiRoutingAllOff, chatQa: true } },
});
const beta = collapsibles.find((section) => section.title === "[BETA] Chat with Gittensory");
expect(beta?.body).toContain("`@gittensory ask <question>`");
expect(beta?.body).toContain("`@gittensory chat <question>`");
expect(beta?.body).toContain("https://example-selfhost.test/docs/gittensory-commands");
// Intent routing is off in this fixture, so its plain-language line must not appear.
expect(beta?.body).not.toContain("Plain-language");
});

it("renders + mentions plain-language routing when intentRouting is enabled, even with chatQa off", () => {
const { currentPr, detection, collisions, queueHealth, preflight, profile } = buildFixtures();
const collapsibles = buildPublicSafeCollapsibles({
repo, pr: currentPr, profile, detection, collisions, preflight, queueHealth, env: {},
settings: { ...settings, advisoryAiRouting: { ...advisoryAiRoutingAllOff, intentRouting: true } },
});
const beta = collapsibles.find((section) => section.title === "[BETA] Chat with Gittensory");
expect(beta?.body).toContain("routed to the closest matching read-only command");
expect(beta?.body).toContain(GITTENSORY_SITE_URL);
});
});
});