feat(manascore): add API conformity and cross-app consistency checks

- API Conformity: 7 checks (responses, errors, pagination, versioning,
  docs, health, validation) with checklist UI
- Cross-App Consistency: track shared package usage (auth, ui, theme,
  branding, i18n, error-tracking + optional storage, llm)
- Both shown as compact badges on overview, detailed cards on detail page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-24 12:24:45 +01:00
parent 61c23d5e79
commit 06694eac19
5 changed files with 206 additions and 0 deletions

View file

@ -228,6 +228,31 @@ const manascoreCollection = defineCollection({
lastChecked: z.string().optional(), // ISO date
})
.optional(),
// API conformity checks
apiConformity: z
.object({
consistentResponses: z.boolean(), // Uses ApiResult<T> format
errorCodes: z.boolean(), // Consistent HTTP error codes
pagination: z.boolean(), // Supports pagination where needed
versioning: z.boolean(), // API versioning (/api/v1/)
documentation: z.boolean(), // Swagger/OpenAPI docs
healthEndpoint: z.boolean(), // /health endpoint
validation: z.boolean(), // DTO validation on all inputs
})
.optional(),
// Cross-app consistency (shared package usage)
consistency: z
.object({
sharedAuth: z.boolean(), // Uses @manacore/shared-nestjs-auth or nestjs-integration
sharedUi: z.boolean(), // Uses @manacore/shared-ui components
sharedTheme: z.boolean(), // Uses @manacore/shared-theme
sharedBranding: z.boolean(), // Uses @manacore/shared-branding
sharedI18n: z.boolean(), // Uses @manacore/shared-i18n
sharedErrorTracking: z.boolean(), // Uses @manacore/shared-error-tracking
sharedStorage: z.boolean().optional(), // Uses @manacore/shared-storage (if applicable)
sharedLlm: z.boolean().optional(), // Uses @manacore/shared-llm (if applicable)
})
.optional(),
// Score history for trend visualization
history: z
.array(

View file

@ -18,6 +18,23 @@ scores:
documentation: 98
security: 92
ux: 95
apiConformity:
consistentResponses: true
errorCodes: true
pagination: true
versioning: true
documentation: true
healthEndpoint: true
validation: true
consistency:
sharedAuth: true
sharedUi: true
sharedTheme: true
sharedBranding: true
sharedI18n: true
sharedErrorTracking: true
sharedStorage: false
sharedLlm: false
dependencies:
total: 42
outdated: 9

View file

@ -18,6 +18,23 @@ scores:
documentation: 95
security: 90
ux: 94
apiConformity:
consistentResponses: true
errorCodes: true
pagination: false
versioning: true
documentation: true
healthEndpoint: true
validation: true
consistency:
sharedAuth: true
sharedUi: true
sharedTheme: true
sharedBranding: true
sharedI18n: true
sharedErrorTracking: true
sharedStorage: false
sharedLlm: false
dependencies:
total: 38
outdated: 9

View file

@ -274,6 +274,108 @@ function getBarColor(score: number): string {
})()
}
{/* API Conformity */}
{
audit.data.apiConformity &&
(() => {
const api = audit.data.apiConformity;
const checks = [
{
key: 'Konsistente Responses',
value: api.consistentResponses,
desc: 'ApiResult<T>',
},
{ key: 'Error Codes', value: api.errorCodes, desc: 'HTTP Status' },
{ key: 'Pagination', value: api.pagination, desc: 'Offset/Cursor' },
{ key: 'Versioning', value: api.versioning, desc: '/api/v1/' },
{ key: 'Dokumentation', value: api.documentation, desc: 'Swagger' },
{ key: 'Health Endpoint', value: api.healthEndpoint, desc: '/health' },
{ key: 'Validation', value: api.validation, desc: 'DTO' },
];
const passed = checks.filter((c) => c.value).length;
return (
<div class="border-border/50 mb-8 rounded-xl border bg-gradient-to-br from-white/5 to-white/[0.02] p-6">
<div class="flex items-center justify-between mb-4">
<div>
<h2 class="text-foreground text-sm font-semibold">API Conformity</h2>
<p class="text-muted-foreground text-xs">Konsistenz der Backend-API</p>
</div>
<span
class={`text-lg font-bold ${passed === checks.length ? 'text-emerald-500' : 'text-yellow-500'}`}
>
{passed}/{checks.length}
</span>
</div>
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
{checks.map(({ key, value, desc }) => (
<div class="flex items-center gap-2">
<span class={`text-sm ${value ? 'text-emerald-500' : 'text-red-500'}`}>
{value ? '✓' : '✗'}
</span>
<span class="text-foreground text-xs font-medium">{key}</span>
<span class="text-muted-foreground/60 text-[10px]">({desc})</span>
</div>
))}
</div>
</div>
);
})()
}
{/* Cross-App Consistency */}
{
audit.data.consistency &&
(() => {
const c = audit.data.consistency;
const packages = [
{ key: 'shared-auth', used: c.sharedAuth, req: true },
{ key: 'shared-ui', used: c.sharedUi, req: true },
{ key: 'shared-theme', used: c.sharedTheme, req: true },
{ key: 'shared-branding', used: c.sharedBranding, req: true },
{ key: 'shared-i18n', used: c.sharedI18n, req: true },
{ key: 'shared-error-tracking', used: c.sharedErrorTracking, req: true },
{ key: 'shared-storage', used: c.sharedStorage ?? false, req: false },
{ key: 'shared-llm', used: c.sharedLlm ?? false, req: false },
];
const core = packages.filter((p) => p.req);
const coreUsed = core.filter((p) => p.used).length;
return (
<div class="border-border/50 mb-8 rounded-xl border bg-gradient-to-br from-white/5 to-white/[0.02] p-6">
<div class="flex items-center justify-between mb-4">
<div>
<h2 class="text-foreground text-sm font-semibold">Cross-App Consistency</h2>
<p class="text-muted-foreground text-xs">Nutzung der shared Packages</p>
</div>
<span
class={`text-lg font-bold ${coreUsed === core.length ? 'text-emerald-500' : 'text-yellow-500'}`}
>
{coreUsed}/{core.length} Core
</span>
</div>
<div class="grid grid-cols-2 gap-2 sm:grid-cols-4">
{packages.map(({ key, used, req }) => (
<div
class={`rounded-lg border p-2 text-center ${used ? 'border-emerald-500/30 bg-emerald-500/5' : req ? 'border-red-500/30 bg-red-500/5' : 'border-border/30'}`}
>
<span
class={`text-sm ${used ? 'text-emerald-500' : req ? 'text-red-500' : 'text-muted-foreground/50'}`}
>
{used ? '✓' : req ? '✗' : '—'}
</span>
<span class="text-[10px] block text-muted-foreground mt-0.5">{key}</span>
{!req && (
<span class="text-[8px] text-muted-foreground/50 block">optional</span>
)}
</div>
))}
</div>
</div>
);
})()
}
{/* Dependency Health */}
{
audit.data.dependencies &&

View file

@ -172,6 +172,51 @@ const statuses = [...new Set(sortedAudits.map((a) => a.data.status))];
))}
</div>
{/* API & Consistency badges */}
{(data.apiConformity || data.consistency) && (
<div class="mt-1.5 flex items-center gap-3">
{data.apiConformity &&
(() => {
const api = data.apiConformity;
const passed = [
api.consistentResponses,
api.errorCodes,
api.pagination,
api.versioning,
api.documentation,
api.healthEndpoint,
api.validation,
].filter(Boolean).length;
return (
<span
class={`text-[10px] font-medium ${passed === 7 ? 'text-emerald-500' : 'text-yellow-500'}`}
>
API: {passed}/7
</span>
);
})()}
{data.consistency &&
(() => {
const c = data.consistency;
const core = [
c.sharedAuth,
c.sharedUi,
c.sharedTheme,
c.sharedBranding,
c.sharedI18n,
c.sharedErrorTracking,
].filter(Boolean).length;
return (
<span
class={`text-[10px] font-medium ${core === 6 ? 'text-emerald-500' : 'text-yellow-500'}`}
>
Shared: {core}/6
</span>
);
})()}
</div>
)}
{/* Dependency health */}
{data.dependencies &&
(() => {