contentbase 0.0.4 → 0.0.6

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 (82) hide show
  1. package/package.json +3 -2
  2. package/.mcp.json +0 -9
  3. package/MCP-DESCRIPTIONS-REVIEW.md +0 -122
  4. package/bun.lock +0 -512
  5. package/dist/cnotes +0 -0
  6. package/examples/sdlc-queries.ts +0 -161
  7. package/public/web-demo/index.html +0 -813
  8. package/scripts/examples/01-collection-setup.ts +0 -46
  9. package/scripts/examples/02-querying.ts +0 -67
  10. package/scripts/examples/03-sections.ts +0 -36
  11. package/scripts/examples/04-relationships.ts +0 -54
  12. package/scripts/examples/05-document-api.ts +0 -54
  13. package/scripts/examples/06-extract-sections.ts +0 -55
  14. package/scripts/examples/07-validation.ts +0 -46
  15. package/scripts/examples/08-serialization.ts +0 -51
  16. package/scripts/examples/lib/format.ts +0 -87
  17. package/scripts/examples/lib/setup.ts +0 -21
  18. package/scripts/examples/run-all.ts +0 -43
  19. package/showcases/national-parks/models.ts +0 -74
  20. package/showcases/national-parks/parks/acadia.mdx +0 -40
  21. package/showcases/national-parks/parks/yosemite.mdx +0 -44
  22. package/showcases/national-parks/parks/zion.mdx +0 -44
  23. package/showcases/national-parks/queries.ts +0 -103
  24. package/showcases/national-parks/trails/angels-landing.mdx +0 -19
  25. package/showcases/national-parks/trails/cathedral-lakes.mdx +0 -19
  26. package/showcases/national-parks/trails/half-dome.mdx +0 -19
  27. package/showcases/national-parks/trails/jordan-pond-path.mdx +0 -19
  28. package/showcases/national-parks/trails/mist-trail.mdx +0 -19
  29. package/showcases/national-parks/trails/observation-point.mdx +0 -19
  30. package/showcases/national-parks/trails/precipice-trail.mdx +0 -19
  31. package/showcases/national-parks/trails/the-narrows.mdx +0 -19
  32. package/showcases/node_modules/.cache/.repl_history +0 -3
  33. package/showcases/recipes/cuisines/chinese.mdx +0 -28
  34. package/showcases/recipes/cuisines/italian.mdx +0 -32
  35. package/showcases/recipes/cuisines/mexican.mdx +0 -28
  36. package/showcases/recipes/models.ts +0 -77
  37. package/showcases/recipes/queries.ts +0 -89
  38. package/showcases/recipes/recipes/chinese/egg-fried-rice.mdx +0 -43
  39. package/showcases/recipes/recipes/chinese/mapo-tofu.mdx +0 -47
  40. package/showcases/recipes/recipes/italian/bruschetta.mdx +0 -38
  41. package/showcases/recipes/recipes/italian/cacio-e-pepe.mdx +0 -39
  42. package/showcases/recipes/recipes/italian/tiramisu.mdx +0 -43
  43. package/showcases/recipes/recipes/mexican/chicken-tinga.mdx +0 -44
  44. package/showcases/recipes/recipes/mexican/guacamole.mdx +0 -39
  45. package/showcases/vinyl-collection/albums/bitches-brew.mdx +0 -36
  46. package/showcases/vinyl-collection/albums/i-put-a-spell-on-you.mdx +0 -35
  47. package/showcases/vinyl-collection/albums/in-rainbows.mdx +0 -35
  48. package/showcases/vinyl-collection/albums/kind-of-blue.mdx +0 -32
  49. package/showcases/vinyl-collection/albums/ok-computer.mdx +0 -37
  50. package/showcases/vinyl-collection/albums/wild-is-the-wind.mdx +0 -35
  51. package/showcases/vinyl-collection/artists/miles-davis.mdx +0 -27
  52. package/showcases/vinyl-collection/artists/nina-simone.mdx +0 -26
  53. package/showcases/vinyl-collection/artists/radiohead.mdx +0 -27
  54. package/showcases/vinyl-collection/models.ts +0 -73
  55. package/showcases/vinyl-collection/queries.ts +0 -87
  56. package/src/__tests__/semantic-search.integration.test.ts +0 -284
  57. package/test/ast-query.test.ts +0 -128
  58. package/test/collection.test.ts +0 -215
  59. package/test/define-model.test.ts +0 -78
  60. package/test/document.test.ts +0 -225
  61. package/test/extract-sections.test.ts +0 -356
  62. package/test/fixtures/sdlc/MODELS.md +0 -106
  63. package/test/fixtures/sdlc/SKILL.md +0 -404
  64. package/test/fixtures/sdlc/epics/authentication.mdx +0 -42
  65. package/test/fixtures/sdlc/epics/searching-and-browsing.mdx +0 -21
  66. package/test/fixtures/sdlc/index.ts +0 -9
  67. package/test/fixtures/sdlc/models.ts +0 -91
  68. package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-login.mdx +0 -20
  69. package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-register.mdx +0 -20
  70. package/test/fixtures/sdlc/templates/epic.md +0 -23
  71. package/test/fixtures/sdlc/templates/story.md +0 -19
  72. package/test/helpers.ts +0 -21
  73. package/test/model-instance.test.ts +0 -197
  74. package/test/pattern.test.ts +0 -191
  75. package/test/query-dsl.test.ts +0 -431
  76. package/test/query.test.ts +0 -196
  77. package/test/relationships.test.ts +0 -214
  78. package/test/section.test.ts +0 -160
  79. package/test/table-of-contents.test.ts +0 -135
  80. package/test/validator.test.ts +0 -62
  81. package/tsconfig.json +0 -18
  82. package/vitest.config.ts +0 -11
