From 1002b16b81a84780ac3b39b985c7cdce61fddab4 Mon Sep 17 00:00:00 2001 From: Gregory Gauthier Date: Thu, 2 Apr 2026 16:30:28 +0100 Subject: [PATCH] feat: add initial WOMM Certification Toolkit implementation Introduces the Works On My Machine certification framework, including: - CLI tool for project certification and badge/certificate generation - Certification checks per WOMM-STD-001:2026 standard - Supporting documents, assets, and tests - Python package setup with pyproject.toml --- .gitignore | 2 + README.md | 64 +++ WOMM-CERTIFICATE.txt | 99 ++++ WOMM-STANDARD-001.md | 482 ++++++++++++++++++ assets/womm-seal.svg | 78 +++ pyproject.toml | 28 + src/womm/__init__.py | 3 + src/womm/badge.py | 159 ++++++ src/womm/certify.py | 335 ++++++++++++ src/womm/cli.py | 172 +++++++ src/womm/document.py | 154 ++++++ src/womm_certification.egg-info/PKG-INFO | 79 +++ src/womm_certification.egg-info/SOURCES.txt | 14 + .../dependency_links.txt | 1 + .../entry_points.txt | 2 + src/womm_certification.egg-info/requires.txt | 8 + src/womm_certification.egg-info/top_level.txt | 1 + tests/__init__.py | 0 tests/test_certify.py | 153 ++++++ 19 files changed, 1834 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 WOMM-CERTIFICATE.txt create mode 100644 WOMM-STANDARD-001.md create mode 100644 assets/womm-seal.svg create mode 100644 pyproject.toml create mode 100644 src/womm/__init__.py create mode 100644 src/womm/badge.py create mode 100644 src/womm/certify.py create mode 100644 src/womm/cli.py create mode 100644 src/womm/document.py create mode 100644 src/womm_certification.egg-info/PKG-INFO create mode 100644 src/womm_certification.egg-info/SOURCES.txt create mode 100644 src/womm_certification.egg-info/dependency_links.txt create mode 100644 src/womm_certification.egg-info/entry_points.txt create mode 100644 src/womm_certification.egg-info/requires.txt create mode 100644 src/womm_certification.egg-info/top_level.txt create mode 100644 tests/__init__.py create mode 100644 tests/test_certify.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ac3d0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +poetry.lock diff --git a/README.md b/README.md new file mode 100644 index 0000000..e576cf5 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# WOMM Certification Toolkit + +**Works On My Machine -- The Official Certification Standard** + +A formal certification framework for the age-old claim "it works on my machine," complete with a bureaucratic standards document, an official seal of approval, and a CLI tool to certify your projects. + +## Installation + +```bash +pip install -e . +``` + +For PNG badge export support: + +```bash +pip install -e '.[png]' +``` + +## Usage + +### Certify a project + +```bash +womm certify # certify current directory +womm certify /path/to/project +womm certify --level gold # target a specific level +``` + +### Generate a badge + +```bash +womm badge # default: gold, womm-seal.svg +womm badge --level platinum -o seal.svg +``` + +### Generate a certificate + +```bash +womm certificate # print to stdout +womm certificate -o cert.txt # save to file +``` + +### Read the standard + +```bash +womm standard +``` + +## Certification Levels + +| Level | Requirements | +|----------|-------------| +| Bronze | Project exists, has source files, no syntax errors | +| Silver | Bronze + tests exist and pass | +| Gold | Silver + README exists, no TODO/FIXME/HACK comments | +| Platinum | Gold + CI config present, git tree is clean | + +## The Standard + +See [WOMM-STD-001:2026](WOMM-STANDARD-001.md) for the full certification standard. + +## License + +MIT diff --git a/WOMM-CERTIFICATE.txt b/WOMM-CERTIFICATE.txt new file mode 100644 index 0000000..5f7582f --- /dev/null +++ b/WOMM-CERTIFICATE.txt @@ -0,0 +1,99 @@ + ================================================================ + WOMM CERTIFICATE OF COMPLIANCE + WOMM-STD-001:2026 + ================================================================ + + Certificate Number: WOMM-20260402162108-3551 + Date of Issuance: 2026-04-02 16:21:08 + Standard: WOMM-STD-001:2026, First Edition + + ---------------------------------------------------------------- + PROJECT INFORMATION + ---------------------------------------------------------------- + + Project Name: womm-certification + Project Path: /Users/gregory.gauthier/Projects/local/womm-certification + Certification Level: BRONZE (It Compiles) + + ---------------------------------------------------------------- + CERTIFICATION AUTHORITY + ---------------------------------------------------------------- + + Certifying Machine: PD2328.local + Certifying User: gregory.gauthier + Operating System: Darwin 24.6.0 + Python Version: 3.9.6 + + This certification was performed on the above machine and is + valid exclusively for this machine in its current configuration, + including all running processes, environment variables, and the + certifier's current emotional state. + + ---------------------------------------------------------------- + AUDIT TRAIL + ---------------------------------------------------------------- + +| Check | Result | Detail | +|------------------------------------------|--------|--------| +| Project directory exists | PASS | Project directory located. We're off to a strong start. | +| Contains source code | PASS | Source files detected. This is, in fact, a software project. | +| Python syntax check | PASS | All Python files parse cleanly. The AST smiles upon you. | +| Test suite exists | PASS | Test suite located. Someone here believes in accountability. | +| Tests pass | FAIL | Could not find a test runner. Neither pytest nor unittest obliged. | +| README exists | PASS | README found. Documentation: the thought that counts. | +| No TODO/FIXME/HACK comments | FAIL | Found markers in: test_certify.py:102, certify.py:215, certify.py:233. The guilt is documented. | +| CI configuration exists | FAIL | No CI configuration found. You are the CI. | +| Git working tree is clean | FAIL | Uncommitted changes detected. Living dangerously. | + + ---------------------------------------------------------------- + DECLARATION + ---------------------------------------------------------------- + + I, Greg Gauthier, do hereby certify that the software + project "womm-certification" has been evaluated in accordance + with WOMM-STD-001:2026 and has achieved: + + *** WOMM BRONZE CERTIFICATION *** + + This certification is granted under the following conditions: + + 1. The software was observed to work on my machine. + 2. No guarantee is made regarding any other machine. + 3. This certificate is void if anyone asks "are you sure?" + 4. Validity expires upon the next git pull, dependency update, + or passage of more than 24 hours. + + ---------------------------------------------------------------- + SIGNATURES + ---------------------------------------------------------------- + + Certified by: + + ___________________________ + Greg Gauthier + My Machine + 2026-04-02 + + Witnessed by: + + ___________________________ + /dev/null + (The Void) + + + Approved by the WOMM Standards Committee: + + ___________________________ + The Committee + (Quorum: 1) + + + ================================================================ + This document was generated in compliance with WOMM-STD-001:2026 + "Works On My Machine" Certification Standard + First Edition, 2026-04-02 + + Copyright 2026 The WOMM Standards Committee. + All rights reserved, except the right to guarantee + it works on your machine. + ================================================================ diff --git a/WOMM-STANDARD-001.md b/WOMM-STANDARD-001.md new file mode 100644 index 0000000..1bbfde0 --- /dev/null +++ b/WOMM-STANDARD-001.md @@ -0,0 +1,482 @@ +# WOMM-STD-001:2026 + +## Works On My Machine — Certification Standard + +**First Edition — 2026-04-02** + +Published by the **International Bureau of Local Development Assurance (IBLDA)** +on behalf of the **WOMM Standards Committee (WSC)** + +--- + +> *"It works on my machine."* +> — Every developer, at some point + +--- + +## Document Classification + +| Field | Value | +|----------------------|--------------------------------------------| +| Standard Number | WOMM-STD-001:2026 | +| Edition | First | +| Status | **ACTIVE** | +| Classification | Public — Developer Eyes Only | +| Supersedes | Verbal assurances, Slack messages, shrugs | +| Committee | WOMM Standards Committee (WSC/TC-1) | +| Secretariat | IBLDA, Geneva (or wherever the laptop is) | + +--- + +## Foreword + +The WOMM Standards Committee (WSC) was convened in response to an +industry-wide crisis of confidence in the phrase "it works on my +machine." For decades, this declaration has served as the final word +in software dispute resolution, yet it has lacked the formal rigour +demanded by modern engineering practice. + +This standard provides a comprehensive framework for evaluating, +certifying, and communicating the operational status of software +within the confines of a single developer's workstation. It is the +product of extensive deliberation by experts from such distinguished +fields as "closing the laptop and hoping for the best" and "I swear +I didn't change anything." + +The WSC acknowledges that this standard cannot guarantee software +will work on any machine other than the one on which certification +was performed. This is, in fact, the entire point. + +Participation in this certification programme is voluntary. However, +developers who do not participate will be asked "but did you even +try?" at their next standup. + +--- + +## 1. Scope + +### 1.1 Purpose + +This standard establishes the requirements for certifying that a +software project **works on the certifier's machine** at the time +of certification. It provides: + +a) A common vocabulary for describing the state of "working" + +b) A tiered certification framework with escalating levels of + assurance + +c) Procedures for auditing and verifying compliance + +d) A formal mechanism for responding to the question "well, does + it work?" + +### 1.2 Applicability + +This standard applies to any software project, repository, script, +or "quick hack I threw together" that resides on a local filesystem +and is subject to the claim "it works on my machine." + +### 1.3 Limitations + +This standard explicitly does **not** certify that the software: + +- Works on anyone else's machine +- Will continue to work after the next `git pull` +- Works when the Wi-Fi is off (unless the project does not require + Wi-Fi, in which case, congratulations) +- Works in production +- Works + +The certification is valid only for the exact hardware, software, +environment variables, running background processes, phase of the +moon, and emotional state of the developer present at the time of +certification. + +--- + +## 2. Normative References + +The following documents are indispensable for the application of +this standard. For dated references, only the edition cited applies. +For undated references, good luck. + +| Reference | Title | +|----------------------|----------------------------------------------------| +| WOMM-STD-000:2025 | Definition of "It" in Software Contexts | +| WOMM-STD-002:2026 | Requirements for the Phrase "I Didn't Touch That" | +| WOMM-STD-003:2026 | Guidelines for Reproducible Shrugging | +| IEEE 404 | Standard Not Found | +| RFC 2324 | Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0)| +| ISO 9001 | Quality management systems (cited for comedic contrast) | +| xkcd 1172 | Workflow (normative) | + +--- + +## 3. Terms and Definitions + +For the purposes of this standard, the following terms and +definitions apply. + +### 3.1 works + +The software executes without producing an error message that the +developer cannot explain. Note: errors that the developer *can* +explain (e.g., "oh that's fine, it always does that") do not +constitute failure. + +### 3.2 my machine + +The specific computer, virtual machine, container, or cardboard +box with blinking lights on which the developer performs their +work. "My machine" is implicitly scoped to the exact configuration +present at certification time, including but not limited to: + +- Operating system and version +- Installed packages and their versions +- Environment variables (exported and forgotten) +- Browser tabs currently open +- Number of Docker containers running +- Whether Spotify is playing lo-fi beats + +### 3.3 it + +The software under certification. The antecedent of "it" must be +identifiable from context, project directory, or vigorous pointing +at a screen. See WOMM-STD-000. + +### 3.4 compiles + +The software can be transformed from source code into an executable +or interpretable form without the build tool exiting with a non-zero +status code. Warnings are not errors. Warnings have never been +errors. We do not speak of `-Werror`. + +### 3.5 tests pass + +All automated tests in the project's test suite complete with a +passing status. Tests that are skipped, marked as expected failures, +or commented out with `# TODO: fix this later` are considered to +have passed in the spiritual sense. + +### 3.6 but did you pull main? + +A ritual question posed during certification disputes. The correct +response is always "yes" regardless of whether one has, in fact, +pulled main. + +### 3.7 the vibes + +An intangible but critical quality metric. Software that works but +feels wrong has poor vibes and may be subject to additional scrutiny +under Section 6. + +### 3.8 nondeterministic + +A word used to describe test failures that only happen in CI. See +also: "cosmic rays", "timing issue", "works on my machine." + +--- + +## 4. Certification Levels + +### 4.1 General + +WOMM certification is awarded at one of four levels, each +representing an increasing degree of assurance that the software +works on the certifier's machine. + +### 4.2 Level Definitions + +#### 4.2.1 WOMM Bronze — "It Compiles" + +Requirements: + +a) The project directory exists and contains at least one source + file recognisable as code + +b) The source code can be parsed without syntax errors + +c) The developer can point to the project and say "that's the + one" with reasonable confidence + +Bronze certification represents the minimum viable claim that +software exists and is not, in fact, just a README. + +#### 4.2.2 WOMM Silver — "Tests Pass" + +Requirements: + +a) All requirements of WOMM Bronze + +b) A test suite exists within the project + +c) The test suite executes and all tests pass (see definition 3.5) + +d) The developer did not achieve this by deleting the failing tests + +Silver certification indicates that the software does what someone, +at some point, thought it should do. + +#### 4.2.3 WOMM Gold — "Production Ready*" + +\*on my machine + +Requirements: + +a) All requirements of WOMM Silver + +b) A README file exists and contains at least one true statement + about the project + +c) No `TODO`, `FIXME`, or `HACK` comments remain in the codebase + (or they have been reclassified as "known architectural decisions") + +d) The developer has mass-suppressed that vague feeling that + something isn't quite right + +Gold certification indicates that the software meets a standard of +quality that the developer would be comfortable showing to a +colleague, provided the colleague does not look too closely. + +#### 4.2.4 WOMM Platinum — "Works Everywhere*" + +\*that I have tested, which is here + +Requirements: + +a) All requirements of WOMM Gold + +b) A continuous integration configuration file is present in the + repository (e.g., `.github/workflows/`, `Jenkinsfile`, + `.gitlab-ci.yml`, `bitbucket-pipelines.yml`) + +c) The git working tree is clean (no uncommitted changes) + +d) The developer has mass-suppressed the even stronger vague feeling + that something isn't quite right + +Platinum certification represents the highest level of assurance +available under this standard. It indicates that the developer has +not only verified local functionality but has at least *gestured* +toward the concept of reproducibility. + +--- + +## 5. Certification Procedure + +### 5.1 Pre-Certification + +Before initiating the certification process, the certifier SHALL: + +a) Close all unrelated terminal windows to reduce anxiety + +b) Ensure the machine is in a "known good state" (defined as: the + state it's in right now) + +c) Optionally mutter "okay, let's see" under their breath + +### 5.2 Certification Execution + +Certification SHALL be performed by executing the WOMM CLI tool +(`womm certify`) in the root directory of the project under test. +The tool will automatically evaluate all applicable requirements +and assign the highest achievable certification level. + +Manual certification (i.e., just saying "it works on my machine" +without running the tool) is deprecated as of this edition but +remains in widespread use. + +### 5.3 Post-Certification + +Upon successful certification, the certifier SHALL: + +a) Generate a certificate of compliance using `womm certificate` + +b) Optionally affix the WOMM seal to the project's README + +c) Lean back in their chair with quiet satisfaction + +d) Not touch anything else + +### 5.4 Recertification + +Certification is valid until: + +- Any file in the project is modified +- Any dependency is updated +- The developer runs `git pull` +- The machine is restarted +- More than 24 hours have elapsed +- Someone asks "are you sure?" + +Recertification follows the same procedure as initial certification. + +--- + +## 6. Audit Procedures + +### 6.1 General + +An audit may be requested by any stakeholder who has reason to +believe that the claim "it works on my machine" is unsubstantiated. +Common triggers include: + +- The software does not work on the auditor's machine +- The CI pipeline is red +- A customer has reported an issue +- It's Monday + +### 6.2 Audit Process + +The audit SHALL proceed as follows: + +a) The auditor requests the certifier demonstrate the software + working on their machine + +b) The certifier navigates to the project directory, ideally while + saying "it was working five minutes ago" + +c) The `womm certify` command is executed + +d) Results are compared against the claimed certification level + +e) If certification fails, proceed to Section 7 + +### 6.3 Environmental Considerations + +The auditor must acknowledge that the software's behaviour may be +influenced by factors beyond the developer's control, including but +not limited to: + +- The auditor's presence (observer effect) +- Solar flares +- npm +- The fact that Mercury is in retrograde + +--- + +## 7. Non-Conformance and Appeals + +### 7.1 Non-Conformance + +A non-conformance occurs when the software fails to meet the +requirements of its claimed certification level during an audit. + +### 7.2 Immediate Remediation Steps + +Upon discovery of non-conformance, the following steps SHALL be +attempted in order: + +1. Run the command again +2. Run the command again, but slower +3. Clear all caches (local, browser, DNS, emotional) +4. Run `git status` and stare at the output pensively +5. Check if the correct branch is checked out +6. Ask "did someone push something?" +7. Restart the machine +8. Restart the machine again, but with feeling + +### 7.3 Appeals + +If remediation is unsuccessful, the certifier may appeal by filing +a Formal Declaration of Bewilderment (Form WOMM-7B), which must +include: + +- Timestamp of last known working state +- A sworn statement that "I literally didn't change anything" +- Screenshot evidence (optional but strongly recommended) +- Stack Overflow link that seems relevant but isn't + +--- + +## 8. Marking and Labelling + +### 8.1 WOMM Seal + +Projects that have achieved WOMM certification MAY display the +official WOMM Seal of Approval in their documentation, README, or +office wall. + +The seal SHALL include: + +- The text "WOMM CERTIFIED" +- The certification level (Bronze, Silver, Gold, or Platinum) +- The date of certification +- The text "Works On My Machine" or an approved abbreviation + +### 8.2 Usage Restrictions + +The WOMM Seal SHALL NOT be used to imply that the software works +on any machine other than the certifier's. Misuse of the seal +constitutes a violation of WOMM-STD-001 and may result in the +offender being asked to "fix it in production, then." + +--- + +## Appendix A (Informative): Common Excuses and Their Classification + +| Excuse | Classification | Accepted? | +|--------|---------------|-----------| +| "It works on my machine" | Standard claim | Yes (with certification) | +| "I didn't change anything" | Denial | Under investigation | +| "It must be a caching issue" | Deflection | Provisionally accepted | +| "That test is flaky" | Technical excuse | Accepted if test is, in fact, flaky | +| "Works in debug mode" | Partial conformance | Bronze only | +| "The API must be down" | External attribution | Requires evidence | +| "Have you tried clearing your node_modules?" | Counter-audit | Procedurally valid | +| "It's a known issue" | Acknowledgement | Accepted if issue is, in fact, known | +| "That's not a bug, it's a feature" | Reclassification | Requires product owner sign-off | +| "I'll fix it tomorrow" | Deferral | Not accepted for certification | +| "It worked yesterday" | Temporal defence | Expired | +| "It works if you do it in the right order" | User-error claim | Gold and above only | + +--- + +## Appendix B (Normative): Official WOMM Seal Usage Guidelines + +### B.1 Minimum Seal Size + +The WOMM Seal SHALL be displayed at a minimum size of 64x64 pixels +in digital media, or 20mm diameter in print. Smaller sizes risk the +seal being mistaken for a loading spinner. + +### B.2 Colour Requirements + +The seal SHALL be displayed in the official WOMM colour palette: + +| Level | Primary Colour | Hex | +|----------|---------------|---------| +| Bronze | Copper | #B87333 | +| Silver | Silver | #C0C0C0 | +| Gold | Gold | #FFD700 | +| Platinum | Platinum | #E5E4E2 | + +### B.3 Prohibited Modifications + +The following modifications to the seal are prohibited: + +- Adding the word "probably" before "certified" +- Replacing the checkmark with a question mark +- Displaying the seal upside-down (this indicates WOMM Decertification) +- Using the seal as a loading animation +- Tattooing the seal on any body part (this is not prohibited per se, + but the Committee questions your judgement) + +--- + +## Appendix C (Informative): Revision History + +| Version | Date | Description | +|---------|------------|-------------------------------------------------| +| 0.1 | 2025-01-15 | Initial draft, written on a napkin | +| 0.2 | 2025-06-01 | Added certification levels after heated debate | +| 0.9 | 2025-11-30 | Committee reached quorum (2 people) | +| 1.0 | 2026-04-02 | First edition published. It works on my machine. | + +--- + +*Copyright 2026 The WOMM Standards Committee. All rights reserved, +except the right to guarantee it works on your machine.* + +*END OF WOMM-STD-001:2026* diff --git a/assets/womm-seal.svg b/assets/womm-seal.svg new file mode 100644 index 0000000..d859794 --- /dev/null +++ b/assets/womm-seal.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WORKS ON MY MACHINE + + + + + + CERTIFIED + + + + + + + + + + + + + + + WOMM + + + + + + SEAL OF APPROVAL + + + EST. 2026 + + + + + + + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..303a55a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[build-system] +requires = ["setuptools>=68.0", "setuptools-scm>=8.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "womm-certification" +version = "1.0.0" +description = "Works On My Machine — The Official Certification Toolkit" +readme = "README.md" +requires-python = ">=3.9" +license = "MIT" +authors = [ + { name = "The WOMM Standards Committee" }, +] +dependencies = [ + "click>=8.0", + "rich>=13.0", +] + +[project.optional-dependencies] +png = ["cairosvg>=2.7"] +dev = ["pytest>=8.0"] + +[project.scripts] +womm = "womm.cli:main" + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/src/womm/__init__.py b/src/womm/__init__.py new file mode 100644 index 0000000..193424d --- /dev/null +++ b/src/womm/__init__.py @@ -0,0 +1,3 @@ +"""WOMM — Works On My Machine Certification Toolkit.""" + +__version__ = "1.0.0" diff --git a/src/womm/badge.py b/src/womm/badge.py new file mode 100644 index 0000000..4e6b843 --- /dev/null +++ b/src/womm/badge.py @@ -0,0 +1,159 @@ +"""Generate WOMM Seal of Approval badges as SVG.""" + +from __future__ import annotations + +from datetime import date +from pathlib import Path + +from womm.certify import Level + +_SVG_TEMPLATE = """\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WORKS ON MY MACHINE + + + + + + CERTIFIED + + + + + + + + + + + WOMM + + + + + + {level_label} + + + {date_str} + + + + + + + +""" + +_CHECK_COLOURS = { + Level.NONE: "#888888", + Level.BRONZE: "#8B5E3C", + Level.SILVER: "#2d6a4f", + Level.GOLD: "#2d6a4f", + Level.PLATINUM: "#2d6a4f", +} + + +def generate_badge_svg( + level: Level = Level.GOLD, + certification_date: date | None = None, + size: int = 400, +) -> str: + """Generate an SVG string for a WOMM Seal of Approval. + + Args: + level: Certification level to display. + certification_date: Date to show on the seal. Defaults to today. + size: Width and height in pixels. + + Returns: + SVG markup as a string. + """ + cert_date = certification_date or date.today() + return _SVG_TEMPLATE.format( + size=size, + colour=level.colour_hex, + check_colour=_CHECK_COLOURS.get(level, "#2d6a4f"), + level_label=level.label.upper(), + date_str=cert_date.strftime("%Y-%m-%d"), + ) + + +def save_badge( + output_path: str | Path, + level: Level = Level.GOLD, + certification_date: date | None = None, + size: int = 400, +) -> Path: + """Generate and save a WOMM badge. + + Args: + output_path: Where to write the file. If it ends in .png and + cairosvg is available, a PNG will be generated. + level: Certification level. + certification_date: Date for the seal. + size: Badge dimensions. + + Returns: + The resolved output path. + """ + out = Path(output_path).resolve() + svg_content = generate_badge_svg(level, certification_date, size) + + if out.suffix.lower() == ".png": + try: + import cairosvg # type: ignore[import-untyped] + cairosvg.svg2png(bytestring=svg_content.encode(), write_to=str(out)) + except ImportError: + # Fall back to saving SVG with a note + svg_path = out.with_suffix(".svg") + svg_path.write_text(svg_content, encoding="utf-8") + raise SystemExit( + f"cairosvg not installed — saved SVG to {svg_path} instead.\n" + "Install with: pip install 'womm-certification[png]'" + ) + else: + out.write_text(svg_content, encoding="utf-8") + + return out diff --git a/src/womm/certify.py b/src/womm/certify.py new file mode 100644 index 0000000..efade16 --- /dev/null +++ b/src/womm/certify.py @@ -0,0 +1,335 @@ +"""WOMM certification checks per WOMM-STD-001:2026.""" + +from __future__ import annotations + +import ast +import os +import subprocess +from dataclasses import dataclass, field +from enum import IntEnum +from pathlib import Path + + +class Level(IntEnum): + """WOMM certification levels (Section 4.2).""" + + NONE = 0 + BRONZE = 1 + SILVER = 2 + GOLD = 3 + PLATINUM = 4 + + @property + def label(self) -> str: + return self.name.capitalize() if self != Level.NONE else "Uncertified" + + @property + def colour_hex(self) -> str: + return { + Level.NONE: "#888888", + Level.BRONZE: "#B87333", + Level.SILVER: "#C0C0C0", + Level.GOLD: "#FFD700", + Level.PLATINUM: "#E5E4E2", + }[self] + + +@dataclass +class CheckResult: + """Outcome of a single certification check.""" + + name: str + passed: bool + message: str + level: Level + + +@dataclass +class CertificationReport: + """Full certification report for a project.""" + + project_path: Path + project_name: str + results: list[CheckResult] = field(default_factory=list) + + @property + def level(self) -> Level: + """Highest level for which all checks passed.""" + achieved = Level.PLATINUM + for result in self.results: + if not result.passed and result.level <= achieved: + achieved = Level(result.level - 1) + return max(achieved, Level.NONE) + + +# --------------------------------------------------------------------------- +# Individual checks +# --------------------------------------------------------------------------- + +def _check_project_exists(path: Path) -> CheckResult: + exists = path.is_dir() + return CheckResult( + name="Project directory exists", + passed=exists, + message=( + "Project directory located. We're off to a strong start." + if exists + else "Project directory not found. This is... concerning." + ), + level=Level.BRONZE, + ) + + +def _check_has_source_files(path: Path) -> CheckResult: + extensions = {".py", ".js", ".ts", ".c", ".cpp", ".go", ".rs", ".java", ".rb", ".sh"} + found = any( + f.suffix in extensions + for f in path.rglob("*") + if f.is_file() and ".git" not in f.parts + ) + return CheckResult( + name="Contains source code", + passed=found, + message=( + "Source files detected. This is, in fact, a software project." + if found + else "No recognisable source files found. Are you sure this isn't just a README?" + ), + level=Level.BRONZE, + ) + + +def _check_python_syntax(path: Path) -> CheckResult: + py_files = [ + f for f in path.rglob("*.py") + if f.is_file() and ".git" not in f.parts + ] + if not py_files: + return CheckResult( + name="Python syntax check", + passed=True, + message="No Python files to check. Blissful ignorance achieved.", + level=Level.BRONZE, + ) + errors: list[str] = [] + for py_file in py_files: + try: + ast.parse(py_file.read_text(encoding="utf-8"), filename=str(py_file)) + except SyntaxError as exc: + errors.append(f"{py_file.name}:{exc.lineno}: {exc.msg}") + return CheckResult( + name="Python syntax check", + passed=len(errors) == 0, + message=( + "All Python files parse cleanly. The AST smiles upon you." + if not errors + else f"Syntax errors found ({len(errors)}): {'; '.join(errors[:3])}" + ), + level=Level.BRONZE, + ) + + +def _check_tests_exist(path: Path) -> CheckResult: + test_indicators = [ + path / "tests", + path / "test", + *path.rglob("test_*.py"), + *path.rglob("*_test.py"), + *path.rglob("tests.py"), + ] + found = any(p.exists() for p in test_indicators) + return CheckResult( + name="Test suite exists", + passed=found, + message=( + "Test suite located. Someone here believes in accountability." + if found + else "No test suite found. Bold strategy." + ), + level=Level.SILVER, + ) + + +def _check_tests_pass(path: Path) -> CheckResult: + # Try pytest, then unittest + for cmd in (["python", "-m", "pytest", "-x", "-q"], ["python", "-m", "unittest", "discover", "-s", "."]): + try: + result = subprocess.run( + cmd, + cwd=path, + capture_output=True, + text=True, + timeout=120, + ) + if result.returncode == 0: + return CheckResult( + name="Tests pass", + passed=True, + message="All tests pass. On this machine, at least.", + level=Level.SILVER, + ) + elif result.returncode == 5 and "pytest" in cmd[2]: + # pytest exit code 5 = no tests collected + continue + else: + snippet = (result.stdout + result.stderr).strip().split("\n")[-1] + return CheckResult( + name="Tests pass", + passed=False, + message=f"Tests failed. {snippet}", + level=Level.SILVER, + ) + except FileNotFoundError: + continue + except subprocess.TimeoutExpired: + return CheckResult( + name="Tests pass", + passed=False, + message="Tests timed out after 120s. The machine grew weary.", + level=Level.SILVER, + ) + return CheckResult( + name="Tests pass", + passed=False, + message="Could not find a test runner. Neither pytest nor unittest obliged.", + level=Level.SILVER, + ) + + +def _check_readme_exists(path: Path) -> CheckResult: + readmes = [path / name for name in ("README.md", "README.rst", "README.txt", "README")] + found = any(r.is_file() for r in readmes) + return CheckResult( + name="README exists", + passed=found, + message=( + "README found. Documentation: the thought that counts." + if found + else "No README. Future you will regret this." + ), + level=Level.GOLD, + ) + + +def _check_no_todo_comments(path: Path) -> CheckResult: + markers = ("TODO", "FIXME", "HACK", "XXX") + offenders: list[str] = [] + for f in path.rglob("*"): + if not f.is_file() or ".git" in f.parts or f.suffix not in ( + ".py", ".js", ".ts", ".c", ".cpp", ".go", ".rs", ".java", ".rb", + ): + continue + try: + for i, line in enumerate(f.read_text(encoding="utf-8", errors="ignore").splitlines(), 1): + if any(m in line for m in markers): + offenders.append(f"{f.name}:{i}") + if len(offenders) >= 5: + break + except (OSError, UnicodeDecodeError): + continue + if len(offenders) >= 5: + break + return CheckResult( + name="No TODO/FIXME/HACK comments", + passed=len(offenders) == 0, + message=( + "No shameful markers found. Either you're thorough, or you're sneaky." + if not offenders + else f"Found markers in: {', '.join(offenders)}. The guilt is documented." + ), + level=Level.GOLD, + ) + + +def _check_ci_config_exists(path: Path) -> CheckResult: + ci_paths = [ + path / ".github" / "workflows", + path / ".gitlab-ci.yml", + path / "Jenkinsfile", + path / ".circleci", + path / "bitbucket-pipelines.yml", + path / ".travis.yml", + path / "azure-pipelines.yml", + ] + found = any(p.exists() for p in ci_paths) + return CheckResult( + name="CI configuration exists", + passed=found, + message=( + "CI config detected. You've at least gestured toward reproducibility." + if found + else "No CI configuration found. You are the CI." + ), + level=Level.PLATINUM, + ) + + +def _check_git_clean(path: Path) -> CheckResult: + try: + result = subprocess.run( + ["git", "status", "--porcelain"], + cwd=path, + capture_output=True, + text=True, + timeout=10, + ) + clean = result.returncode == 0 and result.stdout.strip() == "" + return CheckResult( + name="Git working tree is clean", + passed=clean, + message=( + "Working tree is clean. Pristine. Untouched. (For now.)" + if clean + else "Uncommitted changes detected. Living dangerously." + ), + level=Level.PLATINUM, + ) + except (FileNotFoundError, subprocess.TimeoutExpired): + return CheckResult( + name="Git working tree is clean", + passed=False, + message="Git not available or not a git repository. The void stares back.", + level=Level.PLATINUM, + ) + + +# --------------------------------------------------------------------------- +# Main certification entry point +# --------------------------------------------------------------------------- + +ALL_CHECKS = [ + _check_project_exists, + _check_has_source_files, + _check_python_syntax, + _check_tests_exist, + _check_tests_pass, + _check_readme_exists, + _check_no_todo_comments, + _check_ci_config_exists, + _check_git_clean, +] + + +def certify(path: str | Path | None = None, target_level: Level | None = None) -> CertificationReport: + """Run WOMM certification checks against a project directory. + + Args: + path: Project root directory. Defaults to cwd. + target_level: If set, only run checks up to this level. + + Returns: + A CertificationReport with all check results. + """ + project_path = Path(path or os.getcwd()).resolve() + report = CertificationReport( + project_path=project_path, + project_name=project_path.name, + ) + + for check_fn in ALL_CHECKS: + result = check_fn(project_path) + report.results.append(result) + if target_level is not None and result.level > target_level: + break + + return report diff --git a/src/womm/cli.py b/src/womm/cli.py new file mode 100644 index 0000000..f348d11 --- /dev/null +++ b/src/womm/cli.py @@ -0,0 +1,172 @@ +"""WOMM CLI — Works On My Machine Certification Toolkit.""" + +from __future__ import annotations + +from pathlib import Path + +import click +from rich.console import Console +from rich.panel import Panel +from rich.table import Table +from rich.text import Text + +from womm.certify import Level, certify +from womm.badge import save_badge +from womm.document import generate_certificate, save_certificate + +console = Console() + +LEVEL_STYLES = { + Level.NONE: "dim", + Level.BRONZE: "rgb(184,115,51)", + Level.SILVER: "grey78", + Level.GOLD: "yellow", + Level.PLATINUM: "white", +} + +LEVEL_EMOJI = { + Level.NONE: " ", + Level.BRONZE: " ", + Level.SILVER: " ", + Level.GOLD: " ", + Level.PLATINUM: " ", +} + + +def _level_choice(value: str) -> Level | None: + if value is None: + return None + try: + return Level[value.upper()] + except KeyError: + raise click.BadParameter(f"Unknown level: {value}. Choose from: bronze, silver, gold, platinum") + + +@click.group() +@click.version_option(package_name="womm-certification") +def main() -> None: + """WOMM -- Works On My Machine Certification Toolkit. + + The official tool for certifying that software works on your machine, + in accordance with WOMM-STD-001:2026. + """ + + +@main.command() +@click.argument("path", default=".", type=click.Path(exists=True)) +@click.option("--level", "target_level", default=None, help="Target certification level (bronze/silver/gold/platinum).") +def certify_cmd(path: str, target_level: str | None) -> None: + """Run WOMM certification checks against a project.""" + level = _level_choice(target_level) if target_level else None + report = certify(path, target_level=level) + + # Header + console.print() + console.print( + Panel( + "[bold]WOMM-STD-001:2026[/bold]\nWorks On My Machine — Certification Report", + border_style="blue", + expand=False, + ) + ) + console.print(f" Project: [bold]{report.project_name}[/bold]") + console.print(f" Path: {report.project_path}") + console.print() + + # Results table + table = Table(show_header=True, header_style="bold", expand=True) + table.add_column("Level", width=10) + table.add_column("Check", width=35) + table.add_column("Result", width=6) + table.add_column("Detail") + + for result in report.results: + style = LEVEL_STYLES.get(result.level, "") + status = Text("PASS", style="bold green") if result.passed else Text("FAIL", style="bold red") + table.add_row( + result.level.label, + result.name, + status, + result.message, + style=style if not result.passed else "", + ) + + console.print(table) + console.print() + + # Final verdict + achieved = report.level + style = LEVEL_STYLES.get(achieved, "bold") + emoji = LEVEL_EMOJI.get(achieved, "") + + if achieved == Level.NONE: + console.print( + Panel( + "[bold red]CERTIFICATION FAILED[/bold red]\n\n" + "The software does not meet the minimum requirements of\n" + "WOMM-STD-001:2026. Please address the above findings\n" + "and try again. Or don't. We're not your boss.", + border_style="red", + expand=False, + ) + ) + else: + console.print( + Panel( + f"[bold {style}]{emoji}WOMM {achieved.label.upper()} CERTIFIED{emoji}[/bold {style}]\n\n" + f"This project has been certified to WOMM {achieved.label} level\n" + "in accordance with WOMM-STD-001:2026.\n\n" + "[dim]This certification is valid on this machine only.\n" + "Void if anyone asks 'are you sure?'[/dim]", + border_style=style, + expand=False, + ) + ) + + console.print() + + +@main.command() +@click.option("--level", default="gold", help="Certification level (bronze/silver/gold/platinum).") +@click.option("--output", "-o", default="womm-seal.svg", help="Output file path (.svg or .png).") +@click.option("--size", default=400, type=int, help="Badge size in pixels.") +def badge(level: str, output: str, size: int) -> None: + """Generate a WOMM Seal of Approval badge.""" + cert_level = _level_choice(level) + if cert_level is None or cert_level == Level.NONE: + raise click.BadParameter("Level must be bronze, silver, gold, or platinum.") + + try: + out_path = save_badge(output, level=cert_level, size=size) + console.print(f"[green]Badge saved to:[/green] {out_path}") + except SystemExit as exc: + console.print(f"[yellow]{exc}[/yellow]") + + +@main.command() +@click.argument("path", default=".", type=click.Path(exists=True)) +@click.option("--output", "-o", default=None, help="Save certificate to file (default: print to stdout).") +def certificate(path: str, output: str | None) -> None: + """Generate a formal WOMM certificate of compliance.""" + report = certify(path) + + if output: + out_path = save_certificate(report, output) + console.print(f"[green]Certificate saved to:[/green] {out_path}") + else: + console.print(generate_certificate(report)) + + +@main.command() +def standard() -> None: + """Print the full WOMM-STD-001:2026 standard.""" + standard_path = Path(__file__).resolve().parent.parent.parent / "WOMM-STANDARD-001.md" + if standard_path.is_file(): + console.print(standard_path.read_text(encoding="utf-8")) + else: + console.print("[yellow]Standard document not found.[/yellow]") + console.print("Expected at: " + str(standard_path)) + + +# Register 'certify' as the command name (avoiding clash with the function import) +main.add_command(certify_cmd, name="certify") diff --git a/src/womm/document.py b/src/womm/document.py new file mode 100644 index 0000000..c1b3a97 --- /dev/null +++ b/src/womm/document.py @@ -0,0 +1,154 @@ +"""Generate formal WOMM certification documents.""" + +from __future__ import annotations + +import getpass +import platform +import textwrap +from datetime import datetime +from pathlib import Path + +from womm.certify import CertificationReport, Level + + +def generate_certificate(report: CertificationReport) -> str: + """Generate a formal markdown certificate from a certification report. + + Args: + report: A completed CertificationReport. + + Returns: + Markdown-formatted certificate document. + """ + now = datetime.now() + hostname = platform.node() or "UNKNOWN-HOST" + username = getpass.getuser() + level = report.level + + # Build check results table + check_rows = [] + for r in report.results: + status = "PASS" if r.passed else "FAIL" + check_rows.append(f"| {r.name:<40} | {status:<6} | {r.message} |") + checks_table = "\n".join(check_rows) + + certificate = textwrap.dedent(f"""\ + ================================================================ + WOMM CERTIFICATE OF COMPLIANCE + WOMM-STD-001:2026 + ================================================================ + + Certificate Number: WOMM-{now.strftime('%Y%m%d%H%M%S')}-{abs(hash(report.project_name)) % 10000:04d} + Date of Issuance: {now.strftime('%Y-%m-%d %H:%M:%S')} + Standard: WOMM-STD-001:2026, First Edition + + ---------------------------------------------------------------- + PROJECT INFORMATION + ---------------------------------------------------------------- + + Project Name: {report.project_name} + Project Path: {report.project_path} + Certification Level: {level.label.upper()} ({_level_tagline(level)}) + + ---------------------------------------------------------------- + CERTIFICATION AUTHORITY + ---------------------------------------------------------------- + + Certifying Machine: {hostname} + Certifying User: {username} + Operating System: {platform.system()} {platform.release()} + Python Version: {platform.python_version()} + + This certification was performed on the above machine and is + valid exclusively for this machine in its current configuration, + including all running processes, environment variables, and the + certifier's current emotional state. + + ---------------------------------------------------------------- + AUDIT TRAIL + ---------------------------------------------------------------- + + | {"Check":<40} | {"Result":<6} | Detail | + |{"-" * 42}|{"-" * 8}|--------| + {checks_table} + + ---------------------------------------------------------------- + DECLARATION + ---------------------------------------------------------------- + + I, {username}@{hostname}, do hereby certify that the software + project "{report.project_name}" has been evaluated in accordance + with WOMM-STD-001:2026 and has achieved: + + *** WOMM {level.label.upper()} CERTIFICATION *** + + This certification is granted under the following conditions: + + 1. The software was observed to work on my machine. + 2. No guarantee is made regarding any other machine. + 3. This certificate is void if anyone asks "are you sure?" + 4. Validity expires upon the next git pull, dependency update, + or passage of more than 24 hours. + + ---------------------------------------------------------------- + SIGNATURES + ---------------------------------------------------------------- + + Certified by: + + ___________________________ + {username} + {hostname} + {now.strftime('%Y-%m-%d')} + + Witnessed by: + + ___________________________ + /dev/null + (The Void) + + + Approved by the WOMM Standards Committee: + + ___________________________ + The Committee + (Quorum: 1) + + + ================================================================ + This document was generated in compliance with WOMM-STD-001:2026 + "Works On My Machine" Certification Standard + First Edition, 2026-04-02 + + Copyright 2026 The WOMM Standards Committee. + All rights reserved, except the right to guarantee + it works on your machine. + ================================================================ + """) + + return certificate + + +def _level_tagline(level: Level) -> str: + return { + Level.NONE: "Not Certified", + Level.BRONZE: "It Compiles", + Level.SILVER: "Tests Pass", + Level.GOLD: "Production Ready*", + Level.PLATINUM: "Works Everywhere*", + }.get(level, "Unknown") + + +def save_certificate(report: CertificationReport, output_path: str | Path) -> Path: + """Generate and save a certificate to a file. + + Args: + report: A completed CertificationReport. + output_path: File path to write the certificate. + + Returns: + The resolved output path. + """ + out = Path(output_path).resolve() + out.write_text(generate_certificate(report), encoding="utf-8") + return out diff --git a/src/womm_certification.egg-info/PKG-INFO b/src/womm_certification.egg-info/PKG-INFO new file mode 100644 index 0000000..4cd7f04 --- /dev/null +++ b/src/womm_certification.egg-info/PKG-INFO @@ -0,0 +1,79 @@ +Metadata-Version: 2.4 +Name: womm-certification +Version: 1.0.0 +Summary: Works On My Machine — The Official Certification Toolkit +Author: The WOMM Standards Committee +License-Expression: MIT +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +Requires-Dist: click>=8.0 +Requires-Dist: rich>=13.0 +Provides-Extra: png +Requires-Dist: cairosvg>=2.7; extra == "png" +Provides-Extra: dev +Requires-Dist: pytest>=8.0; extra == "dev" + +# WOMM Certification Toolkit + +**Works On My Machine -- The Official Certification Standard** + +A formal certification framework for the age-old claim "it works on my machine," complete with a bureaucratic standards document, an official seal of approval, and a CLI tool to certify your projects. + +## Installation + +```bash +pip install -e . +``` + +For PNG badge export support: + +```bash +pip install -e '.[png]' +``` + +## Usage + +### Certify a project + +```bash +womm certify # certify current directory +womm certify /path/to/project +womm certify --level gold # target a specific level +``` + +### Generate a badge + +```bash +womm badge # default: gold, womm-seal.svg +womm badge --level platinum -o seal.svg +``` + +### Generate a certificate + +```bash +womm certificate # print to stdout +womm certificate -o cert.txt # save to file +``` + +### Read the standard + +```bash +womm standard +``` + +## Certification Levels + +| Level | Requirements | +|----------|-------------| +| Bronze | Project exists, has source files, no syntax errors | +| Silver | Bronze + tests exist and pass | +| Gold | Silver + README exists, no TODO/FIXME/HACK comments | +| Platinum | Gold + CI config present, git tree is clean | + +## The Standard + +See [WOMM-STD-001:2026](WOMM-STANDARD-001.md) for the full certification standard. + +## License + +MIT diff --git a/src/womm_certification.egg-info/SOURCES.txt b/src/womm_certification.egg-info/SOURCES.txt new file mode 100644 index 0000000..5fc3b79 --- /dev/null +++ b/src/womm_certification.egg-info/SOURCES.txt @@ -0,0 +1,14 @@ +README.md +pyproject.toml +src/womm/__init__.py +src/womm/badge.py +src/womm/certify.py +src/womm/cli.py +src/womm/document.py +src/womm_certification.egg-info/PKG-INFO +src/womm_certification.egg-info/SOURCES.txt +src/womm_certification.egg-info/dependency_links.txt +src/womm_certification.egg-info/entry_points.txt +src/womm_certification.egg-info/requires.txt +src/womm_certification.egg-info/top_level.txt +tests/test_certify.py \ No newline at end of file diff --git a/src/womm_certification.egg-info/dependency_links.txt b/src/womm_certification.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/womm_certification.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/womm_certification.egg-info/entry_points.txt b/src/womm_certification.egg-info/entry_points.txt new file mode 100644 index 0000000..52be02e --- /dev/null +++ b/src/womm_certification.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +womm = womm.cli:main diff --git a/src/womm_certification.egg-info/requires.txt b/src/womm_certification.egg-info/requires.txt new file mode 100644 index 0000000..3e5ec17 --- /dev/null +++ b/src/womm_certification.egg-info/requires.txt @@ -0,0 +1,8 @@ +click>=8.0 +rich>=13.0 + +[dev] +pytest>=8.0 + +[png] +cairosvg>=2.7 diff --git a/src/womm_certification.egg-info/top_level.txt b/src/womm_certification.egg-info/top_level.txt new file mode 100644 index 0000000..931b388 --- /dev/null +++ b/src/womm_certification.egg-info/top_level.txt @@ -0,0 +1 @@ +womm diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_certify.py b/tests/test_certify.py new file mode 100644 index 0000000..a7b671d --- /dev/null +++ b/tests/test_certify.py @@ -0,0 +1,153 @@ +"""Tests for WOMM certification checks.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from womm.certify import ( + CertificationReport, + Level, + certify, + _check_has_source_files, + _check_project_exists, + _check_python_syntax, + _check_readme_exists, + _check_no_todo_comments, +) + + +@pytest.fixture() +def empty_project(tmp_path: Path) -> Path: + """An empty project directory.""" + return tmp_path + + +@pytest.fixture() +def bronze_project(tmp_path: Path) -> Path: + """A project that should achieve Bronze certification.""" + (tmp_path / "main.py").write_text("print('hello')\n") + return tmp_path + + +@pytest.fixture() +def silver_project(tmp_path: Path) -> Path: + """A project that should achieve at least Silver.""" + (tmp_path / "main.py").write_text("def add(a, b):\n return a + b\n") + tests_dir = tmp_path / "tests" + tests_dir.mkdir() + (tests_dir / "__init__.py").write_text("") + (tests_dir / "test_main.py").write_text( + "from pathlib import Path\nimport sys\n" + "sys.path.insert(0, str(Path(__file__).parent.parent))\n" + "from main import add\n\n" + "def test_add():\n assert add(1, 2) == 3\n" + ) + return tmp_path + + +class TestLevel: + def test_level_ordering(self) -> None: + assert Level.BRONZE < Level.SILVER < Level.GOLD < Level.PLATINUM + + def test_label(self) -> None: + assert Level.GOLD.label == "Gold" + assert Level.NONE.label == "Uncertified" + + def test_colour_hex(self) -> None: + assert Level.GOLD.colour_hex == "#FFD700" + + +class TestIndividualChecks: + def test_project_exists_pass(self, tmp_path: Path) -> None: + result = _check_project_exists(tmp_path) + assert result.passed + + def test_project_exists_fail(self, tmp_path: Path) -> None: + result = _check_project_exists(tmp_path / "nonexistent") + assert not result.passed + + def test_has_source_files_pass(self, bronze_project: Path) -> None: + result = _check_has_source_files(bronze_project) + assert result.passed + + def test_has_source_files_fail(self, empty_project: Path) -> None: + result = _check_has_source_files(empty_project) + assert not result.passed + + def test_python_syntax_pass(self, bronze_project: Path) -> None: + result = _check_python_syntax(bronze_project) + assert result.passed + + def test_python_syntax_fail(self, tmp_path: Path) -> None: + (tmp_path / "bad.py").write_text("def oops(\n") + result = _check_python_syntax(tmp_path) + assert not result.passed + + def test_readme_exists_pass(self, tmp_path: Path) -> None: + (tmp_path / "README.md").write_text("# Hello\n") + result = _check_readme_exists(tmp_path) + assert result.passed + + def test_readme_exists_fail(self, empty_project: Path) -> None: + result = _check_readme_exists(empty_project) + assert not result.passed + + def test_no_todos_pass(self, bronze_project: Path) -> None: + result = _check_no_todo_comments(bronze_project) + assert result.passed + + def test_no_todos_fail(self, tmp_path: Path) -> None: + (tmp_path / "main.py").write_text("# TODO: fix this\n") + result = _check_no_todo_comments(tmp_path) + assert not result.passed + + +class TestCertify: + def test_empty_project_gets_none(self, empty_project: Path) -> None: + report = certify(empty_project) + # Empty dir exists but has no source files + assert report.level == Level.NONE + + def test_bronze_project(self, bronze_project: Path) -> None: + report = certify(bronze_project, target_level=Level.BRONZE) + assert report.level >= Level.BRONZE + + def test_report_has_results(self, bronze_project: Path) -> None: + report = certify(bronze_project) + assert len(report.results) > 0 + assert all(r.name and r.message for r in report.results) + + def test_target_level_limits_checks(self, bronze_project: Path) -> None: + report = certify(bronze_project, target_level=Level.BRONZE) + levels_checked = {r.level for r in report.results} + assert Level.PLATINUM not in levels_checked + + +class TestCertificationReport: + def test_level_all_pass(self) -> None: + from womm.certify import CheckResult + + report = CertificationReport( + project_path=Path("/tmp/test"), + project_name="test", + results=[ + CheckResult("check1", True, "ok", Level.BRONZE), + CheckResult("check2", True, "ok", Level.SILVER), + ], + ) + assert report.level >= Level.SILVER + + def test_level_with_failure(self) -> None: + from womm.certify import CheckResult + + report = CertificationReport( + project_path=Path("/tmp/test"), + project_name="test", + results=[ + CheckResult("check1", True, "ok", Level.BRONZE), + CheckResult("check2", False, "nope", Level.SILVER), + ], + ) + assert report.level == Level.BRONZE