Skip to content

Commit d5a1680

Browse files
authored
Merge pull request #492 from dmoliveira/chore/pr-guard-lint-evidence
Recognize uvx lint evidence for PR guards
2 parents bab6122 + 6f9dd97 commit d5a1680

File tree

5 files changed

+60
-5
lines changed

5 files changed

+60
-5
lines changed

plugin/gateway-core/dist/hooks/shared/validation-command-matcher.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const LEADING_ENV_ASSIGNMENTS = /^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S+)\s+)*/;
2-
const LINT_COMMAND = /^(eslint\b|ruff\s+check\b|ruff\s+format\s+--check\b|npm(?:\s+--prefix\s+\S+)?\s+run\s+lint\b|pnpm(?:\s+(?:--filter\s+\S+)*)?\s+(?:run\s+)?lint\b|yarn\s+(?:run\s+)?lint\b|biome\s+check\b|golangci-lint\b|cargo\s+clippy\b|make\s+validate\b)/i;
2+
const UV_WRAPPER = "(?:uvx|uv\\s+run)\\s+";
3+
const LINT_COMMAND = new RegExp(`^(?:${UV_WRAPPER})?(?:eslint\\b|ruff\\s+check\\b|ruff\\s+format\\s+--check\\b|npm(?:\\s+--prefix\\s+\\S+)?\\s+run\\s+lint\\b|pnpm(?:\\s+(?:--filter\\s+\\S+)*)?\\s+(?:run\\s+)?lint\\b|yarn\\s+(?:run\\s+)?lint\\b|biome\\s+check\\b|golangci-lint\\b|cargo\\s+clippy\\b|make\\s+validate\\b)`, "i");
34
const TEST_COMMAND = /^(npm(?:\s+--prefix\s+\S+)?\s+(run\s+)?test\b|pnpm(?:\s+(?:--filter\s+\S+)*)?\s+(?:run\s+)?test\b|yarn\s+(?:run\s+)?test\b|bun\s+test\b|node\s+--test\b|(?:npm|pnpm)\s+exec\s+vitest\b|npx\s+vitest\b|python\d?\s+-m\s+pytest\b|python\d?\s+-m\s+unittest\b|uv\s+run\s+pytest\b|pytest\b|vitest\b|jest\b|go\s+test\b|cargo\s+test\b|pre-commit\s+run\b|make\s+selftest\b|make\s+install-test\b|python\d?\s+scripts\/selftest\.py\b|\.\/scripts\/ci-check\b.*\btest[s]?\b)/i;
45
const TYPECHECK_COMMAND = /^(tsc\b|npm(?:\s+--prefix\s+\S+)?\s+run\s+typecheck\b|pnpm(?:\s+(?:--filter\s+\S+)*)?\s+(?:run\s+)?typecheck\b|yarn\s+(?:run\s+)?typecheck\b|pyright\b|mypy\b|cargo\s+check\b|go\s+vet\b)/i;
56
const BUILD_COMMAND = /^(npm(?:\s+--prefix\s+\S+)?\s+run\s+build\b|pnpm(?:\s+(?:--filter\s+\S+)*)?\s+(?:run\s+)?build\b|yarn\s+(?:run\s+)?build\b|vite\s+build\b|next\s+build\b|cargo\s+build\b|go\s+build\b)/i;

plugin/gateway-core/src/hooks/shared/validation-command-matcher.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import type { ValidationEvidenceCategory } from "../validation-evidence-ledger/evidence.js"
22

33
const LEADING_ENV_ASSIGNMENTS = /^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S+)\s+)*/
4-
const LINT_COMMAND = /^(eslint\b|ruff\s+check\b|ruff\s+format\s+--check\b|npm(?:\s+--prefix\s+\S+)?\s+run\s+lint\b|pnpm(?:\s+(?:--filter\s+\S+)*)?\s+(?:run\s+)?lint\b|yarn\s+(?:run\s+)?lint\b|biome\s+check\b|golangci-lint\b|cargo\s+clippy\b|make\s+validate\b)/i
4+
const UV_WRAPPER = "(?:uvx|uv\\s+run)\\s+"
5+
const LINT_COMMAND = new RegExp(
6+
`^(?:${UV_WRAPPER})?(?:eslint\\b|ruff\\s+check\\b|ruff\\s+format\\s+--check\\b|npm(?:\\s+--prefix\\s+\\S+)?\\s+run\\s+lint\\b|pnpm(?:\\s+(?:--filter\\s+\\S+)*)?\\s+(?:run\\s+)?lint\\b|yarn\\s+(?:run\\s+)?lint\\b|biome\\s+check\\b|golangci-lint\\b|cargo\\s+clippy\\b|make\\s+validate\\b)`,
7+
"i",
8+
)
59
const TEST_COMMAND = /^(npm(?:\s+--prefix\s+\S+)?\s+(run\s+)?test\b|pnpm(?:\s+(?:--filter\s+\S+)*)?\s+(?:run\s+)?test\b|yarn\s+(?:run\s+)?test\b|bun\s+test\b|node\s+--test\b|(?:npm|pnpm)\s+exec\s+vitest\b|npx\s+vitest\b|python\d?\s+-m\s+pytest\b|python\d?\s+-m\s+unittest\b|uv\s+run\s+pytest\b|pytest\b|vitest\b|jest\b|go\s+test\b|cargo\s+test\b|pre-commit\s+run\b|make\s+selftest\b|make\s+install-test\b|python\d?\s+scripts\/selftest\.py\b|\.\/scripts\/ci-check\b.*\btest[s]?\b)/i
610
const TYPECHECK_COMMAND = /^(tsc\b|npm(?:\s+--prefix\s+\S+)?\s+run\s+typecheck\b|pnpm(?:\s+(?:--filter\s+\S+)*)?\s+(?:run\s+)?typecheck\b|yarn\s+(?:run\s+)?typecheck\b|pyright\b|mypy\b|cargo\s+check\b|go\s+vet\b)/i
711
const BUILD_COMMAND = /^(npm(?:\s+--prefix\s+\S+)?\s+run\s+build\b|pnpm(?:\s+(?:--filter\s+\S+)*)?\s+(?:run\s+)?build\b|yarn\s+(?:run\s+)?build\b|vite\s+build\b|next\s+build\b|cargo\s+build\b|go\s+build\b)/i

