Describe the bug
Same family of bug as the companion start-boundary issue, opposite edge. completeMatchRegex.exec() runs against remainingHaystack (search-strategy.ts:67-68)— a slice that simply runs out wherever the current chunk happens to end.
Native $ (no m) and \b/\B at the tail evaluate "is this the end of input" relative to the slice's end, not the true stream's end. The strategy cannot know, mid-stream, whether more chunks are coming — but the regex engine, given only the truncated slice, confidently treats "no more characters in this string" as "true end of input."
Unlike the start-boundary case, this side is not decidable from information already in hand — the correctness of $/\b/\B here depends on the next character, which by definition hasn't arrived yet when a chunk boundary lands mid-pattern. Resolving it correctly needs either (a) the actual next character, or (b) confirmation the stream has truly ended — and (b) only happens at flush(), which today just returns the leftover buffer as raw text and never re-runs completeMatchRegex (see "Additional context" below).
Currently undocumented — README lists boundary assertions as fully "✅ Supported."
To Reproduce
import { RegexSearchStrategy } from "./src/search-strategies/regex/search-strategy.ts";
const strategy = new RegexSearchStrategy(/foo\b/);
const state = strategy.createState();
for (const chunk of ["foo", "bar"]) {
for (const r of strategy.processChunk(chunk, state)) console.log(r);
}
Actual output:
{ isMatch: true, content: [ "foo" ] } // <-- WRONG
{ isMatch: false, content: "bar" }
Same defect reproduces for /foo$/ (no m) over ["foo", "bar"] — $ spuriously succeeds at the end of chunk 1 because the slice "foo" happens to run out there, not because the stream has actually ended.
Expected behaviour
No match. True (unchunked) string is "foobar"; there is no word boundary between "foo" and "bar" (both word characters), so /foo\b/ does not match anywhere in it.
Additional context
- Why this one is harder to fix than the companion start-boundary bug: a correct fix requires deferring the match until either more input arrives (to re-resolve with a real next character) or the stream is confirmed finished. The latter only happens at
flush() — but SearchStrategy.flush(state): string (types.ts) is currently a fixed part of the strategy interface, always treated as raw leftover text by every call site (transform-engine-base.ts:40,47, engine.ts:212,248), never re-run through completeMatchRegex. Deferring these matches in processChunk without also teaching flush() to resolve them would trade today's false positives for new false negatives (a genuinely final /foo$/ match would buffer forever and get dumped as unmatched text). A full fix therefore touches the SearchStrategy interface contract, not just RegexSearchStrategy.
- This is architecturally the same shape as the already-documented "Unbounded Quantifiers" limitation in the README (a greedy quantifier is satisfied eagerly at whatever the chunk boundary happens to be) — except quantifiers are a documented, accepted trade-off, and
$/\b/\B currently are not.
Describe the bug
Same family of bug as the companion start-boundary issue, opposite edge.
completeMatchRegex.exec()runs againstremainingHaystack(search-strategy.ts:67-68)— a slice that simply runs out wherever the current chunk happens to end.Native
$(nom) and\b/\Bat the tail evaluate "is this the end of input" relative to the slice's end, not the true stream's end. The strategy cannot know, mid-stream, whether more chunks are coming — but the regex engine, given only the truncated slice, confidently treats "no more characters in this string" as "true end of input."Unlike the start-boundary case, this side is not decidable from information already in hand — the correctness of
$/\b/\Bhere depends on the next character, which by definition hasn't arrived yet when a chunk boundary lands mid-pattern. Resolving it correctly needs either (a) the actual next character, or (b) confirmation the stream has truly ended — and (b) only happens atflush(), which today just returns the leftover buffer as raw text and never re-runscompleteMatchRegex(see "Additional context" below).Currently undocumented — README lists boundary assertions as fully "✅ Supported."
To Reproduce
Actual output:
Same defect reproduces for
/foo$/(nom) over["foo", "bar"]—$spuriously succeeds at the end of chunk 1 because the slice"foo"happens to run out there, not because the stream has actually ended.Expected behaviour
No match. True (unchunked) string is
"foobar"; there is no word boundary between"foo"and"bar"(both word characters), so/foo\b/does not match anywhere in it.Additional context
flush()— butSearchStrategy.flush(state): string(types.ts) is currently a fixed part of the strategy interface, always treated as raw leftover text by every call site (transform-engine-base.ts:40,47, engine.ts:212,248), never re-run throughcompleteMatchRegex. Deferring these matches inprocessChunkwithout also teachingflush()to resolve them would trade today's false positives for new false negatives (a genuinely final/foo$/match would buffer forever and get dumped as unmatched text). A full fix therefore touches theSearchStrategyinterface contract, not justRegexSearchStrategy.$/\b/\Bcurrently are not.