zam-core 0.5.3 → 0.6.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
@@ -900,6 +900,102 @@ declare function findTokens(db: Database, query: string): Promise<ScoredToken[]>
900
900
  * Results are ordered by bloom_level then slug.
901
901
  */
902
902
  declare function listTokens(db: Database, options?: ListTokensOptions): Promise<Token[]>;
903
+ interface PersonalCard {
904
+ tokenId: string;
905
+ slug: string;
906
+ concept: string;
907
+ domain: string;
908
+ bloomLevel: BloomLevel$1;
909
+ context: string;
910
+ symbiosisMode: SymbiosisMode | null;
911
+ sourceLink: string | null;
912
+ question: string | null;
913
+ createdAt: string;
914
+ updatedAt: string;
915
+ cardId: string | null;
916
+ state: CardState$1 | null;
917
+ dueAt: string | null;
918
+ stability: number | null;
919
+ difficulty: number | null;
920
+ reps: number | null;
921
+ lapses: number | null;
922
+ elapsedDays: number | null;
923
+ scheduledDays: number | null;
924
+ blocked: number | null;
925
+ }
926
+ declare function slugify(text: string): string;
927
+ declare function generateTokenSlug(db: Database, domain: string, concept: string, question?: string | null): Promise<string>;
928
+ declare function listPersonalCards(db: Database, userId: string, options?: {
929
+ query?: string;
930
+ domain?: string;
931
+ }): Promise<PersonalCard[]>;
932
+ interface CurriculumCardInput {
933
+ question: string;
934
+ concept: string;
935
+ domain: string;
936
+ source_link?: string | null;
937
+ context?: string;
938
+ bloom_level?: number;
939
+ symbiosis_mode?: string | null;
940
+ }
941
+ interface ImportCurriculumResult {
942
+ createdCount: number;
943
+ ensuredCount: number;
944
+ }
945
+ /**
946
+ * Import curriculum cards in a single transaction.
947
+ * Reuses existing tokens on slug match and ensures FSRS cards exist.
948
+ */
949
+ declare function importCurriculumCards(db: Database, userId: string, cards: CurriculumCardInput[]): Promise<ImportCurriculumResult>;
950
+ interface SplitProposalInput {
951
+ question: string;
952
+ concept: string;
953
+ domain: string;
954
+ context?: string;
955
+ bloom_level?: number;
956
+ symbiosis_mode?: string | null;
957
+ source_link?: string | null;
958
+ }
959
+ /**
960
+ * Confirm a card split transaction.
961
+ * Creates proposal cards, links them as prerequisites to the original card,
962
+ * and either blocks the original card (surfacing proposals) or deletes it.
963
+ */
964
+ declare function confirmCardSplit(db: Database, userId: string, originalSlug: string, action: "block" | "remove", originalQuestion: string, originalConcept: string, proposals: SplitProposalInput[]): Promise<ImportCurriculumResult>;
965
+ interface FoundationProposalInput {
966
+ question: string;
967
+ concept: string;
968
+ domain: string;
969
+ context?: string;
970
+ bloom_level?: number;
971
+ symbiosis_mode?: string | null;
972
+ source_link?: string | null;
973
+ exists: boolean;
974
+ slug?: string | null;
975
+ }
976
+ interface ConfirmFoundationsResult {
977
+ createdCount: number;
978
+ linkedCount: number;
979
+ }
980
+ /**
981
+ * Confirm foundations import.
982
+ * Reuses existing tokens or creates new ones, then links them as prerequisites to the original card.
983
+ */
984
+ declare function confirmFoundations(db: Database, userId: string, originalSlug: string, proposals: FoundationProposalInput[]): Promise<ConfirmFoundationsResult>;
985
+ interface SourceProposalInput {
986
+ question: string;
987
+ concept: string;
988
+ domain: string;
989
+ bloom_level: number;
990
+ symbiosis_mode: string;
991
+ excerpt: string;
992
+ page_number?: string | null;
993
+ }
994
+ /**
995
+ * Confirm source import transaction.
996
+ * Saves tokens, maps them to the source in token_sources, and ensures cards exist for the user.
997
+ */
998
+ declare function confirmSourceImport(db: Database, userId: string, sourceId: string, proposals: SourceProposalInput[]): Promise<ConfirmFoundationsResult>;
903
999
 
