Skip to content

Commit ca05378

Browse files
committed
refactor: aligned plan criteria dimemsion semantically
1 parent 7abd05c commit ca05378

6 files changed

Lines changed: 24 additions & 21 deletions

File tree

api/src/orchestrator/orchestrator.service.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const CONFIG: Record<string, unknown> = {
5555
};
5656

5757
const PASS_REVIEW_JSON =
58-
'```json\n{"confidence":90,"verdict":"PASS","dimensions":{"correctness":true,"tests":true,"planCoverage":true,"security":true},"issues":[]}\n```';
58+
'```json\n{"confidence":90,"verdict":"PASS","dimensions":{"correctness":true,"tests":true,"criteria":true,"security":true},"issues":[]}\n```';
5959

6060
const okRun = {
6161
runId: 'run1',

api/src/orchestrator/orchestrator.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1539,7 +1539,7 @@ export class OrchestratorService {
15391539
dimensions: {
15401540
correctness: false,
15411541
tests: false,
1542-
planCoverage: false,
1542+
criteria: false,
15431543
security: false,
15441544
},
15451545
issues: [

api/src/review/review.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const UNPARSEABLE_REVIEW_TITLE = 'Unparseable review output';
1313
* Rubric dimensions the reviewer grades independently. Each is a hard gate: the
1414
* verdict can only be PASS if every dimension holds. Confidence is advisory only.
1515
*/
16-
export const REVIEW_DIMENSIONS = ['correctness', 'tests', 'planCoverage', 'security'] as const;
16+
export const REVIEW_DIMENSIONS = ['correctness', 'tests', 'criteria', 'security'] as const;
1717
export type ReviewDimension = (typeof REVIEW_DIMENSIONS)[number];
1818
export type ReviewDimensions = Record<ReviewDimension, boolean>;
1919

api/src/review/review.prompts.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ export function buildReviewPrompt(ctx: ReviewPromptContext): string {
3232
// The repo's tests/build already ran and passed in the dedicated VERIFY stage
3333
// before this review (a failure would have routed to REVISE, not here).
3434
parts.push(
35-
`The project's automated checks (tests/build) have already passed in a separate VERIFY stage — a green result is therefore a given, not evidence of quality, and the implementer wrote its own tests. **Scrutinise the tests themselves:** confirm an automated test exists for each acceptance criterion, that each genuinely exercises the new behaviour (it would fail without the implementation — watch for trivial, tautological, or assertion-free tests), and that no existing test was weakened, skipped, or deleted to reach green. Treat a missing or gamed test as a tests-dimension failure. Then focus on correctness, security, and full plan coverage.`,
35+
`The project's automated checks (tests/build) have already passed in a separate VERIFY stage — a green result is therefore a given, not evidence of quality, and the implementer wrote its own tests. **Scrutinise the tests themselves:** confirm an automated test exists for each acceptance criterion, that each genuinely exercises the new behaviour (it would fail without the implementation — watch for trivial, tautological, or assertion-free tests), and that no existing test was weakened, skipped, or deleted to reach green. Treat a missing or gamed test as a tests-dimension failure. Then focus on correctness, security, and full coverage of the acceptance criteria.`,
3636
);
3737

3838
if (ctx.outOfPlanFiles && ctx.outOfPlanFiles.length > 0) {
3939
parts.push(
40-
`--- SCOPE CHECK ---\nThese files were changed but aren't in the approved plan's "Files to change". Out-of-plan changes are frequently legitimate: a fix the build/tests required, a shared type/config, or repairing pre-existing breakage in another package/workspace. The VERIFY stage has already PASSED, so any change the green build depends on is in scope by definition — do NOT ask for it to be reverted. Only raise an issue if a change is clearly unrelated to the task, unnecessary for a passing build, AND risky (a genuine regression or accidental edit). A pure "this is beyond the plan" observation is at most "low" severity — NEVER high/critical — and on its own must not set "dimensions.planCoverage" to false. Files:\n${ctx.outOfPlanFiles.map((f) => `- ${f}`).join('\n')}\n--- END SCOPE CHECK ---`,
40+
`--- SCOPE CHECK ---\nThese files were changed but aren't in the approved plan's "Files to change". Out-of-plan changes are frequently legitimate: a fix the build/tests required, a shared type/config, or repairing pre-existing breakage in another package/workspace. The VERIFY stage has already PASSED, so any change the green build depends on is in scope by definition — do NOT ask for it to be reverted. Only raise an issue if a change is clearly unrelated to the task, unnecessary for a passing build, AND risky (a genuine regression or accidental edit). A pure "this is beyond the plan" observation is at most "low" severity — NEVER high/critical — and on its own must not set "dimensions.criteria" to false. Files:\n${ctx.outOfPlanFiles.map((f) => `- ${f}`).join('\n')}\n--- END SCOPE CHECK ---`,
4141
);
4242
}
4343

@@ -74,7 +74,7 @@ If the server fails to start, skip the browser step and note it in your summary
7474
"dimensions": {
7575
"correctness": <true|false: the change is logically correct and resolves the issue>,
7676
"tests": <true|false: automated tests meaningfully encode each acceptance criterion — they exercise the new behaviour and would fail without it — and no existing test was weakened, skipped, or deleted>,
77-
"planCoverage": <true|false: every acceptance criterion in the plan is met. Necessary supporting changes (build/test fixes, shared config, repairing other packages the verify gate needs) are fine — only set false for missing criteria or material, unjustified, unrelated divergence>,
77+
"criteria": <true|false: every acceptance criterion in the plan is met. Necessary supporting changes (build/test fixes, shared config, repairing other packages the verify gate needs) are fine — only set false for missing criteria or material, unjustified, unrelated divergence>,
7878
"security": <true|false: no injection, secret-leak, auth, or unsafe-input problems introduced>
7979
},
8080
"summary": "<one-paragraph assessment>",
@@ -92,7 +92,7 @@ If the server fails to start, skip the browser step and note it in your summary
9292
`- Start the response with a single \`\`\`json fenced block — no preamble, narrative, or prose before it.\n` +
9393
`- \`verdict\` MUST be the string "PASS" or "FAIL" (uppercase) — NOT a boolean (\`true\`/\`false\`), number, or any other word.\n` +
9494
`- \`confidence\` MUST be present, as an integer 0-100.\n` +
95-
`- \`dimensions\` MUST contain all four boolean keys: \`correctness\`, \`tests\`, \`planCoverage\`, \`security\`.\n` +
95+
`- \`dimensions\` MUST contain all four boolean keys: \`correctness\`, \`tests\`, \`criteria\`, \`security\`.\n` +
9696
`- MOST IMPORTANT — \`issues\`: every concrete problem MUST be a structured object in the \`issues\` array with the exact \`{severity,title,detail,file?}\` shape. This array is the ONLY thing passed to the agent that fixes the code — any finding left out, written as prose, or placed under a stray key (\`rationale\`, \`findings\`, \`explanation\`, …) is INVISIBLE to the fix stage and WILL NOT be fixed. Each \`detail\` must say both what is wrong and how to fix it. Put a FAIL's full reasoning here, not after the block.`,
9797
);
9898
}

api/src/review/review.utility.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from './review.utility.js';
99
import { type ReviewResult } from './review.model.js';
1010

11-
const PASS_DIMS = { correctness: true, tests: true, planCoverage: true, security: true };
11+
const PASS_DIMS = { correctness: true, tests: true, criteria: true, security: true };
1212

1313
function result(overrides: Partial<ReviewResult> = {}): ReviewResult {
1414
return {
@@ -24,25 +24,25 @@ function result(overrides: Partial<ReviewResult> = {}): ReviewResult {
2424
describe('parseReview', () => {
2525
it('parses a fenced json verdict with rubric dimensions', () => {
2626
const out =
27-
'Here is my review:\n```json\n{"confidence":92,"verdict":"PASS","dimensions":{"correctness":true,"tests":true,"planCoverage":true,"security":true},"issues":[]}\n```';
27+
'Here is my review:\n```json\n{"confidence":92,"verdict":"PASS","dimensions":{"correctness":true,"tests":true,"criteria":true,"security":true},"issues":[]}\n```';
2828
const r = parseReview(out);
2929
expect(r?.confidence).toBe(92);
3030
expect(r?.verdict).toBe('PASS');
3131
expect(r?.dimensions.tests).toBe(true);
3232
expect(r?.verifyOk).toBeNull();
3333
});
3434

35-
it('accepts stringy "pass"/"fail" dimension values and snake_case keys', () => {
35+
it('accepts stringy "pass"/"fail" dimension values', () => {
3636
const r = parseReview(
37-
'{"confidence":50,"dimensions":{"correctness":"pass","tests":"fail","plan_coverage":"yes","security":true},"issues":[]}',
37+
'{"confidence":50,"dimensions":{"correctness":"pass","tests":"fail","criteria":"yes","security":true},"issues":[]}',
3838
);
3939
expect(r?.dimensions.tests).toBe(false);
40-
expect(r?.dimensions.planCoverage).toBe(true);
40+
expect(r?.dimensions.criteria).toBe(true);
4141
});
4242

4343
it('derives FAIL when a dimension fails even with no explicit verdict', () => {
4444
const r = parseReview(
45-
'{"confidence":80,"dimensions":{"correctness":false,"tests":true,"planCoverage":true,"security":true},"issues":[]}',
45+
'{"confidence":80,"dimensions":{"correctness":false,"tests":true,"criteria":true,"security":true},"issues":[]}',
4646
);
4747
expect(r?.verdict).toBe('FAIL');
4848
});
@@ -68,7 +68,7 @@ describe('parseReview', () => {
6868
it('returns null for an off-schema verdict (must re-run, not salvage)', () => {
6969
expect(
7070
parseReview(
71-
'```json\n{"verdict":false,"dimensions":{"correctness":false,"tests":true,"planCoverage":false,"security":true}}\n```',
71+
'```json\n{"verdict":false,"dimensions":{"correctness":false,"tests":true,"criteria":false,"security":true}}\n```',
7272
),
7373
).toBeNull();
7474
});
@@ -105,9 +105,9 @@ describe('meetsThreshold', () => {
105105

106106
describe('failedDimensions', () => {
107107
it('lists the human labels of failing dimensions', () => {
108-
expect(failedDimensions({ ...PASS_DIMS, tests: false, planCoverage: false })).toEqual([
108+
expect(failedDimensions({ ...PASS_DIMS, tests: false, criteria: false })).toEqual([
109109
'tests',
110-
'plan coverage',
110+
'acceptance criteria',
111111
]);
112112
});
113113
});

api/src/review/review.utility.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,22 @@ const boolish = z.preprocess((v) => {
2121
if (typeof v === 'boolean') {
2222
return v;
2323
}
24+
2425
if (typeof v === 'string') {
2526
const s = v.trim().toLowerCase();
27+
2628
if (['pass', 'passed', 'yes', 'true', 'ok', 'y', '1'].includes(s)) {
2729
return true;
2830
}
31+
2932
if (['fail', 'failed', 'no', 'false', 'n', '0'].includes(s)) {
3033
return false;
3134
}
3235
}
36+
3337
return undefined;
3438
}, z.boolean().default(true));
3539

36-
// Tolerate snake_case / loose key names for the multi-word dimension.
3740
const dimensionsSchema = z.preprocess(
3841
(v) => {
3942
if (!v || typeof v !== 'object') {
@@ -43,14 +46,14 @@ const dimensionsSchema = z.preprocess(
4346
return {
4447
correctness: o.correctness,
4548
tests: o.tests,
46-
planCoverage: o.planCoverage ?? o.plan_coverage ?? o.coverage,
49+
criteria: o.criteria,
4750
security: o.security,
4851
};
4952
},
5053
z.object({
5154
correctness: boolish,
5255
tests: boolish,
53-
planCoverage: boolish,
56+
criteria: boolish,
5457
security: boolish,
5558
}),
5659
);
@@ -69,7 +72,7 @@ const reviewSchema = z.object({
6972
const ALL_DIMENSIONS_PASS: ReviewDimensions = {
7073
correctness: true,
7174
tests: true,
72-
planCoverage: true,
75+
criteria: true,
7376
security: true,
7477
};
7578

@@ -169,7 +172,7 @@ export function meetsThreshold(result: ReviewResult): boolean {
169172
const DIMENSION_LABELS: Record<ReviewDimension, string> = {
170173
correctness: 'correctness',
171174
tests: 'tests',
172-
planCoverage: 'plan coverage',
175+
criteria: 'acceptance criteria',
173176
security: 'security',
174177
};
175178

0 commit comments

Comments
 (0)