codemini-cli 0.1.19 → 0.2.1

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.
@@ -28,6 +28,8 @@ import {
28
28
  } from './context-compact.js';
29
29
  import { buildSystemPromptWithReplyLanguage } from './reply-language.js';
30
30
  import { buildSystemPromptWithSoul } from './soul.js';
31
+ import { getProjectPlansDir, getProjectSpecsDir, getProjectWorkspaceDir } from './paths.js';
32
+ import { buildProjectContextSnippet, initializeProjectIndex } from './project-index.js';
31
33
 
32
34
  function toOpenAIMessages(sessionMessages) {
33
35
  const mapped = [];
@@ -875,10 +877,13 @@ async function buildAutoPlanFinalSummary({
875
877
  }
876
878
  }
877
879
 
878
- async function writeMarkdownInCoderDir(subDir, title, body, fallbackName, sessionId) {
879
- const parts = [process.cwd(), '.coder', subDir];
880
- if (sessionId) parts.push(String(sessionId));
881
- const dir = path.join(...parts);
880
+ async function writeMarkdownInProjectDir(subDir, title, body, fallbackName, sessionId) {
881
+ const dir =
882
+ subDir === 'specs'
883
+ ? getProjectSpecsDir(process.cwd(), sessionId)
884
+ : subDir === 'plans'
885
+ ? getProjectPlansDir(process.cwd(), sessionId)
886
+ : path.join(getProjectWorkspaceDir(process.cwd()), subDir, ...(sessionId ? [String(sessionId)] : []));
882
887
  await fs.mkdir(dir, { recursive: true });
883
888
  const slug = slugify(title).slice(0, 64);
884
889
  const fileName = `${nowStamp()}-${slug || fallbackName}.md`;
@@ -1038,7 +1043,7 @@ async function collectLikelyImplementationFiles(cwd) {
1038
1043
  return;
1039
1044
  }
1040
1045
  for (const entry of entries) {
1041
- if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.coder') continue;
1046
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.codemini') continue;
1042
1047
  const abs = path.join(dir, entry.name);
1043
1048
  if (entry.isDirectory()) {
1044
1049
  await visit(abs);
@@ -1162,8 +1167,8 @@ function stampedMessage(role, content, extra = {}) {
1162
1167
  async function resolveSpecPath(rawArg = '', sessionId = '') {
1163
1168
  const input = String(rawArg || '').trim();
1164
1169
  const roots = [
1165
- path.join(process.cwd(), '.coder', 'specs', String(sessionId || '')),
1166
- path.join(process.cwd(), '.coder', 'specs')
1170
+ getProjectSpecsDir(process.cwd(), String(sessionId || '')),
1171
+ getProjectSpecsDir(process.cwd())
1167
1172
  ];
1168
1173
 
1169
1174
  if (input) {
@@ -1304,10 +1309,16 @@ async function askModel({
1304
1309
  await saveSession(session);
1305
1310
  }
1306
1311
 
1312
+ const projectContextSnippet = await buildProjectContextSnippet(process.cwd(), text).catch(() => '');
1313
+ const effectiveSystemPrompt = projectContextSnippet
1314
+ ? `${systemPrompt}\n\n${projectContextSnippet}\n\nUse this project context as lightweight guidance. Prefer tools for fresh verification before assuming details.`
1315
+ : systemPrompt;
1316
+
1307
1317
  const { definitions, handlers } = getBuiltinTools({
1308
1318
  workspaceRoot: process.cwd(),
1309
1319
  config,
1310
- sessionId: session.id
1320
+ sessionId: session.id,
1321
+ onSystemEvent: onAgentEvent
1311
1322
  });
1312
1323
 
1313
1324
  let activeAssistantIndex = -1;
@@ -1353,7 +1364,7 @@ async function askModel({
1353
1364
 
1354
1365
  const loopUserPrompt = persistSession ? '' : text;
1355
1366
  const loopResult = await runAgentLoop({
1356
- systemPrompt,
1367
+ systemPrompt: effectiveSystemPrompt,
1357
1368
  userPrompt: loopUserPrompt,
1358
1369
  model: model || config.model.name,
1359
1370
  maxSteps: Number(config.execution?.max_steps || 16),
@@ -1377,6 +1388,9 @@ async function askModel({
1377
1388
  maxRetries: config.gateway.max_retries ?? 2,
1378
1389
  onTextDelta: (delta) => {
1379
1390
  if (onAgentEvent) onAgentEvent({ type: 'assistant:delta', text: delta });
1391
+ },
1392
+ onToolCallDelta: (toolCall) => {
1393
+ if (onAgentEvent) onAgentEvent({ type: 'assistant:tool_call_delta', toolCall });
1380
1394
  }
1381
1395
  });
1382
1396
  }
@@ -1648,7 +1662,7 @@ async function buildAutoPlanAndRun({
1648
1662
  lines.push('');
1649
1663
  });
1650
1664
 
1651
- const filePath = await writeMarkdownInCoderDir(
1665
+ const filePath = await writeMarkdownInProjectDir(
1652
1666
  'plans',
1653
1667
  `${goal}-auto`,
1654
1668
  lines.join('\n'),
@@ -1698,6 +1712,16 @@ export async function createChatRuntime({
1698
1712
  model,
1699
1713
  systemPrompt
1700
1714
  }) {
1715
+ const startupEvents = [];
1716
+ const initialIndex = await initializeProjectIndex(process.cwd()).catch(() => null);
1717
+ if (initialIndex?.summary) {
1718
+ startupEvents.push({
1719
+ type: 'system_tool',
1720
+ name: 'project_index(.codemini-project/project-map.json,.codemini-project/file-index.json)',
1721
+ status: 'done',
1722
+ summary: initialIndex.summary
1723
+ });
1724
+ }
1701
1725
  let currentSession = session;
1702
1726
  let config = initialConfig;
1703
1727
  const baseSystemPrompt = systemPrompt;
@@ -1776,8 +1800,8 @@ export async function createChatRuntime({
1776
1800
  { name: 'compact', description: 'compress message context' },
1777
1801
  { name: 'tasks', description: 'task board management' },
1778
1802
  { name: 'checkpoint', description: 'create/list/load conversation checkpoints' },
1779
- { name: 'spec', description: 'create a spec markdown file in .coder/specs' },
1780
- { name: 'plan', description: 'create an implementation plan markdown file in .coder/plans' },
1803
+ { name: 'spec', description: 'create a spec markdown file in .codemini/specs' },
1804
+ { name: 'plan', description: 'create an implementation plan markdown file in .codemini/plans' },
1781
1805
  { name: 'agents', description: 'run/list sub-agent roles' },
1782
1806
  { name: 'config', description: 'set/get/list/reset config values' },
1783
1807
  { name: 'history', description: 'list/resume sessions' },
@@ -2317,7 +2341,7 @@ export async function createChatRuntime({
2317
2341
  content = buildSpecTemplate(topic);
2318
2342
  buildNote = `\nGenerated with fallback template because model spec generation failed: ${String(err?.message || err)}`;
2319
2343
  }
2320
- const filePath = await writeMarkdownInCoderDir(
2344
+ const filePath = await writeMarkdownInProjectDir(
2321
2345
  'specs',
2322
2346
  topic,
2323
2347
  content,
@@ -2371,7 +2395,7 @@ export async function createChatRuntime({
2371
2395
  planContent = buildPlanTemplate(specTitle);
2372
2396
  buildNote = `\nGenerated with fallback template because model plan generation failed: ${String(err?.message || err)}`;
2373
2397
  }
2374
- const filePath = await writeMarkdownInCoderDir(
2398
+ const filePath = await writeMarkdownInProjectDir(
2375
2399
  'plans',
2376
2400
  `${specTitle}-from-spec`,
2377
2401
  planContent,
@@ -2386,7 +2410,7 @@ export async function createChatRuntime({
2386
2410
  const goal = parsedInput.args.join(' ').trim();
2387
2411
  if (!goal) return { type: 'system', text: 'Usage: /plan <goal> | /plan auto <goal> | /plan from-spec <spec-path?>' };
2388
2412
  const content = buildPlanTemplate(goal);
2389
- const filePath = await writeMarkdownInCoderDir(
2413
+ const filePath = await writeMarkdownInProjectDir(
2390
2414
  'plans',
2391
2415
  goal,
2392
2416
  content,
@@ -2672,6 +2696,13 @@ export async function createChatRuntime({
2672
2696
  }
2673
2697
 
2674
2698
  const expandedText = await expandFileMentions(parsedInput.text, process.cwd());
2699
+ const selectedAutoSkills = selectAutoSkillNames(expandedText).filter((name) => isSkillEnabled(config, name));
2700
+ if (selectedAutoSkills.length > 0 && onAgentEvent) {
2701
+ onAgentEvent({
2702
+ type: 'skill:auto',
2703
+ names: selectedAutoSkills
2704
+ });
2705
+ }
2675
2706
  const routedSystemPrompt = buildAutoSkillSystemPrompt(activeReplySystemPrompt, commands, config, expandedText);
2676
2707
  const result = await askModel({
2677
2708
  text: expandedText,
@@ -2690,6 +2721,7 @@ export async function createChatRuntime({
2690
2721
  getCompletionOptions,
2691
2722
  isImmediateLocalInput,
2692
2723
  submit,
2724
+ consumeStartupEvents: () => startupEvents.splice(0, startupEvents.length),
2693
2725
  getInputHistory: () => loadInputHistory(),
2694
2726
  getCurrentSessionId: () => currentSession.id,
2695
2727
  getRuntimeState: () =>
@@ -1,8 +1,9 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { getProjectCheckpointsDir } from './paths.js';
3
4
 
4
5
  function checkpointsDir(cwd = process.cwd()) {
5
- return path.join(cwd, '.coder', 'checkpoints');
6
+ return getProjectCheckpointsDir(cwd);
6
7
  }
7
8
 
8
9
  function makeId(name = '') {
@@ -3,9 +3,8 @@ import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import {
5
5
  getCommandsDir,
6
- getLegacyGlobalSkillsDir,
7
- getLegacyProjectSkillsDir,
8
6
  getProjectCommandsDir,
7
+ getProjectSkillsDir,
9
8
  getSkillsDir
10
9
  } from './paths.js';
11
10
  import { readSkillRegistry } from './skill-registry.js';
@@ -188,8 +187,8 @@ export async function loadCommandsAndSkills(cwd = process.cwd()) {
188
187
  loadBundledSkillsFromDir(BUNDLED_SKILLS_DIR, commands);
189
188
  loadMarkdownCommandsFromDir(getCommandsDir(), 'global', commands);
190
189
  loadMarkdownCommandsFromDir(getProjectCommandsDir(cwd), 'project', commands);
191
- loadLegacySkillsFromDir(getLegacyGlobalSkillsDir(), 'global', commands);
192
- loadLegacySkillsFromDir(getLegacyProjectSkillsDir(cwd), 'project', commands);
190
+ loadLegacySkillsFromDir(getSkillsDir(), 'global', commands);
191
+ loadLegacySkillsFromDir(getProjectSkillsDir(cwd), 'project', commands);
193
192
  const registry = await readSkillRegistry();
194
193
  loadInstalledSkillsFromRegistry(getSkillsDir(), registry, commands);
195
194
 
@@ -193,8 +193,10 @@ export async function loadConfig() {
193
193
  const parsed = JSON.parse(raw);
194
194
  return normalizePolicyLists(deepMerge(DEFAULT_CONFIG, parsed));
195
195
  } catch {
196
- if (process.env.CODEMINI_CONFIG_DIR || process.env.COMPANY_CODER_CONFIG_DIR) {
197
- return normalizePolicyLists(structuredClone(DEFAULT_CONFIG));
196
+ const defaultConfig = normalizePolicyLists(structuredClone(DEFAULT_CONFIG));
197
+ if (process.env.CODEMINI_GLOBAL_DIR) {
198
+ await saveConfig(defaultConfig);
199
+ return defaultConfig;
198
200
  }
199
201
  try {
200
202
  const legacyPath = path.join(getLegacyConfigDir(), 'config.json');
@@ -202,7 +204,8 @@ export async function loadConfig() {
202
204
  const parsed = JSON.parse(raw);
203
205
  return normalizePolicyLists(deepMerge(DEFAULT_CONFIG, parsed));
204
206
  } catch {
205
- return normalizePolicyLists(structuredClone(DEFAULT_CONFIG));
207
+ await saveConfig(defaultConfig);
208
+ return defaultConfig;
206
209
  }
207
210
  }
208
211
  }
@@ -1,5 +1,5 @@
1
1
  import { getShellSystemPrompt } from './shell-profile.js';
2
2
 
3
3
  export function buildDefaultSystemPrompt(config = {}) {
4
- return `${getShellSystemPrompt(config?.shell?.default)} If a command or tool is blocked or fails, inspect the error and retry with allowed commands or tools. Do not claim filesystem access is impossible unless the allowed search/read tools also fail.`;
4
+ return `${getShellSystemPrompt(config?.shell?.default)} If a command or tool is blocked or fails, inspect the error and retry with allowed commands or tools. For AST-scoped edits, if edit rejects a call because kind=replace_block or ast_target is missing or stale, fix the tool arguments and retry instead of switching to a broader text edit. Do not claim filesystem access is impossible unless the allowed search/read tools also fail.`;
5
5
  }
package/src/core/paths.js CHANGED
@@ -1,75 +1,33 @@
1
- import fs from 'node:fs';
2
1
  import os from 'node:os';
3
2
  import path from 'node:path';
4
3
 
5
- const APP_DIR = 'codemini-cli';
6
- const LEGACY_APP_DIR = 'company-coder';
4
+ const GLOBAL_APP_DIR = 'codemini-global';
5
+ const PROJECT_APP_DIR = '.codemini';
6
+ const PROJECT_INDEX_DIR = '.codemini-project';
7
7
 
8
- function getPreferredBaseConfigDir() {
9
- if (process.env.CODEMINI_CONFIG_DIR) {
10
- return process.env.CODEMINI_CONFIG_DIR;
11
- }
12
-
13
- if (process.env.COMPANY_CODER_CONFIG_DIR) {
14
- return process.env.COMPANY_CODER_CONFIG_DIR;
15
- }
16
-
17
- if (process.platform === 'win32') {
18
- const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
19
- return path.join(appData, APP_DIR);
20
- }
21
-
22
- if (process.platform === 'darwin') {
23
- return path.join(os.homedir(), 'Library', 'Preferences', APP_DIR);
24
- }
25
-
26
- if (process.env.XDG_CONFIG_HOME) {
27
- return path.join(process.env.XDG_CONFIG_HOME, APP_DIR);
8
+ export function getBaseConfigDir() {
9
+ if (process.env.CODEMINI_GLOBAL_DIR) {
10
+ return process.env.CODEMINI_GLOBAL_DIR;
28
11
  }
29
12
 
30
- // Fallback for restricted/sandboxed non-Windows environments.
31
- return path.join(process.cwd(), '.codemini-cli');
32
- }
33
-
34
- function getLegacyBaseConfigDir() {
35
13
  if (process.platform === 'win32') {
36
14
  const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
37
- return path.join(appData, LEGACY_APP_DIR);
15
+ return path.join(appData, GLOBAL_APP_DIR);
38
16
  }
39
17
 
40
18
  if (process.platform === 'darwin') {
41
- return path.join(os.homedir(), 'Library', 'Preferences', LEGACY_APP_DIR);
19
+ return path.join(os.homedir(), 'Library', 'Preferences', GLOBAL_APP_DIR);
42
20
  }
43
21
 
44
22
  if (process.env.XDG_CONFIG_HOME) {
45
- return path.join(process.env.XDG_CONFIG_HOME, LEGACY_APP_DIR);
23
+ return path.join(process.env.XDG_CONFIG_HOME, GLOBAL_APP_DIR);
46
24
  }
47
25
 
48
- return path.join(process.cwd(), '.company-coder');
49
- }
50
-
51
- function tryMigrateLegacyDir(preferred, legacy) {
52
- if (!preferred || !legacy || preferred === legacy) return preferred;
53
- if (fs.existsSync(preferred) || !fs.existsSync(legacy)) return preferred;
54
- try {
55
- fs.renameSync(legacy, preferred);
56
- return preferred;
57
- } catch {
58
- return preferred;
59
- }
60
- }
61
-
62
- export function getBaseConfigDir() {
63
- const preferred = getPreferredBaseConfigDir();
64
- if (process.env.CODEMINI_CONFIG_DIR || process.env.COMPANY_CODER_CONFIG_DIR) {
65
- return preferred;
66
- }
67
- const legacy = getLegacyBaseConfigDir();
68
- return tryMigrateLegacyDir(preferred, legacy);
26
+ return path.join(process.cwd(), '.codemini-global');
69
27
  }
70
28
 
71
29
  export function getLegacyConfigDir() {
72
- return getLegacyBaseConfigDir();
30
+ return getBaseConfigDir();
73
31
  }
74
32
 
75
33
  export function getConfigFilePath() {
@@ -96,14 +54,50 @@ export function getInputHistoryFilePath() {
96
54
  return path.join(getBaseConfigDir(), 'input-history.json');
97
55
  }
98
56
 
57
+ export function getProjectWorkspaceDir(cwd = process.cwd()) {
58
+ return path.join(cwd, PROJECT_APP_DIR);
59
+ }
60
+
99
61
  export function getProjectCommandsDir(cwd = process.cwd()) {
100
- return path.join(cwd, '.coder', 'commands');
62
+ return path.join(getProjectWorkspaceDir(cwd), 'commands');
101
63
  }
102
64
 
103
- export function getLegacyProjectSkillsDir(cwd = process.cwd()) {
104
- return path.join(cwd, '.coder', 'skills');
65
+ export function getProjectSkillsDir(cwd = process.cwd()) {
66
+ return path.join(getProjectWorkspaceDir(cwd), 'skills');
105
67
  }
106
68
 
107
- export function getLegacyGlobalSkillsDir() {
108
- return path.join(getBaseConfigDir(), 'skills');
69
+ export function getProjectSpecsDir(cwd = process.cwd(), sessionId = '') {
70
+ return sessionId
71
+ ? path.join(getProjectWorkspaceDir(cwd), 'specs', String(sessionId))
72
+ : path.join(getProjectWorkspaceDir(cwd), 'specs');
73
+ }
74
+
75
+ export function getProjectPlansDir(cwd = process.cwd(), sessionId = '') {
76
+ return sessionId
77
+ ? path.join(getProjectWorkspaceDir(cwd), 'plans', String(sessionId))
78
+ : path.join(getProjectWorkspaceDir(cwd), 'plans');
79
+ }
80
+
81
+ export function getProjectCheckpointsDir(cwd = process.cwd()) {
82
+ return path.join(getProjectWorkspaceDir(cwd), 'checkpoints');
83
+ }
84
+
85
+ export function getProjectTasksDir(cwd = process.cwd()) {
86
+ return path.join(getProjectWorkspaceDir(cwd), 'tasks');
87
+ }
88
+
89
+ export function getProjectLegacyTasksFilePath(cwd = process.cwd()) {
90
+ return path.join(getProjectWorkspaceDir(cwd), 'tasks.json');
91
+ }
92
+
93
+ export function getProjectMapPath(cwd = process.cwd()) {
94
+ return path.join(cwd, PROJECT_INDEX_DIR, 'project-map.json');
95
+ }
96
+
97
+ export function getFileIndexPath(cwd = process.cwd()) {
98
+ return path.join(cwd, PROJECT_INDEX_DIR, 'file-index.json');
99
+ }
100
+
101
+ export function getProjectIndexDir(cwd = process.cwd()) {
102
+ return path.join(cwd, PROJECT_INDEX_DIR);
109
103
  }