zigsm 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@ It uses the same approach as Zig's official autodoc (ziglang.org) by reading STD
12
12
  By default, the server uses your locally installed Zig compiler to serve documentation, ensuring you always get docs that match your actual Zig version. It can also fetch documentation from ziglang.org if needed.
13
13
 
14
14
  > [!TIP]
15
- > Add `use zigdocs` to your prompt if you want to explicitly instruct the LLM to use Zig docs tools. Otherwise, LLM will automatically decide when to utilize MCP tools based on the context of your questions.
15
+ > Add `use zsm` to your prompt if you want to explicitly instruct the LLM to use zsm tools. Otherwise, LLM will automatically decide when to utilize MCP tools based on the context of your questions.
16
16
 
17
17
  <p align="center" width="100%">
18
18
  <img src="https://raw.githubusercontent.com/zig-wasm/.github/refs/heads/main/static/readme_mcp_1.gif" width="49%" />
@@ -82,7 +82,7 @@ When using `--doc-source remote`, documentation is fetched from ziglang.org and
82
82
 
83
83
  Optional environment variables:
84
84
  - `VOYAGE_API_KEY` - Enables embeddings for doc search when set.
85
- - `VOYAGE_MODEL` - Embedding model override (default: `voyage-3-lite`).
85
+ - `VOYAGE_MODEL` - Embedding model override (default: `voyage-code-3`).
86
86
 
87
87
  Embeddings are cached per Zig version, doc source, and model in the same cache directory used for docs.
88
88
 
@@ -117,13 +117,19 @@ Add the JSON configuration below to your MCP settings file.
117
117
 
118
118
  ### JSON Configuration Template
119
119
 
120
+ To enable semantic search with Voyage embeddings, include an `env` block like shown below.
121
+
120
122
  **Node.js:**
