@aperdomoll90/ledger-ai 1.4.0 → 1.4.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.
Files changed (84) hide show
  1. package/dist/cli.js +177 -221
  2. package/dist/commands/add.js +51 -100
  3. package/dist/commands/backfill.js +55 -0
  4. package/dist/commands/backup.js +10 -10
  5. package/dist/commands/check.js +21 -29
  6. package/dist/commands/config.js +13 -12
  7. package/dist/commands/delete.js +22 -17
  8. package/dist/commands/eval-judge.js +11 -0
  9. package/dist/commands/eval.js +321 -0
  10. package/dist/commands/export.js +8 -10
  11. package/dist/commands/get.js +9 -0
  12. package/dist/commands/hunt.js +206 -0
  13. package/dist/commands/ingest.js +15 -14
  14. package/dist/commands/init.js +18 -20
  15. package/dist/commands/list.js +21 -7
  16. package/dist/commands/migrate.js +11 -11
  17. package/dist/commands/onboard.js +2 -2
  18. package/dist/commands/pull.js +3 -2
  19. package/dist/commands/push.js +8 -8
  20. package/dist/commands/restore.js +38 -38
  21. package/dist/commands/show.js +13 -16
  22. package/dist/commands/sync.js +58 -19
  23. package/dist/commands/tag.js +20 -14
  24. package/dist/commands/update.js +50 -18
  25. package/dist/commands/wizard.js +3 -3
  26. package/dist/lib/ai-search.js +163 -0
  27. package/dist/lib/audit.js +19 -0
  28. package/dist/lib/backfill.js +60 -0
  29. package/dist/lib/config.js +19 -2
  30. package/dist/lib/document-classification.js +5 -0
  31. package/dist/lib/document-fetching.js +77 -0
  32. package/dist/lib/document-operations.js +150 -0
  33. package/dist/lib/documents/classification.js +5 -0
  34. package/dist/lib/documents/fetching.js +89 -0
  35. package/dist/lib/documents/operations.js +304 -0
  36. package/dist/lib/domains.js +116 -0
  37. package/dist/lib/embeddings.js +190 -0
  38. package/dist/lib/errors.js +3 -1
  39. package/dist/lib/eval/eval-advanced.js +289 -0
  40. package/dist/lib/eval/eval-judge-session.js +233 -0
  41. package/dist/lib/eval/eval-store.js +105 -0
  42. package/dist/lib/eval/eval.js +303 -0
  43. package/dist/lib/file-writer.js +23 -0
  44. package/dist/lib/generators.js +44 -45
  45. package/dist/lib/hunter-db.js +235 -0
  46. package/dist/lib/hunter-rss.js +30 -0
  47. package/dist/lib/hunter-scoring.js +55 -0
  48. package/dist/lib/hunter-types.js +36 -0
  49. package/dist/lib/lint-configs.js +20 -0
  50. package/dist/lib/migrate.js +2 -2
  51. package/dist/lib/notes.js +173 -59
  52. package/dist/lib/observability.js +296 -0
  53. package/dist/lib/op-add-note-types.test.js +7 -6
  54. package/dist/lib/prompt.js +8 -8
  55. package/dist/lib/rate-limiter.js +103 -0
  56. package/dist/lib/search/ai-search.js +396 -0
  57. package/dist/lib/search/chunk-context-enrichment.js +155 -0
  58. package/dist/lib/search/embeddings.js +293 -0
  59. package/dist/lib/search/reranker.js +120 -0
  60. package/dist/lib/search/semantic-cache.js +53 -0
  61. package/dist/lib/type-registry.test.js +6 -6
  62. package/dist/mcp-server.js +553 -66
  63. package/dist/migrations/migrations/005-audit-log.sql +22 -0
  64. package/dist/migrations/migrations/005_opportunities.sql +48 -0
  65. package/dist/migrations/migrations/006-audited-operations.sql +235 -0
  66. package/dist/migrations/migrations/006_hunt_analytics.sql +38 -0
  67. package/dist/migrations/migrations/007-eval-golden-judgments.sql +119 -0
  68. package/dist/migrations/migrations/008-drop-expected-doc-ids.sql +9 -0
  69. package/dist/migrations/migrations/008-judge-helpers.sql +21 -0
  70. package/dist/migrations/migrations/009-semantic-cache.sql +216 -0
  71. package/dist/scripts/batch-grade.js +344 -0
  72. package/dist/scripts/benchmark-ingestion.js +376 -0
  73. package/dist/scripts/convert-judgments-to-graded.js +88 -0
  74. package/dist/scripts/diagnose-first-result.js +333 -0
  75. package/dist/scripts/drop-golden-query.js +53 -0
  76. package/dist/scripts/eval-search.js +115 -0
  77. package/dist/scripts/grade-unjudged-top1.js +138 -0
  78. package/dist/scripts/hunter-analytics.js +38 -0
  79. package/dist/scripts/hunter-cron.js +63 -0
  80. package/dist/scripts/hunter-purge.js +25 -0
  81. package/dist/scripts/migrate-v2.js +140 -0
  82. package/dist/scripts/reindex.js +74 -0
  83. package/dist/scripts/sync-local-docs.js +153 -0
  84. package/package.json +7 -1
