Three parser bugs, all of which end with the user looking at the wrong thing or at nothing. Each is small and independently testable in src/stParser.ts.
1. Truncated blocks are emitted, and the spec says they must not
FORMAT.md is explicit: a block whose closing line is absent is incomplete, and parsers must discard it rather than emit a partial entry.
We do the opposite. The inner loop breaks on the next START — the comment at line 49 literally says "next block began — this one was truncated" — and then line 58 calls parseBlock(block) unconditionally, with no check that the block ended in END.
Why it is visible: the writer emits the header, the session marker and the block as separate writes, and a multi-KB block is not an atomic append. Our watcher fires on that write, so the top row of the list becomes a report with a headline, no culprit (the at ... line has not landed yet), no stack and no story. With no culprit the item gets no command (extension.ts:91-97), so clicking it does nothing, and Copy-for-AI hands the assistant half a report. It heals on the next refresh, which makes it look random rather than reproducible.
Fix: only accept a block whose last line starts with END. Keep the trailing partial in a pending field and re-evaluate it on the next refresh, so a genuinely killed process drops the tail instead of flickering it.
2. A frame with no line number navigates to the wrong file
The frame regex requires \d+:
const FRAME = /\(([\w$]+\.(?:java|kt|groovy|scala)):(\d+)\)/;
But the library writes getLineNumber() verbatim (StackDistiller.java:203), and StackTraceElement.getLineNumber() returns -1 when the class was compiled without -g:lines and -2 for a native method. So a line like at OrderService.confirm(OrderService.java:-1) <- YOUR CODE is legal, routinely produced, and matches nothing.
The marked culprit then yields no frame, culprit stays undefined, and we fall back to frames[0] — which for a wrapped exception is the frame on the wrapped by: line. The user clicks an IllegalStateException attributed to OrderService and silently lands in CheckoutService.java:88.
Fix: allow -?\d+; treat a line number <= 0 as found but not navigable; only fall back to frames[0] when the block contained no marked frame at all.
3. Non-ASCII source file names are invisible
[\w$]+ is ASCII-only in JS without the u flag. Names like Acao.java, Groesse.java or a CJK class name are all legal Java source names — Java identifiers accept Unicode letters — so those frames match nothing and the report ends up with no culprit at all.
Fix: [\p{L}\p{N}_$]+ with the u flag, or simply [^\s:()]+\.\w+. Add .kts to the extension list while you are there.
Verify
A test in src/test/parser.test.ts per case: a truncated block is dropped; a -1 line number is parsed but marked non-navigable and does not steal the culprit; a non-ASCII file name resolves. npm test green.
The same three bugs exist in the JetBrains plugin — the parsers are deliberate twins, so please keep the fixes in step.
Three parser bugs, all of which end with the user looking at the wrong thing or at nothing. Each is small and independently testable in
src/stParser.ts.1. Truncated blocks are emitted, and the spec says they must not
FORMAT.md is explicit: a block whose closing line is absent is incomplete, and parsers must discard it rather than emit a partial entry.
We do the opposite. The inner loop breaks on the next
START— the comment at line 49 literally says "next block began — this one was truncated" — and then line 58 callsparseBlock(block)unconditionally, with no check that the block ended inEND.Why it is visible: the writer emits the header, the session marker and the block as separate writes, and a multi-KB block is not an atomic append. Our watcher fires on that write, so the top row of the list becomes a report with a headline, no culprit (the
at ...line has not landed yet), no stack and no story. With no culprit the item gets nocommand(extension.ts:91-97), so clicking it does nothing, and Copy-for-AI hands the assistant half a report. It heals on the next refresh, which makes it look random rather than reproducible.Fix: only accept a block whose last line starts with
END. Keep the trailing partial in apendingfield and re-evaluate it on the next refresh, so a genuinely killed process drops the tail instead of flickering it.2. A frame with no line number navigates to the wrong file
The frame regex requires
\d+:But the library writes
getLineNumber()verbatim (StackDistiller.java:203), andStackTraceElement.getLineNumber()returns -1 when the class was compiled without-g:linesand -2 for a native method. So a line likeat OrderService.confirm(OrderService.java:-1) <- YOUR CODEis legal, routinely produced, and matches nothing.The marked culprit then yields no frame,
culpritstays undefined, and we fall back toframes[0]— which for a wrapped exception is the frame on thewrapped by:line. The user clicks an IllegalStateException attributed toOrderServiceand silently lands inCheckoutService.java:88.Fix: allow
-?\d+; treat a line number<= 0as found but not navigable; only fall back toframes[0]when the block contained no marked frame at all.3. Non-ASCII source file names are invisible
[\w$]+is ASCII-only in JS without theuflag. Names likeAcao.java,Groesse.javaor a CJK class name are all legal Java source names — Java identifiers accept Unicode letters — so those frames match nothing and the report ends up with no culprit at all.Fix:
[\p{L}\p{N}_$]+with theuflag, or simply[^\s:()]+\.\w+. Add.ktsto the extension list while you are there.Verify
A test in
src/test/parser.test.tsper case: a truncated block is dropped; a-1line number is parsed but marked non-navigable and does not steal the culprit; a non-ASCII file name resolves.npm testgreen.The same three bugs exist in the JetBrains plugin — the parsers are deliberate twins, so please keep the fixes in step.