@velvetmonkey/vault-core 2.0.140 → 2.0.142
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/dist/entities.js +42 -2
- package/dist/migrations.d.ts +36 -1
- package/dist/migrations.js +181 -1
- package/dist/sqlite.d.ts +1 -0
- package/dist/sqlite.js +13 -3
- package/dist/wikilinks.js +3 -0
- package/package.json +1 -1
package/dist/entities.js
CHANGED
|
@@ -249,13 +249,53 @@ function mapFrontmatterType(type) {
|
|
|
249
249
|
* 7. Projects - multi-word (fallback)
|
|
250
250
|
* 8. Other - single word default
|
|
251
251
|
*/
|
|
252
|
-
|
|
252
|
+
// Folder names that imply entity categories
|
|
253
|
+
const FOLDER_CATEGORY_MAP = {
|
|
254
|
+
'people': 'people',
|
|
255
|
+
'person': 'people',
|
|
256
|
+
'contacts': 'people',
|
|
257
|
+
'team': 'people',
|
|
258
|
+
'members': 'people',
|
|
259
|
+
'projects': 'projects',
|
|
260
|
+
'project': 'projects',
|
|
261
|
+
'locations': 'locations',
|
|
262
|
+
'places': 'locations',
|
|
263
|
+
'companies': 'organizations',
|
|
264
|
+
'organizations': 'organizations',
|
|
265
|
+
'orgs': 'organizations',
|
|
266
|
+
'concepts': 'concepts',
|
|
267
|
+
'topics': 'concepts',
|
|
268
|
+
'tools': 'technologies',
|
|
269
|
+
'software': 'technologies',
|
|
270
|
+
'media': 'media',
|
|
271
|
+
'books': 'media',
|
|
272
|
+
'films': 'media',
|
|
273
|
+
'movies': 'media',
|
|
274
|
+
'music': 'media',
|
|
275
|
+
'vehicles': 'vehicles',
|
|
276
|
+
'equipment': 'technologies',
|
|
277
|
+
'food': 'food',
|
|
278
|
+
'recipes': 'food',
|
|
279
|
+
'health': 'health',
|
|
280
|
+
'finance': 'finance',
|
|
281
|
+
'hobbies': 'hobbies',
|
|
282
|
+
};
|
|
283
|
+
function categorizeEntity(name, techKeywords, frontmatterType, notePath) {
|
|
253
284
|
// 0. Frontmatter type takes priority
|
|
254
285
|
if (frontmatterType) {
|
|
255
286
|
const mapped = mapFrontmatterType(frontmatterType);
|
|
256
287
|
if (mapped)
|
|
257
288
|
return mapped;
|
|
258
289
|
}
|
|
290
|
+
// 0.5. Folder-based inference (e.g., people/Andrew.md → person)
|
|
291
|
+
if (notePath) {
|
|
292
|
+
const segments = notePath.toLowerCase().split('/');
|
|
293
|
+
for (const segment of segments) {
|
|
294
|
+
const mapped = FOLDER_CATEGORY_MAP[segment];
|
|
295
|
+
if (mapped)
|
|
296
|
+
return mapped;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
259
299
|
const nameLower = name.toLowerCase();
|
|
260
300
|
const words = name.split(/\s+/);
|
|
261
301
|
// 1. Technology check (keyword match)
|
|
@@ -438,7 +478,7 @@ export async function scanVaultEntities(vaultPath, options = {}) {
|
|
|
438
478
|
},
|
|
439
479
|
};
|
|
440
480
|
for (const entity of uniqueEntities) {
|
|
441
|
-
const category = categorizeEntity(entity.name, techKeywords, entity.frontmatterType);
|
|
481
|
+
const category = categorizeEntity(entity.name, techKeywords, entity.frontmatterType, entity.relativePath);
|
|
442
482
|
// Store as EntityWithAliases object
|
|
443
483
|
const entityObj = {
|
|
444
484
|
name: entity.name,
|
package/dist/migrations.d.ts
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
* SQLite Schema Migrations
|
|
3
3
|
*
|
|
4
4
|
* Database path resolution, schema initialization, migration logic,
|
|
5
|
-
*
|
|
5
|
+
* database file management, backup rotation, integrity checks, and
|
|
6
|
+
* feedback salvage utilities.
|
|
6
7
|
*/
|
|
7
8
|
import Database from 'better-sqlite3';
|
|
9
|
+
export declare const BACKUP_ROTATION_COUNT = 3;
|
|
10
|
+
/** High-value tables whose data should survive a corruption recovery. */
|
|
11
|
+
export declare const SALVAGE_TABLES: readonly ["wikilink_feedback", "wikilink_applications", "suggestion_events", "wikilink_suppressions", "note_links", "note_link_history", "memories", "session_summaries", "corrections"];
|
|
8
12
|
/**
|
|
9
13
|
* Get the database path for a vault
|
|
10
14
|
*/
|
|
@@ -18,4 +22,35 @@ export declare function deleteStateDbFiles(dbPath: string): void;
|
|
|
18
22
|
export declare function backupStateDb(dbPath: string): void;
|
|
19
23
|
/** Preserve a corrupted database for inspection before deleting. */
|
|
20
24
|
export declare function preserveCorruptedDb(dbPath: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Rotate existing backup files: .backup → .backup.1 → .backup.2 → .backup.3
|
|
27
|
+
* Drops the oldest if rotation count exceeded. Does NOT create a new backup.
|
|
28
|
+
*/
|
|
29
|
+
export declare function rotateBackupFiles(dbPath: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* Create a WAL-safe backup using SQLite's backup API.
|
|
32
|
+
* Rotates existing backups first, then writes a new .backup file.
|
|
33
|
+
*/
|
|
34
|
+
export declare function safeBackupAsync(db: Database.Database, dbPath: string): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Run PRAGMA quick_check on the database.
|
|
37
|
+
* Returns { ok: true } or { ok: false, detail: string }.
|
|
38
|
+
*/
|
|
39
|
+
export declare function checkDbIntegrity(db: Database.Database): {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
detail?: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Attempt to copy high-value feedback tables from a source DB into the target.
|
|
45
|
+
* Opens source read-only; copies rows with INSERT OR IGNORE.
|
|
46
|
+
* Handles missing tables and column mismatches gracefully.
|
|
47
|
+
*/
|
|
48
|
+
export declare function salvageFeedbackTables(targetDb: Database.Database, sourceDbPath: string): Record<string, number>;
|
|
49
|
+
/**
|
|
50
|
+
* After corruption forces a fresh DB, attempt to recover feedback data
|
|
51
|
+
* from all available backup files (newest first) and the corrupt file.
|
|
52
|
+
* Merges across all sources — INSERT OR IGNORE deduplicates, so each
|
|
53
|
+
* successive source only adds rows the previous ones didn't cover.
|
|
54
|
+
*/
|
|
55
|
+
export declare function attemptSalvage(targetDb: Database.Database, dbPath: string): void;
|
|
21
56
|
//# sourceMappingURL=migrations.d.ts.map
|
package/dist/migrations.js
CHANGED
|
@@ -2,12 +2,30 @@
|
|
|
2
2
|
* SQLite Schema Migrations
|
|
3
3
|
*
|
|
4
4
|
* Database path resolution, schema initialization, migration logic,
|
|
5
|
-
*
|
|
5
|
+
* database file management, backup rotation, integrity checks, and
|
|
6
|
+
* feedback salvage utilities.
|
|
6
7
|
*/
|
|
8
|
+
import Database from 'better-sqlite3';
|
|
7
9
|
import * as fs from 'fs';
|
|
8
10
|
import * as path from 'path';
|
|
9
11
|
import { SCHEMA_VERSION, SCHEMA_SQL, STATE_DB_FILENAME, FLYWHEEL_DIR } from './schema.js';
|
|
10
12
|
// =============================================================================
|
|
13
|
+
// Backup & Recovery Constants
|
|
14
|
+
// =============================================================================
|
|
15
|
+
export const BACKUP_ROTATION_COUNT = 3;
|
|
16
|
+
/** High-value tables whose data should survive a corruption recovery. */
|
|
17
|
+
export const SALVAGE_TABLES = [
|
|
18
|
+
'wikilink_feedback',
|
|
19
|
+
'wikilink_applications',
|
|
20
|
+
'suggestion_events',
|
|
21
|
+
'wikilink_suppressions',
|
|
22
|
+
'note_links',
|
|
23
|
+
'note_link_history',
|
|
24
|
+
'memories',
|
|
25
|
+
'session_summaries',
|
|
26
|
+
'corrections',
|
|
27
|
+
];
|
|
28
|
+
// =============================================================================
|
|
11
29
|
// Database Path Resolution
|
|
12
30
|
// =============================================================================
|
|
13
31
|
/**
|
|
@@ -317,4 +335,166 @@ export function preserveCorruptedDb(dbPath) {
|
|
|
317
335
|
// Best effort — don't block recovery
|
|
318
336
|
}
|
|
319
337
|
}
|
|
338
|
+
// =============================================================================
|
|
339
|
+
// Backup Rotation & Safe Backup
|
|
340
|
+
// =============================================================================
|
|
341
|
+
/**
|
|
342
|
+
* Rotate existing backup files: .backup → .backup.1 → .backup.2 → .backup.3
|
|
343
|
+
* Drops the oldest if rotation count exceeded. Does NOT create a new backup.
|
|
344
|
+
*/
|
|
345
|
+
export function rotateBackupFiles(dbPath) {
|
|
346
|
+
try {
|
|
347
|
+
// Shift numbered backups down (3→drop, 2→3, 1→2)
|
|
348
|
+
for (let i = BACKUP_ROTATION_COUNT; i >= 1; i--) {
|
|
349
|
+
const src = i === 1
|
|
350
|
+
? `${dbPath}.backup`
|
|
351
|
+
: `${dbPath}.backup.${i - 1}`;
|
|
352
|
+
const dst = `${dbPath}.backup.${i}`;
|
|
353
|
+
if (fs.existsSync(src)) {
|
|
354
|
+
if (i === BACKUP_ROTATION_COUNT && fs.existsSync(dst)) {
|
|
355
|
+
fs.unlinkSync(dst);
|
|
356
|
+
}
|
|
357
|
+
fs.renameSync(src, dst);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
console.error(`[vault-core] Failed to rotate backups: ${err instanceof Error ? err.message : err}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Create a WAL-safe backup using SQLite's backup API.
|
|
367
|
+
* Rotates existing backups first, then writes a new .backup file.
|
|
368
|
+
*/
|
|
369
|
+
export async function safeBackupAsync(db, dbPath) {
|
|
370
|
+
try {
|
|
371
|
+
rotateBackupFiles(dbPath);
|
|
372
|
+
const backupPath = `${dbPath}.backup`;
|
|
373
|
+
await db.backup(backupPath);
|
|
374
|
+
console.error(`[vault-core] Safe backup created: ${path.basename(backupPath)}`);
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
console.error(`[vault-core] Safe backup failed: ${err instanceof Error ? err.message : err}`);
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// =============================================================================
|
|
383
|
+
// Integrity Checks
|
|
384
|
+
// =============================================================================
|
|
385
|
+
/**
|
|
386
|
+
* Run PRAGMA quick_check on the database.
|
|
387
|
+
* Returns { ok: true } or { ok: false, detail: string }.
|
|
388
|
+
*/
|
|
389
|
+
export function checkDbIntegrity(db) {
|
|
390
|
+
try {
|
|
391
|
+
const result = db.pragma('quick_check');
|
|
392
|
+
const firstValue = result.length > 0 ? Object.values(result[0])[0] : 'no result';
|
|
393
|
+
if (result.length === 1 && firstValue === 'ok') {
|
|
394
|
+
return { ok: true };
|
|
395
|
+
}
|
|
396
|
+
return { ok: false, detail: firstValue ?? 'unknown' };
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
return { ok: false, detail: err instanceof Error ? err.message : String(err) };
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// =============================================================================
|
|
403
|
+
// Feedback Salvage
|
|
404
|
+
// =============================================================================
|
|
405
|
+
/**
|
|
406
|
+
* Attempt to copy high-value feedback tables from a source DB into the target.
|
|
407
|
+
* Opens source read-only; copies rows with INSERT OR IGNORE.
|
|
408
|
+
* Handles missing tables and column mismatches gracefully.
|
|
409
|
+
*/
|
|
410
|
+
export function salvageFeedbackTables(targetDb, sourceDbPath) {
|
|
411
|
+
const results = {};
|
|
412
|
+
if (!fs.existsSync(sourceDbPath))
|
|
413
|
+
return results;
|
|
414
|
+
let sourceDb = null;
|
|
415
|
+
try {
|
|
416
|
+
sourceDb = new Database(sourceDbPath, { readonly: true, fileMustExist: true });
|
|
417
|
+
for (const table of SALVAGE_TABLES) {
|
|
418
|
+
try {
|
|
419
|
+
// Check table exists in both source and target
|
|
420
|
+
const srcExists = sourceDb.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).get(table);
|
|
421
|
+
if (!srcExists)
|
|
422
|
+
continue;
|
|
423
|
+
const tgtExists = targetDb.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).get(table);
|
|
424
|
+
if (!tgtExists)
|
|
425
|
+
continue;
|
|
426
|
+
// Find columns common to both (handles schema version mismatches)
|
|
427
|
+
const targetCols = targetDb.pragma(`table_info('${table}')`)
|
|
428
|
+
.map(c => c.name);
|
|
429
|
+
const sourceCols = sourceDb.pragma(`table_info('${table}')`)
|
|
430
|
+
.map(c => c.name);
|
|
431
|
+
const commonCols = targetCols.filter(c => sourceCols.includes(c));
|
|
432
|
+
if (commonCols.length === 0)
|
|
433
|
+
continue;
|
|
434
|
+
const colList = commonCols.join(', ');
|
|
435
|
+
const placeholders = commonCols.map(() => '?').join(', ');
|
|
436
|
+
const rows = sourceDb.prepare(`SELECT ${colList} FROM ${table}`).all();
|
|
437
|
+
if (rows.length === 0)
|
|
438
|
+
continue;
|
|
439
|
+
const insert = targetDb.prepare(`INSERT OR IGNORE INTO ${table} (${colList}) VALUES (${placeholders})`);
|
|
440
|
+
const insertMany = targetDb.transaction((data) => {
|
|
441
|
+
let count = 0;
|
|
442
|
+
for (const row of data) {
|
|
443
|
+
insert.run(...commonCols.map(c => row[c]));
|
|
444
|
+
count++;
|
|
445
|
+
}
|
|
446
|
+
return count;
|
|
447
|
+
});
|
|
448
|
+
const count = insertMany(rows);
|
|
449
|
+
if (count > 0)
|
|
450
|
+
results[table] = count;
|
|
451
|
+
}
|
|
452
|
+
catch (tableErr) {
|
|
453
|
+
console.error(`[vault-core] Salvage ${table}: ${tableErr instanceof Error ? tableErr.message : tableErr}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch (err) {
|
|
458
|
+
console.error(`[vault-core] Cannot open ${path.basename(sourceDbPath)} for salvage: ${err instanceof Error ? err.message : err}`);
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
try {
|
|
462
|
+
sourceDb?.close();
|
|
463
|
+
}
|
|
464
|
+
catch { /* ignore */ }
|
|
465
|
+
}
|
|
466
|
+
return results;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* After corruption forces a fresh DB, attempt to recover feedback data
|
|
470
|
+
* from all available backup files (newest first) and the corrupt file.
|
|
471
|
+
* Merges across all sources — INSERT OR IGNORE deduplicates, so each
|
|
472
|
+
* successive source only adds rows the previous ones didn't cover.
|
|
473
|
+
*/
|
|
474
|
+
export function attemptSalvage(targetDb, dbPath) {
|
|
475
|
+
const sources = [
|
|
476
|
+
`${dbPath}.backup`,
|
|
477
|
+
...Array.from({ length: BACKUP_ROTATION_COUNT }, (_, i) => `${dbPath}.backup.${i + 1}`),
|
|
478
|
+
`${dbPath}.corrupt`,
|
|
479
|
+
];
|
|
480
|
+
let totalSalvaged = 0;
|
|
481
|
+
for (const source of sources) {
|
|
482
|
+
if (!fs.existsSync(source))
|
|
483
|
+
continue;
|
|
484
|
+
console.error(`[vault-core] Attempting feedback salvage from ${path.basename(source)}...`);
|
|
485
|
+
const results = salvageFeedbackTables(targetDb, source);
|
|
486
|
+
const sourceRows = Object.values(results).reduce((a, b) => a + b, 0);
|
|
487
|
+
if (sourceRows > 0) {
|
|
488
|
+
const detail = Object.entries(results).map(([t, n]) => `${t}: ${n}`).join(', ');
|
|
489
|
+
console.error(`[vault-core] Salvaged ${sourceRows} rows from ${path.basename(source)}: ${detail}`);
|
|
490
|
+
totalSalvaged += sourceRows;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (totalSalvaged > 0) {
|
|
494
|
+
console.error(`[vault-core] Total salvaged: ${totalSalvaged} rows across all sources`);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
console.error('[vault-core] No salvageable backup found — starting fresh');
|
|
498
|
+
}
|
|
499
|
+
}
|
|
320
500
|
//# sourceMappingURL=migrations.js.map
|
package/dist/sqlite.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { Statement, Transaction } from 'better-sqlite3';
|
|
|
14
14
|
import type { EntityCategory, EntityWithAliases, EntityIndex } from './types.js';
|
|
15
15
|
export { SCHEMA_VERSION, STATE_DB_FILENAME, FLYWHEEL_DIR, SCHEMA_SQL } from './schema.js';
|
|
16
16
|
export { getStateDbPath, initSchema, deleteStateDbFiles, backupStateDb, preserveCorruptedDb } from './migrations.js';
|
|
17
|
+
export { BACKUP_ROTATION_COUNT, SALVAGE_TABLES, rotateBackupFiles, safeBackupAsync, checkDbIntegrity, salvageFeedbackTables, attemptSalvage, } from './migrations.js';
|
|
17
18
|
export { searchEntities, searchEntitiesPrefix, getEntityByName, getAllEntitiesFromDb, getEntityIndexFromDb, getEntitiesByAlias, recordEntityMention, getEntityRecency, getAllRecency, setWriteState, getWriteState, deleteWriteState, setFlywheelConfig, getFlywheelConfig, getAllFlywheelConfig, deleteFlywheelConfig, saveFlywheelConfigToDb, loadFlywheelConfigFromDb, recordMergeDismissal, getDismissedMergePairs, getStateDbMetadata, isEntityDataStale, escapeFts5Query, rebuildEntitiesFts, stateDbExists, deleteStateDb, saveVaultIndexCache, loadVaultIndexCache, getVaultIndexCacheInfo, clearVaultIndexCache, isVaultIndexCacheValid, loadContentHashes, saveContentHashBatch, renameContentHash, } from './queries.js';
|
|
18
19
|
export type { FlywheelConfigRow, VaultIndexCacheData, VaultIndexCacheInfo } from './queries.js';
|
|
19
20
|
/** Search result from FTS5 entity search */
|
package/dist/sqlite.js
CHANGED
|
@@ -15,10 +15,12 @@ import * as fs from 'fs';
|
|
|
15
15
|
export { SCHEMA_VERSION, STATE_DB_FILENAME, FLYWHEEL_DIR, SCHEMA_SQL } from './schema.js';
|
|
16
16
|
// Re-export migrations
|
|
17
17
|
export { getStateDbPath, initSchema, deleteStateDbFiles, backupStateDb, preserveCorruptedDb } from './migrations.js';
|
|
18
|
+
// Re-export backup & recovery
|
|
19
|
+
export { BACKUP_ROTATION_COUNT, SALVAGE_TABLES, rotateBackupFiles, safeBackupAsync, checkDbIntegrity, salvageFeedbackTables, attemptSalvage, } from './migrations.js';
|
|
18
20
|
// Re-export all query functions
|
|
19
21
|
export { searchEntities, searchEntitiesPrefix, getEntityByName, getAllEntitiesFromDb, getEntityIndexFromDb, getEntitiesByAlias, recordEntityMention, getEntityRecency, getAllRecency, setWriteState, getWriteState, deleteWriteState, setFlywheelConfig, getFlywheelConfig, getAllFlywheelConfig, deleteFlywheelConfig, saveFlywheelConfigToDb, loadFlywheelConfigFromDb, recordMergeDismissal, getDismissedMergePairs, getStateDbMetadata, isEntityDataStale, escapeFts5Query, rebuildEntitiesFts, stateDbExists, deleteStateDb, saveVaultIndexCache, loadVaultIndexCache, getVaultIndexCacheInfo, clearVaultIndexCache, isVaultIndexCacheValid, loadContentHashes, saveContentHashBatch, renameContentHash, } from './queries.js';
|
|
20
22
|
// Import for use in openStateDb
|
|
21
|
-
import { getStateDbPath, initSchema, deleteStateDbFiles,
|
|
23
|
+
import { getStateDbPath, initSchema, deleteStateDbFiles, preserveCorruptedDb, attemptSalvage } from './migrations.js';
|
|
22
24
|
// =============================================================================
|
|
23
25
|
// Factory
|
|
24
26
|
// =============================================================================
|
|
@@ -30,8 +32,9 @@ import { getStateDbPath, initSchema, deleteStateDbFiles, backupStateDb, preserve
|
|
|
30
32
|
*/
|
|
31
33
|
export function openStateDb(vaultPath) {
|
|
32
34
|
const dbPath = getStateDbPath(vaultPath);
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
+
// Note: safe backup with rotation is done AFTER open + integrity check
|
|
36
|
+
// by the caller (initializeVault), not here. This avoids overwriting
|
|
37
|
+
// good backups with a corrupt DB before we've verified it.
|
|
35
38
|
// Guard: Delete corrupted 0-byte database files
|
|
36
39
|
// This can happen when better-sqlite3 fails to compile (e.g., Node 24)
|
|
37
40
|
// and creates an empty file instead of a valid SQLite database
|
|
@@ -42,10 +45,15 @@ export function openStateDb(vaultPath) {
|
|
|
42
45
|
deleteStateDbFiles(dbPath);
|
|
43
46
|
}
|
|
44
47
|
}
|
|
48
|
+
const isNewDb = !fs.existsSync(dbPath);
|
|
45
49
|
let db;
|
|
46
50
|
try {
|
|
47
51
|
db = new Database(dbPath);
|
|
48
52
|
initSchema(db);
|
|
53
|
+
// If we just created a fresh DB but backup files exist, salvage from them
|
|
54
|
+
if (isNewDb) {
|
|
55
|
+
attemptSalvage(db, dbPath);
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
catch (err) {
|
|
51
59
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -61,6 +69,8 @@ export function openStateDb(vaultPath) {
|
|
|
61
69
|
deleteStateDbFiles(dbPath);
|
|
62
70
|
db = new Database(dbPath);
|
|
63
71
|
initSchema(db);
|
|
72
|
+
// Try to recover feedback data from backups or the corrupt file
|
|
73
|
+
attemptSalvage(db, dbPath);
|
|
64
74
|
}
|
|
65
75
|
else {
|
|
66
76
|
// Recoverable error (constraint violation, migration issue, etc.) — don't destroy the DB
|
package/dist/wikilinks.js
CHANGED
|
@@ -1083,6 +1083,9 @@ export function detectImplicitEntities(content, config = {}) {
|
|
|
1083
1083
|
// Length check
|
|
1084
1084
|
if (text.length < minEntityLength)
|
|
1085
1085
|
return true;
|
|
1086
|
+
// Must contain at least one letter — pure punctuation/symbols are never entities
|
|
1087
|
+
if (!/[a-zA-Z]/.test(text))
|
|
1088
|
+
return true;
|
|
1086
1089
|
// Common words
|
|
1087
1090
|
if (IMPLICIT_EXCLUDE_WORDS.has(text.toLowerCase()))
|
|
1088
1091
|
return true;
|
package/package.json
CHANGED