speclock 3.5.0 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speclock",
3
- "version": "3.5.0",
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",
@@ -387,13 +387,31 @@ export const TEMPORAL_MODIFIERS = [
387
387
  // ===================================================================
388
388
 
389
389
  const STOPWORDS = new Set([
390
- "the", "this", "that", "with", "from", "for", "are", "was", "were",
391
- "been", "being", "have", "has", "had", "will", "would", "could",
392
- "should", "may", "might", "shall", "can", "need", "must", "all",
393
- "any", "every", "some", "most", "other", "each", "both", "few",
394
- "more", "before", "after", "during", "while", "about", "into",
395
- "over", "under", "between", "through", "its", "our", "their",
396
- "your", "also", "just", "very", "too", "really", "quite",
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. Check for intent alignment BEFORE adding negation/intent bonuses
944
- // This is the KEY false-positive prevention step.
945
- const hasAnyMatch = directOverlap.length > 0 || synonymMatches.length > 0 ||
946
- euphemismMatches.length > 0 || conceptMatches.length > 0 || phraseOverlap.length > 0;
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
- if (prohibitedIsNegative && !actionIntent.negated) {
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
- // Constructive
987
- "enable", "activate", "add", "create", "implement",
988
- "upgrade", "improve", "enhance", "strengthen", "harden",
989
- "restore", "recover", "repair", "fix",
990
- "maintain", "preserve", "comply",
991
- "encrypt",
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 && hasAnyMatch) {
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" && hasAnyMatch) {
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 && hasAnyMatch) {
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
  }
@@ -436,7 +436,7 @@ function createSpecLockServer() {
436
436
  const app = createMcpExpressApp({ host: "0.0.0.0" });
437
437
 
438
438
  // CORS preflight handler
439
- app.options("*", (req, res) => {
439
+ app.options("/{*path}", (req, res) => {
440
440
  setCorsHeaders(res);
441
441
  res.writeHead(204).end();
442
442
  });
@@ -597,8 +597,8 @@ app.get("/", (req, res) => {
597
597
  name: "speclock",
598
598
  version: VERSION,
599
599
  author: AUTHOR,
600
- description: "AI Continuity Engine with enterprise audit, compliance, and enforcement",
601
- tools: 28,
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
  // ========================================