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
13 changes: 12 additions & 1 deletion .loopover.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,18 @@ settings:
removeOtherTypeLabels: false
trustMaintainerAuthoredIssueForReward: true
reviewEvasionProtection: close
draftPrClosePolicy: close
# Agent-layer autonomy dial (#773): without this block every action class defaults to "observe"
# (deny-by-default) -- loopover had NO repository_settings DB row at all, so merge/close/approve
# actions were silently never taken regardless of review verdict (#6401, #6402 sat fully reviewed,
# CI-green, and mergeable with no auto-merge attempt). Matches the policy JSONbored/awesome-claude and
# JSONbored/metagraphed already carry live in their DB rows -- this makes it config-as-code instead.
autonomy:
merge: auto
close: auto
approve: auto
request_changes: auto
update_branch: auto
assign: auto

# Repo-doc generation roadmap (#2993/#3002) — opt-in only, off by default. Uncomment to let LoopOver open a
# PR generating AGENTS.md/CLAUDE.md from this repo's own profile.
Expand Down
13 changes: 12 additions & 1 deletion src/config/loopover-repo-focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,18 @@ settings:
removeOtherTypeLabels: false
trustMaintainerAuthoredIssueForReward: true
reviewEvasionProtection: close
draftPrClosePolicy: close
# Agent-layer autonomy dial (#773): without this block every action class defaults to "observe"
# (deny-by-default) -- loopover had NO repository_settings DB row at all, so merge/close/approve
# actions were silently never taken regardless of review verdict (#6401, #6402 sat fully reviewed,
# CI-green, and mergeable with no auto-merge attempt). Matches the policy JSONbored/awesome-claude and
# JSONbored/metagraphed already carry live in their DB rows -- this makes it config-as-code instead.
autonomy:
merge: auto
close: auto
approve: auto
request_changes: auto
update_branch: auto
assign: auto

# Repo-doc generation roadmap (#2993/#3002) — opt-in only, off by default. Uncomment to let LoopOver open a
# PR generating AGENTS.md/CLAUDE.md from this repo's own profile.
Expand Down
10 changes: 5 additions & 5 deletions test/unit/backfill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ describe("GitHub backfill", () => {
});

