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
1 change: 1 addition & 0 deletions .judgement/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
results/
55 changes: 55 additions & 0 deletions .judgement/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Roomote rule calibration

These are labeled changes for each of the eight repository rules. Labels describe
what the rule requires, not what the model happened to answer. Keep cases that
fail: they are regression examples, not reasons to change the expected label.

| ID | Rule | Examples |
| ------------- | --------------------------------------- | -------: |
| `criterion_1` | Secrets and private data | 6 |
| `criterion_2` | Worker subprocess environments | 6 |
| `criterion_3` | Sanitized API errors | 6 |
| `criterion_4` | Agent setup prose | 6 |
| `criterion_5` | Independent async operations | 6 |
| `criterion_6` | UI and setup copy | 6 |
| `criterion_7` | Lowercase ordinary nouns | 11 |
| `criterion_8` | Direct descriptions of current behavior | 10 |

IDs currently follow the order in `.judgement/rules.json`. Preserve those IDs explicitly if
reordering rules, and update examples when changing the intended rule behavior.
The `path` in each example matches the real rule's file globs. Before/after strings
are synthetic source files; they are judged as diffs and are never executed.

## Running examples

Run from the repository root:

```sh
pnpm judgement test --dry-run
pnpm judgement test --rule criterion_8
pnpm judgement calibrate --rule criterion_8 --thresholds 0.8,0.85,0.9
pnpm --silent judgement test --format json > .judgement/results/latest.json
```

