gencode-ai 0.1.0 → 0.1.2
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/.gencode/settings.local.json +7 -0
- package/README.md +20 -102
- package/dist/agent/agent.d.ts +43 -2
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +90 -17
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/types.d.ts +9 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/cli/components/AllModelsSelector.d.ts +11 -0
- package/dist/cli/components/AllModelsSelector.d.ts.map +1 -0
- package/dist/cli/components/AllModelsSelector.js +153 -0
- package/dist/cli/components/AllModelsSelector.js.map +1 -0
- package/dist/cli/components/App.d.ts +8 -1
- package/dist/cli/components/App.d.ts.map +1 -1
- package/dist/cli/components/App.js +276 -40
- package/dist/cli/components/App.js.map +1 -1
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
- package/dist/cli/components/CommandSuggestions.js +3 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -1
- package/dist/cli/components/Header.d.ts +1 -1
- package/dist/cli/components/Header.d.ts.map +1 -1
- package/dist/cli/components/Header.js +4 -6
- package/dist/cli/components/Header.js.map +1 -1
- package/dist/cli/components/Logo.d.ts +1 -0
- package/dist/cli/components/Logo.d.ts.map +1 -1
- package/dist/cli/components/Logo.js +16 -3
- package/dist/cli/components/Logo.js.map +1 -1
- package/dist/cli/components/Messages.d.ts +17 -3
- package/dist/cli/components/Messages.d.ts.map +1 -1
- package/dist/cli/components/Messages.js +70 -18
- package/dist/cli/components/Messages.js.map +1 -1
- package/dist/cli/components/ModelSelector.d.ts +7 -7
- package/dist/cli/components/ModelSelector.d.ts.map +1 -1
- package/dist/cli/components/ModelSelector.js +116 -33
- package/dist/cli/components/ModelSelector.js.map +1 -1
- package/dist/cli/components/PermissionPrompt.d.ts +60 -0
- package/dist/cli/components/PermissionPrompt.d.ts.map +1 -0
- package/dist/cli/components/PermissionPrompt.js +192 -0
- package/dist/cli/components/PermissionPrompt.js.map +1 -0
- package/dist/cli/components/ProviderManager.d.ts +8 -0
- package/dist/cli/components/ProviderManager.d.ts.map +1 -0
- package/dist/cli/components/ProviderManager.js +280 -0
- package/dist/cli/components/ProviderManager.js.map +1 -0
- package/dist/cli/components/Spinner.d.ts +7 -2
- package/dist/cli/components/Spinner.d.ts.map +1 -1
- package/dist/cli/components/Spinner.js +116 -25
- package/dist/cli/components/Spinner.js.map +1 -1
- package/dist/cli/components/TodoList.d.ts +7 -0
- package/dist/cli/components/TodoList.d.ts.map +1 -0
- package/dist/cli/components/TodoList.js +34 -0
- package/dist/cli/components/TodoList.js.map +1 -0
- package/dist/cli/components/index.d.ts +1 -0
- package/dist/cli/components/index.d.ts.map +1 -1
- package/dist/cli/components/index.js +1 -0
- package/dist/cli/components/index.js.map +1 -1
- package/dist/cli/components/markdown.d.ts +9 -0
- package/dist/cli/components/markdown.d.ts.map +1 -0
- package/dist/cli/components/markdown.js +129 -0
- package/dist/cli/components/markdown.js.map +1 -0
- package/dist/cli/components/theme.d.ts +5 -0
- package/dist/cli/components/theme.d.ts.map +1 -1
- package/dist/cli/components/theme.js +7 -0
- package/dist/cli/components/theme.js.map +1 -1
- package/dist/cli/index.js +66 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +14 -4
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +19 -3
- package/dist/config/index.js.map +1 -1
- package/dist/config/levels.d.ts +49 -0
- package/dist/config/levels.d.ts.map +1 -0
- package/dist/config/levels.js +222 -0
- package/dist/config/levels.js.map +1 -0
- package/dist/config/loader.d.ts +46 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +153 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/manager.d.ts +115 -15
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +260 -34
- package/dist/config/manager.js.map +1 -1
- package/dist/config/manager.test.d.ts +5 -0
- package/dist/config/manager.test.d.ts.map +1 -0
- package/dist/config/manager.test.js +192 -0
- package/dist/config/manager.test.js.map +1 -0
- package/dist/config/merger.d.ts +56 -0
- package/dist/config/merger.d.ts.map +1 -0
- package/dist/config/merger.js +177 -0
- package/dist/config/merger.js.map +1 -0
- package/dist/config/providers-config.d.ts +28 -0
- package/dist/config/providers-config.d.ts.map +1 -0
- package/dist/config/providers-config.js +79 -0
- package/dist/config/providers-config.js.map +1 -0
- package/dist/config/test-utils.d.ts +24 -0
- package/dist/config/test-utils.d.ts.map +1 -0
- package/dist/config/test-utils.js +55 -0
- package/dist/config/test-utils.js.map +1 -0
- package/dist/config/types.d.ts +108 -9
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +53 -2
- package/dist/config/types.js.map +1 -1
- package/dist/memory/import-resolver.d.ts +46 -0
- package/dist/memory/import-resolver.d.ts.map +1 -0
- package/dist/memory/import-resolver.js +117 -0
- package/dist/memory/import-resolver.js.map +1 -0
- package/dist/memory/index.d.ts +7 -6
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +7 -5
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/init-prompt.d.ts +22 -0
- package/dist/memory/init-prompt.d.ts.map +1 -0
- package/dist/memory/init-prompt.js +103 -0
- package/dist/memory/init-prompt.js.map +1 -0
- package/dist/memory/memory-manager.d.ts +119 -0
- package/dist/memory/memory-manager.d.ts.map +1 -0
- package/dist/memory/memory-manager.js +587 -0
- package/dist/memory/memory-manager.js.map +1 -0
- package/dist/memory/rules-parser.d.ts +38 -0
- package/dist/memory/rules-parser.d.ts.map +1 -0
- package/dist/memory/rules-parser.js +69 -0
- package/dist/memory/rules-parser.js.map +1 -0
- package/dist/memory/test-utils.d.ts +20 -0
- package/dist/memory/test-utils.d.ts.map +1 -0
- package/dist/memory/test-utils.js +44 -0
- package/dist/memory/test-utils.js.map +1 -0
- package/dist/memory/types.d.ts +70 -63
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +42 -2
- package/dist/memory/types.js.map +1 -1
- package/dist/permissions/audit.d.ts +82 -0
- package/dist/permissions/audit.d.ts.map +1 -0
- package/dist/permissions/audit.js +229 -0
- package/dist/permissions/audit.js.map +1 -0
- package/dist/permissions/index.d.ts +11 -1
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +15 -0
- package/dist/permissions/index.js.map +1 -1
- package/dist/permissions/manager.d.ts +149 -13
- package/dist/permissions/manager.d.ts.map +1 -1
- package/dist/permissions/manager.js +480 -35
- package/dist/permissions/manager.js.map +1 -1
- package/dist/permissions/manager.test.d.ts +5 -0
- package/dist/permissions/manager.test.d.ts.map +1 -0
- package/dist/permissions/manager.test.js +213 -0
- package/dist/permissions/manager.test.js.map +1 -0
- package/dist/permissions/persistence.d.ts +74 -0
- package/dist/permissions/persistence.d.ts.map +1 -0
- package/dist/permissions/persistence.js +248 -0
- package/dist/permissions/persistence.js.map +1 -0
- package/dist/permissions/persistence.test.d.ts +5 -0
- package/dist/permissions/persistence.test.d.ts.map +1 -0
- package/dist/permissions/persistence.test.js +171 -0
- package/dist/permissions/persistence.test.js.map +1 -0
- package/dist/permissions/prompt-matcher.d.ts +64 -0
- package/dist/permissions/prompt-matcher.d.ts.map +1 -0
- package/dist/permissions/prompt-matcher.js +415 -0
- package/dist/permissions/prompt-matcher.js.map +1 -0
- package/dist/permissions/prompt-matcher.test.d.ts +5 -0
- package/dist/permissions/prompt-matcher.test.d.ts.map +1 -0
- package/dist/permissions/prompt-matcher.test.js +107 -0
- package/dist/permissions/prompt-matcher.test.js.map +1 -0
- package/dist/permissions/types.d.ts +157 -0
- package/dist/permissions/types.d.ts.map +1 -1
- package/dist/permissions/types.js +43 -8
- package/dist/permissions/types.js.map +1 -1
- package/dist/prompts/index.d.ts +92 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +241 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +14 -3
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +5 -3
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +13 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/registry.d.ts +66 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +158 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/search/brave.d.ts +14 -0
- package/dist/providers/search/brave.d.ts.map +1 -0
- package/dist/providers/search/brave.js +87 -0
- package/dist/providers/search/brave.js.map +1 -0
- package/dist/providers/search/exa.d.ts +12 -0
- package/dist/providers/search/exa.d.ts.map +1 -0
- package/dist/providers/search/exa.js +158 -0
- package/dist/providers/search/exa.js.map +1 -0
- package/dist/providers/search/index.d.ts +31 -0
- package/dist/providers/search/index.d.ts.map +1 -0
- package/dist/providers/search/index.js +75 -0
- package/dist/providers/search/index.js.map +1 -0
- package/dist/providers/search/serper.d.ts +14 -0
- package/dist/providers/search/serper.d.ts.map +1 -0
- package/dist/providers/search/serper.js +87 -0
- package/dist/providers/search/serper.js.map +1 -0
- package/dist/providers/search/types.d.ts +21 -0
- package/dist/providers/search/types.d.ts.map +1 -0
- package/dist/providers/search/types.js +5 -0
- package/dist/providers/search/types.js.map +1 -0
- package/dist/providers/store.d.ts +104 -0
- package/dist/providers/store.d.ts.map +1 -0
- package/dist/providers/store.js +171 -0
- package/dist/providers/store.js.map +1 -0
- package/dist/providers/types.d.ts +7 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/vertex-ai.d.ts +33 -0
- package/dist/providers/vertex-ai.d.ts.map +1 -0
- package/dist/providers/vertex-ai.js +407 -0
- package/dist/providers/vertex-ai.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -1
- package/dist/tools/builtin/bash.js +2 -1
- package/dist/tools/builtin/bash.js.map +1 -1
- package/dist/tools/builtin/edit.d.ts.map +1 -1
- package/dist/tools/builtin/edit.js +2 -1
- package/dist/tools/builtin/edit.js.map +1 -1
- package/dist/tools/builtin/glob.d.ts.map +1 -1
- package/dist/tools/builtin/glob.js +2 -1
- package/dist/tools/builtin/glob.js.map +1 -1
- package/dist/tools/builtin/grep.d.ts.map +1 -1
- package/dist/tools/builtin/grep.js +2 -1
- package/dist/tools/builtin/grep.js.map +1 -1
- package/dist/tools/builtin/read.d.ts.map +1 -1
- package/dist/tools/builtin/read.js +2 -1
- package/dist/tools/builtin/read.js.map +1 -1
- package/dist/tools/builtin/todowrite.d.ts +15 -0
- package/dist/tools/builtin/todowrite.d.ts.map +1 -0
- package/dist/tools/builtin/todowrite.js +88 -0
- package/dist/tools/builtin/todowrite.js.map +1 -0
- package/dist/tools/builtin/webfetch.d.ts +20 -0
- package/dist/tools/builtin/webfetch.d.ts.map +1 -0
- package/dist/tools/builtin/webfetch.js +228 -0
- package/dist/tools/builtin/webfetch.js.map +1 -0
- package/dist/tools/builtin/websearch.d.ts +17 -0
- package/dist/tools/builtin/websearch.d.ts.map +1 -0
- package/dist/tools/builtin/websearch.js +87 -0
- package/dist/tools/builtin/websearch.js.map +1 -0
- package/dist/tools/builtin/write.d.ts.map +1 -1
- package/dist/tools/builtin/write.js +2 -1
- package/dist/tools/builtin/write.js.map +1 -1
- package/dist/tools/index.d.ts +18 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +28 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/types.d.ts +41 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +16 -0
- package/dist/tools/types.js.map +1 -1
- package/dist/tools/utils/ssrf.d.ts +18 -0
- package/dist/tools/utils/ssrf.d.ts.map +1 -0
- package/dist/tools/utils/ssrf.js +70 -0
- package/dist/tools/utils/ssrf.js.map +1 -0
- package/docs/README.md +5 -4
- package/docs/config-system-comparison.md +707 -0
- package/docs/memory-system.md +238 -0
- package/docs/permissions.md +368 -0
- package/docs/proposals/0001-web-fetch-tool.md +32 -2
- package/docs/proposals/0002-web-search-tool.md +59 -2
- package/docs/proposals/0005-todo-system.md +350 -85
- package/docs/proposals/0006-memory-system.md +11 -10
- package/docs/proposals/0012-ask-user-question.md +941 -206
- package/docs/proposals/0023-permission-enhancements.md +61 -2
- package/docs/proposals/0041-configuration-system.md +587 -0
- package/docs/proposals/0042-prompt-optimization.md +866 -0
- package/docs/proposals/README.md +8 -6
- package/docs/providers.md +220 -0
- package/jest.config.js +26 -0
- package/package.json +14 -3
- package/src/agent/agent.ts +120 -18
- package/src/agent/types.ts +9 -1
- package/src/cli/components/App.tsx +369 -47
- package/src/cli/components/CommandSuggestions.tsx +3 -0
- package/src/cli/components/Header.tsx +11 -17
- package/src/cli/components/Logo.tsx +76 -9
- package/src/cli/components/Messages.tsx +146 -38
- package/src/cli/components/ModelSelector.tsx +169 -52
- package/src/cli/components/PermissionPrompt.tsx +388 -0
- package/src/cli/components/ProviderManager.tsx +534 -0
- package/src/cli/components/Spinner.tsx +138 -25
- package/src/cli/components/TodoList.tsx +54 -0
- package/src/cli/components/index.ts +6 -0
- package/src/cli/components/markdown.ts +157 -0
- package/src/cli/components/theme.ts +7 -0
- package/src/cli/index.tsx +76 -13
- package/src/config/index.ts +79 -4
- package/src/config/levels.test.ts +163 -0
- package/src/config/levels.ts +285 -0
- package/src/config/loader.test.ts +120 -0
- package/src/config/loader.ts +178 -0
- package/src/config/manager.test.ts +215 -0
- package/src/config/manager.ts +328 -40
- package/src/config/merger.test.ts +360 -0
- package/src/config/merger.ts +221 -0
- package/src/config/providers-config.ts +85 -0
- package/src/config/test-utils.ts +79 -0
- package/src/config/types.ts +186 -9
- package/src/memory/import-resolver.test.ts +117 -0
- package/src/memory/import-resolver.ts +149 -0
- package/src/memory/index.ts +11 -0
- package/src/memory/init-prompt.ts +113 -0
- package/src/memory/memory-manager.test.ts +198 -0
- package/src/memory/memory-manager.ts +716 -0
- package/src/memory/rules-parser.test.ts +182 -0
- package/src/memory/rules-parser.ts +82 -0
- package/src/memory/test-utils.ts +60 -0
- package/src/memory/types.ts +119 -0
- package/src/permissions/audit.ts +284 -0
- package/src/permissions/index.ts +20 -1
- package/src/permissions/manager.test.ts +260 -0
- package/src/permissions/manager.ts +592 -40
- package/src/permissions/persistence.test.ts +220 -0
- package/src/permissions/persistence.ts +301 -0
- package/src/permissions/prompt-matcher.test.ts +213 -0
- package/src/permissions/prompt-matcher.ts +472 -0
- package/src/permissions/types.ts +236 -8
- package/src/prompts/index.test.ts +279 -0
- package/src/prompts/index.ts +306 -0
- package/src/prompts/system/anthropic.txt +29 -0
- package/src/prompts/system/base.txt +124 -0
- package/src/prompts/system/gemini.txt +35 -0
- package/src/prompts/system/generic.txt +128 -0
- package/src/prompts/system/openai.txt +29 -0
- package/src/prompts/tools/bash.txt +60 -0
- package/src/prompts/tools/edit.txt +29 -0
- package/src/prompts/tools/glob.txt +35 -0
- package/src/prompts/tools/grep.txt +43 -0
- package/src/prompts/tools/read.txt +22 -0
- package/src/prompts/tools/todowrite.txt +71 -0
- package/src/prompts/tools/webfetch.txt +34 -0
- package/src/prompts/tools/websearch.txt +41 -0
- package/src/prompts/tools/write.txt +23 -0
- package/src/providers/gemini.ts +20 -4
- package/src/providers/index.ts +18 -3
- package/src/providers/registry.ts +198 -0
- package/src/providers/search/brave.ts +132 -0
- package/src/providers/search/exa.ts +217 -0
- package/src/providers/search/index.ts +79 -0
- package/src/providers/search/serper.ts +133 -0
- package/src/providers/search/types.ts +24 -0
- package/src/providers/store.ts +216 -0
- package/src/providers/types.ts +9 -1
- package/src/providers/vertex-ai.ts +594 -0
- package/src/tools/builtin/bash.ts +2 -1
- package/src/tools/builtin/edit.ts +2 -1
- package/src/tools/builtin/glob.ts +2 -1
- package/src/tools/builtin/grep.ts +2 -1
- package/src/tools/builtin/read.ts +2 -1
- package/src/tools/builtin/todowrite.ts +102 -0
- package/src/tools/builtin/webfetch.ts +261 -0
- package/src/tools/builtin/websearch.ts +103 -0
- package/src/tools/builtin/write.ts +2 -1
- package/src/tools/index.ts +28 -2
- package/src/tools/types.ts +32 -0
- package/src/tools/utils/ssrf.ts +79 -0
- package/tsconfig.json +1 -1
- package/CLAUDE.md +0 -70
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PermissionPersistence Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import { PermissionPersistence } from './persistence.js';
|
|
9
|
+
|
|
10
|
+
describe('PermissionPersistence', () => {
|
|
11
|
+
let tempDir: string;
|
|
12
|
+
let projectDir: string;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
// Create temp directories for testing
|
|
16
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gencode-perm-test-'));
|
|
17
|
+
projectDir = path.join(tempDir, 'project');
|
|
18
|
+
|
|
19
|
+
await fs.mkdir(projectDir, { recursive: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(async () => {
|
|
23
|
+
// Cleanup temp directories
|
|
24
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('addRule', () => {
|
|
28
|
+
it('should add a rule to project scope', async () => {
|
|
29
|
+
const projectPermDir = path.join(projectDir, '.claude');
|
|
30
|
+
await fs.mkdir(projectPermDir, { recursive: true });
|
|
31
|
+
|
|
32
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
33
|
+
|
|
34
|
+
const rule = await persistence.addRule('WebSearch', 'auto', {
|
|
35
|
+
scope: 'project',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(rule.id).toBeDefined();
|
|
39
|
+
expect(rule.tool).toBe('WebSearch');
|
|
40
|
+
expect(rule.mode).toBe('auto');
|
|
41
|
+
expect(rule.scope).toBe('project');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should add a rule with pattern', async () => {
|
|
45
|
+
const projectPermDir = path.join(projectDir, '.claude');
|
|
46
|
+
await fs.mkdir(projectPermDir, { recursive: true });
|
|
47
|
+
|
|
48
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
49
|
+
|
|
50
|
+
const rule = await persistence.addRule('Bash', 'auto', {
|
|
51
|
+
pattern: 'git add:*',
|
|
52
|
+
scope: 'project',
|
|
53
|
+
description: 'Test rule',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(rule.pattern).toBe('git add:*');
|
|
57
|
+
expect(rule.description).toBe('Test rule');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('getRules', () => {
|
|
62
|
+
it('should return empty array for non-existent project file', async () => {
|
|
63
|
+
const projectPermDir = path.join(projectDir, '.claude');
|
|
64
|
+
await fs.mkdir(projectPermDir, { recursive: true });
|
|
65
|
+
|
|
66
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
67
|
+
|
|
68
|
+
const rules = await persistence.getRules('project');
|
|
69
|
+
expect(rules).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return saved project rules', async () => {
|
|
73
|
+
const projectPermDir = path.join(projectDir, '.claude');
|
|
74
|
+
await fs.mkdir(projectPermDir, { recursive: true });
|
|
75
|
+
|
|
76
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
77
|
+
|
|
78
|
+
await persistence.addRule('Bash', 'auto', {
|
|
79
|
+
pattern: 'npm test:*',
|
|
80
|
+
scope: 'project',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const rules = await persistence.getRules('project');
|
|
84
|
+
expect(rules.length).toBe(1);
|
|
85
|
+
expect(rules[0].tool).toBe('Bash');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('removeRule', () => {
|
|
90
|
+
it('should remove a rule by ID', async () => {
|
|
91
|
+
const projectPermDir = path.join(projectDir, '.claude');
|
|
92
|
+
await fs.mkdir(projectPermDir, { recursive: true });
|
|
93
|
+
|
|
94
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
95
|
+
|
|
96
|
+
const rule = await persistence.addRule('Bash', 'auto', {
|
|
97
|
+
pattern: 'npm:*',
|
|
98
|
+
scope: 'project',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const removed = await persistence.removeRule(rule.id, 'project');
|
|
102
|
+
expect(removed).toBe(true);
|
|
103
|
+
|
|
104
|
+
const rules = await persistence.getRules('project');
|
|
105
|
+
expect(rules.length).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return false for non-existent rule', async () => {
|
|
109
|
+
const projectPermDir = path.join(projectDir, '.claude');
|
|
110
|
+
await fs.mkdir(projectPermDir, { recursive: true });
|
|
111
|
+
|
|
112
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
113
|
+
|
|
114
|
+
const removed = await persistence.removeRule('non-existent-id', 'project');
|
|
115
|
+
expect(removed).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('parseSettingsPermissions', () => {
|
|
120
|
+
it('should parse allow rules', () => {
|
|
121
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
122
|
+
|
|
123
|
+
const rules = persistence.parseSettingsPermissions({
|
|
124
|
+
allow: ['Bash(git add:*)', 'WebSearch'],
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(rules.length).toBe(2);
|
|
128
|
+
expect(rules[0].tool).toBe('Bash');
|
|
129
|
+
expect(rules[0].mode).toBe('auto');
|
|
130
|
+
expect(rules[0].pattern).toBe('git add:*');
|
|
131
|
+
expect(rules[1].tool).toBe('WebSearch');
|
|
132
|
+
expect(rules[1].mode).toBe('auto');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should parse ask rules', () => {
|
|
136
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
137
|
+
|
|
138
|
+
const rules = persistence.parseSettingsPermissions({
|
|
139
|
+
ask: ['Bash(npm run:*)'],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(rules.length).toBe(1);
|
|
143
|
+
expect(rules[0].tool).toBe('Bash');
|
|
144
|
+
expect(rules[0].mode).toBe('confirm');
|
|
145
|
+
expect(rules[0].pattern).toBe('npm run:*');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should parse deny rules', () => {
|
|
149
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
150
|
+
|
|
151
|
+
const rules = persistence.parseSettingsPermissions({
|
|
152
|
+
deny: ['Bash(rm -rf:*)'],
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(rules.length).toBe(1);
|
|
156
|
+
expect(rules[0].tool).toBe('Bash');
|
|
157
|
+
expect(rules[0].mode).toBe('deny');
|
|
158
|
+
expect(rules[0].pattern).toBe('rm -rf:*');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should parse mixed rules', () => {
|
|
162
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
163
|
+
|
|
164
|
+
const rules = persistence.parseSettingsPermissions({
|
|
165
|
+
allow: ['Bash(git:*)'],
|
|
166
|
+
ask: ['Bash(npm run:*)'],
|
|
167
|
+
deny: ['Bash(rm -rf:*)'],
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(rules.length).toBe(3);
|
|
171
|
+
|
|
172
|
+
const allowRule = rules.find(r => r.pattern === 'git:*');
|
|
173
|
+
const askRule = rules.find(r => r.pattern === 'npm run:*');
|
|
174
|
+
const denyRule = rules.find(r => r.pattern === 'rm -rf:*');
|
|
175
|
+
|
|
176
|
+
expect(allowRule?.mode).toBe('auto');
|
|
177
|
+
expect(askRule?.mode).toBe('confirm');
|
|
178
|
+
expect(denyRule?.mode).toBe('deny');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('persistedToRuntime', () => {
|
|
183
|
+
it('should convert persisted rules to runtime format', () => {
|
|
184
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
185
|
+
|
|
186
|
+
const persisted = [
|
|
187
|
+
{
|
|
188
|
+
id: 'test-id',
|
|
189
|
+
tool: 'Bash',
|
|
190
|
+
pattern: 'git:*',
|
|
191
|
+
mode: 'auto' as const,
|
|
192
|
+
scope: 'global' as const,
|
|
193
|
+
createdAt: new Date().toISOString(),
|
|
194
|
+
description: 'Test',
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
const runtime = persistence.persistedToRuntime(persisted);
|
|
199
|
+
|
|
200
|
+
expect(runtime.length).toBe(1);
|
|
201
|
+
expect(runtime[0].tool).toBe('Bash');
|
|
202
|
+
expect(runtime[0].mode).toBe('auto');
|
|
203
|
+
expect(runtime[0].pattern).toBe('git:*');
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('clearRules', () => {
|
|
208
|
+
it('should clear all rules for a scope', async () => {
|
|
209
|
+
const persistence = new PermissionPersistence(projectDir);
|
|
210
|
+
|
|
211
|
+
await persistence.addRule('Bash', 'auto', { scope: 'global' });
|
|
212
|
+
await persistence.addRule('WebSearch', 'auto', { scope: 'global' });
|
|
213
|
+
|
|
214
|
+
await persistence.clearRules('global');
|
|
215
|
+
|
|
216
|
+
const rules = await persistence.getRules('global');
|
|
217
|
+
expect(rules.length).toBe(0);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Persistence - Store and load permission rules
|
|
3
|
+
*
|
|
4
|
+
* Handles persistent storage of permission rules at:
|
|
5
|
+
* - Global: ~/.gencode/permissions.json
|
|
6
|
+
* - Project: .gencode/permissions.json
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'fs/promises';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import * as os from 'os';
|
|
12
|
+
import type {
|
|
13
|
+
PersistedPermissions,
|
|
14
|
+
PersistedRule,
|
|
15
|
+
PermissionRule,
|
|
16
|
+
PermissionScope,
|
|
17
|
+
PermissionMode,
|
|
18
|
+
PermissionSettings,
|
|
19
|
+
} from './types.js';
|
|
20
|
+
import { parsePatternString } from './prompt-matcher.js';
|
|
21
|
+
|
|
22
|
+
const PERMISSIONS_VERSION = 1;
|
|
23
|
+
const PERMISSIONS_FILE = 'permissions.json';
|
|
24
|
+
const GLOBAL_DIR = path.join(os.homedir(), '.gencode');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Permission Persistence Manager
|
|
28
|
+
*/
|
|
29
|
+
export class PermissionPersistence {
|
|
30
|
+
private globalDir: string;
|
|
31
|
+
private projectDir: string | null;
|
|
32
|
+
|
|
33
|
+
constructor(projectPath?: string) {
|
|
34
|
+
this.globalDir = GLOBAL_DIR;
|
|
35
|
+
this.projectDir = projectPath ? path.join(projectPath, '.gencode') : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generate unique rule ID
|
|
40
|
+
*/
|
|
41
|
+
private generateId(): string {
|
|
42
|
+
return `rule_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Ensure directory exists
|
|
47
|
+
*/
|
|
48
|
+
private async ensureDir(dir: string): Promise<void> {
|
|
49
|
+
try {
|
|
50
|
+
await fs.mkdir(dir, { recursive: true });
|
|
51
|
+
} catch {
|
|
52
|
+
// Directory may already exist
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get path for scope
|
|
58
|
+
*/
|
|
59
|
+
private getPath(scope: PermissionScope): string {
|
|
60
|
+
switch (scope) {
|
|
61
|
+
case 'global':
|
|
62
|
+
return path.join(this.globalDir, PERMISSIONS_FILE);
|
|
63
|
+
case 'project':
|
|
64
|
+
if (!this.projectDir) {
|
|
65
|
+
throw new Error('Project path not set for project-scoped permissions');
|
|
66
|
+
}
|
|
67
|
+
return path.join(this.projectDir, PERMISSIONS_FILE);
|
|
68
|
+
default:
|
|
69
|
+
// Session scope is not persisted
|
|
70
|
+
throw new Error('Session-scoped permissions are not persisted');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Load permissions from file
|
|
76
|
+
*/
|
|
77
|
+
async load(scope: PermissionScope = 'global'): Promise<PersistedPermissions> {
|
|
78
|
+
try {
|
|
79
|
+
const filePath = this.getPath(scope);
|
|
80
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
81
|
+
const data = JSON.parse(content) as PersistedPermissions;
|
|
82
|
+
|
|
83
|
+
// Migration: add version if missing
|
|
84
|
+
if (!data.version) {
|
|
85
|
+
data.version = PERMISSIONS_VERSION;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return data;
|
|
89
|
+
} catch {
|
|
90
|
+
// Return empty permissions
|
|
91
|
+
return { version: PERMISSIONS_VERSION, rules: [] };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Save permissions to file
|
|
97
|
+
*/
|
|
98
|
+
async save(permissions: PersistedPermissions, scope: PermissionScope = 'global'): Promise<void> {
|
|
99
|
+
const dir = scope === 'global' ? this.globalDir : this.projectDir;
|
|
100
|
+
if (!dir) {
|
|
101
|
+
throw new Error('Project path not set for project-scoped permissions');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await this.ensureDir(dir);
|
|
105
|
+
const filePath = this.getPath(scope);
|
|
106
|
+
|
|
107
|
+
await fs.writeFile(
|
|
108
|
+
filePath,
|
|
109
|
+
JSON.stringify(permissions, null, 2),
|
|
110
|
+
'utf-8'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Add a new permission rule
|
|
116
|
+
*/
|
|
117
|
+
async addRule(
|
|
118
|
+
tool: string,
|
|
119
|
+
mode: PermissionMode,
|
|
120
|
+
options: {
|
|
121
|
+
pattern?: string;
|
|
122
|
+
scope?: PermissionScope;
|
|
123
|
+
description?: string;
|
|
124
|
+
} = {}
|
|
125
|
+
): Promise<PersistedRule> {
|
|
126
|
+
const scope = options.scope ?? 'global';
|
|
127
|
+
|
|
128
|
+
// Session rules are not persisted
|
|
129
|
+
if (scope === 'session') {
|
|
130
|
+
throw new Error('Session-scoped rules cannot be persisted');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const permissions = await this.load(scope);
|
|
134
|
+
|
|
135
|
+
const rule: PersistedRule = {
|
|
136
|
+
id: this.generateId(),
|
|
137
|
+
tool,
|
|
138
|
+
pattern: options.pattern,
|
|
139
|
+
mode,
|
|
140
|
+
scope,
|
|
141
|
+
createdAt: new Date().toISOString(),
|
|
142
|
+
description: options.description,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
permissions.rules.push(rule);
|
|
146
|
+
await this.save(permissions, scope);
|
|
147
|
+
|
|
148
|
+
return rule;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Remove a permission rule by ID
|
|
153
|
+
*/
|
|
154
|
+
async removeRule(ruleId: string, scope: PermissionScope = 'global'): Promise<boolean> {
|
|
155
|
+
if (scope === 'session') {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const permissions = await this.load(scope);
|
|
160
|
+
const initialLength = permissions.rules.length;
|
|
161
|
+
permissions.rules = permissions.rules.filter((r) => r.id !== ruleId);
|
|
162
|
+
|
|
163
|
+
if (permissions.rules.length < initialLength) {
|
|
164
|
+
await this.save(permissions, scope);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get all persisted rules for a scope
|
|
173
|
+
*/
|
|
174
|
+
async getRules(scope: PermissionScope = 'global'): Promise<PersistedRule[]> {
|
|
175
|
+
if (scope === 'session') {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const permissions = await this.load(scope);
|
|
180
|
+
return permissions.rules;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get all rules (global + project)
|
|
185
|
+
*/
|
|
186
|
+
async getAllRules(): Promise<PersistedRule[]> {
|
|
187
|
+
const globalRules = await this.getRules('global');
|
|
188
|
+
let projectRules: PersistedRule[] = [];
|
|
189
|
+
|
|
190
|
+
if (this.projectDir) {
|
|
191
|
+
try {
|
|
192
|
+
projectRules = await this.getRules('project');
|
|
193
|
+
} catch {
|
|
194
|
+
// Project permissions file may not exist
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return [...projectRules, ...globalRules];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Convert persisted rules to runtime permission rules
|
|
203
|
+
*/
|
|
204
|
+
persistedToRuntime(persisted: PersistedRule[]): PermissionRule[] {
|
|
205
|
+
return persisted.map((p) => ({
|
|
206
|
+
tool: p.tool,
|
|
207
|
+
mode: p.mode,
|
|
208
|
+
pattern: p.pattern,
|
|
209
|
+
scope: p.scope,
|
|
210
|
+
description: p.description,
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Parse settings-style permissions (from settings.json)
|
|
216
|
+
* Claude Code format: { allow: ["Bash(git add:*)"], ask: ["Bash(npm run:*)"], deny: ["Bash(rm -rf:*)"] }
|
|
217
|
+
*/
|
|
218
|
+
parseSettingsPermissions(settings: PermissionSettings): PermissionRule[] {
|
|
219
|
+
const rules: PermissionRule[] = [];
|
|
220
|
+
|
|
221
|
+
// Parse allow rules (auto-approve)
|
|
222
|
+
if (settings.allow) {
|
|
223
|
+
for (const pattern of settings.allow) {
|
|
224
|
+
const parsed = parsePatternString(pattern);
|
|
225
|
+
if (parsed) {
|
|
226
|
+
rules.push({
|
|
227
|
+
tool: parsed.tool,
|
|
228
|
+
mode: 'auto',
|
|
229
|
+
pattern: parsed.pattern,
|
|
230
|
+
scope: 'global',
|
|
231
|
+
description: `Settings: ${pattern}`,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Parse ask rules (require confirmation)
|
|
238
|
+
if (settings.ask) {
|
|
239
|
+
for (const pattern of settings.ask) {
|
|
240
|
+
const parsed = parsePatternString(pattern);
|
|
241
|
+
if (parsed) {
|
|
242
|
+
rules.push({
|
|
243
|
+
tool: parsed.tool,
|
|
244
|
+
mode: 'confirm',
|
|
245
|
+
pattern: parsed.pattern,
|
|
246
|
+
scope: 'global',
|
|
247
|
+
description: `Settings: ${pattern}`,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Parse deny rules (block)
|
|
254
|
+
if (settings.deny) {
|
|
255
|
+
for (const pattern of settings.deny) {
|
|
256
|
+
const parsed = parsePatternString(pattern);
|
|
257
|
+
if (parsed) {
|
|
258
|
+
rules.push({
|
|
259
|
+
tool: parsed.tool,
|
|
260
|
+
mode: 'deny',
|
|
261
|
+
pattern: parsed.pattern,
|
|
262
|
+
scope: 'global',
|
|
263
|
+
description: `Settings: ${pattern}`,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return rules;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Clear all rules for a scope
|
|
274
|
+
*/
|
|
275
|
+
async clearRules(scope: PermissionScope = 'global'): Promise<void> {
|
|
276
|
+
if (scope === 'session') {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const permissions = await this.load(scope);
|
|
281
|
+
permissions.rules = [];
|
|
282
|
+
await this.save(permissions, scope);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Check if permissions file exists
|
|
287
|
+
*/
|
|
288
|
+
async exists(scope: PermissionScope = 'global'): Promise<boolean> {
|
|
289
|
+
if (scope === 'session') {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const filePath = this.getPath(scope);
|
|
295
|
+
await fs.access(filePath);
|
|
296
|
+
return true;
|
|
297
|
+
} catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|