@@ -0,0 +1,63 @@
1
+ import { loadConfig, loadConfigFile } from '../lib/config.js';
2
+ import { fetchAllFeeds } from '../lib/hunter-rss.js';
3
+ import { getExistingUrls, insertRawListings } from '../lib/hunter-db.js';
4
+ import { deduplicateListings } from '../lib/hunter-rss.js';
5
+ import { DEFAULT_HUNTER_CONFIG } from '../lib/hunter-types.js';
6
+ export function buildCronSummary(stats) {
7
+ return `[hunter-cron] ${stats.feedsChecked} feeds checked, ${stats.totalFound} listings found, ${stats.newStored} new stored, ${stats.skippedDuplicates} duplicates skipped`;
8
+ }
9
+ export async function runHunterCron() {
10
+ const config = loadConfig();
11
+ const fileConfig = loadConfigFile();
12
+ const hunterConfig = {
13
+ ...DEFAULT_HUNTER_CONFIG,
14
+ ...fileConfig.hunter,
15
+ };
16
+ const feeds = hunterConfig.feeds;
17
+ if (feeds.length === 0) {
18
+ console.error('[hunter-cron] No feeds configured. Run `ledger config set hunter.feeds`.');
19
+ return { feedsChecked: 0, totalFound: 0, newStored: 0, skippedDuplicates: 0 };
20
+ }
21
+ const allListings = await fetchAllFeeds(feeds);
22
+ const feedsChecked = feeds.length;
23
+ const totalFound = allListings.length;
24
+ if (totalFound === 0) {
25
+ const stats = { feedsChecked, totalFound: 0, newStored: 0, skippedDuplicates: 0 };
26
+ console.error(buildCronSummary(stats));
27
+ return stats;
28
+ }
29
+ const urls = allListings.map(l => l.url);
30
+ const existingUrls = await getExistingUrls(config.supabase, urls);
31
+ const newListings = deduplicateListings(allListings, existingUrls);
32
+ let newStored = 0;
33
+ if (newListings.length > 0) {
34
+ for (const feed of feeds) {
35
+ const feedListings = newListings.filter(l => l.source === feed.name);
36
+ if (feedListings.length > 0) {
37
+ const result = await insertRawListings(config.supabase, feedListings, feed.track);
38
+ newStored += result.inserted;
39
+ }
40
+ }
41
+ }
42
+ const stats = {
43
+ feedsChecked,
44
+ totalFound,
45
+ newStored,
46
+ skippedDuplicates: totalFound - newListings.length,
47
+ };
48
+ console.error(buildCronSummary(stats));
49
+ return stats;
50
+ }
51
+ const isDirectExecution = process.argv[1]?.endsWith('hunter-cron.js')
52
+ || process.argv[1]?.endsWith('hunter-cron.ts');
53
+ if (isDirectExecution) {
54
+ runHunterCron()
55
+ .then(stats => {
56
+ if (stats.newStored > 0)
57
+ console.log(JSON.stringify(stats));
58
+ })
59
+ .catch(err => {
60
+ console.error(`[hunter-cron] Error: ${err.message}`);
61
+ process.exit(1);
62
+ });
63
+ }
@@ -0,0 +1,25 @@
1
+ import { loadConfig, loadConfigFile } from '../lib/config.js';
2
+ import { purgeOldRejected } from '../lib/hunter-db.js';
3
+ import { DEFAULT_HUNTER_CONFIG } from '../lib/hunter-types.js';
4
+ export async function runPurge(purgeDays) {
5
+ const config = loadConfig();
6
+ const fileConfig = loadConfigFile();
7
+ const hunterConfig = {
8
+ ...DEFAULT_HUNTER_CONFIG,
9
+ ...fileConfig.hunter,
10
+ };
11
+ const days = purgeDays ?? hunterConfig.purge_after_days;
12
+ console.error(`[hunter-purge] Purging rejected opportunities older than ${days} days...`);
13
+ const count = await purgeOldRejected(config.supabase, days);
14
+ console.error(`[hunter-purge] Purged ${count} rejected opportunities.`);
15
+ return count;
16
+ }
17
+ const isDirectExecution = process.argv[1]?.endsWith('hunter-purge.js')
18
+ || process.argv[1]?.endsWith('hunter-purge.ts');
19
+ if (isDirectExecution) {
20
+ const days = process.argv[2] ? parseInt(process.argv[2], 10) : undefined;
21
+ runPurge(days).catch(err => {
22
+ console.error(`[hunter-purge] Error: ${err.message}`);
23
+ process.exit(1);
24
+ });
25
+ }
@@ -0,0 +1,140 @@
1
+ // migrate-v2.ts
2
+ // One-time migration: read from old `notes` table, write to new `documents` table.
3
+ // Each note becomes a document via createDocument() RPC (chunk + embed + audit).
4
+ //
5
+ // Run: npx tsx src/scripts/migrate-v2.ts
6
+ // Dry run: npx tsx src/scripts/migrate-v2.ts --dry-run
7
+ import 'dotenv/config';
8
+ import { createClient } from '@supabase/supabase-js';
9
+ import OpenAI from 'openai';
10
+ import { createDocument } from '../lib/document-operations.js';
11
+ // =============================================================================
12
+ // Setup
13
+ // =============================================================================
14
+ const supabaseUrl = process.env.SUPABASE_URL;
15
+ const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
16
+ const openaiKey = process.env.OPENAI_API_KEY;
17
+ if (!supabaseUrl || !supabaseKey || !openaiKey) {
18
+ console.error('Missing SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, or OPENAI_API_KEY');
19
+ process.exit(1);
20
+ }
21
+ const clients = {
22
+ supabase: createClient(supabaseUrl, supabaseKey),
23
+ openai: new OpenAI({ apiKey: openaiKey }),
24
+ };
25
+ const DRY_RUN = process.argv.includes('--dry-run');
26
+ // Valid values for constrained fields — reject anything outside these
27
+ const VALID_DOMAINS = new Set(['system', 'persona', 'workspace', 'project', 'general']);
28
+ const VALID_PROTECTIONS = new Set(['open', 'guarded', 'protected', 'immutable']);
29
+ const VALID_OWNER_TYPES = new Set(['system', 'user', 'team']);
30
+ const VALID_STATUSES = new Set(['idea', 'planning', 'active', 'done']);
31
+ const VALID_SOURCE_TYPES = new Set(['text', 'pdf', 'docx', 'spreadsheet', 'code', 'image', 'audio', 'video', 'web', 'email', 'slides', 'handwriting']);
32
+ // =============================================================================
33
+ // Field mapping
34
+ // =============================================================================
35
+ function mapNoteToDocument(note) {
36
+ const meta = note.metadata;
37
+ const domain = typeof meta.domain === 'string' && VALID_DOMAINS.has(meta.domain) ? meta.domain : 'general';
38
+ const protection = typeof meta.protection === 'string' && VALID_PROTECTIONS.has(meta.protection) ? meta.protection : 'open';
39
+ const ownerType = typeof meta.owner_type === 'string' && VALID_OWNER_TYPES.has(meta.owner_type) ? meta.owner_type : 'user';
40
+ const status = typeof meta.status === 'string' && VALID_STATUSES.has(meta.status) ? meta.status : undefined;
41
+ const sourceType = typeof meta.source_type === 'string' && VALID_SOURCE_TYPES.has(meta.source_type) ? meta.source_type : 'text';
42
+ return {
43
+ name: meta.upsert_key ?? `note-${note.id}`,
44
+ domain,
45
+ document_type: meta.type ?? 'reference',
46
+ content: note.content,
47
+ description: meta.description,
48
+ project: meta.project,
49
+ protection,
50
+ owner_type: ownerType,
51
+ owner_id: meta.owner_id,
52
+ is_auto_load: meta.auto_load === true,
53
+ source_type: sourceType,
54
+ file_path: meta.file_path,
55
+ file_permissions: meta.file_permissions,
56
+ agent: 'migrate-v2',
57
+ status,
58
+ skill_ref: meta.skill_ref,
59
+ embedding_model_id: 'openai/text-embedding-3-small',
60
+ };
61
+ }
62
+ // =============================================================================
63
+ // Migration
64
+ // =============================================================================
65
+ async function migrate() {
66
+ console.log(`\n${'='.repeat(60)}`);
67
+ console.log(`Ledger v2 Migration${DRY_RUN ? ' (DRY RUN)' : ''}`);
68
+ console.log(`${'='.repeat(60)}\n`);
69
+ // Read all notes
70
+ const { data: notes, error } = await clients.supabase
71
+ .from('notes')
72
+ .select('id, content, metadata, created_at')
73
+ .order('id', { ascending: true });
74
+ if (error) {
75
+ console.error('Failed to read notes:', error.message);
76
+ process.exit(1);
77
+ }
78
+ if (!notes || notes.length === 0) {
79
+ console.log('No notes found. Nothing to migrate.');
80
+ return;
81
+ }
82
+ console.log(`Found ${notes.length} notes to migrate.\n`);
83
+ const results = [];
84
+ let succeeded = 0;
85
+ let failed = 0;
86
+ let skipped = 0;
87
+ for (const note of notes) {
88
+ const props = mapNoteToDocument(note);
89
+ const index = notes.indexOf(note);
90
+ const total = notes.length;
91
+ const pct = Math.round(((index + 1) / total) * 100);
92
+ const filled = Math.round(pct / 2.5);
93
+ const bar = '█'.repeat(filled) + '░'.repeat(40 - filled);
94
+ if (DRY_RUN) {
95
+ process.stdout.write(`\r[${bar}] ${pct}% (${index + 1}/${total}) ${props.name.slice(0, 40).padEnd(40)}`);
96
+ results.push({ oldId: note.id, name: props.name });
97
+ skipped++;
98
+ continue;
99
+ }
100
+ try {
101
+ const newId = await createDocument(clients, props);
102
+ process.stdout.write(`\r[${bar}] ${pct}% (${index + 1}/${total}) ${props.name.slice(0, 40).padEnd(40)}`);
103
+ results.push({ oldId: note.id, name: props.name, newId });
104
+ succeeded++;
105
+ }
106
+ catch (err) {
107
+ const message = err.message;
108
+ process.stdout.write(`\r[${bar}] ${pct}% (${index + 1}/${total}) FAIL: ${props.name.slice(0, 30)}\n`);
109
+ results.push({ oldId: note.id, name: props.name, error: message });
110
+ failed++;
111
+ }
112
+ }
113
+ // Clear progress bar line
114
+ process.stdout.write('\n');
115
+ // Summary
116
+ console.log(`\n${'='.repeat(60)}`);
117
+ console.log('Migration Summary');
118
+ console.log(`${'='.repeat(60)}`);
119
+ console.log(`Total notes: ${notes.length}`);
120
+ if (DRY_RUN) {
121
+ console.log(`Dry run: ${skipped} would be migrated`);
122
+ }
123
+ else {
124
+ console.log(`Succeeded: ${succeeded}`);
125
+ console.log(`Failed: ${failed}`);
126
+ }
127
+ console.log();
128
+ if (failed > 0) {
129
+ console.log('Failed notes:');
130
+ for (const result of results) {
131
+ if (result.error) {
132
+ console.log(` note ${result.oldId} "${result.name}": ${result.error}`);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ migrate().catch((err) => {
138
+ console.error('Migration crashed:', err);
139
+ process.exit(1);
140
+ });
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env npx tsx
2
+ // reindex.ts
3
+ // Bulk re-index all documents through the new chunking + enrichment pipeline.
4
+ // Reads all active documents, re-chunks with recursive splitter, generates
5
+ // context summaries via gpt-4o-mini, re-embeds with enriched vectors, and
6
+ // calls document_update RPC (which versions old content before overwriting).
7
+ //
8
+ // Usage:
9
+ // npx tsx src/scripts/reindex.ts # dry-run (default — shows what would change)
10
+ // npx tsx src/scripts/reindex.ts --execute # actually re-index all documents
11
+ // npx tsx src/scripts/reindex.ts --id 42 # re-index one document (dry-run)
12
+ // npx tsx src/scripts/reindex.ts --id 42 --execute # re-index one document (write)
13
+ import 'dotenv/config';
14
+ import { loadConfig } from '../lib/config.js';
15
+ import { updateDocument } from '../lib/documents/operations.js';
16
+ async function main() {
17
+ const dryRun = !process.argv.includes('--execute');
18
+ const singleIdFlag = process.argv.indexOf('--id');
19
+ const singleId = singleIdFlag !== -1 ? Number(process.argv[singleIdFlag + 1]) : null;
20
+ const config = loadConfig();
21
+ const clients = {
22
+ supabase: config.supabase,
23
+ openai: config.openai,
24
+ };
25
+ console.error(dryRun ? '=== DRY RUN (pass --execute to write) ===' : '=== EXECUTING RE-INDEX ===');
26
+ // Fetch documents
27
+ let query = config.supabase
28
+ .from('documents')
29
+ .select('id, name, content, chunk_count')
30
+ .is('deleted_at', null)
31
+ .order('id', { ascending: true });
32
+ if (singleId !== null) {
33
+ query = query.eq('id', singleId);
34
+ }
35
+ const { data: documents, error } = await query;
36
+ if (error)
37
+ throw new Error(`Failed to fetch documents: ${error.message}`);
38
+ const documentList = documents;
39
+ console.error(`Found ${documentList.length} documents to re-index\n`);
40
+ let successCount = 0;
41
+ let failureCount = 0;
42
+ for (const document of documentList) {
43
+ const contentLength = document.content?.length ?? 0;
44
+ const estimatedChunks = Math.max(1, Math.ceil(contentLength / 1000));
45
+ if (dryRun) {
46
+ console.error(`[DRY] #${document.id} ${document.name} — ${contentLength} chars, ${document.chunk_count} chunks → ~${estimatedChunks} chunks`);
47
+ successCount++;
48
+ continue;
49
+ }
50
+ try {
51
+ console.error(`[${successCount + failureCount + 1}/${documentList.length}] #${document.id} ${document.name} — re-indexing...`);
52
+ await updateDocument(clients, {
53
+ id: document.id,
54
+ content: document.content,
55
+ agent: 'reindex-script',
56
+ });
57
+ successCount++;
58
+ console.error(` done (${contentLength} chars → ~${estimatedChunks} chunks)`);
59
+ }
60
+ catch (reindexError) {
61
+ failureCount++;
62
+ console.error(` FAILED: ${reindexError instanceof Error ? reindexError.message : String(reindexError)}`);
63
+ }
64
+ }
65
+ console.error(`\n=== Summary ===`);
66
+ console.error(`Success: ${successCount} | Failed: ${failureCount} | Total: ${documentList.length}`);
67
+ if (dryRun) {
68
+ console.error('This was a dry run. Pass --execute to actually re-index.');
69
+ }
70
+ }
71
+ main().catch((error) => {
72
+ console.error('Fatal error:', error);
73
+ process.exit(1);
74
+ });
@@ -0,0 +1,153 @@
1
+ // sync-local-docs.ts
2
+ // Push local docs/* files to their matching Ledger documents.
3
+ // Handles any file size — chunks, embeds, and updates via the proper
4
+ // updateDocument() pipeline (not the MCP tool).
5
+ //
6
+ // Usage:
7
+ // npx tsx src/scripts/sync-local-docs.ts # sync all known docs
8
+ // npx tsx src/scripts/sync-local-docs.ts --file docs/foo.md # sync one file
9
+ // npx tsx src/scripts/sync-local-docs.ts --dry-run # show what would sync
10
+ import 'dotenv/config';
11
+ import { createClient } from '@supabase/supabase-js';
12
+ import OpenAI from 'openai';
13
+ import { readFileSync, existsSync } from 'fs';
14
+ import { createDocument, updateDocument } from '../lib/documents/operations.js';
15
+ const supabaseUrl = process.env.SUPABASE_URL;
16
+ const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
17
+ const openaiKey = process.env.OPENAI_API_KEY;
18
+ if (!supabaseUrl || !supabaseKey || !openaiKey) {
19
+ console.error('Missing SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, or OPENAI_API_KEY');
20
+ process.exit(1);
21
+ }
22
+ const supabase = createClient(supabaseUrl, supabaseKey);
23
+ const openai = new OpenAI({ apiKey: openaiKey });
24
+ const clients = { supabase, openai, cohereApiKey: undefined };
25
+ const dryRun = process.argv.includes('--dry-run');
26
+ const fileArgIdx = process.argv.indexOf('--file');
27
+ const singleFile = fileArgIdx >= 0 ? process.argv[fileArgIdx + 1] : null;
28
+ // Map of local file paths to Ledger document names.
29
+ // Add entries here as docs are created.
30
+ const FILE_TO_DOC = {
31
+ 'docs/ledger-architecture.md': 'ledger-architecture',
32
+ 'docs/ledger-architecture-database.md': 'ledger-architecture-database',
33
+ 'docs/ledger-architecture-database-tables.md': 'ledger-architecture-database-tables',
34
+ 'docs/ledger-architecture-database-schemas.md': 'ledger-architecture-database-schemas',
35
+ 'docs/ledger-architecture-database-indexes.md': 'ledger-architecture-database-indexes',
36
+ 'docs/ledger-architecture-database-functions.md': 'ledger-architecture-database-functions',
37
+ 'docs/reference-rag-system-architecture.md': 'reference-rag-system-architecture',
38
+ 'docs/reference-rag-core-ingestion.md': 'reference-rag-core-ingestion',
39
+ 'docs/reference-rag-core-query-pipeline.md': 'reference-rag-core-query-pipeline',
40
+ 'docs/reference-rag-core-database-schemas.md': 'reference-rag-core-database-schemas',
41
+ 'docs/reference-rag-quality-evaluation.md': 'reference-rag-quality-evaluation',
42
+ 'docs/reference-rag-quality-improvement.md': 'reference-rag-quality-improvement',
43
+ 'docs/reference-rag-security-access-control.md': 'reference-rag-security-access-control',
44
+ 'docs/reference-rag-security-defenses.md': 'reference-rag-security-defenses',
45
+ 'docs/reference-rag-operations-observability.md': 'reference-rag-operations-observability',
46
+ 'docs/reference-rag-operations-scaling.md': 'reference-rag-operations-scaling',
47
+ 'docs/reference-rag-operations-deployment.md': 'reference-rag-operations-deployment',
48
+ 'docs/reference-rag-interface-api.md': 'reference-rag-interface-api',
49
+ };
50
+ // Metadata for new docs that don't exist in Ledger yet.
51
+ // Only used when creating; updates skip this.
52
+ const NEW_DOC_META = {
53
+ 'reference-rag-core-ingestion': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: ingestion pipeline (extraction, chunking, enrichment, embedding, storage)' },
54
+ 'reference-rag-core-query-pipeline': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: query pipeline (embedding, retrieval, reranking, generation)' },
55
+ 'reference-rag-core-database-schemas': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: production database schemas (SQL for all tables, indexes, functions, triggers, RLS)' },
56
+ 'reference-rag-quality-evaluation': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: evaluation framework (metrics, golden datasets, graded relevance, eval runners)' },
57
+ 'reference-rag-quality-improvement': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: quality optimization levers (reranker, enrichment, chunking, thresholds)' },
58
+ 'reference-rag-security-access-control': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: access control (RLS, RBAC, JWT auth, document permissions)' },
59
+ 'reference-rag-security-defenses': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: security defenses (prompt injection, PII, content sanitization)' },
60
+ 'reference-rag-operations-observability': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: observability (latency tracking, cost monitoring, cache analytics)' },
61
+ 'reference-rag-operations-scaling': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: scaling (connection pooling, index tuning, sharding, rate limiting)' },
62
+ 'reference-rag-operations-deployment': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: deployment (CI/CD, migrations, rollback, monitoring)' },
63
+ 'reference-rag-interface-api': { domain: 'general', documentType: 'knowledge-guide', description: 'RAG reference: API layer (MCP tools, REST endpoints, SDK design)' },
64
+ };
65
+ async function main() {
66
+ const filesToSync = singleFile
67
+ ? { [singleFile]: FILE_TO_DOC[singleFile] }
68
+ : FILE_TO_DOC;
69
+ let synced = 0;
70
+ let skipped = 0;
71
+ let errors = 0;
72
+ for (const [filePath, docName] of Object.entries(filesToSync)) {
73
+ if (!docName) {
74
+ console.error(` [SKIP] ${filePath} — not in FILE_TO_DOC mapping`);
75
+ skipped++;
76
+ continue;
77
+ }
78
+ if (!existsSync(filePath)) {
79
+ console.error(` [SKIP] ${filePath} — file not found`);
80
+ skipped++;
81
+ continue;
82
+ }
83
+ // Look up the document id by name
84
+ const { data: doc, error: lookupError } = await supabase
85
+ .from('documents')
86
+ .select('id, content_hash')
87
+ .eq('name', docName)
88
+ .single();
89
+ const localContent = readFileSync(filePath, 'utf8');
90
+ if (lookupError || !doc) {
91
+ // Document doesn't exist in Ledger yet. Create it if we have metadata.
92
+ const meta = NEW_DOC_META[docName];
93
+ if (!meta) {
94
+ console.error(` [SKIP] ${filePath} — no Ledger document named "${docName}" and no creation metadata`);
95
+ skipped++;
96
+ continue;
97
+ }
98
+ if (dryRun) {
99
+ console.log(` [DRY-CREATE] ${filePath} → NEW "${docName}" (${localContent.length} chars)`);
100
+ synced++;
101
+ continue;
102
+ }
103
+ console.log(` [CREATE] ${filePath} → "${docName}" (${localContent.length} chars)...`);
104
+ try {
105
+ const newId = await createDocument(clients, {
106
+ name: docName,
107
+ domain: meta.domain,
108
+ document_type: meta.documentType,
109
+ content: localContent,
110
+ description: meta.description,
111
+ project: meta.project,
112
+ agent: 'sync-local-docs',
113
+ });
114
+ console.log(` created #${newId}.`);
115
+ synced++;
116
+ }
117
+ catch (createError) {
118
+ const message = createError instanceof Error ? createError.message : String(createError);
119
+ console.error(` [ERR] ${filePath}: ${message}`);
120
+ errors++;
121
+ }
122
+ continue;
123
+ }
124
+ if (dryRun) {
125
+ console.log(` [DRY] ${filePath} → #${doc.id} ${docName} (${localContent.length} chars)`);
126
+ synced++;
127
+ continue;
128
+ }
129
+ console.log(` [SYNC] ${filePath} → #${doc.id} ${docName} (${localContent.length} chars)...`);
130
+ try {
131
+ await updateDocument(clients, {
132
+ id: doc.id,
133
+ content: localContent,
134
+ agent: 'sync-local-docs',
135
+ });
136
+ console.log(` done.`);
137
+ synced++;
138
+ }
139
+ catch (syncError) {
140
+ const message = syncError instanceof Error ? syncError.message : String(syncError);
141
+ console.error(` [ERR] ${filePath}: ${message}`);
142
+ errors++;
143
+ }
144
+ }
145
+ console.log('');
146
+ console.log(`Synced: ${synced}, Skipped: ${skipped}, Errors: ${errors}`);
147
+ if (dryRun)
148
+ console.log('[DRY RUN] No writes performed.');
149
+ }
150
+ main().catch((error) => {
151
+ console.error(error);
152
+ process.exit(1);
153
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aperdomoll90/ledger-ai",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "AI identity and memory system — portable persona, knowledge sync, semantic search across agents and devices",
5
5
  "type": "module",
6
6
  "bin": {
@@ -47,8 +47,14 @@
47
47
  "node": ">=20"
48
48
  },
49
49
  "dependencies": {
50
+ "@langfuse/openai": "^5.1.0",
51
+ "@langfuse/otel": "^5.1.0",
52
+ "@langfuse/tracing": "^5.1.0",
50
53
  "@modelcontextprotocol/sdk": "^1.27.1",
54
+ "@opentelemetry/api": "^1.9.1",
55
+ "@opentelemetry/sdk-trace-node": "^2.6.1",
51
56
  "@supabase/supabase-js": "^2.99.1",
57
+ "bottleneck": "^2.19.5",
52
58
  "commander": "^14.0.3",
53
59
  "dotenv": "^17.3.1",
54
60
  "openai": "^6.29.0",