sparkecoder 0.1.24 → 0.1.26

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.
Files changed (96) hide show
  1. package/dist/agent/index.d.ts +3 -4
  2. package/dist/agent/index.js +670 -470
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +1990 -1022
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +145 -122
  7. package/dist/db/index.js +276 -646
  8. package/dist/db/index.js.map +1 -1
  9. package/dist/{index-CwQ-PrZw.d.ts → index-CWO1mYcx.d.ts} +87 -16
  10. package/dist/index.d.ts +6 -9
  11. package/dist/index.js +1026 -888
  12. package/dist/index.js.map +1 -1
  13. package/dist/schema-NcQknWCg.d.ts +295 -0
  14. package/dist/{search-S0REHtvA.d.ts → search-DApagzr3.d.ts} +5 -5
  15. package/dist/server/index.js +1026 -888
  16. package/dist/server/index.js.map +1 -1
  17. package/dist/tools/index.d.ts +38 -4
  18. package/dist/tools/index.js +569 -291
  19. package/dist/tools/index.js.map +1 -1
  20. package/package.json +1 -7
  21. package/web/.next/BUILD_ID +1 -1
  22. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  23. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  24. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  25. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  26. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  41. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  50. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  76. package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  78. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  84. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  85. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  86. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  87. package/dist/schema-BqpMl6XE.d.ts +0 -1614
  88. /package/web/.next/standalone/web/.next/static/{static/vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_buildManifest.js +0 -0
  89. /package/web/.next/standalone/web/.next/static/{static/vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_clientMiddlewareManifest.json +0 -0
  90. /package/web/.next/standalone/web/.next/static/{static/vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_ssgManifest.js +0 -0
  91. /package/web/.next/standalone/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → static/gnPGpNaKfNwMKujKqMYAv}/_buildManifest.js +0 -0
  92. /package/web/.next/standalone/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → static/gnPGpNaKfNwMKujKqMYAv}/_clientMiddlewareManifest.json +0 -0
  93. /package/web/.next/standalone/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → static/gnPGpNaKfNwMKujKqMYAv}/_ssgManifest.js +0 -0
  94. /package/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_buildManifest.js +0 -0
  95. /package/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_clientMiddlewareManifest.json +0 -0
  96. /package/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_ssgManifest.js +0 -0
@@ -55,6 +55,53 @@ var SessionConfigSchema = z.object({
55
55
  skillsDirectory: z.string().optional(),
56
56
  maxContextChars: z.number().optional().default(2e5)
57
57
  });
58
+ var VectorGatewayConfigSchema = z.object({
59
+ // Redis URL for Vector Gateway (or use VECTOR_REDIS_URL env var)
60
+ redisUrl: z.string().optional(),
61
+ // HTTP URL for database operations (or use VECTOR_HTTP_URL env var)
62
+ httpUrl: z.string().optional(),
63
+ // Embedding model to use (default: text-embedding-3-small)
64
+ embeddingModel: z.string().default("gemini-embedding-001"),
65
+ // Custom namespace override (auto-generated from git remote if not set)
66
+ namespace: z.string().optional(),
67
+ // File patterns to include in indexing
68
+ include: z.array(z.string()).optional().default([
69
+ "**/*.ts",
70
+ "**/*.tsx",
71
+ "**/*.js",
72
+ "**/*.jsx",
73
+ "**/*.py",
74
+ "**/*.go",
75
+ "**/*.rs",
76
+ "**/*.java",
77
+ "**/*.md",
78
+ "**/*.mdx",
79
+ "**/*.txt"
80
+ ]),
81
+ // File patterns to exclude from indexing
82
+ exclude: z.array(z.string()).optional().default([
83
+ "**/node_modules/**",
84
+ "**/dist/**",
85
+ "**/build/**",
86
+ "**/.git/**",
87
+ "**/.next/**",
88
+ "**/*.min.js",
89
+ "**/*.bundle.js",
90
+ "**/pnpm-lock.yaml",
91
+ "**/package-lock.json",
92
+ "**/yarn.lock",
93
+ "**/.test-workspace/**",
94
+ "**/.semantic-test-workspace/**",
95
+ "**/.semantic-integration-test/**"
96
+ ])
97
+ }).optional();
98
+ var RemoteServerConfigSchema = z.object({
99
+ // URL of the remote server (e.g., https://agent.sparkecode.com)
100
+ url: z.string().url().optional(),
101
+ // Auth key for the remote server (auto-generated on first use if not set)
102
+ // Can also be set via SPARKECODER_AUTH_KEY env var
103
+ authKey: z.string().optional()
104
+ }).optional();
58
105
  var SparkcoderConfigSchema = z.object({
59
106
  // Default model to use (Vercel AI Gateway format)
60
107
  defaultModel: z.string().default("anthropic/claude-opus-4-5"),
@@ -88,8 +135,13 @@ var SparkcoderConfigSchema = z.object({
88
135
  // If not set, defaults to http://{host}:{port}
89
136
  publicUrl: z.string().url().optional()
90
137
  }).default({ port: 3141, host: "127.0.0.1" }),
91
- // Database path
92
- databasePath: z.string().optional().default("./sparkecoder.db")
138
+ // Database path (used for local SQLite - ignored if remoteServer is configured)
139
+ databasePath: z.string().optional().default("./sparkecoder.db"),
140
+ // Remote server configuration (for centralized storage)
141
+ // If configured, uses remote MongoDB instead of local SQLite
142
+ remoteServer: RemoteServerConfigSchema,
143
+ // Vector Gateway configuration for semantic search
144
+ vectorGateway: VectorGatewayConfigSchema
93
145
  });
94
146
 
95
147
  // src/config/index.ts
@@ -104,6 +156,13 @@ function getAppDataDirectory() {
104
156
  return join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), appName);
105
157
  }
106
158
  }
