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
97 changes: 97 additions & 0 deletions .claude/skills/invforge-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
name: invforge-review
description: Mandatory pre-push review for InvForge (Python). Runs ruff/mypy/pytest gates and reviews changed files against docs/coding-standards.md. Must be run and must pass before any `git push` in this repo.
---

# InvForge pre-push review

This is a **mandatory gate**, not an optional lint pass. No commit in this
repo gets pushed without this skill having been run against it and having
reported a clean result. If you are about to `git push` in this repo and
haven't run this skill against the commits being pushed, run it first.

## 1. Scope the review

Determine what's changed and being pushed:

```sh
git status --short
git diff --stat $(git merge-base HEAD origin/main 2>/dev/null || echo HEAD)..HEAD
```

If there's no `origin/main` yet (new repo, nothing pushed), review
everything staged/committed so far instead.

## 2. Mechanical gates — all must pass with zero errors/warnings

Run from the repo root, using the project venv:

```sh
.venv/bin/ruff format --check invforge tests
.venv/bin/ruff check invforge tests
.venv/bin/mypy
.venv/bin/python -m pytest tests/unit -q
```

`tests/unit/` is fast and network-free — always run it. If the change
under review touches anything in `invforge/core/connectivity.py`,
`invforge/core/server.py`, `invforge/core/control_api.py`, a scenario
fixture, or the Dockerfile/compose file, also run the integration suite
(needs Docker):

```sh
scripts/integration-test.sh
```

Any failure here is an automatic FAIL — fix it (or, for `ruff format`,
just run `ruff format invforge tests` and re-check) before doing the
semantic review below. Don't hand-wave a mypy or ruff error as
"pre-existing" without confirming it actually predates the change under
review — the whole codebase is expected to be clean at all times, see
`docs/coding-standards.md`.

## 3. Semantic review

Read `docs/coding-standards.md` in full, then walk every changed file
against it. In particular:

- **Correctness against real fixtures**, not just synthetic ones — does
the change still work against `scenarios/recorded/*.yaml`, not only
invented scenarios?
- **Boundary conditions**: empty scenario, register at a unit's block
edge, malformed/partial YAML, an HTTP request with a missing/wrong-typed
field.
- **Resource cleanup** on every exit path — threads, sockets, the
ticker's stop_event.
- **Type-hint accuracy** — does the annotation actually match runtime
behavior, or did mypy only pass because of an `Any` escape hatch
(`# type: ignore`, unannotated `object`, etc.) that hides a real gap?
Every `# type: ignore` in this codebase should have a comment
explaining why (see `core/server.py`'s pymodbus-subclassing comment as
the reference example) — a bare, unexplained one is itself a finding.
- **Test coverage** for the behavior actually changed — a new code path
with no corresponding test is a finding, not just a suggestion.
- **Consistency with `invforge/core/`'s conventions** — did a
vendor-specific quirk leak into `core/` instead of staying inside its
`invforge/profiles/<vendor>/`? That's an architecture violation, not a
style nit.
- **New dependency scrutiny** — anything added to `requirements.txt`
that the stdlib or an existing dependency already covers is a finding.

## 4. Report

Use the `ReportFindings` tool with the verified findings, most severe
first. If nothing survives review, call it with an empty findings list —
don't skip the call.

## 5. Verdict

State explicitly, in plain text after the findings:

- **PASS — safe to push** if all mechanical gates passed and no
CONFIRMED correctness finding remains, or
- **FAIL — do not push** otherwise, naming exactly what has to change
first.

Do not push on a FAIL verdict, even if the user seems to be in a hurry —
say so and let them explicitly override if they choose to.
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.venv/
__pycache__/
*.pyc
.mypy_cache/
.ruff_cache/
.pytest_cache/
.git/
tests/
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: CI

on: [push, pull_request]

jobs:
unit:
runs-on: ubuntu-latest
permissions:
contents: read
code-quality: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install -r requirements-dev.txt
- run: ruff format --check invforge tests
- run: ruff check invforge tests
- run: mypy
- run: python -m pytest tests/unit -q --cov=invforge --cov-report=xml
- name: Upload coverage report
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: actions/upload-code-coverage@v1
with:
file: coverage.xml
language: Python
# Soft-fail until "Code Quality" is enabled in this repo's
# Settings -> Code security page (no API for that toggle as of
# this writing, so it can't be flipped from here) -- without
# this, every CI run hard-fails on a 404 from the upload
# endpoint before that's turned on.
fail-on-error: false

integration:
runs-on: ubuntu-latest
needs: unit
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install -r requirements-dev.txt
- run: docker compose up -d --build
- run: scripts/wait-healthy.sh invforge
- run: python -m pytest tests/integration -q
env:
INVFORGE_MODBUS_PORT: "5020"
INVFORGE_CONTROL_PORT: "8080"
- if: failure()
run: docker compose logs
- if: always()
run: docker compose down
29 changes: 29 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CodeQL

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
- cron: "22 6 * * 3"

jobs:
analyze:
name: Analyze (python)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write

steps:
- uses: actions/checkout@v4

- uses: github/codeql-action/init@v3
with:
languages: python
queries: security-and-quality

- uses: github/codeql-action/analyze@v3
with:
category: "/language:python"
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__/
*.pyc
.venv/
*.egg-info/
.coverage
coverage.xml
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# InvForge — agent instructions

Before any `git push` in this repo, run the `invforge-review` skill
against the commits being pushed and get a PASS verdict. This is a hard
requirement (see `.claude/skills/invforge-review/SKILL.md`), not a
suggestion — do not push on a FAIL verdict without the user explicitly
overriding it.

See `docs/coding-standards.md` for the rules that review enforces.
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.12-slim

WORKDIR /app

RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY invforge ./invforge

EXPOSE 502 8080

HEALTHCHECK --interval=2s --timeout=2s --start-period=5s --retries=5 \
CMD curl -sf http://127.0.0.1:8080/health || exit 1

ENTRYPOINT ["python", "-m", "invforge"]
CMD ["--vendor", "sigenergy", "--firmware", "V100R001C21SPC116", "--modbus-port", "502", "--control-port", "8080"]
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 InvForge contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading
Loading