kitfly 0.1.2

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 (62) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +136 -0
  4. package/VERSION +1 -0
  5. package/package.json +63 -0
  6. package/schemas/README.md +32 -0
  7. package/schemas/site.schema.json +5 -0
  8. package/schemas/theme.schema.json +5 -0
  9. package/schemas/v0/site.schema.json +172 -0
  10. package/schemas/v0/theme.schema.json +210 -0
  11. package/scripts/build-all.ts +121 -0
  12. package/scripts/build.ts +601 -0
  13. package/scripts/bundle.ts +781 -0
  14. package/scripts/dev.ts +777 -0
  15. package/scripts/generate-checksums.sh +78 -0
  16. package/scripts/release/export-release-key.sh +28 -0
  17. package/scripts/release/release-guard-tag-version.sh +79 -0
  18. package/scripts/release/sign-release-assets.sh +123 -0
  19. package/scripts/release/upload-release-assets.sh +76 -0
  20. package/scripts/release/upload-release-provenance.sh +52 -0
  21. package/scripts/release/verify-public-key.sh +48 -0
  22. package/scripts/release/verify-signatures.sh +117 -0
  23. package/scripts/version-sync.ts +82 -0
  24. package/src/__tests__/build.test.ts +240 -0
  25. package/src/__tests__/bundle.test.ts +786 -0
  26. package/src/__tests__/cli.test.ts +706 -0
  27. package/src/__tests__/crucible.test.ts +1043 -0
  28. package/src/__tests__/engine.test.ts +157 -0
  29. package/src/__tests__/init.test.ts +450 -0
  30. package/src/__tests__/pipeline.test.ts +1087 -0
  31. package/src/__tests__/productbook.test.ts +1206 -0
  32. package/src/__tests__/runbook.test.ts +974 -0
  33. package/src/__tests__/server-registry.test.ts +1251 -0
  34. package/src/__tests__/servicebook.test.ts +1248 -0
  35. package/src/__tests__/shared.test.ts +2005 -0
  36. package/src/__tests__/styles.test.ts +14 -0
  37. package/src/__tests__/theme-schema.test.ts +47 -0
  38. package/src/__tests__/theme.test.ts +554 -0
  39. package/src/cli.ts +582 -0
  40. package/src/commands/init.ts +92 -0
  41. package/src/commands/update.ts +444 -0
  42. package/src/engine.ts +20 -0
  43. package/src/logger.ts +15 -0
  44. package/src/migrations/0000_schema_versioning.ts +67 -0
  45. package/src/migrations/0001_server_port.ts +52 -0
  46. package/src/migrations/0002_brand_logo.ts +49 -0
  47. package/src/migrations/index.ts +26 -0
  48. package/src/migrations/schema.ts +24 -0
  49. package/src/server-registry.ts +405 -0
  50. package/src/shared.ts +1239 -0
  51. package/src/site/styles.css +931 -0
  52. package/src/site/template.html +193 -0
  53. package/src/templates/crucible.ts +1163 -0
  54. package/src/templates/driver.ts +876 -0
  55. package/src/templates/handbook.ts +339 -0
  56. package/src/templates/minimal.ts +139 -0
  57. package/src/templates/pipeline.ts +966 -0
  58. package/src/templates/productbook.ts +1032 -0
  59. package/src/templates/runbook.ts +829 -0
  60. package/src/templates/schema.ts +119 -0
  61. package/src/templates/servicebook.ts +1242 -0
  62. package/src/theme.ts +245 -0
