zam-core 0.7.1 → 0.8.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/cli/index.js +2046 -189
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +107 -6
- package/dist/index.js +331 -65
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { readFileSync as readFileSync16 } from "fs";
|
|
5
5
|
import { dirname as dirname10, join as join25 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command27 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/cli/commands/agent.ts
|
|
10
10
|
import { existsSync as existsSync13 } from "fs";
|
|
@@ -543,6 +543,7 @@ var SCHEMA = `
|
|
|
543
543
|
CREATE TABLE IF NOT EXISTS tokens (
|
|
544
544
|
id TEXT PRIMARY KEY,
|
|
545
545
|
slug TEXT UNIQUE NOT NULL,
|
|
546
|
+
title TEXT NOT NULL DEFAULT '',
|
|
546
547
|
concept TEXT NOT NULL,
|
|
547
548
|
domain TEXT NOT NULL DEFAULT '',
|
|
548
549
|
bloom_level INTEGER NOT NULL DEFAULT 1 CHECK (bloom_level BETWEEN 1 AND 5),
|
|
@@ -681,6 +682,22 @@ CREATE TABLE IF NOT EXISTS agent_skills (
|
|
|
681
682
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
682
683
|
);
|
|
683
684
|
|
|
685
|
+
-- Knowledge contexts: work, school, private
|
|
686
|
+
CREATE TABLE IF NOT EXISTS contexts (
|
|
687
|
+
id TEXT PRIMARY KEY,
|
|
688
|
+
name TEXT NOT NULL UNIQUE,
|
|
689
|
+
label TEXT,
|
|
690
|
+
language TEXT,
|
|
691
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
-- Join table mapping tokens to their knowledge contexts
|
|
695
|
+
CREATE TABLE IF NOT EXISTS token_contexts (
|
|
696
|
+
token_id TEXT NOT NULL REFERENCES tokens(id) ON DELETE CASCADE,
|
|
697
|
+
context_id TEXT NOT NULL REFERENCES contexts(id) ON DELETE CASCADE,
|
|
698
|
+
PRIMARY KEY (token_id, context_id)
|
|
699
|
+
);
|
|
700
|
+
|
|
684
701
|
-- Performance indexes
|
|
685
702
|
CREATE INDEX IF NOT EXISTS idx_tokens_domain ON tokens(domain);
|
|
686
703
|
CREATE INDEX IF NOT EXISTS idx_tokens_slug ON tokens(slug);
|
|
@@ -691,6 +708,8 @@ CREATE INDEX IF NOT EXISTS idx_cards_token_user ON cards(token_id, user_id);
|
|
|
691
708
|
CREATE INDEX IF NOT EXISTS idx_review_logs_card ON review_logs(card_id);
|
|
692
709
|
CREATE INDEX IF NOT EXISTS idx_review_logs_user ON review_logs(user_id, reviewed_at);
|
|
693
710
|
CREATE INDEX IF NOT EXISTS idx_session_steps_session ON session_steps(session_id);
|
|
711
|
+
CREATE INDEX IF NOT EXISTS idx_tokens_title ON tokens(title);
|
|
712
|
+
CREATE INDEX IF NOT EXISTS idx_token_contexts_context ON token_contexts(context_id);
|
|
694
713
|
`;
|
|
695
714
|
|
|
696
715
|
// src/kernel/db/sync-adapter.ts
|
|
@@ -1024,6 +1043,34 @@ async function runMigrations(db) {
|
|
|
1024
1043
|
embedded_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1025
1044
|
)
|
|
1026
1045
|
`);
|
|
1046
|
+
if (!tokenCols.some((c) => c.name === "title")) {
|
|
1047
|
+
await db.exec(
|
|
1048
|
+
`ALTER TABLE tokens ADD COLUMN title TEXT NOT NULL DEFAULT ''`
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_tokens_title ON tokens(title)`);
|
|
1052
|
+
await db.exec(
|
|
1053
|
+
`CREATE INDEX IF NOT EXISTS idx_tokens_domain ON tokens(domain)`
|
|
1054
|
+
);
|
|
1055
|
+
await db.exec(`
|
|
1056
|
+
CREATE TABLE IF NOT EXISTS contexts (
|
|
1057
|
+
id TEXT PRIMARY KEY,
|
|
1058
|
+
name TEXT NOT NULL UNIQUE,
|
|
1059
|
+
label TEXT,
|
|
1060
|
+
language TEXT,
|
|
1061
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1062
|
+
)
|
|
1063
|
+
`);
|
|
1064
|
+
await db.exec(`
|
|
1065
|
+
CREATE TABLE IF NOT EXISTS token_contexts (
|
|
1066
|
+
token_id TEXT NOT NULL REFERENCES tokens(id) ON DELETE CASCADE,
|
|
1067
|
+
context_id TEXT NOT NULL REFERENCES contexts(id) ON DELETE CASCADE,
|
|
1068
|
+
PRIMARY KEY (token_id, context_id)
|
|
1069
|
+
)
|
|
1070
|
+
`);
|
|
1071
|
+
await db.exec(`
|
|
1072
|
+
CREATE INDEX IF NOT EXISTS idx_token_contexts_context ON token_contexts(context_id)
|
|
1073
|
+
`);
|
|
1027
1074
|
}
|
|
1028
1075
|
|
|
1029
1076
|
// src/kernel/db/snapshot.ts
|
|
@@ -1487,41 +1534,110 @@ async function deleteCardForUser(db, tokenId, userId) {
|
|
|
1487
1534
|
await db.prepare("DELETE FROM cards WHERE id = ?").run(card.id);
|
|
1488
1535
|
return { card, impact };
|
|
1489
1536
|
}
|
|
1490
|
-
async function getDueCards(db, userId, now, domain) {
|
|
1537
|
+
async function getDueCards(db, userId, now, domain, knowledgeContext) {
|
|
1491
1538
|
const cutoff = now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1539
|
+
let sql = `SELECT c.*, t.slug, t.concept, t.domain, t.bloom_level
|
|
1540
|
+
FROM cards c
|
|
1541
|
+
JOIN tokens t ON t.id = c.token_id
|
|
1542
|
+
WHERE c.user_id = ? AND c.blocked = 0 AND c.due_at <= ?`;
|
|
1543
|
+
const params = [userId, cutoff];
|
|
1492
1544
|
if (domain) {
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
FROM cards c
|
|
1496
|
-
JOIN tokens t ON t.id = c.token_id
|
|
1497
|
-
WHERE c.user_id = ? AND c.blocked = 0 AND c.due_at <= ? AND t.domain = ?
|
|
1498
|
-
ORDER BY t.bloom_level ASC, c.due_at ASC`
|
|
1499
|
-
).all(userId, cutoff, domain);
|
|
1545
|
+
sql += " AND t.domain = ?";
|
|
1546
|
+
params.push(domain);
|
|
1500
1547
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1548
|
+
if (knowledgeContext) {
|
|
1549
|
+
sql += ` AND EXISTS (
|
|
1550
|
+
SELECT 1 FROM token_contexts tc
|
|
1551
|
+
INNER JOIN contexts context_filter ON context_filter.id = tc.context_id
|
|
1552
|
+
WHERE tc.token_id = t.id AND context_filter.name = ?
|
|
1553
|
+
)`;
|
|
1554
|
+
params.push(knowledgeContext);
|
|
1555
|
+
}
|
|
1556
|
+
sql += " ORDER BY t.bloom_level ASC, c.due_at ASC";
|
|
1557
|
+
return await db.prepare(sql).all(...params);
|
|
1508
1558
|
}
|
|
1509
1559
|
|
|
1510
|
-
// src/kernel/models/
|
|
1560
|
+
// src/kernel/models/knowledge-context.ts
|
|
1511
1561
|
import { ulid as ulid3 } from "ulid";
|
|
1512
|
-
|
|
1562
|
+
function normalizeContextName(name) {
|
|
1563
|
+
const normalized = name.trim();
|
|
1564
|
+
if (!normalized) {
|
|
1565
|
+
throw new Error("Context name cannot be empty");
|
|
1566
|
+
}
|
|
1567
|
+
return normalized;
|
|
1568
|
+
}
|
|
1569
|
+
function normalizeOptionalText(value) {
|
|
1570
|
+
if (value == null) return null;
|
|
1571
|
+
const normalized = value.trim();
|
|
1572
|
+
return normalized || null;
|
|
1573
|
+
}
|
|
1574
|
+
async function createKnowledgeContext(db, input8) {
|
|
1513
1575
|
const id = ulid3();
|
|
1514
1576
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1577
|
+
const name = normalizeContextName(input8.name);
|
|
1578
|
+
const existing = await getKnowledgeContextByName(db, name);
|
|
1579
|
+
if (existing) {
|
|
1580
|
+
throw new Error(`Knowledge context with name "${name}" already exists`);
|
|
1581
|
+
}
|
|
1582
|
+
await db.prepare(
|
|
1583
|
+
`INSERT INTO contexts (id, name, label, language, created_at)
|
|
1584
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
1585
|
+
).run(
|
|
1586
|
+
id,
|
|
1587
|
+
name,
|
|
1588
|
+
normalizeOptionalText(input8.label),
|
|
1589
|
+
normalizeOptionalText(input8.language),
|
|
1590
|
+
now
|
|
1591
|
+
);
|
|
1592
|
+
const context = await getKnowledgeContextById(db, id);
|
|
1593
|
+
if (!context) {
|
|
1594
|
+
throw new Error(
|
|
1595
|
+
`Failed to retrieve newly created knowledge context: ${id}`
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
return context;
|
|
1599
|
+
}
|
|
1600
|
+
async function getKnowledgeContextByName(db, name) {
|
|
1601
|
+
const normalized = name.trim();
|
|
1602
|
+
if (!normalized) return void 0;
|
|
1603
|
+
return await db.prepare("SELECT * FROM contexts WHERE name = ?").get(normalized);
|
|
1604
|
+
}
|
|
1605
|
+
async function getKnowledgeContextById(db, id) {
|
|
1606
|
+
return await db.prepare("SELECT * FROM contexts WHERE id = ?").get(id);
|
|
1607
|
+
}
|
|
1608
|
+
async function listKnowledgeContexts(db) {
|
|
1609
|
+
return await db.prepare("SELECT * FROM contexts ORDER BY name ASC").all();
|
|
1610
|
+
}
|
|
1611
|
+
async function deleteKnowledgeContext(db, id) {
|
|
1612
|
+
await db.prepare("DELETE FROM contexts WHERE id = ?").run(id);
|
|
1613
|
+
}
|
|
1614
|
+
async function assignTokenToContext(db, tokenId, contextId) {
|
|
1615
|
+
await db.prepare(
|
|
1616
|
+
`INSERT OR IGNORE INTO token_contexts (token_id, context_id)
|
|
1617
|
+
VALUES (?, ?)`
|
|
1618
|
+
).run(tokenId, contextId);
|
|
1619
|
+
}
|
|
1620
|
+
async function unassignTokenFromContext(db, tokenId, contextId) {
|
|
1621
|
+
await db.prepare("DELETE FROM token_contexts WHERE token_id = ? AND context_id = ?").run(tokenId, contextId);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// src/kernel/models/token.ts
|
|
1625
|
+
import { ulid as ulid4 } from "ulid";
|
|
1626
|
+
async function createToken(db, input8) {
|
|
1627
|
+
const id = ulid4();
|
|
1628
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1515
1629
|
const bloom = input8.bloom_level ?? 1;
|
|
1516
1630
|
if (bloom < 1 || bloom > 5) {
|
|
1517
1631
|
throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
|
|
1518
1632
|
}
|
|
1633
|
+
const title = input8.title ?? "";
|
|
1519
1634
|
await db.prepare(`
|
|
1520
|
-
INSERT INTO tokens (id, slug, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, provider, topic_id, created_at, updated_at)
|
|
1521
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1635
|
+
INSERT INTO tokens (id, slug, title, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, provider, topic_id, created_at, updated_at)
|
|
1636
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1522
1637
|
`).run(
|
|
1523
1638
|
id,
|
|
1524
1639
|
input8.slug,
|
|
1640
|
+
title,
|
|
1525
1641
|
input8.concept,
|
|
1526
1642
|
input8.domain ?? "",
|
|
1527
1643
|
bloom,
|
|
@@ -1564,6 +1680,10 @@ async function updateToken(db, slug, updates) {
|
|
|
1564
1680
|
}
|
|
1565
1681
|
const fields = [];
|
|
1566
1682
|
const values = [];
|
|
1683
|
+
if (updates.title !== void 0) {
|
|
1684
|
+
fields.push("title = ?");
|
|
1685
|
+
values.push(updates.title ?? "");
|
|
1686
|
+
}
|
|
1567
1687
|
if (updates.concept !== void 0) {
|
|
1568
1688
|
fields.push("concept = ?");
|
|
1569
1689
|
values.push(updates.concept);
|
|
@@ -1669,13 +1789,14 @@ async function deleteToken(db, slug) {
|
|
|
1669
1789
|
await db.transaction(async (tx) => {
|
|
1670
1790
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1671
1791
|
const skillRows = await tx.prepare("SELECT id, token_slugs FROM agent_skills").all();
|
|
1792
|
+
const skillUpdateStmt = tx.prepare(
|
|
1793
|
+
"UPDATE agent_skills SET token_slugs = ?, updated_at = ? WHERE id = ?"
|
|
1794
|
+
);
|
|
1672
1795
|
for (const row of skillRows) {
|
|
1673
1796
|
const tokenSlugs = JSON.parse(row.token_slugs);
|
|
1674
1797
|
const filtered = tokenSlugs.filter((tokenSlug) => tokenSlug !== slug);
|
|
1675
1798
|
if (filtered.length !== tokenSlugs.length) {
|
|
1676
|
-
await
|
|
1677
|
-
"UPDATE agent_skills SET token_slugs = ?, updated_at = ? WHERE id = ?"
|
|
1678
|
-
).run(JSON.stringify(filtered), now, row.id);
|
|
1799
|
+
await skillUpdateStmt.run(JSON.stringify(filtered), now, row.id);
|
|
1679
1800
|
}
|
|
1680
1801
|
}
|
|
1681
1802
|
await tx.prepare("DELETE FROM tokens WHERE id = ?").run(token.id);
|
|
@@ -1689,10 +1810,16 @@ async function findTokens(db, query) {
|
|
|
1689
1810
|
const shortTerms = searchTokens.filter((t2) => t2.length <= 2);
|
|
1690
1811
|
const longTerms = searchTokens.filter((t2) => t2.length > 2);
|
|
1691
1812
|
const scoreMap = /* @__PURE__ */ new Map();
|
|
1692
|
-
const likeSQL = `SELECT * FROM tokens WHERE deprecated_at IS NULL AND (lower(slug) LIKE ? OR lower(concept) LIKE ? OR lower(domain) LIKE ?)`;
|
|
1813
|
+
const likeSQL = `SELECT * FROM tokens WHERE deprecated_at IS NULL AND (lower(slug) LIKE ? OR lower(title) LIKE ? OR lower(concept) LIKE ? OR lower(domain) LIKE ?)`;
|
|
1814
|
+
const likeStmt = db.prepare(likeSQL);
|
|
1693
1815
|
for (const term of longTerms) {
|
|
1694
1816
|
const pattern = `%${term}%`;
|
|
1695
|
-
const rows = await
|
|
1817
|
+
const rows = await likeStmt.all(
|
|
1818
|
+
pattern,
|
|
1819
|
+
pattern,
|
|
1820
|
+
pattern,
|
|
1821
|
+
pattern
|
|
1822
|
+
);
|
|
1696
1823
|
for (const row of rows) {
|
|
1697
1824
|
const entry = scoreMap.get(row.id);
|
|
1698
1825
|
if (entry) {
|
|
@@ -1705,7 +1832,7 @@ async function findTokens(db, query) {
|
|
|
1705
1832
|
if (shortTerms.length > 0 || longTerms.length === 0) {
|
|
1706
1833
|
const allTokens = await db.prepare("SELECT * FROM tokens WHERE deprecated_at IS NULL").all();
|
|
1707
1834
|
for (const token of allTokens) {
|
|
1708
|
-
const words = `${token.slug} ${token.concept} ${token.domain}`.toLowerCase().split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter(Boolean);
|
|
1835
|
+
const words = `${token.slug} ${token.title} ${token.concept} ${token.domain}`.toLowerCase().split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter(Boolean);
|
|
1709
1836
|
let matchCount = 0;
|
|
1710
1837
|
for (const term of shortTerms.length > 0 ? shortTerms : searchTokens) {
|
|
1711
1838
|
for (const w of words) {
|
|
@@ -1734,23 +1861,47 @@ async function findTokens(db, query) {
|
|
|
1734
1861
|
return scored;
|
|
1735
1862
|
}
|
|
1736
1863
|
async function listTokens(db, options) {
|
|
1737
|
-
|
|
1864
|
+
const whereClauses = ["deprecated_at IS NULL"];
|
|
1865
|
+
const params = [];
|
|
1738
1866
|
if (options?.domain) {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1867
|
+
whereClauses.push("domain = ?");
|
|
1868
|
+
params.push(options.domain);
|
|
1869
|
+
} else if (options?.domainPrefix) {
|
|
1870
|
+
const prefix = options.domainPrefix;
|
|
1871
|
+
whereClauses.push("(domain = ? OR domain LIKE ?)");
|
|
1872
|
+
params.push(prefix, `${prefix}/%`);
|
|
1873
|
+
}
|
|
1874
|
+
if (options?.knowledgeContext) {
|
|
1875
|
+
whereClauses.push(`EXISTS (
|
|
1876
|
+
SELECT 1 FROM token_contexts tc
|
|
1877
|
+
INNER JOIN contexts c ON c.id = tc.context_id
|
|
1878
|
+
WHERE tc.token_id = tokens.id AND c.name = ?
|
|
1879
|
+
)`);
|
|
1880
|
+
params.push(options.knowledgeContext);
|
|
1881
|
+
}
|
|
1882
|
+
const orderBy = options?.domain || options?.domainPrefix ? "ORDER BY bloom_level, slug" : "ORDER BY bloom_level, domain, slug";
|
|
1883
|
+
const sql = `SELECT * FROM tokens WHERE ${whereClauses.join(" AND ")} ${orderBy}`;
|
|
1884
|
+
const tokens = await db.prepare(sql).all(...params);
|
|
1747
1885
|
for (const token of tokens) {
|
|
1748
1886
|
parseTokenFallback(token);
|
|
1749
1887
|
}
|
|
1750
1888
|
return tokens;
|
|
1751
1889
|
}
|
|
1752
1890
|
function slugify(text) {
|
|
1753
|
-
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1891
|
+
return text.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1892
|
+
}
|
|
1893
|
+
function getShortSlug(slug, domainPrefix) {
|
|
1894
|
+
if (domainPrefix) {
|
|
1895
|
+
const folded = domainPrefix.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1896
|
+
if (folded && slug.startsWith(`${folded}-`)) {
|
|
1897
|
+
return slug.substring(folded.length + 1);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
return slug;
|
|
1901
|
+
}
|
|
1902
|
+
function getDisplayTitle(t2, activeDomainScope) {
|
|
1903
|
+
if (t2.title?.trim()) return t2.title.trim();
|
|
1904
|
+
return getShortSlug(t2.slug, activeDomainScope);
|
|
1754
1905
|
}
|
|
1755
1906
|
async function generateTokenSlug(db, domain, concept, question) {
|
|
1756
1907
|
const baseText = question && question.trim().length > 0 ? question : concept;
|
|
@@ -1780,6 +1931,7 @@ async function listPersonalCards(db, userId, options) {
|
|
|
1780
1931
|
SELECT
|
|
1781
1932
|
t.id AS tokenId,
|
|
1782
1933
|
t.slug,
|
|
1934
|
+
t.title,
|
|
1783
1935
|
t.concept,
|
|
1784
1936
|
t.domain,
|
|
1785
1937
|
t.bloom_level AS bloomLevel,
|
|
@@ -1820,6 +1972,14 @@ async function listPersonalCards(db, userId, options) {
|
|
|
1820
1972
|
sql += " AND t.domain = ?";
|
|
1821
1973
|
values.push(options.domain);
|
|
1822
1974
|
}
|
|
1975
|
+
if (options?.knowledgeContext) {
|
|
1976
|
+
sql += ` AND EXISTS (
|
|
1977
|
+
SELECT 1 FROM token_contexts tc
|
|
1978
|
+
INNER JOIN contexts kc ON kc.id = tc.context_id
|
|
1979
|
+
WHERE tc.token_id = t.id AND kc.name = ?
|
|
1980
|
+
)`;
|
|
1981
|
+
values.push(options.knowledgeContext);
|
|
1982
|
+
}
|
|
1823
1983
|
if (options?.query) {
|
|
1824
1984
|
const terms = options.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1825
1985
|
for (const term of terms) {
|
|
@@ -1881,6 +2041,7 @@ async function importCurriculumCards(db, userId, cards) {
|
|
|
1881
2041
|
);
|
|
1882
2042
|
token = await createToken(tx, {
|
|
1883
2043
|
slug: finalSlug,
|
|
2044
|
+
title: card.title,
|
|
1884
2045
|
concept: card.concept,
|
|
1885
2046
|
domain: card.domain,
|
|
1886
2047
|
bloom_level: bloom,
|
|
@@ -1998,23 +2159,28 @@ async function confirmCardSplit(db, userId, originalSlug, action, originalQuesti
|
|
|
1998
2159
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
1999
2160
|
originalToken.id
|
|
2000
2161
|
);
|
|
2162
|
+
const insertPrereqStmt = tx.prepare(
|
|
2163
|
+
"INSERT OR IGNORE INTO prerequisites (token_id, requires_id) VALUES (?, ?)"
|
|
2164
|
+
);
|
|
2001
2165
|
for (const propToken of proposalTokens) {
|
|
2002
|
-
await
|
|
2003
|
-
"INSERT OR IGNORE INTO prerequisites (token_id, requires_id) VALUES (?, ?)"
|
|
2004
|
-
).run(originalToken.id, propToken.id);
|
|
2166
|
+
await insertPrereqStmt.run(originalToken.id, propToken.id);
|
|
2005
2167
|
}
|
|
2006
2168
|
await tx.prepare(
|
|
2007
2169
|
"UPDATE cards SET blocked = 1 WHERE token_id = ? AND user_id = ?"
|
|
2008
2170
|
).run(originalToken.id, userId);
|
|
2171
|
+
const checkPrereqsStmt = tx.prepare(
|
|
2172
|
+
"SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?"
|
|
2173
|
+
);
|
|
2174
|
+
const unblockCardStmt = tx.prepare(
|
|
2175
|
+
"UPDATE cards SET blocked = 0, due_at = ? WHERE id = ?"
|
|
2176
|
+
);
|
|
2009
2177
|
for (const propToken of proposalTokens) {
|
|
2010
2178
|
const card = await ensureCard(tx, propToken.id, userId);
|
|
2011
2179
|
if (card.blocked === 1) {
|
|
2012
|
-
const prereqOfPrereq = await
|
|
2013
|
-
"SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?"
|
|
2014
|
-
).get(propToken.id);
|
|
2180
|
+
const prereqOfPrereq = await checkPrereqsStmt.get(propToken.id);
|
|
2015
2181
|
if (prereqOfPrereq.n === 0) {
|
|
2016
2182
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2017
|
-
await
|
|
2183
|
+
await unblockCardStmt.run(now, card.id);
|
|
2018
2184
|
}
|
|
2019
2185
|
}
|
|
2020
2186
|
}
|
|
@@ -2085,6 +2251,7 @@ async function confirmFoundations(db, userId, originalSlug, proposals) {
|
|
|
2085
2251
|
);
|
|
2086
2252
|
token = await createToken(tx, {
|
|
2087
2253
|
slug: finalSlug,
|
|
2254
|
+
title: card.title,
|
|
2088
2255
|
concept: card.concept,
|
|
2089
2256
|
domain: card.domain,
|
|
2090
2257
|
bloom_level: bloom,
|
|
@@ -2151,6 +2318,7 @@ async function confirmSourceImport(db, userId, sourceId, proposals) {
|
|
|
2151
2318
|
}
|
|
2152
2319
|
token = await createToken(tx, {
|
|
2153
2320
|
slug: finalSlug,
|
|
2321
|
+
title: card.title,
|
|
2154
2322
|
concept: card.concept,
|
|
2155
2323
|
domain: card.domain,
|
|
2156
2324
|
bloom_level: bloom,
|
|
@@ -2253,7 +2421,7 @@ async function addPrerequisite(db, tokenId, requiresId) {
|
|
|
2253
2421
|
}
|
|
2254
2422
|
async function getPrerequisites(db, tokenId) {
|
|
2255
2423
|
return await db.prepare(
|
|
2256
|
-
`SELECT p.token_id, p.requires_id, t.slug, t.concept, t.domain, t.bloom_level
|
|
2424
|
+
`SELECT p.token_id, p.requires_id, t.slug, t.title, t.concept, t.domain, t.bloom_level
|
|
2257
2425
|
FROM prerequisites p
|
|
2258
2426
|
JOIN tokens t ON t.id = p.requires_id
|
|
2259
2427
|
WHERE p.token_id = ?`
|
|
@@ -2261,7 +2429,7 @@ async function getPrerequisites(db, tokenId) {
|
|
|
2261
2429
|
}
|
|
2262
2430
|
async function getDependents(db, tokenId) {
|
|
2263
2431
|
return await db.prepare(
|
|
2264
|
-
`SELECT p.token_id, p.requires_id, t.slug, t.concept, t.domain, t.bloom_level
|
|
2432
|
+
`SELECT p.token_id, p.requires_id, t.slug, t.title, t.concept, t.domain, t.bloom_level
|
|
2265
2433
|
FROM prerequisites p
|
|
2266
2434
|
JOIN tokens t ON t.id = p.token_id
|
|
2267
2435
|
WHERE p.requires_id = ?`
|
|
@@ -2292,6 +2460,7 @@ async function getTokenNeighborhood(db, tokenId, userId) {
|
|
|
2292
2460
|
const toNode = (t2, card) => ({
|
|
2293
2461
|
id: t2.id,
|
|
2294
2462
|
slug: t2.slug,
|
|
2463
|
+
title: t2.title ?? t2.slug,
|
|
2295
2464
|
concept: t2.concept,
|
|
2296
2465
|
domain: t2.domain,
|
|
2297
2466
|
bloom_level: t2.bloom_level,
|
|
@@ -2311,6 +2480,7 @@ async function getTokenNeighborhood(db, tokenId, userId) {
|
|
|
2311
2480
|
{
|
|
2312
2481
|
id: p.requires_id,
|
|
2313
2482
|
slug: p.slug,
|
|
2483
|
+
title: p.title,
|
|
2314
2484
|
concept: p.concept,
|
|
2315
2485
|
domain: p.domain,
|
|
2316
2486
|
bloom_level: p.bloom_level
|
|
@@ -2323,6 +2493,7 @@ async function getTokenNeighborhood(db, tokenId, userId) {
|
|
|
2323
2493
|
{
|
|
2324
2494
|
id: d.token_id,
|
|
2325
2495
|
slug: d.slug,
|
|
2496
|
+
title: d.title,
|
|
2326
2497
|
concept: d.concept,
|
|
2327
2498
|
domain: d.domain,
|
|
2328
2499
|
bloom_level: d.bloom_level
|
|
@@ -2334,12 +2505,12 @@ async function getTokenNeighborhood(db, tokenId, userId) {
|
|
|
2334
2505
|
}
|
|
2335
2506
|
|
|
2336
2507
|
// src/kernel/models/review.ts
|
|
2337
|
-
import { ulid as
|
|
2508
|
+
import { ulid as ulid5 } from "ulid";
|
|
2338
2509
|
|
|
2339
2510
|
// src/kernel/models/session.ts
|
|
2340
|
-
import { ulid as
|
|
2511
|
+
import { ulid as ulid6 } from "ulid";
|
|
2341
2512
|
async function startSession(db, input8) {
|
|
2342
|
-
const id =
|
|
2513
|
+
const id = ulid6();
|
|
2343
2514
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2344
2515
|
const ctx = input8.execution_context ?? "shell";
|
|
2345
2516
|
await db.prepare(
|
|
@@ -2373,7 +2544,7 @@ async function logStep(db, input8) {
|
|
|
2373
2544
|
if (!session) {
|
|
2374
2545
|
throw new Error(`Session not found: ${input8.session_id}`);
|
|
2375
2546
|
}
|
|
2376
|
-
const id =
|
|
2547
|
+
const id = ulid6();
|
|
2377
2548
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2378
2549
|
await db.prepare(
|
|
2379
2550
|
`INSERT INTO session_steps (id, session_id, token_id, done_by, rating, notes, created_at)
|
|
@@ -2437,7 +2608,8 @@ import { createHash as createHash2 } from "crypto";
|
|
|
2437
2608
|
function embeddingContentForToken(t2) {
|
|
2438
2609
|
return `${t2.concept}
|
|
2439
2610
|
${t2.question ?? ""}
|
|
2440
|
-
${t2.domain}
|
|
2611
|
+
${t2.domain}
|
|
2612
|
+
${t2.title ?? ""}`;
|
|
2441
2613
|
}
|
|
2442
2614
|
function computeContentHash(text) {
|
|
2443
2615
|
return createHash2("sha256").update(text, "utf8").digest("hex");
|
|
@@ -3254,10 +3426,10 @@ async function syncObserverSidecarPolicy(db, dir = getUiObserverDir()) {
|
|
|
3254
3426
|
}
|
|
3255
3427
|
|
|
3256
3428
|
// src/kernel/observation/session-synthesis.ts
|
|
3257
|
-
import { ulid as
|
|
3429
|
+
import { ulid as ulid8 } from "ulid";
|
|
3258
3430
|
|
|
3259
3431
|
// src/kernel/recall/evaluator.ts
|
|
3260
|
-
import { ulid as
|
|
3432
|
+
import { ulid as ulid7 } from "ulid";
|
|
3261
3433
|
|
|
3262
3434
|
// src/kernel/scheduler/fsrs.ts
|
|
3263
3435
|
var DEFAULT_W = [
|
|
@@ -3436,7 +3608,7 @@ async function evaluateRatingWithinTransaction(db, input8) {
|
|
|
3436
3608
|
due_at: updated.dueAt.toISOString(),
|
|
3437
3609
|
last_review_at: now.toISOString()
|
|
3438
3610
|
});
|
|
3439
|
-
const reviewLogId = input8.reviewLogId ??
|
|
3611
|
+
const reviewLogId = input8.reviewLogId ?? ulid7();
|
|
3440
3612
|
await db.prepare(
|
|
3441
3613
|
`INSERT INTO review_logs (id, card_id, token_id, user_id, rating, response_time_ms, reviewed_at, scheduled_at, session_id)
|
|
3442
3614
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
@@ -3770,7 +3942,7 @@ async function applySessionSynthesis(db, input8) {
|
|
|
3770
3942
|
return { applied: false, record: parseSynthesisRow(existing) };
|
|
3771
3943
|
}
|
|
3772
3944
|
const card = await ensureCard(tx, token.id, session.user_id);
|
|
3773
|
-
const reviewLogId =
|
|
3945
|
+
const reviewLogId = ulid8();
|
|
3774
3946
|
await evaluateRatingWithinTransaction(tx, {
|
|
3775
3947
|
cardId: card.id,
|
|
3776
3948
|
tokenId: token.id,
|
|
@@ -4596,11 +4768,11 @@ async function buildReviewQueue(db, options) {
|
|
|
4596
4768
|
const maxReviews = options.maxReviews ?? 50;
|
|
4597
4769
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
4598
4770
|
const nowISO = now.toISOString();
|
|
4599
|
-
|
|
4600
|
-
`SELECT
|
|
4771
|
+
let dueSql = `SELECT
|
|
4601
4772
|
c.id AS card_id,
|
|
4602
4773
|
c.token_id AS token_id,
|
|
4603
4774
|
t.slug AS slug,
|
|
4775
|
+
t.title AS title,
|
|
4604
4776
|
t.concept AS concept,
|
|
4605
4777
|
t.domain AS domain,
|
|
4606
4778
|
t.bloom_level AS bloom_level,
|
|
@@ -4614,14 +4786,23 @@ async function buildReviewQueue(db, options) {
|
|
|
4614
4786
|
AND c.blocked = 0
|
|
4615
4787
|
AND c.due_at <= ?
|
|
4616
4788
|
AND c.state IN ('review', 'relearning', 'learning')
|
|
4617
|
-
AND t.deprecated_at IS NULL
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4789
|
+
AND t.deprecated_at IS NULL`;
|
|
4790
|
+
const dueParams = [options.userId, nowISO];
|
|
4791
|
+
if (options.knowledgeContext) {
|
|
4792
|
+
dueSql += ` AND EXISTS (
|
|
4793
|
+
SELECT 1 FROM token_contexts tc
|
|
4794
|
+
INNER JOIN contexts ctx ON ctx.id = tc.context_id
|
|
4795
|
+
WHERE tc.token_id = t.id AND ctx.name = ?
|
|
4796
|
+
)`;
|
|
4797
|
+
dueParams.push(options.knowledgeContext);
|
|
4798
|
+
}
|
|
4799
|
+
dueSql += ` ORDER BY c.due_at ASC`;
|
|
4800
|
+
const dueRows = await db.prepare(dueSql).all(...dueParams);
|
|
4801
|
+
let newSql = `SELECT
|
|
4622
4802
|
c.id AS card_id,
|
|
4623
4803
|
c.token_id AS token_id,
|
|
4624
4804
|
t.slug AS slug,
|
|
4805
|
+
t.title AS title,
|
|
4625
4806
|
t.concept AS concept,
|
|
4626
4807
|
t.domain AS domain,
|
|
4627
4808
|
t.bloom_level AS bloom_level,
|
|
@@ -4634,10 +4815,19 @@ async function buildReviewQueue(db, options) {
|
|
|
4634
4815
|
WHERE c.user_id = ?
|
|
4635
4816
|
AND c.blocked = 0
|
|
4636
4817
|
AND c.state = 'new'
|
|
4637
|
-
AND t.deprecated_at IS NULL
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4818
|
+
AND t.deprecated_at IS NULL`;
|
|
4819
|
+
const newParams = [options.userId];
|
|
4820
|
+
if (options.knowledgeContext) {
|
|
4821
|
+
newSql += ` AND EXISTS (
|
|
4822
|
+
SELECT 1 FROM token_contexts tc
|
|
4823
|
+
INNER JOIN contexts ctx ON ctx.id = tc.context_id
|
|
4824
|
+
WHERE tc.token_id = t.id AND ctx.name = ?
|
|
4825
|
+
)`;
|
|
4826
|
+
newParams.push(options.knowledgeContext);
|
|
4827
|
+
}
|
|
4828
|
+
newSql += ` ORDER BY t.bloom_level ASC, t.slug ASC LIMIT ?`;
|
|
4829
|
+
newParams.push(maxNew);
|
|
4830
|
+
const newRows = await db.prepare(newSql).all(...newParams);
|
|
4641
4831
|
const nowMs = now.getTime();
|
|
4642
4832
|
const sortedDue = [...dueRows].sort((a, b) => {
|
|
4643
4833
|
const overdueA = nowMs - new Date(a.due_at).getTime();
|
|
@@ -4681,6 +4871,7 @@ function rowToItem(row) {
|
|
|
4681
4871
|
cardId: row.card_id,
|
|
4682
4872
|
tokenId: row.token_id,
|
|
4683
4873
|
slug: row.slug,
|
|
4874
|
+
title: getDisplayTitle(row),
|
|
4684
4875
|
concept: row.concept,
|
|
4685
4876
|
domain: row.domain,
|
|
4686
4877
|
bloomLevel: row.bloom_level,
|
|
@@ -5367,6 +5558,27 @@ function detectSyncProvider(dir) {
|
|
|
5367
5558
|
}
|
|
5368
5559
|
return null;
|
|
5369
5560
|
}
|
|
5561
|
+
function getActiveWorkspaceContext(path = defaultConfigPath()) {
|
|
5562
|
+
const activeWorkspace = getActiveWorkspace(path);
|
|
5563
|
+
return activeWorkspace?.activeKnowledgeContext;
|
|
5564
|
+
}
|
|
5565
|
+
function setActiveWorkspaceContext(contextName, path = defaultConfigPath()) {
|
|
5566
|
+
const config = loadInstallConfig(path);
|
|
5567
|
+
const activeId = config.activeWorkspaceId;
|
|
5568
|
+
if (activeId && config.workspaces) {
|
|
5569
|
+
const workspace = config.workspaces.find((w) => w.id === activeId);
|
|
5570
|
+
if (workspace) {
|
|
5571
|
+
if (contextName) {
|
|
5572
|
+
workspace.activeKnowledgeContext = contextName;
|
|
5573
|
+
} else {
|
|
5574
|
+
delete workspace.activeKnowledgeContext;
|
|
5575
|
+
}
|
|
5576
|
+
saveInstallConfig(config, path);
|
|
5577
|
+
return true;
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5580
|
+
return false;
|
|
5581
|
+
}
|
|
5370
5582
|
|
|
5371
5583
|
// src/kernel/system/installer.ts
|
|
5372
5584
|
import { execFileSync, execSync } from "child_process";
|
|
@@ -6233,7 +6445,7 @@ import { existsSync as existsSync17, readdirSync as readdirSync2, readFileSync a
|
|
|
6233
6445
|
import { homedir as homedir10, tmpdir as tmpdir3 } from "os";
|
|
6234
6446
|
import { join as join18, resolve as resolve4 } from "path";
|
|
6235
6447
|
import { Command as Command2 } from "commander";
|
|
6236
|
-
import { ulid as
|
|
6448
|
+
import { ulid as ulid9 } from "ulid";
|
|
6237
6449
|
|
|
6238
6450
|
// src/cli/adapters/source-reader.ts
|
|
6239
6451
|
import dns from "dns";
|
|
@@ -6640,6 +6852,11 @@ function parseGeneratedCardArray(responseText, label, limits) {
|
|
|
6640
6852
|
);
|
|
6641
6853
|
}
|
|
6642
6854
|
}
|
|
6855
|
+
if (card.title !== void 0 && (typeof card.title !== "string" || card.title.trim().length === 0)) {
|
|
6856
|
+
throw new Error(
|
|
6857
|
+
`Invalid ${label} card at index ${index}: title must be a non-empty string if provided`
|
|
6858
|
+
);
|
|
6859
|
+
}
|
|
6643
6860
|
if (typeof card.bloom_level !== "number" || !Number.isInteger(card.bloom_level) || card.bloom_level < 1 || card.bloom_level > 5) {
|
|
6644
6861
|
throw new Error(
|
|
6645
6862
|
`Invalid ${label} card at index ${index}: bloom_level must be an integer from 1 to 5`
|
|
@@ -6653,6 +6870,7 @@ function parseGeneratedCardArray(responseText, label, limits) {
|
|
|
6653
6870
|
return {
|
|
6654
6871
|
question: card.question,
|
|
6655
6872
|
concept: card.concept,
|
|
6873
|
+
title: card.title ? card.title : void 0,
|
|
6656
6874
|
domain: card.domain,
|
|
6657
6875
|
context: card.context,
|
|
6658
6876
|
bloom_level: card.bloom_level,
|
|
@@ -6703,10 +6921,11 @@ Your task is to analyze curriculum objectives, syllabus requirements, or textboo
|
|
|
6703
6921
|
For each extracted learning card, you MUST generate:
|
|
6704
6922
|
1. "question": A clear, concise active-recall question testing the concept. The question must not reveal the answer itself.
|
|
6705
6923
|
2. "concept": The reference answer, core fact, or target conceptual explanation.
|
|
6706
|
-
3. "
|
|
6707
|
-
4. "
|
|
6708
|
-
5. "
|
|
6709
|
-
6. "
|
|
6924
|
+
3. "title": A short, human-friendly title for the knowledge graph node. Concise name for the concept. No domain prefix. Human-readable, natural language. Support Unicode (umlauts, Chinese, Japanese, Arabic, etc.). Name the core idea as well as possible.
|
|
6925
|
+
4. "domain": The category of the card. Use "${targetCategory}" as the default, but you may refine it if a specific sub-topic is evident.
|
|
6926
|
+
5. "context": The exact sentence or short excerpt from the source text that this card is based on.
|
|
6927
|
+
6. "bloom_level": Initial Bloom cognitive level (1 = Remember, 2 = Understand, 3 = Apply, 4 = Analyze, 5 = Synthesize).
|
|
6928
|
+
7. "symbiosis_mode": ZAM agent symbiosis level: "shadowing" (reading/monitoring), "copilot" (interactive helper), or "autonomy" (autonomous tasks).
|
|
6710
6929
|
|
|
6711
6930
|
Guidelines:
|
|
6712
6931
|
- Break complex requirements into multiple separate, atomic cards.
|
|
@@ -6753,20 +6972,28 @@ JSON Array Output:`;
|
|
|
6753
6972
|
);
|
|
6754
6973
|
return readChatContent(res, "LLM curriculum import");
|
|
6755
6974
|
}
|
|
6756
|
-
async function importCurriculumViaLLM(db, text, targetCategory, sourceUrl) {
|
|
6975
|
+
async function importCurriculumViaLLM(db, text, targetCategory, sourceUrl, options) {
|
|
6757
6976
|
if (text.length > MAX_IMPORT_TEXT_CHARS) {
|
|
6758
6977
|
throw new Error(
|
|
6759
6978
|
`Curriculum text exceeds the ${MAX_IMPORT_TEXT_CHARS.toLocaleString()} character limit`
|
|
6760
6979
|
);
|
|
6761
6980
|
}
|
|
6762
6981
|
const cfg = await getProviderForRole(db, "text");
|
|
6982
|
+
let locale = cfg.locale;
|
|
6983
|
+
const contextName = options?.knowledgeContext || getActiveWorkspaceContext();
|
|
6984
|
+
if (contextName) {
|
|
6985
|
+
const context = await getKnowledgeContextByName(db, contextName);
|
|
6986
|
+
if (context?.language) {
|
|
6987
|
+
locale = normalizeLocale(context.language);
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
6763
6990
|
const endpoint = await resolveUsableTextEndpoint(db);
|
|
6764
|
-
const langName = LANGUAGE_NAMES[
|
|
6991
|
+
const langName = LANGUAGE_NAMES[locale] || "English";
|
|
6765
6992
|
let responseText;
|
|
6766
6993
|
try {
|
|
6767
6994
|
responseText = await requestCurriculumCards(
|
|
6768
6995
|
endpoint,
|
|
6769
|
-
|
|
6996
|
+
locale,
|
|
6770
6997
|
langName,
|
|
6771
6998
|
text,
|
|
6772
6999
|
targetCategory,
|
|
@@ -6783,7 +7010,7 @@ async function importCurriculumViaLLM(db, text, targetCategory, sourceUrl) {
|
|
|
6783
7010
|
try {
|
|
6784
7011
|
chunkResponse = await requestCurriculumCards(
|
|
6785
7012
|
endpoint,
|
|
6786
|
-
|
|
7013
|
+
locale,
|
|
6787
7014
|
langName,
|
|
6788
7015
|
chunk,
|
|
6789
7016
|
targetCategory,
|
|
@@ -7635,6 +7862,122 @@ async function ensureHighQualityQuestion(db, token) {
|
|
|
7635
7862
|
}
|
|
7636
7863
|
return null;
|
|
7637
7864
|
}
|
|
7865
|
+
async function generateTitleViaLLM(db, input8, opts = {}) {
|
|
7866
|
+
const cfg = await getProviderForRole(db, "text");
|
|
7867
|
+
let locale = cfg.locale;
|
|
7868
|
+
const contextName = opts.knowledgeContext || getActiveWorkspaceContext();
|
|
7869
|
+
if (contextName) {
|
|
7870
|
+
const context = await getKnowledgeContextByName(db, contextName);
|
|
7871
|
+
if (context?.language) {
|
|
7872
|
+
locale = normalizeLocale(context.language);
|
|
7873
|
+
}
|
|
7874
|
+
}
|
|
7875
|
+
const endpoint = await resolveUsableTextEndpoint(db);
|
|
7876
|
+
const systemPrompt = `You are an expert at naming knowledge items for a personal knowledge graph.
|
|
7877
|
+
Your task: given a knowledge token's concept (the full reference answer), question, domain, and optional context, produce a short, descriptive, human-friendly TITLE.
|
|
7878
|
+
|
|
7879
|
+
Strict rules from the project ADR:
|
|
7880
|
+
- Concise name for the concept (\u2264 80 chars ideal).
|
|
7881
|
+
- Thoughtful, memorable name \u2014 NOT a definition or the first sentence of the concept.
|
|
7882
|
+
- NEVER echo the domain name (e.g. do not say "Workflow Engine Node Drain" if domain is "workflow-engine"; just "Node Drain Protection").
|
|
7883
|
+
- Prefer a name over spoiling the full concept.
|
|
7884
|
+
- Support Unicode (umlauts, etc.).
|
|
7885
|
+
- Output the title in the same language as the concept/content (e.g., German if the concept is German, English if the concept is English).
|
|
7886
|
+
- Output ONLY the raw title text. No preamble, quotes, explanations, or markdown.
|
|
7887
|
+
|
|
7888
|
+
Example good titles:
|
|
7889
|
+
- "RAG Retrieval Quality Evaluation" (for RAG failure modes focused on retrieval)
|
|
7890
|
+
- "Pythagorean Theorem Converse Proof"
|
|
7891
|
+
- "macOS Applications Directory Layout"
|
|
7892
|
+
- "Chronotype Sleep Preference"`;
|
|
7893
|
+
const userPrompt = `Domain: ${input8.domain}
|
|
7894
|
+
Slug: ${input8.slug}
|
|
7895
|
+
Question: ${input8.question || "(none)"}
|
|
7896
|
+
Concept: ${input8.concept}
|
|
7897
|
+
Context: ${input8.context || "(none)"}
|
|
7898
|
+
|
|
7899
|
+
Title:`;
|
|
7900
|
+
const url = `${endpoint.url}/chat/completions`;
|
|
7901
|
+
const apiKey = endpoint.apiKey;
|
|
7902
|
+
const model = endpoint.model;
|
|
7903
|
+
try {
|
|
7904
|
+
const res = await fetchWithInteractiveTimeout(url, {
|
|
7905
|
+
method: "POST",
|
|
7906
|
+
headers: {
|
|
7907
|
+
"Content-Type": "application/json",
|
|
7908
|
+
Authorization: `Bearer ${apiKey}`
|
|
7909
|
+
},
|
|
7910
|
+
body: JSON.stringify({
|
|
7911
|
+
model,
|
|
7912
|
+
messages: [
|
|
7913
|
+
{ role: "system", content: systemPrompt },
|
|
7914
|
+
{ role: "user", content: userPrompt }
|
|
7915
|
+
],
|
|
7916
|
+
temperature: 0.2,
|
|
7917
|
+
max_tokens: 100
|
|
7918
|
+
}),
|
|
7919
|
+
locale,
|
|
7920
|
+
timeoutMs: opts.timeoutMs,
|
|
7921
|
+
hardTimeoutMs: opts.timeoutMs
|
|
7922
|
+
});
|
|
7923
|
+
const text = await readChatContent(res, "title generation");
|
|
7924
|
+
return {
|
|
7925
|
+
text: text.trim(),
|
|
7926
|
+
model,
|
|
7927
|
+
providerName: endpoint.providerName
|
|
7928
|
+
};
|
|
7929
|
+
} catch (_e) {
|
|
7930
|
+
return {
|
|
7931
|
+
text: input8.concept.split(/[.;]/)[0].trim().substring(0, 80),
|
|
7932
|
+
model: "fallback",
|
|
7933
|
+
providerName: "heuristic"
|
|
7934
|
+
};
|
|
7935
|
+
}
|
|
7936
|
+
}
|
|
7937
|
+
async function repairUmlautsViaLLM(db, input8, opts = {}) {
|
|
7938
|
+
const cfg = await getProviderForRole(db, "text");
|
|
7939
|
+
const endpoint = await resolveUsableTextEndpoint(db);
|
|
7940
|
+
const systemPrompt = `You are an expert editor specializing in German language orthography.
|
|
7941
|
+
Your task: given a text that may contain legacy ASCII-folded German umlauts (like 'ue' instead of '\xFC', 'ae' instead of '\xE4', 'oe' instead of '\xF6', 'Ue' instead of '\xDC', etc.), repair them back to proper German umlauts (\xE4, \xF6, \xFC, \xC4, \xD6, \xDC, \xDF) where appropriate.
|
|
7942
|
+
|
|
7943
|
+
Strict rules:
|
|
7944
|
+
- ONLY change character sequences that are clearly meant to be German umlauts (e.g. change "Ueber" to "\xDCber", "fuer" to "f\xFCr", "moeglich" to "m\xF6glich").
|
|
7945
|
+
- DO NOT change words that are correct in their current form (e.g. do NOT change "nahe" to "n\xE4he", do NOT change English words or technical terms, do NOT change names like "Ueda").
|
|
7946
|
+
- Output ONLY the repaired text. No preamble, no explanation, no markdown formatting.`;
|
|
7947
|
+
const userPrompt = `Text to repair:
|
|
7948
|
+
"""
|
|
7949
|
+
${input8.text}
|
|
7950
|
+
"""
|
|
7951
|
+
|
|
7952
|
+
Repaired text:`;
|
|
7953
|
+
const url = `${endpoint.url}/chat/completions`;
|
|
7954
|
+
const apiKey = endpoint.apiKey;
|
|
7955
|
+
const model = endpoint.model;
|
|
7956
|
+
try {
|
|
7957
|
+
const res = await fetchWithInteractiveTimeout(url, {
|
|
7958
|
+
method: "POST",
|
|
7959
|
+
headers: {
|
|
7960
|
+
"Content-Type": "application/json",
|
|
7961
|
+
Authorization: `Bearer ${apiKey}`
|
|
7962
|
+
},
|
|
7963
|
+
body: JSON.stringify({
|
|
7964
|
+
model,
|
|
7965
|
+
messages: [
|
|
7966
|
+
{ role: "system", content: systemPrompt },
|
|
7967
|
+
{ role: "user", content: userPrompt }
|
|
7968
|
+
],
|
|
7969
|
+
temperature: 0.1
|
|
7970
|
+
}),
|
|
7971
|
+
locale: cfg.locale,
|
|
7972
|
+
timeoutMs: opts.timeoutMs,
|
|
7973
|
+
hardTimeoutMs: opts.timeoutMs
|
|
7974
|
+
});
|
|
7975
|
+
const text = await readChatContent(res, "umlaut repair");
|
|
7976
|
+
return text.trim();
|
|
7977
|
+
} catch (_e) {
|
|
7978
|
+
return input8.text;
|
|
7979
|
+
}
|
|
7980
|
+
}
|
|
7638
7981
|
|
|
7639
7982
|
// src/cli/adapters/source-reader.ts
|
|
7640
7983
|
var MAX_SOURCE_BYTES = 2 * 1024 * 1024;
|
|
@@ -7709,7 +8052,7 @@ async function readWebLink(url) {
|
|
|
7709
8052
|
redirect: "manual",
|
|
7710
8053
|
signal: controller.signal,
|
|
7711
8054
|
headers: {
|
|
7712
|
-
"User-Agent": "ZAM-Content-Studio/0.
|
|
8055
|
+
"User-Agent": "ZAM-Content-Studio/0.8.0"
|
|
7713
8056
|
}
|
|
7714
8057
|
});
|
|
7715
8058
|
if (res.status >= 300 && res.status < 400) {
|
|
@@ -7946,7 +8289,7 @@ function cleanHtmlText(html) {
|
|
|
7946
8289
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
7947
8290
|
}
|
|
7948
8291
|
function normalizeForComparison(str) {
|
|
7949
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8292
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
7950
8293
|
}
|
|
7951
8294
|
|
|
7952
8295
|
// src/cli/curriculum/providers/bildungsplan-bw/manifest.ts
|
|
@@ -8117,7 +8460,7 @@ function cleanHtmlText2(html) {
|
|
|
8117
8460
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
8118
8461
|
}
|
|
8119
8462
|
function normalizeForComparison2(str) {
|
|
8120
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8463
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8121
8464
|
}
|
|
8122
8465
|
|
|
8123
8466
|
// src/cli/curriculum/providers/bildungsplan-hamburg/manifest.ts
|
|
@@ -8275,7 +8618,7 @@ function cleanHtmlText3(html) {
|
|
|
8275
8618
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
8276
8619
|
}
|
|
8277
8620
|
function normalizeForComparison3(str) {
|
|
8278
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8621
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8279
8622
|
}
|
|
8280
8623
|
|
|
8281
8624
|
// src/cli/curriculum/providers/fachanforderungen-sh/manifest.ts
|
|
@@ -8432,7 +8775,7 @@ function cleanHtmlText4(html) {
|
|
|
8432
8775
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
8433
8776
|
}
|
|
8434
8777
|
function normalizeForComparison4(str) {
|
|
8435
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8778
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8436
8779
|
}
|
|
8437
8780
|
|
|
8438
8781
|
// src/cli/curriculum/providers/kerncurriculum-hessen/manifest.ts
|
|
@@ -8626,7 +8969,7 @@ function cleanHtmlText5(html) {
|
|
|
8626
8969
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
8627
8970
|
}
|
|
8628
8971
|
function normalizeForComparison5(str) {
|
|
8629
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8972
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8630
8973
|
}
|
|
8631
8974
|
|
|
8632
8975
|
// src/cli/curriculum/providers/kerncurriculum-niedersachsen/manifest.ts
|
|
@@ -8798,7 +9141,7 @@ function cleanHtmlText6(html) {
|
|
|
8798
9141
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
8799
9142
|
}
|
|
8800
9143
|
function normalizeForComparison6(str) {
|
|
8801
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9144
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8802
9145
|
}
|
|
8803
9146
|
|
|
8804
9147
|
// src/cli/curriculum/providers/kernlehrplan-nrw/manifest.ts
|
|
@@ -9013,7 +9356,7 @@ function cleanHtmlText7(html) {
|
|
|
9013
9356
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9014
9357
|
}
|
|
9015
9358
|
function normalizeForComparison7(str) {
|
|
9016
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9359
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9017
9360
|
}
|
|
9018
9361
|
|
|
9019
9362
|
// src/cli/curriculum/providers/lehrplaene-rp/manifest.ts
|
|
@@ -9170,7 +9513,7 @@ function cleanHtmlText8(html) {
|
|
|
9170
9513
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9171
9514
|
}
|
|
9172
9515
|
function normalizeForComparison8(str) {
|
|
9173
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9516
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9174
9517
|
}
|
|
9175
9518
|
|
|
9176
9519
|
// src/cli/curriculum/providers/lehrplan-saarland/manifest.ts
|
|
@@ -9327,7 +9670,7 @@ function cleanHtmlText9(html) {
|
|
|
9327
9670
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9328
9671
|
}
|
|
9329
9672
|
function normalizeForComparison9(str) {
|
|
9330
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9673
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9331
9674
|
}
|
|
9332
9675
|
|
|
9333
9676
|
// src/cli/curriculum/providers/lehrplan-sachsen/manifest.ts
|
|
@@ -9512,7 +9855,7 @@ function cleanHtmlText10(html) {
|
|
|
9512
9855
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9513
9856
|
}
|
|
9514
9857
|
function normalizeForComparison10(str) {
|
|
9515
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9858
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9516
9859
|
}
|
|
9517
9860
|
|
|
9518
9861
|
// src/cli/curriculum/providers/lehrplan-thueringen/manifest.ts
|
|
@@ -9667,7 +10010,7 @@ function cleanHtmlText11(html) {
|
|
|
9667
10010
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9668
10011
|
}
|
|
9669
10012
|
function normalizeForComparison11(str) {
|
|
9670
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10013
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9671
10014
|
}
|
|
9672
10015
|
|
|
9673
10016
|
// src/cli/curriculum/providers/lehrplanplus-bayern/manifest.ts
|
|
@@ -9911,7 +10254,7 @@ function cleanHtmlText12(html) {
|
|
|
9911
10254
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9912
10255
|
}
|
|
9913
10256
|
function normalizeForComparison12(str) {
|
|
9914
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10257
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9915
10258
|
}
|
|
9916
10259
|
|
|
9917
10260
|
// src/cli/curriculum/providers/rahmenlehrplan-berlin-brandenburg/manifest.ts
|
|
@@ -10096,7 +10439,7 @@ function cleanHtmlText13(html) {
|
|
|
10096
10439
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
10097
10440
|
}
|
|
10098
10441
|
function normalizeForComparison13(str) {
|
|
10099
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10442
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10100
10443
|
}
|
|
10101
10444
|
|
|
10102
10445
|
// src/cli/curriculum/providers/rahmenplan-mv/manifest.ts
|
|
@@ -10253,7 +10596,7 @@ function cleanHtmlText14(html) {
|
|
|
10253
10596
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
10254
10597
|
}
|
|
10255
10598
|
function normalizeForComparison14(str) {
|
|
10256
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10599
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10257
10600
|
}
|
|
10258
10601
|
|
|
10259
10602
|
// src/cli/curriculum/providers/rahmenrichtlinien-st/manifest.ts
|
|
@@ -10408,7 +10751,7 @@ function cleanHtmlText15(html) {
|
|
|
10408
10751
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
10409
10752
|
}
|
|
10410
10753
|
function normalizeForComparison15(str) {
|
|
10411
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10754
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10412
10755
|
}
|
|
10413
10756
|
|
|
10414
10757
|
// src/cli/curriculum/registry.ts
|
|
@@ -10433,6 +10776,25 @@ function getCurriculumProvider(id) {
|
|
|
10433
10776
|
return CURRICULUM_PROVIDERS.find((provider) => provider.id === id);
|
|
10434
10777
|
}
|
|
10435
10778
|
|
|
10779
|
+
// src/cli/knowledge-contexts.ts
|
|
10780
|
+
async function resolveOperationKnowledgeContexts(db, requestedNames) {
|
|
10781
|
+
const explicitNames = [
|
|
10782
|
+
...new Set(requestedNames.map((name) => name.trim()).filter(Boolean))
|
|
10783
|
+
];
|
|
10784
|
+
const activeDefault = getActiveWorkspaceContext();
|
|
10785
|
+
const selectedNames = explicitNames.length > 0 ? explicitNames : activeDefault ? [activeDefault] : [];
|
|
10786
|
+
const contexts = [];
|
|
10787
|
+
for (const name of selectedNames) {
|
|
10788
|
+
const context = await getKnowledgeContextByName(db, name);
|
|
10789
|
+
if (!context) {
|
|
10790
|
+
const subject = explicitNames.length > 0 ? "Knowledge" : "Active knowledge";
|
|
10791
|
+
throw new Error(`${subject} context not found: ${name}`);
|
|
10792
|
+
}
|
|
10793
|
+
contexts.push(context);
|
|
10794
|
+
}
|
|
10795
|
+
return contexts;
|
|
10796
|
+
}
|
|
10797
|
+
|
|
10436
10798
|
// src/cli/llm/embedder.ts
|
|
10437
10799
|
var CANONICAL_EMBEDDING_MODEL_ID = "embeddinggemma-300m";
|
|
10438
10800
|
var EMBEDDINGGEMMA_ALIASES = /* @__PURE__ */ new Set([
|
|
@@ -10606,7 +10968,8 @@ async function findPossibleDuplicates(db, candidate, embed = embedQuery) {
|
|
|
10606
10968
|
const queryText = embeddingContentForToken({
|
|
10607
10969
|
concept: candidate.concept,
|
|
10608
10970
|
question: candidate.question ?? null,
|
|
10609
|
-
domain: candidate.domain ?? ""
|
|
10971
|
+
domain: candidate.domain ?? "",
|
|
10972
|
+
title: candidate.title ?? null
|
|
10610
10973
|
});
|
|
10611
10974
|
const q2 = await embed(db, queryText);
|
|
10612
10975
|
if (!q2) {
|
|
@@ -10637,6 +11000,7 @@ async function findPossibleDuplicates(db, candidate, embed = embedQuery) {
|
|
|
10637
11000
|
results.push({
|
|
10638
11001
|
slug: hit.slug,
|
|
10639
11002
|
concept: hit.concept,
|
|
11003
|
+
title: hit.title,
|
|
10640
11004
|
similarity: hit.similarity
|
|
10641
11005
|
});
|
|
10642
11006
|
}
|
|
@@ -11745,6 +12109,20 @@ function jsonError(message) {
|
|
|
11745
12109
|
console.log(JSON.stringify({ error: msg }, null, 2));
|
|
11746
12110
|
process.exit(1);
|
|
11747
12111
|
}
|
|
12112
|
+
function parseKnowledgeContextNames(value) {
|
|
12113
|
+
if (value == null) return [];
|
|
12114
|
+
if (!Array.isArray(value) || value.some((name) => typeof name !== "string" || !name.trim())) {
|
|
12115
|
+
jsonError("knowledgeContexts must be an array of non-empty context names");
|
|
12116
|
+
}
|
|
12117
|
+
return [...new Set(value.map((name) => name.trim()))];
|
|
12118
|
+
}
|
|
12119
|
+
async function resolveKnowledgeContexts(db, names) {
|
|
12120
|
+
try {
|
|
12121
|
+
return await resolveOperationKnowledgeContexts(db, names);
|
|
12122
|
+
} catch (error) {
|
|
12123
|
+
jsonError(error.message);
|
|
12124
|
+
}
|
|
12125
|
+
}
|
|
11748
12126
|
function parseNonNegativeIntegerOption(name, value) {
|
|
11749
12127
|
const parsed = Number(value);
|
|
11750
12128
|
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
@@ -11798,16 +12176,23 @@ function parseTokenUpdates(opts) {
|
|
|
11798
12176
|
var bridgeCommand = new Command2("bridge").description(
|
|
11799
12177
|
"Machine-readable JSON protocol for AI integration"
|
|
11800
12178
|
);
|
|
11801
|
-
bridgeCommand.command("check-due").description("Check due cards for a user (JSON)").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").action(async (opts) => {
|
|
12179
|
+
bridgeCommand.command("check-due").description("Check due cards for a user (JSON)").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").option("--knowledge-context <context>", "Filter by knowledge context").action(async (opts) => {
|
|
11802
12180
|
await withDb2(async (db) => {
|
|
11803
12181
|
const userId = await resolveUser(opts, db, { json: true });
|
|
11804
|
-
const dueCards = await getDueCards(
|
|
12182
|
+
const dueCards = await getDueCards(
|
|
12183
|
+
db,
|
|
12184
|
+
userId,
|
|
12185
|
+
void 0,
|
|
12186
|
+
opts.domain,
|
|
12187
|
+
opts.knowledgeContext
|
|
12188
|
+
);
|
|
11805
12189
|
const domains = [
|
|
11806
12190
|
...new Set(dueCards.map((c) => c.domain).filter(Boolean))
|
|
11807
12191
|
].sort();
|
|
11808
12192
|
jsonOut2({
|
|
11809
12193
|
userId,
|
|
11810
12194
|
domain: opts.domain ?? null,
|
|
12195
|
+
knowledgeContext: opts.knowledgeContext ?? null,
|
|
11811
12196
|
dueCount: dueCards.length,
|
|
11812
12197
|
domains,
|
|
11813
12198
|
cards: dueCards.map((c) => ({
|
|
@@ -12068,13 +12453,14 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
|
|
|
12068
12453
|
bridgeCommand.command("get-review").description("Get next review card with prompt (JSON)").option("--user <id>", "User ID (default: whoami)").option("--no-resolve", "Skip resolving the token's source_link into context").option(
|
|
12069
12454
|
"--no-dynamic-question",
|
|
12070
12455
|
"Use the stored question without generating a fresh LLM question"
|
|
12071
|
-
).action(async (opts) => {
|
|
12456
|
+
).option("--knowledge-context <context>", "Filter cards by knowledge context").action(async (opts) => {
|
|
12072
12457
|
await withDb2(async (db) => {
|
|
12073
12458
|
const userId = await resolveUser(opts, db, { json: true });
|
|
12074
12459
|
const queue = await buildReviewQueue(db, {
|
|
12075
12460
|
userId,
|
|
12076
12461
|
maxReviews: 1,
|
|
12077
|
-
maxNew: 1
|
|
12462
|
+
maxNew: 1,
|
|
12463
|
+
knowledgeContext: opts.knowledgeContext || void 0
|
|
12078
12464
|
});
|
|
12079
12465
|
if (queue.items.length === 0) {
|
|
12080
12466
|
jsonOut2({
|
|
@@ -12129,7 +12515,10 @@ bridgeCommand.command("get-review").description("Get next review card with promp
|
|
|
12129
12515
|
resolvedContext = null;
|
|
12130
12516
|
}
|
|
12131
12517
|
}
|
|
12132
|
-
const fullQueue = await buildReviewQueue(db, {
|
|
12518
|
+
const fullQueue = await buildReviewQueue(db, {
|
|
12519
|
+
userId,
|
|
12520
|
+
knowledgeContext: opts.knowledgeContext || void 0
|
|
12521
|
+
});
|
|
12133
12522
|
jsonOut2({
|
|
12134
12523
|
userId,
|
|
12135
12524
|
hasReview: true,
|
|
@@ -12400,10 +12789,16 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
12400
12789
|
const possibleDuplicates = await findPossibleDuplicates(db, {
|
|
12401
12790
|
concept: data?.concept,
|
|
12402
12791
|
question: data?.question ?? null,
|
|
12403
|
-
domain: data?.domain
|
|
12792
|
+
domain: data?.domain,
|
|
12793
|
+
title: data?.title ?? null
|
|
12404
12794
|
});
|
|
12795
|
+
const ctxNames = parseKnowledgeContextNames(
|
|
12796
|
+
data?.knowledgeContexts ?? data?.knowledge_contexts
|
|
12797
|
+
);
|
|
12798
|
+
const assignedContexts = await resolveKnowledgeContexts(db, ctxNames);
|
|
12405
12799
|
const token = await createToken(db, {
|
|
12406
12800
|
slug: data?.slug,
|
|
12801
|
+
title: data?.title,
|
|
12407
12802
|
concept: data?.concept,
|
|
12408
12803
|
domain: data?.domain,
|
|
12409
12804
|
bloom_level: data?.bloom_level ?? 1,
|
|
@@ -12412,6 +12807,9 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
12412
12807
|
source_link: data?.source_link ?? null,
|
|
12413
12808
|
question: data?.question ?? null
|
|
12414
12809
|
});
|
|
12810
|
+
for (const context of assignedContexts) {
|
|
12811
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
12812
|
+
}
|
|
12415
12813
|
const card = await ensureCard(db, token.id, userId);
|
|
12416
12814
|
try {
|
|
12417
12815
|
await ensureTokenEmbeddings(db, { limit: 8 });
|
|
@@ -12419,7 +12817,14 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
12419
12817
|
}
|
|
12420
12818
|
jsonOut2({
|
|
12421
12819
|
success: true,
|
|
12422
|
-
token
|
|
12820
|
+
token: {
|
|
12821
|
+
...token,
|
|
12822
|
+
knowledgeContexts: assignedContexts.map((c) => ({
|
|
12823
|
+
name: c.name,
|
|
12824
|
+
label: c.label,
|
|
12825
|
+
language: c.language
|
|
12826
|
+
}))
|
|
12827
|
+
},
|
|
12423
12828
|
card: {
|
|
12424
12829
|
id: card.id,
|
|
12425
12830
|
tokenId: card.token_id,
|
|
@@ -12479,15 +12884,34 @@ bridgeCommand.command("relevant-tokens").description("Find tokens relevant to a
|
|
|
12479
12884
|
limit
|
|
12480
12885
|
});
|
|
12481
12886
|
const tokens = [];
|
|
12887
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
12888
|
+
if (results.length > 0) {
|
|
12889
|
+
const ids = results.map((t2) => t2.id);
|
|
12890
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
12891
|
+
const mappings = await db.prepare(
|
|
12892
|
+
`SELECT tc.token_id, c.name, c.label, c.language
|
|
12893
|
+
FROM token_contexts tc
|
|
12894
|
+
INNER JOIN contexts c ON c.id = tc.context_id
|
|
12895
|
+
WHERE tc.token_id IN (${placeholders})`
|
|
12896
|
+
).all(...ids);
|
|
12897
|
+
for (const m of mappings) {
|
|
12898
|
+
const list = contextMap.get(m.token_id) ?? [];
|
|
12899
|
+
list.push({ name: m.name, label: m.label, language: m.language });
|
|
12900
|
+
contextMap.set(m.token_id, list);
|
|
12901
|
+
}
|
|
12902
|
+
}
|
|
12482
12903
|
for (const t2 of results) {
|
|
12483
12904
|
const card = await getCard(db, t2.id, userId);
|
|
12484
12905
|
tokens.push({
|
|
12485
12906
|
slug: t2.slug,
|
|
12907
|
+
title: t2.title,
|
|
12908
|
+
display_title: getDisplayTitle(t2),
|
|
12486
12909
|
concept: t2.concept,
|
|
12487
12910
|
domain: t2.domain,
|
|
12488
12911
|
bloom_level: t2.bloom_level,
|
|
12489
12912
|
score: t2.score,
|
|
12490
12913
|
similarity: t2.similarity,
|
|
12914
|
+
knowledgeContexts: contextMap.get(t2.id) ?? [],
|
|
12491
12915
|
card: card ? {
|
|
12492
12916
|
state: card.state,
|
|
12493
12917
|
due_at: card.due_at,
|
|
@@ -12545,7 +12969,8 @@ bridgeCommand.command("suggest-foundations").description("Propose existing token
|
|
|
12545
12969
|
queryText = embeddingContentForToken({
|
|
12546
12970
|
concept: data.concept,
|
|
12547
12971
|
question: typeof data.question === "string" ? data.question : null,
|
|
12548
|
-
domain: typeof data.domain === "string" ? data.domain : ""
|
|
12972
|
+
domain: typeof data.domain === "string" ? data.domain : "",
|
|
12973
|
+
title: typeof data.title === "string" ? data.title : null
|
|
12549
12974
|
});
|
|
12550
12975
|
if (data.bloom_level !== void 0) {
|
|
12551
12976
|
if (typeof data.bloom_level !== "number" || !Number.isInteger(data.bloom_level) || data.bloom_level < 1 || data.bloom_level > 5) {
|
|
@@ -13777,12 +14202,20 @@ bridgeCommand.command("list-tokens").description(
|
|
|
13777
14202
|
).option(
|
|
13778
14203
|
"--user <id>",
|
|
13779
14204
|
"User ID (default: whoami) \u2014 when provided, includes personal card info"
|
|
13780
|
-
).option("--domain <domain>", "Filter by domain").
|
|
14205
|
+
).option("--domain <domain>", "Filter by exact domain").option(
|
|
14206
|
+
"--domain-prefix <prefix>",
|
|
14207
|
+
"Filter by domain prefix (e.g. company-team) \u2014 uses / separator for hierarchy"
|
|
14208
|
+
).option("--knowledge-context <context>", "Filter by knowledge context").action(async (opts) => {
|
|
13781
14209
|
await withDb2(async (db) => {
|
|
13782
14210
|
const userId = opts.user ? await resolveUser(opts, db, { json: true }) : void 0;
|
|
14211
|
+
const listOpts = {};
|
|
14212
|
+
if (opts.domain) listOpts.domain = opts.domain;
|
|
14213
|
+
if (opts.domainPrefix) listOpts.domainPrefix = opts.domainPrefix;
|
|
14214
|
+
if (opts.knowledgeContext)
|
|
14215
|
+
listOpts.knowledgeContext = opts.knowledgeContext;
|
|
13783
14216
|
const tokens = await listTokens(
|
|
13784
14217
|
db,
|
|
13785
|
-
|
|
14218
|
+
Object.keys(listOpts).length ? listOpts : void 0
|
|
13786
14219
|
);
|
|
13787
14220
|
const cardMap = /* @__PURE__ */ new Map();
|
|
13788
14221
|
if (userId && tokens.length > 0) {
|
|
@@ -13794,14 +14227,33 @@ bridgeCommand.command("list-tokens").description(
|
|
|
13794
14227
|
).all(...ids, userId);
|
|
13795
14228
|
for (const c of cards) cardMap.set(c.token_id, c);
|
|
13796
14229
|
}
|
|
14230
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
14231
|
+
if (tokens.length > 0) {
|
|
14232
|
+
const ids = tokens.map((t2) => t2.id);
|
|
14233
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
14234
|
+
const mappings = await db.prepare(
|
|
14235
|
+
`SELECT tc.token_id, c.name, c.label, c.language
|
|
14236
|
+
FROM token_contexts tc
|
|
14237
|
+
INNER JOIN contexts c ON c.id = tc.context_id
|
|
14238
|
+
WHERE tc.token_id IN (${placeholders})`
|
|
14239
|
+
).all(...ids);
|
|
14240
|
+
for (const m of mappings) {
|
|
14241
|
+
const list = contextMap.get(m.token_id) ?? [];
|
|
14242
|
+
list.push({ name: m.name, label: m.label, language: m.language });
|
|
14243
|
+
contextMap.set(m.token_id, list);
|
|
14244
|
+
}
|
|
14245
|
+
}
|
|
13797
14246
|
const out = tokens.map((t2) => {
|
|
13798
14247
|
const c = cardMap.get(t2.id);
|
|
13799
14248
|
return {
|
|
13800
14249
|
id: t2.id,
|
|
13801
14250
|
slug: t2.slug,
|
|
14251
|
+
title: t2.title,
|
|
14252
|
+
display_title: getDisplayTitle(t2),
|
|
13802
14253
|
concept: t2.concept,
|
|
13803
14254
|
domain: t2.domain,
|
|
13804
14255
|
bloomLevel: t2.bloom_level,
|
|
14256
|
+
knowledgeContexts: contextMap.get(t2.id) ?? [],
|
|
13805
14257
|
card: c ? {
|
|
13806
14258
|
state: c.state,
|
|
13807
14259
|
reps: c.reps,
|
|
@@ -13829,12 +14281,32 @@ bridgeCommand.command("get-neighborhood").description(
|
|
|
13829
14281
|
jsonError(`Token not found: ${opts.focus}`);
|
|
13830
14282
|
}
|
|
13831
14283
|
const nb = await getTokenNeighborhood(db, token.id, userId);
|
|
14284
|
+
const allTokens = [nb.center, ...nb.prerequisites, ...nb.dependents];
|
|
14285
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
14286
|
+
if (allTokens.length > 0) {
|
|
14287
|
+
const ids = allTokens.map((t2) => t2.id);
|
|
14288
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
14289
|
+
const mappings = await db.prepare(
|
|
14290
|
+
`SELECT tc.token_id, c.name, c.label, c.language
|
|
14291
|
+
FROM token_contexts tc
|
|
14292
|
+
INNER JOIN contexts c ON c.id = tc.context_id
|
|
14293
|
+
WHERE tc.token_id IN (${placeholders})`
|
|
14294
|
+
).all(...ids);
|
|
14295
|
+
for (const m of mappings) {
|
|
14296
|
+
const list = contextMap.get(m.token_id) ?? [];
|
|
14297
|
+
list.push({ name: m.name, label: m.label, language: m.language });
|
|
14298
|
+
contextMap.set(m.token_id, list);
|
|
14299
|
+
}
|
|
14300
|
+
}
|
|
13832
14301
|
const mapToken = (nt) => ({
|
|
13833
14302
|
id: nt.id,
|
|
13834
14303
|
slug: nt.slug,
|
|
14304
|
+
title: nt.title,
|
|
14305
|
+
display_title: getDisplayTitle(nt),
|
|
13835
14306
|
concept: nt.concept,
|
|
13836
14307
|
domain: nt.domain,
|
|
13837
14308
|
bloomLevel: nt.bloom_level,
|
|
14309
|
+
knowledgeContexts: contextMap.get(nt.id) ?? [],
|
|
13838
14310
|
card: nt.card ? {
|
|
13839
14311
|
state: nt.card.state,
|
|
13840
14312
|
reps: nt.card.reps,
|
|
@@ -13853,23 +14325,62 @@ bridgeCommand.command("get-neighborhood").description(
|
|
|
13853
14325
|
});
|
|
13854
14326
|
});
|
|
13855
14327
|
});
|
|
13856
|
-
bridgeCommand.command("personal-card-list").description("List and search personal learning cards (JSON)").option("--user <id>", "User ID (default: whoami)").option("--query <query>", "Text search query").option("--domain <domain>", "Filter by category/domain").action(async (opts) => {
|
|
14328
|
+
bridgeCommand.command("personal-card-list").description("List and search personal learning cards (JSON)").option("--user <id>", "User ID (default: whoami)").option("--query <query>", "Text search query").option("--domain <domain>", "Filter by category/domain").option("--knowledge-context <context>", "Filter by knowledge context").action(async (opts) => {
|
|
13857
14329
|
await withDb2(async (db) => {
|
|
13858
14330
|
const userId = await resolveUser(opts, db, { json: true });
|
|
13859
14331
|
const cards = await listPersonalCards(db, userId, {
|
|
13860
14332
|
query: opts.query,
|
|
13861
|
-
domain: opts.domain
|
|
14333
|
+
domain: opts.domain,
|
|
14334
|
+
knowledgeContext: opts.knowledgeContext
|
|
14335
|
+
});
|
|
14336
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
14337
|
+
if (cards.length > 0) {
|
|
14338
|
+
const tokenIds = cards.map((card) => card.tokenId);
|
|
14339
|
+
const placeholders = tokenIds.map(() => "?").join(",");
|
|
14340
|
+
const mappings = await db.prepare(
|
|
14341
|
+
`SELECT tc.token_id, c.name, c.label, c.language
|
|
14342
|
+
FROM token_contexts tc
|
|
14343
|
+
INNER JOIN contexts c ON c.id = tc.context_id
|
|
14344
|
+
WHERE tc.token_id IN (${placeholders})
|
|
14345
|
+
ORDER BY c.name`
|
|
14346
|
+
).all(...tokenIds);
|
|
14347
|
+
for (const mapping of mappings) {
|
|
14348
|
+
const contexts = contextMap.get(mapping.token_id) ?? [];
|
|
14349
|
+
contexts.push({
|
|
14350
|
+
name: mapping.name,
|
|
14351
|
+
label: mapping.label,
|
|
14352
|
+
language: mapping.language
|
|
14353
|
+
});
|
|
14354
|
+
contextMap.set(mapping.token_id, contexts);
|
|
14355
|
+
}
|
|
14356
|
+
}
|
|
14357
|
+
jsonOut2({
|
|
14358
|
+
cards: cards.map((card) => ({
|
|
14359
|
+
...card,
|
|
14360
|
+
knowledgeContexts: contextMap.get(card.tokenId) ?? []
|
|
14361
|
+
}))
|
|
13862
14362
|
});
|
|
13863
|
-
jsonOut2({ cards });
|
|
13864
14363
|
});
|
|
13865
14364
|
});
|
|
13866
|
-
bridgeCommand.command("personal-card-create").description("Atomically create a token and its personal card (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--concept <concept>", "Concept description / answer").option("--domain <domain>", "Knowledge category / domain", "").option("--question <question>", "Question prompt for recall").option("--source-link <link>", "Source file path or reference URL").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option(
|
|
14365
|
+
bridgeCommand.command("personal-card-create").description("Atomically create a token and its personal card (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--concept <concept>", "Concept description / answer").option("--title <title>", "Human-friendly display title").option("--domain <domain>", "Knowledge category / domain", "").option("--question <question>", "Question prompt for recall").option("--source-link <link>", "Source file path or reference URL").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option(
|
|
13867
14366
|
"--mode <mode>",
|
|
13868
14367
|
"Symbiosis mode: shadowing | copilot | autonomy | none",
|
|
13869
14368
|
"none"
|
|
13870
|
-
).option("--context <context>", "Context description", "").
|
|
14369
|
+
).option("--context <context>", "Context description", "").option(
|
|
14370
|
+
"--knowledge-context <context>",
|
|
14371
|
+
"Assign token to a knowledge context (repeatable)",
|
|
14372
|
+
(val, memo) => {
|
|
14373
|
+
memo.push(val);
|
|
14374
|
+
return memo;
|
|
14375
|
+
},
|
|
14376
|
+
[]
|
|
14377
|
+
).action(async (opts) => {
|
|
13871
14378
|
await withDb2(async (db) => {
|
|
13872
14379
|
const userId = await resolveUser(opts, db, { json: true });
|
|
14380
|
+
const contextNames = parseKnowledgeContextNames(
|
|
14381
|
+
opts.knowledgeContext || []
|
|
14382
|
+
);
|
|
14383
|
+
const contexts = await resolveKnowledgeContexts(db, contextNames);
|
|
13873
14384
|
const bloom = Number(opts.bloom);
|
|
13874
14385
|
if (bloom < 1 || bloom > 5) {
|
|
13875
14386
|
jsonError("bloom must be between 1 and 5");
|
|
@@ -13891,6 +14402,7 @@ bridgeCommand.command("personal-card-create").description("Atomically create a t
|
|
|
13891
14402
|
const { token, card } = await db.transaction(async (tx) => {
|
|
13892
14403
|
const createdToken = await createToken(tx, {
|
|
13893
14404
|
slug,
|
|
14405
|
+
title: opts.title,
|
|
13894
14406
|
concept: opts.concept,
|
|
13895
14407
|
domain: opts.domain,
|
|
13896
14408
|
bloom_level: bloom,
|
|
@@ -13899,6 +14411,9 @@ bridgeCommand.command("personal-card-create").description("Atomically create a t
|
|
|
13899
14411
|
source_link: opts.sourceLink || null,
|
|
13900
14412
|
question
|
|
13901
14413
|
});
|
|
14414
|
+
for (const context of contexts) {
|
|
14415
|
+
await assignTokenToContext(tx, createdToken.id, context.id);
|
|
14416
|
+
}
|
|
13902
14417
|
const createdCard = await ensureCard(tx, createdToken.id, userId);
|
|
13903
14418
|
return { token: createdToken, card: createdCard };
|
|
13904
14419
|
});
|
|
@@ -13907,6 +14422,8 @@ bridgeCommand.command("personal-card-create").description("Atomically create a t
|
|
|
13907
14422
|
token: {
|
|
13908
14423
|
id: token.id,
|
|
13909
14424
|
slug: token.slug,
|
|
14425
|
+
title: token.title,
|
|
14426
|
+
display_title: getDisplayTitle(token),
|
|
13910
14427
|
concept: token.concept,
|
|
13911
14428
|
domain: token.domain,
|
|
13912
14429
|
bloomLevel: token.bloom_level,
|
|
@@ -13915,7 +14432,12 @@ bridgeCommand.command("personal-card-create").description("Atomically create a t
|
|
|
13915
14432
|
sourceLink: token.source_link,
|
|
13916
14433
|
question: token.question,
|
|
13917
14434
|
createdAt: token.created_at,
|
|
13918
|
-
updatedAt: token.updated_at
|
|
14435
|
+
updatedAt: token.updated_at,
|
|
14436
|
+
knowledgeContexts: contexts.map((context) => ({
|
|
14437
|
+
name: context.name,
|
|
14438
|
+
label: context.label,
|
|
14439
|
+
language: context.language
|
|
14440
|
+
}))
|
|
13919
14441
|
},
|
|
13920
14442
|
card: {
|
|
13921
14443
|
id: card.id,
|
|
@@ -13928,13 +14450,14 @@ bridgeCommand.command("personal-card-create").description("Atomically create a t
|
|
|
13928
14450
|
});
|
|
13929
14451
|
});
|
|
13930
14452
|
});
|
|
13931
|
-
bridgeCommand.command("personal-card-update").description("Update the mutable token fields of a personal card (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--slug <slug>", "Token slug to update").option("--concept <concept>", "Updated concept text").option("--domain <domain>", "Updated domain / category").option("--bloom <level>", "Updated Bloom taxonomy level (1-5)").option("--context <context>", "Updated context").option(
|
|
14453
|
+
bridgeCommand.command("personal-card-update").description("Update the mutable token fields of a personal card (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--slug <slug>", "Token slug to update").option("--title <title>", "Updated display title").option("--concept <concept>", "Updated concept text").option("--domain <domain>", "Updated domain / category").option("--bloom <level>", "Updated Bloom taxonomy level (1-5)").option("--context <context>", "Updated context").option(
|
|
13932
14454
|
"--mode <mode>",
|
|
13933
14455
|
"Updated symbiosis mode: shadowing | copilot | autonomy | none"
|
|
13934
14456
|
).option("--source-link <link>", "Updated source link").option("--question <question>", "Updated question text").action(async (opts) => {
|
|
13935
14457
|
await withDb2(async (db) => {
|
|
13936
14458
|
const userId = await resolveUser(opts, db, { json: true });
|
|
13937
14459
|
const updates = {};
|
|
14460
|
+
if (opts.title !== void 0) updates.title = opts.title;
|
|
13938
14461
|
if (opts.concept !== void 0) updates.concept = opts.concept;
|
|
13939
14462
|
if (opts.domain !== void 0) updates.domain = opts.domain;
|
|
13940
14463
|
if (opts.bloom !== void 0) {
|
|
@@ -13976,6 +14499,8 @@ bridgeCommand.command("personal-card-update").description("Update the mutable to
|
|
|
13976
14499
|
token: {
|
|
13977
14500
|
id: token.id,
|
|
13978
14501
|
slug: token.slug,
|
|
14502
|
+
title: token.title,
|
|
14503
|
+
display_title: getDisplayTitle(token),
|
|
13979
14504
|
concept: token.concept,
|
|
13980
14505
|
domain: token.domain,
|
|
13981
14506
|
bloomLevel: token.bloom_level,
|
|
@@ -14008,7 +14533,12 @@ bridgeCommand.command("personal-card-remove").description(
|
|
|
14008
14533
|
success: true,
|
|
14009
14534
|
preview: true,
|
|
14010
14535
|
requiresConfirmation: true,
|
|
14011
|
-
token: {
|
|
14536
|
+
token: {
|
|
14537
|
+
id: token.id,
|
|
14538
|
+
slug: token.slug,
|
|
14539
|
+
title: token.title,
|
|
14540
|
+
display_title: getDisplayTitle(token)
|
|
14541
|
+
},
|
|
14012
14542
|
impact
|
|
14013
14543
|
});
|
|
14014
14544
|
return;
|
|
@@ -14037,7 +14567,12 @@ bridgeCommand.command("personal-card-delete").description("Hard-delete a token a
|
|
|
14037
14567
|
success: true,
|
|
14038
14568
|
preview: true,
|
|
14039
14569
|
requiresConfirmation: true,
|
|
14040
|
-
token: {
|
|
14570
|
+
token: {
|
|
14571
|
+
id: token.id,
|
|
14572
|
+
slug: token.slug,
|
|
14573
|
+
title: token.title,
|
|
14574
|
+
display_title: getDisplayTitle(token)
|
|
14575
|
+
},
|
|
14041
14576
|
impact
|
|
14042
14577
|
});
|
|
14043
14578
|
return;
|
|
@@ -14056,14 +14591,28 @@ bridgeCommand.command("personal-card-delete").description("Hard-delete a token a
|
|
|
14056
14591
|
bridgeCommand.command("personal-card-import-curriculum").description("Parse curriculum text using LLM and import cards (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--text <text>", "Curriculum syllabus/content text").requiredOption(
|
|
14057
14592
|
"--domain <domain>",
|
|
14058
14593
|
"Default category/domain for imported cards"
|
|
14059
|
-
).option("--source <url>", "Provenance source link or URL").option("--preview", "Return parsed cards without saving them").
|
|
14594
|
+
).option("--source <url>", "Provenance source link or URL").option("--preview", "Return parsed cards without saving them").option(
|
|
14595
|
+
"--knowledge-context <context>",
|
|
14596
|
+
"Assign imported tokens to a knowledge context (repeatable)",
|
|
14597
|
+
(val, memo) => {
|
|
14598
|
+
memo.push(val);
|
|
14599
|
+
return memo;
|
|
14600
|
+
},
|
|
14601
|
+
[]
|
|
14602
|
+
).action(async (opts) => {
|
|
14060
14603
|
await withDb2(async (db) => {
|
|
14061
14604
|
const userId = await resolveUser(opts, db, { json: true });
|
|
14605
|
+
const contextNames = parseKnowledgeContextNames(
|
|
14606
|
+
opts.knowledgeContext || []
|
|
14607
|
+
);
|
|
14608
|
+
const contexts = await resolveKnowledgeContexts(db, contextNames);
|
|
14609
|
+
const firstContext = contexts[0]?.name;
|
|
14062
14610
|
const cards = await importCurriculumViaLLM(
|
|
14063
14611
|
db,
|
|
14064
14612
|
opts.text,
|
|
14065
14613
|
opts.domain,
|
|
14066
|
-
opts.source || null
|
|
14614
|
+
opts.source || null,
|
|
14615
|
+
{ knowledgeContext: firstContext }
|
|
14067
14616
|
);
|
|
14068
14617
|
if (opts.preview) {
|
|
14069
14618
|
jsonOut2({
|
|
@@ -14073,6 +14622,24 @@ bridgeCommand.command("personal-card-import-curriculum").description("Parse curr
|
|
|
14073
14622
|
return;
|
|
14074
14623
|
}
|
|
14075
14624
|
const result = await importCurriculumCards(db, userId, cards);
|
|
14625
|
+
for (const card of cards) {
|
|
14626
|
+
const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
|
|
14627
|
+
const cleanDomain = slugify(card.domain || "");
|
|
14628
|
+
const cleanBase = slugify(baseText);
|
|
14629
|
+
let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
|
|
14630
|
+
if (baseSlug.length > 60) {
|
|
14631
|
+
baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
|
|
14632
|
+
}
|
|
14633
|
+
if (!baseSlug) {
|
|
14634
|
+
baseSlug = "token";
|
|
14635
|
+
}
|
|
14636
|
+
const token = await getTokenBySlug(db, baseSlug);
|
|
14637
|
+
if (token) {
|
|
14638
|
+
for (const context of contexts) {
|
|
14639
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
14640
|
+
}
|
|
14641
|
+
}
|
|
14642
|
+
}
|
|
14076
14643
|
jsonOut2({
|
|
14077
14644
|
success: true,
|
|
14078
14645
|
createdCount: result.createdCount,
|
|
@@ -14211,7 +14778,7 @@ bridgeCommand.command("personal-source-import").description(
|
|
|
14211
14778
|
} else {
|
|
14212
14779
|
throw new Error(`Invalid source type: ${opts.type}`);
|
|
14213
14780
|
}
|
|
14214
|
-
const sourceId =
|
|
14781
|
+
const sourceId = ulid9();
|
|
14215
14782
|
await db.prepare(
|
|
14216
14783
|
`INSERT INTO sources (id, type, uri, content)
|
|
14217
14784
|
VALUES (?, ?, ?, ?)
|
|
@@ -14233,16 +14800,46 @@ bridgeCommand.command("personal-source-confirm-import").description(
|
|
|
14233
14800
|
).option("--user <id>", "User ID (default: whoami)").requiredOption("--sourceId <id>", "Source database ID").requiredOption(
|
|
14234
14801
|
"--proposals <json>",
|
|
14235
14802
|
"JSON string representing proposals array"
|
|
14803
|
+
).option(
|
|
14804
|
+
"--knowledge-context <context>",
|
|
14805
|
+
"Assign confirmed tokens to a knowledge context (repeatable)",
|
|
14806
|
+
(val, memo) => {
|
|
14807
|
+
memo.push(val);
|
|
14808
|
+
return memo;
|
|
14809
|
+
},
|
|
14810
|
+
[]
|
|
14236
14811
|
).action(async (opts) => {
|
|
14237
14812
|
await withDb2(async (db) => {
|
|
14238
14813
|
const userId = await resolveUser(opts, db, { json: true });
|
|
14239
14814
|
const proposals = JSON.parse(opts.proposals);
|
|
14815
|
+
const contextNames = parseKnowledgeContextNames(
|
|
14816
|
+
opts.knowledgeContext || []
|
|
14817
|
+
);
|
|
14818
|
+
const contexts = await resolveKnowledgeContexts(db, contextNames);
|
|
14240
14819
|
const result = await confirmSourceImport(
|
|
14241
14820
|
db,
|
|
14242
14821
|
userId,
|
|
14243
14822
|
opts.sourceId,
|
|
14244
14823
|
proposals
|
|
14245
14824
|
);
|
|
14825
|
+
for (const p of proposals) {
|
|
14826
|
+
const baseText = p.question && p.question.trim().length > 0 ? p.question : p.concept;
|
|
14827
|
+
const cleanDomain = slugify(p.domain || "");
|
|
14828
|
+
const cleanBase = slugify(baseText);
|
|
14829
|
+
let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
|
|
14830
|
+
if (baseSlug.length > 60) {
|
|
14831
|
+
baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
|
|
14832
|
+
}
|
|
14833
|
+
if (!baseSlug) {
|
|
14834
|
+
baseSlug = "token";
|
|
14835
|
+
}
|
|
14836
|
+
const token = await getTokenBySlug(db, baseSlug);
|
|
14837
|
+
if (token) {
|
|
14838
|
+
for (const context of contexts) {
|
|
14839
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
14840
|
+
}
|
|
14841
|
+
}
|
|
14842
|
+
}
|
|
14246
14843
|
jsonOut2({
|
|
14247
14844
|
success: true,
|
|
14248
14845
|
createdCount: result.createdCount,
|
|
@@ -14400,7 +14997,7 @@ bridgeCommand.command("curriculum-extract-topics").description(
|
|
|
14400
14997
|
}
|
|
14401
14998
|
}
|
|
14402
14999
|
const pageCleanedText = cleanHtml(rawHtml);
|
|
14403
|
-
const sourceId =
|
|
15000
|
+
const sourceId = ulid9();
|
|
14404
15001
|
await db.prepare(
|
|
14405
15002
|
`INSERT INTO sources (id, type, uri, content)
|
|
14406
15003
|
VALUES (?, 'web', ?, ?)
|
|
@@ -14449,6 +15046,71 @@ bridgeCommand.command("curriculum-set-last-selection").description("Persist the
|
|
|
14449
15046
|
jsonOut2({ success: true });
|
|
14450
15047
|
});
|
|
14451
15048
|
});
|
|
15049
|
+
bridgeCommand.command("list-knowledge-contexts").description("List all knowledge contexts (JSON)").action(async () => {
|
|
15050
|
+
await withDb2(async (db) => {
|
|
15051
|
+
const contexts = await listKnowledgeContexts(db);
|
|
15052
|
+
jsonOut2({ success: true, contexts });
|
|
15053
|
+
});
|
|
15054
|
+
});
|
|
15055
|
+
bridgeCommand.command("assign-knowledge-context").description("Assign a token to a knowledge context (JSON)").requiredOption("--token <slug>", "Token slug").requiredOption("--context <name>", "Context name").action(async (opts) => {
|
|
15056
|
+
await withDb2(async (db) => {
|
|
15057
|
+
const context = await getKnowledgeContextByName(db, opts.context);
|
|
15058
|
+
if (!context) {
|
|
15059
|
+
jsonError(`Knowledge context not found: ${opts.context}`);
|
|
15060
|
+
}
|
|
15061
|
+
const token = await getTokenBySlug(db, opts.token);
|
|
15062
|
+
if (!token) {
|
|
15063
|
+
jsonError(`Token not found: ${opts.token}`);
|
|
15064
|
+
}
|
|
15065
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
15066
|
+
jsonOut2({ success: true, token: token.slug, context: context.name });
|
|
15067
|
+
});
|
|
15068
|
+
});
|
|
15069
|
+
bridgeCommand.command("unassign-knowledge-context").description("Remove a token from a knowledge context (JSON)").requiredOption("--token <slug>", "Token slug").requiredOption("--context <name>", "Context name").action(async (opts) => {
|
|
15070
|
+
await withDb2(async (db) => {
|
|
15071
|
+
const context = await getKnowledgeContextByName(db, opts.context);
|
|
15072
|
+
if (!context) {
|
|
15073
|
+
jsonError(`Knowledge context not found: ${opts.context}`);
|
|
15074
|
+
}
|
|
15075
|
+
const token = await getTokenBySlug(db, opts.token);
|
|
15076
|
+
if (!token) {
|
|
15077
|
+
jsonError(`Token not found: ${opts.token}`);
|
|
15078
|
+
}
|
|
15079
|
+
await unassignTokenFromContext(db, token.id, context.id);
|
|
15080
|
+
jsonOut2({ success: true, token: token.slug, context: context.name });
|
|
15081
|
+
});
|
|
15082
|
+
});
|
|
15083
|
+
bridgeCommand.command("get-active-knowledge-context").description("Get the active knowledge context name (JSON)").action(async () => {
|
|
15084
|
+
await withDb2(async (db) => {
|
|
15085
|
+
const configured = getActiveWorkspaceContext();
|
|
15086
|
+
const active = configured ? await getKnowledgeContextByName(db, configured) : void 0;
|
|
15087
|
+
jsonOut2({
|
|
15088
|
+
success: true,
|
|
15089
|
+
activeContext: active?.name ?? null,
|
|
15090
|
+
staleContext: configured && !active ? configured : null
|
|
15091
|
+
});
|
|
15092
|
+
});
|
|
15093
|
+
});
|
|
15094
|
+
bridgeCommand.command("set-active-knowledge-context").description("Set the active knowledge context name (JSON)").argument("[name]", "Context name to use (use empty/none to clear)").action(async (name) => {
|
|
15095
|
+
await withDb2(async (db) => {
|
|
15096
|
+
if (!name || name === "none" || name === "null" || name === "undefined") {
|
|
15097
|
+
if (!setActiveWorkspaceContext(void 0)) {
|
|
15098
|
+
jsonError("No active workspace configured");
|
|
15099
|
+
}
|
|
15100
|
+
jsonOut2({ success: true, activeContext: null });
|
|
15101
|
+
return;
|
|
15102
|
+
}
|
|
15103
|
+
const context = await getKnowledgeContextByName(db, name);
|
|
15104
|
+
if (!context) {
|
|
15105
|
+
jsonError(`Knowledge context not found: ${name}`);
|
|
15106
|
+
return;
|
|
15107
|
+
}
|
|
15108
|
+
if (!setActiveWorkspaceContext(context.name)) {
|
|
15109
|
+
jsonError("No active workspace configured");
|
|
15110
|
+
}
|
|
15111
|
+
jsonOut2({ success: true, activeContext: context.name });
|
|
15112
|
+
});
|
|
15113
|
+
});
|
|
14452
15114
|
bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/stdout server").option("--stdin", "Use stdin/stdout for communication").action(async (_opts) => {
|
|
14453
15115
|
isServeMode = true;
|
|
14454
15116
|
const {
|
|
@@ -14583,13 +15245,19 @@ import { Command as Command3 } from "commander";
|
|
|
14583
15245
|
var cardCommand = new Command3("card").description(
|
|
14584
15246
|
"Manage spaced-repetition cards"
|
|
14585
15247
|
);
|
|
14586
|
-
cardCommand.command("due").description("Show due tokens for a user").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").option("--json", "Output as JSON").option("--summary", "Show only counts per domain (no slugs or concepts)").action(async (opts) => {
|
|
15248
|
+
cardCommand.command("due").description("Show due tokens for a user").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").option("--knowledge-context <context>", "Filter by knowledge context").option("--json", "Output as JSON").option("--summary", "Show only counts per domain (no slugs or concepts)").action(async (opts) => {
|
|
14587
15249
|
await withDb(async (db) => {
|
|
14588
15250
|
const userId = await resolveUser(opts, db);
|
|
14589
|
-
const dueCards = await getDueCards(
|
|
14590
|
-
|
|
14591
|
-
|
|
14592
|
-
|
|
15251
|
+
const dueCards = await getDueCards(
|
|
15252
|
+
db,
|
|
15253
|
+
userId,
|
|
15254
|
+
void 0,
|
|
15255
|
+
opts.domain,
|
|
15256
|
+
opts.knowledgeContext
|
|
15257
|
+
);
|
|
15258
|
+
if (opts.json) {
|
|
15259
|
+
console.log(JSON.stringify(dueCards, null, 2));
|
|
15260
|
+
return;
|
|
14593
15261
|
}
|
|
14594
15262
|
if (dueCards.length === 0) {
|
|
14595
15263
|
console.log("No cards due for review.");
|
|
@@ -14929,11 +15597,921 @@ async function setupTurso(urlArg, tokenArg, mode) {
|
|
|
14929
15597
|
}
|
|
14930
15598
|
}
|
|
14931
15599
|
|
|
15600
|
+
// src/cli/commands/doctor.ts
|
|
15601
|
+
import { Command as Command5 } from "commander";
|
|
15602
|
+
function shouldApplyFixes(opts) {
|
|
15603
|
+
return opts.fix === true && opts.dryRun !== true;
|
|
15604
|
+
}
|
|
15605
|
+
function stringifyDoctorOutput(value) {
|
|
15606
|
+
if (typeof value === "string") return value;
|
|
15607
|
+
try {
|
|
15608
|
+
return JSON.stringify(value);
|
|
15609
|
+
} catch {
|
|
15610
|
+
return String(value);
|
|
15611
|
+
}
|
|
15612
|
+
}
|
|
15613
|
+
async function captureDoctorTaskOutput(task, db, opts) {
|
|
15614
|
+
const chunks = [];
|
|
15615
|
+
const append = (value) => {
|
|
15616
|
+
for (const line of value.split(/[\r\n]+/)) {
|
|
15617
|
+
const trimmed = line.trim();
|
|
15618
|
+
if (trimmed) chunks.push(trimmed);
|
|
15619
|
+
}
|
|
15620
|
+
};
|
|
15621
|
+
const capture = (...args) => {
|
|
15622
|
+
append(args.map(stringifyDoctorOutput).join(" "));
|
|
15623
|
+
};
|
|
15624
|
+
const originalLog = console.log;
|
|
15625
|
+
const originalWarn = console.warn;
|
|
15626
|
+
const originalError = console.error;
|
|
15627
|
+
const originalWrite = process.stdout.write;
|
|
15628
|
+
console.log = capture;
|
|
15629
|
+
console.warn = capture;
|
|
15630
|
+
console.error = capture;
|
|
15631
|
+
process.stdout.write = ((chunk) => {
|
|
15632
|
+
append(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString());
|
|
15633
|
+
return true;
|
|
15634
|
+
});
|
|
15635
|
+
try {
|
|
15636
|
+
await task.run(db, opts);
|
|
15637
|
+
return { lines: chunks };
|
|
15638
|
+
} catch (error) {
|
|
15639
|
+
return { lines: chunks, error: error.message };
|
|
15640
|
+
} finally {
|
|
15641
|
+
console.log = originalLog;
|
|
15642
|
+
console.warn = originalWarn;
|
|
15643
|
+
console.error = originalError;
|
|
15644
|
+
process.stdout.write = originalWrite;
|
|
15645
|
+
}
|
|
15646
|
+
}
|
|
15647
|
+
async function confirmDeterministicChanges(opts, message) {
|
|
15648
|
+
if (opts.yes) return true;
|
|
15649
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
15650
|
+
console.error(
|
|
15651
|
+
"Cannot request confirmation in a non-interactive terminal. Re-run with --yes to apply deterministic fixes."
|
|
15652
|
+
);
|
|
15653
|
+
return false;
|
|
15654
|
+
}
|
|
15655
|
+
const { confirm: confirm5 } = await import("@inquirer/prompts");
|
|
15656
|
+
return confirm5({ message, default: false });
|
|
15657
|
+
}
|
|
15658
|
+
function canRunInteractiveChoices(opts, taskName) {
|
|
15659
|
+
if (opts.yes) {
|
|
15660
|
+
console.error(
|
|
15661
|
+
`--yes cannot choose how to resolve ${taskName}. Re-run without --yes in an interactive terminal.`
|
|
15662
|
+
);
|
|
15663
|
+
return false;
|
|
15664
|
+
}
|
|
15665
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
15666
|
+
console.error(
|
|
15667
|
+
`${taskName} fixes require an interactive terminal because each change needs an explicit choice.`
|
|
15668
|
+
);
|
|
15669
|
+
return false;
|
|
15670
|
+
}
|
|
15671
|
+
return true;
|
|
15672
|
+
}
|
|
15673
|
+
function getWeakTitleReason(t2) {
|
|
15674
|
+
const title = (t2.title || "").trim();
|
|
15675
|
+
if (!title) {
|
|
15676
|
+
return "Missing or empty title";
|
|
15677
|
+
}
|
|
15678
|
+
if (title === t2.slug) {
|
|
15679
|
+
return "Title is equal to technical slug";
|
|
15680
|
+
}
|
|
15681
|
+
if (title.length < 3) {
|
|
15682
|
+
return "Title is suspiciously short";
|
|
15683
|
+
}
|
|
15684
|
+
if (title.length > 100) {
|
|
15685
|
+
return "Title is too long (> 100 chars)";
|
|
15686
|
+
}
|
|
15687
|
+
if (t2.domain) {
|
|
15688
|
+
const domParts = t2.domain.split(/[-/]/).filter((p) => p.length > 2);
|
|
15689
|
+
const titleLower2 = title.toLowerCase();
|
|
15690
|
+
for (const part of domParts) {
|
|
15691
|
+
if (titleLower2.includes(part.toLowerCase())) {
|
|
15692
|
+
return `Domain echo (contains '${part}' from domain '${t2.domain}')`;
|
|
15693
|
+
}
|
|
15694
|
+
}
|
|
15695
|
+
}
|
|
15696
|
+
const conceptClean = (t2.concept || "").trim().toLowerCase();
|
|
15697
|
+
const titleLower = title.toLowerCase();
|
|
15698
|
+
if (conceptClean.startsWith(titleLower) && t2.concept.length > title.length + 5 && !/[.!?]/.test(title)) {
|
|
15699
|
+
const titleWords = titleLower.split(/\s+/).length;
|
|
15700
|
+
if (titleWords <= 5) {
|
|
15701
|
+
return "Concept-prefix copy";
|
|
15702
|
+
}
|
|
15703
|
+
}
|
|
15704
|
+
if (title.endsWith("?") || /^(what|how|why|who|where|when|which|is|are|was|were|can|do|does|did|ueber|fuer|nahe)\b/i.test(
|
|
15705
|
+
title
|
|
15706
|
+
)) {
|
|
15707
|
+
return "Question stump or interrogative structure";
|
|
15708
|
+
}
|
|
15709
|
+
if (title.length > 3 && /[a-zA-Z]/.test(title)) {
|
|
15710
|
+
if (title === title.toUpperCase()) {
|
|
15711
|
+
return "All uppercase";
|
|
15712
|
+
}
|
|
15713
|
+
if (title === title.toLowerCase()) {
|
|
15714
|
+
return "All lowercase";
|
|
15715
|
+
}
|
|
15716
|
+
}
|
|
15717
|
+
return null;
|
|
15718
|
+
}
|
|
15719
|
+
function stripLeadingDomainWords(value, domain) {
|
|
15720
|
+
let result = value.trim();
|
|
15721
|
+
for (const word of domain.split(/[-/]/).filter(Boolean)) {
|
|
15722
|
+
const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15723
|
+
result = result.replace(new RegExp(`^${escaped}\\s+`, "i"), "");
|
|
15724
|
+
}
|
|
15725
|
+
return result.trim();
|
|
15726
|
+
}
|
|
15727
|
+
function truncateTitle(value) {
|
|
15728
|
+
const trimmed = value.trim().replace(/^["']+|["']+$/g, "");
|
|
15729
|
+
return trimmed.length <= 80 ? trimmed : `${trimmed.substring(0, 77).trimEnd()}...`;
|
|
15730
|
+
}
|
|
15731
|
+
function titleFromSlug(token) {
|
|
15732
|
+
return getShortSlug(token.slug, token.domain).split(/[-_]+/).filter(Boolean).map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(" ");
|
|
15733
|
+
}
|
|
15734
|
+
function validatedTitleProposal(token, raw) {
|
|
15735
|
+
const candidates = [
|
|
15736
|
+
raw,
|
|
15737
|
+
(token.concept || "").split(/[.;]/)[0] || "",
|
|
15738
|
+
titleFromSlug(token)
|
|
15739
|
+
];
|
|
15740
|
+
for (const candidate of candidates) {
|
|
15741
|
+
const normalized = truncateTitle(
|
|
15742
|
+
stripLeadingDomainWords(candidate, token.domain || "")
|
|
15743
|
+
);
|
|
15744
|
+
if (normalized.length > 0 && getWeakTitleReason({ ...token, title: normalized }) === null) {
|
|
15745
|
+
return normalized;
|
|
15746
|
+
}
|
|
15747
|
+
}
|
|
15748
|
+
return null;
|
|
15749
|
+
}
|
|
15750
|
+
var LANGUAGE_MARKERS = {
|
|
15751
|
+
de: ["der", "die", "das", "und", "ist", "sind", "f\xFCr", "mit", "nicht"],
|
|
15752
|
+
en: ["the", "and", "is", "are", "for", "with", "not", "from", "that"],
|
|
15753
|
+
es: ["el", "la", "los", "las", "y", "es", "son", "para", "con"],
|
|
15754
|
+
fr: ["le", "la", "les", "et", "est", "sont", "pour", "avec"],
|
|
15755
|
+
pt: ["o", "a", "os", "as", "e", "\xE9", "s\xE3o", "para", "com"]
|
|
15756
|
+
};
|
|
15757
|
+
function inferEstablishedLanguage(token) {
|
|
15758
|
+
const text = [token.title, token.concept, token.question, token.context].filter(Boolean).join(" ").toLowerCase();
|
|
15759
|
+
if (/[-ヿ]/u.test(text)) return "ja";
|
|
15760
|
+
if (/[㐀-鿿]/u.test(text)) return "zh";
|
|
15761
|
+
const words = new Set(text.match(/\p{L}+/gu) ?? []);
|
|
15762
|
+
const scores = Object.entries(LANGUAGE_MARKERS).map(([language, markers]) => ({
|
|
15763
|
+
language,
|
|
15764
|
+
score: markers.filter((marker) => words.has(marker)).length
|
|
15765
|
+
})).sort((a, b) => b.score - a.score);
|
|
15766
|
+
return scores[0].score >= 2 && scores[0].score > scores[1].score ? scores[0].language : null;
|
|
15767
|
+
}
|
|
15768
|
+
function primaryLanguage(language) {
|
|
15769
|
+
return language?.trim().toLowerCase().split(/[-_]/)[0] || null;
|
|
15770
|
+
}
|
|
15771
|
+
var doctorTasks = [
|
|
15772
|
+
{
|
|
15773
|
+
name: "titles",
|
|
15774
|
+
description: "Backfill missing/weak human-friendly titles (no domain echoes, descriptive names).",
|
|
15775
|
+
run: async (db, opts) => {
|
|
15776
|
+
const tokens = await listTokens(db, {});
|
|
15777
|
+
const needing = tokens.map((t2) => {
|
|
15778
|
+
const reason = getWeakTitleReason(t2);
|
|
15779
|
+
return reason ? { token: t2, reason } : null;
|
|
15780
|
+
}).filter((x) => x !== null);
|
|
15781
|
+
console.log(
|
|
15782
|
+
`Found ${needing.length} tokens needing title work (of ${tokens.length}).`
|
|
15783
|
+
);
|
|
15784
|
+
if (needing.length === 0) return;
|
|
15785
|
+
const cfg = await getLlmConfig(db);
|
|
15786
|
+
const useLLM = cfg.enabled && !opts.noLlm;
|
|
15787
|
+
console.log(
|
|
15788
|
+
`Title generation mode: ${useLLM ? "LLM" : "Heuristic fallback"}${opts.timeoutMs ? ` (timeout: ${opts.timeoutMs}ms)` : ""}`
|
|
15789
|
+
);
|
|
15790
|
+
console.log("Generating proposed titles...");
|
|
15791
|
+
const proposals = [];
|
|
15792
|
+
let rejected = 0;
|
|
15793
|
+
for (let i = 0; i < needing.length; i++) {
|
|
15794
|
+
const item = needing[i];
|
|
15795
|
+
if (!opts.json) {
|
|
15796
|
+
process.stdout.write(
|
|
15797
|
+
`\r [${i + 1}/${needing.length}] ${item.token.slug.slice(0, 40)}...`
|
|
15798
|
+
);
|
|
15799
|
+
}
|
|
15800
|
+
let rawProposal = "";
|
|
15801
|
+
if (useLLM) {
|
|
15802
|
+
try {
|
|
15803
|
+
const gen = await generateTitleViaLLM(
|
|
15804
|
+
db,
|
|
15805
|
+
{
|
|
15806
|
+
slug: item.token.slug,
|
|
15807
|
+
concept: item.token.concept,
|
|
15808
|
+
domain: item.token.domain,
|
|
15809
|
+
question: item.token.question,
|
|
15810
|
+
context: item.token.context
|
|
15811
|
+
},
|
|
15812
|
+
{
|
|
15813
|
+
timeoutMs: opts.timeoutMs,
|
|
15814
|
+
knowledgeContext: opts.knowledgeContext
|
|
15815
|
+
}
|
|
15816
|
+
);
|
|
15817
|
+
rawProposal = gen.text;
|
|
15818
|
+
} catch (_e) {
|
|
15819
|
+
rawProposal = (item.token.concept || "").split(/[.;]/)[0]?.trim() || item.token.slug.replace(/-/g, " ");
|
|
15820
|
+
}
|
|
15821
|
+
} else {
|
|
15822
|
+
rawProposal = (item.token.concept || "").split(/[.;]/)[0]?.trim() || item.token.slug.replace(/-/g, " ");
|
|
15823
|
+
}
|
|
15824
|
+
const proposed = validatedTitleProposal(item.token, rawProposal);
|
|
15825
|
+
if (!proposed) {
|
|
15826
|
+
rejected++;
|
|
15827
|
+
continue;
|
|
15828
|
+
}
|
|
15829
|
+
proposals.push({
|
|
15830
|
+
token: item.token,
|
|
15831
|
+
oldTitle: item.token.title,
|
|
15832
|
+
newTitle: proposed,
|
|
15833
|
+
reason: item.reason
|
|
15834
|
+
});
|
|
15835
|
+
}
|
|
15836
|
+
if (!opts.json) process.stdout.write("\n");
|
|
15837
|
+
if (rejected > 0) {
|
|
15838
|
+
console.warn(
|
|
15839
|
+
`Skipped ${rejected} token(s) because no proposal passed title quality validation.`
|
|
15840
|
+
);
|
|
15841
|
+
}
|
|
15842
|
+
if (proposals.length === 0) return;
|
|
15843
|
+
if (!shouldApplyFixes(opts)) {
|
|
15844
|
+
console.log("\nProposed changes (Dry run):");
|
|
15845
|
+
for (const prop of proposals) {
|
|
15846
|
+
console.log(
|
|
15847
|
+
` ${prop.token.slug}: "${prop.oldTitle}" -> "${prop.newTitle}" (Reason: ${prop.reason})`
|
|
15848
|
+
);
|
|
15849
|
+
}
|
|
15850
|
+
console.log("\nRun with --fix to apply these changes.");
|
|
15851
|
+
return;
|
|
15852
|
+
}
|
|
15853
|
+
if (!opts.yes) {
|
|
15854
|
+
console.log("\nProposed changes:");
|
|
15855
|
+
for (const prop of proposals) {
|
|
15856
|
+
console.log(
|
|
15857
|
+
` ${prop.token.slug}: "${prop.oldTitle}" -> "${prop.newTitle}" (Reason: ${prop.reason})`
|
|
15858
|
+
);
|
|
15859
|
+
}
|
|
15860
|
+
}
|
|
15861
|
+
const confirmed = await confirmDeterministicChanges(
|
|
15862
|
+
opts,
|
|
15863
|
+
`Apply these ${proposals.length} proposed titles?`
|
|
15864
|
+
);
|
|
15865
|
+
if (!confirmed) {
|
|
15866
|
+
console.log("Cancelled. No titles were updated.");
|
|
15867
|
+
return;
|
|
15868
|
+
}
|
|
15869
|
+
let fixed = 0;
|
|
15870
|
+
for (const prop of proposals) {
|
|
15871
|
+
await updateToken(db, prop.token.slug, { title: prop.newTitle });
|
|
15872
|
+
fixed++;
|
|
15873
|
+
console.log(`Set title for ${prop.token.slug} -> ${prop.newTitle}`);
|
|
15874
|
+
}
|
|
15875
|
+
console.log(`Successfully fixed ${fixed} titles.`);
|
|
15876
|
+
}
|
|
15877
|
+
},
|
|
15878
|
+
{
|
|
15879
|
+
name: "texts",
|
|
15880
|
+
description: "Repair legacy ASCII umlauts in prose fields (question/concept/context).",
|
|
15881
|
+
run: async (db, opts) => {
|
|
15882
|
+
const tokens = await listTokens(db, {});
|
|
15883
|
+
const cfg = await getLlmConfig(db);
|
|
15884
|
+
const useLLM = cfg.enabled && !opts.noLlm;
|
|
15885
|
+
const replacements = [
|
|
15886
|
+
[/\bUeber\b/g, "\xDCber"],
|
|
15887
|
+
[/\bueber\b/g, "\xFCber"],
|
|
15888
|
+
[/\bFuer\b/g, "F\xFCr"],
|
|
15889
|
+
[/\bfuer\b/g, "f\xFCr"],
|
|
15890
|
+
[/\bMuehe\b/g, "M\xFChe"],
|
|
15891
|
+
[/\bmuehe\b/g, "m\xFChe"],
|
|
15892
|
+
[/\bKuer\b/g, "K\xFCr"],
|
|
15893
|
+
[/\bkuer\b/g, "k\xFCr"],
|
|
15894
|
+
[/\bUebung\b/g, "\xDCbung"],
|
|
15895
|
+
[/\buebung\b/g, "\xFCbung"],
|
|
15896
|
+
[/\bMoeglich\b/g, "M\xF6glich"],
|
|
15897
|
+
[/\bmoeglich\b/g, "m\xF6glich"],
|
|
15898
|
+
[/\bMoeglichkeiten\b/g, "M\xF6glichkeiten"],
|
|
15899
|
+
[/\bmoeglichkeiten\b/g, "m\xF6glichkeiten"],
|
|
15900
|
+
[/\bSchoen\b/g, "Sch\xF6n"],
|
|
15901
|
+
[/\bschoen\b/g, "sch\xF6n"],
|
|
15902
|
+
[/\bKoennen\b/g, "K\xF6nnen"],
|
|
15903
|
+
[/\bkoennen\b/g, "k\xF6nnen"],
|
|
15904
|
+
[/\bMuessen\b/g, "M\xFCssen"],
|
|
15905
|
+
[/\bmuessen\b/g, "m\xFCssen"],
|
|
15906
|
+
[/\bDuerfen\b/g, "D\xFCrfen"],
|
|
15907
|
+
[/\bduerfen\b/g, "d\xFCrfen"],
|
|
15908
|
+
[/\bAerger\b/g, "\xC4rger"],
|
|
15909
|
+
[/\baerger\b/g, "\xE4rger"],
|
|
15910
|
+
[/\bGruende\b/g, "Gr\xFCnde"],
|
|
15911
|
+
[/\bgruende\b/g, "gr\xFCnde"]
|
|
15912
|
+
];
|
|
15913
|
+
const toFix = [];
|
|
15914
|
+
console.log("Analyzing prose text fields for legacy umlauts...");
|
|
15915
|
+
for (const t2 of tokens) {
|
|
15916
|
+
const fields = [
|
|
15917
|
+
{ key: "question", val: t2.question },
|
|
15918
|
+
{ key: "concept", val: t2.concept },
|
|
15919
|
+
{ key: "context", val: t2.context },
|
|
15920
|
+
{ key: "title", val: t2.title }
|
|
15921
|
+
];
|
|
15922
|
+
for (const f of fields) {
|
|
15923
|
+
if (!f.val || typeof f.val !== "string") continue;
|
|
15924
|
+
let repaired = f.val;
|
|
15925
|
+
if (useLLM) {
|
|
15926
|
+
const hasPossibleUmlautFold = /[aou]e/i.test(f.val);
|
|
15927
|
+
if (hasPossibleUmlautFold) {
|
|
15928
|
+
try {
|
|
15929
|
+
repaired = await repairUmlautsViaLLM(
|
|
15930
|
+
db,
|
|
15931
|
+
{ text: f.val },
|
|
15932
|
+
{ timeoutMs: opts.timeoutMs }
|
|
15933
|
+
);
|
|
15934
|
+
if (repaired === f.val) {
|
|
15935
|
+
for (const [from, to] of replacements) {
|
|
15936
|
+
repaired = repaired.replace(from, to);
|
|
15937
|
+
}
|
|
15938
|
+
}
|
|
15939
|
+
} catch (_e) {
|
|
15940
|
+
for (const [from, to] of replacements) {
|
|
15941
|
+
repaired = repaired.replace(from, to);
|
|
15942
|
+
}
|
|
15943
|
+
}
|
|
15944
|
+
}
|
|
15945
|
+
} else {
|
|
15946
|
+
for (const [from, to] of replacements) {
|
|
15947
|
+
repaired = repaired.replace(from, to);
|
|
15948
|
+
}
|
|
15949
|
+
}
|
|
15950
|
+
if (repaired !== f.val) {
|
|
15951
|
+
toFix.push({ token: t2, field: f.key, old: f.val, new: repaired });
|
|
15952
|
+
}
|
|
15953
|
+
}
|
|
15954
|
+
}
|
|
15955
|
+
console.log(`Found ${toFix.length} prose fields with legacy umlauts.`);
|
|
15956
|
+
if (toFix.length === 0) return;
|
|
15957
|
+
if (!shouldApplyFixes(opts)) {
|
|
15958
|
+
console.log("\nProposed changes (Dry run):");
|
|
15959
|
+
for (const fix of toFix) {
|
|
15960
|
+
console.log(` ${fix.token.slug} [${fix.field}]:`);
|
|
15961
|
+
console.log(` Old: "${fix.old}"`);
|
|
15962
|
+
console.log(` New: "${fix.new}"`);
|
|
15963
|
+
}
|
|
15964
|
+
console.log("\nRun with --fix to apply these changes.");
|
|
15965
|
+
return;
|
|
15966
|
+
}
|
|
15967
|
+
if (!opts.yes) {
|
|
15968
|
+
console.log("\nProposed changes:");
|
|
15969
|
+
for (const fix of toFix) {
|
|
15970
|
+
console.log(
|
|
15971
|
+
` ${fix.token.slug} [${fix.field}]: "${fix.old}" -> "${fix.new}"`
|
|
15972
|
+
);
|
|
15973
|
+
}
|
|
15974
|
+
}
|
|
15975
|
+
const confirmed = await confirmDeterministicChanges(
|
|
15976
|
+
opts,
|
|
15977
|
+
`Apply these ${toFix.length} prose fixes?`
|
|
15978
|
+
);
|
|
15979
|
+
if (!confirmed) {
|
|
15980
|
+
console.log("Cancelled. No changes made.");
|
|
15981
|
+
return;
|
|
15982
|
+
}
|
|
15983
|
+
let fixed = 0;
|
|
15984
|
+
for (const fix of toFix) {
|
|
15985
|
+
const updates = {};
|
|
15986
|
+
updates[fix.field] = fix.new;
|
|
15987
|
+
await updateToken(db, fix.token.slug, updates);
|
|
15988
|
+
fixed++;
|
|
15989
|
+
}
|
|
15990
|
+
console.log(
|
|
15991
|
+
`Fixed ${fixed} fields. (Re-embed will happen automatically for changed content.)`
|
|
15992
|
+
);
|
|
15993
|
+
}
|
|
15994
|
+
},
|
|
15995
|
+
{
|
|
15996
|
+
name: "duplicates",
|
|
15997
|
+
description: "List semantic duplicates for review/merge.",
|
|
15998
|
+
run: async (db, opts) => {
|
|
15999
|
+
const applyingFixes = shouldApplyFixes(opts);
|
|
16000
|
+
if (applyingFixes && !canRunInteractiveChoices(opts, "duplicate fixes")) {
|
|
16001
|
+
return;
|
|
16002
|
+
}
|
|
16003
|
+
const modelSetting = await getSetting(db, "llm.embedding.model");
|
|
16004
|
+
if (!modelSetting) {
|
|
16005
|
+
console.log(
|
|
16006
|
+
"No embedding model configured. Please configure an embedding model to scan duplicates."
|
|
16007
|
+
);
|
|
16008
|
+
return;
|
|
16009
|
+
}
|
|
16010
|
+
const model = canonicalEmbeddingModelId(modelSetting);
|
|
16011
|
+
if (model !== modelSetting.trim().toLowerCase()) {
|
|
16012
|
+
console.log(
|
|
16013
|
+
`Using canonical embedding model "${model}" for configured alias "${modelSetting}".`
|
|
16014
|
+
);
|
|
16015
|
+
}
|
|
16016
|
+
let coverage = await getEmbeddingCoverage(db, model);
|
|
16017
|
+
const initiallyIncomplete = coverage.missing + coverage.stale;
|
|
16018
|
+
if (initiallyIncomplete > 0) {
|
|
16019
|
+
if (applyingFixes) {
|
|
16020
|
+
console.log(
|
|
16021
|
+
`Embedding coverage is incomplete (${coverage.missing} missing, ${coverage.stale} stale). Attempting backfill...`
|
|
16022
|
+
);
|
|
16023
|
+
const result = await ensureTokenEmbeddings(db, {
|
|
16024
|
+
limit: coverage.tokens
|
|
16025
|
+
});
|
|
16026
|
+
coverage = await getEmbeddingCoverage(db, model);
|
|
16027
|
+
const remaining = coverage.missing + coverage.stale;
|
|
16028
|
+
if (remaining > 0) {
|
|
16029
|
+
console.warn(
|
|
16030
|
+
`Warning: Duplicate scan is incomplete: ${remaining} of ${coverage.tokens} active tokens still lack a fresh "${model}" embedding.${result.reason ? ` ${result.reason}` : ""}`
|
|
16031
|
+
);
|
|
16032
|
+
} else {
|
|
16033
|
+
console.log(
|
|
16034
|
+
`Embedding backfill complete (${result.embedded} updated).`
|
|
16035
|
+
);
|
|
16036
|
+
}
|
|
16037
|
+
} else {
|
|
16038
|
+
console.warn(
|
|
16039
|
+
`Warning: Duplicate scan is incomplete: ${initiallyIncomplete} of ${coverage.tokens} active tokens lack a fresh "${model}" embedding. Dry-run will not backfill them; run 'zam token reembed' first for a complete scan.`
|
|
16040
|
+
);
|
|
16041
|
+
}
|
|
16042
|
+
}
|
|
16043
|
+
console.log(`Listing embedded tokens for model "${model}"...`);
|
|
16044
|
+
const embedded = await listEmbeddedTokens(db, model);
|
|
16045
|
+
const threshold = await resolveDedupThreshold(db);
|
|
16046
|
+
console.log(
|
|
16047
|
+
`Scanning ${embedded.length} tokens for semantic duplicates (threshold: ${threshold})...`
|
|
16048
|
+
);
|
|
16049
|
+
const duplicates = [];
|
|
16050
|
+
const seenPairs = /* @__PURE__ */ new Set();
|
|
16051
|
+
for (let i = 0; i < embedded.length; i++) {
|
|
16052
|
+
for (let j = i + 1; j < embedded.length; j++) {
|
|
16053
|
+
const sim = cosineSimilarity(
|
|
16054
|
+
embedded[i].embedding,
|
|
16055
|
+
embedded[j].embedding
|
|
16056
|
+
);
|
|
16057
|
+
if (sim >= threshold) {
|
|
16058
|
+
const idA = embedded[i].token.id;
|
|
16059
|
+
const idB = embedded[j].token.id;
|
|
16060
|
+
const pairKey = idA < idB ? `${idA}:${idB}` : `${idB}:${idA}`;
|
|
16061
|
+
if (!seenPairs.has(pairKey)) {
|
|
16062
|
+
seenPairs.add(pairKey);
|
|
16063
|
+
duplicates.push({
|
|
16064
|
+
a: embedded[i].token,
|
|
16065
|
+
b: embedded[j].token,
|
|
16066
|
+
similarity: sim
|
|
16067
|
+
});
|
|
16068
|
+
}
|
|
16069
|
+
}
|
|
16070
|
+
}
|
|
16071
|
+
}
|
|
16072
|
+
console.log(`Found ${duplicates.length} duplicate pairs.`);
|
|
16073
|
+
if (duplicates.length === 0) return;
|
|
16074
|
+
if (!applyingFixes) {
|
|
16075
|
+
console.log("\nDuplicate pairs found (Dry run):");
|
|
16076
|
+
for (const pair of duplicates) {
|
|
16077
|
+
console.log(
|
|
16078
|
+
` - "${pair.a.slug}" and "${pair.b.slug}" (similarity: ${pair.similarity.toFixed(3)})`
|
|
16079
|
+
);
|
|
16080
|
+
}
|
|
16081
|
+
console.log("\nRun with --fix to resolve duplicates interactively.");
|
|
16082
|
+
return;
|
|
16083
|
+
}
|
|
16084
|
+
const { select: select3 } = await import("@inquirer/prompts");
|
|
16085
|
+
let deprecatedCount = 0;
|
|
16086
|
+
for (const pair of duplicates) {
|
|
16087
|
+
console.log(
|
|
16088
|
+
`
|
|
16089
|
+
Duplicate pair found (similarity: ${pair.similarity.toFixed(3)}):`
|
|
16090
|
+
);
|
|
16091
|
+
console.log(
|
|
16092
|
+
` 1. ${pair.a.slug}: "${pair.a.concept.substring(0, 100)}..."`
|
|
16093
|
+
);
|
|
16094
|
+
console.log(
|
|
16095
|
+
` 2. ${pair.b.slug}: "${pair.b.concept.substring(0, 100)}..."`
|
|
16096
|
+
);
|
|
16097
|
+
const choice = await select3({
|
|
16098
|
+
message: "Select action:",
|
|
16099
|
+
choices: [
|
|
16100
|
+
{ name: "Keep both", value: "keep" },
|
|
16101
|
+
{ name: `Deprecate ${pair.a.slug}`, value: "deprecate_a" },
|
|
16102
|
+
{ name: `Deprecate ${pair.b.slug}`, value: "deprecate_b" }
|
|
16103
|
+
]
|
|
16104
|
+
});
|
|
16105
|
+
if (choice === "deprecate_a") {
|
|
16106
|
+
await deprecateToken(db, pair.a.slug);
|
|
16107
|
+
console.log(` Deprecated ${pair.a.slug}`);
|
|
16108
|
+
deprecatedCount++;
|
|
16109
|
+
} else if (choice === "deprecate_b") {
|
|
16110
|
+
await deprecateToken(db, pair.b.slug);
|
|
16111
|
+
console.log(` Deprecated ${pair.b.slug}`);
|
|
16112
|
+
deprecatedCount++;
|
|
16113
|
+
}
|
|
16114
|
+
}
|
|
16115
|
+
console.log(
|
|
16116
|
+
`
|
|
16117
|
+
Duplicates scan complete. Deprecated ${deprecatedCount} token(s).`
|
|
16118
|
+
);
|
|
16119
|
+
}
|
|
16120
|
+
},
|
|
16121
|
+
{
|
|
16122
|
+
name: "domains",
|
|
16123
|
+
description: "Help rename/unify domains into / hierarchy (slugs untouched).",
|
|
16124
|
+
run: async (db, opts) => {
|
|
16125
|
+
const tokens = await listTokens(db, {});
|
|
16126
|
+
const domains = Array.from(
|
|
16127
|
+
new Set(tokens.map((t2) => t2.domain).filter(Boolean))
|
|
16128
|
+
).sort();
|
|
16129
|
+
console.log(`Current domains (${domains.length}):`);
|
|
16130
|
+
for (const d of domains) {
|
|
16131
|
+
const count2 = tokens.filter((t2) => t2.domain === d).length;
|
|
16132
|
+
const status = d.includes("/") ? "(hierarchical)" : "(flat, valid)";
|
|
16133
|
+
console.log(` ${d} \u2014 ${count2} token(s) ${status}`);
|
|
16134
|
+
}
|
|
16135
|
+
if (!shouldApplyFixes(opts)) {
|
|
16136
|
+
console.log("\nUse --fix to interactively rename/unify domains.");
|
|
16137
|
+
return;
|
|
16138
|
+
}
|
|
16139
|
+
if (!canRunInteractiveChoices(opts, "domain renames")) return;
|
|
16140
|
+
const { confirm: confirm5, input: input8 } = await import("@inquirer/prompts");
|
|
16141
|
+
const changes = [];
|
|
16142
|
+
for (const d of domains) {
|
|
16143
|
+
const count2 = tokens.filter((t2) => t2.domain === d).length;
|
|
16144
|
+
const wantRename = await confirm5({
|
|
16145
|
+
message: `Rename domain "${d}" (${count2} tokens)?`,
|
|
16146
|
+
default: false
|
|
16147
|
+
});
|
|
16148
|
+
if (wantRename) {
|
|
16149
|
+
const newDomain = await input8({
|
|
16150
|
+
message: `Enter new domain name for "${d}":`,
|
|
16151
|
+
validate: (val) => val.trim().length > 0 ? true : "Domain name cannot be empty"
|
|
16152
|
+
});
|
|
16153
|
+
const trimmed = newDomain.trim();
|
|
16154
|
+
if (trimmed === d) {
|
|
16155
|
+
console.log(" New name matches old name. Skipped.");
|
|
16156
|
+
continue;
|
|
16157
|
+
}
|
|
16158
|
+
changes.push({ oldDomain: d, newDomain: trimmed });
|
|
16159
|
+
}
|
|
16160
|
+
}
|
|
16161
|
+
if (changes.length > 0) {
|
|
16162
|
+
await db.transaction(async (tx) => {
|
|
16163
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16164
|
+
const stmt = tx.prepare(
|
|
16165
|
+
"UPDATE tokens SET domain = ?, updated_at = ? WHERE domain = ?"
|
|
16166
|
+
);
|
|
16167
|
+
for (const change of changes) {
|
|
16168
|
+
const res = await stmt.run(change.newDomain, now, change.oldDomain);
|
|
16169
|
+
console.log(
|
|
16170
|
+
` Renamed "${change.oldDomain}" to "${change.newDomain}" for ${res.changes} token(s).`
|
|
16171
|
+
);
|
|
16172
|
+
}
|
|
16173
|
+
});
|
|
16174
|
+
}
|
|
16175
|
+
console.log(
|
|
16176
|
+
`Domains rename complete. Renamed ${changes.length} domain(s).`
|
|
16177
|
+
);
|
|
16178
|
+
}
|
|
16179
|
+
},
|
|
16180
|
+
{
|
|
16181
|
+
name: "contexts",
|
|
16182
|
+
description: "Backfill contexts on old/unassigned tokens using heuristic rules.",
|
|
16183
|
+
run: async (db, opts) => {
|
|
16184
|
+
const tokensWithoutContext = await db.prepare(
|
|
16185
|
+
`SELECT t.* FROM tokens t
|
|
16186
|
+
WHERE NOT EXISTS (
|
|
16187
|
+
SELECT 1 FROM token_contexts tc WHERE tc.token_id = t.id
|
|
16188
|
+
)`
|
|
16189
|
+
).all();
|
|
16190
|
+
const allContexts = await listKnowledgeContexts(db);
|
|
16191
|
+
const contexts = opts.knowledgeContext ? allContexts.filter(
|
|
16192
|
+
(context) => context.name === opts.knowledgeContext
|
|
16193
|
+
) : allContexts;
|
|
16194
|
+
if (opts.knowledgeContext && contexts.length === 0) {
|
|
16195
|
+
throw new Error(
|
|
16196
|
+
`Knowledge context not found: ${opts.knowledgeContext}`
|
|
16197
|
+
);
|
|
16198
|
+
}
|
|
16199
|
+
if (contexts.length === 0) {
|
|
16200
|
+
if (opts.json) {
|
|
16201
|
+
console.log(
|
|
16202
|
+
JSON.stringify({
|
|
16203
|
+
success: false,
|
|
16204
|
+
error: "No knowledge contexts defined."
|
|
16205
|
+
})
|
|
16206
|
+
);
|
|
16207
|
+
} else {
|
|
16208
|
+
console.log(
|
|
16209
|
+
"No knowledge contexts defined. Run `zam kc create` first."
|
|
16210
|
+
);
|
|
16211
|
+
}
|
|
16212
|
+
return;
|
|
16213
|
+
}
|
|
16214
|
+
const proposals = [];
|
|
16215
|
+
for (const t2 of tokensWithoutContext) {
|
|
16216
|
+
const domain = (t2.domain || "").toLowerCase();
|
|
16217
|
+
const slug = t2.slug.toLowerCase();
|
|
16218
|
+
const title = (t2.title || "").toLowerCase();
|
|
16219
|
+
const source = (t2.source_link || "").toLowerCase();
|
|
16220
|
+
const establishedLanguage = inferEstablishedLanguage(t2);
|
|
16221
|
+
const languageMatches = establishedLanguage ? contexts.filter(
|
|
16222
|
+
(context) => primaryLanguage(context.language) === establishedLanguage
|
|
16223
|
+
) : [];
|
|
16224
|
+
for (const ctx of contexts) {
|
|
16225
|
+
const ctxName = ctx.name.toLowerCase();
|
|
16226
|
+
const ctxLabel = (ctx.label || "").toLowerCase();
|
|
16227
|
+
let match = false;
|
|
16228
|
+
let highConfidence = false;
|
|
16229
|
+
if (domain) {
|
|
16230
|
+
if (domain === ctxName) {
|
|
16231
|
+
match = true;
|
|
16232
|
+
highConfidence = true;
|
|
16233
|
+
} else if (ctxName.includes(domain) || domain.includes(ctxName)) {
|
|
16234
|
+
match = true;
|
|
16235
|
+
highConfidence = domain.length >= 2;
|
|
16236
|
+
} else if (ctxLabel && (ctxLabel.includes(domain) || domain.includes(ctxLabel))) {
|
|
16237
|
+
match = true;
|
|
16238
|
+
highConfidence = domain.length >= 2;
|
|
16239
|
+
}
|
|
16240
|
+
}
|
|
16241
|
+
if (slug.includes(ctxName) || title.includes(ctxName)) {
|
|
16242
|
+
match = true;
|
|
16243
|
+
}
|
|
16244
|
+
if (ctxLabel) {
|
|
16245
|
+
const labelWords = ctxLabel.split(/\s+/).filter((w) => w.length > 3);
|
|
16246
|
+
for (const word of labelWords) {
|
|
16247
|
+
if (domain.includes(word) || slug.includes(word) || title.includes(word)) {
|
|
16248
|
+
match = true;
|
|
16249
|
+
}
|
|
16250
|
+
}
|
|
16251
|
+
}
|
|
16252
|
+
if (source && (source.includes(ctxName) || ctxLabel.split(/\s+/).filter((word) => word.length > 3).some((word) => source.includes(word)))) {
|
|
16253
|
+
match = true;
|
|
16254
|
+
highConfidence = true;
|
|
16255
|
+
}
|
|
16256
|
+
if (languageMatches.length === 1 && languageMatches[0].id === ctx.id) {
|
|
16257
|
+
match = true;
|
|
16258
|
+
}
|
|
16259
|
+
if (match) {
|
|
16260
|
+
proposals.push({
|
|
16261
|
+
token: t2,
|
|
16262
|
+
context: ctx,
|
|
16263
|
+
highConfidence
|
|
16264
|
+
});
|
|
16265
|
+
}
|
|
16266
|
+
}
|
|
16267
|
+
}
|
|
16268
|
+
const applying = shouldApplyFixes(opts);
|
|
16269
|
+
if (opts.json) {
|
|
16270
|
+
if (!applying) {
|
|
16271
|
+
const formatted = proposals.map((p) => ({
|
|
16272
|
+
tokenSlug: p.token.slug,
|
|
16273
|
+
contextName: p.context.name,
|
|
16274
|
+
highConfidence: p.highConfidence
|
|
16275
|
+
}));
|
|
16276
|
+
console.log(
|
|
16277
|
+
JSON.stringify({
|
|
16278
|
+
success: true,
|
|
16279
|
+
task: "contexts",
|
|
16280
|
+
proposals: formatted,
|
|
16281
|
+
totalUnassigned: tokensWithoutContext.length
|
|
16282
|
+
})
|
|
16283
|
+
);
|
|
16284
|
+
return;
|
|
16285
|
+
}
|
|
16286
|
+
} else {
|
|
16287
|
+
console.log(
|
|
16288
|
+
`Found ${proposals.length} proposed context assignments for ${tokensWithoutContext.length} unassigned tokens.`
|
|
16289
|
+
);
|
|
16290
|
+
}
|
|
16291
|
+
if (proposals.length === 0) {
|
|
16292
|
+
if (opts.json && applying) {
|
|
16293
|
+
console.log(
|
|
16294
|
+
JSON.stringify({
|
|
16295
|
+
success: true,
|
|
16296
|
+
task: "contexts",
|
|
16297
|
+
applied: [],
|
|
16298
|
+
totalApplied: 0
|
|
16299
|
+
})
|
|
16300
|
+
);
|
|
16301
|
+
}
|
|
16302
|
+
return;
|
|
16303
|
+
}
|
|
16304
|
+
if (!applying) {
|
|
16305
|
+
console.log("\nProposed context assignments (Dry run):");
|
|
16306
|
+
for (const prop of proposals) {
|
|
16307
|
+
const confStr = prop.highConfidence ? " [High Confidence]" : "";
|
|
16308
|
+
console.log(
|
|
16309
|
+
` - Assign token "${prop.token.slug}" to context "${prop.context.name}"${confStr}`
|
|
16310
|
+
);
|
|
16311
|
+
}
|
|
16312
|
+
console.log("\nRun with --fix to apply these assignments.");
|
|
16313
|
+
return;
|
|
16314
|
+
}
|
|
16315
|
+
if (opts.yes) {
|
|
16316
|
+
let applied = 0;
|
|
16317
|
+
const appliedProposals = [];
|
|
16318
|
+
for (const prop of proposals) {
|
|
16319
|
+
if (prop.highConfidence) {
|
|
16320
|
+
await assignTokenToContext(db, prop.token.id, prop.context.id);
|
|
16321
|
+
if (!opts.json) {
|
|
16322
|
+
console.log(
|
|
16323
|
+
`Auto-assigned: "${prop.token.slug}" -> context "${prop.context.name}"`
|
|
16324
|
+
);
|
|
16325
|
+
}
|
|
16326
|
+
appliedProposals.push({
|
|
16327
|
+
tokenSlug: prop.token.slug,
|
|
16328
|
+
contextName: prop.context.name
|
|
16329
|
+
});
|
|
16330
|
+
applied++;
|
|
16331
|
+
}
|
|
16332
|
+
}
|
|
16333
|
+
if (opts.json) {
|
|
16334
|
+
console.log(
|
|
16335
|
+
JSON.stringify({
|
|
16336
|
+
success: true,
|
|
16337
|
+
task: "contexts",
|
|
16338
|
+
applied: appliedProposals,
|
|
16339
|
+
totalApplied: applied
|
|
16340
|
+
})
|
|
16341
|
+
);
|
|
16342
|
+
} else {
|
|
16343
|
+
console.log(`Auto-assigned ${applied} high-confidence contexts.`);
|
|
16344
|
+
}
|
|
16345
|
+
return;
|
|
16346
|
+
}
|
|
16347
|
+
const { confirm: confirm5 } = await import("@inquirer/prompts");
|
|
16348
|
+
let assignedCount = 0;
|
|
16349
|
+
for (const prop of proposals) {
|
|
16350
|
+
const confStr = prop.highConfidence ? " (high confidence)" : "";
|
|
16351
|
+
const ans = await confirm5({
|
|
16352
|
+
message: `Assign token "${prop.token.slug}" to context "${prop.context.name}"${confStr}?`,
|
|
16353
|
+
default: prop.highConfidence
|
|
16354
|
+
});
|
|
16355
|
+
if (ans) {
|
|
16356
|
+
await assignTokenToContext(db, prop.token.id, prop.context.id);
|
|
16357
|
+
console.log(
|
|
16358
|
+
`Assigned "${prop.token.slug}" -> context "${prop.context.name}"`
|
|
16359
|
+
);
|
|
16360
|
+
assignedCount++;
|
|
16361
|
+
}
|
|
16362
|
+
}
|
|
16363
|
+
console.log(
|
|
16364
|
+
`Completed contexts backfill. Assigned ${assignedCount} tokens.`
|
|
16365
|
+
);
|
|
16366
|
+
}
|
|
16367
|
+
}
|
|
16368
|
+
];
|
|
16369
|
+
function parseDoctorTimeout(value) {
|
|
16370
|
+
const parsed = Number(value);
|
|
16371
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
16372
|
+
throw new Error("--timeout must be a positive integer in milliseconds");
|
|
16373
|
+
}
|
|
16374
|
+
return parsed;
|
|
16375
|
+
}
|
|
16376
|
+
var doctorCommand = new Command5("doctor").description(
|
|
16377
|
+
"Diagnose and repair the knowledge base (titles, legacy data, duplicates, domains)."
|
|
16378
|
+
).option("--fix", "Apply changes (default is dry-run)").option("--dry-run", "Explicit dry run (default)").option(
|
|
16379
|
+
"--yes",
|
|
16380
|
+
"Auto-confirm deterministic title/text fixes (duplicate/domain choices remain interactive)"
|
|
16381
|
+
).option("--no-llm", "Skip LLM calls, use heuristic fallback only").option(
|
|
16382
|
+
"--timeout <ms>",
|
|
16383
|
+
"LLM timeout in ms per call (default: 20000)",
|
|
16384
|
+
"20000"
|
|
16385
|
+
).option(
|
|
16386
|
+
"--knowledge-context <context>",
|
|
16387
|
+
"Knowledge context to limit/guide doctor task"
|
|
16388
|
+
).option("--json", "Emit report in JSON format").argument(
|
|
16389
|
+
"[task]",
|
|
16390
|
+
"Specific task: titles, texts, duplicates, domains, contexts"
|
|
16391
|
+
).action(async (taskName, opts) => {
|
|
16392
|
+
await withDb(async (db) => {
|
|
16393
|
+
const fix = !!opts.fix;
|
|
16394
|
+
const dryRun = !fix || !!opts.dryRun;
|
|
16395
|
+
const yes = !!opts.yes;
|
|
16396
|
+
const noLlm = opts.llm === false;
|
|
16397
|
+
const timeoutMs = parseDoctorTimeout(opts.timeout);
|
|
16398
|
+
const knowledgeContext = opts.knowledgeContext;
|
|
16399
|
+
const json = !!opts.json;
|
|
16400
|
+
if (!taskName) {
|
|
16401
|
+
const diagnosisOptions = {
|
|
16402
|
+
fix: false,
|
|
16403
|
+
dryRun: true,
|
|
16404
|
+
yes: false,
|
|
16405
|
+
noLlm: true,
|
|
16406
|
+
timeoutMs,
|
|
16407
|
+
knowledgeContext,
|
|
16408
|
+
json: false
|
|
16409
|
+
};
|
|
16410
|
+
if (json) {
|
|
16411
|
+
const reports = [];
|
|
16412
|
+
for (const task2 of doctorTasks) {
|
|
16413
|
+
const report = await captureDoctorTaskOutput(
|
|
16414
|
+
task2,
|
|
16415
|
+
db,
|
|
16416
|
+
diagnosisOptions
|
|
16417
|
+
);
|
|
16418
|
+
reports.push({
|
|
16419
|
+
name: task2.name,
|
|
16420
|
+
description: task2.description,
|
|
16421
|
+
...report
|
|
16422
|
+
});
|
|
16423
|
+
}
|
|
16424
|
+
const success = reports.every((report) => !report.error);
|
|
16425
|
+
console.log(
|
|
16426
|
+
JSON.stringify({ success, readOnly: true, tasks: reports })
|
|
16427
|
+
);
|
|
16428
|
+
if (!success) process.exitCode = 1;
|
|
16429
|
+
} else {
|
|
16430
|
+
console.log("ZAM doctor read-only diagnosis (LLM disabled):");
|
|
16431
|
+
for (const task2 of doctorTasks) {
|
|
16432
|
+
console.log(`
|
|
16433
|
+
[${task2.name}] ${task2.description}`);
|
|
16434
|
+
await task2.run(db, diagnosisOptions);
|
|
16435
|
+
}
|
|
16436
|
+
}
|
|
16437
|
+
return;
|
|
16438
|
+
}
|
|
16439
|
+
const task = doctorTasks.find((t2) => t2.name === taskName);
|
|
16440
|
+
if (!task) {
|
|
16441
|
+
if (json) {
|
|
16442
|
+
console.log(
|
|
16443
|
+
JSON.stringify({
|
|
16444
|
+
success: false,
|
|
16445
|
+
error: `Unknown task: ${taskName}`
|
|
16446
|
+
})
|
|
16447
|
+
);
|
|
16448
|
+
} else {
|
|
16449
|
+
console.error(`Unknown task: ${taskName}`);
|
|
16450
|
+
}
|
|
16451
|
+
process.exit(1);
|
|
16452
|
+
}
|
|
16453
|
+
if (!json) {
|
|
16454
|
+
console.log(
|
|
16455
|
+
`Running doctor task: ${task.name} (fix=${fix}, dryRun=${dryRun}${noLlm ? ", noLlm=true" : ""})`
|
|
16456
|
+
);
|
|
16457
|
+
}
|
|
16458
|
+
const taskOptions = {
|
|
16459
|
+
fix,
|
|
16460
|
+
dryRun,
|
|
16461
|
+
yes,
|
|
16462
|
+
noLlm,
|
|
16463
|
+
timeoutMs,
|
|
16464
|
+
knowledgeContext,
|
|
16465
|
+
json
|
|
16466
|
+
};
|
|
16467
|
+
if (json && fix && !yes) {
|
|
16468
|
+
console.log(
|
|
16469
|
+
JSON.stringify({
|
|
16470
|
+
success: false,
|
|
16471
|
+
error: "--json --fix requires --yes; interactive prompts are not machine-readable"
|
|
16472
|
+
})
|
|
16473
|
+
);
|
|
16474
|
+
process.exitCode = 1;
|
|
16475
|
+
return;
|
|
16476
|
+
}
|
|
16477
|
+
if (json && fix && yes && (task.name === "duplicates" || task.name === "domains")) {
|
|
16478
|
+
console.log(
|
|
16479
|
+
JSON.stringify({
|
|
16480
|
+
success: false,
|
|
16481
|
+
error: `${task.name} fixes require interactive choices and cannot use --json --yes`
|
|
16482
|
+
})
|
|
16483
|
+
);
|
|
16484
|
+
process.exitCode = 1;
|
|
16485
|
+
return;
|
|
16486
|
+
}
|
|
16487
|
+
if (json) {
|
|
16488
|
+
const report = await captureDoctorTaskOutput(task, db, taskOptions);
|
|
16489
|
+
let payload = {
|
|
16490
|
+
success: report.error === void 0,
|
|
16491
|
+
task: task.name,
|
|
16492
|
+
...report
|
|
16493
|
+
};
|
|
16494
|
+
if (task.name === "contexts" && !report.error && report.lines.length === 1) {
|
|
16495
|
+
try {
|
|
16496
|
+
payload = JSON.parse(report.lines[0]);
|
|
16497
|
+
} catch {
|
|
16498
|
+
}
|
|
16499
|
+
}
|
|
16500
|
+
console.log(JSON.stringify(payload));
|
|
16501
|
+
if (report.error || payload.success === false) {
|
|
16502
|
+
process.exitCode = 1;
|
|
16503
|
+
}
|
|
16504
|
+
return;
|
|
16505
|
+
}
|
|
16506
|
+
await task.run(db, taskOptions);
|
|
16507
|
+
});
|
|
16508
|
+
});
|
|
16509
|
+
|
|
14932
16510
|
// src/cli/commands/git-sync.ts
|
|
14933
16511
|
import { execSync as execSync5 } from "child_process";
|
|
14934
16512
|
import { chmodSync as chmodSync2, existsSync as existsSync18, writeFileSync as writeFileSync8 } from "fs";
|
|
14935
16513
|
import { join as join19 } from "path";
|
|
14936
|
-
import { Command as
|
|
16514
|
+
import { Command as Command6 } from "commander";
|
|
14937
16515
|
function installHook2() {
|
|
14938
16516
|
const gitDir = join19(process.cwd(), ".git");
|
|
14939
16517
|
if (!existsSync18(gitDir)) {
|
|
@@ -14963,7 +16541,7 @@ zam git-sync --commit HEAD --quiet
|
|
|
14963
16541
|
process.exit(1);
|
|
14964
16542
|
}
|
|
14965
16543
|
}
|
|
14966
|
-
var gitSyncCommand = new
|
|
16544
|
+
var gitSyncCommand = new Command6("git-sync").description("Sync learning cards with recent Git file modifications").option("--commit <hash>", "Git commit hash to check", "HEAD").option("--user <id>", "User ID (default: whoami)").option("--install", "Install git post-commit hook in current repo").option("--quiet", "Suppress verbose output").action(async (opts) => {
|
|
14967
16545
|
if (opts.install) {
|
|
14968
16546
|
installHook2();
|
|
14969
16547
|
return;
|
|
@@ -15055,7 +16633,7 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
15055
16633
|
import { existsSync as existsSync19, mkdirSync as mkdirSync11 } from "fs";
|
|
15056
16634
|
import { resolve as resolve5 } from "path";
|
|
15057
16635
|
import { input as input2 } from "@inquirer/prompts";
|
|
15058
|
-
import { Command as
|
|
16636
|
+
import { Command as Command7 } from "commander";
|
|
15059
16637
|
async function resolveGoalsDir() {
|
|
15060
16638
|
let goalsDir;
|
|
15061
16639
|
let db;
|
|
@@ -15068,7 +16646,7 @@ async function resolveGoalsDir() {
|
|
|
15068
16646
|
}
|
|
15069
16647
|
return goalsDir ? resolve5(goalsDir) : resolve5("goals");
|
|
15070
16648
|
}
|
|
15071
|
-
var goalCommand = new
|
|
16649
|
+
var goalCommand = new Command7("goal").description(
|
|
15072
16650
|
"Manage learning goals (markdown files)"
|
|
15073
16651
|
);
|
|
15074
16652
|
goalCommand.command("list").description("List all goals").option(
|
|
@@ -15241,7 +16819,7 @@ import { existsSync as existsSync20, mkdirSync as mkdirSync12, writeFileSync as
|
|
|
15241
16819
|
import { homedir as homedir11 } from "os";
|
|
15242
16820
|
import { join as join20, resolve as resolve6 } from "path";
|
|
15243
16821
|
import { confirm, input as input3 } from "@inquirer/prompts";
|
|
15244
|
-
import { Command as
|
|
16822
|
+
import { Command as Command8 } from "commander";
|
|
15245
16823
|
var HOME2 = homedir11();
|
|
15246
16824
|
function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
15247
16825
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
@@ -15278,7 +16856,7 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
15278
16856
|
);
|
|
15279
16857
|
}
|
|
15280
16858
|
}
|
|
15281
|
-
var initCommand = new
|
|
16859
|
+
var initCommand = new Command8("init").description("Launch the guided interactive onboarding wizard").action(async () => {
|
|
15282
16860
|
printLine();
|
|
15283
16861
|
console.log(
|
|
15284
16862
|
"\x1B[1m\x1B[32m ZAM \u2014 The Symbiotic Learning Agent Onboarding\x1B[0m"
|
|
@@ -15453,9 +17031,202 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
|
|
|
15453
17031
|
printLine();
|
|
15454
17032
|
});
|
|
15455
17033
|
|
|
17034
|
+
// src/cli/commands/knowledge-context.ts
|
|
17035
|
+
import { Command as Command9 } from "commander";
|
|
17036
|
+
var knowledgeContextCommand = new Command9("knowledge-context").alias("kc").description("Manage knowledge contexts (work, school, private)");
|
|
17037
|
+
function commandError(message, json) {
|
|
17038
|
+
if (json) {
|
|
17039
|
+
jsonOut({ success: false, error: message });
|
|
17040
|
+
} else {
|
|
17041
|
+
console.error(message);
|
|
17042
|
+
}
|
|
17043
|
+
process.exit(1);
|
|
17044
|
+
}
|
|
17045
|
+
knowledgeContextCommand.command("list").description("List all knowledge contexts").option("--json", "Output as JSON").action(async (opts) => {
|
|
17046
|
+
await withDb(async (db) => {
|
|
17047
|
+
const contexts = await listKnowledgeContexts(db);
|
|
17048
|
+
if (opts.json) {
|
|
17049
|
+
jsonOut(contexts);
|
|
17050
|
+
return;
|
|
17051
|
+
}
|
|
17052
|
+
if (contexts.length === 0) {
|
|
17053
|
+
console.log("No knowledge contexts registered yet.");
|
|
17054
|
+
return;
|
|
17055
|
+
}
|
|
17056
|
+
console.log(`Knowledge Contexts (${contexts.length})`);
|
|
17057
|
+
console.log("\u2500".repeat(60));
|
|
17058
|
+
for (const c of contexts) {
|
|
17059
|
+
const lang = c.language ? ` (${c.language})` : "";
|
|
17060
|
+
const label = c.label ? ` - ${c.label}` : "";
|
|
17061
|
+
console.log(` ${c.name.padEnd(20)}${label}${lang}`);
|
|
17062
|
+
}
|
|
17063
|
+
});
|
|
17064
|
+
});
|
|
17065
|
+
knowledgeContextCommand.command("create").description("Create a new knowledge context").requiredOption(
|
|
17066
|
+
"--name <name>",
|
|
17067
|
+
"Unique context identifier (e.g. work-company)"
|
|
17068
|
+
).option("--label <label>", "Display name").option(
|
|
17069
|
+
"--language <language>",
|
|
17070
|
+
"Default language (BCP-47 code, e.g. en, de)"
|
|
17071
|
+
).option("--json", "Output as JSON").action(async (opts) => {
|
|
17072
|
+
await withDb(async (db) => {
|
|
17073
|
+
const context = await createKnowledgeContext(db, {
|
|
17074
|
+
name: opts.name,
|
|
17075
|
+
label: opts.label || null,
|
|
17076
|
+
language: opts.language || null
|
|
17077
|
+
});
|
|
17078
|
+
if (opts.json) {
|
|
17079
|
+
jsonOut({ success: true, context });
|
|
17080
|
+
return;
|
|
17081
|
+
}
|
|
17082
|
+
console.log(`Created knowledge context: ${context.name}`);
|
|
17083
|
+
if (context.label) console.log(` Label: ${context.label}`);
|
|
17084
|
+
if (context.language) console.log(` Language: ${context.language}`);
|
|
17085
|
+
});
|
|
17086
|
+
});
|
|
17087
|
+
knowledgeContextCommand.command("delete").description("Delete a knowledge context").requiredOption("--name <name>", "Context name").option(
|
|
17088
|
+
"--confirm",
|
|
17089
|
+
"Confirm removal of the context and its token assignments"
|
|
17090
|
+
).option("--json", "Output as JSON").action(async (opts) => {
|
|
17091
|
+
await withDb(async (db) => {
|
|
17092
|
+
const context = await getKnowledgeContextByName(db, opts.name);
|
|
17093
|
+
if (!context) {
|
|
17094
|
+
commandError(`Knowledge context not found: ${opts.name}`, !!opts.json);
|
|
17095
|
+
}
|
|
17096
|
+
const assignmentCount = (await db.prepare(
|
|
17097
|
+
"SELECT COUNT(*) AS n FROM token_contexts WHERE context_id = ?"
|
|
17098
|
+
).get(context.id)).n;
|
|
17099
|
+
if (!opts.confirm) {
|
|
17100
|
+
if (opts.json) {
|
|
17101
|
+
jsonOut({
|
|
17102
|
+
success: true,
|
|
17103
|
+
preview: true,
|
|
17104
|
+
requiresConfirmation: true,
|
|
17105
|
+
name: context.name,
|
|
17106
|
+
tokenAssignments: assignmentCount
|
|
17107
|
+
});
|
|
17108
|
+
} else {
|
|
17109
|
+
console.log(
|
|
17110
|
+
`Deleting "${context.name}" removes ${assignmentCount} token assignment(s). Re-run with --confirm.`
|
|
17111
|
+
);
|
|
17112
|
+
}
|
|
17113
|
+
return;
|
|
17114
|
+
}
|
|
17115
|
+
await deleteKnowledgeContext(db, context.id);
|
|
17116
|
+
if (getActiveWorkspaceContext() === context.name) {
|
|
17117
|
+
setActiveWorkspaceContext(void 0);
|
|
17118
|
+
}
|
|
17119
|
+
if (opts.json) {
|
|
17120
|
+
jsonOut({
|
|
17121
|
+
success: true,
|
|
17122
|
+
name: context.name,
|
|
17123
|
+
tokenAssignmentsRemoved: assignmentCount
|
|
17124
|
+
});
|
|
17125
|
+
return;
|
|
17126
|
+
}
|
|
17127
|
+
console.log(`Deleted knowledge context: ${context.name}`);
|
|
17128
|
+
});
|
|
17129
|
+
});
|
|
17130
|
+
knowledgeContextCommand.command("assign").description("Assign a token to a knowledge context").requiredOption("--token <slug>", "Token slug").requiredOption("--context <name>", "Context name").option("--json", "Output as JSON").action(async (opts) => {
|
|
17131
|
+
await withDb(async (db) => {
|
|
17132
|
+
const context = await getKnowledgeContextByName(db, opts.context);
|
|
17133
|
+
if (!context) {
|
|
17134
|
+
commandError(
|
|
17135
|
+
`Knowledge context not found: ${opts.context}`,
|
|
17136
|
+
!!opts.json
|
|
17137
|
+
);
|
|
17138
|
+
}
|
|
17139
|
+
const token = await getTokenBySlug(db, opts.token);
|
|
17140
|
+
if (!token) {
|
|
17141
|
+
commandError(`Token not found: ${opts.token}`, !!opts.json);
|
|
17142
|
+
}
|
|
17143
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
17144
|
+
if (opts.json) {
|
|
17145
|
+
jsonOut({ success: true, token: token.slug, context: context.name });
|
|
17146
|
+
return;
|
|
17147
|
+
}
|
|
17148
|
+
console.log(
|
|
17149
|
+
`Assigned token "${token.slug}" to context "${context.name}"`
|
|
17150
|
+
);
|
|
17151
|
+
});
|
|
17152
|
+
});
|
|
17153
|
+
knowledgeContextCommand.command("unassign").description("Remove a token from a knowledge context").requiredOption("--token <slug>", "Token slug").requiredOption("--context <name>", "Context name").option("--json", "Output as JSON").action(async (opts) => {
|
|
17154
|
+
await withDb(async (db) => {
|
|
17155
|
+
const context = await getKnowledgeContextByName(db, opts.context);
|
|
17156
|
+
if (!context) {
|
|
17157
|
+
commandError(
|
|
17158
|
+
`Knowledge context not found: ${opts.context}`,
|
|
17159
|
+
!!opts.json
|
|
17160
|
+
);
|
|
17161
|
+
}
|
|
17162
|
+
const token = await getTokenBySlug(db, opts.token);
|
|
17163
|
+
if (!token) {
|
|
17164
|
+
commandError(`Token not found: ${opts.token}`, !!opts.json);
|
|
17165
|
+
}
|
|
17166
|
+
await unassignTokenFromContext(db, token.id, context.id);
|
|
17167
|
+
if (opts.json) {
|
|
17168
|
+
jsonOut({ success: true, token: token.slug, context: context.name });
|
|
17169
|
+
return;
|
|
17170
|
+
}
|
|
17171
|
+
console.log(
|
|
17172
|
+
`Unassigned token "${token.slug}" from context "${context.name}"`
|
|
17173
|
+
);
|
|
17174
|
+
});
|
|
17175
|
+
});
|
|
17176
|
+
knowledgeContextCommand.command("use").description("Set the active knowledge context default for this device").argument("[name]", "Context name to use (use without argument to clear)").option("--json", "Output as JSON").action(async (name, opts) => {
|
|
17177
|
+
await withDb(async (db) => {
|
|
17178
|
+
if (!name) {
|
|
17179
|
+
if (!setActiveWorkspaceContext(void 0)) {
|
|
17180
|
+
commandError("No active workspace configured", !!opts.json);
|
|
17181
|
+
}
|
|
17182
|
+
if (opts.json) {
|
|
17183
|
+
jsonOut({ success: true, activeContext: null });
|
|
17184
|
+
return;
|
|
17185
|
+
}
|
|
17186
|
+
console.log("Cleared active knowledge context default.");
|
|
17187
|
+
return;
|
|
17188
|
+
}
|
|
17189
|
+
const context = await getKnowledgeContextByName(db, name);
|
|
17190
|
+
if (!context) {
|
|
17191
|
+
commandError(`Knowledge context not found: ${name}`, !!opts.json);
|
|
17192
|
+
}
|
|
17193
|
+
if (!setActiveWorkspaceContext(context.name)) {
|
|
17194
|
+
commandError("No active workspace configured", !!opts.json);
|
|
17195
|
+
}
|
|
17196
|
+
if (opts.json) {
|
|
17197
|
+
jsonOut({ success: true, activeContext: context.name });
|
|
17198
|
+
return;
|
|
17199
|
+
}
|
|
17200
|
+
console.log(`Active knowledge context set to: ${context.name}`);
|
|
17201
|
+
});
|
|
17202
|
+
});
|
|
17203
|
+
knowledgeContextCommand.command("show").description("Show the active knowledge context default for this device").option("--json", "Output as JSON").action(async (opts) => {
|
|
17204
|
+
await withDb(async (db) => {
|
|
17205
|
+
const configured = getActiveWorkspaceContext();
|
|
17206
|
+
const active = configured ? await getKnowledgeContextByName(db, configured) : void 0;
|
|
17207
|
+
if (opts.json) {
|
|
17208
|
+
jsonOut({
|
|
17209
|
+
success: true,
|
|
17210
|
+
activeContext: active?.name ?? null,
|
|
17211
|
+
staleContext: configured && !active ? configured : null
|
|
17212
|
+
});
|
|
17213
|
+
return;
|
|
17214
|
+
}
|
|
17215
|
+
if (active) {
|
|
17216
|
+
console.log(`Active knowledge context: ${active.name}`);
|
|
17217
|
+
} else if (configured) {
|
|
17218
|
+
console.warn(
|
|
17219
|
+
`Configured knowledge context no longer exists: ${configured}`
|
|
17220
|
+
);
|
|
17221
|
+
} else {
|
|
17222
|
+
console.log("No active knowledge context default set.");
|
|
17223
|
+
}
|
|
17224
|
+
});
|
|
17225
|
+
});
|
|
17226
|
+
|
|
15456
17227
|
// src/cli/commands/learn.ts
|
|
15457
17228
|
import { input as input5 } from "@inquirer/prompts";
|
|
15458
|
-
import { Command as
|
|
17229
|
+
import { Command as Command10 } from "commander";
|
|
15459
17230
|
|
|
15460
17231
|
// src/cli/learn-format.ts
|
|
15461
17232
|
var BLOOM_VERBS3 = {
|
|
@@ -15471,16 +17242,22 @@ function clampBloom(n) {
|
|
|
15471
17242
|
function formatHeader(input8) {
|
|
15472
17243
|
const lvl = clampBloom(input8.bloomLevel);
|
|
15473
17244
|
const parts = [`${BLOOM_VERBS3[lvl]} (Bloom ${lvl})`];
|
|
15474
|
-
|
|
17245
|
+
const name = input8.title || input8.slug;
|
|
17246
|
+
if (name) {
|
|
17247
|
+
parts.push(name);
|
|
17248
|
+
} else if (input8.domain?.trim()) {
|
|
15475
17249
|
parts.push(input8.domain.trim());
|
|
15476
17250
|
}
|
|
15477
17251
|
return parts.join(" \xB7 ");
|
|
15478
17252
|
}
|
|
15479
17253
|
function formatReveal(input8) {
|
|
15480
17254
|
const lines = [
|
|
15481
|
-
`Token:
|
|
17255
|
+
`Token: ${input8.title || input8.slug}`,
|
|
15482
17256
|
`Concept: ${input8.concept}`
|
|
15483
17257
|
];
|
|
17258
|
+
if (input8.title) {
|
|
17259
|
+
lines.push(`ID: #${input8.slug}`);
|
|
17260
|
+
}
|
|
15484
17261
|
if (input8.context?.trim()) {
|
|
15485
17262
|
lines.push("", `Context: ${input8.context.trim()}`);
|
|
15486
17263
|
}
|
|
@@ -15793,7 +17570,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set(["q", ":q", "quit", "stop"]);
|
|
|
15793
17570
|
function isExitPrompt(err) {
|
|
15794
17571
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
15795
17572
|
}
|
|
15796
|
-
var learnCommand = new
|
|
17573
|
+
var learnCommand = new Command10("learn").description(
|
|
15797
17574
|
"Run a spoiler-free, in-process learning session (recall \u2192 reveal \u2192 self-rate)"
|
|
15798
17575
|
).option("--user <id>", "User ID (default: whoami)").option("--max-new <n>", "Maximum new cards", "10").option("--max-reviews <n>", "Maximum review cards", "50").option("--no-resolve", "Skip resolving source_link into the revealed answer").action(async (opts) => {
|
|
15799
17576
|
let db;
|
|
@@ -15867,7 +17644,7 @@ ${t(locale, "welcome", { count: queue.items.length })}`);
|
|
|
15867
17644
|
console.log(`
|
|
15868
17645
|
${"\u2500".repeat(50)}`);
|
|
15869
17646
|
console.log(
|
|
15870
|
-
`[${index + 1}/${queue.items.length}] ${formatHeader(item)}`
|
|
17647
|
+
`[${index + 1}/${queue.items.length}] ${formatHeader({ bloomLevel: item.bloomLevel, domain: item.domain })}`
|
|
15871
17648
|
);
|
|
15872
17649
|
console.log(`
|
|
15873
17650
|
${prompt.question}`);
|
|
@@ -15931,6 +17708,7 @@ ${t(locale, "eval_skipped", { reason: err.message })}`
|
|
|
15931
17708
|
);
|
|
15932
17709
|
const reveal = formatReveal({
|
|
15933
17710
|
slug: item.slug,
|
|
17711
|
+
title: item.title,
|
|
15934
17712
|
concept: item.concept,
|
|
15935
17713
|
context: token?.context,
|
|
15936
17714
|
resolved
|
|
@@ -16038,8 +17816,8 @@ learnCommand.command("open").description("Open a new terminal window running zam
|
|
|
16038
17816
|
});
|
|
16039
17817
|
|
|
16040
17818
|
// src/cli/commands/monitor.ts
|
|
16041
|
-
import { Command as
|
|
16042
|
-
var monitorCommand = new
|
|
17819
|
+
import { Command as Command11 } from "commander";
|
|
17820
|
+
var monitorCommand = new Command11("monitor").description(
|
|
16043
17821
|
"Shell observation for real-time task monitoring"
|
|
16044
17822
|
);
|
|
16045
17823
|
monitorCommand.command("start").description("Output shell hook code to install monitoring").requiredOption("--session <id>", "Session ID to monitor").option(
|
|
@@ -16221,8 +17999,8 @@ monitorCommand.command("open").description("Open a new monitored terminal window
|
|
|
16221
17999
|
});
|
|
16222
18000
|
|
|
16223
18001
|
// src/cli/commands/observer.ts
|
|
16224
|
-
import { Command as
|
|
16225
|
-
var observerCommand = new
|
|
18002
|
+
import { Command as Command12 } from "commander";
|
|
18003
|
+
var observerCommand = new Command12("observer").description(
|
|
16226
18004
|
"Configure what the UI observer may capture (Layer 2 policy)"
|
|
16227
18005
|
);
|
|
16228
18006
|
function applyObserverListChange(current, entry, op) {
|
|
@@ -16285,7 +18063,7 @@ observerCommand.command("revoke <process>").description("Remove a process from o
|
|
|
16285
18063
|
|
|
16286
18064
|
// src/cli/commands/profile.ts
|
|
16287
18065
|
import { dirname as dirname6, resolve as resolve7 } from "path";
|
|
16288
|
-
import { Command as
|
|
18066
|
+
import { Command as Command13 } from "commander";
|
|
16289
18067
|
var C2 = {
|
|
16290
18068
|
reset: "\x1B[0m",
|
|
16291
18069
|
bold: "\x1B[1m",
|
|
@@ -16302,7 +18080,7 @@ function render(profile) {
|
|
|
16302
18080
|
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
16303
18081
|
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
16304
18082
|
}
|
|
16305
|
-
var profileCommand = new
|
|
18083
|
+
var profileCommand = new Command13("profile").description("Show or change this machine's ZAM install profile").option("--mode <mode>", "Set install mode: developer | default").option("--dir <path>", "Set the personal-content folder").option("--json", "Output as JSON").action(async (opts) => {
|
|
16306
18084
|
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
16307
18085
|
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
16308
18086
|
process.exit(1);
|
|
@@ -16342,8 +18120,8 @@ var profileCommand = new Command11("profile").description("Show or change this m
|
|
|
16342
18120
|
|
|
16343
18121
|
// src/cli/commands/provider.ts
|
|
16344
18122
|
import { password as password2 } from "@inquirer/prompts";
|
|
16345
|
-
import { Command as
|
|
16346
|
-
var providerCommand = new
|
|
18123
|
+
import { Command as Command14 } from "commander";
|
|
18124
|
+
var providerCommand = new Command14("provider").description(
|
|
16347
18125
|
"Configure role-based AI providers (url/model/flavor/key, per role)"
|
|
16348
18126
|
);
|
|
16349
18127
|
providerCommand.command("list").description("Show configured providers, role bindings, and key status").option("--json", "Output as JSON").option("--machine", "Read machine-local providers from ~/.zam/config.json").action(async (opts) => {
|
|
@@ -16578,16 +18356,33 @@ providerCommand.command("clear-key <ref>").description("Remove a stored API key"
|
|
|
16578
18356
|
});
|
|
16579
18357
|
|
|
16580
18358
|
// src/cli/commands/review.ts
|
|
16581
|
-
import { Command as
|
|
16582
|
-
var reviewCommand = new
|
|
18359
|
+
import { Command as Command15 } from "commander";
|
|
18360
|
+
var reviewCommand = new Command15("review").description("Start an interactive review session").option("--user <id>", "User ID (default: whoami)").option("--max-new <n>", "Maximum new cards", "10").option("--max-reviews <n>", "Maximum review cards", "50").option("--no-resolve", "Skip resolving source_link into inline context").option(
|
|
18361
|
+
"--knowledge-context <context>",
|
|
18362
|
+
"Filter review queue by knowledge context"
|
|
18363
|
+
).action(async (opts) => {
|
|
16583
18364
|
let db;
|
|
16584
18365
|
try {
|
|
16585
18366
|
db = await openDatabase();
|
|
16586
18367
|
const userId = await resolveUser(opts, db);
|
|
18368
|
+
let resolvedContext;
|
|
18369
|
+
if (opts.knowledgeContext) {
|
|
18370
|
+
const context = await getKnowledgeContextByName(
|
|
18371
|
+
db,
|
|
18372
|
+
opts.knowledgeContext
|
|
18373
|
+
);
|
|
18374
|
+
if (!context) {
|
|
18375
|
+
throw new Error(
|
|
18376
|
+
`Knowledge context not found: ${opts.knowledgeContext}`
|
|
18377
|
+
);
|
|
18378
|
+
}
|
|
18379
|
+
resolvedContext = context.name;
|
|
18380
|
+
}
|
|
16587
18381
|
const queue = await buildReviewQueue(db, {
|
|
16588
18382
|
userId,
|
|
16589
18383
|
maxNew: Number(opts.maxNew),
|
|
16590
|
-
maxReviews: Number(opts.maxReviews)
|
|
18384
|
+
maxReviews: Number(opts.maxReviews),
|
|
18385
|
+
knowledgeContext: resolvedContext
|
|
16591
18386
|
});
|
|
16592
18387
|
if (queue.items.length === 0) {
|
|
16593
18388
|
console.log("No cards due for review. You're all caught up!");
|
|
@@ -16596,6 +18391,9 @@ var reviewCommand = new Command13("review").description("Start an interactive re
|
|
|
16596
18391
|
}
|
|
16597
18392
|
console.log(`
|
|
16598
18393
|
Review session: ${queue.items.length} card(s)`);
|
|
18394
|
+
if (resolvedContext) {
|
|
18395
|
+
console.log(` Context: ${resolvedContext}`);
|
|
18396
|
+
}
|
|
16599
18397
|
console.log(
|
|
16600
18398
|
` New: ${queue.newCount} Review: ${queue.reviewCount} Relearn: ${queue.relearnCount}`
|
|
16601
18399
|
);
|
|
@@ -16617,9 +18415,8 @@ Review session: ${queue.items.length} card(s)`);
|
|
|
16617
18415
|
});
|
|
16618
18416
|
console.log(
|
|
16619
18417
|
`
|
|
16620
|
-
[${index + 1}/${queue.items.length}] ${
|
|
18418
|
+
[${index + 1}/${queue.items.length}] ${formatHeader({ bloomLevel: item.bloomLevel, domain: item.domain })}`
|
|
16621
18419
|
);
|
|
16622
|
-
console.log(`Domain: ${prompt.domain || "(none)"}`);
|
|
16623
18420
|
if (prompt.sourceLink) {
|
|
16624
18421
|
console.log(`Source: ${prompt.sourceLink}`);
|
|
16625
18422
|
if (opts.resolve !== false) {
|
|
@@ -16694,8 +18491,8 @@ ${"\u2550".repeat(50)}`);
|
|
|
16694
18491
|
// src/cli/commands/session.ts
|
|
16695
18492
|
import { readFileSync as readFileSync13 } from "fs";
|
|
16696
18493
|
import { input as input6, select as select2 } from "@inquirer/prompts";
|
|
16697
|
-
import { Command as
|
|
16698
|
-
var sessionCommand = new
|
|
18494
|
+
import { Command as Command16 } from "commander";
|
|
18495
|
+
var sessionCommand = new Command16("session").description(
|
|
16699
18496
|
"Manage learning sessions"
|
|
16700
18497
|
);
|
|
16701
18498
|
sessionCommand.command("start").description("Start a new learning session (review \u2192 task)").option("--user <id>", "User ID (default: whoami)").option("--task <description>", "Task description (interactive if omitted)").option(
|
|
@@ -16819,9 +18616,8 @@ Time limit reached (${maxMinutes} min). Moving to task selection.`
|
|
|
16819
18616
|
});
|
|
16820
18617
|
const elapsed = Math.round((Date.now() - startTime) / 6e4);
|
|
16821
18618
|
console.log(
|
|
16822
|
-
`[${index + 1}/${queue.items.length}] ${
|
|
18619
|
+
`[${index + 1}/${queue.items.length}] ${formatHeader({ bloomLevel: item.bloomLevel, domain: item.domain })} (${elapsed}/${maxMinutes} min)`
|
|
16823
18620
|
);
|
|
16824
|
-
console.log(`Domain: ${prompt.domain || "(none)"}`);
|
|
16825
18621
|
console.log(`
|
|
16826
18622
|
${prompt.question}
|
|
16827
18623
|
`);
|
|
@@ -17075,8 +18871,8 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
17075
18871
|
|
|
17076
18872
|
// src/cli/commands/settings.ts
|
|
17077
18873
|
import { existsSync as existsSync21 } from "fs";
|
|
17078
|
-
import { Command as
|
|
17079
|
-
var settingsCommand = new
|
|
18874
|
+
import { Command as Command17 } from "commander";
|
|
18875
|
+
var settingsCommand = new Command17("settings").description(
|
|
17080
18876
|
"Manage user settings"
|
|
17081
18877
|
);
|
|
17082
18878
|
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
@@ -17252,7 +19048,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
17252
19048
|
|
|
17253
19049
|
// src/cli/commands/setup.ts
|
|
17254
19050
|
import { resolve as resolve8 } from "path";
|
|
17255
|
-
import { Command as
|
|
19051
|
+
import { Command as Command18 } from "commander";
|
|
17256
19052
|
function formatDatabaseInitTarget(target) {
|
|
17257
19053
|
switch (target.kind) {
|
|
17258
19054
|
case "local":
|
|
@@ -17293,7 +19089,7 @@ async function activateMachineProviderConfig(db) {
|
|
|
17293
19089
|
` activate ${providerCount} machine-local provider(s) from ~/.zam/config.json`
|
|
17294
19090
|
);
|
|
17295
19091
|
}
|
|
17296
|
-
var setupCommand = new
|
|
19092
|
+
var setupCommand = new Command18("setup").description(
|
|
17297
19093
|
"Link ZAM skill directories into this workspace and initialize the database"
|
|
17298
19094
|
).option("--force", "replace an unmanaged existing ZAM skill directory", false).option("--skip-init", "skip database initialization", false).option("--skip-claude-md", "skip CLAUDE.md generation", false).option("--skip-agents-md", "skip AGENTS.md generation", false).option("--target <path>", "repository/workspace directory to set up").option(
|
|
17299
19095
|
"--agents <list>",
|
|
@@ -17347,8 +19143,8 @@ var setupCommand = new Command16("setup").description(
|
|
|
17347
19143
|
);
|
|
17348
19144
|
|
|
17349
19145
|
// src/cli/commands/skill.ts
|
|
17350
|
-
import { Command as
|
|
17351
|
-
var skillCommand = new
|
|
19146
|
+
import { Command as Command19 } from "commander";
|
|
19147
|
+
var skillCommand = new Command19("skill").description(
|
|
17352
19148
|
"Manage agent skill entries (task recipes)"
|
|
17353
19149
|
);
|
|
17354
19150
|
skillCommand.command("list").description("List all agent skills").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -17435,7 +19231,7 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
17435
19231
|
// src/cli/commands/snapshot.ts
|
|
17436
19232
|
import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
17437
19233
|
import { dirname as dirname7, join as join21 } from "path";
|
|
17438
|
-
import { Command as
|
|
19234
|
+
import { Command as Command20 } from "commander";
|
|
17439
19235
|
function defaultOutName() {
|
|
17440
19236
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
17441
19237
|
return `zam-snapshot-${stamp}.sql`;
|
|
@@ -17449,7 +19245,7 @@ function summarize(tables) {
|
|
|
17449
19245
|
}
|
|
17450
19246
|
return { total, nonEmpty };
|
|
17451
19247
|
}
|
|
17452
|
-
var exportCmd = new
|
|
19248
|
+
var exportCmd = new Command20("export").description("Write a portable SQL-text snapshot of the database").option("--out <file>", "Output file (use - for stdout)").action(async (opts) => {
|
|
17453
19249
|
let db;
|
|
17454
19250
|
try {
|
|
17455
19251
|
db = await openDatabaseWithSync({ initialize: true });
|
|
@@ -17479,7 +19275,7 @@ var exportCmd = new Command18("export").description("Write a portable SQL-text s
|
|
|
17479
19275
|
process.exit(1);
|
|
17480
19276
|
}
|
|
17481
19277
|
});
|
|
17482
|
-
var importCmd = new
|
|
19278
|
+
var importCmd = new Command20("import").description("Restore a snapshot into the database").argument("<file>", "Snapshot file to restore").option("--force", "Overwrite a non-empty database", false).action(async (file, opts) => {
|
|
17483
19279
|
let db;
|
|
17484
19280
|
try {
|
|
17485
19281
|
if (!existsSync22(file)) {
|
|
@@ -17502,7 +19298,7 @@ var importCmd = new Command18("import").description("Restore a snapshot into the
|
|
|
17502
19298
|
process.exit(1);
|
|
17503
19299
|
}
|
|
17504
19300
|
});
|
|
17505
|
-
var verifyCmd = new
|
|
19301
|
+
var verifyCmd = new Command20("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
17506
19302
|
try {
|
|
17507
19303
|
if (!existsSync22(file)) {
|
|
17508
19304
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
@@ -17520,11 +19316,11 @@ var verifyCmd = new Command18("verify").description("Check a snapshot's manifest
|
|
|
17520
19316
|
process.exit(1);
|
|
17521
19317
|
}
|
|
17522
19318
|
});
|
|
17523
|
-
var snapshotCommand = new
|
|
19319
|
+
var snapshotCommand = new Command20("snapshot").description("Export, import, or verify a portable database snapshot").addCommand(exportCmd).addCommand(importCmd).addCommand(verifyCmd);
|
|
17524
19320
|
|
|
17525
19321
|
// src/cli/commands/stats.ts
|
|
17526
|
-
import { Command as
|
|
17527
|
-
var statsCommand = new
|
|
19322
|
+
import { Command as Command21 } from "commander";
|
|
19323
|
+
var statsCommand = new Command21("stats").description("Show learning dashboard for a user").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").action(async (opts) => {
|
|
17528
19324
|
await withDb(async (db) => {
|
|
17529
19325
|
const userId = await resolveUser(opts, db);
|
|
17530
19326
|
const stats = await getUserStats(db, userId);
|
|
@@ -17560,11 +19356,19 @@ var statsCommand = new Command19("stats").description("Show learning dashboard f
|
|
|
17560
19356
|
});
|
|
17561
19357
|
|
|
17562
19358
|
// src/cli/commands/token.ts
|
|
17563
|
-
import { Command as
|
|
17564
|
-
var tokenCommand = new
|
|
19359
|
+
import { Command as Command22 } from "commander";
|
|
19360
|
+
var tokenCommand = new Command22("token").description(
|
|
17565
19361
|
"Manage knowledge tokens"
|
|
17566
19362
|
);
|
|
17567
|
-
tokenCommand.command("register").description("Register a new knowledge token").requiredOption("--slug <slug>", "Unique token slug").requiredOption("--concept <concept>", "Concept description").option("--domain <domain>", "Knowledge domain", "").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option("--source-link <link>", "Source file path or reference URL", "").option("--question <question>", "Specific question prompt for recall", "").option(
|
|
19363
|
+
tokenCommand.command("register").description("Register a new knowledge token").requiredOption("--slug <slug>", "Unique token slug").requiredOption("--concept <concept>", "Concept description").option("--domain <domain>", "Knowledge domain", "").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option("--source-link <link>", "Source file path or reference URL", "").option("--question <question>", "Specific question prompt for recall", "").option(
|
|
19364
|
+
"--knowledge-context <context>",
|
|
19365
|
+
"Assign token to a knowledge context (repeatable)",
|
|
19366
|
+
(val, memo) => {
|
|
19367
|
+
memo.push(val);
|
|
19368
|
+
return memo;
|
|
19369
|
+
},
|
|
19370
|
+
[]
|
|
19371
|
+
).option("--user <id>", "Owner of the personal card (default: whoami)").option("--no-card", "Register the token only; do not create a personal card").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
17568
19372
|
await withDb(async (db) => {
|
|
17569
19373
|
let question = opts.question || null;
|
|
17570
19374
|
if (!question) {
|
|
@@ -17574,6 +19378,10 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
17574
19378
|
opts.domain
|
|
17575
19379
|
);
|
|
17576
19380
|
}
|
|
19381
|
+
const assignedContexts = await resolveOperationKnowledgeContexts(
|
|
19382
|
+
db,
|
|
19383
|
+
opts.knowledgeContext || []
|
|
19384
|
+
);
|
|
17577
19385
|
const possibleDuplicates = await findPossibleDuplicates(db, {
|
|
17578
19386
|
concept: opts.concept,
|
|
17579
19387
|
question,
|
|
@@ -17587,6 +19395,9 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
17587
19395
|
source_link: opts.sourceLink || null,
|
|
17588
19396
|
question
|
|
17589
19397
|
});
|
|
19398
|
+
for (const context of assignedContexts) {
|
|
19399
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
19400
|
+
}
|
|
17590
19401
|
let cardUserId = null;
|
|
17591
19402
|
if (opts.card !== false) {
|
|
17592
19403
|
cardUserId = opts.user ?? await getSetting(db, "user.id");
|
|
@@ -17605,18 +19416,32 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
17605
19416
|
{
|
|
17606
19417
|
...token,
|
|
17607
19418
|
card: cardUserId ? { userId: cardUserId } : null,
|
|
17608
|
-
possible_duplicates: possibleDuplicates
|
|
19419
|
+
possible_duplicates: possibleDuplicates,
|
|
19420
|
+
knowledgeContexts: assignedContexts.map((c) => ({
|
|
19421
|
+
name: c.name,
|
|
19422
|
+
label: c.label,
|
|
19423
|
+
language: c.language
|
|
19424
|
+
}))
|
|
17609
19425
|
},
|
|
17610
19426
|
null,
|
|
17611
19427
|
2
|
|
17612
19428
|
)
|
|
17613
19429
|
);
|
|
17614
19430
|
} else {
|
|
17615
|
-
console.log(`Registered token: ${token.
|
|
19431
|
+
console.log(`Registered token: ${token.title || token.slug}`);
|
|
19432
|
+
if (token.slug !== (token.title || token.slug)) {
|
|
19433
|
+
console.log(` Slug: ${token.slug}`);
|
|
19434
|
+
}
|
|
19435
|
+
console.log(` ID: ${token.id}`);
|
|
17616
19436
|
console.log(` Concept: ${token.concept}`);
|
|
17617
19437
|
console.log(` Domain: ${token.domain || "(none)"}`);
|
|
17618
19438
|
console.log(` Bloom: ${token.bloom_level}`);
|
|
17619
19439
|
console.log(` Question: ${token.question}`);
|
|
19440
|
+
if (assignedContexts.length > 0) {
|
|
19441
|
+
console.log(
|
|
19442
|
+
` Contexts: ${assignedContexts.map((c) => c.name).join(", ")}`
|
|
19443
|
+
);
|
|
19444
|
+
}
|
|
17620
19445
|
if (token.source_link) {
|
|
17621
19446
|
console.log(` Source: ${token.source_link}`);
|
|
17622
19447
|
}
|
|
@@ -17631,8 +19456,9 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
17631
19456
|
console.log(`
|
|
17632
19457
|
WARNING: Possible duplicate tokens found:`);
|
|
17633
19458
|
for (const dup of possibleDuplicates) {
|
|
19459
|
+
const name = dup.title || dup.slug;
|
|
17634
19460
|
console.log(
|
|
17635
|
-
` - ${dup.slug}
|
|
19461
|
+
` - ${name} (slug: ${dup.slug}, similarity: ${dup.similarity.toFixed(2)})`
|
|
17636
19462
|
);
|
|
17637
19463
|
}
|
|
17638
19464
|
}
|
|
@@ -17640,7 +19466,8 @@ WARNING: Possible duplicate tokens found:`);
|
|
|
17640
19466
|
const queryText = embeddingContentForToken({
|
|
17641
19467
|
concept: opts.concept,
|
|
17642
19468
|
question: question ?? null,
|
|
17643
|
-
domain: opts.domain ?? ""
|
|
19469
|
+
domain: opts.domain ?? "",
|
|
19470
|
+
title: opts.title ?? null
|
|
17644
19471
|
});
|
|
17645
19472
|
const q2 = await embedQuery(db, queryText);
|
|
17646
19473
|
if (q2) {
|
|
@@ -17707,23 +19534,29 @@ tokenCommand.command("find").description("Fuzzy search for tokens").requiredOpti
|
|
|
17707
19534
|
console.log(`Found ${results.length} token(s):
|
|
17708
19535
|
`);
|
|
17709
19536
|
console.log(
|
|
17710
|
-
"Score Sim
|
|
19537
|
+
"Score Sim Title Concept Domain Bloom"
|
|
17711
19538
|
);
|
|
17712
19539
|
console.log("\u2500".repeat(95));
|
|
17713
19540
|
for (const t2 of results) {
|
|
17714
19541
|
const scoreStr = t2.score.toFixed(3).padEnd(6);
|
|
17715
19542
|
const simStr = (t2.similarity?.toFixed(2) ?? "-").padEnd(4);
|
|
19543
|
+
const display = getDisplayTitle(t2);
|
|
17716
19544
|
console.log(
|
|
17717
|
-
`${scoreStr} ${simStr} ${
|
|
19545
|
+
`${scoreStr} ${simStr} ${display.padEnd(28)} ${t2.concept.slice(0, 31).padEnd(31)} ${(t2.domain || "-").padEnd(11)} ${t2.bloom_level}`
|
|
17718
19546
|
);
|
|
17719
19547
|
}
|
|
19548
|
+
console.log("\n(Use slug for CLI commands -- slugs are technical IDs)");
|
|
17720
19549
|
});
|
|
17721
19550
|
});
|
|
17722
|
-
tokenCommand.command("list").description("List all tokens").option("--domain <domain>", "Filter by domain").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
19551
|
+
tokenCommand.command("list").description("List all tokens").option("--domain <domain>", "Filter by domain").option("--knowledge-context <context>", "Filter by knowledge context").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
17723
19552
|
await withDb(async (db) => {
|
|
19553
|
+
const filterOpts = {};
|
|
19554
|
+
if (opts.domain) filterOpts.domain = opts.domain;
|
|
19555
|
+
if (opts.knowledgeContext)
|
|
19556
|
+
filterOpts.knowledgeContext = opts.knowledgeContext;
|
|
17724
19557
|
const tokens = await listTokens(
|
|
17725
19558
|
db,
|
|
17726
|
-
|
|
19559
|
+
Object.keys(filterOpts).length ? filterOpts : void 0
|
|
17727
19560
|
);
|
|
17728
19561
|
if (opts.quiet) return;
|
|
17729
19562
|
if (opts.json) {
|
|
@@ -17735,16 +19568,20 @@ tokenCommand.command("list").description("List all tokens").option("--domain <do
|
|
|
17735
19568
|
return;
|
|
17736
19569
|
}
|
|
17737
19570
|
console.log(
|
|
17738
|
-
"
|
|
19571
|
+
"Title Concept Domain Bloom"
|
|
17739
19572
|
);
|
|
17740
|
-
console.log("\u2500".repeat(
|
|
19573
|
+
console.log("\u2500".repeat(85));
|
|
17741
19574
|
for (const t2 of tokens) {
|
|
19575
|
+
const display = getDisplayTitle(t2);
|
|
17742
19576
|
console.log(
|
|
17743
|
-
`${
|
|
19577
|
+
`${display.padEnd(28)} ${t2.concept.slice(0, 31).padEnd(31)} ${(t2.domain || "-").padEnd(11)} ${t2.bloom_level}`
|
|
17744
19578
|
);
|
|
17745
19579
|
}
|
|
17746
19580
|
console.log(`
|
|
17747
19581
|
${tokens.length} token(s) total.`);
|
|
19582
|
+
console.log(
|
|
19583
|
+
"(Use --slug <slug> for commands; slugs are technical identifiers)"
|
|
19584
|
+
);
|
|
17748
19585
|
});
|
|
17749
19586
|
});
|
|
17750
19587
|
tokenCommand.command("edit").description("Edit a token's mutable fields").requiredOption("--slug <slug>", "Token slug").option("--concept <concept>", "Updated concept text").option("--domain <domain>", "Updated domain (blank allowed)").option("--bloom <level>", "Updated Bloom taxonomy level (1-5)").option("--context <context>", "Updated context (blank allowed)").option(
|
|
@@ -17753,7 +19590,10 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
17753
19590
|
).option(
|
|
17754
19591
|
"--source-link <link>",
|
|
17755
19592
|
"Updated source file path or reference URL (blank allowed)"
|
|
17756
|
-
).option("--question <question>", "Updated question text (blank allowed)").option(
|
|
19593
|
+
).option("--question <question>", "Updated question text (blank allowed)").option(
|
|
19594
|
+
"--title <title>",
|
|
19595
|
+
"Updated human-friendly title for graph display (blank allowed)"
|
|
19596
|
+
).option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
17757
19597
|
await withDb(async (db) => {
|
|
17758
19598
|
const updates = {};
|
|
17759
19599
|
if (opts.concept !== void 0) updates.concept = opts.concept;
|
|
@@ -17767,6 +19607,9 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
17767
19607
|
if (opts.question !== void 0) {
|
|
17768
19608
|
updates.question = opts.question === "" ? null : opts.question;
|
|
17769
19609
|
}
|
|
19610
|
+
if (opts.title !== void 0) {
|
|
19611
|
+
updates.title = opts.title === "" ? "" : opts.title;
|
|
19612
|
+
}
|
|
17770
19613
|
if (opts.mode !== void 0) {
|
|
17771
19614
|
const validModes = ["shadowing", "copilot", "autonomy", "none"];
|
|
17772
19615
|
if (!validModes.includes(opts.mode)) {
|
|
@@ -17781,11 +19624,15 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
17781
19624
|
jsonOut(token);
|
|
17782
19625
|
return;
|
|
17783
19626
|
}
|
|
17784
|
-
console.log(`Updated token: ${token.slug}`);
|
|
19627
|
+
console.log(`Updated token: ${token.title || token.slug}`);
|
|
19628
|
+
if (token.slug !== (token.title || token.slug)) {
|
|
19629
|
+
console.log(` Slug: ${token.slug}`);
|
|
19630
|
+
}
|
|
17785
19631
|
console.log(` Concept: ${token.concept}`);
|
|
17786
19632
|
console.log(` Domain: ${token.domain || "(none)"}`);
|
|
17787
19633
|
console.log(` Bloom: ${token.bloom_level}`);
|
|
17788
19634
|
console.log(` Question: ${token.question || "(none)"}`);
|
|
19635
|
+
console.log(` Title: ${token.title || "(none)"}`);
|
|
17789
19636
|
console.log(` Context: ${token.context || "(none)"}`);
|
|
17790
19637
|
console.log(` Mode: ${token.symbiosis_mode ?? "none"}`);
|
|
17791
19638
|
console.log(` Source: ${token.source_link || "(none)"}`);
|
|
@@ -17904,7 +19751,11 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
17904
19751
|
console.log(JSON.stringify(status, null, 2));
|
|
17905
19752
|
return;
|
|
17906
19753
|
}
|
|
17907
|
-
console.log(`Token: ${token.
|
|
19754
|
+
console.log(`Token: ${token.title || token.slug}`);
|
|
19755
|
+
if (token.slug !== (token.title || token.slug)) {
|
|
19756
|
+
console.log(` Slug: ${token.slug}`);
|
|
19757
|
+
}
|
|
19758
|
+
console.log(` ID: ${token.id}`);
|
|
17908
19759
|
console.log(` Concept: ${token.concept}`);
|
|
17909
19760
|
console.log(` Question: ${token.question || "(none)"}`);
|
|
17910
19761
|
console.log(` Domain: ${token.domain || "(none)"}`);
|
|
@@ -17929,7 +19780,9 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
17929
19780
|
if (prereqs.length > 0) {
|
|
17930
19781
|
console.log("Prerequisites:");
|
|
17931
19782
|
for (const p of prereqs) {
|
|
17932
|
-
console.log(
|
|
19783
|
+
console.log(
|
|
19784
|
+
` - ${p.title || p.slug}: ${p.concept} (bloom ${p.bloom_level})`
|
|
19785
|
+
);
|
|
17933
19786
|
}
|
|
17934
19787
|
} else {
|
|
17935
19788
|
console.log("No prerequisites.");
|
|
@@ -17937,7 +19790,9 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
17937
19790
|
if (dependents.length > 0) {
|
|
17938
19791
|
console.log("\nDependents:");
|
|
17939
19792
|
for (const d of dependents) {
|
|
17940
|
-
console.log(
|
|
19793
|
+
console.log(
|
|
19794
|
+
` - ${d.title || d.slug}: ${d.concept} (bloom ${d.bloom_level})`
|
|
19795
|
+
);
|
|
17941
19796
|
}
|
|
17942
19797
|
}
|
|
17943
19798
|
});
|
|
@@ -17999,7 +19854,7 @@ import { existsSync as existsSync23 } from "fs";
|
|
|
17999
19854
|
import { homedir as homedir12 } from "os";
|
|
18000
19855
|
import { dirname as dirname8, join as join22 } from "path";
|
|
18001
19856
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
18002
|
-
import { Command as
|
|
19857
|
+
import { Command as Command23 } from "commander";
|
|
18003
19858
|
var C3 = {
|
|
18004
19859
|
reset: "\x1B[0m",
|
|
18005
19860
|
red: "\x1B[31m",
|
|
@@ -18179,7 +20034,7 @@ function createShortcuts(appPath, repoRoot) {
|
|
|
18179
20034
|
console.error(`${C3.red}\u2717 Could not create shortcuts.${C3.reset}`);
|
|
18180
20035
|
}
|
|
18181
20036
|
}
|
|
18182
|
-
var uiCommand = new
|
|
20037
|
+
var uiCommand = new Command23("ui").description("Launch the ZAM Desktop GUI (Active-Recall Studio)").option("--dev", "Run in hot-reload development mode (needs Rust)").option(
|
|
18183
20038
|
"--build",
|
|
18184
20039
|
"Build the native installer for your OS (needs Rust, one-time)"
|
|
18185
20040
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
@@ -18287,7 +20142,7 @@ import { existsSync as existsSync24, readFileSync as readFileSync15, realpathSyn
|
|
|
18287
20142
|
import { dirname as dirname9, join as join23 } from "path";
|
|
18288
20143
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
18289
20144
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
18290
|
-
import { Command as
|
|
20145
|
+
import { Command as Command24 } from "commander";
|
|
18291
20146
|
var GITHUB_REPO = "zam-os/zam";
|
|
18292
20147
|
var CHANNELS = [
|
|
18293
20148
|
"developer",
|
|
@@ -18365,7 +20220,7 @@ function render2(decision) {
|
|
|
18365
20220
|
);
|
|
18366
20221
|
}
|
|
18367
20222
|
}
|
|
18368
|
-
var checkCmd = new
|
|
20223
|
+
var checkCmd = new Command24("check").description("Check whether a newer ZAM has been released").option(
|
|
18369
20224
|
"--latest <version>",
|
|
18370
20225
|
"Compare against this version instead of fetching"
|
|
18371
20226
|
).option("--channel <channel>", "Override the detected install channel").option("--json", "Output as JSON").action(
|
|
@@ -18532,7 +20387,7 @@ ${C4.dim}This install updates through the desktop app's signed updater \u2014 op
|
|
|
18532
20387
|
process.exit(1);
|
|
18533
20388
|
}
|
|
18534
20389
|
}
|
|
18535
|
-
var updateCommand = new
|
|
20390
|
+
var updateCommand = new Command24("update").description(
|
|
18536
20391
|
"Update ZAM to the latest release (use `update check` to only check)"
|
|
18537
20392
|
).option("-y, --yes", "Apply without confirmation").option(
|
|
18538
20393
|
"--force",
|
|
@@ -18542,8 +20397,8 @@ var updateCommand = new Command22("update").description(
|
|
|
18542
20397
|
}).addCommand(checkCmd);
|
|
18543
20398
|
|
|
18544
20399
|
// src/cli/commands/whoami.ts
|
|
18545
|
-
import { Command as
|
|
18546
|
-
var whoamiCommand = new
|
|
20400
|
+
import { Command as Command25 } from "commander";
|
|
20401
|
+
var whoamiCommand = new Command25("whoami").description("Show or set the default user identity").option("--set <id>", "Set the default user ID").option("--clear", "Remove the default user ID").option("--json", "Output as JSON").action(async (opts) => {
|
|
18547
20402
|
await withDb(async (db) => {
|
|
18548
20403
|
if (opts.set) {
|
|
18549
20404
|
await setSetting(db, "user.id", opts.set);
|
|
@@ -18584,7 +20439,7 @@ import { existsSync as existsSync25, writeFileSync as writeFileSync11 } from "fs
|
|
|
18584
20439
|
import { homedir as homedir13 } from "os";
|
|
18585
20440
|
import { join as join24, resolve as resolve9 } from "path";
|
|
18586
20441
|
import { confirm as confirm4, input as input7 } from "@inquirer/prompts";
|
|
18587
|
-
import { Command as
|
|
20442
|
+
import { Command as Command26 } from "commander";
|
|
18588
20443
|
function runGit2(cwd, args) {
|
|
18589
20444
|
try {
|
|
18590
20445
|
return execFileSync4("git", args, {
|
|
@@ -18602,7 +20457,7 @@ function ghRepoCreateArgs(repoName, repoVisibility) {
|
|
|
18602
20457
|
function gitRemoteArgs(githubUrl, hasOrigin) {
|
|
18603
20458
|
return hasOrigin ? ["remote", "set-url", "origin", githubUrl] : ["remote", "add", "origin", githubUrl];
|
|
18604
20459
|
}
|
|
18605
|
-
var workspaceCommand = new
|
|
20460
|
+
var workspaceCommand = new Command26("workspace").description(
|
|
18606
20461
|
"Manage your ZAM learning workspace"
|
|
18607
20462
|
);
|
|
18608
20463
|
var WORKSPACE_KINDS = [
|
|
@@ -18957,13 +20812,15 @@ var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
|
18957
20812
|
var pkg = JSON.parse(
|
|
18958
20813
|
readFileSync16(join25(__dirname, "..", "..", "package.json"), "utf-8")
|
|
18959
20814
|
);
|
|
18960
|
-
var program = new
|
|
20815
|
+
var program = new Command27();
|
|
18961
20816
|
program.name("zam").description(
|
|
18962
20817
|
"The Symbiotic Learning Kernel: Elevating Human Intelligence through AI Collaboration."
|
|
18963
20818
|
).version(pkg.version);
|
|
18964
20819
|
program.addCommand(initCommand);
|
|
18965
20820
|
program.addCommand(setupCommand);
|
|
18966
20821
|
program.addCommand(tokenCommand);
|
|
20822
|
+
program.addCommand(knowledgeContextCommand);
|
|
20823
|
+
program.addCommand(doctorCommand);
|
|
18967
20824
|
program.addCommand(cardCommand);
|
|
18968
20825
|
program.addCommand(sessionCommand);
|
|
18969
20826
|
program.addCommand(statsCommand);
|