speclock 5.3.1 → 5.4.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,7 @@
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 46 tools" /></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>
12
12
  </p>
13
13
 
14
14
  <p align="center">
@@ -205,6 +205,73 @@ One command. Instant protection. `npx speclock setup --template safe-defaults`
205
205
 
206
206
  ---
207
207
 
208
+ ## Drift Score (v5.4)
209
+
210
+ How much has your AI-built project drifted from your original intent? Only SpecLock can answer this — because only SpecLock knows what was *intended* vs what was *done*.
211
+
212
+ ```bash
213
+ $ speclock drift
214
+
215
+ Drift Score: 23/100 (B) — minor drift
216
+ Trend: improving | Period: 30 days | Active locks: 8
217
+
218
+ Signal Breakdown:
219
+ Violations: 6/30 (4 violations in 12 checks)
220
+ Overrides: 5/20 (1 override)
221
+ Reverts: 3/15 (1 revert detected)
222
+ Lock churn: 0/15 (0 removed, 3 added)
223
+ Goal stability: 0/10 (1 goal change)
224
+ Session gaps: 9/10 (3/5 unsummarized)
225
+
226
+ README badge: ![Drift Score](https://img.shields.io/badge/drift_score-23%2F100-brightgreen.svg)
227
+ ```
228
+
229
+ Put the badge in your README. Show the world your AI respects your architecture.
230
+
231
+ ---
232
+
233
+ ## Lock Coverage Audit (v5.4)
234
+
235
+ SpecLock scans your codebase and tells you what's **unprotected**:
236
+
237
+ ```bash
238
+ $ speclock coverage
239
+
240
+ Lock Coverage: 60% (B) — partially protected
241
+
242
+ [COVERED] CRITICAL authentication 2 file(s)
243
+ [EXPOSED] CRITICAL payments 1 file(s)
244
+ [COVERED] CRITICAL secrets 0 file(s)
245
+ [COVERED] HIGH api-routes 2 file(s)
246
+
247
+ Suggested Locks (ready to apply):
248
+ 1. [CRITICAL] payments (1 file at risk)
249
+ speclock lock "Never modify payment processing or billing without permission"
250
+ ```
251
+
252
+ Like a security scanner, but for AI constraint gaps. Solo founders building fast don't know what they haven't protected — SpecLock tells them.
253
+
254
+ ---
255
+
256
+ ## Lock Strengthener (v5.4)
257
+
258
+ Your locks might be too vague. SpecLock grades each one and suggests improvements:
259
+
260
+ ```bash
261
+ $ speclock strengthen
262
+
263
+ Lock Strength: 72/100 (B) — 3 strong, 1 weak
264
+
265
+ [WEAK ] 45/100 (D) "don't touch auth"
266
+ Issue: Too vague — short locks miss edge cases
267
+ Issue: No specific scope
268
+ Suggested: "Never modify, refactor, or delete auth..."
269
+
270
+ [STRONG] 90/100 (A) "Never expose API keys in client-side code, logs, or error messages"
271
+ ```
272
+
273
+ ---
274
+
208
275
  ## Semantic Engine
209
276
 
210
277
  Not keyword matching — **real semantic analysis** with Gemini Flash hybrid for universal domain coverage. Scored **100/100** on Claude's independent adversarial test battery (7 suites, including false positives, question framing, patch gateway, and diff analysis).
@@ -514,7 +581,7 @@ POST /api/v2/graph/build
514
581
 
515
582
  ---
516
583
 
517
- ## 46 MCP Tools
584
+ ## 49 MCP Tools
518
585
 
519
586
  <details>
520
587
  <summary><b>Memory</b> — goal, locks, decisions, notes, deploy facts</summary>
@@ -631,6 +698,9 @@ POST /api/v2/graph/build
631
698
  | `speclock_list_sync_formats` | List all available sync formats |
632
699
  | `speclock_replay` | Replay a session's activity — what AI tried and what was caught |
633
700
  | `speclock_list_sessions` | List available sessions for replay |
701
+ | `speclock_drift_score` | 0-100 project integrity metric — how much AI deviated from intent |
702
+ | `speclock_coverage` | Lock Coverage Audit — find unprotected code areas |
703
+ | `speclock_strengthen` | Grade locks and suggest stronger versions |
634
704
 
635
705
  </details>
636
706
 
@@ -679,6 +749,12 @@ speclock replay # Replay last session
679
749
  speclock replay --list # List sessions
680
750
  speclock replay --session <id> # Replay specific session
681
751
 
752
+ # Project Health
753
+ speclock drift # Drift Score (0-100)
754
+ speclock drift --days 7 # Last 7 days only
755
+ speclock coverage # Lock Coverage Audit
756
+ speclock strengthen # Grade and improve locks
757
+
682
758
  # Auth
683
759
  speclock auth create-key --role developer
684
760
  speclock auth rotate-key <keyId>
@@ -721,7 +797,7 @@ The AI opens the file and sees:
721
797
  │ AI Tool (Claude Code, Cursor, Bolt.new...) │
722
798
  └────────────┬──────────────────┬──────────────────┘
723
799
  │ │
724
- MCP Protocol (46 tools) npm File-Based
800
+ MCP Protocol (49 tools) npm File-Based
725
801
  │ (SPECLOCK.md + CLI)
726
802
  │ │
727
803
  ┌────────────▼──────────────────▼──────────────────┐
@@ -791,7 +867,7 @@ The AI opens the file and sees:
791
867
  | PII/Export Detection | 8 | 100% | SSN, email export, data access violations |
792
868
  | **Total** | **929** | **100%** | **18 suites, 15+ domains** |
793
869
 
794
- **External validation:** Claude's independent 7-suite adversarial test battery — **100/100 (100%)** on v5.3.0. Zero false positives. Zero missed violations. 15.7ms per check.
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.
795
871
 
