2.2: Repository Structure

What You'll Learn

By the end of this chapter, you'll be able to:

  • Structure your repository so ASPICE assessors can find what they need in seconds
  • Set up Git conventions that make traceability automatic—not a chore
  • Choose between monorepo and multi-repo based on your actual needs
  • Automate traceability matrix generation from your commit history

Introduction

Here's a pattern I've seen too many times: a team with great code, solid tests, and complete requirements—but everything scattered across random folders with no rhyme or reason. When the ASPICE assessor asks "show me your SWE.3 work products," the team spends 20 minutes digging around while everyone gets uncomfortable.

Don't be that team.

A well-organized repository structure is your secret weapon for ASPICE compliance. It enables traceability, supports configuration management (SUP.8), and makes collaboration effortless. Let's set yours up right from the start.


The Battle-Tested Repository Structure

Single ECU Project (Monorepo)

This structure has been refined through dozens of real automotive projects. It's not theory—it's what actually works:

door-lock-controller/
├── .github/                      # GitHub Actions workflows
│   ├── workflows/
│   │   ├── ci.yml               # Continuous Integration
│   │   ├── release.yml          # Release automation
│   │   └── quality-gates.yml    # ASPICE quality checks
│   └── CODEOWNERS               # Automatic code review assignment
│
├── docs/                        # ASPICE work products (gold mine for assessors)
│   ├── requirements/
│   │   ├── system/              # SYS.2 outputs
│   │   │   ├── SRS_v1.2.md
│   │   │   └── traceability.csv
│   │   └── software/            # SWE.1 outputs
│   │       ├── SWRS_v1.0.md
│   │       └── requirements.yaml
│   ├── architecture/            # SYS.3, SWE.2 outputs
│   │   ├── system_architecture.md
│   │   ├── software_architecture.md
│   │   ├── diagrams/
│   │   │   ├── component_diagram.puml
│   │   │   └── deployment_diagram.puml
│   │   └── autosar/
│   │       └── door_lock_swc.arxml
│   ├── design/                  # SWE.3 outputs
│   │   ├── detailed_design.md
│   │   └── interface_specs/
│   ├── test/                    # SWE.4, SWE.5, SWE.6 specs
│   │   ├── unit_test_spec.md
│   │   ├── integration_test_spec.md
│   │   └── qualification_test_spec.md
│   ├── reviews/                 # Review records (SUP.1)
│   │   ├── 2025-Q1/
│   │   └── 2025-Q2/
│   └── project/                 # MAN.3 outputs
│       ├── project_plan.md
│       ├── risk_register.md
│       └── metrics_dashboard.md
│
├── src/                         # Source code (SWE.3 BP2)
│   ├── app/                     # Application software components
│   │   ├── door_lock/
│   │   │   ├── door_lock_ctrl.c
│   │   │   ├── door_lock_ctrl.h
│   │   │   └── README.md        # Component documentation
│   │   └── diagnostics/
│   ├── bsw/                     # Basic software (AUTOSAR BSW)
│   │   ├── os/
│   │   ├── com/
│   │   └── mcal/
│   ├── rte/                     # Runtime environment (generated)
│   │   └── Rte_DoorLock.c
│   └── common/                  # Shared utilities
│       ├── types.h
│       └── utils.c
│
├── tests/                       # Test code (SWE.4, SWE.5)
│   ├── unit/                    # Unit tests
│   │   ├── test_door_lock.c
│   │   └── mocks/
│   ├── integration/             # Integration tests
│   │   └── test_can_comm.c
│   └── system/                  # System tests (if ECU-level)
│       └── test_scenarios.py
│
├── tools/                       # Development tools
│   ├── code_generators/
│   ├── scripts/
│   │   ├── build.sh
│   │   ├── flash.sh
│   │   └── generate_traceability.py
│   └── quality/
│       ├── run_misra_check.sh
│       └── run_coverage.sh
│
├── build/                       # Build outputs (git-ignored)
├── config/                      # Configuration files
│   ├── .clang-format
│   ├── .cppcheck
│   ├── sonar-project.properties
│   └── coverage.ini
│
├── CHANGELOG.md                 # Release notes (SUP.8 BP4)
├── README.md                    # Project overview
├── CMakeLists.txt               # Build configuration
└── .gitignore

Notice the pattern? The docs/ folder mirrors ASPICE process outputs. When an assessor asks for SWE.2 work products, you point them at docs/architecture/. Done.