@@ -0,0 +1,1248 @@
1
+ /**
2
+ * Tests for the Servicebook template definition
3
+ *
4
+ * Covers: src/templates/servicebook.ts
5
+ * Strategy: import the template directly, verify structure and generated content
6
+ */
7
+
8
+ import { describe, expect, it } from "vitest";
9
+ import type { BrandingConfig, TemplateContext, TemplateFile } from "../templates/schema.ts";
10
+ import { servicebook } from "../templates/servicebook.ts";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Helpers
14
+ // ---------------------------------------------------------------------------
15
+
16
+ /** Build a standard TemplateContext for testing */
17
+ function makeCtx(overrides?: Partial<TemplateContext>): TemplateContext {
18
+ const branding: BrandingConfig = {
19
+ siteName: "Test Servicebook",
20
+ brandName: "Acme Consulting",
21
+ brandUrl: "https://acme.example.com",
22
+ primaryColor: "#2563eb",
23
+ footerText: "Footer text",
24
+ };
25
+ return {
26
+ name: "test-servicebook",
27
+ branding,
28
+ template: servicebook,
29
+ year: 2026,
30
+ ...overrides,
31
+ };
32
+ }
33
+
34
+ /** Resolve the content of a TemplateFile to a string */
35
+ function resolveContent(file: TemplateFile, ctx: TemplateContext): string {
36
+ return typeof file.content === "function" ? file.content(ctx) : file.content;
37
+ }
38
+
39
+ /** Find a file entry by its path */
40
+ function findFile(path: string): TemplateFile {
41
+ const f = servicebook.files.find((entry) => entry.path === path);
42
+ if (!f) throw new Error(`Template file not found: ${path}`);
43
+ return f;
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Template Definition Structure
48
+ // ---------------------------------------------------------------------------
49
+
50
+ describe("servicebook template definition", () => {
51
+ it("has correct identity metadata", () => {
52
+ expect(servicebook.id).toBe("servicebook");
53
+ expect(servicebook.name).toBe("Servicebook");
54
+ expect(servicebook.version).toBe(1);
55
+ expect(servicebook.extends).toBe("minimal");
56
+ });
57
+
58
+ it("has a non-empty description", () => {
59
+ expect(servicebook.description).toBeTruthy();
60
+ expect(servicebook.description.length).toBeGreaterThan(10);
61
+ });
62
+
63
+ it("defines exactly six sections", () => {
64
+ expect(servicebook.sections).toHaveLength(6);
65
+ });
66
+
67
+ it("defines the expected section names", () => {
68
+ const names = servicebook.sections.map((s) => s.name);
69
+ expect(names).toEqual([
70
+ "Offerings",
71
+ "Methodology",
72
+ "Delivery",
73
+ "Verticals",
74
+ "Case Studies",
75
+ "Reference",
76
+ ]);
77
+ });
78
+
79
+ it("defines the expected section paths", () => {
80
+ const paths = servicebook.sections.map((s) => s.path);
81
+ expect(paths).toEqual([
82
+ "content/offerings",
83
+ "content/methodology",
84
+ "content/delivery",
85
+ "content/verticals",
86
+ "content/case-studies",
87
+ "content/reference",
88
+ ]);
89
+ });
90
+
91
+ it("every section has a description", () => {
92
+ for (const section of servicebook.sections) {
93
+ expect(section.description).toBeTruthy();
94
+ expect(typeof section.description).toBe("string");
95
+ }
96
+ });
97
+
98
+ it("defines a non-empty files array", () => {
99
+ expect(servicebook.files.length).toBeGreaterThan(0);
100
+ });
101
+
102
+ it("every file has a non-empty path", () => {
103
+ for (const file of servicebook.files) {
104
+ expect(file.path).toBeTruthy();
105
+ expect(typeof file.path).toBe("string");
106
+ }
107
+ });
108
+
109
+ it("every file has content (string or function)", () => {
110
+ for (const file of servicebook.files) {
111
+ expect(["string", "function"]).toContain(typeof file.content);
112
+ }
113
+ });
114
+
115
+ it("has no duplicate file paths", () => {
116
+ const paths = servicebook.files.map((f) => f.path);
117
+ const unique = new Set(paths);
118
+ expect(unique.size).toBe(paths.length);
119
+ });
120
+ });
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // site.yaml Generation
124
+ // ---------------------------------------------------------------------------
125
+
126
+ describe("servicebook site.yaml", () => {
127
+ const ctx = makeCtx();
128
+ const file = findFile("site.yaml");
129
+
130
+ it("exists in the file list", () => {
131
+ expect(file).toBeDefined();
132
+ });
133
+
134
+ it("includes the site title from context", () => {
135
+ const content = resolveContent(file, ctx);
136
+ expect(content).toContain('title: "Test Servicebook"');
137
+ });
138
+
139
+ it("includes the brand name from context", () => {
140
+ const content = resolveContent(file, ctx);
141
+ expect(content).toContain('name: "Acme Consulting"');
142
+ });
143
+
144
+ it("includes the brand url from context", () => {
145
+ const content = resolveContent(file, ctx);
146
+ expect(content).toContain('url: "https://acme.example.com"');
147
+ });
148
+
149
+ it("includes sections block with all six sections", () => {
150
+ const content = resolveContent(file, ctx);
151
+ expect(content).toContain("sections:");
152
+ expect(content).toContain('"Offerings"');
153
+ expect(content).toContain('"Methodology"');
154
+ expect(content).toContain('"Delivery"');
155
+ expect(content).toContain('"Verticals"');
156
+ expect(content).toContain('"Case Studies"');
157
+ expect(content).toContain('"Reference"');
158
+ });
159
+
160
+ it("includes section paths", () => {
161
+ const content = resolveContent(file, ctx);
162
+ expect(content).toContain('"content/offerings"');
163
+ expect(content).toContain('"content/methodology"');
164
+ expect(content).toContain('"content/delivery"');
165
+ expect(content).toContain('"content/verticals"');
166
+ expect(content).toContain('"content/case-studies"');
167
+ expect(content).toContain('"content/reference"');
168
+ });
169
+
170
+ it("sets home page to index.md", () => {
171
+ const content = resolveContent(file, ctx);
172
+ expect(content).toContain('home: "index.md"');
173
+ });
174
+
175
+ it("includes documentation link", () => {
176
+ const content = resolveContent(file, ctx);
177
+ expect(content).toContain("https://github.com/3leaps/kitfly");
178
+ });
179
+
180
+ it("responds to different branding values", () => {
181
+ const customCtx = makeCtx({
182
+ branding: {
183
+ siteName: "Widget Services",
184
+ brandName: "Widget Inc",
185
+ brandUrl: "/",
186
+ },
187
+ });
188
+ const content = resolveContent(file, customCtx);
189
+ expect(content).toContain('title: "Widget Services"');
190
+ expect(content).toContain('name: "Widget Inc"');
191
+ expect(content).toContain('url: "/"');
192
+ });
193
+ });
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // index.md Generation
197
+ // ---------------------------------------------------------------------------
198
+
199
+ describe("servicebook index.md", () => {
200
+ const ctx = makeCtx();
201
+ const file = findFile("index.md");
202
+
203
+ it("exists in the file list", () => {
204
+ expect(file).toBeDefined();
205
+ });
206
+
207
+ it("starts with YAML frontmatter", () => {
208
+ const content = resolveContent(file, ctx);
209
+ expect(content).toMatch(/^---\n/);
210
+ expect(content).toContain("title: Home");
211
+ });
212
+
213
+ it("includes site name in frontmatter description", () => {
214
+ const content = resolveContent(file, ctx);
215
+ expect(content).toContain("description: Test Servicebook - Professional Services Catalog");
216
+ });
217
+
218
+ it("includes site name in the H1 heading", () => {
219
+ const content = resolveContent(file, ctx);
220
+ expect(content).toContain("# Test Servicebook");
221
+ });
222
+
223
+ it("references brand name in body content", () => {
224
+ const content = resolveContent(file, ctx);
225
+ expect(content).toContain("Acme Consulting");
226
+ });
227
+
228
+ it("contains a service offerings table", () => {
229
+ const content = resolveContent(file, ctx);
230
+ expect(content).toContain("## Service Offerings");
231
+ expect(content).toContain("| Service | Engagement Model | Typical Duration | Status |");
232
+ expect(content).toContain("Assessment");
233
+ expect(content).toContain("Advisory");
234
+ expect(content).toContain("Implementation");
235
+ });
236
+
237
+ it("contains quick links to key sections", () => {
238
+ const content = resolveContent(file, ctx);
239
+ expect(content).toContain("## Quick Links");
240
+ expect(content).toContain("[Service Catalog](/content/offerings/overview)");
241
+ expect(content).toContain("[Our Methodology](/content/methodology/phases)");
242
+ expect(content).toContain("[Engagement Lifecycle](/content/delivery/engagement-lifecycle)");
243
+ expect(content).toContain("[Case Studies](/content/case-studies/)");
244
+ });
245
+
246
+ it("includes a last-updated date in ISO format", () => {
247
+ const content = resolveContent(file, ctx);
248
+ expect(content).toMatch(/\*Last updated: \d{4}-\d{2}-\d{2}\*/);
249
+ });
250
+ });
251
+
252
+ // ---------------------------------------------------------------------------
253
+ // Offerings Section Files
254
+ // ---------------------------------------------------------------------------
255
+
256
+ describe("servicebook offerings section", () => {
257
+ const ctx = makeCtx();
258
+
259
+ describe("overview.md", () => {
260
+ const file = findFile("content/offerings/overview.md");
261
+
262
+ it("exists in the file list", () => {
263
+ expect(file).toBeDefined();
264
+ });
265
+
266
+ it("has YAML frontmatter with title and description", () => {
267
+ const content = resolveContent(file, ctx);
268
+ expect(content).toMatch(/^---\n/);
269
+ expect(content).toContain("title: Service Catalog");
270
+ expect(content).toContain("description: Service offerings for Acme Consulting");
271
+ });
272
+
273
+ it("includes service tiers table", () => {
274
+ const content = resolveContent(file, ctx);
275
+ expect(content).toContain("## Service Tiers");
276
+ expect(content).toContain("| Tier | Description | Typical Client | Duration |");
277
+ expect(content).toContain("Assessment");
278
+ expect(content).toContain("Advisory");
279
+ expect(content).toContain("Implementation");
280
+ });
281
+
282
+ it("includes engagement models", () => {
283
+ const content = resolveContent(file, ctx);
284
+ expect(content).toContain("## Engagement Models");
285
+ expect(content).toContain("### Fixed-Scope");
286
+ expect(content).toContain("### Time & Materials");
287
+ expect(content).toContain("### Milestone-Based");
288
+ });
289
+
290
+ it("links to pricing models", () => {
291
+ const content = resolveContent(file, ctx);
292
+ expect(content).toContain("[Pricing Models](/content/reference/pricing-models)");
293
+ });
294
+
295
+ it("links to related pages", () => {
296
+ const content = resolveContent(file, ctx);
297
+ expect(content).toContain("[Methodology](/content/methodology/phases)");
298
+ expect(content).toContain("[Delivery Lifecycle](/content/delivery/engagement-lifecycle)");
299
+ });
300
+ });
301
+
302
+ describe("assess/overview.md", () => {
303
+ const file = findFile("content/offerings/assess/overview.md");
304
+
305
+ it("exists in the file list", () => {
306
+ expect(file).toBeDefined();
307
+ });
308
+
309
+ it("has YAML frontmatter", () => {
310
+ const content = resolveContent(file, ctx);
311
+ expect(content).toMatch(/^---\n/);
312
+ expect(content).toContain("title: Assessment Service");
313
+ });
314
+
315
+ it("describes the service", () => {
316
+ const content = resolveContent(file, ctx);
317
+ expect(content).toContain("## What It Is");
318
+ expect(content).toContain("## Client Problem");
319
+ });
320
+
321
+ it("includes deliverables table", () => {
322
+ const content = resolveContent(file, ctx);
323
+ expect(content).toContain("## Deliverables");
324
+ expect(content).toContain("| Deliverable | Format | Description |");
325
+ expect(content).toContain("Current-State Report");
326
+ expect(content).toContain("Gap Analysis");
327
+ expect(content).toContain("Recommendations");
328
+ expect(content).toContain("Roadmap");
329
+ });
330
+
331
+ it("includes timeline table", () => {
332
+ const content = resolveContent(file, ctx);
333
+ expect(content).toContain("## Timeline");
334
+ expect(content).toContain("| Phase | Duration | Activities |");
335
+ expect(content).toContain("Discovery");
336
+ expect(content).toContain("Analysis");
337
+ expect(content).toContain("Synthesis");
338
+ expect(content).toContain("Delivery");
339
+ });
340
+
341
+ it("links to related pages", () => {
342
+ const content = resolveContent(file, ctx);
343
+ expect(content).toContain("[Assessment Scoping](/content/offerings/assess/scoping)");
344
+ expect(content).toContain("[Deliverables](/content/offerings/assess/deliverables)");
345
+ expect(content).toContain("[Methodology Phases](/content/methodology/phases)");
346
+ });
347
+
348
+ it("is a static template (does not use context)", () => {
349
+ const ctx2 = makeCtx({
350
+ branding: { siteName: "Other", brandName: "Other" },
351
+ });
352
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
353
+ });
354
+ });
355
+
356
+ describe("assess/deliverables.md", () => {
357
+ const file = findFile("content/offerings/assess/deliverables.md");
358
+
359
+ it("exists in the file list", () => {
360
+ expect(file).toBeDefined();
361
+ });
362
+
363
+ it("has YAML frontmatter", () => {
364
+ const content = resolveContent(file, ctx);
365
+ expect(content).toMatch(/^---\n/);
366
+ expect(content).toContain("title: Assessment Deliverables");
367
+ });
368
+
369
+ it("describes each deliverable", () => {
370
+ const content = resolveContent(file, ctx);
371
+ expect(content).toContain("## Current-State Report");
372
+ expect(content).toContain("## Gap Analysis");
373
+ expect(content).toContain("## Recommendations");
374
+ expect(content).toContain("## Roadmap");
375
+ });
376
+
377
+ it("links to deliverable templates", () => {
378
+ const content = resolveContent(file, ctx);
379
+ expect(content).toContain("[Deliverable Templates](/content/delivery/templates/)");
380
+ });
381
+
382
+ it("is a static template (does not use context)", () => {
383
+ const ctx2 = makeCtx({
384
+ branding: { siteName: "Other", brandName: "Other" },
385
+ });
386
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
387
+ });
388
+ });
389
+
390
+ describe("assess/scoping.md", () => {
391
+ const file = findFile("content/offerings/assess/scoping.md");
392
+
393
+ it("exists in the file list", () => {
394
+ expect(file).toBeDefined();
395
+ });
396
+
397
+ it("has YAML frontmatter", () => {
398
+ const content = resolveContent(file, ctx);
399
+ expect(content).toMatch(/^---\n/);
400
+ expect(content).toContain("title: Assessment Scoping");
401
+ });
402
+
403
+ it("includes scoping criteria table", () => {
404
+ const content = resolveContent(file, ctx);
405
+ expect(content).toContain("## Scoping Criteria");
406
+ expect(content).toContain("| Factor | Questions | Impact on Scope |");
407
+ });
408
+
409
+ it("documents the scoping process", () => {
410
+ const content = resolveContent(file, ctx);
411
+ expect(content).toContain("## Scoping Process");
412
+ expect(content).toContain("### 1. Discovery Call");
413
+ expect(content).toContain("### 2. Scope Document");
414
+ expect(content).toContain("### 3. Client Approval");
415
+ });
416
+
417
+ it("includes scope variables table", () => {
418
+ const content = resolveContent(file, ctx);
419
+ expect(content).toContain("## Scope Variables");
420
+ expect(content).toContain("| Variable | Small | Medium | Large |");
421
+ });
422
+
423
+ it("is a static template (does not use context)", () => {
424
+ const ctx2 = makeCtx({
425
+ branding: { siteName: "Other", brandName: "Other" },
426
+ });
427
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
428
+ });
429
+ });
430
+ });
431
+
432
+ // ---------------------------------------------------------------------------
433
+ // Methodology Section Files
434
+ // ---------------------------------------------------------------------------
435
+
436
+ describe("servicebook methodology section", () => {
437
+ const ctx = makeCtx();
438
+
439
+ describe("phases.md", () => {
440
+ const file = findFile("content/methodology/phases.md");
441
+
442
+ it("exists in the file list", () => {
443
+ expect(file).toBeDefined();
444
+ });
445
+
446
+ it("has frontmatter referencing brand name", () => {
447
+ const content = resolveContent(file, ctx);
448
+ expect(content).toMatch(/^---\n/);
449
+ expect(content).toContain("title: Methodology Phases");
450
+ expect(content).toContain("description: Delivery methodology for Acme Consulting");
451
+ });
452
+
453
+ it("includes all four phases", () => {
454
+ const content = resolveContent(file, ctx);
455
+ expect(content).toContain("### 1. Explore");
456
+ expect(content).toContain("### 2. Analyze");
457
+ expect(content).toContain("### 3. Synthesize");
458
+ expect(content).toContain("### 4. Deliver");
459
+ });
460
+
461
+ it("each phase has input/activities/output tables", () => {
462
+ const content = resolveContent(file, ctx);
463
+ // Count occurrences of the table header
464
+ const tableHeaders = content.match(/\| Input \| Activities \| Output \|/g);
465
+ expect(tableHeaders).toHaveLength(4);
466
+ });
467
+
468
+ it("includes phase adaptation table", () => {
469
+ const content = resolveContent(file, ctx);
470
+ expect(content).toContain("## Phase Adaptation");
471
+ expect(content).toContain("| Engagement Type | Explore | Analyze | Synthesize | Deliver |");
472
+ });
473
+
474
+ it("links to related pages", () => {
475
+ const content = resolveContent(file, ctx);
476
+ expect(content).toContain("[Tools](/content/methodology/tools)");
477
+ expect(content).toContain("[Frameworks](/content/methodology/frameworks)");
478
+ expect(content).toContain("[Decisions](/content/methodology/decisions/)");
479
+ });
480
+ });
481
+
482
+ describe("tools.md", () => {
483
+ const file = findFile("content/methodology/tools.md");
484
+
485
+ it("exists in the file list", () => {
486
+ expect(file).toBeDefined();
487
+ });
488
+
489
+ it("has YAML frontmatter", () => {
490
+ const content = resolveContent(file, ctx);
491
+ expect(content).toMatch(/^---\n/);
492
+ expect(content).toContain("title: Tools");
493
+ });
494
+
495
+ it("includes tool categories", () => {
496
+ const content = resolveContent(file, ctx);
497
+ expect(content).toContain("## Discovery Tools");
498
+ expect(content).toContain("## Analysis Tools");
499
+ expect(content).toContain("## Documentation Tools");
500
+ expect(content).toContain("## Collaboration Tools");
501
+ });
502
+
503
+ it("includes tool selection criteria", () => {
504
+ const content = resolveContent(file, ctx);
505
+ expect(content).toContain("## Tool Selection Criteria");
506
+ });
507
+
508
+ it("is a static template (does not use context)", () => {
509
+ const ctx2 = makeCtx({
510
+ branding: { siteName: "Other", brandName: "Other" },
511
+ });
512
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
513
+ });
514
+ });
515
+
516
+ describe("frameworks.md", () => {
517
+ const file = findFile("content/methodology/frameworks.md");
518
+
519
+ it("exists in the file list", () => {
520
+ expect(file).toBeDefined();
521
+ });
522
+
523
+ it("has YAML frontmatter", () => {
524
+ const content = resolveContent(file, ctx);
525
+ expect(content).toMatch(/^---\n/);
526
+ expect(content).toContain("title: Frameworks");
527
+ });
528
+
529
+ it("includes maturity model table", () => {
530
+ const content = resolveContent(file, ctx);
531
+ expect(content).toContain("## Maturity Model");
532
+ expect(content).toContain("| Level | Label | Description |");
533
+ expect(content).toContain("Initial");
534
+ expect(content).toContain("Optimized");
535
+ });
536
+
537
+ it("includes scoring template table", () => {
538
+ const content = resolveContent(file, ctx);
539
+ expect(content).toContain("## Scoring Template");
540
+ expect(content).toContain("| Dimension | Current Level | Target Level | Gap | Priority |");
541
+ });
542
+
543
+ it("includes framework selection table", () => {
544
+ const content = resolveContent(file, ctx);
545
+ expect(content).toContain("## Framework Selection");
546
+ expect(content).toContain("Maturity Model");
547
+ expect(content).toContain("SWOT");
548
+ expect(content).toContain("Risk Matrix");
549
+ expect(content).toContain("Weighted Scoring");
550
+ });
551
+
552
+ it("is a static template (does not use context)", () => {
553
+ const ctx2 = makeCtx({
554
+ branding: { siteName: "Other", brandName: "Other" },
555
+ });
556
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
557
+ });
558
+ });
559
+
560
+ describe("decisions/index.md", () => {
561
+ const file = findFile("content/methodology/decisions/index.md");
562
+
563
+ it("exists in the file list", () => {
564
+ expect(file).toBeDefined();
565
+ });
566
+
567
+ it("has YAML frontmatter", () => {
568
+ const content = resolveContent(file, ctx);
569
+ expect(content).toMatch(/^---\n/);
570
+ expect(content).toContain("title: Methodology Decisions");
571
+ });
572
+
573
+ it("includes a decision log table", () => {
574
+ const content = resolveContent(file, ctx);
575
+ expect(content).toContain("| ID | Decision | Date | Status |");
576
+ expect(content).toContain("MDR-001");
577
+ });
578
+
579
+ it("links to the MDR template", () => {
580
+ const content = resolveContent(file, ctx);
581
+ expect(content).toContain("[MDR Template](./mdr-template)");
582
+ });
583
+
584
+ it("is a static template (does not use context)", () => {
585
+ const ctx2 = makeCtx({
586
+ branding: { siteName: "Other", brandName: "Other" },
587
+ });
588
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
589
+ });
590
+ });
591
+
592
+ describe("decisions/mdr-template.md", () => {
593
+ const file = findFile("content/methodology/decisions/mdr-template.md");
594
+
595
+ it("exists in the file list", () => {
596
+ expect(file).toBeDefined();
597
+ });
598
+
599
+ it("has frontmatter with MDR-000 title", () => {
600
+ const content = resolveContent(file, ctx);
601
+ expect(content).toContain('title: "MDR-000: Decision Template"');
602
+ });
603
+
604
+ it("includes all MDR sections", () => {
605
+ const content = resolveContent(file, ctx);
606
+ const requiredSections = [
607
+ "## Status",
608
+ "## Context",
609
+ "## Options Considered",
610
+ "## Decision",
611
+ "## Consequences",
612
+ ];
613
+ for (const section of requiredSections) {
614
+ expect(content).toContain(section);
615
+ }
616
+ });
617
+
618
+ it("includes positive, negative, and risks subsections", () => {
619
+ const content = resolveContent(file, ctx);
620
+ expect(content).toContain("### Positive");
621
+ expect(content).toContain("### Negative");
622
+ expect(content).toContain("### Risks");
623
+ });
624
+
625
+ it("is a static template (does not use context)", () => {
626
+ const ctx2 = makeCtx({
627
+ branding: { siteName: "Other", brandName: "Other" },
628
+ });
629
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
630
+ });
631
+ });
632
+ });
633
+
634
+ // ---------------------------------------------------------------------------
635
+ // Delivery Section Files
636
+ // ---------------------------------------------------------------------------
637
+
638
+ describe("servicebook delivery section", () => {
639
+ const ctx = makeCtx();
640
+
641
+ describe("engagement-lifecycle.md", () => {
642
+ const file = findFile("content/delivery/engagement-lifecycle.md");
643
+
644
+ it("exists in the file list", () => {
645
+ expect(file).toBeDefined();
646
+ });
647
+
648
+ it("has frontmatter referencing brand name", () => {
649
+ const content = resolveContent(file, ctx);
650
+ expect(content).toMatch(/^---\n/);
651
+ expect(content).toContain("title: Engagement Lifecycle");
652
+ expect(content).toContain("description: End-to-end engagement flow for Acme Consulting");
653
+ });
654
+
655
+ it("includes all six lifecycle stages", () => {
656
+ const content = resolveContent(file, ctx);
657
+ expect(content).toContain("### 1. Qualify");
658
+ expect(content).toContain("### 2. Scope");
659
+ expect(content).toContain("### 3. Kickoff");
660
+ expect(content).toContain("### 4. Deliver");
661
+ expect(content).toContain("### 5. Review");
662
+ expect(content).toContain("### 6. Close");
663
+ });
664
+
665
+ it("includes quality gates at each stage", () => {
666
+ const content = resolveContent(file, ctx);
667
+ expect(content).toContain("**Gate**:");
668
+ });
669
+
670
+ it("links to related pages", () => {
671
+ const content = resolveContent(file, ctx);
672
+ expect(content).toContain("[Client Onboarding](/content/delivery/client-onboarding)");
673
+ expect(content).toContain("[Quality Gates](/content/delivery/quality-gates)");
674
+ });
675
+ });
676
+
677
+ describe("client-onboarding.md", () => {
678
+ const file = findFile("content/delivery/client-onboarding.md");
679
+
680
+ it("exists in the file list", () => {
681
+ expect(file).toBeDefined();
682
+ });
683
+
684
+ it("has YAML frontmatter", () => {
685
+ const content = resolveContent(file, ctx);
686
+ expect(content).toMatch(/^---\n/);
687
+ expect(content).toContain("title: Client Onboarding");
688
+ });
689
+
690
+ it("includes pre-engagement checklist", () => {
691
+ const content = resolveContent(file, ctx);
692
+ expect(content).toContain("## Pre-Engagement Checklist");
693
+ expect(content).toContain("- [ ]");
694
+ });
695
+
696
+ it("includes kickoff meeting agenda", () => {
697
+ const content = resolveContent(file, ctx);
698
+ expect(content).toContain("## Kickoff Meeting Agenda");
699
+ });
700
+
701
+ it("includes working agreements table", () => {
702
+ const content = resolveContent(file, ctx);
703
+ expect(content).toContain("## Working Agreements");
704
+ expect(content).toContain("| Topic | Agreement |");
705
+ });
706
+
707
+ it("includes onboarding for the delivery team", () => {
708
+ const content = resolveContent(file, ctx);
709
+ expect(content).toContain("## Onboarding for the Delivery Team");
710
+ });
711
+
712
+ it("is a static template (does not use context)", () => {
713
+ const ctx2 = makeCtx({
714
+ branding: { siteName: "Other", brandName: "Other" },
715
+ });
716
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
717
+ });
718
+ });
719
+
720
+ describe("quality-gates.md", () => {
721
+ const file = findFile("content/delivery/quality-gates.md");
722
+
723
+ it("exists in the file list", () => {
724
+ expect(file).toBeDefined();
725
+ });
726
+
727
+ it("has YAML frontmatter", () => {
728
+ const content = resolveContent(file, ctx);
729
+ expect(content).toMatch(/^---\n/);
730
+ expect(content).toContain("title: Quality Gates");
731
+ });
732
+
733
+ it("defines all five gates", () => {
734
+ const content = resolveContent(file, ctx);
735
+ expect(content).toContain("### G1: Scope Approval");
736
+ expect(content).toContain("### G2: Discovery Complete");
737
+ expect(content).toContain("### G3: Analysis Reviewed");
738
+ expect(content).toContain("### G4: Deliverable Review");
739
+ expect(content).toContain("### G5: Client Acceptance");
740
+ });
741
+
742
+ it("includes gate process steps", () => {
743
+ const content = resolveContent(file, ctx);
744
+ expect(content).toContain("## Gate Process");
745
+ expect(content).toContain("**Pass**");
746
+ expect(content).toContain("**Conditional pass**");
747
+ expect(content).toContain("**Hold**");
748
+ });
749
+
750
+ it("is a static template (does not use context)", () => {
751
+ const ctx2 = makeCtx({
752
+ branding: { siteName: "Other", brandName: "Other" },
753
+ });
754
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
755
+ });
756
+ });
757
+
758
+ describe("templates/index.md", () => {
759
+ const file = findFile("content/delivery/templates/index.md");
760
+
761
+ it("exists in the file list", () => {
762
+ expect(file).toBeDefined();
763
+ });
764
+
765
+ it("has YAML frontmatter", () => {
766
+ const content = resolveContent(file, ctx);
767
+ expect(content).toMatch(/^---\n/);
768
+ expect(content).toContain("title: Deliverable Templates");
769
+ });
770
+
771
+ it("includes available templates table", () => {
772
+ const content = resolveContent(file, ctx);
773
+ expect(content).toContain("## Available Templates");
774
+ expect(content).toContain("| Template | Used For | Format |");
775
+ expect(content).toContain("Assessment Report");
776
+ expect(content).toContain("Gap Analysis Matrix");
777
+ });
778
+
779
+ it("includes template conventions", () => {
780
+ const content = resolveContent(file, ctx);
781
+ expect(content).toContain("## Template Conventions");
782
+ });
783
+
784
+ it("is a static template (does not use context)", () => {
785
+ const ctx2 = makeCtx({
786
+ branding: { siteName: "Other", brandName: "Other" },
787
+ });
788
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
789
+ });
790
+ });
791
+ });
792
+
793
+ // ---------------------------------------------------------------------------
794
+ // Verticals Section Files
795
+ // ---------------------------------------------------------------------------
796
+
797
+ describe("servicebook verticals section", () => {
798
+ const ctx = makeCtx();
799
+
800
+ describe("overview.md", () => {
801
+ const file = findFile("content/verticals/overview.md");
802
+
803
+ it("exists in the file list", () => {
804
+ expect(file).toBeDefined();
805
+ });
806
+
807
+ it("has YAML frontmatter", () => {
808
+ const content = resolveContent(file, ctx);
809
+ expect(content).toMatch(/^---\n/);
810
+ expect(content).toContain("title: Verticals Overview");
811
+ });
812
+
813
+ it("describes what to capture per vertical", () => {
814
+ const content = resolveContent(file, ctx);
815
+ expect(content).toContain("### Regulatory Environment");
816
+ expect(content).toContain("### Industry Terminology");
817
+ expect(content).toContain("### Common Challenges");
818
+ expect(content).toContain("### Competitive Landscape");
819
+ });
820
+
821
+ it("documents adding a vertical", () => {
822
+ const content = resolveContent(file, ctx);
823
+ expect(content).toContain("## Adding a Vertical");
824
+ expect(content).toContain("content/verticals/");
825
+ });
826
+
827
+ it("is a static template (does not use context)", () => {
828
+ const ctx2 = makeCtx({
829
+ branding: { siteName: "Other", brandName: "Other" },
830
+ });
831
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
832
+ });
833
+ });
834
+ });
835
+
836
+ // ---------------------------------------------------------------------------
837
+ // Case Studies Section Files
838
+ // ---------------------------------------------------------------------------
839
+
840
+ describe("servicebook case-studies section", () => {
841
+ const ctx = makeCtx();
842
+
843
+ describe("index.md", () => {
844
+ const file = findFile("content/case-studies/index.md");
845
+
846
+ it("exists in the file list", () => {
847
+ expect(file).toBeDefined();
848
+ });
849
+
850
+ it("has YAML frontmatter", () => {
851
+ const content = resolveContent(file, ctx);
852
+ expect(content).toMatch(/^---\n/);
853
+ expect(content).toContain("title: Case Studies");
854
+ });
855
+
856
+ it("includes a case studies table", () => {
857
+ const content = resolveContent(file, ctx);
858
+ expect(content).toContain("| Case Study | Vertical | Service | Outcome |");
859
+ });
860
+
861
+ it("documents the case study template structure", () => {
862
+ const content = resolveContent(file, ctx);
863
+ expect(content).toContain("### Context");
864
+ expect(content).toContain("### Approach");
865
+ expect(content).toContain("### Deliverables");
866
+ expect(content).toContain("### Outcomes");
867
+ expect(content).toContain("### Lessons Learned");
868
+ });
869
+
870
+ it("includes anonymization guidelines", () => {
871
+ const content = resolveContent(file, ctx);
872
+ expect(content).toContain("## Anonymization Guidelines");
873
+ });
874
+
875
+ it("is a static template (does not use context)", () => {
876
+ const ctx2 = makeCtx({
877
+ branding: { siteName: "Other", brandName: "Other" },
878
+ });
879
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
880
+ });
881
+ });
882
+ });
883
+
884
+ // ---------------------------------------------------------------------------
885
+ // Reference Section Files
886
+ // ---------------------------------------------------------------------------
887
+
888
+ describe("servicebook reference section", () => {
889
+ const ctx = makeCtx();
890
+
891
+ describe("team-expertise.md", () => {
892
+ const file = findFile("content/reference/team-expertise.md");
893
+
894
+ it("exists in the file list", () => {
895
+ expect(file).toBeDefined();
896
+ });
897
+
898
+ it("has YAML frontmatter", () => {
899
+ const content = resolveContent(file, ctx);
900
+ expect(content).toMatch(/^---\n/);
901
+ expect(content).toContain("title: Team Expertise");
902
+ });
903
+
904
+ it("includes capability matrix table", () => {
905
+ const content = resolveContent(file, ctx);
906
+ expect(content).toContain("## Capability Matrix");
907
+ expect(content).toContain("| Capability | Team Members | Depth | Verticals |");
908
+ });
909
+
910
+ it("includes certifications table", () => {
911
+ const content = resolveContent(file, ctx);
912
+ expect(content).toContain("## Certifications & Credentials");
913
+ expect(content).toContain("| Person | Certification | Issuer | Expiry |");
914
+ });
915
+
916
+ it("includes capacity planning table", () => {
917
+ const content = resolveContent(file, ctx);
918
+ expect(content).toContain("## Capacity Planning");
919
+ });
920
+
921
+ it("is a static template (does not use context)", () => {
922
+ const ctx2 = makeCtx({
923
+ branding: { siteName: "Other", brandName: "Other" },
924
+ });
925
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
926
+ });
927
+ });
928
+
929
+ describe("pricing-models.md", () => {
930
+ const file = findFile("content/reference/pricing-models.md");
931
+
932
+ it("exists in the file list", () => {
933
+ expect(file).toBeDefined();
934
+ });
935
+
936
+ it("has YAML frontmatter", () => {
937
+ const content = resolveContent(file, ctx);
938
+ expect(content).toMatch(/^---\n/);
939
+ expect(content).toContain("title: Pricing Models");
940
+ });
941
+
942
+ it("includes all three pricing types", () => {
943
+ const content = resolveContent(file, ctx);
944
+ expect(content).toContain("### Fixed-Price");
945
+ expect(content).toContain("### Time & Materials");
946
+ expect(content).toContain("### Milestone-Based");
947
+ });
948
+
949
+ it("includes rate structure table", () => {
950
+ const content = resolveContent(file, ctx);
951
+ expect(content).toContain("## Rate Structure");
952
+ expect(content).toContain("| Role | Rate Range | Notes |");
953
+ });
954
+
955
+ it("is a static template (does not use context)", () => {
956
+ const ctx2 = makeCtx({
957
+ branding: { siteName: "Other", brandName: "Other" },
958
+ });
959
+ expect(resolveContent(file, ctx)).toBe(resolveContent(file, ctx2));
960
+ });
961
+ });
962
+
963
+ describe("contacts/directory.md", () => {
964
+ const file = findFile("content/reference/contacts/directory.md");
965
+
966
+ it("exists in the file list", () => {
967
+ expect(file).toBeDefined();
968
+ });
969
+
970
+ it("has frontmatter referencing brand name", () => {
971
+ const content = resolveContent(file, ctx);
972
+ expect(content).toMatch(/^---\n/);
973
+ expect(content).toContain("title: Contact Directory");
974
+ expect(content).toContain("description: Team contacts and escalation for Acme Consulting");
975
+ });
976
+
977
+ it("includes delivery team table", () => {
978
+ const content = resolveContent(file, ctx);
979
+ expect(content).toContain("## Delivery Team");
980
+ expect(content).toContain("| Role | Contact | Responsibility |");
981
+ expect(content).toContain("Practice Lead");
982
+ expect(content).toContain("Engagement Lead");
983
+ });
984
+
985
+ it("includes escalation path", () => {
986
+ const content = resolveContent(file, ctx);
987
+ expect(content).toContain("## Escalation Path");
988
+ expect(content).toContain("**Engagement Lead**");
989
+ expect(content).toContain("**Practice Lead**");
990
+ expect(content).toContain("**Managing Partner**");
991
+ });
992
+
993
+ it("includes client communication table", () => {
994
+ const content = resolveContent(file, ctx);
995
+ expect(content).toContain("## Client Communication");
996
+ expect(content).toContain("| Channel | Purpose | Response Time |");
997
+ });
998
+ });
999
+ });
1000
+
1001
+ // ---------------------------------------------------------------------------
1002
+ // CUSTOMIZING.md
1003
+ // ---------------------------------------------------------------------------
1004
+
1005
+ describe("servicebook CUSTOMIZING.md", () => {
1006
+ const ctx = makeCtx();
1007
+ const file = findFile("CUSTOMIZING.md");
1008
+
1009
+ it("exists in the file list", () => {
1010
+ expect(file).toBeDefined();
1011
+ });
1012
+
1013
+ it("has frontmatter with template metadata", () => {
1014
+ const content = resolveContent(file, ctx);
1015
+ expect(content).toMatch(/^---\n/);
1016
+ expect(content).toContain("template: servicebook");
1017
+ expect(content).toContain("template_version: 1");
1018
+ });
1019
+
1020
+ it("includes the site name in the heading", () => {
1021
+ const content = resolveContent(file, ctx);
1022
+ expect(content).toContain("# Customizing Test Servicebook");
1023
+ });
1024
+
1025
+ it("uses the project name in the directory tree", () => {
1026
+ const content = resolveContent(file, ctx);
1027
+ expect(content).toContain("test-servicebook/");
1028
+ });
1029
+
1030
+ it("documents site.yaml configuration", () => {
1031
+ const content = resolveContent(file, ctx);
1032
+ expect(content).toContain("### site.yaml");
1033
+ expect(content).toContain('title: "Test Servicebook"');
1034
+ expect(content).toContain('name: "Acme Consulting"');
1035
+ });
1036
+
1037
+ it("documents theme.yaml customization", () => {
1038
+ const content = resolveContent(file, ctx);
1039
+ expect(content).toContain("### theme.yaml");
1040
+ });
1041
+
1042
+ it("includes the current year in footer example", () => {
1043
+ const content = resolveContent(file, ctx);
1044
+ expect(content).toContain("2026 Acme Consulting");
1045
+ });
1046
+
1047
+ it("covers adding content types", () => {
1048
+ const content = resolveContent(file, ctx);
1049
+ expect(content).toContain("### New Service Offering");
1050
+ expect(content).toContain("### Documenting a Methodology Change");
1051
+ expect(content).toContain("### Adding a Case Study");
1052
+ expect(content).toContain("### Adding a Vertical");
1053
+ expect(content).toContain("### New Section");
1054
+ });
1055
+
1056
+ it("includes document conventions", () => {
1057
+ const content = resolveContent(file, ctx);
1058
+ expect(content).toContain("### Offerings");
1059
+ expect(content).toContain("### Methodology");
1060
+ expect(content).toContain("### Delivery");
1061
+ expect(content).toContain("### Case Studies");
1062
+ });
1063
+
1064
+ it("includes site structure diagram", () => {
1065
+ const content = resolveContent(file, ctx);
1066
+ expect(content).toContain("## Site Structure");
1067
+ expect(content).toContain("content/");
1068
+ expect(content).toContain("offerings/");
1069
+ expect(content).toContain("methodology/");
1070
+ expect(content).toContain("delivery/");
1071
+ expect(content).toContain("verticals/");
1072
+ expect(content).toContain("case-studies/");
1073
+ expect(content).toContain("reference/");
1074
+ });
1075
+
1076
+ it("responds to different context values", () => {
1077
+ const customCtx = makeCtx({
1078
+ name: "my-services",
1079
+ branding: {
1080
+ siteName: "My Services",
1081
+ brandName: "My Brand",
1082
+ brandUrl: "/",
1083
+ },
1084
+ year: 2027,
1085
+ });
1086
+ const content = resolveContent(file, customCtx);
1087
+ expect(content).toContain("# Customizing My Services");
1088
+ expect(content).toContain("my-services/");
1089
+ expect(content).toContain('name: "My Brand"');
1090
+ expect(content).toContain("2027 My Brand");
1091
+ });
1092
+ });
1093
+
1094
+ // ---------------------------------------------------------------------------
1095
+ // File Coverage Completeness
1096
+ // ---------------------------------------------------------------------------
1097
+
1098
+ describe("servicebook file coverage", () => {
1099
+ const expectedFiles = [
1100
+ "site.yaml",
1101
+ "index.md",
1102
+ "content/offerings/overview.md",
1103
+ "content/offerings/assess/overview.md",
1104
+ "content/offerings/assess/deliverables.md",
1105
+ "content/offerings/assess/scoping.md",
1106
+ "content/methodology/phases.md",
1107
+ "content/methodology/tools.md",
1108
+ "content/methodology/frameworks.md",
1109
+ "content/methodology/decisions/index.md",
1110
+ "content/methodology/decisions/mdr-template.md",
1111
+ "content/delivery/engagement-lifecycle.md",
1112
+ "content/delivery/client-onboarding.md",
1113
+ "content/delivery/quality-gates.md",
1114
+ "content/delivery/templates/index.md",
1115
+ "content/verticals/overview.md",
1116
+ "content/case-studies/index.md",
1117
+ "content/reference/team-expertise.md",
1118
+ "content/reference/pricing-models.md",
1119
+ "content/reference/contacts/directory.md",
1120
+ "CUSTOMIZING.md",
1121
+ ];
1122
+
1123
+ it("defines exactly the expected set of files", () => {
1124
+ const actualPaths = servicebook.files.map((f) => f.path).sort();
1125
+ const expected = [...expectedFiles].sort();
1126
+ expect(actualPaths).toEqual(expected);
1127
+ });
1128
+
1129
+ it("all content generators produce non-empty strings", () => {
1130
+ const ctx = makeCtx();
1131
+ for (const file of servicebook.files) {
1132
+ const content = resolveContent(file, ctx);
1133
+ expect(content.length).toBeGreaterThan(0);
1134
+ expect(typeof content).toBe("string");
1135
+ }
1136
+ });
1137
+
1138
+ it("all markdown files start with YAML frontmatter or a markdown heading", () => {
1139
+ const ctx = makeCtx();
1140
+ for (const file of servicebook.files) {
1141
+ if (!file.path.endsWith(".md")) continue;
1142
+ const content = resolveContent(file, ctx);
1143
+ const startsWithFrontmatter = content.startsWith("---\n");
1144
+ const startsWithHeading = content.startsWith("#");
1145
+ expect(startsWithFrontmatter || startsWithHeading).toBe(true);
1146
+ }
1147
+ });
1148
+
1149
+ it("site.yaml starts with a YAML comment", () => {
1150
+ const ctx = makeCtx();
1151
+ const file = findFile("site.yaml");
1152
+ const content = resolveContent(file, ctx);
1153
+ expect(content.startsWith("#")).toBe(true);
1154
+ });
1155
+ });
1156
+
1157
+ // ---------------------------------------------------------------------------
1158
+ // Branding Substitution Across All Context-Dependent Files
1159
+ // ---------------------------------------------------------------------------
1160
+
1161
+ describe("servicebook branding substitution", () => {
1162
+ it("context-dependent files use brandName, not a hardcoded value", () => {
1163
+ const ctx1 = makeCtx({
1164
+ branding: {
1165
+ siteName: "Alpha Services",
1166
+ brandName: "Alpha Corp",
1167
+ brandUrl: "/",
1168
+ },
1169
+ });
1170
+ const ctx2 = makeCtx({
1171
+ branding: {
1172
+ siteName: "Beta Services",
1173
+ brandName: "Beta Corp",
1174
+ brandUrl: "/",
1175
+ },
1176
+ });
1177
+
1178
+ // Files that use ctx.branding.brandName in their description
1179
+ const brandDependentFiles = [
1180
+ "content/offerings/overview.md",
1181
+ "content/methodology/phases.md",
1182
+ "content/delivery/engagement-lifecycle.md",
1183
+ "content/reference/contacts/directory.md",
1184
+ ];
1185
+
1186
+ for (const path of brandDependentFiles) {
1187
+ const file = findFile(path);
1188
+ const content1 = resolveContent(file, ctx1);
1189
+ const content2 = resolveContent(file, ctx2);
1190
+
1191
+ expect(content1).toContain("Alpha Corp");
1192
+ expect(content1).not.toContain("Beta Corp");
1193
+ expect(content2).toContain("Beta Corp");
1194
+ expect(content2).not.toContain("Alpha Corp");
1195
+ }
1196
+ });
1197
+
1198
+ it("context-dependent files use siteName where appropriate", () => {
1199
+ const ctx1 = makeCtx({
1200
+ branding: {
1201
+ siteName: "Gamma Hub",
1202
+ brandName: "Gamma Inc",
1203
+ brandUrl: "/",
1204
+ },
1205
+ });
1206
+
1207
+ // site.yaml and index.md use siteName
1208
+ const siteYaml = resolveContent(findFile("site.yaml"), ctx1);
1209
+ expect(siteYaml).toContain("Gamma Hub");
1210
+
1211
+ const indexMd = resolveContent(findFile("index.md"), ctx1);
1212
+ expect(indexMd).toContain("Gamma Hub");
1213
+
1214
+ const customizing = resolveContent(findFile("CUSTOMIZING.md"), ctx1);
1215
+ expect(customizing).toContain("Gamma Hub");
1216
+ });
1217
+
1218
+ it("static templates are not affected by context changes", () => {
1219
+ const ctx1 = makeCtx({
1220
+ branding: { siteName: "One", brandName: "One" },
1221
+ });
1222
+ const ctx2 = makeCtx({
1223
+ branding: { siteName: "Two", brandName: "Two" },
1224
+ });
1225
+
1226
+ const staticFiles = [
1227
+ "content/offerings/assess/overview.md",
1228
+ "content/offerings/assess/deliverables.md",
1229
+ "content/offerings/assess/scoping.md",
1230
+ "content/methodology/tools.md",
1231
+ "content/methodology/frameworks.md",
1232
+ "content/methodology/decisions/index.md",
1233
+ "content/methodology/decisions/mdr-template.md",
1234
+ "content/delivery/client-onboarding.md",
1235
+ "content/delivery/quality-gates.md",
1236
+ "content/delivery/templates/index.md",
1237
+ "content/verticals/overview.md",
1238
+ "content/case-studies/index.md",
1239
+ "content/reference/team-expertise.md",
1240
+ "content/reference/pricing-models.md",
1241
+ ];
1242
+
1243
+ for (const path of staticFiles) {
1244
+ const file = findFile(path);
1245
+ expect(resolveContent(file, ctx1)).toBe(resolveContent(file, ctx2));
1246
+ }
1247
+ });
1248
+ });