@archaic-ai/mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/db/client.d.ts +1 -0
- package/build/db/client.js +18 -0
- package/build/db/client.js.map +1 -0
- package/build/db/repositories.d.ts +3 -0
- package/build/db/repositories.js +15 -0
- package/build/db/repositories.js.map +1 -0
- package/build/db/workspaces.d.ts +3 -0
- package/build/db/workspaces.js +15 -0
- package/build/db/workspaces.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +20 -0
- package/build/index.js.map +1 -0
- package/build/render/architecture.d.ts +3 -0
- package/build/render/architecture.js +361 -0
- package/build/render/architecture.js.map +1 -0
- package/build/render/codemap.d.ts +2 -0
- package/build/render/codemap.js +200 -0
- package/build/render/codemap.js.map +1 -0
- package/build/render/open.d.ts +1 -0
- package/build/render/open.js +16 -0
- package/build/render/open.js.map +1 -0
- package/build/render/traces.d.ts +2 -0
- package/build/render/traces.js +147 -0
- package/build/render/traces.js.map +1 -0
- package/build/render/workflows.d.ts +3 -0
- package/build/render/workflows.js +177 -0
- package/build/render/workflows.js.map +1 -0
- package/build/summary/architecture.d.ts +3 -0
- package/build/summary/architecture.js +74 -0
- package/build/summary/architecture.js.map +1 -0
- package/build/summary/codemap.d.ts +2 -0
- package/build/summary/codemap.js +46 -0
- package/build/summary/codemap.js.map +1 -0
- package/build/summary/traces.d.ts +2 -0
- package/build/summary/traces.js +42 -0
- package/build/summary/traces.js.map +1 -0
- package/build/summary/workflows.d.ts +3 -0
- package/build/summary/workflows.js +29 -0
- package/build/summary/workflows.js.map +1 -0
- package/build/tools/repos.d.ts +2 -0
- package/build/tools/repos.js +93 -0
- package/build/tools/repos.js.map +1 -0
- package/build/tools/workspaces.d.ts +2 -0
- package/build/tools/workspaces.js +50 -0
- package/build/tools/workspaces.js.map +1 -0
- package/build/types.d.ts +238 -0
- package/build/types.js +4 -0
- package/build/types.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function mcpFetch<T>(path: string): Promise<T>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const apiUrl = process.env.ARCHAIC_API_URL ?? "https://app.tryarchaic.com";
|
|
2
|
+
const apiKey = process.env.ARCHAIC_API_KEY;
|
|
3
|
+
if (!apiKey) {
|
|
4
|
+
process.stderr.write("Error: ARCHAIC_API_KEY environment variable is required\n");
|
|
5
|
+
process.exit(1);
|
|
6
|
+
}
|
|
7
|
+
export async function mcpFetch(path) {
|
|
8
|
+
const url = `${apiUrl}/api/mcp${path}`;
|
|
9
|
+
const res = await fetch(url, {
|
|
10
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
11
|
+
});
|
|
12
|
+
if (!res.ok) {
|
|
13
|
+
const body = await res.text().catch(() => "");
|
|
14
|
+
throw new Error(`MCP API error ${res.status}: ${body}`);
|
|
15
|
+
}
|
|
16
|
+
return res.json();
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,4BAA4B,CAAC;AAC3E,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAE3C,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAClF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAI,IAAY;IAC5C,MAAM,GAAG,GAAG,GAAG,MAAM,WAAW,IAAI,EAAE,CAAC;IACvC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE;KAC/C,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,EAAgB,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mcpFetch } from "./client.js";
|
|
2
|
+
export async function getRepositoriesByUserId() {
|
|
3
|
+
return mcpFetch("/repos");
|
|
4
|
+
}
|
|
5
|
+
export async function getRepositoryById(id) {
|
|
6
|
+
try {
|
|
7
|
+
return await mcpFetch(`/repos/${id}`);
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
if (err instanceof Error && err.message.includes("404"))
|
|
11
|
+
return null;
|
|
12
|
+
throw err;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=repositories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repositories.js","sourceRoot":"","sources":["../../src/db/repositories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAC3C,OAAO,QAAQ,CAAe,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EAAU;IAChD,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAa,UAAU,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACrE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mcpFetch } from "./client.js";
|
|
2
|
+
export async function getWorkspacesByUserId() {
|
|
3
|
+
return mcpFetch("/workspaces");
|
|
4
|
+
}
|
|
5
|
+
export async function getWorkspaceById(id) {
|
|
6
|
+
try {
|
|
7
|
+
return await mcpFetch(`/workspaces/${id}`);
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
if (err instanceof Error && err.message.includes("404"))
|
|
11
|
+
return null;
|
|
12
|
+
throw err;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=workspaces.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaces.js","sourceRoot":"","sources":["../../src/db/workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,OAAO,QAAQ,CAAc,aAAa,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EAAU;IAC/C,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAY,eAAe,EAAE,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACrE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { registerRepoTools } from "./tools/repos.js";
|
|
5
|
+
import { registerWorkspaceTools } from "./tools/workspaces.js";
|
|
6
|
+
const apiKey = process.env.ARCHAIC_API_KEY;
|
|
7
|
+
if (!apiKey) {
|
|
8
|
+
process.stderr.write("Error: ARCHAIC_API_KEY environment variable is required\n");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const server = new McpServer({
|
|
12
|
+
name: "archaic",
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
});
|
|
15
|
+
registerRepoTools(server);
|
|
16
|
+
registerWorkspaceTools(server);
|
|
17
|
+
const transport = new StdioServerTransport();
|
|
18
|
+
await server.connect(transport);
|
|
19
|
+
process.stderr.write("archaic MCP server running\n");
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAClF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAE/B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAEhC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ArchitectureAnalysis, CrossRepoArchitecture } from "../types.js";
|
|
2
|
+
export declare function renderArchitectureHtml(data: ArchitectureAnalysis, repoName: string): string;
|
|
3
|
+
export declare function renderCrossRepoArchitectureHtml(data: CrossRepoArchitecture, workspaceName: string): string;
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
const NODE_COLORS = {
|
|
2
|
+
service: "#3b82f6",
|
|
3
|
+
database: "#22c55e",
|
|
4
|
+
queue: "#ef4444",
|
|
5
|
+
cache: "#f59e0b",
|
|
6
|
+
frontend: "#3b82f6",
|
|
7
|
+
backend: "#8b5cf6",
|
|
8
|
+
api: "#06b6d4",
|
|
9
|
+
gateway: "#14b8a6",
|
|
10
|
+
external: "#64748b",
|
|
11
|
+
auth: "#14b8a6",
|
|
12
|
+
lambda: "#f59e0b",
|
|
13
|
+
storage: "#22c55e",
|
|
14
|
+
};
|
|
15
|
+
const REPO_COLORS = [
|
|
16
|
+
"#3b82f6", "#8b5cf6", "#ef4444", "#f59e0b",
|
|
17
|
+
"#22c55e", "#06b6d4", "#ec4899", "#14b8a6",
|
|
18
|
+
];
|
|
19
|
+
function getNodeColor(kind) {
|
|
20
|
+
return NODE_COLORS[kind.toLowerCase()] ?? "#94a3b8";
|
|
21
|
+
}
|
|
22
|
+
export function renderArchitectureHtml(data, repoName) {
|
|
23
|
+
const nodes = JSON.stringify(data.nodes);
|
|
24
|
+
const edges = JSON.stringify(data.edges);
|
|
25
|
+
const groups = JSON.stringify(data.groups);
|
|
26
|
+
return `<!DOCTYPE html>
|
|
27
|
+
<html lang="en">
|
|
28
|
+
<head>
|
|
29
|
+
<meta charset="UTF-8">
|
|
30
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
31
|
+
<title>Architecture — ${escapeHtml(repoName)}</title>
|
|
32
|
+
<style>
|
|
33
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
34
|
+
body { background: hsl(240 10% 5%); color: #e2e8f0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; overflow: hidden; }
|
|
35
|
+
#header { display: flex; align-items: center; justify-content: space-between; padding: 12px 20px; background: hsl(240 10% 8%); border-bottom: 1px solid hsl(240 10% 15%); }
|
|
36
|
+
#header h1 { font-size: 16px; font-weight: 600; }
|
|
37
|
+
#header .brand { color: #3b82f6; font-weight: 700; margin-right: 12px; }
|
|
38
|
+
#header .date { color: #64748b; font-size: 13px; }
|
|
39
|
+
#chart { width: 100vw; height: calc(100vh - 49px); }
|
|
40
|
+
.node-rect { rx: 8; ry: 8; stroke-width: 1.5; cursor: pointer; }
|
|
41
|
+
.node-label { font-size: 12px; font-weight: 600; fill: #fff; pointer-events: none; text-anchor: middle; }
|
|
42
|
+
.node-tech { font-size: 10px; fill: #94a3b8; pointer-events: none; text-anchor: middle; }
|
|
43
|
+
.edge-line { fill: none; stroke: #334155; stroke-width: 1.5; }
|
|
44
|
+
.edge-line.cross-repo { stroke: #3b82f6; stroke-width: 2.5; stroke-dasharray: 6 3; }
|
|
45
|
+
.edge-label { font-size: 10px; fill: #64748b; pointer-events: none; }
|
|
46
|
+
.arrowhead { fill: #334155; }
|
|
47
|
+
.group-rect { rx: 12; ry: 12; fill-opacity: 0.08; stroke-opacity: 0.3; stroke-width: 1.5; }
|
|
48
|
+
.group-label { font-size: 13px; font-weight: 600; fill-opacity: 0.7; }
|
|
49
|
+
.tooltip { position: absolute; background: hsl(240 10% 12%); border: 1px solid hsl(240 10% 20%); border-radius: 8px; padding: 10px 14px; font-size: 12px; pointer-events: none; max-width: 300px; display: none; z-index: 10; }
|
|
50
|
+
.tooltip .tt-name { font-weight: 600; margin-bottom: 4px; }
|
|
51
|
+
.tooltip .tt-kind { color: #64748b; font-size: 11px; margin-bottom: 6px; }
|
|
52
|
+
.tooltip .tt-desc { color: #94a3b8; line-height: 1.4; }
|
|
53
|
+
.tooltip .tt-files { color: #64748b; font-size: 11px; margin-top: 6px; font-family: monospace; }
|
|
54
|
+
#legend { position: absolute; bottom: 16px; left: 16px; background: hsl(240 10% 8%); border: 1px solid hsl(240 10% 15%); border-radius: 8px; padding: 12px 16px; font-size: 11px; }
|
|
55
|
+
#legend h3 { font-size: 12px; margin-bottom: 8px; color: #94a3b8; }
|
|
56
|
+
.legend-item { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
|
|
57
|
+
.legend-dot { width: 10px; height: 10px; border-radius: 3px; }
|
|
58
|
+
</style>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
<div id="header">
|
|
62
|
+
<div style="display:flex;align-items:center">
|
|
63
|
+
<span class="brand">archaic</span>
|
|
64
|
+
<h1>Architecture — ${escapeHtml(repoName)}</h1>
|
|
65
|
+
</div>
|
|
66
|
+
<span class="date">Analyzed ${escapeHtml(data.analyzedAt?.split("T")[0] ?? "—")}</span>
|
|
67
|
+
</div>
|
|
68
|
+
<div id="chart"></div>
|
|
69
|
+
<div class="tooltip" id="tooltip"></div>
|
|
70
|
+
<div id="legend"></div>
|
|
71
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
72
|
+
<script>
|
|
73
|
+
const nodes = ${nodes};
|
|
74
|
+
const edges = ${edges};
|
|
75
|
+
const groups = ${groups};
|
|
76
|
+
const nodeColors = ${JSON.stringify(NODE_COLORS)};
|
|
77
|
+
|
|
78
|
+
function getColor(kind) { return nodeColors[kind.toLowerCase()] || "#94a3b8"; }
|
|
79
|
+
|
|
80
|
+
const width = window.innerWidth;
|
|
81
|
+
const height = window.innerHeight - 49;
|
|
82
|
+
const svg = d3.select("#chart").append("svg").attr("width", width).attr("height", height);
|
|
83
|
+
const g = svg.append("g");
|
|
84
|
+
|
|
85
|
+
// Zoom
|
|
86
|
+
const zoom = d3.zoom().scaleExtent([0.1, 4]).on("zoom", (e) => g.attr("transform", e.transform));
|
|
87
|
+
svg.call(zoom);
|
|
88
|
+
|
|
89
|
+
// Build simulation
|
|
90
|
+
const simNodes = nodes.map(n => ({ ...n, x: width/2 + (Math.random()-0.5)*400, y: height/2 + (Math.random()-0.5)*400 }));
|
|
91
|
+
const simEdges = edges.map(e => ({ ...e, source: e.source, target: e.target }));
|
|
92
|
+
|
|
93
|
+
// Group mapping
|
|
94
|
+
const nodeGroup = {};
|
|
95
|
+
nodes.forEach(n => { if (n.group) nodeGroup[n.id] = n.group; });
|
|
96
|
+
|
|
97
|
+
const simulation = d3.forceSimulation(simNodes)
|
|
98
|
+
.force("link", d3.forceLink(simEdges).id(d => d.id).distance(140))
|
|
99
|
+
.force("charge", d3.forceManyBody().strength(-400))
|
|
100
|
+
.force("center", d3.forceCenter(width/2, height/2))
|
|
101
|
+
.force("collision", d3.forceCollide().radius(60));
|
|
102
|
+
|
|
103
|
+
// Group force — nodes in same group attract
|
|
104
|
+
simulation.force("group", d3.forceX().x(d => {
|
|
105
|
+
const gIdx = groups.findIndex(gr => gr.id === d.group);
|
|
106
|
+
if (gIdx < 0) return width/2;
|
|
107
|
+
const cols = Math.ceil(Math.sqrt(groups.length));
|
|
108
|
+
return (gIdx % cols + 0.5) * (width / cols);
|
|
109
|
+
}).strength(d => d.group ? 0.1 : 0));
|
|
110
|
+
simulation.force("groupY", d3.forceY().y(d => {
|
|
111
|
+
const gIdx = groups.findIndex(gr => gr.id === d.group);
|
|
112
|
+
if (gIdx < 0) return height/2;
|
|
113
|
+
const cols = Math.ceil(Math.sqrt(groups.length));
|
|
114
|
+
return (Math.floor(gIdx / cols) + 0.5) * (height / Math.ceil(groups.length / cols));
|
|
115
|
+
}).strength(d => d.group ? 0.1 : 0));
|
|
116
|
+
|
|
117
|
+
// Arrowhead marker
|
|
118
|
+
svg.append("defs").append("marker")
|
|
119
|
+
.attr("id", "arrowhead").attr("viewBox", "0 -5 10 10").attr("refX", 28).attr("refY", 0)
|
|
120
|
+
.attr("markerWidth", 8).attr("markerHeight", 8).attr("orient", "auto")
|
|
121
|
+
.append("path").attr("d", "M0,-5L10,0L0,5").attr("class", "arrowhead");
|
|
122
|
+
|
|
123
|
+
// Edges
|
|
124
|
+
const link = g.append("g").selectAll("line").data(simEdges).join("line")
|
|
125
|
+
.attr("class", d => "edge-line" + (d.isCrossRepo ? " cross-repo" : ""))
|
|
126
|
+
.attr("marker-end", "url(#arrowhead)");
|
|
127
|
+
|
|
128
|
+
// Nodes
|
|
129
|
+
const nodeG = g.append("g").selectAll("g").data(simNodes).join("g").attr("cursor", "grab");
|
|
130
|
+
nodeG.append("rect")
|
|
131
|
+
.attr("class", "node-rect")
|
|
132
|
+
.attr("width", 110).attr("height", 44).attr("x", -55).attr("y", -22)
|
|
133
|
+
.attr("fill", d => getColor(d.kind))
|
|
134
|
+
.attr("fill-opacity", 0.15)
|
|
135
|
+
.attr("stroke", d => getColor(d.kind));
|
|
136
|
+
nodeG.append("text").attr("class", "node-label").attr("dy", d => d.technology ? -3 : 4)
|
|
137
|
+
.text(d => d.name.length > 14 ? d.name.slice(0,13) + "…" : d.name);
|
|
138
|
+
nodeG.append("text").attr("class", "node-tech").attr("dy", 12)
|
|
139
|
+
.text(d => d.technology || "");
|
|
140
|
+
|
|
141
|
+
// Tooltip
|
|
142
|
+
const tooltip = document.getElementById("tooltip");
|
|
143
|
+
nodeG.on("mouseenter", (e, d) => {
|
|
144
|
+
let html = '<div class="tt-name">' + esc(d.name) + '</div>';
|
|
145
|
+
html += '<div class="tt-kind">' + esc(d.kind) + (d.technology ? ' · ' + esc(d.technology) : '') + '</div>';
|
|
146
|
+
if (d.description) html += '<div class="tt-desc">' + esc(d.description) + '</div>';
|
|
147
|
+
if (d.filePaths && d.filePaths.length) html += '<div class="tt-files">' + d.filePaths.map(esc).join('<br>') + '</div>';
|
|
148
|
+
tooltip.innerHTML = html;
|
|
149
|
+
tooltip.style.display = "block";
|
|
150
|
+
tooltip.style.left = (e.pageX + 12) + "px";
|
|
151
|
+
tooltip.style.top = (e.pageY + 12) + "px";
|
|
152
|
+
}).on("mousemove", (e) => {
|
|
153
|
+
tooltip.style.left = (e.pageX + 12) + "px";
|
|
154
|
+
tooltip.style.top = (e.pageY + 12) + "px";
|
|
155
|
+
}).on("mouseleave", () => { tooltip.style.display = "none"; });
|
|
156
|
+
|
|
157
|
+
// Drag
|
|
158
|
+
nodeG.call(d3.drag()
|
|
159
|
+
.on("start", (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
|
|
160
|
+
.on("drag", (e, d) => { d.fx = e.x; d.fy = e.y; })
|
|
161
|
+
.on("end", (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; })
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
simulation.on("tick", () => {
|
|
165
|
+
link.attr("x1", d => d.source.x).attr("y1", d => d.source.y)
|
|
166
|
+
.attr("x2", d => d.target.x).attr("y2", d => d.target.y);
|
|
167
|
+
nodeG.attr("transform", d => "translate(" + d.x + "," + d.y + ")");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Legend
|
|
171
|
+
const kinds = [...new Set(nodes.map(n => n.kind))];
|
|
172
|
+
const legend = document.getElementById("legend");
|
|
173
|
+
legend.innerHTML = "<h3>Node Types</h3>" + kinds.map(k =>
|
|
174
|
+
'<div class="legend-item"><div class="legend-dot" style="background:' + getColor(k) + '"></div>' + esc(k) + '</div>'
|
|
175
|
+
).join("");
|
|
176
|
+
|
|
177
|
+
function esc(s) { const d = document.createElement("div"); d.textContent = s; return d.innerHTML; }
|
|
178
|
+
</script>
|
|
179
|
+
</body>
|
|
180
|
+
</html>`;
|
|
181
|
+
}
|
|
182
|
+
export function renderCrossRepoArchitectureHtml(data, workspaceName) {
|
|
183
|
+
const nodes = JSON.stringify(data.nodes);
|
|
184
|
+
const edges = JSON.stringify(data.edges);
|
|
185
|
+
const groups = JSON.stringify(data.groups);
|
|
186
|
+
const sharedResources = JSON.stringify(data.sharedResources ?? []);
|
|
187
|
+
const connections = JSON.stringify(data.crossRepoConnections ?? []);
|
|
188
|
+
const overview = JSON.stringify(data.systemOverview ?? null);
|
|
189
|
+
// Build repo → color mapping
|
|
190
|
+
const repoNames = [...new Set(data.nodes.map(n => n.repoName))];
|
|
191
|
+
const repoColorMap = {};
|
|
192
|
+
repoNames.forEach((name, i) => {
|
|
193
|
+
repoColorMap[name] = REPO_COLORS[i % REPO_COLORS.length];
|
|
194
|
+
});
|
|
195
|
+
return `<!DOCTYPE html>
|
|
196
|
+
<html lang="en">
|
|
197
|
+
<head>
|
|
198
|
+
<meta charset="UTF-8">
|
|
199
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
200
|
+
<title>Cross-Repo Architecture — ${escapeHtml(workspaceName)}</title>
|
|
201
|
+
<style>
|
|
202
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
203
|
+
body { background: hsl(240 10% 5%); color: #e2e8f0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; overflow: hidden; }
|
|
204
|
+
#header { display: flex; align-items: center; justify-content: space-between; padding: 12px 20px; background: hsl(240 10% 8%); border-bottom: 1px solid hsl(240 10% 15%); }
|
|
205
|
+
#header h1 { font-size: 16px; font-weight: 600; }
|
|
206
|
+
#header .brand { color: #3b82f6; font-weight: 700; margin-right: 12px; }
|
|
207
|
+
#header .date { color: #64748b; font-size: 13px; }
|
|
208
|
+
#main { display: flex; width: 100vw; height: calc(100vh - 49px); }
|
|
209
|
+
#chart { flex: 1; }
|
|
210
|
+
#sidebar { width: 280px; background: hsl(240 10% 7%); border-left: 1px solid hsl(240 10% 15%); padding: 16px; overflow-y: auto; }
|
|
211
|
+
#sidebar h2 { font-size: 14px; color: #94a3b8; margin-bottom: 12px; }
|
|
212
|
+
.sr-card { background: hsl(240 10% 10%); border: 1px solid hsl(240 10% 15%); border-radius: 8px; padding: 10px 12px; margin-bottom: 8px; }
|
|
213
|
+
.sr-name { font-weight: 600; font-size: 13px; }
|
|
214
|
+
.sr-type { color: #64748b; font-size: 11px; }
|
|
215
|
+
.sr-repos { color: #94a3b8; font-size: 11px; margin-top: 4px; }
|
|
216
|
+
.conn-card { background: hsl(240 10% 10%); border: 1px solid hsl(240 10% 15%); border-radius: 8px; padding: 10px 12px; margin-bottom: 8px; font-size: 12px; }
|
|
217
|
+
.conn-type { color: #3b82f6; font-weight: 600; }
|
|
218
|
+
.conn-desc { color: #94a3b8; margin-top: 4px; }
|
|
219
|
+
.node-rect { rx: 8; ry: 8; stroke-width: 1.5; cursor: pointer; }
|
|
220
|
+
.node-label { font-size: 11px; font-weight: 600; fill: #fff; pointer-events: none; text-anchor: middle; }
|
|
221
|
+
.node-repo { font-size: 9px; fill: #94a3b8; pointer-events: none; text-anchor: middle; }
|
|
222
|
+
.edge-line { fill: none; stroke: #334155; stroke-width: 1.5; }
|
|
223
|
+
.edge-line.cross-repo { stroke: #3b82f6; stroke-width: 2.5; stroke-dasharray: 6 3; }
|
|
224
|
+
.arrowhead { fill: #334155; }
|
|
225
|
+
.tooltip { position: absolute; background: hsl(240 10% 12%); border: 1px solid hsl(240 10% 20%); border-radius: 8px; padding: 10px 14px; font-size: 12px; pointer-events: none; max-width: 300px; display: none; z-index: 10; }
|
|
226
|
+
.tooltip .tt-name { font-weight: 600; margin-bottom: 4px; }
|
|
227
|
+
.tooltip .tt-kind { color: #64748b; font-size: 11px; margin-bottom: 6px; }
|
|
228
|
+
.tooltip .tt-desc { color: #94a3b8; line-height: 1.4; }
|
|
229
|
+
#legend { position: absolute; bottom: 16px; left: 16px; background: hsl(240 10% 8%); border: 1px solid hsl(240 10% 15%); border-radius: 8px; padding: 12px 16px; font-size: 11px; }
|
|
230
|
+
#legend h3 { font-size: 12px; margin-bottom: 8px; color: #94a3b8; }
|
|
231
|
+
.legend-item { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
|
|
232
|
+
.legend-dot { width: 10px; height: 10px; border-radius: 3px; }
|
|
233
|
+
.overview-section { margin-top: 20px; }
|
|
234
|
+
.overview-section h3 { font-size: 13px; color: #94a3b8; margin-bottom: 8px; }
|
|
235
|
+
.ov-item { font-size: 12px; color: #cbd5e1; padding: 2px 0; }
|
|
236
|
+
</style>
|
|
237
|
+
</head>
|
|
238
|
+
<body>
|
|
239
|
+
<div id="header">
|
|
240
|
+
<div style="display:flex;align-items:center">
|
|
241
|
+
<span class="brand">archaic</span>
|
|
242
|
+
<h1>Cross-Repo Architecture — ${escapeHtml(workspaceName)}</h1>
|
|
243
|
+
</div>
|
|
244
|
+
<span class="date">Analyzed ${escapeHtml(data.analyzedAt?.split("T")[0] ?? "—")}</span>
|
|
245
|
+
</div>
|
|
246
|
+
<div id="main">
|
|
247
|
+
<div id="chart"></div>
|
|
248
|
+
<div id="sidebar"></div>
|
|
249
|
+
</div>
|
|
250
|
+
<div class="tooltip" id="tooltip"></div>
|
|
251
|
+
<div id="legend"></div>
|
|
252
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
253
|
+
<script>
|
|
254
|
+
const nodes = ${nodes};
|
|
255
|
+
const edges = ${edges};
|
|
256
|
+
const groups = ${groups};
|
|
257
|
+
const sharedResources = ${sharedResources};
|
|
258
|
+
const connections = ${connections};
|
|
259
|
+
const overview = ${overview};
|
|
260
|
+
const repoColors = ${JSON.stringify(repoColorMap)};
|
|
261
|
+
const nodeColors = ${JSON.stringify(NODE_COLORS)};
|
|
262
|
+
|
|
263
|
+
function getColor(kind) { return nodeColors[kind.toLowerCase()] || "#94a3b8"; }
|
|
264
|
+
function getRepoColor(name) { return repoColors[name] || "#94a3b8"; }
|
|
265
|
+
function esc(s) { const d = document.createElement("div"); d.textContent = s; return d.innerHTML; }
|
|
266
|
+
|
|
267
|
+
// Sidebar
|
|
268
|
+
const sidebar = document.getElementById("sidebar");
|
|
269
|
+
let sideHtml = "<h2>Shared Resources</h2>";
|
|
270
|
+
sharedResources.forEach(r => {
|
|
271
|
+
sideHtml += '<div class="sr-card"><div class="sr-name">' + esc(r.name) + '</div><div class="sr-type">' + esc(r.type) + '</div><div class="sr-repos">' + r.repos.map(esc).join(", ") + '</div></div>';
|
|
272
|
+
});
|
|
273
|
+
sideHtml += "<h2 style='margin-top:20px'>Connections</h2>";
|
|
274
|
+
connections.forEach(c => {
|
|
275
|
+
sideHtml += '<div class="conn-card"><span class="conn-type">' + esc(c.connectionType) + '</span> ' + esc(c.sourceRepo) + ' → ' + esc(c.targetRepo) + '<div class="conn-desc">' + esc(c.description) + '</div></div>';
|
|
276
|
+
});
|
|
277
|
+
if (overview) {
|
|
278
|
+
sideHtml += '<div class="overview-section"><h3>System: ' + esc(overview.systemType) + '</h3>';
|
|
279
|
+
overview.repoSummaries.forEach(r => {
|
|
280
|
+
sideHtml += '<div class="ov-item"><strong>' + esc(r.repoName) + '</strong>: ' + esc(r.role) + '</div>';
|
|
281
|
+
});
|
|
282
|
+
sideHtml += '</div>';
|
|
283
|
+
}
|
|
284
|
+
sidebar.innerHTML = sideHtml;
|
|
285
|
+
|
|
286
|
+
// Chart
|
|
287
|
+
const chartEl = document.getElementById("chart");
|
|
288
|
+
const width = chartEl.clientWidth;
|
|
289
|
+
const height = chartEl.clientHeight;
|
|
290
|
+
const svg = d3.select("#chart").append("svg").attr("width", width).attr("height", height);
|
|
291
|
+
const g = svg.append("g");
|
|
292
|
+
svg.call(d3.zoom().scaleExtent([0.1, 4]).on("zoom", (e) => g.attr("transform", e.transform)));
|
|
293
|
+
|
|
294
|
+
const simNodes = nodes.map(n => ({ ...n, x: width/2 + (Math.random()-0.5)*400, y: height/2 + (Math.random()-0.5)*400 }));
|
|
295
|
+
const simEdges = edges.map(e => ({ ...e }));
|
|
296
|
+
|
|
297
|
+
const simulation = d3.forceSimulation(simNodes)
|
|
298
|
+
.force("link", d3.forceLink(simEdges).id(d => d.id).distance(140))
|
|
299
|
+
.force("charge", d3.forceManyBody().strength(-400))
|
|
300
|
+
.force("center", d3.forceCenter(width/2, height/2))
|
|
301
|
+
.force("collision", d3.forceCollide().radius(60));
|
|
302
|
+
|
|
303
|
+
svg.append("defs").append("marker")
|
|
304
|
+
.attr("id", "arrowhead").attr("viewBox", "0 -5 10 10").attr("refX", 28).attr("refY", 0)
|
|
305
|
+
.attr("markerWidth", 8).attr("markerHeight", 8).attr("orient", "auto")
|
|
306
|
+
.append("path").attr("d", "M0,-5L10,0L0,5").attr("class", "arrowhead");
|
|
307
|
+
|
|
308
|
+
const link = g.append("g").selectAll("line").data(simEdges).join("line")
|
|
309
|
+
.attr("class", d => "edge-line" + (d.isCrossRepo ? " cross-repo" : ""))
|
|
310
|
+
.attr("marker-end", "url(#arrowhead)");
|
|
311
|
+
|
|
312
|
+
const nodeG = g.append("g").selectAll("g").data(simNodes).join("g").attr("cursor", "grab");
|
|
313
|
+
nodeG.append("rect").attr("class", "node-rect")
|
|
314
|
+
.attr("width", 120).attr("height", 48).attr("x", -60).attr("y", -24)
|
|
315
|
+
.attr("fill", d => getColor(d.kind)).attr("fill-opacity", 0.15)
|
|
316
|
+
.attr("stroke", d => getColor(d.kind));
|
|
317
|
+
nodeG.append("text").attr("class", "node-label").attr("dy", -4)
|
|
318
|
+
.text(d => d.name.length > 16 ? d.name.slice(0,15) + "…" : d.name);
|
|
319
|
+
nodeG.append("text").attr("class", "node-repo").attr("dy", 10)
|
|
320
|
+
.text(d => d.repoName || "").attr("fill", d => getRepoColor(d.repoName));
|
|
321
|
+
|
|
322
|
+
const tooltip = document.getElementById("tooltip");
|
|
323
|
+
nodeG.on("mouseenter", (e, d) => {
|
|
324
|
+
let html = '<div class="tt-name">' + esc(d.name) + '</div>';
|
|
325
|
+
html += '<div class="tt-kind">' + esc(d.kind) + (d.technology ? ' · ' + esc(d.technology) : '') + ' · ' + esc(d.repoName) + '</div>';
|
|
326
|
+
if (d.description) html += '<div class="tt-desc">' + esc(d.description) + '</div>';
|
|
327
|
+
tooltip.innerHTML = html;
|
|
328
|
+
tooltip.style.display = "block";
|
|
329
|
+
tooltip.style.left = (e.pageX + 12) + "px";
|
|
330
|
+
tooltip.style.top = (e.pageY + 12) + "px";
|
|
331
|
+
}).on("mousemove", (e) => {
|
|
332
|
+
tooltip.style.left = (e.pageX + 12) + "px";
|
|
333
|
+
tooltip.style.top = (e.pageY + 12) + "px";
|
|
334
|
+
}).on("mouseleave", () => { tooltip.style.display = "none"; });
|
|
335
|
+
|
|
336
|
+
nodeG.call(d3.drag()
|
|
337
|
+
.on("start", (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
|
|
338
|
+
.on("drag", (e, d) => { d.fx = e.x; d.fy = e.y; })
|
|
339
|
+
.on("end", (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; })
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
simulation.on("tick", () => {
|
|
343
|
+
link.attr("x1", d => d.source.x).attr("y1", d => d.source.y)
|
|
344
|
+
.attr("x2", d => d.target.x).attr("y2", d => d.target.y);
|
|
345
|
+
nodeG.attr("transform", d => "translate(" + d.x + "," + d.y + ")");
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Legend
|
|
349
|
+
const repoNames = [...new Set(nodes.map(n => n.repoName))];
|
|
350
|
+
const legend = document.getElementById("legend");
|
|
351
|
+
legend.innerHTML = "<h3>Repositories</h3>" + repoNames.map(name =>
|
|
352
|
+
'<div class="legend-item"><div class="legend-dot" style="background:' + getRepoColor(name) + '"></div>' + esc(name) + '</div>'
|
|
353
|
+
).join("");
|
|
354
|
+
</script>
|
|
355
|
+
</body>
|
|
356
|
+
</html>`;
|
|
357
|
+
}
|
|
358
|
+
function escapeHtml(s) {
|
|
359
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=architecture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"architecture.js","sourceRoot":"","sources":["../../src/render/architecture.ts"],"names":[],"mappings":"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"}
|