Skip to content

Commit 2b5edbf

Browse files
prevent self-referential model contexts
1 parent 5546385 commit 2b5edbf

1 file changed

Lines changed: 15 additions & 9 deletions

File tree

global-template/docgen/lib/context.mjs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,17 @@ function rowsForFacts(db, query, limit) {
2222
catch { return db.prepare('SELECT id,kind,name,path,line,statement,snippet,metadata,content_hash FROM facts WHERE lower(name||\' \'||statement||\' \'||path) LIKE ? LIMIT ?').all(`%${String(query).toLowerCase().split(/\s+/)[0] ?? ''}%`, limit); }
2323
}
2424

25-
function rowsForModels(db, query, limit) {
26-
const match = ftsQuery(query);
27-
if (!match) return db.prepare('SELECT id,model,kind,name,statement,classification,confidence,evidence,payload,content_hash FROM model_items LIMIT ?').all(limit);
28-
try { return db.prepare(`SELECT m.id,m.model,m.kind,m.name,m.statement,m.classification,m.confidence,m.evidence,m.payload,m.content_hash,bm25(model_fts) score FROM model_fts JOIN model_items m ON m.id=model_fts.id WHERE model_fts MATCH ? ORDER BY score LIMIT ?`).all(match, limit); }
29-
catch { return []; }
25+
function rowsForModels(db, query, limit, allowedModels) {
26+
if (allowedModels === false) return [];
27+
const allowed = Array.isArray(allowedModels) ? new Set(allowedModels) : null; const fetchLimit = allowed ? Math.max(limit * 4, 1000) : limit;
28+
const match = ftsQuery(query); let rows = [];
29+
if (!match) rows = db.prepare('SELECT id,model,kind,name,statement,classification,confidence,evidence,payload,content_hash FROM model_items LIMIT ?').all(fetchLimit);
30+
else {
31+
try { rows = db.prepare(`SELECT m.id,m.model,m.kind,m.name,m.statement,m.classification,m.confidence,m.evidence,m.payload,m.content_hash,bm25(model_fts) score FROM model_fts JOIN model_items m ON m.id=model_fts.id WHERE model_fts MATCH ? ORDER BY score LIMIT ?`).all(match, fetchLimit); }
32+
catch { rows = []; }
33+
}
34+
if (allowed) rows = rows.filter((row) => allowed.has(row.model));
35+
return rows.slice(0, limit);
3036
}
3137

3238
function compactFact(row) { return { id: row.id, kind: row.kind, name: row.name, path: row.path, line: row.line, statement: row.statement, snippet: row.snippet, metadata: JSON.parse(row.metadata || '{}'), hash: row.content_hash }; }
@@ -43,15 +49,15 @@ function fitBudget(base, facts, models, maxTokens) {
4349
return { selectedFacts, selectedModels, estimatedTokens: used };
4450
}
4551

46-
export function compileContext(root, { stage, target = '', query = '', maxTokens, factLimit = 800, modelLimit = 500, metadata = {} }) {
52+
export function compileContext(root, { stage, target = '', query = '', maxTokens, factLimit = 800, modelLimit = 500, allowedModels = null, metadata = {} }) {
4753
const paths = projectPaths(root); const config = loadConfig(root); const configured = config.context?.maxTokens?.[stage] ?? config.context?.maxTokens?.default ?? 60000;
4854
const budget = Math.max(256, Number(maxTokens ?? configured)); const db = openDatabase(paths.database);
4955
const effectiveQuery = [STAGE_QUERIES[stage] ?? '', query, target].filter(Boolean).join(' ');
50-
const facts = rowsForFacts(db, effectiveQuery, factLimit).map(compactFact); const models = rowsForModels(db, effectiveQuery, modelLimit).map(compactModel);
51-
const base = { schemaVersion: '2.0', stage, target: target || null, query: effectiveQuery, metadata };
56+
const facts = rowsForFacts(db, effectiveQuery, factLimit).map(compactFact); const models = rowsForModels(db, effectiveQuery, modelLimit, allowedModels).map(compactModel);
57+
const base = { schemaVersion: '2.0', stage, target: target || null, query: effectiveQuery, modelScope: allowedModels === false ? [] : allowedModels, metadata };
5258
const fitted = fitBudget(base, facts, models, budget);
5359
const payload = { ...base, generatedAt: now(), tokenBudget: budget, estimatedTokens: fitted.estimatedTokens, facts: fitted.selectedFacts, modelItems: fitted.selectedModels, omissions: { facts: Math.max(0, facts.length - fitted.selectedFacts.length), modelItems: Math.max(0, models.length - fitted.selectedModels.length) } };
54-
payload.inputHash = stableHash({ stage, target, query: effectiveQuery, facts: payload.facts.map((x) => x.hash), models: payload.modelItems.map((x) => x.hash), metadata });
60+
payload.inputHash = stableHash({ stage, target, query: effectiveQuery, modelScope: allowedModels, facts: payload.facts.map((item) => item.hash), models: payload.modelItems.map((item) => item.hash), metadata });
5561
const id = sha256(`${stage}\0${target}\0${payload.inputHash}`).slice(0, 24); payload.id = id;
5662
const file = path.join(paths.context, stage, `${target ? target.replace(/[^a-z0-9_.-]+/gi, '-') : 'global'}.json`); writeJson(file, payload);
5763
db.prepare('INSERT INTO contexts(id,stage,target,query,input_hash,estimated_tokens,payload,created_at) VALUES(?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET input_hash=excluded.input_hash,estimated_tokens=excluded.estimated_tokens,payload=excluded.payload,created_at=excluded.created_at').run(id, stage, target || null, effectiveQuery, payload.inputHash, payload.estimatedTokens, JSON.stringify(payload), now());

0 commit comments

Comments
 (0)