diff --git a/src/commands.cts b/src/commands.cts index 93419df94..9ffd6522c 100644 --- a/src/commands.cts +++ b/src/commands.cts @@ -1334,7 +1334,7 @@ function cmdTodoMatchPhase(cwd: string, phase: string | undefined, raw: boolean) for (const pf of planFiles) { const planContent = platformReadSync(path.join(phaseDir, pf)); if (planContent === null) continue; - const fmFiles = planContent.match(/files_modified:\s*\[([^\]]*)\]/); + const fmFiles = planContent.match(/files_modified:\s*\[([^\]]{0,8000})\]/); if (fmFiles) { phasePlans.push(...fmFiles[1].split(',').map(s => s.trim().replace(/['"]/g, '')).filter(Boolean)); } diff --git a/src/markdown-sectionizer.cts b/src/markdown-sectionizer.cts index 0665ff39c..8a745d37a 100644 --- a/src/markdown-sectionizer.cts +++ b/src/markdown-sectionizer.cts @@ -537,7 +537,7 @@ export function extractTaggedBlocks(content: string, tagName: string): string[] // Escape the tag name for safe interpolation into a RegExp. const escapedTag = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const pattern = new RegExp(`<${escapedTag}>([\\s\\S]*?)`, 'g'); + const pattern = new RegExp(`<${escapedTag}>((?:(?!<${escapedTag}>)[\\s\\S])*?)`, 'g'); const results: string[] = []; let match: RegExpExecArray | null; diff --git a/src/phase.cts b/src/phase.cts index 24c68bf5d..78de4bfae 100644 --- a/src/phase.cts +++ b/src/phase.cts @@ -1519,7 +1519,7 @@ function cmdPhaseComplete(cwd: string, phaseNum: string, raw: boolean): void { } const planCountPattern = new RegExp( - `(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, + `(#{2,4}\\s*Phase\\s+${phaseEscaped}(?:(?!\\n#{1,4}\\s)[\\s\\S])*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, 'i', ); roadmapContent = roadmapContent.replace( diff --git a/src/roadmap.cts b/src/roadmap.cts index 7f6febff9..427c44365 100644 --- a/src/roadmap.cts +++ b/src/roadmap.cts @@ -545,7 +545,7 @@ function cmdRoadmapUpdatePlanProgress(cwd: string, phaseNum: string | null | und // `**Plans:** N plans` — bold "Plans:" (colon inside bold) // `Plans: N plans` — plain text header const planCountPattern = new RegExp( - `(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])[\\s\\S]*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*|(?:^|\\n)Plans:)\\s*)[^\\n]+`, + `(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*|(?:^|\\n)Plans:)\\s*)[^\\n]+`, 'i' ); const planCountText = isComplete @@ -615,11 +615,11 @@ function cmdRoadmapUpdatePlanProgress(cwd: string, phaseNum: string | null | und // Pattern A: anchor to bare `Plans:` header (preferred). // Pattern B: fallback to bold summary when no bare header exists. const insertRowsPatternA = new RegExp( - `(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])[\\s\\S]*?(?:^|\\n)(?:Plans:)[^\\n]*)`, + `(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:^|\\n)(?:Plans:)[^\\n]*)`, 'i' ); const insertRowsPatternB = new RegExp( - `(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])[\\s\\S]*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*)[^\\n]*)`, + `(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*)[^\\n]*)`, 'i' ); diff --git a/src/verify.cts b/src/verify.cts index 27a71d92e..184344cf8 100644 --- a/src/verify.cts +++ b/src/verify.cts @@ -373,20 +373,20 @@ function scanFileWideNegativeGateConflict(content: string): { warnings: string[] const namem = tc.match(/([\s\S]*?)<\/name>/); const name = namem ? namem[1].trim() : 'unnamed'; // Extract entries. - const filesm = tc.match(/([\s\S]*?)<\/files>/); + const filesm = tc.match(/((?:(?!)[\s\S])*?)<\/files>/); const filesText = filesm ? filesm[1] : ''; const files = filesText.split(/[,\s]+/).map(s => s.trim()).filter(Boolean); // Gate text: //. const gateFragments: string[] = []; for (const tag of ['verify', 'automated', 'acceptance_criteria']) { - const re = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, 'g'); + const re = new RegExp(`<${tag}>((?:(?!<${tag}>)[\\s\\S])*?)<\\/${tag}>`, 'g'); let mm: RegExpExecArray | null; while ((mm = re.exec(tc)) !== null) gateFragments.push(mm[1]); } // Requirement text: /. const reqFragments: string[] = []; for (const tag of ['action', 'acceptance_criteria']) { - const re = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, 'g'); + const re = new RegExp(`<${tag}>((?:(?!<${tag}>)[\\s\\S])*?)<\\/${tag}>`, 'g'); let mm: RegExpExecArray | null; while ((mm = re.exec(tc)) !== null) reqFragments.push(mm[1]); } @@ -2094,7 +2094,7 @@ function cmdVerifySchemaDrift( const planFiles = fs.readdirSync(phaseDir).filter((f) => f.endsWith('-PLAN.md')); for (const pf of planFiles) { const content = fs.readFileSync(path.join(phaseDir, pf), 'utf-8'); - const fmMatch = content.match(/files_modified:\s*\[([^\]]*)\]/); + const fmMatch = content.match(/files_modified:\s*\[([^\]]{0,8000})\]/); if (fmMatch) { const files = fmMatch[1].split(',').map((f) => f.trim()).filter(Boolean); allFiles.push(...files);