feat: add forgejo actions coverage receipt

This commit is contained in:
artale 2026-07-01 14:05:28 +02:00
parent f4714f146a
commit 1135ef4d7c
5 changed files with 68 additions and 3 deletions

View File

@ -272,6 +272,9 @@ fable-agent plinius godmode "improve explanation quality"
- `--body <text>`
- `--out <path>`
- `--dry-run`
- `forgejo actions-coverage`
- `--repo <repo>`
- `--output <path>`
- `forgejo intake <ref>`
- `--repo <repo>`
- `--out <path>`

View File

@ -2,13 +2,26 @@ import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { describe, expect, it } from "vitest";
import { createForgejoPullRequest, getForgejoFileContent, listForgejoIssues } from "./forgejo-cli.js";
import { createForgejoActionsCoverageReceipt, createForgejoPullRequest, getForgejoFileContent, listForgejoIssues } from "./forgejo-cli.js";
function tmpDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), "forgejo-cli-"));
}
describe("forgejo cli helpers", () => {
it("documents Forgejo Actions coverage without scraping or mutation", () => {
const receipt = createForgejoActionsCoverageReceipt("org/repo", new Date("2026-07-01T00:00:00.000Z"));
expect(receipt).toMatchObject({
schema: "fable.forgejo.actions_coverage.v1",
repo: "org/repo",
decision: "api-only",
policy: "no-web-ui-scraping-no-action-mutation",
deployAttempted: false,
});
expect(receipt.rows.find((row) => row.capability === "actions cancel/rerun/trigger")?.status).toBe("blocked");
});
it("lists issues via Forgejo API", async () => {
const calls: string[] = [];
const issues = await listForgejoIssues({

View File

@ -25,6 +25,41 @@ export interface ForgejoPullRequestReceipt {
};
}
export interface ForgejoActionsCoverageReceipt {
schema: "fable.forgejo.actions_coverage.v1";
createdAt: string;
repo?: string;
rows: Array<{ capability: string; status: "covered" | "unsupported" | "blocked"; reason: string }>;
decision: "api-only";
policy: "no-web-ui-scraping-no-action-mutation";
deployAttempted: false;
}
export function createForgejoActionsCoverageReceipt(repo?: string, now = new Date()): ForgejoActionsCoverageReceipt {
return {
schema: "fable.forgejo.actions_coverage.v1",
createdAt: now.toISOString(),
repo,
rows: [
{ capability: "actions runs", status: "unsupported", reason: "Forgejo full run details commonly require UI endpoints; Fable will not scrape by default" },
{ capability: "actions jobs", status: "unsupported", reason: "use official API only when available" },
{ capability: "actions logs", status: "unsupported", reason: "logs may contain secrets; no web UI scraping" },
{ capability: "actions artifacts", status: "unsupported", reason: "artifacts may contain secrets; no web UI scraping" },
{ capability: "actions cancel/rerun/trigger", status: "blocked", reason: "mutating CI actions requires explicit implementation and approval" },
{ capability: "pull request create", status: "covered", reason: "forgejo pr-create emits fable.forgejo.pull_request.v1" },
],
decision: "api-only",
policy: "no-web-ui-scraping-no-action-mutation",
deployAttempted: false,
};
}
export function writeForgejoActionsCoverageReceipt(file: string, receipt: ForgejoActionsCoverageReceipt): string {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `${JSON.stringify(receipt, null, 2)}\n`);
return file;
}
export async function forgejoApi<T>(opts: ForgejoCliOptions, endpoint: string, init: { method?: string; body?: unknown } = {}): Promise<T> {
const base = requireBaseUrl(opts.baseUrl);
const res = await (opts.fetcher ?? fetch)(`${base}/api/v1${endpoint}`, {

View File

@ -102,8 +102,8 @@ export type { PromptFingerprintFinding, PromptFingerprintReceipt } from "./promp
export { createJailbreakAssessmentReceipt, writeJailbreakAssessmentReceipt } from "./jailbreak-assessment.js";
export type { JailbreakAssessmentOptions, JailbreakAssessmentReceipt, JailbreakAssessmentScores, JailbreakDecision } from "./jailbreak-assessment.js";
export { createForgejoPullRequest, forgejoApi, getForgejoFileContent, getForgejoRepository, listForgejoIssues, listForgejoPullRequests, listForgejoRepositories } from "./forgejo-cli.js";
export type { ForgejoCliFetch, ForgejoCliOptions, ForgejoPullRequestReceipt } from "./forgejo-cli.js";
export { createForgejoActionsCoverageReceipt, createForgejoPullRequest, forgejoApi, getForgejoFileContent, getForgejoRepository, listForgejoIssues, listForgejoPullRequests, listForgejoRepositories, writeForgejoActionsCoverageReceipt } from "./forgejo-cli.js";
export type { ForgejoActionsCoverageReceipt, ForgejoCliFetch, ForgejoCliOptions, ForgejoPullRequestReceipt } from "./forgejo-cli.js";
export { createPlanReceipt, writePlanReceipt } from "./plan-receipt.js";
export type { PlanAllowedOutcome, PlanPhase, PlanReceipt, PlanReceiptOptions, PlanTaskType } from "./plan-receipt.js";

View File

@ -1824,6 +1824,20 @@ forgejo
if (result.receipt.receipts.quarantine) process.exit(1);
});
forgejo
.command("actions-coverage")
.description("Write a Forgejo Actions CLI coverage receipt without web scraping or action mutation")
.option("--repo <repo>", "Repo slug, e.g. org/repo")
.option("--output <path>", "Receipt output path", path.join(".fable", "forgejo", "actions-coverage-live.json"))
.action(async (opts: { repo?: string; output?: string }) => {
const { createForgejoActionsCoverageReceipt, writeForgejoActionsCoverageReceipt } = await import("./fable5/forgejo-cli.js");
const receipt = createForgejoActionsCoverageReceipt(opts.repo);
const out = opts.output ?? path.join(".fable", "forgejo", "actions-coverage-live.json");
writeForgejoActionsCoverageReceipt(out, receipt);
console.log(JSON.stringify(receipt, null, 2));
console.log(`\n Receipt: ${out}\n`);
});
forgejo
.command("comment <ref>")
.description("Post a scanned receipt-backed comment to a Forgejo issue or PR")