skillwiki 0.10.108 → 0.10.110
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.
- package/dist/{chunk-JCDAEZVD.js → chunk-5MV5SSRM.js} +1 -1
- package/dist/{chunk-Z4NCF6K6.js → chunk-6X6PPQNI.js} +481 -179
- package/dist/{chunk-DTTKCCQ4.js → chunk-KX5Y3GSX.js} +2 -2
- package/dist/{chunk-QU2IXZRN.js → chunk-NA3TZ4JN.js} +2 -2
- package/dist/{chunk-PGD3J2GE.js → chunk-QU2CKXM4.js} +49 -37
- package/dist/{chunk-GJC2CI3W.js → chunk-R2DJ7IJ5.js} +103 -0
- package/dist/{chunk-HMHMCPCE.js → chunk-WPJHDRTP.js} +1 -1
- package/dist/cli.js +135 -101
- package/dist/{index-projection-ESBJOBS7.js → index-projection-6XGESSHV.js} +3 -3
- package/dist/{managed-write-preflight-AYD3CJYD.js → managed-write-preflight-JPOBVVSU.js} +4 -4
- package/dist/skillwiki-mcp.js +7 -7
- package/dist/sources-CLOF5VAT.js +9 -0
- package/dist/vault-sync/scripts/wiki-snapshot.sh +20 -12
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/.cursor-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
- package/skills/skills/skillwiki-connect/SKILL.md +8 -0
- package/skills/skills/skillwiki-mcp/SKILL.md +8 -0
- package/skills/skills/using-skillwiki/SKILL.md +1 -1
- package/skills/skills/wiki-init/SKILL.md +12 -11
- package/skills/skillwiki-connect/SKILL.md +8 -0
- package/skills/skillwiki-mcp/SKILL.md +8 -0
- package/skills/using-skillwiki/SKILL.md +1 -1
- package/skills/wiki-init/SKILL.md +12 -11
- package/templates/launchd/com.karlchow.skillwiki-clip-outbox.plist +22 -0
- package/templates/web-clipper/llm-wiki-clippings.json +1 -1
- package/templates/web-clipper/readme.txt +13 -13
- package/dist/sources-QTQSZMST.js +0 -9
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
renderRootIndex,
|
|
8
8
|
writeRootIndexProjection
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KX5Y3GSX.js";
|
|
10
10
|
import {
|
|
11
11
|
CONFIG_KEYS,
|
|
12
12
|
VAULT_HYGIENE_GENERATED_COMMIT_PATHS,
|
|
@@ -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,11 +41,11 @@ import {
|
|
|
40
41
|
snapshotterAliasForLocalHost,
|
|
41
42
|
toUndirectedWeighted,
|
|
42
43
|
writeDotenv
|
|
43
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-QU2CKXM4.js";
|
|
44
45
|
import {
|
|
45
46
|
atomicWriteText,
|
|
46
47
|
prepareTypedPage
|
|
47
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-WPJHDRTP.js";
|
|
48
49
|
import {
|
|
49
50
|
applySourceCompileClaim,
|
|
50
51
|
applySourceCompilePublished,
|
|
@@ -57,12 +58,13 @@ import {
|
|
|
57
58
|
planSourceCompileRelease,
|
|
58
59
|
planSourceReview,
|
|
59
60
|
runSourcesPending
|
|
60
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-NA3TZ4JN.js";
|
|
61
62
|
import {
|
|
62
63
|
eventPathFor,
|
|
63
64
|
writeLogEvent
|
|
64
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-5MV5SSRM.js";
|
|
65
66
|
import {
|
|
67
|
+
CLIP_OUTBOX_DEFAULTS,
|
|
66
68
|
CompoundSchema,
|
|
67
69
|
ExitCode,
|
|
68
70
|
MetaSchema,
|
|
@@ -72,8 +74,10 @@ import {
|
|
|
72
74
|
detectSchema,
|
|
73
75
|
err,
|
|
74
76
|
extractFrontmatter,
|
|
77
|
+
isClipOutboxKey,
|
|
75
78
|
isVaultSyncKey,
|
|
76
79
|
ok,
|
|
80
|
+
parseClipOutboxKeyValue,
|
|
77
81
|
parseVaultSyncKeyValue,
|
|
78
82
|
readPage,
|
|
79
83
|
redactSensitiveContent,
|
|
@@ -82,7 +86,7 @@ import {
|
|
|
82
86
|
scanVault,
|
|
83
87
|
splitFrontmatter,
|
|
84
88
|
systemdPropertyFor
|
|
85
|
-
} from "./chunk-
|
|
89
|
+
} from "./chunk-R2DJ7IJ5.js";
|
|
86
90
|
|
|
87
91
|
// src/commands/log-append.ts
|
|
88
92
|
import { readFile, stat } from "fs/promises";
|
|
@@ -636,7 +640,7 @@ function computeAdamicAdar(adj) {
|
|
|
636
640
|
import { resolve as resolvePath } from "path";
|
|
637
641
|
async function guardProtectedVaultWrite(input) {
|
|
638
642
|
const env = input.env ?? process.env;
|
|
639
|
-
const home = input.home
|
|
643
|
+
const home = resolveSkillwikiHome(input.home, env);
|
|
640
644
|
const cwd = input.cwd ?? process.cwd();
|
|
641
645
|
const liveVaultPath = await resolveLiveVaultPath({ env, home, cwd });
|
|
642
646
|
const load = await loadFleetManifestAndHost({
|
|
@@ -1016,14 +1020,14 @@ import { readFile as readFile5 } from "fs/promises";
|
|
|
1016
1020
|
import { existsSync as existsSync2 } from "fs";
|
|
1017
1021
|
import { join as join5 } from "path";
|
|
1018
1022
|
function validateKey(key) {
|
|
1019
|
-
return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key) || isVaultSyncKey(key);
|
|
1023
|
+
return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key) || isVaultSyncKey(key) || isClipOutboxKey(key);
|
|
1020
1024
|
}
|
|
1021
1025
|
function displayConfigValue(key, value) {
|
|
1022
1026
|
if (key === "SKILLWIKI_MCP_TOKEN") return value.length > 0 ? "TOKEN_SET" : "";
|
|
1023
1027
|
return value;
|
|
1024
1028
|
}
|
|
1025
1029
|
function configPath(home) {
|
|
1026
|
-
return join5(home, ".skillwiki", ".env");
|
|
1030
|
+
return join5(resolveSkillwikiHome(home), ".skillwiki", ".env");
|
|
1027
1031
|
}
|
|
1028
1032
|
async function runConfigGet(input) {
|
|
1029
1033
|
if (!validateKey(input.key)) {
|
|
@@ -1043,6 +1047,12 @@ async function runConfigSet(input) {
|
|
|
1043
1047
|
return { exitCode: ExitCode.INVALID_CONFIG_VALUE, result: err("INVALID_CONFIG_VALUE", { key: input.key, value: input.value, message: parsed.message }) };
|
|
1044
1048
|
}
|
|
1045
1049
|
}
|
|
1050
|
+
if (isClipOutboxKey(input.key)) {
|
|
1051
|
+
const parsed = parseClipOutboxKeyValue(input.key, input.value);
|
|
1052
|
+
if (!parsed.ok) {
|
|
1053
|
+
return { exitCode: ExitCode.INVALID_CONFIG_VALUE, result: err("INVALID_CONFIG_VALUE", { key: input.key, value: input.value, message: parsed.message }) };
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1046
1056
|
const filePath = configPath(input.home);
|
|
1047
1057
|
try {
|
|
1048
1058
|
let originalContent;
|
|
@@ -1104,7 +1114,7 @@ function mcpAuthFromEnv(env) {
|
|
|
1104
1114
|
}
|
|
1105
1115
|
async function resolveMcpAuthEnv(input) {
|
|
1106
1116
|
const processAuth = mcpAuthFromEnv(input.env ?? process.env);
|
|
1107
|
-
const dotenv = await parseDotenvFile(join6(input.home, ".skillwiki", ".env"));
|
|
1117
|
+
const dotenv = await parseDotenvFile(join6(resolveSkillwikiHome(input.home, input.env), ".skillwiki", ".env"));
|
|
1108
1118
|
const fileAuth = mcpAuthFromEnv(dotenv);
|
|
1109
1119
|
return {
|
|
1110
1120
|
url: processAuth.url ?? fileAuth.url,
|
|
@@ -1129,7 +1139,7 @@ function isMcpOnlyLeaf(input) {
|
|
|
1129
1139
|
}
|
|
1130
1140
|
|
|
1131
1141
|
// src/doctor/runner.ts
|
|
1132
|
-
import { existsSync as
|
|
1142
|
+
import { existsSync as existsSync20 } from "fs";
|
|
1133
1143
|
|
|
1134
1144
|
// src/doctor/probes/environment.ts
|
|
1135
1145
|
import { existsSync as existsSync4, lstatSync, readlinkSync } from "fs";
|
|
@@ -1398,7 +1408,7 @@ async function checkConfigFile(home) {
|
|
|
1398
1408
|
try {
|
|
1399
1409
|
const map = await parseDotenvFile(cfgPath);
|
|
1400
1410
|
const keys = Object.keys(map);
|
|
1401
|
-
return check("pass", "config_file", "Config file exists", `Found with keys: ${keys.length > 0 ? keys.join(", ") : "(none set)"}`);
|
|
1411
|
+
return check("pass", "config_file", "Config file exists", `Found ${cfgPath} with keys: ${keys.length > 0 ? keys.join(", ") : "(none set)"}`);
|
|
1402
1412
|
} catch (e) {
|
|
1403
1413
|
return check("warn", "config_file", "Config file exists", `Failed to parse ${cfgPath}: ${String(e)}`);
|
|
1404
1414
|
}
|
|
@@ -2101,7 +2111,9 @@ import { execSync as execSync4 } from "child_process";
|
|
|
2101
2111
|
// src/utils/s3-mount-health.ts
|
|
2102
2112
|
import { execSync as execSync3 } from "child_process";
|
|
2103
2113
|
import { platform as platform2 } from "os";
|
|
2104
|
-
import { readFileSync as readFileSync6
|
|
2114
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
2115
|
+
import { readFile as readFile6, writeFile as writeFile2, unlink } from "fs/promises";
|
|
2116
|
+
import { randomUUID } from "crypto";
|
|
2105
2117
|
import { join as join13 } from "path";
|
|
2106
2118
|
var OS = platform2();
|
|
2107
2119
|
function findRcloneMountPid() {
|
|
@@ -2259,36 +2271,45 @@ function detectFuseMount(vaultPath) {
|
|
|
2259
2271
|
}
|
|
2260
2272
|
return null;
|
|
2261
2273
|
}
|
|
2262
|
-
function writeTest(dir) {
|
|
2263
|
-
const testFile = join13(dir, `.doctor-write-test-${process.pid}.tmp`);
|
|
2264
|
-
const
|
|
2274
|
+
async function writeTest(dir, options = {}) {
|
|
2275
|
+
const testFile = join13(dir, options.fileName ?? `.doctor-write-test-${process.pid}.tmp`);
|
|
2276
|
+
const io = options.io ?? {
|
|
2277
|
+
write: (path, payload2) => writeFile2(path, payload2, "utf8"),
|
|
2278
|
+
read: (path) => readFile6(path, "utf8"),
|
|
2279
|
+
remove: (path) => unlink(path)
|
|
2280
|
+
};
|
|
2281
|
+
const payload = `skillwiki write test \u2014 ${(/* @__PURE__ */ new Date()).toISOString()} \u2014 ${randomUUID()}`;
|
|
2265
2282
|
const start = Date.now();
|
|
2266
2283
|
try {
|
|
2267
|
-
|
|
2284
|
+
await io.write(testFile, payload);
|
|
2268
2285
|
} catch (e) {
|
|
2286
|
+
try {
|
|
2287
|
+
await io.remove(testFile);
|
|
2288
|
+
} catch {
|
|
2289
|
+
}
|
|
2269
2290
|
return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
|
|
2270
2291
|
}
|
|
2271
2292
|
const writeMs = Date.now() - start;
|
|
2272
2293
|
const readStart = Date.now();
|
|
2273
2294
|
try {
|
|
2274
|
-
const back =
|
|
2295
|
+
const back = await io.read(testFile);
|
|
2275
2296
|
const readMs = Date.now() - readStart;
|
|
2276
2297
|
if (back !== payload) {
|
|
2277
2298
|
try {
|
|
2278
|
-
|
|
2299
|
+
await io.remove(testFile);
|
|
2279
2300
|
} catch {
|
|
2280
2301
|
}
|
|
2281
2302
|
return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
|
|
2282
2303
|
}
|
|
2283
2304
|
} catch (e) {
|
|
2284
2305
|
try {
|
|
2285
|
-
|
|
2306
|
+
await io.remove(testFile);
|
|
2286
2307
|
} catch {
|
|
2287
2308
|
}
|
|
2288
2309
|
return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
|
|
2289
2310
|
}
|
|
2290
2311
|
try {
|
|
2291
|
-
|
|
2312
|
+
await io.remove(testFile);
|
|
2292
2313
|
} catch {
|
|
2293
2314
|
}
|
|
2294
2315
|
return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
|
|
@@ -2522,7 +2543,7 @@ function checkRcloneVersion(resolvedPath, vaultSyncInstalled, pushEnabled = true
|
|
|
2522
2543
|
}
|
|
2523
2544
|
return check("pass", "rclone_version", "rclone version", ver.raw);
|
|
2524
2545
|
}
|
|
2525
|
-
function checkWriteTest(resolvedPath) {
|
|
2546
|
+
async function checkWriteTest(resolvedPath) {
|
|
2526
2547
|
if (!resolvedPath) {
|
|
2527
2548
|
return check("pass", "s3_write_test", "S3 write test", "No vault path \u2014 check skipped");
|
|
2528
2549
|
}
|
|
@@ -2534,7 +2555,7 @@ function checkWriteTest(resolvedPath) {
|
|
|
2534
2555
|
if (!existsSync10(conceptsDir)) {
|
|
2535
2556
|
return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
|
|
2536
2557
|
}
|
|
2537
|
-
const result = writeTest(conceptsDir);
|
|
2558
|
+
const result = await writeTest(conceptsDir);
|
|
2538
2559
|
if (result.success) {
|
|
2539
2560
|
const totalMs = result.writeMs + result.readMs;
|
|
2540
2561
|
if (totalMs > 3e3) {
|
|
@@ -2617,13 +2638,13 @@ function checkVfsCacheHealth(resolvedPath) {
|
|
|
2617
2638
|
}
|
|
2618
2639
|
var s3MountHealthProbe = {
|
|
2619
2640
|
id: "s3_mount_health",
|
|
2620
|
-
run(ctx) {
|
|
2641
|
+
async run(ctx) {
|
|
2621
2642
|
return [
|
|
2622
2643
|
checkS3MountPerf(ctx.resolvedPath),
|
|
2623
2644
|
checkS3MountFreshness(ctx.resolvedPath),
|
|
2624
2645
|
checkRcloneFlagAudit(ctx.resolvedPath),
|
|
2625
2646
|
checkRcloneVersion(ctx.resolvedPath, ctx.vsConfig.installed, ctx.vsConfig.pushEnabled !== false, ctx.mcpOnlyLeaf),
|
|
2626
|
-
checkWriteTest(ctx.resolvedPath),
|
|
2647
|
+
await checkWriteTest(ctx.resolvedPath),
|
|
2627
2648
|
checkVfsCacheHealth(ctx.resolvedPath)
|
|
2628
2649
|
];
|
|
2629
2650
|
}
|
|
@@ -4255,6 +4276,46 @@ var dsStoreNoiseProbe = {
|
|
|
4255
4276
|
// src/doctor/probes/mcp-handshake.ts
|
|
4256
4277
|
import { existsSync as existsSync17 } from "fs";
|
|
4257
4278
|
import { join as join21 } from "path";
|
|
4279
|
+
|
|
4280
|
+
// src/utils/mcp-client.ts
|
|
4281
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4282
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4283
|
+
var MCP_PROBE_TIMEOUT_MS = 1e4;
|
|
4284
|
+
async function withMcpClient(input, probe) {
|
|
4285
|
+
const controller = new AbortController();
|
|
4286
|
+
const client = new Client({ name: input.clientName, version: input.version });
|
|
4287
|
+
const fetchFn = input.fetchFn ?? globalThis.fetch;
|
|
4288
|
+
const transport = new StreamableHTTPClientTransport(new URL(input.url), {
|
|
4289
|
+
requestInit: { headers: { Authorization: `Bearer ${input.token}` } },
|
|
4290
|
+
fetch: (url, init) => fetchFn(url, {
|
|
4291
|
+
...init,
|
|
4292
|
+
signal: init?.signal ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
|
|
4293
|
+
})
|
|
4294
|
+
});
|
|
4295
|
+
let timer;
|
|
4296
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
4297
|
+
timer = setTimeout(() => {
|
|
4298
|
+
const error = new Error(`MCP probe timed out after ${MCP_PROBE_TIMEOUT_MS}ms`);
|
|
4299
|
+
reject(error);
|
|
4300
|
+
controller.abort(error);
|
|
4301
|
+
}, MCP_PROBE_TIMEOUT_MS);
|
|
4302
|
+
});
|
|
4303
|
+
try {
|
|
4304
|
+
const work = async () => {
|
|
4305
|
+
await client.connect(transport, { signal: controller.signal, timeout: MCP_PROBE_TIMEOUT_MS });
|
|
4306
|
+
return probe(client, controller.signal);
|
|
4307
|
+
};
|
|
4308
|
+
return await Promise.race([work(), deadline]);
|
|
4309
|
+
} catch (error) {
|
|
4310
|
+
throw new Error(redactMcpSecret(error instanceof Error ? error.message : String(error), input.token));
|
|
4311
|
+
} finally {
|
|
4312
|
+
clearTimeout(timer);
|
|
4313
|
+
controller.abort();
|
|
4314
|
+
await client.close();
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
|
|
4318
|
+
// src/doctor/probes/mcp-handshake.ts
|
|
4258
4319
|
var DEFAULT_MCP_URL = "https://wiki.karldigi.dev/mcp";
|
|
4259
4320
|
function mcpEnv(ctx) {
|
|
4260
4321
|
return ctx.input.env ?? process.env;
|
|
@@ -4322,54 +4383,6 @@ function redactSecret(detail, secret) {
|
|
|
4322
4383
|
function handshakeRow(status, detail, secret) {
|
|
4323
4384
|
return check(status, HANDSHAKE_ID, HANDSHAKE_LABEL, redactSecret(detail, secret));
|
|
4324
4385
|
}
|
|
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
4386
|
async function checkMcpHandshake(ctx) {
|
|
4374
4387
|
if (!ctx.input.checkMcp) {
|
|
4375
4388
|
return handshakeRow("pass", "not requested \u2014 check skipped");
|
|
@@ -4384,33 +4397,16 @@ async function checkMcpHandshake(ctx) {
|
|
|
4384
4397
|
const fetchFn = ctx.input.mcpFetch ?? globalThis.fetch;
|
|
4385
4398
|
const shipped = ctx.input.currentVersion;
|
|
4386
4399
|
try {
|
|
4387
|
-
const
|
|
4388
|
-
fetchFn,
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
{
|
|
4394
|
-
protocolVersion: "2025-11-25",
|
|
4395
|
-
capabilities: {},
|
|
4396
|
-
clientInfo: { name: "skillwiki-doctor", version: shipped }
|
|
4397
|
-
}
|
|
4400
|
+
const { advertised, tools } = await withMcpClient(
|
|
4401
|
+
{ fetchFn, url, token, clientName: "skillwiki-doctor", version: shipped },
|
|
4402
|
+
async (client, signal) => ({
|
|
4403
|
+
advertised: client.getServerVersion()?.version ?? "",
|
|
4404
|
+
tools: (await client.listTools({}, { signal })).tools.map((tool) => tool.name)
|
|
4405
|
+
})
|
|
4398
4406
|
);
|
|
4399
|
-
const serverInfo = init.result.serverInfo;
|
|
4400
|
-
const advertised = typeof serverInfo === "object" && serverInfo !== null && typeof serverInfo.version === "string" ? serverInfo.version : "";
|
|
4401
4407
|
if (!advertised) {
|
|
4402
4408
|
return handshakeRow("error", "handshake failed \u2014 missing serverInfo.version", token);
|
|
4403
4409
|
}
|
|
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
4410
|
const frozen = Boolean(ctx.resolvedPath && existsSync17(join21(ctx.resolvedPath, ".WIKI_GIT_FROZEN")));
|
|
4415
4411
|
if (frozen && !tools.includes(WORKITEM_TOOL)) {
|
|
4416
4412
|
return handshakeRow(
|
|
@@ -4446,6 +4442,307 @@ var mcpHandshakeProbe = {
|
|
|
4446
4442
|
}
|
|
4447
4443
|
};
|
|
4448
4444
|
|
|
4445
|
+
// src/doctor/probes/clip-outbox.ts
|
|
4446
|
+
import { existsSync as existsSync19 } from "fs";
|
|
4447
|
+
import { execSync as execSync7 } from "child_process";
|
|
4448
|
+
import { platform as platform6 } from "os";
|
|
4449
|
+
import { join as join23 } from "path";
|
|
4450
|
+
|
|
4451
|
+
// src/utils/clip-outbox-config.ts
|
|
4452
|
+
import { realpathSync, statSync as statSync3, existsSync as existsSync18, accessSync, constants } from "fs";
|
|
4453
|
+
import { homedir } from "os";
|
|
4454
|
+
import { isAbsolute, join as join22, resolve as resolve3, sep as sep2 } from "path";
|
|
4455
|
+
var CLIP_NOT_CONFIGURED = "clip channel not configured";
|
|
4456
|
+
var PRODUCTION_WATCHER_LABEL = CLIP_OUTBOX_DEFAULTS.watcher_label;
|
|
4457
|
+
var RETIRED_WATCHER_LABELS = [
|
|
4458
|
+
"com.karlchow.wiki-dev-outbox",
|
|
4459
|
+
"com.karlchow.wiki-dev-sse",
|
|
4460
|
+
"com.karlchow.wiki-dev-hourly",
|
|
4461
|
+
"com.karlchow.skillwiki-note-outbox"
|
|
4462
|
+
];
|
|
4463
|
+
function expandUserPath(raw, home) {
|
|
4464
|
+
if (raw === "~") return home;
|
|
4465
|
+
if (raw.startsWith("~/")) return resolve3(home, raw.slice(2));
|
|
4466
|
+
return raw;
|
|
4467
|
+
}
|
|
4468
|
+
function canonicalPath(raw, home) {
|
|
4469
|
+
const expanded = expandUserPath(raw.trim(), home);
|
|
4470
|
+
if (expanded.includes("\0")) return { ok: false, message: "path contains NUL" };
|
|
4471
|
+
if (expanded.includes("://") || expanded.includes("@")) {
|
|
4472
|
+
return { ok: false, message: "path must not include URL credentials" };
|
|
4473
|
+
}
|
|
4474
|
+
if (!isAbsolute(expanded)) return { ok: false, message: "must be an absolute path" };
|
|
4475
|
+
try {
|
|
4476
|
+
const real = existsSync18(expanded) ? realpathSync(expanded) : resolve3(expanded);
|
|
4477
|
+
if (real.split(sep2).some((seg) => seg === "..")) {
|
|
4478
|
+
return { ok: false, message: "path must not contain traversal" };
|
|
4479
|
+
}
|
|
4480
|
+
return { ok: true, path: real };
|
|
4481
|
+
} catch (error) {
|
|
4482
|
+
return { ok: false, message: error instanceof Error ? error.message : "invalid path" };
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
function nestedOrEqual(a, b) {
|
|
4486
|
+
if (a === b) return true;
|
|
4487
|
+
const prefixA = a.endsWith(sep2) ? a : a + sep2;
|
|
4488
|
+
const prefixB = b.endsWith(sep2) ? b : b + sep2;
|
|
4489
|
+
return prefixA.startsWith(prefixB) || prefixB.startsWith(prefixA);
|
|
4490
|
+
}
|
|
4491
|
+
function intField(value, fallback) {
|
|
4492
|
+
return Number(value && value.length > 0 ? value : fallback);
|
|
4493
|
+
}
|
|
4494
|
+
function requireWritableDir(path, label) {
|
|
4495
|
+
try {
|
|
4496
|
+
const st = statSync3(path);
|
|
4497
|
+
if (!st.isDirectory()) return `${label} is not a directory`;
|
|
4498
|
+
accessSync(path, constants.W_OK);
|
|
4499
|
+
return void 0;
|
|
4500
|
+
} catch {
|
|
4501
|
+
return `${label} is missing or not writable`;
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
function resolveClipOutboxConfig(map, home) {
|
|
4505
|
+
const present = Object.keys(map).filter(isClipOutboxKey);
|
|
4506
|
+
for (const key of present) {
|
|
4507
|
+
const parsed = parseClipOutboxKeyValue(key, map[key] ?? "");
|
|
4508
|
+
if (!parsed.ok) return { ok: false, message: `${key}: ${parsed.message}` };
|
|
4509
|
+
}
|
|
4510
|
+
const enabled = (map["clip_outbox.enabled"] ?? CLIP_OUTBOX_DEFAULTS.enabled) === "true";
|
|
4511
|
+
if (!enabled) {
|
|
4512
|
+
return { ok: true, enabled: false };
|
|
4513
|
+
}
|
|
4514
|
+
const required = [
|
|
4515
|
+
"clip_outbox.root",
|
|
4516
|
+
"clip_outbox.state_root",
|
|
4517
|
+
"clip_outbox.mirror_root",
|
|
4518
|
+
"clip_outbox.ready_url",
|
|
4519
|
+
"clip_outbox.s3_credential_ref",
|
|
4520
|
+
"clip_outbox.ready_credential_ref"
|
|
4521
|
+
];
|
|
4522
|
+
for (const key of required) {
|
|
4523
|
+
if (!map[key]) return { ok: false, message: `${key} is required when clip_outbox.enabled=true` };
|
|
4524
|
+
}
|
|
4525
|
+
const d8Absent = map["clip_outbox.d8_note_absent"] === "true";
|
|
4526
|
+
const d8RootRaw = map["clip_outbox.d8_note_root"];
|
|
4527
|
+
if (d8Absent && d8RootRaw) {
|
|
4528
|
+
return { ok: false, message: "clip_outbox.d8_note_root and clip_outbox.d8_note_absent cannot both be set" };
|
|
4529
|
+
}
|
|
4530
|
+
if (!d8Absent && !d8RootRaw) {
|
|
4531
|
+
return { ok: false, message: "clip_outbox.d8_note_root or clip_outbox.d8_note_absent=true is required when enabled" };
|
|
4532
|
+
}
|
|
4533
|
+
const root = canonicalPath(map["clip_outbox.root"], home);
|
|
4534
|
+
const stateRoot = canonicalPath(map["clip_outbox.state_root"], home);
|
|
4535
|
+
const mirrorRoot = canonicalPath(map["clip_outbox.mirror_root"], home);
|
|
4536
|
+
if (!root.ok) return { ok: false, message: `clip_outbox.root: ${root.message}` };
|
|
4537
|
+
if (!stateRoot.ok) return { ok: false, message: `clip_outbox.state_root: ${stateRoot.message}` };
|
|
4538
|
+
if (!mirrorRoot.ok) return { ok: false, message: `clip_outbox.mirror_root: ${mirrorRoot.message}` };
|
|
4539
|
+
let d8NoteRoot = null;
|
|
4540
|
+
if (d8RootRaw) {
|
|
4541
|
+
const d8 = canonicalPath(d8RootRaw, home);
|
|
4542
|
+
if (!d8.ok) return { ok: false, message: `clip_outbox.d8_note_root: ${d8.message}` };
|
|
4543
|
+
d8NoteRoot = d8.path;
|
|
4544
|
+
}
|
|
4545
|
+
if (nestedOrEqual(root.path, mirrorRoot.path)) {
|
|
4546
|
+
return { ok: false, message: "clip_outbox.root must be outside the frozen mirror" };
|
|
4547
|
+
}
|
|
4548
|
+
if (nestedOrEqual(root.path, stateRoot.path) || nestedOrEqual(stateRoot.path, mirrorRoot.path)) {
|
|
4549
|
+
return { ok: false, message: "clip_outbox.state_root must be disjoint from outbox and mirror" };
|
|
4550
|
+
}
|
|
4551
|
+
if (d8NoteRoot && (nestedOrEqual(root.path, d8NoteRoot) || nestedOrEqual(stateRoot.path, d8NoteRoot))) {
|
|
4552
|
+
return { ok: false, message: "clip outbox must not equal or nest the D8 note outbox" };
|
|
4553
|
+
}
|
|
4554
|
+
const rootErr = requireWritableDir(root.path, "clip_outbox.root");
|
|
4555
|
+
if (rootErr) return { ok: false, message: rootErr };
|
|
4556
|
+
const stateErr = requireWritableDir(stateRoot.path, "clip_outbox.state_root");
|
|
4557
|
+
if (stateErr) {
|
|
4558
|
+
if (existsSync18(stateRoot.path)) return { ok: false, message: stateErr };
|
|
4559
|
+
const parentErr = requireWritableDir(resolve3(stateRoot.path, ".."), "clip_outbox.state_root parent");
|
|
4560
|
+
if (parentErr) return { ok: false, message: stateErr };
|
|
4561
|
+
}
|
|
4562
|
+
if (!existsSync18(mirrorRoot.path)) {
|
|
4563
|
+
return { ok: false, message: "clip_outbox.mirror_root does not exist" };
|
|
4564
|
+
}
|
|
4565
|
+
const vaultId = map["clip_outbox.vault_id"] ?? CLIP_OUTBOX_DEFAULTS.vault_id;
|
|
4566
|
+
if (vaultId !== "central") return { ok: false, message: "clip_outbox.vault_id must be central" };
|
|
4567
|
+
const destination = map["clip_outbox.destination"] ?? CLIP_OUTBOX_DEFAULTS.destination;
|
|
4568
|
+
if (destination !== CLIP_OUTBOX_DEFAULTS.destination) {
|
|
4569
|
+
return { ok: false, message: "clip_outbox.destination must be seaweed-wiki:cloud/wiki" };
|
|
4570
|
+
}
|
|
4571
|
+
const mode = map["clip_outbox.mode"] ?? CLIP_OUTBOX_DEFAULTS.mode;
|
|
4572
|
+
if (mode !== "event-watch") return { ok: false, message: "clip_outbox.mode must be event-watch" };
|
|
4573
|
+
const watcherLabel = map["clip_outbox.watcher_label"] ?? CLIP_OUTBOX_DEFAULTS.watcher_label;
|
|
4574
|
+
if (watcherLabel !== PRODUCTION_WATCHER_LABEL) {
|
|
4575
|
+
return { ok: false, message: "clip_outbox.watcher_label must be the production clip watcher" };
|
|
4576
|
+
}
|
|
4577
|
+
const imageTypes = (map["clip_outbox.image_types"] ?? CLIP_OUTBOX_DEFAULTS.image_types).split(",").map((p) => p.trim()).filter(Boolean);
|
|
4578
|
+
return {
|
|
4579
|
+
ok: true,
|
|
4580
|
+
enabled: true,
|
|
4581
|
+
config: {
|
|
4582
|
+
enabled: true,
|
|
4583
|
+
root: root.path,
|
|
4584
|
+
stateRoot: stateRoot.path,
|
|
4585
|
+
mirrorRoot: mirrorRoot.path,
|
|
4586
|
+
d8NoteRoot,
|
|
4587
|
+
d8NoteAbsent: d8Absent,
|
|
4588
|
+
vaultId: "central",
|
|
4589
|
+
allowedPrefixes: ["raw/articles", "raw/assets"],
|
|
4590
|
+
destination: "seaweed-wiki:cloud/wiki",
|
|
4591
|
+
mode: "event-watch",
|
|
4592
|
+
pollFallbackEnabled: (map["clip_outbox.poll_fallback_enabled"] ?? CLIP_OUTBOX_DEFAULTS.poll_fallback_enabled) === "true",
|
|
4593
|
+
reconcileSeconds: intField(map["clip_outbox.reconcile_seconds"], CLIP_OUTBOX_DEFAULTS.reconcile_seconds),
|
|
4594
|
+
debounceMs: intField(map["clip_outbox.debounce_ms"], CLIP_OUTBOX_DEFAULTS.debounce_ms),
|
|
4595
|
+
stableHashMs: intField(map["clip_outbox.stable_hash_ms"], CLIP_OUTBOX_DEFAULTS.stable_hash_ms),
|
|
4596
|
+
maxArticleBytes: intField(map["clip_outbox.max_article_bytes"], CLIP_OUTBOX_DEFAULTS.max_article_bytes),
|
|
4597
|
+
maxImages: intField(map["clip_outbox.max_images"], CLIP_OUTBOX_DEFAULTS.max_images),
|
|
4598
|
+
maxImageBytes: intField(map["clip_outbox.max_image_bytes"], CLIP_OUTBOX_DEFAULTS.max_image_bytes),
|
|
4599
|
+
maxBundleBytes: intField(map["clip_outbox.max_bundle_bytes"], CLIP_OUTBOX_DEFAULTS.max_bundle_bytes),
|
|
4600
|
+
maxPixels: intField(map["clip_outbox.max_pixels"], CLIP_OUTBOX_DEFAULTS.max_pixels),
|
|
4601
|
+
downloadConcurrency: intField(map["clip_outbox.download_concurrency"], CLIP_OUTBOX_DEFAULTS.download_concurrency),
|
|
4602
|
+
imageTypes,
|
|
4603
|
+
maxRedirects: intField(map["clip_outbox.max_redirects"], CLIP_OUTBOX_DEFAULTS.max_redirects),
|
|
4604
|
+
requestTimeoutMs: intField(map["clip_outbox.request_timeout_ms"], CLIP_OUTBOX_DEFAULTS.request_timeout_ms),
|
|
4605
|
+
localizeTimeoutMs: intField(map["clip_outbox.localize_timeout_ms"], CLIP_OUTBOX_DEFAULTS.localize_timeout_ms),
|
|
4606
|
+
readyUrl: map["clip_outbox.ready_url"],
|
|
4607
|
+
s3CredentialRef: map["clip_outbox.s3_credential_ref"],
|
|
4608
|
+
readyCredentialRef: map["clip_outbox.ready_credential_ref"],
|
|
4609
|
+
watcherLabel: PRODUCTION_WATCHER_LABEL
|
|
4610
|
+
}
|
|
4611
|
+
};
|
|
4612
|
+
}
|
|
4613
|
+
function clipOutboxConfigPath(home) {
|
|
4614
|
+
return join22(resolveSkillwikiHome(home), ".skillwiki", ".env");
|
|
4615
|
+
}
|
|
4616
|
+
async function loadClipOutboxConfig(home) {
|
|
4617
|
+
const map = await parseDotenvFile(clipOutboxConfigPath(home));
|
|
4618
|
+
return resolveClipOutboxConfig(map, home || homedir());
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
// src/doctor/probes/clip-outbox.ts
|
|
4622
|
+
var CLIP_CHECK_IDS = [
|
|
4623
|
+
"clip_config",
|
|
4624
|
+
"clip_frozen_mirror",
|
|
4625
|
+
"clip_push_isolation",
|
|
4626
|
+
"clip_outbox_root",
|
|
4627
|
+
"clip_watcher",
|
|
4628
|
+
"clip_storage_credentials",
|
|
4629
|
+
"clip_ready_channel",
|
|
4630
|
+
"clip_mirror_listener"
|
|
4631
|
+
];
|
|
4632
|
+
function notConfigured() {
|
|
4633
|
+
return CLIP_CHECK_IDS.map(
|
|
4634
|
+
(id) => check("info", id, clipLabel(id), CLIP_NOT_CONFIGURED)
|
|
4635
|
+
);
|
|
4636
|
+
}
|
|
4637
|
+
function clipLabel(id) {
|
|
4638
|
+
switch (id) {
|
|
4639
|
+
case "clip_config":
|
|
4640
|
+
return "Clip outbox configuration";
|
|
4641
|
+
case "clip_frozen_mirror":
|
|
4642
|
+
return "Clip frozen mirror";
|
|
4643
|
+
case "clip_push_isolation":
|
|
4644
|
+
return "Clip push isolation";
|
|
4645
|
+
case "clip_outbox_root":
|
|
4646
|
+
return "Clip outbox root";
|
|
4647
|
+
case "clip_watcher":
|
|
4648
|
+
return "Clip outbox watcher";
|
|
4649
|
+
case "clip_storage_credentials":
|
|
4650
|
+
return "Clip storage credentials";
|
|
4651
|
+
case "clip_ready_channel":
|
|
4652
|
+
return "Clip ready channel";
|
|
4653
|
+
case "clip_mirror_listener":
|
|
4654
|
+
return "Clip mirror listener";
|
|
4655
|
+
default:
|
|
4656
|
+
return id;
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
function launchdLoaded(label) {
|
|
4660
|
+
if (platform6() !== "darwin") return false;
|
|
4661
|
+
try {
|
|
4662
|
+
execSync7(`launchctl list ${label}`, {
|
|
4663
|
+
encoding: "utf8",
|
|
4664
|
+
timeout: 2e3,
|
|
4665
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
4666
|
+
});
|
|
4667
|
+
return true;
|
|
4668
|
+
} catch {
|
|
4669
|
+
return false;
|
|
4670
|
+
}
|
|
4671
|
+
}
|
|
4672
|
+
function watcherStatus(env, label) {
|
|
4673
|
+
const fixture = env.CLIP_WATCHER_FIXTURE;
|
|
4674
|
+
if (fixture === "loaded-enabled") return { loaded: true, enabled: true, kind: "production" };
|
|
4675
|
+
if (fixture === "stopped") return { loaded: false, enabled: false, kind: "production" };
|
|
4676
|
+
if (fixture === "retired-wiki-dev") return { loaded: true, enabled: true, kind: "retired" };
|
|
4677
|
+
if (fixture === "missing") return { loaded: false, enabled: false, kind: "unknown" };
|
|
4678
|
+
if (RETIRED_WATCHER_LABELS.includes(label)) {
|
|
4679
|
+
return { loaded: false, enabled: false, kind: "retired" };
|
|
4680
|
+
}
|
|
4681
|
+
if (!launchdLoaded(label)) {
|
|
4682
|
+
return { loaded: false, enabled: false, kind: "unknown" };
|
|
4683
|
+
}
|
|
4684
|
+
const plist = join23(process.env.HOME ?? "", "Library", "LaunchAgents", `${label}.plist`);
|
|
4685
|
+
return { loaded: true, enabled: existsSync19(plist), kind: "production" };
|
|
4686
|
+
}
|
|
4687
|
+
function pushLoaded(env) {
|
|
4688
|
+
if (env.CLIP_PUSH_FIXTURE === "loaded") return true;
|
|
4689
|
+
if (env.CLIP_PUSH_FIXTURE === "unloaded") return false;
|
|
4690
|
+
return launchdLoaded("com.karlchow.wiki-push");
|
|
4691
|
+
}
|
|
4692
|
+
function credentialPresent(env, ref) {
|
|
4693
|
+
if (env.CLIP_CREDENTIAL_FIXTURE === "present") return true;
|
|
4694
|
+
if (env.CLIP_CREDENTIAL_FIXTURE === "absent") return false;
|
|
4695
|
+
const value = env[ref];
|
|
4696
|
+
return typeof value === "string" && value.length > 0;
|
|
4697
|
+
}
|
|
4698
|
+
function listenerOk(env) {
|
|
4699
|
+
if (env.CLIP_LISTENER_FIXTURE === "ok") return true;
|
|
4700
|
+
return false;
|
|
4701
|
+
}
|
|
4702
|
+
function conditionalCreateEvidence(env) {
|
|
4703
|
+
return env.CLIP_CONDITIONAL_CREATE_FIXTURE === "ok";
|
|
4704
|
+
}
|
|
4705
|
+
var clipOutboxProbe = {
|
|
4706
|
+
id: "clip_outbox",
|
|
4707
|
+
async run(ctx) {
|
|
4708
|
+
if (ctx.mcpOnlyLeaf) {
|
|
4709
|
+
return CLIP_CHECK_IDS.map(
|
|
4710
|
+
(id) => check("info", id, clipLabel(id), MCP_ONLY_LEAF_SKIP)
|
|
4711
|
+
);
|
|
4712
|
+
}
|
|
4713
|
+
const env = ctx.input.env ?? process.env;
|
|
4714
|
+
const resolved = await loadClipOutboxConfig(ctx.input.home);
|
|
4715
|
+
if (!resolved.ok) {
|
|
4716
|
+
return [
|
|
4717
|
+
check("error", "clip_config", clipLabel("clip_config"), resolved.message),
|
|
4718
|
+
...CLIP_CHECK_IDS.slice(1).map((id) => check("error", id, clipLabel(id), "clip configuration invalid"))
|
|
4719
|
+
];
|
|
4720
|
+
}
|
|
4721
|
+
if (!resolved.enabled) {
|
|
4722
|
+
return notConfigured();
|
|
4723
|
+
}
|
|
4724
|
+
const cfg = resolved.config;
|
|
4725
|
+
const frozen = existsSync19(join23(cfg.mirrorRoot, ".WIKI_GIT_FROZEN"));
|
|
4726
|
+
const pushOn = ctx.vsConfig.pushEnabled !== false;
|
|
4727
|
+
const pushIsLoaded = pushLoaded(env);
|
|
4728
|
+
const watch = watcherStatus(env, cfg.watcherLabel);
|
|
4729
|
+
const s3Creds = credentialPresent(env, cfg.s3CredentialRef);
|
|
4730
|
+
const readyCreds = credentialPresent(env, cfg.readyCredentialRef);
|
|
4731
|
+
const condCreate = conditionalCreateEvidence(env);
|
|
4732
|
+
const listener = listenerOk(env);
|
|
4733
|
+
return [
|
|
4734
|
+
check("pass", "clip_config", clipLabel("clip_config"), "clip_outbox.enabled=true; schema valid"),
|
|
4735
|
+
frozen ? check("pass", "clip_frozen_mirror", clipLabel("clip_frozen_mirror"), "frozen marker present") : check("error", "clip_frozen_mirror", clipLabel("clip_frozen_mirror"), "frozen marker missing; do not unfreeze"),
|
|
4736
|
+
!pushOn && !pushIsLoaded ? check("pass", "clip_push_isolation", clipLabel("clip_push_isolation"), "wiki-push disabled and unloaded") : check("error", "clip_push_isolation", clipLabel("clip_push_isolation"), "wiki-push must stay disabled and unloaded"),
|
|
4737
|
+
check("pass", "clip_outbox_root", clipLabel("clip_outbox_root"), "outbox and state roots are canonical and disjoint"),
|
|
4738
|
+
watch.kind === "retired" ? check("error", "clip_watcher", clipLabel("clip_watcher"), "retired wiki-dev or D8 job does not satisfy the clip watcher") : watch.loaded && watch.enabled && watch.kind === "production" ? check("pass", "clip_watcher", clipLabel("clip_watcher"), `loaded and enabled: ${PRODUCTION_WATCHER_LABEL}`) : check("error", "clip_watcher", clipLabel("clip_watcher"), "production clip watcher not loaded and enabled"),
|
|
4739
|
+
s3Creds && readyCreds && condCreate ? check("pass", "clip_storage_credentials", clipLabel("clip_storage_credentials"), "credential references present; conditional-create evidence available") : check("error", "clip_storage_credentials", clipLabel("clip_storage_credentials"), "missing credential reference or conditional-create evidence"),
|
|
4740
|
+
readyCreds ? check("pass", "clip_ready_channel", clipLabel("clip_ready_channel"), "ready URL and credential reference present") : check("error", "clip_ready_channel", clipLabel("clip_ready_channel"), "ready credential missing"),
|
|
4741
|
+
listener ? check("pass", "clip_mirror_listener", clipLabel("clip_mirror_listener"), "inbound production SSE listener configured") : check("error", "clip_mirror_listener", clipLabel("clip_mirror_listener"), "inbound SSE listener disabled or missing")
|
|
4742
|
+
];
|
|
4743
|
+
}
|
|
4744
|
+
};
|
|
4745
|
+
|
|
4449
4746
|
// src/doctor/probes/index.ts
|
|
4450
4747
|
var DOCTOR_PROBES = [
|
|
4451
4748
|
environmentProbe,
|
|
@@ -4460,7 +4757,8 @@ var DOCTOR_PROBES = [
|
|
|
4460
4757
|
fuseStalenessProbe,
|
|
4461
4758
|
activationMarkerProbe,
|
|
4462
4759
|
dsStoreNoiseProbe,
|
|
4463
|
-
mcpHandshakeProbe
|
|
4760
|
+
mcpHandshakeProbe,
|
|
4761
|
+
clipOutboxProbe
|
|
4464
4762
|
];
|
|
4465
4763
|
|
|
4466
4764
|
// src/doctor/runner.ts
|
|
@@ -4471,7 +4769,7 @@ function resolveSnapshotGitWorktree(home) {
|
|
|
4471
4769
|
const configured = resolveConfiguredSnapshotWorktree(home);
|
|
4472
4770
|
if (configured) return configured;
|
|
4473
4771
|
const defaultPath = "/root/wiki-git";
|
|
4474
|
-
return
|
|
4772
|
+
return existsSync20(defaultPath) ? defaultPath : void 0;
|
|
4475
4773
|
}
|
|
4476
4774
|
var DoctorRunner = class {
|
|
4477
4775
|
probes;
|
|
@@ -4482,6 +4780,7 @@ var DoctorRunner = class {
|
|
|
4482
4780
|
return this.probes;
|
|
4483
4781
|
}
|
|
4484
4782
|
async run(input) {
|
|
4783
|
+
input = { ...input, home: resolveSkillwikiHome(input.home, input.env) };
|
|
4485
4784
|
const devSourceRun = isDevSourceRun(input.argv);
|
|
4486
4785
|
const vsConfig = readVaultSyncConfig(input.home);
|
|
4487
4786
|
const baseEnv = input.env ?? process.env;
|
|
@@ -4581,8 +4880,8 @@ function readCliPackageJson(baseUrl = import.meta.url) {
|
|
|
4581
4880
|
|
|
4582
4881
|
// src/utils/vault-write-gates.ts
|
|
4583
4882
|
import { execFileSync } from "child_process";
|
|
4584
|
-
import { existsSync as
|
|
4585
|
-
import { join as
|
|
4883
|
+
import { existsSync as existsSync21, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
|
|
4884
|
+
import { join as join24, relative as relative2 } from "path";
|
|
4586
4885
|
var DEFAULT_DIRTY_VOLUME_THRESHOLD = 50;
|
|
4587
4886
|
var DEFAULT_CAPTURE_BUDGET = 20;
|
|
4588
4887
|
var DEFAULT_NO_DECISION_STREAK = 3;
|
|
@@ -4638,7 +4937,7 @@ function isEventLedgerPath(rel) {
|
|
|
4638
4937
|
return n === "meta/log-events" || n.startsWith("meta/log-events/");
|
|
4639
4938
|
}
|
|
4640
4939
|
function countEventLedgerFiles(vault) {
|
|
4641
|
-
return listFilesRecursive(
|
|
4940
|
+
return listFilesRecursive(join24(vault, "meta", "log-events")).length;
|
|
4642
4941
|
}
|
|
4643
4942
|
function measureDirtyVolume(vault) {
|
|
4644
4943
|
const empty = (extra = {}) => ({
|
|
@@ -4654,7 +4953,7 @@ function measureDirtyVolume(vault) {
|
|
|
4654
4953
|
is_git_repo: false,
|
|
4655
4954
|
...extra
|
|
4656
4955
|
});
|
|
4657
|
-
if (!
|
|
4956
|
+
if (!existsSync21(vault) || !statSync4(vault).isDirectory()) {
|
|
4658
4957
|
return empty();
|
|
4659
4958
|
}
|
|
4660
4959
|
const gitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
|
|
@@ -4698,8 +4997,8 @@ function measureDirtyVolume(vault) {
|
|
|
4698
4997
|
if (!rel) continue;
|
|
4699
4998
|
if (code === "??") {
|
|
4700
4999
|
untracked += 1;
|
|
4701
|
-
const abs =
|
|
4702
|
-
if (
|
|
5000
|
+
const abs = join24(vault, rel);
|
|
5001
|
+
if (existsSync21(abs) && statSync4(abs).isDirectory()) {
|
|
4703
5002
|
const files = listFilesRecursive(abs);
|
|
4704
5003
|
expanded += files.length;
|
|
4705
5004
|
addBucket(rel, files.length);
|
|
@@ -4742,9 +5041,9 @@ function listFilesRecursive(dir) {
|
|
|
4742
5041
|
}
|
|
4743
5042
|
for (const name of entries) {
|
|
4744
5043
|
if (name === ".git") continue;
|
|
4745
|
-
const p =
|
|
5044
|
+
const p = join24(dir, name);
|
|
4746
5045
|
try {
|
|
4747
|
-
const st =
|
|
5046
|
+
const st = statSync4(p);
|
|
4748
5047
|
if (st.isDirectory()) out.push(...listFilesRecursive(p));
|
|
4749
5048
|
else if (st.isFile()) out.push(p);
|
|
4750
5049
|
} catch {
|
|
@@ -4837,7 +5136,7 @@ var CAPTURE_HYGIENE_CONTRACT = `After each productive cycle (or when daily captu
|
|
|
4837
5136
|
function listProjectDayCaptures(vault, project, day) {
|
|
4838
5137
|
const found = [];
|
|
4839
5138
|
const slug = project.replace(/^\[\[/, "").replace(/\]\]$/, "").trim();
|
|
4840
|
-
if (!slug || !
|
|
5139
|
+
if (!slug || !existsSync21(vault)) return found;
|
|
4841
5140
|
const consider = (abs, rel) => {
|
|
4842
5141
|
if (!rel.endsWith(".md")) return;
|
|
4843
5142
|
const base = rel.split(/[/\\]/).pop() ?? "";
|
|
@@ -4865,7 +5164,7 @@ function listProjectDayCaptures(vault, project, day) {
|
|
|
4865
5164
|
}
|
|
4866
5165
|
};
|
|
4867
5166
|
const walk = (dir, relBase) => {
|
|
4868
|
-
if (!
|
|
5167
|
+
if (!existsSync21(dir)) return;
|
|
4869
5168
|
let entries;
|
|
4870
5169
|
try {
|
|
4871
5170
|
entries = readdirSync7(dir);
|
|
@@ -4873,27 +5172,27 @@ function listProjectDayCaptures(vault, project, day) {
|
|
|
4873
5172
|
return;
|
|
4874
5173
|
}
|
|
4875
5174
|
for (const name of entries) {
|
|
4876
|
-
const abs =
|
|
5175
|
+
const abs = join24(dir, name);
|
|
4877
5176
|
const rel = relBase ? `${relBase}/${name}` : name;
|
|
4878
5177
|
try {
|
|
4879
|
-
const st =
|
|
5178
|
+
const st = statSync4(abs);
|
|
4880
5179
|
if (st.isDirectory()) walk(abs, rel);
|
|
4881
5180
|
else if (st.isFile()) consider(abs, rel);
|
|
4882
5181
|
} catch {
|
|
4883
5182
|
}
|
|
4884
5183
|
}
|
|
4885
5184
|
};
|
|
4886
|
-
walk(
|
|
4887
|
-
walk(
|
|
4888
|
-
walk(
|
|
4889
|
-
const workRoot =
|
|
4890
|
-
if (
|
|
5185
|
+
walk(join24(vault, "raw", "transcripts"), "raw/transcripts");
|
|
5186
|
+
walk(join24(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
|
|
5187
|
+
walk(join24(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
|
|
5188
|
+
const workRoot = join24(vault, "projects", slug, "work");
|
|
5189
|
+
if (existsSync21(workRoot)) {
|
|
4891
5190
|
try {
|
|
4892
5191
|
for (const name of readdirSync7(workRoot)) {
|
|
4893
5192
|
if (!name.startsWith(day)) continue;
|
|
4894
5193
|
if (!/investigate|pilot-q|research|cycle|dev-loop/.test(name)) continue;
|
|
4895
|
-
const abs =
|
|
4896
|
-
if (
|
|
5194
|
+
const abs = join24(workRoot, name);
|
|
5195
|
+
if (statSync4(abs).isDirectory()) {
|
|
4897
5196
|
for (const f of listFilesRecursive(abs)) {
|
|
4898
5197
|
const rel = relative2(vault, f).replace(/\\/g, "/");
|
|
4899
5198
|
if (rel.endsWith(".md")) found.push(rel);
|
|
@@ -4937,7 +5236,7 @@ function evaluateCaptureBudget(input) {
|
|
|
4937
5236
|
return { allowed: true, reason: "under_budget", report };
|
|
4938
5237
|
}
|
|
4939
5238
|
function runWritePreflight(input) {
|
|
4940
|
-
if (!input.vault || !
|
|
5239
|
+
if (!input.vault || !existsSync21(input.vault)) {
|
|
4941
5240
|
return err(GateError.VAULT_PATH_INVALID, { path: input.vault });
|
|
4942
5241
|
}
|
|
4943
5242
|
const want = new Set(input.checks ?? ["all"]);
|
|
@@ -4996,9 +5295,9 @@ function runWritePreflight(input) {
|
|
|
4996
5295
|
}
|
|
4997
5296
|
|
|
4998
5297
|
// src/commands/observe.ts
|
|
4999
|
-
import { mkdir as mkdir3, writeFile as
|
|
5000
|
-
import { existsSync as
|
|
5001
|
-
import { join as
|
|
5298
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
5299
|
+
import { existsSync as existsSync22, statSync as statSync5 } from "fs";
|
|
5300
|
+
import { join as join25 } from "path";
|
|
5002
5301
|
import { createHash as createHash2 } from "crypto";
|
|
5003
5302
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
5004
5303
|
function slugify(text) {
|
|
@@ -5021,7 +5320,7 @@ async function runObserve(input) {
|
|
|
5021
5320
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
5022
5321
|
};
|
|
5023
5322
|
}
|
|
5024
|
-
if (!
|
|
5323
|
+
if (!existsSync22(input.vault) || !statSync5(input.vault).isDirectory()) {
|
|
5025
5324
|
return {
|
|
5026
5325
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5027
5326
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
@@ -5047,7 +5346,7 @@ async function runObserve(input) {
|
|
|
5047
5346
|
};
|
|
5048
5347
|
}
|
|
5049
5348
|
}
|
|
5050
|
-
const transcriptsDir =
|
|
5349
|
+
const transcriptsDir = join25(input.vault, "raw", "transcripts");
|
|
5051
5350
|
try {
|
|
5052
5351
|
await mkdir3(transcriptsDir, { recursive: true });
|
|
5053
5352
|
} catch {
|
|
@@ -5059,7 +5358,7 @@ async function runObserve(input) {
|
|
|
5059
5358
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5060
5359
|
const slug = slugify(input.text);
|
|
5061
5360
|
const fileName = `${today}-observation-${slug}.md`;
|
|
5062
|
-
const filePath =
|
|
5361
|
+
const filePath = join25(transcriptsDir, fileName);
|
|
5063
5362
|
const body = `
|
|
5064
5363
|
${input.text.trim()}
|
|
5065
5364
|
`;
|
|
@@ -5077,7 +5376,7 @@ ${input.text.trim()}
|
|
|
5077
5376
|
frontmatterLines.push("---");
|
|
5078
5377
|
const content = frontmatterLines.join("\n") + body;
|
|
5079
5378
|
try {
|
|
5080
|
-
await
|
|
5379
|
+
await writeFile3(filePath, content, "utf8");
|
|
5081
5380
|
} catch (e) {
|
|
5082
5381
|
return {
|
|
5083
5382
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5100,8 +5399,8 @@ ${input.text.trim()}
|
|
|
5100
5399
|
|
|
5101
5400
|
// src/commands/memory.ts
|
|
5102
5401
|
import { createHash as createHash3 } from "crypto";
|
|
5103
|
-
import { mkdir as mkdir4, readFile as readFile7, readdir as readdir2, stat as stat2, writeFile as
|
|
5104
|
-
import { basename as basename2, extname, join as
|
|
5402
|
+
import { mkdir as mkdir4, readFile as readFile7, readdir as readdir2, stat as stat2, writeFile as writeFile4 } from "fs/promises";
|
|
5403
|
+
import { basename as basename2, extname, join as join26, relative as relative3, sep as sep3 } from "path";
|
|
5105
5404
|
|
|
5106
5405
|
// src/utils/memory-authority.ts
|
|
5107
5406
|
var TIER_RANK = {
|
|
@@ -5220,9 +5519,9 @@ async function runMemoryIndex(input) {
|
|
|
5220
5519
|
}
|
|
5221
5520
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5222
5521
|
const relCachePath = memoryCacheRelPath(input.project);
|
|
5223
|
-
const absCachePath =
|
|
5224
|
-
await mkdir4(
|
|
5225
|
-
await
|
|
5522
|
+
const absCachePath = join26(input.vault, relCachePath);
|
|
5523
|
+
await mkdir4(join26(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
|
|
5524
|
+
await writeFile4(absCachePath, `${JSON.stringify({
|
|
5226
5525
|
generated_at: generatedAt,
|
|
5227
5526
|
project: input.project,
|
|
5228
5527
|
topics: state.topics,
|
|
@@ -5413,7 +5712,7 @@ async function buildMemoryIndexState(pages, project) {
|
|
|
5413
5712
|
}
|
|
5414
5713
|
async function checkMemoryIndex(vault, project, current) {
|
|
5415
5714
|
const relCachePath = memoryCacheRelPath(project);
|
|
5416
|
-
const cacheText = await readIfExists(
|
|
5715
|
+
const cacheText = await readIfExists(join26(vault, relCachePath));
|
|
5417
5716
|
if (!cacheText) {
|
|
5418
5717
|
return {
|
|
5419
5718
|
ok: true,
|
|
@@ -5822,7 +6121,7 @@ async function walkImportFiles(dir, out) {
|
|
|
5822
6121
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
5823
6122
|
for (const entry of entries) {
|
|
5824
6123
|
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
5825
|
-
const path =
|
|
6124
|
+
const path = join26(dir, entry.name);
|
|
5826
6125
|
if (entry.isDirectory()) {
|
|
5827
6126
|
await walkImportFiles(path, out);
|
|
5828
6127
|
} else if (entry.isFile() && isImportCandidate(path)) {
|
|
@@ -5872,7 +6171,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
|
|
|
5872
6171
|
const redacted = redactSensitiveContent(extracted, { file });
|
|
5873
6172
|
const privacy = redacted.findings.length > 0 ? "sensitive" : "local";
|
|
5874
6173
|
const sourceSlug = slugify2(basename2(file, extname(file)));
|
|
5875
|
-
const relSource = relative3(sourceRoot, file).split(
|
|
6174
|
+
const relSource = relative3(sourceRoot, file).split(sep3).join("/");
|
|
5876
6175
|
const entry = {
|
|
5877
6176
|
...baseEntry,
|
|
5878
6177
|
status: "ready",
|
|
@@ -5889,9 +6188,9 @@ async function writeImportCapture(vault, entry, today) {
|
|
|
5889
6188
|
const content = hiddenString(entry, "__content");
|
|
5890
6189
|
const project = hiddenString(entry, "__project");
|
|
5891
6190
|
const relPath = await availableImportPath(vault, entry.proposed_path);
|
|
5892
|
-
const absPath =
|
|
5893
|
-
await mkdir4(
|
|
5894
|
-
await
|
|
6191
|
+
const absPath = join26(vault, relPath);
|
|
6192
|
+
await mkdir4(join26(vault, "raw", "transcripts"), { recursive: true });
|
|
6193
|
+
await writeFile4(absPath, renderImportCapture(entry, content, project, today), "utf8");
|
|
5895
6194
|
const validation = await runValidate({ file: absPath });
|
|
5896
6195
|
return {
|
|
5897
6196
|
relPath,
|
|
@@ -5906,7 +6205,7 @@ async function availableImportPath(vault, proposed) {
|
|
|
5906
6205
|
const stem = proposed.slice(0, -ext.length);
|
|
5907
6206
|
let candidate = proposed;
|
|
5908
6207
|
let i = 2;
|
|
5909
|
-
while (await readIfExists(
|
|
6208
|
+
while (await readIfExists(join26(vault, candidate))) {
|
|
5910
6209
|
candidate = `${stem}-${i}${ext}`;
|
|
5911
6210
|
i++;
|
|
5912
6211
|
}
|
|
@@ -5944,7 +6243,7 @@ function hiddenString(entry, key) {
|
|
|
5944
6243
|
return entry[key] ?? "";
|
|
5945
6244
|
}
|
|
5946
6245
|
function classifyImportSource(file) {
|
|
5947
|
-
const rel = file.split(
|
|
6246
|
+
const rel = file.split(sep3).join("/");
|
|
5948
6247
|
const name = basename2(file);
|
|
5949
6248
|
if (rel.includes("/.codex/memories/")) return "codex-memory";
|
|
5950
6249
|
if (rel.includes("/.codex/rules/")) return "codex-rule";
|
|
@@ -6091,10 +6390,10 @@ function memoryCacheRelPath(project) {
|
|
|
6091
6390
|
}
|
|
6092
6391
|
async function readMemoryCache(vault, project) {
|
|
6093
6392
|
if (project) {
|
|
6094
|
-
const projectCache = await readIfExists(
|
|
6393
|
+
const projectCache = await readIfExists(join26(vault, memoryCacheRelPath(project)));
|
|
6095
6394
|
if (projectCache) return projectCache;
|
|
6096
6395
|
}
|
|
6097
|
-
return readIfExists(
|
|
6396
|
+
return readIfExists(join26(vault, ".skillwiki", "memory-topics.json"));
|
|
6098
6397
|
}
|
|
6099
6398
|
function dedupePages(pages) {
|
|
6100
6399
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6186,7 +6485,7 @@ function slugify2(value) {
|
|
|
6186
6485
|
|
|
6187
6486
|
// src/commands/query.ts
|
|
6188
6487
|
import { readFile as readFile9, stat as stat4 } from "fs/promises";
|
|
6189
|
-
import { join as
|
|
6488
|
+
import { join as join28 } from "path";
|
|
6190
6489
|
|
|
6191
6490
|
// src/utils/rrf.ts
|
|
6192
6491
|
var RRF_K = 60;
|
|
@@ -6202,8 +6501,8 @@ function fuseRankings(lists, k = RRF_K) {
|
|
|
6202
6501
|
|
|
6203
6502
|
// src/utils/vector-index.ts
|
|
6204
6503
|
import { mkdir as mkdir5, readFile as readFile8, stat as stat3 } from "fs/promises";
|
|
6205
|
-
import { join as
|
|
6206
|
-
import { existsSync as
|
|
6504
|
+
import { join as join27 } from "path";
|
|
6505
|
+
import { existsSync as existsSync23 } from "fs";
|
|
6207
6506
|
var VECTOR_INDEX_REL = ".skillwiki/vectors/index.json";
|
|
6208
6507
|
var VECTOR_INDEX_SCHEMA = "skillwiki-tfidf-index/v1";
|
|
6209
6508
|
function tokenize(text) {
|
|
@@ -6217,7 +6516,7 @@ function extractPageTokens(text) {
|
|
|
6217
6516
|
return tokenize(`${title} ${body}`);
|
|
6218
6517
|
}
|
|
6219
6518
|
function vectorIndexPath(vault) {
|
|
6220
|
-
return
|
|
6519
|
+
return join27(vault, ...VECTOR_INDEX_REL.split("/"));
|
|
6221
6520
|
}
|
|
6222
6521
|
function termFreq(tokens) {
|
|
6223
6522
|
const tf = /* @__PURE__ */ new Map();
|
|
@@ -6272,7 +6571,7 @@ async function buildVectorIndex(vault, now = (/* @__PURE__ */ new Date()).toISOS
|
|
|
6272
6571
|
docs
|
|
6273
6572
|
};
|
|
6274
6573
|
const dest = vectorIndexPath(vault);
|
|
6275
|
-
await mkdir5(
|
|
6574
|
+
await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
|
|
6276
6575
|
await atomicWriteText(dest, `${JSON.stringify(index)}
|
|
6277
6576
|
`);
|
|
6278
6577
|
return ok(index);
|
|
@@ -6315,7 +6614,7 @@ async function reindexPageInVectorIndex(vault, pageRelPath, now = (/* @__PURE__
|
|
|
6315
6614
|
if (!normalizedPage.endsWith(".md")) {
|
|
6316
6615
|
return err("USAGE", { message: `Page must be a .md file: ${pageRelPath}` });
|
|
6317
6616
|
}
|
|
6318
|
-
const absPath =
|
|
6617
|
+
const absPath = join27(vault, ...normalizedPage.split("/"));
|
|
6319
6618
|
let text;
|
|
6320
6619
|
try {
|
|
6321
6620
|
text = await readFile8(absPath, "utf8");
|
|
@@ -6400,7 +6699,7 @@ async function reindexPageInVectorIndex(vault, pageRelPath, now = (/* @__PURE__
|
|
|
6400
6699
|
}
|
|
6401
6700
|
}
|
|
6402
6701
|
const dest = vectorIndexPath(vault);
|
|
6403
|
-
await mkdir5(
|
|
6702
|
+
await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
|
|
6404
6703
|
await atomicWriteText(dest, `${JSON.stringify(index)}
|
|
6405
6704
|
`);
|
|
6406
6705
|
return ok({
|
|
@@ -6418,8 +6717,8 @@ async function pruneVectorIndex(vault, opts) {
|
|
|
6418
6717
|
const orphans = [];
|
|
6419
6718
|
for (const docKey of Object.keys(index.docs)) {
|
|
6420
6719
|
const normalizedKey = docKey.split(/\\|\//).join("/");
|
|
6421
|
-
const absPath =
|
|
6422
|
-
if (!
|
|
6720
|
+
const absPath = join27(vault, ...normalizedKey.split("/"));
|
|
6721
|
+
if (!existsSync23(absPath)) {
|
|
6423
6722
|
orphans.push(normalizedKey);
|
|
6424
6723
|
}
|
|
6425
6724
|
}
|
|
@@ -6465,7 +6764,7 @@ async function pruneVectorIndex(vault, opts) {
|
|
|
6465
6764
|
index.docs[docKey] = toTfIdf(termFreq(tokens), index.df, newPageCount);
|
|
6466
6765
|
} else {
|
|
6467
6766
|
try {
|
|
6468
|
-
const text = await readFile8(
|
|
6767
|
+
const text = await readFile8(join27(vault, ...docKey.split("/")), "utf8");
|
|
6469
6768
|
const tokens = extractPageTokens(text);
|
|
6470
6769
|
index.docs[docKey] = toTfIdf(termFreq(tokens), index.df, newPageCount);
|
|
6471
6770
|
} catch {
|
|
@@ -6475,7 +6774,7 @@ async function pruneVectorIndex(vault, opts) {
|
|
|
6475
6774
|
}
|
|
6476
6775
|
if (!opts?.dryRun) {
|
|
6477
6776
|
const dest = vectorIndexPath(vault);
|
|
6478
|
-
await mkdir5(
|
|
6777
|
+
await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
|
|
6479
6778
|
await atomicWriteText(dest, `${JSON.stringify(index)}
|
|
6480
6779
|
`);
|
|
6481
6780
|
}
|
|
@@ -6606,7 +6905,7 @@ async function runQuery(input) {
|
|
|
6606
6905
|
}
|
|
6607
6906
|
let pendingSources;
|
|
6608
6907
|
if (input.includePending) {
|
|
6609
|
-
const { runSourcesPending: runSourcesPending2 } = await import("./sources-
|
|
6908
|
+
const { runSourcesPending: runSourcesPending2 } = await import("./sources-CLOF5VAT.js");
|
|
6610
6909
|
const pending = await runSourcesPending2({
|
|
6611
6910
|
vault: input.vault,
|
|
6612
6911
|
match: input.text,
|
|
@@ -6776,7 +7075,7 @@ function computeKeywordScore(terms, title, tags, body, relPath) {
|
|
|
6776
7075
|
return score;
|
|
6777
7076
|
}
|
|
6778
7077
|
async function loadOrBuildGraph(vault) {
|
|
6779
|
-
const graphPath =
|
|
7078
|
+
const graphPath = join28(vault, ".skillwiki", "graph.json");
|
|
6780
7079
|
let needsBuild = false;
|
|
6781
7080
|
try {
|
|
6782
7081
|
const fileStat = await stat4(graphPath);
|
|
@@ -6865,27 +7164,27 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6865
7164
|
import { z } from "zod";
|
|
6866
7165
|
|
|
6867
7166
|
// src/mcp/vault-resolve.ts
|
|
6868
|
-
import { join as
|
|
7167
|
+
import { join as join29, resolve as resolve5 } from "path";
|
|
6869
7168
|
|
|
6870
7169
|
// src/mcp/allowlist.ts
|
|
6871
|
-
import { resolve as
|
|
6872
|
-
import { realpathSync } from "fs";
|
|
7170
|
+
import { resolve as resolve4, sep as sep4 } from "path";
|
|
7171
|
+
import { realpathSync as realpathSync2 } from "fs";
|
|
6873
7172
|
function parseVaultAllowlist(envValue) {
|
|
6874
7173
|
if (envValue === void 0 || envValue.trim() === "") return null;
|
|
6875
|
-
return envValue.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((p) =>
|
|
7174
|
+
return envValue.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((p) => resolve4(p));
|
|
6876
7175
|
}
|
|
6877
7176
|
function vaultAllowedByList(vaultPath, allowlist) {
|
|
6878
7177
|
if (!allowlist || allowlist.length === 0) return true;
|
|
6879
7178
|
let canonical = vaultPath;
|
|
6880
7179
|
try {
|
|
6881
|
-
canonical =
|
|
7180
|
+
canonical = realpathSync2(vaultPath);
|
|
6882
7181
|
} catch {
|
|
6883
|
-
canonical =
|
|
7182
|
+
canonical = resolve4(vaultPath);
|
|
6884
7183
|
}
|
|
6885
|
-
const resolved =
|
|
7184
|
+
const resolved = resolve4(canonical);
|
|
6886
7185
|
return allowlist.some((root) => {
|
|
6887
|
-
const r =
|
|
6888
|
-
return resolved === r || resolved.startsWith(r +
|
|
7186
|
+
const r = resolve4(root);
|
|
7187
|
+
return resolved === r || resolved.startsWith(r + sep4);
|
|
6889
7188
|
});
|
|
6890
7189
|
}
|
|
6891
7190
|
function getVaultAllowlistFromEnv() {
|
|
@@ -6894,11 +7193,11 @@ function getVaultAllowlistFromEnv() {
|
|
|
6894
7193
|
|
|
6895
7194
|
// src/mcp/vault-resolve.ts
|
|
6896
7195
|
async function resolveMcpVault(input) {
|
|
6897
|
-
const home = input.home
|
|
7196
|
+
const home = resolveSkillwikiHome(input.home);
|
|
6898
7197
|
let vaultPath;
|
|
6899
7198
|
let source = "resolved";
|
|
6900
7199
|
if (input.vault !== void 0 && input.vault.length > 0) {
|
|
6901
|
-
vaultPath =
|
|
7200
|
+
vaultPath = resolve5(input.vault);
|
|
6902
7201
|
source = "flag";
|
|
6903
7202
|
} else {
|
|
6904
7203
|
const r = await resolveRuntimePath({
|
|
@@ -6910,7 +7209,7 @@ async function resolveMcpVault(input) {
|
|
|
6910
7209
|
cwd: input.cwd ?? process.cwd()
|
|
6911
7210
|
});
|
|
6912
7211
|
if (!r.ok) return r;
|
|
6913
|
-
vaultPath =
|
|
7212
|
+
vaultPath = resolve5(r.data.path);
|
|
6914
7213
|
source = r.data.source;
|
|
6915
7214
|
}
|
|
6916
7215
|
const scan = await scanVault(vaultPath);
|
|
@@ -6927,7 +7226,7 @@ async function resolveMcpVault(input) {
|
|
|
6927
7226
|
return ok({ vault: vaultPath, source });
|
|
6928
7227
|
}
|
|
6929
7228
|
function defaultGraphOut(vault) {
|
|
6930
|
-
return
|
|
7229
|
+
return join29(vault, ".skillwiki", "graph.json");
|
|
6931
7230
|
}
|
|
6932
7231
|
|
|
6933
7232
|
// src/mcp/result-format.ts
|
|
@@ -6943,8 +7242,7 @@ function formatToolResult(payload) {
|
|
|
6943
7242
|
|
|
6944
7243
|
// src/mcp/audit-log.ts
|
|
6945
7244
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
6946
|
-
import {
|
|
6947
|
-
import { join as join28 } from "path";
|
|
7245
|
+
import { join as join30 } from "path";
|
|
6948
7246
|
function auditEnabled() {
|
|
6949
7247
|
const v = process.env.SKILLWIKI_MCP_AUDIT;
|
|
6950
7248
|
if (v === "0" || v === "false") return false;
|
|
@@ -6956,7 +7254,7 @@ function auditSink() {
|
|
|
6956
7254
|
function auditFilePath() {
|
|
6957
7255
|
const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
|
|
6958
7256
|
if (custom && custom.length > 0) return custom;
|
|
6959
|
-
return
|
|
7257
|
+
return join30(resolveSkillwikiHome(), ".skillwiki", "mcp-audit.jsonl");
|
|
6960
7258
|
}
|
|
6961
7259
|
function auditMcpToolCall(entry) {
|
|
6962
7260
|
if (!auditEnabled()) return;
|
|
@@ -6966,7 +7264,7 @@ function auditMcpToolCall(entry) {
|
|
|
6966
7264
|
return;
|
|
6967
7265
|
}
|
|
6968
7266
|
const path = auditFilePath();
|
|
6969
|
-
mkdirSync2(
|
|
7267
|
+
mkdirSync2(join30(path, ".."), { recursive: true });
|
|
6970
7268
|
appendFileSync(path, line, "utf8");
|
|
6971
7269
|
}
|
|
6972
7270
|
async function runMcpToolHandler(tool, input, fn) {
|
|
@@ -7059,7 +7357,7 @@ function registerMcpTools(server) {
|
|
|
7059
7357
|
await resolveMcpVault({ vault, wiki });
|
|
7060
7358
|
const pkg = readCliPackageJson();
|
|
7061
7359
|
const r = await runDoctor({
|
|
7062
|
-
home:
|
|
7360
|
+
home: resolveSkillwikiHome(),
|
|
7063
7361
|
envValue: process.env.WIKI_PATH,
|
|
7064
7362
|
argv: process.argv,
|
|
7065
7363
|
currentVersion: pkg.version,
|
|
@@ -7132,7 +7430,7 @@ function registerMcpTools(server) {
|
|
|
7132
7430
|
})
|
|
7133
7431
|
},
|
|
7134
7432
|
async ({ key }) => runMcpToolHandler("skillwiki.config_get", {}, async () => {
|
|
7135
|
-
const r = await runConfigGet({ key, home:
|
|
7433
|
+
const r = await runConfigGet({ key, home: resolveSkillwikiHome() });
|
|
7136
7434
|
return formatToolResult(r);
|
|
7137
7435
|
})
|
|
7138
7436
|
);
|
|
@@ -7241,7 +7539,7 @@ function registerMcpMutatingTools(server) {
|
|
|
7241
7539
|
|
|
7242
7540
|
// src/mcp/resources.ts
|
|
7243
7541
|
import { readFile as readFile11 } from "fs/promises";
|
|
7244
|
-
import { join as
|
|
7542
|
+
import { join as join32 } from "path";
|
|
7245
7543
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7246
7544
|
|
|
7247
7545
|
// src/mcp/lint-bucket.ts
|
|
@@ -7371,8 +7669,8 @@ async function fetchQueryPreview(input) {
|
|
|
7371
7669
|
|
|
7372
7670
|
// src/mcp/graph-html.ts
|
|
7373
7671
|
import { readFile as readFile10 } from "fs/promises";
|
|
7374
|
-
import { join as
|
|
7375
|
-
import { existsSync as
|
|
7672
|
+
import { join as join31 } from "path";
|
|
7673
|
+
import { existsSync as existsSync24 } from "fs";
|
|
7376
7674
|
var TYPE_COLORS = {
|
|
7377
7675
|
entities: "#e74c3c",
|
|
7378
7676
|
concepts: "#27ae60",
|
|
@@ -7440,9 +7738,9 @@ ${nodeSvg}
|
|
|
7440
7738
|
return { html, node_count: nodes.length, edge_count: edges.length, truncated };
|
|
7441
7739
|
}
|
|
7442
7740
|
async function fetchGraphHtmlReport(input) {
|
|
7443
|
-
const graphPath = input.graphPath ??
|
|
7741
|
+
const graphPath = input.graphPath ?? join31(input.vault, ".skillwiki", "graph.json");
|
|
7444
7742
|
const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
|
|
7445
|
-
if (!
|
|
7743
|
+
if (!existsSync24(graphPath)) {
|
|
7446
7744
|
return {
|
|
7447
7745
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
7448
7746
|
result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
|
|
@@ -7514,7 +7812,7 @@ async function fetchStaleSummary(input) {
|
|
|
7514
7812
|
|
|
7515
7813
|
// src/mcp/resources.ts
|
|
7516
7814
|
async function readVaultFile(vault, rel) {
|
|
7517
|
-
return readFile11(
|
|
7815
|
+
return readFile11(join32(vault, rel), "utf8");
|
|
7518
7816
|
}
|
|
7519
7817
|
async function tailLines(text, lines) {
|
|
7520
7818
|
const parts = text.split(/\r?\n/);
|
|
@@ -7600,7 +7898,7 @@ function registerMcpResources(server) {
|
|
|
7600
7898
|
if (!v.ok) {
|
|
7601
7899
|
return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
|
|
7602
7900
|
}
|
|
7603
|
-
const path =
|
|
7901
|
+
const path = join32(v.data.vault, ".skillwiki", "graph.json");
|
|
7604
7902
|
try {
|
|
7605
7903
|
const raw = await readFile11(path, "utf8");
|
|
7606
7904
|
const graph = JSON.parse(raw);
|
|
@@ -7955,6 +8253,7 @@ export {
|
|
|
7955
8253
|
MCP_TOKEN_ENV,
|
|
7956
8254
|
MCP_HOST_ID_ENV,
|
|
7957
8255
|
nonEmptyEnvValue,
|
|
8256
|
+
mcpAuthFromEnv,
|
|
7958
8257
|
mergeMcpAuthIntoEnv,
|
|
7959
8258
|
redactMcpSecret,
|
|
7960
8259
|
configPath,
|
|
@@ -7972,7 +8271,10 @@ export {
|
|
|
7972
8271
|
readSatelliteLatestRunFromText,
|
|
7973
8272
|
readSatelliteLatestRunAt,
|
|
7974
8273
|
evaluateSatelliteRunHealthState,
|
|
8274
|
+
withMcpClient,
|
|
7975
8275
|
DEFAULT_MCP_URL,
|
|
8276
|
+
CLIP_NOT_CONFIGURED,
|
|
8277
|
+
loadClipOutboxConfig,
|
|
7976
8278
|
runDoctor,
|
|
7977
8279
|
readCliPackageJson,
|
|
7978
8280
|
DEFAULT_DIRTY_VOLUME_THRESHOLD,
|