159
+ var cachedConfig = null;
160
+ function getConfig() {
161
+ if (!cachedConfig) {
162
+ throw new Error("Config not loaded. Call loadConfig first.");
163
+ }
164
+ return cachedConfig;
165
+ }
107
166
  var PROVIDER_ENV_MAP = {
108
167
  anthropic: "ANTHROPIC_API_KEY",
109
168
  openai: "OPENAI_API_KEY",
@@ -716,323 +775,174 @@ import { resolve as resolve3, relative as relative2, dirname as dirname2 } from
716
775
  import { exec as exec3 } from "child_process";
717
776
  import { promisify as promisify3 } from "util";
718
777
 
719
- // src/db/index.ts
720
- import Database from "better-sqlite3";
721
- import { drizzle } from "drizzle-orm/better-sqlite3";
722
- import { eq, desc, and, sql } from "drizzle-orm";
723
- import { nanoid as nanoid2 } from "nanoid";
724
-
725
- // src/db/schema.ts
726
- import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
727
- var sessions = sqliteTable("sessions", {
728
- id: text("id").primaryKey(),
729
- name: text("name"),
730
- workingDirectory: text("working_directory").notNull(),
731
- model: text("model").notNull(),
732
- status: text("status", { enum: ["active", "waiting", "completed", "error"] }).notNull().default("active"),
733
- config: text("config", { mode: "json" }).$type(),
734
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
735
- updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
736
- });
737
- var messages = sqliteTable("messages", {
738
- id: text("id").primaryKey(),
739
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
740
- // Store the entire ModelMessage as JSON (role + content)
741
- modelMessage: text("model_message", { mode: "json" }).$type().notNull(),
742
- // Sequence number within session to maintain exact ordering
743
- sequence: integer("sequence").notNull().default(0),
744
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
745
- });
746
- var toolExecutions = sqliteTable("tool_executions", {
747
- id: text("id").primaryKey(),
748
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
749
- messageId: text("message_id").references(() => messages.id, { onDelete: "cascade" }),
750
- toolName: text("tool_name").notNull(),
751
- toolCallId: text("tool_call_id").notNull(),
752
- input: text("input", { mode: "json" }),
753
- output: text("output", { mode: "json" }),
754
- status: text("status", { enum: ["pending", "approved", "rejected", "completed", "error"] }).notNull().default("pending"),
755
- requiresApproval: integer("requires_approval", { mode: "boolean" }).notNull().default(false),
756
- error: text("error"),
757
- startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
758
- completedAt: integer("completed_at", { mode: "timestamp" })
759
- });
760
- var todoItems = sqliteTable("todo_items", {
761
- id: text("id").primaryKey(),
762
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
763
- content: text("content").notNull(),
764
- status: text("status", { enum: ["pending", "in_progress", "completed", "cancelled"] }).notNull().default("pending"),
765
- order: integer("order").notNull().default(0),
766
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
767
- updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
768
- });
769
- var loadedSkills = sqliteTable("loaded_skills", {
770
- id: text("id").primaryKey(),
771
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
772
- skillName: text("skill_name").notNull(),
773
- loadedAt: integer("loaded_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
774
- });
775
- var terminals = sqliteTable("terminals", {
776
- id: text("id").primaryKey(),
777
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
778
- name: text("name"),
779
- // Optional friendly name (e.g., "dev-server")
780
- command: text("command").notNull(),
781
- // The command that was run
782
- cwd: text("cwd").notNull(),
783
- // Working directory
784
- pid: integer("pid"),
785
- // Process ID (null if not running)
786
- status: text("status", { enum: ["running", "stopped", "error"] }).notNull().default("running"),
787
- exitCode: integer("exit_code"),
788
- // Exit code if stopped
789
- error: text("error"),
790
- // Error message if status is 'error'
791
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
792
- stoppedAt: integer("stopped_at", { mode: "timestamp" })
793
- });
794
- var activeStreams = sqliteTable("active_streams", {
795
- id: text("id").primaryKey(),
796
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
797
- streamId: text("stream_id").notNull().unique(),
798
- // Unique stream identifier
799
- status: text("status", { enum: ["active", "finished", "error"] }).notNull().default("active"),
800
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
801
- finishedAt: integer("finished_at", { mode: "timestamp" })
802
- });
803
- var checkpoints = sqliteTable("checkpoints", {
804
- id: text("id").primaryKey(),
805
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
806
- // The message sequence number this checkpoint was created BEFORE
807
- // (i.e., the state before this user message was processed)
808
- messageSequence: integer("message_sequence").notNull(),
809
- // Optional git commit hash if in a git repo
810
- gitHead: text("git_head"),
811
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
812
- });
813
- var fileBackups = sqliteTable("file_backups", {
814
- id: text("id").primaryKey(),
815
- checkpointId: text("checkpoint_id").notNull().references(() => checkpoints.id, { onDelete: "cascade" }),
816
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
817
- // Relative path from working directory
818
- filePath: text("file_path").notNull(),
819
- // Original content (null means file didn't exist before)
820
- originalContent: text("original_content"),
821
- // Whether the file existed before this checkpoint
822
- existed: integer("existed", { mode: "boolean" }).notNull().default(true),
823
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
824
- });
825
- var subagentExecutions = sqliteTable("subagent_executions", {
826
- id: text("id").primaryKey(),
827
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
828
- toolCallId: text("tool_call_id").notNull(),
829
- // The tool call that spawned this subagent
830
- subagentType: text("subagent_type").notNull(),
831
- // e.g., 'search', 'analyze', etc.
832
- task: text("task").notNull(),
833
- // The task/query given to the subagent
834
- model: text("model").notNull(),
835
- // The model used (e.g., 'gemini-2.0-flash')
836
- status: text("status", { enum: ["running", "completed", "error", "cancelled"] }).notNull().default("running"),
837
- // Steps taken by the subagent (stored as JSON array)
838
- steps: text("steps", { mode: "json" }).$type().default([]),
839
- // Final result/output
840
- result: text("result", { mode: "json" }),
841
- error: text("error"),
842
- startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
843
- completedAt: integer("completed_at", { mode: "timestamp" })
844
- });
845
-
846
- // src/db/index.ts
847
- var db = null;
848
- function getDb() {
849
- if (!db) {
850
- throw new Error("Database not initialized. Call initDatabase first.");
778
+ // src/db/remote.ts
779
+ var remoteServerUrl = null;
780
+ var authKey = null;
781
+ var DATE_FIELDS = ["createdAt", "updatedAt", "startedAt", "completedAt", "stoppedAt", "finishedAt", "loadedAt", "indexedAt", "lastFullIndex", "lastIncrementalIndex"];
782
+ function parseDates(obj) {
783
+ if (obj === null || obj === void 0) return obj;
784
+ if (Array.isArray(obj)) return obj.map(parseDates);
785
+ if (typeof obj !== "object") return obj;
786
+ const result = { ...obj };
787
+ for (const key of Object.keys(result)) {
788
+ if (DATE_FIELDS.includes(key) && typeof result[key] === "string") {
789
+ result[key] = new Date(result[key]);
790
+ } else if (typeof result[key] === "object") {
791
+ result[key] = parseDates(result[key]);
792
+ }
851
793
  }
852
- return db;
794
+ return result;
853
795
  }
854
- var todoQueries = {
796
+ async function api(path, options = {}) {
797
+ if (!remoteServerUrl || !authKey) {
798
+ throw new Error("Remote database not initialized");
799
+ }
800
+ const url = `${remoteServerUrl}/db${path}`;
801
+ const init = {
802
+ method: options.method || "GET",
803
+ headers: {
804
+ "Content-Type": "application/json",
805
+ "Authorization": `Bearer ${authKey}`
806
+ }
807
+ };
808
+ if (options.body) {
809
+ init.body = JSON.stringify(options.body);
810
+ }
811
+ const response = await fetch(url, init);
812
+ if (!response.ok) {
813
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
814
+ throw new Error(error.error || `HTTP ${response.status}`);
815
+ }
816
+ const text = await response.text();
817
+ if (!text || text === "null") {
818
+ return null;
819
+ }
820
+ return parseDates(JSON.parse(text));
821
+ }
822
+ var remoteTodoQueries = {
855
823
  create(data) {
856
- const id = nanoid2();
857
- const now = /* @__PURE__ */ new Date();
858
- const result = getDb().insert(todoItems).values({
859
- id,
860
- ...data,
861
- createdAt: now,
862
- updatedAt: now
863
- }).returning().get();
864
- return result;
824
+ return api("/todos", { method: "POST", body: data });
865
825
  },
866
826
  createMany(sessionId, items) {
867
- const now = /* @__PURE__ */ new Date();
868
- const values = items.map((item, index) => ({
869
- id: nanoid2(),
870
- sessionId,
871
- content: item.content,
872
- order: item.order ?? index,
873
- createdAt: now,
874
- updatedAt: now
875
- }));
876
- return getDb().insert(todoItems).values(values).returning().all();
827
+ return api("/todos/batch", { method: "POST", body: { sessionId, items } });
877
828
  },
878
829
  getBySession(sessionId) {
879
- return getDb().select().from(todoItems).where(eq(todoItems.sessionId, sessionId)).orderBy(todoItems.order).all();
830
+ return api(`/todos/session/${sessionId}`);
880
831
  },
881
832
  updateStatus(id, status) {
882
- return getDb().update(todoItems).set({ status, updatedAt: /* @__PURE__ */ new Date() }).where(eq(todoItems.id, id)).returning().get();
833
+ return api(`/todos/${id}`, { method: "PATCH", body: { status } });
883
834
  },
884
- delete(id) {
885
- const result = getDb().delete(todoItems).where(eq(todoItems.id, id)).run();
886
- return result.changes > 0;
835
+ async delete(id) {
836
+ const result = await api(`/todos/${id}`, { method: "DELETE" });
837
+ return result?.success ?? false;
887
838
  },
888
- clearSession(sessionId) {
889
- const result = getDb().delete(todoItems).where(eq(todoItems.sessionId, sessionId)).run();
890
- return result.changes;
839
+ async clearSession(sessionId) {
840
+ const result = await api(`/todos/session/${sessionId}`, { method: "DELETE" });
841
+ return result.deleted;
891
842
  }
892
843
  };
893
- var skillQueries = {
844
+ var remoteSkillQueries = {
894
845
  load(sessionId, skillName) {
895
- const id = nanoid2();
896
- const result = getDb().insert(loadedSkills).values({
897
- id,
898
- sessionId,
899
- skillName,
900
- loadedAt: /* @__PURE__ */ new Date()
901
- }).returning().get();
902
- return result;
846
+ return api("/skills", { method: "POST", body: { sessionId, skillName } });
903
847
  },
904
848
  getBySession(sessionId) {
905
- return getDb().select().from(loadedSkills).where(eq(loadedSkills.sessionId, sessionId)).orderBy(loadedSkills.loadedAt).all();
849
+ return api(`/skills/session/${sessionId}`);
906
850
  },
907
- isLoaded(sessionId, skillName) {
908
- const result = getDb().select().from(loadedSkills).where(
909
- and(
910
- eq(loadedSkills.sessionId, sessionId),
911
- eq(loadedSkills.skillName, skillName)
912
- )
913
- ).get();
914
- return !!result;
851
+ async isLoaded(sessionId, skillName) {
852
+ const result = await api(`/skills/session/${sessionId}/is-loaded/${skillName}`);
853
+ return result.isLoaded;
915
854
  }
916
855
  };
917
- var fileBackupQueries = {
856
+ var remoteFileBackupQueries = {
918
857
  create(data) {
919
- const id = nanoid2();
920
- const result = getDb().insert(fileBackups).values({
921
- id,
922
- checkpointId: data.checkpointId,
923
- sessionId: data.sessionId,
924
- filePath: data.filePath,
925
- originalContent: data.originalContent,
926
- existed: data.existed,
927
- createdAt: /* @__PURE__ */ new Date()
928
- }).returning().get();
929
- return result;
858
+ return api("/file-backups", { method: "POST", body: data });
930
859
  },
931
860
  getByCheckpoint(checkpointId) {
932
- return getDb().select().from(fileBackups).where(eq(fileBackups.checkpointId, checkpointId)).all();
861
+ return api(`/file-backups/checkpoint/${checkpointId}`);
933
862
  },
934
863
  getBySession(sessionId) {
935
- return getDb().select().from(fileBackups).where(eq(fileBackups.sessionId, sessionId)).orderBy(fileBackups.createdAt).all();
864
+ return api(`/file-backups/session/${sessionId}`);
936
865
  },
937
- /**
938
- * Get all file backups from a given checkpoint sequence onwards (inclusive)
939
- * (Used when reverting - need to restore these files)
940
- *
941
- * When reverting to checkpoint X, we need backups from checkpoint X and all later ones
942
- * because checkpoint X's backups represent the state BEFORE processing message X.
943
- */
944
866
  getFromSequence(sessionId, messageSequence) {
945
- const checkpointsFrom = getDb().select().from(checkpoints).where(
946
- and(
947
- eq(checkpoints.sessionId, sessionId),
948
- sql`message_sequence >= ${messageSequence}`
949
- )
950
- ).all();
951
- if (checkpointsFrom.length === 0) {
952
- return [];
953
- }
954
- const checkpointIds = checkpointsFrom.map((c) => c.id);
955
- const allBackups = [];
956
- for (const cpId of checkpointIds) {
957
- const backups = getDb().select().from(fileBackups).where(eq(fileBackups.checkpointId, cpId)).all();
958
- allBackups.push(...backups);
959
- }
960
- return allBackups;
867
+ return api(`/file-backups/session/${sessionId}/from-sequence/${messageSequence}`);
961
868
  },
962
- /**
963
- * Check if a file already has a backup in the current checkpoint
964
- */
965
- hasBackup(checkpointId, filePath) {
966
- const result = getDb().select().from(fileBackups).where(
967
- and(
968
- eq(fileBackups.checkpointId, checkpointId),
969
- eq(fileBackups.filePath, filePath)
970
- )
971
- ).get();
972
- return !!result;
869
+ async hasBackup(checkpointId, filePath) {
870
+ const result = await api(
871
+ `/file-backups/checkpoint/${checkpointId}/has-backup/${encodeURIComponent(filePath)}`
872
+ );
873
+ return result.hasBackup;
973
874
  },
974
- deleteBySession(sessionId) {
975
- const result = getDb().delete(fileBackups).where(eq(fileBackups.sessionId, sessionId)).run();
976
- return result.changes;
875
+ async deleteBySession(sessionId) {
876
+ const result = await api(`/file-backups/session/${sessionId}`, { method: "DELETE" });
877
+ return result.deleted;
977
878
  }
978
879
  };
979
- var subagentQueries = {
880
+ var remoteSubagentQueries = {
980
881
  create(data) {
981
- const id = nanoid2();
982
- const result = getDb().insert(subagentExecutions).values({
983
- id,
984
- sessionId: data.sessionId,
985
- toolCallId: data.toolCallId,
986
- subagentType: data.subagentType,
987
- task: data.task,
988
- model: data.model,
989
- status: "running",
990
- steps: [],
991
- startedAt: /* @__PURE__ */ new Date()
992
- }).returning().get();
993
- return result;
882
+ return api("/subagents", { method: "POST", body: data });
994
883
  },
995
884
  getById(id) {
996
- return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.id, id)).get();
885
+ return api(`/subagents/${id}`).catch(() => void 0);
997
886
  },
998
887
  getByToolCallId(toolCallId) {
999
- return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.toolCallId, toolCallId)).get();
888
+ return api(`/subagents/by-tool-call-id/${toolCallId}`).catch(() => void 0);
1000
889
  },
1001
890
  getBySession(sessionId) {
1002
- return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.sessionId, sessionId)).orderBy(desc(subagentExecutions.startedAt)).all();
891
+ return api(`/subagents/session/${sessionId}`);
1003
892
  },
1004
893
  addStep(id, step) {
1005
- const existing = this.getById(id);
1006
- if (!existing) return void 0;
1007
- const currentSteps = existing.steps || [];
1008
- const newSteps = [...currentSteps, step];
1009
- return getDb().update(subagentExecutions).set({ steps: newSteps }).where(eq(subagentExecutions.id, id)).returning().get();
894
+ return api(`/subagents/${id}/add-step`, { method: "POST", body: { step } }).catch(() => void 0);
1010
895
  },
1011
896
  complete(id, result) {
1012
- return getDb().update(subagentExecutions).set({
1013
- status: "completed",
1014
- result,
1015
- completedAt: /* @__PURE__ */ new Date()
1016
- }).where(eq(subagentExecutions.id, id)).returning().get();
897
+ return api(`/subagents/${id}`, { method: "PATCH", body: { status: "completed", result } }).catch(() => void 0);
1017
898
  },
1018
899
  markError(id, error) {
1019
- return getDb().update(subagentExecutions).set({
1020
- status: "error",
1021
- error,
1022
- completedAt: /* @__PURE__ */ new Date()
1023
- }).where(eq(subagentExecutions.id, id)).returning().get();
900
+ return api(`/subagents/${id}`, { method: "PATCH", body: { status: "error", error } }).catch(() => void 0);
1024
901
  },
1025
902
  cancel(id) {
1026
- return getDb().update(subagentExecutions).set({
1027
- status: "cancelled",
1028
- completedAt: /* @__PURE__ */ new Date()
1029
- }).where(eq(subagentExecutions.id, id)).returning().get();
903
+ return api(`/subagents/${id}`, { method: "PATCH", body: { status: "cancelled" } }).catch(() => void 0);
1030
904
  },
1031
- deleteBySession(sessionId) {
1032
- const result = getDb().delete(subagentExecutions).where(eq(subagentExecutions.sessionId, sessionId)).run();
1033
- return result.changes;
905
+ async deleteBySession(sessionId) {
906
+ const result = await api(`/subagents/session/${sessionId}`, { method: "DELETE" });
907
+ return result.deleted;
1034
908
  }
1035
909
  };
910
+ var remoteIndexStatusQueries = {
911
+ upsert(_db, data) {
912
+ return api("/index-status", {
913
+ method: "POST",
914
+ body: {
915
+ ...data,
916
+ lastFullIndex: data.lastFullIndex?.toISOString(),
917
+ lastIncrementalIndex: data.lastIncrementalIndex?.toISOString()
918
+ }
919
+ });
920
+ },
921
+ get(_db, namespace) {
922
+ return api(`/index-status/namespace/${namespace}`).then((r) => r ?? void 0);
923
+ },
924
+ async delete(_db, namespace) {
925
+ const result = await api(`/index-status/namespace/${namespace}`, { method: "DELETE" });
926
+ return result?.success ?? false;
927
+ },
928
+ list(_db) {
929
+ return api("/index-status");
930
+ }
931
+ };
932
+
933
+ // src/db/index.ts
934
+ var initialized = false;
935
+ function getDb() {
936
+ if (!initialized) {
937
+ throw new Error("Database not initialized. Call initDatabase first.");
938
+ }
939
+ return {};
940
+ }
941
+ var todoQueries = remoteTodoQueries;
942
+ var skillQueries = remoteSkillQueries;
943
+ var fileBackupQueries = remoteFileBackupQueries;
944
+ var subagentQueries = remoteSubagentQueries;
945
+ var indexStatusQueries = remoteIndexStatusQueries;
1036
946
 
1037
947
  // src/checkpoints/index.ts
1038
948
  var execAsync3 = promisify3(exec3);
@@ -1057,7 +967,7 @@ async function backupFile(sessionId, workingDirectory, filePath) {
1057
967
  }
1058
968
  const absolutePath = resolve3(workingDirectory, filePath);
1059
969
  const relativePath = relative2(workingDirectory, absolutePath);
1060
- if (fileBackupQueries.hasBackup(manager.currentCheckpointId, relativePath)) {
970
+ if (await fileBackupQueries.hasBackup(manager.currentCheckpointId, relativePath)) {
1061
971
  return null;
1062
972
  }
1063
973
  let originalContent = null;
@@ -1070,7 +980,7 @@ async function backupFile(sessionId, workingDirectory, filePath) {
1070
980
  console.warn(`[checkpoint] Failed to read file for backup: ${error.message}`);
1071
981
  }
1072
982
  }
1073
- const backup = fileBackupQueries.create({
983
+ const backup = await fileBackupQueries.create({
1074
984
  checkpointId: manager.currentCheckpointId,
1075
985
  sessionId,
1076
986
  filePath: relativePath,
@@ -1827,7 +1737,7 @@ Best practices:
1827
1737
  error: "No items provided. Include at least one todo item."
1828
1738
  };
1829
1739
  }
1830
- const created = todoQueries.createMany(options.sessionId, items);
1740
+ const created = await todoQueries.createMany(options.sessionId, items);
1831
1741
  return {
1832
1742
  success: true,
1833
1743
  action: "add",
@@ -1836,7 +1746,7 @@ Best practices:
1836
1746
  };
1837
1747
  }
1838
1748
  case "list": {
1839
- const todos = todoQueries.getBySession(options.sessionId);
1749
+ const todos = await todoQueries.getBySession(options.sessionId);
1840
1750
  const stats = {
1841
1751
  total: todos.length,
1842
1752
  pending: todos.filter((t) => t.status === "pending").length,
@@ -1864,7 +1774,7 @@ Best practices:
1864
1774
  error: 'status is required for "mark" action'
1865
1775
  };
1866
1776
  }
1867
- const updated = todoQueries.updateStatus(todoId, status);
1777
+ const updated = await todoQueries.updateStatus(todoId, status);
1868
1778
  if (!updated) {
1869
1779
  return {
1870
1780
  success: false,
@@ -1878,7 +1788,7 @@ Best practices:
1878
1788
  };
1879
1789
  }
1880
1790
  case "clear": {
1881
- const count = todoQueries.clearSession(options.sessionId);
1791
+ const count = await todoQueries.clearSession(options.sessionId);
1882
1792
  return {
1883
1793
  success: true,
1884
1794
  action: "clear",
@@ -2130,7 +2040,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
2130
2040
  error: 'skillName is required for "load" action'
2131
2041
  };
2132
2042
  }
2133
- if (skillQueries.isLoaded(options.sessionId, skillName)) {
2043
+ if (await skillQueries.isLoaded(options.sessionId, skillName)) {
2134
2044
  return {
2135
2045
  success: false,
2136
2046
  error: `Skill "${skillName}" is already loaded in this session`
@@ -2145,7 +2055,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
2145
2055
  availableSkills: allSkills.map((s) => s.name)
2146
2056
  };
2147
2057
  }
2148
- skillQueries.load(options.sessionId, skillName);
2058
+ await skillQueries.load(options.sessionId, skillName);
2149
2059
  return {
2150
2060
  success: true,
2151
2061
  action: "load",
@@ -2373,7 +2283,7 @@ import {
2373
2283
  generateText,
2374
2284
  stepCountIs
2375
2285
  } from "ai";
2376
- import { nanoid as nanoid3 } from "nanoid";
2286
+ import { nanoid as nanoid2 } from "nanoid";
2377
2287
 
2378
2288
  // src/agent/model.ts
2379
2289
  import { gateway } from "@ai-sdk/gateway";
@@ -2399,8 +2309,8 @@ var Subagent = class {
2399
2309
  * Parse the final result from the subagent's output.
2400
2310
  * Override this to structure the result for your subagent type.
2401
2311
  */
2402
- parseResult(text2, steps) {
2403
- return { text: text2, steps };
2312
+ parseResult(text, steps) {
2313
+ return { text, steps };
2404
2314
  }
2405
2315
  /**
2406
2316
  * Run the subagent with streaming progress updates
@@ -2408,7 +2318,7 @@ var Subagent = class {
2408
2318
  async run(options) {
2409
2319
  const { task, sessionId, toolCallId, onProgress, abortSignal } = options;
2410
2320
  const steps = [];
2411
- const execution = subagentQueries.create({
2321
+ const execution = await subagentQueries.create({
2412
2322
  sessionId,
2413
2323
  toolCallId,
2414
2324
  subagentType: this.type,
@@ -2417,12 +2327,12 @@ var Subagent = class {
2417
2327
  });
2418
2328
  const addStep = async (step) => {
2419
2329
  const fullStep = {
2420
- id: nanoid3(8),
2330
+ id: nanoid2(8),
2421
2331
  timestamp: Date.now(),
2422
2332
  ...step
2423
2333
  };
2424
2334
  steps.push(fullStep);
2425
- subagentQueries.addStep(execution.id, fullStep);
2335
+ await subagentQueries.addStep(execution.id, fullStep);
2426
2336
  await onProgress?.({
2427
2337
  type: "step",
2428
2338
  subagentId: execution.id,
@@ -2492,7 +2402,7 @@ var Subagent = class {
2492
2402
  }
2493
2403
  });
2494
2404
  const parsedResult = this.parseResult(result.text, steps);
2495
- subagentQueries.complete(execution.id, parsedResult);
2405
+ await subagentQueries.complete(execution.id, parsedResult);
2496
2406
  await onProgress?.({
2497
2407
  type: "complete",
2498
2408
  subagentId: execution.id,
@@ -2507,7 +2417,7 @@ var Subagent = class {
2507
2417
  };
2508
2418
  } catch (error) {
2509
2419
  const errorMessage = error.message || "Unknown error";
2510
- subagentQueries.markError(execution.id, errorMessage);
2420
+ await subagentQueries.markError(execution.id, errorMessage);
2511
2421
  await onProgress?.({
2512
2422
  type: "error",
2513
2423
  subagentId: execution.id,
@@ -2817,7 +2727,7 @@ Keep your responses concise and focused on actionable information.`;
2817
2727
  })
2818
2728
  };
2819
2729
  }
2820
- parseResult(text2, steps) {
2730
+ parseResult(text, steps) {
2821
2731
  const findings = [];
2822
2732
  let filesSearched = 0;
2823
2733
  let matchCount = 0;
@@ -2861,7 +2771,7 @@ Keep your responses concise and focused on actionable information.`;
2861
2771
  const query = steps.length > 0 ? steps.find((s) => s.type === "text")?.content || "" : "";
2862
2772
  return {
2863
2773
  query,
2864
- summary: text2,
2774
+ summary: text,
2865
2775
  findings: findings.slice(0, 20),
2866
2776
  // Limit findings
2867
2777
  filesSearched,
@@ -2992,9 +2902,362 @@ Context: ${context}` : query;
2992
2902
  });
2993
2903
  }
2994
2904
 
2905
+ // src/tools/semantic-search.ts
2906
+ import { tool as tool9 } from "ai";
2907
+ import { z as z10 } from "zod";
2908
+ import { existsSync as existsSync11, readFileSync as readFileSync4 } from "fs";
2909
+ import { join as join5 } from "path";
2910
+ import { minimatch as minimatch3 } from "minimatch";
2911
+
2912
+ // src/semantic/namespace.ts
2913
+ import { execSync } from "child_process";
2914
+ function getGitRemoteUrl(workingDirectory) {
2915
+ try {
2916
+ const result = execSync("git remote get-url origin", {
2917
+ cwd: workingDirectory,
2918
+ encoding: "utf-8",
2919
+ stdio: ["pipe", "pipe", "pipe"]
2920
+ });
2921
+ return result.trim();
2922
+ } catch {
2923
+ return null;
2924
+ }
2925
+ }
2926
+ function parseGitRemoteUrl(url) {
2927
+ const cleanUrl = url.replace(/\.git$/, "");
2928
+ const sshMatch = cleanUrl.match(/git@[^:]+:([^/]+)\/(.+)$/);
2929
+ if (sshMatch) {
2930
+ return { org: sshMatch[1], repo: sshMatch[2] };
2931
+ }
2932
+ const httpsMatch = cleanUrl.match(/https?:\/\/[^/]+\/([^/]+)\/(.+)$/);
2933
+ if (httpsMatch) {
2934
+ return { org: httpsMatch[1], repo: httpsMatch[2] };
2935
+ }
2936
+ const sshProtoMatch = cleanUrl.match(/ssh:\/\/[^/]+\/([^/]+)\/(.+)$/);
2937
+ if (sshProtoMatch) {
2938
+ return { org: sshProtoMatch[1], repo: sshProtoMatch[2] };
2939
+ }
2940
+ return null;
2941
+ }
2942
+ function sanitizeForNamespace(str) {
2943
+ return str.toLowerCase().replace(/[^a-z0-9]/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
2944
+ }
2945
+ async function getRepoNamespace(workingDirectory, configuredNamespace) {
2946
+ if (configuredNamespace) {
2947
+ return configuredNamespace;
2948
+ }
2949
+ const remoteUrl = getGitRemoteUrl(workingDirectory);
2950
+ if (!remoteUrl) {
2951
+ return null;
2952
+ }
2953
+ const parsed = parseGitRemoteUrl(remoteUrl);
2954
+ if (!parsed) {
2955
+ return null;
2956
+ }
2957
+ const org = sanitizeForNamespace(parsed.org);
2958
+ const repo = sanitizeForNamespace(parsed.repo);
2959
+ return `sparkecoder_${org}_${repo}`;
2960
+ }
2961
+
2962
+ // src/semantic/hasher.ts
2963
+ import { createHash } from "crypto";
2964
+
2965
+ // src/semantic/chunker.ts
2966
+ import { extname as extname6, basename as basename2 } from "path";
2967
+
2968
+ // src/semantic/client.ts
2969
+ var remoteServerUrl2 = null;
2970
+ var authKey2 = null;
2971
+ function initVectorClient(serverUrl, key) {
2972
+ remoteServerUrl2 = serverUrl.replace(/\/$/, "");
2973
+ authKey2 = key;
2974
+ }
2975
+ function isVectorClientConfigured() {
2976
+ return !!remoteServerUrl2 && !!authKey2;
2977
+ }
2978
+ async function vectorApi(path, options = {}) {
2979
+ if (!remoteServerUrl2 || !authKey2) {
2980
+ throw new Error("Vector client not initialized - remote server not configured");
2981
+ }
2982
+ const url = `${remoteServerUrl2}/vectors${path}`;
2983
+ const init = {
2984
+ method: options.method || "GET",
2985
+ headers: {
2986
+ "Content-Type": "application/json",
2987
+ "Authorization": `Bearer ${authKey2}`
2988
+ }
2989
+ };
2990
+ if (options.body) {
2991
+ init.body = JSON.stringify(options.body);
2992
+ }
2993
+ const response = await fetch(url, init);
2994
+ if (!response.ok) {
2995
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
2996
+ throw new Error(error.error || `HTTP ${response.status}`);
2997
+ }
2998
+ return response.json();
2999
+ }
3000
+ var remoteVectorClient = {
3001
+ embeddings: {
3002
+ /**
3003
+ * Create embeddings and store in vector DB
3004
+ */
3005
+ async createAndWait(texts, options) {
3006
+ return vectorApi("/embed", {
3007
+ method: "POST",
3008
+ body: {
3009
+ texts,
3010
+ namespace: options.namespace,
3011
+ embeddingModel: options.embeddingModel
3012
+ }
3013
+ });
3014
+ }
3015
+ },
3016
+ search: {
3017
+ /**
3018
+ * Query vectors using semantic search
3019
+ */
3020
+ async queryAndWait(query, options) {
3021
+ return vectorApi("/search", {
3022
+ method: "POST",
3023
+ body: {
3024
+ query,
3025
+ namespace: options.namespace,
3026
+ topK: options.topK || 10,
3027
+ embeddingModel: options.embeddingModel
3028
+ }
3029
+ });
3030
+ }
3031
+ },
3032
+ /**
3033
+ * Delete a namespace (if supported)
3034
+ */
3035
+ async deleteNamespace(namespace) {
3036
+ await vectorApi(`/namespace/${encodeURIComponent(namespace)}`, {
3037
+ method: "DELETE"
3038
+ });
3039
+ },
3040
+ /**
3041
+ * Close client (no-op for HTTP client)
3042
+ */
3043
+ async close() {
3044
+ }
3045
+ };
3046
+ function getVectorClient() {
3047
+ if (!isVectorClientConfigured()) {
3048
+ try {
3049
+ const config = getConfig();
3050
+ if (config.resolvedRemoteServer.url && config.resolvedRemoteServer.authKey) {
3051
+ initVectorClient(config.resolvedRemoteServer.url, config.resolvedRemoteServer.authKey);
3052
+ } else {
3053
+ return null;
3054
+ }
3055
+ } catch {
3056
+ return null;
3057
+ }
3058
+ }
3059
+ return remoteVectorClient;
3060
+ }
3061
+ async function closeVectorClient() {
3062
+ }
3063
+ function isVectorGatewayConfigured() {
3064
+ try {
3065
+ const config = getConfig();
3066
+ return !!(config.resolvedRemoteServer.url && config.resolvedRemoteServer.authKey);
3067
+ } catch {
3068
+ return false;
3069
+ }
3070
+ }
3071
+ function getEmbeddingModel() {
3072
+ try {
3073
+ const config = getConfig();
3074
+ return config.resolvedVectorGateway.embeddingModel;
3075
+ } catch {
3076
+ return "gemini-embedding-001";
3077
+ }
3078
+ }
3079
+
3080
+ // src/semantic/indexer.ts
3081
+ import { readFileSync as readFileSync3, statSync } from "fs";
3082
+ import { relative as relative7 } from "path";
3083
+ import { minimatch as minimatch2 } from "minimatch";
3084
+ var MAX_FILE_SIZE3 = 1024 * 1024;
3085
+ async function getIndexStatus(workingDirectory) {
3086
+ const config = getConfig();
3087
+ const namespace = await getRepoNamespace(
3088
+ workingDirectory,
3089
+ config.resolvedVectorGateway.namespace
3090
+ );
3091
+ const isConfigured = !!config.resolvedVectorGateway.redisUrl;
3092
+ if (!namespace) {
3093
+ return {
3094
+ namespace: "",
3095
+ totalChunks: 0,
3096
+ lastFullIndex: null,
3097
+ lastIncrementalIndex: null,
3098
+ isConfigured
3099
+ };
3100
+ }
3101
+ try {
3102
+ const db = getDb();
3103
+ const status = await indexStatusQueries.get(db, namespace);
3104
+ if (!status) {
3105
+ return {
3106
+ namespace,
3107
+ totalChunks: 0,
3108
+ lastFullIndex: null,
3109
+ lastIncrementalIndex: null,
3110
+ isConfigured
3111
+ };
3112
+ }
3113
+ return {
3114
+ namespace,
3115
+ totalChunks: status.totalChunks ?? 0,
3116
+ lastFullIndex: status.lastFullIndex ?? null,
3117
+ lastIncrementalIndex: status.lastIncrementalIndex ?? null,
3118
+ isConfigured
3119
+ };
3120
+ } catch {
3121
+ return {
3122
+ namespace,
3123
+ totalChunks: 0,
3124
+ lastFullIndex: null,
3125
+ lastIncrementalIndex: null,
3126
+ isConfigured
3127
+ };
3128
+ }
3129
+ }
3130
+ async function checkIndexExists(workingDirectory) {
3131
+ const status = await getIndexStatus(workingDirectory);
3132
+ return status.totalChunks > 0;
3133
+ }
3134
+
3135
+ // src/tools/semantic-search.ts
3136
+ var semanticSearchInputSchema = z10.object({
3137
+ query: z10.string().describe("Natural language search query describing what you want to find"),
3138
+ topK: z10.number().optional().default(10).describe("Number of results to return (default: 10, max: 50)"),
3139
+ filePattern: z10.string().optional().describe('Filter results by file glob pattern (e.g., "*.ts", "src/**/*.py")'),
3140
+ language: z10.string().optional().describe('Filter by programming language (e.g., "typescript", "python")')
3141
+ });
3142
+ function createSemanticSearchTool(options) {
3143
+ return tool9({
3144
+ description: `Search the codebase using semantic similarity. This tool finds code by understanding its meaning, not just matching text.
3145
+
3146
+ Use this tool when:
3147
+ - You need to understand how something works in the codebase
3148
+ - You're looking for code related to a concept (e.g., "authentication", "database queries")
3149
+ - You want to find implementations of features
3150
+ - The user asks "where is X?" or "how does Y work?"
3151
+
3152
+ This tool requires the repository to be indexed first with 'sparkecoder index'.
3153
+
3154
+ Returns matching code snippets with file paths, line numbers, and relevance scores.`,
3155
+ inputSchema: semanticSearchInputSchema,
3156
+ execute: async ({
3157
+ query,
3158
+ topK,
3159
+ filePattern,
3160
+ language
3161
+ }) => {
3162
+ const startTime = Date.now();
3163
+ try {
3164
+ const config = getConfig();
3165
+ const namespace = await getRepoNamespace(
3166
+ options.workingDirectory,
3167
+ config.resolvedVectorGateway.namespace
3168
+ );
3169
+ if (!namespace) {
3170
+ return {
3171
+ success: false,
3172
+ error: "Repository namespace not found. Ensure this is a git repository with a remote configured."
3173
+ };
3174
+ }
3175
+ const client = getVectorClient();
3176
+ if (!client) {
3177
+ return {
3178
+ success: false,
3179
+ error: 'Vector Gateway not configured. Run "sparkecoder index --status" for setup instructions.'
3180
+ };
3181
+ }
3182
+ try {
3183
+ const limitedTopK = Math.min(Math.max(1, topK), 50);
3184
+ const embeddingModel = getEmbeddingModel();
3185
+ const result = await client.search.queryAndWait(query, {
3186
+ namespace,
3187
+ topK: limitedTopK * 2,
3188
+ includeMetadata: true,
3189
+ embeddingModel
3190
+ });
3191
+ const matches = [];
3192
+ for (const match of result.matches) {
3193
+ const metadata = match.metadata;
3194
+ if (!metadata) continue;
3195
+ const filePath = metadata.filePath;
3196
+ const startLine = metadata.startLine;
3197
+ const endLine = metadata.endLine;
3198
+ const matchLanguage = metadata.language;
3199
+ const symbolName = metadata.symbolName;
3200
+ if (filePattern) {
3201
+ const matchesPattern = minimatch3(filePath, filePattern, { dot: true });
3202
+ if (!matchesPattern) continue;
3203
+ }
3204
+ if (language && matchLanguage !== language.toLowerCase()) {
3205
+ continue;
3206
+ }
3207
+ const fullPath = join5(options.workingDirectory, filePath);
3208
+ if (!existsSync11(fullPath)) {
3209
+ continue;
3210
+ }
3211
+ let snippet = "";
3212
+ try {
3213
+ const content = readFileSync4(fullPath, "utf-8");
3214
+ const lines = content.split("\n");
3215
+ const snippetLines = lines.slice(
3216
+ Math.max(0, startLine - 1),
3217
+ Math.min(lines.length, endLine)
3218
+ );
3219
+ snippet = snippetLines.join("\n");
3220
+ if (snippet.length > 500) {
3221
+ snippet = snippet.slice(0, 500) + "\n... (truncated)";
3222
+ }
3223
+ } catch {
3224
+ }
3225
+ matches.push({
3226
+ filePath,
3227
+ startLine,
3228
+ endLine,
3229
+ score: match.score,
3230
+ snippet,
3231
+ symbolName,
3232
+ language: matchLanguage
3233
+ });
3234
+ if (matches.length >= limitedTopK) {
3235
+ break;
3236
+ }
3237
+ }
3238
+ return {
3239
+ success: true,
3240
+ query,
3241
+ matches,
3242
+ totalMatches: matches.length,
3243
+ duration: Date.now() - startTime
3244
+ };
3245
+ } finally {
3246
+ await closeVectorClient();
3247
+ }
3248
+ } catch (error) {
3249
+ return {
3250
+ success: false,
3251
+ error: `Semantic search failed: ${error instanceof Error ? error.message : String(error)}`
3252
+ };
3253
+ }
3254
+ }
3255
+ });
3256
+ }
3257
+
2995
3258
  // src/tools/index.ts
2996
- function createTools(options) {
2997
- return {
3259
+ async function createTools(options) {
3260
+ const tools = {
2998
3261
  bash: createBashTool({
2999
3262
  workingDirectory: options.workingDirectory,
3000
3263
  sessionId: options.sessionId,
@@ -3026,6 +3289,20 @@ function createTools(options) {
3026
3289
  onProgress: options.onSearchProgress
3027
3290
  })
3028
3291
  };
3292
+ if (options.enableSemanticSearch !== false) {
3293
+ try {
3294
+ if (isVectorGatewayConfigured()) {
3295
+ const hasIndex = await checkIndexExists(options.workingDirectory);
3296
+ if (hasIndex) {
3297
+ tools.semantic_search = createSemanticSearchTool({
3298
+ workingDirectory: options.workingDirectory
3299
+ });
3300
+ }
3301
+ }
3302
+ } catch {
3303
+ }
3304
+ }
3305
+ return tools;
3029
3306
  }
3030
3307
  export {
3031
3308
  createBashTool,
@@ -3033,6 +3310,7 @@ export {
3033
3310
  createLoadSkillTool,
3034
3311
  createReadFileTool,
3035
3312
  createSearchTool,
3313
+ createSemanticSearchTool,
3036
3314
  createTodoTool,
3037
3315
  createTools,
3038
3316
  createWriteFileTool