@proxysoul/soulforge 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -63,7 +63,7 @@ SoulForge doesn't work that way. On startup, it builds a **live dependency graph
63
63
 
64
64
  - **Lock-in mode.** Hides agent narration during work, shows only tool activity and the final answer. Toggle via `/lock-in` or config.
65
65
  - **Embedded Neovim.** Your actual config, plugins, and LSP servers. The AI works through the same editor you use. [Deep dive →](docs/architecture.md)
66
- - **17 providers.** Anthropic, OpenAI, Google, xAI, Groq, DeepSeek, Mistral, Amazon Bedrock, Fireworks, GitHub Copilot, GitHub Models, Ollama, OpenRouter, LLM Gateway, Vercel AI Gateway, Proxy, and any OpenAI-compatible API.
66
+ - **18 providers.** Anthropic, OpenAI, Google, xAI, Groq, DeepSeek, Mistral, Amazon Bedrock, Fireworks, GitHub Copilot, GitHub Models, Ollama, LM Studio, OpenRouter, LLM Gateway, Vercel AI Gateway, Proxy, and any OpenAI-compatible API.
67
67
  - **Task router.** Assign different models to different jobs. Spark agents (explore/investigate) and ember agents (code edits) can each use different models. You pick what goes where. [Deep dive →](docs/architecture.md)
68
68
  - **Code execution (Smithy).** Sandboxed code execution via Anthropic's `code_execution` tool. The agent can run Python to process data, do calculations, or batch tool calls programmatically.
69
69
  - **User steering.** Type while the agent works. Messages queue up and reach the agent at the next step. [Deep dive →](docs/steering.md)
@@ -88,7 +88,7 @@ SoulForge doesn't work that way. On startup, it builds a **live dependency graph
88
88
  | **Task routing** | Per-task model assignment (spark, ember, web search, verify, desloppify, compact) | Single model | Single model | Per-agent model | Single model |
89
89
  | **Compound tools** | `read` (batch + surgical), `multi_edit` (atomic), `rename_symbol`, `move_symbol`, `rename_file`, `refactor`, `project` | Rename via LSP | — | — | — |
90
90
  | **Editor** | Embedded Neovim (your config, your plugins) | No editor | No editor | No editor | No editor |
91
- | **Providers** | 17 + custom OpenAI-compatible | Anthropic only | Multi-model | OpenAI only | 100+ LLMs |
91
+ | **Providers** | 18 + custom OpenAI-compatible | Anthropic only | Multi-model | OpenAI only | 100+ LLMs |
92
92
  | **License** | BSL 1.1 (source-available) | Proprietary | Proprietary | Apache 2.0 | Apache 2.0 |
93
93
 