796
872
  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.
797
873
 
@@ -829,11 +905,11 @@ Issues and PRs welcome on [GitHub](https://github.com/sgroy10/speclock).
829
905
 
830
906
  **SpecLock** is created and maintained by **[Sandeep Roy](https://github.com/sgroy10)**.
831
907
 
832
- Sandeep Roy is the sole developer of SpecLock — the AI Constraint Engine that enforces project rules across AI coding sessions. All 46 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.
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.
833
909
 
834
910
  - GitHub: [@sgroy10](https://github.com/sgroy10)
835
911
  - npm: [speclock](https://www.npmjs.com/package/speclock)
836
912
 
837
913
  ---
838
914
 
839
- <p align="center"><i>SpecLock v5.3.0 — Developed by Sandeep Roy — 929 tests, 100% pass rate, 46 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>
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>
package/package.json CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  "name": "speclock",
4
4
 
5
- "version": "5.3.1",
5
+ "version": "5.4.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. 46 MCP tools, Gemini LLM hybrid, HMAC audit chain, RBAC, encryption, SOC 2/HIPAA compliance. Developed by Sandeep Roy.",
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.",
8
8
 
9
9
  "type": "module",
10
10
 
package/src/cli/index.js CHANGED
@@ -64,6 +64,9 @@ import {
64
64
  } from "../core/sso.js";
65
65
  import { syncRules, getSyncFormats } from "../core/rules-sync.js";
66
66
  import { getReplay, listSessions, formatReplay } from "../core/replay.js";
67
+ import { computeDriftScore, formatDriftScore } from "../core/drift-score.js";
68
+ import { computeCoverage, formatCoverage } from "../core/coverage.js";
69
+ import { analyzeLockStrength, formatStrength } from "../core/strengthen.js";
67
70
 
68
71
  // --- Argument parsing ---
69
72
 
@@ -119,7 +122,7 @@ function refreshContext(root) {
119
122
 
120
123
  function printHelp() {
121
124
  console.log(`
122
- SpecLock v5.3.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)
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)
123
126
  Developed by Sandeep Roy (github.com/sgroy10)
124
127
 
125
128
  Usage: speclock <command> [options]
@@ -157,6 +160,9 @@ Commands:
157
160
  sync --preview <format> Preview without writing files
158
161
  replay [--session <id>] Replay session activity — what AI tried & what was caught
159
162
  replay --list List available sessions for replay
163
+ drift [--days 30] Drift Score — how much has AI deviated from intent (0-100)
164
+ coverage Lock Coverage Audit — find unprotected code areas
165
+ strengthen Lock Strengthener — grade locks and suggest improvements
160
166
  watch Start file watcher (live dashboard)
161
167
  serve [--project <path>] Start MCP stdio server
162
168
  status Show project brain summary
@@ -1113,6 +1119,38 @@ Tip: Run "speclock sync --all" to push constraints to Cursor, Claude, Copilot, W
1113
1119
  process.exit(1);
1114
1120
  }
1115
1121
 
1122
+ // --- DRIFT (new: drift score) ---
1123
+ if (cmd === "drift") {
1124
+ const flags = parseFlags(args);
1125
+ const days = flags.days ? parseInt(flags.days, 10) : 30;
1126
+ const result = computeDriftScore(root, { days });
1127
+
1128
+ console.log(`\nSpecLock Drift Score`);
1129
+ console.log("=".repeat(60));
1130
+ console.log(formatDriftScore(result));
1131
+ return;
1132
+ }
1133
+
1134
+ // --- COVERAGE (new: lock coverage audit) ---
1135
+ if (cmd === "coverage") {
1136
+ const result = computeCoverage(root);
1137
+
1138
+ console.log(`\nSpecLock Lock Coverage Audit`);
1139
+ console.log("=".repeat(60));
1140
+ console.log(formatCoverage(result));
1141
+ return;
1142
+ }
1143
+
1144
+ // --- STRENGTHEN (new: lock strengthener) ---
1145
+ if (cmd === "strengthen") {
1146
+ const result = analyzeLockStrength(root);
1147
+
1148
+ console.log(`\nSpecLock Lock Strengthener`);
1149
+ console.log("=".repeat(60));
1150
+ console.log(formatStrength(result));
1151
+ return;
1152
+ }
1153
+
1116
1154
  // --- REPLAY (new: incident replay) ---
1117
1155
  if (cmd === "replay") {
1118
1156
  const flags = parseFlags(args);
@@ -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.3.1";
12
+ const VERSION = "5.4.1";
13
13
 
14
14
  // PHI-related keywords for HIPAA filtering
15
15
  const PHI_KEYWORDS = [
@@ -0,0 +1,274 @@
1
+ /**
2
+ * SpecLock Lock Coverage Audit — Find Unprotected Code
3
+ * Scans the codebase for high-risk patterns and identifies files/areas
4
+ * that have no lock covering them. Auto-suggests missing locks.
5
+ *
6
+ * Like a security scanner, but for AI constraint gaps.
7
+ *
8
+ * Developed by Sandeep Roy (https://github.com/sgroy10)
9
+ */
10
+
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import { readBrain } from "./storage.js";
14
+ import { ensureInit } from "./memory.js";
15
+
16
+ // --- High-risk patterns to detect ---
17
+
18
+ const RISK_PATTERNS = [
19
+ {
20
+ category: "authentication",
21
+ keywords: ["auth", "login", "signup", "session", "jwt", "token", "oauth", "passport", "credential"],
22
+ filePatterns: ["auth", "login", "signup", "session", "passport", "middleware/auth"],
23
+ severity: "critical",
24
+ suggestedLock: "Never modify authentication or authorization without explicit permission",
25
+ },
26
+ {
27
+ category: "payments",
28
+ keywords: ["payment", "stripe", "billing", "checkout", "subscription", "invoice", "price", "razorpay", "paypal"],
29
+ filePatterns: ["payment", "billing", "checkout", "stripe", "subscription", "invoice"],
30
+ severity: "critical",
31
+ suggestedLock: "Never modify payment processing, billing, or subscription logic without explicit permission",
32
+ },
33
+ {
34
+ category: "database",
35
+ keywords: ["migration", "schema", "model", "prisma", "knex", "sequelize", "typeorm", "drizzle"],
36
+ filePatterns: ["migration", "schema", "model", "prisma", "db", "database", "seed"],
37
+ severity: "high",
38
+ suggestedLock: "Database schema changes must not drop tables or columns — migrations must be additive only",
39
+ },
40
+ {
41
+ category: "secrets",
42
+ keywords: ["env", "secret", "key", "credential", "password", "config"],
43
+ filePatterns: [".env", "config/secret", "credentials"],
44
+ severity: "critical",
45
+ suggestedLock: "Never expose API keys, secrets, or credentials in client-side code, logs, or error messages",
46
+ },
47
+ {
48
+ category: "api-routes",
49
+ keywords: ["route", "endpoint", "api", "controller", "handler"],
50
+ filePatterns: ["routes/", "api/", "controllers/", "handlers/", "app/api/"],
51
+ severity: "high",
52
+ suggestedLock: "Never remove or change existing API endpoints without explicit permission — clients depend on stability",
53
+ },
54
+ {
55
+ category: "security",
56
+ keywords: ["cors", "helmet", "csp", "csrf", "xss", "sanitize", "rate-limit", "rateLimit"],
57
+ filePatterns: ["security", "cors", "helmet", "middleware/rate"],
58
+ severity: "critical",
59
+ suggestedLock: "Security middleware (CORS, CSP, rate limiting) must not be weakened or removed",
60
+ },
61
+ {
62
+ category: "error-handling",
63
+ keywords: ["error", "catch", "exception", "fallback", "boundary"],
64
+ filePatterns: ["error", "boundary", "fallback", "exception"],
65
+ severity: "medium",
66
+ suggestedLock: "Never remove error handling, error boundaries, or fallback logic",
67
+ },
68
+ {
69
+ category: "logging",
70
+ keywords: ["logger", "logging", "log", "monitor", "telemetry", "sentry", "datadog"],
71
+ filePatterns: ["logger", "logging", "monitor", "telemetry"],
72
+ severity: "medium",
73
+ suggestedLock: "Never disable logging, monitoring, or observability — these keep production alive",
74
+ },
75
+ {
76
+ category: "testing",
77
+ keywords: ["test", "spec", "jest", "mocha", "vitest", "cypress", "playwright"],
78
+ filePatterns: ["test", "spec", "__tests__", "e2e", "cypress"],
79
+ severity: "low",
80
+ suggestedLock: "Never delete or skip existing tests — test coverage must not decrease",
81
+ },
82
+ ];
83
+
84
+ // Files/dirs to ignore
85
+ const IGNORE = [
86
+ "node_modules", ".git", ".speclock", "dist", "build", ".next",
87
+ ".cache", "coverage", ".turbo", ".vercel", ".netlify",
88
+ ];
89
+
90
+ /**
91
+ * Scan project and compute lock coverage.
92
+ *
93
+ * @param {string} root - Project root
94
+ * @param {Object} [options]
95
+ * @param {number} [options.maxFiles] - Max files to scan (default: 500)
96
+ * @returns {Object} Coverage analysis
97
+ */
98
+ export function computeCoverage(root, options = {}) {
99
+ const brain = ensureInit(root);
100
+ const maxFiles = options.maxFiles || 500;
101
+ const activeLocks = (brain.specLock?.items || []).filter((l) => l.active !== false);
102
+ const lockTexts = activeLocks.map((l) => (l.text || "").toLowerCase());
103
+
104
+ // Scan for source files
105
+ const files = scanFiles(root, maxFiles);
106
+
107
+ // Analyze each risk category
108
+ const categories = RISK_PATTERNS.map((pattern) => {
109
+ // Find files matching this category
110
+ const matchingFiles = files.filter((f) => {
111
+ const fileLower = f.toLowerCase();
112
+ return pattern.filePatterns.some((fp) => fileLower.includes(fp));
113
+ });
114
+
115
+ // Check if any lock covers this category
116
+ const coveredBy = activeLocks.filter((lock) => {
117
+ const lockLower = (lock.text || "").toLowerCase();
118
+ return pattern.keywords.some((kw) => lockLower.includes(kw));
119
+ });
120
+
121
+ const isCovered = coveredBy.length > 0;
122
+
123
+ return {
124
+ category: pattern.category,
125
+ severity: pattern.severity,
126
+ filesFound: matchingFiles.length,
127
+ files: matchingFiles.slice(0, 5), // show up to 5
128
+ covered: isCovered,
129
+ coveredBy: coveredBy.map((l) => l.text.substring(0, 80)),
130
+ suggestedLock: !isCovered ? pattern.suggestedLock : null,
131
+ };
132
+ });
133
+
134
+ // Only include categories that have files OR are critical
135
+ const relevant = categories.filter(
136
+ (c) => c.filesFound > 0 || c.severity === "critical"
137
+ );
138
+
139
+ const totalCategories = relevant.length;
140
+ const coveredCategories = relevant.filter((c) => c.covered).length;
141
+ const coveragePercent = totalCategories > 0
142
+ ? Math.round((coveredCategories / totalCategories) * 100)
143
+ : 0;
144
+
145
+ // Unprotected = has files but no lock
146
+ const unprotected = relevant.filter((c) => !c.covered && c.filesFound > 0);
147
+ const suggestions = unprotected
148
+ .sort((a, b) => severityRank(a.severity) - severityRank(b.severity))
149
+ .map((c) => ({
150
+ category: c.category,
151
+ severity: c.severity,
152
+ lock: c.suggestedLock,
153
+ filesAtRisk: c.filesFound,
154
+ }));
155
+
156
+ // Grade
157
+ let grade, status;
158
+ if (coveragePercent >= 90) { grade = "A+"; status = "excellent"; }
159
+ else if (coveragePercent >= 75) { grade = "A"; status = "well protected"; }
160
+ else if (coveragePercent >= 60) { grade = "B"; status = "partially protected"; }
161
+ else if (coveragePercent >= 40) { grade = "C"; status = "gaps found"; }
162
+ else if (coveragePercent >= 20) { grade = "D"; status = "mostly unprotected"; }
163
+ else { grade = "F"; status = "no protection"; }
164
+
165
+ return {
166
+ coveragePercent,
167
+ grade,
168
+ status,
169
+ totalFiles: files.length,
170
+ totalCategories,
171
+ coveredCategories,
172
+ categories: relevant,
173
+ unprotected,
174
+ suggestions,
175
+ activeLocks: activeLocks.length,
176
+ badge: `![Lock Coverage](https://img.shields.io/badge/lock_coverage-${coveragePercent}%25-${coveragePercent >= 75 ? "brightgreen" : coveragePercent >= 50 ? "yellow" : "red"}.svg)`,
177
+ };
178
+ }
179
+
180
+ function severityRank(s) {
181
+ if (s === "critical") return 0;
182
+ if (s === "high") return 1;
183
+ if (s === "medium") return 2;
184
+ return 3;
185
+ }
186
+
187
+ /**
188
+ * Scan for source files in the project.
189
+ */
190
+ function scanFiles(root, maxFiles) {
191
+ const files = [];
192
+ const extensions = new Set([
193
+ ".js", ".ts", ".jsx", ".tsx", ".py", ".rb", ".go",
194
+ ".java", ".rs", ".php", ".vue", ".svelte", ".astro",
195
+ ".mjs", ".cjs", ".mts", ".cts",
196
+ ]);
197
+
198
+ function walk(dir, depth) {
199
+ if (depth > 6 || files.length >= maxFiles) return;
200
+
201
+ let entries;
202
+ try {
203
+ entries = fs.readdirSync(dir, { withFileTypes: true });
204
+ } catch {
205
+ return;
206
+ }
207
+
208
+ for (const entry of entries) {
209
+ if (files.length >= maxFiles) break;
210
+ const name = entry.name;
211
+
212
+ if (IGNORE.some((ig) => name === ig || name.startsWith("."))) continue;
213
+
214
+ const fullPath = path.join(dir, name);
215
+ const relPath = path.relative(root, fullPath).replace(/\\/g, "/");
216
+
217
+ if (entry.isDirectory()) {
218
+ walk(fullPath, depth + 1);
219
+ } else if (entry.isFile()) {
220
+ const ext = path.extname(name).toLowerCase();
221
+ if (extensions.has(ext)) {
222
+ files.push(relPath);
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ walk(root, 0);
229
+ return files;
230
+ }
231
+
232
+ /**
233
+ * Format coverage report for CLI output.
234
+ */
235
+ export function formatCoverage(result) {
236
+ const lines = [];
237
+
238
+ lines.push(`Lock Coverage: ${result.coveragePercent}% (${result.grade}) — ${result.status}`);
239
+ lines.push(`Files scanned: ${result.totalFiles} | Categories: ${result.coveredCategories}/${result.totalCategories} covered | Active locks: ${result.activeLocks}`);
240
+ lines.push("");
241
+
242
+ // Category breakdown
243
+ lines.push("Category Breakdown:");
244
+ lines.push(" " + "-".repeat(55));
245
+
246
+ for (const c of result.categories) {
247
+ const icon = c.covered ? "COVERED" : (c.filesFound > 0 ? "EXPOSED" : "N/A");
248
+ const sev = c.severity.toUpperCase().padEnd(8);
249
+ lines.push(` [${icon.padEnd(7)}] ${sev} ${c.category.padEnd(16)} ${c.filesFound} file(s)`);
250
+ if (c.covered && c.coveredBy.length > 0) {
251
+ lines.push(` Lock: "${c.coveredBy[0]}"`);
252
+ }
253
+ }
254
+
255
+ // Suggestions
256
+ if (result.suggestions.length > 0) {
257
+ lines.push("");
258
+ lines.push("Suggested Locks (ready to apply):");
259
+ lines.push(" " + "-".repeat(55));
260
+ for (let i = 0; i < result.suggestions.length; i++) {
261
+ const s = result.suggestions[i];
262
+ lines.push(` ${i + 1}. [${s.severity.toUpperCase()}] ${s.category} (${s.filesAtRisk} file(s) at risk)`);
263
+ lines.push(` speclock lock "${s.lock}"`);
264
+ lines.push("");
265
+ }
266
+ } else {
267
+ lines.push("");
268
+ lines.push("All detected categories are covered by locks.");
269
+ }
270
+
271
+ lines.push(`README badge: ${result.badge}`);
272
+
273
+ return lines.join("\n");
274
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * SpecLock Drift Score — Project Integrity Metric
3
+ * Measures how much a project has drifted from the founder's original intent.
4
+ * 0 = perfect alignment, 100 = complete drift.
5
+ *
6
+ * Signals:
7
+ * - Lock violations (blocked + warned)
8
+ * - Override frequency
9
+ * - Revert detections
10
+ * - Lock churn (locks removed)
11
+ * - Session continuity gaps
12
+ * - Decision stability
13
+ *
14
+ * Only SpecLock can compute this because only SpecLock knows
15
+ * what was INTENDED vs what was DONE.
16
+ *
17
+ * Developed by Sandeep Roy (https://github.com/sgroy10)
18
+ */
19
+
20
+ import { readBrain, readEvents } from "./storage.js";
21
+ import { ensureInit } from "./memory.js";
22
+
23
+ /**
24
+ * Compute drift score for the project.
25
+ *
26
+ * @param {string} root - Project root
27
+ * @param {Object} [options]
28
+ * @param {number} [options.days] - Look back N days (default: 30)
29
+ * @returns {Object} Drift analysis
30
+ */
31
+ export function computeDriftScore(root, options = {}) {
32
+ const brain = ensureInit(root);
33
+ const days = options.days || 30;
34
+ const cutoff = new Date(Date.now() - days * 86400000).toISOString();
35
+
36
+ const allEvents = readEvents(root, {});
37
+ const recentEvents = allEvents.filter((e) => e.at >= cutoff);
38
+ const activeLocks = (brain.specLock?.items || []).filter((l) => l.active !== false);
39
+
40
+ // --- Signal 1: Violation Rate (0-30 points) ---
41
+ // How often did the AI hit constraints?
42
+ // Violations are stored in brain.state.violations by the conflict checker
43
+ const allViolations = brain.state.violations || [];
44
+ const violations = allViolations.filter((v) => v.at >= cutoff);
45
+ // Also count from events for backward compatibility
46
+ const eventViolations = recentEvents.filter(
47
+ (e) => e.type === "conflict_blocked" || e.type === "conflict_warned" ||
48
+ (e.summary && (e.summary.includes("CONFLICT") || e.summary.includes("BLOCK")))
49
+ );
50
+ const totalViolations = Math.max(violations.length, eventViolations.length);
51
+ // Estimate total checks: violations + safe checks (from events)
52
+ const safeChecks = recentEvents.filter(
53
+ (e) => e.type === "conflict_checked"
54
+ ).length;
55
+ const totalChecks = totalViolations + safeChecks || 1;
56
+ const violationRate = (totalViolations / totalChecks) * 100;
57
+ // 0% violations = 0 drift, 50%+ = 30 drift
58
+ const violationScore = Math.min(30, Math.round(violationRate * 0.6));
59
+
60
+ // --- Signal 2: Override Frequency (0-20 points) ---
61
+ // Overrides mean someone bypassed a constraint — that's drift
62
+ const overrides = recentEvents.filter((e) => e.type === "override_applied");
63
+ const overrideScore = Math.min(20, overrides.length * 5);
64
+
65
+ // --- Signal 3: Revert Detections (0-15 points) ---
66
+ // Reverts indicate instability — code going back and forth
67
+ const reverts = recentEvents.filter((e) => e.type === "revert_detected");
68
+ const revertScore = Math.min(15, reverts.length * 3);
69
+
70
+ // --- Signal 4: Lock Churn (0-15 points) ---
71
+ // Locks being removed means constraints are weakening
72
+ const locksRemoved = recentEvents.filter((e) => e.type === "lock_removed");
73
+ const locksAdded = recentEvents.filter((e) => e.type === "lock_added");
74
+ const churnRatio = locksAdded.length > 0
75
+ ? locksRemoved.length / locksAdded.length
76
+ : locksRemoved.length > 0 ? 1 : 0;
77
+ const churnScore = Math.min(15, Math.round(churnRatio * 15));
78
+
79
+ // --- Signal 5: Goal Stability (0-10 points) ---
80
+ // Frequent goal changes indicate lack of direction
81
+ const goalChanges = recentEvents.filter((e) => e.type === "goal_updated");
82
+ const goalScore = Math.min(10, goalChanges.length > 1 ? (goalChanges.length - 1) * 5 : 0);
83
+
84
+ // --- Signal 6: Session Gaps (0-10 points) ---
85
+ // Many short sessions without summaries indicate fragmented work
86
+ const sessions = brain.sessions.history.filter((s) => s.startedAt >= cutoff);
87
+ const unsummarized = sessions.filter(
88
+ (s) => !s.summary || s.summary === "Session auto-closed (new session started)"
89
+ );
90
+ const gapRatio = sessions.length > 0 ? unsummarized.length / sessions.length : 0;
91
+ const gapScore = Math.min(10, Math.round(gapRatio * 10));
92
+
93
+ // --- Total ---
94
+ const totalScore = violationScore + overrideScore + revertScore +
95
+ churnScore + goalScore + gapScore;
96
+ const driftScore = Math.min(100, totalScore);
97
+
98
+ // --- Grade ---
99
+ let grade, status;
100
+ if (driftScore <= 10) { grade = "A+"; status = "excellent"; }
101
+ else if (driftScore <= 20) { grade = "A"; status = "healthy"; }
102
+ else if (driftScore <= 35) { grade = "B"; status = "minor drift"; }
103
+ else if (driftScore <= 50) { grade = "C"; status = "moderate drift"; }
104
+ else if (driftScore <= 70) { grade = "D"; status = "significant drift"; }
105
+ else { grade = "F"; status = "severe drift"; }
106
+
107
+ // --- Per-session drift breakdown ---
108
+ const sessionDrift = sessions.map((s) => {
109
+ const sessionEvents = recentEvents.filter(
110
+ (e) => e.at >= s.startedAt && (s.endedAt ? e.at <= s.endedAt : true)
111
+ );
112
+ // Check both brain.state.violations and events for this session window
113
+ const brainViolationsInSession = allViolations.filter(
114
+ (v) => v.at >= s.startedAt && (s.endedAt ? v.at <= s.endedAt : true)
115
+ );
116
+ const eventViolationsInSession = sessionEvents.filter(
117
+ (e) => e.type === "conflict_blocked" || e.type === "conflict_warned" ||
118
+ (e.summary && e.summary.includes("CONFLICT"))
119
+ );
120
+ const sessionViolations = brainViolationsInSession.length > eventViolationsInSession.length
121
+ ? brainViolationsInSession : eventViolationsInSession;
122
+ const sessionOverrides = sessionEvents.filter(
123
+ (e) => e.type === "override_applied"
124
+ );
125
+ return {
126
+ id: s.id,
127
+ tool: s.toolUsed,
128
+ date: s.startedAt.substring(0, 10),
129
+ events: sessionEvents.length,
130
+ violations: sessionViolations.length,
131
+ overrides: sessionOverrides.length,
132
+ impact: sessionViolations.length * 3 + sessionOverrides.length * 5,
133
+ };
134
+ }).sort((a, b) => b.impact - a.impact);
135
+
136
+ // --- Trend ---
137
+ // Compare first half vs second half of the period
138
+ const midpoint = new Date(Date.now() - (days / 2) * 86400000).toISOString();
139
+ const firstHalf = recentEvents.filter((e) => e.at < midpoint);
140
+ const secondHalf = recentEvents.filter((e) => e.at >= midpoint);
141
+ const firstViolations = allViolations.filter((v) => v.at >= cutoff && v.at < midpoint).length
142
+ + firstHalf.filter((e) => e.type === "conflict_blocked" || e.type === "conflict_warned").length;
143
+ const secondViolations = allViolations.filter((v) => v.at >= midpoint).length
144
+ + secondHalf.filter((e) => e.type === "conflict_blocked" || e.type === "conflict_warned").length;
145
+
146
+ let trend;
147
+ if (firstViolations === 0 && secondViolations === 0) trend = "stable";
148
+ else if (secondViolations > firstViolations) trend = "worsening";
149
+ else if (secondViolations < firstViolations) trend = "improving";
150
+ else trend = "stable";
151
+
152
+ return {
153
+ score: driftScore,
154
+ grade,
155
+ status,
156
+ trend,
157
+ period: `${days} days`,
158
+ signals: {
159
+ violations: { score: violationScore, max: 30, count: totalViolations, total: totalChecks },
160
+ overrides: { score: overrideScore, max: 20, count: overrides.length },
161
+ reverts: { score: revertScore, max: 15, count: reverts.length },
162
+ lockChurn: { score: churnScore, max: 15, removed: locksRemoved.length, added: locksAdded.length },
163
+ goalStability: { score: goalScore, max: 10, changes: goalChanges.length },
164
+ sessionGaps: { score: gapScore, max: 10, total: sessions.length, unsummarized: unsummarized.length },
165
+ },
166
+ activeLocks: activeLocks.length,
167
+ topDriftSessions: sessionDrift.slice(0, 5),
168
+ badge: `![SpecLock Drift Score](https://img.shields.io/badge/drift_score-${driftScore}%2F100-${driftScore <= 20 ? "brightgreen" : driftScore <= 50 ? "yellow" : "red"}.svg)`,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Format drift score for CLI output.
174
+ */
175
+ export function formatDriftScore(result) {
176
+ const lines = [];
177
+
178
+ lines.push(`Drift Score: ${result.score}/100 (${result.grade}) — ${result.status}`);
179
+ lines.push(`Trend: ${result.trend} | Period: ${result.period} | Active locks: ${result.activeLocks}`);
180
+ lines.push("");
181
+ lines.push("Signal Breakdown:");
182
+ lines.push(" " + "-".repeat(50));
183
+
184
+ const s = result.signals;
185
+ lines.push(` Violations: ${pad(s.violations.score)}/${s.violations.max} (${s.violations.count} violations in ${s.violations.total} checks)`);
186
+ lines.push(` Overrides: ${pad(s.overrides.score)}/${s.overrides.max} (${s.overrides.count} overrides)`);
187
+ lines.push(` Reverts: ${pad(s.reverts.score)}/${s.reverts.max} (${s.reverts.count} reverts detected)`);
188
+ lines.push(` Lock churn: ${pad(s.lockChurn.score)}/${s.lockChurn.max} (${s.lockChurn.removed} removed, ${s.lockChurn.added} added)`);
189
+ lines.push(` Goal stability: ${pad(s.goalStability.score)}/${s.goalStability.max} (${s.goalStability.changes} goal change(s))`);
190
+ lines.push(` Session gaps: ${pad(s.sessionGaps.score)}/${s.sessionGaps.max} (${s.sessionGaps.unsummarized}/${s.sessionGaps.total} unsummarized)`);
191
+
192
+ if (result.topDriftSessions.length > 0) {
193
+ lines.push("");
194
+ lines.push("Top Drift Sessions:");
195
+ for (const ds of result.topDriftSessions) {
196
+ if (ds.impact === 0) continue;
197
+ lines.push(` ${ds.date} ${ds.tool.padEnd(12)} ${ds.violations} violation(s), ${ds.overrides} override(s) [impact: ${ds.impact}]`);
198
+ }
199
+ }
200
+
201
+ lines.push("");
202
+ lines.push(`README badge: ${result.badge}`);
203
+
204
+ return lines.join("\n");
205
+ }
206
+
207
+ function pad(n) {
208
+ return String(n).padStart(2, " ");
209
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * SpecLock Lock Strengthener — Grade and Improve Weak Locks
3
+ * Analyzes each lock's specificity, scope, and detection power.
4
+ * Suggests stronger versions that catch more violations.
5
+ *
6
+ * Developed by Sandeep Roy (https://github.com/sgroy10)
7
+ */
8
+
9
+ import { readBrain } from "./storage.js";
10
+ import { ensureInit } from "./memory.js";
11
+
12
+ // --- Weakness patterns ---
13
+
14
+ const WEAKNESS_RULES = [
15
+ {
16
+ id: "too_short",
17
+ test: (text) => text.split(/\s+/).length < 4,
18
+ issue: "Too vague — short locks miss edge cases",
19
+ fix: (text) => `${text} — no modifications, refactoring, or rewriting allowed without explicit permission`,
20
+ },
21
+ {
22
+ id: "no_action_verb",
23
+ test: (text) => {
24
+ const lower = text.toLowerCase();
25
+ return !["never", "don't", "do not", "must not", "cannot", "must", "always", "no "].some(
26
+ (v) => lower.includes(v)
27
+ );
28
+ },
29
+ issue: "No enforcement verb — AI may interpret as suggestion rather than rule",
30
+ fix: (text) => `Never ${text.charAt(0).toLowerCase() + text.slice(1)}`,
31
+ },
32
+ {
33
+ id: "no_scope",
34
+ test: (text) => {
35
+ const lower = text.toLowerCase();
36
+ const hasScope = ["file", "module", "function", "endpoint", "route", "table",
37
+ "column", "field", "component", "page", "api", "database", "schema",
38
+ "auth", "payment", "config", "secret", "key", "middleware"].some(
39
+ (s) => lower.includes(s)
40
+ );
41
+ return !hasScope;
42
+ },
43
+ issue: "No specific scope — doesn't target files, modules, or components",
44
+ fix: (text) => text, // Can't auto-fix without context
45
+ },
46
+ {
47
+ id: "no_consequence",
48
+ test: (text) => {
49
+ const lower = text.toLowerCase();
50
+ return !["because", "reason", "will break", "will fail", "causes", "leads to",
51
+ "depends on", "clients depend", "users expect", "production", "critical"].some(
52
+ (c) => lower.includes(c)
53
+ );
54
+ },
55
+ issue: "No consequence explained — AI may override without understanding impact",
56
+ fix: null, // Needs context
57
+ },
58
+ {
59
+ id: "ambiguous_touch",
60
+ test: (text) => {
61
+ const lower = text.toLowerCase();
62
+ return lower.includes("touch") && !lower.includes("modify") && !lower.includes("change");
63
+ },
64
+ issue: '"Touch" is ambiguous — does it mean read, write, or delete?',
65
+ fix: (text) => text.replace(/touch/gi, "modify, refactor, or delete"),
66
+ },
67
+ {
68
+ id: "missing_euphemism_guard",
69
+ test: (text) => {
70
+ const lower = text.toLowerCase();
71
+ const hasDelete = lower.includes("delete") || lower.includes("remove");
72
+ const hasCleanup = lower.includes("clean") || lower.includes("simplif") || lower.includes("reorganiz");
73
+ return hasDelete && !hasCleanup;
74
+ },
75
+ issue: 'Doesn\'t guard against euphemisms like "clean up" or "simplify" which often mean deletion',
76
+ fix: (text) => `${text}. This includes euphemisms like "clean up", "simplify", "modernize", or "reorganize" — these often mask destructive changes`,
77
+ },
78
+ ];
79
+
80
+ /**
81
+ * Analyze all locks and suggest improvements.
82
+ *
83
+ * @param {string} root - Project root
84
+ * @returns {Object} Strength analysis
85
+ */
86
+ export function analyzeLockStrength(root) {
87
+ const brain = ensureInit(root);
88
+ const activeLocks = (brain.specLock?.items || []).filter((l) => l.active !== false);
89
+
90
+ if (activeLocks.length === 0) {
91
+ return {
92
+ totalLocks: 0,
93
+ avgStrength: 0,
94
+ locks: [],
95
+ summary: "No active locks to analyze. Add constraints first.",
96
+ };
97
+ }
98
+
99
+ const analyzed = activeLocks.map((lock) => {
100
+ const text = lock.text || "";
101
+ const weaknesses = [];
102
+ const suggestions = [];
103
+
104
+ for (const rule of WEAKNESS_RULES) {
105
+ if (rule.test(text)) {
106
+ weaknesses.push({ id: rule.id, issue: rule.issue });
107
+ if (rule.fix) {
108
+ const fixed = rule.fix(text);
109
+ if (fixed !== text) {
110
+ suggestions.push({ id: rule.id, improved: fixed });
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ // Score: start at 100, deduct per weakness
117
+ const deductions = {
118
+ too_short: 25,
119
+ no_action_verb: 20,
120
+ no_scope: 15,
121
+ no_consequence: 10,
122
+ ambiguous_touch: 15,
123
+ missing_euphemism_guard: 10,
124
+ };
125
+
126
+ let strength = 100;
127
+ for (const w of weaknesses) {
128
+ strength -= deductions[w.id] || 10;
129
+ }
130
+ strength = Math.max(0, strength);
131
+
132
+ let grade;
133
+ if (strength >= 90) grade = "A";
134
+ else if (strength >= 75) grade = "B";
135
+ else if (strength >= 60) grade = "C";
136
+ else if (strength >= 40) grade = "D";
137
+ else grade = "F";
138
+
139
+ return {
140
+ id: lock.id,
141
+ text: text.substring(0, 100),
142
+ fullText: text,
143
+ strength,
144
+ grade,
145
+ weaknesses,
146
+ suggestions: suggestions.slice(0, 2), // top 2 suggestions
147
+ };
148
+ });
149
+
150
+ const avgStrength = Math.round(
151
+ analyzed.reduce((sum, l) => sum + l.strength, 0) / analyzed.length
152
+ );
153
+
154
+ const weak = analyzed.filter((l) => l.strength < 60);
155
+ const strong = analyzed.filter((l) => l.strength >= 80);
156
+
157
+ return {
158
+ totalLocks: analyzed.length,
159
+ avgStrength,
160
+ avgGrade: avgStrength >= 90 ? "A" : avgStrength >= 75 ? "B" : avgStrength >= 60 ? "C" : avgStrength >= 40 ? "D" : "F",
161
+ strongCount: strong.length,
162
+ weakCount: weak.length,
163
+ locks: analyzed,
164
+ summary: `${analyzed.length} locks analyzed. Average strength: ${avgStrength}/100 (${weak.length} weak, ${strong.length} strong).`,
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Format strength analysis for CLI output.
170
+ */
171
+ export function formatStrength(result) {
172
+ if (result.totalLocks === 0) return result.summary;
173
+
174
+ const lines = [];
175
+
176
+ lines.push(`Lock Strength: ${result.avgStrength}/100 (${result.avgGrade}) — ${result.strongCount} strong, ${result.weakCount} weak`);
177
+ lines.push("");
178
+
179
+ // Sort weakest first
180
+ const sorted = [...result.locks].sort((a, b) => a.strength - b.strength);
181
+
182
+ for (const lock of sorted) {
183
+ const icon = lock.strength >= 80 ? "STRONG" : lock.strength >= 60 ? "OK" : "WEAK";
184
+ lines.push(`[${icon.padEnd(6)}] ${lock.strength}/100 (${lock.grade}) "${lock.text}"`);
185
+
186
+ if (lock.weaknesses.length > 0) {
187
+ for (const w of lock.weaknesses) {
188
+ lines.push(` Issue: ${w.issue}`);
189
+ }
190
+ }
191
+ if (lock.suggestions.length > 0) {
192
+ lines.push(` Suggested: "${lock.suggestions[0].improved.substring(0, 100)}"`);
193
+ }
194
+ lines.push("");
195
+ }
196
+
197
+ lines.push(result.summary);
198
+ return lines.join("\n");
199
+ }
@@ -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.3.1 &mdash; AI Constraint Engine</div>
92
+ <div class="meta">v5.4.1 &mdash; AI Constraint Engine</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.3.1 &mdash; Developed by Sandeep Roy &mdash; <a href="https://github.com/sgroy10/speclock" style="color:var(--accent)">GitHub</a>
185
+ SpecLock v5.4.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.3.1";
116
+ const VERSION = "5.4.1";
117
117
  const AUTHOR = "Sandeep Roy";
118
118
  const START_TIME = Date.now();
119
119
 
@@ -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. 46 MCP tools. 929 tests, 100% accuracy.",
905
- tools: 46,
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,
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
@@ -67,6 +67,9 @@ import {
67
67
  import { generateContext, generateContextPack } from "../core/context.js";
68
68
  import { syncRules, getSyncFormats } from "../core/rules-sync.js";
69
69
  import { getReplay, listSessions, formatReplay } from "../core/replay.js";
70
+ import { computeDriftScore, formatDriftScore } from "../core/drift-score.js";
71
+ import { computeCoverage, formatCoverage } from "../core/coverage.js";
72
+ import { analyzeLockStrength, formatStrength } from "../core/strengthen.js";
70
73
  import {
71
74
  readBrain,
72
75
  readEvents,
@@ -122,7 +125,7 @@ const PROJECT_ROOT =
122
125
  args.project || process.env.SPECLOCK_PROJECT_ROOT || process.cwd();
123
126
 
124
127
  // --- MCP Server ---
125
- const VERSION = "5.3.1";
128
+ const VERSION = "5.4.1";
126
129
  const AUTHOR = "Sandeep Roy";
127
130
 
128
131
  const server = new McpServer(
@@ -1999,7 +2002,45 @@ server.tool(
1999
2002
  }
2000
2003
  );
2001
2004
 
2002
- // Tool 39: speclock_list_sync_formats
2005
+ // Tool 39: speclock_drift_score
2006
+ server.tool(
2007
+ "speclock_drift_score",
2008
+ "Compute a 0-100 Drift Score measuring how much the project has drifted from the founder's original intent. Analyzes violations, overrides, reverts, lock churn, goal stability, and session gaps. Returns score, grade, trend, signal breakdown, and per-session impact. Only SpecLock can compute this because only SpecLock knows what was INTENDED vs what was DONE.",
2009
+ {
2010
+ days: z.number().optional().default(30).describe("Look back N days (default: 30)"),
2011
+ },
2012
+ async ({ days }) => {
2013
+ const result = computeDriftScore(PROJECT_ROOT, { days });
2014
+ const formatted = formatDriftScore(result);
2015
+ return { content: [{ type: "text", text: `## Drift Score\n\n\`\`\`\n${formatted}\n\`\`\`` }] };
2016
+ }
2017
+ );
2018
+
2019
+ // Tool 40: speclock_coverage
2020
+ server.tool(
2021
+ "speclock_coverage",
2022
+ "Lock Coverage Audit — scans the codebase for high-risk patterns (auth, payments, database, secrets, API routes, security, error handling, logging) and identifies areas with no lock protecting them. Auto-suggests the missing locks you need. Like a security scanner but for AI constraint gaps.",
2023
+ {},
2024
+ async () => {
2025
+ const result = computeCoverage(PROJECT_ROOT);
2026
+ const formatted = formatCoverage(result);
2027
+ return { content: [{ type: "text", text: `## Lock Coverage Audit\n\n\`\`\`\n${formatted}\n\`\`\`` }] };
2028
+ }
2029
+ );
2030
+
2031
+ // Tool 41: speclock_strengthen
2032
+ server.tool(
2033
+ "speclock_strengthen",
2034
+ "Lock Strengthener — grades each lock's specificity, scope, and detection power (0-100). Identifies weak locks and suggests stronger versions that catch more violations. Checks for: vagueness, missing enforcement verbs, no scope, no consequence, ambiguous language, missing euphemism guards.",
2035
+ {},
2036
+ async () => {
2037
+ const result = analyzeLockStrength(PROJECT_ROOT);
2038
+ const formatted = formatStrength(result);
2039
+ return { content: [{ type: "text", text: `## Lock Strength Analysis\n\n\`\`\`\n${formatted}\n\`\`\`` }] };
2040
+ }
2041
+ );
2042
+
2043
+ // Tool 42: speclock_list_sync_formats
2003
2044
  server.tool(
2004
2045
  "speclock_list_sync_formats",
2005
2046
  "List all available AI tool formats that SpecLock can sync constraints to. Shows format key, tool name, output file path, and description.",