diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..e0ff20ab --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,25 @@ +# Code owners for ForgeLM. +# +# When a pull request is opened, GitHub requests a review from the owner of each +# path it changes. This file only routes those review requests: whether a review +# is *required* before merging is a branch-protection setting, not anything an +# entry here can turn on. See docs/standards/code-review.md for the review +# standard and GOVERNANCE.md for how ownership is granted. +# +# These rules are evaluated last-match-wins, so a more specific pattern placed +# below `*` overrides it. When an area gets its own owner, add their line below +# the default rather than replacing it, so the default owner stays on every path +# an area owner does not name. GitHub does not request a review for a draft or a +# draft pull request. + +* @cemililik + +# Repository scaffolding and policy files stay with the default owner even after +# an area owner is added, because these paths define the rules the area owners +# work under. +/.github/ @cemililik +/CONTRIBUTING.md @cemililik +/GOVERNANCE.md @cemililik +/MAINTAINERS.md @cemililik +/SECURITY.md @cemililik +/pyproject.toml @cemililik diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index c3756212..acb566f8 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,9 +3,9 @@ contact_links: - name: Report a security vulnerability url: https://github.com/HodeTech/ForgeLM/security/advisories/new about: Report vulnerabilities privately, never in a public issue (see SECURITY.md) - - name: Documentation + - name: Browse the documentation url: https://github.com/HodeTech/ForgeLM/tree/main/docs - about: Check the docs and guides before opening an issue + about: Read the docs and guides before opening a documentation issue - name: Discussions (Q&A) url: https://github.com/HodeTech/ForgeLM/discussions about: Ask questions and get help from the community diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 00000000..752e62c7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,52 @@ +name: Documentation +description: Report a documentation error, or propose an improvement to a guide or its Turkish mirror +labels: ["documentation", "needs-triage"] +body: + - type: markdown + attributes: + value: | + Thanks for improving the docs! One correction per issue is easiest to review. + + User-facing documentation is kept as an English page **and** a Turkish + mirror, and the two are updated together — see + [CONTRIBUTING.md](https://github.com/HodeTech/ForgeLM/blob/development/CONTRIBUTING.md). + You do not need to write the Turkish yourself: open the pull request with + the English change and say so, and a maintainer will complete the mirror. + Tell us below which copy you were reading. Documentation pull requests are + welcome directly. + + - type: input + id: page + attributes: + label: Page or file + description: The repository path or the documentation-site page you were reading. + placeholder: docs/guides/quickstart.md — or the Quick start page on the docs site + validations: + required: true + + - type: textarea + id: problem + attributes: + label: What is wrong or missing? + description: What does the page say, and what does the reader need instead? + validations: + required: true + + - type: textarea + id: suggestion + attributes: + label: Suggested wording (optional) + description: If you already know the fix, propose the text. We will tidy the language if needed. + + - type: dropdown + id: language + attributes: + label: Which copy is affected? + description: User-facing docs are maintained as an English page and a Turkish mirror. An issue that needs mirror work is labelled `i18n` when it is triaged. + options: + - English + - Turkish + - Both + - Not sure + validations: + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..7d861eb3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,69 @@ +# Dependabot configuration for ForgeLM. +# +# Dependabot opens a pull request when a dependency's declared range no longer +# covers the newest release. It is the upstream half of the supply-chain story: +# `tools/check_pip_audit.py` and the nightly CVE job catch known advisories, and +# these pull requests make a fix reachable. +# +# `versioning-strategy: widen` matters here. ForgeLM declares bounded ranges +# (`>=floor,` in evidence. Use `https://monitor.example.com:8443/plan` or an `endpoint_env` name (#93). +### Added + +- `CITATION.cff`, so the repository can be cited through GitHub's "Cite this + repository" panel (#964). +- `GOVERNANCE.md` and `MAINTAINERS.md` at the repository root, recording how + decisions are made, how the maintainer roles work, and which areas the current + maintainer owns (#964). +- `.github/dependabot.yml`, so dependency updates arrive as reviewed pull requests + rather than waiting for a manual version check; `.github/CODEOWNERS` to route + review requests without gating a merge; and a Documentation issue template, since + the issue chooser previously offered only a bug and a feature form (#964). + ### Changed - Record monitoring default provenance in `defaulted_fields` and mark literal URL projection with diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..9459c30c --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,37 @@ +cff-version: 1.2.0 +message: "If you use ForgeLM in your work, please cite it using the metadata below." +title: "ForgeLM" +abstract: >- + Config-driven LLM fine-tuning: validated YAML in, a fine-tuned model and an + audit trail out. Covers SFT, DPO, SimPO, KTO, ORPO and GRPO, with optional + dataset auditing, safety evaluation, evaluation gates, human approval and + EU AI Act compliance artifacts. The same configuration runs from a terminal, + a notebook, or a CI/CD pipeline. +type: software +authors: + - name: "ForgeLM Contributors" +repository-code: "https://github.com/HodeTech/ForgeLM" +url: "https://hodetech.github.io/ForgeLM/" +license: Apache-2.0 +keywords: + - llm + - fine-tuning + - lora + - qlora + - peft + - trl + - dpo + - grpo + - reproducibility + - mlops + - eu-ai-act + - ai-governance +# `version` and `date-released` are intentionally omitted for now: the released +# version is single-sourced in `pyproject.toml` (read at runtime by +# `forgelm/_version.py` via `importlib.metadata`, which carries no literal of its +# own) and the site's displayed version is derived from `CHANGELOG.md` by +# `tools/update_site_version.py --check`. This file is not one of that tool's +# targets, so a version here would be an un-guarded copy that no check keeps in +# sync — exactly what `docs/standards/release.md` exists to prevent. Extending the +# tool to rewrite this file (and adding a Zenodo DOI for citable releases) is +# tracked separately. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bab77ca5..d2150ece 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,8 @@ Thanks for your interest in contributing! ForgeLM is an open-source project and 3. Make the change and, for a code fix, add a regression test that reproduces the issue; follow the [testing standard](docs/standards/testing.md). Documentation changes update the English page and its Turkish mirror together. + If you do not write Turkish, open the pull request with the English change and say + so in the description — a maintainer will complete the mirror for you. 4. Run fast checks before pushing (replace the example test module with the one you touched): ```bash diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 00000000..30d303b1 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,123 @@ +# ForgeLM Governance + +> **How decisions are made in ForgeLM, who makes them, and how you can take on +> more responsibility over time.** For how to contribute, see +> [CONTRIBUTING.md](CONTRIBUTING.md); for behaviour expectations, see +> [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). + +## Current model + +ForgeLM is **maintainer-led**. The project has a small group of maintainers who +are accountable for the code, the standards and the releases — today, one +(see [MAINTAINERS.md](MAINTAINERS.md)). There is no foundation and no steering +committee; the goal of this document is not to describe a large organisation but +to make the path into one explicit. + +The project's product principles — reliability before features, enterprise +differentiation over feature parity, every capability config-driven and testable +— are recorded in [`docs/roadmap.md`](docs/roadmap.md) and are not restated here. +How decisions get made rests on two rules: + +1. **The standards are the rulebook.** `docs/standards/` holds the engineering + standards, `docs/roadmap/` holds what ships next, and the + [CHANGELOG.md](CHANGELOG.md) records what shipped. A decision that changes + behaviour is written into one of them; a decision that exists only in a chat + is not a decision. +2. **Public reasoning.** Non-trivial proposals, breaking changes and anything + that changes a documented contract are discussed in an **issue** before a + pull request is opened, so the reasoning is reviewable and linkable. + +## Roles + +| Role | What they do | How it is granted | +|---|---|---| +| **Contributor** | Reports bugs, opens pull requests, reviews others' changes, improves docs | Anyone; no appointment | +| **Maintainer** | Sets direction, merges to `development`, cuts releases, holds the standards | A maintainer invites them (see below) | + +Everyone contributing is bound by the +[Code of Conduct](CODE_OF_CONDUCT.md) and asked to follow the +[code-review standard](docs/standards/code-review.md). + +## How decisions are made + +- **Routine changes** — bug fixes, documentation and mirror updates, dependency + bumps — are decided in the pull request. A pull request merges once the + required status checks pass, every review conversation is resolved, and a + maintainer has reviewed it. The exact gate is a branch-protection setting, not + this document. While the project has one maintainer, that maintainer merges + their own pull requests under the same conditions, which is what + [docs/standards/code-review.md](docs/standards/code-review.md) records. +- **Non-trivial changes** — a new config field, a new module, a new optional + dependency, a change to an exit code, the audit-event vocabulary or a + documented guarantee — start as an issue. Work that has no issue may be asked + to open one first, so the design is agreed before code is written. If the + change is larger than one issue, it becomes a roadmap phase under + [docs/roadmap/](docs/roadmap/). +- **Breaking changes** to the YAML schema or the CLI contract require a + deprecation period. Removing a YAML field is a MAJOR change; the release a + removal lands in is named by a single constant, + `DEPRECATION_REMOVAL_VERSION` in `forgelm/config.py`, and the + [release standard](docs/standards/release.md) governs the sequence. +- **Disagreement** is resolved by discussion on the issue. If a consensus does + not form, the maintainer decides and records the reasoning in the issue and, + when it sets a lasting constraint, in + [docs/roadmap/risks-and-decisions.md](docs/roadmap/risks-and-decisions.md). +- **Security reports** do not follow this path. They go through a private + advisory as [SECURITY.md](SECURITY.md) describes. + +## Becoming a maintainer + +There is no exam and no vote — and no promise. A maintainer role is offered when +the project needs a second maintainer and someone has earned it, not +automatically after a number of merged pull requests. The invitation is normally +made when all of the following hold: + +- they have landed a handful of pull requests across more than one area; +- they have reviewed other people's pull requests with useful, specific + feedback, and respected the standards while doing so; +- they have shown judgement on the edge cases — an error path, a documented + contract, a supply-chain or compliance question; +- they are willing to be reachable, and to triage issues and review pull + requests on a predictable cadence rather than only when they feel like it. + +Area ownership follows the same idea: `MAINTAINERS.md` names who owns which area, +and `.github/CODEOWNERS` routes reviews there. The list is kept accurate rather +than flattering, and a maintainer who needs to step back can say so; that is not +held against them. + +## Access and continuity + +GitHub permissions are granted per role, and the smallest permission that does +the job is the default: + +| Role | Repository permission | Why | +|---|---|---| +| Contributor | None — fork and pull request | Every contribution arrives as a pull request, so no repository access is needed | +| Maintainer | *Write* | Can merge to `development`, push branches and manage issues | +| Owner | *Admin* | Also carries branch protection, the tag ruleset and the release workflow's environment approval | + +*Admin* is not the next rung of the contribution ladder — it is the ability to +change the rules the other roles work under, so it stays with the project owner +and moves only on a deliberate decision, with two-factor authentication required +on any account that holds it. + +**Continuity: a known gap, recorded rather than promised.** There is one owner +today, and nothing in the release path is asynchronous — the same single account +holds the tag ruleset and approves the release workflow's `pypi` environment. An +unplanned absence therefore stops merges, releases and security advisories at +once. The fix is a second organisation owner (or an organisation-level owner with +the release environment's approval); it is not scheduled, and this paragraph is +what says so. + +## Releases + +Releases are cut from `main` by a maintainer, following +[docs/standards/release.md](docs/standards/release.md). `development` is the +integration branch and the default branch; changes land there first and reach +`main` through a release pull request. Only maintainers merge to `main` and tag +releases. + +## Changing this document + +This document is a normal file in the repository: propose a change with a pull +request, and explain the reasoning in the pull request description. diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 00000000..e9d6171d --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,47 @@ +# ForgeLM Maintainers + +> **Who maintains ForgeLM, what each of them owns, and how to reach them.** +> See [GOVERNANCE.md](GOVERNANCE.md) for how the roles work and how to take one +> on, and [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute. + +## Current maintainers + +| GitHub | Areas | Reachable for | +|---|---|---| +| [@cemililik](https://github.com/cemililik) | All areas — the repository's `area:` label set (cli, compliance, config, data-audit, dev-tooling, eval-gates, gdpr-approval, ingestion, integrations, model-data, onboarding, operator-cmds, packaging-ci, pipeline, post-training, site, trainer, verify) | Triage, design questions, reviews, releases | + +The project has a single maintainer today. When a co-maintainer takes +responsibility for an area, they are added here and to +[`.github/CODEOWNERS`](.github/CODEOWNERS), which routes reviews for that area to +them. The "Areas" column mirrors the repository's `area:` labels, so an issue +labelled `area: site` and this table name the same area. + +## How to reach a maintainer + +- **A bug or a feature request** — open an + [issue](https://github.com/HodeTech/ForgeLM/issues/new/choose). +- **A question** — start a + [discussion](https://github.com/HodeTech/ForgeLM/discussions); questions are not + filed on the public issue tracker. Asking in public means the answer is + searchable for the next person. +- **A security vulnerability** — never in public. Use the repository's + **Security** tab, as [SECURITY.md](SECURITY.md) describes. +- **Anything covered by the Code of Conduct** — follow the enforcement section of + the [Code of Conduct](CODE_OF_CONDUCT.md#enforcement). + +## Review cadence + +Issues are triaged and pull requests are reviewed on a best-effort basis, with an +initial-reply target of **72 hours** — the same window [SECURITY.md](SECURITY.md) +commits to for a vulnerability report, so the project has one number rather than +two. An unanswered first pull request is the most expensive thing a young project +can ship, so a **first-time contributor**'s pull request is answered first. If a +pull request has not been reviewed for a while, a polite nudge on it is welcome; +it is not rude. + +## Contributors + +This file names maintainers and area owners only; it is not the contributor list. +Everyone whose work is merged appears in the repository's commit history and on +its GitHub contributors page, and non-maintainer contributions are credited in +the release notes. diff --git a/MANIFEST.in b/MANIFEST.in index f1409d9a..1280670a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include LICENSE include README.md include CHANGELOG.md +include CITATION.cff include config_template.yaml recursive-include configs *.json *.jsonl diff --git a/README.md b/README.md index 19e1b35d..fe9b3584 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,11 @@ feature. Start small; you do not need a GPU to run the unit tests or documentati [Engineering standards](https://github.com/HodeTech/ForgeLM/tree/development/docs/standards). Report vulnerabilities privately using [SECURITY.md](https://github.com/HodeTech/ForgeLM/blob/main/SECURITY.md). +Who decides what, and who owns which area: [GOVERNANCE.md](https://github.com/HodeTech/ForgeLM/blob/development/GOVERNANCE.md) · +[MAINTAINERS.md](https://github.com/HodeTech/ForgeLM/blob/development/MAINTAINERS.md). +To cite ForgeLM, use GitHub's **Cite this repository** button or +[`CITATION.cff`](https://github.com/HodeTech/ForgeLM/blob/development/CITATION.cff). + ## License **Code:** [Apache 2.0](https://github.com/HodeTech/ForgeLM/blob/main/LICENSE). diff --git a/tests/test_check_readme_links.py b/tests/test_check_readme_links.py index 7b9b822d..029963de 100644 --- a/tests/test_check_readme_links.py +++ b/tests/test_check_readme_links.py @@ -59,6 +59,10 @@ def _make_tree(tmp_path: Path, readme: str, contributing: str = "# Contributing\ """Write a minimal repo-shaped tree and return its root.""" (tmp_path / "README.md").write_text(readme, encoding="utf-8") (tmp_path / "CONTRIBUTING.md").write_text(contributing, encoding="utf-8") + # Every surface in `guard._SURFACES` must exist, or the guard reports it as + # missing from the checkout. Keep this in step with that tuple. + for surface in ("CODE_OF_CONDUCT.md", "GOVERNANCE.md", "MAINTAINERS.md", "SECURITY.md"): + (tmp_path / surface).write_text("# stub\n", encoding="utf-8") (tmp_path / "docs" / "guides").mkdir(parents=True, exist_ok=True) (tmp_path / "docs" / "guides" / "real.md").write_text("# real\n", encoding="utf-8") (tmp_path / "LICENSE").write_text("Apache\n", encoding="utf-8") diff --git a/tools/check_readme_links.py b/tools/check_readme_links.py index 1fed1b31..555bb151 100644 --- a/tools/check_readme_links.py +++ b/tools/check_readme_links.py @@ -85,10 +85,21 @@ # ``CONTRIBUTING.md`` is invisible to CI without this entry. _SURFACES: Tuple[Tuple[str, bool], ...] = ( ("README.md", True), + ("CODE_OF_CONDUCT.md", False), ("CONTRIBUTING.md", False), + ("GOVERNANCE.md", False), + ("MAINTAINERS.md", False), + ("SECURITY.md", False), ) -_REPO_BLOB_PREFIX_RE = re.compile(r"^https://github\.com/HodeTech/ForgeLM/(?:blob|tree)/main/(?P[^#?]+)") +# README points at in-repo targets with an absolute `blob/main/…` or +# `blob/development/…` URL. Both are stripped back to a repo-relative path and +# resolved on disk: a file added by an unreleased pull request is linked on +# `development` until the release that puts it on `main`, and a typo in either +# form should still fail. +_REPO_BLOB_PREFIX_RE = re.compile( + r"^https://github\.com/HodeTech/ForgeLM/(?:blob|tree)/(?:main|development)/(?P[^#?]+)" +) # Markdown inline links and images: ``[text](href)`` / ``![alt](href)``. # The href stops at whitespace or the closing paren so that titles diff --git a/tools/check_source_path_refs.py b/tools/check_source_path_refs.py index 4f9094d0..83db5b70 100644 --- a/tools/check_source_path_refs.py +++ b/tools/check_source_path_refs.py @@ -306,7 +306,18 @@ } # Files scanned outside the directory globs below. -_TOP_LEVEL_SURFACES: frozenset[str] = frozenset({"CLAUDE.md", "AGENTS.md", "CONTRIBUTING.md", "README.md"}) +_TOP_LEVEL_SURFACES: frozenset[str] = frozenset( + { + "AGENTS.md", + "CLAUDE.md", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", + "GOVERNANCE.md", + "MAINTAINERS.md", + "README.md", + "SECURITY.md", + } +) @dataclass(frozen=True)