From c9563db3a2b45d4c7e27e56569976ec7e911a9fe Mon Sep 17 00:00:00 2001 From: Derek Cofausper <256792747+decofe@users.noreply.github.com> Date: Fri, 17 Jul 2026 06:08:01 +0000 Subject: [PATCH] feat: support Cyclops perf audits --- README.md | 2 +- actions/pr-audit-comment/README.md | 1 + actions/pr-audit-comment/pr_audit_comment.js | 7 +++++-- actions/pr-audit-comment/pr_audit_comment.test.js | 10 +++++++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1ba6bb5..34eacc6 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ jobs: The comment surface supports: - comments: `cyclops audit`, `@decofe cyclops audit`, `derek audit` -- arguments: `fast`, `iterations=N`, `hours=N`, `config=PATH`, `models=...`, `run-label=LABEL`, `dry-run`, `note="..."` +- arguments: `fast`, `perf`, `iterations=N`, `hours=N`, `config=PATH`, `models=...`, `run-label=LABEL`, `dry-run`, `note="..."` Set `permission-check-mode: org` (with `organization`) for org-membership API checks. Use `permission-token` when those checks need a token distinct from the diff --git a/actions/pr-audit-comment/README.md b/actions/pr-audit-comment/README.md index 76d03c5..a859bc7 100644 --- a/actions/pr-audit-comment/README.md +++ b/actions/pr-audit-comment/README.md @@ -63,4 +63,5 @@ Supported arguments: - `models=...` - `run-label=LABEL` - `dry-run` +- `perf` - `note="..."` diff --git a/actions/pr-audit-comment/pr_audit_comment.js b/actions/pr-audit-comment/pr_audit_comment.js index 5755e35..11f9e66 100644 --- a/actions/pr-audit-comment/pr_audit_comment.js +++ b/actions/pr-audit-comment/pr_audit_comment.js @@ -4,7 +4,7 @@ const path = require("path"); const { spawnSync } = require("child_process"); const usage = [ - '**Usage:** `cyclops audit [fast] [iterations=N] [hours=N] [config=pr-review.yaml] ', + '**Usage:** `cyclops audit [fast] [perf] [iterations=N] [hours=N] [config=pr-review.yaml] ', '[models="anthropic/claude-opus-4-7,openai/gpt-5.5"] [run-label=LABEL] ', '[dry-run] [note="per-run audit guidance"]`', ].join(""); @@ -24,11 +24,12 @@ function parseArgs(body, commandRegex) { models: "", "run-label": "", "dry-run": "false", + perf: "false", note: "", }; const intArgs = new Set(["iterations", "hours"]); const stringArgs = new Set(["config", "models", "run-label", "note"]); - const boolArgs = new Set(["dry-run"]); + const boolArgs = new Set(["dry-run", "perf"]); const unknown = []; const invalid = []; @@ -167,6 +168,7 @@ function buildPayload(context, pr, defaults) { if (defaults.models) data.models = defaults.models; if (defaults["run-label"]) data.run_label = defaults["run-label"]; if (defaults.note) data.audit_note_b64 = Buffer.from(defaults.note, "utf8").toString("base64"); + if (defaults.perf === "true") data.perf = true; return { repository: `${context.repo.owner}/${context.repo.repo}`, @@ -184,6 +186,7 @@ function buildSummary(defaults) { if (defaults.models) summaryParts.push(`models: \`${defaults.models}\``); if (defaults["run-label"]) summaryParts.push(`run-label: \`${defaults["run-label"]}\``); if (defaults["dry-run"] === "true") summaryParts.push("dry-run: `true`"); + if (defaults.perf === "true") summaryParts.push("perf: `true`"); if (defaults.note) { const note = defaults.note.replace(/`/g, "'").slice(0, 160); summaryParts.push(`note: \`${note}${defaults.note.length > 160 ? "..." : ""}\``); diff --git a/actions/pr-audit-comment/pr_audit_comment.test.js b/actions/pr-audit-comment/pr_audit_comment.test.js index 3fad164..f0410dc 100644 --- a/actions/pr-audit-comment/pr_audit_comment.test.js +++ b/actions/pr-audit-comment/pr_audit_comment.test.js @@ -234,6 +234,7 @@ function makeProcessHarness(tmp) { pythonEnv: path.join(tmp, "python-env"), curlArgs: path.join(tmp, "curl-args"), curlEnv: path.join(tmp, "curl-env"), + payload: path.join(tmp, "payload.json"), }; writeExecutable(path.join(bin, "python3"), `#!/bin/sh @@ -244,6 +245,11 @@ exec ${shellQuote(pythonPath)} "$@" writeExecutable(path.join(bin, "curl"), `#!/bin/sh printf '%s\n' "$@" > ${shellQuote(files.curlArgs)} env > ${shellQuote(files.curlEnv)} +for arg in "$@"; do + case "$arg" in + @*) cp "\${arg#@}" ${shellQuote(files.payload)} ;; + esac +done cat >/dev/null `); writeExecutable(path.join(bin, "jq"), `#!/bin/sh @@ -576,13 +582,15 @@ test("comment publisher preserves quoted arguments and isolates parser and curl" process.chdir(hostile); const result = await runScenario({ mode: "association", - body: "cyclops audit note='quoted guidance'", + body: "cyclops audit perf note='quoted guidance'", permissionToken: "permission-token-canary", }); assert.deepEqual(result.core.failures, []); assert.equal(result.primary.calls.commentUpdates.length, 1); assert.match(result.primary.calls.commentUpdates[0].body, /event published/); + assert.match(result.primary.calls.commentUpdates[0].body, /perf: `true`/); + assert.equal(JSON.parse(fs.readFileSync(harness.files.payload)).data.perf, true); assert.deepEqual(readLines(harness.files.pythonArgs).slice(0, 2), ["-I", "-c"]); assert.deepEqual(readLines(harness.files.curlArgs).slice(5, 9), [ "--url",