skillwiki 0.8.0 → 0.8.1-beta.2
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
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
|
11
11
|
import { readFileSync as readFileSync12 } from "fs";
|
|
12
|
-
import { join as
|
|
12
|
+
import { join as join43 } from "path";
|
|
13
13
|
import { Command as Command2 } from "commander";
|
|
14
14
|
|
|
15
15
|
// ../shared/src/exit-codes.ts
|
|
@@ -62,7 +62,8 @@ var ExitCode = {
|
|
|
62
62
|
BACKUP_RESTORE_CONFLICTS: 45,
|
|
63
63
|
USAGE: 46,
|
|
64
64
|
BODY_TRUNCATION_GUARD: 47,
|
|
65
|
-
SYNC_LOCK_HELD: 48
|
|
65
|
+
SYNC_LOCK_HELD: 48,
|
|
66
|
+
LOG_APPEND_LOCK_HELD: 49
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
// ../shared/src/json-output.ts
|
|
@@ -2362,10 +2363,111 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
|
|
|
2362
2363
|
return { exitCode: ExitCode.OK, result: ok({ entries, threshold: input.threshold, rotated: true, rotated_to: rotatedName, humanHint: `rotated ${entries} entries to ${rotatedName}` }) };
|
|
2363
2364
|
}
|
|
2364
2365
|
|
|
2366
|
+
// src/commands/log-append.ts
|
|
2367
|
+
import { readFile as readFile13, rename as rename4, writeFile as writeFile8, stat as stat6 } from "fs/promises";
|
|
2368
|
+
import { join as join17 } from "path";
|
|
2369
|
+
|
|
2370
|
+
// src/utils/log-lock.ts
|
|
2371
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2372
|
+
import { join as join16 } from "path";
|
|
2373
|
+
function logLockPath(vault) {
|
|
2374
|
+
return join16(vault, ".skillwiki", "log-append.lock");
|
|
2375
|
+
}
|
|
2376
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2377
|
+
async function acquireLogLock(vault, opts = {}) {
|
|
2378
|
+
const retryMs = opts.retryMs ?? 2e3;
|
|
2379
|
+
const pollMs = opts.pollMs ?? 50;
|
|
2380
|
+
const staleMs = opts.staleMs ?? 1e4;
|
|
2381
|
+
const path = logLockPath(vault);
|
|
2382
|
+
const dir = join16(vault, ".skillwiki");
|
|
2383
|
+
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
2384
|
+
const deadline = Date.now() + retryMs;
|
|
2385
|
+
const content = JSON.stringify({ pid: process.pid, acquired: (/* @__PURE__ */ new Date()).toISOString() }) + "\n";
|
|
2386
|
+
for (; ; ) {
|
|
2387
|
+
try {
|
|
2388
|
+
writeFileSync2(path, content, { flag: "wx" });
|
|
2389
|
+
return { ok: true };
|
|
2390
|
+
} catch (e) {
|
|
2391
|
+
const err3 = e;
|
|
2392
|
+
if (err3.code !== "EEXIST") throw err3;
|
|
2393
|
+
}
|
|
2394
|
+
try {
|
|
2395
|
+
const age = Date.now() - statSync2(path).mtimeMs;
|
|
2396
|
+
if (age > staleMs) {
|
|
2397
|
+
unlinkSync2(path);
|
|
2398
|
+
continue;
|
|
2399
|
+
}
|
|
2400
|
+
} catch {
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
if (Date.now() >= deadline) return { ok: false };
|
|
2404
|
+
await sleep(pollMs);
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
function releaseLogLock(vault) {
|
|
2408
|
+
try {
|
|
2409
|
+
unlinkSync2(logLockPath(vault));
|
|
2410
|
+
} catch {
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// src/commands/log-append.ts
|
|
2415
|
+
var ENTRY_RE2 = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
|
|
2416
|
+
async function runLogAppend(input) {
|
|
2417
|
+
try {
|
|
2418
|
+
await stat6(join17(input.vault, "SCHEMA.md"));
|
|
2419
|
+
} catch {
|
|
2420
|
+
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
2421
|
+
}
|
|
2422
|
+
const content = (input.content ?? "").trim();
|
|
2423
|
+
if (content.length === 0) {
|
|
2424
|
+
return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--content must be a non-empty log entry" }) };
|
|
2425
|
+
}
|
|
2426
|
+
const acquired = await acquireLogLock(input.vault);
|
|
2427
|
+
if (!acquired.ok) {
|
|
2428
|
+
return { exitCode: ExitCode.LOG_APPEND_LOCK_HELD, result: err("LOG_APPEND_LOCK_HELD", { vault: input.vault }) };
|
|
2429
|
+
}
|
|
2430
|
+
const logPath = join17(input.vault, "log.md");
|
|
2431
|
+
try {
|
|
2432
|
+
let logText;
|
|
2433
|
+
try {
|
|
2434
|
+
logText = await readFile13(logPath, "utf8");
|
|
2435
|
+
} catch {
|
|
2436
|
+
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
2437
|
+
}
|
|
2438
|
+
const entriesBefore = [...logText.matchAll(ENTRY_RE2)].length;
|
|
2439
|
+
const body = logText.replace(/\s+$/, "");
|
|
2440
|
+
const next = `${body}
|
|
2441
|
+
|
|
2442
|
+
${content}
|
|
2443
|
+
`;
|
|
2444
|
+
try {
|
|
2445
|
+
const tmp = logPath + ".tmp";
|
|
2446
|
+
await writeFile8(tmp, next, "utf8");
|
|
2447
|
+
await rename4(tmp, logPath);
|
|
2448
|
+
} catch (e) {
|
|
2449
|
+
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { message: String(e) }) };
|
|
2450
|
+
}
|
|
2451
|
+
appendLastOp(input.vault, {
|
|
2452
|
+
operation: "log-append",
|
|
2453
|
+
summary: `appended log entry (${entriesBefore}->${entriesBefore + 1})`,
|
|
2454
|
+
files: ["log.md"],
|
|
2455
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2456
|
+
});
|
|
2457
|
+
const entriesAfter = entriesBefore + 1;
|
|
2458
|
+
return {
|
|
2459
|
+
exitCode: ExitCode.OK,
|
|
2460
|
+
result: ok({ entries_before: entriesBefore, entries_after: entriesAfter, appended: true, humanHint: `appended log entry (${entriesBefore}->${entriesAfter})` })
|
|
2461
|
+
};
|
|
2462
|
+
} finally {
|
|
2463
|
+
releaseLogLock(input.vault);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2365
2467
|
// src/commands/lint.ts
|
|
2366
|
-
import { existsSync as
|
|
2367
|
-
import { readFile as
|
|
2368
|
-
import { join as
|
|
2468
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2469
|
+
import { readFile as readFile16, rename as rename6 } from "fs/promises";
|
|
2470
|
+
import { join as join21 } from "path";
|
|
2369
2471
|
|
|
2370
2472
|
// src/commands/topic-map-check.ts
|
|
2371
2473
|
var DEFAULT_THRESHOLD = 200;
|
|
@@ -2387,13 +2489,13 @@ async function runTopicMapCheck(input) {
|
|
|
2387
2489
|
}
|
|
2388
2490
|
|
|
2389
2491
|
// src/commands/index-link-format.ts
|
|
2390
|
-
import { readFile as
|
|
2391
|
-
import { join as
|
|
2492
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
2493
|
+
import { join as join18 } from "path";
|
|
2392
2494
|
var MD_LINK_RE = /\[[^\[\]]+\]\([^)]+\.md\)/;
|
|
2393
2495
|
async function runIndexLinkFormat(input) {
|
|
2394
2496
|
let text = "";
|
|
2395
2497
|
try {
|
|
2396
|
-
text = await
|
|
2498
|
+
text = await readFile14(join18(input.vault, "index.md"), "utf8");
|
|
2397
2499
|
} catch {
|
|
2398
2500
|
}
|
|
2399
2501
|
const markdown_links = [];
|
|
@@ -2406,8 +2508,8 @@ ${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
|
|
|
2406
2508
|
}
|
|
2407
2509
|
|
|
2408
2510
|
// src/commands/dedup.ts
|
|
2409
|
-
import { readFileSync as readFileSync3, writeFileSync as
|
|
2410
|
-
import { join as
|
|
2511
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
2512
|
+
import { join as join19 } from "path";
|
|
2411
2513
|
async function runDedup(input) {
|
|
2412
2514
|
const scan = await scanVault(input.vault);
|
|
2413
2515
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -2435,7 +2537,7 @@ async function runDedup(input) {
|
|
|
2435
2537
|
}
|
|
2436
2538
|
}
|
|
2437
2539
|
for (const page of scan.data.typedKnowledge) {
|
|
2438
|
-
const text = readFileSync3(
|
|
2540
|
+
const text = readFileSync3(join19(input.vault, page.relPath), "utf-8");
|
|
2439
2541
|
let updated = text;
|
|
2440
2542
|
let changed = false;
|
|
2441
2543
|
for (const [oldPath, newPath] of replacements) {
|
|
@@ -2453,14 +2555,14 @@ async function runDedup(input) {
|
|
|
2453
2555
|
}
|
|
2454
2556
|
}
|
|
2455
2557
|
if (changed) {
|
|
2456
|
-
|
|
2558
|
+
writeFileSync3(join19(input.vault, page.relPath), updated);
|
|
2457
2559
|
rewired.push(page.relPath);
|
|
2458
2560
|
}
|
|
2459
2561
|
}
|
|
2460
2562
|
for (const [oldPath] of replacements) {
|
|
2461
|
-
const fullPath =
|
|
2563
|
+
const fullPath = join19(input.vault, oldPath);
|
|
2462
2564
|
try {
|
|
2463
|
-
|
|
2565
|
+
unlinkSync3(fullPath);
|
|
2464
2566
|
removed.push(oldPath);
|
|
2465
2567
|
} catch {
|
|
2466
2568
|
}
|
|
@@ -2493,9 +2595,9 @@ async function runDedup(input) {
|
|
|
2493
2595
|
}
|
|
2494
2596
|
|
|
2495
2597
|
// src/utils/safe-write.ts
|
|
2496
|
-
import { open, readFile as
|
|
2598
|
+
import { open, readFile as readFile15, rename as rename5, unlink as unlink2, writeFile as writeFile9 } from "fs/promises";
|
|
2497
2599
|
import { randomBytes } from "crypto";
|
|
2498
|
-
import { dirname as dirname7, basename, join as
|
|
2600
|
+
import { dirname as dirname7, basename, join as join20 } from "path";
|
|
2499
2601
|
var DEFAULT_MIN_BODY_RATIO = 0.5;
|
|
2500
2602
|
var DEFAULT_MIN_OLD_BODY_BYTES = 200;
|
|
2501
2603
|
function bodyBytes(text) {
|
|
@@ -2505,7 +2607,7 @@ function bodyBytes(text) {
|
|
|
2505
2607
|
}
|
|
2506
2608
|
async function readIfExists(absPath) {
|
|
2507
2609
|
try {
|
|
2508
|
-
return await
|
|
2610
|
+
return await readFile15(absPath, "utf8");
|
|
2509
2611
|
} catch (e) {
|
|
2510
2612
|
if (e.code === "ENOENT") return null;
|
|
2511
2613
|
throw e;
|
|
@@ -2544,7 +2646,7 @@ async function safeWritePage(absPath, newContent, opts = {}) {
|
|
|
2544
2646
|
}
|
|
2545
2647
|
const dir = dirname7(absPath);
|
|
2546
2648
|
const tmpName = `.${basename(absPath)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
2547
|
-
const tmpPath =
|
|
2649
|
+
const tmpPath = join20(dir, tmpName);
|
|
2548
2650
|
try {
|
|
2549
2651
|
const handle = await open(tmpPath, "w");
|
|
2550
2652
|
try {
|
|
@@ -2556,7 +2658,7 @@ async function safeWritePage(absPath, newContent, opts = {}) {
|
|
|
2556
2658
|
} finally {
|
|
2557
2659
|
await handle.close();
|
|
2558
2660
|
}
|
|
2559
|
-
await
|
|
2661
|
+
await rename5(tmpPath, absPath);
|
|
2560
2662
|
return ok({ isNew, oldBodyBytes, newBodyBytes, bodyRatio, guardSkippedSmall });
|
|
2561
2663
|
} catch (e) {
|
|
2562
2664
|
try {
|
|
@@ -2689,11 +2791,12 @@ function buildCliSurface() {
|
|
|
2689
2791
|
program2.command("claim").option("--project <slug>").option("--slug <slug>").option("--wiki <name>");
|
|
2690
2792
|
program2.command("pagesize").option("--lines <n>").option("--wiki <name>");
|
|
2691
2793
|
program2.command("log-rotate").option("--threshold <n>").option("--apply").option("--wiki <name>");
|
|
2794
|
+
program2.command("log-append").requiredOption("--content <text>").option("--wiki <name>");
|
|
2692
2795
|
program2.command("lint").option("--days <n>").option("--lines <n>").option("--log-threshold <n>").option("--fix").option("--only <bucket>").option("--wiki <name>");
|
|
2693
2796
|
program2.command("config");
|
|
2694
2797
|
program2.command("doctor");
|
|
2695
2798
|
program2.command("status").option("--wiki <name>");
|
|
2696
|
-
program2.command("archive").option("--wiki <name>");
|
|
2799
|
+
program2.command("archive").option("--wiki <name>").option("--cascade").option("--apply");
|
|
2697
2800
|
program2.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
|
|
2698
2801
|
program2.command("dedup").option("--apply").option("--wiki <name>");
|
|
2699
2802
|
program2.command("migrate-citations").option("--dry-run").option("--wiki <name>");
|
|
@@ -2726,6 +2829,9 @@ function buildCliSurface() {
|
|
|
2726
2829
|
syncCmd2.command("status").option("--wiki <name>");
|
|
2727
2830
|
syncCmd2.command("push").option("--wiki <name>");
|
|
2728
2831
|
syncCmd2.command("pull").option("--wiki <name>");
|
|
2832
|
+
syncCmd2.command("lock").option("--summary <text>").option("--ttl-minutes <n>").option("--force").option("--wiki <name>");
|
|
2833
|
+
syncCmd2.command("unlock").option("--force").option("--wiki <name>");
|
|
2834
|
+
syncCmd2.command("peers").option("--wiki <name>");
|
|
2729
2835
|
const backupCmd2 = program2.commands.find((c) => c.name() === "backup");
|
|
2730
2836
|
backupCmd2.command("sync").option("--dry-run").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--prune").option("--wiki <name>");
|
|
2731
2837
|
backupCmd2.command("restore").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--target <dir>").option("--wiki <name>");
|
|
@@ -2956,7 +3062,7 @@ async function runLint(input) {
|
|
|
2956
3062
|
let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
|
|
2957
3063
|
rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
|
|
2958
3064
|
if (!rawPath.startsWith("raw/") && !rawPath.startsWith("_archive/raw/")) continue;
|
|
2959
|
-
if (!
|
|
3065
|
+
if (!existsSync4(join21(input.vault, rawPath)) && !existsSync4(join21(input.vault, rawPath + ".md")) && !rawPath.startsWith("_archive/") && !existsSync4(join21(input.vault, "_archive", rawPath)) && !existsSync4(join21(input.vault, "_archive", rawPath + ".md"))) {
|
|
2960
3066
|
brokenSourceFlags.push(`${page.relPath}: ${rawPath}`);
|
|
2961
3067
|
}
|
|
2962
3068
|
}
|
|
@@ -3047,11 +3153,11 @@ async function runLint(input) {
|
|
|
3047
3153
|
const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
|
|
3048
3154
|
if (!slugMatch) continue;
|
|
3049
3155
|
const slug = slugMatch[1];
|
|
3050
|
-
const knowledgePath =
|
|
3051
|
-
if (!
|
|
3156
|
+
const knowledgePath = join21(input.vault, "projects", slug, "knowledge.md");
|
|
3157
|
+
if (!existsSync4(knowledgePath)) continue;
|
|
3052
3158
|
const pageRef = page.relPath.replace(/\.md$/, "");
|
|
3053
3159
|
try {
|
|
3054
|
-
const knowledgeContent = await
|
|
3160
|
+
const knowledgeContent = await readFile16(knowledgePath, "utf8");
|
|
3055
3161
|
if (!knowledgeContent.includes(`[[${pageRef}]]`)) {
|
|
3056
3162
|
orphanedProjectPages.push(`${page.relPath}: not in projects/${slug}/knowledge.md`);
|
|
3057
3163
|
}
|
|
@@ -3062,7 +3168,7 @@ async function runLint(input) {
|
|
|
3062
3168
|
if (orphanedProjectPages.length > 0) buckets.orphaned_project_pages = orphanedProjectPages;
|
|
3063
3169
|
const cliRefFlags = [];
|
|
3064
3170
|
const cliSurface = buildCliSurface();
|
|
3065
|
-
const allScanPages = [...scan.data.typedKnowledge
|
|
3171
|
+
const allScanPages = [...scan.data.typedKnowledge];
|
|
3066
3172
|
for (const page of allScanPages) {
|
|
3067
3173
|
const text = await readPage(page);
|
|
3068
3174
|
const violations = validateCliRefs(text, page.relPath, cliSurface);
|
|
@@ -3098,7 +3204,7 @@ async function runLint(input) {
|
|
|
3098
3204
|
for (const relPath of legacyPages) {
|
|
3099
3205
|
try {
|
|
3100
3206
|
const absPath = `${input.vault}/${relPath}`;
|
|
3101
|
-
const raw = await
|
|
3207
|
+
const raw = await readFile16(absPath, "utf8");
|
|
3102
3208
|
const split = splitFrontmatter(raw);
|
|
3103
3209
|
if (!split.ok) {
|
|
3104
3210
|
unresolved.push(relPath);
|
|
@@ -3197,7 +3303,7 @@ ${newBody}`;
|
|
|
3197
3303
|
for (const relPath of noOverview) {
|
|
3198
3304
|
try {
|
|
3199
3305
|
const absPath = `${input.vault}/${relPath}`;
|
|
3200
|
-
const raw = await
|
|
3306
|
+
const raw = await readFile16(absPath, "utf8");
|
|
3201
3307
|
const split = splitFrontmatter(raw);
|
|
3202
3308
|
if (!split.ok) {
|
|
3203
3309
|
unresolved.push(relPath);
|
|
@@ -3238,7 +3344,7 @@ ${trimmedBody}`;
|
|
|
3238
3344
|
for (const relPath of missingTldrFlags) {
|
|
3239
3345
|
try {
|
|
3240
3346
|
const absPath = `${input.vault}/${relPath}`;
|
|
3241
|
-
const raw = await
|
|
3347
|
+
const raw = await readFile16(absPath, "utf8");
|
|
3242
3348
|
const split = splitFrontmatter(raw);
|
|
3243
3349
|
if (!split.ok) {
|
|
3244
3350
|
unresolved.push(relPath);
|
|
@@ -3288,7 +3394,7 @@ ${lines.join("\n")}`;
|
|
|
3288
3394
|
for (const relPath of wikilinkCitationFlags) {
|
|
3289
3395
|
try {
|
|
3290
3396
|
const absPath = `${input.vault}/${relPath}`;
|
|
3291
|
-
const raw = await
|
|
3397
|
+
const raw = await readFile16(absPath, "utf8");
|
|
3292
3398
|
const split = splitFrontmatter(raw);
|
|
3293
3399
|
if (!split.ok) {
|
|
3294
3400
|
unresolved.push(relPath);
|
|
@@ -3375,7 +3481,7 @@ ${newBody}`;
|
|
|
3375
3481
|
for (const relPath of fileSourceUrlFlags) {
|
|
3376
3482
|
try {
|
|
3377
3483
|
const absPath = `${input.vault}/${relPath}`;
|
|
3378
|
-
const raw = await
|
|
3484
|
+
const raw = await readFile16(absPath, "utf8");
|
|
3379
3485
|
const parts = raw.split("---", 3);
|
|
3380
3486
|
if (parts.length < 3) {
|
|
3381
3487
|
unresolved.push(relPath);
|
|
@@ -3417,11 +3523,11 @@ ${newBody}`;
|
|
|
3417
3523
|
const absPath = `${input.vault}/${v.relPath}`;
|
|
3418
3524
|
const newRelPath = truncateFilename(v.relPath);
|
|
3419
3525
|
const newAbsPath = `${input.vault}/${newRelPath}`;
|
|
3420
|
-
await
|
|
3526
|
+
await rename6(absPath, newAbsPath);
|
|
3421
3527
|
const oldPathEscaped = v.relPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3422
3528
|
for (const page of allPages) {
|
|
3423
3529
|
if (page.relPath === v.relPath) continue;
|
|
3424
|
-
const content = await
|
|
3530
|
+
const content = await readFile16(page.absPath, "utf8");
|
|
3425
3531
|
if (!content.includes(v.relPath)) continue;
|
|
3426
3532
|
let updated = content;
|
|
3427
3533
|
const citationRe = new RegExp(`\\^\\[${oldPathEscaped}\\]`, "g");
|
|
@@ -3523,14 +3629,14 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
|
|
|
3523
3629
|
}
|
|
3524
3630
|
|
|
3525
3631
|
// src/commands/config.ts
|
|
3526
|
-
import { readFile as
|
|
3527
|
-
import { existsSync as
|
|
3528
|
-
import { join as
|
|
3632
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
3633
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3634
|
+
import { join as join22 } from "path";
|
|
3529
3635
|
function validateKey(key) {
|
|
3530
3636
|
return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
|
|
3531
3637
|
}
|
|
3532
3638
|
function configPath(home) {
|
|
3533
|
-
return
|
|
3639
|
+
return join22(home, ".skillwiki", ".env");
|
|
3534
3640
|
}
|
|
3535
3641
|
async function runConfigGet(input) {
|
|
3536
3642
|
if (!validateKey(input.key)) {
|
|
@@ -3548,7 +3654,7 @@ async function runConfigSet(input) {
|
|
|
3548
3654
|
try {
|
|
3549
3655
|
let originalContent;
|
|
3550
3656
|
try {
|
|
3551
|
-
originalContent = await
|
|
3657
|
+
originalContent = await readFile17(filePath, "utf8");
|
|
3552
3658
|
} catch {
|
|
3553
3659
|
}
|
|
3554
3660
|
const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
|
|
@@ -3580,18 +3686,18 @@ async function runConfigList(input) {
|
|
|
3580
3686
|
}
|
|
3581
3687
|
async function runConfigPath(input) {
|
|
3582
3688
|
const filePath = configPath(input.home);
|
|
3583
|
-
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists:
|
|
3689
|
+
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync5(filePath), humanHint: filePath }) };
|
|
3584
3690
|
}
|
|
3585
3691
|
|
|
3586
3692
|
// src/commands/doctor.ts
|
|
3587
|
-
import { existsSync as
|
|
3588
|
-
import { join as
|
|
3693
|
+
import { existsSync as existsSync8, lstatSync, readlinkSync, readdirSync, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
|
|
3694
|
+
import { join as join26, resolve as resolve4 } from "path";
|
|
3589
3695
|
import { execSync as execSync2 } from "child_process";
|
|
3590
3696
|
import { platform as platform2 } from "os";
|
|
3591
3697
|
|
|
3592
3698
|
// src/utils/auto-update.ts
|
|
3593
|
-
import { readFileSync as readFileSync4, writeFileSync as
|
|
3594
|
-
import { join as
|
|
3699
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
3700
|
+
import { join as join23, dirname as dirname8 } from "path";
|
|
3595
3701
|
import { spawn } from "child_process";
|
|
3596
3702
|
|
|
3597
3703
|
// src/utils/update-consts.ts
|
|
@@ -3602,7 +3708,7 @@ var CLI_DISABLE_FLAG = "--no-update-notifier";
|
|
|
3602
3708
|
|
|
3603
3709
|
// src/utils/auto-update.ts
|
|
3604
3710
|
function cachePath(home) {
|
|
3605
|
-
return
|
|
3711
|
+
return join23(home, ".skillwiki", CACHE_FILENAME);
|
|
3606
3712
|
}
|
|
3607
3713
|
function readCacheRaw(home) {
|
|
3608
3714
|
try {
|
|
@@ -3621,8 +3727,8 @@ function readCache(home) {
|
|
|
3621
3727
|
}
|
|
3622
3728
|
function writeCache(home, cache) {
|
|
3623
3729
|
const p = cachePath(home);
|
|
3624
|
-
|
|
3625
|
-
|
|
3730
|
+
mkdirSync3(dirname8(p), { recursive: true });
|
|
3731
|
+
writeFileSync4(p, JSON.stringify(cache, null, 2));
|
|
3626
3732
|
}
|
|
3627
3733
|
function latestFromCache(home, currentVersion) {
|
|
3628
3734
|
const { cache } = readCache(home);
|
|
@@ -3640,7 +3746,7 @@ function triggerAutoUpdate(home, currentVersion) {
|
|
|
3640
3746
|
const { isStale: isStale2 } = readCache(home);
|
|
3641
3747
|
if (!isStale2) return;
|
|
3642
3748
|
const bgScript = new URL("../auto-update-bg.js", import.meta.url).pathname;
|
|
3643
|
-
if (!
|
|
3749
|
+
if (!existsSync6(bgScript)) return;
|
|
3644
3750
|
const child = spawn(process.execPath, [bgScript, home, currentVersion], {
|
|
3645
3751
|
detached: true,
|
|
3646
3752
|
stdio: "ignore"
|
|
@@ -3652,12 +3758,12 @@ function triggerAutoUpdate(home, currentVersion) {
|
|
|
3652
3758
|
|
|
3653
3759
|
// src/utils/plugin-registry.ts
|
|
3654
3760
|
import { readFileSync as readFileSync5 } from "fs";
|
|
3655
|
-
import { join as
|
|
3656
|
-
var REGISTRY_PATH =
|
|
3761
|
+
import { join as join24 } from "path";
|
|
3762
|
+
var REGISTRY_PATH = join24(".claude", "plugins", "installed_plugins.json");
|
|
3657
3763
|
var PLUGIN_KEY = "skillwiki@llm-wiki";
|
|
3658
3764
|
function readInstalledPlugins(home) {
|
|
3659
3765
|
try {
|
|
3660
|
-
const raw = readFileSync5(
|
|
3766
|
+
const raw = readFileSync5(join24(home, REGISTRY_PATH), "utf8");
|
|
3661
3767
|
return JSON.parse(raw);
|
|
3662
3768
|
} catch {
|
|
3663
3769
|
return null;
|
|
@@ -3674,8 +3780,8 @@ function findPlugin(home, key = PLUGIN_KEY) {
|
|
|
3674
3780
|
// src/utils/s3-mount-health.ts
|
|
3675
3781
|
import { execSync } from "child_process";
|
|
3676
3782
|
import { platform } from "os";
|
|
3677
|
-
import { readFileSync as readFileSync6, writeFileSync as
|
|
3678
|
-
import { join as
|
|
3783
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readFileSync as readFile18 } from "fs";
|
|
3784
|
+
import { join as join25 } from "path";
|
|
3679
3785
|
var OS = platform();
|
|
3680
3786
|
function findRcloneMountPid() {
|
|
3681
3787
|
try {
|
|
@@ -3833,35 +3939,35 @@ function detectFuseMount(vaultPath) {
|
|
|
3833
3939
|
return null;
|
|
3834
3940
|
}
|
|
3835
3941
|
function writeTest(dir) {
|
|
3836
|
-
const testFile =
|
|
3942
|
+
const testFile = join25(dir, `.doctor-write-test-${process.pid}.tmp`);
|
|
3837
3943
|
const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
|
|
3838
3944
|
const start = Date.now();
|
|
3839
3945
|
try {
|
|
3840
|
-
|
|
3946
|
+
writeFileSync5(testFile, payload, "utf8");
|
|
3841
3947
|
} catch (e) {
|
|
3842
3948
|
return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
|
|
3843
3949
|
}
|
|
3844
3950
|
const writeMs = Date.now() - start;
|
|
3845
3951
|
const readStart = Date.now();
|
|
3846
3952
|
try {
|
|
3847
|
-
const back =
|
|
3953
|
+
const back = readFile18(testFile, "utf8");
|
|
3848
3954
|
const readMs = Date.now() - readStart;
|
|
3849
3955
|
if (back !== payload) {
|
|
3850
3956
|
try {
|
|
3851
|
-
|
|
3957
|
+
unlinkSync4(testFile);
|
|
3852
3958
|
} catch {
|
|
3853
3959
|
}
|
|
3854
3960
|
return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
|
|
3855
3961
|
}
|
|
3856
3962
|
} catch (e) {
|
|
3857
3963
|
try {
|
|
3858
|
-
|
|
3964
|
+
unlinkSync4(testFile);
|
|
3859
3965
|
} catch {
|
|
3860
3966
|
}
|
|
3861
3967
|
return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
|
|
3862
3968
|
}
|
|
3863
3969
|
try {
|
|
3864
|
-
|
|
3970
|
+
unlinkSync4(testFile);
|
|
3865
3971
|
} catch {
|
|
3866
3972
|
}
|
|
3867
3973
|
return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
|
|
@@ -3902,13 +4008,13 @@ function detectCliChannels(argv, home) {
|
|
|
3902
4008
|
}
|
|
3903
4009
|
const plugin = findPlugin(home);
|
|
3904
4010
|
if (plugin) {
|
|
3905
|
-
const pluginBin =
|
|
3906
|
-
if (
|
|
4011
|
+
const pluginBin = join26(plugin.installPath, "bin", "skillwiki");
|
|
4012
|
+
if (existsSync8(pluginBin)) {
|
|
3907
4013
|
channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
|
|
3908
4014
|
}
|
|
3909
4015
|
}
|
|
3910
|
-
const installBin =
|
|
3911
|
-
if (
|
|
4016
|
+
const installBin = join26(home, ".claude", "skills", "bin", "skillwiki");
|
|
4017
|
+
if (existsSync8(installBin)) {
|
|
3912
4018
|
channels.push({ name: "install", path: installBin, isDevLink: false });
|
|
3913
4019
|
}
|
|
3914
4020
|
return channels;
|
|
@@ -3960,7 +4066,7 @@ function checkCliChannels(argv, home) {
|
|
|
3960
4066
|
}
|
|
3961
4067
|
async function checkConfigFile(home) {
|
|
3962
4068
|
const cfgPath = configPath(home);
|
|
3963
|
-
if (!
|
|
4069
|
+
if (!existsSync8(cfgPath)) {
|
|
3964
4070
|
return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
|
|
3965
4071
|
}
|
|
3966
4072
|
try {
|
|
@@ -3975,7 +4081,7 @@ function checkWikiPathExists(resolvedPath) {
|
|
|
3975
4081
|
if (resolvedPath === void 0) {
|
|
3976
4082
|
return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3977
4083
|
}
|
|
3978
|
-
if (
|
|
4084
|
+
if (existsSync8(resolvedPath) && statSync3(resolvedPath).isDirectory()) {
|
|
3979
4085
|
return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
|
|
3980
4086
|
}
|
|
3981
4087
|
return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
|
|
@@ -3984,13 +4090,13 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3984
4090
|
if (resolvedPath === void 0) {
|
|
3985
4091
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3986
4092
|
}
|
|
3987
|
-
if (!
|
|
4093
|
+
if (!existsSync8(resolvedPath)) {
|
|
3988
4094
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
3989
4095
|
}
|
|
3990
4096
|
const missing = [];
|
|
3991
|
-
if (!
|
|
4097
|
+
if (!existsSync8(join26(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
3992
4098
|
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
3993
|
-
if (!
|
|
4099
|
+
if (!existsSync8(join26(resolvedPath, dir))) missing.push(dir + "/");
|
|
3994
4100
|
}
|
|
3995
4101
|
if (missing.length === 0) {
|
|
3996
4102
|
return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
|
|
@@ -3998,8 +4104,8 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3998
4104
|
return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
|
|
3999
4105
|
}
|
|
4000
4106
|
function checkSkillsInstalled(home, cwd) {
|
|
4001
|
-
const srcDir = cwd ?
|
|
4002
|
-
if (srcDir &&
|
|
4107
|
+
const srcDir = cwd ? join26(cwd, "packages", "skills") : void 0;
|
|
4108
|
+
if (srcDir && existsSync8(srcDir)) {
|
|
4003
4109
|
const found = findSkillMd(srcDir);
|
|
4004
4110
|
if (found.length > 0) {
|
|
4005
4111
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
|
|
@@ -4012,8 +4118,8 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
4012
4118
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
|
|
4013
4119
|
}
|
|
4014
4120
|
}
|
|
4015
|
-
const skillsDir =
|
|
4016
|
-
if (
|
|
4121
|
+
const skillsDir = join26(home, ".claude", "skills");
|
|
4122
|
+
if (existsSync8(skillsDir)) {
|
|
4017
4123
|
const found = findSkillMd(skillsDir);
|
|
4018
4124
|
if (found.length > 0) {
|
|
4019
4125
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
|
|
@@ -4023,10 +4129,10 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
4023
4129
|
}
|
|
4024
4130
|
function checkDuplicateSkills(home) {
|
|
4025
4131
|
const plugin = findPlugin(home);
|
|
4026
|
-
const skillsDir =
|
|
4132
|
+
const skillsDir = join26(home, ".claude", "skills");
|
|
4027
4133
|
const agentSkillDirs = [
|
|
4028
|
-
{ label: "~/.codex/skills/", path:
|
|
4029
|
-
{ label: "~/.agents/skills/", path:
|
|
4134
|
+
{ label: "~/.codex/skills/", path: join26(home, ".codex", "skills") },
|
|
4135
|
+
{ label: "~/.agents/skills/", path: join26(home, ".agents", "skills") }
|
|
4030
4136
|
];
|
|
4031
4137
|
if (!plugin) {
|
|
4032
4138
|
return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
|
|
@@ -4103,8 +4209,8 @@ async function checkProfiles(home) {
|
|
|
4103
4209
|
}
|
|
4104
4210
|
async function checkProjectLocalOverride(cwd) {
|
|
4105
4211
|
const dir = cwd ?? process.cwd();
|
|
4106
|
-
const envPath =
|
|
4107
|
-
if (
|
|
4212
|
+
const envPath = join26(dir, ".skillwiki", ".env");
|
|
4213
|
+
if (existsSync8(envPath)) {
|
|
4108
4214
|
return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
|
|
4109
4215
|
}
|
|
4110
4216
|
return check("pass", "project_local", "Project-local config", "None");
|
|
@@ -4113,7 +4219,7 @@ function checkVaultGitRemote(resolvedPath) {
|
|
|
4113
4219
|
if (resolvedPath === void 0) {
|
|
4114
4220
|
return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4115
4221
|
}
|
|
4116
|
-
if (!
|
|
4222
|
+
if (!existsSync8(join26(resolvedPath, ".git"))) {
|
|
4117
4223
|
return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
|
|
4118
4224
|
}
|
|
4119
4225
|
try {
|
|
@@ -4136,9 +4242,9 @@ function checkObsidianTemplates(resolvedPath) {
|
|
|
4136
4242
|
return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4137
4243
|
}
|
|
4138
4244
|
const missing = [];
|
|
4139
|
-
if (!
|
|
4140
|
-
if (!
|
|
4141
|
-
if (!
|
|
4245
|
+
if (!existsSync8(join26(resolvedPath, "_Templates"))) missing.push("_Templates/");
|
|
4246
|
+
if (!existsSync8(join26(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
|
|
4247
|
+
if (!existsSync8(join26(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
|
|
4142
4248
|
if (missing.length === 0) {
|
|
4143
4249
|
return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
|
|
4144
4250
|
}
|
|
@@ -4148,8 +4254,8 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
4148
4254
|
if (resolvedPath === void 0) {
|
|
4149
4255
|
return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4150
4256
|
}
|
|
4151
|
-
const rawDir =
|
|
4152
|
-
if (!
|
|
4257
|
+
const rawDir = join26(resolvedPath, "raw");
|
|
4258
|
+
if (!existsSync8(rawDir)) {
|
|
4153
4259
|
return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
|
|
4154
4260
|
}
|
|
4155
4261
|
const found = [];
|
|
@@ -4164,7 +4270,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
4164
4270
|
if (entry.name === ".DS_Store") {
|
|
4165
4271
|
found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
|
|
4166
4272
|
} else if (entry.isDirectory()) {
|
|
4167
|
-
walk2(
|
|
4273
|
+
walk2(join26(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
4168
4274
|
}
|
|
4169
4275
|
}
|
|
4170
4276
|
})(rawDir, "");
|
|
@@ -4177,7 +4283,7 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
4177
4283
|
if (resolvedPath === void 0) {
|
|
4178
4284
|
return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4179
4285
|
}
|
|
4180
|
-
if (!
|
|
4286
|
+
if (!existsSync8(join26(resolvedPath, ".git"))) {
|
|
4181
4287
|
return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
|
|
4182
4288
|
}
|
|
4183
4289
|
let timestamp;
|
|
@@ -4218,8 +4324,8 @@ function checkS3MountPerf(resolvedPath) {
|
|
|
4218
4324
|
return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
|
|
4219
4325
|
}
|
|
4220
4326
|
const mountPoint = fuse.mountPoint;
|
|
4221
|
-
const conceptsDir =
|
|
4222
|
-
if (!
|
|
4327
|
+
const conceptsDir = join26(resolvedPath, "concepts");
|
|
4328
|
+
if (!existsSync8(conceptsDir)) {
|
|
4223
4329
|
return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
|
|
4224
4330
|
}
|
|
4225
4331
|
const start = Date.now();
|
|
@@ -4337,8 +4443,8 @@ function checkWriteTest(resolvedPath) {
|
|
|
4337
4443
|
if (!fuse) {
|
|
4338
4444
|
return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
|
|
4339
4445
|
}
|
|
4340
|
-
const conceptsDir =
|
|
4341
|
-
if (!
|
|
4446
|
+
const conceptsDir = join26(resolvedPath, "concepts");
|
|
4447
|
+
if (!existsSync8(conceptsDir)) {
|
|
4342
4448
|
return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
|
|
4343
4449
|
}
|
|
4344
4450
|
const result = writeTest(conceptsDir);
|
|
@@ -4424,7 +4530,7 @@ function checkVfsCacheHealth(resolvedPath) {
|
|
|
4424
4530
|
}
|
|
4425
4531
|
function readVaultSyncConfig(home) {
|
|
4426
4532
|
try {
|
|
4427
|
-
const content = readFileSync7(
|
|
4533
|
+
const content = readFileSync7(join26(home, ".skillwiki", ".env"), "utf8");
|
|
4428
4534
|
let installed = false;
|
|
4429
4535
|
let role;
|
|
4430
4536
|
for (const line of content.split(/\r?\n/)) {
|
|
@@ -4458,12 +4564,12 @@ function vaultSyncChecks(input) {
|
|
|
4458
4564
|
];
|
|
4459
4565
|
}
|
|
4460
4566
|
const isMac = os === "darwin";
|
|
4461
|
-
const logDir = input.logDir ?? (isMac ?
|
|
4462
|
-
const shareDir = input.shareDir ?? (isMac ?
|
|
4463
|
-
const filterPath = input.filterPath ??
|
|
4567
|
+
const logDir = input.logDir ?? (isMac ? join26(home, "Library", "Logs") : join26(home, ".local", "state", "vault-sync", "log"));
|
|
4568
|
+
const shareDir = input.shareDir ?? (isMac ? join26(home, "Library", "Application Support", "vault-sync", "bin") : join26(home, ".local", "share", "vault-sync", "bin"));
|
|
4569
|
+
const filterPath = input.filterPath ?? join26(home, ".config", "rclone", "wiki-push-filters.txt");
|
|
4464
4570
|
const snapshotPath = input.snapshotScriptPath ?? "/root/.hermes/scripts/wiki-snapshot-v3.sh";
|
|
4465
|
-
const pushScriptPath =
|
|
4466
|
-
const c1 =
|
|
4571
|
+
const pushScriptPath = join26(shareDir, "wiki-push.sh");
|
|
4572
|
+
const c1 = existsSync8(pushScriptPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found: ${pushScriptPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Script not found at ${pushScriptPath} \u2014 run vault-sync-install`);
|
|
4467
4573
|
let c2;
|
|
4468
4574
|
try {
|
|
4469
4575
|
if (isMac) {
|
|
@@ -4514,7 +4620,7 @@ function vaultSyncChecks(input) {
|
|
|
4514
4620
|
"Scheduler check failed \u2014 run vault-sync-install"
|
|
4515
4621
|
);
|
|
4516
4622
|
}
|
|
4517
|
-
const logFile =
|
|
4623
|
+
const logFile = join26(logDir, "wiki-push.log");
|
|
4518
4624
|
let c3;
|
|
4519
4625
|
try {
|
|
4520
4626
|
const logContent = readFileSync7(logFile, "utf8");
|
|
@@ -4573,7 +4679,7 @@ function vaultSyncChecks(input) {
|
|
|
4573
4679
|
}
|
|
4574
4680
|
}
|
|
4575
4681
|
} catch {
|
|
4576
|
-
c3 =
|
|
4682
|
+
c3 = existsSync8(logDir) ? check(
|
|
4577
4683
|
"warn",
|
|
4578
4684
|
"vault_sync_last_push_age",
|
|
4579
4685
|
"Vault sync last push recency",
|
|
@@ -4585,7 +4691,7 @@ function vaultSyncChecks(input) {
|
|
|
4585
4691
|
`Log directory not found at ${logDir}`
|
|
4586
4692
|
);
|
|
4587
4693
|
}
|
|
4588
|
-
const fetchLogFile =
|
|
4694
|
+
const fetchLogFile = join26(logDir, "wiki-fetch.log");
|
|
4589
4695
|
let cFetch;
|
|
4590
4696
|
try {
|
|
4591
4697
|
const logContent = readFileSync7(fetchLogFile, "utf8");
|
|
@@ -4632,7 +4738,7 @@ function vaultSyncChecks(input) {
|
|
|
4632
4738
|
}
|
|
4633
4739
|
let c4;
|
|
4634
4740
|
try {
|
|
4635
|
-
if (!
|
|
4741
|
+
if (!existsSync8(filterPath)) {
|
|
4636
4742
|
c4 = check(
|
|
4637
4743
|
"error",
|
|
4638
4744
|
"vault_sync_filter_present",
|
|
@@ -4681,7 +4787,7 @@ function vaultSyncChecks(input) {
|
|
|
4681
4787
|
);
|
|
4682
4788
|
} else {
|
|
4683
4789
|
try {
|
|
4684
|
-
if (!
|
|
4790
|
+
if (!existsSync8(snapshotPath)) {
|
|
4685
4791
|
c5 = check(
|
|
4686
4792
|
"error",
|
|
4687
4793
|
"vault_sync_snapshot_guard",
|
|
@@ -4727,9 +4833,9 @@ function findSkillMd(dir) {
|
|
|
4727
4833
|
}
|
|
4728
4834
|
for (const entry of entries) {
|
|
4729
4835
|
if (entry.isFile() && entry.name === "SKILL.md") {
|
|
4730
|
-
results.push(
|
|
4836
|
+
results.push(join26(dir, entry.name));
|
|
4731
4837
|
} else if (entry.isDirectory()) {
|
|
4732
|
-
results.push(...findSkillMd(
|
|
4838
|
+
results.push(...findSkillMd(join26(dir, entry.name)));
|
|
4733
4839
|
}
|
|
4734
4840
|
}
|
|
4735
4841
|
return results;
|
|
@@ -4743,7 +4849,7 @@ function findSkillNames(dir) {
|
|
|
4743
4849
|
return results;
|
|
4744
4850
|
}
|
|
4745
4851
|
for (const entry of entries) {
|
|
4746
|
-
if (entry.isDirectory() &&
|
|
4852
|
+
if (entry.isDirectory() && existsSync8(join26(dir, entry.name, "SKILL.md"))) {
|
|
4747
4853
|
results.push(entry.name);
|
|
4748
4854
|
}
|
|
4749
4855
|
}
|
|
@@ -4807,8 +4913,8 @@ async function runDoctor(input) {
|
|
|
4807
4913
|
}
|
|
4808
4914
|
|
|
4809
4915
|
// src/commands/archive.ts
|
|
4810
|
-
import { rename as
|
|
4811
|
-
import { join as
|
|
4916
|
+
import { rename as rename7, mkdir as mkdir8, readFile as readFile19, writeFile as writeFile10 } from "fs/promises";
|
|
4917
|
+
import { join as join27, dirname as dirname9 } from "path";
|
|
4812
4918
|
function countWikilinks(body, slug) {
|
|
4813
4919
|
const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4814
4920
|
const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
|
|
@@ -4836,7 +4942,7 @@ async function runArchive(input) {
|
|
|
4836
4942
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
4837
4943
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
4838
4944
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
4839
|
-
const archivePath =
|
|
4945
|
+
const archivePath = join27("_archive", relPath).replace(/\\/g, "/");
|
|
4840
4946
|
let cascade;
|
|
4841
4947
|
if (input.cascade) {
|
|
4842
4948
|
const wikilinkRefs = [];
|
|
@@ -4860,7 +4966,7 @@ async function runArchive(input) {
|
|
|
4860
4966
|
const indexRefs = [];
|
|
4861
4967
|
if (!isRaw) {
|
|
4862
4968
|
try {
|
|
4863
|
-
const idx = await
|
|
4969
|
+
const idx = await readFile19(join27(input.vault, "index.md"), "utf8");
|
|
4864
4970
|
idx.split("\n").forEach((line, i) => {
|
|
4865
4971
|
if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
|
|
4866
4972
|
});
|
|
@@ -4886,8 +4992,8 @@ async function runArchive(input) {
|
|
|
4886
4992
|
}
|
|
4887
4993
|
if (input.cascade && input.apply && cascade) {
|
|
4888
4994
|
for (const ref of cascade.source_array_refs) {
|
|
4889
|
-
const absPath =
|
|
4890
|
-
const text = await
|
|
4995
|
+
const absPath = join27(input.vault, ref.page);
|
|
4996
|
+
const text = await readFile19(absPath, "utf8");
|
|
4891
4997
|
const split = splitFrontmatter(text);
|
|
4892
4998
|
if (!split.ok) continue;
|
|
4893
4999
|
const before = split.data.rawFrontmatter;
|
|
@@ -4898,29 +5004,29 @@ async function runArchive(input) {
|
|
|
4898
5004
|
);
|
|
4899
5005
|
if (fmRewritten === before) continue;
|
|
4900
5006
|
if (!arraysEqual(ref.sources_after, ref.sources_before)) {
|
|
4901
|
-
await
|
|
5007
|
+
await writeFile10(absPath, `---
|
|
4902
5008
|
${fmRewritten}
|
|
4903
5009
|
---${split.data.body}`, "utf8");
|
|
4904
5010
|
}
|
|
4905
5011
|
}
|
|
4906
5012
|
}
|
|
4907
|
-
await mkdir8(dirname9(
|
|
5013
|
+
await mkdir8(dirname9(join27(input.vault, archivePath)), { recursive: true });
|
|
4908
5014
|
let indexUpdated = false;
|
|
4909
5015
|
if (!isRaw) {
|
|
4910
|
-
const indexPath =
|
|
5016
|
+
const indexPath = join27(input.vault, "index.md");
|
|
4911
5017
|
try {
|
|
4912
|
-
const idx = await
|
|
5018
|
+
const idx = await readFile19(indexPath, "utf8");
|
|
4913
5019
|
const originalLines = idx.split("\n");
|
|
4914
5020
|
const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
|
|
4915
5021
|
if (filtered.length !== originalLines.length) {
|
|
4916
|
-
await
|
|
5022
|
+
await writeFile10(indexPath, filtered.join("\n"), "utf8");
|
|
4917
5023
|
indexUpdated = true;
|
|
4918
5024
|
}
|
|
4919
5025
|
} catch (e) {
|
|
4920
5026
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
4921
5027
|
}
|
|
4922
5028
|
}
|
|
4923
|
-
await
|
|
5029
|
+
await rename7(join27(input.vault, relPath), join27(input.vault, archivePath));
|
|
4924
5030
|
appendLastOp(input.vault, {
|
|
4925
5031
|
operation: input.cascade ? "archive-cascade" : "archive",
|
|
4926
5032
|
summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
|
|
@@ -5307,14 +5413,14 @@ ${newBody}`;
|
|
|
5307
5413
|
// src/commands/update.ts
|
|
5308
5414
|
import { execSync as execSync3 } from "child_process";
|
|
5309
5415
|
import { readFileSync as readFileSync8 } from "fs";
|
|
5310
|
-
import { join as
|
|
5416
|
+
import { join as join28 } from "path";
|
|
5311
5417
|
function resolveGlobalSkillsRoot() {
|
|
5312
5418
|
try {
|
|
5313
5419
|
const globalRoot = execSync3("npm root -g", {
|
|
5314
5420
|
encoding: "utf8",
|
|
5315
5421
|
timeout: 5e3
|
|
5316
5422
|
}).trim();
|
|
5317
|
-
return
|
|
5423
|
+
return join28(globalRoot, "skillwiki", "skills");
|
|
5318
5424
|
} catch {
|
|
5319
5425
|
return null;
|
|
5320
5426
|
}
|
|
@@ -5340,7 +5446,7 @@ async function runUpdate(input) {
|
|
|
5340
5446
|
);
|
|
5341
5447
|
const currentVersion = pkg2.version;
|
|
5342
5448
|
const tag = input.distTag ?? "latest";
|
|
5343
|
-
const target =
|
|
5449
|
+
const target = join28(input.home, ".claude", "skills");
|
|
5344
5450
|
let latest;
|
|
5345
5451
|
try {
|
|
5346
5452
|
latest = execSync3(`npm view skillwiki@${tag} version`, {
|
|
@@ -5410,16 +5516,16 @@ async function runUpdate(input) {
|
|
|
5410
5516
|
|
|
5411
5517
|
// src/commands/self-update.ts
|
|
5412
5518
|
import { execSync as execSync4 } from "child_process";
|
|
5413
|
-
import { existsSync as
|
|
5414
|
-
import { join as
|
|
5519
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
5520
|
+
import { join as join29 } from "path";
|
|
5415
5521
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
5416
5522
|
async function runSelfUpdate(input) {
|
|
5417
5523
|
const currentVersion = JSON.parse(
|
|
5418
5524
|
readFileSync9(new URL("../../package.json", import.meta.url), "utf8")
|
|
5419
5525
|
).version;
|
|
5420
5526
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
5421
|
-
const localPkgPath =
|
|
5422
|
-
const hasLocalSource =
|
|
5527
|
+
const localPkgPath = join29(sourceRoot, "packages", "cli", "package.json");
|
|
5528
|
+
const hasLocalSource = existsSync9(localPkgPath);
|
|
5423
5529
|
if (input.check) {
|
|
5424
5530
|
let availableVersion = null;
|
|
5425
5531
|
let source;
|
|
@@ -5550,10 +5656,10 @@ async function runSelfUpdate(input) {
|
|
|
5550
5656
|
}
|
|
5551
5657
|
|
|
5552
5658
|
// src/commands/transcripts.ts
|
|
5553
|
-
import { readdir as readdir5, stat as
|
|
5554
|
-
import { join as
|
|
5659
|
+
import { readdir as readdir5, stat as stat7, readFile as readFile20 } from "fs/promises";
|
|
5660
|
+
import { join as join30 } from "path";
|
|
5555
5661
|
async function runTranscripts(input) {
|
|
5556
|
-
const dir =
|
|
5662
|
+
const dir = join30(input.vault, "raw", "transcripts");
|
|
5557
5663
|
let entries;
|
|
5558
5664
|
try {
|
|
5559
5665
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -5563,13 +5669,13 @@ async function runTranscripts(input) {
|
|
|
5563
5669
|
const transcripts = [];
|
|
5564
5670
|
for (const entry of entries) {
|
|
5565
5671
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
5566
|
-
const filePath =
|
|
5567
|
-
const content = await
|
|
5672
|
+
const filePath = join30(dir, entry.name);
|
|
5673
|
+
const content = await readFile20(filePath, "utf8");
|
|
5568
5674
|
const fm = extractFrontmatter(content);
|
|
5569
5675
|
if (!fm.ok) continue;
|
|
5570
5676
|
const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
|
|
5571
5677
|
if (input.since && ingested && ingested < input.since) continue;
|
|
5572
|
-
const s = await
|
|
5678
|
+
const s = await stat7(filePath);
|
|
5573
5679
|
transcripts.push({
|
|
5574
5680
|
file: `raw/transcripts/${entry.name}`,
|
|
5575
5681
|
ingested,
|
|
@@ -5581,12 +5687,12 @@ async function runTranscripts(input) {
|
|
|
5581
5687
|
}
|
|
5582
5688
|
|
|
5583
5689
|
// src/commands/project-index.ts
|
|
5584
|
-
import { readdir as readdir6, readFile as
|
|
5585
|
-
import { join as
|
|
5690
|
+
import { readdir as readdir6, readFile as readFile21, writeFile as writeFile11, mkdir as mkdir9 } from "fs/promises";
|
|
5691
|
+
import { join as join31, dirname as dirname10 } from "path";
|
|
5586
5692
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
5587
5693
|
async function runProjectIndex(input) {
|
|
5588
5694
|
const slug = input.slug;
|
|
5589
|
-
const projectDir =
|
|
5695
|
+
const projectDir = join31(input.vault, "projects", slug);
|
|
5590
5696
|
try {
|
|
5591
5697
|
await readdir6(projectDir);
|
|
5592
5698
|
} catch {
|
|
@@ -5597,15 +5703,15 @@ async function runProjectIndex(input) {
|
|
|
5597
5703
|
}
|
|
5598
5704
|
const wikilinkPattern = `[[${slug}]]`;
|
|
5599
5705
|
const entries = [];
|
|
5600
|
-
const compoundDir =
|
|
5706
|
+
const compoundDir = join31(input.vault, "projects", slug, "compound");
|
|
5601
5707
|
try {
|
|
5602
5708
|
const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
|
|
5603
5709
|
for (const entry of compoundFiles) {
|
|
5604
5710
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
5605
|
-
const filePath =
|
|
5711
|
+
const filePath = join31(compoundDir, entry.name);
|
|
5606
5712
|
let text;
|
|
5607
5713
|
try {
|
|
5608
|
-
text = await
|
|
5714
|
+
text = await readFile21(filePath, "utf8");
|
|
5609
5715
|
} catch {
|
|
5610
5716
|
continue;
|
|
5611
5717
|
}
|
|
@@ -5622,16 +5728,16 @@ async function runProjectIndex(input) {
|
|
|
5622
5728
|
for (const dir of LAYER2_DIRS) {
|
|
5623
5729
|
let files;
|
|
5624
5730
|
try {
|
|
5625
|
-
files = await readdir6(
|
|
5731
|
+
files = await readdir6(join31(input.vault, dir), { withFileTypes: true });
|
|
5626
5732
|
} catch {
|
|
5627
5733
|
continue;
|
|
5628
5734
|
}
|
|
5629
5735
|
for (const entry of files) {
|
|
5630
5736
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
5631
|
-
const filePath =
|
|
5737
|
+
const filePath = join31(input.vault, dir, entry.name);
|
|
5632
5738
|
let text;
|
|
5633
5739
|
try {
|
|
5634
|
-
text = await
|
|
5740
|
+
text = await readFile21(filePath, "utf8");
|
|
5635
5741
|
} catch {
|
|
5636
5742
|
continue;
|
|
5637
5743
|
}
|
|
@@ -5652,11 +5758,11 @@ async function runProjectIndex(input) {
|
|
|
5652
5758
|
const tb = typeOrder[b.type] ?? 99;
|
|
5653
5759
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
5654
5760
|
});
|
|
5655
|
-
const indexPath =
|
|
5761
|
+
const indexPath = join31(projectDir, "knowledge.md");
|
|
5656
5762
|
let existing = false;
|
|
5657
5763
|
let stale = false;
|
|
5658
5764
|
try {
|
|
5659
|
-
const existingText = await
|
|
5765
|
+
const existingText = await readFile21(indexPath, "utf8");
|
|
5660
5766
|
existing = true;
|
|
5661
5767
|
const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
|
|
5662
5768
|
const existingPages = new Set(existingEntries.map((l) => {
|
|
@@ -5697,7 +5803,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
|
|
|
5697
5803
|
if (input.apply) {
|
|
5698
5804
|
try {
|
|
5699
5805
|
await mkdir9(dirname10(indexPath), { recursive: true });
|
|
5700
|
-
await
|
|
5806
|
+
await writeFile11(indexPath, body, "utf8");
|
|
5701
5807
|
} catch (e) {
|
|
5702
5808
|
return {
|
|
5703
5809
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5725,10 +5831,10 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
5725
5831
|
}
|
|
5726
5832
|
|
|
5727
5833
|
// src/commands/compound.ts
|
|
5728
|
-
import { writeFile as
|
|
5729
|
-
import { join as
|
|
5730
|
-
import { existsSync as
|
|
5731
|
-
import { readFile as
|
|
5834
|
+
import { writeFile as writeFile12, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
|
|
5835
|
+
import { join as join32 } from "path";
|
|
5836
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5837
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
5732
5838
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
5733
5839
|
var FIELD_RE = {
|
|
5734
5840
|
improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
|
|
@@ -5826,17 +5932,17 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
5826
5932
|
};
|
|
5827
5933
|
}
|
|
5828
5934
|
async function runCompound(input) {
|
|
5829
|
-
const logPath =
|
|
5935
|
+
const logPath = join32(input.vault, "log.md");
|
|
5830
5936
|
let logText;
|
|
5831
5937
|
try {
|
|
5832
|
-
logText = await
|
|
5938
|
+
logText = await readFile22(logPath, "utf8");
|
|
5833
5939
|
} catch {
|
|
5834
5940
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
5835
5941
|
}
|
|
5836
5942
|
const entries = parseRetroEntries(logText);
|
|
5837
5943
|
const promoted = [];
|
|
5838
5944
|
const skipped = [];
|
|
5839
|
-
const compoundDir =
|
|
5945
|
+
const compoundDir = join32(input.vault, "projects", input.project, "compound");
|
|
5840
5946
|
for (const entry of entries) {
|
|
5841
5947
|
const generalizeValue = entry.generalize.trim();
|
|
5842
5948
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -5844,8 +5950,8 @@ async function runCompound(input) {
|
|
|
5844
5950
|
continue;
|
|
5845
5951
|
}
|
|
5846
5952
|
const slug = slugify(entry.cycleName);
|
|
5847
|
-
const compoundPath =
|
|
5848
|
-
if (
|
|
5953
|
+
const compoundPath = join32(compoundDir, `${slug}.md`);
|
|
5954
|
+
if (existsSync10(compoundPath)) {
|
|
5849
5955
|
skipped.push(entry.date);
|
|
5850
5956
|
continue;
|
|
5851
5957
|
}
|
|
@@ -5883,10 +5989,10 @@ async function runCompound(input) {
|
|
|
5883
5989
|
].join("\n");
|
|
5884
5990
|
const content = frontmatter + "\n" + body;
|
|
5885
5991
|
if (!input.dryRun) {
|
|
5886
|
-
if (!
|
|
5992
|
+
if (!existsSync10(compoundDir)) {
|
|
5887
5993
|
await mkdir10(compoundDir, { recursive: true });
|
|
5888
5994
|
}
|
|
5889
|
-
await
|
|
5995
|
+
await writeFile12(compoundPath, content, "utf8");
|
|
5890
5996
|
}
|
|
5891
5997
|
promoted.push(`${slug}.md`);
|
|
5892
5998
|
}
|
|
@@ -5905,16 +6011,16 @@ async function runCompound(input) {
|
|
|
5905
6011
|
};
|
|
5906
6012
|
}
|
|
5907
6013
|
async function runCompoundDelete(input) {
|
|
5908
|
-
const projectDir =
|
|
5909
|
-
if (!
|
|
6014
|
+
const projectDir = join32(input.vault, "projects", input.project);
|
|
6015
|
+
if (!existsSync10(projectDir)) {
|
|
5910
6016
|
return {
|
|
5911
6017
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
5912
6018
|
result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
|
|
5913
6019
|
};
|
|
5914
6020
|
}
|
|
5915
6021
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
5916
|
-
const compoundPath =
|
|
5917
|
-
if (!
|
|
6022
|
+
const compoundPath = join32(projectDir, "compound", `${entryName}.md`);
|
|
6023
|
+
if (!existsSync10(compoundPath)) {
|
|
5918
6024
|
return {
|
|
5919
6025
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
5920
6026
|
result: err("FILE_NOT_FOUND", { path: compoundPath })
|
|
@@ -5947,8 +6053,8 @@ knowledge.md regenerated`
|
|
|
5947
6053
|
};
|
|
5948
6054
|
}
|
|
5949
6055
|
async function runCompoundList(input) {
|
|
5950
|
-
const compoundDir =
|
|
5951
|
-
if (!
|
|
6056
|
+
const compoundDir = join32(input.vault, "projects", input.project, "compound");
|
|
6057
|
+
if (!existsSync10(compoundDir)) {
|
|
5952
6058
|
return {
|
|
5953
6059
|
exitCode: ExitCode.OK,
|
|
5954
6060
|
result: ok({
|
|
@@ -5978,10 +6084,10 @@ could not read compound directory`
|
|
|
5978
6084
|
const entries = [];
|
|
5979
6085
|
for (const dirent of dirents) {
|
|
5980
6086
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
5981
|
-
const filePath =
|
|
6087
|
+
const filePath = join32(compoundDir, dirent.name);
|
|
5982
6088
|
let text;
|
|
5983
6089
|
try {
|
|
5984
|
-
text = await
|
|
6090
|
+
text = await readFile22(filePath, "utf8");
|
|
5985
6091
|
} catch {
|
|
5986
6092
|
continue;
|
|
5987
6093
|
}
|
|
@@ -6010,9 +6116,9 @@ no compound entries found`;
|
|
|
6010
6116
|
}
|
|
6011
6117
|
|
|
6012
6118
|
// src/commands/observe.ts
|
|
6013
|
-
import { mkdir as mkdir11, writeFile as
|
|
6014
|
-
import { existsSync as
|
|
6015
|
-
import { join as
|
|
6119
|
+
import { mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
|
|
6120
|
+
import { existsSync as existsSync11, statSync as statSync4 } from "fs";
|
|
6121
|
+
import { join as join33 } from "path";
|
|
6016
6122
|
import { createHash as createHash4 } from "crypto";
|
|
6017
6123
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
6018
6124
|
function slugify2(text) {
|
|
@@ -6035,13 +6141,13 @@ async function runObserve(input) {
|
|
|
6035
6141
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
6036
6142
|
};
|
|
6037
6143
|
}
|
|
6038
|
-
if (!
|
|
6144
|
+
if (!existsSync11(input.vault) || !statSync4(input.vault).isDirectory()) {
|
|
6039
6145
|
return {
|
|
6040
6146
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6041
6147
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
6042
6148
|
};
|
|
6043
6149
|
}
|
|
6044
|
-
const transcriptsDir =
|
|
6150
|
+
const transcriptsDir = join33(input.vault, "raw", "transcripts");
|
|
6045
6151
|
try {
|
|
6046
6152
|
await mkdir11(transcriptsDir, { recursive: true });
|
|
6047
6153
|
} catch {
|
|
@@ -6053,7 +6159,7 @@ async function runObserve(input) {
|
|
|
6053
6159
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6054
6160
|
const slug = slugify2(input.text);
|
|
6055
6161
|
const fileName = `${today}-observation-${slug}.md`;
|
|
6056
|
-
const filePath =
|
|
6162
|
+
const filePath = join33(transcriptsDir, fileName);
|
|
6057
6163
|
const body = `
|
|
6058
6164
|
${input.text.trim()}
|
|
6059
6165
|
`;
|
|
@@ -6071,7 +6177,7 @@ ${input.text.trim()}
|
|
|
6071
6177
|
frontmatterLines.push("---");
|
|
6072
6178
|
const content = frontmatterLines.join("\n") + body;
|
|
6073
6179
|
try {
|
|
6074
|
-
await
|
|
6180
|
+
await writeFile13(filePath, content, "utf8");
|
|
6075
6181
|
} catch (e) {
|
|
6076
6182
|
return {
|
|
6077
6183
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -6093,8 +6199,8 @@ ${input.text.trim()}
|
|
|
6093
6199
|
}
|
|
6094
6200
|
|
|
6095
6201
|
// src/commands/ingest.ts
|
|
6096
|
-
import { readFile as
|
|
6097
|
-
import { join as
|
|
6202
|
+
import { readFile as readFile23, writeFile as writeFile14, mkdir as mkdir12 } from "fs/promises";
|
|
6203
|
+
import { join as join34 } from "path";
|
|
6098
6204
|
import { createHash as createHash5 } from "crypto";
|
|
6099
6205
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
6100
6206
|
var TYPE_DIR = {
|
|
@@ -6253,7 +6359,7 @@ async function runIngest(input) {
|
|
|
6253
6359
|
sourceContent = fetchResult.data.body;
|
|
6254
6360
|
} else {
|
|
6255
6361
|
try {
|
|
6256
|
-
sourceContent = await
|
|
6362
|
+
sourceContent = await readFile23(input.source, "utf8");
|
|
6257
6363
|
} catch {
|
|
6258
6364
|
return {
|
|
6259
6365
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -6268,8 +6374,8 @@ async function runIngest(input) {
|
|
|
6268
6374
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
6269
6375
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
6270
6376
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
6271
|
-
const rawAbsPath =
|
|
6272
|
-
const typedAbsPath =
|
|
6377
|
+
const rawAbsPath = join34(input.vault, rawRelPath);
|
|
6378
|
+
const typedAbsPath = join34(input.vault, typedRelPath);
|
|
6273
6379
|
const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
|
|
6274
6380
|
const typedContent = buildTypedContent(
|
|
6275
6381
|
input.title,
|
|
@@ -6332,8 +6438,8 @@ async function runIngest(input) {
|
|
|
6332
6438
|
};
|
|
6333
6439
|
}
|
|
6334
6440
|
try {
|
|
6335
|
-
await mkdir12(
|
|
6336
|
-
await
|
|
6441
|
+
await mkdir12(join34(input.vault, "raw", "articles"), { recursive: true });
|
|
6442
|
+
await writeFile14(rawAbsPath, rawContent, "utf8");
|
|
6337
6443
|
} catch (e) {
|
|
6338
6444
|
return {
|
|
6339
6445
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -6341,8 +6447,8 @@ async function runIngest(input) {
|
|
|
6341
6447
|
};
|
|
6342
6448
|
}
|
|
6343
6449
|
try {
|
|
6344
|
-
await mkdir12(
|
|
6345
|
-
await
|
|
6450
|
+
await mkdir12(join34(input.vault, typedDir), { recursive: true });
|
|
6451
|
+
await writeFile14(typedAbsPath, typedContent, "utf8");
|
|
6346
6452
|
} catch (e) {
|
|
6347
6453
|
return {
|
|
6348
6454
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -6520,12 +6626,12 @@ ${body}`;
|
|
|
6520
6626
|
}
|
|
6521
6627
|
|
|
6522
6628
|
// src/commands/sync.ts
|
|
6523
|
-
import { existsSync as
|
|
6524
|
-
import { join as
|
|
6629
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6630
|
+
import { join as join36 } from "path";
|
|
6525
6631
|
|
|
6526
6632
|
// src/utils/sync-lock.ts
|
|
6527
|
-
import { existsSync as
|
|
6528
|
-
import { join as
|
|
6633
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
6634
|
+
import { join as join35 } from "path";
|
|
6529
6635
|
import { createHash as createHash6 } from "crypto";
|
|
6530
6636
|
function getSessionId() {
|
|
6531
6637
|
if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
|
|
@@ -6533,11 +6639,11 @@ function getSessionId() {
|
|
|
6533
6639
|
return process.pid.toString();
|
|
6534
6640
|
}
|
|
6535
6641
|
function lockPath(vault) {
|
|
6536
|
-
return
|
|
6642
|
+
return join35(vault, ".skillwiki", "sync.lock");
|
|
6537
6643
|
}
|
|
6538
6644
|
function readLock(vault) {
|
|
6539
6645
|
const path = lockPath(vault);
|
|
6540
|
-
if (!
|
|
6646
|
+
if (!existsSync12(path)) return null;
|
|
6541
6647
|
try {
|
|
6542
6648
|
const raw = readFileSync10(path, "utf8");
|
|
6543
6649
|
return JSON.parse(raw);
|
|
@@ -6552,9 +6658,9 @@ function isStale(lock, now) {
|
|
|
6552
6658
|
}
|
|
6553
6659
|
function acquireLock(vault, opts = {}) {
|
|
6554
6660
|
const path = lockPath(vault);
|
|
6555
|
-
const dir =
|
|
6556
|
-
if (!
|
|
6557
|
-
|
|
6661
|
+
const dir = join35(vault, ".skillwiki");
|
|
6662
|
+
if (!existsSync12(dir)) {
|
|
6663
|
+
mkdirSync4(dir, { recursive: true });
|
|
6558
6664
|
}
|
|
6559
6665
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
6560
6666
|
const summary = opts.summary ?? "skillwiki sync";
|
|
@@ -6573,7 +6679,7 @@ function acquireLock(vault, opts = {}) {
|
|
|
6573
6679
|
};
|
|
6574
6680
|
try {
|
|
6575
6681
|
const content = JSON.stringify(lock, null, 2) + "\n";
|
|
6576
|
-
|
|
6682
|
+
writeFileSync6(path, content, { flag: "wx" });
|
|
6577
6683
|
return { ok: true, lock };
|
|
6578
6684
|
} catch (e) {
|
|
6579
6685
|
const err3 = e;
|
|
@@ -6593,19 +6699,19 @@ function acquireLock(vault, opts = {}) {
|
|
|
6593
6699
|
function writeLockedFile(path, lock) {
|
|
6594
6700
|
const tmp = path + ".tmp";
|
|
6595
6701
|
const content = JSON.stringify(lock, null, 2) + "\n";
|
|
6596
|
-
|
|
6702
|
+
writeFileSync6(tmp, content);
|
|
6597
6703
|
renameSync(tmp, path);
|
|
6598
6704
|
}
|
|
6599
6705
|
function releaseLock(vault, opts = {}) {
|
|
6600
6706
|
const path = lockPath(vault);
|
|
6601
|
-
if (!
|
|
6707
|
+
if (!existsSync12(path)) {
|
|
6602
6708
|
return { released: false };
|
|
6603
6709
|
}
|
|
6604
6710
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
6605
6711
|
const existing = readLock(vault);
|
|
6606
6712
|
if (opts.force) {
|
|
6607
6713
|
try {
|
|
6608
|
-
|
|
6714
|
+
unlinkSync5(path);
|
|
6609
6715
|
const prior = existing && existing.session_id !== sessionId ? existing : void 0;
|
|
6610
6716
|
return { released: true, prior };
|
|
6611
6717
|
} catch {
|
|
@@ -6616,7 +6722,7 @@ function releaseLock(vault, opts = {}) {
|
|
|
6616
6722
|
return { released: false };
|
|
6617
6723
|
}
|
|
6618
6724
|
try {
|
|
6619
|
-
|
|
6725
|
+
unlinkSync5(path);
|
|
6620
6726
|
return { released: true };
|
|
6621
6727
|
} catch {
|
|
6622
6728
|
return { released: false };
|
|
@@ -6627,7 +6733,7 @@ function releaseLock(vault, opts = {}) {
|
|
|
6627
6733
|
function runSyncStatus(input) {
|
|
6628
6734
|
const vault = input.vault;
|
|
6629
6735
|
const includeStashes = input.includeStashes ?? false;
|
|
6630
|
-
if (!
|
|
6736
|
+
if (!existsSync13(join36(vault, ".git"))) {
|
|
6631
6737
|
return {
|
|
6632
6738
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6633
6739
|
result: ok({
|
|
@@ -6704,7 +6810,7 @@ function runSyncStatus(input) {
|
|
|
6704
6810
|
}
|
|
6705
6811
|
async function runSyncPush(input) {
|
|
6706
6812
|
const vault = input.vault;
|
|
6707
|
-
if (!
|
|
6813
|
+
if (!existsSync13(join36(vault, ".git"))) {
|
|
6708
6814
|
return {
|
|
6709
6815
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6710
6816
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -6808,7 +6914,7 @@ function enumerateStashes(vault) {
|
|
|
6808
6914
|
}
|
|
6809
6915
|
async function runSyncPull(input) {
|
|
6810
6916
|
const vault = input.vault;
|
|
6811
|
-
if (!
|
|
6917
|
+
if (!existsSync13(join36(vault, ".git"))) {
|
|
6812
6918
|
return {
|
|
6813
6919
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6814
6920
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -6976,7 +7082,7 @@ function runSyncPeers(input) {
|
|
|
6976
7082
|
}
|
|
6977
7083
|
function runSyncLock(input) {
|
|
6978
7084
|
const vault = input.vault;
|
|
6979
|
-
if (!
|
|
7085
|
+
if (!existsSync13(vault)) {
|
|
6980
7086
|
return {
|
|
6981
7087
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6982
7088
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
@@ -7011,7 +7117,7 @@ function runSyncLock(input) {
|
|
|
7011
7117
|
}
|
|
7012
7118
|
function runSyncUnlock(input) {
|
|
7013
7119
|
const vault = input.vault;
|
|
7014
|
-
if (!
|
|
7120
|
+
if (!existsSync13(vault)) {
|
|
7015
7121
|
return {
|
|
7016
7122
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7017
7123
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
@@ -7044,8 +7150,8 @@ function runSyncUnlock(input) {
|
|
|
7044
7150
|
}
|
|
7045
7151
|
|
|
7046
7152
|
// src/commands/backup.ts
|
|
7047
|
-
import { statSync as
|
|
7048
|
-
import { join as
|
|
7153
|
+
import { statSync as statSync5, readdirSync as readdirSync2, readFileSync as readFileSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
7154
|
+
import { join as join37, relative as relative3, dirname as dirname11 } from "path";
|
|
7049
7155
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
7050
7156
|
|
|
7051
7157
|
// src/utils/s3-client.ts
|
|
@@ -7069,7 +7175,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
|
|
|
7069
7175
|
function* walkMarkdown(dir, base) {
|
|
7070
7176
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
7071
7177
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
7072
|
-
const full =
|
|
7178
|
+
const full = join37(dir, entry.name);
|
|
7073
7179
|
if (entry.isDirectory()) {
|
|
7074
7180
|
yield* walkMarkdown(full, base);
|
|
7075
7181
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -7092,8 +7198,8 @@ async function runBackupSync(input) {
|
|
|
7092
7198
|
let failed = 0;
|
|
7093
7199
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
7094
7200
|
for (const relPath of files) {
|
|
7095
|
-
const absPath =
|
|
7096
|
-
const localStat =
|
|
7201
|
+
const absPath = join37(input.vault, relPath);
|
|
7202
|
+
const localStat = statSync5(absPath);
|
|
7097
7203
|
let needsUpload = true;
|
|
7098
7204
|
try {
|
|
7099
7205
|
const head = await client.send(new HeadObjectCommand({ Bucket: input.bucket, Key: relPath }));
|
|
@@ -7168,9 +7274,9 @@ async function runBackupRestore(input) {
|
|
|
7168
7274
|
const objects = list.Contents ?? [];
|
|
7169
7275
|
for (const obj of objects) {
|
|
7170
7276
|
if (!obj.Key) continue;
|
|
7171
|
-
const localPath =
|
|
7277
|
+
const localPath = join37(target, obj.Key);
|
|
7172
7278
|
try {
|
|
7173
|
-
const localStat =
|
|
7279
|
+
const localStat = statSync5(localPath);
|
|
7174
7280
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
7175
7281
|
conflicts++;
|
|
7176
7282
|
continue;
|
|
@@ -7181,8 +7287,8 @@ async function runBackupRestore(input) {
|
|
|
7181
7287
|
const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
|
|
7182
7288
|
const body = await resp.Body?.transformToByteArray();
|
|
7183
7289
|
if (body) {
|
|
7184
|
-
|
|
7185
|
-
|
|
7290
|
+
mkdirSync5(dirname11(localPath), { recursive: true });
|
|
7291
|
+
writeFileSync7(localPath, Buffer.from(body));
|
|
7186
7292
|
downloaded++;
|
|
7187
7293
|
}
|
|
7188
7294
|
} catch {
|
|
@@ -7214,11 +7320,11 @@ async function runBackupRestore(input) {
|
|
|
7214
7320
|
}
|
|
7215
7321
|
|
|
7216
7322
|
// src/commands/status.ts
|
|
7217
|
-
import { existsSync as
|
|
7218
|
-
import { readFile as
|
|
7219
|
-
import { join as
|
|
7323
|
+
import { existsSync as existsSync14, statSync as statSync6 } from "fs";
|
|
7324
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
7325
|
+
import { join as join38 } from "path";
|
|
7220
7326
|
async function runStatus(input) {
|
|
7221
|
-
if (!
|
|
7327
|
+
if (!existsSync14(input.vault)) {
|
|
7222
7328
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
7223
7329
|
}
|
|
7224
7330
|
const scan = await scanVault(input.vault);
|
|
@@ -7243,7 +7349,7 @@ async function runStatus(input) {
|
|
|
7243
7349
|
const compound = scan.data.compound.length;
|
|
7244
7350
|
let schemaVersion = "v1";
|
|
7245
7351
|
try {
|
|
7246
|
-
const schemaContent = await
|
|
7352
|
+
const schemaContent = await readFile24(join38(input.vault, "SCHEMA.md"), "utf8");
|
|
7247
7353
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
7248
7354
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
7249
7355
|
} catch {
|
|
@@ -7259,7 +7365,7 @@ async function runStatus(input) {
|
|
|
7259
7365
|
let maxTime = 0;
|
|
7260
7366
|
for (const page of allPages) {
|
|
7261
7367
|
try {
|
|
7262
|
-
const st =
|
|
7368
|
+
const st = statSync6(page.absPath);
|
|
7263
7369
|
if (st.mtimeMs > maxTime) {
|
|
7264
7370
|
maxTime = st.mtimeMs;
|
|
7265
7371
|
lastModified = st.mtime.toISOString();
|
|
@@ -7303,8 +7409,8 @@ async function runStatus(input) {
|
|
|
7303
7409
|
}
|
|
7304
7410
|
|
|
7305
7411
|
// src/commands/seed.ts
|
|
7306
|
-
import { mkdir as mkdir13, writeFile as
|
|
7307
|
-
import { join as
|
|
7412
|
+
import { mkdir as mkdir13, writeFile as writeFile15, stat as stat8 } from "fs/promises";
|
|
7413
|
+
import { join as join39 } from "path";
|
|
7308
7414
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7309
7415
|
var EXAMPLE_PAGES = {
|
|
7310
7416
|
"entities/example-project.md": `---
|
|
@@ -7373,30 +7479,30 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
7373
7479
|
`;
|
|
7374
7480
|
async function runSeed(input) {
|
|
7375
7481
|
try {
|
|
7376
|
-
await
|
|
7482
|
+
await stat8(join39(input.vault, "SCHEMA.md"));
|
|
7377
7483
|
} catch {
|
|
7378
7484
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
7379
7485
|
}
|
|
7380
7486
|
const created = [];
|
|
7381
7487
|
const skipped = [];
|
|
7382
7488
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
7383
|
-
const absPath =
|
|
7489
|
+
const absPath = join39(input.vault, relPath);
|
|
7384
7490
|
try {
|
|
7385
|
-
await
|
|
7491
|
+
await stat8(absPath);
|
|
7386
7492
|
skipped.push(relPath);
|
|
7387
7493
|
} catch {
|
|
7388
|
-
await mkdir13(
|
|
7389
|
-
await
|
|
7494
|
+
await mkdir13(join39(absPath, ".."), { recursive: true });
|
|
7495
|
+
await writeFile15(absPath, content, "utf8");
|
|
7390
7496
|
created.push(relPath);
|
|
7391
7497
|
}
|
|
7392
7498
|
}
|
|
7393
|
-
const rawPath =
|
|
7499
|
+
const rawPath = join39(input.vault, "raw", "articles", "example-source.md");
|
|
7394
7500
|
try {
|
|
7395
|
-
await
|
|
7501
|
+
await stat8(rawPath);
|
|
7396
7502
|
skipped.push("raw/articles/example-source.md");
|
|
7397
7503
|
} catch {
|
|
7398
|
-
await mkdir13(
|
|
7399
|
-
await
|
|
7504
|
+
await mkdir13(join39(rawPath, ".."), { recursive: true });
|
|
7505
|
+
await writeFile15(rawPath, EXAMPLE_RAW, "utf8");
|
|
7400
7506
|
created.push("raw/articles/example-source.md");
|
|
7401
7507
|
}
|
|
7402
7508
|
if (created.length > 0) {
|
|
@@ -7418,9 +7524,9 @@ async function runSeed(input) {
|
|
|
7418
7524
|
}
|
|
7419
7525
|
|
|
7420
7526
|
// src/commands/canvas.ts
|
|
7421
|
-
import { readFile as
|
|
7422
|
-
import { existsSync as
|
|
7423
|
-
import { join as
|
|
7527
|
+
import { readFile as readFile25, writeFile as writeFile16 } from "fs/promises";
|
|
7528
|
+
import { existsSync as existsSync15 } from "fs";
|
|
7529
|
+
import { join as join40 } from "path";
|
|
7424
7530
|
var NODE_WIDTH = 240;
|
|
7425
7531
|
var NODE_HEIGHT = 60;
|
|
7426
7532
|
var COLUMN_SPACING = 400;
|
|
@@ -7498,8 +7604,8 @@ function buildCanvasEdges(adjacency) {
|
|
|
7498
7604
|
return edges;
|
|
7499
7605
|
}
|
|
7500
7606
|
async function runCanvasGenerate(input) {
|
|
7501
|
-
const graphPath = input.graphPath ??
|
|
7502
|
-
if (!
|
|
7607
|
+
const graphPath = input.graphPath ?? join40(input.vault, ".skillwiki", "graph.json");
|
|
7608
|
+
if (!existsSync15(graphPath)) {
|
|
7503
7609
|
return {
|
|
7504
7610
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
7505
7611
|
result: err("FILE_NOT_FOUND", {
|
|
@@ -7510,7 +7616,7 @@ async function runCanvasGenerate(input) {
|
|
|
7510
7616
|
}
|
|
7511
7617
|
let raw;
|
|
7512
7618
|
try {
|
|
7513
|
-
raw = await
|
|
7619
|
+
raw = await readFile25(graphPath, "utf8");
|
|
7514
7620
|
} catch (e) {
|
|
7515
7621
|
return {
|
|
7516
7622
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -7536,9 +7642,9 @@ async function runCanvasGenerate(input) {
|
|
|
7536
7642
|
const nodes = buildCanvasNodes(paths);
|
|
7537
7643
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
7538
7644
|
const canvas = { nodes, edges };
|
|
7539
|
-
const outPath =
|
|
7645
|
+
const outPath = join40(input.vault, "vault-graph.canvas");
|
|
7540
7646
|
try {
|
|
7541
|
-
await
|
|
7647
|
+
await writeFile16(outPath, JSON.stringify(canvas, null, 2));
|
|
7542
7648
|
} catch (e) {
|
|
7543
7649
|
return {
|
|
7544
7650
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -7558,8 +7664,8 @@ written: ${outPath}`
|
|
|
7558
7664
|
}
|
|
7559
7665
|
|
|
7560
7666
|
// src/commands/query.ts
|
|
7561
|
-
import { readFile as
|
|
7562
|
-
import { join as
|
|
7667
|
+
import { readFile as readFile26, stat as stat9 } from "fs/promises";
|
|
7668
|
+
import { join as join41 } from "path";
|
|
7563
7669
|
var W_KEYWORD = 2;
|
|
7564
7670
|
var W_SOURCE_OVERLAP = 4;
|
|
7565
7671
|
var W_WIKILINK = 3;
|
|
@@ -7680,10 +7786,10 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
7680
7786
|
return score;
|
|
7681
7787
|
}
|
|
7682
7788
|
async function loadOrBuildGraph(vault) {
|
|
7683
|
-
const graphPath =
|
|
7789
|
+
const graphPath = join41(vault, ".skillwiki", "graph.json");
|
|
7684
7790
|
let needsBuild = false;
|
|
7685
7791
|
try {
|
|
7686
|
-
const fileStat = await
|
|
7792
|
+
const fileStat = await stat9(graphPath);
|
|
7687
7793
|
const ageHours = (Date.now() - fileStat.mtimeMs) / (1e3 * 60 * 60);
|
|
7688
7794
|
if (ageHours > 24) needsBuild = true;
|
|
7689
7795
|
} catch {
|
|
@@ -7694,7 +7800,7 @@ async function loadOrBuildGraph(vault) {
|
|
|
7694
7800
|
if (buildResult.exitCode !== 0) return null;
|
|
7695
7801
|
}
|
|
7696
7802
|
try {
|
|
7697
|
-
const raw = await
|
|
7803
|
+
const raw = await readFile26(graphPath, "utf8");
|
|
7698
7804
|
return JSON.parse(raw);
|
|
7699
7805
|
} catch {
|
|
7700
7806
|
return null;
|
|
@@ -7702,14 +7808,14 @@ async function loadOrBuildGraph(vault) {
|
|
|
7702
7808
|
}
|
|
7703
7809
|
|
|
7704
7810
|
// src/utils/auto-commit.ts
|
|
7705
|
-
import { existsSync as
|
|
7706
|
-
import { join as
|
|
7811
|
+
import { existsSync as existsSync16 } from "fs";
|
|
7812
|
+
import { join as join42 } from "path";
|
|
7707
7813
|
async function postCommit(vault, exitCode) {
|
|
7708
7814
|
if (exitCode !== 0) return;
|
|
7709
7815
|
const home = process.env.HOME ?? "";
|
|
7710
7816
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
7711
7817
|
if (dotenv["AUTO_COMMIT"] === "false") return;
|
|
7712
|
-
if (!
|
|
7818
|
+
if (!existsSync16(join42(vault, ".git"))) return;
|
|
7713
7819
|
const lastOps = readLastOp(vault);
|
|
7714
7820
|
if (lastOps.length === 0) return;
|
|
7715
7821
|
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
@@ -7760,7 +7866,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
7760
7866
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
7761
7867
|
});
|
|
7762
7868
|
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) => {
|
|
7763
|
-
const out = opts.out ??
|
|
7869
|
+
const out = opts.out ?? join43(vault, ".skillwiki", "graph.json");
|
|
7764
7870
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
7765
7871
|
});
|
|
7766
7872
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -7886,6 +7992,11 @@ program.command("log-rotate [vault]").description("rotate or trim the vault log
|
|
|
7886
7992
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
7887
7993
|
else emit(await runLogRotate({ vault: v.vault, threshold: opts.threshold, apply: !!opts.apply }), v.vault);
|
|
7888
7994
|
});
|
|
7995
|
+
program.command("log-append [vault]").description("append a single entry to the vault log under a short advisory lock").requiredOption("--content <text>", "log entry text to append").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
7996
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
7997
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
7998
|
+
else emit(await runLogAppend({ vault: v.vault, content: opts.content }), v.vault);
|
|
7999
|
+
});
|
|
7889
8000
|
program.command("lint [vault]").description("run all vault health checks").option("--days <n>", "stale threshold", (s) => parseInt(s, 10), 90).option("--lines <n>", "pagesize threshold", (s) => parseInt(s, 10), 200).option("--log-threshold <n>", "log rotation threshold", (s) => parseInt(s, 10), 500).option("--fix", "auto-fix supported lint violations").option("--only <bucket>", "run only the specified lint bucket").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
7890
8001
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
7891
8002
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|