skillwiki 0.10.73 → 0.10.75

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>");
@@ -40,7 +40,7 @@ import {
40
40
  snapshotterAliasForLocalHost,
41
41
  toUndirectedWeighted,
42
42
  writeDotenv
43
- } from "./chunk-R5SDVKHS.js";
43
+ } from "./chunk-4XCBKPKD.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);
package/dist/cli.js CHANGED
@@ -50,7 +50,7 @@ import {
50
50
  snapshotterHealthChecks,
51
51
  upsertIndexEntry,
52
52
  vectorIndexStatus
53
- } from "./chunk-YKVJ2A3O.js";
53
+ } from "./chunk-WAUWBPIQ.js";
54
54
  import {
55
55
  normalizeDistTag,
56
56
  readCache,
@@ -151,7 +151,7 @@ import {
151
151
  supersedeStaleReviewRequiredJournals,
152
152
  taxonomyCommentForPage,
153
153
  writeDotenv
154
- } from "./chunk-R5SDVKHS.js";
154
+ } from "./chunk-4XCBKPKD.js";
155
155
  import {
156
156
  assertTargetInsideVault,
157
157
  atomicWriteText,
@@ -4246,24 +4246,45 @@ function parseCoreSemver(version) {
4246
4246
  if (!m) return null;
4247
4247
  return { major: parseInt(m[1], 10), minor: parseInt(m[2], 10), patch: parseInt(m[3], 10) };
4248
4248
  }
4249
+ function isAtLeast(parsed, major, minor, patch) {
4250
+ return parsed.major > major || parsed.major === major && parsed.minor > minor || parsed.major === major && parsed.minor === minor && parsed.patch >= patch;
4251
+ }
4252
+ function isBelow(parsed, major, minor, patch) {
4253
+ return !isAtLeast(parsed, major, minor, patch);
4254
+ }
4249
4255
  function needs0101Migration(previousVersion, newVersion) {
4250
4256
  const p = parseCoreSemver(previousVersion);
4251
4257
  const n = parseCoreSemver(newVersion);
4252
4258
  if (!p || !n) return false;
4253
- const prevLt = p.major < 0 || p.major === 0 && p.minor < 10 || p.major === 0 && p.minor === 10 && p.patch < 1;
4254
- const newGe = n.major > 0 || n.major === 0 && n.minor > 10 || n.major === 0 && n.minor === 10 && n.patch >= 1;
4255
- return prevLt && newGe;
4259
+ return isBelow(p, 0, 10, 1) && isAtLeast(n, 0, 10, 1);
4260
+ }
4261
+ function needsHttpMcpMigration(previousVersion, newVersion) {
4262
+ const p = parseCoreSemver(previousVersion);
4263
+ const n = parseCoreSemver(newVersion);
4264
+ if (!p || !n) return false;
4265
+ return isBelow(p, 0, 10, 70) && isAtLeast(n, 0, 10, 70);
4256
4266
  }
4257
4267
  function migrationNotesForUpgrade(previousVersion, newVersion) {
4258
- if (!needs0101Migration(previousVersion, newVersion)) return [];
4259
- return [
4260
- "Migration 0.10.1:",
4261
- "- pull helper resolves from dist/ + host vault-sync install",
4262
- "- run: skillwiki doctor",
4263
- "- if managed writes blocked: skillwiki sync journal list",
4264
- "- then: skillwiki sync journal clear-stale --dry-run",
4265
- "- legacy override: SKILLWIKI_VAULT_SYNC_PULL_HELPER=<path-to-wiki-pull-with-auto-resolve.sh>"
4266
- ];
4268
+ const notes = [];
4269
+ if (needs0101Migration(previousVersion, newVersion)) {
4270
+ notes.push(
4271
+ "Migration 0.10.1:",
4272
+ "- pull helper resolves from dist/ + host vault-sync install",
4273
+ "- run: skillwiki doctor",
4274
+ "- if managed writes blocked: skillwiki sync journal list",
4275
+ "- then: skillwiki sync journal clear-stale --dry-run",
4276
+ "- legacy override: SKILLWIKI_VAULT_SYNC_PULL_HELPER=<path-to-wiki-pull-with-auto-resolve.sh>"
4277
+ );
4278
+ }
4279
+ if (needsHttpMcpMigration(previousVersion, newVersion)) {
4280
+ notes.push(
4281
+ "Migration HTTP MCP:",
4282
+ "- upgrade the plugin channel, then start a new session (plugin instructions do not hot-swap)",
4283
+ "- run: skillwiki doctor --check-mcp",
4284
+ "- frozen-leaf hosts write via HTTP MCP only; do not local-write raw/transcripts/ or vault git"
4285
+ );
4286
+ }
4287
+ return notes;
4267
4288
  }
4268
4289
  function resolveGlobalSkillsRoot() {
4269
4290
  try {
@@ -10410,7 +10431,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
10410
10431
  if (dirty) {
10411
10432
  return emit(dirty, void 0, { postCommit: false });
10412
10433
  }
10413
- const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-PUEJOXJO.js");
10434
+ const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-KCFPPZNF.js");
10414
10435
  const run = await runManagedWriteTransaction2({
10415
10436
  vault,
10416
10437
  command,
@@ -11043,13 +11064,14 @@ configCmd.command("get <key>").description("print the value of a config key").ac
11043
11064
  configCmd.command("set <key> <value>").description("set a config key value").action(async (key, value) => emit(await runConfigSet({ key, value, home: process.env.HOME ?? "" })));
11044
11065
  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 })));
11045
11066
  configCmd.command("path").description("print the config file path").action(async () => emit(await runConfigPath({ home: process.env.HOME ?? "" })));
11046
- program.command("doctor").description("diagnose skillwiki setup issues").option("--check-snapshotter", "SSH-probe fleet snapshotter (short timeout)", false).action(async (opts) => emit(await runDoctor({
11067
+ 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({
11047
11068
  home: process.env.HOME ?? "",
11048
11069
  envValue: process.env.WIKI_PATH,
11049
11070
  argv: process.argv,
11050
11071
  currentVersion: pkg.version,
11051
11072
  cwd: process.cwd(),
11052
- checkSnapshotter: !!opts.checkSnapshotter
11073
+ checkSnapshotter: !!opts.checkSnapshotter,
11074
+ checkMcp: !!opts.checkMcp
11053
11075
  })));
11054
11076
  program.command("status [vault]").description("output vault diagnostics").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
11055
11077
  const v = await resolveVaultArg(vault, opts.wiki);
@@ -6,7 +6,7 @@ import {
6
6
  runManagedWritePeerGate,
7
7
  runManagedWritePreflight,
8
8
  runManagedWriteTransaction
9
- } from "./chunk-R5SDVKHS.js";
9
+ } from "./chunk-4XCBKPKD.js";
10
10
  import "./chunk-BPJ5KWIT.js";
11
11
  import "./chunk-OMO45AHI.js";
12
12
  import "./chunk-HJ4ALQG6.js";
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runSkillwikiMcpStdio
4
- } from "./chunk-YKVJ2A3O.js";
4
+ } from "./chunk-WAUWBPIQ.js";
5
5
  import "./chunk-7I2TPIV5.js";
