@contractspec/bundle.workspace 1.44.1 → 1.45.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.
- package/README.md +5 -1
- package/dist/_virtual/rolldown_runtime.js +1 -19
- package/dist/ai/agents/cursor-agent.js +4 -4
- package/dist/ai/agents/cursor-agent.js.map +1 -1
- package/dist/ai/client.d.ts +14 -0
- package/dist/ai/client.d.ts.map +1 -1
- package/dist/ai/client.js +27 -1
- package/dist/ai/client.js.map +1 -1
- package/dist/ai/index.d.ts +1 -9
- package/dist/ai/index.js +1 -20
- package/dist/ai/prompts/index.js +1 -1
- package/dist/index.d.ts +42 -19
- package/dist/index.js +39 -150
- package/dist/services/agent-guide/agent-guide-service.js +3 -3
- package/dist/services/agent-guide/agent-guide-service.js.map +1 -1
- package/dist/services/ci-check/ci-check-service.d.ts.map +1 -1
- package/dist/services/ci-check/ci-check-service.js +69 -2
- package/dist/services/ci-check/ci-check-service.js.map +1 -1
- package/dist/services/ci-check/types.d.ts +1 -1
- package/dist/services/ci-check/types.d.ts.map +1 -1
- package/dist/services/ci-check/types.js +4 -2
- package/dist/services/ci-check/types.js.map +1 -1
- package/dist/services/config.d.ts +1 -11
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +2 -16
- package/dist/services/config.js.map +1 -1
- package/dist/services/create/ai-generator.d.ts +84 -0
- package/dist/services/create/ai-generator.d.ts.map +1 -0
- package/dist/services/create/ai-generator.js +178 -0
- package/dist/services/create/ai-generator.js.map +1 -0
- package/dist/services/create/index.d.ts +27 -0
- package/dist/services/create/index.d.ts.map +1 -0
- package/dist/services/create/index.js +36 -0
- package/dist/services/create/index.js.map +1 -0
- package/dist/services/create/templates.d.ts +21 -0
- package/dist/services/create/templates.d.ts.map +1 -0
- package/dist/services/create/templates.js +37 -0
- package/dist/services/create/templates.js.map +1 -0
- package/dist/services/docs/docs-service.d.ts +19 -0
- package/dist/services/docs/docs-service.d.ts.map +1 -0
- package/dist/services/docs/docs-service.js +41 -0
- package/dist/services/docs/docs-service.js.map +1 -0
- package/dist/services/docs/index.d.ts +1 -0
- package/dist/services/docs/index.js +1 -0
- package/dist/services/doctor/checks/cli.js +3 -3
- package/dist/services/doctor/checks/cli.js.map +1 -1
- package/dist/services/doctor/checks/index.js +1 -0
- package/dist/services/doctor/checks/layers.js +139 -0
- package/dist/services/doctor/checks/layers.js.map +1 -0
- package/dist/services/doctor/doctor-service.d.ts.map +1 -1
- package/dist/services/doctor/doctor-service.js +2 -0
- package/dist/services/doctor/doctor-service.js.map +1 -1
- package/dist/services/doctor/types.d.ts +1 -1
- package/dist/services/doctor/types.d.ts.map +1 -1
- package/dist/services/doctor/types.js +4 -2
- package/dist/services/doctor/types.js.map +1 -1
- package/dist/services/formatter.d.ts +15 -0
- package/dist/services/formatter.d.ts.map +1 -0
- package/dist/services/formatter.js +26 -0
- package/dist/services/formatter.js.map +1 -0
- package/dist/services/impact/formatters.d.ts +5 -5
- package/dist/services/impact/formatters.d.ts.map +1 -1
- package/dist/services/impact/formatters.js.map +1 -1
- package/dist/services/impact/impact-detection-service.js +6 -6
- package/dist/services/impact/impact-detection-service.js.map +1 -1
- package/dist/services/impact/types.d.ts +3 -3
- package/dist/services/implementation/resolver.js +1 -1
- package/dist/services/implementation/resolver.js.map +1 -1
- package/dist/services/implementation/types.d.ts +1 -1
- package/dist/services/index.d.ts +31 -5
- package/dist/services/index.js +30 -4
- package/dist/services/integrity-diagram.js +1 -1
- package/dist/services/integrity-diagram.js.map +1 -1
- package/dist/services/integrity.d.ts +1 -1
- package/dist/services/integrity.js.map +1 -1
- package/dist/services/layer-discovery.d.ts +77 -0
- package/dist/services/layer-discovery.d.ts.map +1 -0
- package/dist/services/layer-discovery.js +121 -0
- package/dist/services/layer-discovery.js.map +1 -0
- package/dist/services/llm/index.d.ts +28 -0
- package/dist/services/llm/index.d.ts.map +1 -0
- package/dist/services/llm/index.js +187 -0
- package/dist/services/llm/index.js.map +1 -0
- package/dist/services/llm/verify-static.d.ts +26 -0
- package/dist/services/llm/verify-static.d.ts.map +1 -0
- package/dist/services/llm/verify-static.js +82 -0
- package/dist/services/llm/verify-static.js.map +1 -0
- package/dist/services/openapi/import-service.d.ts.map +1 -1
- package/dist/services/openapi/import-service.js +98 -4
- package/dist/services/openapi/import-service.js.map +1 -1
- package/dist/services/openapi/sync-service.js +1 -1
- package/dist/services/setup/config-generators.js +1 -1
- package/dist/services/setup/config-generators.js.map +1 -1
- package/dist/services/sync.d.ts +2 -1
- package/dist/services/sync.d.ts.map +1 -1
- package/dist/services/sync.js +2 -1
- package/dist/services/sync.js.map +1 -1
- package/dist/services/test/index.d.ts +1 -0
- package/dist/services/test/index.js +1 -0
- package/dist/services/test/test-service.d.ts +22 -0
- package/dist/services/test/test-service.d.ts.map +1 -0
- package/dist/services/test/test-service.js +81 -0
- package/dist/services/test/test-service.js.map +1 -0
- package/dist/services/validate/blueprint-validator.d.ts +23 -0
- package/dist/services/validate/blueprint-validator.d.ts.map +1 -0
- package/dist/services/validate/blueprint-validator.js +50 -0
- package/dist/services/validate/blueprint-validator.js.map +1 -0
- package/dist/services/validate/implementation-agent-validator.d.ts +20 -0
- package/dist/services/validate/implementation-agent-validator.d.ts.map +1 -0
- package/dist/services/validate/implementation-agent-validator.js +42 -0
- package/dist/services/validate/implementation-agent-validator.js.map +1 -0
- package/dist/services/{validate-implementation.d.ts → validate/implementation-validator.d.ts} +3 -3
- package/dist/services/validate/implementation-validator.d.ts.map +1 -0
- package/dist/services/{validate-implementation.js → validate/implementation-validator.js} +2 -2
- package/dist/services/validate/implementation-validator.js.map +1 -0
- package/dist/services/validate/index.d.ts +5 -0
- package/dist/services/validate/index.js +5 -0
- package/dist/services/{validate.d.ts → validate/spec-validator.d.ts} +5 -4
- package/dist/services/validate/spec-validator.d.ts.map +1 -0
- package/dist/services/{validate.js → validate/spec-validator.js} +6 -4
- package/dist/services/validate/spec-validator.js.map +1 -0
- package/dist/services/validate/tenant-validator.d.ts +21 -0
- package/dist/services/validate/tenant-validator.d.ts.map +1 -0
- package/dist/services/validate/tenant-validator.js +165 -0
- package/dist/services/validate/tenant-validator.js.map +1 -0
- package/dist/services/watch.js +2 -1
- package/dist/services/watch.js.map +1 -1
- package/dist/templates/data-view.template.js +3 -3
- package/dist/templates/data-view.template.js.map +1 -1
- package/dist/templates/event.template.js +3 -1
- package/dist/templates/event.template.js.map +1 -1
- package/dist/templates/operation.template.js +3 -1
- package/dist/templates/operation.template.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils/module-loader.js +41 -0
- package/dist/utils/module-loader.js.map +1 -0
- package/package.json +9 -9
- package/dist/ai/index.d.ts.map +0 -1
- package/dist/ai/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/services/test.d.ts +0 -15
- package/dist/services/test.d.ts.map +0 -1
- package/dist/services/test.js +0 -30
- package/dist/services/test.js.map +0 -1
- package/dist/services/validate-implementation.d.ts.map +0 -1
- package/dist/services/validate-implementation.js.map +0 -1
- package/dist/services/validate.d.ts.map +0 -1
- package/dist/services/validate.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-service.js","names":["files: OpenApiImportServiceResult['files']","skippedOperations: OpenApiImportServiceResult['skippedOperations']","errorMessages: OpenApiImportServiceResult['errorMessages']"],"sources":["../../../src/services/openapi/import-service.ts"],"sourcesContent":["/**\n * OpenAPI import service - imports specs from OpenAPI documents.\n */\n\nimport {\n importFromOpenApi,\n parseOpenApi,\n} from '@contractspec/lib.contracts-transformers/openapi';\nimport type { FsAdapter } from '../../ports/fs';\nimport type { LoggerAdapter } from '../../ports/logger';\nimport type {\n OpenApiImportServiceOptions,\n OpenApiImportServiceResult,\n} from './types';\nimport { dirname, join } from 'path';\nimport type { ContractsrcConfig } from '@contractspec/lib.contracts';\n\n/**\n * Import ContractSpec specs from an OpenAPI document.\n */\nexport async function importFromOpenApiService(\n contractspecOptions: ContractsrcConfig,\n options: OpenApiImportServiceOptions,\n adapters: { fs: FsAdapter; logger: LoggerAdapter }\n): Promise<OpenApiImportServiceResult> {\n const { fs, logger } = adapters;\n const {\n source,\n outputDir,\n prefix,\n tags,\n exclude,\n defaultStability,\n defaultOwners,\n defaultAuth,\n dryRun = false,\n } = options;\n\n logger.info(`Importing from OpenAPI: ${source}`);\n\n // Parse the OpenAPI document\n const parseResult = await parseOpenApi(source, {\n fetch: globalThis.fetch,\n readFile: (path) => fs.readFile(path),\n });\n\n if (parseResult.warnings.length > 0) {\n for (const warning of parseResult.warnings) {\n logger.warn(`Parse warning: ${warning}`);\n }\n }\n\n logger.info(\n `Parsed ${parseResult.operations.length} operations from ${parseResult.info.title} v${parseResult.info.version}`\n );\n\n // Import operations\n const importResult = importFromOpenApi(parseResult, contractspecOptions, {\n prefix,\n tags,\n exclude,\n defaultStability,\n defaultOwners,\n defaultAuth,\n });\n\n logger.info(\n `Import result: ${importResult.summary.imported} imported, ${importResult.summary.skipped} skipped, ${importResult.summary.errors} errors`\n );\n\n const files: OpenApiImportServiceResult['files'] = [];\n const skippedOperations: OpenApiImportServiceResult['skippedOperations'] = [];\n const errorMessages: OpenApiImportServiceResult['errorMessages'] = [];\n\n // Write imported specs\n for (const spec of importResult.operationSpecs) {\n const filePath = join(outputDir, spec.fileName);\n\n if (dryRun) {\n logger.info(`[DRY RUN] Would create: ${filePath}`);\n } else {\n // Ensure directory exists\n const dir = dirname(filePath);\n await fs.mkdir(dir);\n\n // Write spec file\n await fs.writeFile(filePath, spec.code);\n logger.info(`Created: ${filePath}`);\n }\n\n files.push({\n path: filePath,\n operationId: spec.source.sourceId,\n specName: spec.fileName.replace('.ts', ''),\n });\n }\n\n // Record skipped operations\n for (const skipped of importResult.skipped) {\n skippedOperations.push({\n operationId: skipped.sourceId,\n reason: skipped.reason,\n });\n logger.debug(`Skipped: ${skipped.sourceId} - ${skipped.reason}`);\n }\n\n // Record errors\n for (const error of importResult.errors) {\n errorMessages.push({\n operationId: error.sourceId,\n error: error.error,\n });\n logger.error(`Error: ${error.sourceId} - ${error.error}`);\n }\n\n return {\n imported: importResult.summary.imported,\n skipped: importResult.summary.skipped,\n errors: importResult.summary.errors,\n files,\n skippedOperations,\n errorMessages,\n };\n}\n"],"mappings":";;;;;;;;;;AAoBA,eAAsB,yBACpB,qBACA,SACA,UACqC;CACrC,MAAM,EAAE,IAAI,WAAW;CACvB,MAAM,EACJ,QACA,WACA,QACA,MACA,SACA,kBACA,eACA,aACA,SAAS,UACP;AAEJ,QAAO,KAAK,2BAA2B,SAAS;CAGhD,MAAM,cAAc,MAAM,aAAa,QAAQ;EAC7C,OAAO,WAAW;EAClB,WAAW,SAAS,GAAG,SAAS,KAAK;EACtC,CAAC;AAEF,KAAI,YAAY,SAAS,SAAS,EAChC,MAAK,MAAM,WAAW,YAAY,SAChC,QAAO,KAAK,kBAAkB,UAAU;AAI5C,QAAO,KACL,UAAU,YAAY,WAAW,OAAO,mBAAmB,YAAY,KAAK,MAAM,IAAI,YAAY,KAAK,UACxG;CAGD,MAAM,eAAe,kBAAkB,aAAa,qBAAqB;EACvE;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,QAAO,KACL,kBAAkB,aAAa,QAAQ,SAAS,aAAa,aAAa,QAAQ,QAAQ,YAAY,aAAa,QAAQ,OAAO,SACnI;CAED,MAAMA,QAA6C,EAAE;CACrD,MAAMC,oBAAqE,EAAE;CAC7E,MAAMC,gBAA6D,EAAE;AAGrE,MAAK,MAAM,QAAQ,aAAa,gBAAgB;EAC9C,MAAM,WAAW,KAAK,WAAW,KAAK,SAAS;AAE/C,MAAI,OACF,QAAO,KAAK,2BAA2B,WAAW;OAC7C;GAEL,MAAM,MAAM,QAAQ,SAAS;AAC7B,SAAM,GAAG,MAAM,IAAI;AAGnB,SAAM,GAAG,UAAU,UAAU,KAAK,KAAK;AACvC,UAAO,KAAK,YAAY,WAAW;;AAGrC,QAAM,KAAK;GACT,MAAM;GACN,aAAa,KAAK,OAAO;GACzB,UAAU,KAAK,SAAS,QAAQ,OAAO,GAAG;GAC3C,CAAC;;AAIJ,MAAK,MAAM,WAAW,aAAa,SAAS;AAC1C,oBAAkB,KAAK;GACrB,aAAa,QAAQ;GACrB,QAAQ,QAAQ;GACjB,CAAC;AACF,SAAO,MAAM,YAAY,QAAQ,SAAS,KAAK,QAAQ,SAAS;;AAIlE,MAAK,MAAM,SAAS,aAAa,QAAQ;AACvC,gBAAc,KAAK;GACjB,aAAa,MAAM;GACnB,OAAO,MAAM;GACd,CAAC;AACF,SAAO,MAAM,UAAU,MAAM,SAAS,KAAK,MAAM,QAAQ;;AAG3D,QAAO;EACL,UAAU,aAAa,QAAQ;EAC/B,SAAS,aAAa,QAAQ;EAC9B,QAAQ,aAAa,QAAQ;EAC7B;EACA;EACA;EACD"}
|
|
1
|
+
{"version":3,"file":"import-service.js","names":["files: OpenApiImportServiceResult['files']","skippedOperations: OpenApiImportServiceResult['skippedOperations']","errorMessages: OpenApiImportServiceResult['errorMessages']","type: 'operation' | 'event' | 'model'","match: RegExpMatchArray | null"],"sources":["../../../src/services/openapi/import-service.ts"],"sourcesContent":["/**\n * OpenAPI import service - imports specs from OpenAPI documents.\n */\n\nimport {\n importFromOpenApi,\n parseOpenApi,\n} from '@contractspec/lib.contracts-transformers/openapi';\nimport type { FsAdapter } from '../../ports/fs';\nimport type { LoggerAdapter } from '../../ports/logger';\nimport type {\n OpenApiImportServiceOptions,\n OpenApiImportServiceResult,\n} from './types';\nimport { dirname, join, basename } from 'path';\nimport type { ContractsrcConfig } from '@contractspec/lib.contracts';\n\n/**\n * Get output directory for spec type\n */\nfunction getOutputDir(\n type: 'operation' | 'event' | 'model',\n options: OpenApiImportServiceOptions,\n config: ContractsrcConfig\n): string {\n // If outputDir is explicitly set in options, use it for all types\n if (options.outputDir) {\n return options.outputDir;\n }\n\n // Default base\n const baseDir = config.outputDir ?? 'src';\n const conventions = config.conventions ?? {\n operations: 'operations',\n events: 'events',\n };\n\n switch (type) {\n case 'operation':\n // Conventions usually format like \"operations/**\" or \"operations\"\n return join(\n baseDir,\n conventions.operations.split('|')[0] ?? 'operations'\n );\n case 'event':\n return join(baseDir, conventions.events ?? 'events');\n case 'model':\n return join(baseDir, 'models'); // Standardize on 'models' for now\n default:\n return baseDir;\n }\n}\n\n/**\n * Import ContractSpec specs from an OpenAPI document.\n */\nexport async function importFromOpenApiService(\n contractspecOptions: ContractsrcConfig,\n options: OpenApiImportServiceOptions,\n adapters: { fs: FsAdapter; logger: LoggerAdapter }\n): Promise<OpenApiImportServiceResult> {\n const { fs, logger } = adapters;\n const {\n source,\n\n prefix,\n tags,\n exclude,\n defaultStability,\n defaultOwners,\n defaultAuth,\n dryRun = false,\n } = options;\n\n logger.info(`Importing from OpenAPI: ${source}`);\n\n // Parse the OpenAPI document\n // Use globalThis.fetch because adapters don't have networking yet\n // but we use fs.readFile for local files\n const parseResult = await parseOpenApi(source, {\n fetch: globalThis.fetch,\n readFile: (path) => fs.readFile(path),\n });\n\n if (parseResult.warnings.length > 0) {\n for (const warning of parseResult.warnings) {\n logger.warn(`Parse warning: ${warning}`);\n }\n }\n\n logger.info(\n `Parsed ${parseResult.operations.length} operations from ${parseResult.info.title} v${parseResult.info.version}`\n );\n\n // Import operations\n const importResult = importFromOpenApi(parseResult, contractspecOptions, {\n prefix,\n tags,\n exclude,\n defaultStability,\n defaultOwners,\n defaultAuth,\n });\n\n logger.info(\n `Import result: ${importResult.summary.imported} imported, ${importResult.summary.skipped} skipped, ${importResult.summary.errors} errors`\n );\n\n const files: OpenApiImportServiceResult['files'] = [];\n const skippedOperations: OpenApiImportServiceResult['skippedOperations'] = [];\n const errorMessages: OpenApiImportServiceResult['errorMessages'] = [];\n\n // Write imported specs\n const specsByDir = new Map<\n string,\n { file: string; name: string; type: 'operation' | 'event' | 'model' }[]\n >();\n\n for (const spec of importResult.operationSpecs) {\n // Determine type FIRST to resolve output directory\n let type: 'operation' | 'event' | 'model' = 'operation';\n let match: RegExpMatchArray | null = null;\n if (spec.code.includes('defineEvent(')) {\n type = 'event';\n match = spec.code.match(/export const (\\w+)\\s*=\\s*defineEvent/);\n } else if (\n (spec.code.includes('defineSchemaModel(') ||\n spec.code.includes('new EnumType(') ||\n spec.code.includes('ScalarTypeEnum.') ||\n spec.code.includes('new ZodSchemaType(') ||\n spec.code.includes('z.enum(') ||\n spec.code.includes('new JsonSchemaType(') ||\n spec.code.includes('new GraphQLSchemaType(')) &&\n !spec.code.includes('defineCommand(') &&\n !spec.code.includes('defineQuery(')\n ) {\n type = 'model';\n } else {\n type = 'operation';\n match = spec.code.match(\n /export const (\\w+)\\s*=\\s*define(?:Command|Query)/\n );\n }\n\n // Resolve output directory based on type\n const targetDir = getOutputDir(type, options, contractspecOptions);\n const filePath = join(targetDir, spec.fileName);\n\n if (!match && type === 'model') {\n if (spec.code.includes('new ZodSchemaType(')) {\n match = spec.code.match(/export const (\\w+)\\s*=\\s*new ZodSchemaType\\(/);\n } else if (spec.code.includes('new JsonSchemaType(')) {\n match = spec.code.match(\n /export const (\\w+)\\s*=\\s*new JsonSchemaType\\(/\n );\n } else if (spec.code.includes('new GraphQLSchemaType(')) {\n match = spec.code.match(\n /export const (\\w+)\\s*=\\s*new GraphQLSchemaType\\(/\n );\n }\n if (!match) {\n match = spec.code.match(/export const (\\w+)\\s*=/);\n }\n }\n\n if (dryRun) {\n logger.info(`[DRY RUN] Would create: ${filePath}`);\n } else {\n // Ensure directory exists\n const dir = dirname(filePath);\n const exists = await fs.exists(dir);\n if (!exists) {\n await fs.mkdir(dir);\n }\n\n // Write spec file\n await fs.writeFile(filePath, spec.code);\n logger.info(`Created: ${filePath}`);\n }\n\n files.push({\n path: filePath,\n operationId: spec.source.sourceId,\n specName: spec.fileName.replace('.ts', ''),\n });\n\n if (match) {\n const dir = dirname(filePath);\n const existing = specsByDir.get(dir) || [];\n existing.push({\n file: basename(filePath),\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n name: match[1]!,\n type,\n });\n specsByDir.set(dir, existing);\n }\n }\n\n // Generate registries\n if (!dryRun && files.length > 0) {\n for (const [dir, specs] of specsByDir.entries()) {\n if (specs.length === 0) continue;\n\n // Detect dominant type\n const types = specs.map((s) => s.type);\n const isOperations = types.every((t) => t === 'operation');\n const isEvents = types.every((t) => t === 'event');\n const isModels = types.every((t) => t === 'model');\n\n // Generate Registry File\n let registryCode = `/**\\n * Auto-generated registry file.\\n */\\n`;\n specs.forEach((s) => {\n const importPath = `./${basename(s.file, '.ts')}`;\n registryCode += `import { ${s.name} } from '${importPath}';\\n`;\n });\n registryCode += '\\n';\n\n let hasRegistry = false;\n if (isOperations) {\n registryCode += `import { OperationSpecRegistry } from '@contractspec/lib.contracts';\\n\\n`;\n registryCode += `export const operationRegistry = new OperationSpecRegistry();\\n`;\n specs.forEach((s) => {\n registryCode += `operationRegistry.register(${s.name});\\n`;\n });\n hasRegistry = true;\n } else if (isEvents) {\n registryCode += `import { EventRegistry } from '@contractspec/lib.contracts';\\n\\n`;\n registryCode += `export const eventRegistry = new EventRegistry();\\n`;\n specs.forEach((s) => {\n registryCode += `eventRegistry.register(${s.name});\\n`;\n });\n hasRegistry = true;\n } else if (isModels) {\n registryCode += `import { ModelRegistry } from '@contractspec/lib.contracts';\\n\\n`;\n registryCode += `export const modelRegistry = new ModelRegistry();\\n`;\n specs.forEach((s) => {\n registryCode += `modelRegistry.register(${s.name});\\n`;\n });\n hasRegistry = true;\n }\n\n if (hasRegistry) {\n const registryPath = join(dir, 'registry.ts');\n await fs.writeFile(registryPath, registryCode);\n logger.info(`Created/Updated registry: ${registryPath}`);\n }\n\n // Generate Index File\n let indexCode = `/**\\n * Auto-generated barrel file.\\n */\\n\\n`;\n specs.forEach((s) => {\n const importPath = `./${basename(s.file, '.ts')}`;\n indexCode += `export * from '${importPath}';\\n`;\n });\n\n if (hasRegistry) {\n indexCode += `export * from './registry';\\n`;\n }\n\n const indexPath = join(dir, 'index.ts');\n await fs.writeFile(indexPath, indexCode);\n logger.info(`Created/Updated index: ${indexPath}`);\n }\n }\n\n // Record skipped operations\n for (const skipped of importResult.skipped) {\n skippedOperations.push({\n operationId: skipped.sourceId,\n reason: skipped.reason,\n });\n logger.debug(`Skipped: ${skipped.sourceId} - ${skipped.reason}`);\n }\n\n // Record errors\n for (const error of importResult.errors) {\n errorMessages.push({\n operationId: error.sourceId,\n error: error.error,\n });\n logger.error(`Error: ${error.sourceId} - ${error.error}`);\n }\n\n return {\n imported: importResult.summary.imported,\n skipped: importResult.summary.skipped,\n errors: importResult.summary.errors,\n files,\n skippedOperations,\n errorMessages,\n };\n}\n"],"mappings":";;;;;;;;;;AAoBA,SAAS,aACP,MACA,SACA,QACQ;AAER,KAAI,QAAQ,UACV,QAAO,QAAQ;CAIjB,MAAM,UAAU,OAAO,aAAa;CACpC,MAAM,cAAc,OAAO,eAAe;EACxC,YAAY;EACZ,QAAQ;EACT;AAED,SAAQ,MAAR;EACE,KAAK,YAEH,QAAO,KACL,SACA,YAAY,WAAW,MAAM,IAAI,CAAC,MAAM,aACzC;EACH,KAAK,QACH,QAAO,KAAK,SAAS,YAAY,UAAU,SAAS;EACtD,KAAK,QACH,QAAO,KAAK,SAAS,SAAS;EAChC,QACE,QAAO;;;;;;AAOb,eAAsB,yBACpB,qBACA,SACA,UACqC;CACrC,MAAM,EAAE,IAAI,WAAW;CACvB,MAAM,EACJ,QAEA,QACA,MACA,SACA,kBACA,eACA,aACA,SAAS,UACP;AAEJ,QAAO,KAAK,2BAA2B,SAAS;CAKhD,MAAM,cAAc,MAAM,aAAa,QAAQ;EAC7C,OAAO,WAAW;EAClB,WAAW,SAAS,GAAG,SAAS,KAAK;EACtC,CAAC;AAEF,KAAI,YAAY,SAAS,SAAS,EAChC,MAAK,MAAM,WAAW,YAAY,SAChC,QAAO,KAAK,kBAAkB,UAAU;AAI5C,QAAO,KACL,UAAU,YAAY,WAAW,OAAO,mBAAmB,YAAY,KAAK,MAAM,IAAI,YAAY,KAAK,UACxG;CAGD,MAAM,eAAe,kBAAkB,aAAa,qBAAqB;EACvE;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,QAAO,KACL,kBAAkB,aAAa,QAAQ,SAAS,aAAa,aAAa,QAAQ,QAAQ,YAAY,aAAa,QAAQ,OAAO,SACnI;CAED,MAAMA,QAA6C,EAAE;CACrD,MAAMC,oBAAqE,EAAE;CAC7E,MAAMC,gBAA6D,EAAE;CAGrE,MAAM,6BAAa,IAAI,KAGpB;AAEH,MAAK,MAAM,QAAQ,aAAa,gBAAgB;EAE9C,IAAIC,OAAwC;EAC5C,IAAIC,QAAiC;AACrC,MAAI,KAAK,KAAK,SAAS,eAAe,EAAE;AACtC,UAAO;AACP,WAAQ,KAAK,KAAK,MAAM,uCAAuC;cAE9D,KAAK,KAAK,SAAS,qBAAqB,IACvC,KAAK,KAAK,SAAS,gBAAgB,IACnC,KAAK,KAAK,SAAS,kBAAkB,IACrC,KAAK,KAAK,SAAS,qBAAqB,IACxC,KAAK,KAAK,SAAS,UAAU,IAC7B,KAAK,KAAK,SAAS,sBAAsB,IACzC,KAAK,KAAK,SAAS,yBAAyB,KAC9C,CAAC,KAAK,KAAK,SAAS,iBAAiB,IACrC,CAAC,KAAK,KAAK,SAAS,eAAe,CAEnC,QAAO;OACF;AACL,UAAO;AACP,WAAQ,KAAK,KAAK,MAChB,mDACD;;EAKH,MAAM,WAAW,KADC,aAAa,MAAM,SAAS,oBAAoB,EACjC,KAAK,SAAS;AAE/C,MAAI,CAAC,SAAS,SAAS,SAAS;AAC9B,OAAI,KAAK,KAAK,SAAS,qBAAqB,CAC1C,SAAQ,KAAK,KAAK,MAAM,+CAA+C;YAC9D,KAAK,KAAK,SAAS,sBAAsB,CAClD,SAAQ,KAAK,KAAK,MAChB,gDACD;YACQ,KAAK,KAAK,SAAS,yBAAyB,CACrD,SAAQ,KAAK,KAAK,MAChB,mDACD;AAEH,OAAI,CAAC,MACH,SAAQ,KAAK,KAAK,MAAM,yBAAyB;;AAIrD,MAAI,OACF,QAAO,KAAK,2BAA2B,WAAW;OAC7C;GAEL,MAAM,MAAM,QAAQ,SAAS;AAE7B,OAAI,CADW,MAAM,GAAG,OAAO,IAAI,CAEjC,OAAM,GAAG,MAAM,IAAI;AAIrB,SAAM,GAAG,UAAU,UAAU,KAAK,KAAK;AACvC,UAAO,KAAK,YAAY,WAAW;;AAGrC,QAAM,KAAK;GACT,MAAM;GACN,aAAa,KAAK,OAAO;GACzB,UAAU,KAAK,SAAS,QAAQ,OAAO,GAAG;GAC3C,CAAC;AAEF,MAAI,OAAO;GACT,MAAM,MAAM,QAAQ,SAAS;GAC7B,MAAM,WAAW,WAAW,IAAI,IAAI,IAAI,EAAE;AAC1C,YAAS,KAAK;IACZ,MAAM,SAAS,SAAS;IAExB,MAAM,MAAM;IACZ;IACD,CAAC;AACF,cAAW,IAAI,KAAK,SAAS;;;AAKjC,KAAI,CAAC,UAAU,MAAM,SAAS,EAC5B,MAAK,MAAM,CAAC,KAAK,UAAU,WAAW,SAAS,EAAE;AAC/C,MAAI,MAAM,WAAW,EAAG;EAGxB,MAAM,QAAQ,MAAM,KAAK,MAAM,EAAE,KAAK;EACtC,MAAM,eAAe,MAAM,OAAO,MAAM,MAAM,YAAY;EAC1D,MAAM,WAAW,MAAM,OAAO,MAAM,MAAM,QAAQ;EAClD,MAAM,WAAW,MAAM,OAAO,MAAM,MAAM,QAAQ;EAGlD,IAAI,eAAe;AACnB,QAAM,SAAS,MAAM;GACnB,MAAM,aAAa,KAAK,SAAS,EAAE,MAAM,MAAM;AAC/C,mBAAgB,YAAY,EAAE,KAAK,WAAW,WAAW;IACzD;AACF,kBAAgB;EAEhB,IAAI,cAAc;AAClB,MAAI,cAAc;AAChB,mBAAgB;AAChB,mBAAgB;AAChB,SAAM,SAAS,MAAM;AACnB,oBAAgB,8BAA8B,EAAE,KAAK;KACrD;AACF,iBAAc;aACL,UAAU;AACnB,mBAAgB;AAChB,mBAAgB;AAChB,SAAM,SAAS,MAAM;AACnB,oBAAgB,0BAA0B,EAAE,KAAK;KACjD;AACF,iBAAc;aACL,UAAU;AACnB,mBAAgB;AAChB,mBAAgB;AAChB,SAAM,SAAS,MAAM;AACnB,oBAAgB,0BAA0B,EAAE,KAAK;KACjD;AACF,iBAAc;;AAGhB,MAAI,aAAa;GACf,MAAM,eAAe,KAAK,KAAK,cAAc;AAC7C,SAAM,GAAG,UAAU,cAAc,aAAa;AAC9C,UAAO,KAAK,6BAA6B,eAAe;;EAI1D,IAAI,YAAY;AAChB,QAAM,SAAS,MAAM;GACnB,MAAM,aAAa,KAAK,SAAS,EAAE,MAAM,MAAM;AAC/C,gBAAa,kBAAkB,WAAW;IAC1C;AAEF,MAAI,YACF,cAAa;EAGf,MAAM,YAAY,KAAK,KAAK,WAAW;AACvC,QAAM,GAAG,UAAU,WAAW,UAAU;AACxC,SAAO,KAAK,0BAA0B,YAAY;;AAKtD,MAAK,MAAM,WAAW,aAAa,SAAS;AAC1C,oBAAkB,KAAK;GACrB,aAAa,QAAQ;GACrB,QAAQ,QAAQ;GACjB,CAAC;AACF,SAAO,MAAM,YAAY,QAAQ,SAAS,KAAK,QAAQ,SAAS;;AAIlE,MAAK,MAAM,SAAS,aAAa,QAAQ;AACvC,gBAAc,KAAK;GACjB,aAAa,MAAM;GACnB,OAAO,MAAM;GACd,CAAC;AACF,SAAO,MAAM,UAAU,MAAM,SAAS,KAAK,MAAM,QAAQ;;AAG3D,QAAO;EACL,UAAU,aAAa,QAAQ;EAC/B,SAAS,aAAa,QAAQ;EAC9B,QAAQ,aAAa,QAAQ;EAC7B;EACA;EACA;EACD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { importFromOpenApi, parseOpenApi } from "@contractspec/lib.contracts-transformers/openapi";
|
|
2
1
|
import { dirname, join } from "path";
|
|
2
|
+
import { importFromOpenApi, parseOpenApi } from "@contractspec/lib.contracts-transformers/openapi";
|
|
3
3
|
|
|
4
4
|
//#region src/services/openapi/sync-service.ts
|
|
5
5
|
/**
|
|
@@ -103,7 +103,7 @@ Contracts are located in:
|
|
|
103
103
|
Operations follow this pattern:
|
|
104
104
|
\`\`\`typescript
|
|
105
105
|
defineCommand({
|
|
106
|
-
meta: { name: 'service.action', version: 1, ... },
|
|
106
|
+
meta: { name: 'service.action', version: '1.0.0', ... },
|
|
107
107
|
io: { input: InputSchema, output: OutputSchema },
|
|
108
108
|
policy: { auth: 'user', ... },
|
|
109
109
|
handler: async (args, ctx) => { ... }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-generators.js","names":[],"sources":["../../../src/services/setup/config-generators.ts"],"sourcesContent":["/**\n * Config generators for setup targets.\n *\n * Each generator returns the default configuration for a target.\n */\n\nimport type { SetupOptions } from './types';\n\n/**\n * Generate .contractsrc.json content.\n *\n * Adapts defaults based on monorepo scope.\n */\nexport function generateContractsrcConfig(options: SetupOptions): object {\n // For package-level config in monorepo, use simpler relative paths\n const isPackageLevel = options.isMonorepo && options.scope === 'package';\n\n return {\n $schema: 'https://api.contractspec.io/schemas/contractsrc.json',\n aiProvider: 'claude',\n aiModel: 'claude-sonnet-4-20250514',\n agentMode: 'claude-code',\n // outputDir is relative to the config file location\n outputDir: './src',\n conventions: {\n operations: 'contracts/operations',\n events: 'contracts/events',\n presentations: 'contracts/presentations',\n forms: 'contracts/forms',\n features: 'contracts/features',\n },\n defaultOwners: options.defaultOwners ?? ['@team'],\n defaultTags: [],\n // Add monorepo hint if at package level\n ...(isPackageLevel && options.packageName\n ? { package: options.packageName }\n : {}),\n };\n}\n\n/**\n * Generate .vscode/settings.json ContractSpec settings.\n */\nexport function generateVscodeSettings(): object {\n return {\n 'contractspec.validation.enabled': true,\n 'contractspec.validation.validateOnSave': true,\n 'contractspec.validation.validateOnOpen': true,\n 'contractspec.codeLens.enabled': true,\n 'contractspec.diagnostics.showWarnings': true,\n 'contractspec.diagnostics.showHints': true,\n 'contractspec.integrity.enabled': true,\n 'contractspec.integrity.checkOnSave': true,\n };\n}\n\n/**\n * Generate .cursor/mcp.json content.\n */\nexport function generateCursorMcpConfig(): object {\n return {\n mcpServers: {\n 'contractspec-local': {\n command: 'bunx',\n args: ['contractspec-mcp'],\n },\n },\n };\n}\n\n/**\n * Generate Claude Desktop MCP config.\n * Returns the mcpServers section to merge into claude_desktop_config.json.\n */\nexport function generateClaudeMcpConfig(): object {\n return {\n mcpServers: {\n 'contractspec-local': {\n command: 'bunx',\n args: ['contractspec-mcp'],\n },\n },\n };\n}\n\n/**\n * Generate .cursor/rules/contractspec.mdc content.\n *\n * Adapts paths based on monorepo scope.\n */\nexport function generateCursorRules(options: SetupOptions): string {\n const projectName = options.projectName ?? 'this project';\n const isPackageLevel = options.isMonorepo && options.scope === 'package';\n\n // Base contract path depends on scope\n const basePath =\n isPackageLevel && options.packageRoot\n ? `${options.packageRoot.split('/').slice(-2).join('/')}/src/contracts`\n : 'src/contracts';\n\n const monorepoNote = options.isMonorepo\n ? `\\n## Monorepo Structure\\n\\nThis is a monorepo. Contracts may exist at:\\n- Package level: \\`packages/*/src/contracts/\\`\\n- Workspace level: \\`src/contracts/\\`\\n\\nCheck the appropriate level based on the feature scope.\\n`\n : '';\n\n return `# ContractSpec Development Rules\n\nThis project uses ContractSpec for spec-first development. Follow these guidelines when working with AI agents.\n\n## Spec-First Principle\n\n- **Always update contracts first** before changing implementation code.\n- Contracts are the source of truth for operations, events, and presentations.\n- Implementation code should be generated or derived from contracts.\n${monorepoNote}\n## Contract Locations\n\nContracts are located in:\n- \\`${basePath}/operations/\\` - Command and query specs\n- \\`${basePath}/events/\\` - Event specs\n- \\`${basePath}/presentations/\\` - UI presentation specs\n- \\`${basePath}/features/\\` - Feature module specs\n\n## When Making Changes\n\n1. **Before coding**: Check if a contract exists for the feature.\n2. **If contract exists**: Update the contract first, then regenerate code.\n3. **If no contract**: Create a new contract using \\`contractspec create\\`.\n4. **After changes**: Validate with \\`contractspec validate\\`.\n\n## Key Commands\n\n- \\`contractspec create\\` - Scaffold new specs\n- \\`contractspec validate\\` - Validate specs\n- \\`contractspec build\\` - Generate implementation code\n- \\`contractspec integrity\\` - Check contract health\n\n## Contract Structure\n\nOperations follow this pattern:\n\\`\\`\\`typescript\ndefineCommand({\n meta: { name: 'service.action', version: 1, ... },\n io: { input: InputSchema, output: OutputSchema },\n policy: { auth: 'user', ... },\n handler: async (args, ctx) => { ... }\n});\n\\`\\`\\`\n\n## Rules for ${projectName}\n\n- All API endpoints must have a corresponding operation contract.\n- Events must be declared in contracts before being emitted.\n- UI components should reference presentation contracts.\n- Feature flags should be defined in feature modules.\n`;\n}\n\n/**\n * Generate AGENTS.md content.\n *\n * Adapts paths and instructions based on monorepo scope.\n */\nexport function generateAgentsMd(options: SetupOptions): string {\n const projectName = options.projectName ?? 'This Project';\n const isPackageLevel = options.isMonorepo && options.scope === 'package';\n\n // Contract path depends on scope\n const contractPath = 'src/contracts/';\n\n const monorepoSection = options.isMonorepo\n ? `\n## Monorepo Structure\n\nThis is a monorepo. Contracts can exist at multiple levels:\n\n| Level | Location | Use Case |\n|-------|----------|----------|\n| Package | \\`packages/*/src/contracts/\\` | Package-specific contracts |\n| Workspace | \\`src/contracts/\\` | Shared cross-package contracts |\n\nWhen adding a contract, consider:\n- Is this specific to one package? → Add at package level\n- Is this shared across packages? → Add at workspace level\n\n### Current Scope\n\n${isPackageLevel ? `You are working at the **package level**: \\`${options.packageName ?? options.packageRoot}\\`` : 'You are working at the **workspace level**.'}\n`\n : '';\n\n return `# AI Agent Guide\n\nThis repository uses **ContractSpec** for spec-first development. AI agents should follow these guidelines.\n\n## Project: ${projectName}\n\n## ContractSpec Overview\n\nContractSpec is a deterministic, spec-first compiler that keeps AI-written software coherent, safe, and regenerable.\n\n### Key Principles\n\n1. **Contracts are the source of truth** - Always check/update contracts before modifying implementation.\n2. **Safe regeneration** - Code can be regenerated from specs without breaking invariants.\n3. **Multi-surface consistency** - API, events, and UI stay in sync via shared contracts.\n${monorepoSection}\n## Working in This Repository\n\n### Before Making Changes\n\n1. Check for existing contracts in \\`${contractPath}\\`\n2. If a contract exists, update it first\n3. Regenerate implementation with \\`contractspec build\\`\n4. Validate with \\`contractspec validate\\`\n\n### Creating New Features\n\n1. Create a feature spec: \\`contractspec create --type=feature\\`\n2. Add operations: \\`contractspec create --type=operation\\`\n3. Add events if needed: \\`contractspec create --type=event\\`\n4. Build implementation: \\`contractspec build\\`\n\n### Contract Locations\n\n| Type | Location |\n|------|----------|\n| Operations | \\`${contractPath}operations/\\` |\n| Events | \\`${contractPath}events/\\` |\n| Presentations | \\`${contractPath}presentations/\\` |\n| Features | \\`${contractPath}features/\\` |\n\n## MCP Tools Available\n\nThe ContractSpec MCP server provides these tools:\n\n- \\`integrity.analyze\\` - Check contract health\n- \\`specs.list\\` - List all specs\n- \\`specs.validate\\` - Validate a spec file\n- \\`specs.create\\` - Create new specs\n- \\`deps.analyze\\` - Analyze dependencies\n\n## Common Tasks\n\n### Add a new API endpoint\n\n\\`\\`\\`bash\ncontractspec create --type=operation --name=myService.newAction\n\\`\\`\\`\n\n### Add a new event\n\n\\`\\`\\`bash\ncontractspec create --type=event --name=entity.changed\n\\`\\`\\`\n\n### Check contract integrity\n\n\\`\\`\\`bash\ncontractspec integrity\n\\`\\`\\`\n\n## Nested AGENTS.md\n\nMore specific instructions may exist in subdirectories. Check for \\`AGENTS.md\\` files in the relevant package or module.\n`;\n}\n\n/**\n * Get the file path for Claude Desktop config based on platform.\n */\nexport function getClaudeDesktopConfigPath(): string {\n const platform = process.platform;\n const homeDir = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '';\n\n switch (platform) {\n case 'darwin':\n return `${homeDir}/Library/Application Support/Claude/claude_desktop_config.json`;\n case 'win32':\n return `${process.env['APPDATA'] ?? homeDir}/Claude/claude_desktop_config.json`;\n default:\n // Linux and others\n return `${homeDir}/.config/claude/claude_desktop_config.json`;\n }\n}\n"],"mappings":";;;;;;AAaA,SAAgB,0BAA0B,SAA+B;CAEvE,MAAM,iBAAiB,QAAQ,cAAc,QAAQ,UAAU;AAE/D,QAAO;EACL,SAAS;EACT,YAAY;EACZ,SAAS;EACT,WAAW;EAEX,WAAW;EACX,aAAa;GACX,YAAY;GACZ,QAAQ;GACR,eAAe;GACf,OAAO;GACP,UAAU;GACX;EACD,eAAe,QAAQ,iBAAiB,CAAC,QAAQ;EACjD,aAAa,EAAE;EAEf,GAAI,kBAAkB,QAAQ,cAC1B,EAAE,SAAS,QAAQ,aAAa,GAChC,EAAE;EACP;;;;;AAMH,SAAgB,yBAAiC;AAC/C,QAAO;EACL,mCAAmC;EACnC,0CAA0C;EAC1C,0CAA0C;EAC1C,iCAAiC;EACjC,yCAAyC;EACzC,sCAAsC;EACtC,kCAAkC;EAClC,sCAAsC;EACvC;;;;;AAMH,SAAgB,0BAAkC;AAChD,QAAO,EACL,YAAY,EACV,sBAAsB;EACpB,SAAS;EACT,MAAM,CAAC,mBAAmB;EAC3B,EACF,EACF;;;;;;AAOH,SAAgB,0BAAkC;AAChD,QAAO,EACL,YAAY,EACV,sBAAsB;EACpB,SAAS;EACT,MAAM,CAAC,mBAAmB;EAC3B,EACF,EACF;;;;;;;AAQH,SAAgB,oBAAoB,SAA+B;CACjE,MAAM,cAAc,QAAQ,eAAe;CAI3C,MAAM,WAHiB,QAAQ,cAAc,QAAQ,UAAU,aAI3C,QAAQ,cACtB,GAAG,QAAQ,YAAY,MAAM,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,IAAI,CAAC,kBACtD;AAMN,QAAO;;;;;;;;;EAJc,QAAQ,aACzB,+NACA,GAWS;;;;MAIT,SAAS;MACT,SAAS;MACT,SAAS;MACT,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;eA4BA,YAAY;;;;;;;;;;;;;AAc3B,SAAgB,iBAAiB,SAA+B;CAC9D,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,iBAAiB,QAAQ,cAAc,QAAQ,UAAU;CAG/D,MAAM,eAAe;AAuBrB,QAAO;;;;cAIK,YAAY;;;;;;;;;;;EAzBA,QAAQ,aAC5B;;;;;;;;;;;;;;;;EAgBJ,iBAAiB,+CAA+C,QAAQ,eAAe,QAAQ,YAAY,MAAM,8CAA8C;IAE3J,GAiBY;;;;;uCAKqB,aAAa;;;;;;;;;;;;;;;;mBAgBjC,aAAa;eACjB,aAAa;sBACN,aAAa;iBAClB,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyC9B,SAAgB,6BAAqC;CACnD,MAAM,WAAW,QAAQ;CACzB,MAAM,UAAU,QAAQ,IAAI,WAAW,QAAQ,IAAI,kBAAkB;AAErE,SAAQ,UAAR;EACE,KAAK,SACH,QAAO,GAAG,QAAQ;EACpB,KAAK,QACH,QAAO,GAAG,QAAQ,IAAI,cAAc,QAAQ;EAC9C,QAEE,QAAO,GAAG,QAAQ"}
|
|
1
|
+
{"version":3,"file":"config-generators.js","names":[],"sources":["../../../src/services/setup/config-generators.ts"],"sourcesContent":["/**\n * Config generators for setup targets.\n *\n * Each generator returns the default configuration for a target.\n */\n\nimport type { SetupOptions } from './types';\n\n/**\n * Generate .contractsrc.json content.\n *\n * Adapts defaults based on monorepo scope.\n */\nexport function generateContractsrcConfig(options: SetupOptions): object {\n // For package-level config in monorepo, use simpler relative paths\n const isPackageLevel = options.isMonorepo && options.scope === 'package';\n\n return {\n $schema: 'https://api.contractspec.io/schemas/contractsrc.json',\n aiProvider: 'claude',\n aiModel: 'claude-sonnet-4-20250514',\n agentMode: 'claude-code',\n // outputDir is relative to the config file location\n outputDir: './src',\n conventions: {\n operations: 'contracts/operations',\n events: 'contracts/events',\n presentations: 'contracts/presentations',\n forms: 'contracts/forms',\n features: 'contracts/features',\n },\n defaultOwners: options.defaultOwners ?? ['@team'],\n defaultTags: [],\n // Add monorepo hint if at package level\n ...(isPackageLevel && options.packageName\n ? { package: options.packageName }\n : {}),\n };\n}\n\n/**\n * Generate .vscode/settings.json ContractSpec settings.\n */\nexport function generateVscodeSettings(): object {\n return {\n 'contractspec.validation.enabled': true,\n 'contractspec.validation.validateOnSave': true,\n 'contractspec.validation.validateOnOpen': true,\n 'contractspec.codeLens.enabled': true,\n 'contractspec.diagnostics.showWarnings': true,\n 'contractspec.diagnostics.showHints': true,\n 'contractspec.integrity.enabled': true,\n 'contractspec.integrity.checkOnSave': true,\n };\n}\n\n/**\n * Generate .cursor/mcp.json content.\n */\nexport function generateCursorMcpConfig(): object {\n return {\n mcpServers: {\n 'contractspec-local': {\n command: 'bunx',\n args: ['contractspec-mcp'],\n },\n },\n };\n}\n\n/**\n * Generate Claude Desktop MCP config.\n * Returns the mcpServers section to merge into claude_desktop_config.json.\n */\nexport function generateClaudeMcpConfig(): object {\n return {\n mcpServers: {\n 'contractspec-local': {\n command: 'bunx',\n args: ['contractspec-mcp'],\n },\n },\n };\n}\n\n/**\n * Generate .cursor/rules/contractspec.mdc content.\n *\n * Adapts paths based on monorepo scope.\n */\nexport function generateCursorRules(options: SetupOptions): string {\n const projectName = options.projectName ?? 'this project';\n const isPackageLevel = options.isMonorepo && options.scope === 'package';\n\n // Base contract path depends on scope\n const basePath =\n isPackageLevel && options.packageRoot\n ? `${options.packageRoot.split('/').slice(-2).join('/')}/src/contracts`\n : 'src/contracts';\n\n const monorepoNote = options.isMonorepo\n ? `\\n## Monorepo Structure\\n\\nThis is a monorepo. Contracts may exist at:\\n- Package level: \\`packages/*/src/contracts/\\`\\n- Workspace level: \\`src/contracts/\\`\\n\\nCheck the appropriate level based on the feature scope.\\n`\n : '';\n\n return `# ContractSpec Development Rules\n\nThis project uses ContractSpec for spec-first development. Follow these guidelines when working with AI agents.\n\n## Spec-First Principle\n\n- **Always update contracts first** before changing implementation code.\n- Contracts are the source of truth for operations, events, and presentations.\n- Implementation code should be generated or derived from contracts.\n${monorepoNote}\n## Contract Locations\n\nContracts are located in:\n- \\`${basePath}/operations/\\` - Command and query specs\n- \\`${basePath}/events/\\` - Event specs\n- \\`${basePath}/presentations/\\` - UI presentation specs\n- \\`${basePath}/features/\\` - Feature module specs\n\n## When Making Changes\n\n1. **Before coding**: Check if a contract exists for the feature.\n2. **If contract exists**: Update the contract first, then regenerate code.\n3. **If no contract**: Create a new contract using \\`contractspec create\\`.\n4. **After changes**: Validate with \\`contractspec validate\\`.\n\n## Key Commands\n\n- \\`contractspec create\\` - Scaffold new specs\n- \\`contractspec validate\\` - Validate specs\n- \\`contractspec build\\` - Generate implementation code\n- \\`contractspec integrity\\` - Check contract health\n\n## Contract Structure\n\nOperations follow this pattern:\n\\`\\`\\`typescript\ndefineCommand({\n meta: { name: 'service.action', version: '1.0.0', ... },\n io: { input: InputSchema, output: OutputSchema },\n policy: { auth: 'user', ... },\n handler: async (args, ctx) => { ... }\n});\n\\`\\`\\`\n\n## Rules for ${projectName}\n\n- All API endpoints must have a corresponding operation contract.\n- Events must be declared in contracts before being emitted.\n- UI components should reference presentation contracts.\n- Feature flags should be defined in feature modules.\n`;\n}\n\n/**\n * Generate AGENTS.md content.\n *\n * Adapts paths and instructions based on monorepo scope.\n */\nexport function generateAgentsMd(options: SetupOptions): string {\n const projectName = options.projectName ?? 'This Project';\n const isPackageLevel = options.isMonorepo && options.scope === 'package';\n\n // Contract path depends on scope\n const contractPath = 'src/contracts/';\n\n const monorepoSection = options.isMonorepo\n ? `\n## Monorepo Structure\n\nThis is a monorepo. Contracts can exist at multiple levels:\n\n| Level | Location | Use Case |\n|-------|----------|----------|\n| Package | \\`packages/*/src/contracts/\\` | Package-specific contracts |\n| Workspace | \\`src/contracts/\\` | Shared cross-package contracts |\n\nWhen adding a contract, consider:\n- Is this specific to one package? → Add at package level\n- Is this shared across packages? → Add at workspace level\n\n### Current Scope\n\n${isPackageLevel ? `You are working at the **package level**: \\`${options.packageName ?? options.packageRoot}\\`` : 'You are working at the **workspace level**.'}\n`\n : '';\n\n return `# AI Agent Guide\n\nThis repository uses **ContractSpec** for spec-first development. AI agents should follow these guidelines.\n\n## Project: ${projectName}\n\n## ContractSpec Overview\n\nContractSpec is a deterministic, spec-first compiler that keeps AI-written software coherent, safe, and regenerable.\n\n### Key Principles\n\n1. **Contracts are the source of truth** - Always check/update contracts before modifying implementation.\n2. **Safe regeneration** - Code can be regenerated from specs without breaking invariants.\n3. **Multi-surface consistency** - API, events, and UI stay in sync via shared contracts.\n${monorepoSection}\n## Working in This Repository\n\n### Before Making Changes\n\n1. Check for existing contracts in \\`${contractPath}\\`\n2. If a contract exists, update it first\n3. Regenerate implementation with \\`contractspec build\\`\n4. Validate with \\`contractspec validate\\`\n\n### Creating New Features\n\n1. Create a feature spec: \\`contractspec create --type=feature\\`\n2. Add operations: \\`contractspec create --type=operation\\`\n3. Add events if needed: \\`contractspec create --type=event\\`\n4. Build implementation: \\`contractspec build\\`\n\n### Contract Locations\n\n| Type | Location |\n|------|----------|\n| Operations | \\`${contractPath}operations/\\` |\n| Events | \\`${contractPath}events/\\` |\n| Presentations | \\`${contractPath}presentations/\\` |\n| Features | \\`${contractPath}features/\\` |\n\n## MCP Tools Available\n\nThe ContractSpec MCP server provides these tools:\n\n- \\`integrity.analyze\\` - Check contract health\n- \\`specs.list\\` - List all specs\n- \\`specs.validate\\` - Validate a spec file\n- \\`specs.create\\` - Create new specs\n- \\`deps.analyze\\` - Analyze dependencies\n\n## Common Tasks\n\n### Add a new API endpoint\n\n\\`\\`\\`bash\ncontractspec create --type=operation --name=myService.newAction\n\\`\\`\\`\n\n### Add a new event\n\n\\`\\`\\`bash\ncontractspec create --type=event --name=entity.changed\n\\`\\`\\`\n\n### Check contract integrity\n\n\\`\\`\\`bash\ncontractspec integrity\n\\`\\`\\`\n\n## Nested AGENTS.md\n\nMore specific instructions may exist in subdirectories. Check for \\`AGENTS.md\\` files in the relevant package or module.\n`;\n}\n\n/**\n * Get the file path for Claude Desktop config based on platform.\n */\nexport function getClaudeDesktopConfigPath(): string {\n const platform = process.platform;\n const homeDir = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '';\n\n switch (platform) {\n case 'darwin':\n return `${homeDir}/Library/Application Support/Claude/claude_desktop_config.json`;\n case 'win32':\n return `${process.env['APPDATA'] ?? homeDir}/Claude/claude_desktop_config.json`;\n default:\n // Linux and others\n return `${homeDir}/.config/claude/claude_desktop_config.json`;\n }\n}\n"],"mappings":";;;;;;AAaA,SAAgB,0BAA0B,SAA+B;CAEvE,MAAM,iBAAiB,QAAQ,cAAc,QAAQ,UAAU;AAE/D,QAAO;EACL,SAAS;EACT,YAAY;EACZ,SAAS;EACT,WAAW;EAEX,WAAW;EACX,aAAa;GACX,YAAY;GACZ,QAAQ;GACR,eAAe;GACf,OAAO;GACP,UAAU;GACX;EACD,eAAe,QAAQ,iBAAiB,CAAC,QAAQ;EACjD,aAAa,EAAE;EAEf,GAAI,kBAAkB,QAAQ,cAC1B,EAAE,SAAS,QAAQ,aAAa,GAChC,EAAE;EACP;;;;;AAMH,SAAgB,yBAAiC;AAC/C,QAAO;EACL,mCAAmC;EACnC,0CAA0C;EAC1C,0CAA0C;EAC1C,iCAAiC;EACjC,yCAAyC;EACzC,sCAAsC;EACtC,kCAAkC;EAClC,sCAAsC;EACvC;;;;;AAMH,SAAgB,0BAAkC;AAChD,QAAO,EACL,YAAY,EACV,sBAAsB;EACpB,SAAS;EACT,MAAM,CAAC,mBAAmB;EAC3B,EACF,EACF;;;;;;AAOH,SAAgB,0BAAkC;AAChD,QAAO,EACL,YAAY,EACV,sBAAsB;EACpB,SAAS;EACT,MAAM,CAAC,mBAAmB;EAC3B,EACF,EACF;;;;;;;AAQH,SAAgB,oBAAoB,SAA+B;CACjE,MAAM,cAAc,QAAQ,eAAe;CAI3C,MAAM,WAHiB,QAAQ,cAAc,QAAQ,UAAU,aAI3C,QAAQ,cACtB,GAAG,QAAQ,YAAY,MAAM,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,IAAI,CAAC,kBACtD;AAMN,QAAO;;;;;;;;;EAJc,QAAQ,aACzB,+NACA,GAWS;;;;MAIT,SAAS;MACT,SAAS;MACT,SAAS;MACT,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;eA4BA,YAAY;;;;;;;;;;;;;AAc3B,SAAgB,iBAAiB,SAA+B;CAC9D,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,iBAAiB,QAAQ,cAAc,QAAQ,UAAU;CAG/D,MAAM,eAAe;AAuBrB,QAAO;;;;cAIK,YAAY;;;;;;;;;;;EAzBA,QAAQ,aAC5B;;;;;;;;;;;;;;;;EAgBJ,iBAAiB,+CAA+C,QAAQ,eAAe,QAAQ,YAAY,MAAM,8CAA8C;IAE3J,GAiBY;;;;;uCAKqB,aAAa;;;;;;;;;;;;;;;;mBAgBjC,aAAa;eACjB,aAAa;sBACN,aAAa;iBAClB,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyC9B,SAAgB,6BAAqC;CACnD,MAAM,WAAW,QAAQ;CACzB,MAAM,UAAU,QAAQ,IAAI,WAAW,QAAQ,IAAI,kBAAkB;AAErE,SAAQ,UAAR;EACE,KAAK,SACH,QAAO,GAAG,QAAQ;EACpB,KAAK,QACH,QAAO,GAAG,QAAQ,IAAI,cAAc,QAAQ;EAC9C,QAEE,QAAO,GAAG,QAAQ"}
|
package/dist/services/sync.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FsAdapter } from "../ports/fs.js";
|
|
2
2
|
import { LoggerAdapter } from "../ports/logger.js";
|
|
3
|
-
import { ValidateSpecResult } from "./validate.js";
|
|
3
|
+
import { ValidateSpecResult } from "./validate/spec-validator.js";
|
|
4
|
+
import "./validate/index.js";
|
|
4
5
|
import { BuildSpecOptions } from "./build.js";
|
|
5
6
|
import { WorkspaceConfig } from "@contractspec/module.workspace";
|
|
6
7
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","names":[],"sources":["../../src/services/sync.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sync.d.ts","names":[],"sources":["../../src/services/sync.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAqCY,UAxBK,gBAAA,CA2BZ;EAEO,OAAA,CAAA,EAAA,MAAA;EAEU,UAAA,CAAA,EAAS,CAAA,MAAA,GAAA,SAAA,CAAA,EAAA;EACb,QAAA,CAAA,EAAA,OAAA;EAAmB,YAAA,CAAA,EA5BpB,IA4BoB,CA5Bf,gBA4Be,EAAA,WAAA,CAAA;EAC3B,MAAA,CAAA,EAAA,OAAA;;AAGE,UA5BK,kBAAA,CA4BL;EACG,QAAA,EAAA,MAAA;EAEJ,SAAA,CAAA,EAAA,MAAA;EAAR,UAAA,CAAA,EA5BY,kBA4BZ;EAAO,KAAA,CAAA,EAAA,OAAA;;;;;;UApBO,eAAA;;QAET;;KAGI,sEAGP,QAAQ;KAED,cAAA,yBAAuC,QAAQ;iBAErC,SAAA;MACJ;UAAmB;WAC3B,2BACC;UAEC;aACG;IAEZ,QAAQ"}
|
package/dist/services/sync.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","names":["runs: SyncSpecsRunResult[]","validateFn: SyncValidateFn","buildFn: SyncBuildFn<unknown>","run: SyncSpecsRunResult"],"sources":["../../src/services/sync.ts"],"sourcesContent":["/**\n * Sync service.\n *\n * Build (and optionally validate) all discovered specs, optionally repeating into\n * multiple output buckets (./generated/<bucket>/ or any output dir).\n */\n\nimport type { WorkspaceConfig } from '@contractspec/module.workspace';\nimport type { FsAdapter } from '../ports/fs';\nimport type { LoggerAdapter } from '../ports/logger';\nimport { buildSpec, type BuildSpecOptions } from './build';\nimport { validateSpec, type ValidateSpecResult } from './validate';\n\nexport interface SyncSpecsOptions {\n pattern?: string;\n outputDirs?: (string | undefined)[];\n validate?: boolean;\n buildOptions?: Omit<BuildSpecOptions, 'outputDir'>;\n dryRun?: boolean;\n}\n\nexport interface SyncSpecsRunResult {\n specPath: string;\n outputDir?: string;\n validation?: ValidateSpecResult;\n build?: unknown;\n error?: {\n phase: 'validate' | 'build';\n message: string;\n };\n}\n\nexport interface SyncSpecsResult {\n specs: string[];\n runs: SyncSpecsRunResult[];\n}\n\nexport type SyncBuildFn<B> = (\n specPath: string,\n outputDir: string | undefined\n) => Promise<B>;\n\nexport type SyncValidateFn = (specPath: string) => Promise<ValidateSpecResult>;\n\nexport async function syncSpecs(\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n config: WorkspaceConfig,\n options: SyncSpecsOptions = {},\n overrides?: {\n build?: SyncBuildFn<unknown>;\n validate?: SyncValidateFn;\n }\n): Promise<SyncSpecsResult> {\n const { fs, logger } = adapters;\n\n const specs = await fs.glob({ pattern: options.pattern });\n const outputDirs = options.outputDirs?.length\n ? options.outputDirs\n : [undefined];\n\n const runs: SyncSpecsRunResult[] = [];\n\n const validateFn: SyncValidateFn =\n overrides?.validate ??\n ((specPath) => validateSpec(specPath, { fs, logger }));\n\n const buildFn: SyncBuildFn<unknown> =\n overrides?.build ??\n ((specPath, outputDir) =>\n buildSpec(\n specPath,\n { fs, logger },\n outputDir ? { ...config, outputDir } : config,\n { ...(options.buildOptions ?? {}), outputDir }\n ));\n\n for (const specPath of specs) {\n for (const outputDir of outputDirs) {\n const run: SyncSpecsRunResult = { specPath, outputDir };\n\n if (options.validate) {\n try {\n run.validation = await validateFn(specPath);\n } catch (error) {\n run.error = {\n phase: 'validate',\n message: error instanceof Error ? error.message : String(error),\n };\n runs.push(run);\n continue;\n }\n }\n\n if (!options.dryRun) {\n try {\n run.build = await buildFn(specPath, outputDir);\n } catch (error) {\n run.error = {\n phase: 'build',\n message: error instanceof Error ? error.message : String(error),\n };\n runs.push(run);\n continue;\n }\n } else {\n logger.info('[dry-run] syncSpecs skipped build', {\n specPath,\n outputDir,\n });\n }\n\n runs.push(run);\n }\n }\n\n return { specs, runs };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"sync.js","names":["runs: SyncSpecsRunResult[]","validateFn: SyncValidateFn","buildFn: SyncBuildFn<unknown>","run: SyncSpecsRunResult"],"sources":["../../src/services/sync.ts"],"sourcesContent":["/**\n * Sync service.\n *\n * Build (and optionally validate) all discovered specs, optionally repeating into\n * multiple output buckets (./generated/<bucket>/ or any output dir).\n */\n\nimport type { WorkspaceConfig } from '@contractspec/module.workspace';\nimport type { FsAdapter } from '../ports/fs';\nimport type { LoggerAdapter } from '../ports/logger';\nimport { buildSpec, type BuildSpecOptions } from './build';\nimport { validateSpec, type ValidateSpecResult } from './validate';\n\nexport interface SyncSpecsOptions {\n pattern?: string;\n outputDirs?: (string | undefined)[];\n validate?: boolean;\n buildOptions?: Omit<BuildSpecOptions, 'outputDir'>;\n dryRun?: boolean;\n}\n\nexport interface SyncSpecsRunResult {\n specPath: string;\n outputDir?: string;\n validation?: ValidateSpecResult;\n build?: unknown;\n error?: {\n phase: 'validate' | 'build';\n message: string;\n };\n}\n\nexport interface SyncSpecsResult {\n specs: string[];\n runs: SyncSpecsRunResult[];\n}\n\nexport type SyncBuildFn<B> = (\n specPath: string,\n outputDir: string | undefined\n) => Promise<B>;\n\nexport type SyncValidateFn = (specPath: string) => Promise<ValidateSpecResult>;\n\nexport async function syncSpecs(\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n config: WorkspaceConfig,\n options: SyncSpecsOptions = {},\n overrides?: {\n build?: SyncBuildFn<unknown>;\n validate?: SyncValidateFn;\n }\n): Promise<SyncSpecsResult> {\n const { fs, logger } = adapters;\n\n const specs = await fs.glob({ pattern: options.pattern });\n const outputDirs = options.outputDirs?.length\n ? options.outputDirs\n : [undefined];\n\n const runs: SyncSpecsRunResult[] = [];\n\n const validateFn: SyncValidateFn =\n overrides?.validate ??\n ((specPath) => validateSpec(specPath, { fs, logger }));\n\n const buildFn: SyncBuildFn<unknown> =\n overrides?.build ??\n ((specPath, outputDir) =>\n buildSpec(\n specPath,\n { fs, logger },\n outputDir ? { ...config, outputDir } : config,\n { ...(options.buildOptions ?? {}), outputDir }\n ));\n\n for (const specPath of specs) {\n for (const outputDir of outputDirs) {\n const run: SyncSpecsRunResult = { specPath, outputDir };\n\n if (options.validate) {\n try {\n run.validation = await validateFn(specPath);\n } catch (error) {\n run.error = {\n phase: 'validate',\n message: error instanceof Error ? error.message : String(error),\n };\n runs.push(run);\n continue;\n }\n }\n\n if (!options.dryRun) {\n try {\n run.build = await buildFn(specPath, outputDir);\n } catch (error) {\n run.error = {\n phase: 'build',\n message: error instanceof Error ? error.message : String(error),\n };\n runs.push(run);\n continue;\n }\n } else {\n logger.info('[dry-run] syncSpecs skipped build', {\n specPath,\n outputDir,\n });\n }\n\n runs.push(run);\n }\n }\n\n return { specs, runs };\n}\n"],"mappings":";;;;;AA4CA,eAAsB,UACpB,UACA,QACA,UAA4B,EAAE,EAC9B,WAI0B;CAC1B,MAAM,EAAE,IAAI,WAAW;CAEvB,MAAM,QAAQ,MAAM,GAAG,KAAK,EAAE,SAAS,QAAQ,SAAS,CAAC;CACzD,MAAM,aAAa,QAAQ,YAAY,SACnC,QAAQ,aACR,CAAC,OAAU;CAEf,MAAMA,OAA6B,EAAE;CAErC,MAAMC,aACJ,WAAW,cACT,aAAa,aAAa,UAAU;EAAE;EAAI;EAAQ,CAAC;CAEvD,MAAMC,UACJ,WAAW,WACT,UAAU,cACV,UACE,UACA;EAAE;EAAI;EAAQ,EACd,YAAY;EAAE,GAAG;EAAQ;EAAW,GAAG,QACvC;EAAE,GAAI,QAAQ,gBAAgB,EAAE;EAAG;EAAW,CAC/C;AAEL,MAAK,MAAM,YAAY,MACrB,MAAK,MAAM,aAAa,YAAY;EAClC,MAAMC,MAA0B;GAAE;GAAU;GAAW;AAEvD,MAAI,QAAQ,SACV,KAAI;AACF,OAAI,aAAa,MAAM,WAAW,SAAS;WACpC,OAAO;AACd,OAAI,QAAQ;IACV,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAChE;AACD,QAAK,KAAK,IAAI;AACd;;AAIJ,MAAI,CAAC,QAAQ,OACX,KAAI;AACF,OAAI,QAAQ,MAAM,QAAQ,UAAU,UAAU;WACvC,OAAO;AACd,OAAI,QAAQ;IACV,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAChE;AACD,QAAK,KAAK,IAAI;AACd;;MAGF,QAAO,KAAK,qCAAqC;GAC/C;GACA;GACD,CAAC;AAGJ,OAAK,KAAK,IAAI;;AAIlB,QAAO;EAAE;EAAO;EAAM"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { RunTestsResult, TestServiceOptions, TestServiceResult, runTestSpecs, runTests } from "./test-service.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { runTestSpecs, runTests } from "./test-service.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { WorkspaceAdapters } from "../../ports/logger.js";
|
|
2
|
+
import { OperationSpecRegistry } from "@contractspec/lib.contracts";
|
|
3
|
+
import { TestRunResult, TestSpec } from "@contractspec/lib.contracts/tests";
|
|
4
|
+
|
|
5
|
+
//#region src/services/test/test-service.d.ts
|
|
6
|
+
interface TestServiceOptions {
|
|
7
|
+
registry?: string;
|
|
8
|
+
}
|
|
9
|
+
interface TestServiceResult {
|
|
10
|
+
results: TestRunResult[];
|
|
11
|
+
passed: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface RunTestsResult {
|
|
14
|
+
results: TestRunResult[];
|
|
15
|
+
passed: number;
|
|
16
|
+
failed: number;
|
|
17
|
+
}
|
|
18
|
+
declare function runTests(specs: TestSpec[], registry: OperationSpecRegistry): Promise<RunTestsResult>;
|
|
19
|
+
declare function runTestSpecs(specFiles: string[], options: TestServiceOptions, adapters: WorkspaceAdapters): Promise<TestServiceResult>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { RunTestsResult, TestServiceOptions, TestServiceResult, runTestSpecs, runTests };
|
|
22
|
+
//# sourceMappingURL=test-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-service.d.ts","names":[],"sources":["../../../src/services/test/test-service.ts"],"sourcesContent":[],"mappings":";;;;;UAUiB,kBAAA;;AAAjB;AAIiB,UAAA,iBAAA,CAAiB;EAKjB,OAAA,EAJN,aAIoB,EAAA;EAMT,MAAA,EAAA,OAAQ;;AAElB,UARK,cAAA,CAQL;EACD,OAAA,EARA,aAQA,EAAA;EAAR,MAAA,EAAA,MAAA;EAAO,MAAA,EAAA,MAAA;AAuBV;AAEW,iBA5BW,QAAA,CA4BX,KAAA,EA3BF,QA2BE,EAAA,EAAA,QAAA,EA1BC,qBA0BD,CAAA,EAzBR,OAyBQ,CAzBA,cAyBA,CAAA;AACC,iBAHU,YAAA,CAGV,SAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EADD,kBACC,EAAA,QAAA,EAAA,iBAAA,CAAA,EACT,OADS,CACD,iBADC,CAAA"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { loadTypeScriptModule } from "../../utils/module-loader.js";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { OperationSpecRegistry } from "@contractspec/lib.contracts";
|
|
4
|
+
import { TestRunner } from "@contractspec/lib.contracts/tests";
|
|
5
|
+
|
|
6
|
+
//#region src/services/test/test-service.ts
|
|
7
|
+
async function runTests(specs, registry) {
|
|
8
|
+
const runner = new TestRunner({ registry });
|
|
9
|
+
const results = [];
|
|
10
|
+
let passed = 0;
|
|
11
|
+
let failed = 0;
|
|
12
|
+
for (const spec of specs) {
|
|
13
|
+
const result = await runner.run(spec);
|
|
14
|
+
results.push(result);
|
|
15
|
+
passed += result.passed;
|
|
16
|
+
failed += result.failed;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
results,
|
|
20
|
+
passed,
|
|
21
|
+
failed
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function runTestSpecs(specFiles, options, adapters) {
|
|
25
|
+
const { logger } = adapters;
|
|
26
|
+
let registry;
|
|
27
|
+
if (options.registry) registry = await loadRegistry(resolve(options.registry));
|
|
28
|
+
else {
|
|
29
|
+
registry = new OperationSpecRegistry();
|
|
30
|
+
logger.warn("No registry module provided. Scenarios that execute operations without handlers will fail.");
|
|
31
|
+
}
|
|
32
|
+
const runner = new TestRunner({ registry });
|
|
33
|
+
const results = [];
|
|
34
|
+
let allPassed = true;
|
|
35
|
+
for (const specFile of specFiles) try {
|
|
36
|
+
const specs = extractTestSpecs(await loadTypeScriptModule(resolve(specFile)));
|
|
37
|
+
if (specs.length === 0) {
|
|
38
|
+
logger.warn(`No TestSpec exports found in ${specFile}`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (const spec of specs) {
|
|
42
|
+
logger.info(`Running ${spec.meta.key}...`);
|
|
43
|
+
const result = await runner.run(spec);
|
|
44
|
+
results.push(result);
|
|
45
|
+
if (result.failed > 0) {
|
|
46
|
+
allPassed = false;
|
|
47
|
+
logger.error(`${spec.meta.key} failed (${result.failed}/${result.scenarios.length})`);
|
|
48
|
+
} else logger.info(`${spec.meta.key} passed (${result.passed}/${result.scenarios.length})`);
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.error(`Failed to load/run spec ${specFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
allPassed = false;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
results,
|
|
56
|
+
passed: allPassed
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function extractTestSpecs(exports) {
|
|
60
|
+
const specs = [];
|
|
61
|
+
for (const value of Object.values(exports)) if (isTestSpec(value)) specs.push(value);
|
|
62
|
+
return specs;
|
|
63
|
+
}
|
|
64
|
+
function isTestSpec(value) {
|
|
65
|
+
return typeof value === "object" && value !== null && Array.isArray(value.scenarios) && !!value.meta?.key;
|
|
66
|
+
}
|
|
67
|
+
async function loadRegistry(modulePath) {
|
|
68
|
+
const exports = await loadTypeScriptModule(modulePath);
|
|
69
|
+
if (exports instanceof OperationSpecRegistry) return exports;
|
|
70
|
+
if (exports.registry instanceof OperationSpecRegistry) return exports.registry;
|
|
71
|
+
const factory = typeof exports.createRegistry === "function" ? exports.createRegistry : typeof exports.default === "function" ? exports.default : void 0;
|
|
72
|
+
if (factory) {
|
|
73
|
+
const result = await factory();
|
|
74
|
+
if (result instanceof OperationSpecRegistry) return result;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`Registry module ${modulePath} must export a OperationSpecRegistry instance or a factory function returning one.`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
export { runTestSpecs, runTests };
|
|
81
|
+
//# sourceMappingURL=test-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-service.js","names":["results: TestRunResult[]","registry: OperationSpecRegistry","specs: TestSpec[]"],"sources":["../../../src/services/test/test-service.ts"],"sourcesContent":["import { resolve } from 'path';\nimport {\n TestRunner,\n type TestSpec,\n type TestRunResult,\n} from '@contractspec/lib.contracts/tests';\nimport { OperationSpecRegistry } from '@contractspec/lib.contracts';\nimport { loadTypeScriptModule } from '../../utils/module-loader';\nimport type { WorkspaceAdapters } from '../../ports/logger';\n\nexport interface TestServiceOptions {\n registry?: string;\n}\n\nexport interface TestServiceResult {\n results: TestRunResult[];\n passed: boolean;\n}\n\nexport interface RunTestsResult {\n results: TestRunResult[];\n passed: number;\n failed: number;\n}\n\nexport async function runTests(\n specs: TestSpec[],\n registry: OperationSpecRegistry\n): Promise<RunTestsResult> {\n const runner = new TestRunner({ registry });\n\n const results: TestRunResult[] = [];\n let passed = 0;\n let failed = 0;\n\n for (const spec of specs) {\n const result = await runner.run(spec);\n results.push(result);\n passed += result.passed;\n failed += result.failed;\n }\n\n return { results, passed, failed };\n}\n\ninterface LoadedRegistryModule {\n default?: unknown;\n createRegistry?: () => Promise<OperationSpecRegistry> | OperationSpecRegistry;\n registry?: OperationSpecRegistry;\n}\n\nexport async function runTestSpecs(\n specFiles: string[],\n options: TestServiceOptions,\n adapters: WorkspaceAdapters\n): Promise<TestServiceResult> {\n const { logger } = adapters;\n\n // Load registry\n let registry: OperationSpecRegistry;\n if (options.registry) {\n registry = await loadRegistry(resolve(options.registry));\n } else {\n registry = new OperationSpecRegistry();\n logger.warn(\n 'No registry module provided. Scenarios that execute operations without handlers will fail.'\n );\n }\n\n const runner = new TestRunner({ registry });\n const results: TestRunResult[] = [];\n let allPassed = true;\n\n for (const specFile of specFiles) {\n try {\n const resolvedPath = resolve(specFile);\n const exports = await loadTypeScriptModule(resolvedPath);\n const specs = extractTestSpecs(exports);\n\n if (specs.length === 0) {\n logger.warn(`No TestSpec exports found in ${specFile}`);\n continue;\n }\n\n for (const spec of specs) {\n logger.info(`Running ${spec.meta.key}...`);\n const result = await runner.run(spec);\n results.push(result);\n\n if (result.failed > 0) {\n allPassed = false;\n logger.error(\n `${spec.meta.key} failed (${result.failed}/${result.scenarios.length})`\n );\n } else {\n logger.info(\n `${spec.meta.key} passed (${result.passed}/${result.scenarios.length})`\n );\n }\n }\n } catch (error) {\n logger.error(\n `Failed to load/run spec ${specFile}: ${error instanceof Error ? error.message : String(error)}`\n );\n allPassed = false;\n }\n }\n\n return { results, passed: allPassed };\n}\n\nfunction extractTestSpecs(exports: Record<string, unknown>): TestSpec[] {\n const specs: TestSpec[] = [];\n for (const value of Object.values(exports)) {\n if (isTestSpec(value)) {\n specs.push(value);\n }\n }\n return specs;\n}\n\nfunction isTestSpec(value: unknown): value is TestSpec {\n return (\n typeof value === 'object' &&\n value !== null &&\n Array.isArray((value as TestSpec).scenarios) &&\n !!(value as TestSpec).meta?.key\n );\n}\n\nasync function loadRegistry(\n modulePath: string\n): Promise<OperationSpecRegistry> {\n const exports = (await loadTypeScriptModule(\n modulePath\n )) as LoadedRegistryModule;\n\n if (exports instanceof OperationSpecRegistry) {\n return exports;\n }\n if (exports.registry instanceof OperationSpecRegistry) {\n return exports.registry;\n }\n\n const factory =\n typeof exports.createRegistry === 'function'\n ? exports.createRegistry\n : typeof exports.default === 'function'\n ? (exports.default as () =>\n | Promise<OperationSpecRegistry>\n | OperationSpecRegistry)\n : undefined;\n\n if (factory) {\n const result = await factory();\n if (result instanceof OperationSpecRegistry) {\n return result;\n }\n }\n\n throw new Error(\n `Registry module ${modulePath} must export a OperationSpecRegistry instance or a factory function returning one.`\n );\n}\n"],"mappings":";;;;;;AAyBA,eAAsB,SACpB,OACA,UACyB;CACzB,MAAM,SAAS,IAAI,WAAW,EAAE,UAAU,CAAC;CAE3C,MAAMA,UAA2B,EAAE;CACnC,IAAI,SAAS;CACb,IAAI,SAAS;AAEb,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,MAAM,OAAO,IAAI,KAAK;AACrC,UAAQ,KAAK,OAAO;AACpB,YAAU,OAAO;AACjB,YAAU,OAAO;;AAGnB,QAAO;EAAE;EAAS;EAAQ;EAAQ;;AASpC,eAAsB,aACpB,WACA,SACA,UAC4B;CAC5B,MAAM,EAAE,WAAW;CAGnB,IAAIC;AACJ,KAAI,QAAQ,SACV,YAAW,MAAM,aAAa,QAAQ,QAAQ,SAAS,CAAC;MACnD;AACL,aAAW,IAAI,uBAAuB;AACtC,SAAO,KACL,6FACD;;CAGH,MAAM,SAAS,IAAI,WAAW,EAAE,UAAU,CAAC;CAC3C,MAAMD,UAA2B,EAAE;CACnC,IAAI,YAAY;AAEhB,MAAK,MAAM,YAAY,UACrB,KAAI;EAGF,MAAM,QAAQ,iBADE,MAAM,qBADD,QAAQ,SAAS,CACkB,CACjB;AAEvC,MAAI,MAAM,WAAW,GAAG;AACtB,UAAO,KAAK,gCAAgC,WAAW;AACvD;;AAGF,OAAK,MAAM,QAAQ,OAAO;AACxB,UAAO,KAAK,WAAW,KAAK,KAAK,IAAI,KAAK;GAC1C,MAAM,SAAS,MAAM,OAAO,IAAI,KAAK;AACrC,WAAQ,KAAK,OAAO;AAEpB,OAAI,OAAO,SAAS,GAAG;AACrB,gBAAY;AACZ,WAAO,MACL,GAAG,KAAK,KAAK,IAAI,WAAW,OAAO,OAAO,GAAG,OAAO,UAAU,OAAO,GACtE;SAED,QAAO,KACL,GAAG,KAAK,KAAK,IAAI,WAAW,OAAO,OAAO,GAAG,OAAO,UAAU,OAAO,GACtE;;UAGE,OAAO;AACd,SAAO,MACL,2BAA2B,SAAS,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAC/F;AACD,cAAY;;AAIhB,QAAO;EAAE;EAAS,QAAQ;EAAW;;AAGvC,SAAS,iBAAiB,SAA8C;CACtE,MAAME,QAAoB,EAAE;AAC5B,MAAK,MAAM,SAAS,OAAO,OAAO,QAAQ,CACxC,KAAI,WAAW,MAAM,CACnB,OAAM,KAAK,MAAM;AAGrB,QAAO;;AAGT,SAAS,WAAW,OAAmC;AACrD,QACE,OAAO,UAAU,YACjB,UAAU,QACV,MAAM,QAAS,MAAmB,UAAU,IAC5C,CAAC,CAAE,MAAmB,MAAM;;AAIhC,eAAe,aACb,YACgC;CAChC,MAAM,UAAW,MAAM,qBACrB,WACD;AAED,KAAI,mBAAmB,sBACrB,QAAO;AAET,KAAI,QAAQ,oBAAoB,sBAC9B,QAAO,QAAQ;CAGjB,MAAM,UACJ,OAAO,QAAQ,mBAAmB,aAC9B,QAAQ,iBACR,OAAO,QAAQ,YAAY,aACxB,QAAQ,UAGT;AAER,KAAI,SAAS;EACX,MAAM,SAAS,MAAM,SAAS;AAC9B,MAAI,kBAAkB,sBACpB,QAAO;;AAIX,OAAM,IAAI,MACR,mBAAmB,WAAW,oFAC/B"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FsAdapter } from "../../ports/fs.js";
|
|
2
|
+
import { AppBlueprintSpec, validateBlueprint } from "@contractspec/lib.contracts";
|
|
3
|
+
|
|
4
|
+
//#region src/services/validate/blueprint-validator.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Result of blueprint validation.
|
|
8
|
+
*/
|
|
9
|
+
interface BlueprintValidationResult {
|
|
10
|
+
spec?: AppBlueprintSpec;
|
|
11
|
+
report?: ReturnType<typeof validateBlueprint>;
|
|
12
|
+
valid: boolean;
|
|
13
|
+
errors: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate a blueprint spec file.
|
|
17
|
+
*/
|
|
18
|
+
declare function validateBlueprint$1(blueprintPath: string, adapters: {
|
|
19
|
+
fs: FsAdapter;
|
|
20
|
+
}): Promise<BlueprintValidationResult>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { BlueprintValidationResult, validateBlueprint$1 as validateBlueprint };
|
|
23
|
+
//# sourceMappingURL=blueprint-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blueprint-validator.d.ts","names":[],"sources":["../../../src/services/validate/blueprint-validator.ts"],"sourcesContent":[],"mappings":";;;;;;;AAWA;AACS,UADQ,yBAAA,CACR;EACoB,IAAA,CAAA,EADpB,gBACoB;EAAlB,MAAA,CAAA,EAAA,UAAA,CAAA,OAAkB,iBAAlB,CAAA;EAAU,KAAA,EAAA,OAAA;EAQC,MAAA,EAAA,MAAA,EAAA;;;;;iBAAA,mBAAA;MAEJ;IACf,QAAQ"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import { pathToFileURL } from "url";
|
|
3
|
+
import { validateBlueprint } from "@contractspec/lib.contracts";
|
|
4
|
+
|
|
5
|
+
//#region src/services/validate/blueprint-validator.ts
|
|
6
|
+
/**
|
|
7
|
+
* Validate a blueprint spec file.
|
|
8
|
+
*/
|
|
9
|
+
async function validateBlueprint$1(blueprintPath, adapters) {
|
|
10
|
+
const { fs } = adapters;
|
|
11
|
+
const resolvedPath = resolve(process.cwd(), blueprintPath);
|
|
12
|
+
if (!await fs.exists(resolvedPath)) return {
|
|
13
|
+
valid: false,
|
|
14
|
+
errors: [`Blueprint file not found: ${resolvedPath}`]
|
|
15
|
+
};
|
|
16
|
+
try {
|
|
17
|
+
const spec = extractBlueprintSpec(await loadModule(resolvedPath));
|
|
18
|
+
const report = validateBlueprint(spec);
|
|
19
|
+
return {
|
|
20
|
+
spec,
|
|
21
|
+
report,
|
|
22
|
+
valid: report.valid,
|
|
23
|
+
errors: report.errors.map((e) => `[${e.code}] ${e.path}: ${e.message}`)
|
|
24
|
+
};
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return {
|
|
27
|
+
valid: false,
|
|
28
|
+
errors: [error instanceof Error ? error.message : String(error)]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function loadModule(modulePath) {
|
|
33
|
+
try {
|
|
34
|
+
return await import(pathToFileURL(modulePath).href);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new Error(`Failed to load module at ${modulePath}: ${error}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function extractBlueprintSpec(mod) {
|
|
40
|
+
const candidates = Object.values(mod).filter(isBlueprintSpec);
|
|
41
|
+
if (candidates.length === 0) throw new Error("Blueprint module does not export an AppBlueprintSpec.");
|
|
42
|
+
return candidates[0];
|
|
43
|
+
}
|
|
44
|
+
function isBlueprintSpec(value) {
|
|
45
|
+
return typeof value === "object" && value !== null && "meta" in value && typeof value.meta?.key === "string" && typeof value.meta?.version === "number";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { validateBlueprint$1 as validateBlueprint };
|
|
50
|
+
//# sourceMappingURL=blueprint-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blueprint-validator.js","names":["validateBlueprint","validateBlueprintSpec"],"sources":["../../../src/services/validate/blueprint-validator.ts"],"sourcesContent":["import { resolve } from 'path';\nimport { pathToFileURL } from 'url';\nimport {\n validateBlueprint as validateBlueprintSpec,\n type AppBlueprintSpec,\n} from '@contractspec/lib.contracts';\nimport type { FsAdapter } from '../../ports/fs';\n\n/**\n * Result of blueprint validation.\n */\nexport interface BlueprintValidationResult {\n spec?: AppBlueprintSpec;\n report?: ReturnType<typeof validateBlueprintSpec>;\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Validate a blueprint spec file.\n */\nexport async function validateBlueprint(\n blueprintPath: string,\n adapters: { fs: FsAdapter }\n): Promise<BlueprintValidationResult> {\n const { fs } = adapters;\n const resolvedPath = resolve(process.cwd(), blueprintPath);\n\n if (!(await fs.exists(resolvedPath))) {\n return {\n valid: false,\n errors: [`Blueprint file not found: ${resolvedPath}`],\n };\n }\n\n try {\n const mod = await loadModule(resolvedPath);\n const spec = extractBlueprintSpec(mod);\n const report = validateBlueprintSpec(spec);\n\n return {\n spec,\n report,\n valid: report.valid,\n errors: report.errors.map((e) => `[${e.code}] ${e.path}: ${e.message}`),\n };\n } catch (error) {\n return {\n valid: false,\n errors: [error instanceof Error ? error.message : String(error)],\n };\n }\n}\n\nasync function loadModule(\n modulePath: string\n): Promise<Record<string, unknown>> {\n try {\n const url = pathToFileURL(modulePath).href;\n // Using native import which works with Bun and Node (if configured)\n const mod = await import(url);\n return mod;\n } catch (error) {\n throw new Error(`Failed to load module at ${modulePath}: ${error}`);\n }\n}\n\nfunction extractBlueprintSpec(mod: Record<string, unknown>): AppBlueprintSpec {\n const candidates = Object.values(mod).filter(isBlueprintSpec);\n if (candidates.length === 0) {\n throw new Error('Blueprint module does not export an AppBlueprintSpec.');\n }\n return candidates[0] as AppBlueprintSpec;\n}\n\nfunction isBlueprintSpec(value: unknown): value is AppBlueprintSpec {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'meta' in value &&\n typeof (value as AppBlueprintSpec).meta?.key === 'string' &&\n typeof (value as AppBlueprintSpec).meta?.version === 'number'\n );\n}\n"],"mappings":";;;;;;;;AAqBA,eAAsBA,oBACpB,eACA,UACoC;CACpC,MAAM,EAAE,OAAO;CACf,MAAM,eAAe,QAAQ,QAAQ,KAAK,EAAE,cAAc;AAE1D,KAAI,CAAE,MAAM,GAAG,OAAO,aAAa,CACjC,QAAO;EACL,OAAO;EACP,QAAQ,CAAC,6BAA6B,eAAe;EACtD;AAGH,KAAI;EAEF,MAAM,OAAO,qBADD,MAAM,WAAW,aAAa,CACJ;EACtC,MAAM,SAASC,kBAAsB,KAAK;AAE1C,SAAO;GACL;GACA;GACA,OAAO,OAAO;GACd,QAAQ,OAAO,OAAO,KAAK,MAAM,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,UAAU;GACxE;UACM,OAAO;AACd,SAAO;GACL,OAAO;GACP,QAAQ,CAAC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC;GACjE;;;AAIL,eAAe,WACb,YACkC;AAClC,KAAI;AAIF,SADY,MAAM,OAFN,cAAc,WAAW,CAAC;UAI/B,OAAO;AACd,QAAM,IAAI,MAAM,4BAA4B,WAAW,IAAI,QAAQ;;;AAIvE,SAAS,qBAAqB,KAAgD;CAC5E,MAAM,aAAa,OAAO,OAAO,IAAI,CAAC,OAAO,gBAAgB;AAC7D,KAAI,WAAW,WAAW,EACxB,OAAM,IAAI,MAAM,wDAAwD;AAE1E,QAAO,WAAW;;AAGpB,SAAS,gBAAgB,OAA2C;AAClE,QACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,OAAQ,MAA2B,MAAM,QAAQ,YACjD,OAAQ,MAA2B,MAAM,YAAY"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FsAdapter } from "../../ports/fs.js";
|
|
2
|
+
import { Config } from "../../types/config.js";
|
|
3
|
+
|
|
4
|
+
//#region src/services/validate/implementation-agent-validator.d.ts
|
|
5
|
+
interface ImplementationValidatorOptions {
|
|
6
|
+
implementationPath?: string;
|
|
7
|
+
}
|
|
8
|
+
interface ImplementationValidationResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
errors: string[];
|
|
11
|
+
warnings: string[];
|
|
12
|
+
suggestions: string[];
|
|
13
|
+
report?: string;
|
|
14
|
+
}
|
|
15
|
+
declare function validateImplementationWithAgent(specFile: string, specCode: string, config: Config, options: ImplementationValidatorOptions, adapters: {
|
|
16
|
+
fs: FsAdapter;
|
|
17
|
+
}): Promise<ImplementationValidationResult>;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { ImplementationValidationResult, ImplementationValidatorOptions, validateImplementationWithAgent };
|
|
20
|
+
//# sourceMappingURL=implementation-agent-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"implementation-agent-validator.d.ts","names":[],"sources":["../../../src/services/validate/implementation-agent-validator.ts"],"sourcesContent":[],"mappings":";;;;UAKiB,8BAAA;;AAAjB;AAIiB,UAAA,8BAAA,CAA8B;EAQzB,OAAA,EAAA,OAAA;EAGZ,MAAA,EAAA,MAAA,EAAA;EACC,QAAA,EAAA,MAAA,EAAA;EACO,WAAA,EAAA,MAAA,EAAA;EACP,MAAA,CAAA,EAAA,MAAA;;AAAD,iBANY,+BAAA,CAMZ,QAAA,EAAA,MAAA,EAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EAHA,MAGA,EAAA,OAAA,EAFC,8BAED,EAAA,QAAA,EAAA;MADQ;IACf,QAAQ"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AgentOrchestrator } from "../../ai/agents/orchestrator.js";
|
|
2
|
+
import { basename, dirname, join } from "path";
|
|
3
|
+
|
|
4
|
+
//#region src/services/validate/implementation-agent-validator.ts
|
|
5
|
+
async function validateImplementationWithAgent(specFile, specCode, config, options, adapters) {
|
|
6
|
+
const { fs } = adapters;
|
|
7
|
+
let implementationPath = options.implementationPath;
|
|
8
|
+
if (!implementationPath) {
|
|
9
|
+
const specDir = dirname(specFile);
|
|
10
|
+
const specBaseName = basename(specFile, ".ts");
|
|
11
|
+
const possiblePaths = [
|
|
12
|
+
join(specDir, specBaseName.replace(".contracts", ".handler") + ".ts"),
|
|
13
|
+
join(specDir, specBaseName.replace(".presentation", "") + ".tsx"),
|
|
14
|
+
join(specDir, specBaseName.replace(".form", ".form") + ".tsx"),
|
|
15
|
+
join(dirname(specDir), "handlers", specBaseName.replace(".contracts", ".handler") + ".ts"),
|
|
16
|
+
join(dirname(specDir), "components", specBaseName.replace(".presentation", "") + ".tsx")
|
|
17
|
+
];
|
|
18
|
+
for (const path of possiblePaths) if (await fs.exists(path)) {
|
|
19
|
+
implementationPath = path;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (!implementationPath || !await fs.exists(implementationPath)) return {
|
|
24
|
+
success: true,
|
|
25
|
+
errors: [],
|
|
26
|
+
warnings: ["Implementation file not found. Specify with --implementation-path"],
|
|
27
|
+
suggestions: []
|
|
28
|
+
};
|
|
29
|
+
const implementationCode = await fs.readFile(implementationPath);
|
|
30
|
+
const result = await new AgentOrchestrator(config).validate(specCode, implementationCode);
|
|
31
|
+
return {
|
|
32
|
+
success: result.success,
|
|
33
|
+
errors: result.errors || [],
|
|
34
|
+
warnings: result.warnings || [],
|
|
35
|
+
suggestions: result.suggestions || [],
|
|
36
|
+
report: result.code
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { validateImplementationWithAgent };
|
|
42
|
+
//# sourceMappingURL=implementation-agent-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"implementation-agent-validator.js","names":[],"sources":["../../../src/services/validate/implementation-agent-validator.ts"],"sourcesContent":["import type { FsAdapter } from '../../ports/fs';\nimport { dirname, basename, join } from 'path';\nimport { AgentOrchestrator } from '../../ai/agents/orchestrator';\nimport type { Config } from '../../types/config';\n\nexport interface ImplementationValidatorOptions {\n implementationPath?: string;\n}\n\nexport interface ImplementationValidationResult {\n success: boolean;\n errors: string[];\n warnings: string[];\n suggestions: string[];\n report?: string;\n}\n\nexport async function validateImplementationWithAgent(\n specFile: string,\n specCode: string,\n config: Config,\n options: ImplementationValidatorOptions,\n adapters: { fs: FsAdapter }\n): Promise<ImplementationValidationResult> {\n const { fs } = adapters;\n\n // Find implementation file\n let implementationPath = options.implementationPath;\n\n if (!implementationPath) {\n // Try to infer from spec file path\n const specDir = dirname(specFile);\n const specBaseName = basename(specFile, '.ts');\n\n // Try common patterns\n const possiblePaths = [\n join(specDir, specBaseName.replace('.contracts', '.handler') + '.ts'),\n join(specDir, specBaseName.replace('.presentation', '') + '.tsx'),\n join(specDir, specBaseName.replace('.form', '.form') + '.tsx'),\n join(\n dirname(specDir),\n 'handlers',\n specBaseName.replace('.contracts', '.handler') + '.ts'\n ),\n join(\n dirname(specDir),\n 'components',\n specBaseName.replace('.presentation', '') + '.tsx'\n ),\n ];\n\n for (const path of possiblePaths) {\n if (await fs.exists(path)) {\n implementationPath = path;\n break;\n }\n }\n }\n\n if (!implementationPath || !(await fs.exists(implementationPath))) {\n return {\n success: true, // Not an error if file not found, just nothing to validate\n errors: [],\n warnings: [\n 'Implementation file not found. Specify with --implementation-path',\n ],\n suggestions: [],\n };\n }\n\n const implementationCode = await fs.readFile(implementationPath);\n\n // Use agent orchestrator to validate\n const orchestrator = new AgentOrchestrator(config);\n const result = await orchestrator.validate(specCode, implementationCode);\n\n return {\n success: result.success,\n errors: result.errors || [],\n warnings: result.warnings || [],\n suggestions: result.suggestions || [],\n report: result.code,\n };\n}\n"],"mappings":";;;;AAiBA,eAAsB,gCACpB,UACA,UACA,QACA,SACA,UACyC;CACzC,MAAM,EAAE,OAAO;CAGf,IAAI,qBAAqB,QAAQ;AAEjC,KAAI,CAAC,oBAAoB;EAEvB,MAAM,UAAU,QAAQ,SAAS;EACjC,MAAM,eAAe,SAAS,UAAU,MAAM;EAG9C,MAAM,gBAAgB;GACpB,KAAK,SAAS,aAAa,QAAQ,cAAc,WAAW,GAAG,MAAM;GACrE,KAAK,SAAS,aAAa,QAAQ,iBAAiB,GAAG,GAAG,OAAO;GACjE,KAAK,SAAS,aAAa,QAAQ,SAAS,QAAQ,GAAG,OAAO;GAC9D,KACE,QAAQ,QAAQ,EAChB,YACA,aAAa,QAAQ,cAAc,WAAW,GAAG,MAClD;GACD,KACE,QAAQ,QAAQ,EAChB,cACA,aAAa,QAAQ,iBAAiB,GAAG,GAAG,OAC7C;GACF;AAED,OAAK,MAAM,QAAQ,cACjB,KAAI,MAAM,GAAG,OAAO,KAAK,EAAE;AACzB,wBAAqB;AACrB;;;AAKN,KAAI,CAAC,sBAAsB,CAAE,MAAM,GAAG,OAAO,mBAAmB,CAC9D,QAAO;EACL,SAAS;EACT,QAAQ,EAAE;EACV,UAAU,CACR,oEACD;EACD,aAAa,EAAE;EAChB;CAGH,MAAM,qBAAqB,MAAM,GAAG,SAAS,mBAAmB;CAIhE,MAAM,SAAS,MADM,IAAI,kBAAkB,OAAO,CAChB,SAAS,UAAU,mBAAmB;AAExE,QAAO;EACL,SAAS,OAAO;EAChB,QAAQ,OAAO,UAAU,EAAE;EAC3B,UAAU,OAAO,YAAY,EAAE;EAC/B,aAAa,OAAO,eAAe,EAAE;EACrC,QAAQ,OAAO;EAChB"}
|
package/dist/services/{validate-implementation.d.ts → validate/implementation-validator.d.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { FsAdapter } from "
|
|
1
|
+
import { FsAdapter } from "../../ports/fs.js";
|
|
2
2
|
import { WorkspaceConfig } from "@contractspec/module.workspace";
|
|
3
3
|
|
|
4
|
-
//#region src/services/validate-
|
|
4
|
+
//#region src/services/validate/implementation-validator.d.ts
|
|
5
5
|
|
|
6
6
|
interface ValidateImplementationOptions {
|
|
7
7
|
checkHandlers?: boolean;
|
|
@@ -29,4 +29,4 @@ declare function validateImplementationFiles(specFile: string, adapters: {
|
|
|
29
29
|
}, config: WorkspaceConfig, options?: ValidateImplementationOptions): Promise<ValidateImplementationResult>;
|
|
30
30
|
//#endregion
|
|
31
31
|
export { ValidateImplementationOptions, ValidateImplementationResult, validateImplementationFiles };
|
|
32
|
-
//# sourceMappingURL=
|
|
32
|
+
//# sourceMappingURL=implementation-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"implementation-validator.d.ts","names":[],"sources":["../../../src/services/validate/implementation-validator.ts"],"sourcesContent":[],"mappings":";;;;;AAoDU,UAzCO,6BAAA,CAyCP;EACC,aAAA,CAAA,EAAA,OAAA;EACA,UAAA,CAAA,EAAA,OAAA;EAAR;;;;;UAlCc,4BAAA;;;;;;;;;;;;;iBA6BK,2BAAA;MAEJ;WACR,2BACC,gCACR,QAAQ"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { scanSpecSource } from "@contractspec/module.workspace";
|
|
2
2
|
|
|
3
|
-
//#region src/services/validate-
|
|
3
|
+
//#region src/services/validate/implementation-validator.ts
|
|
4
4
|
function toKebabCase(value) {
|
|
5
5
|
return value.replace(/\./g, "-").replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
6
6
|
}
|
|
@@ -61,4 +61,4 @@ async function validateImplementationFiles(specFile, adapters, config, options =
|
|
|
61
61
|
|
|
62
62
|
//#endregion
|
|
63
63
|
export { validateImplementationFiles };
|
|
64
|
-
//# sourceMappingURL=
|
|
64
|
+
//# sourceMappingURL=implementation-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"implementation-validator.js","names":["errors: string[]","warnings: string[]","expected: ValidateImplementationResult['expected']"],"sources":["../../../src/services/validate/implementation-validator.ts"],"sourcesContent":["/**\n * Implementation validation service (handlers + tests).\n *\n * Deterministic, static checks intended for reuse across CLI/VSCode/web tooling.\n * This does NOT execute spec modules.\n */\n\nimport type { WorkspaceConfig } from '@contractspec/module.workspace';\nimport { scanSpecSource } from '@contractspec/module.workspace';\nimport type { FsAdapter } from '../../ports/fs';\n\nexport interface ValidateImplementationOptions {\n checkHandlers?: boolean;\n checkTests?: boolean;\n /**\n * Override workspace outputDir (defaults to config.outputDir).\n */\n outputDir?: string;\n}\n\nexport interface ValidateImplementationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n expected: {\n handlerPath?: string;\n handlerTestPath?: string;\n componentPath?: string;\n componentTestPath?: string;\n formPath?: string;\n formTestPath?: string;\n };\n}\n\nfunction toKebabCase(value: string): string {\n return value\n .replace(/\\./g, '-')\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .toLowerCase();\n}\n\nfunction toPascalCase(value: string): string {\n return value\n .split(/[-_.]/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n}\n\nexport async function validateImplementationFiles(\n specFile: string,\n adapters: { fs: FsAdapter },\n config: WorkspaceConfig,\n options: ValidateImplementationOptions = {}\n): Promise<ValidateImplementationResult> {\n const { fs } = adapters;\n const errors: string[] = [];\n const warnings: string[] = [];\n\n const exists = await fs.exists(specFile);\n if (!exists) {\n return {\n valid: false,\n errors: [`Spec file not found: ${specFile}`],\n warnings: [],\n expected: {},\n };\n }\n\n const code = await fs.readFile(specFile);\n const scan = scanSpecSource(code, specFile);\n const specName = scan.key ?? fs.basename(specFile).replace(/\\.[jt]s$/, '');\n const outRoot = options.outputDir ?? config.outputDir ?? './src';\n const kebab = toKebabCase(specName);\n\n const expected: ValidateImplementationResult['expected'] = {};\n\n if (scan.specType === 'operation') {\n expected.handlerPath = fs.join(outRoot, 'handlers', `${kebab}.handler.ts`);\n expected.handlerTestPath = fs.join(\n outRoot,\n 'handlers',\n `${kebab}.handler.test.ts`\n );\n }\n if (scan.specType === 'presentation') {\n expected.componentPath = fs.join(outRoot, 'components', `${kebab}.tsx`);\n expected.componentTestPath = fs.join(\n outRoot,\n 'components',\n `${kebab}.test.tsx`\n );\n }\n if (scan.specType === 'form') {\n expected.formPath = fs.join(outRoot, 'forms', `${kebab}.form.tsx`);\n expected.formTestPath = fs.join(outRoot, 'forms', `${kebab}.form.test.tsx`);\n }\n\n if (options.checkHandlers && expected.handlerPath) {\n const handlerExists = await fs.exists(expected.handlerPath);\n if (!handlerExists) {\n errors.push(`Missing handler file: ${expected.handlerPath}`);\n } else {\n const handlerCode = await fs.readFile(expected.handlerPath);\n\n const expectedSpecVar = `${toPascalCase(specName.split('.').pop() ?? specName)}Spec`;\n const hasContractHandlerType = /ContractHandler<\\s*typeof\\s+\\w+\\s*>/.test(\n handlerCode\n );\n const referencesExpectedSpec = new RegExp(\n `typeof\\\\s+${expectedSpecVar}\\\\b`\n ).test(handlerCode);\n if (!hasContractHandlerType) {\n warnings.push(\n `Handler does not appear to type itself as ContractHandler<typeof Spec>: ${expected.handlerPath}`\n );\n } else if (!referencesExpectedSpec) {\n warnings.push(\n `Handler ContractHandler typing does not reference expected spec var (${expectedSpecVar}): ${expected.handlerPath}`\n );\n }\n }\n }\n\n if (options.checkTests) {\n const candidateTests = [\n expected.handlerTestPath,\n expected.componentTestPath,\n expected.formTestPath,\n ].filter((p): p is string => typeof p === 'string');\n\n for (const testPath of candidateTests) {\n const testExists = await fs.exists(testPath);\n if (!testExists) {\n errors.push(`Missing test file: ${testPath}`);\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n expected,\n };\n}\n"],"mappings":";;;AAkCA,SAAS,YAAY,OAAuB;AAC1C,QAAO,MACJ,QAAQ,OAAO,IAAI,CACnB,QAAQ,mBAAmB,QAAQ,CACnC,aAAa;;AAGlB,SAAS,aAAa,OAAuB;AAC3C,QAAO,MACJ,MAAM,QAAQ,CACd,OAAO,QAAQ,CACf,KAAK,SAAS,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,CAC3D,KAAK,GAAG;;AAGb,eAAsB,4BACpB,UACA,UACA,QACA,UAAyC,EAAE,EACJ;CACvC,MAAM,EAAE,OAAO;CACf,MAAMA,SAAmB,EAAE;CAC3B,MAAMC,WAAqB,EAAE;AAG7B,KAAI,CADW,MAAM,GAAG,OAAO,SAAS,CAEtC,QAAO;EACL,OAAO;EACP,QAAQ,CAAC,wBAAwB,WAAW;EAC5C,UAAU,EAAE;EACZ,UAAU,EAAE;EACb;CAIH,MAAM,OAAO,eADA,MAAM,GAAG,SAAS,SAAS,EACN,SAAS;CAC3C,MAAM,WAAW,KAAK,OAAO,GAAG,SAAS,SAAS,CAAC,QAAQ,YAAY,GAAG;CAC1E,MAAM,UAAU,QAAQ,aAAa,OAAO,aAAa;CACzD,MAAM,QAAQ,YAAY,SAAS;CAEnC,MAAMC,WAAqD,EAAE;AAE7D,KAAI,KAAK,aAAa,aAAa;AACjC,WAAS,cAAc,GAAG,KAAK,SAAS,YAAY,GAAG,MAAM,aAAa;AAC1E,WAAS,kBAAkB,GAAG,KAC5B,SACA,YACA,GAAG,MAAM,kBACV;;AAEH,KAAI,KAAK,aAAa,gBAAgB;AACpC,WAAS,gBAAgB,GAAG,KAAK,SAAS,cAAc,GAAG,MAAM,MAAM;AACvE,WAAS,oBAAoB,GAAG,KAC9B,SACA,cACA,GAAG,MAAM,WACV;;AAEH,KAAI,KAAK,aAAa,QAAQ;AAC5B,WAAS,WAAW,GAAG,KAAK,SAAS,SAAS,GAAG,MAAM,WAAW;AAClE,WAAS,eAAe,GAAG,KAAK,SAAS,SAAS,GAAG,MAAM,gBAAgB;;AAG7E,KAAI,QAAQ,iBAAiB,SAAS,YAEpC,KAAI,CADkB,MAAM,GAAG,OAAO,SAAS,YAAY,CAEzD,QAAO,KAAK,yBAAyB,SAAS,cAAc;MACvD;EACL,MAAM,cAAc,MAAM,GAAG,SAAS,SAAS,YAAY;EAE3D,MAAM,kBAAkB,GAAG,aAAa,SAAS,MAAM,IAAI,CAAC,KAAK,IAAI,SAAS,CAAC;EAC/E,MAAM,yBAAyB,sCAAsC,KACnE,YACD;EACD,MAAM,0CAAyB,IAAI,OACjC,aAAa,gBAAgB,KAC9B,EAAC,KAAK,YAAY;AACnB,MAAI,CAAC,uBACH,UAAS,KACP,2EAA2E,SAAS,cACrF;WACQ,CAAC,uBACV,UAAS,KACP,wEAAwE,gBAAgB,KAAK,SAAS,cACvG;;AAKP,KAAI,QAAQ,YAAY;EACtB,MAAM,iBAAiB;GACrB,SAAS;GACT,SAAS;GACT,SAAS;GACV,CAAC,QAAQ,MAAmB,OAAO,MAAM,SAAS;AAEnD,OAAK,MAAM,YAAY,eAErB,KAAI,CADe,MAAM,GAAG,OAAO,SAAS,CAE1C,QAAO,KAAK,sBAAsB,WAAW;;AAKnD,QAAO;EACL,OAAO,OAAO,WAAW;EACzB;EACA;EACA;EACD"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ValidateSpecOptions, ValidateSpecResult, validateSpec, validateSpecs } from "./spec-validator.js";
|
|
2
|
+
import { ValidateImplementationOptions, ValidateImplementationResult, validateImplementationFiles } from "./implementation-validator.js";
|
|
3
|
+
import { BlueprintValidationResult, validateBlueprint } from "./blueprint-validator.js";
|
|
4
|
+
import { TenantValidationContext, TenantValidationResult, validateTenantConfig } from "./tenant-validator.js";
|
|
5
|
+
import { ImplementationValidationResult, ImplementationValidatorOptions, validateImplementationWithAgent } from "./implementation-agent-validator.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { validateSpec, validateSpecs } from "./spec-validator.js";
|
|
2
|
+
import { validateImplementationFiles } from "./implementation-validator.js";
|
|
3
|
+
import { validateBlueprint } from "./blueprint-validator.js";
|
|
4
|
+
import { validateTenantConfig } from "./tenant-validator.js";
|
|
5
|
+
import { validateImplementationWithAgent } from "./implementation-agent-validator.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { FsAdapter } from "
|
|
2
|
-
import { LoggerAdapter } from "
|
|
1
|
+
import { FsAdapter } from "../../ports/fs.js";
|
|
2
|
+
import { LoggerAdapter } from "../../ports/logger.js";
|
|
3
3
|
import { ValidationResult } from "@contractspec/module.workspace";
|
|
4
4
|
|
|
5
|
-
//#region src/services/validate.d.ts
|
|
5
|
+
//#region src/services/validate/spec-validator.d.ts
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Options for spec validation.
|
|
@@ -21,6 +21,7 @@ interface ValidateSpecResult {
|
|
|
21
21
|
structureResult?: ValidationResult;
|
|
22
22
|
errors: string[];
|
|
23
23
|
warnings: string[];
|
|
24
|
+
code?: string;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Validate a spec file.
|
|
@@ -38,4 +39,4 @@ declare function validateSpecs(specFiles: string[], adapters: {
|
|
|
38
39
|
}, options?: ValidateSpecOptions): Promise<Map<string, ValidateSpecResult>>;
|
|
39
40
|
//#endregion
|
|
40
41
|
export { ValidateSpecOptions, ValidateSpecResult, validateSpec, validateSpecs };
|
|
41
|
-
//# sourceMappingURL=
|
|
42
|
+
//# sourceMappingURL=spec-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-validator.d.ts","names":[],"sources":["../../../src/services/validate/spec-validator.ts"],"sourcesContent":[],"mappings":";;;;;;AAwBA;AAWA;;AAEqC,UAvBpB,mBAAA,CAuBoB;EAC1B;;;EACD,aAAA,CAAA,EAAA,OAAA;AAsCV;;;;AAIuB,UAzDN,kBAAA,CAyDM;EAAZ,KAAA,EAAA,OAAA;EAAR,eAAA,CAAA,EAvDiB,gBAuDjB;EAAO,MAAA,EAAA,MAAA,EAAA;;;;;;;iBA9CY,YAAA;MAEJ;UAAmB;aAC1B,sBACR,QAAQ;;;;iBAsCW,aAAA;MAEJ;UAAmB;aAC1B,sBACR,QAAQ,YAAY"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { validateSpecStructure } from "@contractspec/module.workspace";
|
|
2
2
|
|
|
3
|
-
//#region src/services/validate.ts
|
|
3
|
+
//#region src/services/validate/spec-validator.ts
|
|
4
4
|
/**
|
|
5
5
|
* Validation service.
|
|
6
6
|
*/
|
|
@@ -12,7 +12,8 @@ async function validateSpec(specFile, adapters, options = {}) {
|
|
|
12
12
|
if (!await fs.exists(specFile)) return {
|
|
13
13
|
valid: false,
|
|
14
14
|
errors: [`Spec file not found: ${specFile}`],
|
|
15
|
-
warnings: []
|
|
15
|
+
warnings: [],
|
|
16
|
+
code: void 0
|
|
16
17
|
};
|
|
17
18
|
const specCode = await fs.readFile(specFile);
|
|
18
19
|
const fileName = fs.basename(specFile);
|
|
@@ -28,7 +29,8 @@ async function validateSpec(specFile, adapters, options = {}) {
|
|
|
28
29
|
valid: allErrors.length === 0,
|
|
29
30
|
structureResult,
|
|
30
31
|
errors: allErrors,
|
|
31
|
-
warnings: allWarnings
|
|
32
|
+
warnings: allWarnings,
|
|
33
|
+
code: specCode
|
|
32
34
|
};
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
@@ -45,4 +47,4 @@ async function validateSpecs(specFiles, adapters, options = {}) {
|
|
|
45
47
|
|
|
46
48
|
//#endregion
|
|
47
49
|
export { validateSpec, validateSpecs };
|
|
48
|
-
//# sourceMappingURL=
|
|
50
|
+
//# sourceMappingURL=spec-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-validator.js","names":["allErrors: string[]","allWarnings: string[]","structureResult: ValidationResult | undefined"],"sources":["../../../src/services/validate/spec-validator.ts"],"sourcesContent":["/**\n * Validation service.\n */\n\nimport {\n validateSpecStructure,\n type ValidationResult,\n} from '@contractspec/module.workspace';\nimport type { FsAdapter } from '../../ports/fs';\nimport type { LoggerAdapter } from '../../ports/logger';\n\n/**\n * Options for spec validation.\n */\nexport interface ValidateSpecOptions {\n /**\n * Skip spec structure validation (e.g., for blueprint files).\n */\n skipStructure?: boolean;\n}\n\n/**\n * Result of spec validation.\n */\nexport interface ValidateSpecResult {\n valid: boolean;\n structureResult?: ValidationResult;\n errors: string[];\n warnings: string[];\n code?: string;\n}\n\n/**\n * Validate a spec file.\n */\nexport async function validateSpec(\n specFile: string,\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n options: ValidateSpecOptions = {}\n): Promise<ValidateSpecResult> {\n const { fs } = adapters;\n\n const exists = await fs.exists(specFile);\n if (!exists) {\n return {\n valid: false,\n errors: [`Spec file not found: ${specFile}`],\n warnings: [],\n code: undefined,\n };\n }\n\n const specCode = await fs.readFile(specFile);\n const fileName = fs.basename(specFile);\n\n const allErrors: string[] = [];\n const allWarnings: string[] = [];\n let structureResult: ValidationResult | undefined;\n\n if (!options.skipStructure) {\n structureResult = validateSpecStructure(specCode, fileName);\n allErrors.push(...structureResult.errors);\n allWarnings.push(...structureResult.warnings);\n }\n\n return {\n valid: allErrors.length === 0,\n structureResult,\n errors: allErrors,\n warnings: allWarnings,\n code: specCode,\n };\n}\n\n/**\n * Validate multiple spec files.\n */\nexport async function validateSpecs(\n specFiles: string[],\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n options: ValidateSpecOptions = {}\n): Promise<Map<string, ValidateSpecResult>> {\n const results = new Map<string, ValidateSpecResult>();\n\n for (const specFile of specFiles) {\n const result = await validateSpec(specFile, adapters, options);\n results.set(specFile, result);\n }\n\n return results;\n}\n"],"mappings":";;;;;;;;;AAmCA,eAAsB,aACpB,UACA,UACA,UAA+B,EAAE,EACJ;CAC7B,MAAM,EAAE,OAAO;AAGf,KAAI,CADW,MAAM,GAAG,OAAO,SAAS,CAEtC,QAAO;EACL,OAAO;EACP,QAAQ,CAAC,wBAAwB,WAAW;EAC5C,UAAU,EAAE;EACZ,MAAM;EACP;CAGH,MAAM,WAAW,MAAM,GAAG,SAAS,SAAS;CAC5C,MAAM,WAAW,GAAG,SAAS,SAAS;CAEtC,MAAMA,YAAsB,EAAE;CAC9B,MAAMC,cAAwB,EAAE;CAChC,IAAIC;AAEJ,KAAI,CAAC,QAAQ,eAAe;AAC1B,oBAAkB,sBAAsB,UAAU,SAAS;AAC3D,YAAU,KAAK,GAAG,gBAAgB,OAAO;AACzC,cAAY,KAAK,GAAG,gBAAgB,SAAS;;AAG/C,QAAO;EACL,OAAO,UAAU,WAAW;EAC5B;EACA,QAAQ;EACR,UAAU;EACV,MAAM;EACP;;;;;AAMH,eAAsB,cACpB,WACA,UACA,UAA+B,EAAE,EACS;CAC1C,MAAM,0BAAU,IAAI,KAAiC;AAErD,MAAK,MAAM,YAAY,WAAW;EAChC,MAAM,SAAS,MAAM,aAAa,UAAU,UAAU,QAAQ;AAC9D,UAAQ,IAAI,UAAU,OAAO;;AAG/B,QAAO"}
|