audit trail detail, dupes detector
This commit is contained in:
		
							parent
							
								
									e63ec367a5
								
							
						
					
					
						commit
						07c8f707df
					
				
							
								
								
									
										333
									
								
								find-duplicates.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								find-duplicates.mjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,333 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env node
 | 
				
			||||||
 | 
					// find-duplicate-functions.mjs
 | 
				
			||||||
 | 
					// Usage:
 | 
				
			||||||
 | 
					//   node find-duplicate-functions.mjs [rootDir] [--mode exact|struct] [--min-lines N] [--json]
 | 
				
			||||||
 | 
					// Example:
 | 
				
			||||||
 | 
					//   node find-duplicate-functions.mjs . --mode struct --min-lines 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import fs from "fs";
 | 
				
			||||||
 | 
					import path from "path";
 | 
				
			||||||
 | 
					import * as url from "url";
 | 
				
			||||||
 | 
					import ts from "typescript";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** -------- CLI OPTIONS -------- */
 | 
				
			||||||
 | 
					const args = process.argv.slice(2);
 | 
				
			||||||
 | 
					let rootDir = ".";
 | 
				
			||||||
 | 
					let mode = "struct"; // "exact" | "struct"
 | 
				
			||||||
 | 
					let minLines = 3;
 | 
				
			||||||
 | 
					let outputJson = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for (let i = 0; i < args.length; i++) {
 | 
				
			||||||
 | 
					  const a = args[i];
 | 
				
			||||||
 | 
					  if (!a.startsWith("--") && rootDir === ".") {
 | 
				
			||||||
 | 
					    rootDir = a;
 | 
				
			||||||
 | 
					  } else if (a === "--mode") {
 | 
				
			||||||
 | 
					    mode = (args[++i] || "struct").toLowerCase();
 | 
				
			||||||
 | 
					    if (!["exact", "struct"].includes(mode)) {
 | 
				
			||||||
 | 
					      console.error("Invalid --mode. Use 'exact' or 'struct'.");
 | 
				
			||||||
 | 
					      process.exit(1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else if (a === "--min-lines") {
 | 
				
			||||||
 | 
					    minLines = parseInt(args[++i] || "3", 10);
 | 
				
			||||||
 | 
					  } else if (a === "--json") {
 | 
				
			||||||
 | 
					    outputJson = true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** -------- FILE DISCOVERY -------- */
 | 
				
			||||||
 | 
					const DEFAULT_IGNORES = new Set([
 | 
				
			||||||
 | 
					  "node_modules",
 | 
				
			||||||
 | 
					  ".git",
 | 
				
			||||||
 | 
					  ".next",
 | 
				
			||||||
 | 
					  ".vercel",
 | 
				
			||||||
 | 
					  "dist",
 | 
				
			||||||
 | 
					  "build",
 | 
				
			||||||
 | 
					  ".astro", // Astro's generated cache dir
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const VALID_EXTS = new Set([".ts", ".tsx", ".astro", ".mts", ".cts"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function walk(dir) {
 | 
				
			||||||
 | 
					  /** @type {string[]} */
 | 
				
			||||||
 | 
					  const out = [];
 | 
				
			||||||
 | 
					  const entries = fs.readdirSync(dir, { withFileTypes: true });
 | 
				
			||||||
 | 
					  for (const e of entries) {
 | 
				
			||||||
 | 
					    const p = path.join(dir, e.name);
 | 
				
			||||||
 | 
					    if (e.isDirectory()) {
 | 
				
			||||||
 | 
					      if (DEFAULT_IGNORES.has(e.name)) continue;
 | 
				
			||||||
 | 
					      out.push(...walk(p));
 | 
				
			||||||
 | 
					    } else if (e.isFile() && VALID_EXTS.has(path.extname(e.name))) {
 | 
				
			||||||
 | 
					      out.push(p);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** -------- ASTRO CODE EXTRACTION --------
 | 
				
			||||||
 | 
					 * Extract TS/JS code from:
 | 
				
			||||||
 | 
					 * - frontmatter: --- ... ---
 | 
				
			||||||
 | 
					 * - <script ...> ... </script>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function extractCodeFromAstro(source) {
 | 
				
			||||||
 | 
					  /** @type {{code:string, offset:number}[]} */
 | 
				
			||||||
 | 
					  const blocks = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Frontmatter (must be at top in Astro)
 | 
				
			||||||
 | 
					  // Match the FIRST pair of --- ... ---
 | 
				
			||||||
 | 
					  const fm = source.startsWith("---")
 | 
				
			||||||
 | 
					    ? (() => {
 | 
				
			||||||
 | 
					        const end = source.indexOf("\n---", 3);
 | 
				
			||||||
 | 
					        if (end !== -1) {
 | 
				
			||||||
 | 
					          const front = source.slice(3, end + 1); // include trailing \n
 | 
				
			||||||
 | 
					          return { start: 0, end: end + 4, code: front };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					      })()
 | 
				
			||||||
 | 
					    : null;
 | 
				
			||||||
 | 
					  if (fm) {
 | 
				
			||||||
 | 
					    // offset for line numbers is after the first '---\n'
 | 
				
			||||||
 | 
					    blocks.push({ code: fm.code, offset: 4 }); // rough; we’ll fix line numbers via positions later
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // <script ...> ... </script>
 | 
				
			||||||
 | 
					  const scriptRe = /<script\b[^>]*>([\s\S]*?)<\/script>/gi;
 | 
				
			||||||
 | 
					  let m;
 | 
				
			||||||
 | 
					  while ((m = scriptRe.exec(source))) {
 | 
				
			||||||
 | 
					    const code = m[1] || "";
 | 
				
			||||||
 | 
					    blocks.push({ code, offset: indexToLine(source, m.index) });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return blocks;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** -------- UTIL: index -> 1-based line -------- */
 | 
				
			||||||
 | 
					function indexToLine(text, idx) {
 | 
				
			||||||
 | 
					  let line = 1;
 | 
				
			||||||
 | 
					  for (let i = 0; i < idx && i < text.length; i++) {
 | 
				
			||||||
 | 
					    if (text.charCodeAt(i) === 10) line++;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return line;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** -------- AST HELPERS -------- */
 | 
				
			||||||
 | 
					function createSourceFile(virtualPath, code) {
 | 
				
			||||||
 | 
					  return ts.createSourceFile(
 | 
				
			||||||
 | 
					    virtualPath,
 | 
				
			||||||
 | 
					    code,
 | 
				
			||||||
 | 
					    ts.ScriptTarget.Latest,
 | 
				
			||||||
 | 
					    /*setParentNodes*/ true,
 | 
				
			||||||
 | 
					    virtualPath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Normalize AST to a structural signature string
 | 
				
			||||||
 | 
					function structuralSignature(node) {
 | 
				
			||||||
 | 
					  /** @type {string[]} */
 | 
				
			||||||
 | 
					  const parts = [];
 | 
				
			||||||
 | 
					  const visit = (n) => {
 | 
				
			||||||
 | 
					    // Skip trivia: comments/whitespace are already not in AST
 | 
				
			||||||
 | 
					    const kindName = ts.SyntaxKind[n.kind] || `K${n.kind}`;
 | 
				
			||||||
 | 
					    switch (n.kind) {
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.Identifier:
 | 
				
			||||||
 | 
					        parts.push("Id");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.PrivateIdentifier:
 | 
				
			||||||
 | 
					        parts.push("PrivId");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.StringLiteral:
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.TemplateHead:
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.TemplateMiddle:
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.TemplateTail:
 | 
				
			||||||
 | 
					        parts.push("Str");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.NumericLiteral:
 | 
				
			||||||
 | 
					        parts.push("Num");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.TrueKeyword:
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.FalseKeyword:
 | 
				
			||||||
 | 
					        parts.push("Bool");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.NullKeyword:
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.UndefinedKeyword:
 | 
				
			||||||
 | 
					        parts.push("Nil");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.PropertyAssignment:
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.ShorthandPropertyAssignment:
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.MethodDeclaration:
 | 
				
			||||||
 | 
					      case ts.SyntaxKind.MethodSignature:
 | 
				
			||||||
 | 
					        parts.push("Prop");
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        parts.push(kindName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    n.forEachChild(visit);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  visit(node);
 | 
				
			||||||
 | 
					  return parts.join("|");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getFunctionInfo(sf, filePath) {
 | 
				
			||||||
 | 
					  /** @type {Array<{
 | 
				
			||||||
 | 
					    name: string,
 | 
				
			||||||
 | 
					    bodyText: string,
 | 
				
			||||||
 | 
					    structKey: string,
 | 
				
			||||||
 | 
					    start: number,
 | 
				
			||||||
 | 
					    end: number,
 | 
				
			||||||
 | 
					    startLine: number,
 | 
				
			||||||
 | 
					    endLine: number
 | 
				
			||||||
 | 
					  }>} */
 | 
				
			||||||
 | 
					  const out = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const addFunc = (nameNode, bodyNode) => {
 | 
				
			||||||
 | 
					    if (!bodyNode) return;
 | 
				
			||||||
 | 
					    const bodyText = bodyNode.getText(sf).trim();
 | 
				
			||||||
 | 
					    const start = bodyNode.getStart(sf);
 | 
				
			||||||
 | 
					    const end = bodyNode.getEnd();
 | 
				
			||||||
 | 
					    const { line: startLine } = sf.getLineAndCharacterOfPosition(start);
 | 
				
			||||||
 | 
					    const { line: endLine } = sf.getLineAndCharacterOfPosition(end);
 | 
				
			||||||
 | 
					    const name =
 | 
				
			||||||
 | 
					      nameNode && ts.isIdentifier(nameNode) ? nameNode.escapedText.toString() : "(anonymous)";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // min-lines filter
 | 
				
			||||||
 | 
					    const lines = bodyText.split(/\r?\n/).filter(Boolean);
 | 
				
			||||||
 | 
					    if (lines.length < minLines) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // structural signature from the body
 | 
				
			||||||
 | 
					    const structKey = structuralSignature(bodyNode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    out.push({
 | 
				
			||||||
 | 
					      name,
 | 
				
			||||||
 | 
					      bodyText,
 | 
				
			||||||
 | 
					      structKey,
 | 
				
			||||||
 | 
					      start,
 | 
				
			||||||
 | 
					      end,
 | 
				
			||||||
 | 
					      startLine: startLine + 1,
 | 
				
			||||||
 | 
					      endLine: endLine + 1,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const visit = (node) => {
 | 
				
			||||||
 | 
					    if (ts.isFunctionDeclaration(node) && node.body) {
 | 
				
			||||||
 | 
					      addFunc(node.name ?? null, node.body);
 | 
				
			||||||
 | 
					    } else if (
 | 
				
			||||||
 | 
					      ts.isFunctionExpression(node) ||
 | 
				
			||||||
 | 
					      ts.isArrowFunction(node)
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      // find name if it’s assigned: const foo = () => {}
 | 
				
			||||||
 | 
					      let name = null;
 | 
				
			||||||
 | 
					      if (node.parent && ts.isVariableDeclaration(node.parent) && node.parent.name) {
 | 
				
			||||||
 | 
					        name = node.parent.name;
 | 
				
			||||||
 | 
					      } else if (
 | 
				
			||||||
 | 
					        node.parent &&
 | 
				
			||||||
 | 
					        ts.isPropertyAssignment(node.parent) &&
 | 
				
			||||||
 | 
					        ts.isIdentifier(node.parent.name)
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        name = node.parent.name;
 | 
				
			||||||
 | 
					      } else if (node.name) {
 | 
				
			||||||
 | 
					        name = node.name;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (node.body) addFunc(name, node.body);
 | 
				
			||||||
 | 
					    } else if (ts.isMethodDeclaration(node) && node.body) {
 | 
				
			||||||
 | 
					      addFunc(node.name, node.body);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    node.forEachChild(visit);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  visit(sf);
 | 
				
			||||||
 | 
					  return out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** -------- MAIN SCAN -------- */
 | 
				
			||||||
 | 
					const files = walk(path.resolve(process.cwd(), rootDir));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Maps from hash -> occurrences */
 | 
				
			||||||
 | 
					const groups = new Map();
 | 
				
			||||||
 | 
					/** Helper for exact hash */
 | 
				
			||||||
 | 
					import crypto from "crypto";
 | 
				
			||||||
 | 
					const exactHash = (text) => crypto.createHash("sha1").update(text.replace(/\s+/g, " ").trim()).digest("hex");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for (const file of files) {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const ext = path.extname(file).toLowerCase();
 | 
				
			||||||
 | 
					    const raw = fs.readFileSync(file, "utf8");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @type {Array<{virtualPath:string, code:string, lineOffset:number}>} */
 | 
				
			||||||
 | 
					    const codeUnits = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ext === ".astro") {
 | 
				
			||||||
 | 
					      const blocks = extractCodeFromAstro(raw);
 | 
				
			||||||
 | 
					      blocks.forEach((b, i) => {
 | 
				
			||||||
 | 
					        codeUnits.push({
 | 
				
			||||||
 | 
					          virtualPath: file + `#astro${i + 1}.ts`,
 | 
				
			||||||
 | 
					          code: b.code,
 | 
				
			||||||
 | 
					          lineOffset: b.offset || 1,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      codeUnits.push({ virtualPath: file, code: raw, lineOffset: 1 });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const { virtualPath, code, lineOffset } of codeUnits) {
 | 
				
			||||||
 | 
					      const sf = createSourceFile(virtualPath, code);
 | 
				
			||||||
 | 
					      const funcs = getFunctionInfo(sf, file);
 | 
				
			||||||
 | 
					      for (const f of funcs) {
 | 
				
			||||||
 | 
					        const key =
 | 
				
			||||||
 | 
					          mode === "exact" ? exactHash(f.bodyText) : crypto.createHash("sha1").update(f.structKey).digest("hex");
 | 
				
			||||||
 | 
					        const item = {
 | 
				
			||||||
 | 
					          file,
 | 
				
			||||||
 | 
					          where:
 | 
				
			||||||
 | 
					            ext === ".astro"
 | 
				
			||||||
 | 
					              ? `${path.relative(process.cwd(), file)}:${f.startLine + lineOffset - 1}-${f.endLine + lineOffset - 1}`
 | 
				
			||||||
 | 
					              : `${path.relative(process.cwd(), file)}:${f.startLine}-${f.endLine}`,
 | 
				
			||||||
 | 
					          name: f.name,
 | 
				
			||||||
 | 
					          lines: f.endLine - f.startLine + 1,
 | 
				
			||||||
 | 
					          preview: f.bodyText.split(/\r?\n/).slice(0, 5).join("\n") + (f.endLine - f.startLine + 1 > 5 ? "\n..." : ""),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        if (!groups.has(key)) groups.set(key, []);
 | 
				
			||||||
 | 
					        groups.get(key).push(item);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    console.warn(`⚠️ Skipping ${file}: ${e.message}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** -------- REPORT -------- */
 | 
				
			||||||
 | 
					const dupes = [...groups.entries()]
 | 
				
			||||||
 | 
					  .map(([key, arr]) => ({ key, items: arr }))
 | 
				
			||||||
 | 
					  .filter((g) => g.items.length > 1)
 | 
				
			||||||
 | 
					  .sort((a, b) => b.items.length - a.items.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (outputJson) {
 | 
				
			||||||
 | 
					  console.log(JSON.stringify({ mode, minLines, groups: dupes }, null, 2));
 | 
				
			||||||
 | 
					  process.exit(0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (dupes.length === 0) {
 | 
				
			||||||
 | 
					  console.log(`✅ No duplicate functions found (mode=${mode}, min-lines=${minLines}).`);
 | 
				
			||||||
 | 
					  process.exit(0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log(`\nFound ${dupes.length} duplicate group(s) (mode=${mode}, min-lines=${minLines}):\n`);
 | 
				
			||||||
 | 
					dupes.forEach((g, i) => {
 | 
				
			||||||
 | 
					  console.log(`== Group ${i + 1} (${g.items.length} matches) ==`);
 | 
				
			||||||
 | 
					  const example = g.items[0];
 | 
				
			||||||
 | 
					  console.log(`   Sample (${example.lines} lines) from ${example.where}${example.name ? ` [${example.name}]` : ""}`);
 | 
				
			||||||
 | 
					  console.log("   ---");
 | 
				
			||||||
 | 
					  console.log(indent(example.preview, "   "));
 | 
				
			||||||
 | 
					  console.log("   ---");
 | 
				
			||||||
 | 
					  g.items.forEach((it) => {
 | 
				
			||||||
 | 
					    console.log(` • ${it.where}${it.name ? ` [${it.name}]` : ""} (${it.lines} lines)`);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  console.log();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function indent(s, pre) {
 | 
				
			||||||
 | 
					  return s
 | 
				
			||||||
 | 
					    .split("\n")
 | 
				
			||||||
 | 
					    .map((l) => pre + l)
 | 
				
			||||||
 | 
					    .join("\n");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1247,99 +1247,6 @@ class AIQueryInterface {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createSpecificSummary(data, action, type) {
 | 
					 | 
				
			||||||
    if (!data) return 'Leer';
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // Action-specific summaries that provide meaningful information
 | 
					 | 
				
			||||||
    switch (action) {
 | 
					 | 
				
			||||||
      case 'selection-decision':
 | 
					 | 
				
			||||||
        if (type === 'input') {
 | 
					 | 
				
			||||||
          if (data.availableTools && Array.isArray(data.availableTools)) {
 | 
					 | 
				
			||||||
            const preview = data.availableTools.slice(0, 5).join(', ');
 | 
					 | 
				
			||||||
            return `${data.totalAvailable || data.availableTools.length} Tools verfügbar: ${preview}${data.availableTools.length > 5 ? '...' : ''}`;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          return `${data.totalAvailable || 0} Tools verfügbar`;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          return `Ausgewählt: ${Array.isArray(data.selectedTools) ? data.selectedTools.join(', ') : 'keine'}`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
      case 'phase-tool-selection':
 | 
					 | 
				
			||||||
        if (type === 'input') {
 | 
					 | 
				
			||||||
          if (data.availableTools && Array.isArray(data.availableTools)) {
 | 
					 | 
				
			||||||
            return `${data.availableTools.length} Tools für Phase: ${data.availableTools.slice(0, 3).join(', ')}${data.availableTools.length > 3 ? '...' : ''}`;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          return `Phase: ${data.phaseName || data.phaseId || 'unbekannt'} (${data.toolCount || 0} verfügbar)`;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          if (data.selectedTools && Array.isArray(data.selectedTools)) {
 | 
					 | 
				
			||||||
            return `Ausgewählt: ${data.selectedTools.join(', ')}`;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          return `${data.selectionCount || 0} Tools ausgewählt (Ø ${data.avgTaskRelevance || 0}% Relevanz)`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
      case 'similarity-search':
 | 
					 | 
				
			||||||
        if (type === 'input') {
 | 
					 | 
				
			||||||
          return `Suche: "${data.query}" (Schwelle: ${data.threshold})`;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          if (data.topMatches && Array.isArray(data.topMatches)) {
 | 
					 | 
				
			||||||
            return `${data.resultsCount} Treffer: ${data.topMatches.slice(0, 3).join(', ')}`;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          return `${data.resultsCount || 0} Treffer gefunden`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
      case 'phase-enhancement':
 | 
					 | 
				
			||||||
        if (type === 'input') {
 | 
					 | 
				
			||||||
          return `Phase: ${data.phaseName || data.phaseId} (${data.searchStrategy || 'Standard'})`;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          const toolsAdded = Array.isArray(data.addedTools) ? data.addedTools : [];
 | 
					 | 
				
			||||||
          return `${data.toolsAddedCount || toolsAdded.length} Tools hinzugefügt: ${toolsAdded.join(', ') || 'keine'}`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
      case 'ai-decision':
 | 
					 | 
				
			||||||
        if (type === 'input') {
 | 
					 | 
				
			||||||
          return data.prompt ? `KI-Prompt: ${data.prompt.slice(0, 100)}...` : 'KI-Analyse durchgeführt';
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          return data.response ? `KI-Antwort: ${data.response.slice(0, 100)}...` : 'Antwort erhalten';
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
      case 'tool-confidence':
 | 
					 | 
				
			||||||
        if (type === 'input') {
 | 
					 | 
				
			||||||
          return `Tool: ${data.toolName} (Semantik: ${data.semanticSimilarity}%, Aufgabe: ${data.taskRelevance}%)`;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          return `Vertrauen: ${data.overallConfidence}% (Stärken: ${data.strengthIndicators?.length || 0}, Unsicherheiten: ${data.uncertaintyFactors?.length || 0})`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
      case 'tool-added-to-phase':
 | 
					 | 
				
			||||||
        if (type === 'input') {
 | 
					 | 
				
			||||||
          return `Tool: ${data.toolName} für ${data.phaseId} (${data.taskRelevance}% Relevanz, ${data.priority} Priorität)`;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          const justificationPreview = data.justification ? data.justification.slice(0, 80) + '...' : 'Keine Begründung';
 | 
					 | 
				
			||||||
          return `Begründung: ${justificationPreview}`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
      case 'concept-selection':
 | 
					 | 
				
			||||||
        if (type === 'input') {
 | 
					 | 
				
			||||||
          const availableCount = Array.isArray(data.availableConcepts) ? data.availableConcepts.length : 0;
 | 
					 | 
				
			||||||
          return `${availableCount} Konzepte verfügbar für methodische Fundierung`;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          const selectedConcepts = Array.isArray(data.selectedConcepts) ? data.selectedConcepts : [];
 | 
					 | 
				
			||||||
          return `${selectedConcepts.length} ausgewählt: ${selectedConcepts.slice(0, 3).join(', ')}${selectedConcepts.length > 3 ? '...' : ''}`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // Fallback to generic handling for other actions
 | 
					 | 
				
			||||||
    if (typeof data === 'string') {
 | 
					 | 
				
			||||||
      return data.length > 100 ? data.slice(0, 100) + '...' : data;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if (Array.isArray(data)) {
 | 
					 | 
				
			||||||
      if (data.length === 0) return 'Leeres Array';
 | 
					 | 
				
			||||||
      if (data.length <= 3) return data.join(', ');
 | 
					 | 
				
			||||||
      return `${data.slice(0, 3).join(', ')} und ${data.length - 3} weitere`;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    return `${Object.keys(data).length} Eigenschaften`;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  renderPhaseGroups(auditTrail, stats) {
 | 
					  renderPhaseGroups(auditTrail, stats) {
 | 
				
			||||||
    const phaseGroups = new Map();
 | 
					    const phaseGroups = new Map();
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -1439,26 +1346,23 @@ class AIQueryInterface {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  renderDetailedEntryInfo(entry) {
 | 
					  renderDetailedEntryInfo(entry) {
 | 
				
			||||||
    const details = [];
 | 
					    const details = [];
 | 
				
			||||||
    const input = entry.input || {};
 | 
					 | 
				
			||||||
    const output = entry.output || {};
 | 
					 | 
				
			||||||
    const metadata = entry.metadata || {};
 | 
					    const metadata = entry.metadata || {};
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Show input summary with action-specific formatting
 | 
					    // The backend now provides meaningful inputSummary and outputSummary
 | 
				
			||||||
    if (metadata.inputSummary && metadata.inputSummary !== 'Leer') {
 | 
					    if (metadata.inputSummary && metadata.inputSummary !== 'Leer') {
 | 
				
			||||||
      details.push(`<div class="detail-item"><strong>Eingabe:</strong> ${escapeHtml(metadata.inputSummary)}</div>`);
 | 
					      details.push(`<div class="detail-item"><strong>Eingabe:</strong> ${escapeHtml(metadata.inputSummary)}</div>`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Show output summary with action-specific formatting  
 | 
					 | 
				
			||||||
    if (metadata.outputSummary && metadata.outputSummary !== 'Leer') {
 | 
					    if (metadata.outputSummary && metadata.outputSummary !== 'Leer') {
 | 
				
			||||||
      details.push(`<div class="detail-item"><strong>Ausgabe:</strong> ${escapeHtml(metadata.outputSummary)}</div>`);
 | 
					      details.push(`<div class="detail-item"><strong>Ausgabe:</strong> ${escapeHtml(metadata.outputSummary)}</div>`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Show reasoning - this is now meaningful, not generic
 | 
					    // Show meaningful reasoning (backend now avoids generic "completed with X%" messages)
 | 
				
			||||||
    if (metadata.reasoning && !metadata.reasoning.includes('completed with')) {
 | 
					    if (metadata.reasoning && !metadata.reasoning.includes('completed with')) {
 | 
				
			||||||
      details.push(`<div class="detail-item"><strong>Begründung:</strong> ${escapeHtml(metadata.reasoning)}</div>`);
 | 
					      details.push(`<div class="detail-item"><strong>Begründung:</strong> ${escapeHtml(metadata.reasoning)}</div>`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Show specific details based on action type
 | 
					    // Action-specific additional details
 | 
				
			||||||
    if (entry.action === 'similarity-search' && metadata.similarityScores) {
 | 
					    if (entry.action === 'similarity-search' && metadata.similarityScores) {
 | 
				
			||||||
      const topScores = Object.entries(metadata.similarityScores)
 | 
					      const topScores = Object.entries(metadata.similarityScores)
 | 
				
			||||||
        .sort(([,a], [,b]) => (b) - (a))
 | 
					        .sort(([,a], [,b]) => (b) - (a))
 | 
				
			||||||
@ -1477,10 +1381,13 @@ class AIQueryInterface {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if (entry.action === 'selection-decision' && metadata.selectionMethod) {
 | 
					    if (entry.action === 'selection-decision') {
 | 
				
			||||||
      details.push(`<div class="detail-item"><strong>Auswahlmethode:</strong> ${metadata.selectionMethod}</div>`);
 | 
					      if (metadata.selectionMethod) {
 | 
				
			||||||
 | 
					        const methodDisplay = metadata.selectionMethod === 'embeddings_candidates' ? 'Semantische Filterung' : 'Vollständige Analyse';
 | 
				
			||||||
 | 
					        details.push(`<div class="detail-item"><strong>Methode:</strong> ${methodDisplay}</div>`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      if (metadata.reductionRatio) {
 | 
					      if (metadata.reductionRatio) {
 | 
				
			||||||
        details.push(`<div class="detail-item"><strong>Reduktion:</strong> ${(metadata.reductionRatio * 100).toFixed(1)}% der verfügbaren Tools</div>`);
 | 
					        details.push(`<div class="detail-item"><strong>Filterung:</strong> ${(metadata.reductionRatio * 100).toFixed(1)}% der verfügbaren Tools</div>`);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -1501,6 +1408,15 @@ class AIQueryInterface {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    if (entry.action === 'phase-enhancement') {
 | 
				
			||||||
 | 
					      if (metadata.semanticSimilarity) {
 | 
				
			||||||
 | 
					        details.push(`<div class="detail-item"><strong>Semantische Ähnlichkeit:</strong> ${(metadata.semanticSimilarity * 100).toFixed(1)}%</div>`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (metadata.aiReasoningUsed) {
 | 
				
			||||||
 | 
					        details.push(`<div class="detail-item"><strong>KI-Begründung:</strong> Verwendet für Auswahl</div>`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    if (details.length === 0) return '';
 | 
					    if (details.length === 0) return '';
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    return `
 | 
					    return `
 | 
				
			||||||
 | 
				
			|||||||
@ -378,65 +378,105 @@ class AuditService {
 | 
				
			|||||||
  private createSpecificSummary(data: any, action: string, type: 'input' | 'output'): string {
 | 
					  private createSpecificSummary(data: any, action: string, type: 'input' | 'output'): string {
 | 
				
			||||||
    if (!data) return 'Leer';
 | 
					    if (!data) return 'Leer';
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Action-specific summaries
 | 
					    // Action-specific summaries that show actual meaningful data
 | 
				
			||||||
    switch (action) {
 | 
					    switch (action) {
 | 
				
			||||||
      case 'selection-decision':
 | 
					      case 'selection-decision':
 | 
				
			||||||
        if (type === 'input') {
 | 
					        if (type === 'input') {
 | 
				
			||||||
          if (data.availableTools && Array.isArray(data.availableTools)) {
 | 
					          if (data.availableTools && Array.isArray(data.availableTools)) {
 | 
				
			||||||
            const preview = data.availableTools.slice(0, 5).join(', ');
 | 
					            const preview = data.availableTools.slice(0, 3).join(', ');
 | 
				
			||||||
            return `${data.totalAvailable || data.availableTools.length} Tools verfügbar: ${preview}${data.availableTools.length > 5 ? '...' : ''}`;
 | 
					            return `${data.totalAvailable || data.availableTools.length} Tools: ${preview}${data.availableTools.length > 3 ? '...' : ''}`;
 | 
				
			||||||
 | 
					          } else if (data.totalAvailable) {
 | 
				
			||||||
 | 
					            return `${data.totalAvailable} Tools verfügbar, Methode: ${data.selectionMethod}`;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return `${data.totalAvailable || 0} Tools verfügbar`;
 | 
					          return `${data.candidateCount || 0} Kandidaten für Auswahl`;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return `Ausgewählt: ${Array.isArray(data.selectedTools) ? data.selectedTools.join(', ') : 'keine'}`;
 | 
					          if (Array.isArray(data.selectedTools)) {
 | 
				
			||||||
 | 
					            return `${data.selectedTools.length} ausgewählt: ${data.selectedTools.slice(0, 3).join(', ')}${data.selectedTools.length > 3 ? '...' : ''}`;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return `Auswahl: ${data.selectionRatio ? Math.round(data.selectionRatio * 100) + '%' : 'unbekannt'}`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
      case 'phase-tool-selection':
 | 
					      case 'phase-tool-selection':
 | 
				
			||||||
        if (type === 'input') {
 | 
					        if (type === 'input') {
 | 
				
			||||||
          if (data.availableTools && Array.isArray(data.availableTools)) {
 | 
					          if (Array.isArray(data.availableTools)) {
 | 
				
			||||||
            return `${data.availableTools.length} Tools für Phase: ${data.availableTools.slice(0, 3).join(', ')}${data.availableTools.length > 3 ? '...' : ''}`;
 | 
					            const toolPreview = data.availableTools.slice(0, 3).join(', ');
 | 
				
			||||||
 | 
					            return `${data.availableTools.length} Tools für ${data.phaseName || data.phaseId}: ${toolPreview}${data.availableTools.length > 3 ? '...' : ''}`;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return `Phase: ${data.phaseName || data.phaseId || 'unbekannt'}`;
 | 
					          return `Phase: ${data.phaseName || data.phaseId} (${data.toolCount || 0} Tools)`;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          if (data.selectedTools && Array.isArray(data.selectedTools)) {
 | 
					          if (Array.isArray(data.selectedTools)) {
 | 
				
			||||||
            return `Ausgewählt: ${data.selectedTools.join(', ')}`;
 | 
					            return `${data.selectedTools.length} ausgewählt: ${data.selectedTools.join(', ')}`;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return `${data.selectionCount || 0} Tools ausgewählt`;
 | 
					          return `${data.selectionCount || 0} Tools, Ø ${data.avgTaskRelevance || 0}% Relevanz`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
      case 'similarity-search':
 | 
					      case 'similarity-search':
 | 
				
			||||||
        if (type === 'input') {
 | 
					        if (type === 'input') {
 | 
				
			||||||
          return `Suche: "${data.query}" (Schwelle: ${data.threshold})`;
 | 
					          return `Suche: "${data.query}" (Min. ${data.threshold} Ähnlichkeit)`;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          if (data.topMatches && Array.isArray(data.topMatches)) {
 | 
					          if (Array.isArray(data.topMatches)) {
 | 
				
			||||||
            return `${data.resultsCount} Treffer: ${data.topMatches.slice(0, 3).join(', ')}`;
 | 
					            return `${data.resultsCount} Treffer: ${data.topMatches.slice(0, 2).join(', ')}${data.topMatches.length > 2 ? '...' : ''}`;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return `${data.resultsCount || 0} Treffer gefunden`;
 | 
					          return `${data.resultsCount || 0} semantische Treffer gefunden`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
      case 'phase-enhancement':
 | 
					      case 'phase-enhancement':
 | 
				
			||||||
        if (type === 'input') {
 | 
					        if (type === 'input') {
 | 
				
			||||||
          return `Phase: ${data.phaseName || data.phaseId} (${data.searchStrategy || 'Standard'})`;
 | 
					          return `Vervollständige Phase: ${data.phaseName || data.phaseId}`;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return `${data.toolsAddedCount} Tools hinzugefügt: ${Array.isArray(data.addedTools) ? data.addedTools.join(', ') : 'keine'}`;
 | 
					          if (Array.isArray(data.addedTools) && data.addedTools.length > 0) {
 | 
				
			||||||
 | 
					            return `${data.addedTools.length} hinzugefügt: ${data.addedTools.join(', ')}`;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return `${data.toolsAddedCount || 0} Tools für Phase hinzugefügt`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
      case 'ai-decision':
 | 
					      case 'ai-decision':
 | 
				
			||||||
        if (type === 'input') {
 | 
					        if (type === 'input') {
 | 
				
			||||||
          return data.prompt ? `KI-Prompt: ${data.prompt.slice(0, 100)}...` : 'KI-Analyse durchgeführt';
 | 
					          if (data.prompt) {
 | 
				
			||||||
 | 
					            const promptPreview = data.prompt.slice(0, 80).replace(/\n/g, ' ');
 | 
				
			||||||
 | 
					            return `KI-Prompt: ${promptPreview}...`;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return 'KI-Analyse angefordert';
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return data.response ? `KI-Antwort: ${data.response.slice(0, 100)}...` : 'Antwort erhalten';
 | 
					          if (data.response) {
 | 
				
			||||||
 | 
					            const responsePreview = data.response.slice(0, 80).replace(/\n/g, ' ');
 | 
				
			||||||
 | 
					            return `KI-Antwort: ${responsePreview}...`;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return 'KI-Analyse abgeschlossen';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
      case 'tool-confidence':
 | 
					      case 'tool-confidence':
 | 
				
			||||||
        if (type === 'input') {
 | 
					        if (type === 'input') {
 | 
				
			||||||
          return `Tool: ${data.toolName} (Semantik: ${data.semanticSimilarity}%, Aufgabe: ${data.taskRelevance}%)`;
 | 
					          return `Tool: ${data.toolName} (Sem: ${data.semanticSimilarity}%, Task: ${data.taskRelevance}%)`;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return `Vertrauen: ${data.overallConfidence}% (Stärken: ${data.strengthIndicators?.length || 0}, Unsicherheiten: ${data.uncertaintyFactors?.length || 0})`;
 | 
					          const strengthCount = data.strengthIndicators?.length || 0;
 | 
				
			||||||
 | 
					          const uncertaintyCount = data.uncertaintyFactors?.length || 0;
 | 
				
			||||||
 | 
					          return `${data.overallConfidence}% Vertrauen (${strengthCount} Stärken, ${uncertaintyCount} Unsicherheiten)`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      case 'tool-added-to-phase':
 | 
				
			||||||
 | 
					        if (type === 'input') {
 | 
				
			||||||
 | 
					          return `${data.toolName} → ${data.phaseId} (${data.taskRelevance}% Relevanz, ${data.priority})`;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          const justificationPreview = data.justification ? 
 | 
				
			||||||
 | 
					            data.justification.slice(0, 60).replace(/\n/g, ' ') + '...' : 'Hinzugefügt';
 | 
				
			||||||
 | 
					          return `Begründung: ${justificationPreview}`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      case 'concept-selection':
 | 
				
			||||||
 | 
					        if (type === 'input') {
 | 
				
			||||||
 | 
					          const conceptCount = Array.isArray(data.availableConcepts) ? data.availableConcepts.length : 0;
 | 
				
			||||||
 | 
					          const toolContext = Array.isArray(data.selectedToolsContext) ? data.selectedToolsContext.length : 0;
 | 
				
			||||||
 | 
					          return `${conceptCount} Konzepte verfügbar, ${toolContext} Tools als Kontext`;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          if (Array.isArray(data.selectedConcepts)) {
 | 
				
			||||||
 | 
					            return `${data.selectedConcepts.length} ausgewählt: ${data.selectedConcepts.slice(0, 2).join(', ')}${data.selectedConcepts.length > 2 ? '...' : ''}`;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return `Konzeptauswahl abgeschlossen`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Fallback to generic handling
 | 
					    // Enhanced fallback that shows actual key-value content instead of just "X Eigenschaften"
 | 
				
			||||||
    if (typeof data === 'string') {
 | 
					    if (typeof data === 'string') {
 | 
				
			||||||
      return data.length > 100 ? data.slice(0, 100) + '...' : data;
 | 
					      return data.length > 100 ? data.slice(0, 100) + '...' : data;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -444,10 +484,44 @@ class AuditService {
 | 
				
			|||||||
    if (Array.isArray(data)) {
 | 
					    if (Array.isArray(data)) {
 | 
				
			||||||
      if (data.length === 0) return 'Leeres Array';
 | 
					      if (data.length === 0) return 'Leeres Array';
 | 
				
			||||||
      if (data.length <= 3) return data.join(', ');
 | 
					      if (data.length <= 3) return data.join(', ');
 | 
				
			||||||
      return `${data.slice(0, 3).join(', ')} und ${data.length - 3} weitere`;
 | 
					      return `${data.slice(0, 3).join(', ')} + ${data.length - 3} weitere`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    return `${Object.keys(data).length} Eigenschaften`;
 | 
					    if (typeof data === 'object') {
 | 
				
			||||||
 | 
					      const keys = Object.keys(data);
 | 
				
			||||||
 | 
					      if (keys.length === 0) return 'Leeres Objekt';
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      // Show actual key-value pairs for small objects instead of just counting properties
 | 
				
			||||||
 | 
					      if (keys.length <= 2) {
 | 
				
			||||||
 | 
					        const pairs = keys.map(key => {
 | 
				
			||||||
 | 
					          const value = data[key];
 | 
				
			||||||
 | 
					          if (typeof value === 'string' && value.length > 30) {
 | 
				
			||||||
 | 
					            return `${key}: ${value.slice(0, 30)}...`;
 | 
				
			||||||
 | 
					          } else if (Array.isArray(value)) {
 | 
				
			||||||
 | 
					            return `${key}: [${value.length} Items]`;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            return `${key}: ${value}`;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return pairs.join(', ');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        // For larger objects, show key names and some sample values
 | 
				
			||||||
 | 
					        const sampleKeys = keys.slice(0, 3);
 | 
				
			||||||
 | 
					        const sampleValues = sampleKeys.map(key => {
 | 
				
			||||||
 | 
					          const value = data[key];
 | 
				
			||||||
 | 
					          if (typeof value === 'string' && value.length > 20) {
 | 
				
			||||||
 | 
					            return `${key}: ${value.slice(0, 20)}...`;
 | 
				
			||||||
 | 
					          } else if (Array.isArray(value)) {
 | 
				
			||||||
 | 
					            return `${key}: [${value.length}]`;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            return `${key}: ${value}`;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return `${sampleValues.join(', ')}${keys.length > 3 ? ` + ${keys.length - 3} weitere` : ''}`;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return String(data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private generateSpecificReasoning(
 | 
					  private generateSpecificReasoning(
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user