@askexenow/exe-os 0.9.69 → 0.9.70

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 (75) hide show
  1. package/deploy/stack-manifests/v0.9.json +96 -16
  2. package/dist/bin/agentic-ontology-backfill.js +6 -0
  3. package/dist/bin/agentic-reflection-backfill.js +6 -0
  4. package/dist/bin/agentic-semantic-label.js +6 -0
  5. package/dist/bin/backfill-conversations.js +6 -0
  6. package/dist/bin/backfill-responses.js +6 -0
  7. package/dist/bin/backfill-vectors.js +6 -0
  8. package/dist/bin/bulk-sync-postgres.js +6 -0
  9. package/dist/bin/cleanup-stale-review-tasks.js +6 -0
  10. package/dist/bin/cli.js +1257 -178
  11. package/dist/bin/exe-agent.js +6 -0
  12. package/dist/bin/exe-assign.js +6 -0
  13. package/dist/bin/exe-boot.js +6 -0
  14. package/dist/bin/exe-call.js +6 -0
  15. package/dist/bin/exe-cloud.js +6 -0
  16. package/dist/bin/exe-dispatch.js +6 -0
  17. package/dist/bin/exe-doctor.js +6 -0
  18. package/dist/bin/exe-export-behaviors.js +6 -0
  19. package/dist/bin/exe-forget.js +6 -0
  20. package/dist/bin/exe-gateway.js +151 -110
  21. package/dist/bin/exe-heartbeat.js +6 -0
  22. package/dist/bin/exe-kill.js +6 -0
  23. package/dist/bin/exe-launch-agent.js +6 -0
  24. package/dist/bin/exe-new-employee.js +6 -0
  25. package/dist/bin/exe-pending-messages.js +6 -0
  26. package/dist/bin/exe-pending-notifications.js +6 -0
  27. package/dist/bin/exe-pending-reviews.js +6 -0
  28. package/dist/bin/exe-rename.js +13 -4
  29. package/dist/bin/exe-review.js +6 -0
  30. package/dist/bin/exe-search.js +6 -0
  31. package/dist/bin/exe-session-cleanup.js +6 -0
  32. package/dist/bin/exe-start-codex.js +6 -0
  33. package/dist/bin/exe-start-opencode.js +6 -0
  34. package/dist/bin/exe-status.js +6 -0
  35. package/dist/bin/exe-team.js +6 -0
  36. package/dist/bin/git-sweep.js +6 -0
  37. package/dist/bin/graph-backfill.js +150 -110
  38. package/dist/bin/graph-export.js +6 -0
  39. package/dist/bin/intercom-check.js +6 -0
  40. package/dist/bin/registry-proxy.js +207 -0
  41. package/dist/bin/scan-tasks.js +6 -0
  42. package/dist/bin/setup.js +6 -0
  43. package/dist/bin/shard-migrate.js +6 -0
  44. package/dist/bin/stack-update.js +128 -0
  45. package/dist/gateway/index.js +151 -110
  46. package/dist/hooks/bug-report-worker.js +6 -0
  47. package/dist/hooks/codex-stop-task-finalizer.js +6 -0
  48. package/dist/hooks/commit-complete.js +6 -0
  49. package/dist/hooks/error-recall.js +6 -0
  50. package/dist/hooks/ingest.js +6 -0
  51. package/dist/hooks/instructions-loaded.js +6 -0
  52. package/dist/hooks/notification.js +6 -0
  53. package/dist/hooks/post-compact.js +6 -0
  54. package/dist/hooks/post-tool-combined.js +6 -0
  55. package/dist/hooks/pre-compact.js +6 -0
  56. package/dist/hooks/pre-tool-use.js +6 -0
  57. package/dist/hooks/prompt-submit.js +6 -0
  58. package/dist/hooks/session-end.js +6 -0
  59. package/dist/hooks/session-start.js +6 -0
  60. package/dist/hooks/stop.js +6 -0
  61. package/dist/hooks/subagent-stop.js +6 -0
  62. package/dist/hooks/summary-worker.js +6 -0
  63. package/dist/index.js +151 -110
  64. package/dist/lib/employee-templates.js +6 -0
  65. package/dist/lib/exe-daemon.js +382 -234
  66. package/dist/lib/hybrid-search.js +6 -0
  67. package/dist/lib/registry-proxy.js +162 -0
  68. package/dist/lib/schedules.js +6 -0
  69. package/dist/lib/store.js +6 -0
  70. package/dist/mcp/server.js +318 -222
  71. package/dist/runtime/index.js +6 -0
  72. package/dist/tui/App.js +6 -0
  73. package/package.json +3 -2
  74. package/stack.release.json +6 -4
  75. package/stack.release.schema.json +89 -18
