skillwiki 0.10.107 → 0.10.109

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.
@@ -43,9 +43,25 @@ import {
43
43
  vaultIoConcurrency
44
44
  } from "./chunk-GJC2CI3W.js";
45
45
 
46
+ // src/utils/home.ts
47
+ import { homedir } from "os";
48
+ import { resolve } from "path";
49
+ function resolveSkillwikiHome(home, env = process.env) {
50
+ for (const candidate of [home, env.HOME, env.USERPROFILE]) {
51
+ if (candidate?.trim()) return resolve(candidate);
52
+ }
53
+ try {
54
+ const fallback = homedir();
55
+ if (fallback?.trim()) return resolve(fallback);
56
+ } catch {
57
+ throw new Error("Could not resolve SkillWiki home directory");
58
+ }
59
+ throw new Error("Could not resolve SkillWiki home directory");
60
+ }
61
+
46
62
  // src/utils/managed-write-preflight.ts
47
63
  import { existsSync as existsSync11 } from "fs";
48
- import { join as join21, resolve as resolve6 } from "path";
64
+ import { join as join21, resolve as resolve7 } from "path";
49
65
 
50
66
  // src/commands/fleet.ts
51
67
  import { readFile as readFile2 } from "fs/promises";
@@ -175,7 +191,7 @@ async function runFleetValidate(input) {
175
191
  }
