@defai.digital/cli 13.1.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.
Files changed (138) hide show
  1. package/LICENSE +214 -0
  2. package/dist/bin.d.ts +3 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +11 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/bootstrap.d.ts +144 -0
  7. package/dist/bootstrap.d.ts.map +1 -0
  8. package/dist/bootstrap.js +315 -0
  9. package/dist/bootstrap.js.map +1 -0
  10. package/dist/cli.d.ts +14 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +84 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands/ability.d.ts +17 -0
  15. package/dist/commands/ability.d.ts.map +1 -0
  16. package/dist/commands/ability.js +286 -0
  17. package/dist/commands/ability.js.map +1 -0
  18. package/dist/commands/agent.d.ts +18 -0
  19. package/dist/commands/agent.d.ts.map +1 -0
  20. package/dist/commands/agent.js +361 -0
  21. package/dist/commands/agent.js.map +1 -0
  22. package/dist/commands/call.d.ts +15 -0
  23. package/dist/commands/call.d.ts.map +1 -0
  24. package/dist/commands/call.js +503 -0
  25. package/dist/commands/call.js.map +1 -0
  26. package/dist/commands/cleanup.d.ts +18 -0
  27. package/dist/commands/cleanup.d.ts.map +1 -0
  28. package/dist/commands/cleanup.js +300 -0
  29. package/dist/commands/cleanup.js.map +1 -0
  30. package/dist/commands/config.d.ts +16 -0
  31. package/dist/commands/config.d.ts.map +1 -0
  32. package/dist/commands/config.js +513 -0
  33. package/dist/commands/config.js.map +1 -0
  34. package/dist/commands/discuss.d.ts +16 -0
  35. package/dist/commands/discuss.d.ts.map +1 -0
  36. package/dist/commands/discuss.js +700 -0
  37. package/dist/commands/discuss.js.map +1 -0
  38. package/dist/commands/doctor.d.ts +48 -0
  39. package/dist/commands/doctor.d.ts.map +1 -0
  40. package/dist/commands/doctor.js +356 -0
  41. package/dist/commands/doctor.js.map +1 -0
  42. package/dist/commands/guard.d.ts +12 -0
  43. package/dist/commands/guard.d.ts.map +1 -0
  44. package/dist/commands/guard.js +225 -0
  45. package/dist/commands/guard.js.map +1 -0
  46. package/dist/commands/help.d.ts +11 -0
  47. package/dist/commands/help.d.ts.map +1 -0
  48. package/dist/commands/help.js +180 -0
  49. package/dist/commands/help.js.map +1 -0
  50. package/dist/commands/history.d.ts +19 -0
  51. package/dist/commands/history.d.ts.map +1 -0
  52. package/dist/commands/history.js +200 -0
  53. package/dist/commands/history.js.map +1 -0
  54. package/dist/commands/index.d.ts +23 -0
  55. package/dist/commands/index.d.ts.map +1 -0
  56. package/dist/commands/index.js +26 -0
  57. package/dist/commands/index.js.map +1 -0
  58. package/dist/commands/iterate.d.ts +16 -0
  59. package/dist/commands/iterate.d.ts.map +1 -0
  60. package/dist/commands/iterate.js +72 -0
  61. package/dist/commands/iterate.js.map +1 -0
  62. package/dist/commands/list.d.ts +6 -0
  63. package/dist/commands/list.d.ts.map +1 -0
  64. package/dist/commands/list.js +62 -0
  65. package/dist/commands/list.js.map +1 -0
  66. package/dist/commands/mcp.d.ts +16 -0
  67. package/dist/commands/mcp.d.ts.map +1 -0
  68. package/dist/commands/mcp.js +57 -0
  69. package/dist/commands/mcp.js.map +1 -0
  70. package/dist/commands/resume.d.ts +18 -0
  71. package/dist/commands/resume.d.ts.map +1 -0
  72. package/dist/commands/resume.js +208 -0
  73. package/dist/commands/resume.js.map +1 -0
  74. package/dist/commands/review.d.ts +13 -0
  75. package/dist/commands/review.d.ts.map +1 -0
  76. package/dist/commands/review.js +450 -0
  77. package/dist/commands/review.js.map +1 -0
  78. package/dist/commands/run.d.ts +6 -0
  79. package/dist/commands/run.d.ts.map +1 -0
  80. package/dist/commands/run.js +158 -0
  81. package/dist/commands/run.js.map +1 -0
  82. package/dist/commands/scaffold.d.ts +20 -0
  83. package/dist/commands/scaffold.d.ts.map +1 -0
  84. package/dist/commands/scaffold.js +924 -0
  85. package/dist/commands/scaffold.js.map +1 -0
  86. package/dist/commands/session.d.ts +20 -0
  87. package/dist/commands/session.d.ts.map +1 -0
  88. package/dist/commands/session.js +504 -0
  89. package/dist/commands/session.js.map +1 -0
  90. package/dist/commands/setup.d.ts +14 -0
  91. package/dist/commands/setup.d.ts.map +1 -0
  92. package/dist/commands/setup.js +762 -0
  93. package/dist/commands/setup.js.map +1 -0
  94. package/dist/commands/status.d.ts +17 -0
  95. package/dist/commands/status.d.ts.map +1 -0
  96. package/dist/commands/status.js +227 -0
  97. package/dist/commands/status.js.map +1 -0
  98. package/dist/commands/trace.d.ts +6 -0
  99. package/dist/commands/trace.d.ts.map +1 -0
  100. package/dist/commands/trace.js +204 -0
  101. package/dist/commands/trace.js.map +1 -0
  102. package/dist/commands/update.d.ts +24 -0
  103. package/dist/commands/update.d.ts.map +1 -0
  104. package/dist/commands/update.js +296 -0
  105. package/dist/commands/update.js.map +1 -0
  106. package/dist/index.d.ts +8 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +13 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/parser.d.ts +14 -0
  111. package/dist/parser.d.ts.map +1 -0
  112. package/dist/parser.js +288 -0
  113. package/dist/parser.js.map +1 -0
  114. package/dist/types.d.ts +91 -0
  115. package/dist/types.d.ts.map +1 -0
  116. package/dist/types.js +29 -0
  117. package/dist/types.js.map +1 -0
  118. package/dist/utils/dangerous-op-guard.d.ts +33 -0
  119. package/dist/utils/dangerous-op-guard.d.ts.map +1 -0
  120. package/dist/utils/dangerous-op-guard.js +112 -0
  121. package/dist/utils/dangerous-op-guard.js.map +1 -0
  122. package/dist/utils/database.d.ts +85 -0
  123. package/dist/utils/database.d.ts.map +1 -0
  124. package/dist/utils/database.js +184 -0
  125. package/dist/utils/database.js.map +1 -0
  126. package/dist/utils/index.d.ts +7 -0
  127. package/dist/utils/index.d.ts.map +1 -0
  128. package/dist/utils/index.js +7 -0
  129. package/dist/utils/index.js.map +1 -0
  130. package/dist/utils/provider-factory.d.ts +31 -0
  131. package/dist/utils/provider-factory.d.ts.map +1 -0
  132. package/dist/utils/provider-factory.js +109 -0
  133. package/dist/utils/provider-factory.js.map +1 -0
  134. package/dist/utils/storage-instances.d.ts +19 -0
  135. package/dist/utils/storage-instances.d.ts.map +1 -0
  136. package/dist/utils/storage-instances.js +20 -0
  137. package/dist/utils/storage-instances.js.map +1 -0
  138. package/package.json +77 -0