904
1000
  /**
905
1001
  * Monitor log analyzer — maps observed shell commands to token ratings.
@@ -1957,4 +2053,4 @@ declare function resolveAllBeliefPaths(db: Database): Promise<string[]>;
1957
2053
  */
1958
2054
  declare function resolveAllGoalPaths(db: Database): Promise<string[]>;
1959
2055
 
1960
- 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 ConnectionOptions, type CreateAgentSkillInput, type CreateGoalInput, type CreateReviewInput, type CreateSessionInput, type CreateTokenInput, type Credentials, 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 Goal, type GoalFrontmatter, type GoalStatus, type GoalSummary, HOMEBREW_CASK, 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 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 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, 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, 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, importSnapshot, injectShellHooks, installFastFlowLM, installOllama, installOpenCode, interleave, isObserverPolicyConfigured, isUiObservationReport, listAgentSkills, listGoals, 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, resolveReference, resolveRepoPath, resolveReviewContext, saveConfiguredWorkspaces, saveCredentials, saveInstallConfig, saveMachineAiConfig, serializeGoal, setADOCredentials, setActiveWorkspaceId, setInstallChannel, setInstallMode, setProviderApiKey, setSetting, setTursoCredentials, startSession, syncObserverSidecarPolicy, t, toSidecarPrivacyPolicy, uiObservationLogExists, uiObservationTimeSpan, unblockReady, updateCard, updateGoalStatus, updateToken, upsertConfiguredWorkspace, verifySnapshot, wouldCreateCycle, writeMonitorEvent };
2056
+ 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 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, 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, 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 };
package/dist/index.js CHANGED
@@ -616,6 +616,24 @@ CREATE TABLE IF NOT EXISTS session_syntheses (
616
616
  PRIMARY KEY (session_id, token_id)
617
617
  );
618
618
 
619
+ -- Sources: textbook files, web links, or scan paths
620
+ CREATE TABLE IF NOT EXISTS sources (
621
+ id TEXT PRIMARY KEY,
622
+ type TEXT NOT NULL CHECK (type IN ('file', 'web', 'scan')),
623
+ uri TEXT NOT NULL UNIQUE,
624
+ content TEXT,
625
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
626
+ );
627
+
628
+ -- Token sources: mapping between tokens and their sources
629
+ CREATE TABLE IF NOT EXISTS token_sources (
630
+ token_id TEXT NOT NULL REFERENCES tokens(id) ON DELETE CASCADE,
631
+ source_id TEXT NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
632
+ excerpt TEXT NOT NULL DEFAULT '',
633
+ page_number TEXT,
634
+ PRIMARY KEY (token_id, source_id)
635
+ );
636
+
619
637
  -- User configuration
