Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ insert_final_newline = true

[*.py]
indent_size = 4
max_line_length = 99
max_line_length = 100

[*.{ts,tsx,js,jsx,json,css,scss,html}]
indent_size = 2
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.0] - 2026-03-07

### Added

- `backend/pyproject.toml` with ruff (line length 100, Python 3.12, security/typing/style rules) and mypy strict config
- `CONTRIBUTING.md` documenting coding standards, architecture rules, branch strategy, and testing conventions
- ADR-009: Coding Standards and Tooling

### Changed

- Line length updated from 99 to 100 in CODING_STANDARDS.md, .editorconfig, and CLAUDE.md

## [0.4.0] - 2026-03-07

### Added
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ docs/ # Design documentation
**Read `docs/CODING_STANDARDS.md` before writing any code.** Key rules:

### Python
- Formatter/Linter: `ruff` (line length 99)
- Formatter/Linter: `ruff` (line length 100)
- Type checker: `mypy --strict`
- All public functions require type annotations
- Google-style docstrings on public API boundaries only
Expand Down
93 changes: 93 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Contributing to Ground Control

## Getting Started

1. Fork and clone the repository
2. Create a feature branch from `dev`: `git checkout -b feature/your-feature`
3. Make your changes
4. Push and open a PR against `dev`

## Branch Strategy

- `main` — production-ready, protected
- `dev` — integration branch, all PRs target this
- `feature/*` — feature branches, branched from `dev`

## Coding Standards

Read [`docs/CODING_STANDARDS.md`](docs/CODING_STANDARDS.md) for the complete reference. Key points below.

### Python Backend

| Tool | Purpose | Command |
|------|---------|---------|
| `ruff check` | Linting | `cd backend && ruff check src/` |
| `ruff format` | Formatting | `cd backend && ruff format src/` |
| `mypy` | Type checking | `cd backend && mypy src/` |
| `pytest` | Testing | `cd backend && pytest` |

- **Line length**: 100
- **Type hints**: Required on all public functions and methods
- **Docstrings**: Google style, on public API boundaries only
- **Imports**: stdlib, third-party, local (enforced by ruff)
- **No `Any`** unless unavoidable (comment why)
- **No `print()`** — use `structlog`

### TypeScript Frontend

| Tool | Purpose | Command |
|------|---------|---------|
| `biome check` | Lint + format | `cd frontend && npx biome check src/` |
| `tsc` | Type checking | `cd frontend && npx tsc --noEmit` |

- **Strict mode**: `strict: true` in `tsconfig.json`
- **No `any`** — use `unknown` and narrow

### Naming Conventions (Python)

| Element | Convention | Example |
|---------|-----------|---------|
| Modules | `snake_case` | `risk_service.py` |
| Classes | `PascalCase` | `RiskService` |
| Functions/methods | `snake_case` | `create_risk()` |
| Constants | `UPPER_SNAKE_CASE` | `MAX_RETRY_COUNT` |
| Private | `_leading_underscore` | `_validate_score()` |

## Architecture Rules

The dependency rule is enforced by `import-linter` in CI:

```
api/ -> domain/ <- infrastructure/
```

- `domain/` has **zero** framework imports (no FastAPI, no SQLAlchemy)
- `api/` depends on `domain/` and `schemas/` — never imports `infrastructure/`
- `infrastructure/` implements interfaces defined in `domain/`

See [ADR-008](architecture/adrs/008-clean-architecture.md) for rationale.

## Commit Messages

- Imperative mood: `Add risk scoring engine` not `Added risk scoring engine`
- Every commit updates `CHANGELOG.md`

## Pull Requests

- Target `dev`, not `main`
- PRs require passing CI (lint + typecheck + tests + import-linter)
- No coverage regression
- Use the [PR template](.github/PULL_REQUEST_TEMPLATE.md)

## Testing

```
tests/
├── unit/ # Domain logic only. No DB, no HTTP.
├── integration/ # With real PostgreSQL (testcontainers).
└── e2e/ # Playwright, full stack.
```

- Test names describe behavior: `test_create_risk_fails_when_likelihood_out_of_range`
- Coverage minimums: 80% domain, 70% api/infrastructure
- Tests are independent — no shared mutable state
67 changes: 67 additions & 0 deletions architecture/adrs/009-coding-standards.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# ADR-009: Coding Standards and Tooling

## Status

Accepted

## Date

