From aa0e51e704f7f8a00ba26fa8b9c942b82ee8d052 Mon Sep 17 00:00:00 2001 From: LeGreffier <261968324+legreffier[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 11:50:07 +0200 Subject: [PATCH 1/3] feat(tasks,tools): consumer-supplied rubrics for assess_brief MoltNet-Diary: 1ece9904-b651-469a-b4fb-f05e4e90cc39 Task-Group: assess-rubric-decoupling Task-Family: refactor Task-Completes: true --- libs/tasks/src/index.ts | 1 - libs/tasks/src/rubrics/pr-complexity.ts | 91 ------------------------- rubrics/pr-complexity-v1.json | 38 +++++++++++ rubrics/pr-merge-gates.json | 44 ++++++++++++ tools/src/tasks/assess-pr.ts | 76 +++++++++++++++++++-- 5 files changed, 152 insertions(+), 98 deletions(-) delete mode 100644 libs/tasks/src/rubrics/pr-complexity.ts create mode 100644 rubrics/pr-complexity-v1.json create mode 100644 rubrics/pr-merge-gates.json diff --git a/libs/tasks/src/index.ts b/libs/tasks/src/index.ts index d01dc4b07..ded59c521 100644 --- a/libs/tasks/src/index.ts +++ b/libs/tasks/src/index.ts @@ -3,7 +3,6 @@ import './formats.js'; export * from './async-validation.js'; export * from './context.js'; export * from './rubric.js'; -export * from './rubrics/pr-complexity.js'; export * from './success-criteria.js'; export * from './task-type-registry.js'; export * from './task-types/index.js'; diff --git a/libs/tasks/src/rubrics/pr-complexity.ts b/libs/tasks/src/rubrics/pr-complexity.ts deleted file mode 100644 index f7c755dcc..000000000 --- a/libs/tasks/src/rubrics/pr-complexity.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * PR-complexity rubric (v1) — drives an `assess_brief` judgment over a - * pull request, scoring how risky / hard-to-review it is. - * - * The rubric is the variation point that lets `assess_brief` (a generic - * judgment task type) cover PR review without a domain-specific task - * type or executor. Keep the criteria few, weighted, and judge-friendly: - * - * - Each criterion's `description` is the prompt the judge will reason - * over. Be explicit about what scoring 1.0 vs 0.0 looks like so the - * model has anchors. - * - Weights MUST sum to 1.0; the runtime rejects mismatches client-side. - * - All criteria are `llm_score` continuous 0..1 — there's no - * deterministic check that captures "is this PR easy to review." - * - * Future variants (`pr-complexity-v2`, `pr-security-v1`, `pr-a11y-v1`) - * sit alongside this file. The imposer picks one and inlines its rubric - * into `successCriteria.rubric` on the task input. This is the canonical - * "add a new domain by adding a rubric, not a task type" pattern. - */ -import type { Rubric, RubricCriterion } from '../rubric.js'; - -export const PR_COMPLEXITY_V1_ID = 'pr-complexity-v1' as const; - -export const PR_COMPLEXITY_V1_PREAMBLE = ` -You are reviewing a GitHub pull request for **complexity** — how hard -this change is to review safely, NOT whether it's correct or whether -the feature is worthwhile. The diff has already been opened by the -producer; your job is to score reviewability. - -You may run \`gh pr diff \`, \`gh pr view \`, and read -files in the workspace. Don't run tests, don't push commits, don't -modify anything. The PR's GitHub URL is in the target metadata. - -When in doubt about a criterion, score conservatively (lower) and -explain what made the call ambiguous. Reviewers will read your -rationale; "looks fine" is not useful, "the change touches three -unrelated subsystems and the test coverage on the auth path is -unchanged" is. -`.trim(); - -export const PR_COMPLEXITY_V1_CRITERIA: ReadonlyArray = [ - { - id: 'cognitive_load', - description: - 'How much does a reviewer have to hold in their head to assess this change? Score 1.0 for a single-purpose, well-named change with clear before/after semantics. Score 0.0 for a sprawling diff that interleaves refactor, behavior change, and dependency bumps. Look at: number of distinct concepts touched, naming clarity, whether the PR description explains the why.', - weight: 0.25, - scoring: 'llm_score', - }, - { - id: 'blast_radius', - description: - 'How far can a regression from this change propagate? Score 1.0 if the change is isolated to a single module with no public API surface change. Score 0.0 if it modifies a public contract, schema, migration, auth path, or shared utility consumed across the codebase. Look at: package boundaries crossed, exported symbols changed, schema/migration files touched, configuration defaults altered.', - weight: 0.25, - scoring: 'llm_score', - }, - { - id: 'test_coverage_delta', - description: - 'Does the change add or update tests proportional to the risk it introduces? Score 1.0 when new behavior is covered by tests at the right level (unit/integration/e2e) AND existing tests still pass without modification. Score 0.0 when behavior changes are landed without tests, or when tests are deleted/disabled to make the change pass. Look at: ratio of test changes to source changes, whether new branches have at least one test, whether any test file lost coverage.', - weight: 0.2, - scoring: 'llm_score', - }, - { - id: 'security_surface', - description: - 'Does the change touch authentication, authorization, cryptography, secrets, or external network egress? Score 1.0 when the PR is purely internal refactor or pure UI/copy with no security implications. Score 0.0 when it modifies an auth path, changes a permission check, introduces a new external dependency that processes user input, or alters a crypto primitive. Score 0.5 when it touches adjacent code (e.g. logs near a token) without changing the security path itself. Always favor a lower score when in doubt.', - weight: 0.2, - scoring: 'llm_score', - }, - { - id: 'reviewer_orientation', - description: - 'Does the PR description, commit messages, and diary entries (if any) tell a reviewer what changed and why? Score 1.0 for a PR with a clear summary, motivated by a linked issue, and individual commit messages that narrate the change. Score 0.0 for an empty description, single bulk commit titled "wip" or "fix", and no rationale anywhere. Look at: PR title quality, body length and substance, commit message hygiene, presence of "why" rather than just "what."', - weight: 0.1, - scoring: 'llm_score', - }, -]; - -/** - * Convenience export — full Rubric body the imposer inlines into - * `AssessBriefInput.successCriteria.rubric`. The runtime pins it via - * the task's `inputCid`. - */ -export const PR_COMPLEXITY_V1: Rubric = { - rubricId: PR_COMPLEXITY_V1_ID, - version: 'v1', - preamble: PR_COMPLEXITY_V1_PREAMBLE, - criteria: [...PR_COMPLEXITY_V1_CRITERIA], - scope: 'pull_request', -}; diff --git a/rubrics/pr-complexity-v1.json b/rubrics/pr-complexity-v1.json new file mode 100644 index 000000000..3a2099850 --- /dev/null +++ b/rubrics/pr-complexity-v1.json @@ -0,0 +1,38 @@ +{ + "criteria": [ + { + "description": "How much does a reviewer have to hold in their head to assess this change? Score 1.0 for a single-purpose, well-named change with clear before/after semantics. Score 0.0 for a sprawling diff that interleaves refactor, behavior change, and dependency bumps. Look at: number of distinct concepts touched, naming clarity, whether the PR description explains the why.", + "id": "cognitive_load", + "scoring": "llm_score", + "weight": 0.25 + }, + { + "description": "How far can a regression from this change propagate? Score 1.0 if the change is isolated to a single module with no public API surface change. Score 0.0 if it modifies a public contract, schema, migration, auth path, or shared utility consumed across the codebase. Look at: package boundaries crossed, exported symbols changed, schema/migration files touched, configuration defaults altered.", + "id": "blast_radius", + "scoring": "llm_score", + "weight": 0.25 + }, + { + "description": "Does the change add or update tests proportional to the risk it introduces? Score 1.0 when new behavior is covered by tests at the right level (unit/integration/e2e) AND existing tests still pass without modification. Score 0.0 when behavior changes are landed without tests, or when tests are deleted/disabled to make the change pass. Look at: ratio of test changes to source changes, whether new branches have at least one test, whether any test file lost coverage.", + "id": "test_coverage_delta", + "scoring": "llm_score", + "weight": 0.2 + }, + { + "description": "Does the change touch authentication, authorization, cryptography, secrets, or external network egress? Score 1.0 when the PR is purely internal refactor or pure UI/copy with no security implications. Score 0.0 when it modifies an auth path, changes a permission check, introduces a new external dependency that processes user input, or alters a crypto primitive. Score 0.5 when it touches adjacent code (e.g. logs near a token) without changing the security path itself. Always favor a lower score when in doubt.", + "id": "security_surface", + "scoring": "llm_score", + "weight": 0.2 + }, + { + "description": "Does the PR description, commit messages, and diary entries (if any) tell a reviewer what changed and why? Score 1.0 for a PR with a clear summary, motivated by a linked issue, and individual commit messages that narrate the change. Score 0.0 for an empty description, single bulk commit titled \"wip\" or \"fix\", and no rationale anywhere. Look at: PR title quality, body length and substance, commit message hygiene, presence of \"why\" rather than just \"what.\"", + "id": "reviewer_orientation", + "scoring": "llm_score", + "weight": 0.1 + } + ], + "preamble": "You are reviewing a GitHub pull request for **complexity** — how hard this change is to review safely, NOT whether it's correct or whether the feature is worthwhile. The diff has already been opened by the producer; your job is to score reviewability.\n\nYou may run `gh pr diff `, `gh pr view `, and read files in the workspace. Don't run tests, don't push commits, don't modify anything. The PR's GitHub URL is in the target metadata.\n\nWhen in doubt about a criterion, score conservatively (lower) and explain what made the call ambiguous. Reviewers will read your rationale; \"looks fine\" is not useful, \"the change touches three unrelated subsystems and the test coverage on the auth path is unchanged\" is.", + "rubricId": "pr-complexity-v1", + "scope": "pull_request", + "version": "v1" +} diff --git a/rubrics/pr-merge-gates.json b/rubrics/pr-merge-gates.json new file mode 100644 index 000000000..9c5553c04 --- /dev/null +++ b/rubrics/pr-merge-gates.json @@ -0,0 +1,44 @@ +{ + "criteria": [ + { + "description": "PASS if the PR's total changed lines (added + removed, across all non-generated files) is ≤ 400. FAIL if the count exceeds 400. EXCLUDE the following from the count: lockfiles (`pnpm-lock.yaml`, `go.sum`, `go.work.sum`, `package-lock.json`), generated code (`*.gen.ts`, `*.gen.go`, files under any path containing `/generated/` or `/__generated__/`), embedded schema artifacts (`*.schema.json`, `schemas/*.json`), and OpenAPI specs (`openapi.json`). Start from `gh pr diff | wc -l` and subtract the lines belonging to excluded paths. Cite both the raw count and the excluded subtotal in your evidence.", + "id": "gate_line_count", + "scoring": "boolean", + "weight": 0.1666 + }, + { + "description": "PASS if no `*.proto` files are added, modified, or deleted in the diff. FAIL if any `.proto` file is touched. Wire-protocol changes require a separate human review track and cannot auto-merge. Use `gh pr diff --name-only | grep -E '\\\\.proto$'` to confirm.", + "id": "gate_no_protobuf", + "scoring": "boolean", + "weight": 0.1666 + }, + { + "description": "PASS if no files under `libs/database/drizzle/` are added or modified. FAIL if any database migration is touched. Migrations are forward-only and need human review for ordering, rollback strategy, and data-loss risk; never auto-merge them. Also FAIL if the schema definition at `libs/database/src/schema.ts` is modified without a corresponding migration (drift is its own problem).", + "id": "gate_no_migrations", + "scoring": "boolean", + "weight": 0.1666 + }, + { + "description": "PASS if the diff does not touch deployment, container, or CI surface. FAIL if any of these paths are modified: `infra/`, `docker-compose*.yaml`, `Dockerfile*`, `.github/workflows/`, `.github/actions/`, `fly.toml`, `.fly/`, or `nx.json`. These changes affect what runs in production and how — they need a human reviewer who understands the deployment topology.", + "id": "gate_no_infra", + "scoring": "boolean", + "weight": 0.1666 + }, + { + "description": "PASS if the diff does not touch authentication, authorization, or cryptographic code. FAIL if any of these paths are modified: `libs/auth/`, `libs/crypto-service/`, `apps/rest-api/src/plugins/auth*`, `apps/mcp-server/src/auth*`, or any file path containing `auth`, `crypto`, `signing`, `jwt`, `token`, `ory`, or `keto` (case-insensitive). Security primitives need cryptographer-aware review; the gate is intentionally over-broad — false positives are cheap, false negatives are catastrophic.", + "id": "gate_no_auth", + "scoring": "boolean", + "weight": 0.1666 + }, + { + "description": "PASS if the diff does not modify the diary-write or accountable-commit audit pipeline. FAIL if any of these are touched: `libs/diary-service/`, `apps/rest-api/src/routes/diary-entries*`, `apps/rest-api/src/routes/signing*`, the `moltnet_create_entry` custom tool in `libs/pi-extension/src/moltnet/tools.ts`, or the diary-entry signing flow in `libs/crypto-service/`. The audit trail is how MoltNet proves what an agent did; changes here must be reviewed.", + "id": "gate_no_audit_logging", + "scoring": "boolean", + "weight": 0.167 + } + ], + "preamble": "You are evaluating whether a GitHub pull request meets the **objective gates** for low-risk auto-merge in this repository. Each criterion is a deterministic boolean: PASS if the gate is satisfied, FAIL if it is not. There are no fractional scores. Do NOT score reviewability or code quality here — that's the job of a separate rubric. Stick to mechanical inspection of the diff and the file paths it touches.\n\nUse `gh pr diff ` and `gh pr view ` to inspect the diff. Read individual files in the workspace if a path inspection requires confirmation. Don't run tests, don't push commits, don't modify anything.\n\nFor each gate, report PASS or FAIL with concrete evidence: a file path that triggered FAIL, a line count number, the result of a substring search. Vague answers (\"looks clean\") are not useful. When the diff is ambiguous (e.g. a renamed file might or might not be a migration), favor FAIL — the gate exists to prevent unsupervised merges through paths that need human eyes.\n\nThe composite score this rubric produces is purely informational; auto-merge consumers should AND-gate on every PASS, not on a weighted sum.", + "rubricId": "pr-merge-gates-v1", + "scope": "pull_request", + "version": "v1" +} diff --git a/tools/src/tasks/assess-pr.ts b/tools/src/tasks/assess-pr.ts index 5ba0360d4..d0d766216 100644 --- a/tools/src/tasks/assess-pr.ts +++ b/tools/src/tasks/assess-pr.ts @@ -34,12 +34,20 @@ * * Usage * ----- - * pnpm --filter @moltnet/tools task:assess-pr --target-task - * pnpm --filter @moltnet/tools task:assess-pr --target-task --dry-run + * pnpm --filter @moltnet/tools task:assess-pr \ + * --target-task --rubric rubrics/pr-complexity-v1.json + * pnpm --filter @moltnet/tools task:assess-pr \ + * --target-task --rubric rubrics/pr-merge-gates.json --dry-run * * Flags * ----- * -t, --target-task UUID of the fulfill_brief task to assess (required) + * -r, --rubric Path to a Rubric JSON file (required). Resolved + * relative to CWD. The @moltnet/tasks library ships + * no default rubric — consumers author or fork one + * per repo. See `rubrics/` at the repo root for + * starter rubrics (pr-complexity-v1.json, + * pr-merge-gates.json). * -a, --agent MoltNet agent name (default: legreffier) * --dry-run Print the synthesized Task input + skip the POST. * @@ -50,33 +58,47 @@ */ import { execFileSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; -import { join } from 'node:path'; +import { isAbsolute, join, resolve } from 'node:path'; import { parseArgs, parseEnv } from 'node:util'; import { ASSESS_BRIEF_TYPE, type AssessBriefInput, - PR_COMPLEXITY_V1, + Rubric, } from '@moltnet/tasks'; +import { Value } from '@sinclair/typebox/value'; import { connect } from '@themoltnet/sdk'; const { values: args } = parseArgs({ options: { 'target-task': { type: 'string', short: 't' }, agent: { type: 'string', short: 'a', default: 'legreffier' }, + rubric: { type: 'string', short: 'r' }, 'dry-run': { type: 'boolean', default: false }, }, }); if (!args['target-task']) { console.error( - 'Usage: tsx tools/src/tasks/assess-pr.ts --target-task [--agent ] [--dry-run]', + 'Usage: tsx tools/src/tasks/assess-pr.ts --target-task --rubric [--agent ] [--dry-run]', + ); + process.exit(1); +} + +if (!args.rubric) { + console.error( + 'Missing required flag: --rubric \n\n' + + 'Rubrics are repo-specific — there is no default. See `rubrics/` at\n' + + 'the repo root for examples (pr-complexity-v1.json, pr-merge-gates.json),\n' + + 'or author your own. The file must conform to the @moltnet/tasks Rubric\n' + + 'schema (rubricId, version, criteria[]; weights sum to 1).', ); process.exit(1); } const targetTaskId = args['target-task']; const agentName = args.agent!; +const rubricArg = args.rubric; const dryRun = args['dry-run']!; const UUID_RE = @@ -119,11 +141,53 @@ async function main() { ); } + // Resolve --rubric against the repo root, not the caller's CWD, so + // `--rubric rubrics/pr-merge-gates.json` works the same whether the + // operator is at the repo root, in `tools/`, or in a worktree + // subdirectory. Absolute paths are passed through unchanged. The + // lib ships no default rubric — consumers author or fork one per repo. + const rubricPath = isAbsolute(rubricArg) + ? rubricArg + : resolve(mainRepo, rubricArg); + let rubricJson: unknown; + try { + rubricJson = JSON.parse(readFileSync(rubricPath, 'utf8')); + } catch (err) { + throw new Error( + `Failed to read --rubric ${rubricPath}: ` + + (err instanceof Error ? err.message : String(err)), + ); + } + if (!Value.Check(Rubric, rubricJson)) { + const errors = [...Value.Errors(Rubric, rubricJson)].slice(0, 5); + throw new Error( + `Rubric at ${rubricPath} does not match the @moltnet/tasks Rubric schema. ` + + `First ${errors.length} validation error(s):\n` + + errors.map((e) => ` - ${e.path || '(root)'}: ${e.message}`).join('\n'), + ); + } + const rubric = rubricJson; + + // Weights must sum to 1 (TypeBox enforces only per-element 0..1 + // bounds; the cross-element invariant is policy, not schema). Check + // here so the operator gets a fast error instead of a server-side + // rejection later — and to make the rubric author's intent explicit. + const totalWeight = rubric.criteria.reduce( + (acc: number, c: { weight: number }) => acc + c.weight, + 0, + ); + if (Math.abs(totalWeight - 1) > 0.005) { + throw new Error( + `Rubric ${rubric.rubricId}@${rubric.version}: criterion weights ` + + `sum to ${totalWeight.toFixed(4)}, expected 1.0 (tolerance ±0.005).`, + ); + } + const input: AssessBriefInput = { targetTaskId, successCriteria: { version: 1, - rubric: PR_COMPLEXITY_V1, + rubric, }, }; From 997ad647d967304ef6f42052f9a12b006ffbfb5f Mon Sep 17 00:00:00 2001 From: LeGreffier <261968324+legreffier[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 12:06:48 +0200 Subject: [PATCH 2/3] feat(rubrics): tune pr-merge-gates for this repo's risk model + post-judgment PR comment MoltNet-Diary: 02fd7113-1290-45c9-a7e8-9d2d2e274b68 Task-Group: assess-rubric-decoupling Task-Completes: true --- rubrics/pr-merge-gates.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rubrics/pr-merge-gates.json b/rubrics/pr-merge-gates.json index 9c5553c04..d760d00da 100644 --- a/rubrics/pr-merge-gates.json +++ b/rubrics/pr-merge-gates.json @@ -1,7 +1,7 @@ { "criteria": [ { - "description": "PASS if the PR's total changed lines (added + removed, across all non-generated files) is ≤ 400. FAIL if the count exceeds 400. EXCLUDE the following from the count: lockfiles (`pnpm-lock.yaml`, `go.sum`, `go.work.sum`, `package-lock.json`), generated code (`*.gen.ts`, `*.gen.go`, files under any path containing `/generated/` or `/__generated__/`), embedded schema artifacts (`*.schema.json`, `schemas/*.json`), and OpenAPI specs (`openapi.json`). Start from `gh pr diff | wc -l` and subtract the lines belonging to excluded paths. Cite both the raw count and the excluded subtotal in your evidence.", + "description": "PASS if the PR's total changed lines (added + removed, across all non-generated files) is ≤ 300. FAIL if the count exceeds 300. EXCLUDE the following from the count: lockfiles (`pnpm-lock.yaml`, `go.sum`, `go.work.sum`, `package-lock.json`), generated code (`*.gen.ts`, `*.gen.go`, files under any path containing `/generated/` or `/__generated__/`), embedded schema artifacts (`*.schema.json`, `schemas/*.json`), and OpenAPI specs (`openapi.json`). Start from `gh pr diff | wc -l` and subtract the lines belonging to excluded paths. Cite both the raw count and the excluded subtotal in your evidence.", "id": "gate_line_count", "scoring": "boolean", "weight": 0.1666 @@ -13,13 +13,7 @@ "weight": 0.1666 }, { - "description": "PASS if no files under `libs/database/drizzle/` are added or modified. FAIL if any database migration is touched. Migrations are forward-only and need human review for ordering, rollback strategy, and data-loss risk; never auto-merge them. Also FAIL if the schema definition at `libs/database/src/schema.ts` is modified without a corresponding migration (drift is its own problem).", - "id": "gate_no_migrations", - "scoring": "boolean", - "weight": 0.1666 - }, - { - "description": "PASS if the diff does not touch deployment, container, or CI surface. FAIL if any of these paths are modified: `infra/`, `docker-compose*.yaml`, `Dockerfile*`, `.github/workflows/`, `.github/actions/`, `fly.toml`, `.fly/`, or `nx.json`. These changes affect what runs in production and how — they need a human reviewer who understands the deployment topology.", + "description": "PASS if the diff does not touch deployment, container, or CI surface. FAIL if any of these paths are modified: `infra/`, `docker-compose*.yaml`, `Dockerfile*`, `.github/workflows/`, `.github/actions/`, `fly.toml`, `.fly/`, `nx.json`, or release-please configuration (`release-please-config.json`, `.release-please-manifest.json`). These changes affect what runs in production, how releases are cut, or how CI orchestrates the build — they need a human reviewer who understands the deployment topology.", "id": "gate_no_infra", "scoring": "boolean", "weight": 0.1666 @@ -34,10 +28,16 @@ "description": "PASS if the diff does not modify the diary-write or accountable-commit audit pipeline. FAIL if any of these are touched: `libs/diary-service/`, `apps/rest-api/src/routes/diary-entries*`, `apps/rest-api/src/routes/signing*`, the `moltnet_create_entry` custom tool in `libs/pi-extension/src/moltnet/tools.ts`, or the diary-entry signing flow in `libs/crypto-service/`. The audit trail is how MoltNet proves what an agent did; changes here must be reviewed.", "id": "gate_no_audit_logging", "scoring": "boolean", + "weight": 0.1666 + }, + { + "description": "PASS if the diff does not modify the rules that govern how every future agent task runs. FAIL if any of these are touched: `AGENTS.md` and its `CLAUDE.md` symlink at the repo root, any nested `AGENTS.md` / `CLAUDE.md` in subdirectories, `libs/pi-extension/src/runtime/runtime-instructor.ts`, `.claude/rules/`, `.claude/skills/legreffier/`, or `.agents/skills/legreffier/`. These define the operating-context every agent inherits — a bad change here propagates silently to all subsequent task runs and corrupts the audit trail's reliability. Must be reviewed by someone who runs agents regularly.", + "id": "gate_no_agent_runtime_rules", + "scoring": "boolean", "weight": 0.167 } ], - "preamble": "You are evaluating whether a GitHub pull request meets the **objective gates** for low-risk auto-merge in this repository. Each criterion is a deterministic boolean: PASS if the gate is satisfied, FAIL if it is not. There are no fractional scores. Do NOT score reviewability or code quality here — that's the job of a separate rubric. Stick to mechanical inspection of the diff and the file paths it touches.\n\nUse `gh pr diff ` and `gh pr view ` to inspect the diff. Read individual files in the workspace if a path inspection requires confirmation. Don't run tests, don't push commits, don't modify anything.\n\nFor each gate, report PASS or FAIL with concrete evidence: a file path that triggered FAIL, a line count number, the result of a substring search. Vague answers (\"looks clean\") are not useful. When the diff is ambiguous (e.g. a renamed file might or might not be a migration), favor FAIL — the gate exists to prevent unsupervised merges through paths that need human eyes.\n\nThe composite score this rubric produces is purely informational; auto-merge consumers should AND-gate on every PASS, not on a weighted sum.", + "preamble": "You are evaluating whether a GitHub pull request meets the **objective gates** for low-risk auto-merge in this repository. Each criterion is a deterministic boolean: PASS if the gate is satisfied, FAIL if it is not. There are no fractional scores. Do NOT score reviewability or code quality here — that's the job of a separate rubric. Stick to mechanical inspection of the diff and the file paths it touches.\n\nUse `gh pr diff ` and `gh pr view ` to inspect the diff. Read individual files in the workspace if a path inspection requires confirmation. Don't run tests, don't push commits, don't modify anything.\n\nFor each gate, report PASS or FAIL with concrete evidence: a file path that triggered FAIL, a line count number, the result of a substring search. Vague answers (\"looks clean\") are not useful. When the diff is ambiguous (e.g. a renamed file might or might not be agent-facing rules), favor FAIL — the gate exists to prevent unsupervised merges through paths that need human eyes.\n\nThe composite score this rubric produces is purely informational; auto-merge consumers should AND-gate on every PASS, not on a weighted sum.\n\n**After** you submit your structured judgment via the submit tool, post a PR comment on the target PR summarizing each gate's PASS/FAIL verdict with one-line evidence. Use `gh pr comment ` from the bash tool — the guest VM has `gh` on PATH with a valid `GH_TOKEN` from the agent's GitHub App, so this works without host_exec. Format the comment as a markdown checklist (`- [x]` for PASS, `- [ ]` for FAIL), titled `## Auto-merge gate evaluation (pr-merge-gates-v1)`. End with a single-line composite verdict (`Verdict: `). Do NOT include the rubric's full criterion descriptions in the comment — link to `rubrics/pr-merge-gates.json` instead.", "rubricId": "pr-merge-gates-v1", "scope": "pull_request", "version": "v1" From b722654a1d7366f555668ae27e22a39d9133fe24 Mon Sep 17 00:00:00 2001 From: LeGreffier <261968324+legreffier[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 12:25:53 +0200 Subject: [PATCH 3/3] feat(rubrics): replace gate_no_protobuf with gate_no_api_specs MoltNet-Diary: 2ec7f541-9f90-45c4-b712-eaa96f9d9b59 Task-Group: assess-rubric-decoupling Task-Completes: true --- rubrics/pr-merge-gates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rubrics/pr-merge-gates.json b/rubrics/pr-merge-gates.json index d760d00da..79631a2c8 100644 --- a/rubrics/pr-merge-gates.json +++ b/rubrics/pr-merge-gates.json @@ -7,8 +7,8 @@ "weight": 0.1666 }, { - "description": "PASS if no `*.proto` files are added, modified, or deleted in the diff. FAIL if any `.proto` file is touched. Wire-protocol changes require a separate human review track and cannot auto-merge. Use `gh pr diff --name-only | grep -E '\\\\.proto$'` to confirm.", - "id": "gate_no_protobuf", + "description": "PASS if the diff does not touch HTTP API contracts or their generated clients. FAIL if any of these are modified: `apps/rest-api/public/openapi.json` (the served public spec), `libs/moltnet-api-client/openapi-normalized.json` (the ogen input), any `libs/moltnet-api-client/oas_*.go` (ogen-generated client surface), `apps/rest-api/scripts/generate-openapi.ts`, or `libs/moltnet-api-client/generate.go`. These define the wire contract MoltNet exposes; a change here means external clients (Go CLI, agent-daemon, third parties) see a different API shape. Wire-protocol changes need human review of compatibility, versioning, and downstream regeneration. Use `gh pr diff --name-only` and check for any path matching the patterns above.", + "id": "gate_no_api_specs", "scoring": "boolean", "weight": 0.1666 },