zam-core 0.5.3 → 0.6.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.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,425 @@ 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
+ COALESCE(
1722
+ t.source_link,
1723
+ (
1724
+ SELECT s.uri
1725
+ FROM token_sources ts
1726
+ INNER JOIN sources s ON s.id = ts.source_id
1727
+ WHERE ts.token_id = t.id
1728
+ ORDER BY s.created_at DESC, s.id DESC
1729
+ LIMIT 1
1730
+ )
1731
+ ) AS sourceLink,
1732
+ t.question,
1733
+ t.created_at AS createdAt,
1734
+ t.updated_at AS updatedAt,
1735
+ c.id AS cardId,
1736
+ c.state,
1737
+ c.due_at AS dueAt,
1738
+ c.stability,
1739
+ c.difficulty,
1740
+ c.reps,
1741
+ c.lapses,
1742
+ c.elapsed_days AS elapsedDays,
1743
+ c.scheduled_days AS scheduledDays,
1744
+ c.blocked
1745
+ FROM tokens t
1746
+ INNER JOIN cards c ON c.token_id = t.id AND c.user_id = ?
1747
+ WHERE t.deprecated_at IS NULL
1748
+ `;
1749
+ const values = [userId];
1750
+ if (options?.domain) {
1751
+ sql += " AND t.domain = ?";
1752
+ values.push(options.domain);
1753
+ }
1754
+ if (options?.query) {
1755
+ const terms = options.query.toLowerCase().split(/\s+/).filter(Boolean);
1756
+ for (const term of terms) {
1757
+ sql += ` AND (lower(t.slug) LIKE ? OR lower(t.concept) LIKE ? OR lower(t.domain) LIKE ? OR lower(t.question) LIKE ?)`;
1758
+ const pattern = `%${term}%`;
1759
+ values.push(pattern, pattern, pattern, pattern);
1760
+ }
1761
+ }
1762
+ sql += " ORDER BY t.created_at DESC";
1763
+ const rows = await db.prepare(sql).all(...values);
1764
+ return rows;
1765
+ }
1766
+ async function importCurriculumCards(db, userId, cards) {
1767
+ let createdCount = 0;
1768
+ let ensuredCount = 0;
1769
+ await db.transaction(async (tx) => {
1770
+ for (const card of cards) {
1771
+ const bloom = card.bloom_level !== void 0 ? card.bloom_level : 1;
1772
+ if (bloom < 1 || bloom > 5) {
1773
+ throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
1774
+ }
1775
+ let symbiosisMode = null;
1776
+ if (card.symbiosis_mode) {
1777
+ if (!["shadowing", "copilot", "autonomy", "none"].includes(
1778
+ card.symbiosis_mode
1779
+ )) {
1780
+ throw new Error(`Invalid symbiosis_mode: ${card.symbiosis_mode}`);
1781
+ }
1782
+ symbiosisMode = card.symbiosis_mode === "none" ? null : card.symbiosis_mode;
1783
+ }
1784
+ const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
1785
+ const cleanDomain = slugify(card.domain || "");
1786
+ const cleanBase = slugify(baseText);
1787
+ let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
1788
+ if (baseSlug.length > 60) {
1789
+ baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
1790
+ }
1791
+ if (!baseSlug) {
1792
+ baseSlug = "token";
1793
+ }
1794
+ let token = await getTokenBySlug(tx, baseSlug);
1795
+ if (!token) {
1796
+ const finalSlug = await generateTokenSlug(
1797
+ tx,
1798
+ card.domain,
1799
+ card.concept,
1800
+ card.question
1801
+ );
1802
+ token = await createToken(tx, {
1803
+ slug: finalSlug,
1804
+ concept: card.concept,
1805
+ domain: card.domain,
1806
+ bloom_level: bloom,
1807
+ context: card.context || "",
1808
+ symbiosis_mode: symbiosisMode,
1809
+ source_link: card.source_link || null,
1810
+ question: card.question || null
1811
+ });
1812
+ createdCount++;
1813
+ }
1814
+ const existingCard = await getCard(tx, token.id, userId);
1815
+ if (!existingCard) {
1816
+ await ensureCard(tx, token.id, userId);
1817
+ ensuredCount++;
1818
+ }
1819
+ }
1820
+ });
1821
+ return { createdCount, ensuredCount };
1822
+ }
1823
+ async function confirmCardSplit(db, userId, originalSlug, action, originalQuestion, originalConcept, proposals) {
1824
+ if (action !== "block" && action !== "remove") {
1825
+ throw new Error(`Invalid split action: ${action}`);
1826
+ }
1827
+ if (proposals.length < 2 || proposals.length > 4) {
1828
+ throw new Error("A card split requires between 2 and 4 proposals");
1829
+ }
1830
+ const originalToken = await getTokenBySlug(db, originalSlug);
1831
+ if (!originalToken) {
1832
+ throw new Error(`Original token not found: ${originalSlug}`);
1833
+ }
1834
+ let createdCount = 0;
1835
+ let ensuredCount = 0;
1836
+ await db.transaction(async (tx) => {
1837
+ const originalCard = await getCard(tx, originalToken.id, userId);
1838
+ if (!originalCard) {
1839
+ throw new Error(
1840
+ `Card not found for token ${originalSlug} and user ${userId}`
1841
+ );
1842
+ }
1843
+ const proposalTokens = [];
1844
+ for (const card of proposals) {
1845
+ const bloom = card.bloom_level !== void 0 ? card.bloom_level : 1;
1846
+ if (bloom < 1 || bloom > 5) {
1847
+ throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
1848
+ }
1849
+ let symbiosisMode = null;
1850
+ if (card.symbiosis_mode) {
1851
+ if (!["shadowing", "copilot", "autonomy", "none"].includes(
1852
+ card.symbiosis_mode
1853
+ )) {
1854
+ throw new Error(`Invalid symbiosis_mode: ${card.symbiosis_mode}`);
1855
+ }
1856
+ symbiosisMode = card.symbiosis_mode === "none" ? null : card.symbiosis_mode;
1857
+ }
1858
+ const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
1859
+ const cleanDomain = slugify(card.domain || "");
1860
+ const cleanBase = slugify(baseText);
1861
+ let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
1862
+ if (baseSlug.length > 60) {
1863
+ baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
1864
+ }
1865
+ if (!baseSlug) {
1866
+ baseSlug = "token";
1867
+ }
1868
+ let token = await getTokenBySlug(tx, baseSlug);
1869
+ if (!token) {
1870
+ const finalSlug = await generateTokenSlug(
1871
+ tx,
1872
+ card.domain,
1873
+ card.concept,
1874
+ card.question
1875
+ );
1876
+ token = await createToken(tx, {
1877
+ slug: finalSlug,
1878
+ concept: card.concept,
1879
+ domain: card.domain,
1880
+ bloom_level: bloom,
1881
+ context: card.context || "",
1882
+ symbiosis_mode: symbiosisMode,
1883
+ source_link: card.source_link || originalToken.source_link || null,
1884
+ question: card.question || null
1885
+ });
1886
+ createdCount++;
1887
+ }
1888
+ if (token.id === originalToken.id) {
1889
+ throw new Error("A token cannot be a prerequisite of itself");
1890
+ }
1891
+ await assertPrerequisiteDoesNotCreateCycle(
1892
+ tx,
1893
+ originalToken.id,
1894
+ token.id
1895
+ );
1896
+ proposalTokens.push(token);
1897
+ const existingCard = await getCard(tx, token.id, userId);
1898
+ if (!existingCard) {
1899
+ await ensureCard(tx, token.id, userId);
1900
+ ensuredCount++;
1901
+ }
1902
+ }
1903
+ if (action === "block") {
1904
+ await tx.prepare(
1905
+ "UPDATE tokens SET question = ?, concept = ?, updated_at = ? WHERE id = ?"
1906
+ ).run(
1907
+ originalQuestion || null,
1908
+ originalConcept,
1909
+ (/* @__PURE__ */ new Date()).toISOString(),
1910
+ originalToken.id
1911
+ );
1912
+ for (const propToken of proposalTokens) {
1913
+ await tx.prepare(
1914
+ "INSERT OR IGNORE INTO prerequisites (token_id, requires_id) VALUES (?, ?)"
1915
+ ).run(originalToken.id, propToken.id);
1916
+ }
1917
+ await tx.prepare(
1918
+ "UPDATE cards SET blocked = 1 WHERE token_id = ? AND user_id = ?"
1919
+ ).run(originalToken.id, userId);
1920
+ for (const propToken of proposalTokens) {
1921
+ const card = await ensureCard(tx, propToken.id, userId);
1922
+ if (card.blocked === 1) {
1923
+ const prereqOfPrereq = await tx.prepare(
1924
+ "SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?"
1925
+ ).get(propToken.id);
1926
+ if (prereqOfPrereq.n === 0) {
1927
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1928
+ await tx.prepare("UPDATE cards SET blocked = 0, due_at = ? WHERE id = ?").run(now, card.id);
1929
+ }
1930
+ }
1931
+ }
1932
+ } else if (action === "remove") {
1933
+ await deleteCardForUser(tx, originalToken.id, userId);
1934
+ }
1935
+ });
1936
+ return { createdCount, ensuredCount };
1937
+ }
1938
+ async function confirmFoundations(db, userId, originalSlug, proposals) {
1939
+ const originalToken = await getTokenBySlug(db, originalSlug);
1940
+ if (!originalToken) {
1941
+ throw new Error(`Original token not found: ${originalSlug}`);
1942
+ }
1943
+ let createdCount = 0;
1944
+ let linkedCount = 0;
1945
+ await db.transaction(async (tx) => {
1946
+ const originalCard = await getCard(tx, originalToken.id, userId);
1947
+ if (!originalCard) {
1948
+ throw new Error(
1949
+ `Card not found for token ${originalSlug} and user ${userId}`
1950
+ );
1951
+ }
1952
+ for (const card of proposals) {
1953
+ let targetTokenId;
1954
+ if (card.exists && card.slug) {
1955
+ const existingToken = await getTokenBySlug(tx, card.slug);
1956
+ if (!existingToken) {
1957
+ throw new Error(`Prerequisite token not found by slug: ${card.slug}`);
1958
+ }
1959
+ targetTokenId = existingToken.id;
1960
+ const existingCard = await getCard(tx, existingToken.id, userId);
1961
+ if (!existingCard) {
1962
+ await ensureCard(tx, existingToken.id, userId);
1963
+ }
1964
+ linkedCount++;
1965
+ } else {
1966
+ const bloom = card.bloom_level !== void 0 ? card.bloom_level : 1;
1967
+ if (bloom < 1 || bloom > 5) {
1968
+ throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
1969
+ }
1970
+ let symbiosisMode = null;
1971
+ if (card.symbiosis_mode) {
1972
+ if (!["shadowing", "copilot", "autonomy", "none"].includes(
1973
+ card.symbiosis_mode
1974
+ )) {
1975
+ throw new Error(`Invalid symbiosis_mode: ${card.symbiosis_mode}`);
1976
+ }
1977
+ symbiosisMode = card.symbiosis_mode === "none" ? null : card.symbiosis_mode;
1978
+ }
1979
+ const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
1980
+ const cleanDomain = slugify(card.domain || "");
1981
+ const cleanBase = slugify(baseText);
1982
+ let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
1983
+ if (baseSlug.length > 60) {
1984
+ baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
1985
+ }
1986
+ if (!baseSlug) {
1987
+ baseSlug = "token";
1988
+ }
1989
+ let token = await getTokenBySlug(tx, baseSlug);
1990
+ if (!token) {
1991
+ const finalSlug = await generateTokenSlug(
1992
+ tx,
1993
+ card.domain,
1994
+ card.concept,
1995
+ card.question
1996
+ );
1997
+ token = await createToken(tx, {
1998
+ slug: finalSlug,
1999
+ concept: card.concept,
2000
+ domain: card.domain,
2001
+ bloom_level: bloom,
2002
+ context: card.context || "",
2003
+ symbiosis_mode: symbiosisMode,
2004
+ source_link: card.source_link || originalToken.source_link || null,
2005
+ question: card.question || null
2006
+ });
2007
+ createdCount++;
2008
+ }
2009
+ targetTokenId = token.id;
2010
+ const existingCard = await getCard(tx, token.id, userId);
2011
+ if (!existingCard) {
2012
+ await ensureCard(tx, token.id, userId);
2013
+ }
2014
+ }
2015
+ if (targetTokenId === originalToken.id) {
2016
+ throw new Error("A token cannot be a prerequisite of itself");
2017
+ }
2018
+ await assertPrerequisiteDoesNotCreateCycle(
2019
+ tx,
2020
+ originalToken.id,
2021
+ targetTokenId
2022
+ );
2023
+ await tx.prepare(
2024
+ "INSERT OR IGNORE INTO prerequisites (token_id, requires_id) VALUES (?, ?)"
2025
+ ).run(originalToken.id, targetTokenId);
2026
+ }
2027
+ });
2028
+ return { createdCount, linkedCount };
2029
+ }
2030
+ async function confirmSourceImport(db, userId, sourceId, proposals) {
2031
+ let createdCount = 0;
2032
+ let linkedCount = 0;
2033
+ await db.transaction(async (tx) => {
2034
+ const source = await tx.prepare("SELECT id FROM sources WHERE id = ?").get(sourceId);
2035
+ if (!source) {
2036
+ throw new Error(`Source not found: ${sourceId}`);
2037
+ }
2038
+ for (const card of proposals) {
2039
+ const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
2040
+ const cleanDomain = slugify(card.domain || "");
2041
+ const cleanBase = slugify(baseText);
2042
+ let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
2043
+ if (baseSlug.length > 60) {
2044
+ baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
2045
+ }
2046
+ if (!baseSlug) {
2047
+ baseSlug = "token";
2048
+ }
2049
+ let token = await getTokenBySlug(tx, baseSlug);
2050
+ if (!token) {
2051
+ const finalSlug = await generateTokenSlug(
2052
+ tx,
2053
+ card.domain,
2054
+ card.concept,
2055
+ card.question
2056
+ );
2057
+ const bloom = card.bloom_level !== void 0 ? card.bloom_level : 1;
2058
+ let symbiosisMode = null;
2059
+ if (card.symbiosis_mode && card.symbiosis_mode !== "none") {
2060
+ symbiosisMode = card.symbiosis_mode;
2061
+ }
2062
+ token = await createToken(tx, {
2063
+ slug: finalSlug,
2064
+ concept: card.concept,
2065
+ domain: card.domain,
2066
+ bloom_level: bloom,
2067
+ context: card.excerpt || "",
2068
+ symbiosis_mode: symbiosisMode,
2069
+ question: card.question || null
2070
+ });
2071
+ createdCount++;
2072
+ } else {
2073
+ linkedCount++;
2074
+ }
2075
+ const existingCard = await getCard(tx, token.id, userId);
2076
+ if (!existingCard) {
2077
+ await ensureCard(tx, token.id, userId);
2078
+ }
2079
+ await tx.prepare(
2080
+ `INSERT INTO token_sources (token_id, source_id, excerpt, page_number)
2081
+ VALUES (?, ?, ?, ?)
2082
+ ON CONFLICT(token_id, source_id) DO UPDATE SET
2083
+ excerpt = excluded.excerpt,
2084
+ page_number = excluded.page_number`
2085
+ ).run(token.id, sourceId, card.excerpt || "", card.page_number || null);
2086
+ }
2087
+ });
2088
+ return { createdCount, linkedCount };
2089
+ }
2090
+ async function assertPrerequisiteDoesNotCreateCycle(db, tokenId, prerequisiteId) {
2091
+ const cycleCheck = await db.prepare(
2092
+ `WITH RECURSIVE dependents(token_id) AS (
2093
+ SELECT token_id FROM prerequisites WHERE requires_id = ?
2094
+ UNION
2095
+ SELECT p.token_id FROM prerequisites p
2096
+ JOIN dependents d ON p.requires_id = d.token_id
2097
+ )
2098
+ SELECT COUNT(*) as n FROM dependents WHERE token_id = ?`
2099
+ ).get(tokenId, prerequisiteId);
2100
+ if (cycleCheck.n > 0) {
2101
+ throw new Error("Cannot add prerequisite: would create a cycle");
2102
+ }
2103
+ }
1649
2104
 
1650
2105
  // src/kernel/models/prerequisite.ts
1651
2106
  async function buildAncestorMap(db) {
@@ -4637,12 +5092,7 @@ function installFastFlowLM() {
4637
5092
  }
4638
5093
  }
4639
5094
  function installOllama() {
4640
- const isMac = process.platform === "darwin";
4641
- const isWin = process.platform === "win32";
4642
- const hasOllama = hasCommand("ollama") || isMac && existsSync9("/Applications/Ollama.app") || isWin && existsSync9(
4643
- join10(homedir7(), "AppData", "Local", "Programs", "Ollama", "ollama.exe")
4644
- );
4645
- if (hasOllama) {
5095
+ if (isOllamaInstalled()) {
4646
5096
  return { success: true, message: "Ollama is already installed." };
4647
5097
  }
4648
5098
  if (process.platform === "darwin") {
@@ -4697,19 +5147,23 @@ function installOllama() {
4697
5147
  }
4698
5148
  }
4699
5149
  }
4700
- function resolveOllamaCommand() {
4701
- if (hasCommand("ollama")) return "ollama";
4702
- const candidates = process.platform === "win32" ? [
4703
- join10(
4704
- homedir7(),
4705
- "AppData",
4706
- "Local",
4707
- "Programs",
4708
- "Ollama",
4709
- "ollama.exe"
4710
- )
4711
- ] : process.platform === "darwin" ? ["/Applications/Ollama.app/Contents/Resources/ollama"] : [];
4712
- return candidates.find((candidate) => existsSync9(candidate));
5150
+ function resolveOllamaCommand(options = {}) {
5151
+ const platform = options.platform ?? process.platform;
5152
+ const homeDir = options.homeDir ?? homedir7();
5153
+ const commandAvailable = options.commandAvailable ?? hasCommand;
5154
+ const pathExists = options.pathExists ?? existsSync9;
5155
+ if (commandAvailable("ollama")) return "ollama";
5156
+ const candidates = platform === "win32" ? [join10(homeDir, "AppData", "Local", "Programs", "Ollama", "ollama.exe")] : platform === "darwin" ? [
5157
+ "/opt/homebrew/bin/ollama",
5158
+ "/usr/local/bin/ollama",
5159
+ "/Applications/Ollama.app/Contents/Resources/ollama"
5160
+ ] : ["/usr/local/bin/ollama", "/usr/bin/ollama"];
5161
+ return candidates.find((candidate) => pathExists(candidate));
5162
+ }
5163
+ function isOllamaInstalled(options = {}) {
5164
+ const platform = options.platform ?? process.platform;
5165
+ const pathExists = options.pathExists ?? existsSync9;
5166
+ return resolveOllamaCommand(options) !== void 0 || platform === "darwin" && pathExists("/Applications/Ollama.app");
4713
5167
  }
4714
5168
  function prepareLocalModel(runner, model) {
4715
5169
  if (runner === "fastflowlm") {
@@ -5094,6 +5548,9 @@ export {
5094
5548
  clearReviewContextCache,
5095
5549
  clearTursoCredentials,
5096
5550
  compareVersions,
5551
+ confirmCardSplit,
5552
+ confirmFoundations,
5553
+ confirmSourceImport,
5097
5554
  createAgentSkill,
5098
5555
  createFSRS,
5099
5556
  createGoal,
@@ -5126,6 +5583,7 @@ export {
5126
5583
  generatePowerShellHooks,
5127
5584
  generatePowerShellUnhooks,
5128
5585
  generatePrompt,
5586
+ generateTokenSlug,
5129
5587
  generateZshHooks,
5130
5588
  generateZshUnhooks,
5131
5589
  getADOCredentials,
@@ -5171,6 +5629,7 @@ export {
5171
5629
  getUiObserverDir,
5172
5630
  getUserStats,
5173
5631
  hasCommand,
5632
+ importCurriculumCards,
5174
5633
  importSnapshot,
5175
5634
  injectShellHooks,
5176
5635
  installFastFlowLM,
@@ -5178,9 +5637,11 @@ export {
5178
5637
  installOpenCode,
5179
5638
  interleave,
5180
5639
  isObserverPolicyConfigured,
5640
+ isOllamaInstalled,
5181
5641
  isUiObservationReport,
5182
5642
  listAgentSkills,
5183
5643
  listGoals,
5644
+ listPersonalCards,
5184
5645
  listProviderApiKeyRefs,
5185
5646
  listTokens,
5186
5647
  loadADOConfig,
@@ -5214,6 +5675,7 @@ export {
5214
5675
  resolveAllBeliefPaths,
5215
5676
  resolveAllGoalPaths,
5216
5677
  resolveObserverPolicy,
5678
+ resolveOllamaCommand,
5217
5679
  resolveReference,
5218
5680
  resolveRepoPath,
5219
5681
  resolveReviewContext,
@@ -5229,6 +5691,7 @@ export {
5229
5691
  setProviderApiKey,
5230
5692
  setSetting,
5231
5693
  setTursoCredentials,
5694
+ slugify,
5232
5695
  startSession,
5233
5696
  syncObserverSidecarPolicy,
5234
5697
  t,