skillwiki 0.9.5 → 0.9.7
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 +218 -56
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2070,11 +2070,11 @@ async function runInit(input) {
|
|
|
2070
2070
|
].join("\n");
|
|
2071
2071
|
});
|
|
2072
2072
|
if (errTemplate) return errTemplate;
|
|
2073
|
-
const
|
|
2073
|
+
const err2 = await writeOrPreserve("log.md", async () => {
|
|
2074
2074
|
const tpl = await readFile7(join11(input.templates, "log.md"), "utf8");
|
|
2075
2075
|
return tpl.replace(/\{\{INIT_DATE\}\}/g, today).replace("{{DOMAIN}}", domain).replace("{{WIKI_LANG}}", canonicalLang);
|
|
2076
2076
|
});
|
|
2077
|
-
if (
|
|
2077
|
+
if (err2) return err2;
|
|
2078
2078
|
const skipEnv = !!input.noEnv || explicitTarget && !input.profile && swDotenvHadPath && !input.force;
|
|
2079
2079
|
let envWritten = "";
|
|
2080
2080
|
if (!skipEnv) {
|
|
@@ -2788,8 +2788,8 @@ async function acquireLogLock(vault, opts = {}) {
|
|
|
2788
2788
|
writeFileSync2(path, content, { flag: "wx" });
|
|
2789
2789
|
return { ok: true };
|
|
2790
2790
|
} catch (e) {
|
|
2791
|
-
const
|
|
2792
|
-
if (
|
|
2791
|
+
const err2 = e;
|
|
2792
|
+
if (err2.code !== "EEXIST") throw err2;
|
|
2793
2793
|
}
|
|
2794
2794
|
try {
|
|
2795
2795
|
const age = Date.now() - statSync2(path).mtimeMs;
|
|
@@ -2867,7 +2867,7 @@ ${content}
|
|
|
2867
2867
|
// src/commands/lint.ts
|
|
2868
2868
|
import { existsSync as existsSync6 } from "fs";
|
|
2869
2869
|
import { readFile as readFile17 } from "fs/promises";
|
|
2870
|
-
import { createHash as
|
|
2870
|
+
import { createHash as createHash5 } from "crypto";
|
|
2871
2871
|
import { join as join23 } from "path";
|
|
2872
2872
|
|
|
2873
2873
|
// src/commands/sparse-community.ts
|
|
@@ -2922,11 +2922,28 @@ ${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
|
|
|
2922
2922
|
}
|
|
2923
2923
|
|
|
2924
2924
|
// src/commands/dedup.ts
|
|
2925
|
-
import {
|
|
2926
|
-
import {
|
|
2925
|
+
import { createHash as createHash3 } from "crypto";
|
|
2926
|
+
import { execFile } from "child_process";
|
|
2927
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
2928
|
+
import { dirname as dirname7, join as join20, resolve as resolve4 } from "path";
|
|
2929
|
+
import { promisify } from "util";
|
|
2930
|
+
var execFileAsync = promisify(execFile);
|
|
2927
2931
|
async function runDedup(input) {
|
|
2932
|
+
if (input.canonicalPolicy && input.canonicalPolicy !== "scan-order" && input.canonicalPolicy !== "stable-path") {
|
|
2933
|
+
return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--canonical-policy must be stable-path or scan-order" }) };
|
|
2934
|
+
}
|
|
2935
|
+
if (input.remoteDelete && !input.remote) {
|
|
2936
|
+
return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--remote-delete requires --remote" }) };
|
|
2937
|
+
}
|
|
2938
|
+
if (input.remoteDelete && !input.apply && !input.manifestIn) {
|
|
2939
|
+
return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--remote-delete requires --apply or --manifest-in" }) };
|
|
2940
|
+
}
|
|
2928
2941
|
const scan = await scanVault(input.vault);
|
|
2929
2942
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
2943
|
+
const manifestFromFile = input.manifestIn ? readManifest(input.manifestIn) : null;
|
|
2944
|
+
if (manifestFromFile && !manifestFromFile.ok) {
|
|
2945
|
+
return { exitCode: ExitCode.INVALID_FRONTMATTER, result: manifestFromFile };
|
|
2946
|
+
}
|
|
2930
2947
|
const hashMap = /* @__PURE__ */ new Map();
|
|
2931
2948
|
let totalFiles = 0;
|
|
2932
2949
|
for (const raw of scan.data.raw) {
|
|
@@ -2939,18 +2956,42 @@ async function runDedup(input) {
|
|
|
2939
2956
|
if (existing) existing.push(raw.relPath);
|
|
2940
2957
|
else hashMap.set(sha, [raw.relPath]);
|
|
2941
2958
|
}
|
|
2942
|
-
const
|
|
2959
|
+
const canonicalPolicy = input.canonicalPolicy ?? "stable-path";
|
|
2960
|
+
const duplicates = [...hashMap.entries()].filter(([, files]) => files.length > 1).map(([sha256, files]) => ({ sha256, files: orderFiles(files, canonicalPolicy) }));
|
|
2943
2961
|
const rewired = [];
|
|
2944
2962
|
const removed = [];
|
|
2945
|
-
|
|
2963
|
+
const unsafe = [];
|
|
2964
|
+
const safeEntries = manifestFromFile?.ok ? manifestFromFile.data.entries : buildSafeEntries(input.vault, duplicates, unsafe);
|
|
2965
|
+
const manifest = safeEntries.length > 0 ? {
|
|
2966
|
+
version: 1,
|
|
2967
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2968
|
+
vault: resolve4(input.vault),
|
|
2969
|
+
entries: safeEntries
|
|
2970
|
+
} : void 0;
|
|
2971
|
+
const remote = await planAndMaybePruneRemote(input, safeEntries);
|
|
2972
|
+
if (!remote.ok) {
|
|
2973
|
+
return {
|
|
2974
|
+
exitCode: remote.error === "SYNC_PUSH_FAILED" ? ExitCode.SYNC_PUSH_FAILED : ExitCode.USAGE,
|
|
2975
|
+
result: remote
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
if (input.manifestOut && manifest) {
|
|
2979
|
+
try {
|
|
2980
|
+
mkdirSync3(dirname7(input.manifestOut), { recursive: true });
|
|
2981
|
+
writeFileSync3(input.manifestOut, `${JSON.stringify(manifest, null, 2)}
|
|
2982
|
+
`, "utf-8");
|
|
2983
|
+
} catch (e) {
|
|
2984
|
+
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { path: input.manifestOut, message: String(e) }) };
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
if (input.apply && safeEntries.length > 0) {
|
|
2946
2988
|
const replacements = /* @__PURE__ */ new Map();
|
|
2947
|
-
for (const
|
|
2948
|
-
const
|
|
2949
|
-
|
|
2950
|
-
replacements.set(group.files[i], canonical);
|
|
2989
|
+
for (const entry of safeEntries) {
|
|
2990
|
+
for (const duplicate of entry.duplicates) {
|
|
2991
|
+
replacements.set(duplicate, entry.canonical);
|
|
2951
2992
|
}
|
|
2952
2993
|
}
|
|
2953
|
-
for (const page of scan.data.
|
|
2994
|
+
for (const page of scan.data.allMarkdown.filter((p) => !p.relPath.startsWith("raw/"))) {
|
|
2954
2995
|
const text = readFileSync3(join20(input.vault, page.relPath), "utf-8");
|
|
2955
2996
|
let updated = text;
|
|
2956
2997
|
let changed = false;
|
|
@@ -2973,7 +3014,7 @@ async function runDedup(input) {
|
|
|
2973
3014
|
rewired.push(page.relPath);
|
|
2974
3015
|
}
|
|
2975
3016
|
}
|
|
2976
|
-
for (const
|
|
3017
|
+
for (const oldPath of replacements.keys()) {
|
|
2977
3018
|
const fullPath = join20(input.vault, oldPath);
|
|
2978
3019
|
try {
|
|
2979
3020
|
unlinkSync3(fullPath);
|
|
@@ -2999,19 +3040,131 @@ async function runDedup(input) {
|
|
|
2999
3040
|
hintLines.push(`rewired: ${rewired.length} pages`);
|
|
3000
3041
|
hintLines.push(`removed: ${removed.length} raw files`);
|
|
3001
3042
|
}
|
|
3043
|
+
if (unsafe.length > 0) hintLines.push(`unsafe: ${unsafe.length} groups skipped`);
|
|
3044
|
+
if (remote.data.plannedDeletes.length > 0) {
|
|
3045
|
+
hintLines.push(`remote planned deletes: ${remote.data.plannedDeletes.length}`);
|
|
3046
|
+
if (input.remoteDelete) hintLines.push(`remote deleted: ${remote.data.deleted.length}`);
|
|
3047
|
+
}
|
|
3002
3048
|
} else {
|
|
3003
3049
|
hintLines.push("0 duplicates");
|
|
3004
3050
|
}
|
|
3005
3051
|
return {
|
|
3006
3052
|
exitCode,
|
|
3007
|
-
result: ok({
|
|
3053
|
+
result: ok({
|
|
3054
|
+
scanned: totalFiles,
|
|
3055
|
+
duplicates,
|
|
3056
|
+
manifest,
|
|
3057
|
+
unsafe,
|
|
3058
|
+
remote: remote.data,
|
|
3059
|
+
rewired,
|
|
3060
|
+
removed,
|
|
3061
|
+
humanHint: hintLines.join("\n")
|
|
3062
|
+
})
|
|
3008
3063
|
};
|
|
3009
3064
|
}
|
|
3065
|
+
function orderFiles(files, policy) {
|
|
3066
|
+
if (policy === "scan-order") return [...files];
|
|
3067
|
+
return [...files].sort(compareStableRawPath);
|
|
3068
|
+
}
|
|
3069
|
+
function compareStableRawPath(a, b) {
|
|
3070
|
+
return rawPathScore(a) - rawPathScore(b) || a.localeCompare(b);
|
|
3071
|
+
}
|
|
3072
|
+
function rawPathScore(relPath) {
|
|
3073
|
+
const base = relPath.split("/").pop() ?? relPath;
|
|
3074
|
+
const stem = base.replace(/\.md$/i, "");
|
|
3075
|
+
let score = 0;
|
|
3076
|
+
if (relPath.startsWith("raw/articles/obsidian-import/")) score += 1e5;
|
|
3077
|
+
if (/\bdup(licate)?\b/i.test(stem)) score += 1e4;
|
|
3078
|
+
if (/(?:-[0-9]+|-[0-9a-f]{6,}|[0-9a-f]{8})$/i.test(stem)) score += 1e3;
|
|
3079
|
+
score += relPath.length;
|
|
3080
|
+
return score;
|
|
3081
|
+
}
|
|
3082
|
+
function buildSafeEntries(vault, duplicates, unsafe) {
|
|
3083
|
+
const entries = [];
|
|
3084
|
+
for (const group of duplicates) {
|
|
3085
|
+
const canonical = group.files[0];
|
|
3086
|
+
if (!canonical) {
|
|
3087
|
+
unsafe.push({ sha256: group.sha256, files: group.files, reason: "canonical_missing" });
|
|
3088
|
+
continue;
|
|
3089
|
+
}
|
|
3090
|
+
const bodyHashes = /* @__PURE__ */ new Map();
|
|
3091
|
+
for (const file of group.files) {
|
|
3092
|
+
const bodyHash2 = hashRawBody(vault, file);
|
|
3093
|
+
const existing = bodyHashes.get(bodyHash2);
|
|
3094
|
+
if (existing) existing.push(file);
|
|
3095
|
+
else bodyHashes.set(bodyHash2, [file]);
|
|
3096
|
+
}
|
|
3097
|
+
if (bodyHashes.size !== 1) {
|
|
3098
|
+
unsafe.push({ sha256: group.sha256, files: group.files, reason: "body_hash_mismatch" });
|
|
3099
|
+
continue;
|
|
3100
|
+
}
|
|
3101
|
+
const [bodyHash] = bodyHashes.keys();
|
|
3102
|
+
entries.push({
|
|
3103
|
+
sha256: group.sha256,
|
|
3104
|
+
canonical,
|
|
3105
|
+
duplicates: group.files.slice(1),
|
|
3106
|
+
bodyHash
|
|
3107
|
+
});
|
|
3108
|
+
}
|
|
3109
|
+
return entries;
|
|
3110
|
+
}
|
|
3111
|
+
function hashRawBody(vault, relPath) {
|
|
3112
|
+
const text = readFileSync3(join20(vault, relPath), "utf-8");
|
|
3113
|
+
const split = splitFrontmatter(text);
|
|
3114
|
+
const body = split.ok ? split.data.body : text;
|
|
3115
|
+
return createHash3("sha256").update(body).digest("hex");
|
|
3116
|
+
}
|
|
3117
|
+
function readManifest(path) {
|
|
3118
|
+
try {
|
|
3119
|
+
const parsed = JSON.parse(readFileSync3(path, "utf-8"));
|
|
3120
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
3121
|
+
return err("INVALID_FRONTMATTER", { message: "dedup manifest must have version 1 and entries[]" });
|
|
3122
|
+
}
|
|
3123
|
+
return ok(parsed);
|
|
3124
|
+
} catch (e) {
|
|
3125
|
+
return err("INVALID_FRONTMATTER", { path, message: String(e) });
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
async function planAndMaybePruneRemote(input, entries) {
|
|
3129
|
+
const remoteRoot = input.remote?.replace(/\/+$/, "");
|
|
3130
|
+
const plannedDeletes = remoteRoot ? entries.flatMap((entry) => entry.duplicates.map((path) => `${remoteRoot}/${path}`)) : [];
|
|
3131
|
+
const output = { plannedDeletes, deleted: [] };
|
|
3132
|
+
if (!input.remoteDelete) return ok(output);
|
|
3133
|
+
const maxDeletes = input.maxRemoteDeletes ?? 50;
|
|
3134
|
+
if (!Number.isInteger(maxDeletes) || maxDeletes < 1) {
|
|
3135
|
+
return err("USAGE", { message: "--max-remote-deletes must be a positive integer" });
|
|
3136
|
+
}
|
|
3137
|
+
if (plannedDeletes.length > maxDeletes) {
|
|
3138
|
+
return err("USAGE", { message: `remote delete cap exceeded: ${plannedDeletes.length} > ${maxDeletes}` });
|
|
3139
|
+
}
|
|
3140
|
+
const runner = input.rcloneRunner ?? defaultRcloneRunner;
|
|
3141
|
+
for (const path of plannedDeletes) {
|
|
3142
|
+
const result = await runner(["deletefile", path]);
|
|
3143
|
+
if (result.exitCode !== 0) {
|
|
3144
|
+
output.failed = { path, stderr: result.stderr };
|
|
3145
|
+
return err("SYNC_PUSH_FAILED", { path, stderr: result.stderr });
|
|
3146
|
+
}
|
|
3147
|
+
output.deleted.push(path);
|
|
3148
|
+
}
|
|
3149
|
+
return ok(output);
|
|
3150
|
+
}
|
|
3151
|
+
async function defaultRcloneRunner(args) {
|
|
3152
|
+
try {
|
|
3153
|
+
const result = await execFileAsync("rclone", args, { encoding: "utf-8" });
|
|
3154
|
+
return { exitCode: 0, stdout: result.stdout, stderr: result.stderr };
|
|
3155
|
+
} catch (e) {
|
|
3156
|
+
return {
|
|
3157
|
+
exitCode: typeof e?.code === "number" ? e.code : 1,
|
|
3158
|
+
stdout: typeof e?.stdout === "string" ? e.stdout : "",
|
|
3159
|
+
stderr: typeof e?.stderr === "string" ? e.stderr : String(e)
|
|
3160
|
+
};
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3010
3163
|
|
|
3011
3164
|
// src/utils/safe-write.ts
|
|
3012
3165
|
import { open, readFile as readFile15, rename as rename5, unlink as unlink2, writeFile as writeFile9 } from "fs/promises";
|
|
3013
3166
|
import { randomBytes } from "crypto";
|
|
3014
|
-
import { dirname as
|
|
3167
|
+
import { dirname as dirname8, basename, join as join21 } from "path";
|
|
3015
3168
|
var DEFAULT_MIN_BODY_RATIO = 0.5;
|
|
3016
3169
|
var DEFAULT_MIN_OLD_BODY_BYTES = 200;
|
|
3017
3170
|
function bodyBytes(text) {
|
|
@@ -3058,7 +3211,7 @@ async function safeWritePage(absPath, newContent, opts = {}) {
|
|
|
3058
3211
|
if (!isNew && oldContent === newContent) {
|
|
3059
3212
|
return ok({ isNew: false, oldBodyBytes, newBodyBytes, bodyRatio, guardSkippedSmall });
|
|
3060
3213
|
}
|
|
3061
|
-
const dir =
|
|
3214
|
+
const dir = dirname8(absPath);
|
|
3062
3215
|
const tmpName = `.${basename(absPath)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
3063
3216
|
const tmpPath = join21(dir, tmpName);
|
|
3064
3217
|
try {
|
|
@@ -3084,7 +3237,7 @@ async function safeWritePage(absPath, newContent, opts = {}) {
|
|
|
3084
3237
|
}
|
|
3085
3238
|
|
|
3086
3239
|
// src/commands/raw-body-dedup.ts
|
|
3087
|
-
import { createHash as
|
|
3240
|
+
import { createHash as createHash4 } from "crypto";
|
|
3088
3241
|
async function runRawBodyDedup(vault) {
|
|
3089
3242
|
const scan = await scanVault(vault);
|
|
3090
3243
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -3095,7 +3248,7 @@ async function runRawBodyDedup(vault) {
|
|
|
3095
3248
|
const split = splitFrontmatter(text);
|
|
3096
3249
|
if (!split.ok) continue;
|
|
3097
3250
|
totalFiles++;
|
|
3098
|
-
const bodyHash =
|
|
3251
|
+
const bodyHash = createHash4("sha256").update(split.data.body).digest("hex");
|
|
3099
3252
|
const fm = extractFrontmatter(text);
|
|
3100
3253
|
let fmSha256 = null;
|
|
3101
3254
|
if (fm.ok && typeof fm.data.sha256 === "string" && fm.data.sha256.length === 64) {
|
|
@@ -3126,7 +3279,7 @@ async function runRawBodyDedup(vault) {
|
|
|
3126
3279
|
// src/commands/path-too-long.ts
|
|
3127
3280
|
import { existsSync as existsSync5 } from "fs";
|
|
3128
3281
|
import { mkdir as mkdir8, readFile as readFile16, rename as rename6, unlink as unlink3 } from "fs/promises";
|
|
3129
|
-
import { dirname as
|
|
3282
|
+
import { dirname as dirname9, join as join22, posix, resolve as resolve5 } from "path";
|
|
3130
3283
|
var MAX_PATH_LENGTH = 240;
|
|
3131
3284
|
var WINDOWS_ABSOLUTE_PATH_LIMIT = 259;
|
|
3132
3285
|
async function runPathTooLong(input) {
|
|
@@ -3161,7 +3314,7 @@ async function fixPathTooLong(input) {
|
|
|
3161
3314
|
if (target.mode === "dedupe") {
|
|
3162
3315
|
await unlink3(join22(input.vault, violation.relPath));
|
|
3163
3316
|
} else {
|
|
3164
|
-
await mkdir8(
|
|
3317
|
+
await mkdir8(dirname9(join22(input.vault, target.relPath)), { recursive: true });
|
|
3165
3318
|
await rename6(join22(input.vault, violation.relPath), join22(input.vault, target.relPath));
|
|
3166
3319
|
}
|
|
3167
3320
|
fixed.push({ from: violation.relPath, to: target.relPath });
|
|
@@ -3213,7 +3366,7 @@ function findPathTooLongViolations(pages, maxLength) {
|
|
|
3213
3366
|
}
|
|
3214
3367
|
function maxFixPathLength(vault) {
|
|
3215
3368
|
if (process.platform !== "win32") return MAX_PATH_LENGTH;
|
|
3216
|
-
const root =
|
|
3369
|
+
const root = resolve5(vault);
|
|
3217
3370
|
const separatorBudget = root.endsWith("\\") || root.endsWith("/") ? 0 : 1;
|
|
3218
3371
|
const absoluteSafeRelLength = WINDOWS_ABSOLUTE_PATH_LIMIT - root.length - separatorBudget;
|
|
3219
3372
|
return Math.max(1, Math.min(MAX_PATH_LENGTH, absoluteSafeRelLength));
|
|
@@ -3332,7 +3485,7 @@ function buildCliSurface() {
|
|
|
3332
3485
|
program2.command("status").option("--wiki <name>");
|
|
3333
3486
|
program2.command("archive").option("--wiki <name>").option("--cascade").option("--apply");
|
|
3334
3487
|
program2.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
|
|
3335
|
-
program2.command("dedup").option("--apply").option("--wiki <name>");
|
|
3488
|
+
program2.command("dedup").option("--apply").option("--canonical-policy <policy>").option("--manifest-out <path>").option("--manifest-in <path>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>").option("--wiki <name>");
|
|
3336
3489
|
program2.command("migrate-citations").option("--dry-run").option("--wiki <name>");
|
|
3337
3490
|
program2.command("frontmatter-fix").option("--dry-run").option("--wiki <name>");
|
|
3338
3491
|
program2.command("update").option("--tag <tag>");
|
|
@@ -3584,7 +3737,7 @@ function recomputeRawSha256IfPresent(content) {
|
|
|
3584
3737
|
const split = splitFrontmatter(content);
|
|
3585
3738
|
if (!split.ok) return content;
|
|
3586
3739
|
if (!/^sha256:\s*[0-9a-f]{64}$/m.test(split.data.rawFrontmatter)) return content;
|
|
3587
|
-
const sha256 =
|
|
3740
|
+
const sha256 = createHash5("sha256").update(Buffer.from(split.data.body, "utf8")).digest("hex");
|
|
3588
3741
|
const rawFrontmatter = split.data.rawFrontmatter.replace(/^sha256:\s*[0-9a-f]{64}$/m, `sha256: ${sha256}`);
|
|
3589
3742
|
return `---
|
|
3590
3743
|
${rawFrontmatter}
|
|
@@ -4351,13 +4504,13 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
|
|
|
4351
4504
|
}
|
|
4352
4505
|
|
|
4353
4506
|
// src/commands/health.ts
|
|
4354
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
4355
|
-
import { dirname as
|
|
4507
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync8, renameSync, writeFileSync as writeFileSync6 } from "fs";
|
|
4508
|
+
import { dirname as dirname11, join as join30, resolve as resolve7 } from "path";
|
|
4356
4509
|
import { platform as platform3 } from "os";
|
|
4357
4510
|
|
|
4358
4511
|
// src/commands/doctor.ts
|
|
4359
4512
|
import { existsSync as existsSync11, lstatSync, readlinkSync, readdirSync as readdirSync2, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
|
|
4360
|
-
import { join as join29, resolve as
|
|
4513
|
+
import { join as join29, resolve as resolve6 } from "path";
|
|
4361
4514
|
import { execSync as execSync2 } from "child_process";
|
|
4362
4515
|
import { platform as platform2 } from "os";
|
|
4363
4516
|
|
|
@@ -4423,8 +4576,8 @@ async function runConfigPath(input) {
|
|
|
4423
4576
|
}
|
|
4424
4577
|
|
|
4425
4578
|
// src/utils/auto-update.ts
|
|
4426
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as
|
|
4427
|
-
import { join as join25, dirname as
|
|
4579
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
4580
|
+
import { join as join25, dirname as dirname10 } from "path";
|
|
4428
4581
|
import { spawn } from "child_process";
|
|
4429
4582
|
function cachePath(home) {
|
|
4430
4583
|
return join25(home, ".skillwiki", CACHE_FILENAME);
|
|
@@ -4446,7 +4599,7 @@ function readCache(home) {
|
|
|
4446
4599
|
}
|
|
4447
4600
|
function writeCache(home, cache) {
|
|
4448
4601
|
const p = cachePath(home);
|
|
4449
|
-
|
|
4602
|
+
mkdirSync4(dirname10(p), { recursive: true });
|
|
4450
4603
|
writeFileSync4(p, JSON.stringify(cache, null, 2));
|
|
4451
4604
|
}
|
|
4452
4605
|
function latestFromCache(home, currentVersion) {
|
|
@@ -5246,14 +5399,14 @@ function checkNodeVersion() {
|
|
|
5246
5399
|
function detectCliChannels(argv, home) {
|
|
5247
5400
|
const channels = [];
|
|
5248
5401
|
if (argv.length >= 2 && argv[1].endsWith("cli.js")) {
|
|
5249
|
-
const devPath =
|
|
5402
|
+
const devPath = resolve6(argv[1]);
|
|
5250
5403
|
channels.push({ name: "dev", path: devPath, isDevLink: true });
|
|
5251
5404
|
}
|
|
5252
5405
|
try {
|
|
5253
5406
|
const whichOut = execSync2("which skillwiki 2>/dev/null", { encoding: "utf8" }).trim();
|
|
5254
5407
|
if (whichOut) {
|
|
5255
5408
|
const isDev = isDevSymlink(whichOut);
|
|
5256
|
-
if (!channels.some((c) => c.path ===
|
|
5409
|
+
if (!channels.some((c) => c.path === resolve6(whichOut))) {
|
|
5257
5410
|
channels.push({ name: "npm", path: whichOut, isDevLink: isDev });
|
|
5258
5411
|
}
|
|
5259
5412
|
}
|
|
@@ -5276,7 +5429,7 @@ function isDevSymlink(binPath) {
|
|
|
5276
5429
|
try {
|
|
5277
5430
|
const st = lstatSync(binPath);
|
|
5278
5431
|
if (st.isSymbolicLink()) {
|
|
5279
|
-
const target =
|
|
5432
|
+
const target = resolve6(binPath, "..", readlinkSync(binPath));
|
|
5280
5433
|
return target.includes("packages/cli") || target.includes("packages\\cli");
|
|
5281
5434
|
}
|
|
5282
5435
|
} catch {
|
|
@@ -6695,7 +6848,7 @@ function buildHumanHint(report) {
|
|
|
6695
6848
|
return lines.join("\n");
|
|
6696
6849
|
}
|
|
6697
6850
|
function writeReport(out, report) {
|
|
6698
|
-
|
|
6851
|
+
mkdirSync5(dirname11(out), { recursive: true });
|
|
6699
6852
|
const tmp = `${out}.tmp-${process.pid}-${Date.now()}`;
|
|
6700
6853
|
writeFileSync6(tmp, JSON.stringify(ok(report), null, 2) + "\n", "utf8");
|
|
6701
6854
|
renameSync(tmp, out);
|
|
@@ -6797,7 +6950,7 @@ async function runHealth(input) {
|
|
|
6797
6950
|
if (input.out) {
|
|
6798
6951
|
baseReport.report_path = input.out;
|
|
6799
6952
|
}
|
|
6800
|
-
if (input.out &&
|
|
6953
|
+
if (input.out && resolve7(input.out).startsWith(resolve7(input.vault) + "/")) {
|
|
6801
6954
|
baseReport.warnings.push({
|
|
6802
6955
|
id: "report_inside_vault",
|
|
6803
6956
|
message: "health report was written inside the vault; this may create sync churn"
|
|
@@ -6831,7 +6984,7 @@ async function runHealth(input) {
|
|
|
6831
6984
|
|
|
6832
6985
|
// src/commands/archive.ts
|
|
6833
6986
|
import { rename as rename7, mkdir as mkdir9, readFile as readFile21, writeFile as writeFile10 } from "fs/promises";
|
|
6834
|
-
import { join as join31, dirname as
|
|
6987
|
+
import { join as join31, dirname as dirname12 } from "path";
|
|
6835
6988
|
function countWikilinks(body, slug) {
|
|
6836
6989
|
const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6837
6990
|
const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
|
|
@@ -6927,7 +7080,7 @@ ${fmRewritten}
|
|
|
6927
7080
|
}
|
|
6928
7081
|
}
|
|
6929
7082
|
}
|
|
6930
|
-
await mkdir9(
|
|
7083
|
+
await mkdir9(dirname12(join31(input.vault, archivePath)), { recursive: true });
|
|
6931
7084
|
let indexUpdated = false;
|
|
6932
7085
|
if (!isRaw) {
|
|
6933
7086
|
const indexPath = join31(input.vault, "index.md");
|
|
@@ -6966,7 +7119,7 @@ ${fmRewritten}
|
|
|
6966
7119
|
}
|
|
6967
7120
|
|
|
6968
7121
|
// src/commands/drift.ts
|
|
6969
|
-
import { createHash as
|
|
7122
|
+
import { createHash as createHash6 } from "crypto";
|
|
6970
7123
|
|
|
6971
7124
|
// src/utils/fetch.ts
|
|
6972
7125
|
async function controlledFetch(url, opts) {
|
|
@@ -7045,7 +7198,7 @@ async function runDrift(input) {
|
|
|
7045
7198
|
});
|
|
7046
7199
|
continue;
|
|
7047
7200
|
}
|
|
7048
|
-
const currentHash =
|
|
7201
|
+
const currentHash = createHash6("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
|
|
7049
7202
|
const identity = assessSourceIdentity({
|
|
7050
7203
|
rawPath: raw.relPath,
|
|
7051
7204
|
sourceUrl,
|
|
@@ -7648,7 +7801,7 @@ async function runTranscripts(input) {
|
|
|
7648
7801
|
|
|
7649
7802
|
// src/commands/project-index.ts
|
|
7650
7803
|
import { readdir as readdir6, readFile as readFile23, writeFile as writeFile11, mkdir as mkdir10 } from "fs/promises";
|
|
7651
|
-
import { join as join35, dirname as
|
|
7804
|
+
import { join as join35, dirname as dirname13 } from "path";
|
|
7652
7805
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
7653
7806
|
async function runProjectIndex(input) {
|
|
7654
7807
|
const slug = input.slug;
|
|
@@ -7762,7 +7915,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
|
|
|
7762
7915
|
}
|
|
7763
7916
|
if (input.apply) {
|
|
7764
7917
|
try {
|
|
7765
|
-
await mkdir10(
|
|
7918
|
+
await mkdir10(dirname13(indexPath), { recursive: true });
|
|
7766
7919
|
await writeFile11(indexPath, body, "utf8");
|
|
7767
7920
|
} catch (e) {
|
|
7768
7921
|
return {
|
|
@@ -8079,7 +8232,7 @@ no compound entries found`;
|
|
|
8079
8232
|
import { mkdir as mkdir12, writeFile as writeFile13 } from "fs/promises";
|
|
8080
8233
|
import { existsSync as existsSync15, statSync as statSync4 } from "fs";
|
|
8081
8234
|
import { join as join37 } from "path";
|
|
8082
|
-
import { createHash as
|
|
8235
|
+
import { createHash as createHash7 } from "crypto";
|
|
8083
8236
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
8084
8237
|
function slugify2(text) {
|
|
8085
8238
|
const words = text.trim().split(/\s+/).slice(0, 6).join("-").toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -8123,7 +8276,7 @@ async function runObserve(input) {
|
|
|
8123
8276
|
const body = `
|
|
8124
8277
|
${input.text.trim()}
|
|
8125
8278
|
`;
|
|
8126
|
-
const sha256 =
|
|
8279
|
+
const sha256 = createHash7("sha256").update(Buffer.from(body, "utf8")).digest("hex");
|
|
8127
8280
|
const frontmatterLines = [
|
|
8128
8281
|
"---",
|
|
8129
8282
|
"source_url:",
|
|
@@ -8522,7 +8675,7 @@ function dateFromPath(path) {
|
|
|
8522
8675
|
// src/commands/ingest.ts
|
|
8523
8676
|
import { readFile as readFile26, writeFile as writeFile15, mkdir as mkdir14 } from "fs/promises";
|
|
8524
8677
|
import { join as join39 } from "path";
|
|
8525
|
-
import { createHash as
|
|
8678
|
+
import { createHash as createHash8 } from "crypto";
|
|
8526
8679
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
8527
8680
|
var TYPE_DIR = {
|
|
8528
8681
|
entity: "entities",
|
|
@@ -8698,7 +8851,7 @@ async function runIngest(input) {
|
|
|
8698
8851
|
})
|
|
8699
8852
|
};
|
|
8700
8853
|
}
|
|
8701
|
-
const sha256 =
|
|
8854
|
+
const sha256 = createHash8("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
|
|
8702
8855
|
const today = todayIso();
|
|
8703
8856
|
const slug = slugify3(input.title);
|
|
8704
8857
|
const tags = input.tags && input.tags.length > 0 ? input.tags : [];
|
|
@@ -8980,9 +9133,9 @@ import { existsSync as existsSync17 } from "fs";
|
|
|
8980
9133
|
import { join as join41 } from "path";
|
|
8981
9134
|
|
|
8982
9135
|
// src/utils/sync-lock.ts
|
|
8983
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
9136
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync as renameSync2, unlinkSync as unlinkSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
8984
9137
|
import { join as join40 } from "path";
|
|
8985
|
-
import { createHash as
|
|
9138
|
+
import { createHash as createHash9 } from "crypto";
|
|
8986
9139
|
function getSessionId() {
|
|
8987
9140
|
if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
|
|
8988
9141
|
if (process.env.SKILLWIKI_SESSION_ID) return process.env.SKILLWIKI_SESSION_ID;
|
|
@@ -9010,7 +9163,7 @@ function acquireLock(vault, opts = {}) {
|
|
|
9010
9163
|
const path = lockPath(vault);
|
|
9011
9164
|
const dir = join40(vault, ".skillwiki");
|
|
9012
9165
|
if (!existsSync16(dir)) {
|
|
9013
|
-
|
|
9166
|
+
mkdirSync6(dir, { recursive: true });
|
|
9014
9167
|
}
|
|
9015
9168
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
9016
9169
|
const summary = opts.summary ?? "skillwiki sync";
|
|
@@ -9032,8 +9185,8 @@ function acquireLock(vault, opts = {}) {
|
|
|
9032
9185
|
writeFileSync7(path, content, { flag: "wx" });
|
|
9033
9186
|
return { ok: true, lock };
|
|
9034
9187
|
} catch (e) {
|
|
9035
|
-
const
|
|
9036
|
-
if (
|
|
9188
|
+
const err2 = e;
|
|
9189
|
+
if (err2.code !== "EEXIST") throw err2;
|
|
9037
9190
|
}
|
|
9038
9191
|
const existing = readLock(vault);
|
|
9039
9192
|
if (!existing) {
|
|
@@ -9524,8 +9677,8 @@ function runSyncUnlock(input) {
|
|
|
9524
9677
|
}
|
|
9525
9678
|
|
|
9526
9679
|
// src/commands/backup.ts
|
|
9527
|
-
import { statSync as statSync5, readdirSync as readdirSync3, readFileSync as readFileSync12, mkdirSync as
|
|
9528
|
-
import { join as join42, relative as relative4, dirname as
|
|
9680
|
+
import { statSync as statSync5, readdirSync as readdirSync3, readFileSync as readFileSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
9681
|
+
import { join as join42, relative as relative4, dirname as dirname14 } from "path";
|
|
9529
9682
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
9530
9683
|
|
|
9531
9684
|
// src/utils/s3-client.ts
|
|
@@ -9661,7 +9814,7 @@ async function runBackupRestore(input) {
|
|
|
9661
9814
|
const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
|
|
9662
9815
|
const body = await resp.Body?.transformToByteArray();
|
|
9663
9816
|
if (body) {
|
|
9664
|
-
|
|
9817
|
+
mkdirSync7(dirname14(localPath), { recursive: true });
|
|
9665
9818
|
writeFileSync8(localPath, Buffer.from(body));
|
|
9666
9819
|
downloaded++;
|
|
9667
9820
|
}
|
|
@@ -10448,10 +10601,19 @@ program.command("drift [vault]").description("detect content drift in raw source
|
|
|
10448
10601
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
10449
10602
|
else emit(await runDrift({ vault: v.vault, apply: opts.apply, newSince: opts.new }), v.vault);
|
|
10450
10603
|
});
|
|
10451
|
-
program.command("dedup [vault]").description("detect duplicate raw sources by sha256").option("--apply", "rewire citations and remove duplicate raw files", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
10604
|
+
program.command("dedup [vault]").description("detect duplicate raw sources by sha256").option("--apply", "rewire citations and remove duplicate raw files", false).option("--canonical-policy <policy>", "canonical policy: stable-path or scan-order", "stable-path").option("--manifest-out <path>", "write raw dedup delete manifest before applying").option("--manifest-in <path>", "read existing raw dedup delete manifest for remote pruning").option("--remote <remote>", "rclone remote root, for example seaweed-wiki:cloud/wiki").option("--remote-delete", "delete manifest duplicate paths from the remote", false).option("--max-remote-deletes <n>", "maximum remote object deletes allowed", "50").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
10452
10605
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
10453
10606
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
10454
|
-
else emit(await runDedup({
|
|
10607
|
+
else emit(await runDedup({
|
|
10608
|
+
vault: v.vault,
|
|
10609
|
+
apply: opts.apply,
|
|
10610
|
+
canonicalPolicy: opts.canonicalPolicy,
|
|
10611
|
+
manifestOut: opts.manifestOut,
|
|
10612
|
+
manifestIn: opts.manifestIn,
|
|
10613
|
+
remote: opts.remote,
|
|
10614
|
+
remoteDelete: !!opts.remoteDelete,
|
|
10615
|
+
maxRemoteDeletes: Number.parseInt(opts.maxRemoteDeletes, 10)
|
|
10616
|
+
}), v.vault);
|
|
10455
10617
|
});
|
|
10456
10618
|
program.command("migrate-citations [vault]").description("migrate ^[raw/...] markers to paragraph-end citations").option("--dry-run", "preview changes without writing", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
10457
10619
|
const v = await resolveVaultArg(vault, opts.wiki);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"skills": "./",
|
|
5
5
|
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 18 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
|
|
6
6
|
"author": {
|