shieldcortex 3.4.12 → 3.4.14
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/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
- package/dist/api/iron-dome-route-guard.js +7 -1
- package/dist/database/init.d.ts +22 -0
- package/dist/database/init.js +242 -50
- package/package.json +1 -1
- /package/dashboard/.next/standalone/dashboard/.next/static/{IEheIaoI03DPDkMm1iq_v → uiyXVE418p8MtLu_mVA4X}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{IEheIaoI03DPDkMm1iq_v → uiyXVE418p8MtLu_mVA4X}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{IEheIaoI03DPDkMm1iq_v → uiyXVE418p8MtLu_mVA4X}/_ssgManifest.js +0 -0
package/dist/database/init.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Database initialization and connection management
|
|
3
3
|
*/
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
|
-
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, unlinkSync, renameSync, copyFileSync } from 'fs';
|
|
6
|
-
import { dirname, join } from 'path';
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, unlinkSync, renameSync, copyFileSync, readdirSync, openSync, closeSync, realpathSync } from 'fs';
|
|
6
|
+
import { basename, dirname, join } from 'path';
|
|
7
7
|
import { homedir } from 'os';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
@@ -13,6 +13,7 @@ const _currentDir = dirname(_currentFile);
|
|
|
13
13
|
let db = null;
|
|
14
14
|
let currentDbPath = null;
|
|
15
15
|
let lockFilePath = null;
|
|
16
|
+
let lockFileFd = null;
|
|
16
17
|
// Anti-bloat: Database size limits
|
|
17
18
|
const MAX_DB_SIZE = 100 * 1024 * 1024; // 100MB hard limit
|
|
18
19
|
const WARN_DB_SIZE = 50 * 1024 * 1024; // 50MB warning threshold
|
|
@@ -40,6 +41,177 @@ function getDefaultDbPath() {
|
|
|
40
41
|
// Fall back to legacy path for existing users
|
|
41
42
|
return legacyPath;
|
|
42
43
|
}
|
|
44
|
+
function resolveRuntimeInfo() {
|
|
45
|
+
let entryPath = process.argv[1] ?? '';
|
|
46
|
+
try {
|
|
47
|
+
if (entryPath) {
|
|
48
|
+
entryPath = realpathSync(entryPath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Fall back to the raw argv path.
|
|
53
|
+
}
|
|
54
|
+
if (entryPath.includes('/.npm/_npx/')) {
|
|
55
|
+
return { kind: 'npx-cache', entryPath };
|
|
56
|
+
}
|
|
57
|
+
if (entryPath.includes('/node_modules/shieldcortex/')) {
|
|
58
|
+
return { kind: 'installed', entryPath };
|
|
59
|
+
}
|
|
60
|
+
if (entryPath.includes('/ShieldCortex/') || entryPath.includes('\\ShieldCortex\\')) {
|
|
61
|
+
return { kind: 'project-checkout', entryPath };
|
|
62
|
+
}
|
|
63
|
+
return { kind: 'unknown', entryPath };
|
|
64
|
+
}
|
|
65
|
+
function enforceSafeRuntimePath(expandedPath, explicitDbPath) {
|
|
66
|
+
if (explicitDbPath || process.env.SHIELDCORTEX_ALLOW_UNSAFE_RUNTIME === '1') {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const defaultPath = expandPath(getDefaultDbPath());
|
|
70
|
+
if (expandedPath !== defaultPath) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const runtime = resolveRuntimeInfo();
|
|
74
|
+
if (runtime.kind === 'npx-cache' || runtime.kind === 'project-checkout') {
|
|
75
|
+
throw new Error(`[database] Refusing to open ${defaultPath} from ${runtime.kind === 'npx-cache' ? 'an npx cache' : 'a project checkout'}.\n` +
|
|
76
|
+
`Use the installed CLI (\`shieldcortex\`), pass \`--db\` to use a separate database, or set SHIELDCORTEX_ALLOW_UNSAFE_RUNTIME=1 if you really mean to do this.\n` +
|
|
77
|
+
`Runtime path: ${runtime.entryPath}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function inspectDatabaseFile(dbPath) {
|
|
81
|
+
let inspectionDb = null;
|
|
82
|
+
try {
|
|
83
|
+
inspectionDb = new Database(dbPath, {
|
|
84
|
+
readonly: true,
|
|
85
|
+
fileMustExist: true,
|
|
86
|
+
});
|
|
87
|
+
const integrity = runIntegrityCheck(inspectionDb);
|
|
88
|
+
let count = null;
|
|
89
|
+
try {
|
|
90
|
+
count = inspectionDb.prepare('SELECT COUNT(*) AS count FROM memories').get().count;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
count = null;
|
|
94
|
+
}
|
|
95
|
+
return { integrity, count };
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return { integrity: `inspect threw: ${error}`, count: null };
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
try {
|
|
102
|
+
inspectionDb?.close();
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Best-effort close.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function listHealthyBackups(dbPath) {
|
|
110
|
+
const dir = dirname(dbPath);
|
|
111
|
+
const prefix = `${basename(dbPath)}.corrupt.`;
|
|
112
|
+
return readdirSync(dir)
|
|
113
|
+
.filter((name) => name.startsWith(prefix) && !name.endsWith('-wal') && !name.endsWith('-shm'))
|
|
114
|
+
.map((name) => join(dir, name))
|
|
115
|
+
.map((backupPath) => {
|
|
116
|
+
const inspection = inspectDatabaseFile(backupPath);
|
|
117
|
+
const stats = statSync(backupPath);
|
|
118
|
+
return {
|
|
119
|
+
path: backupPath,
|
|
120
|
+
integrity: inspection.integrity,
|
|
121
|
+
count: inspection.count,
|
|
122
|
+
mtimeMs: stats.mtimeMs,
|
|
123
|
+
};
|
|
124
|
+
})
|
|
125
|
+
.filter((candidate) => candidate.integrity === 'ok' && typeof candidate.count === 'number' && candidate.count > 0)
|
|
126
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
|
127
|
+
.map(({ path, count, mtimeMs }) => ({ path, count, mtimeMs }));
|
|
128
|
+
}
|
|
129
|
+
function stashLiveDatabase(dbPath, suffix) {
|
|
130
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
131
|
+
const stashPath = `${dbPath}.${suffix}.${timestamp}`;
|
|
132
|
+
if (existsSync(dbPath)) {
|
|
133
|
+
try {
|
|
134
|
+
renameSync(dbPath, stashPath);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
try {
|
|
138
|
+
copyFileSync(dbPath, stashPath);
|
|
139
|
+
unlinkSync(dbPath);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Last resort: leave the original in place.
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const ext of ['-wal', '-shm']) {
|
|
147
|
+
const liveSidecar = dbPath + ext;
|
|
148
|
+
if (existsSync(liveSidecar)) {
|
|
149
|
+
try {
|
|
150
|
+
renameSync(liveSidecar, stashPath + ext);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
try {
|
|
154
|
+
unlinkSync(liveSidecar);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Best-effort cleanup.
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function restoreBackupAsLive(dbPath, backupPath, reason) {
|
|
164
|
+
stashLiveDatabase(dbPath, reason);
|
|
165
|
+
copyFileSync(backupPath, dbPath);
|
|
166
|
+
}
|
|
167
|
+
function acquireStartupLock(dbPath) {
|
|
168
|
+
lockFilePath = `${dbPath}.lock`;
|
|
169
|
+
const runtime = resolveRuntimeInfo();
|
|
170
|
+
const payload = JSON.stringify({
|
|
171
|
+
pid: process.pid,
|
|
172
|
+
startedAt: new Date().toISOString(),
|
|
173
|
+
entryPath: runtime.entryPath,
|
|
174
|
+
}, null, 2);
|
|
175
|
+
const tryOpen = () => {
|
|
176
|
+
lockFileFd = openSync(lockFilePath, 'wx');
|
|
177
|
+
writeFileSync(lockFileFd, payload, 'utf-8');
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
tryOpen();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
let activeProcessError = null;
|
|
185
|
+
try {
|
|
186
|
+
const existing = JSON.parse(readFileSync(lockFilePath, 'utf-8'));
|
|
187
|
+
if (typeof existing.pid === 'number') {
|
|
188
|
+
try {
|
|
189
|
+
process.kill(existing.pid, 0);
|
|
190
|
+
activeProcessError = new Error(`[database] Refusing startup because ShieldCortex PID ${existing.pid} is already using ${dbPath}` +
|
|
191
|
+
(existing.entryPath ? ` (${existing.entryPath})` : ''));
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
if (error.code !== 'ESRCH') {
|
|
195
|
+
activeProcessError = error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Stale or unreadable lock file — remove and retry.
|
|
202
|
+
}
|
|
203
|
+
if (activeProcessError) {
|
|
204
|
+
throw activeProcessError;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
unlinkSync(lockFilePath);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Best-effort stale lock cleanup.
|
|
211
|
+
}
|
|
212
|
+
tryOpen();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
43
215
|
/**
|
|
44
216
|
* Back up a corrupt database file with a timestamped name.
|
|
45
217
|
* Returns the backup path.
|
|
@@ -164,30 +336,16 @@ function attemptFtsRecovery(database) {
|
|
|
164
336
|
}
|
|
165
337
|
}
|
|
166
338
|
function verifyOnDiskIntegrity(dbPath) {
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
verificationDb = new Database(dbPath, {
|
|
170
|
-
readonly: true,
|
|
171
|
-
fileMustExist: true,
|
|
172
|
-
});
|
|
173
|
-
return runIntegrityCheck(verificationDb);
|
|
174
|
-
}
|
|
175
|
-
catch (error) {
|
|
176
|
-
return `fresh integrity check threw: ${error}`;
|
|
177
|
-
}
|
|
178
|
-
finally {
|
|
179
|
-
try {
|
|
180
|
-
verificationDb?.close();
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
// Best-effort close for verification handles.
|
|
184
|
-
}
|
|
185
|
-
}
|
|
339
|
+
return inspectDatabaseFile(dbPath).integrity;
|
|
186
340
|
}
|
|
187
341
|
export const __databaseTestUtils = {
|
|
188
342
|
isLikelyFtsIntegrityIssue,
|
|
189
343
|
attemptFtsRecovery,
|
|
190
344
|
verifyOnDiskIntegrity,
|
|
345
|
+
inspectDatabaseFile,
|
|
346
|
+
listHealthyBackups,
|
|
347
|
+
resolveRuntimeInfo,
|
|
348
|
+
enforceSafeRuntimePath,
|
|
191
349
|
};
|
|
192
350
|
/**
|
|
193
351
|
* Initialize the database connection
|
|
@@ -195,10 +353,12 @@ export const __databaseTestUtils = {
|
|
|
195
353
|
export function initDatabase(dbPath) {
|
|
196
354
|
// Use auto-detected path if not specified
|
|
197
355
|
const resolvedPath = dbPath || getDefaultDbPath();
|
|
356
|
+
const explicitDbPath = Boolean(dbPath);
|
|
198
357
|
if (db) {
|
|
199
358
|
return db;
|
|
200
359
|
}
|
|
201
360
|
const expandedPath = expandPath(resolvedPath);
|
|
361
|
+
enforceSafeRuntimePath(expandedPath, explicitDbPath);
|
|
202
362
|
const dir = dirname(expandedPath);
|
|
203
363
|
// Create directory if it doesn't exist
|
|
204
364
|
if (!existsSync(dir)) {
|
|
@@ -206,6 +366,9 @@ export function initDatabase(dbPath) {
|
|
|
206
366
|
}
|
|
207
367
|
// Store path for size monitoring
|
|
208
368
|
currentDbPath = expandedPath;
|
|
369
|
+
acquireStartupLock(expandedPath);
|
|
370
|
+
console.log(`[database] Startup runtime=${resolveRuntimeInfo().kind} db=${expandedPath} wal=${existsSync(expandedPath + '-wal')} shm=${existsSync(expandedPath + '-shm')}`);
|
|
371
|
+
const healthyBackups = listHealthyBackups(expandedPath);
|
|
209
372
|
// Wrap the initial open in try/catch to handle corrupt files gracefully
|
|
210
373
|
let database;
|
|
211
374
|
try {
|
|
@@ -213,11 +376,19 @@ export function initDatabase(dbPath) {
|
|
|
213
376
|
}
|
|
214
377
|
catch (openError) {
|
|
215
378
|
// Database file is corrupt or not a valid SQLite database
|
|
216
|
-
console.error(
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
379
|
+
console.error(`❌ Database open failed for ${expandedPath}: ${openError}`);
|
|
380
|
+
const latestHealthyBackup = healthyBackups[0];
|
|
381
|
+
if (latestHealthyBackup) {
|
|
382
|
+
console.error(`[database] Restoring latest healthy backup with ${latestHealthyBackup.count} memories: ${latestHealthyBackup.path}`);
|
|
383
|
+
restoreBackupAsLive(expandedPath, latestHealthyBackup.path, 'failed-open');
|
|
384
|
+
database = new Database(expandedPath);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
const backupPath = backupCorruptDatabase(expandedPath);
|
|
388
|
+
console.error(` Backed up to ${backupPath}`);
|
|
389
|
+
console.error(' Creating fresh database...');
|
|
390
|
+
database = new Database(expandedPath);
|
|
391
|
+
}
|
|
221
392
|
}
|
|
222
393
|
// Integrity check on existing databases (skip for newly created files)
|
|
223
394
|
if (existsSync(expandedPath) && statSync(expandedPath).size > 0) {
|
|
@@ -244,18 +415,40 @@ export function initDatabase(dbPath) {
|
|
|
244
415
|
database = recovered;
|
|
245
416
|
}
|
|
246
417
|
else {
|
|
247
|
-
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
418
|
+
const latestHealthyBackup = healthyBackups[0];
|
|
419
|
+
if (latestHealthyBackup) {
|
|
420
|
+
console.error(`[database] Recovery failed. Restoring latest healthy backup with ${latestHealthyBackup.count} memories: ${latestHealthyBackup.path}`);
|
|
421
|
+
restoreBackupAsLive(expandedPath, latestHealthyBackup.path, 'recovery-failed');
|
|
422
|
+
database = new Database(expandedPath);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
// Recovery failed — backup and create fresh
|
|
426
|
+
if (existsSync(expandedPath)) {
|
|
427
|
+
const backupPath = backupCorruptDatabase(expandedPath);
|
|
428
|
+
console.error(`[database] Recovery failed. Backed up corrupt file to: ${backupPath}`);
|
|
429
|
+
}
|
|
430
|
+
console.error('[database] Creating fresh database...');
|
|
431
|
+
database = new Database(expandedPath);
|
|
251
432
|
}
|
|
252
|
-
console.error('[database] Creating fresh database...');
|
|
253
|
-
database = new Database(expandedPath);
|
|
254
433
|
}
|
|
255
434
|
}
|
|
256
435
|
}
|
|
257
436
|
}
|
|
258
437
|
}
|
|
438
|
+
const currentInspection = inspectDatabaseFile(expandedPath);
|
|
439
|
+
const latestHealthyBackup = healthyBackups[0];
|
|
440
|
+
const liveStats = existsSync(expandedPath) ? statSync(expandedPath) : null;
|
|
441
|
+
if (currentInspection.integrity === 'ok'
|
|
442
|
+
&& currentInspection.count === 0
|
|
443
|
+
&& latestHealthyBackup
|
|
444
|
+
&& latestHealthyBackup.count >= 100
|
|
445
|
+
&& liveStats
|
|
446
|
+
&& (liveStats.mtimeMs - latestHealthyBackup.mtimeMs) < (24 * 60 * 60 * 1000)) {
|
|
447
|
+
console.error(`[database] Empty live database detected alongside a recent healthy backup (${latestHealthyBackup.count} memories). Restoring ${latestHealthyBackup.path}`);
|
|
448
|
+
database.close();
|
|
449
|
+
restoreBackupAsLive(expandedPath, latestHealthyBackup.path, 'empty-live');
|
|
450
|
+
database = new Database(expandedPath);
|
|
451
|
+
}
|
|
259
452
|
db = database;
|
|
260
453
|
// Enable WAL mode for better concurrency
|
|
261
454
|
db.pragma('journal_mode = WAL');
|
|
@@ -265,15 +458,6 @@ export function initDatabase(dbPath) {
|
|
|
265
458
|
db.pragma('busy_timeout = 10000');
|
|
266
459
|
// Auto-checkpoint every 100 pages (~400KB) to prevent WAL bloat
|
|
267
460
|
db.pragma('wal_autocheckpoint = 100');
|
|
268
|
-
// Create lock file to help detect concurrent instances
|
|
269
|
-
lockFilePath = expandedPath + '.lock';
|
|
270
|
-
const pid = process.pid;
|
|
271
|
-
try {
|
|
272
|
-
writeFileSync(lockFilePath, `${pid}\n${new Date().toISOString()}`);
|
|
273
|
-
}
|
|
274
|
-
catch {
|
|
275
|
-
// Non-fatal - lock file is advisory
|
|
276
|
-
}
|
|
277
461
|
// Register cleanup handlers for graceful shutdown
|
|
278
462
|
registerShutdownHandlers();
|
|
279
463
|
// Run migrations FIRST for existing databases
|
|
@@ -633,16 +817,24 @@ export function closeDatabase() {
|
|
|
633
817
|
db.close();
|
|
634
818
|
db = null;
|
|
635
819
|
currentDbPath = null;
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
820
|
+
}
|
|
821
|
+
if (lockFileFd !== null) {
|
|
822
|
+
try {
|
|
823
|
+
closeSync(lockFileFd);
|
|
824
|
+
}
|
|
825
|
+
catch {
|
|
826
|
+
// Best-effort close.
|
|
827
|
+
}
|
|
828
|
+
lockFileFd = null;
|
|
829
|
+
}
|
|
830
|
+
if (lockFilePath && existsSync(lockFilePath)) {
|
|
831
|
+
try {
|
|
832
|
+
unlinkSync(lockFilePath);
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
// Non-fatal
|
|
645
836
|
}
|
|
837
|
+
lockFilePath = null;
|
|
646
838
|
}
|
|
647
839
|
}
|
|
648
840
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shieldcortex",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.14",
|
|
4
4
|
"description": "Trustworthy memory and security for AI agents. Recall debugging, review queue, OpenClaw session capture, and memory poisoning defence for Claude Code, Codex, OpenClaw, LangChain, and MCP agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
File without changes
|
|
File without changes
|