From c4a51c37de39f3997e092caceb710762f70d4314 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 28 Oct 2026 17:15:31 +0800 Subject: [PATCH 1/2] Add project scaffold, tooling, README and container image pyproject.toml (Python 3.13, Selenium only at runtime), src and tests layout, constants.py, Python .gitignore, Doxyfile, README from the framework template with .venv instructions, Dockerfile for headless Chromium, code review RC-014. Closes #8 Closes #9 Closes #10 Closes #11 Closes #12 Closes #13 Task: MIL-002#1 Task: MIL-002#2 Task: MIL-002#3 Task: MIL-002#4 Task: MIL-002#5 Task: MIL-002#6 --- .dockerignore | 10 ++ .gitignore | 37 +++++ Dockerfile | 19 +++ Doxyfile | 16 ++ README.md | 150 +++++++++++++++++- docs/artifact-registry.md | 2 +- docs/sqa/reviews/rc-014-code-mil-002.md | 61 +++++++ docs/sqa/traceability-matrix.md | 1 + pyproject.toml | 62 ++++++++ src/cookie_clicker_bot/__init__.py | 9 ++ src/cookie_clicker_bot/__main__.py | 8 + .../application/__init__.py | 3 + src/cookie_clicker_bot/cli.py | 12 ++ src/cookie_clicker_bot/constants.py | 22 +++ src/cookie_clicker_bot/domain/__init__.py | 3 + .../infrastructure/__init__.py | 3 + tests/unit/test_constants.py | 17 ++ 17 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 Doxyfile create mode 100644 docs/sqa/reviews/rc-014-code-mil-002.md create mode 100644 pyproject.toml create mode 100644 src/cookie_clicker_bot/__init__.py create mode 100644 src/cookie_clicker_bot/__main__.py create mode 100644 src/cookie_clicker_bot/application/__init__.py create mode 100644 src/cookie_clicker_bot/cli.py create mode 100644 src/cookie_clicker_bot/constants.py create mode 100644 src/cookie_clicker_bot/domain/__init__.py create mode 100644 src/cookie_clicker_bot/infrastructure/__init__.py create mode 100644 tests/unit/test_constants.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a5571f2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +.venv +.env +__pycache__ +*.pyc +.pytest_cache +docs/doxygen +framework +.claude +.agents diff --git a/.gitignore b/.gitignore index 7dddeb2..65d4a12 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,40 @@ # AI prompt log (the repository is public) /docs/ai-prompts/ + +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +build/ +dist/ +.eggs/ + +# Virtual environments +.venv/ +venv/ +env/ + +# Tests and coverage +.pytest_cache/ +.coverage +.coverage.* +htmlcov/ +coverage.xml + +# Type checker and linter caches +.mypy_cache/ +.ruff_cache/ + +# Doxygen output +/docs/doxygen/ + +# Browser run artefacts (the lecture screenshots in docs/images are kept) +/output/ +*.log + +# Editors and operating system +.idea/ +.vscode/ +.DS_Store +Thumbs.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6f31eaf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# Headless Chromium for real-browser runs and screenshots (ADR-0003). +FROM python:3.13-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + CHROME_BIN=/usr/bin/chromium \ + CHROMEDRIVER_PATH=/usr/bin/chromedriver + +RUN apt-get update \ + && apt-get install -y --no-install-recommends chromium chromium-driver fonts-liberation \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY pyproject.toml README.md LICENSE ./ +COPY src ./src +RUN python -m pip install --upgrade pip && python -m pip install -e ".[dev]" +COPY tests ./tests + +ENTRYPOINT ["python", "-m", "cookie_clicker_bot"] diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..459b2a3 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,16 @@ +# Doxygen configuration for the Cookie Clicker bot. Run: doxygen Doxyfile +PROJECT_NAME = "Cookie Clicker Bot" +PROJECT_BRIEF = "Selenium bot that plays Cookie Clicker (Udemy 100 Days of Code, day 48)" +OUTPUT_DIRECTORY = docs/doxygen +INPUT = src +FILE_PATTERNS = *.py +RECURSIVE = YES +EXTRACT_ALL = YES +OPTIMIZE_OUTPUT_JAVA = YES +PYTHON_DOCSTRING = YES +AUTOLINK_SUPPORT = YES +GENERATE_HTML = YES +GENERATE_LATEX = NO +QUIET = YES +WARN_IF_UNDOCUMENTED = YES +WARN_AS_ERROR = NO diff --git a/README.md b/README.md index 6417a2d..177028e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,150 @@ -# 048-the-coockie-factory-new +# ๐Ÿช Cookie Clicker Bot +A Selenium bot that plays [Cookie Clicker][game] for you: it clicks the big cookie, buys the most expensive upgrade it can afford every 5 seconds and prints the cookies per second after 5 minutes. It is the solution to day 48 of Udemy's *100 Days of Code: The Complete Python Pro Bootcamp*, written for course participants who want to compare solutions and for visitors who browse for ideas. + +## ๐Ÿ“š Table of Contents + +- [Overview](#-overview) +- [Requirements](#-requirements) +- [Setup](#-setup) +- [Run](#-run) +- [Tests](#-tests) +- [License](#-license) +- [Links](#-links) +- [Appendix: Project documents](#-appendix-project-documents) + +## ๐Ÿงญ Overview + +The bot follows the five steps of the lecture: + +1. Get to know the game. +2. Click the cookie as fast as possible, after dismissing the language selection. +3. Every 5 seconds buy the most expensive upgrade you can afford. +4. After 5 minutes stop and print `cookies/second`. +5. Tweak the strategy (check interval, buy one or buy all affordable). + +The repository is organised like this: + +| Folder or file | What it is for | +| --- | --- | +| `src/cookie_clicker_bot/` | The bot. `domain/` holds the rules (parsing, choosing the upgrade) with no Selenium, `application/` runs the game loop, `infrastructure/` drives the browser, `constants.py` holds every constant | +| `tests/` | `pytest` tests; the unit tests need no browser | +| `docs/` | Project documents, wiki pages (English, Danish, Bahasa Malaysia) and the screenshots of the lecture steps | +| `Dockerfile` | A Linux image with headless Chromium for real-browser runs and screenshots | +| `Doxyfile` | Doxygen configuration for the source comments | + +## ๐Ÿ“‹ Requirements + +- Python 3.13 or newer. +- Selenium (the only runtime dependency; it is installed by `pip` and finds the browser driver itself). +- Google Chrome or Chromium to run the bot on your own machine. +- `pytest` and `pytest-cov` for the tests (development extra only). +- Docker, only if you want to run the bot or take screenshots without opening a window on your desktop. +- Doxygen, only to build the source documentation. + +## ๐Ÿ› ๏ธ Setup + +Create a local virtual environment, upgrade `pip` and install the project with the test tools: + +```bash +python -m venv .venv +``` + +Activate it: + +```bash +# Linux and macOS +source .venv/bin/activate +``` + +```powershell +# Windows PowerShell +.venv\Scripts\Activate.ps1 +``` + +```bash +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +Leave the environment with `deactivate`. The folder `.venv` is ignored by git. + +## โ–ถ๏ธ Run + +With the environment active: + +```bash +cookie-clicker-bot +``` + +or, without installing the command: + +```bash +python -m cookie_clicker_bot +``` + +To keep your desktop free, run it in the Docker container (headless Chromium): + +```bash +docker build -t cookie-clicker-bot . +docker run --rm cookie-clicker-bot +``` + +## ๐Ÿงช Tests + +```bash +python -m pytest +``` + +Coverage of the logic modules is checked with `python -m pytest --cov`. Tests that need a real browser are marked `browser` and run in the Docker container; all other tests need neither a browser nor the network. + +Build the source documentation with Doxygen: + +```bash +doxygen Doxyfile +``` + +## ๐Ÿ“„ License + +GNU Affero General Public License v3.0, see [LICENSE][license]. + +## ๐Ÿ”— Links + +- [Repository][repository] +- [Documentation][documentation] +- [Issue tracker][issues] + +## ๐Ÿ“Ž Appendix: Project documents + +The documents a newcomer needs, in reading order. + +| Document | What it tells you | +| --- | --- | +| [AGENTS.md][agents] | The rules AI agents follow in this repository | +| [Business Case][BC-001] | Why the project exists, its scope and success criteria | +| [Stakeholder Analysis][SA-001] | Who is involved; the stakeholder IDs used for owners and reviewers | +| [Project Plan][PP-001] | The gateways and their schedule | +| [Milestones][milestones] | One document per gateway: Go/No-Go criteria and tasks | +| [Artifact registry][registry] | Where each artifact type lives, the next version, the PO language and domain | +| [Domain dictionary][DICT-001] | The PO terms and the IT terms | +| [Traceability matrix][TM-001] | How the artifacts link and when each was last reviewed | +| [Framework README][framework-readme] | The framework this project uses | +| [Framework changelog][framework-changelog] | What changed in each framework release | + +--- + +[game]: https://ozh.github.io/cookieclicker/ +[license]: ./LICENSE +[repository]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory-new +[documentation]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory-new/wiki +[issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory-new/issues +[agents]: ./AGENTS.md +[BC-001]: ./docs/business-case.md +[SA-001]: ./docs/stakeholder-analysis.md +[PP-001]: ./docs/project-plan.md +[milestones]: ./docs/milestones/ +[registry]: ./docs/artifact-registry.md +[DICT-001]: ./docs/dictionary.md +[TM-001]: ./docs/sqa/traceability-matrix.md +[framework-readme]: ./framework/README.md +[framework-changelog]: ./framework/CHANGELOG.md diff --git a/docs/artifact-registry.md b/docs/artifact-registry.md index 0d7cb57..e91f9ca 100644 --- a/docs/artifact-registry.md +++ b/docs/artifact-registry.md @@ -16,7 +16,7 @@ document of a type. `Primary File` may contain a glob (e.g. | PP | Project Plan | docs/project-plan.md | 002 | | MIL | Milestone / Gateway | docs/milestones/*.md | 008 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0004 | -| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 014 | +| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 015 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | DICT | Domain Dictionary (PO and IT terms) | docs/dictionary.md | 002 | diff --git a/docs/sqa/reviews/rc-014-code-mil-002.md b/docs/sqa/reviews/rc-014-code-mil-002.md new file mode 100644 index 0000000..9d16cbc --- /dev/null +++ b/docs/sqa/reviews/rc-014-code-mil-002.md @@ -0,0 +1,61 @@ +# SQA Review Record + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-014 | +| CrossReference | [MIL-002], [QC-PY-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S02 | Code review record | pending | + +--- + +## Artifact Under Review + +- Instance reviewed: [MIL-002] +- Checklist used: [QC-PY-001] (`QC-PY-001`) +- Scope: full review of the source and tests added by MIL-002: package skeleton, constants.py, cli.py, smoke test +- Language and domain: n/a +- Language reviewer: none +- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S02`) + +## Checklist Results + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | Packages, modules, functions, variables, classes and constants follow PEP 8 casing (`snake_case`, `PascalCase`, `UPPER_SNAKE`) | Pass | ruff (N rules) passes; names follow PEP 8 casing. | +| 2 | Names state purpose in the domain's language; no unexplained abbreviations, no single-letter names outside tiny scopes | Pass | Names use the terms of DICT-001 (cookie, upgrade, bot); no single-letter names outside comprehensions. | +| 3 | Code is produced by the project's formatter and passes its linter with no unexplained suppressions | Pass | `ruff format` and `ruff check` pass with the rules in `pyproject.toml`; no suppressions. | +| 4 | Every function and method signature is type-annotated, including `-> None` | Pass | `mypy --strict` passes; every signature is annotated. | +| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) | Pass | No bare `except`; ruff B and S rules pass. | +| 6 | No mutable default arguments and no shadowed builtins | Pass | No mutable defaults; ruff B006 and A rules pass. | +| 7 | Files, locks and connections are managed with context managers | N-A | No file or connection is opened in this milestone. | +| 8 | Public modules, classes and functions have docstrings that say what, not how | Pass | Public modules, classes and functions have Doxygen docstrings with `@brief`. | +| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output | Pass | `cli.py` prints a placeholder line only; the result line becomes the program output in MIL-005. | +| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded | N-A | No Design Class Diagram exists (ADR-0001 skips use case design); ADR-0002 records the layout instead. | +| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network | Pass | Tests are named for behaviour, independent of order, and need no network or browser. | +| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment | Pass | `mypy --strict` passes; no `Any`. | +| 13 | Dependencies are declared and pinned in the project's dependency file, none unused | Pass | `pyproject.toml` lists Selenium as the only runtime dependency; dev extras are test and lint tools. | +| 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 | Pass | Constants live only in `constants.py`; rules are defined once. | +| 15 | Imports point inward: business-rule modules import no framework, database, UI or delivery-mechanism code, and there are no import cycles | Pass | `domain` imports no Selenium; `application` depends on a port; no import cycles. | +| 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 | Pass | Modules have one reason to change; the game is behind a narrow port. | +| 17 | Source is laid out in the layers and folders of the clean architecture folder structure (`domain`, `application`, `infrastructure`, `presentation` packages under `src/`, and `unit`, `integration` and `e2e` under `tests/`), in `snake_case`, and imports between the layers point inward | Pass | Layers `domain`, `application`, `infrastructure` under `src/`, `unit` and `integration` under `tests/`; no `presentation` package by ADR-0002. | +| 18 | File and folder names are English, contain no space and no character outside letters, digits, `-`, `_` and `.`, and follow the casing of the language for files | Pass | English names, snake_case files, no spaces. | + +## Overall Verdict + +Go โ€” the code of MIL-002 meets every Mandatory criterion; `ruff`, `mypy --strict` and `pytest` were run and pass. The review is not independent: the AI wrote and reviewed it as stand-in for S02, and S01 can challenge it in the end-of-run summary. + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| None | S01 | - | + +--- + +[MIL-002]: ../../milestones/mil-002-scaffold.md +[QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md diff --git a/docs/sqa/traceability-matrix.md b/docs/sqa/traceability-matrix.md index 390be58..a03bb5e 100644 --- a/docs/sqa/traceability-matrix.md +++ b/docs/sqa/traceability-matrix.md @@ -41,6 +41,7 @@ Tracks backward and forward links between artifact instances so that the Busines | [MIL-005] | Milestone | en | it | [BC-001], [PP-001] | [MIL-006] | RC-011 | | [MIL-006] | Milestone | en | it | [BC-001], [PP-001] | [MIL-007] | RC-012 | | [MIL-007] | Milestone | en | it | [BC-001], [PP-001] | - | RC-013 | +| Source and tests of [MIL-002] | Python source code | - | - | [MIL-002], [ADR-0002] | - | RC-014 | ## Concern Coverage diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8776efc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,62 @@ +[build-system] +requires = ["setuptools>=75"] +build-backend = "setuptools.build_meta" + +[project] +name = "cookie-clicker-bot" +version = "0.1.0" +description = "Selenium bot that plays Cookie Clicker (Udemy 100 Days of Code, day 48)." +readme = "README.md" +requires-python = ">=3.13" +license = { file = "LICENSE" } +dependencies = [ + "selenium>=4.25", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.3", + "pytest-cov>=6.0", + "ruff>=0.8", + "mypy>=1.13", +] + +[project.scripts] +cookie-clicker-bot = "cookie_clicker_bot.cli:main" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-ra --strict-markers" +pythonpath = ["src"] +markers = [ + "browser: needs a real browser (run in the Docker container)", +] + +[tool.coverage.run] +source = ["cookie_clicker_bot"] +omit = ["*/infrastructure/*", "*/cli.py", "*/__main__.py"] + +[tool.coverage.report] +fail_under = 80 +show_missing = true + +[tool.ruff] +line-length = 88 +target-version = "py313" +src = ["src", "tests"] +extend-exclude = ["framework", ".claude", ".agents"] + +[tool.ruff.lint] +select = ["E", "F", "I", "B", "UP", "N", "D", "ANN", "S", "PT"] +ignore = ["D203", "D213", "D205", "D212", "D400", "D415", "D401"] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["S101", "D"] + +[tool.mypy] +strict = true +python_version = "3.13" +files = ["src", "tests"] diff --git a/src/cookie_clicker_bot/__init__.py b/src/cookie_clicker_bot/__init__.py new file mode 100644 index 0000000..d75e785 --- /dev/null +++ b/src/cookie_clicker_bot/__init__.py @@ -0,0 +1,9 @@ +"""@file __init__.py +@brief Selenium bot that plays Cookie Clicker (Udemy 100 Days of Code, day 48). + +@mainpage Cookie Clicker Bot +The bot opens the game, clicks the big cookie, buys the most expensive affordable +upgrade every few seconds and prints the cookies per second when the run ends. +The rules live in the domain package, the game loop in the application package +and the Selenium adapter in the infrastructure package. +""" diff --git a/src/cookie_clicker_bot/__main__.py b/src/cookie_clicker_bot/__main__.py new file mode 100644 index 0000000..6091b13 --- /dev/null +++ b/src/cookie_clicker_bot/__main__.py @@ -0,0 +1,8 @@ +"""@file __main__.py +@brief Entry point for `python -m cookie_clicker_bot`. +""" + +from cookie_clicker_bot.cli import main + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/cookie_clicker_bot/application/__init__.py b/src/cookie_clicker_bot/application/__init__.py new file mode 100644 index 0000000..8d1d6ca --- /dev/null +++ b/src/cookie_clicker_bot/application/__init__.py @@ -0,0 +1,3 @@ +"""@file __init__.py +@brief Application layer: the game loop, which talks to the game through a port. +""" diff --git a/src/cookie_clicker_bot/cli.py b/src/cookie_clicker_bot/cli.py new file mode 100644 index 0000000..148cd8a --- /dev/null +++ b/src/cookie_clicker_bot/cli.py @@ -0,0 +1,12 @@ +"""@file cli.py +@brief Command-line entry point (the composition root of the bot). +""" + + +def main() -> int: + """@brief Start the bot. + + @return Process exit code, 0 when the run finished. + """ + print("cookie-clicker-bot: the game loop arrives with MIL-003 to MIL-005.") + return 0 diff --git a/src/cookie_clicker_bot/constants.py b/src/cookie_clicker_bot/constants.py new file mode 100644 index 0000000..926a493 --- /dev/null +++ b/src/cookie_clicker_bot/constants.py @@ -0,0 +1,22 @@ +"""@file constants.py +@brief Every constant of the bot: the game address, timings and messages. + +Selectors of the game page are added here by the milestone that first uses them. +""" + +from typing import Final + +## Address of the new version of Cookie Clicker on GitHub Pages (ozh/cookieclicker). +GAME_URL: Final[str] = "https://ozh.github.io/cookieclicker/" + +## Total playing time of one run, in seconds (lecture step 4: five minutes). +RUN_DURATION_SECONDS: Final[int] = 300 + +## Seconds between two checks for an affordable upgrade (lecture step 3). +UPGRADE_CHECK_INTERVAL_SECONDS: Final[int] = 5 + +## Longest time to wait for an element of the page, in seconds. +WAIT_TIMEOUT_SECONDS: Final[int] = 20 + +## Folder where screenshots of a container run are written. +SCREENSHOT_DIR: Final[str] = "docs/images" diff --git a/src/cookie_clicker_bot/domain/__init__.py b/src/cookie_clicker_bot/domain/__init__.py new file mode 100644 index 0000000..22e3108 --- /dev/null +++ b/src/cookie_clicker_bot/domain/__init__.py @@ -0,0 +1,3 @@ +"""@file __init__.py +@brief Domain layer: the rules of the bot, free of Selenium. +""" diff --git a/src/cookie_clicker_bot/infrastructure/__init__.py b/src/cookie_clicker_bot/infrastructure/__init__.py new file mode 100644 index 0000000..121f33d --- /dev/null +++ b/src/cookie_clicker_bot/infrastructure/__init__.py @@ -0,0 +1,3 @@ +"""@file __init__.py +@brief Infrastructure layer: the Selenium adapter that drives the browser. +""" diff --git a/tests/unit/test_constants.py b/tests/unit/test_constants.py new file mode 100644 index 0000000..8aed730 --- /dev/null +++ b/tests/unit/test_constants.py @@ -0,0 +1,17 @@ +"""@file test_constants.py +@brief Smoke test: the assignment's timings are the ones in constants.py. +""" + +from cookie_clicker_bot import constants + + +def test_run_lasts_five_minutes() -> None: + assert constants.RUN_DURATION_SECONDS == 5 * 60 + + +def test_upgrades_are_checked_every_five_seconds() -> None: + assert constants.UPGRADE_CHECK_INTERVAL_SECONDS == 5 + + +def test_game_address_is_https() -> None: + assert constants.GAME_URL.startswith("https://") -- 2.54.0 From fa8d132fec935f682b581a6115d99899faa7e428 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 28 Oct 2026 17:15:35 +0800 Subject: [PATCH 2/2] Resolve pending commit link for RC-014 --- docs/sqa/reviews/rc-014-code-mil-002.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sqa/reviews/rc-014-code-mil-002.md b/docs/sqa/reviews/rc-014-code-mil-002.md index 9d16cbc..fa9c879 100644 --- a/docs/sqa/reviews/rc-014-code-mil-002.md +++ b/docs/sqa/reviews/rc-014-code-mil-002.md @@ -9,7 +9,7 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S02 | Code review record | pending | +| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S02 | Code review record | [c4a51c3] | --- @@ -59,3 +59,4 @@ Go โ€” the code of MIL-002 meets every Mandatory criterion; `ruff`, `mypy --stri [MIL-002]: ../../milestones/mil-002-scaffold.md [QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md +[c4a51c3]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory-new/commit/c4a51c37de39f3997e092caceb710762f70d4314 -- 2.54.0