codemode-lsp 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/dist/index.js +148 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -39054,7 +39054,13 @@ function unifiedDiff(file2, original, updated) {
39054
39054
 
39055
39055
  // src/lsp-api.ts
39056
39056
  var import_vscode_languageserver_protocol2 = __toESM(require_api2(), 1);
39057
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, realpathSync } from "node:fs";
39057
+ import {
39058
+ existsSync as existsSync2,
39059
+ readdirSync,
39060
+ readFileSync as readFileSync2,
39061
+ realpathSync,
39062
+ statSync
39063
+ } from "node:fs";
39058
39064
  import { dirname as dirname2, isAbsolute, relative, resolve, sep } from "node:path";
39059
39065
  import { fileURLToPath, pathToFileURL } from "node:url";
39060
39066
 
@@ -39067,6 +39073,10 @@ import {
39067
39073
  writeFileSync
39068
39074
  } from "node:fs";
39069
39075
  import { dirname } from "node:path";
39076
+ var LSP_DOCUMENT_EXTENSIONS = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i;
39077
+ function isLspDocumentPath(path) {
39078
+ return LSP_DOCUMENT_EXTENSIONS.test(path);
39079
+ }
39070
39080
 
39071
39081
  class DeletedFileError extends Error {
39072
39082
  constructor(relPath) {
@@ -39102,6 +39112,9 @@ class TransactionalBuffer {
39102
39112
  isDeleted(absPath) {
39103
39113
  return this.files.get(absPath)?.state === "deleted";
39104
39114
  }
39115
+ peekText(absPath) {
39116
+ return this.files.get(absPath)?.current;
39117
+ }
39105
39118
  track(absPath) {
39106
39119
  const existing = this.files.get(absPath);
39107
39120
  if (existing)
@@ -39113,10 +39126,11 @@ class TransactionalBuffer {
39113
39126
  state: "clean",
39114
39127
  original,
39115
39128
  current: original,
39116
- existedOnDisk
39129
+ existedOnDisk,
39130
+ lspVisible: isLspDocumentPath(absPath)
39117
39131
  };
39118
39132
  this.files.set(absPath, tracked);
39119
- if (existedOnDisk && original !== undefined) {
39133
+ if (tracked.lspVisible && existedOnDisk && original !== undefined) {
39120
39134
  this.client.didOpen(absPath, original);
39121
39135
  }
39122
39136
  return tracked;
@@ -39126,14 +39140,18 @@ class TransactionalBuffer {
39126
39140
  if (tracked.state === "deleted") {
39127
39141
  tracked.state = tracked.existedOnDisk ? "modified" : "created";
39128
39142
  tracked.current = text;
39129
- this.client.didOpen(absPath, text);
39130
- this.client.didChangeFullText(absPath, text);
39143
+ if (tracked.lspVisible) {
39144
+ this.client.didOpen(absPath, text);
39145
+ this.client.didChangeFullText(absPath, text);
39146
+ }
39131
39147
  return;
39132
39148
  }
39133
39149
  tracked.current = text;
39134
39150
  if (tracked.state === "clean") {
39135
39151
  tracked.state = tracked.existedOnDisk ? "modified" : "created";
39136
39152
  }
39153
+ if (!tracked.lspVisible)
39154
+ return;
39137
39155
  if (!this.client.isOpen(absPath)) {
39138
39156
  this.client.didOpen(absPath, text);
39139
39157
  } else {
@@ -39150,7 +39168,8 @@ class TransactionalBuffer {
39150
39168
  throw new Error(`Cannot delete "${relPath}": the file does not exist. Use lsp.listFiles() to discover project files.`);
39151
39169
  }
39152
39170
  const wasCreatedThisScript = !tracked.existedOnDisk;
39153
- this.client.didClose(absPath);
39171
+ if (tracked.lspVisible)
39172
+ this.client.didClose(absPath);
39154
39173
  if (wasCreatedThisScript) {
39155
39174
  this.files.delete(absPath);
39156
39175
  return;
@@ -39167,6 +39186,8 @@ class TransactionalBuffer {
39167
39186
  if (tracked.state === "clean") {
39168
39187
  tracked.state = tracked.existedOnDisk ? "modified" : "created";
39169
39188
  }
39189
+ if (!tracked.lspVisible)
39190
+ return;
39170
39191
  if (!this.client.isOpen(absPath)) {
39171
39192
  this.client.didOpen(absPath, tracked.current);
39172
39193
  } else {
@@ -39235,7 +39256,7 @@ class TransactionalBuffer {
39235
39256
  continue;
39236
39257
  }
39237
39258
  try {
39238
- if (file2.state === "modified") {
39259
+ if (!file2.lspVisible) {} else if (file2.state === "modified") {
39239
39260
  if (file2.original !== undefined && this.client.isOpen(absPath)) {
39240
39261
  this.client.didChangeFullText(absPath, file2.original);
39241
39262
  }
@@ -39253,6 +39274,8 @@ class TransactionalBuffer {
39253
39274
  }
39254
39275
  rollback() {
39255
39276
  for (const [absPath, file2] of this.files) {
39277
+ if (!file2.lspVisible)
39278
+ continue;
39256
39279
  try {
39257
39280
  if (file2.state === "modified") {
39258
39281
  if (file2.original !== undefined && this.client.isOpen(absPath)) {
@@ -39682,12 +39705,27 @@ class LspApi {
39682
39705
  await this.client.ensureAlive();
39683
39706
  }
39684
39707
  readTextSafe(resolved) {
39685
- if (this.buffer?.isDeleted(resolved.absPath))
39686
- return "";
39708
+ if (this.buffer) {
39709
+ if (this.buffer.isDeleted(resolved.absPath))
39710
+ return "";
39711
+ const buffered = this.buffer.peekText(resolved.absPath);
39712
+ if (buffered !== undefined)
39713
+ return buffered;
39714
+ }
39687
39715
  try {
39688
- return this.readText(resolved);
39716
+ return readFileSync2(resolved.absPath, "utf8");
39689
39717
  } catch {
39690
- return existsSync2(resolved.absPath) ? readFileSync2(resolved.absPath, "utf8") : "";
39718
+ return "";
39719
+ }
39720
+ }
39721
+ requireStrings(signature, example, args, contentArgs = []) {
39722
+ for (const [name, value] of Object.entries(args)) {
39723
+ const allowEmpty = contentArgs.includes(name);
39724
+ if (typeof value === "string" && (allowEmpty || value.trim() !== "")) {
39725
+ continue;
39726
+ }
39727
+ const got = value === undefined ? "nothing (missing argument)" : value === null ? "null" : Array.isArray(value) ? "an array" : `${typeof value === "object" ? "an" : "a"} ${typeof value}`;
39728
+ throw new Error(`${signature}: "${name}" must be a ${allowEmpty ? "" : "non-empty "}string but got ${got}. Example: ${example}`);
39691
39729
  }
39692
39730
  }
39693
39731
  resolveWorkspacePath(file2) {
@@ -39718,11 +39756,15 @@ class LspApi {
39718
39756
  }
39719
39757
  }
39720
39758
  async readFile(file2) {
39759
+ this.requireStrings("readFile(file)", 'await lsp.readFile("src/auth.ts")', {
39760
+ file: file2
39761
+ });
39721
39762
  const resolved = this.resolveWorkspacePath(file2);
39722
39763
  await this.ensureReadyForBuffer();
39723
39764
  return this.readText(resolved);
39724
39765
  }
39725
39766
  async getSymbolBody(file2, symbolPath) {
39767
+ this.requireStrings("getSymbolBody(file, symbolPath)", 'await lsp.getSymbolBody("src/auth.ts", "AuthService/validate") — get file and symbolPath from getSymbols(file)', { file: file2, symbolPath });
39726
39768
  const resolved = this.resolveWorkspacePath(file2);
39727
39769
  await this.ensureReadyForBuffer();
39728
39770
  const text = this.readText(resolved);
@@ -39737,12 +39779,14 @@ class LspApi {
39737
39779
  return text.slice(start, end);
39738
39780
  }
39739
39781
  async getSymbols(file2) {
39782
+ this.requireStrings("getSymbols(file)", 'await lsp.getSymbols("src/auth.ts")', { file: file2 });
39740
39783
  const resolved = this.resolveWorkspacePath(file2);
39741
39784
  await this.ensureReadyForBuffer();
39742
39785
  const text = this.readText(resolved);
39743
39786
  return buildSymbolInfoTree(await this.documentSymbols(resolved), text);
39744
39787
  }
39745
39788
  async findSymbol(query) {
39789
+ this.requireStrings("findSymbol(query)", 'await lsp.findSymbol("AuthService")', { query });
39746
39790
  const symbols = await this.workspaceSymbolWithIndexWait(query);
39747
39791
  const mapped = [];
39748
39792
  for (const symbol2 of symbols) {
@@ -39768,6 +39812,7 @@ class LspApi {
39768
39812
  return mapped;
39769
39813
  }
39770
39814
  async findReferences(file2, symbolPath) {
39815
+ this.requireStrings("findReferences(file, symbolPath)", 'await lsp.findReferences("src/auth.ts", "AuthService/validate") — get file and symbolPath from getSymbols(file)', { file: file2, symbolPath });
39771
39816
  const resolved = this.resolveWorkspacePath(file2);
39772
39817
  const symbol2 = resolveSymbolPath({
39773
39818
  file: resolved.relPath,
@@ -39797,6 +39842,7 @@ class LspApi {
39797
39842
  return references;
39798
39843
  }
39799
39844
  async goToDefinition(file2, symbolPath) {
39845
+ this.requireStrings("goToDefinition(file, symbolPath)", 'await lsp.goToDefinition("src/auth.ts", "AuthService/validate")', { file: file2, symbolPath });
39800
39846
  const resolved = this.resolveWorkspacePath(file2);
39801
39847
  const symbol2 = resolveSymbolPath({
39802
39848
  file: resolved.relPath,
@@ -39814,6 +39860,10 @@ class LspApi {
39814
39860
  return this.locationToApiLocation(first);
39815
39861
  }
39816
39862
  async searchText(pattern, glob) {
39863
+ this.requireStrings("searchText(pattern, glob?)", 'await lsp.searchText("new NotFoundError\\\\(", "src/**") — the pattern is a regex; escape metacharacters for literal text', { pattern });
39864
+ if (glob !== undefined && glob !== null && typeof glob !== "string") {
39865
+ throw new Error('searchText(pattern, glob?) takes a glob STRING as its second argument, e.g. searchText("TODO", "src/**"). There is no options object — filter or slice the returned array in your script instead.');
39866
+ }
39817
39867
  let regex;
39818
39868
  try {
39819
39869
  regex = new RegExp(pattern, "g");
@@ -39844,8 +39894,38 @@ class LspApi {
39844
39894
  }
39845
39895
  return results;
39846
39896
  }
39897
+ normalizeGlob(glob) {
39898
+ if (glob === undefined || glob === null)
39899
+ return "**/*";
39900
+ if (typeof glob !== "string") {
39901
+ throw new Error(`listFiles/searchText globs must be strings like "src/**/*.ts" (got ${typeof glob}). Omit the glob to cover every file.`);
39902
+ }
39903
+ let candidate = glob.trim();
39904
+ if (candidate.startsWith("./"))
39905
+ candidate = candidate.slice(2);
39906
+ while (candidate.endsWith("/"))
39907
+ candidate = candidate.slice(0, -1);
39908
+ if (candidate === "" || candidate === ".")
39909
+ return "**/*";
39910
+ if (isAbsolute(candidate)) {
39911
+ const rel = relative(this.rootDir, resolve(candidate));
39912
+ if (rel === "")
39913
+ return "**/*";
39914
+ if (rel.startsWith("..") || isAbsolute(rel)) {
39915
+ throw new Error(`Glob "${glob}" points outside the workspace root. Use a workspace-relative pattern like "src/**".`);
39916
+ }
39917
+ candidate = toPosixPath(rel);
39918
+ }
39919
+ if (!/[*?[\]{}]/.test(candidate)) {
39920
+ const abs = resolve(this.rootDir, candidate);
39921
+ if (existsSync2(abs) && statSync(abs).isDirectory()) {
39922
+ return `${candidate}/**`;
39923
+ }
39924
+ }
39925
+ return candidate;
39926
+ }
39847
39927
  async listFiles(glob) {
39848
- const matcher = globToRegExp(glob ?? "**/*");
39928
+ const matcher = globToRegExp(this.normalizeGlob(glob));
39849
39929
  const files = [];
39850
39930
  const visit = (dir) => {
39851
39931
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
@@ -39867,8 +39947,11 @@ class LspApi {
39867
39947
  return files.sort();
39868
39948
  }
39869
39949
  async getDiagnostics(file2) {
39870
- if (file2) {
39950
+ if (file2 !== undefined && file2 !== null) {
39951
+ this.requireStrings("getDiagnostics(file?)", 'await lsp.getDiagnostics("src/auth.ts") — or no argument for every file touched this session', { file: file2 });
39871
39952
  const resolved = this.resolveWorkspacePath(file2);
39953
+ if (!isLspDocumentPath(resolved.absPath))
39954
+ return [];
39872
39955
  await this.client.ensureAlive();
39873
39956
  if (this.buffer)
39874
39957
  this.buffer.track(resolved.absPath);
@@ -39880,6 +39963,7 @@ class LspApi {
39880
39963
  return this.client.getTouchedUris().flatMap((uri) => this.convertDiagnosticsForUri(uri, this.client.getDiagnosticsForUris([uri])));
39881
39964
  }
39882
39965
  async renameSymbol(file2, symbolPath, newName) {
39966
+ this.requireStrings("renameSymbol(file, symbolPath, newName)", 'await lsp.renameSymbol("src/auth.ts", "AuthService/validate", "checkToken")', { file: file2, symbolPath, newName });
39883
39967
  const buffer = this.requireBuffer("renameSymbol");
39884
39968
  const resolved = this.resolveWorkspacePath(file2);
39885
39969
  const symbol2 = resolveSymbolPath({
@@ -39920,12 +40004,14 @@ class LspApi {
39920
40004
  return { file: resolved.relPath, filesChanged, diagnostics };
39921
40005
  }
39922
40006
  async replaceSymbolBody(file2, symbolPath, newText) {
40007
+ this.requireStrings("replaceSymbolBody(file, symbolPath, newText)", 'await lsp.replaceSymbolBody("src/auth.ts", "AuthService/validate", "validate(token: Token): boolean { … }")', { file: file2, symbolPath, newText }, ["newText"]);
39923
40008
  return this.editSymbolRange(file2, symbolPath, (symbol2) => ({
39924
40009
  range: symbol2.range,
39925
40010
  newText
39926
40011
  }));
39927
40012
  }
39928
40013
  async insertBeforeSymbol(file2, symbolPath, text) {
40014
+ this.requireStrings("insertBeforeSymbol(file, symbolPath, text)", 'await lsp.insertBeforeSymbol("src/auth.ts", "AuthService", "// header\\n")', { file: file2, symbolPath, text }, ["text"]);
39929
40015
  return this.editSymbolRange(file2, symbolPath, (symbol2) => ({
39930
40016
  range: {
39931
40017
  start: { line: symbol2.range.start.line, character: 0 },
@@ -39937,6 +40023,7 @@ class LspApi {
39937
40023
  }));
39938
40024
  }
39939
40025
  async insertAfterSymbol(file2, symbolPath, text) {
40026
+ this.requireStrings("insertAfterSymbol(file, symbolPath, text)", 'await lsp.insertAfterSymbol("src/auth.ts", "AuthService", "export const x = 1;\\n")', { file: file2, symbolPath, text }, ["text"]);
39940
40027
  return this.editSymbolRange(file2, symbolPath, (symbol2) => ({
39941
40028
  range: { start: symbol2.range.end, end: symbol2.range.end },
39942
40029
  newText: text.startsWith(`
@@ -39945,6 +40032,7 @@ ${text}`
39945
40032
  }));
39946
40033
  }
39947
40034
  async deleteSymbol(file2, symbolPath) {
40035
+ this.requireStrings("deleteSymbol(file, symbolPath)", 'await lsp.deleteSymbol("src/auth.ts", "AuthService/validate")', { file: file2, symbolPath });
39948
40036
  const buffer = this.requireBuffer("deleteSymbol");
39949
40037
  const resolved = this.resolveWorkspacePath(file2);
39950
40038
  await this.client.ensureAlive();
@@ -39964,6 +40052,7 @@ ${text}`
39964
40052
  };
39965
40053
  }
39966
40054
  async writeFile(file2, content) {
40055
+ this.requireStrings("writeFile(file, content)", 'await lsp.writeFile("src/new.ts", "export const x = 1;\\n")', { file: file2, content }, ["content"]);
39967
40056
  const buffer = this.requireBuffer("writeFile");
39968
40057
  const resolved = this.resolveWorkspacePath(file2);
39969
40058
  await this.client.ensureAlive();
@@ -39976,6 +40065,7 @@ ${text}`
39976
40065
  };
39977
40066
  }
39978
40067
  async deleteFile(file2) {
40068
+ this.requireStrings("deleteFile(file)", 'await lsp.deleteFile("src/old.ts")', { file: file2 });
39979
40069
  const buffer = this.requireBuffer("deleteFile");
39980
40070
  const resolved = this.resolveWorkspacePath(file2);
39981
40071
  await this.client.ensureAlive();
@@ -40058,7 +40148,9 @@ ${text}`
40058
40148
  return byPath;
40059
40149
  }
40060
40150
  async collectDiagnostics(absPaths) {
40061
- const uris = absPaths.map((absPath) => pathToFileURL(absPath).href);
40151
+ const uris = absPaths.filter((absPath) => isLspDocumentPath(absPath)).map((absPath) => pathToFileURL(absPath).href);
40152
+ if (uris.length === 0)
40153
+ return [];
40062
40154
  await this.client.waitForDiagnosticsForUris(uris);
40063
40155
  return uris.flatMap((uri) => this.convertDiagnosticsForUri(uri, this.client.getDiagnosticsForUris([uri])));
40064
40156
  }
@@ -46053,9 +46145,36 @@ function capText(text, cap, marker) {
46053
46145
  return text;
46054
46146
  return text.slice(0, cap) + marker;
46055
46147
  }
46148
+ function findThenablePath(value, path, seen) {
46149
+ if (value === null || typeof value !== "object")
46150
+ return null;
46151
+ if (typeof value.then === "function")
46152
+ return path;
46153
+ if (seen.has(value))
46154
+ return null;
46155
+ seen.add(value);
46156
+ if (Array.isArray(value)) {
46157
+ for (let i2 = 0;i2 < value.length; i2 += 1) {
46158
+ const found = findThenablePath(value[i2], `${path}[${i2}]`, seen);
46159
+ if (found)
46160
+ return found;
46161
+ }
46162
+ return null;
46163
+ }
46164
+ for (const [key, child] of Object.entries(value)) {
46165
+ const found = findThenablePath(child, `${path}.${key}`, seen);
46166
+ if (found)
46167
+ return found;
46168
+ }
46169
+ return null;
46170
+ }
46056
46171
  function serializeResult(value) {
46057
46172
  if (value === undefined)
46058
46173
  return "undefined";
46174
+ const thenableAt = findThenablePath(value, "result", new Set);
46175
+ if (thenableAt !== null) {
46176
+ throw new Error(`Script result contains a Promise at "${thenableAt}" — did you forget ` + "await? Every lsp.* function is async; write e.g. " + '`const files = await lsp.listFiles("src/**");`.');
46177
+ }
46059
46178
  if (typeof value === "function") {
46060
46179
  throw new Error("Script returned a function, which cannot be serialized. Return a " + "JSON-serializable value instead (string, number, boolean, array, or " + "plain object) — e.g. call the function and return its result.");
46061
46180
  }
@@ -46189,7 +46308,7 @@ ${source}
46189
46308
  // src/lsp-types.generated.ts
46190
46309
  var LSP_COMMON_INTERFACES = `interface SymbolInfo {
46191
46310
  name: string;
46192
- /** Exact slash-separated path for use in other lsp.* calls. */
46311
+ /** Slash-separated path within its file; pass it TOGETHER WITH the same \`file\` to other lsp.* calls. */
46193
46312
  path: string;
46194
46313
  /** "class" | "function" | "method" | "variable" | ... */
46195
46314
  kind: string;
@@ -46272,15 +46391,15 @@ var LSP_READ_OP_SIGNATURES = ` /** File contents as a raw string (no line numbe
46272
46391
  getSymbolBody(file: string, symbolPath: string): Promise<string>;
46273
46392
  /** Document symbol tree (file outline). Every path is a usable handle. */
46274
46393
  getSymbols(file: string): Promise<SymbolInfo[]>;
46275
- /** Workspace-wide symbol search; paths are best-effort (confirm with getSymbols). */
46394
+ /** Workspace-wide symbol search; the index warms lazily, so early calls may be empty — getSymbols(file) is exhaustive. */
46276
46395
  findSymbol(query: string): Promise<WorkspaceSymbolInfo[]>;
46277
46396
  /** All references to a symbol across the workspace (incl. the declaration). */
46278
46397
  findReferences(file: string, symbolPath: string): Promise<Reference[]>;
46279
46398
  /** Jump to a symbol's definition. */
46280
46399
  goToDefinition(file: string, symbolPath: string): Promise<Location>;
46281
- /** Regex search across project files (respects .gitignore). */
46400
+ /** Regex search across project files escape metacharacters for literal text; optional second arg is a glob string. */
46282
46401
  searchText(pattern: string, glob?: string): Promise<SearchResult[]>;
46283
- /** Project files matching a glob (respects .gitignore); no glob = all files. */
46402
+ /** Project files matching a glob; no glob = all files, a directory name means everything under it. */
46284
46403
  listFiles(glob?: string): Promise<string[]>;
46285
46404
  /** Diagnostics for a file, or every file touched this session (not project-wide). */
46286
46405
  getDiagnostics(file?: string): Promise<Diagnostic[]>;`;
@@ -46379,9 +46498,19 @@ script's last expression, JSON-serialized.
46379
46498
 
46380
46499
  - The last expression is the return value — end the script with the value you
46381
46500
  want back, e.g. \`({ count })\`.
46501
+ - Every \`lsp.*\` call returns a Promise — always \`await\` it. An un-awaited
46502
+ call inside the result serializes as \`{}\`.
46382
46503
  - \`.filter()\`/\`.map()\` callbacks cannot be async — use \`for...of\` with \`await\`.
46504
+ - Globs match the whole workspace-relative path: \`listFiles("src/**")\` for a
46505
+ directory (a bare directory name like \`"src"\` is treated as \`src/**\`),
46506
+ \`listFiles()\` for every file. \`searchText\`'s second argument is a glob
46507
+ string — there is no options object; filter results in your script.
46383
46508
  - Symbol paths are slash-separated (\`MyClass/myMethod\`); discover exact paths
46384
- with \`getSymbols(file)\` rather than guessing.
46509
+ with \`getSymbols(file)\` rather than guessing. Symbol ops always take the
46510
+ pair \`(file, symbolPath)\` — a \`SymbolInfo.path\` belongs to the file you
46511
+ called \`getSymbols\` on.
46512
+ - \`searchText\` patterns are regexes — escape metacharacters for literal text:
46513
+ \`searchText("new NotFoundError\\\\(")\`.
46385
46514
  - File paths are relative to the workspace root; anything outside it is
46386
46515
  rejected.
46387
46516
  - Diagnostics cover files touched this session only, never the whole project.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemode-lsp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "MCP server exposing a single code-mode tool backed by LSP",
5
5
  "keywords": [
6
6
  "mcp",