zam-core 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1010,6 +1010,97 @@ interface SourceProposalInput {
1010
1010
  */
1011
1011
  declare function confirmSourceImport(db: Database, userId: string, sourceId: string, proposals: SourceProposalInput[]): Promise<ConfirmFoundationsResult>;
1012
1012
 
1013
+ /**
1014
+ * Token embedding repository — stores per-token vectors for semantic search
1015
+ * (ADR 2026-07-03) and derives staleness from a content hash.
1016
+ *
1017
+ * This module is pure storage + classification: no HTTP, no LLM calls. The
1018
+ * CLI layer (`src/cli/llm/embedder.ts`) generates vectors and calls in here.
1019
+ */
1020
+
1021
+ interface TokenEmbedding {
1022
+ token_id: string;
1023
+ model: string;
1024
+ dims: number;
1025
+ content_hash: string;
1026
+ embedded_at: string;
1027
+ embedding: Float32Array;
1028
+ }
1029
+ type EmbeddingStaleness = "missing" | "content-changed" | "model-changed" | "dimension-changed";
1030
+ interface TokenNeedingEmbedding {
1031
+ token: Token;
1032
+ /** Canonical text to embed — already hashed the same way. */
1033
+ text: string;
1034
+ reason: EmbeddingStaleness;
1035
+ }
1036
+ interface EmbeddingCoverage {
1037
+ tokens: number;
1038
+ embedded: number;
1039
+ missing: number;
1040
+ stale: number;
1041
+ }
1042
+ interface EmbeddedTokenRow {
1043
+ token: Token;
1044
+ embedding: Float32Array;
1045
+ }
1046
+ /**
1047
+ * The canonical text embedded for a token. Every stored hash and every stored
1048
+ * vector derives from exactly this string — never the slug, which is an
1049
+ * identifier, not meaning.
1050
+ */
1051
+ declare function embeddingContentForToken(t: Pick<Token, "concept" | "question" | "domain">): string;
1052
+ declare function computeContentHash(text: string): string;
1053
+ /**
1054
+ * Encode a vector as a little-endian float32 BLOB. Builds a fresh buffer (no
1055
+ * aliasing into the caller's array) so the row can be stored independently of
1056
+ * whatever produced the vector.
1057
+ */
1058
+ declare function encodeEmbedding(vec: ArrayLike<number>): Uint8Array;
1059
+ /**
1060
+ * Decode a stored BLOB back into a Float32Array.
1061
+ *
1062
+ * BLOB values come back as `Buffer` (better-sqlite3) or `Uint8Array` (remote
1063
+ * provider). better-sqlite3 Buffers are views into a shared pool and may have
1064
+ * a non-zero, non-4-aligned `byteOffset` — constructing a Float32Array
1065
+ * directly over such a buffer throws (RangeError) or silently reads garbage.
1066
+ * Copying guarantees a fresh, 0-offset backing buffer — but `blob.slice()`
1067
+ * cannot be used for this: `Buffer.prototype.slice` overrides
1068
+ * `Uint8Array.prototype.slice` to return a *view* into the same backing
1069
+ * buffer (a legacy Node.js Buffer API quirk), not a copy, so it would still
1070
+ * carry the misaligned offset. Uint8Array's own `slice` must be borrowed
1071
+ * explicitly to force an actual copy.
1072
+ */
1073
+ declare function decodeEmbedding(blob: Uint8Array): Float32Array;
1074
+ declare function upsertTokenEmbedding(db: Database, input: {
1075
+ tokenId: string;
1076
+ embedding: ArrayLike<number>;
1077
+ model: string;
1078
+ contentHash: string;
1079
+ }): Promise<void>;
1080
+ declare function getTokenEmbedding(db: Database, tokenId: string): Promise<TokenEmbedding | undefined>;
1081
+ /**
1082
+ * Classify every non-deprecated token against a target embedding model:
1083
+ * missing (no stored row), model-changed (stored under a different model
1084
+ * id), or content-changed (stored hash no longer matches the canonical
1085
+ * text). `force: true` returns every token regardless of freshness, tagged
1086
+ * `content-changed` since that is the closest-fitting reason to re-embed.
1087
+ */
1088
+ declare function listTokensNeedingEmbedding(db: Database, model: string, opts?: {
1089
+ limit?: number;
1090
+ force?: boolean;
1091
+ dims?: number;
1092
+ }): Promise<TokenNeedingEmbedding[]>;
1093
+ /** Same classification scan as {@link listTokensNeedingEmbedding}, counts only. */
1094
+ declare function getEmbeddingCoverage(db: Database, model: string, opts?: {
1095
+ dims?: number;
1096
+ }): Promise<EmbeddingCoverage>;
1097
+ /**
1098
+ * All tokens with a fresh vector under `model` to enter the vector search leg.
1099
+ * Re-hashes rows here because lazy top-up is intentionally bounded: a stale
1100
+ * row beyond the current batch must never participate with its old meaning.
1101
+ */
1102
+ declare function listEmbeddedTokens(db: Database, model: string): Promise<EmbeddedTokenRow[]>;
1103
+
1013
1104
  /**
1014
1105
  * Monitor log analyzer — maps observed shell commands to token ratings.
1015
1106
  *
@@ -1790,6 +1881,35 @@ interface ReviewQueue {
1790
1881
  */
1791
1882
  declare function buildReviewQueue(db: Database, options: ReviewQueueOptions): Promise<ReviewQueue>;
1792
1883
 
1884
+ /**
1885
+ * Hybrid lexical/vector token search using reciprocal-rank fusion (RRF).
1886
+ * (ADR 2026-07-03)
1887
+ *
1888
+ * This module is pure math + database queries — zero LLM dependencies, no HTTP.
1889
+ */
1890
+
1891
+ interface HybridSearchOptions {
1892
+ queryEmbedding?: ArrayLike<number>;
1893
+ /** Model the stored vectors must match; required when queryEmbedding is set. */
1894
+ model?: string;
1895
+ limit?: number;
1896
+ rrfK?: number;
1897
+ vectorTopK?: number;
1898
+ }
1899
+ interface HybridScoredToken extends Token {
1900
+ score: number;
1901
+ lexicalRank: number | null;
1902
+ vectorRank: number | null;
1903
+ similarity: number | null;
1904
+ }
1905
+ /** Calculates the cosine similarity between two float vectors. Returns 0 if either norm is 0. */
1906
+ declare function cosineSimilarity(a: Float32Array, b: Float32Array): number;
1907
+ /**
1908
+ * Performs a hybrid search over active tokens.
1909
+ * Combines lexical relevance rankings with vector cosine similarities.
1910
+ */
1911
+ declare function searchTokensHybrid(db: Database, query: string, opts?: HybridSearchOptions): Promise<HybridScoredToken[]>;
1912
+
1793
1913
  /**
1794
1914
  * Get the path to ZAM's internal package SKILL.md.
1795
1915
  */
@@ -1914,7 +2034,7 @@ interface InstallConfig {
1914
2034
  /** Machine-local id of the workspace currently active in this install. */
1915
2035
  activeWorkspaceId?: string;
1916
2036
  }
1917
- type MachineAiRole = "vision" | "recall" | "text";
2037
+ type MachineAiRole = "vision" | "recall" | "text" | "embedding";
1918
2038
  type MachineApiFlavor = "chat-completions" | "anthropic-messages";
1919
2039
  interface MachineProviderRecord {
1920
2040
  label?: string;
@@ -2074,4 +2194,4 @@ declare function resolveAllBeliefPaths(db: Database): Promise<string[]>;
2074
2194
  */
2075
2195
  declare function resolveAllGoalPaths(db: Database): Promise<string[]>;
2076
2196
 
2077
- export { type ADOConfig, type ADOCredentials, type AgentSkill, type AnalysisResult, type ApplySessionSynthesisInput, type ApplySessionSynthesisResult, BUILT_IN_SENSITIVE_MATCHERS, type BloomLevel$1 as BloomLevel, type CaptureDecision, type CaptureDenialReason, type CaptureRequest, type Card, type CardDeletionImpact, type CardState$1 as CardState, type CascadeBlockResult, type CommandRecord, type CommandSequence, type ConfirmFoundationsResult, type ConnectionOptions, type CreateAgentSkillInput, type CreateGoalInput, type CreateReviewInput, type CreateSessionInput, type CreateTokenInput, type Credentials, type CurriculumCardInput, DEFAULT_OBSERVER_POLICY, DEFAULT_REVIEW_CONTEXT_MAX_CHARS, type Database, type DatabaseProvider, type DatabaseTargetInfo, type DatabaseValue, type DeleteCardResult, type DeleteTokenResult, type DiscoveryOptions, type DomainCompetence, type EvaluateInput, type EvaluateResult, type ExecuteReviewActionInput, type ExecutionContext, type FSRSParameters, type FoundationProposalInput, type Goal, type GoalFrontmatter, type GoalStatus, type GoalSummary, HOMEBREW_CASK, type ImportCurriculumResult, type ImportResult, type InstallChannel, type InstallConfig, type InstallMode, type InstallPlan, type InstallResult, type LocalLLMRunner, type LogStepInput, type MachineAiConfig, type MachineProviderRecord, type MachineRoleBinding, type MonitorEvent, type Neighborhood, type NeighborhoodToken, OBSERVER_POLICY_UNSET_HINT, OBSERVER_POLICY_VERSION, type ObservationRating, type ObserverConsent, type ObserverPolicy, type ObserverRetention, type ObserverScope, type ObserverSettingKey, type OllamaDetectionOptions, type PersonalCard, type PrepareSessionSynthesisInput, type Prerequisite, type PrerequisiteWithToken, type PromptInput, REVIEW_CONTEXT_CACHE_TTL_MS, type Rating, type RecallPrompt, type RemoteDatabaseOptions, type RepoPaths, type ResolvedCaptureTarget, type ResolvedReference, type ReviewActionResult, type ReviewActionType, type ReviewContext, type ReviewLog, type ReviewQueue, type ReviewQueueItem, type ReviewQueueOptions, type RunResult, SIDECAR_POLICY_FILE, SNAPSHOT_VERSION, type SchedulingCard, type Session, type SessionStep, type SessionSummary, type SessionSynthesisCandidate, type SessionSynthesisEvidence, type SessionSynthesisPreview, type SessionSynthesisRecord, type SidecarPrivacyPolicy, type SkillProposal, type SkillSource, type SnapshotManifest, type SourceProposalInput, type SplitProposalInput, type Statement, type SupportedLocale, type SymbiosisMode, type SynthesisConfidence, type SystemProfile, type Token, type TokenDeleteImpact, type TokenPattern, type TranslationKey, type TursoCredentials, UI_OBSERVATION_PROTOCOL_VERSION, type UiActionType, type UiApplicationContext, type UiCandidateToken, type UiEvidenceRef, type UiEvidenceType, type UiObservationKind, type UiObservationReport, type UiObservedAction, type UnblockResult, type UpdateActionKind, type UpdateCardInput, type UpdateDecision, type UpdateStep, type UpdateStepKind, type UpdateTokenInput, type UserSetting, type UserStats, WINGET_PACKAGE_ID, type WorkItem, type WorkspaceConfig, type WorkspaceKind, type WorkspaceSourceControl, addPrerequisite, analyzeObservation, appendUiObservationReport, applySessionSynthesis, buildReviewQueue, buildUiSynthesisCandidates, cascadeBlock, clearADOCredentials, clearProviderApiKey, clearReviewContextCache, clearTursoCredentials, compareVersions, confirmCardSplit, confirmFoundations, confirmSourceImport, createAgentSkill, createFSRS, createGoal, createToken, decidePostCapture, decidePreCapture, decideUpdate, deleteCardForUser, deleteSetting, deleteToken, deprecateToken, detectSyncProvider, detectSystemLocale, discoverSkills, distributeGlobalSkills, endSession, ensureCard, ensureMonitorDir, ensureUiObserverDir, evaluateRating, executeReviewAction, exportSnapshot, extractTasks, extractTokenRefs, fetchActiveWorkItems, findTokens, generateBashHooks, generateBashUnhooks, generateConceptFreeCue, generatePowerShellHooks, generatePowerShellUnhooks, generatePrompt, generateTokenSlug, generateZshHooks, generateZshUnhooks, getADOCredentials, getActiveWorkspace, getActiveWorkspaceId, getAgentSkill, getAllSettings, getAllSettingsDetailed, getBlockedCards, getCard, getCardById, getCardDeletionImpact, getConfiguredWorkspaces, getDatabaseTargetInfo, getDefaultDbPath, getDependents, getDomainCompetence, getDueCards, getGoal, getGoalTree, getInstallChannel, getInstallMode, getMachineAiConfig, getMonitorDir, getMonitorLogStats, getMonitorPath, getPackageSkillPath, getPrerequisites, getProviderApiKey, getRepoPaths, getReviewsForCard, getReviewsForUser, getSessionSummary, getSessionSynthesisRecords, getSetting, getSystemProfile, getTokenById, getTokenBySlug, getTokenDeleteImpact, getTokenNeighborhood, getTursoCredentials, getUiObservationPath, getUiObserverDir, getUserStats, hasCommand, importCurriculumCards, importSnapshot, injectShellHooks, installFastFlowLM, installOllama, installOpenCode, interleave, isObserverPolicyConfigured, isOllamaInstalled, isUiObservationReport, listAgentSkills, listGoals, listPersonalCards, listProviderApiKeyRefs, listTokens, loadADOConfig, loadCredentials, loadInstallConfig, logReview, logStep, matchBuiltInSensitive, matchDenylist, matchesFilePath, monitorLogExists, normalizeLocale, normalizePath, openDatabase, openDatabaseWithSync, openRemoteDatabase, pairCommands, parseGoalFile, parseMonitorLog, parseObserverList, parseObserverPolicy, parseSnapshot, parseUiObservationLog, planOpenCodeInstall, planUpdate, prepareLocalModel, prepareSessionSynthesis, readMonitorLog, readUiObservationLog, removeConfiguredWorkspace, resolveAllBeliefPaths, resolveAllGoalPaths, resolveObserverPolicy, resolveOllamaCommand, resolveReference, resolveRepoPath, resolveReviewContext, saveConfiguredWorkspaces, saveCredentials, saveInstallConfig, saveMachineAiConfig, serializeGoal, setADOCredentials, setActiveWorkspaceId, setInstallChannel, setInstallMode, setProviderApiKey, setSetting, setTursoCredentials, slugify, startSession, syncObserverSidecarPolicy, t, toSidecarPrivacyPolicy, uiObservationLogExists, uiObservationTimeSpan, unblockReady, updateCard, updateGoalStatus, updateToken, upsertConfiguredWorkspace, verifySnapshot, wouldCreateCycle, writeMonitorEvent };
2197
+ export { type ADOConfig, type ADOCredentials, type AgentSkill, type AnalysisResult, type ApplySessionSynthesisInput, type ApplySessionSynthesisResult, BUILT_IN_SENSITIVE_MATCHERS, type BloomLevel$1 as BloomLevel, type CaptureDecision, type CaptureDenialReason, type CaptureRequest, type Card, type CardDeletionImpact, type CardState$1 as CardState, type CascadeBlockResult, type CommandRecord, type CommandSequence, type ConfirmFoundationsResult, type ConnectionOptions, type CreateAgentSkillInput, type CreateGoalInput, type CreateReviewInput, type CreateSessionInput, type CreateTokenInput, type Credentials, type CurriculumCardInput, DEFAULT_OBSERVER_POLICY, DEFAULT_REVIEW_CONTEXT_MAX_CHARS, type Database, type DatabaseProvider, type DatabaseTargetInfo, type DatabaseValue, type DeleteCardResult, type DeleteTokenResult, type DiscoveryOptions, type DomainCompetence, type EmbeddedTokenRow, type EmbeddingCoverage, type EmbeddingStaleness, type EvaluateInput, type EvaluateResult, type ExecuteReviewActionInput, type ExecutionContext, type FSRSParameters, type FoundationProposalInput, type Goal, type GoalFrontmatter, type GoalStatus, type GoalSummary, HOMEBREW_CASK, type HybridScoredToken, type HybridSearchOptions, type ImportCurriculumResult, type ImportResult, type InstallChannel, type InstallConfig, type InstallMode, type InstallPlan, type InstallResult, type LocalLLMRunner, type LogStepInput, type MachineAiConfig, type MachineProviderRecord, type MachineRoleBinding, type MonitorEvent, type Neighborhood, type NeighborhoodToken, OBSERVER_POLICY_UNSET_HINT, OBSERVER_POLICY_VERSION, type ObservationRating, type ObserverConsent, type ObserverPolicy, type ObserverRetention, type ObserverScope, type ObserverSettingKey, type OllamaDetectionOptions, type PersonalCard, type PrepareSessionSynthesisInput, type Prerequisite, type PrerequisiteWithToken, type PromptInput, REVIEW_CONTEXT_CACHE_TTL_MS, type Rating, type RecallPrompt, type RemoteDatabaseOptions, type RepoPaths, type ResolvedCaptureTarget, type ResolvedReference, type ReviewActionResult, type ReviewActionType, type ReviewContext, type ReviewLog, type ReviewQueue, type ReviewQueueItem, type ReviewQueueOptions, type RunResult, SIDECAR_POLICY_FILE, SNAPSHOT_VERSION, type SchedulingCard, type Session, type SessionStep, type SessionSummary, type SessionSynthesisCandidate, type SessionSynthesisEvidence, type SessionSynthesisPreview, type SessionSynthesisRecord, type SidecarPrivacyPolicy, type SkillProposal, type SkillSource, type SnapshotManifest, type SourceProposalInput, type SplitProposalInput, type Statement, type SupportedLocale, type SymbiosisMode, type SynthesisConfidence, type SystemProfile, type Token, type TokenDeleteImpact, type TokenEmbedding, type TokenNeedingEmbedding, type TokenPattern, type TranslationKey, type TursoCredentials, UI_OBSERVATION_PROTOCOL_VERSION, type UiActionType, type UiApplicationContext, type UiCandidateToken, type UiEvidenceRef, type UiEvidenceType, type UiObservationKind, type UiObservationReport, type UiObservedAction, type UnblockResult, type UpdateActionKind, type UpdateCardInput, type UpdateDecision, type UpdateStep, type UpdateStepKind, type UpdateTokenInput, type UserSetting, type UserStats, WINGET_PACKAGE_ID, type WorkItem, type WorkspaceConfig, type WorkspaceKind, type WorkspaceSourceControl, addPrerequisite, analyzeObservation, appendUiObservationReport, applySessionSynthesis, buildReviewQueue, buildUiSynthesisCandidates, cascadeBlock, clearADOCredentials, clearProviderApiKey, clearReviewContextCache, clearTursoCredentials, compareVersions, computeContentHash, confirmCardSplit, confirmFoundations, confirmSourceImport, cosineSimilarity, createAgentSkill, createFSRS, createGoal, createToken, decidePostCapture, decidePreCapture, decideUpdate, decodeEmbedding, deleteCardForUser, deleteSetting, deleteToken, deprecateToken, detectSyncProvider, detectSystemLocale, discoverSkills, distributeGlobalSkills, embeddingContentForToken, encodeEmbedding, endSession, ensureCard, ensureMonitorDir, ensureUiObserverDir, evaluateRating, executeReviewAction, exportSnapshot, extractTasks, extractTokenRefs, fetchActiveWorkItems, findTokens, generateBashHooks, generateBashUnhooks, generateConceptFreeCue, generatePowerShellHooks, generatePowerShellUnhooks, generatePrompt, generateTokenSlug, generateZshHooks, generateZshUnhooks, getADOCredentials, getActiveWorkspace, getActiveWorkspaceId, getAgentSkill, getAllSettings, getAllSettingsDetailed, getBlockedCards, getCard, getCardById, getCardDeletionImpact, getConfiguredWorkspaces, getDatabaseTargetInfo, getDefaultDbPath, getDependents, getDomainCompetence, getDueCards, getEmbeddingCoverage, getGoal, getGoalTree, getInstallChannel, getInstallMode, getMachineAiConfig, getMonitorDir, getMonitorLogStats, getMonitorPath, getPackageSkillPath, getPrerequisites, getProviderApiKey, getRepoPaths, getReviewsForCard, getReviewsForUser, getSessionSummary, getSessionSynthesisRecords, getSetting, getSystemProfile, getTokenById, getTokenBySlug, getTokenDeleteImpact, getTokenEmbedding, getTokenNeighborhood, getTursoCredentials, getUiObservationPath, getUiObserverDir, getUserStats, hasCommand, importCurriculumCards, importSnapshot, injectShellHooks, installFastFlowLM, installOllama, installOpenCode, interleave, isObserverPolicyConfigured, isOllamaInstalled, isUiObservationReport, listAgentSkills, listEmbeddedTokens, listGoals, listPersonalCards, listProviderApiKeyRefs, listTokens, listTokensNeedingEmbedding, loadADOConfig, loadCredentials, loadInstallConfig, logReview, logStep, matchBuiltInSensitive, matchDenylist, matchesFilePath, monitorLogExists, normalizeLocale, normalizePath, openDatabase, openDatabaseWithSync, openRemoteDatabase, pairCommands, parseGoalFile, parseMonitorLog, parseObserverList, parseObserverPolicy, parseSnapshot, parseUiObservationLog, planOpenCodeInstall, planUpdate, prepareLocalModel, prepareSessionSynthesis, readMonitorLog, readUiObservationLog, removeConfiguredWorkspace, resolveAllBeliefPaths, resolveAllGoalPaths, resolveObserverPolicy, resolveOllamaCommand, resolveReference, resolveRepoPath, resolveReviewContext, saveConfiguredWorkspaces, saveCredentials, saveInstallConfig, saveMachineAiConfig, searchTokensHybrid, serializeGoal, setADOCredentials, setActiveWorkspaceId, setInstallChannel, setInstallMode, setProviderApiKey, setSetting, setTursoCredentials, slugify, startSession, syncObserverSidecarPolicy, t, toSidecarPrivacyPolicy, uiObservationLogExists, uiObservationTimeSpan, unblockReady, updateCard, updateGoalStatus, updateToken, upsertConfiguredWorkspace, upsertTokenEmbedding, verifySnapshot, wouldCreateCycle, writeMonitorEvent };
package/dist/index.js CHANGED
@@ -643,6 +643,18 @@ CREATE TABLE IF NOT EXISTS user_config (
643
643
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
644
644
  );
645
645
 
646
+ -- Token embeddings: one vector per token for semantic search (ADR 2026-07-03).
647
+ -- Not a column on tokens: hot paths do SELECT * FROM tokens and a ~3KB blob
648
+ -- per row would ride along on all of them.
649
+ CREATE TABLE IF NOT EXISTS token_embeddings (
650
+ token_id TEXT PRIMARY KEY REFERENCES tokens(id) ON DELETE CASCADE,
651
+ embedding BLOB NOT NULL,
652
+ model TEXT NOT NULL,
653
+ dims INTEGER NOT NULL,
654
+ content_hash TEXT NOT NULL,
655
+ embedded_at TEXT NOT NULL DEFAULT (datetime('now'))
656
+ );
657
+
646
658
  -- Agent skills: task recipes the agent learns from user guidance
647
659
  CREATE TABLE IF NOT EXISTS agent_skills (
648
660
  id TEXT PRIMARY KEY,
@@ -989,6 +1001,16 @@ async function runMigrations(db) {
989
1001
  await db.exec(`ALTER TABLE tokens ADD COLUMN topic_id TEXT`);
990
1002
  }
991
1003
  }
1004
+ await db.exec(`
1005
+ CREATE TABLE IF NOT EXISTS token_embeddings (
1006
+ token_id TEXT PRIMARY KEY REFERENCES tokens(id) ON DELETE CASCADE,
1007
+ embedding BLOB NOT NULL,
1008
+ model TEXT NOT NULL,
1009
+ dims INTEGER NOT NULL,
1010
+ content_hash TEXT NOT NULL,
1011
+ embedded_at TEXT NOT NULL DEFAULT (datetime('now'))
1012
+ )
1013
+ `);
992
1014
  }
993
1015
 
994
1016
  // src/kernel/db/snapshot.ts
@@ -2451,6 +2473,165 @@ async function deleteSetting(db, key) {
2451
2473
  return result.changes > 0;
2452
2474
  }
2453
2475
 
2476
+ // src/kernel/models/token-embedding.ts
2477
+ import { createHash as createHash2 } from "crypto";
2478
+ function embeddingContentForToken(t2) {
2479
+ return `${t2.concept}
2480
+ ${t2.question ?? ""}
2481
+ ${t2.domain}`;
2482
+ }
2483
+ function computeContentHash(text) {
2484
+ return createHash2("sha256").update(text, "utf8").digest("hex");
2485
+ }
2486
+ function encodeEmbedding(vec) {
2487
+ const f = Float32Array.from(vec);
2488
+ return new Uint8Array(f.buffer);
2489
+ }
2490
+ function decodeEmbedding(blob) {
2491
+ if (blob.byteLength % 4 !== 0) {
2492
+ throw new Error(
2493
+ "Invalid embedding blob size: must be a multiple of 4 bytes"
2494
+ );
2495
+ }
2496
+ if (blob.byteOffset % 4 === 0) {
2497
+ return new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
2498
+ }
2499
+ const copy = Uint8Array.prototype.slice.call(blob);
2500
+ return new Float32Array(copy.buffer, 0, copy.byteLength / 4);
2501
+ }
2502
+ function decodeRow(row) {
2503
+ return {
2504
+ token_id: row.token_id,
2505
+ model: row.model,
2506
+ dims: row.dims,
2507
+ content_hash: row.content_hash,
2508
+ embedded_at: row.embedded_at,
2509
+ embedding: decodeEmbedding(row.embedding)
2510
+ };
2511
+ }
2512
+ async function upsertTokenEmbedding(db, input) {
2513
+ const encoded = encodeEmbedding(input.embedding);
2514
+ const dims = input.embedding.length;
2515
+ const embeddedAt = (/* @__PURE__ */ new Date()).toISOString();
2516
+ await db.prepare(`
2517
+ INSERT INTO token_embeddings (token_id, embedding, model, dims, content_hash, embedded_at)
2518
+ VALUES (?, ?, ?, ?, ?, ?)
2519
+ ON CONFLICT(token_id) DO UPDATE SET
2520
+ embedding = excluded.embedding,
2521
+ model = excluded.model,
2522
+ dims = excluded.dims,
2523
+ content_hash = excluded.content_hash,
2524
+ embedded_at = excluded.embedded_at
2525
+ `).run(
2526
+ input.tokenId,
2527
+ encoded,
2528
+ input.model,
2529
+ dims,
2530
+ input.contentHash,
2531
+ embeddedAt
2532
+ );
2533
+ }
2534
+ async function getTokenEmbedding(db, tokenId) {
2535
+ const row = await db.prepare("SELECT * FROM token_embeddings WHERE token_id = ?").get(tokenId);
2536
+ return row ? decodeRow(row) : void 0;
2537
+ }
2538
+ async function listTokensNeedingEmbedding(db, model, opts) {
2539
+ const rows = await db.prepare(`
2540
+ SELECT t.*, e.model AS emb_model, e.dims AS emb_dims, e.content_hash AS emb_hash
2541
+ FROM tokens t
2542
+ LEFT JOIN token_embeddings e ON e.token_id = t.id
2543
+ WHERE t.deprecated_at IS NULL
2544
+ `).all();
2545
+ const needing = [];
2546
+ for (const row of rows) {
2547
+ let reason = null;
2548
+ let text = "";
2549
+ if (opts?.force) {
2550
+ reason = "content-changed";
2551
+ } else if (row.emb_model === null) {
2552
+ reason = "missing";
2553
+ } else if (row.emb_model !== model) {
2554
+ reason = "model-changed";
2555
+ } else if (opts?.dims !== void 0 && row.emb_dims !== opts.dims) {
2556
+ reason = "dimension-changed";
2557
+ } else {
2558
+ const computedText = embeddingContentForToken(row);
2559
+ const hash = computeContentHash(computedText);
2560
+ if (row.emb_hash !== hash) {
2561
+ reason = "content-changed";
2562
+ text = computedText;
2563
+ }
2564
+ }
2565
+ if (reason) {
2566
+ if (!text) {
2567
+ text = embeddingContentForToken(row);
2568
+ }
2569
+ needing.push({ token: row, text, reason });
2570
+ }
2571
+ }
2572
+ if (opts?.limit !== void 0) {
2573
+ return needing.slice(0, opts.limit);
2574
+ }
2575
+ return needing;
2576
+ }
2577
+ async function getEmbeddingCoverage(db, model, opts) {
2578
+ const rows = await db.prepare(`
2579
+ SELECT t.*, e.model AS emb_model, e.dims AS emb_dims, e.content_hash AS emb_hash
2580
+ FROM tokens t
2581
+ LEFT JOIN token_embeddings e ON e.token_id = t.id
2582
+ WHERE t.deprecated_at IS NULL
2583
+ `).all();
2584
+ let missing = 0;
2585
+ let stale = 0;
2586
+ for (const row of rows) {
2587
+ if (row.emb_model === null) {
2588
+ missing++;
2589
+ continue;
2590
+ }
2591
+ if (row.emb_model !== model) {
2592
+ stale++;
2593
+ continue;
2594
+ }
2595
+ if (opts?.dims !== void 0 && row.emb_dims !== opts.dims) {
2596
+ stale++;
2597
+ continue;
2598
+ }
2599
+ const hash = computeContentHash(embeddingContentForToken(row));
2600
+ if (row.emb_hash !== hash) {
2601
+ stale++;
2602
+ }
2603
+ }
2604
+ const tokens = rows.length;
2605
+ return {
2606
+ tokens,
2607
+ embedded: tokens - missing - stale,
2608
+ missing,
2609
+ stale
2610
+ };
2611
+ }
2612
+ async function listEmbeddedTokens(db, model) {
2613
+ const rows = await db.prepare(`
2614
+ SELECT t.*, e.embedding AS embedding, e.content_hash AS emb_hash
2615
+ FROM token_embeddings e
2616
+ JOIN tokens t ON t.id = e.token_id
2617
+ WHERE e.model = ? AND t.deprecated_at IS NULL
2618
+ `).all(model);
2619
+ return rows.flatMap((row) => {
2620
+ const { embedding, emb_hash, ...token } = row;
2621
+ if (emb_hash !== computeContentHash(embeddingContentForToken(token))) {
2622
+ return [];
2623
+ }
2624
+ try {
2625
+ return [{ token, embedding: decodeEmbedding(embedding) }];
2626
+ } catch (err) {
2627
+ console.warn(
2628
+ `Warning: Corrupted embedding for token ${token.slug} (${token.id}) ignored: ${err.message}`
2629
+ );
2630
+ return [];
2631
+ }
2632
+ });
2633
+ }
2634
+
2454
2635
  // src/kernel/observation/analyzer.ts
2455
2636
  function parseMonitorLog(jsonl) {
2456
2637
  const events = [];
@@ -4593,6 +4774,82 @@ function intersperseNew(reviews, newCards, interval) {
4593
4774
  return result;
4594
4775
  }
4595
4776
 
4777
+ // src/kernel/search/hybrid.ts
4778
+ function cosineSimilarity(a, b) {
4779
+ if (a.length !== b.length) return 0;
4780
+ let dot = 0;
4781
+ let normA = 0;
4782
+ let normB = 0;
4783
+ const len = a.length;
4784
+ for (let i = 0; i < len; i++) {
4785
+ dot += a[i] * b[i];
4786
+ normA += a[i] * a[i];
4787
+ normB += b[i] * b[i];
4788
+ }
4789
+ if (normA === 0 || normB === 0) return 0;
4790
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB));
4791
+ }
4792
+ async function searchTokensHybrid(db, query, opts) {
4793
+ const limit = opts?.limit ?? 20;
4794
+ const rrfK = opts?.rrfK ?? 60;
4795
+ const vectorTopK = opts?.vectorTopK ?? 10;
4796
+ const lexicalHits = await findTokens(db, query);
4797
+ let vectorHits = [];
4798
+ if (opts?.queryEmbedding && opts?.model) {
4799
+ const queryVec = Float32Array.from(opts.queryEmbedding);
4800
+ const embedded = await listEmbeddedTokens(db, opts.model);
4801
+ const candidates = [];
4802
+ for (const row of embedded) {
4803
+ if (row.embedding.length !== queryVec.length) {
4804
+ continue;
4805
+ }
4806
+ const similarity = cosineSimilarity(queryVec, row.embedding);
4807
+ if (similarity <= 0) {
4808
+ continue;
4809
+ }
4810
+ candidates.push({ token: row.token, similarity });
4811
+ }
4812
+ candidates.sort((a, b) => b.similarity - a.similarity);
4813
+ vectorHits = candidates.slice(0, vectorTopK);
4814
+ }
4815
+ const tokenMap = /* @__PURE__ */ new Map();
4816
+ const getOrCreateEntry = (token) => {
4817
+ let entry = tokenMap.get(token.id);
4818
+ if (!entry) {
4819
+ entry = {
4820
+ ...token,
4821
+ score: 0,
4822
+ lexicalRank: null,
4823
+ vectorRank: null,
4824
+ similarity: null
4825
+ };
4826
+ tokenMap.set(token.id, entry);
4827
+ }
4828
+ return entry;
4829
+ };
4830
+ for (let i = 0; i < lexicalHits.length; i++) {
4831
+ const hit = lexicalHits[i];
4832
+ const entry = getOrCreateEntry(hit);
4833
+ entry.lexicalRank = i + 1;
4834
+ entry.score += 1 / (rrfK + entry.lexicalRank);
4835
+ }
4836
+ for (let j = 0; j < vectorHits.length; j++) {
4837
+ const hit = vectorHits[j];
4838
+ const entry = getOrCreateEntry(hit.token);
4839
+ entry.vectorRank = j + 1;
4840
+ entry.similarity = hit.similarity;
4841
+ entry.score += 1 / (rrfK + entry.vectorRank);
4842
+ }
4843
+ const results = Array.from(tokenMap.values());
4844
+ results.sort((a, b) => {
4845
+ if (Math.abs(a.score - b.score) > 1e-9) {
4846
+ return b.score - a.score;
4847
+ }
4848
+ return a.slug.localeCompare(b.slug);
4849
+ });
4850
+ return results.slice(0, limit);
4851
+ }
4852
+
4596
4853
  // src/kernel/system/hooks.ts
