#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Add JavaDoc to impl package public methods. Documentation-only; no logic changes.""" import os import re import sys from pathlib import Path ROOT = Path(r"d:/ylrz_saas_new/java") IMPL_DIR = ROOT / "fs-service/src/main/java/com/fs/company/service/workflow/impl" WORKFLOW_DIR = ROOT / "fs-service/src/main/java/com/fs/company/service/workflow" CLASS_DESC = { "LobsterEvolutionEngineImpl": "��Ϻ��������ʵ�֣����ֶԻ�����������װ�� AI ���ɡ��������֡��Ϲ������дʹ��˵�������ˮ�ߡ�", "LobsterWorkflowExecutorImpl": "��Ϻ������ִ����ʵ�֣�ʵ���������ڹ������ڵ��ƽ�����ϢͶ����������漯�ɡ�", "DynamicNodeExecutorImpl": "��̬�ڵ�ִ����ʵ�֣����ڵ�����ע�� handler��ִ��ö�ٽڵ��� AI �����߼���", "ContextAssemblerImpl": "��������װ��ʵ�֣����м��ػ���֪ʶ�⡢��ʷ�������ȶ�Դ�����Ĺ� AI ʹ�á�", "SummaryGeneratorImpl": "�Ի�ժҪ������ʵ�֣�ʵ��/ȫ��ժҪ��������ȡ���첽ժҪ���¡�", "LobsterE2eTestServiceImpl": "��Ϻ�˵��˲��Է���ʵ�֣������� E2E �����������ƽ�����ֶԻ����ԡ�", "VariableStoreImpl": "�����������洢ʵ�֣��ڴ� + Redis + ���ݿ�������д����ʵ������ʱ�����־û���", "MultiTurnDialogueManagerImpl": "���ֶԻ�������ʵ�֣�������Ϣ�ռ��ڵ���ִο��ơ�������ȡ������ж���", "QualityScoringServiceImpl": "�������ַ���ʵ�֣���ά�� AI �ظ����֡���������У��������������ɡ�", "SensitiveWordServiceImpl": "���дʹ��˷���ʵ�֣��⻧�����дʼ�⡢�滻��߷��մʴ��á�", "ComplianceServiceImpl": "�Ϲ������ʵ�֣�AI �ظ��Ϲ���У����ּ���ȫ�����滻��", "ConditionEvaluatorImpl": "��������ʽ��ֵ��ʵ�֣���������֧�ڵ�������жϡ�", "DynamicNodeAdjusterImpl": "��̬�ڵ������ʵ�֣��������������·�ɾ�����ת�˹��ж���", "DynamicNodeImplServiceImpl": "��̬�ڵ�ʵ�ֹ������񣺽ڵ� handler ע�������� CRUD��", "DuplicateReplyDetector": "�ظ��ظ����������ֹ��������ͬһ�ͻ��ظ�������ͬ���ݡ�", "FactDecayScheduler": "��ʵ˥����ʱ�������������û���ʵ�빵ͨϰ�߻��档", "LobsterBillingServiceImpl": "��Ϻ�Ʒѷ���ʵ�֣��⻧��Token ���ļ�¼��ͳ�ƻ��ܡ�", "LobsterConversationSummaryServiceImpl": "�Ի�ժҪ��ѯ����ʵ�֣�������ժҪ�б������顣", "LobsterEventAuditServiceImpl": "�¼���Ʒ���ʵ�֣�����/ѧϰ�¼����˹������ת��", "LobsterEvolutionSuggestionServiceImpl": "�����������ʵ�֣�AI ���������б��������ָ��ͳ�ơ�", "LobsterInstanceStatsServiceImpl": "ʵ��ͳ�Ʒ���ʵ�֣�������ʵ������ָ����ܡ�", "LobsterIntegrationTestServiceImpl": "���ɲ��Է���ʵ�֣��������ð�̲�����ڵ�������֤��", "LobsterLearningCorpusServiceImpl": "ѧϰ���Ϸ���ʵ�֣��⻧ѧϰ���ϲ�ѯ��", "LobsterModelConfigServiceImpl": "ģ�����÷���ʵ�֣��⻧�� LLM ·�����������ģ�����á�", "LobsterNodeTypeServiceImpl": "�ڵ�����ע�����ʵ�֣��������ڵ�����Ԫ���ݻ����� CRUD��", "LobsterPromptServiceImpl": "ϵͳ Prompt ��������ʵ�֣����� Prompt ��ϵͳ Prompt CRUD��", "LobsterSalesCorpusServiceImpl": "�������Ϸ���ʵ�֣����ڶԻ����롢�����볡����ѯ��", "LobsterTestScenarioServiceImpl": "���Գ�������ʵ�֣�E2E ���Գ��� CRUD �붨ʱ������", "MultiModelWorkflowGeneratorImpl": "��ģ�͹�����������ʵ�֣�AI �Զ�����/�����Ż������� JSON��", "NodePromptServiceImpl": "�ڵ� Prompt �󶨷���ʵ�֣��ڵ��� Prompt ģ��Ĺ������档", "OutputQualityCheckerImpl": "������������ʵ�֣��������ݵĻ�������У���������", "PendingAuditKnowledgeServiceImpl": "�����֪ʶ����ʵ�֣��������ʴ������������֪ʶ�⡣", "ProfileEnrichmentService": "�û����񸻻����񣺻�����Ϊ�����첽���¿ͻ�����", "PromptManagerImpl": "Prompt ������ʵ�֣��ڵ�/ȫ�� Prompt ��ȡ��־û���", "QualityScoreRuleEngine": "�������ֹ������棺��̬����У�������ⳡ���ӷ���Ϲ�����", "ReplaceWordServiceImpl": "Υ����/�״����滻����ʵ�֣����⻧�����滻�ʱ���Ӧ�á�", "SemanticTakeoverDetectorImpl": "����ӹܼ����ʵ�֣����ͻ�Ҫ��ת�˹��� AI ����ת����ͼ��", "ToolCallFrameworkImpl": "���ߵ��ÿ��ʵ�֣����� AI ���ߵ��ò�ִ�ж���/SMS �����ù��ߡ�", "VariableSubstitutionEngineImpl": "�����滻����ʵ�֣�ģ�� ${var} ռλ���滻�������ȡ��", "WorkflowTemplateAdminServiceImpl": "������ģ���������ʵ�֣�ģ���б����ڵ㱣���뷢����ѯ��", } METHOD_CN = { "init": "��ʼ�������ע�� handler�����ػ���ȣ���", "refreshCache": "ˢ�±���/Redis ���档", "evolve": "ִ�е��ֶԻ�������ˮ�ߣ����� AI �ظ�����һ�ڵ㡣", "enrichWithDynamicNode": "AI ��̬���ɲ�������Ի��������ڵ㡣", "learnCustomerHabit": "�ӱ��ֽ���ѧϰ�ͻ���ͨϰ�߲�д�� habit ����", "startWorkflow": "�����µĹ�����ʵ����ִ���׽ڵ㡣", "executeNextNode": "���տͻ��ظ����ƽ�����������һ�ڵ㡣", "pauseWorkflow": "��ͣ������ʵ��ִ�С�", "resumeWorkflow": "�ָ�����ͣ�Ĺ�����ʵ����", "terminateWorkflow": "��ֹ������ʵ������¼ԭ��", "getInstanceState": "��ѯ������ʵ����ǰ״̬��������ա�", "simulateExecution": "ģ��ִ�й���������ʵ�ʷ���Ϣ�������ڵ��ԡ�", "deliverProactiveMessage": "������ͻ�Ͷ�ݹ�������Ϣ������/���ѣ���", "execute": "ִ��ָ�����͵Ķ�̬�ڵ� handler��", "supports": "�жϽڵ������Ƿ���ע�� handler��", "registerHandler": "ע���Զ���ڵ����� handler��", "getRegisteredHandlers": "������ע���ȫ���ڵ� handler ӳ�䡣", "assembleContext": "��װ�����������ı���token ���ޣ���", "assembleForNode": "Ϊָ���ڵ�������װ�����ġ�", "getContextVariables": "��ȡʵ�������ı��� Map��", "assembleFullContext": "���м��ض�Դ�����IJ���װ���� Map��", "buildAiPrompt": "�������� Map ��ʽ��Ϊ AI Prompt �ı���", "formatKnowledgeForScoring": "��ʽ��֪ʶ��Ƭ�ι���������ʹ�á�", "generateSummary": "���ڶԻ���ʷ����ժҪ�ı���", "extractKeyPoints": "�ӶԻ���ʷ��ȡ�ؼ�Ҫ�㡣", "analyzeSentiment": "����������Ϣ�������", "generateSummaryAsync": "�첽����ʵ��ժҪ���£�����ֵ����", "forceGenerateSummary": "ǿ���������ɲ��־û�ʵ��ժҪ��", "forceGenerateSummaryFromHistory": "���ڸ�����ʷ�ı�ǿ������ժҪ��", "doGenerateSummary": "ִ��ժҪ���ɵĺ����߼���ͬ������", "getCachedSummary": "��ȡ�ѻ�����û�/ʵ��ժҪ��", "updateUserProfile": "��ժҪ��Ϣ�����û������ֶΡ�", "extractConversationVariables": "�ӿͻ���Ϣ�� LLM ��ȡ�ṹ��������", "generateMicroSummary": "������� N �ֶԻ���΢ժҪ��", "generateGlobalSummary": "���ɿ�Ự��ȫ���û�ժҪ��", "runE2e": "�������� E2E ���Բ����ر��档", "getReport": "�� runId ��ѯ E2E ���Ա��档", "stepNext": "E2E �����ƽ���ע���û����벢ִ����һ�ڵ㡣", "multiTurn": "E2E ���ֶԻ����ԣ�����ע������û����롣", "listRuns": "��ҳ�г� E2E �������м�¼��", "setVariable": "���õ���ʵ���������ڴ�+Redis+DB����", "setVariables": "��������ʵ��������", "getVariable": "��ȡ����ʵ��������", "getAllVariables": "��ȡʵ��ȫ��������", "removeVariable": "ɾ������ʵ��������", "clearVariables": "���ʵ��ȫ��������", "persistToDatabase": "���ڴ�����־û������ݿ⡣", "loadFromDatabase": "�����ݿ���ر������ڴ滺�档", "processDialogue": "�������ֶԻ��ڵ��һ�ֽ�����", "isMultiTurnNode": "�жϽڵ��Ƿ�����Ϊ���ֶԻ���", "isDialogueComplete": "�ж϶��ֶԻ��Ƿ�����ɡ�", "getDialogueRound": "��ȡ��ǰ���ֶԻ��ִΡ�", "resetDialogue": "���ö��ֶԻ�״̬��", "scoreWithRetry": "�������ֲ��ڲ����ʱ�����������ɡ�", "score": "�� AI �ظ����ж�ά���������֡�", "getImprovementHint": "���ݵ�ǰ�÷����ɸĽ���ʾ��", "getNodeWarmupHint": "��ȡ�ڵ㼶����Ԥ����ʾ�ʡ�", "filterSensitiveWords": "�����ı��е����дʲ��滻��", "containsSensitiveWord": "����ı��Ƿ�������дʡ�", "detectSensitiveWords": "��Ⲣ�������д���ϸ�б���", "isHighRiskSensitiveWord": "�ж��Ƿ񺬸߷������дʣ�����/����/թƭ����", "addSensitiveWord": "�����⻧���дʲ�ˢ�»��档", "removeSensitiveWord": "�������дʲ�ˢ�»��档", "getSensitiveWordList": "��ѯ�⻧���õ����д��б���", "substitute": "��ģ���еı���ռλ���滻Ϊʵ��ֵ��", "extractVariables": "���������г�ȡ������ֵ��", "checkAiReplyCompliance": "У�� AI �ظ��Ϲ��Բ����طּ������", "adjustNode": "��������������ڽڵ�·���������", "getPrompt": "��ȡ�ڵ�/ȫ�� Prompt ���ݡ�", "getSystemPrompt": "��ȡ�ڵ�ϵͳ Prompt��", "getInstructionPrompt": "��ȡ�ڵ�ָ�� Prompt��", "getFallbackPrompt": "��ȡ�ڵ㶵�� Prompt��", "hasConfiguredNodePrompt": "�жϽڵ��Ƿ�������ָ������ Prompt��", "saveNodePrompt": "����ڵ� Prompt �����ݿ⡣", "saveGlobalPrompt": "����ȫ�� Prompt �����ݿ⡣", "listPrompts": "�г��⻧ Prompt ���á�", "applyReplace": "���ı�Ӧ��Υ�����������״��ʾ�����", "listWords": "��ҳ��ѯ�滻���б���", "saveWord": "�����滻�����á�", "deleteWord": "ɾ���滻�ʡ�", "executeTool": "ִ��ָ�����ƵĹ��ߵ��á�", "extractToolCall": "�� AI �ظ� JSON �н������ߵ��á�", "isToolAvailable": "�жϹ��߶��⻧�Ƿ���á�", "getAvailableTools": "�г��⻧���ù��ߡ�", "registerTool": "ע���Զ��幤�ߡ�", "recordExecLog": "��¼����ִ����־��", "queryOrder": "��ѯ�������飨���ù��ߣ���", "queryUserOrders": "��ѯ�û������б������ù��ߣ���", "sendSms": "���Ͷ��ţ����ù��ߣ���", "detectTakeover": "���ͻ��Ƿ�Ҫ��ת�˹��ӹܡ�", "detectAITransfer": "��� AI �Ƿ�Ӧ����ת���˹���", "checkQuality": "�����������������", "checkAndCorrect": "����������ڱ�Ҫʱ�������ݡ�", "cleanupExpiredFacts": "��ʱ����������ʵ��ϰ�����ݡ�", "isDuplicate": "���ظ��Ƿ�����ڷ��������ظ���", "enrichProfile": "�첽�����û�����", "repairJson": "�޸� LLM ���صĻ��� JSON �ַ�����", } # Build caller index once def build_caller_index(): index = {} java_files = list(ROOT.rglob("*.java")) for jf in java_files: if "impl" in jf.parts and jf.parent.name == "impl" and jf.parent.parent.name == "workflow": continue # skip self for class name search try: text = jf.read_text(encoding="utf-8", errors="replace") except Exception: continue for m in re.finditer(r'\b([A-Z][a-zA-Z0-9]+(?:Service|Executor|Engine|Manager|Store|Assembler|Generator|Framework|Detector|Evaluator|Adjuster|Scheduler|Checker|Router)?)\b', text): name = m.group(1) if name.endswith("Impl"): iface = name[:-4] index.setdefault(iface, set()).add(jf.stem) index.setdefault(name, set()).add(jf.stem) # Also scan for interface usage patterns like "VariableStore variableStore" for jf in java_files: try: text = jf.read_text(encoding="utf-8", errors="replace") except Exception: continue for m in re.finditer(r'\b(Lobster[A-Za-z]+|ContextAssembler|VariableStore|SummaryGenerator|QualityScoringService|SensitiveWordService|DynamicNodeExecutor|MultiTurnDialogueManager|PromptManager|ComplianceService|ToolCallFramework|ConditionEvaluator|DynamicNodeAdjuster|ReplaceWordService|OutputQualityChecker|SemanticTakeoverDetector|VariableSubstitutionEngine|MultiModelWorkflowGenerator|PendingAuditKnowledgeService|I[A-Z][a-zA-Z]+Service)\s+\w+', text): iface = m.group(1) if jf.stem not in {iface, iface + "Impl"}: index.setdefault(iface, set()).add(jf.stem) return index CALLER_INDEX = None def get_callers(class_name, method_name=None): global CALLER_INDEX if CALLER_INDEX is None: CALLER_INDEX = build_caller_index() iface = class_name.replace("Impl", "") if class_name == "ProfileEnrichmentService": iface = class_name callers = set() for key in (class_name, iface, "I" + iface if not iface.startswith("I") else iface): callers.update(CALLER_INDEX.get(key, set())) callers.discard(class_name) # Filter impl siblings unless they're real callers filtered = sorted(c for c in callers if not c.endswith("Impl") or c in { "LobsterWorkflowExecutorImpl", "LobsterEvolutionEngineImpl", "DynamicNodeExecutorImpl", "ContextAssemblerImpl", "MultiTurnDialogueManagerImpl", "LobsterE2eTestServiceImpl", "LobsterTravelValidationServiceImpl", "LobsterIntegrationTestServiceImpl", }) if not filtered: filtered = sorted(callers)[:6] return filtered[:8] def parse_interface_methods(class_name): iface = class_name.replace("Impl", "") if class_name.startswith("I") and class_name.endswith("ServiceImpl"): iface = class_name # ILobsterXServiceImpl -> ILobsterXService iface_file = WORKFLOW_DIR / (iface.replace("Impl", "") + ".java") else: candidates = [ WORKFLOW_DIR / (iface + ".java"), WORKFLOW_DIR / ("I" + iface + ".java"), ] iface_file = next((c for c in candidates if c.exists()), None) methods = {} if iface_file and iface_file.exists(): text = iface_file.read_text(encoding="utf-8", errors="replace") # Match javadoc + method signature pattern = re.compile( r'/\*\*(.*?)\*/\s*(?:@[\w.]+\s*)*([\w<>,\[\]\s.?]+?\s+(\w+)\s*\([^)]*\)\s*;)', re.DOTALL, ) for m in pattern.finditer(text): doc = m.group(1).strip() method = m.group(3) # Extract first line of javadoc as purpose first_line = re.sub(r'^\s*\*\s?', '', doc.split('\n')[0]).strip() if first_line: methods[method] = first_line return methods def has_javadoc(lines, idx): """Check if line at idx already has javadoc immediately above (skipping annotations).""" j = idx - 1 while j >= 0 and lines[j].strip().startswith("@"): j -= 1 if j >= 0 and lines[j].strip().endswith("*/"): return True if j >= 0 and lines[j].strip().startswith("/**"): return True return False def method_purpose(class_name, method_name, iface_methods): if method_name in iface_methods: return iface_methods[method_name] if method_name in METHOD_CN: return METHOD_CN[method_name] if method_name.startswith("get") and method_name != "get": return f"��ȡ{method_name[3:]}��" if method_name.startswith("set") and method_name != "set": return f"����{method_name[3:]}��" if method_name.startswith("is") and method_name != "is": return f"�ж��Ƿ�{method_name[2:]}��" if method_name.startswith("list"): return f"��ѯ{method_name[4:]}�б���" if method_name.startswith("save") or method_name.startswith("create"): return f"����/����{method_name}������ݡ�" if method_name.startswith("delete") or method_name.startswith("remove"): return f"ɾ��{method_name}������ݡ�" if method_name.startswith("update"): return f"����{method_name[6:]}��" if method_name.startswith("batch"): return f"����{method_name[5:]}��" if method_name.startswith("run") or method_name.startswith("test"): return f"����{method_name}���Ի�����" return f"{method_name} �Ĺ�����ڡ�" def format_callers(callers): if not callers: return "Spring ����ע�����" return "��".join(f"{{@code {c}}}" for c in callers) def make_method_javadoc(class_name, method_name, iface_methods): purpose = method_purpose(class_name, method_name, iface_methods) callers = get_callers(class_name, method_name) lines = [ " /**", f" * {purpose}", f" *