plugin/gateway-core/test/event-audit.test.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ test("gateway event audit writes dispatch entries when enabled", async () => {
1818
const lines = readFileSync(auditPath, "utf-8")
1919
.split(/\r?\n/)
2020
.filter(Boolean)
21+
.map((line) => JSON.parse(line))
2122
assert.ok(lines.length >= 1)
22-
const first = JSON.parse(lines[0])
23-
assert.equal(first.reason_code, "event_dispatch")
24-
assert.equal(first.event_type, "session.idle")
23+
const dispatch = lines.find((entry) => entry.reason_code === "event_dispatch")
24+
assert.equal(dispatch?.event_type, "session.idle")
2525
} finally {
2626
if (previous === undefined) {
2727
delete process.env.MY_OPENCODE_GATEWAY_EVENT_AUDIT

plugin/gateway-core/test/pr-body-evidence-guard-hook.test.mjs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,54 @@ test("pr-body-evidence-guard accepts make validate lint evidence from structured
338338
}
339339
})
340340

341+
test("pr-body-evidence-guard accepts uvx ruff lint evidence from structured bash output", async () => {
342+
const directory = mkdtempSync(join(tmpdir(), "gateway-pr-body-"))
343+
try {
344+
const plugin = GatewayCorePlugin({
345+
directory,
346+
config: {
347+
hooks: {
348+
enabled: true,
349+
order: ["validation-evidence-ledger", "pr-body-evidence-guard"],
350+
disabled: ["pr-readiness-guard"],
351+
},
352+
validationEvidenceLedger: {
353+
enabled: true,
354+
},
355+
doneProofEnforcer: {
356+
enabled: true,
357+
requiredMarkers: ["lint"],
358+
requireLedgerEvidence: true,
359+
allowTextFallback: false,
360+
},
361+
prBodyEvidenceGuard: {
362+
enabled: true,
363+
requireSummarySection: true,
364+
requireValidationSection: true,
365+
requireValidationEvidence: true,
366+
allowUninspectableBody: false,
367+
},
368+
},
369+
})
370+
371+
await plugin["tool.execute.before"](
372+
{ tool: "bash", sessionID: "session-pr-body-uvx-ruff" },
373+
{ args: { command: "uvx ruff check ." } },
374+
)
375+
await plugin["tool.execute.after"](
376+
{ tool: "bash", sessionID: "session-pr-body-uvx-ruff" },
377+
{ output: { stdout: "All checks passed!", stderr: "" } },
378+
)
379+
380+
await plugin["tool.execute.before"](
381+
{ tool: "bash", sessionID: "session-pr-body-uvx-ruff" },
382+
{ args: { command: 'gh pr create --title "x" --body "## Summary\n- item\n## Validation\n- uvx ruff check ."' } },
383+
)
384+
} finally {
385+
rmSync(directory, { recursive: true, force: true })
386+
}
387+
})
388+
341389
test("pr-body-evidence-guard uses LLM fallback for semantic summary and validation sections", async () => {
342390
const hook = createPrBodyEvidenceGuardHook({
343391
directory: process.cwd(),

plugin/gateway-core/test/validation-command-matcher.test.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ test("validation-command-matcher covers run and prefix package-manager forms", (
2020
test("validation-command-matcher exposes validation truthiness", () => {
2121
assert.equal(isValidationCommand("make validate"), true)
2222
assert.deepEqual(classifyValidationCommand("make validate"), ["lint"])
23+
assert.deepEqual(classifyValidationCommand("uvx ruff check ."), ["lint"])
24+
assert.deepEqual(classifyValidationCommand("uv run ruff check src"), ["lint"])
2325
assert.equal(isValidationCommand("git status --short"), false)
2426
})
2527

0 commit comments

Comments
 (0)