contentbase 0.0.2 → 0.0.5

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 (130) hide show
  1. package/CLI.md +593 -0
  2. package/MCP-SERVER-SPEC.md +453 -0
  3. package/PRIMER.md +540 -0
  4. package/README.md +289 -13
  5. package/docs/README.md +110 -0
  6. package/docs/TABLE-OF-CONTENTS.md +7 -0
  7. package/docs/models.ts +38 -0
  8. package/models.ts +38 -0
  9. package/package.json +12 -3
  10. package/src/api/endpoints/actions.ts +34 -0
  11. package/src/api/endpoints/doc.ts +187 -0
  12. package/src/api/endpoints/docs-index.ts +26 -0
  13. package/src/api/endpoints/document.ts +171 -0
  14. package/src/api/endpoints/documents.ts +71 -0
  15. package/src/api/endpoints/inspect.ts +16 -0
  16. package/src/api/endpoints/models.ts +10 -0
  17. package/src/api/endpoints/query.ts +88 -0
  18. package/src/api/endpoints/root.ts +7 -0
  19. package/src/api/endpoints/search-reindex.ts +65 -0
  20. package/src/api/endpoints/search-status.ts +55 -0
  21. package/src/api/endpoints/search.ts +104 -0
  22. package/src/api/endpoints/semantic-search.ts +120 -0
  23. package/src/api/endpoints/text-search.ts +63 -0
  24. package/src/api/endpoints/validate.ts +34 -0
  25. package/src/api/helpers.ts +97 -0
  26. package/src/base-model.ts +12 -0
  27. package/src/cli/commands/action.ts +82 -44
  28. package/src/cli/commands/console.ts +124 -0
  29. package/src/cli/commands/create.ts +179 -53
  30. package/src/cli/commands/embed.ts +323 -0
  31. package/src/cli/commands/export.ts +58 -24
  32. package/src/cli/commands/extract.ts +174 -0
  33. package/src/cli/commands/help.ts +81 -0
  34. package/src/cli/commands/index.ts +17 -0
  35. package/src/cli/commands/init.ts +72 -48
  36. package/src/cli/commands/inspect.ts +56 -46
  37. package/src/cli/commands/mcp.ts +1219 -0
  38. package/src/cli/commands/search.ts +285 -0
  39. package/src/cli/commands/serve.ts +348 -0
  40. package/src/cli/commands/summary.ts +60 -0
  41. package/src/cli/commands/teach.ts +86 -0
  42. package/src/cli/commands/text-search.ts +134 -0
  43. package/src/cli/commands/validate.ts +126 -64
  44. package/src/cli/index.ts +88 -19
  45. package/src/cli/load-collection.ts +144 -17
  46. package/src/cli/registry.ts +28 -0
  47. package/src/collection.ts +361 -10
  48. package/src/define-model.ts +101 -4
  49. package/src/document.ts +47 -1
  50. package/src/extract-sections.ts +1 -1
  51. package/src/index.ts +14 -2
  52. package/src/model-instance.ts +35 -5
  53. package/src/node-shortcuts.ts +1 -1
  54. package/src/query/collection-query.ts +96 -9
  55. package/src/query/index.ts +7 -0
  56. package/src/query/query-dsl.ts +259 -0
  57. package/src/relationships/has-many.ts +39 -0
  58. package/src/relationships/index.ts +7 -2
  59. package/src/section.ts +2 -0
  60. package/src/types.ts +23 -1
  61. package/src/utils/index.ts +1 -0
  62. package/src/utils/match-pattern.ts +65 -0
  63. package/src/validator.ts +18 -1
  64. package/bun.lock +0 -473
  65. package/examples/sdlc-queries.ts +0 -161
  66. package/scripts/examples/01-collection-setup.ts +0 -46
  67. package/scripts/examples/02-querying.ts +0 -67
  68. package/scripts/examples/03-sections.ts +0 -36
  69. package/scripts/examples/04-relationships.ts +0 -54
  70. package/scripts/examples/05-document-api.ts +0 -54
  71. package/scripts/examples/06-extract-sections.ts +0 -55
  72. package/scripts/examples/07-validation.ts +0 -46
  73. package/scripts/examples/08-serialization.ts +0 -51
  74. package/scripts/examples/lib/format.ts +0 -87
  75. package/scripts/examples/lib/setup.ts +0 -21
  76. package/scripts/examples/run-all.ts +0 -43
  77. package/showcases/national-parks/models.ts +0 -74
  78. package/showcases/national-parks/parks/acadia.mdx +0 -40
  79. package/showcases/national-parks/parks/yosemite.mdx +0 -44
  80. package/showcases/national-parks/parks/zion.mdx +0 -44
  81. package/showcases/national-parks/queries.ts +0 -103
  82. package/showcases/national-parks/trails/angels-landing.mdx +0 -19
  83. package/showcases/national-parks/trails/cathedral-lakes.mdx +0 -19
  84. package/showcases/national-parks/trails/half-dome.mdx +0 -19
  85. package/showcases/national-parks/trails/jordan-pond-path.mdx +0 -19
  86. package/showcases/national-parks/trails/mist-trail.mdx +0 -19
  87. package/showcases/national-parks/trails/observation-point.mdx +0 -19
  88. package/showcases/national-parks/trails/precipice-trail.mdx +0 -19
  89. package/showcases/national-parks/trails/the-narrows.mdx +0 -19
  90. package/showcases/recipes/cuisines/chinese.mdx +0 -28
  91. package/showcases/recipes/cuisines/italian.mdx +0 -32
  92. package/showcases/recipes/cuisines/mexican.mdx +0 -28
  93. package/showcases/recipes/models.ts +0 -77
  94. package/showcases/recipes/queries.ts +0 -89
  95. package/showcases/recipes/recipes/chinese/egg-fried-rice.mdx +0 -43
  96. package/showcases/recipes/recipes/chinese/mapo-tofu.mdx +0 -47
  97. package/showcases/recipes/recipes/italian/bruschetta.mdx +0 -38
  98. package/showcases/recipes/recipes/italian/cacio-e-pepe.mdx +0 -39
  99. package/showcases/recipes/recipes/italian/tiramisu.mdx +0 -43
  100. package/showcases/recipes/recipes/mexican/chicken-tinga.mdx +0 -44
  101. package/showcases/recipes/recipes/mexican/guacamole.mdx +0 -39
  102. package/showcases/vinyl-collection/albums/bitches-brew.mdx +0 -36
  103. package/showcases/vinyl-collection/albums/i-put-a-spell-on-you.mdx +0 -35
  104. package/showcases/vinyl-collection/albums/in-rainbows.mdx +0 -35
  105. package/showcases/vinyl-collection/albums/kind-of-blue.mdx +0 -32
  106. package/showcases/vinyl-collection/albums/ok-computer.mdx +0 -37
  107. package/showcases/vinyl-collection/albums/wild-is-the-wind.mdx +0 -35
  108. package/showcases/vinyl-collection/artists/miles-davis.mdx +0 -27
  109. package/showcases/vinyl-collection/artists/nina-simone.mdx +0 -26
  110. package/showcases/vinyl-collection/artists/radiohead.mdx +0 -27
  111. package/showcases/vinyl-collection/models.ts +0 -73
  112. package/showcases/vinyl-collection/queries.ts +0 -87
  113. package/test/ast-query.test.ts +0 -128
  114. package/test/collection.test.ts +0 -99
  115. package/test/define-model.test.ts +0 -78
  116. package/test/document.test.ts +0 -225
  117. package/test/extract-sections.test.ts +0 -356
  118. package/test/fixtures/sdlc/epics/authentication.mdx +0 -42
  119. package/test/fixtures/sdlc/epics/searching-and-browsing.mdx +0 -21
  120. package/test/fixtures/sdlc/models.ts +0 -89
  121. package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-register.mdx +0 -20
  122. package/test/helpers.ts +0 -21
  123. package/test/model-instance.test.ts +0 -197
  124. package/test/query.test.ts +0 -167
  125. package/test/relationships.test.ts +0 -84
  126. package/test/section.test.ts +0 -99
  127. package/test/table-of-contents.test.ts +0 -91
  128. package/test/validator.test.ts +0 -62
  129. package/tsconfig.json +0 -18
  130. package/vitest.config.ts +0 -11
