skillwiki 0.4.2 → 0.4.4-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +171 -18
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/agents/proj-decide.md +54 -0
- package/skills/agents/proj-distill.md +60 -0
- package/skills/agents/proj-init.md +58 -0
- package/skills/agents/proj-work.md +64 -0
- package/skills/agents/wiki-adapter-prd.md +74 -0
- package/skills/agents/wiki-add-task.md +62 -0
- package/skills/agents/wiki-archive.md +48 -0
- package/skills/agents/wiki-audit.md +46 -0
- package/skills/agents/wiki-canvas.md +44 -0
- package/skills/agents/wiki-crystallize.md +55 -0
- package/skills/agents/wiki-gate-plan-mode.md +57 -0
- package/skills/agents/wiki-ingest.md +68 -0
- package/skills/agents/wiki-lint.md +47 -0
- package/skills/agents/wiki-query.md +57 -0
- package/skills/agents/wiki-reingest.md +60 -0
- package/skills/agents/wiki-sync.md +70 -0
- package/skills/package.json +1 -1
- package/skills/proj-work/SKILL.md +23 -4
- package/skills/using-skillwiki/SKILL.md +11 -36
- package/skills/wiki-add-task/SKILL.md +48 -78
- package/skills/wiki-crystallize/SKILL.md +2 -11
- package/skills/wiki-ingest/SKILL.md +8 -15
- package/skills/wiki-lint/SKILL.md +4 -13
- package/skills/wiki-query/SKILL.md +9 -14
- package/skills/wiki-sync/SKILL.md +62 -37
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-TPS5XD2J.js";
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
|
11
|
-
import { readFileSync as
|
|
11
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
12
12
|
import { join as join38 } from "path";
|
|
13
13
|
import { Command } from "commander";
|
|
14
14
|
|
|
@@ -2402,6 +2402,46 @@ async function runDedup(input) {
|
|
|
2402
2402
|
};
|
|
2403
2403
|
}
|
|
2404
2404
|
|
|
2405
|
+
// src/commands/raw-body-dedup.ts
|
|
2406
|
+
import { createHash as createHash2 } from "crypto";
|
|
2407
|
+
async function runRawBodyDedup(vault) {
|
|
2408
|
+
const scan = await scanVault(vault);
|
|
2409
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
2410
|
+
const bodyHashMap = /* @__PURE__ */ new Map();
|
|
2411
|
+
let totalFiles = 0;
|
|
2412
|
+
for (const raw of scan.data.raw) {
|
|
2413
|
+
const text = await readPage(raw);
|
|
2414
|
+
const split = splitFrontmatter(text);
|
|
2415
|
+
if (!split.ok) continue;
|
|
2416
|
+
totalFiles++;
|
|
2417
|
+
const bodyHash = createHash2("sha256").update(split.data.body).digest("hex");
|
|
2418
|
+
const fm = extractFrontmatter(text);
|
|
2419
|
+
let fmSha256 = null;
|
|
2420
|
+
if (fm.ok && typeof fm.data.sha256 === "string" && fm.data.sha256.length === 64) {
|
|
2421
|
+
fmSha256 = fm.data.sha256;
|
|
2422
|
+
}
|
|
2423
|
+
const existing = bodyHashMap.get(bodyHash);
|
|
2424
|
+
if (existing) {
|
|
2425
|
+
existing.push({ relPath: raw.relPath, sha256: fmSha256 });
|
|
2426
|
+
} else {
|
|
2427
|
+
bodyHashMap.set(bodyHash, [{ relPath: raw.relPath, sha256: fmSha256 }]);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
const duplicates = [];
|
|
2431
|
+
for (const [bodyHash, files] of bodyHashMap) {
|
|
2432
|
+
if (files.length < 2) continue;
|
|
2433
|
+
const uniqueShas = new Set(files.map((f) => f.sha256));
|
|
2434
|
+
const allHaveSameValidSha = uniqueShas.size === 1 && files.every((f) => f.sha256 !== null);
|
|
2435
|
+
if (!allHaveSameValidSha) {
|
|
2436
|
+
duplicates.push({ bodyHash, files });
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
return {
|
|
2440
|
+
exitCode: 0,
|
|
2441
|
+
result: ok({ scanned: totalFiles, duplicates })
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2405
2445
|
// src/commands/lint.ts
|
|
2406
2446
|
var STRUCT_MIN_BODY_LINES = 60;
|
|
2407
2447
|
var STRUCT_MIN_SECTIONS = 3;
|
|
@@ -2434,7 +2474,7 @@ function extractSourceEntries(rawFm) {
|
|
|
2434
2474
|
return entries;
|
|
2435
2475
|
}
|
|
2436
2476
|
var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy"];
|
|
2437
|
-
var WARNING_ORDER = ["index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "work_item_health", "orphaned_project_pages", "missing_overview"];
|
|
2477
|
+
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "work_item_health", "orphaned_project_pages", "missing_overview"];
|
|
2438
2478
|
var INFO_ORDER = ["bridges", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "missing_diagram"];
|
|
2439
2479
|
async function runLint(input) {
|
|
2440
2480
|
const buckets = {};
|
|
@@ -2484,12 +2524,41 @@ async function runLint(input) {
|
|
|
2484
2524
|
}
|
|
2485
2525
|
const dedup = await runDedup({ vault: input.vault });
|
|
2486
2526
|
if (dedup.result.ok && dedup.result.data.duplicates.length > 0) buckets.raw_dedup = dedup.result.data.duplicates;
|
|
2527
|
+
const bodyDedup = await runRawBodyDedup(input.vault);
|
|
2528
|
+
if (bodyDedup.result.ok && bodyDedup.result.data.duplicates.length > 0) {
|
|
2529
|
+
buckets.raw_body_duplicate = bodyDedup.result.data.duplicates.map((d) => ({
|
|
2530
|
+
body_hash: d.bodyHash.slice(0, 12),
|
|
2531
|
+
files: d.files.map((f) => `${f.relPath} (sha256: ${f.sha256 ?? "none"})`)
|
|
2532
|
+
}));
|
|
2533
|
+
}
|
|
2487
2534
|
const compoundRefs = await validateCompoundReferences(input.vault);
|
|
2488
2535
|
if (compoundRefs.ok && compoundRefs.data.length > 0) buckets.compound_refs = compoundRefs.data;
|
|
2489
2536
|
const scan = await scanVault(input.vault);
|
|
2490
2537
|
const allPages = scan.ok ? [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound] : [];
|
|
2491
2538
|
const slugs = scan.ok ? buildSlugMap(allPages) : /* @__PURE__ */ new Map();
|
|
2492
2539
|
if (scan.ok) {
|
|
2540
|
+
const subDirDupes = [];
|
|
2541
|
+
const flatStems = /* @__PURE__ */ new Map();
|
|
2542
|
+
const deepFiles = [];
|
|
2543
|
+
for (const raw of scan.data.raw) {
|
|
2544
|
+
const parts = raw.relPath.split("/");
|
|
2545
|
+
if (parts.length === 3) {
|
|
2546
|
+
const stem = parts[2].replace(/\.md$/, "");
|
|
2547
|
+
flatStems.set(`${parts[1]}/${stem}`, raw.relPath);
|
|
2548
|
+
} else if (parts.length > 3) {
|
|
2549
|
+
const stem = parts[parts.length - 1].replace(/\.md$/, "");
|
|
2550
|
+
deepFiles.push({ relPath: raw.relPath, stem, parentType: parts[1] });
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
for (const df of deepFiles) {
|
|
2554
|
+
const flatPath = flatStems.get(`${df.parentType}/${df.stem}`);
|
|
2555
|
+
if (flatPath) {
|
|
2556
|
+
subDirDupes.push(`${df.relPath} -> duplicate of ${flatPath}`);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
if (subDirDupes.length > 0) {
|
|
2560
|
+
buckets.raw_subdirectory_duplicate = subDirDupes;
|
|
2561
|
+
}
|
|
2493
2562
|
const legacyPages = [];
|
|
2494
2563
|
const orphanedPages = [];
|
|
2495
2564
|
const structFlags = [];
|
|
@@ -2975,9 +3044,10 @@ async function runConfigPath(input) {
|
|
|
2975
3044
|
}
|
|
2976
3045
|
|
|
2977
3046
|
// src/commands/doctor.ts
|
|
2978
|
-
import { existsSync as existsSync6, lstatSync, readlinkSync, readdirSync, statSync as statSync2 } from "fs";
|
|
3047
|
+
import { existsSync as existsSync6, lstatSync, readlinkSync, readdirSync, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
|
|
2979
3048
|
import { join as join22, resolve as resolve4 } from "path";
|
|
2980
3049
|
import { execSync } from "child_process";
|
|
3050
|
+
import { platform } from "os";
|
|
2981
3051
|
|
|
2982
3052
|
// src/utils/auto-update.ts
|
|
2983
3053
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -3397,6 +3467,88 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
3397
3467
|
}
|
|
3398
3468
|
return check("pass", "sync_last_push", "Vault sync recency", `Last push: ${dateStr} (${daysSince2} day(s) ago)`);
|
|
3399
3469
|
}
|
|
3470
|
+
function detectFuseMount(vaultPath) {
|
|
3471
|
+
const os = platform();
|
|
3472
|
+
try {
|
|
3473
|
+
if (os === "linux") {
|
|
3474
|
+
const mounts = readFileSync6("/proc/mounts", "utf8");
|
|
3475
|
+
let best = null;
|
|
3476
|
+
for (const line of mounts.split("\n")) {
|
|
3477
|
+
const parts = line.split(" ");
|
|
3478
|
+
if (parts.length < 3) continue;
|
|
3479
|
+
const point = parts[1];
|
|
3480
|
+
const fs = parts[2];
|
|
3481
|
+
if (vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
|
|
3482
|
+
best = { point, fs };
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
if (best && best.fs.includes("fuse")) return best.point;
|
|
3486
|
+
} else if (os === "darwin") {
|
|
3487
|
+
const out = execSync("mount", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3488
|
+
let best = null;
|
|
3489
|
+
for (const line of out.split("\n")) {
|
|
3490
|
+
const match = line.match(/^(\S+) on (\S+) \((.*?)\)/);
|
|
3491
|
+
if (!match) continue;
|
|
3492
|
+
const point = match[2];
|
|
3493
|
+
const opts = match[3];
|
|
3494
|
+
if (opts.includes("fuse") && vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
|
|
3495
|
+
best = { point };
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
if (best) return best.point;
|
|
3499
|
+
}
|
|
3500
|
+
} catch {
|
|
3501
|
+
}
|
|
3502
|
+
return null;
|
|
3503
|
+
}
|
|
3504
|
+
function checkS3MountPerf(resolvedPath) {
|
|
3505
|
+
if (resolvedPath === void 0) {
|
|
3506
|
+
return check("pass", "s3_mount_perf", "S3 mount performance", "No vault path \u2014 check skipped");
|
|
3507
|
+
}
|
|
3508
|
+
const mountPoint = detectFuseMount(resolvedPath);
|
|
3509
|
+
if (!mountPoint) {
|
|
3510
|
+
return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
|
|
3511
|
+
}
|
|
3512
|
+
const conceptsDir = join22(resolvedPath, "concepts");
|
|
3513
|
+
if (!existsSync6(conceptsDir)) {
|
|
3514
|
+
return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
|
|
3515
|
+
}
|
|
3516
|
+
const start = Date.now();
|
|
3517
|
+
let timedOut = false;
|
|
3518
|
+
try {
|
|
3519
|
+
execSync(`rg -l "." "${conceptsDir}"`, {
|
|
3520
|
+
timeout: 5e3,
|
|
3521
|
+
encoding: "utf8",
|
|
3522
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3523
|
+
});
|
|
3524
|
+
} catch (e) {
|
|
3525
|
+
if (e.killed || e.status === null && e.signal === "SIGTERM") {
|
|
3526
|
+
timedOut = true;
|
|
3527
|
+
} else if (e.code === "ENOENT") {
|
|
3528
|
+
return check(
|
|
3529
|
+
"info",
|
|
3530
|
+
"s3_mount_perf",
|
|
3531
|
+
"S3 mount performance",
|
|
3532
|
+
`S3 FUSE mount (${mountPoint}) \u2014 rg not found at runtime, benchmark skipped`
|
|
3533
|
+
);
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
const elapsed = (Date.now() - start) / 1e3;
|
|
3537
|
+
if (timedOut || elapsed >= 3) {
|
|
3538
|
+
return check(
|
|
3539
|
+
"warn",
|
|
3540
|
+
"s3_mount_perf",
|
|
3541
|
+
"S3 mount performance",
|
|
3542
|
+
`S3 FUSE mount (${mountPoint}) with cold cache (rg scan: >3s). Vault scans may exceed 60s. Consider running wiki-cache-warm or checking rclone-wiki.service.`
|
|
3543
|
+
);
|
|
3544
|
+
}
|
|
3545
|
+
return check(
|
|
3546
|
+
"pass",
|
|
3547
|
+
"s3_mount_perf",
|
|
3548
|
+
"S3 mount performance",
|
|
3549
|
+
`S3 FUSE mount, cache warm (rg scan: ${elapsed.toFixed(3)}s)`
|
|
3550
|
+
);
|
|
3551
|
+
}
|
|
3400
3552
|
function findSkillMd(dir) {
|
|
3401
3553
|
const results = [];
|
|
3402
3554
|
let entries;
|
|
@@ -3449,6 +3601,7 @@ async function runDoctor(input) {
|
|
|
3449
3601
|
checks.push(checkVaultGitRemote(resolvedPath));
|
|
3450
3602
|
checks.push(checkSyncLastPush(resolvedPath));
|
|
3451
3603
|
checks.push(checkDotStoreClean(resolvedPath));
|
|
3604
|
+
checks.push(checkS3MountPerf(resolvedPath));
|
|
3452
3605
|
checks.push(checkSkillsInstalled(input.home, input.cwd));
|
|
3453
3606
|
checks.push(checkDuplicateSkills(input.home));
|
|
3454
3607
|
checks.push(checkNpmUpdate(input.home, input.currentVersion));
|
|
@@ -3522,7 +3675,7 @@ async function runArchive(input) {
|
|
|
3522
3675
|
}
|
|
3523
3676
|
|
|
3524
3677
|
// src/commands/drift.ts
|
|
3525
|
-
import { createHash as
|
|
3678
|
+
import { createHash as createHash3 } from "crypto";
|
|
3526
3679
|
import { writeFile as writeFile10 } from "fs/promises";
|
|
3527
3680
|
|
|
3528
3681
|
// src/utils/fetch.ts
|
|
@@ -3602,7 +3755,7 @@ async function runDrift(input) {
|
|
|
3602
3755
|
});
|
|
3603
3756
|
continue;
|
|
3604
3757
|
}
|
|
3605
|
-
const currentHash =
|
|
3758
|
+
const currentHash = createHash3("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
|
|
3606
3759
|
const drifted2 = currentHash !== storedHash;
|
|
3607
3760
|
if (drifted2 && input.apply) {
|
|
3608
3761
|
const newFm = rawFrontmatter.replace(/^sha256:\s*[a-f0-9]+$/m, `sha256: ${currentHash}`);
|
|
@@ -3880,7 +4033,7 @@ ${newBody}`;
|
|
|
3880
4033
|
|
|
3881
4034
|
// src/commands/update.ts
|
|
3882
4035
|
import { execSync as execSync2 } from "child_process";
|
|
3883
|
-
import { readFileSync as
|
|
4036
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
3884
4037
|
import { join as join24 } from "path";
|
|
3885
4038
|
function resolveGlobalSkillsRoot() {
|
|
3886
4039
|
try {
|
|
@@ -3910,7 +4063,7 @@ async function refreshInstalledSkills(target) {
|
|
|
3910
4063
|
}
|
|
3911
4064
|
async function runUpdate(input) {
|
|
3912
4065
|
const pkg2 = JSON.parse(
|
|
3913
|
-
|
|
4066
|
+
readFileSync7(new URL("../../package.json", import.meta.url), "utf8")
|
|
3914
4067
|
);
|
|
3915
4068
|
const currentVersion = pkg2.version;
|
|
3916
4069
|
const tag = input.distTag ?? "latest";
|
|
@@ -3984,12 +4137,12 @@ async function runUpdate(input) {
|
|
|
3984
4137
|
|
|
3985
4138
|
// src/commands/self-update.ts
|
|
3986
4139
|
import { execSync as execSync3 } from "child_process";
|
|
3987
|
-
import { existsSync as existsSync7, readFileSync as
|
|
4140
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
3988
4141
|
import { join as join25 } from "path";
|
|
3989
4142
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
3990
4143
|
async function runSelfUpdate(input) {
|
|
3991
4144
|
const currentVersion = JSON.parse(
|
|
3992
|
-
|
|
4145
|
+
readFileSync8(new URL("../../package.json", import.meta.url), "utf8")
|
|
3993
4146
|
).version;
|
|
3994
4147
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
3995
4148
|
const localPkgPath = join25(sourceRoot, "packages", "cli", "package.json");
|
|
@@ -4000,7 +4153,7 @@ async function runSelfUpdate(input) {
|
|
|
4000
4153
|
if (hasLocalSource) {
|
|
4001
4154
|
source = "local";
|
|
4002
4155
|
try {
|
|
4003
|
-
availableVersion = JSON.parse(
|
|
4156
|
+
availableVersion = JSON.parse(readFileSync8(localPkgPath, "utf8")).version ?? null;
|
|
4004
4157
|
} catch {
|
|
4005
4158
|
availableVersion = null;
|
|
4006
4159
|
}
|
|
@@ -4058,7 +4211,7 @@ async function runSelfUpdate(input) {
|
|
|
4058
4211
|
}
|
|
4059
4212
|
const newVersion = (() => {
|
|
4060
4213
|
try {
|
|
4061
|
-
return JSON.parse(
|
|
4214
|
+
return JSON.parse(readFileSync8(localPkgPath, "utf8")).version ?? "unknown";
|
|
4062
4215
|
} catch {
|
|
4063
4216
|
return "unknown";
|
|
4064
4217
|
}
|
|
@@ -4587,7 +4740,7 @@ no compound entries found`;
|
|
|
4587
4740
|
import { mkdir as mkdir11, writeFile as writeFile15 } from "fs/promises";
|
|
4588
4741
|
import { existsSync as existsSync9, statSync as statSync3 } from "fs";
|
|
4589
4742
|
import { join as join29 } from "path";
|
|
4590
|
-
import { createHash as
|
|
4743
|
+
import { createHash as createHash4 } from "crypto";
|
|
4591
4744
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
4592
4745
|
function slugify2(text) {
|
|
4593
4746
|
const words = text.trim().split(/\s+/).slice(0, 6).join("-").toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -4631,7 +4784,7 @@ async function runObserve(input) {
|
|
|
4631
4784
|
const body = `
|
|
4632
4785
|
${input.text.trim()}
|
|
4633
4786
|
`;
|
|
4634
|
-
const sha256 =
|
|
4787
|
+
const sha256 = createHash4("sha256").update(Buffer.from(body, "utf8")).digest("hex");
|
|
4635
4788
|
const frontmatterLines = [
|
|
4636
4789
|
"---",
|
|
4637
4790
|
"source_url:",
|
|
@@ -4669,7 +4822,7 @@ ${input.text.trim()}
|
|
|
4669
4822
|
// src/commands/ingest.ts
|
|
4670
4823
|
import { readFile as readFile20, writeFile as writeFile16, mkdir as mkdir12 } from "fs/promises";
|
|
4671
4824
|
import { join as join30 } from "path";
|
|
4672
|
-
import { createHash as
|
|
4825
|
+
import { createHash as createHash5 } from "crypto";
|
|
4673
4826
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
4674
4827
|
var TYPE_DIR = {
|
|
4675
4828
|
entity: "entities",
|
|
@@ -4835,7 +4988,7 @@ async function runIngest(input) {
|
|
|
4835
4988
|
};
|
|
4836
4989
|
}
|
|
4837
4990
|
}
|
|
4838
|
-
const sha256 =
|
|
4991
|
+
const sha256 = createHash5("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
|
|
4839
4992
|
const today = todayIso();
|
|
4840
4993
|
const slug = slugify3(input.title);
|
|
4841
4994
|
const tags = input.tags && input.tags.length > 0 ? input.tags : [];
|
|
@@ -5324,7 +5477,7 @@ async function runSyncPull(input) {
|
|
|
5324
5477
|
}
|
|
5325
5478
|
|
|
5326
5479
|
// src/commands/backup.ts
|
|
5327
|
-
import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as
|
|
5480
|
+
import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
5328
5481
|
import { join as join32, relative as relative3, dirname as dirname11 } from "path";
|
|
5329
5482
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
5330
5483
|
|
|
@@ -5391,7 +5544,7 @@ async function runBackupSync(input) {
|
|
|
5391
5544
|
continue;
|
|
5392
5545
|
}
|
|
5393
5546
|
try {
|
|
5394
|
-
const body =
|
|
5547
|
+
const body = readFileSync9(absPath);
|
|
5395
5548
|
await client.send(new PutObjectCommand({ Bucket: input.bucket, Key: relPath, Body: body }));
|
|
5396
5549
|
uploaded++;
|
|
5397
5550
|
} catch {
|
|
@@ -6018,7 +6171,7 @@ async function postCommit(vault, exitCode) {
|
|
|
6018
6171
|
}
|
|
6019
6172
|
|
|
6020
6173
|
// src/cli.ts
|
|
6021
|
-
var pkg = JSON.parse(
|
|
6174
|
+
var pkg = JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf8"));
|
|
6022
6175
|
var program = new Command();
|
|
6023
6176
|
program.name("skillwiki").description("Deterministic helpers for CodeWiki skills").version(pkg.version);
|
|
6024
6177
|
program.option("--human", "render terminal-readable output instead of JSON");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4-beta.1",
|
|
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": {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: proj-decide
|
|
3
|
+
description: Use this agent when recording architectural decisions during automated maintenance cycles. Typical triggers include dev-loop IDLE DISCOVERY maintenance, capturing design decisions from work items, or generalizing decisions into concept pages. See "When to invoke" in the agent body for worked scenarios.
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: yellow
|
|
6
|
+
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an architectural decision recorder specializing in writing ADRs and optionally promoting generalizable decisions to concept pages. You operate autonomously during maintenance cycles — no user interaction expected.
|
|
10
|
+
|
|
11
|
+
## When to invoke
|
|
12
|
+
|
|
13
|
+
- **ADR capture.** Dev-loop spawns you to record an architectural decision from a completed work item.
|
|
14
|
+
- **Decision generalization.** A decision recorded in a project likely applies beyond it — create both ADR and concept page.
|
|
15
|
+
- **Periodic distillation.** Part of dev-loop IDLE DISCOVERY: scan work item retros for undocumented decisions.
|
|
16
|
+
|
|
17
|
+
**Your Core Responsibilities:**
|
|
18
|
+
1. Compose an ADR in `projects/{slug}/architecture/YYYY-MM-DD-{adr-slug}.md`
|
|
19
|
+
2. Validate the ADR with `skillwiki validate`
|
|
20
|
+
3. Check if the decision generalizes beyond the project — if so, create a concepts/ page
|
|
21
|
+
4. Apply all writes in order
|
|
22
|
+
|
|
23
|
+
**Execution Process:**
|
|
24
|
+
|
|
25
|
+
1. **Identify context.** Determine project slug from the task prompt. If no project context, default to `playground`.
|
|
26
|
+
2. **Compose the ADR.** Write to `projects/{slug}/architecture/YYYY-MM-DD-{adr-slug}.md`:
|
|
27
|
+
- Frontmatter: `kind: decision`, `status: in-progress` (or `completed` if already implemented), `project: "[[slug]]"`
|
|
28
|
+
- Body sections: **Context** (why this decision matters), **Decision** (what was chosen), **Consequences** (what follows from this choice), **Alternatives Considered** (rejected options and why)
|
|
29
|
+
3. **Validate.** Run `skillwiki validate <adr>`. If non-zero, fix and re-validate. Do NOT proceed until validation passes.
|
|
30
|
+
4. **Generalization check.** If the decision applies beyond this project, create a `concepts/` page with:
|
|
31
|
+
- `provenance: project` (or `mixed` if also research-informed)
|
|
32
|
+
- `provenance_projects: ["[[slug]]"]`
|
|
33
|
+
- `## TL;DR` as first section
|
|
34
|
+
- Body summarizing the decision pattern generically
|
|
35
|
+
- `^[raw/...]` citations where applicable
|
|
36
|
+
- Validate this page too before proceeding.
|
|
37
|
+
5. **Apply writes in order:** ADR → (optional) concept page → vault `index.md` → vault `log.md` → project `log.md`.
|
|
38
|
+
|
|
39
|
+
**Output Format:**
|
|
40
|
+
Return:
|
|
41
|
+
- ADR path and slug
|
|
42
|
+
- Decision summary (1-2 sentences)
|
|
43
|
+
- Whether a concept page was also created (and path if so)
|
|
44
|
+
- Validation results for both pages
|
|
45
|
+
- All log entries appended
|
|
46
|
+
|
|
47
|
+
**Stop Conditions:**
|
|
48
|
+
- `skillwiki validate` returns non-zero on either page (after retry)
|
|
49
|
+
- Insufficient context to compose a meaningful ADR
|
|
50
|
+
|
|
51
|
+
**Forbidden:**
|
|
52
|
+
- Filing a concept page without explicit `provenance:`
|
|
53
|
+
- Skipping the generalization check
|
|
54
|
+
- Updating index/logs before `validate` passes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: proj-distill
|
|
3
|
+
description: Use this agent when promoting project compound entries or retro patterns into vault concept pages during automated maintenance cycles. Typical triggers include dev-loop IDLE DISCOVERY maintenance, compound-entry generalization, or retro-sourced pattern extraction. See "When to invoke" in the agent body for worked scenarios.
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: green
|
|
6
|
+
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are a pattern distiller specializing in the E4 2-step process: analyze project compound entries (or retros) for generalizable patterns, then generate vault concept pages with provenance. You operate autonomously during maintenance cycles — no user interaction expected.
|
|
10
|
+
|
|
11
|
+
## When to invoke
|
|
12
|
+
|
|
13
|
+
- **Compound promotion.** Dev-loop spawns you to check `projects/{slug}/compound/` for entries ready for generalization.
|
|
14
|
+
- **Retro mining.** Project retro entries in vault `log.md` contain `Generalize?: yes` flags — extract recurring patterns.
|
|
15
|
+
- **Periodic distillation.** Part of dev-loop IDLE DISCOVERY: scan for unwritten compound entries and promote them.
|
|
16
|
+
|
|
17
|
+
**Your Core Responsibilities:**
|
|
18
|
+
1. Read source compound entries or retro logs and identify generalizable patterns
|
|
19
|
+
2. Output a candidate concept outline — STOP if no clear universal pattern
|
|
20
|
+
3. Compose the vault concept page with project provenance
|
|
21
|
+
4. Set backlinks and apply all writes in order
|
|
22
|
+
|
|
23
|
+
**Execution Process:**
|
|
24
|
+
|
|
25
|
+
### Step 1 — Analyze
|
|
26
|
+
1. Check `projects/{slug}/compound/` first. If no project context, use `playground`.
|
|
27
|
+
2. Read the source compound entry + linked work items. If no compound entries, fall back to retro entries in `{vault}/log.md` (lines matching `## [YYYY-MM-DD] retro`).
|
|
28
|
+
3. For retro-sourced analysis: collect all retros for the project, focus on `Generalize?: yes` entries, group by recurring theme (≥2 occurrences = candidate concept).
|
|
29
|
+
4. Output a candidate concept outline. **STOP if no clear universal pattern is found** — surface the reasoning instead of forcing a page.
|
|
30
|
+
|
|
31
|
+
### Step 2 — Generate (only if Step 1 found a pattern)
|
|
32
|
+
5. Compose the vault concept page:
|
|
33
|
+
- `provenance: project` and `provenance_projects: ["[[slug]]"]`
|
|
34
|
+
- `tags:` from `{vault}/SCHEMA.md` taxonomy only. Never derive tags from prose text. If no relevant taxonomy tag, use `[dev-loop]`.
|
|
35
|
+
- `## TL;DR` as first section
|
|
36
|
+
- Body with `^[raw/...]` citations
|
|
37
|
+
6. Validate with `skillwiki validate <page>`. If non-zero, fix and re-validate.
|
|
38
|
+
|
|
39
|
+
### Step 3 — Backlink
|
|
40
|
+
7. Set `promoted_to: "[[concept-slug]]"` on the source compound entry. For retro-sourced distillation, skip backlink (log.md is append-only) — instead add `sources:` citing the vault log with date range.
|
|
41
|
+
|
|
42
|
+
### Step 4 — Apply writes in order
|
|
43
|
+
8. Vault concept page → backlink update → project `log.md` → vault `index.md` → vault `log.md`.
|
|
44
|
+
|
|
45
|
+
**Output Format:**
|
|
46
|
+
Return:
|
|
47
|
+
- Source analyzed (compound entry path or retro date range)
|
|
48
|
+
- Pattern identified (theme, recurrence count)
|
|
49
|
+
- Whether distillation proceeded or stopped at Step 1 (with reasoning)
|
|
50
|
+
- If generated: concept page path, validation result, backlink applied
|
|
51
|
+
- All log entries appended
|
|
52
|
+
|
|
53
|
+
**Stop Conditions:**
|
|
54
|
+
- No clear universal pattern found in Step 1
|
|
55
|
+
- `skillwiki validate` returns non-zero (after retry)
|
|
56
|
+
|
|
57
|
+
**Forbidden:**
|
|
58
|
+
- Skipping Step 1 (analysis before generation)
|
|
59
|
+
- Inventing new tags not in SCHEMA.md taxonomy
|
|
60
|
+
- Updating index/logs before `validate` passes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: proj-init
|
|
3
|
+
description: Use this agent when bootstrapping a new project workspace during automated setup cycles. Typical triggers include dev-loop project initialization, new-project scaffolding, or creating a workspace for an existing codebase. See "When to invoke" in the agent body for worked scenarios.
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: green
|
|
6
|
+
tools: ["Read", "Write", "Bash", "Grep", "Glob"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are a project workspace bootstrapper specializing in creating the `projects/{slug}/` directory structure with README, requirements/, architecture/, work/, and compound/. You operate autonomously — the project slug and intent are in your task prompt.
|
|
10
|
+
|
|
11
|
+
## When to invoke
|
|
12
|
+
|
|
13
|
+
- **New project.** Dev-loop spawns you to scaffold a workspace for a new project.
|
|
14
|
+
- **Vault onboarding.** An existing codebase needs a vault project workspace.
|
|
15
|
+
- **Playground init.** Ensure the `playground` project exists for unclassified work.
|
|
16
|
+
|
|
17
|
+
**Your Core Responsibilities:**
|
|
18
|
+
1. Verify the project slug doesn't conflict with existing projects
|
|
19
|
+
2. Create the directory structure
|
|
20
|
+
3. Render README.md from template
|
|
21
|
+
4. Update vault index.md and log.md
|
|
22
|
+
|
|
23
|
+
**Execution Process:**
|
|
24
|
+
|
|
25
|
+
1. **Resolve vault.** Run `skillwiki path`. If NO_VAULT_CONFIGURED, report failure and STOP.
|
|
26
|
+
2. **Verify slug.** The slug is in your task prompt. Check that `projects/{slug}/` does not exist. If it does, report and STOP.
|
|
27
|
+
3. **Create structure:**
|
|
28
|
+
```
|
|
29
|
+
projects/{slug}/
|
|
30
|
+
├── requirements/
|
|
31
|
+
├── architecture/
|
|
32
|
+
├── work/
|
|
33
|
+
└── compound/
|
|
34
|
+
```
|
|
35
|
+
4. **Render README.** Create `projects/{slug}/README.md` with:
|
|
36
|
+
- Project name and one-line intent
|
|
37
|
+
- Section: Knowledge Pages (placeholder for future index entries)
|
|
38
|
+
- Section: Work Items (placeholder)
|
|
39
|
+
- Section: Architecture (placeholder for ADRs)
|
|
40
|
+
- Creation date
|
|
41
|
+
5. **Update index.** Add to `{vault}/index.md` Projects section: `- [[projects/{slug}]]`.
|
|
42
|
+
6. **Log.** Append to `{vault}/log.md`: `## [{date}] project-init | {slug} initialized.`
|
|
43
|
+
|
|
44
|
+
**Output Format:**
|
|
45
|
+
Return:
|
|
46
|
+
- Project slug and path
|
|
47
|
+
- Directories created
|
|
48
|
+
- README.md path
|
|
49
|
+
- Index.md entry added
|
|
50
|
+
- Log entry appended
|
|
51
|
+
|
|
52
|
+
**Stop Conditions:**
|
|
53
|
+
- `projects/{slug}/` already exists
|
|
54
|
+
- `skillwiki path` returns NO_VAULT_CONFIGURED
|
|
55
|
+
|
|
56
|
+
**Forbidden:**
|
|
57
|
+
- Modifying any other project's files
|
|
58
|
+
- Creating the workspace without updating index.md
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: proj-work
|
|
3
|
+
description: Use this agent when creating or executing work items during automated development cycles. Typical triggers include dev-loop work item creation from captured tasks, executing existing work items, or managing status transitions. See "When to invoke" in the agent body for worked scenarios.
|
|
4
|
+
model: sonnet
|
|
5
|
+
color: green
|
|
6
|
+
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are a project work item manager specializing in creating and executing work items under `projects/{slug}/work/YYYY-MM-DD-{slug}/`. You handle both creation (scaffolding spec + plan + tasks) and execution (verifying DONE claims against disk, applying missing fixes). You operate autonomously during maintenance cycles.
|
|
10
|
+
|
|
11
|
+
## When to invoke
|
|
12
|
+
|
|
13
|
+
- **Work item creation.** Dev-loop spawns you to scaffold a new feature/bugfix/refactor work item.
|
|
14
|
+
- **Work item execution.** Dev-loop spawns you to run through an existing work item's task list.
|
|
15
|
+
- **Status management.** Transition work items through planned → in-progress → completed.
|
|
16
|
+
|
|
17
|
+
**Your Core Responsibilities:**
|
|
18
|
+
1. For creation: scaffold work folder with spec.md + tasks.md
|
|
19
|
+
2. For execution: read spec/tasks, verify every DONE claim against disk, apply missing fixes
|
|
20
|
+
3. Validate all pages, manage status transitions, update logs
|
|
21
|
+
|
|
22
|
+
**Execution Process:**
|
|
23
|
+
|
|
24
|
+
### Creating a New Work Item
|
|
25
|
+
1. **Resolve vault.** Run `skillwiki path`.
|
|
26
|
+
2. **Determine slug and kind.** From task prompt: kind (`feature` | `issue` | `refactor` | `decision`) and work slug.
|
|
27
|
+
3. **Create folder.** `projects/{slug}/work/YYYY-MM-DD-{work-slug}/`.
|
|
28
|
+
4. **Write spec.md.** Frontmatter with kind, status=planned, project wikilink. Body with context and scope.
|
|
29
|
+
5. **Write tasks.md.** Break work into task checklist.
|
|
30
|
+
6. **Validate.** `skillwiki validate <spec.md>`. If non-zero, fix and STOP.
|
|
31
|
+
7. **Emit redirect paths.** These are where PRD skills should write their output:
|
|
32
|
+
- spec → `<vault>/projects/{slug}/work/YYYY-MM-DD-{work-slug}/spec.md`
|
|
33
|
+
- plan → `<vault>/projects/{slug}/work/YYYY-MM-DD-{work-slug}/plan.md`
|
|
34
|
+
8. **Log.** Append to vault `log.md`.
|
|
35
|
+
|
|
36
|
+
### Executing an Existing Work Item
|
|
37
|
+
1. **Resolve work folder.** `<vault>/projects/{slug}/work/YYYY-MM-DD-{slug}/`.
|
|
38
|
+
2. **Read spec.md and tasks.md** in full.
|
|
39
|
+
3. **Verify every DONE claim against disk.** This is critical:
|
|
40
|
+
- Check file existence, content, config values on disk
|
|
41
|
+
- Cross-reference crontab entries, script timeouts, config settings
|
|
42
|
+
- Trust nothing in the wiki alone — validate against filesystem
|
|
43
|
+
4. **Apply missing fixes.** For items claimed DONE but not actually applied, apply the fix.
|
|
44
|
+
5. **Update status.** Set `status: complete` with `completed:` date only when ALL fixes verified.
|
|
45
|
+
6. **Log.** Append to vault `log.md` on status transitions.
|
|
46
|
+
|
|
47
|
+
**Output Format:**
|
|
48
|
+
Return:
|
|
49
|
+
- Work item path
|
|
50
|
+
- Kind and slug
|
|
51
|
+
- If creating: spec.md and tasks.md paths, redirect paths for PRD skills
|
|
52
|
+
- If executing: DONE claims verified (count), fixes applied (count), final status
|
|
53
|
+
- Log entries appended
|
|
54
|
+
|
|
55
|
+
**Stop Conditions:**
|
|
56
|
+
- `validate` non-zero
|
|
57
|
+
- Conflicting work folder name
|
|
58
|
+
- No project context and no `playground` fallback
|
|
59
|
+
|
|
60
|
+
**Forbidden:**
|
|
61
|
+
- Writing spec/plan files outside the work folder
|
|
62
|
+
- Marking `status: completed` without a `completed:` date
|
|
63
|
+
- Accepting tasks.md DONE labels without independent disk verification
|
|
64
|
+
- Re-marking tasks as DONE without actually applying the fix
|