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>
This commit is contained in:
parent
da1fe415c4
commit
20c6e388e3
2 changed files with 65 additions and 1 deletions
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 (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, '/')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue