@a2f0/agent-tool 0.1.27 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a2f0/agent-tool",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "packageManager": "bun@1.4.3",
5
5
  "type": "module",
6
6
  "description": "Portable cross-agent review, PR workflows, and shared skills",
@@ -40,7 +40,7 @@ export const CATALOG: CatalogEntry[] = [
40
40
  // `bun x` runs only executables that `permissions.executables` classifies; project.ts adds those.
41
41
  // Package scripts are not classified, so `bun run` keeps the harness default.
42
42
 
43
- /** Direct spellings of classified actions. Asking here keeps a raw command from bypassing its agent-tool form. */
43
+ /** Direct spellings of classified actions. Codex prompts for them, so a raw command cannot bypass its agent-tool form. */
44
44
  export const GUARDS: { command: string; class: PermissionClass }[] = [
45
45
  ...["bun add", "bun a", "npm install", "npm i", "npm add", "pnpm add", "yarn add", "bunx", "bun x", "npx"].map(command => ({ command, class: "intake" as const })),
46
46
  ...["gh pr merge", "npm publish", "bun publish"].map(command => ({ command, class: "release" as const })),
@@ -56,18 +56,25 @@ export function strictest(entry: CatalogEntry): PermissionClass {
56
56
 
57
57
  export interface ClaudePermissions { allow: string[]; ask: string[] }
58
58
 
59
- /** Claude Code matches `*` across spaces and splits compound commands before matching. */
59
+ /**
60
+ * Claude Code matches `*` across spaces and splits compound commands before matching.
61
+ * Only allow rules are generated: Claude Code prompts for an explicit ask rule in every
62
+ * mode, bypassPermissions included, so a committed ask rule would override each user's
63
+ * chosen mode. Classes that ask stay unlisted, so Manual mode prompts for them and auto
64
+ * mode leaves them to the classifier. `ask` stays empty so that reinstalling removes the
65
+ * ask rules earlier versions recorded.
66
+ */
60
67
  export function claudePermissions(project: CatalogEntry[] = []): ClaudePermissions {
61
- const rules: ClaudePermissions = { allow: [], ask: [] };
68
+ const allow: string[] = [];
62
69
  for (const invocation of INVOCATIONS) {
63
70
  for (const entry of [...CATALOG, ...project]) {
71
+ // An allow rule also matches the `--apply` form, so a command whose `--apply` form asks gets none.
72
+ if (CLASSES[strictest(entry)].decision !== "allow") continue;
64
73
  const base = `${invocation} ${entry.command}`;
65
- rules[CLASSES[entry.class].decision].push(entry.command.startsWith("--") ? `Bash(${base})` : `Bash(${base} *)`);
66
- if (entry.applyClass && CLASSES[entry.applyClass].decision !== CLASSES[entry.class].decision) rules[CLASSES[entry.applyClass].decision].push(`Bash(${base} *--apply*)`);
74
+ allow.push(entry.command.startsWith("--") ? `Bash(${base})` : `Bash(${base} *)`);
67
75
  }
68
76
  }
69
- for (const guard of GUARDS) rules[CLASSES[guard.class].decision].push(`Bash(${guard.command} *)`);
70
- return rules;
77
+ return { allow, ask: [] };
71
78
  }
72
79
 
73
80
  const quote = (value: string) => JSON.stringify(value);
@@ -69,7 +69,9 @@ function mergedSettings(settings: Settings, previous: ClaudePermissions | undefi
69
69
  for (const key of ["allow", "ask"] as const) {
70
70
  const owned = new Set(previous?.[key] ?? []);
71
71
  const kept = ((permissions[key] as string[] | undefined) ?? []).filter(item => !owned.has(item) && !current[key].includes(item));
72
- permissions[key] = [...kept, ...current[key]];
72
+ const merged = [...kept, ...current[key]];
73
+ if (merged.length) permissions[key] = merged;
74
+ else delete permissions[key];
73
75
  }
74
76
  return { ...settings, permissions };
75
77
  }