contentbase 0.0.4 → 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.
- package/package.json +1 -1
- package/.mcp.json +0 -9
- package/MCP-DESCRIPTIONS-REVIEW.md +0 -122
- package/bun.lock +0 -512
- package/dist/cnotes +0 -0
- package/examples/sdlc-queries.ts +0 -161
- package/public/web-demo/index.html +0 -813
- package/scripts/examples/01-collection-setup.ts +0 -46
- package/scripts/examples/02-querying.ts +0 -67
- package/scripts/examples/03-sections.ts +0 -36
- package/scripts/examples/04-relationships.ts +0 -54
- package/scripts/examples/05-document-api.ts +0 -54
- package/scripts/examples/06-extract-sections.ts +0 -55
- package/scripts/examples/07-validation.ts +0 -46
- package/scripts/examples/08-serialization.ts +0 -51
- package/scripts/examples/lib/format.ts +0 -87
- package/scripts/examples/lib/setup.ts +0 -21
- package/scripts/examples/run-all.ts +0 -43
- package/showcases/national-parks/models.ts +0 -74
- package/showcases/national-parks/parks/acadia.mdx +0 -40
- package/showcases/national-parks/parks/yosemite.mdx +0 -44
- package/showcases/national-parks/parks/zion.mdx +0 -44
- package/showcases/national-parks/queries.ts +0 -103
- package/showcases/national-parks/trails/angels-landing.mdx +0 -19
- package/showcases/national-parks/trails/cathedral-lakes.mdx +0 -19
- package/showcases/national-parks/trails/half-dome.mdx +0 -19
- package/showcases/national-parks/trails/jordan-pond-path.mdx +0 -19
- package/showcases/national-parks/trails/mist-trail.mdx +0 -19
- package/showcases/national-parks/trails/observation-point.mdx +0 -19
- package/showcases/national-parks/trails/precipice-trail.mdx +0 -19
- package/showcases/national-parks/trails/the-narrows.mdx +0 -19
- package/showcases/node_modules/.cache/.repl_history +0 -3
- package/showcases/recipes/cuisines/chinese.mdx +0 -28
- package/showcases/recipes/cuisines/italian.mdx +0 -32
- package/showcases/recipes/cuisines/mexican.mdx +0 -28
- package/showcases/recipes/models.ts +0 -77
- package/showcases/recipes/queries.ts +0 -89
- package/showcases/recipes/recipes/chinese/egg-fried-rice.mdx +0 -43
- package/showcases/recipes/recipes/chinese/mapo-tofu.mdx +0 -47
- package/showcases/recipes/recipes/italian/bruschetta.mdx +0 -38
- package/showcases/recipes/recipes/italian/cacio-e-pepe.mdx +0 -39
- package/showcases/recipes/recipes/italian/tiramisu.mdx +0 -43
- package/showcases/recipes/recipes/mexican/chicken-tinga.mdx +0 -44
- package/showcases/recipes/recipes/mexican/guacamole.mdx +0 -39
- package/showcases/vinyl-collection/albums/bitches-brew.mdx +0 -36
- package/showcases/vinyl-collection/albums/i-put-a-spell-on-you.mdx +0 -35
- package/showcases/vinyl-collection/albums/in-rainbows.mdx +0 -35
- package/showcases/vinyl-collection/albums/kind-of-blue.mdx +0 -32
- package/showcases/vinyl-collection/albums/ok-computer.mdx +0 -37
- package/showcases/vinyl-collection/albums/wild-is-the-wind.mdx +0 -35
- package/showcases/vinyl-collection/artists/miles-davis.mdx +0 -27
- package/showcases/vinyl-collection/artists/nina-simone.mdx +0 -26
- package/showcases/vinyl-collection/artists/radiohead.mdx +0 -27
- package/showcases/vinyl-collection/models.ts +0 -73
- package/showcases/vinyl-collection/queries.ts +0 -87
- package/src/__tests__/semantic-search.integration.test.ts +0 -284
- package/test/ast-query.test.ts +0 -128
- package/test/collection.test.ts +0 -215
- package/test/define-model.test.ts +0 -78
- package/test/document.test.ts +0 -225
- package/test/extract-sections.test.ts +0 -356
- package/test/fixtures/sdlc/MODELS.md +0 -106
- package/test/fixtures/sdlc/SKILL.md +0 -404
- package/test/fixtures/sdlc/epics/authentication.mdx +0 -42
- package/test/fixtures/sdlc/epics/searching-and-browsing.mdx +0 -21
- package/test/fixtures/sdlc/index.ts +0 -9
- package/test/fixtures/sdlc/models.ts +0 -91
- package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-login.mdx +0 -20
- package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-register.mdx +0 -20
- package/test/fixtures/sdlc/templates/epic.md +0 -23
- package/test/fixtures/sdlc/templates/story.md +0 -19
- package/test/helpers.ts +0 -21
- package/test/model-instance.test.ts +0 -197
- package/test/pattern.test.ts +0 -191
- package/test/query-dsl.test.ts +0 -431
- package/test/query.test.ts +0 -196
- package/test/relationships.test.ts +0 -214
- package/test/section.test.ts +0 -160
- package/test/table-of-contents.test.ts +0 -135
- package/test/validator.test.ts +0 -62
- package/tsconfig.json +0 -18
- 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,106 +0,0 @@
|
|
|
1
|
-
# Models
|
|
2
|
-
|
|
3
|
-
Models define the structure of markdown documents in this collection. Each document is a markdown file with YAML frontmatter (metadata attributes) and a heading-based structure (sections). Models specify the expected frontmatter fields via a schema, named sections that map to `##` headings in the document body, relationships to other models, and computed properties derived at query time.
|
|
4
|
-
|
|
5
|
-
## Epics
|
|
6
|
-
|
|
7
|
-
**Prefix:** `epics`
|
|
8
|
-
|
|
9
|
-
### Attributes
|
|
10
|
-
|
|
11
|
-
| Field | Type | Required | Default | Description |
|
|
12
|
-
|-------|------|----------|---------|-------------|
|
|
13
|
-
| priority | enum(`low`, `medium`, `high`) | optional | — | Importance level for prioritization |
|
|
14
|
-
| status | enum(`created`, `in-progress`, `complete`) | optional | `"created"` | Current workflow state |
|
|
15
|
-
|
|
16
|
-
### Relationships
|
|
17
|
-
|
|
18
|
-
| Name | Type | Target |
|
|
19
|
-
|------|------|--------|
|
|
20
|
-
| stories | hasMany | Story |
|
|
21
|
-
|
|
22
|
-
### Computed Properties
|
|
23
|
-
|
|
24
|
-
- `isComplete`
|
|
25
|
-
|
|
26
|
-
### Example
|
|
27
|
-
|
|
28
|
-
```markdown
|
|
29
|
-
---
|
|
30
|
-
priority: medium
|
|
31
|
-
status: created
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
# Epic Title
|
|
35
|
-
|
|
36
|
-
A brief description of this epic and its goals.
|
|
37
|
-
|
|
38
|
-
## Stories
|
|
39
|
-
|
|
40
|
-
### Story Title
|
|
41
|
-
|
|
42
|
-
A brief description of this story.
|
|
43
|
-
|
|
44
|
-
#### Acceptance Criteria
|
|
45
|
-
|
|
46
|
-
- First acceptance criterion
|
|
47
|
-
- Second acceptance criterion
|
|
48
|
-
|
|
49
|
-
#### Mockups
|
|
50
|
-
|
|
51
|
-
[Wireframe](https://example.com/wireframe)
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## Stories
|
|
57
|
-
|
|
58
|
-
**Prefix:** `stories`
|
|
59
|
-
|
|
60
|
-
### Attributes
|
|
61
|
-
|
|
62
|
-
| Field | Type | Required | Default | Description |
|
|
63
|
-
|-------|------|----------|---------|-------------|
|
|
64
|
-
| status | enum(`created`, `in-progress`, `complete`) | optional | `"created"` | Current workflow state |
|
|
65
|
-
| epic | string | optional | — | Slug of the parent epic |
|
|
66
|
-
|
|
67
|
-
### Sections
|
|
68
|
-
|
|
69
|
-
| Name | Heading | Alternatives | Description |
|
|
70
|
-
|------|---------|--------------|-------------|
|
|
71
|
-
| acceptanceCriteria | Acceptance Criteria | — | List of acceptance criteria as plain text strings |
|
|
72
|
-
| mockups | Mockups | — | Map of mockup label to URL |
|
|
73
|
-
|
|
74
|
-
### Relationships
|
|
75
|
-
|
|
76
|
-
| Name | Type | Target |
|
|
77
|
-
|------|------|--------|
|
|
78
|
-
| epic | belongsTo | Epic |
|
|
79
|
-
|
|
80
|
-
### Computed Properties
|
|
81
|
-
|
|
82
|
-
- `isComplete`
|
|
83
|
-
|
|
84
|
-
### Example
|
|
85
|
-
|
|
86
|
-
```markdown
|
|
87
|
-
---
|
|
88
|
-
status: created
|
|
89
|
-
epic: epic-slug
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
# Story Title
|
|
93
|
-
|
|
94
|
-
A brief description of what this story accomplishes.
|
|
95
|
-
|
|
96
|
-
## Acceptance Criteria
|
|
97
|
-
|
|
98
|
-
- First acceptance criterion
|
|
99
|
-
- Second acceptance criterion
|
|
100
|
-
- Third acceptance criterion
|
|
101
|
-
|
|
102
|
-
## Mockups
|
|
103
|
-
|
|
104
|
-
[Main View](https://example.com/main-view)
|
|
105
|
-
[Detail View](https://example.com/detail-view)
|
|
106
|
-
```
|