feat(sessions): indexação multi-fonte Hermes + OpenCode com fix TypeScript

- Novos indexadores hermes-indexer.ts e opencode-indexer.ts para unificar
  sessões Claude, Hermes Agent e OpenCode num único sessions.db
- SessionMeta alargado: source (obrigatório), model, input/output_tokens,
  estimated_cost; project_path/jsonl_path agora nullable
- Fix TS: tipos explícitos, guard jsonl_path null, dependências instaladas

Security Audit (Regra #47):
- npm audit executado — 0 vulnerabilities após fix
- vite 7→8.0.16 (breaking upgrade, resolve esbuild CVE GHSA-gv7w-rqvm-qjhr)
- vitest 4.0.18→4.1.9 (resolve esbuild interno CVE GHSA-gv7w-rqvm-qjhr)
- shell-quote override ^1.8.4 via package.json#overrides (CVE GHSA-w7jw-789q-3m8p)
- react-router, joi, qs, form-data, ip-address, js-yaml resolvidos via npm audit fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 19:41:32 +01:00
parent 9f3d14dc51
commit f733998945
18 changed files with 1475 additions and 678 deletions
+68 -24
View File
@@ -3,6 +3,8 @@ import { homedir } from 'os'
import { join } from 'path'
import { parseSessionFile } from './parser.js'
import { openSessionsDb, type SessionsDb } from './db.js'
import { indexHermesSessions } from './hermes-indexer.js'
import { indexOCSessions } from './opencode-indexer.js'
import type { SessionMeta } from '../../types/session.js'
export const PROJECTS_ROOT = join(homedir(), '.claude', 'projects')
@@ -52,46 +54,88 @@ export async function indexFile(db: SessionsDb, path: string): Promise<void> {
export interface IndexAllOptions {
dbPath?: string
sources?: ('claude' | 'hermes' | 'opencode')[]
onProgress?: (done: number, total: number) => void
}
/**
* Full scan: percorre todos os JSONL e faz upsert em lote (batch 50 via transacção).
* Full scan: indexa Claude Code (JSONL) + Hermes (state.db) + OpenCode (opencode.db).
*/
export async function indexAll(
options: IndexAllOptions = {},
): Promise<{ indexed: number; failed: number }> {
const db = openSessionsDb(options.dbPath ?? DEFAULT_DB_PATH)
const files = findAllJsonl()
const BATCH = 50
const sources = options.sources ?? ['claude', 'hermes', 'opencode']
let indexed = 0
let failed = 0
let batch: SessionMeta[] = []
const dbPath = options.dbPath ?? DEFAULT_DB_PATH
try {
for (let i = 0; i < files.length; i++) {
try {
const { meta } = await parseSessionFile(files[i])
batch.push(meta)
if (batch.length >= BATCH) {
db.upsertMany(batch)
indexed += batch.length
batch = []
if (sources.includes('claude')) {
const db = openSessionsDb(dbPath)
const files = findAllJsonl()
const BATCH = 50
let batch: SessionMeta[] = []
try {
for (let i = 0; i < files.length; i++) {
try {
const { meta } = await parseSessionFile(files[i])
meta.source = 'claude'
meta.model = meta.model ?? null
meta.ended_at = meta.ended_at ?? null
meta.duration_sec = meta.duration_sec ?? null
meta.input_tokens = meta.input_tokens ?? null
meta.output_tokens = meta.output_tokens ?? null
meta.estimated_cost = meta.estimated_cost ?? null
meta.first_prompt = meta.first_prompt ?? null
meta.permission_mode = meta.permission_mode ?? null
meta.project_path = meta.project_path ?? null
meta.project_slug = meta.project_slug ?? null
meta.jsonl_path = meta.jsonl_path ?? null
batch.push(meta)
if (batch.length >= BATCH) {
db.upsertMany(batch)
indexed += batch.length
batch = []
}
} catch (err) {
failed++
console.error(`[indexer] erro em ${files[i]}:`, err)
}
if (options.onProgress) {
options.onProgress(indexed + failed + batch.length, files.length)
}
} catch (err) {
failed++
console.error(`[indexer] erro em ${files[i]}:`, err)
}
if (options.onProgress) {
options.onProgress(indexed + failed + batch.length, files.length)
if (batch.length > 0) {
db.upsertMany(batch)
indexed += batch.length
}
} finally {
db.close()
}
if (batch.length > 0) {
db.upsertMany(batch)
indexed += batch.length
}
if (sources.includes('hermes')) {
try {
const result = await indexHermesSessions({ dbPath })
indexed += result.indexed
failed += result.failed
console.log(`[indexer] Hermes: ${result.indexed} indexadas, ${result.failed} falhas`)
} catch (err) {
failed++
console.error('[indexer] Erro ao indexar Hermes:', err)
}
}
if (sources.includes('opencode')) {
try {
const result = await indexOCSessions({ dbPath })
indexed += result.indexed
failed += result.failed
console.log(`[indexer] OpenCode: ${result.indexed} indexadas, ${result.failed} falhas`)
} catch (err) {
failed++
console.error('[indexer] Erro ao indexar OpenCode:', err)
}
} finally {
db.close()
}
return { indexed, failed }