codemini-cli 0.5.10 → 0.5.12

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.
Files changed (59) hide show
  1. package/OPERATIONS.md +242 -242
  2. package/README.md +588 -588
  3. package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-7HL7yft8.js → highlighted-body-OFNGDK62-B-G99D0A.js} +1 -1
  4. package/codemini-web/dist/assets/{index-BK75hMb2.js → index-DIGUEzan.js} +108 -108
  5. package/codemini-web/dist/assets/index-Dkq1DdDX.css +2 -0
  6. package/codemini-web/dist/assets/mermaid-GHXKKRXX-va2Kl89u.js +1 -0
  7. package/codemini-web/dist/index.html +35 -23
  8. package/codemini-web/lib/approval-manager.js +32 -32
  9. package/codemini-web/lib/runtime-bridge.js +17 -11
  10. package/codemini-web/server.js +534 -205
  11. package/deployment.md +212 -212
  12. package/package.json +2 -2
  13. package/skills/brainstorm/SKILL.md +77 -77
  14. package/skills/codemini.skills.json +40 -40
  15. package/skills/grill-me/SKILL.md +30 -30
  16. package/skills/superpowers-lite/SKILL.md +82 -82
  17. package/src/cli.js +74 -74
  18. package/src/commands/chat.js +210 -210
  19. package/src/commands/run.js +313 -313
  20. package/src/commands/skill.js +438 -304
  21. package/src/commands/web.js +57 -57
  22. package/src/core/agent-loop.js +980 -980
  23. package/src/core/ast.js +309 -307
  24. package/src/core/chat-runtime.js +6261 -6253
  25. package/src/core/command-evaluator.js +72 -72
  26. package/src/core/command-loader.js +311 -311
  27. package/src/core/command-policy.js +301 -301
  28. package/src/core/command-risk.js +156 -156
  29. package/src/core/config-store.js +286 -285
  30. package/src/core/constants.js +18 -1
  31. package/src/core/context-compact.js +365 -365
  32. package/src/core/default-system-prompt.js +114 -107
  33. package/src/core/dream-audit.js +105 -105
  34. package/src/core/dream-consolidate.js +229 -229
  35. package/src/core/dream-evaluator.js +185 -185
  36. package/src/core/fff-adapter.js +383 -383
  37. package/src/core/memory-store.js +543 -543
  38. package/src/core/project-index.js +737 -548
  39. package/src/core/project-instructions.js +98 -98
  40. package/src/core/provider/anthropic.js +514 -514
  41. package/src/core/provider/openai-compatible.js +501 -501
  42. package/src/core/reflect-skill.js +178 -178
  43. package/src/core/reply-language.js +40 -40
  44. package/src/core/session-store.js +474 -474
  45. package/src/core/shell-profile.js +237 -237
  46. package/src/core/shell.js +323 -323
  47. package/src/core/soul.js +69 -69
  48. package/src/core/system-prompt-composer.js +52 -52
  49. package/src/core/tool-args.js +199 -154
  50. package/src/core/tool-output.js +184 -184
  51. package/src/core/tool-result-store.js +206 -206
  52. package/src/core/tools.js +3024 -2893
  53. package/src/core/version.js +11 -11
  54. package/src/tui/chat-app.js +5173 -5171
  55. package/src/tui/tool-activity/presenters/misc.js +30 -30
  56. package/src/tui/tool-activity/presenters/system.js +20 -20
  57. package/templates/project-requirements/report-shell.html +582 -582
  58. package/codemini-web/dist/assets/index-BSdIdn3L.css +0 -2
  59. package/codemini-web/dist/assets/mermaid-GHXKKRXX-Dg9qh8mg.js +0 -1
