skillwiki 0.8.1-beta.1 → 0.8.1-beta.3
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
CHANGED
|
@@ -548,23 +548,137 @@ function extractBodyWikilinks(body) {
|
|
|
548
548
|
return out;
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
-
// src/
|
|
552
|
-
async function
|
|
553
|
-
const scan = await scanVault(input.vault);
|
|
554
|
-
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
551
|
+
// src/utils/community.ts
|
|
552
|
+
async function buildWikilinkAdjacency(typedKnowledge) {
|
|
555
553
|
const adjacency = {};
|
|
556
554
|
const slugToPath = {};
|
|
557
|
-
for (const p of
|
|
555
|
+
for (const p of typedKnowledge) {
|
|
558
556
|
const slug = p.relPath.replace(/\.md$/, "").split("/").pop();
|
|
559
557
|
slugToPath[slug] = p.relPath;
|
|
560
558
|
}
|
|
561
|
-
for (const p of
|
|
559
|
+
for (const p of typedKnowledge) {
|
|
562
560
|
const text = await readPage(p);
|
|
563
561
|
const split = splitFrontmatter(text);
|
|
564
562
|
const body = split.ok ? split.data.body : text;
|
|
565
563
|
const links = extractBodyWikilinks(body);
|
|
566
564
|
adjacency[p.relPath] = links.map((slug) => slugToPath[slug.split("/").pop()]).filter((x) => Boolean(x));
|
|
567
565
|
}
|
|
566
|
+
return adjacency;
|
|
567
|
+
}
|
|
568
|
+
function toUndirectedWeighted(adj) {
|
|
569
|
+
const g = /* @__PURE__ */ new Map();
|
|
570
|
+
const ensure = (n) => {
|
|
571
|
+
let m = g.get(n);
|
|
572
|
+
if (!m) {
|
|
573
|
+
m = /* @__PURE__ */ new Map();
|
|
574
|
+
g.set(n, m);
|
|
575
|
+
}
|
|
576
|
+
return m;
|
|
577
|
+
};
|
|
578
|
+
for (const node of Object.keys(adj)) ensure(node);
|
|
579
|
+
for (const [a, nbrs] of Object.entries(adj)) {
|
|
580
|
+
for (const b of nbrs) {
|
|
581
|
+
if (a === b) continue;
|
|
582
|
+
ensure(a).set(b, 1);
|
|
583
|
+
ensure(b).set(a, 1);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return g;
|
|
587
|
+
}
|
|
588
|
+
function louvain(g) {
|
|
589
|
+
const nodes = [...g.keys()].sort();
|
|
590
|
+
const comm = /* @__PURE__ */ new Map();
|
|
591
|
+
nodes.forEach((n, i) => comm.set(n, i));
|
|
592
|
+
const k = /* @__PURE__ */ new Map();
|
|
593
|
+
let m2 = 0;
|
|
594
|
+
for (const n of nodes) {
|
|
595
|
+
let deg = 0;
|
|
596
|
+
for (const w of g.get(n).values()) deg += w;
|
|
597
|
+
k.set(n, deg);
|
|
598
|
+
m2 += deg;
|
|
599
|
+
}
|
|
600
|
+
if (m2 === 0) return comm;
|
|
601
|
+
const sumTot = /* @__PURE__ */ new Map();
|
|
602
|
+
for (const n of nodes) {
|
|
603
|
+
const c = comm.get(n);
|
|
604
|
+
sumTot.set(c, (sumTot.get(c) ?? 0) + k.get(n));
|
|
605
|
+
}
|
|
606
|
+
let improved = true;
|
|
607
|
+
while (improved) {
|
|
608
|
+
improved = false;
|
|
609
|
+
for (const n of nodes) {
|
|
610
|
+
const cur = comm.get(n);
|
|
611
|
+
const kn = k.get(n);
|
|
612
|
+
sumTot.set(cur, sumTot.get(cur) - kn);
|
|
613
|
+
const wToComm = /* @__PURE__ */ new Map();
|
|
614
|
+
for (const [nb, w] of g.get(n)) {
|
|
615
|
+
if (nb === n) continue;
|
|
616
|
+
const c = comm.get(nb);
|
|
617
|
+
wToComm.set(c, (wToComm.get(c) ?? 0) + w);
|
|
618
|
+
}
|
|
619
|
+
const gainFor = (c) => (wToComm.get(c) ?? 0) - (sumTot.get(c) ?? 0) * kn / m2;
|
|
620
|
+
const curGain = gainFor(cur);
|
|
621
|
+
let bestComm = cur;
|
|
622
|
+
let bestDelta = 0;
|
|
623
|
+
for (const c of wToComm.keys()) {
|
|
624
|
+
const delta = gainFor(c) - curGain;
|
|
625
|
+
if (delta > bestDelta) {
|
|
626
|
+
bestDelta = delta;
|
|
627
|
+
bestComm = c;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
comm.set(n, bestComm);
|
|
631
|
+
sumTot.set(bestComm, (sumTot.get(bestComm) ?? 0) + kn);
|
|
632
|
+
if (bestComm !== cur) improved = true;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return comm;
|
|
636
|
+
}
|
|
637
|
+
function communityCohesion(members, g) {
|
|
638
|
+
const n = members.length;
|
|
639
|
+
if (n < 2) return 1;
|
|
640
|
+
const set = new Set(members);
|
|
641
|
+
let internal = 0;
|
|
642
|
+
for (const a of members) {
|
|
643
|
+
for (const [b, w] of g.get(a) ?? /* @__PURE__ */ new Map()) {
|
|
644
|
+
if (a < b && set.has(b)) internal += w;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return internal / (n * (n - 1) / 2);
|
|
648
|
+
}
|
|
649
|
+
function findSparseCommunities(adj, opts = {}) {
|
|
650
|
+
const minSize = opts.minSize ?? 3;
|
|
651
|
+
const maxCohesion = opts.maxCohesion ?? 0.15;
|
|
652
|
+
const g = toUndirectedWeighted(adj);
|
|
653
|
+
const comm = louvain(g);
|
|
654
|
+
const groups = /* @__PURE__ */ new Map();
|
|
655
|
+
for (const [node, c] of comm) {
|
|
656
|
+
const arr = groups.get(c);
|
|
657
|
+
if (arr) arr.push(node);
|
|
658
|
+
else groups.set(c, [node]);
|
|
659
|
+
}
|
|
660
|
+
const out = [];
|
|
661
|
+
for (const members of groups.values()) {
|
|
662
|
+
if (members.length < minSize) continue;
|
|
663
|
+
const cohesion = communityCohesion(members, g);
|
|
664
|
+
if (cohesion < maxCohesion) {
|
|
665
|
+
out.push({
|
|
666
|
+
members: [...members].sort(),
|
|
667
|
+
size: members.length,
|
|
668
|
+
cohesion: Math.round(cohesion * 1e3) / 1e3,
|
|
669
|
+
action: members.length <= 5 ? "merge into adjacent community" : "split into smaller topics"
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
out.sort((a, b) => a.cohesion - b.cohesion);
|
|
674
|
+
return out;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/commands/graph.ts
|
|
678
|
+
async function runGraphBuild(input) {
|
|
679
|
+
const scan = await scanVault(input.vault);
|
|
680
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
681
|
+
const adjacency = await buildWikilinkAdjacency(scan.data.typedKnowledge);
|
|
568
682
|
const adamicAdar = computeAdamicAdar(adjacency);
|
|
569
683
|
const edge_count = Object.values(adjacency).reduce((acc, arr) => acc + arr.length, 0);
|
|
570
684
|
try {
|
|
@@ -2469,6 +2583,19 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
2469
2583
|
import { readFile as readFile16, rename as rename6 } from "fs/promises";
|
|
2470
2584
|
import { join as join21 } from "path";
|
|
2471
2585
|
|
|
2586
|
+
// src/commands/sparse-community.ts
|
|
2587
|
+
async function runSparseCommunity(input) {
|
|
2588
|
+
const scan = await scanVault(input.vault);
|
|
2589
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
2590
|
+
const adjacency = await buildWikilinkAdjacency(scan.data.typedKnowledge);
|
|
2591
|
+
const communities = findSparseCommunities(adjacency, {
|
|
2592
|
+
minSize: input.minSize,
|
|
2593
|
+
maxCohesion: input.maxCohesion
|
|
2594
|
+
});
|
|
2595
|
+
const humanHint = communities.length === 0 ? "no sparse communities" : communities.map((c) => ` cohesion ${c.cohesion} (${c.size} pages): ${c.action}`).join("\n");
|
|
2596
|
+
return { exitCode: ExitCode.OK, result: ok({ communities, humanHint }) };
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2472
2599
|
// src/commands/topic-map-check.ts
|
|
2473
2600
|
var DEFAULT_THRESHOLD = 200;
|
|
2474
2601
|
async function runTopicMapCheck(input) {
|
|
@@ -2791,6 +2918,7 @@ function buildCliSurface() {
|
|
|
2791
2918
|
program2.command("claim").option("--project <slug>").option("--slug <slug>").option("--wiki <name>");
|
|
2792
2919
|
program2.command("pagesize").option("--lines <n>").option("--wiki <name>");
|
|
2793
2920
|
program2.command("log-rotate").option("--threshold <n>").option("--apply").option("--wiki <name>");
|
|
2921
|
+
program2.command("log-append").requiredOption("--content <text>").option("--wiki <name>");
|
|
2794
2922
|
program2.command("lint").option("--days <n>").option("--lines <n>").option("--log-threshold <n>").option("--fix").option("--only <bucket>").option("--wiki <name>");
|
|
2795
2923
|
program2.command("config");
|
|
2796
2924
|
program2.command("doctor");
|
|
@@ -2940,7 +3068,7 @@ function extractSourceEntries(rawFm) {
|
|
|
2940
3068
|
}
|
|
2941
3069
|
var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
|
|
2942
3070
|
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "file_source_url", "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", "missing_diagram"];
|
|
2943
|
-
var INFO_ORDER = ["bridges", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
3071
|
+
var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
2944
3072
|
async function runLint(input) {
|
|
2945
3073
|
const buckets = {};
|
|
2946
3074
|
const fixed = [];
|
|
@@ -2983,6 +3111,10 @@ async function runLint(input) {
|
|
|
2983
3111
|
if (orphans.result.data.orphans.length > 0) buckets.orphans = orphans.result.data.orphans;
|
|
2984
3112
|
if (orphans.result.data.bridges.length > 0) buckets.bridges = orphans.result.data.bridges;
|
|
2985
3113
|
}
|
|
3114
|
+
const sparse = await runSparseCommunity({ vault: input.vault });
|
|
3115
|
+
if (sparse.result.ok && sparse.result.data.communities.length > 0) {
|
|
3116
|
+
buckets.sparse_community = sparse.result.data.communities;
|
|
3117
|
+
}
|
|
2986
3118
|
const topicMap = await runTopicMapCheck({ vault: input.vault });
|
|
2987
3119
|
if (topicMap.result.ok && topicMap.result.data.recommended) {
|
|
2988
3120
|
buckets.topic_map_recommended = [{ page_count: topicMap.result.data.page_count, threshold: topicMap.result.data.threshold }];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.8.1-beta.
|
|
3
|
+
"version": "0.8.1-beta.3",
|
|
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": {
|