zam-core 0.11.1 → 0.13.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.
@@ -8352,7 +8352,7 @@ async function prepareRecallChain(db, opts) {
8352
8352
  } else {
8353
8353
  spawnLocalRunner(endpoint.url, endpoint.model, endpoint.runner);
8354
8354
  while (Date.now() < deadline) {
8355
- await new Promise((resolve7) => setTimeout(resolve7, 1e3));
8355
+ await new Promise((resolve8) => setTimeout(resolve8, 1e3));
8356
8356
  if (await isLlmOnline(endpoint.url)) {
8357
8357
  online = true;
8358
8358
  break;
@@ -8645,7 +8645,7 @@ async function startLocalRunner(url, model, locale, hint) {
8645
8645
  let attempts = 0;
8646
8646
  const dotsPerLine = 30;
8647
8647
  while (true) {
8648
- await new Promise((resolve7) => setTimeout(resolve7, 500));
8648
+ await new Promise((resolve8) => setTimeout(resolve8, 500));
8649
8649
  if (await isLlmOnline(url)) {
8650
8650
  if (attempts > 0) process.stdout.write("\n");
8651
8651
  return true;
@@ -8734,8 +8734,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
8734
8734
  const dotsPerLine = 30;
8735
8735
  while (true) {
8736
8736
  let timeoutId;
8737
- const timeoutPromise = new Promise((resolve7) => {
8738
- timeoutId = setTimeout(() => resolve7("timeout"), timeoutMs);
8737
+ const timeoutPromise = new Promise((resolve8) => {
8738
+ timeoutId = setTimeout(() => resolve8("timeout"), timeoutMs);
8739
8739
  });
8740
8740
  const dotsInterval = setInterval(() => {
8741
8741
  process.stdout.write(".");
@@ -8989,10 +8989,317 @@ var init_client = __esm({
8989
8989
  }
8990
8990
  });
8991
8991
 
8992
+ // src/cli/okf/bundle.ts
8993
+ var bundle_exports = {};
8994
+ __export(bundle_exports, {
8995
+ OKF_VERSION: () => OKF_VERSION,
8996
+ RESERVED_FILES: () => RESERVED_FILES,
8997
+ appendLog: () => appendLog,
8998
+ buildCatalog: () => buildCatalog,
8999
+ isReservedFile: () => isReservedFile,
9000
+ parseFrontmatter: () => parseFrontmatter,
9001
+ renderIndex: () => renderIndex,
9002
+ toCatalogEntry: () => toCatalogEntry,
9003
+ validateArticle: () => validateArticle
9004
+ });
9005
+ function isReservedFile(file) {
9006
+ return RESERVED_FILES.includes(file);
9007
+ }
9008
+ function unquote(raw) {
9009
+ const v = raw.trim();
9010
+ if (v.length >= 2 && (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'"))) {
9011
+ return v.slice(1, -1);
9012
+ }
9013
+ return v;
9014
+ }
9015
+ function parseFrontmatter(markdown) {
9016
+ const lines = markdown.split("\n");
9017
+ if (lines[0]?.trim() !== "---") {
9018
+ throw new Error("frontmatter: file must start with a --- fence");
9019
+ }
9020
+ const fields = {};
9021
+ let listKey = null;
9022
+ let i = 1;
9023
+ for (; i < lines.length; i++) {
9024
+ const line = lines[i];
9025
+ if (line.trim() === "---") break;
9026
+ if (line.trim() === "") continue;
9027
+ const listItem = /^\s+-\s+(.+)$/.exec(line);
9028
+ if (listItem) {
9029
+ if (!listKey) {
9030
+ throw new Error(`frontmatter line ${i + 1}: list item without a key`);
9031
+ }
9032
+ fields[listKey].push(unquote(listItem[1]));
9033
+ continue;
9034
+ }
9035
+ const pair = /^([A-Za-z0-9_-]+):(.*)$/.exec(line);
9036
+ if (!pair) {
9037
+ throw new Error(
9038
+ `frontmatter line ${i + 1}: expected "key: value" or "- item"`
9039
+ );
9040
+ }
9041
+ const key = pair[1];
9042
+ const rest = pair[2].trim();
9043
+ if (rest === "") {
9044
+ fields[key] = [];
9045
+ listKey = key;
9046
+ } else {
9047
+ fields[key] = unquote(rest);
9048
+ listKey = null;
9049
+ }
9050
+ }
9051
+ if (i >= lines.length) {
9052
+ throw new Error("frontmatter: missing closing --- fence");
9053
+ }
9054
+ return { fields, body: lines.slice(i + 1).join("\n") };
9055
+ }
9056
+ function scalar(fields, key) {
9057
+ const v = fields[key];
9058
+ return typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
9059
+ }
9060
+ function validateArticle(file, markdown) {
9061
+ const problems = [];
9062
+ if (isReservedFile(file)) {
9063
+ return { ok: false, problems: [`${file}: reserved OKF file name`] };
9064
+ }
9065
+ if (!FILE_NAME_RE.test(file)) {
9066
+ problems.push(`${file}: file name must be kebab-case and end in .md`);
9067
+ }
9068
+ let parsed = null;
9069
+ try {
9070
+ parsed = parseFrontmatter(markdown);
9071
+ } catch (err) {
9072
+ problems.push(
9073
+ `${file}: ${err instanceof Error ? err.message : String(err)}`
9074
+ );
9075
+ }
9076
+ if (parsed) {
9077
+ if (!scalar(parsed.fields, "type")) {
9078
+ problems.push(`${file}: frontmatter field "type" is required`);
9079
+ }
9080
+ if (!scalar(parsed.fields, "description")) {
9081
+ problems.push(`${file}: frontmatter field "description" is required`);
9082
+ }
9083
+ if (parsed.body.trim() === "") {
9084
+ problems.push(`${file}: article body is empty`);
9085
+ }
9086
+ }
9087
+ return { ok: problems.length === 0, problems };
9088
+ }
9089
+ function toCatalogEntry(file, markdown) {
9090
+ const { fields } = parseFrontmatter(markdown);
9091
+ const tags = Array.isArray(fields.tags) ? fields.tags : [];
9092
+ return {
9093
+ file,
9094
+ type: scalar(fields, "type") ?? "",
9095
+ title: scalar(fields, "title") ?? file.replace(/\.md$/, ""),
9096
+ description: scalar(fields, "description") ?? "",
9097
+ tags,
9098
+ resource: scalar(fields, "resource"),
9099
+ timestamp: scalar(fields, "timestamp")
9100
+ };
9101
+ }
9102
+ function buildCatalog(articles) {
9103
+ return articles.map(({ file, markdown }) => toCatalogEntry(file, markdown)).sort((a, b) => a.file.localeCompare(b.file));
9104
+ }
9105
+ function renderIndex(catalog, okfVersion = OKF_VERSION) {
9106
+ const types = [...new Set(catalog.map((e) => e.type))].sort();
9107
+ const sections = types.map((type) => {
9108
+ const rows = catalog.filter((e) => e.type === type).map((e) => `- [${e.title}](${e.file}) \u2014 ${e.description}`).join("\n");
9109
+ return `## ${type}
9110
+
9111
+ ${rows}`;
9112
+ });
9113
+ return [
9114
+ "---",
9115
+ `okf_version: "${okfVersion}"`,
9116
+ "---",
9117
+ "",
9118
+ "# ZAM Knowledge Base",
9119
+ "",
9120
+ "Living reference knowledge for this repository in",
9121
+ "[Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog).",
9122
+ "Current truth only \u2014 the *why* behind it lives in [../adr/](../adr/)",
9123
+ "(ADR 2026-07-17). Do not edit by hand: write through the",
9124
+ "`zam_okf_upsert` MCP tool.",
9125
+ "",
9126
+ sections.join("\n\n"),
9127
+ ""
9128
+ ].join("\n");
9129
+ }
9130
+ function appendLog(existing, date, line) {
9131
+ const header = `## ${date}`;
9132
+ const entry = `- ${line}`;
9133
+ const trimmed = existing.trim();
9134
+ if (trimmed === "") {
9135
+ return `# Log
9136
+
9137
+ ${header}
9138
+
9139
+ ${entry}
9140
+ `;
9141
+ }
9142
+ const lines = trimmed.split("\n");
9143
+ const firstHeaderIdx = lines.findIndex((l) => l.startsWith("## "));
9144
+ if (firstHeaderIdx !== -1 && lines[firstHeaderIdx].trim() === header) {
9145
+ lines.splice(firstHeaderIdx + 2, 0, entry);
9146
+ return `${lines.join("\n")}
9147
+ `;
9148
+ }
9149
+ const insertAt = firstHeaderIdx === -1 ? lines.length : firstHeaderIdx;
9150
+ lines.splice(insertAt, 0, header, "", entry, "");
9151
+ return `${lines.join("\n")}
9152
+ `;
9153
+ }
9154
+ var OKF_VERSION, RESERVED_FILES, FILE_NAME_RE;
9155
+ var init_bundle = __esm({
9156
+ "src/cli/okf/bundle.ts"() {
9157
+ "use strict";
9158
+ OKF_VERSION = "0.1";
9159
+ RESERVED_FILES = ["index.md", "log.md"];
9160
+ FILE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*\.md$/;
9161
+ }
9162
+ });
9163
+
9164
+ // src/cli/okf/io.ts
9165
+ var io_exports = {};
9166
+ __export(io_exports, {
9167
+ DEFAULT_BUNDLE_DIR: () => DEFAULT_BUNDLE_DIR,
9168
+ findRepoRoot: () => findRepoRoot,
9169
+ loadBundle: () => loadBundle,
9170
+ resolveArticlePath: () => resolveArticlePath,
9171
+ resolveCitationPath: () => resolveCitationPath,
9172
+ upsertArticle: () => upsertArticle
9173
+ });
9174
+ 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
9181
+ } from "fs";
9182
+ import { dirname as dirname11, isAbsolute as isAbsolute2, join as join24, relative, resolve as resolve7, sep } from "path";
9183
+ function resolveArticlePath(dir, file) {
9184
+ if (file.includes("/") || file.includes("\\") || file.includes("..")) {
9185
+ throw new Error(`invalid article file name: ${file}`);
9186
+ }
9187
+ if (isReservedFile(file)) {
9188
+ throw new Error(`refusing to address reserved file: ${file}`);
9189
+ }
9190
+ return join24(resolve7(dir), file);
9191
+ }
9192
+ function findRepoRoot(startDir) {
9193
+ let dir = resolve7(startDir);
9194
+ for (; ; ) {
9195
+ if (existsSync21(join24(dir, ".git"))) return dir;
9196
+ const parent = dirname11(dir);
9197
+ if (parent === dir) return resolve7(startDir, "..");
9198
+ dir = parent;
9199
+ }
9200
+ }
9201
+ function resolveCitationPath(bundleDir, target) {
9202
+ if (isAbsolute2(target)) {
9203
+ throw new Error(
9204
+ `invalid citation target: absolute paths are not allowed (${target})`
9205
+ );
9206
+ }
9207
+ if (!target.endsWith(".md")) {
9208
+ throw new Error(
9209
+ `invalid citation target: only .md files are readable (${target})`
9210
+ );
9211
+ }
9212
+ const root = findRepoRoot(bundleDir);
9213
+ const resolved = resolve7(bundleDir, target);
9214
+ assertContained(root, resolved, target);
9215
+ if (existsSync21(resolved)) {
9216
+ assertContained(realpathSync2(root), realpathSync2(resolved), target);
9217
+ }
9218
+ return resolved;
9219
+ }
9220
+ function assertContained(root, candidate, target) {
9221
+ const rel = relative(root, candidate);
9222
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute2(rel)) {
9223
+ throw new Error(
9224
+ `invalid citation target: resolves outside the repository root (${target})`
9225
+ );
9226
+ }
9227
+ }
9228
+ function loadBundle(dir) {
9229
+ const root = resolve7(dir);
9230
+ let entries;
9231
+ try {
9232
+ entries = readdirSync3(root).filter(
9233
+ (name) => name.endsWith(".md") && !isReservedFile(name)
9234
+ );
9235
+ } catch {
9236
+ throw new Error(`OKF bundle directory not found: ${root}`);
9237
+ }
9238
+ const articles = entries.sort().map((file) => ({
9239
+ file,
9240
+ markdown: readFileSync18(join24(root, file), "utf8")
9241
+ }));
9242
+ const problems = articles.flatMap(
9243
+ ({ file, markdown }) => validateArticle(file, markdown).problems
9244
+ );
9245
+ const catalog = problems.length === 0 ? buildCatalog(articles) : safeCatalog(articles);
9246
+ return { dir: root, articles, catalog, problems };
9247
+ }
9248
+ function safeCatalog(articles) {
9249
+ const entries = [];
9250
+ for (const { file, markdown } of articles) {
9251
+ try {
9252
+ entries.push(toCatalogEntry(file, markdown));
9253
+ } catch {
9254
+ }
9255
+ }
9256
+ return entries.sort((a, b) => a.file.localeCompare(b.file));
9257
+ }
9258
+ function upsertArticle(dir, file, markdown, today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) {
9259
+ const target = resolveArticlePath(dir, file);
9260
+ const validation = validateArticle(file, markdown);
9261
+ if (!validation.ok) return { validation };
9262
+ const root = resolve7(dir);
9263
+ mkdirSync16(root, { recursive: true });
9264
+ let created = true;
9265
+ try {
9266
+ readFileSync18(target, "utf8");
9267
+ created = false;
9268
+ } catch {
9269
+ }
9270
+ writeFileSync13(target, markdown, "utf8");
9271
+ const bundle = loadBundle(root);
9272
+ writeFileSync13(join24(root, "index.md"), renderIndex(bundle.catalog), "utf8");
9273
+ let log = "";
9274
+ try {
9275
+ log = readFileSync18(join24(root, "log.md"), "utf8");
9276
+ } catch {
9277
+ }
9278
+ const entry = bundle.catalog.find((e) => e.file === file);
9279
+ writeFileSync13(
9280
+ join24(root, "log.md"),
9281
+ appendLog(
9282
+ log,
9283
+ today,
9284
+ `**${created ? "Creation" : "Update"}** \u2014 [${entry?.title ?? file}](${file})`
9285
+ ),
9286
+ "utf8"
9287
+ );
9288
+ return { validation, entry, created };
9289
+ }
9290
+ var DEFAULT_BUNDLE_DIR;
9291
+ var init_io = __esm({
9292
+ "src/cli/okf/io.ts"() {
9293
+ "use strict";
9294
+ init_bundle();
9295
+ DEFAULT_BUNDLE_DIR = "docs/okf";
9296
+ }
9297
+ });
9298
+
8992
9299
  // src/cli/commands/mcp.ts
8993
9300
  init_kernel();
8994
- import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
8995
- import { dirname as dirname11, join as join24 } from "path";
9301
+ import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
9302
+ import { dirname as dirname12, join as join25 } from "path";
8996
9303
  import { fileURLToPath as fileURLToPath6 } from "url";
8997
9304
  import {
8998
9305
  RESOURCE_MIME_TYPE,
@@ -9085,7 +9392,8 @@ var COMPANION_SURFACES = [
9085
9392
  "recall",
9086
9393
  "graph",
9087
9394
  "settings",
9088
- "studio"
9395
+ "studio",
9396
+ "okf"
9089
9397
  ];
9090
9398
  function isCompanionSurface(value) {
9091
9399
  return typeof value === "string" && COMPANION_SURFACES.includes(value);
@@ -11636,7 +11944,7 @@ async function readWebLink(url) {
11636
11944
  redirect: "manual",
11637
11945
  signal: controller.signal,
11638
11946
  headers: {
11639
- "User-Agent": "ZAM-Content-Studio/0.11.1"
11947
+ "User-Agent": "ZAM-Content-Studio/0.13.0"
11640
11948
  }
11641
11949
  });
11642
11950
  if (res.status >= 300 && res.status < 400) {
@@ -28964,25 +29272,25 @@ async function observeUiSnapshotViaLLM(db, input) {
28964
29272
  const imageUrls = [];
28965
29273
  const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input.imagePath);
28966
29274
  if (isVideo) {
28967
- const { mkdirSync: mkdirSync16, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
29275
+ const { mkdirSync: mkdirSync17, readdirSync: readdirSync4, rmSync: rmSync4 } = await import("fs");
28968
29276
  const { execSync: execSync5 } = await import("child_process");
28969
29277
  const tempDir = join20(
28970
29278
  tmpdir2(),
28971
29279
  `zam-frames-${randomBytes(4).toString("hex")}`
28972
29280
  );
28973
- mkdirSync16(tempDir, { recursive: true });
29281
+ mkdirSync17(tempDir, { recursive: true });
28974
29282
  try {
28975
29283
  execSync5(
28976
29284
  `ffmpeg -i "${input.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
28977
29285
  { stdio: "ignore" }
28978
29286
  );
28979
- let files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
29287
+ let files = readdirSync4(tempDir).filter((f) => f.endsWith(".png")).sort();
28980
29288
  if (files.length === 0) {
28981
29289
  execSync5(
28982
29290
  `ffmpeg -i "${input.imagePath}" -vframes 1 "${tempDir}/frame_001.png"`,
28983
29291
  { stdio: "ignore" }
28984
29292
  );
28985
- files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
29293
+ files = readdirSync4(tempDir).filter((f) => f.endsWith(".png")).sort();
28986
29294
  }
28987
29295
  const maxFrames = cfg.maxFrames ?? 100;
28988
29296
  let sampledFiles = files;
@@ -30801,8 +31109,8 @@ bridgeCommand.command("start-recording").description("Start screen recording in
30801
31109
  const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
30802
31110
  const defaultExt = platform === "win32" ? ".mkv" : ".mov";
30803
31111
  const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
30804
- const { existsSync: existsSync22, writeFileSync: writeFileSync13, openSync, closeSync } = await import("fs");
30805
- if (existsSync22(statePath)) {
31112
+ const { existsSync: existsSync23, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
31113
+ if (existsSync23(statePath)) {
30806
31114
  jsonOut({
30807
31115
  sessionId,
30808
31116
  started: false,
@@ -30856,7 +31164,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
30856
31164
  }
30857
31165
  child.unref();
30858
31166
  if (child.pid) {
30859
- writeFileSync13(
31167
+ writeFileSync14(
30860
31168
  statePath,
30861
31169
  JSON.stringify({
30862
31170
  pid: child.pid,
@@ -30893,8 +31201,8 @@ bridgeCommand.command("stop-recording").description(
30893
31201
  }
30894
31202
  const sessionId = opts.session;
30895
31203
  const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
30896
- const { existsSync: existsSync22, readFileSync: readFileSync19, rmSync: rmSync4 } = await import("fs");
30897
- if (!existsSync22(statePath)) {
31204
+ const { existsSync: existsSync23, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
31205
+ if (!existsSync23(statePath)) {
30898
31206
  jsonOut({
30899
31207
  sessionId,
30900
31208
  stopped: false,
@@ -30902,7 +31210,7 @@ bridgeCommand.command("stop-recording").description(
30902
31210
  });
30903
31211
  return;
30904
31212
  }
30905
- const state = JSON.parse(readFileSync19(statePath, "utf8"));
31213
+ const state = JSON.parse(readFileSync20(statePath, "utf8"));
30906
31214
  const { pid, outputPath } = state;
30907
31215
  try {
30908
31216
  process.kill(pid, "SIGINT");
@@ -30918,7 +31226,7 @@ bridgeCommand.command("stop-recording").description(
30918
31226
  };
30919
31227
  let attempts = 0;
30920
31228
  while (isProcessRunning(pid) && attempts < 20) {
30921
- await new Promise((resolve7) => setTimeout(resolve7, 250));
31229
+ await new Promise((resolve8) => setTimeout(resolve8, 250));
30922
31230
  attempts++;
30923
31231
  }
30924
31232
  if (isProcessRunning(pid)) {
@@ -30931,7 +31239,7 @@ bridgeCommand.command("stop-recording").description(
30931
31239
  rmSync4(statePath, { force: true });
30932
31240
  } catch {
30933
31241
  }
30934
- if (!existsSync22(outputPath)) {
31242
+ if (!existsSync23(outputPath)) {
30935
31243
  jsonOut({
30936
31244
  sessionId,
30937
31245
  stopped: false,
@@ -33555,16 +33863,17 @@ async function publishUiIntent(app, input = {}, opts = {}) {
33555
33863
  }
33556
33864
 
33557
33865
  // src/cli/commands/mcp.ts
33558
- var __dirname = dirname11(fileURLToPath6(import.meta.url));
33559
- var pkgPath = join24(__dirname, "..", "..", "package.json");
33560
- if (!existsSync21(pkgPath)) {
33561
- pkgPath = join24(__dirname, "..", "..", "..", "package.json");
33866
+ var __dirname = dirname12(fileURLToPath6(import.meta.url));
33867
+ var pkgPath = join25(__dirname, "..", "..", "package.json");
33868
+ if (!existsSync22(pkgPath)) {
33869
+ pkgPath = join25(__dirname, "..", "..", "..", "package.json");
33562
33870
  }
33563
- var pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
33871
+ var pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
33564
33872
  var STUDIO_RESOURCE_URI = "ui://zam/studio";
33565
33873
  var RECALL_RESOURCE_URI = "ui://zam/recall";
33566
33874
  var GRAPH_RESOURCE_URI = "ui://zam/graph";
33567
33875
  var SETTINGS_RESOURCE_URI = "ui://zam/settings";
33876
+ var OKF_RESOURCE_URI = "ui://zam/okf";
33568
33877
  var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
33569
33878
  "list-tokens",
33570
33879
  "personal-card-list",
@@ -33587,13 +33896,13 @@ var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
33587
33896
  function loadPanelHtml(fileName, placeholderTitle) {
33588
33897
  const candidates = [
33589
33898
  // dist/cli/commands/mcp.js → dist/ui/
33590
- join24(__dirname, "..", "..", "ui", fileName),
33899
+ join25(__dirname, "..", "..", "ui", fileName),
33591
33900
  // src/cli/commands/mcp.ts via tsx → <repo>/dist/ui/
33592
- join24(__dirname, "..", "..", "..", "dist", "ui", fileName)
33901
+ join25(__dirname, "..", "..", "..", "dist", "ui", fileName)
33593
33902
  ];
33594
33903
  for (const candidate of candidates) {
33595
- if (existsSync21(candidate)) {
33596
- return readFileSync18(candidate, "utf-8");
33904
+ if (existsSync22(candidate)) {
33905
+ return readFileSync19(candidate, "utf-8");
33597
33906
  }
33598
33907
  }
33599
33908
  return `<!doctype html>
@@ -34358,6 +34667,214 @@ function createMcpServer(db) {
34358
34667
  }
34359
34668
  )
34360
34669
  );
34670
+ const okfBundleDirSchema = z.string().optional().describe("Bundle directory (default docs/okf under the server cwd)");
34671
+ server.registerTool(
34672
+ "zam_okf_catalog",
34673
+ {
34674
+ description: "List the OKF knowledge-base articles (type, title, description, tags, resource URL) plus conformance problems, if any",
34675
+ inputSchema: {
34676
+ bundle_dir: okfBundleDirSchema,
34677
+ include_log: z.boolean().optional().describe(
34678
+ "Also return the raw log.md text (empty string if missing)"
34679
+ )
34680
+ },
34681
+ annotations: {
34682
+ ...commonAnnotations,
34683
+ readOnlyHint: true
34684
+ }
34685
+ },
34686
+ wrapHandler(
34687
+ 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);
34690
+ let log;
34691
+ if (params.include_log) {
34692
+ const { readFileSync: readFileSync20 } = await import("fs");
34693
+ try {
34694
+ log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
34695
+ } catch {
34696
+ log = "";
34697
+ }
34698
+ }
34699
+ return {
34700
+ dir: bundle.dir,
34701
+ articles: bundle.catalog,
34702
+ problems: bundle.problems,
34703
+ ...params.include_log ? { log } : {}
34704
+ };
34705
+ }
34706
+ )
34707
+ );
34708
+ server.registerTool(
34709
+ "zam_okf_read",
34710
+ {
34711
+ description: "Read one OKF knowledge-base article: raw markdown plus parsed frontmatter",
34712
+ inputSchema: {
34713
+ bundle_dir: okfBundleDirSchema,
34714
+ file: z.string().describe("Article file name, e.g. fsrs-scheduling.md")
34715
+ },
34716
+ annotations: {
34717
+ ...commonAnnotations,
34718
+ readOnlyHint: true
34719
+ }
34720
+ },
34721
+ wrapHandler(async (params) => {
34722
+ const { readFileSync: readFileSync20 } = await import("fs");
34723
+ const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34724
+ const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
34725
+ const path = resolveArticlePath2(
34726
+ params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
34727
+ params.file
34728
+ );
34729
+ const markdown = readFileSync20(path, "utf8");
34730
+ const { fields } = parseFrontmatter2(markdown);
34731
+ return { file: params.file, frontmatter: fields, markdown };
34732
+ })
34733
+ );
34734
+ server.registerTool(
34735
+ "zam_okf_upsert",
34736
+ {
34737
+ description: "Create or update an OKF knowledge-base article through the validated write path: checks the frontmatter contract, regenerates index.md, and appends the log.md entry. Never edit bundle files directly.",
34738
+ inputSchema: {
34739
+ bundle_dir: okfBundleDirSchema,
34740
+ file: z.string().describe(
34741
+ "Kebab-case article file name ending in .md (permanent ID)"
34742
+ ),
34743
+ markdown: z.string().describe(
34744
+ "Full article: --- frontmatter (type, title, description, tags, resource, timestamp) then the body"
34745
+ )
34746
+ },
34747
+ annotations: {
34748
+ ...commonAnnotations
34749
+ }
34750
+ },
34751
+ wrapHandler(
34752
+ async (params) => {
34753
+ const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, upsertArticle: upsertArticle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34754
+ const result = upsertArticle2(
34755
+ params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
34756
+ params.file,
34757
+ params.markdown
34758
+ );
34759
+ if (!result.validation.ok) {
34760
+ return { ok: false, problems: result.validation.problems };
34761
+ }
34762
+ return { ok: true, created: result.created, entry: result.entry };
34763
+ }
34764
+ )
34765
+ );
34766
+ server.registerTool(
34767
+ "zam_okf_read_citation",
34768
+ {
34769
+ description: "Read a citation target referenced by an OKF article (e.g. an ADR): read-only, restricted to .md files that resolve inside the repository root \u2014 the target may be outside the bundle but never outside the repo",
34770
+ inputSchema: {
34771
+ bundle_dir: okfBundleDirSchema,
34772
+ target: z.string().describe(
34773
+ "Path to the citation target relative to bundle_dir, e.g. ../adr/2026-07-17-x.md"
34774
+ )
34775
+ },
34776
+ annotations: {
34777
+ ...commonAnnotations,
34778
+ readOnlyHint: true
34779
+ }
34780
+ },
34781
+ wrapHandler(async (params) => {
34782
+ const { readFileSync: readFileSync20 } = await import("fs");
34783
+ 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;
34786
+ const path = resolveCitationPath2(bundleDir, params.target);
34787
+ const content = readFileSync20(path, "utf8");
34788
+ const root = findRepoRoot2(bundleDir);
34789
+ const repoRelativePath = relative2(root, path).split(sep2).join("/");
34790
+ return { target: params.target, path: repoRelativePath, content };
34791
+ })
34792
+ );
34793
+ registerAppTool(
34794
+ server,
34795
+ "zam_okf_visualize",
34796
+ {
34797
+ title: "Open ZAM OKF visualizer",
34798
+ description: "Open the ZAM OKF knowledge-base visualizer: articles by type with search, a markdown reader with inline-expandable cited ADRs, a link graph, and the log. Pass `bundle_dir` to browse a bundle other than the default (docs/okf under the server cwd).",
34799
+ inputSchema: {
34800
+ bundle_dir: okfBundleDirSchema
34801
+ },
34802
+ annotations: {
34803
+ ...commonAnnotations,
34804
+ readOnlyHint: true
34805
+ },
34806
+ _meta: {
34807
+ ui: { resourceUri: OKF_RESOURCE_URI }
34808
+ }
34809
+ },
34810
+ wrapHandler(async ({ bundle_dir }) => {
34811
+ const opening = await resolveOpeningCompanionContextSafely(
34812
+ db,
34813
+ "okf",
34814
+ void 0,
34815
+ getNativeClientInfo(),
34816
+ { clientSamplingCapable: getClientSamplingCapable() }
34817
+ );
34818
+ await publishUiIntent("okf", { bundle_dir });
34819
+ const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34820
+ const { resolve: resolve8 } = await import("path");
34821
+ const requestedDir = bundle_dir ?? DEFAULT_BUNDLE_DIR2;
34822
+ let resolvedBundleDir = resolve8(requestedDir);
34823
+ let catalog = [];
34824
+ let problems = [];
34825
+ let log = "";
34826
+ let okfVersion = null;
34827
+ try {
34828
+ const bundle = loadBundle2(requestedDir);
34829
+ resolvedBundleDir = bundle.dir;
34830
+ catalog = bundle.catalog;
34831
+ problems = bundle.problems;
34832
+ const { readFileSync: readFileSync20 } = await import("fs");
34833
+ try {
34834
+ log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
34835
+ } catch {
34836
+ log = "";
34837
+ }
34838
+ try {
34839
+ const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
34840
+ const indexRaw = readFileSync20(join25(bundle.dir, "index.md"), "utf8");
34841
+ const { fields } = parseFrontmatter2(indexRaw);
34842
+ okfVersion = typeof fields.okf_version === "string" ? fields.okf_version : null;
34843
+ } catch {
34844
+ okfVersion = null;
34845
+ }
34846
+ } catch (error) {
34847
+ problems = [error instanceof Error ? error.message : String(error)];
34848
+ }
34849
+ return {
34850
+ okf: "zam",
34851
+ version: pkg.version,
34852
+ user: opening.context.user.currentId ?? null,
34853
+ bundleDir: resolvedBundleDir,
34854
+ okfVersion,
34855
+ catalog,
34856
+ problems,
34857
+ log,
34858
+ companionContext: opening.context,
34859
+ ...opening.degraded ? { companionContextDegraded: true } : {}
34860
+ };
34861
+ })
34862
+ );
34863
+ registerAppResource(
34864
+ server,
34865
+ "zam-okf",
34866
+ OKF_RESOURCE_URI,
34867
+ { mimeType: RESOURCE_MIME_TYPE },
34868
+ async () => ({
34869
+ contents: [
34870
+ {
34871
+ uri: OKF_RESOURCE_URI,
34872
+ mimeType: RESOURCE_MIME_TYPE,
34873
+ text: loadPanelHtml("okf-panel.html", "ZAM OKF")
34874
+ }
34875
+ ]
34876
+ })
34877
+ );
34361
34878
  return server;
34362
34879
  }
34363
34880
  async function runMcpServer() {