@@ -1,289 +1,290 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { getConfigFilePath, getLegacyConfigDir } from './paths.js';
4
- import { normalizeReplyLanguage } from './reply-language.js';
5
- import { normalizeShellName } from './shell-profile.js';
6
-
7
- function normalizeUiLanguage(value) {
8
- const raw = String(value || '').trim().toLowerCase();
9
- if (!raw) return 'zh';
10
- if (['en', 'en-us', 'en_us', 'english'].includes(raw)) return 'en';
11
- if (['zh', 'zh-cn', 'zh_cn', 'cn', 'chinese'].includes(raw)) return 'zh';
12
- return 'zh';
13
- }
14
-
15
- const DEFAULT_CONFIG = {
16
- sdk: {
17
- provider: 'openai-compatible'
18
- },
19
- gateway: {
20
- base_url: 'http://127.0.0.1:8000/v1',
21
- api_key: '',
22
- timeout_ms: 1800000,
23
- max_retries: 2
24
- },
25
- model: {
26
- name: 'gpt-4.1-mini',
27
- fast_name: '',
28
- max_context_tokens: 202752
29
- },
30
- context: {
31
- max_tokens: 32000,
32
- preflight_trigger_pct: 60,
33
- hard_limit_pct: 98,
34
- tool_result_max_chars: 12000,
35
- read_file_default_lines: 220,
36
- read_file_max_chars: 24000,
37
- prompt_budget_audit: false,
38
- microcompact_enabled: true,
39
- microcompact_keep_recent: 5,
40
- project_instructions_enabled: true,
41
- project_instructions_max_chars: 12000
42
- },
43
- execution: {
44
- mode: 'normal',
45
- always_allow_tools: [
46
- 'read',
47
- 'grep',
48
- 'glob',
49
- 'list',
50
- 'edit',
51
- 'write',
52
- 'run',
53
- 'list_background_tasks',
54
- 'get_background_task',
55
- 'stop_background_task'
56
- ],
57
- max_steps: 16
58
- },
59
- sessions: {
60
- max_sessions: 100,
61
- retention_days: 30
62
- },
63
- shell: {
64
- default: normalizeShellName(process.platform === 'win32' ? 'powershell' : 'bash'),
65
- timeout_ms: 1800000
66
- },
67
- ui: {
68
- language: 'zh',
69
- reply_language: 'zh'
70
- },
71
- memory: {
72
- enabled: true,
73
- auto_write: true,
74
- auto_capture: true,
75
- inject_on_session_start: true,
76
- auto_dream_threshold: 10,
77
- max_items_per_scope: 12,
78
- max_prompt_chars: 4000,
79
- max_user_chars: 1375,
80
- max_global_chars: 2200,
81
- max_project_chars: 2200,
82
- project_binding: 'path-or-alias'
83
- },
84
- soul: {
85
- preset: 'default',
86
- custom_path: ''
87
- },
88
- web: {
89
- search_enabled: true
90
- },
91
- policy: {
92
- safe_mode: true,
93
- allow_dangerous_commands: false,
94
- allowed_paths: [],
95
- command_allowlist: [],
96
- blocked_commands: [],
97
- blocked_path_patterns: [],
98
- blocked_command_patterns: ['rm -rf /', 'format c:', 'del /f /s /q C:\\\\']
99
- },
100
- skills: {
101
- enabled: {}
102
- }
103
- };
104
-
105
- async function ensureDir(filePath) {
106
- await fs.mkdir(path.dirname(filePath), { recursive: true });
107
- }
108
-
109
- function isObject(v) {
110
- return v !== null && typeof v === 'object' && !Array.isArray(v);
111
- }
112
-
113
- function deepMerge(base, extra) {
114
- if (!isObject(base) || !isObject(extra)) {
115
- return extra;
116
- }
117
- const out = { ...base };
118
- for (const [k, v] of Object.entries(extra)) {
119
- if (k in out) {
120
- out[k] = deepMerge(out[k], v);
121
- } else {
122
- out[k] = v;
123
- }
124
- }
125
- return out;
126
- }
127
-
128
- function uniqueStrings(items = []) {
129
- const out = [];
130
- const seen = new Set();
131
- for (const it of items) {
132
- const v = String(it || '').trim();
133
- if (!v || seen.has(v)) continue;
134
- seen.add(v);
135
- out.push(v);
136
- }
137
- return out;
138
- }
139
-
140
- function normalizePolicyLists(config) {
141
- const next = structuredClone(config);
142
- next.sdk = next.sdk || {};
143
- next.sdk.provider = ['openai-compatible', 'anthropic'].includes(String(next.sdk.provider || '').toLowerCase())
144
- ? String(next.sdk.provider).toLowerCase()
145
- : 'openai-compatible';
146
- next.shell = next.shell || {};
147
- next.shell.default = normalizeShellName(next.shell.default);
148
- next.execution = next.execution || {};
149
- next.model = next.model || {};
150
- if (typeof next.model.fast_name !== 'string' && typeof next.model.lite_name === 'string') {
151
- next.model.fast_name = next.model.lite_name;
152
- }
153
- next.model.name = String(next.model.name || DEFAULT_CONFIG.model.name).trim() || DEFAULT_CONFIG.model.name;
154
- next.model.fast_name = String(next.model.fast_name || '').trim();
155
- next.execution.mode = ['auto', 'normal', 'plan'].includes(String(next.execution.mode || '').toLowerCase())
156
- ? String(next.execution.mode).toLowerCase()
157
- : 'normal';
158
- const rawTools = Array.isArray(next.execution.always_allow_tools)
159
- ? next.execution.always_allow_tools
160
- : [];
161
- next.execution.always_allow_tools = uniqueStrings(
162
- [
163
- 'read',
164
- 'grep',
165
- 'glob',
166
- 'list',
167
- 'edit',
168
- 'write',
169
- 'run',
170
- 'list_background_tasks',
171
- 'get_background_task',
172
- 'stop_background_task',
173
- ...rawTools
174
- ].filter((name) => String(name) !== 'list_files')
175
- );
176
- next.ui = next.ui || {};
177
- next.ui.language = normalizeUiLanguage(next.ui.language);
178
- next.ui.reply_language = normalizeReplyLanguage(next.ui.reply_language);
179
- next.memory = next.memory || {};
180
- next.memory.enabled = next.memory.enabled !== false;
181
- next.memory.auto_write = next.memory.auto_write !== false;
182
- next.memory.auto_capture = next.memory.auto_capture !== false;
183
- next.memory.inject_on_session_start = next.memory.inject_on_session_start !== false;
184
- next.memory.max_items_per_scope = Math.max(1, Number(next.memory.max_items_per_scope || 12));
185
- next.memory.auto_dream_threshold = Number(next.memory.auto_dream_threshold ?? 10);
186
- next.memory.max_prompt_chars = Math.max(200, Number(next.memory.max_prompt_chars || 4000));
187
- next.memory.max_user_chars = Math.max(80, Number(next.memory.max_user_chars || 1375));
188
- next.memory.max_global_chars = Math.max(80, Number(next.memory.max_global_chars || 2200));
189
- next.memory.max_project_chars = Math.max(80, Number(next.memory.max_project_chars || 2200));
190
- next.memory.project_binding = ['path', 'alias', 'path-or-alias'].includes(String(next.memory.project_binding || ''))
191
- ? String(next.memory.project_binding)
192
- : 'path-or-alias';
193
- next.context = next.context || {};
194
- next.context.prompt_budget_audit = next.context.prompt_budget_audit === true;
195
- next.web = next.web || {};
196
- next.web.search_enabled = next.web.search_enabled !== false;
197
- next.policy = next.policy || {};
198
- next.policy.command_allowlist = uniqueStrings(
199
- Array.isArray(next.policy.command_allowlist) ? next.policy.command_allowlist : []
200
- );
201
- next.policy.allowed_paths = uniqueStrings(
202
- Array.isArray(next.policy.allowed_paths) ? next.policy.allowed_paths : []
203
- );
204
- next.policy.blocked_commands = uniqueStrings(
205
- Array.isArray(next.policy.blocked_commands) ? next.policy.blocked_commands : []
206
- );
207
- next.policy.blocked_path_patterns = uniqueStrings(
208
- Array.isArray(next.policy.blocked_path_patterns) ? next.policy.blocked_path_patterns : []
209
- );
210
- return next;
211
- }
212
-
213
- function getNested(obj, keyPath) {
214
- return keyPath.split('.').reduce((acc, k) => (acc && k in acc ? acc[k] : undefined), obj);
215
- }
216
-
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { getConfigFilePath, getLegacyConfigDir } from './paths.js';
4
+ import { normalizeReplyLanguage } from './reply-language.js';
5
+ import { normalizeShellName } from './shell-profile.js';
6
+
7
+ function normalizeUiLanguage(value) {
8
+ const raw = String(value || '').trim().toLowerCase();
9
+ if (!raw) return 'zh';
10
+ if (['en', 'en-us', 'en_us', 'english'].includes(raw)) return 'en';
11
+ if (['zh', 'zh-cn', 'zh_cn', 'cn', 'chinese'].includes(raw)) return 'zh';
12
+ return 'zh';
13
+ }
14
+
15
+ const DEFAULT_CONFIG = {
16
+ sdk: {
17
+ provider: 'openai-compatible'
18
+ },
19
+ gateway: {
20
+ base_url: 'http://127.0.0.1:8000/v1',
21
+ api_key: '',
22
+ timeout_ms: 1800000,
23
+ max_retries: 2
24
+ },
25
+ model: {
26
+ name: 'gpt-4.1-mini',
27
+ fast_name: '',
28
+ max_context_tokens: 202752
29
+ },
30
+ context: {
31
+ max_tokens: 32000,
32
+ preflight_trigger_pct: 60,
33
+ hard_limit_pct: 98,
34
+ tool_result_max_chars: 12000,
35
+ read_file_default_lines: 220,
36
+ read_file_max_chars: 24000,
37
+ prompt_budget_audit: false,
38
+ microcompact_enabled: true,
39
+ microcompact_keep_recent: 5,
40
+ project_instructions_enabled: true,
41
+ project_instructions_max_chars: 12000
42
+ },
43
+ execution: {
44
+ mode: 'normal',
45
+ always_allow_tools: [
46
+ 'read',
47
+ 'grep',
48
+ 'glob',
49
+ 'list',
50
+ 'edit',
51
+ 'write',
52
+ 'run',
53
+ 'list_background_tasks',
54
+ 'get_background_task',
55
+ 'stop_background_task'
56
+ ],
57
+ max_steps: 16
58
+ },
59
+ sessions: {
60
+ max_sessions: 100,
61
+ retention_days: 30
62
+ },
63
+ shell: {
64
+ default: normalizeShellName(process.platform === 'win32' ? 'powershell' : 'bash'),
65
+ timeout_ms: 1800000
66
+ },
67
+ ui: {
68
+ language: 'zh',
69
+ reply_language: 'zh'
70
+ },
71
+ memory: {
72
+ enabled: true,
73
+ auto_write: true,
74
+ auto_capture: true,
75
+ inject_on_session_start: true,
76
+ auto_dream_threshold: 10,
77
+ max_items_per_scope: 12,
78
+ max_prompt_chars: 4000,
79
+ max_user_chars: 1375,
80
+ max_global_chars: 2200,
81
+ max_project_chars: 2200,
82
+ project_binding: 'path-or-alias'
83
+ },
84
+ soul: {
85
+ preset: 'default',
86
+ custom_path: ''
87
+ },
88
+ web: {
89
+ search_enabled: true
90
+ },
91
+ policy: {
92
+ safe_mode: true,
93
+ allow_dangerous_commands: false,
94
+ allowed_paths: [],
95
+ command_allowlist: [],
96
+ blocked_commands: [],
97
+ blocked_path_patterns: [],
98
+ blocked_command_patterns: ['rm -rf /', 'format c:', 'del /f /s /q C:\\\\']
99
+ },
100
+ skills: {
101
+ enabled: {}
102
+ }
103
+ };
104
+
105
+ async function ensureDir(filePath) {
106
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
107
+ }
108
+
109
+ function isObject(v) {
110
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
111
+ }
112
+
113
+ function deepMerge(base, extra) {
114
+ if (!isObject(base) || !isObject(extra)) {
115
+ return extra;
116
+ }
117
+ const out = { ...base };
118
+ for (const [k, v] of Object.entries(extra)) {
119
+ if (k in out) {
120
+ out[k] = deepMerge(out[k], v);
121
+ } else {
122
+ out[k] = v;
123
+ }
124
+ }
125
+ return out;
126
+ }
127
+
128
+ function uniqueStrings(items = []) {
129
+ const out = [];
130
+ const seen = new Set();
131
+ for (const it of items) {
132
+ const v = String(it || '').trim();
133
+ if (!v || seen.has(v)) continue;
134
+ seen.add(v);
135
+ out.push(v);
136
+ }
137
+ return out;
138
+ }
139
+
140
+ function normalizePolicyLists(config) {
141
+ const next = structuredClone(config);
142
+ next.sdk = next.sdk || {};
143
+ next.sdk.provider = ['openai-compatible', 'anthropic'].includes(String(next.sdk.provider || '').toLowerCase())
144
+ ? String(next.sdk.provider).toLowerCase()
145
+ : 'openai-compatible';
146
+ next.shell = next.shell || {};
147
+ next.shell.default = normalizeShellName(next.shell.default);
148
+ next.execution = next.execution || {};
149
+ next.model = next.model || {};
150
+ if (typeof next.model.fast_name !== 'string' && typeof next.model.lite_name === 'string') {
151
+ next.model.fast_name = next.model.lite_name;
152
+ }
153
+ next.model.name = String(next.model.name || DEFAULT_CONFIG.model.name).trim() || DEFAULT_CONFIG.model.name;
154
+ next.model.fast_name = String(next.model.fast_name || '').trim();
155
+ next.execution.mode = ['auto', 'normal', 'plan'].includes(String(next.execution.mode || '').toLowerCase())
156
+ ? String(next.execution.mode).toLowerCase()
157
+ : 'normal';
158
+ const rawTools = Array.isArray(next.execution.always_allow_tools)
159
+ ? next.execution.always_allow_tools
160
+ : [];
161
+ next.execution.always_allow_tools = uniqueStrings(
162
+ [
163
+ 'read',
164
+ 'grep',
165
+ 'glob',
166
+ 'list',
167
+ 'edit',
168
+ 'write',
169
+ 'run',
170
+ 'list_background_tasks',
171
+ 'get_background_task',
172
+ 'stop_background_task',
173
+ ...rawTools
174
+ ].filter((name) => String(name) !== 'list_files')
175
+ );
176
+ next.ui = next.ui || {};
177
+ next.ui.language = normalizeUiLanguage(next.ui.language);
178
+ next.ui.reply_language = normalizeReplyLanguage(next.ui.reply_language);
179
+ next.memory = next.memory || {};
180
+ next.memory.enabled = next.memory.enabled !== false;
181
+ next.memory.auto_write = next.memory.auto_write !== false;
182
+ next.memory.auto_capture = next.memory.auto_capture !== false;
183
+ next.memory.inject_on_session_start = next.memory.inject_on_session_start !== false;
184
+ next.memory.max_items_per_scope = Math.max(1, Number(next.memory.max_items_per_scope || 12));
185
+ next.memory.auto_dream_threshold = Number(next.memory.auto_dream_threshold ?? 10);
186
+ next.memory.max_prompt_chars = Math.max(200, Number(next.memory.max_prompt_chars || 4000));
187
+ next.memory.max_user_chars = Math.max(80, Number(next.memory.max_user_chars || 1375));
188
+ next.memory.max_global_chars = Math.max(80, Number(next.memory.max_global_chars || 2200));
189
+ next.memory.max_project_chars = Math.max(80, Number(next.memory.max_project_chars || 2200));
190
+ next.memory.project_binding = ['path', 'alias', 'path-or-alias'].includes(String(next.memory.project_binding || ''))
191
+ ? String(next.memory.project_binding)
192
+ : 'path-or-alias';
193
+ next.context = next.context || {};
194
+ next.context.prompt_budget_audit = next.context.prompt_budget_audit === true;
195
+ next.web = next.web || {};
196
+ next.web.search_enabled = next.web.search_enabled !== false;
197
+ next.policy = next.policy || {};
198
+ next.policy.command_allowlist = uniqueStrings(
199
+ Array.isArray(next.policy.command_allowlist) ? next.policy.command_allowlist : []
200
+ );
201
+ next.policy.allowed_paths = uniqueStrings(
202
+ Array.isArray(next.policy.allowed_paths) ? next.policy.allowed_paths : []
203
+ );
204
+ next.policy.blocked_commands = uniqueStrings(
205
+ Array.isArray(next.policy.blocked_commands) ? next.policy.blocked_commands : []
206
+ );
207
+ next.policy.blocked_path_patterns = uniqueStrings(
208
+ Array.isArray(next.policy.blocked_path_patterns) ? next.policy.blocked_path_patterns : []
209
+ );
210
+ return next;
211
+ }
212
+
213
+ function getNested(obj, keyPath) {
214
+ return keyPath.split('.').reduce((acc, k) => (acc && k in acc ? acc[k] : undefined), obj);
215
+ }
216
+
217
217
  function parseValue(input) {
218
+ if (typeof input !== 'string') return input;
218
219
  if (input === 'true') return true;
219
220
  if (input === 'false') return false;
220
221
  if (input === 'null') return null;
221
- if ((input.startsWith('[') && input.endsWith(']')) || (input.startsWith('{') && input.endsWith('}'))) {
222
- try {
223
- return JSON.parse(input);
224
- } catch {
225
- return input;
226
- }
227
- }
228
- if (!Number.isNaN(Number(input)) && input.trim() !== '') return Number(input);
229
- return input;
230
- }
231
-
232
- function setNested(obj, keyPath, rawValue) {
233
- const value = parseValue(rawValue);
234
- const parts = keyPath.split('.');
235
- let cursor = obj;
236
- for (let i = 0; i < parts.length - 1; i += 1) {
237
- const p = parts[i];
238
- if (!isObject(cursor[p])) {
239
- cursor[p] = {};
240
- }
241
- cursor = cursor[p];
242
- }
243
- cursor[parts[parts.length - 1]] = value;
244
- }
245
-
246
- export async function loadConfig() {
247
- const configPath = getConfigFilePath();
248
- try {
249
- const raw = await fs.readFile(configPath, 'utf8');
250
- const parsed = JSON.parse(raw);
251
- return normalizePolicyLists(deepMerge(DEFAULT_CONFIG, parsed));
252
- } catch {
253
- const defaultConfig = normalizePolicyLists(structuredClone(DEFAULT_CONFIG));
254
- if (process.env.CODEMINI_GLOBAL_DIR) {
255
- await saveConfig(defaultConfig);
256
- return defaultConfig;
257
- }
258
- try {
259
- const legacyPath = path.join(getLegacyConfigDir(), 'config.json');
260
- const raw = await fs.readFile(legacyPath, 'utf8');
261
- const parsed = JSON.parse(raw);
262
- return normalizePolicyLists(deepMerge(DEFAULT_CONFIG, parsed));
263
- } catch {
264
- await saveConfig(defaultConfig);
265
- return defaultConfig;
266
- }
267
- }
268
- }
269
-
270
- export async function saveConfig(config) {
271
- const configPath = getConfigFilePath();
272
- await ensureDir(configPath);
273
- await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
274
- }
275
-
276
- export async function setConfigValue(keyPath, value) {
277
- const config = await loadConfig();
278
- setNested(config, keyPath, value);
279
- await saveConfig(config);
280
- }
281
-
282
- export async function getConfigValue(keyPath) {
283
- const config = await loadConfig();
284
- return getNested(config, keyPath);
285
- }
286
-
287
- export async function resetConfig() {
288
- await saveConfig(structuredClone(DEFAULT_CONFIG));
289
- }
222
+ if ((input.startsWith('[') && input.endsWith(']')) || (input.startsWith('{') && input.endsWith('}'))) {
223
+ try {
224
+ return JSON.parse(input);
225
+ } catch {
226
+ return input;
227
+ }
228
+ }
229
+ if (!Number.isNaN(Number(input)) && input.trim() !== '') return Number(input);
230
+ return input;
231
+ }
232
+
233
+ function setNested(obj, keyPath, rawValue) {
234
+ const value = parseValue(rawValue);
235
+ const parts = keyPath.split('.');
236
+ let cursor = obj;
237
+ for (let i = 0; i < parts.length - 1; i += 1) {
238
+ const p = parts[i];
239
+ if (!isObject(cursor[p])) {
240
+ cursor[p] = {};
241
+ }
242
+ cursor = cursor[p];
243
+ }
244
+ cursor[parts[parts.length - 1]] = value;
245
+ }
246
+
247
+ export async function loadConfig() {
248
+ const configPath = getConfigFilePath();
249
+ try {
250
+ const raw = await fs.readFile(configPath, 'utf8');
251
+ const parsed = JSON.parse(raw);
252
+ return normalizePolicyLists(deepMerge(DEFAULT_CONFIG, parsed));
253
+ } catch {
254
+ const defaultConfig = normalizePolicyLists(structuredClone(DEFAULT_CONFIG));
255
+ if (process.env.CODEMINI_GLOBAL_DIR) {
256
+ await saveConfig(defaultConfig);
257
+ return defaultConfig;
258
+ }
259
+ try {
260
+ const legacyPath = path.join(getLegacyConfigDir(), 'config.json');
261
+ const raw = await fs.readFile(legacyPath, 'utf8');
262
+ const parsed = JSON.parse(raw);
263
+ return normalizePolicyLists(deepMerge(DEFAULT_CONFIG, parsed));
264
+ } catch {
265
+ await saveConfig(defaultConfig);
266
+ return defaultConfig;
267
+ }
268
+ }
269
+ }
270
+
271
+ export async function saveConfig(config) {
272
+ const configPath = getConfigFilePath();
273
+ await ensureDir(configPath);
274
+ await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
275
+ }
276
+
277
+ export async function setConfigValue(keyPath, value) {
278
+ const config = await loadConfig();
279
+ setNested(config, keyPath, value);
280
+ await saveConfig(config);
281
+ }
282
+
283
+ export async function getConfigValue(keyPath) {
284
+ const config = await loadConfig();
285
+ return getNested(config, keyPath);
286
+ }
287
+
288
+ export async function resetConfig() {
289
+ await saveConfig(structuredClone(DEFAULT_CONFIG));
290
+ }
@@ -19,18 +19,35 @@ export const INDEX_SKIP_DIRS = new Set([
19
19
  'node_modules',
20
20
  '.codemini',
21
21
  '.codemini-global',
22
+ '.venv',
23
+ 'venv',
24
+ 'env',
25
+ '.env',
26
+ 'virtualenv',
27
+ 'site-packages',
28
+ '__pycache__',
29
+ '.pytest_cache',
30
+ '.mypy_cache',
31
+ '.ruff_cache',
32
+ '.tox',
33
+ '.pnpm',
22
34
  'dist',
23
35
  'coverage',
36
+ 'benchmark',
37
+ 'benchmarks',
24
38
  'sessions',
25
39
  'tmp',
26
40
  'temp',
27
41
  '.cache',
28
42
  '.turbo',
29
43
  '.next',
44
+ '.gradle',
30
45
  'build',
31
46
  'out',
32
47
  'logs',
33
- 'artifacts'
48
+ 'artifacts',
49
+ 'vendor',
50
+ 'third_party'
34
51
  ]);
35
52
 
36
53
  // ─── 文本扩展名 ──────────────────────────────────────────────────────