speclock 5.5.1 → 5.5.2

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  "name": "speclock",
4
4
 
5
- "version": "5.5.1",
5
+ "version": "5.5.2",
6
6
 
7
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
 
package/src/cli/index.js CHANGED
@@ -123,7 +123,7 @@ function refreshContext(root) {
123
123
 
124
124
  function printHelp() {
125
125
  console.log(`
126
- SpecLock v5.5.1 — Your AI has rules. SpecLock makes them unbreakable.
126
+ SpecLock v5.5.2 — Your AI has rules. SpecLock makes them unbreakable.
127
127
  Developed by Sandeep Roy (github.com/sgroy10)
128
128
 
129
129
  Usage: speclock <command> [options]
@@ -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.5.1";
12
+ const VERSION = "5.5.2";
13
13
 
14
14
  // PHI-related keywords for HIPAA filtering
15
15
  const PHI_KEYWORDS = [
@@ -2153,6 +2153,50 @@ export function scoreConflict({ actionText, lockText }) {
2153
2153
  }
2154
2154
  }
2155
2155
 
2156
+ // Check 0b: MUST-mandate inversion
2157
+ // "MUST validate all user input" + "skip input validation" → conflict
2158
+ // When a lock says MUST/ALWAYS + <verb>, and the action uses a negation verb
2159
+ // (skip, remove, disable, bypass, omit, drop, eliminate, stop) targeting the same concept,
2160
+ // that's a violation of the mandate.
2161
+ {
2162
+ const mandateMatch = lockText.match(/^\s*(?:must|always)\s+(\w+(?:\s+\w+){0,3})/i);
2163
+ if (mandateMatch && !prohibitedVerb) {
2164
+ const mandatedPhrase = mandateMatch[1].toLowerCase();
2165
+ const mandatedWords = mandatedPhrase.split(/\s+/).filter(w => !STOPWORDS.has(w) && w.length > 2);
2166
+ const actionLower = actionText.toLowerCase();
2167
+ const NEGATION_VERBS = /\b(skip|skipping|remove|removing|disable|disabling|bypass|bypassing|omit|omitting|drop|dropping|eliminate|eliminating|stop|stopping|avoid|avoiding|delete|deleting|no|without)\b/;
2168
+
2169
+ if (NEGATION_VERBS.test(actionLower)) {
2170
+ // Check if the action targets the same concept as the mandated verb
2171
+ // Stem matching: "validate" ↔ "validation" share root "validat" (5+ chars)
2172
+ const actionWords = actionLower.split(/\s+/).map(w => w.replace(/[^a-z]/g, "")).filter(w => w.length > 2);
2173
+ let conceptOverlap = 0;
2174
+ for (const mw of mandatedWords) {
2175
+ for (const aw of actionWords) {
2176
+ if (aw === mw) { conceptOverlap++; break; }
2177
+ // Share a common root of 5+ chars: validate↔validation (validat)
2178
+ const minLen = Math.min(aw.length, mw.length);
2179
+ if (minLen >= 5) {
2180
+ const root = Math.min(minLen, Math.max(5, minLen - 2));
2181
+ if (aw.substring(0, root) === mw.substring(0, root)) {
2182
+ conceptOverlap++;
2183
+ break;
2184
+ }
2185
+ }
2186
+ }
2187
+ }
2188
+ if (conceptOverlap >= 1) {
2189
+ const bonus = conceptOverlap >= 2 ? 45 : 25;
2190
+ score += bonus;
2191
+ actionPerformsProhibitedOp = true;
2192
+ reasons.push(
2193
+ `mandate violation: action negates MUST-requirement ` +
2194
+ `"${mandatedPhrase}" (${conceptOverlap} concept overlap)`);
2195
+ }
2196
+ }
2197
+ }
2198
+ }
2199
+
2156
2200
  // Check 1: Direct opposite verbs (e.g., "enable" vs "disable")
2157
2201
  if (lockIsProhibitive && prohibitedVerb && actionPrimaryVerb) {
2158
2202
  if (checkOpposites(actionPrimaryVerb, prohibitedVerb)) {
@@ -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: "5.5.1",
260
+ version: "5.5.2",
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.5.1 &mdash; Your AI has rules. SpecLock makes them unbreakable.</div>
92
+ <div class="meta">v5.5.2 &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.5.1 &mdash; Developed by Sandeep Roy &mdash; <a href="https://github.com/sgroy10/speclock" style="color:var(--accent)">GitHub</a>
185
+ SpecLock v5.5.2 &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.5.1";
116
+ const VERSION = "5.5.2";
117
117
  const AUTHOR = "Sandeep Roy";
118
118
  const START_TIME = Date.now();
119
119
 
package/src/mcp/server.js CHANGED
@@ -126,7 +126,7 @@ const PROJECT_ROOT =
126
126
  args.project || process.env.SPECLOCK_PROJECT_ROOT || process.cwd();
127
127
 
128
128
  // --- MCP Server ---
129
- const VERSION = "5.5.1";
129
+ const VERSION = "5.5.2";
130
130
  const AUTHOR = "Sandeep Roy";
131
131
 
132
132
  const server = new McpServer(