zam-core 0.13.0 → 0.15.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.
@@ -590,7 +590,13 @@ CREATE TABLE IF NOT EXISTS tokens (
590
590
  -- validated in code, not via CHECK. Column default is 'llm' so unlabeled
591
591
  -- writes (pre-M013 rows, old snapshot restores) count as LLM-era content;
592
592
  -- createToken() defaults to 'manual' for API callers instead.
593
- question_source TEXT NOT NULL DEFAULT 'llm'
593
+ question_source TEXT NOT NULL DEFAULT 'llm',
594
+ -- Maintenance state (ADR 2026-07-18): when set, the token's binding to
595
+ -- its source is unclear (e.g. stale source_link after an article split,
596
+ -- or an ambiguous re-import). Cards of a token in maintenance are
597
+ -- excluded from scheduling until repaired; learning state is preserved.
598
+ maintenance_at TEXT,
599
+ maintenance_reason TEXT
594
600
  );
595
601
 
596
602
  -- Prerequisite dependency graph: "to learn A, first know B"
@@ -1140,6 +1146,10 @@ async function runMigrations(db) {
1140
1146
  `ALTER TABLE tokens ADD COLUMN question_source TEXT NOT NULL DEFAULT 'llm'`
1141
1147
  );
1142
1148
  }
1149
+ if (tokenCols.length > 0 && !tokenCols.some((c) => c.name === "maintenance_at")) {
1150
+ await db.exec(`ALTER TABLE tokens ADD COLUMN maintenance_at TEXT`);
1151
+ await db.exec(`ALTER TABLE tokens ADD COLUMN maintenance_reason TEXT`);
1152
+ }
1143
1153
  }
1144
1154
  var DEFAULT_DB_DIR, DEFAULT_DB_PATH, nodeRequire, TRANSIENT_REMOTE_ERROR_PATTERNS;
