| name | structural-index |
| description | Generate and query structural indexes for Drupal projects. Parses services.yml, routing.yml, hooks, plugins, and entity types to build an automated dependency graph. Use for blast radius analysis, service dependency mapping, and structural queries. Commands include `/structural-index` to generate, and structural query prefixes via discover (e.g., `discover service:entity_type.manager`, `discover deps:AUTH`). |
| metadata | {"status":"stable","drupal-version":"all","last-reviewed":"2026-04"} |
Structural Index Skill
Automated structural awareness for Drupal projects — parses declarative configs to map services, routes, hooks, plugins, and entities.
Project-Agnostic: Works with any Drupal project.
Why Use This
Fills the gap between semantic docs and raw codebase (Glob/Grep):
Layer 3: Semantic docs (@semantic-architect generated, business "why")
Layer 2: Structural index (script-generated, Drupal-aware "what" + "how connected")
Layer 1: Raw codebase (Glob/Grep/Read)
Layer 3 is generated by the @semantic-architect agent via /drupal-semantic. It reads Layer 2 + source code to produce business index, tech specs, and business schemas.
Without structural index:
- Manually grep for service dependencies
- Miss cross-module hook interactions
- No blast radius awareness for changes
With structural index:
- Instant service dependency graph
- Hook/event chain visibility
- Feature impact analysis via
discover deps:FEATURE
Generate Index
$SKILL_DIR/scripts/generate-all.sh [project-dir]
Generates:
docs/semantic/structural/services.md — Service IDs, classes, injected dependencies
docs/semantic/structural/routes.md — Route names, paths, controllers, access
docs/semantic/structural/hooks.md — Hook implementations (procedural + OOP)
docs/semantic/structural/plugins.md — Block, FieldType, Action, QueueWorker plugins
docs/semantic/structural/entities.md — Content and config entity types (with Fields column)
docs/semantic/structural/schemas.md — Entity schema summary (fields, references, lists per bundle)
docs/semantic/structural/base-fields.md — Base field registry (from PHP baseFieldDefinitions())
docs/semantic/structural/permissions.md — Permission registry (from *.permissions.yml)
docs/semantic/structural/methods.md — Method index (public methods in Service/Controller/Form)
docs/semantic/schemas/*.json — Per-bundle JSON schemas with field types, labels, cardinality, target types/bundles, allowed values
docs/semantic/schemas/*.base-fields.json — Per-entity base field schemas with entity metadata, field types, settings
docs/semantic/DEPENDENCY_GRAPH.md — Cross-reference dependency analysis
docs/semantic/FEATURE_MAP.md — Feature overview with structural counts
Check Staleness
$SKILL_DIR/scripts/check-staleness.sh [project-dir]
Reports which structural files need regeneration based on file modification times.
Query via Discover
Once generated, query structural data through the discover skill:
discover service:entity_type.manager
discover svc:config.factory
discover route:/admin/config
discover path:/node
discover hook:node_presave
discover plugin:Block
discover entity:node
discover schema:node
discover perm:administer
discover method:Timesheet
discover deps:AUTH
discover impact:my_module
Output Formats
services.md
| Service ID | Class | Dependencies | Module | Tags |
|------------|-------|--------------|--------|------|
| my_module.service | Drupal\my_module\Service | @entity_type.manager, @config.factory | my_module | event_subscriber |
routes.md
| Route Name | Path | Controller/Form | Access | Module |
|------------|------|-----------------|--------|--------|
| my_module.settings | /admin/config/my-module | MyController::settings | administer site | my_module |
hooks.md
| Hook Name | Implementation | Type | File | Module |
|-----------|---------------|------|------|--------|
| node_presave | my_module_node_presave | procedural | my_module.module | my_module |
| form_alter | MyHooks::formAlter | oop | src/Hook/MyHooks.php | my_module |
plugins.md
| Plugin Type | ID | Class | Module | File |
|-------------|----|-------|--------|------|
| Block | my_custom_block | MyCustomBlock | my_module | src/Plugin/Block/MyCustomBlock.php |
entities.md
| Entity Type | ID | Class | Handlers | Fields | Module | File |
|-------------|----|-------|----------|--------|--------|------|
| ContentEntityType | node | Node | list_builder, form, views_data | 12 (5 ref, 2 list) | node | core/modules/node/src/Entity/Node.php |
schemas.md
| Entity Type | Bundle | Fields | References | Lists | Schema File |
|-------------|--------|--------|------------|-------|-------------|
| node | article | 5 | 2 | 1 | schemas/node.article.json |
base-fields.md
| Entity Type | Kind | Base Fields | References | Lists | Computed | Schema File |
|-------------|------|-------------|------------|-------|----------|-------------|
| timan_assignment | Content | 19 | 3 | 3 | 1 | schemas/timan_assignment.base-fields.json |
permissions.md
| Permission | Title | Module | Restricted | Description |
|------------|-------|--------|------------|-------------|
| administer timan holidays | Administer holidays | timan_holiday | yes | Create, edit, and delete public holidays. |
methods.md (3 sections: Services, Controllers, Forms)
| Service Class | Method | Return Type | Module | File | Line |
|---------------|--------|-------------|--------|------|------|
| HolidayService | getHolidaysInRange() | array | timan_holiday | www/.../HolidayService.php | 45 |
schemas/*.base-fields.json (loaded on demand via discover schema:ENTITY)
{
"entity_type": "timan_assignment",
"entity_kind": "Content",
"base_table": "timan_assignment",
"entity_keys": {"id": "id", "uuid": "uuid", "label": "label", "owner": "uid"},
"admin_permission": "administer timan assignments",
"has_owner": true,
"base_fields": {
"label": {"type": "string", "label": "Label", "required": true, "cardinality": 1, "settings": {"max_length": 255}}
},
"field_count": 19,
"ref_count": 3,
"list_count": 3,
"computed_count": 1
}
schemas/*.json (loaded on demand via discover schema:ENTITY)
{
"entity_type": "node",
"bundle": "article",
"fields": {
"field_tags": {
"type": "entity_reference",
"label": "Tags",
"required": false,
"cardinality": -1,
"target_type": "taxonomy_term",
"target_bundles": ["tags"]
}
},
"field_count": 5,
"ref_count": 2,
"list_count": 1
}
Known limitations (config schemas):
- Only configurable fields from YAML config are extracted. Base fields are in separate
*.base-fields.json files.
- Config directory auto-detected from common locations (
config/sync, config/default, config/staging, ../config/sync). Projects with non-standard paths need to symlink or adjust.
Known limitations (base fields):
- Fields from
parent::baseFieldDefinitions() (id, uuid, langcode) are implicit — not enumerated.
- Config entities without
baseFieldDefinitions() are skipped.
- Dynamic permissions from
permission_callbacks are not detected.
FEATURE_MAP.md
Compact overview (~500 tokens) always loaded during session priming:
| Code | Name | Services | Hooks | Routes | Plugins | Entities | Hotspots | Spec |
DEPENDENCY_GRAPH.md
Sections:
- Service Dependencies (who injects who)
- Event/Hook Chains
- Entity Cross-References
- External Boundaries (HTTP, SMTP, queues)
- Feature Adjacency Matrix
When This Triggers
- "Generate structural index"
- "Rebuild dependency graph"
- "What services does X use"
- "Blast radius of changing Y"
- "Show me the service graph"
- "What hooks does module X implement"
- "Map dependencies for feature X"
Prerequisites
- Drupal project with standard directory structure
docs/semantic/ directory (for output location)
- No external dependencies required (grep/sed/awk only)
- Optional:
yq for improved YAML parsing accuracy
Integration
- discover skill: Structural query prefixes route to structural index
- prime.sh: FEATURE_MAP.md loaded as primary context when available
- staleness hook: Advisory warnings when structural sources change
- agents: Builder checks deps before multi-file changes; Reviewer validates impact