Skip to content

feat(tui): learn Always-allow answers into the project policy file - #1

Draft
wesleymatosdev wants to merge 1 commit into
feat/auto-permission-classifierfrom
feat/auto-policy-learning
Draft

feat(tui): learn Always-allow answers into the project policy file#1
wesleymatosdev wants to merge 1 commit into
feat/auto-permission-classifierfrom
feat/auto-policy-learning

Conversation

@wesleymatosdev

Copy link
Copy Markdown
Owner

feat(tui): learn "Always allow" answers into the project policy file

Stacked on kingsword09#137 (auto permission classifier) — merge that first.

What it adds

The base PR's classifier answers prompts from a static policy. This PR closes the loop with claude-code-style learning: when the human dialog's "Always allow in this project" is chosen, the client mirrors that answer into a project-local policy overlay, so the classifier learns it for every later prompt — in this session and future sessions in the same project.

How it works

  • The dialog's always-allow choice already returns the runtime's addRules payload ({ behavior: "allow", type: "addRules", rules: [{ toolName, ruleContent }] } — see defaultPermissionChoices). After the response is sent, extractLearningRule() pulls the rule out.
  • appendLearnedRule() appends it to <workspace>/.zcode/auto-permissions.json — deliberately a sparse overlay: only learned rules are stored, never a flattened copy of the built-ins, so the file stays small, diffable, and committable.
  • Loading is layered: built-ins ← learned file ← ZCODE_AUTO_PERMISSIONS_CONFIG (explicit user override wins). The file is re-read on every permission request, so no restart is needed for learned rules to take effect.
  • Existing defaults.unmatched values in the file are preserved; identical rules are deduplicated; a deny-learning would land in softDeny as an escaped regex, never as an allow.
  • Learning is best-effort: any write failure logs a notice and never disturbs the permission flow.

Testing

  • test/auto-permissions-learn.test.ts (7): payload extraction (incl. non-addRules / empty / null responses), project-local path, file creation, merge-over-duplicates, preservation of custom rules and defaults.unmatched, deny-never-becomes-allow.
  • Full targeted suite 24/24; bun run typecheck clean; biome check clean.
  • Live proof over a PTY (isolated HOME, real runtime): an "Always allow" answer on third.txt produced {"tool":"Write","note":"learned from dialog","pathPrefix":"…/probe2/third.txt"} in the probe project's .zcode/auto-permissions.json; a fresh session then auto-answered a later Write to the same path in 14s with no dialog, while an unrelated path (fourth.txt) still rendered the human dialog.

Known edges (documented behavior, not bugs)

  • Learned rules match the ruleContent literally (pathPrefix), same scoping as the runtime's own project rules: a learned rule for third.txt does not cover fourth.txt.
  • macOS path aliases (/tmp vs /private/tmp) are distinct strings and match distinctly — consistent with literal prefix semantics.

Mirrors the dialog's 'Always allow in this project' response (the
runtime's addRules payload) into a project-local policy overlay at
<workspace>/.zcode/auto-permissions.json. Learned rules are sparse
(only learnings are stored), merged over the built-ins at load time,
and re-read on every prompt, so a dialog answer teaches the classifier
for the rest of the session and future sessions in the same project.

Layering: built-ins <- learned file <- ZCODE_AUTO_PERMISSIONS_CONFIG.
The learned file preserves an existing defaults.unmatched value and
deduplicates identical rules. Learnings are best-effort: failures log
a notice and never disturb the permission flow.

Live proof over a PTY (isolated HOME): an 'Always allow' answer on
third.txt produced the learned rule in the probe project, and a fresh
session auto-answered a later Write to the same path in 14s with no
dialog (covered by the learned rule), while an unrelated path still
rendered the human dialog.
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