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