feat(agents): add non-code pattern detection to architecture analyzer

Add deployment topology detection (Dockerfile -> compose -> K8s), data
pipeline detection (schema -> migration -> API -> client), documentation
coverage analysis, and cross-category dependency analysis. Update layer
hints for infrastructure, documentation, data, ci-cd, and configuration
layers. Expand directory pattern matching with non-code patterns. Increase
max layer count from 7 to 10 to accommodate non-code layers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lum1104
2026-03-28 18:50:29 +08:00
co-authored by Claude Opus 4.6
parent c1eda8395f
commit 7c8f056447
@@ -2,11 +2,11 @@
> Used by `/understand` Phase 4. Dispatch as a subagent with this full content as the prompt.
You are an expert software architect. Your job is to analyze a codebase's file structure, summaries, and import relationships to identify logical architectural layers and assign every file to exactly one layer. Your layer assignments must be well-reasoned and reflect the actual organization of the code.
You are an expert software architect. Your job is to analyze a codebase's file structure, summaries, and import relationships to identify logical architectural layers and assign every file to exactly one layer. Your layer assignments must be well-reasoned and reflect the actual organization of the code, including non-code files like configs, documentation, infrastructure, and data schemas.
## Task
Given a list of file nodes (with paths, summaries, tags) and import edges, identify 3-7 logical architecture layers and assign every file node to exactly one layer. You will accomplish this in two phases: first, write and execute a script that computes structural patterns from the import graph and file paths; second, use those structural insights to make semantic layer assignments.
Given a list of file nodes (with paths, summaries, tags, and node types) and import edges, identify 3-10 logical architecture layers and assign every file node to exactly one layer. You will accomplish this in two phases: first, write and execute a script that computes structural patterns from the import graph and file paths; second, use those structural insights to make semantic layer assignments.
---
@@ -20,10 +20,18 @@ Write a Node.js script that analyzes the file paths and import edges to compute
```json
{
"fileNodes": [
{"id": "file:src/routes/index.ts", "name": "index.ts", "filePath": "src/routes/index.ts", "summary": "...", "tags": ["api-handler"]}
{"id": "file:src/routes/index.ts", "type": "file", "name": "index.ts", "filePath": "src/routes/index.ts", "summary": "...", "tags": ["api-handler"]},
{"id": "config:tsconfig.json", "type": "config", "name": "tsconfig.json", "filePath": "tsconfig.json", "summary": "...", "tags": ["configuration"]},
{"id": "document:README.md", "type": "document", "name": "README.md", "filePath": "README.md", "summary": "...", "tags": ["documentation"]},
{"id": "service:Dockerfile", "type": "service", "name": "Dockerfile", "filePath": "Dockerfile", "summary": "...", "tags": ["infrastructure"]}
],
"importEdges": [
{"source": "file:src/routes/index.ts", "target": "file:src/services/auth.ts", "type": "imports"}
],
"allEdges": [
{"source": "file:src/routes/index.ts", "target": "file:src/services/auth.ts", "type": "imports"},
{"source": "config:tsconfig.json", "target": "file:src/index.ts", "type": "configures"},
{"source": "service:Dockerfile", "target": "file:src/index.ts", "type": "deploys"}
]
}
```
@@ -42,13 +50,31 @@ Group all file node IDs by their top-level directory (first path segment after t
If the project has a flat structure (all files in one directory), group by second-level directory or by filename pattern.
**B. Import Adjacency Matrix**
**B. Node Type Grouping**
Group all file node IDs by their node type (`file`, `config`, `document`, `service`, `pipeline`, `table`, `schema`, `resource`, `endpoint`). This reveals the distribution of code vs. non-code files.
**C. Import Adjacency Matrix**
Build an adjacency list of which files import which other files. Compute:
- For each file: fan-out (how many files it imports) and fan-in (how many files import it)
- For each directory group: the set of other groups it imports from and is imported by
**C. Inter-Group Import Frequency**
**D. Cross-Category Dependency Analysis**
Using `allEdges`, compute cross-category relationships:
- Count edges of each type between node type groups (e.g., config→file configures edges, service→file deploys edges)
- Identify which non-code nodes connect to which code nodes
- Output a matrix:
```
config -> file: 5 (configures)
document -> file: 3 (documents)
service -> file: 2 (deploys)
pipeline -> file: 1 (triggers)
schema -> file: 2 (defines_schema)
```
**E. Inter-Group Import Frequency**
For every pair of directory groups, count the number of import edges between them. Produce a matrix:
```
@@ -60,11 +86,11 @@ services -> utils: 5
This reveals dependency direction between groups.
**D. Intra-Group Import Density**
**F. Intra-Group Import Density**
For each directory group, count how many import edges exist between files within the same group versus total edges involving that group. High intra-group density suggests the group is cohesive and should be its own layer.
**E. Directory Pattern Matching**
**G. Directory Pattern Matching**
Classify each directory name against known architectural patterns:
@@ -100,6 +126,13 @@ Classify each directory name against known architectural patterns:
| `blueprints` | `api` |
| `mailers`, `jobs`, `channels` | `service` |
| `bin` | `entry` |
| `docs`, `documentation`, `wiki` | `documentation` |
| `deploy`, `deployment`, `infra`, `infrastructure` | `infrastructure` |
| `.github`, `.gitlab`, `.circleci` | `ci-cd` |
| `k8s`, `kubernetes`, `helm`, `charts` | `infrastructure` |
| `terraform`, `tf` | `infrastructure` |
| `docker` | `infrastructure` |
| `sql`, `database`, `schema` | `data` |
Also check file-level patterns:
- Files matching `*.test.*` or `*.spec.*` or `test_*.py` or `*_test.go` or `*Test.java` or `*_spec.rb` or `*Test.php` or `*Tests.cs` -> `test`
@@ -112,8 +145,68 @@ Also check file-level patterns:
- Files named `Application.java` or `Program.cs` -> `entry` (JVM / .NET entry points)
- Files named `config.ru` -> `entry` (Ruby Rack entry point)
- Files named `Cargo.toml`, `go.mod`, `Gemfile`, `pom.xml`, `build.gradle`, `composer.json` -> `config` (language-level project config)
- `Dockerfile`, `docker-compose.*` -> `infrastructure`
- `*.tf`, `*.tfvars` -> `infrastructure`
- `.github/workflows/*`, `.gitlab-ci.yml`, `Jenkinsfile` -> `ci-cd`
- `*.sql` -> `data`
- `*.graphql`, `*.gql`, `*.proto` -> `types`
- `*.md`, `*.rst` -> `documentation`
- `Makefile` -> `infrastructure`
**F. Dependency Direction**
**H. Deployment Topology Detection**
Identify deployment-related files and their relationships:
- Look for Dockerfile → docker-compose → K8s manifests chains
- Detect multi-environment configurations (e.g., Dockerfile.dev, Dockerfile.prod, docker-compose.prod.yml)
- Identify infrastructure-as-code layering (Terraform modules, CloudFormation stacks)
Output:
```json
"deploymentTopology": {
"hasDockerfile": true,
"hasCompose": true,
"hasK8s": false,
"hasTerraform": false,
"hasCI": true,
"infraFiles": ["Dockerfile", "docker-compose.yml", ".github/workflows/ci.yml"]
}
```
**I. Data Pipeline Detection**
Identify data flow patterns:
- Schema definition files → migration files → API endpoint handlers → client code
- Database schemas → ORM models → service layer → API layer
- Protobuf/GraphQL definitions → generated code → service handlers
Output:
```json
"dataPipeline": {
"schemaFiles": ["schema.sql", "schema.graphql"],
"migrationFiles": ["migrations/001_init.sql"],
"dataModelFiles": ["src/models/user.ts"],
"apiHandlerFiles": ["src/routes/users.ts"]
}
```
**J. Documentation Coverage**
For each directory group, check if there are documentation files:
- Does the directory have a README.md?
- Are there docs/*.md files that reference code in this group?
- Calculate a coverage ratio: groups-with-docs / total-groups
Output:
```json
"docCoverage": {
"groupsWithDocs": 3,
"totalGroups": 7,
"coverageRatio": 0.43,
"undocumentedGroups": ["middleware", "utils", "state", "types"]
}
```
**K. Dependency Direction**
For each pair of groups with imports between them, determine the dominant direction. If group A imports from group B more than B imports from A, then A depends on B. Output this as a list of directed dependency relationships.
@@ -127,6 +220,17 @@ For each pair of groups with imports between them, determine the dominant direct
"services": ["file:src/services/auth.ts", "file:src/services/user.ts"],
"utils": ["file:src/utils/format.ts"]
},
"nodeTypeGroups": {
"file": ["file:src/index.ts", "file:src/utils.ts"],
"config": ["config:tsconfig.json", "config:package.json"],
"document": ["document:README.md"],
"service": ["service:Dockerfile"],
"pipeline": ["pipeline:.github/workflows/ci.yml"]
},
"crossCategoryEdges": [
{"fromType": "config", "toType": "file", "edgeType": "configures", "count": 5},
{"fromType": "service", "toType": "file", "edgeType": "deploys", "count": 2}
],
"interGroupImports": [
{"from": "routes", "to": "services", "count": 12},
{"from": "services", "to": "utils", "count": 5}
@@ -140,13 +244,34 @@ For each pair of groups with imports between them, determine the dominant direct
"services": "service",
"utils": "utility"
},
"deploymentTopology": {
"hasDockerfile": true,
"hasCompose": true,
"hasK8s": false,
"hasTerraform": false,
"hasCI": true,
"infraFiles": ["Dockerfile", "docker-compose.yml", ".github/workflows/ci.yml"]
},
"dataPipeline": {
"schemaFiles": [],
"migrationFiles": [],
"dataModelFiles": ["src/models/user.ts"],
"apiHandlerFiles": ["src/routes/users.ts"]
},
"docCoverage": {
"groupsWithDocs": 1,
"totalGroups": 5,
"coverageRatio": 0.2,
"undocumentedGroups": ["services", "utils", "routes"]
},
"dependencyDirection": [
{"dependent": "routes", "dependsOn": "services"},
{"dependent": "services", "dependsOn": "utils"}
],
"fileStats": {
"totalFileNodes": 42,
"filesPerGroup": {"routes": 8, "services": 12, "utils": 5}
"filesPerGroup": {"routes": 8, "services": 12, "utils": 5},
"nodeTypeCounts": {"file": 30, "config": 5, "document": 3, "service": 2, "pipeline": 2}
},
"fileFanIn": {
"file:src/utils/format.ts": 15,
@@ -166,8 +291,9 @@ Before writing the script, create its input JSON file:
```bash
cat > $PROJECT_ROOT/.understand-anything/tmp/ua-arch-input.json << 'ENDJSON'
{
"fileNodes": [<file nodes from prompt>],
"importEdges": [<import edges from prompt>]
"fileNodes": [<file nodes from prompt — all node types>],
"importEdges": [<import edges from prompt>],
"allEdges": [<all edges from prompt including configures, documents, deploys, etc.>]
}
ENDJSON
```
@@ -203,25 +329,55 @@ Use the `dependencyDirection` data to understand the project's layering:
- Middle layers depend on bottom layers (Data, Utility, Types)
- This forms a dependency hierarchy that should map to your layer ordering
### Step 3 -- Consider File Summaries and Tags
### Step 3 -- Consider Non-Code Layers
Use `nodeTypeGroups` and `deploymentTopology` to determine if non-code layers are warranted:
- **Infrastructure layer:** Create if the project has Dockerfiles, Terraform, K8s manifests, or other deployment files. Include all `service` and `resource` type nodes.
- **CI/CD layer:** Create if the project has CI/CD configs (.github/workflows, .gitlab-ci.yml, Jenkinsfile). Include all `pipeline` type nodes. May be merged with Infrastructure if few files.
- **Documentation layer:** Create if the project has 3+ documentation files (README, guides, API docs). Include all `document` type nodes. May be merged with a "Project" or "Root" layer if few files.
- **Data layer:** Create if the project has SQL, GraphQL, Protobuf, or other schema files. Include `table`, `schema`, and `endpoint` type nodes. May be merged with an existing "Data" or "Models" layer.
- **Configuration layer:** Create if the project has 3+ config files beyond just package.json. Include all `config` type nodes. May be merged with a "Root" or "Project" layer if few files.
**Merging guidance:** For small projects, merge non-code layers into a single "Project Support" or "Infrastructure & Config" layer rather than creating many single-file layers. For larger projects, separate them into distinct layers.
### Step 4 -- Consider File Summaries and Tags
When directory structure alone is ambiguous (e.g., a flat `src/` directory with no subdirectories), use the file summaries and tags from the input data to determine each file's role. Think about what responsibility the file fulfills in the system.
### Step 4 -- Select 3-7 Layers
### Step 5 -- Select 3-10 Layers
Choose layers based on the project's actual architecture, informed by the script's structural data. Common patterns include:
- **Layered architecture:** API -> Service -> Data
- **Component-based:** UI Components, State, Services, Utils
- **MVC:** Models, Views, Controllers
- **Monorepo packages:** Each package forms its own layer
- **Library:** Core, Plugins, Types, Tests
- **Layered architecture:** API -> Service -> Data + Infrastructure + Config
- **Component-based:** UI Components, State, Services, Utils, Infrastructure
- **MVC:** Models, Views, Controllers + Config + Docs
- **Monorepo packages:** Each package forms its own layer + shared infra
- **Library:** Core, Plugins, Types, Tests, Documentation
**Layer hint for non-code files:**
| Pattern | Suggested Layer |
|---|---|
| Dockerfile, docker-compose.*, K8s manifests, Terraform | `layer:infrastructure` |
| .github/workflows/*, .gitlab-ci.yml, Jenkinsfile | `layer:ci-cd` or merge into `layer:infrastructure` |
| README.md, docs/*.md, CONTRIBUTING.md, CHANGELOG.md | `layer:documentation` or merge into relevant code layer |
| *.sql, migrations/*.sql | `layer:data` |
| *.graphql, *.proto, *.prisma | `layer:data` or `layer:types` |
| package.json, tsconfig.json, *.toml, *.yaml configs | `layer:config` or merge into relevant code layer |
Merge small directory groups into larger layers when they share a common purpose. Prefer fewer, well-defined layers over many granular ones.
### Step 5 -- Assign Every File Node
### Step 6 -- Assign Every File Node
Go through each file node ID from the input and assign it to exactly one layer. Use the `directoryGroups` mapping as the primary assignment mechanism -- most files in the same directory group should end up in the same layer.
For non-code files, use the node type as the primary signal:
- `config` nodes → Configuration or root layer
- `document` nodes → Documentation layer
- `service`, `resource` nodes → Infrastructure layer
- `pipeline` nodes → CI/CD or Infrastructure layer
- `table`, `schema`, `endpoint` nodes → Data layer
For files that do not clearly fit any layer, place them in the most relevant layer or create a "Shared" / "Utility" catch-all layer. Do not leave any file unassigned.
**Cross-check:** The sum of all `nodeIds` array lengths across all layers MUST equal the total number of file nodes from the input (`fileStats.totalFileNodes` from the script output).
@@ -231,6 +387,7 @@ For files that do not clearly fit any layer, place them in the most relevant lay
Use `layer:<kebab-case>` format consistently:
- `layer:api`, `layer:service`, `layer:data`, `layer:ui`, `layer:middleware`
- `layer:utility`, `layer:config`, `layer:test`, `layer:types`, `layer:state`
- `layer:infrastructure`, `layer:documentation`, `layer:ci-cd`
## Output Format
@@ -250,6 +407,30 @@ Produce a single, valid JSON array. Every field shown is **required**.
"description": "Core business logic, domain services, and orchestration",
"nodeIds": ["file:src/services/auth.ts", "file:src/services/user.ts"]
},
{
"id": "layer:infrastructure",
"name": "Infrastructure",
"description": "Container definitions, deployment configurations, and CI/CD pipelines",
"nodeIds": ["service:Dockerfile", "service:docker-compose.yml", "pipeline:.github/workflows/ci.yml"]
},
{
"id": "layer:documentation",
"name": "Documentation",
"description": "Project documentation, guides, and API references",
"nodeIds": ["document:README.md", "document:docs/getting-started.md"]
},
{
"id": "layer:data",
"name": "Data Layer",
"description": "Database schemas, migrations, and data model definitions",
"nodeIds": ["table:migrations/001.sql:users", "schema:schema.graphql"]
},
{
"id": "layer:config",
"name": "Configuration",
"description": "Project configuration files and build settings",
"nodeIds": ["config:tsconfig.json", "config:package.json"]
},
{
"id": "layer:utility",
"name": "Utility Layer",
@@ -267,11 +448,11 @@ Produce a single, valid JSON array. Every field shown is **required**.
## Critical Constraints
- EVERY file node ID from the input MUST appear in exactly one layer's `nodeIds` array. Missing file assignments break the downstream pipeline.
- EVERY file node ID from the input MUST appear in exactly one layer's `nodeIds` array. Missing file assignments break the downstream pipeline. This includes non-code nodes (config, document, service, pipeline, table, schema, resource, endpoint).
- NEVER include node IDs in `nodeIds` that were not provided in the input. Do not invent node IDs.
- NEVER create a layer with an empty `nodeIds` array.
- ALWAYS verify your output accounts for all input file nodes. Count them: the sum of all `nodeIds` array lengths must equal the total number of input file nodes.
- Keep to 3-7 layers. If the project is very small (under 10 files), 3 layers is sufficient. If large (100+ files), up to 7 is appropriate.
- Keep to 3-10 layers. If the project is very small (under 10 files), 3 layers is sufficient. If large (100+ files), up to 10 is appropriate.
- Layer `description` must be specific to this project, not generic boilerplate.
- Trust the script's structural analysis. Do NOT re-read source files or re-count imports. The script's adjacency data, density calculations, and pattern matches are deterministic and reliable.