@rohal12/spindle-lsp 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +206 -12
- package/dist/bin.js.map +4 -4
- package/package.json +9 -6
package/dist/bin.js
CHANGED
|
@@ -2979,6 +2979,197 @@ var init_format2 = __esm({
|
|
|
2979
2979
|
}
|
|
2980
2980
|
});
|
|
2981
2981
|
|
|
2982
|
+
// src/mcp/server.ts
|
|
2983
|
+
var server_exports = {};
|
|
2984
|
+
__export(server_exports, {
|
|
2985
|
+
startMcpServer: () => startMcpServer
|
|
2986
|
+
});
|
|
2987
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2988
|
+
import { resolve as resolve2 } from "node:path";
|
|
2989
|
+
import { pathToFileURL as pathToFileURL3, fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2990
|
+
import { glob as glob3 } from "glob";
|
|
2991
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2992
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2993
|
+
import { z } from "zod";
|
|
2994
|
+
function filterBySeverity2(diagnostics, minSeverity) {
|
|
2995
|
+
const minIdx = SEVERITY_ORDER2.indexOf(minSeverity);
|
|
2996
|
+
return diagnostics.filter((d) => {
|
|
2997
|
+
const idx = SEVERITY_ORDER2.indexOf(d.severity);
|
|
2998
|
+
return idx >= minIdx;
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
3001
|
+
async function resolveFiles(pattern, cwd) {
|
|
3002
|
+
const matches = await glob3(pattern, {
|
|
3003
|
+
cwd,
|
|
3004
|
+
absolute: true,
|
|
3005
|
+
nodir: true
|
|
3006
|
+
});
|
|
3007
|
+
return [...new Set(matches)];
|
|
3008
|
+
}
|
|
3009
|
+
function findConfigRoot(files) {
|
|
3010
|
+
const dirs = files.map((f) => resolve2(f, ".."));
|
|
3011
|
+
let configRoot = dirs.reduce((a, b) => {
|
|
3012
|
+
while (!b.startsWith(a)) a = resolve2(a, "..");
|
|
3013
|
+
return a;
|
|
3014
|
+
});
|
|
3015
|
+
let search = configRoot;
|
|
3016
|
+
for (let i = 0; i < 10; i++) {
|
|
3017
|
+
if (findConfigFile(search)) {
|
|
3018
|
+
configRoot = search;
|
|
3019
|
+
break;
|
|
3020
|
+
}
|
|
3021
|
+
const parent = resolve2(search, "..");
|
|
3022
|
+
if (parent === search) break;
|
|
3023
|
+
search = parent;
|
|
3024
|
+
}
|
|
3025
|
+
return configRoot;
|
|
3026
|
+
}
|
|
3027
|
+
function createWorkspace(files) {
|
|
3028
|
+
const configRoot = findConfigRoot(files);
|
|
3029
|
+
const projectConfig = loadConfigFromDisk(configRoot);
|
|
3030
|
+
const workspace = new WorkspaceModel();
|
|
3031
|
+
const fileContents = /* @__PURE__ */ new Map();
|
|
3032
|
+
for (const filePath of files) {
|
|
3033
|
+
try {
|
|
3034
|
+
const text = readFileSync6(filePath, "utf-8");
|
|
3035
|
+
const uri = pathToFileURL3(filePath).toString();
|
|
3036
|
+
fileContents.set(uri, text);
|
|
3037
|
+
} catch {
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
workspace.initialize(fileContents);
|
|
3041
|
+
if (Object.keys(projectConfig.macros).length > 0) {
|
|
3042
|
+
workspace.macros.loadSupplements(projectConfig.macros);
|
|
3043
|
+
}
|
|
3044
|
+
return workspace;
|
|
3045
|
+
}
|
|
3046
|
+
async function startMcpServer() {
|
|
3047
|
+
const server = new McpServer({
|
|
3048
|
+
name: "spindle-lsp",
|
|
3049
|
+
version: "0.3.5"
|
|
3050
|
+
});
|
|
3051
|
+
server.tool(
|
|
3052
|
+
"spindle_check",
|
|
3053
|
+
"Run diagnostics on .tw/.twee files. Returns structured diagnostic results.",
|
|
3054
|
+
{
|
|
3055
|
+
path: z.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to check"),
|
|
3056
|
+
severity: z.enum(["error", "warning", "info", "hint"]).optional().describe("Minimum severity to include")
|
|
3057
|
+
},
|
|
3058
|
+
async (args2) => {
|
|
3059
|
+
const cwd = process.cwd();
|
|
3060
|
+
const files = await resolveFiles(args2.path, cwd);
|
|
3061
|
+
if (files.length === 0) {
|
|
3062
|
+
return {
|
|
3063
|
+
content: [{ type: "text", text: JSON.stringify([], null, 2) }]
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
const workspace = createWorkspace(files);
|
|
3067
|
+
try {
|
|
3068
|
+
const results = [];
|
|
3069
|
+
for (const [uri] of workspace.documents.getUris().map((u) => [u])) {
|
|
3070
|
+
let diags = computeDiagnostics(uri, workspace);
|
|
3071
|
+
if (args2.severity) {
|
|
3072
|
+
diags = filterBySeverity2(diags, args2.severity);
|
|
3073
|
+
}
|
|
3074
|
+
const filePath = fileURLToPath3(uri);
|
|
3075
|
+
const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
3076
|
+
for (const d of diags) {
|
|
3077
|
+
results.push({
|
|
3078
|
+
file: relativePath,
|
|
3079
|
+
line: d.range.start.line + 1,
|
|
3080
|
+
column: d.range.start.character + 1,
|
|
3081
|
+
severity: d.severity,
|
|
3082
|
+
code: d.code,
|
|
3083
|
+
message: d.message
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return {
|
|
3088
|
+
content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
|
|
3089
|
+
};
|
|
3090
|
+
} finally {
|
|
3091
|
+
workspace.dispose();
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
);
|
|
3095
|
+
server.tool(
|
|
3096
|
+
"spindle_format",
|
|
3097
|
+
"Format .tw/.twee files in place.",
|
|
3098
|
+
{
|
|
3099
|
+
path: z.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to format")
|
|
3100
|
+
},
|
|
3101
|
+
async (args2) => {
|
|
3102
|
+
const cwd = process.cwd();
|
|
3103
|
+
const files = await resolveFiles(args2.path, cwd);
|
|
3104
|
+
let formatted = 0;
|
|
3105
|
+
let unchanged = 0;
|
|
3106
|
+
const changedFiles = [];
|
|
3107
|
+
for (const filePath of files) {
|
|
3108
|
+
try {
|
|
3109
|
+
const text = readFileSync6(filePath, "utf-8");
|
|
3110
|
+
const result = formatDocument(text);
|
|
3111
|
+
if (result !== text) {
|
|
3112
|
+
writeFileSync2(filePath, result, "utf-8");
|
|
3113
|
+
formatted++;
|
|
3114
|
+
const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
3115
|
+
changedFiles.push(relativePath);
|
|
3116
|
+
} else {
|
|
3117
|
+
unchanged++;
|
|
3118
|
+
}
|
|
3119
|
+
} catch {
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
const output = { formatted, unchanged, files: changedFiles };
|
|
3123
|
+
return {
|
|
3124
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
);
|
|
3128
|
+
server.tool(
|
|
3129
|
+
"spindle_format_check",
|
|
3130
|
+
"Check formatting without modifying files. Returns list of files that need formatting.",
|
|
3131
|
+
{
|
|
3132
|
+
path: z.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to check")
|
|
3133
|
+
},
|
|
3134
|
+
async (args2) => {
|
|
3135
|
+
const cwd = process.cwd();
|
|
3136
|
+
const files = await resolveFiles(args2.path, cwd);
|
|
3137
|
+
const needsFormatting = [];
|
|
3138
|
+
const alreadyFormatted = [];
|
|
3139
|
+
for (const filePath of files) {
|
|
3140
|
+
try {
|
|
3141
|
+
const text = readFileSync6(filePath, "utf-8");
|
|
3142
|
+
const result = formatDocument(text);
|
|
3143
|
+
const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
3144
|
+
if (result !== text) {
|
|
3145
|
+
needsFormatting.push(relativePath);
|
|
3146
|
+
} else {
|
|
3147
|
+
alreadyFormatted.push(relativePath);
|
|
3148
|
+
}
|
|
3149
|
+
} catch {
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
const output = { needsFormatting, alreadyFormatted };
|
|
3153
|
+
return {
|
|
3154
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
);
|
|
3158
|
+
const transport = new StdioServerTransport();
|
|
3159
|
+
await server.connect(transport);
|
|
3160
|
+
}
|
|
3161
|
+
var SEVERITY_ORDER2;
|
|
3162
|
+
var init_server = __esm({
|
|
3163
|
+
"src/mcp/server.ts"() {
|
|
3164
|
+
"use strict";
|
|
3165
|
+
init_workspace_model();
|
|
3166
|
+
init_config_loader();
|
|
3167
|
+
init_diagnostics();
|
|
3168
|
+
init_format();
|
|
3169
|
+
SEVERITY_ORDER2 = ["hint", "info", "warning", "error"];
|
|
3170
|
+
}
|
|
3171
|
+
});
|
|
3172
|
+
|
|
2982
3173
|
// src/core/plugin/plugin-loader.ts
|
|
2983
3174
|
function loadPlugins(allPlugins2, config) {
|
|
2984
3175
|
const disabled = new Set(config.disabledPlugins ?? []);
|
|
@@ -4833,8 +5024,8 @@ var init_plugins = __esm({
|
|
|
4833
5024
|
});
|
|
4834
5025
|
|
|
4835
5026
|
// src/server/server.ts
|
|
4836
|
-
var
|
|
4837
|
-
__export(
|
|
5027
|
+
var server_exports2 = {};
|
|
5028
|
+
__export(server_exports2, {
|
|
4838
5029
|
startServer: () => startServer
|
|
4839
5030
|
});
|
|
4840
5031
|
import {
|
|
@@ -4844,18 +5035,18 @@ import {
|
|
|
4844
5035
|
FileChangeType
|
|
4845
5036
|
} from "vscode-languageserver/node.js";
|
|
4846
5037
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
4847
|
-
import { fileURLToPath as
|
|
4848
|
-
import { readFileSync as
|
|
4849
|
-
import { glob as
|
|
5038
|
+
import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
5039
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
5040
|
+
import { glob as glob4 } from "glob";
|
|
4850
5041
|
function uriToFsPath(uri) {
|
|
4851
5042
|
try {
|
|
4852
|
-
return
|
|
5043
|
+
return fileURLToPath4(uri);
|
|
4853
5044
|
} catch {
|
|
4854
5045
|
return uri;
|
|
4855
5046
|
}
|
|
4856
5047
|
}
|
|
4857
5048
|
function fsPathToUri(fsPath) {
|
|
4858
|
-
return
|
|
5049
|
+
return pathToFileURL4(fsPath).toString();
|
|
4859
5050
|
}
|
|
4860
5051
|
function startServer(_args) {
|
|
4861
5052
|
const connection = createConnection(ProposedFeatures.all);
|
|
@@ -4954,7 +5145,7 @@ function startServer(_args) {
|
|
|
4954
5145
|
} else {
|
|
4955
5146
|
try {
|
|
4956
5147
|
const fsPath = uriToFsPath(change.uri);
|
|
4957
|
-
const text =
|
|
5148
|
+
const text = readFileSync7(fsPath, "utf-8");
|
|
4958
5149
|
if (workspace.documents.has(change.uri)) {
|
|
4959
5150
|
workspace.documents.update(change.uri, text);
|
|
4960
5151
|
} else {
|
|
@@ -4977,14 +5168,14 @@ async function scanWorkspaceFiles(root) {
|
|
|
4977
5168
|
const contents = /* @__PURE__ */ new Map();
|
|
4978
5169
|
if (!root) return contents;
|
|
4979
5170
|
try {
|
|
4980
|
-
const files = await
|
|
5171
|
+
const files = await glob4("**/*.{tw,twee}", {
|
|
4981
5172
|
cwd: root,
|
|
4982
5173
|
absolute: true,
|
|
4983
5174
|
nodir: true
|
|
4984
5175
|
});
|
|
4985
5176
|
for (const filePath of files) {
|
|
4986
5177
|
try {
|
|
4987
|
-
const text =
|
|
5178
|
+
const text = readFileSync7(filePath, "utf-8");
|
|
4988
5179
|
const uri = fsPathToUri(filePath);
|
|
4989
5180
|
contents.set(uri, text);
|
|
4990
5181
|
} catch {
|
|
@@ -4994,7 +5185,7 @@ async function scanWorkspaceFiles(root) {
|
|
|
4994
5185
|
}
|
|
4995
5186
|
return contents;
|
|
4996
5187
|
}
|
|
4997
|
-
var
|
|
5188
|
+
var init_server2 = __esm({
|
|
4998
5189
|
"src/server/server.ts"() {
|
|
4999
5190
|
"use strict";
|
|
5000
5191
|
init_workspace_model();
|
|
@@ -5015,8 +5206,11 @@ if (args[0] === "check") {
|
|
|
5015
5206
|
const { runFormat: runFormat2 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
5016
5207
|
const exitCode = await runFormat2(args.slice(1));
|
|
5017
5208
|
process.exit(exitCode);
|
|
5209
|
+
} else if (args[0] === "mcp") {
|
|
5210
|
+
const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
5211
|
+
await startMcpServer2();
|
|
5018
5212
|
} else {
|
|
5019
|
-
const { startServer: startServer2 } = await Promise.resolve().then(() => (
|
|
5213
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server2(), server_exports2));
|
|
5020
5214
|
startServer2(args);
|
|
5021
5215
|
}
|
|
5022
5216
|
//# sourceMappingURL=bin.js.map
|