skillwiki 0.5.3 → 0.5.5
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/cli.js +672 -236
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
|
11
11
|
import { readFileSync as readFileSync10 } from "fs";
|
|
12
|
-
import { join as
|
|
12
|
+
import { join as join40 } from "path";
|
|
13
13
|
import { Command as Command2 } from "commander";
|
|
14
14
|
|
|
15
15
|
// ../shared/src/exit-codes.ts
|
|
@@ -60,7 +60,8 @@ var ExitCode = {
|
|
|
60
60
|
SYNC_PULL_FAILED: 43,
|
|
61
61
|
BACKUP_SYNC_FAILED: 44,
|
|
62
62
|
BACKUP_RESTORE_CONFLICTS: 45,
|
|
63
|
-
USAGE: 46
|
|
63
|
+
USAGE: 46,
|
|
64
|
+
BODY_TRUNCATION_GUARD: 47
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
// ../shared/src/json-output.ts
|
|
@@ -307,11 +308,11 @@ async function runHash(input) {
|
|
|
307
308
|
}
|
|
308
309
|
const split = splitFrontmatter(text);
|
|
309
310
|
if (!split.ok) return { exitCode: ExitCode.MISSING_CLOSING_DELIMITER, result: split };
|
|
310
|
-
const
|
|
311
|
-
const sha256 = createHash("sha256").update(
|
|
311
|
+
const bodyBytes2 = Buffer.from(split.data.body, "utf8");
|
|
312
|
+
const sha256 = createHash("sha256").update(bodyBytes2).digest("hex");
|
|
312
313
|
return {
|
|
313
314
|
exitCode: ExitCode.OK,
|
|
314
|
-
result: ok({ path: input.file, sha256, byte_count:
|
|
315
|
+
result: ok({ path: input.file, sha256, byte_count: bodyBytes2.byteLength, humanHint: sha256 })
|
|
315
316
|
};
|
|
316
317
|
}
|
|
317
318
|
|
|
@@ -1957,9 +1958,9 @@ async function runStale(input) {
|
|
|
1957
1958
|
if (input.project && !project.includes(input.project)) continue;
|
|
1958
1959
|
let inferred = false;
|
|
1959
1960
|
if (input.forceScan && !kind) {
|
|
1960
|
-
const
|
|
1961
|
-
if (!LOOP_CYCLE_PATTERN.test(
|
|
1962
|
-
const m =
|
|
1961
|
+
const basename2 = t.relPath.split("/").pop();
|
|
1962
|
+
if (!LOOP_CYCLE_PATTERN.test(basename2)) {
|
|
1963
|
+
const m = basename2.match(KIND_FROM_FILENAME);
|
|
1963
1964
|
if (m) {
|
|
1964
1965
|
kind = m[1];
|
|
1965
1966
|
inferred = true;
|
|
@@ -2362,8 +2363,8 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
|
|
|
2362
2363
|
|
|
2363
2364
|
// src/commands/lint.ts
|
|
2364
2365
|
import { existsSync as existsSync3 } from "fs";
|
|
2365
|
-
import { readFile as
|
|
2366
|
-
import { join as
|
|
2366
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
2367
|
+
import { join as join19 } from "path";
|
|
2367
2368
|
|
|
2368
2369
|
// src/commands/topic-map-check.ts
|
|
2369
2370
|
var DEFAULT_THRESHOLD = 200;
|
|
@@ -2490,6 +2491,81 @@ async function runDedup(input) {
|
|
|
2490
2491
|
};
|
|
2491
2492
|
}
|
|
2492
2493
|
|
|
2494
|
+
// src/utils/safe-write.ts
|
|
2495
|
+
import { open, readFile as readFile14, rename as rename4, unlink as unlink2, writeFile as writeFile8 } from "fs/promises";
|
|
2496
|
+
import { randomBytes } from "crypto";
|
|
2497
|
+
import { dirname as dirname7, basename, join as join18 } from "path";
|
|
2498
|
+
var DEFAULT_MIN_BODY_RATIO = 0.5;
|
|
2499
|
+
var DEFAULT_MIN_OLD_BODY_BYTES = 200;
|
|
2500
|
+
function bodyBytes(text) {
|
|
2501
|
+
const split = splitFrontmatter(text);
|
|
2502
|
+
if (!split.ok) return Buffer.byteLength(text, "utf8");
|
|
2503
|
+
return Buffer.byteLength(split.data.body, "utf8");
|
|
2504
|
+
}
|
|
2505
|
+
async function readIfExists(absPath) {
|
|
2506
|
+
try {
|
|
2507
|
+
return await readFile14(absPath, "utf8");
|
|
2508
|
+
} catch (e) {
|
|
2509
|
+
if (e.code === "ENOENT") return null;
|
|
2510
|
+
throw e;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
async function safeWritePage(absPath, newContent, opts = {}) {
|
|
2514
|
+
const minRatio = opts.minBodyRatio === void 0 ? DEFAULT_MIN_BODY_RATIO : opts.minBodyRatio;
|
|
2515
|
+
const minOldBytes = opts.minOldBodyBytes ?? DEFAULT_MIN_OLD_BODY_BYTES;
|
|
2516
|
+
let oldContent;
|
|
2517
|
+
try {
|
|
2518
|
+
oldContent = await readIfExists(absPath);
|
|
2519
|
+
} catch (e) {
|
|
2520
|
+
return err("WRITE_FAILED", { path: absPath, phase: "read-existing", message: String(e) });
|
|
2521
|
+
}
|
|
2522
|
+
const isNew = oldContent === null;
|
|
2523
|
+
const oldBodyBytes = isNew ? 0 : bodyBytes(oldContent);
|
|
2524
|
+
const newBodyBytes = bodyBytes(newContent);
|
|
2525
|
+
const bodyRatio = oldBodyBytes > 0 ? newBodyBytes / oldBodyBytes : null;
|
|
2526
|
+
let guardSkippedSmall = false;
|
|
2527
|
+
if (!isNew && minRatio !== null && bodyRatio !== null && bodyRatio < minRatio) {
|
|
2528
|
+
if (oldBodyBytes < minOldBytes) {
|
|
2529
|
+
guardSkippedSmall = true;
|
|
2530
|
+
} else {
|
|
2531
|
+
return err("BODY_TRUNCATION_GUARD", {
|
|
2532
|
+
path: absPath,
|
|
2533
|
+
oldBodyBytes,
|
|
2534
|
+
newBodyBytes,
|
|
2535
|
+
bodyRatio,
|
|
2536
|
+
minBodyRatio: minRatio,
|
|
2537
|
+
hint: "Refusing to write \u2014 new body lost too much content. Likely a parse-modify-serialize bug or a write race. Verify the page source before retrying."
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
if (!isNew && oldContent === newContent) {
|
|
2542
|
+
return ok({ isNew: false, oldBodyBytes, newBodyBytes, bodyRatio, guardSkippedSmall });
|
|
2543
|
+
}
|
|
2544
|
+
const dir = dirname7(absPath);
|
|
2545
|
+
const tmpName = `.${basename(absPath)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
2546
|
+
const tmpPath = join18(dir, tmpName);
|
|
2547
|
+
try {
|
|
2548
|
+
const handle = await open(tmpPath, "w");
|
|
2549
|
+
try {
|
|
2550
|
+
await handle.writeFile(newContent, "utf8");
|
|
2551
|
+
try {
|
|
2552
|
+
await handle.sync();
|
|
2553
|
+
} catch {
|
|
2554
|
+
}
|
|
2555
|
+
} finally {
|
|
2556
|
+
await handle.close();
|
|
2557
|
+
}
|
|
2558
|
+
await rename4(tmpPath, absPath);
|
|
2559
|
+
return ok({ isNew, oldBodyBytes, newBodyBytes, bodyRatio, guardSkippedSmall });
|
|
2560
|
+
} catch (e) {
|
|
2561
|
+
try {
|
|
2562
|
+
await unlink2(tmpPath);
|
|
2563
|
+
} catch {
|
|
2564
|
+
}
|
|
2565
|
+
return err("WRITE_FAILED", { path: absPath, phase: "atomic-write", message: String(e) });
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2493
2569
|
// src/commands/raw-body-dedup.ts
|
|
2494
2570
|
import { createHash as createHash2 } from "crypto";
|
|
2495
2571
|
async function runRawBodyDedup(vault) {
|
|
@@ -2813,7 +2889,7 @@ async function runLint(input) {
|
|
|
2813
2889
|
let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
|
|
2814
2890
|
rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
|
|
2815
2891
|
if (!rawPath.startsWith("raw/") && !rawPath.startsWith("_archive/raw/")) continue;
|
|
2816
|
-
if (!existsSync3(
|
|
2892
|
+
if (!existsSync3(join19(input.vault, rawPath)) && !existsSync3(join19(input.vault, rawPath + ".md")) && !rawPath.startsWith("_archive/") && !existsSync3(join19(input.vault, "_archive", rawPath)) && !existsSync3(join19(input.vault, "_archive", rawPath + ".md"))) {
|
|
2817
2893
|
brokenSourceFlags.push(`${page.relPath}: ${rawPath}`);
|
|
2818
2894
|
}
|
|
2819
2895
|
}
|
|
@@ -2904,11 +2980,11 @@ async function runLint(input) {
|
|
|
2904
2980
|
const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
|
|
2905
2981
|
if (!slugMatch) continue;
|
|
2906
2982
|
const slug = slugMatch[1];
|
|
2907
|
-
const knowledgePath =
|
|
2983
|
+
const knowledgePath = join19(input.vault, "projects", slug, "knowledge.md");
|
|
2908
2984
|
if (!existsSync3(knowledgePath)) continue;
|
|
2909
2985
|
const pageRef = page.relPath.replace(/\.md$/, "");
|
|
2910
2986
|
try {
|
|
2911
|
-
const knowledgeContent = await
|
|
2987
|
+
const knowledgeContent = await readFile15(knowledgePath, "utf8");
|
|
2912
2988
|
if (!knowledgeContent.includes(`[[${pageRef}]]`)) {
|
|
2913
2989
|
orphanedProjectPages.push(`${page.relPath}: not in projects/${slug}/knowledge.md`);
|
|
2914
2990
|
}
|
|
@@ -2955,7 +3031,7 @@ async function runLint(input) {
|
|
|
2955
3031
|
for (const relPath of legacyPages) {
|
|
2956
3032
|
try {
|
|
2957
3033
|
const absPath = `${input.vault}/${relPath}`;
|
|
2958
|
-
const raw = await
|
|
3034
|
+
const raw = await readFile15(absPath, "utf8");
|
|
2959
3035
|
const split = splitFrontmatter(raw);
|
|
2960
3036
|
if (!split.ok) {
|
|
2961
3037
|
unresolved.push(relPath);
|
|
@@ -3033,7 +3109,11 @@ async function runLint(input) {
|
|
|
3033
3109
|
${rawFm}
|
|
3034
3110
|
---
|
|
3035
3111
|
${newBody}`;
|
|
3036
|
-
await
|
|
3112
|
+
const w = await safeWritePage(absPath, newContent);
|
|
3113
|
+
if (!w.ok) {
|
|
3114
|
+
unresolved.push(relPath);
|
|
3115
|
+
continue;
|
|
3116
|
+
}
|
|
3037
3117
|
fixed.push(relPath);
|
|
3038
3118
|
} catch {
|
|
3039
3119
|
unresolved.push(relPath);
|
|
@@ -3050,7 +3130,7 @@ ${newBody}`;
|
|
|
3050
3130
|
for (const relPath of noOverview) {
|
|
3051
3131
|
try {
|
|
3052
3132
|
const absPath = `${input.vault}/${relPath}`;
|
|
3053
|
-
const raw = await
|
|
3133
|
+
const raw = await readFile15(absPath, "utf8");
|
|
3054
3134
|
const split = splitFrontmatter(raw);
|
|
3055
3135
|
if (!split.ok) {
|
|
3056
3136
|
unresolved.push(relPath);
|
|
@@ -3071,7 +3151,11 @@ ${rawFm}
|
|
|
3071
3151
|
${overviewSection}
|
|
3072
3152
|
|
|
3073
3153
|
${trimmedBody}`;
|
|
3074
|
-
await
|
|
3154
|
+
const w = await safeWritePage(absPath, newContent);
|
|
3155
|
+
if (!w.ok) {
|
|
3156
|
+
unresolved.push(relPath);
|
|
3157
|
+
continue;
|
|
3158
|
+
}
|
|
3075
3159
|
fixed.push(relPath);
|
|
3076
3160
|
} catch {
|
|
3077
3161
|
unresolved.push(relPath);
|
|
@@ -3087,7 +3171,7 @@ ${trimmedBody}`;
|
|
|
3087
3171
|
for (const relPath of missingTldrFlags) {
|
|
3088
3172
|
try {
|
|
3089
3173
|
const absPath = `${input.vault}/${relPath}`;
|
|
3090
|
-
const raw = await
|
|
3174
|
+
const raw = await readFile15(absPath, "utf8");
|
|
3091
3175
|
const split = splitFrontmatter(raw);
|
|
3092
3176
|
if (!split.ok) {
|
|
3093
3177
|
unresolved.push(relPath);
|
|
@@ -3115,7 +3199,11 @@ ${trimmedBody}`;
|
|
|
3115
3199
|
const newContent = `---
|
|
3116
3200
|
${trimmedFm}---
|
|
3117
3201
|
${lines.join("\n")}`;
|
|
3118
|
-
await
|
|
3202
|
+
const w = await safeWritePage(absPath, newContent);
|
|
3203
|
+
if (!w.ok) {
|
|
3204
|
+
unresolved.push(relPath);
|
|
3205
|
+
continue;
|
|
3206
|
+
}
|
|
3119
3207
|
fixed.push(relPath);
|
|
3120
3208
|
} catch {
|
|
3121
3209
|
unresolved.push(relPath);
|
|
@@ -3133,7 +3221,7 @@ ${lines.join("\n")}`;
|
|
|
3133
3221
|
for (const relPath of wikilinkCitationFlags) {
|
|
3134
3222
|
try {
|
|
3135
3223
|
const absPath = `${input.vault}/${relPath}`;
|
|
3136
|
-
const raw = await
|
|
3224
|
+
const raw = await readFile15(absPath, "utf8");
|
|
3137
3225
|
const split = splitFrontmatter(raw);
|
|
3138
3226
|
if (!split.ok) {
|
|
3139
3227
|
unresolved.push(relPath);
|
|
@@ -3197,7 +3285,11 @@ ${lines.join("\n")}`;
|
|
|
3197
3285
|
${rawFm}
|
|
3198
3286
|
---
|
|
3199
3287
|
${newBody}`;
|
|
3200
|
-
await
|
|
3288
|
+
const w = await safeWritePage(absPath, newContent);
|
|
3289
|
+
if (!w.ok) {
|
|
3290
|
+
unresolved.push(relPath);
|
|
3291
|
+
continue;
|
|
3292
|
+
}
|
|
3201
3293
|
wikilinkFixed.push(relPath);
|
|
3202
3294
|
} catch {
|
|
3203
3295
|
unresolved.push(relPath);
|
|
@@ -3286,14 +3378,14 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
|
|
|
3286
3378
|
}
|
|
3287
3379
|
|
|
3288
3380
|
// src/commands/config.ts
|
|
3289
|
-
import { readFile as
|
|
3381
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
3290
3382
|
import { existsSync as existsSync4 } from "fs";
|
|
3291
|
-
import { join as
|
|
3383
|
+
import { join as join20 } from "path";
|
|
3292
3384
|
function validateKey(key) {
|
|
3293
3385
|
return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
|
|
3294
3386
|
}
|
|
3295
3387
|
function configPath(home) {
|
|
3296
|
-
return
|
|
3388
|
+
return join20(home, ".skillwiki", ".env");
|
|
3297
3389
|
}
|
|
3298
3390
|
async function runConfigGet(input) {
|
|
3299
3391
|
if (!validateKey(input.key)) {
|
|
@@ -3311,7 +3403,7 @@ async function runConfigSet(input) {
|
|
|
3311
3403
|
try {
|
|
3312
3404
|
let originalContent;
|
|
3313
3405
|
try {
|
|
3314
|
-
originalContent = await
|
|
3406
|
+
originalContent = await readFile16(filePath, "utf8");
|
|
3315
3407
|
} catch {
|
|
3316
3408
|
}
|
|
3317
3409
|
const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
|
|
@@ -3347,14 +3439,13 @@ async function runConfigPath(input) {
|
|
|
3347
3439
|
}
|
|
3348
3440
|
|
|
3349
3441
|
// src/commands/doctor.ts
|
|
3350
|
-
import { existsSync as
|
|
3351
|
-
import { join as
|
|
3352
|
-
import { execSync } from "child_process";
|
|
3353
|
-
import { platform } from "os";
|
|
3442
|
+
import { existsSync as existsSync7, lstatSync, readlinkSync, readdirSync, statSync as statSync2 } from "fs";
|
|
3443
|
+
import { join as join24, resolve as resolve4 } from "path";
|
|
3444
|
+
import { execSync as execSync2 } from "child_process";
|
|
3354
3445
|
|
|
3355
3446
|
// src/utils/auto-update.ts
|
|
3356
3447
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
3357
|
-
import { join as
|
|
3448
|
+
import { join as join21, dirname as dirname8 } from "path";
|
|
3358
3449
|
import { spawn } from "child_process";
|
|
3359
3450
|
|
|
3360
3451
|
// src/utils/update-consts.ts
|
|
@@ -3365,7 +3456,7 @@ var CLI_DISABLE_FLAG = "--no-update-notifier";
|
|
|
3365
3456
|
|
|
3366
3457
|
// src/utils/auto-update.ts
|
|
3367
3458
|
function cachePath(home) {
|
|
3368
|
-
return
|
|
3459
|
+
return join21(home, ".skillwiki", CACHE_FILENAME);
|
|
3369
3460
|
}
|
|
3370
3461
|
function readCacheRaw(home) {
|
|
3371
3462
|
try {
|
|
@@ -3384,7 +3475,7 @@ function readCache(home) {
|
|
|
3384
3475
|
}
|
|
3385
3476
|
function writeCache(home, cache) {
|
|
3386
3477
|
const p = cachePath(home);
|
|
3387
|
-
mkdirSync2(
|
|
3478
|
+
mkdirSync2(dirname8(p), { recursive: true });
|
|
3388
3479
|
writeFileSync3(p, JSON.stringify(cache, null, 2));
|
|
3389
3480
|
}
|
|
3390
3481
|
function latestFromCache(home, currentVersion) {
|
|
@@ -3415,12 +3506,12 @@ function triggerAutoUpdate(home, currentVersion) {
|
|
|
3415
3506
|
|
|
3416
3507
|
// src/utils/plugin-registry.ts
|
|
3417
3508
|
import { readFileSync as readFileSync5 } from "fs";
|
|
3418
|
-
import { join as
|
|
3419
|
-
var REGISTRY_PATH =
|
|
3509
|
+
import { join as join22 } from "path";
|
|
3510
|
+
var REGISTRY_PATH = join22(".claude", "plugins", "installed_plugins.json");
|
|
3420
3511
|
var PLUGIN_KEY = "skillwiki@llm-wiki";
|
|
3421
3512
|
function readInstalledPlugins(home) {
|
|
3422
3513
|
try {
|
|
3423
|
-
const raw = readFileSync5(
|
|
3514
|
+
const raw = readFileSync5(join22(home, REGISTRY_PATH), "utf8");
|
|
3424
3515
|
return JSON.parse(raw);
|
|
3425
3516
|
} catch {
|
|
3426
3517
|
return null;
|
|
@@ -3434,6 +3525,208 @@ function findPlugin(home, key = PLUGIN_KEY) {
|
|
|
3434
3525
|
return entries[0];
|
|
3435
3526
|
}
|
|
3436
3527
|
|
|
3528
|
+
// src/utils/s3-mount-health.ts
|
|
3529
|
+
import { execSync } from "child_process";
|
|
3530
|
+
import { platform } from "os";
|
|
3531
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readFileSync as readFile17 } from "fs";
|
|
3532
|
+
import { join as join23 } from "path";
|
|
3533
|
+
var OS = platform();
|
|
3534
|
+
function findRcloneMountPid() {
|
|
3535
|
+
try {
|
|
3536
|
+
const out = execSync("pgrep -f 'rclone.*mount'", {
|
|
3537
|
+
encoding: "utf8",
|
|
3538
|
+
timeout: 2e3,
|
|
3539
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3540
|
+
}).trim();
|
|
3541
|
+
const pids = out.split("\n").filter(Boolean);
|
|
3542
|
+
if (pids.length === 0) return null;
|
|
3543
|
+
return parseInt(pids[0], 10);
|
|
3544
|
+
} catch {
|
|
3545
|
+
try {
|
|
3546
|
+
const out = execSync("ps aux", { encoding: "utf8", timeout: 2e3, stdio: ["pipe", "pipe", "pipe"] });
|
|
3547
|
+
for (const line of out.split("\n")) {
|
|
3548
|
+
if (line.includes("rclone") && line.includes("mount") && !line.includes("grep")) {
|
|
3549
|
+
const parts = line.trim().split(/\s+/);
|
|
3550
|
+
if (parts.length >= 2) return parseInt(parts[1], 10);
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
} catch {
|
|
3554
|
+
}
|
|
3555
|
+
return null;
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
function parseRcloneFlags(pid) {
|
|
3559
|
+
const flags = /* @__PURE__ */ new Map();
|
|
3560
|
+
try {
|
|
3561
|
+
const args = getRcloneArgs(pid);
|
|
3562
|
+
for (let i = 0; i < args.length; i++) {
|
|
3563
|
+
const arg = args[i];
|
|
3564
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
3565
|
+
const eq = arg.indexOf("=");
|
|
3566
|
+
flags.set(arg.slice(0, eq), arg.slice(eq + 1));
|
|
3567
|
+
} else if (arg.startsWith("--")) {
|
|
3568
|
+
const next = args[i + 1];
|
|
3569
|
+
if (next && !next.startsWith("-")) {
|
|
3570
|
+
flags.set(arg, next);
|
|
3571
|
+
i++;
|
|
3572
|
+
} else {
|
|
3573
|
+
flags.set(arg, "");
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
} catch {
|
|
3578
|
+
}
|
|
3579
|
+
return flags;
|
|
3580
|
+
}
|
|
3581
|
+
function getRcloneVersion() {
|
|
3582
|
+
try {
|
|
3583
|
+
const out = execSync("rclone version", {
|
|
3584
|
+
encoding: "utf8",
|
|
3585
|
+
timeout: 3e3,
|
|
3586
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3587
|
+
});
|
|
3588
|
+
const match = out.match(/rclone\s+v(\d+)\.(\d+)\.(\d+)/i);
|
|
3589
|
+
if (!match) return null;
|
|
3590
|
+
return {
|
|
3591
|
+
major: parseInt(match[1], 10),
|
|
3592
|
+
minor: parseInt(match[2], 10),
|
|
3593
|
+
patch: parseInt(match[3], 10),
|
|
3594
|
+
raw: out.split("\n")[0].trim()
|
|
3595
|
+
};
|
|
3596
|
+
} catch {
|
|
3597
|
+
return null;
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
function extractRcloneFs(args) {
|
|
3601
|
+
let foundMount = false;
|
|
3602
|
+
for (const arg of args) {
|
|
3603
|
+
if (arg === "mount") {
|
|
3604
|
+
foundMount = true;
|
|
3605
|
+
continue;
|
|
3606
|
+
}
|
|
3607
|
+
if (foundMount && arg.includes(":") && !arg.startsWith("-") && !arg.startsWith("/")) {
|
|
3608
|
+
return arg;
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
return null;
|
|
3612
|
+
}
|
|
3613
|
+
function getRcloneArgs(pid) {
|
|
3614
|
+
try {
|
|
3615
|
+
if (OS === "linux") {
|
|
3616
|
+
const raw = readFileSync6(`/proc/${pid}/cmdline`);
|
|
3617
|
+
return new TextDecoder().decode(raw).split("\0").filter(Boolean);
|
|
3618
|
+
} else {
|
|
3619
|
+
const out = execSync(`ps -o args= -p ${pid}`, {
|
|
3620
|
+
encoding: "utf8",
|
|
3621
|
+
timeout: 2e3,
|
|
3622
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3623
|
+
}).trim();
|
|
3624
|
+
return out.split(/\s+/);
|
|
3625
|
+
}
|
|
3626
|
+
} catch {
|
|
3627
|
+
return [];
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
function queryRcloneRC(rcAddr, fs) {
|
|
3631
|
+
try {
|
|
3632
|
+
const payload = JSON.stringify({ fs });
|
|
3633
|
+
const out = execSync(
|
|
3634
|
+
`curl -s --max-time 3 -X POST "http://${rcAddr}/vfs/stats" -H "Content-Type: application/json" -d '${payload}' 2>/dev/null`,
|
|
3635
|
+
{ encoding: "utf8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
3636
|
+
);
|
|
3637
|
+
if (!out.trim()) return null;
|
|
3638
|
+
const data = JSON.parse(out);
|
|
3639
|
+
if (data.status && data.status >= 400) {
|
|
3640
|
+
return { error: data.error || `RC error (status ${data.status})`, erroredFiles: 0, uploadsInProgress: 0, uploadsQueued: 0, outOfSpace: false, bytesUsed: 0, files: 0, totalSize: "unknown" };
|
|
3641
|
+
}
|
|
3642
|
+
const dc = data.diskCache || {};
|
|
3643
|
+
return {
|
|
3644
|
+
erroredFiles: dc.erroredFiles ?? 0,
|
|
3645
|
+
uploadsInProgress: dc.uploadsInProgress ?? 0,
|
|
3646
|
+
uploadsQueued: dc.uploadsQueued ?? 0,
|
|
3647
|
+
outOfSpace: dc.outOfSpace ?? false,
|
|
3648
|
+
bytesUsed: dc.bytesUsed ?? 0,
|
|
3649
|
+
files: dc.files ?? 0,
|
|
3650
|
+
totalSize: data.totalSize || "unknown"
|
|
3651
|
+
};
|
|
3652
|
+
} catch {
|
|
3653
|
+
return { error: "RC endpoint unreachable", erroredFiles: 0, uploadsInProgress: 0, uploadsQueued: 0, outOfSpace: false, bytesUsed: 0, files: 0, totalSize: "unknown" };
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
function detectFuseMount(vaultPath) {
|
|
3657
|
+
try {
|
|
3658
|
+
if (OS === "linux") {
|
|
3659
|
+
const mounts = readFileSync6("/proc/mounts", "utf8");
|
|
3660
|
+
let best = null;
|
|
3661
|
+
for (const line of mounts.split("\n")) {
|
|
3662
|
+
const parts = line.split(" ");
|
|
3663
|
+
if (parts.length < 3) continue;
|
|
3664
|
+
const point = parts[1];
|
|
3665
|
+
const fs = parts[2];
|
|
3666
|
+
if (vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
|
|
3667
|
+
best = { point, fs };
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
if (best && best.fs.includes("fuse")) return { mountPoint: best.point, fsType: best.fs };
|
|
3671
|
+
} else if (OS === "darwin") {
|
|
3672
|
+
const out = execSync("mount", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3673
|
+
let best = null;
|
|
3674
|
+
for (const line of out.split("\n")) {
|
|
3675
|
+
const match = line.match(/^(\S+) on (\S+) \((.*?)\)/);
|
|
3676
|
+
if (!match) continue;
|
|
3677
|
+
const point = match[2];
|
|
3678
|
+
const opts = match[3];
|
|
3679
|
+
if (opts.includes("fuse") && vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
|
|
3680
|
+
best = { point, fsType: `fuse.${match[1].split(":")[0] || "unknown"}` };
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
if (best) return best;
|
|
3684
|
+
}
|
|
3685
|
+
} catch {
|
|
3686
|
+
}
|
|
3687
|
+
return null;
|
|
3688
|
+
}
|
|
3689
|
+
function writeTest(dir) {
|
|
3690
|
+
const testFile = join23(dir, `.doctor-write-test-${process.pid}.tmp`);
|
|
3691
|
+
const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
|
|
3692
|
+
const start = Date.now();
|
|
3693
|
+
try {
|
|
3694
|
+
writeFileSync4(testFile, payload, "utf8");
|
|
3695
|
+
} catch (e) {
|
|
3696
|
+
return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
|
|
3697
|
+
}
|
|
3698
|
+
const writeMs = Date.now() - start;
|
|
3699
|
+
const readStart = Date.now();
|
|
3700
|
+
try {
|
|
3701
|
+
const back = readFile17(testFile, "utf8");
|
|
3702
|
+
const readMs = Date.now() - readStart;
|
|
3703
|
+
if (back !== payload) {
|
|
3704
|
+
try {
|
|
3705
|
+
unlinkSync3(testFile);
|
|
3706
|
+
} catch {
|
|
3707
|
+
}
|
|
3708
|
+
return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
|
|
3709
|
+
}
|
|
3710
|
+
} catch (e) {
|
|
3711
|
+
try {
|
|
3712
|
+
unlinkSync3(testFile);
|
|
3713
|
+
} catch {
|
|
3714
|
+
}
|
|
3715
|
+
return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
|
|
3716
|
+
}
|
|
3717
|
+
try {
|
|
3718
|
+
unlinkSync3(testFile);
|
|
3719
|
+
} catch {
|
|
3720
|
+
}
|
|
3721
|
+
return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
|
|
3722
|
+
}
|
|
3723
|
+
var FLAG_THRESHOLDS = {
|
|
3724
|
+
"--vfs-write-back": { min: 15, unit: "s", label: "VFS write-back window" },
|
|
3725
|
+
"--vfs-write-wait": { min: 10, unit: "s", label: "VFS write-wait" },
|
|
3726
|
+
"--vfs-cache-max-age": { min: 24, unit: "h", label: "VFS cache max age" }
|
|
3727
|
+
};
|
|
3728
|
+
var MIN_RCLONE_VERSION = { major: 1, minor: 65, patch: 0 };
|
|
3729
|
+
|
|
3437
3730
|
// src/commands/doctor.ts
|
|
3438
3731
|
function check(status, id, label, detail) {
|
|
3439
3732
|
return { id, label, status, detail };
|
|
@@ -3452,7 +3745,7 @@ function detectCliChannels(argv, home) {
|
|
|
3452
3745
|
channels.push({ name: "dev", path: devPath, isDevLink: true });
|
|
3453
3746
|
}
|
|
3454
3747
|
try {
|
|
3455
|
-
const whichOut =
|
|
3748
|
+
const whichOut = execSync2("which skillwiki 2>/dev/null", { encoding: "utf8" }).trim();
|
|
3456
3749
|
if (whichOut) {
|
|
3457
3750
|
const isDev = isDevSymlink(whichOut);
|
|
3458
3751
|
if (!channels.some((c) => c.path === resolve4(whichOut))) {
|
|
@@ -3463,13 +3756,13 @@ function detectCliChannels(argv, home) {
|
|
|
3463
3756
|
}
|
|
3464
3757
|
const plugin = findPlugin(home);
|
|
3465
3758
|
if (plugin) {
|
|
3466
|
-
const pluginBin =
|
|
3467
|
-
if (
|
|
3759
|
+
const pluginBin = join24(plugin.installPath, "bin", "skillwiki");
|
|
3760
|
+
if (existsSync7(pluginBin)) {
|
|
3468
3761
|
channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
|
|
3469
3762
|
}
|
|
3470
3763
|
}
|
|
3471
|
-
const installBin =
|
|
3472
|
-
if (
|
|
3764
|
+
const installBin = join24(home, ".claude", "skills", "bin", "skillwiki");
|
|
3765
|
+
if (existsSync7(installBin)) {
|
|
3473
3766
|
channels.push({ name: "install", path: installBin, isDevLink: false });
|
|
3474
3767
|
}
|
|
3475
3768
|
return channels;
|
|
@@ -3521,7 +3814,7 @@ function checkCliChannels(argv, home) {
|
|
|
3521
3814
|
}
|
|
3522
3815
|
async function checkConfigFile(home) {
|
|
3523
3816
|
const cfgPath = configPath(home);
|
|
3524
|
-
if (!
|
|
3817
|
+
if (!existsSync7(cfgPath)) {
|
|
3525
3818
|
return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
|
|
3526
3819
|
}
|
|
3527
3820
|
try {
|
|
@@ -3536,7 +3829,7 @@ function checkWikiPathExists(resolvedPath) {
|
|
|
3536
3829
|
if (resolvedPath === void 0) {
|
|
3537
3830
|
return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3538
3831
|
}
|
|
3539
|
-
if (
|
|
3832
|
+
if (existsSync7(resolvedPath) && statSync2(resolvedPath).isDirectory()) {
|
|
3540
3833
|
return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
|
|
3541
3834
|
}
|
|
3542
3835
|
return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
|
|
@@ -3545,13 +3838,13 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3545
3838
|
if (resolvedPath === void 0) {
|
|
3546
3839
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3547
3840
|
}
|
|
3548
|
-
if (!
|
|
3841
|
+
if (!existsSync7(resolvedPath)) {
|
|
3549
3842
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
3550
3843
|
}
|
|
3551
3844
|
const missing = [];
|
|
3552
|
-
if (!
|
|
3845
|
+
if (!existsSync7(join24(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
3553
3846
|
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
3554
|
-
if (!
|
|
3847
|
+
if (!existsSync7(join24(resolvedPath, dir))) missing.push(dir + "/");
|
|
3555
3848
|
}
|
|
3556
3849
|
if (missing.length === 0) {
|
|
3557
3850
|
return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
|
|
@@ -3559,8 +3852,8 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3559
3852
|
return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
|
|
3560
3853
|
}
|
|
3561
3854
|
function checkSkillsInstalled(home, cwd) {
|
|
3562
|
-
const srcDir = cwd ?
|
|
3563
|
-
if (srcDir &&
|
|
3855
|
+
const srcDir = cwd ? join24(cwd, "packages", "skills") : void 0;
|
|
3856
|
+
if (srcDir && existsSync7(srcDir)) {
|
|
3564
3857
|
const found = findSkillMd(srcDir);
|
|
3565
3858
|
if (found.length > 0) {
|
|
3566
3859
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
|
|
@@ -3573,8 +3866,8 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
3573
3866
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
|
|
3574
3867
|
}
|
|
3575
3868
|
}
|
|
3576
|
-
const skillsDir =
|
|
3577
|
-
if (
|
|
3869
|
+
const skillsDir = join24(home, ".claude", "skills");
|
|
3870
|
+
if (existsSync7(skillsDir)) {
|
|
3578
3871
|
const found = findSkillMd(skillsDir);
|
|
3579
3872
|
if (found.length > 0) {
|
|
3580
3873
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
|
|
@@ -3584,10 +3877,10 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
3584
3877
|
}
|
|
3585
3878
|
function checkDuplicateSkills(home) {
|
|
3586
3879
|
const plugin = findPlugin(home);
|
|
3587
|
-
const skillsDir =
|
|
3880
|
+
const skillsDir = join24(home, ".claude", "skills");
|
|
3588
3881
|
const agentSkillDirs = [
|
|
3589
|
-
{ label: "~/.codex/skills/", path:
|
|
3590
|
-
{ label: "~/.agents/skills/", path:
|
|
3882
|
+
{ label: "~/.codex/skills/", path: join24(home, ".codex", "skills") },
|
|
3883
|
+
{ label: "~/.agents/skills/", path: join24(home, ".agents", "skills") }
|
|
3591
3884
|
];
|
|
3592
3885
|
if (!plugin) {
|
|
3593
3886
|
return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
|
|
@@ -3664,8 +3957,8 @@ async function checkProfiles(home) {
|
|
|
3664
3957
|
}
|
|
3665
3958
|
async function checkProjectLocalOverride(cwd) {
|
|
3666
3959
|
const dir = cwd ?? process.cwd();
|
|
3667
|
-
const envPath =
|
|
3668
|
-
if (
|
|
3960
|
+
const envPath = join24(dir, ".skillwiki", ".env");
|
|
3961
|
+
if (existsSync7(envPath)) {
|
|
3669
3962
|
return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
|
|
3670
3963
|
}
|
|
3671
3964
|
return check("pass", "project_local", "Project-local config", "None");
|
|
@@ -3674,17 +3967,17 @@ function checkVaultGitRemote(resolvedPath) {
|
|
|
3674
3967
|
if (resolvedPath === void 0) {
|
|
3675
3968
|
return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3676
3969
|
}
|
|
3677
|
-
if (!
|
|
3970
|
+
if (!existsSync7(join24(resolvedPath, ".git"))) {
|
|
3678
3971
|
return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
|
|
3679
3972
|
}
|
|
3680
3973
|
try {
|
|
3681
|
-
const remote =
|
|
3974
|
+
const remote = execSync2("git remote", { cwd: resolvedPath, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3682
3975
|
if (!remote) {
|
|
3683
3976
|
return check("warn", "vault_git_remote", "Vault git remote", "No remote configured \u2014 push/pull unavailable");
|
|
3684
3977
|
}
|
|
3685
3978
|
let branch = "(no commits yet)";
|
|
3686
3979
|
try {
|
|
3687
|
-
branch =
|
|
3980
|
+
branch = execSync2("git rev-parse --abbrev-ref HEAD", { cwd: resolvedPath, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3688
3981
|
} catch {
|
|
3689
3982
|
}
|
|
3690
3983
|
return check("pass", "vault_git_remote", "Vault git remote", `Remote: ${remote.split("\n")[0]}, branch: ${branch}`);
|
|
@@ -3697,9 +3990,9 @@ function checkObsidianTemplates(resolvedPath) {
|
|
|
3697
3990
|
return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3698
3991
|
}
|
|
3699
3992
|
const missing = [];
|
|
3700
|
-
if (!
|
|
3701
|
-
if (!
|
|
3702
|
-
if (!
|
|
3993
|
+
if (!existsSync7(join24(resolvedPath, "_Templates"))) missing.push("_Templates/");
|
|
3994
|
+
if (!existsSync7(join24(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
|
|
3995
|
+
if (!existsSync7(join24(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
|
|
3703
3996
|
if (missing.length === 0) {
|
|
3704
3997
|
return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
|
|
3705
3998
|
}
|
|
@@ -3709,8 +4002,8 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
3709
4002
|
if (resolvedPath === void 0) {
|
|
3710
4003
|
return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3711
4004
|
}
|
|
3712
|
-
const rawDir =
|
|
3713
|
-
if (!
|
|
4005
|
+
const rawDir = join24(resolvedPath, "raw");
|
|
4006
|
+
if (!existsSync7(rawDir)) {
|
|
3714
4007
|
return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
|
|
3715
4008
|
}
|
|
3716
4009
|
const found = [];
|
|
@@ -3725,7 +4018,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
3725
4018
|
if (entry.name === ".DS_Store") {
|
|
3726
4019
|
found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
|
|
3727
4020
|
} else if (entry.isDirectory()) {
|
|
3728
|
-
walk2(
|
|
4021
|
+
walk2(join24(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
3729
4022
|
}
|
|
3730
4023
|
}
|
|
3731
4024
|
})(rawDir, "");
|
|
@@ -3738,12 +4031,12 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
3738
4031
|
if (resolvedPath === void 0) {
|
|
3739
4032
|
return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3740
4033
|
}
|
|
3741
|
-
if (!
|
|
4034
|
+
if (!existsSync7(join24(resolvedPath, ".git"))) {
|
|
3742
4035
|
return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
|
|
3743
4036
|
}
|
|
3744
4037
|
let timestamp;
|
|
3745
4038
|
try {
|
|
3746
|
-
const out =
|
|
4039
|
+
const out = execSync2("git log -1 --format=%ct origin/HEAD", {
|
|
3747
4040
|
cwd: resolvedPath,
|
|
3748
4041
|
encoding: "utf8",
|
|
3749
4042
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3751,7 +4044,7 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
3751
4044
|
timestamp = parseInt(out, 10);
|
|
3752
4045
|
} catch {
|
|
3753
4046
|
try {
|
|
3754
|
-
const out =
|
|
4047
|
+
const out = execSync2("git log -1 --format=%ct HEAD", {
|
|
3755
4048
|
cwd: resolvedPath,
|
|
3756
4049
|
encoding: "utf8",
|
|
3757
4050
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3770,56 +4063,23 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
3770
4063
|
}
|
|
3771
4064
|
return check("pass", "sync_last_push", "Vault sync recency", `Last push: ${dateStr} (${daysSince2} day(s) ago)`);
|
|
3772
4065
|
}
|
|
3773
|
-
function detectFuseMount(vaultPath) {
|
|
3774
|
-
const os = platform();
|
|
3775
|
-
try {
|
|
3776
|
-
if (os === "linux") {
|
|
3777
|
-
const mounts = readFileSync6("/proc/mounts", "utf8");
|
|
3778
|
-
let best = null;
|
|
3779
|
-
for (const line of mounts.split("\n")) {
|
|
3780
|
-
const parts = line.split(" ");
|
|
3781
|
-
if (parts.length < 3) continue;
|
|
3782
|
-
const point = parts[1];
|
|
3783
|
-
const fs = parts[2];
|
|
3784
|
-
if (vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
|
|
3785
|
-
best = { point, fs };
|
|
3786
|
-
}
|
|
3787
|
-
}
|
|
3788
|
-
if (best && best.fs.includes("fuse")) return best.point;
|
|
3789
|
-
} else if (os === "darwin") {
|
|
3790
|
-
const out = execSync("mount", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3791
|
-
let best = null;
|
|
3792
|
-
for (const line of out.split("\n")) {
|
|
3793
|
-
const match = line.match(/^(\S+) on (\S+) \((.*?)\)/);
|
|
3794
|
-
if (!match) continue;
|
|
3795
|
-
const point = match[2];
|
|
3796
|
-
const opts = match[3];
|
|
3797
|
-
if (opts.includes("fuse") && vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
|
|
3798
|
-
best = { point };
|
|
3799
|
-
}
|
|
3800
|
-
}
|
|
3801
|
-
if (best) return best.point;
|
|
3802
|
-
}
|
|
3803
|
-
} catch {
|
|
3804
|
-
}
|
|
3805
|
-
return null;
|
|
3806
|
-
}
|
|
3807
4066
|
function checkS3MountPerf(resolvedPath) {
|
|
3808
4067
|
if (resolvedPath === void 0) {
|
|
3809
4068
|
return check("pass", "s3_mount_perf", "S3 mount performance", "No vault path \u2014 check skipped");
|
|
3810
4069
|
}
|
|
3811
|
-
const
|
|
3812
|
-
if (!
|
|
4070
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4071
|
+
if (!fuse) {
|
|
3813
4072
|
return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
|
|
3814
4073
|
}
|
|
3815
|
-
const
|
|
3816
|
-
|
|
4074
|
+
const mountPoint = fuse.mountPoint;
|
|
4075
|
+
const conceptsDir = join24(resolvedPath, "concepts");
|
|
4076
|
+
if (!existsSync7(conceptsDir)) {
|
|
3817
4077
|
return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
|
|
3818
4078
|
}
|
|
3819
4079
|
const start = Date.now();
|
|
3820
4080
|
let timedOut = false;
|
|
3821
4081
|
try {
|
|
3822
|
-
|
|
4082
|
+
execSync2(`rg -l "." "${conceptsDir}"`, {
|
|
3823
4083
|
timeout: 5e3,
|
|
3824
4084
|
encoding: "utf8",
|
|
3825
4085
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3852,6 +4112,170 @@ function checkS3MountPerf(resolvedPath) {
|
|
|
3852
4112
|
`S3 FUSE mount, cache warm (rg scan: ${elapsed.toFixed(3)}s)`
|
|
3853
4113
|
);
|
|
3854
4114
|
}
|
|
4115
|
+
function checkRcloneFlagAudit(resolvedPath) {
|
|
4116
|
+
if (!resolvedPath) {
|
|
4117
|
+
return check("pass", "rclone_flags", "rclone VFS flags", "No vault path \u2014 check skipped");
|
|
4118
|
+
}
|
|
4119
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4120
|
+
if (!fuse) {
|
|
4121
|
+
return check("pass", "rclone_flags", "rclone VFS flags", "local disk \u2014 check skipped");
|
|
4122
|
+
}
|
|
4123
|
+
const pid = findRcloneMountPid();
|
|
4124
|
+
if (pid === null) {
|
|
4125
|
+
return check("warn", "rclone_flags", "rclone VFS flags", `S3 FUSE mount (${fuse.mountPoint}) but no rclone process found \u2014 cannot audit flags`);
|
|
4126
|
+
}
|
|
4127
|
+
const flags = parseRcloneFlags(pid);
|
|
4128
|
+
if (flags.size === 0) {
|
|
4129
|
+
return check("warn", "rclone_flags", "rclone VFS flags", `rclone PID ${pid} found but could not parse flags`);
|
|
4130
|
+
}
|
|
4131
|
+
const warnings = [];
|
|
4132
|
+
for (const [flag, threshold] of Object.entries(FLAG_THRESHOLDS)) {
|
|
4133
|
+
const raw = flags.get(flag);
|
|
4134
|
+
if (raw === void 0) {
|
|
4135
|
+
warnings.push(`${flag} not set (default may be unsafe)`);
|
|
4136
|
+
continue;
|
|
4137
|
+
}
|
|
4138
|
+
const value = parseFloat(raw);
|
|
4139
|
+
if (isNaN(value)) continue;
|
|
4140
|
+
let inSeconds = value;
|
|
4141
|
+
if (raw.endsWith("h")) inSeconds = value * 3600;
|
|
4142
|
+
else if (raw.endsWith("m")) inSeconds = value * 60;
|
|
4143
|
+
const thresholdSec = threshold.unit === "h" ? threshold.min * 3600 : threshold.unit === "m" ? threshold.min * 60 : threshold.min;
|
|
4144
|
+
if (inSeconds < thresholdSec) {
|
|
4145
|
+
warnings.push(`${flag}=${raw} (recommended \u2265${threshold.min}${threshold.unit})`);
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
const cacheMode = flags.get("--vfs-cache-mode");
|
|
4149
|
+
if (!cacheMode) {
|
|
4150
|
+
warnings.push("--vfs-cache-mode not set (recommended: full)");
|
|
4151
|
+
} else if (cacheMode !== "full") {
|
|
4152
|
+
warnings.push(`--vfs-cache-mode=${cacheMode} (recommended: full)`);
|
|
4153
|
+
}
|
|
4154
|
+
if (!flags.has("--log-file")) {
|
|
4155
|
+
warnings.push("--log-file not set \u2014 no rclone error log configured");
|
|
4156
|
+
}
|
|
4157
|
+
if (warnings.length > 0) {
|
|
4158
|
+
return check("warn", "rclone_flags", "rclone VFS flags", warnings.join("; "));
|
|
4159
|
+
}
|
|
4160
|
+
return check("pass", "rclone_flags", "rclone VFS flags", `PID ${pid}: all critical flags at safe values`);
|
|
4161
|
+
}
|
|
4162
|
+
function checkRcloneVersion(resolvedPath) {
|
|
4163
|
+
if (!resolvedPath) {
|
|
4164
|
+
return check("pass", "rclone_version", "rclone version", "No vault path \u2014 check skipped");
|
|
4165
|
+
}
|
|
4166
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4167
|
+
if (!fuse) {
|
|
4168
|
+
return check("pass", "rclone_version", "rclone version", "local disk \u2014 check skipped");
|
|
4169
|
+
}
|
|
4170
|
+
const ver = getRcloneVersion();
|
|
4171
|
+
if (!ver) {
|
|
4172
|
+
return check("warn", "rclone_version", "rclone version", "rclone not found on PATH \u2014 cannot verify version");
|
|
4173
|
+
}
|
|
4174
|
+
const min = MIN_RCLONE_VERSION;
|
|
4175
|
+
const tooOld = ver.major < min.major || ver.major === min.major && ver.minor < min.minor || ver.major === min.major && ver.minor === min.minor && ver.patch < min.patch;
|
|
4176
|
+
if (tooOld) {
|
|
4177
|
+
return check(
|
|
4178
|
+
"warn",
|
|
4179
|
+
"rclone_version",
|
|
4180
|
+
"rclone version",
|
|
4181
|
+
`${ver.raw} \u2014 upgrade to \u2265v${min.major}.${min.minor}.${min.patch} for --vfs-write-wait support (current version may silently ignore this flag)`
|
|
4182
|
+
);
|
|
4183
|
+
}
|
|
4184
|
+
return check("pass", "rclone_version", "rclone version", ver.raw);
|
|
4185
|
+
}
|
|
4186
|
+
function checkWriteTest(resolvedPath) {
|
|
4187
|
+
if (!resolvedPath) {
|
|
4188
|
+
return check("pass", "s3_write_test", "S3 write test", "No vault path \u2014 check skipped");
|
|
4189
|
+
}
|
|
4190
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4191
|
+
if (!fuse) {
|
|
4192
|
+
return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
|
|
4193
|
+
}
|
|
4194
|
+
const conceptsDir = join24(resolvedPath, "concepts");
|
|
4195
|
+
if (!existsSync7(conceptsDir)) {
|
|
4196
|
+
return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
|
|
4197
|
+
}
|
|
4198
|
+
const result = writeTest(conceptsDir);
|
|
4199
|
+
if (result.success) {
|
|
4200
|
+
const totalMs = result.writeMs + result.readMs;
|
|
4201
|
+
if (totalMs > 3e3) {
|
|
4202
|
+
return check(
|
|
4203
|
+
"warn",
|
|
4204
|
+
"s3_write_test",
|
|
4205
|
+
"S3 write test",
|
|
4206
|
+
`write+read ${totalMs}ms (write ${result.writeMs}ms, read ${result.readMs}ms, ${result.size}B) \u2014 S3 mount is slow`
|
|
4207
|
+
);
|
|
4208
|
+
}
|
|
4209
|
+
return check(
|
|
4210
|
+
"pass",
|
|
4211
|
+
"s3_write_test",
|
|
4212
|
+
"S3 write test",
|
|
4213
|
+
`write+read ${totalMs}ms (write ${result.writeMs}ms, read ${result.readMs}ms)`
|
|
4214
|
+
);
|
|
4215
|
+
}
|
|
4216
|
+
return check(
|
|
4217
|
+
"warn",
|
|
4218
|
+
"s3_write_test",
|
|
4219
|
+
"S3 write test",
|
|
4220
|
+
`${result.error} \u2014 S3 mount may have a stale FUSE handle or write-back failure`
|
|
4221
|
+
);
|
|
4222
|
+
}
|
|
4223
|
+
function checkVfsCacheHealth(resolvedPath) {
|
|
4224
|
+
if (!resolvedPath) {
|
|
4225
|
+
return check("pass", "vfs_cache_health", "VFS cache health", "No vault path \u2014 check skipped");
|
|
4226
|
+
}
|
|
4227
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4228
|
+
if (!fuse) {
|
|
4229
|
+
return check("pass", "vfs_cache_health", "VFS cache health", "local disk \u2014 check skipped");
|
|
4230
|
+
}
|
|
4231
|
+
const pid = findRcloneMountPid();
|
|
4232
|
+
if (pid === null) {
|
|
4233
|
+
return check("warn", "vfs_cache_health", "VFS cache health", "no rclone process found \u2014 cannot query VFS stats");
|
|
4234
|
+
}
|
|
4235
|
+
const flags = parseRcloneFlags(pid);
|
|
4236
|
+
const rcAddr = flags.get("--rc-addr") || "127.0.0.1:5572";
|
|
4237
|
+
if (!flags.has("--rc")) {
|
|
4238
|
+
return check(
|
|
4239
|
+
"info",
|
|
4240
|
+
"vfs_cache_health",
|
|
4241
|
+
"VFS cache health",
|
|
4242
|
+
`rclone RC not enabled \u2014 add --rc --rc-addr ${rcAddr} to enable cache health monitoring`
|
|
4243
|
+
);
|
|
4244
|
+
}
|
|
4245
|
+
const args = getRcloneArgs(pid);
|
|
4246
|
+
const fs = extractRcloneFs(args) || "unknown:";
|
|
4247
|
+
const stats = queryRcloneRC(rcAddr, fs || "unknown:");
|
|
4248
|
+
if (!stats) {
|
|
4249
|
+
return check(
|
|
4250
|
+
"warn",
|
|
4251
|
+
"vfs_cache_health",
|
|
4252
|
+
"VFS cache health",
|
|
4253
|
+
`RC endpoint ${rcAddr} unreachable \u2014 is rclone --rc enabled?`
|
|
4254
|
+
);
|
|
4255
|
+
}
|
|
4256
|
+
if (stats.error) {
|
|
4257
|
+
return check("warn", "vfs_cache_health", "VFS cache health", stats.error);
|
|
4258
|
+
}
|
|
4259
|
+
const issues = [];
|
|
4260
|
+
if (stats.uploadsInProgress > 0) issues.push(`${stats.uploadsInProgress} upload(s) in progress`);
|
|
4261
|
+
if (stats.uploadsQueued > 10) issues.push(`${stats.uploadsQueued} upload(s) queued (backlog)`);
|
|
4262
|
+
if (stats.erroredFiles > 0) issues.push(`${stats.erroredFiles} errored file(s)`);
|
|
4263
|
+
if (stats.outOfSpace) issues.push("cache disk full");
|
|
4264
|
+
if (issues.length > 0) {
|
|
4265
|
+
return check(
|
|
4266
|
+
"warn",
|
|
4267
|
+
"vfs_cache_health",
|
|
4268
|
+
"VFS cache health",
|
|
4269
|
+
`${stats.files} files, ${stats.bytesUsed} bytes \u2014 ${issues.join("; ")}`
|
|
4270
|
+
);
|
|
4271
|
+
}
|
|
4272
|
+
return check(
|
|
4273
|
+
"pass",
|
|
4274
|
+
"vfs_cache_health",
|
|
4275
|
+
"VFS cache health",
|
|
4276
|
+
`${stats.files} files, ${(stats.bytesUsed / 1024 / 1024).toFixed(1)}MB \u2014 clean (0 errored, 0 pending)`
|
|
4277
|
+
);
|
|
4278
|
+
}
|
|
3855
4279
|
function findSkillMd(dir) {
|
|
3856
4280
|
const results = [];
|
|
3857
4281
|
let entries;
|
|
@@ -3862,9 +4286,9 @@ function findSkillMd(dir) {
|
|
|
3862
4286
|
}
|
|
3863
4287
|
for (const entry of entries) {
|
|
3864
4288
|
if (entry.isFile() && entry.name === "SKILL.md") {
|
|
3865
|
-
results.push(
|
|
4289
|
+
results.push(join24(dir, entry.name));
|
|
3866
4290
|
} else if (entry.isDirectory()) {
|
|
3867
|
-
results.push(...findSkillMd(
|
|
4291
|
+
results.push(...findSkillMd(join24(dir, entry.name)));
|
|
3868
4292
|
}
|
|
3869
4293
|
}
|
|
3870
4294
|
return results;
|
|
@@ -3878,7 +4302,7 @@ function findSkillNames(dir) {
|
|
|
3878
4302
|
return results;
|
|
3879
4303
|
}
|
|
3880
4304
|
for (const entry of entries) {
|
|
3881
|
-
if (entry.isDirectory() &&
|
|
4305
|
+
if (entry.isDirectory() && existsSync7(join24(dir, entry.name, "SKILL.md"))) {
|
|
3882
4306
|
results.push(entry.name);
|
|
3883
4307
|
}
|
|
3884
4308
|
}
|
|
@@ -3905,6 +4329,10 @@ async function runDoctor(input) {
|
|
|
3905
4329
|
checks.push(checkSyncLastPush(resolvedPath));
|
|
3906
4330
|
checks.push(checkDotStoreClean(resolvedPath));
|
|
3907
4331
|
checks.push(checkS3MountPerf(resolvedPath));
|
|
4332
|
+
checks.push(checkRcloneFlagAudit(resolvedPath));
|
|
4333
|
+
checks.push(checkRcloneVersion(resolvedPath));
|
|
4334
|
+
checks.push(checkWriteTest(resolvedPath));
|
|
4335
|
+
checks.push(checkVfsCacheHealth(resolvedPath));
|
|
3908
4336
|
checks.push(checkSkillsInstalled(input.home, input.cwd));
|
|
3909
4337
|
checks.push(checkDuplicateSkills(input.home));
|
|
3910
4338
|
checks.push(checkNpmUpdate(input.home, input.currentVersion));
|
|
@@ -3932,8 +4360,8 @@ async function runDoctor(input) {
|
|
|
3932
4360
|
}
|
|
3933
4361
|
|
|
3934
4362
|
// src/commands/archive.ts
|
|
3935
|
-
import { rename as
|
|
3936
|
-
import { join as
|
|
4363
|
+
import { rename as rename5, mkdir as mkdir8, readFile as readFile18, writeFile as writeFile9 } from "fs/promises";
|
|
4364
|
+
import { join as join25, dirname as dirname9 } from "path";
|
|
3937
4365
|
async function runArchive(input) {
|
|
3938
4366
|
const scan = await scanVault(input.vault);
|
|
3939
4367
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -3949,13 +4377,13 @@ async function runArchive(input) {
|
|
|
3949
4377
|
}
|
|
3950
4378
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
3951
4379
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
3952
|
-
const archivePath =
|
|
3953
|
-
await mkdir8(
|
|
4380
|
+
const archivePath = join25("_archive", relPath).replace(/\\/g, "/");
|
|
4381
|
+
await mkdir8(dirname9(join25(input.vault, archivePath)), { recursive: true });
|
|
3954
4382
|
let indexUpdated = false;
|
|
3955
4383
|
if (!isRaw) {
|
|
3956
|
-
const indexPath =
|
|
4384
|
+
const indexPath = join25(input.vault, "index.md");
|
|
3957
4385
|
try {
|
|
3958
|
-
const idx = await
|
|
4386
|
+
const idx = await readFile18(indexPath, "utf8");
|
|
3959
4387
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
3960
4388
|
const originalLines = idx.split("\n");
|
|
3961
4389
|
const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
|
|
@@ -3967,7 +4395,7 @@ async function runArchive(input) {
|
|
|
3967
4395
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
3968
4396
|
}
|
|
3969
4397
|
}
|
|
3970
|
-
await
|
|
4398
|
+
await rename5(join25(input.vault, relPath), join25(input.vault, archivePath));
|
|
3971
4399
|
appendLastOp(input.vault, {
|
|
3972
4400
|
operation: "archive",
|
|
3973
4401
|
summary: `moved ${relPath} to ${archivePath}`,
|
|
@@ -3979,7 +4407,6 @@ async function runArchive(input) {
|
|
|
3979
4407
|
|
|
3980
4408
|
// src/commands/drift.ts
|
|
3981
4409
|
import { createHash as createHash3 } from "crypto";
|
|
3982
|
-
import { writeFile as writeFile10 } from "fs/promises";
|
|
3983
4410
|
|
|
3984
4411
|
// src/utils/fetch.ts
|
|
3985
4412
|
async function controlledFetch(url, opts) {
|
|
@@ -4066,7 +4493,7 @@ async function runDrift(input) {
|
|
|
4066
4493
|
${newFm}
|
|
4067
4494
|
---
|
|
4068
4495
|
${body}`;
|
|
4069
|
-
await
|
|
4496
|
+
await safeWritePage(raw.absPath, newText);
|
|
4070
4497
|
results.push({
|
|
4071
4498
|
raw_path: raw.relPath,
|
|
4072
4499
|
source_url: sourceUrl,
|
|
@@ -4109,7 +4536,6 @@ ${body}`;
|
|
|
4109
4536
|
}
|
|
4110
4537
|
|
|
4111
4538
|
// src/commands/migrate-citations.ts
|
|
4112
|
-
import { writeFile as writeFile11 } from "fs/promises";
|
|
4113
4539
|
var MARKER_RE2 = /\^\[(raw\/[^\]]+)\]/g;
|
|
4114
4540
|
function moveMarkersToParagraphEnd(body) {
|
|
4115
4541
|
const lines = body.split("\n");
|
|
@@ -4232,7 +4658,11 @@ ${migratedBody}${newFooter}`;
|
|
|
4232
4658
|
continue;
|
|
4233
4659
|
}
|
|
4234
4660
|
if (!input.dryRun) {
|
|
4235
|
-
await
|
|
4661
|
+
const w = await safeWritePage(page.absPath, newText);
|
|
4662
|
+
if (!w.ok) {
|
|
4663
|
+
skipped.push(page.relPath);
|
|
4664
|
+
continue;
|
|
4665
|
+
}
|
|
4236
4666
|
}
|
|
4237
4667
|
migrated.push(page.relPath);
|
|
4238
4668
|
}
|
|
@@ -4262,7 +4692,6 @@ ${migratedBody}${newFooter}`;
|
|
|
4262
4692
|
}
|
|
4263
4693
|
|
|
4264
4694
|
// src/commands/frontmatter-fix.ts
|
|
4265
|
-
import { writeFile as writeFile12 } from "fs/promises";
|
|
4266
4695
|
function isoToday() {
|
|
4267
4696
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4268
4697
|
}
|
|
@@ -4304,7 +4733,11 @@ ${newBody}`;
|
|
|
4304
4733
|
continue;
|
|
4305
4734
|
}
|
|
4306
4735
|
if (!input.dryRun) {
|
|
4307
|
-
await
|
|
4736
|
+
const w = await safeWritePage(page.absPath, newText);
|
|
4737
|
+
if (!w.ok) {
|
|
4738
|
+
skipped.push(page.relPath);
|
|
4739
|
+
continue;
|
|
4740
|
+
}
|
|
4308
4741
|
}
|
|
4309
4742
|
fixed.push(page.relPath);
|
|
4310
4743
|
}
|
|
@@ -4335,16 +4768,16 @@ ${newBody}`;
|
|
|
4335
4768
|
}
|
|
4336
4769
|
|
|
4337
4770
|
// src/commands/update.ts
|
|
4338
|
-
import { execSync as
|
|
4771
|
+
import { execSync as execSync3 } from "child_process";
|
|
4339
4772
|
import { readFileSync as readFileSync7 } from "fs";
|
|
4340
|
-
import { join as
|
|
4773
|
+
import { join as join26 } from "path";
|
|
4341
4774
|
function resolveGlobalSkillsRoot() {
|
|
4342
4775
|
try {
|
|
4343
|
-
const globalRoot =
|
|
4776
|
+
const globalRoot = execSync3("npm root -g", {
|
|
4344
4777
|
encoding: "utf8",
|
|
4345
4778
|
timeout: 5e3
|
|
4346
4779
|
}).trim();
|
|
4347
|
-
return
|
|
4780
|
+
return join26(globalRoot, "skillwiki", "skills");
|
|
4348
4781
|
} catch {
|
|
4349
4782
|
return null;
|
|
4350
4783
|
}
|
|
@@ -4370,10 +4803,10 @@ async function runUpdate(input) {
|
|
|
4370
4803
|
);
|
|
4371
4804
|
const currentVersion = pkg2.version;
|
|
4372
4805
|
const tag = input.distTag ?? "latest";
|
|
4373
|
-
const target =
|
|
4806
|
+
const target = join26(input.home, ".claude", "skills");
|
|
4374
4807
|
let latest;
|
|
4375
4808
|
try {
|
|
4376
|
-
latest =
|
|
4809
|
+
latest = execSync3(`npm view skillwiki@${tag} version`, {
|
|
4377
4810
|
encoding: "utf8",
|
|
4378
4811
|
timeout: 15e3
|
|
4379
4812
|
}).trim();
|
|
@@ -4403,7 +4836,7 @@ async function runUpdate(input) {
|
|
|
4403
4836
|
};
|
|
4404
4837
|
}
|
|
4405
4838
|
try {
|
|
4406
|
-
|
|
4839
|
+
execSync3(`npm install -g skillwiki@${tag}`, {
|
|
4407
4840
|
stdio: "pipe",
|
|
4408
4841
|
timeout: 6e4
|
|
4409
4842
|
});
|
|
@@ -4439,17 +4872,17 @@ async function runUpdate(input) {
|
|
|
4439
4872
|
}
|
|
4440
4873
|
|
|
4441
4874
|
// src/commands/self-update.ts
|
|
4442
|
-
import { execSync as
|
|
4443
|
-
import { existsSync as
|
|
4444
|
-
import { join as
|
|
4875
|
+
import { execSync as execSync4 } from "child_process";
|
|
4876
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
4877
|
+
import { join as join27 } from "path";
|
|
4445
4878
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
4446
4879
|
async function runSelfUpdate(input) {
|
|
4447
4880
|
const currentVersion = JSON.parse(
|
|
4448
4881
|
readFileSync8(new URL("../../package.json", import.meta.url), "utf8")
|
|
4449
4882
|
).version;
|
|
4450
4883
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
4451
|
-
const localPkgPath =
|
|
4452
|
-
const hasLocalSource =
|
|
4884
|
+
const localPkgPath = join27(sourceRoot, "packages", "cli", "package.json");
|
|
4885
|
+
const hasLocalSource = existsSync8(localPkgPath);
|
|
4453
4886
|
if (input.check) {
|
|
4454
4887
|
let availableVersion = null;
|
|
4455
4888
|
let source;
|
|
@@ -4463,7 +4896,7 @@ async function runSelfUpdate(input) {
|
|
|
4463
4896
|
} else {
|
|
4464
4897
|
source = "npm";
|
|
4465
4898
|
try {
|
|
4466
|
-
availableVersion =
|
|
4899
|
+
availableVersion = execSync4("npm view skillwiki@latest version", {
|
|
4467
4900
|
encoding: "utf8",
|
|
4468
4901
|
timeout: 15e3
|
|
4469
4902
|
}).trim();
|
|
@@ -4489,7 +4922,7 @@ async function runSelfUpdate(input) {
|
|
|
4489
4922
|
}
|
|
4490
4923
|
if (hasLocalSource) {
|
|
4491
4924
|
try {
|
|
4492
|
-
|
|
4925
|
+
execSync4("npm run build -w packages/cli", {
|
|
4493
4926
|
cwd: sourceRoot,
|
|
4494
4927
|
stdio: "pipe",
|
|
4495
4928
|
timeout: 6e4
|
|
@@ -4501,7 +4934,7 @@ async function runSelfUpdate(input) {
|
|
|
4501
4934
|
};
|
|
4502
4935
|
}
|
|
4503
4936
|
try {
|
|
4504
|
-
|
|
4937
|
+
execSync4("npm link ./packages/cli", {
|
|
4505
4938
|
cwd: sourceRoot,
|
|
4506
4939
|
stdio: "pipe",
|
|
4507
4940
|
timeout: 3e4
|
|
@@ -4533,7 +4966,7 @@ async function runSelfUpdate(input) {
|
|
|
4533
4966
|
}
|
|
4534
4967
|
let latestVersion;
|
|
4535
4968
|
try {
|
|
4536
|
-
latestVersion =
|
|
4969
|
+
latestVersion = execSync4("npm view skillwiki@latest version", {
|
|
4537
4970
|
encoding: "utf8",
|
|
4538
4971
|
timeout: 15e3
|
|
4539
4972
|
}).trim();
|
|
@@ -4556,7 +4989,7 @@ async function runSelfUpdate(input) {
|
|
|
4556
4989
|
};
|
|
4557
4990
|
}
|
|
4558
4991
|
try {
|
|
4559
|
-
|
|
4992
|
+
execSync4("npm install -g skillwiki@latest", {
|
|
4560
4993
|
stdio: "pipe",
|
|
4561
4994
|
timeout: 6e4
|
|
4562
4995
|
});
|
|
@@ -4580,10 +5013,10 @@ async function runSelfUpdate(input) {
|
|
|
4580
5013
|
}
|
|
4581
5014
|
|
|
4582
5015
|
// src/commands/transcripts.ts
|
|
4583
|
-
import { readdir as readdir5, stat as stat6, readFile as
|
|
4584
|
-
import { join as
|
|
5016
|
+
import { readdir as readdir5, stat as stat6, readFile as readFile19 } from "fs/promises";
|
|
5017
|
+
import { join as join28 } from "path";
|
|
4585
5018
|
async function runTranscripts(input) {
|
|
4586
|
-
const dir =
|
|
5019
|
+
const dir = join28(input.vault, "raw", "transcripts");
|
|
4587
5020
|
let entries;
|
|
4588
5021
|
try {
|
|
4589
5022
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -4593,8 +5026,8 @@ async function runTranscripts(input) {
|
|
|
4593
5026
|
const transcripts = [];
|
|
4594
5027
|
for (const entry of entries) {
|
|
4595
5028
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4596
|
-
const filePath =
|
|
4597
|
-
const content = await
|
|
5029
|
+
const filePath = join28(dir, entry.name);
|
|
5030
|
+
const content = await readFile19(filePath, "utf8");
|
|
4598
5031
|
const fm = extractFrontmatter(content);
|
|
4599
5032
|
if (!fm.ok) continue;
|
|
4600
5033
|
const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
|
|
@@ -4611,12 +5044,12 @@ async function runTranscripts(input) {
|
|
|
4611
5044
|
}
|
|
4612
5045
|
|
|
4613
5046
|
// src/commands/project-index.ts
|
|
4614
|
-
import { readdir as readdir6, readFile as
|
|
4615
|
-
import { join as
|
|
5047
|
+
import { readdir as readdir6, readFile as readFile20, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
|
|
5048
|
+
import { join as join29, dirname as dirname10 } from "path";
|
|
4616
5049
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
4617
5050
|
async function runProjectIndex(input) {
|
|
4618
5051
|
const slug = input.slug;
|
|
4619
|
-
const projectDir =
|
|
5052
|
+
const projectDir = join29(input.vault, "projects", slug);
|
|
4620
5053
|
try {
|
|
4621
5054
|
await readdir6(projectDir);
|
|
4622
5055
|
} catch {
|
|
@@ -4627,15 +5060,15 @@ async function runProjectIndex(input) {
|
|
|
4627
5060
|
}
|
|
4628
5061
|
const wikilinkPattern = `[[${slug}]]`;
|
|
4629
5062
|
const entries = [];
|
|
4630
|
-
const compoundDir =
|
|
5063
|
+
const compoundDir = join29(input.vault, "projects", slug, "compound");
|
|
4631
5064
|
try {
|
|
4632
5065
|
const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
|
|
4633
5066
|
for (const entry of compoundFiles) {
|
|
4634
5067
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4635
|
-
const filePath =
|
|
5068
|
+
const filePath = join29(compoundDir, entry.name);
|
|
4636
5069
|
let text;
|
|
4637
5070
|
try {
|
|
4638
|
-
text = await
|
|
5071
|
+
text = await readFile20(filePath, "utf8");
|
|
4639
5072
|
} catch {
|
|
4640
5073
|
continue;
|
|
4641
5074
|
}
|
|
@@ -4652,16 +5085,16 @@ async function runProjectIndex(input) {
|
|
|
4652
5085
|
for (const dir of LAYER2_DIRS) {
|
|
4653
5086
|
let files;
|
|
4654
5087
|
try {
|
|
4655
|
-
files = await readdir6(
|
|
5088
|
+
files = await readdir6(join29(input.vault, dir), { withFileTypes: true });
|
|
4656
5089
|
} catch {
|
|
4657
5090
|
continue;
|
|
4658
5091
|
}
|
|
4659
5092
|
for (const entry of files) {
|
|
4660
5093
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4661
|
-
const filePath =
|
|
5094
|
+
const filePath = join29(input.vault, dir, entry.name);
|
|
4662
5095
|
let text;
|
|
4663
5096
|
try {
|
|
4664
|
-
text = await
|
|
5097
|
+
text = await readFile20(filePath, "utf8");
|
|
4665
5098
|
} catch {
|
|
4666
5099
|
continue;
|
|
4667
5100
|
}
|
|
@@ -4682,11 +5115,11 @@ async function runProjectIndex(input) {
|
|
|
4682
5115
|
const tb = typeOrder[b.type] ?? 99;
|
|
4683
5116
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
4684
5117
|
});
|
|
4685
|
-
const indexPath =
|
|
5118
|
+
const indexPath = join29(projectDir, "knowledge.md");
|
|
4686
5119
|
let existing = false;
|
|
4687
5120
|
let stale = false;
|
|
4688
5121
|
try {
|
|
4689
|
-
const existingText = await
|
|
5122
|
+
const existingText = await readFile20(indexPath, "utf8");
|
|
4690
5123
|
existing = true;
|
|
4691
5124
|
const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
|
|
4692
5125
|
const existingPages = new Set(existingEntries.map((l) => {
|
|
@@ -4726,8 +5159,8 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
|
|
|
4726
5159
|
}
|
|
4727
5160
|
if (input.apply) {
|
|
4728
5161
|
try {
|
|
4729
|
-
await mkdir9(
|
|
4730
|
-
await
|
|
5162
|
+
await mkdir9(dirname10(indexPath), { recursive: true });
|
|
5163
|
+
await writeFile10(indexPath, body, "utf8");
|
|
4731
5164
|
} catch (e) {
|
|
4732
5165
|
return {
|
|
4733
5166
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -4755,10 +5188,10 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
4755
5188
|
}
|
|
4756
5189
|
|
|
4757
5190
|
// src/commands/compound.ts
|
|
4758
|
-
import { writeFile as
|
|
4759
|
-
import { join as
|
|
4760
|
-
import { existsSync as
|
|
4761
|
-
import { readFile as
|
|
5191
|
+
import { writeFile as writeFile11, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
|
|
5192
|
+
import { join as join30 } from "path";
|
|
5193
|
+
import { existsSync as existsSync9 } from "fs";
|
|
5194
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
4762
5195
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
4763
5196
|
var FIELD_RE = {
|
|
4764
5197
|
improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
|
|
@@ -4856,17 +5289,17 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
4856
5289
|
};
|
|
4857
5290
|
}
|
|
4858
5291
|
async function runCompound(input) {
|
|
4859
|
-
const logPath =
|
|
5292
|
+
const logPath = join30(input.vault, "log.md");
|
|
4860
5293
|
let logText;
|
|
4861
5294
|
try {
|
|
4862
|
-
logText = await
|
|
5295
|
+
logText = await readFile21(logPath, "utf8");
|
|
4863
5296
|
} catch {
|
|
4864
5297
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
4865
5298
|
}
|
|
4866
5299
|
const entries = parseRetroEntries(logText);
|
|
4867
5300
|
const promoted = [];
|
|
4868
5301
|
const skipped = [];
|
|
4869
|
-
const compoundDir =
|
|
5302
|
+
const compoundDir = join30(input.vault, "projects", input.project, "compound");
|
|
4870
5303
|
for (const entry of entries) {
|
|
4871
5304
|
const generalizeValue = entry.generalize.trim();
|
|
4872
5305
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -4874,8 +5307,8 @@ async function runCompound(input) {
|
|
|
4874
5307
|
continue;
|
|
4875
5308
|
}
|
|
4876
5309
|
const slug = slugify(entry.cycleName);
|
|
4877
|
-
const compoundPath =
|
|
4878
|
-
if (
|
|
5310
|
+
const compoundPath = join30(compoundDir, `${slug}.md`);
|
|
5311
|
+
if (existsSync9(compoundPath)) {
|
|
4879
5312
|
skipped.push(entry.date);
|
|
4880
5313
|
continue;
|
|
4881
5314
|
}
|
|
@@ -4913,10 +5346,10 @@ async function runCompound(input) {
|
|
|
4913
5346
|
].join("\n");
|
|
4914
5347
|
const content = frontmatter + "\n" + body;
|
|
4915
5348
|
if (!input.dryRun) {
|
|
4916
|
-
if (!
|
|
5349
|
+
if (!existsSync9(compoundDir)) {
|
|
4917
5350
|
await mkdir10(compoundDir, { recursive: true });
|
|
4918
5351
|
}
|
|
4919
|
-
await
|
|
5352
|
+
await writeFile11(compoundPath, content, "utf8");
|
|
4920
5353
|
}
|
|
4921
5354
|
promoted.push(`${slug}.md`);
|
|
4922
5355
|
}
|
|
@@ -4935,23 +5368,23 @@ async function runCompound(input) {
|
|
|
4935
5368
|
};
|
|
4936
5369
|
}
|
|
4937
5370
|
async function runCompoundDelete(input) {
|
|
4938
|
-
const projectDir =
|
|
4939
|
-
if (!
|
|
5371
|
+
const projectDir = join30(input.vault, "projects", input.project);
|
|
5372
|
+
if (!existsSync9(projectDir)) {
|
|
4940
5373
|
return {
|
|
4941
5374
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
4942
5375
|
result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
|
|
4943
5376
|
};
|
|
4944
5377
|
}
|
|
4945
5378
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
4946
|
-
const compoundPath =
|
|
4947
|
-
if (!
|
|
5379
|
+
const compoundPath = join30(projectDir, "compound", `${entryName}.md`);
|
|
5380
|
+
if (!existsSync9(compoundPath)) {
|
|
4948
5381
|
return {
|
|
4949
5382
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
4950
5383
|
result: err("FILE_NOT_FOUND", { path: compoundPath })
|
|
4951
5384
|
};
|
|
4952
5385
|
}
|
|
4953
5386
|
try {
|
|
4954
|
-
await
|
|
5387
|
+
await unlink3(compoundPath);
|
|
4955
5388
|
} catch (e) {
|
|
4956
5389
|
return {
|
|
4957
5390
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -4977,8 +5410,8 @@ knowledge.md regenerated`
|
|
|
4977
5410
|
};
|
|
4978
5411
|
}
|
|
4979
5412
|
async function runCompoundList(input) {
|
|
4980
|
-
const compoundDir =
|
|
4981
|
-
if (!
|
|
5413
|
+
const compoundDir = join30(input.vault, "projects", input.project, "compound");
|
|
5414
|
+
if (!existsSync9(compoundDir)) {
|
|
4982
5415
|
return {
|
|
4983
5416
|
exitCode: ExitCode.OK,
|
|
4984
5417
|
result: ok({
|
|
@@ -5008,10 +5441,10 @@ could not read compound directory`
|
|
|
5008
5441
|
const entries = [];
|
|
5009
5442
|
for (const dirent of dirents) {
|
|
5010
5443
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
5011
|
-
const filePath =
|
|
5444
|
+
const filePath = join30(compoundDir, dirent.name);
|
|
5012
5445
|
let text;
|
|
5013
5446
|
try {
|
|
5014
|
-
text = await
|
|
5447
|
+
text = await readFile21(filePath, "utf8");
|
|
5015
5448
|
} catch {
|
|
5016
5449
|
continue;
|
|
5017
5450
|
}
|
|
@@ -5040,9 +5473,9 @@ no compound entries found`;
|
|
|
5040
5473
|
}
|
|
5041
5474
|
|
|
5042
5475
|
// src/commands/observe.ts
|
|
5043
|
-
import { mkdir as mkdir11, writeFile as
|
|
5044
|
-
import { existsSync as
|
|
5045
|
-
import { join as
|
|
5476
|
+
import { mkdir as mkdir11, writeFile as writeFile12 } from "fs/promises";
|
|
5477
|
+
import { existsSync as existsSync10, statSync as statSync3 } from "fs";
|
|
5478
|
+
import { join as join31 } from "path";
|
|
5046
5479
|
import { createHash as createHash4 } from "crypto";
|
|
5047
5480
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
5048
5481
|
function slugify2(text) {
|
|
@@ -5065,13 +5498,13 @@ async function runObserve(input) {
|
|
|
5065
5498
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
5066
5499
|
};
|
|
5067
5500
|
}
|
|
5068
|
-
if (!
|
|
5501
|
+
if (!existsSync10(input.vault) || !statSync3(input.vault).isDirectory()) {
|
|
5069
5502
|
return {
|
|
5070
5503
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5071
5504
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
5072
5505
|
};
|
|
5073
5506
|
}
|
|
5074
|
-
const transcriptsDir =
|
|
5507
|
+
const transcriptsDir = join31(input.vault, "raw", "transcripts");
|
|
5075
5508
|
try {
|
|
5076
5509
|
await mkdir11(transcriptsDir, { recursive: true });
|
|
5077
5510
|
} catch {
|
|
@@ -5083,7 +5516,7 @@ async function runObserve(input) {
|
|
|
5083
5516
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5084
5517
|
const slug = slugify2(input.text);
|
|
5085
5518
|
const fileName = `${today}-observation-${slug}.md`;
|
|
5086
|
-
const filePath =
|
|
5519
|
+
const filePath = join31(transcriptsDir, fileName);
|
|
5087
5520
|
const body = `
|
|
5088
5521
|
${input.text.trim()}
|
|
5089
5522
|
`;
|
|
@@ -5101,7 +5534,7 @@ ${input.text.trim()}
|
|
|
5101
5534
|
frontmatterLines.push("---");
|
|
5102
5535
|
const content = frontmatterLines.join("\n") + body;
|
|
5103
5536
|
try {
|
|
5104
|
-
await
|
|
5537
|
+
await writeFile12(filePath, content, "utf8");
|
|
5105
5538
|
} catch (e) {
|
|
5106
5539
|
return {
|
|
5107
5540
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5123,8 +5556,8 @@ ${input.text.trim()}
|
|
|
5123
5556
|
}
|
|
5124
5557
|
|
|
5125
5558
|
// src/commands/ingest.ts
|
|
5126
|
-
import { readFile as
|
|
5127
|
-
import { join as
|
|
5559
|
+
import { readFile as readFile22, writeFile as writeFile13, mkdir as mkdir12 } from "fs/promises";
|
|
5560
|
+
import { join as join32 } from "path";
|
|
5128
5561
|
import { createHash as createHash5 } from "crypto";
|
|
5129
5562
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
5130
5563
|
var TYPE_DIR = {
|
|
@@ -5283,7 +5716,7 @@ async function runIngest(input) {
|
|
|
5283
5716
|
sourceContent = fetchResult.data.body;
|
|
5284
5717
|
} else {
|
|
5285
5718
|
try {
|
|
5286
|
-
sourceContent = await
|
|
5719
|
+
sourceContent = await readFile22(input.source, "utf8");
|
|
5287
5720
|
} catch {
|
|
5288
5721
|
return {
|
|
5289
5722
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -5298,8 +5731,8 @@ async function runIngest(input) {
|
|
|
5298
5731
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
5299
5732
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
5300
5733
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
5301
|
-
const rawAbsPath =
|
|
5302
|
-
const typedAbsPath =
|
|
5734
|
+
const rawAbsPath = join32(input.vault, rawRelPath);
|
|
5735
|
+
const typedAbsPath = join32(input.vault, typedRelPath);
|
|
5303
5736
|
const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
|
|
5304
5737
|
const typedContent = buildTypedContent(
|
|
5305
5738
|
input.title,
|
|
@@ -5362,8 +5795,8 @@ async function runIngest(input) {
|
|
|
5362
5795
|
};
|
|
5363
5796
|
}
|
|
5364
5797
|
try {
|
|
5365
|
-
await mkdir12(
|
|
5366
|
-
await
|
|
5798
|
+
await mkdir12(join32(input.vault, "raw", "articles"), { recursive: true });
|
|
5799
|
+
await writeFile13(rawAbsPath, rawContent, "utf8");
|
|
5367
5800
|
} catch (e) {
|
|
5368
5801
|
return {
|
|
5369
5802
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5371,8 +5804,8 @@ async function runIngest(input) {
|
|
|
5371
5804
|
};
|
|
5372
5805
|
}
|
|
5373
5806
|
try {
|
|
5374
|
-
await mkdir12(
|
|
5375
|
-
await
|
|
5807
|
+
await mkdir12(join32(input.vault, typedDir), { recursive: true });
|
|
5808
|
+
await writeFile13(typedAbsPath, typedContent, "utf8");
|
|
5376
5809
|
} catch (e) {
|
|
5377
5810
|
return {
|
|
5378
5811
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5403,7 +5836,6 @@ async function runIngest(input) {
|
|
|
5403
5836
|
}
|
|
5404
5837
|
|
|
5405
5838
|
// src/commands/tag-sync.ts
|
|
5406
|
-
import { writeFile as writeFile17 } from "fs/promises";
|
|
5407
5839
|
var ENUM_MIRRORS = {
|
|
5408
5840
|
provenance: ["research", "project", "mixed"],
|
|
5409
5841
|
confidence: ["high", "medium", "low"]
|
|
@@ -5518,7 +5950,11 @@ ${newFm}
|
|
|
5518
5950
|
---
|
|
5519
5951
|
${body}`;
|
|
5520
5952
|
if (!input.dryRun) {
|
|
5521
|
-
await
|
|
5953
|
+
const w = await safeWritePage(page.absPath, newText);
|
|
5954
|
+
if (!w.ok) {
|
|
5955
|
+
unchanged++;
|
|
5956
|
+
continue;
|
|
5957
|
+
}
|
|
5522
5958
|
}
|
|
5523
5959
|
synced.push(page.relPath);
|
|
5524
5960
|
}
|
|
@@ -5547,11 +5983,11 @@ ${body}`;
|
|
|
5547
5983
|
}
|
|
5548
5984
|
|
|
5549
5985
|
// src/commands/sync.ts
|
|
5550
|
-
import { existsSync as
|
|
5551
|
-
import { join as
|
|
5986
|
+
import { existsSync as existsSync11 } from "fs";
|
|
5987
|
+
import { join as join33 } from "path";
|
|
5552
5988
|
function runSyncStatus(input) {
|
|
5553
5989
|
const vault = input.vault;
|
|
5554
|
-
if (!
|
|
5990
|
+
if (!existsSync11(join33(vault, ".git"))) {
|
|
5555
5991
|
return {
|
|
5556
5992
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5557
5993
|
result: ok({
|
|
@@ -5620,7 +6056,7 @@ function runSyncStatus(input) {
|
|
|
5620
6056
|
}
|
|
5621
6057
|
async function runSyncPush(input) {
|
|
5622
6058
|
const vault = input.vault;
|
|
5623
|
-
if (!
|
|
6059
|
+
if (!existsSync11(join33(vault, ".git"))) {
|
|
5624
6060
|
return {
|
|
5625
6061
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5626
6062
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -5705,7 +6141,7 @@ async function runSyncPush(input) {
|
|
|
5705
6141
|
}
|
|
5706
6142
|
async function runSyncPull(input) {
|
|
5707
6143
|
const vault = input.vault;
|
|
5708
|
-
if (!
|
|
6144
|
+
if (!existsSync11(join33(vault, ".git"))) {
|
|
5709
6145
|
return {
|
|
5710
6146
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5711
6147
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -5780,8 +6216,8 @@ async function runSyncPull(input) {
|
|
|
5780
6216
|
}
|
|
5781
6217
|
|
|
5782
6218
|
// src/commands/backup.ts
|
|
5783
|
-
import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as
|
|
5784
|
-
import { join as
|
|
6219
|
+
import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
6220
|
+
import { join as join34, relative as relative3, dirname as dirname11 } from "path";
|
|
5785
6221
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
5786
6222
|
|
|
5787
6223
|
// src/utils/s3-client.ts
|
|
@@ -5805,7 +6241,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
|
|
|
5805
6241
|
function* walkMarkdown(dir, base) {
|
|
5806
6242
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
5807
6243
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
5808
|
-
const full =
|
|
6244
|
+
const full = join34(dir, entry.name);
|
|
5809
6245
|
if (entry.isDirectory()) {
|
|
5810
6246
|
yield* walkMarkdown(full, base);
|
|
5811
6247
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -5828,7 +6264,7 @@ async function runBackupSync(input) {
|
|
|
5828
6264
|
let failed = 0;
|
|
5829
6265
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
5830
6266
|
for (const relPath of files) {
|
|
5831
|
-
const absPath =
|
|
6267
|
+
const absPath = join34(input.vault, relPath);
|
|
5832
6268
|
const localStat = statSync4(absPath);
|
|
5833
6269
|
let needsUpload = true;
|
|
5834
6270
|
try {
|
|
@@ -5904,7 +6340,7 @@ async function runBackupRestore(input) {
|
|
|
5904
6340
|
const objects = list.Contents ?? [];
|
|
5905
6341
|
for (const obj of objects) {
|
|
5906
6342
|
if (!obj.Key) continue;
|
|
5907
|
-
const localPath =
|
|
6343
|
+
const localPath = join34(target, obj.Key);
|
|
5908
6344
|
try {
|
|
5909
6345
|
const localStat = statSync4(localPath);
|
|
5910
6346
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -5917,8 +6353,8 @@ async function runBackupRestore(input) {
|
|
|
5917
6353
|
const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
|
|
5918
6354
|
const body = await resp.Body?.transformToByteArray();
|
|
5919
6355
|
if (body) {
|
|
5920
|
-
mkdirSync3(
|
|
5921
|
-
|
|
6356
|
+
mkdirSync3(dirname11(localPath), { recursive: true });
|
|
6357
|
+
writeFileSync5(localPath, Buffer.from(body));
|
|
5922
6358
|
downloaded++;
|
|
5923
6359
|
}
|
|
5924
6360
|
} catch {
|
|
@@ -5950,11 +6386,11 @@ async function runBackupRestore(input) {
|
|
|
5950
6386
|
}
|
|
5951
6387
|
|
|
5952
6388
|
// src/commands/status.ts
|
|
5953
|
-
import { existsSync as
|
|
5954
|
-
import { readFile as
|
|
5955
|
-
import { join as
|
|
6389
|
+
import { existsSync as existsSync12, statSync as statSync5 } from "fs";
|
|
6390
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
6391
|
+
import { join as join35 } from "path";
|
|
5956
6392
|
async function runStatus(input) {
|
|
5957
|
-
if (!
|
|
6393
|
+
if (!existsSync12(input.vault)) {
|
|
5958
6394
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
5959
6395
|
}
|
|
5960
6396
|
const scan = await scanVault(input.vault);
|
|
@@ -5979,7 +6415,7 @@ async function runStatus(input) {
|
|
|
5979
6415
|
const compound = scan.data.compound.length;
|
|
5980
6416
|
let schemaVersion = "v1";
|
|
5981
6417
|
try {
|
|
5982
|
-
const schemaContent = await
|
|
6418
|
+
const schemaContent = await readFile23(join35(input.vault, "SCHEMA.md"), "utf8");
|
|
5983
6419
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
5984
6420
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
5985
6421
|
} catch {
|
|
@@ -6039,8 +6475,8 @@ async function runStatus(input) {
|
|
|
6039
6475
|
}
|
|
6040
6476
|
|
|
6041
6477
|
// src/commands/seed.ts
|
|
6042
|
-
import { mkdir as mkdir13, writeFile as
|
|
6043
|
-
import { join as
|
|
6478
|
+
import { mkdir as mkdir13, writeFile as writeFile14, stat as stat7 } from "fs/promises";
|
|
6479
|
+
import { join as join36 } from "path";
|
|
6044
6480
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6045
6481
|
var EXAMPLE_PAGES = {
|
|
6046
6482
|
"entities/example-project.md": `---
|
|
@@ -6109,30 +6545,30 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
6109
6545
|
`;
|
|
6110
6546
|
async function runSeed(input) {
|
|
6111
6547
|
try {
|
|
6112
|
-
await stat7(
|
|
6548
|
+
await stat7(join36(input.vault, "SCHEMA.md"));
|
|
6113
6549
|
} catch {
|
|
6114
6550
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
6115
6551
|
}
|
|
6116
6552
|
const created = [];
|
|
6117
6553
|
const skipped = [];
|
|
6118
6554
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
6119
|
-
const absPath =
|
|
6555
|
+
const absPath = join36(input.vault, relPath);
|
|
6120
6556
|
try {
|
|
6121
6557
|
await stat7(absPath);
|
|
6122
6558
|
skipped.push(relPath);
|
|
6123
6559
|
} catch {
|
|
6124
|
-
await mkdir13(
|
|
6125
|
-
await
|
|
6560
|
+
await mkdir13(join36(absPath, ".."), { recursive: true });
|
|
6561
|
+
await writeFile14(absPath, content, "utf8");
|
|
6126
6562
|
created.push(relPath);
|
|
6127
6563
|
}
|
|
6128
6564
|
}
|
|
6129
|
-
const rawPath =
|
|
6565
|
+
const rawPath = join36(input.vault, "raw", "articles", "example-source.md");
|
|
6130
6566
|
try {
|
|
6131
6567
|
await stat7(rawPath);
|
|
6132
6568
|
skipped.push("raw/articles/example-source.md");
|
|
6133
6569
|
} catch {
|
|
6134
|
-
await mkdir13(
|
|
6135
|
-
await
|
|
6570
|
+
await mkdir13(join36(rawPath, ".."), { recursive: true });
|
|
6571
|
+
await writeFile14(rawPath, EXAMPLE_RAW, "utf8");
|
|
6136
6572
|
created.push("raw/articles/example-source.md");
|
|
6137
6573
|
}
|
|
6138
6574
|
if (created.length > 0) {
|
|
@@ -6154,9 +6590,9 @@ async function runSeed(input) {
|
|
|
6154
6590
|
}
|
|
6155
6591
|
|
|
6156
6592
|
// src/commands/canvas.ts
|
|
6157
|
-
import { readFile as
|
|
6158
|
-
import { existsSync as
|
|
6159
|
-
import { join as
|
|
6593
|
+
import { readFile as readFile24, writeFile as writeFile15 } from "fs/promises";
|
|
6594
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6595
|
+
import { join as join37 } from "path";
|
|
6160
6596
|
var NODE_WIDTH = 240;
|
|
6161
6597
|
var NODE_HEIGHT = 60;
|
|
6162
6598
|
var COLUMN_SPACING = 400;
|
|
@@ -6234,8 +6670,8 @@ function buildCanvasEdges(adjacency) {
|
|
|
6234
6670
|
return edges;
|
|
6235
6671
|
}
|
|
6236
6672
|
async function runCanvasGenerate(input) {
|
|
6237
|
-
const graphPath = input.graphPath ??
|
|
6238
|
-
if (!
|
|
6673
|
+
const graphPath = input.graphPath ?? join37(input.vault, ".skillwiki", "graph.json");
|
|
6674
|
+
if (!existsSync13(graphPath)) {
|
|
6239
6675
|
return {
|
|
6240
6676
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
6241
6677
|
result: err("FILE_NOT_FOUND", {
|
|
@@ -6246,7 +6682,7 @@ async function runCanvasGenerate(input) {
|
|
|
6246
6682
|
}
|
|
6247
6683
|
let raw;
|
|
6248
6684
|
try {
|
|
6249
|
-
raw = await
|
|
6685
|
+
raw = await readFile24(graphPath, "utf8");
|
|
6250
6686
|
} catch (e) {
|
|
6251
6687
|
return {
|
|
6252
6688
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -6272,9 +6708,9 @@ async function runCanvasGenerate(input) {
|
|
|
6272
6708
|
const nodes = buildCanvasNodes(paths);
|
|
6273
6709
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
6274
6710
|
const canvas = { nodes, edges };
|
|
6275
|
-
const outPath =
|
|
6711
|
+
const outPath = join37(input.vault, "vault-graph.canvas");
|
|
6276
6712
|
try {
|
|
6277
|
-
await
|
|
6713
|
+
await writeFile15(outPath, JSON.stringify(canvas, null, 2));
|
|
6278
6714
|
} catch (e) {
|
|
6279
6715
|
return {
|
|
6280
6716
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -6294,8 +6730,8 @@ written: ${outPath}`
|
|
|
6294
6730
|
}
|
|
6295
6731
|
|
|
6296
6732
|
// src/commands/query.ts
|
|
6297
|
-
import { readFile as
|
|
6298
|
-
import { join as
|
|
6733
|
+
import { readFile as readFile25, stat as stat8 } from "fs/promises";
|
|
6734
|
+
import { join as join38 } from "path";
|
|
6299
6735
|
var W_KEYWORD = 2;
|
|
6300
6736
|
var W_SOURCE_OVERLAP = 4;
|
|
6301
6737
|
var W_WIKILINK = 3;
|
|
@@ -6416,7 +6852,7 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
6416
6852
|
return score;
|
|
6417
6853
|
}
|
|
6418
6854
|
async function loadOrBuildGraph(vault) {
|
|
6419
|
-
const graphPath =
|
|
6855
|
+
const graphPath = join38(vault, ".skillwiki", "graph.json");
|
|
6420
6856
|
let needsBuild = false;
|
|
6421
6857
|
try {
|
|
6422
6858
|
const fileStat = await stat8(graphPath);
|
|
@@ -6430,7 +6866,7 @@ async function loadOrBuildGraph(vault) {
|
|
|
6430
6866
|
if (buildResult.exitCode !== 0) return null;
|
|
6431
6867
|
}
|
|
6432
6868
|
try {
|
|
6433
|
-
const raw = await
|
|
6869
|
+
const raw = await readFile25(graphPath, "utf8");
|
|
6434
6870
|
return JSON.parse(raw);
|
|
6435
6871
|
} catch {
|
|
6436
6872
|
return null;
|
|
@@ -6438,14 +6874,14 @@ async function loadOrBuildGraph(vault) {
|
|
|
6438
6874
|
}
|
|
6439
6875
|
|
|
6440
6876
|
// src/utils/auto-commit.ts
|
|
6441
|
-
import { existsSync as
|
|
6442
|
-
import { join as
|
|
6877
|
+
import { existsSync as existsSync14 } from "fs";
|
|
6878
|
+
import { join as join39 } from "path";
|
|
6443
6879
|
async function postCommit(vault, exitCode) {
|
|
6444
6880
|
if (exitCode !== 0) return;
|
|
6445
6881
|
const home = process.env.HOME ?? "";
|
|
6446
6882
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
6447
6883
|
if (dotenv["AUTO_COMMIT"] === "false") return;
|
|
6448
|
-
if (!
|
|
6884
|
+
if (!existsSync14(join39(vault, ".git"))) return;
|
|
6449
6885
|
const lastOps = readLastOp(vault);
|
|
6450
6886
|
if (lastOps.length === 0) return;
|
|
6451
6887
|
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
@@ -6496,7 +6932,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
6496
6932
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
6497
6933
|
});
|
|
6498
6934
|
program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path (default: <vault>/.skillwiki/graph.json)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
6499
|
-
const out = opts.out ??
|
|
6935
|
+
const out = opts.out ?? join40(vault, ".skillwiki", "graph.json");
|
|
6500
6936
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
6501
6937
|
});
|
|
6502
6938
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|