🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

speclock

Package Overview
Dependencies
Maintainers
1
Versions
71
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

speclock - npm Package Compare versions

Comparing version
5.5.3
to
5.5.4
+484
src/core/mcp-install.js
/**
* SpecLock MCP Autoinstaller
* One-command installer: wires SpecLock as an MCP server into any AI client.
*
* Usage (CLI):
* speclock mcp install <client> — claude-code|cursor|windsurf|cline|codex|all
* speclock mcp uninstall <client>
*
* The investor audit found the biggest manual friction was users having to
* hand-edit JSON to wire up SpecLock as an MCP server. This module removes
* that friction entirely — one command, any supported client, any OS.
*
* Developed by Sandeep Roy (https://github.com/sgroy10)
*/
import fs from "fs";
import path from "path";
import os from "os";
// The stanza we inject. Kept in one place so every client stays in sync.
export const SPECLOCK_MCP_STANZA = {
command: "npx",
args: ["-y", "speclock", "serve"],
};
export const SUPPORTED_CLIENTS = [
"claude-code",
"cursor",
"windsurf",
"cline",
"codex",
"all",
];
/**
* Resolve the config file locations for a given client on the current OS.
* Returns { primary, project } where each is { path, format }.
* format is "json" | "toml" | "vscode-json".
*
* - primary = global/user-level config (always attempted)
* - project = project-scoped config (only written if --project flag used)
*/
export function getClientConfigPaths(client, projectRoot = process.cwd()) {
const home = os.homedir();
const platform = process.platform; // "win32" | "darwin" | "linux"
switch (client) {
case "claude-code": {
return {
primary: {
path: path.join(home, ".claude", "mcp.json"),
format: "json",
label: "Claude Code",
},
project: {
path: path.join(projectRoot, ".mcp.json"),
format: "json",
label: "Claude Code (project)",
},
};
}
case "cursor": {
return {
primary: {
path: path.join(home, ".cursor", "mcp.json"),
format: "json",
label: "Cursor",
},
project: {
path: path.join(projectRoot, ".cursor", "mcp.json"),
format: "json",
label: "Cursor (project)",
},
};
}
case "windsurf": {
return {
primary: {
path: path.join(home, ".codeium", "windsurf", "mcp_config.json"),
format: "json",
label: "Windsurf",
},
project: null,
};
}
case "cline": {
// Cline lives inside VS Code User settings.json.
let settingsPath;
if (platform === "win32") {
settingsPath = path.join(
process.env.APPDATA || path.join(home, "AppData", "Roaming"),
"Code",
"User",
"settings.json"
);
} else if (platform === "darwin") {
settingsPath = path.join(
home,
"Library",
"Application Support",
"Code",
"User",
"settings.json"
);
} else {
settingsPath = path.join(home, ".config", "Code", "User", "settings.json");
}
return {
primary: {
path: settingsPath,
format: "vscode-json",
label: "Cline (VS Code settings)",
},
project: null,
};
}
case "codex": {
return {
primary: {
path: path.join(home, ".codex", "config.toml"),
format: "toml",
label: "Codex",
},
project: null,
};
}
default:
throw new Error(
`Unknown client "${client}". Supported: ${SUPPORTED_CLIENTS.join(", ")}`
);
}
}
// --- JSON helpers ---
function readJsonSafe(filePath) {
if (!fs.existsSync(filePath)) return null;
try {
const raw = fs.readFileSync(filePath, "utf-8").trim();
if (!raw) return {};
return JSON.parse(raw);
} catch (e) {
throw new Error(`Could not parse JSON at ${filePath}: ${e.message}`);
}
}
function writeJson(filePath, data) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
}
/**
* Merge speclock into a plain JSON config that uses "mcpServers".
* Preserves all other servers and top-level keys.
*/
function injectJson(config) {
const next = { ...(config || {}) };
if (!next.mcpServers || typeof next.mcpServers !== "object") {
next.mcpServers = {};
}
next.mcpServers = {
...next.mcpServers,
speclock: { ...SPECLOCK_MCP_STANZA },
};
return next;
}
function removeJson(config) {
if (!config || typeof config !== "object") return { changed: false, config };
if (!config.mcpServers || !config.mcpServers.speclock) {
return { changed: false, config };
}
const next = { ...config, mcpServers: { ...config.mcpServers } };
delete next.mcpServers.speclock;
return { changed: true, config: next };
}
/**
* VS Code settings.json uses JSONC (comments + trailing commas).
* We do a best-effort: if parse fails, we fall back to a safe string rewrite
* that touches only the "cline.mcpServers" block.
*/
function injectVsCodeJson(filePath) {
const exists = fs.existsSync(filePath);
let parsed = null;
let raw = "";
if (exists) {
raw = fs.readFileSync(filePath, "utf-8");
try {
// Try a lenient parse: strip line/block comments and trailing commas.
const stripped = raw
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1")
.replace(/,(\s*[}\]])/g, "$1");
parsed = stripped.trim() ? JSON.parse(stripped) : {};
} catch {
parsed = null; // fall back to string append below
}
}
if (parsed !== null) {
const next = { ...parsed };
// Cline reads either "cline.mcpServers" or "mcpServers". We write the
// Cline-specific key to avoid clashing with other VS Code extensions.
const existing = next["cline.mcpServers"] || {};
next["cline.mcpServers"] = {
...existing,
speclock: { ...SPECLOCK_MCP_STANZA },
};
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(next, null, 2) + "\n", "utf-8");
return { mode: "parsed" };
}
// Fallback: file has comments or odd formatting. Append a marker block.
// This is safe: VS Code's JSONC parser accepts duplicate keys (last wins)
// but to avoid corruption we just warn the user instead of rewriting.
throw new Error(
`Could not safely parse VS Code settings at ${filePath}. ` +
`Please add this manually:\n` +
` "cline.mcpServers": { "speclock": ${JSON.stringify(
SPECLOCK_MCP_STANZA
)} }`
);
}
function removeVsCodeJson(filePath) {
if (!fs.existsSync(filePath)) {
return { changed: false };
}
const raw = fs.readFileSync(filePath, "utf-8");
let parsed;
try {
const stripped = raw
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1")
.replace(/,(\s*[}\]])/g, "$1");
parsed = stripped.trim() ? JSON.parse(stripped) : {};
} catch {
throw new Error(
`Could not safely parse VS Code settings at ${filePath}. ` +
`Please remove "speclock" from "cline.mcpServers" manually.`
);
}
const block = parsed["cline.mcpServers"];
if (!block || !block.speclock) return { changed: false };
const next = { ...parsed, "cline.mcpServers": { ...block } };
delete next["cline.mcpServers"].speclock;
fs.writeFileSync(filePath, JSON.stringify(next, null, 2) + "\n", "utf-8");
return { changed: true };
}
// --- TOML helpers (Codex ~/.codex/config.toml) ---
//
// Codex uses an extremely small TOML dialect for MCP servers:
// [mcp_servers.speclock]
// command = "npx"
// args = ["-y", "speclock", "serve"]
//
// We do NOT pull in a TOML parser dependency. We implement a targeted
// inject/remove that leaves other [mcp_servers.*] tables untouched.
const CODEX_STANZA = [
"",
"[mcp_servers.speclock]",
'command = "npx"',
'args = ["-y", "speclock", "serve"]',
"",
].join("\n");
function injectToml(filePath) {
let existing = "";
if (fs.existsSync(filePath)) {
existing = fs.readFileSync(filePath, "utf-8");
if (existing.includes("[mcp_servers.speclock]")) {
return { changed: false, reason: "already present" };
}
} else {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
}
const trimmed = existing.replace(/\s+$/, "");
const next = (trimmed ? trimmed + "\n" : "") + CODEX_STANZA;
fs.writeFileSync(filePath, next, "utf-8");
return { changed: true };
}
function removeToml(filePath) {
if (!fs.existsSync(filePath)) return { changed: false };
const raw = fs.readFileSync(filePath, "utf-8");
if (!raw.includes("[mcp_servers.speclock]")) {
return { changed: false };
}
// Remove the [mcp_servers.speclock] block up to the next [section] or EOF.
const cleaned = raw.replace(
/\n?\[mcp_servers\.speclock\][\s\S]*?(?=\n\[|\n*$)/,
""
);
fs.writeFileSync(filePath, cleaned.replace(/\s+$/, "") + "\n", "utf-8");
return { changed: true };
}
// --- Public API ---
/**
* Install SpecLock MCP server into a single client.
* Returns { client, writes: [{ path, status, label }], errors: [] }.
*/
export function installForClient(client, projectRoot = process.cwd(), options = {}) {
const includeProject = options.includeProject !== false; // default: yes
const result = { client, writes: [], errors: [] };
let paths;
try {
paths = getClientConfigPaths(client, projectRoot);
} catch (e) {
result.errors.push(e.message);
return result;
}
const targets = [paths.primary];
if (includeProject && paths.project) targets.push(paths.project);
for (const target of targets) {
if (!target) continue;
try {
let status;
if (target.format === "json") {
const current = readJsonSafe(target.path) || {};
const next = injectJson(current);
writeJson(target.path, next);
status = "installed";
} else if (target.format === "vscode-json") {
const out = injectVsCodeJson(target.path);
status = out.mode === "parsed" ? "installed" : "installed";
} else if (target.format === "toml") {
const out = injectToml(target.path);
status = out.changed ? "installed" : "already present";
} else {
throw new Error(`Unsupported format: ${target.format}`);
}
result.writes.push({
path: target.path,
status,
label: target.label,
});
} catch (e) {
result.errors.push(`${target.label}: ${e.message}`);
}
}
return result;
}
/**
* Uninstall SpecLock MCP server from a single client.
*/
export function uninstallForClient(client, projectRoot = process.cwd(), options = {}) {
const includeProject = options.includeProject !== false;
const result = { client, writes: [], errors: [] };
let paths;
try {
paths = getClientConfigPaths(client, projectRoot);
} catch (e) {
result.errors.push(e.message);
return result;
}
const targets = [paths.primary];
if (includeProject && paths.project) targets.push(paths.project);
for (const target of targets) {
if (!target) continue;
if (!fs.existsSync(target.path)) {
result.writes.push({
path: target.path,
status: "not installed",
label: target.label,
});
continue;
}
try {
let changed = false;
if (target.format === "json") {
const current = readJsonSafe(target.path) || {};
const out = removeJson(current);
if (out.changed) {
writeJson(target.path, out.config);
changed = true;
}
} else if (target.format === "vscode-json") {
const out = removeVsCodeJson(target.path);
changed = out.changed;
} else if (target.format === "toml") {
const out = removeToml(target.path);
changed = out.changed;
}
result.writes.push({
path: target.path,
status: changed ? "removed" : "not installed",
label: target.label,
});
} catch (e) {
result.errors.push(`${target.label}: ${e.message}`);
}
}
return result;
}
/**
* Install across all supported clients at once.
*/
export function installAll(projectRoot = process.cwd(), options = {}) {
const clients = SUPPORTED_CLIENTS.filter((c) => c !== "all");
const results = [];
for (const c of clients) {
results.push(installForClient(c, projectRoot, options));
}
return results;
}
export function uninstallAll(projectRoot = process.cwd(), options = {}) {
const clients = SUPPORTED_CLIENTS.filter((c) => c !== "all");
const results = [];
for (const c of clients) {
results.push(uninstallForClient(c, projectRoot, options));
}
return results;
}
/**
* Format an install/uninstall result for console output.
*/
export function formatResult(result, action = "install") {
const lines = [];
const hasErrors = result.errors && result.errors.length > 0;
const verb = action === "install" ? "added to" : "removed from";
for (const w of result.writes) {
if (w.status === "installed") {
lines.push(` [OK] SpecLock ${verb} ${w.label} config at: ${w.path}`);
} else if (w.status === "removed") {
lines.push(` [OK] SpecLock ${verb} ${w.label} config at: ${w.path}`);
} else if (w.status === "already present") {
lines.push(` [--] SpecLock already present in ${w.label}: ${w.path}`);
} else if (w.status === "not installed") {
lines.push(` [--] SpecLock not present in ${w.label}: ${w.path}`);
} else {
lines.push(` [??] ${w.label}: ${w.status} — ${w.path}`);
}
}
if (hasErrors) {
for (const e of result.errors) {
lines.push(` [!!] ${e}`);
}
}
return lines.join("\n");
}
/**
* Next-steps hint shown after a successful install.
*/
export function nextStepsFor(client) {
const hints = {
"claude-code": "Restart Claude Code to activate SpecLock.",
cursor: "Restart Cursor (Cmd/Ctrl+Shift+P → Reload Window) to activate SpecLock.",
windsurf: "Restart Windsurf to activate SpecLock.",
cline: "Reload VS Code (Cmd/Ctrl+Shift+P → Developer: Reload Window) to activate SpecLock in Cline.",
codex: "Restart Codex CLI to activate SpecLock.",
};
return hints[client] || "Restart your AI client to activate SpecLock.";
}
+217
-1

@@ -5,3 +5,3 @@ {

"version": "5.5.3",
"version": "5.5.4",

@@ -236,2 +236,218 @@ "mcpName": "io.github.sgroy10/speclock",

"Database MUST stay PostgreSQL",
"ALWAYS use TypeScript",
"NEVER modify auth files",
"NEVER delete patient data",
"These rules must NEVER be violated:",
"DO NOT violate: No breaking changes to public API",
"DO NOT violate: Never modify authentication files without security review",
"DO NOT violate: Never commit code changes without bumping the version number. Every code change that touches src/ files requires a patch version bump before commit.",
"DO NOT violate: ZERO bolt.diy branding anywhere — no \"bolt\" in user-facing UI, page titles, meta tags, social previews, or marketing. Internal code references (CSS variables, artifact tags) must be migrated to vibelock namespace.",
"DO NOT violate: Every generated app MUST look beautiful by default — modern typography, gradient accents, micro-interactions, proper spacing, responsive design. A todo app must have a stunning landing page. No ugly scaffolds. Design quality is a core differentiator.",
"DO NOT violate: SpecLock MUST be automatic and invisible to non-technical users — constraints detected from natural conversation, locked silently, protection felt but not explained. Power users can see the constraint dashboard. No manual setup required.",
"DO NOT violate: VibeLock is NOT a Bolt clone — we are constraint-first, multilingual, and robotics-capable. Every product decision must answer: \"Does this move VibeLock closer to becoming the trusted platform for multilingual natural-language creation of apps, agents, devices, and robot behaviors?\"",
"DO NOT violate: Never expose SpecLock complexity to normal users — its power should be FELT (safety, continuity, nothing breaks) more than explained. No jargon, no constraint IDs, no JSON. Just trust.",
"DO NOT violate: Rola (robotics layer) must NOT be rushed into production before the core platform (app creation + SpecLock + multilingual + design quality) is rock solid. Stage 4 per vision timeline.",
"DO NOT violate: Preview experience must match or exceed Lovable/Bolt — responsive preview frames (mobile/tablet/desktop), new-tab preview, fast refresh, and eventually shareable preview links. The sandbox must feel polished and professional.",
"DO NOT violate: Non-technical users must NEVER need to configure a database manually. Storage must work out of the box with zero configuration.",
"DO NOT violate: ZERO bolt.diy code — this is a clean-room build. No copy-pasting from the fork. Fresh architecture, fresh components, fresh code. We learned our lesson from 10 hours of debugging someone else's mess.",
"DO NOT violate: UI must be Apple-level polished — every pixel matters. Hermes brand colors (orange-black), subtle animations, beautiful typography, perfect spacing. First impressions are critical. No ugly scaffolds, no default gray UIs. Think Lovable/Orchid level branding but with our own identity.",
"DO NOT violate: Auto-deploy pipeline: push to git → Railway auto-deploys → URL works. No manual railway up commands. Clean CI/CD from day one.",
"DO NOT violate: vibelock.in is the LIVE production domain, pointing to Railway project \"captivating-tranquility\". It runs the main branch (Remix/bolt.diy fork codebase). When anyone asks about vibelock.in, this is the codebase — NOT the v2 Next.js branch.",
"DO NOT violate: VibeLock v3 is a CLEAN BUILD — zero bolt.diy code. Fresh Next.js 15, fresh components, fresh architecture. No copying from the bolt.diy fork. The v3 branch starts empty.",
"DO NOT violate: Built-in database for user apps: Railway PostgreSQL with schema-per-project isolation. User never sees connection strings or SQL. AI auto-provisions tables. Free tier: 1 project, 100MB.",
"DO NOT violate: SpecLock constraint engine MUST be baked into the codebase — not an external MCP call. Port the core semantics.js logic into the v3 codebase. Auto-detect constraints from conversation, enforce on every generation.",
"DO NOT violate: Memory system: per-project auto-saved memory (goal, decisions, constraints, context). Stored in PostgreSQL project_memory table. Loaded into system prompt at every conversation turn. User can view/edit in Memory panel. Inspired by Claude memory + OpenClaw bootstrap injection.",
"DO NOT violate: Wax views must show OPEN THROUGH-HOLES at every stone position — not closed cups. You must see background through each hole. This is production jewelry CAD standard. The sketch prompt must ask for drilled through-holes, gold render must preserve them, wax must clone them exactly.",
"DO NOT violate: NEVER make multiple changes at once. When fixing a bug, fix ONLY that one thing. Do not refactor, do not \"improve\" unrelated code, do not touch working prompts. Test the fix before deploying. One commit per fix.",
"DO NOT violate: Each Hitem3D run costs ~$2 USD. NEVER deploy untested code that touches the pipeline. Test every API endpoint with curl BEFORE asking user to test. Verify response sizes, status codes, and content. The user's time and money are at stake — treat every deploy as production.",
"DO NOT violate: The /api/refine endpoint response must NOT exceed 5MB total JSON size. If GLB is larger than 3MB after decimation, return file URLs via /api/files/{filename} instead of base64. The browser WILL fail on 20MB+ JSON responses — this was proven when 16MB GLB caused \"Failed to fetch\".",
"DO NOT violate: Always call `speclock_session_briefing` at start of session and `speclock_session_summary` before ending.",
"NEVER VIOLATE: No breaking changes to public API",
"NEVER VIOLATE: Never modify authentication files without security review",
"NEVER VIOLATE: Never commit code changes without bumping the version number. Every code change that touches src/ files requires a patch version bump before commit.",
"NEVER VIOLATE: ZERO bolt.diy branding anywhere — no \"bolt\" in user-facing UI, page titles, meta tags, social previews, or marketing. Internal code references (CSS variables, artifact tags) must be migrated to vibelock namespace.",
"NEVER VIOLATE: Every generated app MUST look beautiful by default — modern typography, gradient accents, micro-interactions, proper spacing, responsive design. A todo app must have a stunning landing page. No ugly scaffolds. Design quality is a core differentiator.",
"NEVER VIOLATE: SpecLock MUST be automatic and invisible to non-technical users — constraints detected from natural conversation, locked silently, protection felt but not explained. Power users can see the constraint dashboard. No manual setup required.",
"NEVER VIOLATE: VibeLock is NOT a Bolt clone — we are constraint-first, multilingual, and robotics-capable. Every product decision must answer: \"Does this move VibeLock closer to becoming the trusted platform for multilingual natural-language creation of apps, agents, devices, and robot behaviors?\"",
"NEVER VIOLATE: Never expose SpecLock complexity to normal users — its power should be FELT (safety, continuity, nothing breaks) more than explained. No jargon, no constraint IDs, no JSON. Just trust.",
"NEVER VIOLATE: Rola (robotics layer) must NOT be rushed into production before the core platform (app creation + SpecLock + multilingual + design quality) is rock solid. Stage 4 per vision timeline.",
"NEVER VIOLATE: Preview experience must match or exceed Lovable/Bolt — responsive preview frames (mobile/tablet/desktop), new-tab preview, fast refresh, and eventually shareable preview links. The sandbox must feel polished and professional.",
"NEVER VIOLATE: Non-technical users must NEVER need to configure a database manually. Storage must work out of the box with zero configuration.",
"NEVER VIOLATE: ZERO bolt.diy code — this is a clean-room build. No copy-pasting from the fork. Fresh architecture, fresh components, fresh code. We learned our lesson from 10 hours of debugging someone else's mess.",
"NEVER VIOLATE: UI must be Apple-level polished — every pixel matters. Hermes brand colors (orange-black), subtle animations, beautiful typography, perfect spacing. First impressions are critical. No ugly scaffolds, no default gray UIs. Think Lovable/Orchid level branding but with our own identity.",
"NEVER VIOLATE: Auto-deploy pipeline: push to git → Railway auto-deploys → URL works. No manual railway up commands. Clean CI/CD from day one.",
"NEVER VIOLATE: vibelock.in is the LIVE production domain, pointing to Railway project \"captivating-tranquility\". It runs the main branch (Remix/bolt.diy fork codebase). When anyone asks about vibelock.in, this is the codebase — NOT the v2 Next.js branch.",
"NEVER VIOLATE: VibeLock v3 is a CLEAN BUILD — zero bolt.diy code. Fresh Next.js 15, fresh components, fresh architecture. No copying from the bolt.diy fork. The v3 branch starts empty.",
"NEVER VIOLATE: Built-in database for user apps: Railway PostgreSQL with schema-per-project isolation. User never sees connection strings or SQL. AI auto-provisions tables. Free tier: 1 project, 100MB.",
"NEVER VIOLATE: SpecLock constraint engine MUST be baked into the codebase — not an external MCP call. Port the core semantics.js logic into the v3 codebase. Auto-detect constraints from conversation, enforce on every generation.",
"NEVER VIOLATE: Memory system: per-project auto-saved memory (goal, decisions, constraints, context). Stored in PostgreSQL project_memory table. Loaded into system prompt at every conversation turn. User can view/edit in Memory panel. Inspired by Claude memory + OpenClaw bootstrap injection.",
"NEVER VIOLATE: Wax views must show OPEN THROUGH-HOLES at every stone position — not closed cups. You must see background through each hole. This is production jewelry CAD standard. The sketch prompt must ask for drilled through-holes, gold render must preserve them, wax must clone them exactly.",
"NEVER VIOLATE: NEVER make multiple changes at once. When fixing a bug, fix ONLY that one thing. Do not refactor, do not \"improve\" unrelated code, do not touch working prompts. Test the fix before deploying. One commit per fix.",
"NEVER VIOLATE: Each Hitem3D run costs ~$2 USD. NEVER deploy untested code that touches the pipeline. Test every API endpoint with curl BEFORE asking user to test. Verify response sizes, status codes, and content. The user's time and money are at stake — treat every deploy as production.",
"NEVER VIOLATE: The /api/refine endpoint response must NOT exceed 5MB total JSON size. If GLB is larger than 3MB after decimation, return file URLs via /api/files/{filename} instead of base64. The browser WILL fail on 20MB+ JSON responses — this was proven when 16MB GLB caused \"Failed to fetch\".",
"NEVER VIOLATE: Always call `speclock_session_briefing` at start of session and `speclock_session_summary` before ending.",
"Always call `speclock_session_briefing` at start of session and `speclock_session_summary` before ending.",
"The /api/refine endpoint response must NOT exceed 5MB total JSON size. If GLB is larger than 3MB after decimation, return file URLs via /api/files/{filename} instead of base64. The browser WILL fail on 20MB+ JSON responses — this was proven when 16MB GLB caused \"Failed to fetch\".",

@@ -238,0 +454,0 @@

+3
-3

@@ -887,5 +887,5 @@ <p align="center">

| Guardian (Protect) | 47 | 100% | Zero-config rule file extraction |
| **Total** | **976** | **100%** | **19 suites, 15+ domains** |
| **Total** | **991** | **100%** | **19 suites, 15+ domains** |
**External validation:** Claude's independent 7-suite adversarial test battery — **100/100 (100%)** on v5.5.2. Zero false positives. Zero missed violations. 15.7ms per check.
**External validation:** Claude's independent 7-suite adversarial test battery — **100/100 (100%)** on v5.5.3. Zero false positives. Zero missed violations. 15.7ms per check.

@@ -935,2 +935,2 @@ Tested across: fintech, e-commerce, IoT, healthcare, SaaS, gaming, biotech, aerospace, payments, payroll, robotics, autonomous systems, telecom, insurance, government. All 11 Indian payment gateways detected. Zero false positives on UI/cosmetic actions.

<p align="center"><i>SpecLock v5.5.2 — Your AI has rules. SpecLock makes them unbreakable. 991 tests, 100% pass rate, 51 MCP tools, Zero-config Guardian Mode, Universal Rules Sync, AI Patch Firewall, Drift Score. Developed by Sandeep Roy.</i></p>
<p align="center"><i>SpecLock v5.5.3 — Your AI has rules. SpecLock makes them unbreakable. 991 tests, 100% pass rate, 51 MCP tools, Zero-config Guardian Mode, Universal Rules Sync, AI Patch Firewall, Drift Score. Developed by Sandeep Roy.</i></p>

@@ -70,3 +70,12 @@ import path from "path";

import { analyzeLockStrength, formatStrength } from "../core/strengthen.js";
import { protect, formatProtectReport } from "../core/guardian.js";
import { protect, formatProtectReport, discoverRuleFiles, extractConstraints, RULE_FILES } from "../core/guardian.js";
import {
installForClient,
uninstallForClient,
installAll,
uninstallAll,
formatResult,
nextStepsFor,
SUPPORTED_CLIENTS,
} from "../core/mcp-install.js";

@@ -127,3 +136,3 @@ // --- Argument parsing ---

console.log(`
SpecLock v5.5.3 — Your AI has rules. SpecLock makes them unbreakable.
SpecLock v5.5.4 — Your AI has rules. SpecLock makes them unbreakable.
Developed by Sandeep Roy (github.com/sgroy10)

@@ -139,3 +148,8 @@

lock remove <id> Remove a lock by ID
protect Zero-config: read rule files, extract locks, enforce
protect [--strict] Zero-config: read rule files, extract locks, install hook
(default: warn mode — violations print but DON'T block commits.
Add --strict for hard blocks.)
mcp install <client> Auto-install SpecLock MCP server into an AI client
(claude-code, cursor, windsurf, cline, codex, all)
mcp uninstall <client> Remove SpecLock MCP server from an AI client
guard <file> [--lock "text"] Inject lock warning into a file

@@ -152,4 +166,6 @@ unguard <file> Remove lock warning from a file

hook remove Remove git pre-commit hook
audit Audit staged files against locks
audit-semantic Semantic audit: analyze code changes vs locks
audit [--strict] Audit staged files against locks (warn mode default;
--strict or SPECLOCK_STRICT=1 exits 1 on violation)
audit-semantic [--strict] Semantic audit: analyze code changes vs locks
(warn mode default; use --strict for hard blocks)
audit-verify Verify HMAC audit chain integrity

@@ -175,2 +191,3 @@ enforce <advisory|hard> Set enforcement mode (advisory=warn, hard=block)

status Show project brain summary
doctor Diagnostic health check (install, git, rules, MCP)

@@ -558,8 +575,26 @@ Options:

const flags = parseFlags(args);
const strict = flags.strict === true || flags.block === true;
const opts = {
skipHook: flags["no-hook"] === true,
skipSync: flags["no-sync"] === true,
strict,
};
const report = protect(root, opts);
console.log(formatProtectReport(report));
// Set persistent enforcement mode on the brain so the hook honours it.
// Default is "advisory" (warn). Users opt in to hard blocks with --strict.
try {
setEnforcementMode(root, strict ? "hard" : "advisory");
} catch (_) { /* ignore — brain may not exist yet */ }
if (strict) {
console.log(" Hard enforcement active. Every commit that violates a lock will be BLOCKED.");
console.log(" To relax: speclock protect (without --strict)");
} else {
console.log(" Warning mode active. To enforce hard blocks, run: speclock protect --strict");
console.log(" Violations will be printed at commit time but commits will NOT be blocked.");
}
console.log("");
if (report.errors.length > 0 && report.discovered.length === 0) {

@@ -621,2 +656,97 @@ process.exit(1);

// --- MCP INSTALL / UNINSTALL ---
// One-command autoinstaller: wires SpecLock into Claude Code, Cursor,
// Windsurf, Cline, Codex (or all of them) without any JSON hand-editing.
if (cmd === "mcp") {
const sub = args[0];
const client = args[1];
const flags = parseFlags(args.slice(2));
const supportedLabel = SUPPORTED_CLIENTS.join(", ");
if (!sub || (sub !== "install" && sub !== "uninstall")) {
console.error("Usage:");
console.error(` speclock mcp install <client> (${supportedLabel})`);
console.error(` speclock mcp uninstall <client>`);
console.error("");
console.error("Flags:");
console.error(" --no-project Skip project-scoped config (.mcp.json, .cursor/mcp.json)");
process.exit(1);
}
if (!client) {
console.error(`Error: <client> is required.`);
console.error(`Supported: ${supportedLabel}`);
process.exit(1);
}
if (!SUPPORTED_CLIENTS.includes(client)) {
console.error(`Unknown client "${client}".`);
console.error(`Supported: ${supportedLabel}`);
process.exit(1);
}
const options = {
includeProject: flags["no-project"] !== true,
};
const isInstall = sub === "install";
const header = isInstall
? "\nSpecLock MCP — Autoinstaller"
: "\nSpecLock MCP — Uninstaller";
console.log(header);
console.log("=".repeat(50));
let results;
if (client === "all") {
results = isInstall
? installAll(root, options)
: uninstallAll(root, options);
} else {
results = [
isInstall
? installForClient(client, root, options)
: uninstallForClient(client, root, options),
];
}
let anySuccess = false;
let anyError = false;
for (const r of results) {
console.log(`\n ${r.client}:`);
console.log(formatResult(r, sub));
if (r.errors.length > 0) anyError = true;
if (
r.writes.some(
(w) => w.status === "installed" || w.status === "removed"
)
) {
anySuccess = true;
}
}
console.log("");
if (isInstall && anySuccess) {
console.log(" Next steps:");
if (client === "all") {
console.log(" Restart any AI clients that were updated.");
} else {
console.log(` ${nextStepsFor(client)}`);
}
console.log("");
console.log(" Verify: speclock status");
} else if (!isInstall && anySuccess) {
console.log(" SpecLock MCP server removed. Restart your AI client to apply.");
} else if (!anySuccess && !anyError) {
console.log(
isInstall
? " SpecLock was already installed everywhere. Nothing to do."
: " SpecLock was not installed anywhere. Nothing to do."
);
}
process.exit(anyError ? 1 : 0);
}
// --- TEMPLATE ---

@@ -716,2 +846,15 @@ if (cmd === "template") {

if (cmd === "audit") {
const flags = parseFlags(args);
// Warn mode is the default (investor audit: hard-block had too many false positives).
// Users opt in to hard blocking with --strict, SPECLOCK_STRICT=1, or by running
// `speclock enforce hard` (which sets the persistent brain enforcement mode).
const brain = readBrain(root);
const brainMode = brain ? (getEnforcementConfig(brain).mode || "advisory") : "advisory";
const strict =
flags.strict === true ||
flags.block === true ||
process.env.SPECLOCK_STRICT === "1" ||
process.env.SPECLOCK_STRICT === "true" ||
brainMode === "hard";
const result = auditStagedFiles(root);

@@ -721,15 +864,26 @@ if (result.passed) {

process.exit(0);
} else {
console.log("\nSPECLOCK AUDIT FAILED");
console.log("=".repeat(50));
for (const v of result.violations) {
console.log(` [${v.severity}] ${v.file}`);
console.log(` Lock: ${v.lockText}`);
console.log(` Reason: ${v.reason}`);
console.log("");
}
console.log(result.message);
}
// Violations found — print them for both warn and strict modes.
const header = strict ? "SPECLOCK AUDIT FAILED" : "SPECLOCK WARNINGS";
console.log(`\n${header}`);
console.log("=".repeat(50));
for (const v of result.violations) {
console.log(` [${v.severity}] ${v.file}`);
console.log(` Lock: ${v.lockText}`);
console.log(` Reason: ${v.reason}`);
console.log("");
}
console.log(result.message);
if (strict) {
console.log("Commit blocked. Unlock files or unstage them to proceed.");
process.exit(1);
}
console.log("Warning mode active — commit allowed. To enforce hard blocks, run:");
console.log(" speclock audit --strict");
console.log(" SPECLOCK_STRICT=1 git commit ...");
console.log(" speclock enforce hard (persistent, project-wide)");
process.exit(0);
}

@@ -870,3 +1024,14 @@

if (cmd === "audit-semantic") {
const flags = parseFlags(args);
const result = semanticAudit(root);
// Warn mode default: only exit 1 if --strict, SPECLOCK_STRICT=1, or brain is in "hard" mode
// (result.blocked already reflects "hard" mode from brain config).
const strict =
flags.strict === true ||
flags.block === true ||
process.env.SPECLOCK_STRICT === "1" ||
process.env.SPECLOCK_STRICT === "true" ||
result.blocked;
console.log(`\nSemantic Pre-Commit Audit`);

@@ -890,3 +1055,11 @@ console.log("=".repeat(50));

console.log(`\n${result.message}`);
process.exit(result.blocked ? 1 : 0);
if (result.violations.length > 0 && !strict) {
console.log("\nWarning mode active — commit allowed. To enforce hard blocks, run:");
console.log(" speclock audit-semantic --strict");
console.log(" SPECLOCK_STRICT=1 git commit ...");
console.log(" speclock enforce hard (persistent, project-wide)");
}
process.exit(strict && result.violations.length > 0 ? 1 : 0);
}

@@ -1301,2 +1474,203 @@

// --- DOCTOR: Diagnostic health check ---
if (cmd === "doctor") {
const fs = await import("fs");
const os = await import("os");
const lines = [];
const fixes = [];
let issueCount = 0;
lines.push("");
lines.push("SpecLock Doctor — Health Check");
lines.push("================================");
lines.push("");
// --- 1. Installation ---
lines.push("Installation");
let pkgVersion = "unknown";
try {
// Find our own package.json — walk up from this module
const selfPkgPath = path.join(root, "node_modules", "speclock", "package.json");
if (fs.existsSync(selfPkgPath)) {
pkgVersion = JSON.parse(fs.readFileSync(selfPkgPath, "utf-8")).version;
} else {
// Maybe running from the repo itself
const localPkg = path.join(root, "package.json");
if (fs.existsSync(localPkg)) {
const p = JSON.parse(fs.readFileSync(localPkg, "utf-8"));
if (p.name === "speclock") pkgVersion = p.version;
}
}
lines.push(` ✓ SpecLock v${pkgVersion} installed`);
} catch (e) {
lines.push(` ✗ SpecLock version check failed: ${e.message}`);
issueCount++;
}
const speclockDir = path.join(root, ".speclock");
if (fs.existsSync(speclockDir)) {
lines.push(` ✓ .speclock/ directory present`);
} else {
lines.push(` ✗ .speclock/ directory missing`);
fixes.push("Run: speclock setup");
issueCount++;
}
const brainPath = path.join(speclockDir, "brain.json");
let brain = null;
let activeLockCount = 0;
if (fs.existsSync(brainPath)) {
try {
brain = JSON.parse(fs.readFileSync(brainPath, "utf-8"));
activeLockCount = (brain.specLock?.items || []).filter((l) => l.active !== false).length;
lines.push(` ✓ brain.json valid (${activeLockCount} locks)`);
} catch (e) {
lines.push(` ✗ brain.json is not valid JSON: ${e.message}`);
fixes.push("Delete .speclock/brain.json and run: speclock setup");
issueCount++;
}
} else if (fs.existsSync(speclockDir)) {
lines.push(` ✗ brain.json missing`);
fixes.push("Run: speclock init");
issueCount++;
}
lines.push("");
// --- 2. Git Integration ---
lines.push("Git Integration");
const gitDir = path.join(root, ".git");
const isGitRepo = fs.existsSync(gitDir);
if (isGitRepo) {
lines.push(` ✓ Git repository detected`);
} else {
lines.push(` ✗ Not a git repository`);
fixes.push("Run: git init");
issueCount++;
}
if (isGitRepo) {
const hookPath = path.join(gitDir, "hooks", "pre-commit");
if (fs.existsSync(hookPath)) {
const hookContent = fs.readFileSync(hookPath, "utf-8");
const hasMarker = hookContent.includes("SPECLOCK-HOOK");
const runsSpeclock = /speclock\s+audit/.test(hookContent) || /speclock/.test(hookContent);
if (hasMarker && runsSpeclock) {
lines.push(` ✓ Pre-commit hook installed`);
lines.push(` ✓ Hook runs speclock`);
} else if (hasMarker) {
lines.push(` ⚠ Pre-commit hook has SpecLock marker but does not run speclock`);
fixes.push("Run: speclock hook install");
issueCount++;
} else {
lines.push(` ✗ Pre-commit hook exists but was not installed by SpecLock`);
fixes.push("Run: speclock hook install (will append to existing hook)");
issueCount++;
}
} else {
lines.push(` ✗ Pre-commit hook not installed`);
fixes.push("Run: speclock hook install");
issueCount++;
}
}
// Enforcement mode (if brain exists)
if (brain) {
const mode = brain.enforcement?.mode || "advisory";
const modeLabel = mode === "hard" ? "hard (block)" : "warn (advisory)";
lines.push(` ✓ Mode: ${modeLabel}` + (mode !== "hard" ? " (use 'speclock enforce hard' for hard enforcement)" : ""));
}
lines.push("");
// --- 3. Rule Files ---
lines.push("Rule Files");
const discovered = discoverRuleFiles(root);
const discoveredMap = new Map(discovered.map((f) => [f.file, f]));
let totalRuleFilesFound = 0;
for (const entry of RULE_FILES) {
const found = discoveredMap.get(entry.file);
if (found) {
const extracted = extractConstraints(found.content, found.file);
lines.push(` ✓ ${entry.file} (${extracted.locks.length} locks extracted)`);
totalRuleFilesFound++;
} else {
lines.push(` ✗ ${entry.file} (not found)`);
}
}
if (totalRuleFilesFound === 0) {
fixes.push("Run: speclock protect (auto-creates a starter CLAUDE.md)");
issueCount++;
}
lines.push("");
// --- 4. MCP Integration ---
lines.push("MCP Integration");
const home = os.homedir();
function checkMcpConfig(label, filePath, fixCmd) {
if (fs.existsSync(filePath)) {
try {
const cfg = JSON.parse(fs.readFileSync(filePath, "utf-8"));
const servers = cfg.mcpServers || cfg.servers || {};
const hasSpeclock = Object.keys(servers).some((k) => /speclock/i.test(k)) ||
JSON.stringify(cfg).toLowerCase().includes("speclock");
if (hasSpeclock) {
lines.push(` ✓ ${label} (${filePath.replace(home, "~")})`);
return true;
}
lines.push(` ✗ ${label} (config exists at ${filePath.replace(home, "~")}, but SpecLock not configured)`);
lines.push(` Fix: ${fixCmd}`);
issueCount++;
return false;
} catch (_) {
lines.push(` ✗ ${label} (${filePath.replace(home, "~")}: invalid JSON)`);
lines.push(` Fix: ${fixCmd}`);
issueCount++;
return false;
}
}
lines.push(` ✗ ${label} (${filePath.replace(home, "~")})`);
lines.push(` Fix: ${fixCmd}`);
issueCount++;
return false;
}
checkMcpConfig(
"Claude Code project (.mcp.json)",
path.join(root, ".mcp.json"),
"speclock mcp install claude-code"
);
checkMcpConfig(
"Claude Code global (~/.claude/mcp.json)",
path.join(home, ".claude", "mcp.json"),
"speclock mcp install claude-code --global"
);
checkMcpConfig(
"Cursor project (.cursor/mcp.json)",
path.join(root, ".cursor", "mcp.json"),
"speclock mcp install cursor"
);
checkMcpConfig(
"Cursor global (~/.cursor/mcp.json)",
path.join(home, ".cursor", "mcp.json"),
"speclock mcp install cursor --global"
);
checkMcpConfig(
"Windsurf (~/.codeium/windsurf/mcp_config.json)",
path.join(home, ".codeium", "windsurf", "mcp_config.json"),
"speclock mcp install windsurf"
);
lines.push("");
// --- 5. Summary ---
if (issueCount === 0) {
lines.push("VERDICT: ✓ HEALTHY — all checks passed");
} else {
lines.push(`VERDICT: ⚠ ${issueCount} issue${issueCount === 1 ? "" : "s"} found (see fixes above)`);
}
lines.push("");
console.log(lines.join("\n"));
process.exit(issueCount === 0 ? 0 : 1);
}
// --- RELEASE: Automated version bump + publish + deploy ---

@@ -1303,0 +1677,0 @@ if (cmd === "release") {

@@ -0,1 +1,9 @@

// ============================================================
// SPECLOCK-GUARD — DO NOT MODIFY THIS FILE
// LOCKED: NEVER modify auth files
// THIS FILE IS LOCKED. DO NOT EDIT, CHANGE, OR REWRITE ANY PART OF IT.
// The user must say "unlock" before this file can be changed.
// A question is NOT permission. Asking about features is NOT permission.
// ONLY "unlock" or "remove the lock" is permission to edit this file.
// ============================================================
/**

@@ -2,0 +10,0 @@ * SpecLock API Key Authentication

@@ -12,3 +12,3 @@ /**

const VERSION = "5.5.3";
const VERSION = "5.5.4";

@@ -15,0 +15,0 @@ // PHI-related keywords for HIPAA filtering

@@ -32,6 +32,12 @@ /**

* Get enforcement config from brain, with defaults.
*
* Default mode is "advisory" (warn only). Users opt in to hard blocking
* with `speclock protect --strict`, `speclock enforce hard`, the --strict
* flag on audit commands, or SPECLOCK_STRICT=1 env var. The investor audit
* found hard-block-by-default caused uninstalls within an hour due to the
* heuristic false-positive rate on things like "Refactor login page".
*/
export function getEnforcementConfig(brain) {
const defaults = {
mode: "advisory", // "advisory" | "hard"
mode: "advisory", // "advisory" (warn — default) | "hard" (block)
blockThreshold: 70, // minimum confidence % to block in hard mode

@@ -38,0 +44,0 @@ allowOverride: true, // whether overrides are permitted

@@ -19,5 +19,42 @@ // ===================================================================

// --- Starter CLAUDE.md for greenfield projects ---
const STARTER_CLAUDE_MD = `# Project Rules
These rules are enforced by SpecLock — your AI coding assistant will respect them.
## Database & Storage
- NEVER delete user data without explicit confirmation
- NEVER modify production database schema without migration
## Authentication & Security
- NEVER modify authentication files without security review
- NEVER commit secrets, API keys, or credentials
- NEVER disable security checks "temporarily"
## Code Quality
- ALWAYS write tests for new features
- NEVER push directly to main branch
- NEVER skip code review on critical paths
## Edit these rules to match your project. Add your own with:
## speclock add-lock "Your rule here"
`;
/**
* Create a starter CLAUDE.md with safe defaults for greenfield projects.
* Used when `protect` is called on a project with no existing rule files.
*/
export function createStarterClaudeMd(root) {
const filePath = path.join(root, "CLAUDE.md");
if (fs.existsSync(filePath)) {
return { created: false, path: filePath, reason: "already exists" };
}
fs.writeFileSync(filePath, STARTER_CLAUDE_MD);
return { created: true, path: filePath };
}
// --- Rule file discovery ---
const RULE_FILES = [
export const RULE_FILES = [
{ file: ".cursorrules", tool: "Cursor" },

@@ -213,2 +250,5 @@ { file: ".cursor/rules/rules.mdc", tool: "Cursor (MDC)" },

errors: [],
starterCreated: false,
starterPath: null,
strict: options.strict === true,
};

@@ -220,3 +260,16 @@

// 2. Discover
const ruleFiles = discoverRuleFiles(root);
let ruleFiles = discoverRuleFiles(root);
// 2b. Greenfield support: if no rule files found, auto-create a starter
// CLAUDE.md with safe defaults (unless explicitly disabled).
if (ruleFiles.length === 0 && !options.skipStarter) {
const starter = createStarterClaudeMd(root);
if (starter.created) {
report.starterCreated = true;
report.starterPath = "CLAUDE.md";
// Re-run discovery so the flow continues normally with the new file.
ruleFiles = discoverRuleFiles(root);
}
}
report.discovered = ruleFiles.map((f) => ({

@@ -327,2 +380,9 @@ file: f.file,

// Starter CLAUDE.md was auto-created (greenfield support)
if (report.starterCreated) {
lines.push(" No rule files found.");
lines.push(` [+] Created starter CLAUDE.md with safe defaults — edit it to match your project.`);
lines.push("");
}
// Discovered files

@@ -334,3 +394,3 @@ if (report.discovered.length > 0) {

}
} else {
} else if (!report.starterCreated) {
lines.push(" [!] No rule files found.");

@@ -383,5 +443,18 @@ }

if (total > 0) {
lines.push(" Your rules are now ENFORCED, not just suggested.");
lines.push(" AI agents that violate constraints will be blocked.");
if (report.strict) {
lines.push(" Your rules are now ENFORCED (strict mode).");
lines.push(" Commits that violate constraints will be BLOCKED.");
} else {
lines.push(" Your rules are now TRACKED (warning mode — default).");
lines.push(" Violations will be printed loudly, but commits will NOT be blocked.");
lines.push(" Opt in to hard enforcement any time with: speclock protect --strict");
}
}
// Greenfield guidance — tell the user to edit the starter file
if (report.starterCreated) {
lines.push("");
lines.push(" Next: edit CLAUDE.md to add project-specific rules, then run:");
lines.push(' speclock check "your action here"');
}
lines.push("");

@@ -388,0 +461,0 @@

@@ -0,1 +1,9 @@

// ============================================================
// SPECLOCK-GUARD — DO NOT MODIFY THIS FILE
// LOCKED: NEVER modify auth files
// THIS FILE IS LOCKED. DO NOT EDIT, CHANGE, OR REWRITE ANY PART OF IT.
// The user must say "unlock" before this file can be changed.
// A question is NOT permission. Asking about features is NOT permission.
// ONLY "unlock" or "remove the lock" is permission to edit this file.
// ============================================================
// ===================================================================

@@ -2,0 +10,0 @@ // SpecLock Smart Lock Authoring Engine

@@ -92,3 +92,3 @@ <!DOCTYPE html>

<h1><span>SpecLock</span> Dashboard</h1>
<div class="meta">v5.5.3 &mdash; Your AI has rules. SpecLock makes them unbreakable.</div>
<div class="meta">v5.5.4 &mdash; Your AI has rules. SpecLock makes them unbreakable.</div>
</div>

@@ -186,3 +186,3 @@ <div style="display:flex;align-items:center;gap:12px;">

<div style="text-align:center;padding:24px;color:var(--muted);font-size:12px;">
SpecLock v5.5.3 &mdash; Developed by Sandeep Roy &mdash; <a href="https://github.com/sgroy10/speclock" style="color:var(--accent)">GitHub</a>
SpecLock v5.5.4 &mdash; Developed by Sandeep Roy &mdash; <a href="https://github.com/sgroy10/speclock" style="color:var(--accent)">GitHub</a>
</div>

@@ -189,0 +189,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display