Skip to content

fix(bggrep): accept a leading (?i) flag group and explain the others - #24

Merged
lloydsk merged 3 commits into
mainfrom
fix/bggrep-inline-flags
Sep 26, 2026
Merged

lloydsk merged 3 commits into
mainfrom
fix/bggrep-inline-flags

Conversation

@lloydsk

@lloydsk lloydsk commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

What

Agents write PCRE habits into pattern, and JavaScript's RegExp has no unscoped inline flag syntax — flags are the second argument — so (?i)error is a syntax error. That cost a real tool call in practice: a search with (?i)\(fail\)|error:|… came back as Invalid regular expression: Invalid group and had to be worked around by hand. The tool also offered no way to request case-insensitivity at all, which is why its default pattern hand-writes both Error: and error:.

Change

  • A leading (?i), (?m) or (?s) group is translated into the equivalent JS flags, so (?i)error searches case-insensitively.
  • Only the leading position is translated. PCRE scopes a mid-pattern (?i) to the rest of the pattern, so stripping it there would silently widen the match instead of honoring it. Those still fail — now with an explanation.
  • Flags are threaded through all three compile sites — worker, sync fallback, up-front validation — so they cannot disagree. The worker receives them via workerData; matchLinesWithBudget's trailing workerSource parameter became opts: { workerSource?, flags? }.
  • Messages and details.pattern echo the pattern as the caller wrote it, flags included, while matching uses the translated source.

Not attempted

General PCRE compatibility. \K, atomic groups, possessive quantifiers and conditionals have no faithful JS translation, so accepting them would quietly change what a search means.

Follow-up from review (third commit)

Two gaps and one inaccuracy, all found by review:

  • The hint over-suppressed. inlineFlagHint returned "" whenever the raw pattern started with a flag group — so (?is)a(?m)b (a leading group we honour plus a second group that is the real cause) produced a bare engine error with no explanation. That is the only case the hint exists for. It is now keyed on the pattern after the leading group is stripped, which keeps every earlier assertion while restoring the hint for (?is)a(?m)b and (?i)(?i)b — both now pinned by tests.
  • The sync fallback's flags parameter had no test. Only the worker is reachable from the tool in this suite (worker_threads exists on Node and Bun), so a regression dropping the argument at either fallback call site would have shipped silently — precisely the bug class this branch fixes. The parity test now asserts both paths honour i, and that the flagless calls miss.
  • The wording was wrong. Node 24 and Bun 1.3.6 do support scoped modifier groups ((?i:…), ES2025), which scope exactly as PCRE does — so "JS RegExp has no inline flags" was false. Every surface now says "no unscoped inline flags" and notes that a scoped group is native anywhere. That change also required updating two assertions that matched the old phrasing.

Verification

  • tsc --noEmit clean.
  • 227 tests pass: case-insensitive matching via (?i), the mid-pattern explanation, the translation table, the fallback/worker flag parity, and details.pattern.
  • A live pi session, running the exact call that failed in the wild:
bggrep(pattern: "(?i)mixed_case_line")
   → 1 match for /(?i)mixed_case_line/ in 4 lines
     L2: MIXED_case_LINE

bggrep(pattern: "line(?i)more")
   → bggrep: invalid pattern "line(?i)more": Invalid regular expression:
     /line(?i)more/: Invalid group — JavaScript regexes have no unscoped inline flags …

Docs

The README bggrep row and the skills/run-bg Grep row state the accepted form, that a scoped group is native, and that no unscoped inline-flag syntax exists.

Callers write PCRE habits into `pattern`, and JavaScript's RegExp has no inline
flag syntax at all — flags are a second argument — so `(?i)error` is a syntax
error. That cost a real tool call in practice: a search with
`(?i)\(fail\)|error:|…` came back as `Invalid regular expression: Invalid group`
and had to be worked around by hand. The tool also offered no way to ask for
case-insensitivity, which is why its default pattern hand-writes both `Error:`
and `error:`.

- `splitInlineFlags()` translates a LEADING `(?i)`, `(?m)` or `(?s)` group into
  the equivalent JS flags, so `(?i)error` searches case-insensitively.
- Only the leading position is translated. PCRE scopes a mid-pattern `(?i)` to
  the REST of the pattern, so stripping one there would silently WIDEN the match
  instead of honoring it; those still fail, now with `inlineFlagHint()`
  explaining that JS has no inline flags and naming the alternatives.
- Flags are threaded through all three compile sites — the worker, the sync
  fallback and the up-front validation — so they cannot disagree. The worker
  takes them via `workerData`, and `matchLinesWithBudget`'s trailing
  `workerSource` parameter became `opts: { workerSource?, flags? }`.
- Every message and `details.pattern` now echoes the pattern as the caller wrote
  it, flags included, while matching uses the translated source.
- While in that function: the two inline `import("node:worker_threads")` types
  became a top-level `import type` (erased at compile time, so the runtime
  import — dynamic precisely because this fallback exists for runtimes without
  worker_threads — stays dynamic, with a comment naming why).

Not attempted: general PCRE compatibility. Constructs like `\K`, atomic groups,
possessive quantifiers and conditionals have no faithful JS translation, so
accepting them would quietly change what a search means.

Verified: `tsc --noEmit` clean; 227 tests pass (224 existing plus case-insensitive
matching, the mid-pattern explanation, and the translation table); and a live pi
session calling bggrep with `(?i)mixed_case_line` returned `L2: MIXED_case_LINE`,
while `line(?i)more` came back with the hint instead of a bare engine message.

README and the run-bg skill document the accepted form.
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

CI report

Check Result
tsc --noEmit success
tests success
npm pack --dry-run success (7 files in tarball)

Ref: 5692bfabb042708db3386fa7f84471b7a08b0136

…x the wording

Review of the first commits found two gaps and one inaccuracy.

- `inlineFlagHint` returned "" whenever the RAW pattern started with a flag group,
  so `(?is)a(?m)b` — a leading group we honour plus a SECOND group that is the
  actual cause — reported a bare engine error with no explanation. That is the only
  case the hint exists for. It is now keyed on the pattern AFTER the leading group
  is stripped, which keeps every previous assertion (a leading group we honoured is
  still not the reason for a later failure) while restoring the hint for
  `(?is)a(?m)b` and `(?i)(?i)b`.
- The sync fallback's `flags` parameter was exercised by no test: only the worker is
  reachable from the tool in this suite (worker_threads exists on Node and Bun), so
  a regression dropping the argument at either fallback call site would have shipped
  silently — the exact bug class this branch fixes. The parity test now asserts both
  paths honour "i" and that the flagless calls miss, so the assertion tests the flag
  rather than a pattern that always hits.
- `details.pattern` is asserted to echo the caller's flagged pattern.
- Wording: Node 24 and Bun 1.3.6 DO support scoped modifier groups (`(?i:…)`,
  ES2025), which scope exactly as PCRE does — so "JS RegExp has no inline flags" was
  false. The hint, the tool description, the README row and the skill row now say
  "no unscoped inline flags" and note that a scoped group is native anywhere. (This
  also meant updating the two assertions that matched the old phrasing.)

Verified: `tsc --noEmit` clean; 227 tests pass, including the three added above.
# Conflicts:
#	README.md
#	skills/run-bg/SKILL.md
@lloydsk
lloydsk force-pushed the fix/bggrep-inline-flags branch from 5c1a77c to 5692bfa Compare September 26, 2026 02:23
@lloydsk
lloydsk merged commit 5f2fe09 into main Sep 26, 2026
2 checks passed
@lloydsk
lloydsk deleted the fix/bggrep-inline-flags branch September 26, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant