{"version": "2.1.0", "$schema": "https://json.schemastore.org/sarif-2.1.0.json", "runs": [{"tool": {"driver": {"name": "Repobility", "informationUri": "https://repobility.com", "rules": [{"id": "scanner-09c004c9dc7d3e35", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/widgets/PracticeStats.tsx:245", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/widgets/PracticeStats.tsx:245"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-47add4e345df024f", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/widgets/ChatSessionList.tsx:93", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/widgets/ChatSessionList.tsx:93"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-6c163d3a85951ef1", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/widgets/Header.tsx:59", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/widgets/Header.tsx:59"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-904a35e5995a8a96", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/tests/TopicsPage.tsx:77", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/tests/TopicsPage.tsx:77"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-1918ad65a2b86521", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/tests/ThemesCatalogPage.tsx:64", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/tests/ThemesCatalogPage.tsx:64"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-9032be7ff819c5be", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/tests/TestTopicSelectPage.tsx:115", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/tests/TestTopicSelectPage.tsx:115"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-239a8370cf8e87bd", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/chat/ChatPage.tsx:284", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/chat/ChatPage.tsx:284"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-2490dcd3c37e7b63", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/roadmaps/RoadmapDetailPage.tsx:112", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/roadmaps/RoadmapDetailPage.tsx:112"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-7a0d8645df8fd079", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/profile/ProfileHistoryPage.tsx:120", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/profile/ProfileHistoryPage.tsx:120"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-09929155114e2443", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/live-coding/LiveCodingPage.tsx:90", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/live-coding/LiveCodingPage.tsx:90"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-341b9f2545daa80f", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/live-coding/LiveCodingTaskPage.tsx:654", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/live-coding/LiveCodingTaskPage.tsx:654"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-3a52423ac96c1a77", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/stats/StatsPage.tsx:532", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/stats/StatsPage.tsx:532"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-c258f0c26b6d0275", "name": "Stray `console.log` in TS/JS \u2014 backend/prisma/verify-live-coding.ts:845", "shortDescription": {"text": "Stray `console.log` in TS/JS \u2014 backend/prisma/verify-live-coding.ts:845"}, "fullDescription": {"text": "Replace with the toast helper, an error boundary, or remove. `console.warn` / `console.error` are acceptable.\n\nWhy: Hygiene \u2014 easy to leak debug output.\nRule id: fq.console-leak"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "low", "confidence": 1.0}}, {"id": "scanner-64bb990b5dbcac67", "name": "Stray `console.log` in TS/JS \u2014 backend/prisma/refresh-live-coding.ts:33", "shortDescription": {"text": "Stray `console.log` in TS/JS \u2014 backend/prisma/refresh-live-coding.ts:33"}, "fullDescription": {"text": "Replace with the toast helper, an error boundary, or remove. `console.warn` / `console.error` are acceptable.\n\nWhy: Hygiene \u2014 easy to leak debug output.\nRule id: fq.console-leak"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "low", "confidence": 1.0}}, {"id": "scanner-f6e597023f09ea9f", "name": "Icon-only button without accessible name \u2014 backend/prisma/seed.ts:1013", "shortDescription": {"text": "Icon-only button without accessible name \u2014 backend/prisma/seed.ts:1013"}, "fullDescription": {"text": "A `<button>` whose only child is a single glyph or symbol needs `title=` or `aria-label=` so screen readers (and tooltips on hover) work.\n\nWhy: P3 in CHECKLIST.md \u2014 icon-only buttons skipped a title.\nRule id: fq.button.no-label"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "low", "confidence": 1.0}}, {"id": "scanner-68dc24e8847ff21e", "name": "Stray `console.log` in TS/JS \u2014 backend/prisma/seed.ts:49", "shortDescription": {"text": "Stray `console.log` in TS/JS \u2014 backend/prisma/seed.ts:49"}, "fullDescription": {"text": "Replace with the toast helper, an error boundary, or remove. `console.warn` / `console.error` are acceptable.\n\nWhy: Hygiene \u2014 easy to leak debug output.\nRule id: fq.console-leak"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "low", "confidence": 1.0}}, {"id": "scanner-89786bdb3bb3d355", "name": "`dangerouslySetInnerHTML` used in a React component \u2014 backend/prisma/seed.ts:2832", "shortDescription": {"text": "`dangerouslySetInnerHTML` used in a React component \u2014 backend/prisma/seed.ts:2832"}, "fullDescription": {"text": "Open XSS surface unless the input is provably trusted. Replace with explicit JSX or sanitize via a vetted library.\n\nWhy: OWASP basics. Already partially flagged by the security analyzer.\nRule id: fq.dangerous-html"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-e6a01f543e3ec999", "name": "`dangerouslySetInnerHTML` used in a React component \u2014 backend/prisma/legacy-content/demoTestCatalog.ts:366", "shortDescription": {"text": "`dangerouslySetInnerHTML` used in a React component \u2014 backend/prisma/legacy-content/demoTestCatalog.ts:366"}, "fullDescription": {"text": "Open XSS surface unless the input is provably trusted. Replace with explicit JSX or sanitize via a vetted library.\n\nWhy: OWASP basics. Already partially flagged by the security analyzer.\nRule id: fq.dangerous-html"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-285f05297332f0c4", "name": "Stray `console.log` in TS/JS \u2014 backend/prisma/legacy-content/testCatalog.ts:881", "shortDescription": {"text": "Stray `console.log` in TS/JS \u2014 backend/prisma/legacy-content/testCatalog.ts:881"}, "fullDescription": {"text": "Replace with the toast helper, an error boundary, or remove. `console.warn` / `console.error` are acceptable.\n\nWhy: Hygiene \u2014 easy to leak debug output.\nRule id: fq.console-leak"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "low", "confidence": 1.0}}, {"id": "scanner-35e8fd3dd78a9b9c", "name": "Stray `console.log` in TS/JS \u2014 backend/src/main.ts:99", "shortDescription": {"text": "Stray `console.log` in TS/JS \u2014 backend/src/main.ts:99"}, "fullDescription": {"text": "Replace with the toast helper, an error boundary, or remove. `console.warn` / `console.error` are acceptable.\n\nWhy: Hygiene \u2014 easy to leak debug output.\nRule id: fq.console-leak"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "low", "confidence": 1.0}}, {"id": "scanner-60427b03771411b6", "name": "Dockerfile runs as root: frontend/Dockerfile", "shortDescription": {"text": "Dockerfile runs as root: frontend/Dockerfile"}, "fullDescription": {"text": "No non-root USER set. Containers running as root expand the blast radius of any vulnerability inside the image."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-faa134129e5545ff", "name": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine", "shortDescription": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "fullDescription": {"text": "Container tags can be retagged upstream. Pin production base images to a reviewed digest (`image@sha256:...`) when reproducibility and supply-chain integrity matter."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "low", "confidence": 1.0}}, {"id": "scanner-b048d166901fd868", "name": "Docker base image is tag-pinned but not digest-pinned: nginx:alpine", "shortDescription": {"text": "Docker base image is tag-pinned but not digest-pinned: nginx:alpine"}, "fullDescription": {"text": "Container tags can be retagged upstream. Pin production base images to a reviewed digest (`image@sha256:...`) when reproducibility and supply-chain integrity matter."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "low", "confidence": 1.0}}, {"id": "scanner-3d2a6283f9ebab24", "name": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine", "shortDescription": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "fullDescription": {"text": "Container tags can be retagged upstream. Pin production base images to a reviewed digest (`image@sha256:...`) when reproducibility and supply-chain integrity matter."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "low", "confidence": 1.0}}, {"id": "scanner-aa5acaa49eb8315b", "name": "Containers defined but no K8s/orchestration manifest found", "shortDescription": {"text": "Containers defined but no K8s/orchestration manifest found"}, "fullDescription": {"text": "Repo has Dockerfiles/compose but no Kubernetes/Nomad manifests. If the target deployment is K8s, the manifests may live in a separate ops repo."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "low", "confidence": 1.0}}, {"id": "scanner-e544c3fae31abda9", "name": "Insecure pattern 'new_function_used' in frontend/src/features/live-coding/codeRunner.ts:94", "shortDescription": {"text": "Insecure pattern 'new_function_used' in frontend/src/features/live-coding/codeRunner.ts:94"}, "fullDescription": {"text": "Found a known-risky pattern (new_function_used). Review and replace if possible."}, "properties": {"scanner": "scanner-primary", "layer": "security", "severity": "high", "confidence": 1.0}}, {"id": "scanner-4b2ad35895d18ee3", "name": "Possible secret in backend/test/app.e2e-spec.ts", "shortDescription": {"text": "Possible secret in backend/test/app.e2e-spec.ts"}, "fullDescription": {"text": "Detected pattern matching password_literal. Rotate the credential and move to a secret manager."}, "properties": {"scanner": "scanner-primary", "layer": "security", "severity": "critical", "confidence": 1.0}}, {"id": "scanner-d3d43d6c9272b6e6", "name": "Insecure pattern 'new_function_used' in backend/prisma/verify-live-coding.ts:861", "shortDescription": {"text": "Insecure pattern 'new_function_used' in backend/prisma/verify-live-coding.ts:861"}, "fullDescription": {"text": "Found a known-risky pattern (new_function_used). Review and replace if possible."}, "properties": {"scanner": "scanner-primary", "layer": "security", "severity": "high", "confidence": 1.0}}, {"id": "scanner-caf7b7525235fefb", "name": "Insecure pattern 'direct_innerhtml_assignment' in backend/prisma/seed.ts:2824", "shortDescription": {"text": "Insecure pattern 'direct_innerhtml_assignment' in backend/prisma/seed.ts:2824"}, "fullDescription": {"text": "Found a known-risky pattern (direct_innerhtml_assignment). Review and replace if possible."}, "properties": {"scanner": "scanner-primary", "layer": "security", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-bd20bfbc34142b9d", "name": "Insecure pattern 'weak_hash' in backend/prisma/seed.ts:2786", "shortDescription": {"text": "Insecure pattern 'weak_hash' in backend/prisma/seed.ts:2786"}, "fullDescription": {"text": "Found a known-risky pattern (weak_hash). Review and replace if possible."}, "properties": {"scanner": "scanner-primary", "layer": "security", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-59214023c9342b87", "name": "Insecure pattern 'dangerous_innerhtml' in backend/prisma/legacy-content/demoTestCatalog.ts:366", "shortDescription": {"text": "Insecure pattern 'dangerous_innerhtml' in backend/prisma/legacy-content/demoTestCatalog.ts:366"}, "fullDescription": {"text": "Found a known-risky pattern (dangerous_innerhtml). Review and replace if possible."}, "properties": {"scanner": "scanner-primary", "layer": "security", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-4601e3ad3bb28677", "name": "No CI/CD pipelines detected", "shortDescription": {"text": "No CI/CD pipelines detected"}, "fullDescription": {"text": "No GitHub Actions, GitLab CI, or CircleCI configs found. Without CI you can't gate deploys on tests/lints."}, "properties": {"scanner": "scanner-primary", "layer": "cicd", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-cf4b689d24cf00b0", "name": "Very large file: backend/prisma/seed.ts (4396 lines)", "shortDescription": {"text": "Very large file: backend/prisma/seed.ts (4396 lines)"}, "fullDescription": {"text": "Files with >800 lines often hide complexity hotspots and discourage tests."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-aec659eb21bb2b07", "name": "Very large file: backend/prisma/legacy-content/demoTestCatalog.ts (2978 lines)", "shortDescription": {"text": "Very large file: backend/prisma/legacy-content/demoTestCatalog.ts (2978 lines)"}, "fullDescription": {"text": "Files with >800 lines often hide complexity hotspots and discourage tests."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-5596024e7a27bfe2", "name": "Very large file: backend/prisma/legacy-content/liveCoding.ts (7961 lines)", "shortDescription": {"text": "Very large file: backend/prisma/legacy-content/liveCoding.ts (7961 lines)"}, "fullDescription": {"text": "Files with >800 lines often hide complexity hotspots and discourage tests."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-5f148ea1e6ee8b1a", "name": "Very large file: backend/prisma/legacy-content/extraQuestions.ts (802 lines)", "shortDescription": {"text": "Very large file: backend/prisma/legacy-content/extraQuestions.ts (802 lines)"}, "fullDescription": {"text": "Files with >800 lines often hide complexity hotspots and discourage tests."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ab6905810e5269eb", "name": "Very large file: backend/prisma/legacy-content/testCatalog.ts (2395 lines)", "shortDescription": {"text": "Very large file: backend/prisma/legacy-content/testCatalog.ts (2395 lines)"}, "fullDescription": {"text": "Files with >800 lines often hide complexity hotspots and discourage tests."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-f01e4517e61c3fdc", "name": "Very large file: backend/prisma/legacy-content/roadmap.ts (1633 lines)", "shortDescription": {"text": "Very large file: backend/prisma/legacy-content/roadmap.ts (1633 lines)"}, "fullDescription": {"text": "Files with >800 lines often hide complexity hotspots and discourage tests."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ea3b5e389d8c9c0f", "name": "Low test-to-source ratio", "shortDescription": {"text": "Low test-to-source ratio"}, "fullDescription": {"text": "29 tests / 170 src (ratio 0.17)."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-141b30a41e03817b", "name": "No license file detected", "shortDescription": {"text": "No license file detected"}, "fullDescription": {"text": "No LICENSE/COPYING/NOTICE file was found. Generated repositories often omit licensing, which blocks reuse and automated intake."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-3ab5d313dda8e5f9", "name": "Debug logging residue appears in source files", "shortDescription": {"text": "Debug logging residue appears in source files"}, "fullDescription": {"text": "Found 68 console/debugger/print-style debug statements in non-test source. This is a common fast-generation residue before production cleanup."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-72b2a6250083a784", "name": "Placeholder or mock-heavy implementation detected", "shortDescription": {"text": "Placeholder or mock-heavy implementation detected"}, "fullDescription": {"text": "Found 31 placeholder/mock markers across 16 source files. This often means the repo looks complete while core flows still use generated scaffolding or fake data."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-2d0c7b7ab8f8aacf", "name": "Critical user flow still appears backed by mock or placeholder data", "shortDescription": {"text": "Critical user flow still appears backed by mock or placeholder data"}, "fullDescription": {"text": "A payment/auth/admin/order/billing-style flow contains mock, fake, TODO, dummy, or placeholder markers in runtime source. In the Fable corpus this is a high-leverage completeness smell: the app can look finished while the money, identity, or tenant flow is still scaffolded."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-b9088664ace7f748", "name": "Composite production-readiness gap", "shortDescription": {"text": "Composite production-readiness gap"}, "fullDescription": {"text": "Multiple low-cost hardening controls are missing together: license, ci. Opus verification showed these co-occurring gaps are a better readiness signal than reading each flag in isolation."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ea8f3013f588db25", "name": "Shallow git history limits provenance confidence", "shortDescription": {"text": "Shallow git history limits provenance confidence"}, "fullDescription": {"text": "The repository is a shallow clone. Origin/evolution analysis cannot distinguish fresh generation, imported legacy code, or long-lived human code with high confidence."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-8424db9c75e04ba4", "name": "Very short observed git history", "shortDescription": {"text": "Very short observed git history"}, "fullDescription": {"text": "The repo has multiple source files but two or fewer visible commits. This is not a failure by itself, but it lowers confidence in evolution-based diagnosis."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "info", "confidence": 1.0}}, {"id": "scanner-1baed767c661bccd", "name": "`fetch()` without try/.catch or AbortSignal \u2014 frontend/scripts/check-roadmap-links.mjs:43", "shortDescription": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 frontend/scripts/check-roadmap-links.mjs:43"}, "fullDescription": {"text": "Bare `fetch(...)` will throw an unhandled rejection on network failure. Wrap in try/catch, attach a `.catch(...)`, or pass an AbortSignal with a timeout."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-c7820c06a88b41f5", "name": "Commented-code block (6 lines) in backend/prisma/seed.ts:2873", "shortDescription": {"text": "Commented-code block (6 lines) in backend/prisma/seed.ts:2873"}, "fullDescription": {"text": "A long run of `//` or `#` lines usually means abandoned code. Delete or move to git history. Keeps the canvas + dead-code detection honest."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "info", "confidence": 1.0}}, {"id": "scanner-8019b1e6e2b757f5", "name": "`fetch()` without try/.catch or AbortSignal \u2014 backend/prisma/seed.ts:1052", "shortDescription": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/prisma/seed.ts:1052"}, "fullDescription": {"text": "Bare `fetch(...)` will throw an unhandled rejection on network failure. Wrap in try/catch, attach a `.catch(...)`, or pass an AbortSignal with a timeout."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-e8e95c9994956e10", "name": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/mail/mail.service.ts:134", "shortDescription": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/mail/mail.service.ts:134"}, "fullDescription": {"text": "Bare `fetch(...)` will throw an unhandled rejection on network failure. Wrap in try/catch, attach a `.catch(...)`, or pass an AbortSignal with a timeout."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-af022f937dd5b7fd", "name": "9 env vars used in code but missing from .env.example", "shortDescription": {"text": "9 env vars used in code but missing from .env.example"}, "fullDescription": {"text": "Drift between code and config docs. The first few: `BREVO_API_KEY`, `DEV`, `JWT_SECRET`, `RESEND_API_KEY`, `SMTP_HOST`, `SMTP_PASS`, `SMTP_PORT`, `SMTP_SECURE` + 1 more. Add them (with a placeholder/comment) to .env.example so onboarding doesn't break."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-0907d742dac0aa27", "name": "Dangling fetch: POST https://api.resend.com/emails (backend/src/mail/mail.service.ts:110)", "shortDescription": {"text": "Dangling fetch: POST https://api.resend.com/emails (backend/src/mail/mail.service.ts:110)"}, "fullDescription": {"text": "`backend/src/mail/mail.service.ts:110` calls `POST https://api.resend.com/emails` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/https:/api.resend.com/emails`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-52e841c17a839b34", "name": "Dangling fetch: POST https://api.brevo.com/v3/smtp/email (backend/src/mail/mail.service.ts:134)", "shortDescription": {"text": "Dangling fetch: POST https://api.brevo.com/v3/smtp/email (backend/src/mail/mail.service.ts:134)"}, "fullDescription": {"text": "`backend/src/mail/mail.service.ts:134` calls `POST https://api.brevo.com/v3/smtp/email` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/https:/api.brevo.com/v3/smtp/email`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-a78cb639d353f831", "name": "Unused endpoint: GET /api/users/:id/data", "shortDescription": {"text": "Unused endpoint: GET /api/users/:id/data"}, "fullDescription": {"text": "`backend/prisma/seed.ts` declares `GET /api/users/:id/data` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-8fdbacfe9430a6ed", "name": "Unused endpoint: POST /auth/login", "shortDescription": {"text": "Unused endpoint: POST /auth/login"}, "fullDescription": {"text": "`backend/prisma/seed.ts` declares `POST /auth/login` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-8292c0931391749a", "name": "Unused endpoint: POST /auth/register", "shortDescription": {"text": "Unused endpoint: POST /auth/register"}, "fullDescription": {"text": "`backend/src/auth/auth.controller.ts` declares `POST /auth/register` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-6ff747bc814618ba", "name": "Unused endpoint: POST /auth/register/profile", "shortDescription": {"text": "Unused endpoint: POST /auth/register/profile"}, "fullDescription": {"text": "`backend/src/auth/auth.controller.ts` declares `POST /auth/register/profile` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-c059f57186114027", "name": "Unused endpoint: POST /auth/logout", "shortDescription": {"text": "Unused endpoint: POST /auth/logout"}, "fullDescription": {"text": "`backend/src/auth/auth.controller.ts` declares `POST /auth/logout` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-b36892106ecdc9aa", "name": "Unused endpoint: POST /auth/refresh", "shortDescription": {"text": "Unused endpoint: POST /auth/refresh"}, "fullDescription": {"text": "`backend/src/auth/auth.controller.ts` declares `POST /auth/refresh` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ccadf58fea366726", "name": "Unused endpoint: POST /auth/verify-email", "shortDescription": {"text": "Unused endpoint: POST /auth/verify-email"}, "fullDescription": {"text": "`backend/src/auth/auth.controller.ts` declares `POST /auth/verify-email` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ff5660488b9ce2ca", "name": "Unused endpoint: POST /auth/resend-verification", "shortDescription": {"text": "Unused endpoint: POST /auth/resend-verification"}, "fullDescription": {"text": "`backend/src/auth/auth.controller.ts` declares `POST /auth/resend-verification` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-e2fe5b92648ca462", "name": "Unused endpoint: POST /auth/forgot-password", "shortDescription": {"text": "Unused endpoint: POST /auth/forgot-password"}, "fullDescription": {"text": "`backend/src/auth/auth.controller.ts` declares `POST /auth/forgot-password` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-deb8df763a50c73a", "name": "Unused endpoint: POST /auth/reset-password", "shortDescription": {"text": "Unused endpoint: POST /auth/reset-password"}, "fullDescription": {"text": "`backend/src/auth/auth.controller.ts` declares `POST /auth/reset-password` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ac42422b23e45104", "name": "Unused endpoint: GET /auth/me", "shortDescription": {"text": "Unused endpoint: GET /auth/me"}, "fullDescription": {"text": "`backend/src/auth/auth.controller.ts` declares `GET /auth/me` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-76b9497ce230bcde", "name": "Unused endpoint: GET /chat/sessions", "shortDescription": {"text": "Unused endpoint: GET /chat/sessions"}, "fullDescription": {"text": "`backend/src/chat/chat.controller.ts` declares `GET /chat/sessions` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-724538062502abfe", "name": "Unused endpoint: POST /chat/sessions", "shortDescription": {"text": "Unused endpoint: POST /chat/sessions"}, "fullDescription": {"text": "`backend/src/chat/chat.controller.ts` declares `POST /chat/sessions` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-3ffdac0d81c972e0", "name": "Unused endpoint: DELETE /chat/sessions", "shortDescription": {"text": "Unused endpoint: DELETE /chat/sessions"}, "fullDescription": {"text": "`backend/src/chat/chat.controller.ts` declares `DELETE /chat/sessions` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-901b5058fbd96618", "name": "Unused endpoint: GET /chat/sessions/:id", "shortDescription": {"text": "Unused endpoint: GET /chat/sessions/:id"}, "fullDescription": {"text": "`backend/src/chat/chat.controller.ts` declares `GET /chat/sessions/:id` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-1f61bbb9fd24f311", "name": "Unused endpoint: DELETE /chat/sessions/:id", "shortDescription": {"text": "Unused endpoint: DELETE /chat/sessions/:id"}, "fullDescription": {"text": "`backend/src/chat/chat.controller.ts` declares `DELETE /chat/sessions/:id` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-291976e13f119cc2", "name": "Unused endpoint: POST /chat/sessions/:id/messages", "shortDescription": {"text": "Unused endpoint: POST /chat/sessions/:id/messages"}, "fullDescription": {"text": "`backend/src/chat/chat.controller.ts` declares `POST /chat/sessions/:id/messages` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-b21d76267cbcc2c9", "name": "Unused endpoint: GET /progress/:namespace", "shortDescription": {"text": "Unused endpoint: GET /progress/:namespace"}, "fullDescription": {"text": "`backend/src/progress/progress.controller.ts` declares `GET /progress/:namespace` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-7701e796614ef49f", "name": "Unused endpoint: PUT /progress/:namespace", "shortDescription": {"text": "Unused endpoint: PUT /progress/:namespace"}, "fullDescription": {"text": "`backend/src/progress/progress.controller.ts` declares `PUT /progress/:namespace` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-630e6c3a8eb0db55", "name": "Unused endpoint: GET /roadmaps/:slug", "shortDescription": {"text": "Unused endpoint: GET /roadmaps/:slug"}, "fullDescription": {"text": "`backend/src/content/content.controller.ts` declares `GET /roadmaps/:slug` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-2ae4f3df7e37438c", "name": "Unused endpoint: GET /roadmaps/themes", "shortDescription": {"text": "Unused endpoint: GET /roadmaps/themes"}, "fullDescription": {"text": "`backend/src/content/content.controller.ts` declares `GET /roadmaps/themes` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-bc81b42707846f8b", "name": "Unused endpoint: GET /roadmaps/themes/:slug", "shortDescription": {"text": "Unused endpoint: GET /roadmaps/themes/:slug"}, "fullDescription": {"text": "`backend/src/content/content.controller.ts` declares `GET /roadmaps/themes/:slug` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-13284de4ef8c3502", "name": "Unused endpoint: GET /roadmaps/entries", "shortDescription": {"text": "Unused endpoint: GET /roadmaps/entries"}, "fullDescription": {"text": "`backend/src/content/content.controller.ts` declares `GET /roadmaps/entries` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-10a358803741915e", "name": "Unused endpoint: GET /roadmaps/entries/:id", "shortDescription": {"text": "Unused endpoint: GET /roadmaps/entries/:id"}, "fullDescription": {"text": "`backend/src/content/content.controller.ts` declares `GET /roadmaps/entries/:id` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-d8a84bd53d057920", "name": "Unused endpoint: POST /roadmaps/entries", "shortDescription": {"text": "Unused endpoint: POST /roadmaps/entries"}, "fullDescription": {"text": "`backend/src/content/content.controller.ts` declares `POST /roadmaps/entries` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-8d36c4a34021f4d3", "name": "Unused endpoint: PATCH /roadmaps/entries/:id", "shortDescription": {"text": "Unused endpoint: PATCH /roadmaps/entries/:id"}, "fullDescription": {"text": "`backend/src/content/content.controller.ts` declares `PATCH /roadmaps/entries/:id` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-fcb461952fd0448b", "name": "Unused endpoint: DELETE /roadmaps/entries/:id", "shortDescription": {"text": "Unused endpoint: DELETE /roadmaps/entries/:id"}, "fullDescription": {"text": "`backend/src/content/content.controller.ts` declares `DELETE /roadmaps/entries/:id` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-80e43fd38ede4d40", "name": "Unused endpoint: POST /profile/avatar", "shortDescription": {"text": "Unused endpoint: POST /profile/avatar"}, "fullDescription": {"text": "`backend/src/profile/profile.controller.ts` declares `POST /profile/avatar` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-b6a2e6012c0d3c34", "name": "Unused endpoint: GET /profile/test-history", "shortDescription": {"text": "Unused endpoint: GET /profile/test-history"}, "fullDescription": {"text": "`backend/src/profile/profile.controller.ts` declares `GET /profile/test-history` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ea41d100aaff82e6", "name": "Unused endpoint: GET /materials/admin", "shortDescription": {"text": "Unused endpoint: GET /materials/admin"}, "fullDescription": {"text": "`backend/src/materials/materials.controller.ts` declares `GET /materials/admin` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-86d95fd02d0753ae", "name": "Unused endpoint: GET /materials/admin/:id", "shortDescription": {"text": "Unused endpoint: GET /materials/admin/:id"}, "fullDescription": {"text": "`backend/src/materials/materials.controller.ts` declares `GET /materials/admin/:id` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-dc3a0b8a291436a7", "name": "Unused endpoint: PATCH /materials/:id", "shortDescription": {"text": "Unused endpoint: PATCH /materials/:id"}, "fullDescription": {"text": "`backend/src/materials/materials.controller.ts` declares `PATCH /materials/:id` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-5aa20fd0df915ef3", "name": "Unused endpoint: DELETE /materials/:id", "shortDescription": {"text": "Unused endpoint: DELETE /materials/:id"}, "fullDescription": {"text": "`backend/src/materials/materials.controller.ts` declares `DELETE /materials/:id` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-9eefcd47f26f83e9", "name": "Unused endpoint: GET /materials/favorites", "shortDescription": {"text": "Unused endpoint: GET /materials/favorites"}, "fullDescription": {"text": "`backend/src/materials/materials.controller.ts` declares `GET /materials/favorites` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-320b165f64a9c134", "name": "Unused endpoint: GET /materials/:id", "shortDescription": {"text": "Unused endpoint: GET /materials/:id"}, "fullDescription": {"text": "`backend/src/materials/materials.controller.ts` declares `GET /materials/:id` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-be81d57d456af62a", "name": "Unused endpoint: POST /materials/:id/favorite", "shortDescription": {"text": "Unused endpoint: POST /materials/:id/favorite"}, "fullDescription": {"text": "`backend/src/materials/materials.controller.ts` declares `POST /materials/:id/favorite` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-3664e846df935a85", "name": "Unused endpoint: DELETE /materials/:id/favorite", "shortDescription": {"text": "Unused endpoint: DELETE /materials/:id/favorite"}, "fullDescription": {"text": "`backend/src/materials/materials.controller.ts` declares `DELETE /materials/:id/favorite` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}]}}, "automationDetails": {"id": "repobility/22372"}, "properties": {"repository": "vladis-sh/VKR", "repoUrl": "https://github.com/vladis-sh/VKR", "branch": "main"}, "results": [{"ruleId": "scanner-09c004c9dc7d3e35", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/widgets/PracticeStats.tsx:245"}, "properties": {"repobilityId": "c6b5421b365ebb7c", "scanner": "scanner-primary", "fingerprint": "09c004c9dc7d3e35", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-47add4e345df024f", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/widgets/ChatSessionList.tsx:93"}, "properties": {"repobilityId": "2698fe01abd263cc", "scanner": "scanner-primary", "fingerprint": "47add4e345df024f", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-6c163d3a85951ef1", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/widgets/Header.tsx:59"}, "properties": {"repobilityId": "8131c4eb21840bae", "scanner": "scanner-primary", "fingerprint": "6c163d3a85951ef1", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-904a35e5995a8a96", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/tests/TopicsPage.tsx:77"}, "properties": {"repobilityId": "a7503c2b34a6009c", "scanner": "scanner-primary", "fingerprint": "904a35e5995a8a96", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-1918ad65a2b86521", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/tests/ThemesCatalogPage.tsx:64"}, "properties": {"repobilityId": "62b65892a676d59e", "scanner": "scanner-primary", "fingerprint": "1918ad65a2b86521", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-9032be7ff819c5be", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/tests/TestTopicSelectPage.tsx:115"}, "properties": {"repobilityId": "f1e4179fa986de39", "scanner": "scanner-primary", "fingerprint": "9032be7ff819c5be", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-239a8370cf8e87bd", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/chat/ChatPage.tsx:284"}, "properties": {"repobilityId": "3d22ba904f8407de", "scanner": "scanner-primary", "fingerprint": "239a8370cf8e87bd", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-2490dcd3c37e7b63", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/roadmaps/RoadmapDetailPage.tsx:112"}, "properties": {"repobilityId": "ea85ff325f694dd7", "scanner": "scanner-primary", "fingerprint": "2490dcd3c37e7b63", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-7a0d8645df8fd079", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/profile/ProfileHistoryPage.tsx:120"}, "properties": {"repobilityId": "923cd058c760e31d", "scanner": "scanner-primary", "fingerprint": "7a0d8645df8fd079", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-09929155114e2443", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/live-coding/LiveCodingPage.tsx:90"}, "properties": {"repobilityId": "8e99c4e1ca435f6a", "scanner": "scanner-primary", "fingerprint": "09929155114e2443", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-341b9f2545daa80f", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/live-coding/LiveCodingTaskPage.tsx:654"}, "properties": {"repobilityId": "069462256a09a924", "scanner": "scanner-primary", "fingerprint": "341b9f2545daa80f", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-3a52423ac96c1a77", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/pages/stats/StatsPage.tsx:532"}, "properties": {"repobilityId": "782e98706a86486b", "scanner": "scanner-primary", "fingerprint": "3a52423ac96c1a77", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-c258f0c26b6d0275", "level": "note", "message": {"text": "Stray `console.log` in TS/JS \u2014 backend/prisma/verify-live-coding.ts:845"}, "properties": {"repobilityId": "095539c011738fd1", "scanner": "scanner-primary", "fingerprint": "c258f0c26b6d0275", "layer": "frontend", "severity": "low", "confidence": 1.0, "tags": ["frontend-quality", "fq.console-leak"]}}, {"ruleId": "scanner-64bb990b5dbcac67", "level": "note", "message": {"text": "Stray `console.log` in TS/JS \u2014 backend/prisma/refresh-live-coding.ts:33"}, "properties": {"repobilityId": "ed0350ba018cf43a", "scanner": "scanner-primary", "fingerprint": "64bb990b5dbcac67", "layer": "frontend", "severity": "low", "confidence": 1.0, "tags": ["frontend-quality", "fq.console-leak"]}}, {"ruleId": "scanner-f6e597023f09ea9f", "level": "note", "message": {"text": "Icon-only button without accessible name \u2014 backend/prisma/seed.ts:1013"}, "properties": {"repobilityId": "6f14f3cd82fa1b12", "scanner": "scanner-primary", "fingerprint": "f6e597023f09ea9f", "layer": "frontend", "severity": "low", "confidence": 1.0, "tags": ["frontend-quality", "fq.button.no-label"]}}, {"ruleId": "scanner-68dc24e8847ff21e", "level": "note", "message": {"text": "Stray `console.log` in TS/JS \u2014 backend/prisma/seed.ts:49"}, "properties": {"repobilityId": "be513322403f615b", "scanner": "scanner-primary", "fingerprint": "68dc24e8847ff21e", "layer": "frontend", "severity": "low", "confidence": 1.0, "tags": ["frontend-quality", "fq.console-leak"]}}, {"ruleId": "scanner-89786bdb3bb3d355", "level": "warning", "message": {"text": "`dangerouslySetInnerHTML` used in a React component \u2014 backend/prisma/seed.ts:2832"}, "properties": {"repobilityId": "4c7a41bdd1707a85", "scanner": "scanner-primary", "fingerprint": "89786bdb3bb3d355", "layer": "frontend", "severity": "medium", "confidence": 1.0, "tags": ["frontend-quality", "fq.dangerous-html"]}}, {"ruleId": "scanner-e6a01f543e3ec999", "level": "warning", "message": {"text": "`dangerouslySetInnerHTML` used in a React component \u2014 backend/prisma/legacy-content/demoTestCatalog.ts:366"}, "properties": {"repobilityId": "18b8523d45f3a00f", "scanner": "scanner-primary", "fingerprint": "e6a01f543e3ec999", "layer": "frontend", "severity": "medium", "confidence": 1.0, "tags": ["frontend-quality", "fq.dangerous-html"]}}, {"ruleId": "scanner-285f05297332f0c4", "level": "note", "message": {"text": "Stray `console.log` in TS/JS \u2014 backend/prisma/legacy-content/testCatalog.ts:881"}, "properties": {"repobilityId": "5ed58f243485d1d3", "scanner": "scanner-primary", "fingerprint": "285f05297332f0c4", "layer": "frontend", "severity": "low", "confidence": 1.0, "tags": ["frontend-quality", "fq.console-leak"]}}, {"ruleId": "scanner-35e8fd3dd78a9b9c", "level": "note", "message": {"text": "Stray `console.log` in TS/JS \u2014 backend/src/main.ts:99"}, "properties": {"repobilityId": "b6b8837ef125dd71", "scanner": "scanner-primary", "fingerprint": "35e8fd3dd78a9b9c", "layer": "frontend", "severity": "low", "confidence": 1.0, "tags": ["frontend-quality", "fq.console-leak"]}}, {"ruleId": "scanner-60427b03771411b6", "level": "warning", "message": {"text": "Dockerfile runs as root: frontend/Dockerfile"}, "properties": {"repobilityId": "735c01d8531dfd2c", "scanner": "scanner-primary", "fingerprint": "60427b03771411b6", "layer": "hardware", "severity": "medium", "confidence": 1.0, "tags": ["security", "container"]}}, {"ruleId": "scanner-faa134129e5545ff", "level": "note", "message": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "properties": {"repobilityId": "3e90d440e1f9ece1", "scanner": "scanner-primary", "fingerprint": "faa134129e5545ff", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["supply-chain", "docker", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "frontend/Dockerfile"}, "region": {"startLine": 1}}}]}, {"ruleId": "scanner-b048d166901fd868", "level": "note", "message": {"text": "Docker base image is tag-pinned but not digest-pinned: nginx:alpine"}, "properties": {"repobilityId": "852098b6e3d4c87a", "scanner": "scanner-primary", "fingerprint": "b048d166901fd868", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["supply-chain", "docker", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "frontend/Dockerfile"}, "region": {"startLine": 10}}}]}, {"ruleId": "scanner-3d2a6283f9ebab24", "level": "note", "message": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "properties": {"repobilityId": "e866ff9772e76d62", "scanner": "scanner-primary", "fingerprint": "3d2a6283f9ebab24", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["supply-chain", "docker", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/Dockerfile"}, "region": {"startLine": 1}}}]}, {"ruleId": "scanner-3d2a6283f9ebab24", "level": "note", "message": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "properties": {"repobilityId": "8becb6834d4e36ea", "scanner": "scanner-primary", "fingerprint": "3d2a6283f9ebab24", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["supply-chain", "docker", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/Dockerfile"}, "region": {"startLine": 17}}}]}, {"ruleId": "scanner-aa5acaa49eb8315b", "level": "note", "message": {"text": "Containers defined but no K8s/orchestration manifest found"}, "properties": {"repobilityId": "b230ea9b68736081", "scanner": "scanner-primary", "fingerprint": "aa5acaa49eb8315b", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["coverage", "deployment"]}}, {"ruleId": "scanner-e544c3fae31abda9", "level": "error", "message": {"text": "Insecure pattern 'new_function_used' in frontend/src/features/live-coding/codeRunner.ts:94"}, "properties": {"repobilityId": "c55301d7d9a87540", "scanner": "scanner-primary", "fingerprint": "e544c3fae31abda9", "layer": "security", "severity": "high", "confidence": 1.0, "tags": ["owasp", "new_function_used"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "frontend/src/features/live-coding/codeRunner.ts"}, "region": {"startLine": 94}}}]}, {"ruleId": "scanner-4b2ad35895d18ee3", "level": "error", "message": {"text": "Possible secret in backend/test/app.e2e-spec.ts"}, "properties": {"repobilityId": "f79876a275c30299", "scanner": "scanner-primary", "fingerprint": "4b2ad35895d18ee3", "layer": "security", "severity": "critical", "confidence": 1.0, "tags": ["secrets"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/test/app.e2e-spec.ts"}, "region": {"startLine": 47}}}]}, {"ruleId": "scanner-4b2ad35895d18ee3", "level": "error", "message": {"text": "Possible secret in backend/test/app.e2e-spec.ts"}, "properties": {"repobilityId": "f79876a275c30299", "scanner": "scanner-primary", "fingerprint": "4b2ad35895d18ee3", "layer": "security", "severity": "critical", "confidence": 1.0, "tags": ["secrets"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/test/app.e2e-spec.ts"}, "region": {"startLine": 69}}}]}, {"ruleId": "scanner-4b2ad35895d18ee3", "level": "error", "message": {"text": "Possible secret in backend/test/app.e2e-spec.ts"}, "properties": {"repobilityId": "f79876a275c30299", "scanner": "scanner-primary", "fingerprint": "4b2ad35895d18ee3", "layer": "security", "severity": "critical", "confidence": 1.0, "tags": ["secrets"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/test/app.e2e-spec.ts"}, "region": {"startLine": 127}}}]}, {"ruleId": "scanner-d3d43d6c9272b6e6", "level": "error", "message": {"text": "Insecure pattern 'new_function_used' in backend/prisma/verify-live-coding.ts:861"}, "properties": {"repobilityId": "4c4332d276a88f42", "scanner": "scanner-primary", "fingerprint": "d3d43d6c9272b6e6", "layer": "security", "severity": "high", "confidence": 1.0, "tags": ["owasp", "new_function_used"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/prisma/verify-live-coding.ts"}, "region": {"startLine": 861}}}]}, {"ruleId": "scanner-caf7b7525235fefb", "level": "warning", "message": {"text": "Insecure pattern 'direct_innerhtml_assignment' in backend/prisma/seed.ts:2824"}, "properties": {"repobilityId": "50c528c0c5ea271e", "scanner": "scanner-primary", "fingerprint": "caf7b7525235fefb", "layer": "security", "severity": "medium", "confidence": 1.0, "tags": ["owasp", "direct_innerhtml_assignment"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/prisma/seed.ts"}, "region": {"startLine": 2824}}}]}, {"ruleId": "scanner-bd20bfbc34142b9d", "level": "warning", "message": {"text": "Insecure pattern 'weak_hash' in backend/prisma/seed.ts:2786"}, "properties": {"repobilityId": "55c3fdcb23c6d9fc", "scanner": "scanner-primary", "fingerprint": "bd20bfbc34142b9d", "layer": "security", "severity": "medium", "confidence": 1.0, "tags": ["owasp", "weak_hash"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/prisma/seed.ts"}, "region": {"startLine": 2786}}}]}, {"ruleId": "scanner-59214023c9342b87", "level": "warning", "message": {"text": "Insecure pattern 'dangerous_innerhtml' in backend/prisma/legacy-content/demoTestCatalog.ts:366"}, "properties": {"repobilityId": "ab58798e8be18439", "scanner": "scanner-primary", "fingerprint": "59214023c9342b87", "layer": "security", "severity": "medium", "confidence": 1.0, "tags": ["owasp", "dangerous_innerhtml"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/prisma/legacy-content/demoTestCatalog.ts"}, "region": {"startLine": 366}}}]}, {"ruleId": "scanner-4601e3ad3bb28677", "level": "warning", "message": {"text": "No CI/CD pipelines detected"}, "properties": {"repobilityId": "c3ee439bce2bc51e", "scanner": "scanner-primary", "fingerprint": "4601e3ad3bb28677", "layer": "cicd", "severity": "medium", "confidence": 1.0, "tags": ["coverage"]}}, {"ruleId": "scanner-cf4b689d24cf00b0", "level": "note", "message": {"text": "Very large file: backend/prisma/seed.ts (4396 lines)"}, "properties": {"repobilityId": "e87adbdd49b01ad1", "scanner": "scanner-primary", "fingerprint": "cf4b689d24cf00b0", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["complexity"]}}, {"ruleId": "scanner-aec659eb21bb2b07", "level": "note", "message": {"text": "Very large file: backend/prisma/legacy-content/demoTestCatalog.ts (2978 lines)"}, "properties": {"repobilityId": "7823ad4cbf99dd5b", "scanner": "scanner-primary", "fingerprint": "aec659eb21bb2b07", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["complexity"]}}, {"ruleId": "scanner-5596024e7a27bfe2", "level": "note", "message": {"text": "Very large file: backend/prisma/legacy-content/liveCoding.ts (7961 lines)"}, "properties": {"repobilityId": "82850a5729cd620a", "scanner": "scanner-primary", "fingerprint": "5596024e7a27bfe2", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["complexity"]}}, {"ruleId": "scanner-5f148ea1e6ee8b1a", "level": "note", "message": {"text": "Very large file: backend/prisma/legacy-content/extraQuestions.ts (802 lines)"}, "properties": {"repobilityId": "28d445b132026e8a", "scanner": "scanner-primary", "fingerprint": "5f148ea1e6ee8b1a", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["complexity"]}}, {"ruleId": "scanner-ab6905810e5269eb", "level": "note", "message": {"text": "Very large file: backend/prisma/legacy-content/testCatalog.ts (2395 lines)"}, "properties": {"repobilityId": "582a76ff97588e07", "scanner": "scanner-primary", "fingerprint": "ab6905810e5269eb", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["complexity"]}}, {"ruleId": "scanner-f01e4517e61c3fdc", "level": "note", "message": {"text": "Very large file: backend/prisma/legacy-content/roadmap.ts (1633 lines)"}, "properties": {"repobilityId": "782afef805120907", "scanner": "scanner-primary", "fingerprint": "f01e4517e61c3fdc", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["complexity"]}}, {"ruleId": "scanner-ea3b5e389d8c9c0f", "level": "note", "message": {"text": "Low test-to-source ratio"}, "properties": {"repobilityId": "ef7b2552cc00a375", "scanner": "scanner-primary", "fingerprint": "ea3b5e389d8c9c0f", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["tests"]}}, {"ruleId": "scanner-141b30a41e03817b", "level": "note", "message": {"text": "No license file detected"}, "properties": {"repobilityId": "88104915ad499972", "scanner": "scanner-primary", "fingerprint": "141b30a41e03817b", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["license", "repo-hardening", "generated-repo-pattern"]}}, {"ruleId": "scanner-3ab5d313dda8e5f9", "level": "note", "message": {"text": "Debug logging residue appears in source files"}, "properties": {"repobilityId": "c8aafc837270b873", "scanner": "scanner-primary", "fingerprint": "3ab5d313dda8e5f9", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["debug", "cleanup", "repo-hardening", "generated-repo-pattern"]}}, {"ruleId": "scanner-72b2a6250083a784", "level": "warning", "message": {"text": "Placeholder or mock-heavy implementation detected"}, "properties": {"repobilityId": "dd36d04ad608eb92", "scanner": "scanner-primary", "fingerprint": "72b2a6250083a784", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["placeholder", "mock-data", "incomplete", "generated-repo-pattern"]}}, {"ruleId": "scanner-2d0c7b7ab8f8aacf", "level": "warning", "message": {"text": "Critical user flow still appears backed by mock or placeholder data"}, "properties": {"repobilityId": "e10b99d0c70ecd3e", "scanner": "scanner-primary", "fingerprint": "2d0c7b7ab8f8aacf", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["placeholder", "mock-data", "critical-flow", "generated-repo-pattern"]}}, {"ruleId": "scanner-b9088664ace7f748", "level": "note", "message": {"text": "Composite production-readiness gap"}, "properties": {"repobilityId": "b8962160fc00421c", "scanner": "scanner-primary", "fingerprint": "b9088664ace7f748", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["production-readiness", "repo-hardening", "generated-repo-pattern"]}}, {"ruleId": "scanner-ea8f3013f588db25", "level": "note", "message": {"text": "Shallow git history limits provenance confidence"}, "properties": {"repobilityId": "78196448ca2b4d48", "scanner": "scanner-primary", "fingerprint": "ea8f3013f588db25", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["provenance", "git-history", "generated-repo-pattern"]}}, {"ruleId": "scanner-8424db9c75e04ba4", "level": "none", "message": {"text": "Very short observed git history"}, "properties": {"repobilityId": "f0aaeb7b44526c79", "scanner": "scanner-primary", "fingerprint": "8424db9c75e04ba4", "layer": "quality", "severity": "info", "confidence": 1.0, "tags": ["provenance", "git-history", "generated-repo-pattern"]}}, {"ruleId": "scanner-1baed767c661bccd", "level": "warning", "message": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 frontend/scripts/check-roadmap-links.mjs:43"}, "properties": {"repobilityId": "e1a7e56ddcdf87ce", "scanner": "scanner-primary", "fingerprint": "1baed767c661bccd", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "fragile-runtime", "robustness"]}}, {"ruleId": "scanner-c7820c06a88b41f5", "level": "none", "message": {"text": "Commented-code block (6 lines) in backend/prisma/seed.ts:2873"}, "properties": {"repobilityId": "18bed95822f26125", "scanner": "scanner-primary", "fingerprint": "c7820c06a88b41f5", "layer": "quality", "severity": "info", "confidence": 1.0, "tags": ["integrity", "commented-code", "dead-code"]}}, {"ruleId": "scanner-8019b1e6e2b757f5", "level": "warning", "message": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/prisma/seed.ts:1052"}, "properties": {"repobilityId": "4a125e9f1e5c41df", "scanner": "scanner-primary", "fingerprint": "8019b1e6e2b757f5", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "fragile-runtime", "robustness"]}}, {"ruleId": "scanner-e8e95c9994956e10", "level": "warning", "message": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/mail/mail.service.ts:134"}, "properties": {"repobilityId": "52e1b83fcff00078", "scanner": "scanner-primary", "fingerprint": "e8e95c9994956e10", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "fragile-runtime", "robustness"]}}, {"ruleId": "scanner-af022f937dd5b7fd", "level": "note", "message": {"text": "9 env vars used in code but missing from .env.example"}, "properties": {"repobilityId": "5597384795dea7a4", "scanner": "scanner-primary", "fingerprint": "af022f937dd5b7fd", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["integrity", "config-drift"]}}, {"ruleId": "scanner-0907d742dac0aa27", "level": "error", "message": {"text": "Dangling fetch: POST https://api.resend.com/emails (backend/src/mail/mail.service.ts:110)"}, "properties": {"repobilityId": "7e2192f54d74f1f4", "scanner": "scanner-primary", "fingerprint": "0907d742dac0aa27", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-52e841c17a839b34", "level": "error", "message": {"text": "Dangling fetch: POST https://api.brevo.com/v3/smtp/email (backend/src/mail/mail.service.ts:134)"}, "properties": {"repobilityId": "0fd587e69219abd9", "scanner": "scanner-primary", "fingerprint": "52e841c17a839b34", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-a78cb639d353f831", "level": "note", "message": {"text": "Unused endpoint: GET /api/users/:id/data"}, "properties": {"repobilityId": "1c8fbe8acfe47d7b", "scanner": "scanner-primary", "fingerprint": "a78cb639d353f831", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-8fdbacfe9430a6ed", "level": "note", "message": {"text": "Unused endpoint: POST /auth/login"}, "properties": {"repobilityId": "4caf3ec72adbb7fe", "scanner": "scanner-primary", "fingerprint": "8fdbacfe9430a6ed", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-8292c0931391749a", "level": "note", "message": {"text": "Unused endpoint: POST /auth/register"}, "properties": {"repobilityId": "d89f047d2e273d13", "scanner": "scanner-primary", "fingerprint": "8292c0931391749a", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-6ff747bc814618ba", "level": "note", "message": {"text": "Unused endpoint: POST /auth/register/profile"}, "properties": {"repobilityId": "5d235b5a948ef1a8", "scanner": "scanner-primary", "fingerprint": "6ff747bc814618ba", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-c059f57186114027", "level": "note", "message": {"text": "Unused endpoint: POST /auth/logout"}, "properties": {"repobilityId": "b934ec61e8e1c247", "scanner": "scanner-primary", "fingerprint": "c059f57186114027", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-b36892106ecdc9aa", "level": "note", "message": {"text": "Unused endpoint: POST /auth/refresh"}, "properties": {"repobilityId": "a0b243d33b738ce6", "scanner": "scanner-primary", "fingerprint": "b36892106ecdc9aa", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-ccadf58fea366726", "level": "note", "message": {"text": "Unused endpoint: POST /auth/verify-email"}, "properties": {"repobilityId": "3855028109a550c8", "scanner": "scanner-primary", "fingerprint": "ccadf58fea366726", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-ff5660488b9ce2ca", "level": "note", "message": {"text": "Unused endpoint: POST /auth/resend-verification"}, "properties": {"repobilityId": "46ce6993fdf9d558", "scanner": "scanner-primary", "fingerprint": "ff5660488b9ce2ca", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-e2fe5b92648ca462", "level": "note", "message": {"text": "Unused endpoint: POST /auth/forgot-password"}, "properties": {"repobilityId": "eef2a63e08ee5f47", "scanner": "scanner-primary", "fingerprint": "e2fe5b92648ca462", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-deb8df763a50c73a", "level": "note", "message": {"text": "Unused endpoint: POST /auth/reset-password"}, "properties": {"repobilityId": "0020c196abb9803f", "scanner": "scanner-primary", "fingerprint": "deb8df763a50c73a", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-ac42422b23e45104", "level": "note", "message": {"text": "Unused endpoint: GET /auth/me"}, "properties": {"repobilityId": "c154dc44924dda67", "scanner": "scanner-primary", "fingerprint": "ac42422b23e45104", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-76b9497ce230bcde", "level": "note", "message": {"text": "Unused endpoint: GET /chat/sessions"}, "properties": {"repobilityId": "acee21a9746c829c", "scanner": "scanner-primary", "fingerprint": "76b9497ce230bcde", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-724538062502abfe", "level": "note", "message": {"text": "Unused endpoint: POST /chat/sessions"}, "properties": {"repobilityId": "5ed25d04b0529096", "scanner": "scanner-primary", "fingerprint": "724538062502abfe", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-3ffdac0d81c972e0", "level": "note", "message": {"text": "Unused endpoint: DELETE /chat/sessions"}, "properties": {"repobilityId": "2087fb9a7e99ca60", "scanner": "scanner-primary", "fingerprint": "3ffdac0d81c972e0", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-901b5058fbd96618", "level": "note", "message": {"text": "Unused endpoint: GET /chat/sessions/:id"}, "properties": {"repobilityId": "3408398119377c59", "scanner": "scanner-primary", "fingerprint": "901b5058fbd96618", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-1f61bbb9fd24f311", "level": "note", "message": {"text": "Unused endpoint: DELETE /chat/sessions/:id"}, "properties": {"repobilityId": "677751a656b0164b", "scanner": "scanner-primary", "fingerprint": "1f61bbb9fd24f311", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-291976e13f119cc2", "level": "note", "message": {"text": "Unused endpoint: POST /chat/sessions/:id/messages"}, "properties": {"repobilityId": "f0a854930d67c51f", "scanner": "scanner-primary", "fingerprint": "291976e13f119cc2", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-b21d76267cbcc2c9", "level": "note", "message": {"text": "Unused endpoint: GET /progress/:namespace"}, "properties": {"repobilityId": "503907b4dae969a8", "scanner": "scanner-primary", "fingerprint": "b21d76267cbcc2c9", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-7701e796614ef49f", "level": "note", "message": {"text": "Unused endpoint: PUT /progress/:namespace"}, "properties": {"repobilityId": "f0126f40bdcb1847", "scanner": "scanner-primary", "fingerprint": "7701e796614ef49f", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-630e6c3a8eb0db55", "level": "note", "message": {"text": "Unused endpoint: GET /roadmaps/:slug"}, "properties": {"repobilityId": "19358b9f5bac7808", "scanner": "scanner-primary", "fingerprint": "630e6c3a8eb0db55", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-2ae4f3df7e37438c", "level": "note", "message": {"text": "Unused endpoint: GET /roadmaps/themes"}, "properties": {"repobilityId": "5ac8e6fe1b3d4cb8", "scanner": "scanner-primary", "fingerprint": "2ae4f3df7e37438c", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-bc81b42707846f8b", "level": "note", "message": {"text": "Unused endpoint: GET /roadmaps/themes/:slug"}, "properties": {"repobilityId": "2e1e29e6ce8d35ec", "scanner": "scanner-primary", "fingerprint": "bc81b42707846f8b", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-13284de4ef8c3502", "level": "note", "message": {"text": "Unused endpoint: GET /roadmaps/entries"}, "properties": {"repobilityId": "73cd81073ebfa22c", "scanner": "scanner-primary", "fingerprint": "13284de4ef8c3502", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-10a358803741915e", "level": "note", "message": {"text": "Unused endpoint: GET /roadmaps/entries/:id"}, "properties": {"repobilityId": "0ee98cc63d8a072a", "scanner": "scanner-primary", "fingerprint": "10a358803741915e", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-d8a84bd53d057920", "level": "note", "message": {"text": "Unused endpoint: POST /roadmaps/entries"}, "properties": {"repobilityId": "7d7a48ef2b0b6406", "scanner": "scanner-primary", "fingerprint": "d8a84bd53d057920", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-8d36c4a34021f4d3", "level": "note", "message": {"text": "Unused endpoint: PATCH /roadmaps/entries/:id"}, "properties": {"repobilityId": "b38c4a41f8f4a102", "scanner": "scanner-primary", "fingerprint": "8d36c4a34021f4d3", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-fcb461952fd0448b", "level": "note", "message": {"text": "Unused endpoint: DELETE /roadmaps/entries/:id"}, "properties": {"repobilityId": "7fc3f96c4182a2f4", "scanner": "scanner-primary", "fingerprint": "fcb461952fd0448b", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-80e43fd38ede4d40", "level": "note", "message": {"text": "Unused endpoint: POST /profile/avatar"}, "properties": {"repobilityId": "9912d8aef9c3049f", "scanner": "scanner-primary", "fingerprint": "80e43fd38ede4d40", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-b6a2e6012c0d3c34", "level": "note", "message": {"text": "Unused endpoint: GET /profile/test-history"}, "properties": {"repobilityId": "eb339e9520d235ae", "scanner": "scanner-primary", "fingerprint": "b6a2e6012c0d3c34", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-ea41d100aaff82e6", "level": "note", "message": {"text": "Unused endpoint: GET /materials/admin"}, "properties": {"repobilityId": "916fa6f5206a58da", "scanner": "scanner-primary", "fingerprint": "ea41d100aaff82e6", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-86d95fd02d0753ae", "level": "note", "message": {"text": "Unused endpoint: GET /materials/admin/:id"}, "properties": {"repobilityId": "5746c670b11cdccc", "scanner": "scanner-primary", "fingerprint": "86d95fd02d0753ae", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-dc3a0b8a291436a7", "level": "note", "message": {"text": "Unused endpoint: PATCH /materials/:id"}, "properties": {"repobilityId": "5ebc49c1c14f78db", "scanner": "scanner-primary", "fingerprint": "dc3a0b8a291436a7", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-5aa20fd0df915ef3", "level": "note", "message": {"text": "Unused endpoint: DELETE /materials/:id"}, "properties": {"repobilityId": "5c657f2848acbcd9", "scanner": "scanner-primary", "fingerprint": "5aa20fd0df915ef3", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-9eefcd47f26f83e9", "level": "note", "message": {"text": "Unused endpoint: GET /materials/favorites"}, "properties": {"repobilityId": "ec4c4239c151872e", "scanner": "scanner-primary", "fingerprint": "9eefcd47f26f83e9", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-320b165f64a9c134", "level": "note", "message": {"text": "Unused endpoint: GET /materials/:id"}, "properties": {"repobilityId": "6ad7abf10f2a46a3", "scanner": "scanner-primary", "fingerprint": "320b165f64a9c134", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-be81d57d456af62a", "level": "note", "message": {"text": "Unused endpoint: POST /materials/:id/favorite"}, "properties": {"repobilityId": "8aa0ee164abcd6f0", "scanner": "scanner-primary", "fingerprint": "be81d57d456af62a", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-3664e846df935a85", "level": "note", "message": {"text": "Unused endpoint: DELETE /materials/:id/favorite"}, "properties": {"repobilityId": "066f24f9e56486ea", "scanner": "scanner-primary", "fingerprint": "3664e846df935a85", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}]}]}