speclock 5.4.1 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -9
- package/bin/speclock.js +1 -1
- package/package.json +2 -2
- package/src/cli/index.js +18 -1
- package/src/core/compliance.js +1 -1
- package/src/core/engine.js +8 -0
- package/src/core/guardian.js +348 -0
- package/src/core/rules-sync.js +4 -0
- package/src/core/telemetry.js +1 -1
- package/src/dashboard/index.html +2 -2
- package/src/mcp/http-server.js +4 -4
- package/src/mcp/server.js +42 -1
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-
|
|
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.
|
|
36
|
-
**
|
|
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
|
-
##
|
|
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 (
|
|
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
|
-
|
|
|
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.
|
|
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
|
|
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.
|
|
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
package/package.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
"name": "speclock",
|
|
4
4
|
|
|
5
|
-
"version": "5.
|
|
5
|
+
"version": "5.5.0",
|
|
6
6
|
|
|
7
|
-
"description": "AI
|
|
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.
|
|
126
|
+
SpecLock v5.5.0 — 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();
|
package/src/core/compliance.js
CHANGED
package/src/core/engine.js
CHANGED
|
@@ -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,348 @@
|
|
|
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
|
+
/**
|
|
36
|
+
* Discover all AI rule files in the project.
|
|
37
|
+
*/
|
|
38
|
+
export function discoverRuleFiles(root) {
|
|
39
|
+
const found = [];
|
|
40
|
+
for (const entry of RULE_FILES) {
|
|
41
|
+
const fullPath = path.join(root, entry.file);
|
|
42
|
+
if (fs.existsSync(fullPath)) {
|
|
43
|
+
const content = fs.readFileSync(fullPath, "utf-8").trim();
|
|
44
|
+
if (content.length > 0) {
|
|
45
|
+
found.push({
|
|
46
|
+
file: entry.file,
|
|
47
|
+
tool: entry.tool,
|
|
48
|
+
path: fullPath,
|
|
49
|
+
content,
|
|
50
|
+
size: content.length,
|
|
51
|
+
lines: content.split("\n").length,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return found;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// --- Heuristic constraint extraction (no API key needed) ---
|
|
60
|
+
|
|
61
|
+
// Patterns that signal a constraint/rule
|
|
62
|
+
const CONSTRAINT_PATTERNS = [
|
|
63
|
+
// Strong imperative (NEVER, ALWAYS, MUST, DO NOT)
|
|
64
|
+
/^[-*•]\s*(NEVER|ALWAYS|MUST|DO NOT|DON'T|DONT|SHALL NOT|REQUIRED|MANDATORY|CRITICAL|IMPORTANT)\b/i,
|
|
65
|
+
/^(NEVER|ALWAYS|MUST|DO NOT|DON'T|DONT|SHALL NOT)\b/i,
|
|
66
|
+
// "Do not..." / "Don't..." at line start
|
|
67
|
+
/^[-*•]\s*(Do not|Don't|Dont|Never|Always|Must)\b/,
|
|
68
|
+
/^(Do not|Don't|Dont)\b/,
|
|
69
|
+
// Emphasis markers suggesting importance
|
|
70
|
+
/^\*\*(NEVER|ALWAYS|MUST|DO NOT|DON'T|IMPORTANT|CRITICAL|REQUIRED)\*\*/i,
|
|
71
|
+
// "X is required" / "X is mandatory" / "X is non-negotiable"
|
|
72
|
+
/\b(is required|is mandatory|is non-negotiable|is critical|is forbidden|is prohibited)\b/i,
|
|
73
|
+
// "Keep X" / "Preserve X" / "Protect X"
|
|
74
|
+
/^[-*•]\s*(Keep|Preserve|Protect|Maintain|Ensure|Enforce)\b/,
|
|
75
|
+
// Negative imperatives
|
|
76
|
+
/^[-*•]\s*(Avoid|Prevent|Prohibit|Forbid|Restrict|Disallow)\b/i,
|
|
77
|
+
// "should never" / "must never" / "must always"
|
|
78
|
+
/\b(should never|must never|must always|should always|must not|should not|cannot|can not)\b/i,
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
// Patterns that signal a decision/choice
|
|
82
|
+
const DECISION_PATTERNS = [
|
|
83
|
+
/^[-*•]\s*(Use|Using|Tech stack|Stack|Framework|We use|Built with|Powered by)\b/i,
|
|
84
|
+
/\b(tech stack|architecture|we chose|we decided|we use|built with)\b/i,
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// Lines to skip (headers, empty, comments, boilerplate)
|
|
88
|
+
const SKIP_PATTERNS = [
|
|
89
|
+
/^#+\s/, // Markdown headers
|
|
90
|
+
/^---+$/, // Horizontal rules
|
|
91
|
+
/^\s*$/, // Empty lines
|
|
92
|
+
/^```/, // Code fences
|
|
93
|
+
/^<!--/, // HTML comments
|
|
94
|
+
/^>\s/, // Blockquotes (context, not rules)
|
|
95
|
+
/^Auto-synced by SpecLock/, // Our own output
|
|
96
|
+
/^Powered by \[SpecLock\]/, // Our own footer
|
|
97
|
+
/^#\s*SpecLock/, // SpecLock headers
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Extract constraints from raw text using heuristic pattern matching.
|
|
102
|
+
* No API key required — works offline, instantly.
|
|
103
|
+
*/
|
|
104
|
+
export function extractConstraints(content, sourceFile) {
|
|
105
|
+
const lines = content.split("\n");
|
|
106
|
+
const locks = [];
|
|
107
|
+
const decisions = [];
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < lines.length; i++) {
|
|
111
|
+
const raw = lines[i];
|
|
112
|
+
const trimmed = raw.trim();
|
|
113
|
+
|
|
114
|
+
// Skip non-content lines
|
|
115
|
+
if (SKIP_PATTERNS.some((p) => p.test(trimmed))) continue;
|
|
116
|
+
|
|
117
|
+
// Clean up: remove leading bullet/dash, markdown bold
|
|
118
|
+
const cleaned = trimmed
|
|
119
|
+
.replace(/^[-*•]\s*/, "")
|
|
120
|
+
.replace(/\*\*/g, "")
|
|
121
|
+
.trim();
|
|
122
|
+
|
|
123
|
+
if (cleaned.length < 10 || cleaned.length > 300) continue;
|
|
124
|
+
|
|
125
|
+
// Deduplicate
|
|
126
|
+
const key = cleaned.toLowerCase().replace(/\s+/g, " ");
|
|
127
|
+
if (seen.has(key)) continue;
|
|
128
|
+
|
|
129
|
+
// Check for constraint patterns
|
|
130
|
+
if (CONSTRAINT_PATTERNS.some((p) => p.test(trimmed) || p.test(cleaned))) {
|
|
131
|
+
seen.add(key);
|
|
132
|
+
locks.push({
|
|
133
|
+
text: cleaned,
|
|
134
|
+
tags: [sourceFile.replace(/[/.]/g, "_").replace(/^_+|_+$/g, "")],
|
|
135
|
+
source: "guardian",
|
|
136
|
+
line: i + 1,
|
|
137
|
+
});
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check for decision patterns
|
|
142
|
+
if (DECISION_PATTERNS.some((p) => p.test(trimmed) || p.test(cleaned))) {
|
|
143
|
+
seen.add(key);
|
|
144
|
+
decisions.push({
|
|
145
|
+
text: cleaned,
|
|
146
|
+
tags: [sourceFile.replace(/[/.]/g, "_").replace(/^_+|_+$/g, "")],
|
|
147
|
+
line: i + 1,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { locks, decisions };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Run the full Guardian protect flow:
|
|
157
|
+
* 1. Init SpecLock if needed
|
|
158
|
+
* 2. Discover rule files
|
|
159
|
+
* 3. Extract constraints from each
|
|
160
|
+
* 4. Add as locks (skip duplicates with existing)
|
|
161
|
+
* 5. Install pre-commit hook
|
|
162
|
+
* 6. Sync rules back to all formats
|
|
163
|
+
* 7. Generate context
|
|
164
|
+
*
|
|
165
|
+
* Returns a report object.
|
|
166
|
+
*/
|
|
167
|
+
export function protect(root, options = {}) {
|
|
168
|
+
const report = {
|
|
169
|
+
discovered: [],
|
|
170
|
+
extracted: { locks: 0, decisions: 0 },
|
|
171
|
+
added: { locks: 0, decisions: 0, skipped: 0 },
|
|
172
|
+
hookInstalled: false,
|
|
173
|
+
hookStatus: "",
|
|
174
|
+
synced: [],
|
|
175
|
+
errors: [],
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// 1. Init
|
|
179
|
+
const brain = ensureInit(root);
|
|
180
|
+
|
|
181
|
+
// 2. Discover
|
|
182
|
+
const ruleFiles = discoverRuleFiles(root);
|
|
183
|
+
report.discovered = ruleFiles.map((f) => ({
|
|
184
|
+
file: f.file,
|
|
185
|
+
tool: f.tool,
|
|
186
|
+
lines: f.lines,
|
|
187
|
+
size: f.size,
|
|
188
|
+
}));
|
|
189
|
+
|
|
190
|
+
if (ruleFiles.length === 0) {
|
|
191
|
+
report.errors.push(
|
|
192
|
+
"No AI rule files found (.cursorrules, CLAUDE.md, AGENTS.md, etc). " +
|
|
193
|
+
"Create one first, or use 'speclock setup' to start from scratch."
|
|
194
|
+
);
|
|
195
|
+
return report;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 3. Extract constraints from each file
|
|
199
|
+
const allLocks = [];
|
|
200
|
+
const allDecisions = [];
|
|
201
|
+
|
|
202
|
+
for (const rf of ruleFiles) {
|
|
203
|
+
const result = extractConstraints(rf.content, rf.file);
|
|
204
|
+
allLocks.push(...result.locks);
|
|
205
|
+
allDecisions.push(...result.decisions);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
report.extracted.locks = allLocks.length;
|
|
209
|
+
report.extracted.decisions = allDecisions.length;
|
|
210
|
+
|
|
211
|
+
// 4. Add locks (skip duplicates against existing brain locks)
|
|
212
|
+
const existingTexts = new Set(
|
|
213
|
+
(brain.specLock?.items || [])
|
|
214
|
+
.filter((l) => l.active !== false)
|
|
215
|
+
.map((l) => l.text.toLowerCase().replace(/\s+/g, " "))
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
for (const lock of allLocks) {
|
|
219
|
+
const normalized = lock.text.toLowerCase().replace(/\s+/g, " ");
|
|
220
|
+
if (existingTexts.has(normalized)) {
|
|
221
|
+
report.added.skipped++;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
existingTexts.add(normalized);
|
|
225
|
+
addLock(root, lock.text, lock.tags, lock.source || "guardian");
|
|
226
|
+
report.added.locks++;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const dec of allDecisions) {
|
|
230
|
+
const normalized = dec.text.toLowerCase().replace(/\s+/g, " ");
|
|
231
|
+
if (existingTexts.has(normalized)) {
|
|
232
|
+
report.added.skipped++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
existingTexts.add(normalized);
|
|
236
|
+
addDecision(root, dec.text, dec.tags, "guardian");
|
|
237
|
+
report.added.decisions++;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 5. Install pre-commit hook
|
|
241
|
+
if (!options.skipHook) {
|
|
242
|
+
if (isHookInstalled(root)) {
|
|
243
|
+
report.hookInstalled = true;
|
|
244
|
+
report.hookStatus = "already installed";
|
|
245
|
+
} else {
|
|
246
|
+
const hookResult = installHook(root);
|
|
247
|
+
report.hookInstalled = hookResult.success;
|
|
248
|
+
report.hookStatus = hookResult.success
|
|
249
|
+
? "installed"
|
|
250
|
+
: hookResult.error || "failed";
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
report.hookStatus = "skipped (--no-hook)";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 6. Sync rules to formats that WEREN'T source files (don't overwrite user's originals)
|
|
257
|
+
if (!options.skipSync) {
|
|
258
|
+
const sourceFiles = new Set(ruleFiles.map((f) => f.file));
|
|
259
|
+
try {
|
|
260
|
+
const syncResult = syncRules(root, { format: "all", excludeFiles: sourceFiles });
|
|
261
|
+
report.synced = (syncResult.synced || []).map((s) => s.file || s);
|
|
262
|
+
} catch (e) {
|
|
263
|
+
report.errors.push(`Sync failed: ${e.message}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 7. Generate context
|
|
268
|
+
try {
|
|
269
|
+
generateContext(root);
|
|
270
|
+
} catch (_) {
|
|
271
|
+
// Non-critical
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return report;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Format protect report for CLI output.
|
|
279
|
+
*/
|
|
280
|
+
export function formatProtectReport(report) {
|
|
281
|
+
const lines = [];
|
|
282
|
+
|
|
283
|
+
lines.push("");
|
|
284
|
+
lines.push(" SpecLock Protect — Guardian Mode");
|
|
285
|
+
lines.push(" " + "=".repeat(50));
|
|
286
|
+
lines.push("");
|
|
287
|
+
|
|
288
|
+
// Discovered files
|
|
289
|
+
if (report.discovered.length > 0) {
|
|
290
|
+
lines.push(" Rule files found:");
|
|
291
|
+
for (const f of report.discovered) {
|
|
292
|
+
lines.push(` [+] ${f.file} (${f.tool}, ${f.lines} lines)`);
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
lines.push(" [!] No rule files found.");
|
|
296
|
+
}
|
|
297
|
+
lines.push("");
|
|
298
|
+
|
|
299
|
+
// Extracted
|
|
300
|
+
lines.push(` Extracted: ${report.extracted.locks} constraints, ${report.extracted.decisions} decisions`);
|
|
301
|
+
|
|
302
|
+
// Added
|
|
303
|
+
if (report.added.locks > 0 || report.added.decisions > 0) {
|
|
304
|
+
lines.push(` Added: ${report.added.locks} new locks, ${report.added.decisions} new decisions`);
|
|
305
|
+
}
|
|
306
|
+
if (report.added.skipped > 0) {
|
|
307
|
+
lines.push(` Skipped: ${report.added.skipped} (already existed)`);
|
|
308
|
+
}
|
|
309
|
+
lines.push("");
|
|
310
|
+
|
|
311
|
+
// Hook
|
|
312
|
+
if (report.hookStatus === "installed") {
|
|
313
|
+
lines.push(" Pre-commit hook: INSTALLED");
|
|
314
|
+
lines.push(" Every commit will now be checked against your constraints.");
|
|
315
|
+
} else if (report.hookStatus === "already installed") {
|
|
316
|
+
lines.push(" Pre-commit hook: already active");
|
|
317
|
+
} else {
|
|
318
|
+
lines.push(` Pre-commit hook: ${report.hookStatus}`);
|
|
319
|
+
}
|
|
320
|
+
lines.push("");
|
|
321
|
+
|
|
322
|
+
// Sync
|
|
323
|
+
if (report.synced.length > 0) {
|
|
324
|
+
lines.push(" Rules synced to:");
|
|
325
|
+
for (const s of report.synced) {
|
|
326
|
+
lines.push(` [+] ${s}`);
|
|
327
|
+
}
|
|
328
|
+
lines.push("");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Errors
|
|
332
|
+
if (report.errors.length > 0) {
|
|
333
|
+
for (const e of report.errors) {
|
|
334
|
+
lines.push(` [!] ${e}`);
|
|
335
|
+
}
|
|
336
|
+
lines.push("");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Final message
|
|
340
|
+
const total = report.added.locks + report.added.skipped;
|
|
341
|
+
if (total > 0) {
|
|
342
|
+
lines.push(" Your rules are now ENFORCED, not just suggested.");
|
|
343
|
+
lines.push(" AI agents that violate constraints will be blocked.");
|
|
344
|
+
}
|
|
345
|
+
lines.push("");
|
|
346
|
+
|
|
347
|
+
return lines.join("\n");
|
|
348
|
+
}
|
package/src/core/rules-sync.js
CHANGED
|
@@ -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);
|
package/src/core/telemetry.js
CHANGED
|
@@ -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: "
|
|
260
|
+
version: "5.5.0",
|
|
261
261
|
totalCalls: summary.totalCalls,
|
|
262
262
|
avgResponseMs: summary.avgResponseMs,
|
|
263
263
|
conflicts: summary.conflicts,
|
package/src/dashboard/index.html
CHANGED
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
<div class="header">
|
|
90
90
|
<div>
|
|
91
91
|
<h1><span>SpecLock</span> Dashboard</h1>
|
|
92
|
-
<div class="meta">v5.
|
|
92
|
+
<div class="meta">v5.5.0 — 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.
|
|
185
|
+
SpecLock v5.5.0 — Developed by Sandeep Roy — <a href="https://github.com/sgroy10/speclock" style="color:var(--accent)">GitHub</a>
|
|
186
186
|
</div>
|
|
187
187
|
|
|
188
188
|
<script>
|
package/src/mcp/http-server.js
CHANGED
|
@@ -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.
|
|
116
|
+
const VERSION = "5.5.0";
|
|
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:
|
|
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
|
|
905
|
-
tools:
|
|
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.
|
|
129
|
+
const VERSION = "5.5.0";
|
|
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;
|