Skip to content

A language for hierarchical programming, to describe and enforce software structure beyond traditional language constructs

License

Notifications You must be signed in to change notification settings

ercasta/hielements

Repository files navigation

Hielements

⚠️ 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.


Why Hielements?

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

Prescriptive vs Descriptive

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, and allows control constraints
  • Templates declare what should be true

📝 Descriptive — Document what actually exists

  • Elements describe concrete implementations
  • Scopes bind to actual code and artifacts
  • The implements keyword connects elements to templates
  • The binds keyword 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.


Quick Example

Imagine a microservice with Python code and a Dockerfile. You want to ensure:

  1. The service exposes port 8080
  2. 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.

Reusable Templates

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.


Key Features

🧩 Reusable Element Templates

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.

🔒 Type-Safe Connection Points

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.

🌲 Hierarchical Checks

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

🎯 Cross-Technology Elements

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')

🏗️ Hierarchical Composition

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)

🔗 Explicit Connection Points

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)

✅ Enforceable Rules

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')

🧩 Extensible via Libraries

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.


Use Cases

🆕 Greenfield Development

Define your architecture upfront and use Hielements as design guardrails:

  1. Describe system structure in Hielements
  2. Write implementation code
  3. Run checks to ensure alignment
  4. Agents can use specifications to generate code

🏭 Brownfield/Legacy Systems

Reverse-engineer and enforce architecture in existing codebases:

  1. Analyze code (manually or with agents) to create initial Hielements specs
  2. Refine and formalize the architecture
  3. Enforce rules to prevent degradation
  4. Use specifications as the source of truth for refactoring

🔄 Continuous Architecture Compliance

Integrate Hielements checks into CI/CD:

# .github/workflows/architecture.yml
- name: Check Architecture
  run: hielements check

Reject PRs that violate architectural rules.


How It Works

1. Define Elements (Descriptive)

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

2. Define Templates (Prescriptive - Optional)

Templates establish architectural patterns with:

  • Unbounded scopes: Declared without implementation (scope module<rust>)
  • Rules: Constraints that implementations must satisfy
  • Requirements: Using requires, forbids, and allows keywords
  • Checks: Verifiable properties

3. Bind Implementations to Templates

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')

4. Write Rules

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")

5. Run Checks

hielements check

Hielements evaluates all rules against your actual codebase and reports violations.


Architecture

  • 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  │
                  └─────────────────┘

Getting Started

Installation

# Install via cargo (Rust package manager)
cargo install hielements

# Or download binary from releases
# https://github.com/ercasta/hielements/releases

Your First Hielements Spec

Create 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

Learn More

📖 Usage Guide - Comprehensive guide covering:

  • Writing Hielements specifications
  • Using element templates for reusable patterns
  • Creating custom libraries
  • Best practices and CI/CD integration

IDE Support

Install the Hielements extension for VSCode:

  • Syntax highlighting
  • Real-time error checking
  • Go to definition
  • Auto-completion

Documentation


Project Status

🚧 Hielements is in early development. We are actively building the core interpreter, standard libraries, and tooling.

Roadmap

  • 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)

Contributing

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).


Philosophy

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

Examples

More examples can be found in the examples/ directory:

  • Microservices architecture
  • Layered application (hexagonal architecture)
  • Multi-language monorepo
  • Infrastructure as Code validation

FAQ

Is this a replacement for my programming language?

No. Hielements complements your existing languages by adding a layer of architectural specification and enforcement.

Does Hielements run at compile time or runtime?

Hielements checks are static analysis—they run before your code executes, typically in your CI/CD pipeline.

What languages are supported?

Built-in support for Python, Docker, and file/folder structures. Additional languages can be added via libraries.

Can I use Hielements with existing codebases?

Yes! Hielements works with both greenfield and brownfield projects.

How is this different from linters?

Linters check code quality and style within a single file or module. Hielements checks architectural rules across your entire system, including relationships between components.

What's the difference between prescriptive and descriptive modes?

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.


License

MIT License


Community


Build software that stays true to its design. Start with Hielements.


Self-Describing Architecture

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.hie

We use this feedback loop to keep the code, docs, and architecture in sync — and it makes AI-assisted development far more reliable and trustworthy!

About

A language for hierarchical programming, to describe and enforce software structure beyond traditional language constructs

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •