skillwiki 0.10.73 → 0.10.76

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.
@@ -3331,7 +3331,7 @@ function buildCliSurface() {
3331
3331
  program.command("lint").option("--days <n>").option("--lines <n>").option("--log-threshold <n>").option("--fix").option("--only <bucket>").option("--summary").option("--examples <n>").option("--wiki <name>");
3332
3332
  program.command("config");
3333
3333
  program.command("health").option("--wiki <name>").option("--sync <mode>").option("--no-fail").option("--out <path>").option("--examples <n>");
3334
- program.command("doctor").option("--check-snapshotter");
3334
+ program.command("doctor").option("--check-snapshotter").option("--check-mcp");
3335
3335
  program.command("status").option("--wiki <name>");
3336
3336
  program.command("archive").option("--wiki <name>").option("--cascade").option("--apply").option("--approve <token>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>");
3337
3337
  program.command("remove").option("--wiki <name>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>").option("--reason <text>");
@@ -3360,6 +3360,7 @@ function buildCliSurface() {
3360
3360
  program.command("project-page");
3361
3361
  program.command("write-preflight").option("--command <name>").option("--dirty-threshold <n>").option("--skip-dirty").option("--prior-artifact-file <path>").option("--prior-artifact-text <text>").option("--consecutive-no-decision <n>").option("--no-decision-threshold <n>").option("--human-allow").option("--mission-kind <kind>").option("--skip-mission").option("--project <slug>").option("--capture-day <date>").option("--capture-budget <n>").option("--severity <level>").option("--skip-budget").option("--checks <list>").option("--wiki <name>");
3362
3362
  program.command("mcp");
3363
+ program.command("mcp-auth");
3363
3364
  const graphCmd = program.commands.find((c) => c.name() === "graph");
3364
3365
  graphCmd.command("build").option("--out <path>").option("--wiki <name>");
3365
3366
  const vectorsCmd = program.commands.find((c) => c.name() === "vectors");
@@ -3436,6 +3437,8 @@ function buildCliSurface() {
3436
3437
  fleetCmd.command("validate");
3437
3438
  fleetCmd.command("context").option("--file <path>").option("--host-id <id>");
3438
3439
  fleetCmd.command("health").option("--file <path>").option("--host-id <id>").option("--json");
3440
+ const mcpAuth = program.commands.find((c) => c.name() === "mcp-auth");
3441
+ mcpAuth.command("issue-host").requiredOption("--host-id <id>").option("--map <path>").option("--write");
3439
3442
  const surface = /* @__PURE__ */ new Map();
3440
3443
  const rootFlags = new Set(program.options.map((o) => o.long ?? o.short).filter((f) => f != null));
3441
3444
  function walk(cmd, prefix, parentFlags) {
@@ -40,7 +40,7 @@ import {
40
40
  snapshotterAliasForLocalHost,
41
41
  toUndirectedWeighted,
42
42
  writeDotenv
43
- } from "./chunk-R5SDVKHS.js";
43
+ } from "./chunk-5XT6MBWA.js";
44
44
  import {
45
45
  atomicWriteText,
46
46
  prepareTypedPage
@@ -1079,7 +1079,7 @@ async function runConfigPath(input) {
1079
1079
  }
1080
1080
 
1081
1081
  // src/doctor/runner.ts
1082
- import { existsSync as existsSync17 } from "fs";
1082
+ import { existsSync as existsSync18 } from "fs";
1083
1083
 
1084
1084
  // src/doctor/probes/environment.ts
1085
1085
  import { existsSync as existsSync4, lstatSync, readlinkSync } from "fs";
@@ -3981,6 +3981,204 @@ var dsStoreNoiseProbe = {
3981
3981
  }
3982
3982
  };
3983
3983
 
3984
+ // src/doctor/probes/mcp-handshake.ts
3985
+ import { existsSync as existsSync17 } from "fs";
3986
+ import { join as join20 } from "path";
3987
+ var DEFAULT_MCP_URL = "https://wiki.karldigi.dev/mcp";
3988
+ var URL_ENV = "SKILLWIKI_MCP_URL";
3989
+ var TOKEN_ENV = "SKILLWIKI_MCP_TOKEN";
3990
+ function mcpEnv(ctx) {
3991
+ return ctx.input.env ?? process.env;
3992
+ }
3993
+ function checkMcpUrl(ctx) {
3994
+ const env = mcpEnv(ctx);
3995
+ const override = env[URL_ENV];
3996
+ if (typeof override === "string" && override.trim().length > 0) {
3997
+ return check("pass", "mcp_url_configured", "HTTP MCP URL", "configured via env override");
3998
+ }
3999
+ return check(
4000
+ "pass",
4001
+ "mcp_url_configured",
4002
+ "HTTP MCP URL",
4003
+ `plugin default ${DEFAULT_MCP_URL}`
4004
+ );
4005
+ }
4006
+ function checkMcpCredential(ctx) {
4007
+ const env = mcpEnv(ctx);
4008
+ const value = env[TOKEN_ENV];
4009
+ if (typeof value === "string" && value.trim().length > 0) {
4010
+ return check("pass", "mcp_credential_present", "HTTP MCP auth", "present");
4011
+ }
4012
+ return check(
4013
+ "warn",
4014
+ "mcp_credential_present",
4015
+ "HTTP MCP auth",
4016
+ "not set \u2014 set in process env or host Configure (value not shown)"
4017
+ );
4018
+ }
4019
+ function checkFrozenLeafWritePath(ctx) {
4020
+ const vault = ctx.resolvedPath;
4021
+ if (!vault || !existsSync17(join20(vault, ".WIKI_GIT_FROZEN"))) {
4022
+ return check(
4023
+ "pass",
4024
+ "mcp_frozen_leaf_write_path",
4025
+ "HTTP MCP write path",
4026
+ "not a frozen leaf"
4027
+ );
4028
+ }
4029
+ return check(
4030
+ "pass",
4031
+ "mcp_frozen_leaf_write_path",
4032
+ "HTTP MCP write path",
4033
+ "frozen leaf \u2014 writes must use HTTP MCP, not vault git or raw/transcripts/"
4034
+ );
4035
+ }
4036
+ var HANDSHAKE_ID = "mcp_handshake";
4037
+ var HANDSHAKE_LABEL = "HTTP MCP handshake";
4038
+ var WORKITEM_TOOL = "wiki_workitem_write";
4039
+ function parseCoreSemver(version) {
4040
+ const m = version.match(/^(\d+)\.(\d+)\.(\d+)/);
4041
+ if (!m) return null;
4042
+ return { major: Number.parseInt(m[1], 10), minor: Number.parseInt(m[2], 10), patch: Number.parseInt(m[3], 10) };
4043
+ }
4044
+ function versionLessThan(a, b) {
4045
+ const pa = parseCoreSemver(a);
4046
+ const pb = parseCoreSemver(b);
4047
+ if (!pa || !pb) return false;
4048
+ if (pa.major !== pb.major) return pa.major < pb.major;
4049
+ if (pa.minor !== pb.minor) return pa.minor < pb.minor;
4050
+ return pa.patch < pb.patch;
4051
+ }
4052
+ function redactSecret(detail, secret) {
4053
+ if (!secret || secret.length === 0 || !detail.includes(secret)) return detail;
4054
+ return detail.split(secret).join("[REDACTED]");
4055
+ }
4056
+ function handshakeRow(status, detail, secret) {
4057
+ return check(status, HANDSHAKE_ID, HANDSHAKE_LABEL, redactSecret(detail, secret));
4058
+ }
4059
+ function parseMcpBody(text) {
4060
+ const trimmed = text.trim();
4061
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
4062
+ return JSON.parse(trimmed);
4063
+ }
4064
+ for (const line of trimmed.split(/\r?\n/)) {
4065
+ const prefix = line.startsWith("data:") ? line.slice(5).trim() : "";
4066
+ if (prefix.startsWith("{")) return JSON.parse(prefix);
4067
+ }
4068
+ throw new Error("non-JSON MCP body");
4069
+ }
4070
+ function jsonRpcResult(body) {
4071
+ if (typeof body !== "object" || body === null) {
4072
+ throw new Error("invalid JSON-RPC body");
4073
+ }
4074
+ const rec = body;
4075
+ if (rec.error) {
4076
+ throw new Error(typeof rec.error.message === "string" ? rec.error.message : "JSON-RPC error");
4077
+ }
4078
+ if (typeof rec.result !== "object" || rec.result === null) {
4079
+ throw new Error("missing JSON-RPC result");
4080
+ }
4081
+ return rec.result;
4082
+ }
4083
+ async function mcpJsonRpc(fetchFn, url, token, method, id, params, sessionId) {
4084
+ const headers = {
4085
+ Authorization: `Bearer ${token}`,
4086
+ "Content-Type": "application/json",
4087
+ Accept: "application/json, text/event-stream"
4088
+ };
4089
+ if (sessionId) headers["mcp-session-id"] = sessionId;
4090
+ const res = await fetchFn(url, {
4091
+ method: "POST",
4092
+ headers,
4093
+ body: JSON.stringify({ jsonrpc: "2.0", id, method, params })
4094
+ });
4095
+ if (!res.ok) {
4096
+ throw new Error(`HTTP ${res.status}`);
4097
+ }
4098
+ const nextSession = res.headers.get("mcp-session-id") ?? sessionId;
4099
+ const result = jsonRpcResult(parseMcpBody(await res.text()));
4100
+ return { result, sessionId: nextSession ?? void 0 };
4101
+ }
4102
+ function toolNames(result) {
4103
+ const tools = result.tools;
4104
+ if (!Array.isArray(tools)) return [];
4105
+ return tools.map((t) => typeof t === "object" && t !== null && typeof t.name === "string" ? t.name : "").filter((name) => name.length > 0);
4106
+ }
4107
+ async function checkMcpHandshake(ctx) {
4108
+ if (!ctx.input.checkMcp) {
4109
+ return handshakeRow("pass", "not requested \u2014 check skipped");
4110
+ }
4111
+ const env = mcpEnv(ctx);
4112
+ const url = typeof env[URL_ENV] === "string" && env[URL_ENV].trim().length > 0 ? env[URL_ENV].trim() : DEFAULT_MCP_URL;
4113
+ const token = typeof env[TOKEN_ENV] === "string" ? env[TOKEN_ENV] : "";
4114
+ if (token.trim().length === 0) {
4115
+ return handshakeRow("error", "auth not set \u2014 handshake not attempted (value not shown)");
4116
+ }
4117
+ const fetchFn = ctx.input.mcpFetch ?? globalThis.fetch;
4118
+ const shipped = ctx.input.currentVersion;
4119
+ try {
4120
+ const init = await mcpJsonRpc(
4121
+ fetchFn,
4122
+ url,
4123
+ token,
4124
+ "initialize",
4125
+ 1,
4126
+ {
4127
+ protocolVersion: "2025-11-25",
4128
+ capabilities: {},
4129
+ clientInfo: { name: "skillwiki-doctor", version: shipped }
4130
+ }
4131
+ );
4132
+ const serverInfo = init.result.serverInfo;
4133
+ const advertised = typeof serverInfo === "object" && serverInfo !== null && typeof serverInfo.version === "string" ? serverInfo.version : "";
4134
+ if (!advertised) {
4135
+ return handshakeRow("error", "handshake failed \u2014 missing serverInfo.version", token);
4136
+ }
4137
+ const listed = await mcpJsonRpc(
4138
+ fetchFn,
4139
+ url,
4140
+ token,
4141
+ "tools/list",
4142
+ 2,
4143
+ {},
4144
+ init.sessionId
4145
+ );
4146
+ const tools = toolNames(listed.result);
4147
+ const frozen = Boolean(ctx.resolvedPath && existsSync17(join20(ctx.resolvedPath, ".WIKI_GIT_FROZEN")));
4148
+ if (frozen && !tools.includes(WORKITEM_TOOL)) {
4149
+ return handshakeRow(
4150
+ "error",
4151
+ `captures-only tool list (${tools.length} tools, missing ${WORKITEM_TOOL}) on frozen leaf \u2014 advertised ${advertised}`,
4152
+ token
4153
+ );
4154
+ }
4155
+ if (versionLessThan(advertised, shipped)) {
4156
+ return handshakeRow(
4157
+ "warn",
4158
+ `advertised ${advertised} lags shipped ${shipped} (${tools.length} tools)`,
4159
+ token
4160
+ );
4161
+ }
4162
+ return handshakeRow("pass", `advertised ${advertised}, ${tools.length} tools`, token);
4163
+ } catch (e) {
4164
+ const raw = e instanceof Error ? e.message : String(e);
4165
+ const safe = redactSecret(raw, token);
4166
+ return handshakeRow("error", `handshake failed \u2014 ${safe}`, token);
4167
+ }
4168
+ }
4169
+ var mcpHandshakeProbe = {
4170
+ id: "mcp",
4171
+ label: "HTTP MCP",
4172
+ async run(ctx) {
4173
+ return [
4174
+ checkMcpUrl(ctx),
4175
+ checkMcpCredential(ctx),
4176
+ checkFrozenLeafWritePath(ctx),
4177
+ await checkMcpHandshake(ctx)
4178
+ ];
4179
+ }
4180
+ };
4181
+
3984
4182
  // src/doctor/probes/index.ts
3985
4183
  var DOCTOR_PROBES = [
3986
4184
  environmentProbe,
@@ -3994,7 +4192,8 @@ var DOCTOR_PROBES = [
3994
4192
  metricsProbe,
3995
4193
  fuseStalenessProbe,
3996
4194
  activationMarkerProbe,
3997
- dsStoreNoiseProbe
4195
+ dsStoreNoiseProbe,
4196
+ mcpHandshakeProbe
3998
4197
  ];
3999
4198
 
4000
4199
  // src/doctor/runner.ts
@@ -4005,7 +4204,7 @@ function resolveSnapshotGitWorktree(home) {
4005
4204
  const configured = resolveConfiguredSnapshotWorktree(home);
4006
4205
  if (configured) return configured;
4007
4206
  const defaultPath = "/root/wiki-git";
4008
- return existsSync17(defaultPath) ? defaultPath : void 0;
4207
+ return existsSync18(defaultPath) ? defaultPath : void 0;
4009
4208
  }
4010
4209
  var DoctorRunner = class {
4011
4210
  probes;
@@ -4101,8 +4300,8 @@ function readCliPackageJson(baseUrl = import.meta.url) {
4101
4300
 
4102
4301
  // src/utils/vault-write-gates.ts
4103
4302
  import { execFileSync } from "child_process";
4104
- import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
4105
- import { join as join20, relative as relative2 } from "path";
4303
+ import { existsSync as existsSync19, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
4304
+ import { join as join21, relative as relative2 } from "path";
4106
4305
  var DEFAULT_DIRTY_VOLUME_THRESHOLD = 50;
4107
4306
  var DEFAULT_CAPTURE_BUDGET = 20;
4108
4307
  var DEFAULT_NO_DECISION_STREAK = 3;
@@ -4164,7 +4363,7 @@ function measureDirtyVolume(vault) {
4164
4363
  is_git_repo: false,
4165
4364
  ...extra
4166
4365
  });
4167
- if (!existsSync18(vault) || !statSync3(vault).isDirectory()) {
4366
+ if (!existsSync19(vault) || !statSync3(vault).isDirectory()) {
4168
4367
  return empty();
4169
4368
  }
4170
4369
  const gitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
@@ -4202,8 +4401,8 @@ function measureDirtyVolume(vault) {
4202
4401
  if (!rel) continue;
4203
4402
  if (code === "??") {
4204
4403
  untracked += 1;
4205
- const abs = join20(vault, rel);
4206
- if (existsSync18(abs) && statSync3(abs).isDirectory()) {
4404
+ const abs = join21(vault, rel);
4405
+ if (existsSync19(abs) && statSync3(abs).isDirectory()) {
4207
4406
  const files = listFilesRecursive(abs);
4208
4407
  expanded += files.length;
4209
4408
  addBucket(rel, files.length);
@@ -4239,7 +4438,7 @@ function listFilesRecursive(dir) {
4239
4438
  }
4240
4439
  for (const name of entries) {
4241
4440
  if (name === ".git") continue;
4242
- const p = join20(dir, name);
4441
+ const p = join21(dir, name);
4243
4442
  try {
4244
4443
  const st = statSync3(p);
4245
4444
  if (st.isDirectory()) out.push(...listFilesRecursive(p));
@@ -4334,7 +4533,7 @@ var CAPTURE_HYGIENE_CONTRACT = `After each productive cycle (or when daily captu
4334
4533
  function listProjectDayCaptures(vault, project, day) {
4335
4534
  const found = [];
4336
4535
  const slug = project.replace(/^\[\[/, "").replace(/\]\]$/, "").trim();
4337
- if (!slug || !existsSync18(vault)) return found;
4536
+ if (!slug || !existsSync19(vault)) return found;
4338
4537
  const consider = (abs, rel) => {
4339
4538
  if (!rel.endsWith(".md")) return;
4340
4539
  const base = rel.split(/[/\\]/).pop() ?? "";
@@ -4362,7 +4561,7 @@ function listProjectDayCaptures(vault, project, day) {
4362
4561
  }
4363
4562
  };
4364
4563
  const walk = (dir, relBase) => {
4365
- if (!existsSync18(dir)) return;
4564
+ if (!existsSync19(dir)) return;
4366
4565
  let entries;
4367
4566
  try {
4368
4567
  entries = readdirSync7(dir);
@@ -4370,7 +4569,7 @@ function listProjectDayCaptures(vault, project, day) {
4370
4569
  return;
4371
4570
  }
4372
4571
  for (const name of entries) {
4373
- const abs = join20(dir, name);
4572
+ const abs = join21(dir, name);
4374
4573
  const rel = relBase ? `${relBase}/${name}` : name;
4375
4574
  try {
4376
4575
  const st = statSync3(abs);
@@ -4380,16 +4579,16 @@ function listProjectDayCaptures(vault, project, day) {
4380
4579
  }
4381
4580
  }
4382
4581
  };
4383
- walk(join20(vault, "raw", "transcripts"), "raw/transcripts");
4384
- walk(join20(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
4385
- walk(join20(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
4386
- const workRoot = join20(vault, "projects", slug, "work");
4387
- if (existsSync18(workRoot)) {
4582
+ walk(join21(vault, "raw", "transcripts"), "raw/transcripts");
4583
+ walk(join21(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
4584
+ walk(join21(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
4585
+ const workRoot = join21(vault, "projects", slug, "work");
4586
+ if (existsSync19(workRoot)) {
4388
4587
  try {
4389
4588
  for (const name of readdirSync7(workRoot)) {
4390
4589
  if (!name.startsWith(day)) continue;
4391
4590
  if (!/investigate|pilot-q|research|cycle|dev-loop/.test(name)) continue;
4392
- const abs = join20(workRoot, name);
4591
+ const abs = join21(workRoot, name);
4393
4592
  if (statSync3(abs).isDirectory()) {
4394
4593
  for (const f of listFilesRecursive(abs)) {
4395
4594
  const rel = relative2(vault, f).replace(/\\/g, "/");
@@ -4434,7 +4633,7 @@ function evaluateCaptureBudget(input) {
4434
4633
  return { allowed: true, reason: "under_budget", report };
4435
4634
  }
4436
4635
  function runWritePreflight(input) {
4437
- if (!input.vault || !existsSync18(input.vault)) {
4636
+ if (!input.vault || !existsSync19(input.vault)) {
4438
4637
  return err(GateError.VAULT_PATH_INVALID, { path: input.vault });
4439
4638
  }
4440
4639
  const want = new Set(input.checks ?? ["all"]);
@@ -4494,8 +4693,8 @@ function runWritePreflight(input) {
4494
4693
 
4495
4694
  // src/commands/observe.ts
4496
4695
  import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
4497
- import { existsSync as existsSync19, statSync as statSync4 } from "fs";
4498
- import { join as join21 } from "path";
4696
+ import { existsSync as existsSync20, statSync as statSync4 } from "fs";
4697
+ import { join as join22 } from "path";
4499
4698
  import { createHash as createHash2 } from "crypto";
4500
4699
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
4501
4700
  function slugify(text) {
@@ -4518,7 +4717,7 @@ async function runObserve(input) {
4518
4717
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
4519
4718
  };
4520
4719
  }
4521
- if (!existsSync19(input.vault) || !statSync4(input.vault).isDirectory()) {
4720
+ if (!existsSync20(input.vault) || !statSync4(input.vault).isDirectory()) {
4522
4721
  return {
4523
4722
  exitCode: ExitCode.VAULT_PATH_INVALID,
4524
4723
  result: err("VAULT_PATH_INVALID", { path: input.vault })
@@ -4544,7 +4743,7 @@ async function runObserve(input) {
4544
4743
  };
4545
4744
  }
4546
4745
  }
4547
- const transcriptsDir = join21(input.vault, "raw", "transcripts");
4746
+ const transcriptsDir = join22(input.vault, "raw", "transcripts");
4548
4747
  try {
4549
4748
  await mkdir3(transcriptsDir, { recursive: true });
4550
4749
  } catch {
@@ -4556,7 +4755,7 @@ async function runObserve(input) {
4556
4755
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4557
4756
  const slug = slugify(input.text);
4558
4757
  const fileName = `${today}-observation-${slug}.md`;
4559
- const filePath = join21(transcriptsDir, fileName);
4758
+ const filePath = join22(transcriptsDir, fileName);
4560
4759
  const body = `
4561
4760
  ${input.text.trim()}
4562
4761
  `;
@@ -4598,7 +4797,7 @@ ${input.text.trim()}
4598
4797
  // src/commands/memory.ts
4599
4798
  import { createHash as createHash3 } from "crypto";
4600
4799
  import { mkdir as mkdir4, readFile as readFile7, readdir as readdir2, stat as stat2, writeFile as writeFile3 } from "fs/promises";
4601
- import { basename as basename2, extname, join as join22, relative as relative3, sep as sep2 } from "path";
4800
+ import { basename as basename2, extname, join as join23, relative as relative3, sep as sep2 } from "path";
4602
4801
 
4603
4802
  // src/utils/memory-authority.ts
4604
4803
  var TIER_RANK = {
@@ -4717,8 +4916,8 @@ async function runMemoryIndex(input) {
4717
4916
  }
4718
4917
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
4719
4918
  const relCachePath = memoryCacheRelPath(input.project);
4720
- const absCachePath = join22(input.vault, relCachePath);
4721
- await mkdir4(join22(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
4919
+ const absCachePath = join23(input.vault, relCachePath);
4920
+ await mkdir4(join23(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
4722
4921
  await writeFile3(absCachePath, `${JSON.stringify({
4723
4922
  generated_at: generatedAt,
4724
4923
  project: input.project,
@@ -4910,7 +5109,7 @@ async function buildMemoryIndexState(pages, project) {
4910
5109
  }
4911
5110
  async function checkMemoryIndex(vault, project, current) {
4912
5111
  const relCachePath = memoryCacheRelPath(project);
4913
- const cacheText = await readIfExists(join22(vault, relCachePath));
5112
+ const cacheText = await readIfExists(join23(vault, relCachePath));
4914
5113
  if (!cacheText) {
4915
5114
  return {
4916
5115
  ok: true,
@@ -5319,7 +5518,7 @@ async function walkImportFiles(dir, out) {
5319
5518
  const entries = await readdir2(dir, { withFileTypes: true });
5320
5519
  for (const entry of entries) {
5321
5520
  if (entry.name === ".git" || entry.name === "node_modules") continue;
5322
- const path = join22(dir, entry.name);
5521
+ const path = join23(dir, entry.name);
5323
5522
  if (entry.isDirectory()) {
5324
5523
  await walkImportFiles(path, out);
5325
5524
  } else if (entry.isFile() && isImportCandidate(path)) {
@@ -5386,8 +5585,8 @@ async function writeImportCapture(vault, entry, today) {
5386
5585
  const content = hiddenString(entry, "__content");
5387
5586
  const project = hiddenString(entry, "__project");
5388
5587
  const relPath = await availableImportPath(vault, entry.proposed_path);
5389
- const absPath = join22(vault, relPath);
5390
- await mkdir4(join22(vault, "raw", "transcripts"), { recursive: true });
5588
+ const absPath = join23(vault, relPath);
5589
+ await mkdir4(join23(vault, "raw", "transcripts"), { recursive: true });
5391
5590
  await writeFile3(absPath, renderImportCapture(entry, content, project, today), "utf8");
5392
5591
  const validation = await runValidate({ file: absPath });
5393
5592
  return {
@@ -5403,7 +5602,7 @@ async function availableImportPath(vault, proposed) {
5403
5602
  const stem = proposed.slice(0, -ext.length);
5404
5603
  let candidate = proposed;
5405
5604
  let i = 2;
5406
- while (await readIfExists(join22(vault, candidate))) {
5605
+ while (await readIfExists(join23(vault, candidate))) {
5407
5606
  candidate = `${stem}-${i}${ext}`;
5408
5607
  i++;
5409
5608
  }
@@ -5588,10 +5787,10 @@ function memoryCacheRelPath(project) {
5588
5787
  }
5589
5788
  async function readMemoryCache(vault, project) {
5590
5789
  if (project) {
5591
- const projectCache = await readIfExists(join22(vault, memoryCacheRelPath(project)));
5790
+ const projectCache = await readIfExists(join23(vault, memoryCacheRelPath(project)));
5592
5791
  if (projectCache) return projectCache;
5593
5792
  }
5594
- return readIfExists(join22(vault, ".skillwiki", "memory-topics.json"));
5793
+ return readIfExists(join23(vault, ".skillwiki", "memory-topics.json"));
5595
5794
  }
5596
5795
  function dedupePages(pages) {
5597
5796
  const seen = /* @__PURE__ */ new Set();
@@ -5683,7 +5882,7 @@ function slugify2(value) {
5683
5882
 
5684
5883
  // src/commands/query.ts
5685
5884
  import { readFile as readFile9, stat as stat4 } from "fs/promises";
5686
- import { join as join24 } from "path";
5885
+ import { join as join25 } from "path";
5687
5886
 
5688
5887
  // src/utils/rrf.ts
5689
5888
  var RRF_K = 60;
@@ -5699,8 +5898,8 @@ function fuseRankings(lists, k = RRF_K) {
5699
5898
 
5700
5899
  // src/utils/vector-index.ts
5701
5900
  import { mkdir as mkdir5, readFile as readFile8, stat as stat3 } from "fs/promises";
5702
- import { join as join23 } from "path";
5703
- import { existsSync as existsSync20 } from "fs";
5901
+ import { join as join24 } from "path";
5902
+ import { existsSync as existsSync21 } from "fs";
5704
5903
  var VECTOR_INDEX_REL = ".skillwiki/vectors/index.json";
5705
5904
  var VECTOR_INDEX_SCHEMA = "skillwiki-tfidf-index/v1";
5706
5905
  function tokenize(text) {
@@ -5714,7 +5913,7 @@ function extractPageTokens(text) {
5714
5913
  return tokenize(`${title} ${body}`);
5715
5914
  }
5716
5915
  function vectorIndexPath(vault) {
5717
- return join23(vault, ...VECTOR_INDEX_REL.split("/"));
5916
+ return join24(vault, ...VECTOR_INDEX_REL.split("/"));
5718
5917
  }
5719
5918
  function termFreq(tokens) {
5720
5919
  const tf = /* @__PURE__ */ new Map();
@@ -5769,7 +5968,7 @@ async function buildVectorIndex(vault, now = (/* @__PURE__ */ new Date()).toISOS
5769
5968
  docs
5770
5969
  };
5771
5970
  const dest = vectorIndexPath(vault);
5772
- await mkdir5(join23(vault, ".skillwiki", "vectors"), { recursive: true });
5971
+ await mkdir5(join24(vault, ".skillwiki", "vectors"), { recursive: true });
5773
5972
  await atomicWriteText(dest, `${JSON.stringify(index)}
5774
5973
  `);
5775
5974
  return ok(index);
@@ -5812,7 +6011,7 @@ async function reindexPageInVectorIndex(vault, pageRelPath, now = (/* @__PURE__
5812
6011
  if (!normalizedPage.endsWith(".md")) {
5813
6012
  return err("USAGE", { message: `Page must be a .md file: ${pageRelPath}` });
5814
6013
  }
5815
- const absPath = join23(vault, ...normalizedPage.split("/"));
6014
+ const absPath = join24(vault, ...normalizedPage.split("/"));
5816
6015
  let text;
5817
6016
  try {
5818
6017
  text = await readFile8(absPath, "utf8");
@@ -5897,7 +6096,7 @@ async function reindexPageInVectorIndex(vault, pageRelPath, now = (/* @__PURE__
5897
6096
  }
5898
6097
  }
5899
6098
  const dest = vectorIndexPath(vault);
5900
- await mkdir5(join23(vault, ".skillwiki", "vectors"), { recursive: true });
6099
+ await mkdir5(join24(vault, ".skillwiki", "vectors"), { recursive: true });
5901
6100
  await atomicWriteText(dest, `${JSON.stringify(index)}
5902
6101
  `);
5903
6102
  return ok({
@@ -5915,8 +6114,8 @@ async function pruneVectorIndex(vault, opts) {
5915
6114
  const orphans = [];
5916
6115
  for (const docKey of Object.keys(index.docs)) {
5917
6116
  const normalizedKey = docKey.split(/\\|\//).join("/");
5918
- const absPath = join23(vault, ...normalizedKey.split("/"));
5919
- if (!existsSync20(absPath)) {
6117
+ const absPath = join24(vault, ...normalizedKey.split("/"));
6118
+ if (!existsSync21(absPath)) {
5920
6119
  orphans.push(normalizedKey);
5921
6120
  }
5922
6121
  }
@@ -5962,7 +6161,7 @@ async function pruneVectorIndex(vault, opts) {
5962
6161
  index.docs[docKey] = toTfIdf(termFreq(tokens), index.df, newPageCount);
5963
6162
  } else {
5964
6163
  try {
5965
- const text = await readFile8(join23(vault, ...docKey.split("/")), "utf8");
6164
+ const text = await readFile8(join24(vault, ...docKey.split("/")), "utf8");
5966
6165
  const tokens = extractPageTokens(text);
5967
6166
  index.docs[docKey] = toTfIdf(termFreq(tokens), index.df, newPageCount);
5968
6167
  } catch {
@@ -5972,7 +6171,7 @@ async function pruneVectorIndex(vault, opts) {
5972
6171
  }
5973
6172
  if (!opts?.dryRun) {
5974
6173
  const dest = vectorIndexPath(vault);
5975
- await mkdir5(join23(vault, ".skillwiki", "vectors"), { recursive: true });
6174
+ await mkdir5(join24(vault, ".skillwiki", "vectors"), { recursive: true });
5976
6175
  await atomicWriteText(dest, `${JSON.stringify(index)}
5977
6176
  `);
5978
6177
  }
@@ -6176,7 +6375,7 @@ function computeKeywordScore(terms, title, tags, body) {
6176
6375
  return score;
6177
6376
  }
6178
6377
  async function loadOrBuildGraph(vault) {
6179
- const graphPath = join24(vault, ".skillwiki", "graph.json");
6378
+ const graphPath = join25(vault, ".skillwiki", "graph.json");
6180
6379
  let needsBuild = false;
6181
6380
  try {
6182
6381
  const fileStat = await stat4(graphPath);
@@ -6265,7 +6464,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
6265
6464
  import { z } from "zod";
6266
6465
 
6267
6466
  // src/mcp/vault-resolve.ts
6268
- import { join as join25, resolve as resolve4 } from "path";
6467
+ import { join as join26, resolve as resolve4 } from "path";
6269
6468
 
6270
6469
  // src/mcp/allowlist.ts
6271
6470
  import { resolve as resolve3, sep as sep3 } from "path";
@@ -6327,7 +6526,7 @@ async function resolveMcpVault(input) {
6327
6526
  return ok({ vault: vaultPath, source });
6328
6527
  }
6329
6528
  function defaultGraphOut(vault) {
6330
- return join25(vault, ".skillwiki", "graph.json");
6529
+ return join26(vault, ".skillwiki", "graph.json");
6331
6530
  }
6332
6531
 
6333
6532
  // src/mcp/result-format.ts
@@ -6344,7 +6543,7 @@ function formatToolResult(payload) {
6344
6543
  // src/mcp/audit-log.ts
6345
6544
  import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
6346
6545
  import { homedir } from "os";
6347
- import { join as join26 } from "path";
6546
+ import { join as join27 } from "path";
6348
6547
  function auditEnabled() {
6349
6548
  const v = process.env.SKILLWIKI_MCP_AUDIT;
6350
6549
  if (v === "0" || v === "false") return false;
@@ -6356,7 +6555,7 @@ function auditSink() {
6356
6555
  function auditFilePath() {
6357
6556
  const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
6358
6557
  if (custom && custom.length > 0) return custom;
6359
- return join26(homedir(), ".skillwiki", "mcp-audit.jsonl");
6558
+ return join27(homedir(), ".skillwiki", "mcp-audit.jsonl");
6360
6559
  }
6361
6560
  function auditMcpToolCall(entry) {
6362
6561
  if (!auditEnabled()) return;
@@ -6366,7 +6565,7 @@ function auditMcpToolCall(entry) {
6366
6565
  return;
6367
6566
  }
6368
6567
  const path = auditFilePath();
6369
- mkdirSync2(join26(path, ".."), { recursive: true });
6568
+ mkdirSync2(join27(path, ".."), { recursive: true });
6370
6569
  appendFileSync(path, line, "utf8");
6371
6570
  }
6372
6571
  async function runMcpToolHandler(tool, input, fn) {
@@ -6640,7 +6839,7 @@ function registerMcpMutatingTools(server) {
6640
6839
 
6641
6840
  // src/mcp/resources.ts
6642
6841
  import { readFile as readFile11 } from "fs/promises";
6643
- import { join as join28 } from "path";
6842
+ import { join as join29 } from "path";
6644
6843
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
6645
6844
 
6646
6845
  // src/mcp/lint-bucket.ts
@@ -6770,8 +6969,8 @@ async function fetchQueryPreview(input) {
6770
6969
 
6771
6970
  // src/mcp/graph-html.ts
6772
6971
  import { readFile as readFile10 } from "fs/promises";
6773
- import { join as join27 } from "path";
6774
- import { existsSync as existsSync21 } from "fs";
6972
+ import { join as join28 } from "path";
6973
+ import { existsSync as existsSync22 } from "fs";
6775
6974
  var TYPE_COLORS = {
6776
6975
  entities: "#e74c3c",
6777
6976
  concepts: "#27ae60",
@@ -6839,9 +7038,9 @@ ${nodeSvg}
6839
7038
  return { html, node_count: nodes.length, edge_count: edges.length, truncated };
6840
7039
  }
6841
7040
  async function fetchGraphHtmlReport(input) {
6842
- const graphPath = input.graphPath ?? join27(input.vault, ".skillwiki", "graph.json");
7041
+ const graphPath = input.graphPath ?? join28(input.vault, ".skillwiki", "graph.json");
6843
7042
  const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
6844
- if (!existsSync21(graphPath)) {
7043
+ if (!existsSync22(graphPath)) {
6845
7044
  return {
6846
7045
  exitCode: ExitCode.FILE_NOT_FOUND,
6847
7046
  result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
@@ -6913,7 +7112,7 @@ async function fetchStaleSummary(input) {
6913
7112
 
6914
7113
  // src/mcp/resources.ts
6915
7114
  async function readVaultFile(vault, rel) {
6916
- return readFile11(join28(vault, rel), "utf8");
7115
+ return readFile11(join29(vault, rel), "utf8");
6917
7116
  }
6918
7117
  async function tailLines(text, lines) {
6919
7118
  const parts = text.split(/\r?\n/);
@@ -6999,7 +7198,7 @@ function registerMcpResources(server) {
6999
7198
  if (!v.ok) {
7000
7199
  return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
7001
7200
  }
7002
- const path = join28(v.data.vault, ".skillwiki", "graph.json");
7201
+ const path = join29(v.data.vault, ".skillwiki", "graph.json");
7003
7202
  try {
7004
7203
  const raw = await readFile11(path, "utf8");
7005
7204
  const graph = JSON.parse(raw);