spec-snake 0.0.1-beta.0

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.
@@ -0,0 +1,848 @@
1
+ /**
2
+ * Core type definitions for spec-snake
3
+ *
4
+ * This module defines all TypeScript types used throughout the application,
5
+ * including form fields, sections, steps, scenarios, and configuration.
6
+ *
7
+ * @module types
8
+ */
9
+ import type { McpHttpServerConfig, McpSSEServerConfig, McpStdioServerConfig, Options, PermissionMode } from '@anthropic-ai/claude-agent-sdk';
10
+ /**
11
+ * Option for select/dropdown fields
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const option: SelectOption = {
16
+ * value: 'high',
17
+ * label: 'High Priority'
18
+ * };
19
+ * ```
20
+ */
21
+ export type SelectOption = {
22
+ /** Internal value used in form data */
23
+ value: string;
24
+ /** Display label shown to users */
25
+ label: string;
26
+ };
27
+ /**
28
+ * Text input field configuration
29
+ *
30
+ * Supports various input types including text, date, and URL.
31
+ * Can include autocomplete suggestions.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const field: InputField = {
36
+ * id: 'project_name',
37
+ * type: 'input',
38
+ * label: 'Project Name',
39
+ * description: 'Enter the name of your project',
40
+ * placeholder: 'My Awesome Project',
41
+ * required: true,
42
+ * inputType: 'text',
43
+ * suggestions: ['Project A', 'Project B']
44
+ * };
45
+ * ```
46
+ */
47
+ export type InputField = {
48
+ /** Unique identifier for the field (used as form data key) */
49
+ id: string;
50
+ /** Display label shown above the input */
51
+ label: string;
52
+ /** Help text describing the field's purpose */
53
+ description: string;
54
+ /** Placeholder text shown when input is empty */
55
+ placeholder?: string;
56
+ /** Whether the field must be filled before submission */
57
+ required?: boolean;
58
+ /** Field type discriminator */
59
+ type: 'input';
60
+ /** HTML input type - affects keyboard and validation */
61
+ inputType?: 'text' | 'date' | 'url';
62
+ /** Autocomplete suggestions shown as datalist options */
63
+ suggestions?: string[];
64
+ };
65
+ /**
66
+ * Multi-line text area field configuration
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const field: TextareaField = {
71
+ * id: 'description',
72
+ * type: 'textarea',
73
+ * label: 'Description',
74
+ * description: 'Provide a detailed description',
75
+ * rows: 5
76
+ * };
77
+ * ```
78
+ */
79
+ export type TextareaField = {
80
+ /** Unique identifier for the field */
81
+ id: string;
82
+ /** Display label shown above the textarea */
83
+ label: string;
84
+ /** Help text describing the field's purpose */
85
+ description: string;
86
+ /** Placeholder text shown when textarea is empty */
87
+ placeholder?: string;
88
+ /** Whether the field must be filled before submission */
89
+ required?: boolean;
90
+ /** Field type discriminator */
91
+ type: 'textarea';
92
+ /** Number of visible text rows (affects initial height) */
93
+ rows?: number;
94
+ };
95
+ /**
96
+ * Dropdown select field configuration
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const field: SelectField = {
101
+ * id: 'priority',
102
+ * type: 'select',
103
+ * label: 'Priority',
104
+ * description: 'Select the priority level',
105
+ * options: [
106
+ * { value: 'low', label: 'Low' },
107
+ * { value: 'medium', label: 'Medium' },
108
+ * { value: 'high', label: 'High' }
109
+ * ]
110
+ * };
111
+ * ```
112
+ */
113
+ export type SelectField = {
114
+ /** Unique identifier for the field */
115
+ id: string;
116
+ /** Display label shown above the select */
117
+ label: string;
118
+ /** Help text describing the field's purpose */
119
+ description: string;
120
+ /** Placeholder text (shown as first disabled option) */
121
+ placeholder?: string;
122
+ /** Whether a selection must be made before submission */
123
+ required?: boolean;
124
+ /** Field type discriminator */
125
+ type: 'select';
126
+ /** Available options for selection */
127
+ options: SelectOption[];
128
+ };
129
+ /**
130
+ * Boolean checkbox field configuration
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * const field: CheckboxField = {
135
+ * id: 'agree_terms',
136
+ * type: 'checkbox',
137
+ * label: 'I agree to the terms',
138
+ * description: 'You must agree to continue',
139
+ * required: true
140
+ * };
141
+ * ```
142
+ */
143
+ export type CheckboxField = {
144
+ /** Unique identifier for the field */
145
+ id: string;
146
+ /** Display label shown next to the checkbox */
147
+ label: string;
148
+ /** Help text describing the field's purpose */
149
+ description: string;
150
+ /** Placeholder (not typically used for checkboxes) */
151
+ placeholder?: string;
152
+ /** Whether the checkbox must be checked before submission */
153
+ required?: boolean;
154
+ /** Field type discriminator */
155
+ type: 'checkbox';
156
+ };
157
+ export type FormField = InputField | TextareaField | SelectField | CheckboxField;
158
+ /**
159
+ * Grid layout for arranging multiple fields in columns
160
+ *
161
+ * Allows organizing form fields horizontally within a section.
162
+ * Supports nested fields including other grids (recursive).
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * const layout: GridLayout = {
167
+ * type: 'grid',
168
+ * columns: 2,
169
+ * fields: [
170
+ * { id: 'first_name', type: 'input', ... },
171
+ * { id: 'last_name', type: 'input', ... }
172
+ * ]
173
+ * };
174
+ * ```
175
+ */
176
+ export type GridLayout = {
177
+ /** Layout type discriminator */
178
+ type: 'grid';
179
+ /** Number of columns (fields per row) */
180
+ columns: number;
181
+ /** Fields to arrange in the grid (can include nested layouts) */
182
+ fields: Field[];
183
+ };
184
+ export type LayoutField = GridLayout;
185
+ export type Field = FormField | LayoutField;
186
+ /**
187
+ * Single-instance section containing one set of fields
188
+ *
189
+ * Used when the user fills out the fields exactly once.
190
+ *
191
+ * @example
192
+ * ```ts
193
+ * const section: SingleSection = {
194
+ * type: 'single',
195
+ * name: 'project_info',
196
+ * fields: [...]
197
+ * };
198
+ * ```
199
+ */
200
+ export type SingleSection = {
201
+ /** Section type discriminator */
202
+ type: 'single';
203
+ /** Section name (used as key in form data output) */
204
+ name: string;
205
+ /** Fields contained in this section */
206
+ fields: Field[];
207
+ };
208
+ /**
209
+ * Array section allowing multiple instances of the same fields
210
+ *
211
+ * Used when the user can add multiple entries (e.g., team members, features).
212
+ * Each entry contains the same field structure.
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * const section: ArraySection = {
217
+ * type: 'array',
218
+ * name: 'team_members',
219
+ * fields: [
220
+ * { id: 'name', type: 'input', ... },
221
+ * { id: 'role', type: 'select', ... }
222
+ * ],
223
+ * minFieldCount: 1
224
+ * };
225
+ * ```
226
+ */
227
+ export type ArraySection = {
228
+ /** Section type discriminator */
229
+ type: 'array';
230
+ /** Section name (used as key in form data output) */
231
+ name: string;
232
+ /** Fields template for each array entry */
233
+ fields: Field[];
234
+ /** Minimum number of entries required (default: 0) */
235
+ minFieldCount?: number;
236
+ };
237
+ export type Section = SingleSection | ArraySection;
238
+ /**
239
+ * Step definition for multi-step form wizard
240
+ *
241
+ * Each step represents one page in the form wizard, containing
242
+ * a section with fields for the user to fill out.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * const step: Step = {
247
+ * slug: 'basic-info',
248
+ * title: 'Basic Information',
249
+ * description: 'Enter the basic details of your project',
250
+ * section: {
251
+ * type: 'single',
252
+ * name: 'basic',
253
+ * fields: [...]
254
+ * }
255
+ * };
256
+ * ```
257
+ */
258
+ export type Step = {
259
+ /** URL-friendly identifier (used in routing) */
260
+ slug: string;
261
+ /** Display title shown in step header */
262
+ title: string;
263
+ /** Description text shown below the title */
264
+ description: string;
265
+ /** Section containing the step's fields */
266
+ section: Section;
267
+ };
268
+ /**
269
+ * MCP (Model Context Protocol) Server Configuration
270
+ *
271
+ * Defines how to connect to an MCP server. Supports three transport types:
272
+ * - stdio: Local process started with a command
273
+ * - sse: Remote server via Server-Sent Events
274
+ * - http: Remote server via HTTP
275
+ *
276
+ * Re-exported from `@anthropic-ai/claude-agent-sdk` for convenience.
277
+ * Note: The SDK also supports an 'sdk' type for in-memory servers,
278
+ * but that requires runtime instances and is not serializable to config files.
279
+ *
280
+ * @see https://docs.anthropic.com/en/docs/claude-code/mcp
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * // stdio server (local process)
285
+ * const stdioConfig: McpServerConfig = {
286
+ * command: 'npx',
287
+ * args: ['@modelcontextprotocol/server-filesystem'],
288
+ * env: { ALLOWED_PATHS: '/home/user/projects' }
289
+ * };
290
+ *
291
+ * // SSE server (remote)
292
+ * const sseConfig: McpServerConfig = {
293
+ * type: 'sse',
294
+ * url: 'https://api.example.com/mcp/sse',
295
+ * headers: { 'Authorization': 'Bearer token' }
296
+ * };
297
+ *
298
+ * // HTTP server (remote)
299
+ * const httpConfig: McpServerConfig = {
300
+ * type: 'http',
301
+ * url: 'https://api.example.com/mcp',
302
+ * headers: { 'Authorization': 'Bearer token' }
303
+ * };
304
+ * ```
305
+ */
306
+ export type McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig;
307
+ export type { PermissionMode, McpStdioServerConfig, McpSSEServerConfig, McpHttpServerConfig, };
308
+ /**
309
+ * AI Settings for Claude Agent SDK query options
310
+ *
311
+ * Derived from the SDK's `Options` type using `Pick` to select only the
312
+ * fields relevant for config file serialization. This ensures type safety
313
+ * and automatic updates when the SDK changes.
314
+ *
315
+ * This is a simplified subset focused on model selection, tools, MCP, and permissions.
316
+ * Other SDK options (session, environment, output format, etc.) are managed by the server.
317
+ *
318
+ * @see https://docs.anthropic.com/en/docs/claude-code/sdk
319
+ * @see https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * const aiSettings: AiSettings = {
324
+ * model: 'claude-sonnet-4-5-20250929',
325
+ * maxTurns: 10,
326
+ * maxBudgetUsd: 1.0,
327
+ * permissionMode: 'bypassPermissions',
328
+ * allowDangerouslySkipPermissions: true,
329
+ * mcpServers: {
330
+ * filesystem: {
331
+ * command: 'npx',
332
+ * args: ['@modelcontextprotocol/server-filesystem']
333
+ * }
334
+ * }
335
+ * };
336
+ * ```
337
+ *
338
+ * **Available Tools:**
339
+ *
340
+ * File Operations:
341
+ * - `Read` - Read file contents
342
+ * - `Write` - Write/create files
343
+ * - `Edit` - Edit existing files
344
+ * - `Glob` - Find files by pattern
345
+ * - `Grep` - Search file contents
346
+ * - `NotebookEdit` - Edit Jupyter notebooks
347
+ *
348
+ * Command Execution:
349
+ * - `Bash` - Execute shell commands
350
+ *
351
+ * Web:
352
+ * - `WebSearch` - Search the web
353
+ * - `WebFetch` - Fetch content from URLs
354
+ *
355
+ * Agent & Task:
356
+ * - `Task` - Launch sub-agents
357
+ * - `TodoWrite` - Manage task lists
358
+ *
359
+ * Code Intelligence:
360
+ * - `LSP` - Language Server Protocol operations
361
+ *
362
+ * MCP Tools:
363
+ * - Format: `mcp__<server>__<tool>` (e.g., `mcp__filesystem__read_file`)
364
+ */
365
+ export type AiSettings = Omit<Pick<Options, 'model' | 'fallbackModel' | 'maxThinkingTokens' | 'maxTurns' | 'maxBudgetUsd' | 'allowedTools' | 'disallowedTools' | 'tools' | 'permissionMode' | 'allowDangerouslySkipPermissions' | 'mcpServers' | 'strictMcpConfig'>, 'mcpServers'> & {
366
+ /**
367
+ * MCP server configurations keyed by server name
368
+ *
369
+ * Each server provides additional tools and capabilities to the model.
370
+ * Only serializable transport types (stdio, sse, http) are supported in config files.
371
+ *
372
+ * @see https://docs.anthropic.com/en/docs/claude-code/mcp
373
+ */
374
+ mcpServers?: Record<string, McpServerConfig>;
375
+ };
376
+ /**
377
+ * Base scenario definition (serializable to JSON)
378
+ *
379
+ * Contains the core fields that can be validated by Valibot schema.
380
+ * Extended by `Scenario` with function-based fields.
381
+ */
382
+ export type ScenarioBase = {
383
+ /** Unique identifier for the scenario (used in URLs) */
384
+ id: string;
385
+ /** Display name shown in the scenario list */
386
+ name: string;
387
+ /** Steps that make up the scenario's form wizard */
388
+ steps: Step[];
389
+ /** Prompt template sent to Claude (use {{INPUT_JSON}} placeholder) */
390
+ prompt: string;
391
+ /**
392
+ * AI settings for Claude Agent SDK
393
+ * @see AiSettings
394
+ */
395
+ aiSettings?: AiSettings;
396
+ };
397
+ /**
398
+ * InputData type (transformed form data sent to Claude)
399
+ *
400
+ * This is the structure of `inputData` passed to hooks, prompts, and overrides.
401
+ * It's the same as `{{INPUT_JSON}}` in prompts.
402
+ */
403
+ export type InputData = {
404
+ items: Array<{
405
+ title: string;
406
+ description: string;
407
+ values: Array<{
408
+ label: string;
409
+ description: string;
410
+ value: unknown;
411
+ }> | Array<Array<{
412
+ label: string;
413
+ description: string;
414
+ value: unknown;
415
+ }>>;
416
+ }>;
417
+ };
418
+ /**
419
+ * Lifecycle hooks for scenario events
420
+ *
421
+ * Allows custom behavior during preview and save operations.
422
+ *
423
+ * @typeParam TFormData - Type of the raw form data from UI
424
+ */
425
+ export type ScenarioHooks<TFormData extends Record<string, unknown> = Record<string, unknown>> = {
426
+ /**
427
+ * Called after preview is generated but before displaying to user
428
+ *
429
+ * Use for logging, analytics, or transforming the preview content.
430
+ *
431
+ * @param params.formData - The raw form data from the UI
432
+ * @param params.inputData - The transformed data sent to Claude (same as {{INPUT_JSON}})
433
+ * @param params.content - The generated markdown content
434
+ */
435
+ onPreview?: (params: {
436
+ formData: TFormData;
437
+ inputData: InputData;
438
+ content: string;
439
+ }) => Promise<void>;
440
+ /**
441
+ * Called after document is saved to disk
442
+ *
443
+ * Use for post-processing, notifications, or integrations.
444
+ *
445
+ * @param params.content - The saved markdown content
446
+ * @param params.filename - The filename that was used
447
+ * @param params.outputPath - Full path to the saved file
448
+ * @param params.formData - The raw form data from the UI
449
+ * @param params.inputData - The transformed data sent to Claude (same as {{INPUT_JSON}})
450
+ */
451
+ onSave?: (params: {
452
+ content: string;
453
+ filename: string;
454
+ outputPath: string;
455
+ formData: TFormData;
456
+ inputData: InputData;
457
+ }) => Promise<void>;
458
+ };
459
+ /**
460
+ * Override options for scenario behavior
461
+ *
462
+ * @typeParam TFormData - Type of the raw form data from UI
463
+ */
464
+ export type ScenarioOverrides<TFormData extends Record<string, unknown> = Record<string, unknown>> = {
465
+ /**
466
+ * Custom filename for saved documents
467
+ *
468
+ * Can be a static string or a function for dynamic naming.
469
+ * Default format: `{scenarioId}-{timestamp}.md`
470
+ *
471
+ * @example
472
+ * ```ts
473
+ * // Static filename
474
+ * filename: 'design-doc.md'
475
+ *
476
+ * // Dynamic filename based on form data
477
+ * filename: ({ formData, timestamp }) =>
478
+ * `${formData.project_name}-${timestamp}.md`
479
+ * ```
480
+ */
481
+ filename?: string | ((params: {
482
+ scenarioId: string;
483
+ timestamp: string;
484
+ content: string;
485
+ formData: TFormData;
486
+ inputData: InputData;
487
+ }) => string);
488
+ };
489
+ /**
490
+ * Prompt template type
491
+ *
492
+ * Can be a static string or a function for dynamic prompt generation.
493
+ * Use `{{INPUT_JSON}}` in the template to inject form data.
494
+ *
495
+ * @typeParam TFormData - Type of the raw form data from UI
496
+ *
497
+ * @example
498
+ * ```ts
499
+ * // Static prompt
500
+ * prompt: `Generate a design doc based on: {{INPUT_JSON}}`
501
+ *
502
+ * // Dynamic prompt
503
+ * prompt: ({ formData, inputData }) =>
504
+ * `Create a ${formData.doc_type} document for: {{INPUT_JSON}}`
505
+ * ```
506
+ */
507
+ export type ScenarioPrompt<TFormData extends Record<string, unknown> = Record<string, unknown>> = string | ((params: {
508
+ formData: TFormData;
509
+ inputData: InputData;
510
+ }) => string);
511
+ /**
512
+ * Complete scenario definition
513
+ *
514
+ * Extends ScenarioBase with function-based fields that cannot be
515
+ * serialized to JSON (prompt can be a function, hooks, overrides).
516
+ *
517
+ * @typeParam TFormData - Type of the raw form data from UI (inferred from steps)
518
+ *
519
+ * @example
520
+ * ```ts
521
+ * const scenario: Scenario = {
522
+ * id: 'design-doc',
523
+ * name: 'Design Document',
524
+ * steps: [...],
525
+ * prompt: 'Generate a design doc based on: {{INPUT_JSON}}',
526
+ * outputDir: './docs/designs',
527
+ * aiSettings: {
528
+ * model: 'claude-sonnet-4-5'
529
+ * },
530
+ * hooks: {
531
+ * onSave: async ({ filename }) => {
532
+ * console.log(`Saved: ${filename}`);
533
+ * }
534
+ * }
535
+ * };
536
+ * ```
537
+ */
538
+ export type Scenario<TFormData extends Record<string, unknown> = Record<string, unknown>> = Omit<ScenarioBase, 'prompt'> & {
539
+ /** Prompt template (static string or dynamic function) */
540
+ prompt: ScenarioPrompt<TFormData>;
541
+ /** Directory where generated documents are saved */
542
+ outputDir?: string;
543
+ /** Lifecycle hooks for custom behavior */
544
+ hooks?: ScenarioHooks<TFormData>;
545
+ /** Override default behaviors */
546
+ overrides?: ScenarioOverrides<TFormData>;
547
+ };
548
+ /**
549
+ * Helper type to get the value type for a form field
550
+ */
551
+ type FieldValueType<F> = F extends {
552
+ type: 'checkbox';
553
+ } ? boolean : F extends {
554
+ type: 'select';
555
+ options: readonly {
556
+ value: infer V;
557
+ }[];
558
+ } ? V : string;
559
+ /**
560
+ * Helper type to create an object type from FormField array (not supporting nested GridLayout)
561
+ */
562
+ type FormFieldsToObject<Fields extends readonly FormField[]> = {
563
+ [F in Fields[number] as F['id']]: FieldValueType<F>;
564
+ };
565
+ /**
566
+ * Helper type to merge union to intersection
567
+ */
568
+ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
569
+ /**
570
+ * Infer the formData type from a Scenario's steps
571
+ *
572
+ * Use this utility type to get type-safe access to raw form data
573
+ * in hooks, prompts, and overrides.
574
+ *
575
+ * **Note**: This utility works with FormField arrays only (not GridLayout).
576
+ * Define fields with `as const` for literal type inference.
577
+ *
578
+ * @example
579
+ * ```ts
580
+ * const scenario = {
581
+ * id: 'design-doc',
582
+ * name: 'Design Document',
583
+ * steps: [
584
+ * {
585
+ * slug: 'overview',
586
+ * title: 'Overview',
587
+ * description: 'Project overview',
588
+ * section: {
589
+ * type: 'single',
590
+ * name: 'overview',
591
+ * fields: [
592
+ * { id: 'title', type: 'input', label: 'Title', description: '' },
593
+ * { id: 'description', type: 'textarea', label: 'Description', description: '' },
594
+ * ] as const,
595
+ * },
596
+ * },
597
+ * {
598
+ * slug: 'requirements',
599
+ * title: 'Requirements',
600
+ * description: 'List requirements',
601
+ * section: {
602
+ * type: 'array',
603
+ * name: 'requirements',
604
+ * fields: [
605
+ * { id: 'name', type: 'input', label: 'Name', description: '' },
606
+ * ] as const,
607
+ * },
608
+ * },
609
+ * ],
610
+ * prompt: '...',
611
+ * } as const satisfies Scenario;
612
+ *
613
+ * type MyFormData = InferFormData<typeof scenario>;
614
+ * // Result:
615
+ * // {
616
+ * // overview: { title: string; description: string };
617
+ * // requirements: Array<{ name: string }>;
618
+ * // }
619
+ * ```
620
+ */
621
+ export type InferFormData<T extends Scenario> = T['steps'] extends readonly (infer S)[] ? S extends {
622
+ section: infer Sec;
623
+ } ? Sec extends {
624
+ type: 'single';
625
+ name: infer N;
626
+ fields: readonly FormField[];
627
+ } ? N extends string ? {
628
+ [K in N]: FormFieldsToObject<Sec['fields']>;
629
+ } : never : Sec extends {
630
+ type: 'array';
631
+ name: infer N;
632
+ fields: readonly FormField[];
633
+ } ? N extends string ? {
634
+ [K in N]: Array<FormFieldsToObject<Sec['fields']>>;
635
+ } : never : never : never : never;
636
+ /**
637
+ * Infer the merged formData type from a Scenario
638
+ *
639
+ * This flattens all steps' sections into a single object type.
640
+ *
641
+ * @example
642
+ * ```ts
643
+ * type MyFormData = InferFormDataMerged<typeof scenario>;
644
+ * // {
645
+ * // overview: { title: string; description: string };
646
+ * // requirements: Array<{ name: string }>;
647
+ * // }
648
+ * ```
649
+ */
650
+ export type InferFormDataMerged<T extends Scenario> = UnionToIntersection<InferFormData<T>>;
651
+ /**
652
+ * Helper type to extract FormField from Field (handling one level of GridLayout)
653
+ */
654
+ type ExtractFormFields<F> = F extends FormField ? F : F extends {
655
+ type: 'grid';
656
+ fields: readonly (infer GF)[];
657
+ } ? GF extends FormField ? GF : never : never;
658
+ /**
659
+ * Helper type to create an object type from Field array (supporting GridLayout)
660
+ */
661
+ type FieldsToObject<Fields extends readonly Field[]> = {
662
+ [F in ExtractFormFields<Fields[number]> as F extends {
663
+ id: infer ID extends string;
664
+ } ? ID : never]?: F extends FormField ? FieldValueType<F> : unknown;
665
+ };
666
+ /**
667
+ * Infer the formData type directly from a steps array
668
+ *
669
+ * Use this utility type when defining scenarios with `defineScenario`
670
+ * for type-safe access to formData in hooks, prompts, and overrides.
671
+ *
672
+ * **Note**: For best results, define steps with `as const satisfies Step[]`
673
+ * to preserve literal types for section names and field IDs.
674
+ *
675
+ * @example
676
+ * ```ts
677
+ * const steps = [
678
+ * {
679
+ * slug: 'basic-info',
680
+ * title: 'Basic Info',
681
+ * description: 'Enter basic information',
682
+ * section: {
683
+ * type: 'single',
684
+ * name: 'basicInfo',
685
+ * fields: [
686
+ * { id: 'title', type: 'input', label: 'Title', description: '' },
687
+ * ],
688
+ * },
689
+ * },
690
+ * {
691
+ * slug: 'items',
692
+ * title: 'Items',
693
+ * description: 'Add items',
694
+ * section: {
695
+ * type: 'array',
696
+ * name: 'items',
697
+ * fields: [
698
+ * { id: 'name', type: 'input', label: 'Name', description: '' },
699
+ * ],
700
+ * },
701
+ * },
702
+ * ] as const satisfies Step[];
703
+ *
704
+ * type FormData = InferFormDataFromSteps<typeof steps>;
705
+ * // {
706
+ * // basicInfo?: { title?: string };
707
+ * // items?: Array<{ name?: string }>;
708
+ * // }
709
+ * ```
710
+ */
711
+ export type InferFormDataFromSteps<TSteps extends readonly Step[]> = UnionToIntersection<TSteps[number] extends infer S ? S extends {
712
+ section: infer Sec;
713
+ } ? Sec extends {
714
+ type: 'single';
715
+ name: infer N extends string;
716
+ fields: readonly Field[];
717
+ } ? {
718
+ [K in N]?: FieldsToObject<Sec['fields']>;
719
+ } : Sec extends {
720
+ type: 'array';
721
+ name: infer N extends string;
722
+ fields: readonly Field[];
723
+ } ? {
724
+ [K in N]?: Array<FieldsToObject<Sec['fields']>>;
725
+ } : never : never : never> & Record<string, unknown>;
726
+ /**
727
+ * Permission settings for the application
728
+ */
729
+ export type Permissions = {
730
+ /** Whether users can save generated documents to disk */
731
+ allowSave: boolean;
732
+ };
733
+ /**
734
+ * Root configuration object
735
+ *
736
+ * This is the type for the config file exported from `config.ts`.
737
+ *
738
+ * @example
739
+ * ```ts
740
+ * // config.ts
741
+ * import type { Config } from '@anthropic-ai/claude-agent-sdk';
742
+ *
743
+ * export default {
744
+ * scenarios: [
745
+ * { id: 'design-doc', name: 'Design Doc', ... }
746
+ * ],
747
+ * permissions: {
748
+ * allowSave: true
749
+ * }
750
+ * } satisfies Config;
751
+ * ```
752
+ */
753
+ export type Config = {
754
+ /** Available scenarios (each represents a document type) */
755
+ scenarios: Scenario[];
756
+ /** Global permission settings */
757
+ permissions: Permissions;
758
+ };
759
+ /**
760
+ * Helper function to define a scenario with type-safe formData
761
+ *
762
+ * This function uses TypeScript's const type parameters to infer
763
+ * literal types from the steps array, enabling type-safe access
764
+ * to formData in hooks, prompts, and overrides.
765
+ *
766
+ * **Usage**: Define your steps with `as const satisfies Step[]` for best results.
767
+ *
768
+ * @example
769
+ * ```ts
770
+ * import { defineScenario, type Step, type Config } from '@cut0/spec-snake';
771
+ *
772
+ * const steps = [
773
+ * {
774
+ * slug: 'basic-info',
775
+ * title: 'Basic Info',
776
+ * description: 'Enter basic information',
777
+ * section: {
778
+ * type: 'single',
779
+ * name: 'basicInfo',
780
+ * fields: [
781
+ * { id: 'projectName', type: 'input', label: 'Project Name', description: '' },
782
+ * { id: 'overview', type: 'textarea', label: 'Overview', description: '' },
783
+ * ],
784
+ * },
785
+ * },
786
+ * {
787
+ * slug: 'features',
788
+ * title: 'Features',
789
+ * description: 'List features',
790
+ * section: {
791
+ * type: 'array',
792
+ * name: 'features',
793
+ * fields: [
794
+ * { id: 'name', type: 'input', label: 'Feature Name', description: '' },
795
+ * ],
796
+ * },
797
+ * },
798
+ * ] as const satisfies Step[];
799
+ *
800
+ * const scenario = defineScenario({
801
+ * id: 'my-scenario',
802
+ * name: 'My Scenario',
803
+ * steps,
804
+ * prompt: '...',
805
+ * outputDir: 'docs',
806
+ * overrides: {
807
+ * filename: ({ formData }) => {
808
+ * // formData is now type-safe!
809
+ * // formData.basicInfo?.projectName is typed as string | undefined
810
+ * return `${formData.basicInfo?.projectName ?? 'untitled'}.md`;
811
+ * },
812
+ * },
813
+ * });
814
+ *
815
+ * const config: Config = {
816
+ * scenarios: [scenario],
817
+ * permissions: { allowSave: true },
818
+ * };
819
+ * ```
820
+ */
821
+ export declare function defineScenario<const TSteps extends readonly Step[]>(scenario: Omit<ScenarioBase, 'steps' | 'prompt'> & {
822
+ steps: TSteps;
823
+ prompt: ScenarioPrompt<InferFormDataFromSteps<TSteps>>;
824
+ outputDir?: string;
825
+ hooks?: ScenarioHooks<InferFormDataFromSteps<TSteps>>;
826
+ overrides?: ScenarioOverrides<InferFormDataFromSteps<TSteps>>;
827
+ }): Scenario;
828
+ /**
829
+ * Helper function to define a config object
830
+ *
831
+ * This is a simple identity function that provides better type inference
832
+ * and editor support when defining config files.
833
+ *
834
+ * @example
835
+ * ```ts
836
+ * import { defineConfig, defineScenario } from '@cut0/spec-snake';
837
+ *
838
+ * export default defineConfig({
839
+ * scenarios: [
840
+ * defineScenario({ ... }),
841
+ * ],
842
+ * permissions: {
843
+ * allowSave: true,
844
+ * },
845
+ * });
846
+ * ```
847
+ */
848
+ export declare function defineConfig(config: Config): Config;