diff --git a/.github/workflows/generate-plantuml.yml b/.github/workflows/generate-plantuml.yml
new file mode 100644
index 000000000..d623f481e
--- /dev/null
+++ b/.github/workflows/generate-plantuml.yml
@@ -0,0 +1,44 @@
+name: Generate PlantUML Diagrams
+
+on:
+ push:
+ branches:
+ - master
+
+permissions:
+ contents: write
+
+jobs:
+ generate-diagrams:
+ name: Generate and Commit Diagrams
+ runs-on: ubuntu-latest
+ if: github.actor != 'github-actions[bot]'
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: '17'
+ cache: maven
+
+ - name: Generate PlantUML diagrams
+ working-directory: MtdrSpring/backend
+ run: sh ./mvnw -q -Parchitecture-diagrams -DskipTests process-classes
+
+ - name: Commit generated diagrams
+ run: |
+ if git status --porcelain docs/diagrams | grep -q '^'; then
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add docs/diagrams/*.puml
+ git commit -m "docs: generate PlantUML diagrams [skip ci]"
+ git push origin HEAD:${GITHUB_REF_NAME}
+ else
+ echo "No diagram changes to commit."
+ fi
\ No newline at end of file
diff --git a/MtdrSpring/backend/pom.xml b/MtdrSpring/backend/pom.xml
index b87b3da6a..d91f4a0d6 100644
--- a/MtdrSpring/backend/pom.xml
+++ b/MtdrSpring/backend/pom.xml
@@ -19,6 +19,9 @@
1.5.5.Final
0.12.6
${project.basedir}/src/main/frontend
+ ${project.basedir}/../../docs/diagrams
+ ${project.basedir}/src/main/java
+ com.springboot.MyTodoList
v23.9.0
8.1.2
false
@@ -208,4 +211,46 @@
+
+
+
+ architecture-diagrams
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.5.0
+
+
+ generate-plantuml-diagrams
+ process-classes
+
+ java
+
+
+ com.springboot.MyTodoList.architecture.PlantUmlDiagramGenerator
+ compile
+
+
+ architecture.sourceDir
+ ${architecture.source-dir}
+
+
+ architecture.outputDir
+ ${architecture.output-dir}
+
+
+ architecture.basePackage
+ ${architecture.base-package}
+
+
+
+
+
+
+
+
+
+
diff --git a/MtdrSpring/backend/src/main/frontend/package-lock.json b/MtdrSpring/backend/src/main/frontend/package-lock.json
index 7ad4ddd47..9b089ee29 100644
--- a/MtdrSpring/backend/src/main/frontend/package-lock.json
+++ b/MtdrSpring/backend/src/main/frontend/package-lock.json
@@ -1,11 +1,11 @@
{
- "name": "chuvabot-frontend",
+ "name": "kairobot-frontend",
"version": "0.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "chuvabot-frontend",
+ "name": "kairobot-frontend",
"version": "0.1.0",
"dependencies": {
"react": "^18.2.0",
diff --git a/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/architecture/PlantUmlDiagramGenerator.java b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/architecture/PlantUmlDiagramGenerator.java
new file mode 100644
index 000000000..b228df6e2
--- /dev/null
+++ b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/architecture/PlantUmlDiagramGenerator.java
@@ -0,0 +1,430 @@
+package com.springboot.MyTodoList.architecture;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * Generates PlantUML class diagrams from the Java source tree.
+ *
+ *
The generator keeps the implementation intentionally lightweight so it can run as part of the
+ * Maven build without introducing extra parsing dependencies. It groups classes into package/component
+ * diagrams and writes one .puml file per group.
+ */
+public final class PlantUmlDiagramGenerator {
+
+ private static final Pattern PACKAGE_PATTERN = Pattern.compile("^\\s*package\\s+([\\w.]+)\\s*;");
+ private static final Pattern TYPE_PATTERN = Pattern.compile(
+ "^\\s*(?:@[\\w.()\\\"',\\s]+\\s+)*(?:(?:public|protected|private|abstract|final|static|sealed|non-sealed)\\s+)*(class|interface|enum|record)\\s+(\\w+)\\b(.*)$");
+ private static final Set JAVA_MODIFIERS = Set.of(
+ "public", "protected", "private", "static", "final", "abstract", "transient", "volatile",
+ "synchronized", "default", "native", "strictfp", "sealed", "non-sealed"
+ );
+ private static final Set IGNORE_TYPE_TOKENS = Set.of(
+ "String", "Object", "Integer", "Long", "Short", "Byte", "Double", "Float", "Boolean", "Character",
+ "List", "Map", "Set", "Collection", "Iterable", "Optional", "Stream", "Enum", "Date", "LocalDate",
+ "LocalDateTime", "Instant", "BigDecimal", "BigInteger", "UUID", "Class", "Path", "IOException"
+ );
+
+ private PlantUmlDiagramGenerator() {
+ }
+
+ public static void main(String[] args) throws IOException {
+ Path sourceDir = Path.of(System.getProperty("architecture.sourceDir", "src/main/java"));
+ Path outputDir = Path.of(System.getProperty("architecture.outputDir", "../../docs/diagrams"));
+ String basePackage = System.getProperty("architecture.basePackage", "com.springboot.MyTodoList");
+
+ List types = scanTypes(sourceDir, basePackage);
+ Map typesBySimpleName = indexBySimpleName(types);
+ Map> categorizedTypes = categorize(types);
+
+ Files.createDirectories(outputDir);
+ for (DiagramCategory category : DiagramCategory.values()) {
+ Path diagramPath = outputDir.resolve(category.fileName);
+ List categoryTypes = categorizedTypes.getOrDefault(category, List.of());
+ if (categoryTypes.isEmpty()) {
+ Files.deleteIfExists(diagramPath);
+ continue;
+ }
+
+ writeDiagram(diagramPath, category, categoryTypes, typesBySimpleName);
+ }
+ }
+
+ private static List scanTypes(Path sourceDir, String basePackage) throws IOException {
+ if (!Files.exists(sourceDir)) {
+ return List.of();
+ }
+
+ List types = new ArrayList<>();
+ try (var paths = Files.walk(sourceDir)) {
+ for (Path path : paths.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java")).sorted().toList()) {
+ TypeInfo typeInfo = parseType(path, basePackage);
+ if (typeInfo != null) {
+ types.add(typeInfo);
+ }
+ }
+ }
+ return types;
+ }
+
+ private static TypeInfo parseType(Path file, String basePackage) throws IOException {
+ List lines = Files.readAllLines(file, StandardCharsets.UTF_8);
+ String packageName = null;
+ int typeStartIndex = -1;
+ String kind = null;
+ String simpleName = null;
+ String declarationLine = null;
+
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ if (packageName == null) {
+ Matcher packageMatcher = PACKAGE_PATTERN.matcher(line);
+ if (packageMatcher.find()) {
+ packageName = packageMatcher.group(1);
+ continue;
+ }
+ }
+
+ Matcher typeMatcher = TYPE_PATTERN.matcher(line);
+ if (typeMatcher.find()) {
+ kind = typeMatcher.group(1);
+ simpleName = typeMatcher.group(2);
+ declarationLine = line.trim();
+ typeStartIndex = i;
+ break;
+ }
+ }
+
+ if (packageName == null || typeStartIndex < 0 || simpleName == null || !packageName.startsWith(basePackage) || packageName.startsWith(basePackage + ".architecture")) {
+ return null;
+ }
+
+ List members = new ArrayList<>();
+ Set dependencyTypes = new LinkedHashSet<>();
+ StringBuilder body = new StringBuilder();
+ int depth = 0;
+ boolean typeBodyStarted = false;
+ for (int i = typeStartIndex; i < lines.size(); i++) {
+ String line = lines.get(i);
+ body.append(line).append(System.lineSeparator());
+ depth += count(line, '{');
+ depth -= count(line, '}');
+ if (line.contains("{")) {
+ typeBodyStarted = true;
+ }
+
+ if (!typeBodyStarted || depth < 1) {
+ continue;
+ }
+
+ if (i == typeStartIndex) {
+ dependencyTypes.addAll(extractTypeTokens(extractDeclarationTail(declarationLine)));
+ continue;
+ }
+
+ if (depth == 1) {
+ MemberInfo member = parseMember(line);
+ if (member != null) {
+ members.add(member);
+ dependencyTypes.addAll(extractTypeTokens(member.typeExpression));
+ }
+ }
+
+ if (depth == 0) {
+ break;
+ }
+ }
+
+ return new TypeInfo(packageName, simpleName, kind, members, dependencyTypes, file);
+ }
+
+ private static MemberInfo parseMember(String line) {
+ String trimmed = line.trim();
+ if (trimmed.isEmpty() || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
+ return null;
+ }
+ if (!trimmed.endsWith(";") || trimmed.contains("(") || trimmed.startsWith("return ")) {
+ return null;
+ }
+
+ String declaration = trimmed.substring(0, trimmed.length() - 1).trim();
+ declaration = removeLeadingAnnotations(declaration);
+ declaration = removeLeadingModifiers(declaration);
+ if (declaration.isEmpty() || declaration.contains("{") || declaration.contains("}") || declaration.contains("\"") || declaration.startsWith("@")) {
+ return null;
+ }
+
+ int assignmentIndex = declaration.indexOf('=');
+ if (assignmentIndex >= 0) {
+ declaration = declaration.substring(0, assignmentIndex).trim();
+ }
+
+ int lastSpace = declaration.lastIndexOf(' ');
+ if (lastSpace < 1) {
+ return null;
+ }
+
+ String typeExpression = declaration.substring(0, lastSpace).trim();
+ String name = declaration.substring(lastSpace + 1).trim();
+ if (typeExpression.isEmpty() || name.isEmpty()) {
+ return null;
+ }
+
+ return new MemberInfo(name, typeExpression);
+ }
+
+ private static String removeLeadingAnnotations(String value) {
+ String result = value.trim();
+ while (result.startsWith("@")) {
+ int spaceIndex = result.indexOf(' ');
+ if (spaceIndex < 0) {
+ return "";
+ }
+ result = result.substring(spaceIndex + 1).trim();
+ }
+ return result;
+ }
+
+ private static String removeLeadingModifiers(String value) {
+ String result = value.trim();
+ boolean changed;
+ do {
+ changed = false;
+ for (String modifier : JAVA_MODIFIERS) {
+ String prefix = modifier + " ";
+ if (result.startsWith(prefix)) {
+ result = result.substring(prefix.length()).trim();
+ changed = true;
+ }
+ }
+ } while (changed);
+ return result;
+ }
+
+ private static String extractDeclarationTail(String declarationLine) {
+ if (declarationLine == null) {
+ return "";
+ }
+ int typeIndex = declarationLine.indexOf("class ");
+ if (typeIndex < 0) {
+ typeIndex = declarationLine.indexOf("interface ");
+ }
+ if (typeIndex < 0) {
+ typeIndex = declarationLine.indexOf("enum ");
+ }
+ if (typeIndex < 0) {
+ typeIndex = declarationLine.indexOf("record ");
+ }
+ return typeIndex < 0 ? declarationLine : declarationLine.substring(typeIndex);
+ }
+
+ private static Set extractTypeTokens(String expression) {
+ if (expression == null || expression.isBlank()) {
+ return Set.of();
+ }
+
+ String cleaned = expression
+ .replaceAll("/\\*.*?\\*/", " ")
+ .replaceAll("<[^>]*>", " ")
+ .replace('[', ' ')
+ .replace(']', ' ')
+ .replace('?', ' ')
+ .replace('&', ' ')
+ .replace('|', ' ')
+ .replace(',', ' ')
+ .replace("extends", " ")
+ .replace("super", " ");
+
+ Set tokens = new LinkedHashSet<>();
+ for (String token : cleaned.split("[^A-Za-z0-9_$.]+")) {
+ if (token.isBlank()) {
+ continue;
+ }
+ String simpleToken = token.contains(".") ? token.substring(token.lastIndexOf('.') + 1) : token;
+ if (simpleToken.isBlank()) {
+ continue;
+ }
+ if (Character.isUpperCase(simpleToken.charAt(0)) && !IGNORE_TYPE_TOKENS.contains(simpleToken)) {
+ tokens.add(simpleToken);
+ }
+ }
+ return tokens;
+ }
+
+ private static Map indexBySimpleName(List types) {
+ Map index = new LinkedHashMap<>();
+ for (TypeInfo type : types) {
+ index.putIfAbsent(type.simpleName(), type);
+ }
+ return index;
+ }
+
+ private static Map> categorize(List types) {
+ Map> categorized = new EnumMap<>(DiagramCategory.class);
+ for (DiagramCategory category : DiagramCategory.values()) {
+ categorized.put(category, new ArrayList<>());
+ }
+
+ for (TypeInfo type : types) {
+ EnumSet categories = classify(type);
+ for (DiagramCategory category : categories) {
+ categorized.get(category).add(type);
+ }
+ }
+
+ for (Map.Entry> entry : categorized.entrySet()) {
+ entry.getValue().sort((left, right) -> left.simpleName().compareToIgnoreCase(right.simpleName()));
+ }
+ return categorized;
+ }
+
+ private static EnumSet classify(TypeInfo type) {
+ EnumSet categories = EnumSet.noneOf(DiagramCategory.class);
+ String packageName = type.packageName().toLowerCase(Locale.ROOT);
+ String simpleName = type.simpleName();
+ String lowerSimpleName = simpleName.toLowerCase(Locale.ROOT);
+
+ if (packageName.contains("controller") || lowerSimpleName.endsWith("controller") || lowerSimpleName.equals("spacontroller")) {
+ categories.add(DiagramCategory.CONTROLLERS);
+ }
+ if (packageName.contains("repository") || lowerSimpleName.endsWith("repository")) {
+ categories.add(DiagramCategory.REPOSITORIES);
+ }
+ if (packageName.contains("service") || lowerSimpleName.endsWith("service") || lowerSimpleName.endsWith("scheduler")) {
+ categories.add(DiagramCategory.SERVICES);
+ }
+ if (packageName.contains("notification") || lowerSimpleName.startsWith("notification")) {
+ categories.add(DiagramCategory.NOTIFICATION);
+ }
+ if (packageName.contains("telegram.nlu") || packageName.endsWith(".nlu") || lowerSimpleName.contains("nlu") || simpleName.equals("NaturalLanguageRouter")) {
+ categories.add(DiagramCategory.NLU);
+ }
+ if ((packageName.contains(".telegram") && !packageName.contains(".telegram.nlu")) || lowerSimpleName.endsWith("bot")) {
+ categories.add(DiagramCategory.BOT);
+ }
+ if (packageName.contains("config") || lowerSimpleName.endsWith("config") || lowerSimpleName.endsWith("filter") || lowerSimpleName.endsWith("util") || lowerSimpleName.endsWith("detailsserviceimpl")) {
+ categories.add(DiagramCategory.CONFIG);
+ }
+ if (packageName.contains("audit") || lowerSimpleName.startsWith("audit")) {
+ categories.add(DiagramCategory.AUDIT);
+ }
+ if (categories.isEmpty() && !lowerSimpleName.endsWith("application")) {
+ categories.add(DiagramCategory.MODELS);
+ }
+
+ // Keep domain and shared model classes in the models diagram even if they are also cross-cutting concerns.
+ if (categories.contains(DiagramCategory.AUDIT) || categories.contains(DiagramCategory.CONFIG)) {
+ categories.add(DiagramCategory.MODELS);
+ }
+
+ return categories;
+ }
+
+ private static void writeDiagram(Path diagramPath, DiagramCategory category, List types, Map typesBySimpleName) throws IOException {
+ Set includedSimpleNames = types.stream().map(TypeInfo::simpleName).collect(Collectors.toCollection(TreeSet::new));
+ StringBuilder diagram = new StringBuilder();
+ diagram.append("@startuml\n");
+ diagram.append("title ").append(category.title).append("\n");
+ diagram.append("skinparam classAttributeIconSize 0\n");
+ diagram.append("hide empty members\n");
+ diagram.append("left to right direction\n\n");
+
+ diagram.append("package \"").append(category.title).append("\" {\n");
+ for (TypeInfo type : types) {
+ diagram.append(renderClass(type)).append('\n');
+ }
+ diagram.append("}\n\n");
+
+ Set relationLines = new LinkedHashSet<>();
+ for (TypeInfo type : types) {
+ for (String dependency : type.dependencyTypes()) {
+ TypeInfo resolved = typesBySimpleName.get(dependency);
+ if (resolved == null || resolved.simpleName().equals(type.simpleName())) {
+ continue;
+ }
+ String relation = type.simpleName() + " --> " + resolved.simpleName();
+ if (includedSimpleNames.contains(resolved.simpleName())) {
+ relation += " : uses";
+ } else {
+ relation += " : depends on";
+ }
+ relationLines.add(relation);
+ }
+ }
+
+ if (!relationLines.isEmpty()) {
+ diagram.append(String.join("\n", relationLines)).append('\n');
+ }
+ diagram.append("@enduml\n");
+
+ Files.writeString(diagramPath, diagram.toString(), StandardCharsets.UTF_8);
+ }
+
+ private static String renderClass(TypeInfo type) {
+ StringBuilder rendered = new StringBuilder();
+ rendered.append(type.kind()).append(' ').append(type.simpleName()).append(" {\n");
+ for (MemberInfo member : type.members()) {
+ rendered.append(" + ").append(member.name()).append(" : ").append(member.typeExpression()).append("\n");
+ }
+ rendered.append("}\n");
+ return rendered.toString();
+ }
+
+ private static int count(String value, char needle) {
+ int count = 0;
+ for (int i = 0; i < value.length(); i++) {
+ if (value.charAt(i) == needle) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private record TypeInfo(
+ String packageName,
+ String simpleName,
+ String kind,
+ List members,
+ Set dependencyTypes,
+ Path sourceFile
+ ) {
+ }
+
+ private record MemberInfo(String name, String typeExpression) {
+ }
+
+ private enum DiagramCategory {
+ CONTROLLERS("controllers.puml", "Controllers"),
+ SERVICES("services.puml", "Services"),
+ REPOSITORIES("repositories.puml", "Repositories"),
+ BOT("bot.puml", "Bot"),
+ NOTIFICATION("notification.puml", "Notification"),
+ NLU("nlu.puml", "NLU"),
+ CONFIG("config.puml", "Config"),
+ AUDIT("audit.puml", "Audit"),
+ MODELS("models.puml", "Entities and Models");
+
+ private final String fileName;
+ private final String title;
+
+ DiagramCategory(String fileName, String title) {
+ this.fileName = fileName;
+ this.title = title;
+ }
+ }
+}
\ No newline at end of file
diff --git a/architecture/model.dsl b/architecture/model.dsl
index d43a0ebfb..fd070bfd2 100644
--- a/architecture/model.dsl
+++ b/architecture/model.dsl
@@ -11,17 +11,37 @@ workspace {
bot = softwareSystem "Oracle Java Bot Platform" "Automates task management, NLU-based chat interactions, and integrations." {
webApp = container "Web App" "Task/project dashboard." "React.js"
- telegramBot = container "Telegram Bot Adapter" "Handles message and chat routing with NLU workflow." "Node.js"
- nlu = container "NLU Service" "Parses and classifies user intent/entities for commands via text/chat." "Java Spring Boot or Python"
+ telegramBot = container "Telegram Bot Adapter" "Handles message and chat routing with NLU workflow." "Node.js" {
+ url "https://github.com/PatWhite29/Oracle_Java_Bot/blob/master/docs/diagrams/bot.puml"
+ }
+ nlu = container "NLU Service" "Parses and classifies user intent/entities for commands via text/chat." "Java Spring Boot or Python" {
+ url "https://github.com/PatWhite29/Oracle_Java_Bot/blob/master/docs/diagrams/nlu.puml"
+ }
backend = container "Spring Boot Backend" "Skill orchestration, data/services, notifications, and security." "Java, Spring Boot" {
- userController = component "User Controller" "User CRUD, authZ/authN." "REST Controller"
- taskController = component "Task Controller" "CRUD for tasks, status management, assignment." "REST Controller"
- sprintController = component "Sprint Controller" "Sprint planning/execution." "REST Controller"
- projectService = component "Project Service" "Business/project logic." "Spring Service"
- taskService = component "Task Service" "Task rules, assignment, priorities." "Spring Service"
- notificationService = component "Notification Service" "User notifications via web/Telegram." "Spring Service"
- repositoryLayer = component "Repository Layer" "Repository for all system entities." "Spring Data/JPA"
- dbAccess = component "Database Access Layer" "Performs SQL/PLSQL DB connection." "JDBC/Spring Data"
+ userController = component "User Controller" "User CRUD, authZ/authN." "REST Controller" {
+ url "https://github.com/PatWhite29/Oracle_Java_Bot/blob/master/docs/diagrams/controllers.puml"
+ }
+ taskController = component "Task Controller" "CRUD for tasks, status management, assignment." "REST Controller" {
+ url "https://github.com/PatWhite29/Oracle_Java_Bot/blob/master/docs/diagrams/controllers.puml"
+ }
+ sprintController = component "Sprint Controller" "Sprint planning/execution." "REST Controller" {
+ url "https://github.com/PatWhite29/Oracle_Java_Bot/blob/master/docs/diagrams/controllers.puml"
+ }
+ projectService = component "Project Service" "Business/project logic." "Spring Service" {
+ url "https://github.com/PatWhite29/Oracle_Java_Bot/blob/master/docs/diagrams/services.puml"
+ }
+ taskService = component "Task Service" "Task rules, assignment, priorities." "Spring Service" {
+ url "https://github.com/PatWhite29/Oracle_Java_Bot/blob/master/docs/diagrams/services.puml"
+ }
+ notificationService = component "Notification Service" "User notifications via web/Telegram." "Spring Service" {
+ url "https://github.com/PatWhite29/Oracle_Java_Bot/blob/master/docs/diagrams/notification.puml"
+ }
+ repositoryLayer = component "Repository Layer" "Repository for all system entities." "Spring Data/JPA" {
+ url "https://github.com/PatWhite29/Oracle_Java_Bot/blob/master/docs/diagrams/repositories.puml"
+ }
+ dbAccess = component "Database Access Layer" "Performs SQL/PLSQL DB connection." "JDBC/Spring Data" {
+ url "https://github.com/PatWhite29/Oracle_Java_Bot/blob/master/docs/diagrams/repositories.puml"
+ }
}
db = container "Oracle Autonomous Database" "Persistence for users, tasks, sprints, projects, logs." "Oracle Autonomous DB" {
tag "database"
diff --git a/docs/diagrams/audit.puml b/docs/diagrams/audit.puml
new file mode 100644
index 000000000..87360f664
--- /dev/null
+++ b/docs/diagrams/audit.puml
@@ -0,0 +1,36 @@
+@startuml
+title Audit
+skinparam classAttributeIconSize 0
+hide empty members
+left to right direction
+
+package "Audit" {
+enum AuditAction {
+}
+
+class AuditLog {
+ + id : Long
+ + employee : User
+ + entityType : EntityType
+ + entityId : Long
+ + action : AuditAction
+ + oldValue : String
+ + newValue : String
+ + createdAt : LocalDateTime
+}
+
+interface AuditLogRepository {
+}
+
+class AuditLogService {
+ + auditLogRepository : AuditLogRepository
+ + objectMapper : ObjectMapper
+}
+
+}
+
+AuditLog --> User : depends on
+AuditLog --> EntityType : depends on
+AuditLog --> AuditAction : uses
+AuditLogService --> AuditLogRepository : uses
+@enduml
diff --git a/docs/diagrams/bot.puml b/docs/diagrams/bot.puml
new file mode 100644
index 000000000..0d33c467c
--- /dev/null
+++ b/docs/diagrams/bot.puml
@@ -0,0 +1,89 @@
+@startuml
+title Bot
+skinparam classAttributeIconSize 0
+hide empty members
+left to right direction
+
+package "Bot" {
+class CommentHandler {
+ + userService : UserService
+ + activityService : TaskActivityService
+}
+
+class HelpHandler {
+}
+
+class KairoBot {
+ + botToken : String
+ + telegramClient : TelegramClient
+ + startHandler : StartHandler
+ + loginHandler : LoginHandler
+ + helpHandler : HelpHandler
+ + myProjectsHandler : MyProjectsHandler
+ + myTasksHandler : MyTasksHandler
+ + taskHandler : TaskHandler
+ + taskStatusHandler : TaskStatusHandler
+ + commentHandler : CommentHandler
+ + nluRouter : NaturalLanguageRouter
+ + objectMapper : ObjectMapper
+}
+
+class LoginHandler {
+ + userService : UserService
+}
+
+class MyProjectsHandler {
+ + userService : UserService
+ + projectService : ProjectService
+}
+
+class MyTasksHandler {
+ + userService : UserService
+ + taskService : TaskService
+}
+
+class StartHandler {
+}
+
+class SyntheticUpdateFactory {
+}
+
+class TaskHandler {
+ + userService : UserService
+ + taskService : TaskService
+}
+
+class TaskStatusHandler {
+ + userService : UserService
+ + taskService : TaskService
+}
+
+class TelegramBotConfig {
+}
+
+class TelegramHelper {
+}
+
+}
+
+CommentHandler --> UserService : depends on
+CommentHandler --> TaskActivityService : depends on
+KairoBot --> StartHandler : uses
+KairoBot --> LoginHandler : uses
+KairoBot --> HelpHandler : uses
+KairoBot --> MyProjectsHandler : uses
+KairoBot --> MyTasksHandler : uses
+KairoBot --> TaskHandler : uses
+KairoBot --> TaskStatusHandler : uses
+KairoBot --> CommentHandler : uses
+KairoBot --> NaturalLanguageRouter : depends on
+LoginHandler --> UserService : depends on
+MyProjectsHandler --> UserService : depends on
+MyProjectsHandler --> ProjectService : depends on
+MyTasksHandler --> UserService : depends on
+MyTasksHandler --> TaskService : depends on
+TaskHandler --> UserService : depends on
+TaskHandler --> TaskService : depends on
+TaskStatusHandler --> UserService : depends on
+TaskStatusHandler --> TaskService : depends on
+@enduml
diff --git a/docs/diagrams/config.puml b/docs/diagrams/config.puml
new file mode 100644
index 000000000..c553cc569
--- /dev/null
+++ b/docs/diagrams/config.puml
@@ -0,0 +1,49 @@
+@startuml
+title Config
+skinparam classAttributeIconSize 0
+hide empty members
+left to right direction
+
+package "Config" {
+class CorsConfig {
+}
+
+class JwtAuthFilter {
+ + jwtUtil : JwtUtil
+ + userDetailsService : UserDetailsServiceImpl
+}
+
+class JwtUtil {
+ + signingKey : SecretKey
+ + expirationMs : long
+}
+
+class RequestLoggingFilter {
+}
+
+class SecurityConfig {
+ + jwtAuthFilter : JwtAuthFilter
+ + userDetailsService : UserDetailsServiceImpl
+}
+
+class SpaController {
+}
+
+class SwaggerConfig {
+}
+
+class TelegramBotConfig {
+}
+
+class UserDetailsServiceImpl {
+ + userRepository : UserRepository
+}
+
+}
+
+JwtAuthFilter --> JwtUtil : uses
+JwtAuthFilter --> UserDetailsServiceImpl : uses
+SecurityConfig --> JwtAuthFilter : uses
+SecurityConfig --> UserDetailsServiceImpl : uses
+UserDetailsServiceImpl --> UserRepository : depends on
+@enduml
diff --git a/docs/diagrams/controllers.puml b/docs/diagrams/controllers.puml
new file mode 100644
index 000000000..4ff181c9f
--- /dev/null
+++ b/docs/diagrams/controllers.puml
@@ -0,0 +1,53 @@
+@startuml
+title Controllers
+skinparam classAttributeIconSize 0
+hide empty members
+left to right direction
+
+package "Controllers" {
+class AuthController {
+ + authService : AuthService
+}
+
+class DashboardController {
+ + dashboardService : DashboardService
+}
+
+class ProjectController {
+ + projectService : ProjectService
+}
+
+class ProjectMemberController {
+ + memberService : ProjectMemberService
+}
+
+class SpaController {
+}
+
+class SprintController {
+ + sprintService : SprintService
+}
+
+class TaskActivityController {
+ + activityService : TaskActivityService
+}
+
+class TaskController {
+ + taskService : TaskService
+}
+
+class UserController {
+ + userService : UserService
+}
+
+}
+
+AuthController --> AuthService : depends on
+DashboardController --> DashboardService : depends on
+ProjectController --> ProjectService : depends on
+ProjectMemberController --> ProjectMemberService : depends on
+SprintController --> SprintService : depends on
+TaskActivityController --> TaskActivityService : depends on
+TaskController --> TaskService : depends on
+UserController --> UserService : depends on
+@enduml
diff --git a/docs/diagrams/models.puml b/docs/diagrams/models.puml
new file mode 100644
index 000000000..da9464457
--- /dev/null
+++ b/docs/diagrams/models.puml
@@ -0,0 +1,421 @@
+@startuml
+title Entities and Models
+skinparam classAttributeIconSize 0
+hide empty members
+left to right direction
+
+package "Entities and Models" {
+enum ActivityType {
+}
+
+class AddMemberRequest {
+ + userId : Long
+}
+
+enum AuditAction {
+}
+
+class AuditLog {
+ + id : Long
+ + employee : User
+ + entityType : EntityType
+ + entityId : Long
+ + action : AuditAction
+ + oldValue : String
+ + newValue : String
+ + createdAt : LocalDateTime
+}
+
+interface AuditLogRepository {
+}
+
+class AuditLogService {
+ + auditLogRepository : AuditLogRepository
+ + objectMapper : ObjectMapper
+}
+
+class AuthResponse {
+ + token : String
+ + user : UserResponse
+}
+
+class BacklogResponse {
+ + totalTasks : long
+ + totalStoryPoints : long
+ + byPriority : Map
+}
+
+class BurndownResponse {
+ + sprintId : Long
+ + sprintName : String
+ + totalStoryPoints : long
+ + completedStoryPoints : long
+ + remainingStoryPoints : long
+ + idealBurnPercent : double
+ + actualBurnPercent : double
+}
+
+class ClosedSprintException {
+}
+
+class CloseProjectResponse {
+ + projectId : Long
+ + projectName : String
+ + sprintsClosed : int
+ + pendingTasks : int
+ + message : String
+}
+
+class CommentRequest {
+ + content : String
+}
+
+class ConflictException {
+}
+
+class CorsConfig {
+}
+
+class EfficiencyResponse {
+ + sprintId : Long
+ + sprintName : String
+ + totalActualHours : double
+ + totalSpCompleted : long
+ + members : List
+}
+
+enum EntityType {
+}
+
+class ErrorResponse {
+ + error : String
+ + message : String
+ + status : int
+}
+
+class ForbiddenException {
+}
+
+class GlobalExceptionHandler {
+}
+
+class JwtAuthFilter {
+ + jwtUtil : JwtUtil
+ + userDetailsService : UserDetailsServiceImpl
+}
+
+class JwtUtil {
+ + signingKey : SecretKey
+ + expirationMs : long
+}
+
+class LoginRequest {
+ + email : String
+ + password : String
+}
+
+class NotProjectParticipantException {
+}
+
+record PagedResponse {
+}
+
+class Project {
+ + id : Long
+ + projectName : String
+ + description : String
+ + status : ProjectStatus
+ + manager : User
+ + createdAt : LocalDateTime
+}
+
+interface ProjectMapper {
+}
+
+class ProjectMember {
+ + id : Long
+ + project : Project
+ + employee : User
+ + createdAt : LocalDateTime
+}
+
+interface ProjectMemberMapper {
+}
+
+class ProjectMemberResponse {
+ + id : Long
+ + employee : UserSummary
+ + createdAt : LocalDateTime
+}
+
+class ProjectRequest {
+ + projectName : String
+ + description : String
+}
+
+class ProjectResponse {
+ + id : Long
+ + projectName : String
+ + description : String
+ + status : ProjectStatus
+ + manager : UserSummary
+ + createdAt : LocalDateTime
+}
+
+enum ProjectStatus {
+}
+
+class RegisterRequest {
+ + fullName : String
+ + email : String
+ + password : String
+}
+
+class RequestLoggingFilter {
+}
+
+class ResourceNotFoundException {
+}
+
+class SecurityConfig {
+ + jwtAuthFilter : JwtAuthFilter
+ + userDetailsService : UserDetailsServiceImpl
+}
+
+class SpaController {
+}
+
+class Sprint {
+ + id : Long
+ + project : Project
+ + sprintName : String
+ + goal : String
+ + startDate : LocalDate
+ + endDate : LocalDate
+ + status : SprintStatus
+ + createdAt : LocalDateTime
+}
+
+class SprintChangeRequest {
+ + sprintId : Long
+}
+
+interface SprintMapper {
+}
+
+class SprintRequest {
+ + sprintName : String
+ + goal : String
+ + startDate : LocalDate
+ + endDate : LocalDate
+}
+
+class SprintResponse {
+ + id : Long
+ + projectId : Long
+ + sprintName : String
+ + goal : String
+ + startDate : LocalDate
+ + endDate : LocalDate
+ + status : SprintStatus
+ + createdAt : LocalDateTime
+}
+
+enum SprintStatus {
+}
+
+class SprintSummary {
+ + id : Long
+ + sprintName : String
+ + status : SprintStatus
+}
+
+class SprintSummaryResponse {
+ + sprintId : Long
+ + sprintName : String
+ + statusCounts : Map
+ + spCommitted : long
+ + spCompleted : long
+ + completionPercentage : double
+ + blockedCount : int
+}
+
+class StatusChangeRequest {
+ + status : TaskStatus
+ + actualHours : BigDecimal
+}
+
+class SwaggerConfig {
+}
+
+class Task {
+ + id : Long
+ + project : Project
+ + sprint : Sprint
+ + taskName : String
+ + description : String
+ + status : TaskStatus
+ + priority : TaskPriority
+ + storyPoints : Integer
+ + assignedTo : User
+ + createdBy : User
+ + actualHours : BigDecimal
+ + createdAt : LocalDateTime
+}
+
+class TaskActivity {
+ + id : Long
+ + task : Task
+ + employee : User
+ + activityType : ActivityType
+ + content : String
+ + createdAt : LocalDateTime
+}
+
+interface TaskActivityMapper {
+}
+
+class TaskActivityResponse {
+ + id : Long
+ + taskId : Long
+ + employee : UserSummary
+ + activityType : ActivityType
+ + content : String
+ + createdAt : LocalDateTime
+}
+
+interface TaskMapper {
+}
+
+enum TaskPriority {
+}
+
+class TaskRequest {
+ + taskName : String
+ + description : String
+ + status : TaskStatus
+ + priority : TaskPriority
+ + storyPoints : Integer
+ + assignedTo : Long
+ + sprintId : Long
+}
+
+class TaskResponse {
+ + id : Long
+ + projectId : Long
+ + sprintId : Long
+ + sprint : SprintSummary
+ + taskName : String
+ + description : String
+ + status : TaskStatus
+ + priority : TaskPriority
+ + storyPoints : Integer
+ + assignedTo : UserSummary
+ + createdBy : UserSummary
+ + actualHours : BigDecimal
+ + createdAt : LocalDateTime
+}
+
+enum TaskStatus {
+}
+
+class TelegramBotConfig {
+}
+
+class TransferRequest {
+ + newManagerId : Long
+}
+
+class User {
+ + id : Long
+ + fullName : String
+ + email : String
+ + passwordHash : String
+ + telegramChatId : Long
+ + isActive : boolean
+ + createdAt : LocalDateTime
+}
+
+class UserDetailsServiceImpl {
+ + userRepository : UserRepository
+}
+
+interface UserMapper {
+}
+
+class UserRequest {
+ + fullName : String
+ + email : String
+}
+
+class UserResponse {
+ + id : Long
+ + fullName : String
+ + email : String
+ + telegramChatId : Long
+ + createdAt : LocalDateTime
+}
+
+class UserSummary {
+ + id : Long
+ + fullName : String
+ + email : String
+}
+
+class ValidationException {
+}
+
+class VelocityResponse {
+ + sprintId : Long
+ + sprintName : String
+ + spCompleted : long
+}
+
+class WorkloadResponse {
+ + userId : Long
+ + fullName : String
+ + taskCounts : Map
+ + storyPoints : Map
+}
+
+}
+
+AuditLog --> User : uses
+AuditLog --> EntityType : uses
+AuditLog --> AuditAction : uses
+AuditLogService --> AuditLogRepository : uses
+AuthResponse --> UserResponse : uses
+JwtAuthFilter --> JwtUtil : uses
+JwtAuthFilter --> UserDetailsServiceImpl : uses
+Project --> ProjectStatus : uses
+Project --> User : uses
+ProjectMember --> Project : uses
+ProjectMember --> User : uses
+ProjectMemberResponse --> UserSummary : uses
+ProjectResponse --> ProjectStatus : uses
+ProjectResponse --> UserSummary : uses
+SecurityConfig --> JwtAuthFilter : uses
+SecurityConfig --> UserDetailsServiceImpl : uses
+Sprint --> Project : uses
+Sprint --> SprintStatus : uses
+SprintResponse --> SprintStatus : uses
+SprintSummary --> SprintStatus : uses
+StatusChangeRequest --> TaskStatus : uses
+Task --> Project : uses
+Task --> Sprint : uses
+Task --> TaskStatus : uses
+Task --> TaskPriority : uses
+Task --> User : uses
+TaskActivity --> Task : uses
+TaskActivity --> User : uses
+TaskActivity --> ActivityType : uses
+TaskActivityResponse --> UserSummary : uses
+TaskActivityResponse --> ActivityType : uses
+TaskRequest --> TaskStatus : uses
+TaskRequest --> TaskPriority : uses
+TaskResponse --> SprintSummary : uses
+TaskResponse --> TaskStatus : uses
+TaskResponse --> TaskPriority : uses
+TaskResponse --> UserSummary : uses
+UserDetailsServiceImpl --> UserRepository : depends on
+@enduml
diff --git a/docs/diagrams/nlu.puml b/docs/diagrams/nlu.puml
new file mode 100644
index 000000000..464a9f338
--- /dev/null
+++ b/docs/diagrams/nlu.puml
@@ -0,0 +1,30 @@
+@startuml
+title NLU
+skinparam classAttributeIconSize 0
+hide empty members
+left to right direction
+
+package "NLU" {
+class NaturalLanguageRouter {
+ + anthropicApiKey : String
+ + restTemplate : RestTemplate
+ + objectMapper : ObjectMapper
+}
+
+class NluErrorMessages {
+}
+
+class NluResult {
+ + status : NluStatus
+ + command : String
+ + params : Map
+ + missing : List
+}
+
+enum NluStatus {
+}
+
+}
+
+NluResult --> NluStatus : uses
+@enduml
diff --git a/docs/diagrams/notification.puml b/docs/diagrams/notification.puml
new file mode 100644
index 000000000..82c4c964e
--- /dev/null
+++ b/docs/diagrams/notification.puml
@@ -0,0 +1,38 @@
+@startuml
+title Notification
+skinparam classAttributeIconSize 0
+hide empty members
+left to right direction
+
+package "Notification" {
+class NotificationLog {
+ + id : Long
+ + recipient : User
+ + eventType : String
+ + message : String
+ + deliveryStatus : String
+ + sentAt : LocalDateTime
+}
+
+interface NotificationLogRepository {
+}
+
+class NotificationScheduler {
+ + sprintRepository : SprintRepository
+ + taskRepository : TaskRepository
+ + notificationService : NotificationService
+}
+
+class NotificationService {
+ + notificationLogRepository : NotificationLogRepository
+ + telegramClient : TelegramClient
+}
+
+}
+
+NotificationLog --> User : depends on
+NotificationScheduler --> SprintRepository : depends on
+NotificationScheduler --> TaskRepository : depends on
+NotificationScheduler --> NotificationService : uses
+NotificationService --> NotificationLogRepository : uses
+@enduml
diff --git a/docs/diagrams/repositories.puml b/docs/diagrams/repositories.puml
new file mode 100644
index 000000000..9157a28ef
--- /dev/null
+++ b/docs/diagrams/repositories.puml
@@ -0,0 +1,34 @@
+@startuml
+title Repositories
+skinparam classAttributeIconSize 0
+hide empty members
+left to right direction
+
+package "Repositories" {
+interface AuditLogRepository {
+}
+
+interface NotificationLogRepository {
+}
+
+interface ProjectMemberRepository {
+}
+
+interface ProjectRepository {
+}
+
+interface SprintRepository {
+}
+
+interface TaskActivityRepository {
+}
+
+interface TaskRepository {
+}
+
+interface UserRepository {
+}
+
+}
+
+@enduml
diff --git a/docs/diagrams/services.puml b/docs/diagrams/services.puml
new file mode 100644
index 000000000..3d290d89e
--- /dev/null
+++ b/docs/diagrams/services.puml
@@ -0,0 +1,139 @@
+@startuml
+title Services
+skinparam classAttributeIconSize 0
+hide empty members
+left to right direction
+
+package "Services" {
+class AuditLogService {
+ + auditLogRepository : AuditLogRepository
+ + objectMapper : ObjectMapper
+}
+
+class AuthService {
+ + userRepository : UserRepository
+ + passwordEncoder : PasswordEncoder
+ + jwtUtil : JwtUtil
+ + userMapper : UserMapper
+}
+
+class DashboardService {
+ + projectService : ProjectService
+ + sprintRepository : SprintRepository
+ + taskRepository : TaskRepository
+ + memberRepository : ProjectMemberRepository
+}
+
+class NotificationScheduler {
+ + sprintRepository : SprintRepository
+ + taskRepository : TaskRepository
+ + notificationService : NotificationService
+}
+
+class NotificationService {
+ + notificationLogRepository : NotificationLogRepository
+ + telegramClient : TelegramClient
+}
+
+class ProjectMemberService {
+ + memberRepository : ProjectMemberRepository
+ + projectService : ProjectService
+ + userService : UserService
+ + auditLogService : AuditLogService
+ + memberMapper : ProjectMemberMapper
+}
+
+class ProjectService {
+ + projectRepository : ProjectRepository
+ + memberRepository : ProjectMemberRepository
+ + sprintRepository : SprintRepository
+ + taskRepository : TaskRepository
+ + activityRepository : TaskActivityRepository
+ + userService : UserService
+ + auditLogService : AuditLogService
+ + projectMapper : ProjectMapper
+}
+
+class SprintService {
+ + sprintRepository : SprintRepository
+ + taskRepository : TaskRepository
+ + projectService : ProjectService
+ + userService : UserService
+ + auditLogService : AuditLogService
+ + sprintMapper : SprintMapper
+}
+
+class TaskActivityService {
+ + activityRepository : TaskActivityRepository
+ + taskService : TaskService
+ + projectService : ProjectService
+ + userService : UserService
+ + activityMapper : TaskActivityMapper
+}
+
+class TaskService {
+ + taskRepository : TaskRepository
+ + activityRepository : TaskActivityRepository
+ + sprintRepository : SprintRepository
+ + projectService : ProjectService
+ + userService : UserService
+ + auditLogService : AuditLogService
+ + notificationService : NotificationService
+ + taskMapper : TaskMapper
+}
+
+class UserService {
+ + userRepository : UserRepository
+ + userMapper : UserMapper
+ + passwordEncoder : PasswordEncoder
+}
+
+}
+
+AuditLogService --> AuditLogRepository : depends on
+AuthService --> UserRepository : depends on
+AuthService --> JwtUtil : depends on
+AuthService --> UserMapper : depends on
+DashboardService --> ProjectService : uses
+DashboardService --> SprintRepository : depends on
+DashboardService --> TaskRepository : depends on
+DashboardService --> ProjectMemberRepository : depends on
+NotificationScheduler --> SprintRepository : depends on
+NotificationScheduler --> TaskRepository : depends on
+NotificationScheduler --> NotificationService : uses
+NotificationService --> NotificationLogRepository : depends on
+ProjectMemberService --> ProjectMemberRepository : depends on
+ProjectMemberService --> ProjectService : uses
+ProjectMemberService --> UserService : uses
+ProjectMemberService --> AuditLogService : uses
+ProjectMemberService --> ProjectMemberMapper : depends on
+ProjectService --> ProjectRepository : depends on
+ProjectService --> ProjectMemberRepository : depends on
+ProjectService --> SprintRepository : depends on
+ProjectService --> TaskRepository : depends on
+ProjectService --> TaskActivityRepository : depends on
+ProjectService --> UserService : uses
+ProjectService --> AuditLogService : uses
+ProjectService --> ProjectMapper : depends on
+SprintService --> SprintRepository : depends on
+SprintService --> TaskRepository : depends on
+SprintService --> ProjectService : uses
+SprintService --> UserService : uses
+SprintService --> AuditLogService : uses
+SprintService --> SprintMapper : depends on
+TaskActivityService --> TaskActivityRepository : depends on
+TaskActivityService --> TaskService : uses
+TaskActivityService --> ProjectService : uses
+TaskActivityService --> UserService : uses
+TaskActivityService --> TaskActivityMapper : depends on
+TaskService --> TaskRepository : depends on
+TaskService --> TaskActivityRepository : depends on
+TaskService --> SprintRepository : depends on
+TaskService --> ProjectService : uses
+TaskService --> UserService : uses
+TaskService --> AuditLogService : uses
+TaskService --> NotificationService : uses
+TaskService --> TaskMapper : depends on
+UserService --> UserRepository : depends on
+UserService --> UserMapper : depends on
+@enduml