Merge pull request 'MIL-001 Project scaffold and tooling' (#26) from mil-001-project-scaffold into main
This commit was merged in pull request #26.
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
# Continuous integration for the squirrel census project: runs the pytest tests
|
||||||
|
# on Python 3.13 for every push and pull request. Read by Gitea Actions (a
|
||||||
|
# runner must be registered for the repository). It changes nothing outside the
|
||||||
|
# repository and uses no secret. Decision: MIL-001 task "Add CI workflow".
|
||||||
|
# Host: Gitea Actions; uses only github.* contexts, so it also runs on GitHub.
|
||||||
|
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: "3.13"
|
||||||
|
PIP_VERSION: "26.2.1"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: tests-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pytest:
|
||||||
|
name: Run the tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- name: Check out the code
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5.6.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
- name: Create the virtual environment and install
|
||||||
|
run: |
|
||||||
|
python -m venv .venv
|
||||||
|
.venv/bin/python -m pip install --upgrade "pip==${PIP_VERSION}"
|
||||||
|
.venv/bin/python -m pip install -e ".[dev]"
|
||||||
|
|
||||||
|
- name: Run the tests
|
||||||
|
run: .venv/bin/python -m pytest
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Packaging and builds
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
|
||||||
|
# Test and tool caches
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# Secrets: personal tokens used only by the maintainer, never by the project
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Generated source documentation (Doxygen)
|
||||||
|
docs/doxygen/
|
||||||
|
|
||||||
|
# Editors and operating systems
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# yamllint configuration for the workflow files in .gitea/workflows/.
|
||||||
|
extends: default
|
||||||
|
rules:
|
||||||
|
document-start: disable
|
||||||
|
line-length:
|
||||||
|
max: 100
|
||||||
|
truthy:
|
||||||
|
check-keys: false
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Doxygen configuration for the squirrel census project.
|
||||||
|
# Build the source documentation with: doxygen Doxyfile
|
||||||
|
# Output goes to docs/doxygen/ (ignored by git). Everything not listed here
|
||||||
|
# keeps the Doxygen default.
|
||||||
|
|
||||||
|
PROJECT_NAME = "Squirrel Census"
|
||||||
|
PROJECT_BRIEF = "Count squirrels by primary fur colour"
|
||||||
|
PROJECT_NUMBER = 0.1.0
|
||||||
|
OUTPUT_DIRECTORY = docs/doxygen
|
||||||
|
INPUT = src README.md
|
||||||
|
FILE_PATTERNS = *.py *.md
|
||||||
|
RECURSIVE = YES
|
||||||
|
USE_MDFILE_AS_MAINPAGE = README.md
|
||||||
|
OPTIMIZE_OUTPUT_JAVA = YES
|
||||||
|
EXTRACT_ALL = YES
|
||||||
|
GENERATE_HTML = YES
|
||||||
|
GENERATE_LATEX = NO
|
||||||
|
QUIET = YES
|
||||||
|
WARN_IF_UNDOCUMENTED = YES
|
||||||
|
WARN_AS_ERROR = NO
|
||||||
@@ -16,7 +16,7 @@ document of a type. `Primary File` may contain a glob (e.g.
|
|||||||
| PP | Project Plan | docs/project-plan.md | 002 |
|
| PP | Project Plan | docs/project-plan.md | 002 |
|
||||||
| MIL | Milestone / Gateway | docs/milestones/*.md | 005 |
|
| MIL | Milestone / Gateway | docs/milestones/*.md | 005 |
|
||||||
| ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0001 |
|
| ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0001 |
|
||||||
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 008 |
|
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 011 |
|
||||||
| DICT | Domain Dictionary | docs/dictionary.md | 002 |
|
| DICT | Domain Dictionary | docs/dictionary.md | 002 |
|
||||||
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
|
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ This plan schedules the four phases that deliver the squirrel census assignment:
|
|||||||
|
|
||||||
| Gateway | Document | Window | Decision date | Owner | Stories | Main deliverable | Milestone |
|
| Gateway | Document | Window | Decision date | Owner | Stories | Main deliverable | Milestone |
|
||||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
| G1 Project scaffold and tooling | [MIL-001] | 2026-10-13 to 2026-10-14 | 2026-10-14 | S01 | none | `.gitignore`, `pyproject.toml`, constants, Doxyfile, continuous integration (CI), repository metadata | |
|
| G1 Project scaffold and tooling | [MIL-001] | 2026-10-13 to 2026-10-14 | 2026-10-14 | S01 | none | `.gitignore`, `pyproject.toml`, constants, Doxyfile, continuous integration (CI), repository metadata | [Milestone 124][MS-124] |
|
||||||
| G2 Squirrel census analysis | [MIL-002] | 2026-10-14 to 2026-10-15 | 2026-10-15 | S01 | US-001 | Package, tests, census file, `squirrel_count.csv` | |
|
| G2 Squirrel census analysis | [MIL-002] | 2026-10-14 to 2026-10-15 | 2026-10-15 | S01 | US-001 | Package, tests, census file, `squirrel_count.csv` | [Milestone 125][MS-125] |
|
||||||
| G3 README, source documentation and screenshots | [MIL-003] | 2026-10-15 to 2026-10-16 | 2026-10-16 | S01 | none | README, Doxygen comments, screenshots | |
|
| G3 README, source documentation and screenshots | [MIL-003] | 2026-10-15 to 2026-10-16 | 2026-10-16 | S01 | none | README, Doxygen comments, screenshots | [Milestone 126][MS-126] |
|
||||||
| G4 Wiki and publication | [MIL-004] | 2026-10-16 | 2026-10-16 | S01 | none | Wiki in en, da and ms | |
|
| G4 Wiki and publication | [MIL-004] | 2026-10-16 | 2026-10-16 | S01 | none | Wiki in en, da and ms | [Milestone 127][MS-127] |
|
||||||
|
|
||||||
```plantuml
|
```plantuml
|
||||||
@startgantt
|
@startgantt
|
||||||
@@ -93,3 +93,7 @@ A No-Go at a gateway stops the later gateways; their dates move by the days the
|
|||||||
[MIL-003]: ./milestones/mil-003-documentation.md
|
[MIL-003]: ./milestones/mil-003-documentation.md
|
||||||
[MIL-004]: ./milestones/mil-004-wiki-publication.md
|
[MIL-004]: ./milestones/mil-004-wiki-publication.md
|
||||||
[f1b3c03]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/commit/f1b3c034dc3880b7884646b4a4ed28e5f98fbcd3
|
[f1b3c03]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/commit/f1b3c034dc3880b7884646b4a4ed28e5f98fbcd3
|
||||||
|
[MS-124]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/milestone/124
|
||||||
|
[MS-125]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/milestone/125
|
||||||
|
[MS-126]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/milestone/126
|
||||||
|
[MS-127]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/milestone/127
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# RC Review of the MIL-001 Python files
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
| Key | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | RC-008 |
|
||||||
|
| CrossReference | [PY-MIL-001], [QC-PY-001] |
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [d71b586] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Artifact Under Review
|
||||||
|
|
||||||
|
- Instance reviewed: [PY-MIL-001]
|
||||||
|
- Checklist used: [QC-PY-001]
|
||||||
|
- Scope: full review of src/squirrel/__init__.py, src/squirrel/constants.py, tests/test_constants.py
|
||||||
|
- Language and domain: n/a
|
||||||
|
- Language reviewer: none (the stand-in reviewer reads English and the `it` domain)
|
||||||
|
- Mode: autonomous, not independent (reviewer: AI as stand-in for S01)
|
||||||
|
|
||||||
|
## Checklist Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | Packages, modules, functions, variables, classes and constants follow PEP 8 casing (`snake_case`, `PascalCase`, `UPPER_SNAKE`) (Mandatory) | Pass | Module constants are UPPER_SNAKE; test functions snake_case; package `squirrel`. |
|
||||||
|
| 2 | Names state purpose in the domain's language; no unexplained abbreviations, no single-letter names outside tiny scopes (Mandatory) | Pass | Names state purpose (FUR_COLOR_COLUMN, DEFAULT_OUTPUT_PATH). |
|
||||||
|
| 3 | Code is produced by the project's formatter and passes its linter with no unexplained suppressions (Mandatory) | Pass | `ruff format --check` and `ruff check` (0.17.0, rules E, F, I, UP, B, D) pass with no suppressions. |
|
||||||
|
| 4 | Every function and method signature is type-annotated, including `-> None` (Mandatory) | Pass | Every test function is annotated `-> None`; constants carry annotations; `mypy --strict` passes. |
|
||||||
|
| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) (Mandatory) | Pass | No exception handling in this module. |
|
||||||
|
| 6 | No mutable default arguments and no shadowed builtins (Mandatory) | Pass | No functions with defaults; no builtins shadowed. |
|
||||||
|
| 7 | Files, locks and connections are managed with context managers (Mandatory) | Pass | Test reads the source with `pathlib.Path.read_text`, which closes the file. |
|
||||||
|
| 8 | Public modules, classes and functions have docstrings that say what, not how (Optional) | Pass | Module and constants have Doxygen `##` blocks and a module docstring. |
|
||||||
|
| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output (Mandatory) | Pass | No `print` and no logging; no secrets. |
|
||||||
|
| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded (Mandatory) | N-A | No classes or operations exist; no DCD yet. |
|
||||||
|
| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network (Mandatory) | Pass | 6 tests in `tests/test_constants.py` named for behaviour; no order or network dependence. |
|
||||||
|
| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment (Optional) | Pass | `mypy 2.4.0 --strict`: success, no issues in 3 source files. |
|
||||||
|
| 13 | Dependencies are declared and pinned in the project's dependency file, none unused (Optional) | Pass | No runtime dependencies; dev tools pinned exactly in `pyproject.toml`. |
|
||||||
|
| 14 | Each piece of knowledge (a business rule, constant, format, validation or query) is defined in one place; code is merged only where it expresses the same knowledge, not where it merely looks alike (Mandatory) | Pass | Each literal is defined once in `constants.py`. |
|
||||||
|
| 15 | Imports point inward: business-rule modules import no framework, database, UI or delivery-mechanism code, and there are no import cycles (Mandatory) | Pass | Only `pathlib` is imported; no framework code. |
|
||||||
|
| 16 | SOLID holds: a class or module has one reason to change; new behaviour is added by extension, not by editing a type test in several places; subclasses honour the contract of their base; protocols are narrow; high-level code depends on protocols or abstract types and receives its implementations (Mandatory) | Pass | A data module with no types or branching; nothing to violate. |
|
||||||
|
|
||||||
|
## Overall Verdict
|
||||||
|
|
||||||
|
Go — all Mandatory criteria pass; criterion 10 is not applicable because no class exists.
|
||||||
|
|
||||||
|
## Action Items
|
||||||
|
|
||||||
|
| Action | Owner | Due |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| None | S01 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[PY-MIL-001]: ../../../src/squirrel/constants.py
|
||||||
|
[QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md
|
||||||
|
[d71b586]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/commit/d71b5863eba3dc29c5a5e09e4ffa087c67565fae
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# RC Review of ci.yml against the YAML checklist
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
| Key | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | RC-009 |
|
||||||
|
| CrossReference | [WF-MIL-001], [QC-YML-001] |
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [d71b586] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Artifact Under Review
|
||||||
|
|
||||||
|
- Instance reviewed: [WF-MIL-001]
|
||||||
|
- Checklist used: [QC-YML-001]
|
||||||
|
- Scope: full review
|
||||||
|
- Language and domain: n/a
|
||||||
|
- Language reviewer: none (the stand-in reviewer reads English and the `it` domain)
|
||||||
|
- Mode: autonomous, not independent (reviewer: AI as stand-in for S01)
|
||||||
|
|
||||||
|
## Checklist Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | The file parses with a safe YAML loader and has no duplicate keys in any mapping (Mandatory) | Pass | Parsed by yamllint (PyYAML based); no duplicate keys. |
|
||||||
|
| 2 | Indentation is 2 spaces everywhere, never tabs; lines end with LF; the file ends with one newline and has no trailing spaces (Mandatory) | Pass | 2 spaces, LF, single trailing newline. |
|
||||||
|
| 3 | Passes `yamllint` (or the project's YAML linter) with its configuration committed next to the code and no unexplained `disable` comments (Mandatory) | Pass | `yamllint` with `.yamllint.yml` committed: no errors. |
|
||||||
|
| 4 | A scalar that YAML would coerce to another type is quoted: `yes`, `no`, `on`, `off`, `null`, `~`, numbers with a leading zero, versions such as `1.10`, and values that contain `: ` or ` #` (Mandatory) | Pass | Values quoted: "3.13", "26.2.1". |
|
||||||
|
| 5 | Only safe, plain types are used: no language-specific tags (`!!python/object`, `!ruby/object`) and no `!include`-style extension unless the consuming tool documents it (Mandatory) | Pass | No tags or extensions. |
|
||||||
|
| 6 | No secret is written in the file: no password, token, key or private connection string; a secret is referenced by name and supplied by the consuming tool (Mandatory) | Pass | No secret in the file. |
|
||||||
|
| 7 | Embedded program text (shell, Python, SQL, `jq`, and so on) in a value is at most five lines; a longer one is moved to a file of its own, in its own language, and the YAML calls it (Mandatory) | Pass | The longest `run:` block has 3 lines. |
|
||||||
|
| 8 | A block of keys or values repeated in the file or across files is defined once (an anchor, a template, an include or a reusable unit the consumer supports) (Mandatory) | Pass | No repeated block. |
|
||||||
|
| 9 | Anchors, aliases and merge keys (`&`, `*`, `<<`) are used only where the consuming tool is known to support them (Mandatory) | Pass | No anchors used. |
|
||||||
|
| 10 | The file validates against the schema of the consuming tool where one exists, and the schema or tool version is stated (Optional) | Pass | `actionlint 1.7.12` validates the workflow schema. |
|
||||||
|
| 11 | A header comment states what the file is for, which tool reads it, and where its decision or design is recorded (Mandatory) | Pass | Header comment states purpose, reader, side effects and decision. |
|
||||||
|
| 12 | Key names follow the convention of the consuming tool; one case style is used within the file (kebab-case or snake_case, never mixed) (Mandatory) | Pass | Keys are kebab-case as Gitea Actions expects; env names UPPER_SNAKE. |
|
||||||
|
| 13 | Multi-line strings use a block scalar (`\|` or `>`) and state the chomping indicator (`\|-`, `\|+`) where the trailing newline matters (Optional) | Pass | Block scalar `|` used for the 3-line script; trailing newline is not significant. |
|
||||||
|
| 14 | The file name is lowercase kebab-case with one extension style (`.yml` or `.yaml`) used throughout the repository (Optional) | Pass | `ci.yml` is lowercase; `.yml` is the only extension used. |
|
||||||
|
| 15 | A change is exercised by a check that runs without the real target: a parse and schema test, or a dry run of the consuming tool (Optional) | Pass | yamllint and actionlint run without the real target. |
|
||||||
|
|
||||||
|
## Overall Verdict
|
||||||
|
|
||||||
|
Go — all Mandatory criteria pass.
|
||||||
|
|
||||||
|
## Action Items
|
||||||
|
|
||||||
|
| Action | Owner | Due |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| None | S01 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[WF-MIL-001]: ../../../.gitea/workflows/ci.yml
|
||||||
|
[QC-YML-001]: ../../../framework/qc/code/qc-yaml.md
|
||||||
|
[d71b586]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/commit/d71b5863eba3dc29c5a5e09e4ffa087c67565fae
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# RC Review of ci.yml against the workflow checklist
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
| Key | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | RC-010 |
|
||||||
|
| CrossReference | [WF-MIL-001], [QC-WF-001] |
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 2026-10-13 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [d71b586] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Artifact Under Review
|
||||||
|
|
||||||
|
- Instance reviewed: [WF-MIL-001]
|
||||||
|
- Checklist used: [QC-WF-001]
|
||||||
|
- Scope: full review
|
||||||
|
- Language and domain: n/a
|
||||||
|
- Language reviewer: none (the stand-in reviewer reads English and the `it` domain)
|
||||||
|
- Mode: autonomous, not independent (reviewer: AI as stand-in for S01)
|
||||||
|
|
||||||
|
## Checklist Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | The file meets the YAML checklist ([QC-YML-001]) (Mandatory) | Pass | See RC-009: YAML checklist passes. |
|
||||||
|
| 2 | The file is in `.gitea/workflows/` or `.github/workflows/` with the extension `.yml` or `.yaml`, and the same workflow does not exist in both directories (Mandatory) | Pass | File is in `.gitea/workflows/` only; no `.github/workflows/` copy. |
|
||||||
|
| 3 | The file declares `name`, `on` and `jobs`; `on` lists explicit events and, where it matters, `branches` or `paths` (Mandatory) | Pass | `name`, `on` (push to main, pull_request) and `jobs` are declared. |
|
||||||
|
| 4 | A header comment states the purpose, the triggers, the inputs, the secrets it needs, what it changes outside the repository, and the decision or milestone it implements (Mandatory) | Pass | Header comment states purpose, triggers, secrets (none), outside effects (none) and decision. |
|
||||||
|
| 5 | Every job and step has a descriptive `name`, unique within its job (Mandatory) | Pass | Every job and step has a unique descriptive `name`. |
|
||||||
|
| 6 | The file passes `actionlint` (or the project's workflow validation test) and the tool version is stated (Mandatory) | Pass | `actionlint 1.7.12` reports no findings. |
|
||||||
|
| 7 | Every job declares `permissions` explicitly with the least access that works (`contents: read` or none by default); write access only on the job that needs it (Mandatory) | Pass | Workflow-level `permissions: contents: read`. |
|
||||||
|
| 8 | Every action used is pinned to a full commit SHA or an exact release tag, never to a branch; a moving major tag (`@v1`) is accepted only for an action published by the same organisation under a stated release policy (Mandatory) | Pass | `actions/checkout@v4.2.2` and `actions/setup-python@v5.6.0` are exact release tags. |
|
||||||
|
| 9 | No secret is written in the file; a secret is read as `secrets.NAME`, passed to a step through `env:`, never placed on a command line or echoed (Mandatory) | Pass | No secret is used. |
|
||||||
|
| 10 | Untrusted text (`inputs.*`, event fields such as a title, branch name or label, step outputs derived from them) is never interpolated into a `run:` script; it is passed through `env:` and used as a quoted variable (Mandatory) | Pass | No event field or input reaches a `run:` script; `PIP_VERSION` is a workflow `env` value used as a quoted shell variable. |
|
||||||
|
| 11 | A trigger that gives a run secrets or a write token on code from a fork or an untrusted branch (`pull_request_target`, `workflow_run`) does not check out or execute that code (Mandatory) | Pass | Trigger is `pull_request`, not `pull_request_target`. |
|
||||||
|
| 12 | A workflow that changes state outside the repository (release, tag, publish, sync, deploy) is started by hand or by a protected branch, and a manual run defaults to a dry run with an explicit input to apply (Mandatory) | Pass | The workflow changes nothing outside the repository. |
|
||||||
|
| 13 | The token the workflow uses has only the scope the job needs, and where the host's built-in token is enough it is used instead of a personal token (Optional) | Pass | No token is used. |
|
||||||
|
| 14 | Every job sets `runs-on` to an explicit label and `timeout-minutes` (Mandatory) | Pass | `runs-on: ubuntu-latest` and `timeout-minutes: 10`. |
|
||||||
|
| 15 | `concurrency` is set where two runs would conflict: a check cancels the older run (`cancel-in-progress: true`); a publishing job queues (`cancel-in-progress: false`) (Mandatory) | Pass | Concurrency group per ref with `cancel-in-progress: true`. |
|
||||||
|
| 16 | Triggers are filtered (`branches`, `paths`, `types`) so the workflow runs only for the changes it concerns (Mandatory) | Pass | Push filtered to `main`; pull requests run for all branches. |
|
||||||
|
| 17 | Tools and dependencies are installed at pinned versions; a version is a workflow input with a default or a value in one place (Mandatory) | Pass | Python version and pip version are workflow `env` values; tool versions are exact in `pyproject.toml`. |
|
||||||
|
| 18 | A dependency cache is used where the install is slow enough to matter (Optional) | N-A | Install takes seconds; a cache would add complexity without benefit. |
|
||||||
|
| 19 | A failing step names the tool, the file and the reason in its log (annotation or `::error::` line) (Optional) | Pass | pytest names the failing file, test and reason in the log and exits non-zero. |
|
||||||
|
| 20 | A `run:` block is within the limit on embedded program text of criterion 7 of [QC-YML-001]; a longer script is a file (`tools/`, or a shared repository fetched at a pinned ref) that the step calls with one command, and the workflow passes its settings through `env:` (Mandatory) | Pass | Longest `run:` block is 3 lines. |
|
||||||
|
| 21 | Logic used by more than one workflow or repository is a reusable workflow (`workflow_call`) or a composite action; each input has a `description`, a `type` and a `default` where one is sensible (Mandatory) | N-A | Single workflow; nothing is shared. |
|
||||||
|
| 22 | Every workflow feature used is verified on the instance that runs it, and the host and version are recorded; a feature the target hosts differ on is noted in the header (Mandatory) | Fail | Gitea 1.27.3 and runner labels (`ubuntu-latest`) are known, but the run of this workflow stayed `queued` for over 15 minutes on the only runner (busy), so the features are not yet verified on the instance. |
|
||||||
|
| 23 | A reusable workflow states, in its header, how a caller pins it and the script it fetches, so the workflow and its script are the same release (Optional) | N-A | Not a reusable workflow. |
|
||||||
|
| 24 | A static test of the repository's workflows exists and runs on every pull request: valid YAML, required keys, explicit permissions and runner, no inline script above the limit (Optional) | Fail | No static workflow test exists; actionlint and yamllint were run by hand. |
|
||||||
|
|
||||||
|
## Overall Verdict
|
||||||
|
|
||||||
|
Go-with-conditions — all Mandatory criteria pass except 22: the workflow has not yet run on the instance. The commands it runs were executed locally (pytest 6 passed, ruff, mypy --strict).
|
||||||
|
|
||||||
|
## Action Items
|
||||||
|
|
||||||
|
| Action | Owner | Due |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Re-check criterion 22 when run 1012 or the next run on main completes; record the result in a delta re-review | S01 | 2026-10-16 |
|
||||||
|
| Decide whether to add a static workflow test (criterion 24, optional) | S01 | 2026-10-16 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[WF-MIL-001]: ../../../.gitea/workflows/ci.yml
|
||||||
|
[QC-WF-001]: ../../../framework/qc/code/qc-workflow.md
|
||||||
|
[d71b586]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/commit/d71b5863eba3dc29c5a5e09e4ffa087c67565fae
|
||||||
@@ -31,11 +31,13 @@ updated whenever an artifact instance is created or reviewed.
|
|||||||
| [MIL-003] | Milestone | en | it | [BC-001], [PP-001], [MIL-002] | - | [RC-005] |
|
| [MIL-003] | Milestone | en | it | [BC-001], [PP-001], [MIL-002] | - | [RC-005] |
|
||||||
| [MIL-004] | Milestone | en | it | [BC-001], [PP-001], [MIL-003] | - | [RC-006] |
|
| [MIL-004] | Milestone | en | it | [BC-001], [PP-001], [MIL-003] | - | [RC-006] |
|
||||||
| [DICT-001] | Domain Dictionary | en | it | [BC-001], [SA-001] | - | [RC-007] |
|
| [DICT-001] | Domain Dictionary | en | it | [BC-001], [SA-001] | - | [RC-007] |
|
||||||
|
| `src/squirrel/constants.py`, `tests/test_constants.py` | Python Source Code | - | - | [MIL-001] | - | [RC-008] |
|
||||||
|
| `.gitea/workflows/ci.yml` | CI Workflow | - | - | [MIL-001] | - | [RC-009], [RC-010] |
|
||||||
|
|
||||||
## Coverage Notes
|
## Coverage Notes
|
||||||
|
|
||||||
- `-` in Upstream means foundational; in Downstream, nothing is built on it yet; in Last Reviewed, no `RC-*` exists yet.
|
- `-` in Upstream means foundational; in Downstream, nothing is built on it yet; in Last Reviewed, no `RC-*` exists yet.
|
||||||
- No instance yet of US, UC, SSD, DM, ADR or source code; they are added in MIL-002.
|
- No instance yet of US, UC, SSD, DM or ADR; they are added in MIL-002.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -54,4 +56,7 @@ updated whenever an artifact instance is created or reviewed.
|
|||||||
[RC-005]: ./reviews/rc-005-mil-003-documentation.md
|
[RC-005]: ./reviews/rc-005-mil-003-documentation.md
|
||||||
[RC-006]: ./reviews/rc-006-mil-004-wiki-publication.md
|
[RC-006]: ./reviews/rc-006-mil-004-wiki-publication.md
|
||||||
[RC-007]: ./reviews/rc-007-dictionary.md
|
[RC-007]: ./reviews/rc-007-dictionary.md
|
||||||
|
[RC-008]: ./reviews/rc-008-python-constants.md
|
||||||
|
[RC-009]: ./reviews/rc-009-ci-workflow-yaml.md
|
||||||
|
[RC-010]: ./reviews/rc-010-ci-workflow.md
|
||||||
[f1b3c03]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/commit/f1b3c034dc3880b7884646b4a4ed28e5f98fbcd3
|
[f1b3c03]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/commit/f1b3c034dc3880b7884646b4a4ed28e5f98fbcd3
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Project configuration for the squirrel census assignment (day 25).
|
||||||
|
# Read by pip, setuptools and pytest. The decision to use the standard library
|
||||||
|
# only is recorded in docs/adr/adr-0001-standard-library-instead-of-pandas.md.
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "squirrel"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Count the squirrels of the 2018 Central Park Squirrel Census by primary fur colour."
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
license-files = ["LICENSE"]
|
||||||
|
dependencies = []
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Intended Audience :: Education",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest==9.1.1", "ruff==0.17.0", "mypy==2.4.0"]
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
pythonpath = ["src"]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 88
|
||||||
|
src = ["src", "tests"]
|
||||||
|
extend-exclude = ["docs"]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "UP", "B", "D"]
|
||||||
|
ignore = ["D203", "D213"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
strict = true
|
||||||
|
files = ["src", "tests"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"tests/**" = ["D"]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
##
|
||||||
|
# @file __init__.py
|
||||||
|
# @brief Package root of the squirrel census analysis.
|
||||||
|
|
||||||
|
"""Count the squirrels of the 2018 Central Park Squirrel Census by fur colour."""
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
##
|
||||||
|
# @file constants.py
|
||||||
|
# @brief Literals used by the squirrel census analysis, each defined once.
|
||||||
|
|
||||||
|
"""Literals used by the squirrel census analysis, each defined once."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
## @brief Default location of the census CSV file, relative to the working directory.
|
||||||
|
DEFAULT_INPUT_PATH: pathlib.Path = (
|
||||||
|
pathlib.Path("data") / "2018_Central_Park_Squirrel_Census_-_Squirrel_Data.csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
## @brief Default location of the summary CSV file that is written.
|
||||||
|
DEFAULT_OUTPUT_PATH: pathlib.Path = pathlib.Path("squirrel_count.csv")
|
||||||
|
|
||||||
|
## @brief Text encoding of the input and output files.
|
||||||
|
FILE_ENCODING: str = "utf-8"
|
||||||
|
|
||||||
|
## @brief Name of the census column that holds the primary fur colour.
|
||||||
|
FUR_COLOR_COLUMN: str = "Primary Fur Color"
|
||||||
|
|
||||||
|
## @brief Fur colours that are counted, in the order they appear in the summary.
|
||||||
|
FUR_COLORS: tuple[str, ...] = ("Gray", "Cinnamon", "Black")
|
||||||
|
|
||||||
|
## @brief Header of the summary file: the colour column, then the count column.
|
||||||
|
SUMMARY_COLUMNS: tuple[str, str] = ("Fur Color", "Count")
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""Tests for the squirrel.constants module."""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from squirrel import constants
|
||||||
|
|
||||||
|
|
||||||
|
def test_fur_colors_are_gray_cinnamon_black_in_order() -> None:
|
||||||
|
assert constants.FUR_COLORS == ("Gray", "Cinnamon", "Black")
|
||||||
|
|
||||||
|
|
||||||
|
def test_summary_columns_are_fur_color_and_count() -> None:
|
||||||
|
assert constants.SUMMARY_COLUMNS == ("Fur Color", "Count")
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_input_path_has_census_file_name() -> None:
|
||||||
|
assert constants.DEFAULT_INPUT_PATH.name == (
|
||||||
|
"2018_Central_Park_Squirrel_Census_-_Squirrel_Data.csv"
|
||||||
|
)
|
||||||
|
assert constants.DEFAULT_INPUT_PATH.parent == pathlib.Path("data")
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_output_path_has_summary_file_name() -> None:
|
||||||
|
assert constants.DEFAULT_OUTPUT_PATH == pathlib.Path("squirrel_count.csv")
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_encoding_and_column_name_values() -> None:
|
||||||
|
assert constants.FILE_ENCODING == "utf-8"
|
||||||
|
assert constants.FUR_COLOR_COLUMN == "Primary Fur Color"
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_imports_only_standard_library() -> None:
|
||||||
|
source = pathlib.Path(constants.__file__).read_text(encoding="utf-8")
|
||||||
|
imported: set[str] = set()
|
||||||
|
for node in ast.walk(ast.parse(source)):
|
||||||
|
if isinstance(node, ast.Import):
|
||||||
|
imported.update(alias.name.split(".")[0] for alias in node.names)
|
||||||
|
elif isinstance(node, ast.ImportFrom) and node.module is not None:
|
||||||
|
imported.add(node.module.split(".")[0])
|
||||||
|
|
||||||
|
assert imported <= sys.stdlib_module_names
|
||||||
Reference in New Issue
Block a user