Skip to content

v0.1.0-alpha.79

Choose a tag to compare

@github-actions github-actions released this 03 May 08:54
· 207 commits to main since this release
feat: 3-tier permission rules (allow/deny + glob match) (alpha.79)

Pattern from Claude Code's three-tier permission storage. Adds an
allow/deny ruleset layered ON TOP of the existing tri-state mode
(YOLO/Smart/Strict) — rules win over mode in both directions:
- A deny rule blocks even when YOLO would auto-approve.
- An allow rule grants silent passage even when Strict would prompt.

NEW: src/lib/permission-rules.ts

Storage tiers (deny ALWAYS wins across tiers; allow merges):
  1. <alias>/permissions.json        — project rules (committed)
  2. <alias>/permissions.local.json   — local overrides (gitignore)
  3. ~/.qlaud/permissions.json        — user-tier (cross-project)
  4. ~/.claude/permissions.json       — Claude Code compat

Rule format matches Claude Code's wire form:
  "Bash"                      — applies to ALL bash invocations
  "Bash(npm:*)"               — content glob: starts with "npm "
  "Bash(pnpm install)"        — exact-match content
  "WebFetch(domain:gh.com)"   — domain shorthand for URLs
  "write_file(src/**)"        — path glob

WIRED at three call sites (write_file, edit_file, bash):
1. evaluateRule() runs BEFORE mode-based check.
2. Deny → return error to model with "Blocked by rule X" + path.
3. Allow → skip approval entirely (silent run).
4. No-match → fall through to YOLO/Smart/Strict semantics.

Module-scoped LRU cache; cleared on workspace switch via
clearPermissionRulesCache() so a new workspace doesn't inherit
stale rules from the previous one. The cache is per-workspace path
so multiple workspaces would each get fresh rules anyway, but
clearing on switch is belt-and-suspenders.

Wire form CLI users can adopt today by hand-writing the JSON. UI
to edit rules in Settings comes in a follow-up alpha (the format
is stable now, so users who drop a rules file don't have to migrate
when the UI lands).

Examples that just work:
  // .qcode/permissions.json
  {
    "deny": ["read_file(.env*)", "Bash(rm -rf:*)"],
    "allow": ["Bash(pnpm test)", "Bash(pnpm typecheck)"]
  }

The first two block secrets reads + rm-rf entirely; the second two
let pnpm-test/typecheck run without prompts even on Strict mode.