@@ -0,0 +1,924 @@
1
+ /**
2
+ * Scaffold Command
3
+ *
4
+ * CLI command for scaffolding contract-first project components.
5
+ * Generates Zod schemas, invariants, guard policies, and domain packages.
6
+ *
7
+ * @module cli/commands/scaffold
8
+ */
9
+ import * as fs from 'node:fs';
10
+ import * as path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ // ============================================================================
15
+ // Templates
16
+ // ============================================================================
17
+ /**
18
+ * Generate Zod schema template
19
+ */
20
+ function generateSchemaTemplate(name, description) {
21
+ const pascalName = toPascalCase(name);
22
+ const domainCode = name.substring(0, 3).toUpperCase();
23
+ return `/**
24
+ * ${pascalName} Domain Contracts v1
25
+ *
26
+ * ${description}
27
+ *
28
+ * @module @defai.digital/contracts/${name}/v1
29
+ */
30
+
31
+ import { z } from 'zod';
32
+
33
+ // ============================================================================
34
+ // Value Objects
35
+ // ============================================================================
36
+
37
+ /**
38
+ * ${pascalName} ID value object
39
+ */
40
+ export const ${pascalName}IdSchema = z.string().uuid();
41
+
42
+ export type ${pascalName}Id = z.infer<typeof ${pascalName}IdSchema>;
43
+
44
+ // ============================================================================
45
+ // Enums
46
+ // ============================================================================
47
+
48
+ /**
49
+ * ${pascalName} status
50
+ */
51
+ export const ${pascalName}StatusSchema = z.enum([
52
+ 'draft',
53
+ 'active',
54
+ 'completed',
55
+ 'cancelled',
56
+ ]);
57
+
58
+ export type ${pascalName}Status = z.infer<typeof ${pascalName}StatusSchema>;
59
+
60
+ // ============================================================================
61
+ // Entities
62
+ // ============================================================================
63
+
64
+ /**
65
+ * ${pascalName} entity - Aggregate Root
66
+ *
67
+ * Invariants:
68
+ * - INV-${domainCode}-001: ID must be valid UUID
69
+ * - INV-${domainCode}-002: Status must be valid enum value
70
+ */
71
+ export const ${pascalName}Schema = z.object({
72
+ /** Unique identifier */
73
+ id: ${pascalName}IdSchema,
74
+
75
+ /** Current status */
76
+ status: ${pascalName}StatusSchema,
77
+
78
+ /** Timestamps */
79
+ createdAt: z.string().datetime(),
80
+ updatedAt: z.string().datetime(),
81
+ });
82
+
83
+ export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;
84
+
85
+ // ============================================================================
86
+ // Domain Events
87
+ // ============================================================================
88
+
89
+ /**
90
+ * ${pascalName} domain events
91
+ */
92
+ export const ${pascalName}EventSchema = z.discriminatedUnion('type', [
93
+ z.object({
94
+ type: z.literal('${name}.created'),
95
+ ${name}Id: ${pascalName}IdSchema,
96
+ occurredAt: z.string().datetime(),
97
+ }),
98
+ z.object({
99
+ type: z.literal('${name}.updated'),
100
+ ${name}Id: ${pascalName}IdSchema,
101
+ changes: z.record(z.unknown()),
102
+ occurredAt: z.string().datetime(),
103
+ }),
104
+ z.object({
105
+ type: z.literal('${name}.deleted'),
106
+ ${name}Id: ${pascalName}IdSchema,
107
+ occurredAt: z.string().datetime(),
108
+ }),
109
+ ]);
110
+
111
+ export type ${pascalName}Event = z.infer<typeof ${pascalName}EventSchema>;
112
+
113
+ // ============================================================================
114
+ // Request/Response Schemas
115
+ // ============================================================================
116
+
117
+ /**
118
+ * Create ${pascalName} request
119
+ */
120
+ export const Create${pascalName}RequestSchema = z.object({
121
+ // Add fields as needed
122
+ });
123
+
124
+ export type Create${pascalName}Request = z.infer<typeof Create${pascalName}RequestSchema>;
125
+
126
+ /**
127
+ * Update ${pascalName} request
128
+ */
129
+ export const Update${pascalName}RequestSchema = z.object({
130
+ id: ${pascalName}IdSchema,
131
+ // Add fields as needed
132
+ });
133
+
134
+ export type Update${pascalName}Request = z.infer<typeof Update${pascalName}RequestSchema>;
135
+
136
+ // ============================================================================
137
+ // Validation Functions
138
+ // ============================================================================
139
+
140
+ export function validate${pascalName}(data: unknown): ${pascalName} {
141
+ return ${pascalName}Schema.parse(data);
142
+ }
143
+
144
+ export function validate${pascalName}Event(data: unknown): ${pascalName}Event {
145
+ return ${pascalName}EventSchema.parse(data);
146
+ }
147
+
148
+ // ============================================================================
149
+ // Error Codes
150
+ // ============================================================================
151
+
152
+ export const ${pascalName}ErrorCode = {
153
+ NOT_FOUND: '${domainCode}_NOT_FOUND',
154
+ INVALID_STATUS: '${domainCode}_INVALID_STATUS',
155
+ VALIDATION_FAILED: '${domainCode}_VALIDATION_FAILED',
156
+ } as const;
157
+
158
+ export type ${pascalName}ErrorCode = typeof ${pascalName}ErrorCode[keyof typeof ${pascalName}ErrorCode];
159
+ `;
160
+ }
161
+ /**
162
+ * Generate invariants template
163
+ */
164
+ function generateInvariantsTemplate(name, description) {
165
+ const pascalName = toPascalCase(name);
166
+ const domainCode = name.substring(0, 3).toUpperCase();
167
+ return `# ${pascalName} Domain Invariants
168
+
169
+ ## Overview
170
+
171
+ ${description}
172
+
173
+ ## Schema Invariants
174
+
175
+ ### INV-${domainCode}-001: Valid ID Format
176
+ ${pascalName} ID MUST be a valid UUID v4.
177
+ - **Enforcement**: schema
178
+ - **Test**: \`z.string().uuid()\` rejects invalid UUIDs
179
+
180
+ ### INV-${domainCode}-002: Valid Status
181
+ Status MUST be one of the defined enum values.
182
+ - **Enforcement**: schema
183
+ - **Test**: \`z.enum([...])\` rejects invalid values
184
+
185
+ ## Runtime Invariants
186
+
187
+ ### INV-${domainCode}-101: Status Transitions
188
+ Status transitions MUST follow the defined state machine.
189
+ - **Enforcement**: runtime
190
+ - **Test**: Invalid transitions throw error
191
+ - **Valid Transitions**:
192
+ \`\`\`
193
+ draft → active, cancelled
194
+ active → completed, cancelled
195
+ completed → (terminal state)
196
+ cancelled → (terminal state)
197
+ \`\`\`
198
+
199
+ ### INV-${domainCode}-102: Timestamp Consistency
200
+ UpdatedAt MUST be >= CreatedAt.
201
+ - **Enforcement**: runtime
202
+ - **Test**: Update with earlier timestamp → error
203
+
204
+ ## Business Invariants
205
+
206
+ ### INV-${domainCode}-201: [Add business rules]
207
+ [Description of business invariant]
208
+ - **Enforcement**: [schema|runtime|test]
209
+ - **Test**: [How to verify]
210
+ - **Owner**: [Team]
211
+
212
+ ## Cross-Aggregate Invariants
213
+
214
+ ### INV-${domainCode}-301: [Add cross-aggregate rules]
215
+ [Description of cross-aggregate invariant]
216
+ - **Enforcement**: [saga|event handler]
217
+ - **Aggregates**: [List of aggregates]
218
+ - **Event**: [Triggering event] → [Resulting action]
219
+ `;
220
+ }
221
+ /**
222
+ * Generate guard policy template
223
+ */
224
+ function generateGuardPolicyTemplate(policyId, domain, radius, gates) {
225
+ return `# ${toPascalCase(domain)} Domain Guard Policy
226
+ #
227
+ # This policy governs changes to the ${domain} domain.
228
+
229
+ policy_id: ${policyId}
230
+
231
+ description: |
232
+ Guard policy for ${domain} domain development.
233
+
234
+ allowed_paths:
235
+ # Contract package
236
+ - packages/contracts/src/${domain}/**
237
+ - packages/contracts/src/${domain}/v1/schema.ts
238
+ - packages/contracts/src/${domain}/v1/invariants.md
239
+ - packages/contracts/src/${domain}/v1/index.ts
240
+
241
+ # Domain package
242
+ - packages/core/${domain}-domain/**
243
+ - packages/core/${domain}-domain/src/**
244
+
245
+ # Tests
246
+ - tests/contract/${domain}.test.ts
247
+ - tests/core/${domain}-domain.test.ts
248
+ - tests/integration/${domain}/**
249
+
250
+ forbidden_paths:
251
+ # Other domains' contracts
252
+ - packages/contracts/src/*/v1/schema.ts
253
+ - "!packages/contracts/src/${domain}/v1/schema.ts"
254
+
255
+ # Infrastructure
256
+ - packages/cli/**
257
+ - packages/mcp-server/**
258
+ - packages/guard/**
259
+
260
+ # Adapters
261
+ - packages/adapters/**
262
+
263
+ required_contracts:
264
+ - ${domain}
265
+
266
+ gates:
267
+ ${gates.map((g) => ` - ${g}`).join('\n')}
268
+
269
+ change_radius_limit: ${radius}
270
+
271
+ variables:
272
+ domain: ${domain}
273
+
274
+ metadata:
275
+ owner: ${domain}-team
276
+ created_at: "${new Date().toISOString().split('T')[0]}"
277
+ severity: medium
278
+ auto_apply: false
279
+ `;
280
+ }
281
+ /**
282
+ * Generate index.ts template
283
+ */
284
+ function generateIndexTemplate(name) {
285
+ return `/**
286
+ * ${toPascalCase(name)} Contracts v1
287
+ *
288
+ * @module @defai.digital/contracts/${name}/v1
289
+ */
290
+
291
+ export * from './schema.js';
292
+ `;
293
+ }
294
+ /**
295
+ * Generate domain package.json template
296
+ */
297
+ function generatePackageJsonTemplate(name, scope) {
298
+ return JSON.stringify({
299
+ name: `${scope}/${name}-domain`,
300
+ version: '1.0.0',
301
+ description: `${toPascalCase(name)} domain implementation`,
302
+ type: 'module',
303
+ main: 'dist/index.js',
304
+ types: 'dist/index.d.ts',
305
+ exports: {
306
+ '.': {
307
+ import: './dist/index.js',
308
+ types: './dist/index.d.ts',
309
+ },
310
+ },
311
+ scripts: {
312
+ build: 'tsc --build',
313
+ clean: 'rm -rf dist',
314
+ typecheck: 'tsc --noEmit',
315
+ },
316
+ dependencies: {
317
+ '@defai.digital/contracts': 'workspace:*',
318
+ },
319
+ devDependencies: {
320
+ typescript: '^5.3.0',
321
+ },
322
+ }, null, 2);
323
+ }
324
+ /**
325
+ * Generate domain index.ts template
326
+ */
327
+ function generateDomainIndexTemplate(name) {
328
+ const pascalName = toPascalCase(name);
329
+ return `/**
330
+ * ${pascalName} Domain
331
+ *
332
+ * @module @defai.digital/${name}-domain
333
+ */
334
+
335
+ export * from './types.js';
336
+ export * from './service.js';
337
+ `;
338
+ }
339
+ /**
340
+ * Generate domain types.ts template
341
+ */
342
+ function generateDomainTypesTemplate(name) {
343
+ const pascalName = toPascalCase(name);
344
+ return `/**
345
+ * ${pascalName} Domain Types
346
+ */
347
+
348
+ import type { ${pascalName}, ${pascalName}Event } from '@defai.digital/contracts';
349
+
350
+ /**
351
+ * ${pascalName} repository interface
352
+ */
353
+ export interface ${pascalName}Repository {
354
+ findById(id: string): Promise<${pascalName} | undefined>;
355
+ findAll(): Promise<${pascalName}[]>;
356
+ save(entity: ${pascalName}): Promise<void>;
357
+ delete(id: string): Promise<void>;
358
+ }
359
+
360
+ /**
361
+ * ${pascalName} event publisher interface
362
+ */
363
+ export interface ${pascalName}EventPublisher {
364
+ publish(event: ${pascalName}Event): Promise<void>;
365
+ }
366
+
367
+ /**
368
+ * ${pascalName} service dependencies
369
+ */
370
+ export interface ${pascalName}ServiceDeps {
371
+ repository: ${pascalName}Repository;
372
+ eventPublisher: ${pascalName}EventPublisher;
373
+ }
374
+ `;
375
+ }
376
+ /**
377
+ * Generate domain service.ts template
378
+ */
379
+ function generateDomainServiceTemplate(name) {
380
+ const pascalName = toPascalCase(name);
381
+ return `/**
382
+ * ${pascalName} Domain Service
383
+ */
384
+
385
+ import type { ${pascalName} } from '@defai.digital/contracts';
386
+ import type { ${pascalName}ServiceDeps } from './types.js';
387
+
388
+ /**
389
+ * ${pascalName} service
390
+ */
391
+ export class ${pascalName}Service {
392
+ constructor(private readonly deps: ${pascalName}ServiceDeps) {}
393
+
394
+ async findById(id: string): Promise<${pascalName} | undefined> {
395
+ return this.deps.repository.findById(id);
396
+ }
397
+
398
+ async findAll(): Promise<${pascalName}[]> {
399
+ return this.deps.repository.findAll();
400
+ }
401
+
402
+ // Add domain operations here
403
+ }
404
+
405
+ /**
406
+ * Create ${pascalName} service
407
+ */
408
+ export function create${pascalName}Service(deps: ${pascalName}ServiceDeps): ${pascalName}Service {
409
+ return new ${pascalName}Service(deps);
410
+ }
411
+ `;
412
+ }
413
+ /**
414
+ * Generate test file template
415
+ */
416
+ function generateTestTemplate(name) {
417
+ const pascalName = toPascalCase(name);
418
+ const domainCode = name.substring(0, 3).toUpperCase();
419
+ return `/**
420
+ * ${pascalName} Contract Tests
421
+ *
422
+ * Tests for schema validation and invariant enforcement.
423
+ */
424
+
425
+ import { describe, it, expect } from 'vitest';
426
+ import {
427
+ ${pascalName}Schema,
428
+ ${pascalName}EventSchema,
429
+ validate${pascalName},
430
+ ${pascalName}ErrorCode,
431
+ } from '@defai.digital/contracts';
432
+
433
+ describe('${pascalName} Contract', () => {
434
+ describe('Schema Validation', () => {
435
+ // INV-${domainCode}-001: Valid ID Format
436
+ it('should reject invalid UUID', () => {
437
+ const invalid = {
438
+ id: 'not-a-uuid',
439
+ status: 'active',
440
+ createdAt: new Date().toISOString(),
441
+ updatedAt: new Date().toISOString(),
442
+ };
443
+
444
+ expect(() => ${pascalName}Schema.parse(invalid)).toThrow();
445
+ });
446
+
447
+ // INV-${domainCode}-002: Valid Status
448
+ it('should reject invalid status', () => {
449
+ const invalid = {
450
+ id: '550e8400-e29b-41d4-a716-446655440000',
451
+ status: 'invalid-status',
452
+ createdAt: new Date().toISOString(),
453
+ updatedAt: new Date().toISOString(),
454
+ };
455
+
456
+ expect(() => ${pascalName}Schema.parse(invalid)).toThrow();
457
+ });
458
+
459
+ it('should accept valid entity', () => {
460
+ const valid = {
461
+ id: '550e8400-e29b-41d4-a716-446655440000',
462
+ status: 'active',
463
+ createdAt: new Date().toISOString(),
464
+ updatedAt: new Date().toISOString(),
465
+ };
466
+
467
+ expect(() => ${pascalName}Schema.parse(valid)).not.toThrow();
468
+ });
469
+ });
470
+
471
+ describe('Event Validation', () => {
472
+ it('should accept valid created event', () => {
473
+ const event = {
474
+ type: '${name}.created',
475
+ ${name}Id: '550e8400-e29b-41d4-a716-446655440000',
476
+ occurredAt: new Date().toISOString(),
477
+ };
478
+
479
+ expect(() => ${pascalName}EventSchema.parse(event)).not.toThrow();
480
+ });
481
+
482
+ it('should reject event with invalid type', () => {
483
+ const event = {
484
+ type: 'invalid.event',
485
+ ${name}Id: '550e8400-e29b-41d4-a716-446655440000',
486
+ occurredAt: new Date().toISOString(),
487
+ };
488
+
489
+ expect(() => ${pascalName}EventSchema.parse(event)).toThrow();
490
+ });
491
+ });
492
+ });
493
+ `;
494
+ }
495
+ // ============================================================================
496
+ // Utilities
497
+ // ============================================================================
498
+ /**
499
+ * Convert string to PascalCase
500
+ */
501
+ function toPascalCase(str) {
502
+ return str
503
+ .split(/[-_]/)
504
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
505
+ .join('');
506
+ }
507
+ /**
508
+ * Convert string to UPPER_CASE
509
+ */
510
+ function toUpperCase(str) {
511
+ return str.toUpperCase();
512
+ }
513
+ /**
514
+ * Get substring
515
+ */
516
+ function substring(str, start, end) {
517
+ return str.substring(start, end);
518
+ }
519
+ /**
520
+ * Simple Handlebars-like template engine
521
+ * Supports: {{variable}}, {{pascalCase variable}}, {{upperCase (substring variable 0 3)}}
522
+ */
523
+ function processTemplate(template, variables) {
524
+ // Helper function registry
525
+ const helpers = {
526
+ pascalCase: (s) => toPascalCase(s),
527
+ upperCase: (s) => toUpperCase(s),
528
+ substring: (s, start, end) => substring(s, parseInt(start), parseInt(end)),
529
+ };
530
+ // Process nested helpers like {{upperCase (substring domainName 0 3)}}
531
+ let result = template.replace(/\{\{(\w+)\s+\((\w+)\s+(\w+)\s+(\d+)\s+(\d+)\)\}\}/g, (_, outerHelper, innerHelper, varName, start, end) => {
532
+ const value = variables[varName] || varName;
533
+ const innerResult = helpers[innerHelper] ? helpers[innerHelper](value, start, end) : value;
534
+ return helpers[outerHelper] ? helpers[outerHelper](innerResult) : innerResult;
535
+ });
536
+ // Process single helpers like {{pascalCase domainName}}
537
+ result = result.replace(/\{\{(\w+)\s+(\w+)\}\}/g, (_, helper, varName) => {
538
+ const value = variables[varName] || varName;
539
+ return helpers[helper] ? helpers[helper](value) : value;
540
+ });
541
+ // Process simple variables like {{domainName}}
542
+ result = result.replace(/\{\{(\w+)\}\}/g, (_, varName) => {
543
+ return variables[varName] || '';
544
+ });
545
+ return result;
546
+ }
547
+ /**
548
+ * Write file with optional dry-run
549
+ */
550
+ function writeFile(filePath, content, dryRun, files) {
551
+ const action = fs.existsSync(filePath) ? 'overwrite' : 'create';
552
+ files.push({ path: filePath, action });
553
+ if (!dryRun) {
554
+ const dir = path.dirname(filePath);
555
+ if (!fs.existsSync(dir)) {
556
+ fs.mkdirSync(dir, { recursive: true });
557
+ }
558
+ fs.writeFileSync(filePath, content, 'utf-8');
559
+ }
560
+ }
561
+ // ============================================================================
562
+ // Subcommand Handlers
563
+ // ============================================================================
564
+ /**
565
+ * Handle scaffold contract subcommand
566
+ */
567
+ async function handleScaffoldContract(options) {
568
+ const { name, description = `${toPascalCase(name)} domain.`, output, dryRun = false } = options;
569
+ const outputDir = output || `packages/contracts/src/${name}/v1`;
570
+ const files = [];
571
+ // Generate files
572
+ writeFile(path.join(outputDir, 'schema.ts'), generateSchemaTemplate(name, description), dryRun, files);
573
+ writeFile(path.join(outputDir, 'invariants.md'), generateInvariantsTemplate(name, description), dryRun, files);
574
+ writeFile(path.join(outputDir, 'index.ts'), generateIndexTemplate(name), dryRun, files);
575
+ const message = dryRun
576
+ ? `Dry run - would create ${files.length} files:\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`
577
+ : `Created contract for "${name}":\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`;
578
+ return {
579
+ success: true,
580
+ message,
581
+ data: { name, files, dryRun },
582
+ exitCode: 0,
583
+ };
584
+ }
585
+ /**
586
+ * Handle scaffold domain subcommand
587
+ */
588
+ async function handleScaffoldDomain(options) {
589
+ const { name, output, scope = '@defai.digital', noTests = false, noGuard = false, dryRun = false, } = options;
590
+ const domainDir = output || `packages/core/${name}-domain`;
591
+ const files = [];
592
+ // Generate domain package files
593
+ writeFile(path.join(domainDir, 'package.json'), generatePackageJsonTemplate(name, scope), dryRun, files);
594
+ writeFile(path.join(domainDir, 'src', 'index.ts'), generateDomainIndexTemplate(name), dryRun, files);
595
+ writeFile(path.join(domainDir, 'src', 'types.ts'), generateDomainTypesTemplate(name), dryRun, files);
596
+ writeFile(path.join(domainDir, 'src', 'service.ts'), generateDomainServiceTemplate(name), dryRun, files);
597
+ // Generate test file
598
+ if (!noTests) {
599
+ writeFile(`tests/contract/${name}.test.ts`, generateTestTemplate(name), dryRun, files);
600
+ }
601
+ // Generate guard policy
602
+ if (!noGuard) {
603
+ writeFile(`packages/guard/policies/${name}-development.yaml`, generateGuardPolicyTemplate(`${name}-development`, name, 3, ['path_violation', 'dependency', 'change_radius', 'contract_tests']), dryRun, files);
604
+ }
605
+ const message = dryRun
606
+ ? `Dry run - would create ${files.length} files:\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`
607
+ : `Created domain package for "${name}":\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`;
608
+ return {
609
+ success: true,
610
+ message,
611
+ data: { name, files, dryRun },
612
+ exitCode: 0,
613
+ };
614
+ }
615
+ /**
616
+ * Handle scaffold guard subcommand
617
+ */
618
+ async function handleScaffoldGuard(options) {
619
+ const { policyId, domain = policyId.replace(/-development$/, ''), radius = 3, gates = 'path_violation,dependency,change_radius,contract_tests', dryRun = false, } = options;
620
+ const gatesList = gates.split(',').map((g) => g.trim());
621
+ const files = [];
622
+ writeFile(`packages/guard/policies/${policyId}.yaml`, generateGuardPolicyTemplate(policyId, domain, radius, gatesList), dryRun, files);
623
+ const message = dryRun
624
+ ? `Dry run - would create ${files.length} files:\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`
625
+ : `Created guard policy "${policyId}":\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`;
626
+ return {
627
+ success: true,
628
+ message,
629
+ data: { policyId, domain, files, dryRun },
630
+ exitCode: 0,
631
+ };
632
+ }
633
+ /**
634
+ * Handle scaffold project subcommand
635
+ */
636
+ async function handleScaffoldProject(options) {
637
+ const { projectName, template, domainName, scope = '@myorg', description = 'A contract-first TypeScript project', output, dryRun = false, } = options;
638
+ // Find templates directory (relative to CLI package)
639
+ // Path from packages/cli/dist/commands -> cli/dist -> cli -> packages -> automatosx (root)
640
+ const templatesDir = path.resolve(__dirname, '../../../../templates');
641
+ const templateDir = path.join(templatesDir, template);
642
+ // Check if template exists
643
+ if (!fs.existsSync(templateDir)) {
644
+ return {
645
+ success: false,
646
+ message: `Template "${template}" not found. Available templates: monorepo, standalone`,
647
+ data: undefined,
648
+ exitCode: 1,
649
+ };
650
+ }
651
+ // Load template config
652
+ const configPath = path.join(templateDir, 'template.json');
653
+ if (!fs.existsSync(configPath)) {
654
+ return {
655
+ success: false,
656
+ message: `Template config not found: ${configPath}`,
657
+ data: undefined,
658
+ exitCode: 1,
659
+ };
660
+ }
661
+ let config;
662
+ try {
663
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
664
+ }
665
+ catch (e) {
666
+ return {
667
+ success: false,
668
+ message: `Failed to parse template config: ${e}`,
669
+ data: undefined,
670
+ exitCode: 1,
671
+ };
672
+ }
673
+ // Prepare variables for template processing
674
+ const variables = {
675
+ projectName,
676
+ domainName,
677
+ scope,
678
+ description,
679
+ };
680
+ // Determine output directory
681
+ const outputDir = output || projectName;
682
+ const files = [];
683
+ // Process each item in the template structure
684
+ for (const item of config.structure) {
685
+ // Process path with variables (e.g., {{domainName}})
686
+ const outputPath = processTemplate(item.path, variables);
687
+ const fullOutputPath = path.join(outputDir, outputPath);
688
+ // Handle directory items
689
+ if (item.type === 'directory') {
690
+ const action = fs.existsSync(fullOutputPath) ? 'exists' : 'create';
691
+ files.push({ path: fullOutputPath, action: `${action} (dir)` });
692
+ if (!dryRun && !fs.existsSync(fullOutputPath)) {
693
+ fs.mkdirSync(fullOutputPath, { recursive: true });
694
+ }
695
+ continue;
696
+ }
697
+ // Handle file items
698
+ if (!item.template) {
699
+ continue;
700
+ }
701
+ // Read template file
702
+ const templatePath = path.join(templateDir, item.template);
703
+ if (!fs.existsSync(templatePath)) {
704
+ // Template file doesn't exist, skip
705
+ continue;
706
+ }
707
+ const templateContent = fs.readFileSync(templatePath, 'utf-8');
708
+ // Process template content
709
+ const processedContent = processTemplate(templateContent, variables);
710
+ // Write file
711
+ writeFile(fullOutputPath, processedContent, dryRun, files);
712
+ }
713
+ // Format postCreate commands
714
+ const postCreateCommands = config.postCreate.map(c => typeof c === 'string' ? c : c.command);
715
+ const message = dryRun
716
+ ? `Dry run - would create project "${projectName}" with ${files.length} items:\n` +
717
+ `Template: ${config.displayName}\n\n` +
718
+ `Items:\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}\n\n` +
719
+ `Post-create commands:\n${postCreateCommands.map(c => ` ${c}`).join('\n')}`
720
+ : `Created project "${projectName}":\n` +
721
+ `Template: ${config.displayName}\n\n` +
722
+ `Items:\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}\n\n` +
723
+ `Next steps:\n` +
724
+ ` cd ${outputDir}\n` +
725
+ postCreateCommands.map(c => ` ${c}`).join('\n');
726
+ return {
727
+ success: true,
728
+ message,
729
+ data: { projectName, template, domainName, files, dryRun },
730
+ exitCode: 0,
731
+ };
732
+ }
733
+ // ============================================================================
734
+ // Main Command
735
+ // ============================================================================
736
+ /**
737
+ * Handles the 'scaffold' command
738
+ *
739
+ * Subcommands:
740
+ * scaffold project <name> - Generate new project from template
741
+ * scaffold contract <name> - Generate Zod schema and invariants
742
+ * scaffold domain <name> - Generate complete domain package
743
+ * scaffold guard <id> - Generate guard policy
744
+ */
745
+ export async function scaffoldCommand(args, options) {
746
+ // Handle help request
747
+ if (args.length === 0 || args[0] === 'help' || options.help) {
748
+ return {
749
+ success: true,
750
+ message: 'Usage: ax scaffold <subcommand> [options]\n\n' +
751
+ 'Subcommands:\n' +
752
+ ' project <name> Generate new project from template\n' +
753
+ ' contract <name> Generate Zod schema and invariants\n' +
754
+ ' domain <name> Generate complete domain package\n' +
755
+ ' guard <id> Generate guard policy\n\n' +
756
+ 'Options:\n' +
757
+ ' -t, --template <type> Template: monorepo or standalone (default: standalone)\n' +
758
+ ' -m, --domain <name> Primary domain name (required for project)\n' +
759
+ ' -d, --description <desc> Domain/project description\n' +
760
+ ' -o, --output <path> Output directory\n' +
761
+ ' -s, --scope <scope> Package scope (default: @myorg)\n' +
762
+ ' -r, --radius <n> Change radius limit (default: 3)\n' +
763
+ ' -g, --gates <gates> Comma-separated gates\n' +
764
+ ' --no-tests Skip test scaffolds\n' +
765
+ ' --no-guard Skip guard policy\n' +
766
+ ' --dry-run Preview without writing files',
767
+ data: undefined,
768
+ exitCode: 0,
769
+ };
770
+ }
771
+ const subcommand = args[0];
772
+ const subArgs = args.slice(1);
773
+ // Parse common options
774
+ // Note: Due to CLI parser behavior, flag values appear as positional args
775
+ // BEFORE their flags. We need to handle this specially.
776
+ const dryRun = options.verbose || subArgs.includes('--dry-run');
777
+ // Separate flags from non-flags and track flag positions
778
+ const flags = [];
779
+ const nonFlags = [];
780
+ const boolFlags = new Set(['--dry-run', '--no-tests', '--no-guard']);
781
+ for (const arg of subArgs) {
782
+ if (arg.startsWith('-')) {
783
+ flags.push(arg);
784
+ }
785
+ else {
786
+ nonFlags.push(arg);
787
+ }
788
+ }
789
+ // Map of flag to value based on order of appearance
790
+ // The parser puts values before flags, so nonFlags[i] corresponds to flags[i] (if flags[i] expects a value)
791
+ const flagOrder = ['-m', '--domain', '-t', '--template', '-s', '--scope', '-d', '--description',
792
+ '-o', '--output', '-r', '--radius', '-g', '--gates'];
793
+ // Build a map of flags that are present
794
+ const presentFlags = [];
795
+ for (const flag of flags) {
796
+ if (!boolFlags.has(flag) && flagOrder.includes(flag)) {
797
+ presentFlags.push(flag);
798
+ }
799
+ }
800
+ // Match values to flags by order (after skipping the first non-flag which is the name)
801
+ const flagValueMap = new Map();
802
+ const numValues = nonFlags.length > 0 ? nonFlags.length - 1 : 0; // First nonFlag is the name
803
+ // presentFlags appear in the order their values appeared in nonFlags
804
+ for (let i = 0; i < Math.min(numValues, presentFlags.length); i++) {
805
+ const value = nonFlags[i + 1]; // Skip the first (name)
806
+ const flag = presentFlags[i];
807
+ if (value && flag) {
808
+ flagValueMap.set(flag, value);
809
+ }
810
+ }
811
+ // Get option values from the map
812
+ const descriptionValue = flagValueMap.get('-d') || flagValueMap.get('--description');
813
+ const outputValue = flagValueMap.get('-o') || flagValueMap.get('--output');
814
+ const scopeValue = flagValueMap.get('-s') || flagValueMap.get('--scope');
815
+ const radiusValue = flagValueMap.get('-r') || flagValueMap.get('--radius');
816
+ const gatesValue = flagValueMap.get('-g') || flagValueMap.get('--gates');
817
+ const templateValue = flagValueMap.get('-t') || flagValueMap.get('--template');
818
+ const domainValue = flagValueMap.get('-m') || flagValueMap.get('--domain');
819
+ // First non-flag is the name
820
+ const positionalArgs = nonFlags.length > 0 ? [nonFlags[0]] : [];
821
+ switch (subcommand) {
822
+ case 'project': {
823
+ const projectName = positionalArgs[0];
824
+ if (!projectName) {
825
+ return {
826
+ success: false,
827
+ message: 'Usage: ax scaffold project <name> -m <domain> [options]\n\n' +
828
+ 'Example: ax scaffold project my-app -m order -t monorepo',
829
+ data: undefined,
830
+ exitCode: 1,
831
+ };
832
+ }
833
+ if (!domainValue) {
834
+ return {
835
+ success: false,
836
+ message: 'Domain name is required. Use -m or --domain to specify.\n\n' +
837
+ 'Example: ax scaffold project my-app -m order',
838
+ data: undefined,
839
+ exitCode: 1,
840
+ };
841
+ }
842
+ const templateStr = templateValue || 'standalone';
843
+ if (templateStr !== 'monorepo' && templateStr !== 'standalone') {
844
+ return {
845
+ success: false,
846
+ message: `Invalid template: ${templateStr}. Available: monorepo, standalone`,
847
+ data: undefined,
848
+ exitCode: 1,
849
+ };
850
+ }
851
+ return handleScaffoldProject({
852
+ projectName,
853
+ template: templateStr,
854
+ domainName: domainValue,
855
+ scope: scopeValue,
856
+ description: descriptionValue,
857
+ output: outputValue,
858
+ dryRun,
859
+ });
860
+ }
861
+ case 'contract': {
862
+ const name = positionalArgs[0];
863
+ if (!name) {
864
+ return {
865
+ success: false,
866
+ message: 'Usage: ax scaffold contract <name> [options]',
867
+ data: undefined,
868
+ exitCode: 1,
869
+ };
870
+ }
871
+ return handleScaffoldContract({
872
+ name,
873
+ description: descriptionValue,
874
+ output: outputValue,
875
+ dryRun,
876
+ });
877
+ }
878
+ case 'domain': {
879
+ const name = positionalArgs[0];
880
+ if (!name) {
881
+ return {
882
+ success: false,
883
+ message: 'Usage: ax scaffold domain <name> [options]',
884
+ data: undefined,
885
+ exitCode: 1,
886
+ };
887
+ }
888
+ return handleScaffoldDomain({
889
+ name,
890
+ output: outputValue,
891
+ scope: scopeValue,
892
+ noTests: subArgs.includes('--no-tests'),
893
+ noGuard: subArgs.includes('--no-guard'),
894
+ dryRun,
895
+ });
896
+ }
897
+ case 'guard': {
898
+ const policyId = positionalArgs[0];
899
+ if (!policyId) {
900
+ return {
901
+ success: false,
902
+ message: 'Usage: ax scaffold guard <policy-id> [options]',
903
+ data: undefined,
904
+ exitCode: 1,
905
+ };
906
+ }
907
+ return handleScaffoldGuard({
908
+ policyId,
909
+ domain: domainValue || descriptionValue,
910
+ radius: radiusValue ? parseInt(radiusValue, 10) : undefined,
911
+ gates: gatesValue,
912
+ dryRun,
913
+ });
914
+ }
915
+ default:
916
+ return {
917
+ success: false,
918
+ message: `Unknown subcommand: ${subcommand}\n\nAvailable subcommands: project, contract, domain, guard`,
919
+ data: undefined,
920
+ exitCode: 1,
921
+ };
922
+ }
923
+ }
924
+ //# sourceMappingURL=scaffold.js.map