diff --git a/apps/gittensory-ui/src/routes/docs.tuning.tsx b/apps/gittensory-ui/src/routes/docs.tuning.tsx
index 86b5d47bf0..46bac21749 100644
--- a/apps/gittensory-ui/src/routes/docs.tuning.tsx
+++ b/apps/gittensory-ui/src/routes/docs.tuning.tsx
@@ -157,9 +157,11 @@ function Tuning() {
GITTENSORY_REVIEW_MEMORY — repeat-false-positive suppression: matches an
advisory (non-blocking) AI finding against this repo's stored suppression signals (a
maintainer's own past false-positive dismissals) and demotes or drops it before the
- unified comment renders. Advisory-only by construction — never applied to gate blockers,
- so it can never change the merge/close disposition. Also requires the per-repo{" "}
- review.memory: true opt-in in .gittensory.yml. Per-PR.
+ unified comment renders. A maintainer records a signal with{" "}
+ @gittensory resolve [finding-code] (or a whole-PR{" "}
+ @gittensory resolve ack). Advisory-only by construction — never applied to
+ gate blockers, so it can never change the merge/close disposition. Also requires the
+ per-repo review.memory: true opt-in in .gittensory.yml. Per-PR.
GITTENSORY_REVIEW_REPUTATION — submitter-reputation spend control. A new,
diff --git a/src/queue/processors.ts b/src/queue/processors.ts
index 312a7888e9..8c3bfba0c8 100644
--- a/src/queue/processors.ts
+++ b/src/queue/processors.ts
@@ -5504,6 +5504,7 @@ async function processGitHubWebhook(
return;
}
+ if (eventName === "issue_comment" && (await maybeProcessResolveCommand(env, deliveryId, payload))) { await recordWebhookEvent(env, { deliveryId, eventName, action: payload.action, installationId: payload.installation?.id, repositoryFullName: payload.repository?.full_name, payloadHash: "processed", status: "processed" }); return; }
if (
eventName === "issue_comment" &&
(await maybeProcessPlanCommand(env, deliveryId, payload))
@@ -10342,6 +10343,35 @@ async function recordGateOverrideSkip(
});
}
+async function maybeProcessResolveCommand(env: Env, deliveryId: string, payload: GitHubWebhookPayload): Promise { const command = parseGittensoryMentionCommand(payload.comment?.body);
+ if (!command) return false;
+ if (command.name !== "resolve") return false;
+ const { classifyPrCommandRequest } = await import("../github/pr-command-request");
+ const { normalizeResolveFindingRef, selectWarningsForResolve } = await import("../review/review-memory-wire");
+ const req = classifyPrCommandRequest(payload, getInstallationId(payload));
+ if (!req.ok) { await recordAuditEvent(env, { eventType: "github_app.finding_resolved_skipped", actor: req.actor, targetKey: req.targetKey, outcome: "completed", detail: req.reason, metadata: { deliveryId, repoFullName: req.repoFullName ?? null, reason: req.reason } }); await recordGithubProductUsage(env, "finding_resolved_skipped", { actor: req.actor, repoFullName: req.repoFullName, targetKey: req.targetKey, outcome: "skipped", metadata: { reason: req.reason } }); return true; }
+ const [pr, settings] = await Promise.all([getPullRequest(env, req.repoFullName, req.pr.number), resolveRepositorySettings(env, req.repoFullName)]);
+ const targetKey = `${req.repoFullName}#${req.pr.number}`;
+ if (!pr) { await recordAuditEvent(env, { eventType: "github_app.finding_resolved_skipped", actor: req.actor, targetKey, outcome: "completed", detail: "cached_pr_missing", metadata: { deliveryId, repoFullName: req.repoFullName, reason: "cached_pr_missing" } }); await recordGithubProductUsage(env, "finding_resolved_skipped", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "skipped", metadata: { reason: "cached_pr_missing" } }); return true; }
+ const { authorization } = await authorizePrActionActor({ env, deliveryId, installationId: req.installationId, repoFullName: req.repoFullName, issue: payload.issue!, actor: req.actor, commandName: "resolve" as GittensoryMentionCommandName, settings, pr });
+ if (!authorization.authorized) { await recordAuditEvent(env, { eventType: "github_app.finding_resolved_denied", actor: req.actor, targetKey, outcome: "denied", detail: authorization.reason, metadata: { deliveryId, repoFullName: req.repoFullName, allowedRoles: commandAuthorizationAllowedRoles(settings.commandAuthorization, "resolve") } }); await recordGithubProductUsage(env, "finding_resolved_denied", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "denied", metadata: { reason: authorization.reason, actorKind: authorization.actorKind, allowedRoles: commandAuthorizationAllowedRoles(settings.commandAuthorization, "resolve") } }); return true; }
+ const findingRef = normalizeResolveFindingRef(command.reason);
+ if (!findingRef.ok) { await recordAuditEvent(env, { eventType: "github_app.finding_resolved_skipped", actor: req.actor, targetKey, outcome: "completed", detail: findingRef.reason, metadata: { deliveryId, repoFullName: req.repoFullName, reason: findingRef.reason } }); await recordGithubProductUsage(env, "finding_resolved_skipped", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "skipped", metadata: { reason: findingRef.reason } }); return true; }
+ const { advisory } = await buildAuthorizedPrActionAdvisory(env, req.repoFullName, pr, settings);
+ const gate = evaluateGateCheck(advisory, gateCheckPolicy(settings, null, undefined, pr.slopRisk ?? null));
+ const selection = selectWarningsForResolve(gate.warnings, findingRef);
+ if (selection.reason === "finding_not_found") { await recordAuditEvent(env, { eventType: "github_app.finding_resolved_skipped", actor: req.actor, targetKey, outcome: "completed", detail: selection.reason, metadata: { deliveryId, repoFullName: req.repoFullName, reason: selection.reason } }); await recordGithubProductUsage(env, "finding_resolved_skipped", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "skipped", metadata: { reason: selection.reason } }); return true; }
+ const mode = resolveAgentActionMode({ globalPaused: isGlobalAgentPause(env) || (await isGlobalAgentFrozen(env)), agentPaused: settings.agentPaused, agentDryRun: settings.agentDryRun });
+ if (mode !== "live") { const skipReason = mode === "dry_run" ? "dry_run" : "agent_paused"; await recordAuditEvent(env, { eventType: "github_app.finding_resolved_skipped", actor: req.actor, targetKey, outcome: "completed", detail: skipReason, metadata: { deliveryId, repoFullName: req.repoFullName, reason: skipReason } }); await recordGithubProductUsage(env, "finding_resolved_skipped", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "skipped", metadata: { reason: skipReason } }); return true; }
+ const reviewManifest = await loadRepoFocusManifest(env, req.repoFullName).catch(() => null);
+ const reviewMemoryEnabled = shouldApplyReviewMemory(env, resolveReviewMemoryManifestToggle(reviewManifest));
+ 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; 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"));
+ 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; }
/**
* `@gittensory plan` (#issue-coding-plan, flag-gated by GITTENSORY_REVIEW_PLANNER). On a MAINTAINER's comment on
* an ISSUE (not a PR), generate a concise implementation plan from the issue text via Workers AI and post it as an
diff --git a/src/review/review-memory-wire.ts b/src/review/review-memory-wire.ts
index 2db0b0cd8d..ea64b67f5a 100644
--- a/src/review/review-memory-wire.ts
+++ b/src/review/review-memory-wire.ts
@@ -26,6 +26,9 @@ export function shouldApplyReviewMemory(
): boolean {
return isReviewMemoryEnabled(env) && manifestReviewMemoryEnabled;
}
+const RESOLVE_FINDING_CODE = /^[a-z][a-z0-9_]{0,199}$/;
+export function normalizeResolveFindingRef(raw: string | null | undefined): { ok: true; scope: "whole_pr" } | { ok: true; scope: "single"; findingCode: string } | { ok: false; reason: "malformed_finding_id" } { const trimmed = (raw ?? "").trim(); if (trimmed.length === 0) return { ok: true, scope: "whole_pr" }; const normalized = trimmed.toLowerCase().replace(/^finding-/, ""); if (!RESOLVE_FINDING_CODE.test(normalized)) return { ok: false, reason: "malformed_finding_id" }; return { ok: true, scope: "single", findingCode: normalized }; }
+export function selectWarningsForResolve(warnings: ReadonlyArray, ref: { ok: true; scope: "whole_pr" } | { ok: true; scope: "single"; findingCode: string }): { findings: AdvisoryFinding[]; reason?: "finding_not_found" } { if (ref.scope === "whole_pr") return { findings: [...warnings] }; const matches = warnings.filter((finding) => finding.code === ref.findingCode); if (matches.length === 0) return { findings: [], reason: "finding_not_found" }; return { findings: matches }; }
/** Apply-to-findings wiring (#2181, apply slice of #1964). PURE — no DB I/O (the caller already resolved
* `signals` via listReviewSuppressions); the caller wraps the READ side in its own try/catch (fail-safe: a
diff --git a/test/unit/queue.test.ts b/test/unit/queue.test.ts
index 153f5aaf0b..9afa4b7d92 100644
--- a/test/unit/queue.test.ts
+++ b/test/unit/queue.test.ts
@@ -52,6 +52,7 @@ import {
putCachedAiReview,
markAiReviewPublished,
recordReviewSuppression,
+ listReviewSuppressions,
} from "../../src/db/repositories";
import { agentMaintenanceHeadMatchesGate, changedPathsForGuardrail, claimAiReviewLock, claimPrActuationLock, contributorEvidenceBatchSize, enrichOpenPullRequestsWithChangedFiles, processJob, reconcileLiveDuplicateSiblings, releaseAiReviewLock, releasePrActuationLock, SWEEP_FANOUT_RESOLUTION_CONCURRENCY } from "../../src/queue/processors";
import type { PullRequestRecord } from "../../src/types";
@@ -22167,6 +22168,429 @@ describe("queue processors", () => {
expect(overridden ?? null).toBeNull();
});
+ // #1964 (record slice): `@gittensory resolve` records review-memory suppression signals for advisory warnings.
+ describe("@gittensory resolve (#1964)", () => {
+ async function seedResolvePr(env: Env, repoFullName: string, prNumber: number, headSha: string) {
+ const slash = repoFullName.indexOf("/");
+ const owner = slash >= 0 ? repoFullName.slice(0, slash) : repoFullName;
+ const name = slash >= 0 ? repoFullName.slice(slash + 1) : repoFullName;
+ await upsertRepositoryFromGitHub(env, { name, full_name: repoFullName, private: false, owner: { login: owner } }, 123);
+ await upsertRepositorySettings(env, {
+ repoFullName,
+ commentMode: "off",
+ publicSurface: "off",
+ autoLabelEnabled: false,
+ checkRunMode: "off",
+ gateCheckMode: "enabled",
+ requireLinkedIssue: true,
+ linkedIssueGateMode: "advisory",
+ });
+ await upsertPullRequestFromGitHub(env, repoFullName, {
+ number: prNumber,
+ title: "Resolve me",
+ state: "open",
+ user: { login: "contributor" },
+ author_association: "CONTRIBUTOR",
+ head: { sha: headSha },
+ labels: [],
+ body: "No linked issue on purpose",
+ });
+ }
+
+ it("records a suppression signal and finding_resolved when an authorized maintainer resolves a named warning with review.memory ON", async () => {
+ const repoFullName = "JSONbored/resolve-1964-a";
+ const env = createTestEnv({
+ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(),
+ GITTENSORY_REVIEW_MEMORY: "true",
+ });
+ await seedResolvePr(env, repoFullName, 1964, "resolve-1964-a");
+ await upsertRepoFocusManifest(env, repoFullName, { review: { memory: true } });
+ const calls = { permission: 0, checkPatches: 0, comments: 0 };
+ let confirmationBody = "";
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = input.toString();
+ const method = init?.method ?? "GET";
+ if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ if (url.includes("/collaborators/maintainer/permission")) {
+ calls.permission += 1;
+ return Response.json({ permission: "admin" });
+ }
+ if (url.includes("/issues/1964/comments") && method === "GET") return Response.json([]);
+ if (url.includes("/issues/1964/comments") && method === "POST") {
+ calls.comments += 1;
+ confirmationBody = String((JSON.parse(String(init?.body ?? "{}")) as { body?: string }).body ?? "");
+ return Response.json({ id: 19641 });
+ }
+ if (url.includes("/check-runs") && method === "PATCH") {
+ calls.checkPatches += 1;
+ return Response.json({ id: 1 });
+ }
+ return new Response("not found", { status: 404 });
+ });
+
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: "resolve-1964-allow",
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ repository: { name: "resolve-1964-a", full_name: repoFullName, private: false, owner: { login: "JSONbored" } },
+ issue: { number: 1964, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: {
+ id: 19640,
+ body: "@gittensory resolve missing_linked_issue",
+ author_association: "NONE",
+ user: { login: "maintainer", type: "User" },
+ },
+ sender: { login: "maintainer", type: "User" },
+ },
+ });
+
+ expect(calls.permission).toBe(1);
+ expect(calls.checkPatches).toBe(0);
+ expect(calls.comments).toBe(1);
+ expect(confirmationBody).toContain("Review finding resolved");
+ expect(confirmationBody).toContain("missing_linked_issue");
+ expect(confirmationBody).toContain("Gate check-run is unchanged");
+ const resolved = await env.DB.prepare("select outcome, detail from audit_events where event_type = ?")
+ .bind("github_app.finding_resolved")
+ .first<{ outcome: string; detail: string }>();
+ expect(resolved).toMatchObject({ outcome: "completed" });
+ const memoryRecorded = await env.DB.prepare("select outcome from audit_events where event_type = ?")
+ .bind("github_app.review_memory_recorded")
+ .first<{ outcome: string }>();
+ expect(memoryRecorded).toMatchObject({ outcome: "completed" });
+ const suppressions = await listReviewSuppressions(env, repoFullName);
+ expect(suppressions).toHaveLength(1);
+ expect(suppressions[0]).toMatchObject({
+ category: "missing_linked_issue",
+ createdBy: "maintainer",
+ });
+ });
+
+ it("records finding_resolved without a suppression write when review.memory is OFF (operator kill-switch)", async () => {
+ const repoFullName = "JSONbored/resolve-1965-off";
+ const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
+ await seedResolvePr(env, repoFullName, 1965, "resolve-1965-off");
+ await upsertRepoFocusManifest(env, repoFullName, { review: { memory: true } });
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = input.toString();
+ const method = init?.method ?? "GET";
+ if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ if (url.includes("/collaborators/maintainer/permission")) return Response.json({ permission: "admin" });
+ if (url.includes("/issues/1965/comments") && method === "GET") return Response.json([]);
+ if (url.includes("/issues/1965/comments") && method === "POST") return Response.json({ id: 19651 });
+ return new Response("not found", { status: 404 });
+ });
+
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: "resolve-1965-flag-off",
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ repository: { name: "resolve-1965-off", full_name: repoFullName, private: false, owner: { login: "JSONbored" } },
+ issue: { number: 1965, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: {
+ id: 19650,
+ body: "@gittensory resolve missing_linked_issue",
+ author_association: "NONE",
+ user: { login: "maintainer", type: "User" },
+ },
+ sender: { login: "maintainer", type: "User" },
+ },
+ });
+
+ const memoryRecorded = await env.DB.prepare("select id from audit_events where event_type = ?")
+ .bind("github_app.review_memory_recorded")
+ .first<{ id: string }>();
+ expect(memoryRecorded ?? null).toBeNull();
+ expect(await listReviewSuppressions(env, repoFullName)).toHaveLength(0);
+ const resolved = await env.DB.prepare("select outcome from audit_events where event_type = ?")
+ .bind("github_app.finding_resolved")
+ .first<{ outcome: string }>();
+ expect(resolved).toMatchObject({ outcome: "completed" });
+ });
+
+ it("denies an unauthorized actor and records no suppression signal", async () => {
+ const repoFullName = "JSONbored/resolve-1966-deny";
+ const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), GITTENSORY_REVIEW_MEMORY: "true" });
+ await seedResolvePr(env, repoFullName, 1966, "resolve-1966-deny");
+ await upsertRepoFocusManifest(env, repoFullName, { review: { memory: true } });
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
+ const url = input.toString();
+ if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ if (url.includes("/collaborators/org-member/permission")) return Response.json({ permission: "read" });
+ return new Response("not found", { status: 404 });
+ });
+
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: "resolve-1966-deny",
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ repository: { name: "resolve-1966-deny", full_name: repoFullName, private: false, owner: { login: "JSONbored" } },
+ issue: { number: 1966, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: {
+ id: 19660,
+ body: "@gittensory resolve missing_linked_issue",
+ author_association: "MEMBER",
+ user: { login: "org-member", type: "User" },
+ },
+ sender: { login: "org-member", type: "User" },
+ },
+ });
+
+ const denied = await env.DB.prepare("select outcome from audit_events where event_type = ?")
+ .bind("github_app.finding_resolved_denied")
+ .first<{ outcome: string }>();
+ expect(denied).toMatchObject({ outcome: "denied" });
+ expect(await listReviewSuppressions(env, repoFullName)).toHaveLength(0);
+ });
+
+ it.each([
+ ["malformed finding id", "@gittensory resolve ../escape", "malformed_finding_id"],
+ ["absent finding code", "@gittensory resolve readiness_score_below_threshold", "finding_not_found"],
+ ] as const)("skips resolve when the maintainer supplies %s", async (_label, body, reason) => {
+ const repoFullName = "JSONbored/resolve-1967-skip";
+ const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), GITTENSORY_REVIEW_MEMORY: "true" });
+ await seedResolvePr(env, repoFullName, 1967, "resolve-1967-skip");
+ await upsertRepoFocusManifest(env, repoFullName, { review: { memory: true } });
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
+ const url = input.toString();
+ if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ if (url.includes("/collaborators/maintainer/permission")) return Response.json({ permission: "admin" });
+ return new Response("not found", { status: 404 });
+ });
+
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: `resolve-1967-${reason}`,
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ repository: { name: "resolve-1967-skip", full_name: repoFullName, private: false, owner: { login: "JSONbored" } },
+ issue: { number: 1967, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: { id: 19670, body, author_association: "NONE", user: { login: "maintainer", type: "User" } },
+ sender: { login: "maintainer", type: "User" },
+ },
+ });
+
+ const skipped = await env.DB.prepare("select detail from audit_events where event_type = ?")
+ .bind("github_app.finding_resolved_skipped")
+ .first<{ detail: string }>();
+ expect(skipped?.detail).toBe(reason);
+ expect(await listReviewSuppressions(env, repoFullName)).toHaveLength(0);
+ });
+
+ it("records every current advisory warning for a whole-PR `@gittensory resolve` ack", async () => {
+ const repoFullName = "JSONbored/resolve-1968-whole";
+ const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), GITTENSORY_REVIEW_MEMORY: "true" });
+ await seedResolvePr(env, repoFullName, 1968, "resolve-1968-whole");
+ await upsertRepoFocusManifest(env, repoFullName, { review: { memory: true } });
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = input.toString();
+ const method = init?.method ?? "GET";
+ if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ if (url.includes("/collaborators/maintainer/permission")) return Response.json({ permission: "admin" });
+ if (url.includes("/issues/1968/comments") && method === "GET") return Response.json([]);
+ if (url.includes("/issues/1968/comments") && method === "POST") return Response.json({ id: 19681 });
+ return new Response("not found", { status: 404 });
+ });
+
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: "resolve-1968-whole",
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ repository: { name: "resolve-1968-whole", full_name: repoFullName, private: false, owner: { login: "JSONbored" } },
+ issue: { number: 1968, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: { id: 19680, body: "@gittensory resolve", author_association: "NONE", user: { login: "maintainer", type: "User" } },
+ sender: { login: "maintainer", type: "User" },
+ },
+ });
+
+ expect(await listReviewSuppressions(env, repoFullName)).toHaveLength(2);
+ const resolved = await env.DB.prepare("select metadata_json from audit_events where event_type = ?")
+ .bind("github_app.finding_resolved")
+ .first<{ metadata_json: string }>();
+ expect(JSON.parse(resolved?.metadata_json ?? "{}")).toMatchObject({ scope: "whole_pr", resolvedWarningCount: 2 });
+ });
+
+ it("ignores issue comments that are not @gittensory resolve commands (#1964)", async () => {
+ const repoFullName = "JSONbored/resolve-1973-plain";
+ const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
+ await seedResolvePr(env, repoFullName, 1973, "resolve-1973-plain");
+ let commentPosts = 0;
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = input.toString();
+ const method = init?.method ?? "GET";
+ if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ if (url.includes("/issues/1973/comments") && method === "POST") {
+ commentPosts += 1;
+ return Response.json({ id: 19730 });
+ }
+ return new Response("not found", { status: 404 });
+ });
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: "resolve-plain-comment",
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ repository: { name: "resolve-1973-plain", full_name: repoFullName, private: false, owner: { login: "JSONbored" } },
+ issue: { number: 1973, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: { id: 19731, body: "Looks good to me", author_association: "OWNER", user: { login: "maintainer", type: "User" } },
+ sender: { login: "maintainer", type: "User" },
+ },
+ });
+ expect(commentPosts).toBe(0);
+ const events = await env.DB.prepare("select event_type from audit_events where event_type like ?").bind("github_app.finding_resolved%").all<{ event_type: string }>();
+ expect(events.results ?? []).toEqual([]);
+ });
+
+ it("ignores other @gittensory verbs on the resolve handler path (#1964)", async () => {
+ const repoFullName = "JSONbored/resolve-1974-help";
+ const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
+ await seedResolvePr(env, repoFullName, 1974, "resolve-1974-help");
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
+ if (input.toString().includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ return new Response("not found", { status: 404 });
+ });
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: "resolve-help-verb",
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ repository: { name: "resolve-1974-help", full_name: repoFullName, private: false, owner: { login: "JSONbored" } },
+ issue: { number: 1974, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: { id: 19740, body: "@gittensory help", author_association: "OWNER", user: { login: "maintainer", type: "User" } },
+ sender: { login: "maintainer", type: "User" },
+ },
+ });
+ const events = await env.DB.prepare("select event_type from audit_events where event_type like ?").bind("github_app.finding_resolved%").all<{ event_type: string }>();
+ expect(events.results ?? []).toEqual([]);
+ });
+
+ it("skips resolve when the webhook payload lacks a repository (#1964)", async () => {
+ const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
+ if (input.toString().includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ return new Response("not found", { status: 404 });
+ });
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: "resolve-missing-repo",
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ issue: { number: 1972, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: { id: 19720, body: "@gittensory resolve missing_linked_issue", author_association: "NONE", user: { login: "maintainer", type: "User" } },
+ sender: { login: "maintainer", type: "User" },
+ },
+ });
+ const skipped = await env.DB.prepare("select detail from audit_events where event_type = ?").bind("github_app.finding_resolved_skipped").first<{ detail: string }>();
+ expect(skipped?.detail).toBe("missing_repo_pr_installation_or_actor");
+ });
+
+ it("skips resolve when the cached pull request row is missing (#1964)", async () => {
+ const repoFullName = "JSONbored/resolve-1969-missing-pr";
+ const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), GITTENSORY_REVIEW_MEMORY: "true" });
+ const slash = repoFullName.indexOf("/");
+ await upsertRepositoryFromGitHub(env, { name: repoFullName.slice(slash + 1), full_name: repoFullName, private: false, owner: { login: repoFullName.slice(0, slash) } }, 123);
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
+ if (input.toString().includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ return new Response("not found", { status: 404 });
+ });
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: "resolve-1969-missing-pr",
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ repository: { name: "resolve-1969-missing-pr", full_name: repoFullName, private: false, owner: { login: "JSONbored" } },
+ issue: { number: 1969, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: { id: 19690, body: "@gittensory resolve missing_linked_issue", author_association: "NONE", user: { login: "maintainer", type: "User" } },
+ sender: { login: "maintainer", type: "User" },
+ },
+ });
+ const skipped = await env.DB.prepare("select detail from audit_events where event_type = ?").bind("github_app.finding_resolved_skipped").first<{ detail: string }>();
+ expect(skipped?.detail).toBe("cached_pr_missing");
+ });
+
+ it("skips resolve in agentDryRun without recording finding_resolved (#1964)", async () => {
+ const repoFullName = "JSONbored/resolve-1970-dry-run";
+ const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), GITTENSORY_REVIEW_MEMORY: "true" });
+ await seedResolvePr(env, repoFullName, 1970, "resolve-1970-dry-run");
+ await upsertRepositorySettings(env, { repoFullName, commentMode: "off", publicSurface: "off", autoLabelEnabled: false, checkRunMode: "off", gateCheckMode: "enabled", requireLinkedIssue: true, linkedIssueGateMode: "advisory", agentDryRun: true });
+ await upsertRepoFocusManifest(env, repoFullName, { review: { memory: true } });
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
+ const url = input.toString();
+ if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ if (url.includes("/collaborators/maintainer/permission")) return Response.json({ permission: "admin" });
+ return new Response("not found", { status: 404 });
+ });
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: "resolve-1970-dry-run",
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ repository: { name: "resolve-1970-dry-run", full_name: repoFullName, private: false, owner: { login: "JSONbored" } },
+ issue: { number: 1970, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: { id: 19700, body: "@gittensory resolve missing_linked_issue", author_association: "NONE", user: { login: "maintainer", type: "User" } },
+ sender: { login: "maintainer", type: "User" },
+ },
+ });
+ const skipped = await env.DB.prepare("select detail from audit_events where event_type = ?").bind("github_app.finding_resolved_skipped").first<{ detail: string }>();
+ expect(skipped?.detail).toBe("dry_run");
+ const resolved = await env.DB.prepare("select id from audit_events where event_type = ?").bind("github_app.finding_resolved").first<{ id: string }>();
+ expect(resolved ?? null).toBeNull();
+ });
+
+ it("skips resolve when the repository is agentPaused (#1964)", async () => {
+ const repoFullName = "JSONbored/resolve-1971-paused";
+ const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), GITTENSORY_REVIEW_MEMORY: "true" });
+ await seedResolvePr(env, repoFullName, 1971, "resolve-1971-paused");
+ await upsertRepositorySettings(env, { repoFullName, commentMode: "off", publicSurface: "off", autoLabelEnabled: false, checkRunMode: "off", gateCheckMode: "enabled", requireLinkedIssue: true, linkedIssueGateMode: "advisory", agentPaused: true });
+ await upsertRepoFocusManifest(env, repoFullName, { review: { memory: true } });
+ vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
+ const url = input.toString();
+ if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" });
+ if (url.includes("/collaborators/maintainer/permission")) return Response.json({ permission: "admin" });
+ return new Response("not found", { status: 404 });
+ });
+ await processJob(env, {
+ type: "github-webhook",
+ deliveryId: "resolve-1971-paused",
+ eventName: "issue_comment",
+ payload: {
+ action: "created",
+ installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } },
+ repository: { name: "resolve-1971-paused", full_name: repoFullName, private: false, owner: { login: "JSONbored" } },
+ issue: { number: 1971, title: "Resolve me", state: "open", user: { login: "contributor" }, pull_request: {} },
+ comment: { id: 19710, body: "@gittensory resolve missing_linked_issue", author_association: "NONE", user: { login: "maintainer", type: "User" } },
+ sender: { login: "maintainer", type: "User" },
+ },
+ });
+ const skipped = await env.DB.prepare("select detail from audit_events where event_type = ?").bind("github_app.finding_resolved_skipped").first<{ detail: string }>();
+ expect(skipped?.detail).toBe("agent_paused");
+ });
+ });
+
it("a #1960 action-command verb with no dispatch handler wired yet (e.g. pause) is bailed out of the Q&A answer-card path, not misrendered as help (#2160)", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
await upsertRepositoryFromGitHub(env, { name: "gittensory", full_name: "JSONbored/gittensory", private: false, owner: { login: "JSONbored" } }, 123);
diff --git a/test/unit/review-memory-record.test.ts b/test/unit/review-memory-record.test.ts
new file mode 100644
index 0000000000..db5cb85755
--- /dev/null
+++ b/test/unit/review-memory-record.test.ts
@@ -0,0 +1,62 @@
+import { describe, expect, it } from "vitest";
+import { normalizeResolveFindingRef, selectWarningsForResolve } from "../../src/review/review-memory-wire";
+import type { AdvisoryFinding } from "../../src/types";
+
+function warning(overrides: Partial = {}): AdvisoryFinding {
+ return {
+ code: "readiness_score_below_threshold",
+ severity: "warning",
+ title: "Readiness score is below the configured threshold",
+ detail: "The public readiness score is 25/100, below the repository threshold of 100/100.",
+ ...overrides,
+ };
+}
+
+describe("normalizeResolveFindingRef (#1964)", () => {
+ it("treats empty/absent trailing text as a whole-PR ack", () => {
+ expect(normalizeResolveFindingRef(undefined)).toEqual({ ok: true, scope: "whole_pr" });
+ expect(normalizeResolveFindingRef("")).toEqual({ ok: true, scope: "whole_pr" });
+ expect(normalizeResolveFindingRef(" ")).toEqual({ ok: true, scope: "whole_pr" });
+ });
+
+ it("accepts a bare finding code and the optional finding- prefix", () => {
+ expect(normalizeResolveFindingRef("readiness_score_below_threshold")).toEqual({
+ ok: true,
+ scope: "single",
+ findingCode: "readiness_score_below_threshold",
+ });
+ expect(normalizeResolveFindingRef("finding-readiness_score_below_threshold")).toEqual({
+ ok: true,
+ scope: "single",
+ findingCode: "readiness_score_below_threshold",
+ });
+ });
+
+ it("rejects malformed finding references", () => {
+ expect(normalizeResolveFindingRef("../escape")).toEqual({ ok: false, reason: "malformed_finding_id" });
+ expect(normalizeResolveFindingRef("Bad-Hyphen")).toEqual({ ok: false, reason: "malformed_finding_id" });
+ expect(normalizeResolveFindingRef("has space")).toEqual({ ok: false, reason: "malformed_finding_id" });
+ expect(normalizeResolveFindingRef("9starts_with_digit")).toEqual({ ok: false, reason: "malformed_finding_id" });
+ });
+});
+
+describe("selectWarningsForResolve (#1964)", () => {
+ it("returns every warning for a whole-PR ack", () => {
+ const warnings = [warning(), warning({ code: "duplicate_pr_risk", title: "Duplicate risk" })];
+ expect(selectWarningsForResolve(warnings, { ok: true, scope: "whole_pr" })).toEqual({ findings: warnings });
+ });
+
+ it("returns only the matching warning for a single-finding ref", () => {
+ const a = warning();
+ const b = warning({ code: "duplicate_pr_risk", title: "Duplicate risk" });
+ expect(selectWarningsForResolve([a, b], { ok: true, scope: "single", findingCode: "duplicate_pr_risk" })).toEqual({
+ findings: [b],
+ });
+ });
+
+ it("reports finding_not_found when the requested code is absent from the warnings", () => {
+ expect(
+ selectWarningsForResolve([warning()], { ok: true, scope: "single", findingCode: "missing_linked_issue" }),
+ ).toEqual({ findings: [], reason: "finding_not_found" });
+ });
+});
diff --git a/test/unit/sweep-watchdog.test.ts b/test/unit/sweep-watchdog.test.ts
index 1df3827963..c41440ccf6 100644
--- a/test/unit/sweep-watchdog.test.ts
+++ b/test/unit/sweep-watchdog.test.ts
@@ -62,7 +62,7 @@ describe("runSweepLivenessWatchdog (#audit-sweep-fanout-isolation follow-up)", (
});
it("REGRESSION: reports a finite ageMs for a repo that WAS regated once but fell outside the staleness window (not just a never-regated null marker)", async () => {
- vi.useFakeTimers();
+ vi.useFakeTimers({ shouldAdvanceTime: true });
const start = new Date("2026-07-06T10:00:00.000Z");
vi.setSystemTime(start);
const sent: import("../../src/types").JobMessage[] = [];
@@ -78,7 +78,7 @@ describe("runSweepLivenessWatchdog (#audit-sweep-fanout-isolation follow-up)", (
expect(found).toEqual([expect.objectContaining({ repoFullName: "owner/aged-repo", lastRegatedAt: start.toISOString(), ageMs: SWEEP_STALENESS_THRESHOLD_MS + 60_000 })]);
expect(Number.isFinite(found[0]?.ageMs)).toBe(true);
expect(sent).toEqual([expect.objectContaining({ type: "agent-regate-sweep", repoFullName: "owner/aged-repo" })]);
- });
+ }, 60_000);
it("watches an ALLOWLISTED (GITTENSORY_REVIEW_REPOS) installed repo even with no autonomy configured, and skips a plain repo that is neither allowlisted nor agent-configured", async () => {
const sent: import("../../src/types").JobMessage[] = [];