world-building-framework 0.0.0-stage → 0.2.0

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.
Files changed (145) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +104 -2
  3. package/dist/cli/check.d.ts +13 -0
  4. package/dist/cli/check.js +11 -0
  5. package/dist/cli/commands/create.d.ts +26 -0
  6. package/dist/cli/commands/create.js +35 -0
  7. package/dist/cli/commands/init.d.ts +26 -0
  8. package/dist/cli/commands/init.js +119 -0
  9. package/dist/cli/commands/list-variants.d.ts +1 -0
  10. package/dist/cli/commands/list-variants.js +7 -0
  11. package/dist/cli/commands/publish.d.ts +24 -0
  12. package/dist/cli/commands/publish.js +71 -0
  13. package/dist/cli/commands/refresh.d.ts +29 -0
  14. package/dist/cli/commands/refresh.js +70 -0
  15. package/dist/cli/commands/render.d.ts +14 -0
  16. package/dist/cli/commands/render.js +27 -0
  17. package/dist/cli/commands/schema.d.ts +2 -0
  18. package/dist/cli/commands/schema.js +9 -0
  19. package/dist/cli/commands/search.d.ts +9 -0
  20. package/dist/cli/commands/search.js +20 -0
  21. package/dist/cli/commands/update.d.ts +23 -0
  22. package/dist/cli/commands/update.js +69 -0
  23. package/dist/cli/commands/validate.d.ts +21 -0
  24. package/dist/cli/commands/validate.js +44 -0
  25. package/dist/cli/index.d.ts +2 -0
  26. package/dist/cli/index.js +157 -0
  27. package/dist/cli/io.d.ts +4 -0
  28. package/dist/cli/io.js +25 -0
  29. package/dist/cli/managed-files.d.ts +35 -0
  30. package/dist/cli/managed-files.js +80 -0
  31. package/dist/cli/quartz-runner.d.ts +22 -0
  32. package/dist/cli/quartz-runner.js +76 -0
  33. package/dist/cli/quartz-setup.d.ts +10 -0
  34. package/dist/cli/quartz-setup.js +59 -0
  35. package/dist/cli/vault-path.d.ts +7 -0
  36. package/dist/cli/vault-path.js +44 -0
  37. package/dist/core/config/build-variants.d.ts +5 -0
  38. package/dist/core/config/build-variants.js +13 -0
  39. package/dist/core/config/index.d.ts +4 -0
  40. package/dist/core/config/index.js +4 -0
  41. package/dist/core/config/load.d.ts +4 -0
  42. package/dist/core/config/load.js +25 -0
  43. package/dist/core/config/schema.d.ts +229 -0
  44. package/dist/core/config/schema.js +23 -0
  45. package/dist/core/config/validate-options.d.ts +4 -0
  46. package/dist/core/config/validate-options.js +4 -0
  47. package/dist/core/graph/build.d.ts +8 -0
  48. package/dist/core/graph/build.js +42 -0
  49. package/dist/core/graph/causal.d.ts +22 -0
  50. package/dist/core/graph/causal.js +106 -0
  51. package/dist/core/graph/dating.d.ts +18 -0
  52. package/dist/core/graph/dating.js +30 -0
  53. package/dist/core/graph/entities.d.ts +13 -0
  54. package/dist/core/graph/entities.js +22 -0
  55. package/dist/core/graph/index.d.ts +7 -0
  56. package/dist/core/graph/index.js +7 -0
  57. package/dist/core/graph/queries.d.ts +3 -0
  58. package/dist/core/graph/queries.js +8 -0
  59. package/dist/core/graph/types.d.ts +14 -0
  60. package/dist/core/graph/types.js +1 -0
  61. package/dist/core/graph/versioning.d.ts +15 -0
  62. package/dist/core/graph/versioning.js +50 -0
  63. package/dist/core/id.d.ts +3 -0
  64. package/dist/core/id.js +9 -0
  65. package/dist/core/lint/after-loops.d.ts +15 -0
  66. package/dist/core/lint/after-loops.js +34 -0
  67. package/dist/core/lint/dangling-links.d.ts +9 -0
  68. package/dist/core/lint/dangling-links.js +46 -0
  69. package/dist/core/lint/events-without-entities.d.ts +4 -0
  70. package/dist/core/lint/events-without-entities.js +9 -0
  71. package/dist/core/lint/forked-versions.d.ts +14 -0
  72. package/dist/core/lint/forked-versions.js +29 -0
  73. package/dist/core/lint/index.d.ts +6 -0
  74. package/dist/core/lint/index.js +6 -0
  75. package/dist/core/lint/lint.d.ts +24 -0
  76. package/dist/core/lint/lint.js +50 -0
  77. package/dist/core/lint/target-types.d.ts +9 -0
  78. package/dist/core/lint/target-types.js +47 -0
  79. package/dist/core/query/dates.d.ts +70 -0
  80. package/dist/core/query/dates.js +43 -0
  81. package/dist/core/query/index.d.ts +2 -0
  82. package/dist/core/query/index.js +2 -0
  83. package/dist/core/query/types.d.ts +75 -0
  84. package/dist/core/query/types.js +12 -0
  85. package/dist/core/schema/describe.d.ts +8 -0
  86. package/dist/core/schema/describe.js +51 -0
  87. package/dist/core/schema/fields.d.ts +249 -0
  88. package/dist/core/schema/fields.js +43 -0
  89. package/dist/core/schema/index.d.ts +4 -0
  90. package/dist/core/schema/index.js +4 -0
  91. package/dist/core/schema/validate.d.ts +22 -0
  92. package/dist/core/schema/validate.js +34 -0
  93. package/dist/core/schema/wikilink.d.ts +8 -0
  94. package/dist/core/schema/wikilink.js +23 -0
  95. package/dist/core/search/build.d.ts +11 -0
  96. package/dist/core/search/build.js +26 -0
  97. package/dist/core/search/columns.d.ts +9 -0
  98. package/dist/core/search/columns.js +14 -0
  99. package/dist/core/search/index.d.ts +3 -0
  100. package/dist/core/search/index.js +3 -0
  101. package/dist/core/search/search.d.ts +3 -0
  102. package/dist/core/search/search.js +35 -0
  103. package/dist/core/search/types.d.ts +18 -0
  104. package/dist/core/search/types.js +1 -0
  105. package/dist/core/vault/index.d.ts +6 -0
  106. package/dist/core/vault/index.js +6 -0
  107. package/dist/core/vault/parse.d.ts +8 -0
  108. package/dist/core/vault/parse.js +11 -0
  109. package/dist/core/vault/read.d.ts +13 -0
  110. package/dist/core/vault/read.js +62 -0
  111. package/dist/core/vault/relink.d.ts +10 -0
  112. package/dist/core/vault/relink.js +33 -0
  113. package/dist/core/vault/serialize.d.ts +4 -0
  114. package/dist/core/vault/serialize.js +4 -0
  115. package/dist/core/vault/types.d.ts +5 -0
  116. package/dist/core/vault/types.js +1 -0
  117. package/dist/core/vault/write.d.ts +18 -0
  118. package/dist/core/vault/write.js +37 -0
  119. package/dist/index.d.ts +10 -0
  120. package/dist/index.js +11 -0
  121. package/dist/render-pipeline/build.d.ts +9 -0
  122. package/dist/render-pipeline/build.js +45 -0
  123. package/dist/render-pipeline/filter.d.ts +21 -0
  124. package/dist/render-pipeline/filter.js +80 -0
  125. package/dist/render-pipeline/index.d.ts +10 -0
  126. package/dist/render-pipeline/index.js +10 -0
  127. package/dist/render-pipeline/merge.d.ts +11 -0
  128. package/dist/render-pipeline/merge.js +21 -0
  129. package/dist/render-pipeline/quartz-compat.d.ts +1 -0
  130. package/dist/render-pipeline/quartz-compat.js +18 -0
  131. package/dist/render-pipeline/rewrite.d.ts +8 -0
  132. package/dist/render-pipeline/rewrite.js +23 -0
  133. package/dist/render-pipeline/slugs.d.ts +7 -0
  134. package/dist/render-pipeline/slugs.js +30 -0
  135. package/dist/render-pipeline/sync.d.ts +9 -0
  136. package/dist/render-pipeline/sync.js +18 -0
  137. package/dist/render-pipeline/types.d.ts +8 -0
  138. package/dist/render-pipeline/types.js +1 -0
  139. package/dist/render-pipeline/write.d.ts +3 -0
  140. package/dist/render-pipeline/write.js +12 -0
  141. package/package.json +46 -4
  142. package/skills/world-vault/SKILL.md +100 -0
  143. package/templates/AGENTS.md +11 -0
  144. package/templates/gitignore +5 -0
  145. package/templates/publish.yml +54 -0
