Skip to content

feat(permissions): add Goose tool-permissions adapter (permission.yaml)#1957

Open
dyoshikawa wants to merge 2 commits into
mainfrom
resolve-scrap-issue-1922-goose-permissions
Open

feat(permissions): add Goose tool-permissions adapter (permission.yaml)#1957
dyoshikawa wants to merge 2 commits into
mainfrom
resolve-scrap-issue-1922-goose-permissions

Conversation

@dyoshikawa

Copy link
Copy Markdown
Owner

Summary

Adds a permissions target for Block Goose (codename goose), closing the parity gap tracked in #1922. rulesync now generates Goose's per-tool permission overrides under the global ~/.config/goose/permission.yaml.

Closes #1922#1922

Confirmed permission.yaml schema (primary source)

Verified from the Goose source crates/goose/src/config/permission.rs:

#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct PermissionConfig {
    pub always_allow: Vec<String>,
    pub ask_before: Vec<String>,
    pub never_allow: Vec<String>,
}

pub struct PermissionManager {
    config_path: PathBuf,                                  // "permission.yaml"
    permission_map: RwLock<HashMap<String, PermissionConfig>>,
}
const USER_PERMISSION: &str = "user";

So permission.yaml is a YAML map of mode key → PermissionConfig, where each PermissionConfig has three Vec<String> (lists of tool names): always_allow / ask_before / never_allow. User-set decisions live under the top-level user key (other keys such as smart_approve hold the LLM-decision cache).

What changed

  • New adapter src/features/permissions/goose-permissions.ts (GoosePermissions), global-scope only (mirrors the Rovodev adapter). Mapping:
    • Action: allowalways_allow, askask_before, denynever_allow.
    • Tool name: bashdeveloper__shell, edit/writedeveloper__text_editor; any other category passes through verbatim. write collapses onto developer__text_editor, so a conflicting edit/write catch-all is reported and edit wins.
    • Goose lists hold whole tool names, so only a category's catch-all * is representable; non-catch-all patterns are warned and skipped.
    • The user block is owned by rulesync; the smart_approve cache and every other top-level key are preserved; the file is never deleted.
  • Registered goose in permissions-processor.ts (tool-target tuple + factory map, supportsProject: false, supportsGlobal: true, supportsImport: true).
  • Added GOOSE_PERMISSIONS_FILE_NAME to src/constants/goose-paths.ts.
  • gitignore-entries.ts: noted that Goose permissions are global-only (no project-level entry); pnpm dev gitignore confirms no .gitignore change.
  • Docs: README + docs/reference/supported-tools.md matrix (Goose permissions → 🌏), a Goose paragraph in docs/reference/file-formats.md, and refreshed the stale permissions row in .rulesync/skills/rulesync-feature-research/references/goose.md. docs/skills/rulesync/ kept in sync.
  • Tests: unit tests (goose-permissions.test.ts) covering round-trip allow/ask/deny ↔ always_allow/ask_before/never_allow, global path, global-only enforcement, non-catch-all skip, edit/write conflict, and non-destructive merge; updated permissions-processor.test.ts global-targets expectation; added a global-mode e2e case in e2e-permissions.spec.ts.

Verification

pnpm cicheck passes (code + content): fmt, oxlint, typecheck, 6813 unit tests, sync-skill-docs, cspell, secretlint. The full e2e-permissions.spec.ts (42 tests) passes including the new Goose global case.

🤖 Generated with Claude Code

Block Goose persists per-tool permission overrides in the global
~/.config/goose/permission.yaml, a YAML map of mode key -> PermissionConfig
{ always_allow, ask_before, never_allow } where each field is a list of tool
names. rulesync writes user-set decisions under the `user` key.

- Add GoosePermissions adapter (global-scope only; mirrors the Rovodev adapter)
  mapping canonical allow/ask/deny onto always_allow/ask_before/never_allow.
  Tool-name mapping: bash -> developer__shell, edit/write ->
  developer__text_editor; other categories pass through verbatim. Goose lists
  hold whole tool names, so only a category's catch-all `*` is representable;
  non-catch-all patterns are warned and skipped. The smart_approve LLM cache
  and other top-level keys are preserved; the file is never deleted.
- Register `goose` in permissions-processor (tool target tuple + factory map,
  supportsProject: false, supportsGlobal: true, supportsImport: true).
- Add GOOSE_PERMISSIONS_FILE_NAME constant.
- Update README and docs Tool x Feature matrix, file-formats.md, and the
  rulesync-feature-research goose.md reference; keep docs/<->skills/ in sync.
- Add unit tests (round-trip allow/ask/deny, global-only enforcement, merge)
  and a global-mode e2e permissions case.

Closes #1922
Review nit: the file-formats note said the conflicting edit/write catch-all
resolves with 'the later value wins', implying source-order dependence, but the
implementation is deterministic — edit always takes precedence. Match the
adjacent Rovo Dev wording ('edit takes precedence').
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.

Follow up Goose upstream updates: tool permissions (GOOSE_MODE / permission.yaml)

2 participants