speclock 5.4.1 → 5.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,13 @@
8
8
  <a href="https://www.npmjs.com/package/speclock"><img src="https://img.shields.io/npm/v/speclock.svg?style=flat-square&color=4F46E5" alt="npm version" /></a>
9
9
  <a href="https://www.npmjs.com/package/speclock"><img src="https://img.shields.io/npm/dm/speclock.svg?style=flat-square&color=22C55E" alt="npm downloads" /></a>
10
10
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="MIT License" /></a>
11
- <a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-42_tools-green.svg?style=flat-square" alt="MCP 49 tools" /></a>
11
+ <a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-51_tools-green.svg?style=flat-square" alt="MCP 51 tools" /></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <img src="https://img.shields.io/badge/drift_score-12%2F100-brightgreen.svg?style=flat-square" alt="Drift Score" />
16
+ <img src="https://img.shields.io/badge/lock_coverage-83%25-brightgreen.svg?style=flat-square" alt="Lock Coverage" />
17
+ <img src="https://img.shields.io/badge/lock_strength-85%2F100-brightgreen.svg?style=flat-square" alt="Lock Strength" />
12
18
  </p>
13
19
 
14
20
  <p align="center">
@@ -19,6 +25,10 @@
19
25
 
20
26
  ---
21
27
 
28
+ > **New in v5.4:** `speclock drift` — the only tool that measures how much your AI has drifted from your architecture. `speclock coverage` — find what's unprotected. `speclock strengthen` — grade your locks. Three numbers that tell your project's whole story.
29
+
30
+ ---
31
+
22
32
  ```
23
33
  You: "Never touch the auth system"
24
34
  AI: 🔒 Locked.
@@ -32,8 +42,8 @@ AI: ⚠️ BLOCKED — violates lock "Never touch the auth system"
32
42
  Should I find another approach?
33
43
  ```
34
44
 
35
- **100/100 on Claude's independent test suite. 929 tests across 18 suites. 0 false positives. 15.7ms per check.**
36
- **Gemini Flash hybrid, Spec Compiler, Code Graph, Typed Constraints, Python SDK, ROS2 integration.**
45
+ **100/100 on Claude's independent test suite. 976 tests across 19 suites. 0 false positives. 15.7ms per check.**
46
+ **Zero-config Guardian Mode, Universal Rules Sync, AI Patch Firewall, Drift Score, Spec Compiler, Code Graph.**
37
47
 
38
48
  ---
39
49
 
@@ -45,6 +55,14 @@ npx speclock setup --goal "Build my app"
45
55
 
46
56
  That's it. One command. Works everywhere — Bolt.new, Claude Code, Cursor, Lovable, Windsurf, Cline, Aider.
47
57
 
58
+ ### Already have `.cursorrules`, `CLAUDE.md`, or `AGENTS.md`?
59
+
60
+ ```bash
61
+ npx speclock protect
62
+ ```
63
+
64
+ Zero flags. Reads your existing rule files, extracts enforceable constraints, installs a pre-commit hook, and syncs rules to every AI tool. **Your rules are now enforced, not just suggested.**
65
+
48
66
  ## The Problem
49
67
 
50
68
  AI coding tools have memory now. Claude Code has `CLAUDE.md`. Cursor has `.cursorrules`. Mem0 exists.
@@ -581,7 +599,7 @@ POST /api/v2/graph/build
581
599
 
582
600
  ---
583
601
 
584
- ## 49 MCP Tools
602
+ ## 51 MCP Tools
585
603
 
586
604
  <details>
587
605
  <summary><b>Memory</b> — goal, locks, decisions, notes, deploy facts</summary>
@@ -797,7 +815,7 @@ The AI opens the file and sees:
797
815
  │ AI Tool (Claude Code, Cursor, Bolt.new...) │
798
816
  └────────────┬──────────────────┬──────────────────┘
799
817
  │ │
800
- MCP Protocol (49 tools) npm File-Based
818
+ MCP Protocol (51 tools) npm File-Based
801
819
  │ (SPECLOCK.md + CLI)
802
820
  │ │
803
821
  ┌────────────▼──────────────────▼──────────────────┐
@@ -865,9 +883,10 @@ The AI opens the file and sees:
865
883
  | Question Framing | 9 | 100% | "What if we..." and "How hard would it be..." |
866
884
  | REST API v2 | 9 | 100% | Typed constraint endpoints, SSE |
867
885
  | PII/Export Detection | 8 | 100% | SSN, email export, data access violations |
868
- | **Total** | **929** | **100%** | **18 suites, 15+ domains** |
886
+ | Guardian (Protect) | 47 | 100% | Zero-config rule file extraction |
887
+ | **Total** | **976** | **100%** | **19 suites, 15+ domains** |
869
888
 
870
- **External validation:** Claude's independent 7-suite adversarial test battery — **100/100 (100%)** on v5.4.0. Zero false positives. Zero missed violations. 15.7ms per check.
889
+ **External validation:** Claude's independent 7-suite adversarial test battery — **100/100 (100%)** on v5.5.0. Zero false positives. Zero missed violations. 15.7ms per check.
871
890
 
