zam-core 0.6.3 → 0.7.1

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
@@ -566,12 +566,18 @@ interface PrerequisiteWithToken extends Prerequisite {
566
566
  domain: string;
567
567
  bloom_level: number;
568
568
  }
569
+ /**
570
+ * Collect all prerequisite edges as an adjacency map: child → parent set.
571
+ * Only used for cycle detection; the full graph is loaded once per
572
+ * addPrerequisite call (small N in practice).
573
+ */
574
+ declare function buildAncestorMap(db: Database): Promise<Map<string, Set<string>>>;
569
575
  /**
570
576
  * Returns true if adding edge (tokenId → requiresId) would create a cycle.
571
577
  * Uses BFS from requiresId: if tokenId is reachable, adding the edge closes
572
578
  * a loop.
573
579
  */
574
- declare function wouldCreateCycle(db: Database, tokenId: string, requiresId: string): Promise<boolean>;
580
+ declare function wouldCreateCycle(db: Database, tokenId: string, requiresId: string, ancestors?: Map<string, Set<string>>): Promise<boolean>;
575
581
  /**
576
582
  * Add a prerequisite edge: tokenId requires requiresId.
577
583
  *
@@ -1010,6 +1016,97 @@ interface SourceProposalInput {
1010
1016
  */
1011
1017
  declare function confirmSourceImport(db: Database, userId: string, sourceId: string, proposals: SourceProposalInput[]): Promise<ConfirmFoundationsResult>;
1012
1018
 
1019
+ /**
1020
+ * Token embedding repository — stores per-token vectors for semantic search
1021
+ * (ADR 2026-07-03) and derives staleness from a content hash.
1022
+ *
1023
+ * This module is pure storage + classification: no HTTP, no LLM calls. The
1024
+ * CLI layer (`src/cli/llm/embedder.ts`) generates vectors and calls in here.
1025
+ */
1026
+
1027
+ interface TokenEmbedding {
1028
+ token_id: string;
1029
+ model: string;
1030
+ dims: number;
1031
+ content_hash: string;
1032
+ embedded_at: string;
1033
+ embedding: Float32Array;
1034
+ }
1035
+ type EmbeddingStaleness = "missing" | "content-changed" | "model-changed" | "dimension-changed";
1036
+ interface TokenNeedingEmbedding {
1037
+ token: Token;
1038
+ /** Canonical text to embed — already hashed the same way. */
1039
+ text: string;
1040
+ reason: EmbeddingStaleness;
1041
+ }
1042
+ interface EmbeddingCoverage {
1043
+ tokens: number;
1044
+ embedded: number;
1045
+ missing: number;
1046
+ stale: number;
1047
+ }
1048
+ interface EmbeddedTokenRow {
1049
+ token: Token;
1050
+ embedding: Float32Array;
1051
+ }
1052
+ /**
1053
+ * The canonical text embedded for a token. Every stored hash and every stored
1054
+ * vector derives from exactly this string — never the slug, which is an
1055
+ * identifier, not meaning.
1056
+ */
1057
+ declare function embeddingContentForToken(t: Pick<Token, "concept" | "question" | "domain">): string;
1058
+ declare function computeContentHash(text: string): string;
1059
+ /**
1060
+ * Encode a vector as a little-endian float32 BLOB. Builds a fresh buffer (no
1061
+ * aliasing into the caller's array) so the row can be stored independently of
1062
+ * whatever produced the vector.
1063
+ */
1064
+ declare function encodeEmbedding(vec: ArrayLike<number>): Uint8Array;
1065
+ /**
1066
+ * Decode a stored BLOB back into a Float32Array.
1067
+ *
1068
+ * BLOB values come back as `Buffer` (better-sqlite3) or `Uint8Array` (remote
1069
+ * provider). better-sqlite3 Buffers are views into a shared pool and may have
1070
+ * a non-zero, non-4-aligned `byteOffset` — constructing a Float32Array
1071
+ * directly over such a buffer throws (RangeError) or silently reads garbage.
1072
+ * Copying guarantees a fresh, 0-offset backing buffer — but `blob.slice()`
1073
+ * cannot be used for this: `Buffer.prototype.slice` overrides
1074
+ * `Uint8Array.prototype.slice` to return a *view* into the same backing
1075
+ * buffer (a legacy Node.js Buffer API quirk), not a copy, so it would still
1076
+ * carry the misaligned offset. Uint8Array's own `slice` must be borrowed
1077
+ * explicitly to force an actual copy.
1078
+ */
1079
+ declare function decodeEmbedding(blob: Uint8Array): Float32Array;
1080
+ declare function upsertTokenEmbedding(db: Database, input: {
1081
+ tokenId: string;
1082
+ embedding: ArrayLike<number>;
1083
+ model: string;
1084
+ contentHash: string;
1085
+ }): Promise<void>;
1086
+ declare function getTokenEmbedding(db: Database, tokenId: string): Promise<TokenEmbedding | undefined>;
1087
+ /**
1088
+ * Classify every non-deprecated token against a target embedding model:
1089
+ * missing (no stored row), model-changed (stored under a different model
1090
+ * id), or content-changed (stored hash no longer matches the canonical
1091
+ * text). `force: true` returns every token regardless of freshness, tagged
1092
+ * `content-changed` since that is the closest-fitting reason to re-embed.
1093
+ */
1094
+ declare function listTokensNeedingEmbedding(db: Database, model: string, opts?: {
1095
+ limit?: number;
1096
+ force?: boolean;
1097
+ dims?: number;
1098
+ }): Promise<TokenNeedingEmbedding[]>;
1099
+ /** Same classification scan as {@link listTokensNeedingEmbedding}, counts only. */
1100
+ declare function getEmbeddingCoverage(db: Database, model: string, opts?: {
1101
+ dims?: number;
1102
+ }): Promise<EmbeddingCoverage>;
1103
+ /**
1104
+ * All tokens with a fresh vector under `model` to enter the vector search leg.
1105
+ * Re-hashes rows here because lazy top-up is intentionally bounded: a stale
1106
+ * row beyond the current batch must never participate with its old meaning.
1107
+ */
1108
+ declare function listEmbeddedTokens(db: Database, model: string): Promise<EmbeddedTokenRow[]>;
1109
+
1013
1110
  /**
1014
1111
  * Monitor log analyzer — maps observed shell commands to token ratings.
1015
1112
  *
@@ -1790,6 +1887,68 @@ interface ReviewQueue {
1790
1887
  */