121
123
  ```json
122
124
  {
123
125
  "mcpServers": {
124
126
  "zsm": {
125
127
  "command": "npx",
126
- "args": ["-y", "zigsm@latest"]
128
+ "args": ["-y", "zigsm@latest"],
129
+ "env": {
130
+ "VOYAGE_API_KEY": "your_voyage_api_key",
131
+ "VOYAGE_MODEL": "voyage-code-3"
132
+ }
127
133
  }
128
134
  }
129
135
  }
@@ -135,7 +141,11 @@ Add the JSON configuration below to your MCP settings file.
135
141
  "mcpServers": {
136
142
  "zsm": {
137
143
  "command": "bunx",
138
- "args": ["zigsm@latest"]
144
+ "args": ["zigsm@latest"],
145
+ "env": {
146
+ "VOYAGE_API_KEY": "your_voyage_api_key",
147
+ "VOYAGE_MODEL": "voyage-code-3"
148
+ }
139
149
  }
140
150
  }
141
151
  }
@@ -161,7 +171,11 @@ claude mcp add zsm -- bunx zigsm@latest --doc-source remote --version 0.14.1
161
171
  "mcpServers": {
162
172
  "zsm": {
163
173
  "command": "npx",
164
- "args": ["-y", "zigsm@latest", "--doc-source", "remote", "--version", "master"]
174
+ "args": ["-y", "zigsm@latest", "--doc-source", "remote", "--version", "master"],
175
+ "env": {
176
+ "VOYAGE_API_KEY": "your_voyage_api_key",
177
+ "VOYAGE_MODEL": "voyage-code-3"
178
+ }
165
179
  }
166
180
  }
167
181
  }
@@ -173,7 +187,11 @@ claude mcp add zsm -- bunx zigsm@latest --doc-source remote --version 0.14.1
173
187
  "mcpServers": {
174
188
  "zsm": {
175
189
  "command": "bunx",
176
- "args": ["zigsm@latest", "--doc-source", "remote", "--version", "0.14.1"]
190
+ "args": ["zigsm@latest", "--doc-source", "remote", "--version", "0.14.1"],
191
+ "env": {
192
+ "VOYAGE_API_KEY": "your_voyage_api_key",
193
+ "VOYAGE_MODEL": "voyage-code-3"
194
+ }
177
195
  }
178
196
  }
179
197
  }
package/dist/mcp.js CHANGED
@@ -103,7 +103,7 @@ async function main() {
103
103
  const builtinFunctions = await ensureDocs(options.version, options.updatePolicy, true, options.docSource);
104
104
  const stdSources = await downloadSourcesTar(options.version, true, false, options.docSource);
105
105
  const mcpServer = new McpServer({
106
- name: "ZigDocs",
106
+ name: "ZSM",
107
107
  description: "Retrieves up-to-date documentation for the Zig programming language standard library and builtin functions.",
108
108
  version: options.version,
109
109
  });
package/dist/std.js CHANGED
@@ -741,6 +741,8 @@ function setInputString(s) {
741
741
  wasmArray.set(jsArray);
742
742
  }
743
743
  let inMemoryEmbeddingCache = null;
744
+ let cachedSourceTextIndex = null;
745
+ let cachedSourceTextRef = null;
744
746
  async function initWasmRuntime(wasmPath, stdSources) {
745
747
  const fs = await import("node:fs");
746
748
  const wasmBytes = typeof wasmPath === "string" ? fs.readFileSync(wasmPath) : wasmPath;
@@ -813,6 +815,158 @@ function stripMarkdown(input) {
813
815
  .replace(/\s+/g, " ")
814
816
  .trim();
815
817
  }
818
+ function parseTarOctal(raw) {
819
+ const cleaned = raw.replace(/\0/g, "").trim();
820
+ if (!cleaned)
821
+ return 0;
822
+ const parsed = Number.parseInt(cleaned, 8);
823
+ return Number.isFinite(parsed) ? parsed : 0;
824
+ }
825
+ function parseSourcesTar(stdSources) {
826
+ const files = [];
827
+ let offset = 0;
828
+ while (offset + 512 <= stdSources.length) {
829
+ const header = stdSources.subarray(offset, offset + 512);
830
+ let emptyHeader = true;
831
+ for (let i = 0; i < 512; i++) {
832
+ if (header[i] !== 0) {
833
+ emptyHeader = false;
834
+ break;
835
+ }
836
+ }
837
+ if (emptyHeader)
838
+ break;
839
+ const name = text_decoder.decode(header.subarray(0, 100)).replace(/\0.*$/, "").trim();
840
+ const sizeRaw = text_decoder
841
+ .decode(header.subarray(124, 136))
842
+ .replace(/\0.*$/, "")
843
+ .trim();
844
+ const prefix = text_decoder
845
+ .decode(header.subarray(345, 500))
846
+ .replace(/\0.*$/, "")
847
+ .trim();
848
+ const size = parseTarOctal(sizeRaw);
849
+ const fullPath = prefix.length > 0 ? `${prefix}/${name}` : name;
850
+ const contentStart = offset + 512;
851
+ const contentEnd = contentStart + size;
852
+ if (size > 0 && contentEnd <= stdSources.length && fullPath.endsWith(".zig")) {
853
+ const text = text_decoder.decode(stdSources.subarray(contentStart, contentEnd));
854
+ files.push({ path: fullPath, text, lowerText: text.toLowerCase() });
855
+ }
856
+ const paddedSize = Math.ceil(size / 512) * 512;
857
+ offset = contentStart + paddedSize;
858
+ }
859
+ return files;
860
+ }
861
+ function buildSourceTextIndex(stdSources) {
862
+ if (cachedSourceTextIndex && cachedSourceTextRef === stdSources) {
863
+ return cachedSourceTextIndex;
864
+ }
865
+ const files = parseSourcesTar(stdSources);
866
+ const decls = [];
867
+ const declRegex = /^\s*(?:pub\s+)?(?:const|var|fn)\s+([A-Za-z_][A-Za-z0-9_]*)/;
868
+ for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
869
+ const file = files[fileIndex];
870
+ const lines = file.text.split("\n");
871
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
872
+ const line = lines[lineIndex];
873
+ const match = line.match(declRegex);
874
+ if (!match)
875
+ continue;
876
+ decls.push({
877
+ name: match[1],
878
+ path: file.path,
879
+ line: lineIndex + 1,
880
+ preview: line.trim(),
881
+ });
882
+ }
883
+ }
884
+ cachedSourceTextRef = stdSources;
885
+ cachedSourceTextIndex = { files, decls };
886
+ return cachedSourceTextIndex;
887
+ }
888
+ function fallbackSearchStdLib(stdSources, query, limit) {
889
+ const index = buildSourceTextIndex(stdSources);
890
+ const queryLower = query.toLowerCase().trim();
891
+ const scored = index.decls
892
+ .map((decl) => {
893
+ const nameLower = decl.name.toLowerCase();
894
+ const pathLower = decl.path.toLowerCase();
895
+ let score = 0;
896
+ if (nameLower === queryLower)
897
+ score += 1000;
898
+ else if (nameLower.startsWith(queryLower))
899
+ score += 700;
900
+ else if (nameLower.includes(queryLower))
901
+ score += 450;
902
+ if (pathLower.includes(queryLower))
903
+ score += 150;
904
+ return { decl, score };
905
+ })
906
+ .filter((item) => item.score > 0)
907
+ .sort((a, b) => b.score - a.score);
908
+ let markdown = `# Search Results\n\nQuery: "${query}"\n\n`;
909
+ markdown += `_Fallback mode: text index (parser-incompatible Zig version)_\n\n`;
910
+ if (scored.length === 0) {
911
+ markdown += "No results found.";
912
+ return markdown;
913
+ }
914
+ const limited = scored.slice(0, limit);
915
+ markdown += `Found ${scored.length} results (showing ${limited.length}):\n\n`;
916
+ for (let i = 0; i < limited.length; i++) {
917
+ const entry = limited[i].decl;
918
+ markdown += `- std.${entry.name} (${entry.path}:${entry.line})\n`;
919
+ }
920
+ return markdown;
921
+ }
922
+ function fallbackGetStdLibItem(stdSources, name, getSourceFile) {
923
+ const index = buildSourceTextIndex(stdSources);
924
+ if (getSourceFile) {
925
+ const normalized = name.replace(/^src\//, "");
926
+ const matchedFile = index.files.find((file) => file.path === normalized) ||
927
+ index.files.find((file) => file.path.endsWith(`/${normalized}`)) ||
928
+ index.files.find((file) => file.path.endsWith(name));
929
+ if (!matchedFile) {
930
+ return `# Error\n\nCould not find source file for "${name}" in fallback mode.`;
931
+ }
932
+ return `# ${matchedFile.path}\n\n${matchedFile.text}`;
933
+ }
934
+ const target = name.split(".").pop()?.trim() || name.trim();
935
+ const targetLower = target.toLowerCase();
936
+ const ranked = index.decls
937
+ .map((decl) => {
938
+ const declLower = decl.name.toLowerCase();
939
+ let score = 0;
940
+ if (declLower === targetLower)
941
+ score += 1000;
942
+ else if (declLower.startsWith(targetLower))
943
+ score += 700;
944
+ else if (declLower.includes(targetLower))
945
+ score += 450;
946
+ return { decl, score };
947
+ })
948
+ .filter((item) => item.score > 0)
949
+ .sort((a, b) => b.score - a.score);
950
+ if (ranked.length === 0) {
951
+ return `# Error\n\nDeclaration "${name}" not found (fallback mode).`;
952
+ }
953
+ const best = ranked[0].decl;
954
+ const file = index.files.find((entry) => entry.path === best.path);
955
+ if (!file) {
956
+ return `# Error\n\nDeclaration "${name}" matched, but source file could not be loaded.`;
957
+ }
958
+ const lines = file.text.split("\n");
959
+ const startLine = Math.max(1, best.line - 20);
960
+ const endLine = Math.min(lines.length, best.line + 80);
961
+ const snippet = lines.slice(startLine - 1, endLine).join("\n");
962
+ let markdown = `# ${name}\n\n`;
963
+ markdown += `_Fallback mode: text index (parser-incompatible Zig version)_\n\n`;
964
+ markdown += `Match: ${best.path}:${best.line}\n\n`;
965
+ markdown += "```zig\n";
966
+ markdown += snippet;
967
+ markdown += "\n```\n";
968
+ return markdown;
969
+ }
816
970
  function buildDeclEmbeddingText(decl) {
817
971
  const category = wasm_exports.categorize_decl(decl, 0);
818
972
  const fqn = fullyQualifiedName(decl);
@@ -832,6 +986,14 @@ function buildDeclEmbeddingText(decl) {
832
986
  const text = stripMarkdown(`${fqn}\n${name}\n${proto}\n${typeInfo}\n${docs}\n${source}`).slice(0, 4000);
833
987
  return { decl, fqn, text };
834
988
  }
989
+ function tryBuildDeclEmbeddingText(decl) {
990
+ try {
991
+ return buildDeclEmbeddingText(decl);
992
+ }
993
+ catch {
994
+ return null;
995
+ }
996
+ }
835
997
  async function getEmbeddingCachePath(zigVersion, docSource, model) {
836
998
  const envPathsMod = await import("env-paths");
837
999
  const path = await import("node:path");
@@ -891,9 +1053,14 @@ async function getOrBuildEmbeddingCache(wasmPath, stdSources, options) {
891
1053
  inMemoryEmbeddingCache = existing;
892
1054
  return existing;
893
1055
  }
894
- await initWasmRuntime(wasmPath, stdSources);
895
1056
  const decls = collectDeclsForEmbeddings();
896
- const docs = decls.map((decl) => buildDeclEmbeddingText(decl)).filter((doc) => doc.text.length > 0);
1057
+ const docs = [];
1058
+ for (let i = 0; i < decls.length; i++) {
1059
+ const doc = tryBuildDeclEmbeddingText(decls[i]);
1060
+ if (doc && doc.text.length > 0) {
1061
+ docs.push(doc);
1062
+ }
1063
+ }
897
1064
  if (docs.length === 0) {
898
1065
  return null;
899
1066
  }
@@ -929,7 +1096,12 @@ function buildHybridRanking(lexicalDecls, semanticDecls, maxResults) {
929
1096
  .map(([decl]) => decl);
930
1097
  }
931
1098
  export async function searchStdLib(wasmPath, stdSources, query, limit = 20, options = {}) {
932
- await initWasmRuntime(wasmPath, stdSources);
1099
+ try {
1100
+ await initWasmRuntime(wasmPath, stdSources);
1101
+ }
1102
+ catch {
1103
+ return fallbackSearchStdLib(stdSources, query, limit);
1104
+ }
933
1105
  const ignoreCase = query.toLowerCase() === query;
934
1106
  const lexicalResults = Array.from(executeQuery(query, ignoreCase));
935
1107
  let mergedResults = lexicalResults;
@@ -968,7 +1140,12 @@ export async function searchStdLib(wasmPath, stdSources, query, limit = 20, opti
968
1140
  return markdown;
969
1141
  }
970
1142
  export async function getStdLibItem(wasmPath, stdSources, name, getSourceFile = false) {
971
- await initWasmRuntime(wasmPath, stdSources);
1143
+ try {
1144
+ await initWasmRuntime(wasmPath, stdSources);
1145
+ }
1146
+ catch {
1147
+ return fallbackGetStdLibItem(stdSources, name, getSourceFile);
1148
+ }
972
1149
  const exports = wasm_exports;
973
1150
  const decl_index = findDecl(name);
974
1151
  if (decl_index === null) {
package/dist/voyage.js CHANGED
@@ -6,7 +6,7 @@ export function getVoyageConfig() {
6
6
  }
7
7
  return {
8
8
  apiKey,
9
- model: env?.VOYAGE_MODEL?.trim() || "voyage-3-lite",
9
+ model: env?.VOYAGE_MODEL?.trim() || "voyage-code-3",
10
10
  };
11
11
  }
12
12
  async function embedBatch(input, config, inputType) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigsm",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zsm": "dist/mcp.js"
@@ -24,14 +24,14 @@
24
24
  "description": "MCP server that provides up-to-date documentation for the Zig programming language standard library and builtin functions.",
25
25
  "repository": {
26
26
  "type": "git",
27
- "url": "git+https://github.com/zig-wasm/zig-mcp.git"
27
+ "url": "git+https://github.com/pokeylooted/zsm.git"
28
28
  },
29
29
  "keywords": [
30
30
  "mcp",
31
31
  "modelcontextprotocol",
32
32
  "zig",
33
33
  "ziglang",
34
- "zig-docs",
34
+ "zsm",
35
35
  "cli"
36
36
  ],
37
37
  "dependencies": {