From 20c6e388e36dcd97867cf300cab92128a138845d Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Sat, 9 May 2026 20:19:59 +0200 Subject: [PATCH] fix(verify/classify): negeer pseudo-paths in plan (geen PARTIAL meer voor delete-only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extractPlanPaths beschouwde tokens als `data-debug-label="..."` als file-paden omdat ze een dot bevatten en geen spaties. Resultaat: het pseudo-pad werd nooit in de diff gevonden → coverage < 1 → PARTIAL → met verify_required=ALIGNED faalde de job, ondanks dat het werk volledig gedaan was. Concreet incident T-815 (sprint cmoyiu4yd, 2026-05-09): - 17/17 files data-debug-label verwijderd, grep 0 hits, typecheck groen - Verifier zei PARTIAL → Claude rapporteerde failed → propagateStatusUpwards + cancelPbiOnFailure cancelden 12 siblings + deleten feat/sprint-acq9twtr - T-814's al-gepushte werk verloren Fix: nieuwe `looksLikePath`-helper die backtick-tokens verwerpt als ze operator/quote/bracket chars bevatten, een ellipsis (`..`/`...`) hebben, of geen `/` én geen herkenbare file-extensie hebben. Bullet-extractor blijft onveranderd — die parseert al expliciet op `.ext`. Tests: 5 nieuwe regression-cases + alle 18 bestaande blijven groen. Co-Authored-By: Claude Opus 4.7 (1M context) --- __tests__/verify/classify.test.ts | 50 +++++++++++++++++++++++++++++++ src/verify/classify.ts | 16 +++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/__tests__/verify/classify.test.ts b/__tests__/verify/classify.test.ts index 1658e36..968e125 100644 --- a/__tests__/verify/classify.test.ts +++ b/__tests__/verify/classify.test.ts @@ -163,3 +163,53 @@ describe('classifyDiffAgainstPlan — delete-only commits', () => { expect(r.result).toBe('EMPTY') }) }) + +// Pseudo-paths in plans (code-snippets, attribute-syntax, ellipses) moeten +// niet als plan-paden meetellen — anders krijg je PARTIAL terwijl het werk +// volledig gedaan is. Regression-guard voor T-815-incident (sprint +// cmoyiu4yd000zf917acq9twtr, 2026-05-09). +describe('classifyDiffAgainstPlan — plan met pseudo-paths', () => { + it('negeert `data-debug-label="..."` als pseudo-pad en classificeert ALIGNED', () => { + const plan = [ + 'Verwijder alle voorkomens van `data-debug-label="..."` uit:', + '', + '- `app/components/shared/status-bar.tsx`', + '- `app/components/shared/header.tsx`', + ].join('\n') + const diff = makeDiff([ + 'app/components/shared/status-bar.tsx', + 'app/components/shared/header.tsx', + ]) + const r = classifyDiffAgainstPlan({ diff, plan }) + expect(r.result).toBe('ALIGNED') + }) + + it('negeert ellipsis-tokens (drie of meer dots) als pad', () => { + const plan = 'Refactor `foo(...)` naar `bar()`. Files: `src/a.ts`.' + const diff = makeDiff(['src/a.ts']) + const r = classifyDiffAgainstPlan({ diff, plan }) + expect(r.result).toBe('ALIGNED') + }) + + it('negeert tokens met operators/quotes als pad', () => { + const plan = 'Wijzig `props={x: 1}` en `useState()` in `src/c.tsx`.' + const diff = makeDiff(['src/c.tsx']) + const r = classifyDiffAgainstPlan({ diff, plan }) + expect(r.result).toBe('ALIGNED') + }) + + it('accepteert package.json en andere extension-only paths', () => { + const plan = 'Update `package.json` en `tsconfig.json`.' + const diff = makeDiff(['package.json', 'tsconfig.json']) + const r = classifyDiffAgainstPlan({ diff, plan }) + expect(r.result).toBe('ALIGNED') + }) + + it('blijft PARTIAL retourneren wanneer een echt plan-pad ontbreekt', () => { + const plan = 'Wijzig `src/foo.ts` en `src/bar.ts`. Verwijder `data-x="..."`.' + const diff = makeDiff(['src/foo.ts']) + const r = classifyDiffAgainstPlan({ diff, plan }) + expect(r.result).toBe('PARTIAL') + expect(r.reasoning).toMatch(/bar\.ts/) + }) +}) diff --git a/src/verify/classify.ts b/src/verify/classify.ts index 3fe99f5..429bfe3 100644 --- a/src/verify/classify.ts +++ b/src/verify/classify.ts @@ -27,7 +27,7 @@ function extractPlanPaths(plan: string): string[] { let m: RegExpExecArray | null while ((m = backtickRe.exec(plan)) !== null) { const p = m[1].trim() - if ((p.includes('/') || p.includes('.')) && !p.includes(' ') && p.length > 3) paths.add(p) + if (looksLikePath(p)) paths.add(p) } const bulletRe = /^[-*]\s+\*{0,2}([^\s*][^\s]*)\.([a-zA-Z]{1,6})\*{0,2}\s*[:\n]/gm @@ -38,6 +38,20 @@ function extractPlanPaths(plan: string): string[] { return [...paths] } +// Heuristic: does this backtick-quoted token look like a file path? +// Excludes code-snippets like `data-debug-label="..."`, `foo()`, `
` — +// anything containing operator/quote/bracket chars or an ellipsis is rejected. +// Accepts paths with a slash (multi-segment) or a recognisable file-extension +// suffix (1–6 alphanumeric chars after a final dot, e.g. `.tsx`, `.json`). +function looksLikePath(p: string): boolean { + if (p.length <= 3) return false + if (p.includes(' ')) return false + if (/[="'<>()[\]{};,]/.test(p)) return false + if (/\.{2,}/.test(p)) return false + if (!p.includes('/') && !/\.[a-zA-Z][a-zA-Z0-9]{0,5}$/.test(p)) return false + return true +} + // Path match: exact or suffix match so "classify.ts" matches "src/verify/classify.ts". function pathMatches(planPath: string, diffPaths: string[]): boolean { const norm = planPath.replace(/\\/g, '/')