{
"event": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "\/var\/www\/dev.campus.systemische-tools.de\/src\/View\/docs\/code-graph-global.php",
"content": "<?php\n\ndeclare(strict_types=1);\n\n\/\/ @responsibility: View für globalen Projekt-Dependency-Graph\n\nob_start();\n?>\n\n<nav class=\"breadcrumb\">\n <a href=\"\/docs\/code\">Code-Analyse<\/a> »\n <span>Projekt-Graph<\/span>\n<\/nav>\n\n<h1>Projekt Dependency Graph<\/h1>\n<p style=\"color: var(--text-muted); margin-bottom: 1rem;\">\n Vererbungs- und Interface-Beziehungen im Projekt (extends\/implements)\n<\/p>\n\n<div class=\"graph-controls\" style=\"display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; align-items: center;\">\n <div class=\"graph-legend\" style=\"display: flex; gap: 1rem; flex-wrap: wrap; font-size: 0.875rem;\">\n <span><span style=\"display: inline-block; width: 12px; height: 12px; background: #6366f1; border-radius: 50%; margin-right: 0.25rem;\"><\/span> Class<\/span>\n <span><span style=\"display: inline-block; width: 12px; height: 12px; background: #f59e0b; border-radius: 50%; margin-right: 0.25rem;\"><\/span> Interface<\/span>\n <span><span style=\"display: inline-block; width: 12px; height: 12px; background: #10b981; border-radius: 50%; margin-right: 0.25rem;\"><\/span> Trait<\/span>\n <\/div>\n <div style=\"margin-left: auto; display: flex; gap: 0.5rem; align-items: center;\">\n <label for=\"namespace-filter\" style=\"font-size: 0.875rem;\">Namespace:<\/label>\n <select id=\"namespace-filter\" style=\"padding: 0.25rem 0.5rem; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-secondary);\">\n <option value=\"\">Alle<\/option>\n <\/select>\n <button id=\"reset-zoom\" class=\"btn btn--secondary btn--small\" style=\"padding: 0.25rem 0.5rem; font-size: 0.75rem;\">Reset Zoom<\/button>\n <\/div>\n<\/div>\n\n<div id=\"graph-stats\" style=\"font-size: 0.875rem; color: var(--text-muted); margin-bottom: 0.5rem;\"><\/div>\n\n<div id=\"graph-container\" style=\"width: 100%; height: 700px; background: var(--bg-secondary); border-radius: 8px; overflow: hidden; position: relative;\"><\/div>\n\n<div style=\"margin-top: 1.5rem;\">\n <a href=\"\/docs\/code\" class=\"btn btn--secondary\">← Zurück zur Übersicht<\/a>\n<\/div>\n\n<script src=\"https:\/\/d3js.org\/d3.v7.min.js\"><\/script>\n<script>\n(function() {\n const container = document.getElementById('graph-container');\n const width = container.clientWidth;\n const height = container.clientHeight;\n\n const colors = {\n class: '#6366f1',\n interface: '#f59e0b',\n trait: '#10b981'\n };\n\n const linkColors = {\n extends: '#f59e0b',\n implements: '#3b82f6'\n };\n\n \/\/ Loading indicator\n container.innerHTML = '<div style=\"display: flex; justify-content: center; align-items: center; height: 100%; color: var(--text-muted);\">Lade Graph-Daten...<\/div>';\n\n fetch('\/docs\/code\/graph-data')\n .then(r => r.json())\n .then(data => {\n container.innerHTML = '';\n\n \/\/ Stats\n document.getElementById('graph-stats').textContent =\n `${data.stats.nodes} Klassen | ${data.stats.links} Beziehungen | ${data.stats.namespaces} Namespaces`;\n\n \/\/ Populate namespace filter\n const namespaces = [...new Set(data.nodes.map(n => n.namespace))].sort();\n const filter = document.getElementById('namespace-filter');\n namespaces.forEach(ns => {\n const opt = document.createElement('option');\n opt.value = ns;\n opt.textContent = ns;\n filter.appendChild(opt);\n });\n\n \/\/ Group nodes by namespace for static layout\n const namespaceGroups = {};\n data.nodes.forEach((node, i) => {\n const ns = node.namespace || 'global';\n if (!namespaceGroups[ns]) namespaceGroups[ns] = [];\n namespaceGroups[ns].push(node);\n });\n\n \/\/ Compute static positions (circular layout by namespace)\n const nsKeys = Object.keys(namespaceGroups).sort();\n const nsCount = nsKeys.length;\n const centerX = width \/ 2;\n const centerY = height \/ 2;\n const nsRadius = Math.min(width, height) * 0.35;\n\n nsKeys.forEach((ns, nsIndex) => {\n const nodes = namespaceGroups[ns];\n const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;\n const nsCenterX = centerX + nsRadius * Math.cos(nsAngle);\n const nsCenterY = centerY + nsRadius * Math.sin(nsAngle);\n\n const nodeCount = nodes.length;\n const nodeRadius = Math.min(80, 20 + nodeCount * 5);\n\n nodes.forEach((node, nodeIndex) => {\n if (nodeCount === 1) {\n node.x = nsCenterX;\n node.y = nsCenterY;\n } else {\n const nodeAngle = (2 * Math.PI * nodeIndex) \/ nodeCount;\n node.x = nsCenterX + nodeRadius * Math.cos(nodeAngle);\n node.y = nsCenterY + nodeRadius * Math.sin(nodeAngle);\n }\n });\n });\n\n \/\/ Create node index for links\n const nodeIndex = {};\n data.nodes.forEach((n, i) => nodeIndex[n.id] = i);\n\n \/\/ Resolve link indices\n data.links.forEach(link => {\n if (typeof link.source === 'number') {\n link.sourceNode = data.nodes[link.source];\n link.targetNode = data.nodes[link.target];\n }\n });\n\n \/\/ Create SVG with zoom\n const svg = d3.select('#graph-container')\n .append('svg')\n .attr('width', width)\n .attr('height', height);\n\n const g = svg.append('g');\n\n const zoom = d3.zoom()\n .scaleExtent([0.1, 4])\n .on('zoom', (event) => {\n g.attr('transform', event.transform);\n });\n\n svg.call(zoom);\n\n \/\/ Reset zoom button\n document.getElementById('reset-zoom').addEventListener('click', () => {\n svg.transition().duration(300).call(zoom.transform, d3.zoomIdentity);\n });\n\n \/\/ Arrow markers\n svg.append('defs').selectAll('marker')\n .data(['extends', 'implements'])\n .enter().append('marker')\n .attr('id', d => 'arrow-global-' + d)\n .attr('viewBox', '0 -5 10 10')\n .attr('refX', 15)\n .attr('refY', 0)\n .attr('markerWidth', 5)\n .attr('markerHeight', 5)\n .attr('orient', 'auto')\n .append('path')\n .attr('d', 'M0,-5L10,0L0,5')\n .attr('fill', d => linkColors[d]);\n\n \/\/ Draw namespace labels\n const nsLabels = g.append('g').attr('class', 'ns-labels');\n nsKeys.forEach((ns, nsIndex) => {\n const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;\n const labelRadius = nsRadius + 100;\n const labelX = centerX + labelRadius * Math.cos(nsAngle);\n const labelY = centerY + labelRadius * Math.sin(nsAngle);\n\n nsLabels.append('text')\n .attr('x', labelX)\n .attr('y', labelY)\n .attr('text-anchor', 'middle')\n .attr('fill', 'var(--text-muted)')\n .attr('font-size', '10px')\n .attr('font-weight', 'bold')\n .text(ns.split('\\\\').pop());\n });\n\n \/\/ Draw links\n const link = g.append('g')\n .selectAll('line')\n .data(data.links)\n .enter().append('line')\n .attr('class', 'graph-link')\n .attr('stroke', d => linkColors[d.type] || '#94a3b8')\n .attr('stroke-width', 1.5)\n .attr('stroke-opacity', 0.4)\n .attr('marker-end', d => 'url(#arrow-global-' + d.type + ')')\n .attr('x1', d => data.nodes[d.source].x)\n .attr('y1', d => data.nodes[d.source].y)\n .attr('x2', d => data.nodes[d.target].x)\n .attr('y2', d => data.nodes[d.target].y);\n\n \/\/ Draw nodes\n const node = g.append('g')\n .selectAll('g')\n .data(data.nodes)\n .enter().append('g')\n .attr('class', 'graph-node')\n .attr('data-namespace', d => d.namespace)\n .attr('transform', d => `translate(${d.x},${d.y})`)\n .attr('cursor', d => d.fileId ? 'pointer' : 'default')\n .on('click', (event, d) => {\n if (d.fileId) {\n window.location.href = '\/docs\/code\/' + d.fileId;\n }\n });\n\n node.append('circle')\n .attr('r', 8)\n .attr('fill', d => colors[d.type] || colors.class)\n .attr('stroke', '#fff')\n .attr('stroke-width', 1.5);\n\n node.append('text')\n .text(d => d.label)\n .attr('x', 0)\n .attr('y', 20)\n .attr('text-anchor', 'middle')\n .attr('fill', 'var(--text-primary)')\n .attr('font-size', '8px');\n\n node.append('title')\n .text(d => d.id);\n\n \/\/ Namespace filter\n filter.addEventListener('change', (e) => {\n const selected = e.target.value;\n\n if (!selected) {\n \/\/ Show all\n d3.selectAll('.graph-node').style('opacity', 1);\n d3.selectAll('.graph-link').style('opacity', 0.4);\n } else {\n \/\/ Filter by namespace\n d3.selectAll('.graph-node')\n .style('opacity', d => d.namespace === selected ? 1 : 0.1);\n\n ... [TRUNCATED-058dcf2e4c8c7060]"
}
}
{
"tool_response": {
"type": "create",
"filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/View\/docs\/code-graph-global.php",
"content": "<?php\n\ndeclare(strict_types=1);\n\n\/\/ @responsibility: View für globalen Projekt-Dependency-Graph\n\nob_start();\n?>\n\n<nav class=\"breadcrumb\">\n <a href=\"\/docs\/code\">Code-Analyse<\/a> »\n <span>Projekt-Graph<\/span>\n<\/nav>\n\n<h1>Projekt Dependency Graph<\/h1>\n<p style=\"color: var(--text-muted); margin-bottom: 1rem;\">\n Vererbungs- und Interface-Beziehungen im Projekt (extends\/implements)\n<\/p>\n\n<div class=\"graph-controls\" style=\"display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; align-items: center;\">\n <div class=\"graph-legend\" style=\"display: flex; gap: 1rem; flex-wrap: wrap; font-size: 0.875rem;\">\n <span><span style=\"display: inline-block; width: 12px; height: 12px; background: #6366f1; border-radius: 50%; margin-right: 0.25rem;\"><\/span> Class<\/span>\n <span><span style=\"display: inline-block; width: 12px; height: 12px; background: #f59e0b; border-radius: 50%; margin-right: 0.25rem;\"><\/span> Interface<\/span>\n <span><span style=\"display: inline-block; width: 12px; height: 12px; background: #10b981; border-radius: 50%; margin-right: 0.25rem;\"><\/span> Trait<\/span>\n <\/div>\n <div style=\"margin-left: auto; display: flex; gap: 0.5rem; align-items: center;\">\n <label for=\"namespace-filter\" style=\"font-size: 0.875rem;\">Namespace:<\/label>\n <select id=\"namespace-filter\" style=\"padding: 0.25rem 0.5rem; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-secondary);\">\n <option value=\"\">Alle<\/option>\n <\/select>\n <button id=\"reset-zoom\" class=\"btn btn--secondary btn--small\" style=\"padding: 0.25rem 0.5rem; font-size: 0.75rem;\">Reset Zoom<\/button>\n <\/div>\n<\/div>\n\n<div id=\"graph-stats\" style=\"font-size: 0.875rem; color: var(--text-muted); margin-bottom: 0.5rem;\"><\/div>\n\n<div id=\"graph-container\" style=\"width: 100%; height: 700px; background: var(--bg-secondary); border-radius: 8px; overflow: hidden; position: relative;\"><\/div>\n\n<div style=\"margin-top: 1.5rem;\">\n <a href=\"\/docs\/code\" class=\"btn btn--secondary\">← Zurück zur Übersicht<\/a>\n<\/div>\n\n<script src=\"https:\/\/d3js.org\/d3.v7.min.js\"><\/script>\n<script>\n(function() {\n const container = document.getElementById('graph-container');\n const width = container.clientWidth;\n const height = container.clientHeight;\n\n const colors = {\n class: '#6366f1',\n interface: '#f59e0b',\n trait: '#10b981'\n };\n\n const linkColors = {\n extends: '#f59e0b',\n implements: '#3b82f6'\n };\n\n \/\/ Loading indicator\n container.innerHTML = '<div style=\"display: flex; justify-content: center; align-items: center; height: 100%; color: var(--text-muted);\">Lade Graph-Daten...<\/div>';\n\n fetch('\/docs\/code\/graph-data')\n .then(r => r.json())\n .then(data => {\n container.innerHTML = '';\n\n \/\/ Stats\n document.getElementById('graph-stats').textContent =\n `${data.stats.nodes} Klassen | ${data.stats.links} Beziehungen | ${data.stats.namespaces} Namespaces`;\n\n \/\/ Populate namespace filter\n const namespaces = [...new Set(data.nodes.map(n => n.namespace))].sort();\n const filter = document.getElementById('namespace-filter');\n namespaces.forEach(ns => {\n const opt = document.createElement('option');\n opt.value = ns;\n opt.textContent = ns;\n filter.appendChild(opt);\n });\n\n \/\/ Group nodes by namespace for static layout\n const namespaceGroups = {};\n data.nodes.forEach((node, i) => {\n const ns = node.namespace || 'global';\n if (!namespaceGroups[ns]) namespaceGroups[ns] = [];\n namespaceGroups[ns].push(node);\n });\n\n \/\/ Compute static positions (circular layout by namespace)\n const nsKeys = Object.keys(namespaceGroups).sort();\n const nsCount = nsKeys.length;\n const centerX = width \/ 2;\n const centerY = height \/ 2;\n const nsRadius = Math.min(width, height) * 0.35;\n\n nsKeys.forEach((ns, nsIndex) => {\n const nodes = namespaceGroups[ns];\n const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;\n const nsCenterX = centerX + nsRadius * Math.cos(nsAngle);\n const nsCenterY = centerY + nsRadius * Math.sin(nsAngle);\n\n const nodeCount = nodes.length;\n const nodeRadius = Math.min(80, 20 + nodeCount * 5);\n\n nodes.forEach((node, nodeIndex) => {\n if (nodeCount === 1) {\n node.x = nsCenterX;\n node.y = nsCenterY;\n } else {\n const nodeAngle = (2 * Math.PI * nodeIndex) \/ nodeCount;\n node.x = nsCenterX + nodeRadius * Math.cos(nodeAngle);\n node.y = nsCenterY + nodeRadius * Math.sin(nodeAngle);\n }\n });\n });\n\n \/\/ Create node index for links\n const nodeIndex = {};\n data.nodes.forEach((n, i) => nodeIndex[n.id] = i);\n\n \/\/ Resolve link indices\n data.links.forEach(link => {\n if (typeof link.source === 'number') {\n link.sourceNode = data.nodes[link.source];\n link.targetNode = data.nodes[link.target];\n }\n });\n\n \/\/ Create SVG with zoom\n const svg = d3.select('#graph-container')\n .append('svg')\n .attr('width', width)\n .attr('height', height);\n\n const g = svg.append('g');\n\n const zoom = d3.zoom()\n .scaleExtent([0.1, 4])\n .on('zoom', (event) => {\n g.attr('transform', event.transform);\n });\n\n svg.call(zoom);\n\n \/\/ Reset zoom button\n document.getElementById('reset-zoom').addEventListener('click', () => {\n svg.transition().duration(300).call(zoom.transform, d3.zoomIdentity);\n });\n\n \/\/ Arrow markers\n svg.append('defs').selectAll('marker')\n .data(['extends', 'implements'])\n .enter().append('marker')\n .attr('id', d => 'arrow-global-' + d)\n .attr('viewBox', '0 -5 10 10')\n .attr('refX', 15)\n .attr('refY', 0)\n .attr('markerWidth', 5)\n .attr('markerHeight', 5)\n .attr('orient', 'auto')\n .append('path')\n .attr('d', 'M0,-5L10,0L0,5')\n .attr('fill', d => linkColors[d]);\n\n \/\/ Draw namespace labels\n const nsLabels = g.append('g').attr('class', 'ns-labels');\n nsKeys.forEach((ns, nsIndex) => {\n const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;\n const labelRadius = nsRadius + 100;\n const labelX = centerX + labelRadius * Math.cos(nsAngle);\n const labelY = centerY + labelRadius * Math.sin(nsAngle);\n\n nsLabels.append('text')\n .attr('x', labelX)\n .attr('y', labelY)\n .attr('text-anchor', 'middle')\n .attr('fill', 'var(--text-muted)')\n .attr('font-size', '10px')\n .attr('font-weight', 'bold')\n .text(ns.split('\\\\').pop());\n });\n\n \/\/ Draw links\n const link = g.append('g')\n .selectAll('line')\n .data(data.links)\n .enter().append('line')\n .attr('class', 'graph-link')\n .attr('stroke', d => linkColors[d.type] || '#94a3b8')\n .attr('stroke-width', 1.5)\n .attr('stroke-opacity', 0.4)\n .attr('marker-end', d => 'url(#arrow-global-' + d.type + ')')\n .attr('x1', d => data.nodes[d.source].x)\n .attr('y1', d => data.nodes[d.source].y)\n .attr('x2', d => data.nodes[d.target].x)\n .attr('y2', d => data.nodes[d.target].y);\n\n \/\/ Draw nodes\n const node = g.append('g')\n .selectAll('g')\n .data(data.nodes)\n .enter().append('g')\n .attr('class', 'graph-node')\n .attr('data-namespace', d => d.namespace)\n .attr('transform', d => `translate(${d.x},${d.y})`)\n .attr('cursor', d => d.fileId ? 'pointer' : 'default')\n .on('click', (event, d) => {\n if (d.fileId) {\n window.location.href = '\/docs\/code\/' + d.fileId;\n }\n });\n\n node.append('circle')\n .attr('r', 8)\n .attr('fill', d => colors[d.type] || colors.class)\n .attr('stroke', '#fff')\n .attr('stroke-width', 1.5);\n\n node.append('text')\n .text(d => d.label)\n .attr('x', 0)\n .attr('y', 20)\n .attr('text-anchor', 'middle')\n .attr('fill', 'var(--text-primary)')\n .attr('font-size', '8px');\n\n node.append('title')\n .text(d => d.id);\n\n \/\/ Namespace filter\n filter.addEventListener('change', (e) => {\n const selected = e.target.value;\n\n if (!selected) {\n \/\/ Show all\n d3.selectAll('.graph-node').style('opacity', 1);\n d3.selectAll('.graph-link').style('opacity', 0.4);\n } else {\n \/\/ Filter by namespace\n d3.selectAll('.graph-node')\n .style('opacity', d => d.namespace === selected ? 1 : 0.1);\n\n ... [TRUNCATED-058dcf2e4c8c7060]",
"structuredPatch": [],
"originalFile": null
}
}