init
This commit is contained in:
commit
0964819cdc
34 files changed
+7912
No files matched your search
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Live smoke test: the real pipeline against the real embedding endpoint.
|
||||
*
|
||||
* Not part of `npm test`, because it needs a running Ollama and a model that is
|
||||
* already pulled. It exists because the unit tests can only prove the code does
|
||||
* what the test author believed; this proves the retrieval actually returns the
|
||||
* right file for a question about it, which is the only claim that matters.
|
||||
*
|
||||
* node scripts/smoke.mjs [workspace] [query]
|
||||
*/
|
||||
|
||||
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { chunkFile, embedText } from '../lib/chunk.js'
|
||||
import { createEmbedder } from '../lib/embed.js'
|
||||
import { rankChunks } from '../lib/rank.js'
|
||||
import { RecallIndex } from '../lib/store.js'
|
||||
import { listCodeFiles } from '../lib/walk.js'
|
||||
|
||||
const API_BASE = process.env.RECALL_API_BASE ?? 'http://10.50.1.2:11434'
|
||||
const MODEL = process.env.RECALL_MODEL ?? 'unclemusclez/jina-embeddings-v2-base-code:latest'
|
||||
const DIMENSIONS = Number(process.env.RECALL_DIMENSIONS ?? 768)
|
||||
|
||||
const FIXTURE = {
|
||||
'src/services/auth/session.ts': `import { createHash, randomBytes } from 'node:crypto'
|
||||
|
||||
const SESSION_TTL_MS = 1000 * 60 * 60 * 12
|
||||
|
||||
export interface SessionRecord {
|
||||
userId: string
|
||||
issuedAt: number
|
||||
expiresAt: number
|
||||
}
|
||||
|
||||
export function issueSession(userId: string): SessionRecord {
|
||||
const now = Date.now()
|
||||
return { userId, issuedAt: now, expiresAt: now + SESSION_TTL_MS }
|
||||
}
|
||||
|
||||
export function isExpired(record: SessionRecord, now = Date.now()): boolean {
|
||||
return record.expiresAt <= now
|
||||
}
|
||||
|
||||
export function rotateToken(record: SessionRecord): string {
|
||||
if (isExpired(record)) throw new Error('cannot rotate an expired session')
|
||||
return randomBytes(32).toString('hex')
|
||||
}
|
||||
|
||||
export function fingerprint(token: string): string {
|
||||
return createHash('sha256').update(token).digest('hex').slice(0, 16)
|
||||
}
|
||||
`,
|
||||
'src/services/weather/forecast.ts': `export interface ForecastDay {
|
||||
date: string
|
||||
minC: number
|
||||
maxC: number
|
||||
rainMm: number
|
||||
}
|
||||
|
||||
export function averageTemperature(days: ForecastDay[]): number {
|
||||
if (days.length === 0) return 0
|
||||
const total = days.reduce((sum, day) => sum + (day.minC + day.maxC) / 2, 0)
|
||||
return total / days.length
|
||||
}
|
||||
|
||||
export function totalRainfall(days: ForecastDay[]): number {
|
||||
return days.reduce((sum, day) => sum + day.rainMm, 0)
|
||||
}
|
||||
`,
|
||||
'src/store/sqlite.ts': `import { DatabaseSync } from 'node:sqlite'
|
||||
|
||||
export function openDatabase(path: string): DatabaseSync {
|
||||
const db = new DatabaseSync(path)
|
||||
db.exec('PRAGMA journal_mode = WAL')
|
||||
return db
|
||||
}
|
||||
|
||||
export function pruneRows(db: DatabaseSync, table: string, olderThan: number): number {
|
||||
const statement = db.prepare(\`DELETE FROM \${table} WHERE created_at < ?\`)
|
||||
const result = statement.run(olderThan)
|
||||
return Number(result.changes)
|
||||
}
|
||||
`,
|
||||
}
|
||||
|
||||
/** Write the fixture into a fresh temporary directory. */
|
||||
async function makeWorkspace() {
|
||||
const root = await mkdtemp(join(tmpdir(), 'recall-smoke-'))
|
||||
for (const [path, content] of Object.entries(FIXTURE)) {
|
||||
const absolute = join(root, path)
|
||||
await mkdir(join(absolute, '..'), { recursive: true })
|
||||
await writeFile(absolute, content, 'utf8')
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const query = process.argv[3] ?? 'how long does a user session stay valid before it expires'
|
||||
const root = process.argv[2] ?? (await makeWorkspace())
|
||||
const indexDir = await mkdtemp(join(tmpdir(), 'recall-index-'))
|
||||
const database = join(indexDir, 'index.sqlite')
|
||||
|
||||
const embedder = createEmbedder({ apiBase: API_BASE, model: MODEL, dimensions: DIMENSIONS })
|
||||
const index = new RecallIndex(database)
|
||||
index.open({ model: MODEL, dimensions: DIMENSIONS, apiBase: API_BASE })
|
||||
|
||||
const started = Date.now()
|
||||
const walk = await listCodeFiles(root)
|
||||
let chunks = 0
|
||||
for (const file of walk.files) {
|
||||
const { readFile } = await import('node:fs/promises')
|
||||
const content = await readFile(file.absolute, 'utf8')
|
||||
const split = chunkFile({ path: file.path, content })
|
||||
if (split.chunks.length === 0) continue
|
||||
const vectors = await embedder.embed(split.chunks.map((chunk) => embedText(file.path, chunk)))
|
||||
index.replaceFile(file.path, 'smoke', Buffer.byteLength(content), split.chunks.map((c, at) => ({ ...c, vec: vectors[at] })))
|
||||
chunks += split.chunks.length
|
||||
}
|
||||
const indexMs = Date.now() - started
|
||||
|
||||
const matrix = index.matrix()
|
||||
const queryVector = await embedder.embedOne(query)
|
||||
const searchStart = Date.now()
|
||||
const ranked = rankChunks({ matrix, queryVector, query, limit: 5 })
|
||||
const searchMs = Date.now() - searchStart
|
||||
|
||||
console.log(`workspace : ${root}`)
|
||||
console.log(`files : ${walk.files.length} chunks: ${chunks} indexed in ${indexMs}ms`)
|
||||
console.log(`query : ${query}`)
|
||||
console.log(`search : ${searchMs}ms batch endpoint: ${embedder.batchEnabled}`)
|
||||
console.log('')
|
||||
for (const [at, result] of ranked.results.entries()) {
|
||||
console.log(`${at + 1}. ${result.path}:${result.startLine}-${result.endLine} similarity ${result.similarity}`)
|
||||
console.log(` ${result.excerpt.split('\n')[0]}`)
|
||||
}
|
||||
console.log('')
|
||||
const top = ranked.results[0]
|
||||
const expected = 'src/services/auth/session.ts'
|
||||
console.log(top?.path === expected ? `PASS top hit is ${expected}` : `FAIL top hit was ${top?.path}, expected ${expected}`)
|
||||
|
||||
index.close()
|
||||
await rm(indexDir, { recursive: true, force: true })
|
||||
if (process.argv[2] === undefined) await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
await main()
|
||||
Reference in new issue
Block a user