94
94
  > *Competitor features verified as of March 29, 2026. [Let us know](https://github.com/ProxySoul/soulforge/issues) if something's changed.*
@@ -215,6 +215,7 @@ soulforge --headless --diff "fix the bug" # Show changed files
215
215
  | [**GitHub Copilot**](https://github.com/features/copilot) | OAuth token from IDE ([setup](docs/copilot-provider.md)) |
216
216
  | [**GitHub Models**](https://github.com/marketplace/models) | `GITHUB_MODELS_API_KEY` (PAT with `models:read`) |
217
217
  | [**Ollama**](https://ollama.ai) | Auto-detected |
218
+ | [**LM Studio**](https://lmstudio.ai) | Auto-detected |
218
219
  | [**OpenRouter**](https://openrouter.ai) | `OPENROUTER_API_KEY` |
219
220
  | [**Vercel AI Gateway**](https://vercel.com/ai-gateway) | `AI_GATEWAY_API_KEY` |
220
221
  | [**Proxy**](https://github.com/router-for-me/CLIProxyAPI) | `PROXY_API_KEY` |
@@ -226,6 +227,10 @@ soulforge --headless --diff "fix the bug" # Show changed files
226
227
 
227
228
  **GitHub Models**: Free playground API with per-token billing. Create a fine-grained PAT with `models:read` scope. Lower rate limits than Copilot.
228
229
 
230
+ **Ollama**: Auto-detected at `localhost:11434`. Override with `OLLAMA_HOST=http://host:port`.
231
+
232
+ **LM Studio**: Auto-detected at `localhost:1234`. Uses the [REST API v0](https://lmstudio.ai/docs/developer/rest/endpoints) for rich model data (context length, type filtering). Override with `LM_STUDIO_URL=http://host:port`. Optional auth: set `LM_API_TOKEN` if you've enabled authentication in LM Studio.
233
+
229
234
  Add custom providers in config, no code changes:
230
235
 
231
236
  ```json
package/dist/index.js CHANGED
@@ -39260,7 +39260,7 @@ var package_default;
39260
39260
  var init_package = __esm(() => {
39261
39261
  package_default = {
39262
39262
  name: "@proxysoul/soulforge",
39263
- version: "2.2.0",
39263
+ version: "2.3.0",
39264
39264
  description: "Graph-powered code intelligence \u2014 multi-agent coding with codebase-aware AI",
39265
39265
  repository: {
39266
39266
  type: "git",
@@ -39982,7 +39982,8 @@ function buildCustomProvider(config2) {
39982
39982
  id: config2.id,
39983
39983
  name: config2.name ?? config2.id,
39984
39984
  envVar,
39985
- icon: "\u25C7",
39985
+ icon: "\uF29F",
39986
+ asciiIcon: "\u25C7",
39986
39987
  custom: true,
39987
39988
  createModel(modelId) {
39988
39989
  const apiKey = envVar ? getProviderApiKey(envVar) ?? "" : "custom";
@@ -49575,6 +49576,75 @@ var init_llmgateway = __esm(() => {
49575
49576
  };
49576
49577
  });
49577
49578
 
49579
+ // src/core/llm/providers/lmstudio.ts
49580
+ function getBaseOrigin() {
49581
+ return (process.env.LM_STUDIO_URL ?? "http://localhost:1234").replace(/\/+$/, "");
49582
+ }
49583
+ function openaiBase() {
49584
+ return `${getBaseOrigin()}/v1`;
49585
+ }
49586
+ function restBase() {
49587
+ return `${getBaseOrigin()}/api/v0`;
49588
+ }
49589
+ function getApiToken() {
49590
+ return process.env.LM_API_TOKEN ?? "lm-studio";
49591
+ }
49592
+ function authHeaders() {
49593
+ const token = getApiToken();
49594
+ return token && token !== "lm-studio" ? {
49595
+ Authorization: `Bearer ${token}`
49596
+ } : {};
49597
+ }
49598
+ var lmstudio;
49599
+ var init_lmstudio = __esm(() => {
49600
+ init_dist6();
49601
+ lmstudio = {
49602
+ id: "lmstudio",
49603
+ name: "LM Studio",
49604
+ envVar: "LM_API_TOKEN",
49605
+ secretKey: "lm-api-token",
49606
+ icon: "\uEA79",
49607
+ asciiIcon: "L",
49608
+ description: "Local models via LM Studio \u2014 no key needed",
49609
+ createModel(modelId) {
49610
+ const client = createOpenAI({
49611
+ baseURL: openaiBase(),
49612
+ apiKey: getApiToken()
49613
+ });
49614
+ return client.chat(modelId);
49615
+ },
49616
+ async fetchModels() {
49617
+ const res = await fetch(`${restBase()}/models`, {
49618
+ headers: authHeaders(),
49619
+ signal: AbortSignal.timeout(3000)
49620
+ });
49621
+ if (!res.ok)
49622
+ throw new Error(`LM Studio API ${String(res.status)}`);
49623
+ const data = await res.json();
49624
+ if (!Array.isArray(data.data))
49625
+ return null;
49626
+ return data.data.filter((m) => m.type === "llm" || m.type === "vlm").map((m) => ({
49627
+ id: m.id,
49628
+ name: m.id,
49629
+ contextWindow: m.max_context_length
49630
+ }));
49631
+ },
49632
+ fallbackModels: [],
49633
+ async checkAvailability() {
49634
+ try {
49635
+ const res = await fetch(`${restBase()}/models`, {
49636
+ headers: authHeaders(),
49637
+ signal: AbortSignal.timeout(1000)
49638
+ });
49639
+ return res.ok;
49640
+ } catch {
49641
+ return false;
49642
+ }
49643
+ },
49644
+ contextWindows: []
49645
+ };
49646
+ });
49647
+
49578
49648
  // node_modules/vercel-minimax-ai-provider/node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider/dist/index.mjs
49579
49649
  function getErrorMessage4(error48) {
49580
49650
  if (error48 == null) {
@@ -58676,6 +58746,9 @@ var init_mistral = __esm(() => {
58676
58746
  });
58677
58747
 
58678
58748
  // src/core/llm/providers/ollama.ts
58749
+ function getOllamaHost() {
58750
+ return (process.env.OLLAMA_HOST ?? "http://localhost:11434").replace(/\/+$/, "");
58751
+ }
58679
58752
  var ollama;
58680
58753
  var init_ollama = __esm(() => {
58681
58754
  init_dist6();
@@ -58683,18 +58756,18 @@ var init_ollama = __esm(() => {
58683
58756
  id: "ollama",
58684
58757
  name: "Ollama",
58685
58758
  envVar: "",
58686
- icon: "\uD83E\uDD99",
58687
- asciiIcon: "\uD83E\uDD99",
58759
+ icon: "\uEBA2",
58760
+ asciiIcon: "O",
58688
58761
  description: "Local models \u2014 no key needed",
58689
58762
  createModel(modelId) {
58690
58763
  const client = createOpenAI({
58691
- baseURL: "http://localhost:11434/v1",
58764
+ baseURL: `${getOllamaHost()}/v1`,
58692
58765
  apiKey: "ollama"
58693
58766
  });
58694
58767
  return client.chat(modelId);
58695
58768
  },
58696
58769
  async fetchModels() {
58697
- const res = await fetch("http://localhost:11434/api/tags");
58770
+ const res = await fetch(`${getOllamaHost()}/api/tags`);
58698
58771
  if (!res.ok)
58699
58772
  throw new Error(`Ollama API ${String(res.status)}`);
58700
58773
  const data = await res.json();
@@ -58721,7 +58794,7 @@ var init_ollama = __esm(() => {
58721
58794
  }],
58722
58795
  async checkAvailability() {
58723
58796
  try {
58724
- const res = await fetch("http://localhost:11434/api/tags", {
58797
+ const res = await fetch(`${getOllamaHost()}/api/tags`, {
58725
58798
  signal: AbortSignal.timeout(1000)
58726
58799
  });
58727
58800
  return res.ok;
@@ -77733,6 +77806,7 @@ __export(exports_providers, {
77733
77806
  ollama: () => ollama,
77734
77807
  mistral: () => mistral2,
77735
77808
  minimax: () => minimax2,
77809
+ lmstudio: () => lmstudio,
77736
77810
  llmgateway: () => llmgateway2,
77737
77811
  groq: () => groq2,
77738
77812
  google: () => google2,
@@ -77793,6 +77867,7 @@ var init_providers = __esm(() => {
77793
77867
  init_google();
77794
77868
  init_groq();
77795
77869
  init_llmgateway();
77870
+ init_lmstudio();
77796
77871
  init_minimax();
77797
77872
  init_mistral();
77798
77873
  init_ollama();
@@ -77811,6 +77886,7 @@ var init_providers = __esm(() => {
77811
77886
  init_google();
77812
77887
  init_groq();
77813
77888
  init_llmgateway();
77889
+ init_lmstudio();
77814
77890
  init_minimax();
77815
77891
  init_mistral();
77816
77892
  init_ollama();
@@ -77819,7 +77895,7 @@ var init_providers = __esm(() => {
77819
77895
  init_proxy();
77820
77896
  init_vercel_gateway();
77821
77897
  init_xai();
77822
- BUILTIN_PROVIDERS = [llmgateway2, anthropic2, proxy2, vercelGatewayProvider, openai2, xai2, google2, groq2, deepseek2, mistral2, bedrock2, fireworks2, minimax2, copilot, githubModels, openrouter2, ollama];
77898
+ BUILTIN_PROVIDERS = [llmgateway2, anthropic2, proxy2, vercelGatewayProvider, openai2, xai2, google2, groq2, deepseek2, mistral2, bedrock2, fireworks2, minimax2, copilot, githubModels, openrouter2, ollama, lmstudio];
77823
77899
  allProviders = [...BUILTIN_PROVIDERS];
77824
77900
  providerMap = new Map(allProviders.map((p) => [p.id, p]));
77825
77901
  });
@@ -81561,7 +81637,7 @@ class StandaloneLspClient {
81561
81637
  }
81562
81638
  return;
81563
81639
  }
81564
- const languageId = this.config.language === "typescript" ? "typescript" : this.config.language === "javascript" ? "javascript" : this.config.language === "python" ? "python" : this.config.language === "go" ? "go" : this.config.language === "rust" ? "rust" : "plaintext";
81640
+ const languageId = LANGUAGE_ID_MAP[this.config.language] ?? this.config.language;
81565
81641
  this.notify("textDocument/didOpen", {
81566
81642
  textDocument: {
81567
81643
  uri,
@@ -81931,10 +82007,40 @@ function normalizeLocations(result) {
81931
82007
  }
81932
82008
  return [result];
81933
82009
  }
82010
+ var LANGUAGE_ID_MAP;
81934
82011
  var init_standalone_client = __esm(() => {
81935
82012
  init_errors();
81936
82013
  init_process_tracker();
81937
82014
  init_protocol();
82015
+ LANGUAGE_ID_MAP = {
82016
+ typescript: "typescript",
82017
+ javascript: "javascript",
82018
+ python: "python",
82019
+ go: "go",
82020
+ rust: "rust",
82021
+ java: "java",
82022
+ kotlin: "kotlin",
82023
+ scala: "scala",
82024
+ csharp: "csharp",
82025
+ swift: "swift",
82026
+ dart: "dart",
82027
+ elixir: "elixir",
82028
+ ocaml: "ocaml",
82029
+ lua: "lua",
82030
+ c: "c",
82031
+ cpp: "cpp",
82032
+ ruby: "ruby",
82033
+ php: "php",
82034
+ zig: "zig",
82035
+ bash: "shellscript",
82036
+ css: "css",
82037
+ html: "html",
82038
+ json: "json",
82039
+ toml: "toml",
82040
+ yaml: "yaml",
82041
+ dockerfile: "dockerfile",
82042
+ vue: "vue"
82043
+ };
81938
82044
  });
81939
82045
 
81940
82046
  // src/core/editor/instance.ts
@@ -82478,7 +82584,7 @@ async function findImplementation(filePath, line, col) {
82478
82584
  textDocument = { uri = vim.uri_from_fname(filepath) },
82479
82585
  position = { line = ${String(line)}, character = ${String(col)} },
82480
82586
  }
82481
- local results = vim.lsp.buf_request_sync(bufnr, 'textDocument/implementation', params, 5000)
82587
+ local results = vim.lsp.buf_request_sync(bufnr, 'textDocument/implementation', params, 15000)
82482
82588
  if not results then return '[]' end
82483
82589
  local impls = {}
82484
82590
  for _, res in pairs(results) do
@@ -82615,7 +82721,7 @@ var init_nvim_bridge = __esm(() => {
82615
82721
  });
82616
82722
 
82617
82723
  // src/core/intelligence/backends/lsp/index.ts
82618
- import { existsSync as existsSync11 } from "fs";
82724
+ import { existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
82619
82725
  import { readdir, readFile as readFile4 } from "fs/promises";
82620
82726
  import { dirname as dirname3, join as join10, resolve as resolve5 } from "path";
82621
82727
  function getNvimBridge() {
@@ -83367,6 +83473,9 @@ class LspBackend {
83367
83473
  await new Promise((r) => setTimeout(r, 500));
83368
83474
  }
83369
83475
  }
83476
+ isNvimAvailable() {
83477
+ return nvimBridge.isNvimAvailable();
83478
+ }
83370
83479
  async warmupNvim(file2) {
83371
83480
  if (!nvimBridge.isNvimAvailable())
83372
83481
  return false;
@@ -83773,8 +83882,22 @@ function findProjectRootForLanguage(file2, language) {
83773
83882
  return null;
83774
83883
  let dir = dirname3(resolve5(file2));
83775
83884
  const root = resolve5("/");
83776
- while (dir !== root) {
83777
- for (const marker30 of markers) {
83885
+ const MAX_DIRS = 100;
83886
+ let dirCount = 0;
83887
+ while (dir !== root && dirCount < MAX_DIRS) {
83888
+ dirCount++;
83889
+ const extMarkers = markers.filter((m) => m.startsWith(".") && !m.includes("/"));
83890
+ const nameMarkers = markers.filter((m) => !m.startsWith(".") || m.includes("/"));
83891
+ if (extMarkers.length > 0) {
83892
+ try {
83893
+ const entries = readdirSync3(dir);
83894
+ for (const marker30 of extMarkers) {
83895
+ if (entries.some((e) => e.endsWith(marker30)))
83896
+ return dir;
83897
+ }
83898
+ } catch {}
83899
+ }
83900
+ for (const marker30 of nameMarkers) {
83778
83901
  if (existsSync11(join10(dir, marker30)))
83779
83902
  return dir;
83780
83903
  }
@@ -83806,15 +83929,24 @@ var init_lsp = __esm(() => {
83806
83929
  return mod[prop];
83807
83930
  }
83808
83931
  });
83809
- SUPPORTED_LANGUAGES = new Set(["typescript", "javascript", "python", "go", "rust", "lua", "c", "cpp", "ruby", "php", "zig", "bash", "css", "html", "json", "yaml", "dockerfile"]);
83932
+ SUPPORTED_LANGUAGES = new Set(["typescript", "javascript", "python", "go", "rust", "java", "kotlin", "scala", "csharp", "swift", "dart", "elixir", "ocaml", "lua", "c", "cpp", "ruby", "php", "zig", "bash", "css", "html", "json", "toml", "yaml", "dockerfile", "vue"]);
83810
83933
  PROJECT_FILES = {
83811
83934
  typescript: ["tsconfig.json"],
83812
83935
  javascript: ["jsconfig.json", "tsconfig.json"],
83813
83936
  python: ["pyproject.toml", "setup.py"],
83814
83937
  go: ["go.mod"],
83815
- rust: ["Cargo.toml"]
83938
+ rust: ["Cargo.toml"],
83939
+ java: ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"],
83940
+ kotlin: ["build.gradle.kts", "build.gradle", "settings.gradle.kts", "settings.gradle"],
83941
+ scala: ["build.sbt"],
83942
+ csharp: [".csproj", ".sln"],
83943
+ dart: ["pubspec.yaml"],
83944
+ elixir: ["mix.exs"],
83945
+ ruby: ["Gemfile"],
83946
+ php: ["composer.json"],
83947
+ swift: ["Package.swift"]
83816
83948
  };
83817
- DEFINITION_KEYWORDS = /\b(function|class|const|let|var|type|interface|enum|struct|trait|fn|def|func|impl|mod|pub)\b/;
83949
+ DEFINITION_KEYWORDS = /\b(function|class|const|let|var|type|interface|enum|struct|trait|fn|def|func|impl|mod|pub|public|private|protected|static|void|abstract|override|sealed|record|annotation|object|fun|suspend|data|inline|operator|infix|external|companion|lateinit|val)\b/;
83818
83950
  });
83819
83951
 
83820
83952
  // src/core/intelligence/backends/regex.ts
@@ -330073,7 +330205,7 @@ var tsMorphModule = null;
330073
330205
  var init_ts_morph = () => {};
330074
330206
 
330075
330207
  // src/core/intelligence/router.ts
330076
- import { existsSync as existsSync13, readdirSync as readdirSync3 } from "fs";
330208
+ import { existsSync as existsSync13, readdirSync as readdirSync4 } from "fs";
330077
330209
  import { extname, join as join12 } from "path";
330078
330210
 
330079
330211
  class CodeIntelligenceRouter {
@@ -330236,8 +330368,9 @@ class CodeIntelligenceRouter {
330236
330368
  add(lang);
330237
330369
  }
330238
330370
  const SKIP = new Set(["node_modules", ".git", "dist", "build", "out", "vendor", "__pycache__", ".venv", "venv", "target", ".next", ".nuxt", ".output", "coverage", ".turbo", ".cache"]);
330239
- const MAX_DEPTH = 3;
330240
- const MAX_DIRS = 100;
330371
+ const hasJvm = found.some((l) => l === "java" || l === "kotlin" || l === "scala");
330372
+ const MAX_DEPTH = hasJvm ? 8 : 3;
330373
+ const MAX_DIRS = hasJvm ? 500 : 100;
330241
330374
  const queue = [{
330242
330375
  dir: this.cwd,
330243
330376
  depth: 0
@@ -330249,7 +330382,7 @@ class CodeIntelligenceRouter {
330249
330382
  break;
330250
330383
  visited++;
330251
330384
  try {
330252
- const entries = readdirSync3(item.dir, {
330385
+ const entries = readdirSync4(item.dir, {
330253
330386
  withFileTypes: true
330254
330387
  });
330255
330388
  for (const entry of entries) {
@@ -330285,8 +330418,9 @@ class CodeIntelligenceRouter {
330285
330418
  if (exts.length === 0)
330286
330419
  return null;
330287
330420
  const SKIP = new Set(["node_modules", ".git", "dist", "build", "out", "vendor", "__pycache__", ".venv", "venv", "target", ".next", ".nuxt", ".output", "coverage", ".turbo", ".cache"]);
330288
- const MAX_DEPTH = 4;
330289
- const MAX_DIRS = 200;
330421
+ const isJvmLanguage = language === "java" || language === "kotlin" || language === "scala";
330422
+ const MAX_DEPTH = isJvmLanguage ? 10 : 4;
330423
+ const MAX_DIRS = isJvmLanguage ? 500 : 200;
330290
330424
  const queue = [{
330291
330425
  dir: this.cwd,
330292
330426
  depth: 0
@@ -330298,7 +330432,7 @@ class CodeIntelligenceRouter {
330298
330432
  break;
330299
330433
  visited++;
330300
330434
  try {
330301
- const entries = readdirSync3(item.dir, {
330435
+ const entries = readdirSync4(item.dir, {
330302
330436
  withFileTypes: true
330303
330437
  });
330304
330438
  for (const entry of entries) {
@@ -330389,16 +330523,16 @@ class CodeIntelligenceRouter {
330389
330523
  label: `findImplementation(${probeSymbolName})`,
330390
330524
  fn: (b, f) => b.findImplementation?.(f, probeSymbolName) ?? Promise.resolve(null)
330391
330525
  }];
330392
- const probeOp = async (fn, label, probes) => {
330526
+ const probeOp = async (fn, label, probes, timeout = OP_TIMEOUT) => {
330393
330527
  const start2 = performance.now();
330394
330528
  try {
330395
- const result = await Promise.race([fn(), new Promise((r) => setTimeout(() => r("timeout"), OP_TIMEOUT))]);
330529
+ const result = await Promise.race([fn(), new Promise((r) => setTimeout(() => r("timeout"), timeout))]);
330396
330530
  const ms = Math.round(performance.now() - start2);
330397
330531
  if (result === "timeout") {
330398
330532
  probes.push({
330399
330533
  operation: label,
330400
330534
  status: "timeout",
330401
- ms: OP_TIMEOUT
330535
+ ms: timeout
330402
330536
  });
330403
330537
  } else if (result === null || result === undefined) {
330404
330538
  probes.push({
@@ -330490,13 +330624,43 @@ class CodeIntelligenceRouter {
330490
330624
  }
330491
330625
  }
330492
330626
  const hasStandaloneProbe = backend.name === "lsp" && "probeStandalone" in backend && typeof backend.probeStandalone === "function";
330493
- if (hasStandaloneProbe && probeFile && !initError) {
330627
+ if (hasStandaloneProbe && !initError) {
330494
330628
  const lsp = backend;
330629
+ if (!probeFile) {
330630
+ updateBackend("lsp:nvim", {
330631
+ initialized: this.initialized.has(backend.name),
330632
+ initMs,
330633
+ initError,
330634
+ probes: fileOps.map(({
330635
+ label
330636
+ }) => ({
330637
+ operation: label,
330638
+ status: "empty",
330639
+ error: "no source file found in project"
330640
+ }))
330641
+ });
330642
+ updateBackend("lsp:standalone", {
330643
+ initialized: this.initialized.has(backend.name),
330644
+ initError,
330645
+ probes: fileOps.map(({
330646
+ label
330647
+ }) => ({
330648
+ operation: label,
330649
+ status: "empty",
330650
+ error: "no source file found in project"
330651
+ }))
330652
+ });
330653
+ continue;
330654
+ }
330495
330655
  if (lsp.warmupNvim) {
330496
- try {
330497
- await Promise.race([lsp.warmupNvim(probeFile), new Promise((r) => setTimeout(() => r(false), 25000))]);
330498
- } catch {}
330656
+ const nvimAvailable = lsp.isNvimAvailable?.() ?? false;
330657
+ if (nvimAvailable) {
330658
+ try {
330659
+ await Promise.race([lsp.warmupNvim(probeFile), new Promise((r) => setTimeout(() => r(false), 25000))]);
330660
+ } catch {}
330661
+ }
330499
330662
  }
330663
+ const IMPL_TIMEOUT = 20000;
330500
330664
  const nvimProbes = [];
330501
330665
  for (const {
330502
330666
  op,
@@ -330510,7 +330674,8 @@ class CodeIntelligenceRouter {
330510
330674
  });
330511
330675
  continue;
330512
330676
  }
330513
- await probeOp(() => fn(backend, probeFile), label, nvimProbes);
330677
+ const t = op === "findImplementation" ? IMPL_TIMEOUT : OP_TIMEOUT;
330678
+ await probeOp(() => fn(backend, probeFile), label, nvimProbes, t);
330514
330679
  }
330515
330680
  updateBackend("lsp:nvim", {
330516
330681
  initialized: this.initialized.has(backend.name),
@@ -330533,7 +330698,8 @@ class CodeIntelligenceRouter {
330533
330698
  });
330534
330699
  continue;
330535
330700
  }
330536
- await probeOp(() => lsp.probeStandalone(probeFile, op), label, standaloneProbes);
330701
+ const t = op === "findImplementation" ? IMPL_TIMEOUT : OP_TIMEOUT;
330702
+ await probeOp(() => lsp.probeStandalone(probeFile, op), label, standaloneProbes, t);
330537
330703
  }
330538
330704
  updateBackend("lsp:standalone", {
330539
330705
  initialized: true,
@@ -331782,13 +331948,13 @@ var init_repo_map_constants = __esm(() => {
331782
331948
  });
331783
331949
 
331784
331950
  // src/core/context/file-tree.ts
331785
- import { readdirSync as readdirSync4 } from "fs";
331951
+ import { readdirSync as readdirSync5 } from "fs";
331786
331952
  import { join as join13 } from "path";
331787
331953
  function walkDir(dir, prefix, depth, lines) {
331788
331954
  if (depth <= 0)
331789
331955
  return;
331790
331956
  try {
331791
- const entries = readdirSync4(dir, {
331957
+ const entries = readdirSync5(dir, {
331792
331958
  withFileTypes: true
331793
331959
  }).filter((e) => !IGNORED_DIRS.has(e.name) && !e.name.startsWith(".")).sort((a, b) => {
331794
331960
  if (a.isDirectory() && !b.isDirectory())
@@ -357917,7 +358083,7 @@ ${hint}` : stdout || stderr;
357917
358083
  });
357918
358084
 
357919
358085
  // src/core/skills/manager.ts
357920
- import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync10, realpathSync as realpathSync2, rmSync as rmSync2, statSync as statSync4 } from "fs";
358086
+ import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync10, realpathSync as realpathSync2, rmSync as rmSync2, statSync as statSync4 } from "fs";
357921
358087
  import { homedir as homedir15 } from "os";
357922
358088
  import { dirname as dirname10, join as join21 } from "path";
357923
358089
  async function searchSkills(query2) {
@@ -358014,7 +358180,7 @@ function listInstalledSkills() {
358014
358180
  return [...byName.values()];
358015
358181
  }
358016
358182
  function scanSkillDir(dir, scope, byName, seenPaths) {
358017
- const entries2 = readdirSync5(dir, {
358183
+ const entries2 = readdirSync6(dir, {
358018
358184
  withFileTypes: true
358019
358185
  });
358020
358186
  for (const entry of entries2) {
@@ -382813,7 +382979,7 @@ function rebuildCoreMessages(messages) {
382813
382979
  }
382814
382980
 
382815
382981
  // src/core/sessions/manager.ts
382816
- import { existsSync as existsSync21, mkdirSync as mkdirSync9, readdirSync as readdirSync6, readFileSync as readFileSync11, rmSync as rmSync4, statSync as statSync5 } from "fs";
382982
+ import { existsSync as existsSync21, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync11, rmSync as rmSync4, statSync as statSync5 } from "fs";
382817
382983
  import { rename as rename3, writeFile as writeFile13 } from "fs/promises";
382818
382984
  import { join as join30 } from "path";
382819
382985
 
@@ -382970,7 +383136,7 @@ class SessionManager {
382970
383136
  if (!existsSync21(this.dir))
382971
383137
  return null;
382972
383138
  const normalizedPrefix = prefix.toLowerCase();
382973
- const entries2 = readdirSync6(this.dir);
383139
+ const entries2 = readdirSync7(this.dir);
382974
383140
  for (const entry of entries2) {
382975
383141
  if (entry.toLowerCase().startsWith(normalizedPrefix)) {
382976
383142
  const metaPath = join30(this.dir, entry, "meta.json");
@@ -382984,7 +383150,7 @@ class SessionManager {
382984
383150
  if (!existsSync21(this.dir))
382985
383151
  return [];
382986
383152
  try {
382987
- const entries2 = readdirSync6(this.dir);
383153
+ const entries2 = readdirSync7(this.dir);
382988
383154
  const metas = [];
382989
383155
  for (const entry of entries2) {
382990
383156
  try {
@@ -383025,7 +383191,7 @@ class SessionManager {
383025
383191
  clearAllSessions() {
383026
383192
  if (!existsSync21(this.dir))
383027
383193
  return 0;
383028
- const entries2 = readdirSync6(this.dir);
383194
+ const entries2 = readdirSync7(this.dir);
383029
383195
  let count = 0;
383030
383196
  for (const entry of entries2) {
383031
383197
  try {
@@ -383047,7 +383213,7 @@ class SessionManager {
383047
383213
  if (!existsSync21(this.dir))
383048
383214
  return 0;
383049
383215
  try {
383050
- return readdirSync6(this.dir).filter((e) => {
383216
+ return readdirSync7(this.dir).filter((e) => {
383051
383217
  try {
383052
383218
  return statSync5(join30(this.dir, e)).isDirectory();
383053
383219
  } catch {
@@ -446442,7 +446608,7 @@ var init_shallow2 = __esm(() => {
446442
446608
  });
446443
446609
 
446444
446610
  // src/core/commands/utils.ts
446445
- import { existsSync as existsSync26, readdirSync as readdirSync7, statSync as statSync6 } from "fs";
446611
+ import { existsSync as existsSync26, readdirSync as readdirSync8, statSync as statSync6 } from "fs";
446446
446612
  import { homedir as homedir18 } from "os";
446447
446613
  import { join as join35 } from "path";
446448
446614
  function sysMsg(ctx, content) {
@@ -446464,7 +446630,7 @@ function dirSize(dirPath) {
446464
446630
  if (!existsSync26(dirPath))
446465
446631
  return 0;
446466
446632
  let total = 0;
446467
- for (const entry of readdirSync7(dirPath)) {
446633
+ for (const entry of readdirSync8(dirPath)) {
446468
446634
  const fp = join35(dirPath, entry);
446469
446635
  try {
446470
446636
  const s2 = statSync6(fp);
@@ -448891,6 +449057,12 @@ function matchCopilotPricing(model) {
448891
449057
  }
448892
449058
  return;
448893
449059
  }
449060
+ function isModelLocal(modelId) {
449061
+ const slash = modelId.indexOf("/");
449062
+ if (slash < 0)
449063
+ return false;
449064
+ return LOCAL_PROVIDERS.has(modelId.slice(0, slash).toLowerCase());
449065
+ }
448894
449066
  function isModelFree(modelId) {
448895
449067
  const id = modelId.toLowerCase();
448896
449068
  if (id.endsWith(":free") || id.endsWith("-free"))
@@ -448905,7 +449077,7 @@ function isModelFree(modelId) {
448905
449077
  }
448906
449078
  function matchPricing(modelId) {
448907
449079
  const id = modelId.toLowerCase();
448908
- if (isModelFree(modelId))
449080
+ if (isModelLocal(modelId) || isModelFree(modelId))
448909
449081
  return FREE_PRICING;
448910
449082
  if (id.startsWith("copilot/")) {
448911
449083
  const model = id.slice("copilot/".length);
@@ -449129,7 +449301,7 @@ function startMemoryPoll(intervalMs = 2000) {
449129
449301
  });
449130
449302
  }, intervalMs);
449131
449303
  }
449132
- var MODEL_PRICING, FREE_PRICING, DEFAULT_PRICING, FREE, M025, M033, M1, M32, M30, COPILOT_PRICING, ZERO_USAGE, ZERO_PROCESS_RSS, useStatusBarStore, memPollStarted = false, memPollTimer = null;
449304
+ var MODEL_PRICING, FREE_PRICING, DEFAULT_PRICING, FREE, M025, M033, M1, M32, M30, COPILOT_PRICING, LOCAL_PROVIDERS, ZERO_USAGE, ZERO_PROCESS_RSS, useStatusBarStore, memPollStarted = false, memPollTimer = null;
449133
449305
  var init_statusbar = __esm(() => {
449134
449306
  init_esm();
449135
449307
  init_middleware();
@@ -449529,6 +449701,7 @@ var init_statusbar = __esm(() => {
449529
449701
  "claude-opus-4.6": M32,
449530
449702
  "claude-opus-4.6-fast": M30
449531
449703
  };
449704
+ LOCAL_PROVIDERS = new Set(["ollama", "lmstudio"]);
449532
449705
  ZERO_USAGE = {
449533
449706
  prompt: 0,
449534
449707
  completion: 0,
@@ -462925,14 +463098,14 @@ var init_LockInStreamView = __esm(async () => {
462925
463098
  });
462926
463099
 
462927
463100
  // src/core/utils/syntax.ts
462928
- import { existsSync as existsSync32, readdirSync as readdirSync8 } from "fs";
463101
+ import { existsSync as existsSync32, readdirSync as readdirSync9 } from "fs";
462929
463102
  import { homedir as homedir22 } from "os";
462930
463103
  import { dirname as dirname17, join as join42, resolve as resolve35 } from "path";
462931
463104
  function discoverParsers() {
462932
463105
  const parsers = [];
462933
463106
  let dirs;
462934
463107
  try {
462935
- dirs = readdirSync8(coreAssetsDir, {
463108
+ dirs = readdirSync9(coreAssetsDir, {
462936
463109
  withFileTypes: true
462937
463110
  }).filter((d3) => d3.isDirectory()).map((d3) => d3.name);
462938
463111
  } catch {
@@ -462940,7 +463113,7 @@ function discoverParsers() {
462940
463113
  }
462941
463114
  for (const dir of dirs) {
462942
463115
  const langDir = resolve35(coreAssetsDir, dir);
462943
- const wasmFiles = readdirSync8(langDir).filter((f3) => f3.endsWith(".wasm"));
463116
+ const wasmFiles = readdirSync9(langDir).filter((f3) => f3.endsWith(".wasm"));
462944
463117
  const wasmFile = wasmFiles[0];
462945
463118
  if (!wasmFile)
462946
463119
  continue;
@@ -475004,8 +475177,10 @@ function fmtCost(usd) {
475004
475177
  return `$${usd.toFixed(3)}`;
475005
475178
  return `$${usd.toFixed(2)}`;
475006
475179
  }
475007
- function buildContent2(costCents, cacheHitPct, free) {
475180
+ function buildContent2(costCents, cacheHitPct, free, local) {
475008
475181
  const tk = getThemeTokens();
475182
+ if (local)
475183
+ return new StyledText([fg(tk.success)("Local")]);
475009
475184
  if (free)
475010
475185
  return new StyledText([fg(tk.success)("FREE")]);
475011
475186
  const cost = costCents / 100;
@@ -475020,11 +475195,13 @@ function TokenDisplay() {
475020
475195
  const cacheHitRef = import_react59.useRef(0);
475021
475196
  const currentCostRef = import_react59.useRef(0);
475022
475197
  const freeRef = import_react59.useRef(false);
475198
+ const localRef = import_react59.useRef(false);
475023
475199
  import_react59.useEffect(() => useStatusBarStore.subscribe((state) => {
475024
475200
  const usage = state.tokenUsage;
475025
475201
  const breakdown = usage.modelBreakdown;
475026
475202
  const modelIds = Object.keys(breakdown);
475027
- freeRef.current = modelIds.length > 0 && modelIds.every((mid) => isModelFree(mid));
475203
+ localRef.current = modelIds.length > 0 && modelIds.every((mid) => isModelLocal(mid));
475204
+ freeRef.current = !localRef.current && modelIds.length > 0 && modelIds.every((mid_0) => isModelFree(mid_0));
475028
475205
  const rawCost = breakdown && modelIds.length > 0 ? computeTotalCostFromBreakdown(breakdown) : 0;
475029
475206
  costRef.current = Math.round(rawCost * 100);
475030
475207
  const totalInput = usage.prompt + usage.subagentInput + usage.cacheRead + usage.cacheWrite;
@@ -475038,7 +475215,7 @@ function TokenDisplay() {
475038
475215
  currentCostRef.current = approach2(currentCostRef.current, target);
475039
475216
  try {
475040
475217
  if (textRef.current)
475041
- textRef.current.content = buildContent2(currentCostRef.current, cacheHitRef.current, freeRef.current);
475218
+ textRef.current.content = buildContent2(currentCostRef.current, cacheHitRef.current, freeRef.current, localRef.current);
475042
475219
  } catch {}
475043
475220
  }, STEP_MS2);
475044
475221
  return () => clearInterval(timer);
@@ -475046,7 +475223,7 @@ function TokenDisplay() {
475046
475223
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
475047
475224
  ref: textRef,
475048
475225
  truncate: true,
475049
- content: buildContent2(currentCostRef.current, cacheHitRef.current, freeRef.current)
475226
+ content: buildContent2(currentCostRef.current, cacheHitRef.current, freeRef.current, localRef.current)
475050
475227
  }, undefined, false, undefined, this);
475051
475228
  }
475052
475229
  var import_react59, STEP_MS2 = 50, EASE2 = 0.35;
@@ -491873,9 +492050,10 @@ function StatusDashboard({
491873
492050
  innerW
491874
492051
  }, "t-total", false, undefined, this));
491875
492052
  const sortedBd = Object.entries(su.modelBreakdown ?? {}).sort(([midA, a2], [midB, b5]) => computeModelCost(midB, b5) - computeModelCost(midA, a2));
491876
- const allFree = sortedBd.length > 0 && sortedBd.every(([mid_0]) => isModelFree(mid_0));
492053
+ const allLocal = sortedBd.length > 0 && sortedBd.every(([mid_0]) => isModelLocal(mid_0));
492054
+ const allFree = !allLocal && sortedBd.length > 0 && sortedBd.every(([mid_1]) => isModelFree(mid_1));
491877
492055
  const totalCost = sortedBd.length > 0 ? computeTotalCostFromBreakdown(su.modelBreakdown ?? {}) : 0;
491878
- if (totalCost > 0 || allFree) {
492056
+ if (totalCost > 0 || allFree || allLocal) {
491879
492057
  const fmtCost2 = (c) => c < 0.01 ? `${c.toFixed(3)}` : `${c.toFixed(2)}`;
491880
492058
  lines.push(/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Spacer, {
491881
492059
  innerW
@@ -491886,27 +492064,28 @@ function StatusDashboard({
491886
492064
  innerW
491887
492065
  }, "h-cost", false, undefined, this));
491888
492066
  const costLabelW = Math.min(30, innerW - 20);
491889
- for (const [mid_1, usage_0] of sortedBd) {
491890
- const free = isModelFree(mid_1);
491891
- const c_0 = computeModelCost(mid_1, usage_0);
491892
- if (c_0 <= 0 && !free)
492067
+ for (const [mid_2, usage_0] of sortedBd) {
492068
+ const local = isModelLocal(mid_2);
492069
+ const free = !local && isModelFree(mid_2);
492070
+ const c_0 = computeModelCost(mid_2, usage_0);
492071
+ if (c_0 <= 0 && !free && !local)
491893
492072
  continue;
491894
492073
  const pct = totalCost > 0 ? Math.round(c_0 / totalCost * 100) : 0;
491895
492074
  const maxModelW = costLabelW - 4;
491896
- const shortId = mid_1.length > maxModelW ? `${mid_1.slice(0, maxModelW - 1)}\u2026` : mid_1;
492075
+ const shortId = mid_2.length > maxModelW ? `${mid_2.slice(0, maxModelW - 1)}\u2026` : mid_2;
491897
492076
  lines.push(/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(EntryRow, {
491898
492077
  label: ` ${shortId}`,
491899
- value: free ? "FREE" : `${fmtCost2(c_0)} (${String(pct)}%)`,
491900
- valueColor: free ? t2.success : t2.textPrimary,
492078
+ value: local ? "Local" : free ? "FREE" : `${fmtCost2(c_0)} (${String(pct)}%)`,
492079
+ valueColor: local || free ? t2.success : t2.textPrimary,
491901
492080
  labelW: costLabelW,
491902
492081
  rightAlign: true,
491903
492082
  innerW
491904
- }, `cost-${mid_1}`, false, undefined, this));
492083
+ }, `cost-${mid_2}`, false, undefined, this));
491905
492084
  }
491906
492085
  lines.push(/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(EntryRow, {
491907
492086
  label: " Total",
491908
- value: allFree ? "FREE" : fmtCost2(totalCost),
491909
- valueColor: allFree ? t2.success : t2.warning,
492087
+ value: allLocal ? "Local" : allFree ? "FREE" : fmtCost2(totalCost),
492088
+ valueColor: allLocal || allFree ? t2.success : t2.warning,
491910
492089
  labelW: costLabelW,
491911
492090
  rightAlign: true,
491912
492091
  innerW
@@ -491922,7 +492101,9 @@ function StatusDashboard({
491922
492101
  innerW
491923
492102
  }, "h-tabs", false, undefined, this));
491924
492103
  const fmtCost_0 = (c_1, modelIds) => {
491925
- if (modelIds && modelIds.length > 0 && modelIds.every((mid_2) => isModelFree(mid_2)))
492104
+ if (modelIds && modelIds.length > 0 && modelIds.every((mid_3) => isModelLocal(mid_3)))
492105
+ return "Local";
492106
+ if (modelIds && modelIds.length > 0 && modelIds.every((mid_4) => isModelFree(mid_4)))
491926
492107
  return "FREE";
491927
492108
  return c_1 <= 0 ? "\u2014" : c_1 < 0.01 ? `$${c_1.toFixed(3)}` : `$${c_1.toFixed(2)}`;
491928
492109
  };
@@ -494643,11 +494824,11 @@ function getAllPackageStatus(category) {
494643
494824
  function detectProjectLanguages(cwd2) {
494644
494825
  const languages = [];
494645
494826
  const {
494646
- readdirSync: readdirSync9
494827
+ readdirSync: readdirSync10
494647
494828
  } = __require("fs");
494648
494829
  let files;
494649
494830
  try {
494650
- files = readdirSync9(cwd2);
494831
+ files = readdirSync10(cwd2);
494651
494832
  } catch {
494652
494833
  return [];
494653
494834
  }
@@ -23992,7 +23992,7 @@ async function findImplementation(filePath, line, col) {
23992
23992
  textDocument = { uri = vim.uri_from_fname(filepath) },
23993
23993
  position = { line = ${String(line)}, character = ${String(col)} },
23994
23994
  }
23995
- local results = vim.lsp.buf_request_sync(bufnr, 'textDocument/implementation', params, 5000)
23995
+ local results = vim.lsp.buf_request_sync(bufnr, 'textDocument/implementation', params, 15000)
23996
23996
  if not results then return '[]' end
23997
23997
  local impls = {}
23998
23998
  for _, res in pairs(results) do
@@ -27624,8 +27624,9 @@ class CodeIntelligenceRouter {
27624
27624
  add(lang254);
27625
27625
  }
27626
27626
  const SKIP = new Set(["node_modules", ".git", "dist", "build", "out", "vendor", "__pycache__", ".venv", "venv", "target", ".next", ".nuxt", ".output", "coverage", ".turbo", ".cache"]);
27627
- const MAX_DEPTH2 = 3;
27628
- const MAX_DIRS = 100;
27627
+ const hasJvm = found.some((l3) => l3 === "java" || l3 === "kotlin" || l3 === "scala");
27628
+ const MAX_DEPTH2 = hasJvm ? 8 : 3;
27629
+ const MAX_DIRS = hasJvm ? 500 : 100;
27629
27630
  const queue = [{
27630
27631
  dir: this.cwd,
27631
27632
  depth: 0
@@ -27673,8 +27674,9 @@ class CodeIntelligenceRouter {
27673
27674
  if (exts.length === 0)
27674
27675
  return null;
27675
27676
  const SKIP = new Set(["node_modules", ".git", "dist", "build", "out", "vendor", "__pycache__", ".venv", "venv", "target", ".next", ".nuxt", ".output", "coverage", ".turbo", ".cache"]);
27676
- const MAX_DEPTH2 = 4;
27677
- const MAX_DIRS = 200;
27677
+ const isJvmLanguage = language === "java" || language === "kotlin" || language === "scala";
27678
+ const MAX_DEPTH2 = isJvmLanguage ? 10 : 4;
27679
+ const MAX_DIRS = isJvmLanguage ? 500 : 200;
27678
27680
  const queue = [{
27679
27681
  dir: this.cwd,
27680
27682
  depth: 0
@@ -27777,16 +27779,16 @@ class CodeIntelligenceRouter {
27777
27779
  label: `findImplementation(${probeSymbolName})`,
27778
27780
  fn: (b3, f3) => b3.findImplementation?.(f3, probeSymbolName) ?? Promise.resolve(null)
27779
27781
  }];
27780
- const probeOp = async (fn, label, probes) => {
27782
+ const probeOp = async (fn, label, probes, timeout = OP_TIMEOUT) => {
27781
27783
  const start2 = performance.now();
27782
27784
  try {
27783
- const result = await Promise.race([fn(), new Promise((r4) => setTimeout(() => r4("timeout"), OP_TIMEOUT))]);
27785
+ const result = await Promise.race([fn(), new Promise((r4) => setTimeout(() => r4("timeout"), timeout))]);
27784
27786
  const ms = Math.round(performance.now() - start2);
27785
27787
  if (result === "timeout") {
27786
27788
  probes.push({
27787
27789
  operation: label,
27788
27790
  status: "timeout",
27789
- ms: OP_TIMEOUT
27791
+ ms: timeout
27790
27792
  });
27791
27793
  } else if (result === null || result === undefined) {
27792
27794
  probes.push({
@@ -27878,13 +27880,43 @@ class CodeIntelligenceRouter {
27878
27880
  }
27879
27881
  }
27880
27882
  const hasStandaloneProbe = backend.name === "lsp" && "probeStandalone" in backend && typeof backend.probeStandalone === "function";
27881
- if (hasStandaloneProbe && probeFile && !initError) {
27883
+ if (hasStandaloneProbe && !initError) {
27882
27884
  const lsp = backend;
27885
+ if (!probeFile) {
27886
+ updateBackend("lsp:nvim", {
27887
+ initialized: this.initialized.has(backend.name),
27888
+ initMs,
27889
+ initError,
27890
+ probes: fileOps.map(({
27891
+ label
27892
+ }) => ({
27893
+ operation: label,
27894
+ status: "empty",
27895
+ error: "no source file found in project"
27896
+ }))
27897
+ });
27898
+ updateBackend("lsp:standalone", {
27899
+ initialized: this.initialized.has(backend.name),
27900
+ initError,
27901
+ probes: fileOps.map(({
27902
+ label
27903
+ }) => ({
27904
+ operation: label,
27905
+ status: "empty",
27906
+ error: "no source file found in project"
27907
+ }))
27908
+ });
27909
+ continue;
27910
+ }
27883
27911
  if (lsp.warmupNvim) {
27884
- try {
27885
- await Promise.race([lsp.warmupNvim(probeFile), new Promise((r4) => setTimeout(() => r4(false), 25000))]);
27886
- } catch {}
27912
+ const nvimAvailable = lsp.isNvimAvailable?.() ?? false;
27913
+ if (nvimAvailable) {
27914
+ try {
27915
+ await Promise.race([lsp.warmupNvim(probeFile), new Promise((r4) => setTimeout(() => r4(false), 25000))]);
27916
+ } catch {}
27917
+ }
27887
27918
  }
27919
+ const IMPL_TIMEOUT = 20000;
27888
27920
  const nvimProbes = [];
27889
27921
  for (const {
27890
27922
  op,
@@ -27898,7 +27930,8 @@ class CodeIntelligenceRouter {
27898
27930
  });
27899
27931
  continue;
27900
27932
  }
27901
- await probeOp(() => fn(backend, probeFile), label, nvimProbes);
27933
+ const t = op === "findImplementation" ? IMPL_TIMEOUT : OP_TIMEOUT;
27934
+ await probeOp(() => fn(backend, probeFile), label, nvimProbes, t);
27902
27935
  }
27903
27936
  updateBackend("lsp:nvim", {
27904
27937
  initialized: this.initialized.has(backend.name),
@@ -27921,7 +27954,8 @@ class CodeIntelligenceRouter {
27921
27954
  });
27922
27955
  continue;
27923
27956
  }
27924
- await probeOp(() => lsp.probeStandalone(probeFile, op), label, standaloneProbes);
27957
+ const t = op === "findImplementation" ? IMPL_TIMEOUT : OP_TIMEOUT;
27958
+ await probeOp(() => lsp.probeStandalone(probeFile, op), label, standaloneProbes, t);
27925
27959
  }
27926
27960
  updateBackend("lsp:standalone", {
27927
27961
  initialized: true,
@@ -29567,7 +29601,7 @@ class StandaloneLspClient {
29567
29601
  }
29568
29602
  return;
29569
29603
  }
29570
- const languageId = this.config.language === "typescript" ? "typescript" : this.config.language === "javascript" ? "javascript" : this.config.language === "python" ? "python" : this.config.language === "go" ? "go" : this.config.language === "rust" ? "rust" : "plaintext";
29604
+ const languageId = LANGUAGE_ID_MAP[this.config.language] ?? this.config.language;
29571
29605
  this.notify("textDocument/didOpen", {
29572
29606
  textDocument: {
29573
29607
  uri,
@@ -29937,10 +29971,40 @@ function normalizeLocations(result) {
29937
29971
  }
29938
29972
  return [result];
29939
29973
  }
29974
+ var LANGUAGE_ID_MAP;
29940
29975
  var init_standalone_client = __esm(() => {
29941
29976
  init_errors();
29942
29977
  init_process_tracker();
29943
29978
  init_protocol();
29979
+ LANGUAGE_ID_MAP = {
29980
+ typescript: "typescript",
29981
+ javascript: "javascript",
29982
+ python: "python",
29983
+ go: "go",
29984
+ rust: "rust",
29985
+ java: "java",
29986
+ kotlin: "kotlin",
29987
+ scala: "scala",
29988
+ csharp: "csharp",
29989
+ swift: "swift",
29990
+ dart: "dart",
29991
+ elixir: "elixir",
29992
+ ocaml: "ocaml",
29993
+ lua: "lua",
29994
+ c: "c",
29995
+ cpp: "cpp",
29996
+ ruby: "ruby",
29997
+ php: "php",
29998
+ zig: "zig",
29999
+ bash: "shellscript",
30000
+ css: "css",
30001
+ html: "html",
30002
+ json: "json",
30003
+ toml: "toml",
30004
+ yaml: "yaml",
30005
+ dockerfile: "dockerfile",
30006
+ vue: "vue"
30007
+ };
29944
30008
  });
29945
30009
 
29946
30010
  // src/core/intelligence/backends/lsp/index.ts
@@ -29948,7 +30012,7 @@ var exports_lsp = {};
29948
30012
  __export(exports_lsp, {
29949
30013
  LspBackend: () => LspBackend
29950
30014
  });
29951
- import { existsSync as existsSync7 } from "fs";
30015
+ import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
29952
30016
  import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
29953
30017
  import { dirname as dirname3, join as join9, resolve as resolve5 } from "path";
29954
30018
  function getNvimBridge() {
@@ -30700,6 +30764,9 @@ class LspBackend {
30700
30764
  await new Promise((r4) => setTimeout(r4, 500));
30701
30765
  }
30702
30766
  }
30767
+ isNvimAvailable() {
30768
+ return nvimBridge.isNvimAvailable();
30769
+ }
30703
30770
  async warmupNvim(file) {
30704
30771
  if (!nvimBridge.isNvimAvailable())
30705
30772
  return false;
@@ -31106,8 +31173,22 @@ function findProjectRootForLanguage(file, language) {
31106
31173
  return null;
31107
31174
  let dir = dirname3(resolve5(file));
31108
31175
  const root2 = resolve5("/");
31109
- while (dir !== root2) {
31110
- for (const marker of markers) {
31176
+ const MAX_DIRS = 100;
31177
+ let dirCount = 0;
31178
+ while (dir !== root2 && dirCount < MAX_DIRS) {
31179
+ dirCount++;
31180
+ const extMarkers = markers.filter((m3) => m3.startsWith(".") && !m3.includes("/"));
31181
+ const nameMarkers = markers.filter((m3) => !m3.startsWith(".") || m3.includes("/"));
31182
+ if (extMarkers.length > 0) {
31183
+ try {
31184
+ const entries = readdirSync3(dir);
31185
+ for (const marker of extMarkers) {
31186
+ if (entries.some((e) => e.endsWith(marker)))
31187
+ return dir;
31188
+ }
31189
+ } catch {}
31190
+ }
31191
+ for (const marker of nameMarkers) {
31111
31192
  if (existsSync7(join9(dir, marker)))
31112
31193
  return dir;
31113
31194
  }
@@ -31139,15 +31220,24 @@ var init_lsp = __esm(() => {
31139
31220
  return mod[prop];
31140
31221
  }
31141
31222
  });
31142
- SUPPORTED_LANGUAGES = new Set(["typescript", "javascript", "python", "go", "rust", "lua", "c", "cpp", "ruby", "php", "zig", "bash", "css", "html", "json", "yaml", "dockerfile"]);
31223
+ SUPPORTED_LANGUAGES = new Set(["typescript", "javascript", "python", "go", "rust", "java", "kotlin", "scala", "csharp", "swift", "dart", "elixir", "ocaml", "lua", "c", "cpp", "ruby", "php", "zig", "bash", "css", "html", "json", "toml", "yaml", "dockerfile", "vue"]);
31143
31224
  PROJECT_FILES = {
31144
31225
  typescript: ["tsconfig.json"],
31145
31226
  javascript: ["jsconfig.json", "tsconfig.json"],
31146
31227
  python: ["pyproject.toml", "setup.py"],
31147
31228
  go: ["go.mod"],
31148
- rust: ["Cargo.toml"]
31229
+ rust: ["Cargo.toml"],
31230
+ java: ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"],
31231
+ kotlin: ["build.gradle.kts", "build.gradle", "settings.gradle.kts", "settings.gradle"],
31232
+ scala: ["build.sbt"],
31233
+ csharp: [".csproj", ".sln"],
31234
+ dart: ["pubspec.yaml"],
31235
+ elixir: ["mix.exs"],
31236
+ ruby: ["Gemfile"],
31237
+ php: ["composer.json"],
31238
+ swift: ["Package.swift"]
31149
31239
  };
31150
- DEFINITION_KEYWORDS = /\b(function|class|const|let|var|type|interface|enum|struct|trait|fn|def|func|impl|mod|pub)\b/;
31240
+ DEFINITION_KEYWORDS = /\b(function|class|const|let|var|type|interface|enum|struct|trait|fn|def|func|impl|mod|pub|public|private|protected|static|void|abstract|override|sealed|record|annotation|object|fun|suspend|data|inline|operator|infix|external|companion|lateinit|val)\b/;
31151
31241
  });
31152
31242
 
31153
31243
  // node_modules/@ts-morph/common/dist/typescript.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proxysoul/soulforge",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Graph-powered code intelligence — multi-agent coding with codebase-aware AI",
5
5
  "repository": {
6
6
  "type": "git",