@@ -1,214 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { Collection } from "../src/collection";
3
- import { createModelInstance } from "../src/model-instance";
4
- import { defineModel, hasMany, belongsTo, z } from "../src/index";
5
- import { createTestCollection } from "./helpers";
6
- import { Epic, Story } from "./fixtures/sdlc/models";
7
-
8
- describe("HasManyRelationship", () => {
9
- let collection: Collection;
10
-
11
- beforeEach(async () => {
12
- collection = await createTestCollection();
13
- });
14
-
15
- it("extracts child headings from parent section", () => {
16
- const epic = collection.getModel("epics/authentication", Epic);
17
- const stories = epic.relationships.stories.fetchAll();
18
- expect(stories.length).toBe(2);
19
- });
20
-
21
- it("child instances have correct titles", () => {
22
- const epic = collection.getModel("epics/authentication", Epic);
23
- const stories = epic.relationships.stories.fetchAll();
24
- expect(stories[0].title).toContain("register");
25
- expect(stories[1].title).toContain("login");
26
- });
27
-
28
- it("first returns first child", () => {
29
- const epic = collection.getModel("epics/authentication", Epic);
30
- const first = epic.relationships.stories.first();
31
- expect(first).toBeDefined();
32
- });
33
-
34
- it("last returns last child", () => {
35
- const epic = collection.getModel("epics/authentication", Epic);
36
- const last = epic.relationships.stories.last();
37
- expect(last).toBeDefined();
38
- });
39
-
40
- it("works with epic that has multiple stories", () => {
41
- const epic = collection.getModel(
42
- "epics/searching-and-browsing",
43
- Epic
44
- );
45
- const stories = epic.relationships.stories.fetchAll();
46
- expect(stories.length).toBe(3);
47
- });
48
-
49
- it("computes IDs as targetPrefix/parentSlug/childSlug", () => {
50
- const epic = collection.getModel("epics/authentication", Epic);
51
- const stories = epic.relationships.stories.fetchAll();
52
- expect(stories[0].id).toContain("stories/");
53
- expect(stories[0].id).toContain("authentication");
54
- });
55
- });
56
-
57
- describe("HasMany with foreignKey", () => {
58
- let collection: Collection;
59
-
60
- beforeEach(async () => {
61
- collection = await createTestCollection();
62
- });
63
-
64
- it("finds children by matching meta field to parent slug", () => {
65
- // Define a version of Epic that uses foreignKey instead of heading
66
- const EpicFK = defineModel("Epic", {
67
- prefix: "epics",
68
- meta: z.object({
69
- priority: z.enum(["low", "medium", "high"]).optional(),
70
- status: z.enum(["created", "in-progress", "complete"]).default("created"),
71
- }),
72
- relationships: {
73
- stories: hasMany(() => Story, { foreignKey: "epic" }),
74
- },
75
- });
76
-
77
- const doc = collection.document("epics/authentication");
78
- const epic = createModelInstance(doc, EpicFK, collection);
79
- const stories = epic.relationships.stories.fetchAll();
80
-
81
- expect(stories.length).toBe(2);
82
- expect(stories.map((s: any) => s.meta.epic)).toEqual(["authentication", "authentication"]);
83
- });
84
-
85
- it("infers foreignKey from parent prefix when omitted", () => {
86
- // hasMany with empty options — should infer foreignKey as "epic" from "epics/" prefix
87
- const EpicInferred = defineModel("Epic", {
88
- prefix: "epics",
89
- meta: z.object({
90
- priority: z.enum(["low", "medium", "high"]).optional(),
91
- status: z.enum(["created", "in-progress", "complete"]).default("created"),
92
- }),
93
- relationships: {
94
- stories: hasMany(() => Story, {}),
95
- },
96
- });
97
-
98
- const doc = collection.document("epics/authentication");
99
- const epic = createModelInstance(doc, EpicInferred, collection);
100
- const stories = epic.relationships.stories.fetchAll();
101
-
102
- expect(stories.length).toBe(2);
103
- });
104
-
105
- it("returns empty array when no children match", () => {
106
- const EpicFK = defineModel("Epic", {
107
- prefix: "epics",
108
- meta: z.object({
109
- priority: z.enum(["low", "medium", "high"]).optional(),
110
- status: z.enum(["created", "in-progress", "complete"]).default("created"),
111
- }),
112
- relationships: {
113
- stories: hasMany(() => Story, { foreignKey: "epic" }),
114
- },
115
- });
116
-
117
- const doc = collection.document("epics/searching-and-browsing");
118
- const epic = createModelInstance(doc, EpicFK, collection);
119
- const stories = epic.relationships.stories.fetchAll();
120
-
121
- // No story files have epic: "searching-and-browsing" in their meta
122
- expect(stories.length).toBe(0);
123
- });
124
-
125
- it("children have correct titles and IDs", () => {
126
- const EpicFK = defineModel("Epic", {
127
- prefix: "epics",
128
- meta: z.object({
129
- priority: z.enum(["low", "medium", "high"]).optional(),
130
- status: z.enum(["created", "in-progress", "complete"]).default("created"),
131
- }),
132
- relationships: {
133
- stories: hasMany(() => Story, { foreignKey: "epic" }),
134
- },
135
- });
136
-
137
- const doc = collection.document("epics/authentication");
138
- const epic = createModelInstance(doc, EpicFK, collection);
139
- const stories = epic.relationships.stories.fetchAll();
140
-
141
- const ids = stories.map((s: any) => s.id).sort();
142
- expect(ids).toEqual([
143
- "stories/authentication/a-user-should-be-able-to-login",
144
- "stories/authentication/a-user-should-be-able-to-register",
145
- ]);
146
- });
147
- });
148
-
149
- describe("CollectionQuery include()", () => {
150
- let collection: Collection;
151
-
152
- beforeEach(async () => {
153
- collection = await createTestCollection();
154
- });
155
-
156
- it("includes relationships in toJSON output", async () => {
157
- const results = await collection.query(Epic).include("stories").fetchAll();
158
- const auth = results.find((e: any) => e.id === "epics/authentication");
159
- const json = auth!.toJSON();
160
-
161
- expect(json.stories).toBeDefined();
162
- expect(Array.isArray(json.stories)).toBe(true);
163
- expect((json.stories as any[]).length).toBe(2);
164
- });
165
-
166
- it("toJSON without include does not contain relationships", async () => {
167
- const results = await collection.query(Epic).fetchAll();
168
- const auth = results.find((e: any) => e.id === "epics/authentication");
169
- const json = auth!.toJSON();
170
-
171
- expect(json.stories).toBeUndefined();
172
- });
173
-
174
- it("include can be combined with where", async () => {
175
- const results = await collection.query(Epic)
176
- .where("meta.priority", "high")
177
- .include("stories")
178
- .fetchAll();
179
-
180
- expect(results.length).toBeGreaterThan(0);
181
- const json = results[0].toJSON();
182
- expect(json.stories).toBeDefined();
183
- });
184
- });
185
-
186
- describe("BelongsToRelationship", () => {
187
- let collection: Collection;
188
-
189
- beforeEach(async () => {
190
- collection = await createTestCollection();
191
- });
192
-
193
- it("resolves parent by foreign key", () => {
194
- const story = collection.getModel(
195
- "stories/authentication/a-user-should-be-able-to-register",
196
- Story
197
- );
198
- const epic = story.relationships.epic.fetch();
199
- expect(epic.title).toBe("Authentication");
200
- expect(epic.id).toBe("epics/authentication");
201
- });
202
-
203
- it("throws if parent not found", () => {
204
- const doc = collection.createDocument({
205
- id: "test/orphan",
206
- content: "# Orphan Story\n",
207
- meta: { epic: "nonexistent" },
208
- });
209
- const instance = createModelInstance(doc, Story, collection);
210
- expect(() => instance.relationships.epic.fetch()).toThrow(
211
- 'Could not find Epic'
212
- );
213
- });
214
- });
@@ -1,160 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { section } from "../src/section";
3
- import { Collection } from "../src/collection";
4
- import { createModelInstance } from "../src/model-instance";
5
- import { createTestCollection } from "./helpers";
6
- import { Story } from "./fixtures/sdlc/models";
7
- import { z } from "zod";
8
- import { toString } from "mdast-util-to-string";
9
-
10
- describe("section helper", () => {
11
- it("creates a SectionDefinition with heading and extract", () => {
12
- const sd = section("My Section", {
13
- extract: (q) => q.selectAll("listItem"),
14
- });
15
- expect(sd.heading).toBe("My Section");
16
- expect(sd.extract).toBeDefined();
17
- });
18
-
19
- it("stores optional schema", () => {
20
- const schema = z.array(z.string());
21
- const sd = section("Items", {
22
- extract: (q) =>
23
- q.selectAll("listItem").map((n) => toString(n)),
24
- schema,
25
- });
26
- expect(sd.schema).toBe(schema);
27
- });
28
-
29
- it("extract receives AstQuery scoped to section content only", async () => {
30
- const collection = await createTestCollection();
31
- const doc = collection.document(
32
- "stories/authentication/a-user-should-be-able-to-register"
33
- );
34
- const instance = createModelInstance(doc, Story, collection);
35
-
36
- // acceptanceCriteria should only contain items from that section
37
- const criteria = instance.sections.acceptanceCriteria;
38
- expect(criteria.length).toBe(4);
39
- expect(criteria[0]).toContain("signup form");
40
- });
41
-
42
- it("section data is lazily computed", async () => {
43
- const collection = await createTestCollection();
44
- const doc = collection.document(
45
- "stories/authentication/a-user-should-be-able-to-register"
46
- );
47
-
48
- let extractCalled = 0;
49
- const TestModel = {
50
- name: "Test",
51
- prefix: "test",
52
- meta: z.looseObject({}),
53
- schema: z.looseObject({}),
54
- sections: {
55
- items: section("Acceptance Criteria", {
56
- extract: (q) => {
57
- extractCalled++;
58
- return q.selectAll("listItem").map((n) => toString(n));
59
- },
60
- }),
61
- },
62
- relationships: {},
63
- computed: {},
64
- } as any;
65
-
66
- const instance = createModelInstance(doc, TestModel, collection);
67
- expect(extractCalled).toBe(0); // Not yet accessed
68
- const _items = instance.sections.items;
69
- expect(extractCalled).toBe(1); // Now extracted
70
- const _items2 = instance.sections.items;
71
- expect(extractCalled).toBe(1); // Cached
72
- });
73
-
74
- it("stores alternatives in the definition", () => {
75
- const sd = section("Acceptance Criteria", {
76
- alternatives: ["AC", "ACs"],
77
- extract: (q) => q.selectAll("listItem"),
78
- });
79
- expect(sd.alternatives).toEqual(["AC", "ACs"]);
80
- });
81
-
82
- it("uses alternative heading when primary is missing", async () => {
83
- const collection = await createTestCollection();
84
- const doc = collection.createDocument({
85
- id: "test/alt-heading",
86
- content: "# Title\n\n## AC\n\n- item one\n- item two\n",
87
- });
88
-
89
- const TestModel = {
90
- name: "Test",
91
- prefix: "test",
92
- meta: z.looseObject({}),
93
- schema: z.looseObject({}),
94
- sections: {
95
- criteria: section("Acceptance Criteria", {
96
- alternatives: ["AC", "ACs"],
97
- extract: (q) => q.selectAll("listItem").map((n) => toString(n)),
98
- }),
99
- },
100
- relationships: {},
101
- computed: {},
102
- } as any;
103
-
104
- const instance = createModelInstance(doc, TestModel, collection);
105
- expect(instance.sections.criteria).toEqual(["item one", "item two"]);
106
- });
107
-
108
- it("primary heading takes precedence over alternatives", async () => {
109
- const collection = await createTestCollection();
110
- const doc = collection.createDocument({
111
- id: "test/both-headings",
112
- content:
113
- "# Title\n\n## Acceptance Criteria\n\n- primary item\n\n## AC\n\n- alt item\n",
114
- });
115
-
116
- const TestModel = {
117
- name: "Test",
118
- prefix: "test",
119
- meta: z.looseObject({}),
120
- schema: z.looseObject({}),
121
- sections: {
122
- criteria: section("Acceptance Criteria", {
123
- alternatives: ["AC"],
124
- extract: (q) => q.selectAll("listItem").map((n) => toString(n)),
125
- }),
126
- },
127
- relationships: {},
128
- computed: {},
129
- } as any;
130
-
131
- const instance = createModelInstance(doc, TestModel, collection);
132
- expect(instance.sections.criteria).toEqual(["primary item"]);
133
- });
134
-
135
- it("missing section returns empty data", async () => {
136
- const collection = await createTestCollection();
137
- const doc = collection.createDocument({
138
- id: "test/no-section",
139
- content: "# Just a title\n\nSome content.\n",
140
- });
141
-
142
- const TestModel = {
143
- name: "Test",
144
- prefix: "test",
145
- meta: z.looseObject({}),
146
- schema: z.looseObject({}),
147
- sections: {
148
- missing: section("Nonexistent Section", {
149
- extract: (q) => q.selectAll("listItem").map((n) => toString(n)),
150
- }),
151
- },
152
- relationships: {},
153
- computed: {},
154
- } as any;
155
-
156
- const instance = createModelInstance(doc, TestModel, collection);
157
- // Should not crash, just return empty
158
- expect(instance.sections.missing).toEqual([]);
159
- });
160
- });
@@ -1,135 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { Collection } from "../src/collection";
3
- import { createTestCollection, FIXTURES_PATH } from "./helpers";
4
-
5
- let collection: Collection;
6
-
7
- beforeEach(async () => {
8
- collection = await createTestCollection();
9
- });
10
-
11
- describe("Collection.tableOfContents", () => {
12
- it("generates markdown with model group headings", () => {
13
- const toc = collection.tableOfContents();
14
-
15
- expect(toc).toContain("## Epics");
16
- expect(toc).toContain("## Stories");
17
- });
18
-
19
- it("includes document titles as link text", () => {
20
- const toc = collection.tableOfContents();
21
-
22
- expect(toc).toContain("[Authentication]");
23
- expect(toc).toContain("[Searching And Browsing]");
24
- });
25
-
26
- it("generates relative links with file extensions", () => {
27
- const toc = collection.tableOfContents();
28
-
29
- expect(toc).toContain("(./epics/authentication.mdx)");
30
- expect(toc).toContain("(./epics/searching-and-browsing.mdx)");
31
- });
32
-
33
- it("uses custom basePath for links", () => {
34
- const toc = collection.tableOfContents({ basePath: "./content" });
35
-
36
- expect(toc).toContain("(./content/epics/authentication.mdx)");
37
- });
38
-
39
- it("adds a title heading when provided", () => {
40
- const toc = collection.tableOfContents({ title: "Project Docs" });
41
-
42
- expect(toc).toContain("# Project Docs");
43
- // Model group headings shift to h2
44
- expect(toc).toContain("## Epics");
45
- expect(toc).toContain("## Stories");
46
- });
47
-
48
- it("sorts items alphabetically within groups", () => {
49
- const toc = collection.tableOfContents();
50
- const authIndex = toc.indexOf("[Authentication]");
51
- const searchIndex = toc.indexOf("[Searching And Browsing]");
52
-
53
- expect(authIndex).toBeLessThan(searchIndex);
54
- });
55
-
56
- it("formats entries as markdown list items", () => {
57
- const toc = collection.tableOfContents();
58
- const lines = toc.split("\n");
59
- const listLines = lines.filter((l) => l.startsWith("- ["));
60
-
61
- expect(listLines.length).toBeGreaterThanOrEqual(3);
62
- for (const line of listLines) {
63
- expect(line).toMatch(/^- \[.+\]\(.+\.mdx?\)$/);
64
- }
65
- });
66
-
67
- it("ends with a newline", () => {
68
- const toc = collection.tableOfContents();
69
- expect(toc.endsWith("\n")).toBe(true);
70
- });
71
-
72
- it("works without models registered (flat list)", async () => {
73
- const bare = new Collection({ rootPath: FIXTURES_PATH, autoDiscover: false });
74
- await bare.load();
75
-
76
- const toc = bare.tableOfContents();
77
-
78
- // No group headings — just list items
79
- expect(toc).not.toContain("# Epic");
80
- expect(toc).toContain("- [");
81
- expect(toc).toContain("(./epics/authentication.mdx)");
82
- });
83
-
84
- it("throws if collection not loaded", () => {
85
- const unloaded = new Collection({ rootPath: FIXTURES_PATH });
86
-
87
- expect(() => unloaded.tableOfContents()).toThrow(
88
- "Collection has not been loaded"
89
- );
90
- });
91
- });
92
-
93
- describe("Collection.renderFileTree", () => {
94
- it("renders a tree with directory structure", () => {
95
- const tree = collection.renderFileTree();
96
-
97
- expect(tree).toContain("epics/");
98
- expect(tree).toContain("stories/");
99
- expect(tree).toContain("authentication.mdx");
100
- });
101
-
102
- it("nests files under their directories", () => {
103
- const tree = collection.renderFileTree();
104
- const lines = tree.split("\n");
105
-
106
- // Find the stories directory line and the nested file
107
- const storiesLine = lines.findIndex((l) => l.includes("stories/"));
108
- const nestedFile = lines.findIndex((l) =>
109
- l.includes("a-user-should-be-able-to-register.mdx")
110
- );
111
-
112
- expect(storiesLine).toBeGreaterThanOrEqual(0);
113
- expect(nestedFile).toBeGreaterThan(storiesLine);
114
- });
115
-
116
- it("uses tree connector characters", () => {
117
- const tree = collection.renderFileTree();
118
-
119
- expect(tree).toMatch(/[├└]/);
120
- expect(tree).toMatch(/──/);
121
- });
122
-
123
- it("ends with a newline", () => {
124
- const tree = collection.renderFileTree();
125
- expect(tree.endsWith("\n")).toBe(true);
126
- });
127
-
128
- it("throws if collection not loaded", () => {
129
- const unloaded = new Collection({ rootPath: FIXTURES_PATH });
130
-
131
- expect(() => unloaded.renderFileTree()).toThrow(
132
- "Collection has not been loaded"
133
- );
134
- });
135
- });
@@ -1,62 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { validateDocument } from "../src/validator";
3
- import { Collection } from "../src/collection";
4
- import { createTestCollection } from "./helpers";
5
- import { Epic, Story } from "./fixtures/sdlc/models";
6
-
7
- describe("validateDocument", () => {
8
- let collection: Collection;
9
-
10
- beforeEach(async () => {
11
- collection = await createTestCollection();
12
- });
13
-
14
- it("returns valid for good meta", () => {
15
- const doc = collection.document("epics/authentication");
16
- const result = validateDocument(doc, Epic);
17
- expect(result.valid).toBe(true);
18
- expect(result.errors.length).toBe(0);
19
- });
20
-
21
- it("returns errors for invalid meta", () => {
22
- const doc = collection.createDocument({
23
- id: "test/bad",
24
- content: "# Bad\n",
25
- meta: { status: "BOGUS" },
26
- });
27
- const result = validateDocument(doc, Epic);
28
- expect(result.valid).toBe(false);
29
- expect(result.errors.length).toBeGreaterThan(0);
30
- });
31
-
32
- it("applies defaults before validation", () => {
33
- const doc = collection.createDocument({
34
- id: "test/defaults",
35
- content: "# Defaults\n",
36
- meta: {},
37
- });
38
- const result = validateDocument(doc, Epic);
39
- expect(result.valid).toBe(true);
40
- });
41
-
42
- it("validates sections when schema is present", () => {
43
- const doc = collection.document(
44
- "stories/authentication/a-user-should-be-able-to-register"
45
- );
46
- const result = validateDocument(doc, Story);
47
- expect(result.valid).toBe(true);
48
- });
49
-
50
- it("section validation errors include section key in path", () => {
51
- const doc = collection.createDocument({
52
- id: "test/bad-section",
53
- content: "# Bad\n\n## Acceptance Criteria\n\nNo list items here.\n",
54
- meta: { status: "created" },
55
- });
56
- const result = validateDocument(doc, Story);
57
- // acceptanceCriteria extracts listItems; paragraph text won't produce any
58
- // The schema is z.array(z.string()) which allows empty arrays
59
- // so this should actually pass - the extract returns []
60
- expect(result.valid).toBe(true);
61
- });
62
- });
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "strict": true,
7
- "esModuleInterop": true,
8
- "declaration": true,
9
- "declarationMap": true,
10
- "sourceMap": true,
11
- "outDir": "./dist",
12
- "rootDir": "./src",
13
- "types": ["bun-types"],
14
- "skipLibCheck": true
15
- },
16
- "include": ["src/**/*.ts"],
17
- "exclude": ["node_modules", "dist", "test"]
18
- }
package/vitest.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
-
3
- export default defineConfig({
4
- resolve: {
5
- extensions: [".ts", ".js", ".mjs"],
6
- },
7
- test: {
8
- globals: true,
9
- include: ["test/**/*.test.ts"],
10
- },
11
- });