@proxysoul/soulforge 2.15.6 → 2.16.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.
@@ -25911,67 +25911,70 @@ class RepoMap {
25911
25911
  async generateSemanticSummaries(maxSymbols = 500) {
25912
25912
  if (!this.summaryGenerator || !this.ready)
25913
25913
  return 0;
25914
- const topSymbols = this.db.query(`SELECT s.id AS sym_id, s.name, s.kind, s.signature, s.line, s.end_line,
25915
- f.path AS file_path, f.id AS file_id, f.mtime_ms AS file_mtime
25916
- FROM symbols s
25917
- JOIN files f ON f.id = s.file_id
25918
- WHERE s.is_exported = 1
25919
- AND s.kind IN ('function', 'method', 'class')
25920
- ORDER BY f.pagerank DESC, s.line ASC
25921
- LIMIT ?`).all(maxSymbols);
25922
- const existingById = new Map;
25923
- const existingByKey = new Map;
25924
- for (const row of this.db.query("SELECT symbol_id, file_mtime, file_path, symbol_name FROM semantic_summaries WHERE source = 'llm'").all()) {
25925
- existingById.set(row.symbol_id, row.file_mtime);
25926
- if (row.file_path && row.symbol_name) {
25927
- existingByKey.set(`${row.file_path}\x00${row.symbol_name}`, row.file_mtime);
25928
- }
25929
- }
25930
- const needed = [];
25931
- for (const sym of topSymbols) {
25932
- const cachedMtime = existingById.get(sym.sym_id) ?? existingByKey.get(`${sym.file_path}\x00${sym.name}`);
25933
- if (cachedMtime === sym.file_mtime)
25934
- continue;
25935
- const absPath = join6(this.cwd, sym.file_path);
25936
- let code = "";
25937
- let lineSpan = sym.end_line - sym.line;
25914
+ const PAGE_SIZE = 100;
25915
+ const BATCH_SIZE = 10;
25916
+ const SNIPPET_BUDGET = 2000;
25917
+ const BODY_SCAN_LIMIT = 80;
25918
+ const MIN_LINE_SPAN = 5;
25919
+ const candidates = this.db.query(`SELECT s.id AS sym_id, s.name, s.kind, s.signature, s.line, s.end_line,
25920
+ f.path AS file_path, f.mtime_ms AS file_mtime
25921
+ FROM symbols s
25922
+ JOIN files f ON f.id = s.file_id
25923
+ WHERE s.is_exported = 1
25924
+ AND s.kind IN ('function', 'method', 'class')
25925
+ ORDER BY f.pagerank DESC, s.line ASC
25926
+ LIMIT ? OFFSET ?`);
25927
+ const summaryByIdQuery = this.db.prepare("SELECT file_mtime FROM semantic_summaries WHERE source = 'llm' AND symbol_id = ?");
25928
+ const summaryByKeyQuery = this.db.prepare("SELECT file_mtime FROM semantic_summaries WHERE source = 'llm' AND file_path = ? AND symbol_name = ?");
25929
+ const upsert = this.db.prepare(`INSERT OR REPLACE INTO semantic_summaries (symbol_id, source, summary, file_mtime, file_path, symbol_name)
25930
+ VALUES (?, 'llm', ?, ?, ?, ?)`);
25931
+ const symExists = this.db.prepare("SELECT 1 FROM symbols WHERE id = ?");
25932
+ const prepareSymbol = (sym) => {
25933
+ const byId = summaryByIdQuery.get(sym.sym_id);
25934
+ if (byId && byId.file_mtime === sym.file_mtime)
25935
+ return null;
25936
+ if (!byId) {
25937
+ const byKey = summaryByKeyQuery.get(sym.file_path, sym.name);
25938
+ if (byKey && byKey.file_mtime === sym.file_mtime)
25939
+ return null;
25940
+ }
25941
+ let content;
25938
25942
  try {
25939
- const content = readFileSync3(absPath, "utf-8");
25940
- const lines = content.split(`
25943
+ content = readFileSync3(join6(this.cwd, sym.file_path), "utf-8");
25944
+ } catch {
25945
+ return null;
25946
+ }
25947
+ const lines = content.split(`
25941
25948
  `);
25942
- const startLine = Math.max(0, sym.line - 1);
25943
- let endLine = sym.end_line;
25944
- if (endLine <= sym.line) {
25945
- const limit = Math.min(startLine + 80, lines.length);
25946
- let depth = 0;
25947
- for (let k3 = startLine;k3 < limit; k3++) {
25948
- const l3 = lines[k3] ?? "";
25949
- for (const ch of l3) {
25950
- if (ch === "{" || ch === "(")
25951
- depth++;
25952
- else if (ch === "}" || ch === ")")
25953
- depth--;
25954
- }
25955
- if (depth <= 0 && k3 > startLine) {
25956
- endLine = k3 + 1;
25957
- break;
25958
- }
25949
+ const startLine = Math.max(0, sym.line - 1);
25950
+ let endLine = sym.end_line;
25951
+ if (endLine <= sym.line) {
25952
+ const limit = Math.min(startLine + BODY_SCAN_LIMIT, lines.length);
25953
+ let depth = 0;
25954
+ for (let k3 = startLine;k3 < limit; k3++) {
25955
+ const l3 = lines[k3] ?? "";
25956
+ for (const ch of l3) {
25957
+ if (ch === "{" || ch === "(")
25958
+ depth++;
25959
+ else if (ch === "}" || ch === ")")
25960
+ depth--;
25961
+ }
25962
+ if (depth <= 0 && k3 > startLine) {
25963
+ endLine = k3 + 1;
25964
+ break;
25959
25965
  }
25960
- if (endLine <= sym.line)
25961
- endLine = Math.min(startLine + 20, lines.length);
25962
25966
  }
25963
- endLine = Math.min(lines.length, endLine);
25964
- lineSpan = endLine - startLine;
25965
- if (lineSpan < 5)
25966
- continue;
25967
- const snippet = lines.slice(startLine, endLine).join(`
25968
- `);
25969
- code = snippet.length > 2000 ? `${snippet.slice(0, 2000)}...` : snippet;
25970
- } catch {
25971
- continue;
25967
+ if (endLine <= sym.line)
25968
+ endLine = Math.min(startLine + 20, lines.length);
25972
25969
  }
25973
- const dependents = this.getFileBlastRadius(sym.file_path);
25974
- needed.push({
25970
+ endLine = Math.min(lines.length, endLine);
25971
+ const lineSpan = endLine - startLine;
25972
+ if (lineSpan < MIN_LINE_SPAN)
25973
+ return null;
25974
+ const snippet = lines.slice(startLine, endLine).join(`
25975
+ `);
25976
+ const code = snippet.length > SNIPPET_BUDGET ? `${snippet.slice(0, SNIPPET_BUDGET)}...` : snippet;
25977
+ return {
25975
25978
  symId: sym.sym_id,
25976
25979
  name: sym.name,
25977
25980
  kind: sym.kind,
@@ -25979,19 +25982,13 @@ class RepoMap {
25979
25982
  code,
25980
25983
  filePath: sym.file_path,
25981
25984
  fileMtime: sym.file_mtime,
25982
- dependents,
25985
+ dependents: this.getFileBlastRadius(sym.file_path),
25983
25986
  lineSpan
25984
- });
25985
- }
25986
- if (needed.length === 0)
25987
- return 0;
25988
- const upsert = this.db.prepare(`INSERT OR REPLACE INTO semantic_summaries (symbol_id, source, summary, file_mtime, file_path, symbol_name)
25989
- VALUES (?, 'llm', ?, ?, ?, ?)`);
25990
- const symExists = this.db.prepare("SELECT 1 FROM symbols WHERE id = ?");
25991
- let count = 0;
25992
- const SAVE_CHUNK = 10;
25993
- for (let ci = 0;ci < needed.length; ci += SAVE_CHUNK) {
25994
- const chunk = needed.slice(ci, ci + SAVE_CHUNK);
25987
+ };
25988
+ };
25989
+ const flushChunk = async (chunk) => {
25990
+ if (!this.summaryGenerator || chunk.length === 0)
25991
+ return 0;
25995
25992
  const batch = chunk.map((s2) => ({
25996
25993
  name: s2.name,
25997
25994
  kind: s2.kind,
@@ -26001,25 +25998,65 @@ class RepoMap {
26001
25998
  dependents: s2.dependents,
26002
25999
  lineSpan: s2.lineSpan
26003
26000
  }));
26004
- const results = await this.summaryGenerator(batch, needed.length);
26001
+ const results = await this.summaryGenerator(batch, maxSymbols);
26005
26002
  if (results.length === 0)
26006
- break;
26007
- const summaryMap = new Map;
26008
- const summaryMapLower = new Map;
26003
+ return -1;
26004
+ const lookup = new Map;
26009
26005
  for (const r4 of results) {
26010
- summaryMap.set(r4.name, r4.summary);
26011
- summaryMapLower.set(r4.name.toLowerCase(), r4.summary);
26006
+ lookup.set(r4.name, r4.summary);
26007
+ const lc = r4.name.toLowerCase();
26008
+ if (!lookup.has(lc))
26009
+ lookup.set(lc, r4.summary);
26012
26010
  }
26011
+ let written = 0;
26013
26012
  const tx = this.db.transaction(() => {
26014
26013
  for (const sym of chunk) {
26015
- const summary = summaryMap.get(sym.name) ?? summaryMapLower.get(sym.name.toLowerCase());
26014
+ const summary = lookup.get(sym.name) ?? lookup.get(sym.name.toLowerCase());
26016
26015
  if (summary && symExists.get(sym.symId)) {
26017
26016
  upsert.run(sym.symId, summary, sym.fileMtime, sym.filePath, sym.name);
26018
- count++;
26017
+ written++;
26019
26018
  }
26020
26019
  }
26021
26020
  });
26022
26021
  tx();
26022
+ return written;
26023
+ };
26024
+ let count = 0;
26025
+ let offset = 0;
26026
+ let pending = [];
26027
+ let processed = 0;
26028
+ let aborted = false;
26029
+ outer:
26030
+ while (processed < maxSymbols) {
26031
+ const page = candidates.all(PAGE_SIZE, offset);
26032
+ if (page.length === 0)
26033
+ break;
26034
+ offset += page.length;
26035
+ for (const row of page) {
26036
+ if (processed >= maxSymbols)
26037
+ break outer;
26038
+ const prepared = prepareSymbol(row);
26039
+ if (!prepared)
26040
+ continue;
26041
+ pending.push(prepared);
26042
+ processed++;
26043
+ if (pending.length >= BATCH_SIZE) {
26044
+ const chunk = pending;
26045
+ pending = [];
26046
+ const written = await flushChunk(chunk);
26047
+ if (written < 0) {
26048
+ aborted = true;
26049
+ break outer;
26050
+ }
26051
+ count += written;
26052
+ }
26053
+ }
26054
+ }
26055
+ if (!aborted && pending.length > 0) {
26056
+ const written = await flushChunk(pending);
26057
+ if (written > 0)
26058
+ count += written;
26059
+ pending = [];
26023
26060
  }
26024
26061
  if (count > 0) {
26025
26062
  try {
@@ -1690,6 +1690,7 @@ __export(exports_status, {
1690
1690
  gitStash: () => gitStash,
1691
1691
  gitShow: () => gitShow,
1692
1692
  gitRestore: () => gitRestore,
1693
+ gitResetHard: () => gitResetHard,
1693
1694
  gitReset: () => gitReset,
1694
1695
  gitRebase: () => gitRebase,
1695
1696
  gitPush: () => gitPush,
@@ -2318,6 +2319,16 @@ async function buildGitContext(cwd) {
2318
2319
  return lines.join(`
2319
2320
  `);
2320
2321
  }
2322
+ async function gitResetHard(cwd) {
2323
+ const {
2324
+ ok,
2325
+ stdout
2326
+ } = await run(["reset", "--hard", "HEAD"], cwd);
2327
+ return {
2328
+ ok,
2329
+ output: stdout
2330
+ };
2331
+ }
2321
2332
  var encoder, NAMED_ESCAPES, CO_AUTHOR_LINE = "Co-Authored-By: SoulForge <soulforge@proxysoul.com>", _coAuthorEnabled = true;
2322
2333
  var init_status = __esm(() => {
2323
2334
  init_spawn();
@@ -30964,6 +30975,50 @@ var init_errors4 = __esm(() => {
30964
30975
  })));
30965
30976
  });
30966
30977
 
30978
+ // src/stores/model-events.ts
30979
+ function recordModelCall(event) {
30980
+ try {
30981
+ const s = useModelEventsStore.getState();
30982
+ if (!s.enabled)
30983
+ return;
30984
+ s.push(event);
30985
+ } catch {}
30986
+ }
30987
+ var MAX_EVENTS = 500, useModelEventsStore;
30988
+ var init_model_events = __esm(() => {
30989
+ init_esm();
30990
+ init_middleware();
30991
+ useModelEventsStore = create()(subscribeWithSelector((set2, get) => ({
30992
+ enabled: false,
30993
+ events: [],
30994
+ setEnabled: (v) => set2(v ? {
30995
+ enabled: true
30996
+ } : {
30997
+ enabled: false,
30998
+ events: []
30999
+ }),
31000
+ push: (event) => {
31001
+ if (!get().enabled)
31002
+ return null;
31003
+ const id = event.id ?? crypto.randomUUID();
31004
+ const full = {
31005
+ ...event,
31006
+ id
31007
+ };
31008
+ set2((s) => {
31009
+ const events = s.events.length >= MAX_EVENTS ? [...s.events.slice(-(MAX_EVENTS - 1)), full] : [...s.events, full];
31010
+ return {
31011
+ events
31012
+ };
31013
+ });
31014
+ return id;
31015
+ },
31016
+ clear: () => set2({
31017
+ events: []
31018
+ })
31019
+ })));
31020
+ });
31021
+
30967
31022
  // src/utils/errors.ts
30968
31023
  function toErrorMessage(err) {
30969
31024
  return err instanceof Error ? err.message : String(err);
@@ -30990,7 +31045,8 @@ function keychainGet(key) {
30990
31045
  if (process.platform === "darwin") {
30991
31046
  const result = spawnSync("security", ["find-generic-password", "-a", KEYCHAIN_SERVICE, "-s", key, "-w"], {
30992
31047
  timeout: 5000,
30993
- encoding: "utf-8"
31048
+ encoding: "utf-8",
31049
+ stdio: ["ignore", "pipe", "ignore"]
30994
31050
  });
30995
31051
  if (result.status === 0 && result.stdout) {
30996
31052
  return result.stdout.trim();
@@ -31000,7 +31056,8 @@ function keychainGet(key) {
31000
31056
  if (process.platform === "linux") {
31001
31057
  const result = spawnSync("secret-tool", ["lookup", "service", KEYCHAIN_SERVICE, "key", key], {
31002
31058
  timeout: 5000,
31003
- encoding: "utf-8"
31059
+ encoding: "utf-8",
31060
+ stdio: ["ignore", "pipe", "ignore"]
31004
31061
  });
31005
31062
  if (result.status === 0 && result.stdout) {
31006
31063
  return result.stdout.trim();
@@ -31052,8 +31109,9 @@ function getProviderApiKey(envVar, priority = _defaultPriority) {
31052
31109
  }
31053
31110
  return getEnv() ?? getApp();
31054
31111
  }
31055
- var SECRETS_DIR, SECRETS_FILE, KEYCHAIN_SERVICE = "soulforge", _defaultPriority = "env", STATIC_SECRETS, ENV_MAP, ENV_TO_SECRET;
31112
+ var _keychainHasCache, SECRETS_DIR, SECRETS_FILE, KEYCHAIN_SERVICE = "soulforge", _defaultPriority = "env", STATIC_SECRETS, ENV_MAP, ENV_TO_SECRET;
31056
31113
  var init_secrets = __esm(() => {
31114
+ _keychainHasCache = new Map;
31057
31115
  SECRETS_DIR = join3(homedir2(), ".soulforge");
31058
31116
  SECRETS_FILE = join3(SECRETS_DIR, "secrets.json");
31059
31117
  STATIC_SECRETS = {
@@ -42556,6 +42614,7 @@ var init_ui = __esm(() => {
42556
42614
  hearthSettings: false,
42557
42615
  tabNamePopup: false,
42558
42616
  memoryBrowser: false,
42617
+ modelEvents: false,
42559
42618
  uiDemo: false
42560
42619
  };
42561
42620
  useUIStore = create()(subscribeWithSelector((set2) => ({
@@ -42563,6 +42622,7 @@ var init_ui = __esm(() => {
42563
42622
  ...INITIAL_MODALS
42564
42623
  },
42565
42624
  routerSlotPicking: null,
42625
+ fallbackForModel: null,
42566
42626
  commandPickerConfig: null,
42567
42627
  infoPopupConfig: null,
42568
42628
  statusDashboardTab: "Context",
@@ -42574,7 +42634,8 @@ var init_ui = __esm(() => {
42574
42634
  reasoningExpanded: {},
42575
42635
  suspended: false,
42576
42636
  editorSplit: 60,
42577
- lockIn: true,
42637
+ verboseByTab: {},
42638
+ messageToolExpanded: {},
42578
42639
  openModal: (name21) => set2(() => ({
42579
42640
  modals: {
42580
42641
  ...INITIAL_MODALS,
@@ -42599,6 +42660,9 @@ var init_ui = __esm(() => {
42599
42660
  setRouterSlotPicking: (slot) => set2({
42600
42661
  routerSlotPicking: slot
42601
42662
  }),
42663
+ setFallbackForModel: (modelId) => set2({
42664
+ fallbackForModel: modelId
42665
+ }),
42602
42666
  openCommandPicker: (config2) => set2(() => ({
42603
42667
  commandPickerConfig: config2,
42604
42668
  modals: {
@@ -42684,12 +42748,66 @@ var init_ui = __esm(() => {
42684
42748
  setSuspended: (v) => set2({
42685
42749
  suspended: v
42686
42750
  }),
42687
- setLockIn: (v) => set2({
42688
- lockIn: v
42689
- }),
42690
- toggleLockIn: () => set2((s) => ({
42691
- lockIn: !s.lockIn
42751
+ setTabVerbose: (tabId, v) => set2((s) => ({
42752
+ verboseByTab: {
42753
+ ...s.verboseByTab,
42754
+ [tabId]: v
42755
+ }
42756
+ })),
42757
+ toggleTabVerbose: (tabId) => set2((s) => ({
42758
+ verboseByTab: {
42759
+ ...s.verboseByTab,
42760
+ [tabId]: !s.verboseByTab[tabId]
42761
+ }
42692
42762
  })),
42763
+ ensureTabVerboseDefault: (tabId, def) => set2((s) => {
42764
+ if (s.verboseByTab[tabId] !== undefined)
42765
+ return {};
42766
+ return {
42767
+ verboseByTab: {
42768
+ ...s.verboseByTab,
42769
+ [tabId]: def ?? false
42770
+ }
42771
+ };
42772
+ }),
42773
+ pruneTabVerbose: (tabId) => set2((s) => {
42774
+ const {
42775
+ [tabId]: _v,
42776
+ ...verboseByTab
42777
+ } = s.verboseByTab;
42778
+ return {
42779
+ verboseByTab
42780
+ };
42781
+ }),
42782
+ toggleMessageTool: (msgId, toolId) => set2((s) => {
42783
+ const cur = s.messageToolExpanded[msgId] ?? {};
42784
+ return {
42785
+ messageToolExpanded: {
42786
+ ...s.messageToolExpanded,
42787
+ [msgId]: {
42788
+ ...cur,
42789
+ [toolId]: !cur[toolId]
42790
+ }
42791
+ }
42792
+ };
42793
+ }),
42794
+ pruneMessageTools: (msgIds) => set2((s) => {
42795
+ if (msgIds.length === 0)
42796
+ return {};
42797
+ const next = {
42798
+ ...s.messageToolExpanded
42799
+ };
42800
+ let changed = false;
42801
+ for (const id of msgIds) {
42802
+ if (id in next) {
42803
+ delete next[id];
42804
+ changed = true;
42805
+ }
42806
+ }
42807
+ return changed ? {
42808
+ messageToolExpanded: next
42809
+ } : {};
42810
+ }),
42693
42811
  cycleEditorSplit: () => set2((s) => {
42694
42812
  const splits = [40, 50, 60, 70];
42695
42813
  const idx = splits.indexOf(s.editorSplit);
@@ -50459,7 +50577,7 @@ var package_default;
50459
50577
  var init_package = __esm(() => {
50460
50578
  package_default = {
50461
50579
  name: "@proxysoul/soulforge",
50462
- version: "2.15.6",
50580
+ version: "2.16.0",
50463
50581
  description: "Graph-powered code intelligence \u2014 multi-agent coding with codebase-aware AI",
50464
50582
  repository: {
50465
50583
  type: "git",
@@ -81683,6 +81801,8 @@ async function buildV2Summary(opts) {
81683
81801
  }
81684
81802
  let gapFill;
81685
81803
  let llmUsage;
81804
+ const v2StartedAt = Date.now();
81805
+ const v2ModelId = getModelId(model);
81686
81806
  try {
81687
81807
  const genResult = await generateText({
81688
81808
  model,
@@ -81714,8 +81834,27 @@ async function buildV2Summary(opts) {
81714
81834
  cacheWriteTokens: details?.cacheWriteTokens ?? 0
81715
81835
  };
81716
81836
  }
81837
+ recordModelCall({
81838
+ modelId: v2ModelId,
81839
+ source: "compaction",
81840
+ startedAt: v2StartedAt,
81841
+ durationMs: Math.max(0, Date.now() - v2StartedAt),
81842
+ state: "ok",
81843
+ input: llmUsage?.inputTokens ?? 0,
81844
+ output: llmUsage?.outputTokens ?? 0,
81845
+ cacheRead: llmUsage?.cacheReadTokens ?? 0,
81846
+ cacheWrite: llmUsage?.cacheWriteTokens ?? 0
81847
+ });
81717
81848
  } catch (err) {
81718
81849
  logBackgroundError("compaction-summarize", err instanceof Error ? err.message : String(err));
81850
+ recordModelCall({
81851
+ modelId: v2ModelId,
81852
+ source: "compaction",
81853
+ startedAt: v2StartedAt,
81854
+ durationMs: Math.max(0, Date.now() - v2StartedAt),
81855
+ state: "error",
81856
+ errorMessage: (err instanceof Error ? err.message : String(err)).slice(0, 500)
81857
+ });
81719
81858
  return {
81720
81859
  summary: structuredState
81721
81860
  };
@@ -81780,6 +81919,7 @@ function messageTextFull(msg) {
81780
81919
  var init_summarize = __esm(() => {
81781
81920
  init_dist5();
81782
81921
  init_errors4();
81922
+ init_model_events();
81783
81923
  init_provider_options();
81784
81924
  });
81785
81925
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proxysoul/soulforge",
3
- "version": "2.15.6",
3
+ "version": "2.16.0",
4
4
  "description": "Graph-powered code intelligence — multi-agent coding with codebase-aware AI",
5
5
  "repository": {
6
6
  "type": "git",