zam-core 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2140,6 +2140,15 @@ async function listTokens(db, options) {
2140
2140
  )`);
2141
2141
  params.push(options.knowledgeContext);
2142
2142
  }
2143
+ if (options?.sourceLinkBases) {
2144
+ const clauses = options.sourceLinkBases.map(
2145
+ () => `(source_link = ? OR source_link LIKE ? || '#%' ESCAPE '\\')`
2146
+ );
2147
+ whereClauses.push(clauses.length ? `(${clauses.join(" OR ")})` : "0 = 1");
2148
+ for (const base of options.sourceLinkBases) {
2149
+ params.push(base, escapeLike(base));
2150
+ }
2151
+ }
2143
2152
  const orderBy = options?.domain || options?.domainPrefix ? "ORDER BY bloom_level, slug" : "ORDER BY bloom_level, domain, slug";
2144
2153
  const sql = `SELECT * FROM tokens WHERE ${whereClauses.join(" AND ")} ${orderBy}`;
2145
2154
  const tokens = await db.prepare(sql).all(...params);
@@ -9231,6 +9240,7 @@ var init_bundle = __esm({
9231
9240
  var io_exports = {};
9232
9241
  __export(io_exports, {
9233
9242
  DEFAULT_BUNDLE_DIR: () => DEFAULT_BUNDLE_DIR,
9243
+ collectSourceLinkBases: () => collectSourceLinkBases,
9234
9244
  findRepoRoot: () => findRepoRoot,
9235
9245
  loadBundle: () => loadBundle,
9236
9246
  resolveArticlePath: () => resolveArticlePath,
@@ -9259,6 +9269,12 @@ function resolveBundleDirFromRoots(rootUris, fallback) {
9259
9269
  }
9260
9270
  return dirs.find((dir) => existsSync13(dir)) ?? dirs[0] ?? fallback;
9261
9271
  }
9272
+ function collectSourceLinkBases(dir) {
9273
+ const bundle = loadBundle(dir);
9274
+ return bundle.catalog.map(
9275
+ (entry) => entry.resource ?? resolveArticlePath(dir, entry.file)
9276
+ );
9277
+ }
9262
9278
  function resolveArticlePath(dir, file) {
9263
9279
  if (file.includes("/") || file.includes("\\") || file.includes("..")) {
9264
9280
  throw new Error(`invalid article file name: ${file}`);
@@ -9375,10 +9391,67 @@ var init_io = __esm({
9375
9391
  }
9376
9392
  });
9377
9393
 
9394
+ // src/cli/okf-focus.ts
9395
+ var okf_focus_exports = {};
9396
+ __export(okf_focus_exports, {
9397
+ getOkfFocusPath: () => getOkfFocusPath,
9398
+ readOkfFocus: () => readOkfFocus,
9399
+ writeOkfFocus: () => writeOkfFocus
9400
+ });
9401
+ import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
9402
+ import { homedir as homedir16 } from "os";
9403
+ import { dirname as dirname12, join as join25 } from "path";
9404
+ function getOkfFocusPath(home = homedir16()) {
9405
+ return join25(home, ".zam", "okf-focus.json");
9406
+ }
9407
+ function resolvePath(explicit) {
9408
+ return explicit ?? process.env.ZAM_OKF_FOCUS_PATH ?? getOkfFocusPath();
9409
+ }
9410
+ async function writeOkfFocus(file, bundleDir, opts = {}) {
9411
+ if (file.includes("/") || file.includes("\\") || !file.endsWith(".md")) {
9412
+ throw new Error(`invalid article file name: ${file}`);
9413
+ }
9414
+ const path = resolvePath(opts.path);
9415
+ const focus = {
9416
+ version: 1,
9417
+ file,
9418
+ ...bundleDir ? { bundleDir } : {},
9419
+ updatedAt: (opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
9420
+ };
9421
+ await mkdir2(dirname12(path), { recursive: true });
9422
+ const tmp = `${path}.tmp`;
9423
+ await writeFile2(tmp, `${JSON.stringify(focus, null, 2)}
9424
+ `, "utf8");
9425
+ await rename2(tmp, path);
9426
+ return focus;
9427
+ }
9428
+ async function readOkfFocus(opts = {}) {
9429
+ try {
9430
+ const raw = await readFile2(resolvePath(opts.path), "utf8");
9431
+ const parsed = JSON.parse(raw);
9432
+ if (parsed.version !== 1 || typeof parsed.file !== "string" || typeof parsed.updatedAt !== "string") {
9433
+ return null;
9434
+ }
9435
+ return {
9436
+ version: 1,
9437
+ file: parsed.file,
9438
+ ...typeof parsed.bundleDir === "string" ? { bundleDir: parsed.bundleDir } : {},
9439
+ updatedAt: parsed.updatedAt
9440
+ };
9441
+ } catch {
9442
+ return null;
9443
+ }
9444
+ }
9445
+ var init_okf_focus = __esm({
9446
+ "src/cli/okf-focus.ts"() {
9447
+ "use strict";
9448
+ }
9449
+ });
9450
+
9378
9451
  // src/cli/commands/mcp.ts
9379
9452
  init_kernel();
9380
9453
  import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
9381
- import { dirname as dirname12, join as join25 } from "path";
9454
+ import { basename as basename6, dirname as dirname13, join as join26 } from "path";
9382
9455
  import { fileURLToPath as fileURLToPath7 } from "url";
9383
9456
  import {
9384
9457
  RESOURCE_MIME_TYPE,
@@ -12178,7 +12251,7 @@ async function readWebLink(url) {
12178
12251
  redirect: "manual",
12179
12252
  signal: controller.signal,
12180
12253
  headers: {
12181
- "User-Agent": "ZAM-Content-Studio/0.14.0"
12254
+ "User-Agent": "ZAM-Content-Studio/0.15.0"
12182
12255
  }
12183
12256
  });
12184
12257
  if (res.status >= 300 && res.status < 400) {
@@ -32323,7 +32396,12 @@ bridgeCommand.command("list-tokens").description(
32323
32396
  ).option("--domain <domain>", "Filter by exact domain").option(
32324
32397
  "--domain-prefix <prefix>",
32325
32398
  "Filter by domain prefix (e.g. company-team) \u2014 uses / separator for hierarchy"
32326
- ).option("--knowledge-context <context>", "Filter by knowledge context").action(async (opts) => {
32399
+ ).option("--knowledge-context <context>", "Filter by knowledge context").option(
32400
+ "--source-link-base <base>",
32401
+ "Filter by source-link base (exact or '<base>#<anchor>', as written by OKF imports); repeatable",
32402
+ (value, previous) => [...previous, value],
32403
+ []
32404
+ ).action(async (opts) => {
32327
32405
  await withDb2(async (db) => {
32328
32406
  const userId = opts.user ? await resolveUser(opts, db, { json: true }) : void 0;
32329
32407
  const listOpts = {};
@@ -32331,6 +32409,8 @@ bridgeCommand.command("list-tokens").description(
32331
32409
  if (opts.domainPrefix) listOpts.domainPrefix = opts.domainPrefix;
32332
32410
  if (opts.knowledgeContext)
32333
32411
  listOpts.knowledgeContext = opts.knowledgeContext;
32412
+ if (opts.sourceLinkBase.length)
32413
+ listOpts.sourceLinkBases = opts.sourceLinkBase;
32334
32414
  const tokens = await listTokens(
32335
32415
  db,
32336
32416
  Object.keys(listOpts).length ? listOpts : void 0
@@ -34143,10 +34223,10 @@ async function publishUiIntent(app, input = {}, opts = {}) {
34143
34223
  }
34144
34224
 
34145
34225
  // src/cli/commands/mcp.ts
34146
- var __dirname = dirname12(fileURLToPath7(import.meta.url));
34147
- var pkgPath = join25(__dirname, "..", "..", "package.json");
34226
+ var __dirname = dirname13(fileURLToPath7(import.meta.url));
34227
+ var pkgPath = join26(__dirname, "..", "..", "package.json");
34148
34228
  if (!existsSync22(pkgPath)) {
34149
- pkgPath = join25(__dirname, "..", "..", "..", "package.json");
34229
+ pkgPath = join26(__dirname, "..", "..", "..", "package.json");
34150
34230
  }
34151
34231
  var pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
34152
34232
  var STUDIO_RESOURCE_URI = "ui://zam/studio";
@@ -34176,9 +34256,9 @@ var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
34176
34256
  function loadPanelHtml(fileName, placeholderTitle) {
34177
34257
  const candidates = [
34178
34258
  // dist/cli/commands/mcp.js → dist/ui/
34179
- join25(__dirname, "..", "..", "ui", fileName),
34259
+ join26(__dirname, "..", "..", "ui", fileName),
34180
34260
  // src/cli/commands/mcp.ts via tsx → <repo>/dist/ui/
34181
- join25(__dirname, "..", "..", "..", "dist", "ui", fileName)
34261
+ join26(__dirname, "..", "..", "..", "dist", "ui", fileName)
34182
34262
  ];
34183
34263
  for (const candidate of candidates) {
34184
34264
  if (existsSync22(candidate)) {
@@ -34747,7 +34827,7 @@ function createMcpServer(db) {
34747
34827
  "zam_show_graph",
34748
34828
  {
34749
34829
  title: "Open ZAM knowledge graph",
34750
- description: "Open the ZAM 2D knowledge-graph card, centered on a token's direct prerequisites and dependents. Pass `focus` (a token slug) when the conversation already names one; otherwise the card shows a hint to supply one.",
34830
+ description: "Open the ZAM 2D knowledge-graph card, centered on a token's direct prerequisites and dependents. Pass `focus` (a token slug) when the conversation already names one; without it the card offers scope selectors and defaults to tokens anchored in the current repo's OKF knowledge base.",
34751
34831
  inputSchema: {
34752
34832
  focus: z.string().optional().describe("Token slug to center the graph on"),
34753
34833
  user: z.string().optional().describe("User ID")
@@ -34769,6 +34849,16 @@ function createMcpServer(db) {
34769
34849
  { clientSamplingCapable: getClientSamplingCapable() }
34770
34850
  );
34771
34851
  const userId = opening.context.user.currentId ?? null;
34852
+ let repoScope;
34853
+ try {
34854
+ const { collectSourceLinkBases: collectSourceLinkBases2, findRepoRoot: findRepoRoot2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34855
+ const bundleDir = await resolveOkfBundleDir();
34856
+ const bases = collectSourceLinkBases2(bundleDir);
34857
+ if (bases.length > 0) {
34858
+ repoScope = { label: basename6(findRepoRoot2(bundleDir)), bases };
34859
+ }
34860
+ } catch {
34861
+ }
34772
34862
  await publishUiIntent("graph", { user, focus });
34773
34863
  return {
34774
34864
  graph: "zam",
@@ -34776,6 +34866,7 @@ function createMcpServer(db) {
34776
34866
  version: pkg.version,
34777
34867
  user: userId,
34778
34868
  companionContext: opening.context,
34869
+ ...repoScope ? { repoScope } : {},
34779
34870
  ...opening.degraded ? { companionContextDegraded: true } : {}
34780
34871
  };
34781
34872
  })
@@ -34988,7 +35079,7 @@ function createMcpServer(db) {
34988
35079
  if (params.include_log) {
34989
35080
  const { readFileSync: readFileSync20 } = await import("fs");
34990
35081
  try {
34991
- log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
35082
+ log = readFileSync20(join26(bundle.dir, "log.md"), "utf8");
34992
35083
  } catch {
34993
35084
  log = "";
34994
35085
  }
@@ -35104,6 +35195,52 @@ function createMcpServer(db) {
35104
35195
  }
35105
35196
  )
35106
35197
  );
35198
+ registerAppTool(
35199
+ server,
35200
+ "zam_okf_focus",
35201
+ {
35202
+ description: "App-only: record which OKF article the visualizer panel currently shows, so chat agents can resolve 'the currently focused article'. Not intended for direct model use.",
35203
+ inputSchema: {
35204
+ file: z.string().describe("Article file name inside the bundle (kebab-case .md)"),
35205
+ bundle_dir: z.string().optional().describe("Absolute bundle directory the panel is browsing")
35206
+ },
35207
+ annotations: {
35208
+ ...commonAnnotations,
35209
+ destructiveHint: false,
35210
+ idempotentHint: true
35211
+ },
35212
+ _meta: {
35213
+ ui: { visibility: ["app"] }
35214
+ }
35215
+ },
35216
+ wrapHandler(async (params) => {
35217
+ const { writeOkfFocus: writeOkfFocus2 } = await Promise.resolve().then(() => (init_okf_focus(), okf_focus_exports));
35218
+ const focus = await writeOkfFocus2(params.file, params.bundle_dir);
35219
+ return { recorded: { file: focus.file, updatedAt: focus.updatedAt } };
35220
+ })
35221
+ );
35222
+ server.registerTool(
35223
+ "zam_okf_focused",
35224
+ {
35225
+ description: "The OKF article currently focused in the user's visualizer panel \u2014 resolve requests like 'import this okf', 'import the currently focused article', or 'the open article'. Returns {focused: {file, bundle_dir?, updatedAt} | null}; check updatedAt and confirm the article by name with the user if it looks stale. To import, read the article (zam_okf_read) and follow the zam_okf_import contract.",
35226
+ inputSchema: {},
35227
+ annotations: {
35228
+ ...commonAnnotations,
35229
+ readOnlyHint: true
35230
+ }
35231
+ },
35232
+ wrapHandler(async () => {
35233
+ const { readOkfFocus: readOkfFocus2 } = await Promise.resolve().then(() => (init_okf_focus(), okf_focus_exports));
35234
+ const focus = await readOkfFocus2();
35235
+ return {
35236
+ focused: focus ? {
35237
+ file: focus.file,
35238
+ ...focus.bundleDir ? { bundle_dir: focus.bundleDir } : {},
35239
+ updatedAt: focus.updatedAt
35240
+ } : null
35241
+ };
35242
+ })
35243
+ );
35107
35244
  server.registerTool(
35108
35245
  "zam_okf_read_citation",
35109
35246
  {
@@ -35172,13 +35309,13 @@ function createMcpServer(db) {
35172
35309
  problems = bundle.problems;
35173
35310
  const { readFileSync: readFileSync20 } = await import("fs");
35174
35311
  try {
35175
- log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
35312
+ log = readFileSync20(join26(bundle.dir, "log.md"), "utf8");
35176
35313
  } catch {
35177
35314
  log = "";
35178
35315
  }
35179
35316
  try {
35180
35317
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
35181
- const indexRaw = readFileSync20(join25(bundle.dir, "index.md"), "utf8");
35318
+ const indexRaw = readFileSync20(join26(bundle.dir, "index.md"), "utf8");
35182
35319
  const { fields } = parseFrontmatter2(indexRaw);
35183
35320
  okfVersion = typeof fields.okf_version === "string" ? fields.okf_version : null;
35184
35321
  } catch {