176
192
  async function runFleetContext(input) {
177
193
  const env = input.env ?? process.env;
178
- const home = input.home ?? env.HOME ?? "";
194
+ const home = resolveSkillwikiHome(input.home, env);
179
195
  const cwd = input.cwd ?? process.cwd();
180
196
  const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
181
197
  const user = input.user ?? safeEnvValue(env.USER) ?? safeUserName();
@@ -297,7 +313,7 @@ async function runFleetContext(input) {
297
313
  }
298
314
  function fleetContextEnv(input) {
299
315
  const env = input.env ?? process.env;
300
- const home = input.home ?? env.HOME ?? "";
316
+ const home = resolveSkillwikiHome(input.home, env);
301
317
  const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
302
318
  const vault = input.vault ?? safeEnvValue(env.WIKI_PATH);
303
319
  const file = input.file ?? (vault ? join(vault, FLEET_REL_PATH) : void 0);
@@ -2414,7 +2430,7 @@ async function runTopicMapCheck(input) {
2414
2430
  // src/commands/dedup.ts
2415
2431
  import { createHash as createHash3 } from "crypto";
2416
2432
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2417
- import { dirname as dirname3, join as join10, posix, resolve } from "path";
2433
+ import { dirname as dirname3, join as join10, posix, resolve as resolve2 } from "path";
2418
2434
 
2419
2435
  // src/utils/rclone.ts
2420
2436
  import { execFile } from "child_process";
@@ -2649,7 +2665,7 @@ async function runDedup(input) {
2649
2665
  const manifest = safeEntries.length > 0 ? {
2650
2666
  version: 1,
2651
2667
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
2652
- vault: resolve(input.vault),
2668
+ vault: resolve2(input.vault),
2653
2669
  entries: safeEntries
2654
2670
  } : void 0;
2655
2671
  const remoteDeleteCount = safeEntries.reduce((count, entry) => count + entry.duplicates.length, 0);
@@ -2843,11 +2859,11 @@ function readManifest(path) {
2843
2859
  }
2844
2860
  }
2845
2861
  function validateManifestLocalState(vault, manifest) {
2846
- if (resolve(manifest.vault) !== resolve(vault)) {
2862
+ if (resolve2(manifest.vault) !== resolve2(vault)) {
2847
2863
  return err("APPROVAL_INVALID", {
2848
2864
  message: "dedup manifest belongs to a different vault",
2849
2865
  manifest_vault: manifest.vault,
2850
- vault: resolve(vault)
2866
+ vault: resolve2(vault)
2851
2867
  });
2852
2868
  }
2853
2869
  for (const entry of manifest.entries) {
@@ -2929,7 +2945,7 @@ async function runRawBodyDedup(vault, scan, pageTextCache) {
2929
2945
 
2930
2946
  // src/commands/audit.ts
2931
2947
  import { readFile as readFile11, stat as stat2 } from "fs/promises";
2932
- import { dirname as dirname4, resolve as resolve2, join as join11 } from "path";
2948
+ import { dirname as dirname4, resolve as resolve3, join as join11 } from "path";
2933
2949
  async function runAudit(input) {
2934
2950
  let text;
2935
2951
  try {
@@ -2941,7 +2957,7 @@ async function runAudit(input) {
2941
2957
  if (!fm.ok) return { exitCode: ExitCode.INVALID_FRONTMATTER, result: fm };
2942
2958
  const split = splitFrontmatter(text);
2943
2959
  const body = split.ok ? split.data.body : text;
2944
- const vault = await findVaultRoot(dirname4(resolve2(input.file)));
2960
+ const vault = await findVaultRoot(dirname4(resolve3(input.file)));
2945
2961
  if (!vault) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID") };
2946
2962
  const markers = extractCitationMarkers(body);
2947
2963
  const resolved = await Promise.all(markers.map(async (m) => {
@@ -3043,7 +3059,7 @@ async function validateCompoundReferences(vault, existingScan, pageTextCache) {
3043
3059
  // src/commands/path-too-long.ts
3044
3060
  import { existsSync as existsSync3 } from "fs";
3045
3061
  import { mkdir as mkdir4, readFile as readFile12, rename as rename3, unlink as unlink2 } from "fs/promises";
3046
- import { dirname as dirname5, join as join12, posix as posix2, resolve as resolve3 } from "path";
3062
+ import { dirname as dirname5, join as join12, posix as posix2, resolve as resolve4 } from "path";
3047
3063
  var MAX_PATH_LENGTH = 240;
3048
3064
  var WINDOWS_ABSOLUTE_PATH_LIMIT = 259;
3049
3065
  async function runPathTooLong(input) {
@@ -3136,7 +3152,7 @@ function findPathTooLongViolations(pages, maxLength) {
3136
3152
  }
3137
3153
  function maxFixPathLength(vault) {
3138
3154
  if (process.platform !== "win32") return MAX_PATH_LENGTH;
3139
- const root = resolve3(vault);
3155
+ const root = resolve4(vault);
3140
3156
  const separatorBudget = root.endsWith("\\") || root.endsWith("/") ? 0 : 1;
3141
3157
  const absoluteSafeRelLength = WINDOWS_ABSOLUTE_PATH_LIMIT - root.length - separatorBudget;
3142
3158
  return Math.max(1, Math.min(MAX_PATH_LENGTH, absoluteSafeRelLength));
@@ -5723,7 +5739,7 @@ function probeRemoteHealth(input) {
5723
5739
  // src/utils/vault-sync-helper.ts
5724
5740
  import { spawnSync } from "child_process";
5725
5741
  import { existsSync as existsSync7 } from "fs";
5726
- import { homedir, platform } from "os";
5742
+ import { platform } from "os";
5727
5743
  import { dirname as dirname6, join as join16 } from "path";
5728
5744
  import { fileURLToPath } from "url";
5729
5745
  var HELPER_NAME = "wiki-pull-with-auto-resolve.sh";
@@ -5746,13 +5762,7 @@ function candidateHelperPaths(input = { vault: "" }) {
5746
5762
  paths.push(join16(here, "..", "..", "vault-sync", "scripts", HELPER_NAME));
5747
5763
  paths.push(join16(here, "..", "..", "..", "vault-sync", "scripts", HELPER_NAME));
5748
5764
  }
5749
- const home = input.home ?? env.HOME ?? env.USERPROFILE ?? (() => {
5750
- try {
5751
- return homedir();
5752
- } catch {
5753
- return void 0;
5754
- }
5755
- })();
5765
+ const home = resolveSkillwikiHome(input.home, env);
5756
5766
  if (home) {
5757
5767
  const xdg = env.XDG_DATA_HOME;
5758
5768
  const isDarwin = platform() === "darwin";
@@ -5942,7 +5952,7 @@ function runSyncStatus(input) {
5942
5952
  output.stashes = stashes;
5943
5953
  }
5944
5954
  if (input.includeRemoteHealth) {
5945
- const home = input.home ?? process.env.HOME ?? "";
5955
+ const home = resolveSkillwikiHome(input.home);
5946
5956
  const remote_health = probeRemoteHealth({
5947
5957
  vaultPath: vault,
5948
5958
  home,
@@ -6527,7 +6537,7 @@ import {
6527
6537
  writeFileSync as writeFileSync4
6528
6538
  } from "fs";
6529
6539
  import { hostname } from "os";
6530
- import { dirname as dirname7, join as join18, resolve as resolve4 } from "path";
6540
+ import { dirname as dirname7, join as join18, resolve as resolve5 } from "path";
6531
6541
  function managedWriteLockPath(vault) {
6532
6542
  const gitPath = git(vault, ["rev-parse", "--git-path", "vault-sync/managed-write.lock"]);
6533
6543
  if (gitPath) return gitPath.startsWith("/") ? gitPath : join18(vault, gitPath);
@@ -6572,7 +6582,7 @@ function hasLocalOwnerProof(vault, record) {
6572
6582
  }
6573
6583
  function reclaimDeadManagedWriteLockOwner(vault, options = {}) {
6574
6584
  const path = managedWriteLockPath(vault);
6575
- const gitStateVault = resolve4(options.gitStateVault ?? vault);
6585
+ const gitStateVault = resolve5(options.gitStateVault ?? vault);
6576
6586
  if (!existsSync9(path)) return ok({ reclaimed: false });
6577
6587
  const record = readLockRecord(path);
6578
6588
  if (!record) {
@@ -6859,7 +6869,7 @@ function supersedeStaleReviewRequiredJournals(vault, opts = {}) {
6859
6869
 
6860
6870
  // src/utils/snapshot-worktree.ts
6861
6871
  import { readFileSync as readFileSync8 } from "fs";
6862
- import { join as join20, resolve as resolve5 } from "path";
6872
+ import { join as join20, resolve as resolve6 } from "path";
6863
6873
  function readSkillWikiConfig(path) {
6864
6874
  try {
6865
6875
  return parseDotenvText(readFileSync8(path, "utf8"));
@@ -6888,12 +6898,12 @@ function resolveConfiguredSnapshotWorktree(home) {
6888
6898
  if (!home) return void 0;
6889
6899
  const config = readSkillWikiConfig(join20(home, ".skillwiki", ".env"));
6890
6900
  const explicit = config["vault_sync.snapshot_worktree"];
6891
- if (explicit) return resolve5(explicit);
6901
+ if (explicit) return resolve6(explicit);
6892
6902
  const snapshotProfile = config["vault_sync.snapshot_profile"];
6893
6903
  if (!snapshotProfile) return void 0;
6894
- const profile = readSnapshotProfile(resolve5(snapshotProfile));
6904
+ const profile = readSnapshotProfile(resolve6(snapshotProfile));
6895
6905
  const fromProfile = profile.WIKI_GIT_WORKTREE ?? profile.SNAPSHOT_WORKTREE ?? profile.GIT_DIR;
6896
- return fromProfile ? resolve5(fromProfile) : void 0;
6906
+ return fromProfile ? resolve6(fromProfile) : void 0;
6897
6907
  }
6898
6908
 
6899
6909
  // src/utils/managed-write-preflight.ts
@@ -6910,7 +6920,7 @@ var DEFAULT_DEPS = {
6910
6920
  converge: (input) => runVaultSyncPullHelper(input),
6911
6921
  resolveConfiguredSnapshotWorktree,
6912
6922
  syncPeers: runSyncPeers,
6913
- sleepMs: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
6923
+ sleepMs: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
6914
6924
  nowMs: () => Date.now(),
6915
6925
  logHeartbeat: (message) => {
6916
6926
  process.stderr.write(`${message}
@@ -7102,8 +7112,8 @@ function isGitVault(vault) {
7102
7112
  return Boolean(git(vault, ["rev-parse", "--absolute-git-dir"]));
7103
7113
  }
7104
7114
  async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
7105
- const mutationVault = resolve6(input.vault);
7106
- let convergenceVault = input.convergenceVault && resolve6(input.convergenceVault) !== mutationVault ? resolve6(input.convergenceVault) : void 0;
7115
+ const mutationVault = resolve7(input.vault);
7116
+ let convergenceVault = input.convergenceVault && resolve7(input.convergenceVault) !== mutationVault ? resolve7(input.convergenceVault) : void 0;
7107
7117
  let convergenceSource = convergenceVault ? "explicit" : "single-path";
7108
7118
  const mutationBlocker = preflightBlocker(mutationVault);
7109
7119
  if (mutationBlocker) {
@@ -7181,7 +7191,7 @@ async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
7181
7191
  };
7182
7192
  }
7183
7193
  if (!convergenceVault && host.role === "snapshotter" && host.protected === true) {
7184
- const home = input.home ?? input.env?.HOME ?? process.env.HOME ?? "";
7194
+ const home = resolveSkillwikiHome(input.home, input.env);
7185
7195
  const configured = (deps.resolveConfiguredSnapshotWorktree ?? resolveConfiguredSnapshotWorktree)(home);
7186
7196
  if (!configured) {
7187
7197
  return {
@@ -7193,7 +7203,7 @@ async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
7193
7203
  })
7194
7204
  };
7195
7205
  }
7196
- convergenceVault = resolve6(configured);
7206
+ convergenceVault = resolve7(configured);
7197
7207
  convergenceSource = "configured";
7198
7208
  if (convergenceVault === mutationVault) {
7199
7209
  return {
@@ -7302,9 +7312,9 @@ async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
7302
7312
  };
7303
7313
  }
7304
7314
  async function runManagedWriteTransaction(input, deps = DEFAULT_DEPS) {
7305
- const mutationVault = resolve6(input.vault);
7315
+ const mutationVault = resolve7(input.vault);
7306
7316
  const lock = acquireManagedWriteLock(mutationVault, input.command, {
7307
- gitStateVault: input.convergenceVault ? resolve6(input.convergenceVault) : mutationVault
7317
+ gitStateVault: input.convergenceVault ? resolve7(input.convergenceVault) : mutationVault
7308
7318
  });
7309
7319
  if (!lock.ok) {
7310
7320
  return { exitCode: ExitCode.SYNC_LOCK_HELD, result: lock };
@@ -7357,6 +7367,7 @@ async function runManagedWriteTransaction(input, deps = DEFAULT_DEPS) {
7357
7367
  }
7358
7368
 
7359
7369
  export {
7370
+ resolveSkillwikiHome,
7360
7371
  readLastOp,
7361
7372
  appendLastOp,
7362
7373
  clearLastOp,
@@ -31,6 +31,7 @@ import {
31
31
  releaseOwnedSyncLock,
32
32
  resolveConfiguredSnapshotWorktree,
33
33
  resolveRuntimePath,
34
+ resolveSkillwikiHome,
34
35
  resolveVaultSyncPullHelper,
35
36
  resolveWikiS3Remote,
36
37
  runLint,
@@ -40,7 +41,7 @@ import {
40
41
  snapshotterAliasForLocalHost,
41
42
  toUndirectedWeighted,
42
43
  writeDotenv
43
- } from "./chunk-PGD3J2GE.js";
44
+ } from "./chunk-FYAKMULW.js";
44
45
  import {
45
46
  atomicWriteText,
46
47
  prepareTypedPage
@@ -636,7 +637,7 @@ function computeAdamicAdar(adj) {
636
637
  import { resolve as resolvePath } from "path";
637
638
  async function guardProtectedVaultWrite(input) {
638
639
  const env = input.env ?? process.env;
639
- const home = input.home ?? process.env.HOME ?? "";
640
+ const home = resolveSkillwikiHome(input.home, env);
640
641
  const cwd = input.cwd ?? process.cwd();
641
642
  const liveVaultPath = await resolveLiveVaultPath({ env, home, cwd });
642
643
  const load = await loadFleetManifestAndHost({
@@ -1023,7 +1024,7 @@ function displayConfigValue(key, value) {
1023
1024
  return value;
1024
1025
  }
1025
1026
  function configPath(home) {
1026
- return join5(home, ".skillwiki", ".env");
1027
+ return join5(resolveSkillwikiHome(home), ".skillwiki", ".env");
1027
1028
  }
1028
1029
  async function runConfigGet(input) {
1029
1030
  if (!validateKey(input.key)) {
@@ -1104,7 +1105,7 @@ function mcpAuthFromEnv(env) {
1104
1105
  }
1105
1106
  async function resolveMcpAuthEnv(input) {
1106
1107
  const processAuth = mcpAuthFromEnv(input.env ?? process.env);
1107
- const dotenv = await parseDotenvFile(join6(input.home, ".skillwiki", ".env"));
1108
+ const dotenv = await parseDotenvFile(join6(resolveSkillwikiHome(input.home, input.env), ".skillwiki", ".env"));
1108
1109
  const fileAuth = mcpAuthFromEnv(dotenv);
1109
1110
  return {
1110
1111
  url: processAuth.url ?? fileAuth.url,
@@ -1398,7 +1399,7 @@ async function checkConfigFile(home) {
1398
1399
  try {
1399
1400
  const map = await parseDotenvFile(cfgPath);
1400
1401
  const keys = Object.keys(map);
1401
- return check("pass", "config_file", "Config file exists", `Found with keys: ${keys.length > 0 ? keys.join(", ") : "(none set)"}`);
1402
+ return check("pass", "config_file", "Config file exists", `Found ${cfgPath} with keys: ${keys.length > 0 ? keys.join(", ") : "(none set)"}`);
1402
1403
  } catch (e) {
1403
1404
  return check("warn", "config_file", "Config file exists", `Failed to parse ${cfgPath}: ${String(e)}`);
1404
1405
  }
@@ -4255,6 +4256,46 @@ var dsStoreNoiseProbe = {
4255
4256
  // src/doctor/probes/mcp-handshake.ts
4256
4257
  import { existsSync as existsSync17 } from "fs";
4257
4258
  import { join as join21 } from "path";
4259
+
4260
+ // src/utils/mcp-client.ts
4261
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4262
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4263
+ var MCP_PROBE_TIMEOUT_MS = 1e4;
4264
+ async function withMcpClient(input, probe) {
4265
+ const controller = new AbortController();
4266
+ const client = new Client({ name: input.clientName, version: input.version });
4267
+ const fetchFn = input.fetchFn ?? globalThis.fetch;
4268
+ const transport = new StreamableHTTPClientTransport(new URL(input.url), {
4269
+ requestInit: { headers: { Authorization: `Bearer ${input.token}` } },
4270
+ fetch: (url, init) => fetchFn(url, {
4271
+ ...init,
4272
+ signal: init?.signal ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
4273
+ })
4274
+ });
4275
+ let timer;
4276
+ const deadline = new Promise((_resolve, reject) => {
4277
+ timer = setTimeout(() => {
4278
+ const error = new Error(`MCP probe timed out after ${MCP_PROBE_TIMEOUT_MS}ms`);
4279
+ reject(error);
4280
+ controller.abort(error);
4281
+ }, MCP_PROBE_TIMEOUT_MS);
4282
+ });
4283
+ try {
4284
+ const work = async () => {
4285
+ await client.connect(transport, { signal: controller.signal, timeout: MCP_PROBE_TIMEOUT_MS });
4286
+ return probe(client, controller.signal);
4287
+ };
4288
+ return await Promise.race([work(), deadline]);
4289
+ } catch (error) {
4290
+ throw new Error(redactMcpSecret(error instanceof Error ? error.message : String(error), input.token));
4291
+ } finally {
4292
+ clearTimeout(timer);
4293
+ controller.abort();
4294
+ await client.close();
4295
+ }
4296
+ }
4297
+
4298
+ // src/doctor/probes/mcp-handshake.ts
4258
4299
  var DEFAULT_MCP_URL = "https://wiki.karldigi.dev/mcp";
4259
4300
  function mcpEnv(ctx) {
4260
4301
  return ctx.input.env ?? process.env;
@@ -4322,54 +4363,6 @@ function redactSecret(detail, secret) {
4322
4363
  function handshakeRow(status, detail, secret) {
4323
4364
  return check(status, HANDSHAKE_ID, HANDSHAKE_LABEL, redactSecret(detail, secret));
4324
4365
  }
4325
- function parseMcpBody(text) {
4326
- const trimmed = text.trim();
4327
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
4328
- return JSON.parse(trimmed);
4329
- }
4330
- for (const line of trimmed.split(/\r?\n/)) {
4331
- const prefix = line.startsWith("data:") ? line.slice(5).trim() : "";
4332
- if (prefix.startsWith("{")) return JSON.parse(prefix);
4333
- }
4334
- throw new Error("non-JSON MCP body");
4335
- }
4336
- function jsonRpcResult(body) {
4337
- if (typeof body !== "object" || body === null) {
4338
- throw new Error("invalid JSON-RPC body");
4339
- }
4340
- const rec = body;
4341
- if (rec.error) {
4342
- throw new Error(typeof rec.error.message === "string" ? rec.error.message : "JSON-RPC error");
4343
- }
4344
- if (typeof rec.result !== "object" || rec.result === null) {
4345
- throw new Error("missing JSON-RPC result");
4346
- }
4347
- return rec.result;
4348
- }
4349
- async function mcpJsonRpc(fetchFn, url, token, method, id, params, sessionId) {
4350
- const headers = {
4351
- Authorization: `Bearer ${token}`,
4352
- "Content-Type": "application/json",
4353
- Accept: "application/json, text/event-stream"
4354
- };
4355
- if (sessionId) headers["mcp-session-id"] = sessionId;
4356
- const res = await fetchFn(url, {
4357
- method: "POST",
4358
- headers,
4359
- body: JSON.stringify({ jsonrpc: "2.0", id, method, params })
4360
- });
4361
- if (!res.ok) {
4362
- throw new Error(`HTTP ${res.status}`);
4363
- }
4364
- const nextSession = res.headers.get("mcp-session-id") ?? sessionId;
4365
- const result = jsonRpcResult(parseMcpBody(await res.text()));
4366
- return { result, sessionId: nextSession ?? void 0 };
4367
- }
4368
- function toolNames(result) {
4369
- const tools = result.tools;
4370
- if (!Array.isArray(tools)) return [];
4371
- return tools.map((t) => typeof t === "object" && t !== null && typeof t.name === "string" ? t.name : "").filter((name) => name.length > 0);
4372
- }
4373
4366
  async function checkMcpHandshake(ctx) {
4374
4367
  if (!ctx.input.checkMcp) {
4375
4368
  return handshakeRow("pass", "not requested \u2014 check skipped");
@@ -4384,33 +4377,16 @@ async function checkMcpHandshake(ctx) {
4384
4377
  const fetchFn = ctx.input.mcpFetch ?? globalThis.fetch;
4385
4378
  const shipped = ctx.input.currentVersion;
4386
4379
  try {
4387
- const init = await mcpJsonRpc(
4388
- fetchFn,
4389
- url,
4390
- token,
4391
- "initialize",
4392
- 1,
4393
- {
4394
- protocolVersion: "2025-11-25",
4395
- capabilities: {},
4396
- clientInfo: { name: "skillwiki-doctor", version: shipped }
4397
- }
4380
+ const { advertised, tools } = await withMcpClient(
4381
+ { fetchFn, url, token, clientName: "skillwiki-doctor", version: shipped },
4382
+ async (client, signal) => ({
4383
+ advertised: client.getServerVersion()?.version ?? "",
4384
+ tools: (await client.listTools({}, { signal })).tools.map((tool) => tool.name)
4385
+ })
4398
4386
  );
4399
- const serverInfo = init.result.serverInfo;
4400
- const advertised = typeof serverInfo === "object" && serverInfo !== null && typeof serverInfo.version === "string" ? serverInfo.version : "";
4401
4387
  if (!advertised) {
4402
4388
  return handshakeRow("error", "handshake failed \u2014 missing serverInfo.version", token);
4403
4389
  }
4404
- const listed = await mcpJsonRpc(
4405
- fetchFn,
4406
- url,
4407
- token,
4408
- "tools/list",
4409
- 2,
4410
- {},
4411
- init.sessionId
4412
- );
4413
- const tools = toolNames(listed.result);
4414
4390
  const frozen = Boolean(ctx.resolvedPath && existsSync17(join21(ctx.resolvedPath, ".WIKI_GIT_FROZEN")));
4415
4391
  if (frozen && !tools.includes(WORKITEM_TOOL)) {
4416
4392
  return handshakeRow(
@@ -4482,6 +4458,7 @@ var DoctorRunner = class {
4482
4458
  return this.probes;
4483
4459
  }
4484
4460
  async run(input) {
4461
+ input = { ...input, home: resolveSkillwikiHome(input.home, input.env) };
4485
4462
  const devSourceRun = isDevSourceRun(input.argv);
4486
4463
  const vsConfig = readVaultSyncConfig(input.home);
4487
4464
  const baseEnv = input.env ?? process.env;
@@ -6894,7 +6871,7 @@ function getVaultAllowlistFromEnv() {
6894
6871
 
6895
6872
  // src/mcp/vault-resolve.ts
6896
6873
  async function resolveMcpVault(input) {
6897
- const home = input.home ?? process.env.HOME ?? "";
6874
+ const home = resolveSkillwikiHome(input.home);
6898
6875
  let vaultPath;
6899
6876
  let source = "resolved";
6900
6877
  if (input.vault !== void 0 && input.vault.length > 0) {
@@ -6943,7 +6920,6 @@ function formatToolResult(payload) {
6943
6920
 
6944
6921
  // src/mcp/audit-log.ts
6945
6922
  import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
6946
- import { homedir } from "os";
6947
6923
  import { join as join28 } from "path";
6948
6924
  function auditEnabled() {
6949
6925
  const v = process.env.SKILLWIKI_MCP_AUDIT;
@@ -6956,7 +6932,7 @@ function auditSink() {
6956
6932
  function auditFilePath() {
6957
6933
  const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
6958
6934
  if (custom && custom.length > 0) return custom;
6959
- return join28(homedir(), ".skillwiki", "mcp-audit.jsonl");
6935
+ return join28(resolveSkillwikiHome(), ".skillwiki", "mcp-audit.jsonl");
6960
6936
  }
6961
6937
  function auditMcpToolCall(entry) {
6962
6938
  if (!auditEnabled()) return;
@@ -7059,7 +7035,7 @@ function registerMcpTools(server) {
7059
7035
  await resolveMcpVault({ vault, wiki });
7060
7036
  const pkg = readCliPackageJson();
7061
7037
  const r = await runDoctor({
7062
- home: process.env.HOME ?? "",
7038
+ home: resolveSkillwikiHome(),
7063
7039
  envValue: process.env.WIKI_PATH,
7064
7040
  argv: process.argv,
7065
7041
  currentVersion: pkg.version,
@@ -7132,7 +7108,7 @@ function registerMcpTools(server) {
7132
7108
  })
7133
7109
  },
7134
7110
  async ({ key }) => runMcpToolHandler("skillwiki.config_get", {}, async () => {
7135
- const r = await runConfigGet({ key, home: process.env.HOME ?? "" });
7111
+ const r = await runConfigGet({ key, home: resolveSkillwikiHome() });
7136
7112
  return formatToolResult(r);
7137
7113
  })
7138
7114
  );
@@ -7955,6 +7931,7 @@ export {
7955
7931
  MCP_TOKEN_ENV,
7956
7932
  MCP_HOST_ID_ENV,
7957
7933
  nonEmptyEnvValue,
7934
+ mcpAuthFromEnv,
7958
7935
  mergeMcpAuthIntoEnv,
7959
7936
  redactMcpSecret,
7960
7937
  configPath,
@@ -7972,6 +7949,7 @@ export {
7972
7949
  readSatelliteLatestRunFromText,
7973
7950
  readSatelliteLatestRunAt,
7974
7951
  evaluateSatelliteRunHealthState,
7952
+ withMcpClient,
7975
7953
  DEFAULT_MCP_URL,
7976
7954
  runDoctor,
7977
7955
  readCliPackageJson,
package/dist/cli.js CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  guardProtectedVaultWrite,
22
22
  isEventLedgerPath,
23
23
  isFailedRunStatus,
24
+ mcpAuthFromEnv,
24
25
  measureDirtyVolume,
25
26
  memoryAuthorityTiersRank,
26
27
  mergeMcpAuthIntoEnv,
@@ -62,8 +63,9 @@ import {
62
63
  scanConflictMarkerBlocksInText,
63
64
  snapshotterHealthChecks,
64
65
  upsertIndexEntry,
65
- vectorIndexStatus
66
- } from "./chunk-Z4NCF6K6.js";
66
+ vectorIndexStatus,
67
+ withMcpClient
68
+ } from "./chunk-LDEZQXH6.js";
67
69
  import {
68
70
  normalizeDistTag,
69
71
  readCache,
@@ -133,6 +135,7 @@ import {
133
135
  resolveFleetHostId,
134
136
  resolveInitTimePath,
135
137
  resolveRuntimePath,
138
+ resolveSkillwikiHome,
136
139
  resolveWikiS3Remote,
137
140
  rewriteRawSourceReferences,
138
141
  runAudit,
@@ -168,7 +171,7 @@ import {
168
171
  supersedeStaleReviewRequiredJournals,
169
172
  taxonomyCommentForPage,
170
173
  writeDotenv
171
- } from "./chunk-PGD3J2GE.js";
174
+ } from "./chunk-FYAKMULW.js";
172
175
  import {
173
176
  assertTargetInsideVault,
174
177
  atomicWriteText,
@@ -3671,6 +3674,7 @@ ${formatHostRow(normalizedHash, hostId, granted.vaults)}` };
3671
3674
  }
3672
3675
 
3673
3676
  // src/commands/connect.ts
3677
+ import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
3674
3678
  var CONNECT_DESCRIPTION = "HTTP MCP leaf connect: ingest a chat-attached env file into ~/.skillwiki/.env (mode 0600). Never runs skillwiki init. Cloud Drive / \u4E91\u76D8 is unsupported.";
3675
3679
  var RESERVED_HOST_IDS = ["msi", "doubao-linux-agent", "macos-dev"];
3676
3680
  var DEST_MODE = 384;
@@ -3712,44 +3716,16 @@ function redactConnectPayload(data, secret) {
3712
3716
  };
3713
3717
  }
3714
3718
  async function readWikiStatus(fetchFn, url, token) {
3715
- const headers = {
3716
- Authorization: `Bearer ${token}`,
3717
- "Content-Type": "application/json",
3718
- Accept: "application/json, text/event-stream"
3719
- };
3720
3719
  try {
3721
- const initRes = await fetchFn(url, {
3722
- method: "POST",
3723
- headers,
3724
- body: JSON.stringify({
3725
- jsonrpc: "2.0",
3726
- id: 1,
3727
- method: "initialize",
3728
- params: {
3729
- protocolVersion: "2025-11-25",
3730
- capabilities: {},
3731
- clientInfo: { name: "skillwiki-connect", version: "0" }
3732
- }
3733
- })
3734
- });
3735
- const session = initRes.headers.get("mcp-session-id");
3736
- if (session) headers["mcp-session-id"] = session;
3737
- const statusRes = await fetchFn(url, {
3738
- method: "POST",
3739
- headers,
3740
- body: JSON.stringify({
3741
- jsonrpc: "2.0",
3742
- id: 2,
3743
- method: "tools/call",
3744
- params: { name: "wiki_status", arguments: {} }
3745
- })
3746
- });
3747
- const raw = await statusRes.text();
3748
- const parsed = JSON.parse(raw.startsWith("data:") ? raw.replace(/^data:\s*/m, "") : raw);
3749
- const structured = parsed.result?.structuredContent;
3750
- let body = structured;
3720
+ const result = await withMcpClient(
3721
+ { fetchFn, url, token, clientName: "skillwiki-connect", version: "0" },
3722
+ async (client, signal) => CallToolResultSchema.parse(await client.callTool({ name: "wiki_status", arguments: {} }, void 0, { signal }))
3723
+ );
3724
+ if (result.isError) return { writer_id: null, ok: null, reconcile_ready: null, error: "wiki_status returned a tool error" };
3725
+ let body = result.structuredContent;
3751
3726
  if (!body) {
3752
- const text = parsed.result?.content?.find((c) => typeof c.text === "string")?.text;
3727
+ const content = result.content.find((item) => item.type === "text");
3728
+ const text = content?.type === "text" ? content.text : void 0;
3753
3729
  if (text) {
3754
3730
  try {
3755
3731
  body = JSON.parse(text);
@@ -3758,17 +3734,26 @@ async function readWikiStatus(fetchFn, url, token) {
3758
3734
  }
3759
3735
  }
3760
3736
  }
3761
- if (!body) return { writer_id: null, ok: null, reconcile_ready: null };
3737
+ if (!body || typeof body !== "object" || Array.isArray(body) || typeof body.ok !== "boolean" || typeof body.reconcile_ready !== "boolean" || body.writer_id !== void 0 && typeof body.writer_id !== "string") {
3738
+ return { writer_id: null, ok: null, reconcile_ready: null, error: "wiki_status returned an invalid status object" };
3739
+ }
3740
+ if (!body.ok) return { writer_id: null, ok: false, reconcile_ready: body.reconcile_ready, error: "wiki_status reported failure" };
3762
3741
  return {
3763
- writer_id: typeof body.writer_id === "string" ? body.writer_id : null,
3764
- ok: typeof body.ok === "boolean" ? body.ok : null,
3765
- reconcile_ready: typeof body.reconcile_ready === "boolean" ? body.reconcile_ready : null
3742
+ writer_id: body.writer_id ?? null,
3743
+ ok: body.ok,
3744
+ reconcile_ready: body.reconcile_ready
3745
+ };
3746
+ } catch (error) {
3747
+ return {
3748
+ writer_id: null,
3749
+ ok: null,
3750
+ reconcile_ready: null,
3751
+ error: redactMcpSecret(error instanceof Error ? error.message : String(error), token)
3766
3752
  };
3767
- } catch {
3768
- return { writer_id: null, ok: null, reconcile_ready: null };
3769
3753
  }
3770
3754
  }
3771
3755
  async function runConnect(input) {
3756
+ input = { ...input, home: resolveSkillwikiHome(input.home, input.env) };
3772
3757
  const dest = join18(input.home, ".skillwiki", ".env");
3773
3758
  const fromFile = input.fromFile?.trim();
3774
3759
  const fromStdin = Boolean(input.fromStdin);
@@ -3878,8 +3863,13 @@ async function runConnect(input) {
3878
3863
  if (doctor.result.ok) {
3879
3864
  handshake = doctor.result.data.checks.find((c) => c.id === "mcp_handshake")?.detail ?? null;
3880
3865
  }
3881
- const url = incomingUrl ?? DEFAULT_MCP_URL;
3882
- const status = await readWikiStatus(input.mcpFetch ?? globalThis.fetch, url, incomingToken);
3866
+ const auth = mcpAuthFromEnv(env);
3867
+ const url = auth.url ?? DEFAULT_MCP_URL;
3868
+ const status = await readWikiStatus(input.mcpFetch ?? globalThis.fetch, url, auth.token ?? incomingToken);
3869
+ if (status.error) {
3870
+ doctorExit = ExitCode.DOCTOR_HAS_ERRORS;
3871
+ handshake = `${handshake ?? ""}; wiki_status verification failed \u2014 ${status.error}`;
3872
+ }
3883
3873
  writerId = status.writer_id;
3884
3874
  statusOk = status.ok;
3885
3875
  reconcileReady = status.reconcile_ready;
@@ -6119,7 +6109,6 @@ import {
6119
6109
  unlinkSync,
6120
6110
  writeFileSync as writeFileSync5
6121
6111
  } from "fs";
6122
- import { homedir } from "os";
6123
6112
  import { dirname as dirname6, join as join28 } from "path";
6124
6113
  var PUBLICATION_JOURNAL_SCHEMA = "skillwiki-publication-operation-v1";
6125
6114
  var PHASE_ORDER2 = [
@@ -6148,10 +6137,10 @@ function vaultIdentity(vaultPath) {
6148
6137
  }
6149
6138
  return sha256Hex(real).slice(0, 16);
6150
6139
  }
6151
- function publicationJournalRoot(home = process.env.HOME || homedir()) {
6152
- return join28(home, ".skillwiki", "publication-operations");
6140
+ function publicationJournalRoot(home = resolveSkillwikiHome()) {
6141
+ return join28(resolveSkillwikiHome(home), ".skillwiki", "publication-operations");
6153
6142
  }
6154
- function resolveJournalPaths(vaultPath, operationId2, home = process.env.HOME || homedir()) {
6143
+ function resolveJournalPaths(vaultPath, operationId2, home = resolveSkillwikiHome()) {
6155
6144
  const identity = vaultIdentity(vaultPath);
6156
6145
  const rootDir = publicationJournalRoot(home);
6157
6146
  const vaultDir = join28(rootDir, identity);
@@ -6261,7 +6250,7 @@ function buildIdentitySummary(input) {
6261
6250
  prior_target_sha256: input.prior_target_sha256
6262
6251
  };
6263
6252
  }
6264
- function readPublicationJournal(vaultPath, operationId2, home = process.env.HOME || homedir()) {
6253
+ function readPublicationJournal(vaultPath, operationId2, home = resolveSkillwikiHome()) {
6265
6254
  const { journalPath: journalPath2 } = resolveJournalPaths(vaultPath, operationId2, home);
6266
6255
  if (!existsSync10(journalPath2)) return ok(null);
6267
6256
  let text;
@@ -6279,7 +6268,7 @@ function readPublicationJournal(vaultPath, operationId2, home = process.env.HOME
6279
6268
  return validateRecord(parsed);
6280
6269
  }
6281
6270
  function createPublicationJournal(input) {
6282
- const home = input.home ?? process.env.HOME ?? homedir();
6271
+ const home = resolveSkillwikiHome(input.home);
6283
6272
  const paths = resolveJournalPaths(input.vaultPath, input.operationId, home);
6284
6273
  if (existsSync10(paths.journalPath)) {
6285
6274
  return err("WRITE_FAILED", { message: "journal already exists", path: paths.journalPath });
@@ -6306,7 +6295,7 @@ function createPublicationJournal(input) {
6306
6295
  return ok(record);
6307
6296
  }
6308
6297
  function advancePublicationJournal(input) {
6309
- const home = input.home ?? process.env.HOME ?? homedir();
6298
+ const home = resolveSkillwikiHome(input.home);
6310
6299
  const existing = readPublicationJournal(input.vaultPath, input.operationId, home);
6311
6300
  if (!existing.ok) return existing;
6312
6301
  if (!existing.data) {
@@ -6351,7 +6340,7 @@ function advancePublicationJournal(input) {
6351
6340
  }
6352
6341
  return ok(record);
6353
6342
  }
6354
- function deletePublicationJournal(vaultPath, operationId2, home = process.env.HOME || homedir()) {
6343
+ function deletePublicationJournal(vaultPath, operationId2, home = resolveSkillwikiHome()) {
6355
6344
  const { journalPath: journalPath2 } = resolveJournalPaths(vaultPath, operationId2, home);
6356
6345
  if (!existsSync10(journalPath2)) return ok({ deleted: false });
6357
6346
  try {
@@ -6372,7 +6361,7 @@ function completePublicationJournal(input) {
6372
6361
  const deleted = deletePublicationJournal(
6373
6362
  input.vaultPath,
6374
6363
  input.operationId,
6375
- input.home ?? process.env.HOME ?? homedir()
6364
+ resolveSkillwikiHome(input.home)
6376
6365
  );
6377
6366
  if (!deleted.ok) return deleted;
6378
6367
  return ok({ completed: true });
@@ -8898,7 +8887,7 @@ function refusalErr(code, reason, guidance) {
8898
8887
  }
8899
8888
  async function runSnapshotMaintenanceDryRun(input) {
8900
8889
  const env = input.env ?? process.env;
8901
- const home = input.home ?? env.HOME ?? "";
8890
+ const home = resolveSkillwikiHome(input.home, env);
8902
8891
  const audit = input.auditSink ?? defaultAuditSink(home);
8903
8892
  const now = input.now ?? Date.now();
8904
8893
  const liveVaultPath = input.liveVaultPath ? canonicalize(input.liveVaultPath) : await resolveLiveVault({ env, home });
@@ -9039,7 +9028,7 @@ function makeAuditEvent(input, hostId, now, result, errorCode, approvalId, comma
9039
9028
  }
9040
9029
  async function runSnapshotMaintenanceExecute(input) {
9041
9030
  const env = input.env ?? process.env;
9042
- const home = input.home ?? env.HOME ?? "";
9031
+ const home = resolveSkillwikiHome(input.home, env);
9043
9032
  const audit = input.auditSink ?? defaultAuditSink(home);
9044
9033
  const now = input.now ?? Date.now();
9045
9034
  const isTty = input.isTty ?? !!process.stdin.isTTY;
@@ -9174,7 +9163,7 @@ function extraUnmergedPaths(unmerged) {
9174
9163
  }
9175
9164
  async function authorizeRepairContext(input) {
9176
9165
  const env = input.env ?? process.env;
9177
- const home = input.home ?? env.HOME ?? "";
9166
+ const home = resolveSkillwikiHome(input.home, env);
9178
9167
  const audit = input.auditSink ?? defaultAuditSink(home);
9179
9168
  const now = input.now ?? Date.now();
9180
9169
  const liveVaultPath = input.liveVaultPath ? canonicalize(input.liveVaultPath) : await resolveLiveVault({ env, home });
@@ -9238,7 +9227,7 @@ async function authorizeRepairContext(input) {
9238
9227
  }
9239
9228
  async function runProjectionConflictRepairDryRun(input) {
9240
9229
  const env = input.env ?? process.env;
9241
- const home = input.home ?? env.HOME ?? "";
9230
+ const home = resolveSkillwikiHome(input.home, env);
9242
9231
  const audit = input.auditSink ?? defaultAuditSink(home);
9243
9232
  const now = input.now ?? Date.now();
9244
9233
  const ctx = await authorizeRepairContext(input);
@@ -9341,7 +9330,7 @@ async function performProjectionRepair(input, plan, audit, now) {
9341
9330
  }
9342
9331
  async function runProjectionConflictRepairExecute(input) {
9343
9332
  const env = input.env ?? process.env;
9344
- const home = input.home ?? env.HOME ?? "";
9333
+ const home = resolveSkillwikiHome(input.home, env);
9345
9334
  const audit = input.auditSink ?? defaultAuditSink(home);
9346
9335
  const now = input.now ?? Date.now();
9347
9336
  const isTty = input.isTty ?? !!process.stdin.isTTY;
@@ -10869,7 +10858,7 @@ function rowHealthy(reachable, timer, runUnhealthy, timerBad, platform3) {
10869
10858
  async function runFleetHealth(input) {
10870
10859
  const deps = input.deps ?? defaultDeps3();
10871
10860
  const env = input.env ?? process.env;
10872
- const home = input.home ?? env.HOME ?? "";
10861
+ const home = resolveSkillwikiHome(input.home, env);
10873
10862
  const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
10874
10863
  const vault = input.vault ?? env.WIKI_PATH;
10875
10864
  const file = input.file ?? (vault ? join43(vault, FLEET_REL_PATH) : void 0);
@@ -10974,7 +10963,7 @@ function isValidLastOpPath(file) {
10974
10963
  }
10975
10964
  async function postCommit(vault, exitCode) {
10976
10965
  if (exitCode !== 0) return;
10977
- const home = process.env.HOME ?? "";
10966
+ const home = resolveSkillwikiHome();
10978
10967
  const dotenv = await parseDotenvFile(configPath(home));
10979
10968
  const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
10980
10969
  if (autoCommit === "false") return;
@@ -11192,7 +11181,7 @@ async function emitGuardedVaultWrite(vault, command, run, opts) {
11192
11181
  vault,
11193
11182
  command,
11194
11183
  env: process.env,
11195
- home: process.env.HOME ?? "",
11184
+ home: resolveSkillwikiHome(),
11196
11185
  cwd: process.cwd(),
11197
11186
  osHostname: process.env.HOSTNAME,
11198
11187
  user: process.env.USER
@@ -11211,7 +11200,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
11211
11200
  vault,
11212
11201
  command,
11213
11202
  env: process.env,
11214
- home: process.env.HOME ?? "",
11203
+ home: resolveSkillwikiHome(),
11215
11204
  cwd: process.cwd(),
11216
11205
  osHostname: process.env.HOSTNAME,
11217
11206
  user: process.env.USER
@@ -11223,14 +11212,14 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
11223
11212
  if (dirty) {
11224
11213
  return emit(dirty, void 0, { postCommit: false });
11225
11214
  }
11226
- const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-AYD3CJYD.js");
11215
+ const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-MAIGWFQJ.js");
11227
11216
  const run = await runManagedWriteTransaction2({
11228
11217
  vault,
11229
11218
  command,
11230
11219
  allowImmutableRecord: opts?.allowImmutableRecord === true,
11231
11220
  convergenceVault: opts?.convergenceVault,
11232
11221
  env: process.env,
11233
- home: process.env.HOME ?? "",
11222
+ home: resolveSkillwikiHome(),
11234
11223
  cwd: process.cwd(),
11235
11224
  osHostname: process.env.HOSTNAME,
11236
11225
  user: process.env.USER,
@@ -11476,14 +11465,14 @@ sourcesCmd.command("dispose <raw-path> [vault]").description("permanently delete
11476
11465
  program.command("orphans [vault]").description("find pages not referenced by any other page").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => emit(await runOrphans({
11477
11466
  vault,
11478
11467
  envValue: process.env.WIKI_PATH,
11479
- home: process.env.HOME ?? "",
11468
+ home: resolveSkillwikiHome(),
11480
11469
  wiki: opts.wiki,
11481
11470
  cwd: process.cwd()
11482
11471
  })));
11483
11472
  program.command("audit <file>").description("audit citation markers and source provenance for a vault page").action(async (file) => emit(await runAudit({ file })));
11484
- program.command("install").description("install skillwiki SKILL.md files into ~/.claude/skills/").option("--target <dir>", "target install directory", `${process.env.HOME ?? ""}/.claude/skills/`).option("--dry-run", "preview only", false).option("--skills-root <dir>", "source skills directory (defaults to packaged)").option("--symlink", "create symlinks instead of copies (dev mode \u2014 edits to source are immediately visible)", false).option("--force", "install CLI copies even when the skillwiki@llm-wiki plugin channel is active", false).action(async (opts) => {
11473
+ program.command("install").description("install skillwiki SKILL.md files into ~/.claude/skills/").option("--target <dir>", "target install directory", `${resolveSkillwikiHome()}/.claude/skills/`).option("--dry-run", "preview only", false).option("--skills-root <dir>", "source skills directory (defaults to packaged)").option("--symlink", "create symlinks instead of copies (dev mode \u2014 edits to source are immediately visible)", false).option("--force", "install CLI copies even when the skillwiki@llm-wiki plugin channel is active", false).action(async (opts) => {
11485
11474
  const skillsRoot = opts.skillsRoot ?? new URL("../skills/", import.meta.url).pathname;
11486
- emit(await runInstall({ skillsRoot, target: opts.target, dryRun: !!opts.dryRun, symlink: !!opts.symlink, home: process.env.HOME ?? "", force: !!opts.force }));
11475
+ emit(await runInstall({ skillsRoot, target: opts.target, dryRun: !!opts.dryRun, symlink: !!opts.symlink, home: resolveSkillwikiHome(), force: !!opts.force }));
11487
11476
  });
11488
11477
  program.command("path").description("show the resolved vault path").option("--vault <dir>", "explicit vault override (runtime)").option("--target <dir>", "explicit target override (init-time)").option("--wiki <name>", "wiki profile name").option("--init-time", "use init-time chain instead of runtime", false).option("--explain", "include resolution chain in output", false).option("--plain", "print only the vault path (no JSON wrapper)", false).action(async (opts) => {
11489
11478
  const initTime = !!opts.initTime;
@@ -11491,7 +11480,7 @@ program.command("path").description("show the resolved vault path").option("--va
11491
11480
  const r = await runPath({
11492
11481
  flag,
11493
11482
  envValue: process.env.WIKI_PATH,
11494
- home: process.env.HOME ?? "",
11483
+ home: resolveSkillwikiHome(),
11495
11484
  initTime,
11496
11485
  wiki: opts.wiki,
11497
11486
  cwd: process.cwd(),
@@ -11509,7 +11498,7 @@ program.command("lang").description("get or set the vault language").option("--l
11509
11498
  emit(await runLang({
11510
11499
  flag: opts.lang,
11511
11500
  envValue: process.env.WIKI_LANG,
11512
- home: process.env.HOME ?? "",
11501
+ home: resolveSkillwikiHome(),
11513
11502
  explain: !!opts.explain
11514
11503
  }));
11515
11504
  });
@@ -11519,7 +11508,7 @@ program.command("init").description("bootstrap a new vault with SCHEMA.md, index
11519
11508
  emit(await runInit({
11520
11509
  flag: opts.target,
11521
11510
  envValue: process.env.WIKI_PATH,
11522
- home: process.env.HOME ?? "",
11511
+ home: resolveSkillwikiHome(),
11523
11512
  templates,
11524
11513
  domain: opts.domain,
11525
11514
  taxonomy,
@@ -11537,7 +11526,7 @@ async function resolveVaultArg(arg, wiki) {
11537
11526
  flag: void 0,
11538
11527
  envValue: process.env.WIKI_PATH,
11539
11528
  wikiEnv: process.env.WIKI,
11540
- home: process.env.HOME ?? "",
11529
+ home: resolveSkillwikiHome(),
11541
11530
  wiki,
11542
11531
  cwd: process.cwd()
11543
11532
  });
@@ -11841,7 +11830,7 @@ program.command("health [vault]").description("bounded whole-system wiki health
11841
11830
  else emit(await runHealth({
11842
11831
  vault: v.vault,
11843
11832
  vaultSource: vault ? "flag" : "resolved",
11844
- home: process.env.HOME ?? "",
11833
+ home: resolveSkillwikiHome(),
11845
11834
  envValue: process.env.WIKI_PATH,
11846
11835
  argv: process.argv,
11847
11836
  currentVersion: pkg.version,
@@ -11853,12 +11842,12 @@ program.command("health [vault]").description("bounded whole-system wiki health
11853
11842
  }), void 0, { postCommit: false, humanDetailHints: healthLintDetailHints });
11854
11843
  });
11855
11844
  var configCmd = program.command("config").description("manage skillwiki configuration");
11856
- configCmd.command("get <key>").description("print the value of a config key").action(async (key) => emit(await runConfigGet({ key, home: process.env.HOME ?? "" })));
11857
- configCmd.command("set <key> <value>").description("set a config key value").action(async (key, value) => emit(await runConfigSet({ key, value, home: process.env.HOME ?? "" })));
11858
- configCmd.command("list").option("--profiles", "show wiki profiles summary", false).description("list all config key=value pairs").action(async (opts) => emit(await runConfigList({ home: process.env.HOME ?? "", profiles: !!opts.profiles })));
11859
- configCmd.command("path").description("print the config file path").action(async () => emit(await runConfigPath({ home: process.env.HOME ?? "" })));
11845
+ configCmd.command("get <key>").description("print the value of a config key").action(async (key) => emit(await runConfigGet({ key, home: resolveSkillwikiHome() })));
11846
+ configCmd.command("set <key> <value>").description("set a config key value").action(async (key, value) => emit(await runConfigSet({ key, value, home: resolveSkillwikiHome() })));
11847
+ configCmd.command("list").option("--profiles", "show wiki profiles summary", false).description("list all config key=value pairs").action(async (opts) => emit(await runConfigList({ home: resolveSkillwikiHome(), profiles: !!opts.profiles })));
11848
+ configCmd.command("path").description("print the config file path").action(async () => emit(await runConfigPath({ home: resolveSkillwikiHome() })));
11860
11849
  program.command("doctor").description("diagnose skillwiki setup issues").option("--check-snapshotter", "SSH-probe fleet snapshotter (short timeout)", false).option("--check-mcp", "live-handshake HTTP MCP initialize + tools/list (network)", false).action(async (opts) => emit(await runDoctor({
11861
- home: process.env.HOME ?? "",
11850
+ home: resolveSkillwikiHome(),
11862
11851
  envValue: process.env.WIKI_PATH,
11863
11852
  argv: process.argv,
11864
11853
  currentVersion: pkg.version,
@@ -11868,7 +11857,7 @@ program.command("doctor").description("diagnose skillwiki setup issues").option(
11868
11857
  env: process.env
11869
11858
  })));
11870
11859
  program.command("connect").description(CONNECT_DESCRIPTION).option("--from-file <path>", "chat-attached env file (not Drive / \u4E91\u76D8)").option("--from-stdin", "read env file from stdin (no echo)").option("--dry-run", "validate without writing ~/.skillwiki/.env").option("--force", "overwrite a different existing token").action(async (opts) => emit(await runConnect({
11871
- home: process.env.HOME ?? "",
11860
+ home: resolveSkillwikiHome(),
11872
11861
  fromFile: opts.fromFile,
11873
11862
  fromStdin: !!opts.fromStdin,
11874
11863
  dryRun: !!opts.dryRun,
@@ -11882,7 +11871,7 @@ program.command("status [vault]").description("output vault diagnostics").option
11882
11871
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
11883
11872
  else emit(await runStatus({
11884
11873
  vault: v.vault,
11885
- home: process.env.HOME ?? "",
11874
+ home: resolveSkillwikiHome(),
11886
11875
  langEnvValue: process.env.WIKI_LANG
11887
11876
  }), v.vault);
11888
11877
  });
@@ -11891,7 +11880,7 @@ program.command("copy-status [vault]").description("plane-tagged vault freshness
11891
11880
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
11892
11881
  else emit(await runCopyStatusCommand({
11893
11882
  vault: v.vault,
11894
- home: process.env.HOME ?? ""
11883
+ home: resolveSkillwikiHome()
11895
11884
  }), v.vault, { postCommit: false });
11896
11885
  });
11897
11886
  program.command("archive <page> [vault]").description("archive a typed-knowledge or raw page").option("--wiki <name>", "wiki profile name").option("--cascade", "scan vault for references (wikilinks + sources arrays); preview by default", false).option("--apply", "with --cascade: mutate sources arrays and archive (without --apply, --cascade is preview-only)", false).option("--approve <token>", "approve an exact state-bound raw archive plan").option("--remote <remote>", "rclone remote root to prune the archived source path, for example seaweed-wiki:cloud/wiki").option("--remote-delete", "delete the archived source path from the remote after local archive", false).option("--max-remote-deletes <n>", "maximum remote object deletes allowed", "1").action(async (page, vault, opts) => {
@@ -11998,11 +11987,11 @@ program.command("frontmatter-fix [vault]").description("fix common frontmatter i
11998
11987
  );
11999
11988
  });
12000
11989
  program.command("update").description("update skillwiki CLI from npm dist-tag").option("--tag <tag>", "npm dist-tag", "latest").action(async (opts) => emit(await runUpdate({
12001
- home: process.env.HOME ?? "",
11990
+ home: resolveSkillwikiHome(),
12002
11991
  distTag: opts.tag
12003
11992
  })));
12004
11993
  program.command("self-update").description("update skillwiki CLI from local source or npm dist-tag").option("--check", "check for updates without installing", false).option("--tag <tag>", "npm dist-tag", "latest").action(async (opts) => emit(await runSelfUpdate({
12005
- home: process.env.HOME ?? "",
11994
+ home: resolveSkillwikiHome(),
12006
11995
  check: !!opts.check,
12007
11996
  distTag: opts.tag
12008
11997
  })));
@@ -12071,7 +12060,7 @@ syncCmd.command("status [vault]").description("check vault git sync status").opt
12071
12060
  const v = await resolveVaultArg(vault, opts.wiki);
12072
12061
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
12073
12062
  else {
12074
- const home = process.env.HOME ?? "";
12063
+ const home = resolveSkillwikiHome();
12075
12064
  let snapshotterAlias;
12076
12065
  if (opts.checkSnapshotter) {
12077
12066
  const fleetLoad = await loadFleetManifestAndHost({
@@ -12205,7 +12194,7 @@ backupCmd.command("sync [vault]").description("sync vault to S3-compatible remot
12205
12194
  emit({ exitCode: v.exitCode, result: v.payload });
12206
12195
  return;
12207
12196
  }
12208
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
12197
+ const home = resolveSkillwikiHome();
12209
12198
  const dotenv = await parseDotenvFile(configPath(home));
12210
12199
  emit(await runBackupSync({
12211
12200
  vault: v.vault,
@@ -12224,7 +12213,7 @@ backupCmd.command("restore [vault]").description("restore vault from S3-compatib
12224
12213
  emit({ exitCode: v.exitCode, result: v.payload });
12225
12214
  return;
12226
12215
  }
12227
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
12216
+ const home = resolveSkillwikiHome();
12228
12217
  const dotenv = await parseDotenvFile(configPath(home));
12229
12218
  return emitGuardedVaultWrite(
12230
12219
  v.vault,
@@ -12403,7 +12392,7 @@ fleetCmd.command("context [vault]").description("render compact Runtime Host Con
12403
12392
  file: opts.file,
12404
12393
  hostId: opts.hostId,
12405
12394
  env: process.env,
12406
- home: process.env.HOME ?? "",
12395
+ home: resolveSkillwikiHome(),
12407
12396
  cwd: process.cwd(),
12408
12397
  osHostname: process.env.HOSTNAME,
12409
12398
  user: process.env.USER
@@ -12416,7 +12405,7 @@ fleetCmd.command("health [vault]").description("read-only health probe for skill
12416
12405
  hostId: opts.hostId,
12417
12406
  json: !!opts.json,
12418
12407
  env: process.env,
12419
- home: process.env.HOME ?? "",
12408
+ home: resolveSkillwikiHome(),
12420
12409
  cwd: process.cwd(),
12421
12410
  osHostname: process.env.HOSTNAME,
12422
12411
  user: process.env.USER
@@ -12481,10 +12470,10 @@ mcpAuthCmd.command("issue-host").description("append a host-id hash to the metal
12481
12470
  allowedVaults
12482
12471
  }));
12483
12472
  });
12484
- for (const w of getDeprecatedWarnings(process.env.HOME ?? "")) {
12473
+ for (const w of getDeprecatedWarnings(resolveSkillwikiHome())) {
12485
12474
  process.stderr.write(w + "\n");
12486
12475
  }
12487
- triggerAutoUpdate(process.env.HOME ?? "", pkg.version);
12476
+ triggerAutoUpdate(resolveSkillwikiHome(), pkg.version);
12488
12477
  program.parseAsync(process.argv).catch((e) => {
12489
12478
  process.stdout.write(JSON.stringify({ ok: false, error: "INTERNAL", detail: { message: String(e) } }) + "\n");
12490
12479
  process.exit(ExitCode.INTERNAL_ERROR);
@@ -6,7 +6,7 @@ import {
6
6
  runManagedWritePeerGate,
7
7
  runManagedWritePreflight,
8
8
  runManagedWriteTransaction
9
- } from "./chunk-PGD3J2GE.js";
9
+ } from "./chunk-FYAKMULW.js";
10
10
  import "./chunk-HMHMCPCE.js";
11
11
  import "./chunk-JCDAEZVD.js";
12
12
  import "./chunk-GJC2CI3W.js";
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runSkillwikiMcpStdio
4
- } from "./chunk-Z4NCF6K6.js";
4
+ } from "./chunk-LDEZQXH6.js";
5
5
  import "./chunk-7I2TPIV5.js";
6
6
  import "./chunk-DTTKCCQ4.js";
7
- import "./chunk-PGD3J2GE.js";
7
+ import "./chunk-FYAKMULW.js";
8
8
  import "./chunk-HMHMCPCE.js";
9
9
  import "./chunk-QU2IXZRN.js";
10
10
  import "./chunk-JCDAEZVD.js";
@@ -1067,28 +1067,33 @@ snapshot_post_sync_log_append_race_detected() {
1067
1067
  # delete-intent reconciliation, re-read the current store, and re-run the
1068
1068
  # existing exact worktree parity and semantic preview gates. Returns 0 only when
1069
1069
  # the recovery converged; the caller refuses (without a second retry) otherwise.
1070
- snapshot_retry_sync_after_log_append_race() {
1070
+ # reason is log-append (store log is a strict prefix extension) or
1071
+ # semantic-preview (worktree matches store but preview reports index/log drift).
1072
+ snapshot_retry_sync_after_projection_race() {
1071
1073
  local context="${1:-post-sync}"
1074
+ local reason="${2:-log-append}"
1072
1075
  if ! rclone sync "$CLOUD_REMOTE" "$SNAPSHOT_WORKTREE" "${RCLONE_OPTS[@]}" --stats 10s 2>&1 | tee "$RCLONE_LOG"; then
1073
- log "ERROR: $context log-append race retry rclone sync failed"
1076
+ log "ERROR: $context $reason race retry rclone sync failed"
1074
1077
  tail -50 "$RCLONE_LOG" >> "$LOG_FILE" 2>/dev/null || true
1075
1078
  rm -f "$RCLONE_LOG"
1076
1079
  return 1
1077
1080
  fi
1078
1081
  rm -f "$RCLONE_LOG"
1079
- log "post-sync log-append race detected; performed one additional rclone sync context=$context"
1082
+ log "post-sync $reason race detected; performed one additional rclone sync context=$context"
1080
1083
  if ! snapshot_reconcile_delete_intents; then
1081
- log "ERROR: $context log-append race retry delete-intent reconciliation failed"
1084
+ log "ERROR: $context $reason race retry delete-intent reconciliation failed"
1082
1085
  return 1
1083
1086
  fi
1084
1087
  snapshot_gate_projection_candidate \
1085
- "FAIL $context log-append race retry projection expectation refresh; snapshot promotion refused" \
1086
- "FAIL $context log-append race retry projection candidate verification; snapshot promotion refused"
1088
+ "FAIL $context $reason race retry projection expectation refresh; snapshot promotion refused" \
1089
+ "FAIL $context $reason race retry projection candidate verification; snapshot promotion refused"
1087
1090
  }
1088
1091
 
1089
- # Post-sync projection gate with at most one log-append-race recovery.
1090
- # A first failure that is not the recoverable log-append shape (or that does not
1091
- # converge after the single retry) is logged as the terminal refusal.
1092
+ # Post-sync projection gate with at most one recoverable-race re-sync.
1093
+ # Recoverable shapes are a store-ahead log append, or exact worktree==store
1094
+ # byte parity whose semantic preview still reports index/log drift (a late
1095
+ # typed page whose matching index.md has not landed yet). Any other mismatch,
1096
+ # or a retry that does not converge, is the terminal refusal.
1092
1097
  snapshot_gate_projection_candidate_with_race_retry() {
1093
1098
  local context="${1:-post-sync}"
1094
1099
  local refresh_fail="$2"
@@ -1097,14 +1102,17 @@ snapshot_gate_projection_candidate_with_race_retry() {
1097
1102
  log "$refresh_fail"
1098
1103
  return 1
1099
1104
  fi
1100
- # Exact byte parity is the ordinary path. Its semantic preview is terminal:
1101
- # a later append must not turn a preview failure into a retry.
1102
1105
  if [ -z "$PROJECTION_STATE_DIR" ] \
1103
1106
  || { cmp -s "$PROJECTION_STATE_DIR/expected-index.md" "$SNAPSHOT_WORKTREE/index.md" \
1104
1107
  && cmp -s "$PROJECTION_STATE_DIR/expected-log.md" "$SNAPSHOT_WORKTREE/log.md"; }; then
1105
1108
  if snapshot_verify_projection_candidate; then
1106
1109
  return 0
1107
1110
  fi
1111
+ # Exact byte parity with a drifting preview is the page-vs-index race:
1112
+ # retry once. A later append must not get a second retry after this.
1113
+ if snapshot_retry_sync_after_projection_race "$context" "semantic-preview"; then
1114
+ return 0
1115
+ fi
1108
1116
  log "$verify_fail"
1109
1117
  return 1
1110
1118
  fi
@@ -1113,7 +1121,7 @@ snapshot_gate_projection_candidate_with_race_retry() {
1113
1121
  log "$verify_fail"
1114
1122
  return 1
1115
1123
  fi
1116
- if snapshot_retry_sync_after_log_append_race "$context"; then
1124
+ if snapshot_retry_sync_after_projection_race "$context" "log-append"; then
1117
1125
  return 0
1118
1126
  fi
1119
1127
  log "$verify_fail"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.107",
3
+ "version": "0.10.109",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "skillwiki": "dist/cli.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.107",
3
+ "version": "0.10.109",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 22 prompt-only skills (wiki-*, proj-*, using-skillwiki, skillwiki-mcp, skillwiki-connect) backed by the deterministic `skillwiki` CLI.",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.107",
3
+ "version": "0.10.109",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 22 prompt-only skills backed by the deterministic skillwiki CLI.",
5
5
  "author": {
6
6
  "name": "karlorz",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.107",
3
+ "version": "0.10.109",
4
4
  "description": "Project-aware Karpathy-style knowledge base: 22 prompt-only skills (wiki-*, proj-*, using-skillwiki, skillwiki-mcp, skillwiki-connect) backed by HTTP MCP wiki_capture.",
5
5
  "author": {
6
6
  "name": "karlorz"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.10.107",
3
+ "version": "0.10.109",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",
@@ -68,6 +68,14 @@ skillwiki connect --from-stdin --dry-run
68
68
 
69
69
  Doctor on an MCP-only leaf (no vault + token present + vault-sync not installed) treats missing `WIKI_PATH` as info, not an init cluster.
70
70
 
71
+ ## Windows MCP-only leaves
72
+
73
+ - Do not configure an optional Git mirror as `WIKI_PATH`: CLI `skillwiki query` reads that local mirror, not HTTP MCP. Use the advertised `wiki_query` tool for live reads and `wiki_context` for active work.
74
+ - User config home resolves from a nonblank explicit home, then `HOME`, `USERPROFILE`, and the OS home directory. Empty or unset `HOME` does not require persisting a workaround environment variable. `skillwiki config path` and doctor's config-file row show the resolved absolute path.
75
+ - If the npm-generated PowerShell launcher fails or interrupts a command sequence, use `skillwiki.cmd config path` and `skillwiki.cmd doctor --check-mcp`, or invoke the installed `dist/cli.js` with Node. Do not edit generated shims or infer a SkillWiki defect without inspecting the failing wrapper.
76
+ - POSIX mode `0600` is not proof of a restrictive Windows ACL. The operator must keep the user config private. If a harness blocks attachment/stdin credential transfer, stop and request operator-controlled configuration of that resolved file with restricted access; never automatically bypass the harness, print the token, or store it in a repo or Drive.
77
+ - Record CLI/plugin/daemon versions separately when investigating drift. A scheduled notifier is not evidence that an upgrade completed; use an explicit operator-approved upgrade and restart the client when delivery includes installation changes.
78
+
71
79
  ## Stop conditions
72
80
 
73
81
  - A non-local-only SkillWiki connector already authenticates.
@@ -45,10 +45,18 @@ Check the successful write receipt's `vault_id` against the intended vault befor
45
45
 
46
46
  ## Reads
47
47
 
48
+ CLI `skillwiki query` reads a local configured vault; it does not forward to HTTP MCP. On an MCP-only leaf, leave `WIKI_PATH` unset and use the advertised MCP `wiki_query` for live reads. Optional Git mirrors are read-only snapshots and may lag the live writer.
49
+
48
50
  Local `~/wiki` (or `skillwiki path`) is fine for reads. MCP read tools are optional. Prefer ordinary file reads of the local mirror. Frozen-leaf agents should use HTTP `wiki_sources_pending`, `wiki_lint_summary`, `wiki_stale`, `wiki_compile_status`, and `wiki_reviews` instead of local CLI commands when those tools are advertised.
49
51
 
50
52
  `wiki_query` is typed knowledge only unless `scope` is `work` or `all`. For recent progress, key projects, or to-dos, feature-detect and call `wiki_progress`; apply `project`, `host`, or `agent_role` filters when requested, and present `recent_progress`, `key_projects`, and `todos` as complete bullet lists. For queued work, doctor, plugin-drift, or MCP diagnose questions, call `wiki_context` first (active_work) or pass `scope: "work"` / `"all"`. Do not treat a top typed packet hit as the live work queue.
51
53
 
54
+ ## Bridge diagnostics
55
+
56
+ Use a protocol SDK rather than copying a one-line SSE parser. SkillWiki diagnostic clients use the existing MCP TypeScript SDK to validate initialization, negotiate the protocol, send `notifications/initialized`, carry optional session IDs and negotiated protocol headers, and parse JSON or SSE (including multiline data). Empty HTTP 202 bodies acknowledge notifications/responses, not a successful initialize result. Doctor/connect probes have a 10-second overall deadline per connection and close their transports without terminating the server session.
57
+
58
+ Treat a missing/invalid initialize result, authentication refusal, or timeout as a failed handshake; do not proceed to tools calls merely because the HTTP status is successful. Preserve bearer redaction in all diagnostics.
59
+
52
60
  ## Errors
53
61
 
54
62
  Report handshake, capture, and append failures literally. If tools are missing after a 401 or `missing_prereq`, stop and tell the operator to set `SKILLWIKI_MCP_TOKEN` via process env, host Configure, or `skillwiki connect --from-file` (unknown agents; never paste-into-chat), run `grok plugin update skillwiki`, and start a new session. Over SSH, treat an OAuth login URL the same way: report `headless_oauth_loopback` and stop. Do not fall back to local `raw/transcripts/` writes.
@@ -68,6 +68,14 @@ skillwiki connect --from-stdin --dry-run
68
68
 
69
69
  Doctor on an MCP-only leaf (no vault + token present + vault-sync not installed) treats missing `WIKI_PATH` as info, not an init cluster.
70
70
 
71
+ ## Windows MCP-only leaves
72
+
73
+ - Do not configure an optional Git mirror as `WIKI_PATH`: CLI `skillwiki query` reads that local mirror, not HTTP MCP. Use the advertised `wiki_query` tool for live reads and `wiki_context` for active work.
74
+ - User config home resolves from a nonblank explicit home, then `HOME`, `USERPROFILE`, and the OS home directory. Empty or unset `HOME` does not require persisting a workaround environment variable. `skillwiki config path` and doctor's config-file row show the resolved absolute path.
75
+ - If the npm-generated PowerShell launcher fails or interrupts a command sequence, use `skillwiki.cmd config path` and `skillwiki.cmd doctor --check-mcp`, or invoke the installed `dist/cli.js` with Node. Do not edit generated shims or infer a SkillWiki defect without inspecting the failing wrapper.
76
+ - POSIX mode `0600` is not proof of a restrictive Windows ACL. The operator must keep the user config private. If a harness blocks attachment/stdin credential transfer, stop and request operator-controlled configuration of that resolved file with restricted access; never automatically bypass the harness, print the token, or store it in a repo or Drive.
77
+ - Record CLI/plugin/daemon versions separately when investigating drift. A scheduled notifier is not evidence that an upgrade completed; use an explicit operator-approved upgrade and restart the client when delivery includes installation changes.
78
+
71
79
  ## Stop conditions
72
80
 
73
81
  - A non-local-only SkillWiki connector already authenticates.
@@ -45,10 +45,18 @@ Check the successful write receipt's `vault_id` against the intended vault befor
45
45
 
46
46
  ## Reads
47
47
 
48
+ CLI `skillwiki query` reads a local configured vault; it does not forward to HTTP MCP. On an MCP-only leaf, leave `WIKI_PATH` unset and use the advertised MCP `wiki_query` for live reads. Optional Git mirrors are read-only snapshots and may lag the live writer.
49
+
48
50
  Local `~/wiki` (or `skillwiki path`) is fine for reads. MCP read tools are optional. Prefer ordinary file reads of the local mirror. Frozen-leaf agents should use HTTP `wiki_sources_pending`, `wiki_lint_summary`, `wiki_stale`, `wiki_compile_status`, and `wiki_reviews` instead of local CLI commands when those tools are advertised.
49
51
 
50
52
  `wiki_query` is typed knowledge only unless `scope` is `work` or `all`. For recent progress, key projects, or to-dos, feature-detect and call `wiki_progress`; apply `project`, `host`, or `agent_role` filters when requested, and present `recent_progress`, `key_projects`, and `todos` as complete bullet lists. For queued work, doctor, plugin-drift, or MCP diagnose questions, call `wiki_context` first (active_work) or pass `scope: "work"` / `"all"`. Do not treat a top typed packet hit as the live work queue.
51
53
 
54
+ ## Bridge diagnostics
55
+
56
+ Use a protocol SDK rather than copying a one-line SSE parser. SkillWiki diagnostic clients use the existing MCP TypeScript SDK to validate initialization, negotiate the protocol, send `notifications/initialized`, carry optional session IDs and negotiated protocol headers, and parse JSON or SSE (including multiline data). Empty HTTP 202 bodies acknowledge notifications/responses, not a successful initialize result. Doctor/connect probes have a 10-second overall deadline per connection and close their transports without terminating the server session.
57
+
58
+ Treat a missing/invalid initialize result, authentication refusal, or timeout as a failed handshake; do not proceed to tools calls merely because the HTTP status is successful. Preserve bearer redaction in all diagnostics.
59
+
52
60
  ## Errors
53
61
 
54
62
  Report handshake, capture, and append failures literally. If tools are missing after a 401 or `missing_prereq`, stop and tell the operator to set `SKILLWIKI_MCP_TOKEN` via process env, host Configure, or `skillwiki connect --from-file` (unknown agents; never paste-into-chat), run `grok plugin update skillwiki`, and start a new session. Over SSH, treat an OAuth login URL the same way: report `headless_oauth_loopback` and stop. Do not fall back to local `raw/transcripts/` writes.