4597
4854
  import {
4598
4855
  appendFileSync as appendFileSync3,
@@ -5625,9 +5882,11 @@ export {
5625
5882
  clearReviewContextCache,
5626
5883
  clearTursoCredentials,
5627
5884
  compareVersions,
5885
+ computeContentHash,
5628
5886
  confirmCardSplit,
5629
5887
  confirmFoundations,
5630
5888
  confirmSourceImport,
5889
+ cosineSimilarity,
5631
5890
  createAgentSkill,
5632
5891
  createFSRS,
5633
5892
  createGoal,
@@ -5635,6 +5894,7 @@ export {
5635
5894
  decidePostCapture,
5636
5895
  decidePreCapture,
5637
5896
  decideUpdate,
5897
+ decodeEmbedding,
5638
5898
  deleteCardForUser,
5639
5899
  deleteSetting,
5640
5900
  deleteToken,
@@ -5643,6 +5903,8 @@ export {
5643
5903
  detectSystemLocale,
5644
5904
  discoverSkills,
5645
5905
  distributeGlobalSkills,
5906
+ embeddingContentForToken,
5907
+ encodeEmbedding,
5646
5908
  endSession,
5647
5909
  ensureCard,
5648
5910
  ensureMonitorDir,
@@ -5679,6 +5941,7 @@ export {
5679
5941
  getDependents,
5680
5942
  getDomainCompetence,
5681
5943
  getDueCards,
5944
+ getEmbeddingCoverage,
5682
5945
  getGoal,
5683
5946
  getGoalTree,
5684
5947
  getInstallChannel,
@@ -5700,6 +5963,7 @@ export {
5700
5963
  getTokenById,
5701
5964
  getTokenBySlug,
5702
5965
  getTokenDeleteImpact,
5966
+ getTokenEmbedding,
5703
5967
  getTokenNeighborhood,
5704
5968
  getTursoCredentials,
5705
5969
  getUiObservationPath,
@@ -5717,10 +5981,12 @@ export {
5717
5981
  isOllamaInstalled,
5718
5982
  isUiObservationReport,
5719
5983
  listAgentSkills,
5984
+ listEmbeddedTokens,
5720
5985
  listGoals,
5721
5986
  listPersonalCards,
5722
5987
  listProviderApiKeyRefs,
5723
5988
  listTokens,
5989
+ listTokensNeedingEmbedding,
5724
5990
  loadADOConfig,
5725
5991
  loadCredentials,
5726
5992
  loadInstallConfig,
@@ -5760,6 +6026,7 @@ export {
5760
6026
  saveCredentials,
5761
6027
  saveInstallConfig,
5762
6028
  saveMachineAiConfig,
6029
+ searchTokensHybrid,
5763
6030
  serializeGoal,
5764
6031
  setADOCredentials,
5765
6032
  setActiveWorkspaceId,
@@ -5780,6 +6047,7 @@ export {
5780
6047
  updateGoalStatus,
5781
6048
  updateToken,
5782
6049
  upsertConfiguredWorkspace,
6050
+ upsertTokenEmbedding,
5783
6051
  verifySnapshot,
5784
6052
  wouldCreateCycle,
5785
6053
  writeMonitorEvent