���÷���{format_callers(callers)}��", " */", ] return lines def make_class_javadoc(class_name, implements=None): desc = CLASS_DESC.get(class_name, f"{class_name}��workflow impl �� Spring ����ʵ�֡�") callers = get_callers(class_name) lines = [ "/**", f" * {desc}", f" *

��Ҫ���÷���{format_callers(callers)}��", " */", ] return lines def process_file(path: Path): text = path.read_text(encoding="utf-8") class_name = path.stem lines = text.splitlines(keepends=True) iface_methods = parse_interface_methods(class_name) # Find class declaration class_idx = None for i, line in enumerate(lines): if re.match(r'public\s+(?:final\s+)?class\s+' + re.escape(class_name), line): class_idx = i break if class_idx is None: return False modified = False new_lines = [] # Check class-level javadoc has_class_doc = False if class_idx > 0: j = class_idx - 1 while j >= 0 and (lines[j].strip().startswith("@") or lines[j].strip() == ""): j -= 1 if j >= 0 and (lines[j].strip().startswith("/**") or lines[j].strip().endswith("*/")): has_class_doc = True i = 0 while i < len(lines): line = lines[i] stripped = line.strip() # Insert class javadoc before class if missing if i == class_idx and not has_class_doc: doc = make_class_javadoc(class_name) for dl in doc: new_lines.append(dl + "\n") modified = True # Detect public methods (including @Override public) pub_match = re.match(r'^(\s*)(@Override\s+)?public\s+(?!class\b|interface\b|enum\b|static\s+class\b)', stripped) if pub_match and not has_javadoc(lines, i): indent = pub_match.group(1) # Extract method name sig = stripped # combine with next line if signature spans lines combined = sig k = i while ";" not in combined and "{" not in combined and k + 1 < len(lines): k += 1 combined += " " + lines[k].strip() mm = re.search(r'\b(\w+)\s*\(', combined) if mm: method_name = mm.group(1) if method_name not in ("if", "for", "while", "switch", "catch", "synchronized"): doc_lines = make_method_javadoc(class_name, method_name, iface_methods) # Adjust indent for dl in doc_lines: new_lines.append(indent + dl.strip() + "\n") modified = True new_lines.append(line) i += 1 if modified: path.write_text("".join(new_lines), encoding="utf-8") return modified def main(): modified_files = [] for jf in sorted(IMPL_DIR.glob("*.java")): if process_file(jf): modified_files.append(str(jf)) print(f"MODIFIED: {jf.name}") else: print(f"SKIP (no changes): {jf.name}") print(f"\nTotal modified: {len(modified_files)}") return modified_files if __name__ == "__main__": main()