@@ -0,0 +1,44 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { WORLD_CONFIG_FILENAME } from "../core/config/index.js";
4
+ function findSingleVaultUnderVaultsDir() {
5
+ const vaultsRoot = path.join(process.cwd(), "vaults");
6
+ let entries;
7
+ try {
8
+ entries = readdirSync(vaultsRoot).filter((name) => {
9
+ try {
10
+ return statSync(path.join(vaultsRoot, name)).isDirectory();
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ });
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ return entries.length === 1 ? path.join(vaultsRoot, entries[0]) : undefined;
21
+ }
22
+ /** The current folder, if it's a vault — i.e. it has a world.config.json (the vault repo is the project, see claude-dev/DISTRIBUTION_PLAN.md). */
23
+ function currentFolderIfVault() {
24
+ const cwd = process.cwd();
25
+ return existsSync(path.join(cwd, WORLD_CONFIG_FILENAME)) ? cwd : undefined;
26
+ }
27
+ /**
28
+ * Like resolveVaultDir, but returns undefined instead of throwing — for commands
29
+ * that are useful without a vault (e.g. `schema`).
30
+ */
31
+ export function findVaultDir(cliOption) {
32
+ // The ./vaults/ fallback is the old fork-the-framework layout, kept until the
33
+ // world-repo layout fully replaces it.
34
+ return cliOption ?? process.env.WBF_VAULT_DIR ?? currentFolderIfVault() ?? findSingleVaultUnderVaultsDir();
35
+ }
36
+ /** Resolves which vault to operate on: explicit flag, then WBF_VAULT_DIR, then the current folder if it has a world.config.json, then a single directory under ./vaults/. */
37
+ export function resolveVaultDir(cliOption) {
38
+ const vaultDir = findVaultDir(cliOption);
39
+ if (!vaultDir) {
40
+ throw new Error("No vault specified — run this inside a vault (a folder with world.config.json), pass --vault <path>, " +
41
+ "set WBF_VAULT_DIR, or have exactly one directory under ./vaults/.");
42
+ }
43
+ return vaultDir;
44
+ }
@@ -0,0 +1,5 @@
1
+ import type { BuildVariantQuery } from "../query/index.js";
2
+ import type { WorldConfig } from "./schema.js";
3
+ export declare function listBuildVariants(config: WorldConfig): string[];
4
+ /** Looks up a named build variant's query. Throws if the name isn't declared — a typo'd variant name should fail loudly, not silently render everything. */
5
+ export declare function getBuildVariant(config: WorldConfig, name: string): BuildVariantQuery;
@@ -0,0 +1,13 @@
1
+ export function listBuildVariants(config) {
2
+ return Object.keys(config.buildVariants ?? {});
3
+ }
4
+ /** Looks up a named build variant's query. Throws if the name isn't declared — a typo'd variant name should fail loudly, not silently render everything. */
5
+ export function getBuildVariant(config, name) {
6
+ const variant = config.buildVariants?.[name];
7
+ if (!variant) {
8
+ const known = listBuildVariants(config);
9
+ const knownText = known.length > 0 ? known.join(", ") : "(none declared)";
10
+ throw new Error(`No build variant named "${name}" in this vault's config. Declared variants: ${knownText}`);
11
+ }
12
+ return variant;
13
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./schema.js";
2
+ export * from "./load.js";
3
+ export * from "./validate-options.js";
4
+ export * from "./build-variants.js";
@@ -0,0 +1,4 @@
1
+ export * from "./schema.js";
2
+ export * from "./load.js";
3
+ export * from "./validate-options.js";
4
+ export * from "./build-variants.js";
@@ -0,0 +1,4 @@
1
+ import { type WorldConfig } from "./schema.js";
2
+ export declare const WORLD_CONFIG_FILENAME = "world.config.json";
3
+ /** Loads a vault's world.config.json. A missing file means no project-declared config yet — not an error. */
4
+ export declare function loadWorldConfig(vaultDir: string): Promise<WorldConfig>;
@@ -0,0 +1,25 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { worldConfigSchema } from "./schema.js";
4
+ export const WORLD_CONFIG_FILENAME = "world.config.json";
5
+ /** Loads a vault's world.config.json. A missing file means no project-declared config yet — not an error. */
6
+ export async function loadWorldConfig(vaultDir) {
7
+ const filePath = path.join(vaultDir, WORLD_CONFIG_FILENAME);
8
+ let raw;
9
+ try {
10
+ raw = await readFile(filePath, "utf8");
11
+ }
12
+ catch (err) {
13
+ if (err.code === "ENOENT")
14
+ return {};
15
+ throw err;
16
+ }
17
+ let parsed;
18
+ try {
19
+ parsed = JSON.parse(raw);
20
+ }
21
+ catch (err) {
22
+ throw new Error(`${filePath} is not valid JSON: ${err.message}`);
23
+ }
24
+ return worldConfigSchema.parse(parsed);
25
+ }
@@ -0,0 +1,229 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * A vault's own project-specific setup parameters (PROJECT_CONFIG.md).
4
+ * `title`, `entityTypes` and `buildVariants` are the fields with a decided consumer
5
+ * so far — tag vocabulary itself (as opposed to where it's used, buildVariants)
6
+ * and relation-field naming are still open, so this stays passthrough rather
7
+ * than locking in a shape for those prematurely.
8
+ */
9
+ export declare const worldConfigSchema: z.ZodObject<{
10
+ /** The world's name, shown as the published site's title (every variant and the landing page). */
11
+ title: z.ZodOptional<z.ZodString>;
12
+ entityTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
13
+ /** Named filter queries (render-pipeline's BuildVariantQuery) — PROJECT_CONFIG.md #3. */
14
+ buildVariants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
15
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
16
+ dateRange: z.ZodOptional<z.ZodObject<{
17
+ from: z.ZodOptional<z.ZodObject<{
18
+ year: z.ZodNumber;
19
+ day: z.ZodOptional<z.ZodNumber>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ year: number;
22
+ day?: number | undefined;
23
+ }, {
24
+ year: number;
25
+ day?: number | undefined;
26
+ }>>;
27
+ to: z.ZodOptional<z.ZodObject<{
28
+ year: z.ZodNumber;
29
+ day: z.ZodOptional<z.ZodNumber>;
30
+ }, "strip", z.ZodTypeAny, {
31
+ year: number;
32
+ day?: number | undefined;
33
+ }, {
34
+ year: number;
35
+ day?: number | undefined;
36
+ }>>;
37
+ }, "strip", z.ZodTypeAny, {
38
+ from?: {
39
+ year: number;
40
+ day?: number | undefined;
41
+ } | undefined;
42
+ to?: {
43
+ year: number;
44
+ day?: number | undefined;
45
+ } | undefined;
46
+ }, {
47
+ from?: {
48
+ year: number;
49
+ day?: number | undefined;
50
+ } | undefined;
51
+ to?: {
52
+ year: number;
53
+ day?: number | undefined;
54
+ } | undefined;
55
+ }>>;
56
+ }, "strip", z.ZodTypeAny, {
57
+ tags?: string[] | undefined;
58
+ dateRange?: {
59
+ from?: {
60
+ year: number;
61
+ day?: number | undefined;
62
+ } | undefined;
63
+ to?: {
64
+ year: number;
65
+ day?: number | undefined;
66
+ } | undefined;
67
+ } | undefined;
68
+ }, {
69
+ tags?: string[] | undefined;
70
+ dateRange?: {
71
+ from?: {
72
+ year: number;
73
+ day?: number | undefined;
74
+ } | undefined;
75
+ to?: {
76
+ year: number;
77
+ day?: number | undefined;
78
+ } | undefined;
79
+ } | undefined;
80
+ }>>>;
81
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
82
+ /** The world's name, shown as the published site's title (every variant and the landing page). */
83
+ title: z.ZodOptional<z.ZodString>;
84
+ entityTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
85
+ /** Named filter queries (render-pipeline's BuildVariantQuery) — PROJECT_CONFIG.md #3. */
86
+ buildVariants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
87
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
88
+ dateRange: z.ZodOptional<z.ZodObject<{
89
+ from: z.ZodOptional<z.ZodObject<{
90
+ year: z.ZodNumber;
91
+ day: z.ZodOptional<z.ZodNumber>;
92
+ }, "strip", z.ZodTypeAny, {
93
+ year: number;
94
+ day?: number | undefined;
95
+ }, {
96
+ year: number;
97
+ day?: number | undefined;
98
+ }>>;
99
+ to: z.ZodOptional<z.ZodObject<{
100
+ year: z.ZodNumber;
101
+ day: z.ZodOptional<z.ZodNumber>;
102
+ }, "strip", z.ZodTypeAny, {
103
+ year: number;
104
+ day?: number | undefined;
105
+ }, {
106
+ year: number;
107
+ day?: number | undefined;
108
+ }>>;
109
+ }, "strip", z.ZodTypeAny, {
110
+ from?: {
111
+ year: number;
112
+ day?: number | undefined;
113
+ } | undefined;
114
+ to?: {
115
+ year: number;
116
+ day?: number | undefined;
117
+ } | undefined;
118
+ }, {
119
+ from?: {
120
+ year: number;
121
+ day?: number | undefined;
122
+ } | undefined;
123
+ to?: {
124
+ year: number;
125
+ day?: number | undefined;
126
+ } | undefined;
127
+ }>>;
128
+ }, "strip", z.ZodTypeAny, {
129
+ tags?: string[] | undefined;
130
+ dateRange?: {
131
+ from?: {
132
+ year: number;
133
+ day?: number | undefined;
134
+ } | undefined;
135
+ to?: {
136
+ year: number;
137
+ day?: number | undefined;
138
+ } | undefined;
139
+ } | undefined;
140
+ }, {
141
+ tags?: string[] | undefined;
142
+ dateRange?: {
143
+ from?: {
144
+ year: number;
145
+ day?: number | undefined;
146
+ } | undefined;
147
+ to?: {
148
+ year: number;
149
+ day?: number | undefined;
150
+ } | undefined;
151
+ } | undefined;
152
+ }>>>;
153
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
154
+ /** The world's name, shown as the published site's title (every variant and the landing page). */
155
+ title: z.ZodOptional<z.ZodString>;
156
+ entityTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
157
+ /** Named filter queries (render-pipeline's BuildVariantQuery) — PROJECT_CONFIG.md #3. */
158
+ buildVariants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
159
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
160
+ dateRange: z.ZodOptional<z.ZodObject<{
161
+ from: z.ZodOptional<z.ZodObject<{
162
+ year: z.ZodNumber;
163
+ day: z.ZodOptional<z.ZodNumber>;
164
+ }, "strip", z.ZodTypeAny, {
165
+ year: number;
166
+ day?: number | undefined;
167
+ }, {
168
+ year: number;
169
+ day?: number | undefined;
170
+ }>>;
171
+ to: z.ZodOptional<z.ZodObject<{
172
+ year: z.ZodNumber;
173
+ day: z.ZodOptional<z.ZodNumber>;
174
+ }, "strip", z.ZodTypeAny, {
175
+ year: number;
176
+ day?: number | undefined;
177
+ }, {
178
+ year: number;
179
+ day?: number | undefined;
180
+ }>>;
181
+ }, "strip", z.ZodTypeAny, {
182
+ from?: {
183
+ year: number;
184
+ day?: number | undefined;
185
+ } | undefined;
186
+ to?: {
187
+ year: number;
188
+ day?: number | undefined;
189
+ } | undefined;
190
+ }, {
191
+ from?: {
192
+ year: number;
193
+ day?: number | undefined;
194
+ } | undefined;
195
+ to?: {
196
+ year: number;
197
+ day?: number | undefined;
198
+ } | undefined;
199
+ }>>;
200
+ }, "strip", z.ZodTypeAny, {
201
+ tags?: string[] | undefined;
202
+ dateRange?: {
203
+ from?: {
204
+ year: number;
205
+ day?: number | undefined;
206
+ } | undefined;
207
+ to?: {
208
+ year: number;
209
+ day?: number | undefined;
210
+ } | undefined;
211
+ } | undefined;
212
+ }, {
213
+ tags?: string[] | undefined;
214
+ dateRange?: {
215
+ from?: {
216
+ year: number;
217
+ day?: number | undefined;
218
+ } | undefined;
219
+ to?: {
220
+ year: number;
221
+ day?: number | undefined;
222
+ } | undefined;
223
+ } | undefined;
224
+ }>>>;
225
+ }, z.ZodTypeAny, "passthrough">>;
226
+ export type WorldConfig = z.infer<typeof worldConfigSchema>;
227
+ export declare const DEFAULT_WORLD_TITLE = "World Wiki";
228
+ /** The world's display title, falling back to a neutral default (never Quartz's own "Quartz 5"). */
229
+ export declare function worldTitle(config: WorldConfig): string;
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import { buildVariantQuerySchema } from "../query/index.js";
3
+ /**
4
+ * A vault's own project-specific setup parameters (PROJECT_CONFIG.md).
5
+ * `title`, `entityTypes` and `buildVariants` are the fields with a decided consumer
6
+ * so far — tag vocabulary itself (as opposed to where it's used, buildVariants)
7
+ * and relation-field naming are still open, so this stays passthrough rather
8
+ * than locking in a shape for those prematurely.
9
+ */
10
+ export const worldConfigSchema = z
11
+ .object({
12
+ /** The world's name, shown as the published site's title (every variant and the landing page). */
13
+ title: z.string().min(1).optional(),
14
+ entityTypes: z.array(z.string()).optional(),
15
+ /** Named filter queries (render-pipeline's BuildVariantQuery) — PROJECT_CONFIG.md #3. */
16
+ buildVariants: z.record(z.string(), buildVariantQuerySchema).optional(),
17
+ })
18
+ .passthrough();
19
+ export const DEFAULT_WORLD_TITLE = "World Wiki";
20
+ /** The world's display title, falling back to a neutral default (never Quartz's own "Quartz 5"). */
21
+ export function worldTitle(config) {
22
+ return config.title ?? DEFAULT_WORLD_TITLE;
23
+ }
@@ -0,0 +1,4 @@
1
+ import type { ValidateOptions } from "../schema/validate.js";
2
+ import type { WorldConfig } from "./schema.js";
3
+ /** Bridges a loaded world.config.json into the shape core/schema's validateFrontmatter expects. */
4
+ export declare function validateOptionsFromConfig(config: WorldConfig): ValidateOptions;
@@ -0,0 +1,4 @@
1
+ /** Bridges a loaded world.config.json into the shape core/schema's validateFrontmatter expects. */
2
+ export function validateOptionsFromConfig(config) {
3
+ return { entityTypeVocabulary: config.entityTypes };
4
+ }
@@ -0,0 +1,8 @@
1
+ import type { VaultFile } from "../vault/types.js";
2
+ import type { KnowledgeGraph } from "./types.js";
3
+ /**
4
+ * Builds the relation graph from a set of vault files: nodes are files (by id),
5
+ * edges come from every wikilink-valued frontmatter field, whatever it's named
6
+ * (SCHEMA.md rule 2 — no fixed relation vocabulary).
7
+ */
8
+ export declare function buildGraph(files: VaultFile[]): KnowledgeGraph;
@@ -0,0 +1,42 @@
1
+ import { isWikilink, parseWikilink } from "../schema/wikilink.js";
2
+ function extractWikilinks(value) {
3
+ if (typeof value === "string" && isWikilink(value)) {
4
+ return [parseWikilink(value)];
5
+ }
6
+ if (Array.isArray(value)) {
7
+ return value.flatMap(extractWikilinks);
8
+ }
9
+ return [];
10
+ }
11
+ function push(map, key, edge) {
12
+ const list = map.get(key);
13
+ if (list) {
14
+ list.push(edge);
15
+ }
16
+ else {
17
+ map.set(key, [edge]);
18
+ }
19
+ }
20
+ /**
21
+ * Builds the relation graph from a set of vault files: nodes are files (by id),
22
+ * edges come from every wikilink-valued frontmatter field, whatever it's named
23
+ * (SCHEMA.md rule 2 — no fixed relation vocabulary).
24
+ */
25
+ export function buildGraph(files) {
26
+ const nodes = new Map();
27
+ const edgesFrom = new Map();
28
+ const edgesTo = new Map();
29
+ for (const file of files) {
30
+ nodes.set(file.id, file);
31
+ }
32
+ for (const file of files) {
33
+ for (const [field, value] of Object.entries(file.frontmatter)) {
34
+ for (const link of extractWikilinks(value)) {
35
+ const edge = { field, from: file.id, to: link.targetId, display: link.display };
36
+ push(edgesFrom, file.id, edge);
37
+ push(edgesTo, link.targetId, edge);
38
+ }
39
+ }
40
+ }
41
+ return { nodes, edgesFrom, edgesTo };
42
+ }
@@ -0,0 +1,22 @@
1
+ import type { KnowledgeGraph } from "./types.js";
2
+ /** This event's own `after` targets — its causal predecessors. */
3
+ export declare function predecessors(graph: KnowledgeGraph, eventId: string): string[];
4
+ /** Events whose `after` points at this one — its causal successors. More than one means a branch. */
5
+ export declare function successors(graph: KnowledgeGraph, eventId: string): string[];
6
+ /** Events that fork into more than one successor (SCHEMA.md: a branch is implicit in the `after` graph). */
7
+ export declare function branchPoints(graph: KnowledgeGraph): string[];
8
+ /**
9
+ * Finds a loop in `after` that runs through `startId`, following `afterOf`
10
+ * (an event id -> its `after` target ids). Returns the loop as ids, starting
11
+ * and ending at `startId` — each id is `after` the next one — or undefined.
12
+ * Takes a lookup rather than a graph so lint can check a not-yet-written
13
+ * change against the rest of the vault.
14
+ */
15
+ export declare function findAfterLoop(startId: string, afterOf: (id: string) => string[]): string[] | undefined;
16
+ /**
17
+ * Topologically orders every event by `after` (predecessors before successors), earliest first.
18
+ * Where branches exist, events on different branches aren't causally comparable — this still
19
+ * returns one valid, consistent ordering among them, which is good enough to break ties
20
+ * (see graph/entities.ts) without needing full partial-order comparison logic yet.
21
+ */
22
+ export declare function topologicalOrder(graph: KnowledgeGraph): string[];
@@ -0,0 +1,106 @@
1
+ import { outgoing, incoming } from "./queries.js";
2
+ function isEvent(file) {
3
+ return file.frontmatter.type === "event";
4
+ }
5
+ /** This event's own `after` targets — its causal predecessors. */
6
+ export function predecessors(graph, eventId) {
7
+ return outgoing(graph, eventId, "after").map((edge) => edge.to);
8
+ }
9
+ /** Events whose `after` points at this one — its causal successors. More than one means a branch. */
10
+ export function successors(graph, eventId) {
11
+ return incoming(graph, eventId, "after").map((edge) => edge.from);
12
+ }
13
+ /** Events that fork into more than one successor (SCHEMA.md: a branch is implicit in the `after` graph). */
14
+ export function branchPoints(graph) {
15
+ const points = [];
16
+ for (const [id, file] of graph.nodes) {
17
+ if (isEvent(file) && successors(graph, id).length > 1) {
18
+ points.push(id);
19
+ }
20
+ }
21
+ return points;
22
+ }
23
+ /**
24
+ * Finds a loop in `after` that runs through `startId`, following `afterOf`
25
+ * (an event id -> its `after` target ids). Returns the loop as ids, starting
26
+ * and ending at `startId` — each id is `after` the next one — or undefined.
27
+ * Takes a lookup rather than a graph so lint can check a not-yet-written
28
+ * change against the rest of the vault.
29
+ */
30
+ export function findAfterLoop(startId, afterOf) {
31
+ const reachedFrom = new Map();
32
+ const visited = new Set([startId]);
33
+ const stack = [startId];
34
+ while (stack.length > 0) {
35
+ const id = stack.pop();
36
+ for (const next of afterOf(id)) {
37
+ if (next === startId) {
38
+ const loop = [id];
39
+ while (loop[0] !== startId)
40
+ loop.unshift(reachedFrom.get(loop[0]));
41
+ return [...loop, startId];
42
+ }
43
+ if (!visited.has(next)) {
44
+ visited.add(next);
45
+ reachedFrom.set(next, id);
46
+ stack.push(next);
47
+ }
48
+ }
49
+ }
50
+ return undefined;
51
+ }
52
+ /**
53
+ * Topologically orders every event by `after` (predecessors before successors), earliest first.
54
+ * Where branches exist, events on different branches aren't causally comparable — this still
55
+ * returns one valid, consistent ordering among them, which is good enough to break ties
56
+ * (see graph/entities.ts) without needing full partial-order comparison logic yet.
57
+ */
58
+ export function topologicalOrder(graph) {
59
+ const eventIds = [...graph.nodes.entries()].filter(([, file]) => isEvent(file)).map(([id]) => id);
60
+ const remainingPredecessors = new Map();
61
+ for (const id of eventIds) {
62
+ remainingPredecessors.set(id, new Set(predecessors(graph, id)));
63
+ }
64
+ const order = [];
65
+ const ready = eventIds.filter((id) => remainingPredecessors.get(id).size === 0);
66
+ while (ready.length > 0) {
67
+ const id = ready.shift();
68
+ order.push(id);
69
+ for (const childId of successors(graph, id)) {
70
+ const preds = remainingPredecessors.get(childId);
71
+ if (!preds)
72
+ continue;
73
+ preds.delete(id);
74
+ if (preds.size === 0)
75
+ ready.push(childId);
76
+ }
77
+ }
78
+ if (order.length !== eventIds.length) {
79
+ const ordered = new Set(order);
80
+ throw new Error(describeUnorderable(graph, eventIds.filter((id) => !ordered.has(id))));
81
+ }
82
+ return order;
83
+ }
84
+ /**
85
+ * Why some events couldn't be ordered, naming them — either a real loop, or an
86
+ * `after` that points at something which isn't an event in the vault (which
87
+ * also never becomes "ready"). Lint catches both before writing; this is for
88
+ * whatever got in anyway, e.g. a hand edit.
89
+ */
90
+ function describeUnorderable(graph, stuckIds) {
91
+ const describe = (id) => {
92
+ const title = graph.nodes.get(id)?.frontmatter.title;
93
+ return `"${typeof title === "string" ? title : "untitled"}" (${id})`;
94
+ };
95
+ for (const id of stuckIds) {
96
+ const loop = findAfterLoop(id, (eventId) => predecessors(graph, eventId));
97
+ if (loop) {
98
+ return `Can't order events — loop in \`after\`, each event is after the next: ${loop.map(describe).join(" → ")}`;
99
+ }
100
+ }
101
+ const isEvent = (id) => graph.nodes.get(id)?.frontmatter.type === "event";
102
+ const badLinks = stuckIds.flatMap((id) => predecessors(graph, id)
103
+ .filter((target) => !isEvent(target))
104
+ .map((target) => `${describe(id)} is after ${target}, which isn't an event in the vault`));
105
+ return `Can't order events — ${badLinks.length > 0 ? badLinks.join("; ") : `unresolved: ${stuckIds.map(describe).join(", ")}`}`;
106
+ }
@@ -0,0 +1,18 @@
1
+ import type { VaultFile } from "../vault/types.js";
2
+ import { type DateTuple } from "../query/dates.js";
3
+ import type { KnowledgeGraph } from "./types.js";
4
+ /** An event's own `date`, if it has a well-formed one. */
5
+ export declare function eventDate(file: VaultFile | undefined): DateTuple | undefined;
6
+ /**
7
+ * The span an undated event must fall in, from its causal neighbours
8
+ * (REQUIREMENTS.md: "sometime after the Founding, before the War"):
9
+ * - `earliest`: the latest date among the dated events it's (transitively) `after`.
10
+ * - `latest`: the earliest date among the dated events (transitively) `after` it.
11
+ * Either side is undefined when no dated event bounds it on that side — the
12
+ * event could then be arbitrarily early or late.
13
+ */
14
+ export interface DateWindow {
15
+ earliest?: DateTuple;
16
+ latest?: DateTuple;
17
+ }
18
+ export declare function undatedEventWindow(graph: KnowledgeGraph, eventId: string): DateWindow;
@@ -0,0 +1,30 @@
1
+ import { compareDates } from "../query/dates.js";
2
+ import { predecessors, successors } from "./causal.js";
3
+ /** An event's own `date`, if it has a well-formed one. */
4
+ export function eventDate(file) {
5
+ const date = file?.frontmatter.date;
6
+ return typeof date?.year === "number" && typeof date.day === "number" ? { year: date.year, day: date.day } : undefined;
7
+ }
8
+ function extremeDate(graph, startId, next, pickLater) {
9
+ let best;
10
+ const visited = new Set([startId]);
11
+ const queue = next(graph, startId);
12
+ while (queue.length > 0) {
13
+ const id = queue.shift();
14
+ if (visited.has(id))
15
+ continue;
16
+ visited.add(id);
17
+ const date = eventDate(graph.nodes.get(id));
18
+ if (date && (!best || (pickLater ? compareDates(date, best) > 0 : compareDates(date, best) < 0))) {
19
+ best = date;
20
+ }
21
+ queue.push(...next(graph, id));
22
+ }
23
+ return best;
24
+ }
25
+ export function undatedEventWindow(graph, eventId) {
26
+ return {
27
+ earliest: extremeDate(graph, eventId, predecessors, true),
28
+ latest: extremeDate(graph, eventId, successors, false),
29
+ };
30
+ }
@@ -0,0 +1,13 @@
1
+ import type { KnowledgeGraph } from "./types.js";
2
+ /**
3
+ * Events tied to this entity. Always queried against the anchor id — other files
4
+ * always link to an entity's anchor, never a later version (SCHEMA.md), so an
5
+ * event's `entities` field can never name anything but the anchor.
6
+ */
7
+ export declare function entityTiedEvents(graph: KnowledgeGraph, anchorId: string): string[];
8
+ /**
9
+ * The chronologically-earliest event tied to this entity, by causal order — this is
10
+ * the entity's "effective date" for date-range build filtering (REQUIREMENTS.md).
11
+ * Returns undefined if the entity isn't tied to any event yet.
12
+ */
13
+ export declare function entityFirstEventId(graph: KnowledgeGraph, anchorId: string): string | undefined;
@@ -0,0 +1,22 @@
1
+ import { incoming } from "./queries.js";
2
+ import { topologicalOrder } from "./causal.js";
3
+ /**
4
+ * Events tied to this entity. Always queried against the anchor id — other files
5
+ * always link to an entity's anchor, never a later version (SCHEMA.md), so an
6
+ * event's `entities` field can never name anything but the anchor.
7
+ */
8
+ export function entityTiedEvents(graph, anchorId) {
9
+ return incoming(graph, anchorId, "entities").map((edge) => edge.from);
10
+ }
11
+ /**
12
+ * The chronologically-earliest event tied to this entity, by causal order — this is
13
+ * the entity's "effective date" for date-range build filtering (REQUIREMENTS.md).
14
+ * Returns undefined if the entity isn't tied to any event yet.
15
+ */
16
+ export function entityFirstEventId(graph, anchorId) {
17
+ const tied = new Set(entityTiedEvents(graph, anchorId));
18
+ if (tied.size === 0)
19
+ return undefined;
20
+ const order = topologicalOrder(graph);
21
+ return order.find((id) => tied.has(id));
22
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./types.js";
2
+ export * from "./build.js";
3
+ export * from "./queries.js";
4
+ export * from "./versioning.js";
5
+ export * from "./causal.js";
6
+ export * from "./entities.js";
7
+ export * from "./dating.js";
@@ -0,0 +1,7 @@
1
+ export * from "./types.js";
2
+ export * from "./build.js";
3
+ export * from "./queries.js";
4
+ export * from "./versioning.js";
5
+ export * from "./causal.js";
6
+ export * from "./entities.js";
7
+ export * from "./dating.js";
@@ -0,0 +1,3 @@
1
+ import type { GraphEdge, KnowledgeGraph } from "./types.js";
2
+ export declare function outgoing(graph: KnowledgeGraph, id: string, field?: string): GraphEdge[];
3
+ export declare function incoming(graph: KnowledgeGraph, id: string, field?: string): GraphEdge[];