zam-core 0.12.0 → 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.
@@ -9165,12 +9165,21 @@ var init_bundle = __esm({
9165
9165
  var io_exports = {};
9166
9166
  __export(io_exports, {
9167
9167
  DEFAULT_BUNDLE_DIR: () => DEFAULT_BUNDLE_DIR,
9168
+ findRepoRoot: () => findRepoRoot,
9168
9169
  loadBundle: () => loadBundle,
9169
9170
  resolveArticlePath: () => resolveArticlePath,
9171
+ resolveCitationPath: () => resolveCitationPath,
9170
9172
  upsertArticle: () => upsertArticle
9171
9173
  });
9172
- import { mkdirSync as mkdirSync16, readdirSync as readdirSync3, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "fs";
9173
- import { join as join24, resolve as resolve7 } from "path";
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";
9174
9183
  function resolveArticlePath(dir, file) {
9175
9184
  if (file.includes("/") || file.includes("\\") || file.includes("..")) {
9176
9185
  throw new Error(`invalid article file name: ${file}`);
@@ -9180,6 +9189,42 @@ function resolveArticlePath(dir, file) {
9180
9189
  }
9181
9190
  return join24(resolve7(dir), file);
9182
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
+ }
9183
9228
  function loadBundle(dir) {
9184
9229
  const root = resolve7(dir);
9185
9230
  let entries;
@@ -9253,8 +9298,8 @@ var init_io = __esm({
9253
9298
 
9254
9299
  // src/cli/commands/mcp.ts
9255
9300
  init_kernel();
9256
- import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
9257
- import { dirname as dirname11, join as join25 } from "path";
9301
+ import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
9302
+ import { dirname as dirname12, join as join25 } from "path";
9258
9303
  import { fileURLToPath as fileURLToPath6 } from "url";
9259
9304
  import {
9260
9305
  RESOURCE_MIME_TYPE,
@@ -9347,7 +9392,8 @@ var COMPANION_SURFACES = [
9347
9392
  "recall",
9348
9393
  "graph",
9349
9394
  "settings",
9350
- "studio"
9395
+ "studio",
9396
+ "okf"
9351
9397
  ];
9352
9398
  function isCompanionSurface(value) {
9353
9399
  return typeof value === "string" && COMPANION_SURFACES.includes(value);
@@ -11898,7 +11944,7 @@ async function readWebLink(url) {
11898
11944
  redirect: "manual",
11899
11945
  signal: controller.signal,
11900
11946
  headers: {
11901
- "User-Agent": "ZAM-Content-Studio/0.12.0"
11947
+ "User-Agent": "ZAM-Content-Studio/0.13.0"
11902
11948
  }
11903
11949
  });
11904
11950
  if (res.status >= 300 && res.status < 400) {
@@ -31063,8 +31109,8 @@ bridgeCommand.command("start-recording").description("Start screen recording in
31063
31109
  const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
31064
31110
  const defaultExt = platform === "win32" ? ".mkv" : ".mov";
31065
31111
  const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
31066
- const { existsSync: existsSync22, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
31067
- if (existsSync22(statePath)) {
31112
+ const { existsSync: existsSync23, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
31113
+ if (existsSync23(statePath)) {
31068
31114
  jsonOut({
31069
31115
  sessionId,
31070
31116
  started: false,
@@ -31155,8 +31201,8 @@ bridgeCommand.command("stop-recording").description(
31155
31201
  }
31156
31202
  const sessionId = opts.session;
31157
31203
  const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
31158
- const { existsSync: existsSync22, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
31159
- if (!existsSync22(statePath)) {
31204
+ const { existsSync: existsSync23, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
31205
+ if (!existsSync23(statePath)) {
31160
31206
  jsonOut({
31161
31207
  sessionId,
31162
31208
  stopped: false,
@@ -31193,7 +31239,7 @@ bridgeCommand.command("stop-recording").description(
31193
31239
  rmSync4(statePath, { force: true });
31194
31240
  } catch {
31195
31241
  }
31196
- if (!existsSync22(outputPath)) {
31242
+ if (!existsSync23(outputPath)) {
31197
31243
  jsonOut({
31198
31244
  sessionId,
31199
31245
  stopped: false,
@@ -33817,9 +33863,9 @@ async function publishUiIntent(app, input = {}, opts = {}) {
33817
33863
  }
33818
33864
 
33819
33865
  // src/cli/commands/mcp.ts
33820
- var __dirname = dirname11(fileURLToPath6(import.meta.url));
33866
+ var __dirname = dirname12(fileURLToPath6(import.meta.url));
33821
33867
  var pkgPath = join25(__dirname, "..", "..", "package.json");
33822
- if (!existsSync21(pkgPath)) {
33868
+ if (!existsSync22(pkgPath)) {
33823
33869
  pkgPath = join25(__dirname, "..", "..", "..", "package.json");
33824
33870
  }
33825
33871
  var pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
@@ -33827,6 +33873,7 @@ var STUDIO_RESOURCE_URI = "ui://zam/studio";
33827
33873
  var RECALL_RESOURCE_URI = "ui://zam/recall";
33828
33874
  var GRAPH_RESOURCE_URI = "ui://zam/graph";
33829
33875
  var SETTINGS_RESOURCE_URI = "ui://zam/settings";
33876
+ var OKF_RESOURCE_URI = "ui://zam/okf";
33830
33877
  var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
33831
33878
  "list-tokens",
33832
33879
  "personal-card-list",
@@ -33854,7 +33901,7 @@ function loadPanelHtml(fileName, placeholderTitle) {
33854
33901
  join25(__dirname, "..", "..", "..", "dist", "ui", fileName)
33855
33902
  ];
33856
33903
  for (const candidate of candidates) {
33857
- if (existsSync21(candidate)) {
33904
+ if (existsSync22(candidate)) {
33858
33905
  return readFileSync19(candidate, "utf-8");
33859
33906
  }
33860
33907
  }
@@ -34626,22 +34673,37 @@ function createMcpServer(db) {
34626
34673
  {
34627
34674
  description: "List the OKF knowledge-base articles (type, title, description, tags, resource URL) plus conformance problems, if any",
34628
34675
  inputSchema: {
34629
- bundle_dir: okfBundleDirSchema
34676
+ bundle_dir: okfBundleDirSchema,
34677
+ include_log: z.boolean().optional().describe(
34678
+ "Also return the raw log.md text (empty string if missing)"
34679
+ )
34630
34680
  },
34631
34681
  annotations: {
34632
34682
  ...commonAnnotations,
34633
34683
  readOnlyHint: true
34634
34684
  }
34635
34685
  },
34636
- wrapHandler(async (params) => {
34637
- const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34638
- const bundle = loadBundle2(params.bundle_dir ?? DEFAULT_BUNDLE_DIR2);
34639
- return {
34640
- dir: bundle.dir,
34641
- articles: bundle.catalog,
34642
- problems: bundle.problems
34643
- };
34644
- })
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
+ )
34645
34707
  );
34646
34708
  server.registerTool(
34647
34709
  "zam_okf_read",
@@ -34701,6 +34763,118 @@ function createMcpServer(db) {
34701
34763
  }
34702
34764
  )
34703
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
+ );
34704
34878
  return server;
34705
34879
  }
34706
34880
  async function runMcpServer() {