Skip to content

Commit 73c16a0

Browse files
authored
release: batch 3 — ingest force, sidecar search, cost & insights (v0.6.0)
release: batch 3 — ingest force, sidecar search, cost & insights (v0.6.0)
2 parents dc1cb53 + f065d14 commit 73c16a0

17 files changed

Lines changed: 2559 additions & 29 deletions

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
## [0.6.0] - 2026-03-09
2+
3+
### Added
4+
5+
- feat(ingest): `--force` flag for re-ingesting sessions (deletes sidecars, re-extracts)
6+
- feat(db): sidecar content searchable via unified FTS — artifacts, thinking blocks, attachments, voice notes
7+
- feat(insights): cost & usage analytics module with CLI commands (`smriti insights`)
8+
9+
### Database
10+
11+
- New tables: `smriti_artifacts`, `smriti_thinking`, `smriti_attachments`, `smriti_voice_notes`
12+
- FTS migration to v2 includes sidecar content
13+
14+
---
15+
116
## [0.5.1] - 2026-03-09
217

318
### Fixed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "smriti",
3-
"version": "0.5.1",
3+
"version": "0.6.0",
44
"description": "Smriti - Unified memory layer across all AI agents",
55
"type": "module",
66
"bin": {

src/db.ts

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,51 @@ export function initializeSmritiTables(db: Database): void {
353353
PRIMARY KEY (language, version, framework)
354354
);
355355
356+
-- Artifacts from Claude.ai conversations (code, documents, diagrams)
357+
CREATE TABLE IF NOT EXISTS smriti_artifacts (
358+
id INTEGER PRIMARY KEY AUTOINCREMENT,
359+
message_id INTEGER NOT NULL,
360+
session_id TEXT NOT NULL,
361+
artifact_id TEXT,
362+
type TEXT,
363+
title TEXT,
364+
command TEXT,
365+
language TEXT,
366+
content TEXT,
367+
created_at TEXT NOT NULL
368+
);
369+
370+
-- Thinking blocks (Claude's internal reasoning)
371+
CREATE TABLE IF NOT EXISTS smriti_thinking (
372+
id INTEGER PRIMARY KEY AUTOINCREMENT,
373+
message_id INTEGER NOT NULL,
374+
session_id TEXT NOT NULL,
375+
thinking TEXT NOT NULL,
376+
created_at TEXT NOT NULL
377+
);
378+
379+
-- File attachments with extracted content
380+
CREATE TABLE IF NOT EXISTS smriti_attachments (
381+
id INTEGER PRIMARY KEY AUTOINCREMENT,
382+
message_id INTEGER NOT NULL,
383+
session_id TEXT NOT NULL,
384+
file_name TEXT,
385+
file_type TEXT,
386+
file_size INTEGER,
387+
content TEXT,
388+
created_at TEXT NOT NULL
389+
);
390+
391+
-- Voice note transcripts
392+
CREATE TABLE IF NOT EXISTS smriti_voice_notes (
393+
id INTEGER PRIMARY KEY AUTOINCREMENT,
394+
message_id INTEGER NOT NULL,
395+
session_id TEXT NOT NULL,
396+
title TEXT,
397+
transcript TEXT,
398+
created_at TEXT NOT NULL
399+
);
400+
356401
-- Indexes (original)
357402
CREATE INDEX IF NOT EXISTS idx_smriti_session_meta_agent
358403
ON smriti_session_meta(agent_id);
@@ -390,6 +435,18 @@ export function initializeSmritiTables(db: Database): void {
390435
ON smriti_git_operations(operation);
391436
CREATE INDEX IF NOT EXISTS idx_smriti_rule_cache_language
392437
ON smriti_rule_cache(language);
438+
439+
-- Indexes (claude-web sidecar tables)
440+
CREATE INDEX IF NOT EXISTS idx_smriti_artifacts_session
441+
ON smriti_artifacts(session_id);
442+
CREATE INDEX IF NOT EXISTS idx_smriti_artifacts_type
443+
ON smriti_artifacts(type);
444+
CREATE INDEX IF NOT EXISTS idx_smriti_thinking_session
445+
ON smriti_thinking(session_id);
446+
CREATE INDEX IF NOT EXISTS idx_smriti_attachments_session
447+
ON smriti_attachments(session_id);
448+
CREATE INDEX IF NOT EXISTS idx_smriti_voice_notes_session
449+
ON smriti_voice_notes(session_id);
393450
`);
394451
}
395452

@@ -429,6 +486,18 @@ const DEFAULT_AGENTS = [
429486
log_pattern: "*/chatSessions/*.json",
430487
parser: "copilot",
431488
},
489+
{
490+
id: "generic",
491+
display_name: "Generic Import",
492+
log_pattern: null,
493+
parser: "generic",
494+
},
495+
{
496+
id: "claude-web",
497+
display_name: "Claude.ai",
498+
log_pattern: null,
499+
parser: "claude-web",
500+
},
432501
] as const;
433502

434503
/** Default category taxonomy */
@@ -503,6 +572,87 @@ export function seedDefaults(db: Database): void {
503572
}
504573
}
505574

575+
// =============================================================================
576+
// FTS Migration (sidecar content search)
577+
// =============================================================================
578+
579+
/**
580+
* Migrate memory_fts to v2: adds thinking, artifacts, attachments, voice_notes columns.
581+
* Drops old FTS table + triggers, rebuilds index from existing data.
582+
* Idempotent — skips if already migrated.
583+
*/
584+
export function migrateFTSToV2(db: Database): void {
585+
// Check if migration needed by looking for the 'thinking' column
586+
const cols = db.prepare("PRAGMA table_info(memory_fts)").all() as { name: string }[];
587+
if (cols.some((c) => c.name === "thinking")) return;
588+
589+
console.log("Migrating memory_fts to include sidecar content...");
590+
591+
// 1. Drop old triggers
592+
db.exec(`DROP TRIGGER IF EXISTS memory_messages_ai`);
593+
db.exec(`DROP TRIGGER IF EXISTS memory_messages_ad`);
594+
595+
// 2. Drop old FTS table
596+
db.exec(`DROP TABLE IF EXISTS memory_fts`);
597+
598+
// 3. Create new FTS table with sidecar columns
599+
db.exec(`
600+
CREATE VIRTUAL TABLE memory_fts USING fts5(
601+
session_title, role, content,
602+
thinking, artifacts, attachments, voice_notes,
603+
tokenize='porter unicode61'
604+
)
605+
`);
606+
607+
// 4. Rebuild index from existing messages + sidecar data
608+
db.exec(`
609+
INSERT INTO memory_fts(
610+
rowid, session_title, role, content,
611+
thinking, artifacts, attachments, voice_notes
612+
)
613+
SELECT
614+
mm.id,
615+
COALESCE(ms.title, ''),
616+
mm.role,
617+
mm.content,
618+
COALESCE((SELECT GROUP_CONCAT(thinking, ' ') FROM smriti_thinking WHERE message_id = mm.id), ''),
619+
COALESCE((SELECT GROUP_CONCAT(content, ' ') FROM smriti_artifacts WHERE message_id = mm.id), ''),
620+
COALESCE((SELECT GROUP_CONCAT(content, ' ') FROM smriti_attachments WHERE message_id = mm.id), ''),
621+
COALESCE((SELECT GROUP_CONCAT(transcript, ' ') FROM smriti_voice_notes WHERE message_id = mm.id), '')
622+
FROM memory_messages mm
623+
LEFT JOIN memory_sessions ms ON ms.id = mm.session_id
624+
`);
625+
626+
// 5. Create new triggers with sidecar columns
627+
db.exec(`
628+
CREATE TRIGGER memory_messages_ai AFTER INSERT ON memory_messages
629+
BEGIN
630+
INSERT INTO memory_fts(
631+
rowid, session_title, role, content,
632+
thinking, artifacts, attachments, voice_notes
633+
)
634+
SELECT
635+
new.id,
636+
COALESCE((SELECT title FROM memory_sessions WHERE id = new.session_id), ''),
637+
new.role,
638+
new.content,
639+
COALESCE((SELECT GROUP_CONCAT(thinking, ' ') FROM smriti_thinking WHERE message_id = new.id), ''),
640+
COALESCE((SELECT GROUP_CONCAT(content, ' ') FROM smriti_artifacts WHERE message_id = new.id), ''),
641+
COALESCE((SELECT GROUP_CONCAT(content, ' ') FROM smriti_attachments WHERE message_id = new.id), ''),
642+
COALESCE((SELECT GROUP_CONCAT(transcript, ' ') FROM smriti_voice_notes WHERE message_id = new.id), '');
643+
END
644+
`);
645+
646+
db.exec(`
647+
CREATE TRIGGER memory_messages_ad AFTER DELETE ON memory_messages
648+
BEGIN
649+
DELETE FROM memory_fts WHERE rowid = old.id;
650+
END
651+
`);
652+
653+
console.log("Migration complete.");
654+
}
655+
506656
// =============================================================================
507657
// Convenience
508658
// =============================================================================
@@ -514,6 +664,7 @@ export function initSmriti(dbPath?: string): Database {
514664
// so we just need to initialize Smriti tables
515665
initializeSmritiTables(db);
516666
seedDefaults(db);
667+
migrateFTSToV2(db);
517668
return db;
518669
}
519670

@@ -802,3 +953,68 @@ export function insertGitOperation(
802953
VALUES(?, ?, ?, ?, ?, ?, ?, ?)`
803954
).run(messageId, sessionId, operation, branch, prUrl, prNumber, details, createdAt);
804955
}
956+
957+
// =============================================================================
958+
// Claude-Web Sidecar Insert Helpers
959+
// =============================================================================
960+
961+
export function insertArtifact(
962+
db: Database,
963+
messageId: number,
964+
sessionId: string,
965+
artifactId: string | null,
966+
type: string | null,
967+
title: string | null,
968+
command: string | null,
969+
language: string | null,
970+
content: string | null,
971+
createdAt: string
972+
): void {
973+
db.prepare(
974+
`INSERT INTO smriti_artifacts (message_id, session_id, artifact_id, type, title, command, language, content, created_at)
975+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
976+
).run(messageId, sessionId, artifactId, type, title, command, language, content, createdAt);
977+
}
978+
979+
export function insertThinking(
980+
db: Database,
981+
messageId: number,
982+
sessionId: string,
983+
thinking: string,
984+
createdAt: string
985+
): void {
986+
db.prepare(
987+
`INSERT INTO smriti_thinking (message_id, session_id, thinking, created_at)
988+
VALUES (?, ?, ?, ?)`
989+
).run(messageId, sessionId, thinking, createdAt);
990+
}
991+
992+
export function insertAttachment(
993+
db: Database,
994+
messageId: number,
995+
sessionId: string,
996+
fileName: string | null,
997+
fileType: string | null,
998+
fileSize: number | null,
999+
content: string | null,
1000+
createdAt: string
1001+
): void {
1002+
db.prepare(
1003+
`INSERT INTO smriti_attachments (message_id, session_id, file_name, file_type, file_size, content, created_at)
1004+
VALUES (?, ?, ?, ?, ?, ?, ?)`
1005+
).run(messageId, sessionId, fileName, fileType, fileSize, content, createdAt);
1006+
}
1007+
1008+
export function insertVoiceNote(
1009+
db: Database,
1010+
messageId: number,
1011+
sessionId: string,
1012+
title: string | null,
1013+
transcript: string,
1014+
createdAt: string
1015+
): void {
1016+
db.prepare(
1017+
`INSERT INTO smriti_voice_notes (message_id, session_id, title, transcript, created_at)
1018+
VALUES (?, ?, ?, ?, ?)`
1019+
).run(messageId, sessionId, title, transcript, createdAt);
1020+
}

0 commit comments

Comments
 (0)