minovative-mind-cli 2.11.4 → 2.12.0
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 +21 -7
- package/dist/commands/eval.d.ts +22 -0
- package/dist/commands/eval.js +141 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services/agent/toolLoop.d.ts +4 -0
- package/dist/services/agent/toolLoop.js +61 -10
- package/dist/services/agent-tools.d.ts +5 -5
- package/dist/services/agent-tools.js +147 -9
- package/dist/services/ai.d.ts +1 -1
- package/dist/services/ai.js +40 -7
- package/dist/services/contextAgent.d.ts +1 -0
- package/dist/services/contextAgent.js +29 -6
- package/dist/services/investigationComplexity.d.ts +39 -13
- package/dist/services/investigationComplexity.js +325 -46
- package/dist/services/metrics.d.ts +10 -0
- package/dist/services/metrics.js +24 -0
- package/dist/services/orchestration/scopedTools.d.ts +27 -50
- package/dist/services/orchestration/scopedTools.js +60 -18
- package/dist/services/swebench/gitDiffExtractor.d.ts +57 -0
- package/dist/services/swebench/gitDiffExtractor.js +209 -0
- package/dist/services/swebench/index.d.ts +4 -0
- package/dist/services/swebench/index.js +4 -0
- package/dist/services/swebench/instanceLoader.d.ts +21 -0
- package/dist/services/swebench/instanceLoader.js +171 -0
- package/dist/services/swebench/sweBenchRunnerService.d.ts +38 -0
- package/dist/services/swebench/sweBenchRunnerService.js +618 -0
- package/dist/services/swebench/types.d.ts +167 -0
- package/dist/services/swebench/types.js +7 -0
- package/dist/services/verificationService.js +3 -0
- package/dist/services/workspaceRegistry.d.ts +81 -8
- package/dist/services/workspaceRegistry.js +222 -34
- package/dist/utils/analysisRunner.js +2 -1
- package/dist/utils/pathSecurity.d.ts +56 -14
- package/dist/utils/pathSecurity.js +120 -39
- package/dist/utils/systemPrompts.d.ts +6 -6
- package/dist/utils/systemPrompts.js +46 -25
- package/oclif.manifest.json +137 -1
- package/package.json +1 -1
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { debugLog } from '../utils/logger.js';
|
|
4
|
+
import * as os from 'os';
|
|
4
5
|
/** Regex for valid alias names: lowercase alphanumeric, hyphens, underscores, 1–30 chars. */
|
|
5
6
|
const ALIAS_PATTERN = /^[a-z0-9][a-z0-9_-]{0,29}$/;
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Returns the global configuration directory for Minovative Mind CLI.
|
|
9
|
+
* During automated tests, returns an isolated temporary directory to prevent test runs
|
|
10
|
+
* from modifying the developer's live workspace configurations.
|
|
11
|
+
*/
|
|
12
|
+
export function getGlobalConfigDir() {
|
|
13
|
+
if (process.env.MINO_CONFIG_DIR) {
|
|
14
|
+
return process.env.MINO_CONFIG_DIR;
|
|
15
|
+
}
|
|
16
|
+
if (process.env.NODE_ENV === 'test' || typeof global.describe === 'function') {
|
|
17
|
+
return path.join(os.tmpdir(), '.minovative-mind-cli-test');
|
|
18
|
+
}
|
|
19
|
+
return path.join(process.env.HOME || process.env.USERPROFILE || '~', '.minovative-mind-cli');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Returns the active path to the workspace registry JSON file.
|
|
23
|
+
*/
|
|
24
|
+
export function getRegistryFile() {
|
|
25
|
+
return path.join(getGlobalConfigDir(), 'workspaces.json');
|
|
26
|
+
}
|
|
10
27
|
/**
|
|
11
28
|
* Service that manages a global registry of external workspace roots.
|
|
12
29
|
*
|
|
@@ -28,6 +45,7 @@ class WorkspaceRegistry {
|
|
|
28
45
|
profiles = new Map();
|
|
29
46
|
/** Whether the registry has been loaded from disk. */
|
|
30
47
|
initialized = false;
|
|
48
|
+
primarySubPath = null;
|
|
31
49
|
/**
|
|
32
50
|
* Initializes the registry by loading persisted workspace entries from disk.
|
|
33
51
|
* Safe to call multiple times — subsequent calls are no-ops.
|
|
@@ -38,6 +56,14 @@ class WorkspaceRegistry {
|
|
|
38
56
|
this.initialized = true;
|
|
39
57
|
this.loadFromDisk();
|
|
40
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Internal guard to guarantee the registry is loaded before any read or write operation.
|
|
61
|
+
*/
|
|
62
|
+
ensureInitialized() {
|
|
63
|
+
if (!this.initialized) {
|
|
64
|
+
this.init();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
41
67
|
/**
|
|
42
68
|
* Registers a new external workspace root with the given alias under a profile.
|
|
43
69
|
*
|
|
@@ -50,6 +76,7 @@ class WorkspaceRegistry {
|
|
|
50
76
|
* @throws Error if the alias is invalid, the path doesn't exist, or the alias is already taken.
|
|
51
77
|
*/
|
|
52
78
|
register(profile, alias, absolutePath) {
|
|
79
|
+
this.ensureInitialized();
|
|
53
80
|
const normalizedProfile = profile.toLowerCase().trim();
|
|
54
81
|
const normalizedAlias = alias.toLowerCase().trim();
|
|
55
82
|
// Ensure profile exists
|
|
@@ -110,6 +137,7 @@ class WorkspaceRegistry {
|
|
|
110
137
|
* @returns `true` if the workspace was found and removed, `false` otherwise.
|
|
111
138
|
*/
|
|
112
139
|
unregister(alias) {
|
|
140
|
+
this.ensureInitialized();
|
|
113
141
|
const normalizedAlias = alias.toLowerCase().trim();
|
|
114
142
|
const existed = this.workspaces.delete(normalizedAlias);
|
|
115
143
|
if (existed) {
|
|
@@ -125,6 +153,7 @@ class WorkspaceRegistry {
|
|
|
125
153
|
* @returns `true` if the profile was found and removed, `false` otherwise.
|
|
126
154
|
*/
|
|
127
155
|
unregisterProfile(profileName) {
|
|
156
|
+
this.ensureInitialized();
|
|
128
157
|
const normalizedProfile = profileName.toLowerCase().trim();
|
|
129
158
|
const existed = this.profiles.delete(normalizedProfile);
|
|
130
159
|
if (existed) {
|
|
@@ -144,30 +173,35 @@ class WorkspaceRegistry {
|
|
|
144
173
|
* Returns all registered workspaces as an array, sorted by alias.
|
|
145
174
|
*/
|
|
146
175
|
list() {
|
|
176
|
+
this.ensureInitialized();
|
|
147
177
|
return Array.from(this.workspaces.values()).sort((a, b) => a.alias.localeCompare(b.alias));
|
|
148
178
|
}
|
|
149
179
|
/**
|
|
150
180
|
* Returns all profiles (main workspaces).
|
|
151
181
|
*/
|
|
152
182
|
listProfiles() {
|
|
183
|
+
this.ensureInitialized();
|
|
153
184
|
return Array.from(this.profiles.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
154
185
|
}
|
|
155
186
|
/**
|
|
156
187
|
* Checks whether any profiles (main workspaces) exist.
|
|
157
188
|
*/
|
|
158
189
|
hasProfiles() {
|
|
190
|
+
this.ensureInitialized();
|
|
159
191
|
return this.profiles.size > 0;
|
|
160
192
|
}
|
|
161
193
|
/**
|
|
162
194
|
* Checks whether any workspaces exist.
|
|
163
195
|
*/
|
|
164
196
|
hasWorkspaces() {
|
|
197
|
+
this.ensureInitialized();
|
|
165
198
|
return this.workspaces.size > 0;
|
|
166
199
|
}
|
|
167
200
|
/**
|
|
168
201
|
* Returns all registered workspaces for a given profile.
|
|
169
202
|
*/
|
|
170
203
|
getWorkspacesByProfile(profile) {
|
|
204
|
+
this.ensureInitialized();
|
|
171
205
|
return this.list().filter((ws) => ws.profile === profile.toLowerCase().trim());
|
|
172
206
|
}
|
|
173
207
|
/**
|
|
@@ -177,6 +211,7 @@ class WorkspaceRegistry {
|
|
|
177
211
|
* @returns The registered workspace, or `undefined` if not found.
|
|
178
212
|
*/
|
|
179
213
|
get(alias) {
|
|
214
|
+
this.ensureInitialized();
|
|
180
215
|
return this.workspaces.get(alias.toLowerCase().trim());
|
|
181
216
|
}
|
|
182
217
|
/**
|
|
@@ -187,6 +222,7 @@ class WorkspaceRegistry {
|
|
|
187
222
|
* The primary workspace has `alias: null`.
|
|
188
223
|
*/
|
|
189
224
|
getAllRoots(primaryRoot) {
|
|
225
|
+
this.ensureInitialized();
|
|
190
226
|
const roots = [{ alias: null, root: primaryRoot }];
|
|
191
227
|
for (const ws of this.workspaces.values()) {
|
|
192
228
|
// Skip if a registered workspace happens to be the same as the primary
|
|
@@ -197,36 +233,182 @@ class WorkspaceRegistry {
|
|
|
197
233
|
return roots;
|
|
198
234
|
}
|
|
199
235
|
/**
|
|
200
|
-
*
|
|
236
|
+
* Sets the active primary sub-path for auto-focusing relative file queries.
|
|
237
|
+
*
|
|
238
|
+
* @param subPath - The relative directory path within the primary workspace (e.g., "src/services"), or null to clear.
|
|
239
|
+
*/
|
|
240
|
+
setPrimarySubPath(subPath) {
|
|
241
|
+
if (!subPath || subPath.trim() === '' || subPath.trim() === '.') {
|
|
242
|
+
this.primarySubPath = null;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const trimmed = subPath.trim();
|
|
246
|
+
if (trimmed.includes('\0')) {
|
|
247
|
+
throw new Error(`Invalid primary sub-path containing null bytes: "${trimmed}"`);
|
|
248
|
+
}
|
|
249
|
+
if (path.isAbsolute(trimmed) || trimmed.startsWith('/') || trimmed.startsWith('\\')) {
|
|
250
|
+
throw new Error(`Primary sub-path traversal detected: "${subPath}" is an absolute path. Primary sub-paths must be relative.`);
|
|
251
|
+
}
|
|
252
|
+
const normalized = path.normalize(trimmed).replace(/[/\\]+$/, '');
|
|
253
|
+
if (normalized.startsWith('..') || path.isAbsolute(normalized)) {
|
|
254
|
+
throw new Error(`Primary sub-path traversal detected: "${subPath}" escapes primary workspace root.`);
|
|
255
|
+
}
|
|
256
|
+
this.primarySubPath = normalized;
|
|
257
|
+
debugLog(`[WorkspaceRegistry] Primary sub-path set to: "${this.primarySubPath}"`);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Returns the currently active primary sub-path, or null if none is set.
|
|
261
|
+
*/
|
|
262
|
+
getPrimarySubPath() {
|
|
263
|
+
return this.primarySubPath;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Returns whether a primary sub-path is currently active.
|
|
267
|
+
*/
|
|
268
|
+
hasPrimarySubPath() {
|
|
269
|
+
return this.primarySubPath !== null;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Clears the active primary sub-path.
|
|
273
|
+
*/
|
|
274
|
+
clearPrimarySubPath() {
|
|
275
|
+
this.primarySubPath = null;
|
|
276
|
+
debugLog('[WorkspaceRegistry] Primary sub-path cleared.');
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Gets the focused root directory for the given primary workspace root.
|
|
280
|
+
* If a primary sub-path is active, returns the resolved sub-path directory;
|
|
281
|
+
* otherwise returns the primary root.
|
|
282
|
+
*
|
|
283
|
+
* @param primaryRoot - The base primary workspace root directory.
|
|
284
|
+
*/
|
|
285
|
+
getFocusedRoot(primaryRoot) {
|
|
286
|
+
if (!this.primarySubPath) {
|
|
287
|
+
return path.resolve(primaryRoot);
|
|
288
|
+
}
|
|
289
|
+
return path.resolve(primaryRoot, this.primarySubPath);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Checks whether a target path is securely contained within a given boundary root.
|
|
293
|
+
*
|
|
294
|
+
* @param targetPath - The absolute or relative target path.
|
|
295
|
+
* @param boundaryRoot - The boundary root directory.
|
|
296
|
+
*/
|
|
297
|
+
isPathWithinBoundary(targetPath, boundaryRoot) {
|
|
298
|
+
const absBoundary = path.resolve(boundaryRoot);
|
|
299
|
+
const absTarget = path.resolve(absBoundary, targetPath);
|
|
300
|
+
const rel = path.relative(absBoundary, absTarget);
|
|
301
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Finds a registered workspace that contains the given file or directory path.
|
|
305
|
+
*
|
|
306
|
+
* @param targetPath - The file or directory path to check.
|
|
307
|
+
* @returns The matching `RegisteredWorkspace` or null if not found.
|
|
308
|
+
*/
|
|
309
|
+
findWorkspaceForPath(targetPath) {
|
|
310
|
+
this.ensureInitialized();
|
|
311
|
+
const absTarget = path.resolve(targetPath);
|
|
312
|
+
for (const ws of this.workspaces.values()) {
|
|
313
|
+
if (this.isPathWithinBoundary(absTarget, ws.absolutePath)) {
|
|
314
|
+
return ws;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Checks whether a path resides inside the primary workspace or any registered workspace.
|
|
321
|
+
*
|
|
322
|
+
* @param targetPath - The file or directory path to inspect.
|
|
323
|
+
* @param primaryRoot - Optional primary workspace root.
|
|
324
|
+
*/
|
|
325
|
+
isInsideRegisteredWorkspace(targetPath, primaryRoot) {
|
|
326
|
+
this.ensureInitialized();
|
|
327
|
+
const absTarget = path.resolve(targetPath);
|
|
328
|
+
if (primaryRoot && this.isPathWithinBoundary(absTarget, primaryRoot)) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
return this.findWorkspaceForPath(absTarget) !== null;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Resolves an `@alias/relative/path` string or primary workspace path into constituent parts.
|
|
201
335
|
*
|
|
202
336
|
* @param filePath - A file path that may or may not start with `@alias/`.
|
|
203
|
-
* @
|
|
204
|
-
*
|
|
205
|
-
* workspace-relative path (no `@` prefix or unrecognized alias).
|
|
337
|
+
* @param options - Optional resolution options including primaryRoot and autoFocusSubPath.
|
|
338
|
+
* @returns A `ResolvedWorkspacePath` if resolved, or `null` if the path cannot be resolved.
|
|
206
339
|
* @throws Error if the path has an `@` prefix but the alias is not registered.
|
|
207
340
|
*/
|
|
208
|
-
resolve(filePath) {
|
|
209
|
-
|
|
210
|
-
|
|
341
|
+
resolve(filePath, options) {
|
|
342
|
+
this.ensureInitialized();
|
|
343
|
+
if (filePath.startsWith('@')) {
|
|
344
|
+
// Extract alias and relative path from "@alias/relative/path"
|
|
345
|
+
const withoutAt = filePath.substring(1);
|
|
346
|
+
const slashIndex = withoutAt.indexOf('/');
|
|
347
|
+
const alias = slashIndex === -1 ? withoutAt : withoutAt.substring(0, slashIndex);
|
|
348
|
+
const relativePath = slashIndex === -1 ? '.' : withoutAt.substring(slashIndex + 1);
|
|
349
|
+
const workspace = this.workspaces.get(alias.toLowerCase());
|
|
350
|
+
if (!workspace) {
|
|
351
|
+
throw new Error(`Unknown workspace alias "@${alias}". ` +
|
|
352
|
+
`Registered workspaces: ${this.list().map((w) => `@${w.alias}`).join(', ') || '(none)'}. ` +
|
|
353
|
+
`Use /workspaces to add one.`);
|
|
354
|
+
}
|
|
355
|
+
const absolutePath = path.resolve(workspace.absolutePath, relativePath);
|
|
356
|
+
return {
|
|
357
|
+
alias: workspace.alias,
|
|
358
|
+
workspaceRoot: workspace.absolutePath,
|
|
359
|
+
relativePath,
|
|
360
|
+
absolutePath,
|
|
361
|
+
};
|
|
211
362
|
}
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
363
|
+
// Non-aliased path resolution
|
|
364
|
+
if (options?.primaryRoot) {
|
|
365
|
+
const primaryRoot = path.resolve(options.primaryRoot);
|
|
366
|
+
const shouldAutoFocus = options.autoFocusSubPath ?? true;
|
|
367
|
+
if (shouldAutoFocus && this.primarySubPath) {
|
|
368
|
+
const focusedRoot = this.getFocusedRoot(primaryRoot);
|
|
369
|
+
const focusedCandidate = path.resolve(focusedRoot, filePath);
|
|
370
|
+
// If candidate exists under focused subpath, auto-focus it
|
|
371
|
+
if (this.isPathWithinBoundary(focusedCandidate, primaryRoot) && fs.existsSync(focusedCandidate)) {
|
|
372
|
+
return {
|
|
373
|
+
alias: null,
|
|
374
|
+
workspaceRoot: primaryRoot,
|
|
375
|
+
relativePath: path.relative(primaryRoot, focusedCandidate),
|
|
376
|
+
absolutePath: focusedCandidate,
|
|
377
|
+
isAutoFocused: true,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// Default primary resolution
|
|
382
|
+
const defaultResolved = path.resolve(primaryRoot, filePath);
|
|
383
|
+
return {
|
|
384
|
+
alias: null,
|
|
385
|
+
workspaceRoot: primaryRoot,
|
|
386
|
+
relativePath: path.relative(primaryRoot, defaultResolved),
|
|
387
|
+
absolutePath: defaultResolved,
|
|
388
|
+
isAutoFocused: false,
|
|
389
|
+
};
|
|
222
390
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Resolves a relative path against the primary root with automatic sub-path focusing.
|
|
395
|
+
*
|
|
396
|
+
* @param primaryRoot - The base primary workspace root directory.
|
|
397
|
+
* @param relativePath - The target relative path to resolve.
|
|
398
|
+
*/
|
|
399
|
+
resolveWithAutoFocus(primaryRoot, relativePath) {
|
|
400
|
+
const res = this.resolve(relativePath, { primaryRoot, autoFocusSubPath: true });
|
|
401
|
+
if (!res) {
|
|
402
|
+
const abs = path.resolve(primaryRoot, relativePath);
|
|
403
|
+
return {
|
|
404
|
+
alias: null,
|
|
405
|
+
workspaceRoot: primaryRoot,
|
|
406
|
+
relativePath: path.relative(primaryRoot, abs),
|
|
407
|
+
absolutePath: abs,
|
|
408
|
+
isAutoFocused: false,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return res;
|
|
230
412
|
}
|
|
231
413
|
/**
|
|
232
414
|
* Checks if a given file path uses the `@alias/` prefix syntax.
|
|
@@ -256,11 +438,12 @@ class WorkspaceRegistry {
|
|
|
256
438
|
*/
|
|
257
439
|
loadFromDisk() {
|
|
258
440
|
try {
|
|
259
|
-
|
|
441
|
+
const registryFile = getRegistryFile();
|
|
442
|
+
if (!fs.existsSync(registryFile)) {
|
|
260
443
|
debugLog('Workspace registry file not found, starting fresh.');
|
|
261
444
|
return;
|
|
262
445
|
}
|
|
263
|
-
const raw = fs.readFileSync(
|
|
446
|
+
const raw = fs.readFileSync(registryFile, 'utf-8');
|
|
264
447
|
const data = JSON.parse(raw);
|
|
265
448
|
if (!data || !Array.isArray(data.workspaces)) {
|
|
266
449
|
// Fallback for old schema
|
|
@@ -313,19 +496,24 @@ class WorkspaceRegistry {
|
|
|
313
496
|
* Creates the config directory if it doesn't exist.
|
|
314
497
|
*/
|
|
315
498
|
saveToDisk() {
|
|
499
|
+
if (!this.initialized) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
316
502
|
try {
|
|
503
|
+
const configDir = getGlobalConfigDir();
|
|
504
|
+
const registryFile = getRegistryFile();
|
|
317
505
|
// Ensure the global config directory exists
|
|
318
|
-
if (!fs.existsSync(
|
|
319
|
-
fs.mkdirSync(
|
|
506
|
+
if (!fs.existsSync(configDir)) {
|
|
507
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
320
508
|
}
|
|
321
509
|
const data = {
|
|
322
510
|
workspaces: Array.from(this.workspaces.values()),
|
|
323
511
|
profiles: Array.from(this.profiles.values()),
|
|
324
512
|
};
|
|
325
|
-
const tempPath = `${
|
|
513
|
+
const tempPath = `${registryFile}.${Date.now()}.tmp`;
|
|
326
514
|
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
327
515
|
// Atomic rename to prevent corruption
|
|
328
|
-
fs.renameSync(tempPath,
|
|
516
|
+
fs.renameSync(tempPath, registryFile);
|
|
329
517
|
debugLog(`Saved ${data.workspaces.length} workspace(s) and ${data.profiles.length} profile(s) to global registry.`);
|
|
330
518
|
}
|
|
331
519
|
catch (err) {
|
|
@@ -250,7 +250,8 @@ export async function resolveScriptExtension(workspaceRoot, language, code) {
|
|
|
250
250
|
*/
|
|
251
251
|
function buildTempPath(code, ext) {
|
|
252
252
|
const hash = createHash('sha256').update(code).digest('hex').substring(0, 12);
|
|
253
|
-
|
|
253
|
+
const nonce = `${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
|
|
254
|
+
return path.join(os.tmpdir(), `.mino-analysis-${hash}-${nonce}${ext}`);
|
|
254
255
|
}
|
|
255
256
|
/**
|
|
256
257
|
* Constructs the shell command for executing the given language runtime.
|
|
@@ -11,31 +11,73 @@ export interface ResolvedPath {
|
|
|
11
11
|
relativePath: string;
|
|
12
12
|
/** The workspace alias if an external workspace was matched, or `null` for the primary workspace. */
|
|
13
13
|
alias: string | null;
|
|
14
|
+
/** Whether the path was resolved via primary sub-path auto-focusing. */
|
|
15
|
+
isAutoFocused?: boolean;
|
|
14
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Options controlling path resolution and sub-path auto-focusing.
|
|
19
|
+
*/
|
|
20
|
+
export interface PathResolutionOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Whether to attempt sub-path auto-focusing if a primary sub-path is configured
|
|
23
|
+
* in WorkspaceRegistry. Defaults to true.
|
|
24
|
+
*/
|
|
25
|
+
autoFocusSubPath?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Optional explicit sub-path to focus on for this resolution.
|
|
28
|
+
*/
|
|
29
|
+
subPath?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Checks whether a candidate path safely resides within a boundary root without throwing.
|
|
33
|
+
*
|
|
34
|
+
* @param boundaryRoot - The boundary root directory path.
|
|
35
|
+
* @param candidatePath - The absolute or relative path to test.
|
|
36
|
+
* @returns true if candidatePath is within boundaryRoot, false otherwise.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isWithinWorkspaceBoundary(boundaryRoot: string, candidatePath: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Validates that a candidate path resides strictly within the given boundary root.
|
|
41
|
+
*
|
|
42
|
+
* @param boundaryRoot - The boundary root directory path.
|
|
43
|
+
* @param candidatePath - The candidate path to validate.
|
|
44
|
+
* @param contextLabel - Optional label for descriptive error messages.
|
|
45
|
+
* @throws Error if the path escapes the boundary.
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateWorkspaceBoundary(boundaryRoot: string, candidatePath: string, contextLabel?: string): void;
|
|
15
48
|
/**
|
|
16
49
|
* Resolves a file path against the workspace root and ensures it does not
|
|
17
50
|
* break out of the workspace directory (path traversal defense).
|
|
18
51
|
*
|
|
19
52
|
* @param workspaceRoot The normalized absolute path to the workspace root
|
|
20
53
|
* @param filePath The user or AI provided file path (relative or absolute)
|
|
54
|
+
* @param options Optional resolution options
|
|
21
55
|
* @returns The validated absolute path
|
|
22
|
-
* @throws Error if the resolved path is outside the workspace
|
|
56
|
+
* @throws Error if the resolved path is outside the workspace or invalid
|
|
23
57
|
*/
|
|
24
|
-
export declare function resolveAndValidatePath(workspaceRoot: string, filePath: string): string;
|
|
58
|
+
export declare function resolveAndValidatePath(workspaceRoot: string, filePath: string, options?: PathResolutionOptions): string;
|
|
25
59
|
/**
|
|
26
|
-
* Resolves a file path that may
|
|
60
|
+
* Resolves a file path that may start with `@alias/` to reference an external workspace,
|
|
61
|
+
* or resolves against the primary workspace with automatic sub-path focusing.
|
|
27
62
|
*
|
|
28
|
-
* If
|
|
29
|
-
*
|
|
63
|
+
* - If `filePath` starts with `@alias/relative/path`, it looks up the alias in the
|
|
64
|
+
* `WorkspaceRegistry`, resolves the path within that workspace's root, and validates
|
|
65
|
+
* that it does not escape that workspace boundary.
|
|
66
|
+
* - Otherwise, it validates and resolves against `primaryRoot`, applying active
|
|
67
|
+
* primary sub-path auto-focusing if configured.
|
|
30
68
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
69
|
+
* @param primaryRoot The normalized absolute path to the primary (default) workspace root
|
|
70
|
+
* @param filePath A path that may be `@alias/sub/path` or a standard workspace-relative path
|
|
71
|
+
* @param options Optional path resolution options
|
|
72
|
+
* @returns A `ResolvedPath` with the absolute path, workspace root, relative path, and alias
|
|
73
|
+
* @throws Error if the alias is unknown or if a traversal attack is detected
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveAndValidateMultiWorkspacePath(primaryRoot: string, filePath: string, options?: PathResolutionOptions): ResolvedPath;
|
|
76
|
+
/**
|
|
77
|
+
* Resolves a path explicitly focused on a specific primary sub-path.
|
|
35
78
|
*
|
|
36
|
-
* @param primaryRoot - The primary workspace root
|
|
37
|
-
* @param
|
|
38
|
-
* @
|
|
39
|
-
* @throws Error on path traversal, unknown alias, or invalid path.
|
|
79
|
+
* @param primaryRoot - The base primary workspace root directory.
|
|
80
|
+
* @param subPath - The relative sub-path to focus on.
|
|
81
|
+
* @param filePath - The target file path.
|
|
40
82
|
*/
|
|
41
|
-
export declare function
|
|
83
|
+
export declare function resolveFocusedPath(primaryRoot: string, subPath: string, filePath: string): ResolvedPath;
|