diff --git a/scripts/lib/inspection.js b/scripts/lib/inspection.js index 2150ccf83..e3e257b06 100644 --- a/scripts/lib/inspection.js +++ b/scripts/lib/inspection.js @@ -21,10 +21,11 @@ function normalizeFailureReason(reason) { .replace(/\d{4}-\d{2}-\d{2}[t ]\d{2}:\d{2}:\d{2}[.\dz]*/g, '') // Strip UUIDs (already lowercased) .replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g, '') - // Quoting disambiguates spaces in the final Windows path component. + // Quoted Windows paths preserve spaces in the final path component. .replace(/(["'])(?:[a-z]:[\\/]|\\\\)[^\r\n]*?\1/gu, '$1$1') // Directories may contain spaces, but trailing whitespace separates diagnostic prose. - .replace(/(?:\b[a-z]:|\\)[\\/](?:[^\\/:*?"<>|\r\n]*[^\s\\/:*?"<>|][\\/])*[^\s\\/:*?"<>|(),;]+/gu, '') + // A colon delimits spaced leaves; trailing separators belong to the path. + .replace(/(?:\b[a-z]:|\\)[\\/](?:[^\\/:*?"<>|\r\n]*[^\s\\/:*?"<>|][\\/])*[^\s\\/:*?"<>|(),;]+(?: [^\\/:*?"<>|\r\n]+(?=:))?[\\/]*/gu, '') // Strip remaining file paths .replace(/(?:\b[a-z]:)?[\\/][\w./\\-]+/g, '') // Collapse whitespace diff --git a/tests/lib/inspection.test.js b/tests/lib/inspection.test.js index f354ef6d6..9199a8734 100644 --- a/tests/lib/inspection.test.js +++ b/tests/lib/inspection.test.js @@ -66,6 +66,9 @@ async function runTests() { String.raw`File not found: \\server\share\three.js`, String.raw`File not found: C:/project-a\src\one.js`, String.raw`File not found: D:\project-b/src/two.js`, + 'File not found: C:\\project-a\\', + 'File not found: D:/project-b/', + 'File not found: \\\\server\\share\\', ]) { assert.strictEqual(normalizeFailureReason(reason), 'file not found: '); } @@ -97,6 +100,9 @@ async function runTests() { String.raw`C:\Program Files\node`, 'D:/Program Files/node', String.raw`\\server\shared files\node`, + String.raw`C:\Users\Alice Smith\my file.js`, + 'D:/Users/Bob Jones/your script.js', + String.raw`\\server\shared files\other task.js`, ]) { assert.strictEqual(normalizeFailureReason(`${filePath}: permission denied`), ': permission denied'); } @@ -108,6 +114,8 @@ async function runTests() { 'cannot run failed to open '); assert.strictEqual(normalizeFailureReason('D:/Program Files/node permission denied in /tmp/log'), ' permission denied in '); + assert.strictEqual(normalizeFailureReason('Cannot run C:/tools/node failed to open /tmp/log: permission denied'), + 'cannot run failed to open : permission denied'); })) passed += 1; else failed += 1; if (await test('groupFailures groups by skillId and normalized reason', async () => { @@ -214,15 +222,21 @@ async function runTests() { if (await test('detectPatterns groups failures from different Windows paths', async () => { const runs = [ - makeSkillRun({ id: 'r1', failureReason: String.raw`Cannot open C:\Users\Alice Smith\one.js: permission denied` }), - makeSkillRun({ id: 'r2', failureReason: 'Cannot open D:/Users/Bob Jones/two.js: permission denied' }), - makeSkillRun({ id: 'r3', failureReason: String.raw`Cannot open E:\Users\Carol White\three.js: permission denied` }), + makeSkillRun({ id: 'r1', failureReason: String.raw`Cannot open C:\Users\Alice Smith\my file.js: permission denied` }), + makeSkillRun({ id: 'r2', failureReason: 'Cannot open D:/Users/Bob Jones/your script.js: permission denied' }), + makeSkillRun({ id: 'r3', failureReason: String.raw`Cannot open E:\Users\Carol White\other task.js: permission denied` }), ]; const patterns = detectPatterns(runs); assert.strictEqual(patterns.length, 1); assert.strictEqual(patterns[0].count, 3); assert.strictEqual(patterns[0].normalizedReason, 'cannot open : permission denied'); assert.strictEqual(inspect({ getStatus: () => ({ skillRuns: { recent: runs } }) }).status, 'attention_needed'); + const directoryRuns = ['C:\\project-a\\', 'D:/project-b/', '\\\\server\\share\\'].map((filePath, index) => + makeSkillRun({ id: `directory-${index}`, failureReason: `Cannot open ${filePath}` })); + const directoryPatterns = detectPatterns(directoryRuns); + assert.strictEqual(directoryPatterns.length, 1); + assert.strictEqual(directoryPatterns[0].count, 3); + assert.strictEqual(generateReport(directoryPatterns).status, 'attention_needed'); })) passed += 1; else failed += 1; if (await test('detectPatterns tracks unique session IDs and versions', async () => {