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.
- 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 +26 -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 +229 -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 +70 -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 +75 -0
- package/dist/core/query/types.js +12 -0
- package/dist/core/schema/describe.d.ts +8 -0
- package/dist/core/schema/describe.js +51 -0
- package/dist/core/schema/fields.d.ts +249 -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 +9 -0
- package/dist/render-pipeline/build.js +45 -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 +10 -0
- package/dist/render-pipeline/index.js +10 -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 +7 -0
- package/dist/render-pipeline/slugs.js +30 -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,51 @@
|
|
|
1
|
+
import { commonFieldsSchema, entityFrontmatterSchema, eventFrontmatterSchema } from "./fields.js";
|
|
2
|
+
const FIELD_MEANINGS = {
|
|
3
|
+
type: 'Top-level kind: "entity" or "event".',
|
|
4
|
+
title: "Human-readable display name (required — filenames are UUIDs, this is the real name).",
|
|
5
|
+
tags: "Project-defined tags used for build-time filtering.",
|
|
6
|
+
aliases: "Alternate names/spellings, for search recall and Obsidian autocomplete only — not used for link resolution.",
|
|
7
|
+
entityType: "Category (e.g. character, location). Lint-checked against this vault's declared vocabulary, if any.",
|
|
8
|
+
supersedes: "Marks this file as a new version of an existing entity — points at the file it replaces.",
|
|
9
|
+
causedBy: "The event responsible for this version existing. Required alongside supersedes, and vice versa.",
|
|
10
|
+
after: "Causal predecessor event(s) — the required ordering/branching backbone.",
|
|
11
|
+
date: "Optional in-world date anchor: { year, day }.",
|
|
12
|
+
entities: "The entities this event is tied to.",
|
|
13
|
+
};
|
|
14
|
+
function shapeKeys(schema) {
|
|
15
|
+
return Object.keys(schema.shape);
|
|
16
|
+
}
|
|
17
|
+
function describeFields(names) {
|
|
18
|
+
return names.map((name) => ` - ${name}: ${FIELD_MEANINGS[name] ?? "(see SCHEMA.md)"}`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Prints the live, current schema: field names come straight from the zod
|
|
22
|
+
* schemas in fields.ts, so this can't structurally drift from what
|
|
23
|
+
* validateFrontmatter actually enforces. This is what the CLI's `schema`
|
|
24
|
+
* command exposes so Claude always sees the real, current rules — see
|
|
25
|
+
* claude-dev/PROJECT_STRUCTURE.md.
|
|
26
|
+
*/
|
|
27
|
+
export function describeSchema(entityTypeVocabulary) {
|
|
28
|
+
const commonKeys = shapeKeys(commonFieldsSchema);
|
|
29
|
+
const excluded = new Set([...commonKeys, "type"]);
|
|
30
|
+
const entityKeys = shapeKeys(entityFrontmatterSchema).filter((key) => !excluded.has(key));
|
|
31
|
+
const eventKeys = shapeKeys(eventFrontmatterSchema).filter((key) => !excluded.has(key));
|
|
32
|
+
const lines = [
|
|
33
|
+
"Common fields (every file):",
|
|
34
|
+
...describeFields(["type", ...commonKeys]),
|
|
35
|
+
"",
|
|
36
|
+
"Entity-only fields:",
|
|
37
|
+
...describeFields(entityKeys),
|
|
38
|
+
"",
|
|
39
|
+
"Event-only fields:",
|
|
40
|
+
...describeFields(eventKeys),
|
|
41
|
+
"",
|
|
42
|
+
'Any other frontmatter field whose value is a wikilink ("[[uuid|Display Name]]") is automatically a relation.',
|
|
43
|
+
];
|
|
44
|
+
if (entityTypeVocabulary && entityTypeVocabulary.length > 0) {
|
|
45
|
+
lines.push("", `This vault's declared entityType vocabulary: ${entityTypeVocabulary.join(", ")}`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
lines.push("", "This vault has not declared an entityType vocabulary — any entityType value is accepted.");
|
|
49
|
+
}
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Any field whose value looks like this is auto-detected as a graph edge (SCHEMA.md rule 2) — no fixed vocabulary of relation-field names. */
|
|
3
|
+
export declare const wikilinkSchema: z.ZodEffects<z.ZodString, string, string>;
|
|
4
|
+
export declare const inWorldDateSchema: z.ZodObject<{
|
|
5
|
+
year: z.ZodNumber;
|
|
6
|
+
day: z.ZodNumber;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
year: number;
|
|
9
|
+
day: number;
|
|
10
|
+
}, {
|
|
11
|
+
year: number;
|
|
12
|
+
day: number;
|
|
13
|
+
}>;
|
|
14
|
+
/** Fields every knowledge file carries (SCHEMA.md "Common frontmatter"). */
|
|
15
|
+
export declare const commonFieldsSchema: z.ZodObject<{
|
|
16
|
+
title: z.ZodString;
|
|
17
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
18
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
title: string;
|
|
21
|
+
tags?: string[] | undefined;
|
|
22
|
+
aliases?: string[] | undefined;
|
|
23
|
+
}, {
|
|
24
|
+
title: string;
|
|
25
|
+
tags?: string[] | undefined;
|
|
26
|
+
aliases?: string[] | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
export declare const entityFrontmatterSchema: z.ZodObject<{
|
|
29
|
+
title: z.ZodString;
|
|
30
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
31
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
32
|
+
} & {
|
|
33
|
+
type: z.ZodLiteral<"entity">;
|
|
34
|
+
entityType: z.ZodOptional<z.ZodString>;
|
|
35
|
+
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
36
|
+
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
37
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
38
|
+
title: z.ZodString;
|
|
39
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
40
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
41
|
+
} & {
|
|
42
|
+
type: z.ZodLiteral<"entity">;
|
|
43
|
+
entityType: z.ZodOptional<z.ZodString>;
|
|
44
|
+
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
45
|
+
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
46
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
47
|
+
title: z.ZodString;
|
|
48
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
49
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
50
|
+
} & {
|
|
51
|
+
type: z.ZodLiteral<"entity">;
|
|
52
|
+
entityType: z.ZodOptional<z.ZodString>;
|
|
53
|
+
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
54
|
+
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
55
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
56
|
+
export declare const eventFrontmatterSchema: z.ZodObject<{
|
|
57
|
+
title: z.ZodString;
|
|
58
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
59
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
60
|
+
} & {
|
|
61
|
+
type: z.ZodLiteral<"event">;
|
|
62
|
+
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
63
|
+
date: z.ZodOptional<z.ZodObject<{
|
|
64
|
+
year: z.ZodNumber;
|
|
65
|
+
day: z.ZodNumber;
|
|
66
|
+
}, "strip", z.ZodTypeAny, {
|
|
67
|
+
year: number;
|
|
68
|
+
day: number;
|
|
69
|
+
}, {
|
|
70
|
+
year: number;
|
|
71
|
+
day: number;
|
|
72
|
+
}>>;
|
|
73
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
74
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
75
|
+
title: z.ZodString;
|
|
76
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
77
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
78
|
+
} & {
|
|
79
|
+
type: z.ZodLiteral<"event">;
|
|
80
|
+
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
81
|
+
date: z.ZodOptional<z.ZodObject<{
|
|
82
|
+
year: z.ZodNumber;
|
|
83
|
+
day: z.ZodNumber;
|
|
84
|
+
}, "strip", z.ZodTypeAny, {
|
|
85
|
+
year: number;
|
|
86
|
+
day: number;
|
|
87
|
+
}, {
|
|
88
|
+
year: number;
|
|
89
|
+
day: number;
|
|
90
|
+
}>>;
|
|
91
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
92
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
93
|
+
title: z.ZodString;
|
|
94
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
95
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
96
|
+
} & {
|
|
97
|
+
type: z.ZodLiteral<"event">;
|
|
98
|
+
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
99
|
+
date: z.ZodOptional<z.ZodObject<{
|
|
100
|
+
year: z.ZodNumber;
|
|
101
|
+
day: z.ZodNumber;
|
|
102
|
+
}, "strip", z.ZodTypeAny, {
|
|
103
|
+
year: number;
|
|
104
|
+
day: number;
|
|
105
|
+
}, {
|
|
106
|
+
year: number;
|
|
107
|
+
day: number;
|
|
108
|
+
}>>;
|
|
109
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
110
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
111
|
+
export declare const knowledgeFileFrontmatterSchema: z.ZodEffects<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
112
|
+
title: z.ZodString;
|
|
113
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
114
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
115
|
+
} & {
|
|
116
|
+
type: z.ZodLiteral<"entity">;
|
|
117
|
+
entityType: z.ZodOptional<z.ZodString>;
|
|
118
|
+
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
119
|
+
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
120
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
121
|
+
title: z.ZodString;
|
|
122
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
123
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
124
|
+
} & {
|
|
125
|
+
type: z.ZodLiteral<"entity">;
|
|
126
|
+
entityType: z.ZodOptional<z.ZodString>;
|
|
127
|
+
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
128
|
+
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
129
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
130
|
+
title: z.ZodString;
|
|
131
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
132
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
133
|
+
} & {
|
|
134
|
+
type: z.ZodLiteral<"entity">;
|
|
135
|
+
entityType: z.ZodOptional<z.ZodString>;
|
|
136
|
+
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
137
|
+
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
138
|
+
}, z.ZodTypeAny, "passthrough">>, z.ZodObject<{
|
|
139
|
+
title: z.ZodString;
|
|
140
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
141
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
142
|
+
} & {
|
|
143
|
+
type: z.ZodLiteral<"event">;
|
|
144
|
+
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
145
|
+
date: z.ZodOptional<z.ZodObject<{
|
|
146
|
+
year: z.ZodNumber;
|
|
147
|
+
day: z.ZodNumber;
|
|
148
|
+
}, "strip", z.ZodTypeAny, {
|
|
149
|
+
year: number;
|
|
150
|
+
day: number;
|
|
151
|
+
}, {
|
|
152
|
+
year: number;
|
|
153
|
+
day: number;
|
|
154
|
+
}>>;
|
|
155
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
156
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
157
|
+
title: z.ZodString;
|
|
158
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
159
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
160
|
+
} & {
|
|
161
|
+
type: z.ZodLiteral<"event">;
|
|
162
|
+
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
163
|
+
date: z.ZodOptional<z.ZodObject<{
|
|
164
|
+
year: z.ZodNumber;
|
|
165
|
+
day: z.ZodNumber;
|
|
166
|
+
}, "strip", z.ZodTypeAny, {
|
|
167
|
+
year: number;
|
|
168
|
+
day: number;
|
|
169
|
+
}, {
|
|
170
|
+
year: number;
|
|
171
|
+
day: number;
|
|
172
|
+
}>>;
|
|
173
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
174
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
175
|
+
title: z.ZodString;
|
|
176
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
177
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
178
|
+
} & {
|
|
179
|
+
type: z.ZodLiteral<"event">;
|
|
180
|
+
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
181
|
+
date: z.ZodOptional<z.ZodObject<{
|
|
182
|
+
year: z.ZodNumber;
|
|
183
|
+
day: z.ZodNumber;
|
|
184
|
+
}, "strip", z.ZodTypeAny, {
|
|
185
|
+
year: number;
|
|
186
|
+
day: number;
|
|
187
|
+
}, {
|
|
188
|
+
year: number;
|
|
189
|
+
day: number;
|
|
190
|
+
}>>;
|
|
191
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
192
|
+
}, z.ZodTypeAny, "passthrough">>]>, z.objectOutputType<{
|
|
193
|
+
title: z.ZodString;
|
|
194
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
195
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
196
|
+
} & {
|
|
197
|
+
type: z.ZodLiteral<"entity">;
|
|
198
|
+
entityType: z.ZodOptional<z.ZodString>;
|
|
199
|
+
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
200
|
+
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
201
|
+
}, z.ZodTypeAny, "passthrough"> | z.objectOutputType<{
|
|
202
|
+
title: z.ZodString;
|
|
203
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
204
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
205
|
+
} & {
|
|
206
|
+
type: z.ZodLiteral<"event">;
|
|
207
|
+
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
208
|
+
date: z.ZodOptional<z.ZodObject<{
|
|
209
|
+
year: z.ZodNumber;
|
|
210
|
+
day: z.ZodNumber;
|
|
211
|
+
}, "strip", z.ZodTypeAny, {
|
|
212
|
+
year: number;
|
|
213
|
+
day: number;
|
|
214
|
+
}, {
|
|
215
|
+
year: number;
|
|
216
|
+
day: number;
|
|
217
|
+
}>>;
|
|
218
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
219
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
220
|
+
title: z.ZodString;
|
|
221
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
222
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
223
|
+
} & {
|
|
224
|
+
type: z.ZodLiteral<"entity">;
|
|
225
|
+
entityType: z.ZodOptional<z.ZodString>;
|
|
226
|
+
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
227
|
+
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
228
|
+
}, z.ZodTypeAny, "passthrough"> | z.objectInputType<{
|
|
229
|
+
title: z.ZodString;
|
|
230
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
231
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
232
|
+
} & {
|
|
233
|
+
type: z.ZodLiteral<"event">;
|
|
234
|
+
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
235
|
+
date: z.ZodOptional<z.ZodObject<{
|
|
236
|
+
year: z.ZodNumber;
|
|
237
|
+
day: z.ZodNumber;
|
|
238
|
+
}, "strip", z.ZodTypeAny, {
|
|
239
|
+
year: number;
|
|
240
|
+
day: number;
|
|
241
|
+
}, {
|
|
242
|
+
year: number;
|
|
243
|
+
day: number;
|
|
244
|
+
}>>;
|
|
245
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
246
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
247
|
+
export type EntityFrontmatter = z.infer<typeof entityFrontmatterSchema>;
|
|
248
|
+
export type EventFrontmatter = z.infer<typeof eventFrontmatterSchema>;
|
|
249
|
+
export type KnowledgeFileFrontmatter = z.infer<typeof knowledgeFileFrontmatterSchema>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isWikilink } from "./wikilink.js";
|
|
3
|
+
/** Any field whose value looks like this is auto-detected as a graph edge (SCHEMA.md rule 2) — no fixed vocabulary of relation-field names. */
|
|
4
|
+
export const wikilinkSchema = z.string().refine(isWikilink, {
|
|
5
|
+
message: 'Expected a wikilink in the form "[[uuid|Display Name]]"',
|
|
6
|
+
});
|
|
7
|
+
export const inWorldDateSchema = z.object({
|
|
8
|
+
year: z.number().int(),
|
|
9
|
+
day: z.number().int(),
|
|
10
|
+
});
|
|
11
|
+
/** Fields every knowledge file carries (SCHEMA.md "Common frontmatter"). */
|
|
12
|
+
export const commonFieldsSchema = z.object({
|
|
13
|
+
title: z.string().min(1, "title is required"),
|
|
14
|
+
tags: z.array(z.string()).optional(),
|
|
15
|
+
aliases: z.array(z.string()).optional(),
|
|
16
|
+
});
|
|
17
|
+
export const entityFrontmatterSchema = commonFieldsSchema
|
|
18
|
+
.extend({
|
|
19
|
+
type: z.literal("entity"),
|
|
20
|
+
entityType: z.string().optional(),
|
|
21
|
+
supersedes: wikilinkSchema.optional(),
|
|
22
|
+
causedBy: wikilinkSchema.optional(),
|
|
23
|
+
})
|
|
24
|
+
.passthrough();
|
|
25
|
+
export const eventFrontmatterSchema = commonFieldsSchema
|
|
26
|
+
.extend({
|
|
27
|
+
type: z.literal("event"),
|
|
28
|
+
after: z.array(wikilinkSchema).optional(),
|
|
29
|
+
date: inWorldDateSchema.optional(),
|
|
30
|
+
entities: z.array(wikilinkSchema).optional(),
|
|
31
|
+
})
|
|
32
|
+
.passthrough();
|
|
33
|
+
export const knowledgeFileFrontmatterSchema = z
|
|
34
|
+
.discriminatedUnion("type", [entityFrontmatterSchema, eventFrontmatterSchema])
|
|
35
|
+
.superRefine((data, ctx) => {
|
|
36
|
+
if (data.type === "entity" && Boolean(data.supersedes) !== Boolean(data.causedBy)) {
|
|
37
|
+
ctx.addIssue({
|
|
38
|
+
code: z.ZodIssueCode.custom,
|
|
39
|
+
message: '"supersedes" and "causedBy" must both be present, or both absent',
|
|
40
|
+
path: ["causedBy"],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type KnowledgeFileFrontmatter } from "./fields.js";
|
|
2
|
+
export interface ValidationIssue {
|
|
3
|
+
path: string;
|
|
4
|
+
message: string;
|
|
5
|
+
/** "error" blocks a write and makes a file invalid; "warning" is a suggestion only. */
|
|
6
|
+
severity: "error" | "warning";
|
|
7
|
+
}
|
|
8
|
+
export declare function hasErrors(issues: ValidationIssue[]): boolean;
|
|
9
|
+
export interface ValidationResult {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
frontmatter?: KnowledgeFileFrontmatter;
|
|
12
|
+
issues: ValidationIssue[];
|
|
13
|
+
}
|
|
14
|
+
export interface ValidateOptions {
|
|
15
|
+
/** This project's declared entityType vocabulary (PROJECT_CONFIG.md). Omit to skip the check. */
|
|
16
|
+
entityTypeVocabulary?: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The one place a knowledge file's frontmatter gets checked (PROJECT_STRUCTURE.md:
|
|
20
|
+
* used by vault parsing, CLI `validate`/`create`, and the CLI's `schema` introspection).
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateFrontmatter(raw: unknown, options?: ValidateOptions): ValidationResult;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { knowledgeFileFrontmatterSchema } from "./fields.js";
|
|
2
|
+
export function hasErrors(issues) {
|
|
3
|
+
return issues.some((issue) => issue.severity === "error");
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* The one place a knowledge file's frontmatter gets checked (PROJECT_STRUCTURE.md:
|
|
7
|
+
* used by vault parsing, CLI `validate`/`create`, and the CLI's `schema` introspection).
|
|
8
|
+
*/
|
|
9
|
+
export function validateFrontmatter(raw, options = {}) {
|
|
10
|
+
const result = knowledgeFileFrontmatterSchema.safeParse(raw);
|
|
11
|
+
if (!result.success) {
|
|
12
|
+
return {
|
|
13
|
+
valid: false,
|
|
14
|
+
issues: result.error.issues.map((issue) => ({
|
|
15
|
+
path: issue.path.join("."),
|
|
16
|
+
message: issue.message,
|
|
17
|
+
severity: "error",
|
|
18
|
+
})),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const frontmatter = result.data;
|
|
22
|
+
const issues = [];
|
|
23
|
+
if (frontmatter.type === "entity" &&
|
|
24
|
+
frontmatter.entityType &&
|
|
25
|
+
options.entityTypeVocabulary &&
|
|
26
|
+
!options.entityTypeVocabulary.includes(frontmatter.entityType)) {
|
|
27
|
+
issues.push({
|
|
28
|
+
path: "entityType",
|
|
29
|
+
message: `"${frontmatter.entityType}" is not in this project's declared entityType vocabulary (${options.entityTypeVocabulary.join(", ")})`,
|
|
30
|
+
severity: "error",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return { valid: issues.length === 0, frontmatter, issues };
|
|
34
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface Wikilink {
|
|
2
|
+
targetId: string;
|
|
3
|
+
display: string;
|
|
4
|
+
}
|
|
5
|
+
/** True if `value` is a wikilink in the framework's required form: `[[uuid|Display Name]]` (SCHEMA.md rule 7). */
|
|
6
|
+
export declare function isWikilink(value: unknown): value is string;
|
|
7
|
+
export declare function parseWikilink(raw: string): Wikilink;
|
|
8
|
+
export declare function formatWikilink(targetId: string, display: string): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isUuid } from "../id.js";
|
|
2
|
+
const WIKILINK_RE = /^\[\[([0-9a-f-]{36})\|(.+)\]\]$/i;
|
|
3
|
+
/** True if `value` is a wikilink in the framework's required form: `[[uuid|Display Name]]` (SCHEMA.md rule 7). */
|
|
4
|
+
export function isWikilink(value) {
|
|
5
|
+
if (typeof value !== "string")
|
|
6
|
+
return false;
|
|
7
|
+
const match = WIKILINK_RE.exec(value);
|
|
8
|
+
return match !== null && isUuid(match[1]);
|
|
9
|
+
}
|
|
10
|
+
export function parseWikilink(raw) {
|
|
11
|
+
const match = WIKILINK_RE.exec(raw);
|
|
12
|
+
if (!match) {
|
|
13
|
+
throw new Error(`Not a valid wikilink (expected "[[uuid|Display Name]]"): ${raw}`);
|
|
14
|
+
}
|
|
15
|
+
const [, targetId, display] = match;
|
|
16
|
+
if (!isUuid(targetId)) {
|
|
17
|
+
throw new Error(`Wikilink target is not a valid UUID: ${targetId}`);
|
|
18
|
+
}
|
|
19
|
+
return { targetId, display };
|
|
20
|
+
}
|
|
21
|
+
export function formatWikilink(targetId, display) {
|
|
22
|
+
return `[[${targetId}|${display}]]`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VaultFile } from "../vault/types.js";
|
|
2
|
+
import type { SearchIndex } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds an in-memory FTS5 search index from the current vault state. This is a
|
|
5
|
+
* disposable cache, not a source of truth — rebuild it whenever the vault changes
|
|
6
|
+
* rather than trying to keep it in sync incrementally (see claude-dev/PROJECT_STRUCTURE.md
|
|
7
|
+
* discussion: not worth the complexity until indexing hundreds of files is ever
|
|
8
|
+
* actually slow, which it isn't).
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildSearchIndex(files: VaultFile[]): SearchIndex;
|
|
11
|
+
export declare function closeSearchIndex(index: SearchIndex): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { COLUMNS, toSpaceJoined } from "./columns.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds an in-memory FTS5 search index from the current vault state. This is a
|
|
5
|
+
* disposable cache, not a source of truth — rebuild it whenever the vault changes
|
|
6
|
+
* rather than trying to keep it in sync incrementally (see claude-dev/PROJECT_STRUCTURE.md
|
|
7
|
+
* discussion: not worth the complexity until indexing hundreds of files is ever
|
|
8
|
+
* actually slow, which it isn't).
|
|
9
|
+
*/
|
|
10
|
+
export function buildSearchIndex(files) {
|
|
11
|
+
const db = new Database(":memory:");
|
|
12
|
+
db.exec(`CREATE VIRTUAL TABLE files USING fts5(${COLUMNS.map((c) => (c === "id" ? `${c} UNINDEXED` : c)).join(", ")});`);
|
|
13
|
+
const insert = db.prepare(`INSERT INTO files (${COLUMNS.join(", ")}) VALUES (${COLUMNS.map(() => "?").join(", ")})`);
|
|
14
|
+
const filesById = new Map();
|
|
15
|
+
const insertAll = db.transaction((rows) => {
|
|
16
|
+
for (const file of rows) {
|
|
17
|
+
filesById.set(file.id, file);
|
|
18
|
+
insert.run(file.id, toSpaceJoined(file.frontmatter.title), toSpaceJoined(file.frontmatter.aliases), toSpaceJoined(file.frontmatter.tags), toSpaceJoined(file.frontmatter.entityType), file.body);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
insertAll(files);
|
|
22
|
+
return { db, filesById };
|
|
23
|
+
}
|
|
24
|
+
export function closeSearchIndex(index) {
|
|
25
|
+
index.db.close();
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FTS5 column order for the `files` virtual table: id, title, aliases, tags,
|
|
3
|
+
* entityType, body. Weights boost title/aliases/tags/entityType matches over
|
|
4
|
+
* plain body-text matches; id is UNINDEXED so its weight is irrelevant.
|
|
5
|
+
* Keep build.ts and search.ts in sync via these constants.
|
|
6
|
+
*/
|
|
7
|
+
export declare const COLUMNS: readonly ["id", "title", "aliases", "tags", "entityType", "body"];
|
|
8
|
+
export declare const COLUMN_WEIGHTS = "0.0, 5.0, 3.0, 2.0, 2.0, 1.0";
|
|
9
|
+
export declare function toSpaceJoined(value: unknown): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FTS5 column order for the `files` virtual table: id, title, aliases, tags,
|
|
3
|
+
* entityType, body. Weights boost title/aliases/tags/entityType matches over
|
|
4
|
+
* plain body-text matches; id is UNINDEXED so its weight is irrelevant.
|
|
5
|
+
* Keep build.ts and search.ts in sync via these constants.
|
|
6
|
+
*/
|
|
7
|
+
export const COLUMNS = ["id", "title", "aliases", "tags", "entityType", "body"];
|
|
8
|
+
export const COLUMN_WEIGHTS = "0.0, 5.0, 3.0, 2.0, 2.0, 1.0";
|
|
9
|
+
export function toSpaceJoined(value) {
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
return value.filter((entry) => typeof entry === "string").join(" ");
|
|
12
|
+
}
|
|
13
|
+
return typeof value === "string" ? value : "";
|
|
14
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { SearchIndex, SearchOptions, SearchResult } from "./types.js";
|
|
2
|
+
/** Keyword search over the index. Title/aliases/tags/entityType matches outrank plain body-text matches. */
|
|
3
|
+
export declare function search(index: SearchIndex, query: string, options?: SearchOptions): SearchResult[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { COLUMN_WEIGHTS } from "./columns.js";
|
|
2
|
+
function buildMatchQuery(query) {
|
|
3
|
+
const tokens = query
|
|
4
|
+
.split(/\s+/)
|
|
5
|
+
.map((token) => token.trim())
|
|
6
|
+
.filter(Boolean)
|
|
7
|
+
.map((token) => `"${token.replace(/"/g, '""')}"`);
|
|
8
|
+
return tokens.length > 0 ? tokens.join(" ") : undefined;
|
|
9
|
+
}
|
|
10
|
+
function hasAllTags(file, tags) {
|
|
11
|
+
const fileTags = Array.isArray(file.frontmatter.tags) ? file.frontmatter.tags : [];
|
|
12
|
+
return tags.every((tag) => fileTags.includes(tag));
|
|
13
|
+
}
|
|
14
|
+
/** Keyword search over the index. Title/aliases/tags/entityType matches outrank plain body-text matches. */
|
|
15
|
+
export function search(index, query, options = {}) {
|
|
16
|
+
const matchQuery = buildMatchQuery(query);
|
|
17
|
+
if (!matchQuery)
|
|
18
|
+
return [];
|
|
19
|
+
const rows = index.db
|
|
20
|
+
.prepare(`SELECT id, -bm25(files, ${COLUMN_WEIGHTS}) AS score
|
|
21
|
+
FROM files WHERE files MATCH ?
|
|
22
|
+
ORDER BY score DESC`)
|
|
23
|
+
.all(matchQuery);
|
|
24
|
+
let results = rows
|
|
25
|
+
.map((row) => ({ id: row.id, score: row.score, file: index.filesById.get(row.id) }))
|
|
26
|
+
.filter((result) => result.file !== undefined);
|
|
27
|
+
if (options.tags && options.tags.length > 0) {
|
|
28
|
+
const tags = options.tags;
|
|
29
|
+
results = results.filter((result) => hasAllTags(result.file, tags));
|
|
30
|
+
}
|
|
31
|
+
if (options.limit !== undefined) {
|
|
32
|
+
results = results.slice(0, options.limit);
|
|
33
|
+
}
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import type { VaultFile } from "../vault/types.js";
|
|
3
|
+
/** An in-memory FTS5 index over a vault snapshot — disposable, rebuilt from the vault, never persisted. */
|
|
4
|
+
export interface SearchIndex {
|
|
5
|
+
db: Database.Database;
|
|
6
|
+
filesById: Map<string, VaultFile>;
|
|
7
|
+
}
|
|
8
|
+
export interface SearchOptions {
|
|
9
|
+
/** Only include files carrying every one of these tags. */
|
|
10
|
+
tags?: string[];
|
|
11
|
+
limit?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface SearchResult {
|
|
14
|
+
id: string;
|
|
15
|
+
file: VaultFile;
|
|
16
|
+
/** Relevance score — higher is a better match. */
|
|
17
|
+
score: number;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { VaultFile } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parses a vault file's raw content. Never validates against the schema —
|
|
4
|
+
* reading a vault file must always succeed, even if its frontmatter doesn't
|
|
5
|
+
* conform (e.g. it predates a schema change). Validation happens on write only,
|
|
6
|
+
* see write.ts.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseVaultFile(id: string, raw: string): VaultFile;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import matter from "gray-matter";
|
|
2
|
+
/**
|
|
3
|
+
* Parses a vault file's raw content. Never validates against the schema —
|
|
4
|
+
* reading a vault file must always succeed, even if its frontmatter doesn't
|
|
5
|
+
* conform (e.g. it predates a schema change). Validation happens on write only,
|
|
6
|
+
* see write.ts.
|
|
7
|
+
*/
|
|
8
|
+
export function parseVaultFile(id, raw) {
|
|
9
|
+
const { data, content } = matter(raw);
|
|
10
|
+
return { id, frontmatter: data, body: content };
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { VaultFile } from "./types.js";
|
|
2
|
+
/** Where an existing vault file actually lives — possibly in a local subfolder (see readVault). */
|
|
3
|
+
export declare function findVaultFilePath(vaultDir: string, id: string): Promise<string | undefined>;
|
|
4
|
+
/** Reads and parses one vault file by id. Never validates — see write.ts. */
|
|
5
|
+
export declare function readVaultFile(vaultDir: string, id: string): Promise<VaultFile>;
|
|
6
|
+
/**
|
|
7
|
+
* Reads every vault file: UUID-named `.md` files anywhere under vaultDir.
|
|
8
|
+
* SCHEMA.md's flat pool needs no folders, but a project may still use them
|
|
9
|
+
* locally (rule 5), so this looks recursively rather than assuming a flat layout.
|
|
10
|
+
* Anything not UUID-named (README, config, etc.) is silently skipped, and
|
|
11
|
+
* tooling folders (node_modules, dot-folders) aren't searched at all.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readVault(vaultDir: string): Promise<VaultFile[]>;
|