@matheuskrumenauer/tanya 0.17.0 → 0.17.6

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.
@@ -1276,8 +1276,15 @@ import { join as join5 } from "path";
1276
1276
  var ROUTES_SCHEMA_VERSION = 1;
1277
1277
  var BUILT_IN_ROUTE_DEFAULTS = {
1278
1278
  provider: "openai",
1279
- model: "gpt-4.1-mini"
1279
+ model: "gpt-4.1-mini",
1280
+ maxInputTokens: 128e3
1280
1281
  };
1282
+ var BUILT_IN_ROUTE_CASCADE = [
1283
+ { provider: "deepseek", model: "deepseek-chat", maxInputTokens: 128e3 },
1284
+ { provider: "openai", model: "gpt-5-codex", maxInputTokens: 2e5 },
1285
+ { provider: "claude", model: "claude-sonnet-4-6", maxInputTokens: 1e6 },
1286
+ { provider: "gemini", model: "gemini-1.5-pro", maxInputTokens: 2e6 }
1287
+ ];
1281
1288
  function builtInRouteTable(defaults = BUILT_IN_ROUTE_DEFAULTS) {
1282
1289
  return {
1283
1290
  version: ROUTES_SCHEMA_VERSION,
@@ -1317,7 +1324,8 @@ function builtInRouteTable(defaults = BUILT_IN_ROUTE_DEFAULTS) {
1317
1324
  reasoningCap: { maxTokens: 8e3 }
1318
1325
  }
1319
1326
  ],
1320
- defaults
1327
+ defaults,
1328
+ cascade: BUILT_IN_ROUTE_CASCADE
1321
1329
  };
1322
1330
  }
1323
1331
 
@@ -1343,12 +1351,21 @@ function loadRouteTable(options) {
1343
1351
  ...sourceRoutes(user.value?.routes ?? [], "user"),
1344
1352
  ...sourceRoutes(builtIn.routes, "built-in")
1345
1353
  ];
1354
+ const defaultSource = project.value?.defaults ? "project" : user.value?.defaults ? "user" : "runtime-default";
1355
+ const defaults = project.value?.defaults ?? user.value?.defaults ?? builtIn.defaults;
1356
+ const cascadeSource = project.value ? "project" : user.value ? "user" : "built-in";
1357
+ const cascade = sourceCascade(
1358
+ project.value ? cascadeOrLegacyDefault(project.value) : user.value ? cascadeOrLegacyDefault(user.value) : builtIn.cascade ?? cascadeOrLegacyDefault(builtIn),
1359
+ cascadeSource
1360
+ );
1346
1361
  return {
1347
1362
  table: {
1348
1363
  version: ROUTES_SCHEMA_VERSION,
1349
1364
  routes,
1350
- defaults: project.value?.defaults ?? user.value?.defaults ?? builtIn.defaults,
1351
- defaultSource: project.value?.defaults ? "project" : user.value?.defaults ? "user" : "runtime-default",
1365
+ defaults,
1366
+ defaultSource,
1367
+ cascade,
1368
+ cascadeSource,
1352
1369
  sources
1353
1370
  },
1354
1371
  issues
@@ -1371,14 +1388,16 @@ function validateRouteTable(input) {
1371
1388
  issues.push({ path: "$.version", message: `Expected schema version ${ROUTES_SCHEMA_VERSION}.` });
1372
1389
  }
1373
1390
  const routes = validateRoutes(input.routes, issues);
1374
- const defaults = validateTarget(input.defaults, "$.defaults", issues);
1391
+ const defaults = input.defaults === void 0 ? validateLegacyDefaultTarget(input, issues) : validateTarget(input.defaults, "$.defaults", issues);
1392
+ const cascade = input.cascade === void 0 ? void 0 : validateCascade(input.cascade, issues);
1375
1393
  if (issues.length > 0) return { ok: false, issues };
1376
1394
  return {
1377
1395
  ok: true,
1378
1396
  value: {
1379
1397
  version: ROUTES_SCHEMA_VERSION,
1380
1398
  routes,
1381
- defaults
1399
+ defaults,
1400
+ ...cascade ? { cascade } : {}
1382
1401
  },
1383
1402
  issues: []
1384
1403
  };
@@ -1427,7 +1446,21 @@ function readRouteFile(file, _source) {
1427
1446
  function sourceRoutes(routes, source) {
1428
1447
  return routes.map((route) => ({ ...route, source }));
1429
1448
  }
1449
+ function sourceCascade(cascade, source) {
1450
+ return cascade.map((route) => ({ ...route, source }));
1451
+ }
1452
+ function cascadeOrLegacyDefault(table) {
1453
+ return table.cascade?.length ? table.cascade : [targetToCascade(table.defaults)];
1454
+ }
1455
+ function targetToCascade(target) {
1456
+ return {
1457
+ provider: target.provider,
1458
+ model: target.model,
1459
+ maxInputTokens: target.maxInputTokens ?? contextWindowForProvider(target.provider)
1460
+ };
1461
+ }
1430
1462
  function validateRoutes(input, issues) {
1463
+ if (input === void 0) return [];
1431
1464
  if (!Array.isArray(input)) {
1432
1465
  issues.push({ path: "$.routes", message: "Expected an array." });
1433
1466
  return [];
@@ -1450,12 +1483,31 @@ function validateRoutes(input, issues) {
1450
1483
  match,
1451
1484
  provider: target.provider,
1452
1485
  model: target.model,
1486
+ ...target.maxInputTokens ? { maxInputTokens: target.maxInputTokens } : {},
1453
1487
  ...fallback ? { fallback } : {},
1454
1488
  ...item.escalate !== void 0 ? { escalate: Boolean(item.escalate) } : {},
1455
1489
  ...reasoningCap ? { reasoningCap } : {}
1456
1490
  }];
1457
1491
  });
1458
1492
  }
