refactor: remove handbook sync state and related functionality
All checks were successful
On Push to Main / test (push) Successful in 31s
On Push to Main / publish (push) Successful in 1m4s
On Push to Main / deploy-dev (push) Successful in 1m31s

This commit is contained in:
RaymondVerhoef
2026-05-22 19:45:14 +02:00
parent dc628644b6
commit ca8945ea5b
12 changed files with 159 additions and 552 deletions

View File

@@ -1,24 +1,21 @@
import * as db from './db';
import { callLLM } from './llm';
import { extractionLimiter } from './llmRetry';
import { EMIT_KNOWLEDGE_GRAPH_TOOL, EMIT_HANDBOOK_DELTA_TOOL } from './llmTools';
import { normalizeHandbookResult } from './llmSchemas';
import { EMIT_KNOWLEDGE_GRAPH_TOOL } from './llmTools';
const MAX_KNOWN_IDS_HINT = 200;
const MAX_KNOWN_TOPICS_HINT = 200;
/**
* Build the "already-extracted topic IDs" hint that prepends every chunk
* after the first. Capped at the most-recent `MAX_KNOWN_IDS_HINT` IDs so
* the prompt stays a bounded size; the model uses this list to reuse IDs
* rather than invent variants like `software-developer` for
* `software-engineer`.
* Build the "already-extracted topics" hint included in every chunk prompt.
* Passes both ID and label so the model can match concepts by name and reuse
* the exact ID + label rather than inventing near-duplicate variants.
*/
export function buildKnownIdsHint(ids) {
if (!ids || !ids.length) return '';
const recent = ids.slice(-MAX_KNOWN_IDS_HINT);
export function buildKnownIdsHint(topics) {
if (!topics || !topics.length) return '';
const recent = topics.slice(-MAX_KNOWN_TOPICS_HINT);
return [
'Already-extracted topic IDs (do NOT create new IDs for these — reuse them if the same concept appears here):',
...recent.map((id) => `- ${id}`),
'Already-extracted topics (reuse their ID and label exactly if the same concept appears here):',
...recent.map((t) => `- ${t.id}: "${t.label}"`),
'',
].join('\n');
}
@@ -45,41 +42,8 @@ Assign a learning_relevance to every topic:
Relation types: related_to | depends_on | part_of | executed_by.
`;
const HANDBOOK_SYSTEM_PROMPT = `You are analysing an update to the Respellion Employee Handbook. Emit the extracted topics and relations through the emit_handbook_delta tool.
CRITICAL INSTRUCTIONS:
- Every process must have a role attached. Express this as: process --executed_by--> role.
- Every concept must connect to a process or role.
- Mark handbook topics with metadata.source = "github_handbook".
- Assign learning_relevance using the same scale as extraction: core | standard | peripheral | exclude.
Relation types: related_to | depends_on | part_of | executed_by.
`;
const cachedSystem = (text) => [{ type: 'text', text, cache_control: { type: 'ephemeral' } }];
export async function analyzeHandbookDelta(fileContent, filePath, { signal } = {}) {
const result = await callLLM({
task: 'extract.handbook',
tier: 'standard',
system: cachedSystem(HANDBOOK_SYSTEM_PROMPT),
user: `Analyze the following handbook file update (${filePath}):\n\n${fileContent}`,
tools: [EMIT_HANDBOOK_DELTA_TOOL],
toolChoice: { type: 'tool', name: EMIT_HANDBOOK_DELTA_TOOL.name },
maxTokens: 8192,
timeoutMs: 180_000,
limiter: extractionLimiter,
signal,
});
const raw = result.toolUses[0]?.input;
if (!raw) throw new Error('Handbook extraction did not emit a tool result.');
const extractedData = normalizeHandbookResult(raw);
await mergeKnowledgeGraph(extractedData);
return { success: true, data: extractedData };
}
/**
* Sentence-aware chunker with overlap.
*
@@ -169,7 +133,7 @@ export async function processSourceText(textContent, sourceName, { signal } = {}
console.log(`[Pipeline] Split "${sourceName}" into ${chunks.length} chunks for processing.`);
const existingTopics = await db.getTopics();
const knownIds = existingTopics.map((t) => t.id);
const knownTopics = existingTopics.map((t) => ({ id: t.id, label: t.label }));
const allExtractedTopics = [];
const allExtractedRelations = [];
@@ -178,7 +142,7 @@ export async function processSourceText(textContent, sourceName, { signal } = {}
if (signal?.aborted) throw signal.reason ?? new DOMException('Aborted', 'AbortError');
console.log(`[Pipeline] Processing chunk ${i + 1}/${chunks.length} (${chunks[i].length} chars)...`);
const hint = i > 0 ? buildKnownIdsHint(knownIds) : '';
const hint = buildKnownIdsHint(knownTopics);
const result = await callLLM({
task: 'extract.source',
tier: 'standard',
@@ -197,7 +161,9 @@ export async function processSourceText(textContent, sourceName, { signal } = {}
if (Array.isArray(extractedData.topics)) {
allExtractedTopics.push(...extractedData.topics);
for (const t of extractedData.topics) {
if (t?.id && !knownIds.includes(t.id)) knownIds.push(t.id);
if (t?.id && !knownTopics.some((k) => k.id === t.id)) {
knownTopics.push({ id: t.id, label: t.label });
}
}
}
if (Array.isArray(extractedData.relations)) {