@@ -5790,6 +5790,12 @@ var init_platform_procedures = __esm({
5790
5790
  priority: "p0",
5791
5791
  content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
5792
5792
  },
5793
+ {
5794
+ title: "Code context first for repository orientation",
5795
+ domain: "workflow",
5796
+ priority: "p1",
5797
+ content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
5798
+ },
5793
5799
  {
5794
5800
  title: "Commit discipline \u2014 never leave verified work floating",
5795
5801
  domain: "workflow",
@@ -19020,13 +19026,25 @@ var init_export_graph = __esm({
19020
19026
 
19021
19027
  // src/lib/code-chunker.ts
19022
19028
  import ts from "typescript";
19029
+ function languageForFile(filePath) {
19030
+ const base = filePath.split(/[\\/]/).pop()?.toLowerCase() ?? "";
19031
+ if (["dockerfile", "makefile", "rakefile", "gemfile"].includes(base)) return base;
19032
+ const ext = base.includes(".") ? base.split(".").pop() : void 0;
19033
+ return ext ? LANGUAGE_BY_EXTENSION[ext] : void 0;
19034
+ }
19023
19035
  function chunkSourceFile(source, fileName = "file.ts") {
19036
+ const language = languageForFile(fileName);
19037
+ if (language === "typescript" || language === "javascript") {
19038
+ return chunkTypeScriptLike(source, fileName);
19039
+ }
19040
+ return chunkGenericSource(source, fileName, language);
19041
+ }
19042
+ function chunkTypeScriptLike(source, fileName) {
19024
19043
  const sourceFile = ts.createSourceFile(
19025
19044
  fileName,
19026
19045
  source,
19027
19046
  ts.ScriptTarget.Latest,
19028
19047
  true,
19029
- // setParentNodes
19030
19048
  fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
19031
19049
  );
19032
19050
  const chunks = [];
@@ -19048,144 +19066,117 @@ function chunkSourceFile(source, fileName = "file.ts") {
19048
19066
  if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
19049
19067
  return node.name?.getText(sourceFile) ?? "(anonymous)";
19050
19068
  }
19051
- if (ts.isClassDeclaration(node)) {
19052
- return node.name?.getText(sourceFile) ?? "(anonymous class)";
19053
- }
19054
- if (ts.isInterfaceDeclaration(node)) {
19055
- return node.name.getText(sourceFile);
19056
- }
19057
- if (ts.isTypeAliasDeclaration(node)) {
19058
- return node.name.getText(sourceFile);
19059
- }
19060
- if (ts.isEnumDeclaration(node)) {
19061
- return node.name.getText(sourceFile);
19062
- }
19063
- if (ts.isVariableStatement(node)) {
19064
- const decls = node.declarationList.declarations;
19065
- return decls.map((d) => d.name.getText(sourceFile)).join(", ");
19066
- }
19067
- if (ts.isExportAssignment(node)) {
19068
- return "default export";
19069
- }
19069
+ if (ts.isClassDeclaration(node)) return node.name?.getText(sourceFile) ?? "(anonymous class)";
19070
+ if (ts.isInterfaceDeclaration(node)) return node.name.getText(sourceFile);
19071
+ if (ts.isTypeAliasDeclaration(node)) return node.name.getText(sourceFile);
19072
+ if (ts.isEnumDeclaration(node)) return node.name.getText(sourceFile);
19073
+ if (ts.isVariableStatement(node)) return node.declarationList.declarations.map((d) => d.name.getText(sourceFile)).join(", ");
19074
+ if (ts.isExportAssignment(node)) return "default export";
19070
19075
  return "(unknown)";
19071
19076
  }
19072
19077
  function visitTopLevel(node) {
19073
19078
  if (ts.isImportDeclaration(node)) {
19074
- importLines.push({
19075
- start: getLineNumber(node.getStart(sourceFile)),
19076
- end: getLineNumber(node.getEnd())
19077
- });
19079
+ importLines.push({ start: getLineNumber(node.getStart(sourceFile)), end: getLineNumber(node.getEnd()) });
19078
19080
  return;
19079
19081
  }
19080
19082
  if (ts.isFunctionDeclaration(node)) {
19081
- chunks.push({
19082
- kind: "function",
19083
- name: getName(node),
19084
- text: getNodeText(node),
19085
- startLine: getLineNumber(node.getStart(sourceFile)),
19086
- endLine: getLineNumber(node.getEnd()),
19087
- comment: getLeadingComment(node)
19088
- });
19083
+ chunks.push({ kind: "function", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
19089
19084
  return;
19090
19085
  }
19091
19086
  if (ts.isClassDeclaration(node)) {
19092
- chunks.push({
19093
- kind: "class",
19094
- name: getName(node),
19095
- text: getNodeText(node),
19096
- startLine: getLineNumber(node.getStart(sourceFile)),
19097
- endLine: getLineNumber(node.getEnd()),
19098
- comment: getLeadingComment(node)
19099
- });
19100
- return;
19101
- }
19102
- if (ts.isInterfaceDeclaration(node)) {
19103
- chunks.push({
19104
- kind: "type",
19105
- name: getName(node),
19106
- text: getNodeText(node),
19107
- startLine: getLineNumber(node.getStart(sourceFile)),
19108
- endLine: getLineNumber(node.getEnd()),
19109
- comment: getLeadingComment(node)
19110
- });
19087
+ chunks.push({ kind: "class", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
19111
19088
  return;
19112
19089
  }
19113
- if (ts.isTypeAliasDeclaration(node)) {
19114
- chunks.push({
19115
- kind: "type",
19116
- name: getName(node),
19117
- text: getNodeText(node),
19118
- startLine: getLineNumber(node.getStart(sourceFile)),
19119
- endLine: getLineNumber(node.getEnd()),
19120
- comment: getLeadingComment(node)
19121
- });
19122
- return;
19123
- }
19124
- if (ts.isEnumDeclaration(node)) {
19125
- chunks.push({
19126
- kind: "type",
19127
- name: getName(node),
19128
- text: getNodeText(node),
19129
- startLine: getLineNumber(node.getStart(sourceFile)),
19130
- endLine: getLineNumber(node.getEnd()),
19131
- comment: getLeadingComment(node)
19132
- });
19090
+ if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node)) {
19091
+ chunks.push({ kind: "type", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
19133
19092
  return;
19134
19093
  }
19135
19094
  if (ts.isVariableStatement(node)) {
19136
- const decls = node.declarationList.declarations;
19137
- const isFnLike = decls.some(
19138
- (d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer))
19139
- );
19140
- chunks.push({
19141
- kind: isFnLike ? "function" : "variable",
19142
- name: getName(node),
19143
- text: getNodeText(node),
19144
- startLine: getLineNumber(node.getStart(sourceFile)),
19145
- endLine: getLineNumber(node.getEnd()),
19146
- comment: getLeadingComment(node)
19147
- });
19095
+ const isFnLike = node.declarationList.declarations.some((d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer)));
19096
+ chunks.push({ kind: isFnLike ? "function" : "variable", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
19148
19097
  return;
19149
19098
  }
19150
19099
  if (ts.isExportAssignment(node)) {
19151
- chunks.push({
19152
- kind: "export",
19153
- name: "default export",
19154
- text: getNodeText(node),
19155
- startLine: getLineNumber(node.getStart(sourceFile)),
19156
- endLine: getLineNumber(node.getEnd()),
19157
- comment: getLeadingComment(node)
19158
- });
19100
+ chunks.push({ kind: "export", name: "default export", text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
19159
19101
  return;
19160
19102
  }
19161
19103
  if (ts.isExpressionStatement(node)) {
19162
19104
  const text3 = getNodeText(node);
19163
- if (text3.length > 10) {
19164
- chunks.push({
19165
- kind: "other",
19166
- name: text3.slice(0, 40).replace(/\n/g, " "),
19167
- text: text3,
19168
- startLine: getLineNumber(node.getStart(sourceFile)),
19169
- endLine: getLineNumber(node.getEnd())
19170
- });
19171
- }
19172
- return;
19105
+ if (text3.length > 10) chunks.push({ kind: "other", name: text3.slice(0, 40).replace(/\n/g, " "), text: text3, startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()) });
19173
19106
  }
19174
19107
  }
19175
19108
  sourceFile.statements.forEach(visitTopLevel);
19176
19109
  if (importLines.length > 0) {
19177
19110
  const startLine = importLines[0].start;
19178
19111
  const endLine = importLines[importLines.length - 1].end;
19179
- const importText = lines.slice(startLine - 1, endLine).join("\n");
19180
- chunks.unshift({
19181
- kind: "import",
19182
- name: `${importLines.length} imports`,
19183
- text: importText,
19184
- startLine,
19112
+ chunks.unshift({ kind: "import", name: `${importLines.length} imports`, text: lines.slice(startLine - 1, endLine).join("\n"), startLine, endLine });
19113
+ }
19114
+ chunks.sort((a, b) => a.startLine - b.startLine);
19115
+ return chunks.length > 0 ? chunks : chunkByWindows(source, 80);
19116
+ }
19117
+ function chunkGenericSource(source, _fileName, language) {
19118
+ const lines = source.split("\n");
19119
+ if (source.trim().length === 0) return [];
19120
+ if (language && TEXT_LIKE_LANGUAGES.has(language)) return chunkTextLike(lines, language);
19121
+ const chunks = [];
19122
+ const patterns = [
19123
+ { kind: "function", regex: /^\s*(?:async\s+)?def\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
19124
+ { kind: "class", regex: /^\s*class\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
19125
+ { kind: "function", regex: /^\s*(?:pub\s+)?fn\s+([A-Za-z_][\w]*)\s*[<(]/, name: (m) => m[1] },
19126
+ { kind: "class", regex: /^\s*(?:pub\s+)?(?:struct|enum|trait)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
19127
+ { kind: "function", regex: /^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
19128
+ { kind: "function", regex: /^\s*(?:public|private|protected|static|final|suspend|fun|override|open|internal|export|async|func|function|subroutine)\s+.*?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
19129
+ { kind: "function", regex: /^\s*function\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
19130
+ { kind: "type", regex: /^\s*(?:interface|type|enum|record|data\s+class|case\s+class|contract|library)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
19131
+ { kind: "section", regex: /^\s{0,3}#{1,6}\s+(.+)$/, name: (m) => m[1].trim() }
19132
+ ];
19133
+ const starts = [];
19134
+ for (let i = 0; i < lines.length; i++) {
19135
+ const line = lines[i];
19136
+ for (const pattern of patterns) {
19137
+ const match = line.match(pattern.regex);
19138
+ if (match) {
19139
+ starts.push({ line: i + 1, kind: pattern.kind, name: pattern.name(match) });
19140
+ break;
19141
+ }
19142
+ }
19143
+ }
19144
+ if (starts.length === 0) return chunkByWindows(source, 80);
19145
+ for (let i = 0; i < starts.length; i++) {
19146
+ const start = starts[i];
19147
+ const next = starts[i + 1]?.line ?? lines.length + 1;
19148
+ const endLine = Math.max(start.line, next - 1);
19149
+ chunks.push({
19150
+ kind: start.kind,
19151
+ name: start.name,
19152
+ text: lines.slice(start.line - 1, endLine).join("\n"),
19153
+ startLine: start.line,
19185
19154
  endLine
19186
19155
  });
19187
19156
  }
19188
- chunks.sort((a, b) => a.startLine - b.startLine);
19157
+ return chunks;
19158
+ }
19159
+ function chunkTextLike(lines, language) {
19160
+ if (language === "markdown" || language === "mdx") {
19161
+ const starts = lines.map((line, i) => ({ line, i })).filter(({ line }) => /^\s{0,3}#{1,6}\s+/.test(line));
19162
+ if (starts.length > 0) {
19163
+ return starts.map((start, idx) => {
19164
+ const end = starts[idx + 1]?.i ?? lines.length;
19165
+ const title = start.line.replace(/^\s{0,3}#{1,6}\s+/, "").trim();
19166
+ return { kind: "section", name: title, text: lines.slice(start.i, end).join("\n"), startLine: start.i + 1, endLine: end };
19167
+ });
19168
+ }
19169
+ }
19170
+ return chunkByWindows(lines.join("\n"), 80);
19171
+ }
19172
+ function chunkByWindows(source, windowLines) {
19173
+ const lines = source.split("\n");
19174
+ const chunks = [];
19175
+ for (let i = 0; i < lines.length; i += windowLines) {
19176
+ const end = Math.min(lines.length, i + windowLines);
19177
+ const text3 = lines.slice(i, end).join("\n");
19178
+ if (text3.trim()) chunks.push({ kind: "other", name: `lines ${i + 1}-${end}`, text: text3, startLine: i + 1, endLine: end });
19179
+ }
19189
19180
  return chunks;
19190
19181
  }
19191
19182
  function summarizeChunk(chunk, filePath) {
@@ -19204,17 +19195,69 @@ function summarizeChunk(chunk, filePath) {
19204
19195
  return `Variable ${chunk.name} in ${location}`;
19205
19196
  case "export":
19206
19197
  return `Default export in ${location}`;
19198
+ case "section":
19199
+ return `Section ${chunk.name} in ${location}`;
19207
19200
  default:
19208
19201
  return `${chunk.kind} in ${location}`;
19209
19202
  }
19210
19203
  }
19211
19204
  function isChunkable(filePath) {
19212
- const ext = filePath.split(".").pop()?.toLowerCase();
19213
- return ext === "ts" || ext === "tsx" || ext === "js" || ext === "jsx";
19205
+ return Boolean(languageForFile(filePath));
19214
19206
  }
19207
+ var LANGUAGE_BY_EXTENSION, TEXT_LIKE_LANGUAGES;
19215
19208
  var init_code_chunker = __esm({
19216
19209
  "src/lib/code-chunker.ts"() {
19217
19210
  "use strict";
19211
+ LANGUAGE_BY_EXTENSION = {
19212
+ c: "c",
19213
+ cc: "cpp",
19214
+ cpp: "cpp",
19215
+ cxx: "cpp",
19216
+ h: "c",
19217
+ hh: "cpp",
19218
+ hpp: "cpp",
19219
+ cs: "csharp",
19220
+ css: "css",
19221
+ scss: "scss",
19222
+ f: "fortran",
19223
+ f90: "fortran",
19224
+ f95: "fortran",
19225
+ go: "go",
19226
+ html: "html",
19227
+ htm: "html",
19228
+ java: "java",
19229
+ js: "javascript",
19230
+ jsx: "javascript",
19231
+ json: "json",
19232
+ kt: "kotlin",
19233
+ kts: "kotlin",
19234
+ lua: "lua",
19235
+ md: "markdown",
19236
+ mdx: "mdx",
19237
+ pas: "pascal",
19238
+ php: "php",
19239
+ py: "python",
19240
+ r: "r",
19241
+ rb: "ruby",
19242
+ rs: "rust",
19243
+ scala: "scala",
19244
+ sc: "scala",
19245
+ sol: "solidity",
19246
+ sql: "sql",
19247
+ svelte: "svelte",
19248
+ swift: "swift",
19249
+ toml: "toml",
19250
+ ts: "typescript",
19251
+ tsx: "typescript",
19252
+ vue: "vue",
19253
+ xml: "xml",
19254
+ yaml: "yaml",
19255
+ yml: "yaml",
19256
+ sh: "shell",
19257
+ bash: "shell",
19258
+ zsh: "shell"
19259
+ };
19260
+ TEXT_LIKE_LANGUAGES = /* @__PURE__ */ new Set(["json", "markdown", "mdx", "toml", "yaml", "xml", "html", "css", "scss", "sql"]);
19218
19261
  }
19219
19262
  });
19220
19263
 
@@ -29989,7 +30032,7 @@ var init_create_bug_report = __esm({
29989
30032
  // src/lib/code-context-index.ts
29990
30033
  import crypto20 from "crypto";
29991
30034
  import path54 from "path";
29992
- import { existsSync as existsSync43, mkdirSync as mkdirSync21, readFileSync as readFileSync35, statSync as statSync10, writeFileSync as writeFileSync24 } from "fs";
30035
+ import { existsSync as existsSync43, mkdirSync as mkdirSync21, readFileSync as readFileSync35, readdirSync as readdirSync14, statSync as statSync10, writeFileSync as writeFileSync24 } from "fs";
29993
30036
  import { spawnSync } from "child_process";
29994
30037
  function normalizeProjectRoot(projectRoot) {
29995
30038
  return path54.resolve(projectRoot || process.cwd());
@@ -30008,78 +30051,53 @@ function getCodeContextIndexPath(projectRoot) {
30008
30051
  return path54.join(indexDir(), `${rootHash}.json`);
30009
30052
  }
30010
30053
  function currentBranch(projectRoot) {
30011
- const result2 = spawnSync("git", ["branch", "--show-current"], {
30012
- cwd: projectRoot,
30013
- encoding: "utf8",
30014
- timeout: 2e3
30015
- });
30054
+ const result2 = spawnSync("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
30016
30055
  const branch = result2.status === 0 ? result2.stdout.trim() : "";
30017
30056
  return branch || "detached-or-unknown";
30018
30057
  }
30019
30058
  function shouldIgnore(relPath) {
30020
- const parts = relPath.split(path54.sep);
30059
+ const parts = relPath.split(/[\\/]/);
30021
30060
  return parts.some((part) => IGNORE_SEGMENTS.has(part));
30022
30061
  }
30023
- function isCodeFile(relPath) {
30024
- return CODE_EXTENSIONS.has(path54.extname(relPath).toLowerCase());
30062
+ function listRecursive(projectRoot, dir = projectRoot, out = []) {
30063
+ for (const entry of readdirSync14(dir, { withFileTypes: true })) {
30064
+ const abs = path54.join(dir, entry.name);
30065
+ const rel = path54.relative(projectRoot, abs).replaceAll(path54.sep, "/");
30066
+ if (shouldIgnore(rel)) continue;
30067
+ if (entry.isDirectory()) listRecursive(projectRoot, abs, out);
30068
+ else if (entry.isFile()) out.push(rel);
30069
+ }
30070
+ return out;
30025
30071
  }
30026
30072
  function listCodeFiles(projectRoot, maxFiles) {
30027
- const git = spawnSync("git", ["ls-files", "*.ts", "*.tsx", "*.js", "*.jsx"], {
30028
- cwd: projectRoot,
30029
- encoding: "utf8",
30030
- timeout: 5e3,
30031
- maxBuffer: 1024 * 1024 * 8
30032
- });
30073
+ const git = spawnSync("git", ["ls-files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
30033
30074
  let files = [];
30034
30075
  if (git.status === 0 && git.stdout.trim()) {
30035
30076
  files = git.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
30036
30077
  } else {
30037
- const rg = spawnSync("rg", ["--files", "-g", "*.ts", "-g", "*.tsx", "-g", "*.js", "-g", "*.jsx"], {
30038
- cwd: projectRoot,
30039
- encoding: "utf8",
30040
- timeout: 5e3,
30041
- maxBuffer: 1024 * 1024 * 8
30042
- });
30043
- if (rg.status === 0 && rg.stdout.trim()) {
30044
- files = rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
30045
- }
30078
+ const rg = spawnSync("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
30079
+ files = rg.status === 0 && rg.stdout.trim() ? rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : listRecursive(projectRoot);
30046
30080
  }
30047
- return files.filter((file) => isCodeFile(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
30081
+ return files.map((file) => file.replaceAll(path54.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
30048
30082
  }
30049
30083
  function parseImportPaths2(importText) {
30050
30084
  const paths = [];
30051
- const fromRegex = /from\s+["']([^"']+)["']/g;
30052
- let match;
30053
- while ((match = fromRegex.exec(importText)) !== null) paths.push(match[1]);
30054
- const bareRegex = /^import\s+["']([^"']+)["']/gm;
30055
- while ((match = bareRegex.exec(importText)) !== null) {
30056
- if (!paths.includes(match[1])) paths.push(match[1]);
30057
- }
30058
- const requireRegex = /require\(["']([^"']+)["']\)/g;
30059
- while ((match = requireRegex.exec(importText)) !== null) {
30060
- if (!paths.includes(match[1])) paths.push(match[1]);
30085
+ const patterns = [/from\s+["']([^"']+)["']/g, /^import\s+["']([^"']+)["']/gm, /require\(["']([^"']+)["']\)/g, /use\s+([A-Za-z0-9_:]+)::/g, /#include\s+[<"]([^>"]+)[>"]/g];
30086
+ for (const regex of patterns) {
30087
+ let match;
30088
+ while ((match = regex.exec(importText)) !== null) if (!paths.includes(match[1])) paths.push(match[1]);
30061
30089
  }
30062
30090
  return paths;
30063
30091
  }
30064
30092
  function resolveImport(fromFile, importPath, allFiles) {
30065
30093
  if (!importPath.startsWith(".")) return null;
30066
30094
  const base = path54.posix.normalize(path54.posix.join(path54.posix.dirname(fromFile.replaceAll(path54.sep, "/")), importPath));
30067
- const withoutKnownExt = base.replace(/\.(?:js|jsx|ts|tsx)$/, "");
30068
- const candidates = [
30069
- base,
30070
- `${withoutKnownExt}.ts`,
30071
- `${withoutKnownExt}.tsx`,
30072
- `${withoutKnownExt}.js`,
30073
- `${withoutKnownExt}.jsx`,
30074
- `${base}.ts`,
30075
- `${base}.tsx`,
30076
- `${base}.js`,
30077
- `${base}.jsx`,
30078
- path54.posix.join(base, "index.ts"),
30079
- path54.posix.join(base, "index.tsx"),
30080
- path54.posix.join(base, "index.js"),
30081
- path54.posix.join(base, "index.jsx")
30082
- ];
30095
+ const withoutKnownExt = base.replace(/\.(?:[a-z0-9]+)$/i, "");
30096
+ const candidates = [base];
30097
+ for (const ext of ["ts", "tsx", "js", "jsx", "py", "rs", "go", "java", "cs", "cpp", "c", "rb", "php", "swift", "kt", "scala", "sql", "md", "json", "yaml", "yml"]) {
30098
+ candidates.push(`${withoutKnownExt}.${ext}`, `${base}.${ext}`);
30099
+ }
30100
+ for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path54.posix.join(base, indexName));
30083
30101
  return candidates.find((candidate) => allFiles.has(candidate)) ?? null;
30084
30102
  }
30085
30103
  function symbolId(filePath, chunk) {
@@ -30105,39 +30123,32 @@ function buildFileRecord(projectRoot, relPath, allFiles, previous) {
30105
30123
  try {
30106
30124
  stat = statSync10(absPath);
30107
30125
  } catch {
30108
- return null;
30126
+ return { record: null, reused: false };
30109
30127
  }
30110
- if (!stat.isFile()) return null;
30128
+ if (!stat.isFile()) return { record: null, reused: false };
30129
+ const language = languageForFile(relPath);
30130
+ if (!language || !isChunkable(relPath)) return { record: null, reused: false };
30111
30131
  const source = readFileSync35(absPath, "utf8");
30112
30132
  const hash = hashText(source);
30113
- if (previous && previous.hash === hash && previous.mtimeMs === stat.mtimeMs && previous.size === stat.size) {
30114
- return previous;
30133
+ if (previous && previous.hash === hash && previous.mtimeMs === stat.mtimeMs && previous.size === stat.size && previous.language === language) {
30134
+ return { record: previous, reused: true };
30115
30135
  }
30116
- if (!isChunkable(relPath)) return null;
30117
30136
  const chunks = chunkSourceFile(source, relPath);
30118
30137
  const imports = chunks.filter((chunk) => chunk.kind === "import").flatMap((chunk) => parseImportPaths2(chunk.text));
30119
30138
  const resolvedImports = imports.map((importPath) => resolveImport(relPath, importPath, allFiles)).filter((file) => Boolean(file));
30120
- const symbols = chunks.filter((chunk) => chunk.kind !== "import" && chunk.name !== "(unknown)").map((chunk) => ({
30139
+ const symbols = chunks.filter((chunk) => chunk.name !== "(unknown)").map((chunk) => ({
30121
30140
  id: symbolId(relPath, chunk),
30122
30141
  name: chunk.name,
30123
30142
  kind: chunk.kind,
30124
30143
  filePath: relPath,
30144
+ language,
30125
30145
  startLine: chunk.startLine,
30126
30146
  endLine: chunk.endLine,
30127
30147
  summary: summarizeChunk(chunk, relPath),
30128
- text: chunk.text.slice(0, 6e3),
30148
+ text: chunk.text.slice(0, 8e3),
30129
30149
  comment: chunk.comment
30130
30150
  }));
30131
- return {
30132
- path: relPath,
30133
- absPath,
30134
- hash,
30135
- mtimeMs: stat.mtimeMs,
30136
- size: stat.size,
30137
- imports,
30138
- resolvedImports,
30139
- symbols
30140
- };
30151
+ return { record: { path: relPath, absPath, language, hash, mtimeMs: stat.mtimeMs, size: stat.size, imports, resolvedImports, symbols }, reused: false };
30141
30152
  }
30142
30153
  function buildCodeContextIndex(options = {}) {
30143
30154
  const projectRoot = normalizeProjectRoot(options.projectRoot);
@@ -30147,17 +30158,27 @@ function buildCodeContextIndex(options = {}) {
30147
30158
  const files = listCodeFiles(projectRoot, maxFiles);
30148
30159
  const allFiles = new Set(files.map((file) => file.replaceAll(path54.sep, "/")));
30149
30160
  const fileRecords = {};
30161
+ let rebuiltFiles = 0;
30162
+ let reusedFiles = 0;
30163
+ let skippedFiles = 0;
30150
30164
  for (const rel of files) {
30151
30165
  const normalized = rel.replaceAll(path54.sep, "/");
30152
- const record = buildFileRecord(projectRoot, normalized, allFiles, previous?.files[normalized]);
30153
- if (record) fileRecords[normalized] = record;
30154
- }
30166
+ const { record, reused } = buildFileRecord(projectRoot, normalized, allFiles, previous?.files[normalized]);
30167
+ if (record) {
30168
+ fileRecords[normalized] = record;
30169
+ if (reused) reusedFiles++;
30170
+ else rebuiltFiles++;
30171
+ } else skippedFiles++;
30172
+ }
30173
+ const languageBreakdown = {};
30174
+ for (const file of Object.values(fileRecords)) languageBreakdown[file.language] = (languageBreakdown[file.language] ?? 0) + 1;
30155
30175
  const index = {
30156
30176
  version: INDEX_VERSION,
30157
30177
  projectRoot,
30158
30178
  rootHash: hashText(projectRoot).slice(0, 16),
30159
30179
  branch,
30160
30180
  indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
30181
+ stats: { filesSeen: files.length, filesIndexed: Object.keys(fileRecords).length, rebuiltFiles, reusedFiles, skippedFiles, languageBreakdown },
30161
30182
  files: fileRecords
30162
30183
  };
30163
30184
  saveIndex(index);
@@ -30185,13 +30206,60 @@ function loadOrBuildCodeContextIndex(options = {}) {
30185
30206
  }
30186
30207
  return buildCodeContextIndex(options);
30187
30208
  }
30209
+ function normalizeLanguage(language) {
30210
+ return language.toLowerCase().replace(/^c\+\+$/, "cpp").replace(/^c#$/, "csharp").replace(/^js$/, "javascript").replace(/^ts$/, "typescript");
30211
+ }
30188
30212
  function tokenize(query) {
30189
- return query.toLowerCase().split(/[^a-z0-9_.$/-]+/).map((s) => s.trim()).filter((s) => s.length >= 2);
30213
+ const raw = query.toLowerCase().replace(/([a-z])([A-Z])/g, "$1 $2").split(/[^a-z0-9_.$/-]+/).map((s) => s.trim()).filter((s) => s.length >= 2);
30214
+ const expanded = /* @__PURE__ */ new Set();
30215
+ for (const term of raw) {
30216
+ expanded.add(term);
30217
+ for (const part of term.split(/[_.$/-]+/)) if (part.length >= 2) expanded.add(part);
30218
+ const dashed = term.replace(/[_.$/]+/g, "-");
30219
+ if (dashed.length >= 2) expanded.add(dashed);
30220
+ if (!term.includes("-")) expanded.add(`${term}s`);
30221
+ if (term.endsWith("s") && term.length > 3) expanded.add(term.slice(0, -1));
30222
+ }
30223
+ return [...expanded];
30224
+ }
30225
+ function ngrams(terms) {
30226
+ const grams = [...terms];
30227
+ for (let i = 0; i < terms.length - 1; i++) grams.push(`${terms[i]} ${terms[i + 1]}`);
30228
+ return grams;
30229
+ }
30230
+ function globToRegex(pattern) {
30231
+ let out = "";
30232
+ for (let i = 0; i < pattern.length; i++) {
30233
+ const ch = pattern[i];
30234
+ const next = pattern[i + 1];
30235
+ if (ch === "*" && next === "*") {
30236
+ out += ".*";
30237
+ i++;
30238
+ continue;
30239
+ }
30240
+ if (ch === "*") {
30241
+ out += "[^/]*";
30242
+ continue;
30243
+ }
30244
+ if (".+^${}()|[]\\".includes(ch)) out += `\\${ch}`;
30245
+ else out += ch;
30246
+ }
30247
+ return new RegExp(`^${out}$`);
30248
+ }
30249
+ function matchesPath(filePath, patterns) {
30250
+ if (!patterns || patterns.length === 0) return true;
30251
+ const normalized = filePath.replaceAll(path54.sep, "/");
30252
+ return patterns.some((pattern) => {
30253
+ const p = pattern.replaceAll(path54.sep, "/").replace(/^\.\//, "");
30254
+ return normalized === p || normalized.startsWith(`${p}/`) || normalized.endsWith(p) || globToRegex(p).test(normalized);
30255
+ });
30190
30256
  }
30191
30257
  function scoreSymbol(symbol, terms) {
30258
+ const grams = ngrams(terms);
30192
30259
  const haystacks = {
30193
30260
  name: symbol.name.toLowerCase(),
30194
30261
  path: symbol.filePath.toLowerCase(),
30262
+ language: symbol.language.toLowerCase(),
30195
30263
  summary: symbol.summary.toLowerCase(),
30196
30264
  text: symbol.text.toLowerCase()
30197
30265
  };
@@ -30199,41 +30267,73 @@ function scoreSymbol(symbol, terms) {
30199
30267
  const matches = [];
30200
30268
  for (const term of terms) {
30201
30269
  if (haystacks.name === term) {
30202
- score += 80;
30270
+ score += 100;
30203
30271
  matches.push(`name=${term}`);
30204
30272
  continue;
30205
30273
  }
30206
30274
  if (haystacks.name.includes(term)) {
30207
- score += 40;
30275
+ score += 45;
30208
30276
  matches.push(`name~${term}`);
30209
30277
  }
30210
30278
  if (haystacks.path.includes(term)) {
30211
- score += 15;
30279
+ score += 18;
30212
30280
  matches.push(`path~${term}`);
30213
30281
  }
30282
+ if (haystacks.language === term) {
30283
+ score += 18;
30284
+ matches.push(`language=${term}`);
30285
+ }
30214
30286
  if (haystacks.summary.includes(term)) {
30215
- score += 12;
30287
+ score += 16;
30216
30288
  matches.push(`summary~${term}`);
30217
30289
  }
30218
30290
  if (haystacks.text.includes(term)) {
30219
- score += 4;
30291
+ score += 5;
30220
30292
  matches.push(`text~${term}`);
30221
30293
  }
30222
30294
  }
30295
+ for (const gram of grams.filter((g) => g.includes(" "))) {
30296
+ if (haystacks.text.includes(gram) || haystacks.summary.includes(gram)) {
30297
+ score += 20;
30298
+ matches.push(`phrase~${gram}`);
30299
+ }
30300
+ }
30301
+ const uniqueMatches = new Set(matches.map((m) => m.replace(/^[^=~]+[=~]/, ""))).size;
30302
+ score += uniqueMatches * 3;
30223
30303
  return { score, matches };
30224
30304
  }
30305
+ function filteredFiles(index, options = {}) {
30306
+ const languages = options.languages?.map(normalizeLanguage).filter(Boolean);
30307
+ return Object.values(index.files).filter((file) => {
30308
+ if (languages && languages.length > 0 && !languages.includes(normalizeLanguage(file.language))) return false;
30309
+ return matchesPath(file.path, options.paths);
30310
+ });
30311
+ }
30225
30312
  function searchCodeContext(query, options = {}) {
30226
30313
  const terms = tokenize(query);
30227
30314
  if (terms.length === 0) return [];
30228
- const index = loadOrBuildCodeContextIndex(options);
30315
+ const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
30229
30316
  const results = [];
30230
- for (const file of Object.values(index.files)) {
30317
+ for (const file of filteredFiles(index, options)) {
30231
30318
  for (const symbol of file.symbols) {
30232
30319
  const scored = scoreSymbol(symbol, terms);
30233
- if (scored.score > 0) results.push({ symbol, score: scored.score, matches: scored.matches });
30320
+ if (scored.score > 0) {
30321
+ results.push({
30322
+ symbol,
30323
+ score: scored.score,
30324
+ matches: scored.matches,
30325
+ filePath: symbol.filePath,
30326
+ language: symbol.language,
30327
+ content: symbol.text,
30328
+ startLine: symbol.startLine,
30329
+ endLine: symbol.endLine
30330
+ });
30331
+ }
30234
30332
  }
30235
30333
  }
30236
- return results.sort((a, b) => b.score - a.score || a.symbol.filePath.localeCompare(b.symbol.filePath)).slice(0, options.limit ?? 20);
30334
+ const offset = Math.max(0, options.offset ?? 0);
30335
+ const limit = options.limit ?? 20;
30336
+ return results.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
30237
30337
  }
30238
30338
  function dependentsMap(index) {
30239
30339
  const map = /* @__PURE__ */ new Map();
@@ -30261,19 +30361,10 @@ function traceCodeSymbol(symbolName, options = {}) {
30261
30361
  const index = loadOrBuildCodeContextIndex(options);
30262
30362
  const matches = findSymbols(index, symbolName, options.limit ?? 20);
30263
30363
  const dependents = dependentsMap(index);
30264
- return {
30265
- query: symbolName,
30266
- matches,
30267
- definitions: matches.map((symbol) => {
30268
- const file = index.files[symbol.filePath];
30269
- return {
30270
- symbol,
30271
- imports: file.resolvedImports,
30272
- dependents: [...dependents.get(symbol.filePath) ?? /* @__PURE__ */ new Set()].sort(),
30273
- relatedSymbols: file.symbols.filter((s) => s.id !== symbol.id).slice(0, 20)
30274
- };
30275
- })
30276
- };
30364
+ return { query: symbolName, matches, definitions: matches.map((symbol) => {
30365
+ const file = index.files[symbol.filePath];
30366
+ return { symbol, imports: file.resolvedImports, dependents: [...dependents.get(symbol.filePath) ?? /* @__PURE__ */ new Set()].sort(), relatedSymbols: file.symbols.filter((s) => s.id !== symbol.id).slice(0, 20) };
30367
+ }) };
30277
30368
  }
30278
30369
  function resolveTargetFile(index, input) {
30279
30370
  if (input.filePath) {
@@ -30313,20 +30404,10 @@ function analyzeBlastRadius(input) {
30313
30404
  const lower = file.toLowerCase();
30314
30405
  return (lower.includes("test") || lower.includes("spec")) && (lower.includes(targetBase) || (symbolLower ? index.files[file].symbols.some((s) => s.text.toLowerCase().includes(symbolLower)) : false));
30315
30406
  });
30316
- for (const test of tests) {
30317
- if (!impacted.has(test)) impacted.set(test, { distance: 1, reason: "related test/spec" });
30318
- }
30407
+ for (const test of tests) if (!impacted.has(test)) impacted.set(test, { distance: 1, reason: "related test/spec" });
30319
30408
  const impactedFiles = [...impacted.entries()].map(([filePath, value]) => ({ filePath, distance: value.distance, reason: value.reason })).sort((a, b) => a.distance - b.distance || a.filePath.localeCompare(b.filePath));
30320
30409
  const nonTestImpacted = impactedFiles.filter((f) => !f.filePath.match(/(test|spec)\./i)).length;
30321
- const riskLevel = nonTestImpacted >= 8 ? "high" : nonTestImpacted >= 4 ? "medium" : "low";
30322
- return {
30323
- target: target.target,
30324
- targetFile: target.filePath,
30325
- impactedFiles,
30326
- tests,
30327
- symbolsInTarget: index.files[target.filePath]?.symbols ?? [],
30328
- riskLevel
30329
- };
30410
+ return { target: target.target, targetFile: target.filePath, impactedFiles, tests, symbolsInTarget: index.files[target.filePath]?.symbols ?? [], riskLevel: nonTestImpacted >= 8 ? "high" : nonTestImpacted >= 4 ? "medium" : "low" };
30330
30411
  }
30331
30412
  function getCodeContextStats(options = {}) {
30332
30413
  const index = loadOrBuildCodeContextIndex(options);
@@ -30334,22 +30415,26 @@ function getCodeContextStats(options = {}) {
30334
30415
  projectRoot: index.projectRoot,
30335
30416
  branch: index.branch,
30336
30417
  indexedAt: index.indexedAt,
30418
+ indexAgeMs: Math.max(0, Date.now() - Date.parse(index.indexedAt)),
30337
30419
  files: Object.keys(index.files).length,
30338
30420
  symbols: Object.values(index.files).reduce((sum, file) => sum + file.symbols.length, 0),
30339
30421
  imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
30422
+ languageBreakdown: index.stats.languageBreakdown,
30423
+ rebuiltFiles: index.stats.rebuiltFiles,
30424
+ reusedFiles: index.stats.reusedFiles,
30425
+ skippedFiles: index.stats.skippedFiles,
30340
30426
  indexPath: getCodeContextIndexPath(index.projectRoot)
30341
30427
  };
30342
30428
  }
30343
- var INDEX_VERSION, DEFAULT_MAX_FILES, CODE_EXTENSIONS, IGNORE_SEGMENTS;
30429
+ var INDEX_VERSION, DEFAULT_MAX_FILES, IGNORE_SEGMENTS;
30344
30430
  var init_code_context_index = __esm({
30345
30431
  "src/lib/code-context-index.ts"() {
30346
30432
  "use strict";
30347
30433
  init_config();
30348
30434
  init_code_chunker();
30349
- INDEX_VERSION = 1;
30350
- DEFAULT_MAX_FILES = 2e3;
30351
- CODE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
30352
- IGNORE_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".worktrees", ".next", "build"]);
30435
+ INDEX_VERSION = 2;
30436
+ DEFAULT_MAX_FILES = 5e3;
30437
+ IGNORE_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".worktrees", ".next", "build", "target", "vendor"]);
30353
30438
  }
30354
30439
  });
30355
30440
 
@@ -30373,10 +30458,14 @@ function registerCodeContext(server) {
30373
30458
  file_path: z88.string().optional().describe("File path for blast_radius"),
30374
30459
  force: z88.boolean().optional().describe("Force rebuild before answering"),
30375
30460
  limit: z88.coerce.number().int().min(1).max(100).optional().describe("Max results"),
30461
+ offset: z88.coerce.number().int().min(0).optional().describe("Search pagination offset"),
30462
+ refresh_index: z88.boolean().optional().describe("Refresh/rebuild index before searching"),
30463
+ languages: z88.array(z88.string()).optional().describe('Language filters, e.g. ["python", "typescript"]'),
30464
+ paths: z88.array(z88.string()).optional().describe("Path/glob filters"),
30376
30465
  depth: z88.coerce.number().int().min(1).max(5).optional().describe("Dependent traversal depth for blast_radius"),
30377
30466
  max_files: z88.coerce.number().int().min(1).max(1e4).optional().describe("Max code files to index")
30378
30467
  }
30379
- }, async ({ action, project_root, query, symbol, file_path, force, limit, depth, max_files }) => {
30468
+ }, async ({ action, project_root, query, symbol, file_path, force, limit, offset, refresh_index, languages, paths, depth, max_files }) => {
30380
30469
  const opts = { projectRoot: project_root, force, maxFiles: max_files };
30381
30470
  if (action === "index") {
30382
30471
  const index = buildCodeContextIndex(opts);
@@ -30394,7 +30483,15 @@ function registerCodeContext(server) {
30394
30483
  }
30395
30484
  if (action === "search") {
30396
30485
  if (!query) return errorResult10('code_context action "search" requires query');
30397
- return jsonResult({ query, results: searchCodeContext(query, { ...opts, limit }) });
30486
+ return jsonResult({
30487
+ query,
30488
+ limit: limit ?? 20,
30489
+ offset: offset ?? 0,
30490
+ refresh_index: refresh_index ?? false,
30491
+ languages: languages ?? [],
30492
+ paths: paths ?? [],
30493
+ results: searchCodeContext(query, { ...opts, limit, offset, refreshIndex: refresh_index, languages, paths })
30494
+ });
30398
30495
  }
30399
30496
  if (action === "trace") {
30400
30497
  if (!symbol) return errorResult10('code_context action "trace" requires symbol');
@@ -32139,6 +32236,7 @@ var DAEMON_TOKEN_ENV2 = "EXE_DAEMON_TOKEN";
32139
32236
  var _context = null;
32140
32237
  var _model = null;
32141
32238
  var _llama = null;
32239
+ var _shuttingDown = false;
32142
32240
  var _daemonToken = "";
32143
32241
  var MAX_QUEUE_SIZE = 1e3;
32144
32242
  var highQueue = [];
@@ -32240,6 +32338,8 @@ function checkIdle() {
32240
32338
  }
32241
32339
  }
32242
32340
  async function shutdown() {
32341
+ if (_shuttingDown) return;
32342
+ _shuttingDown = true;
32243
32343
  resetIdleTimer2();
32244
32344
  flushToDisk();
32245
32345
  try {
@@ -32252,6 +32352,19 @@ async function shutdown() {
32252
32352
  disposeShards2();
32253
32353
  } catch {
32254
32354
  }
32355
+ if (process.platform === "darwin" && _llama) {
32356
+ try {
32357
+ unlinkSync14(SOCKET_PATH2);
32358
+ } catch {
32359
+ }
32360
+ try {
32361
+ unlinkSync14(PID_PATH4);
32362
+ } catch {
32363
+ }
32364
+ process.stderr.write("[exed] Shutdown complete (darwin fast native exit).\n");
32365
+ process.kill(process.pid, "SIGKILL");
32366
+ process.exit(0);
32367
+ }
32255
32368
  if (_context) {
32256
32369
  try {
32257
32370
  await _context.dispose();
@@ -32266,7 +32379,13 @@ async function shutdown() {
32266
32379
  }
32267
32380
  _model = null;
32268
32381
  }
32269
- _llama = null;
32382
+ if (_llama) {
32383
+ try {
32384
+ await _llama.dispose();
32385
+ } catch {
32386
+ }
32387
+ _llama = null;
32388
+ }
32270
32389
  try {
32271
32390
  unlinkSync14(SOCKET_PATH2);
32272
32391
  } catch {
@@ -32745,16 +32864,22 @@ async function startMcpHttpServer() {
32745
32864
  for (const [sid, lastSeen] of sessionLastSeen.entries()) {
32746
32865
  if (now2 - lastSeen > MCP_SESSION_TTL_MS) closeMcpSession2(sid, "session_expired");
32747
32866
  }
32748
- }, sendJsonRpcError2 = function(res, status, message, extra) {
32867
+ }, getJsonRpcId2 = function(body) {
32868
+ if (body && typeof body === "object" && !Array.isArray(body) && "id" in body) {
32869
+ const id = body.id;
32870
+ if (typeof id === "string" || typeof id === "number") return id;
32871
+ }
32872
+ return "unknown";
32873
+ }, sendJsonRpcError2 = function(res, status, message, id, extra) {
32749
32874
  res.writeHead(status, { "Content-Type": "application/json" });
32750
32875
  res.end(JSON.stringify({
32751
32876
  jsonrpc: "2.0",
32752
32877
  error: { code: -32e3, message },
32753
- id: null
32878
+ id
32754
32879
  }));
32755
32880
  recordMcpHttpEvent({ level: "warn", message, status, ...extra });
32756
32881
  };
32757
- var parseDurationMs = parseDurationMs2, closeMcpSession = closeMcpSession2, sweepStaleMcpSessions = sweepStaleMcpSessions2, sendJsonRpcError = sendJsonRpcError2;
32882
+ var parseDurationMs = parseDurationMs2, closeMcpSession = closeMcpSession2, sweepStaleMcpSessions = sweepStaleMcpSessions2, getJsonRpcId = getJsonRpcId2, sendJsonRpcError = sendJsonRpcError2;
32758
32883
  const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
32759
32884
  const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
32760
32885
  const { isInitializeRequest } = await import("@modelcontextprotocol/sdk/types.js");
@@ -32783,14 +32908,14 @@ async function startMcpHttpServer() {
32783
32908
  const url = new URL(req.url || "/", `http://127.0.0.1:${MCP_HTTP_PORT}`);
32784
32909
  const authHeader = req.headers.authorization;
32785
32910
  if (!authHeader || authHeader !== `Bearer ${_daemonToken}`) {
32786
- sendJsonRpcError2(res, 401, "Unauthorized: invalid or missing daemon token", {
32911
+ sendJsonRpcError2(res, 401, "Unauthorized: invalid or missing daemon token", "unknown", {
32787
32912
  method: req.method,
32788
32913
  path: url.pathname
32789
32914
  });
32790
32915
  return;
32791
32916
  }
32792
32917
  if (url.pathname !== "/mcp") {
32793
- sendJsonRpcError2(res, 404, "Not Found: MCP endpoint is /mcp", {
32918
+ sendJsonRpcError2(res, 404, "Not Found: MCP endpoint is /mcp", "unknown", {
32794
32919
  method: req.method,
32795
32920
  path: url.pathname
32796
32921
  });
@@ -32814,7 +32939,7 @@ async function startMcpHttpServer() {
32814
32939
  });
32815
32940
  parsedBody = body ? JSON.parse(body) : void 0;
32816
32941
  } catch (err) {
32817
- sendJsonRpcError2(res, 400, "Bad Request: invalid JSON body", {
32942
+ sendJsonRpcError2(res, 400, "Bad Request: invalid JSON body", "unknown", {
32818
32943
  method: req.method,
32819
32944
  agentId,
32820
32945
  agentRole,
@@ -32855,7 +32980,7 @@ async function startMcpHttpServer() {
32855
32980
  await sessionMcp.connect(transport);
32856
32981
  } else {
32857
32982
  const message = sessionId ? "Bad Request: MCP session is stale or unknown; reconnect MCP client" : "Bad Request: missing MCP session; initialize before tool calls";
32858
- sendJsonRpcError2(res, 400, message, {
32983
+ sendJsonRpcError2(res, 400, message, getJsonRpcId2(parsedBody), {
32859
32984
  method: req.method,
32860
32985
  hasSessionId: Boolean(sessionId),
32861
32986
  sessionId,
@@ -32914,7 +33039,7 @@ async function startMcpHttpServer() {
32914
33039
  error: err instanceof Error ? err.message : String(err)
32915
33040
  });
32916
33041
  if (!res.headersSent) {
32917
- sendJsonRpcError2(res, 500, "Internal Server Error: MCP request failed", {
33042
+ sendJsonRpcError2(res, 500, "Internal Server Error: MCP request failed", getJsonRpcId2(parsedBody), {
32918
33043
  method: req.method,
32919
33044
  sessionId,
32920
33045
  agentId,
@@ -33582,8 +33707,31 @@ function startBackgroundJobGuardrails() {
33582
33707
  timer.unref();
33583
33708
  process.stderr.write("[exed] Background job guardrails started (every 60s)\n");
33584
33709
  }
33585
- process.on("SIGINT", () => void shutdown());
33586
- process.on("SIGTERM", () => void shutdown());
33710
+ function handleSignalShutdown() {
33711
+ if (process.platform === "darwin" && _llama) {
33712
+ if (_shuttingDown) return;
33713
+ _shuttingDown = true;
33714
+ resetIdleTimer2();
33715
+ try {
33716
+ flushToDisk();
33717
+ } catch {
33718
+ }
33719
+ try {
33720
+ unlinkSync14(SOCKET_PATH2);
33721
+ } catch {
33722
+ }
33723
+ try {
33724
+ unlinkSync14(PID_PATH4);
33725
+ } catch {
33726
+ }
33727
+ process.stderr.write("[exed] Shutdown complete (darwin signal fast native exit).\n");
33728
+ process.kill(process.pid, "SIGKILL");
33729
+ process.exit(0);
33730
+ }
33731
+ void shutdown();
33732
+ }
33733
+ process.on("SIGINT", handleSignalShutdown);
33734
+ process.on("SIGTERM", handleSignalShutdown);
33587
33735
  function checkExistingDaemon() {
33588
33736
  try {
33589
33737
  if (!existsSync46(PID_PATH4)) return false;