2026-03-07

## Context

Ground Control is a multi-language monorepo (Python backend, TypeScript frontend). Without enforced coding standards, style drift and inconsistent patterns will slow development and increase bugs. We need automated tooling that enforces standards in CI and at commit time, not just documentation.

## Decision

### Python Backend

- **Linter and formatter**: `ruff` (replaces flake8, isort, black in a single binary)
- Line length: 100
- Target: Python 3.12
- Rule sets: pycodestyle (E), pyflakes (F), isort (I), pep8-naming (N), bugbear (B), annotations (ANN), security (S), pydocstyle (D), pyupgrade (UP)
- **Type checker**: `mypy` with `strict = true`
- Disallow untyped defs, no implicit optional
- **Docstrings**: Google style convention, required on public API boundaries only
- **Import ordering**: stdlib, third-party, local (enforced by ruff isort rules)

### TypeScript Frontend

- **Linter and formatter**: `biome` (replaces ESLint + Prettier in a single binary)
- **Type checker**: `tsc --strict`
- **No `any` types** — use `unknown` and narrow

### Shared

- `.editorconfig` for consistent whitespace (4 spaces Python, 2 spaces TS/YAML)
- Pre-commit hooks run ruff, mypy, and gitleaks before every commit
- CI pipeline enforces all checks — failures block merge

### Naming Conventions (Python)

| Element | Convention |
|---------|-----------|
| Modules | `snake_case` |
| Classes | `PascalCase` |
| Functions/methods | `snake_case` |
| Constants | `UPPER_SNAKE_CASE` |
| Private | `_leading_underscore` |

## Consequences

### Positive

- Single tool per language (ruff, biome) reduces config complexity and speeds up checks
- Strict type checking catches bugs before runtime
- Automated enforcement means standards are not optional
- Consistent style reduces cognitive load during code review

### Negative

- Strict mypy can require verbose type annotations in some cases
- Developers must learn ruff/biome if unfamiliar (mitigated: both are fast and well-documented)
- Some valid code patterns are rejected by strict rules (use targeted ignores with justification)

### Risks

- Tool upgrades may introduce new lint rules that break CI (mitigated: pin versions in pre-commit and pyproject.toml)
1 change: 1 addition & 0 deletions architecture/adrs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ We use [MADR](https://adr.github.io/madr/) (Markdown Any Decision Records). Each
| [006](006-multi-tenancy-strategy.md) | Multi-Tenancy Strategy (Shared Schema Default) | Accepted |
| [007](007-agent-first-design.md) | Agent-First Design (AI Agents as First-Class Actors) | Accepted |
| [008](008-clean-architecture.md) | Clean Architecture (API / Domain / Infrastructure) | Accepted |
| [009](009-coding-standards.md) | Coding Standards and Tooling | Accepted |
59 changes: 59 additions & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[project]
name = "ground-control"
version = "0.1.0"
description = "Open IT Risk Management Platform"
requires-python = ">=3.12"
license = "MIT"

[tool.ruff]
target-version = "py312"
line-length = 100
src = ["src"]

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"F", # pyflakes
"I", # isort
"N", # pep8-naming
"B", # flake8-bugbear
"ANN", # flake8-annotations
"S", # flake8-bandit (security)
"D", # pydocstyle
"UP", # pyupgrade
"RUF", # ruff-specific rules
]
ignore = [
"D100", # missing docstring in public module
"D104", # missing docstring in public package
"D106", # missing docstring in public nested class
"D203", # one-blank-line-before-class (conflicts with D211)
"D213", # multi-line-summary-second-line (conflicts with D212)
"ANN101", # missing type annotation for self
"ANN102", # missing type annotation for cls
]

[tool.ruff.lint.isort]
known-first-party = ["ground_control"]

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101", "ANN", "D"]

[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
no_implicit_optional = true
check_untyped_defs = true

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = "-ra -q --strict-markers"
2 changes: 1 addition & 1 deletion docs/CODING_STANDARDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ Every error response includes `request_id` for correlation.
- Linter: `ruff check`
- Type checker: `mypy --strict`
- All functions have type annotations. No `Any` unless unavoidable (and commented why).
- Line length: 99 characters.
- Line length: 100 characters.
- Imports: sorted by `ruff` (isort-compatible).
- Docstrings: only on public API boundaries (use case functions, service classes). Not on every method. Code should be self-explanatory.

Expand Down
Loading