1493
+ function validateCascade(input, issues) {
1494
+ if (!Array.isArray(input)) {
1495
+ issues.push({ path: "$.cascade", message: "Expected an array when present." });
1496
+ return [];
1497
+ }
1498
+ return input.flatMap((item, index) => {
1499
+ const path = `$.cascade[${index}]`;
1500
+ const target = validateTarget(item, path, issues);
1501
+ const stepTypes = validateStepTypes(isRecord2(item) ? item.stepTypes ?? item.step_types : void 0, `${path}.stepTypes`, issues);
1502
+ if (!target || !target.maxInputTokens) return [];
1503
+ return [{
1504
+ provider: target.provider,
1505
+ model: target.model,
1506
+ maxInputTokens: target.maxInputTokens,
1507
+ ...stepTypes ? { stepTypes } : {}
1508
+ }];
1509
+ });
1510
+ }
1459
1511
  function validateReasoningCap(input, path, issues) {
1460
1512
  if (input === void 0) return null;
1461
1513
  if (!isRecord2(input)) {
@@ -1468,6 +1520,22 @@ function validateReasoningCap(input, path, issues) {
1468
1520
  }
1469
1521
  return { maxTokens: Math.floor(input.maxTokens) };
1470
1522
  }
1523
+ function validateStepTypes(input, path, issues) {
1524
+ if (input === void 0) return null;
1525
+ if (!Array.isArray(input)) {
1526
+ issues.push({ path, message: "Expected an array of step types when present." });
1527
+ return null;
1528
+ }
1529
+ const out = [];
1530
+ input.forEach((item, index) => {
1531
+ if (typeof item !== "string" || !STEP_TYPES.has(item)) {
1532
+ issues.push({ path: `${path}[${index}]`, message: "Expected a known step type." });
1533
+ return;
1534
+ }
1535
+ out.push(item);
1536
+ });
1537
+ return out.length ? out : null;
1538
+ }
1471
1539
  function validateMatch(input, path, issues) {
1472
1540
  if (typeof input === "string") {
1473
1541
  if (!STEP_TYPES.has(input)) {
@@ -1498,26 +1566,76 @@ function validateTarget(input, path, issues) {
1498
1566
  issues.push({ path, message: "Expected an object." });
1499
1567
  return { provider: "", model: "" };
1500
1568
  }
1501
- if (typeof input.provider !== "string" || input.provider.trim() === "") {
1569
+ const provider = typeof input.provider === "string" && input.provider.trim() ? input.provider.trim() : typeof input.cli === "string" && input.cli.trim() ? input.cli.trim() : "";
1570
+ const model = typeof input.model === "string" && input.model.trim() ? input.model.trim() : "";
1571
+ const maxInputTokens = numericField(input.maxInputTokens ?? input.max_input_tokens);
1572
+ if (!provider) {
1502
1573
  issues.push({ path: `${path}.provider`, message: "Expected a non-empty provider string." });
1503
1574
  }
1504
- if (typeof input.model !== "string" || input.model.trim() === "") {
1575
+ if (!model) {
1505
1576
  issues.push({ path: `${path}.model`, message: "Expected a non-empty model string." });
1506
1577
  }
1578
+ if ((input.maxInputTokens !== void 0 || input.max_input_tokens !== void 0) && !maxInputTokens) {
1579
+ issues.push({ path: `${path}.maxInputTokens`, message: "Expected a positive number." });
1580
+ }
1507
1581
  return {
1508
- provider: typeof input.provider === "string" ? input.provider : "",
1509
- model: typeof input.model === "string" ? input.model : ""
1582
+ provider,
1583
+ model,
1584
+ ...maxInputTokens ? { maxInputTokens } : {}
1585
+ };
1586
+ }
1587
+ function validateLegacyDefaultTarget(input, issues) {
1588
+ const defaultModel = typeof input.default_model === "string" && input.default_model.trim() ? input.default_model.trim() : typeof input.defaultModel === "string" && input.defaultModel.trim() ? input.defaultModel.trim() : "";
1589
+ const defaultProvider = typeof input.default_cli === "string" && input.default_cli.trim() ? input.default_cli.trim() : typeof input.default_provider === "string" && input.default_provider.trim() ? input.default_provider.trim() : typeof input.defaultProvider === "string" && input.defaultProvider.trim() ? input.defaultProvider.trim() : "";
1590
+ if (!defaultModel) {
1591
+ issues.push({ path: "$.defaults", message: "Expected defaults or legacy default_model." });
1592
+ }
1593
+ const provider = defaultProvider || inferProviderForModel(defaultModel);
1594
+ if (!provider) {
1595
+ issues.push({ path: "$.default_cli", message: "Expected default_cli/default_provider for this default_model." });
1596
+ }
1597
+ const maxInputTokens = numericField(input.maxInputTokens ?? input.max_input_tokens);
1598
+ return {
1599
+ provider,
1600
+ model: defaultModel,
1601
+ ...maxInputTokens ? { maxInputTokens } : {}
1510
1602
  };
1511
1603
  }
1512
1604
  function routeMatches(match, stepType, text2) {
1513
1605
  if (typeof match === "string") return match === stepType;
1514
1606
  return new RegExp(match.regex).test(text2);
1515
1607
  }
1608
+ function numericField(value) {
1609
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
1610
+ return Math.floor(value);
1611
+ }
1612
+ function inferProviderForModel(model) {
1613
+ if (/^deepseek-/i.test(model)) return "deepseek";
1614
+ if (/^(?:gpt-|o\d|o\d-|chatgpt)/i.test(model)) return "openai";
1615
+ if (/^claude-/i.test(model)) return "claude";
1616
+ if (/^gemini-/i.test(model)) return "gemini";
1617
+ if (/^qwen/i.test(model)) return "qwen";
1618
+ return "";
1619
+ }
1620
+ function contextWindowForProvider(provider) {
1621
+ switch (provider) {
1622
+ case "claude":
1623
+ return 1e6;
1624
+ case "gemini":
1625
+ return 2e6;
1626
+ case "openai":
1627
+ return 2e5;
1628
+ default:
1629
+ return 128e3;
1630
+ }
1631
+ }
1516
1632
  function isRecord2(value) {
1517
1633
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1518
1634
  }
1519
1635
 
1520
1636
  // src/router/classify.ts
1637
+ import { existsSync as existsSync6 } from "fs";
1638
+ import { join as join6 } from "path";
1521
1639
  function classifyStep(state) {
1522
1640
  const lastAssistant = state.lastAssistantMessage ?? lastMessageWithRole(state.messages ?? [], "assistant");
1523
1641
  const pendingToolCalls = state.pendingToolCalls ?? lastAssistant?.tool_calls ?? [];
@@ -1530,8 +1648,38 @@ function classifyStep(state) {
1530
1648
  if (content.trim() !== "" && pendingToolCalls.length === 0 && toolResultsSinceUser.length >= 2) {
1531
1649
  return "synthesis";
1532
1650
  }
1651
+ if (looksLikeCodeEditingTask(state)) return "tool_call";
1533
1652
  return "unknown";
1534
1653
  }
1654
+ function looksLikeCodeEditingTask(state) {
1655
+ const runContext = state.runContext;
1656
+ if (runContext?.task?.kind === "coding") return true;
1657
+ if (runContext?.expected_report && Object.keys(runContext.expected_report).length > 0) return true;
1658
+ if (runContext?.verification?.commands?.length) return true;
1659
+ const taskText2 = [
1660
+ runContext?.task?.title,
1661
+ runContext?.task?.summary,
1662
+ runContext?.stack,
1663
+ ...runContext?.languages ?? [],
1664
+ ...runContext?.frameworks ?? [],
1665
+ state.prompt,
1666
+ ...userMessageContent(state.messages ?? [])
1667
+ ].filter(Boolean).join("\n");
1668
+ if (/\bgo-backend-[a-z0-9_-]+\b/i.test(taskText2)) return true;
1669
+ if (/template\s*(?:id|ID)?\s*[:=]?\s*["'`]?go-backend-[a-z0-9_-]+/i.test(taskText2)) return true;
1670
+ if (/EXISTING CODE DETECTED/i.test(taskText2) && /\bEXECUTE\s+mode\b/i.test(taskText2)) return true;
1671
+ return Boolean(state.cwd && isCodeWorkspace(state.cwd) && taskText2.length > 1200);
1672
+ }
1673
+ function isCodeWorkspace(cwd) {
1674
+ return [
1675
+ "go.mod",
1676
+ "package.json",
1677
+ "Cargo.toml",
1678
+ "pyproject.toml",
1679
+ "requirements.txt",
1680
+ "Package.swift"
1681
+ ].some((marker) => existsSync6(join6(cwd, marker)));
1682
+ }
1535
1683
  function hasVerificationTool(toolCalls) {
1536
1684
  return toolCalls.some((tool) => {
1537
1685
  if (isPreferredVerification(tool)) return true;
@@ -1570,6 +1718,9 @@ function messagesSinceLastUser(messages) {
1570
1718
  }
1571
1719
  return messages;
1572
1720
  }
1721
+ function userMessageContent(messages) {
1722
+ return messages.filter((message) => message.role === "user" && typeof message.content === "string").map((message) => message.content ?? "");
1723
+ }
1573
1724
 
1574
1725
  // src/providers/adapters/deepseek.ts
1575
1726
  var deepSeekAdapter = {
@@ -1745,13 +1896,13 @@ function normalizeProviderId(provider) {
1745
1896
 
1746
1897
  // src/memory/runArchive.ts
1747
1898
  import { appendFile, mkdir as mkdir3, readFile as readFile2 } from "fs/promises";
1748
- import { existsSync as existsSync6 } from "fs";
1749
- import { join as join6 } from "path";
1899
+ import { existsSync as existsSync7 } from "fs";
1900
+ import { join as join7 } from "path";
1750
1901
  var appendQueues = /* @__PURE__ */ new Map();
1751
1902
  async function appendArchive(runId, messages, options = {}) {
1752
1903
  if (messages.length === 0) return;
1753
1904
  const path = archivePath(options.workspace ?? process.cwd(), runId);
1754
- await mkdir3(join6(options.workspace ?? process.cwd(), ".tanya", "runs", runId), { recursive: true });
1905
+ await mkdir3(join7(options.workspace ?? process.cwd(), ".tanya", "runs", runId), { recursive: true });
1755
1906
  const payload = messages.map((message) => JSON.stringify(message)).join("\n") + "\n";
1756
1907
  const previous = appendQueues.get(path) ?? Promise.resolve();
1757
1908
  const next = previous.then(() => appendFile(path, payload, "utf8"));
@@ -1761,7 +1912,7 @@ async function appendArchive(runId, messages, options = {}) {
1761
1912
  }
1762
1913
  async function readArchive(runId, options = {}) {
1763
1914
  const path = archivePath(options.workspace ?? process.cwd(), runId);
1764
- if (!existsSync6(path)) return [];
1915
+ if (!existsSync7(path)) return [];
1765
1916
  const raw = await readFile2(path, "utf8");
1766
1917
  return raw.split(/\r?\n/).filter(Boolean).flatMap((line) => {
1767
1918
  try {
@@ -1813,7 +1964,7 @@ function fileTouchPathsFromArchive(entries) {
1813
1964
  return [...paths].sort();
1814
1965
  }
1815
1966
  function archivePath(workspace, runId) {
1816
- return join6(workspace, ".tanya", "runs", runId, "archive.jsonl");
1967
+ return join7(workspace, ".tanya", "runs", runId, "archive.jsonl");
1817
1968
  }
1818
1969
  function fileTouchPathsFromArchivedContent(content) {
1819
1970
  const paths = /* @__PURE__ */ new Set();
@@ -2140,41 +2291,61 @@ var EscalationExhaustedError = class extends Error {
2140
2291
  }
2141
2292
  };
2142
2293
  function resolveRouteWithContextGuard(params) {
2143
- const primary = resolveRoute(params.stepType, params.table, params.routeText);
2144
2294
  const estimate = estimateCompactTokens(params.messages);
2295
+ const safetyFactor = routeSafetyFactor();
2296
+ if (params.forcedRoute) {
2297
+ return resolveForcedRoute(params.forcedRoute, params.stepType, estimate, safetyFactor);
2298
+ }
2299
+ const primary = resolveRoute(params.stepType, params.table, params.routeText);
2300
+ const guardedPrimary = codeEditingDeepSeekChatGuard(primary, params);
2145
2301
  const candidates = [
2146
- primary,
2147
- ...primary.fallback ? [routeFromTarget(primary.fallback, primary, "fallback")] : [],
2148
- {
2149
- provider: params.table.defaults.provider,
2150
- model: params.table.defaults.model,
2151
- match: "defaults",
2152
- escalate: true,
2153
- source: params.table.defaultSource,
2154
- reason: "matched route defaults"
2155
- }
2302
+ guardedPrimary,
2303
+ ...guardedPrimary.fallback ? [routeFromTarget(guardedPrimary.fallback, guardedPrimary, "fallback")] : [],
2304
+ ...cascadeRoutesForStep(params.table, params.stepType)
2156
2305
  ];
2157
2306
  const seen = /* @__PURE__ */ new Set();
2307
+ const attempts = [];
2158
2308
  for (const candidate of candidates) {
2159
2309
  const key = `${candidate.provider}/${candidate.model}`;
2160
2310
  if (seen.has(key)) continue;
2161
2311
  seen.add(key);
2162
- const window = contextWindowForTarget(candidate);
2163
- if (estimate <= window) {
2164
- return candidate === primary ? candidate : { ...candidate, reason: `${candidate.reason}; declined earlier route due to context-window guard` };
2312
+ const attempt = routeAttempt(candidate, estimate, safetyFactor);
2313
+ attempts.push(attempt);
2314
+ if (estimate <= attempt.safetyLimit) {
2315
+ if (attempts.length === 1 && candidate === primary) return candidate;
2316
+ const reason = attempts.length > 1 ? `${candidate.reason}; cascade-fit; declined earlier route due to context-window guard` : candidate.reason;
2317
+ return {
2318
+ ...candidate,
2319
+ reason,
2320
+ cascade: {
2321
+ reason: "cascade-fit",
2322
+ estimatedTokens: estimate,
2323
+ safetyFactor,
2324
+ attemptedRoutes: attempts,
2325
+ selectedRoute: attempt,
2326
+ selectedIndex: attempts.length - 1
2327
+ }
2328
+ };
2165
2329
  }
2166
2330
  }
2331
+ const highest = highestAttempt(attempts);
2332
+ if (highest) {
2333
+ throw new RouteContextOverflowError(
2334
+ `Estimated ${estimate.toLocaleString("en-US")} tokens exceeds the largest configured route ${highest.provider}/${highest.model} (${highest.maxInputTokens.toLocaleString("en-US")} tokens \xD7 ${safetyFactor} safety = ${highest.safetyLimit.toLocaleString("en-US")}). Reduce prompt or add a higher-context route.`
2335
+ );
2336
+ }
2167
2337
  throw new RouteContextOverflowError(
2168
2338
  `No route can fit estimated ${estimate} tokens for step ${params.stepType}.`
2169
2339
  );
2170
2340
  }
2171
2341
  function contextWindowForTarget(target) {
2172
- return resolveProviderAdapter({ provider: target.provider }).capabilities.contextWindow;
2342
+ return target.maxInputTokens ?? modelContextWindow(target) ?? resolveProviderAdapter({ provider: target.provider }).capabilities.contextWindow;
2173
2343
  }
2174
2344
  function routeFromTarget(target, primary, label) {
2175
2345
  return {
2176
2346
  provider: target.provider,
2177
2347
  model: target.model,
2348
+ ...target.maxInputTokens ? { maxInputTokens: target.maxInputTokens } : {},
2178
2349
  match: primary.match,
2179
2350
  escalate: primary.escalate,
2180
2351
  ...primary.reasoningCap ? { reasoningCap: primary.reasoningCap } : {},
@@ -2182,11 +2353,96 @@ function routeFromTarget(target, primary, label) {
2182
2353
  reason: `matched ${label} for ${primary.provider}/${primary.model}`
2183
2354
  };
2184
2355
  }
2356
+ function cascadeRoutesForStep(table, stepType) {
2357
+ return table.cascade.filter((entry) => !entry.stepTypes || entry.stepTypes.includes(stepType)).map((entry) => ({
2358
+ provider: entry.provider,
2359
+ model: entry.model,
2360
+ maxInputTokens: entry.maxInputTokens,
2361
+ match: "defaults",
2362
+ escalate: true,
2363
+ source: entry.source,
2364
+ reason: `matched route cascade ${entry.provider}/${entry.model}`
2365
+ }));
2366
+ }
2367
+ function resolveForcedRoute(target, stepType, estimate, safetyFactor) {
2368
+ const candidate = {
2369
+ provider: target.provider,
2370
+ model: target.model,
2371
+ ...target.maxInputTokens ? { maxInputTokens: target.maxInputTokens } : {},
2372
+ match: stepType,
2373
+ escalate: false,
2374
+ source: "session",
2375
+ reason: `forced route ${target.provider}/${target.model}`
2376
+ };
2377
+ const attempt = routeAttempt(candidate, estimate, safetyFactor);
2378
+ if (estimate <= attempt.safetyLimit) return candidate;
2379
+ throw new RouteContextOverflowError(
2380
+ `Forced route ${target.provider}/${target.model} cannot fit estimated ${estimate.toLocaleString("en-US")} tokens (${attempt.maxInputTokens.toLocaleString("en-US")} tokens \xD7 ${safetyFactor} safety = ${attempt.safetyLimit.toLocaleString("en-US")}). Reduce prompt or choose a higher-context forced route.`
2381
+ );
2382
+ }
2383
+ function routeAttempt(route, _estimate, safetyFactor) {
2384
+ const maxInputTokens = contextWindowForTarget(route);
2385
+ return {
2386
+ provider: route.provider,
2387
+ model: route.model,
2388
+ maxInputTokens,
2389
+ safetyLimit: Math.floor(maxInputTokens * safetyFactor),
2390
+ source: route.source,
2391
+ reason: route.reason
2392
+ };
2393
+ }
2394
+ function highestAttempt(attempts) {
2395
+ return attempts.reduce((highest, attempt) => {
2396
+ if (!highest || attempt.maxInputTokens > highest.maxInputTokens) return attempt;
2397
+ return highest;
2398
+ }, null);
2399
+ }
2400
+ function routeSafetyFactor(env = process.env) {
2401
+ const raw = Number(envValue(env, "TANYA_ROUTE_SAFETY_FACTOR"));
2402
+ if (!Number.isFinite(raw) || raw <= 0 || raw > 1) return 0.85;
2403
+ return raw;
2404
+ }
2405
+ function modelContextWindow(target) {
2406
+ const model = target.model.toLowerCase();
2407
+ if (target.provider === "claude" || model.startsWith("claude-")) return 1e6;
2408
+ if (target.provider === "gemini" || model.startsWith("gemini-")) return 2e6;
2409
+ if (model.includes("gpt-5-codex") || model.includes("o3-codex")) return 2e5;
2410
+ return null;
2411
+ }
2412
+ function codeEditingDeepSeekChatGuard(primary, params) {
2413
+ if (params.stepType !== "unknown") return primary;
2414
+ if (!isDeepSeekChat(primary)) return primary;
2415
+ const codeTaskState = {
2416
+ messages: params.messages,
2417
+ ...params.prompt ? { prompt: params.prompt } : {},
2418
+ ...params.cwd ? { cwd: params.cwd } : {},
2419
+ ...params.runContext ? { runContext: params.runContext } : {}
2420
+ };
2421
+ if (!looksLikeCodeEditingTask(codeTaskState)) {
2422
+ return primary;
2423
+ }
2424
+ const target = firstNonDeepSeekChatTarget([
2425
+ primary.fallback,
2426
+ params.table.defaults,
2427
+ { provider: "deepseek", model: "deepseek-reasoner" }
2428
+ ]);
2429
+ if (!target) return primary;
2430
+ return {
2431
+ ...routeFromTarget(target, primary, "fallback"),
2432
+ reason: `${primary.reason}; declined deepseek-chat for code-editing task`
2433
+ };
2434
+ }
2435
+ function firstNonDeepSeekChatTarget(targets) {
2436
+ return targets.find((target) => Boolean(target && !isDeepSeekChat(target))) ?? null;
2437
+ }
2438
+ function isDeepSeekChat(target) {
2439
+ return target.provider === "deepseek" && target.model === "deepseek-chat";
2440
+ }
2185
2441
 
2186
2442
  // src/context/loader.ts
2187
2443
  import { spawnSync as spawnSync2 } from "child_process";
2188
- import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
2189
- import { join as join7, relative as relative3 } from "path";
2444
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
2445
+ import { join as join8, relative as relative3 } from "path";
2190
2446
  var ignoredNames = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "build", ".turbo", ".cache"]);
2191
2447
  var artifactIgnoredNames = /* @__PURE__ */ new Set([".DS_Store", ".git", "node_modules", "dist", "build"]);
2192
2448
  var instructionFiles = ["TANYA.md", "TANIA.md", "AGENTS.md", "CLAUDE.md", "README.md", ".tania/INSTRUCTIONS.md"];
@@ -2200,7 +2456,7 @@ var projectMarkers = [
2200
2456
  ];
2201
2457
  function readIfExists(filePath, maxChars = 4e3) {
2202
2458
  try {
2203
- if (!existsSync7(filePath)) return null;
2459
+ if (!existsSync8(filePath)) return null;
2204
2460
  const content = readFileSync5(filePath, "utf8");
2205
2461
  return content.length > maxChars ? `${content.slice(0, maxChars)}
2206
2462
  [truncated]` : content;
@@ -2210,7 +2466,7 @@ function readIfExists(filePath, maxChars = 4e3) {
2210
2466
  }
2211
2467
  function readFullIfExists(filePath) {
2212
2468
  try {
2213
- if (!existsSync7(filePath)) return null;
2469
+ if (!existsSync8(filePath)) return null;
2214
2470
  return readFileSync5(filePath, "utf8");
2215
2471
  } catch {
2216
2472
  return null;
@@ -2250,14 +2506,14 @@ function scoreArtifactDirectory(name, taskHint) {
2250
2506
  }
2251
2507
  function artifactDirectoryPreview(artifactsRoot, directory, maxEntries = 10) {
2252
2508
  try {
2253
- return readdirSync2(join7(artifactsRoot, directory), { withFileTypes: true }).filter((entry) => !artifactIgnoredNames.has(entry.name)).sort((a, b) => Number(a.isDirectory()) - Number(b.isDirectory()) || a.name.localeCompare(b.name)).slice(0, maxEntries).map((entry) => entry.isDirectory() ? `${directory}/${entry.name}/` : `${directory}/${entry.name}`);
2509
+ return readdirSync2(join8(artifactsRoot, directory), { withFileTypes: true }).filter((entry) => !artifactIgnoredNames.has(entry.name)).sort((a, b) => Number(a.isDirectory()) - Number(b.isDirectory()) || a.name.localeCompare(b.name)).slice(0, maxEntries).map((entry) => entry.isDirectory() ? `${directory}/${entry.name}/` : `${directory}/${entry.name}`);
2254
2510
  } catch {
2255
2511
  return [];
2256
2512
  }
2257
2513
  }
2258
2514
  function buildArtifactIndexBlock(workspace, taskHint = "") {
2259
- const artifactsRoot = join7(workspace, "artifacts");
2260
- if (!existsSync7(artifactsRoot)) return "";
2515
+ const artifactsRoot = join8(workspace, "artifacts");
2516
+ if (!existsSync8(artifactsRoot)) return "";
2261
2517
  let entries;
2262
2518
  try {
2263
2519
  entries = readdirSync2(artifactsRoot, { withFileTypes: true });
@@ -2270,13 +2526,13 @@ function buildArtifactIndexBlock(workspace, taskHint = "") {
2270
2526
  preview: artifactDirectoryPreview(artifactsRoot, entry.name)
2271
2527
  })).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
2272
2528
  const rulesCandidates = [
2273
- { path: "artifacts/prompts/RULES.md", absolutePath: join7(artifactsRoot, "prompts", "RULES.md") },
2274
- { path: "artifacts/RULES.md", absolutePath: join7(artifactsRoot, "RULES.md") }
2529
+ { path: "artifacts/prompts/RULES.md", absolutePath: join8(artifactsRoot, "prompts", "RULES.md") },
2530
+ { path: "artifacts/RULES.md", absolutePath: join8(artifactsRoot, "RULES.md") }
2275
2531
  ];
2276
2532
  const rulesRead = rulesCandidates.map((candidate) => ({ path: candidate.path, content: readFullIfExists(candidate.absolutePath) })).find((candidate) => candidate.content !== null && candidate.content.trim().length > 0);
2277
2533
  const indexReads = [
2278
- { path: "artifacts/description.md", content: readIfExists(join7(artifactsRoot, "description.md"), 12e3) },
2279
- { path: "artifacts/README.md", content: readIfExists(join7(artifactsRoot, "README.md"), 12e3) }
2534
+ { path: "artifacts/description.md", content: readIfExists(join8(artifactsRoot, "description.md"), 12e3) },
2535
+ { path: "artifacts/README.md", content: readIfExists(join8(artifactsRoot, "README.md"), 12e3) }
2280
2536
  ].filter((entry) => entry.content !== null && entry.content.trim().length > 0);
2281
2537
  if (!rulesRead && indexReads.length === 0 && directories.length === 0) return "";
2282
2538
  const lines = [
@@ -2306,7 +2562,7 @@ function buildArtifactIndexBlock(workspace, taskHint = "") {
2306
2562
  const maxArtifactFiles = 4;
2307
2563
  const scoredFiles = [];
2308
2564
  for (const directory of directories) {
2309
- const subdirPath = join7(artifactsRoot, directory.name);
2565
+ const subdirPath = join8(artifactsRoot, directory.name);
2310
2566
  let mdFiles = [];
2311
2567
  try {
2312
2568
  mdFiles = readdirSync2(subdirPath, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
@@ -2318,7 +2574,7 @@ function buildArtifactIndexBlock(workspace, taskHint = "") {
2318
2574
  if (fileScore > 0) {
2319
2575
  scoredFiles.push({
2320
2576
  path: `artifacts/${directory.name}/${mdFile}`,
2321
- fullPath: join7(subdirPath, mdFile),
2577
+ fullPath: join8(subdirPath, mdFile),
2322
2578
  score: fileScore
2323
2579
  });
2324
2580
  }
@@ -2366,7 +2622,7 @@ function collectTree(workspace, maxEntries = 120, maxDepth = 2) {
2366
2622
  for (const entry of dirents) {
2367
2623
  if (entries.length >= maxEntries) return;
2368
2624
  if (ignoredNames.has(entry.name)) continue;
2369
- const fullPath = join7(dir, entry.name);
2625
+ const fullPath = join8(dir, entry.name);
2370
2626
  const rel = relative3(workspace, fullPath).replace(/\\/g, "/");
2371
2627
  entries.push(entry.isDirectory() ? `${rel}/` : rel);
2372
2628
  if (entry.isDirectory()) walk(fullPath, depth + 1);
@@ -2378,9 +2634,9 @@ function collectTree(workspace, maxEntries = 120, maxDepth = 2) {
2378
2634
  function detectProjectTypes(workspace) {
2379
2635
  const types = /* @__PURE__ */ new Set();
2380
2636
  for (const marker of projectMarkers) {
2381
- if (existsSync7(join7(workspace, marker.file))) types.add(marker.type);
2637
+ if (existsSync8(join8(workspace, marker.file))) types.add(marker.type);
2382
2638
  }
2383
- if (existsSync7(join7(workspace, "gradlew")) || existsSync7(join7(workspace, "settings.gradle")) || existsSync7(join7(workspace, "settings.gradle.kts"))) {
2639
+ if (existsSync8(join8(workspace, "gradlew")) || existsSync8(join8(workspace, "settings.gradle")) || existsSync8(join8(workspace, "settings.gradle.kts"))) {
2384
2640
  types.add("android");
2385
2641
  }
2386
2642
  try {
@@ -2401,7 +2657,7 @@ function collectTypeScriptFiles(workspace) {
2401
2657
  }
2402
2658
  for (const entry of dirents) {
2403
2659
  if (ignoredNames.has(entry.name)) continue;
2404
- const fullPath = join7(dir, entry.name);
2660
+ const fullPath = join8(dir, entry.name);
2405
2661
  if (entry.isDirectory()) {
2406
2662
  walk(fullPath);
2407
2663
  continue;
@@ -2425,7 +2681,7 @@ function formatExportName(entry) {
2425
2681
  return entry.isDefault ? `${entry.name} (default)` : entry.name;
2426
2682
  }
2427
2683
  function parseFileExports(workspace, file) {
2428
- const content = readIfExists(join7(workspace, file), 2e5);
2684
+ const content = readIfExists(join8(workspace, file), 2e5);
2429
2685
  if (!content) return null;
2430
2686
  const exports = [];
2431
2687
  const seen = /* @__PURE__ */ new Set();
@@ -2487,7 +2743,7 @@ function buildExportMap(workspace) {
2487
2743
  function detectVerificationSuggestions(workspace, projectTypes, packageScripts) {
2488
2744
  const suggestions = /* @__PURE__ */ new Set();
2489
2745
  if (projectTypes.includes("android")) {
2490
- const gradle = existsSync7(join7(workspace, "gradlew")) ? "./gradlew" : "gradle";
2746
+ const gradle = existsSync8(join8(workspace, "gradlew")) ? "./gradlew" : "gradle";
2491
2747
  suggestions.add(`${gradle} test`);
2492
2748
  suggestions.add(`${gradle} assembleDebug`);
2493
2749
  }
@@ -2505,7 +2761,7 @@ function detectVerificationSuggestions(workspace, projectTypes, packageScripts)
2505
2761
  return [...suggestions].slice(0, 8);
2506
2762
  }
2507
2763
  function readPackageScripts(workspace) {
2508
- const content = readIfExists(join7(workspace, "package.json"), 8e4);
2764
+ const content = readIfExists(join8(workspace, "package.json"), 8e4);
2509
2765
  if (!content) return {};
2510
2766
  try {
2511
2767
  const parsed = JSON.parse(content);
@@ -2525,7 +2781,7 @@ function loadWorkspaceSummary(workspace) {
2525
2781
  const gitStatus = isGitRepo ? runGit(workspace, ["status", "--short"]) ?? "" : null;
2526
2782
  const instructionReads = [];
2527
2783
  for (const fileName of instructionFiles) {
2528
- const filePath = join7(workspace, fileName);
2784
+ const filePath = join8(workspace, fileName);
2529
2785
  const content = readIfExists(filePath, fileName === "README.md" ? 6e3 : 4e3);
2530
2786
  if (content !== null) {
2531
2787
  instructionReads.push({ path: fileName, content });
@@ -2582,20 +2838,20 @@ function buildContextBlock(workspace) {
2582
2838
  }
2583
2839
 
2584
2840
  // src/skills/load.ts
2585
- import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
2586
- import { dirname as dirname5, join as join9, relative as relative4 } from "path";
2841
+ import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
2842
+ import { dirname as dirname5, join as join10, relative as relative4 } from "path";
2587
2843
  import { fileURLToPath as fileURLToPath2 } from "url";
2588
2844
  import { load as parseYaml } from "js-yaml";
2589
2845
 
2590
2846
  // src/integrations/discovery.ts
2591
- import { existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
2592
- import { basename, dirname as dirname4, join as join8 } from "path";
2847
+ import { existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
2848
+ import { basename, dirname as dirname4, join as join9 } from "path";
2593
2849
  import { fileURLToPath } from "url";
2594
2850
  var moduleRoot = dirname4(fileURLToPath(import.meta.url));
2595
2851
  var defaultIntegrationsRoot = null;
2596
2852
  function safeIsDirectory(path) {
2597
2853
  try {
2598
- return existsSync8(path) && statSync2(path).isDirectory();
2854
+ return existsSync9(path) && statSync2(path).isDirectory();
2599
2855
  } catch {
2600
2856
  return false;
2601
2857
  }
@@ -2610,7 +2866,7 @@ function safeReadDirectory(path) {
2610
2866
  function hasMarkdownFile(root) {
2611
2867
  const entries = safeReadDirectory(root);
2612
2868
  for (const entry of entries) {
2613
- const path = join8(root, entry.name);
2869
+ const path = join9(root, entry.name);
2614
2870
  if (entry.isFile() && entry.name.endsWith(".md")) return true;
2615
2871
  if (entry.isDirectory() && hasMarkdownFile(path)) return true;
2616
2872
  }
@@ -2619,14 +2875,14 @@ function hasMarkdownFile(root) {
2619
2875
  function resolveDefaultSkillsRoot() {
2620
2876
  const candidates = [
2621
2877
  moduleRoot,
2622
- join8(moduleRoot, "skills"),
2623
- join8(moduleRoot, "..", "src", "skills"),
2624
- join8(moduleRoot, "..", "skills")
2878
+ join9(moduleRoot, "skills"),
2879
+ join9(moduleRoot, "..", "src", "skills"),
2880
+ join9(moduleRoot, "..", "skills")
2625
2881
  ];
2626
2882
  for (const candidate of candidates) {
2627
2883
  if (safeIsDirectory(candidate) && hasMarkdownFile(candidate)) return candidate;
2628
2884
  }
2629
- return join8(moduleRoot, "..", "skills");
2885
+ return join9(moduleRoot, "..", "skills");
2630
2886
  }
2631
2887
  function packageRootFromSkillsRoot(skillsRoot) {
2632
2888
  const parent = dirname4(skillsRoot);
@@ -2637,7 +2893,7 @@ function packageRootFromSkillsRoot(skillsRoot) {
2637
2893
  function integrationsRoot(env = process.env) {
2638
2894
  const override = envValue(env, "TANYA_INTEGRATIONS_DIR").trim();
2639
2895
  if (override) return override;
2640
- defaultIntegrationsRoot ??= join8(packageRootFromSkillsRoot(resolveDefaultSkillsRoot()), "integrations");
2896
+ defaultIntegrationsRoot ??= join9(packageRootFromSkillsRoot(resolveDefaultSkillsRoot()), "integrations");
2641
2897
  return defaultIntegrationsRoot;
2642
2898
  }
2643
2899
  function discoverIntegrationEntries(kind, opts = {}) {
@@ -2646,14 +2902,14 @@ function discoverIntegrationEntries(kind, opts = {}) {
2646
2902
  const entries = [];
2647
2903
  const integrations = safeReadDirectory(root).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
2648
2904
  for (const integration of integrations) {
2649
- const kindRoot = join8(root, integration.name, kind);
2905
+ const kindRoot = join9(root, integration.name, kind);
2650
2906
  if (!safeIsDirectory(kindRoot)) continue;
2651
2907
  const discovered = safeReadDirectory(kindRoot).filter((entry) => entry.isDirectory() || entry.isFile()).sort((a, b) => a.name.localeCompare(b.name));
2652
2908
  for (const entry of discovered) {
2653
2909
  entries.push({
2654
2910
  integration: integration.name,
2655
2911
  kind,
2656
- path: join8(kindRoot, entry.name)
2912
+ path: join9(kindRoot, entry.name)
2657
2913
  });
2658
2914
  }
2659
2915
  }
@@ -2685,7 +2941,7 @@ function estimateTokens(text2) {
2685
2941
  }
2686
2942
  function safeExists(path) {
2687
2943
  try {
2688
- return existsSync9(path);
2944
+ return existsSync10(path);
2689
2945
  } catch {
2690
2946
  return false;
2691
2947
  }
@@ -2693,13 +2949,13 @@ function safeExists(path) {
2693
2949
  function resolveDefaultSkillsRoot2() {
2694
2950
  const candidates = [
2695
2951
  moduleRoot2,
2696
- join9(moduleRoot2, "skills"),
2697
- join9(moduleRoot2, "..", "src", "skills"),
2698
- join9(moduleRoot2, "..", "skills")
2952
+ join10(moduleRoot2, "skills"),
2953
+ join10(moduleRoot2, "..", "src", "skills"),
2954
+ join10(moduleRoot2, "..", "skills")
2699
2955
  ];
2700
2956
  for (const candidate of candidates) {
2701
2957
  try {
2702
- if (existsSync9(candidate) && statSync3(candidate).isDirectory() && collectSkillFiles(candidate).length > 0) return candidate;
2958
+ if (existsSync10(candidate) && statSync3(candidate).isDirectory() && collectSkillFiles(candidate).length > 0) return candidate;
2703
2959
  } catch {
2704
2960
  }
2705
2961
  }
@@ -2737,7 +2993,7 @@ function walkWorkspace(workspace, maxEntries = 8e3) {
2737
2993
  for (const entry of entries) {
2738
2994
  if (files.length + dirs.length >= maxEntries) return;
2739
2995
  if (ignoredDirectories.has(entry.name)) continue;
2740
- const fullPath = join9(current, entry.name);
2996
+ const fullPath = join10(current, entry.name);
2741
2997
  const relPath = relative4(workspace, fullPath).replace(/\\/g, "/");
2742
2998
  if (entry.isDirectory()) {
2743
2999
  dirs.push(relPath);
@@ -2761,7 +3017,7 @@ function collectSkillFiles(skillsRoot) {
2761
3017
  }
2762
3018
  for (const entry of entries) {
2763
3019
  if (entry.name.startsWith(".")) continue;
2764
- const fullPath = join9(current, entry.name);
3020
+ const fullPath = join10(current, entry.name);
2765
3021
  if (entry.isDirectory()) {
2766
3022
  walk(fullPath);
2767
3023
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -2857,7 +3113,7 @@ function pathMatchesGlob(path, glob) {
2857
3113
  return new RegExp(`^${escaped}$`).test(path);
2858
3114
  }
2859
3115
  function packageJsonHasDependency(workspace, dep) {
2860
- const parsed = readJson(join9(workspace, "package.json"));
3116
+ const parsed = readJson(join10(workspace, "package.json"));
2861
3117
  if (!parsed) return false;
2862
3118
  for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
2863
3119
  const deps = parsed[key];
@@ -2879,7 +3135,7 @@ function selectContentFiles(files) {
2879
3135
  }
2880
3136
  function anyContentContains(workspace, files, needle) {
2881
3137
  return files.some((file) => {
2882
- const content = safeRead(join9(workspace, file), 12e4);
3138
+ const content = safeRead(join10(workspace, file), 12e4);
2883
3139
  return typeof needle === "string" ? content.includes(needle) : needle.test(content);
2884
3140
  });
2885
3141
  }
@@ -2890,20 +3146,20 @@ function detectWorkspaceSignals(ctx) {
2890
3146
  const go = hasRootFile(files, "go.mod");
2891
3147
  const pkgMigrations = files.some((file) => /^pkg\/[^/]+\/migrations\//.test(file));
2892
3148
  const goHouse = go && pkgMigrations && anyContentContains(workspace, contentFiles, "Module.Attach");
2893
- const goHuma = go && (safeRead(join9(workspace, "go.sum")).includes("github.com/danielgtaylor/huma") || dirs.includes("internal/store/gen") || normalize(ctx.hints.stack ?? "") === "backend-go-huma" || /artifacts\/backend-go|backend-go-huma/i.test(ctx.taskHint ?? ""));
3149
+ const goHuma = go && (safeRead(join10(workspace, "go.sum")).includes("github.com/danielgtaylor/huma") || dirs.includes("internal/store/gen") || normalize(ctx.hints.stack ?? "") === "backend-go-huma" || /artifacts\/backend-go|backend-go-huma/i.test(ctx.taskHint ?? ""));
2894
3150
  const ios = hasRootFile(files, "Package.swift") || hasRootDir(dirs, /(?:^|\/)[^/]+\.xcodeproj$/);
2895
3151
  const swiftData = ios && anyContentContains(workspace, contentFiles, "@Model");
2896
3152
  const revenueCatIos = ios && (anyContentContains(workspace, contentFiles, "import RevenueCat") || anyContentContains(workspace, contentFiles, /RevenueCat/i));
2897
3153
  const gradleFiles = files.filter((file) => /(?:^|\/)build\.gradle(?:\.kts)?$/.test(file));
2898
- const android = hasRootFile(files, "build.gradle.kts") || gradleFiles.some((file) => file.endsWith("build.gradle.kts") || /kotlin/i.test(safeRead(join9(workspace, file), 8e4)));
2899
- const libsVersions = safeRead(join9(workspace, "gradle/libs.versions.toml"), 12e4);
3154
+ const android = hasRootFile(files, "build.gradle.kts") || gradleFiles.some((file) => file.endsWith("build.gradle.kts") || /kotlin/i.test(safeRead(join10(workspace, file), 8e4)));
3155
+ const libsVersions = safeRead(join10(workspace, "gradle/libs.versions.toml"), 12e4);
2900
3156
  const room = android && /room/i.test(libsVersions);
2901
3157
  const retrofit = android && anyContentContains(workspace, contentFiles, /import\s+retrofit2\b/);
2902
3158
  const revenueCatAndroid = android && anyContentContains(workspace, contentFiles, "com.revenuecat.purchases");
2903
- const packageJson = safeRead(join9(workspace, "package.json"), 12e4);
3159
+ const packageJson = safeRead(join10(workspace, "package.json"), 12e4);
2904
3160
  const next = (hasRootFile(files, "next.config.js") || hasRootFile(files, "next.config.ts") || hasRootFile(files, "next.config.mjs")) && /"next"\s*:/.test(packageJson);
2905
3161
  const tailwindV4 = anyContentContains(workspace, files.filter((file) => file.endsWith(".css")), '@import "tailwindcss"') || packageJsonHasDependency(workspace, "@tailwindcss/postcss");
2906
- const shadcn = hasRootFile(files, "components.json") || safeExists(join9(workspace, "components/ui")) || safeExists(join9(workspace, "src/components/ui")) || dirs.includes("components/ui") || dirs.includes("src/components/ui");
3162
+ const shadcn = hasRootFile(files, "components.json") || safeExists(join10(workspace, "components/ui")) || safeExists(join10(workspace, "src/components/ui")) || dirs.includes("components/ui") || dirs.includes("src/components/ui");
2907
3163
  const fastlane = hasRootFile(files, "fastlane/Fastfile");
2908
3164
  return {
2909
3165
  files,
@@ -2933,7 +3189,7 @@ function frontmatterConditionReason(condition, ctx, signals) {
2933
3189
  case "always":
2934
3190
  return "always";
2935
3191
  case "workspace.has":
2936
- return safeExists(join9(ctx.workspace, condition.path)) ? "workspace" : null;
3192
+ return safeExists(join10(ctx.workspace, condition.path)) ? "workspace" : null;
2937
3193
  case "workspace.hasGlob":
2938
3194
  return [...signals.files, ...signals.dirs].some((path) => pathMatchesGlob(path, condition.glob)) ? "workspace" : null;
2939
3195
  case "workspace.packageJson":
@@ -3141,7 +3397,7 @@ function loadSkillPacksFromFiles(ctx, skillsRoot, files) {
3141
3397
  return enforceBudget(matched).map((pack) => ({
3142
3398
  slug: pack.frontmatter.slug,
3143
3399
  title: pack.title,
3144
- sourcePath: join9(skillsRoot, `${pack.relativePath}.md`),
3400
+ sourcePath: join10(skillsRoot, `${pack.relativePath}.md`),
3145
3401
  content: pack.body,
3146
3402
  tokens: pack.tokens,
3147
3403
  reason: pack.reason
@@ -3201,11 +3457,11 @@ function formatSkillPackSummary(packs) {
3201
3457
  }
3202
3458
 
3203
3459
  // src/agent/systemPrompt.ts
3204
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
3205
- import { basename as basename2, join as join10 } from "path";
3460
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
3461
+ import { basename as basename2, join as join11 } from "path";
3206
3462
  function readProjectInstructions(workspace) {
3207
- const path = join10(workspace, ".tania", "INSTRUCTIONS.md");
3208
- if (!existsSync10(path)) return "";
3463
+ const path = join11(workspace, ".tania", "INSTRUCTIONS.md");
3464
+ if (!existsSync11(path)) return "";
3209
3465
  try {
3210
3466
  const content = readFileSync7(path, "utf8").trim();
3211
3467
  return content ? `
@@ -3860,9 +4116,9 @@ function withoutParent(ctx) {
3860
4116
  }
3861
4117
 
3862
4118
  // src/safety/permissions/rules.ts
3863
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
4119
+ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
3864
4120
  import { homedir as homedir2 } from "os";
3865
- import { join as join11 } from "path";
4121
+ import { join as join12 } from "path";
3866
4122
 
3867
4123
  // src/safety/permissions/schema.ts
3868
4124
  var PERMISSIONS_SCHEMA_VERSION = 1;
@@ -4045,15 +4301,15 @@ function positiveNumber(value) {
4045
4301
  function loadPermissionRules(options) {
4046
4302
  const home = options.home ?? homedir2();
4047
4303
  const candidates = [
4048
- join11(home, ".tania", "permissions.json"),
4049
- join11(home, ".tanya", "permissions.json"),
4050
- join11(options.cwd, ".tania", "permissions.json")
4304
+ join12(home, ".tania", "permissions.json"),
4305
+ join12(home, ".tanya", "permissions.json"),
4306
+ join12(options.cwd, ".tania", "permissions.json")
4051
4307
  ];
4052
4308
  let rules = cloneRules(DEFAULT_PERMISSION_RULES);
4053
4309
  const sources = [];
4054
4310
  const issues = [];
4055
4311
  for (const file of candidates) {
4056
- if (!existsSync11(file)) continue;
4312
+ if (!existsSync12(file)) continue;
4057
4313
  const parsed = parsePermissionsJson(readFileSync8(file, "utf8"));
4058
4314
  if (!parsed.ok) {
4059
4315
  issues.push(...parsed.issues.map((issue2) => ({ ...issue2, file })));
@@ -4187,9 +4443,9 @@ function globPrefix(glob) {
4187
4443
  }
4188
4444
 
4189
4445
  // src/tools/fsTools.ts
4190
- import { existsSync as existsSync16, readdirSync as readdirSync6 } from "fs";
4446
+ import { existsSync as existsSync17, readdirSync as readdirSync6 } from "fs";
4191
4447
  import { cp, mkdir as mkdir7, readFile as readFile8, realpath, unlink, writeFile as writeFile7 } from "fs/promises";
4192
- import { basename as basename4, dirname as dirname9, isAbsolute, join as join15, relative as relative8, resolve as resolve7 } from "path";
4448
+ import { basename as basename4, dirname as dirname9, isAbsolute, join as join16, relative as relative8, resolve as resolve7 } from "path";
4193
4449
  import { spawn as spawn2 } from "child_process";
4194
4450
  import sharp2 from "sharp";
4195
4451
 
@@ -4663,8 +4919,8 @@ var validateAndroidLauncherIconSetTool = {
4663
4919
 
4664
4920
  // src/tools/projectContextTools.ts
4665
4921
  import { readdir as readdir2, readFile as readFile4, stat } from "fs/promises";
4666
- import { existsSync as existsSync12 } from "fs";
4667
- import { basename as basename3, extname, join as join12, relative as relative5, resolve as resolve4 } from "path";
4922
+ import { existsSync as existsSync13 } from "fs";
4923
+ import { basename as basename3, extname, join as join13, relative as relative5, resolve as resolve4 } from "path";
4668
4924
  var ignoredNames2 = /* @__PURE__ */ new Set([
4669
4925
  ".git",
4670
4926
  "node_modules",
@@ -4803,7 +5059,7 @@ async function collectFiles(root, maxFiles, current = root, out = []) {
4803
5059
  for (const entry of entries) {
4804
5060
  if (out.length >= maxFiles) break;
4805
5061
  if (ignoredNames2.has(entry.name)) continue;
4806
- const fullPath = join12(current, entry.name);
5062
+ const fullPath = join13(current, entry.name);
4807
5063
  const rel = normalizePath(relative5(root, fullPath));
4808
5064
  if (entry.isDirectory()) {
4809
5065
  if (entry.name === ".tania") {
@@ -4823,13 +5079,13 @@ async function collectFiles(root, maxFiles, current = root, out = []) {
4823
5079
  async function collectTanyaFiles(root, taniaDir, maxFiles, out) {
4824
5080
  for (const rel of ["INSTRUCTIONS.md", "artifacts/manifest.json"]) {
4825
5081
  if (out.length >= maxFiles) return;
4826
- const abs = join12(taniaDir, rel);
5082
+ const abs = join13(taniaDir, rel);
4827
5083
  try {
4828
5084
  if ((await stat(abs)).isFile()) out.push(normalizePath(relative5(root, abs)));
4829
5085
  } catch {
4830
5086
  }
4831
5087
  }
4832
- const contextDir = join12(taniaDir, "context");
5088
+ const contextDir = join13(taniaDir, "context");
4833
5089
  try {
4834
5090
  if ((await stat(contextDir)).isDirectory()) {
4835
5091
  out.push(normalizePath(relative5(root, contextDir)) + "/");
@@ -4848,7 +5104,7 @@ async function readExcerpt(path, maxChars) {
4848
5104
  }
4849
5105
  }
4850
5106
  async function readPackageScripts2(root) {
4851
- const path = join12(root, "package.json");
5107
+ const path = join13(root, "package.json");
4852
5108
  try {
4853
5109
  const parsed = JSON.parse(await readFile4(path, "utf8"));
4854
5110
  return Object.fromEntries(
@@ -5287,7 +5543,7 @@ var inspectProjectContextTool = {
5287
5543
  const candidates = ["artifacts/description.md", ".tania/artifacts/description.md"];
5288
5544
  for (const rel of candidates) {
5289
5545
  const abs = resolve4(root, rel);
5290
- if (existsSync12(abs)) {
5546
+ if (existsSync13(abs)) {
5291
5547
  const head = (await readFile4(abs, "utf8")).split("\n").slice(0, 24).join("\n");
5292
5548
  artifactsCatalog = { hint: `Read ${rel} (full file) for the complete artifact catalog before writing code from scratch.`, head };
5293
5549
  break;
@@ -5394,9 +5650,9 @@ var buildTaskBriefTool = {
5394
5650
  };
5395
5651
 
5396
5652
  // src/obsidian/search.ts
5397
- import { existsSync as existsSync13 } from "fs";
5653
+ import { existsSync as existsSync14 } from "fs";
5398
5654
  import { mkdir as mkdir5, readdir as readdir3, readFile as readFile5, stat as stat2, writeFile as writeFile4 } from "fs/promises";
5399
- import { dirname as dirname7, join as join13, relative as relative6, resolve as resolve5 } from "path";
5655
+ import { dirname as dirname7, join as join14, relative as relative6, resolve as resolve5 } from "path";
5400
5656
  var ignoredNames3 = /* @__PURE__ */ new Set([".obsidian", ".trash", ".git", "node_modules", ".tania"]);
5401
5657
  var stopwords = /* @__PURE__ */ new Set([
5402
5658
  "a",
@@ -5438,7 +5694,7 @@ async function collectMarkdownFiles(root, maxFiles, current = root, out = []) {
5438
5694
  for (const entry of entries) {
5439
5695
  if (out.length >= maxFiles) break;
5440
5696
  if (ignoredNames3.has(entry.name)) continue;
5441
- const fullPath = join13(current, entry.name);
5697
+ const fullPath = join14(current, entry.name);
5442
5698
  if (entry.isDirectory()) {
5443
5699
  await collectMarkdownFiles(root, maxFiles, fullPath, out);
5444
5700
  } else if (entry.isFile() && /\.md$/i.test(entry.name)) {
@@ -5497,7 +5753,7 @@ function safeMaterializedPath(relPath) {
5497
5753
  }
5498
5754
  async function searchObsidianNotes(input) {
5499
5755
  const vault = resolve5(input.vaultPath);
5500
- if (!existsSync13(vault)) return [];
5756
+ if (!existsSync14(vault)) return [];
5501
5757
  const queryTerms = terms(input.query);
5502
5758
  const maxResults = Math.min(input.maxResults ?? 5, 20);
5503
5759
  const maxFiles = Math.min(input.maxFiles ?? 1e3, 5e3);
@@ -5649,18 +5905,18 @@ var searchObsidianNotesTool = {
5649
5905
  };
5650
5906
 
5651
5907
  // src/memory/resultCache.ts
5652
- import { existsSync as existsSync14, mkdirSync as mkdirSync2, readdirSync as readdirSync5, rmSync, statSync as statSync4, writeFileSync as writeFileSync2 } from "fs";
5908
+ import { existsSync as existsSync15, mkdirSync as mkdirSync2, readdirSync as readdirSync5, rmSync, statSync as statSync4, writeFileSync as writeFileSync2 } from "fs";
5653
5909
  import { readFile as readFile6 } from "fs/promises";
5654
- import { join as join14 } from "path";
5910
+ import { join as join15 } from "path";
5655
5911
  var RESULT_CACHE_MAX_BYTES = 100 * 1024 * 1024;
5656
5912
  function safeSegment(value) {
5657
5913
  return value.replace(/[^A-Za-z0-9_.-]+/g, "_").slice(0, 160) || "result";
5658
5914
  }
5659
5915
  function resultCacheDir(workspace, runId) {
5660
- return join14(workspace, ".tania", "cache", "results", safeSegment(runId));
5916
+ return join15(workspace, ".tania", "cache", "results", safeSegment(runId));
5661
5917
  }
5662
5918
  function resultCachePath(workspace, runId, toolCallId) {
5663
- return join14(resultCacheDir(workspace, runId), `${safeSegment(toolCallId)}.txt`);
5919
+ return join15(resultCacheDir(workspace, runId), `${safeSegment(toolCallId)}.txt`);
5664
5920
  }
5665
5921
  function writeCachedToolResult(workspace, runId, toolCallId, content) {
5666
5922
  const dir = resultCacheDir(workspace, runId);
@@ -5672,7 +5928,7 @@ function writeCachedToolResult(workspace, runId, toolCallId, content) {
5672
5928
  }
5673
5929
  async function readCachedToolResult(workspace, runId, toolCallId, range) {
5674
5930
  const path = resultCachePath(workspace, runId, toolCallId);
5675
- if (!existsSync14(path)) return null;
5931
+ if (!existsSync15(path)) return null;
5676
5932
  const buffer = await readFile6(path);
5677
5933
  if (!range) return buffer.toString("utf8");
5678
5934
  const start = Math.max(0, Math.floor(range.startByte));
@@ -5702,7 +5958,7 @@ function evictRunCache(dir) {
5702
5958
  function cacheEntries(dir) {
5703
5959
  try {
5704
5960
  return readdirSync5(dir).filter((file) => file.endsWith(".txt")).flatMap((file) => {
5705
- const path = join14(dir, file);
5961
+ const path = join15(dir, file);
5706
5962
  try {
5707
5963
  const stat8 = statSync4(path);
5708
5964
  return stat8.isFile() ? [{ path, size: stat8.size, mtimeMs: stat8.mtimeMs }] : [];
@@ -5788,7 +6044,7 @@ var expandResultTool = {
5788
6044
  };
5789
6045
 
5790
6046
  // src/agent/subAgentContext.ts
5791
- import { existsSync as existsSync15, realpathSync as realpathSync2 } from "fs";
6047
+ import { existsSync as existsSync16, realpathSync as realpathSync2 } from "fs";
5792
6048
  import { randomUUID } from "crypto";
5793
6049
  import { relative as relative7, resolve as resolve6 } from "path";
5794
6050
  function createRootRunId(now = /* @__PURE__ */ new Date()) {
@@ -5806,7 +6062,7 @@ function resolveSubAgentWorkspace(parentWorkspace, requestedWorkspace) {
5806
6062
  if (lexicalRel.startsWith("..") || lexicalRel === "..") {
5807
6063
  throw new Error(`Sub-agent workspace escapes parent workspace: ${requestedWorkspace ?? target}`);
5808
6064
  }
5809
- if (!existsSync15(target)) return target;
6065
+ if (!existsSync16(target)) return target;
5810
6066
  const realParent = realpathSync2(parentWorkspace);
5811
6067
  const realTarget = realpathSync2(target);
5812
6068
  const realRel = relative7(realParent, realTarget);
@@ -6495,7 +6751,11 @@ var recordMetricsDashboardHandoffTool = {
6495
6751
 
6496
6752
  // src/tools/fsTools.ts
6497
6753
  var ignoredNames4 = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "build", ".turbo", ".cache"]);
6498
- var PROGRESS_THROTTLE_MS = 2e3;
6754
+ function getProgressThrottleMs() {
6755
+ const raw = Number(process.env.TANYA_PROGRESS_THROTTLE_MS);
6756
+ return Number.isFinite(raw) && raw >= 0 ? raw : 2e3;
6757
+ }
6758
+ var PROGRESS_THROTTLE_MS = getProgressThrottleMs();
6499
6759
  function isProtectedLocalConfigPath(filePath) {
6500
6760
  return basename4(filePath.trim().replace(/\\/g, "/")) === "local.properties";
6501
6761
  }
@@ -6537,13 +6797,13 @@ function asOptionalBoolean3(input, key, fallback) {
6537
6797
  return fallback;
6538
6798
  }
6539
6799
  async function pathExists(path) {
6540
- return existsSync16(path);
6800
+ return existsSync17(path);
6541
6801
  }
6542
6802
  function collectFiles2(root, maxFiles, current = root, out = []) {
6543
6803
  if (out.length >= maxFiles) return out;
6544
6804
  for (const entry of readdirSync6(current, { withFileTypes: true })) {
6545
6805
  if (ignoredNames4.has(entry.name)) continue;
6546
- const fullPath = join15(current, entry.name);
6806
+ const fullPath = join16(current, entry.name);
6547
6807
  if (entry.isDirectory()) {
6548
6808
  collectFiles2(root, maxFiles, fullPath, out);
6549
6809
  } else if (entry.isFile()) {
@@ -6637,7 +6897,7 @@ function runShell(script, context, timeoutMs, cwd = context.workspace) {
6637
6897
  if (!context.onProgress || !chunk) return;
6638
6898
  progressBuffers[stream] += chunk;
6639
6899
  if (progressTimers[stream]) return;
6640
- progressTimers[stream] = setTimeout(() => flushProgress(stream), PROGRESS_THROTTLE_MS);
6900
+ progressTimers[stream] = setTimeout(() => flushProgress(stream), getProgressThrottleMs());
6641
6901
  progressTimers[stream]?.unref?.();
6642
6902
  };
6643
6903
  const outputSoFar = () => `${stdout2}${stderr ? `
@@ -7421,16 +7681,16 @@ var validateAppleProjectFilesTool = {
7421
7681
  let pbxprojText = "";
7422
7682
  if (xcodeprojPath) {
7423
7683
  const projectDir = resolveInsideWorkspace(context.workspace, ensureRelativePath3(xcodeprojPath));
7424
- if (!existsSync16(projectDir)) {
7684
+ if (!existsSync17(projectDir)) {
7425
7685
  problems.push(`Missing ${xcodeprojPath}.`);
7426
7686
  } else {
7427
7687
  const pbxprojPath = resolveInsideWorkspace(context.workspace, `${xcodeprojPath.replace(/\/+$/, "")}/project.pbxproj`);
7428
- if (existsSync16(pbxprojPath)) pbxprojText = await readFile8(pbxprojPath, "utf8");
7688
+ if (existsSync17(pbxprojPath)) pbxprojText = await readFile8(pbxprojPath, "utf8");
7429
7689
  }
7430
7690
  }
7431
7691
  for (const requiredPath of requiredPaths) {
7432
7692
  const relPath = ensureRelativePath3(requiredPath);
7433
- if (!existsSync16(resolveInsideWorkspace(context.workspace, relPath))) {
7693
+ if (!existsSync17(resolveInsideWorkspace(context.workspace, relPath))) {
7434
7694
  problems.push(`Missing ${relPath}.`);
7435
7695
  }
7436
7696
  if (requireProjectReferences && pbxprojText) {
@@ -7478,7 +7738,7 @@ var validateFastlaneConfigTool = {
7478
7738
  let fastfile = "";
7479
7739
  const fastfileRel = ensureRelativePath3(fastfilePath);
7480
7740
  const fastfileAbs = resolveInsideWorkspace(context.workspace, fastfileRel);
7481
- if (!existsSync16(fastfileAbs)) {
7741
+ if (!existsSync17(fastfileAbs)) {
7482
7742
  problems.push(`Missing ${fastfileRel}.`);
7483
7743
  } else {
7484
7744
  fastfile = await readFile8(fastfileAbs, "utf8");
@@ -7516,11 +7776,11 @@ var validateFastlaneConfigTool = {
7516
7776
  }
7517
7777
  for (const file of requiredFiles) {
7518
7778
  const relPath = ensureRelativePath3(file);
7519
- if (!existsSync16(resolveInsideWorkspace(context.workspace, relPath))) problems.push(`Missing ${relPath}.`);
7779
+ if (!existsSync17(resolveInsideWorkspace(context.workspace, relPath))) problems.push(`Missing ${relPath}.`);
7520
7780
  }
7521
7781
  for (const file of forbiddenFiles) {
7522
7782
  const relPath = ensureRelativePath3(file);
7523
- if (existsSync16(resolveInsideWorkspace(context.workspace, relPath))) problems.push(`Forbidden file exists: ${relPath}.`);
7783
+ if (existsSync17(resolveInsideWorkspace(context.workspace, relPath))) problems.push(`Forbidden file exists: ${relPath}.`);
7524
7784
  }
7525
7785
  return {
7526
7786
  ok: problems.length === 0,
@@ -7623,7 +7883,7 @@ async function findLargestAppIconPng(context, viewPath) {
7623
7883
  const viewDir = dirname9(viewPath).replace(/\\/g, "/");
7624
7884
  const appIconDir = `${viewDir}/Assets.xcassets/AppIcon.appiconset`;
7625
7885
  const appIconAbs = resolveInsideWorkspace(context.workspace, appIconDir);
7626
- if (!existsSync16(appIconAbs)) return null;
7886
+ if (!existsSync17(appIconAbs)) return null;
7627
7887
  const pngs = readdirSync6(appIconAbs, { withFileTypes: true }).filter((entry) => entry.isFile() && /\.png$/i.test(entry.name)).map((entry) => `${appIconDir}/${entry.name}`);
7628
7888
  if (pngs.length === 0) return null;
7629
7889
  const score = (path) => {
@@ -7736,7 +7996,7 @@ var createIosSplashTool = {
7736
7996
  ]) ?? await findLargestAppIconPng(context, viewPath);
7737
7997
  if (resolvedSourceIcon) {
7738
7998
  const sourceAbs = isAbsolute(resolvedSourceIcon) ? resolvedSourceIcon : resolveInsideWorkspace(context.workspace, ensureRelativePath3(resolvedSourceIcon));
7739
- if (!existsSync16(sourceAbs)) {
7999
+ if (!existsSync17(sourceAbs)) {
7740
8000
  return { ok: false, summary: "Source splash icon not found.", error: `Missing source icon: ${resolvedSourceIcon}` };
7741
8001
  }
7742
8002
  await mkdir7(dirname9(resolveInsideWorkspace(context.workspace, iconPath)), { recursive: true });
@@ -7880,7 +8140,7 @@ function addDependencyLine(gradle, line) {
7880
8140
  async function maybePatchAndroidGradle(context, rootGradlePath, moduleGradlePath) {
7881
8141
  const files = [];
7882
8142
  const rootAbs = resolveInsideWorkspace(context.workspace, rootGradlePath);
7883
- if (existsSync16(rootAbs)) {
8143
+ if (existsSync17(rootAbs)) {
7884
8144
  const rootGradle = await readFile8(rootAbs, "utf8");
7885
8145
  const nextRootGradle = addLineBeforeClosingPluginsBlock(rootGradle, 'id("com.google.devtools.ksp") version "1.9.24-1.0.20" apply false');
7886
8146
  if (nextRootGradle !== rootGradle) {
@@ -7889,7 +8149,7 @@ async function maybePatchAndroidGradle(context, rootGradlePath, moduleGradlePath
7889
8149
  }
7890
8150
  }
7891
8151
  const moduleAbs = resolveInsideWorkspace(context.workspace, moduleGradlePath);
7892
- if (existsSync16(moduleAbs)) {
8152
+ if (existsSync17(moduleAbs)) {
7893
8153
  let moduleGradle = await readFile8(moduleAbs, "utf8");
7894
8154
  const before = moduleGradle;
7895
8155
  moduleGradle = addLineBeforeClosingPluginsBlock(moduleGradle, 'id("com.google.devtools.ksp")');
@@ -7960,7 +8220,7 @@ var createAndroidFoundationTool = {
7960
8220
  ];
7961
8221
  for (const [path, content] of outputs) {
7962
8222
  const target = resolveInsideWorkspace(context.workspace, path);
7963
- if (preserveExisting && existsSync16(target)) continue;
8223
+ if (preserveExisting && existsSync17(target)) continue;
7964
8224
  await mkdir7(dirname9(target), { recursive: true });
7965
8225
  await writeFile7(target, content, "utf8");
7966
8226
  files.push(path);
@@ -8277,8 +8537,8 @@ var commitPlatformChangesTool = {
8277
8537
  const cleanPath = normalizeRelativePathForGit(path);
8278
8538
  const workspaceCandidate = resolveInsideWorkspace(realWorkspace, cleanPath);
8279
8539
  const repoCandidate = resolveInsideWorkspace(realRepoRoot, cleanPath);
8280
- if (existsSync16(repoCandidate)) return relative8(realRepoRoot, await realpath(repoCandidate)).replace(/\\/g, "/");
8281
- if (existsSync16(workspaceCandidate)) return relative8(realRepoRoot, await realpath(workspaceCandidate)).replace(/\\/g, "/");
8540
+ if (existsSync17(repoCandidate)) return relative8(realRepoRoot, await realpath(repoCandidate)).replace(/\\/g, "/");
8541
+ if (existsSync17(workspaceCandidate)) return relative8(realRepoRoot, await realpath(workspaceCandidate)).replace(/\\/g, "/");
8282
8542
  const workspacePrefix = normalizeRelativePathForGit(relative8(realRepoRoot, realWorkspace));
8283
8543
  if (workspacePrefix && workspacePrefix !== "." && (cleanPath === workspacePrefix || cleanPath.startsWith(`${workspacePrefix}/`))) return cleanPath;
8284
8544
  const workspaceRelative = normalizeRelativePathForGit(relative8(realRepoRoot, workspaceCandidate));
@@ -8400,7 +8660,7 @@ function defaultTools() {
8400
8660
  runCommandTool,
8401
8661
  runShellTool
8402
8662
  ].map((tool) => /^validate_/.test(tool.name) || tool.name === "scan_secrets" ? { ...tool, preferredModel: verificationPreferredModel } : tool);
8403
- return tools.filter((tool) => tool.name !== "search" || existsSync16("/usr/bin/rg") || existsSync16("/opt/homebrew/bin/rg") || existsSync16("/usr/local/bin/rg"));
8663
+ return tools.filter((tool) => tool.name !== "search" || existsSync17("/usr/bin/rg") || existsSync17("/opt/homebrew/bin/rg") || existsSync17("/usr/local/bin/rg"));
8404
8664
  }
8405
8665
 
8406
8666
  // src/tools/registry.ts
@@ -8429,7 +8689,7 @@ var ToolRegistry = class {
8429
8689
  // src/memory/goldenTasks.ts
8430
8690
  import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile9, stat as stat3, writeFile as writeFile8, rename } from "fs/promises";
8431
8691
  import { createHash as createHash2 } from "crypto";
8432
- import { dirname as dirname10, join as join16 } from "path";
8692
+ import { dirname as dirname10, join as join17 } from "path";
8433
8693
  var GOLDEN_TASK_MAX_BYTES = 10 * 1024 * 1024;
8434
8694
  var GOLDEN_TASK_KEEP_LATEST = 200;
8435
8695
  async function rotateGoldenTaskFileIfTooLarge(memoryPath) {
@@ -8468,7 +8728,7 @@ function taskSignature(runContext, manifest) {
8468
8728
  }
8469
8729
  async function recordGoldenTaskMemory(workspace, manifest, runContext) {
8470
8730
  if (!enabled(runContext)) return;
8471
- const memoryPath = join16(workspace, ".tania", "memory", "golden-tasks.jsonl");
8731
+ const memoryPath = join17(workspace, ".tania", "memory", "golden-tasks.jsonl");
8472
8732
  const validationErrors = manifest.validation?.issues.filter((issue2) => issue2.severity === "error") ?? [];
8473
8733
  const record = {
8474
8734
  schemaVersion: 1,
@@ -8492,7 +8752,7 @@ async function recordGoldenTaskMemory(workspace, manifest, runContext) {
8492
8752
  await rotateGoldenTaskFileIfTooLarge(memoryPath);
8493
8753
  }
8494
8754
  async function readGoldenTaskMemory(workspace) {
8495
- const memoryPath = join16(workspace, ".tania", "memory", "golden-tasks.jsonl");
8755
+ const memoryPath = join17(workspace, ".tania", "memory", "golden-tasks.jsonl");
8496
8756
  let raw = "";
8497
8757
  try {
8498
8758
  raw = await readFile9(memoryPath, "utf8");
@@ -8552,9 +8812,9 @@ function validateGoldenTaskSummary(summary) {
8552
8812
  import { appendFile as appendFile3, mkdir as mkdir9, readFile as readFile10 } from "fs/promises";
8553
8813
  import { createHash as createHash3 } from "crypto";
8554
8814
  import { homedir as homedir3 } from "os";
8555
- import { dirname as dirname11, join as join17 } from "path";
8815
+ import { dirname as dirname11, join as join18 } from "path";
8556
8816
  function memoryRoot() {
8557
- return envValue({}, "TANYA_MEMORY_HOME").trim() || join17(homedir3(), ".tania", "memory");
8817
+ return envValue({}, "TANYA_MEMORY_HOME").trim() || join18(homedir3(), ".tania", "memory");
8558
8818
  }
8559
8819
  function taskSignature2(runContext, attempts) {
8560
8820
  const source = JSON.stringify({
@@ -8578,7 +8838,7 @@ async function recordRepairRunMemory(runContext, attempts, manifest) {
8578
8838
  finalIssueIds: finalErrors,
8579
8839
  finalBlockers: manifest.blockers
8580
8840
  };
8581
- const path = join17(memoryRoot(), "repair-runs.jsonl");
8841
+ const path = join18(memoryRoot(), "repair-runs.jsonl");
8582
8842
  await mkdir9(dirname11(path), { recursive: true });
8583
8843
  await appendFile3(path, `${JSON.stringify(record)}
8584
8844
  `, "utf8");
@@ -8586,7 +8846,7 @@ async function recordRepairRunMemory(runContext, attempts, manifest) {
8586
8846
 
8587
8847
  // src/obsidian/vaultAppender.ts
8588
8848
  import { appendFile as appendFile4, mkdir as mkdir10, writeFile as writeFile9 } from "fs/promises";
8589
- import { dirname as dirname12, join as join18 } from "path";
8849
+ import { dirname as dirname12, join as join19 } from "path";
8590
8850
  function dailyNoteName(date = /* @__PURE__ */ new Date()) {
8591
8851
  const year = date.getFullYear();
8592
8852
  const month = String(date.getMonth() + 1).padStart(2, "0");
@@ -8620,7 +8880,7 @@ function buildTaskSection(manifest, runContext) {
8620
8880
  ].join("\n");
8621
8881
  }
8622
8882
  async function appendTaskToVault(vaultPath, manifest, runContext) {
8623
- const notePath = join18(vaultPath, dailyNoteName());
8883
+ const notePath = join19(vaultPath, dailyNoteName());
8624
8884
  await mkdir10(dirname12(notePath), { recursive: true });
8625
8885
  try {
8626
8886
  await writeFile9(notePath, "", { flag: "wx" });
@@ -8630,21 +8890,21 @@ async function appendTaskToVault(vaultPath, manifest, runContext) {
8630
8890
  }
8631
8891
 
8632
8892
  // src/memory/auditLog.ts
8633
- import { existsSync as existsSync17, mkdirSync as mkdirSync3, readFileSync as readFileSync9, statSync as statSync5, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
8893
+ import { existsSync as existsSync18, mkdirSync as mkdirSync3, readFileSync as readFileSync9, statSync as statSync5, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
8634
8894
  import { gzipSync } from "zlib";
8635
- import { basename as basename5, join as join19 } from "path";
8895
+ import { basename as basename5, join as join20 } from "path";
8636
8896
  var DEFAULT_AUDIT_MAX_BYTES = 50 * 1024 * 1024;
8637
8897
  var DEFAULT_AUDIT_MAX_AGE_MS = 90 * 24 * 60 * 60 * 1e3;
8638
8898
  function appendAuditDecision(workspace, entry, options = {}) {
8639
8899
  const path = auditPath(workspace);
8640
- mkdirSync3(join19(workspace, ".tania"), { recursive: true });
8900
+ mkdirSync3(join20(workspace, ".tania"), { recursive: true });
8641
8901
  rotateAuditIfNeeded(workspace, options);
8642
8902
  writeFileSync3(path, `${JSON.stringify(entry)}
8643
8903
  `, { encoding: "utf8", flag: "a" });
8644
8904
  }
8645
8905
  function readAuditDecisions(workspace, filters = {}) {
8646
8906
  const path = auditPath(workspace);
8647
- if (!existsSync17(path)) return [];
8907
+ if (!existsSync18(path)) return [];
8648
8908
  const sinceTs = filters.sinceMs === void 0 ? null : Date.now() - filters.sinceMs;
8649
8909
  const entries = readFileSync9(path, "utf8").split("\n").filter(Boolean).flatMap((line) => {
8650
8910
  try {
@@ -8657,20 +8917,20 @@ function readAuditDecisions(workspace, filters = {}) {
8657
8917
  return entries.slice(Math.max(0, entries.length - limit));
8658
8918
  }
8659
8919
  function auditPath(workspace) {
8660
- return join19(workspace, ".tania", "audit.jsonl");
8920
+ return join20(workspace, ".tania", "audit.jsonl");
8661
8921
  }
8662
8922
  function rotateAuditIfNeeded(workspace, options) {
8663
8923
  const path = auditPath(workspace);
8664
- if (!existsSync17(path)) return;
8924
+ if (!existsSync18(path)) return;
8665
8925
  const stats = statSync5(path);
8666
8926
  const maxBytes = options.maxBytes ?? DEFAULT_AUDIT_MAX_BYTES;
8667
8927
  const maxAgeMs = options.maxAgeMs ?? DEFAULT_AUDIT_MAX_AGE_MS;
8668
8928
  const now = options.now ?? /* @__PURE__ */ new Date();
8669
8929
  if (stats.size < maxBytes && now.getTime() - stats.mtimeMs < maxAgeMs) return;
8670
- const archiveDir = join19(workspace, ".tania", "audit", "archive");
8930
+ const archiveDir = join20(workspace, ".tania", "audit", "archive");
8671
8931
  mkdirSync3(archiveDir, { recursive: true });
8672
8932
  const stamp = now.toISOString().replace(/[:.]/g, "-");
8673
- const archivePath2 = join19(archiveDir, `audit-${stamp}-${basename5(path)}.gz`);
8933
+ const archivePath2 = join20(archiveDir, `audit-${stamp}-${basename5(path)}.gz`);
8674
8934
  writeFileSync3(archivePath2, gzipSync(readFileSync9(path)));
8675
8935
  unlinkSync(path);
8676
8936
  }
@@ -8740,9 +9000,9 @@ var FileReadDedupCache = class {
8740
9000
  };
8741
9001
 
8742
9002
  // src/memory/reasoningArchive.ts
8743
- import { existsSync as existsSync18, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync, statSync as statSync6, writeFileSync as writeFileSync4 } from "fs";
9003
+ import { existsSync as existsSync19, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync, statSync as statSync6, writeFileSync as writeFileSync4 } from "fs";
8744
9004
  import { appendFile as appendFile5 } from "fs/promises";
8745
- import { join as join20 } from "path";
9005
+ import { join as join21 } from "path";
8746
9006
  async function appendReasoningChunk(params) {
8747
9007
  if (!params.content) return;
8748
9008
  const dir = reasoningRunDir(params.workspace, params.runId);
@@ -8761,7 +9021,7 @@ async function appendReasoningChunk(params) {
8761
9021
  }
8762
9022
  function readReasoningArchive(workspace, runId) {
8763
9023
  const file = reasoningArchivePath(workspace, runId);
8764
- if (!existsSync18(file)) return [];
9024
+ if (!existsSync19(file)) return [];
8765
9025
  return readFileSync10(file, "utf8").split(/\n+/).filter(Boolean).flatMap((line) => {
8766
9026
  try {
8767
9027
  const parsed = JSON.parse(line);
@@ -8783,7 +9043,7 @@ function readReasoningArchive(workspace, runId) {
8783
9043
  }
8784
9044
  function evictReasoningFromArchive(workspace, runId, thresholdBytes) {
8785
9045
  const file = reasoningArchivePath(workspace, runId);
8786
- if (!existsSync18(file)) return 0;
9046
+ if (!existsSync19(file)) return 0;
8787
9047
  const before = statSync6(file).size;
8788
9048
  if (before <= thresholdBytes) return 0;
8789
9049
  const entries = readReasoningArchive(workspace, runId);
@@ -8803,40 +9063,40 @@ function evictReasoningFromArchive(workspace, runId, thresholdBytes) {
8803
9063
  return Math.max(0, before - statSync6(file).size);
8804
9064
  }
8805
9065
  function reasoningArchivePath(workspace, runId) {
8806
- return join20(reasoningRunDir(workspace, runId), "reasoning.jsonl");
9066
+ return join21(reasoningRunDir(workspace, runId), "reasoning.jsonl");
8807
9067
  }
8808
9068
  function reasoningRunDir(workspace, runId) {
8809
- return join20(workspace, ".tania", "runs", runId);
9069
+ return join21(workspace, ".tania", "runs", runId);
8810
9070
  }
8811
9071
 
8812
9072
  // src/mcp/client.ts
8813
- import { createWriteStream, existsSync as existsSync20, mkdirSync as mkdirSync5, renameSync as renameSync2, statSync as statSync7, unlinkSync as unlinkSync2 } from "fs";
8814
- import { join as join22 } from "path";
9073
+ import { createWriteStream, existsSync as existsSync21, mkdirSync as mkdirSync5, renameSync as renameSync2, statSync as statSync7, unlinkSync as unlinkSync2 } from "fs";
9074
+ import { join as join23 } from "path";
8815
9075
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8816
9076
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
8817
9077
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
8818
9078
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
8819
9079
 
8820
9080
  // src/mcp/config.ts
8821
- import { existsSync as existsSync19, readFileSync as readFileSync11 } from "fs";
9081
+ import { existsSync as existsSync20, readFileSync as readFileSync11 } from "fs";
8822
9082
  import { homedir as homedir4 } from "os";
8823
- import { join as join21 } from "path";
9083
+ import { join as join22 } from "path";
8824
9084
  var EMPTY_CONFIG = { version: 1, servers: [] };
8825
9085
  var TRANSPORTS = /* @__PURE__ */ new Set(["stdio", "sse", "http"]);
8826
9086
  function loadMcpConfig(options) {
8827
9087
  const home = options.home ?? homedir4();
8828
- const userCurrent = join21(home, ".tanya", "mcp.json");
8829
- const userLegacy = join21(home, ".tania", "mcp.json");
8830
- const userFile = existsSync19(userCurrent) ? userCurrent : existsSync19(userLegacy) ? userLegacy : null;
9088
+ const userCurrent = join22(home, ".tanya", "mcp.json");
9089
+ const userLegacy = join22(home, ".tania", "mcp.json");
9090
+ const userFile = existsSync20(userCurrent) ? userCurrent : existsSync20(userLegacy) ? userLegacy : null;
8831
9091
  const candidates = [
8832
9092
  ...userFile ? [userFile] : [],
8833
- join21(options.cwd, ".tania", "mcp.json")
9093
+ join22(options.cwd, ".tania", "mcp.json")
8834
9094
  ];
8835
9095
  let config = cloneConfig(EMPTY_CONFIG);
8836
9096
  const sources = [];
8837
9097
  const issues = [];
8838
9098
  for (const file of candidates) {
8839
- if (!existsSync19(file)) continue;
9099
+ if (!existsSync20(file)) continue;
8840
9100
  const parsed = parseMcpConfig(readFileSync11(file, "utf8"), file);
8841
9101
  if (!parsed.ok) {
8842
9102
  issues.push(...parsed.issues);
@@ -9077,7 +9337,7 @@ var McpClientManager = class {
9077
9337
  createTransport(config) {
9078
9338
  if (config.transport === "stdio") {
9079
9339
  const logPath = mcpLogPath(this.workspace, config.name);
9080
- mkdirSync5(join22(this.workspace, ".tania", "mcp", "logs"), { recursive: true });
9340
+ mkdirSync5(join23(this.workspace, ".tania", "mcp", "logs"), { recursive: true });
9081
9341
  rotateMcpLogIfNeeded(logPath);
9082
9342
  const transport = new StdioClientTransport({
9083
9343
  command: config.command ?? "",
@@ -9225,15 +9485,15 @@ function mcpCallTimeoutMs() {
9225
9485
  return Math.max(1e3, numberEnvValue(process.env, "TANYA_MCP_CALL_TIMEOUT_MS", 3e4));
9226
9486
  }
9227
9487
  function mcpLogPath(workspace, serverName) {
9228
- return join22(workspace, ".tania", "mcp", "logs", `${serverName}.log`);
9488
+ return join23(workspace, ".tania", "mcp", "logs", `${serverName}.log`);
9229
9489
  }
9230
9490
  function rotateMcpLogIfNeeded(path) {
9231
- if (!existsSync20(path)) return;
9491
+ if (!existsSync21(path)) return;
9232
9492
  try {
9233
9493
  if (statSync7(path).size < 10 * 1024 * 1024) return;
9234
9494
  const rotated = `${path}.1`;
9235
9495
  try {
9236
- if (existsSync20(rotated)) unlinkSync2(rotated);
9496
+ if (existsSync21(rotated)) unlinkSync2(rotated);
9237
9497
  } catch {
9238
9498
  }
9239
9499
  renameSync2(path, rotated);
@@ -9248,7 +9508,7 @@ function sleep2(ms) {
9248
9508
  }
9249
9509
 
9250
9510
  // src/agent/verifier/index.ts
9251
- import { existsSync as existsSync22, readFileSync as readFileSync12 } from "fs";
9511
+ import { existsSync as existsSync23, readFileSync as readFileSync12 } from "fs";
9252
9512
 
9253
9513
  // src/agent/verifier/shell.ts
9254
9514
  import { execFile as execFile2 } from "child_process";
@@ -9285,8 +9545,8 @@ var realShell = async (cwd, command, args, options) => {
9285
9545
  };
9286
9546
 
9287
9547
  // src/agent/verifier/verifiers/goBackend.ts
9288
- import { existsSync as existsSync21, readdirSync as readdirSync7, statSync as statSync8 } from "fs";
9289
- import { dirname as dirname13, join as join23, relative as relative9, resolve as resolve8 } from "path";
9548
+ import { existsSync as existsSync22, readdirSync as readdirSync7, statSync as statSync8 } from "fs";
9549
+ import { dirname as dirname13, join as join24, relative as relative9, resolve as resolve8 } from "path";
9290
9550
  import { load as loadYaml } from "js-yaml";
9291
9551
 
9292
9552
  // src/agent/verifier/types.ts
@@ -9331,9 +9591,9 @@ function listDir(path) {
9331
9591
  }
9332
9592
  }
9333
9593
  function dirContainsGoFile(path) {
9334
- if (!existsSync21(path)) return false;
9594
+ if (!existsSync22(path)) return false;
9335
9595
  for (const entry of listDir(path)) {
9336
- const full = join23(path, entry);
9596
+ const full = join24(path, entry);
9337
9597
  try {
9338
9598
  const st = statSync8(full);
9339
9599
  if (st.isFile() && entry.endsWith(".go")) return true;
@@ -9354,7 +9614,7 @@ function scanGoFiles(workspace) {
9354
9614
  visited += 1;
9355
9615
  for (const entry of listDir(dir)) {
9356
9616
  if (entry === "node_modules" || entry === ".git" || entry === "dist" || entry === "build") continue;
9357
- const full = join23(dir, entry);
9617
+ const full = join24(dir, entry);
9358
9618
  try {
9359
9619
  const st = statSync8(full);
9360
9620
  if (st.isFile() && entry.endsWith(".go")) {
@@ -9429,7 +9689,7 @@ function sqlFilesUnder(path) {
9429
9689
  if (!dir) break;
9430
9690
  visited += 1;
9431
9691
  for (const entry of listDir(dir)) {
9432
- const full = join23(dir, entry);
9692
+ const full = join24(dir, entry);
9433
9693
  try {
9434
9694
  const st = statSync8(full);
9435
9695
  if (st.isFile() && entry.endsWith(".sql")) out.push(full);
@@ -9467,7 +9727,7 @@ function referencedSqlQueryFiles(workspace, queryPath) {
9467
9727
  const cleanQueryPath = queryPath.trim();
9468
9728
  if (!cleanQueryPath) return [];
9469
9729
  if (!/[*?]/.test(cleanQueryPath)) {
9470
- return sqlFilesUnder(join23(workspace, cleanQueryPath));
9730
+ return sqlFilesUnder(join24(workspace, cleanQueryPath));
9471
9731
  }
9472
9732
  const firstGlob = cleanQueryPath.search(/[*?]/);
9473
9733
  const prefix = cleanQueryPath.slice(0, firstGlob);
@@ -9487,13 +9747,13 @@ var goBackendVerifier = {
9487
9747
  id: "go-backend",
9488
9748
  platform: "go-backend",
9489
9749
  appliesTo(ctx) {
9490
- if (ctx.fileExists(join23(ctx.workspace, "go.mod"))) return true;
9750
+ if (ctx.fileExists(join24(ctx.workspace, "go.mod"))) return true;
9491
9751
  const text2 = combinedTaskText(ctx.runContext, ctx.prompt);
9492
9752
  return /\bgo\.mod\b/.test(text2) || /\bgolang\b/.test(text2);
9493
9753
  },
9494
9754
  async run(ctx) {
9495
9755
  const checks = [];
9496
- const goModPath = join23(ctx.workspace, "go.mod");
9756
+ const goModPath = join24(ctx.workspace, "go.mod");
9497
9757
  const goModText = ctx.readText(goModPath);
9498
9758
  const text2 = combinedTaskText(ctx.runContext, ctx.prompt);
9499
9759
  checks.push(makeCheck({
@@ -9518,7 +9778,7 @@ var goBackendVerifier = {
9518
9778
  requireDep(/github\.com\/go-chi\/chi\/v5/, "chi/v5", mentionsAny(text2, CHI_PATTERNS));
9519
9779
  requireDep(/github\.com\/jackc\/pgx\/v5/, "pgx/v5", mentionsAny(text2, PGX_PATTERNS));
9520
9780
  if (mentionsAny(text2, REST_SERVER_PATTERNS)) {
9521
- const mainPath = join23(ctx.workspace, "cmd", "server", "main.go");
9781
+ const mainPath = join24(ctx.workspace, "cmd", "server", "main.go");
9522
9782
  const present = ctx.fileExists(mainPath);
9523
9783
  checks.push(makeCheck({
9524
9784
  id: "cmd-server-main",
@@ -9528,7 +9788,7 @@ var goBackendVerifier = {
9528
9788
  error: present ? void 0 : "expected entrypoint at cmd/server/main.go"
9529
9789
  }));
9530
9790
  }
9531
- const internalPath = join23(ctx.workspace, "internal");
9791
+ const internalPath = join24(ctx.workspace, "internal");
9532
9792
  if (mentionsAny(text2, INTERNAL_PKG_PATTERNS)) {
9533
9793
  const hasInternalGo = dirContainsGoFile(internalPath);
9534
9794
  checks.push(makeCheck({
@@ -9539,7 +9799,7 @@ var goBackendVerifier = {
9539
9799
  error: hasInternalGo ? void 0 : "expected at least one Go package under internal/"
9540
9800
  }));
9541
9801
  }
9542
- const sqlcPath = join23(ctx.workspace, "sqlc.yaml");
9802
+ const sqlcPath = join24(ctx.workspace, "sqlc.yaml");
9543
9803
  const sqlcText = ctx.readText(sqlcPath);
9544
9804
  if (sqlcText && mentionsAny(text2, SQLC_PATTERNS)) {
9545
9805
  for (const entry of parseSqlcConfig(sqlcText)) {
@@ -9556,7 +9816,7 @@ var goBackendVerifier = {
9556
9816
  }));
9557
9817
  continue;
9558
9818
  }
9559
- const target = join23(ctx.workspace, dir);
9819
+ const target = join24(ctx.workspace, dir);
9560
9820
  const present = ctx.fileExists(target) && dirContainsGoFile(target);
9561
9821
  checks.push(makeCheck({
9562
9822
  id: `sqlc-out-${dir}`,
@@ -9630,10 +9890,10 @@ var goBackendVerifier = {
9630
9890
  };
9631
9891
 
9632
9892
  // src/agent/verifier/verifiers/nodeBackend.ts
9633
- import { join as join24 } from "path";
9893
+ import { join as join25 } from "path";
9634
9894
  var BACKEND_DEP_HINTS = ["express", "fastify", "@nestjs/core", "hono", "koa", "@fastify/", "@hono/"];
9635
9895
  function readPackageJson(ctx) {
9636
- const text2 = ctx.readText(join24(ctx.workspace, "package.json"));
9896
+ const text2 = ctx.readText(join25(ctx.workspace, "package.json"));
9637
9897
  if (!text2) return null;
9638
9898
  try {
9639
9899
  return JSON.parse(text2);
@@ -9694,10 +9954,10 @@ var nodeBackendVerifier = {
9694
9954
  };
9695
9955
 
9696
9956
  // src/agent/verifier/verifiers/frontend.ts
9697
- import { join as join25 } from "path";
9957
+ import { join as join26 } from "path";
9698
9958
  var FRONTEND_DEP_HINTS = ["next", "react", "vite", "@vitejs/", "@tanstack/router", "remix", "@remix-run/"];
9699
9959
  function readPackageJson2(ctx) {
9700
- const text2 = ctx.readText(join25(ctx.workspace, "package.json"));
9960
+ const text2 = ctx.readText(join26(ctx.workspace, "package.json"));
9701
9961
  if (!text2) return null;
9702
9962
  try {
9703
9963
  return JSON.parse(text2);
@@ -9758,16 +10018,16 @@ var frontendVerifier = {
9758
10018
  };
9759
10019
 
9760
10020
  // src/agent/verifier/verifiers/mobile.ts
9761
- import { join as join26 } from "path";
10021
+ import { join as join27 } from "path";
9762
10022
  var iosVerifier = {
9763
10023
  id: "ios",
9764
10024
  platform: "ios",
9765
10025
  appliesTo(ctx) {
9766
- return ctx.fileExists(join26(ctx.workspace, "Package.swift"));
10026
+ return ctx.fileExists(join27(ctx.workspace, "Package.swift"));
9767
10027
  },
9768
10028
  async run(ctx) {
9769
10029
  const checks = [];
9770
- const text2 = ctx.readText(join26(ctx.workspace, "Package.swift"));
10030
+ const text2 = ctx.readText(join27(ctx.workspace, "Package.swift"));
9771
10031
  checks.push(makeCheck({
9772
10032
  id: "package-swift-present",
9773
10033
  description: "Package.swift exists",
@@ -9782,12 +10042,12 @@ var androidVerifier = {
9782
10042
  id: "android",
9783
10043
  platform: "android",
9784
10044
  appliesTo(ctx) {
9785
- return ctx.fileExists(join26(ctx.workspace, "build.gradle.kts")) || ctx.fileExists(join26(ctx.workspace, "settings.gradle.kts"));
10045
+ return ctx.fileExists(join27(ctx.workspace, "build.gradle.kts")) || ctx.fileExists(join27(ctx.workspace, "settings.gradle.kts"));
9786
10046
  },
9787
10047
  async run(ctx) {
9788
10048
  const checks = [];
9789
- const buildGradle = ctx.readText(join26(ctx.workspace, "build.gradle.kts"));
9790
- const settingsGradle = ctx.readText(join26(ctx.workspace, "settings.gradle.kts"));
10049
+ const buildGradle = ctx.readText(join27(ctx.workspace, "build.gradle.kts"));
10050
+ const settingsGradle = ctx.readText(join27(ctx.workspace, "settings.gradle.kts"));
9791
10051
  const passed = buildGradle !== null || settingsGradle !== null;
9792
10052
  checks.push(makeCheck({
9793
10053
  id: "gradle-config-present",
@@ -9822,7 +10082,7 @@ function buildContext(options) {
9822
10082
  runContext: options.runContext,
9823
10083
  prompt: options.prompt ?? "",
9824
10084
  shell: options.shell ?? defaultShell(),
9825
- fileExists: (path) => existsSync22(path),
10085
+ fileExists: (path) => existsSync23(path),
9826
10086
  readText: (path) => {
9827
10087
  try {
9828
10088
  return readFileSync12(path, "utf8");
@@ -9876,8 +10136,8 @@ async function verifyFinalState(options) {
9876
10136
 
9877
10137
  // src/agent/forbiddenPatterns.ts
9878
10138
  import { readFile as readFile11, writeFile as writeFile10, mkdir as mkdir11 } from "fs/promises";
9879
- import { existsSync as existsSync23 } from "fs";
9880
- import { join as join27 } from "path";
10139
+ import { existsSync as existsSync24 } from "fs";
10140
+ import { join as join28 } from "path";
9881
10141
  var TEST_DIR_EXCLUSIONS = /(?:^|\/)(?:test|tests|__tests__|spec|specs|androidTest)(?:\/|$)/;
9882
10142
  var DEFAULT_FORBIDDEN_PATTERNS = [
9883
10143
  {
@@ -10095,8 +10355,8 @@ var DEFAULT_FORBIDDEN_PATTERNS = [
10095
10355
  }
10096
10356
  ];
10097
10357
  async function loadProjectForbiddenPatterns(workspace) {
10098
- const candidate = join27(workspace, ".tania", "forbidden-patterns.json");
10099
- if (!existsSync23(candidate)) return [];
10358
+ const candidate = join28(workspace, ".tania", "forbidden-patterns.json");
10359
+ if (!existsSync24(candidate)) return [];
10100
10360
  try {
10101
10361
  const raw = await readFile11(candidate, "utf8");
10102
10362
  const parsed = JSON.parse(raw);
@@ -10122,7 +10382,7 @@ async function scanForbiddenPatterns(workspace, changedFiles, patterns2) {
10122
10382
  if (matchingPatterns.length === 0) continue;
10123
10383
  let content;
10124
10384
  try {
10125
- content = await readFile11(join27(workspace, file), "utf8");
10385
+ content = await readFile11(join28(workspace, file), "utf8");
10126
10386
  } catch {
10127
10387
  continue;
10128
10388
  }
@@ -10146,10 +10406,10 @@ async function scanForbiddenPatterns(workspace, changedFiles, patterns2) {
10146
10406
  }
10147
10407
  async function recordFireMetrics(workspace, fireCounts) {
10148
10408
  try {
10149
- const metricsDir = join27(workspace, ".tania", "memory");
10150
- const metricsPath = join27(metricsDir, "forbidden-patterns-metrics.json");
10409
+ const metricsDir = join28(workspace, ".tania", "memory");
10410
+ const metricsPath = join28(metricsDir, "forbidden-patterns-metrics.json");
10151
10411
  let existing = { totals: {}, lastFiredAt: {} };
10152
- if (existsSync23(metricsPath)) {
10412
+ if (existsSync24(metricsPath)) {
10153
10413
  try {
10154
10414
  const raw = await readFile11(metricsPath, "utf8");
10155
10415
  const parsed = JSON.parse(raw);
@@ -10177,11 +10437,11 @@ async function recordFireMetrics(workspace, fireCounts) {
10177
10437
 
10178
10438
  // src/agent/validators/core.ts
10179
10439
  import { readdir as readdir4, readFile as readFile12 } from "fs/promises";
10180
- import { join as join29 } from "path";
10440
+ import { join as join30 } from "path";
10181
10441
 
10182
10442
  // src/agent/validators/rules/load.ts
10183
10443
  import { readdirSync as readdirSync8, readFileSync as readFileSync13, statSync as statSync9 } from "fs";
10184
- import { join as join28 } from "path";
10444
+ import { join as join29 } from "path";
10185
10445
 
10186
10446
  // src/agent/validators/rules/index.ts
10187
10447
  var builtInValidatorRuleFiles = [];
@@ -10305,7 +10565,7 @@ function jsonFiles(path) {
10305
10565
  if (stat8.isFile()) return path.endsWith(".json") ? [path] : [];
10306
10566
  if (!stat8.isDirectory()) return [];
10307
10567
  try {
10308
- return readdirSync8(path, { withFileTypes: true }).flatMap((entry) => jsonFiles(join28(path, entry.name))).sort((a, b) => a.localeCompare(b));
10568
+ return readdirSync8(path, { withFileTypes: true }).flatMap((entry) => jsonFiles(join29(path, entry.name))).sort((a, b) => a.localeCompare(b));
10309
10569
  } catch {
10310
10570
  return [];
10311
10571
  }
@@ -10462,7 +10722,7 @@ function featureCoveredByText(feature, implementationText) {
10462
10722
  }
10463
10723
  async function readWorkspaceFile(workspace, filePath) {
10464
10724
  try {
10465
- return await readFile12(join29(workspace, filePath), "utf8");
10725
+ return await readFile12(join30(workspace, filePath), "utf8");
10466
10726
  } catch {
10467
10727
  return null;
10468
10728
  }
@@ -10479,7 +10739,7 @@ async function findWorkspaceFiles(workspace, predicate, options = {}) {
10479
10739
  if (results.length >= limit) return;
10480
10740
  let entries;
10481
10741
  try {
10482
- entries = await readdir4(join29(workspace, relativeDir), { withFileTypes: true });
10742
+ entries = await readdir4(join30(workspace, relativeDir), { withFileTypes: true });
10483
10743
  } catch {
10484
10744
  return;
10485
10745
  }
@@ -12259,7 +12519,7 @@ function commitStillRequired(manifest, beforeGitSnapshot, runContext) {
12259
12519
 
12260
12520
  // src/agent/report.ts
12261
12521
  import { execFile as execFile4 } from "child_process";
12262
- import { existsSync as existsSync24 } from "fs";
12522
+ import { existsSync as existsSync25 } from "fs";
12263
12523
  import { readdir as readdir6, rm, stat as stat6 } from "fs/promises";
12264
12524
  import { resolve as resolve10 } from "path";
12265
12525
  import { promisify as promisify4 } from "util";
@@ -12293,7 +12553,7 @@ async function cleanupGeneratedNoise(workspace) {
12293
12553
  }
12294
12554
  for (const relPath of generatedFastlanePaths) {
12295
12555
  const absPath = resolve10(workspace, relPath);
12296
- if (!existsSync24(absPath)) continue;
12556
+ if (!existsSync25(absPath)) continue;
12297
12557
  if (await pathIsGitTracked(workspace, relPath)) continue;
12298
12558
  try {
12299
12559
  await rm(absPath, { recursive: true, force: true });
@@ -13060,9 +13320,9 @@ function levenshtein(a, b) {
13060
13320
  }
13061
13321
 
13062
13322
  // src/agent/runner.ts
13063
- import { existsSync as existsSync25, mkdirSync as mkdirSync6, readdirSync as readdirSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
13323
+ import { existsSync as existsSync26, mkdirSync as mkdirSync6, readdirSync as readdirSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
13064
13324
  import { cp as cp2, mkdir as mkdir12, rm as rm2, stat as stat7 } from "fs/promises";
13065
- import { dirname as dirname14, isAbsolute as isAbsolute2, join as join30, relative as relative11, resolve as resolve11 } from "path";
13325
+ import { dirname as dirname14, isAbsolute as isAbsolute2, join as join31, relative as relative11, resolve as resolve11 } from "path";
13066
13326
  var CONTEXT_TOKEN_LIMIT = 48e3;
13067
13327
  var permissionModes = /* @__PURE__ */ new Set(["default", "ask", "bypass", "plan"]);
13068
13328
  var sessionSpendTokens = 0;
@@ -13103,7 +13363,7 @@ function materializedContextCleanupEnabled(manifest, runContext) {
13103
13363
  async function cleanupMaterializedContext(workspace, manifest, runContext) {
13104
13364
  if (!materializedContextCleanupEnabled(manifest, runContext)) return;
13105
13365
  const taniaDir = resolve11(workspace, ".tania");
13106
- if (!existsSync25(taniaDir)) return;
13366
+ if (!existsSync26(taniaDir)) return;
13107
13367
  if (await hasTrackedPathUnder(workspace, ".tania")) return;
13108
13368
  try {
13109
13369
  await rm2(taniaDir, { recursive: true, force: true });
@@ -13139,7 +13399,7 @@ function rotateRunSummaryFiles(runsDir) {
13139
13399
  if (excess <= 0) return;
13140
13400
  for (const stale of entries.slice(0, excess)) {
13141
13401
  try {
13142
- unlinkSync3(join30(runsDir, stale));
13402
+ unlinkSync3(join31(runsDir, stale));
13143
13403
  } catch {
13144
13404
  }
13145
13405
  }
@@ -13148,11 +13408,11 @@ function rotateRunSummaryFiles(runsDir) {
13148
13408
  }
13149
13409
  function logRunSummarySilently(params) {
13150
13410
  try {
13151
- const runsDir = join30(params.workspace, ".tania", "runs");
13152
- const outputDir = params.parentRunId ? join30(runsDir, params.parentRunId) : runsDir;
13411
+ const runsDir = join31(params.workspace, ".tania", "runs");
13412
+ const outputDir = params.parentRunId ? join31(runsDir, params.parentRunId) : runsDir;
13153
13413
  mkdirSync6(outputDir, { recursive: true });
13154
13414
  const ts = (/* @__PURE__ */ new Date()).toISOString();
13155
- const logPath = join30(outputDir, params.parentRunId ? `${params.runId}.json` : `${params.runId}.json`);
13415
+ const logPath = join31(outputDir, params.parentRunId ? `${params.runId}.json` : `${params.runId}.json`);
13156
13416
  writeFileSync5(
13157
13417
  logPath,
13158
13418
  JSON.stringify(
@@ -13297,7 +13557,7 @@ function pruneStaleRepairReminders(messages) {
13297
13557
  return messages.filter((msg, idx) => idx === lastIndex || !isRepairReminder(msg));
13298
13558
  }
13299
13559
  function isTypeScriptProject(workspace) {
13300
- return existsSync25(join30(workspace, "tsconfig.json"));
13560
+ return existsSync26(join31(workspace, "tsconfig.json"));
13301
13561
  }
13302
13562
  function repairAttemptBudget(options) {
13303
13563
  const configured = typeof options.runContext?.metadata?.repairAttempts === "number" ? options.runContext.metadata.repairAttempts : typeof options.runContext?.metadata?.repairAttempts === "string" ? Number(options.runContext.metadata.repairAttempts) : options.repairAttempts;
@@ -13480,6 +13740,32 @@ function auditModelRouted(workspace, context, event) {
13480
13740
  reason: event.reason
13481
13741
  });
13482
13742
  }
13743
+ function forcedRouteFromRunContext(runContext) {
13744
+ const metadata = runContext?.metadata;
13745
+ if (!metadata) return null;
13746
+ const forcedModel = stringMetadata(metadata, "forced_model") ?? stringMetadata(metadata, "forcedModel");
13747
+ const forcedProvider = stringMetadata(metadata, "forced_cli") ?? stringMetadata(metadata, "forcedCli") ?? stringMetadata(metadata, "forced_provider") ?? stringMetadata(metadata, "forcedProvider");
13748
+ if (!forcedModel && !forcedProvider) return null;
13749
+ if (forcedModel?.includes("/") && !forcedProvider) {
13750
+ const [provider2, model] = forcedModel.split("/", 2);
13751
+ if (provider2?.trim() && model?.trim()) return { provider: provider2.trim(), model: model.trim() };
13752
+ }
13753
+ const provider = forcedProvider ?? inferForcedProvider(forcedModel ?? "");
13754
+ if (!provider || !forcedModel) return null;
13755
+ return { provider, model: forcedModel };
13756
+ }
13757
+ function stringMetadata(metadata, key) {
13758
+ const value = metadata[key];
13759
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
13760
+ }
13761
+ function inferForcedProvider(model) {
13762
+ if (/^deepseek-/i.test(model)) return "deepseek";
13763
+ if (/^(?:gpt-|o\d|o\d-|chatgpt)/i.test(model)) return "openai";
13764
+ if (/^claude-/i.test(model)) return "claude";
13765
+ if (/^gemini-/i.test(model)) return "gemini";
13766
+ if (/^qwen/i.test(model)) return "qwen";
13767
+ return null;
13768
+ }
13483
13769
  function auditEscalation(workspace, context, event) {
13484
13770
  appendAuditDecision(workspace, {
13485
13771
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -13742,10 +14028,12 @@ async function runAgent(options) {
13742
14028
  let lastProviderKey = providerKey(activeProvider);
13743
14029
  if (options.routing?.enabled) {
13744
14030
  try {
14031
+ const forcedRoute = forcedRouteFromRunContext(options.runContext);
13745
14032
  const initialRoute = resolveRouteWithContextGuard({
13746
14033
  stepType: "planning",
13747
14034
  table: options.routing.table,
13748
- messages: [...options.history ?? [], { role: "user", content: options.prompt }]
14035
+ messages: [...options.history ?? [], { role: "user", content: options.prompt }],
14036
+ ...forcedRoute ? { forcedRoute } : {}
13749
14037
  });
13750
14038
  activeProvider = options.routing.providerFactory(initialRoute);
13751
14039
  lastProviderKey = providerKey(activeProvider);
@@ -13961,7 +14249,7 @@ async function runAgent(options) {
13961
14249
  const outputRootValue = options.runContext?.metadata?.artifactOutputRoot;
13962
14250
  if (typeof outputRootValue !== "string" || !outputRootValue.trim()) return [];
13963
14251
  const localOutputRoot = resolve11(workspace, ".tania", "artifact-output");
13964
- if (!existsSync25(localOutputRoot)) return [];
14252
+ if (!existsSync26(localOutputRoot)) return [];
13965
14253
  const localFiles = await listFilesRecursive(localOutputRoot);
13966
14254
  if (localFiles.length === 0) return [];
13967
14255
  const outputRoot = resolve11(outputRootValue);
@@ -13988,6 +14276,7 @@ async function runAgent(options) {
13988
14276
  promptTokens: totalPromptTokens,
13989
14277
  completionTokens: totalCompletionTokens,
13990
14278
  reasoningTokens: totalReasoningTokens,
14279
+ costUsd: runSpendUsd,
13991
14280
  systemPromptTokens,
13992
14281
  repoMapTokens,
13993
14282
  toolResultTokens: totalToolResultTokens
@@ -14065,11 +14354,31 @@ async function runAgent(options) {
14065
14354
  auditModelRouted(workspace, permissionContext, event2);
14066
14355
  return { provider: provider2, stepType: forced.stepType };
14067
14356
  }
14068
- const stepType = classifyStep({ messages, turnIndex: turn, pendingToolCalls: pendingToolCallsForRouting() });
14069
- const route = preferredRouteForStep(stepType) ?? resolveRouteWithContextGuard({
14357
+ const classifierState = {
14358
+ messages,
14359
+ turnIndex: turn,
14360
+ pendingToolCalls: pendingToolCallsForRouting(),
14361
+ cwd: workspace,
14362
+ ...options.prompt ? { prompt: options.prompt } : {},
14363
+ ...options.runContext ? { runContext: options.runContext } : {}
14364
+ };
14365
+ const stepType = classifyStep(classifierState);
14366
+ const forcedRoute = forcedRouteFromRunContext(options.runContext);
14367
+ const route = forcedRoute ? resolveRouteWithContextGuard({
14070
14368
  stepType,
14071
14369
  table: options.routing.table,
14072
- messages
14370
+ messages,
14371
+ prompt: options.prompt,
14372
+ cwd: workspace,
14373
+ ...options.runContext ? { runContext: options.runContext } : {},
14374
+ forcedRoute
14375
+ }) : preferredRouteForStep(stepType) ?? resolveRouteWithContextGuard({
14376
+ stepType,
14377
+ table: options.routing.table,
14378
+ messages,
14379
+ prompt: options.prompt,
14380
+ cwd: workspace,
14381
+ ...options.runContext ? { runContext: options.runContext } : {}
14073
14382
  });
14074
14383
  const provider = options.routing.providerFactory(route);
14075
14384
  const key = providerKey(provider);
@@ -14085,6 +14394,24 @@ async function runAgent(options) {
14085
14394
  cacheImpact
14086
14395
  };
14087
14396
  await options.sink(event);
14397
+ if (route.cascade && route.cascade.selectedIndex > 0) {
14398
+ await options.sink({
14399
+ type: "provider.raw",
14400
+ provider: provider.id,
14401
+ model: provider.model,
14402
+ event: {
14403
+ type: "model_routed",
14404
+ reason: "cascade-fit",
14405
+ stepType,
14406
+ provider: provider.id,
14407
+ model: provider.model,
14408
+ attempted_routes: route.cascade.attemptedRoutes,
14409
+ estimated_tokens: route.cascade.estimatedTokens,
14410
+ safety_factor: route.cascade.safetyFactor,
14411
+ selected_route: route.cascade.selectedRoute
14412
+ }
14413
+ });
14414
+ }
14088
14415
  auditModelRouted(workspace, permissionContext, event);
14089
14416
  return { provider, stepType, route };
14090
14417
  }
@@ -14947,7 +15274,7 @@ import { stdout } from "process";
14947
15274
  // src/sessions/storage.ts
14948
15275
  import { randomBytes } from "crypto";
14949
15276
  import {
14950
- existsSync as existsSync26,
15277
+ existsSync as existsSync27,
14951
15278
  mkdirSync as mkdirSync7,
14952
15279
  readFileSync as readFileSync14,
14953
15280
  readdirSync as readdirSync10,
@@ -14957,7 +15284,7 @@ import {
14957
15284
  appendFileSync
14958
15285
  } from "fs";
14959
15286
  import { homedir as homedir5 } from "os";
14960
- import { basename as basename6, dirname as dirname15, join as join31, parse, relative as relative12, resolve as resolve12 } from "path";
15287
+ import { basename as basename6, dirname as dirname15, join as join32, parse, relative as relative12, resolve as resolve12 } from "path";
14961
15288
  var activeSessionPaths = /* @__PURE__ */ new Map();
14962
15289
  function defaultCwd(cwd) {
14963
15290
  return resolve12(cwd ?? process.cwd());
@@ -14966,13 +15293,13 @@ function defaultHome(homeDir) {
14966
15293
  return resolve12(homeDir ?? homedir5());
14967
15294
  }
14968
15295
  function globalSessionsDir(homeDir) {
14969
- return join31(defaultHome(homeDir), ".tania", "sessions", "global");
15296
+ return join32(defaultHome(homeDir), ".tania", "sessions", "global");
14970
15297
  }
14971
15298
  function findProjectTaniaDir(cwd) {
14972
15299
  let current = resolve12(cwd);
14973
15300
  while (true) {
14974
- const candidate = join31(current, ".tania");
14975
- if (existsSync26(candidate) && statSync10(candidate).isDirectory()) return candidate;
15301
+ const candidate = join32(current, ".tania");
15302
+ if (existsSync27(candidate) && statSync10(candidate).isDirectory()) return candidate;
14976
15303
  const parent = dirname15(current);
14977
15304
  if (parent === current) return null;
14978
15305
  current = parent;
@@ -14980,14 +15307,14 @@ function findProjectTaniaDir(cwd) {
14980
15307
  }
14981
15308
  function resolveSessionsDir(options = {}) {
14982
15309
  const projectTania = findProjectTaniaDir(defaultCwd(options.cwd));
14983
- if (projectTania) return { dir: join31(projectTania, "sessions"), scope: "project" };
15310
+ if (projectTania) return { dir: join32(projectTania, "sessions"), scope: "project" };
14984
15311
  return { dir: globalSessionsDir(options.homeDir), scope: "global" };
14985
15312
  }
14986
15313
  function ensureSessionsDir(dir, scope) {
14987
15314
  mkdirSync7(dir, { recursive: true });
14988
15315
  if (scope === "project") {
14989
- const ignorePath = join31(dir, ".gitignore");
14990
- if (!existsSync26(ignorePath)) writeFileSync6(ignorePath, "*\n", "utf8");
15316
+ const ignorePath = join32(dir, ".gitignore");
15317
+ if (!existsSync27(ignorePath)) writeFileSync6(ignorePath, "*\n", "utf8");
14991
15318
  }
14992
15319
  }
14993
15320
  function shortLabel(content) {
@@ -15130,7 +15457,7 @@ function sanitizeTurn(turn) {
15130
15457
  return sanitized;
15131
15458
  }
15132
15459
  function sessionPath(dir, id) {
15133
- return join31(dir, `${id}.json`);
15460
+ return join32(dir, `${id}.json`);
15134
15461
  }
15135
15462
  function jsonlPathFor(path) {
15136
15463
  return path.replace(/\.json$/, ".jsonl");
@@ -15153,7 +15480,7 @@ function readBaseSession(path) {
15153
15480
  };
15154
15481
  }
15155
15482
  function readTurnsFromJsonl(path, fallbackJsonPath) {
15156
- if (!existsSync26(path)) return { turns: readBaseSession(fallbackJsonPath).turns, warnings: [] };
15483
+ if (!existsSync27(path)) return { turns: readBaseSession(fallbackJsonPath).turns, warnings: [] };
15157
15484
  const raw = readFileSync14(path, "utf8");
15158
15485
  const turns = [];
15159
15486
  const warnings = [];
@@ -15224,7 +15551,7 @@ function latestUpdatedAt(base, turns) {
15224
15551
  }
15225
15552
  function resolveSessionJsonPath(sessionId, options = {}) {
15226
15553
  const active = activeSessionPaths.get(sessionId);
15227
- if (active && existsSync26(active)) return active;
15554
+ if (active && existsSync27(active)) return active;
15228
15555
  const matches = findSessionPathMatches(sessionId, options);
15229
15556
  if (matches.length === 1) return matches[0].path;
15230
15557
  if (matches.length > 1) throw new Error(`Session id "${sessionId}" is ambiguous: ${matches.map((match) => match.id).join(", ")}`);
@@ -15271,15 +15598,15 @@ function readSessionSummaries(dir, scope) {
15271
15598
  });
15272
15599
  }
15273
15600
  function sessionJsonFiles(dir) {
15274
- if (!existsSync26(dir)) return [];
15275
- return readdirSync10(dir).filter((file) => file.endsWith(".json")).sort().map((file) => join31(dir, file));
15601
+ if (!existsSync27(dir)) return [];
15602
+ return readdirSync10(dir).filter((file) => file.endsWith(".json")).sort().map((file) => join32(dir, file));
15276
15603
  }
15277
15604
  function sessionDirsForListing(options) {
15278
15605
  if (options.global) return [{ dir: globalSessionsDir(options.homeDir), scope: "global" }];
15279
15606
  const dirs = [];
15280
15607
  const cwd = defaultCwd(options.cwd);
15281
15608
  const projectTania = findProjectTaniaDir(cwd);
15282
- if (projectTania) dirs.push({ dir: join31(projectTania, "sessions"), scope: "project" });
15609
+ if (projectTania) dirs.push({ dir: join32(projectTania, "sessions"), scope: "project" });
15283
15610
  dirs.push({ dir: globalSessionsDir(options.homeDir), scope: "global" });
15284
15611
  return dirs;
15285
15612
  }
@@ -15569,10 +15896,10 @@ function parseDuration(raw) {
15569
15896
  registerCommand(auditCommand);
15570
15897
 
15571
15898
  // src/safety/permissions/config.ts
15572
- import { existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as readFileSync15, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "fs";
15573
- import { dirname as dirname16, join as join32 } from "path";
15899
+ import { existsSync as existsSync28, mkdirSync as mkdirSync8, readFileSync as readFileSync15, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "fs";
15900
+ import { dirname as dirname16, join as join33 } from "path";
15574
15901
  function writeProjectPermissionMode(cwd, mode) {
15575
- const path = join32(cwd, ".tania", "permissions.json");
15902
+ const path = join33(cwd, ".tania", "permissions.json");
15576
15903
  const current = readProjectPermissions(path);
15577
15904
  const next = {
15578
15905
  ...current,
@@ -15586,7 +15913,7 @@ function writeProjectPermissionMode(cwd, mode) {
15586
15913
  return path;
15587
15914
  }
15588
15915
  function appendProjectSpendRule(cwd, rule) {
15589
- const path = join32(cwd, ".tania", "permissions.json");
15916
+ const path = join33(cwd, ".tania", "permissions.json");
15590
15917
  const current = readProjectPermissions(path);
15591
15918
  const next = {
15592
15919
  ...current,
@@ -15596,7 +15923,7 @@ function appendProjectSpendRule(cwd, rule) {
15596
15923
  return path;
15597
15924
  }
15598
15925
  function readProjectPermissions(path) {
15599
- if (!existsSync27(path)) return { ...DEFAULT_PERMISSION_RULES };
15926
+ if (!existsSync28(path)) return { ...DEFAULT_PERMISSION_RULES };
15600
15927
  const parsed = parsePermissionsJson(readFileSync15(path, "utf8"));
15601
15928
  return parsed.ok ? parsed.value : { ...DEFAULT_PERMISSION_RULES };
15602
15929
  }
@@ -15892,7 +16219,7 @@ registerCommand(helpCommand);
15892
16219
 
15893
16220
  // src/commands/builtin/memory.ts
15894
16221
  import { readdir as readdir7, readFile as readFile13 } from "fs/promises";
15895
- import { join as join33 } from "path";
16222
+ import { join as join34 } from "path";
15896
16223
  var DEFAULT_LIMIT2 = 10;
15897
16224
  var memoryCommand = {
15898
16225
  name: "memory",
@@ -15973,7 +16300,7 @@ function indent(text2) {
15973
16300
  }
15974
16301
  registerCommand(memoryCommand);
15975
16302
  async function readChildRunSummary(workspace, runId) {
15976
- const runsRoot = join33(workspace, ".tania", "runs");
16303
+ const runsRoot = join34(workspace, ".tania", "runs");
15977
16304
  const path = await findRunSummaryPath(runsRoot, `${runId}.json`);
15978
16305
  if (!path) return null;
15979
16306
  try {
@@ -15995,7 +16322,7 @@ async function findRunSummaryPath(dir, filename) {
15995
16322
  return null;
15996
16323
  }
15997
16324
  for (const entry of entries) {
15998
- const path = join33(dir, entry.name);
16325
+ const path = join34(dir, entry.name);
15999
16326
  if (entry.isFile() && entry.name === filename) return path;
16000
16327
  if (entry.isDirectory()) {
16001
16328
  const nested = await findRunSummaryPath(path, filename);
@@ -16280,8 +16607,8 @@ registerCommand(resumeCommand);
16280
16607
  registerCommand(saveCommand);
16281
16608
 
16282
16609
  // src/commands/project.ts
16283
- import { existsSync as existsSync28, readdirSync as readdirSync11 } from "fs";
16284
- import { basename as basename7, extname as extname2, join as join34, relative as relative13 } from "path";
16610
+ import { existsSync as existsSync29, readdirSync as readdirSync11 } from "fs";
16611
+ import { basename as basename7, extname as extname2, join as join35, relative as relative13 } from "path";
16285
16612
  import { pathToFileURL } from "url";
16286
16613
  import { tsImport } from "tsx/esm/api";
16287
16614
  var supportedExtensions = /* @__PURE__ */ new Set([".js", ".ts", ".sh"]);
@@ -16291,8 +16618,8 @@ async function loadProjectCommands(workspace) {
16291
16618
  if (loadedWorkspace === workspace) return;
16292
16619
  loadedWorkspace = workspace;
16293
16620
  removeCommandsByCategory("project");
16294
- const commandsDir = join34(workspace, ".tania", "commands");
16295
- if (!existsSync28(commandsDir)) return;
16621
+ const commandsDir = join35(workspace, ".tania", "commands");
16622
+ if (!existsSync29(commandsDir)) return;
16296
16623
  let files = [];
16297
16624
  try {
16298
16625
  files = readdirSync11(commandsDir).filter((file) => supportedExtensions.has(extname2(file))).sort();
@@ -16301,7 +16628,7 @@ async function loadProjectCommands(workspace) {
16301
16628
  return;
16302
16629
  }
16303
16630
  for (const file of files) {
16304
- const path = join34(commandsDir, file);
16631
+ const path = join35(commandsDir, file);
16305
16632
  try {
16306
16633
  const extension2 = extname2(file);
16307
16634
  if (extension2 === ".sh") {
@@ -16501,15 +16828,15 @@ function splitCommandArgs(input) {
16501
16828
  }
16502
16829
 
16503
16830
  // src/safety/permissions/learning.ts
16504
- import { existsSync as existsSync29, mkdirSync as mkdirSync9, readFileSync as readFileSync16, renameSync as renameSync4, writeFileSync as writeFileSync8 } from "fs";
16831
+ import { existsSync as existsSync30, mkdirSync as mkdirSync9, readFileSync as readFileSync16, renameSync as renameSync4, writeFileSync as writeFileSync8 } from "fs";
16505
16832
  import { homedir as homedir6 } from "os";
16506
- import { dirname as dirname17, join as join35 } from "path";
16833
+ import { dirname as dirname17, join as join36 } from "path";
16507
16834
  function permissionPatternForInput(tool, input) {
16508
16835
  return `${tool}:${escapeRegex2(inputShape(input))}`;
16509
16836
  }
16510
16837
  function appendLearnedPermissionRule(options) {
16511
16838
  const home = options.home ?? homedir6();
16512
- const path = join35(home, ".tanya", "permissions.json");
16839
+ const path = join36(home, ".tanya", "permissions.json");
16513
16840
  const pattern2 = permissionPatternForInput(options.tool, options.input);
16514
16841
  const current = readUserPermissions(path);
16515
16842
  const next = {
@@ -16525,7 +16852,7 @@ function appendLearnedPermissionRule(options) {
16525
16852
  return pattern2;
16526
16853
  }
16527
16854
  function readUserPermissions(path) {
16528
- if (!existsSync29(path)) return { ...DEFAULT_PERMISSION_RULES };
16855
+ if (!existsSync30(path)) return { ...DEFAULT_PERMISSION_RULES };
16529
16856
  const parsed = parsePermissionsJson(readFileSync16(path, "utf8"));
16530
16857
  if (!parsed.ok) return { ...DEFAULT_PERMISSION_RULES };
16531
16858
  return parsed.value;
@@ -16876,4 +17203,4 @@ export {
16876
17203
  dispatchInteractiveCommand,
16877
17204
  startInteractiveChat
16878
17205
  };
16879
- //# sourceMappingURL=chunk-5PSV2Y3X.js.map
17206
+ //# sourceMappingURL=chunk-3NV2QP7J.js.map