@nxuss/lemma 0.5.8 ā 0.6.0
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 +20 -6
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +343 -39
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/mcp/index.js +279 -0
- package/dist/cjs/mcp/index.js.map +1 -1
- package/dist/cjs/utils/ContextSqueezer.d.ts +4 -0
- package/dist/cjs/utils/ContextSqueezer.d.ts.map +1 -1
- package/dist/cjs/utils/ContextSqueezer.js +35 -1
- package/dist/cjs/utils/ContextSqueezer.js.map +1 -1
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +343 -39
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/mcp/index.js +279 -0
- package/dist/esm/mcp/index.js.map +1 -1
- package/dist/esm/utils/ContextSqueezer.d.ts +4 -0
- package/dist/esm/utils/ContextSqueezer.d.ts.map +1 -1
- package/dist/esm/utils/ContextSqueezer.js +34 -1
- package/dist/esm/utils/ContextSqueezer.js.map +1 -1
- package/package.json +1 -1
|
@@ -154,9 +154,9 @@ async function ensureChromaRunning() {
|
|
|
154
154
|
}
|
|
155
155
|
console.log(`\nš¦ \x1b[35m[ChromaDB]\x1b[0m ChromaDB is not running on port ${chromaPort}.`);
|
|
156
156
|
console.log(`š Starting ChromaDB in background...`);
|
|
157
|
+
const chromaDataPath = path.join(CACHE_DIR, 'chroma_data');
|
|
157
158
|
try {
|
|
158
159
|
const { spawn } = require('child_process');
|
|
159
|
-
const chromaDataPath = path.join(process.cwd(), 'chroma_data');
|
|
160
160
|
if (!fs.existsSync(chromaDataPath)) {
|
|
161
161
|
fs.mkdirSync(chromaDataPath, { recursive: true });
|
|
162
162
|
}
|
|
@@ -179,7 +179,7 @@ async function ensureChromaRunning() {
|
|
|
179
179
|
}
|
|
180
180
|
catch (err) {
|
|
181
181
|
console.error(`ā Failed to automatically start ChromaDB: ${err.message}`);
|
|
182
|
-
console.error(`š Please start it manually: chroma run --path
|
|
182
|
+
console.error(`š Please start it manually: chroma run --path ${chromaDataPath} --port ${chromaPort}`);
|
|
183
183
|
return false;
|
|
184
184
|
}
|
|
185
185
|
}
|
|
@@ -600,15 +600,16 @@ async function getEmbedding(text) {
|
|
|
600
600
|
return null;
|
|
601
601
|
}
|
|
602
602
|
}
|
|
603
|
-
async function semanticGet(provider, prompt) {
|
|
603
|
+
async function semanticGet(provider, prompt, projectName = 'global') {
|
|
604
604
|
if (!await isPro())
|
|
605
605
|
return null;
|
|
606
606
|
const emb = await getEmbedding(prompt);
|
|
607
607
|
if (!emb)
|
|
608
608
|
return null;
|
|
609
|
+
let localHit = null;
|
|
609
610
|
try {
|
|
610
611
|
const collection = await chroma.getOrCreateCollection({
|
|
611
|
-
name:
|
|
612
|
+
name: `lemma-cache-${projectName}`,
|
|
612
613
|
embeddingFunction: dummyEmbeddingFunction,
|
|
613
614
|
metadata: { "hnsw:space": "cosine" }
|
|
614
615
|
});
|
|
@@ -616,33 +617,83 @@ async function semanticGet(provider, prompt) {
|
|
|
616
617
|
queryEmbeddings: [emb],
|
|
617
618
|
nResults: 10
|
|
618
619
|
});
|
|
619
|
-
if (
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
620
|
+
if (res.ids[0] && res.ids[0].length > 0) {
|
|
621
|
+
for (let i = 0; i < res.ids[0].length; i++) {
|
|
622
|
+
const metadata = res.metadatas[0][i];
|
|
623
|
+
if (metadata.provider !== provider)
|
|
624
|
+
continue;
|
|
625
|
+
const distances = res.distances;
|
|
626
|
+
let distance = 1.0;
|
|
627
|
+
if (distances && distances[0] && typeof distances[0][i] === 'number') {
|
|
628
|
+
distance = distances[0][i];
|
|
629
|
+
}
|
|
630
|
+
const similarity = Math.max(0, 1 - distance);
|
|
631
|
+
const THRESHOLD = 0.82; // raised from 0.7 to 0.82 to prevent false positives
|
|
632
|
+
if (similarity >= THRESHOLD) {
|
|
633
|
+
localHit = {
|
|
634
|
+
data: JSON.parse(metadata.response),
|
|
635
|
+
similarity,
|
|
636
|
+
id: res.ids[0][i],
|
|
637
|
+
prompt: metadata.prompt,
|
|
638
|
+
hiveMind: false
|
|
639
|
+
};
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
639
642
|
}
|
|
640
643
|
}
|
|
641
644
|
}
|
|
642
645
|
catch (e) { }
|
|
646
|
+
if (localHit)
|
|
647
|
+
return localHit;
|
|
648
|
+
// āāā Cross-Project Bug Telepathy Fallback āāā
|
|
649
|
+
try {
|
|
650
|
+
const collections = await chroma.listCollections();
|
|
651
|
+
const projectCollections = collections.filter((c) => c.name.startsWith('lemma-cache-') && c.name !== `lemma-cache-${projectName}`);
|
|
652
|
+
for (const colInfo of projectCollections) {
|
|
653
|
+
try {
|
|
654
|
+
const col = await chroma.getOrCreateCollection({
|
|
655
|
+
name: colInfo.name,
|
|
656
|
+
embeddingFunction: dummyEmbeddingFunction,
|
|
657
|
+
metadata: { "hnsw:space": "cosine" }
|
|
658
|
+
});
|
|
659
|
+
const otherRes = await col.query({
|
|
660
|
+
queryEmbeddings: [emb],
|
|
661
|
+
nResults: 3
|
|
662
|
+
});
|
|
663
|
+
if (otherRes.ids[0] && otherRes.ids[0].length > 0) {
|
|
664
|
+
for (let i = 0; i < otherRes.ids[0].length; i++) {
|
|
665
|
+
const metadata = otherRes.metadatas[0][i];
|
|
666
|
+
if (metadata.provider !== provider)
|
|
667
|
+
continue;
|
|
668
|
+
const distances = otherRes.distances;
|
|
669
|
+
let distance = 1.0;
|
|
670
|
+
if (distances && distances[0] && typeof distances[0][i] === 'number') {
|
|
671
|
+
distance = distances[0][i];
|
|
672
|
+
}
|
|
673
|
+
const similarity = Math.max(0, 1 - distance);
|
|
674
|
+
const HIVE_THRESHOLD = 0.90; // Higher threshold to avoid false positives across different domains
|
|
675
|
+
if (similarity >= HIVE_THRESHOLD) {
|
|
676
|
+
const originProject = colInfo.name.replace('lemma-cache-', '');
|
|
677
|
+
console.log(`\nšÆ \x1b[32m[Hive Mind Telepathy]\x1b[0m Found a high-confidence solution (Similarity: ${(similarity * 100).toFixed(1)}%) in Project [\x1b[36m${originProject}\x1b[0m]! Reusing solution telepathically.\n`);
|
|
678
|
+
return {
|
|
679
|
+
data: JSON.parse(metadata.response),
|
|
680
|
+
similarity,
|
|
681
|
+
id: otherRes.ids[0][i],
|
|
682
|
+
prompt: metadata.prompt,
|
|
683
|
+
hiveMind: true,
|
|
684
|
+
originProject
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
catch { }
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch { }
|
|
643
694
|
return null;
|
|
644
695
|
}
|
|
645
|
-
async function semanticSet(provider, prompt, data) {
|
|
696
|
+
async function semanticSet(provider, prompt, data, projectName = 'global') {
|
|
646
697
|
if (!await isPro())
|
|
647
698
|
return;
|
|
648
699
|
const emb = await getEmbedding(prompt);
|
|
@@ -650,7 +701,7 @@ async function semanticSet(provider, prompt, data) {
|
|
|
650
701
|
return;
|
|
651
702
|
try {
|
|
652
703
|
const collection = await chroma.getOrCreateCollection({
|
|
653
|
-
name:
|
|
704
|
+
name: `lemma-cache-${projectName}`,
|
|
654
705
|
embeddingFunction: dummyEmbeddingFunction,
|
|
655
706
|
metadata: { "hnsw:space": "cosine" }
|
|
656
707
|
});
|
|
@@ -742,7 +793,7 @@ function setSseHeaders(res, fromCache, similarity, tier) {
|
|
|
742
793
|
res.setHeader('Cache-Control', 'no-cache');
|
|
743
794
|
res.setHeader('Connection', 'keep-alive');
|
|
744
795
|
res.setHeader('X-Accel-Buffering', 'no');
|
|
745
|
-
res.setHeader('X-Lemma-Cache', fromCache ? 'HIT' : 'MISS');
|
|
796
|
+
res.setHeader('X-Lemma-Cache', typeof fromCache === 'string' ? fromCache : (fromCache ? 'HIT' : 'MISS'));
|
|
746
797
|
res.setHeader('X-Lemma-Similarity', (similarity || 1.0).toFixed(3));
|
|
747
798
|
res.setHeader('X-Lemma-Tier', tier);
|
|
748
799
|
}
|
|
@@ -758,8 +809,8 @@ function buildAntChunk(text, stop) {
|
|
|
758
809
|
function buildGemChunk(text, stop) {
|
|
759
810
|
return { candidates: [{ content: { parts: [{ text }] }, finishReason: stop ? 'STOP' : undefined }] };
|
|
760
811
|
}
|
|
761
|
-
async function simulateHitStream(res, cachedData, provider, model, tier, similarity) {
|
|
762
|
-
setSseHeaders(res,
|
|
812
|
+
async function simulateHitStream(res, cachedData, provider, model, tier, similarity, cacheHeader = 'HIT') {
|
|
813
|
+
setSseHeaders(res, cacheHeader, similarity, tier);
|
|
763
814
|
const id = `lemma-${Date.now()}`;
|
|
764
815
|
let text = '';
|
|
765
816
|
try {
|
|
@@ -876,6 +927,8 @@ class LemmaServer {
|
|
|
876
927
|
this.stats = {};
|
|
877
928
|
this.dashboardPath = '';
|
|
878
929
|
this.sessions = new Map();
|
|
930
|
+
this.timeline = [];
|
|
931
|
+
this.lastFileContents = new Map();
|
|
879
932
|
this.port = port;
|
|
880
933
|
this.projectName = projectName || detectProject();
|
|
881
934
|
this.projectDir = ensureProjectDir(this.projectName);
|
|
@@ -883,6 +936,7 @@ class LemmaServer {
|
|
|
883
936
|
this.app = express();
|
|
884
937
|
this.setupMiddleware();
|
|
885
938
|
this.setupRoutes();
|
|
939
|
+
this.startWorkspaceTimeWatcher();
|
|
886
940
|
}
|
|
887
941
|
setupMiddleware() {
|
|
888
942
|
this.app.use(express.json({ limit: '10mb' }));
|
|
@@ -1038,7 +1092,7 @@ class LemmaServer {
|
|
|
1038
1092
|
// Try to query ChromaDB for semantic cache count
|
|
1039
1093
|
try {
|
|
1040
1094
|
const collection = await chroma.getOrCreateCollection({
|
|
1041
|
-
name:
|
|
1095
|
+
name: `lemma-cache-${this.projectName}`,
|
|
1042
1096
|
embeddingFunction: dummyEmbeddingFunction,
|
|
1043
1097
|
metadata: { "hnsw:space": "cosine" }
|
|
1044
1098
|
});
|
|
@@ -1095,7 +1149,7 @@ class LemmaServer {
|
|
|
1095
1149
|
if (!emb)
|
|
1096
1150
|
throw new Error('Failed to generate embedding');
|
|
1097
1151
|
const collection = await chroma.getOrCreateCollection({
|
|
1098
|
-
name:
|
|
1152
|
+
name: `lemma-cache-${this.projectName}`,
|
|
1099
1153
|
embeddingFunction: dummyEmbeddingFunction,
|
|
1100
1154
|
metadata: { "hnsw:space": "cosine" }
|
|
1101
1155
|
});
|
|
@@ -1144,7 +1198,7 @@ class LemmaServer {
|
|
|
1144
1198
|
}
|
|
1145
1199
|
// Clear Chroma DB collection
|
|
1146
1200
|
try {
|
|
1147
|
-
await chroma.deleteCollection({ name:
|
|
1201
|
+
await chroma.deleteCollection({ name: `lemma-cache-${this.projectName}` }).catch(() => { });
|
|
1148
1202
|
}
|
|
1149
1203
|
catch (e) {
|
|
1150
1204
|
console.error(`[CacheClear] Failed to clear Chroma collection: ${e.message}`);
|
|
@@ -1161,7 +1215,7 @@ class LemmaServer {
|
|
|
1161
1215
|
// Selective delete from Chroma DB collection using provider metadata filter
|
|
1162
1216
|
try {
|
|
1163
1217
|
const collection = await chroma.getOrCreateCollection({
|
|
1164
|
-
name:
|
|
1218
|
+
name: `lemma-cache-${this.projectName}`,
|
|
1165
1219
|
embeddingFunction: dummyEmbeddingFunction,
|
|
1166
1220
|
metadata: { "hnsw:space": "cosine" }
|
|
1167
1221
|
});
|
|
@@ -1177,6 +1231,117 @@ class LemmaServer {
|
|
|
1177
1231
|
this.app.get('/api/savings-breakdown', (req, res) => {
|
|
1178
1232
|
res.json(savingsLedger.getSnapshot());
|
|
1179
1233
|
});
|
|
1234
|
+
// Serve codebase timeline snapshots (AST Time-Travel Telemetry)
|
|
1235
|
+
this.app.get('/api/project/timeline', (req, res) => {
|
|
1236
|
+
res.json({ success: true, timeline: this.timeline });
|
|
1237
|
+
});
|
|
1238
|
+
// Dynamic Zero-Shot Onboarding mental model builder
|
|
1239
|
+
this.app.get('/api/project/onboarding', async (req, res) => {
|
|
1240
|
+
try {
|
|
1241
|
+
const cwd = process.cwd();
|
|
1242
|
+
// 1. Parse package.json
|
|
1243
|
+
let pkg = {};
|
|
1244
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
1245
|
+
if (fs.existsSync(pkgPath)) {
|
|
1246
|
+
try {
|
|
1247
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
1248
|
+
}
|
|
1249
|
+
catch { }
|
|
1250
|
+
}
|
|
1251
|
+
// 2. Identify stack & tools
|
|
1252
|
+
const techStack = [];
|
|
1253
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
1254
|
+
if (deps['next'])
|
|
1255
|
+
techStack.push('Next.js (React Web App Framework)');
|
|
1256
|
+
else if (deps['react'])
|
|
1257
|
+
techStack.push('React.js (Frontend library)');
|
|
1258
|
+
if (deps['typescript'])
|
|
1259
|
+
techStack.push('TypeScript (Strict static type safety)');
|
|
1260
|
+
if (deps['prisma'])
|
|
1261
|
+
techStack.push('Prisma (Object-Relational Mapping)');
|
|
1262
|
+
if (deps['tailwindcss'])
|
|
1263
|
+
techStack.push('TailwindCSS (Utility-first styling)');
|
|
1264
|
+
if (deps['express'])
|
|
1265
|
+
techStack.push('Express (NodeJS REST API framework)');
|
|
1266
|
+
if (deps['jest'])
|
|
1267
|
+
techStack.push('Jest (Automated Unit Testing framework)');
|
|
1268
|
+
if (fs.existsSync(path.join(cwd, 'Dockerfile')) || fs.existsSync(path.join(cwd, 'docker-compose.yml'))) {
|
|
1269
|
+
techStack.push('Docker / Containerization');
|
|
1270
|
+
}
|
|
1271
|
+
if (techStack.length === 0) {
|
|
1272
|
+
techStack.push('Vanilla Node.js / Web stack');
|
|
1273
|
+
}
|
|
1274
|
+
// 3. Scan directory structure
|
|
1275
|
+
let files = [];
|
|
1276
|
+
try {
|
|
1277
|
+
files = fs.readdirSync(cwd).filter(f => !['node_modules', '.git', 'dist', 'chroma_data', '.lemma'].includes(f));
|
|
1278
|
+
}
|
|
1279
|
+
catch { }
|
|
1280
|
+
// 4. Query core architecture notes from ChromaDB
|
|
1281
|
+
let archMemoriesText = '';
|
|
1282
|
+
try {
|
|
1283
|
+
const emb = await getEmbedding("architectural design guidelines and core stack configuration decisions");
|
|
1284
|
+
if (emb) {
|
|
1285
|
+
const collection = await chroma.getOrCreateCollection({
|
|
1286
|
+
name: `lemma-cache-${this.projectName}`,
|
|
1287
|
+
embeddingFunction: dummyEmbeddingFunction,
|
|
1288
|
+
metadata: { "hnsw:space": "cosine" }
|
|
1289
|
+
});
|
|
1290
|
+
const searchRes = await collection.query({
|
|
1291
|
+
queryEmbeddings: [emb],
|
|
1292
|
+
nResults: 3
|
|
1293
|
+
});
|
|
1294
|
+
if (searchRes.ids[0] && searchRes.ids[0].length > 0) {
|
|
1295
|
+
archMemoriesText = '### š§ Stored Architectural Brain Memories\n';
|
|
1296
|
+
searchRes.ids[0].forEach((id, idx) => {
|
|
1297
|
+
const meta = (searchRes.metadatas && searchRes.metadatas[0]) ? searchRes.metadatas[0][idx] : null;
|
|
1298
|
+
if (meta && typeof meta.response === 'string') {
|
|
1299
|
+
try {
|
|
1300
|
+
const responseObj = JSON.parse(meta.response);
|
|
1301
|
+
const contentText = responseObj.choices?.[0]?.message?.content || responseObj.content?.[0]?.text || '';
|
|
1302
|
+
const promptText = typeof meta.prompt === 'string' ? meta.prompt : 'Architecture Note';
|
|
1303
|
+
archMemoriesText += `#### š Decisión: ${promptText}\n${contentText.substring(0, 500)}${contentText.length > 500 ? '...' : ''}\n\n`;
|
|
1304
|
+
}
|
|
1305
|
+
catch { }
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
catch (e) {
|
|
1312
|
+
// Silently swallow vector db query errors
|
|
1313
|
+
}
|
|
1314
|
+
// 5. Construct gorgeous onboarding markdown guide
|
|
1315
|
+
const mentalModel = [
|
|
1316
|
+
`# š§ Codebase Onboarding & Mental Model: ${this.projectName}`,
|
|
1317
|
+
`This onboarding document was automatically generated by Lemma's Project Brain to align your context in a single shot.`,
|
|
1318
|
+
`\n## š Project Specifications`,
|
|
1319
|
+
`- **Name:** \`${pkg.name || this.projectName}\``,
|
|
1320
|
+
`- **Version:** \`${pkg.version || '0.1.0'}\``,
|
|
1321
|
+
`- **Description:** ${pkg.description || 'No description available in package.json.'}`,
|
|
1322
|
+
`\n## š ļø Detected Technical Stack`,
|
|
1323
|
+
techStack.map(s => `- **${s}**`).join('\n'),
|
|
1324
|
+
`\n## š Directory Structure (Root)`,
|
|
1325
|
+
files.map(f => {
|
|
1326
|
+
const isDir = fs.statSync(path.join(cwd, f)).isDirectory();
|
|
1327
|
+
return `- \`${f}${isDir ? '/' : ''}\``;
|
|
1328
|
+
}).join('\n'),
|
|
1329
|
+
pkg.scripts && Object.keys(pkg.scripts).length > 0
|
|
1330
|
+
? `\n## š Standard Project Scripts\n${Object.entries(pkg.scripts).map(([name, cmd]) => `- \`npm run ${name}\`: \`${cmd}\``).join('\n')}`
|
|
1331
|
+
: '',
|
|
1332
|
+
`\n${archMemoriesText}`,
|
|
1333
|
+
`## š” AI Coding Guidelines for this Project`,
|
|
1334
|
+
`1. **Respect Core Stack:** Adhere strictly to the codebase structure and libraries listed above.`,
|
|
1335
|
+
`2. **Leverage the Brain:** Use Lemma MCP tools (\`search_memory\` and \`store_memory\`) to retrieve past solutions and register key architectural changes.`,
|
|
1336
|
+
`3. **Optimize Context:** Keep conversations focused. Lemma transparently compresses repetitive logs and file blocks to protect context window limits.`,
|
|
1337
|
+
`4. **Cortafuegos Local:** Do not worry about API key exposures. Lemma automatically masks local private variables and credentials.`
|
|
1338
|
+
].filter(Boolean).join('\n');
|
|
1339
|
+
res.json({ success: true, markdown: mentalModel });
|
|
1340
|
+
}
|
|
1341
|
+
catch (err) {
|
|
1342
|
+
res.status(500).json({ success: false, error: err.message });
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1180
1345
|
// Explicit memory storage endpoint (store_memory)
|
|
1181
1346
|
this.app.post('/api/memory/store', async (req, res) => {
|
|
1182
1347
|
const { query, response, provider = 'generic' } = req.body || {};
|
|
@@ -1189,7 +1354,7 @@ class LemmaServer {
|
|
|
1189
1354
|
return res.status(500).json({ error: 'Failed to generate embedding' });
|
|
1190
1355
|
}
|
|
1191
1356
|
const collection = await chroma.getOrCreateCollection({
|
|
1192
|
-
name:
|
|
1357
|
+
name: `lemma-cache-${this.projectName}`,
|
|
1193
1358
|
embeddingFunction: dummyEmbeddingFunction,
|
|
1194
1359
|
metadata: { "hnsw:space": "cosine" }
|
|
1195
1360
|
});
|
|
@@ -1225,17 +1390,148 @@ class LemmaServer {
|
|
|
1225
1390
|
if (!source || !validSources.includes(source)) {
|
|
1226
1391
|
return res.status(400).json({ error: 'Invalid source' });
|
|
1227
1392
|
}
|
|
1393
|
+
let tk = 0;
|
|
1228
1394
|
if (typeof tokens === 'number' && tokens > 0) {
|
|
1395
|
+
tk = tokens;
|
|
1229
1396
|
savingsLedger.recordTokens(source, tokens);
|
|
1230
1397
|
}
|
|
1231
1398
|
else if (typeof charsBefore === 'number' && typeof charsAfter === 'number') {
|
|
1399
|
+
tk = Math.floor(Math.max(0, charsBefore - charsAfter) / 4);
|
|
1232
1400
|
savingsLedger.record(source, charsBefore, charsAfter);
|
|
1233
1401
|
}
|
|
1402
|
+
// Update active project stats to immediately reflect on Dashboard total requests & charts
|
|
1403
|
+
if (tk > 0) {
|
|
1404
|
+
if (!this.stats[this.projectName]) {
|
|
1405
|
+
this.stats[this.projectName] = { total: 0, hits: 0, misses: 0, totalLatency: 0, totalTokensSaved: 0, providers: {} };
|
|
1406
|
+
}
|
|
1407
|
+
const s = this.stats[this.projectName];
|
|
1408
|
+
s.total++;
|
|
1409
|
+
s.totalTokensSaved += tk;
|
|
1410
|
+
s.totalLatency += 15; // mock latency for local MCP metrics
|
|
1411
|
+
if (source === 'cache') {
|
|
1412
|
+
s.hits++;
|
|
1413
|
+
}
|
|
1414
|
+
else {
|
|
1415
|
+
s.misses++;
|
|
1416
|
+
}
|
|
1417
|
+
if (!s.providers['mcp'])
|
|
1418
|
+
s.providers['mcp'] = { hits: 0, misses: 0 };
|
|
1419
|
+
source === 'cache' ? s.providers['mcp'].hits++ : s.providers['mcp'].misses++;
|
|
1420
|
+
// Write updated stats back to disk immediately
|
|
1421
|
+
writeJson(STATS_FILE, this.stats).catch(() => { });
|
|
1422
|
+
// Push to live telemetry logEvent array so the Dashboard event timeline updates instantly!
|
|
1423
|
+
logEvent({
|
|
1424
|
+
type: source === 'cache' ? 'cache:hit' : source,
|
|
1425
|
+
project: this.projectName,
|
|
1426
|
+
latency: 15,
|
|
1427
|
+
provider: 'mcp',
|
|
1428
|
+
tokens: tk
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1234
1431
|
res.json({ ok: true, snapshot: savingsLedger.getSnapshot().total });
|
|
1235
1432
|
});
|
|
1236
1433
|
this.app.get('/dashboard/*', (req, res) => res.sendFile(path.join(this.dashboardPath, 'index.html')));
|
|
1237
1434
|
}
|
|
1238
1435
|
}
|
|
1436
|
+
startWorkspaceTimeWatcher() {
|
|
1437
|
+
const cwd = process.cwd();
|
|
1438
|
+
const { watch } = require('fs');
|
|
1439
|
+
// Debounced capture function
|
|
1440
|
+
let debounceTimer = null;
|
|
1441
|
+
const pendingChanges = new Set();
|
|
1442
|
+
const ignoreDirs = ['node_modules', '.git', 'dist', 'chroma_data', '.lemma', 'dashboard', 'bin', 'sdks'];
|
|
1443
|
+
const watchDirRecursive = (dir) => {
|
|
1444
|
+
try {
|
|
1445
|
+
const watcher = watch(dir, (eventType, filename) => {
|
|
1446
|
+
if (!filename)
|
|
1447
|
+
return;
|
|
1448
|
+
const fullPath = path.join(dir, filename);
|
|
1449
|
+
// Heuristic to ignore generated/system folders
|
|
1450
|
+
const relPath = path.relative(cwd, fullPath);
|
|
1451
|
+
if (ignoreDirs.some(d => relPath.split(path.sep).includes(d)))
|
|
1452
|
+
return;
|
|
1453
|
+
// Ignore non-source files
|
|
1454
|
+
const ext = path.extname(filename);
|
|
1455
|
+
if (!['.ts', '.js', '.tsx', '.jsx', '.json', '.py', '.css'].includes(ext))
|
|
1456
|
+
return;
|
|
1457
|
+
pendingChanges.add(fullPath);
|
|
1458
|
+
if (debounceTimer)
|
|
1459
|
+
clearTimeout(debounceTimer);
|
|
1460
|
+
debounceTimer = setTimeout(() => this.captureSnapshot(pendingChanges), 1500);
|
|
1461
|
+
});
|
|
1462
|
+
// Prevent watcher from keeping process alive if in background
|
|
1463
|
+
if (watcher.unref)
|
|
1464
|
+
watcher.unref();
|
|
1465
|
+
// Recursively watch subfolders
|
|
1466
|
+
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
1467
|
+
for (const file of files) {
|
|
1468
|
+
if (file.isDirectory() && !ignoreDirs.includes(file.name)) {
|
|
1469
|
+
watchDirRecursive(path.join(dir, file.name));
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
catch { }
|
|
1474
|
+
};
|
|
1475
|
+
watchDirRecursive(cwd); // Time travel watcher active!
|
|
1476
|
+
}
|
|
1477
|
+
async captureSnapshot(pendingChanges) {
|
|
1478
|
+
const changes = Array.from(pendingChanges);
|
|
1479
|
+
pendingChanges.clear();
|
|
1480
|
+
const changedFiles = [];
|
|
1481
|
+
for (const filePath of changes) {
|
|
1482
|
+
if (!fs.existsSync(filePath))
|
|
1483
|
+
continue;
|
|
1484
|
+
try {
|
|
1485
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
1486
|
+
const relPath = path.relative(process.cwd(), filePath);
|
|
1487
|
+
const previousContent = this.lastFileContents.get(filePath) || '';
|
|
1488
|
+
if (content === previousContent)
|
|
1489
|
+
continue;
|
|
1490
|
+
let diff = '';
|
|
1491
|
+
if (previousContent) {
|
|
1492
|
+
const { generateAstDiff } = require('../utils/ContextSqueezer');
|
|
1493
|
+
diff = generateAstDiff(previousContent, content);
|
|
1494
|
+
}
|
|
1495
|
+
this.lastFileContents.set(filePath, content);
|
|
1496
|
+
changedFiles.push({
|
|
1497
|
+
filePath: relPath,
|
|
1498
|
+
content,
|
|
1499
|
+
diff
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
catch { }
|
|
1503
|
+
}
|
|
1504
|
+
if (changedFiles.length === 0)
|
|
1505
|
+
return;
|
|
1506
|
+
// Detect current compiler state/errors
|
|
1507
|
+
let status = 'stable';
|
|
1508
|
+
let errorLog = '';
|
|
1509
|
+
// Check if there is a recent crash log in .lemma/live-context.md
|
|
1510
|
+
try {
|
|
1511
|
+
const liveContextPath = path.join(process.cwd(), '.lemma', 'live-context.md');
|
|
1512
|
+
if (fs.existsSync(liveContextPath)) {
|
|
1513
|
+
const content = fs.readFileSync(liveContextPath, 'utf8');
|
|
1514
|
+
if (content.includes('crashed') || content.includes('error') || content.includes('Exception')) {
|
|
1515
|
+
status = 'failure';
|
|
1516
|
+
errorLog = content.substring(0, 1000);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
catch { }
|
|
1521
|
+
const snapshot = {
|
|
1522
|
+
id: Math.random().toString(36).substring(2, 9),
|
|
1523
|
+
timestamp: Date.now(),
|
|
1524
|
+
changedFiles,
|
|
1525
|
+
status,
|
|
1526
|
+
errorLog
|
|
1527
|
+
};
|
|
1528
|
+
this.timeline.push(snapshot);
|
|
1529
|
+
// Cap at 10 snapshots (FIFO eviction)
|
|
1530
|
+
if (this.timeline.length > 10) {
|
|
1531
|
+
this.timeline.shift();
|
|
1532
|
+
}
|
|
1533
|
+
console.log(`š \x1b[35m[Multiverse]\x1b[0m Captured micro-snapshot ${snapshot.id} (${changedFiles.length} file edits mapped).`);
|
|
1534
|
+
}
|
|
1239
1535
|
async handleCompletion(req, res, provider) {
|
|
1240
1536
|
const t0 = Date.now();
|
|
1241
1537
|
const pro = await isPro();
|
|
@@ -1272,16 +1568,20 @@ class LemmaServer {
|
|
|
1272
1568
|
}
|
|
1273
1569
|
// Perform semantic cache check (resolves in ~15ms)
|
|
1274
1570
|
if (pro) {
|
|
1275
|
-
const semHit = await semanticGet(provider, prompt);
|
|
1571
|
+
const semHit = await semanticGet(provider, prompt, this.projectName);
|
|
1276
1572
|
if (semHit) {
|
|
1277
1573
|
if (semHit.similarity >= 0.90) {
|
|
1278
1574
|
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000);
|
|
1279
1575
|
const unmaskedData = semanticScrubber.unmask(semHit.data, tokenMap);
|
|
1576
|
+
const cacheHeader = semHit.hiveMind ? 'HIT-HIVE-MIND' : 'HIT';
|
|
1280
1577
|
if (isStream)
|
|
1281
|
-
return simulateHitStream(res, unmaskedData, provider, originalModel, tier, semHit.similarity);
|
|
1282
|
-
res.setHeader('X-Lemma-Cache',
|
|
1578
|
+
return simulateHitStream(res, unmaskedData, provider, originalModel, tier, semHit.similarity, cacheHeader);
|
|
1579
|
+
res.setHeader('X-Lemma-Cache', cacheHeader);
|
|
1283
1580
|
res.setHeader('X-Lemma-Similarity', semHit.similarity.toFixed(3));
|
|
1284
1581
|
res.setHeader('X-Lemma-Tier', tier);
|
|
1582
|
+
if (semHit.hiveMind && semHit.originProject) {
|
|
1583
|
+
res.setHeader('X-Lemma-Hive-Origin', semHit.originProject);
|
|
1584
|
+
}
|
|
1285
1585
|
return res.json(unmaskedData);
|
|
1286
1586
|
}
|
|
1287
1587
|
else if (semHit.similarity >= 0.82) { // raised from 0.70 to 0.82 to prevent false positives
|
|
@@ -1429,7 +1729,7 @@ Adjusted Answer:`;
|
|
|
1429
1729
|
const data = await this.callUpstream(req.body, provider);
|
|
1430
1730
|
cacheSet(provider, prompt, data);
|
|
1431
1731
|
if (pro) {
|
|
1432
|
-
await semanticSet(provider, prompt, data);
|
|
1732
|
+
await semanticSet(provider, prompt, data, this.projectName);
|
|
1433
1733
|
await cloudSync.set(prompt, data);
|
|
1434
1734
|
}
|
|
1435
1735
|
if (!pro) {
|
|
@@ -1684,7 +1984,11 @@ program.command('start')
|
|
|
1684
1984
|
if (opts.stack) {
|
|
1685
1985
|
const { spawn } = require('child_process');
|
|
1686
1986
|
console.log('š Launching Lemma Full Stack...');
|
|
1687
|
-
const
|
|
1987
|
+
const chromaDataPath = path.join(CACHE_DIR, 'chroma_data');
|
|
1988
|
+
if (!fs.existsSync(chromaDataPath)) {
|
|
1989
|
+
fs.mkdirSync(chromaDataPath, { recursive: true });
|
|
1990
|
+
}
|
|
1991
|
+
const chromaProcess = spawn('chroma', ['run', '--path', chromaDataPath], { stdio: 'ignore', detached: true });
|
|
1688
1992
|
chromaProcess.unref();
|
|
1689
1993
|
console.log(' ā
ChromaDB requested');
|
|
1690
1994
|
const dash = spawn('npm', ['run', 'dev', '--prefix', 'dashboard', '--', '--port', '8082'], { stdio: 'ignore', detached: true });
|