Multi-ECU Projects: Monorepo vs Multi-Repo

This is one of those "it depends" decisions, but let's make it concrete.

Option 1: Monorepo (for Tightly Coupled ECUs)

vehicle-body-control/
├── ecus/
│   ├── door-lock-controller/    # ECU 1
│   │   ├── src/
│   │   ├── tests/
│   │   └── docs/
│   ├── window-controller/        # ECU 2
│   │   ├── src/
│   │   ├── tests/
│   │   └── docs/
│   └── seat-controller/          # ECU 3
│       └── ...
├── shared/                       # Shared across ECUs
│   ├── autosar_interfaces/       # Common AUTOSAR ARXMLs
│   ├── network_database/         # CAN/LIN DBC files
│   └── common_lib/               # Shared utility code
├── docs/                         # System-level docs
│   ├── system_requirements/
│   ├── system_architecture/
│   └── integration_tests/
└── tools/                        # Shared tooling

Choose monorepo when:

  • ECUs share lots of code and interfaces
  • You want atomic cross-ECU changes
  • Single CI/CD pipeline is acceptable
  • Team is relatively small (<20 developers)

Option 2: Multi-Repo (for Independent ECUs)

Separate repositories:
- vehicle-body/door-lock-controller
- vehicle-body/window-controller  
- vehicle-body/seat-controller
- vehicle-body/shared-interfaces (AUTOSAR ARXMLs, DBC files)

Choose multi-repo when:

  • ECUs have independent release cycles
  • Different teams own different ECUs
  • Access control needs to be granular
  • You need vendor isolation

Making Traceability Automatic

This is the magic that separates teams who struggle with ASPICE from teams who breeze through assessments.

Git Commit Message Convention

# Format: <type>(<scope>): <subject> [<req-id>]

# Examples:
git commit -m "feat(door-lock): Add remote unlock via CAN [SWE-042, SWE-043]"
git commit -m "fix(can): Handle bus-off recovery [SWE-089]"
git commit -m "test(unit): Add test for timeout scenario [TC-123]"
git commit -m "docs(arch): Update component diagram [SYS-REV-2025-Q1]"

# The payoff: automated parsing extracts traceability
# Commit SHA → SWE-042, SWE-043

Pre-commit Hook (Enforce This From Day One)

#!/bin/bash
# .git/hooks/commit-msg

COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

# Check for requirement ID pattern
if ! echo "$COMMIT_MSG" | grep -qE '\[(SWE|SYS|TC|STKH)-[0-9]+\]'; then
    echo "ERROR: Commit message must reference at least one requirement ID"
    echo "Format: <type>(<scope>): <subject> [REQ-ID]"
    echo "Example: feat(door): Add unlock [SWE-042]"
    exit 1
fi

# Check for semantic commit format
if ! echo "$COMMIT_MSG" | grep -qE '^(feat|fix|docs|test|refactor|chore)(\(.+\))?:'; then
    echo "ERROR: Commit must use semantic format"
    echo "Types: feat, fix, docs, test, refactor, chore"
    exit 1
fi

exit 0

Pro tip: Add this hook on day one of the project. Retrofitting commit message conventions later is painful.


Configuration Management Done Right (SUP.8)

Your Branching Strategy

main (production)
  ↑
  └── release/v2.1.0 (release candidate)
        ↑
        └── develop (integration)
              ↑
              ├── feature/SWE-042-remote-unlock
              ├── feature/SWE-089-can-recovery
              └── bugfix/SWE-105-timeout-fix

Branch Protection Rules (Non-Negotiable)

# GitHub branch protection for 'main'
main:
  protection:
    required_reviews: 2
    required_checks:
      - ci/build
      - ci/unit-tests
      - ci/misra-check
      - ci/coverage-80-percent
    enforce_admins: true
    restrict_pushes: true
    allow_force_push: false

Version Tags That Tell A Story

# Tag format: vMAJOR.MINOR.PATCH[-PRERELEASE]
git tag -a v2.1.0 -m "Release 2.1.0 - ASIL-B qualified

Implements requirements: SWE-042, SWE-043, SWE-089
Test coverage: 92%
MISRA compliance: 100%
Static analysis: 0 critical/high findings

Approved by: [Tech Lead, QA Manager]
Release date: 2025-12-17
"