1791
1888
  declare function buildReviewQueue(db: Database, options: ReviewQueueOptions): Promise<ReviewQueue>;
1792
1889
 
1890
+ /**
1891
+ * Hybrid lexical/vector token search using reciprocal-rank fusion (RRF).
1892
+ * (ADR 2026-07-03)
1893
+ *
1894
+ * This module is pure math + database queries — zero LLM dependencies, no HTTP.
1895
+ */
1896
+
1897
+ interface HybridSearchOptions {
1898
+ queryEmbedding?: ArrayLike<number>;
1899
+ /** Model the stored vectors must match; required when queryEmbedding is set. */
1900
+ model?: string;
1901
+ limit?: number;
1902
+ rrfK?: number;
1903
+ vectorTopK?: number;
1904
+ }
1905
+ interface HybridScoredToken extends Token {
1906
+ score: number;
1907
+ lexicalRank: number | null;
1908
+ vectorRank: number | null;
1909
+ similarity: number | null;
1910
+ }
1911
+ /** Calculates the cosine similarity between two float vectors. Returns 0 if either norm is 0. */
1912
+ declare function cosineSimilarity(a: Float32Array, b: Float32Array): number;
1913
+ /**
1914
+ * Performs a hybrid search over active tokens.
1915
+ * Combines lexical relevance rankings with vector cosine similarities.
1916
+ */
1917
+ declare function searchTokensHybrid(db: Database, query: string, opts?: HybridSearchOptions): Promise<HybridScoredToken[]>;
1918
+
1919
+ /**
1920
+ * Foundation suggestions for tokens.
1921
+ * Suggests existing tokens that are semantically related as prerequisite candidates.
1922
+ *
1923
+ * This module is pure math + database queries — zero LLM dependencies, no HTTP.
1924
+ */
1925
+
1926
+ interface FoundationSuggestion {
1927
+ token: Token;
1928
+ similarity: number;
1929
+ alreadyPrerequisite: boolean;
1930
+ wouldCreateCycle: boolean;
1931
+ /** Candidate's bloom_level is higher than the target's — unusual for a foundation. */
1932
+ bloomAboveTarget: boolean;
1933
+ }
1934
+ interface SuggestFoundationsOptions {
1935
+ queryEmbedding: ArrayLike<number>;
1936
+ /** Canonical embedding model id the stored vectors must match. */
1937
+ model: string;
1938
+ /** Set when the target token already exists (register-after / rating-1 flow). */
1939
+ targetTokenId?: string;
1940
+ /** Used for the bloomAboveTarget flag; defaults to 5 (nothing flagged). */
1941
+ targetBloomLevel?: BloomLevel$1;
1942
+ limit?: number;
1943
+ minSimilarity?: number;
1944
+ maxSimilarity?: number;
1945
+ }
1946
+ /**
1947
+ * Suggests existing tokens that are semantically related as prerequisite candidates.
1948
+ * Filters by similarity range and ranks descending by similarity.
1949
+ */
1950
+ declare function suggestFoundations(db: Database, opts: SuggestFoundationsOptions): Promise<FoundationSuggestion[]>;
1951
+
1793
1952
  /**
1794
1953
  * Get the path to ZAM's internal package SKILL.md.
1795
1954
  */
