world-building-framework 0.0.0-stage → 0.2.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/LICENSE +9 -0
- package/README.md +104 -2
- package/dist/cli/check.d.ts +13 -0
- package/dist/cli/check.js +11 -0
- package/dist/cli/commands/create.d.ts +20 -0
- package/dist/cli/commands/create.js +35 -0
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.js +119 -0
- package/dist/cli/commands/list-variants.d.ts +1 -0
- package/dist/cli/commands/list-variants.js +7 -0
- package/dist/cli/commands/publish.d.ts +24 -0
- package/dist/cli/commands/publish.js +71 -0
- package/dist/cli/commands/refresh.d.ts +29 -0
- package/dist/cli/commands/refresh.js +70 -0
- package/dist/cli/commands/render.d.ts +14 -0
- package/dist/cli/commands/render.js +27 -0
- package/dist/cli/commands/schema.d.ts +2 -0
- package/dist/cli/commands/schema.js +9 -0
- package/dist/cli/commands/search.d.ts +9 -0
- package/dist/cli/commands/search.js +20 -0
- package/dist/cli/commands/update.d.ts +23 -0
- package/dist/cli/commands/update.js +69 -0
- package/dist/cli/commands/validate.d.ts +21 -0
- package/dist/cli/commands/validate.js +44 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +157 -0
- package/dist/cli/io.d.ts +4 -0
- package/dist/cli/io.js +25 -0
- package/dist/cli/managed-files.d.ts +35 -0
- package/dist/cli/managed-files.js +80 -0
- package/dist/cli/quartz-runner.d.ts +22 -0
- package/dist/cli/quartz-runner.js +76 -0
- package/dist/cli/quartz-setup.d.ts +10 -0
- package/dist/cli/quartz-setup.js +59 -0
- package/dist/cli/vault-path.d.ts +7 -0
- package/dist/cli/vault-path.js +44 -0
- package/dist/core/config/build-variants.d.ts +5 -0
- package/dist/core/config/build-variants.js +13 -0
- package/dist/core/config/index.d.ts +4 -0
- package/dist/core/config/index.js +4 -0
- package/dist/core/config/load.d.ts +4 -0
- package/dist/core/config/load.js +25 -0
- package/dist/core/config/schema.d.ts +29 -0
- package/dist/core/config/schema.js +23 -0
- package/dist/core/config/validate-options.d.ts +4 -0
- package/dist/core/config/validate-options.js +4 -0
- package/dist/core/graph/build.d.ts +8 -0
- package/dist/core/graph/build.js +42 -0
- package/dist/core/graph/causal.d.ts +22 -0
- package/dist/core/graph/causal.js +106 -0
- package/dist/core/graph/dating.d.ts +18 -0
- package/dist/core/graph/dating.js +30 -0
- package/dist/core/graph/entities.d.ts +13 -0
- package/dist/core/graph/entities.js +22 -0
- package/dist/core/graph/index.d.ts +7 -0
- package/dist/core/graph/index.js +7 -0
- package/dist/core/graph/queries.d.ts +3 -0
- package/dist/core/graph/queries.js +8 -0
- package/dist/core/graph/types.d.ts +14 -0
- package/dist/core/graph/types.js +1 -0
- package/dist/core/graph/versioning.d.ts +15 -0
- package/dist/core/graph/versioning.js +50 -0
- package/dist/core/id.d.ts +3 -0
- package/dist/core/id.js +9 -0
- package/dist/core/lint/after-loops.d.ts +15 -0
- package/dist/core/lint/after-loops.js +34 -0
- package/dist/core/lint/dangling-links.d.ts +9 -0
- package/dist/core/lint/dangling-links.js +46 -0
- package/dist/core/lint/events-without-entities.d.ts +4 -0
- package/dist/core/lint/events-without-entities.js +9 -0
- package/dist/core/lint/forked-versions.d.ts +14 -0
- package/dist/core/lint/forked-versions.js +29 -0
- package/dist/core/lint/index.d.ts +6 -0
- package/dist/core/lint/index.js +6 -0
- package/dist/core/lint/lint.d.ts +24 -0
- package/dist/core/lint/lint.js +50 -0
- package/dist/core/lint/target-types.d.ts +9 -0
- package/dist/core/lint/target-types.js +47 -0
- package/dist/core/query/dates.d.ts +34 -0
- package/dist/core/query/dates.js +43 -0
- package/dist/core/query/index.d.ts +2 -0
- package/dist/core/query/index.js +2 -0
- package/dist/core/query/types.d.ts +21 -0
- package/dist/core/query/types.js +12 -0
- package/dist/core/schema/describe.d.ts +34 -0
- package/dist/core/schema/describe.js +99 -0
- package/dist/core/schema/fields.d.ts +57 -0
- package/dist/core/schema/fields.js +43 -0
- package/dist/core/schema/index.d.ts +4 -0
- package/dist/core/schema/index.js +4 -0
- package/dist/core/schema/validate.d.ts +22 -0
- package/dist/core/schema/validate.js +34 -0
- package/dist/core/schema/wikilink.d.ts +8 -0
- package/dist/core/schema/wikilink.js +23 -0
- package/dist/core/search/build.d.ts +11 -0
- package/dist/core/search/build.js +26 -0
- package/dist/core/search/columns.d.ts +9 -0
- package/dist/core/search/columns.js +14 -0
- package/dist/core/search/index.d.ts +3 -0
- package/dist/core/search/index.js +3 -0
- package/dist/core/search/search.d.ts +3 -0
- package/dist/core/search/search.js +35 -0
- package/dist/core/search/types.d.ts +18 -0
- package/dist/core/search/types.js +1 -0
- package/dist/core/vault/index.d.ts +6 -0
- package/dist/core/vault/index.js +6 -0
- package/dist/core/vault/parse.d.ts +8 -0
- package/dist/core/vault/parse.js +11 -0
- package/dist/core/vault/read.d.ts +13 -0
- package/dist/core/vault/read.js +62 -0
- package/dist/core/vault/relink.d.ts +10 -0
- package/dist/core/vault/relink.js +33 -0
- package/dist/core/vault/serialize.d.ts +4 -0
- package/dist/core/vault/serialize.js +4 -0
- package/dist/core/vault/types.d.ts +5 -0
- package/dist/core/vault/types.js +1 -0
- package/dist/core/vault/write.d.ts +18 -0
- package/dist/core/vault/write.js +37 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +11 -0
- package/dist/render-pipeline/build.d.ts +12 -0
- package/dist/render-pipeline/build.js +49 -0
- package/dist/render-pipeline/filter.d.ts +21 -0
- package/dist/render-pipeline/filter.js +80 -0
- package/dist/render-pipeline/index.d.ts +11 -0
- package/dist/render-pipeline/index.js +11 -0
- package/dist/render-pipeline/merge.d.ts +11 -0
- package/dist/render-pipeline/merge.js +21 -0
- package/dist/render-pipeline/quartz-compat.d.ts +1 -0
- package/dist/render-pipeline/quartz-compat.js +18 -0
- package/dist/render-pipeline/rewrite.d.ts +8 -0
- package/dist/render-pipeline/rewrite.js +23 -0
- package/dist/render-pipeline/slugs.d.ts +8 -0
- package/dist/render-pipeline/slugs.js +31 -0
- package/dist/render-pipeline/start-page.d.ts +9 -0
- package/dist/render-pipeline/start-page.js +44 -0
- package/dist/render-pipeline/sync.d.ts +9 -0
- package/dist/render-pipeline/sync.js +18 -0
- package/dist/render-pipeline/types.d.ts +8 -0
- package/dist/render-pipeline/types.js +1 -0
- package/dist/render-pipeline/write.d.ts +3 -0
- package/dist/render-pipeline/write.js +12 -0
- package/package.json +46 -4
- package/skills/world-vault/SKILL.md +100 -0
- package/templates/AGENTS.md +11 -0
- package/templates/gitignore +5 -0
- package/templates/publish.yml +54 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "../core/query/index.js";
|
|
2
|
+
export * from "./types.js";
|
|
3
|
+
export * from "./filter.js";
|
|
4
|
+
export * from "./merge.js";
|
|
5
|
+
export * from "./slugs.js";
|
|
6
|
+
export * from "./rewrite.js";
|
|
7
|
+
export * from "./quartz-compat.js";
|
|
8
|
+
export * from "./start-page.js";
|
|
9
|
+
export * from "./build.js";
|
|
10
|
+
export * from "./write.js";
|
|
11
|
+
export * from "./sync.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "../core/query/index.js";
|
|
2
|
+
export * from "./types.js";
|
|
3
|
+
export * from "./filter.js";
|
|
4
|
+
export * from "./merge.js";
|
|
5
|
+
export * from "./slugs.js";
|
|
6
|
+
export * from "./rewrite.js";
|
|
7
|
+
export * from "./quartz-compat.js";
|
|
8
|
+
export * from "./start-page.js";
|
|
9
|
+
export * from "./build.js";
|
|
10
|
+
export * from "./write.js";
|
|
11
|
+
export * from "./sync.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { KnowledgeGraph } from "../core/graph/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The full body for an included entity: its anchor text, followed by each later
|
|
4
|
+
* version whose causing event is itself included in this build. Using the
|
|
5
|
+
* events-included set (rather than re-checking dates here) means one mechanism
|
|
6
|
+
* handles both date-range truncation and tag-based hiding — a version caused by
|
|
7
|
+
* a hidden event never shows up as an unexplained state change. Each version's
|
|
8
|
+
* body is a full rewrite (SCHEMA.md), not a diff, so skipping a hidden version
|
|
9
|
+
* and keeping a later one still reads coherently.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeEntityBody(graph: KnowledgeGraph, anchorId: string, includedEventIds: Set<string>): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { versionHistory, outgoing } from "../core/graph/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The full body for an included entity: its anchor text, followed by each later
|
|
4
|
+
* version whose causing event is itself included in this build. Using the
|
|
5
|
+
* events-included set (rather than re-checking dates here) means one mechanism
|
|
6
|
+
* handles both date-range truncation and tag-based hiding — a version caused by
|
|
7
|
+
* a hidden event never shows up as an unexplained state change. Each version's
|
|
8
|
+
* body is a full rewrite (SCHEMA.md), not a diff, so skipping a hidden version
|
|
9
|
+
* and keeping a later one still reads coherently.
|
|
10
|
+
*/
|
|
11
|
+
export function mergeEntityBody(graph, anchorId, includedEventIds) {
|
|
12
|
+
const history = versionHistory(graph, anchorId);
|
|
13
|
+
const sections = [history[0]?.body ?? ""];
|
|
14
|
+
for (const version of history.slice(1)) {
|
|
15
|
+
const causedByEdge = outgoing(graph, version.id, "causedBy")[0];
|
|
16
|
+
if (!causedByEdge || !includedEventIds.has(causedByEdge.to))
|
|
17
|
+
continue;
|
|
18
|
+
sections.push(`## After: ${causedByEdge.display}\n\n${version.body}`);
|
|
19
|
+
}
|
|
20
|
+
return sections.join("\n\n");
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function renameQuartzReservedFields(frontmatter: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quartz reserves the `date` frontmatter key for its own created/modified
|
|
3
|
+
* handling — verified hands-on: a real Quartz build logged "invalid date
|
|
4
|
+
* [object Object]" warnings for our event `date: {year, day}` objects, and
|
|
5
|
+
* renaming the key made them disappear. This only affects Stage 1's output —
|
|
6
|
+
* the vault's own schema keeps `date` (SCHEMA.md); this rewrite is purely
|
|
7
|
+
* cosmetic for what Quartz sees.
|
|
8
|
+
*/
|
|
9
|
+
const QUARTZ_RESERVED_FIELD_RENAMES = {
|
|
10
|
+
date: "worldDate",
|
|
11
|
+
};
|
|
12
|
+
export function renameQuartzReservedFields(frontmatter) {
|
|
13
|
+
const renamed = {};
|
|
14
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
15
|
+
renamed[QUARTZ_RESERVED_FIELD_RENAMES[key] ?? key] = value;
|
|
16
|
+
}
|
|
17
|
+
return renamed;
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrites every `[[uuid|Display]]` link (SCHEMA.md rule 7): to `[[slug|Display]]`
|
|
3
|
+
* — a Quartz-compatible wikilink — if the target is included in this build, or
|
|
4
|
+
* strips it to plain `Display` text otherwise (the resolved link-integrity rule,
|
|
5
|
+
* RENDER_PIPELINE.md).
|
|
6
|
+
*/
|
|
7
|
+
export declare function rewriteLinks(text: string, slugsById: Map<string, string>): string;
|
|
8
|
+
export declare function rewriteFrontmatterLinks(frontmatter: Record<string, unknown>, slugsById: Map<string, string>): Record<string, unknown>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const WIKILINK_RE = /\[\[([0-9a-f-]{36})\|([^\]]*)\]\]/gi;
|
|
2
|
+
/**
|
|
3
|
+
* Rewrites every `[[uuid|Display]]` link (SCHEMA.md rule 7): to `[[slug|Display]]`
|
|
4
|
+
* — a Quartz-compatible wikilink — if the target is included in this build, or
|
|
5
|
+
* strips it to plain `Display` text otherwise (the resolved link-integrity rule,
|
|
6
|
+
* RENDER_PIPELINE.md).
|
|
7
|
+
*/
|
|
8
|
+
export function rewriteLinks(text, slugsById) {
|
|
9
|
+
return text.replace(WIKILINK_RE, (_match, targetId, display) => {
|
|
10
|
+
const slug = slugsById.get(targetId);
|
|
11
|
+
return slug ? `[[${slug}|${display}]]` : display;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function rewriteValue(value, slugsById) {
|
|
15
|
+
if (typeof value === "string")
|
|
16
|
+
return rewriteLinks(value, slugsById);
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return value.map((entry) => rewriteValue(entry, slugsById));
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export function rewriteFrontmatterLinks(frontmatter, slugsById) {
|
|
22
|
+
return Object.fromEntries(Object.entries(frontmatter).map(([key, value]) => [key, rewriteValue(value, slugsById)]));
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assigns a unique, readable slug per id — vault files are UUID-named (SCHEMA.md),
|
|
3
|
+
* meaningless as a published URL; this is what Stage 1 uses instead for the
|
|
4
|
+
* derived tree. Iterates `titles` in whatever order the caller provides, so pass
|
|
5
|
+
* ids in a deterministic order for reproducible builds. No page gets a slug
|
|
6
|
+
* in `reserved` (e.g. the start page's "index").
|
|
7
|
+
*/
|
|
8
|
+
export declare function assignSlugs(titles: Map<string, string>, reserved?: string[]): Map<string, string>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function slugify(title) {
|
|
2
|
+
const slug = title
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.trim()
|
|
5
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
6
|
+
.replace(/^-+|-+$/g, "");
|
|
7
|
+
return slug || "untitled";
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Assigns a unique, readable slug per id — vault files are UUID-named (SCHEMA.md),
|
|
11
|
+
* meaningless as a published URL; this is what Stage 1 uses instead for the
|
|
12
|
+
* derived tree. Iterates `titles` in whatever order the caller provides, so pass
|
|
13
|
+
* ids in a deterministic order for reproducible builds. No page gets a slug
|
|
14
|
+
* in `reserved` (e.g. the start page's "index").
|
|
15
|
+
*/
|
|
16
|
+
export function assignSlugs(titles, reserved = []) {
|
|
17
|
+
const used = new Set(reserved);
|
|
18
|
+
const slugs = new Map();
|
|
19
|
+
for (const [id, title] of titles) {
|
|
20
|
+
const base = slugify(title);
|
|
21
|
+
let slug = base;
|
|
22
|
+
let suffix = 2;
|
|
23
|
+
while (used.has(slug)) {
|
|
24
|
+
slug = `${base}-${suffix}`;
|
|
25
|
+
suffix += 1;
|
|
26
|
+
}
|
|
27
|
+
used.add(slug);
|
|
28
|
+
slugs.set(id, slug);
|
|
29
|
+
}
|
|
30
|
+
return slugs;
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RenderedPage } from "./types.js";
|
|
2
|
+
/** Quartz builds a site's root page from `content/index.md`; without it, the wiki's own address is a 404. */
|
|
3
|
+
export declare const START_PAGE_SLUG = "index";
|
|
4
|
+
/**
|
|
5
|
+
* The wiki's start page: every included entity, grouped by entityType and sorted
|
|
6
|
+
* by title, then every included event in causal order. `events` must already be
|
|
7
|
+
* in that order; only pages of this build go in, so nothing filtered out shows up.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildStartPage(title: string, entities: RenderedPage[], events: RenderedPage[]): RenderedPage;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Quartz builds a site's root page from `content/index.md`; without it, the wiki's own address is a 404. */
|
|
2
|
+
export const START_PAGE_SLUG = "index";
|
|
3
|
+
function titleOf(page) {
|
|
4
|
+
return typeof page.frontmatter.title === "string" ? page.frontmatter.title : page.slug;
|
|
5
|
+
}
|
|
6
|
+
function linkTo(page) {
|
|
7
|
+
return `[[${page.slug}|${titleOf(page)}]]`;
|
|
8
|
+
}
|
|
9
|
+
function describeDate(page) {
|
|
10
|
+
const date = page.frontmatter.worldDate;
|
|
11
|
+
if (typeof date?.year !== "number")
|
|
12
|
+
return "";
|
|
13
|
+
return typeof date.day === "number" ? ` (year ${date.year}, day ${date.day})` : ` (year ${date.year})`;
|
|
14
|
+
}
|
|
15
|
+
function heading(entityType) {
|
|
16
|
+
return entityType.charAt(0).toUpperCase() + entityType.slice(1);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The wiki's start page: every included entity, grouped by entityType and sorted
|
|
20
|
+
* by title, then every included event in causal order. `events` must already be
|
|
21
|
+
* in that order; only pages of this build go in, so nothing filtered out shows up.
|
|
22
|
+
*/
|
|
23
|
+
export function buildStartPage(title, entities, events) {
|
|
24
|
+
const groups = new Map();
|
|
25
|
+
for (const page of entities) {
|
|
26
|
+
const entityType = typeof page.frontmatter.entityType === "string" ? page.frontmatter.entityType : "";
|
|
27
|
+
groups.set(entityType, [...(groups.get(entityType) ?? []), page]);
|
|
28
|
+
}
|
|
29
|
+
// Typed groups alphabetically, untyped entities last.
|
|
30
|
+
const groupKeys = [...groups.keys()].sort((a, b) => (a === "" ? 1 : b === "" ? -1 : a.localeCompare(b)));
|
|
31
|
+
const sections = groupKeys.map((key) => {
|
|
32
|
+
const pages = groups.get(key).sort((a, b) => titleOf(a).localeCompare(titleOf(b)));
|
|
33
|
+
return `## ${key === "" ? "Other" : heading(key)}\n\n${pages.map((page) => `- ${linkTo(page)}`).join("\n")}`;
|
|
34
|
+
});
|
|
35
|
+
if (events.length > 0) {
|
|
36
|
+
sections.push(`## Events\n\n${events.map((page) => `- ${linkTo(page)}${describeDate(page)}`).join("\n")}`);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
id: START_PAGE_SLUG,
|
|
40
|
+
slug: START_PAGE_SLUG,
|
|
41
|
+
frontmatter: { title },
|
|
42
|
+
body: sections.length > 0 ? `${sections.join("\n\n")}\n` : "Nothing here yet.\n",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RenderedPage } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Writes the derived tree into a Quartz content folder, first clearing any
|
|
4
|
+
* `.md` files already there. Plain writeDerivedTree alone isn't enough here —
|
|
5
|
+
* without clearing first, a page removed/renamed by a later build (e.g. a
|
|
6
|
+
* build-variant query change, or an entity dropping out of range) would leave
|
|
7
|
+
* a stale, orphaned page behind in Quartz's content folder.
|
|
8
|
+
*/
|
|
9
|
+
export declare function syncDerivedTreeToQuartz(quartzContentDir: string, pages: RenderedPage[]): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { mkdir, readdir, rm } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { writeDerivedTree } from "./write.js";
|
|
4
|
+
/**
|
|
5
|
+
* Writes the derived tree into a Quartz content folder, first clearing any
|
|
6
|
+
* `.md` files already there. Plain writeDerivedTree alone isn't enough here —
|
|
7
|
+
* without clearing first, a page removed/renamed by a later build (e.g. a
|
|
8
|
+
* build-variant query change, or an entity dropping out of range) would leave
|
|
9
|
+
* a stale, orphaned page behind in Quartz's content folder.
|
|
10
|
+
*/
|
|
11
|
+
export async function syncDerivedTreeToQuartz(quartzContentDir, pages) {
|
|
12
|
+
await mkdir(quartzContentDir, { recursive: true });
|
|
13
|
+
const existing = await readdir(quartzContentDir);
|
|
14
|
+
await Promise.all(existing
|
|
15
|
+
.filter((name) => name.endsWith(".md"))
|
|
16
|
+
.map((name) => rm(path.join(quartzContentDir, name))));
|
|
17
|
+
await writeDerivedTree(quartzContentDir, pages);
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { writeFile } from "node:fs/promises";
|
|
4
|
+
import { serializeVaultFile } from "../core/vault/index.js";
|
|
5
|
+
/** Writes the derived tree to disk as plain markdown, ready for Stage 2 (Quartz) to build from later. */
|
|
6
|
+
export async function writeDerivedTree(outputDir, pages) {
|
|
7
|
+
await mkdir(outputDir, { recursive: true });
|
|
8
|
+
for (const page of pages) {
|
|
9
|
+
const content = serializeVaultFile({ frontmatter: page.frontmatter, body: page.body });
|
|
10
|
+
await writeFile(path.join(outputDir, `${page.slug}.md`), content, "utf8");
|
|
11
|
+
}
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "world-building-framework",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Build, search and publish fictional worlds from a vault of Markdown files: the wbf CLI and its core engine.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"wbf": "dist/cli/index.js"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"skills",
|
|
23
|
+
"templates"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.build.json",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "node scripts/run-tests.mjs",
|
|
29
|
+
"cli": "tsx src/cli/index.ts",
|
|
30
|
+
"setup:quartz": "tsx src/cli/index.ts setup-quartz",
|
|
31
|
+
"prepack": "npm run build",
|
|
32
|
+
"prepublishOnly": "npm run typecheck && npm test"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"better-sqlite3": "^13.0.3",
|
|
36
|
+
"commander": "^15.0.0",
|
|
37
|
+
"gray-matter": "^4.0.3",
|
|
38
|
+
"uuid": "^14.0.2",
|
|
39
|
+
"zod": "^4.6.7"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/better-sqlite3": "^9.6.0",
|
|
43
|
+
"@types/node": "^22.20.1",
|
|
44
|
+
"glob": "^13.0.6",
|
|
45
|
+
"tsx": "^4.16.2",
|
|
46
|
+
"typescript": "^5.5.4"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: world-vault
|
|
3
|
+
description: Search, create, correct, and validate entity/event files in a World Building Framework world via the `wbf` CLI. Use whenever the user wants to look up, add, or change world-building content (characters, locations, events, factions, ...) in this repo.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# World Vault authoring
|
|
7
|
+
|
|
8
|
+
This repo is a world built with the World Building Framework. Its content is a vault of entity/event Markdown files, named by UUID, next to a `world.config.json`. You read and write it through the `wbf` command-line tool, which prints JSON.
|
|
9
|
+
|
|
10
|
+
**Never hand-write or hand-edit a vault file.** Only `wbf create` and `wbf update` validate content before it touches disk, and only `create` generates the required UUID filename.
|
|
11
|
+
|
|
12
|
+
Run every command from the repo root as `npx wbf <command>`. That runs the framework version installed in this repo. If it reports "No vault specified", you're not in the folder that holds `world.config.json`: change into it, or pass `--vault <path>`.
|
|
13
|
+
|
|
14
|
+
Commands that take JSON input read it from stdin, or from a file with `--input <file>`. Use a file when piping is awkward in your shell.
|
|
15
|
+
|
|
16
|
+
## 1. Check the current schema before writing anything
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
npx wbf schema
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This prints the framework's fixed fields, plus this world's own `entityType` list from `world.config.json`, if it declares one. Run it whenever you're unsure which fields are valid, especially before creating an entity with an `entityType` you haven't seen used here yet.
|
|
23
|
+
|
|
24
|
+
## 2. Search before creating
|
|
25
|
+
|
|
26
|
+
Search for what already exists before adding anything. That avoids duplicating an entity, and gives you the UUIDs you need to link to things:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
npx wbf search <query terms...>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Each result has an `id` (the UUID you link with), `title` and `score`, and for entities `entityType` and `tags`. Add `--tags a,b` to require specific tags, and `--limit N` to cap the results.
|
|
33
|
+
|
|
34
|
+
## 3. Writing links
|
|
35
|
+
|
|
36
|
+
Every relation, in frontmatter or in body text, uses this link form: the target's UUID before the pipe, and its current title after it.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
[[3af2c9d1-8b4e-4c6a-91f0-2d5e7a9b1c33|Port Kavas]]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Get the UUID from a search result's `id`, or from the `id` a `create` call just returned. Never write a bare `[[Display Name]]`. The vault only resolves links by UUID.
|
|
43
|
+
|
|
44
|
+
## 4. Propose before writing
|
|
45
|
+
|
|
46
|
+
No tool here enforces an approval step, so that's your job. Before calling `create` or `update`, show the user the frontmatter and body you're about to write, and wait for their go-ahead, the way you'd show a diff before editing code.
|
|
47
|
+
|
|
48
|
+
## 5. Validate before (or instead of) creating
|
|
49
|
+
|
|
50
|
+
To check a candidate without writing it:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
echo '{"frontmatter": {"type": "entity", "title": "Alea Vantorr", "entityType": "character"}}' | npx wbf validate --input -
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Each issue has a `severity`:
|
|
57
|
+
- Fix every `"error"` (schema problems, dangling links, wrong link targets) before calling `create`.
|
|
58
|
+
- A `"warning"` (e.g. an event with no `entities` yet) doesn't block anything. Mention it to the user, but it's fine to proceed.
|
|
59
|
+
|
|
60
|
+
`create` runs these same checks, so `valid: true` here means `create` will succeed.
|
|
61
|
+
|
|
62
|
+
## 6. Creating a file
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
echo '{"frontmatter": {...}, "body": "..."}' | npx wbf create
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **Required fields:** `type` and `title`. Everything else depends on whether it's an entity or an event; see `npx wbf schema`.
|
|
69
|
+
- **On success** it prints `{"written": true, "id": "...", "path": "..."}`, plus an `issues` list if there were warnings. Tell the user the new `id`, since anything that links to this file needs that UUID.
|
|
70
|
+
- **If any link points at an id that doesn't exist**, nothing is written, and the error is reported instead.
|
|
71
|
+
|
|
72
|
+
## 7. Correcting an existing file
|
|
73
|
+
|
|
74
|
+
For a **correction** (a typo, a wrong tag, a missing link, badly written text), replace the file in place:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
echo '{"frontmatter": {...}, "body": "..."}' | npx wbf update --id <uuid>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- **Send the complete content.** The input is the file's whole new content, not just the changed fields. Read the current file (`<uuid>.md`) first, change what needs changing, and send all of it. Any field you leave out is removed.
|
|
81
|
+
- **Same checks as `create`**, and `type` can't change.
|
|
82
|
+
- **Show the user what you're changing** before calling it, as with `create`.
|
|
83
|
+
|
|
84
|
+
Changing a `title` also changes **other files**:
|
|
85
|
+
- Every link elsewhere written as `[[<uuid>|<old title>]]` is rewritten to show the new title.
|
|
86
|
+
- Links with different display text, like `[[<uuid>|the old harbour]]`, are left alone.
|
|
87
|
+
- When proposing a rename, tell the user other files will be updated too. Search the vault for `[[<uuid>|<old title>]]` to say which ones.
|
|
88
|
+
- The result lists them under `relinked`. Any file under `relinkFailed` is already invalid on its own and kept the old name; report it to the user.
|
|
89
|
+
|
|
90
|
+
`update` is only for things that were wrong. If something changed **in the story**, use section 8 instead.
|
|
91
|
+
|
|
92
|
+
## 8. Recording a change to an existing entity: never edit it in place
|
|
93
|
+
|
|
94
|
+
When an event changes something about an entity (it looks different, its status changed, and so on), that's **not** an edit to the existing file. Create a **new** entity file with these two fields:
|
|
95
|
+
- `supersedes`: a link to the entity's current file. That's its **latest** version, not the anchor, unless the anchor is the only version so far. A "Fork in the version chain" warning almost always means you pointed at an older version. Tell the user and fix it with `update`, unless they deliberately want a story branch.
|
|
96
|
+
- `causedBy`: a link to the event that caused the change.
|
|
97
|
+
|
|
98
|
+
Both fields are required together. The vault keeps every version, and the published wiki shows the whole history. Never overwrite an entity file's body to reflect an in-world change; that erases history the vault is designed to keep.
|
|
99
|
+
|
|
100
|
+
Other files always link to an entity's **original, first-ever** file (its anchor), never to a later version. If you're not sure which id is the anchor, follow the file's `supersedes` chain back. Or reuse whatever id you already have for that entity: that's always the anchor once created.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Agent instructions
|
|
2
|
+
|
|
3
|
+
This repo is a world built with the World Building Framework (version __WBF_VERSION__). Its content is a vault of UUID-named Markdown files next to `world.config.json`.
|
|
4
|
+
|
|
5
|
+
- Read and write the vault only through the `wbf` command-line tool: run `npx wbf <command>` from this folder. It prints JSON.
|
|
6
|
+
- Run `npx wbf schema` before writing anything, `npx wbf search <terms>` before creating something, and `npx wbf --help` for every command.
|
|
7
|
+
- Never hand-write or hand-edit a vault `.md` file. Only `wbf create` and `wbf update` validate content, and only `create` generates the required UUID filename.
|
|
8
|
+
- Show the user what you're about to write, and wait for their go-ahead, before `create` or `update`.
|
|
9
|
+
- If skills were installed for your agent (`npx wbf init --agent <name>`), the `world-vault` skill has the full workflow.
|
|
10
|
+
|
|
11
|
+
Generated by `wbf init`. `npx wbf refresh` keeps it current while it's unedited.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Generated by world-building-framework __WBF_VERSION__ (`wbf init`).
|
|
2
|
+
# `npx wbf refresh` updates this file to the installed framework version, but
|
|
3
|
+
# only while you haven't edited it. Once you change it, it's yours to maintain.
|
|
4
|
+
name: Publish wiki
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [__BRANCH__]
|
|
9
|
+
workflow_dispatch: {}
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pages: write
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: pages
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
build:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 22
|
|
29
|
+
cache: npm
|
|
30
|
+
|
|
31
|
+
- name: Install the framework (version pinned in package.json)
|
|
32
|
+
run: npm ci
|
|
33
|
+
|
|
34
|
+
# Runs before publish so its base_url output (the Pages site's real
|
|
35
|
+
# address) can be handed to Quartz.
|
|
36
|
+
- uses: actions/configure-pages@v5
|
|
37
|
+
id: pages
|
|
38
|
+
|
|
39
|
+
- name: Publish every build variant
|
|
40
|
+
run: npx wbf publish --output ./site --base-url "${{ steps.pages.outputs.base_url }}"
|
|
41
|
+
|
|
42
|
+
- uses: actions/upload-pages-artifact@v3
|
|
43
|
+
with:
|
|
44
|
+
path: ./site
|
|
45
|
+
|
|
46
|
+
deploy:
|
|
47
|
+
needs: build
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
environment:
|
|
50
|
+
name: github-pages
|
|
51
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/deploy-pages@v4
|
|
54
|
+
id: deployment
|