7.0 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 122 | Support `?` single-character wildcard in permission patterns |
Support ? single-character wildcard
Problem Statement
OpenCode supports ? as a single-character wildcard (matches exactly one character) in permission patterns, but this extension treats ? as a literal character.
Users porting OpenCode configs that use ? patterns get unexpected behavior silently — the pattern matches nothing instead of acting as a single-char wildcard.
Goals
- Support
?as a single-character wildcard incompileWildcardPattern(matches exactly one character, including path separators). - Add tests covering
?matching (single-char match, no match on zero chars, no match on multiple chars, interaction with*). - Update
docs/configuration.mdto document?wildcard. - Update
docs/opencode-compatibility.mdto move?from divergences to shared concepts.
Non-Goals
- Escaping
?(e.g.,\?to match a literal question mark) — follow-up if needed. - Character classes (
[abc]) or other glob features — out of scope.
Background
Permission surface
This change is surface-agnostic — it affects src/wildcard-matcher.ts, which underlies all permission surfaces (tools, bash, mcp, skills, special, external_directory).
Current implementation
compileWildcardPattern splits the pattern on *, escapes each segment with escapeRegExp (which escapes ? to \?), then joins with .*.
Because ? is inside the escaped segments, it is currently treated as a literal ?.
OpenCode prior art
OpenCode's packages/opencode/src/util/wildcard.ts uses .replace(/\?/g, ".") to convert ? to . in the regex.
Design Overview
The fix is localized to compileWildcardPattern in src/wildcard-matcher.ts.
Strategy: After splitting on * and escaping each part, replace escaped \? with . (match exactly one character) in each segment.
This preserves the existing escape-then-patch approach used for *.
Concretely, after the .map((part) => escapeRegExp(part)) step, chain .map((part) => part.replace(/\\\\?/g, ".")) — but since escapeRegExp produces \? (two chars: backslash + question mark), the replacement target is the literal string \\?.
The regex . already matches any character including path separators because the pattern uses the s (dotAll) flag.
No type changes are needed.
No config schema changes are needed — ? is valid in JSON string values.
JSDoc update
The wildcardMatch docblock should mention ? alongside *:
/**
* Test whether `value` matches `pattern` using wildcard rules.
* `*` matches any sequence of characters (including empty).
* `?` matches exactly one character.
*/
Module-Level Changes
src/wildcard-matcher.ts
- In
compileWildcardPattern, after theescapeRegExpmap, replace\?→.in each segment. - Update the
wildcardMatchJSDoc to mention?.
tests/wildcard-matcher.test.ts
- Add a
describe("? single-character wildcard")block with tests:?matches exactly one character.?does not match zero characters.?does not match two or more characters.- Multiple
?in a pattern (e.g.,f??matchesfoobut notfoorfooo). ?combined with*(e.g.,git?*matchesgit statusbut notgit).?matches path separators and special characters.- Literal
?in a value still matches?pattern (a?wildcard matches any single char, including?itself).
docs/configuration.md
- Add
?to the wildcard documentation (near the*explanation).
docs/opencode-compatibility.md
- Move the
?wildcard row from the "Where They Diverge" table to the "What Transfers Directly" table. - Remove or update any prose that mentions
?as unsupported.
Architecture docs
- No architecture doc changes needed —
docs/architecture/architecture.mddoes not describe wildcard syntax at the character level.
Test Impact Analysis
- New unit tests: The
?wildcard tests are purely additive — they test new behavior that was previously impossible. - No existing tests become redundant — all current
*wildcard tests remain valid and necessary. - No existing tests break —
?was previously escaped as a literal, and no existing test patterns contain?.
TDD Order
-
Red: Add
describe("? single-character wildcard")tests intests/wildcard-matcher.test.ts— all should fail because?is currently a literal. Commit:test: add ? single-character wildcard tests (#122) -
Green: Update
compileWildcardPatterninsrc/wildcard-matcher.tsto replace\?→.after escaping. Update JSDoc. Commit:feat: support ? single-character wildcard in permission patterns (#122) -
Docs: Update
docs/configuration.mdanddocs/opencode-compatibility.md. Commit:docs: document ? wildcard and update OpenCode compatibility (#122)
Risks and Mitigations
| Risk | Mitigation |
|---|---|
| Could this silently weaken a permission? | No — ? is currently escaped to \? (literal). No existing pattern uses ? as a wildcard. The change is purely additive. |
Over-matching: ? matching more than one character |
The regex . matches exactly one character. Tests explicitly verify zero-char and multi-char non-matches. |
| Interaction with trailing wildcard optionality | The ( .*)? suffix only applies to trailing *. A trailing ? is not * and won't trigger this path. A combined pattern like git ?* works correctly — ? becomes ., * becomes .*. |
Breaking existing literal ? in patterns |
Unlikely in practice — ? is not meaningful in tool names, bash commands, or file paths. If a user has a literal ? in a pattern today, it would only match a literal ? in the value — the new behavior matches any single character instead, which is strictly broader. |
Open Questions
- Should
\?be supported as an escape sequence for a literal?? Deferred — OpenCode does not support this either, and the need is unlikely given?does not appear in tool names or common paths.