skillwiki 0.9.30 → 0.9.32
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/{chunk-JENSKJP6.js → chunk-OPXMAS4O.js} +121 -43
- package/dist/cli.js +37 -5
- package/dist/skillwiki-mcp.js +1 -1
- 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/skills/proj-distill/SKILL.md +1 -0
- package/skills/proj-work/SKILL.md +1 -0
- package/skills/skills/proj-distill/SKILL.md +1 -0
- package/skills/skills/proj-work/SKILL.md +1 -0
- package/skills/skills/using-skillwiki/SKILL.md +9 -0
- package/skills/skills/wiki-archive/SKILL.md +3 -3
- package/skills/skills/wiki-crystallize/SKILL.md +2 -0
- package/skills/skills/wiki-ingest/SKILL.md +2 -0
- package/skills/using-skillwiki/SKILL.md +9 -0
- package/skills/wiki-archive/SKILL.md +3 -3
- package/skills/wiki-crystallize/SKILL.md +2 -0
- package/skills/wiki-ingest/SKILL.md +2 -0
|
@@ -1170,8 +1170,9 @@ var FRONTMATTER = /^---\n[\s\S]*?\n---\n?/;
|
|
|
1170
1170
|
function stripFences(body) {
|
|
1171
1171
|
return body.replace(FENCE2, "").replace(INLINE_CODE, "");
|
|
1172
1172
|
}
|
|
1173
|
+
var TILDE_FENCE = /~~~[\s\S]*?~~~/g;
|
|
1173
1174
|
function stripFencedBlocks(body) {
|
|
1174
|
-
return body.replace(FENCE2, "");
|
|
1175
|
+
return body.replace(FENCE2, "").replace(TILDE_FENCE, "");
|
|
1175
1176
|
}
|
|
1176
1177
|
function extractCitationMarkers(body) {
|
|
1177
1178
|
const stripped = stripFences(body);
|
|
@@ -2055,11 +2056,57 @@ ${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
|
|
|
2055
2056
|
|
|
2056
2057
|
// src/commands/dedup.ts
|
|
2057
2058
|
import { createHash as createHash2 } from "crypto";
|
|
2058
|
-
import { execFile } from "child_process";
|
|
2059
2059
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
2060
2060
|
import { dirname as dirname4, join as join12, resolve as resolve3 } from "path";
|
|
2061
|
+
|
|
2062
|
+
// src/utils/rclone.ts
|
|
2063
|
+
import { execFile } from "child_process";
|
|
2061
2064
|
import { promisify } from "util";
|
|
2062
2065
|
var execFileAsync = promisify(execFile);
|
|
2066
|
+
function normalizeRemoteRoot(remote) {
|
|
2067
|
+
return remote?.replace(/\/+$/, "");
|
|
2068
|
+
}
|
|
2069
|
+
function buildRemoteObjectPath(remoteRoot, relPath) {
|
|
2070
|
+
return remoteRoot ? `${remoteRoot}/${relPath}` : void 0;
|
|
2071
|
+
}
|
|
2072
|
+
function isValidRemoteDeleteCap(maxRemoteDeletes) {
|
|
2073
|
+
if (maxRemoteDeletes === void 0) return true;
|
|
2074
|
+
return Number.isInteger(maxRemoteDeletes) && maxRemoteDeletes > 0;
|
|
2075
|
+
}
|
|
2076
|
+
async function planAndMaybePruneRemoteObjects(plannedDeletes, input) {
|
|
2077
|
+
const output = { plannedDeletes, deleted: [] };
|
|
2078
|
+
if (!input.remoteDelete) return ok(output);
|
|
2079
|
+
const maxDeletes = input.maxRemoteDeletes ?? input.defaultMaxDeletes ?? 1;
|
|
2080
|
+
if (!Number.isInteger(maxDeletes) || maxDeletes < 1) {
|
|
2081
|
+
return err("USAGE", { message: "--max-remote-deletes must be a positive integer" });
|
|
2082
|
+
}
|
|
2083
|
+
if (plannedDeletes.length > maxDeletes) {
|
|
2084
|
+
return err("USAGE", { message: `remote delete cap exceeded: ${plannedDeletes.length} > ${maxDeletes}` });
|
|
2085
|
+
}
|
|
2086
|
+
const runner = input.rcloneRunner ?? defaultRcloneRunner;
|
|
2087
|
+
for (const path of plannedDeletes) {
|
|
2088
|
+
const result = await runner(["deletefile", path]);
|
|
2089
|
+
if (result.exitCode !== 0) {
|
|
2090
|
+
return err("SYNC_PUSH_FAILED", { path, stderr: result.stderr, deleted: output.deleted });
|
|
2091
|
+
}
|
|
2092
|
+
output.deleted.push(path);
|
|
2093
|
+
}
|
|
2094
|
+
return ok(output);
|
|
2095
|
+
}
|
|
2096
|
+
async function defaultRcloneRunner(args) {
|
|
2097
|
+
try {
|
|
2098
|
+
const result = await execFileAsync("rclone", args, { encoding: "utf-8" });
|
|
2099
|
+
return { exitCode: 0, stdout: result.stdout, stderr: result.stderr };
|
|
2100
|
+
} catch (e) {
|
|
2101
|
+
return {
|
|
2102
|
+
exitCode: typeof e?.code === "number" ? e.code : 1,
|
|
2103
|
+
stdout: typeof e?.stdout === "string" ? e.stdout : "",
|
|
2104
|
+
stderr: typeof e?.stderr === "string" ? e.stderr : String(e)
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// src/commands/dedup.ts
|
|
2063
2110
|
async function runDedup(input) {
|
|
2064
2111
|
if (input.canonicalPolicy && input.canonicalPolicy !== "scan-order" && input.canonicalPolicy !== "stable-path") {
|
|
2065
2112
|
return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--canonical-policy must be stable-path or scan-order" }) };
|
|
@@ -2258,39 +2305,9 @@ function readManifest(path) {
|
|
|
2258
2305
|
}
|
|
2259
2306
|
}
|
|
2260
2307
|
async function planAndMaybePruneRemote(input, entries) {
|
|
2261
|
-
const remoteRoot = input.remote
|
|
2308
|
+
const remoteRoot = normalizeRemoteRoot(input.remote);
|
|
2262
2309
|
const plannedDeletes = remoteRoot ? entries.flatMap((entry) => entry.duplicates.map((path) => `${remoteRoot}/${path}`)) : [];
|
|
2263
|
-
|
|
2264
|
-
if (!input.remoteDelete) return ok(output);
|
|
2265
|
-
const maxDeletes = input.maxRemoteDeletes ?? 50;
|
|
2266
|
-
if (!Number.isInteger(maxDeletes) || maxDeletes < 1) {
|
|
2267
|
-
return err("USAGE", { message: "--max-remote-deletes must be a positive integer" });
|
|
2268
|
-
}
|
|
2269
|
-
if (plannedDeletes.length > maxDeletes) {
|
|
2270
|
-
return err("USAGE", { message: `remote delete cap exceeded: ${plannedDeletes.length} > ${maxDeletes}` });
|
|
2271
|
-
}
|
|
2272
|
-
const runner = input.rcloneRunner ?? defaultRcloneRunner;
|
|
2273
|
-
for (const path of plannedDeletes) {
|
|
2274
|
-
const result = await runner(["deletefile", path]);
|
|
2275
|
-
if (result.exitCode !== 0) {
|
|
2276
|
-
output.failed = { path, stderr: result.stderr };
|
|
2277
|
-
return err("SYNC_PUSH_FAILED", { path, stderr: result.stderr });
|
|
2278
|
-
}
|
|
2279
|
-
output.deleted.push(path);
|
|
2280
|
-
}
|
|
2281
|
-
return ok(output);
|
|
2282
|
-
}
|
|
2283
|
-
async function defaultRcloneRunner(args) {
|
|
2284
|
-
try {
|
|
2285
|
-
const result = await execFileAsync("rclone", args, { encoding: "utf-8" });
|
|
2286
|
-
return { exitCode: 0, stdout: result.stdout, stderr: result.stderr };
|
|
2287
|
-
} catch (e) {
|
|
2288
|
-
return {
|
|
2289
|
-
exitCode: typeof e?.code === "number" ? e.code : 1,
|
|
2290
|
-
stdout: typeof e?.stdout === "string" ? e.stdout : "",
|
|
2291
|
-
stderr: typeof e?.stderr === "string" ? e.stderr : String(e)
|
|
2292
|
-
};
|
|
2293
|
-
}
|
|
2310
|
+
return planAndMaybePruneRemoteObjects(plannedDeletes, { ...input, defaultMaxDeletes: 50 });
|
|
2294
2311
|
}
|
|
2295
2312
|
|
|
2296
2313
|
// src/utils/safe-write.ts
|
|
@@ -2710,7 +2727,7 @@ function buildCliSurface() {
|
|
|
2710
2727
|
program.command("health").option("--wiki <name>").option("--sync <mode>").option("--no-fail").option("--out <path>").option("--examples <n>");
|
|
2711
2728
|
program.command("doctor");
|
|
2712
2729
|
program.command("status").option("--wiki <name>");
|
|
2713
|
-
program.command("archive").option("--wiki <name>").option("--cascade").option("--apply");
|
|
2730
|
+
program.command("archive").option("--wiki <name>").option("--cascade").option("--apply").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>");
|
|
2714
2731
|
program.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
|
|
2715
2732
|
program.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>");
|
|
2716
2733
|
program.command("migrate-citations").option("--dry-run").option("--wiki <name>");
|
|
@@ -2922,6 +2939,23 @@ function hasDuplicateFrontmatter(body) {
|
|
|
2922
2939
|
}
|
|
2923
2940
|
return false;
|
|
2924
2941
|
}
|
|
2942
|
+
var CANONICAL_LOCAL_SOURCE_LABEL = /^\s*(?:>\s*)?(?:[-*+]\s*)?Source (?:file|inspected):/i;
|
|
2943
|
+
var LOCAL_ABSOLUTE_SOURCE_REF = /(?:file:\/\/(?:\/)?(?:Users|home)\/|\/(?:Users|home)\/)/;
|
|
2944
|
+
function hasCanonicalLocalSourceAssertion(body) {
|
|
2945
|
+
const visibleBody = stripFencedBlocks(body);
|
|
2946
|
+
return visibleBody.split(/\r?\n/).some(
|
|
2947
|
+
(line) => CANONICAL_LOCAL_SOURCE_LABEL.test(line) && LOCAL_ABSOLUTE_SOURCE_REF.test(line)
|
|
2948
|
+
);
|
|
2949
|
+
}
|
|
2950
|
+
function shouldCheckCanonicalLocalSourceAssertion(page) {
|
|
2951
|
+
if (page.relPath.startsWith("raw/transcripts/")) return false;
|
|
2952
|
+
if (/^projects\/[^/]+\/work\/[^/]+\/log\.md$/.test(page.relPath)) return false;
|
|
2953
|
+
if (page.relPath.startsWith("raw/")) return true;
|
|
2954
|
+
if (/^(entities|concepts|comparisons|queries|meta)\//.test(page.relPath)) return true;
|
|
2955
|
+
if (/^projects\/[^/]+\/compound\//.test(page.relPath)) return true;
|
|
2956
|
+
if (/^projects\/[^/]+\/work\/[^/]+\/(spec|plan)\.md$/.test(page.relPath)) return true;
|
|
2957
|
+
return false;
|
|
2958
|
+
}
|
|
2925
2959
|
function extractSourceEntries(rawFm) {
|
|
2926
2960
|
const lines = rawFm.split(/\r?\n/);
|
|
2927
2961
|
const sourcesLineIdx = lines.findIndex((l) => /^sources:/.test(l));
|
|
@@ -3196,14 +3230,18 @@ async function runLint(input) {
|
|
|
3196
3230
|
if (subDirDupes.length > 0) {
|
|
3197
3231
|
buckets.raw_subdirectory_duplicate = subDirDupes;
|
|
3198
3232
|
}
|
|
3199
|
-
const fileSourceUrlFlags =
|
|
3233
|
+
const fileSourceUrlFlags = /* @__PURE__ */ new Set();
|
|
3234
|
+
const fileSourceUrlFrontmatterFlags = /* @__PURE__ */ new Set();
|
|
3200
3235
|
const rawIdentityConflicts = [];
|
|
3236
|
+
const rawPageBodyByPath = /* @__PURE__ */ new Map();
|
|
3201
3237
|
for (const raw of scan.data.raw) {
|
|
3202
3238
|
const text = await readPage(raw);
|
|
3203
3239
|
const split = splitFrontmatter(text);
|
|
3204
3240
|
if (!split.ok) continue;
|
|
3241
|
+
rawPageBodyByPath.set(raw.relPath, split.data.body);
|
|
3205
3242
|
if (/^source_url:\s*file:\/\//m.test(split.data.rawFrontmatter)) {
|
|
3206
|
-
fileSourceUrlFlags.
|
|
3243
|
+
fileSourceUrlFlags.add(raw.relPath);
|
|
3244
|
+
fileSourceUrlFrontmatterFlags.add(raw.relPath);
|
|
3207
3245
|
}
|
|
3208
3246
|
const sourceUrl = split.data.rawFrontmatter.match(/^source_url:\s*(.+)$/m)?.[1]?.trim().replace(/^["']|["']$/g, "") ?? "";
|
|
3209
3247
|
const assessment = assessSourceIdentity({
|
|
@@ -3222,7 +3260,26 @@ async function runLint(input) {
|
|
|
3222
3260
|
});
|
|
3223
3261
|
}
|
|
3224
3262
|
}
|
|
3225
|
-
|
|
3263
|
+
const canonicalSourcePages = [
|
|
3264
|
+
...scan.data.raw,
|
|
3265
|
+
...scan.data.typedKnowledge,
|
|
3266
|
+
...scan.data.compound,
|
|
3267
|
+
...scan.data.workItems
|
|
3268
|
+
];
|
|
3269
|
+
for (const page of canonicalSourcePages) {
|
|
3270
|
+
if (!shouldCheckCanonicalLocalSourceAssertion(page)) continue;
|
|
3271
|
+
let body = rawPageBodyByPath.get(page.relPath);
|
|
3272
|
+
if (body === void 0) {
|
|
3273
|
+
const text = await readPage(page);
|
|
3274
|
+
const split = splitFrontmatter(text);
|
|
3275
|
+
if (!split.ok) continue;
|
|
3276
|
+
body = split.data.body;
|
|
3277
|
+
}
|
|
3278
|
+
if (hasCanonicalLocalSourceAssertion(body)) {
|
|
3279
|
+
fileSourceUrlFlags.add(page.relPath);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
if (fileSourceUrlFlags.size > 0) buckets.file_source_url = [...fileSourceUrlFlags];
|
|
3226
3283
|
if (rawIdentityConflicts.length > 0) buckets.raw_source_identity_conflict = rawIdentityConflicts;
|
|
3227
3284
|
const legacyPages = [];
|
|
3228
3285
|
const orphanedPages = [];
|
|
@@ -3697,9 +3754,9 @@ ${newBody}`;
|
|
|
3697
3754
|
else delete buckets.wikilink_citation;
|
|
3698
3755
|
}
|
|
3699
3756
|
}
|
|
3700
|
-
if (shouldFix("file_source_url") &&
|
|
3757
|
+
if (shouldFix("file_source_url") && fileSourceUrlFrontmatterFlags.size > 0) {
|
|
3701
3758
|
const FILE_FIXED = [];
|
|
3702
|
-
for (const relPath of
|
|
3759
|
+
for (const relPath of fileSourceUrlFrontmatterFlags) {
|
|
3703
3760
|
try {
|
|
3704
3761
|
const absPath = `${input.vault}/${relPath}`;
|
|
3705
3762
|
const raw = await readFile12(absPath, "utf8");
|
|
@@ -3730,9 +3787,26 @@ ${newBody}`;
|
|
|
3730
3787
|
}
|
|
3731
3788
|
fixed.push(...FILE_FIXED);
|
|
3732
3789
|
if (FILE_FIXED.length > 0) {
|
|
3733
|
-
const
|
|
3734
|
-
const
|
|
3735
|
-
|
|
3790
|
+
const remaining = new Set(fileSourceUrlFlags);
|
|
3791
|
+
for (const relPath of FILE_FIXED) {
|
|
3792
|
+
try {
|
|
3793
|
+
const page = scan.data.allMarkdown.find((p) => p.relPath === relPath);
|
|
3794
|
+
if (!page) {
|
|
3795
|
+
remaining.delete(relPath);
|
|
3796
|
+
continue;
|
|
3797
|
+
}
|
|
3798
|
+
const text = await readPage(page);
|
|
3799
|
+
const split = splitFrontmatter(text);
|
|
3800
|
+
if (!split.ok) continue;
|
|
3801
|
+
const stillHasFileSourceUrl = /^source_url:\s*file:\/\//m.test(split.data.rawFrontmatter);
|
|
3802
|
+
const stillHasCanonicalBodyAssertion = shouldCheckCanonicalLocalSourceAssertion(page) && hasCanonicalLocalSourceAssertion(split.data.body);
|
|
3803
|
+
if (!stillHasFileSourceUrl && !stillHasCanonicalBodyAssertion) {
|
|
3804
|
+
remaining.delete(relPath);
|
|
3805
|
+
}
|
|
3806
|
+
} catch {
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
if (remaining.size > 0) buckets.file_source_url = [...remaining];
|
|
3736
3810
|
else delete buckets.file_source_url;
|
|
3737
3811
|
}
|
|
3738
3812
|
}
|
|
@@ -8805,6 +8879,10 @@ export {
|
|
|
8805
8879
|
runLogRotate,
|
|
8806
8880
|
runTopicMapCheck,
|
|
8807
8881
|
runIndexLinkFormat,
|
|
8882
|
+
normalizeRemoteRoot,
|
|
8883
|
+
buildRemoteObjectPath,
|
|
8884
|
+
isValidRemoteDeleteCap,
|
|
8885
|
+
planAndMaybePruneRemoteObjects,
|
|
8808
8886
|
runDedup,
|
|
8809
8887
|
safeWritePage,
|
|
8810
8888
|
runFrontmatterFix,
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
TypedKnowledgeSchema,
|
|
7
7
|
appendLastOp,
|
|
8
8
|
assessSourceIdentity,
|
|
9
|
+
buildRemoteObjectPath,
|
|
9
10
|
clearLastOp,
|
|
10
11
|
configPath,
|
|
11
12
|
detectSchema,
|
|
@@ -18,10 +19,13 @@ import {
|
|
|
18
19
|
fixPathTooLong,
|
|
19
20
|
isBlockedHost,
|
|
20
21
|
isFailedRunStatus,
|
|
22
|
+
isValidRemoteDeleteCap,
|
|
21
23
|
loadFleetManifest,
|
|
24
|
+
normalizeRemoteRoot,
|
|
22
25
|
ok,
|
|
23
26
|
parseDotenvFile,
|
|
24
27
|
parseDotenvText,
|
|
28
|
+
planAndMaybePruneRemoteObjects,
|
|
25
29
|
profileKey,
|
|
26
30
|
readCliPackageJson,
|
|
27
31
|
readLastOp,
|
|
@@ -69,7 +73,7 @@ import {
|
|
|
69
73
|
triggerAutoUpdate,
|
|
70
74
|
writeCache,
|
|
71
75
|
writeDotenv
|
|
72
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-OPXMAS4O.js";
|
|
73
77
|
import {
|
|
74
78
|
normalizeDistTag
|
|
75
79
|
} from "./chunk-E6UWZ3S3.js";
|
|
@@ -1339,6 +1343,12 @@ function arraysEqual(a, b) {
|
|
|
1339
1343
|
return true;
|
|
1340
1344
|
}
|
|
1341
1345
|
async function runArchive(input) {
|
|
1346
|
+
if (input.remoteDelete && !input.remote) {
|
|
1347
|
+
return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--remote-delete requires --remote" }) };
|
|
1348
|
+
}
|
|
1349
|
+
if (input.remoteDelete && !isValidRemoteDeleteCap(input.maxRemoteDeletes)) {
|
|
1350
|
+
return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--max-remote-deletes must be a positive integer" }) };
|
|
1351
|
+
}
|
|
1342
1352
|
const scan = await scanVault(input.vault);
|
|
1343
1353
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
1344
1354
|
const lookup = (pages) => {
|
|
@@ -1355,6 +1365,8 @@ async function runArchive(input) {
|
|
|
1355
1365
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
1356
1366
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
1357
1367
|
const archivePath = join10("_archive", relPath).replace(/\\/g, "/");
|
|
1368
|
+
const remoteRoot = normalizeRemoteRoot(input.remote);
|
|
1369
|
+
const remoteObjectPath = buildRemoteObjectPath(remoteRoot, relPath);
|
|
1358
1370
|
let cascade;
|
|
1359
1371
|
if (input.cascade) {
|
|
1360
1372
|
const wikilinkRefs = [];
|
|
@@ -1398,7 +1410,8 @@ async function runArchive(input) {
|
|
|
1398
1410
|
index_updated: false,
|
|
1399
1411
|
applied: false,
|
|
1400
1412
|
cascade,
|
|
1401
|
-
|
|
1413
|
+
...remoteObjectPath ? { remote: { plannedDeletes: [remoteObjectPath], deleted: [] } } : {},
|
|
1414
|
+
humanHint: summary + (remoteObjectPath ? ` (remote planned ${input.remoteDelete ? "delete" : "preview"}: ${remoteObjectPath})` : "")
|
|
1402
1415
|
})
|
|
1403
1416
|
};
|
|
1404
1417
|
}
|
|
@@ -1445,8 +1458,18 @@ ${fmRewritten}
|
|
|
1445
1458
|
files: [relPath],
|
|
1446
1459
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1447
1460
|
});
|
|
1461
|
+
let remote;
|
|
1462
|
+
if (remoteObjectPath) {
|
|
1463
|
+
const plannedDeletes = [remoteObjectPath];
|
|
1464
|
+
const pruned = await planAndMaybePruneRemoteObjects(plannedDeletes, input);
|
|
1465
|
+
if (!pruned.ok) {
|
|
1466
|
+
return { exitCode: ExitCode.SYNC_PUSH_FAILED, result: pruned };
|
|
1467
|
+
}
|
|
1468
|
+
remote = pruned.data;
|
|
1469
|
+
}
|
|
1448
1470
|
const applied = input.cascade ? true : void 0;
|
|
1449
1471
|
const cascadeNote = input.cascade ? ` (cascade: ${cascade.source_array_refs.length} src arrays updated, ${cascade.wikilink_refs.length} wikilinks reported)` : "";
|
|
1472
|
+
const remoteNote = remote ? ` (remote ${input.remoteDelete ? `deleted ${remote.deleted.length}` : `planned ${remote.plannedDeletes.length}`})` : "";
|
|
1450
1473
|
return {
|
|
1451
1474
|
exitCode: ExitCode.OK,
|
|
1452
1475
|
result: ok({
|
|
@@ -1455,7 +1478,8 @@ ${fmRewritten}
|
|
|
1455
1478
|
index_updated: indexUpdated,
|
|
1456
1479
|
...applied !== void 0 ? { applied } : {},
|
|
1457
1480
|
...cascade ? { cascade } : {},
|
|
1458
|
-
|
|
1481
|
+
...remote ? { remote } : {},
|
|
1482
|
+
humanHint: `${relPath} -> ${archivePath}${indexUpdated ? " (index updated)" : ""}${cascadeNote}${remoteNote}`
|
|
1459
1483
|
})
|
|
1460
1484
|
};
|
|
1461
1485
|
}
|
|
@@ -4886,10 +4910,18 @@ program.command("status [vault]").description("output vault diagnostics").option
|
|
|
4886
4910
|
langEnvValue: process.env.WIKI_LANG
|
|
4887
4911
|
}), v.vault);
|
|
4888
4912
|
});
|
|
4889
|
-
program.command("archive <page> [vault]").description("archive a typed-knowledge or raw page").option("--wiki <name>", "wiki profile name").option("--cascade", "scan vault for references (wikilinks + sources arrays); preview by default", false).option("--apply", "with --cascade: mutate sources arrays and archive (without --apply, --cascade is preview-only)", false).action(async (page, vault, opts) => {
|
|
4913
|
+
program.command("archive <page> [vault]").description("archive a typed-knowledge or raw page").option("--wiki <name>", "wiki profile name").option("--cascade", "scan vault for references (wikilinks + sources arrays); preview by default", false).option("--apply", "with --cascade: mutate sources arrays and archive (without --apply, --cascade is preview-only)", false).option("--remote <remote>", "rclone remote root to prune the archived source path, for example seaweed-wiki:cloud/wiki").option("--remote-delete", "delete the archived source path from the remote after local archive", false).option("--max-remote-deletes <n>", "maximum remote object deletes allowed", "1").action(async (page, vault, opts) => {
|
|
4890
4914
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
4891
4915
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
4892
|
-
else emit(await runArchive({
|
|
4916
|
+
else emit(await runArchive({
|
|
4917
|
+
vault: v.vault,
|
|
4918
|
+
page,
|
|
4919
|
+
cascade: !!opts.cascade,
|
|
4920
|
+
apply: !!opts.apply,
|
|
4921
|
+
remote: opts.remote,
|
|
4922
|
+
remoteDelete: !!opts.remoteDelete,
|
|
4923
|
+
maxRemoteDeletes: Number.parseInt(opts.maxRemoteDeletes, 10)
|
|
4924
|
+
}), v.vault);
|
|
4893
4925
|
});
|
|
4894
4926
|
program.command("drift [vault]").description("detect content drift in raw sources").option("--apply", "update sha256 in drifted sources").option("--new <date>", "list raw files ingested on/after this date (YYYY-MM-DD)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
4895
4927
|
const v = await resolveVaultArg(vault, opts.wiki);
|
package/dist/skillwiki-mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.32",
|
|
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": {
|
package/skills/package.json
CHANGED
|
@@ -32,6 +32,7 @@ When reading retros as source material:
|
|
|
32
32
|
`provenance: project` and
|
|
33
33
|
`provenance_projects: ["[[slug]]"]`. Validate with
|
|
34
34
|
`skillwiki validate`.
|
|
35
|
+
- **Portable source references:** follow `using-skillwiki` → Portable Source References (commit-pinned GitHub URLs or repo-relative paths; not canonical `Source file:` / `Source inspected:` host paths).
|
|
35
36
|
- **Tag hygiene:** `tags:` must only contain entries from
|
|
36
37
|
`{vault}/SCHEMA.md` taxonomy. Never derive tags from prose text
|
|
37
38
|
(lesson/evidence sections) — use only established taxonomy tags
|
|
@@ -53,6 +53,7 @@ Rules:
|
|
|
53
53
|
- Resolve `<vault-root>` via `skillwiki path` (never hardcode).
|
|
54
54
|
- proj-work does NOT invoke any PRD skill — it provides paths only.
|
|
55
55
|
- If the PRD skill cannot accept custom save paths, fall back to manual `wiki-ingest`.
|
|
56
|
+
- When `spec.md` or `plan.md` mentions repo files, follow `using-skillwiki` → Portable Source References.
|
|
56
57
|
|
|
57
58
|
## Pitfalls
|
|
58
59
|
- **Wiki-as-truth fallacy**: tasks.md status markers are aspirational claims by previous sessions. They are often wrong. Always audit the actual file system before accepting a "DONE" label.
|
|
@@ -32,6 +32,7 @@ When reading retros as source material:
|
|
|
32
32
|
`provenance: project` and
|
|
33
33
|
`provenance_projects: ["[[slug]]"]`. Validate with
|
|
34
34
|
`skillwiki validate`.
|
|
35
|
+
- **Portable source references:** follow `using-skillwiki` → Portable Source References (commit-pinned GitHub URLs or repo-relative paths; not canonical `Source file:` / `Source inspected:` host paths).
|
|
35
36
|
- **Tag hygiene:** `tags:` must only contain entries from
|
|
36
37
|
`{vault}/SCHEMA.md` taxonomy. Never derive tags from prose text
|
|
37
38
|
(lesson/evidence sections) — use only established taxonomy tags
|
|
@@ -53,6 +53,7 @@ Rules:
|
|
|
53
53
|
- Resolve `<vault-root>` via `skillwiki path` (never hardcode).
|
|
54
54
|
- proj-work does NOT invoke any PRD skill — it provides paths only.
|
|
55
55
|
- If the PRD skill cannot accept custom save paths, fall back to manual `wiki-ingest`.
|
|
56
|
+
- When `spec.md` or `plan.md` mentions repo files, follow `using-skillwiki` → Portable Source References.
|
|
56
57
|
|
|
57
58
|
## Pitfalls
|
|
58
59
|
- **Wiki-as-truth fallacy**: tasks.md status markers are aspirational claims by previous sessions. They are often wrong. Always audit the actual file system before accepting a "DONE" label.
|
|
@@ -59,6 +59,15 @@ sha256: # computed by skillwiki hash over body bytes after closing ---
|
|
|
59
59
|
## Sensitive Content Policy
|
|
60
60
|
Vault content must not contain live credentials, access keys, tokens, passwords, cookies, bearer headers, private keys, or other authenticating secrets. This includes development-only and local-only credentials. Redact values before filing using `[REDACTED:<kind>]` or `[REDACTED:<kind>:<fingerprint>]`. If a source contains live secrets, stop and ask for a redacted source or explicit rotation/remediation direction; do not preserve the secret in `raw/`.
|
|
61
61
|
|
|
62
|
+
## Portable Source References
|
|
63
|
+
The vault is shared across hosts, so host-local absolute paths are not durable source identity.
|
|
64
|
+
|
|
65
|
+
- Prefer commit-pinned GitHub URLs when the source file is in a pushed repository and the commit is known.
|
|
66
|
+
- Otherwise prefer repo-relative identity in prose, such as repo slug + relative path.
|
|
67
|
+
- Use vault-relative references or `[[wikilinks]]` for pages already inside the wiki.
|
|
68
|
+
- Keep host-local absolute paths (`/Users/...`, `/home/...`, `file:///...`) only as clearly labeled observations such as `Observed on host: ...`, not as canonical `Source file:` or `Source inspected:` lines.
|
|
69
|
+
- Do not use markdown links to local vault files when a `[[wikilink]]` should be used instead.
|
|
70
|
+
|
|
62
71
|
### Ad-hoc capture: three entry points
|
|
63
72
|
| Entry | When | What happens |
|
|
64
73
|
|-------|------|-------------|
|
|
@@ -22,7 +22,7 @@ Standard four reads (SCHEMA, index, log, project context if applicable).
|
|
|
22
22
|
|
|
23
23
|
0. Resolve vault: `skillwiki path` and `skillwiki lang`.
|
|
24
24
|
1. Identify the target page. Confirm with the user which page to archive (show full relPath).
|
|
25
|
-
2. Run `skillwiki archive <page> [vault]`. Read the JSON output.
|
|
25
|
+
2. Run `skillwiki archive <page> [vault]`. On a vault-sync leaf host where S3 stale originals must be pruned, use `skillwiki archive <page> [vault] --remote seaweed-wiki:cloud/wiki --remote-delete --max-remote-deletes 1` only when that remote path deletion is explicitly intended. Read the JSON output.
|
|
26
26
|
3. Verify with `skillwiki index-check [vault]` — confirm no ghost entries remain.
|
|
27
27
|
4. Run `skillwiki lint [vault]` — check for broken wikilinks from other pages that still reference the archived page. If found, update those pages to point to the replacement or remove the stale link.
|
|
28
28
|
5. **Raw file archiving (N9 Reingest Protocol only):** When archiving a `raw/` file due to content drift, update ALL `^[raw/...]` citation markers and `sources:` frontmatter entries that reference the old path. Change `raw/articles/foo.md` to `_archive/raw/articles/foo.md` in every referencing page. Verify with `skillwiki audit` that no broken markers remain.
|
|
@@ -30,7 +30,7 @@ Standard four reads (SCHEMA, index, log, project context if applicable).
|
|
|
30
30
|
|
|
31
31
|
## Reversibility
|
|
32
32
|
|
|
33
|
-
Archiving is reversible: move the file back from `_archive/` to its original directory and re-add the wikilink entry to `index.md`.
|
|
33
|
+
Archiving is locally reversible: move the file back from `_archive/` to its original directory and re-add the wikilink entry to `index.md`. If `--remote-delete` was used, the stale active-path object is pruned from the remote after the archive move, but the archived copy remains and a restore will republish the active path on the next push.
|
|
34
34
|
|
|
35
35
|
## Stop conditions
|
|
36
36
|
|
|
@@ -42,4 +42,4 @@ Archiving is reversible: move the file back from `_archive/` to its original dir
|
|
|
42
42
|
- Archiving `raw/` files outside the N9 Reingest Protocol (raw is immutable except during content-drift reingestion).
|
|
43
43
|
- Archiving raw files without updating all `^[raw/...]` citation markers that reference them.
|
|
44
44
|
- Archiving without user confirmation.
|
|
45
|
-
- Deleting files (archive moves
|
|
45
|
+
- Deleting local vault files directly (archive moves locally; remote stale-path pruning is only allowed through the explicit `skillwiki archive --remote ... --remote-delete --max-remote-deletes 1` path).
|
|
@@ -17,6 +17,7 @@ Standard four reads. If cwd is inside `projects/{slug}/`, also read project READ
|
|
|
17
17
|
3. Compose the page with citations pre-attached. Reuse existing `raw/` sources where possible. Every page MUST include:
|
|
18
18
|
- `> **TL;DR:**` blockquote as the first content after the title heading — a one-sentence summary of the page's key takeaway (under 200 chars). See SCHEMA.md `## TL;DR Convention`.
|
|
19
19
|
- For pages tagged `architecture` or explaining workflows/systems: include a Mermaid diagram (`graph TB` or `sequenceDiagram`) in the body. Follow Obsidian-compatible Mermaid rules (see SCHEMA.md `## Mermaid Diagrams`).
|
|
20
|
+
- When referring to local repo files in narrative prose, follow `using-skillwiki` → Portable Source References.
|
|
20
21
|
For `comparison`, evaluation-style `query`, or research-summary pages, end the body with:
|
|
21
22
|
```markdown
|
|
22
23
|
## Decision Closeout
|
|
@@ -36,5 +37,6 @@ Use exactly one disposition. Keep this as a prompt/template convention; do not a
|
|
|
36
37
|
## Forbidden
|
|
37
38
|
- Filing without explicit `provenance:`.
|
|
38
39
|
- Updating `index.md` before `validate` passes.
|
|
40
|
+
- Writing host-local absolute paths as canonical durable source references when a portable reference is available (see `using-skillwiki` → Portable Source References).
|
|
39
41
|
- Writing `[[wikilinks]]` to pages that don't exist in the vault. Before linking, verify the target exists: check `index.md` or `ls` the target directory. If the target doesn't exist yet, use plain text instead of a wikilink.
|
|
40
42
|
- Writing live credentials, access keys, tokens, passwords, cookies, bearer headers, private keys, or other authenticating secrets to the vault.
|
|
@@ -18,6 +18,7 @@ Run `skillwiki lang` at the start. Generate page-body prose, narrative sections,
|
|
|
18
18
|
0. **Resolve vault and language.** Run `skillwiki path` (fail if NO_VAULT_CONFIGURED) and `skillwiki lang`. Use the resolved vault path for all writes; use the canonical language for all generated prose.
|
|
19
19
|
1. **Guard.** For each URL: run `skillwiki fetch-guard <url>`. If exit ≠ 0, STOP and surface the error. Do not retry.
|
|
20
20
|
2. **Fetch.** Use `web_fetch` (or read local file) under Layer 2 controls (the CLI Layer 2 fetcher applies in tests; in skill runtime use `web_fetch` directly and treat any error as STOP).
|
|
21
|
+
- **Portable local-source rule:** follow `using-skillwiki` → Portable Source References. Do not use `source_url: file:///...` as the canonical durable reference; prefer commit-pinned GitHub `blob/<commit>/<path>` when resolvable, else empty `source_url` plus portable repo-relative prose.
|
|
21
22
|
3. **Identity guard.** Before writing raw files, ensure the target raw filename/title, `source_url`, fetched H1/title, and early body subject agree. If `skillwiki ingest` reports `INGEST_VALIDATION_FAILED` with `source identity conflict`, STOP. Do not fix by renaming after the fact; choose the correct title/source pair or ask the user.
|
|
22
23
|
4. **Sensitive content guard.** Before writing or filing any vault page, scan the source and generated body for live credentials, access keys, tokens, passwords, cookies, bearer headers, or private keys. Redact generated prose before writing. If the source itself must remain raw and contains a live secret, STOP instead of preserving it.
|
|
23
24
|
5. **Hash.** Write the raw file (frontmatter + body). Run `skillwiki hash <raw-file>` and embed the result in raw frontmatter `sha256:`.
|
|
@@ -56,6 +57,7 @@ Raw ephemeral data (market feeds, logs, transient JSON) must be written to the *
|
|
|
56
57
|
- Updating `index.md` or `log.md` before all pages validate.
|
|
57
58
|
- Modifying any existing file in `raw/`.
|
|
58
59
|
- Writing raw ephemeral data directly to cloud-mounted wiki paths (`~/wiki/`).
|
|
60
|
+
- Writing host-local absolute paths as canonical durable source references (see `using-skillwiki` → Portable Source References).
|
|
59
61
|
- Writing `[[wikilinks]]` to pages that don't exist in the vault. Before linking, verify the target exists: check `index.md` or `ls` the target directory. If the target doesn't exist yet, use plain text instead of a wikilink.
|
|
60
62
|
## Batch Mode
|
|
61
63
|
When the user provides multiple sources (a directory of files, a list of URLs, or a multi-document input):
|
|
@@ -59,6 +59,15 @@ sha256: # computed by skillwiki hash over body bytes after closing ---
|
|
|
59
59
|
## Sensitive Content Policy
|
|
60
60
|
Vault content must not contain live credentials, access keys, tokens, passwords, cookies, bearer headers, private keys, or other authenticating secrets. This includes development-only and local-only credentials. Redact values before filing using `[REDACTED:<kind>]` or `[REDACTED:<kind>:<fingerprint>]`. If a source contains live secrets, stop and ask for a redacted source or explicit rotation/remediation direction; do not preserve the secret in `raw/`.
|
|
61
61
|
|
|
62
|
+
## Portable Source References
|
|
63
|
+
The vault is shared across hosts, so host-local absolute paths are not durable source identity.
|
|
64
|
+
|
|
65
|
+
- Prefer commit-pinned GitHub URLs when the source file is in a pushed repository and the commit is known.
|
|
66
|
+
- Otherwise prefer repo-relative identity in prose, such as repo slug + relative path.
|
|
67
|
+
- Use vault-relative references or `[[wikilinks]]` for pages already inside the wiki.
|
|
68
|
+
- Keep host-local absolute paths (`/Users/...`, `/home/...`, `file:///...`) only as clearly labeled observations such as `Observed on host: ...`, not as canonical `Source file:` or `Source inspected:` lines.
|
|
69
|
+
- Do not use markdown links to local vault files when a `[[wikilink]]` should be used instead.
|
|
70
|
+
|
|
62
71
|
### Ad-hoc capture: three entry points
|
|
63
72
|
| Entry | When | What happens |
|
|
64
73
|
|-------|------|-------------|
|
|
@@ -22,7 +22,7 @@ Standard four reads (SCHEMA, index, log, project context if applicable).
|
|
|
22
22
|
|
|
23
23
|
0. Resolve vault: `skillwiki path` and `skillwiki lang`.
|
|
24
24
|
1. Identify the target page. Confirm with the user which page to archive (show full relPath).
|
|
25
|
-
2. Run `skillwiki archive <page> [vault]`. Read the JSON output.
|
|
25
|
+
2. Run `skillwiki archive <page> [vault]`. On a vault-sync leaf host where S3 stale originals must be pruned, use `skillwiki archive <page> [vault] --remote seaweed-wiki:cloud/wiki --remote-delete --max-remote-deletes 1` only when that remote path deletion is explicitly intended. Read the JSON output.
|
|
26
26
|
3. Verify with `skillwiki index-check [vault]` — confirm no ghost entries remain.
|
|
27
27
|
4. Run `skillwiki lint [vault]` — check for broken wikilinks from other pages that still reference the archived page. If found, update those pages to point to the replacement or remove the stale link.
|
|
28
28
|
5. **Raw file archiving (N9 Reingest Protocol only):** When archiving a `raw/` file due to content drift, update ALL `^[raw/...]` citation markers and `sources:` frontmatter entries that reference the old path. Change `raw/articles/foo.md` to `_archive/raw/articles/foo.md` in every referencing page. Verify with `skillwiki audit` that no broken markers remain.
|
|
@@ -30,7 +30,7 @@ Standard four reads (SCHEMA, index, log, project context if applicable).
|
|
|
30
30
|
|
|
31
31
|
## Reversibility
|
|
32
32
|
|
|
33
|
-
Archiving is reversible: move the file back from `_archive/` to its original directory and re-add the wikilink entry to `index.md`.
|
|
33
|
+
Archiving is locally reversible: move the file back from `_archive/` to its original directory and re-add the wikilink entry to `index.md`. If `--remote-delete` was used, the stale active-path object is pruned from the remote after the archive move, but the archived copy remains and a restore will republish the active path on the next push.
|
|
34
34
|
|
|
35
35
|
## Stop conditions
|
|
36
36
|
|
|
@@ -42,4 +42,4 @@ Archiving is reversible: move the file back from `_archive/` to its original dir
|
|
|
42
42
|
- Archiving `raw/` files outside the N9 Reingest Protocol (raw is immutable except during content-drift reingestion).
|
|
43
43
|
- Archiving raw files without updating all `^[raw/...]` citation markers that reference them.
|
|
44
44
|
- Archiving without user confirmation.
|
|
45
|
-
- Deleting files (archive moves
|
|
45
|
+
- Deleting local vault files directly (archive moves locally; remote stale-path pruning is only allowed through the explicit `skillwiki archive --remote ... --remote-delete --max-remote-deletes 1` path).
|
|
@@ -17,6 +17,7 @@ Standard four reads. If cwd is inside `projects/{slug}/`, also read project READ
|
|
|
17
17
|
3. Compose the page with citations pre-attached. Reuse existing `raw/` sources where possible. Every page MUST include:
|
|
18
18
|
- `> **TL;DR:**` blockquote as the first content after the title heading — a one-sentence summary of the page's key takeaway (under 200 chars). See SCHEMA.md `## TL;DR Convention`.
|
|
19
19
|
- For pages tagged `architecture` or explaining workflows/systems: include a Mermaid diagram (`graph TB` or `sequenceDiagram`) in the body. Follow Obsidian-compatible Mermaid rules (see SCHEMA.md `## Mermaid Diagrams`).
|
|
20
|
+
- When referring to local repo files in narrative prose, follow `using-skillwiki` → Portable Source References.
|
|
20
21
|
For `comparison`, evaluation-style `query`, or research-summary pages, end the body with:
|
|
21
22
|
```markdown
|
|
22
23
|
## Decision Closeout
|
|
@@ -36,5 +37,6 @@ Use exactly one disposition. Keep this as a prompt/template convention; do not a
|
|
|
36
37
|
## Forbidden
|
|
37
38
|
- Filing without explicit `provenance:`.
|
|
38
39
|
- Updating `index.md` before `validate` passes.
|
|
40
|
+
- Writing host-local absolute paths as canonical durable source references when a portable reference is available (see `using-skillwiki` → Portable Source References).
|
|
39
41
|
- Writing `[[wikilinks]]` to pages that don't exist in the vault. Before linking, verify the target exists: check `index.md` or `ls` the target directory. If the target doesn't exist yet, use plain text instead of a wikilink.
|
|
40
42
|
- Writing live credentials, access keys, tokens, passwords, cookies, bearer headers, private keys, or other authenticating secrets to the vault.
|
|
@@ -18,6 +18,7 @@ Run `skillwiki lang` at the start. Generate page-body prose, narrative sections,
|
|
|
18
18
|
0. **Resolve vault and language.** Run `skillwiki path` (fail if NO_VAULT_CONFIGURED) and `skillwiki lang`. Use the resolved vault path for all writes; use the canonical language for all generated prose.
|
|
19
19
|
1. **Guard.** For each URL: run `skillwiki fetch-guard <url>`. If exit ≠ 0, STOP and surface the error. Do not retry.
|
|
20
20
|
2. **Fetch.** Use `web_fetch` (or read local file) under Layer 2 controls (the CLI Layer 2 fetcher applies in tests; in skill runtime use `web_fetch` directly and treat any error as STOP).
|
|
21
|
+
- **Portable local-source rule:** follow `using-skillwiki` → Portable Source References. Do not use `source_url: file:///...` as the canonical durable reference; prefer commit-pinned GitHub `blob/<commit>/<path>` when resolvable, else empty `source_url` plus portable repo-relative prose.
|
|
21
22
|
3. **Identity guard.** Before writing raw files, ensure the target raw filename/title, `source_url`, fetched H1/title, and early body subject agree. If `skillwiki ingest` reports `INGEST_VALIDATION_FAILED` with `source identity conflict`, STOP. Do not fix by renaming after the fact; choose the correct title/source pair or ask the user.
|
|
22
23
|
4. **Sensitive content guard.** Before writing or filing any vault page, scan the source and generated body for live credentials, access keys, tokens, passwords, cookies, bearer headers, or private keys. Redact generated prose before writing. If the source itself must remain raw and contains a live secret, STOP instead of preserving it.
|
|
23
24
|
5. **Hash.** Write the raw file (frontmatter + body). Run `skillwiki hash <raw-file>` and embed the result in raw frontmatter `sha256:`.
|
|
@@ -56,6 +57,7 @@ Raw ephemeral data (market feeds, logs, transient JSON) must be written to the *
|
|
|
56
57
|
- Updating `index.md` or `log.md` before all pages validate.
|
|
57
58
|
- Modifying any existing file in `raw/`.
|
|
58
59
|
- Writing raw ephemeral data directly to cloud-mounted wiki paths (`~/wiki/`).
|
|
60
|
+
- Writing host-local absolute paths as canonical durable source references (see `using-skillwiki` → Portable Source References).
|
|
59
61
|
- Writing `[[wikilinks]]` to pages that don't exist in the vault. Before linking, verify the target exists: check `index.md` or `ls` the target directory. If the target doesn't exist yet, use plain text instead of a wikilink.
|
|
60
62
|
## Batch Mode
|
|
61
63
|
When the user provides multiple sources (a directory of files, a list of URLs, or a multi-document input):
|