620
638
  CREATE TABLE IF NOT EXISTS user_config (
621
639
  key TEXT PRIMARY KEY,
@@ -943,6 +961,24 @@ async function runMigrations(db) {
943
961
  PRIMARY KEY (session_id, token_id)
944
962
  )
945
963
  `);
964
+ await db.exec(`
965
+ CREATE TABLE IF NOT EXISTS sources (
966
+ id TEXT PRIMARY KEY,
967
+ type TEXT NOT NULL CHECK (type IN ('file', 'web', 'scan')),
968
+ uri TEXT NOT NULL UNIQUE,
969
+ content TEXT,
970
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
971
+ )
972
+ `);
973
+ await db.exec(`
974
+ CREATE TABLE IF NOT EXISTS token_sources (
975
+ token_id TEXT NOT NULL REFERENCES tokens(id) ON DELETE CASCADE,
976
+ source_id TEXT NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
977
+ excerpt TEXT NOT NULL DEFAULT '',
978
+ page_number TEXT,
979
+ PRIMARY KEY (token_id, source_id)
980
+ )
981
+ `);
946
982
  }
947
983
 
948
984
  // src/kernel/db/snapshot.ts
@@ -1646,6 +1682,415 @@ async function listTokens(db, options) {
1646
1682
  "SELECT * FROM tokens WHERE deprecated_at IS NULL ORDER BY bloom_level, domain, slug"
1647
1683
  ).all();
1648
1684
  }
1685
+ function slugify(text) {
1686
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
1687
+ }
1688
+ async function generateTokenSlug(db, domain, concept, question) {
1689
+ const baseText = question && question.trim().length > 0 ? question : concept;
1690
+ const cleanDomain = slugify(domain || "");
1691
+ const cleanBase = slugify(baseText);
1692
+ let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
1693
+ if (baseSlug.length > 60) {
1694
+ baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
1695
+ }
1696
+ if (!baseSlug) {
1697
+ baseSlug = "token";
1698
+ }
1699
+ let slug = baseSlug;
1700
+ let counter = 1;
1701
+ while (true) {
1702
+ const existing = await db.prepare("SELECT id FROM tokens WHERE slug = ?").get(slug);
1703
+ if (!existing) {
1704
+ return slug;
1705
+ }
1706
+ const suffix = `-${counter}`;
1707
+ slug = baseSlug.slice(0, 60 - suffix.length).replace(/-$/, "") + suffix;
1708
+ counter++;
1709
+ }
1710
+ }
1711
+ async function listPersonalCards(db, userId, options) {
1712
+ let sql = `
1713
+ SELECT
1714
+ t.id AS tokenId,
1715
+ t.slug,
1716
+ t.concept,
1717
+ t.domain,
1718
+ t.bloom_level AS bloomLevel,
1719
+ t.context,
1720
+ t.symbiosis_mode AS symbiosisMode,
1721
+ t.source_link AS sourceLink,
1722
+ t.question,
1723
+ t.created_at AS createdAt,
1724
+ t.updated_at AS updatedAt,
1725
+ c.id AS cardId,
1726
+ c.state,
1727
+ c.due_at AS dueAt,
1728
+ c.stability,
1729
+ c.difficulty,
1730
+ c.reps,
1731
+ c.lapses,
1732
+ c.elapsed_days AS elapsedDays,
1733
+ c.scheduled_days AS scheduledDays,
1734
+ c.blocked
1735
+ FROM tokens t
1736
+ INNER JOIN cards c ON c.token_id = t.id AND c.user_id = ?
1737
+ WHERE t.deprecated_at IS NULL
1738
+ `;
1739
+ const values = [userId];
1740
+ if (options?.domain) {
1741
+ sql += " AND t.domain = ?";
1742
+ values.push(options.domain);
1743
+ }
1744
+ if (options?.query) {
1745
+ const terms = options.query.toLowerCase().split(/\s+/).filter(Boolean);
1746
+ for (const term of terms) {
1747
+ sql += ` AND (lower(t.slug) LIKE ? OR lower(t.concept) LIKE ? OR lower(t.domain) LIKE ? OR lower(t.question) LIKE ?)`;
1748
+ const pattern = `%${term}%`;
1749
+ values.push(pattern, pattern, pattern, pattern);
1750
+ }
1751
+ }
1752
+ sql += " ORDER BY t.created_at DESC";
1753
+ const rows = await db.prepare(sql).all(...values);
1754
+ return rows;
1755
+ }
1756
+ async function importCurriculumCards(db, userId, cards) {
1757
+ let createdCount = 0;
1758
+ let ensuredCount = 0;
1759
+ await db.transaction(async (tx) => {
1760
+ for (const card of cards) {
1761
+ const bloom = card.bloom_level !== void 0 ? card.bloom_level : 1;
1762
+ if (bloom < 1 || bloom > 5) {
1763
+ throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
1764
+ }
1765
+ let symbiosisMode = null;
1766
+ if (card.symbiosis_mode) {
1767
+ if (!["shadowing", "copilot", "autonomy", "none"].includes(
1768
+ card.symbiosis_mode
1769
+ )) {
1770
+ throw new Error(`Invalid symbiosis_mode: ${card.symbiosis_mode}`);
1771
+ }
1772
+ symbiosisMode = card.symbiosis_mode === "none" ? null : card.symbiosis_mode;
1773
+ }
1774
+ const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
1775
+ const cleanDomain = slugify(card.domain || "");
1776
+ const cleanBase = slugify(baseText);
1777
+ let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
1778
+ if (baseSlug.length > 60) {
1779
+ baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
1780
+ }
1781
+ if (!baseSlug) {
1782
+ baseSlug = "token";
1783
+ }
1784
+ let token = await getTokenBySlug(tx, baseSlug);
1785
+ if (!token) {
1786
+ const finalSlug = await generateTokenSlug(
1787
+ tx,
1788
+ card.domain,
1789
+ card.concept,
1790
+ card.question
1791
+ );
1792
+ token = await createToken(tx, {
1793
+ slug: finalSlug,
1794
+ concept: card.concept,
1795
+ domain: card.domain,
1796
+ bloom_level: bloom,
1797
+ context: card.context || "",
1798
+ symbiosis_mode: symbiosisMode,
1799
+ source_link: card.source_link || null,
1800
+ question: card.question || null
1801
+ });
1802
+ createdCount++;
1803
+ }
1804
+ const existingCard = await getCard(tx, token.id, userId);
1805
+ if (!existingCard) {
1806
+ await ensureCard(tx, token.id, userId);
1807
+ ensuredCount++;
1808
+ }
1809
+ }
1810
+ });
1811
+ return { createdCount, ensuredCount };
1812
+ }
1813
+ async function confirmCardSplit(db, userId, originalSlug, action, originalQuestion, originalConcept, proposals) {
1814
+ if (action !== "block" && action !== "remove") {
1815
+ throw new Error(`Invalid split action: ${action}`);
1816
+ }
1817
+ if (proposals.length < 2 || proposals.length > 4) {
1818
+ throw new Error("A card split requires between 2 and 4 proposals");
1819
+ }
1820
+ const originalToken = await getTokenBySlug(db, originalSlug);
1821
+ if (!originalToken) {
1822
+ throw new Error(`Original token not found: ${originalSlug}`);
1823
+ }
1824
+ let createdCount = 0;
1825
+ let ensuredCount = 0;
1826
+ await db.transaction(async (tx) => {
1827
+ const originalCard = await getCard(tx, originalToken.id, userId);
1828
+ if (!originalCard) {
1829
+ throw new Error(
1830
+ `Card not found for token ${originalSlug} and user ${userId}`
1831
+ );
1832
+ }
1833
+ const proposalTokens = [];
1834
+ for (const card of proposals) {
1835
+ const bloom = card.bloom_level !== void 0 ? card.bloom_level : 1;
1836
+ if (bloom < 1 || bloom > 5) {
1837
+ throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
1838
+ }
1839
+ let symbiosisMode = null;
1840
+ if (card.symbiosis_mode) {
1841
+ if (!["shadowing", "copilot", "autonomy", "none"].includes(
1842
+ card.symbiosis_mode
1843
+ )) {
1844
+ throw new Error(`Invalid symbiosis_mode: ${card.symbiosis_mode}`);
1845
+ }
1846
+ symbiosisMode = card.symbiosis_mode === "none" ? null : card.symbiosis_mode;
1847
+ }
1848
+ const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
1849
+ const cleanDomain = slugify(card.domain || "");
1850
+ const cleanBase = slugify(baseText);
1851
+ let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
1852
+ if (baseSlug.length > 60) {
1853
+ baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
1854
+ }
1855
+ if (!baseSlug) {
1856
+ baseSlug = "token";
1857
+ }
1858
+ let token = await getTokenBySlug(tx, baseSlug);
1859
+ if (!token) {
1860
+ const finalSlug = await generateTokenSlug(
1861
+ tx,
1862
+ card.domain,
1863
+ card.concept,
1864
+ card.question
1865
+ );
1866
+ token = await createToken(tx, {
1867
+ slug: finalSlug,
1868
+ concept: card.concept,
1869
+ domain: card.domain,
1870
+ bloom_level: bloom,
1871
+ context: card.context || "",
1872
+ symbiosis_mode: symbiosisMode,
1873
+ source_link: card.source_link || originalToken.source_link || null,
1874
+ question: card.question || null
1875
+ });
1876
+ createdCount++;
1877
+ }
1878
+ if (token.id === originalToken.id) {
1879
+ throw new Error("A token cannot be a prerequisite of itself");
1880
+ }
1881
+ await assertPrerequisiteDoesNotCreateCycle(
1882
+ tx,
1883
+ originalToken.id,
1884
+ token.id
1885
+ );
1886
+ proposalTokens.push(token);
1887
+ const existingCard = await getCard(tx, token.id, userId);
1888
+ if (!existingCard) {
1889
+ await ensureCard(tx, token.id, userId);
1890
+ ensuredCount++;
1891
+ }
1892
+ }
1893
+ if (action === "block") {
1894
+ await tx.prepare(
1895
+ "UPDATE tokens SET question = ?, concept = ?, updated_at = ? WHERE id = ?"
1896
+ ).run(
1897
+ originalQuestion || null,
1898
+ originalConcept,
1899
+ (/* @__PURE__ */ new Date()).toISOString(),
1900
+ originalToken.id
1901
+ );
1902
+ for (const propToken of proposalTokens) {
1903
+ await tx.prepare(
1904
+ "INSERT OR IGNORE INTO prerequisites (token_id, requires_id) VALUES (?, ?)"
1905
+ ).run(originalToken.id, propToken.id);
1906
+ }
1907
+ await tx.prepare(
1908
+ "UPDATE cards SET blocked = 1 WHERE token_id = ? AND user_id = ?"
1909
+ ).run(originalToken.id, userId);
1910
+ for (const propToken of proposalTokens) {
1911
+ const card = await ensureCard(tx, propToken.id, userId);
1912
+ if (card.blocked === 1) {
1913
+ const prereqOfPrereq = await tx.prepare(
1914
+ "SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?"
1915
+ ).get(propToken.id);
1916
+ if (prereqOfPrereq.n === 0) {
1917
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1918
+ await tx.prepare("UPDATE cards SET blocked = 0, due_at = ? WHERE id = ?").run(now, card.id);
1919
+ }
1920
+ }
1921
+ }
1922
+ } else if (action === "remove") {
1923
+ await deleteCardForUser(tx, originalToken.id, userId);
1924
+ }
1925
+ });
1926
+ return { createdCount, ensuredCount };
1927
+ }
1928
+ async function confirmFoundations(db, userId, originalSlug, proposals) {
1929
+ const originalToken = await getTokenBySlug(db, originalSlug);
1930
+ if (!originalToken) {
1931
+ throw new Error(`Original token not found: ${originalSlug}`);
1932
+ }
1933
+ let createdCount = 0;
1934
+ let linkedCount = 0;
1935
+ await db.transaction(async (tx) => {
1936
+ const originalCard = await getCard(tx, originalToken.id, userId);
1937
+ if (!originalCard) {
1938
+ throw new Error(
1939
+ `Card not found for token ${originalSlug} and user ${userId}`
1940
+ );
1941
+ }
1942
+ for (const card of proposals) {
1943
+ let targetTokenId;
1944
+ if (card.exists && card.slug) {
1945
+ const existingToken = await getTokenBySlug(tx, card.slug);
1946
+ if (!existingToken) {
1947
+ throw new Error(`Prerequisite token not found by slug: ${card.slug}`);
1948
+ }
1949
+ targetTokenId = existingToken.id;
1950
+ const existingCard = await getCard(tx, existingToken.id, userId);
1951
+ if (!existingCard) {
1952
+ await ensureCard(tx, existingToken.id, userId);
1953
+ }
1954
+ linkedCount++;
1955
+ } else {
1956
+ const bloom = card.bloom_level !== void 0 ? card.bloom_level : 1;
1957
+ if (bloom < 1 || bloom > 5) {
1958
+ throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
1959
+ }
1960
+ let symbiosisMode = null;
1961
+ if (card.symbiosis_mode) {
1962
+ if (!["shadowing", "copilot", "autonomy", "none"].includes(
1963
+ card.symbiosis_mode
1964
+ )) {
1965
+ throw new Error(`Invalid symbiosis_mode: ${card.symbiosis_mode}`);
1966
+ }
1967
+ symbiosisMode = card.symbiosis_mode === "none" ? null : card.symbiosis_mode;
1968
+ }
1969
+ const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
1970
+ const cleanDomain = slugify(card.domain || "");
1971
+ const cleanBase = slugify(baseText);
1972
+ let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
1973
+ if (baseSlug.length > 60) {
1974
+ baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
1975
+ }
1976
+ if (!baseSlug) {
1977
+ baseSlug = "token";
1978
+ }
1979
+ let token = await getTokenBySlug(tx, baseSlug);
1980
+ if (!token) {
1981
+ const finalSlug = await generateTokenSlug(
1982
+ tx,
1983
+ card.domain,
1984
+ card.concept,
1985
+ card.question
1986
+ );
1987
+ token = await createToken(tx, {
1988
+ slug: finalSlug,
1989
+ concept: card.concept,
1990
+ domain: card.domain,
1991
+ bloom_level: bloom,
1992
+ context: card.context || "",
1993
+ symbiosis_mode: symbiosisMode,
1994
+ source_link: card.source_link || originalToken.source_link || null,
1995
+ question: card.question || null
1996
+ });
1997
+ createdCount++;
1998
+ }
1999
+ targetTokenId = token.id;
2000
+ const existingCard = await getCard(tx, token.id, userId);
2001
+ if (!existingCard) {
2002
+ await ensureCard(tx, token.id, userId);
2003
+ }
2004
+ }
2005
+ if (targetTokenId === originalToken.id) {
2006
+ throw new Error("A token cannot be a prerequisite of itself");
2007
+ }
2008
+ await assertPrerequisiteDoesNotCreateCycle(
2009
+ tx,
2010
+ originalToken.id,
2011
+ targetTokenId
2012
+ );
2013
+ await tx.prepare(
2014
+ "INSERT OR IGNORE INTO prerequisites (token_id, requires_id) VALUES (?, ?)"
2015
+ ).run(originalToken.id, targetTokenId);
2016
+ }
2017
+ });
2018
+ return { createdCount, linkedCount };
2019
+ }
2020
+ async function confirmSourceImport(db, userId, sourceId, proposals) {
2021
+ let createdCount = 0;
2022
+ let linkedCount = 0;
2023
+ await db.transaction(async (tx) => {
2024
+ const source = await tx.prepare("SELECT id FROM sources WHERE id = ?").get(sourceId);
2025
+ if (!source) {
2026
+ throw new Error(`Source not found: ${sourceId}`);
2027
+ }
2028
+ for (const card of proposals) {
2029
+ const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
2030
+ const cleanDomain = slugify(card.domain || "");
2031
+ const cleanBase = slugify(baseText);
2032
+ let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
2033
+ if (baseSlug.length > 60) {
2034
+ baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
2035
+ }
2036
+ if (!baseSlug) {
2037
+ baseSlug = "token";
2038
+ }
2039
+ let token = await getTokenBySlug(tx, baseSlug);
2040
+ if (!token) {
2041
+ const finalSlug = await generateTokenSlug(
2042
+ tx,
2043
+ card.domain,
2044
+ card.concept,
2045
+ card.question
2046
+ );
2047
+ const bloom = card.bloom_level !== void 0 ? card.bloom_level : 1;
2048
+ let symbiosisMode = null;
2049
+ if (card.symbiosis_mode && card.symbiosis_mode !== "none") {
2050
+ symbiosisMode = card.symbiosis_mode;
2051
+ }
2052
+ token = await createToken(tx, {
2053
+ slug: finalSlug,
2054
+ concept: card.concept,
2055
+ domain: card.domain,
2056
+ bloom_level: bloom,
2057
+ context: card.excerpt || "",
2058
+ symbiosis_mode: symbiosisMode,
2059
+ question: card.question || null
2060
+ });
2061
+ createdCount++;
2062
+ } else {
2063
+ linkedCount++;
2064
+ }
2065
+ const existingCard = await getCard(tx, token.id, userId);
2066
+ if (!existingCard) {
2067
+ await ensureCard(tx, token.id, userId);
2068
+ }
2069
+ await tx.prepare(
2070
+ `INSERT INTO token_sources (token_id, source_id, excerpt, page_number)
2071
+ VALUES (?, ?, ?, ?)
2072
+ ON CONFLICT(token_id, source_id) DO UPDATE SET
2073
+ excerpt = excluded.excerpt,
2074
+ page_number = excluded.page_number`
2075
+ ).run(token.id, sourceId, card.excerpt || "", card.page_number || null);
2076
+ }
2077
+ });
2078
+ return { createdCount, linkedCount };
2079
+ }
2080
+ async function assertPrerequisiteDoesNotCreateCycle(db, tokenId, prerequisiteId) {
2081
+ const cycleCheck = await db.prepare(
2082
+ `WITH RECURSIVE dependents(token_id) AS (
2083
+ SELECT token_id FROM prerequisites WHERE requires_id = ?
2084
+ UNION
2085
+ SELECT p.token_id FROM prerequisites p
2086
+ JOIN dependents d ON p.requires_id = d.token_id
2087
+ )
2088
+ SELECT COUNT(*) as n FROM dependents WHERE token_id = ?`
2089
+ ).get(tokenId, prerequisiteId);
2090
+ if (cycleCheck.n > 0) {
2091
+ throw new Error("Cannot add prerequisite: would create a cycle");
2092
+ }
2093
+ }
1649
2094
 
1650
2095
  // src/kernel/models/prerequisite.ts
1651
2096
  async function buildAncestorMap(db) {
@@ -5094,6 +5539,9 @@ export {
5094
5539
  clearReviewContextCache,
5095
5540
  clearTursoCredentials,
5096
5541
  compareVersions,
5542
+ confirmCardSplit,
5543
+ confirmFoundations,
5544
+ confirmSourceImport,
5097
5545
  createAgentSkill,
5098
5546
  createFSRS,
5099
5547
  createGoal,
@@ -5126,6 +5574,7 @@ export {
5126
5574
  generatePowerShellHooks,
5127
5575
  generatePowerShellUnhooks,
5128
5576
  generatePrompt,
5577
+ generateTokenSlug,
5129
5578
  generateZshHooks,
5130
5579
  generateZshUnhooks,
5131
5580
  getADOCredentials,
@@ -5171,6 +5620,7 @@ export {
5171
5620
  getUiObserverDir,
5172
5621
  getUserStats,
5173
5622
  hasCommand,
5623
+ importCurriculumCards,
5174
5624
  importSnapshot,
5175
5625
  injectShellHooks,
5176
5626
  installFastFlowLM,
@@ -5181,6 +5631,7 @@ export {
5181
5631
  isUiObservationReport,
5182
5632
  listAgentSkills,
5183
5633
  listGoals,
5634
+ listPersonalCards,
5184
5635
  listProviderApiKeyRefs,
5185
5636
  listTokens,
5186
5637
  loadADOConfig,
@@ -5229,6 +5680,7 @@ export {
5229
5680
  setProviderApiKey,
5230
5681
  setSetting,
5231
5682
  setTursoCredentials,
5683
+ slugify,
5232
5684
  startSession,
5233
5685
  syncObserverSidecarPolicy,
5234
5686
  t,