1145
1155
  var init_connection = __esm({
@@ -1630,6 +1640,23 @@ async function updateCard(db, cardId, updates) {
1630
1640
  }
1631
1641
  return await db.prepare("SELECT * FROM cards WHERE id = ?").get(cardId);
1632
1642
  }
1643
+ async function resetCardsForToken(db, tokenId, now) {
1644
+ const ts = now ?? (/* @__PURE__ */ new Date()).toISOString();
1645
+ const result = await db.prepare(
1646
+ `UPDATE cards SET
1647
+ stability = 0.0,
1648
+ difficulty = 0.5,
1649
+ elapsed_days = 0.0,
1650
+ scheduled_days = 0.0,
1651
+ reps = 0,
1652
+ lapses = 0,
1653
+ state = 'new',
1654
+ due_at = ?,
1655
+ last_review_at = NULL
1656
+ WHERE token_id = ?`
1657
+ ).run(ts, tokenId);
1658
+ return result.changes;
1659
+ }
1633
1660
  async function getCardDeletionImpact(db, tokenId, userId) {
1634
1661
  const card = await getCard(db, tokenId, userId);
1635
1662
  if (!card) {
@@ -1652,7 +1679,8 @@ async function getDueCards(db, userId, now, domain, knowledgeContext) {
1652
1679
  let sql = `SELECT c.*, t.slug, t.concept, t.domain, t.bloom_level
1653
1680
  FROM cards c
1654
1681
  JOIN tokens t ON t.id = c.token_id
1655
- WHERE c.user_id = ? AND c.blocked = 0 AND c.due_at <= ?`;
1682
+ WHERE c.user_id = ? AND c.blocked = 0 AND c.due_at <= ?
1683
+ AND t.maintenance_at IS NULL`;
1656
1684
  const params = [userId, cutoff];
1657
1685
  if (domain) {
1658
1686
  sql += " AND t.domain = ?";
@@ -1953,6 +1981,38 @@ async function deprecateToken(db, slug) {
1953
1981
  ).run(now, now, slug);
1954
1982
  return await getTokenBySlug(db, slug);
1955
1983
  }
1984
+ async function getTokensBySourceLinkBase(db, base) {
1985
+ return await db.prepare(
1986
+ `SELECT * FROM tokens
1987
+ WHERE (source_link = ? OR source_link LIKE ? || '#%' ESCAPE '\\')
1988
+ AND deprecated_at IS NULL`
1989
+ ).all(base, escapeLike(base));
1990
+ }
1991
+ function escapeLike(value) {
1992
+ return value.replace(/[\\%_]/g, (ch) => `\\${ch}`);
1993
+ }
1994
+ async function setTokenMaintenance(db, slug, reason) {
1995
+ const token = await getTokenBySlug(db, slug);
1996
+ if (!token) {
1997
+ throw new Error(`Token not found: ${slug}`);
1998
+ }
1999
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2000
+ await db.prepare(
2001
+ "UPDATE tokens SET maintenance_at = ?, maintenance_reason = ?, updated_at = ? WHERE slug = ?"
2002
+ ).run(now, reason, now, slug);
2003
+ return await getTokenBySlug(db, slug);
2004
+ }
2005
+ async function clearTokenMaintenance(db, slug) {
2006
+ const token = await getTokenBySlug(db, slug);
2007
+ if (!token) {
2008
+ throw new Error(`Token not found: ${slug}`);
2009
+ }
2010
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2011
+ await db.prepare(
2012
+ "UPDATE tokens SET maintenance_at = NULL, maintenance_reason = NULL, updated_at = ? WHERE slug = ?"
2013
+ ).run(now, slug);
2014
+ return await getTokenBySlug(db, slug);
2015
+ }
1956
2016
  async function getTokenDeleteImpact(db, slug) {
1957
2017
  const token = await getTokenBySlug(db, slug);
1958
2018
  if (!token) {
@@ -2080,6 +2140,15 @@ async function listTokens(db, options) {
2080
2140
  )`);
2081
2141
  params.push(options.knowledgeContext);
2082
2142
  }
2143
+ if (options?.sourceLinkBases) {
2144
+ const clauses = options.sourceLinkBases.map(
2145
+ () => `(source_link = ? OR source_link LIKE ? || '#%' ESCAPE '\\')`
2146
+ );
2147
+ whereClauses.push(clauses.length ? `(${clauses.join(" OR ")})` : "0 = 1");
2148
+ for (const base of options.sourceLinkBases) {
2149
+ params.push(base, escapeLike(base));
2150
+ }
2151
+ }
2083
2152
  const orderBy = options?.domain || options?.domainPrefix ? "ORDER BY bloom_level, slug" : "ORDER BY bloom_level, domain, slug";
2084
2153
  const sql = `SELECT * FROM tokens WHERE ${whereClauses.join(" AND ")} ${orderBy}`;
2085
2154
  const tokens = await db.prepare(sql).all(...params);
@@ -5268,7 +5337,8 @@ async function buildReviewQueue(db, options) {
5268
5337
  AND c.blocked = 0
5269
5338
  AND c.due_at <= ?
5270
5339
  AND c.state IN ('review', 'relearning', 'learning')
5271
- AND t.deprecated_at IS NULL`;
5340
+ AND t.deprecated_at IS NULL
5341
+ AND t.maintenance_at IS NULL`;
5272
5342
  const dueParams = [options.userId, nowISO];
5273
5343
  if (options.knowledgeContext) {
5274
5344
  dueSql += ` AND EXISTS (
@@ -5298,7 +5368,8 @@ async function buildReviewQueue(db, options) {
5298
5368
  WHERE c.user_id = ?
5299
5369
  AND c.blocked = 0
5300
5370
  AND c.state = 'new'
5301
- AND t.deprecated_at IS NULL`;
5371
+ AND t.deprecated_at IS NULL
5372
+ AND t.maintenance_at IS NULL`;
5302
5373
  const newParams = [options.userId];
5303
5374
  if (options.knowledgeContext) {
5304
5375
  newSql += ` AND EXISTS (
@@ -6992,6 +7063,7 @@ __export(kernel_exports, {
6992
7063
  clearADOCredentials: () => clearADOCredentials,
6993
7064
  clearProviderApiKey: () => clearProviderApiKey,
6994
7065
  clearReviewContextCache: () => clearReviewContextCache,
7066
+ clearTokenMaintenance: () => clearTokenMaintenance,
6995
7067
  clearTursoCredentials: () => clearTursoCredentials,
6996
7068
  compareVersions: () => compareVersions,
6997
7069
  computeContentHash: () => computeContentHash,
@@ -7100,6 +7172,7 @@ __export(kernel_exports, {
7100
7172
  getTokenDeleteImpact: () => getTokenDeleteImpact,
7101
7173
  getTokenEmbedding: () => getTokenEmbedding,
7102
7174
  getTokenNeighborhood: () => getTokenNeighborhood,
7175
+ getTokensBySourceLinkBase: () => getTokensBySourceLinkBase,
7103
7176
  getTursoCredentials: () => getTursoCredentials,
7104
7177
  getUiObservationPath: () => getUiObservationPath,
7105
7178
  getUiObserverDir: () => getUiObserverDir,
@@ -7154,6 +7227,7 @@ __export(kernel_exports, {
7154
7227
  readMonitorLog: () => readMonitorLog,
7155
7228
  readUiObservationLog: () => readUiObservationLog,
7156
7229
  removeConfiguredWorkspace: () => removeConfiguredWorkspace,
7230
+ resetCardsForToken: () => resetCardsForToken,
7157
7231
  resolveAllBeliefPaths: () => resolveAllBeliefPaths,
7158
7232
  resolveAllGoalPaths: () => resolveAllGoalPaths,
7159
7233
  resolveObserverPolicy: () => resolveObserverPolicy,
@@ -7185,6 +7259,7 @@ __export(kernel_exports, {
7185
7259
  setLastRepairedVersion: () => setLastRepairedVersion,
7186
7260
  setProviderApiKey: () => setProviderApiKey,
7187
7261
  setSetting: () => setSetting,
7262
+ setTokenMaintenance: () => setTokenMaintenance,
7188
7263
  setTursoCredentials: () => setTursoCredentials,
7189
7264
  slugify: () => slugify,
7190
7265
  startSession: () => startSession,
@@ -9165,21 +9240,41 @@ var init_bundle = __esm({
9165
9240
  var io_exports = {};
9166
9241
  __export(io_exports, {
9167
9242
  DEFAULT_BUNDLE_DIR: () => DEFAULT_BUNDLE_DIR,
9243
+ collectSourceLinkBases: () => collectSourceLinkBases,
9168
9244
  findRepoRoot: () => findRepoRoot,
9169
9245
  loadBundle: () => loadBundle,
9170
9246
  resolveArticlePath: () => resolveArticlePath,
9247
+ resolveBundleDirFromRoots: () => resolveBundleDirFromRoots,
9171
9248
  resolveCitationPath: () => resolveCitationPath,
9172
9249
  upsertArticle: () => upsertArticle
9173
9250
  });
9174
9251
  import {
9175
- existsSync as existsSync21,
9176
- mkdirSync as mkdirSync16,
9177
- readdirSync as readdirSync3,
9178
- readFileSync as readFileSync18,
9179
- realpathSync as realpathSync2,
9180
- writeFileSync as writeFileSync13
9252
+ existsSync as existsSync13,
9253
+ mkdirSync as mkdirSync9,
9254
+ readdirSync as readdirSync2,
9255
+ readFileSync as readFileSync11,
9256
+ realpathSync,
9257
+ writeFileSync as writeFileSync6
9181
9258
  } from "fs";
9182
- import { dirname as dirname11, isAbsolute as isAbsolute2, join as join24, relative, resolve as resolve7, sep } from "path";
9259
+ import { dirname as dirname6, isAbsolute, join as join13, relative, resolve as resolve4, sep } from "path";
9260
+ import { fileURLToPath as fileURLToPath3 } from "url";
9261
+ function resolveBundleDirFromRoots(rootUris, fallback) {
9262
+ const dirs = [];
9263
+ for (const uri of rootUris) {
9264
+ if (!uri?.startsWith("file:")) continue;
9265
+ try {
9266
+ dirs.push(join13(fileURLToPath3(uri), DEFAULT_BUNDLE_DIR));
9267
+ } catch {
9268
+ }
9269
+ }
9270
+ return dirs.find((dir) => existsSync13(dir)) ?? dirs[0] ?? fallback;
9271
+ }
9272
+ function collectSourceLinkBases(dir) {
9273
+ const bundle = loadBundle(dir);
9274
+ return bundle.catalog.map(
9275
+ (entry) => entry.resource ?? resolveArticlePath(dir, entry.file)
9276
+ );
9277
+ }
9183
9278
  function resolveArticlePath(dir, file) {
9184
9279
  if (file.includes("/") || file.includes("\\") || file.includes("..")) {
9185
9280
  throw new Error(`invalid article file name: ${file}`);
@@ -9187,19 +9282,19 @@ function resolveArticlePath(dir, file) {
9187
9282
  if (isReservedFile(file)) {
9188
9283
  throw new Error(`refusing to address reserved file: ${file}`);
9189
9284
  }
9190
- return join24(resolve7(dir), file);
9285
+ return join13(resolve4(dir), file);
9191
9286
  }
9192
9287
  function findRepoRoot(startDir) {
9193
- let dir = resolve7(startDir);
9288
+ let dir = resolve4(startDir);
9194
9289
  for (; ; ) {
9195
- if (existsSync21(join24(dir, ".git"))) return dir;
9196
- const parent = dirname11(dir);
9197
- if (parent === dir) return resolve7(startDir, "..");
9290
+ if (existsSync13(join13(dir, ".git"))) return dir;
9291
+ const parent = dirname6(dir);
9292
+ if (parent === dir) return resolve4(startDir, "..");
9198
9293
  dir = parent;
9199
9294
  }
9200
9295
  }
9201
9296
  function resolveCitationPath(bundleDir, target) {
9202
- if (isAbsolute2(target)) {
9297
+ if (isAbsolute(target)) {
9203
9298
  throw new Error(
9204
9299
  `invalid citation target: absolute paths are not allowed (${target})`
9205
9300
  );
@@ -9210,26 +9305,26 @@ function resolveCitationPath(bundleDir, target) {
9210
9305
  );
9211
9306
  }
9212
9307
  const root = findRepoRoot(bundleDir);
9213
- const resolved = resolve7(bundleDir, target);
9308
+ const resolved = resolve4(bundleDir, target);
9214
9309
  assertContained(root, resolved, target);
9215
- if (existsSync21(resolved)) {
9216
- assertContained(realpathSync2(root), realpathSync2(resolved), target);
9310
+ if (existsSync13(resolved)) {
9311
+ assertContained(realpathSync(root), realpathSync(resolved), target);
9217
9312
  }
9218
9313
  return resolved;
9219
9314
  }
9220
9315
  function assertContained(root, candidate, target) {
9221
9316
  const rel = relative(root, candidate);
9222
- if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute2(rel)) {
9317
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
9223
9318
  throw new Error(
9224
9319
  `invalid citation target: resolves outside the repository root (${target})`
9225
9320
  );
9226
9321
  }
9227
9322
  }
9228
9323
  function loadBundle(dir) {
9229
- const root = resolve7(dir);
9324
+ const root = resolve4(dir);
9230
9325
  let entries;
9231
9326
  try {
9232
- entries = readdirSync3(root).filter(
9327
+ entries = readdirSync2(root).filter(
9233
9328
  (name) => name.endsWith(".md") && !isReservedFile(name)
9234
9329
  );
9235
9330
  } catch {
@@ -9237,7 +9332,7 @@ function loadBundle(dir) {
9237
9332
  }
9238
9333
  const articles = entries.sort().map((file) => ({
9239
9334
  file,
9240
- markdown: readFileSync18(join24(root, file), "utf8")
9335
+ markdown: readFileSync11(join13(root, file), "utf8")
9241
9336
  }));
9242
9337
  const problems = articles.flatMap(
9243
9338
  ({ file, markdown }) => validateArticle(file, markdown).problems
@@ -9259,25 +9354,25 @@ function upsertArticle(dir, file, markdown, today = (/* @__PURE__ */ new Date())
9259
9354
  const target = resolveArticlePath(dir, file);
9260
9355
  const validation = validateArticle(file, markdown);
9261
9356
  if (!validation.ok) return { validation };
9262
- const root = resolve7(dir);
9263
- mkdirSync16(root, { recursive: true });
9357
+ const root = resolve4(dir);
9358
+ mkdirSync9(root, { recursive: true });
9264
9359
  let created = true;
9265
9360
  try {
9266
- readFileSync18(target, "utf8");
9361
+ readFileSync11(target, "utf8");
9267
9362
  created = false;
9268
9363
  } catch {
9269
9364
  }
9270
- writeFileSync13(target, markdown, "utf8");
9365
+ writeFileSync6(target, markdown, "utf8");
9271
9366
  const bundle = loadBundle(root);
9272
- writeFileSync13(join24(root, "index.md"), renderIndex(bundle.catalog), "utf8");
9367
+ writeFileSync6(join13(root, "index.md"), renderIndex(bundle.catalog), "utf8");
9273
9368
  let log = "";
9274
9369
  try {
9275
- log = readFileSync18(join24(root, "log.md"), "utf8");
9370
+ log = readFileSync11(join13(root, "log.md"), "utf8");
9276
9371
  } catch {
9277
9372
  }
9278
9373
  const entry = bundle.catalog.find((e) => e.file === file);
9279
- writeFileSync13(
9280
- join24(root, "log.md"),
9374
+ writeFileSync6(
9375
+ join13(root, "log.md"),
9281
9376
  appendLog(
9282
9377
  log,
9283
9378
  today,
@@ -9296,11 +9391,68 @@ var init_io = __esm({
9296
9391
  }
9297
9392
  });
9298
9393
 
9394
+ // src/cli/okf-focus.ts
9395
+ var okf_focus_exports = {};
9396
+ __export(okf_focus_exports, {
9397
+ getOkfFocusPath: () => getOkfFocusPath,
9398
+ readOkfFocus: () => readOkfFocus,
9399
+ writeOkfFocus: () => writeOkfFocus
9400
+ });
9401
+ import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
9402
+ import { homedir as homedir16 } from "os";
9403
+ import { dirname as dirname12, join as join25 } from "path";
9404
+ function getOkfFocusPath(home = homedir16()) {
9405
+ return join25(home, ".zam", "okf-focus.json");
9406
+ }
9407
+ function resolvePath(explicit) {
9408
+ return explicit ?? process.env.ZAM_OKF_FOCUS_PATH ?? getOkfFocusPath();
9409
+ }
9410
+ async function writeOkfFocus(file, bundleDir, opts = {}) {
9411
+ if (file.includes("/") || file.includes("\\") || !file.endsWith(".md")) {
9412
+ throw new Error(`invalid article file name: ${file}`);
9413
+ }
9414
+ const path = resolvePath(opts.path);
9415
+ const focus = {
9416
+ version: 1,
9417
+ file,
9418
+ ...bundleDir ? { bundleDir } : {},
9419
+ updatedAt: (opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
9420
+ };
9421
+ await mkdir2(dirname12(path), { recursive: true });
9422
+ const tmp = `${path}.tmp`;
9423
+ await writeFile2(tmp, `${JSON.stringify(focus, null, 2)}
9424
+ `, "utf8");
9425
+ await rename2(tmp, path);
9426
+ return focus;
9427
+ }
9428
+ async function readOkfFocus(opts = {}) {
9429
+ try {
9430
+ const raw = await readFile2(resolvePath(opts.path), "utf8");
9431
+ const parsed = JSON.parse(raw);
9432
+ if (parsed.version !== 1 || typeof parsed.file !== "string" || typeof parsed.updatedAt !== "string") {
9433
+ return null;
9434
+ }
9435
+ return {
9436
+ version: 1,
9437
+ file: parsed.file,
9438
+ ...typeof parsed.bundleDir === "string" ? { bundleDir: parsed.bundleDir } : {},
9439
+ updatedAt: parsed.updatedAt
9440
+ };
9441
+ } catch {
9442
+ return null;
9443
+ }
9444
+ }
9445
+ var init_okf_focus = __esm({
9446
+ "src/cli/okf-focus.ts"() {
9447
+ "use strict";
9448
+ }
9449
+ });
9450
+
9299
9451
  // src/cli/commands/mcp.ts
9300
9452
  init_kernel();
9301
9453
  import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
9302
- import { dirname as dirname12, join as join25 } from "path";
9303
- import { fileURLToPath as fileURLToPath6 } from "url";
9454
+ import { basename as basename6, dirname as dirname13, join as join26 } from "path";
9455
+ import { fileURLToPath as fileURLToPath7 } from "url";
9304
9456
  import {
9305
9457
  RESOURCE_MIME_TYPE,
9306
9458
  registerAppResource,
@@ -9513,8 +9665,8 @@ function buildCompanionContext(input) {
9513
9665
 
9514
9666
  // src/cli/bridge-handlers.ts
9515
9667
  init_kernel();
9516
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
9517
- import { join as join13 } from "path";
9668
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
9669
+ import { join as join14 } from "path";
9518
9670
 
9519
9671
  // src/cli/knowledge-contexts.ts
9520
9672
  init_kernel();
@@ -10397,6 +10549,161 @@ async function addToken(db, params) {
10397
10549
  possible_duplicates: possibleDuplicates
10398
10550
  };
10399
10551
  }
10552
+ async function importOkfTokens(db, params) {
10553
+ const userId = await resolveHandlerUser(db, params.user);
10554
+ if (!params.file?.trim()) throw new Error("file must be non-empty");
10555
+ if (!Array.isArray(params.tokens) || params.tokens.length === 0) {
10556
+ throw new Error("tokens must be a non-empty array");
10557
+ }
10558
+ const seen = /* @__PURE__ */ new Set();
10559
+ for (const input of params.tokens) {
10560
+ const slug = input.slug?.trim();
10561
+ if (!slug || !input.concept?.trim()) {
10562
+ throw new Error("every token needs a non-empty slug and concept");
10563
+ }
10564
+ if (seen.has(slug)) throw new Error(`duplicate token slug: ${slug}`);
10565
+ seen.add(slug);
10566
+ if (input.bloomLevel !== void 0 && (!Number.isInteger(input.bloomLevel) || input.bloomLevel < 1 || input.bloomLevel > 5)) {
10567
+ throw new Error(`bloomLevel must be 1-5 (token: ${slug})`);
10568
+ }
10569
+ }
10570
+ const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
10571
+ const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
10572
+ const { readFileSync: readFileSync20 } = await import("fs");
10573
+ const bundleDir = params.bundleDir ?? DEFAULT_BUNDLE_DIR2;
10574
+ const articlePath = resolveArticlePath2(bundleDir, params.file);
10575
+ let markdown;
10576
+ try {
10577
+ markdown = readFileSync20(articlePath, "utf8");
10578
+ } catch {
10579
+ throw new Error(`Article not found: ${articlePath}`);
10580
+ }
10581
+ let resource;
10582
+ try {
10583
+ const { fields } = parseFrontmatter2(markdown);
10584
+ resource = typeof fields.resource === "string" ? fields.resource : void 0;
10585
+ } catch {
10586
+ resource = void 0;
10587
+ }
10588
+ const sourceBase = resource ?? articlePath;
10589
+ const sourceLinkFor = (anchor) => anchor?.trim() ? `${sourceBase}#${anchor.trim()}` : sourceBase;
10590
+ const created = [];
10591
+ const updated = [];
10592
+ const replaced = [];
10593
+ const maintenance = [];
10594
+ let cardsEnsured = 0;
10595
+ await db.transaction(async (tx) => {
10596
+ const inImport = /* @__PURE__ */ new Map();
10597
+ for (const input of params.tokens) {
10598
+ const mode = input.mode ?? "new";
10599
+ const existing = await getTokenBySlug(tx, input.slug);
10600
+ if (mode === "new") {
10601
+ if (existing) {
10602
+ throw new Error(
10603
+ `Token '${input.slug}' already exists. Use mode "update" to refresh it (keeps learning state), "replace" if the concept changed (resets learning state), or choose a different slug.`
10604
+ );
10605
+ }
10606
+ const token2 = await createToken(tx, {
10607
+ slug: input.slug,
10608
+ title: input.title,
10609
+ concept: input.concept,
10610
+ domain: input.domain,
10611
+ bloom_level: input.bloomLevel ?? 1,
10612
+ source_link: sourceLinkFor(input.anchor),
10613
+ question: input.question ?? null,
10614
+ question_source: input.question ? "llm" : void 0
10615
+ });
10616
+ inImport.set(input.slug, token2);
10617
+ created.push(input.slug);
10618
+ } else {
10619
+ if (!existing) {
10620
+ throw new Error(
10621
+ `Token '${input.slug}' does not exist \u2014 mode "${mode}" requires an existing token.`
10622
+ );
10623
+ }
10624
+ const updates = {
10625
+ concept: input.concept,
10626
+ source_link: sourceLinkFor(input.anchor)
10627
+ };
10628
+ if (input.title !== void 0) updates.title = input.title;
10629
+ if (input.domain !== void 0) updates.domain = input.domain;
10630
+ if (input.bloomLevel !== void 0) {
10631
+ updates.bloom_level = input.bloomLevel;
10632
+ }
10633
+ if (input.question !== void 0) {
10634
+ updates.question = input.question;
10635
+ updates.question_source = input.question ? "llm" : void 0;
10636
+ }
10637
+ const token2 = await updateToken(tx, input.slug, updates);
10638
+ if (existing.maintenance_at) {
10639
+ await clearTokenMaintenance(tx, input.slug);
10640
+ }
10641
+ if (mode === "replace") {
10642
+ await resetCardsForToken(tx, token2.id);
10643
+ replaced.push(input.slug);
10644
+ } else {
10645
+ updated.push(input.slug);
10646
+ }
10647
+ inImport.set(input.slug, token2);
10648
+ }
10649
+ const ctxNames = parseKnowledgeContextNames(input.knowledgeContexts);
10650
+ const assigned = await resolveKnowledgeContexts(tx, ctxNames);
10651
+ const token = inImport.get(input.slug);
10652
+ if (token) {
10653
+ for (const context of assigned) {
10654
+ await assignTokenToContext(tx, token.id, context.id);
10655
+ }
10656
+ }
10657
+ }
10658
+ for (const input of params.tokens) {
10659
+ const token = inImport.get(input.slug);
10660
+ if (!token) continue;
10661
+ const prereqSlugs = [
10662
+ ...new Set(
10663
+ (input.prerequisites ?? []).map((s) => s.trim()).filter(Boolean)
10664
+ )
10665
+ ];
10666
+ for (const prereqSlug of prereqSlugs) {
10667
+ const target = inImport.get(prereqSlug) ?? await getTokenBySlug(tx, prereqSlug);
10668
+ if (!target) {
10669
+ throw new Error(
10670
+ `Prerequisite token not found: ${prereqSlug} (for '${input.slug}')`
10671
+ );
10672
+ }
10673
+ await addPrerequisite(tx, token.id, target.id);
10674
+ }
10675
+ }
10676
+ for (const token of inImport.values()) {
10677
+ await ensureCard(tx, token.id, userId);
10678
+ cardsEnsured++;
10679
+ }
10680
+ const prior = await getTokensBySourceLinkBase(tx, sourceBase);
10681
+ for (const token of prior) {
10682
+ if (!inImport.has(token.slug)) {
10683
+ await setTokenMaintenance(
10684
+ tx,
10685
+ token.slug,
10686
+ `absent from re-import of ${params.file}`
10687
+ );
10688
+ maintenance.push(token.slug);
10689
+ }
10690
+ }
10691
+ });
10692
+ try {
10693
+ await ensureTokenEmbeddings(db, { limit: 16 });
10694
+ } catch {
10695
+ }
10696
+ return {
10697
+ success: true,
10698
+ user: userId,
10699
+ article: { file: params.file, source_link: sourceBase },
10700
+ created,
10701
+ updated,
10702
+ replaced,
10703
+ maintenance,
10704
+ cards: cardsEnsured
10705
+ };
10706
+ }
10400
10707
  async function findTokens2(db, params) {
10401
10708
  const userId = await resolveHandlerUser(db, params.user);
10402
10709
  if (!params.context.trim()) {
@@ -10721,11 +11028,11 @@ async function backupCreate(db, params) {
10721
11028
  const targetDir = params.dir || (await ensureActiveWorkspace(db)).path;
10722
11029
  const snapshot = await exportSnapshot(db);
10723
11030
  const manifest = verifySnapshot(snapshot);
10724
- const backupDir = join13(targetDir, "zam-backups");
10725
- mkdirSync9(backupDir, { recursive: true });
11031
+ const backupDir = join14(targetDir, "zam-backups");
11032
+ mkdirSync10(backupDir, { recursive: true });
10726
11033
  const stamp = manifest.createdAt.replace(/[:.]/g, "-");
10727
- const path = join13(backupDir, `zam-snapshot-${stamp}.sql`);
10728
- writeFileSync6(path, snapshot, "utf-8");
11034
+ const path = join14(backupDir, `zam-snapshot-${stamp}.sql`);
11035
+ writeFileSync7(path, snapshot, "utf-8");
10729
11036
  return {
10730
11037
  ok: true,
10731
11038
  path,
@@ -10750,27 +11057,27 @@ init_kernel();
10750
11057
 
10751
11058
  // src/cli/agent-connect.ts
10752
11059
  init_kernel();
10753
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "fs";
11060
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
10754
11061
  import { homedir as homedir12 } from "os";
10755
- import { dirname as dirname8 } from "path";
11062
+ import { dirname as dirname9 } from "path";
10756
11063
 
10757
11064
  // src/cli/agent-harness.ts
10758
11065
  import { spawn as spawn2 } from "child_process";
10759
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
11066
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
10760
11067
  import { homedir as homedir9 } from "os";
10761
- import { join as join15 } from "path";
11068
+ import { join as join16 } from "path";
10762
11069
 
10763
11070
  // src/cli/terminal-open.ts
10764
11071
  import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
10765
11072
  import {
10766
11073
  accessSync,
10767
11074
  constants,
10768
- existsSync as existsSync13,
11075
+ existsSync as existsSync14,
10769
11076
  unlinkSync as unlinkSync2,
10770
- writeFileSync as writeFileSync7
11077
+ writeFileSync as writeFileSync8
10771
11078
  } from "fs";
10772
11079
  import { tmpdir } from "os";
10773
- import { basename as basename3, delimiter, extname, isAbsolute, join as join14 } from "path";
11080
+ import { basename as basename3, delimiter, extname, isAbsolute as isAbsolute2, join as join15 } from "path";
10774
11081
  function isPowerShellShell(shell) {
10775
11082
  return shell === "pwsh" || shell === "powershell";
10776
11083
  }
@@ -10812,7 +11119,7 @@ function stripSurroundingQuotes(command) {
10812
11119
  return trimmed;
10813
11120
  }
10814
11121
  function executableExists(path) {
10815
- if (!existsSync13(path)) return false;
11122
+ if (!existsSync14(path)) return false;
10816
11123
  if (process.platform === "win32") return true;
10817
11124
  try {
10818
11125
  accessSync(path, constants.X_OK);
@@ -10828,7 +11135,7 @@ function windowsExecutableNames(command) {
10828
11135
  return extensions.map((ext) => `${command}${ext}`);
10829
11136
  }
10830
11137
  function hasDirectoryPart(command) {
10831
- return isAbsolute(command) || command.includes("/") || command.includes("\\");
11138
+ return isAbsolute2(command) || command.includes("/") || command.includes("\\");
10832
11139
  }
10833
11140
  function findExecutable(command) {
10834
11141
  const normalized = stripSurroundingQuotes(command);
@@ -10843,7 +11150,7 @@ function findExecutable(command) {
10843
11150
  const pathEntries = (process.env.PATH ?? "").split(delimiter).map((entry) => entry.trim()).filter(Boolean);
10844
11151
  for (const entry of pathEntries) {
10845
11152
  for (const name of windowsExecutableNames(normalized)) {
10846
- const candidate = join14(entry, name);
11153
+ const candidate = join15(entry, name);
10847
11154
  if (executableExists(candidate)) matches.push(candidate);
10848
11155
  }
10849
11156
  }
@@ -10881,9 +11188,9 @@ end tell` : `tell application "Terminal"
10881
11188
  activate
10882
11189
  do script "${escaped}"
10883
11190
  end tell`;
10884
- const tmpFile = join14(tmpdir(), `zam-terminal-${label}.scpt`);
11191
+ const tmpFile = join15(tmpdir(), `zam-terminal-${label}.scpt`);
10885
11192
  try {
10886
- writeFileSync7(tmpFile, appleScript);
11193
+ writeFileSync8(tmpFile, appleScript);
10887
11194
  execSync4(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
10888
11195
  if (!silent) {
10889
11196
  console.log(
@@ -10964,7 +11271,7 @@ function openTerminalWindow(opts) {
10964
11271
  // src/cli/agent-harness.ts
10965
11272
  var ANTIGRAVITY_IDE_CANDIDATE_PATHS = {
10966
11273
  darwin: [
10967
- join15(
11274
+ join16(
10968
11275
  homedir9(),
10969
11276
  ".antigravity-ide",
10970
11277
  "antigravity-ide",
@@ -10985,7 +11292,7 @@ var AGENT_HARNESSES = [
10985
11292
  command: "cursor",
10986
11293
  candidatePaths: {
10987
11294
  win32: [
10988
- join15(homedir9(), "AppData", "Local", "Programs", "cursor", "Cursor.exe")
11295
+ join16(homedir9(), "AppData", "Local", "Programs", "cursor", "Cursor.exe")
10989
11296
  ],
10990
11297
  darwin: ["/Applications/Cursor.app/Contents/MacOS/Cursor"]
10991
11298
  }
@@ -11010,7 +11317,7 @@ function getHarness(id) {
11010
11317
  }
11011
11318
  function resolveAntigravityIdeExecutable(deps = {}) {
11012
11319
  const find = deps.find ?? findExecutable;
11013
- const exists = deps.exists ?? existsSync14;
11320
+ const exists = deps.exists ?? existsSync15;
11014
11321
  const platform = deps.platform ?? process.platform;
11015
11322
  const found = find("antigravity-ide");
11016
11323
  if (found) return found;
@@ -11018,7 +11325,7 @@ function resolveAntigravityIdeExecutable(deps = {}) {
11018
11325
  }
11019
11326
  function resolveHarnessExecutable(harness, overrideCommand, deps = {}) {
11020
11327
  const find = deps.find ?? findExecutable;
11021
- const exists = deps.exists ?? existsSync14;
11328
+ const exists = deps.exists ?? existsSync15;
11022
11329
  const platform = deps.platform ?? process.platform;
11023
11330
  const found = find(overrideCommand || harness.command);
11024
11331
  if (found) return found;
@@ -11078,18 +11385,18 @@ function detectInstalledConnectHarnesses(options = {}) {
11078
11385
  const home = options.home ?? homedir9();
11079
11386
  const platform = options.platform ?? process.platform;
11080
11387
  const find = options.find ?? findExecutable;
11081
- const exists = options.exists ?? existsSync14;
11388
+ const exists = options.exists ?? existsSync15;
11082
11389
  const detected = [];
11083
11390
  const hasCommandOrPath = (command, paths = []) => Boolean(find(command)) || paths.some((path) => exists(path));
11084
11391
  if (hasCommandOrPath("codex", [
11085
- join15(home, ".codex"),
11086
- ...platform === "darwin" ? ["/Applications/Codex.app"] : platform === "win32" ? [join15(home, "AppData", "Local", "Programs", "Codex")] : []
11392
+ join16(home, ".codex"),
11393
+ ...platform === "darwin" ? ["/Applications/Codex.app"] : platform === "win32" ? [join16(home, "AppData", "Local", "Programs", "Codex")] : []
11087
11394
  ])) {
11088
11395
  detected.push("codex");
11089
11396
  }
11090
11397
  const vscodePaths = platform === "darwin" ? [
11091
11398
  "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
11092
- join15(
11399
+ join16(
11093
11400
  home,
11094
11401
  "Applications",
11095
11402
  "Visual Studio Code.app",
@@ -11100,7 +11407,7 @@ function detectInstalledConnectHarnesses(options = {}) {
11100
11407
  "code"
11101
11408
  )
11102
11409
  ] : platform === "win32" ? [
11103
- join15(
11410
+ join16(
11104
11411
  home,
11105
11412
  "AppData",
11106
11413
  "Local",
@@ -11111,23 +11418,23 @@ function detectInstalledConnectHarnesses(options = {}) {
11111
11418
  )
11112
11419
  ] : ["/usr/bin/code", "/usr/local/bin/code", "/snap/bin/code"];
11113
11420
  if (hasCommandOrPath("code", vscodePaths)) detected.push("vscode");
11114
- const copilotHome = options.copilotHome ?? join15(home, ".copilot");
11421
+ const copilotHome = options.copilotHome ?? join16(home, ".copilot");
11115
11422
  if (hasCommandOrPath("copilot", [
11116
11423
  copilotHome,
11117
11424
  ...platform === "darwin" ? ["/Applications/GitHub Copilot.app"] : []
11118
11425
  ])) {
11119
11426
  detected.push("copilot");
11120
11427
  }
11121
- if (hasCommandOrPath("opencode", [join15(home, ".config", "opencode")])) {
11428
+ if (hasCommandOrPath("opencode", [join16(home, ".config", "opencode")])) {
11122
11429
  detected.push("opencode");
11123
11430
  }
11124
- if (hasCommandOrPath("goose", [join15(home, ".config", "goose")])) {
11431
+ if (hasCommandOrPath("goose", [join16(home, ".config", "goose")])) {
11125
11432
  detected.push("goose");
11126
11433
  }
11127
11434
  if (hasCommandOrPath("antigravity", [
11128
- join15(home, ".gemini", "config"),
11435
+ join16(home, ".gemini", "config"),
11129
11436
  ...platform === "darwin" ? [
11130
- join15(
11437
+ join16(
11131
11438
  home,
11132
11439
  ".antigravity-ide",
11133
11440
  "antigravity-ide",
@@ -11140,7 +11447,7 @@ function detectInstalledConnectHarnesses(options = {}) {
11140
11447
  ]) || find("antigravity-ide")) {
11141
11448
  detected.push("antigravity");
11142
11449
  }
11143
- const claudeDesktopPath = platform === "darwin" ? "/Applications/Claude.app" : platform === "win32" ? exists(join15(home, "AppData", "Local", "AnthropicClaude")) ? join15(home, "AppData", "Local", "AnthropicClaude") : exists(join15(home, "AppData", "Local", "Claude")) ? join15(home, "AppData", "Local", "Claude") : join15(home, "AppData", "Roaming", "Claude") : join15(home, ".config", "Claude");
11450
+ const claudeDesktopPath = platform === "darwin" ? "/Applications/Claude.app" : platform === "win32" ? exists(join16(home, "AppData", "Local", "AnthropicClaude")) ? join16(home, "AppData", "Local", "AnthropicClaude") : exists(join16(home, "AppData", "Local", "Claude")) ? join16(home, "AppData", "Local", "Claude") : join16(home, "AppData", "Roaming", "Claude") : join16(home, ".config", "Claude");
11144
11451
  if (exists(claudeDesktopPath)) detected.push("claude-desktop");
11145
11452
  return detected;
11146
11453
  }
@@ -11173,11 +11480,11 @@ function connectHarnessMcp(harnessId, opts) {
11173
11480
  return false;
11174
11481
  }
11175
11482
  }
11176
- return existsSync14(p);
11483
+ return existsSync15(p);
11177
11484
  };
11178
11485
  const read = (p) => {
11179
11486
  if (opts.readFile) return opts.readFile(p);
11180
- return readFileSync11(p, "utf-8");
11487
+ return readFileSync12(p, "utf-8");
11181
11488
  };
11182
11489
  let targetPath = "";
11183
11490
  let content = "";
@@ -11209,32 +11516,32 @@ function connectHarnessMcp(harnessId, opts) {
11209
11516
  return JSON.stringify(existing, null, 2);
11210
11517
  };
11211
11518
  if (harnessId === "claude-code") {
11212
- targetPath = join15(opts.cwd, ".mcp.json");
11519
+ targetPath = join16(opts.cwd, ".mcp.json");
11213
11520
  hint = "Claude Code will prompt you to approve the 'zam' MCP server on next launch.";
11214
11521
  content = mergeMcpServersJson(targetPath);
11215
11522
  } else if (harnessId === "claude-desktop") {
11216
11523
  const platform = opts.platform ?? process.platform;
11217
- targetPath = platform === "win32" ? join15(
11524
+ targetPath = platform === "win32" ? join16(
11218
11525
  opts.home,
11219
11526
  "AppData",
11220
11527
  "Roaming",
11221
11528
  "Claude",
11222
11529
  "claude_desktop_config.json"
11223
- ) : platform === "darwin" ? join15(
11530
+ ) : platform === "darwin" ? join16(
11224
11531
  opts.home,
11225
11532
  "Library",
11226
11533
  "Application Support",
11227
11534
  "Claude",
11228
11535
  "claude_desktop_config.json"
11229
- ) : join15(opts.home, ".config", "Claude", "claude_desktop_config.json");
11536
+ ) : join16(opts.home, ".config", "Claude", "claude_desktop_config.json");
11230
11537
  hint = "Restart Claude Desktop to load the 'zam' MCP server; MCP Apps panels render inline in the chat.";
11231
11538
  content = mergeMcpServersJson(targetPath);
11232
11539
  } else if (harnessId === "antigravity") {
11233
- targetPath = join15(opts.home, ".gemini", "config", "mcp_config.json");
11540
+ targetPath = join16(opts.home, ".gemini", "config", "mcp_config.json");
11234
11541
  hint = "Shared config read by Antigravity CLI and IDE (2.0+); older IDE builds read ~/.gemini/antigravity/mcp_config.json instead. Refresh Installed MCP Servers; the first tool call may still require approval.";
11235
11542
  content = mergeMcpServersJson(targetPath);
11236
11543
  } else if (harnessId === "opencode") {
11237
- targetPath = join15(opts.home, ".config", "opencode", "opencode.json");
11544
+ targetPath = join16(opts.home, ".config", "opencode", "opencode.json");
11238
11545
  hint = "OpenCode will load the enabled 'zam' MCP server on next launch.";
11239
11546
  let existing = {};
11240
11547
  if (exists(targetPath)) {
@@ -11262,7 +11569,7 @@ function connectHarnessMcp(harnessId, opts) {
11262
11569
  existing.mcp = servers;
11263
11570
  content = JSON.stringify(existing, null, 2);
11264
11571
  } else if (harnessId === "codex") {
11265
- targetPath = join15(opts.home, ".codex", "config.toml");
11572
+ targetPath = join16(opts.home, ".codex", "config.toml");
11266
11573
  hint = "Codex will prompt for tool execution approvals or respect the TOML approval modes.";
11267
11574
  let existingStr = "";
11268
11575
  if (exists(targetPath)) {
@@ -11289,14 +11596,14 @@ ${block}` : block;
11289
11596
  }
11290
11597
  } else if (harnessId === "vscode") {
11291
11598
  const platform = opts.platform ?? process.platform;
11292
- targetPath = platform === "win32" ? join15(opts.home, "AppData", "Roaming", "Code", "User", "mcp.json") : platform === "darwin" ? join15(
11599
+ targetPath = platform === "win32" ? join16(opts.home, "AppData", "Roaming", "Code", "User", "mcp.json") : platform === "darwin" ? join16(
11293
11600
  opts.home,
11294
11601
  "Library",
11295
11602
  "Application Support",
11296
11603
  "Code",
11297
11604
  "User",
11298
11605
  "mcp.json"
11299
- ) : join15(opts.home, ".config", "Code", "User", "mcp.json");
11606
+ ) : join16(opts.home, ".config", "Code", "User", "mcp.json");
11300
11607
  hint = "Reload VS Code after setup. ZAM Companion stays separate from the Codex chat and can be moved to any panel or sidebar.";
11301
11608
  let existing = {};
11302
11609
  if (exists(targetPath)) {
@@ -11318,7 +11625,7 @@ ${block}` : block;
11318
11625
  if (!existing.inputs) existing.inputs = [];
11319
11626
  content = JSON.stringify(existing, null, 2);
11320
11627
  } else if (harnessId === "goose") {
11321
- targetPath = join15(opts.home, ".config", "goose", "config.yaml");
11628
+ targetPath = join16(opts.home, ".config", "goose", "config.yaml");
11322
11629
  hint = "goose will load the 'zam' extension on next session start. Run 'goose configure' to manage extensions.";
11323
11630
  const isJs = opts.zamPath.endsWith(".js");
11324
11631
  const cmdStr = isJs ? process.execPath : opts.zamPath;
@@ -11357,8 +11664,8 @@ ${zamExtension}
11357
11664
  `;
11358
11665
  }
11359
11666
  } else if (harnessId === "copilot") {
11360
- targetPath = join15(
11361
- opts.copilotHome ?? join15(opts.home, ".copilot"),
11667
+ targetPath = join16(
11668
+ opts.copilotHome ?? join16(opts.home, ".copilot"),
11362
11669
  "mcp-config.json"
11363
11670
  );
11364
11671
  hint = "Restart GitHub Copilot or start a new session to load the 'zam' MCP server and its focused Recall, Graph, and Settings canvases.";
@@ -11400,15 +11707,15 @@ ${zamExtension}
11400
11707
 
11401
11708
  // src/cli/copilot-extension.ts
11402
11709
  import {
11403
- existsSync as existsSync15,
11710
+ existsSync as existsSync16,
11404
11711
  lstatSync,
11405
- mkdirSync as mkdirSync10,
11406
- readFileSync as readFileSync12,
11407
- writeFileSync as writeFileSync8
11712
+ mkdirSync as mkdirSync11,
11713
+ readFileSync as readFileSync13,
11714
+ writeFileSync as writeFileSync9
11408
11715
  } from "fs";
11409
11716
  import { homedir as homedir10 } from "os";
11410
- import { dirname as dirname6, join as join16, resolve as resolve4 } from "path";
11411
- import { fileURLToPath as fileURLToPath3 } from "url";
11717
+ import { dirname as dirname7, join as join17, resolve as resolve5 } from "path";
11718
+ import { fileURLToPath as fileURLToPath4 } from "url";
11412
11719
  var EXTENSION_NAME = "zam-mcp-apps";
11413
11720
  var EXTENSION_FILES = [
11414
11721
  "extension.mjs",
@@ -11417,18 +11724,18 @@ var EXTENSION_FILES = [
11417
11724
  "manifest.json"
11418
11725
  ];
11419
11726
  var packageRoot = [
11420
- fileURLToPath3(new URL("../..", import.meta.url)),
11421
- fileURLToPath3(new URL("../../..", import.meta.url))
11422
- ].find((candidate) => existsSync15(join16(candidate, "package.json"))) ?? fileURLToPath3(new URL("../..", import.meta.url));
11727
+ fileURLToPath4(new URL("../..", import.meta.url)),
11728
+ fileURLToPath4(new URL("../../..", import.meta.url))
11729
+ ].find((candidate) => existsSync16(join17(candidate, "package.json"))) ?? fileURLToPath4(new URL("../..", import.meta.url));
11423
11730
  function defaultCliEntry() {
11424
- return join16(dirname6(fileURLToPath3(import.meta.url)), "index.js");
11731
+ return join17(dirname7(fileURLToPath4(import.meta.url)), "index.js");
11425
11732
  }
11426
11733
  function resolveCopilotHome(home = homedir10(), configuredHome = process.env.COPILOT_HOME) {
11427
- return configuredHome?.trim() ? resolve4(configuredHome) : join16(home, ".copilot");
11734
+ return configuredHome?.trim() ? resolve5(configuredHome) : join17(home, ".copilot");
11428
11735
  }
11429
11736
  function resolveCopilotZamLaunch(zamPath, options = {}) {
11430
11737
  const cliEntry = options.cliEntry ?? defaultCliEntry();
11431
- if (existsSync15(cliEntry)) {
11738
+ if (existsSync16(cliEntry)) {
11432
11739
  return {
11433
11740
  command: options.nodePath ?? process.execPath,
11434
11741
  args: [cliEntry, "mcp"]
@@ -11440,20 +11747,20 @@ function resolveCopilotZamLaunch(zamPath, options = {}) {
11440
11747
  };
11441
11748
  }
11442
11749
  function planCopilotExtensionInstall(options) {
11443
- const sourceDir = options.assetsDir ?? join16(packageRoot, "dist", "copilot-extension");
11750
+ const sourceDir = options.assetsDir ?? join17(packageRoot, "dist", "copilot-extension");
11444
11751
  for (const file of EXTENSION_FILES) {
11445
- if (!existsSync15(join16(sourceDir, file))) {
11752
+ if (!existsSync16(join17(sourceDir, file))) {
11446
11753
  throw new Error(
11447
- `Copilot MCP Apps asset is missing: ${join16(sourceDir, file)}. Run \`npm run build\` and retry.`
11754
+ `Copilot MCP Apps asset is missing: ${join17(sourceDir, file)}. Run \`npm run build\` and retry.`
11448
11755
  );
11449
11756
  }
11450
11757
  }
11451
11758
  const manifest = JSON.parse(
11452
- readFileSync12(join16(sourceDir, "manifest.json"), "utf8")
11759
+ readFileSync13(join17(sourceDir, "manifest.json"), "utf8")
11453
11760
  );
11454
11761
  if (manifest.name !== EXTENSION_NAME || typeof manifest.version !== "string" || !manifest.version) {
11455
11762
  throw new Error(
11456
- `Invalid Copilot MCP Apps manifest: ${join16(sourceDir, "manifest.json")}`
11763
+ `Invalid Copilot MCP Apps manifest: ${join17(sourceDir, "manifest.json")}`
11457
11764
  );
11458
11765
  }
11459
11766
  const copilotHome = resolveCopilotHome(
@@ -11462,15 +11769,15 @@ function planCopilotExtensionInstall(options) {
11462
11769
  );
11463
11770
  return {
11464
11771
  sourceDir,
11465
- destinationDir: join16(copilotHome, "extensions", EXTENSION_NAME),
11772
+ destinationDir: join17(copilotHome, "extensions", EXTENSION_NAME),
11466
11773
  launch: resolveCopilotZamLaunch(options.zamPath, options),
11467
11774
  files: EXTENSION_FILES
11468
11775
  };
11469
11776
  }
11470
11777
  function writeIfChanged(path, content) {
11471
11778
  const next = Buffer.isBuffer(content) ? content : Buffer.from(content);
11472
- if (existsSync15(path) && readFileSync12(path).equals(next)) return false;
11473
- writeFileSync8(path, next);
11779
+ if (existsSync16(path) && readFileSync13(path).equals(next)) return false;
11780
+ writeFileSync9(path, next);
11474
11781
  return true;
11475
11782
  }
11476
11783
  function installCopilotExtension(options) {
@@ -11478,18 +11785,18 @@ function installCopilotExtension(options) {
11478
11785
  if (options.dryRun) {
11479
11786
  return { ...plan, action: "planned", changedFiles: [] };
11480
11787
  }
11481
- const destinationExisted = existsSync15(plan.destinationDir);
11788
+ const destinationExisted = existsSync16(plan.destinationDir);
11482
11789
  if (destinationExisted && lstatSync(plan.destinationDir).isSymbolicLink()) {
11483
11790
  throw new Error(
11484
11791
  `Refusing to replace symlinked Copilot extension directory: ${plan.destinationDir}`
11485
11792
  );
11486
11793
  }
11487
- mkdirSync10(plan.destinationDir, { recursive: true });
11794
+ mkdirSync11(plan.destinationDir, { recursive: true });
11488
11795
  const changedFiles = [];
11489
11796
  for (const file of plan.files) {
11490
- const source = join16(plan.sourceDir, file);
11491
- const destination = join16(plan.destinationDir, file);
11492
- if (writeIfChanged(destination, readFileSync12(source))) {
11797
+ const source = join17(plan.sourceDir, file);
11798
+ const destination = join17(plan.destinationDir, file);
11799
+ if (writeIfChanged(destination, readFileSync13(source))) {
11493
11800
  changedFiles.push(file);
11494
11801
  }
11495
11802
  }
@@ -11503,7 +11810,7 @@ function installCopilotExtension(options) {
11503
11810
  2
11504
11811
  )}
11505
11812
  `;
11506
- if (writeIfChanged(join16(plan.destinationDir, "launch.json"), launchContent)) {
11813
+ if (writeIfChanged(join17(plan.destinationDir, "launch.json"), launchContent)) {
11507
11814
  changedFiles.push("launch.json");
11508
11815
  }
11509
11816
  return {
@@ -11515,24 +11822,24 @@ function installCopilotExtension(options) {
11515
11822
 
11516
11823
  // src/cli/vscode-extension.ts
11517
11824
  import { execFileSync as execFileSync3 } from "child_process";
11518
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
11825
+ import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
11519
11826
  import { homedir as homedir11 } from "os";
11520
- import { dirname as dirname7, join as join17 } from "path";
11521
- import { fileURLToPath as fileURLToPath4 } from "url";
11827
+ import { dirname as dirname8, join as join18 } from "path";
11828
+ import { fileURLToPath as fileURLToPath5 } from "url";
11522
11829
  var packageRoot2 = [
11523
- fileURLToPath4(new URL("../..", import.meta.url)),
11524
- fileURLToPath4(new URL("../../..", import.meta.url))
11525
- ].find((candidate) => existsSync16(join17(candidate, "package.json"))) ?? fileURLToPath4(new URL("../..", import.meta.url));
11830
+ fileURLToPath5(new URL("../..", import.meta.url)),
11831
+ fileURLToPath5(new URL("../../..", import.meta.url))
11832
+ ].find((candidate) => existsSync17(join18(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
11526
11833
  function resolveVscodeExecutable(options = {}) {
11527
11834
  const home = options.home ?? homedir11();
11528
11835
  const platform = options.platform ?? process.platform;
11529
11836
  const find = options.find ?? findExecutable;
11530
- const exists = options.exists ?? existsSync16;
11837
+ const exists = options.exists ?? existsSync17;
11531
11838
  const found = find("code");
11532
11839
  if (found) return found;
11533
11840
  const candidates = platform === "darwin" ? [
11534
11841
  "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
11535
- join17(
11842
+ join18(
11536
11843
  home,
11537
11844
  "Applications",
11538
11845
  "Visual Studio Code.app",
@@ -11543,7 +11850,7 @@ function resolveVscodeExecutable(options = {}) {
11543
11850
  "code"
11544
11851
  )
11545
11852
  ] : platform === "win32" ? [
11546
- join17(
11853
+ join18(
11547
11854
  home,
11548
11855
  "AppData",
11549
11856
  "Local",
@@ -11563,7 +11870,7 @@ function buildVscodeCliInvocation(command, args, platform = process.platform) {
11563
11870
  }
11564
11871
  function packageVersion() {
11565
11872
  const parsed = JSON.parse(
11566
- readFileSync13(join17(packageRoot2, "package.json"), "utf8")
11873
+ readFileSync14(join18(packageRoot2, "package.json"), "utf8")
11567
11874
  );
11568
11875
  if (typeof parsed.version !== "string" || !parsed.version) {
11569
11876
  throw new Error("Cannot determine the ZAM package version");
@@ -11573,9 +11880,9 @@ function packageVersion() {
11573
11880
  function planVscodeExtensionInstall(options) {
11574
11881
  const home = options.home ?? homedir11();
11575
11882
  const version = options.version ?? packageVersion();
11576
- const assetsDir = options.assetsDir ?? join17(packageRoot2, "dist", "vscode-extension");
11577
- const vsixPath = join17(assetsDir, `ZAM_Companion_${version}.vsix`);
11578
- if (!existsSync16(vsixPath)) {
11883
+ const assetsDir = options.assetsDir ?? join18(packageRoot2, "dist", "vscode-extension");
11884
+ const vsixPath = join18(assetsDir, `ZAM_Companion_${version}.vsix`);
11885
+ if (!existsSync17(vsixPath)) {
11579
11886
  throw new Error(
11580
11887
  `ZAM Companion VSIX asset is missing: ${vsixPath}. Run \`npm run build\` and retry.`
11581
11888
  );
@@ -11595,7 +11902,7 @@ function planVscodeExtensionInstall(options) {
11595
11902
  version,
11596
11903
  vsixPath,
11597
11904
  codePath,
11598
- launchConfigPath: join17(home, ".zam", "vscode-launch.json"),
11905
+ launchConfigPath: join18(home, ".zam", "vscode-launch.json"),
11599
11906
  launch: { command: options.zamPath, args: ["mcp"] }
11600
11907
  };
11601
11908
  }
@@ -11631,8 +11938,8 @@ function installVscodeExtension(options) {
11631
11938
  2
11632
11939
  )}
11633
11940
  `;
11634
- const existed = existsSync16(plan.launchConfigPath);
11635
- const changed = !existed || readFileSync13(plan.launchConfigPath, "utf8") !== launchContent;
11941
+ const existed = existsSync17(plan.launchConfigPath);
11942
+ const changed = !existed || readFileSync14(plan.launchConfigPath, "utf8") !== launchContent;
11636
11943
  const run = options.run ?? ((command, args) => {
11637
11944
  const invocation = buildVscodeCliInvocation(command, args);
11638
11945
  execFileSync3(invocation.command, invocation.args, {
@@ -11654,8 +11961,8 @@ function installVscodeExtension(options) {
11654
11961
  }
11655
11962
  run(plan.codePath, ["--install-extension", plan.vsixPath, "--force"]);
11656
11963
  if (changed) {
11657
- mkdirSync11(dirname7(plan.launchConfigPath), { recursive: true });
11658
- writeFileSync9(plan.launchConfigPath, launchContent, "utf8");
11964
+ mkdirSync12(dirname8(plan.launchConfigPath), { recursive: true });
11965
+ writeFileSync10(plan.launchConfigPath, launchContent, "utf8");
11659
11966
  }
11660
11967
  return {
11661
11968
  ...plan,
@@ -11715,8 +12022,8 @@ function resolveDeps(deps) {
11715
12022
  detect: deps.detect ?? (() => detectInstalledConnectHarnesses({ home, copilotHome })),
11716
12023
  connectMcp: deps.connectMcp ?? connectHarnessMcp,
11717
12024
  writeConfig: deps.writeConfig ?? ((path, content) => {
11718
- mkdirSync12(dirname8(path), { recursive: true });
11719
- writeFileSync10(path, content, "utf-8");
12025
+ mkdirSync13(dirname9(path), { recursive: true });
12026
+ writeFileSync11(path, content, "utf-8");
11720
12027
  }),
11721
12028
  installCopilot: deps.installCopilot ?? installCopilotExtension,
11722
12029
  installVscode: deps.installVscode ?? installVscodeExtension,
@@ -11862,9 +12169,9 @@ function inspectConnectHarnesses(deps = {}) {
11862
12169
  init_kernel();
11863
12170
  import { execFileSync as execFileSync5 } from "child_process";
11864
12171
  import { randomBytes as randomBytes2 } from "crypto";
11865
- import { existsSync as existsSync20, readdirSync as readdirSync2, readFileSync as readFileSync17, rmSync as rmSync3 } from "fs";
12172
+ import { existsSync as existsSync21, readdirSync as readdirSync3, readFileSync as readFileSync18, rmSync as rmSync3 } from "fs";
11866
12173
  import { homedir as homedir14, tmpdir as tmpdir3 } from "os";
11867
- import { join as join22, resolve as resolve6 } from "path";
12174
+ import { join as join23, resolve as resolve7 } from "path";
11868
12175
  import { Command } from "commander";
11869
12176
  import { ulid as ulid10 } from "ulid";
11870
12177
 
@@ -11944,7 +12251,7 @@ async function readWebLink(url) {
11944
12251
  redirect: "manual",
11945
12252
  signal: controller.signal,
11946
12253
  headers: {
11947
- "User-Agent": "ZAM-Content-Studio/0.13.0"
12254
+ "User-Agent": "ZAM-Content-Studio/0.15.0"
11948
12255
  }
11949
12256
  });
11950
12257
  if (res.status >= 300 && res.status < 400) {
@@ -12011,16 +12318,16 @@ async function readImageOCR(db, imagePath) {
12011
12318
  import { execFileSync as execFileSync4 } from "child_process";
12012
12319
  import {
12013
12320
  chmodSync as chmodSync2,
12014
- existsSync as existsSync17,
12015
- mkdirSync as mkdirSync13,
12016
- readFileSync as readFileSync14,
12017
- writeFileSync as writeFileSync11
12321
+ existsSync as existsSync18,
12322
+ mkdirSync as mkdirSync14,
12323
+ readFileSync as readFileSync15,
12324
+ writeFileSync as writeFileSync12
12018
12325
  } from "fs";
12019
12326
  import { homedir as homedir13 } from "os";
12020
- import { delimiter as delimiter2, join as join18, resolve as resolve5 } from "path";
12327
+ import { delimiter as delimiter2, join as join19, resolve as resolve6 } from "path";
12021
12328
  var BUILT_CLI_PATTERN = /[\\/]dist[\\/]cli[\\/]index\.js$/;
12022
12329
  function normalizeForCompare(path, platform) {
12023
- const resolved = resolve5(path);
12330
+ const resolved = resolve6(path);
12024
12331
  return platform === "win32" ? resolved.toLowerCase() : resolved;
12025
12332
  }
12026
12333
  function windowsShimContent(nodePath, cliPath) {
@@ -12060,14 +12367,14 @@ function ensureWindowsUserPath(binDir) {
12060
12367
  return output.endsWith("updated");
12061
12368
  }
12062
12369
  function ensureUnixUserPath(home, platform) {
12063
- const profile = join18(home, platform === "darwin" ? ".zprofile" : ".profile");
12064
- const existing = existsSync17(profile) ? readFileSync14(profile, "utf8") : "";
12370
+ const profile = join19(home, platform === "darwin" ? ".zprofile" : ".profile");
12371
+ const existing = existsSync18(profile) ? readFileSync15(profile, "utf8") : "";
12065
12372
  if (existing.includes(".zam/bin")) return false;
12066
12373
  const block = `
12067
12374
  # Added by ZAM: keep the zam CLI on PATH
12068
12375
  export PATH="$HOME/.zam/bin:$PATH"
12069
12376
  `;
12070
- writeFileSync11(profile, existing + block, "utf8");
12377
+ writeFileSync12(profile, existing + block, "utf8");
12071
12378
  return true;
12072
12379
  }
12073
12380
  function installCliShim(options = {}) {
@@ -12075,10 +12382,10 @@ function installCliShim(options = {}) {
12075
12382
  const platform = options.platform ?? process.platform;
12076
12383
  const env = options.env ?? process.env;
12077
12384
  const find = options.find ?? findExecutable;
12078
- const nodePath = resolve5(options.nodePath ?? process.execPath);
12079
- const cliPath = resolve5(options.cliPath ?? process.argv[1] ?? "");
12080
- const binDir = join18(home, ".zam", "bin");
12081
- const shimPath = join18(binDir, platform === "win32" ? "zam.cmd" : "zam");
12385
+ const nodePath = resolve6(options.nodePath ?? process.execPath);
12386
+ const cliPath = resolve6(options.cliPath ?? process.argv[1] ?? "");
12387
+ const binDir = join19(home, ".zam", "bin");
12388
+ const shimPath = join19(binDir, platform === "win32" ? "zam.cmd" : "zam");
12082
12389
  const report = {
12083
12390
  status: "ok",
12084
12391
  binDir,
@@ -12089,7 +12396,7 @@ function installCliShim(options = {}) {
12089
12396
  pathUpdated: false,
12090
12397
  needsNewTerminal: false
12091
12398
  };
12092
- if (!BUILT_CLI_PATTERN.test(cliPath) || !existsSync17(cliPath)) {
12399
+ if (!BUILT_CLI_PATTERN.test(cliPath) || !existsSync18(cliPath)) {
12093
12400
  report.status = "skipped";
12094
12401
  report.detail = `Not running from a built CLI entry (${cliPath}); nothing to link.`;
12095
12402
  return report;
@@ -12103,11 +12410,11 @@ function installCliShim(options = {}) {
12103
12410
  return report;
12104
12411
  }
12105
12412
  const content = platform === "win32" ? windowsShimContent(nodePath, cliPath) : unixShimContent(nodePath, cliPath);
12106
- const existed = existsSync17(shimPath);
12107
- const changed = !existed || readFileSync14(shimPath, "utf8") !== content;
12413
+ const existed = existsSync18(shimPath);
12414
+ const changed = !existed || readFileSync15(shimPath, "utf8") !== content;
12108
12415
  if (changed) {
12109
- mkdirSync13(binDir, { recursive: true });
12110
- writeFileSync11(shimPath, content, "utf8");
12416
+ mkdirSync14(binDir, { recursive: true });
12417
+ writeFileSync12(shimPath, content, "utf8");
12111
12418
  if (platform !== "win32") chmodSync2(shimPath, 493);
12112
12419
  }
12113
12420
  report.status = !existed ? "installed" : changed ? "refreshed" : "ok";
@@ -28816,40 +29123,40 @@ function getCurriculumProvider(id) {
28816
29123
 
28817
29124
  // src/cli/install-repair.ts
28818
29125
  init_kernel();
28819
- import { existsSync as existsSync19 } from "fs";
29126
+ import { existsSync as existsSync20 } from "fs";
28820
29127
 
28821
29128
  // src/cli/provisioning/index.ts
28822
29129
  import {
28823
- existsSync as existsSync18,
29130
+ existsSync as existsSync19,
28824
29131
  lstatSync as lstatSync2,
28825
- mkdirSync as mkdirSync14,
28826
- readFileSync as readFileSync15,
28827
- realpathSync,
29132
+ mkdirSync as mkdirSync15,
29133
+ readFileSync as readFileSync16,
29134
+ realpathSync as realpathSync2,
28828
29135
  rmSync as rmSync2,
28829
29136
  symlinkSync,
28830
- writeFileSync as writeFileSync12
29137
+ writeFileSync as writeFileSync13
28831
29138
  } from "fs";
28832
- import { basename as basename4, dirname as dirname9, join as join19 } from "path";
28833
- import { fileURLToPath as fileURLToPath5 } from "url";
29139
+ import { basename as basename4, dirname as dirname10, join as join20 } from "path";
29140
+ import { fileURLToPath as fileURLToPath6 } from "url";
28834
29141
  var packageRoot3 = [
28835
- fileURLToPath5(new URL("../..", import.meta.url)),
28836
- fileURLToPath5(new URL("../../..", import.meta.url))
28837
- ].find((candidate) => existsSync18(join19(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
29142
+ fileURLToPath6(new URL("../..", import.meta.url)),
29143
+ fileURLToPath6(new URL("../../..", import.meta.url))
29144
+ ].find((candidate) => existsSync19(join20(candidate, "package.json"))) ?? fileURLToPath6(new URL("../..", import.meta.url));
28838
29145
  var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
28839
29146
  var SKILL_PAIRS = [
28840
29147
  {
28841
- from: join19(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
28842
- to: join19(".claude", "skills", "zam", "SKILL.md"),
29148
+ from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
29149
+ to: join20(".claude", "skills", "zam", "SKILL.md"),
28843
29150
  agents: ["claude", "copilot"]
28844
29151
  },
28845
29152
  {
28846
- from: join19(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
28847
- to: join19(".agent", "skills", "zam", "SKILL.md"),
29153
+ from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
29154
+ to: join20(".agent", "skills", "zam", "SKILL.md"),
28848
29155
  agents: ["agent"]
28849
29156
  },
28850
29157
  {
28851
- from: join19(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
28852
- to: join19(".agents", "skills", "zam", "SKILL.md"),
29158
+ from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
29159
+ to: join20(".agents", "skills", "zam", "SKILL.md"),
28853
29160
  agents: ["codex"]
28854
29161
  }
28855
29162
  ];
@@ -28893,7 +29200,7 @@ function isSymbolicLink(path) {
28893
29200
  }
28894
29201
  }
28895
29202
  function comparableRealPath(path) {
28896
- const real = realpathSync(path);
29203
+ const real = realpathSync2(path);
28897
29204
  return process.platform === "win32" ? real.toLowerCase() : real;
28898
29205
  }
28899
29206
  function pathsResolveToSameDirectory(sourceDir, destinationDir) {
@@ -28909,15 +29216,15 @@ function linkPointsTo(sourceDir, destinationDir) {
28909
29216
  function isZamSkillCopy(destinationDir) {
28910
29217
  try {
28911
29218
  if (!lstatSync2(destinationDir).isDirectory()) return false;
28912
- const skillFile = join19(destinationDir, "SKILL.md");
28913
- if (!existsSync18(skillFile)) return false;
28914
- return /^name:\s*zam\s*$/m.test(readFileSync15(skillFile, "utf8"));
29219
+ const skillFile = join20(destinationDir, "SKILL.md");
29220
+ if (!existsSync19(skillFile)) return false;
29221
+ return /^name:\s*zam\s*$/m.test(readFileSync16(skillFile, "utf8"));
28915
29222
  } catch {
28916
29223
  return false;
28917
29224
  }
28918
29225
  }
28919
29226
  function classifySkillDestination(sourceDir, destinationDir) {
28920
- if (!existsSync18(sourceDir)) return "source-missing";
29227
+ if (!existsSync19(sourceDir)) return "source-missing";
28921
29228
  if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
28922
29229
  return "source-directory";
28923
29230
  }
@@ -28934,9 +29241,9 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
28934
29241
  };
28935
29242
  for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
28936
29243
  if (!pairAgents.some((agent) => agents.has(agent))) continue;
28937
- const sourceDir = dirname9(from);
28938
- const destinationDir = dirname9(join19(cwd, to));
28939
- const label = dirname9(to);
29244
+ const sourceDir = dirname10(from);
29245
+ const destinationDir = dirname10(join20(cwd, to));
29246
+ const label = dirname10(to);
28940
29247
  const state = classifySkillDestination(sourceDir, destinationDir);
28941
29248
  if (state === "source-missing") {
28942
29249
  if (!opts.quiet) {
@@ -28993,7 +29300,7 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
28993
29300
  if (destinationExists) {
28994
29301
  rmSync2(destinationDir, { recursive: true, force: true });
28995
29302
  }
28996
- mkdirSync14(dirname9(destinationDir), { recursive: true });
29303
+ mkdirSync15(dirname10(destinationDir), { recursive: true });
28997
29304
  symlinkSync(
28998
29305
  sourceDir,
28999
29306
  destinationDir,
@@ -29009,8 +29316,8 @@ function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
29009
29316
  const results = [];
29010
29317
  for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
29011
29318
  if (!pairAgents.some((agent) => agents.has(agent))) continue;
29012
- const sourceDir = dirname9(from);
29013
- const destinationDir = dirname9(join19(cwd, to));
29319
+ const sourceDir = dirname10(from);
29320
+ const destinationDir = dirname10(join20(cwd, to));
29014
29321
  results.push({
29015
29322
  agents: pairAgents,
29016
29323
  source: sourceDir,
@@ -29041,7 +29348,7 @@ function defaultRepairWorkspaces() {
29041
29348
  try {
29042
29349
  const agents = parseSetupAgents();
29043
29350
  for (const workspace of getConfiguredWorkspaces()) {
29044
- if (!existsSync19(workspace.path)) {
29351
+ if (!existsSync20(workspace.path)) {
29045
29352
  summary.missing += 1;
29046
29353
  continue;
29047
29354
  }
@@ -29234,9 +29541,9 @@ init_client();
29234
29541
  init_kernel();
29235
29542
  init_client();
29236
29543
  import { randomBytes } from "crypto";
29237
- import { readFileSync as readFileSync16 } from "fs";
29544
+ import { readFileSync as readFileSync17 } from "fs";
29238
29545
  import { tmpdir as tmpdir2 } from "os";
29239
- import { basename as basename5, join as join20 } from "path";
29546
+ import { basename as basename5, join as join21 } from "path";
29240
29547
  var LANGUAGE_NAMES2 = {
29241
29548
  en: "English",
29242
29549
  de: "German",
@@ -29274,7 +29581,7 @@ async function observeUiSnapshotViaLLM(db, input) {
29274
29581
  if (isVideo) {
29275
29582
  const { mkdirSync: mkdirSync17, readdirSync: readdirSync4, rmSync: rmSync4 } = await import("fs");
29276
29583
  const { execSync: execSync5 } = await import("child_process");
29277
- const tempDir = join20(
29584
+ const tempDir = join21(
29278
29585
  tmpdir2(),
29279
29586
  `zam-frames-${randomBytes(4).toString("hex")}`
29280
29587
  );
@@ -29307,7 +29614,7 @@ async function observeUiSnapshotViaLLM(db, input) {
29307
29614
  }
29308
29615
  }
29309
29616
  for (const file of sampledFiles) {
29310
- const bytes = readFileSync16(join20(tempDir, file));
29617
+ const bytes = readFileSync17(join21(tempDir, file));
29311
29618
  imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
29312
29619
  }
29313
29620
  } finally {
@@ -29317,7 +29624,7 @@ async function observeUiSnapshotViaLLM(db, input) {
29317
29624
  }
29318
29625
  }
29319
29626
  } else {
29320
- const imageBytes = readFileSync16(input.imagePath);
29627
+ const imageBytes = readFileSync17(input.imagePath);
29321
29628
  const ext = input.imagePath.split(".").pop()?.toLowerCase();
29322
29629
  const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
29323
29630
  imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
@@ -29826,13 +30133,13 @@ async function resolveUser(opts, db, resolveOpts) {
29826
30133
  }
29827
30134
 
29828
30135
  // src/cli/workspaces/backup.ts
29829
- import { mkdirSync as mkdirSync15 } from "fs";
29830
- import { join as join21 } from "path";
30136
+ import { mkdirSync as mkdirSync16 } from "fs";
30137
+ import { join as join22 } from "path";
29831
30138
  async function backupDatabaseTo(db, targetDir) {
29832
- const backupDir = join21(targetDir, "zam-backups");
29833
- mkdirSync15(backupDir, { recursive: true });
30139
+ const backupDir = join22(targetDir, "zam-backups");
30140
+ mkdirSync16(backupDir, { recursive: true });
29834
30141
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
29835
- const dest = join21(backupDir, `zam-${stamp}.db`);
30142
+ const dest = join22(backupDir, `zam-${stamp}.db`);
29836
30143
  await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
29837
30144
  return dest;
29838
30145
  }
@@ -29966,20 +30273,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
29966
30273
  activeWorkspace,
29967
30274
  workspaceDir: activeWorkspace.path,
29968
30275
  defaultWorkspaceDir: defaultWorkspaceDir(),
29969
- dataDir: join22(homedir14(), ".zam")
30276
+ dataDir: join23(homedir14(), ".zam")
29970
30277
  });
29971
30278
  });
29972
30279
  });
29973
30280
  function provisionConfiguredWorkspaces() {
29974
30281
  return getConfiguredWorkspaces().flatMap(
29975
- (workspace) => existsSync20(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
30282
+ (workspace) => existsSync21(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
29976
30283
  );
29977
30284
  }
29978
30285
  function buildWorkspaceLinkHealth(workspaces) {
29979
30286
  const agents = parseSetupAgents();
29980
30287
  const map = {};
29981
30288
  for (const workspace of workspaces) {
29982
- if (!existsSync20(workspace.path)) continue;
30289
+ if (!existsSync21(workspace.path)) continue;
29983
30290
  const links = inspectSkillLinks(workspace.path, agents);
29984
30291
  map[workspace.id] = {
29985
30292
  health: summarizeSkillLinkHealth(links),
@@ -30009,7 +30316,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
30009
30316
  activeWorkspace,
30010
30317
  workspaceDir: activeWorkspace.path,
30011
30318
  defaultWorkspaceDir: defaultWorkspaceDir(),
30012
- dataDir: join22(homedir14(), ".zam"),
30319
+ dataDir: join23(homedir14(), ".zam"),
30013
30320
  linkHealth: buildWorkspaceLinkHealth(workspaces)
30014
30321
  });
30015
30322
  });
@@ -30024,7 +30331,7 @@ bridgeCommand.command("workspace-repair-links").description(
30024
30331
  if (!id) jsonError("A non-empty --id is required");
30025
30332
  const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
30026
30333
  if (!workspace) jsonError(`Workspace "${id}" is not configured`);
30027
- if (!existsSync20(workspace.path)) {
30334
+ if (!existsSync21(workspace.path)) {
30028
30335
  jsonError(`Workspace path does not exist: ${workspace.path}`);
30029
30336
  }
30030
30337
  const agents = parseSetupAgents(opts.agents);
@@ -30055,8 +30362,8 @@ function parseBridgeWorkspaceKind(value) {
30055
30362
  bridgeCommand.command("workspace-add").description("Register an existing directory as a ZAM workspace (JSON)").requiredOption("--path <dir>", "Existing workspace/repository directory").option("--id <id>", "Workspace id").option("--label <label>", "Human-readable label").option("--kind <kind>", "Workspace kind", "custom").action(async (opts) => {
30056
30363
  const raw = String(opts.path ?? "").trim();
30057
30364
  if (!raw) jsonError("A non-empty --path is required");
30058
- const path = resolve6(raw);
30059
- if (!existsSync20(path)) jsonError(`Workspace path does not exist: ${path}`);
30365
+ const path = resolve7(raw);
30366
+ if (!existsSync21(path)) jsonError(`Workspace path does not exist: ${path}`);
30060
30367
  const id = opts.id ? String(opts.id).trim() : void 0;
30061
30368
  if (opts.id && !id) jsonError("A non-empty --id is required");
30062
30369
  const kind = parseBridgeWorkspaceKind(opts.kind);
@@ -30100,8 +30407,8 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
30100
30407
  bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
30101
30408
  const raw = String(opts.dir ?? "").trim();
30102
30409
  if (!raw) jsonError("A non-empty --dir is required");
30103
- const dir = resolve6(raw);
30104
- if (!existsSync20(dir)) jsonError(`Workspace path does not exist: ${dir}`);
30410
+ const dir = resolve7(raw);
30411
+ if (!existsSync21(dir)) jsonError(`Workspace path does not exist: ${dir}`);
30105
30412
  const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
30106
30413
  await withOptionalDb2(async (db) => {
30107
30414
  const workspace = await activateWorkspacePath(db, dir);
@@ -30164,7 +30471,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
30164
30471
  jsonError(`Workspace is not configured: ${opts.workspace}`);
30165
30472
  }
30166
30473
  const activeWorkspace = await ensureActiveWorkspace(db);
30167
- const workspace = opts.dir ? existsSync20(opts.dir) ? opts.dir : homedir14() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
30474
+ const workspace = opts.dir ? existsSync21(opts.dir) ? opts.dir : homedir14() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
30168
30475
  launchHarness(harness, {
30169
30476
  executable,
30170
30477
  workspace,
@@ -30440,6 +30747,52 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
30440
30747
  jsonError(err.message);
30441
30748
  }
30442
30749
  });
30750
+ bridgeCommand.command("okf-import").description(
30751
+ "Record an agent's decomposition of an OKF article as learning tokens (JSON stdin, ADR 2026-07-18)"
30752
+ ).option("--user <id>", "User ID (default: whoami)").action(async (opts) => {
30753
+ try {
30754
+ let raw;
30755
+ if (isServeMode) {
30756
+ raw = serveStdinPayload ?? "";
30757
+ } else {
30758
+ const chunks = [];
30759
+ for await (const chunk of process.stdin) {
30760
+ chunks.push(chunk);
30761
+ }
30762
+ raw = Buffer.concat(chunks).toString("utf-8").trim();
30763
+ }
30764
+ if (!raw) {
30765
+ jsonError(
30766
+ "No input received on stdin. Pipe JSON: { file, bundle_dir?, tokens: [...] }"
30767
+ );
30768
+ }
30769
+ let data;
30770
+ try {
30771
+ data = JSON.parse(raw);
30772
+ } catch {
30773
+ jsonError("Invalid JSON input");
30774
+ }
30775
+ if (!data?.file || !Array.isArray(data?.tokens)) {
30776
+ jsonError("JSON must include 'file' and a 'tokens' array");
30777
+ }
30778
+ await withDb2(async (db) => {
30779
+ try {
30780
+ const userId = await resolveUser(opts, db, { json: true });
30781
+ const result = await importOkfTokens(db, {
30782
+ user: userId,
30783
+ bundleDir: data.bundle_dir,
30784
+ file: data.file,
30785
+ tokens: data.tokens
30786
+ });
30787
+ jsonOut(result);
30788
+ } catch (err) {
30789
+ jsonError(err.message);
30790
+ }
30791
+ });
30792
+ } catch (err) {
30793
+ jsonError(err.message);
30794
+ }
30795
+ });
30443
30796
  bridgeCommand.command("relevant-tokens").description("Find tokens relevant to a given context").option("--user <id>", "User ID (default: whoami)").action(async (opts) => {
30444
30797
  try {
30445
30798
  let raw;
@@ -30536,10 +30889,10 @@ bridgeCommand.command("discover-skills").description(
30536
30889
  "20"
30537
30890
  ).action(async (opts) => {
30538
30891
  try {
30539
- const monitorDir = join22(homedir14(), ".zam", "monitor");
30892
+ const monitorDir = join23(homedir14(), ".zam", "monitor");
30540
30893
  let files;
30541
30894
  try {
30542
- files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
30895
+ files = readdirSync3(monitorDir).filter((f) => f.endsWith(".jsonl"));
30543
30896
  } catch {
30544
30897
  jsonOut({ proposals: [], message: "No monitor logs found." });
30545
30898
  return;
@@ -30549,7 +30902,7 @@ bridgeCommand.command("discover-skills").description(
30549
30902
  return;
30550
30903
  }
30551
30904
  const limit = Number(opts.limit);
30552
- const sorted = files.map((f) => ({ name: f, path: join22(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
30905
+ const sorted = files.map((f) => ({ name: f, path: join23(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
30553
30906
  const sessionCommands = /* @__PURE__ */ new Map();
30554
30907
  for (const file of sorted) {
30555
30908
  const sessionId = file.name.replace(".jsonl", "");
@@ -31051,7 +31404,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
31051
31404
  return;
31052
31405
  }
31053
31406
  }
31054
- const outputPath = opts.image ?? opts.output ?? join22(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
31407
+ const outputPath = opts.image ?? opts.output ?? join23(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
31055
31408
  const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
31056
31409
  if (!isProvided) {
31057
31410
  const post = decidePostCapture(policy, {
@@ -31079,7 +31432,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
31079
31432
  return;
31080
31433
  }
31081
31434
  }
31082
- const imageBytes = readFileSync17(outputPath);
31435
+ const imageBytes = readFileSync18(outputPath);
31083
31436
  const base64 = imageBytes.toString("base64");
31084
31437
  jsonOut({
31085
31438
  sessionId: opts.session ?? null,
@@ -31106,9 +31459,9 @@ bridgeCommand.command("start-recording").description("Start screen recording in
31106
31459
  return;
31107
31460
  }
31108
31461
  const sessionId = opts.session;
31109
- const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
31462
+ const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
31110
31463
  const defaultExt = platform === "win32" ? ".mkv" : ".mov";
31111
- const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
31464
+ const outputPath = opts.output ?? join23(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
31112
31465
  const { existsSync: existsSync23, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
31113
31466
  if (existsSync23(statePath)) {
31114
31467
  jsonOut({
@@ -31118,7 +31471,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
31118
31471
  });
31119
31472
  return;
31120
31473
  }
31121
- const logPath = join22(tmpdir3(), `zam-recording-${sessionId}.log`);
31474
+ const logPath = join23(tmpdir3(), `zam-recording-${sessionId}.log`);
31122
31475
  let logFd;
31123
31476
  try {
31124
31477
  logFd = openSync(logPath, "w");
@@ -31200,7 +31553,7 @@ bridgeCommand.command("stop-recording").description(
31200
31553
  return;
31201
31554
  }
31202
31555
  const sessionId = opts.session;
31203
- const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
31556
+ const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
31204
31557
  const { existsSync: existsSync23, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
31205
31558
  if (!existsSync23(statePath)) {
31206
31559
  jsonOut({
@@ -31669,7 +32022,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
31669
32022
  });
31670
32023
  bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
31671
32024
  const profile = getSystemProfile();
31672
- const flmInstalled = hasCommand("flm") || existsSync20("C:\\Program Files\\flm\\flm.exe");
32025
+ const flmInstalled = hasCommand("flm") || existsSync21("C:\\Program Files\\flm\\flm.exe");
31673
32026
  const ollamaInstalled = isOllamaInstalled();
31674
32027
  const runners = [
31675
32028
  { id: "flm", label: "FastFlowLM", installed: flmInstalled },
@@ -32043,7 +32396,12 @@ bridgeCommand.command("list-tokens").description(
32043
32396
  ).option("--domain <domain>", "Filter by exact domain").option(
32044
32397
  "--domain-prefix <prefix>",
32045
32398
  "Filter by domain prefix (e.g. company-team) \u2014 uses / separator for hierarchy"
32046
- ).option("--knowledge-context <context>", "Filter by knowledge context").action(async (opts) => {
32399
+ ).option("--knowledge-context <context>", "Filter by knowledge context").option(
32400
+ "--source-link-base <base>",
32401
+ "Filter by source-link base (exact or '<base>#<anchor>', as written by OKF imports); repeatable",
32402
+ (value, previous) => [...previous, value],
32403
+ []
32404
+ ).action(async (opts) => {
32047
32405
  await withDb2(async (db) => {
32048
32406
  const userId = opts.user ? await resolveUser(opts, db, { json: true }) : void 0;
32049
32407
  const listOpts = {};
@@ -32051,6 +32409,8 @@ bridgeCommand.command("list-tokens").description(
32051
32409
  if (opts.domainPrefix) listOpts.domainPrefix = opts.domainPrefix;
32052
32410
  if (opts.knowledgeContext)
32053
32411
  listOpts.knowledgeContext = opts.knowledgeContext;
32412
+ if (opts.sourceLinkBase.length)
32413
+ listOpts.sourceLinkBases = opts.sourceLinkBase;
32054
32414
  const tokens = await listTokens(
32055
32415
  db,
32056
32416
  Object.keys(listOpts).length ? listOpts : void 0
@@ -33804,13 +34164,13 @@ async function writeCompanionContext(db, request, options = {}) {
33804
34164
  // src/cli/ui-intent.ts
33805
34165
  import { mkdir, readFile, rename, writeFile } from "fs/promises";
33806
34166
  import { homedir as homedir15 } from "os";
33807
- import { dirname as dirname10, join as join23 } from "path";
34167
+ import { dirname as dirname11, join as join24 } from "path";
33808
34168
  import { ulid as ulid11 } from "ulid";
33809
34169
  function getUiIntentPath(home = homedir15()) {
33810
- return join23(home, ".zam", "ui-intent.json");
34170
+ return join24(home, ".zam", "ui-intent.json");
33811
34171
  }
33812
34172
  function getUiHostRegistrationPath(home = homedir15()) {
33813
- return join23(home, ".zam", "vscode-host.json");
34173
+ return join24(home, ".zam", "vscode-host.json");
33814
34174
  }
33815
34175
  function compactStringInput(input) {
33816
34176
  return Object.fromEntries(
@@ -33829,8 +34189,8 @@ async function writeUiIntent(app, input = {}, opts = {}) {
33829
34189
  input: compactStringInput(input),
33830
34190
  createdAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
33831
34191
  };
33832
- const tempPath = join23(dirname10(path), `.ui-intent-${process.pid}-${id}.tmp`);
33833
- await mkdir(dirname10(path), { recursive: true });
34192
+ const tempPath = join24(dirname11(path), `.ui-intent-${process.pid}-${id}.tmp`);
34193
+ await mkdir(dirname11(path), { recursive: true });
33834
34194
  await writeFile(tempPath, `${JSON.stringify(intent, null, 2)}
33835
34195
  `, "utf8");
33836
34196
  await rename(tempPath, path);
@@ -33863,10 +34223,10 @@ async function publishUiIntent(app, input = {}, opts = {}) {
33863
34223
  }
33864
34224
 
33865
34225
  // src/cli/commands/mcp.ts
33866
- var __dirname = dirname12(fileURLToPath6(import.meta.url));
33867
- var pkgPath = join25(__dirname, "..", "..", "package.json");
34226
+ var __dirname = dirname13(fileURLToPath7(import.meta.url));
34227
+ var pkgPath = join26(__dirname, "..", "..", "package.json");
33868
34228
  if (!existsSync22(pkgPath)) {
33869
- pkgPath = join25(__dirname, "..", "..", "..", "package.json");
34229
+ pkgPath = join26(__dirname, "..", "..", "..", "package.json");
33870
34230
  }
33871
34231
  var pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
33872
34232
  var STUDIO_RESOURCE_URI = "ui://zam/studio";
@@ -33896,9 +34256,9 @@ var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
33896
34256
  function loadPanelHtml(fileName, placeholderTitle) {
33897
34257
  const candidates = [
33898
34258
  // dist/cli/commands/mcp.js → dist/ui/
33899
- join25(__dirname, "..", "..", "ui", fileName),
34259
+ join26(__dirname, "..", "..", "ui", fileName),
33900
34260
  // src/cli/commands/mcp.ts via tsx → <repo>/dist/ui/
33901
- join25(__dirname, "..", "..", "..", "dist", "ui", fileName)
34261
+ join26(__dirname, "..", "..", "..", "dist", "ui", fileName)
33902
34262
  ];
33903
34263
  for (const candidate of candidates) {
33904
34264
  if (existsSync22(candidate)) {
@@ -34467,7 +34827,7 @@ function createMcpServer(db) {
34467
34827
  "zam_show_graph",
34468
34828
  {
34469
34829
  title: "Open ZAM knowledge graph",
34470
- description: "Open the ZAM 2D knowledge-graph card, centered on a token's direct prerequisites and dependents. Pass `focus` (a token slug) when the conversation already names one; otherwise the card shows a hint to supply one.",
34830
+ description: "Open the ZAM 2D knowledge-graph card, centered on a token's direct prerequisites and dependents. Pass `focus` (a token slug) when the conversation already names one; without it the card offers scope selectors and defaults to tokens anchored in the current repo's OKF knowledge base.",
34471
34831
  inputSchema: {
34472
34832
  focus: z.string().optional().describe("Token slug to center the graph on"),
34473
34833
  user: z.string().optional().describe("User ID")
@@ -34489,6 +34849,16 @@ function createMcpServer(db) {
34489
34849
  { clientSamplingCapable: getClientSamplingCapable() }
34490
34850
  );
34491
34851
  const userId = opening.context.user.currentId ?? null;
34852
+ let repoScope;
34853
+ try {
34854
+ const { collectSourceLinkBases: collectSourceLinkBases2, findRepoRoot: findRepoRoot2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34855
+ const bundleDir = await resolveOkfBundleDir();
34856
+ const bases = collectSourceLinkBases2(bundleDir);
34857
+ if (bases.length > 0) {
34858
+ repoScope = { label: basename6(findRepoRoot2(bundleDir)), bases };
34859
+ }
34860
+ } catch {
34861
+ }
34492
34862
  await publishUiIntent("graph", { user, focus });
34493
34863
  return {
34494
34864
  graph: "zam",
@@ -34496,6 +34866,7 @@ function createMcpServer(db) {
34496
34866
  version: pkg.version,
34497
34867
  user: userId,
34498
34868
  companionContext: opening.context,
34869
+ ...repoScope ? { repoScope } : {},
34499
34870
  ...opening.degraded ? { companionContextDegraded: true } : {}
34500
34871
  };
34501
34872
  })
@@ -34667,7 +35038,24 @@ function createMcpServer(db) {
34667
35038
  }
34668
35039
  )
34669
35040
  );
34670
- const okfBundleDirSchema = z.string().optional().describe("Bundle directory (default docs/okf under the server cwd)");
35041
+ const okfBundleDirSchema = z.string().optional().describe(
35042
+ "Bundle directory (default: docs/okf under the client's workspace root, falling back to the server cwd)"
35043
+ );
35044
+ async function resolveOkfBundleDir(explicit) {
35045
+ const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveBundleDirFromRoots: resolveBundleDirFromRoots2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35046
+ if (explicit) return explicit;
35047
+ try {
35048
+ if (server.server.getClientCapabilities()?.roots) {
35049
+ const { roots } = await server.server.listRoots();
35050
+ return resolveBundleDirFromRoots2(
35051
+ (roots ?? []).map((root) => root.uri),
35052
+ DEFAULT_BUNDLE_DIR2
35053
+ );
35054
+ }
35055
+ } catch {
35056
+ }
35057
+ return DEFAULT_BUNDLE_DIR2;
35058
+ }
34671
35059
  server.registerTool(
34672
35060
  "zam_okf_catalog",
34673
35061
  {
@@ -34685,13 +35073,13 @@ function createMcpServer(db) {
34685
35073
  },
34686
35074
  wrapHandler(
34687
35075
  async (params) => {
34688
- const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34689
- const bundle = loadBundle2(params.bundle_dir ?? DEFAULT_BUNDLE_DIR2);
35076
+ const { loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35077
+ const bundle = loadBundle2(await resolveOkfBundleDir(params.bundle_dir));
34690
35078
  let log;
34691
35079
  if (params.include_log) {
34692
35080
  const { readFileSync: readFileSync20 } = await import("fs");
34693
35081
  try {
34694
- log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
35082
+ log = readFileSync20(join26(bundle.dir, "log.md"), "utf8");
34695
35083
  } catch {
34696
35084
  log = "";
34697
35085
  }
@@ -34720,10 +35108,10 @@ function createMcpServer(db) {
34720
35108
  },
34721
35109
  wrapHandler(async (params) => {
34722
35110
  const { readFileSync: readFileSync20 } = await import("fs");
34723
- const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35111
+ const { resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34724
35112
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
34725
35113
  const path = resolveArticlePath2(
34726
- params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
35114
+ await resolveOkfBundleDir(params.bundle_dir),
34727
35115
  params.file
34728
35116
  );
34729
35117
  const markdown = readFileSync20(path, "utf8");
@@ -34750,9 +35138,9 @@ function createMcpServer(db) {
34750
35138
  },
34751
35139
  wrapHandler(
34752
35140
  async (params) => {
34753
- const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, upsertArticle: upsertArticle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35141
+ const { upsertArticle: upsertArticle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34754
35142
  const result = upsertArticle2(
34755
- params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
35143
+ await resolveOkfBundleDir(params.bundle_dir),
34756
35144
  params.file,
34757
35145
  params.markdown
34758
35146
  );
@@ -34763,6 +35151,96 @@ function createMcpServer(db) {
34763
35151
  }
34764
35152
  )
34765
35153
  );
35154
+ server.registerTool(
35155
+ "zam_okf_import",
35156
+ {
35157
+ description: "Record YOUR finished decomposition of one OKF article as learning tokens + cards (ADR 2026-07-18). Decomposition is your job, not this tool's \u2014 before calling: (1) read the FULL article (zam_okf_read); (2) extract the concepts a practitioner must produce FROM MEMORY \u2014 recall-speed knowledge only; facts one would look up stay in the article; (3) one atomic concept per token, with a JUDGED bloom level and a JUDGED domain per token (reuse existing domains where they fit); (4) arrange a prerequisite DAG from foundational to dependent (in-import slugs and existing tokens both allowed); (5) check for existing tokens first (zam_find_tokens) and link them as prerequisites instead of duplicating. Multiple tokens are the expected outcome for real articles. On RE-import classify each token via mode: 'new' adds, 'update' refreshes content and KEEPS learning state, 'replace' means the concept changed \u2014 content refreshed and learning state RESET to the beginning. Previously imported tokens you do not confirm are moved to maintenance (kept, unscheduled) \u2014 never deleted. The write is atomic; every token gets a card for the importing user.",
35158
+ inputSchema: {
35159
+ user: z.string().optional().describe("User ID to create cards for"),
35160
+ bundle_dir: okfBundleDirSchema,
35161
+ file: z.string().describe("Article file name inside the bundle (kebab-case .md)"),
35162
+ tokens: z.array(
35163
+ z.object({
35164
+ slug: z.string().describe("Unique kebab-case token slug"),
35165
+ title: z.string().optional().describe("Display title"),
35166
+ concept: z.string().describe("The memorable concept, stated precisely"),
35167
+ bloomLevel: z.number().int().min(1).max(5).optional().describe("Judged Bloom level (1-5)"),
35168
+ domain: z.string().optional().describe("Judged domain (reuse existing domains)"),
35169
+ anchor: z.string().optional().describe(
35170
+ "Heading anchor in the article; appended to the source link"
35171
+ ),
35172
+ prerequisites: z.array(z.string()).optional().describe(
35173
+ "Slugs this token requires \u2014 in-import or existing tokens"
35174
+ ),
35175
+ knowledgeContexts: z.array(z.string()).optional().describe("Knowledge contexts to assign"),
35176
+ question: z.string().nullable().optional().describe("Optional pre-defined review question"),
35177
+ mode: z.enum(["new", "update", "replace"]).optional().describe(
35178
+ "Re-import classification: new (default) | update (keep learning state) | replace (concept changed, reset learning state)"
35179
+ )
35180
+ })
35181
+ ).min(1).describe("Your finished decomposition")
35182
+ },
35183
+ annotations: {
35184
+ ...commonAnnotations
35185
+ }
35186
+ },
35187
+ wrapHandler(
35188
+ async (params) => {
35189
+ return await importOkfTokens(db, {
35190
+ user: await getUserId(params.user),
35191
+ bundleDir: await resolveOkfBundleDir(params.bundle_dir),
35192
+ file: params.file,
35193
+ tokens: params.tokens
35194
+ });
35195
+ }
35196
+ )
35197
+ );
35198
+ registerAppTool(
35199
+ server,
35200
+ "zam_okf_focus",
35201
+ {
35202
+ description: "App-only: record which OKF article the visualizer panel currently shows, so chat agents can resolve 'the currently focused article'. Not intended for direct model use.",
35203
+ inputSchema: {
35204
+ file: z.string().describe("Article file name inside the bundle (kebab-case .md)"),
35205
+ bundle_dir: z.string().optional().describe("Absolute bundle directory the panel is browsing")
35206
+ },
35207
+ annotations: {
35208
+ ...commonAnnotations,
35209
+ destructiveHint: false,
35210
+ idempotentHint: true
35211
+ },
35212
+ _meta: {
35213
+ ui: { visibility: ["app"] }
35214
+ }
35215
+ },
35216
+ wrapHandler(async (params) => {
35217
+ const { writeOkfFocus: writeOkfFocus2 } = await Promise.resolve().then(() => (init_okf_focus(), okf_focus_exports));
35218
+ const focus = await writeOkfFocus2(params.file, params.bundle_dir);
35219
+ return { recorded: { file: focus.file, updatedAt: focus.updatedAt } };
35220
+ })
35221
+ );
35222
+ server.registerTool(
35223
+ "zam_okf_focused",
35224
+ {
35225
+ description: "The OKF article currently focused in the user's visualizer panel \u2014 resolve requests like 'import this okf', 'import the currently focused article', or 'the open article'. Returns {focused: {file, bundle_dir?, updatedAt} | null}; check updatedAt and confirm the article by name with the user if it looks stale. To import, read the article (zam_okf_read) and follow the zam_okf_import contract.",
35226
+ inputSchema: {},
35227
+ annotations: {
35228
+ ...commonAnnotations,
35229
+ readOnlyHint: true
35230
+ }
35231
+ },
35232
+ wrapHandler(async () => {
35233
+ const { readOkfFocus: readOkfFocus2 } = await Promise.resolve().then(() => (init_okf_focus(), okf_focus_exports));
35234
+ const focus = await readOkfFocus2();
35235
+ return {
35236
+ focused: focus ? {
35237
+ file: focus.file,
35238
+ ...focus.bundleDir ? { bundle_dir: focus.bundleDir } : {},
35239
+ updatedAt: focus.updatedAt
35240
+ } : null
35241
+ };
35242
+ })
35243
+ );
34766
35244
  server.registerTool(
34767
35245
  "zam_okf_read_citation",
34768
35246
  {
@@ -34781,8 +35259,8 @@ function createMcpServer(db) {
34781
35259
  wrapHandler(async (params) => {
34782
35260
  const { readFileSync: readFileSync20 } = await import("fs");
34783
35261
  const { relative: relative2, sep: sep2 } = await import("path");
34784
- const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, findRepoRoot: findRepoRoot2, resolveCitationPath: resolveCitationPath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34785
- const bundleDir = params.bundle_dir ?? DEFAULT_BUNDLE_DIR2;
35262
+ const { findRepoRoot: findRepoRoot2, resolveCitationPath: resolveCitationPath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35263
+ const bundleDir = await resolveOkfBundleDir(params.bundle_dir);
34786
35264
  const path = resolveCitationPath2(bundleDir, params.target);
34787
35265
  const content = readFileSync20(path, "utf8");
34788
35266
  const root = findRepoRoot2(bundleDir);
@@ -34815,11 +35293,11 @@ function createMcpServer(db) {
34815
35293
  getNativeClientInfo(),
34816
35294
  { clientSamplingCapable: getClientSamplingCapable() }
34817
35295
  );
34818
- await publishUiIntent("okf", { bundle_dir });
34819
- const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35296
+ const { loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34820
35297
  const { resolve: resolve8 } = await import("path");
34821
- const requestedDir = bundle_dir ?? DEFAULT_BUNDLE_DIR2;
35298
+ const requestedDir = await resolveOkfBundleDir(bundle_dir);
34822
35299
  let resolvedBundleDir = resolve8(requestedDir);
35300
+ await publishUiIntent("okf", { bundle_dir: resolvedBundleDir });
34823
35301
  let catalog = [];
34824
35302
  let problems = [];
34825
35303
  let log = "";
@@ -34831,13 +35309,13 @@ function createMcpServer(db) {
34831
35309
  problems = bundle.problems;
34832
35310
  const { readFileSync: readFileSync20 } = await import("fs");
34833
35311
  try {
34834
- log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
35312
+ log = readFileSync20(join26(bundle.dir, "log.md"), "utf8");
34835
35313
  } catch {
34836
35314
  log = "";
34837
35315
  }
34838
35316
  try {
34839
35317
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
34840
- const indexRaw = readFileSync20(join25(bundle.dir, "index.md"), "utf8");
35318
+ const indexRaw = readFileSync20(join26(bundle.dir, "index.md"), "utf8");
34841
35319
  const { fields } = parseFrontmatter2(indexRaw);
34842
35320
  okfVersion = typeof fields.okf_version === "string" ? fields.okf_version : null;
34843
35321
  } catch {