speclock 5.2.0 → 5.2.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/README.md +115 -10
- package/package.json +1 -1
- package/src/cli/index.js +130 -1
- package/src/core/compliance.js +1 -1
- package/src/core/diff-analyzer.js +12 -1
- package/src/core/semantics.js +102 -5
- package/src/dashboard/index.html +2 -2
- package/src/mcp/http-server.js +53 -38
- package/src/mcp/server.js +1 -1
package/README.md
CHANGED
|
@@ -305,25 +305,118 @@ pip install speclock-sdk
|
|
|
305
305
|
```
|
|
306
306
|
|
|
307
307
|
```python
|
|
308
|
-
from speclock import
|
|
309
|
-
|
|
310
|
-
|
|
308
|
+
from speclock import SpecLock
|
|
309
|
+
|
|
310
|
+
sl = SpecLock(project_root=".")
|
|
311
|
+
|
|
312
|
+
# Check text constraints (semantic conflict detection)
|
|
313
|
+
result = sl.check_text("Switch database to MongoDB")
|
|
314
|
+
# → { has_conflict: True, conflicting_locks: [...] }
|
|
315
|
+
|
|
316
|
+
# Check typed constraints (numerical/range/state/temporal)
|
|
317
|
+
result = sl.check_typed(metric="speed_mps", value=3.5)
|
|
311
318
|
# → violation: speed exceeds 2.0 m/s limit
|
|
319
|
+
|
|
320
|
+
# Combined check (text + typed in one call)
|
|
321
|
+
result = sl.check(action="Increase speed", speed_mps=3.5)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Uses the same `.speclock/brain.json` as the Node.js MCP server — constraints stay in sync across all environments.
|
|
325
|
+
|
|
326
|
+
**ROS2 Guardian Node:** Real-time constraint enforcement for robots and autonomous systems.
|
|
327
|
+
|
|
328
|
+
```yaml
|
|
329
|
+
# config/constraints.yaml
|
|
330
|
+
constraints:
|
|
331
|
+
- type: range
|
|
332
|
+
metric: joint_position_rad
|
|
333
|
+
min: -3.14
|
|
334
|
+
max: 3.14
|
|
335
|
+
- type: numerical
|
|
336
|
+
metric: velocity_mps
|
|
337
|
+
operator: "<="
|
|
338
|
+
value: 2.0
|
|
339
|
+
- type: state
|
|
340
|
+
metric: system_mode
|
|
341
|
+
forbidden:
|
|
342
|
+
- from: emergency_stop
|
|
343
|
+
to: autonomous
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- Subscribes to `/joint_states`, `/cmd_vel`, `/speclock/state_transition`
|
|
347
|
+
- Publishes violations to `/speclock/violations`
|
|
348
|
+
- Triggers emergency stop via `/speclock/emergency_stop`
|
|
349
|
+
- Checks constraints on every incoming ROS2 message at configurable rate
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Patch Gateway (v5.1)
|
|
354
|
+
|
|
355
|
+
One API call gates every change. Takes a description + file list, returns ALLOW/WARN/BLOCK:
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
speclock_review_patch({
|
|
359
|
+
description: "Add social login to auth page",
|
|
360
|
+
files: ["src/auth/login.js"]
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
→ { verdict: "BLOCK", riskScore: 85,
|
|
364
|
+
reasons: [{ type: "semantic_conflict", lock: "Never modify auth" }],
|
|
365
|
+
blastRadius: { impactPercent: 28.3 },
|
|
366
|
+
summary: "BLOCKED. 1 constraint conflict. 12 files affected." }
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Combines semantic conflict detection + lock-to-file mapping + blast radius + typed constraint awareness into a single risk score (0-100).
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## AI Patch Firewall (v5.2)
|
|
374
|
+
|
|
375
|
+
Reviews actual diffs, not just descriptions. Catches things intent review misses:
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
POST /api/v2/gateway/review-diff
|
|
379
|
+
{
|
|
380
|
+
"description": "Remove password column",
|
|
381
|
+
"diff": "diff --git a/migrations/001.sql ..."
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
→ { verdict: "BLOCK",
|
|
385
|
+
reviewMode: "unified",
|
|
386
|
+
intentVerdict: "ALLOW", ← description alone looks safe
|
|
387
|
+
diffVerdict: "BLOCK", ← diff reveals destructive schema change
|
|
388
|
+
signals: {
|
|
389
|
+
schemaChange: { score: 12, isDestructive: true },
|
|
390
|
+
interfaceBreak: { score: 10 },
|
|
391
|
+
protectedSymbolEdit: { score: 8 },
|
|
392
|
+
dependencyDrift: { score: 5 },
|
|
393
|
+
publicApiImpact: { score: 0 }
|
|
394
|
+
},
|
|
395
|
+
recommendation: { action: "require_approval" } }
|
|
312
396
|
```
|
|
313
397
|
|
|
314
|
-
**
|
|
398
|
+
**Signal detection:** Interface breaks (removed/changed exports), protected symbol edits in locked zones, dependency drift (critical package add/remove), schema/migration destructive changes, public API route changes.
|
|
399
|
+
|
|
400
|
+
**Hard escalation rules:** Auto-BLOCK on destructive schema changes, removed API routes, protected symbol edits, or multiple critical findings — regardless of score.
|
|
401
|
+
|
|
402
|
+
**Unified review:** Merges intent (35%) + diff (65%), takes the stronger verdict. Falls back to intent-only when no diff is available.
|
|
315
403
|
|
|
316
404
|
---
|
|
317
405
|
|
|
318
|
-
## REST API v2
|
|
406
|
+
## REST API v2
|
|
319
407
|
|
|
320
|
-
Real-time constraint checking
|
|
408
|
+
Real-time constraint checking, patch review, and autonomous systems:
|
|
321
409
|
|
|
322
410
|
```bash
|
|
323
|
-
#
|
|
324
|
-
POST /api/v2/
|
|
411
|
+
# Patch Gateway (v5.1)
|
|
412
|
+
POST /api/v2/gateway/review { description, files, useLLM }
|
|
413
|
+
|
|
414
|
+
# AI Patch Firewall (v5.2)
|
|
415
|
+
POST /api/v2/gateway/review-diff { description, files, diff, options }
|
|
416
|
+
POST /api/v2/gateway/parse-diff { diff }
|
|
325
417
|
|
|
326
|
-
#
|
|
418
|
+
# Typed constraint checking
|
|
419
|
+
POST /api/v2/check-typed { metric, value, entity }
|
|
327
420
|
POST /api/v2/check-batch { checks: [...] }
|
|
328
421
|
|
|
329
422
|
# SSE streaming (real-time violations)
|
|
@@ -335,6 +428,7 @@ POST /api/v2/compiler/compile { text, autoApply }
|
|
|
335
428
|
# Code Graph
|
|
336
429
|
GET /api/v2/graph/blast-radius?file=src/core/memory.js
|
|
337
430
|
GET /api/v2/graph/lock-map
|
|
431
|
+
POST /api/v2/graph/build
|
|
338
432
|
```
|
|
339
433
|
|
|
340
434
|
---
|
|
@@ -436,6 +530,17 @@ GET /api/v2/graph/lock-map
|
|
|
436
530
|
|
|
437
531
|
</details>
|
|
438
532
|
|
|
533
|
+
<details>
|
|
534
|
+
<summary><b>Patch Gateway & AI Patch Firewall</b> — change review, diff analysis (v5.1/v5.2)</summary>
|
|
535
|
+
|
|
536
|
+
| Tool | What it does |
|
|
537
|
+
|------|-------------|
|
|
538
|
+
| `speclock_review_patch` | ALLOW/WARN/BLOCK verdict for proposed changes |
|
|
539
|
+
| `speclock_review_patch_diff` | Diff-native review with signal scoring + unified verdict |
|
|
540
|
+
| `speclock_parse_diff` | Parse unified diff into structured changes (debug/inspect) |
|
|
541
|
+
|
|
542
|
+
</details>
|
|
543
|
+
|
|
439
544
|
---
|
|
440
545
|
|
|
441
546
|
## CLI
|
|
@@ -611,4 +716,4 @@ Built by **[Sandeep Roy](https://github.com/sgroy10)**
|
|
|
611
716
|
|
|
612
717
|
---
|
|
613
718
|
|
|
614
|
-
<p align="center"><i>v5.
|
|
719
|
+
<p align="center"><i>v5.2.0 — 1073 tests, 99.4% pass rate, 42 MCP tools, Patch Gateway, AI Patch Firewall, Spec Compiler, Code Graph, Typed Constraints, Python SDK, ROS2, REST API v2. Because remembering isn't enough.</i></p>
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
"name": "speclock",
|
|
4
4
|
|
|
5
|
-
"version": "5.2.
|
|
5
|
+
"version": "5.2.2",
|
|
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.
|
|
120
|
+
SpecLock v5.2.2 — 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);
|
package/src/core/compliance.js
CHANGED
|
@@ -14,7 +14,7 @@ import { analyzeConflict } from "./semantics.js";
|
|
|
14
14
|
// --- Signal score caps (from ChatGPT spec) ---
|
|
15
15
|
|
|
16
16
|
const CAPS = {
|
|
17
|
-
semanticConflict:
|
|
17
|
+
semanticConflict: 45,
|
|
18
18
|
lockFileOverlap: 20,
|
|
19
19
|
blastRadius: 15,
|
|
20
20
|
typedConstraintRelevance: 10,
|
|
@@ -514,6 +514,17 @@ export function calculateVerdict(signals, reasons) {
|
|
|
514
514
|
hardBlockReason = "Multiple critical issues with high confidence.";
|
|
515
515
|
}
|
|
516
516
|
|
|
517
|
+
// Semantic conflict at HIGH confidence (>=0.7) should hard-block
|
|
518
|
+
// even if other signals are absent — the engine is certain this
|
|
519
|
+
// action violates a lock.
|
|
520
|
+
const highConfSemantic = reasons.filter(r =>
|
|
521
|
+
r.type === "semantic_conflict" && (r.confidence || 0) >= 0.7
|
|
522
|
+
);
|
|
523
|
+
if (highConfSemantic.length > 0) {
|
|
524
|
+
hardBlock = true;
|
|
525
|
+
hardBlockReason = `High-confidence semantic conflict: ${highConfSemantic[0].message || "action violates active lock"}.`;
|
|
526
|
+
}
|
|
527
|
+
|
|
517
528
|
// --- Determine verdict ---
|
|
518
529
|
let verdict;
|
|
519
530
|
if (hardBlock || riskScore >= 50) {
|
package/src/core/semantics.js
CHANGED
|
@@ -272,6 +272,10 @@ export const EUPHEMISM_MAP = {
|
|
|
272
272
|
"reconcile": ["modify", "adjust", "change", "alter"],
|
|
273
273
|
"reverse": ["undo", "revert", "modify", "change"],
|
|
274
274
|
"recalculate": ["modify", "change", "update", "alter"],
|
|
275
|
+
"recompute": ["modify", "change", "recalculate", "alter"],
|
|
276
|
+
"reprocess": ["override", "modify", "recalculate", "delete", "redo"],
|
|
277
|
+
"round up": ["modify", "tamper", "falsify", "alter", "inflate"],
|
|
278
|
+
"round down": ["modify", "tamper", "falsify", "alter", "deflate"],
|
|
275
279
|
"backdate": ["modify", "tamper", "falsify", "change"],
|
|
276
280
|
"rebalance": ["modify", "adjust", "change", "redistribute"],
|
|
277
281
|
"reclassify": ["modify", "change", "recategorize"],
|
|
@@ -319,7 +323,7 @@ export const EUPHEMISM_MAP = {
|
|
|
319
323
|
"bridge": ["connect", "link", "merge", "join"],
|
|
320
324
|
"segment": ["split", "separate", "isolate", "divide"],
|
|
321
325
|
"flatten": ["merge", "simplify", "restructure"],
|
|
322
|
-
"consolidate": ["merge", "combine", "reduce"],
|
|
326
|
+
"consolidate": ["merge", "combine", "reduce", "delete", "expose"],
|
|
323
327
|
"spin up": ["create", "deploy", "start"],
|
|
324
328
|
"spin down": ["delete", "remove", "stop"],
|
|
325
329
|
"tear down": ["delete", "remove", "destroy"],
|
|
@@ -349,6 +353,23 @@ export const EUPHEMISM_MAP = {
|
|
|
349
353
|
"scrape": ["extract", "collect", "harvest"],
|
|
350
354
|
"harvest": ["collect", "extract", "scrape"],
|
|
351
355
|
|
|
356
|
+
// Verification/compliance bypass euphemisms
|
|
357
|
+
"skip check": ["bypass", "disable", "remove"],
|
|
358
|
+
"skip verification": ["bypass", "disable", "remove"],
|
|
359
|
+
"streamline verification": ["bypass", "weaken", "remove", "skip"],
|
|
360
|
+
"streamline application": ["bypass", "skip", "remove"],
|
|
361
|
+
"streamline detection": ["weaken", "disable", "bypass"],
|
|
362
|
+
"for research purposes": ["excuse", "bypass", "unauthorized"],
|
|
363
|
+
"in dev": ["excuse", "bypass"],
|
|
364
|
+
"mock change": ["bypass", "modify", "test excuse"],
|
|
365
|
+
|
|
366
|
+
// Record manipulation/destruction euphemisms
|
|
367
|
+
"clean up records": ["delete", "remove", "destroy"],
|
|
368
|
+
"clean up old": ["delete", "remove", "purge"],
|
|
369
|
+
"archive and delete": ["delete", "remove", "destroy"],
|
|
370
|
+
"refresh timestamps": ["falsify", "tamper", "modify"],
|
|
371
|
+
"refresh inspection": ["falsify", "tamper", "modify"],
|
|
372
|
+
|
|
352
373
|
// Encryption euphemisms
|
|
353
374
|
"unencrypted": ["without encryption", "disable encryption", "no encryption", "plaintext"],
|
|
354
375
|
"plaintext": ["without encryption", "unencrypted", "no encryption"],
|
|
@@ -689,6 +710,45 @@ export const CONCEPT_MAP = {
|
|
|
689
710
|
"k8s": ["kubernetes", "cluster", "infrastructure",
|
|
690
711
|
"container orchestration"],
|
|
691
712
|
"cluster": ["kubernetes", "k8s", "infrastructure", "nodes"],
|
|
713
|
+
|
|
714
|
+
// Education / student records
|
|
715
|
+
"gpa": ["grades", "grade point", "academic record", "transcript"],
|
|
716
|
+
"grades": ["gpa", "academic record", "transcript", "marks", "scores"],
|
|
717
|
+
"transcript": ["grades", "gpa", "academic record", "student record"],
|
|
718
|
+
"financial aid": ["student loans", "scholarships", "grants", "student data", "student records"],
|
|
719
|
+
"student records": ["grades", "transcript", "enrollment", "academic data"],
|
|
720
|
+
"weighted averages": ["grades", "gpa", "academic calculation"],
|
|
721
|
+
|
|
722
|
+
// Government / benefits
|
|
723
|
+
"voter rolls": ["voter registration", "election records", "voter data"],
|
|
724
|
+
"citizen database": ["pii", "personal data", "government records", "citizen data"],
|
|
725
|
+
"benefit applications": ["claims", "welfare", "government benefits"],
|
|
726
|
+
"denied applications": ["rejected claims", "denied benefits", "denied requests"],
|
|
727
|
+
|
|
728
|
+
// Insurance / claims
|
|
729
|
+
"claims": ["insurance claims", "benefit claims", "applications"],
|
|
730
|
+
"denied claims": ["rejected claims", "denied applications"],
|
|
731
|
+
"cancelled applications": ["denied claims", "rejected applications", "voided applications"],
|
|
732
|
+
|
|
733
|
+
// Aerospace / aviation safety
|
|
734
|
+
"inspection records": ["safety records", "maintenance records", "compliance records"],
|
|
735
|
+
"discrepancy reports": ["safety reports", "incident reports", "audit findings"],
|
|
736
|
+
"black box": ["flight recorder", "flight data", "telemetry data", "safety data"],
|
|
737
|
+
"inspection timestamps": ["safety records", "maintenance dates", "compliance dates"],
|
|
738
|
+
|
|
739
|
+
// Gaming / virtual economy
|
|
740
|
+
"virtual currency": ["in-game currency", "game tokens", "game economy"],
|
|
741
|
+
"player data": ["user data", "gamer data", "player records", "pii"],
|
|
742
|
+
"player ips": ["ip addresses", "pii", "player data", "network data"],
|
|
743
|
+
"cheat detection": ["anti-cheat", "cheat prevention", "security", "game integrity"],
|
|
744
|
+
|
|
745
|
+
// Real estate / tenant screening
|
|
746
|
+
"background check": ["tenant screening", "verification", "due diligence"],
|
|
747
|
+
"tenant screening": ["background check", "credit check", "verification"],
|
|
748
|
+
|
|
749
|
+
// Telecom / billing
|
|
750
|
+
"call records": ["cdr", "call data", "telecom records", "billing records"],
|
|
751
|
+
"subscriber data": ["customer data", "user data", "telecom records"],
|
|
692
752
|
};
|
|
693
753
|
|
|
694
754
|
// ===================================================================
|
|
@@ -780,7 +840,9 @@ const NEGATIVE_INTENT_MARKERS = [
|
|
|
780
840
|
"clean up", "sunset", "retire", "phase out",
|
|
781
841
|
"decommission", "wind down", "take down",
|
|
782
842
|
"take offline", "pull the plug",
|
|
783
|
-
"streamline",
|
|
843
|
+
"streamline", "consolidate",
|
|
844
|
+
"round up", "round down", "reprocess", "recompute",
|
|
845
|
+
"falsify", "tamper",
|
|
784
846
|
].sort((a, b) => b.length - a.length);
|
|
785
847
|
|
|
786
848
|
// ===================================================================
|
|
@@ -1892,15 +1954,50 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
1892
1954
|
rawWordOverlap &&
|
|
1893
1955
|
euphemismMatches.some(m => m.includes(`euphemism for ${_prohibVerb}`));
|
|
1894
1956
|
|
|
1895
|
-
if
|
|
1957
|
+
// NEW: Check if euphemism maps to lock's prohibited verb even without
|
|
1958
|
+
// word overlap. Cross-domain attacks like "truncate audit_log" vs
|
|
1959
|
+
// "Never delete student records" have no shared nouns but the euphemism
|
|
1960
|
+
// still proves destructive intent matching the lock's prohibition.
|
|
1961
|
+
const _DESTRUCTIVE_VERBS = new Set([
|
|
1962
|
+
"delete", "remove", "destroy", "wipe", "purge", "erase",
|
|
1963
|
+
"disable", "bypass", "expose", "tamper", "falsify",
|
|
1964
|
+
"override", "leak", "steal", "skip", "weaken",
|
|
1965
|
+
]);
|
|
1966
|
+
const euphemismMatchesDestructiveProhibition = !euphemismMatchesProhibitedVerb &&
|
|
1967
|
+
_prohibVerb && _DESTRUCTIVE_VERBS.has(_prohibVerb) &&
|
|
1968
|
+
euphemismMatches.some(m => m.includes(`euphemism for ${_prohibVerb}`));
|
|
1969
|
+
|
|
1970
|
+
if (euphemismMatchesProhibitedVerb) {
|
|
1971
|
+
// Full bypass — euphemism matches prohibited verb AND has content overlap
|
|
1972
|
+
// No reduction applied (existing behavior)
|
|
1973
|
+
} else if (euphemismMatchesDestructiveProhibition) {
|
|
1974
|
+
// Euphemism maps to destructive prohibited verb but no subject overlap
|
|
1975
|
+
// Apply moderate reduction (not 0.15) — still suspicious enough to flag
|
|
1976
|
+
score = Math.floor(score * 0.50);
|
|
1977
|
+
reasons.push("scope gate softened: euphemism matches destructive prohibition without subject overlap");
|
|
1978
|
+
} else {
|
|
1896
1979
|
// NO subject match at all — verb-only match → heavy reduction
|
|
1897
1980
|
score = Math.floor(score * 0.15);
|
|
1898
1981
|
reasons.push("subject gate: no subject overlap — verb-only match, likely false positive");
|
|
1899
1982
|
}
|
|
1900
1983
|
} else if (hasVocabSubjectMatch && !hasScopeMatch && subjectComparison.lockSubjects.length > 0 && subjectComparison.actionSubjects.length > 0) {
|
|
1901
1984
|
// Vocabulary overlap exists but subjects point to DIFFERENT scopes
|
|
1902
|
-
|
|
1903
|
-
|
|
1985
|
+
// If euphemism maps to a destructive verb, soften the gate
|
|
1986
|
+
const _prohibVerb2 = extractProhibitedVerb(lockText);
|
|
1987
|
+
const _DESTRUCTIVE_VERBS2 = new Set([
|
|
1988
|
+
"delete", "remove", "destroy", "wipe", "purge", "erase",
|
|
1989
|
+
"disable", "bypass", "expose", "tamper", "falsify",
|
|
1990
|
+
"override", "leak", "steal", "skip", "weaken",
|
|
1991
|
+
]);
|
|
1992
|
+
const hasDestructiveEuphemism = _prohibVerb2 && _DESTRUCTIVE_VERBS2.has(_prohibVerb2) &&
|
|
1993
|
+
euphemismMatches.some(m => m.includes(`euphemism for ${_prohibVerb2}`));
|
|
1994
|
+
if (hasDestructiveEuphemism) {
|
|
1995
|
+
score = Math.floor(score * 0.55);
|
|
1996
|
+
reasons.push(`scope gate softened: destructive euphemism with different scope — lock targets "${subjectComparison.lockSubjects[0]}", action targets "${subjectComparison.actionSubjects[0]}"`);
|
|
1997
|
+
} else {
|
|
1998
|
+
score = Math.floor(score * 0.35);
|
|
1999
|
+
reasons.push(`scope gate: shared vocabulary but different scope — lock targets "${subjectComparison.lockSubjects[0]}", action targets "${subjectComparison.actionSubjects[0]}"`);
|
|
2000
|
+
}
|
|
1904
2001
|
}
|
|
1905
2002
|
|
|
1906
2003
|
const prohibitedVerb = extractProhibitedVerb(lockText);
|
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.2.
|
|
92
|
+
<div class="meta">v5.2.2 — 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.
|
|
185
|
+
SpecLock v5.2.2 — 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.2.
|
|
116
|
+
const VERSION = "5.2.2";
|
|
117
117
|
const AUTHOR = "Sandeep Roy";
|
|
118
118
|
const START_TIME = Date.now();
|
|
119
119
|
|
|
@@ -901,7 +901,7 @@ app.get("/", (req, res) => {
|
|
|
901
901
|
name: "speclock",
|
|
902
902
|
version: VERSION,
|
|
903
903
|
author: AUTHOR,
|
|
904
|
-
description: "AI Constraint Engine
|
|
904
|
+
description: "AI Constraint Engine — AI Patch Firewall. Patch Gateway (ALLOW/WARN/BLOCK verdicts), diff-native review (interface breaks, protected symbols, dependency drift, schema changes, API impact). Spec Compiler (NL→constraints), Code Graph (blast radius, lock-to-file mapping), Typed constraints, REST API v2, Python SDK + ROS2 integration. Policy-as-Code, RBAC, AES-256-GCM encryption, HMAC audit chain, SOC 2/HIPAA compliance. 42 MCP tools. 1073 tests, 99.4% accuracy.",
|
|
905
905
|
tools: 42,
|
|
906
906
|
mcp_endpoint: "/mcp",
|
|
907
907
|
health_endpoint: "/health",
|
|
@@ -913,45 +913,60 @@ app.get("/", (req, res) => {
|
|
|
913
913
|
// Smithery server card for listing metadata
|
|
914
914
|
app.get("/.well-known/mcp/server-card.json", (req, res) => {
|
|
915
915
|
setCorsHeaders(res);
|
|
916
|
+
// Smithery-compatible server card format (SEP-1649)
|
|
916
917
|
res.json({
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
author: {
|
|
921
|
-
name: "Sandeep Roy",
|
|
922
|
-
url: "https://github.com/sgroy10",
|
|
923
|
-
},
|
|
924
|
-
repository: "https://github.com/sgroy10/speclock",
|
|
925
|
-
homepage: "https://sgroy10.github.io/speclock/",
|
|
926
|
-
license: "MIT",
|
|
927
|
-
capabilities: {
|
|
928
|
-
tools: 42,
|
|
929
|
-
categories: [
|
|
930
|
-
"Memory Management",
|
|
931
|
-
"Change Tracking",
|
|
932
|
-
"Constraint Enforcement",
|
|
933
|
-
"Git Integration",
|
|
934
|
-
"AI Intelligence",
|
|
935
|
-
"Templates & Reports",
|
|
936
|
-
"Compliance & Audit",
|
|
937
|
-
"Hard Enforcement",
|
|
938
|
-
"Policy-as-Code",
|
|
939
|
-
"Telemetry",
|
|
940
|
-
"Typed Constraints",
|
|
941
|
-
"REST API v2",
|
|
942
|
-
"Real-Time Streaming",
|
|
943
|
-
"Robotics / ROS2",
|
|
944
|
-
"Python SDK",
|
|
945
|
-
],
|
|
918
|
+
serverInfo: {
|
|
919
|
+
name: "speclock",
|
|
920
|
+
version: VERSION,
|
|
946
921
|
},
|
|
947
|
-
|
|
948
|
-
"
|
|
949
|
-
"
|
|
950
|
-
"
|
|
951
|
-
"
|
|
952
|
-
"
|
|
953
|
-
"
|
|
922
|
+
tools: [
|
|
923
|
+
{ name: "speclock_init", description: "Initialize SpecLock in the current project directory.", inputSchema: { type: "object", properties: {} } },
|
|
924
|
+
{ name: "speclock_get_context", description: "THE KEY TOOL. Returns the full structured context pack.", inputSchema: { type: "object", properties: { format: { enum: ["markdown","json"], type: "string", default: "markdown" } } } },
|
|
925
|
+
{ name: "speclock_set_goal", description: "Set or update the project goal.", inputSchema: { type: "object", properties: { text: { type: "string", minLength: 1 } } } },
|
|
926
|
+
{ name: "speclock_add_lock", description: "Add a non-negotiable constraint (SpecLock).", inputSchema: { type: "object", properties: { text: { type: "string", minLength: 1 }, tags: { type: "array", items: { type: "string" }, default: [] }, source: { enum: ["user","agent"], type: "string", default: "agent" } } } },
|
|
927
|
+
{ name: "speclock_remove_lock", description: "Remove (deactivate) a SpecLock by its ID.", inputSchema: { type: "object", properties: { lockId: { type: "string", minLength: 1 } } } },
|
|
928
|
+
{ name: "speclock_add_decision", description: "Record an architectural or design decision.", inputSchema: { type: "object", properties: { text: { type: "string", minLength: 1 }, tags: { type: "array", items: { type: "string" }, default: [] }, source: { enum: ["user","agent"], type: "string", default: "agent" } } } },
|
|
929
|
+
{ name: "speclock_add_note", description: "Add a pinned note for reference.", inputSchema: { type: "object", properties: { text: { type: "string", minLength: 1 }, pinned: { type: "boolean", default: true } } } },
|
|
930
|
+
{ name: "speclock_set_deploy_facts", description: "Record deployment configuration facts.", inputSchema: { type: "object", properties: { provider: { type: "string" }, branch: { type: "string" }, url: { type: "string" }, autoDeploy: { type: "boolean" }, notes: { type: "string" } } } },
|
|
931
|
+
{ name: "speclock_log_change", description: "Manually log a significant change.", inputSchema: { type: "object", properties: { summary: { type: "string", minLength: 1 }, files: { type: "array", items: { type: "string" }, default: [] } } } },
|
|
932
|
+
{ name: "speclock_get_changes", description: "Get recent file changes tracked by SpecLock.", inputSchema: { type: "object", properties: { limit: { type: "integer", default: 20, minimum: 1, maximum: 100 } } } },
|
|
933
|
+
{ name: "speclock_get_events", description: "Get the event log, optionally filtered by type.", inputSchema: { type: "object", properties: { type: { type: "string" }, limit: { type: "integer", default: 50, minimum: 1, maximum: 200 }, since: { type: "string" } } } },
|
|
934
|
+
{ name: "speclock_check_conflict", description: "Check if a proposed action conflicts with any active SpecLock. In hard mode, blocks above threshold.", inputSchema: { type: "object", properties: { proposedAction: { type: "string", minLength: 1 } } } },
|
|
935
|
+
{ name: "speclock_session_briefing", description: "Start a new session and get a full briefing.", inputSchema: { type: "object", properties: { toolName: { enum: ["claude-code","cursor","codex","windsurf","cline","unknown"], type: "string", default: "unknown" } } } },
|
|
936
|
+
{ name: "speclock_session_summary", description: "End the current session and record what was accomplished.", inputSchema: { type: "object", properties: { summary: { type: "string", minLength: 1 } } } },
|
|
937
|
+
{ name: "speclock_checkpoint", description: "Create a named git tag checkpoint for easy rollback.", inputSchema: { type: "object", properties: { name: { type: "string", minLength: 1 } } } },
|
|
938
|
+
{ name: "speclock_repo_status", description: "Get current git repository status.", inputSchema: { type: "object", properties: {} } },
|
|
939
|
+
{ name: "speclock_suggest_locks", description: "AI-powered lock suggestions based on project patterns.", inputSchema: { type: "object", properties: {} } },
|
|
940
|
+
{ name: "speclock_detect_drift", description: "Scan recent changes for constraint violations.", inputSchema: { type: "object", properties: {} } },
|
|
941
|
+
{ name: "speclock_health", description: "Health check with completeness score and multi-agent timeline.", inputSchema: { type: "object", properties: {} } },
|
|
942
|
+
{ name: "speclock_apply_template", description: "Apply a pre-built constraint template (nextjs, react, express, supabase, stripe, security-hardened).", inputSchema: { type: "object", properties: { name: { type: "string" } } } },
|
|
943
|
+
{ name: "speclock_report", description: "Violation report — how many times SpecLock blocked changes.", inputSchema: { type: "object", properties: {} } },
|
|
944
|
+
{ name: "speclock_audit", description: "Audit staged files against active locks.", inputSchema: { type: "object", properties: {} } },
|
|
945
|
+
{ name: "speclock_verify_audit", description: "Verify the integrity of the HMAC audit chain.", inputSchema: { type: "object", properties: {} } },
|
|
946
|
+
{ name: "speclock_export_compliance", description: "Generate compliance reports (SOC 2, HIPAA, CSV).", inputSchema: { type: "object", properties: { format: { enum: ["soc2","hipaa","csv"], type: "string" } } } },
|
|
947
|
+
{ name: "speclock_set_enforcement", description: "Set enforcement mode: advisory (warn) or hard (block).", inputSchema: { type: "object", properties: { mode: { enum: ["advisory","hard"], type: "string" }, blockThreshold: { type: "integer", default: 70, minimum: 0, maximum: 100 } } } },
|
|
948
|
+
{ name: "speclock_override_lock", description: "Override a lock with justification. Logged to audit trail.", inputSchema: { type: "object", properties: { lockId: { type: "string", minLength: 1 }, action: { type: "string", minLength: 1 }, reason: { type: "string", minLength: 1 } } } },
|
|
949
|
+
{ name: "speclock_semantic_audit", description: "Semantic pre-commit: analyzes code changes vs locks.", inputSchema: { type: "object", properties: {} } },
|
|
950
|
+
{ name: "speclock_override_history", description: "Show lock override history.", inputSchema: { type: "object", properties: { lockId: { type: "string" } } } },
|
|
951
|
+
{ name: "speclock_policy_evaluate", description: "Evaluate policy-as-code rules against proposed actions.", inputSchema: { type: "object", properties: { files: { type: "array", items: { type: "string" } }, actionType: { type: "string" } } } },
|
|
952
|
+
{ name: "speclock_policy_manage", description: "Policy CRUD: list, add, remove policy rules.", inputSchema: { type: "object", properties: { action: { enum: ["list","add","remove"], type: "string" } } } },
|
|
953
|
+
{ name: "speclock_telemetry", description: "Opt-in usage analytics summary.", inputSchema: { type: "object", properties: { action: { enum: ["status","enable","disable","report"], type: "string" } } } },
|
|
954
|
+
{ name: "speclock_guard_file", description: "Add SPECLOCK-GUARD header to lock specific files.", inputSchema: { type: "object", properties: { file: { type: "string" }, lockId: { type: "string" } } } },
|
|
955
|
+
{ name: "speclock_auto_guard", description: "Auto-guard files related to lock keywords.", inputSchema: { type: "object", properties: {} } },
|
|
956
|
+
{ name: "speclock_add_typed_lock", description: "Add typed constraint (numerical/range/state/temporal).", inputSchema: { type: "object", properties: { constraintType: { enum: ["numerical","range","state","temporal"], type: "string" }, metric: { type: "string" }, operator: { type: "string" }, value: {}, unit: { type: "string" }, description: { type: "string" } } } },
|
|
957
|
+
{ name: "speclock_check_typed", description: "Check proposed values against typed constraints.", inputSchema: { type: "object", properties: { metric: { type: "string" }, value: {} } } },
|
|
958
|
+
{ name: "speclock_list_typed_locks", description: "List all typed constraints with current thresholds.", inputSchema: { type: "object", properties: {} } },
|
|
959
|
+
{ name: "speclock_update_threshold", description: "Update typed lock thresholds dynamically.", inputSchema: { type: "object", properties: { lockId: { type: "string" }, value: {}, operator: { type: "string" } } } },
|
|
960
|
+
{ name: "speclock_compile_spec", description: "Compile natural language (PRDs, READMEs) into structured constraints via Gemini Flash.", inputSchema: { type: "object", properties: { text: { type: "string" }, autoApply: { type: "boolean", default: false } } } },
|
|
961
|
+
{ name: "speclock_build_graph", description: "Build/refresh code dependency graph from imports (JS/TS/Python).", inputSchema: { type: "object", properties: {} } },
|
|
962
|
+
{ name: "speclock_blast_radius", description: "Calculate blast radius — transitive dependents, impact %, depth.", inputSchema: { type: "object", properties: { file: { type: "string" } } } },
|
|
963
|
+
{ name: "speclock_map_locks", description: "Map active locks to actual code files via the dependency graph.", inputSchema: { type: "object", properties: {} } },
|
|
964
|
+
{ name: "speclock_review_patch", description: "ALLOW/WARN/BLOCK verdict — combines semantic conflict + lock-file mapping + blast radius.", inputSchema: { type: "object", properties: { description: { type: "string" }, files: { type: "array", items: { type: "string" } } } } },
|
|
965
|
+
{ name: "speclock_review_patch_diff", description: "Diff-native review — parses actual diffs for interface breaks, protected symbols, dependency drift, schema changes.", inputSchema: { type: "object", properties: { description: { type: "string" }, diff: { type: "string" } } } },
|
|
966
|
+
{ name: "speclock_parse_diff", description: "Parse unified diff into structured changes — imports, exports, symbols, routes, schema detection.", inputSchema: { type: "object", properties: { diff: { type: "string" } } } },
|
|
954
967
|
],
|
|
968
|
+
resources: [],
|
|
969
|
+
prompts: [],
|
|
955
970
|
});
|
|
956
971
|
});
|
|
957
972
|
|
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.
|
|
123
|
+
const VERSION = "5.2.2";
|
|
124
124
|
const AUTHOR = "Sandeep Roy";
|
|
125
125
|
|
|
126
126
|
const server = new McpServer(
|