speclock 4.5.1 → 4.5.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/README.md +1 -1
- package/package.json +1 -1
- package/src/cli/index.js +1 -1
- package/src/core/compliance.js +1 -1
- package/src/core/memory.js +6 -10
- package/src/core/semantics.js +143 -8
- package/src/core/telemetry.js +1 -1
- package/src/dashboard/index.html +2 -2
- package/src/mcp/http-server.js +1 -1
- package/src/mcp/server.js +1 -1
package/README.md
CHANGED
|
@@ -457,4 +457,4 @@ Built by **[Sandeep Roy](https://github.com/sgroy10)**
|
|
|
457
457
|
|
|
458
458
|
---
|
|
459
459
|
|
|
460
|
-
<p align="center"><i>v4.
|
|
460
|
+
<p align="center"><i>v4.5.3 — 600+ tests, 31 MCP tools, 0 false positives, Gemini hybrid. 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": "4.5.
|
|
5
|
+
"version": "4.5.3",
|
|
6
6
|
|
|
7
7
|
"description": "AI constraint engine with Gemini LLM universal detection, 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. Cross-platform: MCP + direct API. 31 MCP tools + CLI. Enterprise platform.",
|
|
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 v4.5.
|
|
120
|
+
SpecLock v4.5.3 — AI Constraint Engine (Gemini LLM + Policy-as-Code + SSO + Dashboard + Telemetry + Auth + RBAC + Encryption)
|
|
121
121
|
Developed by Sandeep Roy (github.com/sgroy10)
|
|
122
122
|
|
|
123
123
|
Usage: speclock <command> [options]
|
package/src/core/compliance.js
CHANGED
package/src/core/memory.js
CHANGED
|
@@ -84,32 +84,28 @@ export function addLock(root, text, tags, source) {
|
|
|
84
84
|
const brain = ensureInit(root);
|
|
85
85
|
const lockId = newId("lock");
|
|
86
86
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
// Store the user's exact words — no rewriting.
|
|
88
|
+
// The semantic engine handles verb contamination via subject extraction
|
|
89
|
+
// and scope matching, so rewriting is no longer needed.
|
|
90
90
|
brain.specLock.items.unshift({
|
|
91
91
|
id: lockId,
|
|
92
|
-
text:
|
|
93
|
-
originalText: normResult.wasRewritten ? normResult.original : undefined,
|
|
92
|
+
text: text,
|
|
94
93
|
createdAt: nowIso(),
|
|
95
94
|
source: source || "user",
|
|
96
95
|
tags: tags || [],
|
|
97
96
|
active: true,
|
|
98
97
|
});
|
|
99
98
|
const eventId = newId("evt");
|
|
100
|
-
const rewriteNote = normResult.wasRewritten
|
|
101
|
-
? ` (auto-rewritten from: "${normResult.original.substring(0, 60)}")`
|
|
102
|
-
: "";
|
|
103
99
|
const event = {
|
|
104
100
|
eventId,
|
|
105
101
|
type: "lock_added",
|
|
106
102
|
at: nowIso(),
|
|
107
103
|
files: [],
|
|
108
|
-
summary: `Lock added: ${
|
|
104
|
+
summary: `Lock added: ${text.substring(0, 80)}`,
|
|
109
105
|
patchPath: "",
|
|
110
106
|
};
|
|
111
107
|
recordEvent(root, brain, event);
|
|
112
|
-
return { brain, lockId, rewritten:
|
|
108
|
+
return { brain, lockId, rewritten: false, rewriteReason: null };
|
|
113
109
|
}
|
|
114
110
|
|
|
115
111
|
export function removeLock(root, lockId) {
|
package/src/core/semantics.js
CHANGED
|
@@ -28,7 +28,7 @@ export const SYNONYM_GROUPS = [
|
|
|
28
28
|
|
|
29
29
|
// --- Modification actions ---
|
|
30
30
|
["change", "modify", "alter", "update", "mutate", "transform",
|
|
31
|
-
"rewrite", "revise", "amend", "adjust", "tweak"],
|
|
31
|
+
"rewrite", "revise", "amend", "adjust", "tweak", "touch", "tamper"],
|
|
32
32
|
["replace", "swap", "substitute", "switch", "exchange",
|
|
33
33
|
"override", "overwrite"],
|
|
34
34
|
["move", "relocate", "migrate", "transfer", "shift", "rearrange", "reorganize",
|
|
@@ -82,6 +82,16 @@ export const SYNONYM_GROUPS = [
|
|
|
82
82
|
["audit", "audit log", "audit trail", "logging", "log",
|
|
83
83
|
"monitoring", "observability", "telemetry", "tracking"],
|
|
84
84
|
|
|
85
|
+
// --- Auth providers ---
|
|
86
|
+
["auth0", "okta", "cognito", "keycloak", "supabase auth"],
|
|
87
|
+
|
|
88
|
+
// --- API keys & secrets ---
|
|
89
|
+
["api key", "api keys", "secret key", "secret keys", "publishable key",
|
|
90
|
+
"private key", "access key", "api secret", "api token",
|
|
91
|
+
"credentials", "credential"],
|
|
92
|
+
["frontend", "frontend code", "client-side", "client side",
|
|
93
|
+
"browser", "react state", "ui component"],
|
|
94
|
+
|
|
85
95
|
// --- Dependencies ---
|
|
86
96
|
["dependency", "package", "library", "module", "import", "require",
|
|
87
97
|
"vendor", "third-party"],
|
|
@@ -619,6 +629,30 @@ export const CONCEPT_MAP = {
|
|
|
619
629
|
"saml": ["sso", "oidc", "single sign-on", "authentication",
|
|
620
630
|
"identity provider"],
|
|
621
631
|
|
|
632
|
+
// API keys & secrets
|
|
633
|
+
"api key": ["api keys", "secret key", "api secret", "api token",
|
|
634
|
+
"credential", "publishable key", "access key",
|
|
635
|
+
"secret", "key", "frontend code", "expose"],
|
|
636
|
+
"api keys": ["api key", "secret key", "api secret", "credential",
|
|
637
|
+
"publishable key", "access key", "frontend code", "expose"],
|
|
638
|
+
"secret key": ["api key", "api secret", "credential", "secret",
|
|
639
|
+
"publishable key", "private key"],
|
|
640
|
+
"publishable key": ["api key", "public key", "stripe key", "client key",
|
|
641
|
+
"frontend", "credential"],
|
|
642
|
+
"secret": ["api key", "secret key", "credential", "api secret",
|
|
643
|
+
"private", "sensitive"],
|
|
644
|
+
"localstorage": ["client-side storage", "browser storage", "frontend",
|
|
645
|
+
"expose", "client-side"],
|
|
646
|
+
// Auth providers
|
|
647
|
+
"auth0": ["authentication", "auth", "identity provider", "sso",
|
|
648
|
+
"oauth", "supabase", "cognito", "okta", "keycloak"],
|
|
649
|
+
"cognito": ["authentication", "auth", "identity provider",
|
|
650
|
+
"auth0", "supabase", "okta"],
|
|
651
|
+
"okta": ["authentication", "auth", "identity provider", "sso",
|
|
652
|
+
"auth0", "supabase", "cognito"],
|
|
653
|
+
"keycloak": ["authentication", "auth", "identity provider", "sso",
|
|
654
|
+
"auth0", "supabase"],
|
|
655
|
+
|
|
622
656
|
// Networking
|
|
623
657
|
"websocket": ["socket", "real-time connection", "ws", "wss",
|
|
624
658
|
"socket connection"],
|
|
@@ -847,7 +881,16 @@ function buildKnownPhrases() {
|
|
|
847
881
|
const KNOWN_PHRASES = buildKnownPhrases();
|
|
848
882
|
|
|
849
883
|
export function tokenize(text) {
|
|
850
|
-
|
|
884
|
+
// Pre-process: split UPPER_CASE_ENV_VARS into component words
|
|
885
|
+
// "STRIPE_SECRET_KEY" → "STRIPE SECRET KEY"
|
|
886
|
+
// "process.env.STRIPE_KEY" → "process env STRIPE KEY"
|
|
887
|
+
let preprocessed = text.replace(/\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+\b/g, match =>
|
|
888
|
+
match.replace(/_/g, " ")
|
|
889
|
+
);
|
|
890
|
+
// Also handle process.env.X patterns
|
|
891
|
+
preprocessed = preprocessed.replace(/process\.env\./gi, "env ");
|
|
892
|
+
|
|
893
|
+
const lower = preprocessed.toLowerCase();
|
|
851
894
|
const phrases = [];
|
|
852
895
|
|
|
853
896
|
// Extract known multi-word phrases (greedy, longest first)
|
|
@@ -1167,6 +1210,7 @@ const NEUTRAL_ACTION_VERBS = [
|
|
|
1167
1210
|
"replace", "swap", "switch", "migrate", "transition", "substitute",
|
|
1168
1211
|
"touch", "mess", "configure", "optimize", "tweak",
|
|
1169
1212
|
"extend", "shorten", "adjust", "customize", "personalize",
|
|
1213
|
+
"update", "rewrite",
|
|
1170
1214
|
];
|
|
1171
1215
|
|
|
1172
1216
|
function extractPrimaryVerb(actionText) {
|
|
@@ -1372,6 +1416,13 @@ function _extractSubjectsInline(text) {
|
|
|
1372
1416
|
content = content.replace(/\s+must\s+(?:be\s+)?(?:preserved|remain)\b.*$/i, "").trim();
|
|
1373
1417
|
content = content.replace(/\s*[—–]\s+(?:prohibited|no\s+|must\s+not|deletion|do\s+not|migration)\b.*$/i, "").trim();
|
|
1374
1418
|
|
|
1419
|
+
// Strip comma-separated explanatory clauses
|
|
1420
|
+
// "KYC verification flow, it's SEC-compliant" → "KYC verification flow"
|
|
1421
|
+
// "patient records, which are HIPAA-protected" → "patient records"
|
|
1422
|
+
// "the auth system, because it's production-critical" → "the auth system"
|
|
1423
|
+
content = content.replace(/,\s+(?:it|they|that|this|which|who)\s*(?:'s|'re|is|are|was|were|has|have|had)\b.*$/i, "").trim();
|
|
1424
|
+
content = content.replace(/,\s+(?:because|since|as|for|due\s+to|given\s+that)\b.*$/i, "").trim();
|
|
1425
|
+
|
|
1375
1426
|
// Strip leading verb
|
|
1376
1427
|
const words = content.split(/\s+/);
|
|
1377
1428
|
let startIdx = 0;
|
|
@@ -1598,6 +1649,7 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
1598
1649
|
|
|
1599
1650
|
let score = 0;
|
|
1600
1651
|
const reasons = [];
|
|
1652
|
+
let hasSecurityViolationPattern = false; // Set when credential-exposure detected
|
|
1601
1653
|
|
|
1602
1654
|
// 1. Direct word overlap (minus stopwords)
|
|
1603
1655
|
const directOverlap = actionTokens.words.filter(w =>
|
|
@@ -1899,6 +1951,24 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
1899
1951
|
}
|
|
1900
1952
|
}
|
|
1901
1953
|
|
|
1954
|
+
// Special case: "add/put/store/embed key/secret/credential in/to frontend/component/state"
|
|
1955
|
+
// is SEMANTICALLY EQUIVALENT to "expose key in frontend" — NOT an opposite action.
|
|
1956
|
+
// Placing credentials in client-side code IS exposing them.
|
|
1957
|
+
if (!actionPerformsProhibitedOp && (prohibitedVerb === "expose" || prohibitedVerb === "leak" || prohibitedVerb === "reveal")) {
|
|
1958
|
+
// Pre-process env vars: STRIPE_SECRET_KEY → stripe secret key
|
|
1959
|
+
const actionNorm = actionText
|
|
1960
|
+
.replace(/\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+\b/g, m => m.replace(/_/g, " "))
|
|
1961
|
+
.toLowerCase();
|
|
1962
|
+
const hasCredentialWord = /\b(key|keys|secret|secrets|credential|credentials|token|api.?key|password|cert)\b/i.test(actionNorm);
|
|
1963
|
+
const hasFrontendLocation = /\b(frontend|front.?end|client|component|state|localstorage|session.?storage|browser|ui|react|vue|angular|svelte|html|template)\b/i.test(actionNorm);
|
|
1964
|
+
const hasPlacementVerb = /\b(add|put|store|embed|include|place|insert|set|hardcode|inline)\b/i.test(actionNorm);
|
|
1965
|
+
if (hasCredentialWord && hasFrontendLocation && hasPlacementVerb) {
|
|
1966
|
+
actionPerformsProhibitedOp = true;
|
|
1967
|
+
hasSecurityViolationPattern = true;
|
|
1968
|
+
reasons.push("security: placing credentials in client-side code is equivalent to exposing them");
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1902
1972
|
if (prohibitedIsNegative && !actionIntent.negated &&
|
|
1903
1973
|
!hasDestructiveLanguageMatch && !actionPerformsProhibitedOp) {
|
|
1904
1974
|
intentAligned = true;
|
|
@@ -1967,10 +2037,10 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
1967
2037
|
// Check 3b: Safe/verification verbs against preservation/maintenance locks
|
|
1968
2038
|
// "Test that Stripe is working" is COMPLIANT with "must always use Stripe"
|
|
1969
2039
|
// "Debug the Stripe webhook" is COMPLIANT — it's verifying the preserved system
|
|
1970
|
-
|
|
2040
|
+
{
|
|
1971
2041
|
const lockIsPreservation = /must remain|must be preserved|must always|at all times|must stay/i.test(lockText);
|
|
1972
2042
|
|
|
1973
|
-
if (lockIsPreservation) {
|
|
2043
|
+
if (!intentAligned && lockIsPreservation) {
|
|
1974
2044
|
const SAFE_FOR_PRESERVATION = new Set([
|
|
1975
2045
|
"test", "verify", "check", "validate", "confirm", "ensure",
|
|
1976
2046
|
"debug", "inspect", "review", "examine", "monitor", "observe",
|
|
@@ -1978,12 +2048,76 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
1978
2048
|
"read", "view", "generate", "fix", "repair", "patch",
|
|
1979
2049
|
"protect", "secure", "guard", "maintain", "preserve",
|
|
1980
2050
|
]);
|
|
1981
|
-
if (SAFE_FOR_PRESERVATION.has(actionPrimaryVerb)) {
|
|
2051
|
+
if (actionPrimaryVerb && SAFE_FOR_PRESERVATION.has(actionPrimaryVerb)) {
|
|
1982
2052
|
intentAligned = true;
|
|
1983
2053
|
reasons.push(
|
|
1984
2054
|
`intent alignment: verification/maintenance "${actionPrimaryVerb}" is ` +
|
|
1985
2055
|
`compliant with preservation lock`);
|
|
1986
2056
|
}
|
|
2057
|
+
// "Write tests for X" — the verb is "write" but the intent is testing
|
|
2058
|
+
// Uses raw text match since "write" may not be in NEUTRAL_ACTION_VERBS
|
|
2059
|
+
if (!intentAligned && /\bwrite\s+tests?\b/i.test(actionText)) {
|
|
2060
|
+
intentAligned = true;
|
|
2061
|
+
reasons.push(
|
|
2062
|
+
`intent alignment: "write tests" is a testing/verification action — ` +
|
|
2063
|
+
`compliant with preservation lock`);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
// Check 3c: Working WITH locked technology (not replacing it)
|
|
2069
|
+
// "Update the Stripe UI components" vs "must always use Stripe" → working WITH Stripe → safe
|
|
2070
|
+
// "Optimize Supabase queries" vs "Supabase Auth lock" → improving existing Supabase → safe
|
|
2071
|
+
// "Write tests for Supabase queries" vs "Supabase lock" → testing existing tech → safe
|
|
2072
|
+
// But: "Update payment to use Razorpay" vs "Stripe lock" → introducing competitor → NOT safe
|
|
2073
|
+
if (!intentAligned && actionPrimaryVerb) {
|
|
2074
|
+
const lockIsPreservationOrFreeze = /must remain|must be preserved|must always|at all times|must stay|do not replace|do not remove|do not switch|don't replace|don't remove|don't switch|uses .+ library/i.test(lockText);
|
|
2075
|
+
if (lockIsPreservationOrFreeze) {
|
|
2076
|
+
// Extract specific brand/tech names from the lock text
|
|
2077
|
+
const lockWords = lockText.toLowerCase().split(/\s+/).map(w => w.replace(/[^a-z0-9]/g, "")).filter(w => w.length > 2);
|
|
2078
|
+
const actionWords = actionText.toLowerCase().split(/\s+/).map(w => w.replace(/[^a-z0-9]/g, "")).filter(w => w.length > 2);
|
|
2079
|
+
|
|
2080
|
+
// Find brand/technology names that appear in BOTH action and lock
|
|
2081
|
+
// These are specific nouns (not verbs, not stopwords) that identify the technology
|
|
2082
|
+
const TECH_BRANDS = new Set([
|
|
2083
|
+
"stripe", "razorpay", "paypal", "phonepe", "paytm", "ccavenue", "cashfree",
|
|
2084
|
+
"braintree", "adyen", "square", "billdesk", "instamojo", "juspay",
|
|
2085
|
+
"postgresql", "postgres", "mysql", "mongodb", "mongo", "firebase",
|
|
2086
|
+
"firestore", "supabase", "dynamodb", "redis", "sqlite", "mariadb",
|
|
2087
|
+
"cassandra", "couchdb", "neo4j",
|
|
2088
|
+
"baileys", "twilio", "whatsapp",
|
|
2089
|
+
"auth0", "okta", "cognito", "keycloak",
|
|
2090
|
+
"react", "vue", "angular", "svelte", "nextjs", "nuxt",
|
|
2091
|
+
"docker", "kubernetes", "terraform", "ansible",
|
|
2092
|
+
"aws", "gcp", "azure", "vercel", "netlify", "railway", "heroku",
|
|
2093
|
+
]);
|
|
2094
|
+
const sharedBrands = lockWords.filter(w => TECH_BRANDS.has(w) && actionWords.includes(w));
|
|
2095
|
+
|
|
2096
|
+
if (sharedBrands.length > 0) {
|
|
2097
|
+
// Action references the SAME tech as the lock — check if it's working WITH it
|
|
2098
|
+
const hasCompetitorInAction = actionWords.some(w =>
|
|
2099
|
+
TECH_BRANDS.has(w) && !lockWords.includes(w)
|
|
2100
|
+
);
|
|
2101
|
+
const WORKING_WITH_VERBS = new Set([
|
|
2102
|
+
"update", "modify", "change", "refactor", "restructure",
|
|
2103
|
+
"optimize", "improve", "enhance", "write", "rewrite",
|
|
2104
|
+
"style", "format", "clean", "cleanup", "simplify",
|
|
2105
|
+
"test", "verify", "check", "validate", "debug", "fix",
|
|
2106
|
+
"repair", "patch", "maintain", "document", "monitor",
|
|
2107
|
+
"configure", "customize", "extend", "expand",
|
|
2108
|
+
]);
|
|
2109
|
+
const DESTRUCTIVE_VERBS = new Set([
|
|
2110
|
+
"remove", "delete", "drop", "destroy", "kill", "purge", "wipe",
|
|
2111
|
+
"disable", "deactivate", "replace", "switch", "migrate", "move",
|
|
2112
|
+
]);
|
|
2113
|
+
if (WORKING_WITH_VERBS.has(actionPrimaryVerb) && !hasCompetitorInAction &&
|
|
2114
|
+
!DESTRUCTIVE_VERBS.has(actionPrimaryVerb)) {
|
|
2115
|
+
intentAligned = true;
|
|
2116
|
+
reasons.push(
|
|
2117
|
+
`intent alignment: "${actionPrimaryVerb}" works WITH locked tech ` +
|
|
2118
|
+
`${sharedBrands.join(", ")} — not replacing it`);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
1987
2121
|
}
|
|
1988
2122
|
}
|
|
1989
2123
|
|
|
@@ -1995,7 +2129,7 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
1995
2129
|
if (!intentAligned && actionPrimaryVerb) {
|
|
1996
2130
|
const ENHANCEMENT_VERBS = new Set([
|
|
1997
2131
|
"increase", "improve", "enhance", "boost", "strengthen",
|
|
1998
|
-
"upgrade", "raise", "expand", "extend", "grow",
|
|
2132
|
+
"upgrade", "raise", "expand", "extend", "grow", "optimize",
|
|
1999
2133
|
]);
|
|
2000
2134
|
const CONSTRUCTIVE_FOR_PRESERVATION = new Set([
|
|
2001
2135
|
"build", "add", "create", "implement", "make", "design",
|
|
@@ -2042,7 +2176,8 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
2042
2176
|
"integrate", "include", "support",
|
|
2043
2177
|
]);
|
|
2044
2178
|
// 5a: Weak scope overlap (single shared word, no strong vocab match)
|
|
2045
|
-
if
|
|
2179
|
+
// Skip if a security violation pattern was detected (credential exposure)
|
|
2180
|
+
if (!intentAligned && !hasSecurityViolationPattern && hasScopeMatch && !hasStrongScopeMatch && !hasStrongVocabMatch) {
|
|
2046
2181
|
if (FEATURE_BUILDING_VERBS.has(actionPrimaryVerb)) {
|
|
2047
2182
|
// Guard: if the shared word is a long, specific entity name (10+ chars),
|
|
2048
2183
|
// it strongly identifies the target system — not an incidental overlap.
|
|
@@ -2059,7 +2194,7 @@ export function scoreConflict({ actionText, lockText }) {
|
|
|
2059
2194
|
}
|
|
2060
2195
|
// 5b: Vocab overlap but NO scope overlap (subjects point to different things)
|
|
2061
2196
|
// Even weaker signal — shared vocabulary but different actual components.
|
|
2062
|
-
if (!intentAligned && hasVocabSubjectMatch && !hasScopeMatch &&
|
|
2197
|
+
if (!intentAligned && !hasSecurityViolationPattern && hasVocabSubjectMatch && !hasScopeMatch &&
|
|
2063
2198
|
subjectComparison.lockSubjects.length > 0 && subjectComparison.actionSubjects.length > 0) {
|
|
2064
2199
|
if (FEATURE_BUILDING_VERBS.has(actionPrimaryVerb)) {
|
|
2065
2200
|
intentAligned = true;
|
package/src/core/telemetry.js
CHANGED
|
@@ -257,7 +257,7 @@ export async function flushToRemote(root) {
|
|
|
257
257
|
// Build anonymized payload
|
|
258
258
|
const payload = {
|
|
259
259
|
instanceId: summary.instanceId,
|
|
260
|
-
version: "4.5.
|
|
260
|
+
version: "4.5.3",
|
|
261
261
|
totalCalls: summary.totalCalls,
|
|
262
262
|
avgResponseMs: summary.avgResponseMs,
|
|
263
263
|
conflicts: summary.conflicts,
|
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">v4.5.
|
|
92
|
+
<div class="meta">v4.5.3 — 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 v4.5.
|
|
185
|
+
SpecLock v4.5.3 — 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
|
@@ -91,7 +91,7 @@ import { fileURLToPath } from "url";
|
|
|
91
91
|
import _path from "path";
|
|
92
92
|
|
|
93
93
|
const PROJECT_ROOT = process.env.SPECLOCK_PROJECT_ROOT || process.cwd();
|
|
94
|
-
const VERSION = "4.5.
|
|
94
|
+
const VERSION = "4.5.3";
|
|
95
95
|
const AUTHOR = "Sandeep Roy";
|
|
96
96
|
const START_TIME = Date.now();
|
|
97
97
|
|
package/src/mcp/server.js
CHANGED
|
@@ -100,7 +100,7 @@ const PROJECT_ROOT =
|
|
|
100
100
|
args.project || process.env.SPECLOCK_PROJECT_ROOT || process.cwd();
|
|
101
101
|
|
|
102
102
|
// --- MCP Server ---
|
|
103
|
-
const VERSION = "4.5.
|
|
103
|
+
const VERSION = "4.5.3";
|
|
104
104
|
const AUTHOR = "Sandeep Roy";
|
|
105
105
|
|
|
106
106
|
const server = new McpServer(
|