it("REGRESSION: broker-mode refresh replaces stale local permissions with the broker token permission snapshot", async () => {
const env = createTestEnv({ ORB_ENROLLMENT_SECRET: "orbsec_test" });
const env = createTestEnv({ ORB_ENROLLMENT_SECRET: "orbsec_test", LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await upsertInstallation(env, {
installation: {
id: 912,
Expand Down Expand Up @@ -994,7 +994,7 @@ describe("GitHub backfill", () => {
});

it("requires Checks write only for repos with check runs enabled", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await seedRegisteredRepo(env);
await upsertInstallation(env, {
installation: {
Expand Down Expand Up @@ -1043,7 +1043,7 @@ describe("GitHub backfill", () => {
// the separate reviewCheckMode axis ("LoopOver Orb Review Agent" check) entirely -- so an installation
// whose repos only ever published the review-agent check (true for JSONbored's own 3 production repos,
// none of which set checkRunMode) was never flagged as needing the Checks permission.
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await seedRegisteredRepo(env);
await upsertInstallation(env, {
installation: {
Expand Down Expand Up @@ -1088,7 +1088,7 @@ describe("GitHub backfill", () => {
});

it("REGRESSION (#audit-install-health): an acting autonomy requires pull_requests:write, so read-only is needs_attention not healthy", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await seedRegisteredRepo(env);
await upsertInstallation(env, {
installation: {
Expand Down Expand Up @@ -1131,7 +1131,7 @@ describe("GitHub backfill", () => {
});

it("REGRESSION: merge autonomy requires contents:write, so contents:read is needs_attention before merge 403s", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await seedRegisteredRepo(env);
await upsertInstallation(env, {
installation: {
Expand Down
3 changes: 2 additions & 1 deletion test/unit/ci-completion-fork-resume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ describe("CI-completion fork PR resume (head-SHA fallback)", () => {
const sent: JobMessage[] = [];
const env = createTestEnv({
JOBS: { async send(message: JobMessage) { sent.push(message); } } as unknown as Queue,
LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo",
});
await seedForkResumeRepo(env, "JSONbored/gittensory", 99, FORK_SHA);
vi.stubGlobal("fetch", async () => new Response("not found", { status: 404 }));
Expand Down Expand Up @@ -184,7 +185,7 @@ describe("CI-completion fork PR resume (head-SHA fallback)", () => {
});

it("dispatch: a SAME-REPO check_suite (populated pull_requests[]) re-reviews WITHOUT the fork-resume audit", async () => {
const env = createTestEnv({});
const env = createTestEnv({ LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await seedForkResumeRepo(env, "JSONbored/gittensory", 99, FORK_SHA);
vi.stubGlobal("fetch", async () => {
throw new Error("fetch must not be called for the populated same-repo path");
Expand Down
2 changes: 1 addition & 1 deletion test/unit/outcomes-wire.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@ describe("runSelfTuneBreaker — miner-scoped breaker (#2352)", () => {

describe("processJob(github-webhook) wires pr_outcome recording on a PR close", () => {
it("a closed+merged pull_request webhook records the pr_outcome ground truth", async () => {
const env = createTestEnv();
const env = createTestEnv({ LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = input.toString();
if (url.includes("/access_tokens"))
Expand Down
16 changes: 8 additions & 8 deletions test/unit/queue-2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3318,7 +3318,7 @@ describe("queue processors", () => {
});

it("marks installation health from queued installation metadata", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -4417,7 +4417,7 @@ describe("queue processors", () => {
});

it("stamps a gate-only surface even when local Gate check-summary persistence fails", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -4473,7 +4473,7 @@ describe("queue processors", () => {
});

it("finalizes a permission-missing gate check through the neutral fallback before stamping the surface", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -4537,7 +4537,7 @@ describe("queue processors", () => {
});

it("does not stamp a permission-missing gate check when the neutral fallback cannot publish", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -4600,7 +4600,7 @@ describe("queue processors", () => {
});

it("suppresses public review output when the live PR head changed before publish", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -4730,7 +4730,7 @@ describe("queue processors", () => {
] as const;

for (const scenario of cases) {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -4870,7 +4870,7 @@ describe("queue processors", () => {
}
await originalRecordAuditEvent(auditEnv, event);
});
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -4936,7 +4936,7 @@ describe("queue processors", () => {
});

it("finalizes the pending gate as skipped when the PR head changes after review work", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down
33 changes: 17 additions & 16 deletions test/unit/queue-4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ describe("queue processors", () => {
});

it("auto-maintain (#778): a repo with no acting autonomy takes no agent action", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload({ "JSONbored/gittensory": { emission_share: 0.01, issue_discovery_share: 0 } }, { kind: "raw-github", url: "https://example.test" }, "2026-05-23T00:00:00.000Z"),
Expand Down Expand Up @@ -1082,6 +1082,7 @@ describe("queue processors", () => {
AI_SUMMARIES_ENABLED: "true",
AI_PUBLIC_COMMENTS_ENABLED: "true",
AI_DAILY_NEURON_BUDGET: "100000",
LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo",
});
await persistRegistrySnapshot(
env,
Expand Down Expand Up @@ -1165,7 +1166,7 @@ describe("queue processors", () => {
});

it("finalizes the Gate to neutral instead of leaving it in_progress when gate completion fails", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -1237,7 +1238,7 @@ describe("queue processors", () => {
});

it("does not stamp a current public surface when a required Gate check never finalizes", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -1307,7 +1308,7 @@ describe("queue processors", () => {
});

it("records the intended label in incomplete-surface audits when a label publishes but Gate never finalizes", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -1382,7 +1383,7 @@ describe("queue processors", () => {
}
await originalRecordAuditEvent(auditEnv, event);
});
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -1441,7 +1442,7 @@ describe("queue processors", () => {
}
await originalRecordAuditEvent(auditEnv, event);
});
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -1495,7 +1496,7 @@ describe("queue processors", () => {
});

it("propagates a rate-limited Gate completion so the queue retries and the pending Gate stays reviewing", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -1597,7 +1598,7 @@ describe("queue processors", () => {
});

it("audits opt-in gate check permission failures without blocking webhook processing", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -1767,7 +1768,7 @@ describe("queue processors", () => {
});

it("reruns the sticky PR panel when a maintainer checks the rerun task", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await upsertRepositoryFromGitHub(env, { name: "gittensory", full_name: "JSONbored/gittensory", private: false, owner: { login: "JSONbored" } }, 123);
await upsertRepositorySettings(env, {
repoFullName: "JSONbored/gittensory",
Expand Down Expand Up @@ -2191,7 +2192,7 @@ describe("queue processors", () => {
});

it("reruns the sticky PR panel when a write collaborator checks the rerun task", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await upsertRepositoryFromGitHub(env, { name: "gittensory", full_name: "JSONbored/gittensory", private: false, owner: { login: "JSONbored" } }, 123);
await upsertRepositorySettings(env, {
repoFullName: "JSONbored/gittensory",
Expand Down Expand Up @@ -5466,7 +5467,7 @@ describe("queue processors", () => {
});

it("skips bots and maintainer authors, and keeps explicitly enabled checks minimal", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -5549,7 +5550,7 @@ describe("queue processors", () => {
});

it("audits advisory context check permission failures without blocking webhook processing", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -5602,7 +5603,7 @@ describe("queue processors", () => {
});

it("audits advisory context check publish failures AND retries the job (GitHub 5xx is transient, GITTENSORY-5)", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -5716,7 +5717,7 @@ describe("queue processors", () => {
});

it("records public comment failure without blocking the context check", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await persistRegistrySnapshot(
env,
normalizeRegistryPayload(
Expand Down Expand Up @@ -6154,7 +6155,7 @@ describe("queue processors", () => {
});

it("keeps GitHub-history-only contributors quiet through not_found cache hits and expiry", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await upsertPullRequestFromGitHub(env, "JSONbored/gittensory", {
number: 9,
title: "Historical merged work",
Expand Down Expand Up @@ -6369,7 +6370,7 @@ describe("queue processors", () => {
});

it("recovers confirmed miners after the unavailable cache window expires", async () => {
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() });
const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), LOOPOVER_DRIFT_ISSUE_REPO: "unrelated-org/unrelated-repo" });
await upsertRepositorySettings(env, {
repoFullName: "JSONbored/gittensory",
commentMode: "detected_contributors_only",
Expand Down
Loading