fix: address code review findings for universal file type support

- Expand persona filter in GraphView.tsx to show all 9 file-level node
  types (file, config, document, service, table, endpoint, pipeline,
  schema, resource) instead of only "file", fixing invisible nodes
- Add serves, provisions, and routes edge types with creation criteria
  to file-analyzer-prompt.md
- Fix related edge weight from 0.3 to 0.5 to match SKILL.md default
- Add filenames to openapiConfig for proper filename-based detection
- Add TODO comments to kubernetesConfig and jsonSchemaConfig explaining
  content-based detection limitations
- Clarify that module/concept types are reserved for higher-level agents
- Update language-registry tests for 38 built-in configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lum1104
2026-03-28 19:14:00 +08:00
co-authored by Claude Opus 4.6
parent 376c235d5e
commit 71468e8082
6 changed files with 85 additions and 9 deletions
@@ -32,9 +32,9 @@ describe("LanguageRegistry", () => {
expect(registry.getForFile("file.unknown")).toBeNull();
});
it("returns null for files without extensions", () => {
it("returns null for files without extensions and no filename match", () => {
const registry = new LanguageRegistry();
expect(registry.getForFile("Makefile")).toBeNull();
expect(registry.getForFile("SOMEFILE")).toBeNull();
});
it("lists all registered languages", () => {
@@ -48,10 +48,10 @@ describe("LanguageRegistry", () => {
});
describe("createDefault", () => {
it("registers all 12 built-in language configs", () => {
it("registers all 38 built-in language configs", () => {
const registry = LanguageRegistry.createDefault();
const all = registry.getAllLanguages();
expect(all.length).toBe(12);
expect(all.length).toBe(38);
});
it("maps all expected extensions", () => {
@@ -88,4 +88,56 @@ describe("LanguageRegistry", () => {
}
});
});
describe("Non-code language configs", () => {
it("detects all non-code file types via extension", () => {
const registry = LanguageRegistry.createDefault();
const expectations: [string, string][] = [
["README.md", "markdown"],
["config.yaml", "yaml"],
["package.json", "json"],
["config.toml", "toml"],
[".env", "env"],
["pom.xml", "xml"],
["Dockerfile", "dockerfile"],
["schema.sql", "sql"],
["schema.graphql", "graphql"],
["types.proto", "protobuf"],
["main.tf", "terraform"],
["Makefile", "makefile"],
["deploy.sh", "shell"],
["index.html", "html"],
["styles.css", "css"],
["data.csv", "csv"],
["deploy.ps1", "powershell"],
];
for (const [file, expectedId] of expectations) {
const config = registry.getForFile(file);
expect(config?.id, `${file} should be detected as ${expectedId}`).toBe(expectedId);
}
});
it("detects filename-based configs (Dockerfile, Makefile, Jenkinsfile)", () => {
const registry = LanguageRegistry.createDefault();
expect(registry.getForFile("Dockerfile")?.id).toBe("dockerfile");
expect(registry.getForFile("Makefile")?.id).toBe("makefile");
expect(registry.getForFile("Jenkinsfile")?.id).toBe("jenkinsfile");
expect(registry.getForFile("src/Dockerfile")?.id).toBe("dockerfile");
expect(registry.getForFile("build/Makefile")?.id).toBe("makefile");
});
it("detects filename-based configs for docker-compose", () => {
const registry = LanguageRegistry.createDefault();
expect(registry.getForFile("docker-compose.yml")?.id).toBe("docker-compose");
expect(registry.getForFile("docker-compose.yaml")?.id).toBe("docker-compose");
expect(registry.getForFile("compose.yml")?.id).toBe("docker-compose");
});
it("detects .env file variants", () => {
const registry = LanguageRegistry.createDefault();
expect(registry.getForFile(".env")?.id).toBe("env");
expect(registry.getForFile(".env.local")?.id).toBe("env");
expect(registry.getForFile(".env.production")?.id).toBe("env");
});
});
});
@@ -1,5 +1,9 @@
import type { LanguageConfig } from "../types.js";
// TODO: JSON Schema files have no unique extension — *.schema.json files will match
// `jsonConfigConfig` by the `.json` extension. Detection requires content-based
// heuristics (e.g., checking for `"$schema"` or `"type"` keys at the root level).
// A future content-based detection pass could re-classify them as JSON Schema.
export const jsonSchemaConfig = {
id: "json-schema",
displayName: "JSON Schema",
@@ -1,5 +1,10 @@
import type { LanguageConfig } from "../types.js";
// TODO: Kubernetes manifests are YAML files with no unique extension or filename.
// Detection requires content-based or path-pattern heuristics (e.g., checking for
// `apiVersion`/`kind` fields in YAML, or matching paths like `k8s/`, `kubernetes/`,
// `deploy/`). Currently these files will match `yamlConfig` by extension (.yaml/.yml).
// A future content-based detection pass could re-classify them as Kubernetes.
export const kubernetesConfig = {
id: "kubernetes",
displayName: "Kubernetes",
@@ -4,6 +4,7 @@ export const openapiConfig = {
id: "openapi",
displayName: "OpenAPI",
extensions: [],
filenames: ["openapi.yaml", "openapi.json", "swagger.yaml", "swagger.json"],
concepts: ["paths", "operations", "schemas", "parameters", "responses", "security schemes", "tags", "servers"],
filePatterns: {
entryPoints: ["openapi.yaml", "swagger.yaml"],
@@ -228,12 +228,20 @@ function useLayerDetailTopology() {
table: "data", endpoint: "data", schema: "data",
};
// Non-technical persona only sees concept/module/file nodes
// All top-level (file-level) node types that should appear in the graph.
// This includes the 8 new non-code types plus the original "file" type.
const fileLevelTypes = new Set([
"file", "config", "document", "service", "table",
"endpoint", "pipeline", "schema", "resource",
]);
// Non-technical persona: show module, concept, and file-level types (hide function/class)
// Junior/experienced persona: show everything including function/class
let filteredGraphNodes = persona === "non-technical"
? graph.nodes.filter(
(n) => layerNodeIds.has(n.id) && (n.type === "concept" || n.type === "module" || n.type === "file"),
(n) => layerNodeIds.has(n.id) && (n.type === "concept" || n.type === "module" || fileLevelTypes.has(n.type)),
)
: graph.nodes.filter((n) => layerNodeIds.has(n.id) && n.type === "file");
: graph.nodes.filter((n) => layerNodeIds.has(n.id) && (fileLevelTypes.has(n.type) || n.type === "module" || n.type === "concept" || n.type === "function" || n.type === "class"));
// Apply node type category filters
filteredGraphNodes = filteredGraphNodes.filter((n) => {
@@ -403,7 +403,10 @@ Using the script's structural data and file categories, create edges:
| `migrates` | SQL migration file modifies a table/schema (e.g., ALTER TABLE, CREATE TABLE) | `0.7` | `forward` |
| `triggers` | CI/CD config triggers a pipeline or deployment (e.g., GitHub Actions workflow deploys on push to main) | `0.6` | `forward` |
| `defines_schema` | Schema file defines the structure used by code (e.g., GraphQL schema defines API types, Protobuf defines message format) | `0.8` | `forward` |
| `related` | Non-code file is topically related to another file without a specific structural relationship | `0.3` | `forward` |
| `serves` | K8s Service/Deployment exposes an endpoint, or a reverse proxy routes to a service | `0.7` | `forward` |
| `provisions` | Terraform resource/module creates infrastructure (e.g., creates a database, provisions a VM) | `0.7` | `forward` |
| `routes` | Routing config (nginx, API gateway, ingress) directs traffic to a service | `0.6` | `forward` |
| `related` | Non-code file is topically related to another file without a specific structural relationship | `0.5` | `forward` |
| `depends_on` | Non-code file depends on another file (e.g., docker-compose depends on Dockerfile, CI workflow depends on Makefile targets) | `0.6` | `forward` |
**Import edge creation rule for code files:** For each resolved path in `batchImportData[filePath]` (provided in the input JSON), create an `imports` edge from the current file node to `file:<resolvedPath>`. The `batchImportData` values contain only resolved project-internal paths — external packages have already been filtered out. Do NOT attempt to re-resolve imports from source.
@@ -415,6 +418,9 @@ Using the script's structural data and file categories, create edges:
- **SQL files:** Create `migrates` edges between migration files and the table nodes they modify. Create `defines_schema` edges from schema files to API handlers that serve that data.
- **CI configs:** Create `triggers` edges to the deployment targets or test suites they invoke.
- **GraphQL/Protobuf schemas:** Create `defines_schema` edges to the code files that implement the resolvers or service handlers.
- **K8s manifests:** Create `serves` edges when a Service/Deployment exposes an endpoint or routes to a container. Create `deploys` edges to the application code that runs inside the container.
- **Terraform files:** Create `provisions` edges from Terraform resource/module definitions to the infrastructure they create (e.g., database resources, VM instances).
- **Routing configs (nginx, API gateway, ingress):** Create `routes` edges from routing configuration to the services they direct traffic to.
Do NOT use edge types not listed in the tables above.
@@ -536,7 +542,7 @@ Produce a single, valid JSON block. Validate it mentally before writing -- malfo
**Required fields for every node:**
- `id` (string) -- must follow the ID conventions above
- `type` (string) -- one of: `file`, `function`, `class`, `config`, `document`, `service`, `table`, `endpoint`, `pipeline`, `schema`, `resource`
- `type` (string) -- one of: `file`, `function`, `class`, `config`, `document`, `service`, `table`, `endpoint`, `pipeline`, `schema`, `resource` (11 of the 13 schema types; `module` and `concept` are reserved for higher-level analysis agents)
- `name` (string) -- display name (filename for file nodes, function/class name for others)
- `summary` (string) -- 1-2 sentence description, NEVER empty
- `tags` (string[]) -- 3-5 lowercase hyphenated tags, NEVER empty