@rosetears/aili-pi 0.1.6 → 0.1.7

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.
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Generated AILI adaptation of pi-permission-modes@2.2.0.
3
+ * Source revision: 23d65d10a53b67043cae42322acf9044d6edb196.
4
+ * Regenerate with scripts/sync-permission-modes.ts; do not edit manually.
5
+ */
6
+ /**
7
+ * Resolution engine — turns a (mode, surface, target) into an allow/ask/deny.
8
+ *
9
+ * Two composition rules, mirroring the opencode model:
10
+ * - within a surface's pattern-map: LAST matching pattern wins (so a leading
11
+ * `"*"` sets the default and later specific patterns override it);
12
+ * - across layers (path gate · external_directory · the named surface):
13
+ * MOST-RESTRICTIVE wins (deny > ask > allow).
14
+ *
15
+ * Pure and SDK-free (only `os.homedir()` for `~`/`$HOME` expansion) so the whole
16
+ * matrix is unit-testable. Out-of-project determination is passed in by the
17
+ * caller (computed via paths.isOutside) to keep this module free of fs/symlink
18
+ * concerns.
19
+ */
20
+
21
+ import os from "node:os";
22
+ import { FILE_SURFACES, type Action, type ModeDef, type Surface, type SurfaceValue } from "pi-permission-modes/src/schema.ts";
23
+
24
+ const RANK: Record<Action, number> = { allow: 0, ask: 1, deny: 2 };
25
+
26
+ /** Expand a leading `~` or `$HOME` to the user's home directory. */
27
+ export function expandHome(s: string): string {
28
+ const home = os.homedir();
29
+ if (s === "~" || s === "$HOME") return home;
30
+ if (s.startsWith("~/")) return home + s.slice(1);
31
+ if (s.startsWith("$HOME/")) return home + s.slice(5);
32
+ return s;
33
+ }
34
+
35
+ /**
36
+ * Glob match: `*` matches any run of characters INCLUDING `/` (so `*` is a true
37
+ * universal fallback and `*.md` matches nested paths); `?` matches exactly one
38
+ * character. `~`/`$HOME` are expanded on both pattern and target before matching.
39
+ */
40
+ export function matchPattern(pattern: string, target: string): boolean {
41
+ const p = expandHome(pattern);
42
+ const t = expandHome(target);
43
+ let re = "^";
44
+ for (const ch of p) {
45
+ if (ch === "*") re += ".*";
46
+ else if (ch === "?") re += ".";
47
+ else re += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
48
+ }
49
+ re += "$";
50
+ return new RegExp(re, "s").test(t);
51
+ }
52
+
53
+ /**
54
+ * Resolve one surface value against a target. String shorthand returns directly;
55
+ * a pattern-map returns the action of the LAST matching pattern (definition
56
+ * order, via Object.entries — keep pattern keys non-numeric to preserve order).
57
+ * Returns undefined when the surface is absent or nothing matches.
58
+ */
59
+ export function resolveSurface(value: SurfaceValue | undefined, target: string): Action | undefined {
60
+ if (value === undefined) return undefined;
61
+ if (typeof value === "string") return value;
62
+ let result: Action | undefined;
63
+ for (const [pattern, action] of Object.entries(value)) {
64
+ if (matchPattern(pattern, target)) result = action;
65
+ }
66
+ return result;
67
+ }
68
+
69
+ /** Most-restrictive of the given actions (deny > ask > allow); undefined ignored. */
70
+ export function mostRestrictive(...actions: (Action | undefined)[]): Action | undefined {
71
+ let best: Action | undefined;
72
+ let bestRank = -1;
73
+ for (const a of actions) {
74
+ if (a === undefined) continue;
75
+ if (RANK[a] > bestRank) {
76
+ best = a;
77
+ bestRank = RANK[a];
78
+ }
79
+ }
80
+ return best;
81
+ }
82
+
83
+ const isFileSurface = (s: Surface): boolean => FILE_SURFACES.includes(s);
84
+
85
+ /**
86
+ * Decide the action for a (surface, target) under a mode. Folds the cross-cutting
87
+ * `path` gate (file surfaces only) and the `external_directory` gate (when the
88
+ * target is outside the project) into the named surface via most-restrictive.
89
+ *
90
+ * `opts.isOutside` — whether `target` resolves outside the project (caller
91
+ * computes via paths.isOutside). `opts.fallback` — the action when no layer
92
+ * matches at all (default "ask", least-privilege; built-in modes always specify
93
+ * a `"*"` so this only affects sparse user-authored modes).
94
+ */
95
+ export function decide(
96
+ mode: ModeDef,
97
+ surface: Surface,
98
+ target: string,
99
+ opts: { isOutside?: boolean; fallback?: Action } = {},
100
+ ): Action {
101
+ const layers: (Action | undefined)[] = [];
102
+
103
+ // The base policy and the project tighten-only overlay are independent sources;
104
+ // composing both with most-restrictive means the overlay can only tighten.
105
+ const sources = mode.projectOverlay ? [mode.permission, mode.projectOverlay] : [mode.permission];
106
+ for (const perm of sources) {
107
+ if (isFileSurface(surface)) layers.push(resolveSurface(perm.path, target));
108
+ if (opts.isOutside) layers.push(resolveSurface(perm.external_directory, target));
109
+ layers.push(resolveSurface(perm[surface], target));
110
+ }
111
+
112
+ return mostRestrictive(...layers) ?? opts.fallback ?? "ask";
113
+ }
114
+
115
+ /**
116
+ * Decide the action for ONE extracted bash command (the tree-sitter path, where
117
+ * each command in a chain is judged separately). Per source (base policy +
118
+ * project tighten-only overlay), most-restrictive of:
119
+ * - the `bash` surface matched against the joined "name args…" string;
120
+ * - the cross-cutting `path` gate matched against that same joined string
121
+ * (parity with the heuristic fallback, where `decide` folds `path` over the
122
+ * whole command line);
123
+ * - the `path` gate matched against each individual token (name and each
124
+ * arg), so path globs like `*.env` bind bash arguments regardless of where
125
+ * they sit in the command.
126
+ *
127
+ * Returns undefined when no layer matches at all; the caller picks the default
128
+ * (the dispatcher treats a fully-unmatched command as "allow" within a chain,
129
+ * matching resolveSurface semantics for absent surfaces).
130
+ */
131
+ export function decideBashCommand(mode: ModeDef, name: string, args: string[]): Action | undefined {
132
+ const tokens = [name, ...args];
133
+ const joined = tokens.join(" ").trim();
134
+ const layers: (Action | undefined)[] = [];
135
+ const sources = mode.projectOverlay ? [mode.permission, mode.projectOverlay] : [mode.permission];
136
+ for (const perm of sources) {
137
+ layers.push(resolveSurface(perm.bash, joined));
138
+ layers.push(resolveSurface(perm.path, joined));
139
+ for (const tok of tokens) layers.push(resolveSurface(perm.path, tok));
140
+ }
141
+ return mostRestrictive(...layers);
142
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "package": {
4
+ "name": "pi-permission-modes",
5
+ "version": "2.2.0",
6
+ "revision": "23d65d10a53b67043cae42322acf9044d6edb196",
7
+ "license": "MIT"
8
+ },
9
+ "upstreamFiles": [
10
+ {
11
+ "path": "src/index.ts",
12
+ "sha256": "fd4462a3b7ba986af734c2e17ba8ea7178df56c933e87ed444ba90ba24c2fd5b"
13
+ },
14
+ {
15
+ "path": "src/resolve.ts",
16
+ "sha256": "13f52a4a9c08d7a55f5f9d03f97302d864768838fb3e9fca2051cb7d94a0ae82"
17
+ },
18
+ {
19
+ "path": "LICENSE",
20
+ "sha256": "d87cb99b43f6bf8771e57be83485db11b977b9dfa21b6bd201b8d3d370bdce43"
21
+ }
22
+ ],
23
+ "adaptedFiles": [
24
+ {
25
+ "path": "src/vendor/pi-permission-modes/index.ts",
26
+ "sha256": "8bfa0364967a2b76e9900cc72b8f434ea1cfa6520899c7827488220f2e288ee8"
27
+ },
28
+ {
29
+ "path": "src/vendor/pi-permission-modes/resolve.ts",
30
+ "sha256": "f71688f847495da5122724f75c5ebe3b41066b3d3cac74cbe99f66b9906404f6"
31
+ },
32
+ {
33
+ "path": "licenses/pi-permission-modes-MIT.txt",
34
+ "sha256": "d87cb99b43f6bf8771e57be83485db11b977b9dfa21b6bd201b8d3d370bdce43"
35
+ }
36
+ ],
37
+ "localChanges": [
38
+ "Package-owned adapted entry redirects all unchanged sibling modules to the exact pi-permission-modes dependency while owning resolve.ts locally.",
39
+ "matchPattern compiles its anchored glob RegExp with dotAll so * and ? include ECMAScript line terminators."
40
+ ],
41
+ "generatedBy": "scripts/sync-permission-modes.ts",
42
+ "verification": [
43
+ "npm run verify:permission-modes",
44
+ "tests/unit/permission-patterns.test.ts",
45
+ "tests/integration/permission-modes.test.ts"
46
+ ]
47
+ }