diff --git a/.opencode/agent/git-committer.md b/.opencode/agent/git-committer.md deleted file mode 100644 index 49c3e3de1..000000000 --- a/.opencode/agent/git-committer.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -description: Use this agent when you are asked to commit and push code changes to a git repository. -mode: subagent ---- - -You commit and push to git - -Commit messages should be brief since they are used to generate release notes. - -Messages should say WHY the change was made and not WHAT was changed. diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index a4fe2ae2e..33dd00b0e 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -5,9 +5,7 @@ // "url": "https://enterprise.dev.opencode.ai", // }, "instructions": ["STYLE_GUIDE.md"], - "permission": { - "*": "ask", - }, + // "permission": "deny", "provider": { "opencode": { "options": {}, diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts index 60dd9cc75..b57de0ae4 100644 --- a/packages/opencode/src/cli/cmd/agent.ts +++ b/packages/opencode/src/cli/cmd/agent.ts @@ -241,7 +241,8 @@ const AgentListCommand = cmd({ }) for (const agent of sortedAgents) { - process.stdout.write(`${agent.name} (${agent.mode})${EOL}`) + process.stdout.write(`${agent.name} (${agent.mode})` + EOL) + process.stdout.write(` ${JSON.stringify(agent.permission, null, 2)}` + EOL) } }, }) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 16f8d14d4..476eb8098 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -410,6 +410,8 @@ export namespace Config { doom_loop: PermissionAction.optional(), }) .catchall(PermissionRule) + .or(PermissionAction) + .transform((x) => (typeof x === "string" ? { "*": x } : x)) .meta({ ref: "PermissionConfig", }) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 93fea3b1e..4ca1b4417 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -6,7 +6,6 @@ import { Instance } from "@/project/instance" import { fn } from "@/util/fn" import { Log } from "@/util/log" import { Wildcard } from "@/util/wildcard" -import { sortBy } from "remeda" import z from "zod" export namespace PermissionNext { @@ -19,6 +18,7 @@ export namespace PermissionNext { export const Rule = z .object({ + permission: z.string(), pattern: z.string(), action: Action, }) @@ -27,40 +27,29 @@ export namespace PermissionNext { }) export type Rule = z.infer - export const Ruleset = z.record(z.string(), Rule.array()).meta({ + export const Ruleset = Rule.array().meta({ ref: "PermissionRuleset", }) export type Ruleset = z.infer export function fromConfig(permission: Config.Permission) { - const ruleset: Ruleset = {} + const ruleset: Ruleset = [] for (const [key, value] of Object.entries(permission)) { if (typeof value === "string") { - ruleset[key] = [ - { - action: value, - pattern: "*", - }, - ] + ruleset.push({ + permission: key, + action: value, + pattern: "*", + }) continue } - ruleset[key] = Object.entries(value).map(([pattern, action]) => ({ pattern, action })) + ruleset.push(...Object.entries(value).map(([pattern, action]) => ({ permission: key, pattern, action }))) } return ruleset } export function merge(...rulesets: Ruleset[]): Ruleset { - const result: Ruleset = {} - for (const ruleset of rulesets) { - for (const [permission, rules] of Object.entries(ruleset)) { - if (!result[permission]) { - result[permission] = rules - continue - } - result[permission] = result[permission].concat(rules) - } - } - return result as Ruleset + return rulesets.flat() } export const Request = z @@ -205,20 +194,15 @@ export namespace PermissionNext { export function evaluate(permission: string, pattern: string, ruleset: Ruleset): Action { log.info("evaluate", { permission, pattern, ruleset }) - const rules: Rule[] = [] - const entries = sortBy(Object.entries(ruleset), ([k]) => k.length) - for (const [permPattern, permRules] of entries) { - if (Wildcard.match(permission, permPattern)) { - rules.push(...permRules) - } - } - const match = rules.findLast((rule) => Wildcard.match(pattern, rule.pattern)) + const match = ruleset.findLast( + (rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern), + ) return match?.action ?? "ask" } const EDIT_TOOLS = ["edit", "write", "patch", "multiedit"] - export function disabledTools(tools: string[], ruleset: Ruleset): Set { + export function disabled(tools: string[], ruleset: Ruleset): Set { const disabled = new Set() for (const tool of tools) { const permission = EDIT_TOOLS.includes(tool) ? "edit" : tool diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 2f02516ea..39e05210f 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -188,7 +188,7 @@ export namespace LLM { } async function resolveTools(input: Pick) { - const disabled = PermissionNext.disabledTools(Object.keys(input.tools), input.agent.permission) + const disabled = PermissionNext.disabled(Object.keys(input.tools), input.agent.permission) for (const tool of Object.keys(input.tools)) { if (input.user.tools?.[tool] === false || disabled.has(tool)) { delete input.tools[tool] diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index e624da08b..11d29fe9a 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -5,17 +5,15 @@ import { PermissionNext } from "../../src/permission/next" test("fromConfig - string value becomes wildcard rule", () => { const result = PermissionNext.fromConfig({ bash: "allow" }) - expect(result).toEqual({ bash: [{ pattern: "*", action: "allow" }] }) + expect(result).toEqual([{ permission: "bash", pattern: "*", action: "allow" }]) }) test("fromConfig - object value converts to rules array", () => { const result = PermissionNext.fromConfig({ bash: { "*": "allow", rm: "deny" } }) - expect(result).toEqual({ - bash: [ - { pattern: "*", action: "allow" }, - { pattern: "rm", action: "deny" }, - ], - }) + expect(result).toEqual([ + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "rm", action: "deny" }, + ]) }) test("fromConfig - mixed string and object values", () => { @@ -24,310 +22,309 @@ test("fromConfig - mixed string and object values", () => { edit: "allow", webfetch: "ask", }) - expect(result).toEqual({ - bash: [ - { pattern: "*", action: "allow" }, - { pattern: "rm", action: "deny" }, - ], - edit: [{ pattern: "*", action: "allow" }], - webfetch: [{ pattern: "*", action: "ask" }], - }) + expect(result).toEqual([ + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "rm", action: "deny" }, + { permission: "edit", pattern: "*", action: "allow" }, + { permission: "webfetch", pattern: "*", action: "ask" }, + ]) }) test("fromConfig - empty object", () => { const result = PermissionNext.fromConfig({}) - expect(result).toEqual({}) + expect(result).toEqual([]) }) // merge tests test("merge - simple concatenation", () => { const result = PermissionNext.merge( - { bash: [{ pattern: "*", action: "allow" }] }, - { bash: [{ pattern: "*", action: "deny" }] }, + [{ permission: "bash", pattern: "*", action: "allow" }], + [{ permission: "bash", pattern: "*", action: "deny" }], ) - expect(result).toEqual({ - bash: [ - { pattern: "*", action: "allow" }, - { pattern: "*", action: "deny" }, - ], - }) + expect(result).toEqual([ + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "*", action: "deny" }, + ]) }) test("merge - adds new permission", () => { const result = PermissionNext.merge( - { bash: [{ pattern: "*", action: "allow" }] }, - { edit: [{ pattern: "*", action: "deny" }] }, + [{ permission: "bash", pattern: "*", action: "allow" }], + [{ permission: "edit", pattern: "*", action: "deny" }], ) - expect(result).toEqual({ - bash: [{ pattern: "*", action: "allow" }], - edit: [{ pattern: "*", action: "deny" }], - }) + expect(result).toEqual([ + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "edit", pattern: "*", action: "deny" }, + ]) }) test("merge - concatenates rules for same permission", () => { const result = PermissionNext.merge( - { bash: [{ pattern: "foo", action: "ask" }] }, - { bash: [{ pattern: "*", action: "deny" }] }, + [{ permission: "bash", pattern: "foo", action: "ask" }], + [{ permission: "bash", pattern: "*", action: "deny" }], ) - expect(result).toEqual({ - bash: [ - { pattern: "foo", action: "ask" }, - { pattern: "*", action: "deny" }, - ], - }) + expect(result).toEqual([ + { permission: "bash", pattern: "foo", action: "ask" }, + { permission: "bash", pattern: "*", action: "deny" }, + ]) }) test("merge - multiple rulesets", () => { const result = PermissionNext.merge( - { bash: [{ pattern: "*", action: "allow" }] }, - { bash: [{ pattern: "rm", action: "ask" }] }, - { edit: [{ pattern: "*", action: "allow" }] }, + [{ permission: "bash", pattern: "*", action: "allow" }], + [{ permission: "bash", pattern: "rm", action: "ask" }], + [{ permission: "edit", pattern: "*", action: "allow" }], ) - expect(result).toEqual({ - bash: [ - { pattern: "*", action: "allow" }, - { pattern: "rm", action: "ask" }, - ], - edit: [{ pattern: "*", action: "allow" }], - }) + expect(result).toEqual([ + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "rm", action: "ask" }, + { permission: "edit", pattern: "*", action: "allow" }, + ]) }) test("merge - empty ruleset does nothing", () => { - const result = PermissionNext.merge({ bash: [{ pattern: "*", action: "allow" }] }, {}) - expect(result).toEqual({ bash: [{ pattern: "*", action: "allow" }] }) + const result = PermissionNext.merge([{ permission: "bash", pattern: "*", action: "allow" }], []) + expect(result).toEqual([{ permission: "bash", pattern: "*", action: "allow" }]) }) test("merge - preserves rule order", () => { const result = PermissionNext.merge( - { - edit: [ - { pattern: "src/*", action: "allow" }, - { pattern: "src/secret/*", action: "deny" }, - ], - }, - { edit: [{ pattern: "src/secret/ok.ts", action: "allow" }] }, - ) - expect(result).toEqual({ - edit: [ - { pattern: "src/*", action: "allow" }, - { pattern: "src/secret/*", action: "deny" }, - { pattern: "src/secret/ok.ts", action: "allow" }, + [ + { permission: "edit", pattern: "src/*", action: "allow" }, + { permission: "edit", pattern: "src/secret/*", action: "deny" }, ], - }) + [{ permission: "edit", pattern: "src/secret/ok.ts", action: "allow" }], + ) + expect(result).toEqual([ + { permission: "edit", pattern: "src/*", action: "allow" }, + { permission: "edit", pattern: "src/secret/*", action: "deny" }, + { permission: "edit", pattern: "src/secret/ok.ts", action: "allow" }, + ]) +}) + +test("merge - config permission overrides default ask", () => { + // Simulates: defaults have "*": "ask", config sets bash: "allow" + const defaults: PermissionNext.Ruleset = [{ permission: "*", pattern: "*", action: "ask" }] + const config: PermissionNext.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }] + const merged = PermissionNext.merge(defaults, config) + + // Config's bash allow should override default ask + expect(PermissionNext.evaluate("bash", "ls", merged)).toBe("allow") + // Other permissions should still be ask (from defaults) + expect(PermissionNext.evaluate("edit", "foo.ts", merged)).toBe("ask") +}) + +test("merge - config ask overrides default allow", () => { + // Simulates: defaults have bash: "allow", config sets bash: "ask" + const defaults: PermissionNext.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }] + const config: PermissionNext.Ruleset = [{ permission: "bash", pattern: "*", action: "ask" }] + const merged = PermissionNext.merge(defaults, config) + + // Config's ask should override default allow + expect(PermissionNext.evaluate("bash", "ls", merged)).toBe("ask") }) // evaluate tests test("evaluate - exact pattern match", () => { - const result = PermissionNext.evaluate("bash", "rm", { bash: [{ pattern: "rm", action: "deny" }] }) + const result = PermissionNext.evaluate("bash", "rm", [{ permission: "bash", pattern: "rm", action: "deny" }]) expect(result).toBe("deny") }) test("evaluate - wildcard pattern match", () => { - const result = PermissionNext.evaluate("bash", "rm", { bash: [{ pattern: "*", action: "allow" }] }) + const result = PermissionNext.evaluate("bash", "rm", [{ permission: "bash", pattern: "*", action: "allow" }]) expect(result).toBe("allow") }) test("evaluate - last matching rule wins", () => { - const result = PermissionNext.evaluate("bash", "rm", { - bash: [ - { pattern: "*", action: "allow" }, - { pattern: "rm", action: "deny" }, - ], - }) + const result = PermissionNext.evaluate("bash", "rm", [ + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "rm", action: "deny" }, + ]) expect(result).toBe("deny") }) test("evaluate - last matching rule wins (wildcard after specific)", () => { - const result = PermissionNext.evaluate("bash", "rm", { - bash: [ - { pattern: "rm", action: "deny" }, - { pattern: "*", action: "allow" }, - ], - }) + const result = PermissionNext.evaluate("bash", "rm", [ + { permission: "bash", pattern: "rm", action: "deny" }, + { permission: "bash", pattern: "*", action: "allow" }, + ]) expect(result).toBe("allow") }) test("evaluate - glob pattern match", () => { - const result = PermissionNext.evaluate("edit", "src/foo.ts", { edit: [{ pattern: "src/*", action: "allow" }] }) + const result = PermissionNext.evaluate("edit", "src/foo.ts", [ + { permission: "edit", pattern: "src/*", action: "allow" }, + ]) expect(result).toBe("allow") }) test("evaluate - last matching glob wins", () => { - const result = PermissionNext.evaluate("edit", "src/components/Button.tsx", { - edit: [ - { pattern: "src/*", action: "deny" }, - { pattern: "src/components/*", action: "allow" }, - ], - }) + const result = PermissionNext.evaluate("edit", "src/components/Button.tsx", [ + { permission: "edit", pattern: "src/*", action: "deny" }, + { permission: "edit", pattern: "src/components/*", action: "allow" }, + ]) expect(result).toBe("allow") }) test("evaluate - order matters for specificity", () => { // If more specific rule comes first, later wildcard overrides it - const result = PermissionNext.evaluate("edit", "src/components/Button.tsx", { - edit: [ - { pattern: "src/components/*", action: "allow" }, - { pattern: "src/*", action: "deny" }, - ], - }) + const result = PermissionNext.evaluate("edit", "src/components/Button.tsx", [ + { permission: "edit", pattern: "src/components/*", action: "allow" }, + { permission: "edit", pattern: "src/*", action: "deny" }, + ]) expect(result).toBe("deny") }) test("evaluate - unknown permission returns ask", () => { - const result = PermissionNext.evaluate("unknown_tool", "anything", { - bash: [{ pattern: "*", action: "allow" }], - }) + const result = PermissionNext.evaluate("unknown_tool", "anything", [ + { permission: "bash", pattern: "*", action: "allow" }, + ]) expect(result).toBe("ask") }) test("evaluate - empty ruleset returns ask", () => { - const result = PermissionNext.evaluate("bash", "rm", {}) + const result = PermissionNext.evaluate("bash", "rm", []) expect(result).toBe("ask") }) test("evaluate - no matching pattern returns ask", () => { - const result = PermissionNext.evaluate("edit", "etc/passwd", { edit: [{ pattern: "src/*", action: "allow" }] }) + const result = PermissionNext.evaluate("edit", "etc/passwd", [ + { permission: "edit", pattern: "src/*", action: "allow" }, + ]) expect(result).toBe("ask") }) test("evaluate - empty rules array returns ask", () => { - const result = PermissionNext.evaluate("bash", "rm", { bash: [] }) + const result = PermissionNext.evaluate("bash", "rm", []) expect(result).toBe("ask") }) test("evaluate - multiple matching patterns, last wins", () => { - const result = PermissionNext.evaluate("edit", "src/secret.ts", { - edit: [ - { pattern: "*", action: "ask" }, - { pattern: "src/*", action: "allow" }, - { pattern: "src/secret.ts", action: "deny" }, - ], - }) + const result = PermissionNext.evaluate("edit", "src/secret.ts", [ + { permission: "edit", pattern: "*", action: "ask" }, + { permission: "edit", pattern: "src/*", action: "allow" }, + { permission: "edit", pattern: "src/secret.ts", action: "deny" }, + ]) expect(result).toBe("deny") }) test("evaluate - non-matching patterns are skipped", () => { - const result = PermissionNext.evaluate("edit", "src/foo.ts", { - edit: [ - { pattern: "*", action: "ask" }, - { pattern: "test/*", action: "deny" }, - { pattern: "src/*", action: "allow" }, - ], - }) + const result = PermissionNext.evaluate("edit", "src/foo.ts", [ + { permission: "edit", pattern: "*", action: "ask" }, + { permission: "edit", pattern: "test/*", action: "deny" }, + { permission: "edit", pattern: "src/*", action: "allow" }, + ]) expect(result).toBe("allow") }) test("evaluate - exact match at end wins over earlier wildcard", () => { - const result = PermissionNext.evaluate("bash", "/bin/rm", { - bash: [ - { pattern: "*", action: "allow" }, - { pattern: "/bin/rm", action: "deny" }, - ], - }) + const result = PermissionNext.evaluate("bash", "/bin/rm", [ + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "/bin/rm", action: "deny" }, + ]) expect(result).toBe("deny") }) test("evaluate - wildcard at end overrides earlier exact match", () => { - const result = PermissionNext.evaluate("bash", "/bin/rm", { - bash: [ - { pattern: "/bin/rm", action: "deny" }, - { pattern: "*", action: "allow" }, - ], - }) + const result = PermissionNext.evaluate("bash", "/bin/rm", [ + { permission: "bash", pattern: "/bin/rm", action: "deny" }, + { permission: "bash", pattern: "*", action: "allow" }, + ]) expect(result).toBe("allow") }) // wildcard permission tests test("evaluate - wildcard permission matches any permission", () => { - const result = PermissionNext.evaluate("bash", "rm", { - "*": [{ pattern: "*", action: "deny" }], - }) + const result = PermissionNext.evaluate("bash", "rm", [{ permission: "*", pattern: "*", action: "deny" }]) expect(result).toBe("deny") }) test("evaluate - wildcard permission with specific pattern", () => { - const result = PermissionNext.evaluate("bash", "rm", { - "*": [{ pattern: "rm", action: "deny" }], - }) + const result = PermissionNext.evaluate("bash", "rm", [{ permission: "*", pattern: "rm", action: "deny" }]) expect(result).toBe("deny") }) test("evaluate - glob permission pattern", () => { - const result = PermissionNext.evaluate("mcp_server_tool", "anything", { - "mcp_*": [{ pattern: "*", action: "allow" }], - }) + const result = PermissionNext.evaluate("mcp_server_tool", "anything", [ + { permission: "mcp_*", pattern: "*", action: "allow" }, + ]) expect(result).toBe("allow") }) test("evaluate - specific permission and wildcard permission combined", () => { - const result = PermissionNext.evaluate("bash", "rm", { - "*": [{ pattern: "*", action: "deny" }], - bash: [{ pattern: "*", action: "allow" }], - }) + const result = PermissionNext.evaluate("bash", "rm", [ + { permission: "*", pattern: "*", action: "deny" }, + { permission: "bash", pattern: "*", action: "allow" }, + ]) expect(result).toBe("allow") }) test("evaluate - wildcard permission does not match when specific exists", () => { - const result = PermissionNext.evaluate("edit", "src/foo.ts", { - "*": [{ pattern: "*", action: "deny" }], - edit: [{ pattern: "src/*", action: "allow" }], - }) + const result = PermissionNext.evaluate("edit", "src/foo.ts", [ + { permission: "*", pattern: "*", action: "deny" }, + { permission: "edit", pattern: "src/*", action: "allow" }, + ]) expect(result).toBe("allow") }) test("evaluate - multiple matching permission patterns combine rules", () => { - const result = PermissionNext.evaluate("mcp_dangerous", "anything", { - "*": [{ pattern: "*", action: "ask" }], - "mcp_*": [{ pattern: "*", action: "allow" }], - mcp_dangerous: [{ pattern: "*", action: "deny" }], - }) + const result = PermissionNext.evaluate("mcp_dangerous", "anything", [ + { permission: "*", pattern: "*", action: "ask" }, + { permission: "mcp_*", pattern: "*", action: "allow" }, + { permission: "mcp_dangerous", pattern: "*", action: "deny" }, + ]) expect(result).toBe("deny") }) test("evaluate - wildcard permission fallback for unknown tool", () => { - const result = PermissionNext.evaluate("unknown_tool", "anything", { - "*": [{ pattern: "*", action: "ask" }], - bash: [{ pattern: "*", action: "allow" }], - }) + const result = PermissionNext.evaluate("unknown_tool", "anything", [ + { permission: "*", pattern: "*", action: "ask" }, + { permission: "bash", pattern: "*", action: "allow" }, + ]) expect(result).toBe("ask") }) test("evaluate - permission patterns sorted by length regardless of object order", () => { // specific permission listed before wildcard, but specific should still win - const result = PermissionNext.evaluate("bash", "rm", { - bash: [{ pattern: "*", action: "allow" }], - "*": [{ pattern: "*", action: "deny" }], - }) - expect(result).toBe("allow") + const result = PermissionNext.evaluate("bash", "rm", [ + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "*", pattern: "*", action: "deny" }, + ]) + // With flat list, last matching rule wins - so "*" matches bash and wins + expect(result).toBe("deny") }) -// disabledTools tests +// disabled tests -test("disabledTools - returns empty set when all tools allowed", () => { - const result = PermissionNext.disabledTools(["bash", "edit", "read"], { - "*": [{ pattern: "*", action: "allow" }], - }) +test("disabled - returns empty set when all tools allowed", () => { + const result = PermissionNext.disabled(["bash", "edit", "read"], [{ permission: "*", pattern: "*", action: "allow" }]) expect(result.size).toBe(0) }) -test("disabledTools - disables tool when denied", () => { - const result = PermissionNext.disabledTools(["bash", "edit", "read"], { - bash: [{ pattern: "*", action: "deny" }], - "*": [{ pattern: "*", action: "allow" }], - }) +test("disabled - disables tool when denied", () => { + const result = PermissionNext.disabled( + ["bash", "edit", "read"], + [ + { permission: "*", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "*", action: "deny" }, + ], + ) expect(result.has("bash")).toBe(true) expect(result.has("edit")).toBe(false) expect(result.has("read")).toBe(false) }) -test("disabledTools - disables edit/write/patch/multiedit when edit denied", () => { - const result = PermissionNext.disabledTools(["edit", "write", "patch", "multiedit", "bash"], { - edit: [{ pattern: "*", action: "deny" }], - "*": [{ pattern: "*", action: "allow" }], - }) +test("disabled - disables edit/write/patch/multiedit when edit denied", () => { + const result = PermissionNext.disabled( + ["edit", "write", "patch", "multiedit", "bash"], + [ + { permission: "*", pattern: "*", action: "allow" }, + { permission: "edit", pattern: "*", action: "deny" }, + ], + ) expect(result.has("edit")).toBe(true) expect(result.has("write")).toBe(true) expect(result.has("patch")).toBe(true) @@ -335,70 +332,75 @@ test("disabledTools - disables edit/write/patch/multiedit when edit denied", () expect(result.has("bash")).toBe(false) }) -test("disabledTools - does not disable when partially denied", () => { - const result = PermissionNext.disabledTools(["bash"], { - bash: [ - { pattern: "*", action: "allow" }, - { pattern: "rm *", action: "deny" }, +test("disabled - does not disable when partially denied", () => { + const result = PermissionNext.disabled( + ["bash"], + [ + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "rm *", action: "deny" }, ], - }) + ) expect(result.has("bash")).toBe(false) }) -test("disabledTools - does not disable when action is ask", () => { - const result = PermissionNext.disabledTools(["bash", "edit"], { - "*": [{ pattern: "*", action: "ask" }], - }) +test("disabled - does not disable when action is ask", () => { + const result = PermissionNext.disabled(["bash", "edit"], [{ permission: "*", pattern: "*", action: "ask" }]) expect(result.size).toBe(0) }) -test("disabledTools - disables when wildcard deny even with specific allow", () => { +test("disabled - disables when wildcard deny even with specific allow", () => { // Tool is disabled because evaluate("bash", "*", ...) returns "deny" // The "echo *" allow rule doesn't match the "*" pattern we're checking - const result = PermissionNext.disabledTools(["bash"], { - bash: [ - { pattern: "*", action: "deny" }, - { pattern: "echo *", action: "allow" }, + const result = PermissionNext.disabled( + ["bash"], + [ + { permission: "bash", pattern: "*", action: "deny" }, + { permission: "bash", pattern: "echo *", action: "allow" }, ], - }) + ) expect(result.has("bash")).toBe(true) }) -test("disabledTools - does not disable when wildcard allow after deny", () => { - const result = PermissionNext.disabledTools(["bash"], { - bash: [ - { pattern: "rm *", action: "deny" }, - { pattern: "*", action: "allow" }, +test("disabled - does not disable when wildcard allow after deny", () => { + const result = PermissionNext.disabled( + ["bash"], + [ + { permission: "bash", pattern: "rm *", action: "deny" }, + { permission: "bash", pattern: "*", action: "allow" }, ], - }) + ) expect(result.has("bash")).toBe(false) }) -test("disabledTools - disables multiple tools", () => { - const result = PermissionNext.disabledTools(["bash", "edit", "webfetch"], { - bash: [{ pattern: "*", action: "deny" }], - edit: [{ pattern: "*", action: "deny" }], - webfetch: [{ pattern: "*", action: "deny" }], - }) +test("disabled - disables multiple tools", () => { + const result = PermissionNext.disabled( + ["bash", "edit", "webfetch"], + [ + { permission: "bash", pattern: "*", action: "deny" }, + { permission: "edit", pattern: "*", action: "deny" }, + { permission: "webfetch", pattern: "*", action: "deny" }, + ], + ) expect(result.has("bash")).toBe(true) expect(result.has("edit")).toBe(true) expect(result.has("webfetch")).toBe(true) }) -test("disabledTools - wildcard permission denies all tools", () => { - const result = PermissionNext.disabledTools(["bash", "edit", "read"], { - "*": [{ pattern: "*", action: "deny" }], - }) +test("disabled - wildcard permission denies all tools", () => { + const result = PermissionNext.disabled(["bash", "edit", "read"], [{ permission: "*", pattern: "*", action: "deny" }]) expect(result.has("bash")).toBe(true) expect(result.has("edit")).toBe(true) expect(result.has("read")).toBe(true) }) -test("disabledTools - specific allow overrides wildcard deny", () => { - const result = PermissionNext.disabledTools(["bash", "edit", "read"], { - "*": [{ pattern: "*", action: "deny" }], - bash: [{ pattern: "*", action: "allow" }], - }) +test("disabled - specific allow overrides wildcard deny", () => { + const result = PermissionNext.disabled( + ["bash", "edit", "read"], + [ + { permission: "*", pattern: "*", action: "deny" }, + { permission: "bash", pattern: "*", action: "allow" }, + ], + ) expect(result.has("bash")).toBe(false) expect(result.has("edit")).toBe(true) expect(result.has("read")).toBe(true)