codemini-cli 0.5.8 → 0.5.10
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/README.md +354 -225
- package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-CCcxtQK_.js → highlighted-body-OFNGDK62-7HL7yft8.js} +1 -1
- package/codemini-web/dist/assets/{index-Cy4HN-FS.js → index-BK75hMb2.js} +95 -93
- package/codemini-web/dist/assets/index-BSdIdn3L.css +2 -0
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-Dg9qh8mg.js +1 -0
- package/codemini-web/dist/index.html +2 -2
- package/codemini-web/lib/runtime-bridge.js +547 -546
- package/codemini-web/server.js +318 -233
- package/package.json +67 -67
- package/skills/brainstorm/SKILL.md +8 -3
- package/skills/codemini.skills.json +40 -0
- package/src/commands/skill.js +16 -5
- package/src/core/ast.js +30 -15
- package/src/core/chat-runtime.js +88 -16
- package/src/core/command-loader.js +120 -25
- package/src/core/command-policy.js +34 -10
- package/src/core/config-store.js +14 -1
- package/src/core/project-index.js +21 -2
- package/src/core/project-instructions.js +98 -0
- package/src/core/shell.js +79 -73
- package/src/core/system-prompt-composer.js +10 -0
- package/src/core/tools.js +114 -65
- package/codemini-web/dist/assets/index-CMISAOFr.css +0 -2
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-BWFzYc7A.js +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildMemorySnapshot } from './memory-prompt.js';
|
|
2
|
+
import { loadProjectInstructions } from './project-instructions.js';
|
|
2
3
|
import { buildSystemPromptWithReplyLanguage, stripReplyLanguageDirective } from './reply-language.js';
|
|
3
4
|
import { buildSystemPromptWithSoul } from './soul.js';
|
|
4
5
|
|
|
@@ -17,6 +18,8 @@ export async function composeSystemPrompt({
|
|
|
17
18
|
skillsPrompt = '',
|
|
18
19
|
memorySnapshot,
|
|
19
20
|
includeMemory = true,
|
|
21
|
+
projectInstructionsSnippet,
|
|
22
|
+
includeProjectInstructions = true,
|
|
20
23
|
projectContextSnippet = '',
|
|
21
24
|
projectContextGuidance = '',
|
|
22
25
|
extraPrompts = [],
|
|
@@ -30,8 +33,15 @@ export async function composeSystemPrompt({
|
|
|
30
33
|
: includeMemory
|
|
31
34
|
? await buildMemorySnapshot({ config, workspaceRoot }).catch(() => '')
|
|
32
35
|
: '';
|
|
36
|
+
const projectInstructionsPrompt = projectInstructionsSnippet !== undefined
|
|
37
|
+
? projectInstructionsSnippet
|
|
38
|
+
: includeProjectInstructions
|
|
39
|
+
? await loadProjectInstructions({ cwd: workspaceRoot, config }).catch(() => '')
|
|
40
|
+
: '';
|
|
41
|
+
const hasProjectInstructions = /\bProject Instructions:\s*\n/i.test(shellAndSoul);
|
|
33
42
|
const body = joinPromptParts([
|
|
34
43
|
shellAndSoul,
|
|
44
|
+
hasProjectInstructions ? '' : projectInstructionsPrompt,
|
|
35
45
|
skillsPrompt,
|
|
36
46
|
memoryPrompt,
|
|
37
47
|
projectContextSnippet,
|
package/src/core/tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { spawn } from 'node:child_process';
|
|
4
5
|
import net from 'node:net';
|
|
@@ -59,16 +60,48 @@ function isWithinResolvedRoot(resolvedRoot, candidatePath) {
|
|
|
59
60
|
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
async function
|
|
63
|
+
async function getAllowedRealRoots(root, config = {}) {
|
|
64
|
+
const roots = [
|
|
65
|
+
root,
|
|
66
|
+
...(Array.isArray(config?.policy?.allowed_paths) ? config.policy.allowed_paths : [])
|
|
67
|
+
]
|
|
68
|
+
.map((item) => String(item || '').trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
const out = [];
|
|
71
|
+
for (const item of roots) {
|
|
72
|
+
try {
|
|
73
|
+
out.push(await fs.realpath(path.resolve(item)));
|
|
74
|
+
} catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isWithinAnyResolvedRoot(roots, candidatePath) {
|
|
82
|
+
return roots.some((resolvedRoot) => isWithinResolvedRoot(resolvedRoot, candidatePath));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resolvesOutsideRoot(root, targetPath = '.') {
|
|
86
|
+
const text = String(targetPath || '').trim();
|
|
87
|
+
if (!text || text === '.') return false;
|
|
88
|
+
return !isWithinResolvedRoot(path.resolve(root), path.resolve(root, text));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function resolveInWorkspace(root, targetPath = '.', config = {}) {
|
|
63
92
|
const absRoot = path.resolve(root);
|
|
64
|
-
const
|
|
93
|
+
const realRoots = await getAllowedRealRoots(absRoot, config);
|
|
94
|
+
if (realRoots.length === 0) {
|
|
95
|
+
throw new Error(`Path escapes workspace: ${targetPath}`);
|
|
96
|
+
}
|
|
65
97
|
const absTarget = path.resolve(absRoot, targetPath);
|
|
66
98
|
const realTarget = await realpathIfExists(absTarget);
|
|
67
99
|
if (realTarget) {
|
|
68
|
-
if (!
|
|
100
|
+
if (!isWithinAnyResolvedRoot(realRoots, realTarget)) {
|
|
69
101
|
throw new Error(`Path escapes workspace: ${targetPath}`);
|
|
70
102
|
}
|
|
71
|
-
|
|
103
|
+
const linkStat = await fs.lstat(absTarget);
|
|
104
|
+
return linkStat.isSymbolicLink() ? realTarget : absTarget;
|
|
72
105
|
}
|
|
73
106
|
|
|
74
107
|
let probe = path.dirname(absTarget);
|
|
@@ -84,10 +117,10 @@ async function resolveInWorkspace(root, targetPath = '.') {
|
|
|
84
117
|
}
|
|
85
118
|
|
|
86
119
|
const resolvedTarget = path.join(resolvedProbe, path.relative(probe, absTarget));
|
|
87
|
-
if (!
|
|
120
|
+
if (!isWithinAnyResolvedRoot(realRoots, resolvedTarget)) {
|
|
88
121
|
throw new Error(`Path escapes workspace: ${targetPath}`);
|
|
89
122
|
}
|
|
90
|
-
return
|
|
123
|
+
return absTarget;
|
|
91
124
|
}
|
|
92
125
|
|
|
93
126
|
async function getBackgroundTasksDir(root) {
|
|
@@ -95,6 +128,17 @@ async function getBackgroundTasksDir(root) {
|
|
|
95
128
|
}
|
|
96
129
|
|
|
97
130
|
function toWorkspaceRelative(root, absPath) {
|
|
131
|
+
const roots = [path.resolve(root)];
|
|
132
|
+
try {
|
|
133
|
+
const realRoot = realpathSync(root);
|
|
134
|
+
if (realRoot) roots.push(realRoot);
|
|
135
|
+
} catch {}
|
|
136
|
+
for (const candidateRoot of roots) {
|
|
137
|
+
const relative = path.relative(candidateRoot, absPath);
|
|
138
|
+
if (relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))) {
|
|
139
|
+
return normalizePath(relative);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
98
142
|
return normalizePath(path.relative(path.resolve(root), absPath));
|
|
99
143
|
}
|
|
100
144
|
|
|
@@ -544,8 +588,8 @@ async function mapLimit(items, limit, worker) {
|
|
|
544
588
|
|
|
545
589
|
const WALKER_CONCURRENCY = 8;
|
|
546
590
|
|
|
547
|
-
async function walkTextFiles(root, startPath = '.', fileTypes = []) {
|
|
548
|
-
const abs = await resolveInWorkspace(root, startPath);
|
|
591
|
+
async function walkTextFiles(root, startPath = '.', fileTypes = [], config = {}) {
|
|
592
|
+
const abs = await resolveInWorkspace(root, startPath, config);
|
|
549
593
|
const allowedExts = new Set((Array.isArray(fileTypes) ? fileTypes : []).map((item) => `.${String(item || '').replace(/^\./, '')}`));
|
|
550
594
|
|
|
551
595
|
async function visit(current) {
|
|
@@ -565,8 +609,8 @@ async function walkTextFiles(root, startPath = '.', fileTypes = []) {
|
|
|
565
609
|
return visit(abs);
|
|
566
610
|
}
|
|
567
611
|
|
|
568
|
-
async function walkWorkspaceEntries(root, startPath = '.', { includeHidden = false } = {}) {
|
|
569
|
-
const abs = await resolveInWorkspace(root, startPath);
|
|
612
|
+
async function walkWorkspaceEntries(root, startPath = '.', { includeHidden = false, config = {} } = {}) {
|
|
613
|
+
const abs = await resolveInWorkspace(root, startPath, config);
|
|
570
614
|
|
|
571
615
|
async function visit(current) {
|
|
572
616
|
const stat = await fs.stat(current);
|
|
@@ -799,8 +843,8 @@ function findEnclosingSymbolLine(lines, anchorLine) {
|
|
|
799
843
|
return 0;
|
|
800
844
|
}
|
|
801
845
|
|
|
802
|
-
async function getFileState(root, relativePath) {
|
|
803
|
-
const target = await resolveInWorkspace(root, relativePath);
|
|
846
|
+
async function getFileState(root, relativePath, config = {}) {
|
|
847
|
+
const target = await resolveInWorkspace(root, relativePath, config);
|
|
804
848
|
const stat = await fs.stat(target);
|
|
805
849
|
const content = await fs.readFile(target, 'utf8');
|
|
806
850
|
return {
|
|
@@ -811,9 +855,9 @@ async function getFileState(root, relativePath) {
|
|
|
811
855
|
};
|
|
812
856
|
}
|
|
813
857
|
|
|
814
|
-
async function readFile(root, args) {
|
|
858
|
+
async function readFile(root, args, config = {}) {
|
|
815
859
|
const normalizedArgs = normalizeReadArgs(args);
|
|
816
|
-
const target = await resolveInWorkspace(root, normalizedArgs?.path);
|
|
860
|
+
const target = await resolveInWorkspace(root, normalizedArgs?.path, config);
|
|
817
861
|
const stat = await fs.stat(target);
|
|
818
862
|
const text = await fs.readFile(target, 'utf8');
|
|
819
863
|
const lines = splitLines(text);
|
|
@@ -893,7 +937,7 @@ async function readFile(root, args) {
|
|
|
893
937
|
};
|
|
894
938
|
}
|
|
895
939
|
|
|
896
|
-
async function writeFile(root, args) {
|
|
940
|
+
async function writeFile(root, args, config = {}) {
|
|
897
941
|
const normalizedArgs = normalizeWriteArgs(args);
|
|
898
942
|
const rawPath = String(normalizedArgs?.path || '').trim();
|
|
899
943
|
if (!rawPath) {
|
|
@@ -902,7 +946,7 @@ async function writeFile(root, args) {
|
|
|
902
946
|
if (rawPath === '.' || rawPath === './') {
|
|
903
947
|
throw new Error('write requires a file path, not the workspace root');
|
|
904
948
|
}
|
|
905
|
-
const target = await resolveInWorkspace(root, rawPath);
|
|
949
|
+
const target = await resolveInWorkspace(root, rawPath, config);
|
|
906
950
|
try {
|
|
907
951
|
const stat = await fs.stat(target);
|
|
908
952
|
if (stat.isDirectory()) {
|
|
@@ -954,21 +998,21 @@ async function writeFile(root, args) {
|
|
|
954
998
|
};
|
|
955
999
|
}
|
|
956
1000
|
|
|
957
|
-
async function prepareDeleteTarget(root, args) {
|
|
1001
|
+
async function prepareDeleteTarget(root, args, config = {}) {
|
|
958
1002
|
const normalizedArgs = normalizePathArgs(args, ['file', 'file_path', 'target', 'directory', 'dir']);
|
|
959
1003
|
const rawPath = String(normalizedArgs?.path || '').trim();
|
|
960
1004
|
if (!rawPath) {
|
|
961
1005
|
throw new Error('delete requires a file or directory path');
|
|
962
1006
|
}
|
|
963
1007
|
const absRoot = path.resolve(root);
|
|
964
|
-
const
|
|
1008
|
+
const realRoots = await getAllowedRealRoots(absRoot, config);
|
|
965
1009
|
const originalTarget = path.resolve(absRoot, rawPath);
|
|
966
1010
|
if (originalTarget === absRoot) {
|
|
967
1011
|
throw new Error('delete requires a path inside the workspace, not the workspace root');
|
|
968
1012
|
}
|
|
969
|
-
const resolvedTarget = await resolveInWorkspace(root, rawPath);
|
|
970
|
-
if (resolvedTarget === realRoot) {
|
|
971
|
-
throw new Error('delete requires a path inside the workspace, not
|
|
1013
|
+
const resolvedTarget = await resolveInWorkspace(root, rawPath, config);
|
|
1014
|
+
if (realRoots.some((realRoot) => resolvedTarget === realRoot)) {
|
|
1015
|
+
throw new Error('delete requires a path inside the workspace or allowed paths, not an allowed root');
|
|
972
1016
|
}
|
|
973
1017
|
|
|
974
1018
|
let rawStat;
|
|
@@ -998,8 +1042,8 @@ async function prepareDeleteTarget(root, args) {
|
|
|
998
1042
|
};
|
|
999
1043
|
}
|
|
1000
1044
|
|
|
1001
|
-
async function deletePath(root, args) {
|
|
1002
|
-
const target = await prepareDeleteTarget(root, args);
|
|
1045
|
+
async function deletePath(root, args, config = {}) {
|
|
1046
|
+
const target = await prepareDeleteTarget(root, args, config);
|
|
1003
1047
|
await fs.rm(target.originalTarget, { recursive: true, force: false });
|
|
1004
1048
|
|
|
1005
1049
|
return {
|
|
@@ -1350,13 +1394,13 @@ async function stopBackgroundTask(_root, args) {
|
|
|
1350
1394
|
return { ...snapshotBackgroundTask(task), stopped: true };
|
|
1351
1395
|
}
|
|
1352
1396
|
|
|
1353
|
-
async function builtinGrep(root, args) {
|
|
1397
|
+
async function builtinGrep(root, args, config = {}) {
|
|
1354
1398
|
const normalizedArgs = normalizePatternArgs(args, ['query', 'symbol', 'q'], ['directory', 'dir', 'cwd']);
|
|
1355
1399
|
const pattern = String(normalizedArgs?.pattern || '').trim();
|
|
1356
1400
|
if (!pattern) throw new Error('grep requires pattern');
|
|
1357
1401
|
const maxResults = Math.max(1, Math.min(200, Number(normalizedArgs?.max_results || 50)));
|
|
1358
1402
|
const caseSensitive = Boolean(normalizedArgs?.case_sensitive);
|
|
1359
|
-
const files = await walkTextFiles(root, normalizedArgs?.path || '.', normalizeFileTypes(normalizedArgs));
|
|
1403
|
+
const files = await walkTextFiles(root, normalizedArgs?.path || '.', normalizeFileTypes(normalizedArgs), config);
|
|
1360
1404
|
const regex = normalizedArgs?.regex
|
|
1361
1405
|
? new RegExp(pattern, caseSensitive ? 'g' : 'gi')
|
|
1362
1406
|
: new RegExp(escapeRegex(pattern), caseSensitive ? 'g' : 'gi');
|
|
@@ -1385,14 +1429,15 @@ async function builtinGrep(root, args) {
|
|
|
1385
1429
|
return { pattern, matches, truncated: false };
|
|
1386
1430
|
}
|
|
1387
1431
|
|
|
1388
|
-
async function builtinGlob(root, args) {
|
|
1432
|
+
async function builtinGlob(root, args, config = {}) {
|
|
1389
1433
|
const normalizedArgs = normalizePatternArgs(args, ['glob', 'query'], ['directory', 'dir', 'cwd']);
|
|
1390
1434
|
const pattern = String(normalizedArgs?.pattern || '').trim();
|
|
1391
1435
|
if (!pattern) throw new Error('glob requires pattern');
|
|
1392
1436
|
const maxResults = Math.max(1, Math.min(500, Number(normalizedArgs?.max_results || 200)));
|
|
1393
1437
|
const regex = globToRegex(pattern);
|
|
1394
1438
|
const entries = await walkWorkspaceEntries(root, normalizedArgs?.path || '.', {
|
|
1395
|
-
includeHidden: Boolean(normalizedArgs?.include_hidden)
|
|
1439
|
+
includeHidden: Boolean(normalizedArgs?.include_hidden),
|
|
1440
|
+
config
|
|
1396
1441
|
});
|
|
1397
1442
|
const matches = entries
|
|
1398
1443
|
.filter((entry) => entry.type === 'file' && regex.test(entry.path))
|
|
@@ -1405,10 +1450,10 @@ async function builtinGlob(root, args) {
|
|
|
1405
1450
|
};
|
|
1406
1451
|
}
|
|
1407
1452
|
|
|
1408
|
-
async function builtinList(root, args) {
|
|
1453
|
+
async function builtinList(root, args, config = {}) {
|
|
1409
1454
|
const normalizedArgs = normalizePathArgs(args, ['dir', 'directory', 'target']);
|
|
1410
1455
|
const relativePath = String(normalizedArgs?.path || '.').trim() || '.';
|
|
1411
|
-
const target = await resolveInWorkspace(root, relativePath);
|
|
1456
|
+
const target = await resolveInWorkspace(root, relativePath, config);
|
|
1412
1457
|
const entries = await fs.readdir(target, { withFileTypes: true });
|
|
1413
1458
|
const includeHidden = Boolean(normalizedArgs?.include_hidden);
|
|
1414
1459
|
const items = entries
|
|
@@ -1428,10 +1473,10 @@ async function builtinList(root, args) {
|
|
|
1428
1473
|
};
|
|
1429
1474
|
}
|
|
1430
1475
|
|
|
1431
|
-
async function readBlock(root, args) {
|
|
1476
|
+
async function readBlock(root, args, config = {}) {
|
|
1432
1477
|
const relativePath = String(args?.path || '').trim();
|
|
1433
1478
|
if (!relativePath) throw new Error('read_block requires path');
|
|
1434
|
-
const { lines } = await getFileState(root, relativePath);
|
|
1479
|
+
const { lines } = await getFileState(root, relativePath, config);
|
|
1435
1480
|
const symbol = String(args?.symbol || '').trim();
|
|
1436
1481
|
const anchorLine = symbol ? findSymbolDefinition(lines, symbol) : Number(args?.line || args?.anchor_line || 1);
|
|
1437
1482
|
const range = findBlockRange(lines, anchorLine);
|
|
@@ -1445,12 +1490,12 @@ async function readBlock(root, args) {
|
|
|
1445
1490
|
};
|
|
1446
1491
|
}
|
|
1447
1492
|
|
|
1448
|
-
async function readSymbolContext(root, args) {
|
|
1493
|
+
async function readSymbolContext(root, args, config = {}) {
|
|
1449
1494
|
const relativePath = String(args?.path || '').trim();
|
|
1450
1495
|
const symbol = String(args?.symbol || '').trim();
|
|
1451
1496
|
if (!relativePath || !symbol) throw new Error('read_symbol_context requires path and symbol');
|
|
1452
|
-
const { lines } = await getFileState(root, relativePath);
|
|
1453
|
-
const mainBlock = await readBlock(root, { path: relativePath, symbol });
|
|
1497
|
+
const { lines } = await getFileState(root, relativePath, config);
|
|
1498
|
+
const mainBlock = await readBlock(root, { path: relativePath, symbol }, config);
|
|
1454
1499
|
return {
|
|
1455
1500
|
file: relativePath,
|
|
1456
1501
|
symbol,
|
|
@@ -1468,11 +1513,11 @@ async function readSymbolContext(root, args) {
|
|
|
1468
1513
|
};
|
|
1469
1514
|
}
|
|
1470
1515
|
|
|
1471
|
-
async function validateEdit(root, args) {
|
|
1516
|
+
async function validateEdit(root, args, config = {}) {
|
|
1472
1517
|
const relativePath = String(args?.path || '').trim();
|
|
1473
1518
|
const kind = String(args?.kind || '').trim();
|
|
1474
1519
|
if (!relativePath || !kind) throw new Error('validate_edit requires path and kind');
|
|
1475
|
-
const { content, lines } = await getFileState(root, relativePath);
|
|
1520
|
+
const { content, lines } = await getFileState(root, relativePath, config);
|
|
1476
1521
|
|
|
1477
1522
|
if (kind === 'replace_block') {
|
|
1478
1523
|
const startLine = Number(args?.target?.start_line || args?.start_line);
|
|
@@ -1563,11 +1608,11 @@ function editResult(pathText, action, beforeContent, afterContent, changedLine =
|
|
|
1563
1608
|
};
|
|
1564
1609
|
}
|
|
1565
1610
|
|
|
1566
|
-
async function replaceBlock(root, args) {
|
|
1611
|
+
async function replaceBlock(root, args, config = {}) {
|
|
1567
1612
|
const relativePath = String(args?.path || '').trim();
|
|
1568
1613
|
const newContent = String(args?.new_content || args?.content || '');
|
|
1569
1614
|
const target = args?.target || {};
|
|
1570
|
-
const state = await getFileState(root, relativePath);
|
|
1615
|
+
const state = await getFileState(root, relativePath, config);
|
|
1571
1616
|
const resolved = resolveReplaceBlockTarget(state, target);
|
|
1572
1617
|
if (!resolved) {
|
|
1573
1618
|
throw new Error('replace_block old_hash mismatch; retry through edit with a symbol or line hint');
|
|
@@ -1582,11 +1627,11 @@ async function replaceBlock(root, args) {
|
|
|
1582
1627
|
return editResult(relativePath, 'replace_block', state.content, afterContent, resolved.start_line);
|
|
1583
1628
|
}
|
|
1584
1629
|
|
|
1585
|
-
async function replaceText(root, args) {
|
|
1630
|
+
async function replaceText(root, args, config = {}) {
|
|
1586
1631
|
const relativePath = String(args?.path || '').trim();
|
|
1587
1632
|
const oldText = String(args?.old_text || '');
|
|
1588
1633
|
const newText = String(args?.new_text || '');
|
|
1589
|
-
const state = await getFileState(root, relativePath);
|
|
1634
|
+
const state = await getFileState(root, relativePath, config);
|
|
1590
1635
|
const occurrences = state.content.split(oldText).length - 1;
|
|
1591
1636
|
if (occurrences !== 1) {
|
|
1592
1637
|
throw new Error(
|
|
@@ -1601,11 +1646,11 @@ async function replaceText(root, args) {
|
|
|
1601
1646
|
return editResult(relativePath, 'replace_text', state.content, afterContent, changedLine);
|
|
1602
1647
|
}
|
|
1603
1648
|
|
|
1604
|
-
async function insertRelative(root, args, mode) {
|
|
1649
|
+
async function insertRelative(root, args, mode, config = {}) {
|
|
1605
1650
|
const relativePath = String(args?.path || '').trim();
|
|
1606
1651
|
const anchorText = String(args?.anchor_text || '');
|
|
1607
1652
|
const content = String(args?.content || '');
|
|
1608
|
-
const state = await getFileState(root, relativePath);
|
|
1653
|
+
const state = await getFileState(root, relativePath, config);
|
|
1609
1654
|
const occurrences = state.content.split(anchorText).length - 1;
|
|
1610
1655
|
if (occurrences !== 1) {
|
|
1611
1656
|
throw new Error(occurrences === 0 ? `${mode} anchor not found` : `${mode} anchor not unique`);
|
|
@@ -1617,7 +1662,7 @@ async function insertRelative(root, args, mode) {
|
|
|
1617
1662
|
return editResult(relativePath, mode, state.content, afterContent, changedLine);
|
|
1618
1663
|
}
|
|
1619
1664
|
|
|
1620
|
-
async function openTarget(root, args) {
|
|
1665
|
+
async function openTarget(root, args, config = {}) {
|
|
1621
1666
|
const file = String(args?.file || args?.path || '').trim();
|
|
1622
1667
|
if (!file) throw new Error('open_target requires file');
|
|
1623
1668
|
const symbol = String(args?.symbol || '').trim();
|
|
@@ -1629,8 +1674,8 @@ async function openTarget(root, args) {
|
|
|
1629
1674
|
max_related_calls: args?.max_related_calls,
|
|
1630
1675
|
max_related_imports: args?.max_related_imports,
|
|
1631
1676
|
max_related_types: args?.max_related_types
|
|
1632
|
-
})
|
|
1633
|
-
: { file, symbol: '', main_block: await readBlock(root, { path: file, line }), related: { imports: [], local_symbols: [] } };
|
|
1677
|
+
}, config)
|
|
1678
|
+
: { file, symbol: '', main_block: await readBlock(root, { path: file, line }, config), related: { imports: [], local_symbols: [] } };
|
|
1634
1679
|
const block = mainBlock.main_block || mainBlock;
|
|
1635
1680
|
return {
|
|
1636
1681
|
file,
|
|
@@ -1682,7 +1727,7 @@ function normalizeEditTargetArgs(args = {}) {
|
|
|
1682
1727
|
};
|
|
1683
1728
|
}
|
|
1684
1729
|
|
|
1685
|
-
async function editTarget(root, args) {
|
|
1730
|
+
async function editTarget(root, args, config = {}) {
|
|
1686
1731
|
const normalized = normalizeEditTargetArgs(args);
|
|
1687
1732
|
const file = normalized.file;
|
|
1688
1733
|
const astTarget = normalized.ast_target;
|
|
@@ -1736,26 +1781,26 @@ async function editTarget(root, args) {
|
|
|
1736
1781
|
file,
|
|
1737
1782
|
symbol: edit.symbol || args?.symbol,
|
|
1738
1783
|
line: edit.line || args?.line
|
|
1739
|
-
})
|
|
1784
|
+
}, config)
|
|
1740
1785
|
).edit;
|
|
1741
1786
|
try {
|
|
1742
1787
|
return await replaceBlock(root, {
|
|
1743
1788
|
path: file,
|
|
1744
1789
|
target: resolvedTarget,
|
|
1745
1790
|
new_content: edit.new_content
|
|
1746
|
-
});
|
|
1791
|
+
}, config);
|
|
1747
1792
|
} catch (error) {
|
|
1748
1793
|
if (!/old_hash mismatch/i.test(String(error?.message || ''))) throw error;
|
|
1749
1794
|
const validation = await validateEdit(root, {
|
|
1750
1795
|
path: file,
|
|
1751
1796
|
kind: 'replace_block',
|
|
1752
1797
|
target: resolvedTarget
|
|
1753
|
-
});
|
|
1798
|
+
}, config);
|
|
1754
1799
|
return replaceBlock(root, {
|
|
1755
1800
|
path: file,
|
|
1756
1801
|
target: validation.target,
|
|
1757
1802
|
new_content: edit.new_content
|
|
1758
|
-
});
|
|
1803
|
+
}, config);
|
|
1759
1804
|
}
|
|
1760
1805
|
}
|
|
1761
1806
|
if (kind === 'replace_text') {
|
|
@@ -1763,20 +1808,20 @@ async function editTarget(root, args) {
|
|
|
1763
1808
|
path: file,
|
|
1764
1809
|
old_text: edit.old_text,
|
|
1765
1810
|
new_text: edit.new_text
|
|
1766
|
-
});
|
|
1811
|
+
}, config);
|
|
1767
1812
|
}
|
|
1768
1813
|
if (kind === 'insert_before') {
|
|
1769
|
-
return insertRelative(root, { path: file, anchor_text: edit.anchor_text, content: edit.content }, 'insert_before');
|
|
1814
|
+
return insertRelative(root, { path: file, anchor_text: edit.anchor_text, content: edit.content }, 'insert_before', config);
|
|
1770
1815
|
}
|
|
1771
1816
|
if (kind === 'insert_after') {
|
|
1772
|
-
return insertRelative(root, { path: file, anchor_text: edit.anchor_text, content: edit.content }, 'insert_after');
|
|
1817
|
+
return insertRelative(root, { path: file, anchor_text: edit.anchor_text, content: edit.content }, 'insert_after', config);
|
|
1773
1818
|
}
|
|
1774
1819
|
if (kind === 'rewrite_file') {
|
|
1775
1820
|
return writeFile(root, {
|
|
1776
1821
|
path: file,
|
|
1777
1822
|
content: edit.new_content ?? edit.content ?? '',
|
|
1778
1823
|
full_file_rewrite: true
|
|
1779
|
-
});
|
|
1824
|
+
}, config);
|
|
1780
1825
|
}
|
|
1781
1826
|
throw new Error(`edit does not support kind: ${kind}`);
|
|
1782
1827
|
}
|
|
@@ -2370,36 +2415,39 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2370
2415
|
}
|
|
2371
2416
|
|
|
2372
2417
|
async function grep(args) {
|
|
2373
|
-
|
|
2418
|
+
const normalizedArgs = normalizePatternArgs(args, ['query', 'symbol', 'q'], ['directory', 'dir', 'cwd']);
|
|
2419
|
+
if (!resolvesOutsideRoot(workspaceRoot, normalizedArgs?.path || '.') && activeFffAdapter?.grep) {
|
|
2374
2420
|
try {
|
|
2375
2421
|
await ensureFffConnected();
|
|
2376
2422
|
const result = await activeFffAdapter.grep(args);
|
|
2377
2423
|
if (result && Array.isArray(result.matches)) return result;
|
|
2378
2424
|
} catch {}
|
|
2379
2425
|
}
|
|
2380
|
-
return builtinGrep(workspaceRoot, args);
|
|
2426
|
+
return builtinGrep(workspaceRoot, args, config);
|
|
2381
2427
|
}
|
|
2382
2428
|
|
|
2383
2429
|
async function glob(args) {
|
|
2384
|
-
|
|
2430
|
+
const normalizedArgs = normalizePatternArgs(args, ['glob', 'query'], ['directory', 'dir', 'cwd']);
|
|
2431
|
+
if (!resolvesOutsideRoot(workspaceRoot, normalizedArgs?.path || '.') && activeFffAdapter?.glob) {
|
|
2385
2432
|
try {
|
|
2386
2433
|
await ensureFffConnected();
|
|
2387
2434
|
const result = await activeFffAdapter.glob(args);
|
|
2388
2435
|
if (result && Array.isArray(result.matches)) return result;
|
|
2389
2436
|
} catch {}
|
|
2390
2437
|
}
|
|
2391
|
-
return builtinGlob(workspaceRoot, args);
|
|
2438
|
+
return builtinGlob(workspaceRoot, args, config);
|
|
2392
2439
|
}
|
|
2393
2440
|
|
|
2394
2441
|
async function list(args) {
|
|
2395
|
-
|
|
2442
|
+
const normalizedArgs = normalizePathArgs(args, ['dir', 'directory', 'target']);
|
|
2443
|
+
if (!resolvesOutsideRoot(workspaceRoot, normalizedArgs?.path || '.') && activeFffAdapter?.list) {
|
|
2396
2444
|
try {
|
|
2397
2445
|
await ensureFffConnected();
|
|
2398
2446
|
const result = await activeFffAdapter.list(args);
|
|
2399
2447
|
if (result && Array.isArray(result.items)) return result;
|
|
2400
2448
|
} catch {}
|
|
2401
2449
|
}
|
|
2402
|
-
return builtinList(workspaceRoot, args);
|
|
2450
|
+
return builtinList(workspaceRoot, args, config);
|
|
2403
2451
|
}
|
|
2404
2452
|
|
|
2405
2453
|
const handlers = {
|
|
@@ -2455,7 +2503,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2455
2503
|
typeof args?.max_chars === 'number'
|
|
2456
2504
|
? args.max_chars
|
|
2457
2505
|
: config.context?.read_file_max_chars ?? 24000
|
|
2458
|
-
});
|
|
2506
|
+
}, config);
|
|
2459
2507
|
const readPath = String(result?.path || args?.path || '').trim();
|
|
2460
2508
|
if (readPath) lastReadPath = readPath;
|
|
2461
2509
|
return result;
|
|
@@ -2487,25 +2535,26 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2487
2535
|
const astTarget = resolveCachedAstTarget(args, { requireAstScope: normalizedKind === 'replace_block' });
|
|
2488
2536
|
const result = await editTarget(
|
|
2489
2537
|
workspaceRoot,
|
|
2490
|
-
astTarget ? { ...args, ast_target: astTarget, recent_file: lastReadPath } : { ...args, recent_file: lastReadPath }
|
|
2538
|
+
astTarget ? { ...args, ast_target: astTarget, recent_file: lastReadPath } : { ...args, recent_file: lastReadPath },
|
|
2539
|
+
config
|
|
2491
2540
|
);
|
|
2492
2541
|
if (result?.path) await refreshProjectFile(result.path);
|
|
2493
2542
|
return result;
|
|
2494
2543
|
},
|
|
2495
2544
|
write: async (args) => {
|
|
2496
2545
|
await ensureProjectIndex();
|
|
2497
|
-
const result = await writeFile(workspaceRoot, args);
|
|
2546
|
+
const result = await writeFile(workspaceRoot, args, config);
|
|
2498
2547
|
if (result?.path) await refreshProjectFile(result.path);
|
|
2499
2548
|
return result;
|
|
2500
2549
|
},
|
|
2501
2550
|
delete: Object.assign(async (args) => {
|
|
2502
2551
|
await ensureProjectIndex();
|
|
2503
|
-
const result = await deletePath(workspaceRoot, args);
|
|
2552
|
+
const result = await deletePath(workspaceRoot, args, config);
|
|
2504
2553
|
if (result?.path) await refreshProjectFile(result.path);
|
|
2505
2554
|
return result;
|
|
2506
2555
|
}, {
|
|
2507
2556
|
prepareApproval: async (args) => {
|
|
2508
|
-
const target = await prepareDeleteTarget(workspaceRoot, args);
|
|
2557
|
+
const target = await prepareDeleteTarget(workspaceRoot, args, config);
|
|
2509
2558
|
return {
|
|
2510
2559
|
path: target.path,
|
|
2511
2560
|
name: target.name,
|