@@ -1,356 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { toString } from "mdast-util-to-string";
3
- import type { Heading } from "mdast";
4
- import { Collection } from "../src/collection";
5
- import { parse, extractSections } from "../src/index";
6
- import { createTestCollection, FIXTURES_PATH } from "./helpers";
7
- import path from "path";
8
-
9
- let collection: Collection;
10
-
11
- beforeEach(async () => {
12
- collection = await createTestCollection();
13
- });
14
-
15
- describe("extractSections", () => {
16
- describe("grouped mode (default)", () => {
17
- it("groups sections under source document titles", () => {
18
- const doc1 = collection.document("epics/authentication");
19
- const doc2 = collection.document("epics/searching-and-browsing");
20
-
21
- const combined = extractSections([
22
- { source: doc1, sections: "Stories" },
23
- { source: doc2, sections: "Stories" },
24
- ]);
25
-
26
- const headings = combined.astQuery.selectAll("heading") as Heading[];
27
-
28
- // Source titles become h1
29
- expect(headings[0].depth).toBe(1);
30
- expect(toString(headings[0])).toBe("Authentication");
31
-
32
- // "Stories" section headings become h2
33
- expect(headings[1].depth).toBe(2);
34
- expect(toString(headings[1])).toBe("Stories");
35
-
36
- // Sub-headings within Stories shift accordingly
37
- expect(headings[2].depth).toBe(3);
38
- expect(toString(headings[2])).toBe("A User should be able to register.");
39
- });
40
-
41
- it("with title, nests source titles under it", () => {
42
- const doc1 = collection.document("epics/authentication");
43
- const doc2 = collection.document("epics/searching-and-browsing");
44
-
45
- const combined = extractSections(
46
- [
47
- { source: doc1, sections: "Stories" },
48
- { source: doc2, sections: "Stories" },
49
- ],
50
- { title: "All Stories" },
51
- );
52
-
53
- const headings = combined.astQuery.selectAll("heading") as Heading[];
54
-
55
- // Title is h1
56
- expect(headings[0].depth).toBe(1);
57
- expect(toString(headings[0])).toBe("All Stories");
58
-
59
- // Source titles become h2
60
- expect(headings[1].depth).toBe(2);
61
- expect(toString(headings[1])).toBe("Authentication");
62
-
63
- // Section headings become h3
64
- expect(headings[2].depth).toBe(3);
65
- expect(toString(headings[2])).toBe("Stories");
66
-
67
- // Sub-headings shift to h4
68
- expect(headings[3].depth).toBe(4);
69
- expect(toString(headings[3])).toBe("A User should be able to register.");
70
- });
71
-
72
- it("extracts multiple sections from one source", () => {
73
- const doc = collection.document("epics/authentication");
74
-
75
- // The Authentication epic has "Stories" as a section.
76
- // Within that, individual stories have "Acceptance Criteria" and "Mockups".
77
- // Let's extract the top-level "Stories" section.
78
- const combined = extractSections([
79
- { source: doc, sections: "Stories" },
80
- ]);
81
-
82
- const headings = combined.astQuery.selectAll("heading") as Heading[];
83
-
84
- // Source title is h1
85
- expect(toString(headings[0])).toBe("Authentication");
86
- expect(headings[0].depth).toBe(1);
87
-
88
- // "Stories" is h2
89
- expect(toString(headings[1])).toBe("Stories");
90
- expect(headings[1].depth).toBe(2);
91
- });
92
- });
93
-
94
- describe("flat mode", () => {
95
- it("places sections sequentially without source grouping", () => {
96
- const doc1 = collection.document("epics/authentication");
97
- const doc2 = collection.document("epics/searching-and-browsing");
98
-
99
- const combined = extractSections(
100
- [
101
- { source: doc1, sections: "Stories" },
102
- { source: doc2, sections: "Stories" },
103
- ],
104
- { mode: "flat" },
105
- );
106
-
107
- const headings = combined.astQuery.selectAll("heading") as Heading[];
108
-
109
- // Both "Stories" sections become h1 (no source grouping)
110
- expect(headings[0].depth).toBe(1);
111
- expect(toString(headings[0])).toBe("Stories");
112
-
113
- // Sub-headings shift accordingly
114
- expect(headings[1].depth).toBe(2);
115
- expect(toString(headings[1])).toBe("A User should be able to register.");
116
- });
117
-
118
- it("with title, sections start at h2", () => {
119
- const doc1 = collection.document("epics/authentication");
120
- const doc2 = collection.document("epics/searching-and-browsing");
121
-
122
- const combined = extractSections(
123
- [
124
- { source: doc1, sections: "Stories" },
125
- { source: doc2, sections: "Stories" },
126
- ],
127
- { mode: "flat", title: "Combined Stories" },
128
- );
129
-
130
- const headings = combined.astQuery.selectAll("heading") as Heading[];
131
-
132
- // Title is h1
133
- expect(headings[0].depth).toBe(1);
134
- expect(toString(headings[0])).toBe("Combined Stories");
135
-
136
- // Section headings are h2
137
- expect(headings[1].depth).toBe(2);
138
- expect(toString(headings[1])).toBe("Stories");
139
-
140
- // Sub-headings are h3
141
- expect(headings[2].depth).toBe(3);
142
- });
143
- });
144
-
145
- describe("works with ParsedDocument", () => {
146
- it("accepts ParsedDocument as source", async () => {
147
- const parsed = await parse(
148
- path.join(FIXTURES_PATH, "epics/authentication.mdx"),
149
- );
150
-
151
- const combined = extractSections([
152
- { source: parsed, sections: "Stories" },
153
- ]);
154
-
155
- const headings = combined.astQuery.selectAll("heading") as Heading[];
156
- expect(toString(headings[0])).toBe("Authentication");
157
- expect(headings[0].depth).toBe(1);
158
- });
159
-
160
- it("accepts mixed Document and ParsedDocument sources", async () => {
161
- const doc = collection.document("epics/authentication");
162
- const parsed = await parse(
163
- path.join(FIXTURES_PATH, "epics/searching-and-browsing.mdx"),
164
- );
165
-
166
- const combined = extractSections([
167
- { source: doc, sections: "Stories" },
168
- { source: parsed, sections: "Stories" },
169
- ]);
170
-
171
- const headings = combined.astQuery.selectAll("heading") as Heading[];
172
-
173
- // Both source titles present
174
- const titles = headings
175
- .filter((h) => h.depth === 1)
176
- .map((h) => toString(h));
177
- expect(titles).toContain("Authentication");
178
- expect(titles).toContain("Searching And Browsing");
179
- });
180
- });
181
-
182
- describe("onMissing", () => {
183
- it("skips missing sections by default", () => {
184
- const doc = collection.document("epics/authentication");
185
-
186
- const combined = extractSections([
187
- { source: doc, sections: "Nonexistent Section" },
188
- ]);
189
-
190
- // Should have only the source title heading, no section content
191
- const headings = combined.astQuery.selectAll("heading") as Heading[];
192
- expect(headings).toHaveLength(1);
193
- expect(toString(headings[0])).toBe("Authentication");
194
- });
195
-
196
- it("throws on missing section when onMissing is 'throw'", () => {
197
- const doc = collection.document("epics/authentication");
198
-
199
- expect(() =>
200
- extractSections(
201
- [{ source: doc, sections: "Nonexistent Section" }],
202
- { onMissing: "throw" },
203
- ),
204
- ).toThrow("Heading not found");
205
- });
206
- });
207
-
208
- describe("edge cases", () => {
209
- it("returns empty document for empty entries", () => {
210
- const combined = extractSections([]);
211
-
212
- expect(combined.content).toBe("");
213
- expect(combined.ast.children).toHaveLength(0);
214
- });
215
-
216
- it("returns document with only title for empty entries with title", () => {
217
- const combined = extractSections([], { title: "Empty Doc" });
218
-
219
- const headings = combined.astQuery.selectAll("heading") as Heading[];
220
- expect(headings).toHaveLength(1);
221
- expect(toString(headings[0])).toBe("Empty Doc");
222
- });
223
-
224
- it("clamps heading depths to max 6", async () => {
225
- // Create a document with deeply nested headings (h4+ subsections)
226
- // In grouped mode with title, h4 sections would shift by +3 → h7, should clamp to h6
227
- const deepDoc = await parse(
228
- "# Doc\n## Section\n### Sub\n#### Deep\n##### Deeper\n###### Deepest\n\nContent here.",
229
- );
230
-
231
- const combined = extractSections(
232
- [{ source: deepDoc, sections: "Section" }],
233
- { title: "Wrapper" },
234
- );
235
-
236
- const headings = combined.astQuery.selectAll("heading") as Heading[];
237
- // All heading depths should be <= 6
238
- for (const h of headings) {
239
- expect(h.depth).toBeLessThanOrEqual(6);
240
- expect(h.depth).toBeGreaterThanOrEqual(1);
241
- }
242
- });
243
-
244
- it("handles source with no title using (Untitled)", async () => {
245
- // A document with no heading — just content
246
- const noTitleDoc = await parse("## Section\n\nSome content.");
247
-
248
- const combined = extractSections([
249
- { source: noTitleDoc, sections: "Section" },
250
- ]);
251
-
252
- const headings = combined.astQuery.selectAll("heading") as Heading[];
253
- // In grouped mode, source title should be "(Untitled)" since doc title comes from first heading
254
- // But actually, parse() sets title from first heading which is "Section" here
255
- // Let's use a truly headingless doc
256
- const bareDoc = await parse("Just a paragraph, no headings at all.");
257
- const combined2 = extractSections([
258
- { source: bareDoc, sections: "Nonexistent" },
259
- ]);
260
- const headings2 = combined2.astQuery.selectAll("heading") as Heading[];
261
- expect(toString(headings2[0])).toBe("(Untitled)");
262
- });
263
- });
264
-
265
- describe("returned ParsedDocument", () => {
266
- it("has working content and stringify", () => {
267
- const doc = collection.document("epics/authentication");
268
-
269
- const combined = extractSections(
270
- [{ source: doc, sections: "Stories" }],
271
- { title: "Test" },
272
- );
273
-
274
- expect(combined.content).toContain("# Test");
275
- expect(combined.stringify()).toBe(combined.content);
276
- });
277
-
278
- it("has working extractSection", () => {
279
- const doc = collection.document("epics/authentication");
280
-
281
- const combined = extractSections([
282
- { source: doc, sections: "Stories" },
283
- ]);
284
-
285
- // The combined doc should have "Stories" as an h2 under "Authentication"
286
- const section = combined.extractSection("Stories");
287
- expect(section.length).toBeGreaterThan(0);
288
- expect(section[0].type).toBe("heading");
289
- });
290
-
291
- it("has working querySection", () => {
292
- const doc = collection.document("epics/authentication");
293
-
294
- const combined = extractSections([
295
- { source: doc, sections: "Stories" },
296
- ]);
297
-
298
- const query = combined.querySection("Stories");
299
- const subHeadings = query.selectAll("heading") as Heading[];
300
- expect(subHeadings.length).toBeGreaterThan(0);
301
- });
302
-
303
- it("has working nodes accessor", () => {
304
- const doc = collection.document("epics/authentication");
305
-
306
- const combined = extractSections(
307
- [{ source: doc, sections: "Stories" }],
308
- { title: "Test" },
309
- );
310
-
311
- expect(combined.nodes).toBeDefined();
312
- expect(combined.title).toBe("Test");
313
- });
314
-
315
- it("has empty meta", () => {
316
- const doc = collection.document("epics/authentication");
317
-
318
- const combined = extractSections([
319
- { source: doc, sections: "Stories" },
320
- ]);
321
-
322
- expect(combined.meta).toEqual({});
323
- });
324
- });
325
-
326
- describe("sections as array", () => {
327
- it("accepts string for single section", () => {
328
- const doc = collection.document("epics/authentication");
329
-
330
- const combined = extractSections([
331
- { source: doc, sections: "Stories" },
332
- ]);
333
-
334
- const headings = combined.astQuery.selectAll("heading") as Heading[];
335
- expect(headings.length).toBeGreaterThan(1);
336
- });
337
-
338
- it("accepts array for multiple sections", async () => {
339
- // Use a parsed doc with distinct top-level sections
340
- const doc = await parse(
341
- "# My Doc\n\n## Section A\n\nContent A.\n\n## Section B\n\nContent B.\n\n## Section C\n\nContent C.",
342
- );
343
-
344
- const combined = extractSections([
345
- { source: doc, sections: ["Section A", "Section B"] },
346
- ]);
347
-
348
- const headings = combined.astQuery.selectAll("heading") as Heading[];
349
- const headingTexts = headings.map((h) => toString(h));
350
-
351
- expect(headingTexts).toContain("Section A");
352
- expect(headingTexts).toContain("Section B");
353
- expect(headingTexts).not.toContain("Section C");
354
- });
355
- });
356
- });
@@ -1,42 +0,0 @@
1
- ---
2
- priority: high
3
- status: created
4
- ---
5
-
6
- # Authentication
7
-
8
- The Authentication stories cover users logging in and out of the application, as well as the roles and permissions granted to these users and how they are enforced in the application.
9
-
10
- ## Stories
11
-
12
- ### A User should be able to register.
13
-
14
- As a User I would like to register so that I can use the application.
15
-
16
- #### Acceptance Criteria
17
-
18
- - A user can visit the signup form, supply their name, email, and password
19
- - The signup form should validate the user's information and supply errors
20
- - The user should receive a confirmation email
21
- - The user should show up in our database as confirmed after clicking the confirmation link
22
-
23
- #### Mockups
24
-
25
- - [Invision: Registration Form](https://invisionapp.com)
26
- - [Invision: Registration Form Error State](https://invisionapp.com)
27
-
28
- ### A User should be able to login.
29
-
30
- As a User I would like to login so that I can use the application.
31
-
32
- #### Acceptance Criteria
33
-
34
- - A user can visit the signup form, supply their name, email, and password
35
- - The signup form should validate the user's information and supply errors
36
- - The user should receive a confirmation email
37
- - The user should show up in our database as confirmed after clicking the confirmation link
38
-
39
- #### Mockups
40
-
41
- - [Invision: Login Form](https://invisionapp.com)
42
- - [Invision: Login Form Error State ](https://invisionapp.com)
@@ -1,21 +0,0 @@
1
- ---
2
- status: created
3
- ---
4
-
5
- # Searching And Browsing
6
-
7
- This epic covers the stories related to searching for a specific item.
8
-
9
- ## Stories
10
-
11
- ### Searching for a product by category
12
-
13
- As a user I want to be able to search for a product by category so that I can find the right product for me.
14
-
15
- ### Searching for a product by manufacturer
16
-
17
- As a user I want to be able to search for a product by manufacturer so that I can find the right product for me.
18
-
19
- ### Searching for a product by name
20
-
21
- As a user I want to be able to search for a product by name so that I can find the right product for me.
@@ -1,89 +0,0 @@
1
- import {
2
- defineModel,
3
- section,
4
- hasMany,
5
- belongsTo,
6
- z,
7
- type ModelDefinition,
8
- type HasManyDefinition,
9
- type BelongsToDefinition,
10
- type SectionDefinition,
11
- } from "../../../src/index";
12
- import { toString } from "mdast-util-to-string";
13
-
14
- const epicMeta = z.object({
15
- priority: z.enum(["low", "medium", "high"]).optional(),
16
- status: z
17
- .enum(["created", "in-progress", "complete"])
18
- .default("created"),
19
- });
20
-
21
- const storyMeta = z.object({
22
- status: z
23
- .enum(["created", "in-progress", "complete"])
24
- .default("created"),
25
- epic: z.string().optional(),
26
- });
27
-
28
- /** Explicit type for Epic so circular Epic↔Story inference doesn’t collapse to never */
29
- export type EpicDef = ModelDefinition<
30
- "Epic",
31
- typeof epicMeta,
32
- Record<string, never>,
33
- { stories: HasManyDefinition<StoryDef> },
34
- { isComplete: (self: any) => boolean }
35
- >;
36
-
37
- /** Explicit type for Story so circular Epic↔Story inference doesn’t collapse to never */
38
- export type StoryDef = ModelDefinition<
39
- "Story",
40
- typeof storyMeta,
41
- Record<string, SectionDefinition<any>>,
42
- { epic: BelongsToDefinition<EpicDef> },
43
- { isComplete: (self: any) => boolean }
44
- >;
45
-
46
- export const Epic: EpicDef = defineModel("Epic", {
47
- prefix: "epics",
48
- meta: epicMeta,
49
- relationships: {
50
- stories: hasMany(() => Story, {
51
- heading: "Stories",
52
- }),
53
- },
54
- computed: {
55
- isComplete: (self: any) => self.meta.status === "complete",
56
- },
57
- defaults: {
58
- status: "created",
59
- },
60
- });
61
-
62
- export const Story: StoryDef = defineModel("Story", {
63
- prefix: "stories",
64
- meta: storyMeta,
65
- sections: {
66
- acceptanceCriteria: section("Acceptance Criteria", {
67
- extract: (query) =>
68
- query.selectAll("listItem").map((n) => toString(n)),
69
- schema: z.array(z.string()),
70
- }),
71
- mockups: section("Mockups", {
72
- extract: (query) =>
73
- Object.fromEntries(
74
- query
75
- .selectAll("link")
76
- .map((l: any) => [toString(l), l.url])
77
- ),
78
- schema: z.record(z.string(), z.string()),
79
- }),
80
- },
81
- relationships: {
82
- epic: belongsTo(() => Epic, {
83
- foreignKey: (doc) => doc.meta.epic as string,
84
- }),
85
- },
86
- computed: {
87
- isComplete: (self: any) => self.meta.status === "complete",
88
- },
89
- });
@@ -1,20 +0,0 @@
1
- ---
2
- status: created
3
- epic: authentication
4
- ---
5
-
6
- # A User should be able to register.
7
-
8
- As a User I would like to register so that I can use the application.
9
-
10
- ## Acceptance Criteria
11
-
12
- - A user can visit the signup form, supply their name, email, and password
13
- - The signup form should validate the user's information and supply errors
14
- - The user should receive a confirmation email
15
- - The user should show up in our database as confirmed after clicking the confirmation link
16
-
17
- ## Mockups
18
-
19
- - [Invision: Registration Form](https://invisionapp.com)
20
- - [Invision: Registration Form Error State](https://invisionapp.com)
package/test/helpers.ts DELETED
@@ -1,21 +0,0 @@
1
- import path from "path";
2
- import { Collection } from "../src/collection";
3
- import { Epic, Story } from "./fixtures/sdlc/models";
4
-
5
- const dir = import.meta.dirname ?? new URL(".", import.meta.url).pathname;
6
-
7
- export const FIXTURES_PATH = path.resolve(
8
- dir,
9
- "fixtures/sdlc"
10
- );
11
-
12
- export async function createTestCollection(): Promise<Collection> {
13
- const collection = new Collection({
14
- rootPath: FIXTURES_PATH,
15
- name: "test-sdlc",
16
- });
17
- collection.register(Epic);
18
- collection.register(Story);
19
- await collection.load();
20
- return collection;
21
- }