@rowan-agent/agent 0.4.6 → 0.4.7

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.
@@ -0,0 +1,494 @@
1
+ import {
2
+ resolveInWorkspace,
3
+ resolveWorkspacePaths
4
+ } from "./chunk-S7DI7HIF.js";
5
+
6
+ // src/harness/phases/loader.ts
7
+ import { existsSync as existsSync3 } from "fs";
8
+ import { readdir } from "fs/promises";
9
+ import { dirname as dirname2, join as join3 } from "path";
10
+
11
+ // src/harness/loader.ts
12
+ import { existsSync } from "fs";
13
+ import { readFile } from "fs/promises";
14
+ import { basename, dirname, extname, isAbsolute, join, resolve } from "path";
15
+ function parseFrontmatter(raw) {
16
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
17
+ if (!match) return { frontmatter: {}, body: raw };
18
+ const lines = match[1].split("\n");
19
+ const frontmatter = {};
20
+ for (let i = 0; i < lines.length; i++) {
21
+ const line = lines[i];
22
+ const idx = line.indexOf(":");
23
+ if (idx === -1) continue;
24
+ const key = line.slice(0, idx).trim();
25
+ const value = line.slice(idx + 1).trim();
26
+ if (!key) continue;
27
+ if (value === "" && i + 1 < lines.length) {
28
+ const nested = {};
29
+ while (i + 1 < lines.length) {
30
+ const next = lines[i + 1];
31
+ if (!next.startsWith(" ") || next.trim() === "") break;
32
+ const nIdx = next.indexOf(":");
33
+ if (nIdx === -1) break;
34
+ const nKey = next.slice(0, nIdx).trim();
35
+ const nValue = next.slice(nIdx + 1).trim();
36
+ nested[nKey] = nValue;
37
+ i++;
38
+ }
39
+ if (Object.keys(nested).length) {
40
+ frontmatter[key] = nested;
41
+ }
42
+ continue;
43
+ }
44
+ frontmatter[key] = parseValue(value);
45
+ }
46
+ return { frontmatter, body: raw.slice(match[0].length) };
47
+ }
48
+ function parseValue(value) {
49
+ const arrayMatch = value.match(/^\[(.*)\]$/);
50
+ if (arrayMatch) {
51
+ return arrayMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
52
+ }
53
+ if (value.toLowerCase() === "true") return true;
54
+ if (value.toLowerCase() === "false") return false;
55
+ if (/^\d+$/.test(value)) return Number(value);
56
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
57
+ return value.slice(1, -1);
58
+ }
59
+ return value;
60
+ }
61
+ async function loadMarkdown(filePath) {
62
+ const raw = await readFile(filePath, "utf8");
63
+ const { frontmatter, body } = parseFrontmatter(raw);
64
+ return { frontmatter, body, raw };
65
+ }
66
+ function isExplicitPath(input) {
67
+ return input.includes("/") || input.includes("\\") || Boolean(extname(input));
68
+ }
69
+ function inferResourceName(path, markerFile) {
70
+ const file = basename(path);
71
+ if (file.toLowerCase() === markerFile.toLowerCase()) {
72
+ return basename(dirname(path));
73
+ }
74
+ const extension = extname(file);
75
+ return extension ? file.slice(0, -extension.length) : file;
76
+ }
77
+ function resolveResourcePath(input, resourceType, markerFile, workspace) {
78
+ const ws = workspace ?? resolveWorkspacePaths();
79
+ if (isAbsolute(input)) {
80
+ return input;
81
+ }
82
+ if (!isExplicitPath(input)) {
83
+ return join(ws.rowanDir, resourceType, input, markerFile);
84
+ }
85
+ const workspacePath = resolveInWorkspace(input, ws);
86
+ if (existsSync(workspacePath)) {
87
+ return workspacePath;
88
+ }
89
+ return resolve(input);
90
+ }
91
+
92
+ // src/harness/config.ts
93
+ import { existsSync as existsSync2 } from "fs";
94
+ import { readFile as readFile2 } from "fs/promises";
95
+ import { join as join2 } from "path";
96
+ import { parse as parseYaml } from "yaml";
97
+ import { registerModel } from "@rowan-agent/models";
98
+ var DEFAULT_REASONING = false;
99
+ var DEFAULT_INPUT = ["text"];
100
+ var DEFAULT_CONTEXT_WINDOW = 128e3;
101
+ var DEFAULT_MAX_TOKENS = 16384;
102
+ var DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
103
+ var DEFAULT_TIMEOUT_MS = 6e4;
104
+ var ENV_VAR_PATTERN = /\$\{([^}]+)\}/g;
105
+ function interpolateEnvVars(value) {
106
+ return value.replace(ENV_VAR_PATTERN, (match, varName) => {
107
+ const resolved = process.env[varName];
108
+ if (resolved === void 0 || resolved === "") {
109
+ throw new Error(
110
+ `Environment variable "${varName}" is not set but is referenced in config.yaml via "${match}".`
111
+ );
112
+ }
113
+ return resolved;
114
+ });
115
+ }
116
+ var CONFIG_CANDIDATES = ["config.yaml", "config.yml"];
117
+ async function loadConfigFile(workspace) {
118
+ let configPath;
119
+ let configFilename;
120
+ for (const candidate of CONFIG_CANDIDATES) {
121
+ const p = join2(workspace.rowanDir, candidate);
122
+ if (existsSync2(p)) {
123
+ configPath = p;
124
+ configFilename = candidate;
125
+ break;
126
+ }
127
+ }
128
+ if (!configPath || !configFilename) {
129
+ return void 0;
130
+ }
131
+ const raw = await readFile2(configPath, "utf8");
132
+ let parsed;
133
+ try {
134
+ parsed = parseYaml(raw);
135
+ } catch (error) {
136
+ const message = error instanceof Error ? error.message : String(error);
137
+ throw new Error(`Failed to parse .rowan/${configFilename}: ${message}`);
138
+ }
139
+ if (parsed === null || parsed === void 0) {
140
+ return void 0;
141
+ }
142
+ return validateConfigFile(parsed, configFilename);
143
+ }
144
+ function validateConfigFile(parsed, configFilename) {
145
+ if (typeof parsed !== "object" || parsed === null) {
146
+ throw new Error(`.rowan/${configFilename} must be a YAML object at the top level.`);
147
+ }
148
+ const obj = parsed;
149
+ const providers = obj.providers;
150
+ if (!Array.isArray(providers)) {
151
+ throw new Error(`.rowan/${configFilename} must have a "providers" array.`);
152
+ }
153
+ const validatedProviders = providers.map((p, i) => {
154
+ if (typeof p !== "object" || p === null) {
155
+ throw new Error(`providers[${i}] must be an object.`);
156
+ }
157
+ const provider = p;
158
+ if (typeof provider.id !== "string" || !provider.id) {
159
+ throw new Error(`providers[${i}].id is required and must be a non-empty string.`);
160
+ }
161
+ if (typeof provider.baseUrl !== "string" || !provider.baseUrl) {
162
+ throw new Error(`providers[${i}].baseUrl is required and must be a non-empty string.`);
163
+ }
164
+ if (typeof provider.apiKey !== "string" || !provider.apiKey) {
165
+ throw new Error(`providers[${i}].apiKey is required and must be a non-empty string.`);
166
+ }
167
+ if (typeof provider.protocol !== "string" || !provider.protocol) {
168
+ throw new Error(`providers[${i}].protocol is required and must be a non-empty string.`);
169
+ }
170
+ if (!Array.isArray(provider.models) || provider.models.length === 0) {
171
+ throw new Error(`providers[${i}].models must be a non-empty array.`);
172
+ }
173
+ const interpolatedApiKey = interpolateEnvVars(provider.apiKey);
174
+ const validatedModels = provider.models.map((m, j) => {
175
+ if (typeof m !== "object" || m === null) {
176
+ throw new Error(`providers[${i}].models[${j}] must be an object.`);
177
+ }
178
+ const model = m;
179
+ if (typeof model.id !== "string" || !model.id) {
180
+ throw new Error(`providers[${i}].models[${j}].id is required and must be a non-empty string.`);
181
+ }
182
+ return model;
183
+ });
184
+ return {
185
+ id: provider.id,
186
+ baseUrl: provider.baseUrl,
187
+ apiKey: interpolatedApiKey,
188
+ protocol: provider.protocol,
189
+ ...typeof provider.name === "string" ? { name: provider.name } : {},
190
+ ...typeof provider.timeoutMs === "number" ? { timeoutMs: provider.timeoutMs } : {},
191
+ ...typeof provider.maxRetries === "number" ? { maxRetries: provider.maxRetries } : {},
192
+ ...typeof provider.retryDelayMs === "number" ? { retryDelayMs: provider.retryDelayMs } : {},
193
+ ...provider.headers && typeof provider.headers === "object" ? { headers: provider.headers } : {},
194
+ models: validatedModels
195
+ };
196
+ });
197
+ const result = { providers: validatedProviders };
198
+ if (obj.model && typeof obj.model === "object") {
199
+ const modelRef = obj.model;
200
+ if (typeof modelRef.provider !== "string" || typeof modelRef.id !== "string") {
201
+ throw new Error(`.rowan/${configFilename} "model" must have "provider" and "id" string fields.`);
202
+ }
203
+ result.model = { provider: modelRef.provider, id: modelRef.id };
204
+ }
205
+ if (obj.logLevel !== void 0) {
206
+ const valid = ["debug", "info", "warn", "error", "silent"];
207
+ if (typeof obj.logLevel !== "string" || !valid.includes(obj.logLevel)) {
208
+ throw new Error(`.rowan/${configFilename} "logLevel" must be one of: ${valid.join(", ")}.`);
209
+ }
210
+ result.logLevel = obj.logLevel;
211
+ }
212
+ return result;
213
+ }
214
+ function resolveDefaultModel(config) {
215
+ if (config.model) {
216
+ return { provider: config.model.provider, id: config.model.id };
217
+ }
218
+ for (const provider of config.providers) {
219
+ for (const model of provider.models) {
220
+ if (model.primary) {
221
+ return { provider: provider.id, id: model.id };
222
+ }
223
+ }
224
+ }
225
+ for (const provider of config.providers) {
226
+ if (provider.models.length > 0) {
227
+ return { provider: provider.id, id: provider.models[0].id };
228
+ }
229
+ }
230
+ return void 0;
231
+ }
232
+ function registerConfigModels(config) {
233
+ for (const provider of config.providers) {
234
+ for (const fileModel of provider.models) {
235
+ const model = {
236
+ id: fileModel.id,
237
+ name: fileModel.name ?? fileModel.id,
238
+ protocol: provider.protocol,
239
+ provider: provider.id,
240
+ baseUrl: provider.baseUrl,
241
+ reasoning: fileModel.reasoning ?? DEFAULT_REASONING,
242
+ input: fileModel.input ?? DEFAULT_INPUT,
243
+ cost: { ...DEFAULT_COST, ...fileModel.cost },
244
+ contextWindow: fileModel.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
245
+ maxTokens: fileModel.maxTokens ?? DEFAULT_MAX_TOKENS,
246
+ apiKey: provider.apiKey,
247
+ timeoutMs: provider.timeoutMs ?? DEFAULT_TIMEOUT_MS,
248
+ ...provider.maxRetries !== void 0 ? { maxRetries: provider.maxRetries } : {},
249
+ ...provider.retryDelayMs !== void 0 ? { retryDelayMs: provider.retryDelayMs } : {},
250
+ ...provider.headers ? { headers: provider.headers } : {}
251
+ };
252
+ registerModel(model);
253
+ }
254
+ }
255
+ }
256
+ function parseModelRef(input) {
257
+ if (!input) return void 0;
258
+ const slashIndex = input.indexOf("/");
259
+ if (slashIndex === -1) {
260
+ return { provider: "*", id: input };
261
+ }
262
+ return {
263
+ provider: input.slice(0, slashIndex),
264
+ id: input.slice(slashIndex + 1)
265
+ };
266
+ }
267
+
268
+ // src/harness/context/resource-formatter.ts
269
+ function escapeXml(value) {
270
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
271
+ }
272
+ function buildStructuredSection(tag, items) {
273
+ const lines = [];
274
+ for (const item of items) {
275
+ lines.push(` <${tag}>`);
276
+ for (const [key, value] of Object.entries(item)) {
277
+ lines.push(` <${key}>${escapeXml(value)}</${key}>`);
278
+ }
279
+ lines.push(` </${tag}>`);
280
+ }
281
+ return lines.join("\n");
282
+ }
283
+ function buildSkillsDescription(skills) {
284
+ const visibleSkills = skills.filter((skill) => !skill.disableModelInvocation);
285
+ if (visibleSkills.length === 0) return "";
286
+ const lines = ["<available_skills>"];
287
+ for (const skill of visibleSkills) {
288
+ lines.push(" <skill>");
289
+ lines.push(` <name>${escapeXml(skill.name)}</name>`);
290
+ lines.push(` <description>${escapeXml(skill.description)}</description>`);
291
+ lines.push(` <location>${escapeXml(skill.filePath)}</location>`);
292
+ lines.push(" </skill>");
293
+ }
294
+ lines.push("</available_skills>");
295
+ return lines.join("\n");
296
+ }
297
+ function formatResourceOutput(resource) {
298
+ const parts = [`<${resource.type} name="${escapeXml(resource.name)}" location="${escapeXml(resource.location)}">`];
299
+ if (resource.baseDir) {
300
+ parts.push(`References are relative to ${resource.baseDir}.`);
301
+ parts.push("");
302
+ }
303
+ parts.push(resource.content);
304
+ parts.push(`</${resource.type}>`);
305
+ return parts.join("\n");
306
+ }
307
+ function detectResourceType(filePath) {
308
+ const normalized = filePath.replace(/\\/g, "/");
309
+ if (normalized.includes("/.rowan/skills/") && normalized.endsWith("/SKILL.md")) return "skill";
310
+ if (normalized.includes("/.rowan/phases/") && normalized.endsWith("/PHASE.md")) return "phase";
311
+ if (normalized.endsWith(".md")) return "markdown";
312
+ if (/\.(ts|js|tsx|jsx|py|rs|go|java|c|cpp|rb|sh|sql|yaml|yml|json|toml)$/.test(normalized)) return "code";
313
+ return "file";
314
+ }
315
+ function jsonToXml(value, depth) {
316
+ const indent = " ".repeat(depth);
317
+ if (value === null || value === void 0) return "";
318
+ if (typeof value !== "object") return `${indent}${escapeXml(String(value))}`;
319
+ if (Array.isArray(value)) {
320
+ return value.map((item) => {
321
+ if (typeof item === "object" && item !== null) {
322
+ return `${indent}<item>
323
+ ${jsonToXml(item, depth + 1)}
324
+ ${indent}</item>`;
325
+ }
326
+ return `${indent}<item>${escapeXml(String(item))}</item>`;
327
+ }).join("\n");
328
+ }
329
+ return Object.entries(value).map(([key, val]) => {
330
+ const tag = escapeXml(key);
331
+ if (typeof val === "object" && val !== null) {
332
+ return `${indent}<${tag}>
333
+ ${jsonToXml(val, depth + 1)}
334
+ ${indent}</${tag}>`;
335
+ }
336
+ return `${indent}<${tag}>${escapeXml(String(val))}</${tag}>`;
337
+ }).join("\n");
338
+ }
339
+ function buildPhaseResultMessage(phases, toolUseId, instruction) {
340
+ const parts = [];
341
+ if (instruction) {
342
+ parts.push(`<instruction>${instruction}</instruction>`);
343
+ }
344
+ parts.push(`<phase_results>`);
345
+ for (const p of phases) {
346
+ parts.push(` <phase name="${p.name}">`);
347
+ if (p.content) {
348
+ parts.push(` <content>${p.content}</content>`);
349
+ }
350
+ if (p.output !== void 0) {
351
+ parts.push(` <output>${jsonToXml(p.output, 2)}</output>`);
352
+ }
353
+ if (!p.content && p.output === void 0) {
354
+ parts.push(` <content>Phase "${p.name}" completed with no output.</content>`);
355
+ }
356
+ parts.push(` </phase>`);
357
+ }
358
+ parts.push(`</phase_results>`);
359
+ return [{
360
+ type: "tool_result",
361
+ toolUseId,
362
+ content: parts.join("\n"),
363
+ isError: false
364
+ }];
365
+ }
366
+
367
+ // src/harness/phases/loader.ts
368
+ var PHASE_MARKER = "PHASE.md";
369
+ var PHASE_DIR = "phases";
370
+ async function loadPhase(input, workspace) {
371
+ const resolved = resolveResourcePath(input, PHASE_DIR, PHASE_MARKER, workspace);
372
+ const { frontmatter, body } = await loadMarkdown(resolved);
373
+ const baseDir = dirname2(resolved);
374
+ const phase = {
375
+ id: inferResourceName(resolved, PHASE_MARKER),
376
+ name: frontmatter.name ?? inferResourceName(resolved, PHASE_MARKER),
377
+ description: frontmatter.description ?? "",
378
+ tools: frontmatter.tools,
379
+ skills: frontmatter.skills,
380
+ target: frontmatter.target,
381
+ input: frontmatter.input,
382
+ isolated: frontmatter.isolated,
383
+ filePath: resolved,
384
+ baseDir,
385
+ content: body,
386
+ model: parseModelRef(frontmatter.model)
387
+ };
388
+ const codePath = await discoverPhaseCode(baseDir);
389
+ if (codePath) {
390
+ const code = await loadPhaseCode(codePath);
391
+ if (code.factory) {
392
+ phase.factory = code.factory;
393
+ } else if (code.run) {
394
+ phase.run = code.run;
395
+ }
396
+ }
397
+ return phase;
398
+ }
399
+ function readPhaseContent(phase) {
400
+ return formatResourceOutput({
401
+ type: "phase",
402
+ name: phase.name,
403
+ location: phase.filePath,
404
+ content: phase.content,
405
+ baseDir: phase.baseDir
406
+ });
407
+ }
408
+ async function loadPhases(workspace, paths) {
409
+ const phases = /* @__PURE__ */ new Map();
410
+ if (paths && paths.length > 0) {
411
+ for (const path of paths) {
412
+ const phase = await loadPhase(path, workspace);
413
+ phases.set(phase.id, phase);
414
+ }
415
+ return { phases, entryPhaseId: null };
416
+ }
417
+ const ws = workspace ?? (await import("./path-TJ2G5L4N.js")).resolveWorkspacePaths();
418
+ const phasesDir = join3(ws.rowanDir, PHASE_DIR);
419
+ if (!existsSync3(phasesDir)) {
420
+ return { phases, entryPhaseId: null };
421
+ }
422
+ const entries = await readdir(phasesDir, { withFileTypes: true });
423
+ for (const entry of entries) {
424
+ if (!entry.isDirectory()) continue;
425
+ const phaseFile = join3(phasesDir, entry.name, PHASE_MARKER);
426
+ if (!existsSync3(phaseFile)) continue;
427
+ try {
428
+ const phase = await loadPhase(entry.name, ws);
429
+ phases.set(phase.id, phase);
430
+ } catch (error) {
431
+ console.warn(`Failed to load phase "${entry.name}":`, error);
432
+ }
433
+ }
434
+ return { phases, entryPhaseId: null };
435
+ }
436
+ async function discoverPhaseCode(baseDir) {
437
+ const candidates = ["index.ts", "index.js"];
438
+ for (const candidate of candidates) {
439
+ const fullPath = join3(baseDir, candidate);
440
+ if (existsSync3(fullPath)) {
441
+ return fullPath;
442
+ }
443
+ }
444
+ return null;
445
+ }
446
+ async function loadPhaseCode(codePath) {
447
+ const { createJiti } = await import("jiti");
448
+ const jiti = createJiti(import.meta.url, {
449
+ moduleCache: false
450
+ });
451
+ const mod = await jiti.import(codePath, { default: true });
452
+ const fn = typeof mod === "function" ? mod : mod?.default;
453
+ if (typeof fn === "function") {
454
+ return { factory: fn };
455
+ }
456
+ if (typeof mod?.run === "function") {
457
+ return { run: mod.run };
458
+ }
459
+ throw new Error(`Phase code at "${codePath}" must export a default function or a run() function.`);
460
+ }
461
+ async function reloadPhases(registry, workspace) {
462
+ for (const [id, phase] of registry.phases) {
463
+ if (!phase.filePath) {
464
+ continue;
465
+ }
466
+ try {
467
+ const fresh = await loadPhase(phase.filePath, workspace);
468
+ registry.phases.set(id, fresh);
469
+ } catch (error) {
470
+ console.warn(`Failed to reload phase "${id}":`, error);
471
+ }
472
+ }
473
+ }
474
+
475
+ export {
476
+ parseFrontmatter,
477
+ loadMarkdown,
478
+ inferResourceName,
479
+ resolveResourcePath,
480
+ interpolateEnvVars,
481
+ loadConfigFile,
482
+ resolveDefaultModel,
483
+ registerConfigModels,
484
+ parseModelRef,
485
+ buildStructuredSection,
486
+ buildSkillsDescription,
487
+ formatResourceOutput,
488
+ detectResourceType,
489
+ buildPhaseResultMessage,
490
+ loadPhase,
491
+ readPhaseContent,
492
+ loadPhases,
493
+ reloadPhases
494
+ };
@@ -0,0 +1,145 @@
1
+ // src/harness/env/path.ts
2
+ import { existsSync, readFileSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "path";
5
+ var WORKSPACE_ENV = "ROWAN_WORKSPACE";
6
+ var RUNTIME_ENV = "ROWAN_RUNTIME";
7
+ var PACKAGED_ENV = "ROWAN_PACKAGED";
8
+ var BINARY_WORKSPACE_DIR = ".rowan";
9
+ function nonEmptyEnv(env, key) {
10
+ const value = env[key]?.trim();
11
+ return value ? value : void 0;
12
+ }
13
+ function isTruthyEnvValue(value) {
14
+ return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
15
+ }
16
+ function resolveUserPath(path, homeDir) {
17
+ if (path === "~") {
18
+ return homeDir;
19
+ }
20
+ if (path.startsWith("~/") || path.startsWith("~\\")) {
21
+ return join(homeDir, path.slice(2));
22
+ }
23
+ return resolve(path);
24
+ }
25
+ function isSourceWorkspaceRoot(path) {
26
+ const packagePath = join(path, "package.json");
27
+ if (!existsSync(packagePath)) {
28
+ return false;
29
+ }
30
+ try {
31
+ const manifest = JSON.parse(readFileSync(packagePath, "utf8"));
32
+ return manifest.name === "rowan-agent" || Array.isArray(manifest.workspaces);
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+ function findSourceWorkspaceRoot(startDir = process.cwd()) {
38
+ let current = resolve(startDir);
39
+ const { root } = parse(current);
40
+ while (true) {
41
+ if (isSourceWorkspaceRoot(current)) {
42
+ return current;
43
+ }
44
+ if (current === root) {
45
+ return resolve(startDir);
46
+ }
47
+ current = dirname(current);
48
+ }
49
+ }
50
+ function detectRuntimeMode(input = {}) {
51
+ const env = input.env ?? process.env;
52
+ const explicitMode = nonEmptyEnv(env, RUNTIME_ENV)?.toLowerCase();
53
+ if (explicitMode === "source" || explicitMode === "binary") {
54
+ return explicitMode;
55
+ }
56
+ if (isTruthyEnvValue(nonEmptyEnv(env, PACKAGED_ENV))) {
57
+ return "binary";
58
+ }
59
+ const executable = basename(input.execPath ?? process.execPath).toLowerCase().replace(/\.exe$/, "");
60
+ return executable === "bun" ? "source" : "binary";
61
+ }
62
+ function defaultSourceStartDir(options) {
63
+ if (options.cwd) {
64
+ return options.cwd;
65
+ }
66
+ const entrypoint = options.entrypoint ?? process.argv[1];
67
+ if (entrypoint) {
68
+ const entrypointPath = resolve(process.cwd(), entrypoint);
69
+ if (existsSync(entrypointPath)) {
70
+ return dirname(entrypointPath);
71
+ }
72
+ }
73
+ return process.cwd();
74
+ }
75
+ function resolveWorkspaceRoot(options = {}) {
76
+ const env = options.env ?? process.env;
77
+ const homeDir = options.homeDir ?? homedir();
78
+ const override = nonEmptyEnv(env, WORKSPACE_ENV);
79
+ if (override) {
80
+ return resolveUserPath(override, homeDir);
81
+ }
82
+ const mode = options.mode ?? detectRuntimeMode(options);
83
+ if (mode === "binary") {
84
+ return homeDir;
85
+ }
86
+ return findSourceWorkspaceRoot(defaultSourceStartDir(options));
87
+ }
88
+ function resolveWorkspacePaths(options = {}) {
89
+ const mode = options.mode ?? detectRuntimeMode(options);
90
+ const cwd = resolveWorkspaceRoot({ ...options, mode });
91
+ return {
92
+ mode,
93
+ cwd,
94
+ rowanDir: join(cwd, BINARY_WORKSPACE_DIR)
95
+ };
96
+ }
97
+ function resolveInWorkspace(path, rootOrPaths) {
98
+ if (path === "~" || path.startsWith("~/") || path.startsWith("~\\")) {
99
+ return resolveUserPath(path, homedir());
100
+ }
101
+ if (isAbsolute(path)) {
102
+ return path;
103
+ }
104
+ const root = typeof rootOrPaths === "string" ? rootOrPaths : rootOrPaths.cwd;
105
+ return resolve(root, path);
106
+ }
107
+ function normalizeRelativePath(path) {
108
+ return path.split(sep).join("/");
109
+ }
110
+ function normalizeWorkspaceInputPath(path = ".") {
111
+ const trimmed = path.trim();
112
+ if (!trimmed || trimmed === "/" || trimmed === "\\") {
113
+ return ".";
114
+ }
115
+ return path;
116
+ }
117
+ function resolveWorkspacePath(context, path = ".") {
118
+ const root = resolve(context.root);
119
+ const inputPath = normalizeWorkspaceInputPath(path);
120
+ const absolutePath = resolve(root, inputPath);
121
+ const relativePath = relative(root, absolutePath);
122
+ if (relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) {
123
+ throw new Error(`Path escapes workspace root: ${path}`);
124
+ }
125
+ return {
126
+ root,
127
+ inputPath,
128
+ absolutePath,
129
+ relativePath: normalizeRelativePath(relativePath || ".")
130
+ };
131
+ }
132
+
133
+ export {
134
+ WORKSPACE_ENV,
135
+ RUNTIME_ENV,
136
+ PACKAGED_ENV,
137
+ BINARY_WORKSPACE_DIR,
138
+ findSourceWorkspaceRoot,
139
+ detectRuntimeMode,
140
+ resolveWorkspaceRoot,
141
+ resolveWorkspacePaths,
142
+ resolveInWorkspace,
143
+ normalizeRelativePath,
144
+ resolveWorkspacePath
145
+ };