speclock 5.4.0 → 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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  "name": "speclock",
4
4
 
5
- "version": "5.4.0",
5
+ "version": "5.4.1",
6
6
 
7
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
 
package/src/cli/index.js CHANGED
@@ -122,7 +122,7 @@ function refreshContext(root) {
122
122
 
123
123
  function printHelp() {
124
124
  console.log(`
125
- SpecLock v5.4.0 — 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)
126
126
  Developed by Sandeep Roy (github.com/sgroy10)
127
127
 
128
128
  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.4.0";
12
+ const VERSION = "5.4.1";
13
13
 
14
14
  // PHI-related keywords for HIPAA filtering
15
15
  const PHI_KEYWORDS = [
@@ -39,17 +39,21 @@ export function computeDriftScore(root, options = {}) {
39
39
 
40
40
  // --- Signal 1: Violation Rate (0-30 points) ---
41
41
  // How often did the AI hit constraints?
42
- const violations = recentEvents.filter(
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(
43
47
  (e) => e.type === "conflict_blocked" || e.type === "conflict_warned" ||
44
48
  (e.summary && (e.summary.includes("CONFLICT") || e.summary.includes("BLOCK")))
45
49
  );
46
- const checks = recentEvents.filter(
47
- (e) => e.type === "conflict_checked" || e.type === "conflict_blocked" ||
48
- e.type === "conflict_warned"
49
- );
50
- const violationRate = checks.length > 0
51
- ? (violations.length / checks.length) * 100
52
- : 0;
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;
53
57
  // 0% violations = 0 drift, 50%+ = 30 drift
54
58
  const violationScore = Math.min(30, Math.round(violationRate * 0.6));
55
59
 
@@ -105,10 +109,16 @@ export function computeDriftScore(root, options = {}) {
105
109
  const sessionEvents = recentEvents.filter(
106
110
  (e) => e.at >= s.startedAt && (s.endedAt ? e.at <= s.endedAt : true)
107
111
  );
108
- const sessionViolations = sessionEvents.filter(
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(
109
117
  (e) => e.type === "conflict_blocked" || e.type === "conflict_warned" ||
110
118
  (e.summary && e.summary.includes("CONFLICT"))
111
119
  );
120
+ const sessionViolations = brainViolationsInSession.length > eventViolationsInSession.length
121
+ ? brainViolationsInSession : eventViolationsInSession;
112
122
  const sessionOverrides = sessionEvents.filter(
113
123
  (e) => e.type === "override_applied"
114
124
  );
@@ -128,12 +138,10 @@ export function computeDriftScore(root, options = {}) {
128
138
  const midpoint = new Date(Date.now() - (days / 2) * 86400000).toISOString();
129
139
  const firstHalf = recentEvents.filter((e) => e.at < midpoint);
130
140
  const secondHalf = recentEvents.filter((e) => e.at >= midpoint);
131
- const firstViolations = firstHalf.filter(
132
- (e) => e.type === "conflict_blocked" || e.type === "conflict_warned"
133
- ).length;
134
- const secondViolations = secondHalf.filter(
135
- (e) => e.type === "conflict_blocked" || e.type === "conflict_warned"
136
- ).length;
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;
137
145
 
138
146
  let trend;
139
147
  if (firstViolations === 0 && secondViolations === 0) trend = "stable";
@@ -148,7 +156,7 @@ export function computeDriftScore(root, options = {}) {
148
156
  trend,
149
157
  period: `${days} days`,
150
158
  signals: {
151
- violations: { score: violationScore, max: 30, count: violations.length, total: checks.length },
159
+ violations: { score: violationScore, max: 30, count: totalViolations, total: totalChecks },
152
160
  overrides: { score: overrideScore, max: 20, count: overrides.length },
153
161
  reverts: { score: revertScore, max: 15, count: reverts.length },
154
162
  lockChurn: { score: churnScore, max: 15, removed: locksRemoved.length, added: locksAdded.length },
@@ -89,7 +89,7 @@
89
89
  <div class="header">
90
90
  <div>
91
91
  <h1><span>SpecLock</span> Dashboard</h1>
92
- <div class="meta">v5.4.0 &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.4.0 &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.4.0";
116
+ const VERSION = "5.4.1";
117
117
  const AUTHOR = "Sandeep Roy";
118
118
  const START_TIME = Date.now();
119
119
 
package/src/mcp/server.js CHANGED
@@ -125,7 +125,7 @@ const PROJECT_ROOT =
125
125
  args.project || process.env.SPECLOCK_PROJECT_ROOT || process.cwd();
126
126
 
127
127
  // --- MCP Server ---
128
- const VERSION = "5.4.0";
128
+ const VERSION = "5.4.1";
129
129
  const AUTHOR = "Sandeep Roy";
130
130
 
131
131
  const server = new McpServer(