skillwiki 0.10.1 → 0.10.2

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.
@@ -9,8 +9,7 @@ import {
9
9
  } from "./chunk-C5OLZRRM.js";
10
10
 
11
11
  // src/utils/index-projection.ts
12
- import { readdirSync, readFileSync as readFileSync2 } from "fs";
13
- import { readFile as readFile3 } from "fs/promises";
12
+ import { readFile as readFile4 } from "fs/promises";
14
13
  import { join as join3 } from "path";
15
14
 
16
15
  // src/utils/atomic-write.ts
@@ -59,6 +58,9 @@ async function atomicWriteText(path, text) {
59
58
  }
60
59
  }
61
60
 
61
+ // src/utils/index-universe.ts
62
+ import { readFile as readFile3 } from "fs/promises";
63
+
62
64
  // src/utils/typed-page.ts
63
65
  import { lstatSync, realpathSync } from "fs";
64
66
  import { dirname as dirname2, posix, relative, resolve, sep } from "path";
@@ -440,8 +442,15 @@ async function readPageCached(p, cache) {
440
442
  return pending;
441
443
  }
442
444
 
443
- // src/utils/index-projection.ts
444
- var SECTION_ORDER = ["Entities", "Concepts", "Comparisons", "Queries", "Meta", "Projects"];
445
+ // src/utils/index-universe.ts
446
+ var ROOT_INDEX_SECTION_ORDER = [
447
+ "Entities",
448
+ "Concepts",
449
+ "Comparisons",
450
+ "Queries",
451
+ "Meta",
452
+ "Projects"
453
+ ];
445
454
  var TYPE_SECTION = {
446
455
  entity: "Entities",
447
456
  concept: "Concepts",
@@ -450,6 +459,106 @@ var TYPE_SECTION = {
450
459
  meta: "Meta"
451
460
  };
452
461
  var compareText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
462
+ function projectTitleFromReadme(text, slug) {
463
+ const match = text.match(/^#\s+Project:\s+(.+)$/m);
464
+ return match?.[1]?.trim() || slug;
465
+ }
466
+ async function buildRootIndexUniverse(input) {
467
+ const scanned = input.scan ? ok(input.scan) : await scanVault(input.vault);
468
+ if (!scanned.ok) return scanned;
469
+ const required = [];
470
+ const rejectedTyped = [];
471
+ const seen = /* @__PURE__ */ new Map();
472
+ let duplicatesRemoved = 0;
473
+ const typedPages = [...scanned.data.typedKnowledge].sort((a, b) => compareText(a.relPath, b.relPath));
474
+ for (const page of typedPages) {
475
+ let text;
476
+ try {
477
+ text = await readFile3(page.absPath, "utf8");
478
+ } catch {
479
+ continue;
480
+ }
481
+ const prepared = prepareTypedPage(text, page.relPath);
482
+ if (!prepared.ok) {
483
+ rejectedTyped.push({
484
+ relPath: page.relPath,
485
+ error: prepared.error,
486
+ detail: prepared.detail
487
+ });
488
+ continue;
489
+ }
490
+ const section = TYPE_SECTION[prepared.data.type];
491
+ if (!section) {
492
+ rejectedTyped.push({
493
+ relPath: page.relPath,
494
+ error: "SCHEME_REJECTED",
495
+ detail: { type: prepared.data.type }
496
+ });
497
+ continue;
498
+ }
499
+ const target = prepared.data.target.replace(/\.md$/, "");
500
+ const previousTitle = seen.get(target);
501
+ if (previousTitle !== void 0) {
502
+ if (previousTitle !== prepared.data.title) {
503
+ return err("SCHEME_REJECTED", {
504
+ message: `duplicate index target ${target} with differing titles`,
505
+ titles: [previousTitle, prepared.data.title]
506
+ });
507
+ }
508
+ duplicatesRemoved += 1;
509
+ continue;
510
+ }
511
+ seen.set(target, prepared.data.title);
512
+ required.push({
513
+ section,
514
+ target,
515
+ title: prepared.data.title,
516
+ source: "typed"
517
+ });
518
+ }
519
+ const projectReadmes = scanned.data.allMarkdown.filter((page) => /^projects\/[^/]+\/README\.md$/.test(page.relPath)).sort((a, b) => compareText(a.relPath, b.relPath));
520
+ for (const readme of projectReadmes) {
521
+ let text;
522
+ try {
523
+ text = await readFile3(readme.absPath, "utf8");
524
+ } catch {
525
+ continue;
526
+ }
527
+ const projectSlug = readme.relPath.split("/")[1];
528
+ const target = `projects/${projectSlug}/README`;
529
+ const title = projectTitleFromReadme(text, projectSlug);
530
+ const previousTitle = seen.get(target);
531
+ if (previousTitle !== void 0) {
532
+ if (previousTitle !== title) {
533
+ return err("SCHEME_REJECTED", {
534
+ message: `duplicate index target ${target} with differing titles`
535
+ });
536
+ }
537
+ duplicatesRemoved += 1;
538
+ continue;
539
+ }
540
+ seen.set(target, title);
541
+ required.push({ section: "Projects", target, title, source: "project" });
542
+ }
543
+ required.sort((a, b) => {
544
+ const sectionA = ROOT_INDEX_SECTION_ORDER.indexOf(a.section);
545
+ const sectionB = ROOT_INDEX_SECTION_ORDER.indexOf(b.section);
546
+ if (sectionA !== sectionB) return sectionA - sectionB;
547
+ return compareText(a.target, b.target);
548
+ });
549
+ const knownTargets = new Set(required.map((entry) => entry.target));
550
+ for (const page of [...scanned.data.compound].sort((a, b) => compareText(a.relPath, b.relPath))) {
551
+ knownTargets.add(page.relPath.replace(/\.md$/, ""));
552
+ }
553
+ return ok({
554
+ required,
555
+ knownTargets,
556
+ rejectedTyped,
557
+ duplicatesRemoved
558
+ });
559
+ }
560
+
561
+ // src/utils/index-projection.ts
453
562
  var UNMANAGED_START = "<!-- skillwiki:index-unmanaged:start -->";
454
563
  var UNMANAGED_END = "<!-- skillwiki:index-unmanaged:end -->";
455
564
  function extractUnmanaged(currentText) {
@@ -480,94 +589,27 @@ function priorWikilinkTargets(text) {
480
589
  }
481
590
  return out;
482
591
  }
483
- function projectTitleFromReadme(text, slug) {
484
- const m = text.match(/^#\s+Project:\s+(.+)$/m);
485
- return m?.[1]?.trim() || slug;
486
- }
487
592
  async function renderRootIndex(input) {
488
593
  const vault = input.vault;
489
594
  let currentText = input.currentText;
490
595
  if (currentText === void 0) {
491
596
  try {
492
- currentText = await readFile3(join3(vault, "index.md"), "utf8");
597
+ currentText = await readFile4(join3(vault, "index.md"), "utf8");
493
598
  } catch {
494
599
  currentText = "";
495
600
  }
496
601
  }
497
602
  const unmanaged = extractUnmanaged(currentText);
498
603
  if (!unmanaged.ok) return unmanaged;
499
- const scan = await scanVault(vault);
500
- if (!scan.ok) return scan;
501
- const entries = [];
502
- const seen = /* @__PURE__ */ new Map();
503
- let duplicatesRemoved = 0;
504
- for (const page of scan.data.typedKnowledge) {
505
- let text;
506
- try {
507
- text = await readFile3(join3(vault, page.relPath), "utf8");
508
- } catch {
509
- continue;
510
- }
511
- const prepared = prepareTypedPage(text, page.relPath);
512
- if (!prepared.ok) continue;
513
- const section = TYPE_SECTION[prepared.data.type];
514
- if (!section) continue;
515
- const target = prepared.data.target.replace(/\.md$/, "");
516
- const prev = seen.get(target);
517
- if (prev !== void 0) {
518
- if (prev !== prepared.data.title) {
519
- return err("SCHEME_REJECTED", {
520
- message: `duplicate index target ${target} with differing titles`,
521
- titles: [prev, prepared.data.title]
522
- });
523
- }
524
- duplicatesRemoved += 1;
525
- continue;
526
- }
527
- seen.set(target, prepared.data.title);
528
- entries.push({
529
- section,
530
- target,
531
- title: prepared.data.title,
532
- source: "typed"
533
- });
534
- }
535
- try {
536
- const projectsRoot = join3(vault, "projects");
537
- for (const slug of readdirSync(projectsRoot, { withFileTypes: true })) {
538
- if (!slug.isDirectory()) continue;
539
- const readmePath = join3(projectsRoot, slug.name, "README.md");
540
- let text;
541
- try {
542
- text = readFileSync2(readmePath, "utf8");
543
- } catch {
544
- continue;
545
- }
546
- const target = `projects/${slug.name}/README`;
547
- const title = projectTitleFromReadme(text, slug.name);
548
- const prev = seen.get(target);
549
- if (prev !== void 0) {
550
- if (prev !== title) {
551
- return err("SCHEME_REJECTED", {
552
- message: `duplicate index target ${target} with differing titles`
553
- });
554
- }
555
- duplicatesRemoved += 1;
556
- continue;
557
- }
558
- seen.set(target, title);
559
- entries.push({ section: "Projects", target, title, source: "project" });
560
- }
561
- } catch {
562
- }
563
- entries.sort((a, b) => {
564
- const sa = SECTION_ORDER.indexOf(a.section);
565
- const sb = SECTION_ORDER.indexOf(b.section);
566
- if (sa !== sb) return sa - sb;
567
- return compareText(a.target, b.target);
568
- });
604
+ const universe = await buildRootIndexUniverse({ vault });
605
+ if (!universe.ok) return universe;
606
+ const entries = universe.data.required;
607
+ let duplicatesRemoved = universe.data.duplicatesRemoved;
569
608
  const prior = priorWikilinkTargets(currentText);
570
- const generatedTargets = new Set(entries.map((e) => e.target));
609
+ const knownTargets = universe.data.knownTargets;
610
+ const knownBasenames = new Set(
611
+ [...knownTargets].map((target) => target.split("/").pop().toLowerCase())
612
+ );
571
613
  const priorLower = /* @__PURE__ */ new Map();
572
614
  for (const t of prior) {
573
615
  const k = t.toLowerCase();
@@ -585,15 +627,10 @@ async function renderRootIndex(input) {
585
627
  const ghostsRemoved = [
586
628
  ...new Set(
587
629
  prior.filter((t) => {
588
- const bare = t.includes("/") ? t : null;
589
- if (bare && !generatedTargets.has(bare) && !generatedTargets.has(t)) {
590
- if (!seen.has(t)) return true;
591
- }
592
630
  if (!t.includes("/")) {
593
- const matches = [...generatedTargets].filter((g) => g.split("/").pop() === t);
594
- return matches.length === 0;
631
+ return !knownBasenames.has(t.toLowerCase());
595
632
  }
596
- return !generatedTargets.has(t);
633
+ return !knownTargets.has(t);
597
634
  })
598
635
  )
599
636
  ];
@@ -603,7 +640,7 @@ async function renderRootIndex(input) {
603
640
  "Generated by `skillwiki index rebuild`. Generated sections are derived from typed-page frontmatter and project manifests.",
604
641
  ""
605
642
  ];
606
- for (const section of SECTION_ORDER) {
643
+ for (const section of ROOT_INDEX_SECTION_ORDER) {
607
644
  lines.push(`## ${section}`, "");
608
645
  const sectionEntries = entries.filter((e) => e.section === section);
609
646
  for (const e of sectionEntries) {
@@ -642,6 +679,7 @@ export {
642
679
  scanVault,
643
680
  readPage,
644
681
  readPageCached,
682
+ buildRootIndexUniverse,
645
683
  renderRootIndex,
646
684
  writeRootIndexProjection
647
685
  };
@@ -5,6 +5,7 @@ import {
5
5
  } from "./chunk-7I2TPIV5.js";
6
6
  import {
7
7
  atomicWriteText,
8
+ buildRootIndexUniverse,
8
9
  extractFrontmatter,
9
10
  mapWithConcurrency,
10
11
  prepareTypedPage,
@@ -18,7 +19,7 @@ import {
18
19
  splitFrontmatter,
19
20
  vaultIoConcurrency,
20
21
  writeRootIndexProjection
21
- } from "./chunk-IZABIE44.js";
22
+ } from "./chunk-U34B2XQJ.js";
22
23
  import {
23
24
  CONFIG_KEYS,
24
25
  isValidWikiProfileKey,
@@ -1561,8 +1562,11 @@ function normalizeIndexTarget(raw) {
1561
1562
  return raw.replace(/\.md$/, "").replace(/^\.?\//, "");
1562
1563
  }
1563
1564
  async function runIndexCheck(input) {
1564
- const scan = input.scan ? ok(input.scan) : await scanVault(input.vault);
1565
- if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
1565
+ const universe = await buildRootIndexUniverse({ vault: input.vault, scan: input.scan });
1566
+ if (!universe.ok) {
1567
+ const exitCode = universe.error === "VAULT_PATH_INVALID" ? ExitCode.VAULT_PATH_INVALID : ExitCode.SCHEME_REJECTED;
1568
+ return { exitCode, result: universe };
1569
+ }
1566
1570
  let indexText = "";
1567
1571
  try {
1568
1572
  indexText = await readFile6(join10(input.vault, "index.md"), "utf8");
@@ -1579,25 +1583,22 @@ async function runIndexCheck(input) {
1579
1583
  indexBare.set(bare, list);
1580
1584
  }
1581
1585
  const required = /* @__PURE__ */ new Map();
1582
- const known = /* @__PURE__ */ new Set();
1583
- for (const p of scan.data.typedKnowledge) {
1584
- const target = p.relPath.replace(/\.md$/, "");
1585
- required.set(target, p.relPath);
1586
- known.add(target);
1586
+ for (const entry of universe.data.required) {
1587
+ required.set(entry.target, `${entry.target}.md`);
1587
1588
  }
1588
- for (const p of scan.data.compound) {
1589
- known.add(p.relPath.replace(/\.md$/, ""));
1589
+ const requiredBasenameCounts = /* @__PURE__ */ new Map();
1590
+ for (const target of required.keys()) {
1591
+ const basename3 = target.split("/").pop().toLowerCase();
1592
+ requiredBasenameCounts.set(basename3, (requiredBasenameCounts.get(basename3) ?? 0) + 1);
1590
1593
  }
1594
+ const known = universe.data.knownTargets;
1591
1595
  const missing_from_index = [];
1592
1596
  for (const [target, relPath] of required.entries()) {
1593
1597
  if (indexTargets.has(target)) continue;
1594
1598
  const bare = target.split("/").pop().toLowerCase();
1595
1599
  const bareHits = indexBare.get(bare) ?? [];
1596
1600
  const basenameOnly = bareHits.filter((t) => !t.includes("/"));
1597
- const sameNameRequired = [...required.keys()].filter(
1598
- (t) => t.split("/").pop().toLowerCase() === bare
1599
- );
1600
- if (basenameOnly.length === 1 && sameNameRequired.length === 1) continue;
1601
+ if (basenameOnly.length === 1 && requiredBasenameCounts.get(bare) === 1) continue;
1601
1602
  missing_from_index.push(relPath);
1602
1603
  }
1603
1604
  const ghost_entries = [];
package/dist/cli.js CHANGED
@@ -68,7 +68,7 @@ import {
68
68
  satelliteLatestRunPath,
69
69
  taxonomyCommentForPage,
70
70
  upsertIndexEntry
71
- } from "./chunk-XSTXPA34.js";
71
+ } from "./chunk-Z3WS45PL.js";
72
72
  import {
73
73
  normalizeDistTag,
74
74
  readCache,
@@ -88,7 +88,7 @@ import {
88
88
  scanVault,
89
89
  splitFrontmatter,
90
90
  writeRootIndexProjection
91
- } from "./chunk-IZABIE44.js";
91
+ } from "./chunk-U34B2XQJ.js";
92
92
  import {
93
93
  acquireManagedWriteLock,
94
94
  releaseManagedWriteLock,
@@ -2533,7 +2533,7 @@ ${fmRewritten}
2533
2533
  await rename2(join17(input.vault, relPath), join17(input.vault, archivePath));
2534
2534
  let indexUpdated = false;
2535
2535
  if (!isRaw) {
2536
- const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-ERFX76U5.js");
2536
+ const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-VNJ3TLEK.js");
2537
2537
  const before = await readFile7(join17(input.vault, "index.md"), "utf8").catch(() => "");
2538
2538
  const fullTarget = relPath.replace(/\.md$/, "");
2539
2539
  const bare = fullTarget.split("/").pop() ?? fullTarget;
@@ -2644,7 +2644,7 @@ async function runRemove(input) {
2644
2644
  if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
2645
2645
  const { readFile: readFile17 } = await import("fs/promises");
2646
2646
  const { join: pathJoin } = await import("path");
2647
- const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-ERFX76U5.js");
2647
+ const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-VNJ3TLEK.js");
2648
2648
  const before = await readFile17(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
2649
2649
  const fullTarget = relPath.replace(/\.md$/, "");
2650
2650
  const bare = fullTarget.split("/").pop() ?? fullTarget;
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  renderRootIndex,
4
4
  writeRootIndexProjection
5
- } from "./chunk-IZABIE44.js";
5
+ } from "./chunk-U34B2XQJ.js";
6
6
  import "./chunk-C5OLZRRM.js";
7
7
  export {
8
8
  renderRootIndex,
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runSkillwikiMcpStdio
4
- } from "./chunk-XSTXPA34.js";
4
+ } from "./chunk-Z3WS45PL.js";
5
5
  import "./chunk-7I2TPIV5.js";
6
- import "./chunk-IZABIE44.js";
6
+ import "./chunk-U34B2XQJ.js";
7
7
  import "./chunk-R6BKJWVC.js";
8
8
  import "./chunk-C5OLZRRM.js";
9
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "skillwiki": "dist/cli.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 19 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 19 prompt-only skills backed by the deterministic skillwiki CLI.",
5
5
  "author": {
6
6
  "name": "karlorz",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",