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,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Levels Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
|
9
|
+
import {
|
|
10
|
+
findProjectRoot,
|
|
11
|
+
parseExtraConfigDirs,
|
|
12
|
+
getConfigLevels,
|
|
13
|
+
getPrimarySettingsDir,
|
|
14
|
+
getSettingsFilePath,
|
|
15
|
+
} from './levels.js';
|
|
16
|
+
import { createTestProject, type TestProject } from './test-utils.js';
|
|
17
|
+
|
|
18
|
+
describe('findProjectRoot', () => {
|
|
19
|
+
let test: TestProject;
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
test = await createTestProject('gencode-levels-');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => test.cleanup());
|
|
26
|
+
|
|
27
|
+
it('should find git root', async () => {
|
|
28
|
+
const subDir = path.join(test.projectDir, 'src', 'components');
|
|
29
|
+
await fs.mkdir(subDir, { recursive: true });
|
|
30
|
+
|
|
31
|
+
expect(await findProjectRoot(subDir)).toBe(test.projectDir);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should find .gencode directory as project root', async () => {
|
|
35
|
+
// Remove .git, add .gencode
|
|
36
|
+
await fs.rm(path.join(test.projectDir, '.git'), { recursive: true });
|
|
37
|
+
await fs.mkdir(path.join(test.projectDir, '.gencode'));
|
|
38
|
+
const subDir = path.join(test.projectDir, 'src');
|
|
39
|
+
await fs.mkdir(subDir, { recursive: true });
|
|
40
|
+
|
|
41
|
+
expect(await findProjectRoot(subDir)).toBe(test.projectDir);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should find .claude directory as project root', async () => {
|
|
45
|
+
await fs.rm(path.join(test.projectDir, '.git'), { recursive: true });
|
|
46
|
+
await fs.mkdir(path.join(test.projectDir, '.claude'));
|
|
47
|
+
const subDir = path.join(test.projectDir, 'lib');
|
|
48
|
+
await fs.mkdir(subDir, { recursive: true });
|
|
49
|
+
|
|
50
|
+
expect(await findProjectRoot(subDir)).toBe(test.projectDir);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should return cwd if no project markers found', async () => {
|
|
54
|
+
const subDir = path.join(test.tempDir, 'random', 'path');
|
|
55
|
+
await fs.mkdir(subDir, { recursive: true });
|
|
56
|
+
|
|
57
|
+
expect(await findProjectRoot(subDir)).toBe(subDir);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('parseExtraConfigDirs', () => {
|
|
62
|
+
const originalEnv = process.env.GENCODE_CONFIG_DIRS;
|
|
63
|
+
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
if (originalEnv === undefined) {
|
|
66
|
+
delete process.env.GENCODE_CONFIG_DIRS;
|
|
67
|
+
} else {
|
|
68
|
+
process.env.GENCODE_CONFIG_DIRS = originalEnv;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return empty array when env var not set', () => {
|
|
73
|
+
delete process.env.GENCODE_CONFIG_DIRS;
|
|
74
|
+
expect(parseExtraConfigDirs()).toEqual([]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should parse single directory', () => {
|
|
78
|
+
process.env.GENCODE_CONFIG_DIRS = '/team/config';
|
|
79
|
+
expect(parseExtraConfigDirs()).toEqual(['/team/config']);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should parse multiple directories', () => {
|
|
83
|
+
process.env.GENCODE_CONFIG_DIRS = '/team/config:/shared/rules';
|
|
84
|
+
expect(parseExtraConfigDirs()).toEqual(['/team/config', '/shared/rules']);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should expand tilde to home directory', () => {
|
|
88
|
+
process.env.GENCODE_CONFIG_DIRS = '~/my-config';
|
|
89
|
+
expect(parseExtraConfigDirs()[0]).toBe(path.join(os.homedir(), 'my-config'));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should trim whitespace and filter empty strings', () => {
|
|
93
|
+
process.env.GENCODE_CONFIG_DIRS = ' /path/one : : /path/two ';
|
|
94
|
+
expect(parseExtraConfigDirs()).toEqual(['/path/one', '/path/two']);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('getConfigLevels', () => {
|
|
99
|
+
let test: TestProject;
|
|
100
|
+
|
|
101
|
+
beforeEach(async () => {
|
|
102
|
+
test = await createTestProject('gencode-levels-');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
afterEach(() => test.cleanup());
|
|
106
|
+
|
|
107
|
+
it('should return levels in priority order', async () => {
|
|
108
|
+
const types = (await getConfigLevels(test.projectDir)).map((l) => l.type);
|
|
109
|
+
|
|
110
|
+
expect(types.indexOf('user')).toBeLessThan(types.indexOf('project'));
|
|
111
|
+
expect(types.indexOf('project')).toBeLessThan(types.indexOf('local'));
|
|
112
|
+
expect(types.indexOf('local')).toBeLessThan(types.indexOf('managed'));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should include both claude and gencode paths at each level', async () => {
|
|
116
|
+
const userLevel = (await getConfigLevels(test.projectDir)).find((l) => l.type === 'user');
|
|
117
|
+
|
|
118
|
+
expect(userLevel?.paths.length).toBe(2);
|
|
119
|
+
expect(userLevel?.paths.some((p) => p.namespace === 'claude')).toBe(true);
|
|
120
|
+
expect(userLevel?.paths.some((p) => p.namespace === 'gencode')).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should include extra dirs when env var is set', async () => {
|
|
124
|
+
process.env.GENCODE_CONFIG_DIRS = '/team/config';
|
|
125
|
+
const extraLevels = (await getConfigLevels(test.projectDir)).filter((l) => l.type === 'extra');
|
|
126
|
+
|
|
127
|
+
expect(extraLevels.length).toBeGreaterThan(0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should have claude before gencode in each level (for merge order)', async () => {
|
|
131
|
+
for (const level of await getConfigLevels(test.projectDir)) {
|
|
132
|
+
if (level.paths.length >= 2) {
|
|
133
|
+
const claudeIdx = level.paths.findIndex((p) => p.namespace === 'claude');
|
|
134
|
+
const gencodeIdx = level.paths.findIndex((p) => p.namespace === 'gencode');
|
|
135
|
+
if (claudeIdx !== -1 && gencodeIdx !== -1) {
|
|
136
|
+
expect(claudeIdx).toBeLessThan(gencodeIdx);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('getPrimarySettingsDir', () => {
|
|
144
|
+
it('should return ~/.gencode for user level', () => {
|
|
145
|
+
expect(getPrimarySettingsDir('user', '/project')).toBe(path.join(os.homedir(), '.gencode'));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should return project/.gencode for project and local levels', () => {
|
|
149
|
+
expect(getPrimarySettingsDir('project', '/my/project')).toBe('/my/project/.gencode');
|
|
150
|
+
expect(getPrimarySettingsDir('local', '/my/project')).toBe('/my/project/.gencode');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('getSettingsFilePath', () => {
|
|
155
|
+
it('should return correct paths for each level', () => {
|
|
156
|
+
expect(getSettingsFilePath('user', '/project'))
|
|
157
|
+
.toBe(path.join(os.homedir(), '.gencode', 'settings.json'));
|
|
158
|
+
expect(getSettingsFilePath('project', '/my/project'))
|
|
159
|
+
.toBe('/my/project/.gencode/settings.json');
|
|
160
|
+
expect(getSettingsFilePath('local', '/my/project'))
|
|
161
|
+
.toBe('/my/project/.gencode/settings.local.json');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Levels - Path resolution for multi-level config
|
|
3
|
+
*
|
|
4
|
+
* Defines the configuration hierarchy and resolves paths for each level.
|
|
5
|
+
* At each level, both .gencode and .claude directories are loaded and merged,
|
|
6
|
+
* with .gencode taking higher priority.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import * as os from 'os';
|
|
11
|
+
import * as fs from 'fs/promises';
|
|
12
|
+
import {
|
|
13
|
+
ConfigLevelType,
|
|
14
|
+
GENCODE_DIR,
|
|
15
|
+
CLAUDE_DIR,
|
|
16
|
+
SETTINGS_FILE_NAME,
|
|
17
|
+
SETTINGS_LOCAL_FILE_NAME,
|
|
18
|
+
MANAGED_SETTINGS_FILE_NAME,
|
|
19
|
+
GENCODE_CONFIG_DIRS_ENV,
|
|
20
|
+
getManagedPaths,
|
|
21
|
+
} from './types.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration path info for a specific namespace
|
|
25
|
+
*/
|
|
26
|
+
export interface ConfigPathInfo {
|
|
27
|
+
settingsPath: string;
|
|
28
|
+
localSettingsPath?: string;
|
|
29
|
+
dir: string;
|
|
30
|
+
namespace: 'gencode' | 'claude' | 'extra';
|
|
31
|
+
exists: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configuration level with resolved paths
|
|
36
|
+
*/
|
|
37
|
+
export interface ResolvedLevel {
|
|
38
|
+
type: ConfigLevelType;
|
|
39
|
+
priority: number;
|
|
40
|
+
paths: ConfigPathInfo[];
|
|
41
|
+
description: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Find the project root by walking up the directory tree
|
|
46
|
+
*/
|
|
47
|
+
export async function findProjectRoot(cwd: string): Promise<string> {
|
|
48
|
+
let current = path.resolve(cwd);
|
|
49
|
+
const root = path.parse(current).root;
|
|
50
|
+
|
|
51
|
+
while (current !== root) {
|
|
52
|
+
// Check for git directory
|
|
53
|
+
try {
|
|
54
|
+
await fs.access(path.join(current, '.git'));
|
|
55
|
+
return current;
|
|
56
|
+
} catch {
|
|
57
|
+
// Not a git root, continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check for .gencode or .claude directories
|
|
61
|
+
try {
|
|
62
|
+
await fs.access(path.join(current, GENCODE_DIR));
|
|
63
|
+
return current;
|
|
64
|
+
} catch {
|
|
65
|
+
// Continue
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await fs.access(path.join(current, CLAUDE_DIR));
|
|
70
|
+
return current;
|
|
71
|
+
} catch {
|
|
72
|
+
// Continue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const parent = path.dirname(current);
|
|
76
|
+
if (parent === current) break;
|
|
77
|
+
current = parent;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return cwd;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if a path exists
|
|
85
|
+
*/
|
|
86
|
+
async function pathExists(filePath: string): Promise<boolean> {
|
|
87
|
+
try {
|
|
88
|
+
await fs.access(filePath);
|
|
89
|
+
return true;
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Parse GENCODE_CONFIG_DIRS environment variable
|
|
97
|
+
*/
|
|
98
|
+
export function parseExtraConfigDirs(): string[] {
|
|
99
|
+
const value = process.env[GENCODE_CONFIG_DIRS_ENV];
|
|
100
|
+
if (!value) return [];
|
|
101
|
+
|
|
102
|
+
return value
|
|
103
|
+
.split(':')
|
|
104
|
+
.map((dir) => dir.trim())
|
|
105
|
+
.filter((dir) => dir.length > 0)
|
|
106
|
+
.map((dir) => dir.replace(/^~/, os.homedir()));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get all configuration levels with resolved paths
|
|
111
|
+
*/
|
|
112
|
+
export async function getConfigLevels(cwd: string): Promise<ResolvedLevel[]> {
|
|
113
|
+
const projectRoot = await findProjectRoot(cwd);
|
|
114
|
+
const home = os.homedir();
|
|
115
|
+
const managedPaths = getManagedPaths();
|
|
116
|
+
const extraDirs = parseExtraConfigDirs();
|
|
117
|
+
|
|
118
|
+
const levels: ResolvedLevel[] = [];
|
|
119
|
+
|
|
120
|
+
// Level 1: User (lowest priority for settings, loaded first)
|
|
121
|
+
const userPaths: ConfigPathInfo[] = [];
|
|
122
|
+
|
|
123
|
+
// Claude first (lower priority within level)
|
|
124
|
+
const userClaudeDir = path.join(home, CLAUDE_DIR);
|
|
125
|
+
userPaths.push({
|
|
126
|
+
settingsPath: path.join(userClaudeDir, SETTINGS_FILE_NAME),
|
|
127
|
+
dir: userClaudeDir,
|
|
128
|
+
namespace: 'claude',
|
|
129
|
+
exists: await pathExists(path.join(userClaudeDir, SETTINGS_FILE_NAME)),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// GenCode second (higher priority within level)
|
|
133
|
+
const userGencodeDir = path.join(home, GENCODE_DIR);
|
|
134
|
+
userPaths.push({
|
|
135
|
+
settingsPath: path.join(userGencodeDir, SETTINGS_FILE_NAME),
|
|
136
|
+
dir: userGencodeDir,
|
|
137
|
+
namespace: 'gencode',
|
|
138
|
+
exists: await pathExists(path.join(userGencodeDir, SETTINGS_FILE_NAME)),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
levels.push({
|
|
142
|
+
type: 'user',
|
|
143
|
+
priority: 10,
|
|
144
|
+
paths: userPaths,
|
|
145
|
+
description: 'User global settings',
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Level 2: Extra config dirs from environment variable
|
|
149
|
+
if (extraDirs.length > 0) {
|
|
150
|
+
for (let i = 0; i < extraDirs.length; i++) {
|
|
151
|
+
const dir = extraDirs[i];
|
|
152
|
+
const extraPaths: ConfigPathInfo[] = [
|
|
153
|
+
{
|
|
154
|
+
settingsPath: path.join(dir, SETTINGS_FILE_NAME),
|
|
155
|
+
dir,
|
|
156
|
+
namespace: 'extra',
|
|
157
|
+
exists: await pathExists(path.join(dir, SETTINGS_FILE_NAME)),
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
levels.push({
|
|
162
|
+
type: 'extra',
|
|
163
|
+
priority: 20 + i, // Each extra dir has slightly higher priority
|
|
164
|
+
paths: extraPaths,
|
|
165
|
+
description: `Extra config from ${dir}`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Level 3: Project (shared, committed to git)
|
|
171
|
+
const projectPaths: ConfigPathInfo[] = [];
|
|
172
|
+
|
|
173
|
+
// Claude first (lower priority within level)
|
|
174
|
+
const projectClaudeDir = path.join(projectRoot, CLAUDE_DIR);
|
|
175
|
+
projectPaths.push({
|
|
176
|
+
settingsPath: path.join(projectClaudeDir, SETTINGS_FILE_NAME),
|
|
177
|
+
dir: projectClaudeDir,
|
|
178
|
+
namespace: 'claude',
|
|
179
|
+
exists: await pathExists(path.join(projectClaudeDir, SETTINGS_FILE_NAME)),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// GenCode second (higher priority within level)
|
|
183
|
+
const projectGencodeDir = path.join(projectRoot, GENCODE_DIR);
|
|
184
|
+
projectPaths.push({
|
|
185
|
+
settingsPath: path.join(projectGencodeDir, SETTINGS_FILE_NAME),
|
|
186
|
+
dir: projectGencodeDir,
|
|
187
|
+
namespace: 'gencode',
|
|
188
|
+
exists: await pathExists(path.join(projectGencodeDir, SETTINGS_FILE_NAME)),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
levels.push({
|
|
192
|
+
type: 'project',
|
|
193
|
+
priority: 30,
|
|
194
|
+
paths: projectPaths,
|
|
195
|
+
description: 'Project shared settings',
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Level 4: Local (personal, gitignored)
|
|
199
|
+
const localPaths: ConfigPathInfo[] = [];
|
|
200
|
+
|
|
201
|
+
// Claude first (lower priority within level)
|
|
202
|
+
localPaths.push({
|
|
203
|
+
settingsPath: path.join(projectClaudeDir, SETTINGS_LOCAL_FILE_NAME),
|
|
204
|
+
dir: projectClaudeDir,
|
|
205
|
+
namespace: 'claude',
|
|
206
|
+
exists: await pathExists(path.join(projectClaudeDir, SETTINGS_LOCAL_FILE_NAME)),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// GenCode second (higher priority within level)
|
|
210
|
+
localPaths.push({
|
|
211
|
+
settingsPath: path.join(projectGencodeDir, SETTINGS_LOCAL_FILE_NAME),
|
|
212
|
+
dir: projectGencodeDir,
|
|
213
|
+
namespace: 'gencode',
|
|
214
|
+
exists: await pathExists(path.join(projectGencodeDir, SETTINGS_LOCAL_FILE_NAME)),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
levels.push({
|
|
218
|
+
type: 'local',
|
|
219
|
+
priority: 40,
|
|
220
|
+
paths: localPaths,
|
|
221
|
+
description: 'Local personal settings (gitignored)',
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Level 5: Managed (highest priority, enforced)
|
|
225
|
+
const managedPathsList: ConfigPathInfo[] = [];
|
|
226
|
+
|
|
227
|
+
// Claude first (lower priority within level)
|
|
228
|
+
managedPathsList.push({
|
|
229
|
+
settingsPath: path.join(managedPaths.claude, MANAGED_SETTINGS_FILE_NAME),
|
|
230
|
+
dir: managedPaths.claude,
|
|
231
|
+
namespace: 'claude',
|
|
232
|
+
exists: await pathExists(path.join(managedPaths.claude, MANAGED_SETTINGS_FILE_NAME)),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// GenCode second (higher priority within level)
|
|
236
|
+
managedPathsList.push({
|
|
237
|
+
settingsPath: path.join(managedPaths.gencode, MANAGED_SETTINGS_FILE_NAME),
|
|
238
|
+
dir: managedPaths.gencode,
|
|
239
|
+
namespace: 'gencode',
|
|
240
|
+
exists: await pathExists(path.join(managedPaths.gencode, MANAGED_SETTINGS_FILE_NAME)),
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
levels.push({
|
|
244
|
+
type: 'managed',
|
|
245
|
+
priority: 100, // Highest priority
|
|
246
|
+
paths: managedPathsList,
|
|
247
|
+
description: 'System-wide managed settings (enforced)',
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Sort by priority (ascending, so we merge from low to high)
|
|
251
|
+
return levels.sort((a, b) => a.priority - b.priority);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get the primary settings directory for saving
|
|
256
|
+
* Prefers .gencode if it exists, otherwise creates it
|
|
257
|
+
*/
|
|
258
|
+
export function getPrimarySettingsDir(
|
|
259
|
+
level: 'user' | 'project' | 'local',
|
|
260
|
+
projectRoot: string
|
|
261
|
+
): string {
|
|
262
|
+
const home = os.homedir();
|
|
263
|
+
|
|
264
|
+
switch (level) {
|
|
265
|
+
case 'user':
|
|
266
|
+
return path.join(home, GENCODE_DIR);
|
|
267
|
+
case 'project':
|
|
268
|
+
case 'local':
|
|
269
|
+
return path.join(projectRoot, GENCODE_DIR);
|
|
270
|
+
default:
|
|
271
|
+
return path.join(home, GENCODE_DIR);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get the settings file path for a specific level
|
|
277
|
+
*/
|
|
278
|
+
export function getSettingsFilePath(
|
|
279
|
+
level: 'user' | 'project' | 'local',
|
|
280
|
+
projectRoot: string
|
|
281
|
+
): string {
|
|
282
|
+
const dir = getPrimarySettingsDir(level, projectRoot);
|
|
283
|
+
const fileName = level === 'local' ? SETTINGS_LOCAL_FILE_NAME : SETTINGS_FILE_NAME;
|
|
284
|
+
return path.join(dir, fileName);
|
|
285
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Loader Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
|
8
|
+
import {
|
|
9
|
+
loadAllSources,
|
|
10
|
+
loadSourcesByLevel,
|
|
11
|
+
loadProjectSettings,
|
|
12
|
+
getExistingConfigFiles,
|
|
13
|
+
} from './loader.js';
|
|
14
|
+
import { createTestProject, writeSettings, type TestProject } from './test-utils.js';
|
|
15
|
+
|
|
16
|
+
describe('Config Loader', () => {
|
|
17
|
+
let test: TestProject;
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
test = await createTestProject('gencode-loader-');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => test.cleanup());
|
|
24
|
+
|
|
25
|
+
describe('loadAllSources', () => {
|
|
26
|
+
it('should load settings from project .claude directory', async () => {
|
|
27
|
+
await writeSettings(test.projectDir, 'claude', { provider: 'openai' });
|
|
28
|
+
const sources = await loadAllSources(test.projectDir);
|
|
29
|
+
|
|
30
|
+
const source = sources.find((s) => s.level === 'project' && s.namespace === 'claude');
|
|
31
|
+
expect(source?.settings.provider).toBe('openai');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should load settings from project .gencode directory', async () => {
|
|
35
|
+
await writeSettings(test.projectDir, 'gencode', { provider: 'anthropic' });
|
|
36
|
+
const sources = await loadAllSources(test.projectDir);
|
|
37
|
+
|
|
38
|
+
const source = sources.find((s) => s.level === 'project' && s.namespace === 'gencode');
|
|
39
|
+
expect(source?.settings.provider).toBe('anthropic');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should load both .claude and .gencode settings at same level', async () => {
|
|
43
|
+
await writeSettings(test.projectDir, 'claude', { provider: 'openai', model: 'gpt-4' });
|
|
44
|
+
await writeSettings(test.projectDir, 'gencode', { provider: 'anthropic' });
|
|
45
|
+
|
|
46
|
+
const sources = await loadAllSources(test.projectDir);
|
|
47
|
+
const claude = sources.find((s) => s.level === 'project' && s.namespace === 'claude');
|
|
48
|
+
const gencode = sources.find((s) => s.level === 'project' && s.namespace === 'gencode');
|
|
49
|
+
|
|
50
|
+
expect(claude?.settings.provider).toBe('openai');
|
|
51
|
+
expect(gencode?.settings.provider).toBe('anthropic');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should load local settings', async () => {
|
|
55
|
+
await writeSettings(test.projectDir, 'gencode', { alwaysThinkingEnabled: true }, true);
|
|
56
|
+
const sources = await loadAllSources(test.projectDir);
|
|
57
|
+
|
|
58
|
+
const local = sources.find((s) => s.level === 'local');
|
|
59
|
+
expect(local?.settings.alwaysThinkingEnabled).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should load from extra config dirs', async () => {
|
|
63
|
+
const extraDir = path.join(test.tempDir, 'extra-config');
|
|
64
|
+
await fs.mkdir(extraDir, { recursive: true });
|
|
65
|
+
await fs.writeFile(path.join(extraDir, 'settings.json'), JSON.stringify({ theme: 'dark' }));
|
|
66
|
+
process.env.GENCODE_CONFIG_DIRS = extraDir;
|
|
67
|
+
|
|
68
|
+
const sources = await loadAllSources(test.projectDir);
|
|
69
|
+
const extra = sources.find((s) => s.level === 'extra');
|
|
70
|
+
expect(extra?.settings.theme).toBe('dark');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('loadSourcesByLevel', () => {
|
|
75
|
+
it('should group sources by level', async () => {
|
|
76
|
+
await writeSettings(test.projectDir, 'claude', { model: 'gpt-4' });
|
|
77
|
+
await writeSettings(test.projectDir, 'gencode', { model: 'claude-sonnet' });
|
|
78
|
+
|
|
79
|
+
const sourcesByLevel = await loadSourcesByLevel(test.projectDir);
|
|
80
|
+
|
|
81
|
+
expect(sourcesByLevel.has('project')).toBe(true);
|
|
82
|
+
expect(sourcesByLevel.get('project')?.length).toBe(2);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('loadProjectSettings', () => {
|
|
87
|
+
it('should only load project-level settings', async () => {
|
|
88
|
+
await writeSettings(test.projectDir, 'gencode', { model: 'claude-sonnet' });
|
|
89
|
+
await writeSettings(test.projectDir, 'gencode', { theme: 'dark' }, true);
|
|
90
|
+
|
|
91
|
+
const sources = await loadProjectSettings(test.projectDir);
|
|
92
|
+
|
|
93
|
+
expect(sources.length).toBe(1);
|
|
94
|
+
expect(sources[0].level).toBe('project');
|
|
95
|
+
expect(sources[0].settings.model).toBe('claude-sonnet');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('getExistingConfigFiles', () => {
|
|
100
|
+
it('should list all existing config files', async () => {
|
|
101
|
+
await writeSettings(test.projectDir, 'claude', {});
|
|
102
|
+
await writeSettings(test.projectDir, 'gencode', {});
|
|
103
|
+
await writeSettings(test.projectDir, 'gencode', {}, true);
|
|
104
|
+
|
|
105
|
+
const files = await getExistingConfigFiles(test.projectDir);
|
|
106
|
+
|
|
107
|
+
expect(files.length).toBeGreaterThanOrEqual(3);
|
|
108
|
+
expect(files.some((f) => f.includes('.claude/settings.json'))).toBe(true);
|
|
109
|
+
expect(files.some((f) => f.includes('.gencode/settings.json'))).toBe(true);
|
|
110
|
+
expect(files.some((f) => f.includes('settings.local.json'))).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should return array when no config files exist', async () => {
|
|
114
|
+
const emptyDir = path.join(test.tempDir, 'empty');
|
|
115
|
+
await fs.mkdir(emptyDir, { recursive: true });
|
|
116
|
+
|
|
117
|
+
expect(Array.isArray(await getExistingConfigFiles(emptyDir))).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|