skillwiki 0.2.0-beta.2 → 0.2.0-beta.4
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 +278 -26
- package/package.json +14 -5
- package/skills/.claude-plugin/plugin.json +23 -0
- package/skills/README.md +10 -0
- package/skills/package.json +6 -0
- package/skills/proj-decide/SKILL.md +24 -0
- package/skills/proj-distill/SKILL.md +26 -0
- package/skills/proj-init/SKILL.md +29 -0
- package/skills/proj-work/SKILL.md +29 -0
- package/skills/wiki-audit/SKILL.md +33 -0
- package/skills/wiki-crystallize/SKILL.md +34 -0
- package/skills/wiki-ingest/SKILL.md +47 -0
- package/skills/wiki-init/SKILL.md +36 -0
- package/skills/wiki-lint/SKILL.md +33 -0
- package/skills/wiki-query/SKILL.md +40 -0
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
+
import { readFileSync } from "fs";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
|
|
6
7
|
// src/utils/output.ts
|
|
@@ -9,9 +10,14 @@ function printJson(r) {
|
|
|
9
10
|
}
|
|
10
11
|
function printHuman(r) {
|
|
11
12
|
if (r.ok) {
|
|
12
|
-
|
|
13
|
+
if (typeof r.data === "object" && r.data !== null && "humanHint" in r.data) {
|
|
14
|
+
process.stdout.write(`${r.data.humanHint}
|
|
15
|
+
`);
|
|
16
|
+
} else {
|
|
17
|
+
process.stdout.write(`OK
|
|
13
18
|
${formatData(r.data)}
|
|
14
19
|
`);
|
|
20
|
+
}
|
|
15
21
|
} else {
|
|
16
22
|
process.stdout.write(`ERR ${r.error}
|
|
17
23
|
${r.detail !== void 0 ? formatData(r.detail) + "\n" : ""}`);
|
|
@@ -53,15 +59,19 @@ var ExitCode = {
|
|
|
53
59
|
LINT_HAS_WARNINGS: 22,
|
|
54
60
|
LINT_HAS_ERRORS: 23,
|
|
55
61
|
ENV_WRITE_CONFLICT: 24,
|
|
56
|
-
NO_VAULT_CONFIGURED: 25
|
|
62
|
+
NO_VAULT_CONFIGURED: 25,
|
|
63
|
+
INVALID_CONFIG_KEY: 26,
|
|
64
|
+
CONFIG_WRITE_FAILED: 27,
|
|
65
|
+
DOCTOR_HAS_WARNINGS: 28,
|
|
66
|
+
DOCTOR_HAS_ERRORS: 29
|
|
57
67
|
};
|
|
58
68
|
|
|
59
69
|
// ../shared/src/json-output.ts
|
|
60
70
|
function ok(data) {
|
|
61
71
|
return { ok: true, data };
|
|
62
72
|
}
|
|
63
|
-
function err(
|
|
64
|
-
return detail === void 0 ? { ok: false, error } : { ok: false, error, detail };
|
|
73
|
+
function err(error2, detail) {
|
|
74
|
+
return detail === void 0 ? { ok: false, error: error2 } : { ok: false, error: error2, detail };
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
// ../shared/src/schemas.ts
|
|
@@ -479,7 +489,8 @@ async function runOverlap(input) {
|
|
|
479
489
|
import { join as join2 } from "path";
|
|
480
490
|
|
|
481
491
|
// src/utils/dotenv.ts
|
|
482
|
-
import { readFile as readFile4 } from "fs/promises";
|
|
492
|
+
import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
493
|
+
import { dirname as dirname2 } from "path";
|
|
483
494
|
var WHITELIST = /* @__PURE__ */ new Set(["WIKI_PATH", "WIKI_LANG"]);
|
|
484
495
|
async function parseDotenvFile(path) {
|
|
485
496
|
let text;
|
|
@@ -502,6 +513,50 @@ async function parseDotenvFile(path) {
|
|
|
502
513
|
}
|
|
503
514
|
return out;
|
|
504
515
|
}
|
|
516
|
+
async function writeDotenv(filePath, entries, originalContent) {
|
|
517
|
+
const lines = originalContent !== void 0 ? updateLines(originalContent, entries) : freshLines(entries);
|
|
518
|
+
await mkdir2(dirname2(filePath), { recursive: true });
|
|
519
|
+
await writeFile2(filePath, lines.join("\n") + "\n", "utf8");
|
|
520
|
+
}
|
|
521
|
+
function freshLines(entries) {
|
|
522
|
+
const out = [];
|
|
523
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
524
|
+
if (value !== void 0) out.push(`${key}=${value}`);
|
|
525
|
+
}
|
|
526
|
+
return out;
|
|
527
|
+
}
|
|
528
|
+
function updateLines(originalContent, entries) {
|
|
529
|
+
let rawLines = originalContent.split(/\r?\n/);
|
|
530
|
+
if (rawLines.length > 0 && rawLines[rawLines.length - 1] === "") {
|
|
531
|
+
rawLines = rawLines.slice(0, -1);
|
|
532
|
+
}
|
|
533
|
+
const keysToWrite = new Set(Object.keys(entries));
|
|
534
|
+
const out = [];
|
|
535
|
+
for (const line of rawLines) {
|
|
536
|
+
const trimmed = line.trim();
|
|
537
|
+
if (trimmed.length === 0 || trimmed.startsWith("#")) {
|
|
538
|
+
out.push(line);
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
const eq = trimmed.indexOf("=");
|
|
542
|
+
if (eq <= 0) {
|
|
543
|
+
out.push(line);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
const key = trimmed.slice(0, eq).trim();
|
|
547
|
+
if (keysToWrite.has(key)) {
|
|
548
|
+
out.push(`${key}=${entries[key]}`);
|
|
549
|
+
keysToWrite.delete(key);
|
|
550
|
+
} else {
|
|
551
|
+
out.push(line);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
for (const key of keysToWrite) {
|
|
555
|
+
const value = entries[key];
|
|
556
|
+
if (value !== void 0) out.push(`${key}=${value}`);
|
|
557
|
+
}
|
|
558
|
+
return out;
|
|
559
|
+
}
|
|
505
560
|
|
|
506
561
|
// src/utils/wiki-path.ts
|
|
507
562
|
async function resolveInitTimePath(input) {
|
|
@@ -630,7 +685,7 @@ function simulateRemoval(adj, removed) {
|
|
|
630
685
|
|
|
631
686
|
// src/commands/audit.ts
|
|
632
687
|
import { readFile as readFile5, stat as stat2 } from "fs/promises";
|
|
633
|
-
import { dirname as
|
|
688
|
+
import { dirname as dirname3, resolve, join as join3 } from "path";
|
|
634
689
|
|
|
635
690
|
// src/parsers/citations.ts
|
|
636
691
|
var FENCE2 = /```[\s\S]*?```/g;
|
|
@@ -657,7 +712,7 @@ async function runAudit(input) {
|
|
|
657
712
|
if (!fm.ok) return { exitCode: ExitCode.INVALID_FRONTMATTER, result: fm };
|
|
658
713
|
const split = splitFrontmatter(text);
|
|
659
714
|
const body = split.ok ? split.data.body : text;
|
|
660
|
-
const vault = await findVaultRoot(
|
|
715
|
+
const vault = await findVaultRoot(dirname3(resolve(input.file)));
|
|
661
716
|
if (!vault) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID") };
|
|
662
717
|
const markers = extractCitationMarkers(body);
|
|
663
718
|
const resolved = await Promise.all(markers.map(async (m) => {
|
|
@@ -688,7 +743,7 @@ async function findVaultRoot(start) {
|
|
|
688
743
|
return cur;
|
|
689
744
|
} catch {
|
|
690
745
|
}
|
|
691
|
-
const parent =
|
|
746
|
+
const parent = dirname3(cur);
|
|
692
747
|
if (parent === cur) return null;
|
|
693
748
|
cur = parent;
|
|
694
749
|
}
|
|
@@ -700,10 +755,10 @@ import { readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
|
700
755
|
import { join as join4 } from "path";
|
|
701
756
|
|
|
702
757
|
// src/utils/install-fs.ts
|
|
703
|
-
import { copyFile, mkdir as
|
|
704
|
-
import { dirname as
|
|
758
|
+
import { copyFile, mkdir as mkdir3, rename, writeFile as writeFile3, stat as stat3 } from "fs/promises";
|
|
759
|
+
import { dirname as dirname4 } from "path";
|
|
705
760
|
async function atomicCopyWithBackup(src, dst) {
|
|
706
|
-
await
|
|
761
|
+
await mkdir3(dirname4(dst), { recursive: true });
|
|
707
762
|
let backupPath = null;
|
|
708
763
|
try {
|
|
709
764
|
await stat3(dst);
|
|
@@ -721,9 +776,9 @@ async function atomicCopyWithBackup(src, dst) {
|
|
|
721
776
|
return ok({ copied: true, backupPath });
|
|
722
777
|
}
|
|
723
778
|
async function writeManifest(path, m) {
|
|
724
|
-
await
|
|
779
|
+
await mkdir3(dirname4(path), { recursive: true });
|
|
725
780
|
const enriched = { installed_at: (/* @__PURE__ */ new Date()).toISOString(), ...m };
|
|
726
|
-
await
|
|
781
|
+
await writeFile3(path, JSON.stringify(enriched, null, 2));
|
|
727
782
|
}
|
|
728
783
|
|
|
729
784
|
// src/commands/install.ts
|
|
@@ -839,8 +894,8 @@ async function runLang(input) {
|
|
|
839
894
|
}
|
|
840
895
|
|
|
841
896
|
// src/commands/init.ts
|
|
842
|
-
import { mkdir as
|
|
843
|
-
import { join as join7, dirname as
|
|
897
|
+
import { mkdir as mkdir4, readFile as readFile6, stat as stat5, writeFile as writeFile4 } from "fs/promises";
|
|
898
|
+
import { join as join7, dirname as dirname5 } from "path";
|
|
844
899
|
var DEFAULT_TAXONOMY = [
|
|
845
900
|
"research",
|
|
846
901
|
"comparison",
|
|
@@ -899,9 +954,9 @@ async function runInit(input) {
|
|
|
899
954
|
}
|
|
900
955
|
const created = [];
|
|
901
956
|
try {
|
|
902
|
-
await
|
|
957
|
+
await mkdir4(target, { recursive: true });
|
|
903
958
|
for (const d of VAULT_DIRS) {
|
|
904
|
-
await
|
|
959
|
+
await mkdir4(join7(target, d), { recursive: true });
|
|
905
960
|
created.push(d + "/");
|
|
906
961
|
}
|
|
907
962
|
} catch (e) {
|
|
@@ -913,7 +968,7 @@ async function runInit(input) {
|
|
|
913
968
|
try {
|
|
914
969
|
const schemaTpl = await readFile6(join7(input.templates, "SCHEMA.md"), "utf8");
|
|
915
970
|
const schema = schemaTpl.replace("{{DOMAIN}}", input.domain).replace("{{WIKI_LANG}}", canonicalLang).replace("{{TAXONOMY_YAML}}", taxonomyYaml);
|
|
916
|
-
await
|
|
971
|
+
await writeFile4(join7(target, "SCHEMA.md"), schema, "utf8");
|
|
917
972
|
created.push("SCHEMA.md");
|
|
918
973
|
} catch (e) {
|
|
919
974
|
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { file: "SCHEMA.md", message: String(e) }) };
|
|
@@ -921,7 +976,7 @@ async function runInit(input) {
|
|
|
921
976
|
try {
|
|
922
977
|
const idxTpl = await readFile6(join7(input.templates, "index.md"), "utf8");
|
|
923
978
|
const idx = idxTpl.replace("{{INIT_DATE}}", today);
|
|
924
|
-
await
|
|
979
|
+
await writeFile4(join7(target, "index.md"), idx, "utf8");
|
|
925
980
|
created.push("index.md");
|
|
926
981
|
} catch (e) {
|
|
927
982
|
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { file: "index.md", message: String(e) }) };
|
|
@@ -929,17 +984,17 @@ async function runInit(input) {
|
|
|
929
984
|
try {
|
|
930
985
|
const logTpl = await readFile6(join7(input.templates, "log.md"), "utf8");
|
|
931
986
|
const log = logTpl.replace(/\{\{INIT_DATE\}\}/g, today).replace("{{DOMAIN}}", input.domain).replace("{{WIKI_LANG}}", canonicalLang);
|
|
932
|
-
await
|
|
987
|
+
await writeFile4(join7(target, "log.md"), log, "utf8");
|
|
933
988
|
created.push("log.md");
|
|
934
989
|
} catch (e) {
|
|
935
990
|
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { file: "log.md", message: String(e) }) };
|
|
936
991
|
}
|
|
937
992
|
try {
|
|
938
|
-
await
|
|
993
|
+
await mkdir4(dirname5(envPath), { recursive: true });
|
|
939
994
|
const envBody = `WIKI_PATH=${target}
|
|
940
995
|
WIKI_LANG=${canonicalLang}
|
|
941
996
|
`;
|
|
942
|
-
await
|
|
997
|
+
await writeFile4(envPath, envBody, "utf8");
|
|
943
998
|
} catch (e) {
|
|
944
999
|
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { file: envPath, message: String(e) }) };
|
|
945
1000
|
}
|
|
@@ -1131,7 +1186,7 @@ async function runPagesize(input) {
|
|
|
1131
1186
|
}
|
|
1132
1187
|
|
|
1133
1188
|
// src/commands/log-rotate.ts
|
|
1134
|
-
import { readFile as readFile10, rename as rename2, writeFile as
|
|
1189
|
+
import { readFile as readFile10, rename as rename2, writeFile as writeFile5, stat as stat6 } from "fs/promises";
|
|
1135
1190
|
import { join as join11 } from "path";
|
|
1136
1191
|
var ENTRY_RE = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
|
|
1137
1192
|
async function runLogRotate(input) {
|
|
@@ -1172,7 +1227,7 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
|
|
|
1172
1227
|
|
|
1173
1228
|
- Previous log moved to ${rotatedName}
|
|
1174
1229
|
`;
|
|
1175
|
-
await
|
|
1230
|
+
await writeFile5(logPath, fresh, "utf8");
|
|
1176
1231
|
} catch (e) {
|
|
1177
1232
|
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { message: String(e) }) };
|
|
1178
1233
|
}
|
|
@@ -1236,9 +1291,195 @@ async function runLint(input) {
|
|
|
1236
1291
|
};
|
|
1237
1292
|
}
|
|
1238
1293
|
|
|
1294
|
+
// src/commands/config.ts
|
|
1295
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
1296
|
+
import { existsSync } from "fs";
|
|
1297
|
+
import { join as join12 } from "path";
|
|
1298
|
+
function validateKey(key) {
|
|
1299
|
+
return key === "WIKI_PATH" || key === "WIKI_LANG";
|
|
1300
|
+
}
|
|
1301
|
+
function configPath(home) {
|
|
1302
|
+
return join12(home, ".skillwiki", ".env");
|
|
1303
|
+
}
|
|
1304
|
+
async function runConfigGet(input) {
|
|
1305
|
+
if (!validateKey(input.key)) {
|
|
1306
|
+
return { exitCode: ExitCode.INVALID_CONFIG_KEY, result: err("INVALID_CONFIG_KEY", { key: input.key }) };
|
|
1307
|
+
}
|
|
1308
|
+
const map = await parseDotenvFile(configPath(input.home));
|
|
1309
|
+
const value = map[input.key] ?? "";
|
|
1310
|
+
return { exitCode: ExitCode.OK, result: ok({ key: input.key, value, humanHint: value }) };
|
|
1311
|
+
}
|
|
1312
|
+
async function runConfigSet(input) {
|
|
1313
|
+
if (!validateKey(input.key)) {
|
|
1314
|
+
return { exitCode: ExitCode.INVALID_CONFIG_KEY, result: err("INVALID_CONFIG_KEY", { key: input.key }) };
|
|
1315
|
+
}
|
|
1316
|
+
const filePath = configPath(input.home);
|
|
1317
|
+
try {
|
|
1318
|
+
let originalContent;
|
|
1319
|
+
try {
|
|
1320
|
+
originalContent = await readFile11(filePath, "utf8");
|
|
1321
|
+
} catch {
|
|
1322
|
+
}
|
|
1323
|
+
const existing = originalContent !== void 0 ? await parseDotenvFile(filePath) : {};
|
|
1324
|
+
const merged = { ...existing, [input.key]: input.value };
|
|
1325
|
+
await writeDotenv(filePath, merged, originalContent);
|
|
1326
|
+
return { exitCode: ExitCode.OK, result: ok({ key: input.key, value: input.value, written: true, humanHint: `${input.key}=${input.value}` }) };
|
|
1327
|
+
} catch (e) {
|
|
1328
|
+
return { exitCode: ExitCode.CONFIG_WRITE_FAILED, result: err("CONFIG_WRITE_FAILED", { key: input.key, error: String(e) }) };
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
async function runConfigList(input) {
|
|
1332
|
+
const map = await parseDotenvFile(configPath(input.home));
|
|
1333
|
+
const entries = Object.entries(map).map(([key, value]) => ({ key, value: value ?? "" }));
|
|
1334
|
+
return { exitCode: ExitCode.OK, result: ok({ entries, humanHint: entries.map((e) => `${e.key}=${e.value}`).join("\n") }) };
|
|
1335
|
+
}
|
|
1336
|
+
async function runConfigPath(input) {
|
|
1337
|
+
const filePath = configPath(input.home);
|
|
1338
|
+
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync(filePath), humanHint: filePath }) };
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// src/commands/doctor.ts
|
|
1342
|
+
import { existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
1343
|
+
import { join as join13 } from "path";
|
|
1344
|
+
import { execSync } from "child_process";
|
|
1345
|
+
function pass(id, label, detail) {
|
|
1346
|
+
return { id, label, status: "pass", detail };
|
|
1347
|
+
}
|
|
1348
|
+
function warn(id, label, detail) {
|
|
1349
|
+
return { id, label, status: "warn", detail };
|
|
1350
|
+
}
|
|
1351
|
+
function error(id, label, detail) {
|
|
1352
|
+
return { id, label, status: "error", detail };
|
|
1353
|
+
}
|
|
1354
|
+
function checkNodeVersion() {
|
|
1355
|
+
const major = parseInt(process.version.slice(1).split(".")[0], 10);
|
|
1356
|
+
if (major >= 20) {
|
|
1357
|
+
return pass("node_version", "Node.js version", `v${major} >= 20`);
|
|
1358
|
+
}
|
|
1359
|
+
return error("node_version", "Node.js version", `Node.js v${major} is below minimum v20`);
|
|
1360
|
+
}
|
|
1361
|
+
function checkCliOnPath(argv) {
|
|
1362
|
+
if (argv.length >= 2 && argv[1].endsWith("cli.js")) {
|
|
1363
|
+
return warn("cli_on_path", "skillwiki on PATH", "Running via node cli.js (dev mode) \u2014 PATH check skipped");
|
|
1364
|
+
}
|
|
1365
|
+
if (argv.length >= 2 && argv[1] === "skillwiki") {
|
|
1366
|
+
return pass("cli_on_path", "skillwiki on PATH", "Running as skillwiki \u2014 already on PATH");
|
|
1367
|
+
}
|
|
1368
|
+
try {
|
|
1369
|
+
execSync("which skillwiki 2>/dev/null", { encoding: "utf8" }).trim();
|
|
1370
|
+
return pass("cli_on_path", "skillwiki on PATH", "skillwiki found on PATH");
|
|
1371
|
+
} catch {
|
|
1372
|
+
return warn("cli_on_path", "skillwiki on PATH", "skillwiki not found on PATH");
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
async function checkConfigFile(home) {
|
|
1376
|
+
const cfgPath = join13(home, ".skillwiki", ".env");
|
|
1377
|
+
if (!existsSync2(cfgPath)) {
|
|
1378
|
+
return warn("config_file", "Config file exists", `${cfgPath} not found`);
|
|
1379
|
+
}
|
|
1380
|
+
try {
|
|
1381
|
+
const map = await parseDotenvFile(cfgPath);
|
|
1382
|
+
const keys = Object.keys(map);
|
|
1383
|
+
return pass("config_file", "Config file exists", `Found with keys: ${keys.length > 0 ? keys.join(", ") : "(none set)"}`);
|
|
1384
|
+
} catch (e) {
|
|
1385
|
+
return warn("config_file", "Config file exists", `Failed to parse ${cfgPath}: ${String(e)}`);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
async function checkWikiPathSet(input) {
|
|
1389
|
+
const r = await resolveRuntimePath({ flag: void 0, envValue: input.envValue, home: input.home });
|
|
1390
|
+
if (r.ok) {
|
|
1391
|
+
return pass("wiki_path_set", "WIKI_PATH configured", `Resolved via ${r.data.source}: ${r.data.path}`);
|
|
1392
|
+
}
|
|
1393
|
+
return error("wiki_path_set", "WIKI_PATH configured", "No vault configured. Run `skillwiki init` or pass --vault.");
|
|
1394
|
+
}
|
|
1395
|
+
function checkWikiPathExists(resolvedPath) {
|
|
1396
|
+
if (resolvedPath === void 0) {
|
|
1397
|
+
return error("wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
1398
|
+
}
|
|
1399
|
+
if (existsSync2(resolvedPath) && statSync(resolvedPath).isDirectory()) {
|
|
1400
|
+
return pass("wiki_path_exists", "Vault directory exists", resolvedPath);
|
|
1401
|
+
}
|
|
1402
|
+
return error("wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
|
|
1403
|
+
}
|
|
1404
|
+
function checkVaultStructure(resolvedPath) {
|
|
1405
|
+
if (resolvedPath === void 0) {
|
|
1406
|
+
return error("vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
1407
|
+
}
|
|
1408
|
+
if (!existsSync2(resolvedPath)) {
|
|
1409
|
+
return error("vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
1410
|
+
}
|
|
1411
|
+
const missing = [];
|
|
1412
|
+
if (!existsSync2(join13(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
1413
|
+
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
1414
|
+
if (!existsSync2(join13(resolvedPath, dir))) missing.push(dir + "/");
|
|
1415
|
+
}
|
|
1416
|
+
if (missing.length === 0) {
|
|
1417
|
+
return pass("vault_structure", "Vault structure valid", "All required files and directories present");
|
|
1418
|
+
}
|
|
1419
|
+
return error("vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")}`);
|
|
1420
|
+
}
|
|
1421
|
+
function checkSkillsInstalled(home) {
|
|
1422
|
+
const skillsDir = join13(home, ".claude", "skills");
|
|
1423
|
+
if (!existsSync2(skillsDir)) {
|
|
1424
|
+
return warn("skills_installed", "Skills installed", `${skillsDir} not found`);
|
|
1425
|
+
}
|
|
1426
|
+
const found = findSkillMd(skillsDir);
|
|
1427
|
+
if (found.length > 0) {
|
|
1428
|
+
return pass("skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found`);
|
|
1429
|
+
}
|
|
1430
|
+
return warn("skills_installed", "Skills installed", "No SKILL.md files found in ~/.claude/skills/");
|
|
1431
|
+
}
|
|
1432
|
+
function findSkillMd(dir) {
|
|
1433
|
+
const results = [];
|
|
1434
|
+
let entries;
|
|
1435
|
+
try {
|
|
1436
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1437
|
+
} catch {
|
|
1438
|
+
return results;
|
|
1439
|
+
}
|
|
1440
|
+
for (const entry of entries) {
|
|
1441
|
+
if (entry.isFile() && entry.name === "SKILL.md") {
|
|
1442
|
+
results.push(join13(dir, entry.name));
|
|
1443
|
+
} else if (entry.isDirectory()) {
|
|
1444
|
+
results.push(...findSkillMd(join13(dir, entry.name)));
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return results;
|
|
1448
|
+
}
|
|
1449
|
+
async function runDoctor(input) {
|
|
1450
|
+
const checks = [];
|
|
1451
|
+
checks.push(checkNodeVersion());
|
|
1452
|
+
checks.push(checkCliOnPath(input.argv));
|
|
1453
|
+
checks.push(await checkConfigFile(input.home));
|
|
1454
|
+
const wikiPathCheck = await checkWikiPathSet(input);
|
|
1455
|
+
checks.push(wikiPathCheck);
|
|
1456
|
+
const resolved = await resolveRuntimePath({ flag: void 0, envValue: input.envValue, home: input.home });
|
|
1457
|
+
const resolvedPath = resolved.ok ? resolved.data.path : void 0;
|
|
1458
|
+
checks.push(checkWikiPathExists(resolvedPath));
|
|
1459
|
+
checks.push(checkVaultStructure(resolvedPath));
|
|
1460
|
+
checks.push(checkSkillsInstalled(input.home));
|
|
1461
|
+
const summary = {
|
|
1462
|
+
pass: checks.filter((c) => c.status === "pass").length,
|
|
1463
|
+
warn: checks.filter((c) => c.status === "warn").length,
|
|
1464
|
+
error: checks.filter((c) => c.status === "error").length
|
|
1465
|
+
};
|
|
1466
|
+
const exitCode = summary.error > 0 ? ExitCode.DOCTOR_HAS_ERRORS : summary.warn > 0 ? ExitCode.DOCTOR_HAS_WARNINGS : ExitCode.OK;
|
|
1467
|
+
const statusIcon = { pass: "\u2713", warn: "\u26A0", error: "\u2717" };
|
|
1468
|
+
const lines = checks.map((c) => {
|
|
1469
|
+
const icon = statusIcon[c.status];
|
|
1470
|
+
const padded = c.label.padEnd(24);
|
|
1471
|
+
return ` ${icon} ${padded} ${c.detail}`;
|
|
1472
|
+
});
|
|
1473
|
+
lines.push("");
|
|
1474
|
+
lines.push(`${summary.pass} pass \xB7 ${summary.warn} warn \xB7 ${summary.error} error`);
|
|
1475
|
+
const humanHint = lines.join("\n");
|
|
1476
|
+
return { exitCode, result: ok({ checks, summary, humanHint }) };
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1239
1479
|
// src/cli.ts
|
|
1480
|
+
var pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
1240
1481
|
var program = new Command();
|
|
1241
|
-
program.name("skillwiki").description("Deterministic helpers for CodeWiki skills").version(
|
|
1482
|
+
program.name("skillwiki").description("Deterministic helpers for CodeWiki skills").version(pkg.version);
|
|
1242
1483
|
program.option("--human", "render terminal-readable output instead of JSON");
|
|
1243
1484
|
function emit(r) {
|
|
1244
1485
|
if (program.opts().human) printHuman(r.result);
|
|
@@ -1257,7 +1498,7 @@ program.command("orphans [vault]").action(async (vault) => emit(await runOrphans
|
|
|
1257
1498
|
})));
|
|
1258
1499
|
program.command("audit <file>").action(async (file) => emit(await runAudit({ file })));
|
|
1259
1500
|
program.command("install").option("--target <dir>", "target install directory", `${process.env.HOME ?? ""}/.claude/skills/`).option("--dry-run", "preview only", false).option("--skills-root <dir>", "source skills directory (defaults to packaged)").action(async (opts) => {
|
|
1260
|
-
const skillsRoot = opts.skillsRoot ?? new URL("
|
|
1501
|
+
const skillsRoot = opts.skillsRoot ?? new URL("../skills/", import.meta.url).pathname;
|
|
1261
1502
|
emit(await runInstall({ skillsRoot, target: opts.target, dryRun: !!opts.dryRun }));
|
|
1262
1503
|
});
|
|
1263
1504
|
program.command("path").option("--vault <dir>", "explicit vault override (runtime)").option("--target <dir>", "explicit target override (init-time)").option("--init-time", "use init-time chain instead of runtime", false).option("--explain", "include resolution chain in output", false).action(async (opts) => {
|
|
@@ -1344,6 +1585,17 @@ program.command("lint [vault]").option("--days <n>", "stale threshold", (s) => p
|
|
|
1344
1585
|
logThreshold: opts.logThreshold
|
|
1345
1586
|
}));
|
|
1346
1587
|
});
|
|
1588
|
+
var configCmd = program.command("config").description("manage skillwiki configuration");
|
|
1589
|
+
configCmd.command("get <key>").description("print the value of a config key").action(async (key) => emit(await runConfigGet({ key, home: process.env.HOME ?? "" })));
|
|
1590
|
+
configCmd.command("set <key> <value>").description("set a config key value").action(async (key, value) => emit(await runConfigSet({ key, value, home: process.env.HOME ?? "" })));
|
|
1591
|
+
configCmd.command("list").description("list all config key=value pairs").action(async () => emit(await runConfigList({ home: process.env.HOME ?? "" })));
|
|
1592
|
+
configCmd.command("path").description("print the config file path").action(async () => emit(await runConfigPath({ home: process.env.HOME ?? "" })));
|
|
1593
|
+
program.command("doctor").description("diagnose skillwiki setup issues").action(async () => emit(await runDoctor({
|
|
1594
|
+
home: process.env.HOME ?? "",
|
|
1595
|
+
envValue: process.env.WIKI_PATH,
|
|
1596
|
+
envLang: process.env.WIKI_LANG,
|
|
1597
|
+
argv: process.argv
|
|
1598
|
+
})));
|
|
1347
1599
|
program.parseAsync(process.argv).catch((e) => {
|
|
1348
1600
|
process.stdout.write(JSON.stringify({ ok: false, error: "INTERNAL", detail: { message: String(e) } }) + "\n");
|
|
1349
1601
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.4",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"bin": {
|
|
6
|
-
|
|
5
|
+
"bin": {
|
|
6
|
+
"skillwiki": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"templates",
|
|
11
|
+
"skills",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
7
14
|
"scripts": {
|
|
8
|
-
"build": "tsup",
|
|
15
|
+
"build": "tsup && rm -rf ./skills && cp -r ../skills ./skills",
|
|
9
16
|
"test": "vitest run",
|
|
10
17
|
"test:watch": "vitest",
|
|
11
18
|
"typecheck": "tsc --noEmit"
|
|
@@ -23,5 +30,7 @@
|
|
|
23
30
|
"typescript": "^5.7.0",
|
|
24
31
|
"vitest": "^2.1.0"
|
|
25
32
|
},
|
|
26
|
-
"engines": {
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20"
|
|
35
|
+
}
|
|
27
36
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skillwiki",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 10 prompt-only skills (wiki-* + proj-*) backed by the deterministic `skillwiki` CLI (8 subcommands, JSON-by-default).",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "karlorz",
|
|
7
|
+
"url": "https://github.com/karlorz"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/karlorz/llm-wiki",
|
|
10
|
+
"repository": "https://github.com/karlorz/llm-wiki",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"knowledge-base",
|
|
14
|
+
"wiki",
|
|
15
|
+
"obsidian",
|
|
16
|
+
"claude-code",
|
|
17
|
+
"skills",
|
|
18
|
+
"karpathy",
|
|
19
|
+
"markdown",
|
|
20
|
+
"research",
|
|
21
|
+
"rag-alternative"
|
|
22
|
+
]
|
|
23
|
+
}
|
package/skills/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# @skillwiki/skills
|
|
2
|
+
|
|
3
|
+
Prompt-only Markdown skills for Claude Code. Installed via `skillwiki install`.
|
|
4
|
+
|
|
5
|
+
| Namespace | Skills |
|
|
6
|
+
|---|---|
|
|
7
|
+
| `wiki-*` | `wiki-init`, `wiki-ingest`, `wiki-query`, `wiki-lint`, `wiki-crystallize`, `wiki-audit` |
|
|
8
|
+
| `proj-*` | `proj-init`, `proj-work`, `proj-distill`, `proj-decide` |
|
|
9
|
+
|
|
10
|
+
Each subdirectory holds one `SKILL.md`. No build step.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: proj-decide
|
|
3
|
+
description: Write an Architectural Decision Record (ADR). If the decision generalizes, also create a concepts/ page.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# proj-decide
|
|
7
|
+
|
|
8
|
+
## When to invoke
|
|
9
|
+
- User commits to an architectural decision worth recording for future reference.
|
|
10
|
+
|
|
11
|
+
## Pre-orientation reads
|
|
12
|
+
Standard four + project context.
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
1. Compose the ADR in `projects/{slug}/architecture/YYYY-MM-DD-{adr-slug}.md`. Frontmatter: kind=decision, status=in-progress or completed, project link.
|
|
16
|
+
2. `npx skillwiki validate <adr>`. If non-zero, STOP.
|
|
17
|
+
3. **Generalization check.** If the decision applies beyond this project, create a `concepts/` page with `provenance: project` (or `mixed` if research-informed).
|
|
18
|
+
4. Apply writes: ADR → (optional) concept page → vault `index.md` → vault `log.md` and project `log.md`.
|
|
19
|
+
|
|
20
|
+
## Stop conditions
|
|
21
|
+
- `validate` non-zero on either page.
|
|
22
|
+
|
|
23
|
+
## Forbidden
|
|
24
|
+
- Filing the concept page without explicit `provenance:`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: proj-distill
|
|
3
|
+
description: 2-step distillation (E4) — analyze project compound entry, then generate a vault concept page with provenance.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# proj-distill
|
|
7
|
+
|
|
8
|
+
## When to invoke
|
|
9
|
+
- A project compound entry captures a pattern that generalizes beyond the project.
|
|
10
|
+
|
|
11
|
+
## Pre-orientation reads
|
|
12
|
+
Standard four + project context.
|
|
13
|
+
|
|
14
|
+
## Steps (E4 — 2-step pattern)
|
|
15
|
+
1. **Step 1 — Analyze.** Read the source compound entry + linked work items. Output a candidate concept outline. STOP if no clear universal pattern is found — surface the reasoning instead of forcing a page.
|
|
16
|
+
2. **Step 2 — Generate.** Compose the vault concept page with `provenance: project` and `provenance_projects: ["[[slug]]"]`. Validate with `npx skillwiki validate`.
|
|
17
|
+
3. **Backlink.** Set `promoted_to: "[[concept-slug]]"` on the source compound entry.
|
|
18
|
+
4. **Apply writes in order.** Vault concept page → backlink update → project `log.md` → vault `index.md` → vault `log.md`.
|
|
19
|
+
|
|
20
|
+
## Stop conditions
|
|
21
|
+
- No clear universal pattern.
|
|
22
|
+
- `validate` non-zero on either page.
|
|
23
|
+
|
|
24
|
+
## Forbidden
|
|
25
|
+
- Skipping Step 1 (no direct generation).
|
|
26
|
+
- Updating index/logs before `validate` passes.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: proj-init
|
|
3
|
+
description: Bootstrap a project workspace at projects/{slug}/ with README, requirements/, architecture/, work/, compound/.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# proj-init
|
|
7
|
+
|
|
8
|
+
## When to invoke
|
|
9
|
+
- User starts a new project that should live inside the vault.
|
|
10
|
+
|
|
11
|
+
## Pre-orientation reads
|
|
12
|
+
Standard four reads (vault SCHEMA, index, log) — no project context yet.
|
|
13
|
+
|
|
14
|
+
## Inputs
|
|
15
|
+
- Slug (lowercase, hyphenated).
|
|
16
|
+
- One-line intent.
|
|
17
|
+
|
|
18
|
+
## Steps
|
|
19
|
+
1. Verify `projects/{slug}/` does not exist.
|
|
20
|
+
2. Create folders: `projects/{slug}/{requirements,architecture,work,compound}/`.
|
|
21
|
+
3. Render `projects/{slug}/README.md` from `project-README.md` template, filling `{{slug}}` and `{{date}}`.
|
|
22
|
+
4. Update vault `index.md` "Projects" section: add `- [[projects/{slug}]]`.
|
|
23
|
+
5. Append vault `log.md` entry: "Project {slug} initialized."
|
|
24
|
+
|
|
25
|
+
## Stop conditions
|
|
26
|
+
- `projects/{slug}/` already exists.
|
|
27
|
+
|
|
28
|
+
## Forbidden
|
|
29
|
+
- Modifying any other project's files.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: proj-work
|
|
3
|
+
description: Open or run a work item under projects/{slug}/work/YYYY-MM-DD-{slug}/. Redirects brainstorming/writing-plans output paths.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# proj-work
|
|
7
|
+
|
|
8
|
+
## When to invoke
|
|
9
|
+
- User starts a feature, issue, refactor, or decision inside an existing project.
|
|
10
|
+
- Brainstorming or writing-plans skills would otherwise default-write outside the project tree.
|
|
11
|
+
|
|
12
|
+
## Pre-orientation reads
|
|
13
|
+
Standard four + project context (project README, last ~5 work logs).
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
1. Determine `kind:` (feature | issue | refactor | decision) and slug.
|
|
17
|
+
2. Create folder `projects/{slug}/work/YYYY-MM-DD-{work-slug}/`.
|
|
18
|
+
3. Override default output paths for any nested skill: `spec.md`, `plan.md`, and `log.md` are written here, not at vault root.
|
|
19
|
+
4. Validate work-item frontmatter via `npx skillwiki validate <spec.md>`. If non-zero, STOP.
|
|
20
|
+
5. Manage status transitions: `planned` → `in-progress` → `completed` (set `completed:` date) or `abandoned`.
|
|
21
|
+
6. Append vault `log.md` entry on creation and on each status transition.
|
|
22
|
+
|
|
23
|
+
## Stop conditions
|
|
24
|
+
- `validate` non-zero.
|
|
25
|
+
- Conflicting work folder name.
|
|
26
|
+
|
|
27
|
+
## Forbidden
|
|
28
|
+
- Writing spec/plan files outside the work folder.
|
|
29
|
+
- Marking `status: completed` without a `completed:` date.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wiki-audit
|
|
3
|
+
description: Verify per-page that every ^[raw/...] resolves and sources frontmatter matches the body.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# wiki-audit
|
|
7
|
+
|
|
8
|
+
## When This Skill Activates
|
|
9
|
+
|
|
10
|
+
- User asks for a per-page audit or invokes a pre-merge gate.
|
|
11
|
+
- A vault is resolvable (see step 0).
|
|
12
|
+
|
|
13
|
+
## Output language
|
|
14
|
+
|
|
15
|
+
Run `skillwiki lang` at the start. Generate audit narrative and `--human` summaries in the resolved language. Frontmatter keys, file names, schema headers, index/log structural lines, citation markers, and wikilink slugs MUST stay English.
|
|
16
|
+
|
|
17
|
+
## Pre-orientation reads
|
|
18
|
+
Standard four reads.
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
0. **Resolve vault and language.** Run `skillwiki path` (fail if NO_VAULT_CONFIGURED) and `skillwiki lang`.
|
|
22
|
+
1. `npx skillwiki audit <page>`. Read the JSON report.
|
|
23
|
+
2. Reason over the report:
|
|
24
|
+
- For each unresolved marker: suggest ingesting the missing source or correcting the path.
|
|
25
|
+
- For each `unused_sources` entry: suggest adding a body marker or removing from `sources:`.
|
|
26
|
+
- For each `missing_from_sources` entry: suggest adding to `sources:`.
|
|
27
|
+
3. Append one `log.md` entry summarizing the audit and any suggested follow-ups.
|
|
28
|
+
|
|
29
|
+
## Stop conditions
|
|
30
|
+
None — audit always completes.
|
|
31
|
+
|
|
32
|
+
## Forbidden
|
|
33
|
+
- Auto-applying suggested fixes (audit is observation-only).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wiki-crystallize
|
|
3
|
+
description: Distill the current working session into a typed-knowledge page with provenance.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# wiki-crystallize
|
|
7
|
+
|
|
8
|
+
## When This Skill Activates
|
|
9
|
+
|
|
10
|
+
- User asks to crystallize, consolidate, or promote draft material into typed-knowledge pages.
|
|
11
|
+
- A vault is resolvable (see step 0).
|
|
12
|
+
|
|
13
|
+
## Output language
|
|
14
|
+
|
|
15
|
+
Run `skillwiki lang` at the start. Generate consolidated page prose and `--human` summaries in the resolved language. Frontmatter keys, file names, schema headers, index/log structural lines, citation markers, and wikilink slugs MUST stay English.
|
|
16
|
+
|
|
17
|
+
## Pre-orientation reads
|
|
18
|
+
Standard four reads. If cwd is inside `projects/{slug}/`, also read project README and recent work logs.
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
0. **Resolve vault and language.** Run `skillwiki path` (fail if NO_VAULT_CONFIGURED) and `skillwiki lang`.
|
|
22
|
+
1. Identify type: entity / concept / comparison / query / summary.
|
|
23
|
+
2. Set `provenance:`. Default `research`. If in project context: `project` with `provenance_projects: ["[[slug]]"]`.
|
|
24
|
+
3. Compose the page with citations pre-attached. Reuse existing `raw/` sources where possible.
|
|
25
|
+
4. `npx skillwiki validate <page>`. If non-zero, STOP.
|
|
26
|
+
5. Apply writes: page → `index.md` → `log.md`.
|
|
27
|
+
|
|
28
|
+
## Stop conditions
|
|
29
|
+
- `validate` non-zero.
|
|
30
|
+
- Missing `provenance:` for project-context runs.
|
|
31
|
+
|
|
32
|
+
## Forbidden
|
|
33
|
+
- Filing without explicit `provenance:`.
|
|
34
|
+
- Updating `index.md` before `validate` passes.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wiki-ingest
|
|
3
|
+
description: Convert URLs, files, or pasted text into typed-knowledge pages with raw provenance. Single-pass v1.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# wiki-ingest
|
|
7
|
+
|
|
8
|
+
## When This Skill Activates
|
|
9
|
+
|
|
10
|
+
- User shares a URL, paste, or local file to capture in the vault.
|
|
11
|
+
- The output target is `entities/`, `concepts/`, `comparisons/`, or `queries/`.
|
|
12
|
+
- A vault is resolvable (see step 0).
|
|
13
|
+
|
|
14
|
+
## Output language
|
|
15
|
+
|
|
16
|
+
Run `skillwiki lang` at the start. Generate page-body prose, narrative sections, and `--human` summaries in the resolved language. Frontmatter keys, file names, schema headers, index/log structural lines, citation markers, and wikilink slugs MUST stay English.
|
|
17
|
+
|
|
18
|
+
## Pre-orientation reads (mandatory before any write)
|
|
19
|
+
1. `SCHEMA.md`
|
|
20
|
+
2. `index.md`
|
|
21
|
+
3. Last 20–30 entries of `log.md`
|
|
22
|
+
4. (Project context only) `projects/{slug}/README.md` and last ~5 work-item logs.
|
|
23
|
+
|
|
24
|
+
## Steps (in order — N6, N7, N8)
|
|
25
|
+
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.
|
|
26
|
+
1. **Guard.** For each URL: run `npx skillwiki fetch-guard <url>`. If exit ≠ 0, STOP and surface the error. Do not retry.
|
|
27
|
+
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).
|
|
28
|
+
3. **Hash.** Write the raw file (frontmatter + body). Run `npx skillwiki hash <raw-file>` and embed the result in raw frontmatter `sha256:`.
|
|
29
|
+
4. **Generate page(s).** Compose typed-knowledge page(s) with citations pre-attached (`^[raw/...]` markers).
|
|
30
|
+
5. **Validate.** For each generated page: run `npx skillwiki validate <page>`. If exit ≠ 0, STOP — do not write index/log.
|
|
31
|
+
6. **Apply writes in order.** raw → page(s) → `index.md` → `log.md`.
|
|
32
|
+
7. **Confidence flag.** If only one source is cited, set `confidence: low`.
|
|
33
|
+
|
|
34
|
+
## Provenance defaults
|
|
35
|
+
- Default `provenance: research`.
|
|
36
|
+
- If cwd is inside `projects/{slug}/`, set `provenance: project` and add `provenance_projects: ["[[slug]]"]`.
|
|
37
|
+
|
|
38
|
+
## Stop conditions
|
|
39
|
+
- `fetch-guard` non-zero.
|
|
40
|
+
- Fetch timeout / size limit exceeded.
|
|
41
|
+
- `validate` non-zero on any page.
|
|
42
|
+
- sha256 already exists in vault for the same source.
|
|
43
|
+
|
|
44
|
+
## Forbidden
|
|
45
|
+
- Skipping `fetch-guard`.
|
|
46
|
+
- Updating `index.md` or `log.md` before all pages validate.
|
|
47
|
+
- Modifying any existing file in `raw/`.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wiki-init
|
|
3
|
+
description: Bootstrap a CodeWiki vault — domain-aware SCHEMA.md, index.md, log.md, and ~/.skillwiki/.env binding. Use when starting a fresh vault.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# wiki-init
|
|
7
|
+
|
|
8
|
+
## When This Skill Activates
|
|
9
|
+
|
|
10
|
+
- User asks to create, build, or start a vault, wiki, or knowledge base.
|
|
11
|
+
- The resolved vault path (see step 0) does not yet contain SCHEMA.md.
|
|
12
|
+
|
|
13
|
+
## Pre-orientation reads
|
|
14
|
+
|
|
15
|
+
None for the first run.
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
0. **Resolve target.** Run `skillwiki path --init-time` to see what target the CLI will pick. Confirm with the user, or override with `--target <dir>`.
|
|
20
|
+
1. Verify target is empty or has no SCHEMA.md.
|
|
21
|
+
2. Ask the domain question: "What knowledge domain will this vault cover? Be specific."
|
|
22
|
+
3. Propose a 10–15 tag taxonomy tailored to the domain. Confirm or accept the user's revision.
|
|
23
|
+
4. Ask the language question: "What language should generated page prose use? Default is `en`. Aliases like `chinese-traditional` or `zh-Hant` are accepted."
|
|
24
|
+
5. Run `skillwiki init --target <dir> --domain "<answer>" --taxonomy "<comma list>" --lang "<lang>"`.
|
|
25
|
+
6. **Suggest first sources.** Propose 3–5 initial sources (URLs, papers, articles) appropriate to the domain. Prompt the user to provide the first one to ingest, then hand off to wiki-ingest.
|
|
26
|
+
|
|
27
|
+
## Stop conditions
|
|
28
|
+
|
|
29
|
+
- Target non-empty and `--force` not consented.
|
|
30
|
+
- `~/.skillwiki/.env` already binds a different vault or language and `--force` not consented.
|
|
31
|
+
|
|
32
|
+
## Forbidden
|
|
33
|
+
|
|
34
|
+
- Modifying anything outside the target directory or `~/.skillwiki/.env`.
|
|
35
|
+
- Writing to `~/.hermes/.env` (read-only fallback).
|
|
36
|
+
- Running any LLM-driven content generation in this skill.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wiki-lint
|
|
3
|
+
description: Vault health check via the umbrella `skillwiki lint` subcommand. Read-only by default; rotation requires explicit user consent.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# wiki-lint
|
|
7
|
+
|
|
8
|
+
## When This Skill Activates
|
|
9
|
+
|
|
10
|
+
- User asks for a vault health report, lint, or audit.
|
|
11
|
+
- Periodic maintenance.
|
|
12
|
+
|
|
13
|
+
## Pre-orientation reads
|
|
14
|
+
|
|
15
|
+
Standard four reads.
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
0. Resolve vault: `skillwiki path` (record source for context).
|
|
20
|
+
1. Run `skillwiki lint <vault>`. Read the JSON.
|
|
21
|
+
2. Reason over findings; present grouped by severity with concrete suggested actions per kind.
|
|
22
|
+
3. If `log_rotate_needed` is present and the user consents, run `skillwiki log-rotate <vault> --apply`. Otherwise leave alone.
|
|
23
|
+
4. Append one `log.md` entry summarizing the lint counts (errors/warnings/info).
|
|
24
|
+
|
|
25
|
+
## Stop conditions
|
|
26
|
+
|
|
27
|
+
None — lint reports all findings even on per-page errors.
|
|
28
|
+
|
|
29
|
+
## Forbidden
|
|
30
|
+
|
|
31
|
+
- Auto-rotating logs.
|
|
32
|
+
- Auto-updating sha256 fields.
|
|
33
|
+
- Modifying any page beyond the lint summary entry in `log.md`.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wiki-query
|
|
3
|
+
description: Search the vault and synthesize an answer with E2 4-signal ranking. Optional file to queries/ or comparisons/.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# wiki-query
|
|
7
|
+
|
|
8
|
+
## When This Skill Activates
|
|
9
|
+
|
|
10
|
+
- User asks a question that should be answered from vault contents.
|
|
11
|
+
- A vault is resolvable (see step 0).
|
|
12
|
+
|
|
13
|
+
## Output language
|
|
14
|
+
|
|
15
|
+
Run `skillwiki lang` at the start. Generate query-result prose and `--human` summaries in the resolved language. Frontmatter keys, file names, schema headers, index/log structural lines, citation markers, and wikilink slugs MUST stay English.
|
|
16
|
+
|
|
17
|
+
## Pre-orientation reads
|
|
18
|
+
Standard four reads (SCHEMA, index, log, project context if applicable).
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
0. **Resolve vault and language.** Run `skillwiki path` (fail if NO_VAULT_CONFIGURED) and `skillwiki lang`.
|
|
22
|
+
1. **Determine scope.** Ask the user once if ambiguous: vault | current project | project+concepts.
|
|
23
|
+
2. **Refresh graph.** If `.skillwiki/graph.json` is missing or older than 24h: `npx skillwiki graph build <vault>`.
|
|
24
|
+
3. **Compute overlap.** `npx skillwiki overlap <vault>`.
|
|
25
|
+
4. **Score candidates** in prompt using the 4 signals:
|
|
26
|
+
- Direct wikilink: 3.0×
|
|
27
|
+
- Source overlap: 4.0× (read from overlap output)
|
|
28
|
+
- Adamic-Adar: 1.5× (read from graph output)
|
|
29
|
+
- Type affinity: 1.0×
|
|
30
|
+
5. **Read top candidates** in full (frontmatter + body).
|
|
31
|
+
6. **Synthesize answer** with explicit citations to the candidate pages.
|
|
32
|
+
7. **Optional file.** If user accepts: write to `queries/<slug>.md` or `comparisons/<slug>.md` with full frontmatter, validate, then update `index.md` then `log.md`.
|
|
33
|
+
|
|
34
|
+
## Stop conditions
|
|
35
|
+
- Zero matching pages.
|
|
36
|
+
- User declines to file.
|
|
37
|
+
|
|
38
|
+
## Forbidden
|
|
39
|
+
- Filing without `validate` passing.
|
|
40
|
+
- Skipping the orientation reads even for "quick" queries.
|