Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Madhura68
20c6e388e3 fix(verify/classify): negeer pseudo-paths in plan (geen PARTIAL meer voor delete-only)
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) <noreply@anthropic.com>
2026-05-09 20:19:59 +02:00
2 changed files with 65 additions and 1 deletions

View file

@ -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<string>()` 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/)
})
})

View file

@ -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()`, `<div>` —
// anything containing operator/quote/bracket chars or an ellipsis is rejected.
// Accepts paths with a slash (multi-segment) or a recognisable file-extension
// suffix (16 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, '/')