skillwiki 0.7.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 {
|
|
@@ -2607,6 +2709,60 @@ async function runRawBodyDedup(vault) {
|
|
|
2607
2709
|
};
|
|
2608
2710
|
}
|
|
2609
2711
|
|
|
2712
|
+
// src/commands/path-too-long.ts
|
|
2713
|
+
var MAX_PATH_LENGTH = 240;
|
|
2714
|
+
async function runPathTooLong(input) {
|
|
2715
|
+
const scan = await scanVault(input.vault);
|
|
2716
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
2717
|
+
const allPages = [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound];
|
|
2718
|
+
const violations = [];
|
|
2719
|
+
for (const page of allPages) {
|
|
2720
|
+
if (page.relPath.length > MAX_PATH_LENGTH) {
|
|
2721
|
+
violations.push({ relPath: page.relPath, length: page.relPath.length });
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
if (violations.length > 0) {
|
|
2725
|
+
return {
|
|
2726
|
+
exitCode: ExitCode.LINT_HAS_ERRORS,
|
|
2727
|
+
result: ok({
|
|
2728
|
+
violations,
|
|
2729
|
+
humanHint: violations.map((v) => `${v.relPath}: ${v.length} chars (max ${MAX_PATH_LENGTH})`).join("\n")
|
|
2730
|
+
})
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
2733
|
+
return { exitCode: ExitCode.OK, result: ok({ violations, humanHint: "all paths within length limit" }) };
|
|
2734
|
+
}
|
|
2735
|
+
function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
|
|
2736
|
+
if (relPath.length <= maxLength) return relPath;
|
|
2737
|
+
const lastSlash = relPath.lastIndexOf("/");
|
|
2738
|
+
const dir = lastSlash >= 0 ? relPath.slice(0, lastSlash) : "";
|
|
2739
|
+
const filename = lastSlash >= 0 ? relPath.slice(lastSlash + 1) : relPath;
|
|
2740
|
+
const hash = computeShortHash(relPath);
|
|
2741
|
+
const ext = filename.endsWith(".md") ? ".md" : "";
|
|
2742
|
+
const base = filename.endsWith(".md") ? filename.slice(0, -3) : filename;
|
|
2743
|
+
const suffix = `-${hash}${ext}`;
|
|
2744
|
+
const dirPrefix = dir ? dir + "/" : "";
|
|
2745
|
+
const maxPrefixLen = maxLength - dirPrefix.length - suffix.length;
|
|
2746
|
+
if (maxPrefixLen <= 0) {
|
|
2747
|
+
const fallback = dirPrefix + hash + ext;
|
|
2748
|
+
if (fallback.length > maxLength) {
|
|
2749
|
+
const dirBudget = maxLength - suffix.length;
|
|
2750
|
+
return dirPrefix.slice(0, Math.max(0, dirBudget)) + suffix;
|
|
2751
|
+
}
|
|
2752
|
+
return fallback;
|
|
2753
|
+
}
|
|
2754
|
+
const prefix = base.slice(0, maxPrefixLen).replace(/[-_\s]+$/, "");
|
|
2755
|
+
return dirPrefix + prefix + suffix;
|
|
2756
|
+
}
|
|
2757
|
+
function computeShortHash(input) {
|
|
2758
|
+
let hash = 2166136261;
|
|
2759
|
+
for (let i = 0; i < input.length; i++) {
|
|
2760
|
+
hash ^= input.charCodeAt(i);
|
|
2761
|
+
hash = Math.imul(hash, 16777619);
|
|
2762
|
+
}
|
|
2763
|
+
return (hash >>> 0).toString(16).padStart(8, "0").slice(0, 8);
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2610
2766
|
// src/utils/cli-surface.ts
|
|
2611
2767
|
import { Command } from "commander";
|
|
2612
2768
|
function buildCliSurface() {
|
|
@@ -2639,7 +2795,7 @@ function buildCliSurface() {
|
|
|
2639
2795
|
program2.command("config");
|
|
2640
2796
|
program2.command("doctor");
|
|
2641
2797
|
program2.command("status").option("--wiki <name>");
|
|
2642
|
-
program2.command("archive").option("--wiki <name>");
|
|
2798
|
+
program2.command("archive").option("--wiki <name>").option("--cascade").option("--apply");
|
|
2643
2799
|
program2.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
|
|
2644
2800
|
program2.command("dedup").option("--apply").option("--wiki <name>");
|
|
2645
2801
|
program2.command("migrate-citations").option("--dry-run").option("--wiki <name>");
|
|
@@ -2672,6 +2828,9 @@ function buildCliSurface() {
|
|
|
2672
2828
|
syncCmd2.command("status").option("--wiki <name>");
|
|
2673
2829
|
syncCmd2.command("push").option("--wiki <name>");
|
|
2674
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>");
|
|
2675
2834
|
const backupCmd2 = program2.commands.find((c) => c.name() === "backup");
|
|
2676
2835
|
backupCmd2.command("sync").option("--dry-run").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--prune").option("--wiki <name>");
|
|
2677
2836
|
backupCmd2.command("restore").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--target <dir>").option("--wiki <name>");
|
|
@@ -2779,7 +2938,7 @@ function extractSourceEntries(rawFm) {
|
|
|
2779
2938
|
}
|
|
2780
2939
|
return entries;
|
|
2781
2940
|
}
|
|
2782
|
-
var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy"];
|
|
2941
|
+
var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
|
|
2783
2942
|
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "file_source_url", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
|
|
2784
2943
|
var INFO_ORDER = ["bridges", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
2785
2944
|
async function runLint(input) {
|
|
@@ -2839,6 +2998,8 @@ async function runLint(input) {
|
|
|
2839
2998
|
}
|
|
2840
2999
|
const compoundRefs = await validateCompoundReferences(input.vault);
|
|
2841
3000
|
if (compoundRefs.ok && compoundRefs.data.length > 0) buckets.compound_refs = compoundRefs.data;
|
|
3001
|
+
const pathCheck = await runPathTooLong({ vault: input.vault });
|
|
3002
|
+
if (pathCheck.result.ok && pathCheck.result.data.violations.length > 0) buckets.path_too_long = pathCheck.result.data.violations;
|
|
2842
3003
|
const scan = await scanVault(input.vault);
|
|
2843
3004
|
const allPages = scan.ok ? [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound] : [];
|
|
2844
3005
|
const slugs = scan.ok ? buildSlugMap(allPages) : /* @__PURE__ */ new Map();
|
|
@@ -2900,7 +3061,7 @@ async function runLint(input) {
|
|
|
2900
3061
|
let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
|
|
2901
3062
|
rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
|
|
2902
3063
|
if (!rawPath.startsWith("raw/") && !rawPath.startsWith("_archive/raw/")) continue;
|
|
2903
|
-
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"))) {
|
|
2904
3065
|
brokenSourceFlags.push(`${page.relPath}: ${rawPath}`);
|
|
2905
3066
|
}
|
|
2906
3067
|
}
|
|
@@ -2991,11 +3152,11 @@ async function runLint(input) {
|
|
|
2991
3152
|
const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
|
|
2992
3153
|
if (!slugMatch) continue;
|
|
2993
3154
|
const slug = slugMatch[1];
|
|
2994
|
-
const knowledgePath =
|
|
2995
|
-
if (!
|
|
3155
|
+
const knowledgePath = join21(input.vault, "projects", slug, "knowledge.md");
|
|
3156
|
+
if (!existsSync4(knowledgePath)) continue;
|
|
2996
3157
|
const pageRef = page.relPath.replace(/\.md$/, "");
|
|
2997
3158
|
try {
|
|
2998
|
-
const knowledgeContent = await
|
|
3159
|
+
const knowledgeContent = await readFile16(knowledgePath, "utf8");
|
|
2999
3160
|
if (!knowledgeContent.includes(`[[${pageRef}]]`)) {
|
|
3000
3161
|
orphanedProjectPages.push(`${page.relPath}: not in projects/${slug}/knowledge.md`);
|
|
3001
3162
|
}
|
|
@@ -3006,7 +3167,7 @@ async function runLint(input) {
|
|
|
3006
3167
|
if (orphanedProjectPages.length > 0) buckets.orphaned_project_pages = orphanedProjectPages;
|
|
3007
3168
|
const cliRefFlags = [];
|
|
3008
3169
|
const cliSurface = buildCliSurface();
|
|
3009
|
-
const allScanPages = [...scan.data.typedKnowledge
|
|
3170
|
+
const allScanPages = [...scan.data.typedKnowledge];
|
|
3010
3171
|
for (const page of allScanPages) {
|
|
3011
3172
|
const text = await readPage(page);
|
|
3012
3173
|
const violations = validateCliRefs(text, page.relPath, cliSurface);
|
|
@@ -3042,7 +3203,7 @@ async function runLint(input) {
|
|
|
3042
3203
|
for (const relPath of legacyPages) {
|
|
3043
3204
|
try {
|
|
3044
3205
|
const absPath = `${input.vault}/${relPath}`;
|
|
3045
|
-
const raw = await
|
|
3206
|
+
const raw = await readFile16(absPath, "utf8");
|
|
3046
3207
|
const split = splitFrontmatter(raw);
|
|
3047
3208
|
if (!split.ok) {
|
|
3048
3209
|
unresolved.push(relPath);
|
|
@@ -3141,7 +3302,7 @@ ${newBody}`;
|
|
|
3141
3302
|
for (const relPath of noOverview) {
|
|
3142
3303
|
try {
|
|
3143
3304
|
const absPath = `${input.vault}/${relPath}`;
|
|
3144
|
-
const raw = await
|
|
3305
|
+
const raw = await readFile16(absPath, "utf8");
|
|
3145
3306
|
const split = splitFrontmatter(raw);
|
|
3146
3307
|
if (!split.ok) {
|
|
3147
3308
|
unresolved.push(relPath);
|
|
@@ -3182,7 +3343,7 @@ ${trimmedBody}`;
|
|
|
3182
3343
|
for (const relPath of missingTldrFlags) {
|
|
3183
3344
|
try {
|
|
3184
3345
|
const absPath = `${input.vault}/${relPath}`;
|
|
3185
|
-
const raw = await
|
|
3346
|
+
const raw = await readFile16(absPath, "utf8");
|
|
3186
3347
|
const split = splitFrontmatter(raw);
|
|
3187
3348
|
if (!split.ok) {
|
|
3188
3349
|
unresolved.push(relPath);
|
|
@@ -3232,7 +3393,7 @@ ${lines.join("\n")}`;
|
|
|
3232
3393
|
for (const relPath of wikilinkCitationFlags) {
|
|
3233
3394
|
try {
|
|
3234
3395
|
const absPath = `${input.vault}/${relPath}`;
|
|
3235
|
-
const raw = await
|
|
3396
|
+
const raw = await readFile16(absPath, "utf8");
|
|
3236
3397
|
const split = splitFrontmatter(raw);
|
|
3237
3398
|
if (!split.ok) {
|
|
3238
3399
|
unresolved.push(relPath);
|
|
@@ -3319,7 +3480,7 @@ ${newBody}`;
|
|
|
3319
3480
|
for (const relPath of fileSourceUrlFlags) {
|
|
3320
3481
|
try {
|
|
3321
3482
|
const absPath = `${input.vault}/${relPath}`;
|
|
3322
|
-
const raw = await
|
|
3483
|
+
const raw = await readFile16(absPath, "utf8");
|
|
3323
3484
|
const parts = raw.split("---", 3);
|
|
3324
3485
|
if (parts.length < 3) {
|
|
3325
3486
|
unresolved.push(relPath);
|
|
@@ -3353,6 +3514,45 @@ ${newBody}`;
|
|
|
3353
3514
|
else delete buckets.file_source_url;
|
|
3354
3515
|
}
|
|
3355
3516
|
}
|
|
3517
|
+
const pathViolations = buckets.path_too_long;
|
|
3518
|
+
if (input.fix && pathViolations && pathViolations.length > 0) {
|
|
3519
|
+
const pathFixed = [];
|
|
3520
|
+
for (const v of pathViolations) {
|
|
3521
|
+
try {
|
|
3522
|
+
const absPath = `${input.vault}/${v.relPath}`;
|
|
3523
|
+
const newRelPath = truncateFilename(v.relPath);
|
|
3524
|
+
const newAbsPath = `${input.vault}/${newRelPath}`;
|
|
3525
|
+
await rename6(absPath, newAbsPath);
|
|
3526
|
+
const oldPathEscaped = v.relPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3527
|
+
for (const page of allPages) {
|
|
3528
|
+
if (page.relPath === v.relPath) continue;
|
|
3529
|
+
const content = await readFile16(page.absPath, "utf8");
|
|
3530
|
+
if (!content.includes(v.relPath)) continue;
|
|
3531
|
+
let updated = content;
|
|
3532
|
+
const citationRe = new RegExp(`\\^\\[${oldPathEscaped}\\]`, "g");
|
|
3533
|
+
updated = updated.replace(citationRe, `^[${newRelPath}]`);
|
|
3534
|
+
const wikilinkRe = new RegExp(`\\[\\[${oldPathEscaped}(\\|[^\\]]*)?\\]\\]`, "g");
|
|
3535
|
+
updated = updated.replace(wikilinkRe, (_m, alias) => `[[${newRelPath}${alias ?? ""}]]`);
|
|
3536
|
+
if (updated !== content) {
|
|
3537
|
+
const w = await safeWritePage(page.absPath, updated);
|
|
3538
|
+
if (!w.ok) {
|
|
3539
|
+
unresolved.push(`${page.relPath} (rewire)`);
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
pathFixed.push(v.relPath);
|
|
3544
|
+
} catch {
|
|
3545
|
+
unresolved.push(v.relPath);
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
fixed.push(...pathFixed);
|
|
3549
|
+
if (pathFixed.length > 0) {
|
|
3550
|
+
const fixedSet = new Set(pathFixed);
|
|
3551
|
+
const remaining = pathViolations.filter((v) => !fixedSet.has(v.relPath));
|
|
3552
|
+
if (remaining.length > 0) buckets.path_too_long = remaining;
|
|
3553
|
+
else delete buckets.path_too_long;
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3356
3556
|
}
|
|
3357
3557
|
const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
3358
3558
|
const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
@@ -3428,14 +3628,14 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
|
|
|
3428
3628
|
}
|
|
3429
3629
|
|
|
3430
3630
|
// src/commands/config.ts
|
|
3431
|
-
import { readFile as
|
|
3432
|
-
import { existsSync as
|
|
3433
|
-
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";
|
|
3434
3634
|
function validateKey(key) {
|
|
3435
3635
|
return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
|
|
3436
3636
|
}
|
|
3437
3637
|
function configPath(home) {
|
|
3438
|
-
return
|
|
3638
|
+
return join22(home, ".skillwiki", ".env");
|
|
3439
3639
|
}
|
|
3440
3640
|
async function runConfigGet(input) {
|
|
3441
3641
|
if (!validateKey(input.key)) {
|
|
@@ -3453,7 +3653,7 @@ async function runConfigSet(input) {
|
|
|
3453
3653
|
try {
|
|
3454
3654
|
let originalContent;
|
|
3455
3655
|
try {
|
|
3456
|
-
originalContent = await
|
|
3656
|
+
originalContent = await readFile17(filePath, "utf8");
|
|
3457
3657
|
} catch {
|
|
3458
3658
|
}
|
|
3459
3659
|
const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
|
|
@@ -3485,18 +3685,18 @@ async function runConfigList(input) {
|
|
|
3485
3685
|
}
|
|
3486
3686
|
async function runConfigPath(input) {
|
|
3487
3687
|
const filePath = configPath(input.home);
|
|
3488
|
-
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists:
|
|
3688
|
+
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync5(filePath), humanHint: filePath }) };
|
|
3489
3689
|
}
|
|
3490
3690
|
|
|
3491
3691
|
// src/commands/doctor.ts
|
|
3492
|
-
import { existsSync as
|
|
3493
|
-
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";
|
|
3494
3694
|
import { execSync as execSync2 } from "child_process";
|
|
3495
3695
|
import { platform as platform2 } from "os";
|
|
3496
3696
|
|
|
3497
3697
|
// src/utils/auto-update.ts
|
|
3498
|
-
import { readFileSync as readFileSync4, writeFileSync as
|
|
3499
|
-
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";
|
|
3500
3700
|
import { spawn } from "child_process";
|
|
3501
3701
|
|
|
3502
3702
|
// src/utils/update-consts.ts
|
|
@@ -3507,7 +3707,7 @@ var CLI_DISABLE_FLAG = "--no-update-notifier";
|
|
|
3507
3707
|
|
|
3508
3708
|
// src/utils/auto-update.ts
|
|
3509
3709
|
function cachePath(home) {
|
|
3510
|
-
return
|
|
3710
|
+
return join23(home, ".skillwiki", CACHE_FILENAME);
|
|
3511
3711
|
}
|
|
3512
3712
|
function readCacheRaw(home) {
|
|
3513
3713
|
try {
|
|
@@ -3526,8 +3726,8 @@ function readCache(home) {
|
|
|
3526
3726
|
}
|
|
3527
3727
|
function writeCache(home, cache) {
|
|
3528
3728
|
const p = cachePath(home);
|
|
3529
|
-
|
|
3530
|
-
|
|
3729
|
+
mkdirSync3(dirname8(p), { recursive: true });
|
|
3730
|
+
writeFileSync4(p, JSON.stringify(cache, null, 2));
|
|
3531
3731
|
}
|
|
3532
3732
|
function latestFromCache(home, currentVersion) {
|
|
3533
3733
|
const { cache } = readCache(home);
|
|
@@ -3545,7 +3745,7 @@ function triggerAutoUpdate(home, currentVersion) {
|
|
|
3545
3745
|
const { isStale: isStale2 } = readCache(home);
|
|
3546
3746
|
if (!isStale2) return;
|
|
3547
3747
|
const bgScript = new URL("../auto-update-bg.js", import.meta.url).pathname;
|
|
3548
|
-
if (!
|
|
3748
|
+
if (!existsSync6(bgScript)) return;
|
|
3549
3749
|
const child = spawn(process.execPath, [bgScript, home, currentVersion], {
|
|
3550
3750
|
detached: true,
|
|
3551
3751
|
stdio: "ignore"
|
|
@@ -3557,12 +3757,12 @@ function triggerAutoUpdate(home, currentVersion) {
|
|
|
3557
3757
|
|
|
3558
3758
|
// src/utils/plugin-registry.ts
|
|
3559
3759
|
import { readFileSync as readFileSync5 } from "fs";
|
|
3560
|
-
import { join as
|
|
3561
|
-
var REGISTRY_PATH =
|
|
3760
|
+
import { join as join24 } from "path";
|
|
3761
|
+
var REGISTRY_PATH = join24(".claude", "plugins", "installed_plugins.json");
|
|
3562
3762
|
var PLUGIN_KEY = "skillwiki@llm-wiki";
|
|
3563
3763
|
function readInstalledPlugins(home) {
|
|
3564
3764
|
try {
|
|
3565
|
-
const raw = readFileSync5(
|
|
3765
|
+
const raw = readFileSync5(join24(home, REGISTRY_PATH), "utf8");
|
|
3566
3766
|
return JSON.parse(raw);
|
|
3567
3767
|
} catch {
|
|
3568
3768
|
return null;
|
|
@@ -3579,8 +3779,8 @@ function findPlugin(home, key = PLUGIN_KEY) {
|
|
|
3579
3779
|
// src/utils/s3-mount-health.ts
|
|
3580
3780
|
import { execSync } from "child_process";
|
|
3581
3781
|
import { platform } from "os";
|
|
3582
|
-
import { readFileSync as readFileSync6, writeFileSync as
|
|
3583
|
-
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";
|
|
3584
3784
|
var OS = platform();
|
|
3585
3785
|
function findRcloneMountPid() {
|
|
3586
3786
|
try {
|
|
@@ -3738,35 +3938,35 @@ function detectFuseMount(vaultPath) {
|
|
|
3738
3938
|
return null;
|
|
3739
3939
|
}
|
|
3740
3940
|
function writeTest(dir) {
|
|
3741
|
-
const testFile =
|
|
3941
|
+
const testFile = join25(dir, `.doctor-write-test-${process.pid}.tmp`);
|
|
3742
3942
|
const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
|
|
3743
3943
|
const start = Date.now();
|
|
3744
3944
|
try {
|
|
3745
|
-
|
|
3945
|
+
writeFileSync5(testFile, payload, "utf8");
|
|
3746
3946
|
} catch (e) {
|
|
3747
3947
|
return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
|
|
3748
3948
|
}
|
|
3749
3949
|
const writeMs = Date.now() - start;
|
|
3750
3950
|
const readStart = Date.now();
|
|
3751
3951
|
try {
|
|
3752
|
-
const back =
|
|
3952
|
+
const back = readFile18(testFile, "utf8");
|
|
3753
3953
|
const readMs = Date.now() - readStart;
|
|
3754
3954
|
if (back !== payload) {
|
|
3755
3955
|
try {
|
|
3756
|
-
|
|
3956
|
+
unlinkSync4(testFile);
|
|
3757
3957
|
} catch {
|
|
3758
3958
|
}
|
|
3759
3959
|
return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
|
|
3760
3960
|
}
|
|
3761
3961
|
} catch (e) {
|
|
3762
3962
|
try {
|
|
3763
|
-
|
|
3963
|
+
unlinkSync4(testFile);
|
|
3764
3964
|
} catch {
|
|
3765
3965
|
}
|
|
3766
3966
|
return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
|
|
3767
3967
|
}
|
|
3768
3968
|
try {
|
|
3769
|
-
|
|
3969
|
+
unlinkSync4(testFile);
|
|
3770
3970
|
} catch {
|
|
3771
3971
|
}
|
|
3772
3972
|
return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
|
|
@@ -3807,13 +4007,13 @@ function detectCliChannels(argv, home) {
|
|
|
3807
4007
|
}
|
|
3808
4008
|
const plugin = findPlugin(home);
|
|
3809
4009
|
if (plugin) {
|
|
3810
|
-
const pluginBin =
|
|
3811
|
-
if (
|
|
4010
|
+
const pluginBin = join26(plugin.installPath, "bin", "skillwiki");
|
|
4011
|
+
if (existsSync8(pluginBin)) {
|
|
3812
4012
|
channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
|
|
3813
4013
|
}
|
|
3814
4014
|
}
|
|
3815
|
-
const installBin =
|
|
3816
|
-
if (
|
|
4015
|
+
const installBin = join26(home, ".claude", "skills", "bin", "skillwiki");
|
|
4016
|
+
if (existsSync8(installBin)) {
|
|
3817
4017
|
channels.push({ name: "install", path: installBin, isDevLink: false });
|
|
3818
4018
|
}
|
|
3819
4019
|
return channels;
|
|
@@ -3865,7 +4065,7 @@ function checkCliChannels(argv, home) {
|
|
|
3865
4065
|
}
|
|
3866
4066
|
async function checkConfigFile(home) {
|
|
3867
4067
|
const cfgPath = configPath(home);
|
|
3868
|
-
if (!
|
|
4068
|
+
if (!existsSync8(cfgPath)) {
|
|
3869
4069
|
return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
|
|
3870
4070
|
}
|
|
3871
4071
|
try {
|
|
@@ -3880,7 +4080,7 @@ function checkWikiPathExists(resolvedPath) {
|
|
|
3880
4080
|
if (resolvedPath === void 0) {
|
|
3881
4081
|
return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3882
4082
|
}
|
|
3883
|
-
if (
|
|
4083
|
+
if (existsSync8(resolvedPath) && statSync3(resolvedPath).isDirectory()) {
|
|
3884
4084
|
return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
|
|
3885
4085
|
}
|
|
3886
4086
|
return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
|
|
@@ -3889,13 +4089,13 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3889
4089
|
if (resolvedPath === void 0) {
|
|
3890
4090
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3891
4091
|
}
|
|
3892
|
-
if (!
|
|
4092
|
+
if (!existsSync8(resolvedPath)) {
|
|
3893
4093
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
3894
4094
|
}
|
|
3895
4095
|
const missing = [];
|
|
3896
|
-
if (!
|
|
4096
|
+
if (!existsSync8(join26(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
3897
4097
|
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
3898
|
-
if (!
|
|
4098
|
+
if (!existsSync8(join26(resolvedPath, dir))) missing.push(dir + "/");
|
|
3899
4099
|
}
|
|
3900
4100
|
if (missing.length === 0) {
|
|
3901
4101
|
return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
|
|
@@ -3903,8 +4103,8 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3903
4103
|
return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
|
|
3904
4104
|
}
|
|
3905
4105
|
function checkSkillsInstalled(home, cwd) {
|
|
3906
|
-
const srcDir = cwd ?
|
|
3907
|
-
if (srcDir &&
|
|
4106
|
+
const srcDir = cwd ? join26(cwd, "packages", "skills") : void 0;
|
|
4107
|
+
if (srcDir && existsSync8(srcDir)) {
|
|
3908
4108
|
const found = findSkillMd(srcDir);
|
|
3909
4109
|
if (found.length > 0) {
|
|
3910
4110
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
|
|
@@ -3917,8 +4117,8 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
3917
4117
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
|
|
3918
4118
|
}
|
|
3919
4119
|
}
|
|
3920
|
-
const skillsDir =
|
|
3921
|
-
if (
|
|
4120
|
+
const skillsDir = join26(home, ".claude", "skills");
|
|
4121
|
+
if (existsSync8(skillsDir)) {
|
|
3922
4122
|
const found = findSkillMd(skillsDir);
|
|
3923
4123
|
if (found.length > 0) {
|
|
3924
4124
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
|
|
@@ -3928,10 +4128,10 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
3928
4128
|
}
|
|
3929
4129
|
function checkDuplicateSkills(home) {
|
|
3930
4130
|
const plugin = findPlugin(home);
|
|
3931
|
-
const skillsDir =
|
|
4131
|
+
const skillsDir = join26(home, ".claude", "skills");
|
|
3932
4132
|
const agentSkillDirs = [
|
|
3933
|
-
{ label: "~/.codex/skills/", path:
|
|
3934
|
-
{ label: "~/.agents/skills/", path:
|
|
4133
|
+
{ label: "~/.codex/skills/", path: join26(home, ".codex", "skills") },
|
|
4134
|
+
{ label: "~/.agents/skills/", path: join26(home, ".agents", "skills") }
|
|
3935
4135
|
];
|
|
3936
4136
|
if (!plugin) {
|
|
3937
4137
|
return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
|
|
@@ -4008,8 +4208,8 @@ async function checkProfiles(home) {
|
|
|
4008
4208
|
}
|
|
4009
4209
|
async function checkProjectLocalOverride(cwd) {
|
|
4010
4210
|
const dir = cwd ?? process.cwd();
|
|
4011
|
-
const envPath =
|
|
4012
|
-
if (
|
|
4211
|
+
const envPath = join26(dir, ".skillwiki", ".env");
|
|
4212
|
+
if (existsSync8(envPath)) {
|
|
4013
4213
|
return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
|
|
4014
4214
|
}
|
|
4015
4215
|
return check("pass", "project_local", "Project-local config", "None");
|
|
@@ -4018,7 +4218,7 @@ function checkVaultGitRemote(resolvedPath) {
|
|
|
4018
4218
|
if (resolvedPath === void 0) {
|
|
4019
4219
|
return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4020
4220
|
}
|
|
4021
|
-
if (!
|
|
4221
|
+
if (!existsSync8(join26(resolvedPath, ".git"))) {
|
|
4022
4222
|
return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
|
|
4023
4223
|
}
|
|
4024
4224
|
try {
|
|
@@ -4041,9 +4241,9 @@ function checkObsidianTemplates(resolvedPath) {
|
|
|
4041
4241
|
return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4042
4242
|
}
|
|
4043
4243
|
const missing = [];
|
|
4044
|
-
if (!
|
|
4045
|
-
if (!
|
|
4046
|
-
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");
|
|
4047
4247
|
if (missing.length === 0) {
|
|
4048
4248
|
return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
|
|
4049
4249
|
}
|
|
@@ -4053,8 +4253,8 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
4053
4253
|
if (resolvedPath === void 0) {
|
|
4054
4254
|
return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4055
4255
|
}
|
|
4056
|
-
const rawDir =
|
|
4057
|
-
if (!
|
|
4256
|
+
const rawDir = join26(resolvedPath, "raw");
|
|
4257
|
+
if (!existsSync8(rawDir)) {
|
|
4058
4258
|
return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
|
|
4059
4259
|
}
|
|
4060
4260
|
const found = [];
|
|
@@ -4069,7 +4269,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
4069
4269
|
if (entry.name === ".DS_Store") {
|
|
4070
4270
|
found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
|
|
4071
4271
|
} else if (entry.isDirectory()) {
|
|
4072
|
-
walk2(
|
|
4272
|
+
walk2(join26(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
4073
4273
|
}
|
|
4074
4274
|
}
|
|
4075
4275
|
})(rawDir, "");
|
|
@@ -4082,7 +4282,7 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
4082
4282
|
if (resolvedPath === void 0) {
|
|
4083
4283
|
return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4084
4284
|
}
|
|
4085
|
-
if (!
|
|
4285
|
+
if (!existsSync8(join26(resolvedPath, ".git"))) {
|
|
4086
4286
|
return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
|
|
4087
4287
|
}
|
|
4088
4288
|
let timestamp;
|
|
@@ -4123,8 +4323,8 @@ function checkS3MountPerf(resolvedPath) {
|
|
|
4123
4323
|
return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
|
|
4124
4324
|
}
|
|
4125
4325
|
const mountPoint = fuse.mountPoint;
|
|
4126
|
-
const conceptsDir =
|
|
4127
|
-
if (!
|
|
4326
|
+
const conceptsDir = join26(resolvedPath, "concepts");
|
|
4327
|
+
if (!existsSync8(conceptsDir)) {
|
|
4128
4328
|
return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
|
|
4129
4329
|
}
|
|
4130
4330
|
const start = Date.now();
|
|
@@ -4242,8 +4442,8 @@ function checkWriteTest(resolvedPath) {
|
|
|
4242
4442
|
if (!fuse) {
|
|
4243
4443
|
return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
|
|
4244
4444
|
}
|
|
4245
|
-
const conceptsDir =
|
|
4246
|
-
if (!
|
|
4445
|
+
const conceptsDir = join26(resolvedPath, "concepts");
|
|
4446
|
+
if (!existsSync8(conceptsDir)) {
|
|
4247
4447
|
return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
|
|
4248
4448
|
}
|
|
4249
4449
|
const result = writeTest(conceptsDir);
|
|
@@ -4329,7 +4529,7 @@ function checkVfsCacheHealth(resolvedPath) {
|
|
|
4329
4529
|
}
|
|
4330
4530
|
function readVaultSyncConfig(home) {
|
|
4331
4531
|
try {
|
|
4332
|
-
const content = readFileSync7(
|
|
4532
|
+
const content = readFileSync7(join26(home, ".skillwiki", ".env"), "utf8");
|
|
4333
4533
|
let installed = false;
|
|
4334
4534
|
let role;
|
|
4335
4535
|
for (const line of content.split(/\r?\n/)) {
|
|
@@ -4363,12 +4563,12 @@ function vaultSyncChecks(input) {
|
|
|
4363
4563
|
];
|
|
4364
4564
|
}
|
|
4365
4565
|
const isMac = os === "darwin";
|
|
4366
|
-
const logDir = input.logDir ?? (isMac ?
|
|
4367
|
-
const shareDir = input.shareDir ?? (isMac ?
|
|
4368
|
-
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");
|
|
4369
4569
|
const snapshotPath = input.snapshotScriptPath ?? "/root/.hermes/scripts/wiki-snapshot-v3.sh";
|
|
4370
|
-
const pushScriptPath =
|
|
4371
|
-
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`);
|
|
4372
4572
|
let c2;
|
|
4373
4573
|
try {
|
|
4374
4574
|
if (isMac) {
|
|
@@ -4419,7 +4619,7 @@ function vaultSyncChecks(input) {
|
|
|
4419
4619
|
"Scheduler check failed \u2014 run vault-sync-install"
|
|
4420
4620
|
);
|
|
4421
4621
|
}
|
|
4422
|
-
const logFile =
|
|
4622
|
+
const logFile = join26(logDir, "wiki-push.log");
|
|
4423
4623
|
let c3;
|
|
4424
4624
|
try {
|
|
4425
4625
|
const logContent = readFileSync7(logFile, "utf8");
|
|
@@ -4478,7 +4678,7 @@ function vaultSyncChecks(input) {
|
|
|
4478
4678
|
}
|
|
4479
4679
|
}
|
|
4480
4680
|
} catch {
|
|
4481
|
-
c3 =
|
|
4681
|
+
c3 = existsSync8(logDir) ? check(
|
|
4482
4682
|
"warn",
|
|
4483
4683
|
"vault_sync_last_push_age",
|
|
4484
4684
|
"Vault sync last push recency",
|
|
@@ -4490,7 +4690,7 @@ function vaultSyncChecks(input) {
|
|
|
4490
4690
|
`Log directory not found at ${logDir}`
|
|
4491
4691
|
);
|
|
4492
4692
|
}
|
|
4493
|
-
const fetchLogFile =
|
|
4693
|
+
const fetchLogFile = join26(logDir, "wiki-fetch.log");
|
|
4494
4694
|
let cFetch;
|
|
4495
4695
|
try {
|
|
4496
4696
|
const logContent = readFileSync7(fetchLogFile, "utf8");
|
|
@@ -4537,7 +4737,7 @@ function vaultSyncChecks(input) {
|
|
|
4537
4737
|
}
|
|
4538
4738
|
let c4;
|
|
4539
4739
|
try {
|
|
4540
|
-
if (!
|
|
4740
|
+
if (!existsSync8(filterPath)) {
|
|
4541
4741
|
c4 = check(
|
|
4542
4742
|
"error",
|
|
4543
4743
|
"vault_sync_filter_present",
|
|
@@ -4586,7 +4786,7 @@ function vaultSyncChecks(input) {
|
|
|
4586
4786
|
);
|
|
4587
4787
|
} else {
|
|
4588
4788
|
try {
|
|
4589
|
-
if (!
|
|
4789
|
+
if (!existsSync8(snapshotPath)) {
|
|
4590
4790
|
c5 = check(
|
|
4591
4791
|
"error",
|
|
4592
4792
|
"vault_sync_snapshot_guard",
|
|
@@ -4632,9 +4832,9 @@ function findSkillMd(dir) {
|
|
|
4632
4832
|
}
|
|
4633
4833
|
for (const entry of entries) {
|
|
4634
4834
|
if (entry.isFile() && entry.name === "SKILL.md") {
|
|
4635
|
-
results.push(
|
|
4835
|
+
results.push(join26(dir, entry.name));
|
|
4636
4836
|
} else if (entry.isDirectory()) {
|
|
4637
|
-
results.push(...findSkillMd(
|
|
4837
|
+
results.push(...findSkillMd(join26(dir, entry.name)));
|
|
4638
4838
|
}
|
|
4639
4839
|
}
|
|
4640
4840
|
return results;
|
|
@@ -4648,7 +4848,7 @@ function findSkillNames(dir) {
|
|
|
4648
4848
|
return results;
|
|
4649
4849
|
}
|
|
4650
4850
|
for (const entry of entries) {
|
|
4651
|
-
if (entry.isDirectory() &&
|
|
4851
|
+
if (entry.isDirectory() && existsSync8(join26(dir, entry.name, "SKILL.md"))) {
|
|
4652
4852
|
results.push(entry.name);
|
|
4653
4853
|
}
|
|
4654
4854
|
}
|
|
@@ -4712,8 +4912,8 @@ async function runDoctor(input) {
|
|
|
4712
4912
|
}
|
|
4713
4913
|
|
|
4714
4914
|
// src/commands/archive.ts
|
|
4715
|
-
import { rename as
|
|
4716
|
-
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";
|
|
4717
4917
|
function countWikilinks(body, slug) {
|
|
4718
4918
|
const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4719
4919
|
const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
|
|
@@ -4741,7 +4941,7 @@ async function runArchive(input) {
|
|
|
4741
4941
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
4742
4942
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
4743
4943
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
4744
|
-
const archivePath =
|
|
4944
|
+
const archivePath = join27("_archive", relPath).replace(/\\/g, "/");
|
|
4745
4945
|
let cascade;
|
|
4746
4946
|
if (input.cascade) {
|
|
4747
4947
|
const wikilinkRefs = [];
|
|
@@ -4765,7 +4965,7 @@ async function runArchive(input) {
|
|
|
4765
4965
|
const indexRefs = [];
|
|
4766
4966
|
if (!isRaw) {
|
|
4767
4967
|
try {
|
|
4768
|
-
const idx = await
|
|
4968
|
+
const idx = await readFile19(join27(input.vault, "index.md"), "utf8");
|
|
4769
4969
|
idx.split("\n").forEach((line, i) => {
|
|
4770
4970
|
if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
|
|
4771
4971
|
});
|
|
@@ -4791,8 +4991,8 @@ async function runArchive(input) {
|
|
|
4791
4991
|
}
|
|
4792
4992
|
if (input.cascade && input.apply && cascade) {
|
|
4793
4993
|
for (const ref of cascade.source_array_refs) {
|
|
4794
|
-
const absPath =
|
|
4795
|
-
const text = await
|
|
4994
|
+
const absPath = join27(input.vault, ref.page);
|
|
4995
|
+
const text = await readFile19(absPath, "utf8");
|
|
4796
4996
|
const split = splitFrontmatter(text);
|
|
4797
4997
|
if (!split.ok) continue;
|
|
4798
4998
|
const before = split.data.rawFrontmatter;
|
|
@@ -4803,29 +5003,29 @@ async function runArchive(input) {
|
|
|
4803
5003
|
);
|
|
4804
5004
|
if (fmRewritten === before) continue;
|
|
4805
5005
|
if (!arraysEqual(ref.sources_after, ref.sources_before)) {
|
|
4806
|
-
await
|
|
5006
|
+
await writeFile10(absPath, `---
|
|
4807
5007
|
${fmRewritten}
|
|
4808
5008
|
---${split.data.body}`, "utf8");
|
|
4809
5009
|
}
|
|
4810
5010
|
}
|
|
4811
5011
|
}
|
|
4812
|
-
await mkdir8(dirname9(
|
|
5012
|
+
await mkdir8(dirname9(join27(input.vault, archivePath)), { recursive: true });
|
|
4813
5013
|
let indexUpdated = false;
|
|
4814
5014
|
if (!isRaw) {
|
|
4815
|
-
const indexPath =
|
|
5015
|
+
const indexPath = join27(input.vault, "index.md");
|
|
4816
5016
|
try {
|
|
4817
|
-
const idx = await
|
|
5017
|
+
const idx = await readFile19(indexPath, "utf8");
|
|
4818
5018
|
const originalLines = idx.split("\n");
|
|
4819
5019
|
const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
|
|
4820
5020
|
if (filtered.length !== originalLines.length) {
|
|
4821
|
-
await
|
|
5021
|
+
await writeFile10(indexPath, filtered.join("\n"), "utf8");
|
|
4822
5022
|
indexUpdated = true;
|
|
4823
5023
|
}
|
|
4824
5024
|
} catch (e) {
|
|
4825
5025
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
4826
5026
|
}
|
|
4827
5027
|
}
|
|
4828
|
-
await
|
|
5028
|
+
await rename7(join27(input.vault, relPath), join27(input.vault, archivePath));
|
|
4829
5029
|
appendLastOp(input.vault, {
|
|
4830
5030
|
operation: input.cascade ? "archive-cascade" : "archive",
|
|
4831
5031
|
summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
|
|
@@ -5212,14 +5412,14 @@ ${newBody}`;
|
|
|
5212
5412
|
// src/commands/update.ts
|
|
5213
5413
|
import { execSync as execSync3 } from "child_process";
|
|
5214
5414
|
import { readFileSync as readFileSync8 } from "fs";
|
|
5215
|
-
import { join as
|
|
5415
|
+
import { join as join28 } from "path";
|
|
5216
5416
|
function resolveGlobalSkillsRoot() {
|
|
5217
5417
|
try {
|
|
5218
5418
|
const globalRoot = execSync3("npm root -g", {
|
|
5219
5419
|
encoding: "utf8",
|
|
5220
5420
|
timeout: 5e3
|
|
5221
5421
|
}).trim();
|
|
5222
|
-
return
|
|
5422
|
+
return join28(globalRoot, "skillwiki", "skills");
|
|
5223
5423
|
} catch {
|
|
5224
5424
|
return null;
|
|
5225
5425
|
}
|
|
@@ -5245,7 +5445,7 @@ async function runUpdate(input) {
|
|
|
5245
5445
|
);
|
|
5246
5446
|
const currentVersion = pkg2.version;
|
|
5247
5447
|
const tag = input.distTag ?? "latest";
|
|
5248
|
-
const target =
|
|
5448
|
+
const target = join28(input.home, ".claude", "skills");
|
|
5249
5449
|
let latest;
|
|
5250
5450
|
try {
|
|
5251
5451
|
latest = execSync3(`npm view skillwiki@${tag} version`, {
|
|
@@ -5315,16 +5515,16 @@ async function runUpdate(input) {
|
|
|
5315
5515
|
|
|
5316
5516
|
// src/commands/self-update.ts
|
|
5317
5517
|
import { execSync as execSync4 } from "child_process";
|
|
5318
|
-
import { existsSync as
|
|
5319
|
-
import { join as
|
|
5518
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
5519
|
+
import { join as join29 } from "path";
|
|
5320
5520
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
5321
5521
|
async function runSelfUpdate(input) {
|
|
5322
5522
|
const currentVersion = JSON.parse(
|
|
5323
5523
|
readFileSync9(new URL("../../package.json", import.meta.url), "utf8")
|
|
5324
5524
|
).version;
|
|
5325
5525
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
5326
|
-
const localPkgPath =
|
|
5327
|
-
const hasLocalSource =
|
|
5526
|
+
const localPkgPath = join29(sourceRoot, "packages", "cli", "package.json");
|
|
5527
|
+
const hasLocalSource = existsSync9(localPkgPath);
|
|
5328
5528
|
if (input.check) {
|
|
5329
5529
|
let availableVersion = null;
|
|
5330
5530
|
let source;
|
|
@@ -5455,10 +5655,10 @@ async function runSelfUpdate(input) {
|
|
|
5455
5655
|
}
|
|
5456
5656
|
|
|
5457
5657
|
// src/commands/transcripts.ts
|
|
5458
|
-
import { readdir as readdir5, stat as
|
|
5459
|
-
import { join as
|
|
5658
|
+
import { readdir as readdir5, stat as stat7, readFile as readFile20 } from "fs/promises";
|
|
5659
|
+
import { join as join30 } from "path";
|
|
5460
5660
|
async function runTranscripts(input) {
|
|
5461
|
-
const dir =
|
|
5661
|
+
const dir = join30(input.vault, "raw", "transcripts");
|
|
5462
5662
|
let entries;
|
|
5463
5663
|
try {
|
|
5464
5664
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -5468,13 +5668,13 @@ async function runTranscripts(input) {
|
|
|
5468
5668
|
const transcripts = [];
|
|
5469
5669
|
for (const entry of entries) {
|
|
5470
5670
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
5471
|
-
const filePath =
|
|
5472
|
-
const content = await
|
|
5671
|
+
const filePath = join30(dir, entry.name);
|
|
5672
|
+
const content = await readFile20(filePath, "utf8");
|
|
5473
5673
|
const fm = extractFrontmatter(content);
|
|
5474
5674
|
if (!fm.ok) continue;
|
|
5475
5675
|
const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
|
|
5476
5676
|
if (input.since && ingested && ingested < input.since) continue;
|
|
5477
|
-
const s = await
|
|
5677
|
+
const s = await stat7(filePath);
|
|
5478
5678
|
transcripts.push({
|
|
5479
5679
|
file: `raw/transcripts/${entry.name}`,
|
|
5480
5680
|
ingested,
|
|
@@ -5486,12 +5686,12 @@ async function runTranscripts(input) {
|
|
|
5486
5686
|
}
|
|
5487
5687
|
|
|
5488
5688
|
// src/commands/project-index.ts
|
|
5489
|
-
import { readdir as readdir6, readFile as
|
|
5490
|
-
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";
|
|
5491
5691
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
5492
5692
|
async function runProjectIndex(input) {
|
|
5493
5693
|
const slug = input.slug;
|
|
5494
|
-
const projectDir =
|
|
5694
|
+
const projectDir = join31(input.vault, "projects", slug);
|
|
5495
5695
|
try {
|
|
5496
5696
|
await readdir6(projectDir);
|
|
5497
5697
|
} catch {
|
|
@@ -5502,15 +5702,15 @@ async function runProjectIndex(input) {
|
|
|
5502
5702
|
}
|
|
5503
5703
|
const wikilinkPattern = `[[${slug}]]`;
|
|
5504
5704
|
const entries = [];
|
|
5505
|
-
const compoundDir =
|
|
5705
|
+
const compoundDir = join31(input.vault, "projects", slug, "compound");
|
|
5506
5706
|
try {
|
|
5507
5707
|
const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
|
|
5508
5708
|
for (const entry of compoundFiles) {
|
|
5509
5709
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
5510
|
-
const filePath =
|
|
5710
|
+
const filePath = join31(compoundDir, entry.name);
|
|
5511
5711
|
let text;
|
|
5512
5712
|
try {
|
|
5513
|
-
text = await
|
|
5713
|
+
text = await readFile21(filePath, "utf8");
|
|
5514
5714
|
} catch {
|
|
5515
5715
|
continue;
|
|
5516
5716
|
}
|
|
@@ -5527,16 +5727,16 @@ async function runProjectIndex(input) {
|
|
|
5527
5727
|
for (const dir of LAYER2_DIRS) {
|
|
5528
5728
|
let files;
|
|
5529
5729
|
try {
|
|
5530
|
-
files = await readdir6(
|
|
5730
|
+
files = await readdir6(join31(input.vault, dir), { withFileTypes: true });
|
|
5531
5731
|
} catch {
|
|
5532
5732
|
continue;
|
|
5533
5733
|
}
|
|
5534
5734
|
for (const entry of files) {
|
|
5535
5735
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
5536
|
-
const filePath =
|
|
5736
|
+
const filePath = join31(input.vault, dir, entry.name);
|
|
5537
5737
|
let text;
|
|
5538
5738
|
try {
|
|
5539
|
-
text = await
|
|
5739
|
+
text = await readFile21(filePath, "utf8");
|
|
5540
5740
|
} catch {
|
|
5541
5741
|
continue;
|
|
5542
5742
|
}
|
|
@@ -5557,11 +5757,11 @@ async function runProjectIndex(input) {
|
|
|
5557
5757
|
const tb = typeOrder[b.type] ?? 99;
|
|
5558
5758
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
5559
5759
|
});
|
|
5560
|
-
const indexPath =
|
|
5760
|
+
const indexPath = join31(projectDir, "knowledge.md");
|
|
5561
5761
|
let existing = false;
|
|
5562
5762
|
let stale = false;
|
|
5563
5763
|
try {
|
|
5564
|
-
const existingText = await
|
|
5764
|
+
const existingText = await readFile21(indexPath, "utf8");
|
|
5565
5765
|
existing = true;
|
|
5566
5766
|
const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
|
|
5567
5767
|
const existingPages = new Set(existingEntries.map((l) => {
|
|
@@ -5602,7 +5802,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
|
|
|
5602
5802
|
if (input.apply) {
|
|
5603
5803
|
try {
|
|
5604
5804
|
await mkdir9(dirname10(indexPath), { recursive: true });
|
|
5605
|
-
await
|
|
5805
|
+
await writeFile11(indexPath, body, "utf8");
|
|
5606
5806
|
} catch (e) {
|
|
5607
5807
|
return {
|
|
5608
5808
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5630,10 +5830,10 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
5630
5830
|
}
|
|
5631
5831
|
|
|
5632
5832
|
// src/commands/compound.ts
|
|
5633
|
-
import { writeFile as
|
|
5634
|
-
import { join as
|
|
5635
|
-
import { existsSync as
|
|
5636
|
-
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";
|
|
5637
5837
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
5638
5838
|
var FIELD_RE = {
|
|
5639
5839
|
improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
|
|
@@ -5731,17 +5931,17 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
5731
5931
|
};
|
|
5732
5932
|
}
|
|
5733
5933
|
async function runCompound(input) {
|
|
5734
|
-
const logPath =
|
|
5934
|
+
const logPath = join32(input.vault, "log.md");
|
|
5735
5935
|
let logText;
|
|
5736
5936
|
try {
|
|
5737
|
-
logText = await
|
|
5937
|
+
logText = await readFile22(logPath, "utf8");
|
|
5738
5938
|
} catch {
|
|
5739
5939
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
5740
5940
|
}
|
|
5741
5941
|
const entries = parseRetroEntries(logText);
|
|
5742
5942
|
const promoted = [];
|
|
5743
5943
|
const skipped = [];
|
|
5744
|
-
const compoundDir =
|
|
5944
|
+
const compoundDir = join32(input.vault, "projects", input.project, "compound");
|
|
5745
5945
|
for (const entry of entries) {
|
|
5746
5946
|
const generalizeValue = entry.generalize.trim();
|
|
5747
5947
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -5749,8 +5949,8 @@ async function runCompound(input) {
|
|
|
5749
5949
|
continue;
|
|
5750
5950
|
}
|
|
5751
5951
|
const slug = slugify(entry.cycleName);
|
|
5752
|
-
const compoundPath =
|
|
5753
|
-
if (
|
|
5952
|
+
const compoundPath = join32(compoundDir, `${slug}.md`);
|
|
5953
|
+
if (existsSync10(compoundPath)) {
|
|
5754
5954
|
skipped.push(entry.date);
|
|
5755
5955
|
continue;
|
|
5756
5956
|
}
|
|
@@ -5788,10 +5988,10 @@ async function runCompound(input) {
|
|
|
5788
5988
|
].join("\n");
|
|
5789
5989
|
const content = frontmatter + "\n" + body;
|
|
5790
5990
|
if (!input.dryRun) {
|
|
5791
|
-
if (!
|
|
5991
|
+
if (!existsSync10(compoundDir)) {
|
|
5792
5992
|
await mkdir10(compoundDir, { recursive: true });
|
|
5793
5993
|
}
|
|
5794
|
-
await
|
|
5994
|
+
await writeFile12(compoundPath, content, "utf8");
|
|
5795
5995
|
}
|
|
5796
5996
|
promoted.push(`${slug}.md`);
|
|
5797
5997
|
}
|
|
@@ -5810,16 +6010,16 @@ async function runCompound(input) {
|
|
|
5810
6010
|
};
|
|
5811
6011
|
}
|
|
5812
6012
|
async function runCompoundDelete(input) {
|
|
5813
|
-
const projectDir =
|
|
5814
|
-
if (!
|
|
6013
|
+
const projectDir = join32(input.vault, "projects", input.project);
|
|
6014
|
+
if (!existsSync10(projectDir)) {
|
|
5815
6015
|
return {
|
|
5816
6016
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
5817
6017
|
result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
|
|
5818
6018
|
};
|
|
5819
6019
|
}
|
|
5820
6020
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
5821
|
-
const compoundPath =
|
|
5822
|
-
if (!
|
|
6021
|
+
const compoundPath = join32(projectDir, "compound", `${entryName}.md`);
|
|
6022
|
+
if (!existsSync10(compoundPath)) {
|
|
5823
6023
|
return {
|
|
5824
6024
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
5825
6025
|
result: err("FILE_NOT_FOUND", { path: compoundPath })
|
|
@@ -5852,8 +6052,8 @@ knowledge.md regenerated`
|
|
|
5852
6052
|
};
|
|
5853
6053
|
}
|
|
5854
6054
|
async function runCompoundList(input) {
|
|
5855
|
-
const compoundDir =
|
|
5856
|
-
if (!
|
|
6055
|
+
const compoundDir = join32(input.vault, "projects", input.project, "compound");
|
|
6056
|
+
if (!existsSync10(compoundDir)) {
|
|
5857
6057
|
return {
|
|
5858
6058
|
exitCode: ExitCode.OK,
|
|
5859
6059
|
result: ok({
|
|
@@ -5883,10 +6083,10 @@ could not read compound directory`
|
|
|
5883
6083
|
const entries = [];
|
|
5884
6084
|
for (const dirent of dirents) {
|
|
5885
6085
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
5886
|
-
const filePath =
|
|
6086
|
+
const filePath = join32(compoundDir, dirent.name);
|
|
5887
6087
|
let text;
|
|
5888
6088
|
try {
|
|
5889
|
-
text = await
|
|
6089
|
+
text = await readFile22(filePath, "utf8");
|
|
5890
6090
|
} catch {
|
|
5891
6091
|
continue;
|
|
5892
6092
|
}
|
|
@@ -5915,9 +6115,9 @@ no compound entries found`;
|
|
|
5915
6115
|
}
|
|
5916
6116
|
|
|
5917
6117
|
// src/commands/observe.ts
|
|
5918
|
-
import { mkdir as mkdir11, writeFile as
|
|
5919
|
-
import { existsSync as
|
|
5920
|
-
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";
|
|
5921
6121
|
import { createHash as createHash4 } from "crypto";
|
|
5922
6122
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
5923
6123
|
function slugify2(text) {
|
|
@@ -5940,13 +6140,13 @@ async function runObserve(input) {
|
|
|
5940
6140
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
5941
6141
|
};
|
|
5942
6142
|
}
|
|
5943
|
-
if (!
|
|
6143
|
+
if (!existsSync11(input.vault) || !statSync4(input.vault).isDirectory()) {
|
|
5944
6144
|
return {
|
|
5945
6145
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5946
6146
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
5947
6147
|
};
|
|
5948
6148
|
}
|
|
5949
|
-
const transcriptsDir =
|
|
6149
|
+
const transcriptsDir = join33(input.vault, "raw", "transcripts");
|
|
5950
6150
|
try {
|
|
5951
6151
|
await mkdir11(transcriptsDir, { recursive: true });
|
|
5952
6152
|
} catch {
|
|
@@ -5958,7 +6158,7 @@ async function runObserve(input) {
|
|
|
5958
6158
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5959
6159
|
const slug = slugify2(input.text);
|
|
5960
6160
|
const fileName = `${today}-observation-${slug}.md`;
|
|
5961
|
-
const filePath =
|
|
6161
|
+
const filePath = join33(transcriptsDir, fileName);
|
|
5962
6162
|
const body = `
|
|
5963
6163
|
${input.text.trim()}
|
|
5964
6164
|
`;
|
|
@@ -5976,7 +6176,7 @@ ${input.text.trim()}
|
|
|
5976
6176
|
frontmatterLines.push("---");
|
|
5977
6177
|
const content = frontmatterLines.join("\n") + body;
|
|
5978
6178
|
try {
|
|
5979
|
-
await
|
|
6179
|
+
await writeFile13(filePath, content, "utf8");
|
|
5980
6180
|
} catch (e) {
|
|
5981
6181
|
return {
|
|
5982
6182
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5998,8 +6198,8 @@ ${input.text.trim()}
|
|
|
5998
6198
|
}
|
|
5999
6199
|
|
|
6000
6200
|
// src/commands/ingest.ts
|
|
6001
|
-
import { readFile as
|
|
6002
|
-
import { join as
|
|
6201
|
+
import { readFile as readFile23, writeFile as writeFile14, mkdir as mkdir12 } from "fs/promises";
|
|
6202
|
+
import { join as join34 } from "path";
|
|
6003
6203
|
import { createHash as createHash5 } from "crypto";
|
|
6004
6204
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
6005
6205
|
var TYPE_DIR = {
|
|
@@ -6158,7 +6358,7 @@ async function runIngest(input) {
|
|
|
6158
6358
|
sourceContent = fetchResult.data.body;
|
|
6159
6359
|
} else {
|
|
6160
6360
|
try {
|
|
6161
|
-
sourceContent = await
|
|
6361
|
+
sourceContent = await readFile23(input.source, "utf8");
|
|
6162
6362
|
} catch {
|
|
6163
6363
|
return {
|
|
6164
6364
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -6173,8 +6373,8 @@ async function runIngest(input) {
|
|
|
6173
6373
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
6174
6374
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
6175
6375
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
6176
|
-
const rawAbsPath =
|
|
6177
|
-
const typedAbsPath =
|
|
6376
|
+
const rawAbsPath = join34(input.vault, rawRelPath);
|
|
6377
|
+
const typedAbsPath = join34(input.vault, typedRelPath);
|
|
6178
6378
|
const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
|
|
6179
6379
|
const typedContent = buildTypedContent(
|
|
6180
6380
|
input.title,
|
|
@@ -6237,8 +6437,8 @@ async function runIngest(input) {
|
|
|
6237
6437
|
};
|
|
6238
6438
|
}
|
|
6239
6439
|
try {
|
|
6240
|
-
await mkdir12(
|
|
6241
|
-
await
|
|
6440
|
+
await mkdir12(join34(input.vault, "raw", "articles"), { recursive: true });
|
|
6441
|
+
await writeFile14(rawAbsPath, rawContent, "utf8");
|
|
6242
6442
|
} catch (e) {
|
|
6243
6443
|
return {
|
|
6244
6444
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -6246,8 +6446,8 @@ async function runIngest(input) {
|
|
|
6246
6446
|
};
|
|
6247
6447
|
}
|
|
6248
6448
|
try {
|
|
6249
|
-
await mkdir12(
|
|
6250
|
-
await
|
|
6449
|
+
await mkdir12(join34(input.vault, typedDir), { recursive: true });
|
|
6450
|
+
await writeFile14(typedAbsPath, typedContent, "utf8");
|
|
6251
6451
|
} catch (e) {
|
|
6252
6452
|
return {
|
|
6253
6453
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -6425,12 +6625,12 @@ ${body}`;
|
|
|
6425
6625
|
}
|
|
6426
6626
|
|
|
6427
6627
|
// src/commands/sync.ts
|
|
6428
|
-
import { existsSync as
|
|
6429
|
-
import { join as
|
|
6628
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6629
|
+
import { join as join36 } from "path";
|
|
6430
6630
|
|
|
6431
6631
|
// src/utils/sync-lock.ts
|
|
6432
|
-
import { existsSync as
|
|
6433
|
-
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";
|
|
6434
6634
|
import { createHash as createHash6 } from "crypto";
|
|
6435
6635
|
function getSessionId() {
|
|
6436
6636
|
if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
|
|
@@ -6438,11 +6638,11 @@ function getSessionId() {
|
|
|
6438
6638
|
return process.pid.toString();
|
|
6439
6639
|
}
|
|
6440
6640
|
function lockPath(vault) {
|
|
6441
|
-
return
|
|
6641
|
+
return join35(vault, ".skillwiki", "sync.lock");
|
|
6442
6642
|
}
|
|
6443
6643
|
function readLock(vault) {
|
|
6444
6644
|
const path = lockPath(vault);
|
|
6445
|
-
if (!
|
|
6645
|
+
if (!existsSync12(path)) return null;
|
|
6446
6646
|
try {
|
|
6447
6647
|
const raw = readFileSync10(path, "utf8");
|
|
6448
6648
|
return JSON.parse(raw);
|
|
@@ -6457,9 +6657,9 @@ function isStale(lock, now) {
|
|
|
6457
6657
|
}
|
|
6458
6658
|
function acquireLock(vault, opts = {}) {
|
|
6459
6659
|
const path = lockPath(vault);
|
|
6460
|
-
const dir =
|
|
6461
|
-
if (!
|
|
6462
|
-
|
|
6660
|
+
const dir = join35(vault, ".skillwiki");
|
|
6661
|
+
if (!existsSync12(dir)) {
|
|
6662
|
+
mkdirSync4(dir, { recursive: true });
|
|
6463
6663
|
}
|
|
6464
6664
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
6465
6665
|
const summary = opts.summary ?? "skillwiki sync";
|
|
@@ -6478,7 +6678,7 @@ function acquireLock(vault, opts = {}) {
|
|
|
6478
6678
|
};
|
|
6479
6679
|
try {
|
|
6480
6680
|
const content = JSON.stringify(lock, null, 2) + "\n";
|
|
6481
|
-
|
|
6681
|
+
writeFileSync6(path, content, { flag: "wx" });
|
|
6482
6682
|
return { ok: true, lock };
|
|
6483
6683
|
} catch (e) {
|
|
6484
6684
|
const err3 = e;
|
|
@@ -6498,19 +6698,19 @@ function acquireLock(vault, opts = {}) {
|
|
|
6498
6698
|
function writeLockedFile(path, lock) {
|
|
6499
6699
|
const tmp = path + ".tmp";
|
|
6500
6700
|
const content = JSON.stringify(lock, null, 2) + "\n";
|
|
6501
|
-
|
|
6701
|
+
writeFileSync6(tmp, content);
|
|
6502
6702
|
renameSync(tmp, path);
|
|
6503
6703
|
}
|
|
6504
6704
|
function releaseLock(vault, opts = {}) {
|
|
6505
6705
|
const path = lockPath(vault);
|
|
6506
|
-
if (!
|
|
6706
|
+
if (!existsSync12(path)) {
|
|
6507
6707
|
return { released: false };
|
|
6508
6708
|
}
|
|
6509
6709
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
6510
6710
|
const existing = readLock(vault);
|
|
6511
6711
|
if (opts.force) {
|
|
6512
6712
|
try {
|
|
6513
|
-
|
|
6713
|
+
unlinkSync5(path);
|
|
6514
6714
|
const prior = existing && existing.session_id !== sessionId ? existing : void 0;
|
|
6515
6715
|
return { released: true, prior };
|
|
6516
6716
|
} catch {
|
|
@@ -6521,7 +6721,7 @@ function releaseLock(vault, opts = {}) {
|
|
|
6521
6721
|
return { released: false };
|
|
6522
6722
|
}
|
|
6523
6723
|
try {
|
|
6524
|
-
|
|
6724
|
+
unlinkSync5(path);
|
|
6525
6725
|
return { released: true };
|
|
6526
6726
|
} catch {
|
|
6527
6727
|
return { released: false };
|
|
@@ -6532,7 +6732,7 @@ function releaseLock(vault, opts = {}) {
|
|
|
6532
6732
|
function runSyncStatus(input) {
|
|
6533
6733
|
const vault = input.vault;
|
|
6534
6734
|
const includeStashes = input.includeStashes ?? false;
|
|
6535
|
-
if (!
|
|
6735
|
+
if (!existsSync13(join36(vault, ".git"))) {
|
|
6536
6736
|
return {
|
|
6537
6737
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6538
6738
|
result: ok({
|
|
@@ -6609,7 +6809,7 @@ function runSyncStatus(input) {
|
|
|
6609
6809
|
}
|
|
6610
6810
|
async function runSyncPush(input) {
|
|
6611
6811
|
const vault = input.vault;
|
|
6612
|
-
if (!
|
|
6812
|
+
if (!existsSync13(join36(vault, ".git"))) {
|
|
6613
6813
|
return {
|
|
6614
6814
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6615
6815
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -6713,7 +6913,7 @@ function enumerateStashes(vault) {
|
|
|
6713
6913
|
}
|
|
6714
6914
|
async function runSyncPull(input) {
|
|
6715
6915
|
const vault = input.vault;
|
|
6716
|
-
if (!
|
|
6916
|
+
if (!existsSync13(join36(vault, ".git"))) {
|
|
6717
6917
|
return {
|
|
6718
6918
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6719
6919
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -6881,7 +7081,7 @@ function runSyncPeers(input) {
|
|
|
6881
7081
|
}
|
|
6882
7082
|
function runSyncLock(input) {
|
|
6883
7083
|
const vault = input.vault;
|
|
6884
|
-
if (!
|
|
7084
|
+
if (!existsSync13(vault)) {
|
|
6885
7085
|
return {
|
|
6886
7086
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6887
7087
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
@@ -6916,7 +7116,7 @@ function runSyncLock(input) {
|
|
|
6916
7116
|
}
|
|
6917
7117
|
function runSyncUnlock(input) {
|
|
6918
7118
|
const vault = input.vault;
|
|
6919
|
-
if (!
|
|
7119
|
+
if (!existsSync13(vault)) {
|
|
6920
7120
|
return {
|
|
6921
7121
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6922
7122
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
@@ -6949,8 +7149,8 @@ function runSyncUnlock(input) {
|
|
|
6949
7149
|
}
|
|
6950
7150
|
|
|
6951
7151
|
// src/commands/backup.ts
|
|
6952
|
-
import { statSync as
|
|
6953
|
-
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";
|
|
6954
7154
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
6955
7155
|
|
|
6956
7156
|
// src/utils/s3-client.ts
|
|
@@ -6974,7 +7174,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
|
|
|
6974
7174
|
function* walkMarkdown(dir, base) {
|
|
6975
7175
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
6976
7176
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
6977
|
-
const full =
|
|
7177
|
+
const full = join37(dir, entry.name);
|
|
6978
7178
|
if (entry.isDirectory()) {
|
|
6979
7179
|
yield* walkMarkdown(full, base);
|
|
6980
7180
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -6997,8 +7197,8 @@ async function runBackupSync(input) {
|
|
|
6997
7197
|
let failed = 0;
|
|
6998
7198
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
6999
7199
|
for (const relPath of files) {
|
|
7000
|
-
const absPath =
|
|
7001
|
-
const localStat =
|
|
7200
|
+
const absPath = join37(input.vault, relPath);
|
|
7201
|
+
const localStat = statSync5(absPath);
|
|
7002
7202
|
let needsUpload = true;
|
|
7003
7203
|
try {
|
|
7004
7204
|
const head = await client.send(new HeadObjectCommand({ Bucket: input.bucket, Key: relPath }));
|
|
@@ -7073,9 +7273,9 @@ async function runBackupRestore(input) {
|
|
|
7073
7273
|
const objects = list.Contents ?? [];
|
|
7074
7274
|
for (const obj of objects) {
|
|
7075
7275
|
if (!obj.Key) continue;
|
|
7076
|
-
const localPath =
|
|
7276
|
+
const localPath = join37(target, obj.Key);
|
|
7077
7277
|
try {
|
|
7078
|
-
const localStat =
|
|
7278
|
+
const localStat = statSync5(localPath);
|
|
7079
7279
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
7080
7280
|
conflicts++;
|
|
7081
7281
|
continue;
|
|
@@ -7086,8 +7286,8 @@ async function runBackupRestore(input) {
|
|
|
7086
7286
|
const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
|
|
7087
7287
|
const body = await resp.Body?.transformToByteArray();
|
|
7088
7288
|
if (body) {
|
|
7089
|
-
|
|
7090
|
-
|
|
7289
|
+
mkdirSync5(dirname11(localPath), { recursive: true });
|
|
7290
|
+
writeFileSync7(localPath, Buffer.from(body));
|
|
7091
7291
|
downloaded++;
|
|
7092
7292
|
}
|
|
7093
7293
|
} catch {
|
|
@@ -7119,11 +7319,11 @@ async function runBackupRestore(input) {
|
|
|
7119
7319
|
}
|
|
7120
7320
|
|
|
7121
7321
|
// src/commands/status.ts
|
|
7122
|
-
import { existsSync as
|
|
7123
|
-
import { readFile as
|
|
7124
|
-
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";
|
|
7125
7325
|
async function runStatus(input) {
|
|
7126
|
-
if (!
|
|
7326
|
+
if (!existsSync14(input.vault)) {
|
|
7127
7327
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
7128
7328
|
}
|
|
7129
7329
|
const scan = await scanVault(input.vault);
|
|
@@ -7148,7 +7348,7 @@ async function runStatus(input) {
|
|
|
7148
7348
|
const compound = scan.data.compound.length;
|
|
7149
7349
|
let schemaVersion = "v1";
|
|
7150
7350
|
try {
|
|
7151
|
-
const schemaContent = await
|
|
7351
|
+
const schemaContent = await readFile24(join38(input.vault, "SCHEMA.md"), "utf8");
|
|
7152
7352
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
7153
7353
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
7154
7354
|
} catch {
|
|
@@ -7164,7 +7364,7 @@ async function runStatus(input) {
|
|
|
7164
7364
|
let maxTime = 0;
|
|
7165
7365
|
for (const page of allPages) {
|
|
7166
7366
|
try {
|
|
7167
|
-
const st =
|
|
7367
|
+
const st = statSync6(page.absPath);
|
|
7168
7368
|
if (st.mtimeMs > maxTime) {
|
|
7169
7369
|
maxTime = st.mtimeMs;
|
|
7170
7370
|
lastModified = st.mtime.toISOString();
|
|
@@ -7208,8 +7408,8 @@ async function runStatus(input) {
|
|
|
7208
7408
|
}
|
|
7209
7409
|
|
|
7210
7410
|
// src/commands/seed.ts
|
|
7211
|
-
import { mkdir as mkdir13, writeFile as
|
|
7212
|
-
import { join as
|
|
7411
|
+
import { mkdir as mkdir13, writeFile as writeFile15, stat as stat8 } from "fs/promises";
|
|
7412
|
+
import { join as join39 } from "path";
|
|
7213
7413
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7214
7414
|
var EXAMPLE_PAGES = {
|
|
7215
7415
|
"entities/example-project.md": `---
|
|
@@ -7278,30 +7478,30 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
7278
7478
|
`;
|
|
7279
7479
|
async function runSeed(input) {
|
|
7280
7480
|
try {
|
|
7281
|
-
await
|
|
7481
|
+
await stat8(join39(input.vault, "SCHEMA.md"));
|
|
7282
7482
|
} catch {
|
|
7283
7483
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
7284
7484
|
}
|
|
7285
7485
|
const created = [];
|
|
7286
7486
|
const skipped = [];
|
|
7287
7487
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
7288
|
-
const absPath =
|
|
7488
|
+
const absPath = join39(input.vault, relPath);
|
|
7289
7489
|
try {
|
|
7290
|
-
await
|
|
7490
|
+
await stat8(absPath);
|
|
7291
7491
|
skipped.push(relPath);
|
|
7292
7492
|
} catch {
|
|
7293
|
-
await mkdir13(
|
|
7294
|
-
await
|
|
7493
|
+
await mkdir13(join39(absPath, ".."), { recursive: true });
|
|
7494
|
+
await writeFile15(absPath, content, "utf8");
|
|
7295
7495
|
created.push(relPath);
|
|
7296
7496
|
}
|
|
7297
7497
|
}
|
|
7298
|
-
const rawPath =
|
|
7498
|
+
const rawPath = join39(input.vault, "raw", "articles", "example-source.md");
|
|
7299
7499
|
try {
|
|
7300
|
-
await
|
|
7500
|
+
await stat8(rawPath);
|
|
7301
7501
|
skipped.push("raw/articles/example-source.md");
|
|
7302
7502
|
} catch {
|
|
7303
|
-
await mkdir13(
|
|
7304
|
-
await
|
|
7503
|
+
await mkdir13(join39(rawPath, ".."), { recursive: true });
|
|
7504
|
+
await writeFile15(rawPath, EXAMPLE_RAW, "utf8");
|
|
7305
7505
|
created.push("raw/articles/example-source.md");
|
|
7306
7506
|
}
|
|
7307
7507
|
if (created.length > 0) {
|
|
@@ -7323,9 +7523,9 @@ async function runSeed(input) {
|
|
|
7323
7523
|
}
|
|
7324
7524
|
|
|
7325
7525
|
// src/commands/canvas.ts
|
|
7326
|
-
import { readFile as
|
|
7327
|
-
import { existsSync as
|
|
7328
|
-
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";
|
|
7329
7529
|
var NODE_WIDTH = 240;
|
|
7330
7530
|
var NODE_HEIGHT = 60;
|
|
7331
7531
|
var COLUMN_SPACING = 400;
|
|
@@ -7403,8 +7603,8 @@ function buildCanvasEdges(adjacency) {
|
|
|
7403
7603
|
return edges;
|
|
7404
7604
|
}
|
|
7405
7605
|
async function runCanvasGenerate(input) {
|
|
7406
|
-
const graphPath = input.graphPath ??
|
|
7407
|
-
if (!
|
|
7606
|
+
const graphPath = input.graphPath ?? join40(input.vault, ".skillwiki", "graph.json");
|
|
7607
|
+
if (!existsSync15(graphPath)) {
|
|
7408
7608
|
return {
|
|
7409
7609
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
7410
7610
|
result: err("FILE_NOT_FOUND", {
|
|
@@ -7415,7 +7615,7 @@ async function runCanvasGenerate(input) {
|
|
|
7415
7615
|
}
|
|
7416
7616
|
let raw;
|
|
7417
7617
|
try {
|
|
7418
|
-
raw = await
|
|
7618
|
+
raw = await readFile25(graphPath, "utf8");
|
|
7419
7619
|
} catch (e) {
|
|
7420
7620
|
return {
|
|
7421
7621
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -7441,9 +7641,9 @@ async function runCanvasGenerate(input) {
|
|
|
7441
7641
|
const nodes = buildCanvasNodes(paths);
|
|
7442
7642
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
7443
7643
|
const canvas = { nodes, edges };
|
|
7444
|
-
const outPath =
|
|
7644
|
+
const outPath = join40(input.vault, "vault-graph.canvas");
|
|
7445
7645
|
try {
|
|
7446
|
-
await
|
|
7646
|
+
await writeFile16(outPath, JSON.stringify(canvas, null, 2));
|
|
7447
7647
|
} catch (e) {
|
|
7448
7648
|
return {
|
|
7449
7649
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -7463,8 +7663,8 @@ written: ${outPath}`
|
|
|
7463
7663
|
}
|
|
7464
7664
|
|
|
7465
7665
|
// src/commands/query.ts
|
|
7466
|
-
import { readFile as
|
|
7467
|
-
import { join as
|
|
7666
|
+
import { readFile as readFile26, stat as stat9 } from "fs/promises";
|
|
7667
|
+
import { join as join41 } from "path";
|
|
7468
7668
|
var W_KEYWORD = 2;
|
|
7469
7669
|
var W_SOURCE_OVERLAP = 4;
|
|
7470
7670
|
var W_WIKILINK = 3;
|
|
@@ -7585,10 +7785,10 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
7585
7785
|
return score;
|
|
7586
7786
|
}
|
|
7587
7787
|
async function loadOrBuildGraph(vault) {
|
|
7588
|
-
const graphPath =
|
|
7788
|
+
const graphPath = join41(vault, ".skillwiki", "graph.json");
|
|
7589
7789
|
let needsBuild = false;
|
|
7590
7790
|
try {
|
|
7591
|
-
const fileStat = await
|
|
7791
|
+
const fileStat = await stat9(graphPath);
|
|
7592
7792
|
const ageHours = (Date.now() - fileStat.mtimeMs) / (1e3 * 60 * 60);
|
|
7593
7793
|
if (ageHours > 24) needsBuild = true;
|
|
7594
7794
|
} catch {
|
|
@@ -7599,7 +7799,7 @@ async function loadOrBuildGraph(vault) {
|
|
|
7599
7799
|
if (buildResult.exitCode !== 0) return null;
|
|
7600
7800
|
}
|
|
7601
7801
|
try {
|
|
7602
|
-
const raw = await
|
|
7802
|
+
const raw = await readFile26(graphPath, "utf8");
|
|
7603
7803
|
return JSON.parse(raw);
|
|
7604
7804
|
} catch {
|
|
7605
7805
|
return null;
|
|
@@ -7607,14 +7807,14 @@ async function loadOrBuildGraph(vault) {
|
|
|
7607
7807
|
}
|
|
7608
7808
|
|
|
7609
7809
|
// src/utils/auto-commit.ts
|
|
7610
|
-
import { existsSync as
|
|
7611
|
-
import { join as
|
|
7810
|
+
import { existsSync as existsSync16 } from "fs";
|
|
7811
|
+
import { join as join42 } from "path";
|
|
7612
7812
|
async function postCommit(vault, exitCode) {
|
|
7613
7813
|
if (exitCode !== 0) return;
|
|
7614
7814
|
const home = process.env.HOME ?? "";
|
|
7615
7815
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
7616
7816
|
if (dotenv["AUTO_COMMIT"] === "false") return;
|
|
7617
|
-
if (!
|
|
7817
|
+
if (!existsSync16(join42(vault, ".git"))) return;
|
|
7618
7818
|
const lastOps = readLastOp(vault);
|
|
7619
7819
|
if (lastOps.length === 0) return;
|
|
7620
7820
|
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
@@ -7665,7 +7865,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
7665
7865
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
7666
7866
|
});
|
|
7667
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) => {
|
|
7668
|
-
const out = opts.out ??
|
|
7868
|
+
const out = opts.out ?? join43(vault, ".skillwiki", "graph.json");
|
|
7669
7869
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
7670
7870
|
});
|
|
7671
7871
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -7791,7 +7991,12 @@ program.command("log-rotate [vault]").description("rotate or trim the vault log
|
|
|
7791
7991
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
7792
7992
|
else emit(await runLogRotate({ vault: v.vault, threshold: opts.threshold, apply: !!opts.apply }), v.vault);
|
|
7793
7993
|
});
|
|
7794
|
-
program.command("
|
|
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
|
+
});
|
|
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) => {
|
|
7795
8000
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
7796
8001
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
7797
8002
|
else emit(await runLint({
|