speclock 5.5.3 → 5.5.4
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 +3 -3
- package/package.json +217 -1
- package/src/cli/index.js +390 -16
- package/src/core/auth.js +8 -0
- package/src/core/compliance.js +1 -1
- package/src/core/enforcer.js +7 -1
- package/src/core/guardian.js +78 -5
- package/src/core/lock-author.js +8 -0
- package/src/core/mcp-install.js +484 -0
- package/src/dashboard/index.html +2 -2
- package/src/mcp/http-server.js +2 -2
- package/src/mcp/server.js +1 -1
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecLock MCP Autoinstaller
|
|
3
|
+
* One-command installer: wires SpecLock as an MCP server into any AI client.
|
|
4
|
+
*
|
|
5
|
+
* Usage (CLI):
|
|
6
|
+
* speclock mcp install <client> — claude-code|cursor|windsurf|cline|codex|all
|
|
7
|
+
* speclock mcp uninstall <client>
|
|
8
|
+
*
|
|
9
|
+
* The investor audit found the biggest manual friction was users having to
|
|
10
|
+
* hand-edit JSON to wire up SpecLock as an MCP server. This module removes
|
|
11
|
+
* that friction entirely — one command, any supported client, any OS.
|
|
12
|
+
*
|
|
13
|
+
* Developed by Sandeep Roy (https://github.com/sgroy10)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "fs";
|
|
17
|
+
import path from "path";
|
|
18
|
+
import os from "os";
|
|
19
|
+
|
|
20
|
+
// The stanza we inject. Kept in one place so every client stays in sync.
|
|
21
|
+
export const SPECLOCK_MCP_STANZA = {
|
|
22
|
+
command: "npx",
|
|
23
|
+
args: ["-y", "speclock", "serve"],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const SUPPORTED_CLIENTS = [
|
|
27
|
+
"claude-code",
|
|
28
|
+
"cursor",
|
|
29
|
+
"windsurf",
|
|
30
|
+
"cline",
|
|
31
|
+
"codex",
|
|
32
|
+
"all",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the config file locations for a given client on the current OS.
|
|
37
|
+
* Returns { primary, project } where each is { path, format }.
|
|
38
|
+
* format is "json" | "toml" | "vscode-json".
|
|
39
|
+
*
|
|
40
|
+
* - primary = global/user-level config (always attempted)
|
|
41
|
+
* - project = project-scoped config (only written if --project flag used)
|
|
42
|
+
*/
|
|
43
|
+
export function getClientConfigPaths(client, projectRoot = process.cwd()) {
|
|
44
|
+
const home = os.homedir();
|
|
45
|
+
const platform = process.platform; // "win32" | "darwin" | "linux"
|
|
46
|
+
|
|
47
|
+
switch (client) {
|
|
48
|
+
case "claude-code": {
|
|
49
|
+
return {
|
|
50
|
+
primary: {
|
|
51
|
+
path: path.join(home, ".claude", "mcp.json"),
|
|
52
|
+
format: "json",
|
|
53
|
+
label: "Claude Code",
|
|
54
|
+
},
|
|
55
|
+
project: {
|
|
56
|
+
path: path.join(projectRoot, ".mcp.json"),
|
|
57
|
+
format: "json",
|
|
58
|
+
label: "Claude Code (project)",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case "cursor": {
|
|
64
|
+
return {
|
|
65
|
+
primary: {
|
|
66
|
+
path: path.join(home, ".cursor", "mcp.json"),
|
|
67
|
+
format: "json",
|
|
68
|
+
label: "Cursor",
|
|
69
|
+
},
|
|
70
|
+
project: {
|
|
71
|
+
path: path.join(projectRoot, ".cursor", "mcp.json"),
|
|
72
|
+
format: "json",
|
|
73
|
+
label: "Cursor (project)",
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case "windsurf": {
|
|
79
|
+
return {
|
|
80
|
+
primary: {
|
|
81
|
+
path: path.join(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
82
|
+
format: "json",
|
|
83
|
+
label: "Windsurf",
|
|
84
|
+
},
|
|
85
|
+
project: null,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case "cline": {
|
|
90
|
+
// Cline lives inside VS Code User settings.json.
|
|
91
|
+
let settingsPath;
|
|
92
|
+
if (platform === "win32") {
|
|
93
|
+
settingsPath = path.join(
|
|
94
|
+
process.env.APPDATA || path.join(home, "AppData", "Roaming"),
|
|
95
|
+
"Code",
|
|
96
|
+
"User",
|
|
97
|
+
"settings.json"
|
|
98
|
+
);
|
|
99
|
+
} else if (platform === "darwin") {
|
|
100
|
+
settingsPath = path.join(
|
|
101
|
+
home,
|
|
102
|
+
"Library",
|
|
103
|
+
"Application Support",
|
|
104
|
+
"Code",
|
|
105
|
+
"User",
|
|
106
|
+
"settings.json"
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
settingsPath = path.join(home, ".config", "Code", "User", "settings.json");
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
primary: {
|
|
113
|
+
path: settingsPath,
|
|
114
|
+
format: "vscode-json",
|
|
115
|
+
label: "Cline (VS Code settings)",
|
|
116
|
+
},
|
|
117
|
+
project: null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
case "codex": {
|
|
122
|
+
return {
|
|
123
|
+
primary: {
|
|
124
|
+
path: path.join(home, ".codex", "config.toml"),
|
|
125
|
+
format: "toml",
|
|
126
|
+
label: "Codex",
|
|
127
|
+
},
|
|
128
|
+
project: null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
default:
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Unknown client "${client}". Supported: ${SUPPORTED_CLIENTS.join(", ")}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- JSON helpers ---
|
|
140
|
+
|
|
141
|
+
function readJsonSafe(filePath) {
|
|
142
|
+
if (!fs.existsSync(filePath)) return null;
|
|
143
|
+
try {
|
|
144
|
+
const raw = fs.readFileSync(filePath, "utf-8").trim();
|
|
145
|
+
if (!raw) return {};
|
|
146
|
+
return JSON.parse(raw);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
throw new Error(`Could not parse JSON at ${filePath}: ${e.message}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function writeJson(filePath, data) {
|
|
153
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
154
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Merge speclock into a plain JSON config that uses "mcpServers".
|
|
159
|
+
* Preserves all other servers and top-level keys.
|
|
160
|
+
*/
|
|
161
|
+
function injectJson(config) {
|
|
162
|
+
const next = { ...(config || {}) };
|
|
163
|
+
if (!next.mcpServers || typeof next.mcpServers !== "object") {
|
|
164
|
+
next.mcpServers = {};
|
|
165
|
+
}
|
|
166
|
+
next.mcpServers = {
|
|
167
|
+
...next.mcpServers,
|
|
168
|
+
speclock: { ...SPECLOCK_MCP_STANZA },
|
|
169
|
+
};
|
|
170
|
+
return next;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function removeJson(config) {
|
|
174
|
+
if (!config || typeof config !== "object") return { changed: false, config };
|
|
175
|
+
if (!config.mcpServers || !config.mcpServers.speclock) {
|
|
176
|
+
return { changed: false, config };
|
|
177
|
+
}
|
|
178
|
+
const next = { ...config, mcpServers: { ...config.mcpServers } };
|
|
179
|
+
delete next.mcpServers.speclock;
|
|
180
|
+
return { changed: true, config: next };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* VS Code settings.json uses JSONC (comments + trailing commas).
|
|
185
|
+
* We do a best-effort: if parse fails, we fall back to a safe string rewrite
|
|
186
|
+
* that touches only the "cline.mcpServers" block.
|
|
187
|
+
*/
|
|
188
|
+
function injectVsCodeJson(filePath) {
|
|
189
|
+
const exists = fs.existsSync(filePath);
|
|
190
|
+
let parsed = null;
|
|
191
|
+
let raw = "";
|
|
192
|
+
|
|
193
|
+
if (exists) {
|
|
194
|
+
raw = fs.readFileSync(filePath, "utf-8");
|
|
195
|
+
try {
|
|
196
|
+
// Try a lenient parse: strip line/block comments and trailing commas.
|
|
197
|
+
const stripped = raw
|
|
198
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
199
|
+
.replace(/(^|[^:])\/\/.*$/gm, "$1")
|
|
200
|
+
.replace(/,(\s*[}\]])/g, "$1");
|
|
201
|
+
parsed = stripped.trim() ? JSON.parse(stripped) : {};
|
|
202
|
+
} catch {
|
|
203
|
+
parsed = null; // fall back to string append below
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (parsed !== null) {
|
|
208
|
+
const next = { ...parsed };
|
|
209
|
+
// Cline reads either "cline.mcpServers" or "mcpServers". We write the
|
|
210
|
+
// Cline-specific key to avoid clashing with other VS Code extensions.
|
|
211
|
+
const existing = next["cline.mcpServers"] || {};
|
|
212
|
+
next["cline.mcpServers"] = {
|
|
213
|
+
...existing,
|
|
214
|
+
speclock: { ...SPECLOCK_MCP_STANZA },
|
|
215
|
+
};
|
|
216
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
217
|
+
fs.writeFileSync(filePath, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
218
|
+
return { mode: "parsed" };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Fallback: file has comments or odd formatting. Append a marker block.
|
|
222
|
+
// This is safe: VS Code's JSONC parser accepts duplicate keys (last wins)
|
|
223
|
+
// but to avoid corruption we just warn the user instead of rewriting.
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Could not safely parse VS Code settings at ${filePath}. ` +
|
|
226
|
+
`Please add this manually:\n` +
|
|
227
|
+
` "cline.mcpServers": { "speclock": ${JSON.stringify(
|
|
228
|
+
SPECLOCK_MCP_STANZA
|
|
229
|
+
)} }`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function removeVsCodeJson(filePath) {
|
|
234
|
+
if (!fs.existsSync(filePath)) {
|
|
235
|
+
return { changed: false };
|
|
236
|
+
}
|
|
237
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
238
|
+
let parsed;
|
|
239
|
+
try {
|
|
240
|
+
const stripped = raw
|
|
241
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
242
|
+
.replace(/(^|[^:])\/\/.*$/gm, "$1")
|
|
243
|
+
.replace(/,(\s*[}\]])/g, "$1");
|
|
244
|
+
parsed = stripped.trim() ? JSON.parse(stripped) : {};
|
|
245
|
+
} catch {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`Could not safely parse VS Code settings at ${filePath}. ` +
|
|
248
|
+
`Please remove "speclock" from "cline.mcpServers" manually.`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const block = parsed["cline.mcpServers"];
|
|
252
|
+
if (!block || !block.speclock) return { changed: false };
|
|
253
|
+
const next = { ...parsed, "cline.mcpServers": { ...block } };
|
|
254
|
+
delete next["cline.mcpServers"].speclock;
|
|
255
|
+
fs.writeFileSync(filePath, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
256
|
+
return { changed: true };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// --- TOML helpers (Codex ~/.codex/config.toml) ---
|
|
260
|
+
//
|
|
261
|
+
// Codex uses an extremely small TOML dialect for MCP servers:
|
|
262
|
+
// [mcp_servers.speclock]
|
|
263
|
+
// command = "npx"
|
|
264
|
+
// args = ["-y", "speclock", "serve"]
|
|
265
|
+
//
|
|
266
|
+
// We do NOT pull in a TOML parser dependency. We implement a targeted
|
|
267
|
+
// inject/remove that leaves other [mcp_servers.*] tables untouched.
|
|
268
|
+
|
|
269
|
+
const CODEX_STANZA = [
|
|
270
|
+
"",
|
|
271
|
+
"[mcp_servers.speclock]",
|
|
272
|
+
'command = "npx"',
|
|
273
|
+
'args = ["-y", "speclock", "serve"]',
|
|
274
|
+
"",
|
|
275
|
+
].join("\n");
|
|
276
|
+
|
|
277
|
+
function injectToml(filePath) {
|
|
278
|
+
let existing = "";
|
|
279
|
+
if (fs.existsSync(filePath)) {
|
|
280
|
+
existing = fs.readFileSync(filePath, "utf-8");
|
|
281
|
+
if (existing.includes("[mcp_servers.speclock]")) {
|
|
282
|
+
return { changed: false, reason: "already present" };
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
286
|
+
}
|
|
287
|
+
const trimmed = existing.replace(/\s+$/, "");
|
|
288
|
+
const next = (trimmed ? trimmed + "\n" : "") + CODEX_STANZA;
|
|
289
|
+
fs.writeFileSync(filePath, next, "utf-8");
|
|
290
|
+
return { changed: true };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function removeToml(filePath) {
|
|
294
|
+
if (!fs.existsSync(filePath)) return { changed: false };
|
|
295
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
296
|
+
if (!raw.includes("[mcp_servers.speclock]")) {
|
|
297
|
+
return { changed: false };
|
|
298
|
+
}
|
|
299
|
+
// Remove the [mcp_servers.speclock] block up to the next [section] or EOF.
|
|
300
|
+
const cleaned = raw.replace(
|
|
301
|
+
/\n?\[mcp_servers\.speclock\][\s\S]*?(?=\n\[|\n*$)/,
|
|
302
|
+
""
|
|
303
|
+
);
|
|
304
|
+
fs.writeFileSync(filePath, cleaned.replace(/\s+$/, "") + "\n", "utf-8");
|
|
305
|
+
return { changed: true };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// --- Public API ---
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Install SpecLock MCP server into a single client.
|
|
312
|
+
* Returns { client, writes: [{ path, status, label }], errors: [] }.
|
|
313
|
+
*/
|
|
314
|
+
export function installForClient(client, projectRoot = process.cwd(), options = {}) {
|
|
315
|
+
const includeProject = options.includeProject !== false; // default: yes
|
|
316
|
+
const result = { client, writes: [], errors: [] };
|
|
317
|
+
|
|
318
|
+
let paths;
|
|
319
|
+
try {
|
|
320
|
+
paths = getClientConfigPaths(client, projectRoot);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
result.errors.push(e.message);
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const targets = [paths.primary];
|
|
327
|
+
if (includeProject && paths.project) targets.push(paths.project);
|
|
328
|
+
|
|
329
|
+
for (const target of targets) {
|
|
330
|
+
if (!target) continue;
|
|
331
|
+
try {
|
|
332
|
+
let status;
|
|
333
|
+
if (target.format === "json") {
|
|
334
|
+
const current = readJsonSafe(target.path) || {};
|
|
335
|
+
const next = injectJson(current);
|
|
336
|
+
writeJson(target.path, next);
|
|
337
|
+
status = "installed";
|
|
338
|
+
} else if (target.format === "vscode-json") {
|
|
339
|
+
const out = injectVsCodeJson(target.path);
|
|
340
|
+
status = out.mode === "parsed" ? "installed" : "installed";
|
|
341
|
+
} else if (target.format === "toml") {
|
|
342
|
+
const out = injectToml(target.path);
|
|
343
|
+
status = out.changed ? "installed" : "already present";
|
|
344
|
+
} else {
|
|
345
|
+
throw new Error(`Unsupported format: ${target.format}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
result.writes.push({
|
|
349
|
+
path: target.path,
|
|
350
|
+
status,
|
|
351
|
+
label: target.label,
|
|
352
|
+
});
|
|
353
|
+
} catch (e) {
|
|
354
|
+
result.errors.push(`${target.label}: ${e.message}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Uninstall SpecLock MCP server from a single client.
|
|
363
|
+
*/
|
|
364
|
+
export function uninstallForClient(client, projectRoot = process.cwd(), options = {}) {
|
|
365
|
+
const includeProject = options.includeProject !== false;
|
|
366
|
+
const result = { client, writes: [], errors: [] };
|
|
367
|
+
|
|
368
|
+
let paths;
|
|
369
|
+
try {
|
|
370
|
+
paths = getClientConfigPaths(client, projectRoot);
|
|
371
|
+
} catch (e) {
|
|
372
|
+
result.errors.push(e.message);
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const targets = [paths.primary];
|
|
377
|
+
if (includeProject && paths.project) targets.push(paths.project);
|
|
378
|
+
|
|
379
|
+
for (const target of targets) {
|
|
380
|
+
if (!target) continue;
|
|
381
|
+
if (!fs.existsSync(target.path)) {
|
|
382
|
+
result.writes.push({
|
|
383
|
+
path: target.path,
|
|
384
|
+
status: "not installed",
|
|
385
|
+
label: target.label,
|
|
386
|
+
});
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
let changed = false;
|
|
392
|
+
if (target.format === "json") {
|
|
393
|
+
const current = readJsonSafe(target.path) || {};
|
|
394
|
+
const out = removeJson(current);
|
|
395
|
+
if (out.changed) {
|
|
396
|
+
writeJson(target.path, out.config);
|
|
397
|
+
changed = true;
|
|
398
|
+
}
|
|
399
|
+
} else if (target.format === "vscode-json") {
|
|
400
|
+
const out = removeVsCodeJson(target.path);
|
|
401
|
+
changed = out.changed;
|
|
402
|
+
} else if (target.format === "toml") {
|
|
403
|
+
const out = removeToml(target.path);
|
|
404
|
+
changed = out.changed;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
result.writes.push({
|
|
408
|
+
path: target.path,
|
|
409
|
+
status: changed ? "removed" : "not installed",
|
|
410
|
+
label: target.label,
|
|
411
|
+
});
|
|
412
|
+
} catch (e) {
|
|
413
|
+
result.errors.push(`${target.label}: ${e.message}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Install across all supported clients at once.
|
|
422
|
+
*/
|
|
423
|
+
export function installAll(projectRoot = process.cwd(), options = {}) {
|
|
424
|
+
const clients = SUPPORTED_CLIENTS.filter((c) => c !== "all");
|
|
425
|
+
const results = [];
|
|
426
|
+
for (const c of clients) {
|
|
427
|
+
results.push(installForClient(c, projectRoot, options));
|
|
428
|
+
}
|
|
429
|
+
return results;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function uninstallAll(projectRoot = process.cwd(), options = {}) {
|
|
433
|
+
const clients = SUPPORTED_CLIENTS.filter((c) => c !== "all");
|
|
434
|
+
const results = [];
|
|
435
|
+
for (const c of clients) {
|
|
436
|
+
results.push(uninstallForClient(c, projectRoot, options));
|
|
437
|
+
}
|
|
438
|
+
return results;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Format an install/uninstall result for console output.
|
|
443
|
+
*/
|
|
444
|
+
export function formatResult(result, action = "install") {
|
|
445
|
+
const lines = [];
|
|
446
|
+
const hasErrors = result.errors && result.errors.length > 0;
|
|
447
|
+
const verb = action === "install" ? "added to" : "removed from";
|
|
448
|
+
|
|
449
|
+
for (const w of result.writes) {
|
|
450
|
+
if (w.status === "installed") {
|
|
451
|
+
lines.push(` [OK] SpecLock ${verb} ${w.label} config at: ${w.path}`);
|
|
452
|
+
} else if (w.status === "removed") {
|
|
453
|
+
lines.push(` [OK] SpecLock ${verb} ${w.label} config at: ${w.path}`);
|
|
454
|
+
} else if (w.status === "already present") {
|
|
455
|
+
lines.push(` [--] SpecLock already present in ${w.label}: ${w.path}`);
|
|
456
|
+
} else if (w.status === "not installed") {
|
|
457
|
+
lines.push(` [--] SpecLock not present in ${w.label}: ${w.path}`);
|
|
458
|
+
} else {
|
|
459
|
+
lines.push(` [??] ${w.label}: ${w.status} — ${w.path}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (hasErrors) {
|
|
464
|
+
for (const e of result.errors) {
|
|
465
|
+
lines.push(` [!!] ${e}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return lines.join("\n");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Next-steps hint shown after a successful install.
|
|
474
|
+
*/
|
|
475
|
+
export function nextStepsFor(client) {
|
|
476
|
+
const hints = {
|
|
477
|
+
"claude-code": "Restart Claude Code to activate SpecLock.",
|
|
478
|
+
cursor: "Restart Cursor (Cmd/Ctrl+Shift+P → Reload Window) to activate SpecLock.",
|
|
479
|
+
windsurf: "Restart Windsurf to activate SpecLock.",
|
|
480
|
+
cline: "Reload VS Code (Cmd/Ctrl+Shift+P → Developer: Reload Window) to activate SpecLock in Cline.",
|
|
481
|
+
codex: "Restart Codex CLI to activate SpecLock.",
|
|
482
|
+
};
|
|
483
|
+
return hints[client] || "Restart your AI client to activate SpecLock.";
|
|
484
|
+
}
|
package/src/dashboard/index.html
CHANGED
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
<div class="header">
|
|
90
90
|
<div>
|
|
91
91
|
<h1><span>SpecLock</span> Dashboard</h1>
|
|
92
|
-
<div class="meta">v5.5.
|
|
92
|
+
<div class="meta">v5.5.4 — Your AI has rules. SpecLock makes them unbreakable.</div>
|
|
93
93
|
</div>
|
|
94
94
|
<div style="display:flex;align-items:center;gap:12px;">
|
|
95
95
|
<span id="health-badge" class="status-badge healthy">Loading...</span>
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
</div>
|
|
183
183
|
|
|
184
184
|
<div style="text-align:center;padding:24px;color:var(--muted);font-size:12px;">
|
|
185
|
-
SpecLock v5.5.
|
|
185
|
+
SpecLock v5.5.4 — Developed by Sandeep Roy — <a href="https://github.com/sgroy10/speclock" style="color:var(--accent)">GitHub</a>
|
|
186
186
|
</div>
|
|
187
187
|
|
|
188
188
|
<script>
|
package/src/mcp/http-server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SpecLock MCP HTTP Server — for Railway / remote deployment
|
|
3
|
-
* Wraps the same
|
|
3
|
+
* Wraps the same 51 tools as the stdio server using Streamable HTTP transport.
|
|
4
4
|
* Developed by Sandeep Roy (https://github.com/sgroy10)
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -113,7 +113,7 @@ import { fileURLToPath } from "url";
|
|
|
113
113
|
import _path from "path";
|
|
114
114
|
|
|
115
115
|
const PROJECT_ROOT = process.env.SPECLOCK_PROJECT_ROOT || process.cwd();
|
|
116
|
-
const VERSION = "5.5.
|
|
116
|
+
const VERSION = "5.5.4";
|
|
117
117
|
const AUTHOR = "Sandeep Roy";
|
|
118
118
|
const START_TIME = Date.now();
|
|
119
119
|
|
package/src/mcp/server.js
CHANGED
|
@@ -126,7 +126,7 @@ const PROJECT_ROOT =
|
|
|
126
126
|
args.project || process.env.SPECLOCK_PROJECT_ROOT || process.cwd();
|
|
127
127
|
|
|
128
128
|
// --- MCP Server ---
|
|
129
|
-
const VERSION = "5.5.
|
|
129
|
+
const VERSION = "5.5.4";
|
|
130
130
|
const AUTHOR = "Sandeep Roy";
|
|
131
131
|
|
|
132
132
|
const server = new McpServer(
|