minovative-mind-cli 2.11.3 → 2.11.5

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.
@@ -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
- /** Global config directory for cross-project persistence. */
7
- const GLOBAL_CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '~', '.minovative-mind-cli');
8
- /** Path to the global workspace registry file. */
9
- const REGISTRY_FILE = path.join(GLOBAL_CONFIG_DIR, 'workspaces.json');
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
- * Resolves an `@alias/relative/path` string into its constituent parts.
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
- * @returns A `ResolvedWorkspacePath` if the path has a valid `@alias/` prefix
204
- * and the alias is registered, or `null` if the path is a standard
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
- if (!filePath.startsWith('@')) {
210
- return null;
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
- // Extract alias and relative path from "@alias/relative/path"
213
- const withoutAt = filePath.substring(1);
214
- const slashIndex = withoutAt.indexOf('/');
215
- const alias = slashIndex === -1 ? withoutAt : withoutAt.substring(0, slashIndex);
216
- const relativePath = slashIndex === -1 ? '.' : withoutAt.substring(slashIndex + 1);
217
- const workspace = this.workspaces.get(alias.toLowerCase());
218
- if (!workspace) {
219
- throw new Error(`Unknown workspace alias "@${alias}". ` +
220
- `Registered workspaces: ${this.list().map((w) => `@${w.alias}`).join(', ') || '(none)'}. ` +
221
- `Use /workspaces to add one.`);
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
- const absolutePath = path.resolve(workspace.absolutePath, relativePath);
224
- return {
225
- alias: workspace.alias,
226
- workspaceRoot: workspace.absolutePath,
227
- relativePath,
228
- absolutePath,
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
- if (!fs.existsSync(REGISTRY_FILE)) {
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(REGISTRY_FILE, 'utf-8');
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(GLOBAL_CONFIG_DIR)) {
319
- fs.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
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 = `${REGISTRY_FILE}.${Date.now()}.tmp`;
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, REGISTRY_FILE);
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
- return path.join(os.tmpdir(), `.mino-analysis-${hash}${ext}`);
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.
@@ -37,8 +37,18 @@ export declare const TPM_COOLING_DELAYS: {
37
37
  readonly INTER_TURN_MS: 3000;
38
38
  /** Pause before dispatching parallel sub-agent investigations simultaneously */
39
39
  readonly PARALLEL_DISPATCH_MS: 3000;
40
+ /** Pause between batches/chunks of parallel agents (if > 2 agents) */
41
+ readonly PARALLEL_CHUNK_MS: 2000;
40
42
  /** Pause after context gathering completes before starting the execution stream */
41
43
  readonly POST_INVESTIGATION_MS: 3000;
44
+ /** Pause between un-cached file compression calls during context gathering */
45
+ readonly CONTEXT_COMPRESSION_MS: 2000;
46
+ /** Pause before starting sub-agent execution waves in Orchestrator */
47
+ readonly ORCHESTRATION_WAVE_MS: 2000;
48
+ /** Pause after sub-agent waves complete before PM synthesis/reconciliation */
49
+ readonly ORCHESTRATION_RECONCILE_MS: 2000;
50
+ /** Pause before sending automated verification self-correction prompts */
51
+ readonly CORRECTION_TURN_MS: 2000;
42
52
  };
43
53
  /**
44
54
  * Checks if BYOK is currently enabled for the user.
@@ -37,8 +37,18 @@ export const TPM_COOLING_DELAYS = {
37
37
  INTER_TURN_MS: 3000,
38
38
  /** Pause before dispatching parallel sub-agent investigations simultaneously */
39
39
  PARALLEL_DISPATCH_MS: 3000,
40
+ /** Pause between batches/chunks of parallel agents (if > 2 agents) */
41
+ PARALLEL_CHUNK_MS: 2000,
40
42
  /** Pause after context gathering completes before starting the execution stream */
41
43
  POST_INVESTIGATION_MS: 3000,
44
+ /** Pause between un-cached file compression calls during context gathering */
45
+ CONTEXT_COMPRESSION_MS: 2000,
46
+ /** Pause before starting sub-agent execution waves in Orchestrator */
47
+ ORCHESTRATION_WAVE_MS: 2000,
48
+ /** Pause after sub-agent waves complete before PM synthesis/reconciliation */
49
+ ORCHESTRATION_RECONCILE_MS: 2000,
50
+ /** Pause before sending automated verification self-correction prompts */
51
+ CORRECTION_TURN_MS: 2000,
42
52
  };
43
53
  /**
44
54
  * Checks if BYOK is currently enabled for the user.
@@ -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 use the `@alias/path` multi-workspace prefix syntax.
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 the path starts with `@`, it is resolved against the matching registered workspace.
29
- * Otherwise, it falls through to standard single-workspace resolution against `primaryRoot`.
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
- * Security guarantees are identical to `resolveAndValidatePath`:
32
- * - Path traversal via `..` is blocked (resolved path must remain within the matched root)
33
- * - Absolute paths (outside `@alias/` syntax) are rejected
34
- * - Only explicitly registered workspace roots are accessible
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 (from `process.cwd()`).
37
- * @param filePath - A file path that may or may not use `@alias/` prefix syntax.
38
- * @returns A `ResolvedPath` with the absolute path and workspace metadata.
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 resolveAndValidateMultiWorkspacePath(primaryRoot: string, filePath: string): ResolvedPath;
83
+ export declare function resolveFocusedPath(primaryRoot: string, subPath: string, filePath: string): ResolvedPath;