zam-core 0.12.0 → 0.14.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) {
@@ -5268,7 +5328,8 @@ async function buildReviewQueue(db, options) {
5268
5328
  AND c.blocked = 0
5269
5329
  AND c.due_at <= ?
5270
5330
  AND c.state IN ('review', 'relearning', 'learning')
5271
- AND t.deprecated_at IS NULL`;
5331
+ AND t.deprecated_at IS NULL
5332
+ AND t.maintenance_at IS NULL`;
5272
5333
  const dueParams = [options.userId, nowISO];
5273
5334
  if (options.knowledgeContext) {
5274
5335
  dueSql += ` AND EXISTS (
@@ -5298,7 +5359,8 @@ async function buildReviewQueue(db, options) {
5298
5359
  WHERE c.user_id = ?
5299
5360
  AND c.blocked = 0
5300
5361
  AND c.state = 'new'
5301
- AND t.deprecated_at IS NULL`;
5362
+ AND t.deprecated_at IS NULL
5363
+ AND t.maintenance_at IS NULL`;
5302
5364
  const newParams = [options.userId];
5303
5365
  if (options.knowledgeContext) {
5304
5366
  newSql += ` AND EXISTS (
@@ -6992,6 +7054,7 @@ __export(kernel_exports, {
6992
7054
  clearADOCredentials: () => clearADOCredentials,
6993
7055
  clearProviderApiKey: () => clearProviderApiKey,
6994
7056
  clearReviewContextCache: () => clearReviewContextCache,
7057
+ clearTokenMaintenance: () => clearTokenMaintenance,
6995
7058
  clearTursoCredentials: () => clearTursoCredentials,
6996
7059
  compareVersions: () => compareVersions,
6997
7060
  computeContentHash: () => computeContentHash,
@@ -7100,6 +7163,7 @@ __export(kernel_exports, {
7100
7163
  getTokenDeleteImpact: () => getTokenDeleteImpact,
7101
7164
  getTokenEmbedding: () => getTokenEmbedding,
7102
7165
  getTokenNeighborhood: () => getTokenNeighborhood,
7166
+ getTokensBySourceLinkBase: () => getTokensBySourceLinkBase,
7103
7167
  getTursoCredentials: () => getTursoCredentials,
7104
7168
  getUiObservationPath: () => getUiObservationPath,
7105
7169
  getUiObserverDir: () => getUiObserverDir,
@@ -7154,6 +7218,7 @@ __export(kernel_exports, {
7154
7218
  readMonitorLog: () => readMonitorLog,
7155
7219
  readUiObservationLog: () => readUiObservationLog,
7156
7220
  removeConfiguredWorkspace: () => removeConfiguredWorkspace,
7221
+ resetCardsForToken: () => resetCardsForToken,
7157
7222
  resolveAllBeliefPaths: () => resolveAllBeliefPaths,
7158
7223
  resolveAllGoalPaths: () => resolveAllGoalPaths,
7159
7224
  resolveObserverPolicy: () => resolveObserverPolicy,
@@ -7185,6 +7250,7 @@ __export(kernel_exports, {
7185
7250
  setLastRepairedVersion: () => setLastRepairedVersion,
7186
7251
  setProviderApiKey: () => setProviderApiKey,
7187
7252
  setSetting: () => setSetting,
7253
+ setTokenMaintenance: () => setTokenMaintenance,
7188
7254
  setTursoCredentials: () => setTursoCredentials,
7189
7255
  slugify: () => slugify,
7190
7256
  startSession: () => startSession,
@@ -9165,12 +9231,34 @@ var init_bundle = __esm({
9165
9231
  var io_exports = {};
9166
9232
  __export(io_exports, {
9167
9233
  DEFAULT_BUNDLE_DIR: () => DEFAULT_BUNDLE_DIR,
9234
+ findRepoRoot: () => findRepoRoot,
9168
9235
  loadBundle: () => loadBundle,
9169
9236
  resolveArticlePath: () => resolveArticlePath,
9237
+ resolveBundleDirFromRoots: () => resolveBundleDirFromRoots,
9238
+ resolveCitationPath: () => resolveCitationPath,
9170
9239
  upsertArticle: () => upsertArticle
9171
9240
  });
9172
- import { mkdirSync as mkdirSync16, readdirSync as readdirSync3, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "fs";
9173
- import { join as join24, resolve as resolve7 } from "path";
9241
+ import {
9242
+ existsSync as existsSync13,
9243
+ mkdirSync as mkdirSync9,
9244
+ readdirSync as readdirSync2,
9245
+ readFileSync as readFileSync11,
9246
+ realpathSync,
9247
+ writeFileSync as writeFileSync6
9248
+ } from "fs";
9249
+ import { dirname as dirname6, isAbsolute, join as join13, relative, resolve as resolve4, sep } from "path";
9250
+ import { fileURLToPath as fileURLToPath3 } from "url";
9251
+ function resolveBundleDirFromRoots(rootUris, fallback) {
9252
+ const dirs = [];
9253
+ for (const uri of rootUris) {
9254
+ if (!uri?.startsWith("file:")) continue;
9255
+ try {
9256
+ dirs.push(join13(fileURLToPath3(uri), DEFAULT_BUNDLE_DIR));
9257
+ } catch {
9258
+ }
9259
+ }
9260
+ return dirs.find((dir) => existsSync13(dir)) ?? dirs[0] ?? fallback;
9261
+ }
9174
9262
  function resolveArticlePath(dir, file) {
9175
9263
  if (file.includes("/") || file.includes("\\") || file.includes("..")) {
9176
9264
  throw new Error(`invalid article file name: ${file}`);
@@ -9178,13 +9266,49 @@ function resolveArticlePath(dir, file) {
9178
9266
  if (isReservedFile(file)) {
9179
9267
  throw new Error(`refusing to address reserved file: ${file}`);
9180
9268
  }
9181
- return join24(resolve7(dir), file);
9269
+ return join13(resolve4(dir), file);
9270
+ }
9271
+ function findRepoRoot(startDir) {
9272
+ let dir = resolve4(startDir);
9273
+ for (; ; ) {
9274
+ if (existsSync13(join13(dir, ".git"))) return dir;
9275
+ const parent = dirname6(dir);
9276
+ if (parent === dir) return resolve4(startDir, "..");
9277
+ dir = parent;
9278
+ }
9279
+ }
9280
+ function resolveCitationPath(bundleDir, target) {
9281
+ if (isAbsolute(target)) {
9282
+ throw new Error(
9283
+ `invalid citation target: absolute paths are not allowed (${target})`
9284
+ );
9285
+ }
9286
+ if (!target.endsWith(".md")) {
9287
+ throw new Error(
9288
+ `invalid citation target: only .md files are readable (${target})`
9289
+ );
9290
+ }
9291
+ const root = findRepoRoot(bundleDir);
9292
+ const resolved = resolve4(bundleDir, target);
9293
+ assertContained(root, resolved, target);
9294
+ if (existsSync13(resolved)) {
9295
+ assertContained(realpathSync(root), realpathSync(resolved), target);
9296
+ }
9297
+ return resolved;
9298
+ }
9299
+ function assertContained(root, candidate, target) {
9300
+ const rel = relative(root, candidate);
9301
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
9302
+ throw new Error(
9303
+ `invalid citation target: resolves outside the repository root (${target})`
9304
+ );
9305
+ }
9182
9306
  }
9183
9307
  function loadBundle(dir) {
9184
- const root = resolve7(dir);
9308
+ const root = resolve4(dir);
9185
9309
  let entries;
9186
9310
  try {
9187
- entries = readdirSync3(root).filter(
9311
+ entries = readdirSync2(root).filter(
9188
9312
  (name) => name.endsWith(".md") && !isReservedFile(name)
9189
9313
  );
9190
9314
  } catch {
@@ -9192,7 +9316,7 @@ function loadBundle(dir) {
9192
9316
  }
9193
9317
  const articles = entries.sort().map((file) => ({
9194
9318
  file,
9195
- markdown: readFileSync18(join24(root, file), "utf8")
9319
+ markdown: readFileSync11(join13(root, file), "utf8")
9196
9320
  }));
9197
9321
  const problems = articles.flatMap(
9198
9322
  ({ file, markdown }) => validateArticle(file, markdown).problems
@@ -9214,25 +9338,25 @@ function upsertArticle(dir, file, markdown, today = (/* @__PURE__ */ new Date())
9214
9338
  const target = resolveArticlePath(dir, file);
9215
9339
  const validation = validateArticle(file, markdown);
9216
9340
  if (!validation.ok) return { validation };
9217
- const root = resolve7(dir);
9218
- mkdirSync16(root, { recursive: true });
9341
+ const root = resolve4(dir);
9342
+ mkdirSync9(root, { recursive: true });
9219
9343
  let created = true;
9220
9344
  try {
9221
- readFileSync18(target, "utf8");
9345
+ readFileSync11(target, "utf8");
9222
9346
  created = false;
9223
9347
  } catch {
9224
9348
  }
9225
- writeFileSync13(target, markdown, "utf8");
9349
+ writeFileSync6(target, markdown, "utf8");
9226
9350
  const bundle = loadBundle(root);
9227
- writeFileSync13(join24(root, "index.md"), renderIndex(bundle.catalog), "utf8");
9351
+ writeFileSync6(join13(root, "index.md"), renderIndex(bundle.catalog), "utf8");
9228
9352
  let log = "";
9229
9353
  try {
9230
- log = readFileSync18(join24(root, "log.md"), "utf8");
9354
+ log = readFileSync11(join13(root, "log.md"), "utf8");
9231
9355
  } catch {
9232
9356
  }
9233
9357
  const entry = bundle.catalog.find((e) => e.file === file);
9234
- writeFileSync13(
9235
- join24(root, "log.md"),
9358
+ writeFileSync6(
9359
+ join13(root, "log.md"),
9236
9360
  appendLog(
9237
9361
  log,
9238
9362
  today,
@@ -9253,9 +9377,9 @@ var init_io = __esm({
9253
9377
 
9254
9378
  // src/cli/commands/mcp.ts
9255
9379
  init_kernel();
9256
- import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
9257
- import { dirname as dirname11, join as join25 } from "path";
9258
- import { fileURLToPath as fileURLToPath6 } from "url";
9380
+ import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
9381
+ import { dirname as dirname12, join as join25 } from "path";
9382
+ import { fileURLToPath as fileURLToPath7 } from "url";
9259
9383
  import {
9260
9384
  RESOURCE_MIME_TYPE,
9261
9385
  registerAppResource,
@@ -9347,7 +9471,8 @@ var COMPANION_SURFACES = [
9347
9471
  "recall",
9348
9472
  "graph",
9349
9473
  "settings",
9350
- "studio"
9474
+ "studio",
9475
+ "okf"
9351
9476
  ];
9352
9477
  function isCompanionSurface(value) {
9353
9478
  return typeof value === "string" && COMPANION_SURFACES.includes(value);
@@ -9467,8 +9592,8 @@ function buildCompanionContext(input) {
9467
9592
 
9468
9593
  // src/cli/bridge-handlers.ts
9469
9594
  init_kernel();
9470
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
9471
- import { join as join13 } from "path";
9595
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
9596
+ import { join as join14 } from "path";
9472
9597
 
9473
9598
  // src/cli/knowledge-contexts.ts
9474
9599
  init_kernel();
@@ -10351,6 +10476,161 @@ async function addToken(db, params) {
10351
10476
  possible_duplicates: possibleDuplicates
10352
10477
  };
10353
10478
  }
10479
+ async function importOkfTokens(db, params) {
10480
+ const userId = await resolveHandlerUser(db, params.user);
10481
+ if (!params.file?.trim()) throw new Error("file must be non-empty");
10482
+ if (!Array.isArray(params.tokens) || params.tokens.length === 0) {
10483
+ throw new Error("tokens must be a non-empty array");
10484
+ }
10485
+ const seen = /* @__PURE__ */ new Set();
10486
+ for (const input of params.tokens) {
10487
+ const slug = input.slug?.trim();
10488
+ if (!slug || !input.concept?.trim()) {
10489
+ throw new Error("every token needs a non-empty slug and concept");
10490
+ }
10491
+ if (seen.has(slug)) throw new Error(`duplicate token slug: ${slug}`);
10492
+ seen.add(slug);
10493
+ if (input.bloomLevel !== void 0 && (!Number.isInteger(input.bloomLevel) || input.bloomLevel < 1 || input.bloomLevel > 5)) {
10494
+ throw new Error(`bloomLevel must be 1-5 (token: ${slug})`);
10495
+ }
10496
+ }
10497
+ const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
10498
+ const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
10499
+ const { readFileSync: readFileSync20 } = await import("fs");
10500
+ const bundleDir = params.bundleDir ?? DEFAULT_BUNDLE_DIR2;
10501
+ const articlePath = resolveArticlePath2(bundleDir, params.file);
10502
+ let markdown;
10503
+ try {
10504
+ markdown = readFileSync20(articlePath, "utf8");
10505
+ } catch {
10506
+ throw new Error(`Article not found: ${articlePath}`);
10507
+ }
10508
+ let resource;
10509
+ try {
10510
+ const { fields } = parseFrontmatter2(markdown);
10511
+ resource = typeof fields.resource === "string" ? fields.resource : void 0;
10512
+ } catch {
10513
+ resource = void 0;
10514
+ }
10515
+ const sourceBase = resource ?? articlePath;
10516
+ const sourceLinkFor = (anchor) => anchor?.trim() ? `${sourceBase}#${anchor.trim()}` : sourceBase;
10517
+ const created = [];
10518
+ const updated = [];
10519
+ const replaced = [];
10520
+ const maintenance = [];
10521
+ let cardsEnsured = 0;
10522
+ await db.transaction(async (tx) => {
10523
+ const inImport = /* @__PURE__ */ new Map();
10524
+ for (const input of params.tokens) {
10525
+ const mode = input.mode ?? "new";
10526
+ const existing = await getTokenBySlug(tx, input.slug);
10527
+ if (mode === "new") {
10528
+ if (existing) {
10529
+ throw new Error(
10530
+ `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.`
10531
+ );
10532
+ }
10533
+ const token2 = await createToken(tx, {
10534
+ slug: input.slug,
10535
+ title: input.title,
10536
+ concept: input.concept,
10537
+ domain: input.domain,
10538
+ bloom_level: input.bloomLevel ?? 1,
10539
+ source_link: sourceLinkFor(input.anchor),
10540
+ question: input.question ?? null,
10541
+ question_source: input.question ? "llm" : void 0
10542
+ });
10543
+ inImport.set(input.slug, token2);
10544
+ created.push(input.slug);
10545
+ } else {
10546
+ if (!existing) {
10547
+ throw new Error(
10548
+ `Token '${input.slug}' does not exist \u2014 mode "${mode}" requires an existing token.`
10549
+ );
10550
+ }
10551
+ const updates = {
10552
+ concept: input.concept,
10553
+ source_link: sourceLinkFor(input.anchor)
10554
+ };
10555
+ if (input.title !== void 0) updates.title = input.title;
10556
+ if (input.domain !== void 0) updates.domain = input.domain;
10557
+ if (input.bloomLevel !== void 0) {
10558
+ updates.bloom_level = input.bloomLevel;
10559
+ }
10560
+ if (input.question !== void 0) {
10561
+ updates.question = input.question;
10562
+ updates.question_source = input.question ? "llm" : void 0;
10563
+ }
10564
+ const token2 = await updateToken(tx, input.slug, updates);
10565
+ if (existing.maintenance_at) {
10566
+ await clearTokenMaintenance(tx, input.slug);
10567
+ }
10568
+ if (mode === "replace") {
10569
+ await resetCardsForToken(tx, token2.id);
10570
+ replaced.push(input.slug);
10571
+ } else {
10572
+ updated.push(input.slug);
10573
+ }
10574
+ inImport.set(input.slug, token2);
10575
+ }
10576
+ const ctxNames = parseKnowledgeContextNames(input.knowledgeContexts);
10577
+ const assigned = await resolveKnowledgeContexts(tx, ctxNames);
10578
+ const token = inImport.get(input.slug);
10579
+ if (token) {
10580
+ for (const context of assigned) {
10581
+ await assignTokenToContext(tx, token.id, context.id);
10582
+ }
10583
+ }
10584
+ }
10585
+ for (const input of params.tokens) {
10586
+ const token = inImport.get(input.slug);
10587
+ if (!token) continue;
10588
+ const prereqSlugs = [
10589
+ ...new Set(
10590
+ (input.prerequisites ?? []).map((s) => s.trim()).filter(Boolean)
10591
+ )
10592
+ ];
10593
+ for (const prereqSlug of prereqSlugs) {
10594
+ const target = inImport.get(prereqSlug) ?? await getTokenBySlug(tx, prereqSlug);
10595
+ if (!target) {
10596
+ throw new Error(
10597
+ `Prerequisite token not found: ${prereqSlug} (for '${input.slug}')`
10598
+ );
10599
+ }
10600
+ await addPrerequisite(tx, token.id, target.id);
10601
+ }
10602
+ }
10603
+ for (const token of inImport.values()) {
10604
+ await ensureCard(tx, token.id, userId);
10605
+ cardsEnsured++;
10606
+ }
10607
+ const prior = await getTokensBySourceLinkBase(tx, sourceBase);
10608
+ for (const token of prior) {
10609
+ if (!inImport.has(token.slug)) {
10610
+ await setTokenMaintenance(
10611
+ tx,
10612
+ token.slug,
10613
+ `absent from re-import of ${params.file}`
10614
+ );
10615
+ maintenance.push(token.slug);
10616
+ }
10617
+ }
10618
+ });
10619
+ try {
10620
+ await ensureTokenEmbeddings(db, { limit: 16 });
10621
+ } catch {
10622
+ }
10623
+ return {
10624
+ success: true,
10625
+ user: userId,
10626
+ article: { file: params.file, source_link: sourceBase },
10627
+ created,
10628
+ updated,
10629
+ replaced,
10630
+ maintenance,
10631
+ cards: cardsEnsured
10632
+ };
10633
+ }
10354
10634
  async function findTokens2(db, params) {
10355
10635
  const userId = await resolveHandlerUser(db, params.user);
10356
10636
  if (!params.context.trim()) {
@@ -10675,11 +10955,11 @@ async function backupCreate(db, params) {
10675
10955
  const targetDir = params.dir || (await ensureActiveWorkspace(db)).path;
10676
10956
  const snapshot = await exportSnapshot(db);
10677
10957
  const manifest = verifySnapshot(snapshot);
10678
- const backupDir = join13(targetDir, "zam-backups");
10679
- mkdirSync9(backupDir, { recursive: true });
10958
+ const backupDir = join14(targetDir, "zam-backups");
10959
+ mkdirSync10(backupDir, { recursive: true });
10680
10960
  const stamp = manifest.createdAt.replace(/[:.]/g, "-");
10681
- const path = join13(backupDir, `zam-snapshot-${stamp}.sql`);
10682
- writeFileSync6(path, snapshot, "utf-8");
10961
+ const path = join14(backupDir, `zam-snapshot-${stamp}.sql`);
10962
+ writeFileSync7(path, snapshot, "utf-8");
10683
10963
  return {
10684
10964
  ok: true,
10685
10965
  path,
@@ -10704,27 +10984,27 @@ init_kernel();
10704
10984
 
10705
10985
  // src/cli/agent-connect.ts
10706
10986
  init_kernel();
10707
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "fs";
10987
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
10708
10988
  import { homedir as homedir12 } from "os";
10709
- import { dirname as dirname8 } from "path";
10989
+ import { dirname as dirname9 } from "path";
10710
10990
 
10711
10991
  // src/cli/agent-harness.ts
10712
10992
  import { spawn as spawn2 } from "child_process";
10713
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
10993
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
10714
10994
  import { homedir as homedir9 } from "os";
10715
- import { join as join15 } from "path";
10995
+ import { join as join16 } from "path";
10716
10996
 
10717
10997
  // src/cli/terminal-open.ts
10718
10998
  import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
10719
10999
  import {
10720
11000
  accessSync,
10721
11001
  constants,
10722
- existsSync as existsSync13,
11002
+ existsSync as existsSync14,
10723
11003
  unlinkSync as unlinkSync2,
10724
- writeFileSync as writeFileSync7
11004
+ writeFileSync as writeFileSync8
10725
11005
  } from "fs";
10726
11006
  import { tmpdir } from "os";
10727
- import { basename as basename3, delimiter, extname, isAbsolute, join as join14 } from "path";
11007
+ import { basename as basename3, delimiter, extname, isAbsolute as isAbsolute2, join as join15 } from "path";
10728
11008
  function isPowerShellShell(shell) {
10729
11009
  return shell === "pwsh" || shell === "powershell";
10730
11010
  }
@@ -10766,7 +11046,7 @@ function stripSurroundingQuotes(command) {
10766
11046
  return trimmed;
10767
11047
  }
10768
11048
  function executableExists(path) {
10769
- if (!existsSync13(path)) return false;
11049
+ if (!existsSync14(path)) return false;
10770
11050
  if (process.platform === "win32") return true;
10771
11051
  try {
10772
11052
  accessSync(path, constants.X_OK);
@@ -10782,7 +11062,7 @@ function windowsExecutableNames(command) {
10782
11062
  return extensions.map((ext) => `${command}${ext}`);
10783
11063
  }
10784
11064
  function hasDirectoryPart(command) {
10785
- return isAbsolute(command) || command.includes("/") || command.includes("\\");
11065
+ return isAbsolute2(command) || command.includes("/") || command.includes("\\");
10786
11066
  }
10787
11067
  function findExecutable(command) {
10788
11068
  const normalized = stripSurroundingQuotes(command);
@@ -10797,7 +11077,7 @@ function findExecutable(command) {
10797
11077
  const pathEntries = (process.env.PATH ?? "").split(delimiter).map((entry) => entry.trim()).filter(Boolean);
10798
11078
  for (const entry of pathEntries) {
10799
11079
  for (const name of windowsExecutableNames(normalized)) {
10800
- const candidate = join14(entry, name);
11080
+ const candidate = join15(entry, name);
10801
11081
  if (executableExists(candidate)) matches.push(candidate);
10802
11082
  }
10803
11083
  }
@@ -10835,9 +11115,9 @@ end tell` : `tell application "Terminal"
10835
11115
  activate
10836
11116
  do script "${escaped}"
10837
11117
  end tell`;
10838
- const tmpFile = join14(tmpdir(), `zam-terminal-${label}.scpt`);
11118
+ const tmpFile = join15(tmpdir(), `zam-terminal-${label}.scpt`);
10839
11119
  try {
10840
- writeFileSync7(tmpFile, appleScript);
11120
+ writeFileSync8(tmpFile, appleScript);
10841
11121
  execSync4(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
10842
11122
  if (!silent) {
10843
11123
  console.log(
@@ -10918,7 +11198,7 @@ function openTerminalWindow(opts) {
10918
11198
  // src/cli/agent-harness.ts
10919
11199
  var ANTIGRAVITY_IDE_CANDIDATE_PATHS = {
10920
11200
  darwin: [
10921
- join15(
11201
+ join16(
10922
11202
  homedir9(),
10923
11203
  ".antigravity-ide",
10924
11204
  "antigravity-ide",
@@ -10939,7 +11219,7 @@ var AGENT_HARNESSES = [
10939
11219
  command: "cursor",
10940
11220
  candidatePaths: {
10941
11221
  win32: [
10942
- join15(homedir9(), "AppData", "Local", "Programs", "cursor", "Cursor.exe")
11222
+ join16(homedir9(), "AppData", "Local", "Programs", "cursor", "Cursor.exe")
10943
11223
  ],
10944
11224
  darwin: ["/Applications/Cursor.app/Contents/MacOS/Cursor"]
10945
11225
  }
@@ -10964,7 +11244,7 @@ function getHarness(id) {
10964
11244
  }
10965
11245
  function resolveAntigravityIdeExecutable(deps = {}) {
10966
11246
  const find = deps.find ?? findExecutable;
10967
- const exists = deps.exists ?? existsSync14;
11247
+ const exists = deps.exists ?? existsSync15;
10968
11248
  const platform = deps.platform ?? process.platform;
10969
11249
  const found = find("antigravity-ide");
10970
11250
  if (found) return found;
@@ -10972,7 +11252,7 @@ function resolveAntigravityIdeExecutable(deps = {}) {
10972
11252
  }
10973
11253
  function resolveHarnessExecutable(harness, overrideCommand, deps = {}) {
10974
11254
  const find = deps.find ?? findExecutable;
10975
- const exists = deps.exists ?? existsSync14;
11255
+ const exists = deps.exists ?? existsSync15;
10976
11256
  const platform = deps.platform ?? process.platform;
10977
11257
  const found = find(overrideCommand || harness.command);
10978
11258
  if (found) return found;
@@ -11032,18 +11312,18 @@ function detectInstalledConnectHarnesses(options = {}) {
11032
11312
  const home = options.home ?? homedir9();
11033
11313
  const platform = options.platform ?? process.platform;
11034
11314
  const find = options.find ?? findExecutable;
11035
- const exists = options.exists ?? existsSync14;
11315
+ const exists = options.exists ?? existsSync15;
11036
11316
  const detected = [];
11037
11317
  const hasCommandOrPath = (command, paths = []) => Boolean(find(command)) || paths.some((path) => exists(path));
11038
11318
  if (hasCommandOrPath("codex", [
11039
- join15(home, ".codex"),
11040
- ...platform === "darwin" ? ["/Applications/Codex.app"] : platform === "win32" ? [join15(home, "AppData", "Local", "Programs", "Codex")] : []
11319
+ join16(home, ".codex"),
11320
+ ...platform === "darwin" ? ["/Applications/Codex.app"] : platform === "win32" ? [join16(home, "AppData", "Local", "Programs", "Codex")] : []
11041
11321
  ])) {
11042
11322
  detected.push("codex");
11043
11323
  }
11044
11324
  const vscodePaths = platform === "darwin" ? [
11045
11325
  "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
11046
- join15(
11326
+ join16(
11047
11327
  home,
11048
11328
  "Applications",
11049
11329
  "Visual Studio Code.app",
@@ -11054,7 +11334,7 @@ function detectInstalledConnectHarnesses(options = {}) {
11054
11334
  "code"
11055
11335
  )
11056
11336
  ] : platform === "win32" ? [
11057
- join15(
11337
+ join16(
11058
11338
  home,
11059
11339
  "AppData",
11060
11340
  "Local",
@@ -11065,23 +11345,23 @@ function detectInstalledConnectHarnesses(options = {}) {
11065
11345
  )
11066
11346
  ] : ["/usr/bin/code", "/usr/local/bin/code", "/snap/bin/code"];
11067
11347
  if (hasCommandOrPath("code", vscodePaths)) detected.push("vscode");
11068
- const copilotHome = options.copilotHome ?? join15(home, ".copilot");
11348
+ const copilotHome = options.copilotHome ?? join16(home, ".copilot");
11069
11349
  if (hasCommandOrPath("copilot", [
11070
11350
  copilotHome,
11071
11351
  ...platform === "darwin" ? ["/Applications/GitHub Copilot.app"] : []
11072
11352
  ])) {
11073
11353
  detected.push("copilot");
11074
11354
  }
11075
- if (hasCommandOrPath("opencode", [join15(home, ".config", "opencode")])) {
11355
+ if (hasCommandOrPath("opencode", [join16(home, ".config", "opencode")])) {
11076
11356
  detected.push("opencode");
11077
11357
  }
11078
- if (hasCommandOrPath("goose", [join15(home, ".config", "goose")])) {
11358
+ if (hasCommandOrPath("goose", [join16(home, ".config", "goose")])) {
11079
11359
  detected.push("goose");
11080
11360
  }
11081
11361
  if (hasCommandOrPath("antigravity", [
11082
- join15(home, ".gemini", "config"),
11362
+ join16(home, ".gemini", "config"),
11083
11363
  ...platform === "darwin" ? [
11084
- join15(
11364
+ join16(
11085
11365
  home,
11086
11366
  ".antigravity-ide",
11087
11367
  "antigravity-ide",
@@ -11094,7 +11374,7 @@ function detectInstalledConnectHarnesses(options = {}) {
11094
11374
  ]) || find("antigravity-ide")) {
11095
11375
  detected.push("antigravity");
11096
11376
  }
11097
- 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");
11377
+ 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");
11098
11378
  if (exists(claudeDesktopPath)) detected.push("claude-desktop");
11099
11379
  return detected;
11100
11380
  }
@@ -11127,11 +11407,11 @@ function connectHarnessMcp(harnessId, opts) {
11127
11407
  return false;
11128
11408
  }
11129
11409
  }
11130
- return existsSync14(p);
11410
+ return existsSync15(p);
11131
11411
  };
11132
11412
  const read = (p) => {
11133
11413
  if (opts.readFile) return opts.readFile(p);
11134
- return readFileSync11(p, "utf-8");
11414
+ return readFileSync12(p, "utf-8");
11135
11415
  };
11136
11416
  let targetPath = "";
11137
11417
  let content = "";
@@ -11163,32 +11443,32 @@ function connectHarnessMcp(harnessId, opts) {
11163
11443
  return JSON.stringify(existing, null, 2);
11164
11444
  };
11165
11445
  if (harnessId === "claude-code") {
11166
- targetPath = join15(opts.cwd, ".mcp.json");
11446
+ targetPath = join16(opts.cwd, ".mcp.json");
11167
11447
  hint = "Claude Code will prompt you to approve the 'zam' MCP server on next launch.";
11168
11448
  content = mergeMcpServersJson(targetPath);
11169
11449
  } else if (harnessId === "claude-desktop") {
11170
11450
  const platform = opts.platform ?? process.platform;
11171
- targetPath = platform === "win32" ? join15(
11451
+ targetPath = platform === "win32" ? join16(
11172
11452
  opts.home,
11173
11453
  "AppData",
11174
11454
  "Roaming",
11175
11455
  "Claude",
11176
11456
  "claude_desktop_config.json"
11177
- ) : platform === "darwin" ? join15(
11457
+ ) : platform === "darwin" ? join16(
11178
11458
  opts.home,
11179
11459
  "Library",
11180
11460
  "Application Support",
11181
11461
  "Claude",
11182
11462
  "claude_desktop_config.json"
11183
- ) : join15(opts.home, ".config", "Claude", "claude_desktop_config.json");
11463
+ ) : join16(opts.home, ".config", "Claude", "claude_desktop_config.json");
11184
11464
  hint = "Restart Claude Desktop to load the 'zam' MCP server; MCP Apps panels render inline in the chat.";
11185
11465
  content = mergeMcpServersJson(targetPath);
11186
11466
  } else if (harnessId === "antigravity") {
11187
- targetPath = join15(opts.home, ".gemini", "config", "mcp_config.json");
11467
+ targetPath = join16(opts.home, ".gemini", "config", "mcp_config.json");
11188
11468
  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.";
11189
11469
  content = mergeMcpServersJson(targetPath);
11190
11470
  } else if (harnessId === "opencode") {
11191
- targetPath = join15(opts.home, ".config", "opencode", "opencode.json");
11471
+ targetPath = join16(opts.home, ".config", "opencode", "opencode.json");
11192
11472
  hint = "OpenCode will load the enabled 'zam' MCP server on next launch.";
11193
11473
  let existing = {};
11194
11474
  if (exists(targetPath)) {
@@ -11216,7 +11496,7 @@ function connectHarnessMcp(harnessId, opts) {
11216
11496
  existing.mcp = servers;
11217
11497
  content = JSON.stringify(existing, null, 2);
11218
11498
  } else if (harnessId === "codex") {
11219
- targetPath = join15(opts.home, ".codex", "config.toml");
11499
+ targetPath = join16(opts.home, ".codex", "config.toml");
11220
11500
  hint = "Codex will prompt for tool execution approvals or respect the TOML approval modes.";
11221
11501
  let existingStr = "";
11222
11502
  if (exists(targetPath)) {
@@ -11243,14 +11523,14 @@ ${block}` : block;
11243
11523
  }
11244
11524
  } else if (harnessId === "vscode") {
11245
11525
  const platform = opts.platform ?? process.platform;
11246
- targetPath = platform === "win32" ? join15(opts.home, "AppData", "Roaming", "Code", "User", "mcp.json") : platform === "darwin" ? join15(
11526
+ targetPath = platform === "win32" ? join16(opts.home, "AppData", "Roaming", "Code", "User", "mcp.json") : platform === "darwin" ? join16(
11247
11527
  opts.home,
11248
11528
  "Library",
11249
11529
  "Application Support",
11250
11530
  "Code",
11251
11531
  "User",
11252
11532
  "mcp.json"
11253
- ) : join15(opts.home, ".config", "Code", "User", "mcp.json");
11533
+ ) : join16(opts.home, ".config", "Code", "User", "mcp.json");
11254
11534
  hint = "Reload VS Code after setup. ZAM Companion stays separate from the Codex chat and can be moved to any panel or sidebar.";
11255
11535
  let existing = {};
11256
11536
  if (exists(targetPath)) {
@@ -11272,7 +11552,7 @@ ${block}` : block;
11272
11552
  if (!existing.inputs) existing.inputs = [];
11273
11553
  content = JSON.stringify(existing, null, 2);
11274
11554
  } else if (harnessId === "goose") {
11275
- targetPath = join15(opts.home, ".config", "goose", "config.yaml");
11555
+ targetPath = join16(opts.home, ".config", "goose", "config.yaml");
11276
11556
  hint = "goose will load the 'zam' extension on next session start. Run 'goose configure' to manage extensions.";
11277
11557
  const isJs = opts.zamPath.endsWith(".js");
11278
11558
  const cmdStr = isJs ? process.execPath : opts.zamPath;
@@ -11311,8 +11591,8 @@ ${zamExtension}
11311
11591
  `;
11312
11592
  }
11313
11593
  } else if (harnessId === "copilot") {
11314
- targetPath = join15(
11315
- opts.copilotHome ?? join15(opts.home, ".copilot"),
11594
+ targetPath = join16(
11595
+ opts.copilotHome ?? join16(opts.home, ".copilot"),
11316
11596
  "mcp-config.json"
11317
11597
  );
11318
11598
  hint = "Restart GitHub Copilot or start a new session to load the 'zam' MCP server and its focused Recall, Graph, and Settings canvases.";
@@ -11354,15 +11634,15 @@ ${zamExtension}
11354
11634
 
11355
11635
  // src/cli/copilot-extension.ts
11356
11636
  import {
11357
- existsSync as existsSync15,
11637
+ existsSync as existsSync16,
11358
11638
  lstatSync,
11359
- mkdirSync as mkdirSync10,
11360
- readFileSync as readFileSync12,
11361
- writeFileSync as writeFileSync8
11639
+ mkdirSync as mkdirSync11,
11640
+ readFileSync as readFileSync13,
11641
+ writeFileSync as writeFileSync9
11362
11642
  } from "fs";
11363
11643
  import { homedir as homedir10 } from "os";
11364
- import { dirname as dirname6, join as join16, resolve as resolve4 } from "path";
11365
- import { fileURLToPath as fileURLToPath3 } from "url";
11644
+ import { dirname as dirname7, join as join17, resolve as resolve5 } from "path";
11645
+ import { fileURLToPath as fileURLToPath4 } from "url";
11366
11646
  var EXTENSION_NAME = "zam-mcp-apps";
11367
11647
  var EXTENSION_FILES = [
11368
11648
  "extension.mjs",
@@ -11371,18 +11651,18 @@ var EXTENSION_FILES = [
11371
11651
  "manifest.json"
11372
11652
  ];
11373
11653
  var packageRoot = [
11374
- fileURLToPath3(new URL("../..", import.meta.url)),
11375
- fileURLToPath3(new URL("../../..", import.meta.url))
11376
- ].find((candidate) => existsSync15(join16(candidate, "package.json"))) ?? fileURLToPath3(new URL("../..", import.meta.url));
11654
+ fileURLToPath4(new URL("../..", import.meta.url)),
11655
+ fileURLToPath4(new URL("../../..", import.meta.url))
11656
+ ].find((candidate) => existsSync16(join17(candidate, "package.json"))) ?? fileURLToPath4(new URL("../..", import.meta.url));
11377
11657
  function defaultCliEntry() {
11378
- return join16(dirname6(fileURLToPath3(import.meta.url)), "index.js");
11658
+ return join17(dirname7(fileURLToPath4(import.meta.url)), "index.js");
11379
11659
  }
11380
11660
  function resolveCopilotHome(home = homedir10(), configuredHome = process.env.COPILOT_HOME) {
11381
- return configuredHome?.trim() ? resolve4(configuredHome) : join16(home, ".copilot");
11661
+ return configuredHome?.trim() ? resolve5(configuredHome) : join17(home, ".copilot");
11382
11662
  }
11383
11663
  function resolveCopilotZamLaunch(zamPath, options = {}) {
11384
11664
  const cliEntry = options.cliEntry ?? defaultCliEntry();
11385
- if (existsSync15(cliEntry)) {
11665
+ if (existsSync16(cliEntry)) {
11386
11666
  return {
11387
11667
  command: options.nodePath ?? process.execPath,
11388
11668
  args: [cliEntry, "mcp"]
@@ -11394,20 +11674,20 @@ function resolveCopilotZamLaunch(zamPath, options = {}) {
11394
11674
  };
11395
11675
  }
11396
11676
  function planCopilotExtensionInstall(options) {
11397
- const sourceDir = options.assetsDir ?? join16(packageRoot, "dist", "copilot-extension");
11677
+ const sourceDir = options.assetsDir ?? join17(packageRoot, "dist", "copilot-extension");
11398
11678
  for (const file of EXTENSION_FILES) {
11399
- if (!existsSync15(join16(sourceDir, file))) {
11679
+ if (!existsSync16(join17(sourceDir, file))) {
11400
11680
  throw new Error(
11401
- `Copilot MCP Apps asset is missing: ${join16(sourceDir, file)}. Run \`npm run build\` and retry.`
11681
+ `Copilot MCP Apps asset is missing: ${join17(sourceDir, file)}. Run \`npm run build\` and retry.`
11402
11682
  );
11403
11683
  }
11404
11684
  }
11405
11685
  const manifest = JSON.parse(
11406
- readFileSync12(join16(sourceDir, "manifest.json"), "utf8")
11686
+ readFileSync13(join17(sourceDir, "manifest.json"), "utf8")
11407
11687
  );
11408
11688
  if (manifest.name !== EXTENSION_NAME || typeof manifest.version !== "string" || !manifest.version) {
11409
11689
  throw new Error(
11410
- `Invalid Copilot MCP Apps manifest: ${join16(sourceDir, "manifest.json")}`
11690
+ `Invalid Copilot MCP Apps manifest: ${join17(sourceDir, "manifest.json")}`
11411
11691
  );
11412
11692
  }
11413
11693
  const copilotHome = resolveCopilotHome(
@@ -11416,15 +11696,15 @@ function planCopilotExtensionInstall(options) {
11416
11696
  );
11417
11697
  return {
11418
11698
  sourceDir,
11419
- destinationDir: join16(copilotHome, "extensions", EXTENSION_NAME),
11699
+ destinationDir: join17(copilotHome, "extensions", EXTENSION_NAME),
11420
11700
  launch: resolveCopilotZamLaunch(options.zamPath, options),
11421
11701
  files: EXTENSION_FILES
11422
11702
  };
11423
11703
  }
11424
11704
  function writeIfChanged(path, content) {
11425
11705
  const next = Buffer.isBuffer(content) ? content : Buffer.from(content);
11426
- if (existsSync15(path) && readFileSync12(path).equals(next)) return false;
11427
- writeFileSync8(path, next);
11706
+ if (existsSync16(path) && readFileSync13(path).equals(next)) return false;
11707
+ writeFileSync9(path, next);
11428
11708
  return true;
11429
11709
  }
11430
11710
  function installCopilotExtension(options) {
@@ -11432,18 +11712,18 @@ function installCopilotExtension(options) {
11432
11712
  if (options.dryRun) {
11433
11713
  return { ...plan, action: "planned", changedFiles: [] };
11434
11714
  }
11435
- const destinationExisted = existsSync15(plan.destinationDir);
11715
+ const destinationExisted = existsSync16(plan.destinationDir);
11436
11716
  if (destinationExisted && lstatSync(plan.destinationDir).isSymbolicLink()) {
11437
11717
  throw new Error(
11438
11718
  `Refusing to replace symlinked Copilot extension directory: ${plan.destinationDir}`
11439
11719
  );
11440
11720
  }
11441
- mkdirSync10(plan.destinationDir, { recursive: true });
11721
+ mkdirSync11(plan.destinationDir, { recursive: true });
11442
11722
  const changedFiles = [];
11443
11723
  for (const file of plan.files) {
11444
- const source = join16(plan.sourceDir, file);
11445
- const destination = join16(plan.destinationDir, file);
11446
- if (writeIfChanged(destination, readFileSync12(source))) {
11724
+ const source = join17(plan.sourceDir, file);
11725
+ const destination = join17(plan.destinationDir, file);
11726
+ if (writeIfChanged(destination, readFileSync13(source))) {
11447
11727
  changedFiles.push(file);
11448
11728
  }
11449
11729
  }
@@ -11457,7 +11737,7 @@ function installCopilotExtension(options) {
11457
11737
  2
11458
11738
  )}
11459
11739
  `;
11460
- if (writeIfChanged(join16(plan.destinationDir, "launch.json"), launchContent)) {
11740
+ if (writeIfChanged(join17(plan.destinationDir, "launch.json"), launchContent)) {
11461
11741
  changedFiles.push("launch.json");
11462
11742
  }
11463
11743
  return {
@@ -11469,24 +11749,24 @@ function installCopilotExtension(options) {
11469
11749
 
11470
11750
  // src/cli/vscode-extension.ts
11471
11751
  import { execFileSync as execFileSync3 } from "child_process";
11472
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
11752
+ import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
11473
11753
  import { homedir as homedir11 } from "os";
11474
- import { dirname as dirname7, join as join17 } from "path";
11475
- import { fileURLToPath as fileURLToPath4 } from "url";
11754
+ import { dirname as dirname8, join as join18 } from "path";
11755
+ import { fileURLToPath as fileURLToPath5 } from "url";
11476
11756
  var packageRoot2 = [
11477
- fileURLToPath4(new URL("../..", import.meta.url)),
11478
- fileURLToPath4(new URL("../../..", import.meta.url))
11479
- ].find((candidate) => existsSync16(join17(candidate, "package.json"))) ?? fileURLToPath4(new URL("../..", import.meta.url));
11757
+ fileURLToPath5(new URL("../..", import.meta.url)),
11758
+ fileURLToPath5(new URL("../../..", import.meta.url))
11759
+ ].find((candidate) => existsSync17(join18(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
11480
11760
  function resolveVscodeExecutable(options = {}) {
11481
11761
  const home = options.home ?? homedir11();
11482
11762
  const platform = options.platform ?? process.platform;
11483
11763
  const find = options.find ?? findExecutable;
11484
- const exists = options.exists ?? existsSync16;
11764
+ const exists = options.exists ?? existsSync17;
11485
11765
  const found = find("code");
11486
11766
  if (found) return found;
11487
11767
  const candidates = platform === "darwin" ? [
11488
11768
  "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
11489
- join17(
11769
+ join18(
11490
11770
  home,
11491
11771
  "Applications",
11492
11772
  "Visual Studio Code.app",
@@ -11497,7 +11777,7 @@ function resolveVscodeExecutable(options = {}) {
11497
11777
  "code"
11498
11778
  )
11499
11779
  ] : platform === "win32" ? [
11500
- join17(
11780
+ join18(
11501
11781
  home,
11502
11782
  "AppData",
11503
11783
  "Local",
@@ -11517,7 +11797,7 @@ function buildVscodeCliInvocation(command, args, platform = process.platform) {
11517
11797
  }
11518
11798
  function packageVersion() {
11519
11799
  const parsed = JSON.parse(
11520
- readFileSync13(join17(packageRoot2, "package.json"), "utf8")
11800
+ readFileSync14(join18(packageRoot2, "package.json"), "utf8")
11521
11801
  );
11522
11802
  if (typeof parsed.version !== "string" || !parsed.version) {
11523
11803
  throw new Error("Cannot determine the ZAM package version");
@@ -11527,9 +11807,9 @@ function packageVersion() {
11527
11807
  function planVscodeExtensionInstall(options) {
11528
11808
  const home = options.home ?? homedir11();
11529
11809
  const version = options.version ?? packageVersion();
11530
- const assetsDir = options.assetsDir ?? join17(packageRoot2, "dist", "vscode-extension");
11531
- const vsixPath = join17(assetsDir, `ZAM_Companion_${version}.vsix`);
11532
- if (!existsSync16(vsixPath)) {
11810
+ const assetsDir = options.assetsDir ?? join18(packageRoot2, "dist", "vscode-extension");
11811
+ const vsixPath = join18(assetsDir, `ZAM_Companion_${version}.vsix`);
11812
+ if (!existsSync17(vsixPath)) {
11533
11813
  throw new Error(
11534
11814
  `ZAM Companion VSIX asset is missing: ${vsixPath}. Run \`npm run build\` and retry.`
11535
11815
  );
@@ -11549,7 +11829,7 @@ function planVscodeExtensionInstall(options) {
11549
11829
  version,
11550
11830
  vsixPath,
11551
11831
  codePath,
11552
- launchConfigPath: join17(home, ".zam", "vscode-launch.json"),
11832
+ launchConfigPath: join18(home, ".zam", "vscode-launch.json"),
11553
11833
  launch: { command: options.zamPath, args: ["mcp"] }
11554
11834
  };
11555
11835
  }
@@ -11585,8 +11865,8 @@ function installVscodeExtension(options) {
11585
11865
  2
11586
11866
  )}
11587
11867
  `;
11588
- const existed = existsSync16(plan.launchConfigPath);
11589
- const changed = !existed || readFileSync13(plan.launchConfigPath, "utf8") !== launchContent;
11868
+ const existed = existsSync17(plan.launchConfigPath);
11869
+ const changed = !existed || readFileSync14(plan.launchConfigPath, "utf8") !== launchContent;
11590
11870
  const run = options.run ?? ((command, args) => {
11591
11871
  const invocation = buildVscodeCliInvocation(command, args);
11592
11872
  execFileSync3(invocation.command, invocation.args, {
@@ -11608,8 +11888,8 @@ function installVscodeExtension(options) {
11608
11888
  }
11609
11889
  run(plan.codePath, ["--install-extension", plan.vsixPath, "--force"]);
11610
11890
  if (changed) {
11611
- mkdirSync11(dirname7(plan.launchConfigPath), { recursive: true });
11612
- writeFileSync9(plan.launchConfigPath, launchContent, "utf8");
11891
+ mkdirSync12(dirname8(plan.launchConfigPath), { recursive: true });
11892
+ writeFileSync10(plan.launchConfigPath, launchContent, "utf8");
11613
11893
  }
11614
11894
  return {
11615
11895
  ...plan,
@@ -11669,8 +11949,8 @@ function resolveDeps(deps) {
11669
11949
  detect: deps.detect ?? (() => detectInstalledConnectHarnesses({ home, copilotHome })),
11670
11950
  connectMcp: deps.connectMcp ?? connectHarnessMcp,
11671
11951
  writeConfig: deps.writeConfig ?? ((path, content) => {
11672
- mkdirSync12(dirname8(path), { recursive: true });
11673
- writeFileSync10(path, content, "utf-8");
11952
+ mkdirSync13(dirname9(path), { recursive: true });
11953
+ writeFileSync11(path, content, "utf-8");
11674
11954
  }),
11675
11955
  installCopilot: deps.installCopilot ?? installCopilotExtension,
11676
11956
  installVscode: deps.installVscode ?? installVscodeExtension,
@@ -11816,9 +12096,9 @@ function inspectConnectHarnesses(deps = {}) {
11816
12096
  init_kernel();
11817
12097
  import { execFileSync as execFileSync5 } from "child_process";
11818
12098
  import { randomBytes as randomBytes2 } from "crypto";
11819
- import { existsSync as existsSync20, readdirSync as readdirSync2, readFileSync as readFileSync17, rmSync as rmSync3 } from "fs";
12099
+ import { existsSync as existsSync21, readdirSync as readdirSync3, readFileSync as readFileSync18, rmSync as rmSync3 } from "fs";
11820
12100
  import { homedir as homedir14, tmpdir as tmpdir3 } from "os";
11821
- import { join as join22, resolve as resolve6 } from "path";
12101
+ import { join as join23, resolve as resolve7 } from "path";
11822
12102
  import { Command } from "commander";
11823
12103
  import { ulid as ulid10 } from "ulid";
11824
12104
 
@@ -11898,7 +12178,7 @@ async function readWebLink(url) {
11898
12178
  redirect: "manual",
11899
12179
  signal: controller.signal,
11900
12180
  headers: {
11901
- "User-Agent": "ZAM-Content-Studio/0.12.0"
12181
+ "User-Agent": "ZAM-Content-Studio/0.14.0"
11902
12182
  }
11903
12183
  });
11904
12184
  if (res.status >= 300 && res.status < 400) {
@@ -11965,16 +12245,16 @@ async function readImageOCR(db, imagePath) {
11965
12245
  import { execFileSync as execFileSync4 } from "child_process";
11966
12246
  import {
11967
12247
  chmodSync as chmodSync2,
11968
- existsSync as existsSync17,
11969
- mkdirSync as mkdirSync13,
11970
- readFileSync as readFileSync14,
11971
- writeFileSync as writeFileSync11
12248
+ existsSync as existsSync18,
12249
+ mkdirSync as mkdirSync14,
12250
+ readFileSync as readFileSync15,
12251
+ writeFileSync as writeFileSync12
11972
12252
  } from "fs";
11973
12253
  import { homedir as homedir13 } from "os";
11974
- import { delimiter as delimiter2, join as join18, resolve as resolve5 } from "path";
12254
+ import { delimiter as delimiter2, join as join19, resolve as resolve6 } from "path";
11975
12255
  var BUILT_CLI_PATTERN = /[\\/]dist[\\/]cli[\\/]index\.js$/;
11976
12256
  function normalizeForCompare(path, platform) {
11977
- const resolved = resolve5(path);
12257
+ const resolved = resolve6(path);
11978
12258
  return platform === "win32" ? resolved.toLowerCase() : resolved;
11979
12259
  }
11980
12260
  function windowsShimContent(nodePath, cliPath) {
@@ -12014,14 +12294,14 @@ function ensureWindowsUserPath(binDir) {
12014
12294
  return output.endsWith("updated");
12015
12295
  }
12016
12296
  function ensureUnixUserPath(home, platform) {
12017
- const profile = join18(home, platform === "darwin" ? ".zprofile" : ".profile");
12018
- const existing = existsSync17(profile) ? readFileSync14(profile, "utf8") : "";
12297
+ const profile = join19(home, platform === "darwin" ? ".zprofile" : ".profile");
12298
+ const existing = existsSync18(profile) ? readFileSync15(profile, "utf8") : "";
12019
12299
  if (existing.includes(".zam/bin")) return false;
12020
12300
  const block = `
12021
12301
  # Added by ZAM: keep the zam CLI on PATH
12022
12302
  export PATH="$HOME/.zam/bin:$PATH"
12023
12303
  `;
12024
- writeFileSync11(profile, existing + block, "utf8");
12304
+ writeFileSync12(profile, existing + block, "utf8");
12025
12305
  return true;
12026
12306
  }
12027
12307
  function installCliShim(options = {}) {
@@ -12029,10 +12309,10 @@ function installCliShim(options = {}) {
12029
12309
  const platform = options.platform ?? process.platform;
12030
12310
  const env = options.env ?? process.env;
12031
12311
  const find = options.find ?? findExecutable;
12032
- const nodePath = resolve5(options.nodePath ?? process.execPath);
12033
- const cliPath = resolve5(options.cliPath ?? process.argv[1] ?? "");
12034
- const binDir = join18(home, ".zam", "bin");
12035
- const shimPath = join18(binDir, platform === "win32" ? "zam.cmd" : "zam");
12312
+ const nodePath = resolve6(options.nodePath ?? process.execPath);
12313
+ const cliPath = resolve6(options.cliPath ?? process.argv[1] ?? "");
12314
+ const binDir = join19(home, ".zam", "bin");
12315
+ const shimPath = join19(binDir, platform === "win32" ? "zam.cmd" : "zam");
12036
12316
  const report = {
12037
12317
  status: "ok",
12038
12318
  binDir,
@@ -12043,7 +12323,7 @@ function installCliShim(options = {}) {
12043
12323
  pathUpdated: false,
12044
12324
  needsNewTerminal: false
12045
12325
  };
12046
- if (!BUILT_CLI_PATTERN.test(cliPath) || !existsSync17(cliPath)) {
12326
+ if (!BUILT_CLI_PATTERN.test(cliPath) || !existsSync18(cliPath)) {
12047
12327
  report.status = "skipped";
12048
12328
  report.detail = `Not running from a built CLI entry (${cliPath}); nothing to link.`;
12049
12329
  return report;
@@ -12057,11 +12337,11 @@ function installCliShim(options = {}) {
12057
12337
  return report;
12058
12338
  }
12059
12339
  const content = platform === "win32" ? windowsShimContent(nodePath, cliPath) : unixShimContent(nodePath, cliPath);
12060
- const existed = existsSync17(shimPath);
12061
- const changed = !existed || readFileSync14(shimPath, "utf8") !== content;
12340
+ const existed = existsSync18(shimPath);
12341
+ const changed = !existed || readFileSync15(shimPath, "utf8") !== content;
12062
12342
  if (changed) {
12063
- mkdirSync13(binDir, { recursive: true });
12064
- writeFileSync11(shimPath, content, "utf8");
12343
+ mkdirSync14(binDir, { recursive: true });
12344
+ writeFileSync12(shimPath, content, "utf8");
12065
12345
  if (platform !== "win32") chmodSync2(shimPath, 493);
12066
12346
  }
12067
12347
  report.status = !existed ? "installed" : changed ? "refreshed" : "ok";
@@ -28770,40 +29050,40 @@ function getCurriculumProvider(id) {
28770
29050
 
28771
29051
  // src/cli/install-repair.ts
28772
29052
  init_kernel();
28773
- import { existsSync as existsSync19 } from "fs";
29053
+ import { existsSync as existsSync20 } from "fs";
28774
29054
 
28775
29055
  // src/cli/provisioning/index.ts
28776
29056
  import {
28777
- existsSync as existsSync18,
29057
+ existsSync as existsSync19,
28778
29058
  lstatSync as lstatSync2,
28779
- mkdirSync as mkdirSync14,
28780
- readFileSync as readFileSync15,
28781
- realpathSync,
29059
+ mkdirSync as mkdirSync15,
29060
+ readFileSync as readFileSync16,
29061
+ realpathSync as realpathSync2,
28782
29062
  rmSync as rmSync2,
28783
29063
  symlinkSync,
28784
- writeFileSync as writeFileSync12
29064
+ writeFileSync as writeFileSync13
28785
29065
  } from "fs";
28786
- import { basename as basename4, dirname as dirname9, join as join19 } from "path";
28787
- import { fileURLToPath as fileURLToPath5 } from "url";
29066
+ import { basename as basename4, dirname as dirname10, join as join20 } from "path";
29067
+ import { fileURLToPath as fileURLToPath6 } from "url";
28788
29068
  var packageRoot3 = [
28789
- fileURLToPath5(new URL("../..", import.meta.url)),
28790
- fileURLToPath5(new URL("../../..", import.meta.url))
28791
- ].find((candidate) => existsSync18(join19(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
29069
+ fileURLToPath6(new URL("../..", import.meta.url)),
29070
+ fileURLToPath6(new URL("../../..", import.meta.url))
29071
+ ].find((candidate) => existsSync19(join20(candidate, "package.json"))) ?? fileURLToPath6(new URL("../..", import.meta.url));
28792
29072
  var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
28793
29073
  var SKILL_PAIRS = [
28794
29074
  {
28795
- from: join19(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
28796
- to: join19(".claude", "skills", "zam", "SKILL.md"),
29075
+ from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
29076
+ to: join20(".claude", "skills", "zam", "SKILL.md"),
28797
29077
  agents: ["claude", "copilot"]
28798
29078
  },
28799
29079
  {
28800
- from: join19(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
28801
- to: join19(".agent", "skills", "zam", "SKILL.md"),
29080
+ from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
29081
+ to: join20(".agent", "skills", "zam", "SKILL.md"),
28802
29082
  agents: ["agent"]
28803
29083
  },
28804
29084
  {
28805
- from: join19(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
28806
- to: join19(".agents", "skills", "zam", "SKILL.md"),
29085
+ from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
29086
+ to: join20(".agents", "skills", "zam", "SKILL.md"),
28807
29087
  agents: ["codex"]
28808
29088
  }
28809
29089
  ];
@@ -28847,7 +29127,7 @@ function isSymbolicLink(path) {
28847
29127
  }
28848
29128
  }
28849
29129
  function comparableRealPath(path) {
28850
- const real = realpathSync(path);
29130
+ const real = realpathSync2(path);
28851
29131
  return process.platform === "win32" ? real.toLowerCase() : real;
28852
29132
  }
28853
29133
  function pathsResolveToSameDirectory(sourceDir, destinationDir) {
@@ -28863,15 +29143,15 @@ function linkPointsTo(sourceDir, destinationDir) {
28863
29143
  function isZamSkillCopy(destinationDir) {
28864
29144
  try {
28865
29145
  if (!lstatSync2(destinationDir).isDirectory()) return false;
28866
- const skillFile = join19(destinationDir, "SKILL.md");
28867
- if (!existsSync18(skillFile)) return false;
28868
- return /^name:\s*zam\s*$/m.test(readFileSync15(skillFile, "utf8"));
29146
+ const skillFile = join20(destinationDir, "SKILL.md");
29147
+ if (!existsSync19(skillFile)) return false;
29148
+ return /^name:\s*zam\s*$/m.test(readFileSync16(skillFile, "utf8"));
28869
29149
  } catch {
28870
29150
  return false;
28871
29151
  }
28872
29152
  }
28873
29153
  function classifySkillDestination(sourceDir, destinationDir) {
28874
- if (!existsSync18(sourceDir)) return "source-missing";
29154
+ if (!existsSync19(sourceDir)) return "source-missing";
28875
29155
  if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
28876
29156
  return "source-directory";
28877
29157
  }
@@ -28888,9 +29168,9 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
28888
29168
  };
28889
29169
  for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
28890
29170
  if (!pairAgents.some((agent) => agents.has(agent))) continue;
28891
- const sourceDir = dirname9(from);
28892
- const destinationDir = dirname9(join19(cwd, to));
28893
- const label = dirname9(to);
29171
+ const sourceDir = dirname10(from);
29172
+ const destinationDir = dirname10(join20(cwd, to));
29173
+ const label = dirname10(to);
28894
29174
  const state = classifySkillDestination(sourceDir, destinationDir);
28895
29175
  if (state === "source-missing") {
28896
29176
  if (!opts.quiet) {
@@ -28947,7 +29227,7 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
28947
29227
  if (destinationExists) {
28948
29228
  rmSync2(destinationDir, { recursive: true, force: true });
28949
29229
  }
28950
- mkdirSync14(dirname9(destinationDir), { recursive: true });
29230
+ mkdirSync15(dirname10(destinationDir), { recursive: true });
28951
29231
  symlinkSync(
28952
29232
  sourceDir,
28953
29233
  destinationDir,
@@ -28963,8 +29243,8 @@ function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
28963
29243
  const results = [];
28964
29244
  for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
28965
29245
  if (!pairAgents.some((agent) => agents.has(agent))) continue;
28966
- const sourceDir = dirname9(from);
28967
- const destinationDir = dirname9(join19(cwd, to));
29246
+ const sourceDir = dirname10(from);
29247
+ const destinationDir = dirname10(join20(cwd, to));
28968
29248
  results.push({
28969
29249
  agents: pairAgents,
28970
29250
  source: sourceDir,
@@ -28995,7 +29275,7 @@ function defaultRepairWorkspaces() {
28995
29275
  try {
28996
29276
  const agents = parseSetupAgents();
28997
29277
  for (const workspace of getConfiguredWorkspaces()) {
28998
- if (!existsSync19(workspace.path)) {
29278
+ if (!existsSync20(workspace.path)) {
28999
29279
  summary.missing += 1;
29000
29280
  continue;
29001
29281
  }
@@ -29188,9 +29468,9 @@ init_client();
29188
29468
  init_kernel();
29189
29469
  init_client();
29190
29470
  import { randomBytes } from "crypto";
29191
- import { readFileSync as readFileSync16 } from "fs";
29471
+ import { readFileSync as readFileSync17 } from "fs";
29192
29472
  import { tmpdir as tmpdir2 } from "os";
29193
- import { basename as basename5, join as join20 } from "path";
29473
+ import { basename as basename5, join as join21 } from "path";
29194
29474
  var LANGUAGE_NAMES2 = {
29195
29475
  en: "English",
29196
29476
  de: "German",
@@ -29228,7 +29508,7 @@ async function observeUiSnapshotViaLLM(db, input) {
29228
29508
  if (isVideo) {
29229
29509
  const { mkdirSync: mkdirSync17, readdirSync: readdirSync4, rmSync: rmSync4 } = await import("fs");
29230
29510
  const { execSync: execSync5 } = await import("child_process");
29231
- const tempDir = join20(
29511
+ const tempDir = join21(
29232
29512
  tmpdir2(),
29233
29513
  `zam-frames-${randomBytes(4).toString("hex")}`
29234
29514
  );
@@ -29261,7 +29541,7 @@ async function observeUiSnapshotViaLLM(db, input) {
29261
29541
  }
29262
29542
  }
29263
29543
  for (const file of sampledFiles) {
29264
- const bytes = readFileSync16(join20(tempDir, file));
29544
+ const bytes = readFileSync17(join21(tempDir, file));
29265
29545
  imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
29266
29546
  }
29267
29547
  } finally {
@@ -29271,7 +29551,7 @@ async function observeUiSnapshotViaLLM(db, input) {
29271
29551
  }
29272
29552
  }
29273
29553
  } else {
29274
- const imageBytes = readFileSync16(input.imagePath);
29554
+ const imageBytes = readFileSync17(input.imagePath);
29275
29555
  const ext = input.imagePath.split(".").pop()?.toLowerCase();
29276
29556
  const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
29277
29557
  imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
@@ -29780,13 +30060,13 @@ async function resolveUser(opts, db, resolveOpts) {
29780
30060
  }
29781
30061
 
29782
30062
  // src/cli/workspaces/backup.ts
29783
- import { mkdirSync as mkdirSync15 } from "fs";
29784
- import { join as join21 } from "path";
30063
+ import { mkdirSync as mkdirSync16 } from "fs";
30064
+ import { join as join22 } from "path";
29785
30065
  async function backupDatabaseTo(db, targetDir) {
29786
- const backupDir = join21(targetDir, "zam-backups");
29787
- mkdirSync15(backupDir, { recursive: true });
30066
+ const backupDir = join22(targetDir, "zam-backups");
30067
+ mkdirSync16(backupDir, { recursive: true });
29788
30068
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
29789
- const dest = join21(backupDir, `zam-${stamp}.db`);
30069
+ const dest = join22(backupDir, `zam-${stamp}.db`);
29790
30070
  await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
29791
30071
  return dest;
29792
30072
  }
@@ -29920,20 +30200,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
29920
30200
  activeWorkspace,
29921
30201
  workspaceDir: activeWorkspace.path,
29922
30202
  defaultWorkspaceDir: defaultWorkspaceDir(),
29923
- dataDir: join22(homedir14(), ".zam")
30203
+ dataDir: join23(homedir14(), ".zam")
29924
30204
  });
29925
30205
  });
29926
30206
  });
29927
30207
  function provisionConfiguredWorkspaces() {
29928
30208
  return getConfiguredWorkspaces().flatMap(
29929
- (workspace) => existsSync20(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
30209
+ (workspace) => existsSync21(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
29930
30210
  );
29931
30211
  }
29932
30212
  function buildWorkspaceLinkHealth(workspaces) {
29933
30213
  const agents = parseSetupAgents();
29934
30214
  const map = {};
29935
30215
  for (const workspace of workspaces) {
29936
- if (!existsSync20(workspace.path)) continue;
30216
+ if (!existsSync21(workspace.path)) continue;
29937
30217
  const links = inspectSkillLinks(workspace.path, agents);
29938
30218
  map[workspace.id] = {
29939
30219
  health: summarizeSkillLinkHealth(links),
@@ -29963,7 +30243,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
29963
30243
  activeWorkspace,
29964
30244
  workspaceDir: activeWorkspace.path,
29965
30245
  defaultWorkspaceDir: defaultWorkspaceDir(),
29966
- dataDir: join22(homedir14(), ".zam"),
30246
+ dataDir: join23(homedir14(), ".zam"),
29967
30247
  linkHealth: buildWorkspaceLinkHealth(workspaces)
29968
30248
  });
29969
30249
  });
@@ -29978,7 +30258,7 @@ bridgeCommand.command("workspace-repair-links").description(
29978
30258
  if (!id) jsonError("A non-empty --id is required");
29979
30259
  const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
29980
30260
  if (!workspace) jsonError(`Workspace "${id}" is not configured`);
29981
- if (!existsSync20(workspace.path)) {
30261
+ if (!existsSync21(workspace.path)) {
29982
30262
  jsonError(`Workspace path does not exist: ${workspace.path}`);
29983
30263
  }
29984
30264
  const agents = parseSetupAgents(opts.agents);
@@ -30009,8 +30289,8 @@ function parseBridgeWorkspaceKind(value) {
30009
30289
  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) => {
30010
30290
  const raw = String(opts.path ?? "").trim();
30011
30291
  if (!raw) jsonError("A non-empty --path is required");
30012
- const path = resolve6(raw);
30013
- if (!existsSync20(path)) jsonError(`Workspace path does not exist: ${path}`);
30292
+ const path = resolve7(raw);
30293
+ if (!existsSync21(path)) jsonError(`Workspace path does not exist: ${path}`);
30014
30294
  const id = opts.id ? String(opts.id).trim() : void 0;
30015
30295
  if (opts.id && !id) jsonError("A non-empty --id is required");
30016
30296
  const kind = parseBridgeWorkspaceKind(opts.kind);
@@ -30054,8 +30334,8 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
30054
30334
  bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
30055
30335
  const raw = String(opts.dir ?? "").trim();
30056
30336
  if (!raw) jsonError("A non-empty --dir is required");
30057
- const dir = resolve6(raw);
30058
- if (!existsSync20(dir)) jsonError(`Workspace path does not exist: ${dir}`);
30337
+ const dir = resolve7(raw);
30338
+ if (!existsSync21(dir)) jsonError(`Workspace path does not exist: ${dir}`);
30059
30339
  const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
30060
30340
  await withOptionalDb2(async (db) => {
30061
30341
  const workspace = await activateWorkspacePath(db, dir);
@@ -30118,7 +30398,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
30118
30398
  jsonError(`Workspace is not configured: ${opts.workspace}`);
30119
30399
  }
30120
30400
  const activeWorkspace = await ensureActiveWorkspace(db);
30121
- const workspace = opts.dir ? existsSync20(opts.dir) ? opts.dir : homedir14() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
30401
+ const workspace = opts.dir ? existsSync21(opts.dir) ? opts.dir : homedir14() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
30122
30402
  launchHarness(harness, {
30123
30403
  executable,
30124
30404
  workspace,
@@ -30394,6 +30674,52 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
30394
30674
  jsonError(err.message);
30395
30675
  }
30396
30676
  });
30677
+ bridgeCommand.command("okf-import").description(
30678
+ "Record an agent's decomposition of an OKF article as learning tokens (JSON stdin, ADR 2026-07-18)"
30679
+ ).option("--user <id>", "User ID (default: whoami)").action(async (opts) => {
30680
+ try {
30681
+ let raw;
30682
+ if (isServeMode) {
30683
+ raw = serveStdinPayload ?? "";
30684
+ } else {
30685
+ const chunks = [];
30686
+ for await (const chunk of process.stdin) {
30687
+ chunks.push(chunk);
30688
+ }
30689
+ raw = Buffer.concat(chunks).toString("utf-8").trim();
30690
+ }
30691
+ if (!raw) {
30692
+ jsonError(
30693
+ "No input received on stdin. Pipe JSON: { file, bundle_dir?, tokens: [...] }"
30694
+ );
30695
+ }
30696
+ let data;
30697
+ try {
30698
+ data = JSON.parse(raw);
30699
+ } catch {
30700
+ jsonError("Invalid JSON input");
30701
+ }
30702
+ if (!data?.file || !Array.isArray(data?.tokens)) {
30703
+ jsonError("JSON must include 'file' and a 'tokens' array");
30704
+ }
30705
+ await withDb2(async (db) => {
30706
+ try {
30707
+ const userId = await resolveUser(opts, db, { json: true });
30708
+ const result = await importOkfTokens(db, {
30709
+ user: userId,
30710
+ bundleDir: data.bundle_dir,
30711
+ file: data.file,
30712
+ tokens: data.tokens
30713
+ });
30714
+ jsonOut(result);
30715
+ } catch (err) {
30716
+ jsonError(err.message);
30717
+ }
30718
+ });
30719
+ } catch (err) {
30720
+ jsonError(err.message);
30721
+ }
30722
+ });
30397
30723
  bridgeCommand.command("relevant-tokens").description("Find tokens relevant to a given context").option("--user <id>", "User ID (default: whoami)").action(async (opts) => {
30398
30724
  try {
30399
30725
  let raw;
@@ -30490,10 +30816,10 @@ bridgeCommand.command("discover-skills").description(
30490
30816
  "20"
30491
30817
  ).action(async (opts) => {
30492
30818
  try {
30493
- const monitorDir = join22(homedir14(), ".zam", "monitor");
30819
+ const monitorDir = join23(homedir14(), ".zam", "monitor");
30494
30820
  let files;
30495
30821
  try {
30496
- files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
30822
+ files = readdirSync3(monitorDir).filter((f) => f.endsWith(".jsonl"));
30497
30823
  } catch {
30498
30824
  jsonOut({ proposals: [], message: "No monitor logs found." });
30499
30825
  return;
@@ -30503,7 +30829,7 @@ bridgeCommand.command("discover-skills").description(
30503
30829
  return;
30504
30830
  }
30505
30831
  const limit = Number(opts.limit);
30506
- const sorted = files.map((f) => ({ name: f, path: join22(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
30832
+ const sorted = files.map((f) => ({ name: f, path: join23(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
30507
30833
  const sessionCommands = /* @__PURE__ */ new Map();
30508
30834
  for (const file of sorted) {
30509
30835
  const sessionId = file.name.replace(".jsonl", "");
@@ -31005,7 +31331,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
31005
31331
  return;
31006
31332
  }
31007
31333
  }
31008
- const outputPath = opts.image ?? opts.output ?? join22(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
31334
+ const outputPath = opts.image ?? opts.output ?? join23(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
31009
31335
  const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
31010
31336
  if (!isProvided) {
31011
31337
  const post = decidePostCapture(policy, {
@@ -31033,7 +31359,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
31033
31359
  return;
31034
31360
  }
31035
31361
  }
31036
- const imageBytes = readFileSync17(outputPath);
31362
+ const imageBytes = readFileSync18(outputPath);
31037
31363
  const base64 = imageBytes.toString("base64");
31038
31364
  jsonOut({
31039
31365
  sessionId: opts.session ?? null,
@@ -31060,11 +31386,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
31060
31386
  return;
31061
31387
  }
31062
31388
  const sessionId = opts.session;
31063
- const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
31389
+ const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
31064
31390
  const defaultExt = platform === "win32" ? ".mkv" : ".mov";
31065
- const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
31066
- const { existsSync: existsSync22, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
31067
- if (existsSync22(statePath)) {
31391
+ const outputPath = opts.output ?? join23(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
31392
+ const { existsSync: existsSync23, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
31393
+ if (existsSync23(statePath)) {
31068
31394
  jsonOut({
31069
31395
  sessionId,
31070
31396
  started: false,
@@ -31072,7 +31398,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
31072
31398
  });
31073
31399
  return;
31074
31400
  }
31075
- const logPath = join22(tmpdir3(), `zam-recording-${sessionId}.log`);
31401
+ const logPath = join23(tmpdir3(), `zam-recording-${sessionId}.log`);
31076
31402
  let logFd;
31077
31403
  try {
31078
31404
  logFd = openSync(logPath, "w");
@@ -31154,9 +31480,9 @@ bridgeCommand.command("stop-recording").description(
31154
31480
  return;
31155
31481
  }
31156
31482
  const sessionId = opts.session;
31157
- const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
31158
- const { existsSync: existsSync22, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
31159
- if (!existsSync22(statePath)) {
31483
+ const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
31484
+ const { existsSync: existsSync23, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
31485
+ if (!existsSync23(statePath)) {
31160
31486
  jsonOut({
31161
31487
  sessionId,
31162
31488
  stopped: false,
@@ -31193,7 +31519,7 @@ bridgeCommand.command("stop-recording").description(
31193
31519
  rmSync4(statePath, { force: true });
31194
31520
  } catch {
31195
31521
  }
31196
- if (!existsSync22(outputPath)) {
31522
+ if (!existsSync23(outputPath)) {
31197
31523
  jsonOut({
31198
31524
  sessionId,
31199
31525
  stopped: false,
@@ -31623,7 +31949,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
31623
31949
  });
31624
31950
  bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
31625
31951
  const profile = getSystemProfile();
31626
- const flmInstalled = hasCommand("flm") || existsSync20("C:\\Program Files\\flm\\flm.exe");
31952
+ const flmInstalled = hasCommand("flm") || existsSync21("C:\\Program Files\\flm\\flm.exe");
31627
31953
  const ollamaInstalled = isOllamaInstalled();
31628
31954
  const runners = [
31629
31955
  { id: "flm", label: "FastFlowLM", installed: flmInstalled },
@@ -33758,13 +34084,13 @@ async function writeCompanionContext(db, request, options = {}) {
33758
34084
  // src/cli/ui-intent.ts
33759
34085
  import { mkdir, readFile, rename, writeFile } from "fs/promises";
33760
34086
  import { homedir as homedir15 } from "os";
33761
- import { dirname as dirname10, join as join23 } from "path";
34087
+ import { dirname as dirname11, join as join24 } from "path";
33762
34088
  import { ulid as ulid11 } from "ulid";
33763
34089
  function getUiIntentPath(home = homedir15()) {
33764
- return join23(home, ".zam", "ui-intent.json");
34090
+ return join24(home, ".zam", "ui-intent.json");
33765
34091
  }
33766
34092
  function getUiHostRegistrationPath(home = homedir15()) {
33767
- return join23(home, ".zam", "vscode-host.json");
34093
+ return join24(home, ".zam", "vscode-host.json");
33768
34094
  }
33769
34095
  function compactStringInput(input) {
33770
34096
  return Object.fromEntries(
@@ -33783,8 +34109,8 @@ async function writeUiIntent(app, input = {}, opts = {}) {
33783
34109
  input: compactStringInput(input),
33784
34110
  createdAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
33785
34111
  };
33786
- const tempPath = join23(dirname10(path), `.ui-intent-${process.pid}-${id}.tmp`);
33787
- await mkdir(dirname10(path), { recursive: true });
34112
+ const tempPath = join24(dirname11(path), `.ui-intent-${process.pid}-${id}.tmp`);
34113
+ await mkdir(dirname11(path), { recursive: true });
33788
34114
  await writeFile(tempPath, `${JSON.stringify(intent, null, 2)}
33789
34115
  `, "utf8");
33790
34116
  await rename(tempPath, path);
@@ -33817,9 +34143,9 @@ async function publishUiIntent(app, input = {}, opts = {}) {
33817
34143
  }
33818
34144
 
33819
34145
  // src/cli/commands/mcp.ts
33820
- var __dirname = dirname11(fileURLToPath6(import.meta.url));
34146
+ var __dirname = dirname12(fileURLToPath7(import.meta.url));
33821
34147
  var pkgPath = join25(__dirname, "..", "..", "package.json");
33822
- if (!existsSync21(pkgPath)) {
34148
+ if (!existsSync22(pkgPath)) {
33823
34149
  pkgPath = join25(__dirname, "..", "..", "..", "package.json");
33824
34150
  }
33825
34151
  var pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
@@ -33827,6 +34153,7 @@ var STUDIO_RESOURCE_URI = "ui://zam/studio";
33827
34153
  var RECALL_RESOURCE_URI = "ui://zam/recall";
33828
34154
  var GRAPH_RESOURCE_URI = "ui://zam/graph";
33829
34155
  var SETTINGS_RESOURCE_URI = "ui://zam/settings";
34156
+ var OKF_RESOURCE_URI = "ui://zam/okf";
33830
34157
  var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
33831
34158
  "list-tokens",
33832
34159
  "personal-card-list",
@@ -33854,7 +34181,7 @@ function loadPanelHtml(fileName, placeholderTitle) {
33854
34181
  join25(__dirname, "..", "..", "..", "dist", "ui", fileName)
33855
34182
  ];
33856
34183
  for (const candidate of candidates) {
33857
- if (existsSync21(candidate)) {
34184
+ if (existsSync22(candidate)) {
33858
34185
  return readFileSync19(candidate, "utf-8");
33859
34186
  }
33860
34187
  }
@@ -34620,28 +34947,60 @@ function createMcpServer(db) {
34620
34947
  }
34621
34948
  )
34622
34949
  );
34623
- const okfBundleDirSchema = z.string().optional().describe("Bundle directory (default docs/okf under the server cwd)");
34950
+ const okfBundleDirSchema = z.string().optional().describe(
34951
+ "Bundle directory (default: docs/okf under the client's workspace root, falling back to the server cwd)"
34952
+ );
34953
+ async function resolveOkfBundleDir(explicit) {
34954
+ const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveBundleDirFromRoots: resolveBundleDirFromRoots2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34955
+ if (explicit) return explicit;
34956
+ try {
34957
+ if (server.server.getClientCapabilities()?.roots) {
34958
+ const { roots } = await server.server.listRoots();
34959
+ return resolveBundleDirFromRoots2(
34960
+ (roots ?? []).map((root) => root.uri),
34961
+ DEFAULT_BUNDLE_DIR2
34962
+ );
34963
+ }
34964
+ } catch {
34965
+ }
34966
+ return DEFAULT_BUNDLE_DIR2;
34967
+ }
34624
34968
  server.registerTool(
34625
34969
  "zam_okf_catalog",
34626
34970
  {
34627
34971
  description: "List the OKF knowledge-base articles (type, title, description, tags, resource URL) plus conformance problems, if any",
34628
34972
  inputSchema: {
34629
- bundle_dir: okfBundleDirSchema
34973
+ bundle_dir: okfBundleDirSchema,
34974
+ include_log: z.boolean().optional().describe(
34975
+ "Also return the raw log.md text (empty string if missing)"
34976
+ )
34630
34977
  },
34631
34978
  annotations: {
34632
34979
  ...commonAnnotations,
34633
34980
  readOnlyHint: true
34634
34981
  }
34635
34982
  },
34636
- wrapHandler(async (params) => {
34637
- const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34638
- const bundle = loadBundle2(params.bundle_dir ?? DEFAULT_BUNDLE_DIR2);
34639
- return {
34640
- dir: bundle.dir,
34641
- articles: bundle.catalog,
34642
- problems: bundle.problems
34643
- };
34644
- })
34983
+ wrapHandler(
34984
+ async (params) => {
34985
+ const { loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34986
+ const bundle = loadBundle2(await resolveOkfBundleDir(params.bundle_dir));
34987
+ let log;
34988
+ if (params.include_log) {
34989
+ const { readFileSync: readFileSync20 } = await import("fs");
34990
+ try {
34991
+ log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
34992
+ } catch {
34993
+ log = "";
34994
+ }
34995
+ }
34996
+ return {
34997
+ dir: bundle.dir,
34998
+ articles: bundle.catalog,
34999
+ problems: bundle.problems,
35000
+ ...params.include_log ? { log } : {}
35001
+ };
35002
+ }
35003
+ )
34645
35004
  );
34646
35005
  server.registerTool(
34647
35006
  "zam_okf_read",
@@ -34658,10 +35017,10 @@ function createMcpServer(db) {
34658
35017
  },
34659
35018
  wrapHandler(async (params) => {
34660
35019
  const { readFileSync: readFileSync20 } = await import("fs");
34661
- const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35020
+ const { resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34662
35021
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
34663
35022
  const path = resolveArticlePath2(
34664
- params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
35023
+ await resolveOkfBundleDir(params.bundle_dir),
34665
35024
  params.file
34666
35025
  );
34667
35026
  const markdown = readFileSync20(path, "utf8");
@@ -34688,9 +35047,9 @@ function createMcpServer(db) {
34688
35047
  },
34689
35048
  wrapHandler(
34690
35049
  async (params) => {
34691
- const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, upsertArticle: upsertArticle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35050
+ const { upsertArticle: upsertArticle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34692
35051
  const result = upsertArticle2(
34693
- params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
35052
+ await resolveOkfBundleDir(params.bundle_dir),
34694
35053
  params.file,
34695
35054
  params.markdown
34696
35055
  );
@@ -34701,6 +35060,162 @@ function createMcpServer(db) {
34701
35060
  }
34702
35061
  )
34703
35062
  );
35063
+ server.registerTool(
35064
+ "zam_okf_import",
35065
+ {
35066
+ 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.",
35067
+ inputSchema: {
35068
+ user: z.string().optional().describe("User ID to create cards for"),
35069
+ bundle_dir: okfBundleDirSchema,
35070
+ file: z.string().describe("Article file name inside the bundle (kebab-case .md)"),
35071
+ tokens: z.array(
35072
+ z.object({
35073
+ slug: z.string().describe("Unique kebab-case token slug"),
35074
+ title: z.string().optional().describe("Display title"),
35075
+ concept: z.string().describe("The memorable concept, stated precisely"),
35076
+ bloomLevel: z.number().int().min(1).max(5).optional().describe("Judged Bloom level (1-5)"),
35077
+ domain: z.string().optional().describe("Judged domain (reuse existing domains)"),
35078
+ anchor: z.string().optional().describe(
35079
+ "Heading anchor in the article; appended to the source link"
35080
+ ),
35081
+ prerequisites: z.array(z.string()).optional().describe(
35082
+ "Slugs this token requires \u2014 in-import or existing tokens"
35083
+ ),
35084
+ knowledgeContexts: z.array(z.string()).optional().describe("Knowledge contexts to assign"),
35085
+ question: z.string().nullable().optional().describe("Optional pre-defined review question"),
35086
+ mode: z.enum(["new", "update", "replace"]).optional().describe(
35087
+ "Re-import classification: new (default) | update (keep learning state) | replace (concept changed, reset learning state)"
35088
+ )
35089
+ })
35090
+ ).min(1).describe("Your finished decomposition")
35091
+ },
35092
+ annotations: {
35093
+ ...commonAnnotations
35094
+ }
35095
+ },
35096
+ wrapHandler(
35097
+ async (params) => {
35098
+ return await importOkfTokens(db, {
35099
+ user: await getUserId(params.user),
35100
+ bundleDir: await resolveOkfBundleDir(params.bundle_dir),
35101
+ file: params.file,
35102
+ tokens: params.tokens
35103
+ });
35104
+ }
35105
+ )
35106
+ );
35107
+ server.registerTool(
35108
+ "zam_okf_read_citation",
35109
+ {
35110
+ description: "Read a citation target referenced by an OKF article (e.g. an ADR): read-only, restricted to .md files that resolve inside the repository root \u2014 the target may be outside the bundle but never outside the repo",
35111
+ inputSchema: {
35112
+ bundle_dir: okfBundleDirSchema,
35113
+ target: z.string().describe(
35114
+ "Path to the citation target relative to bundle_dir, e.g. ../adr/2026-07-17-x.md"
35115
+ )
35116
+ },
35117
+ annotations: {
35118
+ ...commonAnnotations,
35119
+ readOnlyHint: true
35120
+ }
35121
+ },
35122
+ wrapHandler(async (params) => {
35123
+ const { readFileSync: readFileSync20 } = await import("fs");
35124
+ const { relative: relative2, sep: sep2 } = await import("path");
35125
+ const { findRepoRoot: findRepoRoot2, resolveCitationPath: resolveCitationPath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35126
+ const bundleDir = await resolveOkfBundleDir(params.bundle_dir);
35127
+ const path = resolveCitationPath2(bundleDir, params.target);
35128
+ const content = readFileSync20(path, "utf8");
35129
+ const root = findRepoRoot2(bundleDir);
35130
+ const repoRelativePath = relative2(root, path).split(sep2).join("/");
35131
+ return { target: params.target, path: repoRelativePath, content };
35132
+ })
35133
+ );
35134
+ registerAppTool(
35135
+ server,
35136
+ "zam_okf_visualize",
35137
+ {
35138
+ title: "Open ZAM OKF visualizer",
35139
+ description: "Open the ZAM OKF knowledge-base visualizer: articles by type with search, a markdown reader with inline-expandable cited ADRs, a link graph, and the log. Pass `bundle_dir` to browse a bundle other than the default (docs/okf under the server cwd).",
35140
+ inputSchema: {
35141
+ bundle_dir: okfBundleDirSchema
35142
+ },
35143
+ annotations: {
35144
+ ...commonAnnotations,
35145
+ readOnlyHint: true
35146
+ },
35147
+ _meta: {
35148
+ ui: { resourceUri: OKF_RESOURCE_URI }
35149
+ }
35150
+ },
35151
+ wrapHandler(async ({ bundle_dir }) => {
35152
+ const opening = await resolveOpeningCompanionContextSafely(
35153
+ db,
35154
+ "okf",
35155
+ void 0,
35156
+ getNativeClientInfo(),
35157
+ { clientSamplingCapable: getClientSamplingCapable() }
35158
+ );
35159
+ const { loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35160
+ const { resolve: resolve8 } = await import("path");
35161
+ const requestedDir = await resolveOkfBundleDir(bundle_dir);
35162
+ let resolvedBundleDir = resolve8(requestedDir);
35163
+ await publishUiIntent("okf", { bundle_dir: resolvedBundleDir });
35164
+ let catalog = [];
35165
+ let problems = [];
35166
+ let log = "";
35167
+ let okfVersion = null;
35168
+ try {
35169
+ const bundle = loadBundle2(requestedDir);
35170
+ resolvedBundleDir = bundle.dir;
35171
+ catalog = bundle.catalog;
35172
+ problems = bundle.problems;
35173
+ const { readFileSync: readFileSync20 } = await import("fs");
35174
+ try {
35175
+ log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
35176
+ } catch {
35177
+ log = "";
35178
+ }
35179
+ try {
35180
+ const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
35181
+ const indexRaw = readFileSync20(join25(bundle.dir, "index.md"), "utf8");
35182
+ const { fields } = parseFrontmatter2(indexRaw);
35183
+ okfVersion = typeof fields.okf_version === "string" ? fields.okf_version : null;
35184
+ } catch {
35185
+ okfVersion = null;
35186
+ }
35187
+ } catch (error) {
35188
+ problems = [error instanceof Error ? error.message : String(error)];
35189
+ }
35190
+ return {
35191
+ okf: "zam",
35192
+ version: pkg.version,
35193
+ user: opening.context.user.currentId ?? null,
35194
+ bundleDir: resolvedBundleDir,
35195
+ okfVersion,
35196
+ catalog,
35197
+ problems,
35198
+ log,
35199
+ companionContext: opening.context,
35200
+ ...opening.degraded ? { companionContextDegraded: true } : {}
35201
+ };
35202
+ })
35203
+ );
35204
+ registerAppResource(
35205
+ server,
35206
+ "zam-okf",
35207
+ OKF_RESOURCE_URI,
35208
+ { mimeType: RESOURCE_MIME_TYPE },
35209
+ async () => ({
35210
+ contents: [
35211
+ {
35212
+ uri: OKF_RESOURCE_URI,
35213
+ mimeType: RESOURCE_MIME_TYPE,
35214
+ text: loadPanelHtml("okf-panel.html", "ZAM OKF")
35215
+ }
35216
+ ]
35217
+ })
35218
+ );
34704
35219
  return server;
34705
35220
  }
34706
35221
  async function runMcpServer() {