mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 16:45:22 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
---
|
||||
issue: 123
|
||||
issue_title: "Support trailing wildcard optionality (`command *` matches bare `command`)"
|
||||
---
|
||||
|
||||
# Trailing wildcard optionality
|
||||
|
||||
## Problem Statement
|
||||
|
||||
A pattern like `"git *"` only matches commands with arguments (e.g., `"git status"`), not the bare command `"git"`.
|
||||
Users must write two rules (`"git"` and `"git *"`) to cover both cases.
|
||||
OpenCode treats the trailing `*` as optional, and users naturally read `"git *"` as "git with anything, including nothing."
|
||||
|
||||
## Goals
|
||||
|
||||
- Make `compileWildcardPattern` treat a trailing `*` (space + wildcard) as optional, so `"git *"` also matches bare `"git"`.
|
||||
- Align with OpenCode's behavior for this specific case.
|
||||
- Update docs to reflect the new behavior.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Supporting the `?` single-character wildcard (separate divergence).
|
||||
- Changing non-trailing `*` behavior — `"g*t"` still requires content between `g` and `t`.
|
||||
|
||||
## Background
|
||||
|
||||
The wildcard matcher lives in `src/wildcard-matcher.ts`.
|
||||
`compileWildcardPattern` splits on `*`, escapes each segment, and joins with `.*`.
|
||||
The compiled regex is used across all permission surfaces (bash, tools, MCP, skills, special) via `findCompiledWildcardMatch` and `wildcardMatch`.
|
||||
|
||||
The change is isolated to one function — `compileWildcardPattern` — because all surfaces use it.
|
||||
|
||||
`docs/opencode-compatibility.md` explicitly documents this as a divergence (line 44, line 85–105).
|
||||
`docs/configuration.md` uses `"git *"` in examples that would benefit from the new behavior.
|
||||
|
||||
## Design Overview
|
||||
|
||||
In `compileWildcardPattern`, after building the escaped regex string, check if it ends with `.*`.
|
||||
If so, replace the trailing `.*` with `( .*)?` to make the space-and-arguments portion optional.
|
||||
|
||||
```typescript
|
||||
// After joining escaped segments with ".*":
|
||||
if (escaped.endsWith(" .*")) {
|
||||
escaped = escaped.slice(0, -3) + "( .*)?";
|
||||
}
|
||||
```
|
||||
|
||||
This matches OpenCode's implementation exactly.
|
||||
|
||||
Edge cases:
|
||||
|
||||
- `"git*"` (no space before `*`) — unaffected, still matches `"git"` and `"gitfoo"`.
|
||||
- `"*"` (lone wildcard) — unaffected, the escaped string is `.*` not `.*`.
|
||||
- `" *"` (space-only prefix + wildcard) — escaped is `.*`, becomes `( .*)?`, matching empty string and `anything`.
|
||||
This is an unlikely pattern but harmless.
|
||||
- `"git status *"` — trailing `.*` becomes optional, so matches both `"git status"` and `"git status --short"`.
|
||||
Correct.
|
||||
|
||||
This broadens existing patterns.
|
||||
A user who wrote `"rm *": "deny"` would now also block bare `rm`.
|
||||
This matches user intent and is consistent with the principle that `deny` should err on the side of blocking more.
|
||||
|
||||
## Module-Level Changes
|
||||
|
||||
### `src/wildcard-matcher.ts`
|
||||
|
||||
- In `compileWildcardPattern`: after building the escaped regex string, add the trailing `.*` → `( .*)?` transformation.
|
||||
|
||||
### `tests/wildcard-matcher.test.ts`
|
||||
|
||||
- Add tests for trailing wildcard optionality:
|
||||
1. `"git *"` matches `"git"` (bare command).
|
||||
2. `"git *"` matches `"git status"` (with arguments — existing behavior preserved).
|
||||
3. `"git *"` matches `"git status --short"` (multiple arguments).
|
||||
4. `"git *"` does not match `"npm install"` (different prefix).
|
||||
5. `"git status *"` matches bare `"git status"`.
|
||||
6. Non-trailing `*` is unaffected: `"g*t"` does not match `"g"` or `"t"`.
|
||||
7. `"git*"` (no space) still matches `"git"` — unchanged behavior.
|
||||
8. `"*"` alone still matches everything — unchanged behavior.
|
||||
- Update existing test `"glob pattern matches with wildcard"` assertion for `"git *"` against `"git"` (previously `false`, now `true` — or add a new test alongside).
|
||||
|
||||
### `docs/opencode-compatibility.md`
|
||||
|
||||
- Move "Trailing wildcard optionality" from divergences table to shared concepts.
|
||||
- Remove the workaround section (lines 85–105) or replace it with a note that the behavior now matches.
|
||||
|
||||
### `docs/configuration.md`
|
||||
|
||||
- Remove the need for duplicate `"git"` + `"git *"` rules in examples where trailing wildcard optionality applies.
|
||||
|
||||
## Test Impact Analysis
|
||||
|
||||
1. **New tests enabled**: Direct unit tests for the trailing optionality behavior — straightforward additions to the existing `wildcardMatch` and `findCompiledWildcardMatch` describe blocks.
|
||||
2. **Existing tests that may break**: The test `"glob pattern matches with wildcard"` currently asserts `"git *"` does NOT match bare `"git"` implicitly (the test checks `"git status"` and `"git push origin main"` but not `"git"`).
|
||||
No existing assertion should break since none test `"git *"` against `"git"`.
|
||||
However, review all tests to confirm.
|
||||
3. **Existing tests that stay**: All other wildcard tests (exact match, last-match-wins, regex escaping, home path expansion, multiline) are unaffected.
|
||||
|
||||
## TDD Order
|
||||
|
||||
1. **Red**: Add tests for trailing wildcard optionality (`"git *"` matches `"git"`, `"git status *"` matches `"git status"`, non-trailing `*` unaffected).
|
||||
Commit: `test: add trailing wildcard optionality cases (#123)`
|
||||
|
||||
2. **Green**: Update `compileWildcardPattern` to apply the `( .*)?` transformation.
|
||||
Commit: `feat: support trailing wildcard optionality (#123)`
|
||||
|
||||
3. **Docs**: Update `docs/opencode-compatibility.md` and `docs/configuration.md` to reflect the new behavior.
|
||||
Commit: `docs: update wildcard optionality docs (#123)`
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Broadens existing `deny` patterns (e.g., `"rm *": "deny"` now blocks bare `rm`) | This matches user intent — bare `rm` is also dangerous. Aligns with least-privilege principle. |
|
||||
| Could silently weaken a permission? | No — the change only broadens what a pattern *matches*, not what decision it produces. A `deny` pattern matching more commands is *more* restrictive, not less. An `allow` pattern matching bare commands is what users expect. |
|
||||
| Breaks users who rely on `"cmd *"` NOT matching bare `"cmd"` | Unlikely — the current behavior is unintuitive and requires a workaround. The workaround (`"cmd": "allow"` alongside `"cmd *": "allow"`) still works after the change. |
|
||||
|
||||
## Open Questions
|
||||
|
||||
None — the implementation matches OpenCode's approach exactly and the issue is fully specified.
|
||||
Reference in New Issue
Block a user