nano-brain 2026.3.1 → 2026.3.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 +1 -1
- package/src/index.ts +144 -2
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -164,6 +164,7 @@ nano-brain - Memory system with hybrid search
|
|
|
164
164
|
--batch-size=<n> Vectors per batch (default: 500)
|
|
165
165
|
--dry-run Show counts without migrating
|
|
166
166
|
--activate Switch to Qdrant provider after migration
|
|
167
|
+
verify Compare SQLite vector counts against Qdrant
|
|
167
168
|
activate Switch config to use Qdrant as vector provider
|
|
168
169
|
cleanup Drop SQLite vector tables (requires Qdrant active with vectors)
|
|
169
170
|
Logging Config (~/.nano-brain/config.yml):
|
|
@@ -1336,7 +1337,7 @@ async function handleQdrant(globalOpts: GlobalOptions, commandArgs: string[]): P
|
|
|
1336
1337
|
const subcommand = commandArgs[0];
|
|
1337
1338
|
|
|
1338
1339
|
if (!subcommand) {
|
|
1339
|
-
console.error('Missing qdrant subcommand (up, down, status, migrate, activate, cleanup)');
|
|
1340
|
+
console.error('Missing qdrant subcommand (up, down, status, migrate, verify, activate, cleanup)');
|
|
1340
1341
|
process.exit(1);
|
|
1341
1342
|
}
|
|
1342
1343
|
|
|
@@ -1688,6 +1689,147 @@ async function handleQdrant(globalOpts: GlobalOptions, commandArgs: string[]): P
|
|
|
1688
1689
|
break;
|
|
1689
1690
|
}
|
|
1690
1691
|
|
|
1692
|
+
case 'verify': {
|
|
1693
|
+
const config = loadCollectionConfig(globalOpts.configPath);
|
|
1694
|
+
const vectorConfig = config?.vector;
|
|
1695
|
+
const qdrantUrl = vectorConfig?.url || 'http://localhost:6333';
|
|
1696
|
+
const resolvedUrl = resolveHostUrl(qdrantUrl);
|
|
1697
|
+
|
|
1698
|
+
try {
|
|
1699
|
+
const healthRes = await fetch(`${resolvedUrl}/healthz`);
|
|
1700
|
+
if (!healthRes.ok) {
|
|
1701
|
+
throw new Error(`HTTP ${healthRes.status}`);
|
|
1702
|
+
}
|
|
1703
|
+
} catch {
|
|
1704
|
+
console.error(`❌ Qdrant is not reachable at ${resolvedUrl}.`);
|
|
1705
|
+
console.error(' Run `npx nano-brain qdrant up` first.');
|
|
1706
|
+
console.error(' If running inside a container, Qdrant must be accessible at host.docker.internal:6333.');
|
|
1707
|
+
process.exit(1);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
const dataDir = DEFAULT_DB_DIR;
|
|
1711
|
+
if (!fs.existsSync(dataDir)) {
|
|
1712
|
+
console.log('No databases found in ' + dataDir);
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
const sqliteFiles = fs.readdirSync(dataDir).filter(f => f.endsWith('.sqlite'));
|
|
1717
|
+
if (sqliteFiles.length === 0) {
|
|
1718
|
+
console.log('No SQLite databases found');
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
console.log('Verifying migration...');
|
|
1723
|
+
console.log('═══════════════════════════════════════════════════');
|
|
1724
|
+
|
|
1725
|
+
const Database = (await import('better-sqlite3')).default;
|
|
1726
|
+
const sqliteVec = await import('sqlite-vec');
|
|
1727
|
+
|
|
1728
|
+
let totalVectors = 0;
|
|
1729
|
+
let dbCount = 0;
|
|
1730
|
+
const uniqueKeys = new Set<string>();
|
|
1731
|
+
let sawVectorTables = false;
|
|
1732
|
+
|
|
1733
|
+
for (const sqliteFile of sqliteFiles) {
|
|
1734
|
+
const dbPath = path.join(dataDir, sqliteFile);
|
|
1735
|
+
const db = new Database(dbPath);
|
|
1736
|
+
|
|
1737
|
+
try {
|
|
1738
|
+
sqliteVec.load(db);
|
|
1739
|
+
} catch {
|
|
1740
|
+
console.log(`[${sqliteFile}] sqlite-vec not available, skipping`);
|
|
1741
|
+
db.close();
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
let vectorCount = 0;
|
|
1746
|
+
try {
|
|
1747
|
+
const countStmt = db.prepare(`
|
|
1748
|
+
SELECT COUNT(*) as cnt FROM content_vectors cv
|
|
1749
|
+
JOIN vectors_vec vv ON cv.hash || ':' || cv.seq = vv.hash_seq
|
|
1750
|
+
`);
|
|
1751
|
+
const countRow = countStmt.get() as { cnt: number };
|
|
1752
|
+
vectorCount = countRow.cnt;
|
|
1753
|
+
} catch {
|
|
1754
|
+
console.log(`[${sqliteFile}] no vector tables, skipping`);
|
|
1755
|
+
db.close();
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
sawVectorTables = true;
|
|
1760
|
+
|
|
1761
|
+
if (vectorCount === 0) {
|
|
1762
|
+
console.log(`[${sqliteFile}] 0 vectors`);
|
|
1763
|
+
db.close();
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
const keyStmt = db.prepare(`
|
|
1768
|
+
SELECT DISTINCT cv.hash || ':' || cv.seq as key FROM content_vectors cv
|
|
1769
|
+
JOIN vectors_vec vv ON cv.hash || ':' || cv.seq = vv.hash_seq
|
|
1770
|
+
`);
|
|
1771
|
+
const rows = keyStmt.all() as Array<{ key: string }>;
|
|
1772
|
+
for (const row of rows) {
|
|
1773
|
+
uniqueKeys.add(row.key);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
console.log(`[${sqliteFile}] ${vectorCount.toLocaleString()} vectors in SQLite`);
|
|
1777
|
+
totalVectors += vectorCount;
|
|
1778
|
+
dbCount++;
|
|
1779
|
+
db.close();
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
if (!sawVectorTables || totalVectors === 0) {
|
|
1783
|
+
let pointsCount = 0;
|
|
1784
|
+
try {
|
|
1785
|
+
const collectionRes = await fetch(`${resolvedUrl}/collections/nano-brain`);
|
|
1786
|
+
if (collectionRes.ok) {
|
|
1787
|
+
const collectionData = await collectionRes.json();
|
|
1788
|
+
const result = collectionData.result || collectionData;
|
|
1789
|
+
pointsCount = result.points_count ?? result.vectors_count ?? 0;
|
|
1790
|
+
}
|
|
1791
|
+
} catch {
|
|
1792
|
+
console.error('❌ Failed to check Qdrant collection');
|
|
1793
|
+
process.exit(1);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
console.log('SQLite: no vector data (already cleaned up)');
|
|
1797
|
+
console.log(`Qdrant: ${pointsCount.toLocaleString()} vectors`);
|
|
1798
|
+
console.log(`ℹ️ Cannot verify — SQLite vectors already cleaned. Qdrant has ${pointsCount.toLocaleString()} vectors.`);
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
console.log('───────────────────────────────────────────────────');
|
|
1803
|
+
console.log(`SQLite total: ${totalVectors.toLocaleString()} vectors (across ${dbCount} databases)`);
|
|
1804
|
+
|
|
1805
|
+
let pointsCount = 0;
|
|
1806
|
+
try {
|
|
1807
|
+
const collectionRes = await fetch(`${resolvedUrl}/collections/nano-brain`);
|
|
1808
|
+
if (collectionRes.ok) {
|
|
1809
|
+
const collectionData = await collectionRes.json();
|
|
1810
|
+
const result = collectionData.result || collectionData;
|
|
1811
|
+
pointsCount = result.points_count ?? result.vectors_count ?? 0;
|
|
1812
|
+
}
|
|
1813
|
+
} catch {
|
|
1814
|
+
console.error('❌ Failed to check Qdrant collection');
|
|
1815
|
+
process.exit(1);
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
const uniqueCount = uniqueKeys.size;
|
|
1819
|
+
console.log(`Qdrant total: ${pointsCount.toLocaleString()} unique vectors`);
|
|
1820
|
+
const difference = totalVectors - pointsCount;
|
|
1821
|
+
console.log(`Difference: ${difference.toLocaleString()} (expected — cross-workspace duplicates share the same hash:seq key)`);
|
|
1822
|
+
console.log('');
|
|
1823
|
+
|
|
1824
|
+
if (uniqueCount > pointsCount) {
|
|
1825
|
+
const missing = uniqueCount - pointsCount;
|
|
1826
|
+
console.log(`⚠️ Found ${missing.toLocaleString()} vectors in SQLite not present in Qdrant. Run \`npx nano-brain qdrant migrate\` to sync.`);
|
|
1827
|
+
} else {
|
|
1828
|
+
console.log('✅ Migration verified: Qdrant has all unique vectors');
|
|
1829
|
+
}
|
|
1830
|
+
break;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1691
1833
|
case 'activate': {
|
|
1692
1834
|
const config = loadCollectionConfig(globalOpts.configPath);
|
|
1693
1835
|
const vectorConfig = config?.vector;
|
|
@@ -1841,7 +1983,7 @@ async function handleQdrant(globalOpts: GlobalOptions, commandArgs: string[]): P
|
|
|
1841
1983
|
|
|
1842
1984
|
default:
|
|
1843
1985
|
console.error(`Unknown qdrant subcommand: ${subcommand}`);
|
|
1844
|
-
console.error('Available: up, down, status, migrate, activate, cleanup');
|
|
1986
|
+
console.error('Available: up, down, status, migrate, verify, activate, cleanup');
|
|
1845
1987
|
process.exit(1);
|
|
1846
1988
|
}
|
|
1847
1989
|
}
|