6
6
  import "./chunk-KFEOMMWK.js";
7
- import "./chunk-R5SDVKHS.js";
7
+ import "./chunk-4XCBKPKD.js";
8
8
  import "./chunk-BPJ5KWIT.js";
9
9
  import "./chunk-NPTIYO2S.js";
10
10
  import "./chunk-OMO45AHI.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.73",
3
+ "version": "0.10.75",
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.73",
3
+ "version": "0.10.75",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 21 prompt-only skills (wiki-*, proj-*, using-skillwiki, skillwiki-mcp) backed by the deterministic `skillwiki` CLI.",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.73",
3
+ "version": "0.10.75",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 21 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.73",
3
+ "version": "0.10.75",
4
4
  "description": "Project-aware Karpathy-style knowledge base: 21 prompt-only skills (wiki-*, proj-*, using-skillwiki, skillwiki-mcp) backed by HTTP MCP wiki_capture.",
5
5
  "author": {
6
6
  "name": "karlorz"
@@ -21,10 +21,15 @@ You are a project work item manager specializing in creating and executing work
21
21
 
22
22
  **Execution Process:**
23
23
 
24
- If `$VAULT/.WIKI_GIT_FROZEN` exists after `skillwiki path`: do not create or mutate work folders; do not `page publish` or git commit. Capture a close note via HTTP MCP `wiki_capture` or STOP. Work-item close waits unpublished Tier 2 (`wiki_workitem_write`). Reads of existing folders are allowed.
24
+ If `$VAULT/.WIKI_GIT_FROZEN` exists after `skillwiki path`:
25
+ - Existing work folder reads are allowed.
26
+ - Git add/commit/push and wiki-sync remain **fail closed**.
27
+ - **If live MCP tools include `wiki_workitem_write`**: create, mutate, or close work items via MCP `wiki_workitem_write` (using CAS base_sha256). Do not write local files in `~/wiki`.
28
+ - **If live MCP tools include `wiki_page_publish`**: publish typed pages via MCP `wiki_page_publish` CAS.
29
+ - **If those tools are absent**: capture a close note via HTTP MCP `wiki_capture` or STOP. Do not local-write.
25
30
 
26
31
  ### Creating a New Work Item
27
- 1. **Resolve vault.** Run `skillwiki path`. If `.WIKI_GIT_FROZEN` exists, STOP (see freeze rule above).
32
+ 1. **Resolve vault.** Run `skillwiki path`. If `.WIKI_GIT_FROZEN` exists, follow the freeze rule above (MCP `wiki_workitem_write` when advertised; otherwise STOP). Do not local-create.
28
33
  2. **Determine slug and kind.** From task prompt: kind (`feature` | `issue` | `refactor` | `decision`) and work slug.
29
34
  3. **Create folder.** `projects/{slug}/work/YYYY-MM-DD-{work-slug}/`.
30
35
  4. **Write spec.md.** Frontmatter with kind, status=planned, project wikilink. Body with context and scope.
@@ -55,13 +60,14 @@ Return:
55
60
  - Log entries appended
56
61
 
57
62
  **Stop Conditions:**
58
- - `$VAULT/.WIKI_GIT_FROZEN` exists and the task would create, mutate, or close a work item
63
+ - `$VAULT/.WIKI_GIT_FROZEN` exists and the task would create, mutate, or close a work item when `wiki_workitem_write` is absent from live MCP tools
59
64
  - `validate` non-zero
60
65
  - Conflicting work folder name
61
66
  - No project context and no `playground` fallback
62
67
 
63
68
  **Forbidden:**
64
- - Creating or mutating work items on a vault that has `.WIKI_GIT_FROZEN`
69
+ - Creating or mutating local work items on a vault that has `.WIKI_GIT_FROZEN` (use MCP `wiki_workitem_write` when advertised)
70
+ - `git add` / `git commit` / `git push` on a vault that has `.WIKI_GIT_FROZEN`
65
71
  - Writing spec/plan files outside the work folder
66
72
  - Marking `status: completed` without a `completed:` date
67
73
  - Accepting tasks.md DONE labels without independent disk verification
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.10.73",
3
+ "version": "0.10.75",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",
@@ -33,9 +33,10 @@ Standard four + project context (project README, last ~5 work logs).
33
33
  After `skillwiki path`, if `$VAULT/.WIKI_GIT_FROZEN` exists:
34
34
 
35
35
  - **Reads** of existing work folders are allowed.
36
- - **Do not** create or mutate `projects/*/work/**`, `knowledge.md`, or run `skillwiki page publish` on this leaf.
37
- - **Do not** `git commit` / `wiki-sync` push to “close” the item.
38
- - Work-item close is unpublished until Tier 2 (`wiki_workitem_write` / `wiki_page_publish`). If the user asked to close or complete work, capture a note via HTTP MCP `wiki_capture` (`kind: note` or `task`) and STOP. Do not local-write the work folder.
36
+ - **Git fail-closed:** Git add/commit/push / `wiki-sync`: still **fail closed**. GitHub is sg01 `wiki-snapshot`.
37
+ - **If the live MCP tool list includes `wiki_workitem_write`**: create, mutate, or close work items via MCP `wiki_workitem_write`. Read current bytes (`wiki_read_page` sha256 or local file hash), pass `path`, `content`, `base_sha256`. On `FILE_CHANGED`, re-read and retry once. Never local-write `~/wiki`.
38
+ - **If the live MCP tool list includes `wiki_page_publish`**: publish typed Layer-2 pages via MCP `wiki_page_publish` with the same CAS. Do not run local `skillwiki page publish` on the frozen leaf.
39
+ - **If those tools are absent** (daemon still captures-only): capture a close note via HTTP MCP `wiki_capture` (`kind: note` or `task`) or STOP. Do not local-write. Do not claim the tools exist.
39
40
 
40
41
  ## Executing an Existing Work Item
41
42
 
@@ -99,12 +100,13 @@ Rules:
99
100
  - **Re-marking without doing**: do not simply re-write tasks.md to say DONE without applying the corresponding fix. The next session will find the same gap.
100
101
 
101
102
  ## Stop conditions
102
- - `$VAULT/.WIKI_GIT_FROZEN` exists and the user asked to create, mutate, or close a work item — capture via `wiki_capture` or STOP; do not local-write.
103
+ - `$VAULT/.WIKI_GIT_FROZEN` exists and the user asked to create, mutate, or close a work item when `wiki_workitem_write` is absent from the live MCP tool list — capture via `wiki_capture` or STOP; do not local-write.
103
104
  - `validate` non-zero.
104
105
  - Conflicting work folder name.
105
106
 
106
107
  ## Forbidden
107
- - Creating or mutating work items on a vault that has `.WIKI_GIT_FROZEN`.
108
+ - Creating or mutating local work items on a vault that has `.WIKI_GIT_FROZEN` (use MCP `wiki_workitem_write` when advertised).
109
+ - `git add` / `git commit` / `git push` on a vault that has `.WIKI_GIT_FROZEN`.
108
110
  - Writing spec/plan files outside the work folder.
109
111
  - Marking `status: completed` without a `completed:` date.
110
112
  - Accepting tasks.md status labels without independent disk verification.
@@ -33,9 +33,10 @@ Standard four + project context (project README, last ~5 work logs).
33
33
  After `skillwiki path`, if `$VAULT/.WIKI_GIT_FROZEN` exists:
34
34
 
35
35
  - **Reads** of existing work folders are allowed.
36
- - **Do not** create or mutate `projects/*/work/**`, `knowledge.md`, or run `skillwiki page publish` on this leaf.
37
- - **Do not** `git commit` / `wiki-sync` push to “close” the item.
38
- - Work-item close is unpublished until Tier 2 (`wiki_workitem_write` / `wiki_page_publish`). If the user asked to close or complete work, capture a note via HTTP MCP `wiki_capture` (`kind: note` or `task`) and STOP. Do not local-write the work folder.
36
+ - **Git fail-closed:** Git add/commit/push / `wiki-sync`: still **fail closed**. GitHub is sg01 `wiki-snapshot`.
37
+ - **If the live MCP tool list includes `wiki_workitem_write`**: create, mutate, or close work items via MCP `wiki_workitem_write`. Read current bytes (`wiki_read_page` sha256 or local file hash), pass `path`, `content`, `base_sha256`. On `FILE_CHANGED`, re-read and retry once. Never local-write `~/wiki`.
38
+ - **If the live MCP tool list includes `wiki_page_publish`**: publish typed Layer-2 pages via MCP `wiki_page_publish` with the same CAS. Do not run local `skillwiki page publish` on the frozen leaf.
39
+ - **If those tools are absent** (daemon still captures-only): capture a close note via HTTP MCP `wiki_capture` (`kind: note` or `task`) or STOP. Do not local-write. Do not claim the tools exist.
39
40
 
40
41
  ## Executing an Existing Work Item
41
42
 
@@ -99,12 +100,13 @@ Rules:
99
100
  - **Re-marking without doing**: do not simply re-write tasks.md to say DONE without applying the corresponding fix. The next session will find the same gap.
100
101
 
101
102
  ## Stop conditions
102
- - `$VAULT/.WIKI_GIT_FROZEN` exists and the user asked to create, mutate, or close a work item — capture via `wiki_capture` or STOP; do not local-write.
103
+ - `$VAULT/.WIKI_GIT_FROZEN` exists and the user asked to create, mutate, or close a work item when `wiki_workitem_write` is absent from the live MCP tool list — capture via `wiki_capture` or STOP; do not local-write.
103
104
  - `validate` non-zero.
104
105
  - Conflicting work folder name.
105
106
 
106
107
  ## Forbidden
107
- - Creating or mutating work items on a vault that has `.WIKI_GIT_FROZEN`.
108
+ - Creating or mutating local work items on a vault that has `.WIKI_GIT_FROZEN` (use MCP `wiki_workitem_write` when advertised).
109
+ - `git add` / `git commit` / `git push` on a vault that has `.WIKI_GIT_FROZEN`.
108
110
  - Writing spec/plan files outside the work folder.
109
111
  - Marking `status: completed` without a `completed:` date.
110
112
  - Accepting tasks.md status labels without independent disk verification.
@@ -22,13 +22,20 @@ SkillWiki captures are HTTP MCP only (`type: http`). Claude/Grok use `SKILLWIKI_
22
22
  - Never auto-source `mcp.env` or auto-write `~/.cursor/mcp.json`, Grok `config.toml`, or `mcp.env`.
23
23
  - Never print the bearer token.
24
24
 
25
- ## Writes (captures-only)
25
+ ## Upgrade / migration
26
+
27
+ Remotes still on a pre-HTTP-MCP plugin or CLI must upgrade the plugin channel (and the CLI where that host has one), then start a **new session** — plugin instructions do not hot-swap. The operator check is the same `skillwiki doctor` as metal: default rows cover URL/auth presence and frozen-leaf write-path; `skillwiki doctor --check-mcp` does the live initialize handshake. Do not invent a second admin skill or HTTP MCP admin tool. `wiki_status` remains daemon health only.
28
+
29
+ ## Writes
26
30
 
27
31
  On leaf hosts, wiki captures go through MCP. Do **not** write `raw/transcripts/` or `log.md` as local files.
28
32
 
29
33
  1. Call MCP `wiki_capture` with `kind` (`task` | `idea` | `bug` | `note`), `project`, `title`, and `content`. Optional `agent_note`.
30
34
  2. Call MCP `wiki_log_append` when a structural `log.md` line is needed. Pass append-only `content`. Do not rewrite log history.
31
- 3. Write surface is captures-only. Do not call unpublished Tier 2 tools (`wiki_workitem_write`, `wiki_page_publish`). Those tools are not on the live daemon. Work-item close is not available from this MCP. Do not `git commit` / `wiki-push` against `~/wiki` for these captures. If `$VAULT/.WIKI_GIT_FROZEN` exists, that freeze is the reason nearby agents cannot close wiki work until Tier 2 ships.
35
+ 3. **Feature-detect work-item and page-publish tools**:
36
+ - When the server advertises `wiki_workitem_write`: use it to create, mutate, or close work items under `projects/<slug>/work/**` or update `projects/<slug>/knowledge.md`. Pass `path`, `content`, and `base_sha256` of existing bytes (from `wiki_read_page` or local mirror). On `FILE_CHANGED`, re-read and retry once.
37
+ - When the server advertises `wiki_page_publish`: use it to publish typed Layer-2 pages (`entities/`, `concepts/`, `comparisons/`, `queries/`, `meta/`) using the same CAS protocol.
38
+ - When those tools are **absent** from the live tool list (e.g. server is captures-only): do not call them or claim they exist. If `$VAULT/.WIKI_GIT_FROZEN` exists, fall back to capturing a close note via `wiki_capture` or STOP. Work-item close remains unavailable on the frozen leaf without `wiki_workitem_write`. Do not `git commit` / `wiki-push` against `~/wiki`.
32
39
 
33
40
  ## Reads
34
41
 
@@ -170,8 +170,9 @@ If the vault has `.WIKI_GIT_FROZEN`:
170
170
  | Intent | Allowed? | How |
171
171
  |---|---|---|
172
172
  | Capture note/idea/bug/task | Yes | HTTP MCP `wiki_capture` / `wiki_log_append` |
173
- | Close or mutate a work item (`projects/*/work/**`, `knowledge.md`) | **No** until Tier 2 | Do not `page publish` or git commit. Capture a close note via MCP or STOP. |
174
- | `wiki-sync` push/commit | **No** | GitHub is sg01 `wiki-snapshot`. |
173
+ | Close or mutate a work item (`projects/*/work/**`, `knowledge.md`) | Conditional | If live MCP tools include `wiki_workitem_write`, mutate/close via MCP CAS; if absent, capture a close note via MCP `wiki_capture` or STOP. Never local-write. |
174
+ | Publish typed Layer-2 page (`concepts/`, `queries/`, etc.) | Conditional | If live MCP tools include `wiki_page_publish`, publish via MCP CAS; if absent, STOP. Do not run local `skillwiki page publish`. |
175
+ | `wiki-sync` push/commit | **No** | Git fail-closed. GitHub is sg01 `wiki-snapshot`. |
175
176
  | Read local `~/wiki` | Yes | Mirror reads only |
176
177
  | `/wiki-add-task <text>` | You're in an interactive session on an authoring host | Creates `raw/transcripts/YYYY-MM-DD-{type}-{slug}.md` with ad-hoc capture frontmatter |
177
178
  | Filesystem drop | You're NOT in a Claude session (Obsidian, editor, sync) on an authoring host | Create a new `.md` file in `raw/transcripts/` — dev-loop discovers it on next cycle; do not edit it after capture |
@@ -245,7 +246,7 @@ Key CLI subcommands: `init`, `health`, `lint`, `config`, `doctor`, `path`, `lang
245
246
  Optional read-only MCP (`skillwiki mcp`) exposes pending/compile/review list tools. Do not use MCP for compile claim, publish, or review writes.
246
247
  `skillwiki claim` binds a transcript to a work item only through an exact `raw/transcripts/...` path in `source:` / `sources:` / `closes:`. A `--project` that contradicts the capture's explicit project is rejected. `skillwiki stale --project` uses exact normalized slugs, not substring matching. `skillwiki claims audit` is the read-only integrity report for duplicate, malformed, dangling, cross-project, and unbacked claims; it never rewrites captures or work items.
247
248
 
248
- Run `skillwiki health <vault> --out /tmp/skillwiki-health.json --no-fail` for a bounded whole-system report that includes the nonblocking source-lifecycle backlog. Pending captures are informational and do not make health fail. Run `skillwiki lint <vault> --summary` for lint-only bucket counts with capped examples and details commands. Run `skillwiki doctor` to diagnose setup/runtime issues only. Run `skillwiki config list` to see current configuration.
249
+ Run `skillwiki health <vault> --out /tmp/skillwiki-health.json --no-fail` for a bounded whole-system report that includes the nonblocking source-lifecycle backlog. Pending captures are informational and do not make health fail. Run `skillwiki lint <vault> --summary` for lint-only bucket counts with capped examples and details commands. Run `skillwiki doctor` to diagnose setup/runtime issues only, including HTTP MCP URL/auth presence and the frozen-leaf write path. Pass `--check-mcp` for a live initialize handshake and pin/tool-list lag; the default doctor path does not contact HTTP MCP. There is no second MCP admin skill. Run `skillwiki config list` to see current configuration.
249
250
 
250
251
  ## Runtime Host Context and Fleet Freshness
251
252
  Resolve the active project vault with `skillwiki path` first. Then pass that exact path to `skillwiki --human fleet context <vault>` for host identity and safety guidance. `fleet context` is authoritative for host identity. It overrides stale injected SessionStart context, remembered workspace context, and prior conversation summaries. `fleet context` is local and network-free; it reports `identity_status`, resolver trace, warnings, and the fact that remote freshness was not checked.
@@ -11,13 +11,13 @@ Standard four reads.
11
11
  ## Steps
12
12
  0. Resolve vault: `skillwiki path` (record source for context).
13
13
  - **CRITICAL**: Verify the correct vault when the user has multiple wiki instances (e.g., ~/wiki vs ~/wiki-fin). User may explicitly specify which vault to target — confirm before destructive operations.
14
- 1. For a whole-system health report, run `skillwiki health <vault> --out /tmp/skillwiki-health.json --no-fail`. Read the JSON envelope from stdout or the report file. Treat `skillwiki doctor` as setup/runtime diagnostics only, not a vault-content health report.
14
+ 1. For a whole-system health report, run `skillwiki health <vault> --out /tmp/skillwiki-health.json --no-fail`. Read the JSON envelope from stdout or the report file. Treat `skillwiki doctor` as setup/runtime diagnostics only, not a vault-content health report. HTTP MCP URL, pin lag, handshake, and frozen-leaf write-path belong to `skillwiki doctor` (pass `--check-mcp` for the live handshake). Do not use this skill for client/plugin fleet upgrades.
15
15
  2. For lint-only maintenance, run `skillwiki lint <vault> --summary`. This returns bounded bucket counts, capped examples, and `details_command` hints without full item arrays.
16
16
  3. Drill into important buckets with `skillwiki lint <vault> --only <bucket>` when examples are insufficient for remediation.
17
17
  4. Reason over findings; present grouped by severity with concrete suggested actions per kind. If the CLI was recently updated with new lint checks, re-running lint on the full vault may flag pre-existing pages that predate the new rule — treat these as legitimate findings, not false positives.
18
18
  5. Treat `sensitive_content` as a security error. Drill down with `skillwiki lint <vault> --only sensitive_content --human`; never print the secret value. Raw findings are report/quarantine-only: `--fix` must not redact, rewrite, or recompute existing raw evidence. Maintained-page repairs may proceed only through their approved write workflow.
19
19
  6. If `log_rotate_needed` is present and the user consents, run `skillwiki log-rotate <vault> --apply`. Otherwise leave alone.
20
- 7. **Post-migration verification**: If the user recently migrated content (e.g., moved entity/concept pages to another vault), re-run `skillwiki lint <vault> --summary` and verify that broken_wikilinks count decreased. Remaining broken links for migrated content indicate pages still referencing the moved files — these should be cleaned up (remove citations or migrate the referencing pages too).
20
+ 7. **Post-migration verification**: If the user recently migrated **vault content** (e.g., moved entity/concept pages to another vault), re-run `skillwiki lint <vault> --summary` and verify that broken_wikilinks count decreased. Remaining broken links for migrated content indicate pages still referencing the moved files — these should be cleaned up (remove citations or migrate the referencing pages too). This step is content moves only — not HTTP MCP client/plugin upgrades (those use `skillwiki doctor --check-mcp`).
21
21
  8. Append a `log.md` entry summarizing lint counts only when the user asked to record the maintenance result. Do not log routine `health` reports by default.
22
22
  ## Stop conditions
23
23
  None — lint reports all findings even on per-page errors.
@@ -20,7 +20,7 @@ If `$VAULT/.WIKI_GIT_FROZEN` exists, **fail closed** before any stash, commit, p
20
20
 
21
21
  - Do **not** `git add`, `git commit`, `git pull`, `git push`, or `skillwiki sync push`.
22
22
  - Captures go through HTTP MCP `wiki_capture` / `wiki_log_append`.
23
- - Closing or mutating work items is unpublished until Tier 2 (`wiki_workitem_write` / `wiki_page_publish`). Capture a close note via MCP or STOP.
23
+ - Work items mutate/close via MCP `wiki_workitem_write` when advertised by the live MCP server; if absent, capture a close note via MCP `wiki_capture` or STOP. Never local-write or git-commit on the frozen leaf.
24
24
  - GitHub promotion is sg01 `wiki-snapshot`, not this leaf.
25
25
  - Report the freeze marker and stop. Do not treat dirty `~/wiki` as a sync job.
26
26
 
@@ -220,7 +220,7 @@ High-signal safety rule:
220
220
  Some older deployments separated a cloud-backed live vault from a Git snapshot worktree. That architecture still exists on protected snapshotters, but the snapshot worktree is **pipeline-internal**. Historical recipes that rsync into the worktree, reset it hard to origin/main, or run snapshot shell scripts by hand are obsolete and must not be copied.
221
221
 
222
222
  ## Stop conditions
223
- - `$VAULT/.WIKI_GIT_FROZEN` exists — frozen leaf; refuse git close/sync. Captures via HTTP MCP; work-item close waits Tier 2.
223
+ - `$VAULT/.WIKI_GIT_FROZEN` exists — frozen leaf; refuse git close/sync. Captures via HTTP MCP; work-item close uses MCP `wiki_workitem_write` if advertised, else fallback capture or STOP.
224
224
  - `skillwiki sync status` reports `not_a_repo` — the vault is not a git repository. On protected snapshotters this is expected for the FUSE live path; do not switch to `/root/wiki-git` to force a push.
225
225
  - Lint errors are found before a push — do not push until resolved.
226
226
  - `git push` or `git pull` fails with a network error — report and stop.
@@ -22,13 +22,20 @@ SkillWiki captures are HTTP MCP only (`type: http`). Claude/Grok use `SKILLWIKI_
22
22
  - Never auto-source `mcp.env` or auto-write `~/.cursor/mcp.json`, Grok `config.toml`, or `mcp.env`.
23
23
  - Never print the bearer token.
24
24
 
25
- ## Writes (captures-only)
25
+ ## Upgrade / migration
26
+
27
+ Remotes still on a pre-HTTP-MCP plugin or CLI must upgrade the plugin channel (and the CLI where that host has one), then start a **new session** — plugin instructions do not hot-swap. The operator check is the same `skillwiki doctor` as metal: default rows cover URL/auth presence and frozen-leaf write-path; `skillwiki doctor --check-mcp` does the live initialize handshake. Do not invent a second admin skill or HTTP MCP admin tool. `wiki_status` remains daemon health only.
28
+
29
+ ## Writes
26
30
 
27
31
  On leaf hosts, wiki captures go through MCP. Do **not** write `raw/transcripts/` or `log.md` as local files.
28
32
 
29
33
  1. Call MCP `wiki_capture` with `kind` (`task` | `idea` | `bug` | `note`), `project`, `title`, and `content`. Optional `agent_note`.
30
34
  2. Call MCP `wiki_log_append` when a structural `log.md` line is needed. Pass append-only `content`. Do not rewrite log history.
31
- 3. Write surface is captures-only. Do not call unpublished Tier 2 tools (`wiki_workitem_write`, `wiki_page_publish`). Those tools are not on the live daemon. Work-item close is not available from this MCP. Do not `git commit` / `wiki-push` against `~/wiki` for these captures. If `$VAULT/.WIKI_GIT_FROZEN` exists, that freeze is the reason nearby agents cannot close wiki work until Tier 2 ships.
35
+ 3. **Feature-detect work-item and page-publish tools**:
36
+ - When the server advertises `wiki_workitem_write`: use it to create, mutate, or close work items under `projects/<slug>/work/**` or update `projects/<slug>/knowledge.md`. Pass `path`, `content`, and `base_sha256` of existing bytes (from `wiki_read_page` or local mirror). On `FILE_CHANGED`, re-read and retry once.
37
+ - When the server advertises `wiki_page_publish`: use it to publish typed Layer-2 pages (`entities/`, `concepts/`, `comparisons/`, `queries/`, `meta/`) using the same CAS protocol.
38
+ - When those tools are **absent** from the live tool list (e.g. server is captures-only): do not call them or claim they exist. If `$VAULT/.WIKI_GIT_FROZEN` exists, fall back to capturing a close note via `wiki_capture` or STOP. Work-item close remains unavailable on the frozen leaf without `wiki_workitem_write`. Do not `git commit` / `wiki-push` against `~/wiki`.
32
39
 
33
40
  ## Reads
34
41
 
@@ -170,8 +170,9 @@ If the vault has `.WIKI_GIT_FROZEN`:
170
170
  | Intent | Allowed? | How |
171
171
  |---|---|---|
172
172
  | Capture note/idea/bug/task | Yes | HTTP MCP `wiki_capture` / `wiki_log_append` |
173
- | Close or mutate a work item (`projects/*/work/**`, `knowledge.md`) | **No** until Tier 2 | Do not `page publish` or git commit. Capture a close note via MCP or STOP. |
174
- | `wiki-sync` push/commit | **No** | GitHub is sg01 `wiki-snapshot`. |
173
+ | Close or mutate a work item (`projects/*/work/**`, `knowledge.md`) | Conditional | If live MCP tools include `wiki_workitem_write`, mutate/close via MCP CAS; if absent, capture a close note via MCP `wiki_capture` or STOP. Never local-write. |
174
+ | Publish typed Layer-2 page (`concepts/`, `queries/`, etc.) | Conditional | If live MCP tools include `wiki_page_publish`, publish via MCP CAS; if absent, STOP. Do not run local `skillwiki page publish`. |
175
+ | `wiki-sync` push/commit | **No** | Git fail-closed. GitHub is sg01 `wiki-snapshot`. |
175
176
  | Read local `~/wiki` | Yes | Mirror reads only |
176
177
  | `/wiki-add-task <text>` | You're in an interactive session on an authoring host | Creates `raw/transcripts/YYYY-MM-DD-{type}-{slug}.md` with ad-hoc capture frontmatter |
177
178
  | Filesystem drop | You're NOT in a Claude session (Obsidian, editor, sync) on an authoring host | Create a new `.md` file in `raw/transcripts/` — dev-loop discovers it on next cycle; do not edit it after capture |
@@ -245,7 +246,7 @@ Key CLI subcommands: `init`, `health`, `lint`, `config`, `doctor`, `path`, `lang
245
246
  Optional read-only MCP (`skillwiki mcp`) exposes pending/compile/review list tools. Do not use MCP for compile claim, publish, or review writes.
246
247
  `skillwiki claim` binds a transcript to a work item only through an exact `raw/transcripts/...` path in `source:` / `sources:` / `closes:`. A `--project` that contradicts the capture's explicit project is rejected. `skillwiki stale --project` uses exact normalized slugs, not substring matching. `skillwiki claims audit` is the read-only integrity report for duplicate, malformed, dangling, cross-project, and unbacked claims; it never rewrites captures or work items.
247
248
 
248
- Run `skillwiki health <vault> --out /tmp/skillwiki-health.json --no-fail` for a bounded whole-system report that includes the nonblocking source-lifecycle backlog. Pending captures are informational and do not make health fail. Run `skillwiki lint <vault> --summary` for lint-only bucket counts with capped examples and details commands. Run `skillwiki doctor` to diagnose setup/runtime issues only. Run `skillwiki config list` to see current configuration.
249
+ Run `skillwiki health <vault> --out /tmp/skillwiki-health.json --no-fail` for a bounded whole-system report that includes the nonblocking source-lifecycle backlog. Pending captures are informational and do not make health fail. Run `skillwiki lint <vault> --summary` for lint-only bucket counts with capped examples and details commands. Run `skillwiki doctor` to diagnose setup/runtime issues only, including HTTP MCP URL/auth presence and the frozen-leaf write path. Pass `--check-mcp` for a live initialize handshake and pin/tool-list lag; the default doctor path does not contact HTTP MCP. There is no second MCP admin skill. Run `skillwiki config list` to see current configuration.
249
250
 
250
251
  ## Runtime Host Context and Fleet Freshness
251
252
  Resolve the active project vault with `skillwiki path` first. Then pass that exact path to `skillwiki --human fleet context <vault>` for host identity and safety guidance. `fleet context` is authoritative for host identity. It overrides stale injected SessionStart context, remembered workspace context, and prior conversation summaries. `fleet context` is local and network-free; it reports `identity_status`, resolver trace, warnings, and the fact that remote freshness was not checked.
@@ -11,13 +11,13 @@ Standard four reads.
11
11
  ## Steps
12
12
  0. Resolve vault: `skillwiki path` (record source for context).
13
13
  - **CRITICAL**: Verify the correct vault when the user has multiple wiki instances (e.g., ~/wiki vs ~/wiki-fin). User may explicitly specify which vault to target — confirm before destructive operations.
14
- 1. For a whole-system health report, run `skillwiki health <vault> --out /tmp/skillwiki-health.json --no-fail`. Read the JSON envelope from stdout or the report file. Treat `skillwiki doctor` as setup/runtime diagnostics only, not a vault-content health report.
14
+ 1. For a whole-system health report, run `skillwiki health <vault> --out /tmp/skillwiki-health.json --no-fail`. Read the JSON envelope from stdout or the report file. Treat `skillwiki doctor` as setup/runtime diagnostics only, not a vault-content health report. HTTP MCP URL, pin lag, handshake, and frozen-leaf write-path belong to `skillwiki doctor` (pass `--check-mcp` for the live handshake). Do not use this skill for client/plugin fleet upgrades.
15
15
  2. For lint-only maintenance, run `skillwiki lint <vault> --summary`. This returns bounded bucket counts, capped examples, and `details_command` hints without full item arrays.
16
16
  3. Drill into important buckets with `skillwiki lint <vault> --only <bucket>` when examples are insufficient for remediation.
17
17
  4. Reason over findings; present grouped by severity with concrete suggested actions per kind. If the CLI was recently updated with new lint checks, re-running lint on the full vault may flag pre-existing pages that predate the new rule — treat these as legitimate findings, not false positives.
18
18
  5. Treat `sensitive_content` as a security error. Drill down with `skillwiki lint <vault> --only sensitive_content --human`; never print the secret value. Raw findings are report/quarantine-only: `--fix` must not redact, rewrite, or recompute existing raw evidence. Maintained-page repairs may proceed only through their approved write workflow.
19
19
  6. If `log_rotate_needed` is present and the user consents, run `skillwiki log-rotate <vault> --apply`. Otherwise leave alone.
20
- 7. **Post-migration verification**: If the user recently migrated content (e.g., moved entity/concept pages to another vault), re-run `skillwiki lint <vault> --summary` and verify that broken_wikilinks count decreased. Remaining broken links for migrated content indicate pages still referencing the moved files — these should be cleaned up (remove citations or migrate the referencing pages too).
20
+ 7. **Post-migration verification**: If the user recently migrated **vault content** (e.g., moved entity/concept pages to another vault), re-run `skillwiki lint <vault> --summary` and verify that broken_wikilinks count decreased. Remaining broken links for migrated content indicate pages still referencing the moved files — these should be cleaned up (remove citations or migrate the referencing pages too). This step is content moves only — not HTTP MCP client/plugin upgrades (those use `skillwiki doctor --check-mcp`).
21
21
  8. Append a `log.md` entry summarizing lint counts only when the user asked to record the maintenance result. Do not log routine `health` reports by default.
22
22
  ## Stop conditions
23
23
  None — lint reports all findings even on per-page errors.
@@ -20,7 +20,7 @@ If `$VAULT/.WIKI_GIT_FROZEN` exists, **fail closed** before any stash, commit, p
20
20
 
21
21
  - Do **not** `git add`, `git commit`, `git pull`, `git push`, or `skillwiki sync push`.
22
22
  - Captures go through HTTP MCP `wiki_capture` / `wiki_log_append`.
23
- - Closing or mutating work items is unpublished until Tier 2 (`wiki_workitem_write` / `wiki_page_publish`). Capture a close note via MCP or STOP.
23
+ - Work items mutate/close via MCP `wiki_workitem_write` when advertised by the live MCP server; if absent, capture a close note via MCP `wiki_capture` or STOP. Never local-write or git-commit on the frozen leaf.
24
24
  - GitHub promotion is sg01 `wiki-snapshot`, not this leaf.
25
25
  - Report the freeze marker and stop. Do not treat dirty `~/wiki` as a sync job.
26
26
 
@@ -220,7 +220,7 @@ High-signal safety rule:
220
220
  Some older deployments separated a cloud-backed live vault from a Git snapshot worktree. That architecture still exists on protected snapshotters, but the snapshot worktree is **pipeline-internal**. Historical recipes that rsync into the worktree, reset it hard to origin/main, or run snapshot shell scripts by hand are obsolete and must not be copied.
221
221
 
222
222
  ## Stop conditions
223
- - `$VAULT/.WIKI_GIT_FROZEN` exists — frozen leaf; refuse git close/sync. Captures via HTTP MCP; work-item close waits Tier 2.
223
+ - `$VAULT/.WIKI_GIT_FROZEN` exists — frozen leaf; refuse git close/sync. Captures via HTTP MCP; work-item close uses MCP `wiki_workitem_write` if advertised, else fallback capture or STOP.
224
224
  - `skillwiki sync status` reports `not_a_repo` — the vault is not a git repository. On protected snapshotters this is expected for the FUSE live path; do not switch to `/root/wiki-git` to force a push.
225
225
  - Lint errors are found before a push — do not push until resolved.
226
226
  - `git push` or `git pull` fails with a network error — report and stop.