zam-core 0.13.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,
@@ -9168,18 +9234,31 @@ __export(io_exports, {
9168
9234
  findRepoRoot: () => findRepoRoot,
9169
9235
  loadBundle: () => loadBundle,
9170
9236
  resolveArticlePath: () => resolveArticlePath,
9237
+ resolveBundleDirFromRoots: () => resolveBundleDirFromRoots,
9171
9238
  resolveCitationPath: () => resolveCitationPath,
9172
9239
  upsertArticle: () => upsertArticle
9173
9240
  });
9174
9241
  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
9242
+ existsSync as existsSync13,
9243
+ mkdirSync as mkdirSync9,
9244
+ readdirSync as readdirSync2,
9245
+ readFileSync as readFileSync11,
9246
+ realpathSync,
9247
+ writeFileSync as writeFileSync6
9181
9248
  } from "fs";
9182
- import { dirname as dirname11, isAbsolute as isAbsolute2, join as join24, relative, resolve as resolve7, sep } from "path";
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
+ }
9183
9262
  function resolveArticlePath(dir, file) {
9184
9263
  if (file.includes("/") || file.includes("\\") || file.includes("..")) {
9185
9264
  throw new Error(`invalid article file name: ${file}`);
@@ -9187,19 +9266,19 @@ function resolveArticlePath(dir, file) {
9187
9266
  if (isReservedFile(file)) {
9188
9267
  throw new Error(`refusing to address reserved file: ${file}`);
9189
9268
  }
9190
- return join24(resolve7(dir), file);
9269
+ return join13(resolve4(dir), file);
9191
9270
  }
9192
9271
  function findRepoRoot(startDir) {
9193
- let dir = resolve7(startDir);
9272
+ let dir = resolve4(startDir);
9194
9273
  for (; ; ) {
9195
- if (existsSync21(join24(dir, ".git"))) return dir;
9196
- const parent = dirname11(dir);
9197
- if (parent === dir) return resolve7(startDir, "..");
9274
+ if (existsSync13(join13(dir, ".git"))) return dir;
9275
+ const parent = dirname6(dir);
9276
+ if (parent === dir) return resolve4(startDir, "..");
9198
9277
  dir = parent;
9199
9278
  }
9200
9279
  }
9201
9280
  function resolveCitationPath(bundleDir, target) {
9202
- if (isAbsolute2(target)) {
9281
+ if (isAbsolute(target)) {
9203
9282
  throw new Error(
9204
9283
  `invalid citation target: absolute paths are not allowed (${target})`
9205
9284
  );
@@ -9210,26 +9289,26 @@ function resolveCitationPath(bundleDir, target) {
9210
9289
  );
9211
9290
  }
9212
9291
  const root = findRepoRoot(bundleDir);
9213
- const resolved = resolve7(bundleDir, target);
9292
+ const resolved = resolve4(bundleDir, target);
9214
9293
  assertContained(root, resolved, target);
9215
- if (existsSync21(resolved)) {
9216
- assertContained(realpathSync2(root), realpathSync2(resolved), target);
9294
+ if (existsSync13(resolved)) {
9295
+ assertContained(realpathSync(root), realpathSync(resolved), target);
9217
9296
  }
9218
9297
  return resolved;
9219
9298
  }
9220
9299
  function assertContained(root, candidate, target) {
9221
9300
  const rel = relative(root, candidate);
9222
- if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute2(rel)) {
9301
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
9223
9302
  throw new Error(
9224
9303
  `invalid citation target: resolves outside the repository root (${target})`
9225
9304
  );
9226
9305
  }
9227
9306
  }
9228
9307
  function loadBundle(dir) {
9229
- const root = resolve7(dir);
9308
+ const root = resolve4(dir);
9230
9309
  let entries;
9231
9310
  try {
9232
- entries = readdirSync3(root).filter(
9311
+ entries = readdirSync2(root).filter(
9233
9312
  (name) => name.endsWith(".md") && !isReservedFile(name)
9234
9313
  );
9235
9314
  } catch {
@@ -9237,7 +9316,7 @@ function loadBundle(dir) {
9237
9316
  }
9238
9317
  const articles = entries.sort().map((file) => ({
9239
9318
  file,
9240
- markdown: readFileSync18(join24(root, file), "utf8")
9319
+ markdown: readFileSync11(join13(root, file), "utf8")
9241
9320
  }));
9242
9321
  const problems = articles.flatMap(
9243
9322
  ({ file, markdown }) => validateArticle(file, markdown).problems
@@ -9259,25 +9338,25 @@ function upsertArticle(dir, file, markdown, today = (/* @__PURE__ */ new Date())
9259
9338
  const target = resolveArticlePath(dir, file);
9260
9339
  const validation = validateArticle(file, markdown);
9261
9340
  if (!validation.ok) return { validation };
9262
- const root = resolve7(dir);
9263
- mkdirSync16(root, { recursive: true });
9341
+ const root = resolve4(dir);
9342
+ mkdirSync9(root, { recursive: true });
9264
9343
  let created = true;
9265
9344
  try {
9266
- readFileSync18(target, "utf8");
9345
+ readFileSync11(target, "utf8");
9267
9346
  created = false;
9268
9347
  } catch {
9269
9348
  }
9270
- writeFileSync13(target, markdown, "utf8");
9349
+ writeFileSync6(target, markdown, "utf8");
9271
9350
  const bundle = loadBundle(root);
9272
- writeFileSync13(join24(root, "index.md"), renderIndex(bundle.catalog), "utf8");
9351
+ writeFileSync6(join13(root, "index.md"), renderIndex(bundle.catalog), "utf8");
9273
9352
  let log = "";
9274
9353
  try {
9275
- log = readFileSync18(join24(root, "log.md"), "utf8");
9354
+ log = readFileSync11(join13(root, "log.md"), "utf8");
9276
9355
  } catch {
9277
9356
  }
9278
9357
  const entry = bundle.catalog.find((e) => e.file === file);
9279
- writeFileSync13(
9280
- join24(root, "log.md"),
9358
+ writeFileSync6(
9359
+ join13(root, "log.md"),
9281
9360
  appendLog(
9282
9361
  log,
9283
9362
  today,
@@ -9300,7 +9379,7 @@ var init_io = __esm({
9300
9379
  init_kernel();
9301
9380
  import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
9302
9381
  import { dirname as dirname12, join as join25 } from "path";
9303
- import { fileURLToPath as fileURLToPath6 } from "url";
9382
+ import { fileURLToPath as fileURLToPath7 } from "url";
9304
9383
  import {
9305
9384
  RESOURCE_MIME_TYPE,
9306
9385
  registerAppResource,
@@ -9513,8 +9592,8 @@ function buildCompanionContext(input) {
9513
9592
 
9514
9593
  // src/cli/bridge-handlers.ts
9515
9594
  init_kernel();
9516
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
9517
- import { join as join13 } from "path";
9595
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
9596
+ import { join as join14 } from "path";
9518
9597
 
9519
9598
  // src/cli/knowledge-contexts.ts
9520
9599
  init_kernel();
@@ -10397,6 +10476,161 @@ async function addToken(db, params) {
10397
10476
  possible_duplicates: possibleDuplicates
10398
10477
  };
10399
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
+ }
10400
10634
  async function findTokens2(db, params) {
10401
10635
  const userId = await resolveHandlerUser(db, params.user);
10402
10636
  if (!params.context.trim()) {
@@ -10721,11 +10955,11 @@ async function backupCreate(db, params) {
10721
10955
  const targetDir = params.dir || (await ensureActiveWorkspace(db)).path;
10722
10956
  const snapshot = await exportSnapshot(db);
10723
10957
  const manifest = verifySnapshot(snapshot);
10724
- const backupDir = join13(targetDir, "zam-backups");
10725
- mkdirSync9(backupDir, { recursive: true });
10958
+ const backupDir = join14(targetDir, "zam-backups");
10959
+ mkdirSync10(backupDir, { recursive: true });
10726
10960
  const stamp = manifest.createdAt.replace(/[:.]/g, "-");
10727
- const path = join13(backupDir, `zam-snapshot-${stamp}.sql`);
10728
- writeFileSync6(path, snapshot, "utf-8");
10961
+ const path = join14(backupDir, `zam-snapshot-${stamp}.sql`);
10962
+ writeFileSync7(path, snapshot, "utf-8");
10729
10963
  return {
10730
10964
  ok: true,
10731
10965
  path,
@@ -10750,27 +10984,27 @@ init_kernel();
10750
10984
 
10751
10985
  // src/cli/agent-connect.ts
10752
10986
  init_kernel();
10753
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "fs";
10987
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
10754
10988
  import { homedir as homedir12 } from "os";
10755
- import { dirname as dirname8 } from "path";
10989
+ import { dirname as dirname9 } from "path";
10756
10990
 
10757
10991
  // src/cli/agent-harness.ts
10758
10992
  import { spawn as spawn2 } from "child_process";
10759
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
10993
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
10760
10994
  import { homedir as homedir9 } from "os";
10761
- import { join as join15 } from "path";
10995
+ import { join as join16 } from "path";
10762
10996
 
10763
10997
  // src/cli/terminal-open.ts
10764
10998
  import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
10765
10999
  import {
10766
11000
  accessSync,
10767
11001
  constants,
10768
- existsSync as existsSync13,
11002
+ existsSync as existsSync14,
10769
11003
  unlinkSync as unlinkSync2,
10770
- writeFileSync as writeFileSync7
11004
+ writeFileSync as writeFileSync8
10771
11005
  } from "fs";
10772
11006
  import { tmpdir } from "os";
10773
- 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";
10774
11008
  function isPowerShellShell(shell) {
10775
11009
  return shell === "pwsh" || shell === "powershell";
10776
11010
  }
@@ -10812,7 +11046,7 @@ function stripSurroundingQuotes(command) {
10812
11046
  return trimmed;
10813
11047
  }
10814
11048
  function executableExists(path) {
10815
- if (!existsSync13(path)) return false;
11049
+ if (!existsSync14(path)) return false;
10816
11050
  if (process.platform === "win32") return true;
10817
11051
  try {
10818
11052
  accessSync(path, constants.X_OK);
@@ -10828,7 +11062,7 @@ function windowsExecutableNames(command) {
10828
11062
  return extensions.map((ext) => `${command}${ext}`);
10829
11063
  }
10830
11064
  function hasDirectoryPart(command) {
10831
- return isAbsolute(command) || command.includes("/") || command.includes("\\");
11065
+ return isAbsolute2(command) || command.includes("/") || command.includes("\\");
10832
11066
  }
10833
11067
  function findExecutable(command) {
10834
11068
  const normalized = stripSurroundingQuotes(command);
@@ -10843,7 +11077,7 @@ function findExecutable(command) {
10843
11077
  const pathEntries = (process.env.PATH ?? "").split(delimiter).map((entry) => entry.trim()).filter(Boolean);
10844
11078
  for (const entry of pathEntries) {
10845
11079
  for (const name of windowsExecutableNames(normalized)) {
10846
- const candidate = join14(entry, name);
11080
+ const candidate = join15(entry, name);
10847
11081
  if (executableExists(candidate)) matches.push(candidate);
10848
11082
  }
10849
11083
  }
@@ -10881,9 +11115,9 @@ end tell` : `tell application "Terminal"
10881
11115
  activate
10882
11116
  do script "${escaped}"
10883
11117
  end tell`;
10884
- const tmpFile = join14(tmpdir(), `zam-terminal-${label}.scpt`);
11118
+ const tmpFile = join15(tmpdir(), `zam-terminal-${label}.scpt`);
10885
11119
  try {
10886
- writeFileSync7(tmpFile, appleScript);
11120
+ writeFileSync8(tmpFile, appleScript);
10887
11121
  execSync4(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
10888
11122
  if (!silent) {
10889
11123
  console.log(
@@ -10964,7 +11198,7 @@ function openTerminalWindow(opts) {
10964
11198
  // src/cli/agent-harness.ts
10965
11199
  var ANTIGRAVITY_IDE_CANDIDATE_PATHS = {
10966
11200
  darwin: [
10967
- join15(
11201
+ join16(
10968
11202
  homedir9(),
10969
11203
  ".antigravity-ide",
10970
11204
  "antigravity-ide",
@@ -10985,7 +11219,7 @@ var AGENT_HARNESSES = [
10985
11219
  command: "cursor",
10986
11220
  candidatePaths: {
10987
11221
  win32: [
10988
- join15(homedir9(), "AppData", "Local", "Programs", "cursor", "Cursor.exe")
11222
+ join16(homedir9(), "AppData", "Local", "Programs", "cursor", "Cursor.exe")
10989
11223
  ],
10990
11224
  darwin: ["/Applications/Cursor.app/Contents/MacOS/Cursor"]
10991
11225
  }
@@ -11010,7 +11244,7 @@ function getHarness(id) {
11010
11244
  }
11011
11245
  function resolveAntigravityIdeExecutable(deps = {}) {
11012
11246
  const find = deps.find ?? findExecutable;
11013
- const exists = deps.exists ?? existsSync14;
11247
+ const exists = deps.exists ?? existsSync15;
11014
11248
  const platform = deps.platform ?? process.platform;
11015
11249
  const found = find("antigravity-ide");
11016
11250
  if (found) return found;
@@ -11018,7 +11252,7 @@ function resolveAntigravityIdeExecutable(deps = {}) {
11018
11252
  }
11019
11253
  function resolveHarnessExecutable(harness, overrideCommand, deps = {}) {
11020
11254
  const find = deps.find ?? findExecutable;
11021
- const exists = deps.exists ?? existsSync14;
11255
+ const exists = deps.exists ?? existsSync15;
11022
11256
  const platform = deps.platform ?? process.platform;
11023
11257
  const found = find(overrideCommand || harness.command);
11024
11258
  if (found) return found;
@@ -11078,18 +11312,18 @@ function detectInstalledConnectHarnesses(options = {}) {
11078
11312
  const home = options.home ?? homedir9();
11079
11313
  const platform = options.platform ?? process.platform;
11080
11314
  const find = options.find ?? findExecutable;
11081
- const exists = options.exists ?? existsSync14;
11315
+ const exists = options.exists ?? existsSync15;
11082
11316
  const detected = [];
11083
11317
  const hasCommandOrPath = (command, paths = []) => Boolean(find(command)) || paths.some((path) => exists(path));
11084
11318
  if (hasCommandOrPath("codex", [
11085
- join15(home, ".codex"),
11086
- ...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")] : []
11087
11321
  ])) {
11088
11322
  detected.push("codex");
11089
11323
  }
11090
11324
  const vscodePaths = platform === "darwin" ? [
11091
11325
  "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
11092
- join15(
11326
+ join16(
11093
11327
  home,
11094
11328
  "Applications",
11095
11329
  "Visual Studio Code.app",
@@ -11100,7 +11334,7 @@ function detectInstalledConnectHarnesses(options = {}) {
11100
11334
  "code"
11101
11335
  )
11102
11336
  ] : platform === "win32" ? [
11103
- join15(
11337
+ join16(
11104
11338
  home,
11105
11339
  "AppData",
11106
11340
  "Local",
@@ -11111,23 +11345,23 @@ function detectInstalledConnectHarnesses(options = {}) {
11111
11345
  )
11112
11346
  ] : ["/usr/bin/code", "/usr/local/bin/code", "/snap/bin/code"];
11113
11347
  if (hasCommandOrPath("code", vscodePaths)) detected.push("vscode");
11114
- const copilotHome = options.copilotHome ?? join15(home, ".copilot");
11348
+ const copilotHome = options.copilotHome ?? join16(home, ".copilot");
11115
11349
  if (hasCommandOrPath("copilot", [
11116
11350
  copilotHome,
11117
11351
  ...platform === "darwin" ? ["/Applications/GitHub Copilot.app"] : []
11118
11352
  ])) {
11119
11353
  detected.push("copilot");
11120
11354
  }
11121
- if (hasCommandOrPath("opencode", [join15(home, ".config", "opencode")])) {
11355
+ if (hasCommandOrPath("opencode", [join16(home, ".config", "opencode")])) {
11122
11356
  detected.push("opencode");
11123
11357
  }
11124
- if (hasCommandOrPath("goose", [join15(home, ".config", "goose")])) {
11358
+ if (hasCommandOrPath("goose", [join16(home, ".config", "goose")])) {
11125
11359
  detected.push("goose");
11126
11360
  }
11127
11361
  if (hasCommandOrPath("antigravity", [
11128
- join15(home, ".gemini", "config"),
11362
+ join16(home, ".gemini", "config"),
11129
11363
  ...platform === "darwin" ? [
11130
- join15(
11364
+ join16(
11131
11365
  home,
11132
11366
  ".antigravity-ide",
11133
11367
  "antigravity-ide",
@@ -11140,7 +11374,7 @@ function detectInstalledConnectHarnesses(options = {}) {
11140
11374
  ]) || find("antigravity-ide")) {
11141
11375
  detected.push("antigravity");
11142
11376
  }
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");
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");
11144
11378
  if (exists(claudeDesktopPath)) detected.push("claude-desktop");
11145
11379
  return detected;
11146
11380
  }
@@ -11173,11 +11407,11 @@ function connectHarnessMcp(harnessId, opts) {
11173
11407
  return false;
11174
11408
  }
11175
11409
  }
11176
- return existsSync14(p);
11410
+ return existsSync15(p);
11177
11411
  };
11178
11412
  const read = (p) => {
11179
11413
  if (opts.readFile) return opts.readFile(p);
11180
- return readFileSync11(p, "utf-8");
11414
+ return readFileSync12(p, "utf-8");
11181
11415
  };
11182
11416
  let targetPath = "";
11183
11417
  let content = "";
@@ -11209,32 +11443,32 @@ function connectHarnessMcp(harnessId, opts) {
11209
11443
  return JSON.stringify(existing, null, 2);
11210
11444
  };
11211
11445
  if (harnessId === "claude-code") {
11212
- targetPath = join15(opts.cwd, ".mcp.json");
11446
+ targetPath = join16(opts.cwd, ".mcp.json");
11213
11447
  hint = "Claude Code will prompt you to approve the 'zam' MCP server on next launch.";
11214
11448
  content = mergeMcpServersJson(targetPath);
11215
11449
  } else if (harnessId === "claude-desktop") {
11216
11450
  const platform = opts.platform ?? process.platform;
11217
- targetPath = platform === "win32" ? join15(
11451
+ targetPath = platform === "win32" ? join16(
11218
11452
  opts.home,
11219
11453
  "AppData",
11220
11454
  "Roaming",
11221
11455
  "Claude",
11222
11456
  "claude_desktop_config.json"
11223
- ) : platform === "darwin" ? join15(
11457
+ ) : platform === "darwin" ? join16(
11224
11458
  opts.home,
11225
11459
  "Library",
11226
11460
  "Application Support",
11227
11461
  "Claude",
11228
11462
  "claude_desktop_config.json"
11229
- ) : join15(opts.home, ".config", "Claude", "claude_desktop_config.json");
11463
+ ) : join16(opts.home, ".config", "Claude", "claude_desktop_config.json");
11230
11464
  hint = "Restart Claude Desktop to load the 'zam' MCP server; MCP Apps panels render inline in the chat.";
11231
11465
  content = mergeMcpServersJson(targetPath);
11232
11466
  } else if (harnessId === "antigravity") {
11233
- targetPath = join15(opts.home, ".gemini", "config", "mcp_config.json");
11467
+ targetPath = join16(opts.home, ".gemini", "config", "mcp_config.json");
11234
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.";
11235
11469
  content = mergeMcpServersJson(targetPath);
11236
11470
  } else if (harnessId === "opencode") {
11237
- targetPath = join15(opts.home, ".config", "opencode", "opencode.json");
11471
+ targetPath = join16(opts.home, ".config", "opencode", "opencode.json");
11238
11472
  hint = "OpenCode will load the enabled 'zam' MCP server on next launch.";
11239
11473
  let existing = {};
11240
11474
  if (exists(targetPath)) {
@@ -11262,7 +11496,7 @@ function connectHarnessMcp(harnessId, opts) {
11262
11496
  existing.mcp = servers;
11263
11497
  content = JSON.stringify(existing, null, 2);
11264
11498
  } else if (harnessId === "codex") {
11265
- targetPath = join15(opts.home, ".codex", "config.toml");
11499
+ targetPath = join16(opts.home, ".codex", "config.toml");
11266
11500
  hint = "Codex will prompt for tool execution approvals or respect the TOML approval modes.";
11267
11501
  let existingStr = "";
11268
11502
  if (exists(targetPath)) {
@@ -11289,14 +11523,14 @@ ${block}` : block;
11289
11523
  }
11290
11524
  } else if (harnessId === "vscode") {
11291
11525
  const platform = opts.platform ?? process.platform;
11292
- 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(
11293
11527
  opts.home,
11294
11528
  "Library",
11295
11529
  "Application Support",
11296
11530
  "Code",
11297
11531
  "User",
11298
11532
  "mcp.json"
11299
- ) : join15(opts.home, ".config", "Code", "User", "mcp.json");
11533
+ ) : join16(opts.home, ".config", "Code", "User", "mcp.json");
11300
11534
  hint = "Reload VS Code after setup. ZAM Companion stays separate from the Codex chat and can be moved to any panel or sidebar.";
11301
11535
  let existing = {};
11302
11536
  if (exists(targetPath)) {
@@ -11318,7 +11552,7 @@ ${block}` : block;
11318
11552
  if (!existing.inputs) existing.inputs = [];
11319
11553
  content = JSON.stringify(existing, null, 2);
11320
11554
  } else if (harnessId === "goose") {
11321
- targetPath = join15(opts.home, ".config", "goose", "config.yaml");
11555
+ targetPath = join16(opts.home, ".config", "goose", "config.yaml");
11322
11556
  hint = "goose will load the 'zam' extension on next session start. Run 'goose configure' to manage extensions.";
11323
11557
  const isJs = opts.zamPath.endsWith(".js");
11324
11558
  const cmdStr = isJs ? process.execPath : opts.zamPath;
@@ -11357,8 +11591,8 @@ ${zamExtension}
11357
11591
  `;
11358
11592
  }
11359
11593
  } else if (harnessId === "copilot") {
11360
- targetPath = join15(
11361
- opts.copilotHome ?? join15(opts.home, ".copilot"),
11594
+ targetPath = join16(
11595
+ opts.copilotHome ?? join16(opts.home, ".copilot"),
11362
11596
  "mcp-config.json"
11363
11597
  );
11364
11598
  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 +11634,15 @@ ${zamExtension}
11400
11634
 
11401
11635
  // src/cli/copilot-extension.ts
11402
11636
  import {
11403
- existsSync as existsSync15,
11637
+ existsSync as existsSync16,
11404
11638
  lstatSync,
11405
- mkdirSync as mkdirSync10,
11406
- readFileSync as readFileSync12,
11407
- writeFileSync as writeFileSync8
11639
+ mkdirSync as mkdirSync11,
11640
+ readFileSync as readFileSync13,
11641
+ writeFileSync as writeFileSync9
11408
11642
  } from "fs";
11409
11643
  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";
11644
+ import { dirname as dirname7, join as join17, resolve as resolve5 } from "path";
11645
+ import { fileURLToPath as fileURLToPath4 } from "url";
11412
11646
  var EXTENSION_NAME = "zam-mcp-apps";
11413
11647
  var EXTENSION_FILES = [
11414
11648
  "extension.mjs",
@@ -11417,18 +11651,18 @@ var EXTENSION_FILES = [
11417
11651
  "manifest.json"
11418
11652
  ];
11419
11653
  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));
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));
11423
11657
  function defaultCliEntry() {
11424
- return join16(dirname6(fileURLToPath3(import.meta.url)), "index.js");
11658
+ return join17(dirname7(fileURLToPath4(import.meta.url)), "index.js");
11425
11659
  }
11426
11660
  function resolveCopilotHome(home = homedir10(), configuredHome = process.env.COPILOT_HOME) {
11427
- return configuredHome?.trim() ? resolve4(configuredHome) : join16(home, ".copilot");
11661
+ return configuredHome?.trim() ? resolve5(configuredHome) : join17(home, ".copilot");
11428
11662
  }
11429
11663
  function resolveCopilotZamLaunch(zamPath, options = {}) {
11430
11664
  const cliEntry = options.cliEntry ?? defaultCliEntry();
11431
- if (existsSync15(cliEntry)) {
11665
+ if (existsSync16(cliEntry)) {
11432
11666
  return {
11433
11667
  command: options.nodePath ?? process.execPath,
11434
11668
  args: [cliEntry, "mcp"]
@@ -11440,20 +11674,20 @@ function resolveCopilotZamLaunch(zamPath, options = {}) {
11440
11674
  };
11441
11675
  }
11442
11676
  function planCopilotExtensionInstall(options) {
11443
- const sourceDir = options.assetsDir ?? join16(packageRoot, "dist", "copilot-extension");
11677
+ const sourceDir = options.assetsDir ?? join17(packageRoot, "dist", "copilot-extension");
11444
11678
  for (const file of EXTENSION_FILES) {
11445
- if (!existsSync15(join16(sourceDir, file))) {
11679
+ if (!existsSync16(join17(sourceDir, file))) {
11446
11680
  throw new Error(
11447
- `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.`
11448
11682
  );
11449
11683
  }
11450
11684
  }
11451
11685
  const manifest = JSON.parse(
11452
- readFileSync12(join16(sourceDir, "manifest.json"), "utf8")
11686
+ readFileSync13(join17(sourceDir, "manifest.json"), "utf8")
11453
11687
  );
11454
11688
  if (manifest.name !== EXTENSION_NAME || typeof manifest.version !== "string" || !manifest.version) {
11455
11689
  throw new Error(
11456
- `Invalid Copilot MCP Apps manifest: ${join16(sourceDir, "manifest.json")}`
11690
+ `Invalid Copilot MCP Apps manifest: ${join17(sourceDir, "manifest.json")}`
11457
11691
  );
11458
11692
  }
11459
11693
  const copilotHome = resolveCopilotHome(
@@ -11462,15 +11696,15 @@ function planCopilotExtensionInstall(options) {
11462
11696
  );
11463
11697
  return {
11464
11698
  sourceDir,
11465
- destinationDir: join16(copilotHome, "extensions", EXTENSION_NAME),
11699
+ destinationDir: join17(copilotHome, "extensions", EXTENSION_NAME),
11466
11700
  launch: resolveCopilotZamLaunch(options.zamPath, options),
11467
11701
  files: EXTENSION_FILES
11468
11702
  };
11469
11703
  }
11470
11704
  function writeIfChanged(path, content) {
11471
11705
  const next = Buffer.isBuffer(content) ? content : Buffer.from(content);
11472
- if (existsSync15(path) && readFileSync12(path).equals(next)) return false;
11473
- writeFileSync8(path, next);
11706
+ if (existsSync16(path) && readFileSync13(path).equals(next)) return false;
11707
+ writeFileSync9(path, next);
11474
11708
  return true;
11475
11709
  }
11476
11710
  function installCopilotExtension(options) {
@@ -11478,18 +11712,18 @@ function installCopilotExtension(options) {
11478
11712
  if (options.dryRun) {
11479
11713
  return { ...plan, action: "planned", changedFiles: [] };
11480
11714
  }
11481
- const destinationExisted = existsSync15(plan.destinationDir);
11715
+ const destinationExisted = existsSync16(plan.destinationDir);
11482
11716
  if (destinationExisted && lstatSync(plan.destinationDir).isSymbolicLink()) {
11483
11717
  throw new Error(
11484
11718
  `Refusing to replace symlinked Copilot extension directory: ${plan.destinationDir}`
11485
11719
  );
11486
11720
  }
11487
- mkdirSync10(plan.destinationDir, { recursive: true });
11721
+ mkdirSync11(plan.destinationDir, { recursive: true });
11488
11722
  const changedFiles = [];
11489
11723
  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))) {
11724
+ const source = join17(plan.sourceDir, file);
11725
+ const destination = join17(plan.destinationDir, file);
11726
+ if (writeIfChanged(destination, readFileSync13(source))) {
11493
11727
  changedFiles.push(file);
11494
11728
  }
11495
11729
  }
@@ -11503,7 +11737,7 @@ function installCopilotExtension(options) {
11503
11737
  2
11504
11738
  )}
11505
11739
  `;
11506
- if (writeIfChanged(join16(plan.destinationDir, "launch.json"), launchContent)) {
11740
+ if (writeIfChanged(join17(plan.destinationDir, "launch.json"), launchContent)) {
11507
11741
  changedFiles.push("launch.json");
11508
11742
  }
11509
11743
  return {
@@ -11515,24 +11749,24 @@ function installCopilotExtension(options) {
11515
11749
 
11516
11750
  // src/cli/vscode-extension.ts
11517
11751
  import { execFileSync as execFileSync3 } from "child_process";
11518
- 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";
11519
11753
  import { homedir as homedir11 } from "os";
11520
- import { dirname as dirname7, join as join17 } from "path";
11521
- import { fileURLToPath as fileURLToPath4 } from "url";
11754
+ import { dirname as dirname8, join as join18 } from "path";
11755
+ import { fileURLToPath as fileURLToPath5 } from "url";
11522
11756
  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));
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));
11526
11760
  function resolveVscodeExecutable(options = {}) {
11527
11761
  const home = options.home ?? homedir11();
11528
11762
  const platform = options.platform ?? process.platform;
11529
11763
  const find = options.find ?? findExecutable;
11530
- const exists = options.exists ?? existsSync16;
11764
+ const exists = options.exists ?? existsSync17;
11531
11765
  const found = find("code");
11532
11766
  if (found) return found;
11533
11767
  const candidates = platform === "darwin" ? [
11534
11768
  "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
11535
- join17(
11769
+ join18(
11536
11770
  home,
11537
11771
  "Applications",
11538
11772
  "Visual Studio Code.app",
@@ -11543,7 +11777,7 @@ function resolveVscodeExecutable(options = {}) {
11543
11777
  "code"
11544
11778
  )
11545
11779
  ] : platform === "win32" ? [
11546
- join17(
11780
+ join18(
11547
11781
  home,
11548
11782
  "AppData",
11549
11783
  "Local",
@@ -11563,7 +11797,7 @@ function buildVscodeCliInvocation(command, args, platform = process.platform) {
11563
11797
  }
11564
11798
  function packageVersion() {
11565
11799
  const parsed = JSON.parse(
11566
- readFileSync13(join17(packageRoot2, "package.json"), "utf8")
11800
+ readFileSync14(join18(packageRoot2, "package.json"), "utf8")
11567
11801
  );
11568
11802
  if (typeof parsed.version !== "string" || !parsed.version) {
11569
11803
  throw new Error("Cannot determine the ZAM package version");
@@ -11573,9 +11807,9 @@ function packageVersion() {
11573
11807
  function planVscodeExtensionInstall(options) {
11574
11808
  const home = options.home ?? homedir11();
11575
11809
  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)) {
11810
+ const assetsDir = options.assetsDir ?? join18(packageRoot2, "dist", "vscode-extension");
11811
+ const vsixPath = join18(assetsDir, `ZAM_Companion_${version}.vsix`);
11812
+ if (!existsSync17(vsixPath)) {
11579
11813
  throw new Error(
11580
11814
  `ZAM Companion VSIX asset is missing: ${vsixPath}. Run \`npm run build\` and retry.`
11581
11815
  );
@@ -11595,7 +11829,7 @@ function planVscodeExtensionInstall(options) {
11595
11829
  version,
11596
11830
  vsixPath,
11597
11831
  codePath,
11598
- launchConfigPath: join17(home, ".zam", "vscode-launch.json"),
11832
+ launchConfigPath: join18(home, ".zam", "vscode-launch.json"),
11599
11833
  launch: { command: options.zamPath, args: ["mcp"] }
11600
11834
  };
11601
11835
  }
@@ -11631,8 +11865,8 @@ function installVscodeExtension(options) {
11631
11865
  2
11632
11866
  )}
11633
11867
  `;
11634
- const existed = existsSync16(plan.launchConfigPath);
11635
- const changed = !existed || readFileSync13(plan.launchConfigPath, "utf8") !== launchContent;
11868
+ const existed = existsSync17(plan.launchConfigPath);
11869
+ const changed = !existed || readFileSync14(plan.launchConfigPath, "utf8") !== launchContent;
11636
11870
  const run = options.run ?? ((command, args) => {
11637
11871
  const invocation = buildVscodeCliInvocation(command, args);
11638
11872
  execFileSync3(invocation.command, invocation.args, {
@@ -11654,8 +11888,8 @@ function installVscodeExtension(options) {
11654
11888
  }
11655
11889
  run(plan.codePath, ["--install-extension", plan.vsixPath, "--force"]);
11656
11890
  if (changed) {
11657
- mkdirSync11(dirname7(plan.launchConfigPath), { recursive: true });
11658
- writeFileSync9(plan.launchConfigPath, launchContent, "utf8");
11891
+ mkdirSync12(dirname8(plan.launchConfigPath), { recursive: true });
11892
+ writeFileSync10(plan.launchConfigPath, launchContent, "utf8");
11659
11893
  }
11660
11894
  return {
11661
11895
  ...plan,
@@ -11715,8 +11949,8 @@ function resolveDeps(deps) {
11715
11949
  detect: deps.detect ?? (() => detectInstalledConnectHarnesses({ home, copilotHome })),
11716
11950
  connectMcp: deps.connectMcp ?? connectHarnessMcp,
11717
11951
  writeConfig: deps.writeConfig ?? ((path, content) => {
11718
- mkdirSync12(dirname8(path), { recursive: true });
11719
- writeFileSync10(path, content, "utf-8");
11952
+ mkdirSync13(dirname9(path), { recursive: true });
11953
+ writeFileSync11(path, content, "utf-8");
11720
11954
  }),
11721
11955
  installCopilot: deps.installCopilot ?? installCopilotExtension,
11722
11956
  installVscode: deps.installVscode ?? installVscodeExtension,
@@ -11862,9 +12096,9 @@ function inspectConnectHarnesses(deps = {}) {
11862
12096
  init_kernel();
11863
12097
  import { execFileSync as execFileSync5 } from "child_process";
11864
12098
  import { randomBytes as randomBytes2 } from "crypto";
11865
- 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";
11866
12100
  import { homedir as homedir14, tmpdir as tmpdir3 } from "os";
11867
- import { join as join22, resolve as resolve6 } from "path";
12101
+ import { join as join23, resolve as resolve7 } from "path";
11868
12102
  import { Command } from "commander";
11869
12103
  import { ulid as ulid10 } from "ulid";
11870
12104
 
@@ -11944,7 +12178,7 @@ async function readWebLink(url) {
11944
12178
  redirect: "manual",
11945
12179
  signal: controller.signal,
11946
12180
  headers: {
11947
- "User-Agent": "ZAM-Content-Studio/0.13.0"
12181
+ "User-Agent": "ZAM-Content-Studio/0.14.0"
11948
12182
  }
11949
12183
  });
11950
12184
  if (res.status >= 300 && res.status < 400) {
@@ -12011,16 +12245,16 @@ async function readImageOCR(db, imagePath) {
12011
12245
  import { execFileSync as execFileSync4 } from "child_process";
12012
12246
  import {
12013
12247
  chmodSync as chmodSync2,
12014
- existsSync as existsSync17,
12015
- mkdirSync as mkdirSync13,
12016
- readFileSync as readFileSync14,
12017
- writeFileSync as writeFileSync11
12248
+ existsSync as existsSync18,
12249
+ mkdirSync as mkdirSync14,
12250
+ readFileSync as readFileSync15,
12251
+ writeFileSync as writeFileSync12
12018
12252
  } from "fs";
12019
12253
  import { homedir as homedir13 } from "os";
12020
- 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";
12021
12255
  var BUILT_CLI_PATTERN = /[\\/]dist[\\/]cli[\\/]index\.js$/;
12022
12256
  function normalizeForCompare(path, platform) {
12023
- const resolved = resolve5(path);
12257
+ const resolved = resolve6(path);
12024
12258
  return platform === "win32" ? resolved.toLowerCase() : resolved;
12025
12259
  }
12026
12260
  function windowsShimContent(nodePath, cliPath) {
@@ -12060,14 +12294,14 @@ function ensureWindowsUserPath(binDir) {
12060
12294
  return output.endsWith("updated");
12061
12295
  }
12062
12296
  function ensureUnixUserPath(home, platform) {
12063
- const profile = join18(home, platform === "darwin" ? ".zprofile" : ".profile");
12064
- const existing = existsSync17(profile) ? readFileSync14(profile, "utf8") : "";
12297
+ const profile = join19(home, platform === "darwin" ? ".zprofile" : ".profile");
12298
+ const existing = existsSync18(profile) ? readFileSync15(profile, "utf8") : "";
12065
12299
  if (existing.includes(".zam/bin")) return false;
12066
12300
  const block = `
12067
12301
  # Added by ZAM: keep the zam CLI on PATH
12068
12302
  export PATH="$HOME/.zam/bin:$PATH"
12069
12303
  `;
12070
- writeFileSync11(profile, existing + block, "utf8");
12304
+ writeFileSync12(profile, existing + block, "utf8");
12071
12305
  return true;
12072
12306
  }
12073
12307
  function installCliShim(options = {}) {
@@ -12075,10 +12309,10 @@ function installCliShim(options = {}) {
12075
12309
  const platform = options.platform ?? process.platform;
12076
12310
  const env = options.env ?? process.env;
12077
12311
  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");
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");
12082
12316
  const report = {
12083
12317
  status: "ok",
12084
12318
  binDir,
@@ -12089,7 +12323,7 @@ function installCliShim(options = {}) {
12089
12323
  pathUpdated: false,
12090
12324
  needsNewTerminal: false
12091
12325
  };
12092
- if (!BUILT_CLI_PATTERN.test(cliPath) || !existsSync17(cliPath)) {
12326
+ if (!BUILT_CLI_PATTERN.test(cliPath) || !existsSync18(cliPath)) {
12093
12327
  report.status = "skipped";
12094
12328
  report.detail = `Not running from a built CLI entry (${cliPath}); nothing to link.`;
12095
12329
  return report;
@@ -12103,11 +12337,11 @@ function installCliShim(options = {}) {
12103
12337
  return report;
12104
12338
  }
12105
12339
  const content = platform === "win32" ? windowsShimContent(nodePath, cliPath) : unixShimContent(nodePath, cliPath);
12106
- const existed = existsSync17(shimPath);
12107
- const changed = !existed || readFileSync14(shimPath, "utf8") !== content;
12340
+ const existed = existsSync18(shimPath);
12341
+ const changed = !existed || readFileSync15(shimPath, "utf8") !== content;
12108
12342
  if (changed) {
12109
- mkdirSync13(binDir, { recursive: true });
12110
- writeFileSync11(shimPath, content, "utf8");
12343
+ mkdirSync14(binDir, { recursive: true });
12344
+ writeFileSync12(shimPath, content, "utf8");
12111
12345
  if (platform !== "win32") chmodSync2(shimPath, 493);
12112
12346
  }
12113
12347
  report.status = !existed ? "installed" : changed ? "refreshed" : "ok";
@@ -28816,40 +29050,40 @@ function getCurriculumProvider(id) {
28816
29050
 
28817
29051
  // src/cli/install-repair.ts
28818
29052
  init_kernel();
28819
- import { existsSync as existsSync19 } from "fs";
29053
+ import { existsSync as existsSync20 } from "fs";
28820
29054
 
28821
29055
  // src/cli/provisioning/index.ts
28822
29056
  import {
28823
- existsSync as existsSync18,
29057
+ existsSync as existsSync19,
28824
29058
  lstatSync as lstatSync2,
28825
- mkdirSync as mkdirSync14,
28826
- readFileSync as readFileSync15,
28827
- realpathSync,
29059
+ mkdirSync as mkdirSync15,
29060
+ readFileSync as readFileSync16,
29061
+ realpathSync as realpathSync2,
28828
29062
  rmSync as rmSync2,
28829
29063
  symlinkSync,
28830
- writeFileSync as writeFileSync12
29064
+ writeFileSync as writeFileSync13
28831
29065
  } from "fs";
28832
- import { basename as basename4, dirname as dirname9, join as join19 } from "path";
28833
- 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";
28834
29068
  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));
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));
28838
29072
  var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
28839
29073
  var SKILL_PAIRS = [
28840
29074
  {
28841
- from: join19(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
28842
- to: join19(".claude", "skills", "zam", "SKILL.md"),
29075
+ from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
29076
+ to: join20(".claude", "skills", "zam", "SKILL.md"),
28843
29077
  agents: ["claude", "copilot"]
28844
29078
  },
28845
29079
  {
28846
- from: join19(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
28847
- to: join19(".agent", "skills", "zam", "SKILL.md"),
29080
+ from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
29081
+ to: join20(".agent", "skills", "zam", "SKILL.md"),
28848
29082
  agents: ["agent"]
28849
29083
  },
28850
29084
  {
28851
- from: join19(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
28852
- to: join19(".agents", "skills", "zam", "SKILL.md"),
29085
+ from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
29086
+ to: join20(".agents", "skills", "zam", "SKILL.md"),
28853
29087
  agents: ["codex"]
28854
29088
  }
28855
29089
  ];
@@ -28893,7 +29127,7 @@ function isSymbolicLink(path) {
28893
29127
  }
28894
29128
  }
28895
29129
  function comparableRealPath(path) {
28896
- const real = realpathSync(path);
29130
+ const real = realpathSync2(path);
28897
29131
  return process.platform === "win32" ? real.toLowerCase() : real;
28898
29132
  }
28899
29133
  function pathsResolveToSameDirectory(sourceDir, destinationDir) {
@@ -28909,15 +29143,15 @@ function linkPointsTo(sourceDir, destinationDir) {
28909
29143
  function isZamSkillCopy(destinationDir) {
28910
29144
  try {
28911
29145
  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"));
29146
+ const skillFile = join20(destinationDir, "SKILL.md");
29147
+ if (!existsSync19(skillFile)) return false;
29148
+ return /^name:\s*zam\s*$/m.test(readFileSync16(skillFile, "utf8"));
28915
29149
  } catch {
28916
29150
  return false;
28917
29151
  }
28918
29152
  }
28919
29153
  function classifySkillDestination(sourceDir, destinationDir) {
28920
- if (!existsSync18(sourceDir)) return "source-missing";
29154
+ if (!existsSync19(sourceDir)) return "source-missing";
28921
29155
  if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
28922
29156
  return "source-directory";
28923
29157
  }
@@ -28934,9 +29168,9 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
28934
29168
  };
28935
29169
  for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
28936
29170
  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);
29171
+ const sourceDir = dirname10(from);
29172
+ const destinationDir = dirname10(join20(cwd, to));
29173
+ const label = dirname10(to);
28940
29174
  const state = classifySkillDestination(sourceDir, destinationDir);
28941
29175
  if (state === "source-missing") {
28942
29176
  if (!opts.quiet) {
@@ -28993,7 +29227,7 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
28993
29227
  if (destinationExists) {
28994
29228
  rmSync2(destinationDir, { recursive: true, force: true });
28995
29229
  }
28996
- mkdirSync14(dirname9(destinationDir), { recursive: true });
29230
+ mkdirSync15(dirname10(destinationDir), { recursive: true });
28997
29231
  symlinkSync(
28998
29232
  sourceDir,
28999
29233
  destinationDir,
@@ -29009,8 +29243,8 @@ function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
29009
29243
  const results = [];
29010
29244
  for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
29011
29245
  if (!pairAgents.some((agent) => agents.has(agent))) continue;
29012
- const sourceDir = dirname9(from);
29013
- const destinationDir = dirname9(join19(cwd, to));
29246
+ const sourceDir = dirname10(from);
29247
+ const destinationDir = dirname10(join20(cwd, to));
29014
29248
  results.push({
29015
29249
  agents: pairAgents,
29016
29250
  source: sourceDir,
@@ -29041,7 +29275,7 @@ function defaultRepairWorkspaces() {
29041
29275
  try {
29042
29276
  const agents = parseSetupAgents();
29043
29277
  for (const workspace of getConfiguredWorkspaces()) {
29044
- if (!existsSync19(workspace.path)) {
29278
+ if (!existsSync20(workspace.path)) {
29045
29279
  summary.missing += 1;
29046
29280
  continue;
29047
29281
  }
@@ -29234,9 +29468,9 @@ init_client();
29234
29468
  init_kernel();
29235
29469
  init_client();
29236
29470
  import { randomBytes } from "crypto";
29237
- import { readFileSync as readFileSync16 } from "fs";
29471
+ import { readFileSync as readFileSync17 } from "fs";
29238
29472
  import { tmpdir as tmpdir2 } from "os";
29239
- import { basename as basename5, join as join20 } from "path";
29473
+ import { basename as basename5, join as join21 } from "path";
29240
29474
  var LANGUAGE_NAMES2 = {
29241
29475
  en: "English",
29242
29476
  de: "German",
@@ -29274,7 +29508,7 @@ async function observeUiSnapshotViaLLM(db, input) {
29274
29508
  if (isVideo) {
29275
29509
  const { mkdirSync: mkdirSync17, readdirSync: readdirSync4, rmSync: rmSync4 } = await import("fs");
29276
29510
  const { execSync: execSync5 } = await import("child_process");
29277
- const tempDir = join20(
29511
+ const tempDir = join21(
29278
29512
  tmpdir2(),
29279
29513
  `zam-frames-${randomBytes(4).toString("hex")}`
29280
29514
  );
@@ -29307,7 +29541,7 @@ async function observeUiSnapshotViaLLM(db, input) {
29307
29541
  }
29308
29542
  }
29309
29543
  for (const file of sampledFiles) {
29310
- const bytes = readFileSync16(join20(tempDir, file));
29544
+ const bytes = readFileSync17(join21(tempDir, file));
29311
29545
  imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
29312
29546
  }
29313
29547
  } finally {
@@ -29317,7 +29551,7 @@ async function observeUiSnapshotViaLLM(db, input) {
29317
29551
  }
29318
29552
  }
29319
29553
  } else {
29320
- const imageBytes = readFileSync16(input.imagePath);
29554
+ const imageBytes = readFileSync17(input.imagePath);
29321
29555
  const ext = input.imagePath.split(".").pop()?.toLowerCase();
29322
29556
  const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
29323
29557
  imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
@@ -29826,13 +30060,13 @@ async function resolveUser(opts, db, resolveOpts) {
29826
30060
  }
29827
30061
 
29828
30062
  // src/cli/workspaces/backup.ts
29829
- import { mkdirSync as mkdirSync15 } from "fs";
29830
- import { join as join21 } from "path";
30063
+ import { mkdirSync as mkdirSync16 } from "fs";
30064
+ import { join as join22 } from "path";
29831
30065
  async function backupDatabaseTo(db, targetDir) {
29832
- const backupDir = join21(targetDir, "zam-backups");
29833
- mkdirSync15(backupDir, { recursive: true });
30066
+ const backupDir = join22(targetDir, "zam-backups");
30067
+ mkdirSync16(backupDir, { recursive: true });
29834
30068
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
29835
- const dest = join21(backupDir, `zam-${stamp}.db`);
30069
+ const dest = join22(backupDir, `zam-${stamp}.db`);
29836
30070
  await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
29837
30071
  return dest;
29838
30072
  }
@@ -29966,20 +30200,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
29966
30200
  activeWorkspace,
29967
30201
  workspaceDir: activeWorkspace.path,
29968
30202
  defaultWorkspaceDir: defaultWorkspaceDir(),
29969
- dataDir: join22(homedir14(), ".zam")
30203
+ dataDir: join23(homedir14(), ".zam")
29970
30204
  });
29971
30205
  });
29972
30206
  });
29973
30207
  function provisionConfiguredWorkspaces() {
29974
30208
  return getConfiguredWorkspaces().flatMap(
29975
- (workspace) => existsSync20(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
30209
+ (workspace) => existsSync21(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
29976
30210
  );
29977
30211
  }
29978
30212
  function buildWorkspaceLinkHealth(workspaces) {
29979
30213
  const agents = parseSetupAgents();
29980
30214
  const map = {};
29981
30215
  for (const workspace of workspaces) {
29982
- if (!existsSync20(workspace.path)) continue;
30216
+ if (!existsSync21(workspace.path)) continue;
29983
30217
  const links = inspectSkillLinks(workspace.path, agents);
29984
30218
  map[workspace.id] = {
29985
30219
  health: summarizeSkillLinkHealth(links),
@@ -30009,7 +30243,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
30009
30243
  activeWorkspace,
30010
30244
  workspaceDir: activeWorkspace.path,
30011
30245
  defaultWorkspaceDir: defaultWorkspaceDir(),
30012
- dataDir: join22(homedir14(), ".zam"),
30246
+ dataDir: join23(homedir14(), ".zam"),
30013
30247
  linkHealth: buildWorkspaceLinkHealth(workspaces)
30014
30248
  });
30015
30249
  });
@@ -30024,7 +30258,7 @@ bridgeCommand.command("workspace-repair-links").description(
30024
30258
  if (!id) jsonError("A non-empty --id is required");
30025
30259
  const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
30026
30260
  if (!workspace) jsonError(`Workspace "${id}" is not configured`);
30027
- if (!existsSync20(workspace.path)) {
30261
+ if (!existsSync21(workspace.path)) {
30028
30262
  jsonError(`Workspace path does not exist: ${workspace.path}`);
30029
30263
  }
30030
30264
  const agents = parseSetupAgents(opts.agents);
@@ -30055,8 +30289,8 @@ function parseBridgeWorkspaceKind(value) {
30055
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) => {
30056
30290
  const raw = String(opts.path ?? "").trim();
30057
30291
  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}`);
30292
+ const path = resolve7(raw);
30293
+ if (!existsSync21(path)) jsonError(`Workspace path does not exist: ${path}`);
30060
30294
  const id = opts.id ? String(opts.id).trim() : void 0;
30061
30295
  if (opts.id && !id) jsonError("A non-empty --id is required");
30062
30296
  const kind = parseBridgeWorkspaceKind(opts.kind);
@@ -30100,8 +30334,8 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
30100
30334
  bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
30101
30335
  const raw = String(opts.dir ?? "").trim();
30102
30336
  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}`);
30337
+ const dir = resolve7(raw);
30338
+ if (!existsSync21(dir)) jsonError(`Workspace path does not exist: ${dir}`);
30105
30339
  const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
30106
30340
  await withOptionalDb2(async (db) => {
30107
30341
  const workspace = await activateWorkspacePath(db, dir);
@@ -30164,7 +30398,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
30164
30398
  jsonError(`Workspace is not configured: ${opts.workspace}`);
30165
30399
  }
30166
30400
  const activeWorkspace = await ensureActiveWorkspace(db);
30167
- 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);
30168
30402
  launchHarness(harness, {
30169
30403
  executable,
30170
30404
  workspace,
@@ -30440,6 +30674,52 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
30440
30674
  jsonError(err.message);
30441
30675
  }
30442
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
+ });
30443
30723
  bridgeCommand.command("relevant-tokens").description("Find tokens relevant to a given context").option("--user <id>", "User ID (default: whoami)").action(async (opts) => {
30444
30724
  try {
30445
30725
  let raw;
@@ -30536,10 +30816,10 @@ bridgeCommand.command("discover-skills").description(
30536
30816
  "20"
30537
30817
  ).action(async (opts) => {
30538
30818
  try {
30539
- const monitorDir = join22(homedir14(), ".zam", "monitor");
30819
+ const monitorDir = join23(homedir14(), ".zam", "monitor");
30540
30820
  let files;
30541
30821
  try {
30542
- files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
30822
+ files = readdirSync3(monitorDir).filter((f) => f.endsWith(".jsonl"));
30543
30823
  } catch {
30544
30824
  jsonOut({ proposals: [], message: "No monitor logs found." });
30545
30825
  return;
@@ -30549,7 +30829,7 @@ bridgeCommand.command("discover-skills").description(
30549
30829
  return;
30550
30830
  }
30551
30831
  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);
30832
+ const sorted = files.map((f) => ({ name: f, path: join23(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
30553
30833
  const sessionCommands = /* @__PURE__ */ new Map();
30554
30834
  for (const file of sorted) {
30555
30835
  const sessionId = file.name.replace(".jsonl", "");
@@ -31051,7 +31331,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
31051
31331
  return;
31052
31332
  }
31053
31333
  }
31054
- 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`);
31055
31335
  const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
31056
31336
  if (!isProvided) {
31057
31337
  const post = decidePostCapture(policy, {
@@ -31079,7 +31359,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
31079
31359
  return;
31080
31360
  }
31081
31361
  }
31082
- const imageBytes = readFileSync17(outputPath);
31362
+ const imageBytes = readFileSync18(outputPath);
31083
31363
  const base64 = imageBytes.toString("base64");
31084
31364
  jsonOut({
31085
31365
  sessionId: opts.session ?? null,
@@ -31106,9 +31386,9 @@ bridgeCommand.command("start-recording").description("Start screen recording in
31106
31386
  return;
31107
31387
  }
31108
31388
  const sessionId = opts.session;
31109
- const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
31389
+ const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
31110
31390
  const defaultExt = platform === "win32" ? ".mkv" : ".mov";
31111
- const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
31391
+ const outputPath = opts.output ?? join23(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
31112
31392
  const { existsSync: existsSync23, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
31113
31393
  if (existsSync23(statePath)) {
31114
31394
  jsonOut({
@@ -31118,7 +31398,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
31118
31398
  });
31119
31399
  return;
31120
31400
  }
31121
- const logPath = join22(tmpdir3(), `zam-recording-${sessionId}.log`);
31401
+ const logPath = join23(tmpdir3(), `zam-recording-${sessionId}.log`);
31122
31402
  let logFd;
31123
31403
  try {
31124
31404
  logFd = openSync(logPath, "w");
@@ -31200,7 +31480,7 @@ bridgeCommand.command("stop-recording").description(
31200
31480
  return;
31201
31481
  }
31202
31482
  const sessionId = opts.session;
31203
- const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
31483
+ const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
31204
31484
  const { existsSync: existsSync23, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
31205
31485
  if (!existsSync23(statePath)) {
31206
31486
  jsonOut({
@@ -31669,7 +31949,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
31669
31949
  });
31670
31950
  bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
31671
31951
  const profile = getSystemProfile();
31672
- const flmInstalled = hasCommand("flm") || existsSync20("C:\\Program Files\\flm\\flm.exe");
31952
+ const flmInstalled = hasCommand("flm") || existsSync21("C:\\Program Files\\flm\\flm.exe");
31673
31953
  const ollamaInstalled = isOllamaInstalled();
31674
31954
  const runners = [
31675
31955
  { id: "flm", label: "FastFlowLM", installed: flmInstalled },
@@ -33804,13 +34084,13 @@ async function writeCompanionContext(db, request, options = {}) {
33804
34084
  // src/cli/ui-intent.ts
33805
34085
  import { mkdir, readFile, rename, writeFile } from "fs/promises";
33806
34086
  import { homedir as homedir15 } from "os";
33807
- import { dirname as dirname10, join as join23 } from "path";
34087
+ import { dirname as dirname11, join as join24 } from "path";
33808
34088
  import { ulid as ulid11 } from "ulid";
33809
34089
  function getUiIntentPath(home = homedir15()) {
33810
- return join23(home, ".zam", "ui-intent.json");
34090
+ return join24(home, ".zam", "ui-intent.json");
33811
34091
  }
33812
34092
  function getUiHostRegistrationPath(home = homedir15()) {
33813
- return join23(home, ".zam", "vscode-host.json");
34093
+ return join24(home, ".zam", "vscode-host.json");
33814
34094
  }
33815
34095
  function compactStringInput(input) {
33816
34096
  return Object.fromEntries(
@@ -33829,8 +34109,8 @@ async function writeUiIntent(app, input = {}, opts = {}) {
33829
34109
  input: compactStringInput(input),
33830
34110
  createdAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
33831
34111
  };
33832
- const tempPath = join23(dirname10(path), `.ui-intent-${process.pid}-${id}.tmp`);
33833
- 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 });
33834
34114
  await writeFile(tempPath, `${JSON.stringify(intent, null, 2)}
33835
34115
  `, "utf8");
33836
34116
  await rename(tempPath, path);
@@ -33863,7 +34143,7 @@ async function publishUiIntent(app, input = {}, opts = {}) {
33863
34143
  }
33864
34144
 
33865
34145
  // src/cli/commands/mcp.ts
33866
- var __dirname = dirname12(fileURLToPath6(import.meta.url));
34146
+ var __dirname = dirname12(fileURLToPath7(import.meta.url));
33867
34147
  var pkgPath = join25(__dirname, "..", "..", "package.json");
33868
34148
  if (!existsSync22(pkgPath)) {
33869
34149
  pkgPath = join25(__dirname, "..", "..", "..", "package.json");
@@ -34667,7 +34947,24 @@ function createMcpServer(db) {
34667
34947
  }
34668
34948
  )
34669
34949
  );
34670
- 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
+ }
34671
34968
  server.registerTool(
34672
34969
  "zam_okf_catalog",
34673
34970
  {
@@ -34685,8 +34982,8 @@ function createMcpServer(db) {
34685
34982
  },
34686
34983
  wrapHandler(
34687
34984
  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);
34985
+ const { loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34986
+ const bundle = loadBundle2(await resolveOkfBundleDir(params.bundle_dir));
34690
34987
  let log;
34691
34988
  if (params.include_log) {
34692
34989
  const { readFileSync: readFileSync20 } = await import("fs");
@@ -34720,10 +35017,10 @@ function createMcpServer(db) {
34720
35017
  },
34721
35018
  wrapHandler(async (params) => {
34722
35019
  const { readFileSync: readFileSync20 } = await import("fs");
34723
- 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));
34724
35021
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
34725
35022
  const path = resolveArticlePath2(
34726
- params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
35023
+ await resolveOkfBundleDir(params.bundle_dir),
34727
35024
  params.file
34728
35025
  );
34729
35026
  const markdown = readFileSync20(path, "utf8");
@@ -34750,9 +35047,9 @@ function createMcpServer(db) {
34750
35047
  },
34751
35048
  wrapHandler(
34752
35049
  async (params) => {
34753
- 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));
34754
35051
  const result = upsertArticle2(
34755
- params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
35052
+ await resolveOkfBundleDir(params.bundle_dir),
34756
35053
  params.file,
34757
35054
  params.markdown
34758
35055
  );
@@ -34763,6 +35060,50 @@ function createMcpServer(db) {
34763
35060
  }
34764
35061
  )
34765
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
+ );
34766
35107
  server.registerTool(
34767
35108
  "zam_okf_read_citation",
34768
35109
  {
@@ -34781,8 +35122,8 @@ function createMcpServer(db) {
34781
35122
  wrapHandler(async (params) => {
34782
35123
  const { readFileSync: readFileSync20 } = await import("fs");
34783
35124
  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;
35125
+ const { findRepoRoot: findRepoRoot2, resolveCitationPath: resolveCitationPath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35126
+ const bundleDir = await resolveOkfBundleDir(params.bundle_dir);
34786
35127
  const path = resolveCitationPath2(bundleDir, params.target);
34787
35128
  const content = readFileSync20(path, "utf8");
34788
35129
  const root = findRepoRoot2(bundleDir);
@@ -34815,11 +35156,11 @@ function createMcpServer(db) {
34815
35156
  getNativeClientInfo(),
34816
35157
  { clientSamplingCapable: getClientSamplingCapable() }
34817
35158
  );
34818
- await publishUiIntent("okf", { bundle_dir });
34819
- const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
35159
+ const { loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34820
35160
  const { resolve: resolve8 } = await import("path");
34821
- const requestedDir = bundle_dir ?? DEFAULT_BUNDLE_DIR2;
35161
+ const requestedDir = await resolveOkfBundleDir(bundle_dir);
34822
35162
  let resolvedBundleDir = resolve8(requestedDir);
35163
+ await publishUiIntent("okf", { bundle_dir: resolvedBundleDir });
34823
35164
  let catalog = [];
34824
35165
  let problems = [];
34825
35166
  let log = "";