zam-core 0.9.2 → 0.9.4

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
@@ -864,6 +864,11 @@ declare function deleteSetting(db: Database, key: string): Promise<boolean>;
864
864
 
865
865
  type BloomLevel$1 = 1 | 2 | 3 | 4 | 5;
866
866
  type SymbiosisMode = "shadowing" | "copilot" | "autonomy";
867
+ /**
868
+ * Who authored a token's current recall question. LLM healing only
869
+ * overwrites questions whose source is not 'manual'.
870
+ */
871
+ type QuestionSource = "manual" | "llm" | "template";
867
872
  interface Token {
868
873
  id: string;
869
874
  slug: string;
@@ -875,6 +880,7 @@ interface Token {
875
880
  symbiosis_mode: SymbiosisMode | null;
876
881
  source_link: string | null;
877
882
  question: string | null;
883
+ question_source: QuestionSource;
878
884
  created_at: string;
879
885
  updated_at: string;
880
886
  deprecated_at: string | null;
@@ -891,6 +897,7 @@ interface CreateTokenInput {
891
897
  symbiosis_mode?: SymbiosisMode | null;
892
898
  source_link?: string | null;
893
899
  question?: string | null;
900
+ question_source?: QuestionSource;
894
901
  provider?: string | null;
895
902
  topic_id?: string | null;
896
903
  }
@@ -903,6 +910,7 @@ interface UpdateTokenInput {
903
910
  symbiosis_mode?: SymbiosisMode | null;
904
911
  source_link?: string | null;
905
912
  question?: string | null;
913
+ question_source?: QuestionSource;
906
914
  provider?: string | null;
907
915
  topic_id?: string | null;
908
916
  }
@@ -1479,6 +1487,9 @@ declare function cascadeBlock(db: Database, userId: string, tokenSlug: string):
1479
1487
  * If a blocked card has no prerequisites at all, it is unblocked immediately
1480
1488
  * (it was likely blocked in error or its prerequisites were removed).
1481
1489
  *
1490
+ * Unblocking cascades: when unblocking a card satisfies the last unmet
1491
+ * prerequisite of another blocked card, that card unblocks in the same call.
1492
+ *
1482
1493
  * @param db - Database connection
1483
1494
  * @param userId - The user whose blocked cards to check
1484
1495
  * @returns List of cards that were unblocked
@@ -1960,6 +1971,7 @@ interface ReviewQueueItem {
1960
1971
  dueAt: string;
1961
1972
  sourceLink: string | null;
1962
1973
  question: string | null;
1974
+ questionSource: string;
1963
1975
  }
1964
1976
  interface ReviewQueue {
1965
1977
  items: ReviewQueueItem[];
@@ -2334,4 +2346,4 @@ declare function resolveAllBeliefPaths(db: Database): Promise<string[]>;
2334
2346
  */
2335
2347
  declare function resolveAllGoalPaths(db: Database): Promise<string[]>;
2336
2348
 
2337
- 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 CreateKnowledgeContextInput, 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 KnowledgeContext, type ListTokensOptions, 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 UpdateKnowledgeContextInput, 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, assignTokenToContext, buildAncestorMap, buildReviewQueue, buildUiSynthesisCandidates, cascadeBlock, clearADOCredentials, clearProviderApiKey, clearReviewContextCache, clearTursoCredentials, compareVersions, computeContentHash, confirmCardSplit, confirmFoundations, confirmSourceImport, cosineSimilarity, createAgentSkill, createFSRS, createGoal, createKnowledgeContext, createToken, decidePostCapture, decidePreCapture, decideUpdate, decodeEmbedding, deleteCardForUser, deleteKnowledgeContext, 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, getActiveWorkspaceContext, getActiveWorkspaceId, getAgentSkill, getAllSettings, getAllSettingsDetailed, getBlockedCards, getCard, getCardById, getCardDeletionImpact, getConfiguredWorkspaces, getDatabaseTargetInfo, getDefaultDbPath, getDependents, getDisplayTitle, getDomainCompetence, getDueCards, getEmbeddingCoverage, getGoal, getGoalTree, getInstallChannel, getInstallMode, getKnowledgeContextById, getKnowledgeContextByName, getMachineAiConfig, getMonitorDir, getMonitorLogStats, getMonitorPath, getPackageSkillPath, getPrerequisites, getProviderApiKey, getRepoPaths, getReviewsForCard, getReviewsForUser, getSessionSummary, getSessionSynthesisRecords, getSetting, getShortSlug, getSystemProfile, getTokenById, getTokenBySlug, getTokenDeleteImpact, getTokenEmbedding, getTokenNeighborhood, getTursoCredentials, getUiObservationPath, getUiObserverDir, getUserStats, hasCommand, importCurriculumCards, importSnapshot, injectShellHooks, installFastFlowLM, installOllama, installOpenCode, interleave, isObserverPolicyConfigured, isOllamaInstalled, isUiObservationReport, listAgentSkills, listContextsForToken, listEmbeddedTokens, listGoals, listKnowledgeContexts, 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, setActiveWorkspaceContext, setActiveWorkspaceId, setInstallChannel, setInstallMode, setProviderApiKey, setSetting, setTursoCredentials, slugify, startSession, suggestFoundations, syncObserverSidecarPolicy, t, toSidecarPrivacyPolicy, uiObservationLogExists, uiObservationTimeSpan, unassignTokenFromContext, unblockReady, updateCard, updateGoalStatus, updateKnowledgeContext, updateToken, upsertConfiguredWorkspace, upsertTokenEmbedding, verifySnapshot, wouldCreateCycle, writeMonitorEvent };
2349
+ 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 CreateKnowledgeContextInput, 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 KnowledgeContext, type ListTokensOptions, 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, type QuestionSource, 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 UpdateKnowledgeContextInput, 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, assignTokenToContext, buildAncestorMap, buildReviewQueue, buildUiSynthesisCandidates, cascadeBlock, clearADOCredentials, clearProviderApiKey, clearReviewContextCache, clearTursoCredentials, compareVersions, computeContentHash, confirmCardSplit, confirmFoundations, confirmSourceImport, cosineSimilarity, createAgentSkill, createFSRS, createGoal, createKnowledgeContext, createToken, decidePostCapture, decidePreCapture, decideUpdate, decodeEmbedding, deleteCardForUser, deleteKnowledgeContext, 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, getActiveWorkspaceContext, getActiveWorkspaceId, getAgentSkill, getAllSettings, getAllSettingsDetailed, getBlockedCards, getCard, getCardById, getCardDeletionImpact, getConfiguredWorkspaces, getDatabaseTargetInfo, getDefaultDbPath, getDependents, getDisplayTitle, getDomainCompetence, getDueCards, getEmbeddingCoverage, getGoal, getGoalTree, getInstallChannel, getInstallMode, getKnowledgeContextById, getKnowledgeContextByName, getMachineAiConfig, getMonitorDir, getMonitorLogStats, getMonitorPath, getPackageSkillPath, getPrerequisites, getProviderApiKey, getRepoPaths, getReviewsForCard, getReviewsForUser, getSessionSummary, getSessionSynthesisRecords, getSetting, getShortSlug, getSystemProfile, getTokenById, getTokenBySlug, getTokenDeleteImpact, getTokenEmbedding, getTokenNeighborhood, getTursoCredentials, getUiObservationPath, getUiObserverDir, getUserStats, hasCommand, importCurriculumCards, importSnapshot, injectShellHooks, installFastFlowLM, installOllama, installOpenCode, interleave, isObserverPolicyConfigured, isOllamaInstalled, isUiObservationReport, listAgentSkills, listContextsForToken, listEmbeddedTokens, listGoals, listKnowledgeContexts, 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, setActiveWorkspaceContext, setActiveWorkspaceId, setInstallChannel, setInstallMode, setProviderApiKey, setSetting, setTursoCredentials, slugify, startSession, suggestFoundations, syncObserverSidecarPolicy, t, toSidecarPrivacyPolicy, uiObservationLogExists, uiObservationTimeSpan, unassignTokenFromContext, unblockReady, updateCard, updateGoalStatus, updateKnowledgeContext, updateToken, upsertConfiguredWorkspace, upsertTokenEmbedding, verifySnapshot, wouldCreateCycle, writeMonitorEvent };
package/dist/index.js CHANGED
@@ -542,7 +542,12 @@ CREATE TABLE IF NOT EXISTS tokens (
542
542
  deprecated_at TEXT,
543
543
  question TEXT,
544
544
  provider TEXT,
545
- topic_id TEXT
545
+ topic_id TEXT,
546
+ -- Who authored the current question ('manual' | 'llm' | 'template');
547
+ -- validated in code, not via CHECK. Column default is 'llm' so unlabeled
548
+ -- writes (pre-M013 rows, old snapshot restores) count as LLM-era content;
549
+ -- createToken() defaults to 'manual' for API callers instead.
550
+ question_source TEXT NOT NULL DEFAULT 'llm'
546
551
  );
547
552
 
548
553
  -- Prerequisite dependency graph: "to learn A, first know B"
@@ -1058,6 +1063,11 @@ async function runMigrations(db) {
1058
1063
  await db.exec(`
1059
1064
  CREATE INDEX IF NOT EXISTS idx_token_contexts_context ON token_contexts(context_id)
1060
1065
  `);
1066
+ if (tokenCols.length > 0 && !tokenCols.some((c) => c.name === "question_source")) {
1067
+ await db.exec(
1068
+ `ALTER TABLE tokens ADD COLUMN question_source TEXT NOT NULL DEFAULT 'llm'`
1069
+ );
1070
+ }
1061
1071
  }
1062
1072
 
1063
1073
  // src/kernel/db/snapshot.ts
@@ -1074,7 +1084,11 @@ var SNAPSHOT_TABLES = [
1074
1084
  "review_logs",
1075
1085
  "session_syntheses",
1076
1086
  "user_config",
1077
- "agent_skills"
1087
+ "agent_skills",
1088
+ "sources",
1089
+ "token_sources",
1090
+ "contexts",
1091
+ "token_contexts"
1078
1092
  ];
1079
1093
  function quoteValue(value) {
1080
1094
  if (value === null || value === void 0) return "NULL";
@@ -1660,6 +1674,14 @@ async function listContextsForToken(db, tokenId) {
1660
1674
 
1661
1675
  // src/kernel/models/token.ts
1662
1676
  import { ulid as ulid4 } from "ulid";
1677
+ var VALID_QUESTION_SOURCES = ["manual", "llm", "template"];
1678
+ function validateQuestionSource(value) {
1679
+ if (!VALID_QUESTION_SOURCES.includes(value)) {
1680
+ throw new Error(
1681
+ `Invalid question_source: ${value} (expected one of ${VALID_QUESTION_SOURCES.join(", ")})`
1682
+ );
1683
+ }
1684
+ }
1663
1685
  async function createToken(db, input) {
1664
1686
  const id = ulid4();
1665
1687
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1668,9 +1690,11 @@ async function createToken(db, input) {
1668
1690
  throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
1669
1691
  }
1670
1692
  const title = input.title ?? "";
1693
+ const questionSource = input.question_source ?? "manual";
1694
+ validateQuestionSource(questionSource);
1671
1695
  await db.prepare(`
1672
- INSERT INTO tokens (id, slug, title, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, provider, topic_id, created_at, updated_at)
1673
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1696
+ INSERT INTO tokens (id, slug, title, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, question_source, provider, topic_id, created_at, updated_at)
1697
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1674
1698
  `).run(
1675
1699
  id,
1676
1700
  input.slug,
@@ -1682,6 +1706,7 @@ async function createToken(db, input) {
1682
1706
  input.symbiosis_mode ?? null,
1683
1707
  input.source_link ?? null,
1684
1708
  input.question ?? null,
1709
+ questionSource,
1685
1710
  input.provider ?? null,
1686
1711
  input.topic_id ?? null,
1687
1712
  now,
@@ -1705,6 +1730,18 @@ async function getTokenBySlug(db, slug) {
1705
1730
  parseTokenFallback(token);
1706
1731
  return token;
1707
1732
  }
1733
+ async function getTokensBySlugs(db, slugs) {
1734
+ const tokens = /* @__PURE__ */ new Map();
1735
+ const unique = [...new Set(slugs)];
1736
+ if (unique.length === 0) return tokens;
1737
+ const placeholders = unique.map(() => "?").join(",");
1738
+ const rows = await db.prepare(`SELECT * FROM tokens WHERE slug IN (${placeholders})`).all(...unique);
1739
+ for (const token of rows) {
1740
+ parseTokenFallback(token);
1741
+ tokens.set(token.slug, token);
1742
+ }
1743
+ return tokens;
1744
+ }
1708
1745
  async function getTokenById(db, id) {
1709
1746
  const token = await db.prepare("SELECT * FROM tokens WHERE id = ?").get(id);
1710
1747
  parseTokenFallback(token);
@@ -1758,6 +1795,12 @@ async function updateToken(db, slug, updates) {
1758
1795
  fields.push("question = ?");
1759
1796
  values.push(updates.question);
1760
1797
  }
1798
+ const questionSource = updates.question_source ?? (updates.question !== void 0 ? "manual" : void 0);
1799
+ if (questionSource !== void 0) {
1800
+ validateQuestionSource(questionSource);
1801
+ fields.push("question_source = ?");
1802
+ values.push(questionSource);
1803
+ }
1761
1804
  if (updates.provider !== void 0) {
1762
1805
  fields.push("provider = ?");
1763
1806
  values.push(updates.provider);
@@ -2086,6 +2129,9 @@ async function importCurriculumCards(db, userId, cards) {
2086
2129
  symbiosis_mode: symbiosisMode,
2087
2130
  source_link: card.source_link || null,
2088
2131
  question: card.question || null,
2132
+ // Curriculum cards are LLM-extracted; their questions are LLM
2133
+ // inventions, not human-authored content.
2134
+ question_source: "llm",
2089
2135
  provider: card.provider || null,
2090
2136
  topic_id: card.topic_id || null
2091
2137
  });
@@ -3769,25 +3815,29 @@ async function cascadeBlock(db, userId, tokenSlug) {
3769
3815
  };
3770
3816
  }
3771
3817
  async function unblockReady(db, userId) {
3772
- const blockedCards = await db.prepare(
3773
- `SELECT c.token_id, t.slug, t.concept
3774
- FROM cards c
3775
- JOIN tokens t ON t.id = c.token_id
3776
- WHERE c.user_id = ? AND c.blocked = 1`
3777
- ).all(userId);
3778
3818
  const unblocked = [];
3779
- for (const card of blockedCards) {
3780
- const totalPrereqs = await db.prepare("SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?").get(card.token_id);
3781
- const metPrereqs = await db.prepare(
3782
- `SELECT COUNT(*) as n FROM cards c
3783
- JOIN prerequisites p ON p.requires_id = c.token_id
3784
- WHERE p.token_id = ? AND c.user_id = ? AND c.reps >= 1 AND c.blocked = 0`
3785
- ).get(card.token_id, userId);
3786
- if (totalPrereqs.n === 0 || metPrereqs.n === totalPrereqs.n) {
3787
- const now = (/* @__PURE__ */ new Date()).toISOString();
3788
- await db.prepare(
3789
- "UPDATE cards SET blocked = 0, due_at = ? WHERE token_id = ? AND user_id = ?"
3790
- ).run(now, card.token_id, userId);
3819
+ for (; ; ) {
3820
+ const readyCards = await db.prepare(
3821
+ `SELECT c.token_id, t.slug, t.concept
3822
+ FROM cards c
3823
+ JOIN tokens t ON t.id = c.token_id
3824
+ WHERE c.user_id = ? AND c.blocked = 1
3825
+ AND (SELECT COUNT(*) FROM prerequisites p
3826
+ WHERE p.token_id = c.token_id) =
3827
+ (SELECT COUNT(*) FROM prerequisites p
3828
+ JOIN cards pc ON pc.token_id = p.requires_id
3829
+ AND pc.user_id = c.user_id
3830
+ WHERE p.token_id = c.token_id
3831
+ AND pc.reps >= 1 AND pc.blocked = 0)`
3832
+ ).all(userId);
3833
+ if (readyCards.length === 0) break;
3834
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3835
+ const placeholders = readyCards.map(() => "?").join(",");
3836
+ await db.prepare(
3837
+ `UPDATE cards SET blocked = 0, due_at = ?
3838
+ WHERE user_id = ? AND token_id IN (${placeholders})`
3839
+ ).run(now, userId, ...readyCards.map((card) => card.token_id));
3840
+ for (const card of readyCards) {
3791
3841
  unblocked.push({ slug: card.slug, concept: card.concept });
3792
3842
  }
3793
3843
  }
@@ -3949,10 +3999,14 @@ async function prepareSessionSynthesis(db, input) {
3949
3999
  await buildSkillPatterns(db),
3950
4000
  input.explicitPatterns ?? []
3951
4001
  );
4002
+ const patternTokens = await getTokensBySlugs(
4003
+ db,
4004
+ patterns.map((pattern) => pattern.slug)
4005
+ );
3952
4006
  const validPatterns = [];
3953
4007
  const tokens = /* @__PURE__ */ new Map();
3954
4008
  for (const pattern of patterns) {
3955
- const token = await getTokenBySlug(db, pattern.slug);
4009
+ const token = patternTokens.get(pattern.slug);
3956
4010
  if (!token || token.deprecated_at) continue;
3957
4011
  validPatterns.push(pattern);
3958
4012
  tokens.set(pattern.slug, token);
@@ -3965,13 +4019,13 @@ async function prepareSessionSynthesis(db, input) {
3965
4019
  const minConfidence = input.minConfidence ?? "medium";
3966
4020
  if (session.execution_context === "ui") {
3967
4021
  const reports = readUiObservationLog(input.sessionId);
3968
- for (const report of reports) {
3969
- for (const candidate of report.candidateTokens) {
3970
- if (tokens.has(candidate.slug)) continue;
3971
- const token = await getTokenBySlug(db, candidate.slug);
3972
- if (token && !token.deprecated_at) {
3973
- tokens.set(candidate.slug, token);
3974
- }
4022
+ const candidateTokens = await getTokensBySlugs(
4023
+ db,
4024
+ reports.flatMap((report) => report.candidateTokens).map((candidate) => candidate.slug).filter((slug) => !tokens.has(slug))
4025
+ );
4026
+ for (const [slug, token] of candidateTokens) {
4027
+ if (!token.deprecated_at) {
4028
+ tokens.set(slug, token);
3975
4029
  }
3976
4030
  }
3977
4031
  const { candidates: candidates2, skippedLowConfidence: skippedLowConfidence2 } = buildUiSynthesisCandidates(
@@ -4881,7 +4935,8 @@ async function buildReviewQueue(db, options) {
4881
4935
  c.state AS state,
4882
4936
  c.due_at AS due_at,
4883
4937
  t.source_link AS source_link,
4884
- t.question AS question
4938
+ t.question AS question,
4939
+ t.question_source AS question_source
4885
4940
  FROM cards c
4886
4941
  JOIN tokens t ON t.id = c.token_id
4887
4942
  WHERE c.user_id = ?
@@ -4911,7 +4966,8 @@ async function buildReviewQueue(db, options) {
4911
4966
  c.state AS state,
4912
4967
  c.due_at AS due_at,
4913
4968
  t.source_link AS source_link,
4914
- t.question AS question
4969
+ t.question AS question,
4970
+ t.question_source AS question_source
4915
4971
  FROM cards c
4916
4972
  JOIN tokens t ON t.id = c.token_id
4917
4973
  WHERE c.user_id = ?
@@ -4980,7 +5036,8 @@ function rowToItem(row) {
4980
5036
  state: row.state,
4981
5037
  dueAt: row.due_at,
4982
5038
  sourceLink: row.source_link,
4983
- question: row.question
5039
+ question: row.question,
5040
+ questionSource: row.question_source
4984
5041
  };
4985
5042
  }
4986
5043
  function intersperseNew(reviews, newCards, interval) {