fix: preserve diagnostics after extensionless Windows paths

(cherry picked from commit 2a2e6e3848)
This commit is contained in:
Eneko Ruiz authored and haelyra committed 2026-10-20 17:38:38 -04:00
1 parent 58b191c25d
commit 9388e06ec7
2 files changed
+18 -2

No files matched your search

+2 -2
View File
@@ -24,8 +24,8 @@ function normalizeFailureReason(reason) {
// Quoted Windows paths preserve spaces in the final path component.
.replace(/(["'])(?:[a-z]:[\\/]|\\\\)[^\r\n]*?\1/gu, '$1<path>$1')
// Directories may contain spaces, but trailing whitespace separates diagnostic prose.
// A colon delimits spaced leaves; trailing separators belong to the path.
.replace(/(?:\b[a-z]:|\\)[\\/](?:[^\\/:*?"<>|\r\n]*[^\s\\/:*?"<>|][\\/])*[^\s\\/:*?"<>|(),;]+(?: [^\\/:*?"<>|\r\n]+(?=:))?[\\/]*/gu, '<path>')
// Spaced leaves need an extension before a colon, or quotes; preserve extensionless diagnostics.
.replace(/(?:\b[a-z]:|\\)[\\/](?:[^\\/:*?"<>|\r\n]*[^\s\\/:*?"<>|][\\/])*[^\s\\/:*?"<>|(),;]+(?: [^\\/:*?"<>|\r\n]*\.[\p{L}\p{N}_-]+(?=:))?[\\/]*/gu, '<path>')
// Strip remaining file paths
.replace(/(?:\b[a-z]:)?[\\/][\w./\\-]+/g, '<path>')
// Collapse whitespace
+16
View File
@@ -87,6 +87,7 @@ async function runTests() {
String.raw`C:\Usuarios\José Pérez\mi archivo.js`,
String.raw`D:\用户\张三\我的 文件.js`,
String.raw`\\servidor\recurso compartido\José\mi archivo.js`,
String.raw`C:\Program Files\my node`,
]) {
for (const quote of ['"', "'"]) {
assert.strictEqual(normalizeFailureReason(`Cannot open ${quote}${filePath}${quote}: permission denied`),
@@ -116,6 +117,9 @@ async function runTests() {
'<path> permission denied in <path>');
assert.strictEqual(normalizeFailureReason('Cannot run C:/tools/node failed to open /tmp/log: permission denied'),
'cannot run <path> failed to open <path>: permission denied');
const denied = normalizeFailureReason(String.raw`C:\Program Files\node permission denied: EACCES`);
assert.strictEqual(denied, '<path> permission denied: eacces');
assert.notStrictEqual(denied, normalizeFailureReason(String.raw`C:\Program Files\node: EACCES`));
})) passed += 1; else failed += 1;
if (await test('groupFailures groups by skillId and normalized reason', async () => {
@@ -138,6 +142,18 @@ async function runTests() {
assert.strictEqual(skillBGroup.runs.length, 1);
})) passed += 1; else failed += 1;
if (await test('keeps distinct diagnostics after extensionless Windows paths separate', async () => {
const runs = [
String.raw`Command C:\tools\node exited with code: 1`,
String.raw`Command D:\tools\node exited with signal: 1`,
String.raw`Command E:\tools\node failed with status: 1`,
].map((failureReason, index) => makeSkillRun({ id: `diagnostic-${index}`, failureReason }));
const patterns = detectPatterns(runs);
assert.strictEqual(generateReport(patterns).status, 'clean');
assert.deepStrictEqual(patterns, []);
assert.strictEqual(groupFailures(runs).size, 3);
})) passed += 1; else failed += 1;
if (await test('groupFailures handles mixed outcome casing', async () => {
const runs = [
makeSkillRun({ id: 'r1', outcome: 'FAILURE', failureReason: 'timeout' }),