speclock 5.2.1 → 5.2.3

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.2.1",
5
+ "version": "5.2.3",
6
6
 
7
7
  "description": "AI Constraint Engine — AI Patch Firewall. Diff-native review (interface breaks, protected symbols, dependency drift, schema changes, API impact), Patch Gateway (ALLOW/WARN/BLOCK verdicts), Spec Compiler (NL→constraints), Code Graph (blast radius, lock-to-file mapping), Typed constraints, REST API v2, Python SDK, ROS2 integration. 42 MCP tools, Gemini LLM hybrid, HMAC audit chain, RBAC, encryption, SOC 2/HIPAA compliance.",
8
8
 
package/src/cli/index.js CHANGED
@@ -117,7 +117,7 @@ function refreshContext(root) {
117
117
 
118
118
  function printHelp() {
119
119
  console.log(`
120
- SpecLock v5.2.1 — AI Constraint Engine (Spec Compiler + Code Graph + Typed Constraints + Python SDK + ROS2 + REST API v2 + Gemini LLM + Policy-as-Code + Auth + RBAC + Encryption)
120
+ SpecLock v5.2.3 — AI Constraint Engine (Spec Compiler + Code Graph + Typed Constraints + Python SDK + ROS2 + REST API v2 + Gemini LLM + Policy-as-Code + Auth + RBAC + Encryption)
121
121
  Developed by Sandeep Roy (github.com/sgroy10)
122
122
 
123
123
  Usage: speclock <command> [options]
@@ -1102,6 +1102,135 @@ Tip: When starting a new chat, tell the AI:
1102
1102
  return;
1103
1103
  }
1104
1104
 
1105
+ // --- RELEASE: Automated version bump + publish + deploy ---
1106
+ if (cmd === "release") {
1107
+ const bump = args[0]; // "patch", "minor", or "major"
1108
+ if (!bump || !["patch", "minor", "major"].includes(bump)) {
1109
+ console.log("Usage: speclock release <patch|minor|major>");
1110
+ console.log(" Bumps version in ALL files, commits, pushes, npm publishes, deploys.");
1111
+ process.exit(1);
1112
+ }
1113
+
1114
+ const { execSync } = await import("child_process");
1115
+ const fs = await import("fs");
1116
+
1117
+ // Read current version
1118
+ const pkgPath = path.join(root, "package.json");
1119
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
1120
+ const [major, minor, patch] = pkg.version.split(".").map(Number);
1121
+ let newVersion;
1122
+ if (bump === "patch") newVersion = `${major}.${minor}.${patch + 1}`;
1123
+ else if (bump === "minor") newVersion = `${major}.${minor + 1}.0`;
1124
+ else newVersion = `${major + 1}.0.0`;
1125
+
1126
+ console.log(`\n Releasing v${newVersion} (was ${pkg.version})\n`);
1127
+
1128
+ // Step 1: Version bump in all 7 files
1129
+ const VERSION_FILES = [
1130
+ { file: "package.json", pattern: `"version": "${pkg.version}"`, replacement: `"version": "${newVersion}"` },
1131
+ { file: "src/mcp/http-server.js", pattern: `const VERSION = "${pkg.version}"`, replacement: `const VERSION = "${newVersion}"` },
1132
+ { file: "src/mcp/server.js", pattern: `const VERSION = "${pkg.version}"`, replacement: `const VERSION = "${newVersion}"` },
1133
+ { file: "src/core/compliance.js", pattern: `const VERSION = "${pkg.version}"`, replacement: `const VERSION = "${newVersion}"` },
1134
+ { file: "src/cli/index.js", pattern: `SpecLock v${pkg.version}`, replacement: `SpecLock v${newVersion}` },
1135
+ { file: "src/dashboard/index.html", pattern: `v${pkg.version}`, replacement: `v${newVersion}` },
1136
+ ];
1137
+
1138
+ let filesUpdated = 0;
1139
+ for (const { file, pattern, replacement } of VERSION_FILES) {
1140
+ const filePath = path.join(root, file);
1141
+ if (!fs.existsSync(filePath)) {
1142
+ console.log(` SKIP ${file} (not found)`);
1143
+ continue;
1144
+ }
1145
+ const content = fs.readFileSync(filePath, "utf8");
1146
+ const updated = content.replaceAll(pattern, replacement);
1147
+ if (updated !== content) {
1148
+ fs.writeFileSync(filePath, updated);
1149
+ const count = (content.split(pattern).length - 1);
1150
+ console.log(` DONE ${file} (${count} replacement${count > 1 ? "s" : ""})`);
1151
+ filesUpdated++;
1152
+ } else {
1153
+ console.log(` SKIP ${file} (pattern not found)`);
1154
+ }
1155
+ }
1156
+ console.log(`\n ${filesUpdated} files updated to v${newVersion}\n`);
1157
+
1158
+ // Step 2: Git commit + push
1159
+ console.log(" Committing...");
1160
+ try {
1161
+ execSync(`git add -A`, { cwd: root, stdio: "pipe" });
1162
+ execSync(`git commit -m "v${newVersion}"`, { cwd: root, stdio: "pipe" });
1163
+ console.log(" DONE git commit");
1164
+ } catch (e) {
1165
+ console.error(" FAIL git commit:", e.message);
1166
+ process.exit(1);
1167
+ }
1168
+
1169
+ console.log(" Pushing...");
1170
+ try {
1171
+ execSync(`git push origin main`, { cwd: root, stdio: "pipe" });
1172
+ console.log(" DONE git push");
1173
+ } catch (e) {
1174
+ console.error(" FAIL git push:", e.message);
1175
+ }
1176
+
1177
+ // Step 3: npm publish
1178
+ console.log(" Publishing to npm...");
1179
+ try {
1180
+ execSync(`npm publish`, { cwd: root, stdio: "pipe" });
1181
+ console.log(" DONE npm publish speclock@" + newVersion);
1182
+ } catch (e) {
1183
+ console.error(" FAIL npm publish:", e.message);
1184
+ }
1185
+
1186
+ // Step 4: Git tag
1187
+ console.log(" Tagging...");
1188
+ try {
1189
+ execSync(`git tag v${newVersion}`, { cwd: root, stdio: "pipe" });
1190
+ execSync(`git push origin v${newVersion}`, { cwd: root, stdio: "pipe" });
1191
+ console.log(` DONE git tag v${newVersion}`);
1192
+ } catch (e) {
1193
+ console.error(" FAIL git tag:", e.message);
1194
+ }
1195
+
1196
+ // Step 5: Railway deploy
1197
+ console.log(" Deploying to Railway...");
1198
+ try {
1199
+ execSync(`railway up`, { cwd: root, stdio: "pipe", timeout: 120000 });
1200
+ console.log(" DONE railway up");
1201
+ } catch (e) {
1202
+ console.log(" WARN railway up (may need manual deploy):", e.message?.slice(0, 100));
1203
+ }
1204
+
1205
+ // Step 6: Verify
1206
+ console.log("\n Verifying...");
1207
+ try {
1208
+ const health = execSync(`curl -s https://speclock-mcp-production.up.railway.app/health`, { timeout: 15000 }).toString();
1209
+ const parsed = JSON.parse(health);
1210
+ if (parsed.version === newVersion) {
1211
+ console.log(` DONE Railway health: v${parsed.version} (${parsed.tools} tools)`);
1212
+ } else {
1213
+ console.log(` WARN Railway shows v${parsed.version}, expected v${newVersion} (may need a moment)`);
1214
+ }
1215
+ } catch (e) {
1216
+ console.log(" WARN Health check failed (Railway may still be deploying)");
1217
+ }
1218
+
1219
+ try {
1220
+ const npmVer = execSync(`npm view speclock version`, { timeout: 10000 }).toString().trim();
1221
+ if (npmVer === newVersion) {
1222
+ console.log(` DONE npm: speclock@${npmVer}`);
1223
+ } else {
1224
+ console.log(` WARN npm shows ${npmVer}, expected ${newVersion} (cache delay)`);
1225
+ }
1226
+ } catch (e) {
1227
+ console.log(" WARN npm check failed");
1228
+ }
1229
+
1230
+ console.log(`\n Release v${newVersion} complete.\n`);
1231
+ return;
1232
+ }
1233
+
1105
1234
  console.error(`Unknown command: ${cmd}`);
1106
1235
  console.error("Run 'speclock --help' for usage.");
1107
1236
  process.exit(1);
@@ -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.2.1";
12
+ const VERSION = "5.2.3";
13
13
 
14
14
  // PHI-related keywords for HIPAA filtering
15
15
  const PHI_KEYWORDS = [
@@ -749,6 +749,43 @@ export const CONCEPT_MAP = {
749
749
  // Telecom / billing
750
750
  "call records": ["cdr", "call data", "telecom records", "billing records"],
751
751
  "subscriber data": ["customer data", "user data", "telecom records"],
752
+
753
+ // Frontend frameworks (alternatives = change framework conflict)
754
+ "react": ["frontend framework", "ui framework", "vue", "angular",
755
+ "svelte", "sveltekit", "next.js", "nextjs"],
756
+ "vue": ["frontend framework", "ui framework", "react", "angular",
757
+ "svelte", "sveltekit", "nuxt"],
758
+ "vue 3": ["frontend framework", "ui framework", "react", "angular",
759
+ "svelte", "sveltekit", "nuxt", "vue"],
760
+ "vue.js": ["frontend framework", "ui framework", "react", "angular",
761
+ "svelte", "sveltekit", "nuxt", "vue"],
762
+ "svelte": ["frontend framework", "ui framework", "react", "vue",
763
+ "angular", "sveltekit"],
764
+ "sveltekit": ["frontend framework", "ui framework", "react", "vue",
765
+ "angular", "svelte", "next.js", "nuxt"],
766
+ "angular": ["frontend framework", "ui framework", "react", "vue",
767
+ "svelte", "sveltekit"],
768
+ "next.js": ["frontend framework", "ui framework", "react", "nuxt",
769
+ "sveltekit", "nextjs"],
770
+ "nextjs": ["frontend framework", "ui framework", "react", "nuxt",
771
+ "sveltekit", "next.js"],
772
+ "nuxt": ["frontend framework", "ui framework", "vue", "next.js",
773
+ "nextjs", "sveltekit"],
774
+ "frontend framework":["react", "vue", "angular", "svelte", "sveltekit",
775
+ "ui framework", "next.js", "nuxt"],
776
+ "ui framework": ["frontend framework", "react", "vue", "angular",
777
+ "svelte", "sveltekit"],
778
+
779
+ // Backend frameworks (alternatives = change backend conflict)
780
+ "express": ["backend framework", "fastify", "koa", "hapi", "nestjs"],
781
+ "fastify": ["backend framework", "express", "koa", "hapi", "nestjs"],
782
+ "django": ["backend framework", "flask", "fastapi", "rails"],
783
+ "flask": ["backend framework", "django", "fastapi"],
784
+ "fastapi": ["backend framework", "django", "flask"],
785
+ "rails": ["backend framework", "django", "laravel", "ruby on rails"],
786
+ "laravel": ["backend framework", "rails", "django", "symfony"],
787
+ "spring": ["backend framework", "spring boot", "java framework"],
788
+ "nestjs": ["backend framework", "express", "fastify"],
752
789
  };
753
790
 
754
791
  // ===================================================================
@@ -2160,6 +2197,45 @@ export function scoreConflict({ actionText, lockText }) {
2160
2197
  }
2161
2198
  }
2162
2199
 
2200
+ // Check 3d: Non-destructive sub-activities against ANY prohibitive lock
2201
+ // These patterns are inherently safe regardless of subject overlap:
2202
+ // "Write tests for X" → testing never modifies the system
2203
+ // "Update X library version" → version bumps are maintenance
2204
+ // "Add validation to X" → adding safety checks is constructive
2205
+ // "Optimize X queries/performance" → performance tuning ≠ schema change
2206
+ //
2207
+ // GUARD: compound sentences may hide destructive ops behind safe prefixes.
2208
+ // "Optimize DB performance by moving data to unencrypted replica" is NOT safe.
2209
+ // Skip safe-intent if text also contains clearly destructive/insecure content.
2210
+ if (!intentAligned && lockIsProhibitive) {
2211
+ const _actionLowerSafe = actionText.toLowerCase();
2212
+ const _compoundDestructive = /\b(?:unencrypted|plaintext|without\s+encryption|without\s+auth|unsigned|untrusted|insecure|delet(?:e|ing)|remov(?:e|ing)|drop(?:ping)?|destroy|purg(?:e|ing)|wip(?:e|ing)|eras(?:e|ing)|bypass|disabl(?:e|ing)|expos(?:e|ing)|leak|truncat(?:e|ing)|nuk(?:e|ing))\b/i.test(_actionLowerSafe);
2213
+
2214
+ // Pattern 1: Writing/creating/running tests
2215
+ if (!_compoundDestructive && /\b(?:write|create|add|run)\s+(?:unit\s+|integration\s+|e2e\s+|end-to-end\s+)?tests?\b/i.test(_actionLowerSafe)) {
2216
+ intentAligned = true;
2217
+ reasons.push("intent alignment: writing/running tests is non-destructive — does not modify locked system");
2218
+ }
2219
+
2220
+ // Pattern 2: Updating library/client/package version
2221
+ if (!intentAligned && !_compoundDestructive && /\b(?:update|upgrade|bump)\s+\S+\s+(?:library|client|package|dependency|sdk|version)\b/i.test(_actionLowerSafe)) {
2222
+ intentAligned = true;
2223
+ reasons.push("intent alignment: updating library/client version is maintenance — does not modify locked system");
2224
+ }
2225
+
2226
+ // Pattern 3: Adding validation/sanitization (safety improvement)
2227
+ if (!intentAligned && !_compoundDestructive && /\b(?:add|implement|create)\s+(?:input\s+)?(?:validation|sanitization|sanitizing|input\s+checks?)\b/i.test(_actionLowerSafe)) {
2228
+ intentAligned = true;
2229
+ reasons.push("intent alignment: adding validation/sanitization is a safety improvement — does not modify locked system");
2230
+ }
2231
+
2232
+ // Pattern 4: Optimizing queries/performance (non-structural)
2233
+ if (!intentAligned && !_compoundDestructive && /\boptimize\s+\S+\s+(?:query|queries|performance|speed|latency|throughput)\b/i.test(_actionLowerSafe)) {
2234
+ intentAligned = true;
2235
+ reasons.push("intent alignment: optimizing queries/performance is non-destructive — does not modify locked schema/system");
2236
+ }
2237
+ }
2238
+
2163
2239
  // Check 3c: Working WITH locked technology (not replacing it)
2164
2240
  // "Update the Stripe UI components" vs "must always use Stripe" → working WITH Stripe → safe
2165
2241
  // "Update the Stripe payment UI" vs "Stripe API keys must never be exposed" → different subject → safe
@@ -2407,6 +2483,7 @@ const QUESTION_PREFIXES = [
2407
2483
  /^is\s+(?:it\s+)?(?:a\s+)?(?:good\s+idea\s+)?(?:to\s+)?/i,
2408
2484
  /^let\s+me\s+/i,
2409
2485
  /^we\s+should\s+(?:probably\s+)?(?:consider\s+)?(?:look\s+at\s+)?/i,
2486
+ /^how\s+hard\s+(?:would|will|could)\s+it\s+be\s+to\s+/i,
2410
2487
  /^explore\s+(?:using\s+)?/i,
2411
2488
  ];
2412
2489
 
@@ -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.2.1 &mdash; AI Constraint Engine</div>
92
+ <div class="meta">v5.2.3 &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.2.1 &mdash; Developed by Sandeep Roy &mdash; <a href="https://github.com/sgroy10/speclock" style="color:var(--accent)">GitHub</a>
185
+ SpecLock v5.2.3 &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.2.1";
116
+ const VERSION = "5.2.3";
117
117
  const AUTHOR = "Sandeep Roy";
118
118
  const START_TIME = Date.now();
119
119
 
package/src/mcp/server.js CHANGED
@@ -120,7 +120,7 @@ const PROJECT_ROOT =
120
120
  args.project || process.env.SPECLOCK_PROJECT_ROOT || process.cwd();
121
121
 
122
122
  // --- MCP Server ---
123
- const VERSION = "5.2.1";
123
+ const VERSION = "5.2.3";
124
124
  const AUTHOR = "Sandeep Roy";
125
125
 
126
126
  const server = new McpServer(