mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
An adversarial audit of the piragi -> in-house swap surfaced nine confirmed issues; this fixes all of them. - Re-ingest now REPLACES a source's chunks instead of appending. Add VectorStore.delete_by_source and BaseIndexPlugin.replace_on_reingest (default True), called before add_chunks in both ingest paths. Without it every startup / periodic / manual reindex appended a fresh copy of each doc's chunks, growing the tables unbounded and crowding out distinct results. Conversations opt OUT (replace_on_reingest=False): their many messages share one source URI, so delete-by-source would wipe history. - index_* now honor the plugin IngestResult. The explicit record endpoints (error / standard / decision / review / learning) raise on failure instead of writing a green tracking row for content that never persisted; conversation / journal indexing stays best-effort but skips the tracking row when the embed fails. index_message / index_entry return IngestResult. - A deprecated index type (code) now returns 404 instead of a 500 leaked from _get_plugin's missing-plugin error: add OptimalService.is_index_registered and guard the stats / clear / refresh routes. The panel drops the dead 'Code' category, filter, badge, label, and mock data. - Panel: getContext reads 'results' (matches SearchResponse) instead of a non-existent 'context' field; the reindex toast no longer reports phantom '0 code files'; the stats 'Updated' label uses the max timestamp across indexes rather than indexes[0]; ProactiveContextItem matches the wire shape. - Drop the always-zero per-document chunk_count from the documents API. - Remove dead RAG settings (hybrid_search, cross_encoder) the engine never consumed, and correct stale piragi / BM25 references in code, README, and CLAUDE.md. Delete the unused duplicate roboco/kb embedder package the swap shipped. Adds tests for replace-on-reingest (incl. the conversations carve-out) and the deprecated-index 404.
646 lines
20 KiB
TypeScript
646 lines
20 KiB
TypeScript
/**
|
|
* Knowledge Base API Client
|
|
*
|
|
* Semantic search and RAG queries against indexed knowledge.
|
|
* Matches backend: roboco/api/routes/optimal.py
|
|
*/
|
|
|
|
import api from "./client";
|
|
import {
|
|
KBIndexType,
|
|
type KBSearchRequest,
|
|
type KBSearchResponse,
|
|
type KBSearchResult,
|
|
type RAGQueryRequest,
|
|
type RAGQueryResponse,
|
|
type KBStats,
|
|
type KBIndexStats,
|
|
type RAGHealthResponse,
|
|
type MentorAskRequest,
|
|
type MentorAskResponse,
|
|
type ErrorSearchRequest,
|
|
type ErrorSearchResponse,
|
|
type ErrorRecordRequest,
|
|
type ErrorRecordResponse,
|
|
type DecisionCheckRequest,
|
|
type DecisionCheckResponse,
|
|
type DecisionRecordRequest,
|
|
type DecisionRecordResponse,
|
|
type StandardsGetRequest,
|
|
type StandardsGetResponse,
|
|
type ValidateActionRequest,
|
|
type ValidateActionResponse,
|
|
type CodeReviewRequest,
|
|
type CodeReviewResponse,
|
|
type LearningRecordRequest,
|
|
type LearningRecordResponse,
|
|
type LearningSearchRequest,
|
|
type ProactiveContextRequest,
|
|
type ProactiveContextResponse,
|
|
type TokenEstimateRequest,
|
|
type TokenEstimateResponse,
|
|
type RefreshIndexRequest,
|
|
type RefreshIndexResponse,
|
|
type ClearIndexResponse,
|
|
type ReindexResponse,
|
|
type ReindexRequest,
|
|
type IndexStalenessResponse,
|
|
} from "@/types";
|
|
import { isMockMode } from "@/lib/mock-data";
|
|
|
|
// =============================================================================
|
|
// MOCK DATA
|
|
// =============================================================================
|
|
|
|
const mockSearchResults: KBSearchResult[] = [
|
|
{
|
|
content: "## Task Lifecycle\n\nTasks follow a strict state machine from PENDING through IN_PROGRESS to COMPLETED. Each transition requires specific conditions...",
|
|
source: "docs/architecture/task-lifecycle.md",
|
|
score: 0.88,
|
|
index_type: KBIndexType.DOCUMENTATION,
|
|
metadata: { section: "Architecture" },
|
|
},
|
|
{
|
|
content: "BE-DEV-1: I've completed the task validation logic. The acceptance criteria now require at least one item before a task can be created.",
|
|
source: "channel:backend/session:abc123",
|
|
score: 0.75,
|
|
index_type: KBIndexType.CONVERSATIONS,
|
|
metadata: { agent: "be-dev-1", timestamp: "2024-01-15T10:30:00Z" },
|
|
},
|
|
{
|
|
content: "Learned that proper error boundaries in the task form prevent cascading failures. Applied this pattern to all form components.",
|
|
source: "journal:be-dev-1/entry:xyz789",
|
|
score: 0.71,
|
|
index_type: KBIndexType.JOURNALS,
|
|
metadata: { agent: "be-dev-1", type: "learning" },
|
|
},
|
|
];
|
|
|
|
const mockStats: KBStats = {
|
|
indexes: [
|
|
{ index_type: KBIndexType.DOCUMENTATION, document_count: 45, chunk_count: 320, last_updated: "2024-01-15T11:30:00Z" },
|
|
{ index_type: KBIndexType.CONVERSATIONS, document_count: 890, chunk_count: 4200, last_updated: "2024-01-15T12:15:00Z" },
|
|
{ index_type: KBIndexType.JOURNALS, document_count: 156, chunk_count: 780, last_updated: "2024-01-15T10:00:00Z" },
|
|
{ index_type: KBIndexType.ERRORS, document_count: 45, chunk_count: 180, last_updated: "2024-01-15T10:00:00Z" },
|
|
{ index_type: KBIndexType.STANDARDS, document_count: 12, chunk_count: 60, last_updated: "2024-01-15T10:00:00Z" },
|
|
{ index_type: KBIndexType.DECISIONS, document_count: 78, chunk_count: 390, last_updated: "2024-01-15T10:00:00Z" },
|
|
{ index_type: KBIndexType.REVIEWS, document_count: 234, chunk_count: 1170, last_updated: "2024-01-15T10:00:00Z" },
|
|
{ index_type: KBIndexType.LEARNINGS, document_count: 89, chunk_count: 445, last_updated: "2024-01-15T10:00:00Z" },
|
|
],
|
|
total_documents: 1549,
|
|
total_chunks: 6745,
|
|
};
|
|
|
|
// Backend returns stats as dict, we need to transform to array
|
|
interface BackendIndexStats {
|
|
initialized: boolean;
|
|
indexes: Record<string, { document_count: number; chunk_count: number; last_updated: string | null }>;
|
|
}
|
|
|
|
function transformStatsResponse(backendStats: BackendIndexStats): KBStats {
|
|
const indexes: KBIndexStats[] = Object.entries(backendStats.indexes || {}).map(([indexType, stats]) => ({
|
|
index_type: indexType as KBIndexType,
|
|
document_count: stats.document_count ?? 0,
|
|
chunk_count: stats.chunk_count ?? 0,
|
|
last_updated: stats.last_updated ?? null,
|
|
}));
|
|
|
|
const total_documents = indexes.reduce((sum, idx) => sum + idx.document_count, 0);
|
|
const total_chunks = indexes.reduce((sum, idx) => sum + idx.chunk_count, 0);
|
|
|
|
return { indexes, total_documents, total_chunks };
|
|
}
|
|
|
|
// =============================================================================
|
|
// SEARCH ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Semantic search across indexed content
|
|
*/
|
|
async function search(params: KBSearchRequest): Promise<KBSearchResponse> {
|
|
if (isMockMode()) {
|
|
// Filter by index types if specified
|
|
let results = [...mockSearchResults];
|
|
if (params.index_types && params.index_types.length > 0) {
|
|
results = results.filter((r) => params.index_types!.includes(r.index_type));
|
|
}
|
|
// Filter by min score
|
|
if (params.min_score) {
|
|
results = results.filter((r) => r.score >= params.min_score!);
|
|
}
|
|
// Limit results
|
|
const topK = params.top_k ?? 10;
|
|
results = results.slice(0, topK);
|
|
|
|
return {
|
|
results,
|
|
total: results.length,
|
|
query: params.query,
|
|
};
|
|
}
|
|
|
|
const response = await api.post<KBSearchResponse>("/optimal/kb/search", params);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// RAG ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* RAG query - ask a question, get AI answer with citations
|
|
*/
|
|
async function ragQuery(params: RAGQueryRequest): Promise<RAGQueryResponse> {
|
|
if (isMockMode()) {
|
|
// Simulate RAG response with mock data
|
|
return {
|
|
answer: `Based on the indexed knowledge, here's what I found about "${params.question}":\n\nThe system uses a structured approach where tasks follow a defined lifecycle. Each task starts in PENDING state and can transition through various states like IN_PROGRESS, BLOCKED, and eventually COMPLETED or CANCELLED.\n\nKey points:\n- Tasks require acceptance criteria before creation\n- State transitions are validated by the TaskService\n- Agents can claim and work on tasks based on their role and team`,
|
|
citations: mockSearchResults.slice(0, 3).map((r) => ({
|
|
content: r.content,
|
|
source: r.source,
|
|
score: r.score,
|
|
index_type: r.index_type,
|
|
metadata: r.metadata,
|
|
})),
|
|
query: params.question,
|
|
context_used: 3,
|
|
};
|
|
}
|
|
|
|
// Transform frontend params to backend format
|
|
const backendParams = {
|
|
query: params.question,
|
|
index_types: params.index_types,
|
|
top_k: params.max_context_chunks ?? 5,
|
|
};
|
|
|
|
const response = await api.post<RAGQueryResponse>("/optimal/rag/query", backendParams);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Get context for a query without generating an answer
|
|
*/
|
|
async function getContext(params: RAGQueryRequest): Promise<KBSearchResult[]> {
|
|
if (isMockMode()) {
|
|
return mockSearchResults.slice(0, params.max_context_chunks ?? 5);
|
|
}
|
|
|
|
// Transform frontend params to backend format
|
|
const backendParams = {
|
|
query: params.question,
|
|
index_types: params.index_types,
|
|
top_k: params.max_context_chunks ?? 5,
|
|
};
|
|
|
|
const response = await api.post<KBSearchResponse>("/optimal/rag/context", backendParams);
|
|
return response.data.results;
|
|
}
|
|
|
|
// =============================================================================
|
|
// STATS ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get index statistics
|
|
*/
|
|
async function getStats(): Promise<KBStats> {
|
|
if (isMockMode()) {
|
|
return mockStats;
|
|
}
|
|
|
|
const response = await api.get<BackendIndexStats>("/optimal/stats");
|
|
return transformStatsResponse(response.data);
|
|
}
|
|
|
|
/**
|
|
* Get stats for a specific index type
|
|
*/
|
|
async function getIndexStats(indexType: KBIndexType): Promise<KBIndexStats> {
|
|
if (isMockMode()) {
|
|
const stats = mockStats.indexes.find((i) => i.index_type === indexType);
|
|
if (stats) return stats;
|
|
return {
|
|
index_type: indexType,
|
|
document_count: 0,
|
|
chunk_count: 0,
|
|
last_updated: null,
|
|
};
|
|
}
|
|
|
|
const response = await api.get<KBIndexStats>(`/optimal/stats/${indexType}`);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// BROWSE ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* List documents in a specific index (for browsing)
|
|
*/
|
|
async function listDocuments(
|
|
indexType: KBIndexType,
|
|
params?: { limit?: number; offset?: number }
|
|
): Promise<{ documents: Array<{ id: string; source: string; indexed_at: string; metadata?: Record<string, unknown> }>; total: number }> {
|
|
if (isMockMode()) {
|
|
// Generate mock document list
|
|
const docs = mockSearchResults
|
|
.filter((r) => r.index_type === indexType)
|
|
.map((r, idx) => ({
|
|
id: `doc-${indexType}-${idx}`,
|
|
source: r.source,
|
|
indexed_at: "2024-01-15T12:00:00Z",
|
|
}));
|
|
return { documents: docs, total: docs.length };
|
|
}
|
|
|
|
const response = await api.get<{ documents: Array<{ id: string; source: string; indexed_at: string; metadata?: Record<string, unknown> }>; total: number; index_type: string }>(
|
|
`/optimal/kb/${indexType}/documents`,
|
|
{ params }
|
|
);
|
|
return { documents: response.data.documents, total: response.data.total };
|
|
}
|
|
|
|
// =============================================================================
|
|
// HEALTH ENDPOINT
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get RAG system health status
|
|
*/
|
|
async function getHealth(): Promise<RAGHealthResponse> {
|
|
if (isMockMode()) {
|
|
return {
|
|
healthy: true,
|
|
embedding_status: "healthy",
|
|
llm_status: "healthy",
|
|
vector_store_status: "healthy",
|
|
details: {},
|
|
};
|
|
}
|
|
const response = await api.get<RAGHealthResponse>("/optimal/health");
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// INDEX MANAGEMENT
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Delete/clear an index
|
|
*/
|
|
async function deleteIndex(indexType: KBIndexType): Promise<ClearIndexResponse> {
|
|
if (isMockMode()) {
|
|
return { status: "cleared", index_type: indexType };
|
|
}
|
|
const response = await api.delete<ClearIndexResponse>(`/optimal/kb/${indexType}`);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Refresh an index with updated sources
|
|
*/
|
|
async function refreshIndex(request: RefreshIndexRequest): Promise<RefreshIndexResponse> {
|
|
if (isMockMode()) {
|
|
return { status: "refreshed", index_type: request.index_type, sources: request.sources };
|
|
}
|
|
const response = await api.post<RefreshIndexResponse>("/optimal/kb/refresh", request);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Trigger full reindex with detailed reporting
|
|
*
|
|
* @param request - Optional parameters for reindexing
|
|
* @param request.force - Force reindex even if indexes have content
|
|
* @param request.timeout_seconds - Max time to wait (default: 300s)
|
|
* @returns Detailed report of what was indexed, failed, and skipped
|
|
*/
|
|
async function reindexAll(request?: ReindexRequest): Promise<ReindexResponse> {
|
|
if (isMockMode()) {
|
|
return {
|
|
status: "reindexed",
|
|
code: {
|
|
index_type: "code",
|
|
total_attempted: 100,
|
|
successful: 98,
|
|
failed: 2,
|
|
skipped: 0,
|
|
success_rate: 98.0,
|
|
has_failures: true,
|
|
failed_sources: [
|
|
["/path/to/file1.py", "UTF-8 decode error"],
|
|
["/path/to/file2.py", "File too large"],
|
|
],
|
|
duration_seconds: 45.2,
|
|
},
|
|
documentation: {
|
|
index_type: "documentation",
|
|
total_attempted: 50,
|
|
successful: 50,
|
|
failed: 0,
|
|
skipped: 0,
|
|
success_rate: 100.0,
|
|
has_failures: false,
|
|
failed_sources: [],
|
|
duration_seconds: 12.5,
|
|
},
|
|
overall_success: true,
|
|
warnings: ["Code indexing had 2 failures"],
|
|
// Legacy fields
|
|
code_count: 98,
|
|
docs_count: 50,
|
|
};
|
|
}
|
|
const response = await api.post<ReindexResponse>("/optimal/kb/reindex", null, {
|
|
params: {
|
|
force: request?.force ?? false,
|
|
timeout_seconds: request?.timeout_seconds ?? 300,
|
|
},
|
|
});
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Check if indexes are stale (source files modified after last indexing)
|
|
*
|
|
* @returns Staleness info for each index type
|
|
*/
|
|
async function checkStaleness(): Promise<IndexStalenessResponse> {
|
|
if (isMockMode()) {
|
|
return {
|
|
needs_reindex: true,
|
|
stale_indexes: ["code"],
|
|
details: {
|
|
code: {
|
|
status: "stale",
|
|
last_indexed: new Date(Date.now() - 86400000).toISOString(),
|
|
stale_file_count: 5,
|
|
stale_files_sample: [
|
|
"/roboco/services/task.py",
|
|
"/roboco/services/optimal.py",
|
|
],
|
|
recommendation: "Run /kb/reindex?force=true to update",
|
|
},
|
|
documentation: {
|
|
status: "current",
|
|
last_indexed: new Date().toISOString(),
|
|
indexed_sources_count: 50,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
const response = await api.get<IndexStalenessResponse>(
|
|
"/optimal/stats/staleness"
|
|
);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// MENTOR ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Ask the mentor for help
|
|
*/
|
|
async function askMentor(request: MentorAskRequest): Promise<MentorAskResponse> {
|
|
if (isMockMode()) {
|
|
return {
|
|
answer: `Here's what I found about "${request.question}":\n\nBased on our organizational knowledge, I recommend following the established patterns and consulting the relevant documentation.`,
|
|
sources: mockSearchResults.slice(0, 2),
|
|
conversation_id: "conv-" + Date.now(),
|
|
suggested_followups: ["Can you elaborate?", "What are the alternatives?"],
|
|
};
|
|
}
|
|
const response = await api.post<MentorAskResponse>("/optimal/mentor/ask", request);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// ERROR ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Search for known error solutions
|
|
*/
|
|
async function searchErrors(request: ErrorSearchRequest): Promise<ErrorSearchResponse> {
|
|
if (isMockMode()) {
|
|
return { results: [], total: 0 };
|
|
}
|
|
const response = await api.post<ErrorSearchResponse>("/optimal/errors/search", request);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Record an error solution
|
|
*/
|
|
async function recordError(request: ErrorRecordRequest): Promise<ErrorRecordResponse> {
|
|
if (isMockMode()) {
|
|
return { error_id: "err-" + Date.now(), status: "recorded" };
|
|
}
|
|
const response = await api.post<ErrorRecordResponse>("/optimal/errors/record", request);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// DECISION ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Check if a similar decision was made before
|
|
*/
|
|
async function checkDecision(request: DecisionCheckRequest): Promise<DecisionCheckResponse> {
|
|
if (isMockMode()) {
|
|
return { has_precedent: false, decisions: [], recommendation: "No similar decisions found" };
|
|
}
|
|
const response = await api.post<DecisionCheckResponse>("/optimal/decisions/check", request);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Record a decision for future reference
|
|
*/
|
|
async function recordDecision(request: DecisionRecordRequest): Promise<DecisionRecordResponse> {
|
|
if (isMockMode()) {
|
|
return { decision_id: "dec-" + Date.now(), status: "recorded" };
|
|
}
|
|
const response = await api.post<DecisionRecordResponse>("/optimal/decisions/record", request);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// STANDARDS ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get standards for a domain
|
|
*/
|
|
async function getStandards(request: StandardsGetRequest): Promise<StandardsGetResponse> {
|
|
if (isMockMode()) {
|
|
return { standards: [], total: 0 };
|
|
}
|
|
const response = await api.post<StandardsGetResponse>("/optimal/standards/get", request);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Validate an action against standards
|
|
*/
|
|
async function validateAction(request: ValidateActionRequest): Promise<ValidateActionResponse> {
|
|
if (isMockMode()) {
|
|
return { allowed: true, violations: [], warnings: [], relevant_standards: [] };
|
|
}
|
|
const response = await api.post<ValidateActionResponse>("/optimal/standards/validate", request);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// CODE REVIEW ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Review code against standards
|
|
*/
|
|
async function reviewCode(request: CodeReviewRequest): Promise<CodeReviewResponse> {
|
|
if (isMockMode()) {
|
|
return {
|
|
file_path: request.file_path,
|
|
approved: true,
|
|
score: 85,
|
|
comments: [],
|
|
standards_checked: ["coding-standards"],
|
|
similar_reviews: [],
|
|
};
|
|
}
|
|
const response = await api.post<CodeReviewResponse>("/optimal/review/code", request);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// LEARNING ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Record a learning for knowledge sharing
|
|
*/
|
|
async function recordLearning(request: LearningRecordRequest): Promise<LearningRecordResponse> {
|
|
if (isMockMode()) {
|
|
return { learning_id: "learn-" + Date.now(), status: "recorded" };
|
|
}
|
|
const response = await api.post<LearningRecordResponse>("/optimal/learnings/record", request);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Search for relevant learnings
|
|
*/
|
|
async function searchLearnings(request: LearningSearchRequest): Promise<KBSearchResponse> {
|
|
if (isMockMode()) {
|
|
return { results: [], total: 0, query: request.query };
|
|
}
|
|
const response = await api.post<KBSearchResponse>("/optimal/learnings/search", request);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// PROACTIVE CONTEXT ENDPOINTS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get proactive context for a task
|
|
*/
|
|
async function getProactiveContext(request: ProactiveContextRequest): Promise<ProactiveContextResponse> {
|
|
if (isMockMode()) {
|
|
return {
|
|
task_id: request.task_id,
|
|
similar_tasks: [],
|
|
relevant_learnings: [],
|
|
code_patterns: [],
|
|
applicable_standards: [],
|
|
recent_decisions: [],
|
|
known_issues: [],
|
|
summary: "No relevant context found for this task.",
|
|
};
|
|
}
|
|
const response = await api.post<ProactiveContextResponse>("/optimal/context/proactive", request);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// TOKEN ESTIMATION
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Estimate token count for content
|
|
*/
|
|
async function estimateTokens(request: TokenEstimateRequest): Promise<TokenEstimateResponse> {
|
|
if (isMockMode()) {
|
|
return {
|
|
token_count: Math.ceil(request.content.length / 4),
|
|
model: request.model || "claude-sonnet-4-20250514",
|
|
content_length: request.content.length,
|
|
};
|
|
}
|
|
const response = await api.post<TokenEstimateResponse>("/optimal/tokens/estimate", request);
|
|
return response.data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// EXPORT API
|
|
// =============================================================================
|
|
|
|
export const knowledgeBaseApi = {
|
|
// Search
|
|
search,
|
|
|
|
// RAG
|
|
ragQuery,
|
|
getContext,
|
|
|
|
// Stats & Health
|
|
getStats,
|
|
getIndexStats,
|
|
getHealth,
|
|
|
|
// Browse
|
|
listDocuments,
|
|
|
|
// Index Management
|
|
deleteIndex,
|
|
refreshIndex,
|
|
reindexAll,
|
|
checkStaleness,
|
|
|
|
// Mentor
|
|
askMentor,
|
|
|
|
// Errors
|
|
searchErrors,
|
|
recordError,
|
|
|
|
// Decisions
|
|
checkDecision,
|
|
recordDecision,
|
|
|
|
// Standards
|
|
getStandards,
|
|
validateAction,
|
|
|
|
// Code Review
|
|
reviewCode,
|
|
|
|
// Learnings
|
|
recordLearning,
|
|
searchLearnings,
|
|
|
|
// Proactive Context
|
|
getProactiveContext,
|
|
|
|
// Token Estimation
|
|
estimateTokens,
|
|
};
|
|
|
|
export default knowledgeBaseApi;
|