# Lightweight pre-release tags for internal milestones
git tag v2.1.0-rc1
git tag v2.1.0-beta2

Automated Traceability Matrix Generation

Stop maintaining traceability matrices by hand. Here's a Python script that generates them automatically from your Git history:

"""
Generate ASPICE traceability matrix from Git repository
Maps Requirements → Commits → Code Files → Tests
"""

import git
import re
import csv
from collections import defaultdict
from typing import Dict, List, Set

class TraceabilityExtractor:
    def __init__(self, repo_path: str):
        self.repo = git.Repo(repo_path)
        self.req_to_commits = defaultdict(set)
        self.commit_to_files = {}
        self.file_to_tests = defaultdict(set)
    
    def extract_from_commits(self, since: str = "v1.0.0"):
        """Extract requirement IDs from commit messages."""
        commits = list(self.repo.iter_commits(f"{since}..HEAD"))
        
        pattern = r'\[(SWE|SYS|TC|STKH)-(\d+)\]'
        
        for commit in commits:
            # Find all requirement IDs in commit message
            req_ids = re.findall(pattern, commit.message)
            
            for prefix, num in req_ids:
                req_id = f"{prefix}-{num}"
                self.req_to_commits[req_id].add(commit.hexsha[:8])
                
                # Track which files were modified
                if commit.parents:
                    diff = commit.parents[0].diff(commit)
                    files = [item.b_path for item in diff if item.b_path]
                    self.commit_to_files[commit.hexsha[:8]] = files
    
    def find_associated_tests(self):
        """Find test files associated with source files."""
        for commit_sha, files in self.commit_to_files.items():
            for file_path in files:
                if file_path.startswith('src/'):
                    # Look for corresponding test file
                    test_file = file_path.replace('src/', 'tests/unit/test_')
                    test_file = test_file.replace('.c', '.c')
                    
                    if test_file in self.repo.tree().traverse():
                        self.file_to_tests[file_path].add(test_file)
    
    def generate_matrix(self, output_file: str):
        """Generate traceability matrix CSV."""
        with open(output_file, 'w', newline='') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow([
                'Requirement ID',
                'Commits',
                'Source Files',
                'Test Files',
                'Coverage Status'
            ])
            
            for req_id in sorted(self.req_to_commits.keys()):
                commits = sorted(self.req_to_commits[req_id])
                
                # Get all files touched by these commits
                source_files = set()
                test_files = set()
                
                for commit in commits:
                    if commit in self.commit_to_files:
                        for f in self.commit_to_files[commit]:
                            if f.startswith('src/'):
                                source_files.add(f)
                            elif f.startswith('tests/'):
                                test_files.add(f)
                
                # Determine coverage status
                if test_files:
                    coverage_status = "[OK] Tested"
                elif source_files:
                    coverage_status = "[WARN] No tests"
                else:
                    coverage_status = "? No code"
                
                writer.writerow([
                    req_id,
                    '; '.join(commits),
                    '; '.join(sorted(source_files)),
                    '; '.join(sorted(test_files)),
                    coverage_status
                ])
        
        print(f"✅ Traceability matrix generated: {output_file}")

# Usage
if __name__ == "__main__":
    extractor = TraceabilityExtractor("/path/to/repo")
    extractor.extract_from_commits(since="v2.0.0")
    extractor.find_associated_tests()
    extractor.generate_matrix("traceability_matrix.csv")

Run this script weekly (or add it to your CI pipeline) and you'll always have an up-to-date traceability matrix ready for assessors.


Summary

A well-structured repository is your foundation for ASPICE success. Here's what to remember:

  • Organize by Work Products: Mirror ASPICE process outputs in your docs/ folder
  • Traceability Embedded: Requirement IDs in commit messages—enforced by hooks
  • Configuration Management: Git branching, tagging, and protection rules
  • Automation: Generate traceability matrices automatically
  • Monorepo vs Multi-Repo: Choose based on ECU coupling and team structure

Your Action Items:

  1. 📚 Document your structure in README.md so new team members understand it immediately
  2. 🔒 Enforce commit message format with pre-commit hooks from day one
  3. 🤖 Automate traceability extraction rather than maintaining it by hand
  4. 🛡️ Protect main/release branches with required reviews and CI checks
  5. 🏷️ Use semantic versioning for clear release identification

Your repository structure is set. Now let's automate your quality gates with CI/CD.