skillwiki 0.2.0-beta.4 → 0.2.0-beta.6
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 +44 -51
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +3 -2
- package/skills/hooks/hooks.json +16 -0
- package/skills/hooks/run-hook.cmd +43 -0
- package/skills/hooks/session-start +29 -0
- package/skills/package.json +2 -2
- package/skills/using-skillwiki/SKILL.md +57 -0
package/dist/cli.js
CHANGED
|
@@ -70,8 +70,8 @@ var ExitCode = {
|
|
|
70
70
|
function ok(data) {
|
|
71
71
|
return { ok: true, data };
|
|
72
72
|
}
|
|
73
|
-
function err(
|
|
74
|
-
return detail === void 0 ? { ok: false, error
|
|
73
|
+
function err(error, detail) {
|
|
74
|
+
return detail === void 0 ? { ok: false, error } : { ok: false, error, detail };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// ../shared/src/schemas.ts
|
|
@@ -491,14 +491,9 @@ import { join as join2 } from "path";
|
|
|
491
491
|
// src/utils/dotenv.ts
|
|
492
492
|
import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
493
493
|
import { dirname as dirname2 } from "path";
|
|
494
|
-
var
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
try {
|
|
498
|
-
text = await readFile4(path, "utf8");
|
|
499
|
-
} catch {
|
|
500
|
-
return {};
|
|
501
|
-
}
|
|
494
|
+
var CONFIG_KEYS = ["WIKI_PATH", "WIKI_LANG"];
|
|
495
|
+
var _whitelist = new Set(CONFIG_KEYS);
|
|
496
|
+
function parseDotenvText(text) {
|
|
502
497
|
const out = {};
|
|
503
498
|
for (const rawLine of text.split(/\r?\n/)) {
|
|
504
499
|
const line = rawLine.trim();
|
|
@@ -507,12 +502,21 @@ async function parseDotenvFile(path) {
|
|
|
507
502
|
if (eq <= 0) continue;
|
|
508
503
|
const key = line.slice(0, eq).trim();
|
|
509
504
|
const value = line.slice(eq + 1).trim();
|
|
510
|
-
if (!
|
|
505
|
+
if (!_whitelist.has(key)) continue;
|
|
511
506
|
if (value.length === 0) continue;
|
|
512
507
|
out[key] = value;
|
|
513
508
|
}
|
|
514
509
|
return out;
|
|
515
510
|
}
|
|
511
|
+
async function parseDotenvFile(path) {
|
|
512
|
+
let text;
|
|
513
|
+
try {
|
|
514
|
+
text = await readFile4(path, "utf8");
|
|
515
|
+
} catch {
|
|
516
|
+
return {};
|
|
517
|
+
}
|
|
518
|
+
return parseDotenvText(text);
|
|
519
|
+
}
|
|
516
520
|
async function writeDotenv(filePath, entries, originalContent) {
|
|
517
521
|
const lines = originalContent !== void 0 ? updateLines(originalContent, entries) : freshLines(entries);
|
|
518
522
|
await mkdir2(dirname2(filePath), { recursive: true });
|
|
@@ -1296,7 +1300,7 @@ import { readFile as readFile11 } from "fs/promises";
|
|
|
1296
1300
|
import { existsSync } from "fs";
|
|
1297
1301
|
import { join as join12 } from "path";
|
|
1298
1302
|
function validateKey(key) {
|
|
1299
|
-
return key
|
|
1303
|
+
return CONFIG_KEYS.includes(key);
|
|
1300
1304
|
}
|
|
1301
1305
|
function configPath(home) {
|
|
1302
1306
|
return join12(home, ".skillwiki", ".env");
|
|
@@ -1320,7 +1324,7 @@ async function runConfigSet(input) {
|
|
|
1320
1324
|
originalContent = await readFile11(filePath, "utf8");
|
|
1321
1325
|
} catch {
|
|
1322
1326
|
}
|
|
1323
|
-
const existing = originalContent !== void 0 ?
|
|
1327
|
+
const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
|
|
1324
1328
|
const merged = { ...existing, [input.key]: input.value };
|
|
1325
1329
|
await writeDotenv(filePath, merged, originalContent);
|
|
1326
1330
|
return { exitCode: ExitCode.OK, result: ok({ key: input.key, value: input.value, written: true, humanHint: `${input.key}=${input.value}` }) };
|
|
@@ -1342,71 +1346,58 @@ async function runConfigPath(input) {
|
|
|
1342
1346
|
import { existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
1343
1347
|
import { join as join13 } from "path";
|
|
1344
1348
|
import { execSync } from "child_process";
|
|
1345
|
-
function
|
|
1346
|
-
return { id, label, status
|
|
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 };
|
|
1349
|
+
function check(status, id, label, detail) {
|
|
1350
|
+
return { id, label, status, detail };
|
|
1353
1351
|
}
|
|
1354
1352
|
function checkNodeVersion() {
|
|
1355
1353
|
const major = parseInt(process.version.slice(1).split(".")[0], 10);
|
|
1356
1354
|
if (major >= 20) {
|
|
1357
|
-
return
|
|
1355
|
+
return check("pass", "node_version", "Node.js version", `v${major} >= 20`);
|
|
1358
1356
|
}
|
|
1359
|
-
return
|
|
1357
|
+
return check("error", "node_version", "Node.js version", `Node.js v${major} is below minimum v20`);
|
|
1360
1358
|
}
|
|
1361
1359
|
function checkCliOnPath(argv) {
|
|
1362
1360
|
if (argv.length >= 2 && argv[1].endsWith("cli.js")) {
|
|
1363
|
-
return
|
|
1361
|
+
return check("warn", "cli_on_path", "skillwiki on PATH", "Running via node cli.js (dev mode) \u2014 PATH check skipped");
|
|
1364
1362
|
}
|
|
1365
1363
|
if (argv.length >= 2 && argv[1] === "skillwiki") {
|
|
1366
|
-
return
|
|
1364
|
+
return check("pass", "cli_on_path", "skillwiki on PATH", "Running as skillwiki \u2014 already on PATH");
|
|
1367
1365
|
}
|
|
1368
1366
|
try {
|
|
1369
1367
|
execSync("which skillwiki 2>/dev/null", { encoding: "utf8" }).trim();
|
|
1370
|
-
return
|
|
1368
|
+
return check("pass", "cli_on_path", "skillwiki on PATH", "skillwiki found on PATH");
|
|
1371
1369
|
} catch {
|
|
1372
|
-
return
|
|
1370
|
+
return check("warn", "cli_on_path", "skillwiki on PATH", "skillwiki not found on PATH");
|
|
1373
1371
|
}
|
|
1374
1372
|
}
|
|
1375
1373
|
async function checkConfigFile(home) {
|
|
1376
|
-
const cfgPath =
|
|
1374
|
+
const cfgPath = configPath(home);
|
|
1377
1375
|
if (!existsSync2(cfgPath)) {
|
|
1378
|
-
return
|
|
1376
|
+
return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
|
|
1379
1377
|
}
|
|
1380
1378
|
try {
|
|
1381
1379
|
const map = await parseDotenvFile(cfgPath);
|
|
1382
1380
|
const keys = Object.keys(map);
|
|
1383
|
-
return
|
|
1381
|
+
return check("pass", "config_file", "Config file exists", `Found with keys: ${keys.length > 0 ? keys.join(", ") : "(none set)"}`);
|
|
1384
1382
|
} catch (e) {
|
|
1385
|
-
return
|
|
1383
|
+
return check("warn", "config_file", "Config file exists", `Failed to parse ${cfgPath}: ${String(e)}`);
|
|
1386
1384
|
}
|
|
1387
1385
|
}
|
|
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
1386
|
function checkWikiPathExists(resolvedPath) {
|
|
1396
1387
|
if (resolvedPath === void 0) {
|
|
1397
|
-
return
|
|
1388
|
+
return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
1398
1389
|
}
|
|
1399
1390
|
if (existsSync2(resolvedPath) && statSync(resolvedPath).isDirectory()) {
|
|
1400
|
-
return
|
|
1391
|
+
return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
|
|
1401
1392
|
}
|
|
1402
|
-
return
|
|
1393
|
+
return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
|
|
1403
1394
|
}
|
|
1404
1395
|
function checkVaultStructure(resolvedPath) {
|
|
1405
1396
|
if (resolvedPath === void 0) {
|
|
1406
|
-
return
|
|
1397
|
+
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
1407
1398
|
}
|
|
1408
1399
|
if (!existsSync2(resolvedPath)) {
|
|
1409
|
-
return
|
|
1400
|
+
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
1410
1401
|
}
|
|
1411
1402
|
const missing = [];
|
|
1412
1403
|
if (!existsSync2(join13(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
@@ -1414,20 +1405,20 @@ function checkVaultStructure(resolvedPath) {
|
|
|
1414
1405
|
if (!existsSync2(join13(resolvedPath, dir))) missing.push(dir + "/");
|
|
1415
1406
|
}
|
|
1416
1407
|
if (missing.length === 0) {
|
|
1417
|
-
return
|
|
1408
|
+
return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
|
|
1418
1409
|
}
|
|
1419
|
-
return
|
|
1410
|
+
return check("error", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")}`);
|
|
1420
1411
|
}
|
|
1421
1412
|
function checkSkillsInstalled(home) {
|
|
1422
1413
|
const skillsDir = join13(home, ".claude", "skills");
|
|
1423
1414
|
if (!existsSync2(skillsDir)) {
|
|
1424
|
-
return
|
|
1415
|
+
return check("warn", "skills_installed", "Skills installed", `${skillsDir} not found`);
|
|
1425
1416
|
}
|
|
1426
1417
|
const found = findSkillMd(skillsDir);
|
|
1427
1418
|
if (found.length > 0) {
|
|
1428
|
-
return
|
|
1419
|
+
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found`);
|
|
1429
1420
|
}
|
|
1430
|
-
return
|
|
1421
|
+
return check("warn", "skills_installed", "Skills installed", "No SKILL.md files found in ~/.claude/skills/");
|
|
1431
1422
|
}
|
|
1432
1423
|
function findSkillMd(dir) {
|
|
1433
1424
|
const results = [];
|
|
@@ -1451,9 +1442,12 @@ async function runDoctor(input) {
|
|
|
1451
1442
|
checks.push(checkNodeVersion());
|
|
1452
1443
|
checks.push(checkCliOnPath(input.argv));
|
|
1453
1444
|
checks.push(await checkConfigFile(input.home));
|
|
1454
|
-
const wikiPathCheck = await checkWikiPathSet(input);
|
|
1455
|
-
checks.push(wikiPathCheck);
|
|
1456
1445
|
const resolved = await resolveRuntimePath({ flag: void 0, envValue: input.envValue, home: input.home });
|
|
1446
|
+
if (resolved.ok) {
|
|
1447
|
+
checks.push(check("pass", "wiki_path_set", "WIKI_PATH configured", `Resolved via ${resolved.data.source}: ${resolved.data.path}`));
|
|
1448
|
+
} else {
|
|
1449
|
+
checks.push(check("error", "wiki_path_set", "WIKI_PATH configured", "No vault configured. Run `skillwiki init` or pass --vault."));
|
|
1450
|
+
}
|
|
1457
1451
|
const resolvedPath = resolved.ok ? resolved.data.path : void 0;
|
|
1458
1452
|
checks.push(checkWikiPathExists(resolvedPath));
|
|
1459
1453
|
checks.push(checkVaultStructure(resolvedPath));
|
|
@@ -1593,7 +1587,6 @@ configCmd.command("path").description("print the config file path").action(async
|
|
|
1593
1587
|
program.command("doctor").description("diagnose skillwiki setup issues").action(async () => emit(await runDoctor({
|
|
1594
1588
|
home: process.env.HOME ?? "",
|
|
1595
1589
|
envValue: process.env.WIKI_PATH,
|
|
1596
|
-
envLang: process.env.WIKI_LANG,
|
|
1597
1590
|
argv: process.argv
|
|
1598
1591
|
})));
|
|
1599
1592
|
program.parseAsync(process.argv).catch((e) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.2.0-beta.6",
|
|
4
|
+
"skills": "./",
|
|
5
|
+
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 11 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI (8 subcommands, JSON-by-default).",
|
|
5
6
|
"author": {
|
|
6
7
|
"name": "karlorz",
|
|
7
8
|
"url": "https://github.com/karlorz"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
: << 'CMDBLOCK'
|
|
2
|
+
@echo off
|
|
3
|
+
REM Cross-platform polyglot wrapper for hook scripts.
|
|
4
|
+
REM On Windows: cmd.exe runs the batch portion, which finds and calls bash.
|
|
5
|
+
REM On Unix: the shell interprets this as a script (: is a no-op in bash).
|
|
6
|
+
REM
|
|
7
|
+
REM Hook scripts use extensionless filenames (e.g. "session-start" not
|
|
8
|
+
REM "session-start.sh") so Claude Code's Windows auto-detection -- which
|
|
9
|
+
REM prepends "bash" to any command containing .sh -- doesn't interfere.
|
|
10
|
+
|
|
11
|
+
if "%~1"=="" (
|
|
12
|
+
echo run-hook.cmd: missing script name >&2
|
|
13
|
+
exit /b 1
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
set "HOOK_DIR=%~dp0"
|
|
17
|
+
|
|
18
|
+
REM Try Git for Windows bash in standard locations
|
|
19
|
+
if exist "C:\Program Files\Git\bin\bash.exe" (
|
|
20
|
+
"C:\Program Files\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
|
21
|
+
exit /b %ERRORLEVEL%
|
|
22
|
+
)
|
|
23
|
+
if exist "C:\Program Files (x86)\Git\bin\bash.exe" (
|
|
24
|
+
"C:\Program Files (x86)\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
|
25
|
+
exit /b %ERRORLEVEL%
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
REM Try bash on PATH
|
|
29
|
+
where bash >nul 2>nul
|
|
30
|
+
if %ERRORLEVEL% equ 0 (
|
|
31
|
+
bash "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
|
32
|
+
exit /b %ERRORLEVEL%
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
REM No bash found - exit silently
|
|
36
|
+
exit /b 0
|
|
37
|
+
CMDBLOCK
|
|
38
|
+
|
|
39
|
+
# Unix: run the named script directly
|
|
40
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
41
|
+
SCRIPT_NAME="$1"
|
|
42
|
+
shift
|
|
43
|
+
exec bash "${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SessionStart hook for skillwiki plugin
|
|
3
|
+
# Injects using-skillwiki SKILL.md content into every conversation.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
8
|
+
|
|
9
|
+
skill_content=$(cat "${PLUGIN_ROOT}/using-skillwiki/SKILL.md" 2>/dev/null || echo "Error reading using-skillwiki skill")
|
|
10
|
+
|
|
11
|
+
# Escape string for JSON embedding using bash parameter substitution.
|
|
12
|
+
# Each ${s//old/new} is a single C-level pass.
|
|
13
|
+
escape_for_json() {
|
|
14
|
+
local s="$1"
|
|
15
|
+
s="${s//\\/\\\\}"
|
|
16
|
+
s="${s//\"/\\\"}"
|
|
17
|
+
s="${s//$'\n'/\\n}"
|
|
18
|
+
s="${s//$'\r'/\\r}"
|
|
19
|
+
s="${s//$'\t'/\\t}"
|
|
20
|
+
printf '%s' "$s"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
skill_escaped=$(escape_for_json "$skill_content")
|
|
24
|
+
session_context="<EXTREMELY_IMPORTANT>\nYou have skillwiki.\n\n**Below is the full content of your 'skillwiki:using-skillwiki' skill - your introduction to the skillwiki skills. For all other skills, use the 'Skill' tool:**\n\n${skill_escaped}\n</EXTREMELY_IMPORTANT>"
|
|
25
|
+
|
|
26
|
+
# Uses printf instead of heredoc to work around bash 5.3+ heredoc hang.
|
|
27
|
+
printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$session_context"
|
|
28
|
+
|
|
29
|
+
exit 0
|
package/skills/package.json
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: using-skillwiki
|
|
3
|
+
description: Invoke at session start or when knowledge-base tasks arise — maps all wiki-*/proj-* skills and teaches the skillwiki CLI workflow
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<SUBAGENT-STOP>
|
|
7
|
+
If you were dispatched as a subagent to execute a specific task, skip this skill.
|
|
8
|
+
</SUBAGENT-STOP>
|
|
9
|
+
|
|
10
|
+
# using-skillwiki
|
|
11
|
+
|
|
12
|
+
You have skillwiki — a project-aware Karpathy-style knowledge base for Claude Code.
|
|
13
|
+
|
|
14
|
+
## When to Use These Skills
|
|
15
|
+
|
|
16
|
+
Invoke a skillwiki skill when the user:
|
|
17
|
+
- Wants to create, build, or start a vault/wiki/knowledge base
|
|
18
|
+
- Mentions ingesting sources, reading URLs into notes, converting content
|
|
19
|
+
- Asks to search, query, or find information in their vault
|
|
20
|
+
- Wants a health check or lint on their vault
|
|
21
|
+
- Mentions crystallizing a session into a note
|
|
22
|
+
- Talks about project workspaces, ADRs, or distillation
|
|
23
|
+
- Asks about their skillwiki configuration or setup health
|
|
24
|
+
|
|
25
|
+
## Skill Map
|
|
26
|
+
|
|
27
|
+
| Skill | When to Invoke |
|
|
28
|
+
|-------|----------------|
|
|
29
|
+
| `wiki-init` | Bootstrap a new vault — SCHEMA.md, index.md, log.md, ~/.skillwiki/.env |
|
|
30
|
+
| `wiki-ingest` | Convert URLs, files, or pasted text into typed-knowledge pages |
|
|
31
|
+
| `wiki-query` | Search the vault and synthesize an answer with ranked results |
|
|
32
|
+
| `wiki-lint` | Vault health check (stale pages, oversized pages, log rotation) |
|
|
33
|
+
| `wiki-crystallize` | Distill the current working session into a typed-knowledge page |
|
|
34
|
+
| `wiki-audit` | Verify raw provenance references and source frontmatter integrity |
|
|
35
|
+
| `proj-init` | Bootstrap a project workspace (README, requirements, architecture) |
|
|
36
|
+
| `proj-work` | Open or run a work item under a project's work/ directory |
|
|
37
|
+
| `proj-distill` | Distill project compound entries into vault concept pages |
|
|
38
|
+
| `proj-decide` | Write an Architectural Decision Record (ADR) |
|
|
39
|
+
|
|
40
|
+
## CLI Backbone
|
|
41
|
+
|
|
42
|
+
All skills are backed by the `skillwiki` CLI — a deterministic tool with no LLM calls. It handles path resolution, config management, validation, and linting. Skills invoke it via Bash for the mechanical parts and use Claude for the creative parts.
|
|
43
|
+
|
|
44
|
+
Key CLI subcommands: `init`, `lint`, `config`, `doctor`, `path`, `lang`, `install`, `graph build`.
|
|
45
|
+
|
|
46
|
+
Run `skillwiki doctor` to diagnose setup issues. Run `skillwiki config list` to see current configuration.
|
|
47
|
+
|
|
48
|
+
## Typical Workflow
|
|
49
|
+
|
|
50
|
+
1. **Init** (`wiki-init`) — create vault, set domain and taxonomy
|
|
51
|
+
2. **Ingest** (`wiki-ingest`) — add sources, build pages
|
|
52
|
+
3. **Query** (`wiki-query`) — search and synthesize answers
|
|
53
|
+
4. **Lint** (`wiki-lint`) — periodic health checks
|
|
54
|
+
5. **Crystallize** (`wiki-crystallize`) — save session insights as pages
|
|
55
|
+
6. **Audit** (`wiki-audit`) — verify source integrity
|
|
56
|
+
|
|
57
|
+
For longer-running project work, use `proj-init` → `proj-work` → `proj-distill` / `proj-decide`.
|