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.
Files changed (82) hide show
  1. package/package.json +1 -1
  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,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
- ```