speclock 3.5.1 → 3.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 +1 -1
- package/src/core/semantics.js +84 -28
- package/src/mcp/http-server.js +40 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "speclock",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.2",
|
|
4
4
|
"description": "AI constraint engine with Policy-as-Code DSL, OAuth/OIDC SSO, admin dashboard, telemetry, API key auth, RBAC, AES-256-GCM encryption, hard enforcement, semantic pre-commit, HMAC audit chain, SOC 2/HIPAA compliance. 100% detection, 0% false positives. 31 MCP tools + CLI. Enterprise platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/mcp/server.js",
|
package/src/core/semantics.js
CHANGED
|
@@ -387,13 +387,31 @@ export const TEMPORAL_MODIFIERS = [
|
|
|
387
387
|
// ===================================================================
|
|
388
388
|
|
|
389
389
|
const STOPWORDS = new Set([
|
|
390
|
-
|
|
391
|
-
"
|
|
392
|
-
"
|
|
393
|
-
|
|
394
|
-
"
|
|
395
|
-
"
|
|
396
|
-
"
|
|
390
|
+
// Articles & pronouns
|
|
391
|
+
"a", "an", "the", "this", "that", "it", "its", "our", "their",
|
|
392
|
+
"your", "my", "his", "her", "we", "they", "them", "i",
|
|
393
|
+
// Prepositions & conjunctions
|
|
394
|
+
"to", "of", "in", "on", "at", "by", "up", "as", "or", "and",
|
|
395
|
+
"nor", "but", "so", "if", "no", "not", "is", "be", "do", "did",
|
|
396
|
+
"with", "from", "for", "into", "over", "under", "between", "through",
|
|
397
|
+
"about", "before", "after", "during", "while",
|
|
398
|
+
// Auxiliary verbs & common verbs
|
|
399
|
+
"are", "was", "were", "been", "being", "have", "has", "had",
|
|
400
|
+
"will", "would", "could", "should", "may", "might", "shall",
|
|
401
|
+
"can", "need", "must", "does", "done",
|
|
402
|
+
// Quantifiers & adjectives
|
|
403
|
+
"all", "any", "every", "some", "most", "other", "each", "both",
|
|
404
|
+
"few", "more", "less", "many", "much",
|
|
405
|
+
// Adverbs
|
|
406
|
+
"also", "just", "very", "too", "really", "quite", "only", "then",
|
|
407
|
+
"now", "here", "there", "when", "where", "how", "what", "which",
|
|
408
|
+
"who", "whom", "why",
|
|
409
|
+
// Common generic nouns (too vague to be meaningful in conflict matching)
|
|
410
|
+
"system", "page", "app", "application", "project", "code", "file",
|
|
411
|
+
"files", "data", "way", "thing", "things", "part", "set", "use",
|
|
412
|
+
"using", "used", "make", "made", "new", "get", "got",
|
|
413
|
+
"module", "component", "service", "feature", "function", "method",
|
|
414
|
+
"class", "type", "model", "view", "controller", "handler",
|
|
397
415
|
]);
|
|
398
416
|
|
|
399
417
|
// ===================================================================
|
|
@@ -695,6 +713,9 @@ export function expandSemantics(tokens) {
|
|
|
695
713
|
for (const token of tokens) {
|
|
696
714
|
const t = token.toLowerCase();
|
|
697
715
|
|
|
716
|
+
// Skip stopwords — they shouldn't trigger synonym/euphemism/concept expansions
|
|
717
|
+
if (STOPWORDS.has(t)) continue;
|
|
718
|
+
|
|
698
719
|
// Synonym group expansion
|
|
699
720
|
for (const group of SYNONYM_GROUPS) {
|
|
700
721
|
if (group.includes(t)) {
|
|
@@ -940,10 +961,41 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
940
961
|
reasons.push(`concept match: ${conceptMatches.slice(0, 2).join("; ")}`);
|
|
941
962
|
}
|
|
942
963
|
|
|
943
|
-
// 6.
|
|
944
|
-
//
|
|
945
|
-
|
|
946
|
-
|
|
964
|
+
// 6. Subject relevance gate — prevent false positives where only verb-level
|
|
965
|
+
// matches exist (euphemism/synonym on verbs) but the subjects are different.
|
|
966
|
+
// "Optimize images" should NOT conflict with "Do not modify calculateShipping"
|
|
967
|
+
// because the subjects (images vs shipping function) don't overlap.
|
|
968
|
+
//
|
|
969
|
+
// However, subject-level synonyms like "content safety" → "CSAM detection"
|
|
970
|
+
// should still count as subject relevance (same concept, different words).
|
|
971
|
+
const ACTION_VERBS_SET = new Set([
|
|
972
|
+
"modify", "change", "alter", "update", "delete", "remove", "add", "create",
|
|
973
|
+
"disable", "enable", "replace", "swap", "switch", "move", "migrate",
|
|
974
|
+
"install", "uninstall", "deploy", "rewrite", "revise", "restructure",
|
|
975
|
+
"refactor", "clean", "purge", "wipe", "drop", "kill", "destroy",
|
|
976
|
+
"reduce", "simplify", "fix", "repair", "restore", "recover", "break",
|
|
977
|
+
"expose", "hide", "connect", "disconnect", "merge", "split", "truncate",
|
|
978
|
+
"bypass", "skip", "ignore", "override", "adjust", "tweak", "tune",
|
|
979
|
+
]);
|
|
980
|
+
|
|
981
|
+
// Check if any synonym/concept match involves a non-verb term (= subject match)
|
|
982
|
+
const hasSynonymSubjectMatch = synonymMatches.some(m => {
|
|
983
|
+
// Format: "term → expansion" — check if expansion is not a common verb
|
|
984
|
+
const parts = m.split(" → ");
|
|
985
|
+
const expansion = (parts[1] || "").trim();
|
|
986
|
+
return !ACTION_VERBS_SET.has(expansion);
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
const hasSubjectMatch = directOverlap.length > 0 || phraseOverlap.length > 0 ||
|
|
990
|
+
conceptMatches.length > 0 || hasSynonymSubjectMatch;
|
|
991
|
+
const hasAnyMatch = hasSubjectMatch || synonymMatches.length > 0 ||
|
|
992
|
+
euphemismMatches.length > 0;
|
|
993
|
+
|
|
994
|
+
// If the ONLY matches are verb-level (euphemism/synonym) with no subject
|
|
995
|
+
// overlap, drastically reduce the score — these are likely false positives
|
|
996
|
+
if (!hasSubjectMatch && (synonymMatches.length > 0 || euphemismMatches.length > 0)) {
|
|
997
|
+
score = Math.floor(score * 0.15);
|
|
998
|
+
}
|
|
947
999
|
|
|
948
1000
|
const prohibitedVerb = extractProhibitedVerb(lockText);
|
|
949
1001
|
const actionPrimaryVerb = extractPrimaryVerb(actionText);
|
|
@@ -961,10 +1013,13 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
961
1013
|
}
|
|
962
1014
|
|
|
963
1015
|
// Check 2: Positive action intent against a lock that prohibits a negative action
|
|
1016
|
+
// ONLY applies when there are no euphemism/synonym matches suggesting the
|
|
1017
|
+
// action is actually destructive despite sounding positive (e.g., "reseed" → "reset")
|
|
964
1018
|
if (!intentAligned && lockIsProhibitive && actionIntent.intent === "positive" && prohibitedVerb) {
|
|
965
1019
|
const prohibitedIsNegative = NEGATIVE_INTENT_MARKERS.some(m =>
|
|
966
1020
|
prohibitedVerb === m || prohibitedVerb.startsWith(m));
|
|
967
|
-
|
|
1021
|
+
const hasEuphemismOrSynonymMatch = euphemismMatches.length > 0 || synonymMatches.length > 0;
|
|
1022
|
+
if (prohibitedIsNegative && !actionIntent.negated && !hasEuphemismOrSynonymMatch) {
|
|
968
1023
|
intentAligned = true;
|
|
969
1024
|
reasons.push(
|
|
970
1025
|
`intent alignment: positive action "${actionPrimaryVerb}" against ` +
|
|
@@ -976,19 +1031,20 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
976
1031
|
// the prohibited operation — even if they share subject nouns
|
|
977
1032
|
if (!intentAligned && lockIsProhibitive && actionPrimaryVerb) {
|
|
978
1033
|
const SAFE_ACTION_VERBS = new Set([
|
|
979
|
-
// Read-only / observational
|
|
1034
|
+
// Read-only / observational — these NEVER modify the system
|
|
980
1035
|
"read", "view", "inspect", "review", "examine",
|
|
981
1036
|
"monitor", "observe", "watch", "check", "scan", "detect",
|
|
982
1037
|
"generate", "report", "document", "test",
|
|
983
|
-
// Security / verification
|
|
1038
|
+
// Security / verification — passive checking
|
|
984
1039
|
"verify", "validate", "confirm", "ensure", "enforce",
|
|
985
1040
|
"protect", "secure", "guard", "shield",
|
|
986
|
-
//
|
|
987
|
-
"enable", "activate",
|
|
988
|
-
|
|
989
|
-
"
|
|
990
|
-
"
|
|
991
|
-
|
|
1041
|
+
// Activation — enabling features/checks is observational
|
|
1042
|
+
"enable", "activate",
|
|
1043
|
+
// Preservation — maintaining state
|
|
1044
|
+
"maintain", "preserve", "comply", "encrypt",
|
|
1045
|
+
// NOTE: "add", "create", "implement", "improve", "enhance", "upgrade"
|
|
1046
|
+
// are NOT safe — they modify the target system. Adding to a locked area
|
|
1047
|
+
// IS a modification. Only truly read-only and activation verbs are safe.
|
|
992
1048
|
]);
|
|
993
1049
|
|
|
994
1050
|
const PROHIBITED_ACTION_VERBS = new Set([
|
|
@@ -1016,33 +1072,33 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
1016
1072
|
} else {
|
|
1017
1073
|
// NOT aligned — apply standard conflict bonuses
|
|
1018
1074
|
|
|
1019
|
-
// 7. Negation conflict bonus
|
|
1020
|
-
if (lockIsProhibitive &&
|
|
1075
|
+
// 7. Negation conflict bonus — requires subject match, not just verb-level matches
|
|
1076
|
+
if (lockIsProhibitive && hasSubjectMatch) {
|
|
1021
1077
|
score += SCORING.negationConflict;
|
|
1022
1078
|
reasons.push("lock prohibits this action (negation detected)");
|
|
1023
1079
|
}
|
|
1024
1080
|
|
|
1025
|
-
// 8. Intent conflict bonus
|
|
1026
|
-
if (lockIsProhibitive && actionIntent.intent === "negative" &&
|
|
1081
|
+
// 8. Intent conflict bonus — requires subject match
|
|
1082
|
+
if (lockIsProhibitive && actionIntent.intent === "negative" && hasSubjectMatch) {
|
|
1027
1083
|
score += SCORING.intentConflict;
|
|
1028
1084
|
reasons.push(
|
|
1029
1085
|
`intent conflict: action "${actionIntent.actionVerb}" ` +
|
|
1030
1086
|
`conflicts with lock prohibition`);
|
|
1031
1087
|
}
|
|
1032
1088
|
|
|
1033
|
-
// 9. Destructive action bonus
|
|
1089
|
+
// 9. Destructive action bonus — requires subject match
|
|
1034
1090
|
const DESTRUCTIVE = new Set(["remove", "delete", "drop", "destroy",
|
|
1035
1091
|
"kill", "purge", "wipe", "break", "disable", "truncate",
|
|
1036
1092
|
"erase", "nuke", "obliterate"]);
|
|
1037
1093
|
const actionIsDestructive = actionTokens.all.some(t => DESTRUCTIVE.has(t)) ||
|
|
1038
1094
|
actionIntent.intent === "negative";
|
|
1039
|
-
if (actionIsDestructive &&
|
|
1095
|
+
if (actionIsDestructive && hasSubjectMatch) {
|
|
1040
1096
|
score += SCORING.destructiveAction;
|
|
1041
1097
|
reasons.push("destructive action against locked constraint");
|
|
1042
1098
|
}
|
|
1043
1099
|
|
|
1044
|
-
// 10. Temporal evasion (BONUS, not reduction)
|
|
1045
|
-
if (hasTemporalMod && score > 0) {
|
|
1100
|
+
// 10. Temporal evasion (BONUS, not reduction) — requires subject match
|
|
1101
|
+
if (hasTemporalMod && score > 0 && hasSubjectMatch) {
|
|
1046
1102
|
score += SCORING.temporalEvasion;
|
|
1047
1103
|
reasons.push(`temporal modifier "${hasTemporalMod}" does NOT reduce severity`);
|
|
1048
1104
|
}
|
package/src/mcp/http-server.js
CHANGED
|
@@ -597,8 +597,8 @@ app.get("/", (req, res) => {
|
|
|
597
597
|
name: "speclock",
|
|
598
598
|
version: VERSION,
|
|
599
599
|
author: AUTHOR,
|
|
600
|
-
description: "AI
|
|
601
|
-
tools:
|
|
600
|
+
description: "AI Constraint Engine with Policy-as-Code DSL, OAuth/OIDC SSO, admin dashboard, telemetry, API key auth, RBAC, AES-256-GCM encryption, hard enforcement, semantic pre-commit, HMAC audit chain, SOC 2/HIPAA compliance. 31 MCP tools. Enterprise platform.",
|
|
601
|
+
tools: 31,
|
|
602
602
|
mcp_endpoint: "/mcp",
|
|
603
603
|
health_endpoint: "/health",
|
|
604
604
|
npm: "https://www.npmjs.com/package/speclock",
|
|
@@ -606,6 +606,44 @@ app.get("/", (req, res) => {
|
|
|
606
606
|
});
|
|
607
607
|
});
|
|
608
608
|
|
|
609
|
+
// Smithery server card for listing metadata
|
|
610
|
+
app.get("/.well-known/mcp/server-card.json", (req, res) => {
|
|
611
|
+
setCorsHeaders(res);
|
|
612
|
+
res.json({
|
|
613
|
+
name: "SpecLock",
|
|
614
|
+
version: VERSION,
|
|
615
|
+
description: "AI Constraint Engine — memory + enforcement for AI coding tools. Policy-as-Code DSL, OAuth/OIDC SSO, admin dashboard, telemetry, API key auth, RBAC, AES-256-GCM encryption, hard enforcement, semantic pre-commit, HMAC audit chain, SOC 2/HIPAA compliance. 100% detection, 0% false positives. 31 MCP tools + CLI. Works with Claude Code, Cursor, Windsurf, Cline, Bolt.new, Lovable.",
|
|
616
|
+
author: {
|
|
617
|
+
name: "Sandeep Roy",
|
|
618
|
+
url: "https://github.com/sgroy10",
|
|
619
|
+
},
|
|
620
|
+
repository: "https://github.com/sgroy10/speclock",
|
|
621
|
+
homepage: "https://sgroy10.github.io/speclock/",
|
|
622
|
+
license: "MIT",
|
|
623
|
+
capabilities: {
|
|
624
|
+
tools: 31,
|
|
625
|
+
categories: [
|
|
626
|
+
"Memory Management",
|
|
627
|
+
"Change Tracking",
|
|
628
|
+
"Constraint Enforcement",
|
|
629
|
+
"Git Integration",
|
|
630
|
+
"AI Intelligence",
|
|
631
|
+
"Templates & Reports",
|
|
632
|
+
"Compliance & Audit",
|
|
633
|
+
"Hard Enforcement",
|
|
634
|
+
"Policy-as-Code",
|
|
635
|
+
"Telemetry",
|
|
636
|
+
],
|
|
637
|
+
},
|
|
638
|
+
keywords: [
|
|
639
|
+
"ai-memory", "constraint-enforcement", "mcp", "policy-as-code",
|
|
640
|
+
"sso", "oauth", "rbac", "encryption", "audit", "compliance",
|
|
641
|
+
"soc2", "hipaa", "dashboard", "telemetry", "claude-code",
|
|
642
|
+
"cursor", "bolt-new", "lovable", "enterprise",
|
|
643
|
+
],
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
|
|
609
647
|
// ========================================
|
|
610
648
|
// DASHBOARD (v3.5)
|
|
611
649
|
// ========================================
|