872
891
  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.
873
892
 
@@ -905,11 +924,11 @@ Issues and PRs welcome on [GitHub](https://github.com/sgroy10/speclock).
905
924
 
906
925
  **SpecLock** is created and maintained by **[Sandeep Roy](https://github.com/sgroy10)**.
907
926
 
908
- Sandeep Roy is the sole developer of SpecLock — the AI Constraint Engine that enforces project rules across AI coding sessions. All 49 MCP tools, the semantic conflict detection engine, enterprise security features (SOC 2, HIPAA, RBAC, encryption), and the pre-publish test gate were designed and built by Sandeep Roy.
927
+ Sandeep Roy is the sole developer of SpecLock — the AI Constraint Engine that enforces project rules across AI coding sessions. All 51 MCP tools, the semantic conflict detection engine, enterprise security features (SOC 2, HIPAA, RBAC, encryption), and the pre-publish test gate were designed and built by Sandeep Roy.
909
928
 
910
929
  - GitHub: [@sgroy10](https://github.com/sgroy10)
911
930
  - npm: [speclock](https://www.npmjs.com/package/speclock)
912
931
 
913
932
  ---
914
933
 
915
- <p align="center"><i>SpecLock v5.4.0 — Developed by Sandeep Roy 929 tests, 100% pass rate, 49 MCP tools, Universal Rules Sync, Incident Replay, AI Patch Firewall, Spec Compiler, Code Graph, Typed Constraints, Python SDK, ROS2, REST API v2. Because remembering isn't enough.</i></p>
934
+ <p align="center"><i>SpecLock v5.5.0 — Your AI has rules. SpecLock makes them unbreakable. 976 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>
package/bin/speclock.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  // SpecLock CLI — AI Constraint Engine
3
3
  // Developed by Sandeep Roy (https://github.com/sgroy10)
4
4
  import "../src/cli/index.js";
package/package.json CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  "name": "speclock",
4
4
 
5
- "version": "5.4.1",
5
+ "version": "5.5.1",
6
6
 
7
- "description": "AI Constraint Engine by Sandeep Roy Universal Rules Sync (one command syncs constraints to Cursor, Claude Code, Copilot, Windsurf, Gemini, Aider, AGENTS.md). AI Patch Firewall, diff-native review, Patch Gateway (ALLOW/WARN/BLOCK), Spec Compiler (NL→constraints), Code Graph (blast radius), Typed constraints, REST API v2, Python SDK, ROS2 integration. 49 MCP tools, Gemini LLM hybrid, HMAC audit chain, RBAC, encryption, SOC 2/HIPAA compliance. Developed by Sandeep Roy.",
7
+ "description": "Stop AI from breaking code you told it not to touch. Enforces .cursorrules, CLAUDE.md, and AGENTS.md not just suggests. Zero-config: npx speclock protect reads your existing AI rule files, extracts constraints, installs pre-commit hooks, and makes your rules unbreakable. 51 MCP tools, Universal Rules Sync, AI Patch Firewall, Spec Compiler, Code Graph, Typed Constraints, Drift Score, HMAC audit chain, SOC 2/HIPAA compliance. Developed by Sandeep Roy.",
8
8
 
9
9
  "type": "module",
10
10
 
package/src/cli/index.js CHANGED
@@ -67,6 +67,7 @@ import { getReplay, listSessions, formatReplay } from "../core/replay.js";
67
67
  import { computeDriftScore, formatDriftScore } from "../core/drift-score.js";
68
68
  import { computeCoverage, formatCoverage } from "../core/coverage.js";
69
69
  import { analyzeLockStrength, formatStrength } from "../core/strengthen.js";
70
+ import { protect, formatProtectReport } from "../core/guardian.js";
70
71
 
71
72
  // --- Argument parsing ---
72
73
 
@@ -122,7 +123,7 @@ function refreshContext(root) {
122
123
 
123
124
  function printHelp() {
124
125
  console.log(`
125
- SpecLock v5.4.1 — AI Constraint Engine (Universal Rules Sync + Spec Compiler + Code Graph + Typed Constraints + Python SDK + ROS2 + REST API v2 + Gemini LLM + Policy-as-Code + Auth + RBAC + Encryption)
126
+ SpecLock v5.5.1 — Your AI has rules. SpecLock makes them unbreakable.
126
127
  Developed by Sandeep Roy (github.com/sgroy10)
127
128
 
128
129
  Usage: speclock <command> [options]
@@ -133,6 +134,7 @@ Commands:
133
134
  goal <text> Set or update the project goal
134
135
  lock <text> [--tags a,b] Add a non-negotiable constraint
135
136
  lock remove <id> Remove a lock by ID
137
+ protect Zero-config: read rule files, extract locks, enforce
136
138
  guard <file> [--lock "text"] Inject lock warning into a file
137
139
  unguard <file> Remove lock warning from a file
138
140
  decide <text> [--tags a,b] Record a decision
@@ -545,6 +547,21 @@ Tip: Run "speclock sync --all" to push constraints to Cursor, Claude, Copilot, W
545
547
  return;
546
548
  }
547
549
 
550
+ // --- PROTECT (zero-config guardian mode) ---
551
+ if (cmd === "protect") {
552
+ const flags = parseFlags(args);
553
+ const opts = {
554
+ skipHook: flags["no-hook"] === true,
555
+ skipSync: flags["no-sync"] === true,
556
+ };
557
+ const report = protect(root, opts);
558
+ console.log(formatProtectReport(report));
559
+ if (report.errors.length > 0 && report.discovered.length === 0) {
560
+ process.exit(1);
561
+ }
562
+ return;
563
+ }
564
+
548
565
  // --- FACTS ---
549
566
  if (cmd === "facts") {
550
567
  const sub = args.shift();
@@ -9,7 +9,7 @@
9
9
  import { readBrain, readEvents } from "./storage.js";
10
10
  import { verifyAuditChain } from "./audit.js";
11
11
 
12
- const VERSION = "5.4.1";
12
+ const VERSION = "5.5.1";
13
13
 
14
14
  // PHI-related keywords for HIPAA filtering
15
15
  const PHI_KEYWORDS = [
@@ -73,6 +73,14 @@ export {
73
73
  semanticAudit,
74
74
  } from "./pre-commit-semantic.js";
75
75
 
76
+ // --- Guardian (v5.5) ---
77
+ export {
78
+ discoverRuleFiles,
79
+ extractConstraints,
80
+ protect,
81
+ formatProtectReport,
82
+ } from "./guardian.js";
83
+
76
84
  // --- File Watcher ---
77
85
  // watchRepo stays here because it uses multiple modules
78
86
 
@@ -0,0 +1,384 @@
1
+ // ===================================================================
2
+ // SpecLock Guardian — Zero-Config Protection from AI Rule Files
3
+ // Reads existing .cursorrules, CLAUDE.md, AGENTS.md, copilot-instructions.md
4
+ // and auto-extracts enforceable constraints. One command. No flags.
5
+ //
6
+ // "Your AI has rules. SpecLock makes them unbreakable."
7
+ //
8
+ // Developed by Sandeep Roy (https://github.com/sgroy10)
9
+ // ===================================================================
10
+
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import { ensureInit, addLock, addDecision } from "./memory.js";
14
+ import { readBrain } from "./storage.js";
15
+ import { installHook, isHookInstalled } from "./hooks.js";
16
+ import { syncRules } from "./rules-sync.js";
17
+ import { generateContext } from "./context.js";
18
+
19
+ // --- Rule file discovery ---
20
+
21
+ const RULE_FILES = [
22
+ { file: ".cursorrules", tool: "Cursor" },
23
+ { file: ".cursor/rules/rules.mdc", tool: "Cursor (MDC)" },
24
+ { file: "CLAUDE.md", tool: "Claude Code" },
25
+ { file: "AGENTS.md", tool: "AGENTS.md" },
26
+ { file: ".github/copilot-instructions.md", tool: "GitHub Copilot" },
27
+ { file: ".windsurfrules", tool: "Windsurf" },
28
+ { file: ".windsurf/rules/rules.md", tool: "Windsurf (dir)" },
29
+ { file: "GEMINI.md", tool: "Gemini" },
30
+ { file: ".aider.conf.yml", tool: "Aider" },
31
+ { file: "COPILOT.md", tool: "Copilot (alt)" },
32
+ { file: ".github/instructions.md", tool: "GitHub (alt)" },
33
+ ];
34
+
35
+ // Files that SpecLock's sync creates — these are OUTPUT, not INPUT.
36
+ // Never read these back as source rule files.
37
+ const SPECLOCK_OUTPUT_FILES = new Set([
38
+ ".cursor/rules/speclock.mdc",
39
+ ".windsurf/rules/speclock.md",
40
+ ]);
41
+
42
+ // Header markers that indicate a file was auto-generated by SpecLock sync.
43
+ // If ANY of these appear in the first 8 lines, skip the file.
44
+ const SPECLOCK_SYNC_MARKERS = [
45
+ "Auto-synced from SpecLock",
46
+ "Auto-synced by SpecLock",
47
+ "Auto-synced.",
48
+ "(SpecLock)",
49
+ "# SpecLock Constraints",
50
+ "# Generated:",
51
+ "Do not edit manually — run `speclock sync`",
52
+ "speclock sync --format",
53
+ "speclock_session_briefing",
54
+ ];
55
+
56
+ /**
57
+ * Discover all AI rule files in the project.
58
+ */
59
+ export function discoverRuleFiles(root) {
60
+ const found = [];
61
+ for (const entry of RULE_FILES) {
62
+ // Skip known SpecLock output files
63
+ if (SPECLOCK_OUTPUT_FILES.has(entry.file)) continue;
64
+
65
+ const fullPath = path.join(root, entry.file);
66
+ if (fs.existsSync(fullPath)) {
67
+ const content = fs.readFileSync(fullPath, "utf-8").trim();
68
+ if (content.length === 0) continue;
69
+
70
+ // Skip files that were auto-generated by SpecLock sync
71
+ if (isSpeclockGenerated(content)) continue;
72
+
73
+ found.push({
74
+ file: entry.file,
75
+ tool: entry.tool,
76
+ path: fullPath,
77
+ content,
78
+ size: content.length,
79
+ lines: content.split("\n").length,
80
+ });
81
+ }
82
+ }
83
+ return found;
84
+ }
85
+
86
+ /**
87
+ * Check if file content was auto-generated by SpecLock sync.
88
+ * Looks at the first 5 lines for sync markers.
89
+ */
90
+ function isSpeclockGenerated(content) {
91
+ const header = content.split("\n").slice(0, 8).join("\n");
92
+ return SPECLOCK_SYNC_MARKERS.some((marker) => header.includes(marker));
93
+ }
94
+
95
+ // --- Heuristic constraint extraction (no API key needed) ---
96
+
97
+ // Patterns that signal a constraint/rule
98
+ const CONSTRAINT_PATTERNS = [
99
+ // Strong imperative (NEVER, ALWAYS, MUST, DO NOT)
100
+ /^[-*•]\s*(NEVER|ALWAYS|MUST|DO NOT|DON'T|DONT|SHALL NOT|REQUIRED|MANDATORY|CRITICAL|IMPORTANT)\b/i,
101
+ /^(NEVER|ALWAYS|MUST|DO NOT|DON'T|DONT|SHALL NOT)\b/i,
102
+ // "Do not..." / "Don't..." at line start
103
+ /^[-*•]\s*(Do not|Don't|Dont|Never|Always|Must)\b/,
104
+ /^(Do not|Don't|Dont)\b/,
105
+ // Emphasis markers suggesting importance
106
+ /^\*\*(NEVER|ALWAYS|MUST|DO NOT|DON'T|IMPORTANT|CRITICAL|REQUIRED)\*\*/i,
107
+ // "X is required" / "X is mandatory" / "X is non-negotiable"
108
+ /\b(is required|is mandatory|is non-negotiable|is critical|is forbidden|is prohibited)\b/i,
109
+ // "Keep X" / "Preserve X" / "Protect X"
110
+ /^[-*•]\s*(Keep|Preserve|Protect|Maintain|Ensure|Enforce)\b/,
111
+ // Negative imperatives
112
+ /^[-*•]\s*(Avoid|Prevent|Prohibit|Forbid|Restrict|Disallow)\b/i,
113
+ // "should never" / "must never" / "must always"
114
+ /\b(should never|must never|must always|should always|must not|should not|cannot|can not)\b/i,
115
+ ];
116
+
117
+ // Patterns that signal a decision/choice
118
+ const DECISION_PATTERNS = [
119
+ /^[-*•]\s*(Use|Using|Tech stack|Stack|Framework|We use|Built with|Powered by)\b/i,
120
+ /\b(tech stack|architecture|we chose|we decided|we use|built with)\b/i,
121
+ ];
122
+
123
+ // Lines to skip (headers, empty, comments, boilerplate)
124
+ const SKIP_PATTERNS = [
125
+ /^#+\s/, // Markdown headers
126
+ /^---+$/, // Horizontal rules
127
+ /^\s*$/, // Empty lines
128
+ /^```/, // Code fences
129
+ /^<!--/, // HTML comments
130
+ /^>\s/, // Blockquotes (context, not rules)
131
+ /^Auto-synced by SpecLock/, // Our own output
132
+ /^Powered by \[SpecLock\]/, // Our own footer
133
+ /^#\s*SpecLock/, // SpecLock headers
134
+ ];
135
+
136
+ /**
137
+ * Extract constraints from raw text using heuristic pattern matching.
138
+ * No API key required — works offline, instantly.
139
+ */
140
+ export function extractConstraints(content, sourceFile) {
141
+ const lines = content.split("\n");
142
+ const locks = [];
143
+ const decisions = [];
144
+ const seen = new Set();
145
+
146
+ for (let i = 0; i < lines.length; i++) {
147
+ const raw = lines[i];
148
+ const trimmed = raw.trim();
149
+
150
+ // Skip non-content lines
151
+ if (SKIP_PATTERNS.some((p) => p.test(trimmed))) continue;
152
+
153
+ // Clean up: remove leading bullet/dash, markdown bold
154
+ const cleaned = trimmed
155
+ .replace(/^[-*•]\s*/, "")
156
+ .replace(/\*\*/g, "")
157
+ .trim();
158
+
159
+ if (cleaned.length < 10 || cleaned.length > 300) continue;
160
+
161
+ // Deduplicate
162
+ const key = cleaned.toLowerCase().replace(/\s+/g, " ");
163
+ if (seen.has(key)) continue;
164
+
165
+ // Check for constraint patterns
166
+ if (CONSTRAINT_PATTERNS.some((p) => p.test(trimmed) || p.test(cleaned))) {
167
+ seen.add(key);
168
+ locks.push({
169
+ text: cleaned,
170
+ tags: [sourceFile.replace(/[/.]/g, "_").replace(/^_+|_+$/g, "")],
171
+ source: "guardian",
172
+ line: i + 1,
173
+ });
174
+ continue;
175
+ }
176
+
177
+ // Check for decision patterns
178
+ if (DECISION_PATTERNS.some((p) => p.test(trimmed) || p.test(cleaned))) {
179
+ seen.add(key);
180
+ decisions.push({
181
+ text: cleaned,
182
+ tags: [sourceFile.replace(/[/.]/g, "_").replace(/^_+|_+$/g, "")],
183
+ line: i + 1,
184
+ });
185
+ }
186
+ }
187
+
188
+ return { locks, decisions };
189
+ }
190
+
191
+ /**
192
+ * Run the full Guardian protect flow:
193
+ * 1. Init SpecLock if needed
194
+ * 2. Discover rule files
195
+ * 3. Extract constraints from each
196
+ * 4. Add as locks (skip duplicates with existing)
197
+ * 5. Install pre-commit hook
198
+ * 6. Sync rules back to all formats
199
+ * 7. Generate context
200
+ *
201
+ * Returns a report object.
202
+ */
203
+ export function protect(root, options = {}) {
204
+ const report = {
205
+ discovered: [],
206
+ extracted: { locks: 0, decisions: 0 },
207
+ added: { locks: 0, decisions: 0, skipped: 0 },
208
+ hookInstalled: false,
209
+ hookStatus: "",
210
+ synced: [],
211
+ errors: [],
212
+ };
213
+
214
+ // 1. Init
215
+ const brain = ensureInit(root);
216
+
217
+ // 2. Discover
218
+ const ruleFiles = discoverRuleFiles(root);
219
+ report.discovered = ruleFiles.map((f) => ({
220
+ file: f.file,
221
+ tool: f.tool,
222
+ lines: f.lines,
223
+ size: f.size,
224
+ }));
225
+
226
+ if (ruleFiles.length === 0) {
227
+ report.errors.push(
228
+ "No AI rule files found (.cursorrules, CLAUDE.md, AGENTS.md, etc). " +
229
+ "Create one first, or use 'speclock setup' to start from scratch."
230
+ );
231
+ return report;
232
+ }
233
+
234
+ // 3. Extract constraints from each file
235
+ const allLocks = [];
236
+ const allDecisions = [];
237
+
238
+ for (const rf of ruleFiles) {
239
+ const result = extractConstraints(rf.content, rf.file);
240
+ allLocks.push(...result.locks);
241
+ allDecisions.push(...result.decisions);
242
+ }
243
+
244
+ report.extracted.locks = allLocks.length;
245
+ report.extracted.decisions = allDecisions.length;
246
+
247
+ // 4. Add locks (skip duplicates against existing brain locks)
248
+ const existingTexts = new Set(
249
+ (brain.specLock?.items || [])
250
+ .filter((l) => l.active !== false)
251
+ .map((l) => l.text.toLowerCase().replace(/\s+/g, " "))
252
+ );
253
+
254
+ for (const lock of allLocks) {
255
+ const normalized = lock.text.toLowerCase().replace(/\s+/g, " ");
256
+ if (existingTexts.has(normalized)) {
257
+ report.added.skipped++;
258
+ continue;
259
+ }
260
+ existingTexts.add(normalized);
261
+ addLock(root, lock.text, lock.tags, lock.source || "guardian");
262
+ report.added.locks++;
263
+ }
264
+
265
+ for (const dec of allDecisions) {
266
+ const normalized = dec.text.toLowerCase().replace(/\s+/g, " ");
267
+ if (existingTexts.has(normalized)) {
268
+ report.added.skipped++;
269
+ continue;
270
+ }
271
+ existingTexts.add(normalized);
272
+ addDecision(root, dec.text, dec.tags, "guardian");
273
+ report.added.decisions++;
274
+ }
275
+
276
+ // 5. Install pre-commit hook
277
+ if (!options.skipHook) {
278
+ if (isHookInstalled(root)) {
279
+ report.hookInstalled = true;
280
+ report.hookStatus = "already installed";
281
+ } else {
282
+ const hookResult = installHook(root);
283
+ report.hookInstalled = hookResult.success;
284
+ report.hookStatus = hookResult.success
285
+ ? "installed"
286
+ : hookResult.error || "failed";
287
+ }
288
+ } else {
289
+ report.hookStatus = "skipped (--no-hook)";
290
+ }
291
+
292
+ // 6. Sync rules to formats that WEREN'T source files (don't overwrite user's originals)
293
+ if (!options.skipSync) {
294
+ const sourceFiles = new Set(ruleFiles.map((f) => f.file));
295
+ try {
296
+ const syncResult = syncRules(root, { format: "all", excludeFiles: sourceFiles });
297
+ report.synced = (syncResult.synced || []).map((s) => s.file || s);
298
+ } catch (e) {
299
+ report.errors.push(`Sync failed: ${e.message}`);
300
+ }
301
+ }
302
+
303
+ // 7. Generate context
304
+ try {
305
+ generateContext(root);
306
+ } catch (_) {
307
+ // Non-critical
308
+ }
309
+
310
+ return report;
311
+ }
312
+
313
+ /**
314
+ * Format protect report for CLI output.
315
+ */
316
+ export function formatProtectReport(report) {
317
+ const lines = [];
318
+
319
+ lines.push("");
320
+ lines.push(" SpecLock Protect — Guardian Mode");
321
+ lines.push(" " + "=".repeat(50));
322
+ lines.push("");
323
+
324
+ // Discovered files
325
+ if (report.discovered.length > 0) {
326
+ lines.push(" Rule files found:");
327
+ for (const f of report.discovered) {
328
+ lines.push(` [+] ${f.file} (${f.tool}, ${f.lines} lines)`);
329
+ }
330
+ } else {
331
+ lines.push(" [!] No rule files found.");
332
+ }
333
+ lines.push("");
334
+
335
+ // Extracted
336
+ lines.push(` Extracted: ${report.extracted.locks} constraints, ${report.extracted.decisions} decisions`);
337
+
338
+ // Added
339
+ if (report.added.locks > 0 || report.added.decisions > 0) {
340
+ lines.push(` Added: ${report.added.locks} new locks, ${report.added.decisions} new decisions`);
341
+ }
342
+ if (report.added.skipped > 0) {
343
+ lines.push(` Skipped: ${report.added.skipped} (already existed)`);
344
+ }
345
+ lines.push("");
346
+
347
+ // Hook
348
+ if (report.hookStatus === "installed") {
349
+ lines.push(" Pre-commit hook: INSTALLED");
350
+ lines.push(" Every commit will now be checked against your constraints.");
351
+ } else if (report.hookStatus === "already installed") {
352
+ lines.push(" Pre-commit hook: already active");
353
+ } else {
354
+ lines.push(` Pre-commit hook: ${report.hookStatus}`);
355
+ }
356
+ lines.push("");
357
+
358
+ // Sync
359
+ if (report.synced.length > 0) {
360
+ lines.push(" Rules synced to:");
361
+ for (const s of report.synced) {
362
+ lines.push(` [+] ${s}`);
363
+ }
364
+ lines.push("");
365
+ }
366
+
367
+ // Errors
368
+ if (report.errors.length > 0) {
369
+ for (const e of report.errors) {
370
+ lines.push(` [!] ${e}`);
371
+ }
372
+ lines.push("");
373
+ }
374
+
375
+ // Final message
376
+ const total = report.added.locks + report.added.skipped;
377
+ if (total > 0) {
378
+ lines.push(" Your rules are now ENFORCED, not just suggested.");
379
+ lines.push(" AI agents that violate constraints will be blocked.");
380
+ }
381
+ lines.push("");
382
+
383
+ return lines.join("\n");
384
+ }
@@ -451,7 +451,11 @@ export function syncRules(root, options = {}) {
451
451
  const synced = [];
452
452
  const errors = [];
453
453
 
454
+ // Skip formats whose output files are in the exclude list (used by guardian to avoid overwriting source files)
455
+ const excludeFiles = options.excludeFiles instanceof Set ? options.excludeFiles : new Set();
456
+
454
457
  for (const [key, fmt] of Object.entries(formats)) {
458
+ if (excludeFiles.has(fmt.file)) continue;
455
459
  try {
456
460
  const content = fmt.generate(brain);
457
461
  const filePath = path.join(root, fmt.file);
@@ -787,6 +787,17 @@ export const CONCEPT_MAP = {
787
787
  "location data": ["subscriber data", "tracking data", "geolocation",
788
788
  "user location", "pii"],
789
789
 
790
+ // Programming languages (alternatives = language switch conflict)
791
+ "typescript": ["programming language", "typed language", "javascript",
792
+ "language", "ts"],
793
+ "javascript": ["programming language", "scripting language", "typescript",
794
+ "language", "js"],
795
+ "python": ["programming language", "scripting language", "language"],
796
+ "golang": ["programming language", "language", "go"],
797
+ "rust": ["programming language", "systems language", "language"],
798
+ "java": ["programming language", "language", "kotlin"],
799
+ "kotlin": ["programming language", "language", "java"],
800
+
790
801
  // Frontend frameworks (alternatives = change framework conflict)
791
802
  "react": ["frontend framework", "ui framework", "frontend", "ui",
792
803
  "vue", "angular", "svelte", "sveltekit", "next.js", "nextjs"],
@@ -1433,7 +1444,9 @@ function isProhibitiveLock(lockText) {
1433
1444
  || /\bno\s+\w/i.test(lockText)
1434
1445
  // Normalized lock patterns from lock-author.js rewriting
1435
1446
  || /\bis\s+frozen\b/i.test(lockText)
1436
- || /\bmust\s+(remain|be\s+preserved|stay|always)\b/i.test(lockText);
1447
+ || /\bmust\s+(remain|be\s+preserved|stay|always)\b/i.test(lockText)
1448
+ // "ALWAYS use X" is a preservation mandate — removing X violates it
1449
+ || /^\s*always\b/i.test(lockText);
1437
1450
  }
1438
1451
 
1439
1452
  // ===================================================================
@@ -2227,7 +2240,7 @@ export function scoreConflict({ actionText, lockText }) {
2227
2240
  // "Test that Stripe is working" is COMPLIANT with "must always use Stripe"
2228
2241
  // "Debug the Stripe webhook" is COMPLIANT — it's verifying the preserved system
2229
2242
  {
2230
- const lockIsPreservation = /must remain|must be preserved|must always|at all times|must stay/i.test(lockText);
2243
+ const lockIsPreservation = /must remain|must be preserved|must always|at all times|must stay|^\s*always\b/im.test(lockText);
2231
2244
 
2232
2245
  if (!intentAligned && lockIsPreservation) {
2233
2246
  const SAFE_FOR_PRESERVATION = new Set([
@@ -2339,7 +2352,7 @@ export function scoreConflict({ actionText, lockText }) {
2339
2352
  // But: "Update payment to use Razorpay" vs "Stripe lock" → introducing competitor → NOT safe
2340
2353
  // But: "Add Stripe key to frontend" → "add" not in WORKING_WITH_VERBS → NOT safe
2341
2354
  if (!intentAligned && actionPrimaryVerb) {
2342
- const lockIsPreservationOrFreeze = /must remain|must be preserved|must always|at all times|must stay|must never|must not|should never|do not replace|do not remove|do not switch|don't replace|don't remove|don't switch|don't|do not|never|uses .+ library/i.test(lockText);
2355
+ const lockIsPreservationOrFreeze = /must remain|must be preserved|must always|at all times|must stay|must never|must not|should never|do not replace|do not remove|do not switch|don't replace|don't remove|don't switch|don't|do not|never|uses .+ library|^\s*always\b/im.test(lockText);
2343
2356
  if (lockIsPreservationOrFreeze) {
2344
2357
  // Extract specific brand/tech names from the lock text
2345
2358
  const lockWords = lockText.toLowerCase().split(/\s+/).map(w => w.replace(/[^a-z0-9]/g, "")).filter(w => w.length > 2);
@@ -2356,6 +2369,9 @@ export function scoreConflict({ actionText, lockText }) {
2356
2369
  "baileys", "twilio", "whatsapp",
2357
2370
  "auth0", "okta", "cognito", "keycloak",
2358
2371
  "react", "vue", "angular", "svelte", "nextjs", "nuxt",
2372
+ "typescript", "javascript", "python", "golang", "rust", "java", "kotlin",
2373
+ "tailwind", "bootstrap", "prisma", "drizzle", "sequelize",
2374
+ "express", "fastapi", "django", "flask", "rails",
2359
2375
  "docker", "kubernetes", "terraform", "ansible",
2360
2376
  "aws", "gcp", "azure", "vercel", "netlify", "railway", "heroku",
2361
2377
  ]);
@@ -2406,7 +2422,7 @@ export function scoreConflict({ actionText, lockText }) {
2406
2422
  "build", "add", "create", "implement", "make", "design",
2407
2423
  "develop", "introduce",
2408
2424
  ]);
2409
- const lockIsPreservation = /must remain|must be preserved|must always|at all times|must stay/i.test(lockText);
2425
+ const lockIsPreservation = /must remain|must be preserved|must always|at all times|must stay|^\s*always\b/im.test(lockText);
2410
2426
  if (lockIsPreservation) {
2411
2427
  if (ENHANCEMENT_VERBS.has(actionPrimaryVerb)) {
2412
2428
  // Enhancement verbs always align with preservation locks
@@ -257,7 +257,7 @@ export async function flushToRemote(root) {
257
257
  // Build anonymized payload
258
258
  const payload = {
259
259
  instanceId: summary.instanceId,
260
- version: "4.5.7",
260
+ version: "5.5.1",
261
261
  totalCalls: summary.totalCalls,
262
262
  avgResponseMs: summary.avgResponseMs,
263
263
  conflicts: summary.conflicts,
@@ -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.4.1 &mdash; AI Constraint Engine</div>
92
+ <div class="meta">v5.5.1 &mdash; 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.4.1 &mdash; Developed by Sandeep Roy &mdash; <a href="https://github.com/sgroy10/speclock" style="color:var(--accent)">GitHub</a>
185
+ SpecLock v5.5.1 &mdash; Developed by Sandeep Roy &mdash; <a href="https://github.com/sgroy10/speclock" style="color:var(--accent)">GitHub</a>
186
186
  </div>
187
187
 
188
188
  <script>
@@ -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.4.1";
116
+ const VERSION = "5.5.1";
117
117
  const AUTHOR = "Sandeep Roy";
118
118
  const START_TIME = Date.now();
119
119
 
@@ -887,7 +887,7 @@ app.get("/health", (req, res) => {
887
887
  status: "healthy",
888
888
  version: VERSION,
889
889
  uptime: Math.floor((Date.now() - START_TIME) / 1000),
890
- tools: 42,
890
+ tools: 51,
891
891
  auditChain: auditStatus,
892
892
  authEnabled: isAuthEnabled(PROJECT_ROOT),
893
893
  rateLimit: { limit: RATE_LIMIT, windowMs: RATE_WINDOW_MS },
@@ -901,8 +901,8 @@ app.get("/", (req, res) => {
901
901
  name: "speclock",
902
902
  version: VERSION,
903
903
  author: AUTHOR,
904
- description: "AI Constraint Engine Universal Rules Sync + AI Patch Firewall. Syncs constraints to Cursor, Claude Code, Copilot, Windsurf, Gemini, Aider, AGENTS.md. Patch Gateway (ALLOW/WARN/BLOCK verdicts), diff-native review (interface breaks, protected symbols, dependency drift, schema changes, API impact). Spec Compiler (NL→constraints), Code Graph (blast radius, lock-to-file mapping), Typed constraints, REST API v2, Python SDK + ROS2 integration. Policy-as-Code, RBAC, AES-256-GCM encryption, HMAC audit chain, SOC 2/HIPAA compliance. 49 MCP tools. 929 tests, 100% accuracy.",
905
- tools: 49,
904
+ description: "Stop AI from breaking code you told it not to touch. Enforces .cursorrules, CLAUDE.md, and AGENTS.md not just suggests. Zero-config Guardian Mode, Universal Rules Sync, AI Patch Firewall, Spec Compiler, Code Graph, Drift Score, HMAC audit chain, SOC 2/HIPAA compliance. 51 MCP tools. 976 tests.",
905
+ tools: 51,
906
906
  mcp_endpoint: "/mcp",
907
907
  health_endpoint: "/health",
908
908
  npm: "https://www.npmjs.com/package/speclock",
package/src/mcp/server.js CHANGED
@@ -70,6 +70,7 @@ import { getReplay, listSessions, formatReplay } from "../core/replay.js";
70
70
  import { computeDriftScore, formatDriftScore } from "../core/drift-score.js";
71
71
  import { computeCoverage, formatCoverage } from "../core/coverage.js";
72
72
  import { analyzeLockStrength, formatStrength } from "../core/strengthen.js";
73
+ import { discoverRuleFiles, protect, formatProtectReport } from "../core/guardian.js";
73
74
  import {
74
75
  readBrain,
75
76
  readEvents,
@@ -125,7 +126,7 @@ const PROJECT_ROOT =
125
126
  args.project || process.env.SPECLOCK_PROJECT_ROOT || process.cwd();
126
127
 
127
128
  // --- MCP Server ---
128
- const VERSION = "5.4.1";
129
+ const VERSION = "5.5.1";
129
130
  const AUTHOR = "Sandeep Roy";
130
131
 
131
132
  const server = new McpServer(
@@ -2065,6 +2066,46 @@ server.tool(
2065
2066
  }
2066
2067
  );
2067
2068
 
2069
+ // Tool 43: speclock_protect
2070
+ server.tool(
2071
+ "speclock_protect",
2072
+ "Zero-config Guardian Mode — Reads existing AI rule files (.cursorrules, CLAUDE.md, AGENTS.md, copilot-instructions.md, etc.), auto-extracts enforceable constraints, installs pre-commit hook, and syncs rules to all AI tools. One command, no flags. Makes your AI rules unbreakable.",
2073
+ {
2074
+ skipHook: { type: "boolean", description: "Skip pre-commit hook installation", default: false },
2075
+ skipSync: { type: "boolean", description: "Skip syncing rules to all formats", default: false },
2076
+ },
2077
+ async ({ skipHook, skipSync }) => {
2078
+ const report = protect(PROJECT_ROOT, { skipHook, skipSync });
2079
+ const formatted = formatProtectReport(report);
2080
+ return { content: [{ type: "text", text: formatted }] };
2081
+ }
2082
+ );
2083
+
2084
+ // Tool 44: speclock_discover_rules
2085
+ server.tool(
2086
+ "speclock_discover_rules",
2087
+ "Discover all AI rule files in the project (.cursorrules, CLAUDE.md, AGENTS.md, copilot-instructions.md, .windsurfrules, GEMINI.md, .aider.conf.yml). Shows which tools have rules configured and their sizes. Use this to see what rules exist before running speclock_protect.",
2088
+ {},
2089
+ async () => {
2090
+ const files = discoverRuleFiles(PROJECT_ROOT);
2091
+ if (files.length === 0) {
2092
+ return { content: [{ type: "text", text: "No AI rule files found in this project. Create a .cursorrules, CLAUDE.md, or AGENTS.md file first." }] };
2093
+ }
2094
+ const lines = [
2095
+ `## AI Rule Files Found`,
2096
+ ``,
2097
+ `| File | Tool | Lines | Size |`,
2098
+ `|------|------|-------|------|`,
2099
+ ];
2100
+ for (const f of files) {
2101
+ lines.push(`| \`${f.file}\` | ${f.tool} | ${f.lines} | ${f.size} bytes |`);
2102
+ }
2103
+ lines.push(``);
2104
+ lines.push(`Run \`speclock_protect\` to extract constraints and make them enforceable.`);
2105
+ return { content: [{ type: "text", text: lines.join("\n") }] };
2106
+ }
2107
+ );
2108
+
2068
2109
  // --- Smithery sandbox export ---
2069
2110
  export default function createSandboxServer() {
2070
2111
  return server;