mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
feat(core): add non-code file support to GraphBuilder
Add addNonCodeFile() and addNonCodeFileWithAnalysis() methods that create graph nodes with the appropriate non-code types (document, config, service, table, endpoint, pipeline, schema, resource). addNonCodeFileWithAnalysis() creates child nodes for definitions, services, endpoints, steps, and resources with "contains" edges to the parent file. Add mapKindToNodeType() helper for mapping definition kinds to node types. Extend EXTENSION_LANGUAGE map with all non-code file extensions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -212,4 +212,130 @@ describe("GraphBuilder", () => {
|
||||
const graph = builder.build();
|
||||
expect(graph.project.languages).toEqual(["go", "javascript", "rust"]);
|
||||
});
|
||||
|
||||
describe("Non-code file support", () => {
|
||||
it("adds non-code file nodes with correct types", () => {
|
||||
const builder = new GraphBuilder("test", "abc123");
|
||||
builder.addNonCodeFile("README.md", {
|
||||
nodeType: "document",
|
||||
summary: "Project documentation",
|
||||
tags: ["documentation"],
|
||||
complexity: "simple",
|
||||
});
|
||||
const graph = builder.build();
|
||||
expect(graph.nodes).toHaveLength(1);
|
||||
expect(graph.nodes[0].type).toBe("document");
|
||||
expect(graph.nodes[0].id).toBe("file:README.md");
|
||||
});
|
||||
|
||||
it("adds non-code child nodes (definitions)", () => {
|
||||
const builder = new GraphBuilder("test", "abc123");
|
||||
builder.addNonCodeFileWithAnalysis("schema.sql", {
|
||||
nodeType: "file",
|
||||
summary: "Database schema",
|
||||
tags: ["database"],
|
||||
complexity: "moderate",
|
||||
definitions: [
|
||||
{ name: "users", kind: "table", lineRange: [1, 20] as [number, number], fields: ["id", "name", "email"] },
|
||||
],
|
||||
});
|
||||
const graph = builder.build();
|
||||
// File node + table child node
|
||||
expect(graph.nodes).toHaveLength(2);
|
||||
expect(graph.nodes[1].type).toBe("table");
|
||||
expect(graph.nodes[1].name).toBe("users");
|
||||
// Contains edge
|
||||
expect(graph.edges.some(e => e.type === "contains" && e.target.includes("users"))).toBe(true);
|
||||
});
|
||||
|
||||
it("adds service child nodes", () => {
|
||||
const builder = new GraphBuilder("test", "abc123");
|
||||
builder.addNonCodeFileWithAnalysis("docker-compose.yml", {
|
||||
nodeType: "config",
|
||||
summary: "Docker compose config",
|
||||
tags: ["infra"],
|
||||
complexity: "moderate",
|
||||
services: [
|
||||
{ name: "web", image: "node:22", ports: [3000] },
|
||||
{ name: "db", image: "postgres:15", ports: [5432] },
|
||||
],
|
||||
});
|
||||
const graph = builder.build();
|
||||
// File node + 2 service child nodes
|
||||
expect(graph.nodes).toHaveLength(3);
|
||||
expect(graph.nodes[1].type).toBe("service");
|
||||
expect(graph.nodes[1].name).toBe("web");
|
||||
expect(graph.nodes[2].type).toBe("service");
|
||||
expect(graph.nodes[2].name).toBe("db");
|
||||
});
|
||||
|
||||
it("adds endpoint child nodes", () => {
|
||||
const builder = new GraphBuilder("test", "abc123");
|
||||
builder.addNonCodeFileWithAnalysis("schema.graphql", {
|
||||
nodeType: "schema",
|
||||
summary: "GraphQL schema",
|
||||
tags: ["api"],
|
||||
complexity: "moderate",
|
||||
endpoints: [
|
||||
{ method: "Query", path: "users", lineRange: [5, 5] as [number, number] },
|
||||
],
|
||||
});
|
||||
const graph = builder.build();
|
||||
expect(graph.nodes).toHaveLength(2);
|
||||
expect(graph.nodes[1].type).toBe("endpoint");
|
||||
});
|
||||
|
||||
it("adds resource child nodes", () => {
|
||||
const builder = new GraphBuilder("test", "abc123");
|
||||
builder.addNonCodeFileWithAnalysis("main.tf", {
|
||||
nodeType: "resource",
|
||||
summary: "Terraform config",
|
||||
tags: ["infra"],
|
||||
complexity: "moderate",
|
||||
resources: [
|
||||
{ name: "aws_s3_bucket.main", kind: "aws_s3_bucket", lineRange: [1, 10] as [number, number] },
|
||||
],
|
||||
});
|
||||
const graph = builder.build();
|
||||
expect(graph.nodes).toHaveLength(2);
|
||||
expect(graph.nodes[1].type).toBe("resource");
|
||||
expect(graph.nodes[1].name).toBe("aws_s3_bucket.main");
|
||||
});
|
||||
|
||||
it("adds step child nodes", () => {
|
||||
const builder = new GraphBuilder("test", "abc123");
|
||||
builder.addNonCodeFileWithAnalysis("Makefile", {
|
||||
nodeType: "pipeline",
|
||||
summary: "Build targets",
|
||||
tags: ["build"],
|
||||
complexity: "simple",
|
||||
steps: [
|
||||
{ name: "build", lineRange: [1, 3] as [number, number] },
|
||||
{ name: "test", lineRange: [5, 7] as [number, number] },
|
||||
],
|
||||
});
|
||||
const graph = builder.build();
|
||||
expect(graph.nodes).toHaveLength(3);
|
||||
expect(graph.nodes[1].type).toBe("pipeline");
|
||||
expect(graph.nodes[1].name).toBe("build");
|
||||
});
|
||||
|
||||
it("detects non-code languages from EXTENSION_LANGUAGE map", () => {
|
||||
const builder = new GraphBuilder("test", "abc123");
|
||||
builder.addFile("config.yaml", { summary: "Config", tags: [], complexity: "simple" });
|
||||
const graph = builder.build();
|
||||
expect(graph.project.languages).toContain("yaml");
|
||||
});
|
||||
|
||||
it("detects new non-code extensions", () => {
|
||||
const builder = new GraphBuilder("test", "abc123");
|
||||
builder.addFile("schema.graphql", { summary: "Schema", tags: [], complexity: "simple" });
|
||||
builder.addFile("main.tf", { summary: "Terraform", tags: [], complexity: "simple" });
|
||||
builder.addFile("types.proto", { summary: "Protobuf", tags: [], complexity: "simple" });
|
||||
const graph = builder.build();
|
||||
expect(graph.project.languages).toContain("graphql");
|
||||
expect(graph.project.languages).toContain("terraform");
|
||||
expect(graph.project.languages).toContain("protobuf");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,12 @@ import type {
|
||||
GraphNode,
|
||||
GraphEdge,
|
||||
StructuralAnalysis,
|
||||
DefinitionInfo,
|
||||
ServiceInfo,
|
||||
EndpointInfo,
|
||||
StepInfo,
|
||||
ResourceInfo,
|
||||
SectionInfo,
|
||||
} from "../types.js";
|
||||
|
||||
interface FileMeta {
|
||||
@@ -16,7 +22,21 @@ interface FileAnalysisMeta extends FileMeta {
|
||||
fileSummary: string;
|
||||
}
|
||||
|
||||
interface NonCodeFileMeta extends FileMeta {
|
||||
nodeType: GraphNode["type"];
|
||||
}
|
||||
|
||||
interface NonCodeFileAnalysisMeta extends NonCodeFileMeta {
|
||||
definitions?: DefinitionInfo[];
|
||||
services?: ServiceInfo[];
|
||||
endpoints?: EndpointInfo[];
|
||||
steps?: StepInfo[];
|
||||
resources?: ResourceInfo[];
|
||||
sections?: SectionInfo[];
|
||||
}
|
||||
|
||||
const EXTENSION_LANGUAGE: Record<string, string> = {
|
||||
// Code languages
|
||||
".ts": "typescript",
|
||||
".tsx": "typescript",
|
||||
".js": "javascript",
|
||||
@@ -37,20 +57,41 @@ const EXTENSION_LANGUAGE: Record<string, string> = {
|
||||
".cs": "csharp",
|
||||
".php": "php",
|
||||
".lua": "lua",
|
||||
// Non-code languages
|
||||
".sh": "shell",
|
||||
".bash": "shell",
|
||||
".zsh": "shell",
|
||||
".json": "json",
|
||||
".jsonc": "json",
|
||||
".yaml": "yaml",
|
||||
".yml": "yaml",
|
||||
".toml": "toml",
|
||||
".xml": "xml",
|
||||
".html": "html",
|
||||
".htm": "html",
|
||||
".css": "css",
|
||||
".scss": "scss",
|
||||
".less": "less",
|
||||
".scss": "css",
|
||||
".less": "css",
|
||||
".md": "markdown",
|
||||
".mdx": "markdown",
|
||||
".sql": "sql",
|
||||
".graphql": "graphql",
|
||||
".gql": "graphql",
|
||||
".proto": "protobuf",
|
||||
".tf": "terraform",
|
||||
".tfvars": "terraform",
|
||||
".mk": "makefile",
|
||||
".env": "env",
|
||||
".csv": "csv",
|
||||
".tsv": "csv",
|
||||
".rst": "restructuredtext",
|
||||
".ps1": "powershell",
|
||||
".psm1": "powershell",
|
||||
".psd1": "powershell",
|
||||
".bat": "batch",
|
||||
".cmd": "batch",
|
||||
".txt": "plaintext",
|
||||
".svg": "xml",
|
||||
};
|
||||
|
||||
function detectLanguage(filePath: string): string {
|
||||
@@ -187,6 +228,129 @@ export class GraphBuilder {
|
||||
});
|
||||
}
|
||||
|
||||
addNonCodeFile(filePath: string, meta: NonCodeFileMeta): void {
|
||||
const lang = detectLanguage(filePath);
|
||||
if (lang !== "unknown") this.languages.add(lang);
|
||||
const name = filePath.split("/").pop() ?? filePath;
|
||||
this.nodes.push({
|
||||
id: `file:${filePath}`,
|
||||
type: meta.nodeType,
|
||||
name,
|
||||
filePath,
|
||||
summary: meta.summary,
|
||||
tags: meta.tags,
|
||||
complexity: meta.complexity,
|
||||
});
|
||||
}
|
||||
|
||||
addNonCodeFileWithAnalysis(filePath: string, meta: NonCodeFileAnalysisMeta): void {
|
||||
this.addNonCodeFile(filePath, meta);
|
||||
const fileId = `file:${filePath}`;
|
||||
|
||||
// Create child nodes for definitions (tables, schemas, etc.)
|
||||
for (const def of meta.definitions ?? []) {
|
||||
const childId = `${def.kind}:${filePath}:${def.name}`;
|
||||
this.nodes.push({
|
||||
id: childId,
|
||||
type: this.mapKindToNodeType(def.kind),
|
||||
name: def.name,
|
||||
filePath,
|
||||
lineRange: def.lineRange,
|
||||
summary: `${def.kind}: ${def.name} (${def.fields.length} fields)`,
|
||||
tags: [],
|
||||
complexity: meta.complexity,
|
||||
});
|
||||
this.edges.push({ source: fileId, target: childId, type: "contains", direction: "forward", weight: 1 });
|
||||
}
|
||||
|
||||
// Create child nodes for services
|
||||
for (const svc of meta.services ?? []) {
|
||||
const childId = `service:${filePath}:${svc.name}`;
|
||||
this.nodes.push({
|
||||
id: childId,
|
||||
type: "service",
|
||||
name: svc.name,
|
||||
filePath,
|
||||
summary: `Service ${svc.name}${svc.image ? ` (image: ${svc.image})` : ""}`,
|
||||
tags: [],
|
||||
complexity: meta.complexity,
|
||||
});
|
||||
this.edges.push({ source: fileId, target: childId, type: "contains", direction: "forward", weight: 1 });
|
||||
}
|
||||
|
||||
// Create child nodes for endpoints
|
||||
for (const ep of meta.endpoints ?? []) {
|
||||
const childId = `endpoint:${filePath}:${ep.path}`;
|
||||
this.nodes.push({
|
||||
id: childId,
|
||||
type: "endpoint",
|
||||
name: `${ep.method ?? ""} ${ep.path}`.trim(),
|
||||
filePath,
|
||||
lineRange: ep.lineRange,
|
||||
summary: `Endpoint: ${ep.method ?? ""} ${ep.path}`.trim(),
|
||||
tags: [],
|
||||
complexity: meta.complexity,
|
||||
});
|
||||
this.edges.push({ source: fileId, target: childId, type: "contains", direction: "forward", weight: 1 });
|
||||
}
|
||||
|
||||
// Create child nodes for steps (pipeline/makefile targets)
|
||||
for (const step of meta.steps ?? []) {
|
||||
const childId = `step:${filePath}:${step.name}`;
|
||||
this.nodes.push({
|
||||
id: childId,
|
||||
type: "pipeline",
|
||||
name: step.name,
|
||||
filePath,
|
||||
lineRange: step.lineRange,
|
||||
summary: `Step: ${step.name}`,
|
||||
tags: [],
|
||||
complexity: meta.complexity,
|
||||
});
|
||||
this.edges.push({ source: fileId, target: childId, type: "contains", direction: "forward", weight: 1 });
|
||||
}
|
||||
|
||||
// Create child nodes for resources (Terraform, etc.)
|
||||
for (const res of meta.resources ?? []) {
|
||||
const childId = `resource:${filePath}:${res.name}`;
|
||||
this.nodes.push({
|
||||
id: childId,
|
||||
type: "resource",
|
||||
name: res.name,
|
||||
filePath,
|
||||
lineRange: res.lineRange,
|
||||
summary: `Resource: ${res.name} (${res.kind})`,
|
||||
tags: [],
|
||||
complexity: meta.complexity,
|
||||
});
|
||||
this.edges.push({ source: fileId, target: childId, type: "contains", direction: "forward", weight: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
private mapKindToNodeType(kind: string): GraphNode["type"] {
|
||||
const mapping: Record<string, GraphNode["type"]> = {
|
||||
table: "table",
|
||||
view: "table",
|
||||
index: "table",
|
||||
message: "schema",
|
||||
type: "schema",
|
||||
enum: "schema",
|
||||
resource: "resource",
|
||||
module: "resource",
|
||||
service: "service",
|
||||
deployment: "service",
|
||||
job: "pipeline",
|
||||
stage: "pipeline",
|
||||
target: "pipeline",
|
||||
route: "endpoint",
|
||||
query: "endpoint",
|
||||
mutation: "endpoint",
|
||||
variable: "config",
|
||||
output: "config",
|
||||
};
|
||||
return mapping[kind] ?? "concept";
|
||||
}
|
||||
|
||||
build(): KnowledgeGraph {
|
||||
return {
|
||||
version: "1.0.0",
|
||||
|
||||
Reference in New Issue
Block a user