Create `.judgement/results/` before redirecting output there. The commands use
Roomote's existing model settings and server credentials through the shared
Judgement library. Keep generated reports local; `results/` is ignored by Git.
See [Judgement's agent guide](https://github.com/RooCodeInc/judgement/blob/main/docs/agents.md)
for rule design, calibration, and interpreting incomplete results.

## Coverage limits

The privacy rule explicitly allows synthetic credentials and fictional identities.
Its fixtures are therefore **all valid**. Do not relabel a placeholder as a secret
violation just to get a positive test, and never add real credentials or private
data as fixtures. These examples measure false alarms only; they do not establish
secret-detection recall or justify a new privacy threshold.

The other rules include violations, allowed exceptions, and fixes to existing
violations. Rule 7 also checks identifiers, sentence starts, title case, exact UI
labels, and an unrelated edit near an unchanged capitalization violation. Rule 8
also covers migration guides, release notes, supported choices, and useful security
constraints.

Incomplete checks allow hook commits but fail strict checks. They do not count as
successful violation detection or complete approval of a valid edit.
47 changes: 47 additions & 0 deletions .judgement/examples/criterion_1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"ruleId": "criterion_1",
"examples": [
{
"name": "Clearly synthetic credential placeholder",
"path": ".env.example",
"before": "TOKEN=\n",
"after": "TOKEN=REPLACE_WITH_YOUR_TOKEN\n",
"expected": "pass"
},
{
"name": "Server-side credential lookup",
"path": "apps/api/src/config.ts",
"before": "export const config = {};\n",
"after": "export const config = { apiKey: process.env.PROVIDER_API_KEY };\n",
"expected": "pass"
},
{
"name": "Redacted diagnostic example",
"path": "docs/troubleshooting.md",
"before": "Example output:\n",
"after": "Example output: {\"authorization\":\"[REDACTED]\"}\n",
"expected": "pass"
},
{
"name": "Public configuration",
"path": "src/config.ts",
"before": "export const options = {};\n",
"after": "export const options = { timeoutMs: 3000, retries: 2 };\n",
"expected": "pass"
},
{
"name": "Unmistakably fictional fixture",
"path": "test/users.fixture.ts",
"before": "export const users = [];\n",
"after": "export const users = [{ name: \"Synthetic Test User\", email: \"test-user@example.invalid\" }];\n",
"expected": "pass"
},
{
"name": "Remove a placeholder from an example",
"path": ".env.example",
"before": "API_KEY=REPLACE_WITH_YOUR_KEY\n",
"after": "API_KEY=\n",
"expected": "pass"
}
]
}
47 changes: 47 additions & 0 deletions .judgement/examples/criterion_2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"ruleId": "criterion_2",
"examples": [
{
"name": "Spread all launcher variables",
"path": "apps/worker/src/run-task/launch.ts",
"before": "export function taskEnv() { return { PATH: process.env.PATH }; }\n",
"after": "export function taskEnv() { return { ...process.env }; }\n",
"expected": "violation"
},
{
"name": "Forward worker authentication to task",
"path": "apps/worker/src/run-task/launch.ts",
"before": "export function taskEnv() { return { PATH: process.env.PATH }; }\n",
"after": "export function taskEnv() { return { PATH: process.env.PATH, WORKER_AUTH_TOKEN: process.env.WORKER_AUTH_TOKEN }; }\n",
"expected": "violation"
},
{
"name": "Explicit non-sensitive allowlist",
"path": "apps/worker/src/run-task/launch.ts",
"before": "export function taskEnv() { return {}; }\n",
"after": "export function taskEnv() { return { PATH: process.env.PATH, LANG: process.env.LANG }; }\n",
"expected": "pass"
},
{
"name": "Named nested runtime contract",
"path": "apps/worker/src/run-task/launch.ts",
"before": "export function taskEnv() { return { PATH: process.env.PATH }; }\n",
"after": "// The nested Node service requires NODE_OPTIONS to set its heap limit.\nexport function taskEnv() { return { PATH: process.env.PATH, NODE_OPTIONS: \"--max-old-space-size=512\" }; }\n",
"expected": "pass"
},
{
"name": "Scrub captured worker token before launch",
"path": "apps/worker/src/run-task/launch.ts",
"before": "export function launch(spawn, binary, capturedEnv) { return spawn(binary, [], { env: capturedEnv }); }\n",
"after": "export function launch(spawn, binary, capturedEnv) {\n const env = { PATH: capturedEnv.PATH, LANG: capturedEnv.LANG };\n return spawn(binary, [], { env });\n}\n",
"expected": "pass"
},
{
"name": "Synthetic fixture with no inherited environment",
"path": "apps/worker/src/run-task/launch.test.ts",
"before": "export const exampleEnv = {};\n",
"after": "export const exampleEnv = { PATH: \"/synthetic/bin\", WORKER_AUTH_TOKEN: \"NON_AUTHENTICATING_TEST_PLACEHOLDER\" };\n",
"expected": "pass"
}
]
}
47 changes: 47 additions & 0 deletions .judgement/examples/criterion_3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"ruleId": "criterion_3",
"examples": [
{
"name": "Return raw exception message",
"path": "apps/api/src/handlers/project.ts",
"before": "export function failure(c, error) { return c.json({ error: \"Unable to load the project.\" }, 500); }\n",
"after": "export function failure(c, error) { return c.json({ error: error.message }, 500); }\n",
"expected": "violation"
},
{
"name": "Return stack trace",
"path": "apps/api/src/handlers/project.ts",
"before": "export function failure(c, error) { return c.json({ error: \"Unable to load the project.\" }, 500); }\n",
"after": "export function failure(c, error) { return c.json({ error: error.stack }, 500); }\n",
"expected": "violation"
},
{
"name": "Return full exception object",
"path": "apps/api/src/handlers/project.ts",
"before": "export function failure(c, error) { return c.json({ error: \"Request failed.\" }, 500); }\n",
"after": "export function failure(c, error) { return c.json({ error }, 500); }\n",
"expected": "violation"
},
{
"name": "Sanitized failure response",
"path": "apps/api/src/handlers/project.ts",
"before": "export function failure(c, error) { return c.json({ error: error.message }, 500); }\n",
"after": "export function failure(c, error) { return c.json({ error: \"Unable to load the project. Try again.\" }, 500); }\n",
"expected": "pass"
},
{
"name": "Safe not-found response",
"path": "apps/api/src/handlers/project.ts",
"before": "export function missing(c) { return c.json({}, 404); }\n",
"after": "export function missing(c) { return c.json({ error: \"Project not found.\" }, 404); }\n",
"expected": "pass"
},
{
"name": "Safe validation message",
"path": "apps/api/src/handlers/project.ts",
"before": "export function invalid(c) { return c.json({}, 400); }\n",
"after": "export function invalid(c) { return c.json({ error: \"Enter a valid project name.\" }, 400); }\n",
"expected": "pass"
}
]
}
47 changes: 47 additions & 0 deletions .judgement/examples/criterion_4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"ruleId": "criterion_4",
"examples": [
{
"name": "Tell user to operate an internal card",
"path": "packages/cloud-agents/src/server/setup-prompt.ts",
"before": "export const setupReply = \"Connect a repository so I can review your code.\";\n",
"after": "export const setupReply = \"Click the Configure button in the setup card on the right rail.\";\n",
"expected": "violation"
},
{
"name": "Ask for credentials in chat",
"path": "packages/cloud-agents/src/server/setup-prompt.ts",
"before": "export const setupReply = \"Connect your account securely to continue.\";\n",
"after": "export const setupReply = \"Paste your API key into this chat so I can connect your account.\";\n",
"expected": "violation"
},
{
"name": "State needed capability",
"path": "packages/cloud-agents/src/server/setup-prompt.ts",
"before": "export const setupReply = \"I need access.\";\n",
"after": "export const setupReply = \"Connect a repository so I can review your code.\";\n",
"expected": "pass"
},
{
"name": "State user outcome",
"path": "packages/cloud-agents/src/server/setup-prompt.ts",
"before": "export const setupReply = \"Configuration complete.\";\n",
"after": "export const setupReply = \"I can now review pull requests in your repository.\";\n",
"expected": "pass"
},
{
"name": "Internal tool schema may describe controls",
"path": "packages/cloud-agents/src/server/setup-prompt.ts",
"before": "export const toolDescription = \"Configure access.\";\n",
"after": "// Internal tool schema, never shown as agent prose.\nexport const toolDescription = \"Render the trusted repository configuration card with its Connect button.\";\n",
"expected": "pass"
},
{
"name": "Trusted UI description may describe controls",
"path": "packages/cloud-agents/src/server/setup-prompt.ts",
"before": "export const trustedUiDescription = \"Connection settings\";\n",
"after": "// Trusted UI description for the setup renderer; not agent prose.\nexport const trustedUiDescription = \"The Connect button opens the repository permissions dialog.\";\n",
"expected": "pass"
}
]
}
47 changes: 47 additions & 0 deletions .judgement/examples/criterion_5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"ruleId": "criterion_5",
"examples": [
{
"name": "Serialize independent read requests",
"path": "apps/web/src/server/dashboard.ts",
"before": "export async function load(db) {\n return Promise.all([db.projects.findMany(), db.notifications.findMany()]);\n}\n",
"after": "export async function load(db) {\n const projects = await db.projects.findMany();\n const notifications = await db.notifications.findMany();\n return [projects, notifications];\n}\n",
"expected": "violation"
},
{
"name": "Await work before unrelated early return",
"path": "apps/web/src/server/dashboard.ts",
"before": "export async function load(fetchProjects, showProjects) {\n if (!showProjects) return [];\n return fetchProjects();\n}\n",
"after": "export async function load(fetchProjects, showProjects) {\n const projects = await fetchProjects();\n if (!showProjects) return [];\n return projects;\n}\n",
"expected": "violation"
},
{
"name": "Parallel independent reads",
"path": "apps/web/src/server/dashboard.ts",
"before": "export async function load(db) {\n const projects = await db.projects.findMany();\n const notifications = await db.notifications.findMany();\n return [projects, notifications];\n}\n",
"after": "export async function load(db) {\n return Promise.all([db.projects.findMany(), db.notifications.findMany()]);\n}\n",
"expected": "pass"
},
{
"name": "Second query needs first result",
"path": "apps/web/src/server/dashboard.ts",
"before": "export async function load(db, slug) { return db.projects.findBySlug(slug); }\n",
"after": "export async function load(db, slug) {\n const project = await db.projects.findBySlug(slug);\n return db.tasks.findMany({ projectId: project.id });\n}\n",
"expected": "pass"
},
{
"name": "Preserve transaction order",
"path": "apps/web/src/server/dashboard.ts",
"before": "export async function update(db, id) { return db.project.update(id); }\n",
"after": "export async function update(db, id) {\n return db.transaction(async tx => {\n await tx.project.lock(id);\n return tx.project.update(id);\n });\n}\n",
"expected": "pass"
},
{
"name": "Explicit rate-limited operation",
"path": "apps/web/src/server/dashboard.ts",
"before": "export async function send(sendOne, ids) { return ids; }\n",
"after": "// Provider contract permits one outstanding request at a time.\nexport async function send(sendOne, ids) {\n for (const id of ids) await sendOne(id);\n}\n",
"expected": "pass"
}
]
}
47 changes: 47 additions & 0 deletions .judgement/examples/criterion_6.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"ruleId": "criterion_6",
"examples": [
{
"name": "Describe implementation in setup copy",
"path": "apps/web/src/components/setup-copy.ts",
"before": "export const description = \"Get updates when work is ready.\";\n",
"after": "export const description = \"This feature is default off and uses a polling worker to materialize event records in a Redis queue.\";\n",
"expected": "violation"
},
{
"name": "Expose internal mechanism in task description",
"path": "apps/web/src/components/setup-copy.ts",
"before": "export const description = \"Review your latest code changes.\";\n",
"after": "export const description = \"This task hydrates the worker state, dispatches an internal tool schema, and serializes the result into our event envelope.\";\n",
"expected": "violation"
},
{
"name": "Explain user value",
"path": "apps/web/src/components/setup-copy.ts",
"before": "export const description = \"Configure notifications.\";\n",
"after": "export const description = \"Get updates when your work is ready.\";\n",
"expected": "pass"
},
{
"name": "Necessary permission consequence",
"path": "apps/web/src/components/setup-copy.ts",
"before": "export const permissionHelp = \"Connect your repository.\";\n",
"after": "export const permissionHelp = \"Allow read access so your agent can review code. It cannot push changes without write access.\";\n",
"expected": "pass"
},
{
"name": "Actionable recovery message",
"path": "apps/web/src/components/setup-copy.ts",
"before": "export const errorMessage = \"Failed.\";\n",
"after": "export const errorMessage = \"Connection expired. Reconnect your account to continue.\";\n",
"expected": "pass"
},
{
"name": "Necessary security copy",
"path": "apps/web/src/components/setup-copy.ts",
"before": "export const securityHelp = \"Account settings\";\n",
"after": "export const securityHelp = \"Revoking this token immediately stops access for apps that use it.\";\n",
"expected": "pass"
}
]
}
Loading
Loading