@rohal12/spindle-lsp 0.3.4 → 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 +214 -22
- package/dist/bin.js.map +4 -4
- package/package.json +9 -6
package/dist/bin.js
CHANGED
|
@@ -2087,6 +2087,7 @@ var init_link_parser = __esm({
|
|
|
2087
2087
|
|
|
2088
2088
|
// src/plugins/diagnostics.ts
|
|
2089
2089
|
function computeDiagnostics(uri, workspace) {
|
|
2090
|
+
if (!workspace.initialized) return [];
|
|
2090
2091
|
try {
|
|
2091
2092
|
const text = workspace.documents.getText(uri);
|
|
2092
2093
|
if (text === void 0) return [];
|
|
@@ -2109,15 +2110,13 @@ function computeDiagnostics(uri, workspace) {
|
|
|
2109
2110
|
validateVariables(uri, workspace, diagnostics);
|
|
2110
2111
|
} catch {
|
|
2111
2112
|
}
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
} catch {
|
|
2120
|
-
}
|
|
2113
|
+
try {
|
|
2114
|
+
validateLinks(text, passages, passageNames, diagnostics);
|
|
2115
|
+
} catch {
|
|
2116
|
+
}
|
|
2117
|
+
try {
|
|
2118
|
+
validateWidgetInvocations(macros, workspace, diagnostics);
|
|
2119
|
+
} catch {
|
|
2121
2120
|
}
|
|
2122
2121
|
return diagnostics;
|
|
2123
2122
|
} catch {
|
|
@@ -2278,7 +2277,6 @@ function validateArguments(macros, workspace, passageNames, diagnostics) {
|
|
|
2278
2277
|
}
|
|
2279
2278
|
}
|
|
2280
2279
|
function validateVariables(uri, workspace, diagnostics) {
|
|
2281
|
-
if (!workspace.initialized) return;
|
|
2282
2280
|
if (!workspace.variables.hasStoryVariables()) {
|
|
2283
2281
|
const undeclared2 = workspace.variables.getUndeclared(uri);
|
|
2284
2282
|
const text = workspace.documents.getText(uri);
|
|
@@ -2981,6 +2979,197 @@ var init_format2 = __esm({
|
|
|
2981
2979
|
}
|
|
2982
2980
|
});
|
|
2983
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
|
+
|
|
2984
3173
|
// src/core/plugin/plugin-loader.ts
|
|
2985
3174
|
function loadPlugins(allPlugins2, config) {
|
|
2986
3175
|
const disabled = new Set(config.disabledPlugins ?? []);
|
|
@@ -4835,8 +5024,8 @@ var init_plugins = __esm({
|
|
|
4835
5024
|
});
|
|
4836
5025
|
|
|
4837
5026
|
// src/server/server.ts
|
|
4838
|
-
var
|
|
4839
|
-
__export(
|
|
5027
|
+
var server_exports2 = {};
|
|
5028
|
+
__export(server_exports2, {
|
|
4840
5029
|
startServer: () => startServer
|
|
4841
5030
|
});
|
|
4842
5031
|
import {
|
|
@@ -4846,18 +5035,18 @@ import {
|
|
|
4846
5035
|
FileChangeType
|
|
4847
5036
|
} from "vscode-languageserver/node.js";
|
|
4848
5037
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
4849
|
-
import { fileURLToPath as
|
|
4850
|
-
import { readFileSync as
|
|
4851
|
-
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";
|
|
4852
5041
|
function uriToFsPath(uri) {
|
|
4853
5042
|
try {
|
|
4854
|
-
return
|
|
5043
|
+
return fileURLToPath4(uri);
|
|
4855
5044
|
} catch {
|
|
4856
5045
|
return uri;
|
|
4857
5046
|
}
|
|
4858
5047
|
}
|
|
4859
5048
|
function fsPathToUri(fsPath) {
|
|
4860
|
-
return
|
|
5049
|
+
return pathToFileURL4(fsPath).toString();
|
|
4861
5050
|
}
|
|
4862
5051
|
function startServer(_args) {
|
|
4863
5052
|
const connection = createConnection(ProposedFeatures.all);
|
|
@@ -4956,7 +5145,7 @@ function startServer(_args) {
|
|
|
4956
5145
|
} else {
|
|
4957
5146
|
try {
|
|
4958
5147
|
const fsPath = uriToFsPath(change.uri);
|
|
4959
|
-
const text =
|
|
5148
|
+
const text = readFileSync7(fsPath, "utf-8");
|
|
4960
5149
|
if (workspace.documents.has(change.uri)) {
|
|
4961
5150
|
workspace.documents.update(change.uri, text);
|
|
4962
5151
|
} else {
|
|
@@ -4979,14 +5168,14 @@ async function scanWorkspaceFiles(root) {
|
|
|
4979
5168
|
const contents = /* @__PURE__ */ new Map();
|
|
4980
5169
|
if (!root) return contents;
|
|
4981
5170
|
try {
|
|
4982
|
-
const files = await
|
|
5171
|
+
const files = await glob4("**/*.{tw,twee}", {
|
|
4983
5172
|
cwd: root,
|
|
4984
5173
|
absolute: true,
|
|
4985
5174
|
nodir: true
|
|
4986
5175
|
});
|
|
4987
5176
|
for (const filePath of files) {
|
|
4988
5177
|
try {
|
|
4989
|
-
const text =
|
|
5178
|
+
const text = readFileSync7(filePath, "utf-8");
|
|
4990
5179
|
const uri = fsPathToUri(filePath);
|
|
4991
5180
|
contents.set(uri, text);
|
|
4992
5181
|
} catch {
|
|
@@ -4996,7 +5185,7 @@ async function scanWorkspaceFiles(root) {
|
|
|
4996
5185
|
}
|
|
4997
5186
|
return contents;
|
|
4998
5187
|
}
|
|
4999
|
-
var
|
|
5188
|
+
var init_server2 = __esm({
|
|
5000
5189
|
"src/server/server.ts"() {
|
|
5001
5190
|
"use strict";
|
|
5002
5191
|
init_workspace_model();
|
|
@@ -5017,8 +5206,11 @@ if (args[0] === "check") {
|
|
|
5017
5206
|
const { runFormat: runFormat2 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
5018
5207
|
const exitCode = await runFormat2(args.slice(1));
|
|
5019
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();
|
|
5020
5212
|
} else {
|
|
5021
|
-
const { startServer: startServer2 } = await Promise.resolve().then(() => (
|
|
5213
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server2(), server_exports2));
|
|
5022
5214
|
startServer2(args);
|
|
5023
5215
|
}
|
|
5024
5216
|
//# sourceMappingURL=bin.js.map
|