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,876 @@
1
+ /**
2
+ * Template Driver - Base Operations
3
+ *
4
+ * Provides common operations for template generation:
5
+ * - Directory creation
6
+ * - File writing with template expansion
7
+ * - Git initialization
8
+ * - Standalone mode (copy site code)
9
+ */
10
+
11
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
12
+ import { dirname, join } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { crucible } from "./crucible.ts";
15
+ import { handbook } from "./handbook.ts";
16
+ import { minimal } from "./minimal.ts";
17
+ import { pipeline } from "./pipeline.ts";
18
+ import { productbook } from "./productbook.ts";
19
+ import { runbook } from "./runbook.ts";
20
+ import type {
21
+ BrandingConfig,
22
+ InitOptions,
23
+ SiteManifest,
24
+ StandaloneProvenance,
25
+ TemplateContext,
26
+ TemplateDef,
27
+ TemplateFile,
28
+ TemplateRegistry,
29
+ } from "./schema.ts";
30
+ import { servicebook } from "./servicebook.ts";
31
+
32
+ // Resolve kitfly root
33
+ const __dirname = dirname(fileURLToPath(import.meta.url));
34
+ const KITFLY_ROOT = join(__dirname, "../..");
35
+
36
+ // -----------------------------------------------------------------------------
37
+ // Template Registry
38
+ // -----------------------------------------------------------------------------
39
+
40
+ const templates: TemplateRegistry = new Map();
41
+
42
+ export function registerTemplate(template: TemplateDef): void {
43
+ templates.set(template.id, template);
44
+ }
45
+
46
+ export function getTemplate(id: string): TemplateDef | undefined {
47
+ return templates.get(id);
48
+ }
49
+
50
+ export function listTemplates(): TemplateDef[] {
51
+ return Array.from(templates.values());
52
+ }
53
+
54
+ // Register built-in templates
55
+ registerTemplate(minimal);
56
+ registerTemplate(handbook);
57
+ registerTemplate(pipeline);
58
+ registerTemplate(productbook);
59
+ registerTemplate(runbook);
60
+ registerTemplate(servicebook);
61
+ registerTemplate(crucible);
62
+
63
+ // -----------------------------------------------------------------------------
64
+ // File Operations
65
+ // -----------------------------------------------------------------------------
66
+
67
+ export async function ensureDir(path: string): Promise<void> {
68
+ await mkdir(path, { recursive: true });
69
+ }
70
+
71
+ export async function writeTemplateFile(
72
+ root: string,
73
+ file: TemplateFile,
74
+ ctx: TemplateContext,
75
+ ): Promise<void> {
76
+ const filePath = join(root, file.path);
77
+ await ensureDir(dirname(filePath));
78
+
79
+ const content = typeof file.content === "function" ? file.content(ctx) : file.content;
80
+
81
+ await writeFile(filePath, content, "utf-8");
82
+ }
83
+
84
+ // -----------------------------------------------------------------------------
85
+ // Git Operations
86
+ // -----------------------------------------------------------------------------
87
+
88
+ export async function initGit(root: string): Promise<boolean> {
89
+ try {
90
+ const proc = Bun.spawn(["git", "init"], {
91
+ cwd: root,
92
+ stdout: "pipe",
93
+ stderr: "pipe",
94
+ });
95
+ await proc.exited;
96
+ return proc.exitCode === 0;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ export async function gitCommit(root: string, message: string): Promise<boolean> {
103
+ try {
104
+ const add = Bun.spawn(["git", "add", "-A"], { cwd: root, stdout: "ignore", stderr: "ignore" });
105
+ await add.exited;
106
+
107
+ const commit = Bun.spawn(["git", "commit", "-m", message], {
108
+ cwd: root,
109
+ stdout: "ignore",
110
+ stderr: "ignore",
111
+ });
112
+ await commit.exited;
113
+ return commit.exitCode === 0;
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ // -----------------------------------------------------------------------------
120
+ // Branding Defaults
121
+ // -----------------------------------------------------------------------------
122
+
123
+ export function defaultBranding(name: string): BrandingConfig {
124
+ const titleCase = name
125
+ .split(/[-_]/)
126
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
127
+ .join(" ");
128
+
129
+ return {
130
+ siteName: titleCase,
131
+ brandName: titleCase,
132
+ brandUrl: "/",
133
+ primaryColor: "#2563eb",
134
+ footerText: `© ${new Date().getFullYear()} ${titleCase}`,
135
+ };
136
+ }
137
+
138
+ // -----------------------------------------------------------------------------
139
+ // Standalone Mode - Files to Copy
140
+ // -----------------------------------------------------------------------------
141
+
142
+ const STANDALONE_FILES = [
143
+ // Core scripts
144
+ "scripts/dev.ts",
145
+ "scripts/build.ts",
146
+ "scripts/bundle.ts",
147
+ // Shared code
148
+ "src/shared.ts",
149
+ "src/engine.ts",
150
+ "src/theme.ts",
151
+ // Schemas (editor validation + migrations)
152
+ "schemas/README.md",
153
+ "schemas/site.schema.json",
154
+ "schemas/theme.schema.json",
155
+ "schemas/v0/site.schema.json",
156
+ "schemas/v0/theme.schema.json",
157
+ // Site templates and assets
158
+ "src/site/template.html",
159
+ "src/site/styles.css",
160
+ ];
161
+
162
+ // Directories to copy entirely
163
+ const STANDALONE_DIRS = ["assets"];
164
+
165
+ function toArrayBuffer(data: Uint8Array): ArrayBuffer {
166
+ // Normalize to a standalone ArrayBuffer to satisfy SubtleCrypto typing.
167
+ return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;
168
+ }
169
+
170
+ async function hashBytes(data: Uint8Array): Promise<string> {
171
+ const hashBuffer = await crypto.subtle.digest("SHA-256", toArrayBuffer(data));
172
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
173
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
174
+ }
175
+
176
+ async function hashContent(content: string): Promise<string> {
177
+ return hashBytes(new TextEncoder().encode(content));
178
+ }
179
+
180
+ async function getKitflyVersion(): Promise<string> {
181
+ try {
182
+ const versionFile = join(KITFLY_ROOT, "VERSION");
183
+ return (await readFile(versionFile, "utf-8")).trim();
184
+ } catch {
185
+ return "0.0.0";
186
+ }
187
+ }
188
+
189
+ async function copyStandaloneFiles(
190
+ root: string,
191
+ ctx: TemplateContext,
192
+ ): Promise<StandaloneProvenance> {
193
+ const provenance: StandaloneProvenance = {
194
+ kitflyVersion: await getKitflyVersion(),
195
+ createdAt: new Date().toISOString(),
196
+ template: ctx.template.id,
197
+ files: [],
198
+ };
199
+
200
+ // Copy individual files
201
+ for (const relPath of STANDALONE_FILES) {
202
+ const srcPath = join(KITFLY_ROOT, relPath);
203
+ const destPath = join(root, relPath);
204
+
205
+ try {
206
+ const content = await readFile(srcPath, "utf-8");
207
+ await ensureDir(dirname(destPath));
208
+ await writeFile(destPath, content, "utf-8");
209
+ const localHash = await hashContent(content);
210
+
211
+ provenance.files.push({
212
+ path: relPath,
213
+ sourceHash: localHash,
214
+ localHash,
215
+ modified: false,
216
+ });
217
+
218
+ console.log(` + ${relPath} (standalone)`);
219
+ } catch (e) {
220
+ console.warn(` ! Could not copy ${relPath}: ${e}`);
221
+ }
222
+ }
223
+
224
+ // Copy asset directories
225
+ for (const dirName of STANDALONE_DIRS) {
226
+ const srcDir = join(KITFLY_ROOT, dirName);
227
+ const destDir = join(root, dirName);
228
+
229
+ try {
230
+ await copyDir(srcDir, destDir, provenance);
231
+ } catch {
232
+ // Directory may not exist, that's ok
233
+ }
234
+ }
235
+
236
+ return provenance;
237
+ }
238
+
239
+ async function copyDir(src: string, dest: string, provenance: StandaloneProvenance): Promise<void> {
240
+ const { readdir } = await import("node:fs/promises");
241
+
242
+ await ensureDir(dest);
243
+ const entries = await readdir(src, { withFileTypes: true });
244
+
245
+ for (const entry of entries) {
246
+ const srcPath = join(src, entry.name);
247
+ const destPath = join(dest, entry.name);
248
+
249
+ if (entry.isDirectory()) {
250
+ await copyDir(srcPath, destPath, provenance);
251
+ } else {
252
+ const content = await readFile(srcPath);
253
+ await writeFile(destPath, content);
254
+
255
+ // Get relative path from KITFLY_ROOT for provenance
256
+ const relPath = srcPath.replace(`${KITFLY_ROOT}/`, "");
257
+ const bytes = new Uint8Array(content);
258
+ const localHash = await hashBytes(bytes);
259
+ provenance.files.push({
260
+ path: relPath,
261
+ sourceHash: localHash,
262
+ localHash,
263
+ modified: false,
264
+ });
265
+ }
266
+ }
267
+ }
268
+
269
+ function ensureYamlSchemaComment(content: string, schemaPath: string): string {
270
+ const lines = content.split("\n");
271
+ const desired = `# yaml-language-server: $schema=${schemaPath}`;
272
+ if (lines[0]?.startsWith("# yaml-language-server:")) {
273
+ lines[0] = desired;
274
+ return lines.join("\n");
275
+ }
276
+ return `${desired}\n${content}`;
277
+ }
278
+
279
+ function ensureSiteSchemaVersion(content: string, version: string): string {
280
+ if (/^schemaVersion\s*:/m.test(content)) return content;
281
+ const lines = content.split("\n");
282
+ const insertLine = `schemaVersion: "${version}"`;
283
+ if (lines[0]?.startsWith("# yaml-language-server:")) {
284
+ lines.splice(1, 0, insertLine);
285
+ return lines.join("\n");
286
+ }
287
+ return `${insertLine}\n${content}`;
288
+ }
289
+
290
+ async function stampSchemaVersion(root: string, version: string): Promise<void> {
291
+ const sitePath = join(root, "site.yaml");
292
+ try {
293
+ let content = await readFile(sitePath, "utf-8");
294
+ content = ensureYamlSchemaComment(content, "./schemas/v0/site.schema.json");
295
+ content = ensureSiteSchemaVersion(content, version);
296
+ await writeFile(sitePath, content, "utf-8");
297
+ } catch {
298
+ // site.yaml may not exist; ignore
299
+ }
300
+
301
+ const themePath = join(root, "theme.yaml");
302
+ try {
303
+ let content = await readFile(themePath, "utf-8");
304
+ content = ensureYamlSchemaComment(content, "./schemas/v0/theme.schema.json");
305
+ await writeFile(themePath, content, "utf-8");
306
+ } catch {
307
+ // theme.yaml may not exist; ignore
308
+ }
309
+ }
310
+
311
+ function generateStandalonePackageJson(ctx: TemplateContext): string {
312
+ return JSON.stringify(
313
+ {
314
+ name: ctx.name,
315
+ version: "0.1.0",
316
+ type: "module",
317
+ description: ctx.branding.siteName,
318
+ scripts: {
319
+ dev: "bun run scripts/dev.ts",
320
+ build: "bun run scripts/build.ts",
321
+ bundle: "bun run scripts/bundle.ts",
322
+ },
323
+ dependencies: {
324
+ marked: "^15.0.0",
325
+ },
326
+ devDependencies: {
327
+ "@types/bun": "^1.2.0",
328
+ },
329
+ },
330
+ null,
331
+ 2,
332
+ );
333
+ }
334
+
335
+ function generateStandaloneReadme(ctx: TemplateContext): string {
336
+ return `# ${ctx.branding.siteName}
337
+
338
+ Documentation site built with [Kitfly](https://github.com/3leaps/kitfly) (standalone mode).
339
+
340
+ ## Prerequisites
341
+
342
+ ### Install Bun
343
+
344
+ This site uses [Bun](https://bun.sh) as its JavaScript runtime.
345
+
346
+ **macOS/Linux:**
347
+ \`\`\`bash
348
+ curl -fsSL https://bun.sh/install | bash
349
+ \`\`\`
350
+
351
+ **Windows:**
352
+ \`\`\`powershell
353
+ powershell -c "irm bun.sh/install.ps1 | iex"
354
+ \`\`\`
355
+
356
+ For other installation methods, see: https://bun.sh/docs/installation
357
+
358
+ ### Install Dependencies
359
+
360
+ \`\`\`bash
361
+ bun install
362
+ \`\`\`
363
+
364
+ ## Development
365
+
366
+ \`\`\`bash
367
+ # Preview locally with hot reload
368
+ bun run dev
369
+
370
+ # Build static site to dist/
371
+ bun run build
372
+
373
+ # Create offline bundle (single HTML file)
374
+ bun run bundle
375
+ \`\`\`
376
+
377
+ ## Structure
378
+
379
+ \`\`\`
380
+ ${ctx.name}/
381
+ ├── site.yaml # Site configuration
382
+ ├── theme.yaml # Theme customization (optional)
383
+ ├── index.md # Home page
384
+ ├── content/ # Documentation content
385
+ ├── assets/ # Static assets (images, brand files)
386
+ ├── scripts/ # Build scripts (standalone)
387
+ │ ├── dev.ts # Development server
388
+ │ ├── build.ts # Static site generator
389
+ │ └── bundle.ts # Single-file bundler
390
+ └── src/ # Site engine (standalone)
391
+ ├── shared.ts # Shared utilities
392
+ ├── engine.ts # Engine paths
393
+ ├── theme.ts # Theme system
394
+ └── site/ # HTML template & styles
395
+ \`\`\`
396
+
397
+ ## Customization
398
+
399
+ - Edit \`site.yaml\` to configure sections and branding
400
+ - Add a \`theme.yaml\` for color/typography customization
401
+ - Replace files in \`assets/brand/\` with your logo and favicon
402
+
403
+ ## Standalone Mode
404
+
405
+ This site was created with \`--standalone\` flag, meaning all build tooling is included.
406
+ No external kitfly installation is required.
407
+
408
+ See \`.kitfly/provenance.json\` for version tracking.
409
+
410
+ ---
411
+ © ${ctx.year} ${ctx.branding.brandName}
412
+ `;
413
+ }
414
+
415
+ function generateDependentReadme(ctx: TemplateContext): string {
416
+ return `# ${ctx.branding.siteName}
417
+
418
+ Documentation site built with [Kitfly](https://github.com/3leaps/kitfly).
419
+
420
+ ## Prerequisites
421
+
422
+ ### Install Bun
423
+
424
+ This site uses [Bun](https://bun.sh) as its JavaScript runtime.
425
+
426
+ **macOS/Linux:**
427
+ \`\`\`bash
428
+ curl -fsSL https://bun.sh/install | bash
429
+ \`\`\`
430
+
431
+ **Windows:**
432
+ \`\`\`powershell
433
+ powershell -c "irm bun.sh/install.ps1 | iex"
434
+ \`\`\`
435
+
436
+ ### Install Kitfly
437
+
438
+ \`\`\`bash
439
+ # Via npm/bun
440
+ bun install -g kitfly
441
+
442
+ # Or clone and link
443
+ git clone https://github.com/3leaps/kitfly.git
444
+ cd kitfly && bun link
445
+ \`\`\`
446
+
447
+ ## Development
448
+
449
+ \`\`\`bash
450
+ # Preview locally with hot reload
451
+ kitfly dev
452
+
453
+ # Build static site to dist/
454
+ kitfly build
455
+
456
+ # Create offline bundle (single HTML file)
457
+ kitfly bundle
458
+ \`\`\`
459
+
460
+ ## Structure
461
+
462
+ \`\`\`
463
+ ${ctx.name}/
464
+ ├── site.yaml # Site configuration
465
+ ├── index.md # Home page
466
+ ├── content/ # Documentation content
467
+ └── assets/brand/ # Logo, favicon, etc.
468
+ \`\`\`
469
+
470
+ ## Customization
471
+
472
+ - Edit \`site.yaml\` to configure sections and branding
473
+ - Add a \`theme.yaml\` for color/typography customization
474
+ - Replace \`assets/brand/\` files with your logo
475
+
476
+ ---
477
+ © ${ctx.year} ${ctx.branding.brandName}
478
+ `;
479
+ }
480
+
481
+ // -----------------------------------------------------------------------------
482
+ // AI Assistance Instrumentation
483
+ // -----------------------------------------------------------------------------
484
+
485
+ function generateAgentsMd(ctx: TemplateContext): string {
486
+ const templateType = ctx.template.id;
487
+ const isOperational = templateType === "runbook" || templateType === "pipeline";
488
+ const isPipeline = templateType === "pipeline";
489
+ const isProductbook = templateType === "productbook";
490
+ const isServicebook = templateType === "servicebook";
491
+ const isCrucible = templateType === "crucible";
492
+
493
+ const typeLabel = isCrucible
494
+ ? "information architecture SSOT"
495
+ : isServicebook
496
+ ? "professional services catalog"
497
+ : isProductbook
498
+ ? "product and domain documentation site"
499
+ : isPipeline
500
+ ? "pipeline operations site"
501
+ : templateType === "runbook"
502
+ ? "runbook"
503
+ : "documentation site";
504
+
505
+ return `# ${ctx.branding.siteName} - AI Agent Guide
506
+
507
+ ## Overview
508
+
509
+ This ${typeLabel} is instrumented for AI assistance. AI coding assistants can help maintain and extend this documentation.
510
+
511
+ ## Site Structure
512
+
513
+ See \`CUSTOMIZING.md\` for full details on:
514
+ - Adding new pages and sections
515
+ - Configuration options
516
+ - Linking conventions
517
+ - Brand assets
518
+
519
+ ## Content Guidelines
520
+
521
+ ${
522
+ isCrucible
523
+ ? `### Specs
524
+ - Use **RFC 2119 language** (MUST, SHOULD, MAY) for requirements
525
+ - Include **version and status** in every specification
526
+ - Provide **compliant and non-compliant examples**
527
+ - State scope explicitly — what is and isn't covered
528
+
529
+ ### Schemas
530
+ - Document **purpose, fields, and examples** for each schema
531
+ - Link to the raw \`.schema.json\` file in \`schemas/\`
532
+ - Note **breaking change policy** and version compatibility
533
+ - Include sample valid documents
534
+
535
+ ### Config
536
+ - Document **valid values and defaults** for every config entry
537
+ - Explain what each config controls and who consumes it
538
+ - Link to the validating schema (if any)
539
+
540
+ ### Policies
541
+ - Be prescriptive — use **MUST/SHOULD/MAY** language
542
+ - Include **rationale** for each rule
543
+ - Define **consequences** for non-compliance
544
+ - State review and approval requirements`
545
+ : isServicebook
546
+ ? `### Offerings
547
+ - State the **client problem** before describing the service
548
+ - Define **deliverables** with format, content, and audience
549
+ - Include **scoping criteria** so engagements are sized consistently
550
+ - Link to relevant methodology phases and delivery templates
551
+
552
+ ### Methodology
553
+ - Document phases with **inputs, activities, and outputs**
554
+ - Tools should include purpose, capabilities, and when they're used
555
+ - Frameworks need clear **scoring criteria and limitations**
556
+ - Record methodology changes as decision records (MDRs)
557
+
558
+ ### Delivery
559
+ - Engagement lifecycle needs **quality gates** between each stage
560
+ - Quality gates need **specific, checkable criteria**
561
+ - Deliverable templates should be ready to use, not just described
562
+ - Document onboarding as a checklist with clear prerequisites
563
+
564
+ ### Case Studies
565
+ - Always **anonymize** unless client has approved attribution
566
+ - **Quantify outcomes** where possible
567
+ - Include **lessons learned** — what you'd do differently
568
+ - Link to relevant verticals and methodology`
569
+ : isProductbook
570
+ ? `### Product
571
+ - State the **user problem** before the solution
572
+ - Include **acceptance criteria** and **success metrics**
573
+ - Link to related domain context and specs
574
+
575
+ ### Domain
576
+ - Write for someone **new to the business** — explain why, not just what
577
+ - Be precise with terminology — the **data dictionary is canonical**
578
+ - Document **edge cases and variations**, not just happy paths
579
+ - Business processes need: triggers, steps, actors, systems, business rules
580
+
581
+ ### Planning
582
+ - **Decisions**: capture context and alternatives, not just the choice
583
+ - **Specs**: problem first, solution second, acceptance criteria always
584
+ - **Research**: state methodology and limitations
585
+
586
+ ### Guides
587
+ - Write for the audience (team member vs end user)
588
+ - Start with what they need to know first`
589
+ : isPipeline
590
+ ? `### Pipeline Stages
591
+ - Start with **Objective** (what this stage accomplishes)
592
+ - List **Prerequisites** as checkboxes
593
+ - Number **Steps** explicitly with verification
594
+ - Include expected outputs and verification commands
595
+
596
+ ### Sources & Destinations
597
+ - Document connection details, auth profiles, data formats
598
+ - Keep index tables in \`index.md\` up to date
599
+ - Include schema or path structure documentation
600
+
601
+ ### Troubleshooting
602
+ - Lead with **Symptoms** (what user observes)
603
+ - List **Possible Causes**
604
+ - Provide step-by-step **Resolution**
605
+ - Include **Prevention** guidance
606
+
607
+ ### Checklists
608
+ - Use checkbox format: \`- [ ] Item\`
609
+ - Group by phase or category
610
+ - Include Go/No-Go decision point`
611
+ : isOperational
612
+ ? `### Procedures
613
+ - Start with **Objective** (what this accomplishes)
614
+ - List **Prerequisites** as checkboxes
615
+ - Number **Steps** explicitly with verification
616
+ - Include expected outputs
617
+ - Always provide **Rollback** section
618
+
619
+ ### Troubleshooting
620
+ - Lead with **Symptoms** (what user observes)
621
+ - List **Possible Causes**
622
+ - Provide step-by-step **Resolution**
623
+ - Include **Escalation** path
624
+
625
+ ### Checklists
626
+ - Use checkbox format: \`- [ ] Item\`
627
+ - Group by phase or category
628
+ - Include Go/No-Go decision point`
629
+ : `### General Guidelines
630
+ - Use clear, descriptive headings
631
+ - Include code examples where helpful
632
+ - Link related content using relative paths
633
+ - Mark placeholder content with \`<!-- ← CUSTOMIZE -->\``
634
+ }
635
+
636
+ ## AI Assistant Instructions
637
+
638
+ When working on this site:
639
+
640
+ 1. **Read \`CUSTOMIZING.md\` first** - Understand the structure before making changes
641
+ 2. **Follow existing patterns** - Match the style of existing content
642
+ 3. **Check \`.kitfly/manifest.json\`** - Know which template was used
643
+ 4. **Respect limitations** - Content must be inside this folder; link external resources via URL
644
+ 5. **Preserve frontmatter** - Every markdown file needs title and description
645
+
646
+ ## Roles
647
+
648
+ ${
649
+ isCrucible
650
+ ? `Recommended roles for crucible maintenance:
651
+ - \`infoarch\` - Information architecture, standards structure, consistency
652
+ - \`devlead\` - Schema design, config catalogs, technical standards
653
+ - \`advisor\` - Governance, policy design, strategic decisions
654
+ - \`analyst\` - Research, gap analysis, ecosystem assessment
655
+ - \`qa\` - Validation, compliance checking, review`
656
+ : isServicebook
657
+ ? `Recommended roles for servicebook maintenance:
658
+ - \`prodstrat\` - Service strategy, offering design, market positioning
659
+ - \`advisor\` - Client engagement, industry expertise, methodology
660
+ - \`analyst\` - Research, frameworks, case study analysis
661
+ - \`devlead\` - Delivery operations, tooling, quality processes
662
+ - \`infoarch\` - Documentation structure, consistency`
663
+ : isProductbook
664
+ ? `Recommended roles for productbook maintenance:
665
+ - \`prodstrat\` - Product direction, roadmap, feature prioritization
666
+ - \`advisor\` - Domain knowledge, business context, strategic decisions
667
+ - \`analyst\` - Research, data modeling, business process analysis
668
+ - \`devlead\` - Architecture, operations, implementation
669
+ - \`infoarch\` - Documentation structure, consistency`
670
+ : isOperational
671
+ ? `Recommended roles for ${isPipeline ? "pipeline operations" : "runbook"} maintenance:
672
+ - \`devlead\` - Implementation, fixing procedures
673
+ - \`infoarch\` - Documentation structure, organization
674
+ - \`qa\` - Testing, validation, checklists`
675
+ : `Recommended roles for documentation:
676
+ - \`devlead\` - Technical content, code examples
677
+ - \`infoarch\` - Structure, navigation, organization
678
+ - \`prodmktg\` - Messaging, user-facing content`
679
+ }
680
+
681
+ See \`config/agentic/roles/\` for role definitions (if present).
682
+
683
+ ## Quick Reference
684
+
685
+ | Task | Location |
686
+ |------|----------|
687
+ | Site config | \`site.yaml\` |
688
+ | Theme/styling | \`theme.yaml\` (create if needed) |
689
+ | Add content | \`content/<section>/\` |
690
+ | Brand assets | \`assets/brand/\` |
691
+ | AI instructions | This file (\`AGENTS.md\`) |
692
+ | Customization guide | \`CUSTOMIZING.md\` |
693
+
694
+ ---
695
+
696
+ *Generated by kitfly from ${templateType} template*
697
+ `;
698
+ }
699
+
700
+ async function addAiAssistInstrumentation(root: string, ctx: TemplateContext): Promise<void> {
701
+ // Generate AGENTS.md
702
+ const agentsMd = generateAgentsMd(ctx);
703
+ await writeFile(join(root, "AGENTS.md"), agentsMd, "utf-8");
704
+ console.log(` + AGENTS.md (AI assistance)`);
705
+
706
+ // Copy relevant roles from kitfly's config
707
+ const rolesSource = join(KITFLY_ROOT, "config/agentic/roles");
708
+ const rolesTarget = join(root, "config/agentic/roles");
709
+
710
+ try {
711
+ const { readdir } = await import("node:fs/promises");
712
+ const entries = await readdir(rolesSource);
713
+
714
+ // Select roles relevant to documentation work
715
+ const relevantRoles = ["devlead.yaml", "infoarch.yaml", "qa.yaml", "README.md"];
716
+ if (ctx.template.id === "crucible") {
717
+ relevantRoles.push("advisor.yaml", "analyst.yaml");
718
+ } else if (ctx.template.id === "productbook" || ctx.template.id === "servicebook") {
719
+ relevantRoles.push("prodstrat.yaml", "advisor.yaml", "analyst.yaml");
720
+ } else if (ctx.template.id !== "runbook" && ctx.template.id !== "pipeline") {
721
+ relevantRoles.push("prodmktg.yaml");
722
+ }
723
+
724
+ await ensureDir(rolesTarget);
725
+
726
+ for (const entry of entries) {
727
+ if (relevantRoles.includes(entry)) {
728
+ const srcPath = join(rolesSource, entry);
729
+ const destPath = join(rolesTarget, entry);
730
+ const content = await readFile(srcPath, "utf-8");
731
+ await writeFile(destPath, content, "utf-8");
732
+ }
733
+ }
734
+ console.log(` + config/agentic/roles/ (${relevantRoles.length} files)`);
735
+ } catch {
736
+ // Roles directory not found - skip silently
737
+ console.log(` ! config/agentic/roles/ not copied (source not found)`);
738
+ }
739
+ }
740
+
741
+ // -----------------------------------------------------------------------------
742
+ // Template Execution
743
+ // -----------------------------------------------------------------------------
744
+
745
+ export async function runTemplate(options: InitOptions): Promise<void> {
746
+ const template = getTemplate(options.template);
747
+ if (!template) {
748
+ throw new Error(`Unknown template: ${options.template}`);
749
+ }
750
+
751
+ // Resolve inheritance chain
752
+ const chain = resolveTemplateChain(template);
753
+
754
+ // Build context
755
+ const branding: BrandingConfig = {
756
+ ...defaultBranding(options.name),
757
+ ...options.branding,
758
+ };
759
+
760
+ const ctx: TemplateContext = {
761
+ name: options.name,
762
+ branding,
763
+ template,
764
+ year: new Date().getFullYear(),
765
+ };
766
+
767
+ const root = join(process.cwd(), options.name);
768
+ const modeLabel = options.standalone ? "standalone" : "standard";
769
+
770
+ console.log(`Creating ${template.name} site (${modeLabel}): ${options.name}/\n`);
771
+
772
+ // Create root directory
773
+ await ensureDir(root);
774
+
775
+ // Create sections from all templates in chain
776
+ for (const tpl of chain) {
777
+ for (const section of tpl.sections) {
778
+ const sectionPath = join(root, section.path);
779
+ await ensureDir(sectionPath);
780
+ console.log(` + ${section.path}/`);
781
+ }
782
+ }
783
+
784
+ // Write files from all templates in chain (except README which we handle specially)
785
+ for (const tpl of chain) {
786
+ for (const file of tpl.files) {
787
+ if (file.path === "README.md") continue; // Skip, we generate mode-specific README
788
+ await writeTemplateFile(root, file, ctx);
789
+ console.log(` + ${file.path}`);
790
+ }
791
+ }
792
+
793
+ // Generate mode-specific README
794
+ const readme = options.standalone ? generateStandaloneReadme(ctx) : generateDependentReadme(ctx);
795
+ await writeFile(join(root, "README.md"), readme, "utf-8");
796
+ console.log(` + README.md`);
797
+
798
+ // Create .kitfly/ metadata folder with manifest (for all sites)
799
+ const kitflyVersion = await getKitflyVersion();
800
+ const manifest: SiteManifest = {
801
+ template: template.id,
802
+ templateVersion: template.version,
803
+ created: new Date().toISOString(),
804
+ kitflyVersion,
805
+ standalone: options.standalone ?? false,
806
+ schemaVersion: kitflyVersion,
807
+ };
808
+ await ensureDir(join(root, ".kitfly"));
809
+ await writeFile(join(root, ".kitfly/manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
810
+ console.log(` + .kitfly/manifest.json`);
811
+
812
+ // Handle standalone mode
813
+ if (options.standalone) {
814
+ // Copy site code
815
+ const provenance = await copyStandaloneFiles(root, ctx);
816
+ await stampSchemaVersion(root, kitflyVersion);
817
+
818
+ // Write standalone package.json
819
+ const packageJson = generateStandalonePackageJson(ctx);
820
+ await writeFile(join(root, "package.json"), packageJson, "utf-8");
821
+ console.log(` + package.json (standalone)`);
822
+
823
+ // Write provenance
824
+ await writeFile(
825
+ join(root, ".kitfly/provenance.json"),
826
+ JSON.stringify(provenance, null, 2),
827
+ "utf-8",
828
+ );
829
+ console.log(` + .kitfly/provenance.json`);
830
+ }
831
+
832
+ // Add AI assistance instrumentation if requested
833
+ if (options.aiAssist) {
834
+ await addAiAssistInstrumentation(root, ctx);
835
+ }
836
+
837
+ // Initialize git if requested
838
+ if (options.git !== false) {
839
+ const gitOk = await initGit(root);
840
+ if (gitOk) {
841
+ await gitCommit(root, "Initial commit from kitfly init");
842
+ console.log(` + .git/ (initialized)`);
843
+ }
844
+ }
845
+
846
+ console.log(`\n✓ Site created at ${options.name}/`);
847
+ console.log(`\nNext steps:`);
848
+ console.log(` cd ${options.name}`);
849
+
850
+ if (options.standalone) {
851
+ console.log(` bun install`);
852
+ console.log(` bun run dev`);
853
+ } else {
854
+ console.log(` kitfly dev`);
855
+ }
856
+ }
857
+
858
+ // -----------------------------------------------------------------------------
859
+ // Template Inheritance
860
+ // -----------------------------------------------------------------------------
861
+
862
+ function resolveTemplateChain(template: TemplateDef): TemplateDef[] {
863
+ const chain: TemplateDef[] = [];
864
+ let current: TemplateDef | undefined = template;
865
+
866
+ while (current) {
867
+ chain.unshift(current); // Add to front (base first)
868
+ if (current.extends) {
869
+ current = getTemplate(current.extends);
870
+ } else {
871
+ current = undefined;
872
+ }
873
+ }
874
+
875
+ return chain;
876
+ }