speclock 5.5.0 → 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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  "name": "speclock",
4
4
 
5
- "version": "5.5.0",
5
+ "version": "5.5.1",
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.0 — Your AI has rules. SpecLock makes them unbreakable.
126
+ SpecLock v5.5.1 — 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.0";
12
+ const VERSION = "5.5.1";
13
13
 
14
14
  // PHI-related keywords for HIPAA filtering
15
15
  const PHI_KEYWORDS = [
@@ -32,30 +32,66 @@ const RULE_FILES = [
32
32
  { file: ".github/instructions.md", tool: "GitHub (alt)" },
33
33
  ];
34
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
+
35
56
  /**
36
57
  * Discover all AI rule files in the project.
37
58
  */
38
59
  export function discoverRuleFiles(root) {
39
60
  const found = [];
40
61
  for (const entry of RULE_FILES) {
62
+ // Skip known SpecLock output files
63
+ if (SPECLOCK_OUTPUT_FILES.has(entry.file)) continue;
64
+
41
65
  const fullPath = path.join(root, entry.file);
42
66
  if (fs.existsSync(fullPath)) {
43
67
  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
- }
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
+ });
54
81
  }
55
82
  }
56
83
  return found;
57
84
  }
58
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
+
59
95
  // --- Heuristic constraint extraction (no API key needed) ---
60
96
 
61
97
  // Patterns that signal a constraint/rule
@@ -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: "5.5.0",
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.5.0 &mdash; Your AI has rules. SpecLock makes them unbreakable.</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.5.0 &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.5.0";
116
+ const VERSION = "5.5.1";
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.0";
129
+ const VERSION = "5.5.1";
130
130
  const AUTHOR = "Sandeep Roy";
131
131
 
132
132
  const server = new McpServer(