@@ -1914,7 +2073,7 @@ interface InstallConfig {
1914
2073
  /** Machine-local id of the workspace currently active in this install. */
1915
2074
  activeWorkspaceId?: string;
1916
2075
  }
1917
- type MachineAiRole = "vision" | "recall" | "text";
2076
+ type MachineAiRole = "vision" | "recall" | "text" | "embedding";
1918
2077
  type MachineApiFlavor = "chat-completions" | "anthropic-messages";
1919
2078
  interface MachineProviderRecord {
1920
2079
  label?: string;
@@ -2074,4 +2233,4 @@ declare function resolveAllBeliefPaths(db: Database): Promise<string[]>;
2074
2233
  */
2075
2234
  declare function resolveAllGoalPaths(db: Database): Promise<string[]>;
2076
2235
 
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 };
2236
+ 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 FoundationSuggestion, 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 SuggestFoundationsOptions, 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, buildAncestorMap, 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, suggestFoundations, 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
@@ -2193,9 +2215,9 @@ async function buildAncestorMap(db) {
2193
2215
  }
2194
2216
  return map;
2195
2217
  }
2196
- async function wouldCreateCycle(db, tokenId, requiresId) {
2218
+ async function wouldCreateCycle(db, tokenId, requiresId, ancestors) {
2197
2219
  if (tokenId === requiresId) return true;
2198
- const ancestors = await buildAncestorMap(db);
2220
+ const map = ancestors ?? await buildAncestorMap(db);
2199
2221
  const visited = /* @__PURE__ */ new Set();
2200
2222
  const queue = [requiresId];
2201
2223
  while (queue.length > 0) {
@@ -2203,7 +2225,7 @@ async function wouldCreateCycle(db, tokenId, requiresId) {
2203
2225
  if (current === tokenId) return true;
2204
2226
  if (visited.has(current)) continue;
2205
2227
  visited.add(current);
2206
- const parents = ancestors.get(current);
2228
+ const parents = map.get(current);
2207
2229
  if (parents) {
2208
2230
  for (const parent of parents) {
2209
2231
  if (!visited.has(parent)) queue.push(parent);
@@ -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,147 @@ 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
+
4853
+ // src/kernel/search/suggestions.ts
4854
+ async function suggestFoundations(db, opts) {
4855
+ const minSimilarity = opts.minSimilarity ?? 0.45;
4856
+ const maxSimilarity = opts.maxSimilarity ?? 0.85;
4857
+ const limit = opts.limit ?? 5;
4858
+ const targetBloomLevel = opts.targetBloomLevel ?? 5;
4859
+ if (minSimilarity >= maxSimilarity) {
4860
+ return [];
4861
+ }
4862
+ const embedded = await listEmbeddedTokens(db, opts.model);
4863
+ if (embedded.length === 0) {
4864
+ return [];
4865
+ }
4866
+ const queryVec = Float32Array.from(opts.queryEmbedding);
4867
+ const candidates = [];
4868
+ for (const row of embedded) {
4869
+ if (opts.targetTokenId && row.token.id === opts.targetTokenId) {
4870
+ continue;
4871
+ }
4872
+ const similarity = cosineSimilarity(queryVec, row.embedding);
4873
+ if (similarity >= minSimilarity && similarity < maxSimilarity) {
4874
+ candidates.push({ token: row.token, similarity });
4875
+ }
4876
+ }
4877
+ candidates.sort((a, b) => {
4878
+ if (Math.abs(a.similarity - b.similarity) > 1e-9) {
4879
+ return b.similarity - a.similarity;
4880
+ }
4881
+ return a.token.slug.localeCompare(b.token.slug);
4882
+ });
4883
+ const topCandidates = candidates.slice(0, limit);
4884
+ const prereqIds = /* @__PURE__ */ new Set();
4885
+ let ancestors;
4886
+ if (opts.targetTokenId) {
4887
+ const prereqs = await getPrerequisites(db, opts.targetTokenId);
4888
+ for (const p of prereqs) {
4889
+ prereqIds.add(p.requires_id);
4890
+ }
4891
+ ancestors = await buildAncestorMap(db);
4892
+ }
4893
+ const results = [];
4894
+ for (const cand of topCandidates) {
4895
+ let alreadyPrerequisite = false;
4896
+ let wouldCreateCycleFlag = false;
4897
+ const bloomAboveTarget = cand.token.bloom_level > targetBloomLevel;
4898
+ if (opts.targetTokenId) {
4899
+ alreadyPrerequisite = prereqIds.has(cand.token.id);
4900
+ wouldCreateCycleFlag = await wouldCreateCycle(
4901
+ db,
4902
+ opts.targetTokenId,
4903
+ cand.token.id,
4904
+ ancestors
4905
+ );
4906
+ }
4907
+ results.push({
4908
+ token: cand.token,
4909
+ similarity: cand.similarity,
4910
+ alreadyPrerequisite,
4911
+ wouldCreateCycle: wouldCreateCycleFlag,
4912
+ bloomAboveTarget
4913
+ });
4914
+ }
4915
+ return results;
4916
+ }
4917
+
4596
4918
  // src/kernel/system/hooks.ts
4597
4919
  import {
4598
4920
  appendFileSync as appendFileSync3,
@@ -5617,6 +5939,7 @@ export {
5617
5939
  analyzeObservation,
5618
5940
  appendUiObservationReport,
5619
5941
  applySessionSynthesis,
5942
+ buildAncestorMap,
5620
5943
  buildReviewQueue,
5621
5944
  buildUiSynthesisCandidates,
5622
5945
  cascadeBlock,
@@ -5625,9 +5948,11 @@ export {
5625
5948
  clearReviewContextCache,
5626
5949
  clearTursoCredentials,
5627
5950
  compareVersions,
5951
+ computeContentHash,
5628
5952
  confirmCardSplit,
5629
5953
  confirmFoundations,
5630
5954
  confirmSourceImport,
5955
+ cosineSimilarity,
5631
5956
  createAgentSkill,
5632
5957
  createFSRS,
5633
5958
  createGoal,
@@ -5635,6 +5960,7 @@ export {
5635
5960
  decidePostCapture,
5636
5961
  decidePreCapture,
5637
5962
  decideUpdate,
5963
+ decodeEmbedding,
5638
5964
  deleteCardForUser,
5639
5965
  deleteSetting,
5640
5966
  deleteToken,
@@ -5643,6 +5969,8 @@ export {
5643
5969
  detectSystemLocale,
5644
5970
  discoverSkills,
5645
5971
  distributeGlobalSkills,
5972
+ embeddingContentForToken,
5973
+ encodeEmbedding,
5646
5974
  endSession,
5647
5975
  ensureCard,
5648
5976
  ensureMonitorDir,
@@ -5679,6 +6007,7 @@ export {
5679
6007
  getDependents,
5680
6008
  getDomainCompetence,
5681
6009
  getDueCards,
6010
+ getEmbeddingCoverage,
5682
6011
  getGoal,
5683
6012
  getGoalTree,
5684
6013
  getInstallChannel,
@@ -5700,6 +6029,7 @@ export {
5700
6029
  getTokenById,
5701
6030
  getTokenBySlug,
5702
6031
  getTokenDeleteImpact,
6032
+ getTokenEmbedding,
5703
6033
  getTokenNeighborhood,
5704
6034
  getTursoCredentials,
5705
6035
  getUiObservationPath,
@@ -5717,10 +6047,12 @@ export {
5717
6047
  isOllamaInstalled,
5718
6048
  isUiObservationReport,
5719
6049
  listAgentSkills,
6050
+ listEmbeddedTokens,
5720
6051
  listGoals,
5721
6052
  listPersonalCards,
5722
6053
  listProviderApiKeyRefs,
5723
6054
  listTokens,
6055
+ listTokensNeedingEmbedding,
5724
6056
  loadADOConfig,
5725
6057
  loadCredentials,
5726
6058
  loadInstallConfig,
@@ -5760,6 +6092,7 @@ export {
5760
6092
  saveCredentials,
5761
6093
  saveInstallConfig,
5762
6094
  saveMachineAiConfig,
6095
+ searchTokensHybrid,
5763
6096
  serializeGoal,
5764
6097
  setADOCredentials,
5765
6098
  setActiveWorkspaceId,
@@ -5770,6 +6103,7 @@ export {
5770
6103
  setTursoCredentials,
5771
6104
  slugify,
5772
6105
  startSession,
6106
+ suggestFoundations,
5773
6107
  syncObserverSidecarPolicy,
5774
6108
  t,
5775
6109
  toSidecarPrivacyPolicy,
@@ -5780,6 +6114,7 @@ export {
5780
6114
  updateGoalStatus,
5781
6115
  updateToken,
5782
6116
  upsertConfiguredWorkspace,
6117
+ upsertTokenEmbedding,
5783
6118
  verifySnapshot,
5784
6119
  wouldCreateCycle,
5785
6120
  writeMonitorEvent