skillwiki 0.10.109 → 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-LDEZQXH6.js → chunk-6X6PPQNI.js} +424 -100
- package/dist/{chunk-DTTKCCQ4.js → chunk-KX5Y3GSX.js} +2 -2
- package/dist/{chunk-QU2IXZRN.js → chunk-NA3TZ4JN.js} +2 -2
- package/dist/{chunk-FYAKMULW.js → chunk-QU2CKXM4.js} +5 -4
- package/dist/{chunk-GJC2CI3W.js → chunk-R2DJ7IJ5.js} +103 -0
- package/dist/{chunk-HMHMCPCE.js → chunk-WPJHDRTP.js} +1 -1
- package/dist/cli.js +57 -12
- package/dist/{index-projection-ESBJOBS7.js → index-projection-6XGESSHV.js} +3 -3
- package/dist/{managed-write-preflight-MAIGWFQJ.js → managed-write-preflight-JPOBVVSU.js} +4 -4
- package/dist/skillwiki-mcp.js +7 -7
- package/dist/sources-CLOF5VAT.js +9 -0
- 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/using-skillwiki/SKILL.md +1 -1
- package/skills/skills/wiki-init/SKILL.md +12 -11
- 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,
|
|
@@ -41,11 +41,11 @@ import {
|
|
|
41
41
|
snapshotterAliasForLocalHost,
|
|
42
42
|
toUndirectedWeighted,
|
|
43
43
|
writeDotenv
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-QU2CKXM4.js";
|
|
45
45
|
import {
|
|
46
46
|
atomicWriteText,
|
|
47
47
|
prepareTypedPage
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-WPJHDRTP.js";
|
|
49
49
|
import {
|
|
50
50
|
applySourceCompileClaim,
|
|
51
51
|
applySourceCompilePublished,
|
|
@@ -58,12 +58,13 @@ import {
|
|
|
58
58
|
planSourceCompileRelease,
|
|
59
59
|
planSourceReview,
|
|
60
60
|
runSourcesPending
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-NA3TZ4JN.js";
|
|
62
62
|
import {
|
|
63
63
|
eventPathFor,
|
|
64
64
|
writeLogEvent
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-5MV5SSRM.js";
|
|
66
66
|
import {
|
|
67
|
+
CLIP_OUTBOX_DEFAULTS,
|
|
67
68
|
CompoundSchema,
|
|
68
69
|
ExitCode,
|
|
69
70
|
MetaSchema,
|
|
@@ -73,8 +74,10 @@ import {
|
|
|
73
74
|
detectSchema,
|
|
74
75
|
err,
|
|
75
76
|
extractFrontmatter,
|
|
77
|
+
isClipOutboxKey,
|
|
76
78
|
isVaultSyncKey,
|
|
77
79
|
ok,
|
|
80
|
+
parseClipOutboxKeyValue,
|
|
78
81
|
parseVaultSyncKeyValue,
|
|
79
82
|
readPage,
|
|
80
83
|
redactSensitiveContent,
|
|
@@ -83,7 +86,7 @@ import {
|
|
|
83
86
|
scanVault,
|
|
84
87
|
splitFrontmatter,
|
|
85
88
|
systemdPropertyFor
|
|
86
|
-
} from "./chunk-
|
|
89
|
+
} from "./chunk-R2DJ7IJ5.js";
|
|
87
90
|
|
|
88
91
|
// src/commands/log-append.ts
|
|
89
92
|
import { readFile, stat } from "fs/promises";
|
|
@@ -1017,7 +1020,7 @@ import { readFile as readFile5 } from "fs/promises";
|
|
|
1017
1020
|
import { existsSync as existsSync2 } from "fs";
|
|
1018
1021
|
import { join as join5 } from "path";
|
|
1019
1022
|
function validateKey(key) {
|
|
1020
|
-
return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key) || isVaultSyncKey(key);
|
|
1023
|
+
return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key) || isVaultSyncKey(key) || isClipOutboxKey(key);
|
|
1021
1024
|
}
|
|
1022
1025
|
function displayConfigValue(key, value) {
|
|
1023
1026
|
if (key === "SKILLWIKI_MCP_TOKEN") return value.length > 0 ? "TOKEN_SET" : "";
|
|
@@ -1044,6 +1047,12 @@ async function runConfigSet(input) {
|
|
|
1044
1047
|
return { exitCode: ExitCode.INVALID_CONFIG_VALUE, result: err("INVALID_CONFIG_VALUE", { key: input.key, value: input.value, message: parsed.message }) };
|
|
1045
1048
|
}
|
|
1046
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
|
+
}
|
|
1047
1056
|
const filePath = configPath(input.home);
|
|
1048
1057
|
try {
|
|
1049
1058
|
let originalContent;
|
|
@@ -1130,7 +1139,7 @@ function isMcpOnlyLeaf(input) {
|
|
|
1130
1139
|
}
|
|
1131
1140
|
|
|
1132
1141
|
// src/doctor/runner.ts
|
|
1133
|
-
import { existsSync as
|
|
1142
|
+
import { existsSync as existsSync20 } from "fs";
|
|
1134
1143
|
|
|
1135
1144
|
// src/doctor/probes/environment.ts
|
|
1136
1145
|
import { existsSync as existsSync4, lstatSync, readlinkSync } from "fs";
|
|
@@ -2102,7 +2111,9 @@ import { execSync as execSync4 } from "child_process";
|
|
|
2102
2111
|
// src/utils/s3-mount-health.ts
|
|
2103
2112
|
import { execSync as execSync3 } from "child_process";
|
|
2104
2113
|
import { platform as platform2 } from "os";
|
|
2105
|
-
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";
|
|
2106
2117
|
import { join as join13 } from "path";
|
|
2107
2118
|
var OS = platform2();
|
|
2108
2119
|
function findRcloneMountPid() {
|
|
@@ -2260,36 +2271,45 @@ function detectFuseMount(vaultPath) {
|
|
|
2260
2271
|
}
|
|
2261
2272
|
return null;
|
|
2262
2273
|
}
|
|
2263
|
-
function writeTest(dir) {
|
|
2264
|
-
const testFile = join13(dir, `.doctor-write-test-${process.pid}.tmp`);
|
|
2265
|
-
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()}`;
|
|
2266
2282
|
const start = Date.now();
|
|
2267
2283
|
try {
|
|
2268
|
-
|
|
2284
|
+
await io.write(testFile, payload);
|
|
2269
2285
|
} catch (e) {
|
|
2286
|
+
try {
|
|
2287
|
+
await io.remove(testFile);
|
|
2288
|
+
} catch {
|
|
2289
|
+
}
|
|
2270
2290
|
return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
|
|
2271
2291
|
}
|
|
2272
2292
|
const writeMs = Date.now() - start;
|
|
2273
2293
|
const readStart = Date.now();
|
|
2274
2294
|
try {
|
|
2275
|
-
const back =
|
|
2295
|
+
const back = await io.read(testFile);
|
|
2276
2296
|
const readMs = Date.now() - readStart;
|
|
2277
2297
|
if (back !== payload) {
|
|
2278
2298
|
try {
|
|
2279
|
-
|
|
2299
|
+
await io.remove(testFile);
|
|
2280
2300
|
} catch {
|
|
2281
2301
|
}
|
|
2282
2302
|
return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
|
|
2283
2303
|
}
|
|
2284
2304
|
} catch (e) {
|
|
2285
2305
|
try {
|
|
2286
|
-
|
|
2306
|
+
await io.remove(testFile);
|
|
2287
2307
|
} catch {
|
|
2288
2308
|
}
|
|
2289
2309
|
return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
|
|
2290
2310
|
}
|
|
2291
2311
|
try {
|
|
2292
|
-
|
|
2312
|
+
await io.remove(testFile);
|
|
2293
2313
|
} catch {
|
|
2294
2314
|
}
|
|
2295
2315
|
return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
|
|
@@ -2523,7 +2543,7 @@ function checkRcloneVersion(resolvedPath, vaultSyncInstalled, pushEnabled = true
|
|
|
2523
2543
|
}
|
|
2524
2544
|
return check("pass", "rclone_version", "rclone version", ver.raw);
|
|
2525
2545
|
}
|
|
2526
|
-
function checkWriteTest(resolvedPath) {
|
|
2546
|
+
async function checkWriteTest(resolvedPath) {
|
|
2527
2547
|
if (!resolvedPath) {
|
|
2528
2548
|
return check("pass", "s3_write_test", "S3 write test", "No vault path \u2014 check skipped");
|
|
2529
2549
|
}
|
|
@@ -2535,7 +2555,7 @@ function checkWriteTest(resolvedPath) {
|
|
|
2535
2555
|
if (!existsSync10(conceptsDir)) {
|
|
2536
2556
|
return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
|
|
2537
2557
|
}
|
|
2538
|
-
const result = writeTest(conceptsDir);
|
|
2558
|
+
const result = await writeTest(conceptsDir);
|
|
2539
2559
|
if (result.success) {
|
|
2540
2560
|
const totalMs = result.writeMs + result.readMs;
|
|
2541
2561
|
if (totalMs > 3e3) {
|
|
@@ -2618,13 +2638,13 @@ function checkVfsCacheHealth(resolvedPath) {
|
|
|
2618
2638
|
}
|
|
2619
2639
|
var s3MountHealthProbe = {
|
|
2620
2640
|
id: "s3_mount_health",
|
|
2621
|
-
run(ctx) {
|
|
2641
|
+
async run(ctx) {
|
|
2622
2642
|
return [
|
|
2623
2643
|
checkS3MountPerf(ctx.resolvedPath),
|
|
2624
2644
|
checkS3MountFreshness(ctx.resolvedPath),
|
|
2625
2645
|
checkRcloneFlagAudit(ctx.resolvedPath),
|
|
2626
2646
|
checkRcloneVersion(ctx.resolvedPath, ctx.vsConfig.installed, ctx.vsConfig.pushEnabled !== false, ctx.mcpOnlyLeaf),
|
|
2627
|
-
checkWriteTest(ctx.resolvedPath),
|
|
2647
|
+
await checkWriteTest(ctx.resolvedPath),
|
|
2628
2648
|
checkVfsCacheHealth(ctx.resolvedPath)
|
|
2629
2649
|
];
|
|
2630
2650
|
}
|
|
@@ -4422,6 +4442,307 @@ var mcpHandshakeProbe = {
|
|
|
4422
4442
|
}
|
|
4423
4443
|
};
|
|
4424
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
|
+
|
|
4425
4746
|
// src/doctor/probes/index.ts
|
|
4426
4747
|
var DOCTOR_PROBES = [
|
|
4427
4748
|
environmentProbe,
|
|
@@ -4436,7 +4757,8 @@ var DOCTOR_PROBES = [
|
|
|
4436
4757
|
fuseStalenessProbe,
|
|
4437
4758
|
activationMarkerProbe,
|
|
4438
4759
|
dsStoreNoiseProbe,
|
|
4439
|
-
mcpHandshakeProbe
|
|
4760
|
+
mcpHandshakeProbe,
|
|
4761
|
+
clipOutboxProbe
|
|
4440
4762
|
];
|
|
4441
4763
|
|
|
4442
4764
|
// src/doctor/runner.ts
|
|
@@ -4447,7 +4769,7 @@ function resolveSnapshotGitWorktree(home) {
|
|
|
4447
4769
|
const configured = resolveConfiguredSnapshotWorktree(home);
|
|
4448
4770
|
if (configured) return configured;
|
|
4449
4771
|
const defaultPath = "/root/wiki-git";
|
|
4450
|
-
return
|
|
4772
|
+
return existsSync20(defaultPath) ? defaultPath : void 0;
|
|
4451
4773
|
}
|
|
4452
4774
|
var DoctorRunner = class {
|
|
4453
4775
|
probes;
|
|
@@ -4558,8 +4880,8 @@ function readCliPackageJson(baseUrl = import.meta.url) {
|
|
|
4558
4880
|
|
|
4559
4881
|
// src/utils/vault-write-gates.ts
|
|
4560
4882
|
import { execFileSync } from "child_process";
|
|
4561
|
-
import { existsSync as
|
|
4562
|
-
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";
|
|
4563
4885
|
var DEFAULT_DIRTY_VOLUME_THRESHOLD = 50;
|
|
4564
4886
|
var DEFAULT_CAPTURE_BUDGET = 20;
|
|
4565
4887
|
var DEFAULT_NO_DECISION_STREAK = 3;
|
|
@@ -4615,7 +4937,7 @@ function isEventLedgerPath(rel) {
|
|
|
4615
4937
|
return n === "meta/log-events" || n.startsWith("meta/log-events/");
|
|
4616
4938
|
}
|
|
4617
4939
|
function countEventLedgerFiles(vault) {
|
|
4618
|
-
return listFilesRecursive(
|
|
4940
|
+
return listFilesRecursive(join24(vault, "meta", "log-events")).length;
|
|
4619
4941
|
}
|
|
4620
4942
|
function measureDirtyVolume(vault) {
|
|
4621
4943
|
const empty = (extra = {}) => ({
|
|
@@ -4631,7 +4953,7 @@ function measureDirtyVolume(vault) {
|
|
|
4631
4953
|
is_git_repo: false,
|
|
4632
4954
|
...extra
|
|
4633
4955
|
});
|
|
4634
|
-
if (!
|
|
4956
|
+
if (!existsSync21(vault) || !statSync4(vault).isDirectory()) {
|
|
4635
4957
|
return empty();
|
|
4636
4958
|
}
|
|
4637
4959
|
const gitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
|
|
@@ -4675,8 +4997,8 @@ function measureDirtyVolume(vault) {
|
|
|
4675
4997
|
if (!rel) continue;
|
|
4676
4998
|
if (code === "??") {
|
|
4677
4999
|
untracked += 1;
|
|
4678
|
-
const abs =
|
|
4679
|
-
if (
|
|
5000
|
+
const abs = join24(vault, rel);
|
|
5001
|
+
if (existsSync21(abs) && statSync4(abs).isDirectory()) {
|
|
4680
5002
|
const files = listFilesRecursive(abs);
|
|
4681
5003
|
expanded += files.length;
|
|
4682
5004
|
addBucket(rel, files.length);
|
|
@@ -4719,9 +5041,9 @@ function listFilesRecursive(dir) {
|
|
|
4719
5041
|
}
|
|
4720
5042
|
for (const name of entries) {
|
|
4721
5043
|
if (name === ".git") continue;
|
|
4722
|
-
const p =
|
|
5044
|
+
const p = join24(dir, name);
|
|
4723
5045
|
try {
|
|
4724
|
-
const st =
|
|
5046
|
+
const st = statSync4(p);
|
|
4725
5047
|
if (st.isDirectory()) out.push(...listFilesRecursive(p));
|
|
4726
5048
|
else if (st.isFile()) out.push(p);
|
|
4727
5049
|
} catch {
|
|
@@ -4814,7 +5136,7 @@ var CAPTURE_HYGIENE_CONTRACT = `After each productive cycle (or when daily captu
|
|
|
4814
5136
|
function listProjectDayCaptures(vault, project, day) {
|
|
4815
5137
|
const found = [];
|
|
4816
5138
|
const slug = project.replace(/^\[\[/, "").replace(/\]\]$/, "").trim();
|
|
4817
|
-
if (!slug || !
|
|
5139
|
+
if (!slug || !existsSync21(vault)) return found;
|
|
4818
5140
|
const consider = (abs, rel) => {
|
|
4819
5141
|
if (!rel.endsWith(".md")) return;
|
|
4820
5142
|
const base = rel.split(/[/\\]/).pop() ?? "";
|
|
@@ -4842,7 +5164,7 @@ function listProjectDayCaptures(vault, project, day) {
|
|
|
4842
5164
|
}
|
|
4843
5165
|
};
|
|
4844
5166
|
const walk = (dir, relBase) => {
|
|
4845
|
-
if (!
|
|
5167
|
+
if (!existsSync21(dir)) return;
|
|
4846
5168
|
let entries;
|
|
4847
5169
|
try {
|
|
4848
5170
|
entries = readdirSync7(dir);
|
|
@@ -4850,27 +5172,27 @@ function listProjectDayCaptures(vault, project, day) {
|
|
|
4850
5172
|
return;
|
|
4851
5173
|
}
|
|
4852
5174
|
for (const name of entries) {
|
|
4853
|
-
const abs =
|
|
5175
|
+
const abs = join24(dir, name);
|
|
4854
5176
|
const rel = relBase ? `${relBase}/${name}` : name;
|
|
4855
5177
|
try {
|
|
4856
|
-
const st =
|
|
5178
|
+
const st = statSync4(abs);
|
|
4857
5179
|
if (st.isDirectory()) walk(abs, rel);
|
|
4858
5180
|
else if (st.isFile()) consider(abs, rel);
|
|
4859
5181
|
} catch {
|
|
4860
5182
|
}
|
|
4861
5183
|
}
|
|
4862
5184
|
};
|
|
4863
|
-
walk(
|
|
4864
|
-
walk(
|
|
4865
|
-
walk(
|
|
4866
|
-
const workRoot =
|
|
4867
|
-
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)) {
|
|
4868
5190
|
try {
|
|
4869
5191
|
for (const name of readdirSync7(workRoot)) {
|
|
4870
5192
|
if (!name.startsWith(day)) continue;
|
|
4871
5193
|
if (!/investigate|pilot-q|research|cycle|dev-loop/.test(name)) continue;
|
|
4872
|
-
const abs =
|
|
4873
|
-
if (
|
|
5194
|
+
const abs = join24(workRoot, name);
|
|
5195
|
+
if (statSync4(abs).isDirectory()) {
|
|
4874
5196
|
for (const f of listFilesRecursive(abs)) {
|
|
4875
5197
|
const rel = relative2(vault, f).replace(/\\/g, "/");
|
|
4876
5198
|
if (rel.endsWith(".md")) found.push(rel);
|
|
@@ -4914,7 +5236,7 @@ function evaluateCaptureBudget(input) {
|
|
|
4914
5236
|
return { allowed: true, reason: "under_budget", report };
|
|
4915
5237
|
}
|
|
4916
5238
|
function runWritePreflight(input) {
|
|
4917
|
-
if (!input.vault || !
|
|
5239
|
+
if (!input.vault || !existsSync21(input.vault)) {
|
|
4918
5240
|
return err(GateError.VAULT_PATH_INVALID, { path: input.vault });
|
|
4919
5241
|
}
|
|
4920
5242
|
const want = new Set(input.checks ?? ["all"]);
|
|
@@ -4973,9 +5295,9 @@ function runWritePreflight(input) {
|
|
|
4973
5295
|
}
|
|
4974
5296
|
|
|
4975
5297
|
// src/commands/observe.ts
|
|
4976
|
-
import { mkdir as mkdir3, writeFile as
|
|
4977
|
-
import { existsSync as
|
|
4978
|
-
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";
|
|
4979
5301
|
import { createHash as createHash2 } from "crypto";
|
|
4980
5302
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
4981
5303
|
function slugify(text) {
|
|
@@ -4998,7 +5320,7 @@ async function runObserve(input) {
|
|
|
4998
5320
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
4999
5321
|
};
|
|
5000
5322
|
}
|
|
5001
|
-
if (!
|
|
5323
|
+
if (!existsSync22(input.vault) || !statSync5(input.vault).isDirectory()) {
|
|
5002
5324
|
return {
|
|
5003
5325
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5004
5326
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
@@ -5024,7 +5346,7 @@ async function runObserve(input) {
|
|
|
5024
5346
|
};
|
|
5025
5347
|
}
|
|
5026
5348
|
}
|
|
5027
|
-
const transcriptsDir =
|
|
5349
|
+
const transcriptsDir = join25(input.vault, "raw", "transcripts");
|
|
5028
5350
|
try {
|
|
5029
5351
|
await mkdir3(transcriptsDir, { recursive: true });
|
|
5030
5352
|
} catch {
|
|
@@ -5036,7 +5358,7 @@ async function runObserve(input) {
|
|
|
5036
5358
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5037
5359
|
const slug = slugify(input.text);
|
|
5038
5360
|
const fileName = `${today}-observation-${slug}.md`;
|
|
5039
|
-
const filePath =
|
|
5361
|
+
const filePath = join25(transcriptsDir, fileName);
|
|
5040
5362
|
const body = `
|
|
5041
5363
|
${input.text.trim()}
|
|
5042
5364
|
`;
|
|
@@ -5054,7 +5376,7 @@ ${input.text.trim()}
|
|
|
5054
5376
|
frontmatterLines.push("---");
|
|
5055
5377
|
const content = frontmatterLines.join("\n") + body;
|
|
5056
5378
|
try {
|
|
5057
|
-
await
|
|
5379
|
+
await writeFile3(filePath, content, "utf8");
|
|
5058
5380
|
} catch (e) {
|
|
5059
5381
|
return {
|
|
5060
5382
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5077,8 +5399,8 @@ ${input.text.trim()}
|
|
|
5077
5399
|
|
|
5078
5400
|
// src/commands/memory.ts
|
|
5079
5401
|
import { createHash as createHash3 } from "crypto";
|
|
5080
|
-
import { mkdir as mkdir4, readFile as readFile7, readdir as readdir2, stat as stat2, writeFile as
|
|
5081
|
-
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";
|
|
5082
5404
|
|
|
5083
5405
|
// src/utils/memory-authority.ts
|
|
5084
5406
|
var TIER_RANK = {
|
|
@@ -5197,9 +5519,9 @@ async function runMemoryIndex(input) {
|
|
|
5197
5519
|
}
|
|
5198
5520
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5199
5521
|
const relCachePath = memoryCacheRelPath(input.project);
|
|
5200
|
-
const absCachePath =
|
|
5201
|
-
await mkdir4(
|
|
5202
|
-
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({
|
|
5203
5525
|
generated_at: generatedAt,
|
|
5204
5526
|
project: input.project,
|
|
5205
5527
|
topics: state.topics,
|
|
@@ -5390,7 +5712,7 @@ async function buildMemoryIndexState(pages, project) {
|
|
|
5390
5712
|
}
|
|
5391
5713
|
async function checkMemoryIndex(vault, project, current) {
|
|
5392
5714
|
const relCachePath = memoryCacheRelPath(project);
|
|
5393
|
-
const cacheText = await readIfExists(
|
|
5715
|
+
const cacheText = await readIfExists(join26(vault, relCachePath));
|
|
5394
5716
|
if (!cacheText) {
|
|
5395
5717
|
return {
|
|
5396
5718
|
ok: true,
|
|
@@ -5799,7 +6121,7 @@ async function walkImportFiles(dir, out) {
|
|
|
5799
6121
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
5800
6122
|
for (const entry of entries) {
|
|
5801
6123
|
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
5802
|
-
const path =
|
|
6124
|
+
const path = join26(dir, entry.name);
|
|
5803
6125
|
if (entry.isDirectory()) {
|
|
5804
6126
|
await walkImportFiles(path, out);
|
|
5805
6127
|
} else if (entry.isFile() && isImportCandidate(path)) {
|
|
@@ -5849,7 +6171,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
|
|
|
5849
6171
|
const redacted = redactSensitiveContent(extracted, { file });
|
|
5850
6172
|
const privacy = redacted.findings.length > 0 ? "sensitive" : "local";
|
|
5851
6173
|
const sourceSlug = slugify2(basename2(file, extname(file)));
|
|
5852
|
-
const relSource = relative3(sourceRoot, file).split(
|
|
6174
|
+
const relSource = relative3(sourceRoot, file).split(sep3).join("/");
|
|
5853
6175
|
const entry = {
|
|
5854
6176
|
...baseEntry,
|
|
5855
6177
|
status: "ready",
|
|
@@ -5866,9 +6188,9 @@ async function writeImportCapture(vault, entry, today) {
|
|
|
5866
6188
|
const content = hiddenString(entry, "__content");
|
|
5867
6189
|
const project = hiddenString(entry, "__project");
|
|
5868
6190
|
const relPath = await availableImportPath(vault, entry.proposed_path);
|
|
5869
|
-
const absPath =
|
|
5870
|
-
await mkdir4(
|
|
5871
|
-
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");
|
|
5872
6194
|
const validation = await runValidate({ file: absPath });
|
|
5873
6195
|
return {
|
|
5874
6196
|
relPath,
|
|
@@ -5883,7 +6205,7 @@ async function availableImportPath(vault, proposed) {
|
|
|
5883
6205
|
const stem = proposed.slice(0, -ext.length);
|
|
5884
6206
|
let candidate = proposed;
|
|
5885
6207
|
let i = 2;
|
|
5886
|
-
while (await readIfExists(
|
|
6208
|
+
while (await readIfExists(join26(vault, candidate))) {
|
|
5887
6209
|
candidate = `${stem}-${i}${ext}`;
|
|
5888
6210
|
i++;
|
|
5889
6211
|
}
|
|
@@ -5921,7 +6243,7 @@ function hiddenString(entry, key) {
|
|
|
5921
6243
|
return entry[key] ?? "";
|
|
5922
6244
|
}
|
|
5923
6245
|
function classifyImportSource(file) {
|
|
5924
|
-
const rel = file.split(
|
|
6246
|
+
const rel = file.split(sep3).join("/");
|
|
5925
6247
|
const name = basename2(file);
|
|
5926
6248
|
if (rel.includes("/.codex/memories/")) return "codex-memory";
|
|
5927
6249
|
if (rel.includes("/.codex/rules/")) return "codex-rule";
|
|
@@ -6068,10 +6390,10 @@ function memoryCacheRelPath(project) {
|
|
|
6068
6390
|
}
|
|
6069
6391
|
async function readMemoryCache(vault, project) {
|
|
6070
6392
|
if (project) {
|
|
6071
|
-
const projectCache = await readIfExists(
|
|
6393
|
+
const projectCache = await readIfExists(join26(vault, memoryCacheRelPath(project)));
|
|
6072
6394
|
if (projectCache) return projectCache;
|
|
6073
6395
|
}
|
|
6074
|
-
return readIfExists(
|
|
6396
|
+
return readIfExists(join26(vault, ".skillwiki", "memory-topics.json"));
|
|
6075
6397
|
}
|
|
6076
6398
|
function dedupePages(pages) {
|
|
6077
6399
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6163,7 +6485,7 @@ function slugify2(value) {
|
|
|
6163
6485
|
|
|
6164
6486
|
// src/commands/query.ts
|
|
6165
6487
|
import { readFile as readFile9, stat as stat4 } from "fs/promises";
|
|
6166
|
-
import { join as
|
|
6488
|
+
import { join as join28 } from "path";
|
|
6167
6489
|
|
|
6168
6490
|
// src/utils/rrf.ts
|
|
6169
6491
|
var RRF_K = 60;
|
|
@@ -6179,8 +6501,8 @@ function fuseRankings(lists, k = RRF_K) {
|
|
|
6179
6501
|
|
|
6180
6502
|
// src/utils/vector-index.ts
|
|
6181
6503
|
import { mkdir as mkdir5, readFile as readFile8, stat as stat3 } from "fs/promises";
|
|
6182
|
-
import { join as
|
|
6183
|
-
import { existsSync as
|
|
6504
|
+
import { join as join27 } from "path";
|
|
6505
|
+
import { existsSync as existsSync23 } from "fs";
|
|
6184
6506
|
var VECTOR_INDEX_REL = ".skillwiki/vectors/index.json";
|
|
6185
6507
|
var VECTOR_INDEX_SCHEMA = "skillwiki-tfidf-index/v1";
|
|
6186
6508
|
function tokenize(text) {
|
|
@@ -6194,7 +6516,7 @@ function extractPageTokens(text) {
|
|
|
6194
6516
|
return tokenize(`${title} ${body}`);
|
|
6195
6517
|
}
|
|
6196
6518
|
function vectorIndexPath(vault) {
|
|
6197
|
-
return
|
|
6519
|
+
return join27(vault, ...VECTOR_INDEX_REL.split("/"));
|
|
6198
6520
|
}
|
|
6199
6521
|
function termFreq(tokens) {
|
|
6200
6522
|
const tf = /* @__PURE__ */ new Map();
|
|
@@ -6249,7 +6571,7 @@ async function buildVectorIndex(vault, now = (/* @__PURE__ */ new Date()).toISOS
|
|
|
6249
6571
|
docs
|
|
6250
6572
|
};
|
|
6251
6573
|
const dest = vectorIndexPath(vault);
|
|
6252
|
-
await mkdir5(
|
|
6574
|
+
await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
|
|
6253
6575
|
await atomicWriteText(dest, `${JSON.stringify(index)}
|
|
6254
6576
|
`);
|
|
6255
6577
|
return ok(index);
|
|
@@ -6292,7 +6614,7 @@ async function reindexPageInVectorIndex(vault, pageRelPath, now = (/* @__PURE__
|
|
|
6292
6614
|
if (!normalizedPage.endsWith(".md")) {
|
|
6293
6615
|
return err("USAGE", { message: `Page must be a .md file: ${pageRelPath}` });
|
|
6294
6616
|
}
|
|
6295
|
-
const absPath =
|
|
6617
|
+
const absPath = join27(vault, ...normalizedPage.split("/"));
|
|
6296
6618
|
let text;
|
|
6297
6619
|
try {
|
|
6298
6620
|
text = await readFile8(absPath, "utf8");
|
|
@@ -6377,7 +6699,7 @@ async function reindexPageInVectorIndex(vault, pageRelPath, now = (/* @__PURE__
|
|
|
6377
6699
|
}
|
|
6378
6700
|
}
|
|
6379
6701
|
const dest = vectorIndexPath(vault);
|
|
6380
|
-
await mkdir5(
|
|
6702
|
+
await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
|
|
6381
6703
|
await atomicWriteText(dest, `${JSON.stringify(index)}
|
|
6382
6704
|
`);
|
|
6383
6705
|
return ok({
|
|
@@ -6395,8 +6717,8 @@ async function pruneVectorIndex(vault, opts) {
|
|
|
6395
6717
|
const orphans = [];
|
|
6396
6718
|
for (const docKey of Object.keys(index.docs)) {
|
|
6397
6719
|
const normalizedKey = docKey.split(/\\|\//).join("/");
|
|
6398
|
-
const absPath =
|
|
6399
|
-
if (!
|
|
6720
|
+
const absPath = join27(vault, ...normalizedKey.split("/"));
|
|
6721
|
+
if (!existsSync23(absPath)) {
|
|
6400
6722
|
orphans.push(normalizedKey);
|
|
6401
6723
|
}
|
|
6402
6724
|
}
|
|
@@ -6442,7 +6764,7 @@ async function pruneVectorIndex(vault, opts) {
|
|
|
6442
6764
|
index.docs[docKey] = toTfIdf(termFreq(tokens), index.df, newPageCount);
|
|
6443
6765
|
} else {
|
|
6444
6766
|
try {
|
|
6445
|
-
const text = await readFile8(
|
|
6767
|
+
const text = await readFile8(join27(vault, ...docKey.split("/")), "utf8");
|
|
6446
6768
|
const tokens = extractPageTokens(text);
|
|
6447
6769
|
index.docs[docKey] = toTfIdf(termFreq(tokens), index.df, newPageCount);
|
|
6448
6770
|
} catch {
|
|
@@ -6452,7 +6774,7 @@ async function pruneVectorIndex(vault, opts) {
|
|
|
6452
6774
|
}
|
|
6453
6775
|
if (!opts?.dryRun) {
|
|
6454
6776
|
const dest = vectorIndexPath(vault);
|
|
6455
|
-
await mkdir5(
|
|
6777
|
+
await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
|
|
6456
6778
|
await atomicWriteText(dest, `${JSON.stringify(index)}
|
|
6457
6779
|
`);
|
|
6458
6780
|
}
|
|
@@ -6583,7 +6905,7 @@ async function runQuery(input) {
|
|
|
6583
6905
|
}
|
|
6584
6906
|
let pendingSources;
|
|
6585
6907
|
if (input.includePending) {
|
|
6586
|
-
const { runSourcesPending: runSourcesPending2 } = await import("./sources-
|
|
6908
|
+
const { runSourcesPending: runSourcesPending2 } = await import("./sources-CLOF5VAT.js");
|
|
6587
6909
|
const pending = await runSourcesPending2({
|
|
6588
6910
|
vault: input.vault,
|
|
6589
6911
|
match: input.text,
|
|
@@ -6753,7 +7075,7 @@ function computeKeywordScore(terms, title, tags, body, relPath) {
|
|
|
6753
7075
|
return score;
|
|
6754
7076
|
}
|
|
6755
7077
|
async function loadOrBuildGraph(vault) {
|
|
6756
|
-
const graphPath =
|
|
7078
|
+
const graphPath = join28(vault, ".skillwiki", "graph.json");
|
|
6757
7079
|
let needsBuild = false;
|
|
6758
7080
|
try {
|
|
6759
7081
|
const fileStat = await stat4(graphPath);
|
|
@@ -6842,27 +7164,27 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6842
7164
|
import { z } from "zod";
|
|
6843
7165
|
|
|
6844
7166
|
// src/mcp/vault-resolve.ts
|
|
6845
|
-
import { join as
|
|
7167
|
+
import { join as join29, resolve as resolve5 } from "path";
|
|
6846
7168
|
|
|
6847
7169
|
// src/mcp/allowlist.ts
|
|
6848
|
-
import { resolve as
|
|
6849
|
-
import { realpathSync } from "fs";
|
|
7170
|
+
import { resolve as resolve4, sep as sep4 } from "path";
|
|
7171
|
+
import { realpathSync as realpathSync2 } from "fs";
|
|
6850
7172
|
function parseVaultAllowlist(envValue) {
|
|
6851
7173
|
if (envValue === void 0 || envValue.trim() === "") return null;
|
|
6852
|
-
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));
|
|
6853
7175
|
}
|
|
6854
7176
|
function vaultAllowedByList(vaultPath, allowlist) {
|
|
6855
7177
|
if (!allowlist || allowlist.length === 0) return true;
|
|
6856
7178
|
let canonical = vaultPath;
|
|
6857
7179
|
try {
|
|
6858
|
-
canonical =
|
|
7180
|
+
canonical = realpathSync2(vaultPath);
|
|
6859
7181
|
} catch {
|
|
6860
|
-
canonical =
|
|
7182
|
+
canonical = resolve4(vaultPath);
|
|
6861
7183
|
}
|
|
6862
|
-
const resolved =
|
|
7184
|
+
const resolved = resolve4(canonical);
|
|
6863
7185
|
return allowlist.some((root) => {
|
|
6864
|
-
const r =
|
|
6865
|
-
return resolved === r || resolved.startsWith(r +
|
|
7186
|
+
const r = resolve4(root);
|
|
7187
|
+
return resolved === r || resolved.startsWith(r + sep4);
|
|
6866
7188
|
});
|
|
6867
7189
|
}
|
|
6868
7190
|
function getVaultAllowlistFromEnv() {
|
|
@@ -6875,7 +7197,7 @@ async function resolveMcpVault(input) {
|
|
|
6875
7197
|
let vaultPath;
|
|
6876
7198
|
let source = "resolved";
|
|
6877
7199
|
if (input.vault !== void 0 && input.vault.length > 0) {
|
|
6878
|
-
vaultPath =
|
|
7200
|
+
vaultPath = resolve5(input.vault);
|
|
6879
7201
|
source = "flag";
|
|
6880
7202
|
} else {
|
|
6881
7203
|
const r = await resolveRuntimePath({
|
|
@@ -6887,7 +7209,7 @@ async function resolveMcpVault(input) {
|
|
|
6887
7209
|
cwd: input.cwd ?? process.cwd()
|
|
6888
7210
|
});
|
|
6889
7211
|
if (!r.ok) return r;
|
|
6890
|
-
vaultPath =
|
|
7212
|
+
vaultPath = resolve5(r.data.path);
|
|
6891
7213
|
source = r.data.source;
|
|
6892
7214
|
}
|
|
6893
7215
|
const scan = await scanVault(vaultPath);
|
|
@@ -6904,7 +7226,7 @@ async function resolveMcpVault(input) {
|
|
|
6904
7226
|
return ok({ vault: vaultPath, source });
|
|
6905
7227
|
}
|
|
6906
7228
|
function defaultGraphOut(vault) {
|
|
6907
|
-
return
|
|
7229
|
+
return join29(vault, ".skillwiki", "graph.json");
|
|
6908
7230
|
}
|
|
6909
7231
|
|
|
6910
7232
|
// src/mcp/result-format.ts
|
|
@@ -6920,7 +7242,7 @@ function formatToolResult(payload) {
|
|
|
6920
7242
|
|
|
6921
7243
|
// src/mcp/audit-log.ts
|
|
6922
7244
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
6923
|
-
import { join as
|
|
7245
|
+
import { join as join30 } from "path";
|
|
6924
7246
|
function auditEnabled() {
|
|
6925
7247
|
const v = process.env.SKILLWIKI_MCP_AUDIT;
|
|
6926
7248
|
if (v === "0" || v === "false") return false;
|
|
@@ -6932,7 +7254,7 @@ function auditSink() {
|
|
|
6932
7254
|
function auditFilePath() {
|
|
6933
7255
|
const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
|
|
6934
7256
|
if (custom && custom.length > 0) return custom;
|
|
6935
|
-
return
|
|
7257
|
+
return join30(resolveSkillwikiHome(), ".skillwiki", "mcp-audit.jsonl");
|
|
6936
7258
|
}
|
|
6937
7259
|
function auditMcpToolCall(entry) {
|
|
6938
7260
|
if (!auditEnabled()) return;
|
|
@@ -6942,7 +7264,7 @@ function auditMcpToolCall(entry) {
|
|
|
6942
7264
|
return;
|
|
6943
7265
|
}
|
|
6944
7266
|
const path = auditFilePath();
|
|
6945
|
-
mkdirSync2(
|
|
7267
|
+
mkdirSync2(join30(path, ".."), { recursive: true });
|
|
6946
7268
|
appendFileSync(path, line, "utf8");
|
|
6947
7269
|
}
|
|
6948
7270
|
async function runMcpToolHandler(tool, input, fn) {
|
|
@@ -7217,7 +7539,7 @@ function registerMcpMutatingTools(server) {
|
|
|
7217
7539
|
|
|
7218
7540
|
// src/mcp/resources.ts
|
|
7219
7541
|
import { readFile as readFile11 } from "fs/promises";
|
|
7220
|
-
import { join as
|
|
7542
|
+
import { join as join32 } from "path";
|
|
7221
7543
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7222
7544
|
|
|
7223
7545
|
// src/mcp/lint-bucket.ts
|
|
@@ -7347,8 +7669,8 @@ async function fetchQueryPreview(input) {
|
|
|
7347
7669
|
|
|
7348
7670
|
// src/mcp/graph-html.ts
|
|
7349
7671
|
import { readFile as readFile10 } from "fs/promises";
|
|
7350
|
-
import { join as
|
|
7351
|
-
import { existsSync as
|
|
7672
|
+
import { join as join31 } from "path";
|
|
7673
|
+
import { existsSync as existsSync24 } from "fs";
|
|
7352
7674
|
var TYPE_COLORS = {
|
|
7353
7675
|
entities: "#e74c3c",
|
|
7354
7676
|
concepts: "#27ae60",
|
|
@@ -7416,9 +7738,9 @@ ${nodeSvg}
|
|
|
7416
7738
|
return { html, node_count: nodes.length, edge_count: edges.length, truncated };
|
|
7417
7739
|
}
|
|
7418
7740
|
async function fetchGraphHtmlReport(input) {
|
|
7419
|
-
const graphPath = input.graphPath ??
|
|
7741
|
+
const graphPath = input.graphPath ?? join31(input.vault, ".skillwiki", "graph.json");
|
|
7420
7742
|
const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
|
|
7421
|
-
if (!
|
|
7743
|
+
if (!existsSync24(graphPath)) {
|
|
7422
7744
|
return {
|
|
7423
7745
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
7424
7746
|
result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
|
|
@@ -7490,7 +7812,7 @@ async function fetchStaleSummary(input) {
|
|
|
7490
7812
|
|
|
7491
7813
|
// src/mcp/resources.ts
|
|
7492
7814
|
async function readVaultFile(vault, rel) {
|
|
7493
|
-
return readFile11(
|
|
7815
|
+
return readFile11(join32(vault, rel), "utf8");
|
|
7494
7816
|
}
|
|
7495
7817
|
async function tailLines(text, lines) {
|
|
7496
7818
|
const parts = text.split(/\r?\n/);
|
|
@@ -7576,7 +7898,7 @@ function registerMcpResources(server) {
|
|
|
7576
7898
|
if (!v.ok) {
|
|
7577
7899
|
return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
|
|
7578
7900
|
}
|
|
7579
|
-
const path =
|
|
7901
|
+
const path = join32(v.data.vault, ".skillwiki", "graph.json");
|
|
7580
7902
|
try {
|
|
7581
7903
|
const raw = await readFile11(path, "utf8");
|
|
7582
7904
|
const graph = JSON.parse(raw);
|
|
@@ -7951,6 +8273,8 @@ export {
|
|
|
7951
8273
|
evaluateSatelliteRunHealthState,
|
|
7952
8274
|
withMcpClient,
|
|
7953
8275
|
DEFAULT_MCP_URL,
|
|
8276
|
+
CLIP_NOT_CONFIGURED,
|
|
8277
|
+
loadClipOutboxConfig,
|
|
7954
8278
|
runDoctor,
|
|
7955
8279
|
readCliPackageJson,
|
|
7956
8280
|
DEFAULT_DIRTY_VOLUME_THRESHOLD,
|