#!/usr/bin/env node /** * cli:roslyn-validator * SmartStack C# static analysis — checks per-layer conventions (SS001-SS010). * * Usage: * npx --prefer-offline tsx skills/validation/roslyn/cli/index.ts --src [--layer ] * * Exit codes: * 0 Success (0 errors) * 1 Errors detected * 2 Internal CLI error / input validation */ import { parseArgs } from 'node:util' import { resolve } from 'node:path' import { validate } from './validate' import { execute } from './execute' import { executeEnvelope, failExecute, printEnvelope } from '../../../lib/output.js' const COMMAND = 'roslyn-validator' const { values } = parseArgs({ options: { src: { type: 'string' }, layer: { type: 'string' }, help: { type: 'boolean', short: 'h' }, }, strict: true, }) if (values.help) { const paren = String.fromCharCode(41) console.log(`cli:roslyn-validator — SmartStack Studio SmartStack C# static analysis (SS001-SS010${paren}. Usage: npx --prefer-offline tsx index.ts --src [--layer ] Options: --src Directory containing the .cs files (required${paren} --layer Optional filter: controller, service, repository, entity, all (default: all${paren} -h, --help Show this help Rules: SS001 Controller inherits ControllerBase + [ApiController] SS002 Single IService injection (no DbContext${paren} SS003 [NavRoute] present and module.section format SS004 [RequirePermission] on every public endpoint SS005 Returns DTOs, never entities SS006 CancellationToken as last async parameter SS007 NavRoute unique within the project SS008 Service without DbContext SS009 Repository without business logic SS010 Entity inherits BaseEntity Exit codes: 0 Success (0 errors${paren} 1 Errors detected 2 Internal CLI error`) process.exit(0) } if (!values.src) { printEnvelope(failExecute(COMMAND, ['--src is required'])) process.exit(2) } const srcPath = resolve(values.src) const validation = validate({ src: srcPath, layer: values.layer || 'all' }) if (!validation.valid) { printEnvelope(failExecute(COMMAND, validation.blockers)) process.exit(2) } const report = execute(validation.data!) if (report.errors === 0) { printEnvelope(executeEnvelope(COMMAND, { report, warnings: report.warnings > 0 ? [`${report.warnings} warning(s)`] : [] })) } else { printEnvelope({ ...failExecute(COMMAND, [`${report.errors} error(s) detected`]), report }) } process.exit(report.errors > 0 ? 1 : 0)