⚠️ Alpha / Experimental — This project is in early development. The language syntax, APIs, and tooling may change significantly. Use at your own risk and expect breaking changes.
A language to describe and enforce software architecture.
Hielements helps you define, document, and enforce the logical structure of your software systems. Unlike traditional architecture documentation that becomes stale, Hielements specifications are formally checked against your actual code—ensuring your architecture stays aligned with reality.
📝 Note: This documentation describes Hielements V2, which introduces a clearer separation between prescriptive (templates with rules) and descriptive (actual implementations) parts of the language. V2 is incompatible with V1.
Modern software systems are complex. As codebases grow, their actual structure diverges from the original design. Architecture diagrams become outdated, and the "mental model" of how components interact exists only in developers' heads (if at all).
Hielements solves this by:
- 📐 Formalizing architecture in a declarative language
- ✅ Enforcing architectural rules via static checks
- 🔗 Making relationships explicit between components
- 🏗️ Supporting hierarchical composition for complex systems
- 🌲 Providing hierarchical checks that compose through element hierarchies
- 🎨 Enabling reusable templates for consistent architectural patterns
- 🌐 Working across languages (Python, Docker, Terraform, and more)
- 🤝 Enabling human-AI collaboration through structured specifications
Hielements V2 separates two key concerns:
🏗️ Prescriptive — Define the rules and constraints
- Element templates establish architectural patterns
- Checks enforce rules and requirements
- Keywords like
requires,forbids, andallowscontrol constraints - Templates declare what should be true
📝 Descriptive — Document what actually exists
- Elements describe concrete implementations
- Scopes bind to actual code and artifacts
- The
implementskeyword connects elements to templates - The
bindskeyword maps implementations to template declarations
You can use Hielements descriptively only (documenting structure without enforcement) or prescriptively (with templates and checks for enforcement). Mix and match based on your needs.
Imagine a microservice with Python code and a Dockerfile. You want to ensure:
- The service exposes port 8080
- The Docker container uses the correct Python module as the entry point
With Hielements:
element orders_service:
# Define scopes with language annotations (V2 syntax)
scope python_module<python> = python.module_selector('orders')
scope dockerfile<docker> = docker.file_selector('orders_service.dockerfile')
# Define connection points with types
connection_point main: PythonModule = python.get_main_module(python_module)
# Enforce rules
check docker.exposes_port(dockerfile, 8080)
check docker.entry_point(dockerfile, main)
Run hielements check and Hielements will verify your architecture against the actual code. If someone changes the Dockerfile or renames the module, the checks will fail—keeping your architecture in sync.
Define architectural patterns once and reuse them across your system:
# Define a template for microservices (prescriptive)
template microservice:
element api:
scope module<python> # Unbounded scope in template
connection_point rest_endpoint: RestEndpoint
element database:
connection_point connection: DatabaseConnection
check microservice.api.exposes_rest()
# Implement the template multiple times (descriptive + prescriptive)
element orders_service implements microservice:
# Bind template scopes to actual code using V2 syntax
scope api_mod<python> binds microservice.api.module = python.module_selector('orders.api')
connection_point endpoint: RestEndpoint binds microservice.api.rest_endpoint = python.public_functions(api_mod)
connection_point db: DatabaseConnection binds microservice.database.connection = postgres.database_selector('orders_db')
element payments_service implements microservice:
scope api_mod<python> binds microservice.api.module = python.module_selector('payments.api')
connection_point endpoint: RestEndpoint binds microservice.api.rest_endpoint = python.public_functions(api_mod)
connection_point db: DatabaseConnection binds microservice.database.connection = postgres.database_selector('payments_db')
Templates ensure consistency across similar components and make architectural patterns explicit.
Define architectural patterns once and reuse them across your codebase:
template compiler:
element lexer:
scope module<rust> # Unbounded scope (V2)
connection_point tokens: TokenStream
element parser:
scope module<rust> # Unbounded scope (V2)
connection_point ast: AbstractSyntaxTree
check compiler.lexer.tokens.compatible_with(compiler.parser.input)
element python_compiler implements compiler:
# Bind template scopes using V2 binds keyword
scope lexer_mod<rust> binds compiler.lexer.module = rust.module_selector('pycompiler::lexer')
connection_point lexer_tokens: TokenStream binds compiler.lexer.tokens = rust.function_selector(lexer_mod, 'tokenize')
scope parser_mod<rust> binds compiler.parser.module = rust.module_selector('pycompiler::parser')
connection_point parser_ast: AbstractSyntaxTree binds compiler.parser.ast = rust.function_selector(parser_mod, 'parse')
Templates ensure consistency across similar components, making architectural patterns explicit and enforceable.
Explicit type annotations are required for all connection points, enabling correct integration across multiple libraries and languages. Below are examples of connection points typing added for better interfacing:
element api_service:
# Basic types (mandatory)
connection_point port: integer = docker.exposed_port(dockerfile)
connection_point api_url: string = config.get_url()
connection_point ssl_enabled: boolean = config.get_flag('ssl')
# Custom types for domain-specific interfaces
connection_point handler: HttpHandler = python.public_functions(module)
connection_point db_conn: DatabaseConnection = python.class_selector(module, 'Database')
Mandatory types provide safety and serve as inline documentation of interfaces.
Define requirements that must be satisfied somewhere in your element hierarchy, enabling flexible yet enforceable architectural constraints:
template dockerized:
## At least one descendant must have a docker configuration
requires descendant scope dockerfile<docker>
requires descendant check docker.has_healthcheck(dockerfile)
element my_app implements dockerized:
element frontend:
scope src<files> = files.folder_selector('frontend')
# Not dockerized - that's OK
element backend:
scope dockerfile<docker> binds dockerized.dockerfile = docker.file_selector('Dockerfile.backend')
check docker.has_healthcheck(dockerfile)
# This satisfies the hierarchical requirement!
Hierarchical checks also support connection boundaries to control architectural dependencies:
template frontend_zone:
## Code in this zone may only import from API gateway
allows connection to api_gateway.public_api
forbids connection to database.*
element my_frontend implements frontend_zone:
element web_app:
scope src<javascript> = files.folder_selector('frontend/web')
# Inherits connection boundaries - cannot access database
Benefits:
- Flexible enforcement: Requirements can be satisfied by any descendant
- Architectural boundaries: Control dependencies between system layers
- Composable constraints: Boundaries inherit through element hierarchy
Define elements that span multiple languages and artifacts:
element full_stack_feature:
scope frontend<typescript> = typescript.module_selector('components/OrderForm')
scope backend<python> = python.module_selector('api/orders')
scope database<sql> = sql.migration_selector('create_orders_table')
scope container<docker> = docker.file_selector('orders.dockerfile')
Build complex systems from smaller, well-defined elements:
element payment_system:
element payment_gateway
element fraud_detection
element transaction_log
check payment_gateway.exposes_api(payment_gateway.api, fraud_detection)
Make inter-component relationships visible and verifiable:
element api_server:
connection_point rest_api = python.public_functions(api_module)
connection_point database = postgres.connection(config)
Rules are actually checked, not just documented:
check docker.exposes_port(dockerfile, 8080)
check python.no_circular_dependencies(module_a, module_b)
check files.matches_pattern(config, '*.yaml')
Built-in support for Python, Docker, and files/folders. Extend with custom libraries using:
- External Process Plugins: Write plugins in any language (Python, JS, Go, etc.) via JSON-RPC
- WASM Plugins: Sandboxed, near-native performance for security-critical use cases (infrastructure ready, runtime integration in progress)
The hybrid approach balances flexibility (external tools when needed) with security (WASM sandboxing for untrusted code).
Learn how to create and share custom libraries in the Usage Guide or External Libraries Guide.
Define your architecture upfront and use Hielements as design guardrails:
- Describe system structure in Hielements
- Write implementation code
- Run checks to ensure alignment
- Agents can use specifications to generate code
Reverse-engineer and enforce architecture in existing codebases:
- Analyze code (manually or with agents) to create initial Hielements specs
- Refine and formalize the architecture
- Enforce rules to prevent degradation
- Use specifications as the source of truth for refactoring
Integrate Hielements checks into CI/CD:
# .github/workflows/architecture.yml
- name: Check Architecture
run: hielements checkReject PRs that violate architectural rules.
Elements represent logical components with:
- Scope: What code/artifacts belong to this element (with V2 language annotations like
<rust>) - Connection Points: APIs, interfaces, or dependencies the element exposes
- Children: Sub-elements for hierarchical composition
Templates establish architectural patterns with:
- Unbounded scopes: Declared without implementation (
scope module<rust>) - Rules: Constraints that implementations must satisfy
- Requirements: Using
requires,forbids, andallowskeywords - Checks: Verifiable properties
Use the implements and binds keywords to connect:
element my_service implements observable:
scope metrics_mod<rust> binds observable.metrics.module = rust.module_selector('api')
Rules use library functions to check properties:
check python.function_exists(module, "handle_payment")
check docker.base_image(dockerfile, "python:3.11-slim")
check files.no_files_matching(src, "*.tmp")
hielements checkHielements evaluates all rules against your actual codebase and reports violations.
- Interpreter: Written in Rust for performance and reliability
- Extensible: Language support via pluggable libraries
- Language Server Protocol: Full IDE integration (VSCode, with more coming)
- External Tools: Libraries can invoke existing static analysis tools
┌────────────────────────────────────────────────────────┐
│ Hielements Spec (.hie) │
└────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ Interpreter │
│ (Rust) │
└─────────────────┘
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Python │ │ Docker │ │ Custom │
│ Library │ │ Library │ │ Library │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└──────────────────┼──────────────────┘
▼
┌─────────────────┐
│ Your Codebase │
└─────────────────┘
# Install via cargo (Rust package manager)
cargo install hielements
# Or download binary from releases
# https://github.com/ercasta/hielements/releasesCreate a file architecture.hie:
element my_service:
scope src<files> = files.folder_selector('src/')
check files.contains(src, 'main.py')
Run the check:
hielements check architecture.hie📖 Usage Guide - Comprehensive guide covering:
- Writing Hielements specifications
- Using element templates for reusable patterns
- Creating custom libraries
- Best practices and CI/CD integration
Install the Hielements extension for VSCode:
- Syntax highlighting
- Real-time error checking
- Go to definition
- Auto-completion
- 📘 Usage Guide - Complete guide to using Hielements
- 📖 Language Reference - Complete syntax and semantics
- 🔌 External Libraries Guide - Creating custom libraries
- 🏗️ Technical Architecture - Implementation details
- 🔍 Related Work - Comparison with similar tools
- 📝 Summary - High-level overview
🚧 Hielements is in early development. We are actively building the core interpreter, standard libraries, and tooling.
- Language design and specification
- Core interpreter implementation (Rust)
- Standard libraries (Python, Docker, files)
- Element templates for reusable patterns
- VSCode extension
- Language Server Protocol
- CI/CD integration templates
- Additional language libraries (JavaScript, Go, Terraform)
We welcome contributions! Whether you're interested in:
- Core interpreter development (Rust)
- Language library development (any language)
- Documentation and examples
- IDE extensions
- Testing and feedback
Check out our Contributing Guide (coming soon).
Architecture should be:
- Explicit: Not hidden in code or developers' minds
- Enforced: Checked automatically, not just documented
- Evolvable: Easy to update as systems change
- Multi-level: From high-level system design to low-level module structure
- Flexible: Support both description (documenting what exists) and prescription (enforcing what should be)
Hielements V2 makes this possible through:
- Descriptive mode: Document your architecture without enforcement
- Prescriptive mode: Use templates and checks to enforce architectural rules
- Hybrid approach: Mix both modes as needed for different parts of your system
More examples can be found in the examples/ directory:
- Microservices architecture
- Layered application (hexagonal architecture)
- Multi-language monorepo
- Infrastructure as Code validation
No. Hielements complements your existing languages by adding a layer of architectural specification and enforcement.
Hielements checks are static analysis—they run before your code executes, typically in your CI/CD pipeline.
Built-in support for Python, Docker, and file/folder structures. Additional languages can be added via libraries.
Yes! Hielements works with both greenfield and brownfield projects.
Linters check code quality and style within a single file or module. Hielements checks architectural rules across your entire system, including relationships between components.
Descriptive mode lets you document your architecture without enforcement—useful for understanding existing systems or when you need flexibility. Prescriptive mode uses templates, requires/forbids/allows keywords, and checks to enforce architectural rules. You can mix both modes: describe some parts of your system while prescribing rules for others.
- 💬 Discussions
- 🐛 Issue Tracker
- 📧 Email: hielements@example.com
Build software that stays true to its design. Start with Hielements.
Hielements literally documents and checks itself — how cool is that?! The repository is driven by a living specification written in hielements.hie, and we continuously validate that spec during AI-assisted coding sessions (and in CI) so architectural drift gets caught early.
Peek at the live self-description: hielements.hie
# Live excerpt from hielements.hie
element hielements_repo:
# sanity checks that run as part of validation
check files.exists('README.md')
check struct_exists('Interpreter')
Want to see it in action? Run:
hielements check hielements.hieWe use this feedback loop to keep the code, docs, and architecture in sync — and it makes AI-assisted development far more reliable and trustworthy!